Commit 0552a8b11f
Changed files (5)
src
src/codegen/spirv/Assembler.zig
@@ -11,7 +11,8 @@ const IdRef = spec.IdRef;
const IdResult = spec.IdResult;
const SpvModule = @import("Module.zig");
-const SpvType = @import("type.zig").Type;
+const CacheRef = SpvModule.CacheRef;
+const CacheKey = SpvModule.CacheKey;
/// Represents a token in the assembly template.
const Token = struct {
@@ -126,7 +127,7 @@ const AsmValue = union(enum) {
value: IdRef,
/// This result-value represents a type registered into the module's type system.
- ty: SpvType.Ref,
+ ty: CacheRef,
/// Retrieve the result-id of this AsmValue. Asserts that this AsmValue
/// is of a variant that allows the result to be obtained (not an unresolved
@@ -135,7 +136,7 @@ const AsmValue = union(enum) {
return switch (self) {
.just_declared, .unresolved_forward_reference => unreachable,
.value => |result| result,
- .ty => |ref| spv.typeId(ref),
+ .ty => |ref| spv.resultId(ref),
};
}
};
@@ -267,9 +268,9 @@ fn processInstruction(self: *Assembler) !void {
/// refers to the result.
fn processTypeInstruction(self: *Assembler) !AsmValue {
const operands = self.inst.operands.items;
- const ty = switch (self.inst.opcode) {
- .OpTypeVoid => SpvType.initTag(.void),
- .OpTypeBool => SpvType.initTag(.bool),
+ const ref = switch (self.inst.opcode) {
+ .OpTypeVoid => try self.spv.resolve(.void_type),
+ .OpTypeBool => try self.spv.resolve(.bool_type),
.OpTypeInt => blk: {
const signedness: std.builtin.Signedness = switch (operands[2].literal32) {
0 => .unsigned,
@@ -282,7 +283,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
const width = std.math.cast(u16, operands[1].literal32) orelse {
return self.fail(0, "int type of {} bits is too large", .{operands[1].literal32});
};
- break :blk try SpvType.int(self.spv.arena, signedness, width);
+ break :blk try self.spv.intType(signedness, width);
},
.OpTypeFloat => blk: {
const bits = operands[1].literal32;
@@ -292,136 +293,36 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
return self.fail(0, "{} is not a valid bit count for floats (expected 16, 32 or 64)", .{bits});
},
}
- break :blk SpvType.float(@intCast(u16, bits));
- },
- .OpTypeVector => blk: {
- const payload = try self.spv.arena.create(SpvType.Payload.Vector);
- payload.* = .{
- .component_type = try self.resolveTypeRef(operands[1].ref_id),
- .component_count = operands[2].literal32,
- };
- break :blk SpvType.initPayload(&payload.base);
- },
- .OpTypeMatrix => blk: {
- const payload = try self.spv.arena.create(SpvType.Payload.Matrix);
- payload.* = .{
- .column_type = try self.resolveTypeRef(operands[1].ref_id),
- .column_count = operands[2].literal32,
- };
- break :blk SpvType.initPayload(&payload.base);
- },
- .OpTypeImage => blk: {
- const payload = try self.spv.arena.create(SpvType.Payload.Image);
- payload.* = .{
- .sampled_type = try self.resolveTypeRef(operands[1].ref_id),
- .dim = @intToEnum(spec.Dim, operands[2].value),
- .depth = switch (operands[3].literal32) {
- 0 => .no,
- 1 => .yes,
- 2 => .maybe,
- else => {
- return self.fail(0, "'{}' is not a valid image depth (expected 0, 1 or 2)", .{operands[3].literal32});
- },
- },
- .arrayed = switch (operands[4].literal32) {
- 0 => false,
- 1 => true,
- else => {
- return self.fail(0, "'{}' is not a valid image arrayed-ness (expected 0 or 1)", .{operands[4].literal32});
- },
- },
- .multisampled = switch (operands[5].literal32) {
- 0 => false,
- 1 => true,
- else => {
- return self.fail(0, "'{}' is not a valid image multisampled-ness (expected 0 or 1)", .{operands[5].literal32});
- },
- },
- .sampled = switch (operands[6].literal32) {
- 0 => .known_at_runtime,
- 1 => .with_sampler,
- 2 => .without_sampler,
- else => {
- return self.fail(0, "'{}' is not a valid image sampled-ness (expected 0, 1 or 2)", .{operands[6].literal32});
- },
- },
- .format = @intToEnum(spec.ImageFormat, operands[7].value),
- .access_qualifier = if (operands.len > 8)
- @intToEnum(spec.AccessQualifier, operands[8].value)
- else
- null,
- };
- break :blk SpvType.initPayload(&payload.base);
- },
- .OpTypeSampler => SpvType.initTag(.sampler),
- .OpTypeSampledImage => blk: {
- const payload = try self.spv.arena.create(SpvType.Payload.SampledImage);
- payload.* = .{
- .image_type = try self.resolveTypeRef(operands[1].ref_id),
- };
- break :blk SpvType.initPayload(&payload.base);
+ break :blk try self.spv.resolve(.{ .float_type = .{ .bits = @intCast(u16, bits) } });
},
+ .OpTypeVector => try self.spv.resolve(.{ .vector_type = .{
+ .component_type = try self.resolveTypeRef(operands[1].ref_id),
+ .component_count = operands[2].literal32,
+ } }),
.OpTypeArray => {
// TODO: The length of an OpTypeArray is determined by a constant (which may be a spec constant),
// and so some consideration must be taken when entering this in the type system.
return self.todo("process OpTypeArray", .{});
},
- .OpTypeRuntimeArray => blk: {
- const payload = try self.spv.arena.create(SpvType.Payload.RuntimeArray);
- payload.* = .{
- .element_type = try self.resolveTypeRef(operands[1].ref_id),
- // TODO: Fetch array stride from decorations.
- .array_stride = 0,
- };
- break :blk SpvType.initPayload(&payload.base);
- },
- .OpTypeOpaque => blk: {
- const payload = try self.spv.arena.create(SpvType.Payload.Opaque);
- const name_offset = operands[1].string;
- payload.* = .{
- .name = std.mem.sliceTo(self.inst.string_bytes.items[name_offset..], 0),
- };
- break :blk SpvType.initPayload(&payload.base);
- },
- .OpTypePointer => blk: {
- const payload = try self.spv.arena.create(SpvType.Payload.Pointer);
- payload.* = .{
- .storage_class = @intToEnum(spec.StorageClass, operands[1].value),
- .child_type = try self.resolveTypeRef(operands[2].ref_id),
- // TODO: Fetch decorations
- };
- break :blk SpvType.initPayload(&payload.base);
- },
+ .OpTypePointer => try self.spv.ptrType(
+ try self.resolveTypeRef(operands[2].ref_id),
+ @intToEnum(spec.StorageClass, operands[1].value),
+ ),
.OpTypeFunction => blk: {
const param_operands = operands[2..];
- const param_types = try self.spv.arena.alloc(SpvType.Ref, param_operands.len);
+ const param_types = try self.spv.gpa.alloc(CacheRef, param_operands.len);
+ defer self.spv.gpa.free(param_types);
for (param_types, 0..) |*param, i| {
param.* = try self.resolveTypeRef(param_operands[i].ref_id);
}
- const payload = try self.spv.arena.create(SpvType.Payload.Function);
- payload.* = .{
+ break :blk try self.spv.resolve(.{ .function_type = .{
.return_type = try self.resolveTypeRef(operands[1].ref_id),
.parameters = param_types,
- };
- break :blk SpvType.initPayload(&payload.base);
+ } });
},
- .OpTypeEvent => SpvType.initTag(.event),
- .OpTypeDeviceEvent => SpvType.initTag(.device_event),
- .OpTypeReserveId => SpvType.initTag(.reserve_id),
- .OpTypeQueue => SpvType.initTag(.queue),
- .OpTypePipe => blk: {
- const payload = try self.spv.arena.create(SpvType.Payload.Pipe);
- payload.* = .{
- .qualifier = @intToEnum(spec.AccessQualifier, operands[1].value),
- };
- break :blk SpvType.initPayload(&payload.base);
- },
- .OpTypePipeStorage => SpvType.initTag(.pipe_storage),
- .OpTypeNamedBarrier => SpvType.initTag(.named_barrier),
else => return self.todo("process type instruction {s}", .{@tagName(self.inst.opcode)}),
};
- const ref = try self.spv.resolveType(ty);
return AsmValue{ .ty = ref };
}
@@ -528,7 +429,7 @@ fn resolveRef(self: *Assembler, ref: AsmValue.Ref) !AsmValue {
}
/// Resolve a value reference as type.
-fn resolveTypeRef(self: *Assembler, ref: AsmValue.Ref) !SpvType.Ref {
+fn resolveTypeRef(self: *Assembler, ref: AsmValue.Ref) !CacheRef {
const value = try self.resolveRef(ref);
switch (value) {
.just_declared, .unresolved_forward_reference => unreachable,
@@ -761,19 +662,20 @@ fn parseContextDependentNumber(self: *Assembler) !void {
const tok = self.currentToken();
const result_type_ref = try self.resolveTypeRef(self.inst.operands.items[0].ref_id);
- const result_type = self.spv.type_cache.keys()[@enumToInt(result_type_ref)];
- if (result_type.isInt()) {
- try self.parseContextDependentInt(result_type.intSignedness(), result_type.intFloatBits());
- } else if (result_type.isFloat()) {
- const width = result_type.intFloatBits();
- switch (width) {
- 16 => try self.parseContextDependentFloat(16),
- 32 => try self.parseContextDependentFloat(32),
- 64 => try self.parseContextDependentFloat(64),
- else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{width}),
- }
- } else {
- return self.fail(tok.start, "cannot parse literal constant {s}", .{@tagName(result_type.tag())});
+ const result_type = self.spv.cache.lookup(result_type_ref);
+ switch (result_type) {
+ .int_type => |int| {
+ try self.parseContextDependentInt(int.signedness, int.bits);
+ },
+ .float_type => |float| {
+ switch (float.bits) {
+ 16 => try self.parseContextDependentFloat(16),
+ 32 => try self.parseContextDependentFloat(32),
+ 64 => try self.parseContextDependentFloat(64),
+ else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{float.bits}),
+ }
+ },
+ else => return self.fail(tok.start, "cannot parse literal constant", .{}),
}
}
src/codegen/spirv/Module.zig
@@ -20,12 +20,13 @@ const IdResult = spec.IdResult;
const IdResultType = spec.IdResultType;
const Section = @import("Section.zig");
-const Type = @import("type.zig").Type;
-pub const TypeConstantCache = @import("TypeConstantCache.zig");
-const TypeCache = std.ArrayHashMapUnmanaged(Type, IdResultType, Type.ShallowHashContext32, true);
+const Cache = @import("TypeConstantCache.zig");
+pub const CacheKey = Cache.Key;
+pub const CacheRef = Cache.Ref;
+pub const CacheString = Cache.String;
-/// This structure represents a function that is in-progress of being emitted.
+/// This structure represents a function that isc in-progress of being emitted.
/// Commonly, the contents of this structure will be merged with the appropriate
/// sections of the module and re-used. Note that the SPIR-V module system makes
/// no attempt of compacting result-id's, so any Fn instance should ultimately
@@ -130,7 +131,7 @@ sections: struct {
/// From this section, OpLine and OpNoLine is allowed.
/// According to the SPIR-V documentation, this section normally
/// also holds type and constant instructions. These are managed
- /// via the tc_cache instead, which is the sole structure that
+ /// via the cache instead, which is the sole structure that
/// manages that section. These will be inserted between this and
/// the previous section when emitting the final binary.
/// TODO: Do we need this section? Globals are also managed with another mechanism.
@@ -152,10 +153,9 @@ next_result_id: Word,
/// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource.
source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},
-type_cache: TypeCache = .{},
/// SPIR-V type- and constant cache. This structure is used to store information about these in a more
/// efficient manner.
-tc_cache: TypeConstantCache = .{},
+cache: Cache = .{},
/// Set of Decls, referred to by Decl.Index.
decls: std.ArrayListUnmanaged(Decl) = .{},
@@ -196,7 +196,7 @@ pub fn deinit(self: *Module) void {
self.sections.functions.deinit(self.gpa);
self.source_file_names.deinit(self.gpa);
- self.tc_cache.deinit(self);
+ self.cache.deinit(self);
self.decls.deinit(self.gpa);
self.decl_deps.deinit(self.gpa);
@@ -223,20 +223,20 @@ pub fn idBound(self: Module) Word {
return self.next_result_id;
}
-pub fn resolve(self: *Module, key: TypeConstantCache.Key) !TypeConstantCache.Ref {
- return self.tc_cache.resolve(self, key);
+pub fn resolve(self: *Module, key: CacheKey) !CacheRef {
+ return self.cache.resolve(self, key);
}
-pub fn resultId(self: *Module, ref: TypeConstantCache.Ref) IdResult {
- return self.tc_cache.resultId(ref);
+pub fn resultId(self: *const Module, ref: CacheRef) IdResult {
+ return self.cache.resultId(ref);
}
-pub fn resolveId(self: *Module, key: TypeConstantCache.Key) !IdResult {
+pub fn resolveId(self: *Module, key: CacheKey) !IdResult {
return self.resultId(try self.resolve(key));
}
-pub fn resolveString(self: *Module, str: []const u8) !TypeConstantCache.String {
- return try self.tc_cache.addString(self, str);
+pub fn resolveString(self: *Module, str: []const u8) !CacheString {
+ return try self.cache.addString(self, str);
}
fn orderGlobalsInto(
@@ -350,7 +350,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
var entry_points = try self.entryPoints();
defer entry_points.deinit(self.gpa);
- var types_constants = try self.tc_cache.materialize(self);
+ var types_constants = try self.cache.materialize(self);
defer types_constants.deinit(self.gpa);
// Note: needs to be kept in order according to section 2.3!
@@ -364,6 +364,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
self.sections.debug_names.toWords(),
self.sections.annotations.toWords(),
types_constants.toWords(),
+ self.sections.types_globals_constants.toWords(),
self.sections.globals.toWords(),
globals.toWords(),
self.sections.functions.toWords(),
@@ -416,364 +417,14 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef {
return result.value_ptr.*;
}
-/// Fetch a result-id for a spir-v type. This function deduplicates the type as appropriate,
-/// and returns a cached version if that exists.
-/// Note: This function does not attempt to perform any validation on the type.
-/// The type is emitted in a shallow fashion; any child types should already
-/// be emitted at this point.
-pub fn resolveType(self: *Module, ty: Type) !Type.Ref {
- const result = try self.type_cache.getOrPut(self.gpa, ty);
- const index = @intToEnum(Type.Ref, result.index);
-
- if (!result.found_existing) {
- const ref = try self.emitType(ty);
- self.type_cache.values()[result.index] = ref;
- }
-
- return index;
-}
-
-pub fn resolveTypeId(self: *Module, ty: Type) !IdResultType {
- const ty_ref = try self.resolveType(ty);
- return self.typeId(ty_ref);
-}
-
-pub fn typeRefType(self: Module, ty_ref: Type.Ref) Type {
- return self.type_cache.keys()[@enumToInt(ty_ref)];
-}
-
-/// Get the result-id of a particular type, by reference. Asserts type_ref is valid.
-pub fn typeId(self: Module, ty_ref: Type.Ref) IdResultType {
- return self.type_cache.values()[@enumToInt(ty_ref)];
-}
-
-/// Unconditionally emit a spir-v type into the appropriate section.
-/// Note: If this function is called with a type that is already generated, it may yield an invalid module
-/// as non-pointer non-aggregrate types must me unique!
-/// Note: This function does not attempt to perform any validation on the type.
-/// The type is emitted in a shallow fashion; any child types should already
-/// be emitted at this point.
-pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType {
- const result_id = self.allocId();
- const ref_id = result_id;
- const types = &self.sections.types_globals_constants;
- const debug_names = &self.sections.debug_names;
- const result_id_operand = .{ .id_result = result_id };
-
- switch (ty.tag()) {
- .void => {
- try types.emit(self.gpa, .OpTypeVoid, result_id_operand);
- try debug_names.emit(self.gpa, .OpName, .{
- .target = result_id,
- .name = "void",
- });
- },
- .bool => {
- try types.emit(self.gpa, .OpTypeBool, result_id_operand);
- try debug_names.emit(self.gpa, .OpName, .{
- .target = result_id,
- .name = "bool",
- });
- },
- .u8,
- .u16,
- .u32,
- .u64,
- .i8,
- .i16,
- .i32,
- .i64,
- .int,
- => {
- // TODO: Kernels do not support OpTypeInt that is signed. We can probably
- // can get rid of the signedness all together, in Shaders also.
- const bits = ty.intFloatBits();
- const signedness: spec.LiteralInteger = switch (ty.intSignedness()) {
- .unsigned => 0,
- .signed => 1,
- };
-
- try types.emit(self.gpa, .OpTypeInt, .{
- .id_result = result_id,
- .width = bits,
- .signedness = signedness,
- });
-
- const ui: []const u8 = switch (signedness) {
- 0 => "u",
- 1 => "i",
- else => unreachable,
- };
- const name = try std.fmt.allocPrint(self.gpa, "{s}{}", .{ ui, bits });
- defer self.gpa.free(name);
-
- try debug_names.emit(self.gpa, .OpName, .{
- .target = result_id,
- .name = name,
- });
- },
- .f16, .f32, .f64 => {
- const bits = ty.intFloatBits();
- try types.emit(self.gpa, .OpTypeFloat, .{
- .id_result = result_id,
- .width = bits,
- });
-
- const name = try std.fmt.allocPrint(self.gpa, "f{}", .{bits});
- defer self.gpa.free(name);
- try debug_names.emit(self.gpa, .OpName, .{
- .target = result_id,
- .name = name,
- });
- },
- .vector => try types.emit(self.gpa, .OpTypeVector, .{
- .id_result = result_id,
- .component_type = self.typeId(ty.childType()),
- .component_count = ty.payload(.vector).component_count,
- }),
- .matrix => try types.emit(self.gpa, .OpTypeMatrix, .{
- .id_result = result_id,
- .column_type = self.typeId(ty.childType()),
- .column_count = ty.payload(.matrix).column_count,
- }),
- .image => {
- const info = ty.payload(.image);
- try types.emit(self.gpa, .OpTypeImage, .{
- .id_result = result_id,
- .sampled_type = self.typeId(ty.childType()),
- .dim = info.dim,
- .depth = @enumToInt(info.depth),
- .arrayed = @boolToInt(info.arrayed),
- .ms = @boolToInt(info.multisampled),
- .sampled = @enumToInt(info.sampled),
- .image_format = info.format,
- .access_qualifier = info.access_qualifier,
- });
- },
- .sampler => try types.emit(self.gpa, .OpTypeSampler, result_id_operand),
- .sampled_image => try types.emit(self.gpa, .OpTypeSampledImage, .{
- .id_result = result_id,
- .image_type = self.typeId(ty.childType()),
- }),
- .array => {
- const info = ty.payload(.array);
- assert(info.length != 0);
-
- const size_type = Type.initTag(.u32);
- const size_type_id = try self.resolveTypeId(size_type);
- const length_id = self.allocId();
- try self.emitConstant(size_type_id, length_id, .{ .uint32 = info.length });
-
- try types.emit(self.gpa, .OpTypeArray, .{
- .id_result = result_id,
- .element_type = self.typeId(ty.childType()),
- .length = length_id,
- });
- if (info.array_stride != 0) {
- try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
- }
- },
- .runtime_array => {
- const info = ty.payload(.runtime_array);
- try types.emit(self.gpa, .OpTypeRuntimeArray, .{
- .id_result = result_id,
- .element_type = self.typeId(ty.childType()),
- });
- if (info.array_stride != 0) {
- try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
- }
- },
- .@"struct" => {
- const info = ty.payload(.@"struct");
- try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len);
- types.writeOperand(IdResult, result_id);
- for (info.members) |member| {
- types.writeOperand(IdRef, self.typeId(member.ty));
- }
- try self.decorateStruct(ref_id, info);
- },
- .@"opaque" => try types.emit(self.gpa, .OpTypeOpaque, .{
- .id_result = result_id,
- .literal_string = ty.payload(.@"opaque").name,
- }),
- .pointer => {
- const info = ty.payload(.pointer);
- try types.emit(self.gpa, .OpTypePointer, .{
- .id_result = result_id,
- .storage_class = info.storage_class,
- .type = self.typeId(ty.childType()),
- });
- if (info.array_stride != 0) {
- try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
- }
- if (info.alignment != 0) {
- try self.decorate(ref_id, .{ .Alignment = .{ .alignment = info.alignment } });
- }
- if (info.max_byte_offset) |max_byte_offset| {
- try self.decorate(ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } });
- }
- },
- .function => {
- const info = ty.payload(.function);
- try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len);
- types.writeOperand(IdResult, result_id);
- types.writeOperand(IdRef, self.typeId(info.return_type));
- for (info.parameters) |parameter_type| {
- types.writeOperand(IdRef, self.typeId(parameter_type));
- }
- },
- .event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand),
- .device_event => try types.emit(self.gpa, .OpTypeDeviceEvent, result_id_operand),
- .reserve_id => try types.emit(self.gpa, .OpTypeReserveId, result_id_operand),
- .queue => try types.emit(self.gpa, .OpTypeQueue, result_id_operand),
- .pipe => try types.emit(self.gpa, .OpTypePipe, .{
- .id_result = result_id,
- .qualifier = ty.payload(.pipe).qualifier,
- }),
- .pipe_storage => try types.emit(self.gpa, .OpTypePipeStorage, result_id_operand),
- .named_barrier => try types.emit(self.gpa, .OpTypeNamedBarrier, result_id_operand),
- }
-
- return result_id;
-}
-
-fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void {
- const debug_names = &self.sections.debug_names;
-
- if (info.name.len != 0) {
- try debug_names.emit(self.gpa, .OpName, .{
- .target = target,
- .name = info.name,
- });
- }
-
- // Decorations for the struct type itself.
- if (info.decorations.block)
- try self.decorate(target, .Block);
- if (info.decorations.buffer_block)
- try self.decorate(target, .BufferBlock);
- if (info.decorations.glsl_shared)
- try self.decorate(target, .GLSLShared);
- if (info.decorations.glsl_packed)
- try self.decorate(target, .GLSLPacked);
- if (info.decorations.c_packed)
- try self.decorate(target, .CPacked);
-
- // Decorations for the struct members.
- const extra = info.member_decoration_extra;
- var extra_i: u32 = 0;
- for (info.members, 0..) |member, i| {
- const d = member.decorations;
- const index = @intCast(Word, i);
-
- if (member.name.len != 0) {
- try debug_names.emit(self.gpa, .OpMemberName, .{
- .type = target,
- .member = index,
- .name = member.name,
- });
- }
-
- switch (member.offset) {
- .none => {},
- else => try self.decorateMember(
- target,
- index,
- .{ .Offset = .{ .byte_offset = @enumToInt(member.offset) } },
- ),
- }
-
- switch (d.matrix_layout) {
- .row_major => try self.decorateMember(target, index, .RowMajor),
- .col_major => try self.decorateMember(target, index, .ColMajor),
- .none => {},
- }
- if (d.matrix_layout != .none) {
- try self.decorateMember(target, index, .{
- .MatrixStride = .{ .matrix_stride = extra[extra_i] },
- });
- extra_i += 1;
- }
-
- if (d.no_perspective)
- try self.decorateMember(target, index, .NoPerspective);
- if (d.flat)
- try self.decorateMember(target, index, .Flat);
- if (d.patch)
- try self.decorateMember(target, index, .Patch);
- if (d.centroid)
- try self.decorateMember(target, index, .Centroid);
- if (d.sample)
- try self.decorateMember(target, index, .Sample);
- if (d.invariant)
- try self.decorateMember(target, index, .Invariant);
- if (d.@"volatile")
- try self.decorateMember(target, index, .Volatile);
- if (d.coherent)
- try self.decorateMember(target, index, .Coherent);
- if (d.non_writable)
- try self.decorateMember(target, index, .NonWritable);
- if (d.non_readable)
- try self.decorateMember(target, index, .NonReadable);
-
- if (d.builtin) {
- try self.decorateMember(target, index, .{
- .BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) },
- });
- extra_i += 1;
- }
- if (d.stream) {
- try self.decorateMember(target, index, .{
- .Stream = .{ .stream_number = extra[extra_i] },
- });
- extra_i += 1;
- }
- if (d.location) {
- try self.decorateMember(target, index, .{
- .Location = .{ .location = extra[extra_i] },
- });
- extra_i += 1;
- }
- if (d.component) {
- try self.decorateMember(target, index, .{
- .Component = .{ .component = extra[extra_i] },
- });
- extra_i += 1;
- }
- if (d.xfb_buffer) {
- try self.decorateMember(target, index, .{
- .XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] },
- });
- extra_i += 1;
- }
- if (d.xfb_stride) {
- try self.decorateMember(target, index, .{
- .XfbStride = .{ .xfb_stride = extra[extra_i] },
- });
- extra_i += 1;
- }
- if (d.user_semantic) {
- const len = extra[extra_i];
- extra_i += 1;
- const semantic = @ptrCast([*]const u8, &extra[extra_i])[0..len];
- try self.decorateMember(target, index, .{
- .UserSemantic = .{ .semantic = semantic },
- });
- extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable;
- }
- }
-}
-
-pub fn simpleStructType(self: *Module, members: []const Type.Payload.Struct.Member) !Type.Ref {
- const payload = try self.arena.create(Type.Payload.Struct);
- payload.* = .{
- .members = try self.arena.dupe(Type.Payload.Struct.Member, members),
- .decorations = .{},
- };
- return try self.resolveType(Type.initPayload(&payload.base));
+pub fn intType(self: *Module, signedness: std.builtin.Signedness, bits: u16) !CacheRef {
+ return try self.resolve(.{ .int_type = .{
+ .signedness = signedness,
+ .bits = bits,
+ } });
}
-pub fn arrayType2(self: *Module, len: u32, elem_ty_ref: TypeConstantCache.Ref) !TypeConstantCache.Ref {
+pub fn arrayType(self: *Module, len: u32, elem_ty_ref: CacheRef) !CacheRef {
const len_ty_ref = try self.resolve(.{ .int_type = .{
.signedness = .unsigned,
.bits = 32,
@@ -788,41 +439,45 @@ pub fn arrayType2(self: *Module, len: u32, elem_ty_ref: TypeConstantCache.Ref) !
} });
}
-pub fn arrayType(self: *Module, len: u32, ty: Type.Ref) !Type.Ref {
- const payload = try self.arena.create(Type.Payload.Array);
- payload.* = .{
- .element_type = ty,
- .length = len,
- };
- return try self.resolveType(Type.initPayload(&payload.base));
-}
-
pub fn ptrType(
self: *Module,
- child: Type.Ref,
+ child: CacheRef,
storage_class: spec.StorageClass,
- alignment: u32,
-) !Type.Ref {
- const ptr_payload = try self.arena.create(Type.Payload.Pointer);
- ptr_payload.* = .{
+) !CacheRef {
+ return try self.resolve(.{ .ptr_type = .{
.storage_class = storage_class,
.child_type = child,
- .alignment = alignment,
- };
- return try self.resolveType(Type.initPayload(&ptr_payload.base));
+ } });
+}
+
+pub fn constInt(self: *Module, ty_ref: CacheRef, value: anytype) !IdRef {
+ const ty = self.cache.lookup(ty_ref).int_type;
+ const Value = Cache.Key.Int.Value;
+ return try self.resolveId(.{ .int = .{
+ .ty = ty_ref,
+ .value = switch (ty.signedness) {
+ .signed => Value{ .int64 = @intCast(i64, value) },
+ .unsigned => Value{ .uint64 = @intCast(u64, value) },
+ },
+ } });
+}
+
+pub fn constUndef(self: *Module, ty_ref: CacheRef) !IdRef {
+ return try self.resolveId(.{ .undef = .{ .ty = ty_ref } });
+}
+
+pub fn constNull(self: *Module, ty_ref: CacheRef) !IdRef {
+ return try self.resolveId(.{ .null = .{ .ty = ty_ref } });
}
-pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_class: spec.StorageClass) !Type.Ref {
- const payload = try self.arena.create(Type.Payload.Pointer);
- payload.* = self.typeRefType(ptr_ty_ref).payload(.pointer).*;
- payload.storage_class = new_storage_class;
- return try self.resolveType(Type.initPayload(&payload.base));
+pub fn constBool(self: *Module, ty_ref: CacheRef, value: bool) !IdRef {
+ return try self.resolveId(.{ .bool = .{ .ty = ty_ref, .value = value } });
}
-pub fn constComposite(self: *Module, ty_ref: Type.Ref, members: []const IdRef) !IdRef {
+pub fn constComposite(self: *Module, ty_ref: CacheRef, members: []const IdRef) !IdRef {
const result_id = self.allocId();
try self.sections.types_globals_constants.emit(self.gpa, .OpSpecConstantComposite, .{
- .id_result_type = self.typeId(ty_ref),
+ .id_result_type = self.resultId(ty_ref),
.id_result = result_id,
.constituents = members,
});
src/codegen/spirv/type.zig
@@ -1,567 +0,0 @@
-//! This module models a SPIR-V Type. These are distinct from Zig types, with some types
-//! which are not representable by Zig directly.
-
-const std = @import("std");
-const assert = std.debug.assert;
-const Signedness = std.builtin.Signedness;
-const Allocator = std.mem.Allocator;
-
-const spec = @import("spec.zig");
-
-pub const Type = extern union {
- tag_if_small_enough: Tag,
- ptr_otherwise: *Payload,
-
- /// A reference to another SPIR-V type.
- pub const Ref = enum(u32) { _ };
-
- pub fn initTag(comptime small_tag: Tag) Type {
- comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);
- return .{ .tag_if_small_enough = small_tag };
- }
-
- pub fn initPayload(pl: *Payload) Type {
- assert(@enumToInt(pl.tag) >= Tag.no_payload_count);
- return .{ .ptr_otherwise = pl };
- }
-
- pub fn int(arena: Allocator, signedness: Signedness, bits: u16) !Type {
- const bits_and_signedness = switch (signedness) {
- .signed => -@as(i32, bits),
- .unsigned => @as(i32, bits),
- };
-
- return switch (bits_and_signedness) {
- 8 => initTag(.u8),
- 16 => initTag(.u16),
- 32 => initTag(.u32),
- 64 => initTag(.u64),
- -8 => initTag(.i8),
- -16 => initTag(.i16),
- -32 => initTag(.i32),
- -64 => initTag(.i64),
- else => {
- const int_payload = try arena.create(Payload.Int);
- int_payload.* = .{
- .width = bits,
- .signedness = signedness,
- };
- return initPayload(&int_payload.base);
- },
- };
- }
-
- pub fn float(bits: u16) Type {
- return switch (bits) {
- 16 => initTag(.f16),
- 32 => initTag(.f32),
- 64 => initTag(.f64),
- else => unreachable, // Enable more types if required.
- };
- }
-
- pub fn tag(self: Type) Tag {
- if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) {
- return self.tag_if_small_enough;
- } else {
- return self.ptr_otherwise.tag;
- }
- }
-
- pub fn castTag(self: Type, comptime t: Tag) ?*t.Type() {
- if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count)
- return null;
-
- if (self.ptr_otherwise.tag == t)
- return self.payload(t);
-
- return null;
- }
-
- /// Access the payload of a type directly.
- pub fn payload(self: Type, comptime t: Tag) *t.Type() {
- assert(self.tag() == t);
- return @fieldParentPtr(t.Type(), "base", self.ptr_otherwise);
- }
-
- /// Perform a shallow equality test, comparing two types while assuming that any child types
- /// are equal only if their references are equal.
- pub fn eqlShallow(a: Type, b: Type) bool {
- if (a.tag_if_small_enough == b.tag_if_small_enough)
- return true;
-
- const tag_a = a.tag();
- const tag_b = b.tag();
- if (tag_a != tag_b)
- return false;
-
- inline for (@typeInfo(Tag).Enum.fields) |field| {
- const t = @field(Tag, field.name);
- if (t == tag_a) {
- return eqlPayloads(t, a, b);
- }
- }
-
- unreachable;
- }
-
- /// Compare the payload of two compatible tags, given that we already know the tag of both types.
- fn eqlPayloads(comptime t: Tag, a: Type, b: Type) bool {
- switch (t) {
- .void,
- .bool,
- .sampler,
- .event,
- .device_event,
- .reserve_id,
- .queue,
- .pipe_storage,
- .named_barrier,
- .u8,
- .u16,
- .u32,
- .u64,
- .i8,
- .i16,
- .i32,
- .i64,
- .f16,
- .f32,
- .f64,
- => return true,
- .int,
- .vector,
- .matrix,
- .sampled_image,
- .array,
- .runtime_array,
- .@"opaque",
- .pointer,
- .pipe,
- .image,
- => return std.meta.eql(a.payload(t).*, b.payload(t).*),
- .@"struct" => {
- const struct_a = a.payload(.@"struct");
- const struct_b = b.payload(.@"struct");
- if (struct_a.members.len != struct_b.members.len)
- return false;
- for (struct_a.members, 0..) |mem_a, i| {
- if (!std.meta.eql(mem_a, struct_b.members[i]))
- return false;
- }
- return true;
- },
- .function => {
- const fn_a = a.payload(.function);
- const fn_b = b.payload(.function);
- if (fn_a.return_type != fn_b.return_type)
- return false;
- return std.mem.eql(Ref, fn_a.parameters, fn_b.parameters);
- },
- }
- }
-
- /// Perform a shallow hash, which hashes the reference value of child types instead of recursing.
- pub fn hashShallow(self: Type) u64 {
- var hasher = std.hash.Wyhash.init(0);
- const t = self.tag();
- std.hash.autoHash(&hasher, t);
-
- inline for (@typeInfo(Tag).Enum.fields) |field| {
- if (@field(Tag, field.name) == t) {
- switch (@field(Tag, field.name)) {
- .void,
- .bool,
- .sampler,
- .event,
- .device_event,
- .reserve_id,
- .queue,
- .pipe_storage,
- .named_barrier,
- .u8,
- .u16,
- .u32,
- .u64,
- .i8,
- .i16,
- .i32,
- .i64,
- .f16,
- .f32,
- .f64,
- => {},
- else => self.hashPayload(@field(Tag, field.name), &hasher),
- }
- }
- }
-
- return hasher.final();
- }
-
- /// Perform a shallow hash, given that we know the tag of the field ahead of time.
- fn hashPayload(self: Type, comptime t: Tag, hasher: *std.hash.Wyhash) void {
- const fields = @typeInfo(t.Type()).Struct.fields;
- const pl = self.payload(t);
- comptime assert(std.mem.eql(u8, fields[0].name, "base"));
- inline for (fields[1..]) |field| { // Skip the 'base' field.
- std.hash.autoHashStrat(hasher, @field(pl, field.name), .DeepRecursive);
- }
- }
-
- /// Hash context that hashes and compares types in a shallow fashion, useful for type caches.
- pub const ShallowHashContext32 = struct {
- pub fn hash(self: @This(), t: Type) u32 {
- _ = self;
- return @truncate(u32, t.hashShallow());
- }
- pub fn eql(self: @This(), a: Type, b: Type, b_index: usize) bool {
- _ = self;
- _ = b_index;
- return a.eqlShallow(b);
- }
- };
-
- /// Return the reference to any child type. Asserts the type is one of:
- /// - Vectors
- /// - Matrices
- /// - Images
- /// - SampledImages,
- /// - Arrays
- /// - RuntimeArrays
- /// - Pointers
- pub fn childType(self: Type) Ref {
- return switch (self.tag()) {
- .vector => self.payload(.vector).component_type,
- .matrix => self.payload(.matrix).column_type,
- .image => self.payload(.image).sampled_type,
- .sampled_image => self.payload(.sampled_image).image_type,
- .array => self.payload(.array).element_type,
- .runtime_array => self.payload(.runtime_array).element_type,
- .pointer => self.payload(.pointer).child_type,
- else => unreachable,
- };
- }
-
- pub fn isInt(self: Type) bool {
- return switch (self.tag()) {
- .u8,
- .u16,
- .u32,
- .u64,
- .i8,
- .i16,
- .i32,
- .i64,
- .int,
- => true,
- else => false,
- };
- }
-
- pub fn isFloat(self: Type) bool {
- return switch (self.tag()) {
- .f16, .f32, .f64 => true,
- else => false,
- };
- }
-
- /// Returns the number of bits that make up an int or float type.
- /// Asserts type is either int or float.
- pub fn intFloatBits(self: Type) u16 {
- return switch (self.tag()) {
- .u8, .i8 => 8,
- .u16, .i16, .f16 => 16,
- .u32, .i32, .f32 => 32,
- .u64, .i64, .f64 => 64,
- .int => self.payload(.int).width,
- else => unreachable,
- };
- }
-
- /// Returns the signedness of an integer type.
- /// Asserts that the type is an int.
- pub fn intSignedness(self: Type) Signedness {
- return switch (self.tag()) {
- .u8, .u16, .u32, .u64 => .unsigned,
- .i8, .i16, .i32, .i64 => .signed,
- .int => self.payload(.int).signedness,
- else => unreachable,
- };
- }
-
- pub const Tag = enum(usize) {
- void,
- bool,
- sampler,
- event,
- device_event,
- reserve_id,
- queue,
- pipe_storage,
- named_barrier,
- u8,
- u16,
- u32,
- u64,
- i8,
- i16,
- i32,
- i64,
- f16,
- f32,
- f64,
-
- // After this, the tag requires a payload.
- int,
- vector,
- matrix,
- image,
- sampled_image,
- array,
- runtime_array,
- @"struct",
- @"opaque",
- pointer,
- function,
- pipe,
-
- pub const last_no_payload_tag = Tag.f64;
- pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
-
- pub fn Type(comptime t: Tag) type {
- return switch (t) {
- .void,
- .bool,
- .sampler,
- .event,
- .device_event,
- .reserve_id,
- .queue,
- .pipe_storage,
- .named_barrier,
- .u8,
- .u16,
- .u32,
- .u64,
- .i8,
- .i16,
- .i32,
- .i64,
- .f16,
- .f32,
- .f64,
- => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
- .int => Payload.Int,
- .vector => Payload.Vector,
- .matrix => Payload.Matrix,
- .image => Payload.Image,
- .sampled_image => Payload.SampledImage,
- .array => Payload.Array,
- .runtime_array => Payload.RuntimeArray,
- .@"struct" => Payload.Struct,
- .@"opaque" => Payload.Opaque,
- .pointer => Payload.Pointer,
- .function => Payload.Function,
- .pipe => Payload.Pipe,
- };
- }
- };
-
- pub const Payload = struct {
- tag: Tag,
-
- pub const Int = struct {
- base: Payload = .{ .tag = .int },
- width: u16,
- signedness: Signedness,
- };
-
- pub const Vector = struct {
- base: Payload = .{ .tag = .vector },
- component_type: Ref,
- component_count: u32,
- };
-
- pub const Matrix = struct {
- base: Payload = .{ .tag = .matrix },
- column_type: Ref,
- column_count: u32,
- };
-
- pub const Image = struct {
- base: Payload = .{ .tag = .image },
- sampled_type: Ref,
- dim: spec.Dim,
- depth: enum(u2) {
- no = 0,
- yes = 1,
- maybe = 2,
- },
- arrayed: bool,
- multisampled: bool,
- sampled: enum(u2) {
- known_at_runtime = 0,
- with_sampler = 1,
- without_sampler = 2,
- },
- format: spec.ImageFormat,
- access_qualifier: ?spec.AccessQualifier,
- };
-
- pub const SampledImage = struct {
- base: Payload = .{ .tag = .sampled_image },
- image_type: Ref,
- };
-
- pub const Array = struct {
- base: Payload = .{ .tag = .array },
- element_type: Ref,
- /// Note: Must be emitted as constant, not as literal!
- length: u32,
- /// Type has the 'ArrayStride' decoration.
- /// If zero, no stride is present.
- array_stride: u32 = 0,
- };
-
- pub const RuntimeArray = struct {
- base: Payload = .{ .tag = .runtime_array },
- element_type: Ref,
- /// Type has the 'ArrayStride' decoration.
- /// If zero, no stride is present.
- array_stride: u32 = 0,
- };
-
- pub const Struct = struct {
- base: Payload = .{ .tag = .@"struct" },
- members: []Member,
- name: []const u8 = "",
- decorations: StructDecorations = .{},
-
- /// Extra information for decorations, packed for efficiency. Fields are stored sequentially by
- /// order of the `members` slice and `MemberDecorations` struct.
- member_decoration_extra: []u32 = &.{},
-
- pub const Member = struct {
- ty: Ref,
- name: []const u8 = "",
- offset: MemberOffset = .none,
- decorations: MemberDecorations = .{},
- };
-
- pub const MemberOffset = enum(u32) { none = 0xFFFF_FFFF, _ };
-
- pub const StructDecorations = packed struct {
- /// Type has the 'Block' decoration.
- block: bool = false,
- /// Type has the 'BufferBlock' decoration.
- buffer_block: bool = false,
- /// Type has the 'GLSLShared' decoration.
- glsl_shared: bool = false,
- /// Type has the 'GLSLPacked' decoration.
- glsl_packed: bool = false,
- /// Type has the 'CPacked' decoration.
- c_packed: bool = false,
- };
-
- pub const MemberDecorations = packed struct {
- /// Matrix layout for (arrays of) matrices. If this field is not .none,
- /// then there is also an extra field containing the matrix stride corresponding
- /// to the 'MatrixStride' decoration.
- matrix_layout: enum(u2) {
- /// Member has the 'RowMajor' decoration. The member type
- /// must be a matrix or an array of matrices.
- row_major,
- /// Member has the 'ColMajor' decoration. The member type
- /// must be a matrix or an array of matrices.
- col_major,
- /// Member is not a matrix or array of matrices.
- none,
- } = .none,
-
- // Regular decorations, these do not imply extra fields.
-
- /// Member has the 'NoPerspective' decoration.
- no_perspective: bool = false,
- /// Member has the 'Flat' decoration.
- flat: bool = false,
- /// Member has the 'Patch' decoration.
- patch: bool = false,
- /// Member has the 'Centroid' decoration.
- centroid: bool = false,
- /// Member has the 'Sample' decoration.
- sample: bool = false,
- /// Member has the 'Invariant' decoration.
- /// Note: requires parent struct to have 'Block'.
- invariant: bool = false,
- /// Member has the 'Volatile' decoration.
- @"volatile": bool = false,
- /// Member has the 'Coherent' decoration.
- coherent: bool = false,
- /// Member has the 'NonWritable' decoration.
- non_writable: bool = false,
- /// Member has the 'NonReadable' decoration.
- non_readable: bool = false,
-
- // The following decorations all imply extra field(s).
-
- /// Member has the 'BuiltIn' decoration.
- /// This decoration has an extra field of type `spec.BuiltIn`.
- /// Note: If any member of a struct has the BuiltIn decoration, all members must have one.
- /// Note: Each builtin may only be reachable once for a particular entry point.
- /// Note: The member type may be constrained by a particular built-in, defined in the client API specification.
- builtin: bool = false,
- /// Member has the 'Stream' decoration.
- /// This member has an extra field of type `u32`.
- stream: bool = false,
- /// Member has the 'Location' decoration.
- /// This member has an extra field of type `u32`.
- location: bool = false,
- /// Member has the 'Component' decoration.
- /// This member has an extra field of type `u32`.
- component: bool = false,
- /// Member has the 'XfbBuffer' decoration.
- /// This member has an extra field of type `u32`.
- xfb_buffer: bool = false,
- /// Member has the 'XfbStride' decoration.
- /// This member has an extra field of type `u32`.
- xfb_stride: bool = false,
- /// Member has the 'UserSemantic' decoration.
- /// This member has an extra field of type `[]u8`, which is encoded
- /// by an `u32` containing the number of chars exactly, and then the string padded to
- /// a multiple of 4 bytes with zeroes.
- user_semantic: bool = false,
- };
- };
-
- pub const Opaque = struct {
- base: Payload = .{ .tag = .@"opaque" },
- name: []u8,
- };
-
- pub const Pointer = struct {
- base: Payload = .{ .tag = .pointer },
- storage_class: spec.StorageClass,
- child_type: Ref,
- /// Type has the 'ArrayStride' decoration.
- /// This is valid for pointers to elements of an array.
- /// If zero, no stride is present.
- array_stride: u32 = 0,
- /// If nonzero, type has the 'Alignment' decoration.
- alignment: u32 = 0,
- /// Type has the 'MaxByteOffset' decoration.
- max_byte_offset: ?u32 = null,
- };
-
- pub const Function = struct {
- base: Payload = .{ .tag = .function },
- return_type: Ref,
- parameters: []Ref,
- };
-
- pub const Pipe = struct {
- base: Payload = .{ .tag = .pipe },
- qualifier: spec.AccessQualifier,
- };
- };
-};
src/codegen/spirv/TypeConstantCache.zig
@@ -111,6 +111,18 @@ const Tag = enum {
/// Value of type f64
/// data is payload to Float16
float64,
+ /// Undefined value
+ /// data is type
+ undef,
+ /// Null value
+ /// data is type
+ null,
+ /// Bool value that is true
+ /// data is (bool) type
+ bool_true,
+ /// Bool value that is false
+ /// data is (bool) type
+ bool_false,
const SimpleType = enum { void, bool };
@@ -227,6 +239,9 @@ pub const Key = union(enum) {
// -- values
int: Int,
float: Float,
+ undef: Undef,
+ null: Null,
+ bool: Bool,
pub const IntType = std.builtin.Type.Int;
pub const FloatType = std.builtin.Type.Float;
@@ -323,6 +338,19 @@ pub const Key = union(enum) {
};
};
+ pub const Undef = struct {
+ ty: Ref,
+ };
+
+ pub const Null = struct {
+ ty: Ref,
+ };
+
+ pub const Bool = struct {
+ ty: Ref,
+ value: bool,
+ };
+
fn hash(self: Key) u32 {
var hasher = std.hash.Wyhash.init(0);
switch (self) {
@@ -539,6 +567,32 @@ fn emit(
.value = lit,
});
},
+ .undef => |undef| {
+ try section.emit(spv.gpa, .OpUndef, .{
+ .id_result_type = self.resultId(undef.ty),
+ .id_result = result_id,
+ });
+ },
+ .null => |null_info| {
+ try section.emit(spv.gpa, .OpConstantNull, .{
+ .id_result_type = self.resultId(null_info.ty),
+ .id_result = result_id,
+ });
+ },
+ .bool => |bool_info| switch (bool_info.value) {
+ true => {
+ try section.emit(spv.gpa, .OpConstantTrue, .{
+ .id_result_type = self.resultId(bool_info.ty),
+ .id_result = result_id,
+ });
+ },
+ false => {
+ try section.emit(spv.gpa, .OpConstantFalse, .{
+ .id_result_type = self.resultId(bool_info.ty),
+ .id_result = result_id,
+ });
+ },
+ },
}
}
@@ -713,6 +767,24 @@ pub fn resolve(self: *Self, spv: *Module, key: Key) !Ref {
},
else => unreachable,
},
+ .undef => |undef| .{
+ .tag = .undef,
+ .result_id = result_id,
+ .data = @enumToInt(undef.ty),
+ },
+ .null => |null_info| .{
+ .tag = .null,
+ .result_id = result_id,
+ .data = @enumToInt(null_info.ty),
+ },
+ .bool => |bool_info| .{
+ .tag = switch (bool_info.value) {
+ true => Tag.bool_true,
+ false => Tag.bool_false,
+ },
+ .result_id = result_id,
+ .data = @enumToInt(bool_info.ty),
+ },
};
try self.items.append(spv.gpa, item);
@@ -850,6 +922,20 @@ pub fn lookup(self: *const Self, ref: Ref) Key {
.value = .{ .uint64 = payload.decode() },
} };
},
+ .undef => .{ .undef = .{
+ .ty = @intToEnum(Ref, data),
+ } },
+ .null => .{ .null = .{
+ .ty = @intToEnum(Ref, data),
+ } },
+ .bool_true => .{ .bool = .{
+ .ty = @intToEnum(Ref, data),
+ .value = true,
+ } },
+ .bool_false => .{ .bool = .{
+ .ty = @intToEnum(Ref, data),
+ .value = false,
+ } },
};
}
src/codegen/spirv.zig
@@ -22,11 +22,10 @@ const IdResultType = spec.IdResultType;
const StorageClass = spec.StorageClass;
const SpvModule = @import("spirv/Module.zig");
-const SpvCacheRef = SpvModule.TypeConstantCache.Ref;
-const SpvCacheString = SpvModule.TypeConstantCache.String;
+const CacheRef = SpvModule.CacheRef;
+const CacheString = SpvModule.CacheString;
const SpvSection = @import("spirv/Section.zig");
-const SpvType = @import("spirv/type.zig").Type;
const SpvAssembler = @import("spirv/Assembler.zig");
const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
@@ -380,74 +379,23 @@ pub const DeclGen = struct {
};
}
- fn genConstInt(self: *DeclGen, ty_ref: SpvType.Ref, result_id: IdRef, value: anytype) !void {
- const ty = self.spv.typeRefType(ty_ref);
- const ty_id = self.typeId(ty_ref);
-
- const Lit = spec.LiteralContextDependentNumber;
- const literal = switch (ty.intSignedness()) {
- .signed => switch (ty.intFloatBits()) {
- 1...32 => Lit{ .int32 = @intCast(i32, value) },
- 33...64 => Lit{ .int64 = @intCast(i64, value) },
- else => unreachable, // TODO: composite integer literals
- },
- .unsigned => switch (ty.intFloatBits()) {
- 1...32 => Lit{ .uint32 = @intCast(u32, value) },
- 33...64 => Lit{ .uint64 = @intCast(u64, value) },
- else => unreachable,
- },
- };
-
- try self.spv.emitConstant(ty_id, result_id, literal);
- }
-
- fn constInt(self: *DeclGen, ty_ref: SpvType.Ref, value: anytype) !IdRef {
- const result_id = self.spv.allocId();
- try self.genConstInt(ty_ref, result_id, value);
- return result_id;
- }
-
- fn constUndef(self: *DeclGen, ty_ref: SpvType.Ref) !IdRef {
- const result_id = self.spv.allocId();
- try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpUndef, .{
- .id_result_type = self.typeId(ty_ref),
- .id_result = result_id,
- });
- return result_id;
- }
-
- fn constNull(self: *DeclGen, ty_ref: SpvType.Ref) !IdRef {
- const result_id = self.spv.allocId();
- try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantNull, .{
- .id_result_type = self.typeId(ty_ref),
- .id_result = result_id,
- });
- return result_id;
- }
-
+ /// Emits a bool constant in a particular representation.
fn constBool(self: *DeclGen, value: bool, repr: Repr) !IdRef {
switch (repr) {
.indirect => {
const int_ty_ref = try self.intType(.unsigned, 1);
- return self.constInt(int_ty_ref, @boolToInt(value));
+ return self.spv.constInt(int_ty_ref, @boolToInt(value));
},
.direct => {
const bool_ty_ref = try self.resolveType(Type.bool, .direct);
- const result_id = self.spv.allocId();
- const operands = .{ .id_result_type = self.typeId(bool_ty_ref), .id_result = result_id };
- if (value) {
- try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantTrue, operands);
- } else {
- try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantFalse, operands);
- }
- return result_id;
+ return self.spv.constBool(bool_ty_ref, value);
},
}
}
/// Construct a struct at runtime.
/// result_ty_ref must be a struct type.
- fn constructStruct(self: *DeclGen, result_ty_ref: SpvType.Ref, constituents: []const IdRef) !IdRef {
+ fn constructStruct(self: *DeclGen, result_ty_ref: CacheRef, constituents: []const IdRef) !IdRef {
// The Khronos LLVM-SPIRV translator crashes because it cannot construct structs which'
// operands are not constant.
// See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349
@@ -456,11 +404,13 @@ pub const DeclGen = struct {
const ptr_composite_id = try self.alloc(result_ty_ref, null);
// Note: using 32-bit ints here because usize crashes the translator as well
const index_ty_ref = try self.intType(.unsigned, 32);
- const spv_composite_ty = self.spv.typeRefType(result_ty_ref);
- const members = spv_composite_ty.payload(.@"struct").members;
- for (constituents, members, 0..) |constitent_id, member, index| {
- const index_id = try self.constInt(index_ty_ref, index);
- const ptr_member_ty_ref = try self.spv.ptrType(member.ty, .Generic, 0);
+
+ const spv_composite_ty = self.spv.cache.lookup(result_ty_ref).struct_type;
+ const member_types = spv_composite_ty.member_types;
+
+ for (constituents, member_types, 0..) |constitent_id, member_ty_ref, index| {
+ const index_id = try self.spv.constInt(index_ty_ref, index);
+ const ptr_member_ty_ref = try self.spv.ptrType(member_ty_ref, .Generic);
const ptr_id = try self.accessChain(ptr_member_ty_ref, ptr_composite_id, &.{index_id});
try self.func.body.emit(self.spv.gpa, .OpStore, .{
.pointer = ptr_id,
@@ -481,11 +431,11 @@ pub const DeclGen = struct {
dg: *DeclGen,
/// Cached reference of the u32 type.
- u32_ty_ref: SpvType.Ref,
+ u32_ty_ref: CacheRef,
/// Cached type id of the u32 type.
u32_ty_id: IdRef,
/// The members of the resulting structure type
- members: std.ArrayList(SpvType.Payload.Struct.Member),
+ members: std.ArrayList(CacheRef),
/// The initializers of each of the members.
initializers: std.ArrayList(IdRef),
/// The current size of the structure. Includes
@@ -519,7 +469,7 @@ pub const DeclGen = struct {
const result_id = self.dg.spv.allocId();
// TODO: Integrate with caching mechanism
try self.dg.spv.emitConstant(self.u32_ty_id, result_id, .{ .uint32 = word });
- try self.members.append(.{ .ty = self.u32_ty_ref });
+ try self.members.append(self.u32_ty_ref);
try self.initializers.append(result_id);
self.partial_word.len = 0;
@@ -555,7 +505,7 @@ pub const DeclGen = struct {
}
}
- fn addPtr(self: *@This(), ptr_ty_ref: SpvType.Ref, ptr_id: IdRef) !void {
+ fn addPtr(self: *@This(), ptr_ty_ref: CacheRef, ptr_id: IdRef) !void {
// TODO: Double check pointer sizes here.
// shared pointers might be u32...
const target = self.dg.getTarget();
@@ -563,12 +513,12 @@ pub const DeclGen = struct {
if (self.size % width != 0) {
return self.dg.todo("misaligned pointer constants", .{});
}
- try self.members.append(.{ .ty = ptr_ty_ref });
+ try self.members.append(ptr_ty_ref);
try self.initializers.append(ptr_id);
self.size += width;
}
- fn addNullPtr(self: *@This(), ptr_ty_ref: SpvType.Ref) !void {
+ fn addNullPtr(self: *@This(), ptr_ty_ref: CacheRef) !void {
const result_id = self.dg.spv.allocId();
try self.dg.spv.sections.types_globals_constants.emit(self.dg.spv.gpa, .OpConstantNull, .{
.id_result_type = self.dg.typeId(ptr_ty_ref),
@@ -931,7 +881,7 @@ pub const DeclGen = struct {
const section = &self.spv.globals.section;
const ty_ref = try self.resolveType(ty, .indirect);
- const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, 0);
+ const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class);
// const target = self.getTarget();
@@ -960,7 +910,7 @@ pub const DeclGen = struct {
.dg = self,
.u32_ty_ref = u32_ty_ref,
.u32_ty_id = self.typeId(u32_ty_ref),
- .members = std.ArrayList(SpvType.Payload.Struct.Member).init(self.gpa),
+ .members = std.ArrayList(CacheRef).init(self.gpa),
.initializers = std.ArrayList(IdRef).init(self.gpa),
.decl_deps = std.AutoArrayHashMap(SpvModule.Decl.Index, void).init(self.gpa),
};
@@ -972,8 +922,10 @@ pub const DeclGen = struct {
try icl.lower(ty, val);
try icl.flush();
- const constant_struct_ty_ref = try self.spv.simpleStructType(icl.members.items);
- const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class, 0);
+ const constant_struct_ty_ref = try self.spv.resolve(.{ .struct_type = .{
+ .member_types = icl.members.items,
+ } });
+ const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class);
const constant_struct_id = self.spv.allocId();
try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{
@@ -1007,7 +959,7 @@ pub const DeclGen = struct {
});
if (cast_to_generic) {
- const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0);
+ const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic);
try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
.id_result_type = self.typeId(generic_ptr_ty_ref),
.id_result = result_id,
@@ -1044,9 +996,9 @@ pub const DeclGen = struct {
switch (ty.zigTypeTag()) {
.Int => {
if (ty.isSignedInt()) {
- return try self.constInt(result_ty_ref, val.toSignedInt(target));
+ return try self.spv.constInt(result_ty_ref, val.toSignedInt(target));
} else {
- return try self.constInt(result_ty_ref, val.toUnsignedInt(target));
+ return try self.spv.constInt(result_ty_ref, val.toUnsignedInt(target));
}
},
.Bool => switch (repr) {
@@ -1060,7 +1012,7 @@ pub const DeclGen = struct {
}
return result_id;
},
- .indirect => return try self.constInt(result_ty_ref, @boolToInt(val.toBool())),
+ .indirect => return try self.spv.constInt(result_ty_ref, @boolToInt(val.toBool())),
},
.Float => {
const result_id = self.spv.allocId();
@@ -1084,7 +1036,7 @@ pub const DeclGen = struct {
else => unreachable,
};
- return try self.constInt(result_ty_ref, value);
+ return try self.spv.constInt(result_ty_ref, value);
},
.ErrorUnion => {
const payload_ty = ty.errorUnionPayload();
@@ -1143,45 +1095,31 @@ pub const DeclGen = struct {
/// Turn a Zig type into a SPIR-V Type, and return its type result-id.
fn resolveTypeId(self: *DeclGen, ty: Type) !IdResultType {
const type_ref = try self.resolveType(ty, .direct);
- return self.typeId(type_ref);
+ return self.spv.resultId(type_ref);
}
- fn typeId(self: *DeclGen, ty_ref: SpvType.Ref) IdRef {
- return self.spv.typeId(ty_ref);
+ fn typeId(self: *DeclGen, ty_ref: CacheRef) IdRef {
+ return self.spv.resultId(ty_ref);
}
/// Create an integer type suitable for storing at least 'bits' bits.
- fn intType(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvType.Ref {
+ /// The integer type that is returned by this function is the type that is used to perform
+ /// actual operations (as well as store) a Zig type of a particular number of bits. To create
+ /// a type with an exact size, use SpvModule.intType.
+ fn intType(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !CacheRef {
const backing_bits = self.backingIntBits(bits) orelse {
// TODO: Integers too big for any native type are represented as "composite integers":
// An array of largestSupportedIntBits.
return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits });
};
-
- return try self.spv.resolveType(try SpvType.int(self.spv.arena, signedness, backing_bits));
- }
-
- fn intType2(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvCacheRef {
- const backing_bits = self.backingIntBits(bits) orelse {
- // TODO: Integers too big for any native type are represented as "composite integers":
- // An array of largestSupportedIntBits.
- return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits });
- };
- return try self.spv.resolve(.{ .int_type = .{
- .signedness = signedness,
- .bits = backing_bits,
- } });
+ return self.spv.intType(signedness, backing_bits);
}
/// Create an integer type that represents 'usize'.
- fn sizeType(self: *DeclGen) !SpvType.Ref {
+ fn sizeType(self: *DeclGen) !CacheRef {
return try self.intType(.unsigned, self.getTarget().ptrBitWidth());
}
- fn sizeType2(self: *DeclGen) !SpvCacheRef {
- return try self.intType2(.unsigned, self.getTarget().ptrBitWidth());
- }
-
/// Generate a union type, optionally with a known field. If the tag alignment is greater
/// than that of the payload, a regular union (non-packed, with both tag and payload), will
/// be generated as follows:
@@ -1204,7 +1142,7 @@ pub const DeclGen = struct {
/// If any of the fields' size is 0, it will be omitted.
/// NOTE: When the active field is set to something other than the most aligned field, the
/// resulting struct will be *underaligned*.
- fn resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !SpvType.Ref {
+ fn resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !CacheRef {
const target = self.getTarget();
const layout = ty.unionGetLayout(target);
const union_ty = ty.cast(Type.Payload.Union).?.data;
@@ -1218,7 +1156,8 @@ pub const DeclGen = struct {
return try self.resolveType(union_ty.tag_ty, .indirect);
}
- var members = std.BoundedArray(SpvType.Payload.Struct.Member, 4){};
+ var member_types = std.BoundedArray(CacheRef, 4){};
+ var member_names = std.BoundedArray(CacheString, 4){};
const has_tag = layout.tag_size != 0;
const tag_first = layout.tag_align >= layout.payload_align;
@@ -1226,82 +1165,6 @@ pub const DeclGen = struct {
if (has_tag and tag_first) {
const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect);
- members.appendAssumeCapacity(.{ .name = "tag", .ty = tag_ty_ref });
- }
-
- const active_field = maybe_active_field orelse layout.most_aligned_field;
- const active_field_ty = union_ty.fields.values()[active_field].ty;
-
- const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: {
- const active_payload_ty_ref = try self.resolveType(active_field_ty, .indirect);
- members.appendAssumeCapacity(.{ .name = "payload", .ty = active_payload_ty_ref });
- break :blk active_field_ty.abiSize(target);
- } else 0;
-
- const payload_padding_len = layout.payload_size - active_field_size;
- if (payload_padding_len != 0) {
- const payload_padding_ty_ref = try self.spv.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref);
- members.appendAssumeCapacity(.{ .name = "padding_payload", .ty = payload_padding_ty_ref });
- }
-
- if (has_tag and !tag_first) {
- const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect);
- members.appendAssumeCapacity(.{ .name = "tag", .ty = tag_ty_ref });
- }
-
- if (layout.padding != 0) {
- const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref);
- members.appendAssumeCapacity(.{ .name = "padding", .ty = padding_ty_ref });
- }
-
- return try self.spv.simpleStructType(members.slice());
- }
-
- /// Generate a union type, optionally with a known field. If the tag alignment is greater
- /// than that of the payload, a regular union (non-packed, with both tag and payload), will
- /// be generated as follows:
- /// If the active field is known:
- /// struct {
- /// tag: TagType,
- /// payload: ActivePayloadType,
- /// payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8,
- /// padding: [padding_size]u8,
- /// }
- /// If the payload alignment is greater than that of the tag:
- /// struct {
- /// payload: ActivePayloadType,
- /// payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8,
- /// tag: TagType,
- /// padding: [padding_size]u8,
- /// }
- /// If the active payload is unknown, it will default back to the most aligned field. This is
- /// to make sure that the overal struct has the correct alignment in spir-v.
- /// If any of the fields' size is 0, it will be omitted.
- /// NOTE: When the active field is set to something other than the most aligned field, the
- /// resulting struct will be *underaligned*.
- fn resolveUnionType2(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !SpvCacheRef {
- const target = self.getTarget();
- const layout = ty.unionGetLayout(target);
- const union_ty = ty.cast(Type.Payload.Union).?.data;
-
- if (union_ty.layout == .Packed) {
- return self.todo("packed union types", .{});
- }
-
- if (layout.payload_size == 0) {
- // No payload, so represent this as just the tag type.
- return try self.resolveType2(union_ty.tag_ty, .indirect);
- }
-
- var member_types = std.BoundedArray(SpvCacheRef, 4){};
- var member_names = std.BoundedArray(SpvCacheString, 4){};
-
- const has_tag = layout.tag_size != 0;
- const tag_first = layout.tag_align >= layout.payload_align;
- const u8_ty_ref = try self.intType2(.unsigned, 8); // TODO: What if Int8Type is not enabled?
-
- if (has_tag and tag_first) {
- const tag_ty_ref = try self.resolveType2(union_ty.tag_ty, .indirect);
member_types.appendAssumeCapacity(tag_ty_ref);
member_names.appendAssumeCapacity(try self.spv.resolveString("tag"));
}
@@ -1310,7 +1173,7 @@ pub const DeclGen = struct {
const active_field_ty = union_ty.fields.values()[active_field].ty;
const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: {
- const active_payload_ty_ref = try self.resolveType2(active_field_ty, .indirect);
+ const active_payload_ty_ref = try self.resolveType(active_field_ty, .indirect);
member_types.appendAssumeCapacity(active_payload_ty_ref);
member_names.appendAssumeCapacity(try self.spv.resolveString("payload"));
break :blk active_field_ty.abiSize(target);
@@ -1318,19 +1181,19 @@ pub const DeclGen = struct {
const payload_padding_len = layout.payload_size - active_field_size;
if (payload_padding_len != 0) {
- const payload_padding_ty_ref = try self.spv.arrayType2(@intCast(u32, payload_padding_len), u8_ty_ref);
+ const payload_padding_ty_ref = try self.spv.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref);
member_types.appendAssumeCapacity(payload_padding_ty_ref);
member_names.appendAssumeCapacity(try self.spv.resolveString("payload_padding"));
}
if (has_tag and !tag_first) {
- const tag_ty_ref = try self.resolveType2(union_ty.tag_ty, .indirect);
+ const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect);
member_types.appendAssumeCapacity(tag_ty_ref);
member_names.appendAssumeCapacity(try self.spv.resolveString("tag"));
}
if (layout.padding != 0) {
- const padding_ty_ref = try self.spv.arrayType2(layout.padding, u8_ty_ref);
+ const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref);
member_types.appendAssumeCapacity(padding_ty_ref);
member_names.appendAssumeCapacity(try self.spv.resolveString("padding"));
}
@@ -1341,22 +1204,24 @@ pub const DeclGen = struct {
} });
}
- fn resolveType2(self: *DeclGen, ty: Type, repr: Repr) Error!SpvCacheRef {
+ /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
+ fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!CacheRef {
+ log.debug("resolveType: ty = {}", .{ty.fmt(self.module)});
const target = self.getTarget();
switch (ty.zigTypeTag()) {
.Void, .NoReturn => return try self.spv.resolve(.void_type),
.Bool => switch (repr) {
.direct => return try self.spv.resolve(.bool_type),
- .indirect => return try self.intType2(.unsigned, 1),
+ .indirect => return try self.intType(.unsigned, 1),
},
.Int => {
const int_info = ty.intInfo(target);
- return try self.intType2(int_info.signedness, int_info.bits);
+ return try self.intType(int_info.signedness, int_info.bits);
},
.Enum => {
var buffer: Type.Payload.Bits = undefined;
const tag_ty = ty.intTagType(&buffer);
- return self.resolveType2(tag_ty, repr);
+ return self.resolveType(tag_ty, repr);
},
.Float => {
// We can (and want) not really emulate floating points with other floating point types like with the integer types,
@@ -1378,11 +1243,11 @@ pub const DeclGen = struct {
},
.Array => {
const elem_ty = ty.childType();
- const elem_ty_ref = try self.resolveType2(elem_ty, .direct);
+ const elem_ty_ref = try self.resolveType(elem_ty, .direct);
const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse {
return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()});
};
- return self.spv.arrayType2(total_len, elem_ty_ref);
+ return self.spv.arrayType(total_len, elem_ty_ref);
},
.Fn => switch (repr) {
.direct => {
@@ -1390,12 +1255,12 @@ pub const DeclGen = struct {
if (ty.fnIsVarArgs())
return self.fail("VarArgs functions are unsupported for SPIR-V", .{});
- const param_ty_refs = try self.gpa.alloc(SpvCacheRef, ty.fnParamLen());
+ const param_ty_refs = try self.gpa.alloc(CacheRef, ty.fnParamLen());
defer self.gpa.free(param_ty_refs);
for (param_ty_refs, 0..) |*param_type, i| {
- param_type.* = try self.resolveType2(ty.fnParamType(i), .direct);
+ param_type.* = try self.resolveType(ty.fnParamType(i), .direct);
}
- const return_ty_ref = try self.resolveType2(ty.fnReturnType(), .direct);
+ const return_ty_ref = try self.resolveType(ty.fnReturnType(), .direct);
return try self.spv.resolve(.{ .function_type = .{
.return_type = return_ty_ref,
@@ -1405,14 +1270,14 @@ pub const DeclGen = struct {
.indirect => {
// TODO: Represent function pointers properly.
// For now, just use an usize type.
- return try self.sizeType2();
+ return try self.sizeType();
},
},
.Pointer => {
const ptr_info = ty.ptrInfo().data;
const storage_class = spvStorageClass(ptr_info.@"addrspace");
- const child_ty_ref = try self.resolveType2(ptr_info.pointee_type, .indirect);
+ const child_ty_ref = try self.resolveType(ptr_info.pointee_type, .indirect);
const ptr_ty_ref = try self.spv.resolve(.{ .ptr_type = .{
.storage_class = storage_class,
.child_type = child_ty_ref,
@@ -1420,7 +1285,15 @@ pub const DeclGen = struct {
if (ptr_info.size != .Slice) {
return ptr_ty_ref;
}
- unreachable; // TODO
+
+ const size_ty_ref = try self.sizeType();
+ return self.spv.resolve(.{ .struct_type = .{
+ .member_types = &.{ ptr_ty_ref, size_ty_ref },
+ .member_names = &.{
+ try self.spv.resolveString("ptr"),
+ try self.spv.resolveString("len"),
+ },
+ } });
},
.Vector => {
// Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
@@ -1433,33 +1306,47 @@ pub const DeclGen = struct {
// TODO: Properly verify sizes and child type.
return try self.spv.resolve(.{ .vector_type = .{
- .component_type = try self.resolveType2(ty.elemType(), repr),
+ .component_type = try self.resolveType(ty.elemType(), repr),
.component_count = @intCast(u32, ty.vectorLen()),
} });
},
.Struct => {
if (ty.isSimpleTupleOrAnonStruct()) {
- unreachable; // TODO
+ const tuple = ty.tupleFields();
+ const member_types = try self.gpa.alloc(CacheRef, tuple.types.len);
+ defer self.gpa.free(member_types);
+
+ var member_index: usize = 0;
+ for (tuple.types, 0..) |field_ty, i| {
+ const field_val = tuple.values[i];
+ if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue;
+
+ member_types[member_index] = try self.resolveType(field_ty, .indirect);
+ member_index += 1;
+ }
+
+ return try self.spv.resolve(.{ .struct_type = .{
+ .member_types = member_types[0..member_index],
+ } });
}
const struct_ty = ty.castTag(.@"struct").?.data;
if (struct_ty.layout == .Packed) {
- return try self.resolveType2(struct_ty.backing_int_ty, .direct);
+ return try self.resolveType(struct_ty.backing_int_ty, .direct);
}
- const member_types = try self.gpa.alloc(SpvCacheRef, struct_ty.fields.count());
+ const member_types = try self.gpa.alloc(CacheRef, struct_ty.fields.count());
defer self.gpa.free(member_types);
- const member_names = try self.gpa.alloc(SpvCacheString, struct_ty.fields.count());
+ const member_names = try self.gpa.alloc(CacheString, struct_ty.fields.count());
defer self.gpa.free(member_names);
- // const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, struct_ty.fields.count());
var member_index: usize = 0;
for (struct_ty.fields.values(), 0..) |field, i| {
if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
- member_types[member_index] = try self.resolveType2(field.ty, .indirect);
+ member_types[member_index] = try self.resolveType(field.ty, .indirect);
member_names[member_index] = try self.spv.resolveString(struct_ty.fields.keys()[i]);
member_index += 1;
}
@@ -1480,16 +1367,16 @@ pub const DeclGen = struct {
// Just use a bool.
// Note: Always generate the bool with indirect format, to save on some sanity
// Perform the conversion to a direct bool when the field is extracted.
- return try self.resolveType2(Type.bool, .indirect);
+ return try self.resolveType(Type.bool, .indirect);
}
- const payload_ty_ref = try self.resolveType2(payload_ty, .indirect);
+ const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
if (ty.optionalReprIsPayload()) {
// Optional is actually a pointer or a slice.
return payload_ty_ref;
}
- const bool_ty_ref = try self.resolveType2(Type.bool, .indirect);
+ const bool_ty_ref = try self.resolveType(Type.bool, .indirect);
return try self.spv.resolve(.{ .struct_type = .{
.member_types = &.{ payload_ty_ref, bool_ty_ref },
@@ -1499,21 +1386,21 @@ pub const DeclGen = struct {
},
} });
},
- .Union => return try self.resolveUnionType2(ty, null),
- .ErrorSet => return try self.intType2(.unsigned, 16),
+ .Union => return try self.resolveUnionType(ty, null),
+ .ErrorSet => return try self.intType(.unsigned, 16),
.ErrorUnion => {
const payload_ty = ty.errorUnionPayload();
- const error_ty_ref = try self.resolveType2(Type.anyerror, .indirect);
+ const error_ty_ref = try self.resolveType(Type.anyerror, .indirect);
const eu_layout = self.errorUnionLayout(payload_ty);
if (!eu_layout.payload_has_bits) {
return error_ty_ref;
}
- const payload_ty_ref = try self.resolveType2(payload_ty, .indirect);
+ const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
- var member_types: [2]SpvCacheRef = undefined;
- var member_names: [2]SpvCacheString = undefined;
+ var member_types: [2]CacheRef = undefined;
+ var member_names: [2]CacheString = undefined;
if (eu_layout.error_first) {
// Put the error first
member_types = .{ error_ty_ref, payload_ty_ref };
@@ -1550,226 +1437,6 @@ pub const DeclGen = struct {
}
}
- /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
- fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!SpvType.Ref {
- log.debug("resolveType: ty = {}", .{ty.fmt(self.module)});
- _ = try self.resolveType2(ty, repr);
- const target = self.getTarget();
- switch (ty.zigTypeTag()) {
- .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)),
- .Bool => switch (repr) {
- .direct => return try self.spv.resolveType(SpvType.initTag(.bool)),
- // SPIR-V booleans are opaque, which is fine for operations, but they cant be stored.
- // This function returns the *stored* type, for values directly we convert this into a bool when
- // it is loaded, and convert it back to this type when stored.
- .indirect => return try self.intType(.unsigned, 1),
- },
- .Int => {
- const int_info = ty.intInfo(target);
- return try self.intType(int_info.signedness, int_info.bits);
- },
- .Enum => {
- var buffer: Type.Payload.Bits = undefined;
- const tag_ty = ty.intTagType(&buffer);
- return self.resolveType(tag_ty, repr);
- },
- .Float => {
- // We can (and want) not really emulate floating points with other floating point types like with the integer types,
- // so if the float is not supported, just return an error.
- const bits = ty.floatBits(target);
- const supported = switch (bits) {
- 16 => Target.spirv.featureSetHas(target.cpu.features, .Float16),
- // 32-bit floats are always supported (see spec, 2.16.1, Data rules).
- 32 => true,
- 64 => Target.spirv.featureSetHas(target.cpu.features, .Float64),
- else => false,
- };
-
- if (!supported) {
- return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
- }
-
- return try self.spv.resolveType(SpvType.float(bits));
- },
- .Array => {
- const elem_ty = ty.childType();
- const elem_ty_ref = try self.resolveType(elem_ty, .indirect);
- const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse {
- return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()});
- };
- return try self.spv.arrayType(total_len, elem_ty_ref);
- },
- .Fn => switch (repr) {
- .direct => {
- // TODO: Put this somewhere in Sema.zig
- if (ty.fnIsVarArgs())
- return self.fail("VarArgs functions are unsupported for SPIR-V", .{});
-
- // TODO: Parameter passing convention etc.
-
- const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen());
- for (param_types, 0..) |*param, i| {
- param.* = try self.resolveType(ty.fnParamType(i), .direct);
- }
-
- const return_type = try self.resolveType(ty.fnReturnType(), .direct);
-
- const payload = try self.spv.arena.create(SpvType.Payload.Function);
- payload.* = .{ .return_type = return_type, .parameters = param_types };
- return try self.spv.resolveType(SpvType.initPayload(&payload.base));
- },
- .indirect => {
- // TODO: Represent function pointers properly.
- // For now, just use an usize type.
- return try self.sizeType();
- },
- },
- .Pointer => {
- const ptr_info = ty.ptrInfo().data;
-
- const storage_class = spvStorageClass(ptr_info.@"addrspace");
- const child_ty_ref = try self.resolveType(ptr_info.pointee_type, .indirect);
- const ptr_ty_ref = try self.spv.ptrType(child_ty_ref, storage_class, 0);
-
- if (ptr_info.size != .Slice) {
- return ptr_ty_ref;
- }
-
- return try self.spv.simpleStructType(&.{
- .{ .ty = ptr_ty_ref, .name = "ptr" },
- .{ .ty = try self.sizeType(), .name = "len" },
- });
- },
- .Vector => {
- // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
- // which work on them), so simply use those.
- // Note: SPIR-V vectors only support bools, ints and floats, so pointer vectors need to be supported another way.
- // "composite integers" (larger than the largest supported native type) can probably be represented by an array of vectors.
- // TODO: The SPIR-V spec mentions that vector sizes may be quite restricted! look into which we can use, and whether OpTypeVector
- // is adequate at all for this.
-
- // TODO: Properly verify sizes and child type.
-
- const payload = try self.spv.arena.create(SpvType.Payload.Vector);
- payload.* = .{
- .component_type = try self.resolveType(ty.elemType(), repr),
- .component_count = @intCast(u32, ty.vectorLen()),
- };
- return try self.spv.resolveType(SpvType.initPayload(&payload.base));
- },
- .Struct => {
- if (ty.isSimpleTupleOrAnonStruct()) {
- const tuple = ty.tupleFields();
- const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, tuple.types.len);
- var member_index: u32 = 0;
- for (tuple.types, 0..) |field_ty, i| {
- const field_val = tuple.values[i];
- if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBitsIgnoreComptime()) continue;
- members[member_index] = .{
- .ty = try self.resolveType(field_ty, .indirect),
- };
- member_index += 1;
- }
- const payload = try self.spv.arena.create(SpvType.Payload.Struct);
- payload.* = .{
- .members = members[0..member_index],
- };
- return try self.spv.resolveType(SpvType.initPayload(&payload.base));
- }
-
- const struct_ty = ty.castTag(.@"struct").?.data;
-
- if (struct_ty.layout == .Packed) {
- return try self.resolveType(struct_ty.backing_int_ty, .indirect);
- }
-
- const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, struct_ty.fields.count());
- var member_index: usize = 0;
- for (struct_ty.fields.values(), 0..) |field, i| {
- if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
-
- members[member_index] = .{
- .ty = try self.resolveType(field.ty, .indirect),
- .name = struct_ty.fields.keys()[i],
- };
- member_index += 1;
- }
-
- const name = try struct_ty.getFullyQualifiedName(self.module);
- defer self.module.gpa.free(name);
-
- const payload = try self.spv.arena.create(SpvType.Payload.Struct);
- payload.* = .{
- .members = members[0..member_index],
- .name = try self.spv.arena.dupe(u8, name),
- };
- return try self.spv.resolveType(SpvType.initPayload(&payload.base));
- },
- .Optional => {
- var buf: Type.Payload.ElemType = undefined;
- const payload_ty = ty.optionalChild(&buf);
- if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
- // Just use a bool.
- // Note: Always generate the bool with indirect format, to save on some sanity
- // Perform the converison to a direct bool when the field is extracted.
- return try self.resolveType(Type.bool, .indirect);
- }
-
- const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
- if (ty.optionalReprIsPayload()) {
- // Optional is actually a pointer or a slice.
- return payload_ty_ref;
- }
-
- const bool_ty_ref = try self.resolveType(Type.bool, .indirect);
-
- // its an actual optional
- return try self.spv.simpleStructType(&.{
- .{ .ty = payload_ty_ref, .name = "payload" },
- .{ .ty = bool_ty_ref, .name = "valid" },
- });
- },
- .Union => return try self.resolveUnionType(ty, null),
- .ErrorSet => return try self.intType(.unsigned, 16),
- .ErrorUnion => {
- const payload_ty = ty.errorUnionPayload();
- const error_ty_ref = try self.resolveType(Type.anyerror, .indirect);
-
- const eu_layout = self.errorUnionLayout(payload_ty);
- if (!eu_layout.payload_has_bits) {
- return error_ty_ref;
- }
-
- const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
-
- var members = std.BoundedArray(SpvType.Payload.Struct.Member, 2){};
- if (eu_layout.error_first) {
- // Put the error first
- members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
- members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
- // TODO: ABI padding?
- } else {
- // Put the payload first.
- members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
- members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
- // TODO: ABI padding?
- }
-
- return try self.spv.simpleStructType(members.slice());
- },
-
- .Null,
- .Undefined,
- .EnumLiteral,
- .ComptimeFloat,
- .ComptimeInt,
- .Type,
- => unreachable, // Must be comptime.
-
- else => |tag| return self.todo("Implement zig type '{}'", .{tag}),
- }
- }
-
fn spvStorageClass(as: std.builtin.AddressSpace) StorageClass {
return switch (as) {
.generic => .Generic,
@@ -1839,17 +1506,13 @@ pub const DeclGen = struct {
/// the name of an error in the text executor.
fn generateTestEntryPoint(self: *DeclGen, name: []const u8, spv_test_decl_index: SpvModule.Decl.Index) !void {
const anyerror_ty_ref = try self.resolveType(Type.anyerror, .direct);
- const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup, 0);
+ const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup);
const void_ty_ref = try self.resolveType(Type.void, .direct);
- const kernel_proto_ty_ref = blk: {
- const proto_payload = try self.spv.arena.create(SpvType.Payload.Function);
- proto_payload.* = .{
- .return_type = void_ty_ref,
- .parameters = try self.spv.arena.dupe(SpvType.Ref, &.{ptr_anyerror_ty_ref}),
- };
- break :blk try self.spv.resolveType(SpvType.initPayload(&proto_payload.base));
- };
+ const kernel_proto_ty_ref = try self.spv.resolve(.{ .function_type = .{
+ .return_type = void_ty_ref,
+ .parameters = &.{ptr_anyerror_ty_ref},
+ } });
const test_id = self.spv.declPtr(spv_test_decl_index).result_id;
@@ -1983,9 +1646,9 @@ pub const DeclGen = struct {
}
}
- fn boolToInt(self: *DeclGen, result_ty_ref: SpvType.Ref, condition_id: IdRef) !IdRef {
- const zero_id = try self.constInt(result_ty_ref, 0);
- const one_id = try self.constInt(result_ty_ref, 1);
+ fn boolToInt(self: *DeclGen, result_ty_ref: CacheRef, condition_id: IdRef) !IdRef {
+ const zero_id = try self.spv.constInt(result_ty_ref, 0);
+ const one_id = try self.spv.constInt(result_ty_ref, 1);
const result_id = self.spv.allocId();
try self.func.body.emit(self.spv.gpa, .OpSelect, .{
.id_result_type = self.typeId(result_ty_ref),
@@ -2004,7 +1667,7 @@ pub const DeclGen = struct {
.Bool => blk: {
const direct_bool_ty_ref = try self.resolveType(ty, .direct);
const indirect_bool_ty_ref = try self.resolveType(ty, .indirect);
- const zero_id = try self.constInt(indirect_bool_ty_ref, 0);
+ const zero_id = try self.spv.constInt(indirect_bool_ty_ref, 0);
const result_id = self.spv.allocId();
try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{
.id_result_type = self.typeId(direct_bool_ty_ref),
@@ -2242,10 +1905,10 @@ pub const DeclGen = struct {
return result_id;
}
- fn maskStrangeInt(self: *DeclGen, ty_ref: SpvType.Ref, value_id: IdRef, bits: u16) !IdRef {
+ fn maskStrangeInt(self: *DeclGen, ty_ref: CacheRef, value_id: IdRef, bits: u16) !IdRef {
const mask_value = if (bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @intCast(u6, bits)) - 1;
const result_id = self.spv.allocId();
- const mask_id = try self.constInt(ty_ref, mask_value);
+ const mask_id = try self.spv.constInt(ty_ref, mask_value);
try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{
.id_result_type = self.typeId(ty_ref),
.id_result = result_id,
@@ -2384,7 +2047,7 @@ pub const DeclGen = struct {
// Note that signed overflow is also wrapping in spir-v.
const rhs_lt_zero_id = self.spv.allocId();
- const zero_id = try self.constInt(operand_ty_ref, 0);
+ const zero_id = try self.spv.constInt(operand_ty_ref, 0);
try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{
.id_result_type = self.typeId(bool_ty_ref),
.id_result = rhs_lt_zero_id,
@@ -2463,7 +2126,7 @@ pub const DeclGen = struct {
/// is the latter and PtrAccessChain is the former.
fn accessChain(
self: *DeclGen,
- result_ty_ref: SpvType.Ref,
+ result_ty_ref: CacheRef,
base: IdRef,
indexes: []const IdRef,
) !IdRef {
@@ -2479,7 +2142,7 @@ pub const DeclGen = struct {
fn ptrAccessChain(
self: *DeclGen,
- result_ty_ref: SpvType.Ref,
+ result_ty_ref: CacheRef,
base: IdRef,
element: IdRef,
indexes: []const IdRef,
@@ -2854,7 +2517,7 @@ pub const DeclGen = struct {
// Construct new pointer type for the resulting pointer
const elem_ty = ptr_ty.elemType2(); // use elemType() so that we get T for *[N]T.
const elem_ty_ref = try self.resolveType(elem_ty, .direct);
- const elem_ptr_ty_ref = try self.spv.ptrType(elem_ty_ref, spvStorageClass(ptr_ty.ptrAddressSpace()), 0);
+ const elem_ptr_ty_ref = try self.spv.ptrType(elem_ty_ref, spvStorageClass(ptr_ty.ptrAddressSpace()));
if (ptr_ty.isSinglePointer()) {
// Pointer-to-array. In this case, the resulting pointer is not of the same type
// as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain.
@@ -2970,7 +2633,7 @@ pub const DeclGen = struct {
fn makePointerConstant(
self: *DeclGen,
section: *SpvSection,
- ptr_ty_ref: SpvType.Ref,
+ ptr_ty_ref: CacheRef,
ptr_id: IdRef,
) !IdRef {
const result_id = self.spv.allocId();
@@ -2988,11 +2651,11 @@ pub const DeclGen = struct {
// placed in the Function address space.
fn alloc(
self: *DeclGen,
- ty_ref: SpvType.Ref,
+ ty_ref: CacheRef,
initializer: ?IdRef,
) !IdRef {
- const fn_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Function, 0);
- const general_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0);
+ const fn_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Function);
+ const general_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic);
// SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to
// directly generate them into func.prologue instead of the body.
@@ -3146,7 +2809,7 @@ pub const DeclGen = struct {
const val_is_undef = if (self.air.value(bin_op.rhs)) |val| val.isUndefDeep() else false;
if (val_is_undef) {
- const undef = try self.constUndef(ptr_ty_ref);
+ const undef = try self.spv.constUndef(ptr_ty_ref);
try self.store(ptr_ty, ptr, undef);
} else {
try self.store(ptr_ty, ptr, value);
@@ -3217,7 +2880,7 @@ pub const DeclGen = struct {
else
err_union_id;
- const zero_id = try self.constInt(err_ty_ref, 0);
+ const zero_id = try self.spv.constInt(err_ty_ref, 0);
const is_err_id = self.spv.allocId();
try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{
.id_result_type = self.typeId(bool_ty_ref),
@@ -3266,7 +2929,7 @@ pub const DeclGen = struct {
if (err_union_ty.errorUnionSet().errorSetIsEmpty()) {
// No error possible, so just return undefined.
- return try self.constUndef(err_ty_ref);
+ return try self.spv.constUndef(err_ty_ref);
}
const payload_ty = err_union_ty.errorUnionPayload();
@@ -3295,7 +2958,7 @@ pub const DeclGen = struct {
const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
var members = std.BoundedArray(IdRef, 2){};
- const payload_id = try self.constUndef(payload_ty_ref);
+ const payload_id = try self.spv.constUndef(payload_ty_ref);
if (eu_layout.error_first) {
members.appendAssumeCapacity(operand_id);
members.appendAssumeCapacity(payload_id);
@@ -3337,7 +3000,7 @@ pub const DeclGen = struct {
operand_id;
const payload_ty_ref = try self.resolveType(ptr_ty, .direct);
- const null_id = try self.constNull(payload_ty_ref);
+ const null_id = try self.spv.constNull(payload_ty_ref);
const result_id = self.spv.allocId();
const operands = .{
.id_result_type = self.typeId(bool_ty_ref),