From 96a66d14a16a89f759c1ed86a61cef710e3a7d05 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 29 May 2023 03:30:38 +0200 Subject: spirv: TypeConstantCache --- src/codegen/spirv/Module.zig | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'src/codegen/spirv/Module.zig') diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 5e7e6508fa..d5c293d912 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -125,6 +125,8 @@ sections: struct { // OpModuleProcessed - skip for now. /// Annotation instructions (OpDecorate etc). annotations: Section = .{}, + /// Type and constant declarations that are generated by the TypeConstantCache. + types_and_constants: Section = .{}, /// Type declarations, constants, global variables /// Below this section, OpLine and OpNoLine is allowed. types_globals_constants: Section = .{}, @@ -182,6 +184,7 @@ 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_and_constants(self.gpa); self.sections.types_globals_constants.deinit(self.gpa); self.sections.functions.deinit(self.gpa); @@ -334,6 +337,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(), + self.sections.types_constants.toWords(), self.sections.types_globals_constants.toWords(), globals.toWords(), self.sections.functions.toWords(), @@ -883,3 +887,12 @@ 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 debug.emit(self.gpa, .OpName, .{ + .target = result_id, + .name = name, + }); +} -- cgit v1.2.3 From b2a984cda67edd25fa2bf3ebe697649d561ff80f Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 29 May 2023 13:19:08 +0200 Subject: spirv: basic setup for using new type constant cache --- src/codegen/spirv.zig | 66 ++++++++++ src/codegen/spirv/Module.zig | 50 +++++--- src/codegen/spirv/TypeConstantCache.zig | 216 +++++++++++++++++++------------- 3 files changed, 229 insertions(+), 103 deletions(-) (limited to 'src/codegen/spirv/Module.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 2536158b36..9ee88ad6a4 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -22,6 +22,8 @@ const IdResultType = spec.IdResultType; const StorageClass = spec.StorageClass; const SpvModule = @import("spirv/Module.zig"); +const SpvRef = SpvModule.TypeConstantCache.Ref; + const SpvSection = @import("spirv/Section.zig"); const SpvType = @import("spirv/type.zig").Type; const SpvAssembler = @import("spirv/Assembler.zig"); @@ -1158,6 +1160,18 @@ pub const DeclGen = struct { return try self.spv.resolveType(try SpvType.int(self.spv.arena, signedness, backing_bits)); } + fn intType2(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvRef { + const backing_bits = self.backingIntBits(bits) orelse { + // TODO: Integers too big for any native type are represented as "composite integers": + // An array of largestSupportedIntBits. + return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits }); + }; + return try self.spv.resolve(.{ .int_type = .{ + .signedness = signedness, + .bits = backing_bits, + } }); + } + /// Create an integer type that represents 'usize'. fn sizeType(self: *DeclGen) !SpvType.Ref { return try self.intType(.unsigned, self.getTarget().ptrBitWidth()); @@ -1238,9 +1252,61 @@ pub const DeclGen = struct { return try self.spv.simpleStructType(members.slice()); } + fn resolveType2(self: *DeclGen, ty: Type, repr: Repr) !SpvRef { + const target = self.getTarget(); + switch (ty.zigTypeTag()) { + .Void, .NoReturn => return try self.spv.resolve(.void_type), + .Bool => switch (repr) { + .direct => return try self.spv.resolve(.bool_type), + .indirect => return try self.intType2(.unsigned, 1), + }, + .Int => { + const int_info = ty.intInfo(target); + return try self.intType2(int_info.signedness, int_info.bits); + }, + .Enum => { + var buffer: Type.Payload.Bits = undefined; + const tag_ty = ty.intTagType(&buffer); + return self.resolveType2(tag_ty, repr); + }, + .Float => { + // We can (and want) not really emulate floating points with other floating point types like with the integer types, + // so if the float is not supported, just return an error. + const bits = ty.floatBits(target); + const supported = switch (bits) { + 16 => Target.spirv.featureSetHas(target.cpu.features, .Float16), + // 32-bit floats are always supported (see spec, 2.16.1, Data rules). + 32 => true, + 64 => Target.spirv.featureSetHas(target.cpu.features, .Float64), + else => false, + }; + + if (!supported) { + return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits}); + } + + return try self.spv.resolve(.{ .float_type = .{ .bits = bits } }); + }, + .Array => { + const elem_ty = ty.childType(); + const elem_ty_ref = try self.resolveType2(elem_ty, .direct); + const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse { + return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()}); + }; + _ = total_len; + return self.spv.resolve(.{ .array_type = .{ + .element_type = elem_ty_ref, + .length = @intToEnum(SpvRef, 0), + } }); + }, + else => unreachable, // TODO + } + } + /// Turn a Zig type into a SPIR-V Type, and return a reference to it. fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!SpvType.Ref { log.debug("resolveType: ty = {}", .{ty.fmt(self.module)}); + _ = try self.resolveType2(ty, repr); const target = self.getTarget(); switch (ty.zigTypeTag()) { .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)), diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index d5c293d912..9fcb27b366 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -21,6 +21,7 @@ const IdResultType = spec.IdResultType; const Section = @import("Section.zig"); const Type = @import("type.zig").Type; +pub const TypeConstantCache = @import("TypeConstantCache.zig"); const TypeCache = std.ArrayHashMapUnmanaged(Type, IdResultType, Type.ShallowHashContext32, true); @@ -125,8 +126,16 @@ sections: struct { // OpModuleProcessed - skip for now. /// Annotation instructions (OpDecorate etc). annotations: Section = .{}, - /// Type and constant declarations that are generated by the TypeConstantCache. - types_and_constants: Section = .{}, + /// Global variable declarations + /// From this section, OpLine and OpNoLine is allowed. + /// According to the SPIR-V documentation, this section normally + /// also holds type and constant instructions. These are managed + /// via the tc_cache instead, which is the sole structure that + /// 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. + /// The only thing that needs to be kept here is OpUndef + globals: Section = .{}, /// Type declarations, constants, global variables /// Below this section, OpLine and OpNoLine is allowed. types_globals_constants: Section = .{}, @@ -143,11 +152,10 @@ 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. +tc_cache: TypeConstantCache = .{}, /// Set of Decls, referred to by Decl.Index. decls: std.ArrayListUnmanaged(Decl) = .{}, @@ -165,7 +173,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 = .{}, } = .{}, @@ -184,12 +192,11 @@ 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_and_constants(self.gpa); - self.sections.types_globals_constants.deinit(self.gpa); + self.sections.globals.deinit(self.gpa); self.sections.functions.deinit(self.gpa); self.source_file_names.deinit(self.gpa); - self.type_cache.deinit(self.gpa); + self.tc_cache.deinit(self); self.decls.deinit(self.gpa); self.decl_deps.deinit(self.gpa); @@ -216,6 +223,18 @@ pub fn idBound(self: Module) Word { return self.next_result_id; } +pub fn resolve(self: *Module, key: TypeConstantCache.Key) !TypeConstantCache.Ref { + return self.tc_cache.resolve(self, key); +} + +pub fn resultId(self: *Module, ref: TypeConstantCache.Ref) IdResult { + return self.tc_cache.resultId(ref); +} + +pub fn resolveId(self: *Module, key: TypeConstantCache.Key) !IdResult { + return self.resultId(try self.resolve(key)); +} + fn orderGlobalsInto( self: *Module, decl_index: Decl.Index, @@ -327,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.tc_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, @@ -337,8 +359,8 @@ pub fn flush(self: *Module, file: std.fs.File) !void { self.sections.debug_strings.toWords(), self.sections.debug_names.toWords(), self.sections.annotations.toWords(), - self.sections.types_constants.toWords(), - self.sections.types_globals_constants.toWords(), + types_constants.toWords(), + self.sections.globals.toWords(), globals.toWords(), self.sections.functions.toWords(), }; @@ -891,8 +913,8 @@ pub fn declareEntryPoint(self: *Module, decl_index: Decl.Index, name: []const u8 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 debug.emit(self.gpa, .OpName, .{ - .target = result_id, + try self.sections.debug_names.emit(self.gpa, .OpName, .{ + .target = target, .name = name, }); } diff --git a/src/codegen/spirv/TypeConstantCache.zig b/src/codegen/spirv/TypeConstantCache.zig index 62bc817fbb..0f92665ba5 100644 --- a/src/codegen/spirv/TypeConstantCache.zig +++ b/src/codegen/spirv/TypeConstantCache.zig @@ -1,16 +1,19 @@ //! This file implements an InternPool-like structure that caches -//! SPIR-V types and constants. -//! In the case of SPIR-V, the type- and constant instructions -//! describe the type and constant fully. This means we can save -//! memory by representing these items directly in spir-v code, -//! and decoding that when required. -//! This does not work for OpDecorate instructions though, and for -//! those we keep some additional metadata. +//! 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 Allocator = std.mem.Allocator; -const Section = @import("section.zig"); +const Section = @import("Section.zig"); const Module = @import("Module.zig"); const spec = @import("spec.zig"); @@ -21,7 +24,7 @@ const Self = @This(); map: std.AutoArrayHashMapUnmanaged(void, void) = .{}, items: std.MultiArrayList(Item) = .{}, -extra: std.ArrayHashMapUnmanaged(u32) = .{}, +extra: std.ArrayListUnmanaged(u32) = .{}, const Item = struct { tag: Tag, @@ -32,6 +35,7 @@ const Item = struct { }; const Tag = enum { + // -- Types /// Simple type that has no additional data. /// data is SimpleType. type_simple, @@ -45,13 +49,18 @@ const Tag = enum { /// data is number of bits type_float, /// Vector type - /// data is payload to Key.VectorType + /// data is payload to VectorType type_vector, + /// Array type + /// data is payload to ArrayType + type_array, - const SimpleType = enum { - void, - bool, - }; + // -- Values + + const SimpleType = enum { void, bool }; + + const VectorType = Key.VectorType; + const ArrayType = Key.ArrayType; }; pub const Ref = enum(u32) { _ }; @@ -61,11 +70,15 @@ pub const Ref = enum(u32) { _ }; /// database: Values described for this structure are ephemeral and stored /// in a more memory-efficient manner internally. pub const Key = union(enum) { - void_ty, - bool_ty, - int_ty: IntType, - float_ty: FloatType, - vector_ty: VectorType, + // -- Types + void_type, + bool_type, + int_type: IntType, + float_type: FloatType, + vector_type: VectorType, + array_type: ArrayType, + + // -- values pub const IntType = std.builtin.Type.Int; pub const FloatType = std.builtin.Type.Float; @@ -75,55 +88,66 @@ pub const Key = union(enum) { 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, + }; + fn hash(self: Key) u32 { var hasher = std.hash.Wyhash.init(0); std.hash.autoHash(&hasher, self); return @truncate(u32, hasher.final()); } - fn eql(a: Key, b: Key) u32 { + fn eql(a: Key, b: Key) bool { return std.meta.eql(a, b); } pub const Adapter = struct { self: *const Self, - pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: u32) bool { + pub fn eql(ctx: @This(), a: Key, b_void: void, b_index: usize) bool { _ = b_void; - return ctx.self.lookup(@intToEnum(Ref, b_map_index)).eql(a); + return ctx.self.lookup(@intToEnum(Ref, b_index)).eql(a); } pub fn hash(ctx: @This(), a: Key) u32 { - return ctx.self.hash(a); + _ = ctx; + return a.hash(); } }; fn toSimpleType(self: Key) Tag.SimpleType { return switch (self) { - .void_ty => .void, - .bool_ty => .bool, + .void_type => .void, + .bool_type => .bool, else => unreachable, }; } }; -pub fn deinit(self: *Self, spv: Module) void { +pub fn deinit(self: *Self, spv: *const Module) void { self.map.deinit(spv.gpa); self.items.deinit(spv.gpa); self.extra.deinit(spv.gpa); } /// Actually materialize the database into spir-v instructions. -// TODO: This should generate decorations as well as regular instructions. -// Important is that these are generated in-order, but that should be fine. -pub fn finalize(self: *Self, spv: *Module) !void { - // This function should really be the only one that modifies spv.types_and_constants. - // TODO: Make this function return the section instead. - std.debug.assert(spv.sections.types_and_constants.instructions.items.len == 0); - +/// 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: *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)); + try self.emit(spv, result_id, @intToEnum(Ref, index), §ion); } + return section; } fn emit( @@ -131,97 +155,104 @@ fn emit( spv: *Module, result_id: IdResult, ref: Ref, + section: *Section, ) !void { - const tc = &spv.sections.types_and_constants; const key = self.lookup(ref); switch (key) { - .void_ty => { - try tc.emit(spv.gpa, .OpTypeVoid, .{ .id_result = result_id }); + .void_type => { + try section.emit(spv.gpa, .OpTypeVoid, .{ .id_result = result_id }); try spv.debugName(result_id, "void", .{}); }, - .bool_ty => { - try tc.emit(spv.gpa, .OpTypeBool, .{ .id_result = result_id }); + .bool_type => { + try section.emit(spv.gpa, .OpTypeBool, .{ .id_result = result_id }); try spv.debugName(result_id, "bool", .{}); }, - .int_ty => |int| { - try tc.emit(spv.gpa, .OpTypeInt, .{ + .int_type => |int| { + try section.emit(spv.gpa, .OpTypeInt, .{ .id_result = result_id, .width = int.bits, .signedness = switch (int.signedness) { - .unsigned => 0, + .unsigned => @as(spec.Word, 0), .signed => 1, }, }); const ui: []const u8 = switch (int.signedness) { - 0 => "u", - 1 => "i", - else => unreachable, + .unsigned => "u", + .signed => "i", }; try spv.debugName(result_id, "{s}{}", .{ ui, int.bits }); }, - .float_ty => |float| { - try tc.emit(spv.gpa, .OpTypeFloat, .{ + .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_ty => |vector| { - try tc.emit(spv.gpa, .OpTypeVector, .{ + .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 } }); + } + }, } } /// 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 add(self: *Self, spv: *Module, key: Key) !Ref { +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(); - try self.items.ensureUnusedCapacity(spv.gpa, 1); - switch (key) { - inline .void_ty, .bool_ty => { - self.items.appendAssumeCapacity(.{ - .tag = .type_simple, - .result_id = result_id, - .data = @enumToInt(key.toSimpleType()), - }); + const item: Item = switch (key) { + inline .void_type, .bool_type => .{ + .tag = .type_simple, + .result_id = result_id, + .data = @enumToInt(key.toSimpleType()), }, - .int_ty => |int| { + .int_type => |int| blk: { const t: Tag = switch (int.signedness) { .signed => .type_int_signed, .unsigned => .type_int_unsigned, }; - self.items.appendAssumeCapacity(.{ + break :blk .{ .tag = t, .result_id = result_id, .data = int.bits, - }); + }; }, - .float_ty => |float| { - self.items.appendAssumeCapacity(.{ - .tag = .type_float, - .result_id = result_id, - .data = float.bits, - }); + .float_type => |float| .{ + .tag = .type_float, + .result_id = result_id, + .data = float.bits, }, - .vector_ty => |vec| { - const payload = try self.addExtra(vec); - self.items.appendAssumeCapacity(.{ - .tag = .type_vector, - .result_id = result_id, - .data = payload, - }); + .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), + }, + }; + try self.items.append(spv.gpa, item); return @intToEnum(Ref, entry.index); } @@ -238,36 +269,35 @@ pub fn lookup(self: *const Self, ref: Ref) Key { const data = item.data; return switch (item.tag) { .type_simple => switch (@intToEnum(Tag.SimpleType, data)) { - .void => .void_ty, - .bool => .bool_ty, + .void => .void_type, + .bool => .bool_type, }, - .type_int_signed => .{ .int_ty = .{ + .type_int_signed => .{ .int_type = .{ .signedness = .signed, .bits = @intCast(u16, data), } }, - .type_int_unsigned => .{ .int_ty = .{ + .type_int_unsigned => .{ .int_type = .{ .signedness = .unsigned, .bits = @intCast(u16, data), } }, - .type_float => .{ .float_ty = .{ + .type_float => .{ .float_type = .{ .bits = @intCast(u16, data), } }, - .type_vector => .{ - .vector_ty = self.extraData(Key.VectorType, data), - }, + .type_vector => .{ .vector_type = self.extraData(Tag.VectorType, data) }, + .type_array => .{ .array_type = self.extraData(Tag.ArrayType, data) }, }; } -fn addExtra(self: *Self, gpa: Allocator, extra: anytype) !u32 { +fn addExtra(self: *Self, spv: *Module, extra: anytype) !u32 { const fields = @typeInfo(@TypeOf(extra)).Struct.fields; - try self.extra.ensureUnusedCapacity(gpa, fields.len); - try self.addExtraAssumeCapacity(extra); + 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(field, field.name); + const field_val = @field(extra, field.name); const word = switch (field.type) { u32 => field_val, Ref => @enumToInt(field_val), @@ -279,8 +309,13 @@ fn addExtraAssumeCapacity(self: *Self, extra: anytype) !u32 { } 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; - inline for (@typeInfo(T).Struct.fields, 0..) |field, i| { + 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, @@ -288,5 +323,8 @@ fn extraData(self: Self, comptime T: type, offset: u32) T { else => @compileError("Invalid type: " ++ @typeName(field.type)), }; } - return result; + return .{ + .data = result, + .trail = offset + @intCast(u32, fields.len), + }; } -- cgit v1.2.3 From a72179fed0f20619f6787c760fae00ece16e9d20 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 29 May 2023 19:25:48 +0200 Subject: spirv: translate structs to cache key --- src/codegen/spirv/Module.zig | 10 ++++++ src/codegen/spirv/TypeConstantCache.zig | 62 +++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 3 deletions(-) (limited to 'src/codegen/spirv/Module.zig') diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 9fcb27b366..54b868aba2 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -918,3 +918,13 @@ pub fn debugName(self: *Module, target: IdResult, comptime fmt: []const u8, args .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/TypeConstantCache.zig b/src/codegen/spirv/TypeConstantCache.zig index 2a94359f15..aec1ba226f 100644 --- a/src/codegen/spirv/TypeConstantCache.zig +++ b/src/codegen/spirv/TypeConstantCache.zig @@ -11,6 +11,7 @@ //! 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"); @@ -68,8 +69,11 @@ const Tag = enum { /// data is child type type_ptr_function, /// Simple pointer type that does not have any decorations. - /// data is SimplePointerType + /// 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, // -- Values /// Value of type u8 @@ -107,7 +111,7 @@ const Tag = enum { const ArrayType = Key.ArrayType; // Trailing: - // - [param_len]Ref: parameter types + // - [param_len]Ref: parameter types. const FunctionType = struct { param_len: u32, return_type: Ref, @@ -118,6 +122,13 @@ const Tag = enum { child_type: Ref, }; + /// Trailing: + /// - [members_len]Ref: Member types. + const SimpleStructType = struct { + /// Number of members that this struct has. + members_len: u32, + }; + const Float64 = struct { // Low-order 32 bits of the value. low: u32, @@ -201,6 +212,7 @@ pub const Key = union(enum) { array_type: ArrayType, function_type: FunctionType, ptr_type: PointerType, + struct_type: StructType, // -- values int: Int, @@ -238,6 +250,12 @@ pub const Key = union(enum) { // - MaxByteOffset, }; + pub const StructType = struct { + // TODO: Decorations. + /// The type of each member. + member_types: []const Ref, + }; + pub const Int = struct { /// The type: any bitness integer. ty: Ref, @@ -304,6 +322,11 @@ pub const Key = union(enum) { std.hash.autoHash(&hasher, param_type); } }, + .struct_type => |struct_type| { + for (struct_type.member_types) |member_type| { + std.hash.autoHash(&hasher, member_type); + } + }, inline else => |key| std.hash.autoHash(&hasher, key), } return @truncate(u32, hasher.final()); @@ -318,10 +341,14 @@ pub const Key = union(enum) { } return switch (a) { .function_type => |a_func| { - const b_func = a.function_type; + 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 std.mem.eql(Ref, a_struct.member_types, b_struct.member_types); + }, // TODO: Unroll? else => std.meta.eql(a, b), }; @@ -442,6 +469,14 @@ fn emit( }); // 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)); + } + // TODO: Decorations? + }, .int => |int| { const int_type = self.lookup(int.ty).int_type; const ty_id = self.resultId(int.ty); @@ -552,6 +587,18 @@ pub fn resolve(self: *Self, spv: *Module, key: Key) !Ref { }), }, }, + .struct_type => |struct_type| blk: { + const extra = try self.addExtra(spv, Tag.SimpleStructType{ + .members_len = @intCast(u32, struct_type.member_types.len), + }); + try self.extra.appendSlice(spv.gpa, @ptrCast([]const u32, struct_type.member_types)); + + 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) { @@ -687,6 +734,15 @@ pub fn lookup(self: *const Self, ref: Ref) Key { }, }; }, + .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 = .{ + .member_types = member_types, + }, + }; + }, .float16 => .{ .float = .{ .ty = self.get(.{ .float_type = .{ .bits = 16 } }), .value = .{ .float16 = @bitCast(f16, @intCast(u16, data)) }, -- cgit v1.2.3 From fcb422585c1a9e91933ff998417eb8682a4ffbcc Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 29 May 2023 20:45:54 +0200 Subject: spirv: translate remaining types --- src/codegen/spirv.zig | 215 +++++++++++++++++++++++++++++--- src/codegen/spirv/Module.zig | 19 +++ src/codegen/spirv/TypeConstantCache.zig | 16 +-- 3 files changed, 227 insertions(+), 23 deletions(-) (limited to 'src/codegen/spirv/Module.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index fa429c024b..b0e4c8e950 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -22,7 +22,8 @@ const IdResultType = spec.IdResultType; const StorageClass = spec.StorageClass; const SpvModule = @import("spirv/Module.zig"); -const SpvRef = SpvModule.TypeConstantCache.Ref; +const SpvCacheRef = SpvModule.TypeConstantCache.Ref; +const SpvCacheString = SpvModule.TypeConstantCache.String; const SpvSection = @import("spirv/Section.zig"); const SpvType = @import("spirv/type.zig").Type; @@ -1160,7 +1161,7 @@ pub const DeclGen = struct { return try self.spv.resolveType(try SpvType.int(self.spv.arena, signedness, backing_bits)); } - fn intType2(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvRef { + fn intType2(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvCacheRef { const backing_bits = self.backingIntBits(bits) orelse { // TODO: Integers too big for any native type are represented as "composite integers": // An array of largestSupportedIntBits. @@ -1177,7 +1178,7 @@ pub const DeclGen = struct { return try self.intType(.unsigned, self.getTarget().ptrBitWidth()); } - fn sizeType2(self: *DeclGen) !SpvRef { + fn sizeType2(self: *DeclGen) !SpvCacheRef { return try self.intType2(.unsigned, self.getTarget().ptrBitWidth()); } @@ -1256,7 +1257,91 @@ pub const DeclGen = struct { return try self.spv.simpleStructType(members.slice()); } - fn resolveType2(self: *DeclGen, ty: Type, repr: Repr) !SpvRef { + /// Generate a union type, optionally with a known field. If the tag alignment is greater + /// than that of the payload, a regular union (non-packed, with both tag and payload), will + /// be generated as follows: + /// If the active field is known: + /// struct { + /// tag: TagType, + /// payload: ActivePayloadType, + /// payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8, + /// padding: [padding_size]u8, + /// } + /// If the payload alignment is greater than that of the tag: + /// struct { + /// payload: ActivePayloadType, + /// payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8, + /// tag: TagType, + /// padding: [padding_size]u8, + /// } + /// If the active payload is unknown, it will default back to the most aligned field. This is + /// to make sure that the overal struct has the correct alignment in spir-v. + /// If any of the fields' size is 0, it will be omitted. + /// NOTE: When the active field is set to something other than the most aligned field, the + /// resulting struct will be *underaligned*. + fn resolveUnionType2(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !SpvCacheRef { + const target = self.getTarget(); + const layout = ty.unionGetLayout(target); + const union_ty = ty.cast(Type.Payload.Union).?.data; + + if (union_ty.layout == .Packed) { + return self.todo("packed union types", .{}); + } + + if (layout.payload_size == 0) { + // No payload, so represent this as just the tag type. + return try self.resolveType2(union_ty.tag_ty, .indirect); + } + + var member_types = std.BoundedArray(SpvCacheRef, 4){}; + var member_names = std.BoundedArray(SpvCacheString, 4){}; + + const has_tag = layout.tag_size != 0; + const tag_first = layout.tag_align >= layout.payload_align; + const u8_ty_ref = try self.intType2(.unsigned, 8); // TODO: What if Int8Type is not enabled? + + if (has_tag and tag_first) { + const tag_ty_ref = try self.resolveType2(union_ty.tag_ty, .indirect); + member_types.appendAssumeCapacity(tag_ty_ref); + member_names.appendAssumeCapacity(try self.spv.resolveString("tag")); + } + + const active_field = maybe_active_field orelse layout.most_aligned_field; + const active_field_ty = union_ty.fields.values()[active_field].ty; + + const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: { + const active_payload_ty_ref = try self.resolveType2(active_field_ty, .indirect); + member_types.appendAssumeCapacity(active_payload_ty_ref); + member_names.appendAssumeCapacity(try self.spv.resolveString("payload")); + break :blk active_field_ty.abiSize(target); + } else 0; + + const payload_padding_len = layout.payload_size - active_field_size; + if (payload_padding_len != 0) { + const payload_padding_ty_ref = try self.spv.arrayType2(@intCast(u32, payload_padding_len), u8_ty_ref); + member_types.appendAssumeCapacity(payload_padding_ty_ref); + member_names.appendAssumeCapacity(try self.spv.resolveString("payload_padding")); + } + + if (has_tag and !tag_first) { + const tag_ty_ref = try self.resolveType2(union_ty.tag_ty, .indirect); + member_types.appendAssumeCapacity(tag_ty_ref); + member_names.appendAssumeCapacity(try self.spv.resolveString("tag")); + } + + if (layout.padding != 0) { + const padding_ty_ref = try self.spv.arrayType2(layout.padding, u8_ty_ref); + member_types.appendAssumeCapacity(padding_ty_ref); + member_names.appendAssumeCapacity(try self.spv.resolveString("padding")); + } + + return try self.spv.resolve(.{ .struct_type = .{ + .member_types = member_types.slice(), + .member_names = member_names.slice(), + } }); + } + + fn resolveType2(self: *DeclGen, ty: Type, repr: Repr) Error!SpvCacheRef { const target = self.getTarget(); switch (ty.zigTypeTag()) { .Void, .NoReturn => return try self.spv.resolve(.void_type), @@ -1297,15 +1382,7 @@ pub const DeclGen = struct { const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse { return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()}); }; - const len_ty_ref = try self.intType2(.unsigned, 32); - const len_ref = try self.spv.resolve(.{ .int = .{ - .ty = len_ty_ref, - .value = .{ .uint64 = total_len }, - } }); - return try self.spv.resolve(.{ .array_type = .{ - .element_type = elem_ty_ref, - .length = len_ref, - } }); + return self.spv.arrayType2(total_len, elem_ty_ref); }, .Fn => switch (repr) { .direct => { @@ -1313,7 +1390,7 @@ pub const DeclGen = struct { if (ty.fnIsVarArgs()) return self.fail("VarArgs functions are unsupported for SPIR-V", .{}); - const param_ty_refs = try self.gpa.alloc(SpvRef, ty.fnParamLen()); + const param_ty_refs = try self.gpa.alloc(SpvCacheRef, ty.fnParamLen()); defer self.gpa.free(param_ty_refs); for (param_ty_refs, 0..) |*param_type, i| { param_type.* = try self.resolveType2(ty.fnParamType(i), .direct); @@ -1360,8 +1437,116 @@ pub const DeclGen = struct { .component_count = @intCast(u32, ty.vectorLen()), } }); }, + .Struct => { + if (ty.isSimpleTupleOrAnonStruct()) { + unreachable; // TODO + } - else => unreachable, // TODO + const struct_ty = ty.castTag(.@"struct").?.data; + + if (struct_ty.layout == .Packed) { + return try self.resolveType2(struct_ty.backing_int_ty, .direct); + } + + const member_types = try self.gpa.alloc(SpvCacheRef, struct_ty.fields.count()); + defer self.gpa.free(member_types); + + const member_names = try self.gpa.alloc(SpvCacheString, struct_ty.fields.count()); + defer self.gpa.free(member_names); + + // const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, struct_ty.fields.count()); + var member_index: usize = 0; + for (struct_ty.fields.values(), 0..) |field, i| { + if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; + + member_types[member_index] = try self.resolveType2(field.ty, .indirect); + member_names[member_index] = try self.spv.resolveString(struct_ty.fields.keys()[i]); + member_index += 1; + } + + const name = try struct_ty.getFullyQualifiedName(self.module); + defer self.module.gpa.free(name); + + return try self.spv.resolve(.{ .struct_type = .{ + .name = try self.spv.resolveString(name), + .member_types = member_types[0..member_index], + .member_names = member_names[0..member_index], + } }); + }, + .Optional => { + var buf: Type.Payload.ElemType = undefined; + const payload_ty = ty.optionalChild(&buf); + if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { + // Just use a bool. + // Note: Always generate the bool with indirect format, to save on some sanity + // Perform the conversion to a direct bool when the field is extracted. + return try self.resolveType2(Type.bool, .indirect); + } + + const payload_ty_ref = try self.resolveType2(payload_ty, .indirect); + if (ty.optionalReprIsPayload()) { + // Optional is actually a pointer or a slice. + return payload_ty_ref; + } + + const bool_ty_ref = try self.resolveType2(Type.bool, .indirect); + + return try self.spv.resolve(.{ .struct_type = .{ + .member_types = &.{ payload_ty_ref, bool_ty_ref }, + .member_names = &.{ + try self.spv.resolveString("payload"), + try self.spv.resolveString("valid"), + }, + } }); + }, + .Union => return try self.resolveUnionType2(ty, null), + .ErrorSet => return try self.intType2(.unsigned, 16), + .ErrorUnion => { + const payload_ty = ty.errorUnionPayload(); + const error_ty_ref = try self.resolveType2(Type.anyerror, .indirect); + + const eu_layout = self.errorUnionLayout(payload_ty); + if (!eu_layout.payload_has_bits) { + return error_ty_ref; + } + + const payload_ty_ref = try self.resolveType2(payload_ty, .indirect); + + var member_types: [2]SpvCacheRef = undefined; + var member_names: [2]SpvCacheString = undefined; + if (eu_layout.error_first) { + // Put the error first + member_types = .{ error_ty_ref, payload_ty_ref }; + member_names = .{ + try self.spv.resolveString("error"), + try self.spv.resolveString("payload"), + }; + // TODO: ABI padding? + } else { + // Put the payload first. + member_types = .{ payload_ty_ref, error_ty_ref }; + member_names = .{ + try self.spv.resolveString("payload"), + try self.spv.resolveString("error"), + }; + // TODO: ABI padding? + } + + return try self.spv.resolve(.{ .struct_type = .{ + .member_types = &member_types, + .member_names = &member_names, + } }); + }, + + .Null, + .Undefined, + .EnumLiteral, + .ComptimeFloat, + .ComptimeInt, + .Type, + => unreachable, // Must be comptime. + + else => |tag| return self.todo("Implement zig type '{}'", .{tag}), } } diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 54b868aba2..85d0bbf78c 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -235,6 +235,10 @@ pub fn resolveId(self: *Module, key: TypeConstantCache.Key) !IdResult { return self.resultId(try self.resolve(key)); } +pub fn resolveString(self: *Module, str: []const u8) !TypeConstantCache.String { + return try self.tc_cache.addString(self, str); +} + fn orderGlobalsInto( self: *Module, decl_index: Decl.Index, @@ -769,6 +773,21 @@ pub fn simpleStructType(self: *Module, members: []const Type.Payload.Struct.Memb return try self.resolveType(Type.initPayload(&payload.base)); } +pub fn arrayType2(self: *Module, len: u32, elem_ty_ref: TypeConstantCache.Ref) !TypeConstantCache.Ref { + 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, + } }); +} + pub fn arrayType(self: *Module, len: u32, ty: Type.Ref) !Type.Ref { const payload = try self.arena.create(Type.Payload.Array); payload.* = .{ diff --git a/src/codegen/spirv/TypeConstantCache.zig b/src/codegen/spirv/TypeConstantCache.zig index b8258b8819..d5bc888276 100644 --- a/src/codegen/spirv/TypeConstantCache.zig +++ b/src/codegen/spirv/TypeConstantCache.zig @@ -263,7 +263,7 @@ pub const Key = union(enum) { pub const StructType = struct { // TODO: Decorations. /// The name of the structure. Can be `.none`. - name: String, + name: String = .none, /// The type of each member. member_types: []const Ref, /// Name for each member. May be omitted. @@ -922,14 +922,14 @@ pub const String = enum(u32) { self: *const Self, pub fn eql(ctx: @This(), a: []const u8, _: void, b_index: usize) bool { - const offset = ctx.self.string_map.values()[b_index]; + 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; - const hasher = std.hash.Wyhash.init(0); + var hasher = std.hash.Wyhash.init(0); hasher.update(a); return @truncate(u32, hasher.final()); } @@ -937,16 +937,16 @@ pub const String = enum(u32) { }; /// Add a string to the cache. Must not contain any 0 values. -pub fn addString(self: *Self, spv: *Module, str: []const u8) String { +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(1 + str.len); - self.string_bytes.appendAssumeCapacity(str); - self.string_bytes.append(0); - entry.value_ptr.* = offset; + 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); -- cgit v1.2.3 From 0552a8b11f973fc9621971e2130c25ad3a4af0ad Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 29 May 2023 23:54:09 +0200 Subject: spirv: translate remaining types --- src/codegen/spirv.zig | 581 +++++++------------------------- src/codegen/spirv/Assembler.zig | 170 ++-------- src/codegen/spirv/Module.zig | 447 +++--------------------- src/codegen/spirv/TypeConstantCache.zig | 86 +++++ src/codegen/spirv/type.zig | 567 ------------------------------- 5 files changed, 295 insertions(+), 1556 deletions(-) delete mode 100644 src/codegen/spirv/type.zig (limited to 'src/codegen/spirv/Module.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index b0e4c8e950..bedff8cc56 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -22,11 +22,10 @@ const IdResultType = spec.IdResultType; const StorageClass = spec.StorageClass; const SpvModule = @import("spirv/Module.zig"); -const SpvCacheRef = SpvModule.TypeConstantCache.Ref; -const SpvCacheString = SpvModule.TypeConstantCache.String; +const CacheRef = SpvModule.CacheRef; +const CacheString = SpvModule.CacheString; const SpvSection = @import("spirv/Section.zig"); -const SpvType = @import("spirv/type.zig").Type; const SpvAssembler = @import("spirv/Assembler.zig"); const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef); @@ -380,74 +379,23 @@ pub const DeclGen = struct { }; } - fn genConstInt(self: *DeclGen, ty_ref: SpvType.Ref, result_id: IdRef, value: anytype) !void { - const ty = self.spv.typeRefType(ty_ref); - const ty_id = self.typeId(ty_ref); - - const Lit = spec.LiteralContextDependentNumber; - const literal = switch (ty.intSignedness()) { - .signed => switch (ty.intFloatBits()) { - 1...32 => Lit{ .int32 = @intCast(i32, value) }, - 33...64 => Lit{ .int64 = @intCast(i64, value) }, - else => unreachable, // TODO: composite integer literals - }, - .unsigned => switch (ty.intFloatBits()) { - 1...32 => Lit{ .uint32 = @intCast(u32, value) }, - 33...64 => Lit{ .uint64 = @intCast(u64, value) }, - else => unreachable, - }, - }; - - try self.spv.emitConstant(ty_id, result_id, literal); - } - - fn constInt(self: *DeclGen, ty_ref: SpvType.Ref, value: anytype) !IdRef { - const result_id = self.spv.allocId(); - try self.genConstInt(ty_ref, result_id, value); - return result_id; - } - - fn constUndef(self: *DeclGen, ty_ref: SpvType.Ref) !IdRef { - const result_id = self.spv.allocId(); - try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpUndef, .{ - .id_result_type = self.typeId(ty_ref), - .id_result = result_id, - }); - return result_id; - } - - fn constNull(self: *DeclGen, ty_ref: SpvType.Ref) !IdRef { - const result_id = self.spv.allocId(); - try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantNull, .{ - .id_result_type = self.typeId(ty_ref), - .id_result = result_id, - }); - return result_id; - } - + /// Emits a bool constant in a particular representation. fn constBool(self: *DeclGen, value: bool, repr: Repr) !IdRef { switch (repr) { .indirect => { const int_ty_ref = try self.intType(.unsigned, 1); - return self.constInt(int_ty_ref, @boolToInt(value)); + return self.spv.constInt(int_ty_ref, @boolToInt(value)); }, .direct => { const bool_ty_ref = try self.resolveType(Type.bool, .direct); - const result_id = self.spv.allocId(); - const operands = .{ .id_result_type = self.typeId(bool_ty_ref), .id_result = result_id }; - if (value) { - try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantTrue, operands); - } else { - try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantFalse, operands); - } - return result_id; + return self.spv.constBool(bool_ty_ref, value); }, } } /// Construct a struct at runtime. /// result_ty_ref must be a struct type. - fn constructStruct(self: *DeclGen, result_ty_ref: SpvType.Ref, constituents: []const IdRef) !IdRef { + fn constructStruct(self: *DeclGen, result_ty_ref: CacheRef, constituents: []const IdRef) !IdRef { // The Khronos LLVM-SPIRV translator crashes because it cannot construct structs which' // operands are not constant. // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349 @@ -456,11 +404,13 @@ pub const DeclGen = struct { const ptr_composite_id = try self.alloc(result_ty_ref, null); // Note: using 32-bit ints here because usize crashes the translator as well const index_ty_ref = try self.intType(.unsigned, 32); - const spv_composite_ty = self.spv.typeRefType(result_ty_ref); - const members = spv_composite_ty.payload(.@"struct").members; - for (constituents, members, 0..) |constitent_id, member, index| { - const index_id = try self.constInt(index_ty_ref, index); - const ptr_member_ty_ref = try self.spv.ptrType(member.ty, .Generic, 0); + + const spv_composite_ty = self.spv.cache.lookup(result_ty_ref).struct_type; + const member_types = spv_composite_ty.member_types; + + for (constituents, member_types, 0..) |constitent_id, member_ty_ref, index| { + const index_id = try self.spv.constInt(index_ty_ref, index); + const ptr_member_ty_ref = try self.spv.ptrType(member_ty_ref, .Generic); const ptr_id = try self.accessChain(ptr_member_ty_ref, ptr_composite_id, &.{index_id}); try self.func.body.emit(self.spv.gpa, .OpStore, .{ .pointer = ptr_id, @@ -481,11 +431,11 @@ pub const DeclGen = struct { dg: *DeclGen, /// Cached reference of the u32 type. - u32_ty_ref: SpvType.Ref, + u32_ty_ref: CacheRef, /// Cached type id of the u32 type. u32_ty_id: IdRef, /// The members of the resulting structure type - members: std.ArrayList(SpvType.Payload.Struct.Member), + members: std.ArrayList(CacheRef), /// The initializers of each of the members. initializers: std.ArrayList(IdRef), /// The current size of the structure. Includes @@ -519,7 +469,7 @@ pub const DeclGen = struct { const result_id = self.dg.spv.allocId(); // TODO: Integrate with caching mechanism try self.dg.spv.emitConstant(self.u32_ty_id, result_id, .{ .uint32 = word }); - try self.members.append(.{ .ty = self.u32_ty_ref }); + try self.members.append(self.u32_ty_ref); try self.initializers.append(result_id); self.partial_word.len = 0; @@ -555,7 +505,7 @@ pub const DeclGen = struct { } } - fn addPtr(self: *@This(), ptr_ty_ref: SpvType.Ref, ptr_id: IdRef) !void { + fn addPtr(self: *@This(), ptr_ty_ref: CacheRef, ptr_id: IdRef) !void { // TODO: Double check pointer sizes here. // shared pointers might be u32... const target = self.dg.getTarget(); @@ -563,12 +513,12 @@ pub const DeclGen = struct { if (self.size % width != 0) { return self.dg.todo("misaligned pointer constants", .{}); } - try self.members.append(.{ .ty = ptr_ty_ref }); + try self.members.append(ptr_ty_ref); try self.initializers.append(ptr_id); self.size += width; } - fn addNullPtr(self: *@This(), ptr_ty_ref: SpvType.Ref) !void { + fn addNullPtr(self: *@This(), ptr_ty_ref: CacheRef) !void { const result_id = self.dg.spv.allocId(); try self.dg.spv.sections.types_globals_constants.emit(self.dg.spv.gpa, .OpConstantNull, .{ .id_result_type = self.dg.typeId(ptr_ty_ref), @@ -931,7 +881,7 @@ pub const DeclGen = struct { const section = &self.spv.globals.section; const ty_ref = try self.resolveType(ty, .indirect); - const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, 0); + const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class); // const target = self.getTarget(); @@ -960,7 +910,7 @@ pub const DeclGen = struct { .dg = self, .u32_ty_ref = u32_ty_ref, .u32_ty_id = self.typeId(u32_ty_ref), - .members = std.ArrayList(SpvType.Payload.Struct.Member).init(self.gpa), + .members = std.ArrayList(CacheRef).init(self.gpa), .initializers = std.ArrayList(IdRef).init(self.gpa), .decl_deps = std.AutoArrayHashMap(SpvModule.Decl.Index, void).init(self.gpa), }; @@ -972,8 +922,10 @@ pub const DeclGen = struct { try icl.lower(ty, val); try icl.flush(); - const constant_struct_ty_ref = try self.spv.simpleStructType(icl.members.items); - const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class, 0); + const constant_struct_ty_ref = try self.spv.resolve(.{ .struct_type = .{ + .member_types = icl.members.items, + } }); + const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class); const constant_struct_id = self.spv.allocId(); try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ @@ -1007,7 +959,7 @@ pub const DeclGen = struct { }); if (cast_to_generic) { - const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0); + const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic); try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ .id_result_type = self.typeId(generic_ptr_ty_ref), .id_result = result_id, @@ -1044,9 +996,9 @@ pub const DeclGen = struct { switch (ty.zigTypeTag()) { .Int => { if (ty.isSignedInt()) { - return try self.constInt(result_ty_ref, val.toSignedInt(target)); + return try self.spv.constInt(result_ty_ref, val.toSignedInt(target)); } else { - return try self.constInt(result_ty_ref, val.toUnsignedInt(target)); + return try self.spv.constInt(result_ty_ref, val.toUnsignedInt(target)); } }, .Bool => switch (repr) { @@ -1060,7 +1012,7 @@ pub const DeclGen = struct { } return result_id; }, - .indirect => return try self.constInt(result_ty_ref, @boolToInt(val.toBool())), + .indirect => return try self.spv.constInt(result_ty_ref, @boolToInt(val.toBool())), }, .Float => { const result_id = self.spv.allocId(); @@ -1084,7 +1036,7 @@ pub const DeclGen = struct { else => unreachable, }; - return try self.constInt(result_ty_ref, value); + return try self.spv.constInt(result_ty_ref, value); }, .ErrorUnion => { const payload_ty = ty.errorUnionPayload(); @@ -1143,45 +1095,31 @@ pub const DeclGen = struct { /// Turn a Zig type into a SPIR-V Type, and return its type result-id. fn resolveTypeId(self: *DeclGen, ty: Type) !IdResultType { const type_ref = try self.resolveType(ty, .direct); - return self.typeId(type_ref); + return self.spv.resultId(type_ref); } - fn typeId(self: *DeclGen, ty_ref: SpvType.Ref) IdRef { - return self.spv.typeId(ty_ref); + fn typeId(self: *DeclGen, ty_ref: CacheRef) IdRef { + return self.spv.resultId(ty_ref); } /// Create an integer type suitable for storing at least 'bits' bits. - fn intType(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvType.Ref { + /// The integer type that is returned by this function is the type that is used to perform + /// actual operations (as well as store) a Zig type of a particular number of bits. To create + /// a type with an exact size, use SpvModule.intType. + fn intType(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !CacheRef { const backing_bits = self.backingIntBits(bits) orelse { // TODO: Integers too big for any native type are represented as "composite integers": // An array of largestSupportedIntBits. return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits }); }; - - return try self.spv.resolveType(try SpvType.int(self.spv.arena, signedness, backing_bits)); - } - - fn intType2(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvCacheRef { - const backing_bits = self.backingIntBits(bits) orelse { - // TODO: Integers too big for any native type are represented as "composite integers": - // An array of largestSupportedIntBits. - return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits }); - }; - return try self.spv.resolve(.{ .int_type = .{ - .signedness = signedness, - .bits = backing_bits, - } }); + return self.spv.intType(signedness, backing_bits); } /// Create an integer type that represents 'usize'. - fn sizeType(self: *DeclGen) !SpvType.Ref { + fn sizeType(self: *DeclGen) !CacheRef { return try self.intType(.unsigned, self.getTarget().ptrBitWidth()); } - fn sizeType2(self: *DeclGen) !SpvCacheRef { - return try self.intType2(.unsigned, self.getTarget().ptrBitWidth()); - } - /// Generate a union type, optionally with a known field. If the tag alignment is greater /// than that of the payload, a regular union (non-packed, with both tag and payload), will /// be generated as follows: @@ -1204,7 +1142,7 @@ pub const DeclGen = struct { /// If any of the fields' size is 0, it will be omitted. /// NOTE: When the active field is set to something other than the most aligned field, the /// resulting struct will be *underaligned*. - fn resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !SpvType.Ref { + fn resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !CacheRef { const target = self.getTarget(); const layout = ty.unionGetLayout(target); const union_ty = ty.cast(Type.Payload.Union).?.data; @@ -1218,7 +1156,8 @@ pub const DeclGen = struct { return try self.resolveType(union_ty.tag_ty, .indirect); } - var members = std.BoundedArray(SpvType.Payload.Struct.Member, 4){}; + var member_types = std.BoundedArray(CacheRef, 4){}; + var member_names = std.BoundedArray(CacheString, 4){}; const has_tag = layout.tag_size != 0; const tag_first = layout.tag_align >= layout.payload_align; @@ -1226,82 +1165,6 @@ pub const DeclGen = struct { if (has_tag and tag_first) { const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect); - members.appendAssumeCapacity(.{ .name = "tag", .ty = tag_ty_ref }); - } - - const active_field = maybe_active_field orelse layout.most_aligned_field; - const active_field_ty = union_ty.fields.values()[active_field].ty; - - const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: { - const active_payload_ty_ref = try self.resolveType(active_field_ty, .indirect); - members.appendAssumeCapacity(.{ .name = "payload", .ty = active_payload_ty_ref }); - break :blk active_field_ty.abiSize(target); - } else 0; - - const payload_padding_len = layout.payload_size - active_field_size; - if (payload_padding_len != 0) { - const payload_padding_ty_ref = try self.spv.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref); - members.appendAssumeCapacity(.{ .name = "padding_payload", .ty = payload_padding_ty_ref }); - } - - if (has_tag and !tag_first) { - const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect); - members.appendAssumeCapacity(.{ .name = "tag", .ty = tag_ty_ref }); - } - - if (layout.padding != 0) { - const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref); - members.appendAssumeCapacity(.{ .name = "padding", .ty = padding_ty_ref }); - } - - return try self.spv.simpleStructType(members.slice()); - } - - /// Generate a union type, optionally with a known field. If the tag alignment is greater - /// than that of the payload, a regular union (non-packed, with both tag and payload), will - /// be generated as follows: - /// If the active field is known: - /// struct { - /// tag: TagType, - /// payload: ActivePayloadType, - /// payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8, - /// padding: [padding_size]u8, - /// } - /// If the payload alignment is greater than that of the tag: - /// struct { - /// payload: ActivePayloadType, - /// payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8, - /// tag: TagType, - /// padding: [padding_size]u8, - /// } - /// If the active payload is unknown, it will default back to the most aligned field. This is - /// to make sure that the overal struct has the correct alignment in spir-v. - /// If any of the fields' size is 0, it will be omitted. - /// NOTE: When the active field is set to something other than the most aligned field, the - /// resulting struct will be *underaligned*. - fn resolveUnionType2(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !SpvCacheRef { - const target = self.getTarget(); - const layout = ty.unionGetLayout(target); - const union_ty = ty.cast(Type.Payload.Union).?.data; - - if (union_ty.layout == .Packed) { - return self.todo("packed union types", .{}); - } - - if (layout.payload_size == 0) { - // No payload, so represent this as just the tag type. - return try self.resolveType2(union_ty.tag_ty, .indirect); - } - - var member_types = std.BoundedArray(SpvCacheRef, 4){}; - var member_names = std.BoundedArray(SpvCacheString, 4){}; - - const has_tag = layout.tag_size != 0; - const tag_first = layout.tag_align >= layout.payload_align; - const u8_ty_ref = try self.intType2(.unsigned, 8); // TODO: What if Int8Type is not enabled? - - if (has_tag and tag_first) { - const tag_ty_ref = try self.resolveType2(union_ty.tag_ty, .indirect); member_types.appendAssumeCapacity(tag_ty_ref); member_names.appendAssumeCapacity(try self.spv.resolveString("tag")); } @@ -1310,7 +1173,7 @@ pub const DeclGen = struct { const active_field_ty = union_ty.fields.values()[active_field].ty; const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: { - const active_payload_ty_ref = try self.resolveType2(active_field_ty, .indirect); + const active_payload_ty_ref = try self.resolveType(active_field_ty, .indirect); member_types.appendAssumeCapacity(active_payload_ty_ref); member_names.appendAssumeCapacity(try self.spv.resolveString("payload")); break :blk active_field_ty.abiSize(target); @@ -1318,19 +1181,19 @@ pub const DeclGen = struct { const payload_padding_len = layout.payload_size - active_field_size; if (payload_padding_len != 0) { - const payload_padding_ty_ref = try self.spv.arrayType2(@intCast(u32, payload_padding_len), u8_ty_ref); + const payload_padding_ty_ref = try self.spv.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref); member_types.appendAssumeCapacity(payload_padding_ty_ref); member_names.appendAssumeCapacity(try self.spv.resolveString("payload_padding")); } if (has_tag and !tag_first) { - const tag_ty_ref = try self.resolveType2(union_ty.tag_ty, .indirect); + const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect); member_types.appendAssumeCapacity(tag_ty_ref); member_names.appendAssumeCapacity(try self.spv.resolveString("tag")); } if (layout.padding != 0) { - const padding_ty_ref = try self.spv.arrayType2(layout.padding, u8_ty_ref); + const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref); member_types.appendAssumeCapacity(padding_ty_ref); member_names.appendAssumeCapacity(try self.spv.resolveString("padding")); } @@ -1341,22 +1204,24 @@ pub const DeclGen = struct { } }); } - fn resolveType2(self: *DeclGen, ty: Type, repr: Repr) Error!SpvCacheRef { + /// Turn a Zig type into a SPIR-V Type, and return a reference to it. + fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!CacheRef { + log.debug("resolveType: ty = {}", .{ty.fmt(self.module)}); const target = self.getTarget(); switch (ty.zigTypeTag()) { .Void, .NoReturn => return try self.spv.resolve(.void_type), .Bool => switch (repr) { .direct => return try self.spv.resolve(.bool_type), - .indirect => return try self.intType2(.unsigned, 1), + .indirect => return try self.intType(.unsigned, 1), }, .Int => { const int_info = ty.intInfo(target); - return try self.intType2(int_info.signedness, int_info.bits); + return try self.intType(int_info.signedness, int_info.bits); }, .Enum => { var buffer: Type.Payload.Bits = undefined; const tag_ty = ty.intTagType(&buffer); - return self.resolveType2(tag_ty, repr); + return self.resolveType(tag_ty, repr); }, .Float => { // We can (and want) not really emulate floating points with other floating point types like with the integer types, @@ -1378,11 +1243,11 @@ pub const DeclGen = struct { }, .Array => { const elem_ty = ty.childType(); - const elem_ty_ref = try self.resolveType2(elem_ty, .direct); + const elem_ty_ref = try self.resolveType(elem_ty, .direct); const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse { return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()}); }; - return self.spv.arrayType2(total_len, elem_ty_ref); + return self.spv.arrayType(total_len, elem_ty_ref); }, .Fn => switch (repr) { .direct => { @@ -1390,12 +1255,12 @@ pub const DeclGen = struct { if (ty.fnIsVarArgs()) return self.fail("VarArgs functions are unsupported for SPIR-V", .{}); - const param_ty_refs = try self.gpa.alloc(SpvCacheRef, ty.fnParamLen()); + const param_ty_refs = try self.gpa.alloc(CacheRef, ty.fnParamLen()); defer self.gpa.free(param_ty_refs); for (param_ty_refs, 0..) |*param_type, i| { - param_type.* = try self.resolveType2(ty.fnParamType(i), .direct); + param_type.* = try self.resolveType(ty.fnParamType(i), .direct); } - const return_ty_ref = try self.resolveType2(ty.fnReturnType(), .direct); + const return_ty_ref = try self.resolveType(ty.fnReturnType(), .direct); return try self.spv.resolve(.{ .function_type = .{ .return_type = return_ty_ref, @@ -1405,14 +1270,14 @@ pub const DeclGen = struct { .indirect => { // TODO: Represent function pointers properly. // For now, just use an usize type. - return try self.sizeType2(); + return try self.sizeType(); }, }, .Pointer => { const ptr_info = ty.ptrInfo().data; const storage_class = spvStorageClass(ptr_info.@"addrspace"); - const child_ty_ref = try self.resolveType2(ptr_info.pointee_type, .indirect); + const child_ty_ref = try self.resolveType(ptr_info.pointee_type, .indirect); const ptr_ty_ref = try self.spv.resolve(.{ .ptr_type = .{ .storage_class = storage_class, .child_type = child_ty_ref, @@ -1420,7 +1285,15 @@ pub const DeclGen = struct { if (ptr_info.size != .Slice) { return ptr_ty_ref; } - unreachable; // TODO + + const size_ty_ref = try self.sizeType(); + return self.spv.resolve(.{ .struct_type = .{ + .member_types = &.{ ptr_ty_ref, size_ty_ref }, + .member_names = &.{ + try self.spv.resolveString("ptr"), + try self.spv.resolveString("len"), + }, + } }); }, .Vector => { // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations @@ -1433,33 +1306,47 @@ pub const DeclGen = struct { // TODO: Properly verify sizes and child type. return try self.spv.resolve(.{ .vector_type = .{ - .component_type = try self.resolveType2(ty.elemType(), repr), + .component_type = try self.resolveType(ty.elemType(), repr), .component_count = @intCast(u32, ty.vectorLen()), } }); }, .Struct => { if (ty.isSimpleTupleOrAnonStruct()) { - unreachable; // TODO + const tuple = ty.tupleFields(); + const member_types = try self.gpa.alloc(CacheRef, tuple.types.len); + defer self.gpa.free(member_types); + + var member_index: usize = 0; + for (tuple.types, 0..) |field_ty, i| { + const field_val = tuple.values[i]; + if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue; + + member_types[member_index] = try self.resolveType(field_ty, .indirect); + member_index += 1; + } + + return try self.spv.resolve(.{ .struct_type = .{ + .member_types = member_types[0..member_index], + } }); } const struct_ty = ty.castTag(.@"struct").?.data; if (struct_ty.layout == .Packed) { - return try self.resolveType2(struct_ty.backing_int_ty, .direct); + return try self.resolveType(struct_ty.backing_int_ty, .direct); } - const member_types = try self.gpa.alloc(SpvCacheRef, struct_ty.fields.count()); + const member_types = try self.gpa.alloc(CacheRef, struct_ty.fields.count()); defer self.gpa.free(member_types); - const member_names = try self.gpa.alloc(SpvCacheString, struct_ty.fields.count()); + const member_names = try self.gpa.alloc(CacheString, struct_ty.fields.count()); defer self.gpa.free(member_names); - // const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, struct_ty.fields.count()); var member_index: usize = 0; for (struct_ty.fields.values(), 0..) |field, i| { if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; - member_types[member_index] = try self.resolveType2(field.ty, .indirect); + member_types[member_index] = try self.resolveType(field.ty, .indirect); member_names[member_index] = try self.spv.resolveString(struct_ty.fields.keys()[i]); member_index += 1; } @@ -1480,16 +1367,16 @@ pub const DeclGen = struct { // Just use a bool. // Note: Always generate the bool with indirect format, to save on some sanity // Perform the conversion to a direct bool when the field is extracted. - return try self.resolveType2(Type.bool, .indirect); + return try self.resolveType(Type.bool, .indirect); } - const payload_ty_ref = try self.resolveType2(payload_ty, .indirect); + const payload_ty_ref = try self.resolveType(payload_ty, .indirect); if (ty.optionalReprIsPayload()) { // Optional is actually a pointer or a slice. return payload_ty_ref; } - const bool_ty_ref = try self.resolveType2(Type.bool, .indirect); + const bool_ty_ref = try self.resolveType(Type.bool, .indirect); return try self.spv.resolve(.{ .struct_type = .{ .member_types = &.{ payload_ty_ref, bool_ty_ref }, @@ -1499,21 +1386,21 @@ pub const DeclGen = struct { }, } }); }, - .Union => return try self.resolveUnionType2(ty, null), - .ErrorSet => return try self.intType2(.unsigned, 16), + .Union => return try self.resolveUnionType(ty, null), + .ErrorSet => return try self.intType(.unsigned, 16), .ErrorUnion => { const payload_ty = ty.errorUnionPayload(); - const error_ty_ref = try self.resolveType2(Type.anyerror, .indirect); + const error_ty_ref = try self.resolveType(Type.anyerror, .indirect); const eu_layout = self.errorUnionLayout(payload_ty); if (!eu_layout.payload_has_bits) { return error_ty_ref; } - const payload_ty_ref = try self.resolveType2(payload_ty, .indirect); + const payload_ty_ref = try self.resolveType(payload_ty, .indirect); - var member_types: [2]SpvCacheRef = undefined; - var member_names: [2]SpvCacheString = undefined; + var member_types: [2]CacheRef = undefined; + var member_names: [2]CacheString = undefined; if (eu_layout.error_first) { // Put the error first member_types = .{ error_ty_ref, payload_ty_ref }; @@ -1550,226 +1437,6 @@ pub const DeclGen = struct { } } - /// Turn a Zig type into a SPIR-V Type, and return a reference to it. - fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!SpvType.Ref { - log.debug("resolveType: ty = {}", .{ty.fmt(self.module)}); - _ = try self.resolveType2(ty, repr); - const target = self.getTarget(); - switch (ty.zigTypeTag()) { - .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)), - .Bool => switch (repr) { - .direct => return try self.spv.resolveType(SpvType.initTag(.bool)), - // SPIR-V booleans are opaque, which is fine for operations, but they cant be stored. - // This function returns the *stored* type, for values directly we convert this into a bool when - // it is loaded, and convert it back to this type when stored. - .indirect => return try self.intType(.unsigned, 1), - }, - .Int => { - const int_info = ty.intInfo(target); - return try self.intType(int_info.signedness, int_info.bits); - }, - .Enum => { - var buffer: Type.Payload.Bits = undefined; - const tag_ty = ty.intTagType(&buffer); - return self.resolveType(tag_ty, repr); - }, - .Float => { - // We can (and want) not really emulate floating points with other floating point types like with the integer types, - // so if the float is not supported, just return an error. - const bits = ty.floatBits(target); - const supported = switch (bits) { - 16 => Target.spirv.featureSetHas(target.cpu.features, .Float16), - // 32-bit floats are always supported (see spec, 2.16.1, Data rules). - 32 => true, - 64 => Target.spirv.featureSetHas(target.cpu.features, .Float64), - else => false, - }; - - if (!supported) { - return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits}); - } - - return try self.spv.resolveType(SpvType.float(bits)); - }, - .Array => { - const elem_ty = ty.childType(); - const elem_ty_ref = try self.resolveType(elem_ty, .indirect); - const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse { - return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()}); - }; - return try self.spv.arrayType(total_len, elem_ty_ref); - }, - .Fn => switch (repr) { - .direct => { - // TODO: Put this somewhere in Sema.zig - if (ty.fnIsVarArgs()) - return self.fail("VarArgs functions are unsupported for SPIR-V", .{}); - - // TODO: Parameter passing convention etc. - - const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen()); - for (param_types, 0..) |*param, i| { - param.* = try self.resolveType(ty.fnParamType(i), .direct); - } - - const return_type = try self.resolveType(ty.fnReturnType(), .direct); - - const payload = try self.spv.arena.create(SpvType.Payload.Function); - payload.* = .{ .return_type = return_type, .parameters = param_types }; - return try self.spv.resolveType(SpvType.initPayload(&payload.base)); - }, - .indirect => { - // TODO: Represent function pointers properly. - // For now, just use an usize type. - return try self.sizeType(); - }, - }, - .Pointer => { - const ptr_info = ty.ptrInfo().data; - - const storage_class = spvStorageClass(ptr_info.@"addrspace"); - const child_ty_ref = try self.resolveType(ptr_info.pointee_type, .indirect); - const ptr_ty_ref = try self.spv.ptrType(child_ty_ref, storage_class, 0); - - if (ptr_info.size != .Slice) { - return ptr_ty_ref; - } - - return try self.spv.simpleStructType(&.{ - .{ .ty = ptr_ty_ref, .name = "ptr" }, - .{ .ty = try self.sizeType(), .name = "len" }, - }); - }, - .Vector => { - // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations - // which work on them), so simply use those. - // Note: SPIR-V vectors only support bools, ints and floats, so pointer vectors need to be supported another way. - // "composite integers" (larger than the largest supported native type) can probably be represented by an array of vectors. - // TODO: The SPIR-V spec mentions that vector sizes may be quite restricted! look into which we can use, and whether OpTypeVector - // is adequate at all for this. - - // TODO: Properly verify sizes and child type. - - const payload = try self.spv.arena.create(SpvType.Payload.Vector); - payload.* = .{ - .component_type = try self.resolveType(ty.elemType(), repr), - .component_count = @intCast(u32, ty.vectorLen()), - }; - return try self.spv.resolveType(SpvType.initPayload(&payload.base)); - }, - .Struct => { - if (ty.isSimpleTupleOrAnonStruct()) { - const tuple = ty.tupleFields(); - const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, tuple.types.len); - var member_index: u32 = 0; - for (tuple.types, 0..) |field_ty, i| { - const field_val = tuple.values[i]; - if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBitsIgnoreComptime()) continue; - members[member_index] = .{ - .ty = try self.resolveType(field_ty, .indirect), - }; - member_index += 1; - } - const payload = try self.spv.arena.create(SpvType.Payload.Struct); - payload.* = .{ - .members = members[0..member_index], - }; - return try self.spv.resolveType(SpvType.initPayload(&payload.base)); - } - - const struct_ty = ty.castTag(.@"struct").?.data; - - if (struct_ty.layout == .Packed) { - return try self.resolveType(struct_ty.backing_int_ty, .indirect); - } - - const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, struct_ty.fields.count()); - var member_index: usize = 0; - for (struct_ty.fields.values(), 0..) |field, i| { - if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; - - members[member_index] = .{ - .ty = try self.resolveType(field.ty, .indirect), - .name = struct_ty.fields.keys()[i], - }; - member_index += 1; - } - - const name = try struct_ty.getFullyQualifiedName(self.module); - defer self.module.gpa.free(name); - - const payload = try self.spv.arena.create(SpvType.Payload.Struct); - payload.* = .{ - .members = members[0..member_index], - .name = try self.spv.arena.dupe(u8, name), - }; - return try self.spv.resolveType(SpvType.initPayload(&payload.base)); - }, - .Optional => { - var buf: Type.Payload.ElemType = undefined; - const payload_ty = ty.optionalChild(&buf); - if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { - // Just use a bool. - // Note: Always generate the bool with indirect format, to save on some sanity - // Perform the converison to a direct bool when the field is extracted. - return try self.resolveType(Type.bool, .indirect); - } - - const payload_ty_ref = try self.resolveType(payload_ty, .indirect); - if (ty.optionalReprIsPayload()) { - // Optional is actually a pointer or a slice. - return payload_ty_ref; - } - - const bool_ty_ref = try self.resolveType(Type.bool, .indirect); - - // its an actual optional - return try self.spv.simpleStructType(&.{ - .{ .ty = payload_ty_ref, .name = "payload" }, - .{ .ty = bool_ty_ref, .name = "valid" }, - }); - }, - .Union => return try self.resolveUnionType(ty, null), - .ErrorSet => return try self.intType(.unsigned, 16), - .ErrorUnion => { - const payload_ty = ty.errorUnionPayload(); - const error_ty_ref = try self.resolveType(Type.anyerror, .indirect); - - const eu_layout = self.errorUnionLayout(payload_ty); - if (!eu_layout.payload_has_bits) { - return error_ty_ref; - } - - const payload_ty_ref = try self.resolveType(payload_ty, .indirect); - - var members = std.BoundedArray(SpvType.Payload.Struct.Member, 2){}; - if (eu_layout.error_first) { - // Put the error first - members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" }); - members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" }); - // TODO: ABI padding? - } else { - // Put the payload first. - members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" }); - members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" }); - // TODO: ABI padding? - } - - return try self.spv.simpleStructType(members.slice()); - }, - - .Null, - .Undefined, - .EnumLiteral, - .ComptimeFloat, - .ComptimeInt, - .Type, - => unreachable, // Must be comptime. - - else => |tag| return self.todo("Implement zig type '{}'", .{tag}), - } - } - fn spvStorageClass(as: std.builtin.AddressSpace) StorageClass { return switch (as) { .generic => .Generic, @@ -1839,17 +1506,13 @@ pub const DeclGen = struct { /// the name of an error in the text executor. fn generateTestEntryPoint(self: *DeclGen, name: []const u8, spv_test_decl_index: SpvModule.Decl.Index) !void { const anyerror_ty_ref = try self.resolveType(Type.anyerror, .direct); - const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup, 0); + const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup); const void_ty_ref = try self.resolveType(Type.void, .direct); - const kernel_proto_ty_ref = blk: { - const proto_payload = try self.spv.arena.create(SpvType.Payload.Function); - proto_payload.* = .{ - .return_type = void_ty_ref, - .parameters = try self.spv.arena.dupe(SpvType.Ref, &.{ptr_anyerror_ty_ref}), - }; - break :blk try self.spv.resolveType(SpvType.initPayload(&proto_payload.base)); - }; + const kernel_proto_ty_ref = try self.spv.resolve(.{ .function_type = .{ + .return_type = void_ty_ref, + .parameters = &.{ptr_anyerror_ty_ref}, + } }); const test_id = self.spv.declPtr(spv_test_decl_index).result_id; @@ -1983,9 +1646,9 @@ pub const DeclGen = struct { } } - fn boolToInt(self: *DeclGen, result_ty_ref: SpvType.Ref, condition_id: IdRef) !IdRef { - const zero_id = try self.constInt(result_ty_ref, 0); - const one_id = try self.constInt(result_ty_ref, 1); + fn boolToInt(self: *DeclGen, result_ty_ref: CacheRef, condition_id: IdRef) !IdRef { + const zero_id = try self.spv.constInt(result_ty_ref, 0); + const one_id = try self.spv.constInt(result_ty_ref, 1); const result_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpSelect, .{ .id_result_type = self.typeId(result_ty_ref), @@ -2004,7 +1667,7 @@ pub const DeclGen = struct { .Bool => blk: { const direct_bool_ty_ref = try self.resolveType(ty, .direct); const indirect_bool_ty_ref = try self.resolveType(ty, .indirect); - const zero_id = try self.constInt(indirect_bool_ty_ref, 0); + const zero_id = try self.spv.constInt(indirect_bool_ty_ref, 0); const result_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{ .id_result_type = self.typeId(direct_bool_ty_ref), @@ -2242,10 +1905,10 @@ pub const DeclGen = struct { return result_id; } - fn maskStrangeInt(self: *DeclGen, ty_ref: SpvType.Ref, value_id: IdRef, bits: u16) !IdRef { + fn maskStrangeInt(self: *DeclGen, ty_ref: CacheRef, value_id: IdRef, bits: u16) !IdRef { const mask_value = if (bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @intCast(u6, bits)) - 1; const result_id = self.spv.allocId(); - const mask_id = try self.constInt(ty_ref, mask_value); + const mask_id = try self.spv.constInt(ty_ref, mask_value); try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{ .id_result_type = self.typeId(ty_ref), .id_result = result_id, @@ -2384,7 +2047,7 @@ pub const DeclGen = struct { // Note that signed overflow is also wrapping in spir-v. const rhs_lt_zero_id = self.spv.allocId(); - const zero_id = try self.constInt(operand_ty_ref, 0); + const zero_id = try self.spv.constInt(operand_ty_ref, 0); try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{ .id_result_type = self.typeId(bool_ty_ref), .id_result = rhs_lt_zero_id, @@ -2463,7 +2126,7 @@ pub const DeclGen = struct { /// is the latter and PtrAccessChain is the former. fn accessChain( self: *DeclGen, - result_ty_ref: SpvType.Ref, + result_ty_ref: CacheRef, base: IdRef, indexes: []const IdRef, ) !IdRef { @@ -2479,7 +2142,7 @@ pub const DeclGen = struct { fn ptrAccessChain( self: *DeclGen, - result_ty_ref: SpvType.Ref, + result_ty_ref: CacheRef, base: IdRef, element: IdRef, indexes: []const IdRef, @@ -2854,7 +2517,7 @@ pub const DeclGen = struct { // Construct new pointer type for the resulting pointer const elem_ty = ptr_ty.elemType2(); // use elemType() so that we get T for *[N]T. const elem_ty_ref = try self.resolveType(elem_ty, .direct); - const elem_ptr_ty_ref = try self.spv.ptrType(elem_ty_ref, spvStorageClass(ptr_ty.ptrAddressSpace()), 0); + const elem_ptr_ty_ref = try self.spv.ptrType(elem_ty_ref, spvStorageClass(ptr_ty.ptrAddressSpace())); if (ptr_ty.isSinglePointer()) { // Pointer-to-array. In this case, the resulting pointer is not of the same type // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain. @@ -2970,7 +2633,7 @@ pub const DeclGen = struct { fn makePointerConstant( self: *DeclGen, section: *SpvSection, - ptr_ty_ref: SpvType.Ref, + ptr_ty_ref: CacheRef, ptr_id: IdRef, ) !IdRef { const result_id = self.spv.allocId(); @@ -2988,11 +2651,11 @@ pub const DeclGen = struct { // placed in the Function address space. fn alloc( self: *DeclGen, - ty_ref: SpvType.Ref, + ty_ref: CacheRef, initializer: ?IdRef, ) !IdRef { - const fn_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Function, 0); - const general_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0); + const fn_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Function); + const general_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic); // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to // directly generate them into func.prologue instead of the body. @@ -3146,7 +2809,7 @@ pub const DeclGen = struct { const val_is_undef = if (self.air.value(bin_op.rhs)) |val| val.isUndefDeep() else false; if (val_is_undef) { - const undef = try self.constUndef(ptr_ty_ref); + const undef = try self.spv.constUndef(ptr_ty_ref); try self.store(ptr_ty, ptr, undef); } else { try self.store(ptr_ty, ptr, value); @@ -3217,7 +2880,7 @@ pub const DeclGen = struct { else err_union_id; - const zero_id = try self.constInt(err_ty_ref, 0); + const zero_id = try self.spv.constInt(err_ty_ref, 0); const is_err_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{ .id_result_type = self.typeId(bool_ty_ref), @@ -3266,7 +2929,7 @@ pub const DeclGen = struct { if (err_union_ty.errorUnionSet().errorSetIsEmpty()) { // No error possible, so just return undefined. - return try self.constUndef(err_ty_ref); + return try self.spv.constUndef(err_ty_ref); } const payload_ty = err_union_ty.errorUnionPayload(); @@ -3295,7 +2958,7 @@ pub const DeclGen = struct { const payload_ty_ref = try self.resolveType(payload_ty, .indirect); var members = std.BoundedArray(IdRef, 2){}; - const payload_id = try self.constUndef(payload_ty_ref); + const payload_id = try self.spv.constUndef(payload_ty_ref); if (eu_layout.error_first) { members.appendAssumeCapacity(operand_id); members.appendAssumeCapacity(payload_id); @@ -3337,7 +3000,7 @@ pub const DeclGen = struct { operand_id; const payload_ty_ref = try self.resolveType(ptr_ty, .direct); - const null_id = try self.constNull(payload_ty_ref); + const null_id = try self.spv.constNull(payload_ty_ref); const result_id = self.spv.allocId(); const operands = .{ .id_result_type = self.typeId(bool_ty_ref), 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/Module.zig b/src/codegen/spirv/Module.zig index 85d0bbf78c..4c078f301b 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -20,12 +20,13 @@ const IdResult = spec.IdResult; const IdResultType = spec.IdResultType; const Section = @import("Section.zig"); -const Type = @import("type.zig").Type; -pub const TypeConstantCache = @import("TypeConstantCache.zig"); -const TypeCache = std.ArrayHashMapUnmanaged(Type, IdResultType, Type.ShallowHashContext32, true); +const Cache = @import("TypeConstantCache.zig"); +pub const CacheKey = Cache.Key; +pub const CacheRef = Cache.Ref; +pub const CacheString = Cache.String; -/// This structure represents a function that is in-progress of being emitted. +/// This structure represents a function that isc in-progress of being emitted. /// Commonly, the contents of this structure will be merged with the appropriate /// sections of the module and re-used. Note that the SPIR-V module system makes /// no attempt of compacting result-id's, so any Fn instance should ultimately @@ -130,7 +131,7 @@ sections: struct { /// From this section, OpLine and OpNoLine is allowed. /// According to the SPIR-V documentation, this section normally /// also holds type and constant instructions. These are managed - /// via the tc_cache instead, which is the sole structure that + /// via the cache instead, which is the sole structure that /// manages that section. These will be inserted between this and /// the previous section when emitting the final binary. /// TODO: Do we need this section? Globals are also managed with another mechanism. @@ -152,10 +153,9 @@ next_result_id: Word, /// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource. source_file_names: std.StringHashMapUnmanaged(IdRef) = .{}, -type_cache: TypeCache = .{}, /// SPIR-V type- and constant cache. This structure is used to store information about these in a more /// efficient manner. -tc_cache: TypeConstantCache = .{}, +cache: Cache = .{}, /// Set of Decls, referred to by Decl.Index. decls: std.ArrayListUnmanaged(Decl) = .{}, @@ -196,7 +196,7 @@ pub fn deinit(self: *Module) void { self.sections.functions.deinit(self.gpa); self.source_file_names.deinit(self.gpa); - self.tc_cache.deinit(self); + self.cache.deinit(self); self.decls.deinit(self.gpa); self.decl_deps.deinit(self.gpa); @@ -223,20 +223,20 @@ pub fn idBound(self: Module) Word { return self.next_result_id; } -pub fn resolve(self: *Module, key: TypeConstantCache.Key) !TypeConstantCache.Ref { - return self.tc_cache.resolve(self, key); +pub fn resolve(self: *Module, key: CacheKey) !CacheRef { + return self.cache.resolve(self, key); } -pub fn resultId(self: *Module, ref: TypeConstantCache.Ref) IdResult { - return self.tc_cache.resultId(ref); +pub fn resultId(self: *const Module, ref: CacheRef) IdResult { + return self.cache.resultId(ref); } -pub fn resolveId(self: *Module, key: TypeConstantCache.Key) !IdResult { +pub fn resolveId(self: *Module, key: CacheKey) !IdResult { return self.resultId(try self.resolve(key)); } -pub fn resolveString(self: *Module, str: []const u8) !TypeConstantCache.String { - return try self.tc_cache.addString(self, str); +pub fn resolveString(self: *Module, str: []const u8) !CacheString { + return try self.cache.addString(self, str); } fn orderGlobalsInto( @@ -350,7 +350,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void { var entry_points = try self.entryPoints(); defer entry_points.deinit(self.gpa); - var types_constants = try self.tc_cache.materialize(self); + var types_constants = try self.cache.materialize(self); defer types_constants.deinit(self.gpa); // Note: needs to be kept in order according to section 2.3! @@ -364,6 +364,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void { self.sections.debug_names.toWords(), self.sections.annotations.toWords(), types_constants.toWords(), + self.sections.types_globals_constants.toWords(), self.sections.globals.toWords(), globals.toWords(), self.sections.functions.toWords(), @@ -416,364 +417,14 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef { return result.value_ptr.*; } -/// Fetch a result-id for a spir-v type. This function deduplicates the type as appropriate, -/// and returns a cached version if that exists. -/// Note: This function does not attempt to perform any validation on the type. -/// The type is emitted in a shallow fashion; any child types should already -/// be emitted at this point. -pub fn resolveType(self: *Module, ty: Type) !Type.Ref { - const result = try self.type_cache.getOrPut(self.gpa, ty); - const index = @intToEnum(Type.Ref, result.index); - - if (!result.found_existing) { - const ref = try self.emitType(ty); - self.type_cache.values()[result.index] = ref; - } - - return index; -} - -pub fn resolveTypeId(self: *Module, ty: Type) !IdResultType { - const ty_ref = try self.resolveType(ty); - return self.typeId(ty_ref); -} - -pub fn typeRefType(self: Module, ty_ref: Type.Ref) Type { - return self.type_cache.keys()[@enumToInt(ty_ref)]; -} - -/// Get the result-id of a particular type, by reference. Asserts type_ref is valid. -pub fn typeId(self: Module, ty_ref: Type.Ref) IdResultType { - return self.type_cache.values()[@enumToInt(ty_ref)]; -} - -/// Unconditionally emit a spir-v type into the appropriate section. -/// Note: If this function is called with a type that is already generated, it may yield an invalid module -/// as non-pointer non-aggregrate types must me unique! -/// Note: This function does not attempt to perform any validation on the type. -/// The type is emitted in a shallow fashion; any child types should already -/// be emitted at this point. -pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { - const result_id = self.allocId(); - const ref_id = result_id; - const types = &self.sections.types_globals_constants; - const debug_names = &self.sections.debug_names; - const result_id_operand = .{ .id_result = result_id }; - - switch (ty.tag()) { - .void => { - try types.emit(self.gpa, .OpTypeVoid, result_id_operand); - try debug_names.emit(self.gpa, .OpName, .{ - .target = result_id, - .name = "void", - }); - }, - .bool => { - try types.emit(self.gpa, .OpTypeBool, result_id_operand); - try debug_names.emit(self.gpa, .OpName, .{ - .target = result_id, - .name = "bool", - }); - }, - .u8, - .u16, - .u32, - .u64, - .i8, - .i16, - .i32, - .i64, - .int, - => { - // TODO: Kernels do not support OpTypeInt that is signed. We can probably - // can get rid of the signedness all together, in Shaders also. - const bits = ty.intFloatBits(); - const signedness: spec.LiteralInteger = switch (ty.intSignedness()) { - .unsigned => 0, - .signed => 1, - }; - - try types.emit(self.gpa, .OpTypeInt, .{ - .id_result = result_id, - .width = bits, - .signedness = signedness, - }); - - const ui: []const u8 = switch (signedness) { - 0 => "u", - 1 => "i", - else => unreachable, - }; - const name = try std.fmt.allocPrint(self.gpa, "{s}{}", .{ ui, bits }); - defer self.gpa.free(name); - - try debug_names.emit(self.gpa, .OpName, .{ - .target = result_id, - .name = name, - }); - }, - .f16, .f32, .f64 => { - const bits = ty.intFloatBits(); - try types.emit(self.gpa, .OpTypeFloat, .{ - .id_result = result_id, - .width = bits, - }); - - const name = try std.fmt.allocPrint(self.gpa, "f{}", .{bits}); - defer self.gpa.free(name); - try debug_names.emit(self.gpa, .OpName, .{ - .target = result_id, - .name = name, - }); - }, - .vector => try types.emit(self.gpa, .OpTypeVector, .{ - .id_result = result_id, - .component_type = self.typeId(ty.childType()), - .component_count = ty.payload(.vector).component_count, - }), - .matrix => try types.emit(self.gpa, .OpTypeMatrix, .{ - .id_result = result_id, - .column_type = self.typeId(ty.childType()), - .column_count = ty.payload(.matrix).column_count, - }), - .image => { - const info = ty.payload(.image); - try types.emit(self.gpa, .OpTypeImage, .{ - .id_result = result_id, - .sampled_type = self.typeId(ty.childType()), - .dim = info.dim, - .depth = @enumToInt(info.depth), - .arrayed = @boolToInt(info.arrayed), - .ms = @boolToInt(info.multisampled), - .sampled = @enumToInt(info.sampled), - .image_format = info.format, - .access_qualifier = info.access_qualifier, - }); - }, - .sampler => try types.emit(self.gpa, .OpTypeSampler, result_id_operand), - .sampled_image => try types.emit(self.gpa, .OpTypeSampledImage, .{ - .id_result = result_id, - .image_type = self.typeId(ty.childType()), - }), - .array => { - const info = ty.payload(.array); - assert(info.length != 0); - - const size_type = Type.initTag(.u32); - const size_type_id = try self.resolveTypeId(size_type); - const length_id = self.allocId(); - try self.emitConstant(size_type_id, length_id, .{ .uint32 = info.length }); - - try types.emit(self.gpa, .OpTypeArray, .{ - .id_result = result_id, - .element_type = self.typeId(ty.childType()), - .length = length_id, - }); - if (info.array_stride != 0) { - try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); - } - }, - .runtime_array => { - const info = ty.payload(.runtime_array); - try types.emit(self.gpa, .OpTypeRuntimeArray, .{ - .id_result = result_id, - .element_type = self.typeId(ty.childType()), - }); - if (info.array_stride != 0) { - try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); - } - }, - .@"struct" => { - const info = ty.payload(.@"struct"); - try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len); - types.writeOperand(IdResult, result_id); - for (info.members) |member| { - types.writeOperand(IdRef, self.typeId(member.ty)); - } - try self.decorateStruct(ref_id, info); - }, - .@"opaque" => try types.emit(self.gpa, .OpTypeOpaque, .{ - .id_result = result_id, - .literal_string = ty.payload(.@"opaque").name, - }), - .pointer => { - const info = ty.payload(.pointer); - try types.emit(self.gpa, .OpTypePointer, .{ - .id_result = result_id, - .storage_class = info.storage_class, - .type = self.typeId(ty.childType()), - }); - if (info.array_stride != 0) { - try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); - } - if (info.alignment != 0) { - try self.decorate(ref_id, .{ .Alignment = .{ .alignment = info.alignment } }); - } - if (info.max_byte_offset) |max_byte_offset| { - try self.decorate(ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } }); - } - }, - .function => { - const info = ty.payload(.function); - try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len); - types.writeOperand(IdResult, result_id); - types.writeOperand(IdRef, self.typeId(info.return_type)); - for (info.parameters) |parameter_type| { - types.writeOperand(IdRef, self.typeId(parameter_type)); - } - }, - .event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand), - .device_event => try types.emit(self.gpa, .OpTypeDeviceEvent, result_id_operand), - .reserve_id => try types.emit(self.gpa, .OpTypeReserveId, result_id_operand), - .queue => try types.emit(self.gpa, .OpTypeQueue, result_id_operand), - .pipe => try types.emit(self.gpa, .OpTypePipe, .{ - .id_result = result_id, - .qualifier = ty.payload(.pipe).qualifier, - }), - .pipe_storage => try types.emit(self.gpa, .OpTypePipeStorage, result_id_operand), - .named_barrier => try types.emit(self.gpa, .OpTypeNamedBarrier, result_id_operand), - } - - return result_id; -} - -fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void { - const debug_names = &self.sections.debug_names; - - if (info.name.len != 0) { - try debug_names.emit(self.gpa, .OpName, .{ - .target = target, - .name = info.name, - }); - } - - // Decorations for the struct type itself. - if (info.decorations.block) - try self.decorate(target, .Block); - if (info.decorations.buffer_block) - try self.decorate(target, .BufferBlock); - if (info.decorations.glsl_shared) - try self.decorate(target, .GLSLShared); - if (info.decorations.glsl_packed) - try self.decorate(target, .GLSLPacked); - if (info.decorations.c_packed) - try self.decorate(target, .CPacked); - - // Decorations for the struct members. - const extra = info.member_decoration_extra; - var extra_i: u32 = 0; - for (info.members, 0..) |member, i| { - const d = member.decorations; - const index = @intCast(Word, i); - - if (member.name.len != 0) { - try debug_names.emit(self.gpa, .OpMemberName, .{ - .type = target, - .member = index, - .name = member.name, - }); - } - - switch (member.offset) { - .none => {}, - else => try self.decorateMember( - target, - index, - .{ .Offset = .{ .byte_offset = @enumToInt(member.offset) } }, - ), - } - - switch (d.matrix_layout) { - .row_major => try self.decorateMember(target, index, .RowMajor), - .col_major => try self.decorateMember(target, index, .ColMajor), - .none => {}, - } - if (d.matrix_layout != .none) { - try self.decorateMember(target, index, .{ - .MatrixStride = .{ .matrix_stride = extra[extra_i] }, - }); - extra_i += 1; - } - - if (d.no_perspective) - try self.decorateMember(target, index, .NoPerspective); - if (d.flat) - try self.decorateMember(target, index, .Flat); - if (d.patch) - try self.decorateMember(target, index, .Patch); - if (d.centroid) - try self.decorateMember(target, index, .Centroid); - if (d.sample) - try self.decorateMember(target, index, .Sample); - if (d.invariant) - try self.decorateMember(target, index, .Invariant); - if (d.@"volatile") - try self.decorateMember(target, index, .Volatile); - if (d.coherent) - try self.decorateMember(target, index, .Coherent); - if (d.non_writable) - try self.decorateMember(target, index, .NonWritable); - if (d.non_readable) - try self.decorateMember(target, index, .NonReadable); - - if (d.builtin) { - try self.decorateMember(target, index, .{ - .BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) }, - }); - extra_i += 1; - } - if (d.stream) { - try self.decorateMember(target, index, .{ - .Stream = .{ .stream_number = extra[extra_i] }, - }); - extra_i += 1; - } - if (d.location) { - try self.decorateMember(target, index, .{ - .Location = .{ .location = extra[extra_i] }, - }); - extra_i += 1; - } - if (d.component) { - try self.decorateMember(target, index, .{ - .Component = .{ .component = extra[extra_i] }, - }); - extra_i += 1; - } - if (d.xfb_buffer) { - try self.decorateMember(target, index, .{ - .XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] }, - }); - extra_i += 1; - } - if (d.xfb_stride) { - try self.decorateMember(target, index, .{ - .XfbStride = .{ .xfb_stride = extra[extra_i] }, - }); - extra_i += 1; - } - if (d.user_semantic) { - const len = extra[extra_i]; - extra_i += 1; - const semantic = @ptrCast([*]const u8, &extra[extra_i])[0..len]; - try self.decorateMember(target, index, .{ - .UserSemantic = .{ .semantic = semantic }, - }); - extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable; - } - } -} - -pub fn simpleStructType(self: *Module, members: []const Type.Payload.Struct.Member) !Type.Ref { - const payload = try self.arena.create(Type.Payload.Struct); - payload.* = .{ - .members = try self.arena.dupe(Type.Payload.Struct.Member, members), - .decorations = .{}, - }; - return try self.resolveType(Type.initPayload(&payload.base)); +pub fn intType(self: *Module, signedness: std.builtin.Signedness, bits: u16) !CacheRef { + return try self.resolve(.{ .int_type = .{ + .signedness = signedness, + .bits = bits, + } }); } -pub fn arrayType2(self: *Module, len: u32, elem_ty_ref: TypeConstantCache.Ref) !TypeConstantCache.Ref { +pub fn arrayType(self: *Module, len: u32, elem_ty_ref: CacheRef) !CacheRef { const len_ty_ref = try self.resolve(.{ .int_type = .{ .signedness = .unsigned, .bits = 32, @@ -788,41 +439,45 @@ pub fn arrayType2(self: *Module, len: u32, elem_ty_ref: TypeConstantCache.Ref) ! } }); } -pub fn arrayType(self: *Module, len: u32, ty: Type.Ref) !Type.Ref { - const payload = try self.arena.create(Type.Payload.Array); - payload.* = .{ - .element_type = ty, - .length = len, - }; - return try self.resolveType(Type.initPayload(&payload.base)); -} - pub fn ptrType( self: *Module, - child: Type.Ref, + child: CacheRef, storage_class: spec.StorageClass, - alignment: u32, -) !Type.Ref { - const ptr_payload = try self.arena.create(Type.Payload.Pointer); - ptr_payload.* = .{ +) !CacheRef { + return try self.resolve(.{ .ptr_type = .{ .storage_class = storage_class, .child_type = child, - .alignment = alignment, - }; - return try self.resolveType(Type.initPayload(&ptr_payload.base)); + } }); +} + +pub fn constInt(self: *Module, ty_ref: CacheRef, value: anytype) !IdRef { + const ty = self.cache.lookup(ty_ref).int_type; + const Value = Cache.Key.Int.Value; + return try self.resolveId(.{ .int = .{ + .ty = ty_ref, + .value = switch (ty.signedness) { + .signed => Value{ .int64 = @intCast(i64, value) }, + .unsigned => Value{ .uint64 = @intCast(u64, value) }, + }, + } }); +} + +pub fn constUndef(self: *Module, ty_ref: CacheRef) !IdRef { + return try self.resolveId(.{ .undef = .{ .ty = ty_ref } }); +} + +pub fn constNull(self: *Module, ty_ref: CacheRef) !IdRef { + return try self.resolveId(.{ .null = .{ .ty = ty_ref } }); } -pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_class: spec.StorageClass) !Type.Ref { - const payload = try self.arena.create(Type.Payload.Pointer); - payload.* = self.typeRefType(ptr_ty_ref).payload(.pointer).*; - payload.storage_class = new_storage_class; - return try self.resolveType(Type.initPayload(&payload.base)); +pub fn constBool(self: *Module, ty_ref: CacheRef, value: bool) !IdRef { + return try self.resolveId(.{ .bool = .{ .ty = ty_ref, .value = value } }); } -pub fn constComposite(self: *Module, ty_ref: Type.Ref, members: []const IdRef) !IdRef { +pub fn constComposite(self: *Module, ty_ref: CacheRef, members: []const IdRef) !IdRef { const result_id = self.allocId(); try self.sections.types_globals_constants.emit(self.gpa, .OpSpecConstantComposite, .{ - .id_result_type = self.typeId(ty_ref), + .id_result_type = self.resultId(ty_ref), .id_result = result_id, .constituents = members, }); diff --git a/src/codegen/spirv/TypeConstantCache.zig b/src/codegen/spirv/TypeConstantCache.zig index d5bc888276..4c41bf583b 100644 --- a/src/codegen/spirv/TypeConstantCache.zig +++ b/src/codegen/spirv/TypeConstantCache.zig @@ -111,6 +111,18 @@ const Tag = enum { /// Value of type f64 /// data is payload to Float16 float64, + /// Undefined value + /// data is type + undef, + /// Null value + /// data is type + null, + /// Bool value that is true + /// data is (bool) type + bool_true, + /// Bool value that is false + /// data is (bool) type + bool_false, const SimpleType = enum { void, bool }; @@ -227,6 +239,9 @@ pub const Key = union(enum) { // -- values int: Int, float: Float, + undef: Undef, + null: Null, + bool: Bool, pub const IntType = std.builtin.Type.Int; pub const FloatType = std.builtin.Type.Float; @@ -323,6 +338,19 @@ pub const Key = union(enum) { }; }; + pub const Undef = struct { + ty: Ref, + }; + + pub const Null = struct { + ty: Ref, + }; + + pub const Bool = struct { + ty: Ref, + value: bool, + }; + fn hash(self: Key) u32 { var hasher = std.hash.Wyhash.init(0); switch (self) { @@ -539,6 +567,32 @@ fn emit( .value = lit, }); }, + .undef => |undef| { + try section.emit(spv.gpa, .OpUndef, .{ + .id_result_type = self.resultId(undef.ty), + .id_result = result_id, + }); + }, + .null => |null_info| { + try section.emit(spv.gpa, .OpConstantNull, .{ + .id_result_type = self.resultId(null_info.ty), + .id_result = result_id, + }); + }, + .bool => |bool_info| switch (bool_info.value) { + true => { + try section.emit(spv.gpa, .OpConstantTrue, .{ + .id_result_type = self.resultId(bool_info.ty), + .id_result = result_id, + }); + }, + false => { + try section.emit(spv.gpa, .OpConstantFalse, .{ + .id_result_type = self.resultId(bool_info.ty), + .id_result = result_id, + }); + }, + }, } } @@ -713,6 +767,24 @@ pub fn resolve(self: *Self, spv: *Module, key: Key) !Ref { }, else => unreachable, }, + .undef => |undef| .{ + .tag = .undef, + .result_id = result_id, + .data = @enumToInt(undef.ty), + }, + .null => |null_info| .{ + .tag = .null, + .result_id = result_id, + .data = @enumToInt(null_info.ty), + }, + .bool => |bool_info| .{ + .tag = switch (bool_info.value) { + true => Tag.bool_true, + false => Tag.bool_false, + }, + .result_id = result_id, + .data = @enumToInt(bool_info.ty), + }, }; try self.items.append(spv.gpa, item); @@ -850,6 +922,20 @@ pub fn lookup(self: *const Self, ref: Ref) Key { .value = .{ .uint64 = payload.decode() }, } }; }, + .undef => .{ .undef = .{ + .ty = @intToEnum(Ref, data), + } }, + .null => .{ .null = .{ + .ty = @intToEnum(Ref, data), + } }, + .bool_true => .{ .bool = .{ + .ty = @intToEnum(Ref, data), + .value = true, + } }, + .bool_false => .{ .bool = .{ + .ty = @intToEnum(Ref, data), + .value = false, + } }, }; } 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, - }; - }; -}; -- cgit v1.2.3 From 0c41945a01a3a5254e8b4266a0677f2d8224aa1a Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 29 May 2023 23:57:36 +0200 Subject: spirv: rename TypeConstantCache -> Cache --- src/codegen/spirv/Cache.zig | 1046 +++++++++++++++++++++++++++++++ src/codegen/spirv/Module.zig | 2 +- src/codegen/spirv/TypeConstantCache.zig | 1046 ------------------------------- 3 files changed, 1047 insertions(+), 1047 deletions(-) create mode 100644 src/codegen/spirv/Cache.zig delete mode 100644 src/codegen/spirv/TypeConstantCache.zig (limited to 'src/codegen/spirv/Module.zig') 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 4c078f301b..a9a2571ff0 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -21,7 +21,7 @@ const IdResultType = spec.IdResultType; const Section = @import("Section.zig"); -const Cache = @import("TypeConstantCache.zig"); +const Cache = @import("Cache.zig"); pub const CacheKey = Cache.Key; pub const CacheRef = Cache.Ref; pub const CacheString = Cache.String; diff --git a/src/codegen/spirv/TypeConstantCache.zig b/src/codegen/spirv/TypeConstantCache.zig deleted file mode 100644 index 4c41bf583b..0000000000 --- a/src/codegen/spirv/TypeConstantCache.zig +++ /dev/null @@ -1,1046 +0,0 @@ -//! 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), - }; -} -- cgit v1.2.3 From 3c4cc1eedb959aa804ca752e20268ccecc14ccef Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 30 May 2023 18:41:31 +0200 Subject: spirv: eliminate remaining uses of emitConstant --- src/codegen/spirv.zig | 56 ++++++++++---------------------------------- src/codegen/spirv/Module.zig | 21 +---------------- 2 files changed, 14 insertions(+), 63 deletions(-) (limited to 'src/codegen/spirv/Module.zig') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index bedff8cc56..09ace669a9 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -432,8 +432,6 @@ pub const DeclGen = struct { dg: *DeclGen, /// Cached reference of the u32 type. u32_ty_ref: CacheRef, - /// Cached type id of the u32 type. - u32_ty_id: IdRef, /// The members of the resulting structure type members: std.ArrayList(CacheRef), /// The initializers of each of the members. @@ -466,9 +464,7 @@ pub const DeclGen = struct { } const word = @bitCast(Word, self.partial_word.buffer); - const result_id = self.dg.spv.allocId(); - // TODO: Integrate with caching mechanism - try self.dg.spv.emitConstant(self.u32_ty_id, result_id, .{ .uint32 = word }); + const result_id = try self.dg.spv.constInt(self.u32_ty_ref, word); try self.members.append(self.u32_ty_ref); try self.initializers.append(result_id); @@ -519,11 +515,7 @@ pub const DeclGen = struct { } fn addNullPtr(self: *@This(), ptr_ty_ref: CacheRef) !void { - const result_id = self.dg.spv.allocId(); - try self.dg.spv.sections.types_globals_constants.emit(self.dg.spv.gpa, .OpConstantNull, .{ - .id_result_type = self.dg.typeId(ptr_ty_ref), - .id_result = result_id, - }); + const result_id = try self.dg.spv.constNull(ptr_ty_ref); try self.addPtr(ptr_ty_ref, result_id); } @@ -909,7 +901,6 @@ pub const DeclGen = struct { var icl = IndirectConstantLowering{ .dg = self, .u32_ty_ref = u32_ty_ref, - .u32_ty_id = self.typeId(u32_ty_ref), .members = std.ArrayList(CacheRef).init(self.gpa), .initializers = std.ArrayList(IdRef).init(self.gpa), .decl_deps = std.AutoArrayHashMap(SpvModule.Decl.Index, void).init(self.gpa), @@ -978,19 +969,12 @@ pub const DeclGen = struct { /// This function should only be called during function code generation. fn constant(self: *DeclGen, ty: Type, val: Value, repr: Repr) !IdRef { const target = self.getTarget(); - const section = &self.spv.sections.types_globals_constants; const result_ty_ref = try self.resolveType(ty, repr); - const result_ty_id = self.typeId(result_ty_ref); log.debug("constant: ty = {}, val = {}", .{ ty.fmt(self.module), val.fmtValue(ty, self.module) }); if (val.isUndef()) { - const result_id = self.spv.allocId(); - try section.emit(self.spv.gpa, .OpUndef, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - }); - return result_id; + return self.spv.constUndef(result_ty_ref); } switch (ty.zigTypeTag()) { @@ -1002,28 +986,15 @@ pub const DeclGen = struct { } }, .Bool => switch (repr) { - .direct => { - const result_id = self.spv.allocId(); - const operands = .{ .id_result_type = result_ty_id, .id_result = result_id }; - if (val.toBool()) { - try section.emit(self.spv.gpa, .OpConstantTrue, operands); - } else { - try section.emit(self.spv.gpa, .OpConstantFalse, operands); - } - return result_id; - }, + .direct => return try self.spv.constBool(result_ty_ref, val.toBool()), .indirect => return try self.spv.constInt(result_ty_ref, @boolToInt(val.toBool())), }, - .Float => { - const result_id = self.spv.allocId(); - switch (ty.floatBits(target)) { - 16 => try self.spv.emitConstant(result_ty_id, result_id, .{ .float32 = val.toFloat(f16) }), - 32 => try self.spv.emitConstant(result_ty_id, result_id, .{ .float32 = val.toFloat(f32) }), - 64 => try self.spv.emitConstant(result_ty_id, result_id, .{ .float64 = val.toFloat(f64) }), - 80, 128 => unreachable, // TODO - else => unreachable, - } - return result_id; + .Float => return switch (ty.floatBits(target)) { + 16 => try self.spv.resolveId(.{ .float = .{ .ty = result_ty_ref, .value = .{ .float16 = val.toFloat(f16) } } }), + 32 => try self.spv.resolveId(.{ .float = .{ .ty = result_ty_ref, .value = .{ .float32 = val.toFloat(f32) } } }), + 64 => try self.spv.resolveId(.{ .float = .{ .ty = result_ty_ref, .value = .{ .float64 = val.toFloat(f64) } } }), + 80, 128 => unreachable, // TODO + else => unreachable, }, .ErrorSet => { const value = switch (val.tag()) { @@ -1081,7 +1052,7 @@ pub const DeclGen = struct { try self.func.decl_deps.put(self.spv.gpa, spv_decl_index, {}); try self.func.body.emit(self.spv.gpa, .OpLoad, .{ - .id_result_type = result_ty_id, + .id_result_type = self.typeId(result_ty_ref), .id_result = result_id, .pointer = self.spv.declPtr(spv_decl_index).result_id, }); @@ -2607,9 +2578,8 @@ pub const DeclGen = struct { .Struct => switch (object_ty.containerLayout()) { .Packed => unreachable, // TODO else => { - const u32_ty_id = self.typeId(try self.intType(.unsigned, 32)); - const field_index_id = self.spv.allocId(); - try self.spv.emitConstant(u32_ty_id, field_index_id, .{ .uint32 = field_index }); + const field_index_ty_ref = try self.intType(.unsigned, 32); + const field_index_id = try self.spv.constInt(field_index_ty_ref, field_index); const result_ty_ref = try self.resolveType(result_ptr_ty, .direct); return try self.accessChain(result_ty_ref, object_ptr, &.{field_index_id}); }, diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index a9a2571ff0..1d4840aeb7 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -127,7 +127,7 @@ sections: struct { // OpModuleProcessed - skip for now. /// Annotation instructions (OpDecorate etc). annotations: Section = .{}, - /// Global variable declarations + /// Type declarations, constants, global variables /// 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 @@ -135,10 +135,6 @@ sections: struct { /// 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. - /// The only thing that needs to be kept here is OpUndef - globals: Section = .{}, - /// Type declarations, constants, global variables - /// Below this section, OpLine and OpNoLine is allowed. types_globals_constants: Section = .{}, // Functions without a body - skip for now. /// Regular function definitions. @@ -192,7 +188,6 @@ 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.globals.deinit(self.gpa); self.sections.functions.deinit(self.gpa); self.source_file_names.deinit(self.gpa); @@ -365,7 +360,6 @@ pub fn flush(self: *Module, file: std.fs.File) !void { self.sections.annotations.toWords(), types_constants.toWords(), self.sections.types_globals_constants.toWords(), - self.sections.globals.toWords(), globals.toWords(), self.sections.functions.toWords(), }; @@ -484,19 +478,6 @@ pub fn constComposite(self: *Module, ty_ref: CacheRef, members: []const IdRef) ! return result_id; } -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, - .id_result = result_id, - .value = value, - }); -} - /// Decorate a result-id. pub fn decorate( self: *Module, -- cgit v1.2.3