diff options
Diffstat (limited to 'src/codegen/spirv')
| -rw-r--r-- | src/codegen/spirv/Assembler.zig | 170 | ||||
| -rw-r--r-- | src/codegen/spirv/Cache.zig | 1046 | ||||
| -rw-r--r-- | src/codegen/spirv/Module.zig | 505 | ||||
| -rw-r--r-- | src/codegen/spirv/spec.zig | 2 | ||||
| -rw-r--r-- | src/codegen/spirv/type.zig | 567 |
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), §ion); + } + 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, - }; - }; -}; |
