aboutsummaryrefslogtreecommitdiff
path: root/src/codegen/spirv
diff options
context:
space:
mode:
Diffstat (limited to 'src/codegen/spirv')
-rw-r--r--src/codegen/spirv/Assembler.zig170
-rw-r--r--src/codegen/spirv/Cache.zig1046
-rw-r--r--src/codegen/spirv/Module.zig505
-rw-r--r--src/codegen/spirv/spec.zig2
-rw-r--r--src/codegen/spirv/type.zig567
5 files changed, 1191 insertions, 1099 deletions
diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig
index eebf43866d..c7848bbc92 100644
--- a/src/codegen/spirv/Assembler.zig
+++ b/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", .{}),
}
}
diff --git a/src/codegen/spirv/Cache.zig b/src/codegen/spirv/Cache.zig
new file mode 100644
index 0000000000..4c41bf583b
--- /dev/null
+++ b/src/codegen/spirv/Cache.zig
@@ -0,0 +1,1046 @@
+//! This file implements an InternPool-like structure that caches
+//! SPIR-V types and constants. Instead of generating type and
+//! constant instructions directly, we first keep a representation
+//! in a compressed database. This is then only later turned into
+//! actual SPIR-V instructions.
+//! Note: This cache is insertion-ordered. This means that we
+//! can materialize the SPIR-V instructions in the proper order,
+//! as SPIR-V requires that the type is emitted before use.
+//! Note: According to SPIR-V spec section 2.8, Types and Variables,
+//! non-pointer non-aggrerate types (which includes matrices and
+//! vectors) must have a _unique_ representation in the final binary.
+
+const std = @import("std");
+const assert = std.debug.assert;
+const Allocator = std.mem.Allocator;
+
+const Section = @import("Section.zig");
+const Module = @import("Module.zig");
+
+const spec = @import("spec.zig");
+const Opcode = spec.Opcode;
+const IdResult = spec.IdResult;
+const StorageClass = spec.StorageClass;
+
+const Self = @This();
+
+map: std.AutoArrayHashMapUnmanaged(void, void) = .{},
+items: std.MultiArrayList(Item) = .{},
+extra: std.ArrayListUnmanaged(u32) = .{},
+
+string_bytes: std.ArrayListUnmanaged(u8) = .{},
+strings: std.AutoArrayHashMapUnmanaged(void, u32) = .{},
+
+const Item = struct {
+ tag: Tag,
+ /// The result-id that this item uses.
+ result_id: IdResult,
+ /// The Tag determines how this should be interpreted.
+ data: u32,
+};
+
+const Tag = enum {
+ // -- Types
+ /// Simple type that has no additional data.
+ /// data is SimpleType.
+ type_simple,
+ /// Signed integer type
+ /// data is number of bits
+ type_int_signed,
+ /// Unsigned integer type
+ /// data is number of bits
+ type_int_unsigned,
+ /// Floating point type
+ /// data is number of bits
+ type_float,
+ /// Vector type
+ /// data is payload to VectorType
+ type_vector,
+ /// Array type
+ /// data is payload to ArrayType
+ type_array,
+ /// Function (proto)type
+ /// data is payload to FunctionType
+ type_function,
+ /// Pointer type in the CrossWorkgroup storage class
+ /// data is child type
+ type_ptr_generic,
+ /// Pointer type in the CrossWorkgroup storage class
+ /// data is child type
+ type_ptr_crosswgp,
+ /// Pointer type in the Function storage class
+ /// data is child type
+ type_ptr_function,
+ /// Simple pointer type that does not have any decorations.
+ /// data is payload to SimplePointerType
+ type_ptr_simple,
+ /// Simple structure type that does not have any decorations.
+ /// data is payload to SimpleStructType
+ type_struct_simple,
+ /// Simple structure type that does not have any decorations, but does
+ /// have member names trailing.
+ /// data is payload to SimpleStructType
+ type_struct_simple_with_member_names,
+
+ // -- Values
+ /// Value of type u8
+ /// data is value
+ uint8,
+ /// Value of type u32
+ /// data is value
+ uint32,
+ // TODO: More specialized tags here.
+ /// Integer value for signed values that are smaller than 32 bits.
+ /// data is pointer to Int32
+ int_small,
+ /// Integer value for unsigned values that are smaller than 32 bits.
+ /// data is pointer to UInt32
+ uint_small,
+ /// Integer value for signed values that are beteen 32 and 64 bits.
+ /// data is pointer to Int64
+ int_large,
+ /// Integer value for unsinged values that are beteen 32 and 64 bits.
+ /// data is pointer to UInt64
+ uint_large,
+ /// Value of type f16
+ /// data is value
+ float16,
+ /// Value of type f32
+ /// data is value
+ float32,
+ /// 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 };
+
+ const VectorType = Key.VectorType;
+ const ArrayType = Key.ArrayType;
+
+ // Trailing:
+ // - [param_len]Ref: parameter types.
+ const FunctionType = struct {
+ param_len: u32,
+ return_type: Ref,
+ };
+
+ const SimplePointerType = struct {
+ storage_class: StorageClass,
+ child_type: Ref,
+ };
+
+ /// Trailing:
+ /// - [members_len]Ref: Member types.
+ /// - [members_len]String: Member names, -- ONLY if the tag is type_struct_simple_with_member_names
+ const SimpleStructType = struct {
+ /// (optional) The name of the struct.
+ name: String,
+ /// Number of members that this struct has.
+ members_len: u32,
+ };
+
+ const Float64 = struct {
+ // Low-order 32 bits of the value.
+ low: u32,
+ // High-order 32 bits of the value.
+ high: u32,
+
+ fn encode(value: f64) Float64 {
+ const bits = @bitCast(u64, value);
+ return .{
+ .low = @truncate(u32, bits),
+ .high = @truncate(u32, bits >> 32),
+ };
+ }
+
+ fn decode(self: Float64) f64 {
+ const bits = @as(u64, self.low) | (@as(u64, self.high) << 32);
+ return @bitCast(f64, bits);
+ }
+ };
+
+ const Int32 = struct {
+ ty: Ref,
+ value: i32,
+ };
+
+ const UInt32 = struct {
+ ty: Ref,
+ value: u32,
+ };
+
+ const UInt64 = struct {
+ ty: Ref,
+ low: u32,
+ high: u32,
+
+ fn encode(ty: Ref, value: u64) Int64 {
+ return .{
+ .ty = ty,
+ .low = @truncate(u32, value),
+ .high = @truncate(u32, value >> 32),
+ };
+ }
+
+ fn decode(self: UInt64) u64 {
+ return @as(u64, self.low) | (@as(u64, self.high) << 32);
+ }
+ };
+
+ const Int64 = struct {
+ ty: Ref,
+ low: u32,
+ high: u32,
+
+ fn encode(ty: Ref, value: i64) Int64 {
+ return .{
+ .ty = ty,
+ .low = @truncate(u32, @bitCast(u64, value)),
+ .high = @truncate(u32, @bitCast(u64, value) >> 32),
+ };
+ }
+
+ fn decode(self: Int64) i64 {
+ return @bitCast(i64, @as(u64, self.low) | (@as(u64, self.high) << 32));
+ }
+ };
+};
+
+pub const Ref = enum(u32) { _ };
+
+/// This union represents something that can be interned. This includes
+/// types and constants. This structure is used for interfacing with the
+/// database: Values described for this structure are ephemeral and stored
+/// in a more memory-efficient manner internally.
+pub const Key = union(enum) {
+ // -- Types
+ void_type,
+ bool_type,
+ int_type: IntType,
+ float_type: FloatType,
+ vector_type: VectorType,
+ array_type: ArrayType,
+ function_type: FunctionType,
+ ptr_type: PointerType,
+ struct_type: StructType,
+
+ // -- 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;
+
+ pub const VectorType = struct {
+ component_type: Ref,
+ component_count: u32,
+ };
+
+ pub const ArrayType = struct {
+ /// Child type of this array.
+ element_type: Ref,
+ /// Reference to a constant.
+ length: Ref,
+ /// Type has the 'ArrayStride' decoration.
+ /// If zero, no stride is present.
+ stride: u32 = 0,
+ };
+
+ pub const FunctionType = struct {
+ return_type: Ref,
+ parameters: []const Ref,
+ };
+
+ pub const PointerType = struct {
+ storage_class: StorageClass,
+ child_type: Ref,
+ // TODO: Decorations:
+ // - Alignment
+ // - ArrayStride,
+ // - MaxByteOffset,
+ };
+
+ pub const StructType = struct {
+ // TODO: Decorations.
+ /// The name of the structure. Can be `.none`.
+ name: String = .none,
+ /// The type of each member.
+ member_types: []const Ref,
+ /// Name for each member. May be omitted.
+ member_names: ?[]const String = null,
+
+ fn memberNames(self: @This()) []const String {
+ return if (self.member_names) |member_names| member_names else &.{};
+ }
+ };
+
+ pub const Int = struct {
+ /// The type: any bitness integer.
+ ty: Ref,
+ /// The actual value. Only uint64 and int64 types
+ /// are available here: Smaller types should use these
+ /// fields.
+ value: Value,
+
+ pub const Value = union(enum) {
+ uint64: u64,
+ int64: i64,
+ };
+
+ /// Turns this value into the corresponding 32-bit literal, 2s complement signed.
+ fn toBits32(self: Int) u32 {
+ return switch (self.value) {
+ .uint64 => |val| @intCast(u32, val),
+ .int64 => |val| if (val < 0) @bitCast(u32, @intCast(i32, val)) else @intCast(u32, val),
+ };
+ }
+
+ fn toBits64(self: Int) u64 {
+ return switch (self.value) {
+ .uint64 => |val| val,
+ .int64 => |val| @bitCast(u64, val),
+ };
+ }
+
+ fn to(self: Int, comptime T: type) T {
+ return switch (self.value) {
+ inline else => |val| @intCast(T, val),
+ };
+ }
+ };
+
+ /// Represents a numberic value of some type.
+ pub const Float = struct {
+ /// The type: 16, 32, or 64-bit float.
+ ty: Ref,
+ /// The actual value.
+ value: Value,
+
+ pub const Value = union(enum) {
+ float16: f16,
+ float32: f32,
+ float64: f64,
+ };
+ };
+
+ 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) {
+ .float => |float| {
+ std.hash.autoHash(&hasher, float.ty);
+ switch (float.value) {
+ .float16 => |value| std.hash.autoHash(&hasher, @bitCast(u16, value)),
+ .float32 => |value| std.hash.autoHash(&hasher, @bitCast(u32, value)),
+ .float64 => |value| std.hash.autoHash(&hasher, @bitCast(u64, value)),
+ }
+ },
+ .function_type => |func| {
+ std.hash.autoHash(&hasher, func.return_type);
+ for (func.parameters) |param_type| {
+ std.hash.autoHash(&hasher, param_type);
+ }
+ },
+ .struct_type => |struct_type| {
+ std.hash.autoHash(&hasher, struct_type.name);
+ for (struct_type.member_types) |member_type| {
+ std.hash.autoHash(&hasher, member_type);
+ }
+ for (struct_type.memberNames()) |member_name| {
+ std.hash.autoHash(&hasher, member_name);
+ }
+ },
+ inline else => |key| std.hash.autoHash(&hasher, key),
+ }
+ return @truncate(u32, hasher.final());
+ }
+
+ fn eql(a: Key, b: Key) bool {
+ const KeyTag = @typeInfo(Key).Union.tag_type.?;
+ const a_tag: KeyTag = a;
+ const b_tag: KeyTag = b;
+ if (a_tag != b_tag) {
+ return false;
+ }
+ return switch (a) {
+ .function_type => |a_func| {
+ const b_func = b.function_type;
+ return a_func.return_type == b_func.return_type and
+ std.mem.eql(Ref, a_func.parameters, b_func.parameters);
+ },
+ .struct_type => |a_struct| {
+ const b_struct = b.struct_type;
+ return a_struct.name == b_struct.name and
+ std.mem.eql(Ref, a_struct.member_types, b_struct.member_types) and
+ std.mem.eql(String, a_struct.memberNames(), b_struct.memberNames());
+ },
+ // TODO: Unroll?
+ else => std.meta.eql(a, b),
+ };
+ }
+
+ pub const Adapter = struct {
+ self: *const Self,
+
+ pub fn eql(ctx: @This(), a: Key, b_void: void, b_index: usize) bool {
+ _ = b_void;
+ return ctx.self.lookup(@intToEnum(Ref, b_index)).eql(a);
+ }
+
+ pub fn hash(ctx: @This(), a: Key) u32 {
+ _ = ctx;
+ return a.hash();
+ }
+ };
+
+ fn toSimpleType(self: Key) Tag.SimpleType {
+ return switch (self) {
+ .void_type => .void,
+ .bool_type => .bool,
+ else => unreachable,
+ };
+ }
+};
+
+pub fn deinit(self: *Self, spv: *const Module) void {
+ self.map.deinit(spv.gpa);
+ self.items.deinit(spv.gpa);
+ self.extra.deinit(spv.gpa);
+ self.string_bytes.deinit(spv.gpa);
+ self.strings.deinit(spv.gpa);
+}
+
+/// Actually materialize the database into spir-v instructions.
+/// This function returns a spir-v section of (only) constant and type instructions.
+/// Additionally, decorations, debug names, etc, are all directly emitted into the
+/// `spv` module. The section is allocated with `spv.gpa`.
+pub fn materialize(self: *const Self, spv: *Module) !Section {
+ var section = Section{};
+ errdefer section.deinit(spv.gpa);
+ for (self.items.items(.result_id), 0..) |result_id, index| {
+ try self.emit(spv, result_id, @intToEnum(Ref, index), &section);
+ }
+ return section;
+}
+
+fn emit(
+ self: *const Self,
+ spv: *Module,
+ result_id: IdResult,
+ ref: Ref,
+ section: *Section,
+) !void {
+ const key = self.lookup(ref);
+ const Lit = spec.LiteralContextDependentNumber;
+ switch (key) {
+ .void_type => {
+ try section.emit(spv.gpa, .OpTypeVoid, .{ .id_result = result_id });
+ try spv.debugName(result_id, "void", .{});
+ },
+ .bool_type => {
+ try section.emit(spv.gpa, .OpTypeBool, .{ .id_result = result_id });
+ try spv.debugName(result_id, "bool", .{});
+ },
+ .int_type => |int| {
+ try section.emit(spv.gpa, .OpTypeInt, .{
+ .id_result = result_id,
+ .width = int.bits,
+ .signedness = switch (int.signedness) {
+ .unsigned => @as(spec.Word, 0),
+ .signed => 1,
+ },
+ });
+ const ui: []const u8 = switch (int.signedness) {
+ .unsigned => "u",
+ .signed => "i",
+ };
+ try spv.debugName(result_id, "{s}{}", .{ ui, int.bits });
+ },
+ .float_type => |float| {
+ try section.emit(spv.gpa, .OpTypeFloat, .{
+ .id_result = result_id,
+ .width = float.bits,
+ });
+ try spv.debugName(result_id, "f{}", .{float.bits});
+ },
+ .vector_type => |vector| {
+ try section.emit(spv.gpa, .OpTypeVector, .{
+ .id_result = result_id,
+ .component_type = self.resultId(vector.component_type),
+ .component_count = vector.component_count,
+ });
+ },
+ .array_type => |array| {
+ try section.emit(spv.gpa, .OpTypeArray, .{
+ .id_result = result_id,
+ .element_type = self.resultId(array.element_type),
+ .length = self.resultId(array.length),
+ });
+ if (array.stride != 0) {
+ try spv.decorate(result_id, .{ .ArrayStride = .{ .array_stride = array.stride } });
+ }
+ },
+ .function_type => |function| {
+ try section.emitRaw(spv.gpa, .OpTypeFunction, 2 + function.parameters.len);
+ section.writeOperand(IdResult, result_id);
+ section.writeOperand(IdResult, self.resultId(function.return_type));
+ for (function.parameters) |param_type| {
+ section.writeOperand(IdResult, self.resultId(param_type));
+ }
+ },
+ .ptr_type => |ptr| {
+ try section.emit(spv.gpa, .OpTypePointer, .{
+ .id_result = result_id,
+ .storage_class = ptr.storage_class,
+ .type = self.resultId(ptr.child_type),
+ });
+ // TODO: Decorations?
+ },
+ .struct_type => |struct_type| {
+ try section.emitRaw(spv.gpa, .OpTypeStruct, 1 + struct_type.member_types.len);
+ section.writeOperand(IdResult, result_id);
+ for (struct_type.member_types) |member_type| {
+ section.writeOperand(IdResult, self.resultId(member_type));
+ }
+ if (self.getString(struct_type.name)) |name| {
+ try spv.debugName(result_id, "{s}", .{name});
+ }
+ for (struct_type.memberNames(), 0..) |member_name, i| {
+ if (self.getString(member_name)) |name| {
+ try spv.memberDebugName(result_id, @intCast(u32, i), "{s}", .{name});
+ }
+ }
+ // TODO: Decorations?
+ },
+ .int => |int| {
+ const int_type = self.lookup(int.ty).int_type;
+ const ty_id = self.resultId(int.ty);
+ const lit: Lit = switch (int_type.bits) {
+ 1...32 => .{ .uint32 = int.toBits32() },
+ 33...64 => .{ .uint64 = int.toBits64() },
+ else => unreachable,
+ };
+
+ try section.emit(spv.gpa, .OpConstant, .{
+ .id_result_type = ty_id,
+ .id_result = result_id,
+ .value = lit,
+ });
+ },
+ .float => |float| {
+ const ty_id = self.resultId(float.ty);
+ const lit: Lit = switch (float.value) {
+ .float16 => |value| .{ .uint32 = @bitCast(u16, value) },
+ .float32 => |value| .{ .float32 = value },
+ .float64 => |value| .{ .float64 = value },
+ };
+ try section.emit(spv.gpa, .OpConstant, .{
+ .id_result_type = ty_id,
+ .id_result = result_id,
+ .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,
+ });
+ },
+ },
+ }
+}
+
+/// Add a key to this cache. Returns a reference to the key that
+/// was added. The corresponding result-id can be queried using
+/// self.resultId with the result.
+pub fn resolve(self: *Self, spv: *Module, key: Key) !Ref {
+ const adapter: Key.Adapter = .{ .self = self };
+ const entry = try self.map.getOrPutAdapted(spv.gpa, key, adapter);
+ if (entry.found_existing) {
+ return @intToEnum(Ref, entry.index);
+ }
+ const result_id = spv.allocId();
+ const item: Item = switch (key) {
+ inline .void_type, .bool_type => .{
+ .tag = .type_simple,
+ .result_id = result_id,
+ .data = @enumToInt(key.toSimpleType()),
+ },
+ .int_type => |int| blk: {
+ const t: Tag = switch (int.signedness) {
+ .signed => .type_int_signed,
+ .unsigned => .type_int_unsigned,
+ };
+ break :blk .{
+ .tag = t,
+ .result_id = result_id,
+ .data = int.bits,
+ };
+ },
+ .float_type => |float| .{
+ .tag = .type_float,
+ .result_id = result_id,
+ .data = float.bits,
+ },
+ .vector_type => |vector| .{
+ .tag = .type_vector,
+ .result_id = result_id,
+ .data = try self.addExtra(spv, vector),
+ },
+ .array_type => |array| .{
+ .tag = .type_array,
+ .result_id = result_id,
+ .data = try self.addExtra(spv, array),
+ },
+ .function_type => |function| blk: {
+ const extra = try self.addExtra(spv, Tag.FunctionType{
+ .param_len = @intCast(u32, function.parameters.len),
+ .return_type = function.return_type,
+ });
+ try self.extra.appendSlice(spv.gpa, @ptrCast([]const u32, function.parameters));
+ break :blk .{
+ .tag = .type_function,
+ .result_id = result_id,
+ .data = extra,
+ };
+ },
+ .ptr_type => |ptr| switch (ptr.storage_class) {
+ .Generic => Item{
+ .tag = .type_ptr_generic,
+ .result_id = result_id,
+ .data = @enumToInt(ptr.child_type),
+ },
+ .CrossWorkgroup => Item{
+ .tag = .type_ptr_crosswgp,
+ .result_id = result_id,
+ .data = @enumToInt(ptr.child_type),
+ },
+ .Function => Item{
+ .tag = .type_ptr_function,
+ .result_id = result_id,
+ .data = @enumToInt(ptr.child_type),
+ },
+ else => |storage_class| Item{
+ .tag = .type_ptr_simple,
+ .result_id = result_id,
+ .data = try self.addExtra(spv, Tag.SimplePointerType{
+ .storage_class = storage_class,
+ .child_type = ptr.child_type,
+ }),
+ },
+ },
+ .struct_type => |struct_type| blk: {
+ const extra = try self.addExtra(spv, Tag.SimpleStructType{
+ .name = struct_type.name,
+ .members_len = @intCast(u32, struct_type.member_types.len),
+ });
+ try self.extra.appendSlice(spv.gpa, @ptrCast([]const u32, struct_type.member_types));
+
+ if (struct_type.member_names) |member_names| {
+ try self.extra.appendSlice(spv.gpa, @ptrCast([]const u32, member_names));
+ break :blk Item{
+ .tag = .type_struct_simple_with_member_names,
+ .result_id = result_id,
+ .data = extra,
+ };
+ } else {
+ break :blk Item{
+ .tag = .type_struct_simple,
+ .result_id = result_id,
+ .data = extra,
+ };
+ }
+ },
+ .int => |int| blk: {
+ const int_type = self.lookup(int.ty).int_type;
+ if (int_type.signedness == .unsigned and int_type.bits == 8) {
+ break :blk .{
+ .tag = .uint8,
+ .result_id = result_id,
+ .data = int.to(u8),
+ };
+ } else if (int_type.signedness == .unsigned and int_type.bits == 32) {
+ break :blk .{
+ .tag = .uint32,
+ .result_id = result_id,
+ .data = int.to(u32),
+ };
+ }
+
+ switch (int.value) {
+ inline else => |val| {
+ if (val >= 0 and val <= std.math.maxInt(u32)) {
+ break :blk .{
+ .tag = .uint_small,
+ .result_id = result_id,
+ .data = try self.addExtra(spv, Tag.UInt32{
+ .ty = int.ty,
+ .value = @intCast(u32, val),
+ }),
+ };
+ } else if (val >= std.math.minInt(i32) and val <= std.math.maxInt(i32)) {
+ break :blk .{
+ .tag = .int_small,
+ .result_id = result_id,
+ .data = try self.addExtra(spv, Tag.Int32{
+ .ty = int.ty,
+ .value = @intCast(i32, val),
+ }),
+ };
+ } else if (val < 0) {
+ break :blk .{
+ .tag = .int_large,
+ .result_id = result_id,
+ .data = try self.addExtra(spv, Tag.Int64.encode(int.ty, @intCast(i64, val))),
+ };
+ } else {
+ break :blk .{
+ .tag = .uint_large,
+ .result_id = result_id,
+ .data = try self.addExtra(spv, Tag.UInt64.encode(int.ty, @intCast(u64, val))),
+ };
+ }
+ },
+ }
+ },
+ .float => |float| switch (self.lookup(float.ty).float_type.bits) {
+ 16 => .{
+ .tag = .float16,
+ .result_id = result_id,
+ .data = @bitCast(u16, float.value.float16),
+ },
+ 32 => .{
+ .tag = .float32,
+ .result_id = result_id,
+ .data = @bitCast(u32, float.value.float32),
+ },
+ 64 => .{
+ .tag = .float64,
+ .result_id = result_id,
+ .data = try self.addExtra(spv, Tag.Float64.encode(float.value.float64)),
+ },
+ 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);
+
+ return @intToEnum(Ref, entry.index);
+}
+
+/// Turn a Ref back into a Key.
+/// The Key is valid until the next call to resolve().
+pub fn lookup(self: *const Self, ref: Ref) Key {
+ const item = self.items.get(@enumToInt(ref));
+ const data = item.data;
+ return switch (item.tag) {
+ .type_simple => switch (@intToEnum(Tag.SimpleType, data)) {
+ .void => .void_type,
+ .bool => .bool_type,
+ },
+ .type_int_signed => .{ .int_type = .{
+ .signedness = .signed,
+ .bits = @intCast(u16, data),
+ } },
+ .type_int_unsigned => .{ .int_type = .{
+ .signedness = .unsigned,
+ .bits = @intCast(u16, data),
+ } },
+ .type_float => .{ .float_type = .{
+ .bits = @intCast(u16, data),
+ } },
+ .type_vector => .{ .vector_type = self.extraData(Tag.VectorType, data) },
+ .type_array => .{ .array_type = self.extraData(Tag.ArrayType, data) },
+ .type_function => {
+ const payload = self.extraDataTrail(Tag.FunctionType, data);
+ return .{
+ .function_type = .{
+ .return_type = payload.data.return_type,
+ .parameters = @ptrCast([]const Ref, self.extra.items[payload.trail..][0..payload.data.param_len]),
+ },
+ };
+ },
+ .type_ptr_generic => .{
+ .ptr_type = .{
+ .storage_class = .Generic,
+ .child_type = @intToEnum(Ref, data),
+ },
+ },
+ .type_ptr_crosswgp => .{
+ .ptr_type = .{
+ .storage_class = .CrossWorkgroup,
+ .child_type = @intToEnum(Ref, data),
+ },
+ },
+ .type_ptr_function => .{
+ .ptr_type = .{
+ .storage_class = .Function,
+ .child_type = @intToEnum(Ref, data),
+ },
+ },
+ .type_ptr_simple => {
+ const payload = self.extraData(Tag.SimplePointerType, data);
+ return .{
+ .ptr_type = .{
+ .storage_class = payload.storage_class,
+ .child_type = payload.child_type,
+ },
+ };
+ },
+ .type_struct_simple => {
+ const payload = self.extraDataTrail(Tag.SimpleStructType, data);
+ const member_types = @ptrCast([]const Ref, self.extra.items[payload.trail..][0..payload.data.members_len]);
+ return .{
+ .struct_type = .{
+ .name = payload.data.name,
+ .member_types = member_types,
+ .member_names = null,
+ },
+ };
+ },
+ .type_struct_simple_with_member_names => {
+ const payload = self.extraDataTrail(Tag.SimpleStructType, data);
+ const trailing = self.extra.items[payload.trail..];
+ const member_types = @ptrCast([]const Ref, trailing[0..payload.data.members_len]);
+ const member_names = @ptrCast([]const String, trailing[payload.data.members_len..][0..payload.data.members_len]);
+ return .{
+ .struct_type = .{
+ .name = payload.data.name,
+ .member_types = member_types,
+ .member_names = member_names,
+ },
+ };
+ },
+ .float16 => .{ .float = .{
+ .ty = self.get(.{ .float_type = .{ .bits = 16 } }),
+ .value = .{ .float16 = @bitCast(f16, @intCast(u16, data)) },
+ } },
+ .float32 => .{ .float = .{
+ .ty = self.get(.{ .float_type = .{ .bits = 32 } }),
+ .value = .{ .float32 = @bitCast(f32, data) },
+ } },
+ .float64 => .{ .float = .{
+ .ty = self.get(.{ .float_type = .{ .bits = 64 } }),
+ .value = .{ .float64 = self.extraData(Tag.Float64, data).decode() },
+ } },
+ .uint8 => .{ .int = .{
+ .ty = self.get(.{ .int_type = .{ .signedness = .unsigned, .bits = 8 } }),
+ .value = .{ .uint64 = data },
+ } },
+ .uint32 => .{ .int = .{
+ .ty = self.get(.{ .int_type = .{ .signedness = .unsigned, .bits = 32 } }),
+ .value = .{ .uint64 = data },
+ } },
+ .int_small => {
+ const payload = self.extraData(Tag.Int32, data);
+ return .{ .int = .{
+ .ty = payload.ty,
+ .value = .{ .int64 = payload.value },
+ } };
+ },
+ .uint_small => {
+ const payload = self.extraData(Tag.UInt32, data);
+ return .{ .int = .{
+ .ty = payload.ty,
+ .value = .{ .uint64 = payload.value },
+ } };
+ },
+ .int_large => {
+ const payload = self.extraData(Tag.Int64, data);
+ return .{ .int = .{
+ .ty = payload.ty,
+ .value = .{ .int64 = payload.decode() },
+ } };
+ },
+ .uint_large => {
+ const payload = self.extraData(Tag.UInt64, data);
+ return .{ .int = .{
+ .ty = payload.ty,
+ .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,
+ } },
+ };
+}
+
+/// Look op the result-id that corresponds to a particular
+/// ref.
+pub fn resultId(self: Self, ref: Ref) IdResult {
+ return self.items.items(.result_id)[@enumToInt(ref)];
+}
+
+/// Get the ref for a key that has already been added to the cache.
+fn get(self: *const Self, key: Key) Ref {
+ const adapter: Key.Adapter = .{ .self = self };
+ const index = self.map.getIndexAdapted(key, adapter).?;
+ return @intToEnum(Ref, index);
+}
+
+fn addExtra(self: *Self, spv: *Module, extra: anytype) !u32 {
+ const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
+ try self.extra.ensureUnusedCapacity(spv.gpa, fields.len);
+ return try self.addExtraAssumeCapacity(extra);
+}
+
+fn addExtraAssumeCapacity(self: *Self, extra: anytype) !u32 {
+ const payload_offset = @intCast(u32, self.extra.items.len);
+ inline for (@typeInfo(@TypeOf(extra)).Struct.fields) |field| {
+ const field_val = @field(extra, field.name);
+ const word = switch (field.type) {
+ u32 => field_val,
+ i32 => @bitCast(u32, field_val),
+ Ref => @enumToInt(field_val),
+ StorageClass => @enumToInt(field_val),
+ String => @enumToInt(field_val),
+ else => @compileError("Invalid type: " ++ @typeName(field.type)),
+ };
+ self.extra.appendAssumeCapacity(word);
+ }
+ return payload_offset;
+}
+
+fn extraData(self: Self, comptime T: type, offset: u32) T {
+ return self.extraDataTrail(T, offset).data;
+}
+
+fn extraDataTrail(self: Self, comptime T: type, offset: u32) struct { data: T, trail: u32 } {
+ var result: T = undefined;
+ const fields = @typeInfo(T).Struct.fields;
+ inline for (fields, 0..) |field, i| {
+ const word = self.extra.items[offset + i];
+ @field(result, field.name) = switch (field.type) {
+ u32 => word,
+ i32 => @bitCast(i32, word),
+ Ref => @intToEnum(Ref, word),
+ StorageClass => @intToEnum(StorageClass, word),
+ String => @intToEnum(String, word),
+ else => @compileError("Invalid type: " ++ @typeName(field.type)),
+ };
+ }
+ return .{
+ .data = result,
+ .trail = offset + @intCast(u32, fields.len),
+ };
+}
+
+/// Represents a reference to some null-terminated string.
+pub const String = enum(u32) {
+ none = std.math.maxInt(u32),
+ _,
+
+ pub const Adapter = struct {
+ self: *const Self,
+
+ pub fn eql(ctx: @This(), a: []const u8, _: void, b_index: usize) bool {
+ const offset = ctx.self.strings.values()[b_index];
+ const b = std.mem.sliceTo(ctx.self.string_bytes.items[offset..], 0);
+ return std.mem.eql(u8, a, b);
+ }
+
+ pub fn hash(ctx: @This(), a: []const u8) u32 {
+ _ = ctx;
+ var hasher = std.hash.Wyhash.init(0);
+ hasher.update(a);
+ return @truncate(u32, hasher.final());
+ }
+ };
+};
+
+/// Add a string to the cache. Must not contain any 0 values.
+pub fn addString(self: *Self, spv: *Module, str: []const u8) !String {
+ assert(std.mem.indexOfScalar(u8, str, 0) == null);
+ const adapter = String.Adapter{ .self = self };
+ const entry = try self.strings.getOrPutAdapted(spv.gpa, str, adapter);
+ if (!entry.found_existing) {
+ const offset = self.string_bytes.items.len;
+ try self.string_bytes.ensureUnusedCapacity(spv.gpa, 1 + str.len);
+ self.string_bytes.appendSliceAssumeCapacity(str);
+ self.string_bytes.appendAssumeCapacity(0);
+ entry.value_ptr.* = @intCast(u32, offset);
+ }
+
+ return @intToEnum(String, entry.index);
+}
+
+pub fn getString(self: *const Self, ref: String) ?[]const u8 {
+ return switch (ref) {
+ .none => null,
+ else => std.mem.sliceTo(self.string_bytes.items[self.strings.values()[@enumToInt(ref)]..], 0),
+ };
+}
diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig
index 4bd6c834ce..d53dcb4368 100644
--- a/src/codegen/spirv/Module.zig
+++ b/src/codegen/spirv/Module.zig
@@ -11,7 +11,8 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
-const ZigDecl = @import("../../Module.zig").Decl;
+const ZigModule = @import("../../Module.zig");
+const ZigDecl = ZigModule.Decl;
const spec = @import("spec.zig");
const Word = spec.Word;
@@ -20,11 +21,13 @@ const IdResult = spec.IdResult;
const IdResultType = spec.IdResultType;
const Section = @import("Section.zig");
-const Type = @import("type.zig").Type;
-const TypeCache = std.ArrayHashMapUnmanaged(Type, IdResultType, Type.ShallowHashContext32, true);
+const Cache = @import("Cache.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
@@ -126,7 +129,13 @@ sections: struct {
/// Annotation instructions (OpDecorate etc).
annotations: Section = .{},
/// Type declarations, constants, global variables
- /// Below this section, OpLine and OpNoLine is allowed.
+ /// 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 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.
types_globals_constants: Section = .{},
// Functions without a body - skip for now.
/// Regular function definitions.
@@ -141,11 +150,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) = .{},
-/// SPIR-V type cache. Note that according to SPIR-V spec section 2.8, Types and Variables, non-pointer
-/// non-aggrerate types (which includes matrices and vectors) must have a _unique_ representation in
-/// the final binary.
-/// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref).
-type_cache: TypeCache = .{},
+/// SPIR-V type- and constant cache. This structure is used to store information about these in a more
+/// efficient manner.
+cache: Cache = .{},
/// Set of Decls, referred to by Decl.Index.
decls: std.ArrayListUnmanaged(Decl) = .{},
@@ -163,7 +170,7 @@ globals: struct {
globals: std.AutoArrayHashMapUnmanaged(Decl.Index, Global) = .{},
/// This pseudo-section contains the initialization code for all the globals. Instructions from
/// here are reordered when flushing the module. Its contents should be part of the
- /// `types_globals_constants` SPIR-V section.
+ /// `types_globals_constants` SPIR-V section when the module is emitted.
section: Section = .{},
} = .{},
@@ -182,11 +189,10 @@ pub fn deinit(self: *Module) void {
self.sections.debug_strings.deinit(self.gpa);
self.sections.debug_names.deinit(self.gpa);
self.sections.annotations.deinit(self.gpa);
- self.sections.types_globals_constants.deinit(self.gpa);
self.sections.functions.deinit(self.gpa);
self.source_file_names.deinit(self.gpa);
- self.type_cache.deinit(self.gpa);
+ self.cache.deinit(self);
self.decls.deinit(self.gpa);
self.decl_deps.deinit(self.gpa);
@@ -213,6 +219,22 @@ pub fn idBound(self: Module) Word {
return self.next_result_id;
}
+pub fn resolve(self: *Module, key: CacheKey) !CacheRef {
+ return self.cache.resolve(self, key);
+}
+
+pub fn resultId(self: *const Module, ref: CacheRef) IdResult {
+ return self.cache.resultId(ref);
+}
+
+pub fn resolveId(self: *Module, key: CacheKey) !IdResult {
+ return self.resultId(try self.resolve(key));
+}
+
+pub fn resolveString(self: *Module, str: []const u8) !CacheString {
+ return try self.cache.addString(self, str);
+}
+
fn orderGlobalsInto(
self: *Module,
decl_index: Decl.Index,
@@ -324,6 +346,9 @@ 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.cache.materialize(self);
+ defer types_constants.deinit(self.gpa);
+
// Note: needs to be kept in order according to section 2.3!
const buffers = &[_][]const Word{
&header,
@@ -334,6 +359,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
self.sections.debug_strings.toWords(),
self.sections.debug_names.toWords(),
self.sections.annotations.toWords(),
+ types_constants.toWords(),
self.sections.types_globals_constants.toWords(),
globals.toWords(),
self.sections.functions.toWords(),
@@ -364,8 +390,8 @@ pub fn addFunction(self: *Module, decl_index: Decl.Index, func: Fn) !void {
/// Fetch the result-id of an OpString instruction that encodes the path of the source
/// file of the decl. This function may also emit an OpSource with source-level information regarding
/// the decl.
-pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef {
- const path = decl.getFileScope().sub_file_path;
+pub fn resolveSourceFileName(self: *Module, zig_module: *ZigModule, zig_decl: *ZigDecl) !IdRef {
+ const path = zig_decl.getFileScope(zig_module).sub_file_path;
const result = try self.source_file_names.getOrPut(self.gpa, path);
if (!result.found_existing) {
const file_result_id = self.allocId();
@@ -386,405 +412,71 @@ 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 intType(self: *Module, signedness: std.builtin.Signedness, bits: u16) !CacheRef {
+ return try self.resolve(.{ .int_type = .{
+ .signedness = signedness,
+ .bits = bits,
+ } });
}
-pub fn typeRefType(self: Module, ty_ref: Type.Ref) Type {
- return self.type_cache.keys()[@enumToInt(ty_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,
+ } });
+ const len_ref = try self.resolve(.{ .int = .{
+ .ty = len_ty_ref,
+ .value = .{ .uint64 = len },
+ } });
+ return try self.resolve(.{ .array_type = .{
+ .element_type = elem_ty_ref,
+ .length = len_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)];
+pub fn ptrType(
+ self: *Module,
+ child: CacheRef,
+ storage_class: spec.StorageClass,
+) !CacheRef {
+ return try self.resolve(.{ .ptr_type = .{
+ .storage_class = storage_class,
+ .child_type = child,
+ } });
}
-/// 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);
+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) },
},
- .@"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 constUndef(self: *Module, ty_ref: CacheRef) !IdRef {
+ return try self.resolveId(.{ .undef = .{ .ty = ty_ref } });
}
-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 constNull(self: *Module, ty_ref: CacheRef) !IdRef {
+ return try self.resolveId(.{ .null = .{ .ty = ty_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 constBool(self: *Module, ty_ref: CacheRef, value: bool) !IdRef {
+ return try self.resolveId(.{ .bool = .{ .ty = ty_ref, .value = value } });
}
-pub fn ptrType(
- self: *Module,
- child: Type.Ref,
- storage_class: spec.StorageClass,
- alignment: u32,
-) !Type.Ref {
- const ptr_payload = try self.arena.create(Type.Payload.Pointer);
- ptr_payload.* = .{
- .storage_class = storage_class,
- .child_type = child,
- .alignment = alignment,
- };
- return try self.resolveType(Type.initPayload(&ptr_payload.base));
-}
-
-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 emitConstant(
- self: *Module,
- ty_id: IdRef,
- result_id: IdRef,
- value: spec.LiteralContextDependentNumber,
-) !void {
- try self.sections.types_globals_constants.emit(self.gpa, .OpConstant, .{
- .id_result_type = ty_id,
+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.resultId(ty_ref),
.id_result = result_id,
- .value = value,
+ .constituents = members,
});
+ return result_id;
}
/// Decorate a result-id.
@@ -873,3 +565,22 @@ pub fn declareEntryPoint(self: *Module, decl_index: Decl.Index, name: []const u8
.name = try self.arena.dupe(u8, name),
});
}
+
+pub fn debugName(self: *Module, target: IdResult, comptime fmt: []const u8, args: anytype) !void {
+ const name = try std.fmt.allocPrint(self.gpa, fmt, args);
+ defer self.gpa.free(name);
+ try self.sections.debug_names.emit(self.gpa, .OpName, .{
+ .target = target,
+ .name = name,
+ });
+}
+
+pub fn memberDebugName(self: *Module, target: IdResult, member: u32, comptime fmt: []const u8, args: anytype) !void {
+ const name = try std.fmt.allocPrint(self.gpa, fmt, args);
+ defer self.gpa.free(name);
+ try self.sections.debug_names.emit(self.gpa, .OpMemberName, .{
+ .type = target,
+ .member = member,
+ .name = name,
+ });
+}
diff --git a/src/codegen/spirv/spec.zig b/src/codegen/spirv/spec.zig
index 60d16461cb..f73487f41f 100644
--- a/src/codegen/spirv/spec.zig
+++ b/src/codegen/spirv/spec.zig
@@ -1,6 +1,6 @@
//! This file is auto-generated by tools/gen_spirv_spec.zig.
-const Version = @import("std").builtin.Version;
+const Version = @import("std").SemanticVersion;
pub const Word = u32;
pub const IdResult = struct {
diff --git a/src/codegen/spirv/type.zig b/src/codegen/spirv/type.zig
deleted file mode 100644
index 2e1661c14e..0000000000
--- a/src/codegen/spirv/type.zig
+++ /dev/null
@@ -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,
- };
- };
-};