From 17de4a88e9d494cfd7e07ab5277b5e335a2dc9e3 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 25 Nov 2022 01:31:06 +0100 Subject: [PATCH 01/51] spirv: add Addresses capability for opencl This capability is required to generate SPIR-V kernels with the Physical32 and Physical64 memory models, which we use in OpenCL kernels. --- src/link/SpirV.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 2d74e404eb..284e25671d 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -242,7 +242,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No fn writeCapabilities(spv: *SpvModule, target: std.Target) !void { // TODO: Integrate with a hypothetical feature system const caps: []const spec.Capability = switch (target.os.tag) { - .opencl => &.{.Kernel}, + .opencl => &.{ .Kernel, .Addresses }, .glsl450 => &.{.Shader}, .vulkan => &.{.Shader}, else => unreachable, // TODO From 6dc1fafe9882bdfb063c9bc02149c4a97fcaa2b5 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 25 Nov 2022 01:51:55 +0100 Subject: [PATCH 02/51] std: add generic target for spirv This adds a general target for SPIR-V compilation. Previously there was not any target machine defined for SPIR-V. TODO is to reword the features for this target. We don't really need the full list of capabilities in the features, we should only put a few features here which we can actually use during code generation. --- lib/std/target.zig | 2 ++ lib/std/target/spirv.zig | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/lib/std/target.zig b/lib/std/target.zig index c02efa2495..5d7d91a229 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -1329,6 +1329,7 @@ pub const Target = struct { .amdgcn => comptime allCpusFromDecls(amdgpu.cpu), .riscv32, .riscv64 => comptime allCpusFromDecls(riscv.cpu), .sparc, .sparc64, .sparcel => comptime allCpusFromDecls(sparc.cpu), + .spirv32, .spirv64 => comptime allCpusFromDecls(spirv.cpu), .s390x => comptime allCpusFromDecls(s390x.cpu), .x86, .x86_64 => comptime allCpusFromDecls(x86.cpu), .xtensa => comptime allCpusFromDecls(xtensa.cpu), @@ -1392,6 +1393,7 @@ pub const Target = struct { .amdgcn => &amdgpu.cpu.generic, .riscv32 => &riscv.cpu.generic_rv32, .riscv64 => &riscv.cpu.generic_rv64, + .spirv32, .spirv64 => &spirv.cpu.generic, .sparc, .sparcel => &sparc.cpu.generic, .sparc64 => &sparc.cpu.v9, // 64-bit SPARC needs v9 as the baseline .s390x => &s390x.cpu.generic, diff --git a/lib/std/target/spirv.zig b/lib/std/target/spirv.zig index 39a91c7537..b0f5a19461 100644 --- a/lib/std/target/spirv.zig +++ b/lib/std/target/spirv.zig @@ -2081,3 +2081,11 @@ pub const all_features = blk: { } break :blk result; }; + +pub const cpu = struct { + pub const generic = CpuModel{ + .name = "generic", + .llvm_name = "generic", + .features = featureSet(&[_]Feature{}), + }; +}; From c6fbe0d5d89a7b2665a0afa7fad23027fd0b87ca Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 25 Nov 2022 14:58:14 +0100 Subject: [PATCH 03/51] dont destroy old bin file on link openpath failure This was causing some crashes. --- src/Compilation.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index f6f4851e5e..caa572e29d 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1948,8 +1948,9 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void .sub_path = std.fs.path.basename(sub_path), }; } - comp.bin_file.destroy(); + var old_bin_file = comp.bin_file; comp.bin_file = try link.File.openPath(comp.gpa, options); + old_bin_file.destroy(); } // For compiling C objects, we rely on the cache hash system to avoid duplicating work. From 12ff36265448850add6917c20a49a7f56a95dfe4 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 25 Nov 2022 14:58:37 +0100 Subject: [PATCH 04/51] spirv: make genericName match target name. Changes the 'generic name' of the target to spirv, from spir-v. --- lib/std/target.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/target.zig b/lib/std/target.zig index 5d7d91a229..18e1f28f4a 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -1276,7 +1276,7 @@ pub const Target = struct { .x86, .x86_64 => "x86", .nvptx, .nvptx64 => "nvptx", .wasm32, .wasm64 => "wasm", - .spirv32, .spirv64 => "spir-v", + .spirv32, .spirv64 => "spirv", else => @tagName(arch), }; } From ba60d456b45bd391ec25195c800d61ab14de7985 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 9 Apr 2023 01:50:43 +0200 Subject: [PATCH 05/51] spirv: cannot build libc SPIR-V cannot build libc, ssp, compiler-rt, etc at the time of this commit, so prevent trying to build them. --- lib/std/target.zig | 6 +++++- src/Compilation.zig | 9 +++++---- src/target.zig | 8 ++++---- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/std/target.zig b/lib/std/target.zig index 18e1f28f4a..fba399f02c 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -944,7 +944,7 @@ pub const Target = struct { }; } - pub fn isSPIRV(arch: Arch) bool { + pub fn isSpirV(arch: Arch) bool { return switch (arch) { .spirv32, .spirv64 => true, else => false, @@ -1534,6 +1534,10 @@ pub const Target = struct { return !self.cpu.arch.isWasm(); } + pub fn isSpirV(self: Target) bool { + return self.cpu.arch.isSpirV(); + } + pub const FloatAbi = enum { hard, soft, diff --git a/src/Compilation.zig b/src/Compilation.zig index caa572e29d..6eef76cf14 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -722,10 +722,11 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { // Once they are capable this condition could be removed. When removing this condition, // also test the use case of `build-obj -fcompiler-rt` with the native backends // and make sure the compiler-rt symbols are emitted. - const capable_of_building_compiler_rt = build_options.have_llvm and options.target.os.tag != .plan9; - - const capable_of_building_zig_libc = build_options.have_llvm and options.target.os.tag != .plan9; - const capable_of_building_ssp = build_options.have_llvm and options.target.os.tag != .plan9; + const is_p9 = options.target.os.tag == .plan9; + const is_spv = options.target.cpu.arch.isSpirV(); + const capable_of_building_compiler_rt = build_options.have_llvm and !is_p9 and !is_spv; + const capable_of_building_zig_libc = build_options.have_llvm and !is_p9 and !is_spv; + const capable_of_building_ssp = build_options.have_llvm and !is_p9 and !is_spv; const comp: *Compilation = comp: { // For allocations that have the same lifetime as Compilation. This arena is used only during this diff --git a/src/target.zig b/src/target.zig index f785082bd4..ad3e0905eb 100644 --- a/src/target.zig +++ b/src/target.zig @@ -163,7 +163,7 @@ pub fn canBuildLibC(target: std.Target) bool { pub fn cannotDynamicLink(target: std.Target) bool { return switch (target.os.tag) { .freestanding, .other => true, - else => false, + else => target.isSpirV(), }; } @@ -331,18 +331,18 @@ pub fn supportsStackProbing(target: std.Target) bool { } pub fn supportsStackProtector(target: std.Target) bool { - _ = target; - return true; + return !target.isSpirV(); } pub fn libcProvidesStackProtector(target: std.Target) bool { - return !target.isMinGW() and target.os.tag != .wasi; + return !target.isMinGW() and target.os.tag != .wasi and !target.isSpirV(); } pub fn supportsReturnAddress(target: std.Target) bool { return switch (target.cpu.arch) { .wasm32, .wasm64 => target.os.tag == .emscripten, .bpfel, .bpfeb => false, + .spirv32, .spirv64 => false, else => true, }; } From df5577c28bf1cc26f8bcf21ed1201e31987ac009 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 25 Nov 2022 23:58:21 +0100 Subject: [PATCH 06/51] spirv: allow more calling conventions This allows the Zig calling convention and makes way for a Kernel calling convention in the future. Any future checks on calling conventions should be placed in Sema.zig. --- src/codegen/spirv.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 5f27c14e95..3e2e202fc6 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -491,9 +491,7 @@ pub const DeclGen = struct { break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); }, .Fn => blk: { - // We only support C-calling-convention functions for now, no varargs. - if (ty.fnCallingConvention() != .C) - return self.fail("Unsupported calling convention for SPIR-V", .{}); + // TODO: Put this somewhere in Sema.zig if (ty.fnIsVarArgs()) return self.fail("VarArgs functions are unsupported for SPIR-V", .{}); From a60308f87c658a210e9b3bd619e908368db6f24e Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 26 Nov 2022 00:48:42 +0100 Subject: [PATCH 07/51] spirv: enum type This gives the spir-v backend the power to emit enum types. These are simply lowered to their backing integer type. --- src/codegen/spirv.zig | 63 +++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 3e2e202fc6..93bb7b19f8 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -443,32 +443,43 @@ pub const DeclGen = struct { return self.spv.typeResultId(type_ref); } + /// Create an integer type suitable for storing at least 'bits' bits. + fn intType(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvType.Ref { + 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 }); + }; + + const payload = try self.spv.arena.create(SpvType.Payload.Int); + payload.* = .{ + .width = backing_bits, + .signedness = signedness, + }; + return try self.spv.resolveType(SpvType.initPayload(&payload.base)); + } + /// Turn a Zig type into a SPIR-V Type, and return a reference to it. fn resolveType(self: *DeclGen, ty: Type) Error!SpvType.Ref { const target = self.getTarget(); - return switch (ty.zigTypeTag()) { - .Void => try self.spv.resolveType(SpvType.initTag(.void)), - .Bool => blk: { + switch (ty.zigTypeTag()) { + .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)), + .Bool => { // TODO: SPIR-V booleans are opaque. For local variables this is fine, but for structs // members we want to use integer types instead. - break :blk try self.spv.resolveType(SpvType.initTag(.bool)); + return try self.spv.resolveType(SpvType.initTag(.bool)); }, - .Int => blk: { + .Int => { const int_info = ty.intInfo(target); - const backing_bits = self.backingIntBits(int_info.bits) orelse { - // TODO: Integers too big for any native type are represented as "composite integers": - // An array of largestSupportedIntBits. - return self.todo("Implement composite int type {}", .{ty.fmtDebug()}); - }; - - const payload = try self.spv.arena.create(SpvType.Payload.Int); - payload.* = .{ - .width = backing_bits, - .signedness = int_info.signedness, - }; - break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); + return try self.intType(int_info.signedness, int_info.bits); }, - .Float => blk: { + .Enum => { + var buffer: Type.Payload.Bits = undefined; + const int_ty = ty.intTagType(&buffer); + const int_info = int_ty.intInfo(target); + return try self.intType(.unsigned, int_info.bits); + }, + .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); @@ -488,9 +499,9 @@ pub const DeclGen = struct { payload.* = .{ .width = bits, }; - break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); + return try self.spv.resolveType(SpvType.initPayload(&payload.base)); }, - .Fn => blk: { + .Fn => { // TODO: Put this somewhere in Sema.zig if (ty.fnIsVarArgs()) return self.fail("VarArgs functions are unsupported for SPIR-V", .{}); @@ -504,9 +515,9 @@ pub const DeclGen = struct { const payload = try self.spv.arena.create(SpvType.Payload.Function); payload.* = .{ .return_type = return_type, .parameters = param_types }; - break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); + return try self.spv.resolveType(SpvType.initPayload(&payload.base)); }, - .Pointer => blk: { + .Pointer => { const payload = try self.spv.arena.create(SpvType.Payload.Pointer); payload.* = .{ .storage_class = spirvStorageClass(ty.ptrAddressSpace()), @@ -516,9 +527,9 @@ pub const DeclGen = struct { .alignment = null, .max_byte_offset = null, }; - break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); + return try self.spv.resolveType(SpvType.initPayload(&payload.base)); }, - .Vector => blk: { + .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. @@ -533,7 +544,7 @@ pub const DeclGen = struct { .component_type = try self.resolveType(ty.elemType()), .component_count = @intCast(u32, ty.vectorLen()), }; - break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); + return try self.spv.resolveType(SpvType.initPayload(&payload.base)); }, .Null, @@ -545,7 +556,7 @@ pub const DeclGen = struct { => unreachable, // Must be comptime. else => |tag| return self.todo("Implement zig type '{}'", .{tag}), - }; + } } fn spirvStorageClass(as: std.builtin.AddressSpace) spec.StorageClass { From 5826a8a0640d79de83be131434ec91315e75087b Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 26 Nov 2022 01:05:37 +0100 Subject: [PATCH 08/51] spirv: make Type.Ref stronger Making Type.Ref an unbounded enum rather than a simple integer ensures that we don't accidently confuse this token for another type. --- src/codegen/spirv/Assembler.zig | 2 +- src/codegen/spirv/Module.zig | 6 +++--- src/codegen/spirv/type.zig | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig index 6e77818fa5..3a35bc4db9 100644 --- a/src/codegen/spirv/Assembler.zig +++ b/src/codegen/spirv/Assembler.zig @@ -753,7 +753,7 @@ 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()[result_type_ref]; + const result_type = self.spv.type_cache.keys()[@enumToInt(result_type_ref)]; switch (result_type.tag()) { .int => { const int = result_type.castTag(.int).?; diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 3562e87be4..2b62bcaf0e 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -216,7 +216,7 @@ pub fn resolveType(self: *Module, ty: Type) !Type.Ref { result.value_ptr.* = try self.emitType(ty); } - return result.index; + return @intToEnum(Type.Ref, result.index); } pub fn resolveTypeId(self: *Module, ty: Type) !IdRef { @@ -226,12 +226,12 @@ pub fn resolveTypeId(self: *Module, ty: Type) !IdRef { /// Get the result-id of a particular type, by reference. Asserts type_ref is valid. pub fn typeResultId(self: Module, type_ref: Type.Ref) IdResultType { - return self.type_cache.values()[type_ref]; + return self.type_cache.values()[@enumToInt(type_ref)]; } /// Get the result-id of a particular type as IdRef, by Type.Ref. Asserts type_ref is valid. pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef { - return self.type_cache.values()[type_ref].toRef(); + return self.type_cache.values()[@enumToInt(type_ref)].toRef(); } /// Unconditionally emit a spir-v type into the appropriate section. diff --git a/src/codegen/spirv/type.zig b/src/codegen/spirv/type.zig index dc993b62ff..5ec013cb0a 100644 --- a/src/codegen/spirv/type.zig +++ b/src/codegen/spirv/type.zig @@ -11,7 +11,7 @@ pub const Type = extern union { ptr_otherwise: *Payload, /// A reference to another SPIR-V type. - pub const Ref = usize; + pub const Ref = enum(u32) { _ }; pub fn initTag(comptime small_tag: Tag) Type { comptime assert(@enumToInt(small_tag) < Tag.no_payload_count); From 3eafe3033ef83e5b34e3ccbd6e803c7a046df390 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 26 Nov 2022 12:23:07 +0100 Subject: [PATCH 09/51] spirv: improve storage efficiency for integer and float types In practice there are only a few variations of these types allowed, so it kind-of makes sense to write them all out. Because the types are hashed this does not actually save all that many bytes in the long run, though. Perhaps some of these types should be pre-registered? --- src/codegen/spirv.zig | 13 +-- src/codegen/spirv/Assembler.zig | 50 +++++------ src/codegen/spirv/Module.zig | 19 ++-- src/codegen/spirv/type.zig | 153 +++++++++++++++++++++++++++++--- 4 files changed, 181 insertions(+), 54 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 93bb7b19f8..22045a282d 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -451,12 +451,7 @@ pub const DeclGen = struct { return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits }); }; - const payload = try self.spv.arena.create(SpvType.Payload.Int); - payload.* = .{ - .width = backing_bits, - .signedness = signedness, - }; - return try self.spv.resolveType(SpvType.initPayload(&payload.base)); + return try self.spv.resolveType(try SpvType.int(self.spv.arena, signedness, backing_bits)); } /// Turn a Zig type into a SPIR-V Type, and return a reference to it. @@ -495,11 +490,7 @@ pub const DeclGen = struct { return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits}); } - const payload = try self.spv.arena.create(SpvType.Payload.Float); - payload.* = .{ - .width = bits, - }; - return try self.spv.resolveType(SpvType.initPayload(&payload.base)); + return try self.spv.resolveType(SpvType.float(bits)); }, .Fn => { // TODO: Put this somewhere in Sema.zig diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig index 3a35bc4db9..c4432102a0 100644 --- a/src/codegen/spirv/Assembler.zig +++ b/src/codegen/spirv/Assembler.zig @@ -266,27 +266,28 @@ fn processTypeInstruction(self: *Assembler) !AsmValue { .OpTypeVoid => SpvType.initTag(.void), .OpTypeBool => SpvType.initTag(.bool), .OpTypeInt => blk: { - const payload = try self.spv.arena.create(SpvType.Payload.Int); const signedness: std.builtin.Signedness = switch (operands[2].literal32) { 0 => .unsigned, 1 => .signed, else => { // TODO: Improve source location. - return self.fail(0, "'{}' is not a valid signedness (expected 0 or 1)", .{operands[2].literal32}); + return self.fail(0, "{} is not a valid signedness (expected 0 or 1)", .{operands[2].literal32}); }, }; - payload.* = .{ - .width = operands[1].literal32, - .signedness = signedness, + 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 SpvType.initPayload(&payload.base); + break :blk try SpvType.int(self.spv.arena, signedness, width); }, .OpTypeFloat => blk: { - const payload = try self.spv.arena.create(SpvType.Payload.Float); - payload.* = .{ - .width = operands[1].literal32, - }; - break :blk SpvType.initPayload(&payload.base); + const bits = operands[1].literal32; + switch (bits) { + 16, 32, 64 => {}, + else => { + 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); @@ -754,21 +755,18 @@ 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)]; - switch (result_type.tag()) { - .int => { - const int = result_type.castTag(.int).?; - try self.parseContextDependentInt(int.signedness, int.width); - }, - .float => { - const width = result_type.castTag(.float).?.width; - 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())}), + 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())}); } } diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 2b62bcaf0e..6998d13f42 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -250,21 +250,30 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { switch (ty.tag()) { .void => try types.emit(self.gpa, .OpTypeVoid, result_id_operand), .bool => try types.emit(self.gpa, .OpTypeBool, result_id_operand), - .int => { - const signedness: spec.LiteralInteger = switch (ty.payload(.int).signedness) { + .u8, + .u16, + .u32, + .u64, + .i8, + .i16, + .i32, + .i64, + .int, + => { + const signedness: spec.LiteralInteger = switch (ty.intSignedness()) { .unsigned => 0, .signed => 1, }; try types.emit(self.gpa, .OpTypeInt, .{ .id_result = result_id, - .width = ty.payload(.int).width, + .width = ty.intFloatBits(), .signedness = signedness, }); }, - .float => try types.emit(self.gpa, .OpTypeFloat, .{ + .f16, .f32, .f64 => try types.emit(self.gpa, .OpTypeFloat, .{ .id_result = result_id, - .width = ty.payload(.float).width, + .width = ty.intFloatBits(), }), .vector => try types.emit(self.gpa, .OpTypeVector, .{ .id_result = result_id, diff --git a/src/codegen/spirv/type.zig b/src/codegen/spirv/type.zig index 5ec013cb0a..a65ddca01e 100644 --- a/src/codegen/spirv/type.zig +++ b/src/codegen/spirv/type.zig @@ -3,6 +3,8 @@ const std = @import("std"); const assert = std.debug.assert; +const Signedness = std.builtin.Signedness; +const Allocator = std.mem.Allocator; const spec = @import("spec.zig"); @@ -23,6 +25,41 @@ pub const Type = extern union { 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; @@ -80,9 +117,19 @@ pub const Type = extern union { .queue, .pipe_storage, .named_barrier, + .u8, + .u16, + .u32, + .u64, + .i8, + .i16, + .i32, + .i64, + .f16, + .f32, + .f64, => return true, .int, - .float, .vector, .matrix, .sampled_image, @@ -132,6 +179,17 @@ pub const Type = extern union { .queue, .pipe_storage, .named_barrier, + .u8, + .u16, + .u32, + .u64, + .i8, + .i16, + .i32, + .i64, + .f16, + .f32, + .f64, => {}, else => self.hashPayload(@field(Tag, field.name), &hasher), } @@ -185,6 +243,53 @@ pub const Type = extern union { }; } + 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, @@ -195,10 +300,20 @@ pub const Type = extern union { queue, pipe_storage, named_barrier, + u8, + u16, + u32, + u64, + i8, + i16, + i32, + i64, + f16, + f32, + f64, // After this, the tag requires a payload. int, - float, vector, matrix, image, @@ -211,14 +326,33 @@ pub const Type = extern union { function, pipe, - pub const last_no_payload_tag = Tag.named_barrier; + 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 => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"), + .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, - .float => Payload.Float, .vector => Payload.Vector, .matrix => Payload.Matrix, .image => Payload.Image, @@ -239,13 +373,8 @@ pub const Type = extern union { pub const Int = struct { base: Payload = .{ .tag = .int }, - width: u32, - signedness: std.builtin.Signedness, - }; - - pub const Float = struct { - base: Payload = .{ .tag = .float }, - width: u32, + width: u16, + signedness: Signedness, }; pub const Vector = struct { From dae8b4c11f6a59dc6ccd3e4fe327c43eb73c44cb Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 26 Nov 2022 12:39:04 +0100 Subject: [PATCH 10/51] spirv: emit OpName for some primitive types OpName instructions assign a debug name to a type. Some basic types - bool, void, ints, and floats are given a debug name this way. TODO is to extend this to the other types. --- src/codegen/spirv/Module.zig | 56 ++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 6998d13f42..a6e0783bf4 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -75,7 +75,8 @@ sections: struct { execution_modes: Section = .{}, /// OpString, OpSourcExtension, OpSource, OpSourceContinued. debug_strings: Section = .{}, - // OpName, OpMemberName - skip for now. + // OpName, OpMemberName. + debug_names: Section = .{}, // OpModuleProcessed - skip for now. /// Annotation instructions (OpDecorate etc). annotations: Section = .{}, @@ -115,6 +116,7 @@ pub fn deinit(self: *Module) void { self.sections.entry_points.deinit(self.gpa); self.sections.execution_modes.deinit(self.gpa); self.sections.debug_strings.deinit(self.gpa); + self.sections.debug_names.deinit(self.gpa); self.sections.annotations.deinit(self.gpa); self.sections.types_globals_constants.deinit(self.gpa); self.sections.functions.deinit(self.gpa); @@ -154,6 +156,7 @@ pub fn flush(self: Module, file: std.fs.File) !void { self.sections.entry_points.toWords(), self.sections.execution_modes.toWords(), self.sections.debug_strings.toWords(), + self.sections.debug_names.toWords(), self.sections.annotations.toWords(), self.sections.types_globals_constants.toWords(), self.sections.functions.toWords(), @@ -244,12 +247,25 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { const result_id = self.allocId(); const ref_id = result_id.toRef(); const types = &self.sections.types_globals_constants; + const debug_names = &self.sections.debug_names; const annotations = &self.sections.annotations; const result_id_operand = .{ .id_result = result_id }; switch (ty.tag()) { - .void => try types.emit(self.gpa, .OpTypeVoid, result_id_operand), - .bool => try types.emit(self.gpa, .OpTypeBool, result_id_operand), + .void => { + try types.emit(self.gpa, .OpTypeVoid, result_id_operand); + try debug_names.emit(self.gpa, .OpName, .{ + .target = result_id.toRef(), + .name = "void", + }); + }, + .bool => { + try types.emit(self.gpa, .OpTypeBool, result_id_operand); + try debug_names.emit(self.gpa, .OpName, .{ + .target = result_id.toRef(), + .name = "bool", + }); + }, .u8, .u16, .u32, @@ -260,6 +276,7 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { .i64, .int, => { + const bits = ty.intFloatBits(); const signedness: spec.LiteralInteger = switch (ty.intSignedness()) { .unsigned => 0, .signed => 1, @@ -267,14 +284,37 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { try types.emit(self.gpa, .OpTypeInt, .{ .id_result = result_id, - .width = ty.intFloatBits(), + .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.toRef(), + .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.toRef(), + .name = name, + }); }, - .f16, .f32, .f64 => try types.emit(self.gpa, .OpTypeFloat, .{ - .id_result = result_id, - .width = ty.intFloatBits(), - }), .vector => try types.emit(self.gpa, .OpTypeVector, .{ .id_result = result_id, .component_type = self.typeResultId(ty.childType()).toRef(), From 3f92eaceb61796254d0465ba5689378f15155791 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 26 Nov 2022 16:51:53 +0100 Subject: [PATCH 11/51] spirv: array, structs, bitcast, call Implements type lowering for arrays and structs, and implements instruction lowering for bitcast and call. Bitcast currently naively maps to the OpBitcast instruction - this is only valid for some primitive types, and should be improved to work with composites. --- src/codegen/spirv.zig | 105 ++++++++++++++++++++++++++++++++++- src/codegen/spirv/Module.zig | 17 +++++- src/codegen/spirv/type.zig | 50 +++++++++-------- src/link/SpirV.zig | 2 + 4 files changed, 146 insertions(+), 28 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 22045a282d..5b73a64837 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -492,6 +492,21 @@ pub const DeclGen = struct { return try self.spv.resolveType(SpvType.float(bits)); }, + .Array => { + const elem_ty = ty.childType(); + const total_len_u64 = ty.arrayLen() + @boolToInt(ty.sentinel() != null); + const total_len = std.math.cast(u32, total_len_u64) orelse { + return self.fail("array type of {} elements is too large", .{total_len_u64}); + }; + + const payload = try self.spv.arena.create(SpvType.Payload.Array); + payload.* = .{ + .element_type = try self.resolveType(elem_ty), + .length = total_len, + .array_stride = @intCast(u32, ty.abiSize(target)), + }; + return try self.spv.resolveType(SpvType.initPayload(&payload.base)); + }, .Fn => { // TODO: Put this somewhere in Sema.zig if (ty.fnIsVarArgs()) @@ -537,7 +552,37 @@ pub const DeclGen = struct { }; return try self.spv.resolveType(SpvType.initPayload(&payload.base)); }, + .Struct => { + if (ty.isSimpleTupleOrAnonStruct()) { + return self.todo("implement tuple struct type", .{}); + } + const struct_ty = ty.castTag(.@"struct").?.data; + + if (struct_ty.layout == .Packed) { + return try self.resolveType(struct_ty.backing_int_ty); + } + + 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()) |field| { + if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; + + members[member_index] = .{ + .ty = try self.resolveType(field.ty), + .offset = field.offset, + .decorations = .{}, + }; + } + + const payload = try self.spv.arena.create(SpvType.Payload.Struct); + payload.* = .{ + .members = members[0..member_index], + .decorations = .{}, + .member_decoration_extra = &.{}, + }; + return try self.spv.resolveType(SpvType.initPayload(&payload.base)); + }, .Null, .Undefined, .EnumLiteral, @@ -632,7 +677,7 @@ pub const DeclGen = struct { .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd), .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr), - .not => try self.airNot(inst), + .not => try self.airNot(inst), .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual), .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual), @@ -646,6 +691,7 @@ pub const DeclGen = struct { .block => (try self.airBlock(inst)) orelse return, .load => try self.airLoad(inst), + .bitcast => try self.airBitcast(inst), .br => return self.airBr(inst), .breakpoint => return, .cond_br => return self.airCondBr(inst), @@ -657,6 +703,11 @@ pub const DeclGen = struct { .unreach => return self.airUnreach(), .assembly => (try self.airAssembly(inst)) orelse return, + .call => (try self.airCall(inst, .auto)) orelse return, + .call_always_tail => (try self.airCall(inst, .always_tail)) orelse return, + .call_never_tail => (try self.airCall(inst, .never_tail)) orelse return, + .call_never_inline => (try self.airCall(inst, .never_inline)) orelse return, + .dbg_var_ptr => return, .dbg_var_val => return, .dbg_block_begin => return, @@ -911,6 +962,19 @@ pub const DeclGen = struct { return result_id.toRef(); } + fn airBitcast(self: *DeclGen, inst: Air.Inst.Index) !IdRef { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand_id = try self.resolve(ty_op.operand); + const result_id = self.spv.allocId(); + const result_type_id = try self.resolveTypeId(Type.initTag(.bool)); + try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ + .id_result_type = result_type_id, + .id_result = result_id, + .operand = operand_id, + }); + return result_id.toRef(); + } + fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void { const br = self.air.instructions.items(.data)[inst].br; const block = self.blocks.get(br.block_inst).?; @@ -1158,4 +1222,43 @@ pub const DeclGen = struct { return null; } + + fn airCall(self: *DeclGen, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.Modifier) !?IdRef { + _ = modifier; + + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const extra = self.air.extraData(Air.Call, pl_op.payload); + const args = @ptrCast([]const Air.Inst.Ref, self.air.extra[extra.end..][0..extra.data.args_len]); + const callee_ty = self.air.typeOf(pl_op.operand); + const zig_fn_ty = switch (callee_ty.zigTypeTag()) { + .Fn => callee_ty, + .Pointer => return self.fail("cannot call function pointers", .{}), + else => unreachable, + }; + const fn_info = zig_fn_ty.fnInfo(); + const return_type = fn_info.return_type; + + const result_type_id = try self.resolveTypeId(return_type); + const result_id = self.spv.allocId(); + const callee_id = try self.resolve(pl_op.operand); + + try self.func.body.emitRaw(self.spv.gpa, .OpFunctionCall, 3 + args.len); + self.func.body.writeOperand(spec.IdResultType, result_type_id); + self.func.body.writeOperand(spec.IdResult, result_id); + self.func.body.writeOperand(spec.IdRef, callee_id); + + for (args) |arg| { + const arg_id = try self.resolve(arg); + const arg_ty = self.air.typeOf(arg); + if (!arg_ty.hasRuntimeBitsIgnoreComptime()) continue; + + self.func.body.writeOperand(spec.IdRef, arg_id); + } + + if (return_type.isNoReturn()) { + try self.func.body.emit(self.spv.gpa, .OpUnreachable, {}); + } + + return result_id.toRef(); + } }; diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index a6e0783bf4..f6c4cd735e 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -222,7 +222,7 @@ pub fn resolveType(self: *Module, ty: Type) !Type.Ref { return @intToEnum(Type.Ref, result.index); } -pub fn resolveTypeId(self: *Module, ty: Type) !IdRef { +pub fn resolveTypeId(self: *Module, ty: Type) !IdResultType { const type_ref = try self.resolveType(ty); return self.typeResultId(type_ref); } @@ -243,7 +243,7 @@ pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef { /// 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) !IdResultType { +pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { const result_id = self.allocId(); const ref_id = result_id.toRef(); const types = &self.sections.types_globals_constants; @@ -347,10 +347,21 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { .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 types.emit(self.gpa, .OpConstant, .{ + .id_result_type = size_type_id, + .id_result = length_id, + .value = .{ .uint32 = info.length }, + }); + try types.emit(self.gpa, .OpTypeArray, .{ .id_result = result_id, .element_type = self.typeResultId(ty.childType()).toRef(), - .length = .{ .id = 0 }, // TODO: info.length must be emitted as constant! + .length = length_id.toRef(), }); if (info.array_stride != 0) { try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); diff --git a/src/codegen/spirv/type.zig b/src/codegen/spirv/type.zig index a65ddca01e..2d9d6befe1 100644 --- a/src/codegen/spirv/type.zig +++ b/src/codegen/spirv/type.zig @@ -421,7 +421,7 @@ pub const Type = extern union { length: u32, /// Type has the 'ArrayStride' decoration. /// If zero, no stride is present. - array_stride: u32, + array_stride: u32 = 0, }; pub const RuntimeArray = struct { @@ -434,6 +434,7 @@ pub const Type = extern union { pub const Struct = struct { base: Payload = .{ .tag = .@"struct" }, + // TODO: name members: []Member, decorations: StructDecorations, @@ -444,20 +445,21 @@ pub const Type = extern union { pub const Member = struct { ty: Ref, offset: u32, + // TODO: name decorations: MemberDecorations, }; pub const StructDecorations = packed struct { /// Type has the 'Block' decoration. - block: bool, + block: bool = false, /// Type has the 'BufferBlock' decoration. - buffer_block: bool, + buffer_block: bool = false, /// Type has the 'GLSLShared' decoration. - glsl_shared: bool, + glsl_shared: bool = false, /// Type has the 'GLSLPacked' decoration. - glsl_packed: bool, + glsl_packed: bool = false, /// Type has the 'CPacked' decoration. - c_packed: bool, + c_packed: bool = false, }; pub const MemberDecorations = packed struct { @@ -473,31 +475,31 @@ pub const Type = extern union { 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, + no_perspective: bool = false, /// Member has the 'Flat' decoration. - flat: bool, + flat: bool = false, /// Member has the 'Patch' decoration. - patch: bool, + patch: bool = false, /// Member has the 'Centroid' decoration. - centroid: bool, + centroid: bool = false, /// Member has the 'Sample' decoration. - sample: bool, + sample: bool = false, /// Member has the 'Invariant' decoration. /// Note: requires parent struct to have 'Block'. - invariant: bool, + invariant: bool = false, /// Member has the 'Volatile' decoration. - @"volatile": bool, + @"volatile": bool = false, /// Member has the 'Coherent' decoration. - coherent: bool, + coherent: bool = false, /// Member has the 'NonWritable' decoration. - non_writable: bool, + non_writable: bool = false, /// Member has the 'NonReadable' decoration. - non_readable: bool, + non_readable: bool = false, // The following decorations all imply extra field(s). @@ -506,27 +508,27 @@ pub const Type = extern union { /// 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, + builtin: bool = false, /// Member has the 'Stream' decoration. /// This member has an extra field of type `u32`. - stream: bool, + stream: bool = false, /// Member has the 'Location' decoration. /// This member has an extra field of type `u32`. - location: bool, + location: bool = false, /// Member has the 'Component' decoration. /// This member has an extra field of type `u32`. - component: bool, + component: bool = false, /// Member has the 'XfbBuffer' decoration. /// This member has an extra field of type `u32`. - xfb_buffer: bool, + xfb_buffer: bool = false, /// Member has the 'XfbStride' decoration. /// This member has an extra field of type `u32`. - xfb_stride: bool, + 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, + user_semantic: bool = false, }; }; diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 284e25671d..2d5ca723b2 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -226,6 +226,8 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No const air = entry.value_ptr.air; const liveness = entry.value_ptr.liveness; + log.debug("generating code for {s}", .{decl.name}); + // Note, if `decl` is not a function, air/liveness may be undefined. if (try decl_gen.gen(decl_index, air, liveness)) |msg| { try module.failed_decls.put(module.gpa, decl_index, msg); From 39016948f068c52b3cec24cdfdf5882193ea09b9 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 26 Nov 2022 17:11:30 +0100 Subject: [PATCH 12/51] spirv: slice types Implements type lowering for slices. --- src/codegen/spirv.zig | 52 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 5b73a64837..48f893404a 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -454,6 +454,11 @@ pub const DeclGen = struct { return try self.spv.resolveType(try SpvType.int(self.spv.arena, signedness, backing_bits)); } + /// Create an integer type that represents 'usize'. + fn sizeType(self: *DeclGen) !SpvType.Ref { + return try self.intType(.unsigned, self.getTarget().cpu.arch.ptrBitWidth()); + } + /// Turn a Zig type into a SPIR-V Type, and return a reference to it. fn resolveType(self: *DeclGen, ty: Type) Error!SpvType.Ref { const target = self.getTarget(); @@ -524,16 +529,51 @@ pub const DeclGen = struct { return try self.spv.resolveType(SpvType.initPayload(&payload.base)); }, .Pointer => { - const payload = try self.spv.arena.create(SpvType.Payload.Pointer); - payload.* = .{ - .storage_class = spirvStorageClass(ty.ptrAddressSpace()), - .child_type = try self.resolveType(ty.elemType()), + const ptr_info = ty.ptrInfo().data; + + const ptr_payload = try self.spv.arena.create(SpvType.Payload.Pointer); + ptr_payload.* = .{ + .storage_class = spirvStorageClass(ptr_info.@"addrspace"), + .child_type = try self.resolveType(ptr_info.pointee_type), + // TODO: ??? .array_stride = 0, // Note: only available in Kernels! - .alignment = null, + .alignment = ty.ptrAlignment(target) * 8, .max_byte_offset = null, }; - return try self.spv.resolveType(SpvType.initPayload(&payload.base)); + const spv_ptr_ty = try self.spv.resolveType(SpvType.initPayload(&ptr_payload.base)); + + if (ptr_info.size != .Slice) { + return spv_ptr_ty; + } + + var buf: Type.SlicePtrFieldTypeBuffer = undefined; + const ptr_ty = ty.slicePtrFieldType(&buf); + const len_ty = Type.usize; + + const ptr_size = ptr_ty.abiSize(target); + const len_align = len_ty.abiAlignment(target); + const len_offset = std.mem.alignForwardGeneric(u64, ptr_size, len_align); + + const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, 2); + members[0] = .{ + .ty = spv_ptr_ty, + .offset = 0, + .decorations = .{}, + }; + members[1] = .{ + .ty = try self.sizeType(), + .offset = @intCast(u32, len_offset), + .decorations = .{}, + }; + + const slice_payload = try self.spv.arena.create(SpvType.Payload.Struct); + slice_payload.* = .{ + .members = members, + .decorations = .{}, + .member_decoration_extra = &.{}, + }; + return try self.spv.resolveType(SpvType.initPayload(&slice_payload.base)); }, .Vector => { // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations From 3c5ab4dd3d23b191b184b70dc8255b253a7ddc2c Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 26 Nov 2022 17:37:13 +0100 Subject: [PATCH 13/51] spirv: add liveness checks When a result of a pure instruction is not used, it also does not need to be generated. The other backends already implement these checks, they were ignored in SPIR-V up until now. New instructions added in the future should have these be implemented from the start. --- src/codegen/spirv.zig | 72 ++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 48f893404a..82f156fc99 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -703,7 +703,7 @@ pub const DeclGen = struct { fn genInst(self: *DeclGen, inst: Air.Inst.Index) !void { const air_tags = self.air.instructions.items(.tag); - const result_id = switch (air_tags[inst]) { + const maybe_result_id: ?IdRef = switch (air_tags[inst]) { // zig fmt: off .add, .addwrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd), .sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub), @@ -717,7 +717,8 @@ pub const DeclGen = struct { .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd), .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr), - .not => try self.airNot(inst), + .bitcast => try self.airBitcast(inst), + .not => try self.airNot(inst), .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual), .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual), @@ -728,10 +729,9 @@ pub const DeclGen = struct { .arg => self.airArg(), .alloc => try self.airAlloc(inst), - .block => (try self.airBlock(inst)) orelse return, + .block => try self.airBlock(inst), .load => try self.airLoad(inst), - .bitcast => try self.airBitcast(inst), .br => return self.airBr(inst), .breakpoint => return, .cond_br => return self.airCondBr(inst), @@ -741,12 +741,13 @@ pub const DeclGen = struct { .ret => return self.airRet(inst), .store => return self.airStore(inst), .unreach => return self.airUnreach(), - .assembly => (try self.airAssembly(inst)) orelse return, - .call => (try self.airCall(inst, .auto)) orelse return, - .call_always_tail => (try self.airCall(inst, .always_tail)) orelse return, - .call_never_tail => (try self.airCall(inst, .never_tail)) orelse return, - .call_never_inline => (try self.airCall(inst, .never_inline)) orelse return, + .assembly => try self.airAssembly(inst), + + .call => try self.airCall(inst, .auto), + .call_always_tail => try self.airCall(inst, .always_tail), + .call_never_tail => try self.airCall(inst, .never_tail), + .call_never_inline => try self.airCall(inst, .never_inline), .dbg_var_ptr => return, .dbg_var_val => return, @@ -757,10 +758,12 @@ pub const DeclGen = struct { else => |tag| return self.todo("implement AIR tag {s}", .{@tagName(tag)}), }; + const result_id = maybe_result_id orelse return; try self.inst_results.putNoClobber(self.gpa, inst, result_id); } - fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !IdRef { + fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef { + if (self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs_id = try self.resolve(bin_op.lhs); const rhs_id = try self.resolve(bin_op.rhs); @@ -781,7 +784,8 @@ pub const DeclGen = struct { comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode, - ) !IdRef { + ) !?IdRef { + if (self.liveness.isUnused(inst)) return null; // LHS and RHS are guaranteed to have the same type, and AIR guarantees // the result to be the same as the LHS and RHS, which matches SPIR-V. const ty = self.air.typeOfIndex(inst); @@ -833,7 +837,8 @@ pub const DeclGen = struct { return result_id.toRef(); } - fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !IdRef { + fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; const ty = self.air.typeOfIndex(inst); const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const extra = self.air.extraData(Air.Shuffle, ty_pl.payload).data; @@ -868,7 +873,8 @@ pub const DeclGen = struct { return result_id.toRef(); } - fn airCmp(self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode) !IdRef { + fn airCmp(self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode) !?IdRef { + if (self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs_id = try self.resolve(bin_op.lhs); const rhs_id = try self.resolve(bin_op.rhs); @@ -913,7 +919,22 @@ pub const DeclGen = struct { return result_id.toRef(); } - fn airNot(self: *DeclGen, inst: Air.Inst.Index) !IdRef { + fn airBitcast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand_id = try self.resolve(ty_op.operand); + const result_id = self.spv.allocId(); + const result_type_id = try self.resolveTypeId(Type.initTag(.bool)); + try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ + .id_result_type = result_type_id, + .id_result = result_id, + .operand = operand_id, + }); + return result_id.toRef(); + } + + fn airNot(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; const ty_op = self.air.instructions.items(.data)[inst].ty_op; const operand_id = try self.resolve(ty_op.operand); const result_id = self.spv.allocId(); @@ -926,7 +947,8 @@ pub const DeclGen = struct { return result_id.toRef(); } - fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !IdRef { + fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; const ty = self.air.typeOfIndex(inst); const result_type_id = try self.resolveTypeId(ty); const result_id = self.spv.allocId(); @@ -981,7 +1003,7 @@ pub const DeclGen = struct { try self.beginSpvBlock(label_id); // If this block didn't produce a value, simply return here. - if (!ty.hasRuntimeBits()) + if (!ty.hasRuntimeBitsIgnoreComptime()) return null; // Combine the result from the blocks using the Phi instruction. @@ -1002,19 +1024,6 @@ pub const DeclGen = struct { return result_id.toRef(); } - fn airBitcast(self: *DeclGen, inst: Air.Inst.Index) !IdRef { - const ty_op = self.air.instructions.items(.data)[inst].ty_op; - const operand_id = try self.resolve(ty_op.operand); - const result_id = self.spv.allocId(); - const result_type_id = try self.resolveTypeId(Type.initTag(.bool)); - try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ - .id_result_type = result_type_id, - .id_result = result_id, - .operand = operand_id, - }); - return result_id.toRef(); - } - fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void { const br = self.air.instructions.items(.data)[inst].br; const block = self.blocks.get(br.block_inst).?; @@ -1104,6 +1113,7 @@ pub const DeclGen = struct { } fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void { + if (self.liveness.isUnused(inst)) return; const operand = self.air.instructions.items(.data)[inst].un_op; const operand_ty = self.air.typeOf(operand); if (operand_ty.hasRuntimeBits()) { @@ -1299,6 +1309,10 @@ pub const DeclGen = struct { try self.func.body.emit(self.spv.gpa, .OpUnreachable, {}); } + if (self.liveness.isUnused(inst) or !return_type.hasRuntimeBitsIgnoreComptime()) { + return null; + } + return result_id.toRef(); } }; From c23d668c79e25ba6a83ec01d9e58f136a5afe7cf Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 26 Nov 2022 18:55:25 +0100 Subject: [PATCH 14/51] spirv: slice operations This commit adds support for SPIR-V code generation for the following AIR instructions: - slice_ptr - slice_len - slice_elem_ptr - slice_elem_val --- src/codegen/spirv.zig | 97 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 82f156fc99..9ef44a18ba 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -717,8 +717,12 @@ pub const DeclGen = struct { .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd), .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr), - .bitcast => try self.airBitcast(inst), - .not => try self.airNot(inst), + .bitcast => try self.airBitcast(inst), + .not => try self.airNot(inst), + .slice_ptr => try self.airSliceField(inst, 0), + .slice_len => try self.airSliceField(inst, 1), + .slice_elem_ptr => try self.airSliceElemPtr(inst), + .slice_elem_val => try self.airSliceElemVal(inst), .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual), .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual), @@ -924,7 +928,7 @@ pub const DeclGen = struct { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const operand_id = try self.resolve(ty_op.operand); const result_id = self.spv.allocId(); - const result_type_id = try self.resolveTypeId(Type.initTag(.bool)); + const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst)); try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ .id_result_type = result_type_id, .id_result = result_id, @@ -947,6 +951,93 @@ pub const DeclGen = struct { return result_id.toRef(); } + fn airSliceField(self: *DeclGen, inst: Air.Inst.Index, field: u32) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ + .id_result_type = try self.resolveTypeId(self.air.typeOfIndex(inst)), + .id_result = result_id, + .composite = try self.resolve(ty_op.operand), + .indexes = &.{field}, + }); + return result_id.toRef(); + } + + fn airSliceElemPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const slice_ty = self.air.typeOf(bin_op.lhs); + if (!slice_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; + + const slice = try self.resolve(bin_op.lhs); + const index = try self.resolve(bin_op.rhs); + + const spv_ptr_ty = try self.resolveTypeId(self.air.typeOfIndex(inst)); + + const slice_ptr = blk: { + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ + .id_result_type = spv_ptr_ty, + .id_result = result_id, + .composite = slice, + .indexes = &.{0}, + }); + break :blk result_id.toRef(); + }; + + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ + .id_result_type = spv_ptr_ty, + .id_result = result_id, + .base = slice_ptr, + .indexes = &.{index}, + }); + return result_id.toRef(); + } + + fn airSliceElemVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const slice_ty = self.air.typeOf(bin_op.lhs); + if (!slice_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; + + const slice = try self.resolve(bin_op.lhs); + const index = try self.resolve(bin_op.rhs); + + const spv_elem_ty = try self.resolveTypeId(self.air.typeOfIndex(inst)); + var slice_buf: Type.SlicePtrFieldTypeBuffer = undefined; + const spv_ptr_ty = try self.resolveTypeId(slice_ty.slicePtrFieldType(&slice_buf)); + + const slice_ptr = blk: { + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ + .id_result_type = spv_ptr_ty, + .id_result = result_id, + .composite = slice, + .indexes = &.{0}, + }); + break :blk result_id.toRef(); + }; + + const elem_ptr = blk: { + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ + .id_result_type = spv_ptr_ty, + .id_result = result_id, + .base = slice_ptr, + .indexes = &.{index}, + }); + break :blk result_id.toRef(); + }; + + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpLoad, .{ + .id_result_type = spv_elem_ty, + .id_result = result_id, + .pointer = elem_ptr, + }); + return result_id.toRef(); + } + fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; const ty = self.air.typeOfIndex(inst); From 6146abee1ef19f1c5380d0afba6f38895ce81e0b Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 26 Nov 2022 23:42:04 +0100 Subject: [PATCH 15/51] spirv: add_with_overflow Implements lowering for the add_with_overflow AIR instructions. Also implements a helper function, simpleStructType, to quickly generate a SPIR-V structure type without having to do the whole allocation dance. --- src/codegen/spirv.zig | 176 +++++++++++++++++++++++++++++-------- src/codegen/spirv/type.zig | 6 +- 2 files changed, 144 insertions(+), 38 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 9ef44a18ba..f2209efe2d 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -459,6 +459,22 @@ pub const DeclGen = struct { return try self.intType(.unsigned, self.getTarget().cpu.arch.ptrBitWidth()); } + /// Construct a simple struct type which consists of some members, and no decorations. + /// `members` lifetime only needs to last for this function as it is copied. + fn simpleStructType(self: *DeclGen, members: []const SpvType.Payload.Struct.Member) !SpvType.Ref { + const payload = try self.spv.arena.create(SpvType.Payload.Struct); + payload.* = .{ + .members = try self.spv.arena.dupe(SpvType.Payload.Struct.Member, members), + .decorations = .{}, + }; + return try self.spv.resolveType(SpvType.initPayload(&payload.base)); + } + + fn simpleStructTypeId(self: *DeclGen, members: []const SpvType.Payload.Struct.Member) !IdResultType { + const type_ref = try self.simpleStructType(members); + return self.spv.typeResultId(type_ref); + } + /// Turn a Zig type into a SPIR-V Type, and return a reference to it. fn resolveType(self: *DeclGen, ty: Type) Error!SpvType.Ref { const target = self.getTarget(); @@ -555,25 +571,10 @@ pub const DeclGen = struct { const len_align = len_ty.abiAlignment(target); const len_offset = std.mem.alignForwardGeneric(u64, ptr_size, len_align); - const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, 2); - members[0] = .{ - .ty = spv_ptr_ty, - .offset = 0, - .decorations = .{}, - }; - members[1] = .{ - .ty = try self.sizeType(), - .offset = @intCast(u32, len_offset), - .decorations = .{}, - }; - - const slice_payload = try self.spv.arena.create(SpvType.Payload.Struct); - slice_payload.* = .{ - .members = members, - .decorations = .{}, - .member_decoration_extra = &.{}, - }; - return try self.spv.resolveType(SpvType.initPayload(&slice_payload.base)); + return try self.simpleStructType(&.{ + .{ .ty = spv_ptr_ty, .offset = 0 }, + .{ .ty = try self.sizeType(), .offset = @intCast(u32, len_offset) }, + }); }, .Vector => { // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations @@ -594,7 +595,24 @@ pub const DeclGen = struct { }, .Struct => { if (ty.isSimpleTupleOrAnonStruct()) { - return self.todo("implement tuple struct type", .{}); + const tuple = ty.tupleFields(); + const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, tuple.types.len); + var member_index: usize = 0; + for (tuple.types) |field_ty, i| { + const field_val = tuple.values[i]; + if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue; + + members[member_index] = .{ + .ty = try self.resolveType(field_ty), + .offset = 0, + }; + } + + 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; @@ -611,15 +629,12 @@ pub const DeclGen = struct { members[member_index] = .{ .ty = try self.resolveType(field.ty), .offset = field.offset, - .decorations = .{}, }; } const payload = try self.spv.arena.create(SpvType.Payload.Struct); payload.* = .{ .members = members[0..member_index], - .decorations = .{}, - .member_decoration_extra = &.{}, }; return try self.spv.resolveType(SpvType.initPayload(&payload.base)); }, @@ -709,6 +724,8 @@ pub const DeclGen = struct { .sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub), .mul, .mulwrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul), + .add_with_overflow => try self.airOverflowArithOp(inst), + .shuffle => try self.airShuffle(inst), .bit_and => try self.airBinOpSimple(inst, .OpBitwiseAnd), @@ -719,6 +736,7 @@ pub const DeclGen = struct { .bitcast => try self.airBitcast(inst), .not => try self.airNot(inst), + .slice_ptr => try self.airSliceField(inst, 0), .slice_len => try self.airSliceField(inst, 1), .slice_elem_ptr => try self.airSliceElemPtr(inst), @@ -841,6 +859,82 @@ pub const DeclGen = struct { return result_id.toRef(); } + fn airOverflowArithOp(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + + const target = self.getTarget(); + + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; + const lhs = try self.resolve(extra.lhs); + const rhs = try self.resolve(extra.rhs); + + const operand_ty = self.air.typeOf(extra.lhs); + const result_ty = self.air.typeOfIndex(inst); + + const operand_ty_id = try self.resolveTypeId(operand_ty); + const result_type_id = try self.resolveTypeId(result_ty); + + const operand_bits = operand_ty.intInfo(target).bits; + const overflow_member_ty = try self.intType(.unsigned, operand_bits); + const overflow_member_ty_id = self.spv.typeResultId(overflow_member_ty); + + const op_result_id = blk: { + // Construct the SPIR-V result type. + // It is almost the same as the zig one, except that the fields must be the same type + // and they must be unsigned. + const overflow_result_ty = try self.simpleStructTypeId(&.{ + .{ .ty = overflow_member_ty, .offset = 0 }, + .{ .ty = overflow_member_ty, .offset = @intCast(u32, operand_ty.abiSize(target)) }, + }); + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{ + .id_result_type = overflow_result_ty, + .id_result = result_id, + .operand_1 = lhs, + .operand_2 = rhs, + }); + break :blk result_id.toRef(); + }; + + // Now convert the SPIR-V flavor result into a Zig-flavor result. + // First, extract the two fields. + const unsigned_result = try self.extractField(overflow_member_ty_id, op_result_id, 0); + const overflow = try self.extractField(overflow_member_ty_id, op_result_id, 0); + + // We need to convert the results to the types that Zig expects here. + // The `result` is the same type except unsigned, so we can just bitcast that. + const result = try self.bitcast(operand_ty_id, unsigned_result); + + // The overflow needs to be converted into whatever is used to represent it in Zig. + const casted_overflow = blk: { + const ov_ty = result_ty.tupleFields().types[1]; + const ov_ty_id = try self.resolveTypeId(ov_ty); + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ + .id_result_type = ov_ty_id, + .id_result = result_id, + .unsigned_value = overflow, + }); + break :blk result_id.toRef(); + }; + + // TODO: If copying this function for borrow, make sure to convert -1 to 1 as appropriate. + + // Finally, construct the Zig type. + // Layout is result, overflow. + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{ + .id_result_type = result_type_id, + .id_result = result_id, + .constituents = &.{ + result, + casted_overflow, + }, + }); + return result_id.toRef(); + } + fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; const ty = self.air.typeOfIndex(inst); @@ -923,18 +1017,22 @@ pub const DeclGen = struct { return result_id.toRef(); } + fn bitcast(self: *DeclGen, target_type_id: IdResultType, value_id: IdRef) !IdRef { + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ + .id_result_type = target_type_id, + .id_result = result_id, + .operand = value_id, + }); + return result_id.toRef(); + } + fn airBitcast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; const ty_op = self.air.instructions.items(.data)[inst].ty_op; const operand_id = try self.resolve(ty_op.operand); - const result_id = self.spv.allocId(); const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst)); - try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ - .id_result_type = result_type_id, - .id_result = result_id, - .operand = operand_id, - }); - return result_id.toRef(); + return try self.bitcast(result_type_id, operand_id); } fn airNot(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -951,19 +1049,27 @@ pub const DeclGen = struct { return result_id.toRef(); } - fn airSliceField(self: *DeclGen, inst: Air.Inst.Index, field: u32) !?IdRef { - if (self.liveness.isUnused(inst)) return null; - const ty_op = self.air.instructions.items(.data)[inst].ty_op; + fn extractField(self: *DeclGen, result_ty: IdResultType, object: IdRef, field: u32) !IdRef { const result_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ - .id_result_type = try self.resolveTypeId(self.air.typeOfIndex(inst)), + .id_result_type = result_ty, .id_result = result_id, - .composite = try self.resolve(ty_op.operand), + .composite = object, .indexes = &.{field}, }); return result_id.toRef(); } + fn airSliceField(self: *DeclGen, inst: Air.Inst.Index, field: u32) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + return try self.extractField( + try self.resolveTypeId(self.air.typeOfIndex(inst)), + try self.resolve(ty_op.operand), + field, + ); + } + fn airSliceElemPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const slice_ty = self.air.typeOf(bin_op.lhs); diff --git a/src/codegen/spirv/type.zig b/src/codegen/spirv/type.zig index 2d9d6befe1..648a9c3a78 100644 --- a/src/codegen/spirv/type.zig +++ b/src/codegen/spirv/type.zig @@ -436,17 +436,17 @@ pub const Type = extern union { base: Payload = .{ .tag = .@"struct" }, // TODO: name members: []Member, - decorations: StructDecorations, + 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, + member_decoration_extra: []u32 = &.{}, pub const Member = struct { ty: Ref, offset: u32, // TODO: name - decorations: MemberDecorations, + decorations: MemberDecorations = .{}, }; pub const StructDecorations = packed struct { From bca6f2901af5e2ff30f1ff1780eb23b5e05e2f86 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 27 Nov 2022 01:17:38 +0100 Subject: [PATCH 16/51] spirv: enum values, struct_field_val, ret_ptr, ret_load Implements lowering for enum constants, as well as the struct_field_val, ret_ptr, and ret_load AIR instructions. --- src/codegen/spirv.zig | 96 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index f2209efe2d..f691c4ce41 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -429,9 +429,33 @@ pub const DeclGen = struct { }, else => unreachable, // TODO }, + .Enum => { + var int_buffer: Value.Payload.U64 = undefined; + const int_val = val.enumToInt(ty, &int_buffer).toUnsignedInt(target); + + var buffer: Type.Payload.Bits = undefined; + const int_ty = ty.intTagType(&buffer); + const int_info = int_ty.intInfo(target); + + const backing_bits = self.backingIntBits(int_info.bits) orelse { + return self.todo("implement composite int constants for {}", .{int_ty.fmtDebug()}); + }; + + const value: spec.LiteralContextDependentNumber = switch (backing_bits) { + 1...32 => .{ .uint32 = @truncate(u32, int_val) }, + 33...64 => .{ .uint64 = int_val }, + else => unreachable, + }; + + try section.emit(self.spv.gpa, .OpConstant, .{ + .id_result_type = result_type_id, + .id_result = result_id, + .value = value, + }); + }, .Void => unreachable, .Fn => unreachable, - else => return self.todo("constant generation of type {}", .{ty.fmtDebug()}), + else => return self.todo("constant generation of type {s}: {}", .{ @tagName(ty.zigTypeTag()), ty.fmtDebug() }), } return result_id.toRef(); @@ -742,6 +766,8 @@ pub const DeclGen = struct { .slice_elem_ptr => try self.airSliceElemPtr(inst), .slice_elem_val => try self.airSliceElemVal(inst), + .struct_field_val => try self.airStructFieldVal(inst), + .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual), .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual), .cmp_gt => try self.airCmp(inst, .OpFOrdGreaterThan, .OpSGreaterThan, .OpUGreaterThan), @@ -749,10 +775,12 @@ pub const DeclGen = struct { .cmp_lt => try self.airCmp(inst, .OpFOrdLessThan, .OpSLessThan, .OpULessThan), .cmp_lte => try self.airCmp(inst, .OpFOrdLessThanEqual, .OpSLessThanEqual, .OpULessThanEqual), - .arg => self.airArg(), - .alloc => try self.airAlloc(inst), - .block => try self.airBlock(inst), - .load => try self.airLoad(inst), + .arg => self.airArg(), + .alloc => try self.airAlloc(inst), + // TODO: We probably need to have a special implementation of this for the C abi. + .ret_ptr => try self.airAlloc(inst), + .block => try self.airBlock(inst), + .load => try self.airLoad(inst), .br => return self.airBr(inst), .breakpoint => return, @@ -761,6 +789,7 @@ pub const DeclGen = struct { .dbg_stmt => return self.airDbgStmt(inst), .loop => return self.airLoop(inst), .ret => return self.airRet(inst), + .ret_load => return self.airRetLoad(inst), .store => return self.airStore(inst), .unreach => return self.airUnreach(), @@ -989,12 +1018,12 @@ pub const DeclGen = struct { .composite_integer => { return self.todo("binary operations for composite integers", .{}); }, - .strange_integer => { - return self.todo("comparison for strange integers", .{}); - }, .float => 0, .bool => 1, - .integer => switch (info.signedness) { + // TODO: Should strange integers be masked before comparison? + .strange_integer, + .integer, + => switch (info.signedness) { .signed => @as(usize, 1), .unsigned => @as(usize, 2), }, @@ -1144,6 +1173,32 @@ pub const DeclGen = struct { return result_id.toRef(); } + fn airStructFieldVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data; + + const struct_ty = self.air.typeOf(struct_field.struct_operand); + const object = try self.resolve(struct_field.struct_operand); + const field_index = struct_field.field_index; + const field_ty = struct_ty.structFieldType(field_index); + const field_ty_id = try self.resolveTypeId(field_ty); + + if (!field_ty.hasRuntimeBitsIgnoreComptime()) return null; + + assert(struct_ty.zigTypeTag() == .Struct); // Cannot do unions yet. + + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ + .id_result_type = field_ty_id, + .id_result = result_id, + .composite = object, + .indexes = &.{field_index}, + }); + return result_id.toRef(); + } + fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; const ty = self.air.typeOfIndex(inst); @@ -1153,7 +1208,7 @@ pub const DeclGen = struct { // Rather than generating into code here, we're just going to generate directly into the functions section so that // variable declarations appear in the first block of the function. const storage_class = spirvStorageClass(ty.ptrAddressSpace()); - const section = if (storage_class == .Function) + const section = if (storage_class == .Function or storage_class == .Generic) &self.func.prologue else &self.spv.sections.types_globals_constants; @@ -1321,6 +1376,27 @@ pub const DeclGen = struct { } } + fn airRetLoad(self: *DeclGen, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const ptr_ty = self.air.typeOf(un_op); + const ret_ty = ptr_ty.childType(); + const ret_ty_id = try self.resolveTypeId(ret_ty); + + if (!ret_ty.hasRuntimeBitsIgnoreComptime()) { + try self.func.body.emit(self.spv.gpa, .OpReturn, {}); + return; + } + + const ptr = try self.resolve(un_op); + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpLoad, .{ + .id_result_type = ret_ty_id, + .id_result = result_id, + .pointer = ptr, + }); + try self.func.body.emit(self.spv.gpa, .OpReturnValue, .{ .value = result_id.toRef() }); + } + fn airStore(self: *DeclGen, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const dst_ptr_id = try self.resolve(bin_op.lhs); From 23e210c38f9c35e26ac43f6f585c6d8a4751e318 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 27 Nov 2022 12:51:05 +0100 Subject: [PATCH 17/51] spirv: (some) array and struct constants Starts implementing constant lowering for some array and struct constants. In particular, TODO are packed structs. --- src/codegen/spirv.zig | 98 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 89 insertions(+), 9 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index f691c4ce41..5c55e68eb9 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -410,6 +410,44 @@ pub const DeclGen = struct { .value = value, }); }, + .Array => switch (val.tag()) { + .aggregate => { // todo: combine with Vector + const elem_vals = val.castTag(.aggregate).?.data; + const elem_ty = ty.elemType(); + const len = @intCast(u32, ty.arrayLenIncludingSentinel()); // TODO: limit spir-v to 32 bit arrays in a more elegant way. + const constituents = try self.spv.gpa.alloc(IdRef, len); + defer self.spv.gpa.free(constituents); + for (elem_vals[0..len]) |elem_val, i| { + constituents[i] = try self.genConstant(elem_ty, elem_val); + } + try section.emit(self.spv.gpa, .OpConstantComposite, .{ + .id_result_type = result_type_id, + .id_result = result_id, + .constituents = constituents, + }); + }, + .repeated => { + const elem_val = val.castTag(.repeated).?.data; + const elem_ty = ty.elemType(); + const len = @intCast(u32, ty.arrayLenIncludingSentinel()); // TODO: limit spir-v to 32 bit arrays in a more elegant way. + const constituents = try self.spv.gpa.alloc(IdRef, len); + defer self.spv.gpa.free(constituents); + + const elem_val_id = try self.genConstant(elem_ty, elem_val); + for (constituents[0..len]) |*elem| { + elem.* = elem_val_id; + } + if (ty.sentinel()) |sentinel| { + constituents[len] = try self.genConstant(elem_ty, sentinel); + } + try section.emit(self.spv.gpa, .OpConstantComposite, .{ + .id_result_type = result_type_id, + .id_result = result_id, + .constituents = constituents, + }); + }, + else => return self.todo("array constant with tag {s}", .{@tagName(val.tag())}), + }, .Vector => switch (val.tag()) { .aggregate => { const elem_vals = val.castTag(.aggregate).?.data; @@ -427,20 +465,20 @@ pub const DeclGen = struct { .constituents = elem_refs, }); }, - else => unreachable, // TODO + else => return self.todo("vector constant with tag {s}", .{@tagName(val.tag())}), }, .Enum => { - var int_buffer: Value.Payload.U64 = undefined; - const int_val = val.enumToInt(ty, &int_buffer).toUnsignedInt(target); - - var buffer: Type.Payload.Bits = undefined; - const int_ty = ty.intTagType(&buffer); + var ty_buffer: Type.Payload.Bits = undefined; + const int_ty = ty.intTagType(&ty_buffer); const int_info = int_ty.intInfo(target); const backing_bits = self.backingIntBits(int_info.bits) orelse { return self.todo("implement composite int constants for {}", .{int_ty.fmtDebug()}); }; + var int_buffer: Value.Payload.U64 = undefined; + const int_val = val.enumToInt(ty, &int_buffer).toUnsignedInt(target); // TODO: composite integer constants + const value: spec.LiteralContextDependentNumber = switch (backing_bits) { 1...32 => .{ .uint32 = @truncate(u32, int_val) }, 33...64 => .{ .uint64 = int_val }, @@ -453,6 +491,48 @@ pub const DeclGen = struct { .value = value, }); }, + .Struct => { + const constituents = if (ty.isSimpleTupleOrAnonStruct()) blk: { + const tuple = ty.tupleFields(); + const constituents = try self.spv.gpa.alloc(IdRef, tuple.types.len); + errdefer self.spv.gpa.free(constituents); + + var member_index: usize = 0; + for (tuple.types) |field_ty, i| { + const field_val = tuple.values[i]; + if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue; + constituents[member_index] = try self.genConstant(field_ty, field_val); + member_index += 1; + } + + break :blk constituents[0..member_index]; + } else blk: { + const struct_ty = ty.castTag(.@"struct").?.data; + + if (struct_ty.layout == .Packed) { + return self.todo("packed struct constants", .{}); + } + + const field_vals = val.castTag(.aggregate).?.data; + const constituents = try self.spv.gpa.alloc(IdRef, struct_ty.fields.count()); + errdefer self.spv.gpa.free(constituents); + var member_index: usize = 0; + for (struct_ty.fields.values()) |field, i| { + if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; + constituents[member_index] = try self.genConstant(field.ty, field_vals[i]); + member_index += 1; + } + + break :blk constituents[0..member_index]; + }; + defer self.spv.gpa.free(constituents); + + try section.emit(self.spv.gpa, .OpConstantComposite, .{ + .id_result_type = result_type_id, + .id_result = result_id, + .constituents = constituents, + }); + }, .Void => unreachable, .Fn => unreachable, else => return self.todo("constant generation of type {s}: {}", .{ @tagName(ty.zigTypeTag()), ty.fmtDebug() }), @@ -539,9 +619,8 @@ pub const DeclGen = struct { }, .Array => { const elem_ty = ty.childType(); - const total_len_u64 = ty.arrayLen() + @boolToInt(ty.sentinel() != null); - const total_len = std.math.cast(u32, total_len_u64) orelse { - return self.fail("array type of {} elements is too large", .{total_len_u64}); + const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse { + return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()}); }; const payload = try self.spv.arena.create(SpvType.Payload.Array); @@ -630,6 +709,7 @@ pub const DeclGen = struct { .ty = try self.resolveType(field_ty), .offset = 0, }; + member_index += 1; } const payload = try self.spv.arena.create(SpvType.Payload.Struct); From 8608d6e23573a6d7d4d1ce92bf461b035970c592 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 27 Nov 2022 14:15:08 +0100 Subject: [PATCH 18/51] spirv: div, rem, intcast, some strange integer masking Implements the div-family and intcast AIR instructions, and starts implementing a mechanism for masking the value of 'strange' integers before they are used in an operation that does not hold under modulo. --- src/codegen/spirv.zig | 120 +++++++++++++++++++++++++++++++++++------- 1 file changed, 102 insertions(+), 18 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 5c55e68eb9..8fbac5bf02 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -824,9 +824,21 @@ pub const DeclGen = struct { const air_tags = self.air.instructions.items(.tag); const maybe_result_id: ?IdRef = switch (air_tags[inst]) { // zig fmt: off - .add, .addwrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd), - .sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub), - .mul, .mulwrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul), + .add, .addwrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd, true), + .sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub, true), + .mul, .mulwrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul, true), + + .div_float, + .div_float_optimized, + // TODO: Check that this is the right operation. + .div_trunc, + .div_trunc_optimized, + => try self.airArithOp(inst, .OpFDiv, .OpSDiv, .OpUDiv, false), + // TODO: Check if this is the right operation + // TODO: Make airArithOp for rem not emit a mask for the LHS. + .rem, + .rem_optimized, + => try self.airArithOp(inst, .OpFRem, .OpSRem, .OpSRem, false), .add_with_overflow => try self.airOverflowArithOp(inst), @@ -838,8 +850,9 @@ pub const DeclGen = struct { .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd), .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr), - .bitcast => try self.airBitcast(inst), - .not => try self.airNot(inst), + .bitcast => try self.airBitcast(inst), + .intcast => try self.airIntcast(inst), + .not => try self.airNot(inst), .slice_ptr => try self.airSliceField(inst, 0), .slice_len => try self.airSliceField(inst, 1), @@ -909,20 +922,47 @@ pub const DeclGen = struct { return result_id.toRef(); } + fn maskStrangeInt(self: *DeclGen, ty_id: IdResultType, int_id: IdRef, bits: u16) !IdRef { + const backing_bits = self.backingIntBits(bits).?; + const mask_value = if (bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @intCast(u6, bits)) - 1; + const mask_lit: spec.LiteralContextDependentNumber = switch (backing_bits) { + 1...32 => .{ .uint32 = @truncate(u32, mask_value) }, + 33...64 => .{ .uint64 = mask_value }, + else => unreachable, + }; + // TODO: We should probably optimize these constants a bit. + const mask_id = self.spv.allocId(); + try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstant, .{ + .id_result_type = ty_id, + .id_result = mask_id, + .value = mask_lit, + }); + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{ + .id_result_type = ty_id, + .id_result = result_id, + .operand_1 = int_id, + .operand_2 = mask_id.toRef(), + }); + return result_id.toRef(); + } + fn airArithOp( self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode, + /// true if this operation holds under modular arithmetic. + comptime modular: bool, ) !?IdRef { if (self.liveness.isUnused(inst)) return null; // LHS and RHS are guaranteed to have the same type, and AIR guarantees // the result to be the same as the LHS and RHS, which matches SPIR-V. const ty = self.air.typeOfIndex(inst); const bin_op = self.air.instructions.items(.data)[inst].bin_op; - const lhs_id = try self.resolve(bin_op.lhs); - const rhs_id = try self.resolve(bin_op.rhs); + var lhs_id = try self.resolve(bin_op.lhs); + var rhs_id = try self.resolve(bin_op.rhs); const result_id = self.spv.allocId(); const result_type_id = try self.resolveTypeId(ty); @@ -938,15 +978,22 @@ pub const DeclGen = struct { .composite_integer => { return self.todo("binary operations for composite integers", .{}); }, - .strange_integer => { - return self.todo("binary operations for strange integers", .{}); + .strange_integer => blk: { + if (!modular) { + lhs_id = try self.maskStrangeInt(result_type_id, lhs_id, info.bits); + rhs_id = try self.maskStrangeInt(result_type_id, rhs_id, info.bits); + } + break :blk switch (info.signedness) { + .signed => @as(usize, 1), + .unsigned => @as(usize, 2), + }; }, .integer => switch (info.signedness) { .signed => @as(usize, 1), .unsigned => @as(usize, 2), }, .float => 0, - else => unreachable, + .bool => unreachable, }; const operands = .{ @@ -981,11 +1028,18 @@ pub const DeclGen = struct { const operand_ty = self.air.typeOf(extra.lhs); const result_ty = self.air.typeOfIndex(inst); + const info = try self.arithmeticTypeInfo(operand_ty); + switch (info.class) { + .composite_integer => return self.todo("overflow ops for composite integers", .{}), + .strange_integer => return self.todo("overflow ops for strange integers", .{}), + .integer => {}, + .float, .bool => unreachable, + } + const operand_ty_id = try self.resolveTypeId(operand_ty); const result_type_id = try self.resolveTypeId(result_ty); - const operand_bits = operand_ty.intInfo(target).bits; - const overflow_member_ty = try self.intType(.unsigned, operand_bits); + const overflow_member_ty = try self.intType(.unsigned, info.bits); const overflow_member_ty_id = self.spv.typeResultId(overflow_member_ty); const op_result_id = blk: { @@ -1083,8 +1137,8 @@ pub const DeclGen = struct { fn airCmp(self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode) !?IdRef { if (self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; - const lhs_id = try self.resolve(bin_op.lhs); - const rhs_id = try self.resolve(bin_op.rhs); + var lhs_id = try self.resolve(bin_op.lhs); + var rhs_id = try self.resolve(bin_op.rhs); const result_id = self.spv.allocId(); const result_type_id = try self.resolveTypeId(Type.initTag(.bool)); const op_ty = self.air.typeOf(bin_op.lhs); @@ -1100,10 +1154,15 @@ pub const DeclGen = struct { }, .float => 0, .bool => 1, - // TODO: Should strange integers be masked before comparison? - .strange_integer, - .integer, - => switch (info.signedness) { + .strange_integer => blk: { + lhs_id = try self.maskStrangeInt(result_type_id, lhs_id, info.bits); + rhs_id = try self.maskStrangeInt(result_type_id, rhs_id, info.bits); + break :blk switch (info.signedness) { + .signed => @as(usize, 1), + .unsigned => @as(usize, 2), + }; + }, + .integer => switch (info.signedness) { .signed => @as(usize, 1), .unsigned => @as(usize, 2), }, @@ -1144,6 +1203,31 @@ pub const DeclGen = struct { return try self.bitcast(result_type_id, operand_id); } + fn airIntcast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand_id = try self.resolve(ty_op.operand); + const dest_ty = self.air.typeOfIndex(inst); + const dest_info = try self.arithmeticTypeInfo(dest_ty); + const dest_ty_id = try self.resolveTypeId(dest_ty); + + const result_id = self.spv.allocId(); + switch (dest_info.signedness) { + .signed => try self.func.body.emit(self.spv.gpa, .OpSConvert, .{ + .id_result_type = dest_ty_id, + .id_result = result_id, + .signed_value = operand_id, + }), + .unsigned => try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ + .id_result_type = dest_ty_id, + .id_result = result_id, + .unsigned_value = operand_id, + }), + } + return result_id.toRef(); + } + fn airNot(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; const ty_op = self.air.instructions.items(.data)[inst].ty_op; From ea97966c9e76727e7f7e4c0a2c4578f64b608001 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 27 Nov 2022 14:50:20 +0100 Subject: [PATCH 19/51] spirv: struct field ptr index, ptr elem ptr Implements the ptr_elem_ptr and struct_field_ptr_index_* AIR instructions for the SPIR-V backend. --- src/codegen/spirv.zig | 74 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 8fbac5bf02..47848d70c5 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -858,9 +858,15 @@ pub const DeclGen = struct { .slice_len => try self.airSliceField(inst, 1), .slice_elem_ptr => try self.airSliceElemPtr(inst), .slice_elem_val => try self.airSliceElemVal(inst), + .ptr_elem_ptr => try self.airPtrElemPtr(inst), .struct_field_val => try self.airStructFieldVal(inst), + .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0), + .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1), + .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2), + .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3), + .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual), .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual), .cmp_gt => try self.airCmp(inst, .OpFOrdGreaterThan, .OpSGreaterThan, .OpUGreaterThan), @@ -1337,6 +1343,31 @@ pub const DeclGen = struct { return result_id.toRef(); } + fn airPtrElemPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; + const ptr_ty = self.air.typeOf(bin_op.lhs); + const result_ty = self.air.typeOfIndex(inst); + const elem_ty = ptr_ty.childType(); + // TODO: Make this return a null ptr or something + if (!elem_ty.hasRuntimeBitsIgnoreComptime()) return null; + + const result_type_id = try self.resolveTypeId(result_ty); + const base_ptr = try self.resolve(bin_op.lhs); + const rhs = try self.resolve(bin_op.rhs); + + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ + .id_result_type = result_type_id, + .id_result = result_id, + .base = base_ptr, + .indexes = &.{rhs}, + }); + return result_id.toRef(); + } + fn airStructFieldVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; @@ -1363,6 +1394,49 @@ pub const DeclGen = struct { return result_id.toRef(); } + fn structFieldPtr( + self: *DeclGen, + result_ptr_ty: Type, + object_ptr_ty: Type, + object_ptr: IdRef, + field_index: u32, + ) !?IdRef { + const object_ty = object_ptr_ty.childType(); + switch (object_ty.zigTypeTag()) { + .Struct => switch (object_ty.containerLayout()) { + .Packed => unreachable, // TODO + else => { + const field_index_id = self.spv.allocId(); + const u32_ty_id = self.spv.typeResultId(try self.intType(.unsigned, 32)); + try self.func.body.emit(self.spv.gpa, .OpConstant, .{ + .id_result_type = u32_ty_id, + .id_result = field_index_id, + .value = .{ .uint32 = field_index }, + }); + const result_id = self.spv.allocId(); + const result_type_id = try self.resolveTypeId(result_ptr_ty); + try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ + .id_result_type = result_type_id, + .id_result = result_id, + .base = object_ptr, + .indexes = &.{field_index_id.toRef()}, + }); + return result_id.toRef(); + }, + }, + else => unreachable, // TODO + } + } + + fn airStructFieldPtrIndex(self: *DeclGen, inst: Air.Inst.Index, field_index: u32) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const struct_ptr = try self.resolve(ty_op.operand); + const struct_ptr_ty = self.air.typeOf(ty_op.operand); + const result_ptr_ty = self.air.typeOfIndex(inst); + return try self.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index); + } + fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; const ty = self.air.typeOfIndex(inst); From 205d928b24d46fd1fd5798c5d66e66ef25aa013f Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 27 Nov 2022 15:04:16 +0100 Subject: [PATCH 20/51] spirv: left shift Implements the AIR left_shift operation for the SPIR-V backend. --- src/codegen/spirv.zig | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 47848d70c5..bfd2c2da30 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -850,6 +850,8 @@ pub const DeclGen = struct { .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd), .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr), + .shl => try self.airShift(inst, .OpShiftLeftLogical), + .bitcast => try self.airBitcast(inst), .intcast => try self.airIntcast(inst), .not => try self.airNot(inst), @@ -928,6 +930,31 @@ pub const DeclGen = struct { return result_id.toRef(); } + fn airShift(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef { + if (self.liveness.isUnused(inst)) return null; + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs_id = try self.resolve(bin_op.lhs); + const rhs_id = try self.resolve(bin_op.rhs); + const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst)); + + // the shift and the base must be the same type in SPIR-V, but in Zig the shift is a smaller int. + const shift_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ + .id_result_type = result_type_id, + .id_result = shift_id, + .unsigned_value = rhs_id, + }); + + const result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, opcode, .{ + .id_result_type = result_type_id, + .id_result = result_id, + .base = lhs_id, + .shift = shift_id.toRef(), + }); + return result_id.toRef(); + } + fn maskStrangeInt(self: *DeclGen, ty_id: IdResultType, int_id: IdRef, bits: u16) !IdRef { const backing_bits = self.backingIntBits(bits).?; const mask_value = if (bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @intCast(u6, bits)) - 1; From e443b1bed7ccce45bf039a304fa8fa271f1faa0b Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 27 Nov 2022 16:22:01 +0100 Subject: [PATCH 21/51] spirv: switch_br lowering Implements lowering switch statements in the SPIR-V backend. --- src/codegen/spirv.zig | 117 +++++++++++++++++++++++++++++++++++ src/codegen/spirv/Module.zig | 5 ++ 2 files changed, 122 insertions(+) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index bfd2c2da30..b6ccbec0b7 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -887,11 +887,13 @@ pub const DeclGen = struct { .breakpoint => return, .cond_br => return self.airCondBr(inst), .constant => unreachable, + .const_ty => unreachable, .dbg_stmt => return self.airDbgStmt(inst), .loop => return self.airLoop(inst), .ret => return self.airRet(inst), .ret_load => return self.airRetLoad(inst), .store => return self.airStore(inst), + .switch_br => return self.airSwitchBr(inst), .unreach => return self.airUnreach(), .assembly => try self.airAssembly(inst), @@ -1679,6 +1681,121 @@ pub const DeclGen = struct { }); } + fn airSwitchBr(self: *DeclGen, inst: Air.Inst.Index) !void { + const target = self.getTarget(); + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const cond = try self.resolve(pl_op.operand); + const cond_ty = self.air.typeOf(pl_op.operand); + const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload); + + const cond_words: u32 = switch (cond_ty.zigTypeTag()) { + .Int => blk: { + const bits = cond_ty.intInfo(target).bits; + const backing_bits = self.backingIntBits(bits) orelse { + return self.todo("implement composite int switch", .{}); + }; + break :blk if (backing_bits <= 32) 1 else 2; + }, + .Enum => blk: { + var buffer: Type.Payload.Bits = undefined; + const int_ty = cond_ty.intTagType(&buffer); + const int_info = int_ty.intInfo(target); + const backing_bits = self.backingIntBits(int_info.bits) orelse { + return self.todo("implement composite int switch", .{}); + }; + break :blk if (backing_bits <= 32) 1 else 2; + }, + else => return self.todo("implement switch for type {s}", .{@tagName(cond_ty.zigTypeTag())}), // TODO: Figure out which types apply here, and work around them as we can only do integers. + }; + + const num_cases = switch_br.data.cases_len; + + // Compute the total number of arms that we need. + // Zig switches are grouped by condition, so we need to loop through all of them + const num_conditions = blk: { + var extra_index: usize = switch_br.end; + var case_i: u32 = 0; + var num_conditions: u32 = 0; + while (case_i < num_cases) : (case_i += 1) { + const case = self.air.extraData(Air.SwitchBr.Case, extra_index); + const case_body = self.air.extra[case.end + case.data.items_len ..][0..case.data.body_len]; + extra_index = case.end + case.data.items_len + case_body.len; + num_conditions += case.data.items_len; + } + break :blk num_conditions; + }; + + // First, pre-allocate the labels for the cases. + const first_case_label = self.spv.allocIds(num_cases); + // We always need the default case - if zig has none, we will generate unreachable there. + const default = self.spv.allocId(); + + // Emit the instruction before generating the blocks. + try self.func.body.emitRaw(self.spv.gpa, .OpSwitch, 2 + (cond_words + 1) * num_conditions); + self.func.body.writeOperand(IdRef, cond); + self.func.body.writeOperand(IdRef, default.toRef()); + + // Emit each of the cases + { + var extra_index: usize = switch_br.end; + var case_i: u32 = 0; + while (case_i < num_cases) : (case_i += 1) { + // SPIR-V needs a literal here, which' width depends on the case condition. + const case = self.air.extraData(Air.SwitchBr.Case, extra_index); + const items = @ptrCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]); + const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len]; + extra_index = case.end + case.data.items_len + case_body.len; + + const label = IdRef{ .id = first_case_label.id + case_i }; + + for (items) |item| { + const value = self.air.value(item) orelse { + return self.todo("switch on runtime value???", .{}); + }; + const int_val = switch (cond_ty.zigTypeTag()) { + .Int => if (cond_ty.isSignedInt()) @bitCast(u64, value.toSignedInt()) else value.toUnsignedInt(target), + .Enum => blk: { + var int_buffer: Value.Payload.U64 = undefined; + // TODO: figure out of cond_ty is correct (something with enum literals) + break :blk value.enumToInt(cond_ty, &int_buffer).toUnsignedInt(target); // TODO: composite integer constants + }, + else => unreachable, + }; + const int_lit: spec.LiteralContextDependentNumber = switch (cond_words) { + 1 => .{ .uint32 = @intCast(u32, int_val) }, + 2 => .{ .uint64 = int_val }, + else => unreachable, + }; + self.func.body.writeOperand(spec.LiteralContextDependentNumber, int_lit); + self.func.body.writeOperand(IdRef, label); + } + } + } + + // Now, finally, we can start emitting each of the cases. + var extra_index: usize = switch_br.end; + var case_i: u32 = 0; + while (case_i < num_cases) : (case_i += 1) { + const case = self.air.extraData(Air.SwitchBr.Case, extra_index); + const items = @ptrCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]); + const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len]; + extra_index = case.end + case.data.items_len + case_body.len; + + const label = IdResult{ .id = first_case_label.id + case_i }; + + try self.beginSpvBlock(label); + try self.genBody(case_body); + } + + const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len]; + try self.beginSpvBlock(default); + if (else_body.len != 0) { + try self.genBody(else_body); + } else { + try self.func.body.emit(self.spv.gpa, .OpUnreachable, {}); + } + } + fn airUnreach(self: *DeclGen) !void { try self.func.body.emit(self.spv.gpa, .OpUnreachable, {}); } diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index f6c4cd735e..ab9d0588ca 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -132,6 +132,11 @@ pub fn allocId(self: *Module) spec.IdResult { return .{ .id = self.next_result_id }; } +pub fn allocIds(self: *Module, n: u32) spec.IdResult { + defer self.next_result_id += n; + return .{ .id = self.next_result_id }; +} + pub fn idBound(self: Module) Word { return self.next_result_id; } From 0c2526b18ec21d8155b070022d3b3b9069303744 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 28 Nov 2022 18:12:03 +0100 Subject: [PATCH 22/51] spirv: some fixes and improvements - Adds the Int8. Int16, Int64 and GenericPointer capabilities. TODO: This should integrate with the feature system. - Default some struct fields of SPIR-V types so that we dont need to type them all the time. - Store struct field name's in SPIR-V types, and generate the OpMemberName decoration if they are non-null. - Also add the field names to the actual SPIR-V types. - Generate OpName for functions. --- src/codegen/spirv.zig | 67 +++++++++++++++++------------------- src/codegen/spirv/Module.zig | 27 +++++++++++++++ src/codegen/spirv/type.zig | 14 ++++---- src/link/SpirV.zig | 2 +- 4 files changed, 68 insertions(+), 42 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index b6ccbec0b7..2f1440fca7 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -585,9 +585,10 @@ pub const DeclGen = struct { switch (ty.zigTypeTag()) { .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)), .Bool => { - // TODO: SPIR-V booleans are opaque. For local variables this is fine, but for structs - // members we want to use integer types instead. - 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. + return try self.intType(.unsigned, 1); }, .Int => { const int_info = ty.intInfo(target); @@ -627,7 +628,6 @@ pub const DeclGen = struct { payload.* = .{ .element_type = try self.resolveType(elem_ty), .length = total_len, - .array_stride = @intCast(u32, ty.abiSize(target)), }; return try self.spv.resolveType(SpvType.initPayload(&payload.base)); }, @@ -654,29 +654,18 @@ pub const DeclGen = struct { ptr_payload.* = .{ .storage_class = spirvStorageClass(ptr_info.@"addrspace"), .child_type = try self.resolveType(ptr_info.pointee_type), - // TODO: ??? - .array_stride = 0, // Note: only available in Kernels! .alignment = ty.ptrAlignment(target) * 8, - .max_byte_offset = null, }; - const spv_ptr_ty = try self.spv.resolveType(SpvType.initPayload(&ptr_payload.base)); + const ptr_ty_id = try self.spv.resolveType(SpvType.initPayload(&ptr_payload.base)); if (ptr_info.size != .Slice) { - return spv_ptr_ty; + return ptr_ty_id; } - var buf: Type.SlicePtrFieldTypeBuffer = undefined; - const ptr_ty = ty.slicePtrFieldType(&buf); - const len_ty = Type.usize; - - const ptr_size = ptr_ty.abiSize(target); - const len_align = len_ty.abiAlignment(target); - const len_offset = std.mem.alignForwardGeneric(u64, ptr_size, len_align); - return try self.simpleStructType(&.{ - .{ .ty = spv_ptr_ty, .offset = 0 }, - .{ .ty = try self.sizeType(), .offset = @intCast(u32, len_offset) }, + .{ .ty = ptr_ty_id, .name = "ptr" }, + .{ .ty = try self.sizeType(), .name = "len" }, }); }, .Vector => { @@ -700,18 +689,15 @@ pub const DeclGen = 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: usize = 0; + var member_index: u32 = 0; for (tuple.types) |field_ty, i| { const field_val = tuple.values[i]; - if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue; - + if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBitsIgnoreComptime()) continue; members[member_index] = .{ .ty = try self.resolveType(field_ty), - .offset = 0, }; member_index += 1; } - const payload = try self.spv.arena.create(SpvType.Payload.Struct); payload.* = .{ .members = members[0..member_index], @@ -727,18 +713,23 @@ pub const DeclGen = struct { 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()) |field| { + for (struct_ty.fields.values()) |field, i| { if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; members[member_index] = .{ .ty = try self.resolveType(field.ty), - .offset = field.offset, + .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)); }, @@ -808,6 +799,14 @@ pub const DeclGen = struct { // Append the actual code into the functions section. try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {}); try self.spv.addFunction(self.func); + + const fqn = try decl.getFullyQualifiedName(self.module); + defer self.module.gpa.free(fqn); + + try self.spv.sections.debug_names.emit(self.gpa, .OpName, .{ + .target = result_id.toRef(), + .name = fqn, + }); } else { // TODO // return self.todo("generate decl type {}", .{decl.ty.zigTypeTag()}); @@ -1053,8 +1052,6 @@ pub const DeclGen = struct { fn airOverflowArithOp(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; - const target = self.getTarget(); - const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; const lhs = try self.resolve(extra.lhs); @@ -1082,8 +1079,8 @@ pub const DeclGen = struct { // It is almost the same as the zig one, except that the fields must be the same type // and they must be unsigned. const overflow_result_ty = try self.simpleStructTypeId(&.{ - .{ .ty = overflow_member_ty, .offset = 0 }, - .{ .ty = overflow_member_ty, .offset = @intCast(u32, operand_ty.abiSize(target)) }, + .{ .ty = overflow_member_ty, .name = "res" }, + .{ .ty = overflow_member_ty, .name = "ov" }, }); const result_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{ @@ -1098,7 +1095,7 @@ pub const DeclGen = struct { // Now convert the SPIR-V flavor result into a Zig-flavor result. // First, extract the two fields. const unsigned_result = try self.extractField(overflow_member_ty_id, op_result_id, 0); - const overflow = try self.extractField(overflow_member_ty_id, op_result_id, 0); + const overflow = try self.extractField(overflow_member_ty_id, op_result_id, 1); // We need to convert the results to the types that Zig expects here. // The `result` is the same type except unsigned, so we can just bitcast that. @@ -1190,8 +1187,9 @@ pub const DeclGen = struct { .float => 0, .bool => 1, .strange_integer => blk: { - lhs_id = try self.maskStrangeInt(result_type_id, lhs_id, info.bits); - rhs_id = try self.maskStrangeInt(result_type_id, rhs_id, info.bits); + const op_ty_id = try self.resolveTypeId(op_ty); + lhs_id = try self.maskStrangeInt(op_ty_id, lhs_id, info.bits); + rhs_id = try self.maskStrangeInt(op_ty_id, rhs_id, info.bits); break :blk switch (info.signedness) { .signed => @as(usize, 1), .unsigned => @as(usize, 2), @@ -1437,7 +1435,7 @@ pub const DeclGen = struct { else => { const field_index_id = self.spv.allocId(); const u32_ty_id = self.spv.typeResultId(try self.intType(.unsigned, 32)); - try self.func.body.emit(self.spv.gpa, .OpConstant, .{ + try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstant, .{ .id_result_type = u32_ty_id, .id_result = field_index_id, .value = .{ .uint32 = field_index }, @@ -1632,7 +1630,6 @@ pub const DeclGen = struct { } fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void { - if (self.liveness.isUnused(inst)) return; const operand = self.air.instructions.items(.data)[inst].un_op; const operand_ty = self.air.typeOf(operand); if (operand_ty.hasRuntimeBits()) { diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index ab9d0588ca..b8dfd3fb96 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -437,8 +437,16 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { } fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void { + const debug_names = &self.sections.debug_names; const annotations = &self.sections.annotations; + 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 annotations.decorate(self.gpa, target, .Block); @@ -457,6 +465,25 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct 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 annotations.decorateMember( + self.gpa, + target, + index, + .{ .Offset = .{ .byte_offset = @enumToInt(member.offset) } }, + ), + } + switch (d.matrix_layout) { .row_major => try annotations.decorateMember(self.gpa, target, index, .RowMajor), .col_major => try annotations.decorateMember(self.gpa, target, index, .ColMajor), diff --git a/src/codegen/spirv/type.zig b/src/codegen/spirv/type.zig index 648a9c3a78..118563c6fa 100644 --- a/src/codegen/spirv/type.zig +++ b/src/codegen/spirv/type.zig @@ -429,13 +429,13 @@ pub const Type = extern union { element_type: Ref, /// Type has the 'ArrayStride' decoration. /// If zero, no stride is present. - array_stride: u32, + array_stride: u32 = 0, }; pub const Struct = struct { base: Payload = .{ .tag = .@"struct" }, - // TODO: name members: []Member, + name: []const u8 = "", decorations: StructDecorations = .{}, /// Extra information for decorations, packed for efficiency. Fields are stored sequentially by @@ -444,11 +444,13 @@ pub const Type = extern union { pub const Member = struct { ty: Ref, - offset: u32, - // TODO: name + 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, @@ -544,11 +546,11 @@ pub const Type = extern union { /// Type has the 'ArrayStride' decoration. /// This is valid for pointers to elements of an array. /// If zero, no stride is present. - array_stride: u32, + array_stride: u32 = 0, /// Type has the 'Alignment' decoration. alignment: ?u32, /// Type has the 'MaxByteOffset' decoration. - max_byte_offset: ?u32, + max_byte_offset: ?u32 = null, }; pub const Function = struct { diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 2d5ca723b2..561775a757 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -244,7 +244,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No fn writeCapabilities(spv: *SpvModule, target: std.Target) !void { // TODO: Integrate with a hypothetical feature system const caps: []const spec.Capability = switch (target.os.tag) { - .opencl => &.{ .Kernel, .Addresses }, + .opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .GenericPointer }, .glsl450 => &.{.Shader}, .vulkan => &.{.Shader}, else => unreachable, // TODO From 700dee34d5c7bdedc678032689c761e3a417fa03 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 28 Nov 2022 19:14:16 +0100 Subject: [PATCH 23/51] spirv: make IdResultType and IdRef weak aliases of IdResult Previously they were strong aliases, but as these types are used quite intermittendly it resulted in a lot of toRef() calls. Removing them improves readability a bit. --- src/codegen/spirv.zig | 86 ++++++++++++++++----------------- src/codegen/spirv/Assembler.zig | 4 +- src/codegen/spirv/Module.zig | 40 +++++++-------- src/codegen/spirv/Section.zig | 8 +-- src/codegen/spirv/spec.zig | 15 +----- tools/gen_spirv_spec.zig | 15 +----- 6 files changed, 73 insertions(+), 95 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 2f1440fca7..37ebfe1f4f 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -227,7 +227,7 @@ pub const DeclGen = struct { /// keep track of the previous block. fn beginSpvBlock(self: *DeclGen, label_id: IdResult) !void { try self.func.body.emit(self.spv.gpa, .OpLabel, .{ .id_result = label_id }); - self.current_block_label_id = label_id.toRef(); + self.current_block_label_id = label_id; } /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need @@ -340,7 +340,7 @@ pub const DeclGen = struct { }; const decl = self.module.declPtr(fn_decl_index); self.module.markDeclAlive(decl); - return self.ids.get(fn_decl_index).?.toRef(); + return self.ids.get(fn_decl_index).?; } const target = self.getTarget(); @@ -350,7 +350,7 @@ pub const DeclGen = struct { if (val.isUndef()) { try section.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = result_type_id, .id_result = result_id }); - return result_id.toRef(); + return result_id; } switch (ty.zigTypeTag()) { @@ -538,7 +538,7 @@ pub const DeclGen = struct { else => return self.todo("constant generation of type {s}: {}", .{ @tagName(ty.zigTypeTag()), ty.fmtDebug() }), } - return result_id.toRef(); + return result_id; } /// Turn a Zig type into a SPIR-V Type, and return its type result-id. @@ -766,7 +766,7 @@ pub const DeclGen = struct { .id_result_type = try self.resolveTypeId(decl.ty.fnReturnType()), .id_result = result_id, .function_control = .{}, // TODO: We can set inline here if the type requires it. - .function_type = prototype_id.toRef(), + .function_type = prototype_id, }); const params = decl.ty.fnParamLen(); @@ -780,7 +780,7 @@ pub const DeclGen = struct { .id_result_type = param_type_id, .id_result = arg_result_id, }); - self.args.appendAssumeCapacity(arg_result_id.toRef()); + self.args.appendAssumeCapacity(arg_result_id); } // TODO: This could probably be done in a better way... @@ -791,7 +791,7 @@ pub const DeclGen = struct { try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{ .id_result = root_block_id, }); - self.current_block_label_id = root_block_id.toRef(); + self.current_block_label_id = root_block_id; const main_body = self.air.getMainBody(); try self.genBody(main_body); @@ -804,7 +804,7 @@ pub const DeclGen = struct { defer self.module.gpa.free(fqn); try self.spv.sections.debug_names.emit(self.gpa, .OpName, .{ - .target = result_id.toRef(), + .target = result_id, .name = fqn, }); } else { @@ -928,7 +928,7 @@ pub const DeclGen = struct { .operand_1 = lhs_id, .operand_2 = rhs_id, }); - return result_id.toRef(); + return result_id; } fn airShift(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef { @@ -951,9 +951,9 @@ pub const DeclGen = struct { .id_result_type = result_type_id, .id_result = result_id, .base = lhs_id, - .shift = shift_id.toRef(), + .shift = shift_id, }); - return result_id.toRef(); + return result_id; } fn maskStrangeInt(self: *DeclGen, ty_id: IdResultType, int_id: IdRef, bits: u16) !IdRef { @@ -976,9 +976,9 @@ pub const DeclGen = struct { .id_result_type = ty_id, .id_result = result_id, .operand_1 = int_id, - .operand_2 = mask_id.toRef(), + .operand_2 = mask_id, }); - return result_id.toRef(); + return result_id; } fn airArithOp( @@ -1046,7 +1046,7 @@ pub const DeclGen = struct { // TODO: Trap on overflow? Probably going to be annoying. // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap. - return result_id.toRef(); + return result_id; } fn airOverflowArithOp(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -1089,7 +1089,7 @@ pub const DeclGen = struct { .operand_1 = lhs, .operand_2 = rhs, }); - break :blk result_id.toRef(); + break :blk result_id; }; // Now convert the SPIR-V flavor result into a Zig-flavor result. @@ -1111,7 +1111,7 @@ pub const DeclGen = struct { .id_result = result_id, .unsigned_value = overflow, }); - break :blk result_id.toRef(); + break :blk result_id; }; // TODO: If copying this function for borrow, make sure to convert -1 to 1 as appropriate. @@ -1127,7 +1127,7 @@ pub const DeclGen = struct { casted_overflow, }, }); - return result_id.toRef(); + return result_id; } fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -1163,7 +1163,7 @@ pub const DeclGen = struct { self.func.body.writeOperand(spec.LiteralInteger, unsigned); } } - return result_id.toRef(); + return result_id; } fn airCmp(self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode) !?IdRef { @@ -1215,7 +1215,7 @@ pub const DeclGen = struct { else => unreachable, } - return result_id.toRef(); + return result_id; } fn bitcast(self: *DeclGen, target_type_id: IdResultType, value_id: IdRef) !IdRef { @@ -1225,7 +1225,7 @@ pub const DeclGen = struct { .id_result = result_id, .operand = value_id, }); - return result_id.toRef(); + return result_id; } fn airBitcast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -1258,7 +1258,7 @@ pub const DeclGen = struct { .unsigned_value = operand_id, }), } - return result_id.toRef(); + return result_id; } fn airNot(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -1272,7 +1272,7 @@ pub const DeclGen = struct { .id_result = result_id, .operand = operand_id, }); - return result_id.toRef(); + return result_id; } fn extractField(self: *DeclGen, result_ty: IdResultType, object: IdRef, field: u32) !IdRef { @@ -1283,7 +1283,7 @@ pub const DeclGen = struct { .composite = object, .indexes = &.{field}, }); - return result_id.toRef(); + return result_id; } fn airSliceField(self: *DeclGen, inst: Air.Inst.Index, field: u32) !?IdRef { @@ -1314,7 +1314,7 @@ pub const DeclGen = struct { .composite = slice, .indexes = &.{0}, }); - break :blk result_id.toRef(); + break :blk result_id; }; const result_id = self.spv.allocId(); @@ -1324,7 +1324,7 @@ pub const DeclGen = struct { .base = slice_ptr, .indexes = &.{index}, }); - return result_id.toRef(); + return result_id; } fn airSliceElemVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -1347,7 +1347,7 @@ pub const DeclGen = struct { .composite = slice, .indexes = &.{0}, }); - break :blk result_id.toRef(); + break :blk result_id; }; const elem_ptr = blk: { @@ -1358,7 +1358,7 @@ pub const DeclGen = struct { .base = slice_ptr, .indexes = &.{index}, }); - break :blk result_id.toRef(); + break :blk result_id; }; const result_id = self.spv.allocId(); @@ -1367,7 +1367,7 @@ pub const DeclGen = struct { .id_result = result_id, .pointer = elem_ptr, }); - return result_id.toRef(); + return result_id; } fn airPtrElemPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -1392,7 +1392,7 @@ pub const DeclGen = struct { .base = base_ptr, .indexes = &.{rhs}, }); - return result_id.toRef(); + return result_id; } fn airStructFieldVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -1418,7 +1418,7 @@ pub const DeclGen = struct { .composite = object, .indexes = &.{field_index}, }); - return result_id.toRef(); + return result_id; } fn structFieldPtr( @@ -1446,9 +1446,9 @@ pub const DeclGen = struct { .id_result_type = result_type_id, .id_result = result_id, .base = object_ptr, - .indexes = &.{field_index_id.toRef()}, + .indexes = &.{field_index_id}, }); - return result_id.toRef(); + return result_id; }, }, else => unreachable, // TODO @@ -1483,7 +1483,7 @@ pub const DeclGen = struct { .id_result = result_id, .storage_class = storage_class, }); - return result_id.toRef(); + return result_id; } fn airArg(self: *DeclGen) IdRef { @@ -1503,7 +1503,7 @@ pub const DeclGen = struct { var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.gpa, 4); try self.blocks.putNoClobber(self.gpa, inst, .{ - .label_id = label_id.toRef(), + .label_id = label_id, .incoming_blocks = &incoming_blocks, }); defer { @@ -1538,7 +1538,7 @@ pub const DeclGen = struct { self.func.body.writeOperand(spec.PairIdRefIdRef, .{ incoming.break_value_id, incoming.src_label_id }); } - return result_id.toRef(); + return result_id; } fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void { @@ -1571,8 +1571,8 @@ pub const DeclGen = struct { try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{ .condition = condition_id, - .true_label = then_label_id.toRef(), - .false_label = else_label_id.toRef(), + .true_label = then_label_id, + .false_label = else_label_id, }); try self.beginSpvBlock(then_label_id); @@ -1610,7 +1610,7 @@ pub const DeclGen = struct { .memory_access = access, }); - return result_id.toRef(); + return result_id; } fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void { @@ -1620,13 +1620,13 @@ pub const DeclGen = struct { const loop_label_id = self.spv.allocId(); // Jump to the loop entry point - try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id.toRef() }); + try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id }); // TODO: Look into OpLoopMerge. try self.beginSpvBlock(loop_label_id); try self.genBody(body); - try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id.toRef() }); + try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id }); } fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void { @@ -1658,7 +1658,7 @@ pub const DeclGen = struct { .id_result = result_id, .pointer = ptr, }); - try self.func.body.emit(self.spv.gpa, .OpReturnValue, .{ .value = result_id.toRef() }); + try self.func.body.emit(self.spv.gpa, .OpReturnValue, .{ .value = result_id }); } fn airStore(self: *DeclGen, inst: Air.Inst.Index) !void { @@ -1730,7 +1730,7 @@ pub const DeclGen = struct { // Emit the instruction before generating the blocks. try self.func.body.emitRaw(self.spv.gpa, .OpSwitch, 2 + (cond_words + 1) * num_conditions); self.func.body.writeOperand(IdRef, cond); - self.func.body.writeOperand(IdRef, default.toRef()); + self.func.body.writeOperand(IdRef, default); // Emit each of the cases { @@ -1965,6 +1965,6 @@ pub const DeclGen = struct { return null; } - return result_id.toRef(); + return result_id; } }; diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig index c4432102a0..9b63ef8191 100644 --- a/src/codegen/spirv/Assembler.zig +++ b/src/codegen/spirv/Assembler.zig @@ -135,7 +135,7 @@ const AsmValue = union(enum) { return switch (self) { .just_declared, .unresolved_forward_reference => unreachable, .value => |result| result, - .ty => |ref| spv.typeResultId(ref).toRef(), + .ty => |ref| spv.typeResultId(ref), }; } }; @@ -486,7 +486,7 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue { section.instructions.items[first_word] |= @as(u32, @intCast(u16, actual_word_count)) << 16 | @enumToInt(self.inst.opcode); if (maybe_result_id) |result| { - return AsmValue{ .value = result.toRef() }; + return AsmValue{ .value = result }; } return null; } diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index b8dfd3fb96..23d188deb3 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -196,7 +196,7 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef { const result = try self.source_file_names.getOrPut(self.gpa, path); if (!result.found_existing) { const file_result_id = self.allocId(); - result.value_ptr.* = file_result_id.toRef(); + result.value_ptr.* = file_result_id; try self.sections.debug_strings.emit(self.gpa, .OpString, .{ .id_result = file_result_id, .string = path, @@ -205,7 +205,7 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef { try self.sections.debug_strings.emit(self.gpa, .OpSource, .{ .source_language = .Unknown, // TODO: Register Zig source language. .version = 0, // TODO: Zig version as u32? - .file = file_result_id.toRef(), + .file = file_result_id, .source = null, // TODO: Store actual source also? }); } @@ -239,7 +239,7 @@ pub fn typeResultId(self: Module, type_ref: Type.Ref) IdResultType { /// Get the result-id of a particular type as IdRef, by Type.Ref. Asserts type_ref is valid. pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef { - return self.type_cache.values()[@enumToInt(type_ref)].toRef(); + return self.type_cache.values()[@enumToInt(type_ref)]; } /// Unconditionally emit a spir-v type into the appropriate section. @@ -250,7 +250,7 @@ pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef { /// 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.toRef(); + const ref_id = result_id; const types = &self.sections.types_globals_constants; const debug_names = &self.sections.debug_names; const annotations = &self.sections.annotations; @@ -260,14 +260,14 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { .void => { try types.emit(self.gpa, .OpTypeVoid, result_id_operand); try debug_names.emit(self.gpa, .OpName, .{ - .target = result_id.toRef(), + .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.toRef(), + .target = result_id, .name = "bool", }); }, @@ -302,7 +302,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { defer self.gpa.free(name); try debug_names.emit(self.gpa, .OpName, .{ - .target = result_id.toRef(), + .target = result_id, .name = name, }); }, @@ -316,25 +316,25 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { 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.toRef(), + .target = result_id, .name = name, }); }, .vector => try types.emit(self.gpa, .OpTypeVector, .{ .id_result = result_id, - .component_type = self.typeResultId(ty.childType()).toRef(), + .component_type = self.typeResultId(ty.childType()), .component_count = ty.payload(.vector).component_count, }), .matrix => try types.emit(self.gpa, .OpTypeMatrix, .{ .id_result = result_id, - .column_type = self.typeResultId(ty.childType()).toRef(), + .column_type = self.typeResultId(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.typeResultId(ty.childType()).toRef(), + .sampled_type = self.typeResultId(ty.childType()), .dim = info.dim, .depth = @enumToInt(info.depth), .arrayed = @boolToInt(info.arrayed), @@ -347,7 +347,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { .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.typeResultId(ty.childType()).toRef(), + .image_type = self.typeResultId(ty.childType()), }), .array => { const info = ty.payload(.array); @@ -365,8 +365,8 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { try types.emit(self.gpa, .OpTypeArray, .{ .id_result = result_id, - .element_type = self.typeResultId(ty.childType()).toRef(), - .length = length_id.toRef(), + .element_type = self.typeResultId(ty.childType()), + .length = length_id, }); if (info.array_stride != 0) { try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); @@ -376,7 +376,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { const info = ty.payload(.runtime_array); try types.emit(self.gpa, .OpTypeRuntimeArray, .{ .id_result = result_id, - .element_type = self.typeResultId(ty.childType()).toRef(), + .element_type = self.typeResultId(ty.childType()), }); if (info.array_stride != 0) { try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); @@ -387,7 +387,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len); types.writeOperand(IdResult, result_id); for (info.members) |member| { - types.writeOperand(IdRef, self.typeResultId(member.ty).toRef()); + types.writeOperand(IdRef, self.typeResultId(member.ty)); } try self.decorateStruct(ref_id, info); }, @@ -400,7 +400,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { try types.emit(self.gpa, .OpTypePointer, .{ .id_result = result_id, .storage_class = info.storage_class, - .type = self.typeResultId(ty.childType()).toRef(), + .type = self.typeResultId(ty.childType()), }); if (info.array_stride != 0) { try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); @@ -416,9 +416,9 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { const info = ty.payload(.function); try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len); types.writeOperand(IdResult, result_id); - types.writeOperand(IdRef, self.typeResultId(info.return_type).toRef()); + types.writeOperand(IdRef, self.typeResultId(info.return_type)); for (info.parameters) |parameter_type| { - types.writeOperand(IdRef, self.typeResultId(parameter_type).toRef()); + types.writeOperand(IdRef, self.typeResultId(parameter_type)); } }, .event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand), @@ -433,7 +433,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { .named_barrier => try types.emit(self.gpa, .OpTypeNamedBarrier, result_id_operand), } - return result_id.toResultType(); + return result_id; } fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void { diff --git a/src/codegen/spirv/Section.zig b/src/codegen/spirv/Section.zig index a76314f5fa..2fd8309261 100644 --- a/src/codegen/spirv/Section.zig +++ b/src/codegen/spirv/Section.zig @@ -122,7 +122,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void { switch (Operand) { - spec.IdResultType, spec.IdResult, spec.IdRef => section.writeWord(operand.id), + spec.IdResult => section.writeWord(operand.id), spec.LiteralInteger => section.writeWord(operand), @@ -258,9 +258,7 @@ fn operandsSize(comptime Operands: type, operands: Operands) usize { fn operandSize(comptime Operand: type, operand: Operand) usize { return switch (Operand) { - spec.IdResultType, spec.IdResult, - spec.IdRef, spec.LiteralInteger, spec.LiteralExtInstInteger, => 1, @@ -382,7 +380,9 @@ test "SPIR-V Section emit() - string" { }, section.instructions.items); } -test "SPIR-V Section emit()- extended mask" { +test "SPIR-V Section emit() - extended mask" { + if (@import("builtin").zig_backend == .stage1) return error.SkipZigTest; + var section = Section{}; defer section.deinit(std.testing.allocator); diff --git a/src/codegen/spirv/spec.zig b/src/codegen/spirv/spec.zig index 3978231829..60d16461cb 100644 --- a/src/codegen/spirv/spec.zig +++ b/src/codegen/spirv/spec.zig @@ -3,22 +3,11 @@ const Version = @import("std").builtin.Version; pub const Word = u32; -pub const IdResultType = struct { - id: Word, - pub fn toRef(self: IdResultType) IdRef { - return .{ .id = self.id }; - } -}; pub const IdResult = struct { id: Word, - pub fn toRef(self: IdResult) IdRef { - return .{ .id = self.id }; - } - pub fn toResultType(self: IdResult) IdResultType { - return .{ .id = self.id }; - } }; -pub const IdRef = struct { id: Word }; +pub const IdResultType = IdResult; +pub const IdRef = IdResult; pub const IdMemorySemantics = IdRef; pub const IdScope = IdRef; diff --git a/tools/gen_spirv_spec.zig b/tools/gen_spirv_spec.zig index 31dbbb1911..5ed76448e4 100644 --- a/tools/gen_spirv_spec.zig +++ b/tools/gen_spirv_spec.zig @@ -80,22 +80,11 @@ fn render(writer: anytype, allocator: Allocator, registry: g.CoreRegistry) !void \\const Version = @import("std").builtin.Version; \\ \\pub const Word = u32; - \\pub const IdResultType = struct{ - \\ id: Word, - \\ pub fn toRef(self: IdResultType) IdRef { - \\ return .{.id = self.id}; - \\ } - \\}; \\pub const IdResult = struct{ \\ id: Word, - \\ pub fn toRef(self: IdResult) IdRef { - \\ return .{.id = self.id}; - \\ } - \\ pub fn toResultType(self: IdResult) IdResultType { - \\ return .{.id = self.id}; - \\ } \\}; - \\pub const IdRef = struct{ id: Word }; + \\pub const IdResultType = IdResult; + \\pub const IdRef = IdResult; \\ \\pub const IdMemorySemantics = IdRef; \\pub const IdScope = IdRef; From 8a00ec162c76ef28cbbca59a7124d7d175a77e97 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 29 Nov 2022 23:44:31 +0100 Subject: [PATCH 24/51] spirv: more fixes and improvements - Formatting. - Improve `decorate` helper function to generate a decoration for a result-id. - Reorder some functions in a more logical way --- src/codegen/spirv.zig | 130 ++++++++++++++++------------------ src/codegen/spirv/Module.zig | 113 ++++++++++++++++++----------- src/codegen/spirv/Section.zig | 28 -------- 3 files changed, 132 insertions(+), 139 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 37ebfe1f4f..add4464f4d 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -880,7 +880,9 @@ pub const DeclGen = struct { // TODO: We probably need to have a special implementation of this for the C abi. .ret_ptr => try self.airAlloc(inst), .block => try self.airBlock(inst), + .load => try self.airLoad(inst), + .store => return self.airStore(inst), .br => return self.airBr(inst), .breakpoint => return, @@ -891,7 +893,6 @@ pub const DeclGen = struct { .loop => return self.airLoop(inst), .ret => return self.airRet(inst), .ret_load => return self.airRetLoad(inst), - .store => return self.airStore(inst), .switch_br => return self.airSwitchBr(inst), .unreach => return self.airUnreach(), @@ -964,13 +965,8 @@ pub const DeclGen = struct { 33...64 => .{ .uint64 = mask_value }, else => unreachable, }; - // TODO: We should probably optimize these constants a bit. - const mask_id = self.spv.allocId(); - try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstant, .{ - .id_result_type = ty_id, - .id_result = mask_id, - .value = mask_lit, - }); + // TODO: We should probably optimize the amount of these constants a bit. + const mask_id = try self.spv.emitConstant(ty_id, mask_lit); const result_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{ .id_result_type = ty_id, @@ -1119,13 +1115,11 @@ pub const DeclGen = struct { // Finally, construct the Zig type. // Layout is result, overflow. const result_id = self.spv.allocId(); + const constituents = [_]IdRef{ result, casted_overflow }; try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{ .id_result_type = result_type_id, .id_result = result_id, - .constituents = &.{ - result, - casted_overflow, - }, + .constituents = &constituents, }); return result_id; } @@ -1277,11 +1271,12 @@ pub const DeclGen = struct { fn extractField(self: *DeclGen, result_ty: IdResultType, object: IdRef, field: u32) !IdRef { const result_id = self.spv.allocId(); + const indexes = [_]u32{field}; try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ .id_result_type = result_ty, .id_result = result_id, .composite = object, - .indexes = &.{field}, + .indexes = &indexes, }); return result_id; } @@ -1318,11 +1313,12 @@ pub const DeclGen = struct { }; const result_id = self.spv.allocId(); + const indexes = [_]IdRef{index}; try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ .id_result_type = spv_ptr_ty, .id_result = result_id, .base = slice_ptr, - .indexes = &.{index}, + .indexes = &indexes, }); return result_id; } @@ -1335,14 +1331,13 @@ pub const DeclGen = struct { const slice = try self.resolve(bin_op.lhs); const index = try self.resolve(bin_op.rhs); - const spv_elem_ty = try self.resolveTypeId(self.air.typeOfIndex(inst)); var slice_buf: Type.SlicePtrFieldTypeBuffer = undefined; - const spv_ptr_ty = try self.resolveTypeId(slice_ty.slicePtrFieldType(&slice_buf)); + const ptr_ty_id = try self.resolveTypeId(slice_ty.slicePtrFieldType(&slice_buf)); const slice_ptr = blk: { const result_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ - .id_result_type = spv_ptr_ty, + .id_result_type = ptr_ty_id, .id_result = result_id, .composite = slice, .indexes = &.{0}, @@ -1352,22 +1347,17 @@ pub const DeclGen = struct { const elem_ptr = blk: { const result_id = self.spv.allocId(); + const indexes = [_]IdRef{index}; try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ - .id_result_type = spv_ptr_ty, + .id_result_type = ptr_ty_id, .id_result = result_id, .base = slice_ptr, - .indexes = &.{index}, + .indexes = &indexes, }); break :blk result_id; }; - const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpLoad, .{ - .id_result_type = spv_elem_ty, - .id_result = result_id, - .pointer = elem_ptr, - }); - return result_id; + return try self.load(slice_ty, elem_ptr); } fn airPtrElemPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { @@ -1386,11 +1376,12 @@ pub const DeclGen = struct { const rhs = try self.resolve(bin_op.rhs); const result_id = self.spv.allocId(); + const indexes = [_]IdRef{rhs}; try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ .id_result_type = result_type_id, .id_result = result_id, .base = base_ptr, - .indexes = &.{rhs}, + .indexes = &indexes, }); return result_id; } @@ -1412,11 +1403,12 @@ pub const DeclGen = struct { assert(struct_ty.zigTypeTag() == .Struct); // Cannot do unions yet. const result_id = self.spv.allocId(); + const indexes = [_]u32{field_index}; try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{ .id_result_type = field_ty_id, .id_result = result_id, .composite = object, - .indexes = &.{field_index}, + .indexes = &indexes, }); return result_id; } @@ -1433,20 +1425,16 @@ pub const DeclGen = struct { .Struct => switch (object_ty.containerLayout()) { .Packed => unreachable, // TODO else => { - const field_index_id = self.spv.allocId(); const u32_ty_id = self.spv.typeResultId(try self.intType(.unsigned, 32)); - try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstant, .{ - .id_result_type = u32_ty_id, - .id_result = field_index_id, - .value = .{ .uint32 = field_index }, - }); + const field_index_id = try self.spv.emitConstant(u32_ty_id, .{ .uint32 = field_index }); const result_id = self.spv.allocId(); const result_type_id = try self.resolveTypeId(result_ptr_ty); + const indexes = [_]IdRef{field_index_id}; try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ .id_result_type = result_type_id, .id_result = result_id, .base = object_ptr, - .indexes = &.{field_index_id}, + .indexes = &indexes, }); return result_id; }, @@ -1591,28 +1579,51 @@ pub const DeclGen = struct { }); } - fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !IdRef { + fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { const ty_op = self.air.instructions.items(.data)[inst].ty_op; - const operand_id = try self.resolve(ty_op.operand); - const ty = self.air.typeOfIndex(inst); + const ptr_ty = self.air.typeOf(ty_op.operand); + const operand = try self.resolve(ty_op.operand); + if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; - const result_type_id = try self.resolveTypeId(ty); + return try self.load(ptr_ty, operand); + } + + fn load(self: *DeclGen, ptr_ty: Type, ptr: IdRef) !IdRef { + const value_ty = ptr_ty.childType(); + const result_type_id = try self.resolveTypeId(value_ty); const result_id = self.spv.allocId(); - const access = spec.MemoryAccess.Extended{ - .Volatile = ty.isVolatilePtr(), + .Volatile = ptr_ty.isVolatilePtr(), }; - try self.func.body.emit(self.spv.gpa, .OpLoad, .{ .id_result_type = result_type_id, .id_result = result_id, - .pointer = operand_id, + .pointer = ptr, .memory_access = access, }); - return result_id; } + fn airStore(self: *DeclGen, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const ptr_ty = self.air.typeOf(bin_op.lhs); + const ptr = try self.resolve(bin_op.lhs); + const value = try self.resolve(bin_op.rhs); + + try self.store(ptr_ty, ptr, value); + } + + fn store(self: *DeclGen, ptr_ty: Type, ptr: IdRef, value: IdRef) !void { + const access = spec.MemoryAccess.Extended{ + .Volatile = ptr_ty.isVolatilePtr(), + }; + try self.func.body.emit(self.spv.gpa, .OpStore, .{ + .pointer = ptr, + .object = value, + .memory_access = access, + }); + } + fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void { const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const loop = self.air.extraData(Air.Block, ty_pl.payload); @@ -1644,7 +1655,6 @@ pub const DeclGen = struct { const un_op = self.air.instructions.items(.data)[inst].un_op; const ptr_ty = self.air.typeOf(un_op); const ret_ty = ptr_ty.childType(); - const ret_ty_id = try self.resolveTypeId(ret_ty); if (!ret_ty.hasRuntimeBitsIgnoreComptime()) { try self.func.body.emit(self.spv.gpa, .OpReturn, {}); @@ -1652,29 +1662,9 @@ pub const DeclGen = struct { } const ptr = try self.resolve(un_op); - const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpLoad, .{ - .id_result_type = ret_ty_id, - .id_result = result_id, - .pointer = ptr, - }); - try self.func.body.emit(self.spv.gpa, .OpReturnValue, .{ .value = result_id }); - } - - fn airStore(self: *DeclGen, inst: Air.Inst.Index) !void { - const bin_op = self.air.instructions.items(.data)[inst].bin_op; - const dst_ptr_id = try self.resolve(bin_op.lhs); - const src_val_id = try self.resolve(bin_op.rhs); - const lhs_ty = self.air.typeOf(bin_op.lhs); - - const access = spec.MemoryAccess.Extended{ - .Volatile = lhs_ty.isVolatilePtr(), - }; - - try self.func.body.emit(self.spv.gpa, .OpStore, .{ - .pointer = dst_ptr_id, - .object = src_val_id, - .memory_access = access, + const value = try self.load(ptr_ty, ptr); + try self.func.body.emit(self.spv.gpa, .OpReturnValue, .{ + .value = value, }); } @@ -1691,7 +1681,7 @@ pub const DeclGen = struct { const backing_bits = self.backingIntBits(bits) orelse { return self.todo("implement composite int switch", .{}); }; - break :blk if (backing_bits <= 32) 1 else 2; + break :blk if (backing_bits <= 32) @as(u32, 1) else 2; }, .Enum => blk: { var buffer: Type.Payload.Bits = undefined; @@ -1700,7 +1690,7 @@ pub const DeclGen = struct { const backing_bits = self.backingIntBits(int_info.bits) orelse { return self.todo("implement composite int switch", .{}); }; - break :blk if (backing_bits <= 32) 1 else 2; + break :blk if (backing_bits <= 32) @as(u32, 1) else 2; }, else => return self.todo("implement switch for type {s}", .{@tagName(cond_ty.zigTypeTag())}), // TODO: Figure out which types apply here, and work around them as we can only do integers. }; diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 23d188deb3..1a6ac95a0a 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -253,7 +253,6 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { const ref_id = result_id; const types = &self.sections.types_globals_constants; const debug_names = &self.sections.debug_names; - const annotations = &self.sections.annotations; const result_id_operand = .{ .id_result = result_id }; switch (ty.tag()) { @@ -355,13 +354,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { const size_type = Type.initTag(.u32); const size_type_id = try self.resolveTypeId(size_type); - - const length_id = self.allocId(); - try types.emit(self.gpa, .OpConstant, .{ - .id_result_type = size_type_id, - .id_result = length_id, - .value = .{ .uint32 = info.length }, - }); + const length_id = try self.emitConstant(size_type_id, .{ .uint32 = info.length }); try types.emit(self.gpa, .OpTypeArray, .{ .id_result = result_id, @@ -369,7 +362,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { .length = length_id, }); if (info.array_stride != 0) { - try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); + try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); } }, .runtime_array => { @@ -379,7 +372,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { .element_type = self.typeResultId(ty.childType()), }); if (info.array_stride != 0) { - try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); + try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); } }, .@"struct" => { @@ -403,13 +396,13 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { .type = self.typeResultId(ty.childType()), }); if (info.array_stride != 0) { - try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); + try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); } if (info.alignment) |alignment| { - try annotations.decorate(self.gpa, ref_id, .{ .Alignment = .{ .alignment = alignment } }); + try self.decorate(ref_id, .{ .Alignment = .{ .alignment = alignment } }); } if (info.max_byte_offset) |max_byte_offset| { - try annotations.decorate(self.gpa, ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } }); + try self.decorate(ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } }); } }, .function => { @@ -438,7 +431,6 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void { const debug_names = &self.sections.debug_names; - const annotations = &self.sections.annotations; if (info.name.len != 0) { try debug_names.emit(self.gpa, .OpName, .{ @@ -449,15 +441,15 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct // Decorations for the struct type itself. if (info.decorations.block) - try annotations.decorate(self.gpa, target, .Block); + try self.decorate(target, .Block); if (info.decorations.buffer_block) - try annotations.decorate(self.gpa, target, .BufferBlock); + try self.decorate(target, .BufferBlock); if (info.decorations.glsl_shared) - try annotations.decorate(self.gpa, target, .GLSLShared); + try self.decorate(target, .GLSLShared); if (info.decorations.glsl_packed) - try annotations.decorate(self.gpa, target, .GLSLPacked); + try self.decorate(target, .GLSLPacked); if (info.decorations.c_packed) - try annotations.decorate(self.gpa, target, .CPacked); + try self.decorate(target, .CPacked); // Decorations for the struct members. const extra = info.member_decoration_extra; @@ -476,8 +468,7 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct switch (member.offset) { .none => {}, - else => try annotations.decorateMember( - self.gpa, + else => try self.decorateMember( target, index, .{ .Offset = .{ .byte_offset = @enumToInt(member.offset) } }, @@ -485,70 +476,70 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct } switch (d.matrix_layout) { - .row_major => try annotations.decorateMember(self.gpa, target, index, .RowMajor), - .col_major => try annotations.decorateMember(self.gpa, target, index, .ColMajor), + .row_major => try self.decorateMember(target, index, .RowMajor), + .col_major => try self.decorateMember(target, index, .ColMajor), .none => {}, } if (d.matrix_layout != .none) { - try annotations.decorateMember(self.gpa, target, index, .{ + try self.decorateMember(target, index, .{ .MatrixStride = .{ .matrix_stride = extra[extra_i] }, }); extra_i += 1; } if (d.no_perspective) - try annotations.decorateMember(self.gpa, target, index, .NoPerspective); + try self.decorateMember(target, index, .NoPerspective); if (d.flat) - try annotations.decorateMember(self.gpa, target, index, .Flat); + try self.decorateMember(target, index, .Flat); if (d.patch) - try annotations.decorateMember(self.gpa, target, index, .Patch); + try self.decorateMember(target, index, .Patch); if (d.centroid) - try annotations.decorateMember(self.gpa, target, index, .Centroid); + try self.decorateMember(target, index, .Centroid); if (d.sample) - try annotations.decorateMember(self.gpa, target, index, .Sample); + try self.decorateMember(target, index, .Sample); if (d.invariant) - try annotations.decorateMember(self.gpa, target, index, .Invariant); + try self.decorateMember(target, index, .Invariant); if (d.@"volatile") - try annotations.decorateMember(self.gpa, target, index, .Volatile); + try self.decorateMember(target, index, .Volatile); if (d.coherent) - try annotations.decorateMember(self.gpa, target, index, .Coherent); + try self.decorateMember(target, index, .Coherent); if (d.non_writable) - try annotations.decorateMember(self.gpa, target, index, .NonWritable); + try self.decorateMember(target, index, .NonWritable); if (d.non_readable) - try annotations.decorateMember(self.gpa, target, index, .NonReadable); + try self.decorateMember(target, index, .NonReadable); if (d.builtin) { - try annotations.decorateMember(self.gpa, target, index, .{ + try self.decorateMember(target, index, .{ .BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) }, }); extra_i += 1; } if (d.stream) { - try annotations.decorateMember(self.gpa, target, index, .{ + try self.decorateMember(target, index, .{ .Stream = .{ .stream_number = extra[extra_i] }, }); extra_i += 1; } if (d.location) { - try annotations.decorateMember(self.gpa, target, index, .{ + try self.decorateMember(target, index, .{ .Location = .{ .location = extra[extra_i] }, }); extra_i += 1; } if (d.component) { - try annotations.decorateMember(self.gpa, target, index, .{ + try self.decorateMember(target, index, .{ .Component = .{ .component = extra[extra_i] }, }); extra_i += 1; } if (d.xfb_buffer) { - try annotations.decorateMember(self.gpa, target, index, .{ + try self.decorateMember(target, index, .{ .XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] }, }); extra_i += 1; } if (d.xfb_stride) { - try annotations.decorateMember(self.gpa, target, index, .{ + try self.decorateMember(target, index, .{ .XfbStride = .{ .xfb_stride = extra[extra_i] }, }); extra_i += 1; @@ -557,10 +548,50 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct const len = extra[extra_i]; extra_i += 1; const semantic = @ptrCast([*]const u8, &extra[extra_i])[0..len]; - try annotations.decorateMember(self.gpa, target, index, .{ + try self.decorateMember(target, index, .{ .UserSemantic = .{ .semantic = semantic }, }); extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable; } } } + +pub fn emitConstant( + self: *Module, + ty_id: spec.IdRef, + value: spec.LiteralContextDependentNumber, +) !IdRef { + const result_id = self.allocId(); + try self.sections.types_globals_constants.emit(self.gpa, .OpConstant, .{ + .id_result_type = ty_id, + .id_result = result_id, + .value = value, + }); + return result_id; +} + +/// Decorate a result-id. +pub fn decorate( + self: *Module, + target: spec.IdRef, + decoration: spec.Decoration.Extended, +) !void { + try self.sections.annotations.emit(self.gpa, .OpDecorate, .{ + .target = target, + .decoration = decoration, + }); +} + +/// Decorate a result-id which is a member of some struct. +pub fn decorateMember( + self: *Module, + structure_type: spec.IdRef, + member: u32, + decoration: spec.Decoration.Extended, +) !void { + try self.sections.annotations.emit(self.gpa, .OpMemberDecorate, .{ + .structure_type = structure_type, + .member = member, + .decoration = decoration, + }); +} diff --git a/src/codegen/spirv/Section.zig b/src/codegen/spirv/Section.zig index 2fd8309261..ce40717ef3 100644 --- a/src/codegen/spirv/Section.zig +++ b/src/codegen/spirv/Section.zig @@ -65,34 +65,6 @@ pub fn emit( section.writeOperands(opcode.Operands(), operands); } -/// Decorate a result-id. -pub fn decorate( - section: *Section, - allocator: Allocator, - target: spec.IdRef, - decoration: spec.Decoration.Extended, -) !void { - try section.emit(allocator, .OpDecorate, .{ - .target = target, - .decoration = decoration, - }); -} - -/// Decorate a result-id which is a member of some struct. -pub fn decorateMember( - section: *Section, - allocator: Allocator, - structure_type: spec.IdRef, - member: u32, - decoration: spec.Decoration.Extended, -) !void { - try section.emit(allocator, .OpMemberDecorate, .{ - .structure_type = structure_type, - .member = member, - .decoration = decoration, - }); -} - pub fn writeWord(section: *Section, word: Word) void { section.instructions.appendAssumeCapacity(word); } From 2a8e784989b9053ce609a38a9d384a77ce5badaa Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Wed, 30 Nov 2022 22:28:35 +0100 Subject: [PATCH 25/51] spirv: introduce type/value representations There are two main ways in which a value can be stored: "Direct", as it will be operated on as an immediate value, and "indirect", as it is stored in memory. Some types need a different representation here: Bools, for example, are opaque in SPIR-V, and so these need to have a different representation in memory. The bool operations are not easily interchangable with integer operations, though, so they need to be OpTypeBool as immediate value. --- src/codegen/spirv.zig | 235 ++++++++++++++++---------------- src/codegen/spirv/Assembler.zig | 2 +- src/codegen/spirv/Module.zig | 37 +++-- 3 files changed, 133 insertions(+), 141 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index add4464f4d..4deb9a225b 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -133,6 +133,16 @@ pub const DeclGen = struct { class: Class, }; + /// Data can be lowered into in two basic representations: indirect, which is when + /// a type is stored in memory, and direct, which is how a type is stored when its + /// a direct SPIR-V value. + const Repr = enum { + /// A SPIR-V value as it would be used in operations. + direct, + /// A SPIR-V value as it is stored in memory. + indirect, + }; + /// Initialize the common resources of a DeclGen. Some fields are left uninitialized, /// only set when `gen` is called. pub fn init( @@ -215,7 +225,7 @@ pub const DeclGen = struct { /// Fetch the result-id for a previously generated instruction or constant. fn resolve(self: *DeclGen, inst: Air.Inst.Ref) !IdRef { if (self.air.value(inst)) |val| { - return self.genConstant(self.air.typeOf(inst), val); + return self.genConstant(self.air.typeOf(inst), val, .direct); } const index = Air.refToIndex(inst).?; return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage. @@ -329,9 +339,29 @@ pub const DeclGen = struct { }; } + fn constInt(self: *DeclGen, ty_ref: SpvType.Ref, value: anytype) !IdRef { + const ty = self.spv.typeRefType(ty_ref); + const ty_id = self.typeId(ty_ref); + + const literal: spec.LiteralContextDependentNumber = switch (ty.intSignedness()) { + .signed => switch (ty.intFloatBits()) { + 1...32 => .{ .int32 = @intCast(i32, value) }, + 33...64 => .{ .int64 = @intCast(i64, value) }, + else => unreachable, // TODO: composite integer literals + }, + .unsigned => switch (ty.intFloatBits()) { + 1...32 => .{ .uint32 = @intCast(u32, value) }, + 33...64 => .{ .uint64 = @intCast(u64, value) }, + else => unreachable, + }, + }; + + return try self.spv.emitConstant(ty_id, literal); + } + /// Generate a constant representing `val`. /// TODO: Deduplication? - fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!IdRef { + fn genConstant(self: *DeclGen, ty: Type, val: Value, repr: Repr) Error!IdRef { if (ty.zigTypeTag() == .Fn) { const fn_decl_index = switch (val.tag()) { .extern_fn => val.castTag(.extern_fn).?.data.owner_decl, @@ -345,56 +375,37 @@ pub const DeclGen = struct { const target = self.getTarget(); const section = &self.spv.sections.types_globals_constants; - const result_id = self.spv.allocId(); - const result_type_id = try self.resolveTypeId(ty); + const result_ty_ref = try self.resolveType(ty, repr); + const result_ty_id = self.typeId(result_ty_ref); if (val.isUndef()) { - try section.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = result_type_id, .id_result = result_id }); + 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; } switch (ty.zigTypeTag()) { .Int => { - const int_info = ty.intInfo(target); - const backing_bits = self.backingIntBits(int_info.bits) orelse { - // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits. - return self.todo("implement composite int constants for {}", .{ty.fmtDebug()}); - }; - - // We can just use toSignedInt/toUnsignedInt here as it returns u64 - a type large enough to hold any - // SPIR-V native type (up to i/u64 with Int64). If SPIR-V ever supports native ints of a larger size, this - // might need to be updated. - assert(self.largestSupportedIntBits() <= @bitSizeOf(u64)); - - // Note, value is required to be sign-extended, so we don't need to mask off the upper bits. - // See https://www.khronos.org/registry/SPIR-V/specs/unified1/SPIRV.html#Literal - var int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt(target)) else val.toUnsignedInt(target); - - const value: spec.LiteralContextDependentNumber = switch (backing_bits) { - 1...32 => .{ .uint32 = @truncate(u32, int_bits) }, - 33...64 => .{ .uint64 = int_bits }, - else => unreachable, - }; - - try section.emit(self.spv.gpa, .OpConstant, .{ - .id_result_type = result_type_id, - .id_result = result_id, - .value = value, - }); + const int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt(target)) else val.toUnsignedInt(target); + return self.constInt(result_ty_ref, int_bits); }, - .Bool => { - const operands = .{ .id_result_type = result_type_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); - } + .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; + }, + .indirect => return try self.constInt(result_ty_ref, @boolToInt(val.toBool())), }, .Float => { // At this point we are guaranteed that the target floating point type is supported, otherwise the function // would have exited at resolveTypeId(ty). - - const value: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) { + const literal: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) { // Prevent upcasting to f32 by bitcasting and writing as a uint32. 16 => .{ .uint32 = @bitCast(u16, val.toFloat(f16)) }, 32 => .{ .float32 = val.toFloat(f32) }, @@ -404,11 +415,7 @@ pub const DeclGen = struct { else => unreachable, }; - try section.emit(self.spv.gpa, .OpConstant, .{ - .id_result_type = result_type_id, - .id_result = result_id, - .value = value, - }); + return try self.spv.emitConstant(result_ty_id, literal); }, .Array => switch (val.tag()) { .aggregate => { // todo: combine with Vector @@ -417,14 +424,16 @@ pub const DeclGen = struct { const len = @intCast(u32, ty.arrayLenIncludingSentinel()); // TODO: limit spir-v to 32 bit arrays in a more elegant way. const constituents = try self.spv.gpa.alloc(IdRef, len); defer self.spv.gpa.free(constituents); - for (elem_vals[0..len]) |elem_val, i| { - constituents[i] = try self.genConstant(elem_ty, elem_val); + for (elem_vals[0..len], 0..) |elem_val, i| { + constituents[i] = try self.genConstant(elem_ty, elem_val, repr); } + const result_id = self.spv.allocId(); try section.emit(self.spv.gpa, .OpConstantComposite, .{ - .id_result_type = result_type_id, + .id_result_type = result_ty_id, .id_result = result_id, .constituents = constituents, }); + return result_id; }, .repeated => { const elem_val = val.castTag(.repeated).?.data; @@ -433,18 +442,20 @@ pub const DeclGen = struct { const constituents = try self.spv.gpa.alloc(IdRef, len); defer self.spv.gpa.free(constituents); - const elem_val_id = try self.genConstant(elem_ty, elem_val); + const elem_val_id = try self.genConstant(elem_ty, elem_val, repr); for (constituents[0..len]) |*elem| { elem.* = elem_val_id; } if (ty.sentinel()) |sentinel| { - constituents[len] = try self.genConstant(elem_ty, sentinel); + constituents[len] = try self.genConstant(elem_ty, sentinel, repr); } + const result_id = self.spv.allocId(); try section.emit(self.spv.gpa, .OpConstantComposite, .{ - .id_result_type = result_type_id, + .id_result_type = result_ty_id, .id_result = result_id, .constituents = constituents, }); + return result_id; }, else => return self.todo("array constant with tag {s}", .{@tagName(val.tag())}), }, @@ -457,39 +468,22 @@ pub const DeclGen = struct { const elem_refs = try self.gpa.alloc(IdRef, vector_len); defer self.gpa.free(elem_refs); for (elem_refs, 0..) |*elem, i| { - elem.* = try self.genConstant(elem_ty, elem_vals[i]); + elem.* = try self.genConstant(elem_ty, elem_vals[i], repr); } + const result_id = self.spv.allocId(); try section.emit(self.spv.gpa, .OpConstantComposite, .{ - .id_result_type = result_type_id, + .id_result_type = result_ty_id, .id_result = result_id, .constituents = elem_refs, }); + return result_id; }, else => return self.todo("vector constant with tag {s}", .{@tagName(val.tag())}), }, .Enum => { - var ty_buffer: Type.Payload.Bits = undefined; - const int_ty = ty.intTagType(&ty_buffer); - const int_info = int_ty.intInfo(target); - - const backing_bits = self.backingIntBits(int_info.bits) orelse { - return self.todo("implement composite int constants for {}", .{int_ty.fmtDebug()}); - }; - var int_buffer: Value.Payload.U64 = undefined; const int_val = val.enumToInt(ty, &int_buffer).toUnsignedInt(target); // TODO: composite integer constants - - const value: spec.LiteralContextDependentNumber = switch (backing_bits) { - 1...32 => .{ .uint32 = @truncate(u32, int_val) }, - 33...64 => .{ .uint64 = int_val }, - else => unreachable, - }; - - try section.emit(self.spv.gpa, .OpConstant, .{ - .id_result_type = result_type_id, - .id_result = result_id, - .value = value, - }); + return self.constInt(result_ty_ref, int_val); }, .Struct => { const constituents = if (ty.isSimpleTupleOrAnonStruct()) blk: { @@ -498,10 +492,10 @@ pub const DeclGen = struct { errdefer self.spv.gpa.free(constituents); var member_index: usize = 0; - for (tuple.types) |field_ty, i| { + for (tuple.types, 0..) |field_ty, i| { const field_val = tuple.values[i]; if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue; - constituents[member_index] = try self.genConstant(field_ty, field_val); + constituents[member_index] = try self.genConstant(field_ty, field_val, repr); member_index += 1; } @@ -517,9 +511,9 @@ pub const DeclGen = struct { const constituents = try self.spv.gpa.alloc(IdRef, struct_ty.fields.count()); errdefer self.spv.gpa.free(constituents); var member_index: usize = 0; - for (struct_ty.fields.values()) |field, i| { + for (struct_ty.fields.values(), 0..) |field, i| { if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; - constituents[member_index] = try self.genConstant(field.ty, field_vals[i]); + constituents[member_index] = try self.genConstant(field.ty, field_vals[i], repr); member_index += 1; } @@ -527,24 +521,28 @@ pub const DeclGen = struct { }; defer self.spv.gpa.free(constituents); + const result_id = self.spv.allocId(); try section.emit(self.spv.gpa, .OpConstantComposite, .{ - .id_result_type = result_type_id, + .id_result_type = result_ty_id, .id_result = result_id, .constituents = constituents, }); + return result_id; }, .Void => unreachable, .Fn => unreachable, else => return self.todo("constant generation of type {s}: {}", .{ @tagName(ty.zigTypeTag()), ty.fmtDebug() }), } - - return result_id; } /// 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); - return self.spv.typeResultId(type_ref); + const type_ref = try self.resolveType(ty, .direct); + return self.typeId(type_ref); + } + + fn typeId(self: *DeclGen, ty_ref: SpvType.Ref) IdRef { + return self.spv.typeId(ty_ref); } /// Create an integer type suitable for storing at least 'bits' bits. @@ -576,19 +574,20 @@ pub const DeclGen = struct { fn simpleStructTypeId(self: *DeclGen, members: []const SpvType.Payload.Struct.Member) !IdResultType { const type_ref = try self.simpleStructType(members); - return self.spv.typeResultId(type_ref); + return self.typeId(type_ref); } /// Turn a Zig type into a SPIR-V Type, and return a reference to it. - fn resolveType(self: *DeclGen, ty: Type) Error!SpvType.Ref { + fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!SpvType.Ref { const target = self.getTarget(); switch (ty.zigTypeTag()) { .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)), - .Bool => { + .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. - return try self.intType(.unsigned, 1); + .indirect => return try self.intType(.unsigned, 1), }, .Int => { const int_info = ty.intInfo(target); @@ -596,9 +595,8 @@ pub const DeclGen = struct { }, .Enum => { var buffer: Type.Payload.Bits = undefined; - const int_ty = ty.intTagType(&buffer); - const int_info = int_ty.intInfo(target); - return try self.intType(.unsigned, int_info.bits); + 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, @@ -626,7 +624,7 @@ pub const DeclGen = struct { const payload = try self.spv.arena.create(SpvType.Payload.Array); payload.* = .{ - .element_type = try self.resolveType(elem_ty), + .element_type = try self.resolveType(elem_ty, repr), .length = total_len, }; return try self.spv.resolveType(SpvType.initPayload(&payload.base)); @@ -636,12 +634,14 @@ pub const DeclGen = struct { 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)); + param.* = try self.resolveType(ty.fnParamType(i), .direct); } - const return_type = try self.resolveType(ty.fnReturnType()); + 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 }; @@ -653,7 +653,7 @@ pub const DeclGen = struct { const ptr_payload = try self.spv.arena.create(SpvType.Payload.Pointer); ptr_payload.* = .{ .storage_class = spirvStorageClass(ptr_info.@"addrspace"), - .child_type = try self.resolveType(ptr_info.pointee_type), + .child_type = try self.resolveType(ptr_info.pointee_type, .indirect), // Note: only available in Kernels! .alignment = ty.ptrAlignment(target) * 8, }; @@ -680,7 +680,7 @@ pub const DeclGen = struct { const payload = try self.spv.arena.create(SpvType.Payload.Vector); payload.* = .{ - .component_type = try self.resolveType(ty.elemType()), + .component_type = try self.resolveType(ty.elemType(), repr), .component_count = @intCast(u32, ty.vectorLen()), }; return try self.spv.resolveType(SpvType.initPayload(&payload.base)); @@ -690,11 +690,11 @@ pub const DeclGen = struct { 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) |field_ty, i| { + 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), + .ty = try self.resolveType(field_ty, repr), }; member_index += 1; } @@ -708,16 +708,16 @@ pub const DeclGen = struct { const struct_ty = ty.castTag(.@"struct").?.data; if (struct_ty.layout == .Packed) { - return try self.resolveType(struct_ty.backing_int_ty); + return try self.resolveType(struct_ty.backing_int_ty, repr); } 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()) |field, i| { + 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), + .ty = try self.resolveType(field.ty, repr), .name = struct_ty.fields.keys()[i], }; member_index += 1; @@ -957,21 +957,14 @@ pub const DeclGen = struct { return result_id; } - fn maskStrangeInt(self: *DeclGen, ty_id: IdResultType, int_id: IdRef, bits: u16) !IdRef { - const backing_bits = self.backingIntBits(bits).?; + fn maskStrangeInt(self: *DeclGen, ty_ref: SpvType.Ref, 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 mask_lit: spec.LiteralContextDependentNumber = switch (backing_bits) { - 1...32 => .{ .uint32 = @truncate(u32, mask_value) }, - 33...64 => .{ .uint64 = mask_value }, - else => unreachable, - }; - // TODO: We should probably optimize the amount of these constants a bit. - const mask_id = try self.spv.emitConstant(ty_id, mask_lit); const result_id = self.spv.allocId(); + const mask_id = try self.constInt(ty_ref, mask_value); try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{ - .id_result_type = ty_id, + .id_result_type = self.typeId(ty_ref), .id_result = result_id, - .operand_1 = int_id, + .operand_1 = value_id, .operand_2 = mask_id, }); return result_id; @@ -994,8 +987,7 @@ pub const DeclGen = struct { var lhs_id = try self.resolve(bin_op.lhs); var rhs_id = try self.resolve(bin_op.rhs); - const result_id = self.spv.allocId(); - const result_type_id = try self.resolveTypeId(ty); + const result_ty_ref = try self.resolveType(ty, .direct); assert(self.air.typeOf(bin_op.lhs).eql(ty, self.module)); assert(self.air.typeOf(bin_op.rhs).eql(ty, self.module)); @@ -1010,8 +1002,8 @@ pub const DeclGen = struct { }, .strange_integer => blk: { if (!modular) { - lhs_id = try self.maskStrangeInt(result_type_id, lhs_id, info.bits); - rhs_id = try self.maskStrangeInt(result_type_id, rhs_id, info.bits); + lhs_id = try self.maskStrangeInt(result_ty_ref, lhs_id, info.bits); + rhs_id = try self.maskStrangeInt(result_ty_ref, rhs_id, info.bits); } break :blk switch (info.signedness) { .signed => @as(usize, 1), @@ -1026,8 +1018,9 @@ pub const DeclGen = struct { .bool => unreachable, }; + const result_id = self.spv.allocId(); const operands = .{ - .id_result_type = result_type_id, + .id_result_type = self.typeId(result_ty_ref), .id_result = result_id, .operand_1 = lhs_id, .operand_2 = rhs_id, @@ -1068,7 +1061,7 @@ pub const DeclGen = struct { const result_type_id = try self.resolveTypeId(result_ty); const overflow_member_ty = try self.intType(.unsigned, info.bits); - const overflow_member_ty_id = self.spv.typeResultId(overflow_member_ty); + const overflow_member_ty_id = self.typeId(overflow_member_ty); const op_result_id = blk: { // Construct the SPIR-V result type. @@ -1181,9 +1174,9 @@ pub const DeclGen = struct { .float => 0, .bool => 1, .strange_integer => blk: { - const op_ty_id = try self.resolveTypeId(op_ty); - lhs_id = try self.maskStrangeInt(op_ty_id, lhs_id, info.bits); - rhs_id = try self.maskStrangeInt(op_ty_id, rhs_id, info.bits); + const op_ty_ref = try self.resolveType(op_ty, .direct); + lhs_id = try self.maskStrangeInt(op_ty_ref, lhs_id, info.bits); + rhs_id = try self.maskStrangeInt(op_ty_ref, rhs_id, info.bits); break :blk switch (info.signedness) { .signed => @as(usize, 1), .unsigned => @as(usize, 2), @@ -1425,7 +1418,7 @@ pub const DeclGen = struct { .Struct => switch (object_ty.containerLayout()) { .Packed => unreachable, // TODO else => { - const u32_ty_id = self.spv.typeResultId(try self.intType(.unsigned, 32)); + const u32_ty_id = self.typeId(try self.intType(.unsigned, 32)); const field_index_id = try self.spv.emitConstant(u32_ty_id, .{ .uint32 = field_index }); const result_id = self.spv.allocId(); const result_type_id = try self.resolveTypeId(result_ptr_ty); @@ -1740,7 +1733,7 @@ pub const DeclGen = struct { return self.todo("switch on runtime value???", .{}); }; const int_val = switch (cond_ty.zigTypeTag()) { - .Int => if (cond_ty.isSignedInt()) @bitCast(u64, value.toSignedInt()) else value.toUnsignedInt(target), + .Int => if (cond_ty.isSignedInt()) @bitCast(u64, value.toSignedInt(target)) else value.toUnsignedInt(target), .Enum => blk: { var int_buffer: Value.Payload.U64 = undefined; // TODO: figure out of cond_ty is correct (something with enum literals) diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig index 9b63ef8191..5f9726a0fe 100644 --- a/src/codegen/spirv/Assembler.zig +++ b/src/codegen/spirv/Assembler.zig @@ -135,7 +135,7 @@ const AsmValue = union(enum) { return switch (self) { .just_declared, .unresolved_forward_reference => unreachable, .value => |result| result, - .ty => |ref| spv.typeResultId(ref), + .ty => |ref| spv.typeId(ref), }; } }; diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 1a6ac95a0a..b9bae9972f 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -228,18 +228,17 @@ pub fn resolveType(self: *Module, ty: Type) !Type.Ref { } pub fn resolveTypeId(self: *Module, ty: Type) !IdResultType { - const type_ref = try self.resolveType(ty); - return self.typeResultId(type_ref); + 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 typeResultId(self: Module, type_ref: Type.Ref) IdResultType { - return self.type_cache.values()[@enumToInt(type_ref)]; -} - -/// Get the result-id of a particular type as IdRef, by Type.Ref. Asserts type_ref is valid. -pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef { - return self.type_cache.values()[@enumToInt(type_ref)]; +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. @@ -321,19 +320,19 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { }, .vector => try types.emit(self.gpa, .OpTypeVector, .{ .id_result = result_id, - .component_type = self.typeResultId(ty.childType()), + .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.typeResultId(ty.childType()), + .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.typeResultId(ty.childType()), + .sampled_type = self.typeId(ty.childType()), .dim = info.dim, .depth = @enumToInt(info.depth), .arrayed = @boolToInt(info.arrayed), @@ -346,7 +345,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { .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.typeResultId(ty.childType()), + .image_type = self.typeId(ty.childType()), }), .array => { const info = ty.payload(.array); @@ -358,7 +357,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { try types.emit(self.gpa, .OpTypeArray, .{ .id_result = result_id, - .element_type = self.typeResultId(ty.childType()), + .element_type = self.typeId(ty.childType()), .length = length_id, }); if (info.array_stride != 0) { @@ -369,7 +368,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { const info = ty.payload(.runtime_array); try types.emit(self.gpa, .OpTypeRuntimeArray, .{ .id_result = result_id, - .element_type = self.typeResultId(ty.childType()), + .element_type = self.typeId(ty.childType()), }); if (info.array_stride != 0) { try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); @@ -380,7 +379,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len); types.writeOperand(IdResult, result_id); for (info.members) |member| { - types.writeOperand(IdRef, self.typeResultId(member.ty)); + types.writeOperand(IdRef, self.typeId(member.ty)); } try self.decorateStruct(ref_id, info); }, @@ -393,7 +392,7 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { try types.emit(self.gpa, .OpTypePointer, .{ .id_result = result_id, .storage_class = info.storage_class, - .type = self.typeResultId(ty.childType()), + .type = self.typeId(ty.childType()), }); if (info.array_stride != 0) { try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); @@ -409,9 +408,9 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { const info = ty.payload(.function); try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len); types.writeOperand(IdResult, result_id); - types.writeOperand(IdRef, self.typeResultId(info.return_type)); + types.writeOperand(IdRef, self.typeId(info.return_type)); for (info.parameters) |parameter_type| { - types.writeOperand(IdRef, self.typeResultId(parameter_type)); + types.writeOperand(IdRef, self.typeId(parameter_type)); } }, .event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand), From caf8461af8df9a34abc5985042d98b8188575cd0 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Wed, 30 Nov 2022 23:28:07 +0100 Subject: [PATCH 26/51] spirv: make locals generic pointers Taking the address of a local variable should result in a generic pointer - too much code breaks if we do not do this. We cannot lower locals into the generic storage class directly though, so instead, lower the variables into the Function storage class implicitly, and convert the pointer to a generic pointer. Also Correct OpInboundsAccessChain generation (we only need the one index). --- src/codegen/spirv.zig | 51 ++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 4deb9a225b..22c3791df0 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1306,12 +1306,11 @@ pub const DeclGen = struct { }; const result_id = self.spv.allocId(); - const indexes = [_]IdRef{index}; - try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ + try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{ .id_result_type = spv_ptr_ty, .id_result = result_id, .base = slice_ptr, - .indexes = &indexes, + .element = index, }); return result_id; } @@ -1340,12 +1339,11 @@ pub const DeclGen = struct { const elem_ptr = blk: { const result_id = self.spv.allocId(); - const indexes = [_]IdRef{index}; - try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{ + try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{ .id_result_type = ptr_ty_id, .id_result = result_id, .base = slice_ptr, - .indexes = &indexes, + .element = index, }); break :blk result_id; }; @@ -1448,22 +1446,45 @@ pub const DeclGen = struct { fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; const ty = self.air.typeOfIndex(inst); - const result_type_id = try self.resolveTypeId(ty); + const result_ty_ref = try self.resolveType(ty, .direct); + const result_ty_id = self.typeId(result_ty_ref); const result_id = self.spv.allocId(); - // Rather than generating into code here, we're just going to generate directly into the functions section so that - // variable declarations appear in the first block of the function. const storage_class = spirvStorageClass(ty.ptrAddressSpace()); - const section = if (storage_class == .Function or storage_class == .Generic) - &self.func.prologue - else - &self.spv.sections.types_globals_constants; + const ptr_ty_id = switch (storage_class) { + .Generic => blk: { + const payload = try self.spv.arena.create(SpvType.Payload.Pointer); + payload.* = self.spv.typeRefType(result_ty_ref).payload(.pointer).*; + payload.storage_class = .Function; + break :blk try self.spv.resolveTypeId(SpvType.initPayload(&payload.base)); + }, + else => result_ty_id, + }; + const actual_storage_class = switch (storage_class) { + .Generic, .Function => .Function, + else => storage_class, + }; + const section = switch (storage_class) { + // 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. + .Generic, .Function => &self.func.prologue, + else => &self.spv.sections.types_globals_constants, + }; try section.emit(self.spv.gpa, .OpVariable, .{ - .id_result_type = result_type_id, + .id_result_type = ptr_ty_id, .id_result = result_id, - .storage_class = storage_class, + .storage_class = actual_storage_class, }); + if (storage_class == .Generic) { + const casted_result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{ + .id_result_type = result_ty_id, + .id_result = casted_result_id, + .pointer = result_id, + }); + return casted_result_id; + } return result_id; } From f1229e0f00114e38001df8b57725de9c3e13a06d Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Wed, 30 Nov 2022 23:50:47 +0100 Subject: [PATCH 27/51] spirv: convert bools on load/store Bools have a different immediate representation and memory representation - which means that they must be converted every time a bool is loaded from or stored into memory. --- src/codegen/spirv.zig | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 22c3791df0..0f3981368d 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1604,17 +1604,30 @@ pub const DeclGen = struct { fn load(self: *DeclGen, ptr_ty: Type, ptr: IdRef) !IdRef { const value_ty = ptr_ty.childType(); - const result_type_id = try self.resolveTypeId(value_ty); + const direct_result_ty_ref = try self.resolveType(value_ty, .direct); + const indirect_result_ty_ref = try self.resolveType(value_ty, .indirect); const result_id = self.spv.allocId(); const access = spec.MemoryAccess.Extended{ .Volatile = ptr_ty.isVolatilePtr(), }; try self.func.body.emit(self.spv.gpa, .OpLoad, .{ - .id_result_type = result_type_id, + .id_result_type = self.typeId(indirect_result_ty_ref), .id_result = result_id, .pointer = ptr, .memory_access = access, }); + if (value_ty.zigTypeTag() == .Bool) { + // Convert indirect bool to direct bool + const zero_id = try self.constInt(indirect_result_ty_ref, 0); + const casted_result_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{ + .id_result_type = self.typeId(direct_result_ty_ref), + .id_result = casted_result_id, + .operand_1 = result_id, + .operand_2 = zero_id, + }); + return casted_result_id; + } return result_id; } @@ -1628,12 +1641,30 @@ pub const DeclGen = struct { } fn store(self: *DeclGen, ptr_ty: Type, ptr: IdRef, value: IdRef) !void { + const value_ty = ptr_ty.childType(); + const converted_value = switch (value_ty.zigTypeTag()) { + .Bool => blk: { + const indirect_bool_ty_ref = try self.resolveType(value_ty, .indirect); + const result_id = self.spv.allocId(); + const zero = try self.constInt(indirect_bool_ty_ref, 0); + const one = try self.constInt(indirect_bool_ty_ref, 1); + try self.func.body.emit(self.spv.gpa, .OpSelect, .{ + .id_result_type = self.typeId(indirect_bool_ty_ref), + .id_result = result_id, + .condition = value, + .object_1 = one, + .object_2 = zero, + }); + break :blk result_id; + }, + else => value, + }; const access = spec.MemoryAccess.Extended{ .Volatile = ptr_ty.isVolatilePtr(), }; try self.func.body.emit(self.spv.gpa, .OpStore, .{ .pointer = ptr, - .object = value, + .object = converted_value, .memory_access = access, }); } From 34b98ee372f8974e21cd34b3ca7588b13d93e31a Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 1 Dec 2022 23:26:02 +0100 Subject: [PATCH 28/51] spirv: start lowering non-function decls Start to lower decls which are not functions. These generate an OpVariable instruction by which they can be used later on. --- src/codegen/spirv.zig | 95 ++++++++++++++++++++---------------- src/codegen/spirv/Module.zig | 8 +-- 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 0f3981368d..cae6b4cd8b 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -225,7 +225,21 @@ pub const DeclGen = struct { /// Fetch the result-id for a previously generated instruction or constant. fn resolve(self: *DeclGen, inst: Air.Inst.Ref) !IdRef { if (self.air.value(inst)) |val| { - return self.genConstant(self.air.typeOf(inst), val, .direct); + const ty = self.air.typeOf(inst); + if (ty.zigTypeTag() == .Fn) { + const fn_decl_index = switch (val.tag()) { + .extern_fn => val.castTag(.extern_fn).?.data.owner_decl, + .function => val.castTag(.function).?.data.owner_decl, + else => unreachable, + }; + const decl = self.module.declPtr(fn_decl_index); + self.module.markDeclAlive(decl); + return self.ids.get(fn_decl_index).?; + } + + const result_id = self.spv.allocId(); + try self.genConstant(result_id, ty, val, .direct); + return result_id; } const index = Air.refToIndex(inst).?; return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage. @@ -339,7 +353,7 @@ pub const DeclGen = struct { }; } - fn constInt(self: *DeclGen, ty_ref: SpvType.Ref, value: anytype) !IdRef { + 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); @@ -356,51 +370,44 @@ pub const DeclGen = struct { }, }; - return try self.spv.emitConstant(ty_id, literal); + 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; } /// Generate a constant representing `val`. /// TODO: Deduplication? - fn genConstant(self: *DeclGen, ty: Type, val: Value, repr: Repr) Error!IdRef { - if (ty.zigTypeTag() == .Fn) { - const fn_decl_index = switch (val.tag()) { - .extern_fn => val.castTag(.extern_fn).?.data.owner_decl, - .function => val.castTag(.function).?.data.owner_decl, - else => unreachable, - }; - const decl = self.module.declPtr(fn_decl_index); - self.module.markDeclAlive(decl); - return self.ids.get(fn_decl_index).?; - } - + fn genConstant(self: *DeclGen, result_id: IdRef, ty: Type, val: Value, repr: Repr) Error!void { 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("genConstant: ty = {}, val = {}", .{ ty.fmtDebug(), val.fmtDebug() }); + 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; } switch (ty.zigTypeTag()) { .Int => { const int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt(target)) else val.toUnsignedInt(target); - return self.constInt(result_ty_ref, int_bits); + try self.genConstInt(result_ty_ref, result_id, int_bits); }, .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; }, - .indirect => return try self.constInt(result_ty_ref, @boolToInt(val.toBool())), + .indirect => try self.genConstInt(result_ty_ref, result_id, @boolToInt(val.toBool())), }, .Float => { // At this point we are guaranteed that the target floating point type is supported, otherwise the function @@ -415,7 +422,7 @@ pub const DeclGen = struct { else => unreachable, }; - return try self.spv.emitConstant(result_ty_id, literal); + try self.spv.emitConstant(result_ty_id, result_id, literal); }, .Array => switch (val.tag()) { .aggregate => { // todo: combine with Vector @@ -425,15 +432,14 @@ pub const DeclGen = struct { const constituents = try self.spv.gpa.alloc(IdRef, len); defer self.spv.gpa.free(constituents); for (elem_vals[0..len], 0..) |elem_val, i| { - constituents[i] = try self.genConstant(elem_ty, elem_val, repr); + constituents[i] = self.spv.allocId(); + try self.genConstant(constituents[i], elem_ty, elem_val, repr); } - const result_id = self.spv.allocId(); try section.emit(self.spv.gpa, .OpConstantComposite, .{ .id_result_type = result_ty_id, .id_result = result_id, .constituents = constituents, }); - return result_id; }, .repeated => { const elem_val = val.castTag(.repeated).?.data; @@ -442,20 +448,20 @@ pub const DeclGen = struct { const constituents = try self.spv.gpa.alloc(IdRef, len); defer self.spv.gpa.free(constituents); - const elem_val_id = try self.genConstant(elem_ty, elem_val, repr); + const elem_val_id = self.spv.allocId(); + try self.genConstant(elem_val_id, elem_ty, elem_val, repr); for (constituents[0..len]) |*elem| { elem.* = elem_val_id; } if (ty.sentinel()) |sentinel| { - constituents[len] = try self.genConstant(elem_ty, sentinel, repr); + constituents[len] = self.spv.allocId(); + try self.genConstant(constituents[len], elem_ty, sentinel, repr); } - const result_id = self.spv.allocId(); try section.emit(self.spv.gpa, .OpConstantComposite, .{ .id_result_type = result_ty_id, .id_result = result_id, .constituents = constituents, }); - return result_id; }, else => return self.todo("array constant with tag {s}", .{@tagName(val.tag())}), }, @@ -468,22 +474,21 @@ pub const DeclGen = struct { const elem_refs = try self.gpa.alloc(IdRef, vector_len); defer self.gpa.free(elem_refs); for (elem_refs, 0..) |*elem, i| { - elem.* = try self.genConstant(elem_ty, elem_vals[i], repr); + elem.* = self.spv.allocId(); + try self.genConstant(elem.*, elem_ty, elem_vals[i], repr); } - const result_id = self.spv.allocId(); try section.emit(self.spv.gpa, .OpConstantComposite, .{ .id_result_type = result_ty_id, .id_result = result_id, .constituents = elem_refs, }); - return result_id; }, else => return self.todo("vector constant with tag {s}", .{@tagName(val.tag())}), }, .Enum => { var int_buffer: Value.Payload.U64 = undefined; const int_val = val.enumToInt(ty, &int_buffer).toUnsignedInt(target); // TODO: composite integer constants - return self.constInt(result_ty_ref, int_val); + return self.genConstInt(result_ty_ref, result_id, int_val); }, .Struct => { const constituents = if (ty.isSimpleTupleOrAnonStruct()) blk: { @@ -495,7 +500,9 @@ pub const DeclGen = struct { for (tuple.types, 0..) |field_ty, i| { const field_val = tuple.values[i]; if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue; - constituents[member_index] = try self.genConstant(field_ty, field_val, repr); + const member_id = self.spv.allocId(); + try self.genConstant(member_id, field_ty, field_val, repr); + constituents[member_index] = member_id; member_index += 1; } @@ -513,7 +520,9 @@ pub const DeclGen = struct { var member_index: usize = 0; for (struct_ty.fields.values(), 0..) |field, i| { if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; - constituents[member_index] = try self.genConstant(field.ty, field_vals[i], repr); + const member_id = self.spv.allocId(); + try self.genConstant(member_id, field.ty, field_vals[i], repr); + constituents[member_index] = member_id; member_index += 1; } @@ -521,16 +530,17 @@ pub const DeclGen = struct { }; defer self.spv.gpa.free(constituents); - const result_id = self.spv.allocId(); try section.emit(self.spv.gpa, .OpConstantComposite, .{ .id_result_type = result_ty_id, .id_result = result_id, .constituents = constituents, }); - return result_id; + }, + .Fn => switch (repr) { + .direct => unreachable, + .indirect => return self.todo("function pointers", .{}), }, .Void => unreachable, - .Fn => unreachable, else => return self.todo("constant generation of type {s}: {}", .{ @tagName(ty.zigTypeTag()), ty.fmtDebug() }), } } @@ -579,6 +589,7 @@ 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.fmtDebug()}); const target = self.getTarget(); switch (ty.zigTypeTag()) { .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)), @@ -808,8 +819,7 @@ pub const DeclGen = struct { .name = fqn, }); } else { - // TODO - // return self.todo("generate decl type {}", .{decl.ty.zigTypeTag()}); + try self.genConstant(result_id, decl.ty, decl.val, .direct); } } @@ -1417,7 +1427,8 @@ pub const DeclGen = struct { .Packed => unreachable, // TODO else => { const u32_ty_id = self.typeId(try self.intType(.unsigned, 32)); - const field_index_id = try self.spv.emitConstant(u32_ty_id, .{ .uint32 = field_index }); + const field_index_id = self.spv.allocId(); + try self.spv.emitConstant(u32_ty_id, field_index_id, .{ .uint32 = field_index }); const result_id = self.spv.allocId(); const result_type_id = try self.resolveTypeId(result_ptr_ty); const indexes = [_]IdRef{field_index_id}; @@ -1960,7 +1971,7 @@ pub const DeclGen = struct { return null; } - fn airCall(self: *DeclGen, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.Modifier) !?IdRef { + fn airCall(self: *DeclGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !?IdRef { _ = modifier; const pl_op = self.air.instructions.items(.data)[inst].pl_op; diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index b9bae9972f..803276d78c 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -353,7 +353,8 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { const size_type = Type.initTag(.u32); const size_type_id = try self.resolveTypeId(size_type); - const length_id = try self.emitConstant(size_type_id, .{ .uint32 = info.length }); + 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, @@ -558,15 +559,14 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct pub fn emitConstant( self: *Module, ty_id: spec.IdRef, + result_id: IdRef, value: spec.LiteralContextDependentNumber, -) !IdRef { - const result_id = self.allocId(); +) !void { try self.sections.types_globals_constants.emit(self.gpa, .OpConstant, .{ .id_result_type = ty_id, .id_result = result_id, .value = value, }); - return result_id; } /// Decorate a result-id. From c9db6e43af366215cd8bf494ea54ad14e4b97cc0 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 2 Dec 2022 00:22:26 +0100 Subject: [PATCH 29/51] spirv: generate code directly in updateFunc/updateDecl This cloneAir/cloneLiveness idea used to ignore Zig's internals has proven buggy. Instead, just generate the code directly from updateFunc and updateDecl as the other backends do, but pretend that Zig is not an incremental compiler. The SPIR-V backend will for the time being not support this. --- src/codegen/spirv.zig | 30 ++++++-- src/link/SpirV.zig | 172 ++++++++---------------------------------- 2 files changed, 54 insertions(+), 148 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index cae6b4cd8b..f44e894a29 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -37,6 +37,8 @@ pub const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct { incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock), }); +pub const DeclMap = std.AutoHashMap(Module.Decl.Index, IdResult); + /// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that. pub const DeclGen = struct { /// A general-purpose allocator that can be used for any allocations for this DeclGen. @@ -59,7 +61,8 @@ pub const DeclGen = struct { /// Note: If the declaration is not a function, this value will be undefined! liveness: Liveness, - ids: *const std.AutoHashMap(Decl.Index, IdResult), + /// Maps Zig Decl indices to SPIR-V result indices. + decl_ids: *DeclMap, /// An array of function argument result-ids. Each index corresponds with the /// function argument of the same index. @@ -149,7 +152,7 @@ pub const DeclGen = struct { allocator: Allocator, module: *Module, spv: *SpvModule, - ids: *const std.AutoHashMap(Decl.Index, IdResult), + decl_ids: *DeclMap, ) DeclGen { return .{ .gpa = allocator, @@ -158,7 +161,7 @@ pub const DeclGen = struct { .decl_index = undefined, .air = undefined, .liveness = undefined, - .ids = ids, + .decl_ids = decl_ids, .next_arg_index = undefined, .current_block_label_id = undefined, .error_msg = undefined, @@ -232,9 +235,7 @@ pub const DeclGen = struct { .function => val.castTag(.function).?.data.owner_decl, else => unreachable, }; - const decl = self.module.declPtr(fn_decl_index); - self.module.markDeclAlive(decl); - return self.ids.get(fn_decl_index).?; + return try self.resolveDecl(fn_decl_index); } const result_id = self.spv.allocId(); @@ -245,6 +246,21 @@ pub const DeclGen = struct { return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage. } + /// Fetch or allocate a result id for decl index. This function also marks the decl as alive. + /// Note: Function does not actually generate the decl. + fn resolveDecl(self: *DeclGen, decl_index: Module.Decl.Index) !IdResult { + const decl = self.module.declPtr(decl_index); + self.module.markDeclAlive(decl); + + const entry = try self.decl_ids.getOrPut(decl_index); + if (entry.found_existing) { + return entry.value_ptr.*; + } + const result_id = self.spv.allocId(); + entry.value_ptr.* = result_id; + return result_id; + } + /// Start a new SPIR-V block, Emits the label of the new block, and stores which /// block we are currently generating. /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to @@ -767,8 +783,8 @@ pub const DeclGen = struct { } fn genDecl(self: *DeclGen) !void { - const result_id = self.ids.get(self.decl_index).?; const decl = self.module.declPtr(self.decl_index); + const result_id = try self.resolveDecl(self.decl_index); if (decl.val.castTag(.function)) |_| { assert(decl.ty.zigTypeTag() == .Fn); diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 561775a757..c63e4a20af 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -44,34 +44,25 @@ const IdResult = spec.IdResult; base: link.File, -/// This linker backend does not try to incrementally link output SPIR-V code. -/// Instead, it tracks all declarations in this table, and iterates over it -/// in the flush function. -decl_table: std.AutoArrayHashMapUnmanaged(Module.Decl.Index, DeclGenContext) = .{}, - -const DeclGenContext = struct { - air: Air, - air_arena: ArenaAllocator.State, - liveness: Liveness, - - fn deinit(self: *DeclGenContext, gpa: Allocator) void { - self.air.deinit(gpa); - self.liveness.deinit(gpa); - self.air_arena.promote(gpa).deinit(); - self.* = undefined; - } -}; +spv: SpvModule, +spv_arena: ArenaAllocator, +decl_ids: codegen.DeclMap, pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV { - const spirv = try gpa.create(SpirV); - spirv.* = .{ + const self = try gpa.create(SpirV); + self.* = .{ .base = .{ .tag = .spirv, .options = options, .file = null, .allocator = gpa, }, + .spv = undefined, + .spv_arena = ArenaAllocator.init(gpa), + .decl_ids = codegen.DeclMap.init(self.base.allocator), }; + self.spv = SpvModule.init(gpa, self.spv_arena.allocator()); + errdefer self.deinit(); // TODO: Figure out where to put all of these switch (options.target.cpu.arch) { @@ -88,7 +79,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV { return error.TODOAbiNotSupported; } - return spirv; + return self; } pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*SpirV { @@ -107,44 +98,35 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option } pub fn deinit(self: *SpirV) void { - self.decl_table.deinit(self.base.allocator); + self.spv.deinit(); + self.spv_arena.deinit(); + self.decl_ids.deinit(); } pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void { if (build_options.skip_non_native) { @panic("Attempted to compile for architecture that was disabled by build configuration"); } - _ = module; - // Keep track of all decls so we can iterate over them on flush(). - const result = try self.decl_table.getOrPut(self.base.allocator, func.owner_decl); - if (result.found_existing) { - result.value_ptr.deinit(self.base.allocator); + var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_ids); + defer decl_gen.deinit(); + + if (try decl_gen.gen(func.owner_decl, air, liveness)) |msg| { + try module.failed_decls.put(module.gpa, func.owner_decl, msg); } - - var arena = ArenaAllocator.init(self.base.allocator); - errdefer arena.deinit(); - - var new_air = try cloneAir(air, self.base.allocator, arena.allocator()); - errdefer new_air.deinit(self.base.allocator); - - var new_liveness = try cloneLiveness(liveness, self.base.allocator); - errdefer new_liveness.deinit(self.base.allocator); - - result.value_ptr.* = .{ - .air = new_air, - .air_arena = arena.state, - .liveness = new_liveness, - }; } pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index) !void { if (build_options.skip_non_native) { @panic("Attempted to compile for architecture that was disabled by build configuration"); } - _ = module; - // Keep track of all decls so we can iterate over them on flush(). - _ = try self.decl_table.getOrPut(self.base.allocator, decl_index); + + var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_ids); + defer decl_gen.deinit(); + + if (try decl_gen.gen(decl_index, undefined, undefined)) |msg| { + try module.failed_decls.put(module.gpa, decl_index, msg); + } } pub fn updateDeclExports( @@ -160,13 +142,8 @@ pub fn updateDeclExports( } pub fn freeDecl(self: *SpirV, decl_index: Module.Decl.Index) void { - if (self.decl_table.getIndex(decl_index)) |index| { - const module = self.base.options.module.?; - const decl = module.declPtr(decl_index); - if (decl.val.tag() == .function) { - self.decl_table.values()[index].deinit(self.base.allocator); - } - } + _ = self; + _ = decl_index; } pub fn flush(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void { @@ -189,56 +166,11 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No sub_prog_node.activate(); defer sub_prog_node.end(); - const module = self.base.options.module.?; const target = comp.getTarget(); + try writeCapabilities(&self.spv, target); + try writeMemoryModel(&self.spv, target); - var arena = std.heap.ArenaAllocator.init(self.base.allocator); - defer arena.deinit(); - - var spv = SpvModule.init(self.base.allocator, arena.allocator()); - defer spv.deinit(); - - // Allocate an ID for every declaration before generating code, - // so that we can access them before processing them. - // TODO: We're allocating an ID unconditionally now, are there - // declarations which don't generate a result? - var ids = std.AutoHashMap(Module.Decl.Index, IdResult).init(self.base.allocator); - defer ids.deinit(); - try ids.ensureTotalCapacity(@intCast(u32, self.decl_table.count())); - - for (self.decl_table.keys()) |decl_index| { - const decl = module.declPtr(decl_index); - if (decl.has_tv) { - ids.putAssumeCapacityNoClobber(decl_index, spv.allocId()); - } - } - - // Now, actually generate the code for all declarations. - var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &spv, &ids); - defer decl_gen.deinit(); - - var it = self.decl_table.iterator(); - while (it.next()) |entry| { - const decl_index = entry.key_ptr.*; - const decl = module.declPtr(decl_index); - if (!decl.has_tv) continue; - - const air = entry.value_ptr.air; - const liveness = entry.value_ptr.liveness; - - log.debug("generating code for {s}", .{decl.name}); - - // Note, if `decl` is not a function, air/liveness may be undefined. - if (try decl_gen.gen(decl_index, air, liveness)) |msg| { - try module.failed_decls.put(module.gpa, decl_index, msg); - return; // TODO: Attempt to generate more decls? - } - } - - try writeCapabilities(&spv, target); - try writeMemoryModel(&spv, target); - - try spv.flush(self.base.file.?); + try self.spv.flush(self.base.file.?); } fn writeCapabilities(spv: *SpvModule, target: std.Target) !void { @@ -281,45 +213,3 @@ fn writeMemoryModel(spv: *SpvModule, target: std.Target) !void { .memory_model = memory_model, }); } - -fn cloneLiveness(l: Liveness, gpa: Allocator) !Liveness { - const tomb_bits = try gpa.dupe(usize, l.tomb_bits); - errdefer gpa.free(tomb_bits); - - const extra = try gpa.dupe(u32, l.extra); - errdefer gpa.free(extra); - - return Liveness{ - .tomb_bits = tomb_bits, - .extra = extra, - .special = try l.special.clone(gpa), - }; -} - -fn cloneAir(air: Air, gpa: Allocator, air_arena: Allocator) !Air { - const values = try gpa.alloc(Value, air.values.len); - errdefer gpa.free(values); - - for (values, 0..) |*value, i| { - value.* = try air.values[i].copy(air_arena); - } - - var instructions = try air.instructions.toMultiArrayList().clone(gpa); - errdefer instructions.deinit(gpa); - - const air_tags = instructions.items(.tag); - const air_datas = instructions.items(.data); - - for (air_tags, 0..) |tag, i| { - switch (tag) { - .alloc, .ret_ptr, .const_ty => air_datas[i].ty = try air_datas[i].ty.copy(air_arena), - else => {}, - } - } - - return Air{ - .instructions = instructions.slice(), - .extra = try gpa.dupe(u32, air.extra), - .values = values, - }; -} From fbe5f0c3459484babcf3d4ba6fe4901612a409bb Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 3 Dec 2022 00:13:25 +0100 Subject: [PATCH 30/51] spirv: initial decl_ref pointer generation Starts lowering decl_ref Pointer constants. --- src/codegen/spirv.zig | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index f44e894a29..d96377a29c 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -373,15 +373,16 @@ pub const DeclGen = struct { const ty = self.spv.typeRefType(ty_ref); const ty_id = self.typeId(ty_ref); - const literal: spec.LiteralContextDependentNumber = switch (ty.intSignedness()) { + const Lit = spec.LiteralContextDependentNumber; + const literal = switch (ty.intSignedness()) { .signed => switch (ty.intFloatBits()) { - 1...32 => .{ .int32 = @intCast(i32, value) }, - 33...64 => .{ .int64 = @intCast(i64, value) }, + 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 => .{ .uint32 = @intCast(u32, value) }, - 33...64 => .{ .uint64 = @intCast(u64, value) }, + 1...32 => Lit{ .uint32 = @intCast(u32, value) }, + 33...64 => Lit{ .uint64 = @intCast(u64, value) }, else => unreachable, }, }; @@ -552,6 +553,19 @@ pub const DeclGen = struct { .constituents = constituents, }); }, + .Pointer => switch (val.tag()) { + .decl_ref => { + const decl_index = val.castTag(.decl_ref).?.data; + const decl_result_id = self.spv.allocId(); + try self.genDeclRef(decl_result_id, decl_index); + try section.emit(self.spv.gpa, .OpVariable, .{ + .id_result_type = result_ty_id, + .id_result = result_id, + .storage_class = spirvStorageClass(ty.ptrAddressSpace()), + }); + }, + else => return self.todo("constant pointer of value type {s}", .{@tagName(val.tag())}), + }, .Fn => switch (repr) { .direct => unreachable, .indirect => return self.todo("function pointers", .{}), @@ -561,6 +575,12 @@ pub const DeclGen = struct { } } + fn genDeclRef(self: *DeclGen, result_id: IdRef, decl_index: Decl.Index) Error!void { + const decl = self.module.declPtr(decl_index); + self.module.markDeclAlive(decl); + try self.genConstant(result_id, decl.ty, decl.val, .indirect); + } + /// 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); From 3c7f93aa69e495820448a17869bd4663ed396ff2 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 10 Dec 2022 01:25:28 +0100 Subject: [PATCH 31/51] spirv: generic global pointers Similar to function locals, taking the address of a global that does not have an explicit address space assigned to it should result in a generic pointer, not a global pointer. Also similar to function locals, they cannot be generated into the generic storage class, and so are generated into the global storage class and then cast to a generic pointer, using OpSpecConstantOp. Note that using OpSpecConstantOp results is only allowed by a hand full of other OpSpecConstant instructions - which is why we generate constant structs using OpSpecConstantComposite: These may use OpVariable and OpSpecConstantOp results, while OpConstantComposite may not. --- src/codegen/spirv.zig | 113 +++++++++++++++++++++-------------- src/codegen/spirv/Module.zig | 9 ++- 2 files changed, 75 insertions(+), 47 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index d96377a29c..1e50645c91 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -452,7 +452,7 @@ pub const DeclGen = struct { constituents[i] = self.spv.allocId(); try self.genConstant(constituents[i], elem_ty, elem_val, repr); } - try section.emit(self.spv.gpa, .OpConstantComposite, .{ + try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ .id_result_type = result_ty_id, .id_result = result_id, .constituents = constituents, @@ -474,7 +474,7 @@ pub const DeclGen = struct { constituents[len] = self.spv.allocId(); try self.genConstant(constituents[len], elem_ty, sentinel, repr); } - try section.emit(self.spv.gpa, .OpConstantComposite, .{ + try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ .id_result_type = result_ty_id, .id_result = result_id, .constituents = constituents, @@ -494,7 +494,7 @@ pub const DeclGen = struct { elem.* = self.spv.allocId(); try self.genConstant(elem.*, elem_ty, elem_vals[i], repr); } - try section.emit(self.spv.gpa, .OpConstantComposite, .{ + try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ .id_result_type = result_ty_id, .id_result = result_id, .constituents = elem_refs, @@ -547,7 +547,7 @@ pub const DeclGen = struct { }; defer self.spv.gpa.free(constituents); - try section.emit(self.spv.gpa, .OpConstantComposite, .{ + try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ .id_result_type = result_ty_id, .id_result = result_id, .constituents = constituents, @@ -558,11 +558,7 @@ pub const DeclGen = struct { const decl_index = val.castTag(.decl_ref).?.data; const decl_result_id = self.spv.allocId(); try self.genDeclRef(decl_result_id, decl_index); - try section.emit(self.spv.gpa, .OpVariable, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - .storage_class = spirvStorageClass(ty.ptrAddressSpace()), - }); + try self.variable(.global, result_id, result_ty_ref, decl_result_id); }, else => return self.todo("constant pointer of value type {s}", .{@tagName(val.tag())}), }, @@ -1490,48 +1486,73 @@ pub const DeclGen = struct { return try self.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index); } + fn variable( + self: *DeclGen, + comptime context: enum { function, global }, + result_id: IdRef, + ptr_ty_ref: SpvType.Ref, + initializer: ?IdRef, + ) !void { + const storage_class = self.spv.typeRefType(ptr_ty_ref).payload(.pointer).storage_class; + const actual_storage_class = switch (storage_class) { + .Generic => switch (context) { + .function => .Function, + .global => .CrossWorkgroup, + }, + else => storage_class, + }; + const actual_ptr_ty_ref = switch (storage_class) { + .Generic => try self.spv.changePtrStorageClass(ptr_ty_ref, actual_storage_class), + else => ptr_ty_ref, + }; + const alloc_result_id = switch (storage_class) { + .Generic => self.spv.allocId(), + else => result_id, + }; + + const section = switch (actual_storage_class) { + .Generic => unreachable, + // 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. + .Function => &self.func.prologue, + else => &self.spv.sections.types_globals_constants, + }; + try section.emit(self.spv.gpa, .OpVariable, .{ + .id_result_type = self.typeId(actual_ptr_ty_ref), + .id_result = alloc_result_id, + .storage_class = actual_storage_class, + .initializer = initializer, + }); + + if (storage_class != .Generic) { + return; + } + + // Now we need to convert the pointer. + // If this is a function local, we need to perform the conversion at runtime. Otherwise, we can do + // it ahead of time using OpSpecConstantOp. + switch (actual_storage_class) { + .Function => try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{ + .id_result_type = self.typeId(ptr_ty_ref), + .id_result = result_id, + .pointer = alloc_result_id, + }), + else => { + try section.emitRaw(self.spv.gpa, .OpSpecConstantOp, 3 + 1); + section.writeOperand(IdRef, self.typeId(ptr_ty_ref)); + section.writeOperand(IdRef, result_id); + section.writeOperand(Opcode, .OpPtrCastToGeneric); + section.writeOperand(IdRef, alloc_result_id); + }, + } + } + fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { if (self.liveness.isUnused(inst)) return null; const ty = self.air.typeOfIndex(inst); const result_ty_ref = try self.resolveType(ty, .direct); - const result_ty_id = self.typeId(result_ty_ref); const result_id = self.spv.allocId(); - - const storage_class = spirvStorageClass(ty.ptrAddressSpace()); - - const ptr_ty_id = switch (storage_class) { - .Generic => blk: { - const payload = try self.spv.arena.create(SpvType.Payload.Pointer); - payload.* = self.spv.typeRefType(result_ty_ref).payload(.pointer).*; - payload.storage_class = .Function; - break :blk try self.spv.resolveTypeId(SpvType.initPayload(&payload.base)); - }, - else => result_ty_id, - }; - const actual_storage_class = switch (storage_class) { - .Generic, .Function => .Function, - else => storage_class, - }; - const section = switch (storage_class) { - // 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. - .Generic, .Function => &self.func.prologue, - else => &self.spv.sections.types_globals_constants, - }; - try section.emit(self.spv.gpa, .OpVariable, .{ - .id_result_type = ptr_ty_id, - .id_result = result_id, - .storage_class = actual_storage_class, - }); - if (storage_class == .Generic) { - const casted_result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{ - .id_result_type = result_ty_id, - .id_result = casted_result_id, - .pointer = result_id, - }); - return casted_result_id; - } + try self.variable(.function, result_id, result_ty_ref, null); return result_id; } diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 803276d78c..ff7ae29993 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -556,9 +556,16 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct } } +pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_class: spec.StorageClass) !Type.Ref { + const payload = try self.arena.create(Type.Payload.Pointer); + payload.* = self.typeRefType(ptr_ty_ref).payload(.pointer).*; + payload.storage_class = new_storage_class; + return try self.resolveType(Type.initPayload(&payload.base)); +} + pub fn emitConstant( self: *Module, - ty_id: spec.IdRef, + ty_id: IdRef, result_id: IdRef, value: spec.LiteralContextDependentNumber, ) !void { From 27833004dbf066158ddf9574acbdef5c695941fa Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 10 Dec 2022 01:44:05 +0100 Subject: [PATCH 32/51] spirv: optional types Implements lowering optional types in the SPIR-V backend. --- src/codegen/spirv.zig | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 1e50645c91..f81a248f63 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -776,6 +776,28 @@ pub const DeclGen = struct { }; 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. + return try self.resolveType(Type.initTag(.bool), repr); + } + + const payload_ty_ref = try self.resolveType(payload_ty, .indirect); + if (ty.optionalReprIsPayload()) { + // Optional is actually a pointer. + return payload_ty_ref; + } + + const bool_ty_ref = try self.resolveType(Type.initTag(.bool), .indirect); + + // its an actual optional + return try self.simpleStructType(&.{ + .{ .ty = payload_ty_ref, .name = "payload" }, + .{ .ty = bool_ty_ref, .name = "valid" }, + }); + }, .Null, .Undefined, .EnumLiteral, From e788dfa142f27717df1258009cadb61f314610dd Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 10 Dec 2022 14:07:52 +0100 Subject: [PATCH 33/51] spirv: string literals Implements lowering string literal constants in the SPIR-V backend --- src/codegen/spirv.zig | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index f81a248f63..7df4360c41 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -461,8 +461,9 @@ pub const DeclGen = struct { .repeated => { const elem_val = val.castTag(.repeated).?.data; const elem_ty = ty.elemType(); - const len = @intCast(u32, ty.arrayLenIncludingSentinel()); // TODO: limit spir-v to 32 bit arrays in a more elegant way. - const constituents = try self.spv.gpa.alloc(IdRef, len); + const len = @intCast(u32, ty.arrayLen()); + const total_len = @intCast(u32, ty.arrayLenIncludingSentinel()); // TODO: limit spir-v to 32 bit arrays in a more elegant way. + const constituents = try self.spv.gpa.alloc(IdRef, total_len); defer self.spv.gpa.free(constituents); const elem_val_id = self.spv.allocId(); @@ -480,6 +481,31 @@ pub const DeclGen = struct { .constituents = constituents, }); }, + .str_lit => { + // TODO: This is very efficient code generation, should probably implement constant caching for this. + const str_lit = val.castTag(.str_lit).?.data; + const bytes = self.module.string_literal_bytes.items[str_lit.index..][0..str_lit.len]; + const elem_ty = ty.elemType(); + const elem_ty_id = try self.resolveTypeId(elem_ty); + const len = @intCast(u32, ty.arrayLen()); + const total_len = @intCast(u32, ty.arrayLenIncludingSentinel()); + const constituents = try self.spv.gpa.alloc(IdRef, total_len); + defer self.spv.gpa.free(constituents); + for (bytes) |byte, i| { + constituents[i] = self.spv.allocId(); + try self.spv.emitConstant(elem_ty_id, constituents[i], .{ .uint32 = byte }); + } + if (ty.sentinel()) |sentinel| { + constituents[len] = self.spv.allocId(); + const byte = @intCast(u8, sentinel.toUnsignedInt(target)); + try self.spv.emitConstant(elem_ty_id, constituents[len], .{ .uint32 = byte }); + } + try section.emit(self.spv.gpa, .OpConstantComposite, .{ + .id_result_type = result_ty_id, + .id_result = result_id, + .constituents = constituents, + }); + }, else => return self.todo("array constant with tag {s}", .{@tagName(val.tag())}), }, .Vector => switch (val.tag()) { From 12a3a8b100b7af4110cdc30de4def7d8f8d8c148 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 11 Dec 2022 13:39:29 +0100 Subject: [PATCH 34/51] spirv: slice constants Implements lowering slice constants in the SPIR-V backend --- src/codegen/spirv.zig | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 7df4360c41..fb5cdbd126 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -586,7 +586,23 @@ pub const DeclGen = struct { try self.genDeclRef(decl_result_id, decl_index); try self.variable(.global, result_id, result_ty_ref, decl_result_id); }, - else => return self.todo("constant pointer of value type {s}", .{@tagName(val.tag())}), + .slice => { + const slice = val.castTag(.slice).?.data; + var buf: Type.SlicePtrFieldTypeBuffer = undefined; + + const ptr_id = self.spv.allocId(); + try self.genConstant(ptr_id, ty.slicePtrFieldType(&buf), slice.ptr, .indirect); + const len_id = self.spv.allocId(); + try self.genConstant(len_id, Type.usize, slice.len, .indirect); + + const constituents = [_]IdRef{ ptr_id, len_id }; + try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ + .id_result_type = result_ty_id, + .id_result = result_id, + .constituents = &constituents, + }); + }, + else => return self.todo("pointer of value type {s}", .{@tagName(val.tag())}), }, .Fn => switch (repr) { .direct => unreachable, From 0c53fea42cea8543733612c7c63a6982ca6e59e6 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 11 Dec 2022 14:08:51 +0100 Subject: [PATCH 35/51] spirv: improve genConstant usage This little wrapper function allocates a result-id for us, so that we don't have to do that. --- src/codegen/spirv.zig | 65 +++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index fb5cdbd126..1980d82925 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -238,9 +238,7 @@ pub const DeclGen = struct { return try self.resolveDecl(fn_decl_index); } - const result_id = self.spv.allocId(); - try self.genConstant(result_id, ty, val, .direct); - return result_id; + return try self.genConstant(ty, val, .direct); } const index = Air.refToIndex(inst).?; return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage. @@ -396,9 +394,15 @@ pub const DeclGen = struct { return result_id; } + fn genConstant(self: *DeclGen, ty: Type, val: Value, repr: Repr) Error!IdRef { + const result_id = self.spv.allocId(); + try self.genConstantForId(result_id, ty, val, repr); + return result_id; + } + /// Generate a constant representing `val`. /// TODO: Deduplication? - fn genConstant(self: *DeclGen, result_id: IdRef, ty: Type, val: Value, repr: Repr) Error!void { + fn genConstantForId(self: *DeclGen, result_id: IdRef, ty: Type, val: Value, repr: Repr) Error!void { const target = self.getTarget(); const section = &self.spv.sections.types_globals_constants; const result_ty_ref = try self.resolveType(ty, repr); @@ -449,8 +453,7 @@ pub const DeclGen = struct { const constituents = try self.spv.gpa.alloc(IdRef, len); defer self.spv.gpa.free(constituents); for (elem_vals[0..len], 0..) |elem_val, i| { - constituents[i] = self.spv.allocId(); - try self.genConstant(constituents[i], elem_ty, elem_val, repr); + constituents[i] = try self.genConstant(elem_ty, elem_val, repr); } try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ .id_result_type = result_ty_id, @@ -466,14 +469,12 @@ pub const DeclGen = struct { const constituents = try self.spv.gpa.alloc(IdRef, total_len); defer self.spv.gpa.free(constituents); - const elem_val_id = self.spv.allocId(); - try self.genConstant(elem_val_id, elem_ty, elem_val, repr); + const elem_val_id = try self.genConstant(elem_ty, elem_val, repr); for (constituents[0..len]) |*elem| { elem.* = elem_val_id; } if (ty.sentinel()) |sentinel| { - constituents[len] = self.spv.allocId(); - try self.genConstant(constituents[len], elem_ty, sentinel, repr); + constituents[len] = try self.genConstant(elem_ty, sentinel, repr); } try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ .id_result_type = result_ty_id, @@ -517,8 +518,7 @@ pub const DeclGen = struct { const elem_refs = try self.gpa.alloc(IdRef, vector_len); defer self.gpa.free(elem_refs); for (elem_refs, 0..) |*elem, i| { - elem.* = self.spv.allocId(); - try self.genConstant(elem.*, elem_ty, elem_vals[i], repr); + elem.* = try self.genConstant(elem_ty, elem_vals[i], repr); } try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ .id_result_type = result_ty_id, @@ -539,17 +539,15 @@ pub const DeclGen = struct { const constituents = try self.spv.gpa.alloc(IdRef, tuple.types.len); errdefer self.spv.gpa.free(constituents); - var member_index: usize = 0; + var member_i: 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; - const member_id = self.spv.allocId(); - try self.genConstant(member_id, field_ty, field_val, repr); - constituents[member_index] = member_id; - member_index += 1; + constituents[member_i] = try self.genConstant(field_ty, field_val, repr); + member_i += 1; } - break :blk constituents[0..member_index]; + break :blk constituents[0..member_i]; } else blk: { const struct_ty = ty.castTag(.@"struct").?.data; @@ -560,16 +558,14 @@ pub const DeclGen = struct { const field_vals = val.castTag(.aggregate).?.data; const constituents = try self.spv.gpa.alloc(IdRef, struct_ty.fields.count()); errdefer self.spv.gpa.free(constituents); - var member_index: usize = 0; + var member_i: usize = 0; for (struct_ty.fields.values(), 0..) |field, i| { if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; - const member_id = self.spv.allocId(); - try self.genConstant(member_id, field.ty, field_vals[i], repr); - constituents[member_index] = member_id; - member_index += 1; + constituents[member_i] = try self.genConstant(field.ty, field_vals[i], repr); + member_i += 1; } - break :blk constituents[0..member_index]; + break :blk constituents[0..member_i]; }; defer self.spv.gpa.free(constituents); @@ -580,20 +576,14 @@ pub const DeclGen = struct { }); }, .Pointer => switch (val.tag()) { - .decl_ref => { - const decl_index = val.castTag(.decl_ref).?.data; - const decl_result_id = self.spv.allocId(); - try self.genDeclRef(decl_result_id, decl_index); - try self.variable(.global, result_id, result_ty_ref, decl_result_id); - }, + .decl_ref_mut => try self.genDeclRef(result_ty_ref, result_id, val.castTag(.decl_ref_mut).?.data.decl_index), + .decl_ref => try self.genDeclRef(result_ty_ref, result_id, val.castTag(.decl_ref).?.data), .slice => { const slice = val.castTag(.slice).?.data; var buf: Type.SlicePtrFieldTypeBuffer = undefined; - const ptr_id = self.spv.allocId(); - try self.genConstant(ptr_id, ty.slicePtrFieldType(&buf), slice.ptr, .indirect); - const len_id = self.spv.allocId(); - try self.genConstant(len_id, Type.usize, slice.len, .indirect); + const ptr_id = try self.genConstant(ty.slicePtrFieldType(&buf), slice.ptr, .indirect); + const len_id = try self.genConstant(Type.usize, slice.len, .indirect); const constituents = [_]IdRef{ ptr_id, len_id }; try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ @@ -613,10 +603,11 @@ pub const DeclGen = struct { } } - fn genDeclRef(self: *DeclGen, result_id: IdRef, decl_index: Decl.Index) Error!void { + fn genDeclRef(self: *DeclGen, result_ty_ref: SpvType.Ref, result_id: IdRef, decl_index: Decl.Index) Error!void { const decl = self.module.declPtr(decl_index); self.module.markDeclAlive(decl); - try self.genConstant(result_id, decl.ty, decl.val, .indirect); + const decl_id = try self.genConstant(decl.ty, decl.val, .indirect); + try self.variable(.global, result_id, result_ty_ref, decl_id); } /// Turn a Zig type into a SPIR-V Type, and return its type result-id. @@ -915,7 +906,7 @@ pub const DeclGen = struct { .name = fqn, }); } else { - try self.genConstant(result_id, decl.ty, decl.val, .direct); + try self.genConstantForId(result_id, decl.ty, decl.val, .direct); } } From 75abe3b43bf28dc1cd1ab06cc9403d4fa93e7002 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 11 Dec 2022 14:40:18 +0100 Subject: [PATCH 36/51] spirv: optional constants Implements lowering optional constants in the SPIR-V backend. --- src/codegen/spirv.zig | 79 ++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 19 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 1980d82925..fff872d359 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -238,7 +238,7 @@ pub const DeclGen = struct { return try self.resolveDecl(fn_decl_index); } - return try self.genConstant(ty, val, .direct); + return try self.constant(ty, val, .direct); } const index = Air.refToIndex(inst).?; return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage. @@ -394,15 +394,15 @@ pub const DeclGen = struct { return result_id; } - fn genConstant(self: *DeclGen, ty: Type, val: Value, repr: Repr) Error!IdRef { + fn constant(self: *DeclGen, ty: Type, val: Value, repr: Repr) Error!IdRef { const result_id = self.spv.allocId(); - try self.genConstantForId(result_id, ty, val, repr); + try self.genConstant(result_id, ty, val, repr); return result_id; } /// Generate a constant representing `val`. /// TODO: Deduplication? - fn genConstantForId(self: *DeclGen, result_id: IdRef, ty: Type, val: Value, repr: Repr) Error!void { + fn genConstant(self: *DeclGen, result_id: IdRef, ty: Type, val: Value, repr: Repr) Error!void { const target = self.getTarget(); const section = &self.spv.sections.types_globals_constants; const result_ty_ref = try self.resolveType(ty, repr); @@ -453,7 +453,7 @@ pub const DeclGen = struct { const constituents = try self.spv.gpa.alloc(IdRef, len); defer self.spv.gpa.free(constituents); for (elem_vals[0..len], 0..) |elem_val, i| { - constituents[i] = try self.genConstant(elem_ty, elem_val, repr); + constituents[i] = try self.constant(elem_ty, elem_val, repr); } try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ .id_result_type = result_ty_id, @@ -469,12 +469,12 @@ pub const DeclGen = struct { const constituents = try self.spv.gpa.alloc(IdRef, total_len); defer self.spv.gpa.free(constituents); - const elem_val_id = try self.genConstant(elem_ty, elem_val, repr); + const elem_val_id = try self.constant(elem_ty, elem_val, repr); for (constituents[0..len]) |*elem| { elem.* = elem_val_id; } if (ty.sentinel()) |sentinel| { - constituents[len] = try self.genConstant(elem_ty, sentinel, repr); + constituents[len] = try self.constant(elem_ty, sentinel, repr); } try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ .id_result_type = result_ty_id, @@ -492,7 +492,7 @@ pub const DeclGen = struct { const total_len = @intCast(u32, ty.arrayLenIncludingSentinel()); const constituents = try self.spv.gpa.alloc(IdRef, total_len); defer self.spv.gpa.free(constituents); - for (bytes) |byte, i| { + for (bytes, 0..) |byte, i| { constituents[i] = self.spv.allocId(); try self.spv.emitConstant(elem_ty_id, constituents[i], .{ .uint32 = byte }); } @@ -518,7 +518,7 @@ pub const DeclGen = struct { const elem_refs = try self.gpa.alloc(IdRef, vector_len); defer self.gpa.free(elem_refs); for (elem_refs, 0..) |*elem, i| { - elem.* = try self.genConstant(elem_ty, elem_vals[i], repr); + elem.* = try self.constant(elem_ty, elem_vals[i], repr); } try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ .id_result_type = result_ty_id, @@ -543,7 +543,7 @@ pub const DeclGen = struct { for (tuple.types, 0..) |field_ty, i| { const field_val = tuple.values[i]; if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue; - constituents[member_i] = try self.genConstant(field_ty, field_val, repr); + constituents[member_i] = try self.constant(field_ty, field_val, repr); member_i += 1; } @@ -561,7 +561,7 @@ pub const DeclGen = struct { var member_i: usize = 0; for (struct_ty.fields.values(), 0..) |field, i| { if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; - constituents[member_i] = try self.genConstant(field.ty, field_vals[i], repr); + constituents[member_i] = try self.constant(field.ty, field_vals[i], repr); member_i += 1; } @@ -582,8 +582,8 @@ pub const DeclGen = struct { const slice = val.castTag(.slice).?.data; var buf: Type.SlicePtrFieldTypeBuffer = undefined; - const ptr_id = try self.genConstant(ty.slicePtrFieldType(&buf), slice.ptr, .indirect); - const len_id = try self.genConstant(Type.usize, slice.len, .indirect); + const ptr_id = try self.constant(ty.slicePtrFieldType(&buf), slice.ptr, .indirect); + const len_id = try self.constant(Type.usize, slice.len, .indirect); const constituents = [_]IdRef{ ptr_id, len_id }; try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ @@ -594,6 +594,45 @@ pub const DeclGen = struct { }, else => return self.todo("pointer of value type {s}", .{@tagName(val.tag())}), }, + .Optional => { + var buf: Type.Payload.ElemType = undefined; + const payload_ty = ty.optionalChild(&buf); + + const has_payload = !val.isNull(); + + // Note: keep in sync with the resolveType implementation for optionals. + if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { + // Just a bool. Note: always in indirect representation. + try self.genConstInt(result_ty_ref, result_id, @boolToInt(has_payload)); + } else if (ty.optionalReprIsPayload()) { + // A nullable pointer. + if (val.castTag(.opt_payload)) |payload| { + try self.genConstant(result_id, payload_ty, payload.data, repr); + } else if (has_payload) { + try self.genConstant(result_id, payload_ty, val, repr); + } else { + try section.emit(self.spv.gpa, .OpConstantNull, .{ + .id_result_type = result_ty_id, + .id_result = result_id, + }); + } + return; + } + + // Struct-and-field pair. + // Note: If this optional has no payload, we initialize the the data member with OpUndef. + const bool_ty_ref = try self.resolveType(Type.bool, .indirect); + const valid_id = try self.constInt(bool_ty_ref, @boolToInt(has_payload)); + const payload_val = if (val.castTag(.opt_payload)) |pl| pl.data else Value.undef; + const payload_id = try self.constant(payload_ty, payload_val, .indirect); + + const constituents = [_]IdRef{ payload_id, valid_id }; + try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ + .id_result_type = result_ty_id, + .id_result = result_id, + .constituents = &constituents, + }); + }, .Fn => switch (repr) { .direct => unreachable, .indirect => return self.todo("function pointers", .{}), @@ -606,7 +645,7 @@ pub const DeclGen = struct { fn genDeclRef(self: *DeclGen, result_ty_ref: SpvType.Ref, result_id: IdRef, decl_index: Decl.Index) Error!void { const decl = self.module.declPtr(decl_index); self.module.markDeclAlive(decl); - const decl_id = try self.genConstant(decl.ty, decl.val, .indirect); + const decl_id = try self.constant(decl.ty, decl.val, .indirect); try self.variable(.global, result_id, result_ty_ref, decl_id); } @@ -814,7 +853,9 @@ pub const DeclGen = struct { const payload_ty = ty.optionalChild(&buf); if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { // Just use a bool. - return try self.resolveType(Type.initTag(.bool), repr); + // 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); @@ -823,7 +864,7 @@ pub const DeclGen = struct { return payload_ty_ref; } - const bool_ty_ref = try self.resolveType(Type.initTag(.bool), .indirect); + const bool_ty_ref = try self.resolveType(Type.bool, .indirect); // its an actual optional return try self.simpleStructType(&.{ @@ -906,7 +947,7 @@ pub const DeclGen = struct { .name = fqn, }); } else { - try self.genConstantForId(result_id, decl.ty, decl.val, .direct); + try self.genConstant(result_id, decl.ty, decl.val, .direct); } } @@ -1256,7 +1297,7 @@ pub const DeclGen = struct { var lhs_id = try self.resolve(bin_op.lhs); var rhs_id = try self.resolve(bin_op.rhs); const result_id = self.spv.allocId(); - const result_type_id = try self.resolveTypeId(Type.initTag(.bool)); + const result_type_id = try self.resolveTypeId(Type.bool); const op_ty = self.air.typeOf(bin_op.lhs); assert(op_ty.eql(self.air.typeOf(bin_op.rhs), self.module)); @@ -1350,7 +1391,7 @@ pub const DeclGen = struct { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const operand_id = try self.resolve(ty_op.operand); const result_id = self.spv.allocId(); - const result_type_id = try self.resolveTypeId(Type.initTag(.bool)); + const result_type_id = try self.resolveTypeId(Type.bool); try self.func.body.emit(self.spv.gpa, .OpLogicalNot, .{ .id_result_type = result_type_id, .id_result = result_id, From 764f19034d9aa74ce2220937d090c60f8f8bf919 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 26 Mar 2023 18:56:49 +0200 Subject: [PATCH 37/51] spirv: union types/constants Implements lowering union types and constants in the SPIR-V backend. --- src/codegen/spirv.zig | 177 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 165 insertions(+), 12 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index fff872d359..080f2f645b 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -394,6 +394,16 @@ pub const DeclGen = struct { return result_id; } + fn genUndef(self: *DeclGen, ty_ref: SpvType.Ref) Error!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 constant(self: *DeclGen, ty: Type, val: Value, repr: Repr) Error!IdRef { const result_id = self.spv.allocId(); try self.genConstant(result_id, ty, val, repr); @@ -543,7 +553,7 @@ pub const DeclGen = struct { for (tuple.types, 0..) |field_ty, i| { const field_val = tuple.values[i]; if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue; - constituents[member_i] = try self.constant(field_ty, field_val, repr); + constituents[member_i] = try self.constant(field_ty, field_val, .indirect); member_i += 1; } @@ -561,7 +571,7 @@ pub const DeclGen = struct { var member_i: usize = 0; for (struct_ty.fields.values(), 0..) |field, i| { if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; - constituents[member_i] = try self.constant(field.ty, field_vals[i], repr); + constituents[member_i] = try self.constant(field.ty, field_vals[i], .indirect); member_i += 1; } @@ -633,6 +643,67 @@ pub const DeclGen = struct { .constituents = &constituents, }); }, + .Union => { + const tag_and_val = val.castTag(.@"union").?.data; + const layout = ty.unionGetLayout(target); + + if (layout.payload_size == 0) { + return try self.genConstant(result_id, ty.unionTagTypeSafety().?, tag_and_val.tag, .indirect); + } + + const union_ty = ty.cast(Type.Payload.Union).?.data; + if (union_ty.layout == .Packed) { + return self.todo("packed union constants", .{}); + } + + const active_field = ty.unionTagFieldIndex(tag_and_val.tag, self.module).?; + const union_ty_ref = try self.resolveUnionType(ty, active_field); + const active_field_ty = union_ty.fields.values()[active_field].ty; + + const tag_first = layout.tag_align >= layout.payload_align; + const u8_ty_ref = try self.intType(.unsigned, 8); + + const tag = if (layout.tag_size != 0) + try self.constant(ty.unionTagTypeSafety().?, tag_and_val.tag, .indirect) + else + null; + + var members = std.BoundedArray(IdRef, 4){}; + + if (tag_first) { + if (tag) |id| members.appendAssumeCapacity(id); + } + + const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: { + const payload = try self.constant(active_field_ty, tag_and_val.val, .indirect); + members.appendAssumeCapacity(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.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref); + members.appendAssumeCapacity(try self.genUndef(payload_padding_ty_ref)); + } + + if (!tag_first) { + if (tag) |id| members.appendAssumeCapacity(id); + } + + if (layout.padding != 0) { + const padding_ty_ref = try self.arrayType(layout.padding, u8_ty_ref); + members.appendAssumeCapacity(try self.genUndef(padding_ty_ref)); + } + + try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ + .id_result_type = self.typeId(union_ty_ref), + .id_result = result_id, + .constituents = members.slice(), + }); + + // TODO: Cast to general union type? Required for pointers only or something? + }, + .Fn => switch (repr) { .direct => unreachable, .indirect => return self.todo("function pointers", .{}), @@ -691,6 +762,91 @@ pub const DeclGen = struct { return self.typeId(type_ref); } + /// Construct an array type which has 'len' elements of 'type' + fn arrayType(self: *DeclGen, len: u32, ty: SpvType.Ref) !SpvType.Ref { + const payload = try self.spv.arena.create(SpvType.Payload.Array); + payload.* = .{ + .element_type = ty, + .length = len, + }; + return try self.spv.resolveType(SpvType.initPayload(&payload.base)); + } + + /// 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 resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !SpvType.Ref { + 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", .{}); + } + + const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect); + if (layout.payload_size == 0) { + // No payload, so represent this as just the tag type. + return tag_ty_ref; + } + + var members = std.BoundedArray(SpvType.Payload.Struct.Member, 4){}; + + const has_tag = layout.tag_size != 0; + const tag_first = layout.tag_align >= layout.payload_align; + const tag_member = .{ .name = "tag", .ty = tag_ty_ref }; + const u8_ty_ref = try self.intType(.unsigned, 8); // TODO: What if Int8Type is not enabled? + + if (has_tag and tag_first) { + members.appendAssumeCapacity(tag_member); + } + + 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.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) { + members.appendAssumeCapacity(tag_member); + } + + if (layout.padding != 0) { + const padding_ty_ref = try self.arrayType(layout.padding, u8_ty_ref); + members.appendAssumeCapacity(.{ .name = "padding", .ty = padding_ty_ref }); + } + + return try self.simpleStructType(members.slice()); + } + /// 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.fmtDebug()}); @@ -733,16 +889,11 @@ pub const DeclGen = struct { }, .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()}); }; - - const payload = try self.spv.arena.create(SpvType.Payload.Array); - payload.* = .{ - .element_type = try self.resolveType(elem_ty, repr), - .length = total_len, - }; - return try self.spv.resolveType(SpvType.initPayload(&payload.base)); + return try self.arrayType(total_len, elem_ty_ref); }, .Fn => { // TODO: Put this somewhere in Sema.zig @@ -809,7 +960,7 @@ pub const DeclGen = struct { 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, repr), + .ty = try self.resolveType(field_ty, .indirect), }; member_index += 1; } @@ -823,7 +974,7 @@ pub const DeclGen = struct { const struct_ty = ty.castTag(.@"struct").?.data; if (struct_ty.layout == .Packed) { - return try self.resolveType(struct_ty.backing_int_ty, repr); + 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()); @@ -832,7 +983,7 @@ pub const DeclGen = struct { if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; members[member_index] = .{ - .ty = try self.resolveType(field.ty, repr), + .ty = try self.resolveType(field.ty, .indirect), .name = struct_ty.fields.keys()[i], }; member_index += 1; @@ -872,6 +1023,8 @@ pub const DeclGen = struct { .{ .ty = bool_ty_ref, .name = "valid" }, }); }, + .Union => return try self.resolveUnionType(ty, null), + .Null, .Undefined, .EnumLiteral, From 80b84355692606ac840584baa62aaafdd8ecd425 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Wed, 29 Mar 2023 01:12:05 +0200 Subject: [PATCH 38/51] spirv: overhaul constant lowering Lowering constants is currently not really compatible with unions. In this commit, constant lowering is drastically overhauled: instead of playing nice and generating SPIR-V constant representations for everything directly, we're just going to treat globals as an untyped bag of bytes ( or rather, SPIR-V 32-bit words), which we cast to the desired type at usage. This is similar to how Rust generates constants in its LLVm backend. --- src/codegen/spirv.zig | 860 +++++++++++++++++++++------------- src/codegen/spirv/Module.zig | 37 +- src/codegen/spirv/Section.zig | 19 + 3 files changed, 576 insertions(+), 340 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 080f2f645b..eb67f8e1c3 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -238,7 +238,7 @@ pub const DeclGen = struct { return try self.resolveDecl(fn_decl_index); } - return try self.constant(ty, val, .direct); + return try self.constant(ty, val); } const index = Air.refToIndex(inst).?; return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage. @@ -404,320 +404,493 @@ pub const DeclGen = struct { return result_id; } - fn constant(self: *DeclGen, ty: Type, val: Value, repr: Repr) Error!IdRef { - const result_id = self.spv.allocId(); - try self.genConstant(result_id, ty, val, repr); - return result_id; - } + const IndirectConstantLowering = struct { + const undef = 0xAA; - /// Generate a constant representing `val`. - /// TODO: Deduplication? - fn genConstant(self: *DeclGen, result_id: IdRef, ty: Type, val: Value, repr: Repr) Error!void { - 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); + dg: *DeclGen, + /// Cached reference of the u32 type. + u32_ty_ref: SpvType.Ref, + /// 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), + /// The initializers of each of the members. + initializers: std.ArrayList(IdRef), + /// The current size of the structure. Includes + /// the bytes in partial_word. + size: u32 = 0, + /// The partially filled last constant. + /// If full, its flushed. + partial_word: std.BoundedArray(u8, @sizeOf(Word)) = .{}, - log.debug("genConstant: ty = {}, val = {}", .{ ty.fmtDebug(), val.fmtDebug() }); + /// Flush the partial_word to the members. If the partial_word is not + /// filled, this adds padding bytes (which are undefined). + fn flush(self: *@This()) !void { + if (self.partial_word.len == 0) { + // No need to add it there. + return; + } + + for (self.partial_word.unusedCapacitySlice()) |*unused| { + // TODO: Perhaps we should generate OpUndef for these bytes? + unused.* = undef; + } + + const word = @bitCast(Word, self.partial_word.buffer); + const result_id = self.dg.spv.allocId(); + try self.dg.spv.emitConstant(self.u32_ty_id, result_id, .{ .uint32 = word }); + try self.members.append(.{ .ty = self.u32_ty_ref }); + try self.initializers.append(result_id); + + self.partial_word.len = 0; + self.size = std.mem.alignForwardGeneric(u32, self.size, @sizeOf(Word)); + } + + /// Fill the buffer with undefined values until the size is aligned to `align`. + fn fillToAlign(self: *@This(), alignment: u32) !void { + const target_size = std.mem.alignForwardGeneric(u32, self.size, alignment); + try self.addUndef(target_size - self.size); + } + + fn addUndef(self: *@This(), amt: u64) !void { + for (0..@intCast(usize, amt)) |_| { + try self.addByte(undef); + } + } + + /// Add a single byte of data to the constant. + fn addByte(self: *@This(), data: u8) !void { + self.partial_word.append(data) catch { + try self.flush(); + self.partial_word.append(data) catch unreachable; + }; + self.size += 1; + } + + /// Add many bytes of data to the constnat. + fn addBytes(self: *@This(), data: []const u8) !void { + // TODO: Improve performance by adding in bulk, or something? + for (data) |byte| { + try self.addByte(byte); + } + } + + fn addPtr(self: *@This(), ptr_ty_ref: SpvType.Ref, ptr_id: IdRef) !void { + // TODO: Double check pointer sizes here. + // shared pointers might be u32... + const target = self.dg.getTarget(); + const width = @divExact(target.cpu.arch.ptrBitWidth(), 8); + if (self.size % width != 0) { + return self.dg.todo("misaligned pointer constants", .{}); + } + try self.members.append(.{ .ty = ptr_ty_ref }); + try self.initializers.append(ptr_id); + self.size += width; + } + + fn addNullPtr(self: *@This(), ptr_ty_ref: SpvType.Ref) !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, + }); + try self.addPtr(ptr_ty_ref, result_id); + } + + fn addConstInt(self: *@This(), comptime T: type, value: T) !void { + if (@bitSizeOf(T) % 8 != 0) { + @compileError("todo: non byte aligned int constants"); + } + + // TODO: Swap endianness if the compiler is big endian. + try self.addBytes(std.mem.asBytes(&value)); + } + + fn addConstBool(self: *@This(), value: bool) !void { + try self.addByte(@boolToInt(value)); // TODO: Keep in sync with something? + } + + fn addInt(self: *@This(), ty: Type, val: Value) !void { + const target = self.dg.getTarget(); + const int_info = ty.intInfo(target); + const int_bits = switch (int_info.signedness) { + .signed => @bitCast(u64, val.toSignedInt(target)), + .unsigned => val.toUnsignedInt(target), + }; + + // TODO: Swap endianess if the compiler is big endian. + const len = ty.abiSize(target); + try self.addBytes(std.mem.asBytes(&int_bits)[0..@intCast(usize, len)]); + } + + fn lower(self: *@This(), ty: Type, val: Value) !void { + const target = self.dg.getTarget(); + const dg = self.dg; + + switch (ty.zigTypeTag()) { + .Int => try self.addInt(ty, val), + .Bool => try self.addConstBool(val.toBool()), + .Array => switch (val.tag()) { + .aggregate => { + const elem_vals = val.castTag(.aggregate).?.data; + const elem_ty = ty.elemType(); + const len = @intCast(u32, ty.arrayLenIncludingSentinel()); // TODO: limit spir-v to 32 bit arrays in a more elegant way. + for (elem_vals[0..len]) |elem_val| { + try self.lower(elem_ty, elem_val); + } + }, + .repeated => { + const elem_val = val.castTag(.repeated).?.data; + const elem_ty = ty.elemType(); + const len = @intCast(u32, ty.arrayLen()); + for (0..len) |_| { + try self.lower(elem_ty, elem_val); + } + if (ty.sentinel()) |sentinel| { + try self.lower(elem_ty, sentinel); + } + }, + .str_lit => { + const str_lit = val.castTag(.str_lit).?.data; + const bytes = dg.module.string_literal_bytes.items[str_lit.index..][0..str_lit.len]; + try self.addBytes(bytes); + if (ty.sentinel()) |sentinel| { + try self.addByte(@intCast(u8, sentinel.toUnsignedInt(target))); + } + }, + else => |tag| return dg.todo("indirect array constant with tag {s}", .{@tagName(tag)}), + }, + .Pointer => switch (val.tag()) { + .decl_ref_mut => { + const ptr_ty_ref = try dg.resolveType(ty, .indirect); + const ptr_id = dg.spv.allocId(); + const decl_index = val.castTag(.decl_ref_mut).?.data.decl_index; + try dg.genDeclRef(ptr_ty_ref, ptr_id, decl_index); + try self.addPtr(ptr_ty_ref, ptr_id); + }, + .decl_ref => { + const ptr_ty_ref = try dg.resolveType(ty, .indirect); + const ptr_id = dg.spv.allocId(); + const decl_index = val.castTag(.decl_ref).?.data; + try dg.genDeclRef(ptr_ty_ref, ptr_id, decl_index); + try self.addPtr(ptr_ty_ref, ptr_id); + }, + .slice => { + const slice = val.castTag(.slice).?.data; + + var buf: Type.SlicePtrFieldTypeBuffer = undefined; + const ptr_ty = ty.slicePtrFieldType(&buf); + + try self.lower(ptr_ty, slice.ptr); + try self.addInt(Type.usize, slice.len); + }, + else => |tag| return dg.todo("pointer value of type {s}", .{@tagName(tag)}), + }, + .Struct => { + if (ty.isSimpleTupleOrAnonStruct()) { + unreachable; // TODO + } else { + const struct_ty = ty.castTag(.@"struct").?.data; + + if (struct_ty.layout == .Packed) { + return dg.todo("packed struct constants", .{}); + } + + const struct_begin = self.size; + const field_vals = val.castTag(.aggregate).?.data; + for (struct_ty.fields.values(), 0..) |field, i| { + if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; + try self.lower(field.ty, field_vals[i]); + + // Add padding if required. + // TODO: Add to type generation as well? + const unpadded_field_end = self.size - struct_begin; + const padded_field_end = ty.structFieldOffset(i + 1, target); + const padding = padded_field_end - unpadded_field_end; + try self.addUndef(padding); + } + } + }, + .Optional => { + var opt_buf: Type.Payload.ElemType = undefined; + const payload_ty = ty.optionalChild(&opt_buf); + const has_payload = !val.isNull(); + const abi_size = ty.abiSize(target); + + if (!payload_ty.hasRuntimeBits()) { + try self.addConstBool(has_payload); + return; + } else if (ty.optionalReprIsPayload()) { + // Optional representation is a nullable pointer. + if (val.castTag(.opt_payload)) |payload| { + try self.lower(payload_ty, payload.data); + } else if (has_payload) { + try self.lower(payload_ty, val); + } else { + const ptr_ty_ref = try dg.resolveType(ty, .indirect); + try self.addNullPtr(ptr_ty_ref); + } + return; + } + + // Optional representation is a structure. + // { Payload, Bool } + + // Subtract 1 for @sizeOf(bool). + // TODO: Make this not hardcoded. + const payload_size = payload_ty.abiSize(target); + const padding = abi_size - payload_size - 1; + + if (val.castTag(.opt_payload)) |payload| { + try self.lower(payload_ty, payload.data); + } else { + try self.addUndef(payload_size); + } + try self.addConstBool(has_payload); + try self.addUndef(padding); + }, + .Enum => { + var int_val_buffer: Value.Payload.U64 = undefined; + const int_val = val.enumToInt(ty, &int_val_buffer); + + var int_ty_buffer: Type.Payload.Bits = undefined; + const int_ty = ty.intTagType(&int_ty_buffer); + + try self.lower(int_ty, int_val); + }, + .Union => { + const tag_and_val = val.castTag(.@"union").?.data; + const layout = ty.unionGetLayout(target); + + if (layout.payload_size == 0) { + return try self.lower(ty.unionTagTypeSafety().?, tag_and_val.tag); + } + + const union_ty = ty.cast(Type.Payload.Union).?.data; + if (union_ty.layout == .Packed) { + return dg.todo("packed union constants", .{}); + } + + const active_field = ty.unionTagFieldIndex(tag_and_val.tag, dg.module).?; + const active_field_ty = union_ty.fields.values()[active_field].ty; + + const has_tag = layout.tag_size != 0; + const tag_first = layout.tag_align >= layout.payload_align; + + if (has_tag and tag_first) { + try self.lower(ty.unionTagTypeSafety().?, tag_and_val.tag); + } + + const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: { + try self.lower(active_field_ty, tag_and_val.val); + break :blk active_field_ty.abiSize(target); + } else 0; + + const payload_padding_len = layout.payload_size - active_field_size; + try self.addUndef(payload_padding_len); + + if (has_tag and !tag_first) { + try self.lower(ty.unionTagTypeSafety().?, tag_and_val.tag); + } + + try self.addUndef(layout.padding); + }, + else => |tag| return dg.todo("indirect constant of type {s}", .{@tagName(tag)}), + } + } + }; + + /// Returns a pointer to `val`. The value is placed directly + /// into the storage class `storage_class`, and this is also where the resulting + /// pointer points to. Note: result is not necessarily an OpVariable instruction! + fn lowerIndirectConstant( + self: *DeclGen, + result_id: IdRef, + ty: Type, + val: Value, + storage_class: spec.StorageClass, + alignment: u32, + ) Error!void { + // To simplify constant generation, we're going to generate constants as a word-array, and + // pointer cast the result to the right type. + // This means that the final constant will be generated as follows: + // %T = OpTypeStruct %members... + // %P = OpTypePointer %T + // %U = OpTypePointer %ty + // %1 = OpConstantComposite %T %initializers... + // %2 = OpVariable %P %1 + // %result_id = OpSpecConstantOp OpBitcast %U %2 + // + // The members consist of two options: + // - Literal values: ints, strings, etc. These are generated as u32 words. + // - Relocations, such as pointers: These are generated by embedding the pointer into the + // to-be-generated structure. There are two options here, depending on the alignment of the + // pointer value itself (not the alignment of the pointee). + // - Natively or over-aligned values. These can just be generated directly. + // - Underaligned pointers. These need to be packed into the word array by using a mixture of + // OpSpecConstantOp instructions such as OpConvertPtrToU, OpBitcast, OpShift, etc. + + log.debug("lowerIndirectConstant: ty = {}, val = {}", .{ ty.fmtDebug(), val.fmtDebug() }); + + const constant_section = &self.spv.sections.types_globals_constants; + + const ty_ref = try self.resolveType(ty, .indirect); + const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, alignment); if (val.isUndef()) { - try section.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = result_ty_id, .id_result = result_id }); + // Special case: the entire value is undefined. In this case, we can just + // generate an OpVariable with no initializer. + try constant_section.emit(self.spv.gpa, .OpVariable, .{ + .id_result_type = self.typeId(ptr_ty_ref), + .id_result = result_id, + .storage_class = storage_class, + }); + return; + } + + const u32_ty_ref = try self.intType(.unsigned, 32); + var icl = IndirectConstantLowering{ + .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), + .initializers = std.ArrayList(IdRef).init(self.gpa), + }; + + try icl.lower(ty, val); + try icl.flush(); + + defer icl.members.deinit(); + defer icl.initializers.deinit(); + + 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, alignment); + + const constant_struct_id = self.spv.allocId(); + try constant_section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ + .id_result_type = self.typeId(constant_struct_ty_ref), + .id_result = constant_struct_id, + .constituents = icl.initializers.items, + }); + + const var_id = self.spv.allocId(); + switch (storage_class) { + .Generic => unreachable, + .Function => { + try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{ + .id_result_type = self.typeId(ptr_constant_struct_ty_ref), + .id_result = var_id, + .storage_class = storage_class, + .initializer = constant_struct_id, + }); + // TODO: Set alignment of OpVariable. + + try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ + .id_result_type = self.typeId(ptr_ty_ref), + .id_result = result_id, + .operand = var_id, + }); + }, + else => { + try constant_section.emit(self.spv.gpa, .OpVariable, .{ + .id_result_type = self.typeId(ptr_constant_struct_ty_ref), + .id_result = var_id, + .storage_class = storage_class, + .initializer = constant_struct_id, + }); + // TODO: Set alignment of OpVariable. + + try constant_section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{ + .id_result_type = self.typeId(ptr_ty_ref), + .id_result = result_id, + .operand = var_id, + }); + }, + } + } + + /// This function generates a load for a constant in direct (ie, non-memory) representation. + /// When the constant is simple, it can be generated directly using OpConstant instructions. When + /// the constant is more complicated however, it needs to be lowered to an indirect constant, which + /// is then loaded using OpLoad. Such values are loaded into the Function address space by default. + /// This function should only be called during function code generation. + fn constant(self: *DeclGen, ty: Type, val: Value) !IdRef { + const target = self.getTarget(); + const section = &self.spv.sections.types_globals_constants; + const result_ty_ref = try self.resolveType(ty, .direct); + const result_ty_id = self.typeId(result_ty_ref); + const result_id = self.spv.allocId(); + + if (val.isUndef()) { + try section.emit(self.spv.gpa, .OpUndef, .{ + .id_result_type = result_ty_id, + .id_result = result_id, + }); + return result_id; } switch (ty.zigTypeTag()) { .Int => { - const int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt(target)) else val.toUnsignedInt(target); + const int_bits = if (ty.isSignedInt()) + @bitCast(u64, val.toSignedInt(target)) + else + val.toUnsignedInt(target); try self.genConstInt(result_ty_ref, result_id, int_bits); }, - .Bool => switch (repr) { - .direct => { - 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); - } - }, - .indirect => try self.genConstInt(result_ty_ref, result_id, @boolToInt(val.toBool())), + .Bool => { + 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); + } }, - .Float => { - // At this point we are guaranteed that the target floating point type is supported, otherwise the function - // would have exited at resolveTypeId(ty). - const literal: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) { - // Prevent upcasting to f32 by bitcasting and writing as a uint32. - 16 => .{ .uint32 = @bitCast(u16, val.toFloat(f16)) }, - 32 => .{ .float32 = val.toFloat(f32) }, - 64 => .{ .float64 = val.toFloat(f64) }, - 128 => unreachable, // Filtered out in the call to resolveTypeId. - // TODO: Insert case for long double when the layout for that is determined? - else => unreachable, - }; - - try self.spv.emitConstant(result_ty_id, result_id, literal); - }, - .Array => switch (val.tag()) { - .aggregate => { // todo: combine with Vector - const elem_vals = val.castTag(.aggregate).?.data; - const elem_ty = ty.elemType(); - const len = @intCast(u32, ty.arrayLenIncludingSentinel()); // TODO: limit spir-v to 32 bit arrays in a more elegant way. - const constituents = try self.spv.gpa.alloc(IdRef, len); - defer self.spv.gpa.free(constituents); - for (elem_vals[0..len], 0..) |elem_val, i| { - constituents[i] = try self.constant(elem_ty, elem_val, repr); - } - try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - .constituents = constituents, - }); - }, - .repeated => { - const elem_val = val.castTag(.repeated).?.data; - const elem_ty = ty.elemType(); - const len = @intCast(u32, ty.arrayLen()); - const total_len = @intCast(u32, ty.arrayLenIncludingSentinel()); // TODO: limit spir-v to 32 bit arrays in a more elegant way. - const constituents = try self.spv.gpa.alloc(IdRef, total_len); - defer self.spv.gpa.free(constituents); - - const elem_val_id = try self.constant(elem_ty, elem_val, repr); - for (constituents[0..len]) |*elem| { - elem.* = elem_val_id; - } - if (ty.sentinel()) |sentinel| { - constituents[len] = try self.constant(elem_ty, sentinel, repr); - } - try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - .constituents = constituents, - }); - }, - .str_lit => { - // TODO: This is very efficient code generation, should probably implement constant caching for this. - const str_lit = val.castTag(.str_lit).?.data; - const bytes = self.module.string_literal_bytes.items[str_lit.index..][0..str_lit.len]; - const elem_ty = ty.elemType(); - const elem_ty_id = try self.resolveTypeId(elem_ty); - const len = @intCast(u32, ty.arrayLen()); - const total_len = @intCast(u32, ty.arrayLenIncludingSentinel()); - const constituents = try self.spv.gpa.alloc(IdRef, total_len); - defer self.spv.gpa.free(constituents); - for (bytes, 0..) |byte, i| { - constituents[i] = self.spv.allocId(); - try self.spv.emitConstant(elem_ty_id, constituents[i], .{ .uint32 = byte }); - } - if (ty.sentinel()) |sentinel| { - constituents[len] = self.spv.allocId(); - const byte = @intCast(u8, sentinel.toUnsignedInt(target)); - try self.spv.emitConstant(elem_ty_id, constituents[len], .{ .uint32 = byte }); - } - try section.emit(self.spv.gpa, .OpConstantComposite, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - .constituents = constituents, - }); - }, - else => return self.todo("array constant with tag {s}", .{@tagName(val.tag())}), - }, - .Vector => switch (val.tag()) { - .aggregate => { - const elem_vals = val.castTag(.aggregate).?.data; - const vector_len = @intCast(usize, ty.vectorLen()); - const elem_ty = ty.elemType(); - - const elem_refs = try self.gpa.alloc(IdRef, vector_len); - defer self.gpa.free(elem_refs); - for (elem_refs, 0..) |*elem, i| { - elem.* = try self.constant(elem_ty, elem_vals[i], repr); - } - try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - .constituents = elem_refs, - }); - }, - else => return self.todo("vector constant with tag {s}", .{@tagName(val.tag())}), - }, - .Enum => { - var int_buffer: Value.Payload.U64 = undefined; - const int_val = val.enumToInt(ty, &int_buffer).toUnsignedInt(target); // TODO: composite integer constants - return self.genConstInt(result_ty_ref, result_id, int_val); - }, - .Struct => { - const constituents = if (ty.isSimpleTupleOrAnonStruct()) blk: { - const tuple = ty.tupleFields(); - const constituents = try self.spv.gpa.alloc(IdRef, tuple.types.len); - errdefer self.spv.gpa.free(constituents); - - var member_i: 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; - constituents[member_i] = try self.constant(field_ty, field_val, .indirect); - member_i += 1; - } - - break :blk constituents[0..member_i]; - } else blk: { - const struct_ty = ty.castTag(.@"struct").?.data; - - if (struct_ty.layout == .Packed) { - return self.todo("packed struct constants", .{}); - } - - const field_vals = val.castTag(.aggregate).?.data; - const constituents = try self.spv.gpa.alloc(IdRef, struct_ty.fields.count()); - errdefer self.spv.gpa.free(constituents); - var member_i: usize = 0; - for (struct_ty.fields.values(), 0..) |field, i| { - if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; - constituents[member_i] = try self.constant(field.ty, field_vals[i], .indirect); - member_i += 1; - } - - break :blk constituents[0..member_i]; - }; - defer self.spv.gpa.free(constituents); - - try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ + else => { + // The value cannot be generated directly, so generate it as an indirect function-local + // constant, and then perform an OpLoad. + const ptr_id = self.spv.allocId(); + const alignment = ty.abiAlignment(target); + try self.lowerIndirectConstant(ptr_id, ty, val, .Function, alignment); + try self.func.body.emit(self.spv.gpa, .OpLoad, .{ .id_result_type = result_ty_id, .id_result = result_id, - .constituents = constituents, + .pointer = ptr_id, }); + // TODO: Convert bools? This logic should hook into `load`. }, - .Pointer => switch (val.tag()) { - .decl_ref_mut => try self.genDeclRef(result_ty_ref, result_id, val.castTag(.decl_ref_mut).?.data.decl_index), - .decl_ref => try self.genDeclRef(result_ty_ref, result_id, val.castTag(.decl_ref).?.data), - .slice => { - const slice = val.castTag(.slice).?.data; - var buf: Type.SlicePtrFieldTypeBuffer = undefined; - - const ptr_id = try self.constant(ty.slicePtrFieldType(&buf), slice.ptr, .indirect); - const len_id = try self.constant(Type.usize, slice.len, .indirect); - - const constituents = [_]IdRef{ ptr_id, len_id }; - try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - .constituents = &constituents, - }); - }, - else => return self.todo("pointer of value type {s}", .{@tagName(val.tag())}), - }, - .Optional => { - var buf: Type.Payload.ElemType = undefined; - const payload_ty = ty.optionalChild(&buf); - - const has_payload = !val.isNull(); - - // Note: keep in sync with the resolveType implementation for optionals. - if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { - // Just a bool. Note: always in indirect representation. - try self.genConstInt(result_ty_ref, result_id, @boolToInt(has_payload)); - } else if (ty.optionalReprIsPayload()) { - // A nullable pointer. - if (val.castTag(.opt_payload)) |payload| { - try self.genConstant(result_id, payload_ty, payload.data, repr); - } else if (has_payload) { - try self.genConstant(result_id, payload_ty, val, repr); - } else { - try section.emit(self.spv.gpa, .OpConstantNull, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - }); - } - return; - } - - // Struct-and-field pair. - // Note: If this optional has no payload, we initialize the the data member with OpUndef. - const bool_ty_ref = try self.resolveType(Type.bool, .indirect); - const valid_id = try self.constInt(bool_ty_ref, @boolToInt(has_payload)); - const payload_val = if (val.castTag(.opt_payload)) |pl| pl.data else Value.undef; - const payload_id = try self.constant(payload_ty, payload_val, .indirect); - - const constituents = [_]IdRef{ payload_id, valid_id }; - try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - .constituents = &constituents, - }); - }, - .Union => { - const tag_and_val = val.castTag(.@"union").?.data; - const layout = ty.unionGetLayout(target); - - if (layout.payload_size == 0) { - return try self.genConstant(result_id, ty.unionTagTypeSafety().?, tag_and_val.tag, .indirect); - } - - const union_ty = ty.cast(Type.Payload.Union).?.data; - if (union_ty.layout == .Packed) { - return self.todo("packed union constants", .{}); - } - - const active_field = ty.unionTagFieldIndex(tag_and_val.tag, self.module).?; - const union_ty_ref = try self.resolveUnionType(ty, active_field); - const active_field_ty = union_ty.fields.values()[active_field].ty; - - const tag_first = layout.tag_align >= layout.payload_align; - const u8_ty_ref = try self.intType(.unsigned, 8); - - const tag = if (layout.tag_size != 0) - try self.constant(ty.unionTagTypeSafety().?, tag_and_val.tag, .indirect) - else - null; - - var members = std.BoundedArray(IdRef, 4){}; - - if (tag_first) { - if (tag) |id| members.appendAssumeCapacity(id); - } - - const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: { - const payload = try self.constant(active_field_ty, tag_and_val.val, .indirect); - members.appendAssumeCapacity(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.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref); - members.appendAssumeCapacity(try self.genUndef(payload_padding_ty_ref)); - } - - if (!tag_first) { - if (tag) |id| members.appendAssumeCapacity(id); - } - - if (layout.padding != 0) { - const padding_ty_ref = try self.arrayType(layout.padding, u8_ty_ref); - members.appendAssumeCapacity(try self.genUndef(padding_ty_ref)); - } - - try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ - .id_result_type = self.typeId(union_ty_ref), - .id_result = result_id, - .constituents = members.slice(), - }); - - // TODO: Cast to general union type? Required for pointers only or something? - }, - - .Fn => switch (repr) { - .direct => unreachable, - .indirect => return self.todo("function pointers", .{}), - }, - .Void => unreachable, - else => return self.todo("constant generation of type {s}: {}", .{ @tagName(ty.zigTypeTag()), ty.fmtDebug() }), } + + return result_id; } fn genDeclRef(self: *DeclGen, result_ty_ref: SpvType.Ref, result_id: IdRef, decl_index: Decl.Index) Error!void { + // TODO: Clean up const decl = self.module.declPtr(decl_index); self.module.markDeclAlive(decl); - const decl_id = try self.constant(decl.ty, decl.val, .indirect); - try self.variable(.global, result_id, result_ty_ref, decl_id); + // _ = result_ty_ref; + // const decl_id = try self.constant(decl.ty, decl.val, .indirect); + // try self.variable(.global, result_id, result_ty_ref, decl_id); + const result_storage_class = self.spv.typeRefType(result_ty_ref).payload(.pointer).storage_class; + const indirect_result_id = if (result_storage_class != .CrossWorkgroup) + self.spv.allocId() + else + result_id; + + try self.lowerIndirectConstant( + indirect_result_id, + decl.ty, + decl.val, + .CrossWorkgroup, // TODO: Make this .Function if required + decl.@"align", + ); + const section = &self.spv.sections.types_globals_constants; + if (result_storage_class != .CrossWorkgroup) { + try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ + .id_result_type = self.typeId(result_ty_ref), + .id_result = result_id, + .pointer = indirect_result_id, + }); + } } /// Turn a Zig type into a SPIR-V Type, and return its type result-id. @@ -746,32 +919,6 @@ pub const DeclGen = struct { return try self.intType(.unsigned, self.getTarget().cpu.arch.ptrBitWidth()); } - /// Construct a simple struct type which consists of some members, and no decorations. - /// `members` lifetime only needs to last for this function as it is copied. - fn simpleStructType(self: *DeclGen, members: []const SpvType.Payload.Struct.Member) !SpvType.Ref { - const payload = try self.spv.arena.create(SpvType.Payload.Struct); - payload.* = .{ - .members = try self.spv.arena.dupe(SpvType.Payload.Struct.Member, members), - .decorations = .{}, - }; - return try self.spv.resolveType(SpvType.initPayload(&payload.base)); - } - - fn simpleStructTypeId(self: *DeclGen, members: []const SpvType.Payload.Struct.Member) !IdResultType { - const type_ref = try self.simpleStructType(members); - return self.typeId(type_ref); - } - - /// Construct an array type which has 'len' elements of 'type' - fn arrayType(self: *DeclGen, len: u32, ty: SpvType.Ref) !SpvType.Ref { - const payload = try self.spv.arena.create(SpvType.Payload.Array); - payload.* = .{ - .element_type = ty, - .length = len, - }; - return try self.spv.resolveType(SpvType.initPayload(&payload.base)); - } - /// 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: @@ -831,7 +978,7 @@ 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.arrayType(@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); members.appendAssumeCapacity(.{ .name = "padding_payload", .ty = payload_padding_ty_ref }); } @@ -840,11 +987,11 @@ pub const DeclGen = struct { } if (layout.padding != 0) { - const padding_ty_ref = try self.arrayType(layout.padding, u8_ty_ref); + const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref); members.appendAssumeCapacity(.{ .name = "padding", .ty = padding_ty_ref }); } - return try self.simpleStructType(members.slice()); + return try self.spv.simpleStructType(members.slice()); } /// Turn a Zig type into a SPIR-V Type, and return a reference to it. @@ -893,7 +1040,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()}); }; - return try self.arrayType(total_len, elem_ty_ref); + return try self.spv.arrayType(total_len, elem_ty_ref); }, .Fn => { // TODO: Put this somewhere in Sema.zig @@ -918,7 +1065,7 @@ pub const DeclGen = struct { const ptr_payload = try self.spv.arena.create(SpvType.Payload.Pointer); ptr_payload.* = .{ - .storage_class = spirvStorageClass(ptr_info.@"addrspace"), + .storage_class = spvStorageClass(ptr_info.@"addrspace"), .child_type = try self.resolveType(ptr_info.pointee_type, .indirect), // Note: only available in Kernels! .alignment = ty.ptrAlignment(target) * 8, @@ -929,7 +1076,7 @@ pub const DeclGen = struct { return ptr_ty_id; } - return try self.simpleStructType(&.{ + return try self.spv.simpleStructType(&.{ .{ .ty = ptr_ty_id, .name = "ptr" }, .{ .ty = try self.sizeType(), .name = "len" }, }); @@ -1018,7 +1165,7 @@ pub const DeclGen = struct { const bool_ty_ref = try self.resolveType(Type.bool, .indirect); // its an actual optional - return try self.simpleStructType(&.{ + return try self.spv.simpleStructType(&.{ .{ .ty = payload_ty_ref, .name = "payload" }, .{ .ty = bool_ty_ref, .name = "valid" }, }); @@ -1037,7 +1184,7 @@ pub const DeclGen = struct { } } - fn spirvStorageClass(as: std.builtin.AddressSpace) spec.StorageClass { + fn spvStorageClass(as: std.builtin.AddressSpace) spec.StorageClass { return switch (as) { .generic => .Generic, // TODO: Disallow? .gs, .fs, .ss => unreachable, @@ -1100,7 +1247,46 @@ pub const DeclGen = struct { .name = fqn, }); } else { - try self.genConstant(result_id, decl.ty, decl.val, .direct); + const init_val = if (decl.val.castTag(.variable)) |payload| + payload.data.init + else + decl.val; + + if (init_val.tag() == .unreachable_value) { + return self.todo("importing extern variables", .{}); + } + + // TODO: integrate with variable(). + + const storage_class = spvStorageClass(decl.@"addrspace"); + const actual_storage_class = switch (storage_class) { + .Generic => .CrossWorkgroup, + else => storage_class, + }; + + const var_result_id = switch (storage_class) { + .Generic => self.spv.allocId(), + else => result_id, + }; + + try self.lowerIndirectConstant( + var_result_id, + decl.ty, + init_val, + actual_storage_class, + decl.@"align", + ); + + if (storage_class == .Generic) { + const section = &self.spv.sections.types_globals_constants; + const ty_ref = try self.resolveType(decl.ty, .indirect); + const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align"); + try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ + .id_result_type = self.typeId(ptr_ty_ref), + .id_result = result_id, + .pointer = var_result_id, + }); + } } } @@ -1358,13 +1544,13 @@ pub const DeclGen = struct { // Construct the SPIR-V result type. // It is almost the same as the zig one, except that the fields must be the same type // and they must be unsigned. - const overflow_result_ty = try self.simpleStructTypeId(&.{ + const overflow_result_ty_ref = try self.spv.simpleStructType(&.{ .{ .ty = overflow_member_ty, .name = "res" }, .{ .ty = overflow_member_ty, .name = "ov" }, }); const result_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{ - .id_result_type = overflow_result_ty, + .id_result_type = self.typeId(overflow_result_ty_ref), .id_result = result_id, .operand_1 = lhs, .operand_2 = rhs, @@ -1786,13 +1972,11 @@ pub const DeclGen = struct { .id_result = result_id, .pointer = alloc_result_id, }), - else => { - try section.emitRaw(self.spv.gpa, .OpSpecConstantOp, 3 + 1); - section.writeOperand(IdRef, self.typeId(ptr_ty_ref)); - section.writeOperand(IdRef, result_id); - section.writeOperand(Opcode, .OpPtrCastToGeneric); - section.writeOperand(IdRef, alloc_result_id); - }, + else => try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ + .id_result_type = self.typeId(ptr_ty_ref), + .id_result = result_id, + .pointer = alloc_result_id, + }), } } diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index ff7ae29993..dc5e8053c1 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -556,6 +556,39 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct } } +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 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, + storage_class: spec.StorageClass, + alignment: ?u32, +) !Type.Ref { + const ptr_payload = try self.arena.create(Type.Payload.Pointer); + ptr_payload.* = .{ + .storage_class = storage_class, + .child_type = child, + .alignment = alignment, + }; + return try self.resolveType(Type.initPayload(&ptr_payload.base)); +} + pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_class: spec.StorageClass) !Type.Ref { const payload = try self.arena.create(Type.Payload.Pointer); payload.* = self.typeRefType(ptr_ty_ref).payload(.pointer).*; @@ -579,7 +612,7 @@ pub fn emitConstant( /// Decorate a result-id. pub fn decorate( self: *Module, - target: spec.IdRef, + target: IdRef, decoration: spec.Decoration.Extended, ) !void { try self.sections.annotations.emit(self.gpa, .OpDecorate, .{ @@ -591,7 +624,7 @@ pub fn decorate( /// Decorate a result-id which is a member of some struct. pub fn decorateMember( self: *Module, - structure_type: spec.IdRef, + structure_type: IdRef, member: u32, decoration: spec.Decoration.Extended, ) !void { diff --git a/src/codegen/spirv/Section.zig b/src/codegen/spirv/Section.zig index ce40717ef3..b6087bbc3b 100644 --- a/src/codegen/spirv/Section.zig +++ b/src/codegen/spirv/Section.zig @@ -65,6 +65,25 @@ pub fn emit( section.writeOperands(opcode.Operands(), operands); } +pub fn emitSpecConstantOp( + section: *Section, + allocator: Allocator, + comptime opcode: spec.Opcode, + operands: opcode.Operands(), +) !void { + const word_count = operandsSize(opcode.Operands(), operands); + try section.emitRaw(allocator, .OpSpecConstantOp, 1 + word_count); + section.writeOperand(spec.IdRef, operands.id_result_type); + section.writeOperand(spec.IdRef, operands.id_result); + section.writeOperand(Opcode, opcode); + + const fields = @typeInfo(opcode.Operands()).Struct.fields; + // First 2 fields are always id_result_type and id_result. + inline for (fields[2..]) |field| { + section.writeOperand(field.type, @field(operands, field.name)); + } +} + pub fn writeWord(section: *Section, word: Word) void { section.instructions.appendAssumeCapacity(word); } From 8bbfbfc956af163434c734e196d5c2a77e77ff07 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 9 Apr 2023 01:29:39 +0200 Subject: [PATCH 39/51] spirv: improve linking globals SPIR-V globals must be emitted in order, so that any declaration precedes usage. Zig, however, generates globals in random order. To this end we keep for each global a list of dependencies and perform a topological sort when flushing the module. --- src/codegen/spirv.zig | 288 ++++++++++++++++++++--------------- src/codegen/spirv/Module.zig | 127 ++++++++++++++- src/link/SpirV.zig | 10 +- 3 files changed, 298 insertions(+), 127 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index eb67f8e1c3..b58f889f8e 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -32,12 +32,28 @@ const IncomingBlock = struct { break_value_id: IdRef, }; -pub const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct { +const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct { label_id: IdRef, incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock), }); -pub const DeclMap = std.AutoHashMap(Module.Decl.Index, IdResult); +/// Linking information about a particular decl. +/// The active field of this enum depends on the type of the corresponding decl. +const DeclLink = union { + /// Linking information about a function. + /// Active when the decl is a function. + func: struct { + /// Result-id of the OpFunction instruction. + result_id: IdResult, + }, + /// Linking information about a global. This index points into the + /// SPIR-V module's `globals` array. + /// Active when the decl is a variable. + global: SpvModule.Global.Index, +}; + +/// Maps Zig decl indices to linking SPIR-V linking information. +pub const DeclLinkMap = std.AutoHashMap(Module.Decl.Index, DeclLink); /// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that. pub const DeclGen = struct { @@ -61,8 +77,8 @@ pub const DeclGen = struct { /// Note: If the declaration is not a function, this value will be undefined! liveness: Liveness, - /// Maps Zig Decl indices to SPIR-V result indices. - decl_ids: *DeclMap, + /// Maps Zig Decl indices to SPIR-V globals. + decl_link: *DeclLinkMap, /// An array of function argument result-ids. Each index corresponds with the /// function argument of the same index. @@ -152,7 +168,7 @@ pub const DeclGen = struct { allocator: Allocator, module: *Module, spv: *SpvModule, - decl_ids: *DeclMap, + decl_link: *DeclLinkMap, ) DeclGen { return .{ .gpa = allocator, @@ -161,7 +177,7 @@ pub const DeclGen = struct { .decl_index = undefined, .air = undefined, .liveness = undefined, - .decl_ids = decl_ids, + .decl_link = decl_link, .next_arg_index = undefined, .current_block_label_id = undefined, .error_msg = undefined, @@ -235,7 +251,8 @@ pub const DeclGen = struct { .function => val.castTag(.function).?.data.owner_decl, else => unreachable, }; - return try self.resolveDecl(fn_decl_index); + const link = try self.resolveDecl(fn_decl_index); + return link.func.result_id; } return try self.constant(ty, val); @@ -246,17 +263,22 @@ pub const DeclGen = struct { /// Fetch or allocate a result id for decl index. This function also marks the decl as alive. /// Note: Function does not actually generate the decl. - fn resolveDecl(self: *DeclGen, decl_index: Module.Decl.Index) !IdResult { + fn resolveDecl(self: *DeclGen, decl_index: Module.Decl.Index) !DeclLink { const decl = self.module.declPtr(decl_index); self.module.markDeclAlive(decl); - const entry = try self.decl_ids.getOrPut(decl_index); - if (entry.found_existing) { - return entry.value_ptr.*; - } + const entry = try self.decl_link.getOrPut(decl_index); const result_id = self.spv.allocId(); - entry.value_ptr.* = result_id; - return result_id; + + if (!entry.found_existing) { + if (decl.val.castTag(.function)) |_| { + entry.value_ptr.* = .{.func = .{ .result_id = result_id }}; + } else { + entry.value_ptr.* = .{ .global = try self.spv.allocGlobal() }; + } + } + + return entry.value_ptr.*; } /// Start a new SPIR-V block, Emits the label of the new block, and stores which @@ -363,7 +385,7 @@ pub const DeclGen = struct { // As of yet, there is no vector support in the self-hosted compiler. .Vector => self.todo("implement arithmeticTypeInfo for Vector", .{}), // TODO: For which types is this the case? - else => self.todo("implement arithmeticTypeInfo for {}", .{ty.fmtDebug()}), + else => self.todo("implement arithmeticTypeInfo for {}", .{ty.fmt(self.module)}), }; } @@ -399,7 +421,7 @@ pub const DeclGen = struct { try self.spv.sections.types_globals_constants.emit( self.spv.gpa, .OpUndef, - .{ .id_result_type = self.typeId(ty_ref), .id_result = result_id }, + .{ .id_result_type = self.typeId(ty_ref), .id_result = result_id } ); return result_id; } @@ -423,6 +445,11 @@ pub const DeclGen = struct { /// If full, its flushed. partial_word: std.BoundedArray(u8, @sizeOf(Word)) = .{}, + /// Utility function to get the section that instructions should be lowered to. + fn section(self: *@This()) *SpvSection { + return &self.dg.spv.globals.section; + } + /// Flush the partial_word to the members. If the partial_word is not /// filled, this adds padding bytes (which are undefined). fn flush(self: *@This()) !void { @@ -438,6 +465,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 }); try self.members.append(.{ .ty = self.u32_ty_ref }); try self.initializers.append(result_id); @@ -523,10 +551,52 @@ pub const DeclGen = struct { try self.addBytes(std.mem.asBytes(&int_bits)[0..@intCast(usize, len)]); } + fn addDeclRef(self: *@This(), ty: Type, decl_index: Decl.Index) !void { + const dg = self.dg; + + const ty_ref = try self.dg.resolveType(ty, .indirect); + const ty_id = dg.typeId(ty_ref); + + const decl = dg.module.declPtr(decl_index); + const link = try dg.resolveDecl(decl_index); + + switch (decl.val.tag()) { + .function => { + // TODO: Properly lower function pointers. For now we are going to hack around it and + // just generate an empty pointer. Function pointers are represented by usize for now, + // though. + try self.addInt(Type.usize, Value.initTag(.zero)); + return; + }, + .extern_fn => unreachable, // TODO + else => { + const result_id = dg.spv.allocId(); + log.debug("addDeclRef {s} = {}", .{ decl.name, result_id.id }); + + const global = dg.spv.globalPtr(link.global); + try dg.spv.addGlobalDependency(link.global); + // TODO: Do we need a storage class cast here? + // TODO: We can probably eliminate these casts + try dg.spv.globals.section.emitSpecConstantOp(dg.spv.gpa, .OpBitcast, .{ + .id_result_type = ty_id, + .id_result = result_id, + .operand = global.result_id, + }); + + try self.addPtr(ty_ref, result_id); + }, + } + } + fn lower(self: *@This(), ty: Type, val: Value) !void { const target = self.dg.getTarget(); const dg = self.dg; + if (val.isUndef()) { + const size = ty.abiSize(target); + return try self.addUndef(size); + } + switch (ty.zigTypeTag()) { .Int => try self.addInt(ty, val), .Bool => try self.addConstBool(val.toBool()), @@ -558,22 +628,20 @@ pub const DeclGen = struct { try self.addByte(@intCast(u8, sentinel.toUnsignedInt(target))); } }, + .bytes => { + const bytes = val.castTag(.bytes).?.data; + try self.addBytes(bytes); + }, else => |tag| return dg.todo("indirect array constant with tag {s}", .{@tagName(tag)}), }, .Pointer => switch (val.tag()) { .decl_ref_mut => { - const ptr_ty_ref = try dg.resolveType(ty, .indirect); - const ptr_id = dg.spv.allocId(); const decl_index = val.castTag(.decl_ref_mut).?.data.decl_index; - try dg.genDeclRef(ptr_ty_ref, ptr_id, decl_index); - try self.addPtr(ptr_ty_ref, ptr_id); + try self.addDeclRef(ty, decl_index); }, .decl_ref => { - const ptr_ty_ref = try dg.resolveType(ty, .indirect); - const ptr_id = dg.spv.allocId(); const decl_index = val.castTag(.decl_ref).?.data; - try dg.genDeclRef(ptr_ty_ref, ptr_id, decl_index); - try self.addPtr(ptr_ty_ref, ptr_id); + try self.addDeclRef(ty, decl_index); }, .slice => { const slice = val.castTag(.slice).?.data; @@ -730,22 +798,31 @@ pub const DeclGen = struct { // - Underaligned pointers. These need to be packed into the word array by using a mixture of // OpSpecConstantOp instructions such as OpConvertPtrToU, OpBitcast, OpShift, etc. - log.debug("lowerIndirectConstant: ty = {}, val = {}", .{ ty.fmtDebug(), val.fmtDebug() }); + assert(storage_class != .Generic and storage_class != .Function); - const constant_section = &self.spv.sections.types_globals_constants; + log.debug("lowerIndirectConstant: ty = {}, val = {}", .{ ty.fmt(self.module), val.fmtDebug() }); + + 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, alignment); + const target = self.getTarget(); + if (val.isUndef()) { // Special case: the entire value is undefined. In this case, we can just // generate an OpVariable with no initializer. - try constant_section.emit(self.spv.gpa, .OpVariable, .{ + return try section.emit(self.spv.gpa, .OpVariable, .{ .id_result_type = self.typeId(ptr_ty_ref), .id_result = result_id, .storage_class = storage_class, }); - return; + } else if (ty.abiSize(target) == 0) { + // Special case: if the type has no size, then return an undefined pointer. + return try section.emit(self.spv.gpa, .OpUndef, .{ + .id_result_type = self.typeId(ptr_ty_ref), + .id_result = result_id, + }); } const u32_ty_ref = try self.intType(.unsigned, 32); @@ -757,62 +834,42 @@ pub const DeclGen = struct { .initializers = std.ArrayList(IdRef).init(self.gpa), }; - try icl.lower(ty, val); - try icl.flush(); - defer icl.members.deinit(); defer icl.initializers.deinit(); + 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, alignment); const constant_struct_id = self.spv.allocId(); - try constant_section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ + try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ .id_result_type = self.typeId(constant_struct_ty_ref), .id_result = constant_struct_id, .constituents = icl.initializers.items, }); const var_id = self.spv.allocId(); - switch (storage_class) { - .Generic => unreachable, - .Function => { - try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{ - .id_result_type = self.typeId(ptr_constant_struct_ty_ref), - .id_result = var_id, - .storage_class = storage_class, - .initializer = constant_struct_id, - }); - // TODO: Set alignment of OpVariable. - - try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ - .id_result_type = self.typeId(ptr_ty_ref), - .id_result = result_id, - .operand = var_id, - }); - }, - else => { - try constant_section.emit(self.spv.gpa, .OpVariable, .{ - .id_result_type = self.typeId(ptr_constant_struct_ty_ref), - .id_result = var_id, - .storage_class = storage_class, - .initializer = constant_struct_id, - }); - // TODO: Set alignment of OpVariable. - - try constant_section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{ - .id_result_type = self.typeId(ptr_ty_ref), - .id_result = result_id, - .operand = var_id, - }); - }, - } + try section.emit(self.spv.gpa, .OpVariable, .{ + .id_result_type = self.typeId(ptr_constant_struct_ty_ref), + .id_result = var_id, + .storage_class = storage_class, + .initializer = constant_struct_id, + }); + // TODO: Set alignment of OpVariable. + // TODO: We may be able to eliminate this cast. + try section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{ + .id_result_type = self.typeId(ptr_ty_ref), + .id_result = result_id, + .operand = var_id, + }); } /// This function generates a load for a constant in direct (ie, non-memory) representation. /// When the constant is simple, it can be generated directly using OpConstant instructions. When /// the constant is more complicated however, it needs to be lowered to an indirect constant, which - /// is then loaded using OpLoad. Such values are loaded into the Function address space by default. + /// is then loaded using OpLoad. Such values are loaded into the UniformConstant storage class by default. /// This function should only be called during function code generation. fn constant(self: *DeclGen, ty: Type, val: Value) !IdRef { const target = self.getTarget(); @@ -846,53 +903,27 @@ pub const DeclGen = struct { } }, else => { - // The value cannot be generated directly, so generate it as an indirect function-local - // constant, and then perform an OpLoad. - const ptr_id = self.spv.allocId(); + // The value cannot be generated directly, so generate it as an indirect constant, + // and then perform an OpLoad. const alignment = ty.abiAlignment(target); - try self.lowerIndirectConstant(ptr_id, ty, val, .Function, alignment); + const global_index = try self.spv.allocGlobal(); + log.debug("constant {}", .{global_index}); + const ptr_id = self.spv.beginGlobal(global_index); + defer self.spv.endGlobal(); + try self.lowerIndirectConstant(ptr_id, ty, val, .UniformConstant, alignment); try self.func.body.emit(self.spv.gpa, .OpLoad, .{ .id_result_type = result_ty_id, .id_result = result_id, .pointer = ptr_id, }); - // TODO: Convert bools? This logic should hook into `load`. + // TODO: Convert bools? This logic should hook into `load`. It should be a dead + // path though considering .Bool is handled above. }, } return result_id; } - fn genDeclRef(self: *DeclGen, result_ty_ref: SpvType.Ref, result_id: IdRef, decl_index: Decl.Index) Error!void { - // TODO: Clean up - const decl = self.module.declPtr(decl_index); - self.module.markDeclAlive(decl); - // _ = result_ty_ref; - // const decl_id = try self.constant(decl.ty, decl.val, .indirect); - // try self.variable(.global, result_id, result_ty_ref, decl_id); - const result_storage_class = self.spv.typeRefType(result_ty_ref).payload(.pointer).storage_class; - const indirect_result_id = if (result_storage_class != .CrossWorkgroup) - self.spv.allocId() - else - result_id; - - try self.lowerIndirectConstant( - indirect_result_id, - decl.ty, - decl.val, - .CrossWorkgroup, // TODO: Make this .Function if required - decl.@"align", - ); - const section = &self.spv.sections.types_globals_constants; - if (result_storage_class != .CrossWorkgroup) { - try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ - .id_result_type = self.typeId(result_ty_ref), - .id_result = result_id, - .pointer = indirect_result_id, - }); - } - } - /// 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); @@ -996,7 +1027,7 @@ 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.fmtDebug()}); + log.debug("resolveType: ty = {}", .{ty.fmt(self.module)}); const target = self.getTarget(); switch (ty.zigTypeTag()) { .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)), @@ -1042,23 +1073,30 @@ pub const DeclGen = struct { }; return try self.spv.arrayType(total_len, elem_ty_ref); }, - .Fn => { - // TODO: Put this somewhere in Sema.zig - if (ty.fnIsVarArgs()) - return self.fail("VarArgs functions are unsupported for SPIR-V", .{}); + .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. + // 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 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 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)); + 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; @@ -1196,14 +1234,16 @@ pub const DeclGen = struct { fn genDecl(self: *DeclGen) !void { const decl = self.module.declPtr(self.decl_index); - const result_id = try self.resolveDecl(self.decl_index); + const link = try self.resolveDecl(self.decl_index); if (decl.val.castTag(.function)) |_| { + log.debug("genDecl function {s} = {}", .{decl.name, link.func.result_id.id}); + assert(decl.ty.zigTypeTag() == .Fn); const prototype_id = try self.resolveTypeId(decl.ty); try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{ .id_result_type = try self.resolveTypeId(decl.ty.fnReturnType()), - .id_result = result_id, + .id_result = link.func.result_id, .function_control = .{}, // TODO: We can set inline here if the type requires it. .function_type = prototype_id, }); @@ -1243,7 +1283,7 @@ pub const DeclGen = struct { defer self.module.gpa.free(fqn); try self.spv.sections.debug_names.emit(self.gpa, .OpName, .{ - .target = result_id, + .target = link.func.result_id, .name = fqn, }); } else { @@ -1264,9 +1304,13 @@ pub const DeclGen = struct { else => storage_class, }; + const global_result_id = self.spv.beginGlobal(link.global); + defer self.spv.endGlobal(); + log.debug("genDecl {}", .{link.global}); + const var_result_id = switch (storage_class) { .Generic => self.spv.allocId(), - else => result_id, + else => global_result_id, }; try self.lowerIndirectConstant( @@ -1278,12 +1322,13 @@ pub const DeclGen = struct { ); if (storage_class == .Generic) { - const section = &self.spv.sections.types_globals_constants; + const section = &self.spv.globals.section; const ty_ref = try self.resolveType(decl.ty, .indirect); const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align"); + // TODO: Can we eliminate this cast? try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ .id_result_type = self.typeId(ptr_ty_ref), - .id_result = result_id, + .id_result = global_result_id, .pointer = var_result_id, }); } @@ -1972,6 +2017,7 @@ pub const DeclGen = struct { .id_result = result_id, .pointer = alloc_result_id, }), + // TODO: Can we do without this cast or move it to runtime? else => try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ .id_result_type = self.typeId(ptr_ty_ref), .id_result = result_id, diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index dc5e8053c1..7501ec5d92 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -55,6 +55,27 @@ pub const Fn = struct { } }; +/// Globals must be kept in order: operations involving globals must be ordered +/// so that the global declaration precedes any usage. +pub const Global = struct { + /// Index type to refer to a global by. + pub const Index = enum(u32) { _ }; + + /// The result-id to be used for this global declaration. Note that this does not + /// necessarily refer to an OpVariable instruction - it may also be the final result + /// id of a number of OpSpecConstantOp instructions. + result_id: IdRef, + /// The offset into `self.globals.section` of the first instruction of this global + /// declaration. + begin_inst: u32, + /// The past-end offset into `self.flobals.section`. + end_inst: u32, + /// The first dependency in the `self.globals.dependencies` array list. + begin_dep: u32, + /// The past-end dependency in `self.globals.dependencies`. + end_dep: u32, +}; + /// A general-purpose allocator which may be used to allocate resources for this module gpa: Allocator, @@ -102,6 +123,20 @@ source_file_names: std.StringHashMapUnmanaged(IdRef) = .{}, /// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref). type_cache: TypeCache = .{}, +/// The fields in this structure help to maintain the required order for global variables. +globals: struct { + /// The graph nodes of global variables present in the module. + nodes: std.ArrayListUnmanaged(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. + section: Section = .{}, + /// Holds a list of dependent global variables for each global variable. + dependencies: std.ArrayListUnmanaged(Global.Index) = .{}, + /// The global that initialization code/dependencies are currently being generated for, if any. + current_global: ?Global.Index = null, +} = .{}, + pub fn init(gpa: Allocator, arena: Allocator) Module { return .{ .gpa = gpa, @@ -124,6 +159,10 @@ pub fn deinit(self: *Module) void { self.source_file_names.deinit(self.gpa); self.type_cache.deinit(self.gpa); + self.globals.nodes.deinit(self.gpa); + self.globals.section.deinit(self.gpa); + self.globals.dependencies.deinit(self.gpa); + self.* = undefined; } @@ -141,18 +180,60 @@ pub fn idBound(self: Module) Word { return self.next_result_id; } +fn orderGlobalsInto( + self: Module, + global_index: Global.Index, + section: *Section, + seen: *std.DynamicBitSetUnmanaged, +) !void { + const node = self.globals.nodes.items[@enumToInt(global_index)]; + const deps = self.globals.dependencies.items[node.begin_dep .. node.end_dep]; + const insts = self.globals.section.instructions.items[node.begin_inst .. node.end_inst]; + + seen.set(@enumToInt(global_index)); + + for (deps) |dep| { + if (!seen.isSet(@enumToInt(dep))) { + try self.orderGlobalsInto(dep, section, seen); + } + } + + try section.instructions.appendSlice(self.gpa, insts); +} + +fn orderGlobals(self: Module) !Section { + const nodes = self.globals.nodes.items; + + var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, nodes.len); + defer seen.deinit(self.gpa); + + var ordered_globals = Section{}; + + for (0..nodes.len) |global_index| { + if (!seen.isSet(global_index)) { + try self.orderGlobalsInto(@intToEnum(Global.Index, @intCast(u32, global_index)), &ordered_globals, &seen); + } + } + + return ordered_globals; +} + /// Emit this module as a spir-v binary. pub fn flush(self: Module, file: std.fs.File) !void { // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction" const header = [_]Word{ spec.magic_number, - (1 << 16) | (5 << 8), + (1 << 16) | (4 << 8), // TODO: From cpu features 0, // TODO: Register Zig compiler magic number. self.idBound(), 0, // Schema (currently reserved for future use) }; + // TODO: Perform topological sort on the globals. + var globals = try self.orderGlobals(); + defer globals.deinit(self.gpa); + // Note: needs to be kept in order according to section 2.3! const buffers = &[_][]const Word{ &header, @@ -164,6 +245,7 @@ pub fn flush(self: Module, file: std.fs.File) !void { self.sections.debug_names.toWords(), self.sections.annotations.toWords(), self.sections.types_globals_constants.toWords(), + globals.toWords(), self.sections.functions.toWords(), }; @@ -279,6 +361,8 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { .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, @@ -634,3 +718,44 @@ pub fn decorateMember( .decoration = decoration, }); } + +pub fn allocGlobal(self: *Module) !Global.Index { + try self.globals.nodes.append(self.gpa, .{ + .result_id = self.allocId(), + .begin_inst = undefined, + .end_inst = undefined, + .begin_dep = undefined, + .end_dep = undefined, + }); + return @intToEnum(Global.Index, @intCast(u32, self.globals.nodes.items.len - 1)); +} + +pub fn globalPtr(self: *Module, index: Global.Index) *Global { + return &self.globals.nodes.items[@enumToInt(index)]; +} + +/// Begin generating the global for `index`. The previous global is finalized +/// at this point, and the global for `index` is made active. Any new calls to +/// `addGlobalDependency` will affect this global. After a new call to this function, +/// the prior active global cannot be modified again. +pub fn beginGlobal(self: *Module, index: Global.Index) IdRef { + const global = self.globalPtr(index); + global.begin_inst = @intCast(u32, self.globals.section.instructions.items.len); + global.begin_dep = @intCast(u32, self.globals.dependencies.items.len); + self.globals.current_global = index; + return global.result_id; +} + +/// Finalize the global. After this point, the current global cannot be modified anymore. +pub fn endGlobal(self: *Module) void { + const global = self.globalPtr(self.globals.current_global.?); + global.end_inst = @intCast(u32, self.globals.section.instructions.items.len); + global.end_dep = @intCast(u32, self.globals.dependencies.items.len); + self.globals.current_global = null; +} + +pub fn addGlobalDependency(self: *Module, dependency: Global.Index) !void { + assert(self.globals.current_global != null); + assert(self.globals.current_global.? != dependency); + try self.globals.dependencies.append(self.gpa, dependency); +} diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index c63e4a20af..7ec4c2cac6 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -46,7 +46,7 @@ base: link.File, spv: SpvModule, spv_arena: ArenaAllocator, -decl_ids: codegen.DeclMap, +decl_link: codegen.DeclLinkMap, pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV { const self = try gpa.create(SpirV); @@ -59,7 +59,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV { }, .spv = undefined, .spv_arena = ArenaAllocator.init(gpa), - .decl_ids = codegen.DeclMap.init(self.base.allocator), + .decl_link = codegen.DeclLinkMap.init(self.base.allocator), }; self.spv = SpvModule.init(gpa, self.spv_arena.allocator()); errdefer self.deinit(); @@ -100,7 +100,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option pub fn deinit(self: *SpirV) void { self.spv.deinit(); self.spv_arena.deinit(); - self.decl_ids.deinit(); + self.decl_link.deinit(); } pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void { @@ -108,7 +108,7 @@ pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liv @panic("Attempted to compile for architecture that was disabled by build configuration"); } - var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_ids); + var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link); defer decl_gen.deinit(); if (try decl_gen.gen(func.owner_decl, air, liveness)) |msg| { @@ -121,7 +121,7 @@ pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index) @panic("Attempted to compile for architecture that was disabled by build configuration"); } - var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_ids); + var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link); defer decl_gen.deinit(); if (try decl_gen.gen(decl_index, undefined, undefined)) |msg| { From 719d47d823a7e27ba0902b2878835297a6001fd0 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 9 Apr 2023 01:30:06 +0200 Subject: [PATCH 40/51] spirv: implement error set and error unions Implements lowering types and constants for error sets and error unions. --- src/codegen/spirv.zig | 70 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index b58f889f8e..3700b5bce5 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -763,6 +763,47 @@ pub const DeclGen = struct { try self.addUndef(layout.padding); }, + .ErrorSet => switch (val.tag()) { + .@"error" => { + const err_name = val.castTag(.@"error").?.data.name; + const kv = try dg.module.getErrorValue(err_name); + try self.addConstInt(u16, @intCast(u16, kv.value)); + }, + .zero => { + // Unactivated error set. + try self.addConstInt(u16, 0); + }, + else => unreachable, + }, + .ErrorUnion => { + const payload_ty = ty.errorUnionPayload(); + const is_pl = val.errorUnionIsPayload(); + const error_val = if (!is_pl) val else Value.initTag(.zero); + + if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { + return try self.lower(Type.anyerror, error_val); + } + + const payload_align = payload_ty.abiAlignment(target); + const error_align = Type.anyerror.abiAlignment(target); + + const payload_size = payload_ty.abiSize(target); + const error_size = Type.anyerror.abiAlignment(target); + const ty_size = ty.abiSize(target); + const padding = ty_size - payload_size - error_size; + + const payload_val = if (val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef); + + if (error_align > payload_align) { + try self.lower(Type.anyerror, error_val); + try self.lower(payload_ty, payload_val); + } else { + try self.lower(payload_ty, payload_val); + try self.lower(Type.anyerror, error_val); + } + + try self.addUndef(padding); + }, else => |tag| return dg.todo("indirect constant of type {s}", .{@tagName(tag)}), } } @@ -1209,6 +1250,35 @@ pub const DeclGen = struct { }); }, .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); + if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { + return error_ty_ref; + } + + const payload_ty_ref = try self.resolveType(payload_ty, .indirect); + + const payload_align = payload_ty.abiAlignment(target); + const error_align = Type.anyerror.abiAlignment(target); + + var members = std.BoundedArray(SpvType.Payload.Struct.Member, 2){}; + // Similar to unions, we're going to put the most aligned member first. + if (error_align > payload_align) { + // 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, From efe7fae6afe1ecdfc3838a97651dc617c4c747c2 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 9 Apr 2023 01:27:02 +0200 Subject: [PATCH 41/51] spirv: temporarily emit test kernels SPIR-V cannot represent function pointers without extensions that no vendor implements. For the time being, generate a test kernel for each error, so that we can at least run SOME tests. In the future we may be able to emulate function pointers in some way, but that is not today. --- src/codegen/spirv.zig | 128 ++++++++++++++++++++++++++++++----- src/codegen/spirv/Module.zig | 6 +- 2 files changed, 113 insertions(+), 21 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 3700b5bce5..3b155f42c3 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -272,7 +272,7 @@ pub const DeclGen = struct { if (!entry.found_existing) { if (decl.val.castTag(.function)) |_| { - entry.value_ptr.* = .{.func = .{ .result_id = result_id }}; + entry.value_ptr.* = .{ .func = .{ .result_id = result_id } }; } else { entry.value_ptr.* = .{ .global = try self.spv.allocGlobal() }; } @@ -418,11 +418,7 @@ pub const DeclGen = struct { fn genUndef(self: *DeclGen, ty_ref: SpvType.Ref) Error!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 } - ); + 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; } @@ -899,11 +895,12 @@ pub const DeclGen = struct { .initializer = constant_struct_id, }); // TODO: Set alignment of OpVariable. - // TODO: We may be able to eliminate this cast. + // TODO: We may be able to eliminate these casts. + const const_ptr_id = try self.makePointerConstant(section, ptr_constant_struct_ty_ref, var_id); try section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{ .id_result_type = self.typeId(ptr_ty_ref), .id_result = result_id, - .operand = var_id, + .operand = const_ptr_id, }); } @@ -1267,13 +1264,13 @@ pub const DeclGen = struct { // Similar to unions, we're going to put the most aligned member first. if (error_align > payload_align) { // Put the error first - members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" }); - members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" }); + 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" }); + members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" }); + members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" }); // TODO: ABI padding? } @@ -1302,12 +1299,81 @@ pub const DeclGen = struct { }; } + /// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure. + /// In order to be able to run tests, we "temporarily" lower test kernels into separate entry- + /// points. The test executor will then be able to invoke these to run the tests. + /// Note that tests are lowered according to std.builtin.TestFn, which is `fn () anyerror!void`. + /// (anyerror!void has the same layout as anyerror). + /// Each test declaration generates a function like. + /// %anyerror = OpTypeInt 0 16 + /// %p_anyerror = OpTypePointer CrossWorkgroup %anyerror + /// %K = OpTypeFunction %void %p_anyerror + /// + /// %test = OpFunction %void %K + /// %p_err = OpFunctionParameter %p_anyerror + /// %lbl = OpLabel + /// %result = OpFunctionCall %anyerror %func + /// OpStore %p_err %result + /// OpFunctionEnd + /// TODO is to also write out the error as a function call parameter, and to somehow fetch + /// the name of an error in the text executor. + fn generateTestEntryPoint(self: *DeclGen, name: []const u8, func: IdResult) !void { + const anyerror_ty_ref = try self.resolveType(Type.anyerror, .direct); + const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup, null); + 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_id = self.spv.allocId(); + const error_id = self.spv.allocId(); + const p_error_id = self.spv.allocId(); + + const section = &self.spv.sections.functions; + try section.emit(self.spv.gpa, .OpFunction, .{ + .id_result_type = self.typeId(void_ty_ref), + .id_result = kernel_id, + .function_control = .{}, + .function_type = self.typeId(kernel_proto_ty_ref), + }); + try section.emit(self.spv.gpa, .OpFunctionParameter, .{ + .id_result_type = self.typeId(ptr_anyerror_ty_ref), + .id_result = p_error_id, + }); + try section.emit(self.spv.gpa, .OpLabel, .{ + .id_result = self.spv.allocId(), + }); + try section.emit(self.spv.gpa, .OpFunctionCall, .{ + .id_result_type = self.typeId(anyerror_ty_ref), + .id_result = error_id, + .function = func, + }); + try section.emit(self.spv.gpa, .OpStore, .{ + .pointer = p_error_id, + .object = error_id, + }); + try section.emit(self.spv.gpa, .OpReturn, {}); + try section.emit(self.spv.gpa, .OpFunctionEnd, {}); + + try self.spv.sections.entry_points.emit(self.spv.gpa, .OpEntryPoint, .{ + .execution_model = .Kernel, + .entry_point = kernel_id, + .name = name, + }); + } + fn genDecl(self: *DeclGen) !void { const decl = self.module.declPtr(self.decl_index); const link = try self.resolveDecl(self.decl_index); if (decl.val.castTag(.function)) |_| { - log.debug("genDecl function {s} = {}", .{decl.name, link.func.result_id.id}); + log.debug("genDecl function {s} = {}", .{ decl.name, link.func.result_id.id }); assert(decl.ty.zigTypeTag() == .Fn); const prototype_id = try self.resolveTypeId(decl.ty); @@ -1356,6 +1422,10 @@ pub const DeclGen = struct { .target = link.func.result_id, .name = fqn, }); + + if (self.module.test_functions.contains(self.decl_index)) { + try self.generateTestEntryPoint(fqn, link.func.result_id); + } } else { const init_val = if (decl.val.castTag(.variable)) |payload| payload.data.init @@ -1396,6 +1466,7 @@ pub const DeclGen = struct { const ty_ref = try self.resolveType(decl.ty, .indirect); const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align"); // TODO: Can we eliminate this cast? + // TODO: Const-wash pointer try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ .id_result_type = self.typeId(ptr_ty_ref), .id_result = global_result_id, @@ -2036,6 +2107,24 @@ pub const DeclGen = struct { return try self.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index); } + /// We cannot use an OpVariable directly in an OpSpecConstantOp, but we can + /// after we insert a dummy AccessChain... + /// TODO: Get rid of this + fn makePointerConstant( + self: *DeclGen, + section: *SpvSection, + ptr_ty_ref: SpvType.Ref, + ptr_id: IdRef, + ) !IdRef { + const result_id = self.spv.allocId(); + try section.emitSpecConstantOp(self.spv.gpa, .OpInBoundsAccessChain, .{ + .id_result_type = self.typeId(ptr_ty_ref), + .id_result = result_id, + .base = ptr_id, + }); + return result_id; + } + fn variable( self: *DeclGen, comptime context: enum { function, global }, @@ -2088,11 +2177,14 @@ pub const DeclGen = struct { .pointer = alloc_result_id, }), // TODO: Can we do without this cast or move it to runtime? - else => try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ - .id_result_type = self.typeId(ptr_ty_ref), - .id_result = result_id, - .pointer = alloc_result_id, - }), + else => { + const const_ptr_id = try self.makePointerConstant(section, actual_ptr_ty_ref, alloc_result_id); + try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ + .id_result_type = self.typeId(ptr_ty_ref), + .id_result = result_id, + .pointer = const_ptr_id, + }); + }, } } diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 7501ec5d92..2bf1efeee2 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -187,8 +187,8 @@ fn orderGlobalsInto( seen: *std.DynamicBitSetUnmanaged, ) !void { const node = self.globals.nodes.items[@enumToInt(global_index)]; - const deps = self.globals.dependencies.items[node.begin_dep .. node.end_dep]; - const insts = self.globals.section.instructions.items[node.begin_inst .. node.end_inst]; + const deps = self.globals.dependencies.items[node.begin_dep..node.end_dep]; + const insts = self.globals.section.instructions.items[node.begin_inst..node.end_inst]; seen.set(@enumToInt(global_index)); @@ -725,7 +725,7 @@ pub fn allocGlobal(self: *Module) !Global.Index { .begin_inst = undefined, .end_inst = undefined, .begin_dep = undefined, - .end_dep = undefined, + .end_dep = undefined, }); return @intToEnum(Global.Index, @intCast(u32, self.globals.nodes.items.len - 1)); } From 405f7298acaa4818a26fdc93991c48705c19de15 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 8 Apr 2023 00:55:18 +0200 Subject: [PATCH 42/51] spirv: add decl dependencies for functions also Entry points need to be attributed with a complete list of global variables that they use. To that end, the global dependencies mechanism is extended to also allow functions - when flushing the module, the list of dependencies is examined to generate this list of global variable result-ids. --- src/codegen/spirv.zig | 195 +++++++++++++++++++---------------- src/codegen/spirv/Module.zig | 160 +++++++++++++++++----------- 2 files changed, 205 insertions(+), 150 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 3b155f42c3..1963724e8d 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -19,6 +19,7 @@ const Word = spec.Word; const IdRef = spec.IdRef; const IdResult = spec.IdResult; const IdResultType = spec.IdResultType; +const StorageClass = spec.StorageClass; const SpvModule = @import("spirv/Module.zig"); const SpvSection = @import("spirv/Section.zig"); @@ -37,23 +38,8 @@ const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct { incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock), }); -/// Linking information about a particular decl. -/// The active field of this enum depends on the type of the corresponding decl. -const DeclLink = union { - /// Linking information about a function. - /// Active when the decl is a function. - func: struct { - /// Result-id of the OpFunction instruction. - result_id: IdResult, - }, - /// Linking information about a global. This index points into the - /// SPIR-V module's `globals` array. - /// Active when the decl is a variable. - global: SpvModule.Global.Index, -}; - /// Maps Zig decl indices to linking SPIR-V linking information. -pub const DeclLinkMap = std.AutoHashMap(Module.Decl.Index, DeclLink); +pub const DeclLinkMap = std.AutoHashMap(Module.Decl.Index, SpvModule.Decl.Index); /// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that. pub const DeclGen = struct { @@ -251,8 +237,8 @@ pub const DeclGen = struct { .function => val.castTag(.function).?.data.owner_decl, else => unreachable, }; - const link = try self.resolveDecl(fn_decl_index); - return link.func.result_id; + const spv_decl_index = try self.resolveDecl(fn_decl_index); + return self.spv.declPtr(spv_decl_index).result_id; } return try self.constant(ty, val); @@ -263,19 +249,19 @@ pub const DeclGen = struct { /// Fetch or allocate a result id for decl index. This function also marks the decl as alive. /// Note: Function does not actually generate the decl. - fn resolveDecl(self: *DeclGen, decl_index: Module.Decl.Index) !DeclLink { + fn resolveDecl(self: *DeclGen, decl_index: Module.Decl.Index) !SpvModule.Decl.Index { const decl = self.module.declPtr(decl_index); self.module.markDeclAlive(decl); const entry = try self.decl_link.getOrPut(decl_index); - const result_id = self.spv.allocId(); - if (!entry.found_existing) { - if (decl.val.castTag(.function)) |_| { - entry.value_ptr.* = .{ .func = .{ .result_id = result_id } }; - } else { - entry.value_ptr.* = .{ .global = try self.spv.allocGlobal() }; - } + // TODO: Extern fn? + const kind: SpvModule.DeclKind = if (decl.val.tag() == .function) + .func + else + .global; + + entry.value_ptr.* = try self.spv.allocDecl(kind); } return entry.value_ptr.*; @@ -440,6 +426,8 @@ pub const DeclGen = struct { /// The partially filled last constant. /// If full, its flushed. partial_word: std.BoundedArray(u8, @sizeOf(Word)) = .{}, + /// The declaration dependencies of the constant we are lowering. + decl_deps: std.ArrayList(SpvModule.Decl.Index), /// Utility function to get the section that instructions should be lowered to. fn section(self: *@This()) *SpvSection { @@ -554,7 +542,7 @@ pub const DeclGen = struct { const ty_id = dg.typeId(ty_ref); const decl = dg.module.declPtr(decl_index); - const link = try dg.resolveDecl(decl_index); + const spv_decl_index = try dg.resolveDecl(decl_index); switch (decl.val.tag()) { .function => { @@ -569,14 +557,15 @@ pub const DeclGen = struct { const result_id = dg.spv.allocId(); log.debug("addDeclRef {s} = {}", .{ decl.name, result_id.id }); - const global = dg.spv.globalPtr(link.global); - try dg.spv.addGlobalDependency(link.global); + try self.decl_deps.append(spv_decl_index); + + const decl_id = dg.spv.declPtr(spv_decl_index).result_id; // TODO: Do we need a storage class cast here? // TODO: We can probably eliminate these casts try dg.spv.globals.section.emitSpecConstantOp(dg.spv.gpa, .OpBitcast, .{ .id_result_type = ty_id, .id_result = result_id, - .operand = global.result_id, + .operand = decl_id, }); try self.addPtr(ty_ref, result_id); @@ -810,10 +799,11 @@ pub const DeclGen = struct { /// pointer points to. Note: result is not necessarily an OpVariable instruction! fn lowerIndirectConstant( self: *DeclGen, - result_id: IdRef, + spv_decl_index: SpvModule.Decl.Index, ty: Type, val: Value, - storage_class: spec.StorageClass, + storage_class: StorageClass, + cast_to_generic: bool, alignment: u32, ) Error!void { // To simplify constant generation, we're going to generate constants as a word-array, and @@ -844,23 +834,27 @@ pub const DeclGen = struct { const ty_ref = try self.resolveType(ty, .indirect); const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, alignment); - const target = self.getTarget(); + // const target = self.getTarget(); - if (val.isUndef()) { - // Special case: the entire value is undefined. In this case, we can just - // generate an OpVariable with no initializer. - return try section.emit(self.spv.gpa, .OpVariable, .{ - .id_result_type = self.typeId(ptr_ty_ref), - .id_result = result_id, - .storage_class = storage_class, - }); - } else if (ty.abiSize(target) == 0) { - // Special case: if the type has no size, then return an undefined pointer. - return try section.emit(self.spv.gpa, .OpUndef, .{ - .id_result_type = self.typeId(ptr_ty_ref), - .id_result = result_id, - }); - } + // TODO: Fix the resulting global linking for these paths. + // if (val.isUndef()) { + // // Special case: the entire value is undefined. In this case, we can just + // // generate an OpVariable with no initializer. + // return try section.emit(self.spv.gpa, .OpVariable, .{ + // .id_result_type = self.typeId(ptr_ty_ref), + // .id_result = result_id, + // .storage_class = storage_class, + // }); + // } else if (ty.abiSize(target) == 0) { + // // Special case: if the type has no size, then return an undefined pointer. + // return try section.emit(self.spv.gpa, .OpUndef, .{ + // .id_result_type = self.typeId(ptr_ty_ref), + // .id_result = result_id, + // }); + // } + + // TODO: Capture the above stuff in here as well... + const begin_inst = self.spv.beginGlobal(); const u32_ty_ref = try self.intType(.unsigned, 32); var icl = IndirectConstantLowering{ @@ -869,10 +863,12 @@ pub const DeclGen = struct { .u32_ty_id = self.typeId(u32_ty_ref), .members = std.ArrayList(SpvType.Payload.Struct.Member).init(self.gpa), .initializers = std.ArrayList(IdRef).init(self.gpa), + .decl_deps = std.ArrayList(SpvModule.Decl.Index).init(self.gpa), }; defer icl.members.deinit(); defer icl.initializers.deinit(); + defer icl.decl_deps.deinit(); try icl.lower(ty, val); try icl.flush(); @@ -888,6 +884,7 @@ pub const DeclGen = struct { }); const var_id = self.spv.allocId(); + self.spv.globalPtr(spv_decl_index).?.result_id = var_id; try section.emit(self.spv.gpa, .OpVariable, .{ .id_result_type = self.typeId(ptr_constant_struct_ty_ref), .id_result = var_id, @@ -896,12 +893,32 @@ pub const DeclGen = struct { }); // TODO: Set alignment of OpVariable. // TODO: We may be able to eliminate these casts. + const const_ptr_id = try self.makePointerConstant(section, ptr_constant_struct_ty_ref, var_id); + const result_id = self.spv.declPtr(spv_decl_index).result_id; + + const bitcast_result_id = if (cast_to_generic) + self.spv.allocId() + else + result_id; + try section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{ .id_result_type = self.typeId(ptr_ty_ref), - .id_result = result_id, + .id_result = bitcast_result_id, .operand = const_ptr_id, }); + + if (cast_to_generic) { + const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, alignment); + try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ + .id_result_type = self.typeId(generic_ptr_ty_ref), + .id_result = result_id, + .pointer = bitcast_result_id, + }); + } + + try self.spv.declareDeclDeps(spv_decl_index, icl.decl_deps.items); + self.spv.endGlobal(spv_decl_index, begin_inst); } /// This function generates a load for a constant in direct (ie, non-memory) representation. @@ -940,19 +957,28 @@ pub const DeclGen = struct { try section.emit(self.spv.gpa, .OpConstantFalse, operands); } }, + // TODO: We can handle most pointers here (decl refs etc), because now they emit an extra + // OpVariable that is not really required. else => { // The value cannot be generated directly, so generate it as an indirect constant, // and then perform an OpLoad. const alignment = ty.abiAlignment(target); - const global_index = try self.spv.allocGlobal(); - log.debug("constant {}", .{global_index}); - const ptr_id = self.spv.beginGlobal(global_index); - defer self.spv.endGlobal(); - try self.lowerIndirectConstant(ptr_id, ty, val, .UniformConstant, alignment); + const spv_decl_index = try self.spv.allocDecl(.global); + + try self.lowerIndirectConstant( + spv_decl_index, + ty, + val, + .UniformConstant, + false, + alignment, + ); + try self.func.decl_deps.append(self.spv.gpa, spv_decl_index); + try self.func.body.emit(self.spv.gpa, .OpLoad, .{ .id_result_type = result_ty_id, .id_result = result_id, - .pointer = ptr_id, + .pointer = self.spv.declPtr(spv_decl_index).result_id, }); // TODO: Convert bools? This logic should hook into `load`. It should be a dead // path though considering .Bool is handled above. @@ -1289,7 +1315,7 @@ pub const DeclGen = struct { } } - fn spvStorageClass(as: std.builtin.AddressSpace) spec.StorageClass { + fn spvStorageClass(as: std.builtin.AddressSpace) StorageClass { return switch (as) { .generic => .Generic, // TODO: Disallow? .gs, .fs, .ss => unreachable, @@ -1370,16 +1396,17 @@ pub const DeclGen = struct { fn genDecl(self: *DeclGen) !void { const decl = self.module.declPtr(self.decl_index); - const link = try self.resolveDecl(self.decl_index); + const spv_decl_index = try self.resolveDecl(self.decl_index); + + const decl_id = self.spv.declPtr(spv_decl_index).result_id; + log.debug("genDecl {s} = {}", .{ decl.name, decl_id }); if (decl.val.castTag(.function)) |_| { - log.debug("genDecl function {s} = {}", .{ decl.name, link.func.result_id.id }); - assert(decl.ty.zigTypeTag() == .Fn); const prototype_id = try self.resolveTypeId(decl.ty); try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{ .id_result_type = try self.resolveTypeId(decl.ty.fnReturnType()), - .id_result = link.func.result_id, + .id_result = decl_id, .function_control = .{}, // TODO: We can set inline here if the type requires it. .function_type = prototype_id, }); @@ -1413,18 +1440,18 @@ pub const DeclGen = struct { // Append the actual code into the functions section. try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {}); - try self.spv.addFunction(self.func); + try self.spv.addFunction(spv_decl_index, self.func); const fqn = try decl.getFullyQualifiedName(self.module); defer self.module.gpa.free(fqn); try self.spv.sections.debug_names.emit(self.gpa, .OpName, .{ - .target = link.func.result_id, + .target = decl_id, .name = fqn, }); if (self.module.test_functions.contains(self.decl_index)) { - try self.generateTestEntryPoint(fqn, link.func.result_id); + try self.generateTestEntryPoint(fqn, decl_id); } } else { const init_val = if (decl.val.castTag(.variable)) |payload| @@ -1438,41 +1465,33 @@ pub const DeclGen = struct { // TODO: integrate with variable(). - const storage_class = spvStorageClass(decl.@"addrspace"); - const actual_storage_class = switch (storage_class) { + const final_storage_class = spvStorageClass(decl.@"addrspace"); + const actual_storage_class = switch (final_storage_class) { .Generic => .CrossWorkgroup, - else => storage_class, - }; - - const global_result_id = self.spv.beginGlobal(link.global); - defer self.spv.endGlobal(); - log.debug("genDecl {}", .{link.global}); - - const var_result_id = switch (storage_class) { - .Generic => self.spv.allocId(), - else => global_result_id, + else => final_storage_class, }; try self.lowerIndirectConstant( - var_result_id, + spv_decl_index, decl.ty, init_val, actual_storage_class, + final_storage_class == .Generic, decl.@"align", ); - if (storage_class == .Generic) { - const section = &self.spv.globals.section; - const ty_ref = try self.resolveType(decl.ty, .indirect); - const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align"); - // TODO: Can we eliminate this cast? - // TODO: Const-wash pointer - try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ - .id_result_type = self.typeId(ptr_ty_ref), - .id_result = global_result_id, - .pointer = var_result_id, - }); - } + // if (storage_class == .Generic) { + // const section = &self.spv.globals.section; + // const ty_ref = try self.resolveType(decl.ty, .indirect); + // const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align"); + // // TODO: Can we eliminate this cast? + // // TODO: Const-wash pointer? + // try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ + // .id_result_type = self.typeId(ptr_ty_ref), + // .id_result = global_result_id, + // .pointer = casted_result_id, + // }); + // } } } diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index 2bf1efeee2..eddaa858eb 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -39,41 +39,58 @@ pub const Fn = struct { /// This section should also contain the OpFunctionEnd instruction marking /// the end of this function definition. body: Section = .{}, + /// The decl dependencies that this function depends on. + decl_deps: std.ArrayListUnmanaged(Decl.Index) = .{}, /// Reset this function without deallocating resources, so that /// it may be used to emit code for another function. pub fn reset(self: *Fn) void { self.prologue.reset(); self.body.reset(); + self.decl_deps.items.len = 0; } /// Free the resources owned by this function. pub fn deinit(self: *Fn, a: Allocator) void { self.prologue.deinit(a); self.body.deinit(a); + self.decl_deps.deinit(a); self.* = undefined; } }; +/// Declarations, both functions and globals, can have dependencies. These are used for 2 things: +/// - Globals must be declared before they are used, also between globals. The compiler processes +/// globals unordered, so we must use the dependencies here to figure out how to order the globals +/// in the final module. The Globals structure is also used for that. +/// - Entry points must declare the complete list of OpVariable instructions that they access. +/// For these we use the same dependency structure. +/// In this mechanism, globals will only depend on other globals, while functions may depend on +/// globals or other functions. +pub const Decl = struct { + /// Index to refer to a Decl by. + pub const Index = enum(u32) { _ }; + + /// The result-id to be used for this declaration. This is the final result-id + /// of the decl, which may be an OpFunction, OpVariable, or the result of a sequence + /// of OpSpecConstantOp operations. + result_id: IdRef, + /// The offset of the first dependency of this decl in the `decl_deps` array. + begin_dep: u32, + /// The past-end offset of the dependencies of this decl in the `decl_deps` array. + end_dep: u32, +}; + /// Globals must be kept in order: operations involving globals must be ordered /// so that the global declaration precedes any usage. pub const Global = struct { - /// Index type to refer to a global by. - pub const Index = enum(u32) { _ }; - - /// The result-id to be used for this global declaration. Note that this does not - /// necessarily refer to an OpVariable instruction - it may also be the final result - /// id of a number of OpSpecConstantOp instructions. + /// This is the result-id of the OpVariable instruction that declares the global. result_id: IdRef, /// The offset into `self.globals.section` of the first instruction of this global /// declaration. begin_inst: u32, /// The past-end offset into `self.flobals.section`. end_inst: u32, - /// The first dependency in the `self.globals.dependencies` array list. - begin_dep: u32, - /// The past-end dependency in `self.globals.dependencies`. - end_dep: u32, }; /// A general-purpose allocator which may be used to allocate resources for this module @@ -123,18 +140,19 @@ source_file_names: std.StringHashMapUnmanaged(IdRef) = .{}, /// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref). type_cache: TypeCache = .{}, +/// Set of Decls, referred to by Decl.Index. +decls: std.ArrayListUnmanaged(Decl) = .{}, + +decl_deps: std.ArrayListUnmanaged(Decl.Index) = .{}, + /// The fields in this structure help to maintain the required order for global variables. globals: struct { - /// The graph nodes of global variables present in the module. - nodes: std.ArrayListUnmanaged(Global) = .{}, + /// Set of globals, referred to by Decl.Index. + 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. section: Section = .{}, - /// Holds a list of dependent global variables for each global variable. - dependencies: std.ArrayListUnmanaged(Global.Index) = .{}, - /// The global that initialization code/dependencies are currently being generated for, if any. - current_global: ?Global.Index = null, } = .{}, pub fn init(gpa: Allocator, arena: Allocator) Module { @@ -159,9 +177,11 @@ pub fn deinit(self: *Module) void { self.source_file_names.deinit(self.gpa); self.type_cache.deinit(self.gpa); - self.globals.nodes.deinit(self.gpa); + self.decls.deinit(self.gpa); + self.decl_deps.deinit(self.gpa); + + self.globals.globals.deinit(self.gpa); self.globals.section.deinit(self.gpa); - self.globals.dependencies.deinit(self.gpa); self.* = undefined; } @@ -181,16 +201,17 @@ pub fn idBound(self: Module) Word { } fn orderGlobalsInto( - self: Module, - global_index: Global.Index, + self: *Module, + index: Decl.Index, section: *Section, seen: *std.DynamicBitSetUnmanaged, ) !void { - const node = self.globals.nodes.items[@enumToInt(global_index)]; - const deps = self.globals.dependencies.items[node.begin_dep..node.end_dep]; - const insts = self.globals.section.instructions.items[node.begin_inst..node.end_inst]; + const decl = self.declPtr(index); + const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep]; + const global = self.globalPtr(index).?; + const insts = self.globals.section.instructions.items[global.begin_inst..global.end_inst]; - seen.set(@enumToInt(global_index)); + seen.set(@enumToInt(index)); for (deps) |dep| { if (!seen.isSet(@enumToInt(dep))) { @@ -201,17 +222,16 @@ fn orderGlobalsInto( try section.instructions.appendSlice(self.gpa, insts); } -fn orderGlobals(self: Module) !Section { - const nodes = self.globals.nodes.items; +fn orderGlobals(self: *Module) !Section { + const globals = self.globals.globals.keys(); - var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, nodes.len); + var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len); defer seen.deinit(self.gpa); var ordered_globals = Section{}; - - for (0..nodes.len) |global_index| { - if (!seen.isSet(global_index)) { - try self.orderGlobalsInto(@intToEnum(Global.Index, @intCast(u32, global_index)), &ordered_globals, &seen); + for (globals) |decl_index| { + if (!seen.isSet(@enumToInt(decl_index))) { + try self.orderGlobalsInto(decl_index, &ordered_globals, &seen); } } @@ -219,12 +239,14 @@ fn orderGlobals(self: Module) !Section { } /// Emit this module as a spir-v binary. -pub fn flush(self: Module, file: std.fs.File) !void { +pub fn flush(self: *Module, file: std.fs.File) !void { // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction" const header = [_]Word{ spec.magic_number, - (1 << 16) | (4 << 8), // TODO: From cpu features + // TODO: From cpu features + // Emit SPIR-V 1.4 for now. This is the highest version that Intel's CPU OpenCL supports. + (1 << 16) | (4 << 8), 0, // TODO: Register Zig compiler magic number. self.idBound(), 0, // Schema (currently reserved for future use) @@ -265,9 +287,10 @@ pub fn flush(self: Module, file: std.fs.File) !void { } /// Merge the sections making up a function declaration into this module. -pub fn addFunction(self: *Module, func: Fn) !void { +pub fn addFunction(self: *Module, decl_index: Decl.Index, func: Fn) !void { try self.sections.functions.append(self.gpa, func.prologue); try self.sections.functions.append(self.gpa, func.body); + try self.declareDeclDeps(decl_index, func.decl_deps.items); } /// Fetch the result-id of an OpString instruction that encodes the path of the source @@ -719,43 +742,56 @@ pub fn decorateMember( }); } -pub fn allocGlobal(self: *Module) !Global.Index { - try self.globals.nodes.append(self.gpa, .{ +pub const DeclKind = enum { + func, + global, +}; + +pub fn allocDecl(self: *Module, kind: DeclKind) !Decl.Index { + try self.decls.append(self.gpa, .{ .result_id = self.allocId(), - .begin_inst = undefined, - .end_inst = undefined, .begin_dep = undefined, .end_dep = undefined, }); - return @intToEnum(Global.Index, @intCast(u32, self.globals.nodes.items.len - 1)); + const index = @intToEnum(Decl.Index, @intCast(u32, self.decls.items.len - 1)); + switch (kind) { + .func => {}, + // If the decl represents a global, also allocate a global node. + .global => try self.globals.globals.putNoClobber(self.gpa, index, .{ + .result_id = undefined, + .begin_inst = undefined, + .end_inst = undefined, + }), + } + + return index; } -pub fn globalPtr(self: *Module, index: Global.Index) *Global { - return &self.globals.nodes.items[@enumToInt(index)]; +pub fn declPtr(self: *Module, index: Decl.Index) *Decl { + return &self.decls.items[@enumToInt(index)]; } -/// Begin generating the global for `index`. The previous global is finalized -/// at this point, and the global for `index` is made active. Any new calls to -/// `addGlobalDependency` will affect this global. After a new call to this function, -/// the prior active global cannot be modified again. -pub fn beginGlobal(self: *Module, index: Global.Index) IdRef { - const global = self.globalPtr(index); - global.begin_inst = @intCast(u32, self.globals.section.instructions.items.len); - global.begin_dep = @intCast(u32, self.globals.dependencies.items.len); - self.globals.current_global = index; - return global.result_id; +pub fn globalPtr(self: *Module, index: Decl.Index) ?*Global { + return self.globals.globals.getPtr(index); } -/// Finalize the global. After this point, the current global cannot be modified anymore. -pub fn endGlobal(self: *Module) void { - const global = self.globalPtr(self.globals.current_global.?); +/// Declare ALL dependencies for a decl. +pub fn declareDeclDeps(self: *Module, decl_index: Decl.Index, deps: []const Decl.Index) !void { + const begin_dep = @intCast(u32, self.decl_deps.items.len); + try self.decl_deps.appendSlice(self.gpa, deps); + const end_dep = @intCast(u32, self.decl_deps.items.len); + + const decl = self.declPtr(decl_index); + decl.begin_dep = begin_dep; + decl.end_dep = end_dep; +} + +pub fn beginGlobal(self: *Module) u32 { + return @intCast(u32, self.globals.section.instructions.items.len); +} + +pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32) void { + const global = self.globalPtr(global_index).?; + global.begin_inst = begin_inst; global.end_inst = @intCast(u32, self.globals.section.instructions.items.len); - global.end_dep = @intCast(u32, self.globals.dependencies.items.len); - self.globals.current_global = null; -} - -pub fn addGlobalDependency(self: *Module, dependency: Global.Index) !void { - assert(self.globals.current_global != null); - assert(self.globals.current_global.? != dependency); - try self.globals.dependencies.append(self.gpa, dependency); } From 3f2025f59e4488bff9e046cabbbff047fa1db508 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 8 Apr 2023 01:37:00 +0200 Subject: [PATCH 43/51] spirv: emit interface variables for entry points Also actually implement generating the OpEntryPoint instructions. --- src/codegen/spirv.zig | 24 +++++---- src/codegen/spirv/Assembler.zig | 4 +- src/codegen/spirv/Module.zig | 91 ++++++++++++++++++++++++++++++--- 3 files changed, 101 insertions(+), 18 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 1963724e8d..f813c393cd 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1343,7 +1343,7 @@ pub const DeclGen = struct { /// OpFunctionEnd /// TODO is to also write out the error as a function call parameter, and to somehow fetch /// the name of an error in the text executor. - fn generateTestEntryPoint(self: *DeclGen, name: []const u8, func: IdResult) !void { + 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, null); const void_ty_ref = try self.resolveType(Type.void, .direct); @@ -1357,7 +1357,11 @@ pub const DeclGen = struct { break :blk try self.spv.resolveType(SpvType.initPayload(&proto_payload.base)); }; - const kernel_id = self.spv.allocId(); + const test_id = self.spv.declPtr(spv_test_decl_index).result_id; + + const spv_decl_index = try self.spv.allocDecl(.func); + const kernel_id = self.spv.declPtr(spv_decl_index).result_id; + const error_id = self.spv.allocId(); const p_error_id = self.spv.allocId(); @@ -1378,7 +1382,7 @@ pub const DeclGen = struct { try section.emit(self.spv.gpa, .OpFunctionCall, .{ .id_result_type = self.typeId(anyerror_ty_ref), .id_result = error_id, - .function = func, + .function = test_id, }); try section.emit(self.spv.gpa, .OpStore, .{ .pointer = p_error_id, @@ -1387,11 +1391,13 @@ pub const DeclGen = struct { try section.emit(self.spv.gpa, .OpReturn, {}); try section.emit(self.spv.gpa, .OpFunctionEnd, {}); - try self.spv.sections.entry_points.emit(self.spv.gpa, .OpEntryPoint, .{ - .execution_model = .Kernel, - .entry_point = kernel_id, - .name = name, - }); + try self.spv.declareDeclDeps(spv_decl_index, &.{spv_test_decl_index}); + + // Just generate a quick other name because the intel runtime crashes when the entry- + // point name is the same as a different OpName. + const test_name = try std.fmt.allocPrint(self.gpa, "test {s}", .{name}); + defer self.gpa.free(test_name); + try self.spv.declareEntryPoint(spv_decl_index, test_name); } fn genDecl(self: *DeclGen) !void { @@ -1451,7 +1457,7 @@ pub const DeclGen = struct { }); if (self.module.test_functions.contains(self.decl_index)) { - try self.generateTestEntryPoint(fqn, decl_id); + try self.generateTestEntryPoint(fqn, spv_decl_index); } } else { const init_val = if (decl.val.castTag(.variable)) |payload| diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig index 5f9726a0fe..a8ff5175d8 100644 --- a/src/codegen/spirv/Assembler.zig +++ b/src/codegen/spirv/Assembler.zig @@ -435,10 +435,12 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue { .Annotation => &self.spv.sections.annotations, .TypeDeclaration => unreachable, // Handled elsewhere. else => switch (self.inst.opcode) { - .OpEntryPoint => &self.spv.sections.entry_points, + // TODO: This should emit a proper entry point. + .OpEntryPoint => unreachable, // &self.spv.sections.entry_points, .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes, .OpVariable => switch (@intToEnum(spec.StorageClass, operands[2].value)) { .Function => &self.func.prologue, + // TODO: Emit a decl dependency else => &self.spv.sections.types_globals_constants, }, // Default case - to be worked out further. diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index eddaa858eb..f7d392e9e3 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -93,6 +93,14 @@ pub const Global = struct { end_inst: u32, }; +/// This models a kernel entry point. +pub const EntryPoint = struct { + /// The declaration that should be exported. + decl_index: Decl.Index, + /// The name of the kernel to be exported. + name: []const u8, +}; + /// A general-purpose allocator which may be used to allocate resources for this module gpa: Allocator, @@ -107,8 +115,7 @@ sections: struct { extensions: Section = .{}, // OpExtInstImport instructions - skip for now. // memory model defined by target, not required here. - /// OpEntryPoint instructions. - entry_points: Section = .{}, + /// OpEntryPoint instructions - Handled by `self.entry_points`. /// OpExecutionMode and OpExecutionModeId instructions. execution_modes: Section = .{}, /// OpString, OpSourcExtension, OpSource, OpSourceContinued. @@ -143,8 +150,13 @@ type_cache: TypeCache = .{}, /// Set of Decls, referred to by Decl.Index. decls: std.ArrayListUnmanaged(Decl) = .{}, +/// List of dependencies, per decl. This list holds all the dependencies, sliced by the +/// begin_dep and end_dep in `self.decls`. decl_deps: std.ArrayListUnmanaged(Decl.Index) = .{}, +/// The list of entry points that should be exported from this module. +entry_points: std.ArrayListUnmanaged(EntryPoint) = .{}, + /// The fields in this structure help to maintain the required order for global variables. globals: struct { /// Set of globals, referred to by Decl.Index. @@ -166,7 +178,6 @@ pub fn init(gpa: Allocator, arena: Allocator) Module { pub fn deinit(self: *Module) void { self.sections.capabilities.deinit(self.gpa); self.sections.extensions.deinit(self.gpa); - self.sections.entry_points.deinit(self.gpa); self.sections.execution_modes.deinit(self.gpa); self.sections.debug_strings.deinit(self.gpa); self.sections.debug_names.deinit(self.gpa); @@ -180,6 +191,8 @@ pub fn deinit(self: *Module) void { self.decls.deinit(self.gpa); self.decl_deps.deinit(self.gpa); + self.entry_points.deinit(self.gpa); + self.globals.globals.deinit(self.gpa); self.globals.section.deinit(self.gpa); @@ -202,16 +215,16 @@ pub fn idBound(self: Module) Word { fn orderGlobalsInto( self: *Module, - index: Decl.Index, + decl_index: Decl.Index, section: *Section, seen: *std.DynamicBitSetUnmanaged, ) !void { - const decl = self.declPtr(index); + const decl = self.declPtr(decl_index); const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep]; - const global = self.globalPtr(index).?; + const global = self.globalPtr(decl_index).?; const insts = self.globals.section.instructions.items[global.begin_inst..global.end_inst]; - seen.set(@enumToInt(index)); + seen.set(@enumToInt(decl_index)); for (deps) |dep| { if (!seen.isSet(@enumToInt(dep))) { @@ -229,6 +242,8 @@ fn orderGlobals(self: *Module) !Section { defer seen.deinit(self.gpa); var ordered_globals = Section{}; + errdefer ordered_globals.deinit(self.gpa); + for (globals) |decl_index| { if (!seen.isSet(@enumToInt(decl_index))) { try self.orderGlobalsInto(decl_index, &ordered_globals, &seen); @@ -238,6 +253,56 @@ fn orderGlobals(self: *Module) !Section { return ordered_globals; } +fn addEntryPointDeps( + self: *Module, + decl_index: Decl.Index, + seen: *std.DynamicBitSetUnmanaged, + interface: *std.ArrayList(IdRef), +) !void { + const decl = self.declPtr(decl_index); + const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep]; + + seen.set(@enumToInt(decl_index)); + + if (self.globalPtr(decl_index)) |global| { + try interface.append(global.result_id); + } + + for (deps) |dep| { + if (!seen.isSet(@enumToInt(dep))) { + try self.addEntryPointDeps(dep, seen, interface); + } + } +} + +fn entryPoints(self: *Module) !Section { + var entry_points = Section{}; + errdefer entry_points.deinit(self.gpa); + + var interface = std.ArrayList(IdRef).init(self.gpa); + defer interface.deinit(); + + var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len); + defer seen.deinit(self.gpa); + + for (self.entry_points.items) |entry_point| { + interface.items.len = 0; + seen.setRangeValue(.{ .start = 0, .end = self.decls.items.len }, false); + + try self.addEntryPointDeps(entry_point.decl_index, &seen, &interface); + + const entry_point_id = self.declPtr(entry_point.decl_index).result_id; + try entry_points.emit(self.gpa, .OpEntryPoint, .{ + .execution_model = .Kernel, + .entry_point = entry_point_id, + .name = entry_point.name, + .interface = interface.items, + }); + } + + return entry_points; +} + /// Emit this module as a spir-v binary. pub fn flush(self: *Module, file: std.fs.File) !void { // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction" @@ -256,12 +321,15 @@ pub fn flush(self: *Module, file: std.fs.File) !void { var globals = try self.orderGlobals(); defer globals.deinit(self.gpa); + var entry_points = try self.entryPoints(); + defer entry_points.deinit(self.gpa); + // Note: needs to be kept in order according to section 2.3! const buffers = &[_][]const Word{ &header, self.sections.capabilities.toWords(), self.sections.extensions.toWords(), - self.sections.entry_points.toWords(), + entry_points.toWords(), self.sections.execution_modes.toWords(), self.sections.debug_strings.toWords(), self.sections.debug_names.toWords(), @@ -795,3 +863,10 @@ pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32) void global.begin_inst = begin_inst; global.end_inst = @intCast(u32, self.globals.section.instructions.items.len); } + +pub fn declareEntryPoint(self: *Module, decl_index: Decl.Index, name: []const u8) !void { + try self.entry_points.append(self.gpa, .{ + .decl_index = decl_index, + .name = try self.arena.dupe(u8, name), + }); +} From f12beb857ad7868396a4246b8f62f83f625c0978 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 8 Apr 2023 14:17:34 +0200 Subject: [PATCH 44/51] amdgpu,nvptx: unify kernel calling conventions AmdgpuKernel and NvptxKernel are unified into a Kernel calling convention. There is really no reason for these to be separate; no backend is allowed to emit the calling convention of the other. This is in the same spirit as the .Interrupt calling convention lowering to different LLVM calling conventions, and opens the way for SPIR-V kernels to be exported using the Kernel calling convention. --- lib/std/builtin.zig | 3 +-- src/Sema.zig | 32 +++++++++++++++----------------- src/codegen/llvm.zig | 5 +---- src/link/NvPtx.zig | 2 +- src/type.zig | 7 +++++-- test/nvptx.zig | 8 ++++---- 6 files changed, 27 insertions(+), 30 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 869756fe5c..1d28e69d61 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -160,8 +160,7 @@ pub const CallingConvention = enum { AAPCSVFP, SysV, Win64, - PtxKernel, - AmdgpuKernel, + Kernel, }; /// This data structure is used by the Zig language code generation and diff --git a/src/Sema.zig b/src/Sema.zig index c3c9345205..e538334fcd 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8883,7 +8883,7 @@ fn funcCommon( }; return sema.failWithOwnedErrorMsg(msg); } - if (!ret_poison and !Type.fnCallingConventionAllowsZigTypes(cc_resolved) and !try sema.validateExternType(return_type, .ret_ty)) { + if (!ret_poison and !Type.fnCallingConventionAllowsZigTypes(target, cc_resolved) and !try sema.validateExternType(return_type, .ret_ty)) { const msg = msg: { const msg = try sema.errMsg(block, ret_ty_src, "return type '{}' not allowed in function with calling convention '{s}'", .{ return_type.fmt(sema.mod), @tagName(cc_resolved), @@ -8961,13 +8961,9 @@ fn funcCommon( .x86_64 => null, else => @as([]const u8, "x86_64"), }, - .PtxKernel => switch (arch) { - .nvptx, .nvptx64 => null, - else => @as([]const u8, "nvptx and nvptx64"), - }, - .AmdgpuKernel => switch (arch) { - .amdgcn => null, - else => @as([]const u8, "amdgcn"), + .Kernel => switch (arch) { + .nvptx, .nvptx64, .amdgcn, .spirv32, .spirv64 => null, + else => @as([]const u8, "nvptx, amdgcn and SPIR-V"), }, }) |allowed_platform| { return sema.fail(block, cc_src, "callconv '{s}' is only available on {s}, not {s}", .{ @@ -9093,10 +9089,11 @@ fn analyzeParameter( comptime_params[i] = param.is_comptime or requires_comptime; const this_generic = param.ty.tag() == .generic_poison; is_generic.* = is_generic.* or this_generic; - if (param.is_comptime and !Type.fnCallingConventionAllowsZigTypes(cc)) { + const target = sema.mod.getTarget(); + if (param.is_comptime and !Type.fnCallingConventionAllowsZigTypes(target, cc)) { return sema.fail(block, param_src, "comptime parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)}); } - if (this_generic and !sema.no_partial_func_ty and !Type.fnCallingConventionAllowsZigTypes(cc)) { + if (this_generic and !sema.no_partial_func_ty and !Type.fnCallingConventionAllowsZigTypes(target, cc)) { return sema.fail(block, param_src, "generic parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)}); } if (!param.ty.isValidParamType()) { @@ -9112,7 +9109,7 @@ fn analyzeParameter( }; return sema.failWithOwnedErrorMsg(msg); } - if (!this_generic and !Type.fnCallingConventionAllowsZigTypes(cc) and !try sema.validateExternType(param.ty, .param_ty)) { + if (!this_generic and !Type.fnCallingConventionAllowsZigTypes(target, cc) and !try sema.validateExternType(param.ty, .param_ty)) { const msg = msg: { const msg = try sema.errMsg(block, param_src, "parameter of type '{}' not allowed in function with calling convention '{s}'", .{ param.ty.fmt(sema.mod), @tagName(cc), @@ -22786,12 +22783,13 @@ fn validateExternType( }, .Fn => { if (position != .other) return false; - return switch (ty.fnCallingConvention()) { - // For now we want to authorize PTX kernel to use zig objects, even if we end up exposing the ABI. - // The goal is to experiment with more integrated CPU/GPU code. - .PtxKernel => true, - else => !Type.fnCallingConventionAllowsZigTypes(ty.fnCallingConvention()), - }; + const target = sema.mod.getTarget(); + // For now we want to authorize PTX kernel to use zig objects, even if we end up exposing the ABI. + // The goal is to experiment with more integrated CPU/GPU code. + if (ty.fnCallingConvention() == .Kernel and (target.cpu.arch == .nvptx or target.cpu.arch == .nvptx64)) { + return true; + } + return !Type.fnCallingConventionAllowsZigTypes(target, ty.fnCallingConvention()); }, .Enum => { var buf: Type.Payload.Bits = undefined; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 233ec21ac1..80c9f0d024 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -10350,11 +10350,8 @@ fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) llvm.Ca .Signal => .AVR_SIGNAL, .SysV => .X86_64_SysV, .Win64 => .Win64, - .PtxKernel => return switch (target.cpu.arch) { + .Kernel => return switch (target.cpu.arch) { .nvptx, .nvptx64 => .PTX_Kernel, - else => unreachable, - }, - .AmdgpuKernel => return switch (target.cpu.arch) { .amdgcn => .AMDGPU_KERNEL, else => unreachable, }, diff --git a/src/link/NvPtx.zig b/src/link/NvPtx.zig index 4c6c6fd8be..c542241cd9 100644 --- a/src/link/NvPtx.zig +++ b/src/link/NvPtx.zig @@ -1,7 +1,7 @@ //! NVidia PTX (Paralle Thread Execution) //! https://docs.nvidia.com/cuda/parallel-thread-execution/index.html //! For this we rely on the nvptx backend of LLVM -//! Kernel functions need to be marked both as "export" and "callconv(.PtxKernel)" +//! Kernel functions need to be marked both as "export" and "callconv(.Kernel)" const NvPtx = @This(); diff --git a/src/type.zig b/src/type.zig index 15525f14eb..9f1905b1ad 100644 --- a/src/type.zig +++ b/src/type.zig @@ -4796,9 +4796,12 @@ pub const Type = extern union { } /// Asserts the type is a function. - pub fn fnCallingConventionAllowsZigTypes(cc: std.builtin.CallingConvention) bool { + pub fn fnCallingConventionAllowsZigTypes(target: Target, cc: std.builtin.CallingConvention) bool { return switch (cc) { - .Unspecified, .Async, .Inline, .PtxKernel => true, + .Unspecified, .Async, .Inline => true, + // For now we want to authorize PTX kernel to use zig objects, even if we end up exposing the ABI. + // The goal is to experiment with more integrated CPU/GPU code. + .Kernel => target.cpu.arch == .nvptx or target.cpu.arch == .nvptx64, else => false, }; } diff --git a/test/nvptx.zig b/test/nvptx.zig index 57853a657d..0bdc9455f7 100644 --- a/test/nvptx.zig +++ b/test/nvptx.zig @@ -10,7 +10,7 @@ pub fn addCases(ctx: *Cases) !void { \\ return a + b; \\} \\ - \\pub export fn add_and_substract(a: i32, out: *i32) callconv(.PtxKernel) void { + \\pub export fn add_and_substract(a: i32, out: *i32) callconv(.Kernel) void { \\ const x = add(a, 7); \\ var y = add(2, 0); \\ y -= x; @@ -29,7 +29,7 @@ pub fn addCases(ctx: *Cases) !void { \\ ); \\} \\ - \\pub export fn special_reg(a: []const i32, out: []i32) callconv(.PtxKernel) void { + \\pub export fn special_reg(a: []const i32, out: []i32) callconv(.Kernel) void { \\ const i = threadIdX(); \\ out[i] = a[i] + 7; \\} @@ -42,7 +42,7 @@ pub fn addCases(ctx: *Cases) !void { case.addCompile( \\var x: i32 addrspace(.global) = 0; \\ - \\pub export fn increment(out: *i32) callconv(.PtxKernel) void { + \\pub export fn increment(out: *i32) callconv(.Kernel) void { \\ x += 1; \\ out.* = x; \\} @@ -59,7 +59,7 @@ pub fn addCases(ctx: *Cases) !void { \\} \\ \\ var _sdata: [1024]f32 addrspace(.shared) = undefined; - \\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.PtxKernel) void { + \\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.Kernel) void { \\ var sdata = @addrSpaceCast(.generic, &_sdata); \\ const tid: u32 = threadIdX(); \\ var sum = d_x[tid]; From e389f524c9a09719ea2f6b684c0bf6f3fe4c3c9b Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 8 Apr 2023 14:55:39 +0200 Subject: [PATCH 45/51] spirv: export functions with .Kernel callconv as entry point Exported functions which have the .Kernel calling convention are now exported as entry point. This also works with @export. --- src/codegen/spirv.zig | 1 + src/link/SpirV.zig | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index f813c393cd..aebe16a10a 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1456,6 +1456,7 @@ pub const DeclGen = struct { .name = fqn, }); + // Temporarily generate a test kernel declaration if this is a test function. if (self.module.test_functions.contains(self.decl_index)) { try self.generateTestEntryPoint(fqn, spv_decl_index); } diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 7ec4c2cac6..3eed369933 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -135,10 +135,21 @@ pub fn updateDeclExports( decl_index: Module.Decl.Index, exports: []const *Module.Export, ) !void { - _ = self; - _ = module; - _ = decl_index; - _ = exports; + const decl = module.declPtr(decl_index); + if (decl.val.tag() == .function and decl.ty.fnCallingConvention() == .Kernel) { + // TODO: Unify with resolveDecl in spirv.zig. + const entry = try self.decl_link.getOrPut(decl_index); + if (!entry.found_existing) { + entry.value_ptr.* = try self.spv.allocDecl(.func); + } + const spv_decl_index = entry.value_ptr.*; + + for (exports) |exp| { + try self.spv.declareEntryPoint(spv_decl_index, exp.options.name); + } + } + + // TODO: Export regular functions, variables, etc using Linkage attributes. } pub fn freeDecl(self: *SpirV, decl_index: Module.Decl.Index) void { From 1de2d2ee1c70dfd01dc8b161af53ce188e84b1d0 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 8 Apr 2023 15:03:05 +0200 Subject: [PATCH 46/51] spirv: deny OpEntryPoint in asm Kernels should be exported by marking the kernel using callconv(.Kernel) and exporting it as a regular function. --- src/codegen/spirv/Assembler.zig | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig index a8ff5175d8..ffb5503afc 100644 --- a/src/codegen/spirv/Assembler.zig +++ b/src/codegen/spirv/Assembler.zig @@ -239,12 +239,17 @@ fn todo(self: *Assembler, comptime fmt: []const u8, args: anytype) Error { /// If this function returns `error.AssembleFail`, an explanatory /// error message has already been emitted into `self.errors`. fn processInstruction(self: *Assembler) !void { - const result = switch (self.inst.opcode.class()) { - .TypeDeclaration => try self.processTypeInstruction(), - else => if (try self.processGenericInstruction()) |result| - result - else - return, + const result = switch (self.inst.opcode) { + .OpEntryPoint => { + return self.fail(0, "cannot export entry points via OpEntryPoint, export the kernel using callconv(.Kernel)", .{}); + }, + else => switch (self.inst.opcode.class()) { + .TypeDeclaration => try self.processTypeInstruction(), + else => if (try self.processGenericInstruction()) |result| + result + else + return, + }, }; const result_ref = self.inst.result().?; @@ -435,8 +440,7 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue { .Annotation => &self.spv.sections.annotations, .TypeDeclaration => unreachable, // Handled elsewhere. else => switch (self.inst.opcode) { - // TODO: This should emit a proper entry point. - .OpEntryPoint => unreachable, // &self.spv.sections.entry_points, + .OpEntryPoint => unreachable, .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes, .OpVariable => switch (@intToEnum(spec.StorageClass, operands[2].value)) { .Function => &self.func.prologue, From fe0fb93fa04bf4767058cc7046100f3c4f33b3d4 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 8 Apr 2023 15:05:29 +0200 Subject: [PATCH 47/51] spirv: deny global OpVariable in inline asm This feature requires to be integrated with the mechanism that orders the global variables, and that is not yet in place. --- src/codegen/spirv/Assembler.zig | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig index ffb5503afc..fcfdbf56ee 100644 --- a/src/codegen/spirv/Assembler.zig +++ b/src/codegen/spirv/Assembler.zig @@ -444,8 +444,12 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue { .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes, .OpVariable => switch (@intToEnum(spec.StorageClass, operands[2].value)) { .Function => &self.func.prologue, - // TODO: Emit a decl dependency - else => &self.spv.sections.types_globals_constants, + else => { + // This is currently disabled because global variables are required to be + // emitted in the proper order, and this should be honored in inline assembly + // as well. + return self.todo("global variables", .{}); + }, }, // Default case - to be worked out further. else => &self.func.body, From 45b5f467709e3df01aafc37c25070bbbebd43e1e Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 8 Apr 2023 19:05:00 +0200 Subject: [PATCH 48/51] spirv: allow global, constant address spaces These should actually just work fine in SPIR-V. They are mapped to CrossWorkgroup and UniformConstant respectively. --- src/codegen/spirv.zig | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index aebe16a10a..63a5942ca9 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1317,11 +1317,22 @@ pub const DeclGen = struct { fn spvStorageClass(as: std.builtin.AddressSpace) StorageClass { return switch (as) { - .generic => .Generic, // TODO: Disallow? - .gs, .fs, .ss => unreachable, + .generic => .Generic, .shared => .Workgroup, .local => .Private, - .global, .param, .constant, .flash, .flash1, .flash2, .flash3, .flash4, .flash5 => unreachable, + .global => .CrossWorkgroup, + .constant => .UniformConstant, + .gs, + .fs, + .ss, + .param, + .flash, + .flash1, + .flash2, + .flash3, + .flash4, + .flash5, + => unreachable, }; } From a7563e453dce0cc256a0c40af434731f2cf7dcaf Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 8 Apr 2023 19:02:31 +0200 Subject: [PATCH 49/51] spirv: minimal start code For SPIR-V, only export the main function if it is actually declared. Kernel entry points will often have parameters and more than one kernel declared. In general, SPIR-V binaries should mostly be compiled as libraries and not as executables. However, this start code is required so that we can build test executables. Note that a call to isSpirV() would emit the code for that function, even though the call is at comptime. To save that function from being emitted the checks are just inlined manually. --- lib/std/start.zig | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index 1b686e43f5..377a317df2 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -24,7 +24,9 @@ pub const simplified_logic = builtin.zig_backend == .stage2_aarch64 or builtin.zig_backend == .stage2_arm or builtin.zig_backend == .stage2_riscv64 or - builtin.zig_backend == .stage2_sparc64; + builtin.zig_backend == .stage2_sparc64 or + builtin.cpu.arch == .spirv32 or + builtin.cpu.arch == .spirv64; comptime { // No matter what, we import the root file, so that any export, test, comptime @@ -43,6 +45,9 @@ comptime { } } else if (builtin.os.tag == .wasi and @hasDecl(root, "main")) { @export(wasiMain2, .{ .name = "_start" }); + } else if (builtin.os.tag == .opencl) { + if (@hasDecl(root, "main")) + @export(spirvMain2, .{ .name = "main" }); } else { if (!@hasDecl(root, "_start")) { @export(_start2, .{ .name = "_start" }); @@ -127,6 +132,10 @@ fn wasiMain2() callconv(.C) noreturn { } } +fn spirvMain2() callconv(.Kernel) void { + root.main(); +} + fn wWinMainCRTStartup2() callconv(.C) noreturn { root.main(); exit2(0); From 979b4102588fbb0d066060a5a6b6e10f584158ac Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 8 Apr 2023 19:05:48 +0200 Subject: [PATCH 50/51] spirv: Do not generate the Alignment attribute on pointers for now It seems that some implementations may have problems with these right now, like Intel and Rusticl. In theory, these attributes should be superficial on the pointer type, as alignment guarantees are also added via the alignment option of the OpLoad and OpStore instructions. Therefore, get rid of them for now. --- src/codegen/spirv.zig | 41 +++++++++++---------------------- src/codegen/spirv/Assembler.zig | 5 +--- src/codegen/spirv/Module.zig | 6 ++--- src/codegen/spirv/type.zig | 4 ++-- 4 files changed, 20 insertions(+), 36 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 63a5942ca9..58da5539ac 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -825,6 +825,11 @@ pub const DeclGen = struct { // - Underaligned pointers. These need to be packed into the word array by using a mixture of // OpSpecConstantOp instructions such as OpConvertPtrToU, OpBitcast, OpShift, etc. + // TODO: Implement alignment here. + // This is hoing to require some hacks because there is no real way to + // set an OpVariable's alignment. + _ = alignment; + assert(storage_class != .Generic and storage_class != .Function); log.debug("lowerIndirectConstant: ty = {}, val = {}", .{ ty.fmt(self.module), val.fmtDebug() }); @@ -832,7 +837,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, alignment); + const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, 0); // const target = self.getTarget(); @@ -874,7 +879,7 @@ pub const DeclGen = struct { 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, alignment); + const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class, 0); const constant_struct_id = self.spv.allocId(); try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ @@ -909,7 +914,7 @@ pub const DeclGen = struct { }); if (cast_to_generic) { - const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, alignment); + const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0); try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ .id_result_type = self.typeId(generic_ptr_ty_ref), .id_result = result_id, @@ -1165,21 +1170,16 @@ pub const DeclGen = struct { .Pointer => { const ptr_info = ty.ptrInfo().data; - const ptr_payload = try self.spv.arena.create(SpvType.Payload.Pointer); - ptr_payload.* = .{ - .storage_class = spvStorageClass(ptr_info.@"addrspace"), - .child_type = try self.resolveType(ptr_info.pointee_type, .indirect), - // Note: only available in Kernels! - .alignment = ty.ptrAlignment(target) * 8, - }; - const ptr_ty_id = try self.spv.resolveType(SpvType.initPayload(&ptr_payload.base)); + 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_id; + return ptr_ty_ref; } return try self.spv.simpleStructType(&.{ - .{ .ty = ptr_ty_id, .name = "ptr" }, + .{ .ty = ptr_ty_ref, .name = "ptr" }, .{ .ty = try self.sizeType(), .name = "len" }, }); }, @@ -1356,7 +1356,7 @@ 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, null); + const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup, 0); const void_ty_ref = try self.resolveType(Type.void, .direct); const kernel_proto_ty_ref = blk: { @@ -1497,19 +1497,6 @@ pub const DeclGen = struct { final_storage_class == .Generic, decl.@"align", ); - - // if (storage_class == .Generic) { - // const section = &self.spv.globals.section; - // const ty_ref = try self.resolveType(decl.ty, .indirect); - // const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align"); - // // TODO: Can we eliminate this cast? - // // TODO: Const-wash pointer? - // try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ - // .id_result_type = self.typeId(ptr_ty_ref), - // .id_result = global_result_id, - // .pointer = casted_result_id, - // }); - // } } } diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig index fcfdbf56ee..eebf43866d 100644 --- a/src/codegen/spirv/Assembler.zig +++ b/src/codegen/spirv/Assembler.zig @@ -388,10 +388,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue { payload.* = .{ .storage_class = @intToEnum(spec.StorageClass, operands[1].value), .child_type = try self.resolveTypeRef(operands[2].ref_id), - // TODO: Fetch these values from decorations. - .array_stride = 0, - .alignment = null, - .max_byte_offset = null, + // TODO: Fetch decorations }; break :blk SpvType.initPayload(&payload.base); }, diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index f7d392e9e3..7ae6cb0c6a 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -573,8 +573,8 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { if (info.array_stride != 0) { try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); } - if (info.alignment) |alignment| { - try self.decorate(ref_id, .{ .Alignment = .{ .alignment = alignment } }); + 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 } }); @@ -753,7 +753,7 @@ pub fn ptrType( self: *Module, child: Type.Ref, storage_class: spec.StorageClass, - alignment: ?u32, + alignment: u32, ) !Type.Ref { const ptr_payload = try self.arena.create(Type.Payload.Pointer); ptr_payload.* = .{ diff --git a/src/codegen/spirv/type.zig b/src/codegen/spirv/type.zig index 118563c6fa..2e1661c14e 100644 --- a/src/codegen/spirv/type.zig +++ b/src/codegen/spirv/type.zig @@ -547,8 +547,8 @@ pub const Type = extern union { /// This is valid for pointers to elements of an array. /// If zero, no stride is present. array_stride: u32 = 0, - /// Type has the 'Alignment' decoration. - alignment: ?u32, + /// If nonzero, type has the 'Alignment' decoration. + alignment: u32 = 0, /// Type has the 'MaxByteOffset' decoration. max_byte_offset: ?u32 = null, }; From d1484bf4b96177135183916b95198de25dc63356 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 8 Apr 2023 23:49:17 +0200 Subject: [PATCH 51/51] spirv: emit nonsemantic info with zig errors In order to be able to report nice errors in the test runner, and in order to check SkipZigTest errors, we need to pass the error names to the consumer. This kind of information can be passed via nonsemantic instructions - using OpSourceExtension here. All errors are concatenated into a single string, starting with 'zig_errors:' for identification, separated by a colon (:). To ensure that we can represent all error codes, even those which contain a colon, the error names are URI- escaped. URI-escaping, rather than base64, allows us to see the error names when viewing disassembled SPIR-V code. --- src/link/SpirV.zig | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 3eed369933..fbdcbd5a8e 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -181,6 +181,27 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No try writeCapabilities(&self.spv, target); try writeMemoryModel(&self.spv, target); + // We need to export the list of error names somewhere so that we can pretty-print them in the + // executor. This is not really an important thing though, so we can just dump it in any old + // nonsemantic instruction. For now, just put it in OpSourceExtension with a special name. + + var error_info = std.ArrayList(u8).init(self.spv.arena); + try error_info.appendSlice("zig_errors"); + const module = self.base.options.module.?; + for (module.error_name_list.items) |name| { + // Errors can contain pretty much any character - to encode them in a string we must escape + // them somehow. Easiest here is to use some established scheme, one which also preseves the + // name if it contains no strange characters is nice for debugging. URI encoding fits the bill. + // We're using : as separator, which is a reserved character. + + const escaped_name = try std.Uri.escapeString(self.base.allocator, name); + defer self.base.allocator.free(escaped_name); + try error_info.writer().print(":{s}", .{escaped_name}); + } + try self.spv.sections.debug_strings.emit(self.spv.gpa, .OpSourceExtension, .{ + .extension = error_info.items, + }); + try self.spv.flush(self.base.file.?); }