From 6e8a83282f10bcce7f80c9f213d2ae6407651163 Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Sun, 21 Jun 2026 08:31:03 +0330 Subject: [PATCH 1/9] Sema: implement `@typeInfo` for spirv types Forgotten in #35461 --- src/Sema.zig | 38 ++++++++++++++++++++++++++++++++++- src/Zcu.zig | 14 ++++++++++++- test/behavior/type_info.zig | 40 +++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 411f0c3ab3794c6a55d8580c91b624cc75b07b1a..e008f9a9008f241876a67b9585b386d0173840ea 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -16793,7 +16793,43 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .val = (try pt.aggregateValue(type_opaque_ty, &field_values)).toIntern(), }))); }, - .spirv => unreachable, // TODO: ALI + .spirv => { + const spirv_info = ip.loadSpirvType(ty.toIntern()); + const spirv_union_ty = try sema.getStdLangType(src, .@"Type.Spirv"); + const spirv_tag_ty = spirv_union_ty.unionTagType(zcu).?; + const spirv_tag_val = try pt.enumValueFieldIndex(spirv_tag_ty, @intFromEnum(spirv_info.flags.tag)); + const spirv_payload_val: Value = switch (spirv_info.flags.tag) { + .sampler => .void, + .sampled_image, .runtime_array => .fromInterned(spirv_info.ty), + .image => image: { + const image_ty = try sema.getStdLangType(src, .@"Type.Spirv.Image"); + const usage_union_ty = try sema.getStdLangType(src, .@"Type.Spirv.Image.Usage"); + const format_ty = try sema.getStdLangType(src, .@"Type.Spirv.Image.Format"); + const dim_ty = try sema.getStdLangType(src, .@"Type.Spirv.Image.Dimensionality"); + const depth_ty = try sema.getStdLangType(src, .@"Type.Spirv.Image.Depth"); + const access_ty = try sema.getStdLangType(src, .@"Type.Spirv.Image.Access"); + const usage_tag_ty = usage_union_ty.unionTagType(zcu).?; + const usage_tag_val = try pt.enumValueFieldIndex(usage_tag_ty, @intFromEnum(spirv_info.flags.usage)); + const usage_val = try pt.unionValue(usage_union_ty, usage_tag_val, .fromInterned(spirv_info.ty)); + const image_field_vals = [_]InternPool.Index{ + usage_val.toIntern(), + (try pt.enumValueFieldIndex(format_ty, @intFromEnum(spirv_info.flags.format))).toIntern(), + (try pt.enumValueFieldIndex(dim_ty, @intFromEnum(spirv_info.flags.dim))).toIntern(), + (try pt.enumValueFieldIndex(depth_ty, @intFromEnum(spirv_info.flags.depth))).toIntern(), + (try pt.enumValueFieldIndex(access_ty, @intFromEnum(spirv_info.flags.access))).toIntern(), + Value.makeBool(spirv_info.flags.is_arrayed).toIntern(), + Value.makeBool(spirv_info.flags.is_multisampled).toIntern(), + }; + break :image try pt.aggregateValue(image_ty, &image_field_vals); + }, + }; + const spirv_val = try pt.unionValue(spirv_union_ty, spirv_tag_val, spirv_payload_val); + return Air.internedToRef((try pt.internUnion(.{ + .ty = type_info_ty.toIntern(), + .tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.lang.TypeId.spirv))).toIntern(), + .val = spirv_val.toIntern(), + }))); + }, .frame => return sema.failWithUseOfAsync(block, src), .@"anyframe" => return sema.failWithUseOfAsync(block, src), } diff --git a/src/Zcu.zig b/src/Zcu.zig index ee3063051f28947bad9c9248189b953aefb96204..e3a36fe31b987dce03f89d1f4694cf718d72d3dc 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -469,6 +469,12 @@ pub const StdLangDecl = enum { @"Type.ContainerLayout", @"Type.Opaque", @"Type.Spirv", + @"Type.Spirv.Image", + @"Type.Spirv.Image.Usage", + @"Type.Spirv.Image.Format", + @"Type.Spirv.Image.Dimensionality", + @"Type.Spirv.Image.Depth", + @"Type.Spirv.Image.Access", panic, @"panic.call", @@ -550,6 +556,12 @@ pub const StdLangDecl = enum { .@"Type.ContainerLayout", .@"Type.Opaque", .@"Type.Spirv", + .@"Type.Spirv.Image", + .@"Type.Spirv.Image.Usage", + .@"Type.Spirv.Image.Format", + .@"Type.Spirv.Image.Dimensionality", + .@"Type.Spirv.Image.Depth", + .@"Type.Spirv.Image.Access", => .type, .panic => .type, @@ -603,7 +615,7 @@ pub const StdLangDecl = enum { .VaList => .va_list, .assembly, .@"assembly.Clobbers" => .assembly, else => { - if (@intFromEnum(decl) <= @intFromEnum(StdLangDecl.@"Type.Spirv")) { + if (@intFromEnum(decl) <= @intFromEnum(StdLangDecl.@"Type.Spirv.Image.Access")) { return .main; } else { return .panic; diff --git a/test/behavior/type_info.zig b/test/behavior/type_info.zig index d04e1b947059a2113ab28c666596727b58d94715..eeea34879b7a7cb8226a8c9107b381a79981a474 100644 --- a/test/behavior/type_info.zig +++ b/test/behavior/type_info.zig @@ -613,3 +613,43 @@ test "@typeInfo function with generic return type and inferred error set" { const ret_ty = @typeInfo(@TypeOf(S.testFn)).@"fn".return_type; comptime assert(ret_ty == null); } + +test "type info: spirv info" { + if (builtin.zig_backend != .stage2_spirv) return error.SkipZigTest; + + try testSpirv(); + try comptime testSpirv(); +} + +fn testSpirv() !void { + const image_info = @typeInfo(Image); + try expect(image_info.spirv.image.usage.sampled == f32); + try expect(image_info.spirv.image.format == .unknown); + try expect(image_info.spirv.image.dim == .@"2d"); + try expect(image_info.spirv.image.depth == .not_depth); + try expect(image_info.spirv.image.arrayed == false); + try expect(image_info.spirv.image.multisampled == false); + try expect(image_info.spirv.image.access == .unknown); + + const sampled_image_info = @typeInfo(SampledImage); + try expect(sampled_image_info.spirv.sampled_image == Image); + + const sampler_info = @typeInfo(Sampler); + try expect(sampler_info.spirv.sampler == {}); + + const runtime_array_info = @typeInfo(RuntimeArray); + try expect(runtime_array_info.spirv.runtime_array == f32); +} + +pub const Image = @SpirvType(.{ .image = .{ + .usage = .{ .sampled = f32 }, + .format = .unknown, + .dim = .@"2d", + .depth = .not_depth, + .arrayed = false, + .multisampled = false, + .access = .unknown, +} }); +pub const SampledImage = @SpirvType(.{ .sampled_image = Image }); +pub const Sampler = @SpirvType(.sampler); +pub const RuntimeArray = @SpirvType(.{ .runtime_array = f32 }); -- 2.54.0 From 14d5da916447f10bb812c3c6a199ab4ab76f61ec Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Sun, 21 Jun 2026 08:49:15 +0330 Subject: [PATCH 2/9] spirv: don't put dummy `OpVariable` for non-Function pointers This would raise a validation error when the parameter type is a non-Function opaque pointer. e.g. an `@extern(*addrspace(.constant) const SampledImage, ...)` --- src/codegen/spirv/CodeGen.zig | 3 ++- test/cases/image_sampling_spirv.zig | 42 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 test/cases/image_sampling_spirv.zig diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index f6c533c31aadd92f7dc52d1b3898667cf880cbfa..d60ccf8dc1fd3f2d44e2ac3017f7c904f0946bfb 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -8112,7 +8112,8 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.lang.CallModifier) if (!arg_ty.hasRuntimeBits(zcu)) continue; if (arg_ty.zigTypeTag(zcu) == .pointer and !arg_ty.isSlice(zcu) and - !arg_ty.childType(zcu).hasRuntimeBits(zcu)) + !arg_ty.childType(zcu).hasRuntimeBits(zcu) and + cg.module.storageClass(arg_ty.ptrAddressSpace(zcu)) == .function) { // in logical addressing, pointer arguments to function calls // must be memory object declarations (OpVariable). for pointers to diff --git a/test/cases/image_sampling_spirv.zig b/test/cases/image_sampling_spirv.zig new file mode 100644 index 0000000000000000000000000000000000000000..030727e95c106b6b2521ab72d07bb608b58a22a8 --- /dev/null +++ b/test/cases/image_sampling_spirv.zig @@ -0,0 +1,42 @@ +pub const Image = @SpirvType(.{ .image = .{ + .usage = .{ .sampled = f32 }, + .format = .unknown, + .dim = .@"2d", + .depth = .not_depth, + .arrayed = false, + .multisampled = false, + .access = .unknown, +} }); +pub const SampledImage = @SpirvType(.{ .sampled_image = Image }); + +const image_in = @extern(*addrspace(.constant) const SampledImage, .{ + .name = "image_in", + .decoration = .{ .descriptor = .{ .set = 2, .binding = 0 } }, +}); +const uv_in = @extern(*addrspace(.input) @Vector(2, f32), .{ .name = "uv", .decoration = .{ .location = 0 } }); +const color_out = @extern(*addrspace(.output) @Vector(4, f32), .{ .name = "color", .decoration = .{ .location = 0 } }); + +export fn main() callconv(.{ .spirv_fragment = .{} }) void { + color_out.* = imageSample(image_in, uv_in.*); +} + +fn imageSample( + sampled_image: *addrspace(.constant) const SampledImage, + uv: @Vector(2, f32), +) @Vector(4, f32) { + return asm volatile ( + \\%loaded_sampler = OpLoad %SampledImage %sampled_image + \\%ret = OpImageSampleImplicitLod %Result %loaded_sampler %uv + : [ret] "" (-> @Vector(4, f32)), + : [SampledImage] "t" (SampledImage), + [sampled_image] "" (sampled_image), + [Result] "t" (@Vector(4, f32)), + [uv] "" (uv), + ); +} + +// compile +// output_mode=Exe +// backend=selfhosted +// target=spirv32-vulkan +// emit_bin=true -- 2.54.0 From 6754fc8af277e65827ae531ca4d114df0576398c Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Sun, 21 Jun 2026 08:56:39 +0330 Subject: [PATCH 3/9] spirv: prune_unused: mark operands of all live function-body instructions --- src/link/SpirV/prune_unused.zig | 4 +--- test/cases/spirv_mergable_pointers.zig | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/link/SpirV/prune_unused.zig b/src/link/SpirV/prune_unused.zig index 99f09a5c597a384a3ce67ba6e105126dc5ee1f2d..356da14952784938ade741ba08e5ed52fd859df3 100644 --- a/src/link/SpirV/prune_unused.zig +++ b/src/link/SpirV/prune_unused.zig @@ -92,9 +92,7 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void { } // mark operands of alive function contents - if (!canPrune(inst.opcode)) { - try markAlive(parser, binary.*, inst, &alive, &id_to_index, &code_offsets, &id_offset_buf); - } + try markAlive(parser, binary.*, inst, &alive, &id_to_index, &code_offsets, &id_offset_buf); } // rewrite diff --git a/test/cases/spirv_mergable_pointers.zig b/test/cases/spirv_mergable_pointers.zig index 106ee8efbe2f1ba588ca830d7a5e67bda95d8ee3..004dcbb9dc6affdfb7247156480e0e5da9f2ced9 100644 --- a/test/cases/spirv_mergable_pointers.zig +++ b/test/cases/spirv_mergable_pointers.zig @@ -14,4 +14,4 @@ export fn a() void { // output_mode=Obj // backend=selfhosted // target=spirv64-vulkan -// emit_bin=false +// emit_bin=true -- 2.54.0 From dccc724179d984ac5785b6a20bd187bb9c8f749d Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Sun, 21 Jun 2026 09:51:04 +0330 Subject: [PATCH 4/9] spirv: require `mesh_shading_ext` for `spirv_task` and `spirv_mesh` Also adds a `cpu_features` key to the test manifest. --- src/Sema.zig | 15 +++++++++++++-- test/behavior/array.zig | 2 ++ test/cases/callconv_spirv.zig | 5 +++-- .../compile_errors/SpirvType_vulkan_target.zig | 2 +- .../callconv_spirv_invalid_options.zig | 2 +- .../callconv_spirv_mesh_task_require_vulkan.zig | 17 ----------------- .../callconv_spirv_on_unsupported_platform.zig | 1 + ...embedding_spirv_type_in_struct_and_union.zig | 2 +- .../extern_spirv_decoration_validation.zig | 2 +- .../spirv_merge_logical_pointers.zig | 4 ++-- test/cases/spirv_mergable_pointers.zig | 2 +- test/src/Cases.zig | 13 ++++++++++++- 12 files changed, 38 insertions(+), 29 deletions(-) delete mode 100644 test/cases/compile_errors/callconv_spirv_mesh_task_require_vulkan.zig diff --git a/src/Sema.zig b/src/Sema.zig index e008f9a9008f241876a67b9585b386d0173840ea..78eeb2dfcfe0a818f811a6f4881c9e51a3eccbca 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8694,15 +8694,26 @@ fn checkReturnTypeAndCallConv( return sema.fail(block, callconv_src, "'pixel_centered_integer' is not supported on this target", .{}); } }, - .spirv_kernel, .spirv_task => |kernel| { + .spirv_kernel => |kernel| { if (kernel.x == 0 or kernel.y == 0 or kernel.z == 0) { return sema.fail(block, callconv_src, "kernel workgroup dimensions must be at least 1", .{}); } }, + .spirv_task => |task| { + if (task.x == 0 or task.y == 0 or task.z == 0) { + return sema.fail(block, callconv_src, "kernel workgroup dimensions must be at least 1", .{}); + } + if (!target.cpu.has(.spirv, .mesh_shading_ext)) { + return sema.fail(block, callconv_src, "calling convention '{t}' requires the 'mesh_shading_ext' feature", .{@"callconv"}); + } + }, .spirv_mesh => |mesh| { if (mesh.max_vertices == 0 or mesh.max_primitives == 0) { return sema.fail(block, callconv_src, "mesh shader 'max_vertices' and 'max_primitives' must be at least 1", .{}); } + if (!target.cpu.has(.spirv, .mesh_shading_ext)) { + return sema.fail(block, callconv_src, "calling convention '{t}' requires the 'mesh_shading_ext' feature", .{@"callconv"}); + } }, else => {}, } @@ -8844,7 +8855,7 @@ fn checkMergeAllowed(sema: *Sema, block: *Block, src: LazySrcLoc, peer_ty: Type) try sema.errNote(runtime_src, msg, "runtime control flow here", .{}); const backend = target_util.zigBackend(target, zcu.comp.config.use_llvm); - try sema.errNote(src, msg, "pointers with address space '{s}' cannot be returned from a branch on target {s}-{s} by compiler backend {s}", .{ + try sema.errNote(src, msg, "pointers with address space '{s}' cannot be returned from a branch on target '{s}-{s}' by compiler backend '{s}'", .{ @tagName(as), @tagName(target.cpu.arch.family()), @tagName(target.os.tag), diff --git a/test/behavior/array.zig b/test/behavior/array.zig index 330ac2fb685559ac546fca5c71056dd8391208fd..961125f18aa7bcdad61985e7408e4dfd89a0ed88 100644 --- a/test/behavior/array.zig +++ b/test/behavior/array.zig @@ -1150,6 +1150,8 @@ test "resist alias of explicit copy of array passed as arg" { } test "access element through reference" { + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; + const S = struct { fn doTheTest(x: u8) !void { { diff --git a/test/cases/callconv_spirv.zig b/test/cases/callconv_spirv.zig index de32151f19a72c2a9df8bd26ebcb9c820ab18722..25be9d0c27a7493811da50525ce5de0bf696c001 100644 --- a/test/cases/callconv_spirv.zig +++ b/test/cases/callconv_spirv.zig @@ -7,5 +7,6 @@ export fn mesh() callconv(.{ .spirv_mesh = .{ .stage_output = .output_lines, .ma // compile // output_mode=Obj // backend=selfhosted -// target=spirv64-vulkan -// emit_bin=false +// target=spirv32-vulkan +// cpu_features=vulkan_v1_2+mesh_shading_ext +// emit_bin=true diff --git a/test/cases/compile_errors/SpirvType_vulkan_target.zig b/test/cases/compile_errors/SpirvType_vulkan_target.zig index 5448f8d1535760ad67e76ae931c50f5a2355fbd0..894b3441a8e5101cbf5cf66a30c5e2a3ec795b0e 100644 --- a/test/cases/compile_errors/SpirvType_vulkan_target.zig +++ b/test/cases/compile_errors/SpirvType_vulkan_target.zig @@ -48,7 +48,7 @@ comptime { // error // backend=selfhosted -// target=spirv64-vulkan +// target=spirv32-vulkan // // :2:21: error: access qualifier '.read_only' is only valid under the 'opencl' os // :14:21: error: invalid 'sampled' field value 'bool' diff --git a/test/cases/compile_errors/callconv_spirv_invalid_options.zig b/test/cases/compile_errors/callconv_spirv_invalid_options.zig index f23797709f2bd7b979724fb125d9d0a85362ff15..a04646f3c20ccc9387d8aea1c70fd5dbdac951d2 100644 --- a/test/cases/compile_errors/callconv_spirv_invalid_options.zig +++ b/test/cases/compile_errors/callconv_spirv_invalid_options.zig @@ -21,7 +21,7 @@ export fn entry4() void { // error // backend=selfhosted -// target=spirv64-vulkan +// target=spirv32-vulkan // // :1:28: error: kernel workgroup dimensions must be at least 1 // :2:28: error: kernel workgroup dimensions must be at least 1 diff --git a/test/cases/compile_errors/callconv_spirv_mesh_task_require_vulkan.zig b/test/cases/compile_errors/callconv_spirv_mesh_task_require_vulkan.zig deleted file mode 100644 index b3164e604a73418f2af44a10dfec43ac3a51ffbf..0000000000000000000000000000000000000000 --- a/test/cases/compile_errors/callconv_spirv_mesh_task_require_vulkan.zig +++ /dev/null @@ -1,17 +0,0 @@ -const F1 = fn () callconv(.{ .spirv_task = .{ .x = 1, .y = 1, .z = 1 } }) void; -const F2 = fn () callconv(.{ .spirv_mesh = .{} }) void; -export fn entry1() void { - const a: F1 = undefined; - _ = a; -} -export fn entry2() void { - const a: F2 = undefined; - _ = a; -} - -// error -// backend=selfhosted -// target=spirv64-opengl -// -// :1:28: error: calling convention 'spirv_task' not supported by compiler backend 'stage2_spirv' -// :2:28: error: calling convention 'spirv_mesh' not supported by compiler backend 'stage2_spirv' diff --git a/test/cases/compile_errors/callconv_spirv_on_unsupported_platform.zig b/test/cases/compile_errors/callconv_spirv_on_unsupported_platform.zig index 25a9bdd9aba23fec6d31bc1a7d1d37cd5b203d90..1c41095021ad498dce4178d67556f1eb7b45e8d4 100644 --- a/test/cases/compile_errors/callconv_spirv_on_unsupported_platform.zig +++ b/test/cases/compile_errors/callconv_spirv_on_unsupported_platform.zig @@ -22,6 +22,7 @@ export fn entry4() void { // error // backend=selfhosted // target=spirv64-opencl +// cpu_features=opencl_v2+mesh_shading_ext // // :1:28: error: calling convention 'spirv_fragment' not supported by compiler backend 'stage2_spirv' // :2:28: error: calling convention 'spirv_vertex' not supported by compiler backend 'stage2_spirv' diff --git a/test/cases/compile_errors/directly_embedding_spirv_type_in_struct_and_union.zig b/test/cases/compile_errors/directly_embedding_spirv_type_in_struct_and_union.zig index cc8c03873879c917f5db31c4ceea361a665ba240..732db0b4408883a1e0aaacf019bb6c459d62205f 100644 --- a/test/cases/compile_errors/directly_embedding_spirv_type_in_struct_and_union.zig +++ b/test/cases/compile_errors/directly_embedding_spirv_type_in_struct_and_union.zig @@ -25,7 +25,7 @@ export fn d() void { // error // backend=selfhosted -// target=spirv64-vulkan +// target=spirv32-vulkan // // :4:8: error: cannot directly embed SPIR-V type 'tmp.Sampler__SpirvType_4' in struct // :4:8: note: opaque types have unknown size diff --git a/test/cases/compile_errors/extern_spirv_decoration_validation.zig b/test/cases/compile_errors/extern_spirv_decoration_validation.zig index 2e8651a6882b5f0c64ac57ebe97c75092b8cfa24..ea51feb3b931f5d6a93d8640a9a8819251bb3cfc 100644 --- a/test/cases/compile_errors/extern_spirv_decoration_validation.zig +++ b/test/cases/compile_errors/extern_spirv_decoration_validation.zig @@ -8,6 +8,6 @@ comptime { // error // backend=selfhosted -// target=spirv64-vulkan +// target=spirv32-vulkan // // :1:45: error: 'flat' decoration requires 'input' address space diff --git a/test/cases/compile_errors/spirv_merge_logical_pointers.zig b/test/cases/compile_errors/spirv_merge_logical_pointers.zig index 09ee6dd78cfc9509b5bb3cb3067119ed886ce75c..ccceef8097c8418dcfc363f6baa87c0e04073b3c 100644 --- a/test/cases/compile_errors/spirv_merge_logical_pointers.zig +++ b/test/cases/compile_errors/spirv_merge_logical_pointers.zig @@ -11,8 +11,8 @@ export fn a() void { } // error -// target=spirv64-vulkan +// target=spirv32-vulkan // // :9:13: error: value with non-mergable pointer type '*i32' depends on runtime control flow // :9:17: note: runtime control flow here -// :9:13: note: pointers with address space 'generic' cannot be returned from a branch on target spirv-vulkan by compiler backend stage2_spirv +// :9:13: note: pointers with address space 'generic' cannot be returned from a branch on target 'spirv-vulkan' by compiler backend 'stage2_spirv' diff --git a/test/cases/spirv_mergable_pointers.zig b/test/cases/spirv_mergable_pointers.zig index 004dcbb9dc6affdfb7247156480e0e5da9f2ced9..e44c1b53cf7ec89926218ceb4fb16b55adfd72b8 100644 --- a/test/cases/spirv_mergable_pointers.zig +++ b/test/cases/spirv_mergable_pointers.zig @@ -13,5 +13,5 @@ export fn a() void { // compile // output_mode=Obj // backend=selfhosted -// target=spirv64-vulkan +// target=spirv32-vulkan // emit_bin=true diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 9ffc5ec61645f2ecc6364389b63b58e2b5717935..f321d15851e117539f78f38681bd20cb8e06b1c4 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -357,7 +357,15 @@ fn addFromDirInner( var manifest = try TestManifest.parse(ctx.arena, src); const backends = try manifest.getConfigForKeyAlloc(ctx.arena, "backend", Backend); - const targets = try manifest.getConfigForKeyAlloc(ctx.arena, "target", std.Target.Query); + const target_strs = try manifest.getConfigForKeyAlloc(ctx.arena, "target", []const u8); + const cpu_features_str = manifest.config_map.get("cpu_features") orelse ""; + const targets = try ctx.arena.alloc(std.Target.Query, target_strs.len); + for (targets, target_strs) |*query, target_str| { + query.* = try std.Target.Query.parse(.{ + .arch_os_abi = target_str, + .cpu_features = if (cpu_features_str.len == 0) null else cpu_features_str, + }); + } const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool); const link_libc = try manifest.getConfigForKeyAssertSingle("link_libc", bool); const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode); @@ -707,6 +715,8 @@ const TestManifestConfigDefaults = struct { return "null"; } else if (std.mem.eql(u8, key, "imports")) { return ""; + } else if (std.mem.eql(u8, key, "cpu_features")) { + return ""; } else unreachable; } }; @@ -739,6 +749,7 @@ const TestManifest = struct { .{ "is_test", {} }, .{ "output_mode", {} }, .{ "target", {} }, + .{ "cpu_features", {} }, .{ "c_frontend", {} }, .{ "link_libc", {} }, .{ "backend", {} }, -- 2.54.0 From fcb9d475a1f1db7ac11038766fedc67c21949d8c Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Sun, 21 Jun 2026 10:20:57 +0330 Subject: [PATCH 5/9] spirv: merge Module into CodeGen and trim caches - Delete `Module.zig`. Fields and helpers are moved into `CodeGen.zig`. - Drop the caches that are already handled by `dedup_types` and fix the bugs that got exposed after this change. - Reorder fields by lifecycle and inline some functions into their caller --- src/codegen/spirv/Assembler.zig | 159 +- src/codegen/spirv/CodeGen.zig | 2984 ++++++++++++++++++------------- src/codegen/spirv/Mir.zig | 10 +- src/codegen/spirv/Module.zig | 791 -------- 4 files changed, 1792 insertions(+), 2152 deletions(-) delete mode 100644 src/codegen/spirv/Module.zig diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig index 5d12878350211b63e826d7f59b81f5e1f2dda7e8..f18f9028212b4916ed70a89d080e28a3bad75f3f 100644 --- a/src/codegen/spirv/Assembler.zig +++ b/src/codegen/spirv/Assembler.zig @@ -3,7 +3,7 @@ const Allocator = std.mem.Allocator; const assert = std.debug.assert; const CodeGen = @import("CodeGen.zig"); -const Decl = @import("Module.zig").Decl; +const Decl = @import("CodeGen.zig").Decl; const spec = @import("spec.zig"); const Opcode = spec.Opcode; @@ -56,7 +56,7 @@ const Operand = union(enum) { }; pub fn deinit(ass: *Assembler) void { - const gpa = ass.cg.module.gpa; + const gpa = ass.cg.gpa; for (ass.errors.items) |err| gpa.free(err.msg); ass.tokens.deinit(gpa); ass.errors.deinit(gpa); @@ -69,7 +69,7 @@ pub fn deinit(ass: *Assembler) void { const Error = error{ AssembleFail, OutOfMemory }; pub fn assemble(ass: *Assembler, src: []const u8) Error!void { - const gpa = ass.cg.module.gpa; + const gpa = ass.cg.gpa; ass.src = src; ass.errors.clearRetainingCapacity(); @@ -100,7 +100,7 @@ const ErrorMsg = struct { }; fn addError(ass: *Assembler, offset: u32, comptime fmt: []const u8, args: anytype) !void { - const gpa = ass.cg.module.gpa; + const gpa = ass.cg.gpa; const msg = try std.fmt.allocPrint(gpa, fmt, args); errdefer gpa.free(msg); try ass.errors.append(gpa, .{ @@ -159,7 +159,7 @@ const AsmValue = union(enum) { /// If this function returns `error.AssembleFail`, an explanatory /// error message has already been emitted into `ass.errors`. fn processInstruction(ass: *Assembler) !void { - const module = ass.cg.module; + const cg = ass.cg; const result: AsmValue = switch (ass.inst.opcode) { .OpEntryPoint => { return ass.fail(ass.currentToken().start, "cannot export entry points in assembly", .{}); @@ -176,7 +176,7 @@ fn processInstruction(ass: *Assembler) !void { const set_tag = std.meta.stringToEnum(spec.InstructionSet, set_name) orelse { return ass.fail(set_name_offset, "unknown instruction set: {s}", .{set_name}); }; - break :blk .{ .value = try module.importInstructionSet(set_tag) }; + break :blk .{ .value = try cg.importInstructionSet(set_tag) }; }, else => switch (ass.inst.opcode.class()) { .type_declaration => try ass.processTypeInstruction(), @@ -197,13 +197,12 @@ fn processInstruction(ass: *Assembler) !void { fn processTypeInstruction(ass: *Assembler) !AsmValue { const cg = ass.cg; - const gpa = cg.module.gpa; - const module = cg.module; + const gpa = cg.gpa; const operands = ass.inst.operands.items; - const section = &module.sections.globals; + const section = &cg.sections.globals; const id = switch (ass.inst.opcode) { - .OpTypeVoid => try module.voidType(), - .OpTypeBool => try module.boolType(), + .OpTypeVoid => try cg.voidType(), + .OpTypeBool => try cg.boolType(), .OpTypeInt => blk: { const signedness: std.lang.Signedness = switch (operands[2].literal32) { 0 => .unsigned, @@ -216,7 +215,7 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue { const width = std.math.cast(u16, operands[1].literal32) orelse { return ass.fail(0, "int type of {} bits is too large", .{operands[1].literal32}); }; - break :blk try module.intType(signedness, width); + break :blk try cg.intType(signedness, width); }, .OpTypeFloat => blk: { const bits = operands[1].literal32; @@ -226,11 +225,11 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue { return ass.fail(0, "{} is not a valid bit count for floats (expected 16, 32 or 64)", .{bits}); }, } - break :blk try module.floatType(@intCast(bits)); + break :blk try cg.floatType(@intCast(bits)); }, .OpTypeVector => blk: { const child_type = try ass.resolveRefId(operands[1].ref_id); - break :blk try module.vectorType(operands[2].literal32, child_type); + break :blk try cg.vectorType(operands[2].literal32, child_type); }, .OpTypeArray => { // TODO: The length of an OpTypeArray is determined by a constant (which may be a spec constant), @@ -239,8 +238,8 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue { }, .OpTypeRuntimeArray => blk: { const element_type = try ass.resolveRefId(operands[1].ref_id); - const result_id = module.allocId(); - try section.emit(module.gpa, .OpTypeRuntimeArray, .{ + const result_id = cg.allocId(); + try section.emit(cg.gpa, .OpTypeRuntimeArray, .{ .id_result = result_id, .element_type = element_type, }); @@ -249,8 +248,8 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue { .OpTypePointer => blk: { const storage_class: StorageClass = @enumFromInt(operands[1].value); const child_type = try ass.resolveRefId(operands[2].ref_id); - const result_id = module.allocId(); - try section.emit(module.gpa, .OpTypePointer, .{ + const result_id = cg.allocId(); + try section.emit(cg.gpa, .OpTypePointer, .{ .id_result = result_id, .storage_class = storage_class, .type = child_type, @@ -262,11 +261,11 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue { defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); const ids = try cg.id_scratch.addManyAsSlice(gpa, operands[1..].len); for (operands[1..], ids) |op, *id| id.* = try ass.resolveRefId(op.ref_id); - break :blk try module.structType(ids, null, .none); + break :blk try cg.structType(ids, null, .none); }, .OpTypeImage => blk: { const sampled_type = try ass.resolveRefId(operands[1].ref_id); - const result_id = module.allocId(); + const result_id = cg.allocId(); try section.emit(gpa, .OpTypeImage, .{ .id_result = result_id, .sampled_type = sampled_type, @@ -280,13 +279,13 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue { break :blk result_id; }, .OpTypeSampler => blk: { - const result_id = module.allocId(); + const result_id = cg.allocId(); try section.emit(gpa, .OpTypeSampler, .{ .id_result = result_id }); break :blk result_id; }, .OpTypeSampledImage => blk: { const image_type = try ass.resolveRefId(operands[1].ref_id); - const result_id = module.allocId(); + const result_id = cg.allocId(); try section.emit(gpa, .OpTypeSampledImage, .{ .id_result = result_id, .image_type = image_type }); break :blk result_id; }, @@ -301,8 +300,8 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue { for (param_types, param_operands) |*param, operand| { param.* = try ass.resolveRefId(operand.ref_id); } - const result_id = module.allocId(); - try section.emit(module.gpa, .OpTypeFunction, .{ + const result_id = cg.allocId(); + try section.emit(cg.gpa, .OpTypeFunction, .{ .id_result = result_id, .return_type = return_type, .id_ref_2 = param_types, @@ -318,27 +317,27 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue { /// - No forward references are allowed in operands. /// - Target section is determined from instruction type. fn processGenericInstruction(ass: *Assembler) !?AsmValue { - const module = ass.cg.module; - const target = module.zcu.getTarget(); + const cg = ass.cg; + const target = cg.zcu.getTarget(); const operands = ass.inst.operands.items; var maybe_spv_decl_index: ?Decl.Index = null; const section = switch (ass.inst.opcode.class()) { - .constant_creation => &module.sections.globals, - .annotation => &module.sections.annotations, + .constant_creation => &cg.sections.globals, + .annotation => &cg.sections.annotations, .type_declaration => unreachable, // Handled elsewhere. else => switch (ass.inst.opcode) { .OpEntryPoint => unreachable, - .OpExecutionMode, .OpExecutionModeId => &module.sections.execution_modes, + .OpExecutionMode, .OpExecutionModeId => &cg.sections.execution_modes, .OpVariable => section: { const storage_class: spec.StorageClass = @enumFromInt(operands[2].value); if (storage_class == .function) break :section &ass.cg.prologue; - maybe_spv_decl_index = try module.allocDecl(.global); + maybe_spv_decl_index = try cg.allocDecl(.global); if (!target.cpu.has(.spirv, .v1_4) and storage_class != .input and storage_class != .output) { // Before version 1.4, the interface’s storage classes are limited to the Input and Output - break :section &module.sections.globals; + break :section &cg.sections.globals; } - try ass.cg.module.decl_deps.append(module.gpa, maybe_spv_decl_index.?); - break :section &module.sections.globals; + try ass.cg.decl_deps.append(cg.gpa, maybe_spv_decl_index.?); + break :section &cg.sections.globals; }, else => &ass.cg.body, }, @@ -348,36 +347,36 @@ fn processGenericInstruction(ass: *Assembler) !?AsmValue { const first_word = section.instructions.items.len; // At this point we're not quite sure how many operands this instruction is // going to have, so insert 0 and patch up the actual opcode word later. - try section.ensureUnusedCapacity(module.gpa, 1); + try section.ensureUnusedCapacity(cg.gpa, 1); section.writeWord(0); for (operands) |operand| { switch (operand) { .value, .literal32 => |word| { - try section.ensureUnusedCapacity(module.gpa, 1); + try section.ensureUnusedCapacity(cg.gpa, 1); section.writeWord(word); }, .literal64 => |dword| { - try section.ensureUnusedCapacity(module.gpa, 2); + try section.ensureUnusedCapacity(cg.gpa, 2); section.writeDoubleWord(dword); }, .result_id => { maybe_result_id = if (maybe_spv_decl_index) |spv_decl_index| - module.declPtr(spv_decl_index).result_id + cg.declPtr(spv_decl_index).result_id else - module.allocId(); - try section.ensureUnusedCapacity(module.gpa, 1); + cg.allocId(); + try section.ensureUnusedCapacity(cg.gpa, 1); section.writeOperand(Id, maybe_result_id.?); }, .ref_id => |index| { const result = try ass.resolveRef(index); - try section.ensureUnusedCapacity(module.gpa, 1); + try section.ensureUnusedCapacity(cg.gpa, 1); section.writeOperand(spec.Id, result.resultId()); }, .string => |offset| { const text = std.mem.sliceTo(ass.inst.string_bytes.items[offset..], 0); const size = std.math.divCeil(usize, text.len + 1, @sizeOf(Word)) catch unreachable; - try section.ensureUnusedCapacity(module.gpa, size); + try section.ensureUnusedCapacity(cg.gpa, size); section.writeOperand(spec.LiteralString, text); }, } @@ -430,7 +429,7 @@ fn resolveRefId(ass: *Assembler, ref: AsmValue.Ref) !Id { } fn parseInstruction(ass: *Assembler) !void { - const gpa = ass.cg.module.gpa; + const gpa = ass.cg.gpa; ass.inst.opcode = undefined; ass.inst.operands.clearRetainingCapacity(); @@ -522,7 +521,7 @@ fn parseOperand(ass: *Assembler, kind: spec.OperandKind) Error!void { /// Also handles parsing any required extra operands. fn parseBitEnum(ass: *Assembler, kind: spec.OperandKind) !void { - const gpa = ass.cg.module.gpa; + const gpa = ass.cg.gpa; var tok = ass.currentToken(); try ass.expectToken(.value); @@ -571,7 +570,7 @@ fn parseBitEnum(ass: *Assembler, kind: spec.OperandKind) !void { /// Also handles parsing any required extra operands. fn parseValueEnum(ass: *Assembler, kind: spec.OperandKind) !void { - const gpa = ass.cg.module.gpa; + const gpa = ass.cg.gpa; const tok = ass.currentToken(); if (ass.eatToken(.placeholder)) { @@ -622,7 +621,7 @@ fn parseValueEnum(ass: *Assembler, kind: spec.OperandKind) !void { } fn parseRefId(ass: *Assembler) !void { - const gpa = ass.cg.module.gpa; + const gpa = ass.cg.gpa; const tok = ass.currentToken(); try ass.expectToken(.result_id); @@ -638,7 +637,7 @@ fn parseRefId(ass: *Assembler) !void { } fn parseLiteralInteger(ass: *Assembler) !void { - const gpa = ass.cg.module.gpa; + const gpa = ass.cg.gpa; const tok = ass.currentToken(); if (ass.eatToken(.placeholder)) { @@ -671,7 +670,7 @@ fn parseLiteralInteger(ass: *Assembler) !void { } fn parseLiteralExtInstInteger(ass: *Assembler) !void { - const gpa = ass.cg.module.gpa; + const gpa = ass.cg.gpa; const tok = ass.currentToken(); if (ass.eatToken(.placeholder)) { @@ -699,7 +698,7 @@ fn parseLiteralExtInstInteger(ass: *Assembler) !void { } fn parseString(ass: *Assembler) !void { - const gpa = ass.cg.module.gpa; + const gpa = ass.cg.gpa; const tok = ass.currentToken(); try ass.expectToken(.string); @@ -722,46 +721,36 @@ fn parseString(ass: *Assembler) !void { } fn parseContextDependentNumber(ass: *Assembler) !void { - const module = ass.cg.module; - - // For context dependent numbers, the actual type to parse is determined by the instruction. - // Currently, this operand appears in OpConstant and OpSpecConstant, where the too-be-parsed type - // is determined by the result type. That means that in this instructions we have to resolve the - // operand type early and look at the result to see how we need to proceed. + const cg = ass.cg; assert(ass.inst.opcode == .OpConstant or ass.inst.opcode == .OpSpecConstant); const tok = ass.currentToken(); const result = try ass.resolveRef(ass.inst.operands.items[0].ref_id); const result_id = result.resultId(); - // We are going to cheat a little bit: The types we are interested in, int and float, - // are added to the module and cached via module.intType and module.floatType. Therefore, - // we can determine the width of these types by directly checking the cache. - // This only works if the Assembler and codegen both use spv.intType and spv.floatType though. - // We don't expect there to be many of these types, so just look it up every time. - // TODO: Count be improved to be a little bit more efficent. - { - var it = module.cache.int_types.iterator(); - while (it.next()) |entry| { - const id = entry.value_ptr.*; - if (id != result_id) continue; - const info = entry.key_ptr.*; - return try ass.parseContextDependentInt(info.signedness, info.bits); - } - } - - { - var it = module.cache.float_types.iterator(); - while (it.next()) |entry| { - const id = entry.value_ptr.*; - if (id != result_id) continue; - const info = entry.key_ptr.*; - switch (info.bits) { - 16 => try ass.parseContextDependentFloat(16), - 32 => try ass.parseContextDependentFloat(32), - 64 => try ass.parseContextDependentFloat(64), - else => return ass.fail(tok.start, "cannot parse {}-bit info literal", .{info.bits}), - } + const words = cg.sections.globals.instructions.items; + var offset: usize = 0; + while (offset < words.len) { + const word_count = words[offset] >> 16; + const opcode: Opcode = @enumFromInt(words[offset] & 0xFFFF); + defer offset += word_count; + if (word_count == 0) break; + switch (opcode) { + .OpTypeInt => if (word_count >= 4 and @as(Id, @enumFromInt(words[offset + 1])) == result_id) { + const width: u16 = @intCast(words[offset + 2]); + const signedness: std.lang.Signedness = if (words[offset + 3] == 0) .unsigned else .signed; + return ass.parseContextDependentInt(signedness, width); + }, + .OpTypeFloat => if (word_count >= 3 and @as(Id, @enumFromInt(words[offset + 1])) == result_id) { + const bits = words[offset + 2]; + return switch (bits) { + 16 => ass.parseContextDependentFloat(16), + 32 => ass.parseContextDependentFloat(32), + 64 => ass.parseContextDependentFloat(64), + else => ass.fail(tok.start, "cannot parse {}-bit info literal", .{bits}), + }; + }, + else => {}, } } @@ -769,7 +758,7 @@ fn parseContextDependentNumber(ass: *Assembler) !void { } fn parseContextDependentInt(ass: *Assembler, signedness: std.lang.Signedness, width: u32) !void { - const gpa = ass.cg.module.gpa; + const gpa = ass.cg.gpa; const tok = ass.currentToken(); if (ass.eatToken(.placeholder)) { @@ -820,7 +809,7 @@ fn parseContextDependentInt(ass: *Assembler, signedness: std.lang.Signedness, wi } fn parseContextDependentFloat(ass: *Assembler, comptime width: u16) !void { - const gpa = ass.cg.module.gpa; + const gpa = ass.cg.gpa; const Float = std.meta.Float(width); const Int = @Int(.unsigned, width); @@ -893,7 +882,7 @@ fn tokenText(ass: Assembler, tok: Token) []const u8 { /// Tokenize `ass.src` and put the tokens in `ass.tokens`. /// Any errors encountered are appended to `ass.errors`. fn tokenize(ass: *Assembler) !void { - const gpa = ass.cg.module.gpa; + const gpa = ass.cg.gpa; ass.tokens.clearRetainingCapacity(); diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index d60ccf8dc1fd3f2d44e2ac3017f7c904f0946bfb..dd9a522b1719661129a2923003948a399eafb3c9 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -1,36 +1,103 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; -const Target = std.Target; -const Signedness = std.lang.Signedness; -const assert = std.debug.assert; -const log = std.log.scoped(.codegen); - -const builtin = @import("builtin"); -const link = @import("../../link.zig"); -const codegen = @import("../../codegen.zig"); -const Zcu = @import("../../Zcu.zig"); -const Type = @import("../../Type.zig"); -const Value = @import("../../Value.zig"); -const Air = @import("../../Air.zig"); -const InternPool = @import("../../InternPool.zig"); -const Section = @import("Section.zig"); -const Assembler = @import("Assembler.zig"); -const Mir = @import("Mir.zig"); - -const spec = @import("spec.zig"); -const Opcode = spec.Opcode; -const Word = spec.Word; -const Id = spec.Id; -const IdRange = spec.IdRange; -const StorageClass = spec.StorageClass; - -const Module = @import("Module.zig"); -const Decl = Module.Decl; -const Repr = Module.Repr; -const InternMap = Module.InternMap; -const PtrTypeMap = Module.PtrTypeMap; - -const CodeGen = @This(); +// Compilation +pt: Zcu.PerThread, +zcu: *Zcu, +gpa: Allocator, +arena: Allocator, +air: Air, +liveness: Air.Liveness, +owner_nav: InternPool.Nav.Index, +base_line: u32, + +// Module-level output (accumulated across the nav's codegen) +next_result_id: Word = 1, +decls: std.ArrayList(Decl) = .empty, +decl_deps: std.ArrayList(Decl.Index) = .empty, +nav_link: std.AutoHashMapUnmanaged(InternPool.Nav.Index, Decl.Index) = .empty, +uav_link: std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Decl.Index) = .empty, +entry_points: std.array_hash_map.Auto(Id, EntryPoint) = .empty, +error_buffer: ?Decl.Index = null, +struct_types: std.array_hash_map.Custom(StructType, Id, StructType.HashContext, true) = .empty, +builtins: std.AutoHashMapUnmanaged(struct { spec.BuiltIn, spec.StorageClass }, Decl.Index) = .empty, +sections: struct { + // Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module". + extended_instruction_set: Section = .{}, + memory_model: Section = .{}, + execution_modes: Section = .{}, + debug_strings: Section = .{}, + debug_names: Section = .{}, + annotations: Section = .{}, + globals: Section = .{}, + functions: Section = .{}, +} = .{}, + +// Per-function state (reset between top-level genNav calls) +prologue: Section = .{}, +body: Section = .{}, +args: std.ArrayList(Id) = .empty, +next_arg_index: u32 = 0, +block_stack: std.ArrayList(*Block) = .empty, +block_label: Id = .none, +/// Whether the current block has been terminated by a terminator +/// instruction (e.g. OpKill from inline assembly). When true, no further +/// branch instructions should be emitted for the current block. +block_terminated: bool = false, +block_results: std.AutoHashMapUnmanaged(Air.Inst.Index, Id) = .empty, +inst_results: std.AutoHashMapUnmanaged(Air.Inst.Index, Id) = .empty, +tracked_allocas: std.AutoHashMapUnmanaged(Id, ?Id) = .empty, +loop_switches: std.AutoHashMapUnmanaged(Air.Inst.Index, LoopSwitch) = .empty, +id_scratch: std.ArrayList(Id) = .empty, + +const big_int_bits = @bitSizeOf(u32); + +/// 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. +pub 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, +}; + +/// A function or global, tracked here so the linker can order globals and build +/// per-entry-point interface lists. +pub const Decl = struct { + pub const Index = enum(u32) { _ }; + pub const Kind = enum { func, global, invocation_global }; + + kind: Kind, + /// Result-id of the associated OpFunction / OpVariable / InvocationGlobal. + result_id: Id, + /// Range into `decl_deps` for this decl's dependencies. + begin_dep: usize = 0, + end_dep: usize = 0, + /// Whether an extern-function stub has been emitted. + has_extern_stub: bool = false, +}; + +pub const EntryPoint = struct { + decl_index: Decl.Index, + name: []const u8, + cc: std.builtin.CallingConvention, +}; + +const StructType = struct { + fields: []const Id, + ip_index: InternPool.Index, + + const HashContext = struct { + pub fn hash(_: @This(), ty: StructType) u32 { + var hasher = std.hash.Wyhash.init(0); + hasher.update(std.mem.sliceAsBytes(ty.fields)); + hasher.update(std.mem.asBytes(&ty.ip_index)); + return @truncate(hasher.final()); + } + + pub fn eql(_: @This(), a: StructType, b: StructType, _: usize) bool { + return a.ip_index == b.ip_index and std.mem.eql(Id, a.fields, b.fields); + } + }; +}; pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { return comptime &.initMany(&.{ @@ -43,60 +110,40 @@ pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { }); } -pub const zig_call_abi_ver = 3; - const LoopSwitch = struct { cond_var: Id, continue_label: Id }; -/// This type indicates the way that a block is terminated. The -/// state of a particular block is used to track how a jump from -/// inside the block must reach the outside. +/// Pointer-typed AIR refs should resolve through `resolvePtr` to handle the +/// `tracked_allocas` case explicitly at every use site. +const Ptr = union(enum) { + id: Id, + /// Function-local pointer whose value lives in `tracked_allocas` rather + /// than a real OpVariable. `slot` is the current pointee value. + tracked: struct { id: Id, slot: *?Id }, +}; + +/// Tracks how control flow leaves a Zig `block` under SPIR-V's structured +/// control flow rules. const Block = union(enum) { const Incoming = struct { src_label: Id, - /// Instruction that returns an u32 value of the - /// `Air.Inst.Index` that control flow should jump to. + /// Block index (u32) that control flow should jump to next. next_block: Id, }; const SelectionMerge = struct { - /// Incoming block from the `then` label. - /// Note that the incoming block from the `else` label is - /// either given by the next element in the stack. incoming: Incoming, - /// The label id of the cond_br's merge block. - /// For the top-most element in the stack, this - /// value is undefined. + /// Label of the cond_br's merge block (undefined for top-of-stack). merge_block: Id, }; - /// For a `selection` type block, we cannot use early exits, and we - /// must generate a 'merge ladder' of OpSelection instructions. To that end, - /// we keep a stack of the merges that still must be closed at the end of - /// a block. - /// - /// This entire structure basically just resembles a tree like - /// a x - /// \ / - /// b o merge - /// \ / - /// c o merge - /// \ / - /// o merge - /// / - /// o jump to next block + /// Selection blocks can't use early exits. Closing requires a "merge ladder" + /// of nested OpSelectionMerge instructions, one per pending merge. selection: struct { - /// In order to know which merges we still need to do, we need to keep - /// a stack of those. merge_stack: std.ArrayList(SelectionMerge) = .empty, }, - /// For a `loop` type block, we can early-exit the block by - /// jumping to the loop exit node, and we don't need to generate - /// an entire stack of merges. + /// Loop blocks early-exit by jumping to the loop merge label. loop: struct { - /// The next block to jump to can be determined from any number - /// of conditions that jump to the loop exit. merges: std.ArrayList(Incoming) = .empty, - /// The label id of the loop's merge block. merge_block: Id, }, @@ -109,39 +156,36 @@ const Block = union(enum) { } }; -pt: Zcu.PerThread, -air: Air, -liveness: Air.Liveness, -owner_nav: InternPool.Nav.Index, -module: *Module, -block_stack: std.ArrayList(*Block) = .empty, -block_results: std.AutoHashMapUnmanaged(Air.Inst.Index, Id) = .empty, -base_line: u32, -block_label: Id = .none, -/// Whether the current block has been terminated by a terminator -/// instruction (e.g. OpKill from inline assembly). When true, no further -/// branch instructions should be emitted for the current block. -block_terminated: bool = false, -next_arg_index: u32 = 0, -args: std.ArrayList(Id) = .empty, -virtual_allocas: std.AutoHashMapUnmanaged(Id, ?Id) = .empty, -inst_results: std.AutoHashMapUnmanaged(Air.Inst.Index, Id) = .empty, -loop_switches: std.AutoHashMapUnmanaged(Air.Inst.Index, LoopSwitch) = .empty, -id_scratch: std.ArrayList(Id) = .empty, -prologue: Section = .{}, -body: Section = .{}, - pub fn deinit(cg: *CodeGen) void { - const gpa = cg.module.gpa; + const gpa = cg.gpa; cg.block_stack.deinit(gpa); cg.block_results.deinit(gpa); cg.args.deinit(gpa); - cg.virtual_allocas.deinit(gpa); + cg.tracked_allocas.deinit(gpa); cg.inst_results.deinit(gpa); cg.loop_switches.deinit(gpa); cg.id_scratch.deinit(gpa); cg.prologue.deinit(gpa); cg.body.deinit(gpa); + + cg.nav_link.deinit(gpa); + cg.uav_link.deinit(gpa); + + cg.sections.extended_instruction_set.deinit(gpa); + cg.sections.memory_model.deinit(gpa); + cg.sections.execution_modes.deinit(gpa); + cg.sections.debug_strings.deinit(gpa); + cg.sections.debug_names.deinit(gpa); + cg.sections.annotations.deinit(gpa); + cg.sections.globals.deinit(gpa); + cg.sections.functions.deinit(gpa); + + cg.struct_types.deinit(gpa); + cg.builtins.deinit(gpa); + + cg.decls.deinit(gpa); + cg.decl_deps.deinit(gpa); + cg.entry_points.deinit(gpa); } pub fn generate( @@ -157,19 +201,15 @@ pub fn generate( var arena = std.heap.ArenaAllocator.init(gpa); defer arena.deinit(); - var module: Module = .{ - .gpa = gpa, - .arena = arena.allocator(), - .zcu = zcu, - }; - defer module.deinit(); var cg: CodeGen = .{ .pt = pt, + .gpa = gpa, + .arena = arena.allocator(), + .zcu = zcu, .air = air.*, .liveness = liveness.*.?, .owner_nav = nav, - .module = &module, .base_line = zcu.navSrcLine(nav), }; defer cg.deinit(); @@ -191,19 +231,15 @@ pub fn generateNav( var arena = std.heap.ArenaAllocator.init(gpa); defer arena.deinit(); - var module: Module = .{ - .gpa = gpa, - .arena = arena.allocator(), - .zcu = zcu, - }; - defer module.deinit(); var cg: CodeGen = .{ .pt = pt, + .gpa = gpa, + .arena = arena.allocator(), + .zcu = zcu, .air = undefined, .liveness = undefined, .owner_nav = nav_index, - .module = &module, .base_line = zcu.navSrcLine(nav_index), }; defer cg.deinit(); @@ -217,11 +253,9 @@ pub fn generateNav( } fn serializeToMir(cg: *CodeGen, gpa: Allocator) codegen.Error!Mir { - const module = cg.module; - - const owner_entry = module.nav_link.get(cg.owner_nav); + const owner_entry = cg.nav_link.get(cg.owner_nav); const owner_decl_index = owner_entry orelse return .{ - .id_bound = module.next_result_id, + .id_bound = cg.next_result_id, .owner_nav = cg.owner_nav, .kind = .func, .decl_result_id = .none, @@ -239,14 +273,14 @@ fn serializeToMir(cg: *CodeGen, gpa: Allocator) codegen.Error!Mir { .entry_points = &.{}, }; - const owner_decl = module.declPtr(owner_decl_index); + const owner_decl = cg.declPtr(owner_decl_index); var nav_refs: std.ArrayList(Mir.NavRef) = .empty; defer nav_refs.deinit(gpa); - var nav_it = module.nav_link.iterator(); + var nav_it = cg.nav_link.iterator(); while (nav_it.next()) |entry| { if (entry.key_ptr.* == cg.owner_nav) continue; - const decl = module.declPtr(entry.value_ptr.*); + const decl = cg.declPtr(entry.value_ptr.*); try nav_refs.append(gpa, .{ .local_id = decl.result_id, .nav = entry.key_ptr.*, @@ -256,9 +290,9 @@ fn serializeToMir(cg: *CodeGen, gpa: Allocator) codegen.Error!Mir { var uav_refs: std.ArrayList(Mir.UavRef) = .empty; defer uav_refs.deinit(gpa); - var uav_it = module.uav_link.iterator(); + var uav_it = cg.uav_link.iterator(); while (uav_it.next()) |entry| { - const decl = module.declPtr(entry.value_ptr.*); + const decl = cg.declPtr(entry.value_ptr.*); try uav_refs.append(gpa, .{ .local_id = decl.result_id, .val = entry.key_ptr.*[0], @@ -272,9 +306,9 @@ fn serializeToMir(cg: *CodeGen, gpa: Allocator) codegen.Error!Mir { var internal_globals: std.ArrayList(Id) = .empty; defer internal_globals.deinit(gpa); - const deps = module.decl_deps.items[owner_decl.begin_dep..owner_decl.end_dep]; + const deps = cg.decl_deps.items[owner_decl.begin_dep..owner_decl.end_dep]; for (deps) |dep_index| { - const dep_decl = module.declPtr(dep_index); + const dep_decl = cg.declPtr(dep_index); var found = false; nav_it.index = 0; while (nav_it.next()) |entry| { @@ -294,10 +328,10 @@ fn serializeToMir(cg: *CodeGen, gpa: Allocator) codegen.Error!Mir { var ep_list: std.ArrayList(Mir.EntryPoint) = .empty; defer ep_list.deinit(gpa); - var ep_it = module.entry_points.iterator(); + var ep_it = cg.entry_points.iterator(); while (ep_it.next()) |entry| { const ep = entry.value_ptr; - const ep_decl = module.declPtr(ep.decl_index); + const ep_decl = cg.declPtr(ep.decl_index); try ep_list.append(gpa, .{ .local_id = ep_decl.result_id, .name = try gpa.dupe(u8, ep.name), @@ -306,17 +340,17 @@ fn serializeToMir(cg: *CodeGen, gpa: Allocator) codegen.Error!Mir { } return .{ - .id_bound = module.next_result_id, + .id_bound = cg.next_result_id, .owner_nav = cg.owner_nav, .kind = owner_decl.kind, .decl_result_id = owner_decl.result_id, - .extended_instruction_set = try module.sections.extended_instruction_set.instructions.toOwnedSlice(gpa), - .globals = try module.sections.globals.instructions.toOwnedSlice(gpa), - .functions = try module.sections.functions.instructions.toOwnedSlice(gpa), - .annotations = try module.sections.annotations.instructions.toOwnedSlice(gpa), - .debug_names = try module.sections.debug_names.instructions.toOwnedSlice(gpa), - .debug_strings = try module.sections.debug_strings.instructions.toOwnedSlice(gpa), - .execution_modes = try module.sections.execution_modes.instructions.toOwnedSlice(gpa), + .extended_instruction_set = try cg.sections.extended_instruction_set.instructions.toOwnedSlice(gpa), + .globals = try cg.sections.globals.instructions.toOwnedSlice(gpa), + .functions = try cg.sections.functions.instructions.toOwnedSlice(gpa), + .annotations = try cg.sections.annotations.instructions.toOwnedSlice(gpa), + .debug_names = try cg.sections.debug_names.instructions.toOwnedSlice(gpa), + .debug_strings = try cg.sections.debug_strings.instructions.toOwnedSlice(gpa), + .execution_modes = try cg.sections.execution_modes.instructions.toOwnedSlice(gpa), .nav_refs = try nav_refs.toOwnedSlice(gpa), .uav_refs = try uav_refs.toOwnedSlice(gpa), .decl_deps = try decl_deps.toOwnedSlice(gpa), @@ -325,11 +359,365 @@ fn serializeToMir(cg: *CodeGen, gpa: Allocator) codegen.Error!Mir { }; } +fn typeOf(cg: *CodeGen, inst: Air.Inst.Ref) Type { + const zcu = cg.zcu; + return cg.air.typeOf(inst, &zcu.intern_pool); +} + +fn typeOfIndex(cg: *CodeGen, inst: Air.Inst.Index) Type { + const zcu = cg.zcu; + return cg.air.typeOfIndex(inst, &zcu.intern_pool); +} + +/// Does not generate the nav. +pub fn resolveNav(cg: *CodeGen, ip: *InternPool, nav_index: InternPool.Nav.Index) !Decl.Index { + const entry = try cg.nav_link.getOrPut(cg.gpa, nav_index); + if (!entry.found_existing) { + const nav = ip.getNav(nav_index); + // TODO: Extern fn? + const kind: Decl.Kind = if (ip.isFunctionType(nav.resolved.?.type)) + .func + else switch (nav.resolved.?.@"addrspace") { + .generic => .invocation_global, + else => .global, + }; + entry.value_ptr.* = try cg.allocDecl(kind); + } + + return entry.value_ptr.*; +} + +pub fn allocIds(cg: *CodeGen, n: u32) spec.IdRange { + defer cg.next_result_id += n; + return .{ .base = cg.next_result_id, .len = n }; +} + +pub fn allocId(cg: *CodeGen) Id { + return cg.allocIds(1).at(0); +} + +pub fn idBound(cg: *const CodeGen) Word { + return cg.next_result_id; +} + +pub fn addEntryPointDeps( + cg: *CodeGen, + decl_index: Decl.Index, + seen: *std.bit_set.Dynamic, + interface: *std.array_list.Managed(Id), +) !void { + const decl = cg.declPtr(decl_index); + const deps = cg.decl_deps.items[decl.begin_dep..decl.end_dep]; + + if (seen.isSet(@intFromEnum(decl_index))) { + return; + } + + seen.set(@intFromEnum(decl_index)); + + if (decl.kind == .global) { + try interface.append(decl.result_id); + } + + for (deps) |dep| { + try cg.addEntryPointDeps(dep, seen, interface); + } +} + +pub fn importInstructionSet(cg: *CodeGen, set: spec.InstructionSet) !Id { + assert(set != .core); + const result_id = cg.allocId(); + try cg.sections.extended_instruction_set.emit(cg.gpa, .OpExtInstImport, .{ + .id_result = result_id, + .name = @tagName(set), + }); + return result_id; +} + +pub fn boolType(cg: *CodeGen) !Id { + const result_id = cg.allocId(); + try cg.sections.globals.emit(cg.gpa, .OpTypeBool, .{ + .id_result = result_id, + }); + return result_id; +} + +pub fn voidType(cg: *CodeGen) !Id { + const result_id = cg.allocId(); + try cg.sections.globals.emit(cg.gpa, .OpTypeVoid, .{ + .id_result = result_id, + }); + try cg.debugName(result_id, "void"); + return result_id; +} + +pub fn opaqueType(cg: *CodeGen, name: []const u8) !Id { + const result_id = cg.allocId(); + try cg.sections.globals.emit(cg.gpa, .OpTypeOpaque, .{ + .id_result = result_id, + .literal_string = name, + }); + try cg.debugName(result_id, name); + return result_id; +} + +pub fn backingIntBits(cg: *const CodeGen, bits: u16) struct { u16, bool } { + assert(bits != 0); + const target = cg.zcu.getTarget(); + const ints = [_]struct { bits: u16, enabled: bool }{ + .{ .bits = 8, .enabled = target.cpu.has(.spirv, .int8) }, + .{ .bits = 16, .enabled = target.cpu.has(.spirv, .int16) }, + .{ .bits = 32, .enabled = true }, + .{ .bits = 64, .enabled = target.cpu.has(.spirv, .int64) or target.cpu.arch == .spirv64 }, + }; + + for (ints) |int| { + if (bits <= int.bits and int.enabled) return .{ int.bits, false }; + } + + return .{ std.mem.alignForward(u16, bits, big_int_bits), true }; +} + +pub fn intType(cg: *CodeGen, signedness: std.lang.Signedness, bits: u16) !Id { + assert(bits > 0); + + const target = cg.zcu.getTarget(); + const actual_signedness = switch (target.os.tag) { + // Kernel only supports unsigned ints. + .opencl, .amdhsa => .unsigned, + else => signedness, + }; + const backing_bits, const big_int = cg.backingIntBits(bits); + if (big_int) { + const u32_ty = try cg.intType(.unsigned, 32); + const len_id = cg.allocId(); + try cg.sections.globals.emit(cg.gpa, .OpConstant, .{ + .id_result_type = u32_ty, + .id_result = len_id, + .value = .{ .uint32 = backing_bits / big_int_bits }, + }); + return cg.arrayType(len_id, u32_ty); + } + + const result_id = cg.allocId(); + try cg.sections.globals.emit(cg.gpa, .OpTypeInt, .{ + .id_result = result_id, + .width = backing_bits, + .signedness = switch (actual_signedness) { + .signed => 1, + .unsigned => 0, + }, + }); + switch (actual_signedness) { + .signed => try cg.debugNameFmt(result_id, "i{}", .{backing_bits}), + .unsigned => try cg.debugNameFmt(result_id, "u{}", .{backing_bits}), + } + return result_id; +} + +pub fn floatType(cg: *CodeGen, bits: u16) !Id { + assert(bits > 0); + const result_id = cg.allocId(); + try cg.sections.globals.emit(cg.gpa, .OpTypeFloat, .{ + .id_result = result_id, + .width = bits, + }); + try cg.debugNameFmt(result_id, "f{}", .{bits}); + return result_id; +} + +pub fn vectorType(cg: *CodeGen, len: u32, child_ty_id: Id) !Id { + const result_id = cg.allocId(); + try cg.sections.globals.emit(cg.gpa, .OpTypeVector, .{ + .id_result = result_id, + .component_type = child_ty_id, + .component_count = len, + }); + return result_id; +} + +pub fn arrayType(cg: *CodeGen, len_id: Id, child_ty_id: Id) !Id { + const result_id = cg.allocId(); + try cg.sections.globals.emit(cg.gpa, .OpTypeArray, .{ + .id_result = result_id, + .element_type = child_ty_id, + .length = len_id, + }); + return result_id; +} + +pub fn ptrType(cg: *CodeGen, child_ty_id: Id, storage_class: spec.StorageClass) !Id { + const result_id = cg.allocId(); + try cg.sections.globals.emit(cg.gpa, .OpTypePointer, .{ + .id_result = result_id, + .storage_class = storage_class, + .type = child_ty_id, + }); + return result_id; +} + +pub fn structType( + cg: *CodeGen, + types: []const Id, + maybe_names: ?[]const []const u8, + ip_index: InternPool.Index, +) !Id { + const actual_ip_index = if (cg.zcu.comp.config.root_strip) .none else ip_index; + + if (cg.struct_types.get(.{ .fields = types, .ip_index = actual_ip_index })) |id| return id; + const result_id = cg.allocId(); + const types_dup = try cg.arena.dupe(Id, types); + try cg.sections.globals.emit(cg.gpa, .OpTypeStruct, .{ + .id_result = result_id, + .id_ref = types_dup, + }); + + if (maybe_names) |names| { + assert(names.len == types.len); + for (names, 0..) |name, i| { + try cg.memberDebugName(result_id, @intCast(i), name); + } + } + + try cg.struct_types.put( + cg.gpa, + .{ .fields = types_dup, .ip_index = actual_ip_index }, + result_id, + ); + return result_id; +} + +pub fn functionType(cg: *CodeGen, return_ty_id: Id, param_type_ids: []const Id) !Id { + const result_id = cg.allocId(); + try cg.sections.globals.emit(cg.gpa, .OpTypeFunction, .{ + .id_result = result_id, + .return_type = return_ty_id, + .id_ref_2 = param_type_ids, + }); + return result_id; +} + +pub fn constUndef(cg: *CodeGen, ty_id: Id) !Id { + const result_id = cg.allocId(); + try cg.sections.globals.emit(cg.gpa, .OpUndef, .{ + .id_result_type = ty_id, + .id_result = result_id, + }); + return result_id; +} + +pub fn constNull(cg: *CodeGen, ty_id: Id) !Id { + const result_id = cg.allocId(); + try cg.sections.globals.emit(cg.gpa, .OpConstantNull, .{ + .id_result_type = ty_id, + .id_result = result_id, + }); + return result_id; +} + +pub fn decorate( + cg: *CodeGen, + target: Id, + decoration: spec.Decoration.Extended, +) !void { + try cg.sections.annotations.emit(cg.gpa, .OpDecorate, .{ + .target = target, + .decoration = decoration, + }); +} + +pub fn decorateMember( + cg: *CodeGen, + structure_type: Id, + member: u32, + decoration: spec.Decoration.Extended, +) !void { + try cg.sections.annotations.emit(cg.gpa, .OpMemberDecorate, .{ + .structure_type = structure_type, + .member = member, + .decoration = decoration, + }); +} + +pub fn allocDecl(cg: *CodeGen, kind: Decl.Kind) !Decl.Index { + try cg.decls.append(cg.gpa, .{ + .kind = kind, + .result_id = cg.allocId(), + }); + + return @as(Decl.Index, @enumFromInt(@as(u32, @intCast(cg.decls.items.len - 1)))); +} + +pub fn declPtr(cg: *CodeGen, index: Decl.Index) *Decl { + return &cg.decls.items[@intFromEnum(index)]; +} + +pub fn debugName(cg: *CodeGen, target: Id, name: []const u8) !void { + if (cg.zcu.comp.config.root_strip) return; + try cg.sections.debug_names.emit(cg.gpa, .OpName, .{ + .target = target, + .name = name, + }); +} + +pub fn debugNameFmt(cg: *CodeGen, target: Id, comptime fmt: []const u8, args: anytype) !void { + if (cg.zcu.comp.config.root_strip) return; + const name = try std.fmt.allocPrint(cg.gpa, fmt, args); + defer cg.gpa.free(name); + try cg.debugName(target, name); +} + +pub fn memberDebugName(cg: *CodeGen, target: Id, member: u32, name: []const u8) !void { + if (cg.zcu.comp.config.root_strip) return; + try cg.sections.debug_names.emit(cg.gpa, .OpMemberName, .{ + .type = target, + .member = member, + .name = name, + }); +} + +pub fn storageClass(cg: *const CodeGen, as: std.lang.AddressSpace) spec.StorageClass { + const target = cg.zcu.getTarget(); + return switch (as) { + .generic => .function, + .global => switch (target.os.tag) { + .opencl, .amdhsa => .cross_workgroup, + else => .storage_buffer, + }, + .push_constant => .push_constant, + .output => .output, + .uniform => .uniform, + .storage_buffer => .storage_buffer, + .physical_storage_buffer => .physical_storage_buffer, + .constant => .uniform_constant, + .shared => .workgroup, + .local => .function, + .input => .input, + .gs, + .fs, + .ss, + .far, + .param, + .flash, + .flash1, + .flash2, + .flash3, + .flash4, + .flash5, + .cog, + .lut, + .hub, + .externref, + .funcref, + => unreachable, + }; +} + const Error = error{ AlreadyReported, OutOfMemory }; pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; + const gpa = cg.gpa; + const zcu = cg.zcu; const ip = &zcu.intern_pool; const target = zcu.getTarget(); @@ -339,17 +727,17 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { if (!do_codegen and !ty.hasRuntimeBits(zcu)) return; - const spv_decl_index = try cg.module.resolveNav(ip, cg.owner_nav); - const decl = cg.module.declPtr(spv_decl_index); + const spv_decl_index = try cg.resolveNav(ip, cg.owner_nav); + const decl = cg.declPtr(spv_decl_index); const result_id = decl.result_id; - decl.begin_dep = cg.module.decl_deps.items.len; + decl.begin_dep = cg.decl_deps.items.len; switch (decl.kind) { .func => { if (nav.resolved.?.is_extern_decl) { _ = try cg.resolveType(ty, .direct); try emitExternFnStub(cg, nav, decl, ty); - decl.end_dep = cg.module.decl_deps.items.len; + decl.end_dep = cg.decl_deps.items.len; return; } @@ -357,7 +745,7 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { const return_ty_id = try cg.resolveFnReturnType(.fromInterned(fn_info.return_type)); const is_test = zcu.test_functions.contains(cg.owner_nav); - const func_result_id = if (is_test) cg.module.allocId() else result_id; + const func_result_id = if (is_test) cg.allocId() else result_id; const prototype_ty_id = try cg.resolveType(ty, .direct); try cg.prologue.emit(gpa, .OpFunction, .{ .id_result_type = return_ty_id, @@ -368,14 +756,13 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { .function_control = .{}, }); - comptime assert(zig_call_abi_ver == 3); try cg.args.ensureUnusedCapacity(gpa, fn_info.param_types.len); for (fn_info.param_types.get(ip)) |param_ty_index| { const param_ty: Type = .fromInterned(param_ty_index); if (!param_ty.hasRuntimeBits(zcu)) continue; const param_type_id = try cg.resolveType(param_ty, .direct); - const arg_result_id = cg.module.allocId(); + const arg_result_id = cg.allocId(); try cg.prologue.emit(gpa, .OpFunctionParameter, .{ .id_result_type = param_type_id, .id_result = arg_result_id, @@ -384,7 +771,7 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { } // TODO: This could probably be done in a better way... - const root_block_id = cg.module.allocId(); + const root_block_id = cg.allocId(); // The root block of a function declaration should appear before OpVariable instructions, // so it is generated into the function's prologue. @@ -400,26 +787,26 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { try cg.body.emit(gpa, .OpUnreachable, {}); try cg.body.emit(gpa, .OpFunctionEnd, {}); // Append the actual code into the functions section. - try cg.module.sections.functions.append(gpa, cg.prologue); - try cg.module.sections.functions.append(gpa, cg.body); + try cg.sections.functions.append(gpa, cg.prologue); + try cg.sections.functions.append(gpa, cg.body); // Temporarily generate a test kernel declaration if this is a test function. if (is_test) { try cg.generateTestEntryPoint(nav.fqn.toSlice(ip), spv_decl_index, func_result_id); } - try cg.module.debugName(func_result_id, nav.fqn.toSlice(ip)); + try cg.debugName(func_result_id, nav.fqn.toSlice(ip)); }, .global => { const key = ip.indexToKey(val.toIntern()).@"extern"; - const storage_class = cg.module.storageClass(nav.resolved.?.@"addrspace"); + const storage_class = cg.storageClass(nav.resolved.?.@"addrspace"); assert(storage_class != .generic); // These should be instance globals const ty_id = try cg.resolveType(ty, .indirect); - const ptr_ty_id = try cg.module.ptrType(ty_id, storage_class); + const ptr_ty_id = try cg.ptrType(ty_id, storage_class); - try cg.module.sections.globals.emit(gpa, .OpVariable, .{ + try cg.sections.globals.emit(gpa, .OpVariable, .{ .id_result_type = ptr_ty_id, .id_result = result_id, .storage_class = storage_class, @@ -430,11 +817,11 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { switch (storage_class) { .uniform, .push_constant, .storage_buffer, .physical_storage_buffer => { if (ty.zigTypeTag(zcu) == .@"struct" and storage_class != .physical_storage_buffer) { - try cg.module.decorate(ty_id, .block); + try cg.decorate(ty_id, .block); } if (ty.hasRuntimeBits(zcu)) { - try cg.module.decorate(ptr_ty_id, .{ + try cg.decorate(ptr_ty_id, .{ .array_stride = .{ .array_stride = @intCast(ty.abiSize(zcu)) }, }); try cg.decorateLayout(ty, ty_id); @@ -448,23 +835,23 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { if (storage_class != .output and storage_class != .input and storage_class != .uniform_constant) { return cg.fail("storage class must be one of (output, input, uniform_constant) but is {s}", .{@tagName(storage_class)}); } - try cg.module.decorate(result_id, .{ + try cg.decorate(result_id, .{ .location = .{ .location = location }, }); }, .flat => |location| { - try cg.module.decorate(result_id, .{ .location = .{ .location = location } }); - try cg.module.decorate(result_id, .flat); + try cg.decorate(result_id, .{ .location = .{ .location = location } }); + try cg.decorate(result_id, .flat); }, .descriptor => |descriptor| { if (storage_class != .storage_buffer and storage_class != .uniform and storage_class != .uniform_constant) { return cg.fail("storage class must be one of (storage_buffer, uniform, uniform_constant) but is {s}", .{@tagName(storage_class)}); } - try cg.module.decorate(result_id, .{ + try cg.decorate(result_id, .{ .binding = .{ .binding_point = descriptor.binding }, }); - try cg.module.decorate(result_id, .{ + try cg.decorate(result_id, .{ .descriptor_set = .{ .descriptor_set = descriptor.set }, }); }, @@ -474,10 +861,10 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { } if (std.meta.stringToEnum(spec.BuiltIn, nav.fqn.toSlice(ip))) |built_in| { - try cg.module.decorate(result_id, .{ .built_in = .{ .built_in = built_in } }); + try cg.decorate(result_id, .{ .built_in = .{ .built_in = built_in } }); } - try cg.module.debugName(result_id, nav.fqn.toSlice(ip)); + try cg.debugName(result_id, nav.fqn.toSlice(ip)); }, .invocation_global => { // `@extern()` produces an invocation_global whose value is a @@ -488,18 +875,18 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { if (ptr_key.base_addr != .nav or ptr_key.byte_offset != 0) break :alias; const underlying_nav = ip.getNav(ptr_key.base_addr.nav); if (!underlying_nav.resolved.?.is_extern_decl) break :alias; - cg.module.declPtr(spv_decl_index).end_dep = cg.module.decl_deps.items.len; + cg.declPtr(spv_decl_index).end_dep = cg.decl_deps.items.len; return; } const ty_id = try cg.resolveType(ty, .indirect); - const ptr_ty_id = try cg.module.ptrType(ty_id, .function); + const ptr_ty_id = try cg.ptrType(ty_id, .function); // TODO: Combine with resolveAnonDecl? const void_ty_id = try cg.resolveType(.void, .direct); - const initializer_proto_ty_id = try cg.module.functionType(void_ty_id, &.{}); + const initializer_proto_ty_id = try cg.functionType(void_ty_id, &.{}); - const initializer_id = cg.module.allocId(); + const initializer_id = cg.allocId(); try cg.prologue.emit(gpa, .OpFunction, .{ .id_result_type = try cg.resolveType(.void, .direct), .id_result = initializer_id, @@ -507,7 +894,7 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { .function_type = initializer_proto_ty_id, }); - const root_block_id = cg.module.allocId(); + const root_block_id = cg.allocId(); try cg.prologue.emit(gpa, .OpLabel, .{ .id_result = root_block_id, }); @@ -521,33 +908,33 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { try cg.body.emit(gpa, .OpReturn, {}); try cg.body.emit(gpa, .OpFunctionEnd, {}); - try cg.module.sections.functions.append(gpa, cg.prologue); - try cg.module.sections.functions.append(gpa, cg.body); + try cg.sections.functions.append(gpa, cg.prologue); + try cg.sections.functions.append(gpa, cg.body); - try cg.module.debugNameFmt(initializer_id, "initializer of {f}", .{nav.fqn.fmt(ip)}); - try cg.module.debugName(result_id, nav.fqn.toSlice(ip)); + try cg.debugNameFmt(initializer_id, "initializer of {f}", .{nav.fqn.fmt(ip)}); + try cg.debugName(result_id, nav.fqn.toSlice(ip)); - try cg.module.sections.globals.emit(gpa, .OpExtInst, .{ + try cg.sections.globals.emit(gpa, .OpExtInst, .{ .id_result_type = ptr_ty_id, .id_result = result_id, - .set = try cg.module.importInstructionSet(.zig), + .set = try cg.importInstructionSet(.zig), .instruction = .{ .inst = @intFromEnum(spec.Zig.InvocationGlobal) }, .id_ref_4 = &.{initializer_id}, }); }, } - cg.module.declPtr(spv_decl_index).end_dep = cg.module.decl_deps.items.len; + cg.declPtr(spv_decl_index).end_dep = cg.decl_deps.items.len; } fn decorateLayout(cg: *CodeGen, ty: Type, ty_id: spec.Id) Error!void { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ip = &zcu.intern_pool; switch (ty.zigTypeTag(zcu)) { .array => { const elem_ty = ty.childType(zcu); if (!elem_ty.hasRuntimeBits(zcu)) return; - try cg.module.decorate(ty_id, .{ + try cg.decorate(ty_id, .{ .array_stride = .{ .array_stride = @intCast(elem_ty.abiSize(zcu)) }, }); try cg.decorateLayout(elem_ty, try cg.resolveType(elem_ty, .indirect)); @@ -556,7 +943,7 @@ fn decorateLayout(cg: *CodeGen, ty: Type, ty_id: spec.Id) Error!void { const elem_ty = ty.childType(zcu); try cg.decorateLayout(elem_ty, try cg.resolveType(elem_ty, .indirect)); if (cg.isSpvVector(ty)) return; - try cg.module.decorate(ty_id, .{ + try cg.decorate(ty_id, .{ .array_stride = .{ .array_stride = @intCast(elem_ty.abiSize(zcu)) }, }); }, @@ -570,7 +957,7 @@ fn decorateLayout(cg: *CodeGen, ty: Type, ty_id: spec.Id) Error!void { const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]); if (!field_ty.hasRuntimeBits(zcu)) continue; const offset: u32 = @intCast(ty.structFieldOffset(field_index, zcu)); - try cg.module.decorateMember(ty_id, member, .{ .offset = .{ .byte_offset = offset } }); + try cg.decorateMember(ty_id, member, .{ .offset = .{ .byte_offset = offset } }); try cg.decorateLayout(field_ty, try cg.resolveType(field_ty, .indirect)); member += 1; } @@ -598,13 +985,13 @@ fn decorateLayout(cg: *CodeGen, ty: Type, ty_id: spec.Id) Error!void { const u8_id = try cg.resolveType(.u8, .direct); if (layout.payload_padding_size != 0) { const len_id = try cg.constInt(.u32, layout.payload_padding_size); - const arr_id = try cg.module.arrayType(len_id, u8_id); - try cg.module.decorate(arr_id, .{ .array_stride = .{ .array_stride = 1 } }); + const arr_id = try cg.arrayType(len_id, u8_id); + try cg.decorate(arr_id, .{ .array_stride = .{ .array_stride = 1 } }); } if (layout.padding_size != 0) { const len_id = try cg.constInt(.u32, layout.padding_size); - const arr_id = try cg.module.arrayType(len_id, u8_id); - try cg.module.decorate(arr_id, .{ .array_stride = .{ .array_stride = 1 } }); + const arr_id = try cg.arrayType(len_id, u8_id); + try cg.decorate(arr_id, .{ .array_stride = .{ .array_stride = 1 } }); } }, .optional => { @@ -621,7 +1008,7 @@ fn decorateLayout(cg: *CodeGen, ty: Type, ty_id: spec.Id) Error!void { pub fn fail(cg: *CodeGen, comptime format: []const u8, args: anytype) Error { @branchHint(.cold); - return cg.module.zcu.codegenFail(cg.owner_nav, format, args); + return cg.zcu.codegenFail(cg.owner_nav, format, args); } pub fn todo(cg: *CodeGen, comptime format: []const u8, args: anytype) Error { @@ -631,17 +1018,17 @@ pub fn todo(cg: *CodeGen, comptime format: []const u8, args: anytype) Error { /// This imports the "default" extended instruction set for the target /// For OpenCL, OpenCL.std.100. For Vulkan and OpenGL, GLSL.std.450. fn importExtendedSet(cg: *CodeGen) !Id { - const target = cg.module.zcu.getTarget(); + const target = cg.zcu.getTarget(); return switch (target.os.tag) { - .opencl, .amdhsa => try cg.module.importInstructionSet(.@"OpenCL.std"), - .vulkan, .opengl => try cg.module.importInstructionSet(.@"GLSL.std.450"), + .opencl, .amdhsa => try cg.importInstructionSet(.@"OpenCL.std"), + .vulkan, .opengl => try cg.importInstructionSet(.@"GLSL.std.450"), else => unreachable, }; } /// Fetch the result-id for a previously generated instruction or constant. fn resolve(cg: *CodeGen, inst: Air.Inst.Ref) !Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ip = &zcu.intern_pool; if (inst.toInterned()) |val_ip_index| { const ty = cg.typeOf(inst); @@ -652,9 +1039,9 @@ fn resolve(cg: *CodeGen, inst: Air.Inst.Ref) !Id { .func => |func| func.owner_nav, else => unreachable, }; - const spv_decl_index = try cg.module.resolveNav(ip, fn_nav); - try cg.module.decl_deps.append(cg.module.gpa, spv_decl_index); - const decl = cg.module.declPtr(spv_decl_index); + const spv_decl_index = try cg.resolveNav(ip, fn_nav); + try cg.decl_deps.append(cg.gpa, spv_decl_index); + const decl = cg.declPtr(spv_decl_index); if (val_key == .@"extern") { const nav = ip.getNav(fn_nav); const nav_ty: Type = .fromInterned(nav.resolved.?.type); @@ -670,22 +1057,22 @@ fn resolve(cg: *CodeGen, inst: Air.Inst.Ref) !Id { } fn resolveUav(cg: *CodeGen, val: InternPool.Index) !Id { - const gpa = cg.module.gpa; + const gpa = cg.gpa; // TODO: This cannot be a function at this point, but it should probably be handled anyway. - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ty: Type = .fromInterned(zcu.intern_pool.typeOf(val)); const ty_id = try cg.resolveType(ty, .indirect); const spv_decl_index = blk: { - const entry = try cg.module.uav_link.getOrPut(gpa, .{ val, .function }); + const entry = try cg.uav_link.getOrPut(gpa, .{ val, .function }); if (entry.found_existing) { try cg.addFunctionDep(entry.value_ptr.*, .function); - return cg.module.declPtr(entry.value_ptr.*).result_id; + return cg.declPtr(entry.value_ptr.*).result_id; } - const spv_decl_index = try cg.module.allocDecl(.invocation_global); + const spv_decl_index = try cg.allocDecl(.invocation_global); try cg.addFunctionDep(spv_decl_index, .function); entry.value_ptr.* = spv_decl_index; break :blk spv_decl_index; @@ -697,7 +1084,7 @@ fn resolveUav(cg: *CodeGen, val: InternPool.Index) !Id { // constant lowering of this value will need to be deferred to an initializer similar to // other globals. - const result_id = cg.module.declPtr(spv_decl_index).result_id; + const result_id = cg.declPtr(spv_decl_index).result_id; { // Save the current state so that we can temporarily generate into a different function. @@ -719,16 +1106,16 @@ fn resolveUav(cg: *CodeGen, val: InternPool.Index) !Id { } const void_ty_id = try cg.resolveType(.void, .direct); - const initializer_proto_ty_id = try cg.module.functionType(void_ty_id, &.{}); + const initializer_proto_ty_id = try cg.functionType(void_ty_id, &.{}); - const initializer_id = cg.module.allocId(); + const initializer_id = cg.allocId(); try cg.prologue.emit(gpa, .OpFunction, .{ .id_result_type = try cg.resolveType(.void, .direct), .id_result = initializer_id, .function_control = .{}, .function_type = initializer_proto_ty_id, }); - const root_block_id = cg.module.allocId(); + const root_block_id = cg.allocId(); try cg.prologue.emit(gpa, .OpLabel, .{ .id_result = root_block_id, }); @@ -743,16 +1130,16 @@ fn resolveUav(cg: *CodeGen, val: InternPool.Index) !Id { try cg.body.emit(gpa, .OpReturn, {}); try cg.body.emit(gpa, .OpFunctionEnd, {}); - try cg.module.sections.functions.append(gpa, cg.prologue); - try cg.module.sections.functions.append(gpa, cg.body); + try cg.sections.functions.append(gpa, cg.prologue); + try cg.sections.functions.append(gpa, cg.body); - try cg.module.debugNameFmt(initializer_id, "initializer of __anon_{d}", .{@intFromEnum(val)}); + try cg.debugNameFmt(initializer_id, "initializer of __anon_{d}", .{@intFromEnum(val)}); - const fn_decl_ptr_ty_id = try cg.module.ptrType(ty_id, .function); - try cg.module.sections.globals.emit(gpa, .OpExtInst, .{ + const fn_decl_ptr_ty_id = try cg.ptrType(ty_id, .function); + try cg.sections.globals.emit(gpa, .OpExtInst, .{ .id_result_type = fn_decl_ptr_ty_id, .id_result = result_id, - .set = try cg.module.importInstructionSet(.zig), + .set = try cg.importInstructionSet(.zig), .instruction = .{ .inst = @intFromEnum(spec.Zig.InvocationGlobal) }, .id_ref_4 = &.{initializer_id}, }); @@ -761,15 +1148,21 @@ fn resolveUav(cg: *CodeGen, val: InternPool.Index) !Id { return result_id; } -fn addFunctionDep(cg: *CodeGen, decl_index: Module.Decl.Index, storage_class: StorageClass) !void { - const gpa = cg.module.gpa; - const target = cg.module.zcu.getTarget(); +fn resolvePtr(cg: *CodeGen, ref: Air.Inst.Ref) !Ptr { + const id = try cg.resolve(ref); + if (cg.tracked_allocas.getPtr(id)) |slot| return .{ .tracked = .{ .id = id, .slot = slot } }; + return .{ .id = id }; +} + +fn addFunctionDep(cg: *CodeGen, decl_index: Decl.Index, storage_class: StorageClass) !void { + const gpa = cg.gpa; + const target = cg.zcu.getTarget(); if (target.cpu.has(.spirv, .v1_4)) { - try cg.module.decl_deps.append(gpa, decl_index); + try cg.decl_deps.append(gpa, decl_index); } else { // Before version 1.4, the interface’s storage classes are limited to the Input and Output if (storage_class == .input or storage_class == .output) { - try cg.module.decl_deps.append(gpa, decl_index); + try cg.decl_deps.append(gpa, decl_index); } } } @@ -779,25 +1172,11 @@ fn addFunctionDep(cg: *CodeGen, decl_index: Module.Decl.Index, storage_class: St /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to /// keep track of the previous block. fn beginSpvBlock(cg: *CodeGen, label: Id) !void { - try cg.body.emit(cg.module.gpa, .OpLabel, .{ .id_result = label }); + try cg.body.emit(cg.gpa, .OpLabel, .{ .id_result = label }); cg.block_label = label; cg.block_terminated = false; } -/// Return the amount of bits in the largest supported integer type. This is either 32 (always supported), or 64 (if -/// the Int64 capability is enabled). -/// Note: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits). -/// In theory that could also be used, but since the spec says that it only guarantees support up to 32-bit ints there -/// is no way of knowing whether those are actually supported. -/// TODO: Maybe this should be cached? -fn largestSupportedIntBits(cg: *CodeGen) u16 { - const target = cg.module.zcu.getTarget(); - if (target.cpu.has(.spirv, .int64) or target.cpu.arch == .spirv64) { - return 64; - } - return 32; -} - const ArithmeticTypeInfo = struct { const Class = enum { bool, @@ -835,8 +1214,8 @@ const ArithmeticTypeInfo = struct { }; fn arithmeticTypeInfo(cg: *CodeGen, ty: Type) ArithmeticTypeInfo { - const zcu = cg.module.zcu; - const target = cg.module.zcu.getTarget(); + const zcu = cg.zcu; + const target = cg.zcu.getTarget(); var scalar_ty = ty.scalarType(zcu); if (scalar_ty.zigTypeTag(zcu) == .@"enum") { scalar_ty = scalar_ty.intTagType(zcu); @@ -845,7 +1224,7 @@ fn arithmeticTypeInfo(cg: *CodeGen, ty: Type) ArithmeticTypeInfo { return switch (scalar_ty.zigTypeTag(zcu)) { .bool => .{ .bits = 1, // Doesn't matter for this class. - .backing_bits = cg.module.backingIntBits(1).@"0", + .backing_bits = cg.backingIntBits(1).@"0", .vector_len = vector_len, .signedness = .unsigned, // Technically, but doesn't matter for this class. .class = .bool, @@ -860,7 +1239,7 @@ fn arithmeticTypeInfo(cg: *CodeGen, ty: Type) ArithmeticTypeInfo { .int => blk: { const int_info = scalar_ty.intInfo(zcu); // TODO: Maybe it's useful to also return this value. - const backing_bits, const big_int = cg.module.backingIntBits(int_info.bits); + const backing_bits, const big_int = cg.backingIntBits(int_info.bits); break :blk .{ .bits = int_info.bits, .backing_bits = backing_bits, @@ -880,8 +1259,8 @@ fn arithmeticTypeInfo(cg: *CodeGen, ty: Type) ArithmeticTypeInfo { /// Checks whether the type can be directly translated to SPIR-V vectors fn isSpvVector(cg: *CodeGen, ty: Type) bool { - const zcu = cg.module.zcu; - const target = cg.module.zcu.getTarget(); + const zcu = cg.zcu; + const target = cg.zcu.getTarget(); if (ty.zigTypeTag(zcu) != .vector) return false; // TODO: This check must be expanded for types that can be represented @@ -909,23 +1288,34 @@ fn isSpvVector(cg: *CodeGen, ty: Type) bool { /// Emits a bool constant in a particular representation. fn constBool(cg: *CodeGen, value: bool, repr: Repr) !Id { - return switch (repr) { - .indirect => cg.constInt(.u1, @intFromBool(value)), - .direct => cg.module.constBool(value), - }; + switch (repr) { + .indirect => return cg.constInt(.u1, @intFromBool(value)), + .direct => { + const result_ty_id = try cg.boolType(); + const result_id = cg.allocId(); + switch (value) { + inline else => |value_ct| try cg.sections.globals.emit( + cg.gpa, + if (value_ct) .OpConstantTrue else .OpConstantFalse, + .{ .id_result_type = result_ty_id, .id_result = result_id }, + ), + } + return result_id; + }, + } } /// Emits an integer constant. -/// This function, unlike Module.constInt, takes care to bitcast +/// This function, unlike cg.constInt, takes care to bitcast /// the value to an unsigned int first for Kernels. fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; - const target = cg.module.zcu.getTarget(); + const gpa = cg.gpa; + const zcu = cg.zcu; + const target = cg.zcu.getTarget(); const scalar_ty = ty.scalarType(zcu); const int_info = scalar_ty.intInfo(zcu); // Use backing bits so that negatives are sign extended - const backing_bits, const big_int = cg.module.backingIntBits(int_info.bits); + const backing_bits, const big_int = cg.backingIntBits(int_info.bits); assert(backing_bits != 0); // u0 is comptime const result_ty_id = try cg.resolveType(scalar_ty, .indirect); @@ -939,7 +1329,7 @@ fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id { .signed => @bitCast(@as(i64, @intCast(value))), .unsigned => @as(u64, @intCast(value)), }; - const n_limbs = backing_bits / Module.big_int_bits; + const n_limbs = backing_bits / big_int_bits; const fill: u32 = if (signedness == .signed and value < 0) 0xFFFFFFFF else 0; const scratch_top = cg.id_scratch.items.len; defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); @@ -979,61 +1369,45 @@ fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id { }, }; - const result_id = try cg.module.constant(result_ty_id, final_value); + const result_id = cg.allocId(); + try cg.sections.globals.emit(cg.gpa, .OpConstant, .{ + .id_result_type = result_ty_id, + .id_result = result_id, + .value = final_value, + }); if (!ty.isVector(zcu)) return result_id; return cg.constructCompositeSplat(ty, result_id); } -fn constIntBig(cg: *CodeGen, ty: Type, val: Value) !Id { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; - const int_info = ty.intInfo(zcu); - const backing_bits, _ = cg.module.backingIntBits(int_info.bits); - const n_limbs = backing_bits / Module.big_int_bits; - const result_ty_id = try cg.resolveType(ty, .indirect); - - var bigint_space: Value.BigIntSpace = undefined; - const bigint = val.toBigInt(&bigint_space, zcu); - - const limb_values = try gpa.alloc(u32, n_limbs); - defer gpa.free(limb_values); - - const bytes = std.mem.sliceAsBytes(limb_values); - bigint.writeTwosComplement(bytes, .little); - if (builtin.cpu.arch.endian() == .big) { - for (limb_values) |*limb| limb.* = @byteSwap(limb.*); - } - - const scratch_top = cg.id_scratch.items.len; - defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); - const constituents = try cg.id_scratch.addManyAsSlice(gpa, n_limbs); - for (constituents, 0..) |*c, i| { - c.* = try cg.constInt(.u32, limb_values[i]); - } - return cg.constructComposite(result_ty_id, constituents); -} - /// Construct a composite value from its constituents. /// In logical addressing mode (Vulkan/OpenGL), OpCompositeConstruct cannot accept /// pointer operands, so for struct types we use alloc, store for each field and load instead. pub fn constructComposite(cg: *CodeGen, result_ty_id: Id, constituents: []const Id) !Id { - const gpa = cg.module.gpa; + const gpa = cg.gpa; - if (cg.module.structFields(result_ty_id)) |fields| { + const maybe_fields: ?[]const Id = for (cg.struct_types.keys(), cg.struct_types.values()) |key, val| { + if (val == result_ty_id) break key.fields; + } else null; + if (maybe_fields) |fields| { assert(fields.len == constituents.len); - const u32_ty_id = try cg.module.intType(.unsigned, 32); + const u32_ty_id = try cg.intType(.unsigned, 32); const var_id = try cg.alloc(result_ty_id, null); for (fields, constituents, 0..) |field_ty_id, constituent, i| { - const field_ptr_ty_id = try cg.module.ptrType(field_ty_id, .function); - const index_id = try cg.module.constant(u32_ty_id, .{ .uint32 = @intCast(i) }); + const field_ptr_ty_id = try cg.ptrType(field_ty_id, .function); + const index_id = cg.allocId(); + try cg.sections.globals.emit(gpa, .OpConstant, .{ + .id_result_type = u32_ty_id, + .id_result = index_id, + .value = .{ .uint32 = @intCast(i) }, + }); const field_ptr = try cg.accessChainId(field_ptr_ty_id, var_id, &.{index_id}); try cg.body.emit(gpa, .OpStore, .{ .pointer = field_ptr, .object = constituent, }); } - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); try cg.body.emit(gpa, .OpLoad, .{ .id_result_type = result_ty_id, .id_result = result_id, @@ -1042,7 +1416,7 @@ pub fn constructComposite(cg: *CodeGen, result_ty_id: Id, constituents: []const return result_id; } - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); try cg.body.emit(gpa, .OpCompositeConstruct, .{ .id_result_type = result_ty_id, .id_result = result_id, @@ -1054,8 +1428,8 @@ pub fn constructComposite(cg: *CodeGen, result_ty_id: Id, constituents: []const /// Construct a composite at runtime with all lanes set to the same value. /// ty must be an aggregate type. fn constructCompositeSplat(cg: *CodeGen, ty: Type, constituent: Id) !Id { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; + const gpa = cg.gpa; + const zcu = cg.zcu; const n: usize = @intCast(ty.arrayLen(zcu)); const scratch_top = cg.id_scratch.items.len; @@ -1075,24 +1449,17 @@ fn constructCompositeSplat(cg: *CodeGen, ty: Type, constituent: Id) !Id { // /// This function should only be called during function code generation. fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { - const gpa = cg.module.gpa; - - // Note: Using intern_map can only be used with constants that DO NOT generate any runtime code!! - // Ideally that should be all constants in the future, or it should be cleaned up somehow. For - // now, only use the intern_map on case-by-case basis by breaking to :cache. - if (cg.module.intern_map.get(.{ val.toIntern(), repr })) |id| { - return id; - } + const gpa = cg.gpa; const pt = cg.pt; - const zcu = cg.module.zcu; - const target = cg.module.zcu.getTarget(); + const zcu = cg.zcu; + const target = cg.zcu.getTarget(); const result_ty_id = try cg.resolveType(ty, repr); const ip = &zcu.intern_pool; log.debug("lowering constant: ty = {f}, val = {f}, key = {s}", .{ ty.fmt(pt), val.fmtValue(pt), @tagName(ip.indexToKey(val.toIntern())) }); if (val.isUndef(zcu)) { - return cg.module.constUndef(result_ty_id); + return cg.constUndef(result_ty_id); } const cacheable_id = cache: { @@ -1133,9 +1500,25 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { }, .int => { const int_info = ty.intInfo(zcu); - _, const is_big_int = cg.module.backingIntBits(int_info.bits); + const backing_bits, const is_big_int = cg.backingIntBits(int_info.bits); if (is_big_int) { - break :cache try cg.constIntBig(ty, val); + const n_limbs = backing_bits / big_int_bits; + const big_result_ty_id = try cg.resolveType(ty, .indirect); + var bigint_space: Value.BigIntSpace = undefined; + const bigint = val.toBigInt(&bigint_space, zcu); + const limb_values = try gpa.alloc(u32, n_limbs); + defer gpa.free(limb_values); + bigint.writeTwosComplement(std.mem.sliceAsBytes(limb_values), .little); + if (builtin.cpu.arch.endian() == .big) { + for (limb_values) |*limb| limb.* = @byteSwap(limb.*); + } + const scratch_top = cg.id_scratch.items.len; + defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); + const constituents = try cg.id_scratch.addManyAsSlice(gpa, n_limbs); + for (constituents, 0..) |*c, i| { + c.* = try cg.constInt(.u32, limb_values[i]); + } + break :cache try cg.constructComposite(big_result_ty_id, constituents); } if (ty.isSignedInt(zcu)) { break :cache try cg.constInt(ty, val.toSignedInt(zcu)); @@ -1151,7 +1534,13 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { 80, 128 => unreachable, // TODO else => unreachable, }; - break :cache try cg.module.constant(result_ty_id, lit); + const lit_id = cg.allocId(); + try cg.sections.globals.emit(gpa, .OpConstant, .{ + .id_result_type = result_ty_id, + .id_result = lit_id, + .value = lit, + }); + break :cache lit_id; }, .err => |err| { const value = try pt.getErrorValue(err.name); @@ -1218,7 +1607,7 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { if (maybe_payload_val) |payload_val| { return try cg.constant(payload_ty, payload_val, .indirect); } else { - break :cache try cg.module.constNull(result_ty_id); + break :cache try cg.constNull(result_ty_id); } } @@ -1229,7 +1618,7 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { const payload_id = if (maybe_payload_val) |payload_val| try cg.constant(payload_ty, payload_val, .indirect) else - try cg.module.constUndef(try cg.resolveType(payload_ty, .indirect)); + try cg.constUndef(try cg.resolveType(payload_ty, .indirect)); const comp_ty_id = try cg.resolveType(ty, .direct); return try cg.constructComposite(comp_ty_id, &.{ payload_id, has_pl_id }); @@ -1339,21 +1728,18 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { .memoized_call => unreachable, } }; - - try cg.module.intern_map.putNoClobber(gpa, .{ val.toIntern(), repr }, cacheable_id); - return cacheable_id; } fn constantPtr(cg: *CodeGen, ptr_val: Value) !Id { const pt = cg.pt; - const zcu = cg.module.zcu; - const gpa = cg.module.gpa; + const zcu = cg.zcu; + const gpa = cg.gpa; if (ptr_val.isUndef(zcu)) { const result_ty = ptr_val.typeOf(zcu); const result_ty_id = try cg.resolveType(result_ty, .direct); - return cg.module.constUndef(result_ty_id); + return cg.constUndef(result_ty_id); } var arena = std.heap.ArenaAllocator.init(gpa); @@ -1364,9 +1750,9 @@ fn constantPtr(cg: *CodeGen, ptr_val: Value) !Id { } fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { - const gpa = cg.module.gpa; + const gpa = cg.gpa; const pt = cg.pt; - const zcu = cg.module.zcu; + const zcu = cg.zcu; const target = zcu.getTarget(); switch (derivation) { .comptime_alloc_ptr, .comptime_field_ptr => unreachable, @@ -1383,7 +1769,7 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { // TODO: This can probably be an OpSpecConstantOp Bitcast, but // that is not implemented by Mesa yet. Therefore, just generate it // as a runtime operation. - const result_ptr_id = cg.module.allocId(); + const result_ptr_id = cg.allocId(); const value_id = try cg.constInt(.usize, int.addr); try cg.body.emit(gpa, .OpConvertUToPtr, .{ .id_result_type = result_ty_id, @@ -1392,13 +1778,83 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { }); return result_ptr_id; }, - .nav_ptr => |nav| { - const result_ptr_ty = try pt.navPtrType(nav); - return cg.constantNavRef(result_ptr_ty, nav); + .nav_ptr => |nav_index| { + const ip = &zcu.intern_pool; + const result_ptr_ty = try pt.navPtrType(nav_index); + const ty_id = try cg.resolveType(result_ptr_ty, .direct); + const nav = ip.getNav(nav_index); + const nav_ty: Type = .fromInterned(nav.resolved.?.type); + + switch (nav.resolved.?.value) { + .none => {}, + else => |value| switch (ip.indexToKey(value)) { + // TODO: Properly lower function pointers; for now substitute undef. + .func => return try cg.constUndef(ty_id), + .@"extern" => if (ip.isFunctionType(nav_ty.toIntern())) { + const spv_decl_index = try cg.resolveNav(ip, nav_index); + const decl = cg.declPtr(spv_decl_index); + try emitExternFnStub(cg, nav, decl, nav_ty); + return decl.result_id; + }, + else => {}, + }, + } + + if (!nav_ty.hasRuntimeBits(zcu)) return cg.constUndef(ty_id); + + const spv_decl_index = try cg.resolveNav(ip, nav_index); + const spv_decl = cg.declPtr(spv_decl_index); + assert(spv_decl.kind != .func); + const storage_class = cg.storageClass(nav.resolved.?.@"addrspace"); + try cg.addFunctionDep(spv_decl_index, storage_class); + + const nav_ty_id = try cg.resolveType(nav_ty, .indirect); + const decl_ptr_ty_id = try cg.ptrType(nav_ty_id, storage_class); + if (decl_ptr_ty_id == ty_id) return spv_decl.result_id; + switch (target.os.tag) { + .vulkan, .opengl => return spv_decl.result_id, + else => {}, + } + const casted_ptr_id = cg.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = ty_id, + .id_result = casted_ptr_id, + .operand = spv_decl.result_id, + }); + return casted_ptr_id; }, .uav_ptr => |uav| { + const ip = &zcu.intern_pool; const result_ptr_ty: Type = .fromInterned(uav.orig_ty); - return cg.constantUavRef(result_ptr_ty, uav); + const ty_id = try cg.resolveType(result_ptr_ty, .direct); + const uav_ty: Type = .fromInterned(ip.typeOf(uav.val)); + + switch (ip.indexToKey(uav.val)) { + .func => unreachable, // TODO + .@"extern" => assert(!ip.isFunctionType(uav_ty.toIntern())), + else => {}, + } + + if (!uav_ty.hasRuntimeBits(zcu)) return cg.constUndef(ty_id); + + // Uav refs are always generic. + assert(result_ptr_ty.ptrAddressSpace(zcu) == .generic); + const uav_ty_id = try cg.resolveType(uav_ty, .indirect); + const decl_ptr_ty_id = try cg.ptrType(uav_ty_id, .function); + const ptr_id = try cg.resolveUav(uav.val); + + if (decl_ptr_ty_id == ty_id) return ptr_id; + switch (target.os.tag) { + .vulkan, .opengl => return ptr_id, + else => {}, + } + const casted_ptr_id = cg.allocId(); + try cg.body.emit(gpa, .OpBitcast, .{ + .id_result_type = ty_id, + .id_result = casted_ptr_id, + .operand = ptr_id, + }); + return casted_ptr_id; }, .eu_payload_ptr => @panic("TODO"), .opt_payload_ptr => @panic("TODO"), @@ -1450,7 +1906,7 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { return cg.accessChainId(result_ty_id, parent_ptr_id, ids); } if (target.os.tag == .opencl) { - const result_ptr_id = cg.module.allocId(); + const result_ptr_id = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = result_ty_id, .id_result = result_ptr_id, @@ -1468,59 +1924,15 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { } } -fn constantUavRef( - cg: *CodeGen, - ty: Type, - uav: InternPool.Key.Ptr.BaseAddr.Uav, -) !Id { - // TODO: Merge this function with constantDeclRef. - - const zcu = cg.module.zcu; - const ip = &zcu.intern_pool; - const ty_id = try cg.resolveType(ty, .direct); - const uav_ty: Type = .fromInterned(ip.typeOf(uav.val)); - - switch (ip.indexToKey(uav.val)) { - .func => unreachable, // TODO - .@"extern" => assert(!ip.isFunctionType(uav_ty.toIntern())), - else => {}, - } - - // const is_fn_body = decl_ty.zigTypeTag(zcu) == .@"fn"; - if (!uav_ty.hasRuntimeBits(zcu)) { - // Pointer to nothing - return undefined - return cg.module.constUndef(ty_id); - } - - // Uav refs are always generic. - assert(ty.ptrAddressSpace(zcu) == .generic); - const uav_ty_id = try cg.resolveType(uav_ty, .indirect); - const decl_ptr_ty_id = try cg.module.ptrType(uav_ty_id, .function); - const ptr_id = try cg.resolveUav(uav.val); - - if (decl_ptr_ty_id != ty_id) { - // Differing pointer types, insert a cast. - const casted_ptr_id = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpBitcast, .{ - .id_result_type = ty_id, - .id_result = casted_ptr_id, - .operand = ptr_id, - }); - return casted_ptr_id; - } else { - return ptr_id; - } -} - /// Emit a stub OpFunction/OpFunctionEnd + Import linkage decoration for an /// extern function so the module is structurally valid. The stub will be /// replaced by the real definition at link time. -fn emitExternFnStub(cg: *CodeGen, nav: InternPool.Nav, decl: *Module.Decl, fn_ty: Type) !void { +fn emitExternFnStub(cg: *CodeGen, nav: InternPool.Nav, decl: *Decl, fn_ty: Type) !void { if (decl.has_extern_stub) return; decl.has_extern_stub = true; - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; + const gpa = cg.gpa; + const zcu = cg.zcu; const ip = &zcu.intern_pool; const fn_info = zcu.typeToFunc(fn_ty).?; const return_ty_id = try cg.resolveFnReturnType(.fromInterned(fn_info.return_type)); @@ -1540,81 +1952,25 @@ fn emitExternFnStub(cg: *CodeGen, nav: InternPool.Nav, decl: *Module.Decl, fn_ty const param_type_id = try cg.resolveType(param_ty, .direct); try stub.emit(gpa, .OpFunctionParameter, .{ .id_result_type = param_type_id, - .id_result = cg.module.allocId(), + .id_result = cg.allocId(), }); } try stub.emit(gpa, .OpFunctionEnd, {}); - try cg.module.sections.functions.append(gpa, stub); + try cg.sections.functions.append(gpa, stub); const extern_name = nav.getExtern(ip).?.name.toSlice(ip); - try cg.module.sections.annotations.emit(gpa, .OpDecorate, .{ + try cg.sections.annotations.emit(gpa, .OpDecorate, .{ .target = decl.result_id, .decoration = .{ .linkage_attributes = .{ .name = extern_name, .linkage_type = .import, } }, }); - try cg.module.debugName(decl.result_id, extern_name); + try cg.debugName(decl.result_id, extern_name); } -fn constantNavRef(cg: *CodeGen, ty: Type, nav_index: InternPool.Nav.Index) !Id { - const zcu = cg.module.zcu; - const ip = &zcu.intern_pool; - const ty_id = try cg.resolveType(ty, .direct); - const nav = ip.getNav(nav_index); - const nav_ty: Type = .fromInterned(nav.resolved.?.type); - - switch (nav.resolved.?.value) { - .none => {}, // this is not a function or extern - else => |value| switch (ip.indexToKey(value)) { - .func => { - // 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 a pointer to usize. - return try cg.module.constUndef(ty_id); - }, - .@"extern" => if (ip.isFunctionType(nav_ty.toIntern())) { - const spv_decl_index = try cg.module.resolveNav(ip, nav_index); - const decl = cg.module.declPtr(spv_decl_index); - try emitExternFnStub(cg, nav, decl, nav_ty); - return decl.result_id; - }, - else => {}, - }, - } - - if (!nav_ty.hasRuntimeBits(zcu)) { - // Pointer to nothing - return undefined. - return cg.module.constUndef(ty_id); - } - - const spv_decl_index = try cg.module.resolveNav(ip, nav_index); - const spv_decl = cg.module.declPtr(spv_decl_index); - const spv_decl_result_id = spv_decl.result_id; - assert(spv_decl.kind != .func); - - const storage_class = cg.module.storageClass(nav.resolved.?.@"addrspace"); - try cg.addFunctionDep(spv_decl_index, storage_class); - - const nav_ty_id = try cg.resolveType(nav_ty, .indirect); - const decl_ptr_ty_id = try cg.module.ptrType(nav_ty_id, storage_class); - - if (decl_ptr_ty_id != ty_id) { - // Differing pointer types, insert a cast. - const casted_ptr_id = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpBitcast, .{ - .id_result_type = ty_id, - .id_result = casted_ptr_id, - .operand = spv_decl_result_id, - }); - return casted_ptr_id; - } - - return spv_decl_result_id; -} - -// Turn a Zig type's name into a cache reference. fn resolveTypeName(cg: *CodeGen, ty: Type) ![]const u8 { - const gpa = cg.module.gpa; + const gpa = cg.gpa; var aw: std.Io.Writer.Allocating = .init(gpa); defer aw.deinit(); ty.print(&aw.writer, cg.pt, null) catch |err| switch (err) { @@ -1641,67 +1997,8 @@ fn resolveTypeName(cg: *CodeGen, ty: Type) ![]const u8 { /// padding: [padding_size]u8, /// } /// If any of the fields' size is 0, it will be omitted. -fn resolveUnionType(cg: *CodeGen, ty: Type) !Id { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; - const union_obj = zcu.typeToUnion(ty).?; - - if (union_obj.layout == .@"packed") { - return try cg.module.intType(.unsigned, @intCast(ty.bitSize(zcu))); - } - - const layout = cg.unionLayout(ty); - if (!layout.has_payload) { - // No payload, so represent this as just the tag type. - return try cg.resolveType(.fromInterned(union_obj.enum_tag_type), .indirect); - } - - var member_types: [4]Id = undefined; - var member_names: [4][]const u8 = undefined; - - const u8_ty_id = try cg.resolveType(.u8, .direct); - - if (layout.tag_size != 0) { - const tag_ty_id = try cg.resolveType(.fromInterned(union_obj.enum_tag_type), .indirect); - member_types[layout.tag_index] = tag_ty_id; - member_names[layout.tag_index] = "(tag)"; - } - - if (layout.payload_size != 0) { - const payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect); - member_types[layout.payload_index] = payload_ty_id; - member_names[layout.payload_index] = "(payload)"; - } - - if (layout.payload_padding_size != 0) { - const len_id = try cg.constInt(.u32, layout.payload_padding_size); - const payload_padding_ty_id = try cg.module.arrayType(len_id, u8_ty_id); - member_types[layout.payload_padding_index] = payload_padding_ty_id; - member_names[layout.payload_padding_index] = "(payload padding)"; - } - - if (layout.padding_size != 0) { - const len_id = try cg.constInt(.u32, layout.padding_size); - const padding_ty_id = try cg.module.arrayType(len_id, u8_ty_id); - member_types[layout.padding_index] = padding_ty_id; - member_names[layout.padding_index] = "(padding)"; - } - - const result_id = try cg.module.structType( - member_types[0..layout.total_fields], - member_names[0..layout.total_fields], - .none, - ); - - const type_name = try cg.resolveTypeName(ty); - defer gpa.free(type_name); - try cg.module.debugName(result_id, type_name); - - return result_id; -} - fn resolveFnReturnType(cg: *CodeGen, ret_ty: Type) !Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; if (!ret_ty.hasRuntimeBits(zcu)) { // If the return type is an error set or an error union, then we make this // anyerror return type instead, so that it can be coerced into a function @@ -1717,38 +2014,38 @@ fn resolveFnReturnType(cg: *CodeGen, ret_ty: Type) !Id { } fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { - const gpa = cg.module.gpa; + const gpa = cg.gpa; const pt = cg.pt; - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ip = &zcu.intern_pool; - const target = cg.module.zcu.getTarget(); + const target = cg.zcu.getTarget(); log.debug("resolveType: ty = {f}", .{ty.fmt(pt)}); switch (ty.zigTypeTag(zcu)) { .noreturn => { assert(repr == .direct); - return try cg.module.voidType(); + return try cg.voidType(); }, .void => switch (repr) { - .direct => return try cg.module.voidType(), + .direct => return try cg.voidType(), .indirect => { if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); - return try cg.module.opaqueType("void"); + return try cg.opaqueType("void"); }, }, .bool => switch (repr) { - .direct => return try cg.module.boolType(), + .direct => return try cg.boolType(), .indirect => return try cg.resolveType(.u1, .indirect), }, .int => { if (ty.toIntern() == .u0_type) { assert(repr == .indirect); if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); - return try cg.module.opaqueType("u0"); + return try cg.opaqueType("u0"); } const int_info = ty.intInfo(zcu); - return try cg.module.intType(int_info.signedness, int_info.bits); + return try cg.intType(int_info.signedness, int_info.bits); }, .@"enum" => return try cg.resolveType(ty.intTagType(zcu), repr), .float => { @@ -1767,7 +2064,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { ); } - return try cg.module.floatType(bits); + return try cg.floatType(bits); }, .array => { const elem_ty = ty.childType(zcu); @@ -1779,7 +2076,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { if (!elem_ty.hasRuntimeBits(zcu)) { assert(repr == .indirect); if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); - return try cg.module.opaqueType("zero-sized-array"); + return try cg.opaqueType("zero-sized-array"); } else if (total_len == 0) { // The size of the array would be 0, but that is not allowed in SPIR-V. // This path can be reached for example when there is a slicing of a pointer @@ -1790,25 +2087,24 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { // generate an array of 1 element instead, so that ptr_elem_ptr instructions // can be lowered to ptrAccessChain instead of manually performing the math. const len_id = try cg.constInt(.u32, 1); - return try cg.module.arrayType(len_id, elem_ty_id); + return try cg.arrayType(len_id, elem_ty_id); } else { const total_len_id = try cg.constInt(.u32, total_len); - return try cg.module.arrayType(total_len_id, elem_ty_id); + return try cg.arrayType(total_len_id, elem_ty_id); } }, .vector => { const elem_ty = ty.childType(zcu); const elem_ty_id = try cg.resolveType(elem_ty, repr); const len = ty.vectorLen(zcu); - if (cg.isSpvVector(ty)) return try cg.module.vectorType(len, elem_ty_id); + if (cg.isSpvVector(ty)) return try cg.vectorType(len, elem_ty_id); const len_id = try cg.constInt(.u32, len); - return try cg.module.arrayType(len_id, elem_ty_id); + return try cg.arrayType(len_id, elem_ty_id); }, .@"fn" => switch (repr) { .direct => { const fn_info = zcu.typeToFunc(ty).?; - comptime assert(zig_call_abi_ver == 3); assert(!fn_info.is_var_args); switch (fn_info.cc) { .auto, @@ -1837,7 +2133,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { param_index += 1; } - return try cg.module.functionType(return_ty_id, param_ty_ids[0..param_index]); + return try cg.functionType(return_ty_id, param_ty_ids[0..param_index]); }, .indirect => { // TODO: Represent function pointers properly. @@ -1860,15 +2156,15 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { }, }; const child_ty_id = try cg.resolveType(child_ty, .indirect); - const storage_class = cg.module.storageClass(ptr_info.flags.address_space); - const ptr_ty_id = try cg.module.ptrType(child_ty_id, storage_class); + const storage_class = cg.storageClass(ptr_info.flags.address_space); + const ptr_ty_id = try cg.ptrType(child_ty_id, storage_class); if (ptr_info.flags.size != .slice) { return ptr_ty_id; } const size_ty_id = try cg.resolveType(.usize, .direct); - return try cg.module.structType( + return try cg.structType( &.{ ptr_ty_id, size_ty_id }, &.{ "ptr", "len" }, .none, @@ -1889,14 +2185,14 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { member_index += 1; } - const result_id = try cg.module.structType( + const result_id = try cg.structType( member_types[0..member_index], null, .none, ); const type_name = try cg.resolveTypeName(ty); defer gpa.free(type_name); - try cg.module.debugName(result_id, type_name); + try cg.debugName(result_id, type_name); return result_id; }, .struct_type => ip.loadStructType(ty.toIntern()), @@ -1923,7 +2219,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { try member_names.append(field_name.toSlice(ip)); } - const result_id = try cg.module.structType( + const result_id = try cg.structType( member_types.items, member_names.items, ty.toIntern(), @@ -1931,7 +2227,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { const type_name = try cg.resolveTypeName(ty); defer gpa.free(type_name); - try cg.module.debugName(result_id, type_name); + try cg.debugName(result_id, type_name); return result_id; }, @@ -1952,13 +2248,52 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { const bool_ty_id = try cg.resolveType(.bool, .indirect); - return try cg.module.structType( + return try cg.structType( &.{ payload_ty_id, bool_ty_id }, &.{ "payload", "valid" }, .none, ); }, - .@"union" => return try cg.resolveUnionType(ty), + .@"union" => { + const union_obj = zcu.typeToUnion(ty).?; + if (union_obj.layout == .@"packed") { + return try cg.intType(.unsigned, @intCast(ty.bitSize(zcu))); + } + const layout = cg.unionLayout(ty); + if (!layout.has_payload) { + return try cg.resolveType(.fromInterned(union_obj.enum_tag_type), .indirect); + } + var member_types: [4]Id = undefined; + var member_names: [4][]const u8 = undefined; + const u8_ty_id = try cg.resolveType(.u8, .direct); + if (layout.tag_size != 0) { + member_types[layout.tag_index] = try cg.resolveType(.fromInterned(union_obj.enum_tag_type), .indirect); + member_names[layout.tag_index] = "(tag)"; + } + if (layout.payload_size != 0) { + member_types[layout.payload_index] = try cg.resolveType(layout.payload_ty, .indirect); + member_names[layout.payload_index] = "(payload)"; + } + if (layout.payload_padding_size != 0) { + const len_id = try cg.constInt(.u32, layout.payload_padding_size); + member_types[layout.payload_padding_index] = try cg.arrayType(len_id, u8_ty_id); + member_names[layout.payload_padding_index] = "(payload padding)"; + } + if (layout.padding_size != 0) { + const len_id = try cg.constInt(.u32, layout.padding_size); + member_types[layout.padding_index] = try cg.arrayType(len_id, u8_ty_id); + member_names[layout.padding_index] = "(padding)"; + } + const result_id = try cg.structType( + member_types[0..layout.total_fields], + member_names[0..layout.total_fields], + .none, + ); + const type_name = try cg.resolveTypeName(ty); + defer gpa.free(type_name); + try cg.debugName(result_id, type_name); + return result_id; + }, .error_set => { const err_int_ty = try pt.errorIntType(); return try cg.resolveType(err_int_ty, repr); @@ -1989,46 +2324,46 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { // TODO: ABI padding? } - return try cg.module.structType(&member_types, &member_names, .none); + return try cg.structType(&member_types, &member_names, .none); }, .@"opaque" => { if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); const type_name = try cg.resolveTypeName(ty); defer gpa.free(type_name); - return try cg.module.opaqueType(type_name); + return try cg.opaqueType(type_name); }, .spirv => { - const ip_index = ty.toIntern(); - const spirv_type = ip.loadSpirvType(ip_index); + const spirv_type = ip.loadSpirvType(ty.toIntern()); + const result_id = cg.allocId(); switch (spirv_type.flags.tag) { - .sampler => return try cg.module.samplerType(ip_index), + .sampler => try cg.sections.globals.emit(gpa, .OpTypeSampler, .{ .id_result = result_id }), .image => { - const sampled_type_id = blk: { - if (spirv_type.ty == .none) break :blk try cg.module.intType(.unsigned, 32); - break :blk try cg.resolveType(Type.fromInterned(spirv_type.ty), .direct); - }; - return try cg.module.imageType( - ip_index, - sampled_type_id, - switch (spirv_type.flags.dim) { + const sampled_type_id = if (spirv_type.ty == .none) + try cg.intType(.unsigned, 32) + else + try cg.resolveType(Type.fromInterned(spirv_type.ty), .direct); + try cg.sections.globals.emit(gpa, .OpTypeImage, .{ + .id_result = result_id, + .sampled_type = sampled_type_id, + .dim = switch (spirv_type.flags.dim) { .@"1d" => .@"1d", .@"2d" => .@"2d", .@"3d" => .@"3d", .cube => .cube, }, - switch (spirv_type.flags.depth) { + .depth = switch (spirv_type.flags.depth) { .not_depth => 0, .depth => 1, .unknown => 2, }, - @intFromBool(spirv_type.flags.is_arrayed), - @intFromBool(spirv_type.flags.is_multisampled), - switch (spirv_type.flags.usage) { + .arrayed = @intFromBool(spirv_type.flags.is_arrayed), + .ms = @intFromBool(spirv_type.flags.is_multisampled), + .sampled = switch (spirv_type.flags.usage) { .unknown => 1, .sampled => 1, .storage => 2, }, - switch (spirv_type.flags.format) { + .image_format = switch (spirv_type.flags.format) { .unknown => .unknown, .rgba32f => .rgba32f, .rgba32i => .rgba32i, @@ -2044,31 +2379,36 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { .r32i => .r32i, .r32u => .r32ui, }, - switch (spirv_type.flags.access) { + .access_qualifier = switch (spirv_type.flags.access) { .unknown => null, .read_only => .read_only, .write_only => .write_only, .read_write => .read_write, }, - ); + }); }, .sampled_image => { const image_ty_id = try cg.resolveType(.fromInterned(spirv_type.ty), .indirect); - return try cg.module.sampledImageType(ip_index, image_ty_id); + try cg.sections.globals.emit(gpa, .OpTypeSampledImage, .{ + .id_result = result_id, + .image_type = image_ty_id, + }); }, .runtime_array => { const elem_ty: Type = .fromInterned(spirv_type.ty); const elem_ty_id = try cg.resolveType(elem_ty, .indirect); - const result_id = try cg.module.runtimeArrayType(ip_index, elem_ty_id); - + try cg.sections.globals.emit(gpa, .OpTypeRuntimeArray, .{ + .id_result = result_id, + .element_type = elem_ty_id, + }); if (elem_ty.hasRuntimeBits(zcu)) { - try cg.module.decorate(result_id, .{ .array_stride = .{ + try cg.decorate(result_id, .{ .array_stride = .{ .array_stride = @intCast(elem_ty.abiSize(zcu)), } }); } - return result_id; }, } + return result_id; }, .null, @@ -2099,7 +2439,7 @@ const ErrorUnionLayout = struct { }; fn errorUnionLayout(cg: *CodeGen, payload_ty: Type) ErrorUnionLayout { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const error_align = Type.abiAlignment(.anyerror, zcu); const payload_align = payload_ty.abiAlignment(zcu); @@ -2130,7 +2470,7 @@ const UnionLayout = struct { }; fn unionLayout(cg: *CodeGen, ty: Type) UnionLayout { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ip = &zcu.intern_pool; const layout = ty.unionGetLayout(zcu); const union_obj = zcu.typeToUnion(ty).?; @@ -2220,8 +2560,8 @@ const Temporary = struct { } fn materialize(temp: Temporary, cg: *CodeGen) !Id { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; + const gpa = cg.gpa; + const zcu = cg.zcu; switch (temp.value) { .singleton => |id| return id, .exploded_vector => |range| { @@ -2255,7 +2595,7 @@ const Temporary = struct { /// 'Explode' a temporary into separate elements. This turns a vector /// into a bag of elements. fn explode(temp: Temporary, cg: *CodeGen) !IdRange { - const zcu = cg.module.zcu; + const zcu = cg.zcu; // If the value is a scalar, then this is a no-op. if (!temp.ty.isVector(zcu)) { @@ -2267,7 +2607,7 @@ const Temporary = struct { const ty_id = try cg.resolveType(temp.ty.scalarType(zcu), .direct); const n = temp.ty.vectorLen(zcu); - const results = cg.module.allocIds(n); + const results = cg.allocIds(n); const id = switch (temp.value) { .singleton => |id| id, @@ -2276,7 +2616,7 @@ const Temporary = struct { for (0..n) |i| { const indexes = [_]u32{@intCast(i)}; - try cg.body.emit(cg.module.gpa, .OpCompositeExtract, .{ + try cg.body.emit(cg.gpa, .OpCompositeExtract, .{ .id_result_type = ty_id, .id_result = results.at(i), .composite = id, @@ -2296,12 +2636,12 @@ const CompositeInt = struct { info: ArithmeticTypeInfo, fn init(cg: *CodeGen, composite_id: Id, info: ArithmeticTypeInfo) !CompositeInt { - const n_limbs: u16 = info.backing_bits / Module.big_int_bits; - const gpa = cg.module.gpa; + const n_limbs: u16 = info.backing_bits / big_int_bits; + const gpa = cg.gpa; const u32_ty_id = try cg.resolveType(.u32, .direct); const limbs = try cg.id_scratch.addManyAsSlice(gpa, n_limbs); for (limbs, 0..) |*limb, i| { - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = result_id, @@ -2323,8 +2663,8 @@ const CompositeInt = struct { } fn zero(cg: *CodeGen, info: ArithmeticTypeInfo) !CompositeInt { - const n_limbs: u16 = info.backing_bits / Module.big_int_bits; - const limbs = try cg.id_scratch.addManyAsSlice(cg.module.gpa, n_limbs); + const n_limbs: u16 = info.backing_bits / big_int_bits; + const limbs = try cg.id_scratch.addManyAsSlice(cg.gpa, n_limbs); const zero_id = try cg.constInt(.u32, @as(u32, 0)); for (limbs) |*limb| limb.* = zero_id; return .{ .cg = cg, .limbs = limbs, .n_limbs = n_limbs, .info = info }; @@ -2337,9 +2677,9 @@ const CompositeInt = struct { fn limbBinOp(ci: CompositeInt, opcode: Opcode, lhs: Id, rhs: Id) !Id { const cg = ci.cg; - const gpa = cg.module.gpa; + const gpa = cg.gpa; const u32_ty_id = try cg.resolveType(.u32, .direct); - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); try cg.body.emitRaw(gpa, opcode, 4); cg.body.writeOperand(Id, u32_ty_id); cg.body.writeOperand(Id, result_id); @@ -2350,9 +2690,9 @@ const CompositeInt = struct { fn limbUnOp(ci: CompositeInt, opcode: Opcode, operand: Id) !Id { const cg = ci.cg; - const gpa = cg.module.gpa; + const gpa = cg.gpa; const u32_ty_id = try cg.resolveType(.u32, .direct); - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); try cg.body.emitRaw(gpa, opcode, 3); cg.body.writeOperand(Id, u32_ty_id); cg.body.writeOperand(Id, result_id); @@ -2362,7 +2702,7 @@ const CompositeInt = struct { fn bitwiseOp(ci: CompositeInt, other: CompositeInt, opcode: Opcode) !CompositeInt { const cg = ci.cg; - const gpa = cg.module.gpa; + const gpa = cg.gpa; const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, ci.n_limbs); for (result_limbs, 0..) |*r, i| { r.* = try ci.limbBinOp(opcode, ci.limbs[i], other.limbs[i]); @@ -2372,7 +2712,7 @@ const CompositeInt = struct { fn bitwiseNot(ci: CompositeInt) !CompositeInt { const cg = ci.cg; - const gpa = cg.module.gpa; + const gpa = cg.gpa; const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, ci.n_limbs); for (result_limbs, 0..) |*r, i| { r.* = try ci.limbUnOp(.OpNot, ci.limbs[i]); @@ -2382,41 +2722,45 @@ const CompositeInt = struct { fn cmp(ci: CompositeInt, other: CompositeInt, op: std.math.CompareOperator) !Id { const cg = ci.cg; - const gpa = cg.module.gpa; + const gpa = cg.gpa; const bool_ty_id = try cg.resolveType(.bool, .direct); switch (op) { .eq, .neq => { var result = blk: { - const r = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpIEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, r); - cg.body.writeOperand(Id, ci.limbs[0]); - cg.body.writeOperand(Id, other.limbs[0]); + const r = cg.allocId(); + try cg.body.emit(gpa, .OpIEqual, .{ + .id_result_type = bool_ty_id, + .id_result = r, + .operand_1 = ci.limbs[0], + .operand_2 = other.limbs[0], + }); break :blk r; }; for (1..ci.n_limbs) |i| { - const limb_eq = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpIEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, limb_eq); - cg.body.writeOperand(Id, ci.limbs[i]); - cg.body.writeOperand(Id, other.limbs[i]); - const combined = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpLogicalAnd, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, combined); - cg.body.writeOperand(Id, result); - cg.body.writeOperand(Id, limb_eq); + const limb_eq = cg.allocId(); + try cg.body.emit(gpa, .OpIEqual, .{ + .id_result_type = bool_ty_id, + .id_result = limb_eq, + .operand_1 = ci.limbs[i], + .operand_2 = other.limbs[i], + }); + const combined = cg.allocId(); + try cg.body.emit(gpa, .OpLogicalAnd, .{ + .id_result_type = bool_ty_id, + .id_result = combined, + .operand_1 = result, + .operand_2 = limb_eq, + }); result = combined; } if (op == .neq) { - const negated = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpLogicalNot, 3); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, negated); - cg.body.writeOperand(Id, result); + const negated = cg.allocId(); + try cg.body.emit(gpa, .OpLogicalNot, .{ + .id_result_type = bool_ty_id, + .id_result = negated, + .operand = result, + }); result = negated; } return result; @@ -2429,12 +2773,13 @@ const CompositeInt = struct { for (0..ci.n_limbs) |i| { const l = ci.limbs[i]; const r = other.limbs[i]; - const limb_ne = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpINotEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, limb_ne); - cg.body.writeOperand(Id, l); - cg.body.writeOperand(Id, r); + const limb_ne = cg.allocId(); + try cg.body.emit(gpa, .OpINotEqual, .{ + .id_result_type = bool_ty_id, + .id_result = limb_ne, + .operand_1 = l, + .operand_2 = r, + }); const is_top = (i == ci.n_limbs - 1); const use_signed = is_top and ci.info.signedness == .signed; @@ -2442,13 +2787,13 @@ const CompositeInt = struct { var cmp_r = r; if (use_signed) { const i32_ty_id = try cg.resolveType(.i32, .direct); - const sl = cg.module.allocId(); + const sl = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = i32_ty_id, .id_result = sl, .operand = l, }); - const sr = cg.module.allocId(); + const sr = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = i32_ty_id, .id_result = sr, @@ -2463,14 +2808,14 @@ const CompositeInt = struct { else (if (use_signed) .OpSGreaterThan else .OpUGreaterThan); - const limb_cmp = cg.module.allocId(); + const limb_cmp = cg.allocId(); try cg.body.emitRaw(gpa, cmp_opcode, 4); cg.body.writeOperand(Id, bool_ty_id); cg.body.writeOperand(Id, limb_cmp); cg.body.writeOperand(Id, cmp_l); cg.body.writeOperand(Id, cmp_r); - const selected = cg.module.allocId(); + const selected = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ .id_result_type = bool_ty_id, .id_result = selected, @@ -2487,9 +2832,9 @@ const CompositeInt = struct { fn addSub(ci: CompositeInt, other: CompositeInt, comptime is_add: bool) !CompositeInt { const cg = ci.cg; - const gpa = cg.module.gpa; + const gpa = cg.gpa; const pt = cg.pt; - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ip = &zcu.intern_pool; const comp = zcu.comp; const io = comp.io; @@ -2508,21 +2853,21 @@ const CompositeInt = struct { const opcode: Opcode = if (is_add) .OpIAddCarry else .OpISubBorrow; for (0..ci.n_limbs) |i| { - const op1 = cg.module.allocId(); + const op1 = cg.allocId(); try cg.body.emitRaw(gpa, opcode, 4); cg.body.writeOperand(Id, carry_struct_ty_id); cg.body.writeOperand(Id, op1); cg.body.writeOperand(Id, ci.limbs[i]); cg.body.writeOperand(Id, other.limbs[i]); - const sum1 = cg.module.allocId(); + const sum1 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = sum1, .composite = op1, .indexes = &.{0}, }); - const carry1 = cg.module.allocId(); + const carry1 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = carry1, @@ -2530,21 +2875,21 @@ const CompositeInt = struct { .indexes = &.{1}, }); - const op2 = cg.module.allocId(); + const op2 = cg.allocId(); try cg.body.emitRaw(gpa, opcode, 4); cg.body.writeOperand(Id, carry_struct_ty_id); cg.body.writeOperand(Id, op2); cg.body.writeOperand(Id, sum1); cg.body.writeOperand(Id, carry_id); - result_limbs[i] = cg.module.allocId(); + result_limbs[i] = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = result_limbs[i], .composite = op2, .indexes = &.{0}, }); - const carry2 = cg.module.allocId(); + const carry2 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = carry2, @@ -2560,7 +2905,7 @@ const CompositeInt = struct { fn shl(ci: CompositeInt, shift_amt_id: Id) !CompositeInt { const cg = ci.cg; - const gpa = cg.module.gpa; + const gpa = cg.gpa; const u32_ty_id = try cg.resolveType(.u32, .direct); const bool_ty_id = try cg.resolveType(.bool, .direct); const zero_id = try cg.constInt(.u32, @as(u32, 0)); @@ -2572,12 +2917,13 @@ const CompositeInt = struct { const frac = try ci.limbBinOp(.OpBitwiseAnd, shift_amt_id, thirty_one_id); const comp_frac = try ci.limbBinOp(.OpISub, thirty_two_id, frac); const frac_is_zero = blk: { - const r = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpIEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, r); - cg.body.writeOperand(Id, frac); - cg.body.writeOperand(Id, zero_id); + const r = cg.allocId(); + try cg.body.emit(gpa, .OpIEqual, .{ + .id_result_type = bool_ty_id, + .id_result = r, + .operand_1 = frac, + .operand_2 = zero_id, + }); break :blk r; }; @@ -2593,17 +2939,18 @@ const CompositeInt = struct { const j_plus_whole = try ci.limbBinOp(.OpIAdd, j_id, whole); const is_main = blk: { - const r = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpIEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, r); - cg.body.writeOperand(Id, j_plus_whole); - cg.body.writeOperand(Id, i_id); + const r = cg.allocId(); + try cg.body.emit(gpa, .OpIEqual, .{ + .id_result_type = bool_ty_id, + .id_result = r, + .operand_1 = j_plus_whole, + .operand_2 = i_id, + }); break :blk r; }; const shifted = try ci.limbBinOp(.OpShiftLeftLogical, ci.limbs[j], frac); main_val = blk: { - const r = cg.module.allocId(); + const r = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ .id_result_type = u32_ty_id, .id_result = r, @@ -2617,17 +2964,18 @@ const CompositeInt = struct { const one_id = try cg.constInt(.u32, @as(u32, 1)); const j_plus_whole_plus_1 = try ci.limbBinOp(.OpIAdd, j_plus_whole, one_id); const is_carry = blk: { - const r = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpIEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, r); - cg.body.writeOperand(Id, j_plus_whole_plus_1); - cg.body.writeOperand(Id, i_id); + const r = cg.allocId(); + try cg.body.emit(gpa, .OpIEqual, .{ + .id_result_type = bool_ty_id, + .id_result = r, + .operand_1 = j_plus_whole_plus_1, + .operand_2 = i_id, + }); break :blk r; }; const carry_shifted = try ci.limbBinOp(.OpShiftRightLogical, ci.limbs[j], comp_frac); const guarded_carry = blk: { - const r = cg.module.allocId(); + const r = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ .id_result_type = u32_ty_id, .id_result = r, @@ -2638,7 +2986,7 @@ const CompositeInt = struct { break :blk r; }; carry_val = blk: { - const r = cg.module.allocId(); + const r = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ .id_result_type = u32_ty_id, .id_result = r, @@ -2658,7 +3006,7 @@ const CompositeInt = struct { fn shr(ci: CompositeInt, shift_amt_id: Id, comptime is_arithmetic: bool) !CompositeInt { const cg = ci.cg; - const gpa = cg.module.gpa; + const gpa = cg.gpa; const u32_ty_id = try cg.resolveType(.u32, .direct); const bool_ty_id = try cg.resolveType(.bool, .direct); const zero_id = try cg.constInt(.u32, @as(u32, 0)); @@ -2670,31 +3018,33 @@ const CompositeInt = struct { const frac = try ci.limbBinOp(.OpBitwiseAnd, shift_amt_id, thirty_one_id); const comp_frac = try ci.limbBinOp(.OpISub, thirty_two_id, frac); const frac_is_zero = blk: { - const r = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpIEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, r); - cg.body.writeOperand(Id, frac); - cg.body.writeOperand(Id, zero_id); + const r = cg.allocId(); + try cg.body.emit(gpa, .OpIEqual, .{ + .id_result_type = bool_ty_id, + .id_result = r, + .operand_1 = frac, + .operand_2 = zero_id, + }); break :blk r; }; const fill_id = if (is_arithmetic) blk: { const i32_ty_id = try cg.resolveType(.i32, .direct); - const msb_signed = cg.module.allocId(); + const msb_signed = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = i32_ty_id, .id_result = msb_signed, .operand = ci.limbs[ci.n_limbs - 1], }); const shift31 = try cg.constInt(.i32, @as(i32, 31)); - const sign_ext = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpShiftRightArithmetic, 4); - cg.body.writeOperand(Id, i32_ty_id); - cg.body.writeOperand(Id, sign_ext); - cg.body.writeOperand(Id, msb_signed); - cg.body.writeOperand(Id, shift31); - const back = cg.module.allocId(); + const sign_ext = cg.allocId(); + try cg.body.emit(gpa, .OpShiftRightArithmetic, .{ + .id_result_type = i32_ty_id, + .id_result = sign_ext, + .base = msb_signed, + .shift = shift31, + }); + const back = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = u32_ty_id, .id_result = back, @@ -2707,7 +3057,7 @@ const CompositeInt = struct { const arith_carry_init = if (is_arithmetic) blk: { const shifted_fill = try ci.limbBinOp(.OpShiftLeftLogical, fill_id, comp_frac); - const guarded = cg.module.allocId(); + const guarded = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ .id_result_type = u32_ty_id, .id_result = guarded, @@ -2727,17 +3077,18 @@ const CompositeInt = struct { const j_id = try cg.constInt(.u32, @as(u32, @intCast(j))); const i_plus_whole = try ci.limbBinOp(.OpIAdd, i_id, whole); const is_main = blk: { - const r = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpIEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, r); - cg.body.writeOperand(Id, j_id); - cg.body.writeOperand(Id, i_plus_whole); + const r = cg.allocId(); + try cg.body.emit(gpa, .OpIEqual, .{ + .id_result_type = bool_ty_id, + .id_result = r, + .operand_1 = j_id, + .operand_2 = i_plus_whole, + }); break :blk r; }; const shifted = try ci.limbBinOp(.OpShiftRightLogical, ci.limbs[j], frac); main_val = blk: { - const r = cg.module.allocId(); + const r = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ .id_result_type = u32_ty_id, .id_result = r, @@ -2751,17 +3102,18 @@ const CompositeInt = struct { const one_id = try cg.constInt(.u32, @as(u32, 1)); const i_plus_whole_plus_1 = try ci.limbBinOp(.OpIAdd, i_plus_whole, one_id); const is_carry = blk: { - const r = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpIEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, r); - cg.body.writeOperand(Id, j_id); - cg.body.writeOperand(Id, i_plus_whole_plus_1); + const r = cg.allocId(); + try cg.body.emit(gpa, .OpIEqual, .{ + .id_result_type = bool_ty_id, + .id_result = r, + .operand_1 = j_id, + .operand_2 = i_plus_whole_plus_1, + }); break :blk r; }; const carry_shifted = try ci.limbBinOp(.OpShiftLeftLogical, ci.limbs[j], comp_frac); const guarded_carry = blk: { - const r = cg.module.allocId(); + const r = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ .id_result_type = u32_ty_id, .id_result = r, @@ -2772,7 +3124,7 @@ const CompositeInt = struct { break :blk r; }; carry_val = blk: { - const r = cg.module.allocId(); + const r = cg.allocId(); try cg.body.emit(gpa, .OpSelect, .{ .id_result_type = u32_ty_id, .id_result = r, @@ -2792,9 +3144,9 @@ const CompositeInt = struct { fn mul(ci: CompositeInt, other: CompositeInt, comptime wide: bool) ![]Id { const cg = ci.cg; - const gpa = cg.module.gpa; + const gpa = cg.gpa; const pt = cg.pt; - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ip = &zcu.intern_pool; const comp = zcu.comp; const io = comp.io; @@ -2825,15 +3177,16 @@ const CompositeInt = struct { var hi: Id = undefined; switch (target.os.tag) { .opencl => { - lo = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpIMul, 4); - cg.body.writeOperand(Id, u32_ty_id); - cg.body.writeOperand(Id, lo); - cg.body.writeOperand(Id, ci.limbs[i]); - cg.body.writeOperand(Id, other.limbs[j]); + lo = cg.allocId(); + try cg.body.emit(gpa, .OpIMul, .{ + .id_result_type = u32_ty_id, + .id_result = lo, + .operand_1 = ci.limbs[i], + .operand_2 = other.limbs[j], + }); const set = try cg.importExtendedSet(); - hi = cg.module.allocId(); + hi = cg.allocId(); try cg.body.emit(gpa, .OpExtInst, .{ .id_result_type = u32_ty_id, .id_result = hi, @@ -2843,21 +3196,22 @@ const CompositeInt = struct { }); }, else => { - const mul_result = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpUMulExtended, 4); - cg.body.writeOperand(Id, pair_struct_ty_id); - cg.body.writeOperand(Id, mul_result); - cg.body.writeOperand(Id, ci.limbs[i]); - cg.body.writeOperand(Id, other.limbs[j]); + const mul_result = cg.allocId(); + try cg.body.emit(gpa, .OpUMulExtended, .{ + .id_result_type = pair_struct_ty_id, + .id_result = mul_result, + .operand_1 = ci.limbs[i], + .operand_2 = other.limbs[j], + }); - lo = cg.module.allocId(); + lo = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = lo, .composite = mul_result, .indexes = &.{0}, }); - hi = cg.module.allocId(); + hi = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = hi, @@ -2867,21 +3221,22 @@ const CompositeInt = struct { }, } - const add1 = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpIAddCarry, 4); - cg.body.writeOperand(Id, pair_struct_ty_id); - cg.body.writeOperand(Id, add1); - cg.body.writeOperand(Id, result_limbs[k]); - cg.body.writeOperand(Id, lo); + const add1 = cg.allocId(); + try cg.body.emit(gpa, .OpIAddCarry, .{ + .id_result_type = pair_struct_ty_id, + .id_result = add1, + .operand_1 = result_limbs[k], + .operand_2 = lo, + }); - const sum1 = cg.module.allocId(); + const sum1 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = sum1, .composite = add1, .indexes = &.{0}, }); - const c1 = cg.module.allocId(); + const c1 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = c1, @@ -2889,21 +3244,22 @@ const CompositeInt = struct { .indexes = &.{1}, }); - const add2 = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpIAddCarry, 4); - cg.body.writeOperand(Id, pair_struct_ty_id); - cg.body.writeOperand(Id, add2); - cg.body.writeOperand(Id, sum1); - cg.body.writeOperand(Id, carry_id); + const add2 = cg.allocId(); + try cg.body.emit(gpa, .OpIAddCarry, .{ + .id_result_type = pair_struct_ty_id, + .id_result = add2, + .operand_1 = sum1, + .operand_2 = carry_id, + }); - result_limbs[k] = cg.module.allocId(); + result_limbs[k] = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = result_limbs[k], .composite = add2, .indexes = &.{0}, }); - const c2 = cg.module.allocId(); + const c2 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = c2, @@ -2925,8 +3281,8 @@ const CompositeInt = struct { fn normalize(ci: CompositeInt) !CompositeInt { if (ci.info.bits == ci.info.backing_bits) return ci; const cg = ci.cg; - const gpa = cg.module.gpa; - const top_bits: u16 = ci.info.bits % Module.big_int_bits; + const gpa = cg.gpa; + const top_bits: u16 = ci.info.bits % big_int_bits; assert(top_bits != 0); const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, ci.n_limbs); @@ -2947,25 +3303,27 @@ const CompositeInt = struct { const shift_amt: u32 = 32 - top_bits; const shift_id = try cg.constInt(.u32, shift_amt); - const as_signed = cg.module.allocId(); + const as_signed = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = i32_ty_id, .id_result = as_signed, .operand = top_limb, }); - const shifted_left = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpShiftLeftLogical, 4); - cg.body.writeOperand(Id, i32_ty_id); - cg.body.writeOperand(Id, shifted_left); - cg.body.writeOperand(Id, as_signed); - cg.body.writeOperand(Id, shift_id); - const shifted_right = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpShiftRightArithmetic, 4); - cg.body.writeOperand(Id, i32_ty_id); - cg.body.writeOperand(Id, shifted_right); - cg.body.writeOperand(Id, shifted_left); - cg.body.writeOperand(Id, shift_id); - const back = cg.module.allocId(); + const shifted_left = cg.allocId(); + try cg.body.emit(gpa, .OpShiftLeftLogical, .{ + .id_result_type = i32_ty_id, + .id_result = shifted_left, + .base = as_signed, + .shift = shift_id, + }); + const shifted_right = cg.allocId(); + try cg.body.emit(gpa, .OpShiftRightArithmetic, .{ + .id_result_type = i32_ty_id, + .id_result = shifted_right, + .base = shifted_left, + .shift = shift_id, + }); + const back = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = u32_ty_id, .id_result = back, @@ -3000,7 +3358,7 @@ const Vectorization = union(enum) { /// Derive a vectorization from a particular type fn fromType(ty: Type, cg: *CodeGen) Vectorization { - const zcu = cg.module.zcu; + const zcu = cg.zcu; if (!ty.isVector(zcu)) return .scalar; return .{ .unrolled = ty.vectorLen(zcu) }; } @@ -3034,7 +3392,7 @@ const Vectorization = union(enum) { /// `ty` may be a scalar or vector, it doesn't matter. fn resultType(vec: Vectorization, cg: *CodeGen, ty: Type) !Type { const pt = cg.pt; - const zcu = cg.module.zcu; + const zcu = cg.zcu; const scalar_ty = ty.scalarType(zcu); return switch (vec) { .scalar => scalar_ty, @@ -3046,7 +3404,7 @@ const Vectorization = union(enum) { /// this setup, and returns a new type that holds the relevant information on how to access /// elements of the input. fn prepare(vec: Vectorization, cg: *CodeGen, tmp: Temporary) !PreparedOperand { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const is_vector = tmp.ty.isVector(zcu); const value: PreparedOperand.Value = switch (tmp.value) { .singleton => |id| switch (vec) { @@ -3146,26 +3504,28 @@ fn vectorization(cg: *CodeGen, args: anytype) Vectorization { /// This function builds an OpSConvert of OpUConvert depending on the /// signedness of the types. fn buildConvert(cg: *CodeGen, dst_ty: Type, src: Temporary) !Temporary { - const zcu = cg.module.zcu; - - const dst_ty_id = try cg.resolveType(dst_ty.scalarType(zcu), .direct); - const src_ty_id = try cg.resolveType(src.ty.scalarType(zcu), .direct); + const zcu = cg.zcu; const v = cg.vectorization(.{ dst_ty, src }); const result_ty = try v.resultType(cg, dst_ty); - // We can directly compare integers, because those type-IDs are cached. - if (dst_ty_id == src_ty_id) { - // Nothing to do, type-pun to the right value. - // Note, Caller guarantees that the types fit (or caller will normalize after), - // so we don't have to normalize here. - // Note, dst_ty may be a scalar type even if we expect a vector, so we have to - // convert to the right type here. + const dst_scalar = dst_ty.scalarType(zcu); + const src_scalar = src.ty.scalarType(zcu); + if (dst_scalar.toIntern() == src_scalar.toIntern()) { return src.pun(result_ty); } + if (dst_scalar.isInt(zcu) and src_scalar.isInt(zcu)) { + const dst_info = dst_scalar.intInfo(zcu); + const src_info = src_scalar.intInfo(zcu); + if (cg.backingIntBits(dst_info.bits).@"0" == cg.backingIntBits(src_info.bits).@"0" and + dst_info.signedness == src_info.signedness) + { + return src.pun(result_ty); + } + } const ops = v.components(); - const results = cg.module.allocIds(ops); + const results = cg.allocIds(ops); const op_result_ty = dst_ty.scalarType(zcu); const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); @@ -3179,7 +3539,7 @@ fn buildConvert(cg: *CodeGen, dst_ty: Type, src: Temporary) !Temporary { const op_src = try v.prepare(cg, src); for (0..ops) |i| { - try cg.body.emitRaw(cg.module.gpa, opcode, 3); + try cg.body.emitRaw(cg.gpa, opcode, 3); cg.body.writeOperand(Id, op_result_ty_id); cg.body.writeOperand(Id, results.at(i)); cg.body.writeOperand(Id, op_src.at(i)); @@ -3188,51 +3548,12 @@ fn buildConvert(cg: *CodeGen, dst_ty: Type, src: Temporary) !Temporary { return v.finalize(result_ty, results); } -fn buildFma(cg: *CodeGen, a: Temporary, b: Temporary, c: Temporary) !Temporary { - const zcu = cg.module.zcu; - const target = cg.module.zcu.getTarget(); - - const v = cg.vectorization(.{ a, b, c }); - const ops = v.components(); - const results = cg.module.allocIds(ops); - - const op_result_ty = a.ty.scalarType(zcu); - const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); - const result_ty = try v.resultType(cg, a.ty); - - const op_a = try v.prepare(cg, a); - const op_b = try v.prepare(cg, b); - const op_c = try v.prepare(cg, c); - - const set = try cg.importExtendedSet(); - const opcode: u32 = switch (target.os.tag) { - .opencl => @intFromEnum(spec.OpenClOpcode.fma), - // NOTE: Vulkan's FMA instruction does *NOT* produce the right values! - // its precision guarantees do NOT match zigs and it does NOT match OpenCLs! - // it needs to be emulated! - .vulkan, .opengl => @intFromEnum(spec.GlslOpcode.Fma), - else => unreachable, - }; - - for (0..ops) |i| { - try cg.body.emit(cg.module.gpa, .OpExtInst, .{ - .id_result_type = op_result_ty_id, - .id_result = results.at(i), - .set = set, - .instruction = .{ .inst = opcode }, - .id_ref_4 = &.{ op_a.at(i), op_b.at(i), op_c.at(i) }, - }); - } - - return v.finalize(result_ty, results); -} - fn buildSelect(cg: *CodeGen, condition: Temporary, lhs: Temporary, rhs: Temporary) !Temporary { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const v = cg.vectorization(.{ condition, lhs, rhs }); const ops = v.components(); - const results = cg.module.allocIds(ops); + const results = cg.allocIds(ops); const op_result_ty = lhs.ty.scalarType(zcu); const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); @@ -3245,7 +3566,7 @@ fn buildSelect(cg: *CodeGen, condition: Temporary, lhs: Temporary, rhs: Temporar const object_2 = try v.prepare(cg, rhs); for (0..ops) |i| { - try cg.body.emit(cg.module.gpa, .OpSelect, .{ + try cg.body.emit(cg.gpa, .OpSelect, .{ .id_result_type = op_result_ty_id, .id_result = results.at(i), .condition = cond.at(i), @@ -3260,7 +3581,7 @@ fn buildSelect(cg: *CodeGen, condition: Temporary, lhs: Temporary, rhs: Temporar fn buildCmp(cg: *CodeGen, opcode: Opcode, lhs: Temporary, rhs: Temporary) !Temporary { const v = cg.vectorization(.{ lhs, rhs }); const ops = v.components(); - const results = cg.module.allocIds(ops); + const results = cg.allocIds(ops); const op_result_ty: Type = .bool; const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); @@ -3270,7 +3591,7 @@ fn buildCmp(cg: *CodeGen, opcode: Opcode, lhs: Temporary, rhs: Temporary) !Tempo const op_rhs = try v.prepare(cg, rhs); for (0..ops) |i| { - try cg.body.emitRaw(cg.module.gpa, opcode, 4); + try cg.body.emitRaw(cg.gpa, opcode, 4); cg.body.writeOperand(Id, op_result_ty_id); cg.body.writeOperand(Id, results.at(i)); cg.body.writeOperand(Id, op_lhs.at(i)); @@ -3351,11 +3672,11 @@ const UnaryOp = enum { }; fn buildUnary(cg: *CodeGen, op: UnaryOp, operand: Temporary) !Temporary { - const zcu = cg.module.zcu; - const target = cg.module.zcu.getTarget(); + const zcu = cg.zcu; + const target = cg.zcu.getTarget(); const v = cg.vectorization(.{operand}); const ops = v.components(); - const results = cg.module.allocIds(ops); + const results = cg.allocIds(ops); const op_result_ty = operand.ty.scalarType(zcu); const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); const result_ty = try v.resultType(cg, operand.ty); @@ -3364,7 +3685,7 @@ fn buildUnary(cg: *CodeGen, op: UnaryOp, operand: Temporary) !Temporary { if (op.extInstOpcode(target)) |opcode| { const set = try cg.importExtendedSet(); for (0..ops) |i| { - try cg.body.emit(cg.module.gpa, .OpExtInst, .{ + try cg.body.emit(cg.gpa, .OpExtInst, .{ .id_result_type = op_result_ty_id, .id_result = results.at(i), .set = set, @@ -3384,7 +3705,7 @@ fn buildUnary(cg: *CodeGen, op: UnaryOp, operand: Temporary) !Temporary { ), }; for (0..ops) |i| { - try cg.body.emitRaw(cg.module.gpa, opcode, 3); + try cg.body.emitRaw(cg.gpa, opcode, 3); cg.body.writeOperand(Id, op_result_ty_id); cg.body.writeOperand(Id, results.at(i)); cg.body.writeOperand(Id, op_operand.at(i)); @@ -3395,11 +3716,11 @@ fn buildUnary(cg: *CodeGen, op: UnaryOp, operand: Temporary) !Temporary { } fn buildBinary(cg: *CodeGen, opcode: Opcode, lhs: Temporary, rhs: Temporary) !Temporary { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const v = cg.vectorization(.{ lhs, rhs }); const ops = v.components(); - const results = cg.module.allocIds(ops); + const results = cg.allocIds(ops); const op_result_ty = lhs.ty.scalarType(zcu); const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); @@ -3409,7 +3730,7 @@ fn buildBinary(cg: *CodeGen, opcode: Opcode, lhs: Temporary, rhs: Temporary) !Te const op_rhs = try v.prepare(cg, rhs); for (0..ops) |i| { - try cg.body.emitRaw(cg.module.gpa, opcode, 4); + try cg.body.emitRaw(cg.gpa, opcode, 4); cg.body.writeOperand(Id, op_result_ty_id); cg.body.writeOperand(Id, results.at(i)); cg.body.writeOperand(Id, op_lhs.at(i)); @@ -3428,11 +3749,11 @@ fn buildWideMul( rhs: Temporary, ) !struct { Temporary, Temporary } { const pt = cg.pt; - const zcu = cg.module.zcu; + const zcu = cg.zcu; const comp = zcu.comp; const gpa = comp.gpa; const io = comp.io; - const target = cg.module.zcu.getTarget(); + const target = cg.zcu.getTarget(); const ip = &zcu.intern_pool; const v = lhs.vectorization(cg).unify(rhs.vectorization(cg)); @@ -3444,8 +3765,8 @@ fn buildWideMul( const lhs_op = try v.prepare(cg, lhs); const rhs_op = try v.prepare(cg, rhs); - const value_results = cg.module.allocIds(ops); - const overflow_results = cg.module.allocIds(ops); + const value_results = cg.allocIds(ops); + const overflow_results = cg.allocIds(ops); switch (target.os.tag) { .opencl => { @@ -3490,7 +3811,7 @@ fn buildWideMul( }; for (0..ops) |i| { - const op_result = cg.module.allocId(); + const op_result = cg.allocId(); try cg.body.emitRaw(gpa, opcode, 4); cg.body.writeOperand(Id, op_result_ty_id); @@ -3550,12 +3871,12 @@ fn buildWideMul( fn generateTestEntryPoint( cg: *CodeGen, name: []const u8, - spv_decl_index: Module.Decl.Index, + spv_decl_index: Decl.Index, test_id: Id, ) !void { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; - const target = cg.module.zcu.getTarget(); + const gpa = cg.gpa; + const zcu = cg.zcu; + const target = cg.zcu.getTarget(); const anyerror_ty_id = try cg.resolveType(.anyerror, .direct); const ptr_anyerror_ty = try cg.pt.ptrType(.{ @@ -3564,15 +3885,15 @@ fn generateTestEntryPoint( }); const ptr_anyerror_ty_id = try cg.resolveType(ptr_anyerror_ty, .direct); - const kernel_id = cg.module.declPtr(spv_decl_index).result_id; + const kernel_id = cg.declPtr(spv_decl_index).result_id; - const section = &cg.module.sections.functions; + const section = &cg.sections.functions; - const p_error_id = cg.module.allocId(); + const p_error_id = cg.allocId(); switch (target.os.tag) { .opencl, .amdhsa => { const void_ty_id = try cg.resolveType(.void, .direct); - const kernel_proto_ty_id = try cg.module.functionType(void_ty_id, &.{ptr_anyerror_ty_id}); + const kernel_proto_ty_id = try cg.functionType(void_ty_id, &.{ptr_anyerror_ty_id}); try section.emit(gpa, .OpFunction, .{ .id_result_type = try cg.resolveType(.void, .direct), @@ -3587,43 +3908,43 @@ fn generateTestEntryPoint( }); try section.emit(gpa, .OpLabel, .{ - .id_result = cg.module.allocId(), + .id_result = cg.allocId(), }); }, .vulkan, .opengl => { - if (cg.module.error_buffer == null) { - const spv_err_decl_index = try cg.module.allocDecl(.global); - const err_buf_result_id = cg.module.declPtr(spv_err_decl_index).result_id; + if (cg.error_buffer == null) { + const spv_err_decl_index = try cg.allocDecl(.global); + const err_buf_result_id = cg.declPtr(spv_err_decl_index).result_id; - const buffer_struct_ty_id = cg.module.allocId(); - try cg.module.sections.globals.emit(gpa, .OpTypeStruct, .{ + const buffer_struct_ty_id = cg.allocId(); + try cg.sections.globals.emit(gpa, .OpTypeStruct, .{ .id_result = buffer_struct_ty_id, .id_ref = &.{anyerror_ty_id}, }); - try cg.module.memberDebugName(buffer_struct_ty_id, 0, "error_out"); - try cg.module.decorate(buffer_struct_ty_id, .block); - try cg.module.decorateMember(buffer_struct_ty_id, 0, .{ .offset = .{ .byte_offset = 0 } }); + try cg.memberDebugName(buffer_struct_ty_id, 0, "error_out"); + try cg.decorate(buffer_struct_ty_id, .block); + try cg.decorateMember(buffer_struct_ty_id, 0, .{ .offset = .{ .byte_offset = 0 } }); - const ptr_buffer_struct_ty_id = cg.module.allocId(); - try cg.module.sections.globals.emit(gpa, .OpTypePointer, .{ + const ptr_buffer_struct_ty_id = cg.allocId(); + try cg.sections.globals.emit(gpa, .OpTypePointer, .{ .id_result = ptr_buffer_struct_ty_id, - .storage_class = cg.module.storageClass(.global), + .storage_class = cg.storageClass(.global), .type = buffer_struct_ty_id, }); - try cg.module.sections.globals.emit(gpa, .OpVariable, .{ + try cg.sections.globals.emit(gpa, .OpVariable, .{ .id_result_type = ptr_buffer_struct_ty_id, .id_result = err_buf_result_id, - .storage_class = cg.module.storageClass(.global), + .storage_class = cg.storageClass(.global), }); - try cg.module.decorate(err_buf_result_id, .{ .descriptor_set = .{ .descriptor_set = 0 } }); - try cg.module.decorate(err_buf_result_id, .{ .binding = .{ .binding_point = 0 } }); + try cg.decorate(err_buf_result_id, .{ .descriptor_set = .{ .descriptor_set = 0 } }); + try cg.decorate(err_buf_result_id, .{ .binding = .{ .binding_point = 0 } }); - cg.module.error_buffer = spv_err_decl_index; + cg.error_buffer = spv_err_decl_index; } const void_ty_id = try cg.resolveType(.void, .direct); - const kernel_proto_ty_id = try cg.module.functionType(void_ty_id, &.{}); + const kernel_proto_ty_id = try cg.functionType(void_ty_id, &.{}); try section.emit(gpa, .OpFunction, .{ .id_result_type = try cg.resolveType(.void, .direct), .id_result = kernel_id, @@ -3631,12 +3952,12 @@ fn generateTestEntryPoint( .function_type = kernel_proto_ty_id, }); try section.emit(gpa, .OpLabel, .{ - .id_result = cg.module.allocId(), + .id_result = cg.allocId(), }); - const spv_err_decl_index = cg.module.error_buffer.?; - const buffer_id = cg.module.declPtr(spv_err_decl_index).result_id; - try cg.module.decl_deps.append(gpa, spv_err_decl_index); + const spv_err_decl_index = cg.error_buffer.?; + const buffer_id = cg.declPtr(spv_err_decl_index).result_id; + try cg.decl_deps.append(gpa, spv_err_decl_index); const zero_id = try cg.constInt(.u32, 0); try section.emit(gpa, .OpInBoundsAccessChain, .{ @@ -3649,7 +3970,7 @@ fn generateTestEntryPoint( else => unreachable, } - const error_id = cg.module.allocId(); + const error_id = cg.allocId(); try section.emit(gpa, .OpFunctionCall, .{ .id_result_type = anyerror_ty_id, .id_result = error_id, @@ -3668,9 +3989,14 @@ fn generateTestEntryPoint( // 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(cg.module.arena, "test {s}", .{name}); + const test_name = try std.fmt.allocPrint(cg.arena, "test {s}", .{name}); - try cg.module.declareEntryPoint(spv_decl_index, test_name, .{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }); + const ep_gop = try cg.entry_points.getOrPut(cg.gpa, cg.declPtr(spv_decl_index).result_id); + ep_gop.value_ptr.* = .{ + .decl_index = spv_decl_index, + .name = test_name, + .cc = .{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }, + }; } fn intFromBool(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary { @@ -3688,7 +4014,7 @@ fn intFromBool(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary { /// This converts the argument type from resolveType(ty, .indirect) to resolveType(ty, .direct). fn convertToDirect(cg: *CodeGen, ty: Type, operand_id: Id) !Id { const pt = cg.pt; - const zcu = cg.module.zcu; + const zcu = cg.zcu; switch (ty.scalarType(zcu).zigTypeTag(zcu)) { .bool => { const false_id = try cg.constBool(false, .indirect); @@ -3714,7 +4040,7 @@ fn convertToDirect(cg: *CodeGen, ty: Type, operand_id: Id) !Id { /// Convert representation from direct (in 'register) to direct (in memory) /// This converts the argument type from resolveType(ty, .direct) to resolveType(ty, .indirect). fn convertToIndirect(cg: *CodeGen, ty: Type, operand_id: Id) !Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; switch (ty.scalarType(zcu).zigTypeTag(zcu)) { .bool => { const result = try cg.intFromBool(.init(ty, operand_id), .u1); @@ -3726,9 +4052,9 @@ fn convertToIndirect(cg: *CodeGen, ty: Type, operand_id: Id) !Id { fn extractField(cg: *CodeGen, result_ty: Type, object: Id, field: u32) !Id { const result_ty_id = try cg.resolveType(result_ty, .indirect); - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); const indexes = [_]u32{field}; - try cg.body.emit(cg.module.gpa, .OpCompositeExtract, .{ + try cg.body.emit(cg.gpa, .OpCompositeExtract, .{ .id_result_type = result_ty_id, .id_result = result_id, .composite = object, @@ -3740,9 +4066,9 @@ fn extractField(cg: *CodeGen, result_ty: Type, object: Id, field: u32) !Id { fn extractVectorComponent(cg: *CodeGen, result_ty: Type, vector_id: Id, field: u32) !Id { const result_ty_id = try cg.resolveType(result_ty, .direct); - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); const indexes = [_]u32{field}; - try cg.body.emit(cg.module.gpa, .OpCompositeExtract, .{ + try cg.body.emit(cg.gpa, .OpCompositeExtract, .{ .id_result_type = result_ty_id, .id_result = result_id, .composite = vector_id, @@ -3757,15 +4083,15 @@ const MemoryOptions = struct { }; fn load(cg: *CodeGen, value_ty: Type, ptr_id: Id, options: MemoryOptions) !Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const alignment: u32 = @intCast(value_ty.abiAlignment(zcu).toByteUnits().?); const indirect_value_ty_id = try cg.resolveType(value_ty, .indirect); - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); const access: spec.MemoryAccess.Extended = .{ .@"volatile" = options.is_volatile, .aligned = .{ .literal_integer = alignment }, }; - try cg.body.emit(cg.module.gpa, .OpLoad, .{ + try cg.body.emit(cg.gpa, .OpLoad, .{ .id_result_type = indirect_value_ty_id, .id_result = result_id, .pointer = ptr_id, @@ -3777,7 +4103,7 @@ fn load(cg: *CodeGen, value_ty: Type, ptr_id: Id, options: MemoryOptions) !Id { fn store(cg: *CodeGen, value_ty: Type, ptr_id: Id, value_id: Id, options: MemoryOptions) !void { const indirect_value_id = try cg.convertToIndirect(value_ty, value_id); const access: spec.MemoryAccess.Extended = .{ .@"volatile" = options.is_volatile }; - try cg.body.emit(cg.module.gpa, .OpStore, .{ + try cg.body.emit(cg.gpa, .OpStore, .{ .pointer = ptr_id, .object = indirect_value_id, .memory_access = access, @@ -3791,8 +4117,8 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) !void { } fn genInst(cg: *CodeGen, inst: Air.Inst.Index) Error!void { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; + const gpa = cg.gpa; + const zcu = cg.zcu; const ip = &zcu.intern_pool; if (cg.liveness.isUnused(inst) and !cg.air.mustLower(inst, ip)) return; @@ -4028,7 +4354,7 @@ fn airBitwiseOp(cg: *CodeGen, inst: Air.Inst.Index, op: BitwiseOp) !?Id { } fn airShift(cg: *CodeGen, inst: Air.Inst.Index, unsigned: Opcode, signed: Opcode) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; if (cg.typeOf(bin_op.lhs).isVector(zcu) and !cg.typeOf(bin_op.rhs).isVector(zcu)) { @@ -4048,8 +4374,8 @@ fn airShift(cg: *CodeGen, inst: Air.Inst.Index, unsigned: Opcode, signed: Opcode .composite_integer => blk: { const shift_id = try shift.materialize(cg); const u32_ty_id = try cg.resolveType(.u32, .direct); - const result_id = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpCompositeExtract, .{ + const result_id = cg.allocId(); + try cg.body.emit(cg.gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = result_id, .composite = shift_id, @@ -4156,13 +4482,13 @@ fn airMinMax(cg: *CodeGen, inst: Air.Inst.Index, op: MinMax) !?Id { } fn minMax(cg: *CodeGen, lhs: Temporary, rhs: Temporary, op: MinMax) !Temporary { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const target = zcu.getTarget(); const info = cg.arithmeticTypeInfo(lhs.ty); const v = cg.vectorization(.{ lhs, rhs }); const ops = v.components(); - const results = cg.module.allocIds(ops); + const results = cg.allocIds(ops); const op_result_ty = lhs.ty.scalarType(zcu); const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); @@ -4174,7 +4500,7 @@ fn minMax(cg: *CodeGen, lhs: Temporary, rhs: Temporary, op: MinMax) !Temporary { const set = try cg.importExtendedSet(); const opcode = op.extInstOpcode(target, info); for (0..ops) |i| { - try cg.body.emit(cg.module.gpa, .OpExtInst, .{ + try cg.body.emit(cg.gpa, .OpExtInst, .{ .id_result_type = op_result_ty_id, .id_result = results.at(i), .set = set, @@ -4195,7 +4521,7 @@ fn minMax(cg: *CodeGen, lhs: Temporary, rhs: Temporary, op: MinMax) !Temporary { /// All other values are returned unmodified (this makes strange integer /// wrapping easier to use in generic operations). fn normalize(cg: *CodeGen, value: Temporary, info: ArithmeticTypeInfo) !Temporary { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ty = value.ty; switch (info.class) { .integer, .bool, .float => return value, @@ -4341,29 +4667,24 @@ fn airArithOp( } fn airAbs(cg: *CodeGen, inst: Air.Inst.Index) !?Id { + const zcu = cg.zcu; + const target = zcu.getTarget(); const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; - const operand = try cg.temporary(ty_op.operand); - // Note: operand_ty may be signed, while ty is always unsigned! + const value = try cg.temporary(ty_op.operand); + // Note: operand_ty may be signed, while ty is always unsigned. const result_ty = cg.typeOfIndex(inst); - const result = try cg.abs(result_ty, operand); - return try result.materialize(cg); -} - -fn abs(cg: *CodeGen, result_ty: Type, value: Temporary) !Temporary { - const zcu = cg.module.zcu; - const target = cg.module.zcu.getTarget(); const operand_info = cg.arithmeticTypeInfo(value.ty); - switch (operand_info.class) { - .float => return try cg.buildUnary(.f_abs, value), - .integer, .strange_integer => { + const result: Temporary = switch (operand_info.class) { + .float => try cg.buildUnary(.f_abs, value), + .integer, .strange_integer => abs: { var abs_value = try cg.buildUnary(.i_abs, value); switch (target.os.tag) { .vulkan, .opengl => { if (value.ty.intInfo(zcu).signedness == .signed) { const abs_id = try abs_value.materialize(cg); const dst_ty_id = try cg.resolveType(result_ty, .direct); - const cast_id = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpBitcast, .{ + const cast_id = cg.allocId(); + try cg.body.emit(cg.gpa, .OpBitcast, .{ .id_result_type = dst_ty_id, .id_result = cast_id, .operand = abs_id, @@ -4373,9 +4694,9 @@ fn abs(cg: *CodeGen, result_ty: Type, value: Temporary) !Temporary { }, else => {}, } - return try cg.normalize(abs_value, cg.arithmeticTypeInfo(result_ty)); + break :abs try cg.normalize(abs_value, cg.arithmeticTypeInfo(result_ty)); }, - .composite_integer => { + .composite_integer => abs: { const val_id = try value.materialize(cg); const scratch_top = cg.id_scratch.items.len; defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); @@ -4385,10 +4706,10 @@ fn abs(cg: *CodeGen, result_ty: Type, value: Temporary) !Temporary { const ci_neg = try ci_z.addSub(ci, false); const result_info = cg.arithmeticTypeInfo(result_ty); const u32_ty_id = try cg.resolveType(.u32, .direct); - const result_limbs = try cg.id_scratch.addManyAsSlice(cg.module.gpa, ci.n_limbs); + const result_limbs = try cg.id_scratch.addManyAsSlice(cg.gpa, ci.n_limbs); for (0..ci.n_limbs) |i| { - result_limbs[i] = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpSelect, .{ + result_limbs[i] = cg.allocId(); + try cg.body.emit(cg.gpa, .OpSelect, .{ .id_result_type = u32_ty_id, .id_result = result_limbs[i], .condition = is_neg, @@ -4398,10 +4719,11 @@ fn abs(cg: *CodeGen, result_ty: Type, value: Temporary) !Temporary { } const ci_result = CompositeInt.fromLimbs(cg, result_limbs, result_info); const normalized = try ci_result.normalize(); - return .init(result_ty, try normalized.materialize(result_ty)); + break :abs .init(result_ty, try normalized.materialize(result_ty)); }, .bool => unreachable, - } + }; + return try result.materialize(cg); } fn airAddSubOverflow( @@ -4457,32 +4779,36 @@ fn airAddSubOverflow( const res_neg = try ci_res2.cmp(ci_z, .lt); const bool_ty_id = try cg.resolveType(.bool, .direct); - const signs_match = cg.module.allocId(); - try cg.body.emitRaw(cg.module.gpa, .OpLogicalEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, signs_match); - cg.body.writeOperand(Id, lhs_neg); - cg.body.writeOperand(Id, rhs_neg); - const res_sign_diff = cg.module.allocId(); - try cg.body.emitRaw(cg.module.gpa, .OpLogicalNotEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, res_sign_diff); - cg.body.writeOperand(Id, lhs_neg); - cg.body.writeOperand(Id, res_neg); + const signs_match = cg.allocId(); + try cg.body.emit(cg.gpa, .OpLogicalEqual, .{ + .id_result_type = bool_ty_id, + .id_result = signs_match, + .operand_1 = lhs_neg, + .operand_2 = rhs_neg, + }); + const res_sign_diff = cg.allocId(); + try cg.body.emit(cg.gpa, .OpLogicalNotEqual, .{ + .id_result_type = bool_ty_id, + .id_result = res_sign_diff, + .operand_1 = lhs_neg, + .operand_2 = res_neg, + }); const ov_cond = if (add == .OpIAdd) signs_match else blk2: { - const not_match = cg.module.allocId(); - try cg.body.emitRaw(cg.module.gpa, .OpLogicalNot, 3); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, not_match); - cg.body.writeOperand(Id, signs_match); + const not_match = cg.allocId(); + try cg.body.emit(cg.gpa, .OpLogicalNot, .{ + .id_result_type = bool_ty_id, + .id_result = not_match, + .operand = signs_match, + }); break :blk2 not_match; }; - const ov_result = cg.module.allocId(); - try cg.body.emitRaw(cg.module.gpa, .OpLogicalAnd, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, ov_result); - cg.body.writeOperand(Id, ov_cond); - cg.body.writeOperand(Id, res_sign_diff); + const ov_result = cg.allocId(); + try cg.body.emit(cg.gpa, .OpLogicalAnd, .{ + .id_result_type = bool_ty_id, + .id_result = ov_result, + .operand_1 = ov_cond, + .operand_2 = res_sign_diff, + }); break :blk ov_result; }, }; @@ -4531,7 +4857,7 @@ fn airAddSubOverflow( fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const pt = cg.pt; - const gpa = cg.module.gpa; + const gpa = cg.gpa; const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data; const lhs = try cg.temporary(extra.lhs); @@ -4559,32 +4885,35 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const bool_ty_id = try cg.resolveType(.bool, .direct); const u32_ty_id = try cg.resolveType(.u32, .direct); - const n: usize = info.backing_bits / Module.big_int_bits; + const n: usize = info.backing_bits / big_int_bits; const ov_bool = switch (info.signedness) { .unsigned => blk: { const zero_id = try cg.constInt(.u32, @as(u32, 0)); - var any_nonzero = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpINotEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, any_nonzero); - cg.body.writeOperand(Id, high_limbs[0]); - cg.body.writeOperand(Id, zero_id); + var any_nonzero = cg.allocId(); + try cg.body.emit(gpa, .OpINotEqual, .{ + .id_result_type = bool_ty_id, + .id_result = any_nonzero, + .operand_1 = high_limbs[0], + .operand_2 = zero_id, + }); for (1..n) |i| { - const limb_nz = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpINotEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, limb_nz); - cg.body.writeOperand(Id, high_limbs[i]); - cg.body.writeOperand(Id, zero_id); + const limb_nz = cg.allocId(); + try cg.body.emit(gpa, .OpINotEqual, .{ + .id_result_type = bool_ty_id, + .id_result = limb_nz, + .operand_1 = high_limbs[i], + .operand_2 = zero_id, + }); - const combined = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpLogicalOr, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, combined); - cg.body.writeOperand(Id, any_nonzero); - cg.body.writeOperand(Id, limb_nz); + const combined = cg.allocId(); + try cg.body.emit(gpa, .OpLogicalOr, .{ + .id_result_type = bool_ty_id, + .id_result = combined, + .operand_1 = any_nonzero, + .operand_2 = limb_nz, + }); any_nonzero = combined; } @@ -4595,92 +4924,99 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const top_limb = ci_res.limbs[n - 1]; const i32_ty_id = try cg.resolveType(.i32, .direct); - const top_bits: u16 = if (info.bits % Module.big_int_bits == 0) - Module.big_int_bits + const top_bits: u16 = if (info.bits % big_int_bits == 0) + big_int_bits else - info.bits % Module.big_int_bits; + info.bits % big_int_bits; const shift_amt: u32 = top_bits - 1; const shift_id = try cg.constInt(.u32, shift_amt); - const as_signed = cg.module.allocId(); + const as_signed = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = i32_ty_id, .id_result = as_signed, .operand = top_limb, }); - const sign_ext = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpShiftRightArithmetic, 4); - cg.body.writeOperand(Id, i32_ty_id); - cg.body.writeOperand(Id, sign_ext); - cg.body.writeOperand(Id, as_signed); - cg.body.writeOperand(Id, shift_id); - const expected = cg.module.allocId(); + const sign_ext = cg.allocId(); + try cg.body.emit(gpa, .OpShiftRightArithmetic, .{ + .id_result_type = i32_ty_id, + .id_result = sign_ext, + .base = as_signed, + .shift = shift_id, + }); + const expected = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = u32_ty_id, .id_result = expected, .operand = sign_ext, }); - var any_mismatch = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpINotEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, any_mismatch); - cg.body.writeOperand(Id, high_limbs[0]); - cg.body.writeOperand(Id, expected); + var any_mismatch = cg.allocId(); + try cg.body.emit(gpa, .OpINotEqual, .{ + .id_result_type = bool_ty_id, + .id_result = any_mismatch, + .operand_1 = high_limbs[0], + .operand_2 = expected, + }); for (1..n) |i| { - const limb_ne = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpINotEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, limb_ne); - cg.body.writeOperand(Id, high_limbs[i]); - cg.body.writeOperand(Id, expected); + const limb_ne = cg.allocId(); + try cg.body.emit(gpa, .OpINotEqual, .{ + .id_result_type = bool_ty_id, + .id_result = limb_ne, + .operand_1 = high_limbs[i], + .operand_2 = expected, + }); - const combined = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpLogicalOr, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, combined); - cg.body.writeOperand(Id, any_mismatch); - cg.body.writeOperand(Id, limb_ne); + const combined = cg.allocId(); + try cg.body.emit(gpa, .OpLogicalOr, .{ + .id_result_type = bool_ty_id, + .id_result = combined, + .operand_1 = any_mismatch, + .operand_2 = limb_ne, + }); any_mismatch = combined; } if (info.bits != info.backing_bits) { - const top_bits_s: u16 = info.bits % Module.big_int_bits; + const top_bits_s: u16 = info.bits % big_int_bits; const s_shift_id = try cg.constInt(.u32, top_bits_s - 1); - const top_as_signed = cg.module.allocId(); + const top_as_signed = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = i32_ty_id, .id_result = top_as_signed, .operand = top_limb, }); - const top_sign_ext = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpShiftRightArithmetic, 4); - cg.body.writeOperand(Id, i32_ty_id); - cg.body.writeOperand(Id, top_sign_ext); - cg.body.writeOperand(Id, top_as_signed); - cg.body.writeOperand(Id, s_shift_id); - const top_expected = cg.module.allocId(); + const top_sign_ext = cg.allocId(); + try cg.body.emit(gpa, .OpShiftRightArithmetic, .{ + .id_result_type = i32_ty_id, + .id_result = top_sign_ext, + .base = top_as_signed, + .shift = s_shift_id, + }); + const top_expected = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = u32_ty_id, .id_result = top_expected, .operand = top_sign_ext, }); - const top_mismatch = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpINotEqual, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, top_mismatch); - cg.body.writeOperand(Id, top_limb); - cg.body.writeOperand(Id, top_expected); + const top_mismatch = cg.allocId(); + try cg.body.emit(gpa, .OpINotEqual, .{ + .id_result_type = bool_ty_id, + .id_result = top_mismatch, + .operand_1 = top_limb, + .operand_2 = top_expected, + }); - const combined = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpLogicalOr, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, combined); - cg.body.writeOperand(Id, any_mismatch); - cg.body.writeOperand(Id, top_mismatch); + const combined = cg.allocId(); + try cg.body.emit(gpa, .OpLogicalOr, .{ + .id_result_type = bool_ty_id, + .id_result = combined, + .operand_1 = any_mismatch, + .operand_2 = top_mismatch, + }); any_mismatch = combined; } @@ -4702,7 +5038,8 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { // - Additionally, if info.bits != 32, we'll have to check the high bits // of the result too. - const largest_int_bits = cg.largestSupportedIntBits(); + const target = cg.zcu.getTarget(); + const largest_int_bits: u16 = if (target.cpu.has(.spirv, .int64) or target.cpu.arch == .spirv64) 64 else 32; // If non-null, the number of bits that the multiplication should be performed in. If // null, we have to use wide multiplication. const maybe_op_ty_bits: ?u16 = switch (info.bits) { @@ -4846,7 +5183,7 @@ fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airShlOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data; @@ -4898,14 +5235,48 @@ fn airMulAdd(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const info = cg.arithmeticTypeInfo(result_ty); assert(info.class == .float); // .mul_add is only emitted for floats - const result = try cg.buildFma(a, b, c); + const zcu = cg.zcu; + const target = zcu.getTarget(); + + const v = cg.vectorization(.{ a, b, c }); + const ops = v.components(); + const results = cg.allocIds(ops); + + const op_result_ty = a.ty.scalarType(zcu); + const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); + const result_temp_ty = try v.resultType(cg, a.ty); + + const op_a = try v.prepare(cg, a); + const op_b = try v.prepare(cg, b); + const op_c = try v.prepare(cg, c); + + const set = try cg.importExtendedSet(); + const opcode: u32 = switch (target.os.tag) { + .opencl => @intFromEnum(spec.OpenClOpcode.fma), + // NOTE: Vulkan's FMA does not meet Zig's nor OpenCL's precision guarantees and needs + // to be emulated. + .vulkan, .opengl => @intFromEnum(spec.GlslOpcode.Fma), + else => unreachable, + }; + + for (0..ops) |i| { + try cg.body.emit(cg.gpa, .OpExtInst, .{ + .id_result_type = op_result_ty_id, + .id_result = results.at(i), + .set = set, + .instruction = .{ .inst = opcode }, + .id_ref_4 = &.{ op_a.at(i), op_b.at(i), op_c.at(i) }, + }); + } + + const result = v.finalize(result_temp_ty, results); return try result.materialize(cg); } fn airClzCtz(cg: *CodeGen, inst: Air.Inst.Index, op: UnaryOp) !?Id { if (cg.liveness.isUnused(inst)) return null; - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const operand = try cg.temporary(ty_op.operand); @@ -4948,7 +5319,7 @@ fn airSplat(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airReduce(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const reduce = cg.air.instructions.items(.data)[@intFromEnum(inst)].reduce; const operand = try cg.resolve(reduce.operand); const operand_ty = cg.typeOf(reduce.operand); @@ -5016,7 +5387,7 @@ fn airReduce(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airShuffleOne(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const gpa = zcu.gpa; const unwrapped = cg.air.unwrapShuffleOne(zcu, inst); @@ -5041,7 +5412,7 @@ fn airShuffleOne(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airShuffleTwo(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const gpa = zcu.gpa; const unwrapped = cg.air.unwrapShuffleTwo(zcu, inst); @@ -5060,7 +5431,7 @@ fn airShuffleTwo(cg: *CodeGen, inst: Air.Inst.Index) !?Id { id.* = switch (mask_elem.unwrap()) { .a_elem => |idx| try cg.extractVectorComponent(elem_ty, operand_a, idx), .b_elem => |idx| try cg.extractVectorComponent(elem_ty, operand_b, idx), - .undef => try cg.module.constUndef(elem_ty_id), + .undef => try cg.constUndef(elem_ty_id), }; } @@ -5074,8 +5445,8 @@ fn accessChainId( base: Id, indices: []const Id, ) !Id { - const result_id = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpInBoundsAccessChain, .{ + const result_id = cg.allocId(); + try cg.body.emit(cg.gpa, .OpInBoundsAccessChain, .{ .id_result_type = result_ty_id, .id_result = result_id, .base = base, @@ -5094,7 +5465,7 @@ fn accessChain( base: Id, indices: []const u32, ) !Id { - const gpa = cg.module.gpa; + const gpa = cg.gpa; const scratch_top = cg.id_scratch.items.len; defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); const ids = try cg.id_scratch.addManyAsSlice(gpa, indices.len); @@ -5111,8 +5482,8 @@ fn ptrAccessChain( element: Id, indices: []const u32, ) !Id { - const gpa = cg.module.gpa; - const target = cg.module.zcu.getTarget(); + const gpa = cg.gpa; + const target = cg.zcu.getTarget(); const scratch_top = cg.id_scratch.items.len; defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); @@ -5121,7 +5492,7 @@ fn ptrAccessChain( id.* = try cg.constInt(.u32, index); } - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); switch (target.os.tag) { .opencl, .amdhsa => { try cg.body.emit(gpa, .OpInBoundsPtrAccessChain, .{ @@ -5147,7 +5518,7 @@ fn ptrAccessChain( } fn ptrAdd(cg: *CodeGen, result_ty: Type, ptr_ty: Type, ptr_id: Id, offset_id: Id) !Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const result_ty_id = try cg.resolveType(result_ty, .direct); switch (ptr_ty.ptrSize(zcu)) { @@ -5188,8 +5559,8 @@ fn airPtrSub(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const offset_ty_id = try cg.resolveType(offset_ty, .direct); const result_ty = cg.typeOfIndex(inst); - const negative_offset_id = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpSNegate, .{ + const negative_offset_id = cg.allocId(); + try cg.body.emit(cg.gpa, .OpSNegate, .{ .id_result_type = offset_ty_id, .id_result = negative_offset_id, .operand = offset_id, @@ -5203,9 +5574,9 @@ fn cmp( lhs: Temporary, rhs: Temporary, ) !Temporary { - const gpa = cg.module.gpa; + const gpa = cg.gpa; const pt = cg.pt; - const zcu = cg.module.zcu; + const zcu = cg.zcu; const scalar_ty = lhs.ty.scalarType(zcu); const is_vector = lhs.ty.isVector(zcu); @@ -5234,14 +5605,14 @@ fn cmp( const usize_ty_id = try cg.resolveType(.usize, .direct); - const lhs_int_id = cg.module.allocId(); + const lhs_int_id = cg.allocId(); try cg.body.emit(gpa, .OpConvertPtrToU, .{ .id_result_type = usize_ty_id, .id_result = lhs_int_id, .pointer = try lhs.materialize(cg), }); - const rhs_int_id = cg.module.allocId(); + const rhs_int_id = cg.allocId(); try cg.body.emit(gpa, .OpConvertPtrToU, .{ .id_result_type = usize_ty_id, .id_result = rhs_int_id, @@ -5410,14 +5781,29 @@ fn bitCast( src_ty: Type, src_id: Id, ) !Id { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; + const gpa = cg.gpa; + const zcu = cg.zcu; const target = zcu.getTarget(); - const src_ty_id = try cg.resolveType(src_ty, .direct); + + if (src_ty.toIntern() == dst_ty.toIntern()) return src_id; + if (src_ty.isPtrAtRuntime(zcu) and dst_ty.isPtrAtRuntime(zcu)) switch (target.os.tag) { + .vulkan, .opengl => if (src_ty.ptrAddressSpace(zcu) != .physical_storage_buffer) return src_id, + else => {}, + }; + const dst_ty_id = try cg.resolveType(dst_ty, .direct); - const result_id = blk: { - if (src_ty_id == dst_ty_id) break :blk src_id; + // Big-int ↔ big-int bitcast: the indirect representation is an array, + // which OpBitcast cannot operate on. The arrays are bitwise identical + // apart from the top limb's padding; the normalize pass below fixes + // the padding. + if (src_ty.isInt(zcu) and dst_ty.isInt(zcu)) { + const src_info = src_ty.intInfo(zcu); + const dst_info = dst_ty.intInfo(zcu); + const src_backing, const src_big = cg.backingIntBits(src_info.bits); + const dst_backing, const dst_big = cg.backingIntBits(dst_info.bits); + if (src_backing == dst_backing and src_big and dst_big) break :blk src_id; + } // TODO: Some more cases are missing here // See fn bitCast in llvm.zig @@ -5432,7 +5818,7 @@ fn bitCast( } } - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); try cg.body.emit(gpa, .OpConvertUToPtr, .{ .id_result_type = dst_ty_id, .id_result = result_id, @@ -5446,7 +5832,7 @@ fn bitCast( // otherwise use a temporary and perform a pointer cast. const can_bitcast = (src_ty.isNumeric(zcu) and dst_ty.isNumeric(zcu)) or (src_ty.isPtrAtRuntime(zcu) and dst_ty.isPtrAtRuntime(zcu)); if (can_bitcast) { - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = dst_ty_id, .id_result = result_id, @@ -5456,12 +5842,24 @@ fn bitCast( break :blk result_id; } - const dst_ptr_ty_id = try cg.module.ptrType(dst_ty_id, .function); + switch (target.os.tag) { + .vulkan, .opengl => { + // Logical addressing forbids OpBitcast on pointers. Allocate + // the temp with dst_ty so the load reads through a slot of the right type. + const dst_ty_indirect_id = try cg.resolveType(dst_ty, .indirect); + const tmp_id = try cg.alloc(dst_ty_indirect_id, null); + try cg.store(dst_ty, tmp_id, src_id, .{}); + break :blk try cg.load(dst_ty, tmp_id, .{}); + }, + else => {}, + } + + const dst_ptr_ty_id = try cg.ptrType(dst_ty_id, .function); const src_ty_indirect_id = try cg.resolveType(src_ty, .indirect); const tmp_id = try cg.alloc(src_ty_indirect_id, null); try cg.store(src_ty, tmp_id, src_id, .{}); - const casted_ptr_id = cg.module.allocId(); + const casted_ptr_id = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = dst_ptr_ty_id, .id_result = casted_ptr_id, @@ -5492,8 +5890,13 @@ fn airBitCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const result = try cg.intFromBool(operand, .u1); return try result.materialize(cg); } + if (operand_ty.zigTypeTag(cg.zcu) == .pointer) { + switch (try cg.resolvePtr(ty_op.operand)) { + .tracked => |t| return t.id, // TODO + .id => |operand_id| return try cg.bitCast(result_ty, operand_ty, operand_id), + } + } const operand_id = try cg.resolve(ty_op.operand); - if (cg.virtual_allocas.contains(operand_id)) return operand_id; return try cg.bitCast(result_ty, operand_ty, operand_id); } @@ -5509,19 +5912,19 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const dst_composite = dst_info.class == .composite_integer; if (src_composite or dst_composite) { - const gpa = cg.module.gpa; + const gpa = cg.gpa; const scratch_top = cg.id_scratch.items.len; defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); if (src_composite and dst_composite) { const src_id = try src.materialize(cg); - const src_n: u16 = src_info.backing_bits / Module.big_int_bits; - const dst_n: u16 = dst_info.backing_bits / Module.big_int_bits; + const src_n: u16 = src_info.backing_bits / big_int_bits; + const dst_n: u16 = dst_info.backing_bits / big_int_bits; const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, dst_n); const min_n = @min(src_n, dst_n); const u32_ty_id = try cg.resolveType(.u32, .direct); for (0..min_n) |i| { - result_limbs[i] = cg.module.allocId(); + result_limbs[i] = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = result_limbs[i], @@ -5533,20 +5936,21 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const fill = if (src_info.signedness == .signed) blk: { const i32_ty_id = try cg.resolveType(.i32, .direct); const msb = result_limbs[src_n - 1]; - const msb_signed = cg.module.allocId(); + const msb_signed = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = i32_ty_id, .id_result = msb_signed, .operand = msb, }); const shift31 = try cg.constInt(.i32, @as(i32, 31)); - const sign_ext = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpShiftRightArithmetic, 4); - cg.body.writeOperand(Id, i32_ty_id); - cg.body.writeOperand(Id, sign_ext); - cg.body.writeOperand(Id, msb_signed); - cg.body.writeOperand(Id, shift31); - const back = cg.module.allocId(); + const sign_ext = cg.allocId(); + try cg.body.emit(gpa, .OpShiftRightArithmetic, .{ + .id_result_type = i32_ty_id, + .id_result = sign_ext, + .base = msb_signed, + .shift = shift31, + }); + const back = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = u32_ty_id, .id_result = back, @@ -5565,7 +5969,7 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const src_id = try src.materialize(cg); const u32_ty_id = try cg.resolveType(.u32, .direct); if (dst_info.backing_bits <= 32) { - const limb0 = cg.module.allocId(); + const limb0 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = limb0, @@ -5580,14 +5984,14 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { converted; return try result.materialize(cg); } else { - const limb0 = cg.module.allocId(); + const limb0 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = limb0, .composite = src_id, .indexes = &.{@as(u32, 0)}, }); - const limb1 = cg.module.allocId(); + const limb1 = cg.allocId(); try cg.body.emit(gpa, .OpCompositeExtract, .{ .id_result_type = u32_ty_id, .id_result = limb1, @@ -5595,29 +5999,33 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { .indexes = &.{@as(u32, 1)}, }); const u64_ty_id = try cg.resolveType(.u64, .direct); - const lo = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpUConvert, 3); - cg.body.writeOperand(Id, u64_ty_id); - cg.body.writeOperand(Id, lo); - cg.body.writeOperand(Id, limb0); - const hi = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpUConvert, 3); - cg.body.writeOperand(Id, u64_ty_id); - cg.body.writeOperand(Id, hi); - cg.body.writeOperand(Id, limb1); + const lo = cg.allocId(); + try cg.body.emit(gpa, .OpUConvert, .{ + .id_result_type = u64_ty_id, + .id_result = lo, + .unsigned_value = limb0, + }); + const hi = cg.allocId(); + try cg.body.emit(gpa, .OpUConvert, .{ + .id_result_type = u64_ty_id, + .id_result = hi, + .unsigned_value = limb1, + }); const shift32 = try cg.constInt(.u64, @as(u64, 32)); - const hi_shifted = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpShiftLeftLogical, 4); - cg.body.writeOperand(Id, u64_ty_id); - cg.body.writeOperand(Id, hi_shifted); - cg.body.writeOperand(Id, hi); - cg.body.writeOperand(Id, shift32); - const combined = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpBitwiseOr, 4); - cg.body.writeOperand(Id, u64_ty_id); - cg.body.writeOperand(Id, combined); - cg.body.writeOperand(Id, lo); - cg.body.writeOperand(Id, hi_shifted); + const hi_shifted = cg.allocId(); + try cg.body.emit(gpa, .OpShiftLeftLogical, .{ + .id_result_type = u64_ty_id, + .id_result = hi_shifted, + .base = hi, + .shift = shift32, + }); + const combined = cg.allocId(); + try cg.body.emit(gpa, .OpBitwiseOr, .{ + .id_result_type = u64_ty_id, + .id_result = combined, + .operand_1 = lo, + .operand_2 = hi_shifted, + }); const tmp: Temporary = .init(.u64, combined); const converted = try cg.buildConvert(dst_ty, tmp); const result = if (dst_info.bits < src_info.bits) @@ -5627,7 +6035,7 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { return try result.materialize(cg); } } else { - const dst_n: u16 = dst_info.backing_bits / Module.big_int_bits; + const dst_n: u16 = dst_info.backing_bits / big_int_bits; const result_limbs = try cg.id_scratch.addManyAsSlice(gpa, dst_n); const u32_ty_id = try cg.resolveType(.u32, .direct); @@ -5637,44 +6045,48 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } else { const src_as_u64 = try cg.buildConvert(.u64, src); const src_id = try src_as_u64.materialize(cg); - result_limbs[0] = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpUConvert, 3); - cg.body.writeOperand(Id, u32_ty_id); - cg.body.writeOperand(Id, result_limbs[0]); - cg.body.writeOperand(Id, src_id); + result_limbs[0] = cg.allocId(); + try cg.body.emit(gpa, .OpUConvert, .{ + .id_result_type = u32_ty_id, + .id_result = result_limbs[0], + .unsigned_value = src_id, + }); const u64_ty_id = try cg.resolveType(.u64, .direct); const shift32 = try cg.constInt(.u64, @as(u64, 32)); - const hi = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpShiftRightLogical, 4); - cg.body.writeOperand(Id, u64_ty_id); - cg.body.writeOperand(Id, hi); - cg.body.writeOperand(Id, src_id); - cg.body.writeOperand(Id, shift32); - result_limbs[1] = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpUConvert, 3); - cg.body.writeOperand(Id, u32_ty_id); - cg.body.writeOperand(Id, result_limbs[1]); - cg.body.writeOperand(Id, hi); + const hi = cg.allocId(); + try cg.body.emit(gpa, .OpShiftRightLogical, .{ + .id_result_type = u64_ty_id, + .id_result = hi, + .base = src_id, + .shift = shift32, + }); + result_limbs[1] = cg.allocId(); + try cg.body.emit(gpa, .OpUConvert, .{ + .id_result_type = u32_ty_id, + .id_result = result_limbs[1], + .unsigned_value = hi, + }); } // Sign/zero-extend remaining limbs. const fill_start: u16 = if (src_info.backing_bits <= 32) 1 else 2; const fill = if (src_info.signedness == .signed) blk: { const i32_ty_id = try cg.resolveType(.i32, .direct); const msb = result_limbs[fill_start - 1]; - const msb_signed = cg.module.allocId(); + const msb_signed = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = i32_ty_id, .id_result = msb_signed, .operand = msb, }); const shift31 = try cg.constInt(.i32, @as(i32, 31)); - const sign_ext = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpShiftRightArithmetic, 4); - cg.body.writeOperand(Id, i32_ty_id); - cg.body.writeOperand(Id, sign_ext); - cg.body.writeOperand(Id, msb_signed); - cg.body.writeOperand(Id, shift31); - const back = cg.module.allocId(); + const sign_ext = cg.allocId(); + try cg.body.emit(gpa, .OpShiftRightArithmetic, .{ + .id_result_type = i32_ty_id, + .id_result = sign_ext, + .base = msb_signed, + .shift = shift31, + }); + const back = cg.allocId(); try cg.body.emit(gpa, .OpBitcast, .{ .id_result_type = u32_ty_id, .id_result = back, @@ -5715,8 +6127,8 @@ fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { fn intFromPtr(cg: *CodeGen, operand_id: Id) !Id { const result_type_id = try cg.resolveType(.usize, .direct); - const result_id = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpConvertPtrToU, .{ + const result_id = cg.allocId(); + try cg.body.emit(cg.gpa, .OpConvertPtrToU, .{ .id_result_type = result_type_id, .id_result = result_id, .pointer = operand_id, @@ -5725,17 +6137,13 @@ fn intFromPtr(cg: *CodeGen, operand_id: Id) !Id { } fn airFloatFromInt(cg: *CodeGen, inst: Air.Inst.Index) !?Id { + const gpa = cg.gpa; const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const operand_ty = cg.typeOf(ty_op.operand); const operand_id = try cg.resolve(ty_op.operand); const result_ty = cg.typeOfIndex(inst); - return try cg.floatFromInt(result_ty, operand_ty, operand_id); -} - -fn floatFromInt(cg: *CodeGen, result_ty: Type, operand_ty: Type, operand_id: Id) !Id { - const gpa = cg.module.gpa; const operand_info = cg.arithmeticTypeInfo(operand_ty); - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); const result_ty_id = try cg.resolveType(result_ty, .direct); switch (operand_info.signedness) { .signed => try cg.body.emit(gpa, .OpConvertSToF, .{ @@ -5753,17 +6161,13 @@ fn floatFromInt(cg: *CodeGen, result_ty: Type, operand_ty: Type, operand_id: Id) } fn airIntFromFloat(cg: *CodeGen, inst: Air.Inst.Index) !?Id { + const gpa = cg.gpa; const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const operand_id = try cg.resolve(ty_op.operand); const result_ty = cg.typeOfIndex(inst); - return try cg.intFromFloat(result_ty, operand_id); -} - -fn intFromFloat(cg: *CodeGen, result_ty: Type, operand_id: Id) !Id { - const gpa = cg.module.gpa; const result_info = cg.arithmeticTypeInfo(result_ty); const result_ty_id = try cg.resolveType(result_ty, .direct); - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); switch (result_info.signedness) { .signed => try cg.body.emit(gpa, .OpConvertFToS, .{ .id_result_type = result_ty_id, @@ -5815,7 +6219,7 @@ fn airNot(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airArrayToSlice(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const array_ptr_ty = cg.typeOf(ty_op.operand); const array_ty = array_ptr_ty.childType(zcu); @@ -5849,11 +6253,11 @@ fn airSlice(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airAggregateInit(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const gpa = cg.module.gpa; + const gpa = cg.gpa; const pt = cg.pt; - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ip = &zcu.intern_pool; - const target = cg.module.zcu.getTarget(); + const target = cg.zcu.getTarget(); const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const result_ty = cg.typeOfIndex(inst); const len: usize = @intCast(result_ty.arrayLen(zcu)); @@ -5978,23 +6382,8 @@ fn airAggregateInit(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } } -fn sliceOrArrayLen(cg: *CodeGen, operand_id: Id, ty: Type) !Id { - const zcu = cg.module.zcu; - switch (ty.ptrSize(zcu)) { - .slice => return cg.extractField(.usize, operand_id, 1), - .one => { - const array_ty = ty.childType(zcu); - const elem_ty = array_ty.childType(zcu); - const abi_size = elem_ty.abiSize(zcu); - const size = array_ty.arrayLenIncludingSentinel(zcu) * abi_size; - return try cg.constInt(.usize, size); - }, - .many, .c => unreachable, - } -} - fn sliceOrArrayPtr(cg: *CodeGen, operand_id: Id, ty: Type) !Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; if (ty.isSlice(zcu)) { const ptr_ty = ty.slicePtrFieldType(zcu); return cg.extractField(ptr_ty, operand_id, 0); @@ -6010,8 +6399,17 @@ fn airMemcpy(cg: *CodeGen, inst: Air.Inst.Index) !void { const src_ty = cg.typeOf(bin_op.rhs); const dest_ptr = try cg.sliceOrArrayPtr(dest_slice, dest_ty); const src_ptr = try cg.sliceOrArrayPtr(src_slice, src_ty); - const len = try cg.sliceOrArrayLen(dest_slice, dest_ty); - try cg.body.emit(cg.module.gpa, .OpCopyMemorySized, .{ + const len = switch (dest_ty.ptrSize(cg.zcu)) { + .slice => try cg.extractField(.usize, dest_slice, 1), + .one => len: { + const array_ty = dest_ty.childType(cg.zcu); + const elem_ty = array_ty.childType(cg.zcu); + const size = array_ty.arrayLenIncludingSentinel(cg.zcu) * elem_ty.abiSize(cg.zcu); + break :len try cg.constInt(.usize, size); + }, + .many, .c => unreachable, + }; + try cg.body.emit(cg.gpa, .OpCopyMemorySized, .{ .target = dest_ptr, .source = src_ptr, .size = len, @@ -6031,12 +6429,12 @@ fn airSliceField(cg: *CodeGen, inst: Air.Inst.Index, field: u32) !?Id { } fn airSpirvRuntimeArrayLen(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const gpa = cg.module.gpa; + const gpa = cg.gpa; const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = cg.air.extraData(Air.StructField, ty_pl.payload).data; const struct_ptr_id = try cg.resolve(extra.struct_operand); - const u32_ty_id = try cg.module.intType(.unsigned, 32); - const result_id = cg.module.allocId(); + const u32_ty_id = try cg.intType(.unsigned, 32); + const result_id = cg.allocId(); try cg.body.emit(gpa, .OpArrayLength, .{ .id_result_type = u32_ty_id, .id_result = result_id, @@ -6047,7 +6445,7 @@ fn airSpirvRuntimeArrayLen(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airSliceElemPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const bin_op = cg.air.extraData(Air.Bin, ty_pl.payload).data; const slice_ty = cg.typeOf(bin_op.lhs); @@ -6064,7 +6462,7 @@ fn airSliceElemPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airSliceElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const slice_ty = cg.typeOf(bin_op.lhs); if (!slice_ty.isVolatilePtr(zcu) and cg.liveness.isUnused(inst)) return null; @@ -6081,11 +6479,11 @@ fn airSliceElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn ptrElemPtr(cg: *CodeGen, ptr_ty: Type, ptr_id: Id, index_id: Id) !Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; // Construct new pointer type for the resulting pointer const elem_ty = ptr_ty.indexableElem(zcu); const elem_ty_id = try cg.resolveType(elem_ty, .indirect); - const elem_ptr_ty_id = try cg.module.ptrType(elem_ty_id, cg.module.storageClass(ptr_ty.ptrAddressSpace(zcu))); + const elem_ptr_ty_id = try cg.ptrType(elem_ty_id, cg.storageClass(ptr_ty.ptrAddressSpace(zcu))); if (ptr_ty.isSinglePointer(zcu)) { // Pointer-to-array. In this case, the resulting pointer is not of the same type // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain. @@ -6097,7 +6495,7 @@ fn ptrElemPtr(cg: *CodeGen, ptr_ty: Type, ptr_id: Id, index_id: Id) !Id { } fn airPtrElemPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const bin_op = cg.air.extraData(Air.Bin, ty_pl.payload).data; const src_ptr_ty = cg.typeOf(bin_op.lhs); @@ -6111,8 +6509,8 @@ fn airPtrElemPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airArrayElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; + const gpa = cg.gpa; + const zcu = cg.zcu; const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const array_ty = cg.typeOf(bin_op.lhs); const elem_ty = array_ty.childType(zcu); @@ -6127,10 +6525,10 @@ fn airArrayElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const elem_repr: Repr = if (is_vector) .direct else .indirect; const array_ty_id = try cg.resolveType(array_ty, .direct); const elem_ty_id = try cg.resolveType(elem_ty, elem_repr); - const ptr_array_ty_id = try cg.module.ptrType(array_ty_id, .function); - const ptr_elem_ty_id = try cg.module.ptrType(elem_ty_id, .function); + const ptr_array_ty_id = try cg.ptrType(array_ty_id, .function); + const ptr_elem_ty_id = try cg.ptrType(elem_ty_id, .function); - const tmp_id = cg.module.allocId(); + const tmp_id = cg.allocId(); try cg.prologue.emit(gpa, .OpVariable, .{ .id_result_type = ptr_array_ty_id, .id_result = tmp_id, @@ -6144,7 +6542,7 @@ fn airArrayElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const elem_ptr_id = try cg.accessChainId(ptr_elem_ty_id, tmp_id, &.{index_id}); - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); try cg.body.emit(gpa, .OpLoad, .{ .id_result_type = try cg.resolveType(elem_ty, elem_repr), .id_result = result_id, @@ -6163,7 +6561,7 @@ fn airArrayElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airPtrElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const ptr_ty = cg.typeOf(bin_op.lhs); const elem_ty = cg.typeOfIndex(inst); @@ -6174,7 +6572,7 @@ fn airPtrElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airSetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !void { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const un_ptr_ty = cg.typeOf(bin_op.lhs); const un_ty = un_ptr_ty.childType(zcu); @@ -6184,7 +6582,7 @@ fn airSetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !void { const tag_ty = un_ty.unionTagTypeRuntime(zcu).?; const tag_ty_id = try cg.resolveType(tag_ty, .indirect); - const tag_ptr_ty_id = try cg.module.ptrType(tag_ty_id, cg.module.storageClass(un_ptr_ty.ptrAddressSpace(zcu))); + const tag_ptr_ty_id = try cg.ptrType(tag_ty_id, cg.storageClass(un_ptr_ty.ptrAddressSpace(zcu))); const union_ptr_id = try cg.resolve(bin_op.lhs); const new_tag_id = try cg.resolve(bin_op.rhs); @@ -6201,7 +6599,7 @@ fn airGetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const un_ty = cg.typeOf(ty_op.operand); - const zcu = cg.module.zcu; + const zcu = cg.zcu; const layout = cg.unionLayout(un_ty); if (layout.tag_size == 0) return null; @@ -6225,7 +6623,7 @@ fn unionInit( // Note: The result here is not cached, because it generates runtime code. const pt = cg.pt; - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ip = &zcu.intern_pool; const union_ty = zcu.typeToUnion(ty).?; const tag_ty: Type = .fromInterned(union_ty.enum_tag_type); @@ -6250,7 +6648,7 @@ fn unionInit( if (layout.tag_size != 0) { const tag_ty_id = try cg.resolveType(tag_ty, .indirect); - const tag_ptr_ty_id = try cg.module.ptrType(tag_ty_id, .function); + const tag_ptr_ty_id = try cg.ptrType(tag_ty_id, .function); const ptr_id = try cg.accessChain(tag_ptr_ty_id, tmp_id, &.{@as(u32, @intCast(layout.tag_index))}); const tag_id = try cg.constInt(tag_ty, tag_int); try cg.store(tag_ty, ptr_id, tag_id, .{}); @@ -6258,13 +6656,13 @@ fn unionInit( if (payload_ty.hasRuntimeBits(zcu)) { const layout_payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect); - const pl_ptr_ty_id = try cg.module.ptrType(layout_payload_ty_id, .function); + const pl_ptr_ty_id = try cg.ptrType(layout_payload_ty_id, .function); const pl_ptr_id = try cg.accessChain(pl_ptr_ty_id, tmp_id, &.{layout.payload_index}); const active_pl_ptr_id = if (!layout.payload_ty.eql(payload_ty)) blk: { const payload_ty_id = try cg.resolveType(payload_ty, .indirect); - const active_pl_ptr_ty_id = try cg.module.ptrType(payload_ty_id, .function); - const active_pl_ptr_id = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpBitcast, .{ + const active_pl_ptr_ty_id = try cg.ptrType(payload_ty_id, .function); + const active_pl_ptr_id = cg.allocId(); + try cg.body.emit(cg.gpa, .OpBitcast, .{ .id_result_type = active_pl_ptr_ty_id, .id_result = active_pl_ptr_id, .operand = pl_ptr_id, @@ -6284,7 +6682,7 @@ fn unionInit( } fn airUnionInit(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ip = &zcu.intern_pool; const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = cg.air.extraData(Air.UnionInit, ty_pl.payload).data; @@ -6301,7 +6699,7 @@ fn airUnionInit(cg: *CodeGen, inst: Air.Inst.Index) !?Id { fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const pt = cg.pt; - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const struct_field = cg.air.extraData(Air.StructField, ty_pl.payload).data; @@ -6316,7 +6714,7 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { .@"struct" => switch (object_ty.containerLayout(zcu)) { .@"packed" => { const struct_ty = zcu.typeToPackedStruct(object_ty).?; - const struct_backing_int_bits = cg.module.backingIntBits(@intCast(object_ty.bitSize(zcu))).@"0"; + const struct_backing_int_bits = cg.backingIntBits(@intCast(object_ty.bitSize(zcu))).@"0"; const bit_offset = zcu.structPackedFieldBitOffset(struct_ty, field_index); // We use the same int type the packed struct is backed by, because even though it would // be valid SPIR-V to use an smaller type like u16, some implementations like PoCL will complain. @@ -6329,7 +6727,7 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const mask_id = try cg.constInt(object_ty, (@as(u64, 1) << @as(u6, @intCast(field_bit_size))) - 1); const masked = try cg.buildBinary(.OpBitwiseAnd, shift, .{ .ty = object_ty, .value = .{ .singleton = mask_id } }); const result_id = blk: { - if (cg.module.backingIntBits(field_bit_size).@"0" == struct_backing_int_bits) + if (cg.backingIntBits(field_bit_size).@"0" == struct_backing_int_bits) break :blk try cg.bitCast(field_int_ty, object_ty, try masked.materialize(cg)); const trunc = try cg.buildConvert(field_int_ty, masked); break :blk try trunc.materialize(cg); @@ -6353,7 +6751,7 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { .{ .ty = backing_int_ty, .value = .{ .singleton = mask_id } }, ); const result_id = blk: { - if (cg.module.backingIntBits(field_bit_size).@"0" == cg.module.backingIntBits(@intCast(backing_int_ty.bitSize(zcu))).@"0") + if (cg.backingIntBits(field_bit_size).@"0" == cg.backingIntBits(@intCast(backing_int_ty.bitSize(zcu))).@"0") break :blk try cg.bitCast(int_ty, backing_int_ty, try masked.materialize(cg)); const trunc = try cg.buildConvert(int_ty, masked); break :blk try trunc.materialize(cg); @@ -6372,13 +6770,13 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { try cg.store(object_ty, tmp_id, object_id, .{}); const layout_payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect); - const pl_ptr_ty_id = try cg.module.ptrType(layout_payload_ty_id, .function); + const pl_ptr_ty_id = try cg.ptrType(layout_payload_ty_id, .function); const pl_ptr_id = try cg.accessChain(pl_ptr_ty_id, tmp_id, &.{layout.payload_index}); const field_ty_id = try cg.resolveType(field_ty, .indirect); - const active_pl_ptr_ty_id = try cg.module.ptrType(field_ty_id, .function); - const active_pl_ptr_id = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpBitcast, .{ + const active_pl_ptr_ty_id = try cg.ptrType(field_ty_id, .function); + const active_pl_ptr_id = cg.allocId(); + try cg.body.emit(cg.gpa, .OpBitcast, .{ .id_result_type = active_pl_ptr_ty_id, .id_result = active_pl_ptr_id, .operand = pl_ptr_id, @@ -6391,7 +6789,7 @@ fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airFieldParentPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const target = zcu.getTarget(); const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = cg.air.extraData(Air.FieldParentPtr, ty_pl.payload).data; @@ -6424,8 +6822,8 @@ fn airFieldParentPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } } - const base_ptr = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpConvertUToPtr, .{ + const base_ptr = cg.allocId(); + try cg.body.emit(cg.gpa, .OpConvertUToPtr, .{ .id_result_type = result_ty_id, .id_result = base_ptr, .integer_value = base_ptr_int, @@ -6443,7 +6841,7 @@ fn structFieldPtr( ) !Id { const result_ty_id = try cg.resolveType(result_ptr_ty, .direct); - const zcu = cg.module.zcu; + const zcu = cg.zcu; const object_ty = object_ptr_ty.childType(zcu); switch (object_ty.zigTypeTag(zcu)) { .pointer => { @@ -6455,8 +6853,8 @@ fn structFieldPtr( const byte_offset = codegen.fieldOffset(object_ptr_ty, result_ptr_ty, field_index, zcu); if (byte_offset == 0) return object_ptr; const usize_ty_id = try cg.resolveType(.usize, .direct); - const base_int = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpConvertPtrToU, .{ + const base_int = cg.allocId(); + try cg.body.emit(cg.gpa, .OpConvertPtrToU, .{ .id_result_type = usize_ty_id, .id_result = base_int, .pointer = object_ptr, @@ -6464,8 +6862,8 @@ fn structFieldPtr( const offset_id = try cg.constInt(.usize, byte_offset); const adjusted = try cg.buildBinary(.OpIAdd, .{ .ty = .usize, .value = .{ .singleton = base_int } }, .{ .ty = .usize, .value = .{ .singleton = offset_id } }); const adjusted_id = try adjusted.materialize(cg); - const result_id = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpConvertUToPtr, .{ + const result_id = cg.allocId(); + try cg.body.emit(cg.gpa, .OpConvertUToPtr, .{ .id_result_type = result_ty_id, .id_result = result_id, .integer_value = adjusted_id, @@ -6483,19 +6881,19 @@ fn structFieldPtr( if (!layout.has_payload) { // Asked to get a pointer to a zero-sized field. Just lower this // to undefined, there is no reason to make it be a valid pointer. - return try cg.module.constUndef(result_ty_id); + return try cg.constUndef(result_ty_id); } - const storage_class = cg.module.storageClass(object_ptr_ty.ptrAddressSpace(zcu)); + const storage_class = cg.storageClass(object_ptr_ty.ptrAddressSpace(zcu)); const layout_payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect); - const pl_ptr_ty_id = try cg.module.ptrType(layout_payload_ty_id, storage_class); + const pl_ptr_ty_id = try cg.ptrType(layout_payload_ty_id, storage_class); const pl_ptr_id = blk: { if (object_ty.containerLayout(zcu) == .@"packed") break :blk object_ptr; break :blk try cg.accessChain(pl_ptr_ty_id, object_ptr, &.{layout.payload_index}); }; - const active_pl_ptr_id = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpBitcast, .{ + const active_pl_ptr_id = cg.allocId(); + try cg.body.emit(cg.gpa, .OpBitcast, .{ .id_result_type = result_ty_id, .id_result = active_pl_ptr_id, .operand = pl_ptr_id, @@ -6525,9 +6923,9 @@ fn airStructFieldPtrIndex(cg: *CodeGen, inst: Air.Inst.Index, field_index: u32) } fn alloc(cg: *CodeGen, ty_id: Id, initializer: ?Id) !Id { - const ptr_ty_id = try cg.module.ptrType(ty_id, .function); - const result_id = cg.module.allocId(); - try cg.prologue.emit(cg.module.gpa, .OpVariable, .{ + const ptr_ty_id = try cg.ptrType(ty_id, .function); + const result_id = cg.allocId(); + try cg.prologue.emit(cg.gpa, .OpVariable, .{ .id_result_type = ptr_ty_id, .id_result = result_id, .storage_class = .function, @@ -6537,7 +6935,7 @@ fn alloc(cg: *CodeGen, ty_id: Id, initializer: ?Id) !Id { } fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const target = zcu.getTarget(); const ptr_ty = cg.typeOfIndex(inst); const child_ty = ptr_ty.childType(zcu); @@ -6546,9 +6944,9 @@ fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) !?Id { .vulkan, .opengl => { if (child_ty.zigTypeTag(zcu) == .pointer and !child_ty.isSlice(zcu)) { const as = child_ty.ptrAddressSpace(zcu); - if (cg.module.storageClass(as) == .function) { - const result_id = cg.module.allocId(); - try cg.virtual_allocas.put(cg.module.gpa, result_id, null); + if (cg.storageClass(as) == .function) { + const result_id = cg.allocId(); + try cg.tracked_allocas.put(cg.gpa, result_id, null); return result_id; } } @@ -6561,7 +6959,7 @@ fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const result_id = try cg.alloc(child_ty_id, null); if (ptr_align != child_ty.abiAlignment(zcu)) { if (target.os.tag != .opencl) return cg.fail("cannot apply alignment to variables", .{}); - try cg.module.decorate(result_id, .{ + try cg.decorate(result_id, .{ .alignment = .{ .alignment = @intCast(ptr_align.toByteUnits().?) }, }); } @@ -6578,9 +6976,9 @@ fn airArg(cg: *CodeGen) Id { /// inside the merge block of the block. /// This function should only be called with structured control flow generation. fn structuredNextBlock(cg: *CodeGen, incoming: []const Block.Incoming) !Id { - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); const block_id_ty_id = try cg.resolveType(.u32, .direct); - try cg.body.emitRaw(cg.module.gpa, .OpPhi, @intCast(2 + incoming.len * 2)); // result type + result + variable/parent... + try cg.body.emitRaw(cg.gpa, .OpPhi, @intCast(2 + incoming.len * 2)); // result type + result + variable/parent... cg.body.writeOperand(Id, block_id_ty_id); cg.body.writeOperand(Id, result_id); @@ -6597,11 +6995,11 @@ fn structuredNextBlock(cg: *CodeGen, incoming: []const Block.Incoming) !Id { fn structuredBreak(cg: *CodeGen, target_block: Id) !void { if (cg.block_terminated) return; - const gpa = cg.module.gpa; + const gpa = cg.gpa; const sblock = cg.block_stack.getLast().?; const merge_block = switch (sblock.*) { .selection => |*merge| blk: { - const merge_label = cg.module.allocId(); + const merge_label = cg.allocId(); try merge.merge_stack.append(gpa, .{ .incoming = .{ .src_label = cg.block_label, @@ -6641,7 +7039,7 @@ fn genStructuredBody( }, body: []const Air.Inst.Index, ) !Id { - const gpa = cg.module.gpa; + const gpa = cg.gpa; var sblock: Block = switch (block_merge_type) { .loop => |merge| .{ .loop = .{ @@ -6679,9 +7077,9 @@ fn genStructuredBody( // Make sure that we are still in a block when exiting the function. // TODO: Can we get rid of that? - try cg.beginSpvBlock(cg.module.allocId()); + try cg.beginSpvBlock(cg.allocId()); const block_id_ty_id = try cg.resolveType(.u32, .direct); - return try cg.module.constUndef(block_id_ty_id); + return try cg.constUndef(block_id_ty_id); } // The top-most merge actually only has a single source, the @@ -6735,8 +7133,8 @@ fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, body: []const Air.Inst.Index) // of the block, then a label, and then generate the rest of the current // ir.Block in a different SPIR-V block. - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; + const gpa = cg.gpa; + const zcu = cg.zcu; const ty = cg.typeOfIndex(inst); const have_block_result = ty.hasRuntimeBits(zcu); @@ -6756,7 +7154,7 @@ fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, body: []const Air.Inst.Index) // Check if the target of the branch was this current block. const this_block = try cg.constInt(.u32, @intFromEnum(inst)); - const jump_to_this_block_id = cg.module.allocId(); + const jump_to_this_block_id = cg.allocId(); const bool_ty_id = try cg.resolveType(.bool, .direct); try cg.body.emit(gpa, .OpIEqual, .{ .id_result_type = bool_ty_id, @@ -6776,8 +7174,8 @@ fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, body: []const Air.Inst.Index) .selection => |*merge| { // To jump out of a selection block, push a new entry onto its merge stack and // generate a conditional branch to there and to the instructions following this block. - const merge_label = cg.module.allocId(); - const then_label = cg.module.allocId(); + const merge_label = cg.allocId(); + const then_label = cg.allocId(); try cg.body.emit(gpa, .OpSelectionMerge, .{ .merge_block = merge_label, .selection_control = .{}, @@ -6800,7 +7198,7 @@ fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, body: []const Air.Inst.Index) .loop => |*merge| { // To jump out of a loop block, generate a conditional that exits the block // to the loop merge if the target ID is not the one of this block. - const continue_label = cg.module.allocId(); + const continue_label = cg.allocId(); try cg.body.emit(gpa, .OpBranchConditional, .{ .condition = jump_to_this_block_id, .true_label = continue_label, @@ -6823,7 +7221,7 @@ fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, body: []const Air.Inst.Index) } fn airBr(cg: *CodeGen, inst: Air.Inst.Index) !void { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const br = cg.air.instructions.items(.data)[@intFromEnum(inst)].br; const operand_ty = cg.typeOf(br.operand); @@ -6838,16 +7236,16 @@ fn airBr(cg: *CodeGen, inst: Air.Inst.Index) !void { } fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) !void { - const gpa = cg.module.gpa; + const gpa = cg.gpa; const cond_br = cg.air.unwrapCondBr(inst); const then_body = cond_br.then_body; const else_body = cond_br.else_body; const condition_id = try cg.resolve(cond_br.condition); - const then_label = cg.module.allocId(); - const else_label = cg.module.allocId(); + const then_label = cg.allocId(); + const else_label = cg.allocId(); - const merge_label = cg.module.allocId(); + const merge_label = cg.allocId(); try cg.body.emit(gpa, .OpSelectionMerge, .{ .merge_block = merge_label, @@ -6888,14 +7286,14 @@ fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) !void { } fn airLoop(cg: *CodeGen, inst: Air.Inst.Index) !void { - const gpa = cg.module.gpa; + const gpa = cg.gpa; const block = cg.air.unwrapBlock(inst); - const body_label = cg.module.allocId(); + const body_label = cg.allocId(); - const header_label = cg.module.allocId(); - const merge_label = cg.module.allocId(); - const continue_label = cg.module.allocId(); + const header_label = cg.allocId(); + const merge_label = cg.allocId(); + const continue_label = cg.allocId(); // The back-edge must point to the loop header, so generate a separate block for the // loop header so that we don't accidentally include some instructions from there @@ -6927,18 +7325,19 @@ fn airLoop(cg: *CodeGen, inst: Air.Inst.Index) !void { } fn airLoad(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const pt = cg.pt; const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; - - const ptr_info = cg.typeOf(ty_op.operand).ptrInfo(zcu); - + const ptr_ty = cg.typeOf(ty_op.operand); + const ptr_info = ptr_ty.ptrInfo(zcu); const elem_ty = cg.typeOfIndex(inst); - const operand_ptr_id = try cg.resolve(ty_op.operand); - + const ptr = try cg.resolvePtr(ty_op.operand); assert(ptr_info.child == elem_ty.toIntern()); - if (cg.virtual_allocas.get(operand_ptr_id)) |stored| return stored.?; + const operand_ptr_id = switch (ptr) { + .tracked => |t| return t.slot.*.?, + .id => |id| id, + }; if (ptr_info.packed_offset.host_size != 0 and ptr_info.flags.vector_index == .none) @@ -6955,7 +7354,7 @@ fn airLoad(cg: *CodeGen, inst: Air.Inst.Index) !?Id { break :blk try shifted.materialize(cg); } else host_val; const result_id = blk: { - if (cg.module.backingIntBits(elem_bit_size).@"0" == cg.module.backingIntBits(host_bits).@"0") + if (cg.backingIntBits(elem_bit_size).@"0" == cg.backingIntBits(host_bits).@"0") break :blk try cg.bitCast(field_int_ty, host_int_ty, narrowed); const trunc = try cg.buildConvert(field_int_ty, .{ .ty = host_int_ty, .value = .{ .singleton = narrowed } }); break :blk try trunc.materialize(cg); @@ -6968,9 +7367,9 @@ fn airLoad(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const ptr_id = switch (ptr_info.flags.vector_index) { .none => operand_ptr_id, else => |index| ptr_id: { - const elem_ptr_ty_id = try cg.module.ptrType( + const elem_ptr_ty_id = try cg.ptrType( try cg.resolveType(elem_ty, .indirect), - cg.module.storageClass(ptr_info.flags.address_space), + cg.storageClass(ptr_info.flags.address_space), ); break :ptr_id try cg.accessChain(elem_ptr_ty_id, operand_ptr_id, &.{@intFromEnum(index)}); }, @@ -6979,18 +7378,20 @@ fn airLoad(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airStore(cg: *CodeGen, inst: Air.Inst.Index) !void { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const pt = cg.pt; const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; - const ptr_info = cg.typeOf(bin_op.lhs).ptrInfo(zcu); + const ptr_ty = cg.typeOf(bin_op.lhs); + const ptr_info = ptr_ty.ptrInfo(zcu); const elem_ty: Type = .fromInterned(ptr_info.child); - const operand_ptr_id = try cg.resolve(bin_op.lhs); const value_id = try cg.resolve(bin_op.rhs); - - if (cg.virtual_allocas.getPtr(operand_ptr_id)) |slot| { - slot.* = value_id; - return; - } + const operand_ptr_id = switch (try cg.resolvePtr(bin_op.lhs)) { + .tracked => |t| { + t.slot.* = value_id; + return; + }, + .id => |id| id, + }; if (ptr_info.packed_offset.host_size != 0 and ptr_info.flags.vector_index == .none) @@ -7013,7 +7414,7 @@ fn airStore(cg: *CodeGen, inst: Air.Inst.Index) !void { } const extended = blk: { - if (cg.module.backingIntBits(elem_bit_size).@"0" == cg.module.backingIntBits(host_bits).@"0") + if (cg.backingIntBits(elem_bit_size).@"0" == cg.backingIntBits(host_bits).@"0") break :blk try cg.bitCast(host_int_ty, field_int_ty, value_as_int); const conv = try cg.buildConvert(host_int_ty, .{ .ty = field_int_ty, .value = .{ .singleton = value_as_int } }); break :blk try conv.materialize(cg); @@ -7037,9 +7438,9 @@ fn airStore(cg: *CodeGen, inst: Air.Inst.Index) !void { const ptr_id = switch (ptr_info.flags.vector_index) { .none => operand_ptr_id, else => |index| ptr_id: { - const elem_ptr_ty_id = try cg.module.ptrType( + const elem_ptr_ty_id = try cg.ptrType( try cg.resolveType(elem_ty, .indirect), - cg.module.storageClass(ptr_info.flags.address_space), + cg.storageClass(ptr_info.flags.address_space), ); break :ptr_id try cg.accessChain(elem_ptr_ty_id, operand_ptr_id, &.{@intFromEnum(index)}); }, @@ -7049,8 +7450,8 @@ fn airStore(cg: *CodeGen, inst: Air.Inst.Index) !void { } fn airRet(cg: *CodeGen, inst: Air.Inst.Index) !void { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; + const gpa = cg.gpa; + const zcu = cg.zcu; const operand = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const ret_ty = cg.typeOf(operand); if (!ret_ty.hasRuntimeBits(zcu)) { @@ -7071,8 +7472,8 @@ fn airRet(cg: *CodeGen, inst: Air.Inst.Index) !void { } fn airRetLoad(cg: *CodeGen, inst: Air.Inst.Index) !void { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; + const gpa = cg.gpa; + const zcu = cg.zcu; const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const ptr_ty = cg.typeOf(un_op); const ret_ty = ptr_ty.childType(zcu); @@ -7090,16 +7491,18 @@ fn airRetLoad(cg: *CodeGen, inst: Air.Inst.Index) !void { } } - const ptr = try cg.resolve(un_op); - const value = try cg.load(ret_ty, ptr, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) }); + const value = switch (try cg.resolvePtr(un_op)) { + .tracked => |t| t.slot.*.?, + .id => |ptr| try cg.load(ret_ty, ptr, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) }), + }; try cg.body.emit(gpa, .OpReturnValue, .{ .value = value, }); } fn airTry(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; + const gpa = cg.gpa; + const zcu = cg.zcu; const unwrapped_try = cg.air.unwrapTry(inst); const body = unwrapped_try.else_body; @@ -7118,7 +7521,7 @@ fn airTry(cg: *CodeGen, inst: Air.Inst.Index) !?Id { err_union_id; const zero_id = try cg.constInt(.anyerror, 0); - const is_err_id = cg.module.allocId(); + const is_err_id = cg.allocId(); try cg.body.emit(gpa, .OpINotEqual, .{ .id_result_type = bool_ty_id, .id_result = is_err_id, @@ -7130,8 +7533,8 @@ fn airTry(cg: *CodeGen, inst: Air.Inst.Index) !?Id { // with the current body. // Just generate a new block here, then generate a new block inline for the remainder of the body. - const err_block = cg.module.allocId(); - const ok_block = cg.module.allocId(); + const err_block = cg.allocId(); + const ok_block = cg.allocId(); // According to AIR documentation, this block is guaranteed // to not break and end in a return instruction. Thus, @@ -7162,7 +7565,7 @@ fn airTry(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airErrUnionErr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const operand_id = try cg.resolve(ty_op.operand); const err_union_ty = cg.typeOf(ty_op.operand); @@ -7170,7 +7573,7 @@ fn airErrUnionErr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { if (err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) { // No error possible, so just return undefined. - return try cg.module.constUndef(err_ty_id); + return try cg.constUndef(err_ty_id); } const payload_ty = err_union_ty.errorUnionPayload(zcu); @@ -7198,7 +7601,7 @@ fn airErrUnionPayload(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airWrapErrUnionErr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const err_union_ty = cg.typeOfIndex(inst); const payload_ty = err_union_ty.errorUnionPayload(zcu); @@ -7213,7 +7616,7 @@ fn airWrapErrUnionErr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { var members: [2]Id = undefined; members[eu_layout.errorFieldIndex()] = operand_id; - members[eu_layout.payloadFieldIndex()] = try cg.module.constUndef(payload_ty_id); + members[eu_layout.payloadFieldIndex()] = try cg.constUndef(payload_ty_id); var types: [2]Type = undefined; types[eu_layout.errorFieldIndex()] = .anyerror; @@ -7247,7 +7650,7 @@ fn airWrapErrUnionPayload(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airIsNull(cg: *CodeGen, inst: Air.Inst.Index, is_pointer: bool, pred: enum { is_null, is_non_null }) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const operand_id = try cg.resolve(un_op); const operand_ty = cg.typeOf(un_op); @@ -7274,7 +7677,7 @@ fn airIsNull(cg: *CodeGen, inst: Air.Inst.Index, is_pointer: bool, pred: enum { loaded_id; const ptr_ty_id = try cg.resolveType(ptr_ty, .direct); - const null_id = try cg.module.constNull(ptr_ty_id); + const null_id = try cg.constNull(ptr_ty_id); const null_tmp: Temporary = .init(ptr_ty, null_id); const ptr: Temporary = .init(ptr_ty, ptr_id); @@ -7289,9 +7692,9 @@ fn airIsNull(cg: *CodeGen, inst: Air.Inst.Index, is_pointer: bool, pred: enum { const is_non_null_id = blk: { if (is_pointer) { if (payload_ty.hasRuntimeBits(zcu)) { - const storage_class = cg.module.storageClass(operand_ty.ptrAddressSpace(zcu)); + const storage_class = cg.storageClass(operand_ty.ptrAddressSpace(zcu)); const bool_indirect_ty_id = try cg.resolveType(.bool, .indirect); - const bool_ptr_ty_id = try cg.module.ptrType(bool_indirect_ty_id, storage_class); + const bool_ptr_ty_id = try cg.ptrType(bool_indirect_ty_id, storage_class); const tag_ptr_id = try cg.accessChain(bool_ptr_ty_id, operand_id, &.{1}); break :blk try cg.load(.bool, tag_ptr_id, .{}); } @@ -7311,8 +7714,8 @@ fn airIsNull(cg: *CodeGen, inst: Air.Inst.Index, is_pointer: bool, pred: enum { return switch (pred) { .is_null => blk: { // Invert condition - const result_id = cg.module.allocId(); - try cg.body.emit(cg.module.gpa, .OpLogicalNot, .{ + const result_id = cg.allocId(); + try cg.body.emit(cg.gpa, .OpLogicalNot, .{ .id_result_type = bool_ty_id, .id_result = result_id, .operand = is_non_null_id, @@ -7324,7 +7727,7 @@ fn airIsNull(cg: *CodeGen, inst: Air.Inst.Index, is_pointer: bool, pred: enum { } fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, pred: enum { is_err, is_non_err }) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const operand_id = try cg.resolve(un_op); const err_union_ty = cg.typeOf(un_op); @@ -7342,10 +7745,10 @@ fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, pred: enum { is_err, is_non_err else try cg.extractField(.anyerror, operand_id, eu_layout.errorFieldIndex()); - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); switch (pred) { inline else => |pred_ct| try cg.body.emit( - cg.module.gpa, + cg.gpa, switch (pred_ct) { .is_err => .OpINotEqual, .is_non_err => .OpIEqual, @@ -7362,7 +7765,7 @@ fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, pred: enum { is_err, is_non_err } fn airUnwrapOptional(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const operand_id = try cg.resolve(ty_op.operand); const optional_ty = cg.typeOf(ty_op.operand); @@ -7378,7 +7781,7 @@ fn airUnwrapOptional(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airUnwrapOptionalPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const operand_id = try cg.resolve(ty_op.operand); const operand_ty = cg.typeOf(ty_op.operand); @@ -7402,7 +7805,7 @@ fn airUnwrapOptionalPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airWrapOptional(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const payload_ty = cg.typeOf(ty_op.operand); @@ -7422,9 +7825,9 @@ fn airWrapOptional(cg: *CodeGen, inst: Air.Inst.Index) !?Id { } fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; - const target = cg.module.zcu.getTarget(); + const gpa = cg.gpa; + const zcu = cg.zcu; + const target = cg.zcu.getTarget(); const switch_br = cg.air.unwrapSwitch(inst); const cond_ty = cg.typeOf(switch_br.operand); const cond = try cg.resolve(switch_br.operand); @@ -7434,14 +7837,14 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { .bool, .error_set => 1, .int => blk: { const bits = cond_ty.intInfo(zcu).bits; - const backing_bits, const big_int = cg.module.backingIntBits(bits); + const backing_bits, const big_int = cg.backingIntBits(bits); if (big_int) return cg.todo("implement composite int switch", .{}); break :blk if (backing_bits <= 32) 1 else 2; }, .@"enum" => blk: { const int_ty = cond_ty.intTagType(zcu); const int_info = int_ty.intInfo(zcu); - const backing_bits, const big_int = cg.module.backingIntBits(int_info.bits); + const backing_bits, const big_int = cg.backingIntBits(int_info.bits); if (big_int) return cg.todo("implement composite int switch", .{}); break :blk if (backing_bits <= 32) 1 else 2; }, @@ -7470,12 +7873,12 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { } // First, pre-allocate the labels for the cases. - const case_labels = cg.module.allocIds(num_cases); + const case_labels = cg.allocIds(num_cases); // We always need the default case - if zig has none, we will generate unreachable there. - const default_label = cg.module.allocId(); - const switch_default = if (last_range_case != null) cg.module.allocId() else default_label; + const default_label = cg.allocId(); + const switch_default = if (last_range_case != null) cg.allocId() else default_label; - const merge_label = cg.module.allocId(); + const merge_label = cg.allocId(); try cg.body.emit(gpa, .OpSelectionMerge, .{ .merge_block = merge_label, @@ -7540,12 +7943,13 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { const item_tmp: Temporary = try cg.temporary(item); const eq = try (try cg.cmp(.eq, cond_tmp, item_tmp)).materialize(cg); case_cond = if (case_cond) |prev| blk: { - const combined = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpLogicalOr, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, combined); - cg.body.writeOperand(Id, prev); - cg.body.writeOperand(Id, eq); + const combined = cg.allocId(); + try cg.body.emit(gpa, .OpLogicalOr, .{ + .id_result_type = bool_ty_id, + .id_result = combined, + .operand_1 = prev, + .operand_2 = eq, + }); break :blk combined; } else eq; } @@ -7555,26 +7959,28 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { const hi_tmp: Temporary = try cg.temporary(range[1]); const ge = try (try cg.cmp(.gte, cond_tmp, lo_tmp)).materialize(cg); const le = try (try cg.cmp(.lte, cond_tmp, hi_tmp)).materialize(cg); - const in_range = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpLogicalAnd, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, in_range); - cg.body.writeOperand(Id, ge); - cg.body.writeOperand(Id, le); + const in_range = cg.allocId(); + try cg.body.emit(gpa, .OpLogicalAnd, .{ + .id_result_type = bool_ty_id, + .id_result = in_range, + .operand_1 = ge, + .operand_2 = le, + }); case_cond = if (case_cond) |prev| blk: { - const combined = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpLogicalOr, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, combined); - cg.body.writeOperand(Id, prev); - cg.body.writeOperand(Id, in_range); + const combined = cg.allocId(); + try cg.body.emit(gpa, .OpLogicalOr, .{ + .id_result_type = bool_ty_id, + .id_result = combined, + .operand_1 = prev, + .operand_2 = in_range, + }); break :blk combined; } else in_range; } const case_label = case_labels.at(case.idx); const is_last = case.idx == last_range_case.?; - const next_check = if (is_last) default_label else cg.module.allocId(); + const next_check = if (is_last) default_label else cg.allocId(); try cg.body.emit(gpa, .OpSelectionMerge, .{ .merge_block = next_check, @@ -7633,9 +8039,9 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { } fn airLoopSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; - const target = cg.module.zcu.getTarget(); + const gpa = cg.gpa; + const zcu = cg.zcu; + const target = cg.zcu.getTarget(); const switch_br = cg.air.unwrapSwitch(inst); const cond_ty = cg.typeOf(switch_br.operand); const initial_cond = try cg.resolve(switch_br.operand); @@ -7645,14 +8051,14 @@ fn airLoopSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { .bool, .error_set => 1, .int => blk: { const bits = cond_ty.intInfo(zcu).bits; - const backing_bits, const big_int = cg.module.backingIntBits(bits); + const backing_bits, const big_int = cg.backingIntBits(bits); if (big_int) return cg.todo("implement composite int loop switch", .{}); break :blk if (backing_bits <= 32) 1 else 2; }, .@"enum" => blk: { const int_ty = cond_ty.intTagType(zcu); const int_info = int_ty.intInfo(zcu); - const backing_bits, const big_int = cg.module.backingIntBits(int_info.bits); + const backing_bits, const big_int = cg.backingIntBits(int_info.bits); if (big_int) return cg.todo("implement composite int loop switch", .{}); break :blk if (backing_bits <= 32) 1 else 2; }, @@ -7682,15 +8088,15 @@ fn airLoopSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { } } - const case_labels = cg.module.allocIds(num_cases); - const default_label = cg.module.allocId(); - const switch_default = if (last_range_case != null) cg.module.allocId() else default_label; + const case_labels = cg.allocIds(num_cases); + const default_label = cg.allocId(); + const switch_default = if (last_range_case != null) cg.allocId() else default_label; - const header_label = cg.module.allocId(); - const loop_merge = cg.module.allocId(); - const continue_label = cg.module.allocId(); - const switch_merge = cg.module.allocId(); - const body_label = cg.module.allocId(); + const header_label = cg.allocId(); + const loop_merge = cg.allocId(); + const continue_label = cg.allocId(); + const switch_merge = cg.allocId(); + const body_label = cg.allocId(); // switch_dispatch signals "continue the loop" by using this sentinel as the // next_block in structuredBreak. at switch_merge, a phi + comparison distinguishes @@ -7772,12 +8178,13 @@ fn airLoopSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { const item_tmp: Temporary = try cg.temporary(item); const eq = try (try cg.cmp(.eq, cond_tmp, item_tmp)).materialize(cg); case_cond = if (case_cond) |prev| blk: { - const combined = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpLogicalOr, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, combined); - cg.body.writeOperand(Id, prev); - cg.body.writeOperand(Id, eq); + const combined = cg.allocId(); + try cg.body.emit(gpa, .OpLogicalOr, .{ + .id_result_type = bool_ty_id, + .id_result = combined, + .operand_1 = prev, + .operand_2 = eq, + }); break :blk combined; } else eq; } @@ -7787,26 +8194,28 @@ fn airLoopSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { const hi_tmp: Temporary = try cg.temporary(range[1]); const ge = try (try cg.cmp(.gte, cond_tmp, lo_tmp)).materialize(cg); const le = try (try cg.cmp(.lte, cond_tmp, hi_tmp)).materialize(cg); - const in_range = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpLogicalAnd, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, in_range); - cg.body.writeOperand(Id, ge); - cg.body.writeOperand(Id, le); + const in_range = cg.allocId(); + try cg.body.emit(gpa, .OpLogicalAnd, .{ + .id_result_type = bool_ty_id, + .id_result = in_range, + .operand_1 = ge, + .operand_2 = le, + }); case_cond = if (case_cond) |prev| blk: { - const combined = cg.module.allocId(); - try cg.body.emitRaw(gpa, .OpLogicalOr, 4); - cg.body.writeOperand(Id, bool_ty_id); - cg.body.writeOperand(Id, combined); - cg.body.writeOperand(Id, prev); - cg.body.writeOperand(Id, in_range); + const combined = cg.allocId(); + try cg.body.emit(gpa, .OpLogicalOr, .{ + .id_result_type = bool_ty_id, + .id_result = combined, + .operand_1 = prev, + .operand_2 = in_range, + }); break :blk combined; } else in_range; } const case_label = case_labels.at(case.idx); const is_last = case.idx == last_range_case.?; - const next_check = if (is_last) default_label else cg.module.allocId(); + const next_check = if (is_last) default_label else cg.allocId(); try cg.body.emit(gpa, .OpSelectionMerge, .{ .merge_block = next_check, @@ -7860,7 +8269,7 @@ fn airLoopSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { try cg.beginSpvBlock(switch_merge); const next_block = try cg.structuredNextBlock(incoming_structured_blocks.items); - const is_dispatch = cg.module.allocId(); + const is_dispatch = cg.allocId(); const bool_ty_id = try cg.resolveType(.bool, .direct); try cg.body.emit(gpa, .OpIEqual, .{ .id_result_type = bool_ty_id, @@ -7869,12 +8278,12 @@ fn airLoopSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { .operand_2 = dispatch_sentinel, }); - const dispatch_check_merge = cg.module.allocId(); + const dispatch_check_merge = cg.allocId(); try cg.body.emit(gpa, .OpSelectionMerge, .{ .merge_block = dispatch_check_merge, .selection_control = .{}, }); - const exit_block = cg.module.allocId(); + const exit_block = cg.allocId(); try cg.body.emit(gpa, .OpBranchConditional, .{ .condition = is_dispatch, .true_label = dispatch_check_merge, @@ -7906,25 +8315,30 @@ fn airSwitchDispatch(cg: *CodeGen, inst: Air.Inst.Index) !void { } fn airUnreach(cg: *CodeGen) !void { - try cg.body.emit(cg.module.gpa, .OpUnreachable, {}); + try cg.body.emit(cg.gpa, .OpUnreachable, {}); } fn airDbgStmt(cg: *CodeGen, inst: Air.Inst.Index) !void { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const dbg_stmt = cg.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt; const path = zcu.navFileScope(cg.owner_nav).sub_file_path; if (zcu.comp.config.root_strip) return; - try cg.body.emit(cg.module.gpa, .OpLine, .{ - .file = try cg.module.debugString(path), + const path_id = cg.allocId(); + try cg.sections.debug_strings.emit(cg.gpa, .OpString, .{ + .id_result = path_id, + .string = path, + }); + try cg.body.emit(cg.gpa, .OpLine, .{ + .file = path_id, .line = cg.base_line + dbg_stmt.line + 1, .column = dbg_stmt.column + 1, }); } fn airDbgInlineBlock(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const zcu = cg.module.zcu; + const zcu = cg.zcu; const block = cg.air.unwrapDbgBlock(inst); const old_base_line = cg.base_line; defer cg.base_line = old_base_line; @@ -7934,15 +8348,17 @@ fn airDbgInlineBlock(cg: *CodeGen, inst: Air.Inst.Index) !?Id { fn airDbgVar(cg: *CodeGen, inst: Air.Inst.Index) !void { const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const target_id = try cg.resolve(pl_op.operand); - if (cg.virtual_allocas.contains(target_id)) return; + const target_id = switch (try cg.resolvePtr(pl_op.operand)) { + .tracked => return, + .id => |id| id, + }; const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload); - try cg.module.debugName(target_id, name.toSlice(cg.air)); + try cg.debugName(target_id, name.toSlice(cg.air)); } fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; + const gpa = cg.gpa; + const zcu = cg.zcu; const unwrapped_asm = cg.air.unwrapAsm(inst); const is_volatile = unwrapped_asm.is_volatile; @@ -7974,7 +8390,6 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { return cg.fail("assembly inputs with 'c' constraint have to be compile-time known", .{}); }); - // TODO: This entire function should be handled a bit better... const ip = &zcu.intern_pool; switch (ip.indexToKey(val.toIntern())) { .int_type, @@ -8080,8 +8495,8 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.lang.CallModifier) !?Id { _ = modifier; - const gpa = cg.module.gpa; - const zcu = cg.module.zcu; + const gpa = cg.gpa; + const zcu = cg.zcu; const air_call = cg.air.unwrapCall(inst); const args = air_call.args; const callee_ty = cg.typeOf(air_call.callee); @@ -8094,11 +8509,9 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.lang.CallModifier) const return_type = fn_info.return_type; const result_type_id = try cg.resolveFnReturnType(.fromInterned(return_type)); - const result_id = cg.module.allocId(); + const result_id = cg.allocId(); const callee_id = try cg.resolve(air_call.callee); - comptime assert(zig_call_abi_ver == 3); - const scratch_top = cg.id_scratch.items.len; defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); const params = try cg.id_scratch.addManyAsSlice(gpa, args.len); @@ -8113,7 +8526,7 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.lang.CallModifier) if (arg_ty.zigTypeTag(zcu) == .pointer and !arg_ty.isSlice(zcu) and !arg_ty.childType(zcu).hasRuntimeBits(zcu) and - cg.module.storageClass(arg_ty.ptrAddressSpace(zcu)) == .function) + cg.storageClass(arg_ty.ptrAddressSpace(zcu)) == .function) { // in logical addressing, pointer arguments to function calls // must be memory object declarations (OpVariable). for pointers to @@ -8148,15 +8561,26 @@ fn builtin3D( dimension: u32, out_of_range_value: anytype, ) !Id { - const gpa = cg.module.gpa; + const gpa = cg.gpa; if (dimension >= 3) return try cg.constInt(result_ty, out_of_range_value); - const u32_ty_id = try cg.module.intType(.unsigned, 32); - const vec_ty_id = try cg.module.vectorType(3, u32_ty_id); - const ptr_ty_id = try cg.module.ptrType(vec_ty_id, .input); - const spv_decl_index = try cg.module.builtin(ptr_ty_id, built_in, .input); - try cg.module.decl_deps.append(gpa, spv_decl_index); - const ptr_id = cg.module.declPtr(spv_decl_index).result_id; - const vec_id = cg.module.allocId(); + const u32_ty_id = try cg.intType(.unsigned, 32); + const vec_ty_id = try cg.vectorType(3, u32_ty_id); + const ptr_ty_id = try cg.ptrType(vec_ty_id, .input); + const builtins_gop = try cg.builtins.getOrPut(gpa, .{ built_in, .input }); + if (!builtins_gop.found_existing) { + builtins_gop.value_ptr.* = try cg.allocDecl(.global); + const decl = cg.declPtr(builtins_gop.value_ptr.*); + try cg.sections.globals.emit(gpa, .OpVariable, .{ + .id_result_type = ptr_ty_id, + .id_result = decl.result_id, + .storage_class = .input, + }); + try cg.decorate(decl.result_id, .{ .built_in = .{ .built_in = built_in } }); + } + const spv_decl_index = builtins_gop.value_ptr.*; + try cg.decl_deps.append(gpa, spv_decl_index); + const ptr_id = cg.declPtr(spv_decl_index).result_id; + const vec_id = cg.allocId(); try cg.body.emit(gpa, .OpLoad, .{ .id_result_type = vec_ty_id, .id_result = vec_id, @@ -8187,12 +8611,30 @@ fn airWorkGroupId(cg: *CodeGen, inst: Air.Inst.Index) !?Id { return try cg.builtin3D(.u32, .workgroup_id, dimension, 0); } -fn typeOf(cg: *CodeGen, inst: Air.Inst.Ref) Type { - const zcu = cg.module.zcu; - return cg.air.typeOf(inst, &zcu.intern_pool); -} +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Target = std.Target; +const Signedness = std.lang.Signedness; +const assert = std.debug.assert; +const log = std.log.scoped(.codegen); -fn typeOfIndex(cg: *CodeGen, inst: Air.Inst.Index) Type { - const zcu = cg.module.zcu; - return cg.air.typeOfIndex(inst, &zcu.intern_pool); -} +const builtin = @import("builtin"); +const link = @import("../../link.zig"); +const codegen = @import("../../codegen.zig"); +const Zcu = @import("../../Zcu.zig"); +const Type = @import("../../Type.zig"); +const Value = @import("../../Value.zig"); +const Air = @import("../../Air.zig"); +const InternPool = @import("../../InternPool.zig"); +const Section = @import("Section.zig"); +const Assembler = @import("Assembler.zig"); +const Mir = @import("Mir.zig"); + +const spec = @import("spec.zig"); +const Opcode = spec.Opcode; +const Word = spec.Word; +const Id = spec.Id; +const IdRange = spec.IdRange; +const StorageClass = spec.StorageClass; + +const CodeGen = @This(); diff --git a/src/codegen/spirv/Mir.zig b/src/codegen/spirv/Mir.zig index 82fc36ab370d434e151956aa2660b83c8c6652d4..c91c362426f83d8b458bce82e2b872e9d24722ff 100644 --- a/src/codegen/spirv/Mir.zig +++ b/src/codegen/spirv/Mir.zig @@ -6,13 +6,13 @@ const Word = spec.Word; const Id = spec.Id; const InternPool = @import("../../InternPool.zig"); -const Module = @import("Module.zig"); +const CodeGen = @import("CodeGen.zig"); const Mir = @This(); id_bound: Word, owner_nav: InternPool.Nav.Index, -kind: Module.Decl.Kind, +kind: CodeGen.Decl.Kind, decl_result_id: Id, extended_instruction_set: []const Word, globals: []const Word, @@ -30,18 +30,18 @@ entry_points: []const EntryPoint, pub const NavRef = struct { local_id: Id, nav: InternPool.Nav.Index, - kind: Module.Decl.Kind, + kind: CodeGen.Decl.Kind, }; pub const UavRef = struct { local_id: Id, val: InternPool.Index, storage_class: spec.StorageClass, - kind: Module.Decl.Kind, + kind: CodeGen.Decl.Kind, }; pub const DeclDep = struct { - kind: Module.Decl.Kind, + kind: CodeGen.Decl.Kind, nav: InternPool.Nav.Index, }; diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig deleted file mode 100644 index b4ea2c8f945a46a90d3828706cbd4f17027cd549..0000000000000000000000000000000000000000 --- a/src/codegen/spirv/Module.zig +++ /dev/null @@ -1,791 +0,0 @@ -//! This structure represents a SPIR-V (sections) module being compiled, and keeps -//! track of all relevant information. That includes the actual instructions, the -//! current result-id bound, and data structures for querying result-id's of data -//! which needs to be persistent over different calls to Decl code generation. -//! -//! A SPIR-V binary module supports both little- and big endian layout. The layout -//! is detected by the magic word in the header. Therefore, we can ignore any byte -//! order throughout the implementation, and just use the host byte order, and make -//! this a problem for the consumer. -const std = @import("std"); -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; - -const Zcu = @import("../../Zcu.zig"); -const InternPool = @import("../../InternPool.zig"); -const Section = @import("Section.zig"); -const spec = @import("spec.zig"); -const Word = spec.Word; -const Id = spec.Id; - -const Module = @This(); - -gpa: Allocator, -arena: Allocator, -zcu: *Zcu, -nav_link: std.AutoHashMapUnmanaged(InternPool.Nav.Index, Decl.Index) = .empty, -uav_link: std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Decl.Index) = .empty, -intern_map: std.AutoHashMapUnmanaged(struct { InternPool.Index, Repr }, Id) = .empty, -decls: std.ArrayList(Decl) = .empty, -decl_deps: std.ArrayList(Decl.Index) = .empty, -entry_points: std.array_hash_map.Auto(Id, EntryPoint) = .empty, -/// This map serves a dual purpose: -/// - It keeps track of pointers that are currently being emitted, so that we can tell -/// if they are recursive and need an OpTypeForwardPointer. -/// - It caches pointers by child-type. This is required because sometimes we rely on -/// ID-equality for pointers, and pointers constructed via `ptrType()` aren't interned -/// via the usual `intern_map` mechanism. -ptr_types: std.AutoHashMapUnmanaged(struct { Id, spec.StorageClass }, Id) = .{}, -/// For test declarations compiled for Vulkan target, we have to add a buffer. -/// We only need to generate this once, this holds the link information related to that. -error_buffer: ?Decl.Index = null, -/// SPIR-V instructions return result-ids. -/// This variable holds the module-wide counter for these. -next_result_id: Word = 1, -/// Some types shouldn't be emitted more than one time, but cannot be caught by -/// the `intern_map` during codegen. Sometimes, IDs are compared to check if -/// types are the same, so we can't delay until the dedup pass. Therefore, -/// this is an ad-hoc structure to cache types where required. -/// According to the SPIR-V specification, section 2.8, this includes all non-aggregate -/// non-pointer types. -/// Additionally, this is used for other values which can be cached, for example, -/// built-in variables. -cache: struct { - bool_type: ?Id = null, - void_type: ?Id = null, - opaque_types: std.StringHashMapUnmanaged(Id) = .empty, - int_types: std.AutoHashMapUnmanaged(std.lang.Type.Int, Id) = .empty, - float_types: std.AutoHashMapUnmanaged(std.lang.Type.Float, Id) = .empty, - vector_types: std.AutoHashMapUnmanaged(struct { Id, u32 }, Id) = .empty, - array_types: std.AutoHashMapUnmanaged(struct { Id, Id }, Id) = .empty, - struct_types: std.array_hash_map.Custom(StructType, Id, StructType.HashContext, true) = .empty, - fn_types: std.array_hash_map.Custom(FnType, Id, FnType.HashContext, true) = .empty, - - extended_instruction_set: std.AutoHashMapUnmanaged(spec.InstructionSet, Id) = .empty, - decorations: std.AutoHashMapUnmanaged(struct { Id, spec.Decoration }, void) = .empty, - builtins: std.AutoHashMapUnmanaged(struct { spec.BuiltIn, spec.StorageClass }, Decl.Index) = .empty, - strings: std.array_hash_map.String(Id) = .empty, - - bool_const: [2]?Id = .{ null, null }, - constants: std.array_hash_map.Custom(Constant, Id, Constant.HashContext, true) = .empty, - - spirv_types: std.AutoHashMapUnmanaged(InternPool.Index, Id) = .empty, -} = .{}, -/// Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module". -sections: struct { - extended_instruction_set: Section = .{}, - memory_model: Section = .{}, - execution_modes: Section = .{}, - debug_strings: Section = .{}, - debug_names: Section = .{}, - annotations: Section = .{}, - globals: Section = .{}, - functions: Section = .{}, -} = .{}, - -pub const big_int_bits = 32; - -/// 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. -pub 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, -}; - -/// 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) { _ }; - - /// Useful to tell what kind of decl this is, and hold the result-id or field index - /// to be used for this decl. - pub const Kind = enum { - func, - global, - invocation_global, - }; - - /// See comment on Kind - kind: Kind, - /// The result-id associated to this decl. The specific meaning of this depends on `kind`: - /// - For `func`, this is the result-id of the associated OpFunction instruction. - /// - For `global`, this is the result-id of the associated OpVariable instruction. - /// - For `invocation_global`, this is the result-id of the associated InvocationGlobal instruction. - result_id: Id, - /// The offset of the first dependency of this decl in the `decl_deps` array. - begin_dep: usize = 0, - /// The past-end offset of the dependencies of this decl in the `decl_deps` array. - end_dep: usize = 0, - /// Whether a stub OpFunction/OpFunctionEnd + Import linkage decoration has - /// already been emitted for this extern function decl. - has_extern_stub: bool = false, -}; - -pub const EntryPoint = struct { - decl_index: Decl.Index, - name: []const u8, - cc: std.builtin.CallingConvention, -}; - -const StructType = struct { - fields: []const Id, - ip_index: InternPool.Index, - - const HashContext = struct { - pub fn hash(_: @This(), ty: StructType) u32 { - var hasher = std.hash.Wyhash.init(0); - hasher.update(std.mem.sliceAsBytes(ty.fields)); - hasher.update(std.mem.asBytes(&ty.ip_index)); - return @truncate(hasher.final()); - } - - pub fn eql(_: @This(), a: StructType, b: StructType, _: usize) bool { - return a.ip_index == b.ip_index and std.mem.eql(Id, a.fields, b.fields); - } - }; -}; - -const FnType = struct { - return_ty: Id, - params: []const Id, - - const HashContext = struct { - pub fn hash(_: @This(), ty: FnType) u32 { - var hasher = std.hash.Wyhash.init(0); - hasher.update(std.mem.asBytes(&ty.return_ty)); - hasher.update(std.mem.sliceAsBytes(ty.params)); - return @truncate(hasher.final()); - } - - pub fn eql(_: @This(), a: FnType, b: FnType, _: usize) bool { - return a.return_ty == b.return_ty and - std.mem.eql(Id, a.params, b.params); - } - }; -}; - -const Constant = struct { - ty: Id, - value: spec.LiteralContextDependentNumber, - - const HashContext = struct { - pub fn hash(_: @This(), value: Constant) u32 { - const Tag = @typeInfo(spec.LiteralContextDependentNumber).@"union".tag_type.?; - var hasher = std.hash.Wyhash.init(0); - hasher.update(std.mem.asBytes(&value.ty)); - hasher.update(std.mem.asBytes(&@as(Tag, value.value))); - switch (value.value) { - inline else => |v| hasher.update(std.mem.asBytes(&v)), - } - return @truncate(hasher.final()); - } - - pub fn eql(_: @This(), a: Constant, b: Constant, _: usize) bool { - if (a.ty != b.ty) return false; - const Tag = @typeInfo(spec.LiteralContextDependentNumber).@"union".tag_type.?; - if (@as(Tag, a.value) != @as(Tag, b.value)) return false; - return switch (a.value) { - inline else => |v, tag| v == @field(b.value, @tagName(tag)), - }; - } - }; -}; - -pub fn deinit(module: *Module) void { - module.nav_link.deinit(module.gpa); - module.uav_link.deinit(module.gpa); - module.intern_map.deinit(module.gpa); - module.ptr_types.deinit(module.gpa); - - module.sections.extended_instruction_set.deinit(module.gpa); - module.sections.memory_model.deinit(module.gpa); - module.sections.execution_modes.deinit(module.gpa); - module.sections.debug_strings.deinit(module.gpa); - module.sections.debug_names.deinit(module.gpa); - module.sections.annotations.deinit(module.gpa); - module.sections.globals.deinit(module.gpa); - module.sections.functions.deinit(module.gpa); - - module.cache.opaque_types.deinit(module.gpa); - module.cache.int_types.deinit(module.gpa); - module.cache.float_types.deinit(module.gpa); - module.cache.vector_types.deinit(module.gpa); - module.cache.array_types.deinit(module.gpa); - module.cache.struct_types.deinit(module.gpa); - module.cache.fn_types.deinit(module.gpa); - module.cache.spirv_types.deinit(module.gpa); - module.cache.extended_instruction_set.deinit(module.gpa); - module.cache.decorations.deinit(module.gpa); - module.cache.builtins.deinit(module.gpa); - module.cache.strings.deinit(module.gpa); - - module.cache.constants.deinit(module.gpa); - - module.decls.deinit(module.gpa); - module.decl_deps.deinit(module.gpa); - module.entry_points.deinit(module.gpa); - - module.* = undefined; -} - -/// Fetch or allocate a result id for nav index. This function also marks the nav as alive. -/// Note: Function does not actually generate the nav, it just allocates an index. -pub fn resolveNav(module: *Module, ip: *InternPool, nav_index: InternPool.Nav.Index) !Decl.Index { - const entry = try module.nav_link.getOrPut(module.gpa, nav_index); - if (!entry.found_existing) { - const nav = ip.getNav(nav_index); - // TODO: Extern fn? - const kind: Decl.Kind = if (ip.isFunctionType(nav.resolved.?.type)) - .func - else switch (nav.resolved.?.@"addrspace") { - .generic => .invocation_global, - else => .global, - }; - entry.value_ptr.* = try module.allocDecl(kind); - } - - return entry.value_ptr.*; -} - -pub fn allocIds(module: *Module, n: u32) spec.IdRange { - defer module.next_result_id += n; - return .{ .base = module.next_result_id, .len = n }; -} - -pub fn allocId(module: *Module) Id { - return module.allocIds(1).at(0); -} - -pub fn idBound(module: Module) Word { - return module.next_result_id; -} - -pub fn addEntryPointDeps( - module: *Module, - decl_index: Decl.Index, - seen: *std.bit_set.Dynamic, - interface: *std.array_list.Managed(Id), -) !void { - const decl = module.declPtr(decl_index); - const deps = module.decl_deps.items[decl.begin_dep..decl.end_dep]; - - if (seen.isSet(@intFromEnum(decl_index))) { - return; - } - - seen.set(@intFromEnum(decl_index)); - - if (decl.kind == .global) { - try interface.append(decl.result_id); - } - - for (deps) |dep| { - try module.addEntryPointDeps(dep, seen, interface); - } -} - -/// Imports or returns the existing id of an extended instruction set -pub fn importInstructionSet(module: *Module, set: spec.InstructionSet) !Id { - assert(set != .core); - - const gop = try module.cache.extended_instruction_set.getOrPut(module.gpa, set); - if (gop.found_existing) return gop.value_ptr.*; - - const result_id = module.allocId(); - try module.sections.extended_instruction_set.emit(module.gpa, .OpExtInstImport, .{ - .id_result = result_id, - .name = @tagName(set), - }); - gop.value_ptr.* = result_id; - - return result_id; -} - -pub fn boolType(module: *Module) !Id { - if (module.cache.bool_type) |id| return id; - - const result_id = module.allocId(); - try module.sections.globals.emit(module.gpa, .OpTypeBool, .{ - .id_result = result_id, - }); - module.cache.bool_type = result_id; - return result_id; -} - -pub fn voidType(module: *Module) !Id { - if (module.cache.void_type) |id| return id; - - const result_id = module.allocId(); - try module.sections.globals.emit(module.gpa, .OpTypeVoid, .{ - .id_result = result_id, - }); - module.cache.void_type = result_id; - try module.debugName(result_id, "void"); - return result_id; -} - -pub fn opaqueType(module: *Module, name: []const u8) !Id { - if (module.cache.opaque_types.get(name)) |id| return id; - const result_id = module.allocId(); - const name_dup = try module.arena.dupe(u8, name); - try module.sections.globals.emit(module.gpa, .OpTypeOpaque, .{ - .id_result = result_id, - .literal_string = name_dup, - }); - try module.debugName(result_id, name_dup); - try module.cache.opaque_types.put(module.gpa, name_dup, result_id); - return result_id; -} - -pub fn backingIntBits(module: *Module, bits: u16) struct { u16, bool } { - assert(bits != 0); - const target = module.zcu.getTarget(); - const ints = [_]struct { bits: u16, enabled: bool }{ - .{ .bits = 8, .enabled = target.cpu.has(.spirv, .int8) }, - .{ .bits = 16, .enabled = target.cpu.has(.spirv, .int16) }, - .{ .bits = 32, .enabled = true }, - .{ .bits = 64, .enabled = target.cpu.has(.spirv, .int64) or target.cpu.arch == .spirv64 }, - }; - - for (ints) |int| { - if (bits <= int.bits and int.enabled) return .{ int.bits, false }; - } - - return .{ std.mem.alignForward(u16, bits, big_int_bits), true }; -} - -pub fn intType(module: *Module, signedness: std.lang.Signedness, bits: u16) !Id { - assert(bits > 0); - - const target = module.zcu.getTarget(); - const actual_signedness = switch (target.os.tag) { - // Kernel only supports unsigned ints. - .opencl, .amdhsa => .unsigned, - else => signedness, - }; - const backing_bits, const big_int = module.backingIntBits(bits); - if (big_int) { - const u32_ty = try module.intType(.unsigned, 32); - const len_id = try module.constant(u32_ty, .{ .uint32 = backing_bits / big_int_bits }); - return module.arrayType(len_id, u32_ty); - } - - const entry = try module.cache.int_types.getOrPut(module.gpa, .{ .signedness = actual_signedness, .bits = backing_bits }); - if (!entry.found_existing) { - const result_id = module.allocId(); - entry.value_ptr.* = result_id; - try module.sections.globals.emit(module.gpa, .OpTypeInt, .{ - .id_result = result_id, - .width = backing_bits, - .signedness = switch (actual_signedness) { - .signed => 1, - .unsigned => 0, - }, - }); - - switch (actual_signedness) { - .signed => try module.debugNameFmt(result_id, "i{}", .{backing_bits}), - .unsigned => try module.debugNameFmt(result_id, "u{}", .{backing_bits}), - } - } - return entry.value_ptr.*; -} - -pub fn floatType(module: *Module, bits: u16) !Id { - assert(bits > 0); - const entry = try module.cache.float_types.getOrPut(module.gpa, .{ .bits = bits }); - if (!entry.found_existing) { - const result_id = module.allocId(); - entry.value_ptr.* = result_id; - try module.sections.globals.emit(module.gpa, .OpTypeFloat, .{ - .id_result = result_id, - .width = bits, - }); - try module.debugNameFmt(result_id, "f{}", .{bits}); - } - return entry.value_ptr.*; -} - -pub fn vectorType(module: *Module, len: u32, child_ty_id: Id) !Id { - const entry = try module.cache.vector_types.getOrPut(module.gpa, .{ child_ty_id, len }); - if (!entry.found_existing) { - const result_id = module.allocId(); - entry.value_ptr.* = result_id; - try module.sections.globals.emit(module.gpa, .OpTypeVector, .{ - .id_result = result_id, - .component_type = child_ty_id, - .component_count = len, - }); - } - return entry.value_ptr.*; -} - -pub fn arrayType(module: *Module, len_id: Id, child_ty_id: Id) !Id { - const entry = try module.cache.array_types.getOrPut(module.gpa, .{ child_ty_id, len_id }); - if (!entry.found_existing) { - const result_id = module.allocId(); - entry.value_ptr.* = result_id; - try module.sections.globals.emit(module.gpa, .OpTypeArray, .{ - .id_result = result_id, - .element_type = child_ty_id, - .length = len_id, - }); - } - return entry.value_ptr.*; -} - -pub fn ptrType(module: *Module, child_ty_id: Id, storage_class: spec.StorageClass) !Id { - const key = .{ child_ty_id, storage_class }; - const gop = try module.ptr_types.getOrPut(module.gpa, key); - if (!gop.found_existing) { - gop.value_ptr.* = module.allocId(); - try module.sections.globals.emit(module.gpa, .OpTypePointer, .{ - .id_result = gop.value_ptr.*, - .storage_class = storage_class, - .type = child_ty_id, - }); - return gop.value_ptr.*; - } - return gop.value_ptr.*; -} - -pub fn structType( - module: *Module, - types: []const Id, - maybe_names: ?[]const []const u8, - ip_index: InternPool.Index, -) !Id { - const actual_ip_index = if (module.zcu.comp.config.root_strip) .none else ip_index; - - if (module.cache.struct_types.get(.{ .fields = types, .ip_index = actual_ip_index })) |id| return id; - const result_id = module.allocId(); - const types_dup = try module.arena.dupe(Id, types); - try module.sections.globals.emit(module.gpa, .OpTypeStruct, .{ - .id_result = result_id, - .id_ref = types_dup, - }); - - if (maybe_names) |names| { - assert(names.len == types.len); - for (names, 0..) |name, i| { - try module.memberDebugName(result_id, @intCast(i), name); - } - } - - try module.cache.struct_types.put( - module.gpa, - .{ .fields = types_dup, .ip_index = actual_ip_index }, - result_id, - ); - return result_id; -} - -pub fn structFields(module: *const Module, struct_ty_id: Id) ?[]const Id { - for (module.cache.struct_types.keys(), module.cache.struct_types.values()) |key, val| { - if (val == struct_ty_id) return key.fields; - } - return null; -} - -pub fn functionType(module: *Module, return_ty_id: Id, param_type_ids: []const Id) !Id { - if (module.cache.fn_types.get(.{ - .return_ty = return_ty_id, - .params = param_type_ids, - })) |id| return id; - const result_id = module.allocId(); - const params_dup = try module.arena.dupe(Id, param_type_ids); - try module.sections.globals.emit(module.gpa, .OpTypeFunction, .{ - .id_result = result_id, - .return_type = return_ty_id, - .id_ref_2 = params_dup, - }); - try module.cache.fn_types.put(module.gpa, .{ - .return_ty = return_ty_id, - .params = params_dup, - }, result_id); - return result_id; -} - -pub fn samplerType(module: *Module, ip_index: InternPool.Index) !Id { - const entry = try module.cache.spirv_types.getOrPut(module.gpa, ip_index); - if (!entry.found_existing) { - const result_id = module.allocId(); - entry.value_ptr.* = result_id; - try module.sections.globals.emit(module.gpa, .OpTypeSampler, .{ - .id_result = result_id, - }); - } - return entry.value_ptr.*; -} - -pub fn imageType( - module: *Module, - ip_index: InternPool.Index, - sampled_ty_id: Id, - dim: spec.Dim, - depth: spec.LiteralInteger, - arrayed: spec.LiteralInteger, - ms: spec.LiteralInteger, - sampled: spec.LiteralInteger, - image_format: spec.ImageFormat, - access_qualifier: ?spec.AccessQualifier, -) !Id { - const entry = try module.cache.spirv_types.getOrPut(module.gpa, ip_index); - if (!entry.found_existing) { - const result_id = module.allocId(); - entry.value_ptr.* = result_id; - try module.sections.globals.emit(module.gpa, .OpTypeImage, .{ - .id_result = result_id, - .sampled_type = sampled_ty_id, - .dim = dim, - .depth = depth, - .arrayed = arrayed, - .ms = ms, - .sampled = sampled, - .image_format = image_format, - .access_qualifier = access_qualifier, - }); - } - return entry.value_ptr.*; -} - -pub fn sampledImageType(module: *Module, ip_index: InternPool.Index, image_ty_id: Id) !Id { - const entry = try module.cache.spirv_types.getOrPut(module.gpa, ip_index); - if (!entry.found_existing) { - const result_id = module.allocId(); - entry.value_ptr.* = result_id; - try module.sections.globals.emit(module.gpa, .OpTypeSampledImage, .{ - .id_result = result_id, - .image_type = image_ty_id, - }); - } - return entry.value_ptr.*; -} - -pub fn runtimeArrayType(module: *Module, ip_index: InternPool.Index, elem_ty_id: Id) !Id { - const entry = try module.cache.spirv_types.getOrPut(module.gpa, ip_index); - if (!entry.found_existing) { - const result_id = module.allocId(); - entry.value_ptr.* = result_id; - try module.sections.globals.emit(module.gpa, .OpTypeRuntimeArray, .{ - .id_result = result_id, - .element_type = elem_ty_id, - }); - } - return entry.value_ptr.*; -} - -pub fn constant(module: *Module, ty_id: Id, value: spec.LiteralContextDependentNumber) !Id { - const gop = try module.cache.constants.getOrPut(module.gpa, .{ .ty = ty_id, .value = value }); - if (!gop.found_existing) { - gop.value_ptr.* = module.allocId(); - try module.sections.globals.emit(module.gpa, .OpConstant, .{ - .id_result_type = ty_id, - .id_result = gop.value_ptr.*, - .value = value, - }); - } - return gop.value_ptr.*; -} - -pub fn constBool(module: *Module, value: bool) !Id { - if (module.cache.bool_const[@intFromBool(value)]) |b| return b; - - const result_ty_id = try module.boolType(); - const result_id = module.allocId(); - module.cache.bool_const[@intFromBool(value)] = result_id; - - switch (value) { - inline else => |value_ct| try module.sections.globals.emit( - module.gpa, - if (value_ct) .OpConstantTrue else .OpConstantFalse, - .{ - .id_result_type = result_ty_id, - .id_result = result_id, - }, - ), - } - - return result_id; -} - -pub fn builtin( - module: *Module, - result_ty_id: Id, - spirv_builtin: spec.BuiltIn, - storage_class: spec.StorageClass, -) !Decl.Index { - const gop = try module.cache.builtins.getOrPut(module.gpa, .{ spirv_builtin, storage_class }); - if (!gop.found_existing) { - const decl_index = try module.allocDecl(.global); - const decl = module.declPtr(decl_index); - - gop.value_ptr.* = decl_index; - try module.sections.globals.emit(module.gpa, .OpVariable, .{ - .id_result_type = result_ty_id, - .id_result = decl.result_id, - .storage_class = storage_class, - }); - try module.decorate(decl.result_id, .{ .built_in = .{ .built_in = spirv_builtin } }); - } - return gop.value_ptr.*; -} - -pub fn constUndef(module: *Module, ty_id: Id) !Id { - const result_id = module.allocId(); - try module.sections.globals.emit(module.gpa, .OpUndef, .{ - .id_result_type = ty_id, - .id_result = result_id, - }); - return result_id; -} - -pub fn constNull(module: *Module, ty_id: Id) !Id { - const result_id = module.allocId(); - try module.sections.globals.emit(module.gpa, .OpConstantNull, .{ - .id_result_type = ty_id, - .id_result = result_id, - }); - return result_id; -} - -/// Decorate a result-id. -pub fn decorate( - module: *Module, - target: Id, - decoration: spec.Decoration.Extended, -) !void { - const gop = try module.cache.decorations.getOrPut(module.gpa, .{ target, decoration }); - if (!gop.found_existing) { - try module.sections.annotations.emit(module.gpa, .OpDecorate, .{ - .target = target, - .decoration = decoration, - }); - } -} - -/// Decorate a result-id which is a member of some struct. -/// We really don't have to and shouldn't need to cache this. -pub fn decorateMember( - module: *Module, - structure_type: Id, - member: u32, - decoration: spec.Decoration.Extended, -) !void { - try module.sections.annotations.emit(module.gpa, .OpMemberDecorate, .{ - .structure_type = structure_type, - .member = member, - .decoration = decoration, - }); -} - -pub fn allocDecl(module: *Module, kind: Decl.Kind) !Decl.Index { - try module.decls.append(module.gpa, .{ - .kind = kind, - .result_id = module.allocId(), - }); - - return @as(Decl.Index, @enumFromInt(@as(u32, @intCast(module.decls.items.len - 1)))); -} - -pub fn declPtr(module: *Module, index: Decl.Index) *Decl { - return &module.decls.items[@intFromEnum(index)]; -} - -/// Declare a SPIR-V function as an entry point. This causes an extra wrapper -/// function to be generated, which is then exported as the real entry point. The purpose of this -/// wrapper is to allocate and initialize the structure holding the instance globals. -pub fn declareEntryPoint( - module: *Module, - decl_index: Decl.Index, - name: []const u8, - cc: std.builtin.CallingConvention, -) !void { - const gop = try module.entry_points.getOrPut(module.gpa, module.declPtr(decl_index).result_id); - gop.value_ptr.decl_index = decl_index; - gop.value_ptr.name = name; - gop.value_ptr.cc = cc; -} - -pub fn debugName(module: *Module, target: Id, name: []const u8) !void { - if (module.zcu.comp.config.root_strip) return; - try module.sections.debug_names.emit(module.gpa, .OpName, .{ - .target = target, - .name = name, - }); -} - -pub fn debugNameFmt(module: *Module, target: Id, comptime fmt: []const u8, args: anytype) !void { - if (module.zcu.comp.config.root_strip) return; - const name = try std.fmt.allocPrint(module.gpa, fmt, args); - defer module.gpa.free(name); - try module.debugName(target, name); -} - -pub fn memberDebugName(module: *Module, target: Id, member: u32, name: []const u8) !void { - if (module.zcu.comp.config.root_strip) return; - try module.sections.debug_names.emit(module.gpa, .OpMemberName, .{ - .type = target, - .member = member, - .name = name, - }); -} - -pub fn debugString(module: *Module, string: []const u8) !Id { - const entry = try module.cache.strings.getOrPut(module.gpa, string); - if (!entry.found_existing) { - entry.value_ptr.* = module.allocId(); - try module.sections.debug_strings.emit(module.gpa, .OpString, .{ - .id_result = entry.value_ptr.*, - .string = string, - }); - } - return entry.value_ptr.*; -} - -pub fn storageClass(module: *Module, as: std.lang.AddressSpace) spec.StorageClass { - const target = module.zcu.getTarget(); - return switch (as) { - .generic => .function, - .global => switch (target.os.tag) { - .opencl, .amdhsa => .cross_workgroup, - else => .storage_buffer, - }, - .push_constant => .push_constant, - .output => .output, - .uniform => .uniform, - .storage_buffer => .storage_buffer, - .physical_storage_buffer => .physical_storage_buffer, - .constant => .uniform_constant, - .shared => .workgroup, - .local => .function, - .input => .input, - .gs, - .fs, - .ss, - .far, - .param, - .flash, - .flash1, - .flash2, - .flash3, - .flash4, - .flash5, - .cog, - .lut, - .hub, - .externref, - .funcref, - => unreachable, - }; -} -- 2.54.0 From 3c857583fccf9e88ddad934b136d1283c1ea1dab Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Sun, 21 Jun 2026 21:46:32 +0330 Subject: [PATCH 6/9] spirv: fix `@SpirvType` opaque externs lowered as `OpUndef` --- src/codegen/spirv/CodeGen.zig | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index dd9a522b1719661129a2923003948a399eafb3c9..c04bc4f1ef648c565e7ae87c13a35ab18b89fe94 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -725,7 +725,10 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { const val = zcu.navValue(cg.owner_nav); const ty = val.typeOf(zcu); - if (!do_codegen and !ty.hasRuntimeBits(zcu)) return; + if (!do_codegen and !ty.hasRuntimeBits(zcu)) { + const child_ty = if (ty.zigTypeTag(zcu) == .pointer) ty.childType(zcu) else ty; + if (child_ty.zigTypeTag(zcu) != .spirv) return; + } const spv_decl_index = try cg.resolveNav(ip, cg.owner_nav); const decl = cg.declPtr(spv_decl_index); @@ -1800,7 +1803,9 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { }, } - if (!nav_ty.hasRuntimeBits(zcu)) return cg.constUndef(ty_id); + if (!nav_ty.hasRuntimeBits(zcu) and nav_ty.zigTypeTag(zcu) != .spirv) { + return cg.constUndef(ty_id); + } const spv_decl_index = try cg.resolveNav(ip, nav_index); const spv_decl = cg.declPtr(spv_decl_index); @@ -1835,7 +1840,9 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { else => {}, } - if (!uav_ty.hasRuntimeBits(zcu)) return cg.constUndef(ty_id); + if (!uav_ty.hasRuntimeBits(zcu) and uav_ty.zigTypeTag(zcu) != .spirv) { + return cg.constUndef(ty_id); + } // Uav refs are always generic. assert(result_ptr_ty.ptrAddressSpace(zcu) == .generic); -- 2.54.0 From 36d7d5907227b9356e581705731c6154864e7081 Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Sun, 21 Jun 2026 23:42:47 +0330 Subject: [PATCH 7/9] spirv: fix non-input/output globals leaking into `OpEntryPoint` interface --- src/link/SpirV.zig | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index b3241827021b60ac6284efdd5613da95d0c93f16..1e395a11441196cecc7976cd1ccd9b70cf7c807f 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -1034,10 +1034,6 @@ fn collectEntryPointInterface( for (mir.decl_deps) |dep| { try collectEntryPointInterface(linker, dep.nav, interface, visited, nav_final_ids, uav_final_ids, frag_infos, gpa); } - - for (mir.nav_refs) |ref| { - try collectEntryPointInterface(linker, ref.nav, interface, visited, nav_final_ids, uav_final_ids, frag_infos, gpa); - } } fn remapAndAppend( -- 2.54.0 From ffc510a70c3f1c3c28e1b2e444ccc826df121c05 Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Wed, 24 Jun 2026 20:23:37 +0330 Subject: [PATCH 8/9] spirv: link: handle bit_enum/value_enum parameters during id remap Operands of category `bit_enum` and `value_enum` fell into the `else` branch and only their mask/value word was advanced past, so any id-typed parameters that follow (e.g. the `%bias` after `Bias` in `ImageOperands`) were left unremapped and ended up referencing whichever instruction landed on the same final id by chance. --- src/link/SpirV.zig | 102 +++++++++++++++++++----------- test/behavior/array.zig | 2 + test/behavior/bitcast.zig | 10 +++ test/behavior/comptime_memory.zig | 2 + test/behavior/globals.zig | 1 + 5 files changed, 81 insertions(+), 36 deletions(-) diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 1e395a11441196cecc7976cd1ccd9b70cf7c807f..e6cdabd133e641406bfd0ef96328e9fe8e07469a 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -1074,52 +1074,82 @@ fn remapAndAppendInst( for (inst_spec.operands) |operand| { const cat = operand.kind.category(); switch (operand.quantifier) { - .required => { + .required, .optional => { if (offset >= inst.operands.len) break; - if (cat == .id) { - remapSingleId(&inst_slice[1 + offset], id_offset, id_remap); - offset += 1; - } else if (cat == .literal) { - offset += operandLiteralWordCount(operand.kind, inst, offset); - } else if (cat == .composite) { - remapCompositeOperand(operand.kind, inst_slice, offset, id_offset, id_remap); - offset += 2; - } else { - offset += 1; - } - }, - .optional => { - if (offset >= inst.operands.len) break; - if (cat == .id) { - remapSingleId(&inst_slice[1 + offset], id_offset, id_remap); - offset += 1; - } else if (cat == .literal) { - offset += operandLiteralWordCount(operand.kind, inst, offset); - } else { - offset += 1; - } + offset += remapOperand(operand.kind, cat, inst, inst_slice, offset, id_offset, id_remap); }, .variadic => { while (offset < inst.operands.len) { - if (cat == .id) { - remapSingleId(&inst_slice[1 + offset], id_offset, id_remap); - offset += 1; - } else if (cat == .literal) { - offset += operandLiteralWordCount(operand.kind, inst, offset); - } else if (cat == .composite) { - if (offset + 1 < inst.operands.len) { - remapCompositeOperand(operand.kind, inst_slice, offset, id_offset, id_remap); - } - offset += 2; - } else { - offset += 1; - } + offset += remapOperand(operand.kind, cat, inst, inst_slice, offset, id_offset, id_remap); } }, } } } +fn remapOperand( + kind: spec.OperandKind, + cat: spec.OperandCategory, + inst: BinaryModule.Instruction, + inst_slice: []Word, + offset: usize, + id_offset: Word, + id_remap: *const std.AutoHashMapUnmanaged(Id, Id), +) usize { + switch (cat) { + .id => { + remapSingleId(&inst_slice[1 + offset], id_offset, id_remap); + return 1; + }, + .literal => return operandLiteralWordCount(kind, inst, offset), + .composite => { + remapCompositeOperand(kind, inst_slice, offset, id_offset, id_remap); + return 2; + }, + .bit_enum => { + const mask = inst_slice[1 + offset]; + var consumed: usize = 1; + for (kind.enumerants()) |e| { + if ((mask & e.value) == 0) continue; + for (e.parameters) |param_kind| { + if (offset + consumed >= inst.operands.len) return consumed; + consumed += remapOperand( + param_kind, + param_kind.category(), + inst, + inst_slice, + offset + consumed, + id_offset, + id_remap, + ); + } + } + return consumed; + }, + .value_enum => { + const value = inst_slice[1 + offset]; + var consumed: usize = 1; + for (kind.enumerants()) |e| { + if (e.value != value) continue; + for (e.parameters) |param_kind| { + if (offset + consumed >= inst.operands.len) return consumed; + consumed += remapOperand( + param_kind, + param_kind.category(), + inst, + inst_slice, + offset + consumed, + id_offset, + id_remap, + ); + } + break; + } + return consumed; + }, + } +} + fn remapCompositeOperand( kind: spec.OperandKind, inst_slice: []Word, diff --git a/test/behavior/array.zig b/test/behavior/array.zig index 961125f18aa7bcdad61985e7408e4dfd89a0ed88..f7e179034a9a2f16807a6682f8ee7f1f50ba97e8 100644 --- a/test/behavior/array.zig +++ b/test/behavior/array.zig @@ -654,6 +654,8 @@ test "array of array agregate init" { } test "pointer to array has ptr field" { + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; + const arr: *const [5]u32 = &.{ 10, 20, 30, 40, 50 }; try std.testing.expect(arr.ptr == @as([*]const u32, arr)); try std.testing.expect(arr.ptr[0] == 10); diff --git a/test/behavior/bitcast.zig b/test/behavior/bitcast.zig index a0c1624665287885ff6445a1e74efd3d2d7d3c02..3b77d2c6023256829590cc98d9b8c217e6bbe660 100644 --- a/test/behavior/bitcast.zig +++ b/test/behavior/bitcast.zig @@ -439,6 +439,8 @@ test "@bitCast of packed struct with void field to integer" { } test "@bitCast vector to array with different element size" { + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; + const static = struct { fn doTheTest(v: @Vector(4, u5)) !void { const result: [5]u4 = @bitCast(v); @@ -462,6 +464,8 @@ test "@bitCast vector to array with different element size" { } test "@bitCast packed struct to array of bits" { + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; + const S = packed struct(u16) { foo: u5, bar: i7, @@ -508,6 +512,8 @@ test "@bitCast packed struct to array of bits" { } test "@bitCast nested arrays of vectors" { + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; + const Src = [2][2]@Vector(4, u5); const Dest = [5]@Vector(2, u8); @@ -543,6 +549,8 @@ test "@bitCast nested arrays of vectors" { } test "@bitCast nested arrays of bool to scalar" { + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; + const static = struct { fn doTheTest(src: [4][4]bool) !void { const result: u16 = @bitCast(src); @@ -560,6 +568,8 @@ test "@bitCast nested arrays of bool to scalar" { } test "@bitCast deeply nested arrays to scalar" { + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; + const static = struct { fn doTheTest(src: [2][1][3][5]u4) !void { const signed: i120 = @bitCast(src); diff --git a/test/behavior/comptime_memory.zig b/test/behavior/comptime_memory.zig index faaee0f763f4a24a6da0f0b6ca8efb75052dd7c5..d11a7cb0165dca71e86447ec8a059257001d4cb4 100644 --- a/test/behavior/comptime_memory.zig +++ b/test/behavior/comptime_memory.zig @@ -585,6 +585,8 @@ test "comptime store to extern struct reinterpreted as byte array" { } test "reinterpret sentinel-terminated array as packed struct" { + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; + const S = packed struct(u16) { lo: u8, hi: u8 }; const data: [2:0]u8 = .{ 0x12, 0x34 }; const ptr: *align(1) const S = @ptrCast(&data); diff --git a/test/behavior/globals.zig b/test/behavior/globals.zig index eab88cdfbdc4da73079c041fdc71cde54ba16821..98ebedae41cf85ac2f7e6500e5d8cb380159a3d4 100644 --- a/test/behavior/globals.zig +++ b/test/behavior/globals.zig @@ -16,6 +16,7 @@ var vpos = @Vector(2, f32){ 0.0, 0.0 }; test "store to global vector" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try expect(vpos[1] == 0.0); vpos = @Vector(2, f32){ 0.0, 1.0 }; -- 2.54.0 From e8ccd4fbcd8375d579042b5c38d8370ca271a00f Mon Sep 17 00:00:00 2001 From: Ali Cheraghi Date: Wed, 24 Jun 2026 20:52:21 +0330 Subject: [PATCH 9/9] spirv: allow specifying texel type for storage images --- lib/std/lang.zig | 2 +- src/Sema.zig | 87 +++++++++---------- src/codegen/spirv/CodeGen.zig | 7 +- test/behavior/spirv.zig | 2 +- .../SpirvType_vulkan_target.zig | 2 +- 5 files changed, 47 insertions(+), 53 deletions(-) diff --git a/lib/std/lang.zig b/lib/std/lang.zig index 8bcc3712a42faa1a8dc338a54de71291fcdea957..21a4fdae87a59afb4a63cbfe106558a0d1a0281d 100644 --- a/lib/std/lang.zig +++ b/lib/std/lang.zig @@ -838,7 +838,7 @@ pub const Type = union(enum) { pub const Usage = union(enum(u2)) { unknown: type, sampled: type, - storage, + storage: type, }; pub const Format = enum(u4) { diff --git a/src/Sema.zig b/src/Sema.zig index 78eeb2dfcfe0a818f811a6f4881c9e51a3eccbca..45b1bd7065a5e14bf69754c06da474aeca910ac9 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -20641,62 +20641,59 @@ fn zirReifySpirvType( break :ip_data .{ .name = name, .zir_index = tracked_inst, - .ty = switch (usage_tag) { - .sampled, .unknown => blk: { - const sampled_type = usage_val.unionPayload(zcu).toType(); - std.hash.autoHash(&hasher, sampled_type.toIntern()); + .ty = blk: { + const sampled_type = usage_val.unionPayload(zcu).toType(); + std.hash.autoHash(&hasher, sampled_type.toIntern()); - if (target.os.tag != .opencl and sampled_type.toIntern() == .void_type) { - return sema.fail(block, operand_src, "'void' type for '{t}' field is only valid under the 'opencl' os", .{usage_tag}); - } - if (target.os.tag == .opencl and sampled_type.toIntern() != .void_type) { - return sema.fail(block, operand_src, "'{t}' field type must be 'void' under the 'opencl' os", .{usage_tag}); - } + if (target.os.tag != .opencl and sampled_type.toIntern() == .void_type) { + return sema.fail(block, operand_src, "'void' type for '{t}' field is only valid under the 'opencl' os", .{usage_tag}); + } + if (target.os.tag == .opencl and sampled_type.toIntern() != .void_type) { + return sema.fail(block, operand_src, "'{t}' field type must be 'void' under the 'opencl' os", .{usage_tag}); + } + + if (sampled_type.toIntern() != .void_type and + (!sampled_type.hasRuntimeBits(zcu) or (!sampled_type.isRuntimeFloat() and !sampled_type.isInt(zcu)))) + { + return sema.fail(block, operand_src, "invalid '{t}' field value '{f}'", .{ usage_tag, sampled_type.fmt(pt) }); + } - if (sampled_type.toIntern() != .void_type and - (!sampled_type.hasRuntimeBits(zcu) or (!sampled_type.isRuntimeFloat() and !sampled_type.isInt(zcu)))) - { - return sema.fail(block, operand_src, "invalid '{t}' field value '{f}'", .{ usage_tag, sampled_type.fmt(pt) }); + if (target.os.tag == .vulkan) { + const ok = (sampled_type.isRuntimeFloat() and sampled_type.bitSize(zcu) == 32) or + (sampled_type.isInt(zcu) and (sampled_type.bitSize(zcu) == 32 or sampled_type.bitSize(zcu) == 64)); + if (!ok) { + return sema.fail( + block, + operand_src, + "'{t}' field value must be a 32-bit int, 64-bit int or 32-bit float under the 'vulkan' os", + .{usage_tag}, + ); } - if (target.os.tag == .vulkan) { - const ok = (sampled_type.isRuntimeFloat() and sampled_type.bitSize(zcu) == 32) or - (sampled_type.isInt(zcu) and (sampled_type.bitSize(zcu) == 32 or sampled_type.bitSize(zcu) == 64)); - if (!ok) { + if (format != .unknown) { + const format_kind: enum { float, sint, uint } = switch (format) { + .rgba32f, .rgba16f, .rgba8unorm, .rgba8snorm, .r32f => .float, + .rgba32i, .rgba16i, .rgba8i, .r32i => .sint, + .rgba32u, .rgba16u, .rgba8u, .r32u => .uint, + .unknown => unreachable, + }; + const matches = switch (format_kind) { + .float => sampled_type.isRuntimeFloat(), + .sint => sampled_type.isInt(zcu) and sampled_type.intInfo(zcu).signedness == .signed, + .uint => sampled_type.isInt(zcu) and sampled_type.intInfo(zcu).signedness == .unsigned, + }; + if (!matches) { return sema.fail( block, operand_src, - "'{t}' field value must be a 32-bit int, 64-bit int or 32-bit float under the 'vulkan' os", - .{usage_tag}, + "image 'format' '.{t}' does not match '{t}' type '{f}' under the 'vulkan' os", + .{ format, usage_tag, sampled_type.fmt(pt) }, ); } - - if (format != .unknown) { - const format_kind: enum { float, sint, uint } = switch (format) { - .rgba32f, .rgba16f, .rgba8unorm, .rgba8snorm, .r32f => .float, - .rgba32i, .rgba16i, .rgba8i, .r32i => .sint, - .rgba32u, .rgba16u, .rgba8u, .r32u => .uint, - .unknown => unreachable, - }; - const matches = switch (format_kind) { - .float => sampled_type.isRuntimeFloat(), - .sint => sampled_type.isInt(zcu) and sampled_type.intInfo(zcu).signedness == .signed, - .uint => sampled_type.isInt(zcu) and sampled_type.intInfo(zcu).signedness == .unsigned, - }; - if (!matches) { - return sema.fail( - block, - operand_src, - "image 'format' '.{t}' does not match '{t}' type '{f}' under the 'vulkan' os", - .{ format, usage_tag, sampled_type.fmt(pt) }, - ); - } - } } + } - break :blk sampled_type.toIntern(); - }, - .storage => .none, + break :blk sampled_type.toIntern(); }, .flags = .{ .tag = .image, diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index c04bc4f1ef648c565e7ae87c13a35ab18b89fe94..80b8ceafb3f0dc84b12c878665784441597b5add 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -2345,10 +2345,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { switch (spirv_type.flags.tag) { .sampler => try cg.sections.globals.emit(gpa, .OpTypeSampler, .{ .id_result = result_id }), .image => { - const sampled_type_id = if (spirv_type.ty == .none) - try cg.intType(.unsigned, 32) - else - try cg.resolveType(Type.fromInterned(spirv_type.ty), .direct); + const sampled_type_id = try cg.resolveType(.fromInterned(spirv_type.ty), .direct); try cg.sections.globals.emit(gpa, .OpTypeImage, .{ .id_result = result_id, .sampled_type = sampled_type_id, @@ -2366,7 +2363,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { .arrayed = @intFromBool(spirv_type.flags.is_arrayed), .ms = @intFromBool(spirv_type.flags.is_multisampled), .sampled = switch (spirv_type.flags.usage) { - .unknown => 1, + .unknown => 0, .sampled => 1, .storage => 2, }, diff --git a/test/behavior/spirv.zig b/test/behavior/spirv.zig index 3028fc577c7c9a588755501279d3fd66ba1a5e27..6d11032959e665ed26063894e494cd7084f953e4 100644 --- a/test/behavior/spirv.zig +++ b/test/behavior/spirv.zig @@ -10,7 +10,7 @@ const Image = @SpirvType(.{ .image = .{ } }); const SampledImage = @SpirvType(.{ .sampled_image = Image }); const StorageImage = @SpirvType(.{ .image = .{ - .usage = .storage, + .usage = .{ .storage = u32 }, .format = .unknown, .dim = .@"2d", .depth = .unknown, diff --git a/test/cases/compile_errors/SpirvType_vulkan_target.zig b/test/cases/compile_errors/SpirvType_vulkan_target.zig index 894b3441a8e5101cbf5cf66a30c5e2a3ec795b0e..fdb53678e3139870df23563a9b2ac5494e524cea 100644 --- a/test/cases/compile_errors/SpirvType_vulkan_target.zig +++ b/test/cases/compile_errors/SpirvType_vulkan_target.zig @@ -1,6 +1,6 @@ comptime { _ = @SpirvType(.{ .image = .{ - .usage = .storage, + .usage = .{ .storage = u32 }, .format = .unknown, .dim = .@"2d", .depth = .unknown, -- 2.54.0