| author | |
| committer | |
| log | b3727d9bd1abc193bed55d31754737980b9087e1 |
| tree | d15b7ecf280430dbf0003ac9a8428b2a0c7ba9b4 |
| parent | 80fe9b2b7fc981166cbfe98165e17dad6107cb34 |
Previously, the SPIR-V linker would merge every function's MIR into one module, then parsed
it in `BinaryModule`, and run three passes over it:
- `prune_unused` walked the module from its entry points to drop dead declarations
- `dedup_types` deduplicated types and constants
- `lower_invocation_globals` rewrote function signatures to pass a pointer to global
variables as function parameter
Now codegen emits instructions in the order it resolves them (not the spec specified order)
into one section per function. then the linker sorts them into the correct order while also
interning types, constants, strings, etc in the flush step.
Navs the frontend no longer references are now skipped using `Zcu.resolveReferences` instead
of a pass like `prune_unused`. Extern variables are also only loaded when an MIR references them.
`lower_invocation_globals` is gone too. non-`threadlocal` globals on SPIR-V targets are disallowed now.
Currently only vulkan supports them via the newly added `private` address space. See #36715
The new linker also supports incremental compilation now!20 files changed, 2096 insertions(+), 4272 deletions(-)
lib/std/Target.zig+1| ... | @@ -2392,6 +2392,7 @@ pub fn supportsAddressSpace( | ... | @@ -2392,6 +2392,7 @@ pub fn supportsAddressSpace( |
| 2392 | .lut => arch == .propeller and std.Target.propeller.featureSetHas(target.cpu.features, .p2), | 2392 | .lut => arch == .propeller and std.Target.propeller.featureSetHas(target.cpu.features, .p2), |
| 2393 | 2393 | ||
| 2394 | .global, .local, .shared => is_gpu, | 2394 | .global, .local, .shared => is_gpu, |
| 2395 | .private => is_spirv, | ||
| 2395 | .constant => (is_gpu and (context == null or context == .constant)) or | 2396 | .constant => (is_gpu and (context == null or context == .constant)) or |
| 2396 | (is_spirv and (context == null or context == .constant or context == .pointer)), | 2397 | (is_spirv and (context == null or context == .constant or context == .pointer)), |
| 2397 | .param => is_nvptx, | 2398 | .param => is_nvptx, |
lib/std/lang.zig+1| ... | @@ -591,6 +591,7 @@ pub const AddressSpace = enum(u5) { | ... | @@ -591,6 +591,7 @@ pub const AddressSpace = enum(u5) { |
| 591 | param, | 591 | param, |
| 592 | shared, | 592 | shared, |
| 593 | local, | 593 | local, |
| 594 | private, | ||
| 594 | input, | 595 | input, |
| 595 | output, | 596 | output, |
| 596 | uniform, | 597 | uniform, |
src/Sema.zig+36-30| ... | @@ -20914,13 +20914,8 @@ fn zirReifySpirvType( | ... | @@ -20914,13 +20914,8 @@ fn zirReifySpirvType( |
| 20914 | const depth = try sema.interpretStdLangType(block, operand_src, depth_val, std.lang.Type.Spirv.Image.Depth); | 20914 | const depth = try sema.interpretStdLangType(block, operand_src, depth_val, std.lang.Type.Spirv.Image.Depth); |
| 20915 | const access = try sema.interpretStdLangType(block, operand_src, access_val, std.lang.Type.Spirv.Image.Access); | 20915 | const access = try sema.interpretStdLangType(block, operand_src, access_val, std.lang.Type.Spirv.Image.Access); |
| 20916 | 20916 | ||
| 20917 | switch (target.os.tag) { | 20917 | if (target.os.tag == .opencl and access == .unknown) { |
| 20918 | .opencl => if (access == .unknown) { | 20918 | return sema.fail(block, operand_src, "'access' field must be specified under the 'opencl' OS", .{}); |
| 20919 | return sema.fail(block, operand_src, "'access' field must be specified under the 'opencl' os", .{}); | ||
| 20920 | }, | ||
| 20921 | else => if (access != .unknown) { | ||
| 20922 | return sema.fail(block, operand_src, "access qualifier '.{t}' is only valid under the 'opencl' os", .{access}); | ||
| 20923 | }, | ||
| 20924 | } | 20919 | } |
| 20925 | 20920 | ||
| 20926 | const arrayed = try sema.interpretStdLangType(block, operand_src, arrayed_val, bool); | 20921 | const arrayed = try sema.interpretStdLangType(block, operand_src, arrayed_val, bool); |
| ... | @@ -20930,31 +20925,24 @@ fn zirReifySpirvType( | ... | @@ -20930,31 +20925,24 @@ fn zirReifySpirvType( |
| 20930 | const usage_tag = try sema.interpretStdLangType(block, operand_src, usage_tag_val, @typeInfo(std.lang.Type.Spirv.Image.Usage).@"union".tag_type.?); | 20925 | const usage_tag = try sema.interpretStdLangType(block, operand_src, usage_tag_val, @typeInfo(std.lang.Type.Spirv.Image.Usage).@"union".tag_type.?); |
| 20931 | 20926 | ||
| 20932 | switch (target.os.tag) { | 20927 | switch (target.os.tag) { |
| 20933 | .vulkan => { | 20928 | .vulkan => if (usage_tag == .unknown) { |
| 20934 | if (usage_tag == .unknown) { | 20929 | return sema.fail(block, operand_src, "'usage' must be '.sampled' or '.storage' under the 'vulkan' OS", .{}); |
| 20935 | return sema.fail( | ||
| 20936 | block, | ||
| 20937 | operand_src, | ||
| 20938 | "'usage' must be '.sampled' or '.storage' under the 'vulkan' os (Sampled == 0 is forbidden)", | ||
| 20939 | .{}, | ||
| 20940 | ); | ||
| 20941 | } | ||
| 20942 | }, | 20930 | }, |
| 20943 | .opencl => { | 20931 | .opencl => { |
| 20944 | if (usage_tag != .unknown) { | 20932 | if (usage_tag != .unknown) { |
| 20945 | return sema.fail(block, operand_src, "'usage' must be '.unknown' under the 'opencl' os", .{}); | 20933 | return sema.fail(block, operand_src, "'usage' must be '.unknown' under the 'opencl' OS", .{}); |
| 20946 | } | 20934 | } |
| 20947 | if (multisampled) { | 20935 | if (multisampled) { |
| 20948 | return sema.fail(block, operand_src, "'multisampled' must be 'false' under the 'opencl' os", .{}); | 20936 | return sema.fail(block, operand_src, "'multisampled' must be 'false' under the 'opencl' OS", .{}); |
| 20949 | } | 20937 | } |
| 20950 | if (format != .unknown) { | 20938 | if (format != .unknown) { |
| 20951 | return sema.fail(block, operand_src, "'format' must be '.unknown' under the 'opencl' os", .{}); | 20939 | return sema.fail(block, operand_src, "'format' must be '.unknown' under the 'opencl' OS", .{}); |
| 20952 | } | 20940 | } |
| 20953 | if (dim == .cube) { | 20941 | if (dim == .cube) { |
| 20954 | return sema.fail(block, operand_src, "'dim' '.cube' is not allowed under the 'opencl' os", .{}); | 20942 | return sema.fail(block, operand_src, "'dim' '.cube' is not allowed under the 'opencl' OS", .{}); |
| 20955 | } | 20943 | } |
| 20956 | if (arrayed and dim != .@"1d" and dim != .@"2d") { | 20944 | if (arrayed and dim != .@"1d" and dim != .@"2d") { |
| 20957 | return sema.fail(block, operand_src, "'arrayed' may only be 'true' when 'dim' is '.1d' or '.2d' under the 'opencl' os", .{}); | 20945 | return sema.fail(block, operand_src, "'arrayed' may only be 'true' when 'dim' is '.1d' or '.2d' under the 'opencl' OS", .{}); |
| 20958 | } | 20946 | } |
| 20959 | }, | 20947 | }, |
| 20960 | else => {}, | 20948 | else => {}, |
| ... | @@ -20965,10 +20953,10 @@ fn zirReifySpirvType( | ... | @@ -20965,10 +20953,10 @@ fn zirReifySpirvType( |
| 20965 | const sampled_type = usage_val.unionPayload(zcu).toType(); | 20953 | const sampled_type = usage_val.unionPayload(zcu).toType(); |
| 20966 | 20954 | ||
| 20967 | if (target.os.tag != .opencl and sampled_type.toIntern() == .void_type) { | 20955 | if (target.os.tag != .opencl and sampled_type.toIntern() == .void_type) { |
| 20968 | return sema.fail(block, operand_src, "'void' type for '{t}' field is only valid under the 'opencl' os", .{usage_tag}); | 20956 | return sema.fail(block, operand_src, "'void' type for '{t}' field is only valid under the 'opencl' OS", .{usage_tag}); |
| 20969 | } | 20957 | } |
| 20970 | if (target.os.tag == .opencl and sampled_type.toIntern() != .void_type) { | 20958 | if (target.os.tag == .opencl and sampled_type.toIntern() != .void_type) { |
| 20971 | return sema.fail(block, operand_src, "'{t}' field type must be 'void' under the 'opencl' os", .{usage_tag}); | 20959 | return sema.fail(block, operand_src, "'{t}' field type must be 'void' under the 'opencl' OS", .{usage_tag}); |
| 20972 | } | 20960 | } |
| 20973 | 20961 | ||
| 20974 | if (sampled_type.toIntern() != .void_type and | 20962 | if (sampled_type.toIntern() != .void_type and |
| ... | @@ -20984,7 +20972,7 @@ fn zirReifySpirvType( | ... | @@ -20984,7 +20972,7 @@ fn zirReifySpirvType( |
| 20984 | return sema.fail( | 20972 | return sema.fail( |
| 20985 | block, | 20973 | block, |
| 20986 | operand_src, | 20974 | operand_src, |
| 20987 | "'{t}' field value must be a 32-bit int, 64-bit int or 32-bit float under the 'vulkan' os", | 20975 | "'{t}' field value must be a 32-bit int, 64-bit int or 32-bit float under the 'vulkan' OS", |
| 20988 | .{usage_tag}, | 20976 | .{usage_tag}, |
| 20989 | ); | 20977 | ); |
| 20990 | } | 20978 | } |
| ... | @@ -21005,7 +20993,7 @@ fn zirReifySpirvType( | ... | @@ -21005,7 +20993,7 @@ fn zirReifySpirvType( |
| 21005 | return sema.fail( | 20993 | return sema.fail( |
| 21006 | block, | 20994 | block, |
| 21007 | operand_src, | 20995 | operand_src, |
| 21008 | "image 'format' '.{t}' does not match '{t}' type '{f}' under the 'vulkan' os", | 20996 | "image 'format' '.{t}' does not match '{t}' type '{f}' under the 'vulkan' OS", |
| 21009 | .{ format, usage_tag, sampled_type.fmt(pt) }, | 20997 | .{ format, usage_tag, sampled_type.fmt(pt) }, |
| 21010 | ); | 20998 | ); |
| 21011 | } | 20999 | } |
| ... | @@ -21058,7 +21046,7 @@ fn zirReifySpirvType( | ... | @@ -21058,7 +21046,7 @@ fn zirReifySpirvType( |
| 21058 | elem_ty.zigTypeTag(zcu) == .spirv and | 21046 | elem_ty.zigTypeTag(zcu) == .spirv and |
| 21059 | ip.loadSpirvType(elem_ty.toIntern()).flags.tag == .runtime_array) | 21047 | ip.loadSpirvType(elem_ty.toIntern()).flags.tag == .runtime_array) |
| 21060 | { | 21048 | { |
| 21061 | return sema.fail(block, operand_src, "'runtime_array' of 'runtime_array' is not allowed under the 'vulkan' os", .{}); | 21049 | return sema.fail(block, operand_src, "'runtime_array' of 'runtime_array' is not allowed under the 'vulkan' OS", .{}); |
| 21062 | } | 21050 | } |
| 21063 | break :blk .{ | 21051 | break :blk .{ |
| 21064 | .ty = union_val.val, | 21052 | .ty = union_val.val, |
| ... | @@ -25315,11 +25303,14 @@ fn zirBuiltinExtern( | ... | @@ -25315,11 +25303,14 @@ fn zirBuiltinExtern( |
| 25315 | } | 25303 | } |
| 25316 | 25304 | ||
| 25317 | if (options.decoration) |decoration| switch (decoration) { | 25305 | if (options.decoration) |decoration| switch (decoration) { |
| 25318 | .flat => switch (ptr_info.flags.address_space) { | 25306 | .location, .flat => switch (ptr_info.flags.address_space) { |
| 25319 | .input, .output => {}, | 25307 | .input, .output => {}, |
| 25320 | else => return sema.fail(block, options_src, "\"flat\" decoration requires \"input\" or \"output\" address space", .{}), | 25308 | else => return sema.fail(block, options_src, "'{t}' decoration requires 'input' or 'output' address space", .{decoration}), |
| 25309 | }, | ||
| 25310 | .descriptor => switch (ptr_info.flags.address_space) { | ||
| 25311 | .global, .storage_buffer, .uniform, .constant => {}, | ||
| 25312 | else => return sema.fail(block, options_src, "'descriptor' decoration requires 'storage_buffer', 'uniform', 'constant' or 'global' address space", .{}), | ||
| 25321 | }, | 25313 | }, |
| 25322 | .location, .descriptor => {}, | ||
| 25323 | }; | 25314 | }; |
| 25324 | 25315 | ||
| 25325 | const target = zcu.getTarget(); | 25316 | const target = zcu.getTarget(); |
| ... | @@ -34913,6 +34904,7 @@ pub fn resolveNavPtrModifiers( | ... | @@ -34913,6 +34904,7 @@ pub fn resolveNavPtrModifiers( |
| 34913 | const gpa = comp.gpa; | 34904 | const gpa = comp.gpa; |
| 34914 | const io = comp.io; | 34905 | const io = comp.io; |
| 34915 | const ip = &zcu.intern_pool; | 34906 | const ip = &zcu.intern_pool; |
| 34907 | const target = zcu.getTarget(); | ||
| 34916 | 34908 | ||
| 34917 | const align_src = block.src(.{ .node_offset_var_decl_align = .zero }); | 34909 | const align_src = block.src(.{ .node_offset_var_decl_align = .zero }); |
| 34918 | const section_src = block.src(.{ .node_offset_var_decl_section = .zero }); | 34910 | const section_src = block.src(.{ .node_offset_var_decl_section = .zero }); |
| ... | @@ -34944,7 +34936,6 @@ pub fn resolveNavPtrModifiers( | ... | @@ -34944,7 +34936,6 @@ pub fn resolveNavPtrModifiers( |
| 34944 | else => .constant, | 34936 | else => .constant, |
| 34945 | }, | 34937 | }, |
| 34946 | }; | 34938 | }; |
| 34947 | const target = zcu.getTarget(); | ||
| 34948 | const addrspace_body = zir_decl.addrspace_body orelse { | 34939 | const addrspace_body = zir_decl.addrspace_body orelse { |
| 34949 | if (zir_decl.linkage == .@"extern" and | 34940 | if (zir_decl.linkage == .@"extern" and |
| 34950 | target.cpu.arch.isSpirV() and | 34941 | target.cpu.arch.isSpirV() and |
| ... | @@ -34968,6 +34959,21 @@ pub fn resolveNavPtrModifiers( | ... | @@ -34968,6 +34959,21 @@ pub fn resolveNavPtrModifiers( |
| 34968 | break :as try sema.analyzeAsAddressSpace(block, addrspace_src, addrspace_ref, addrspace_ctx); | 34959 | break :as try sema.analyzeAsAddressSpace(block, addrspace_src, addrspace_ref, addrspace_ctx); |
| 34969 | }; | 34960 | }; |
| 34970 | 34961 | ||
| 34962 | if (target.cpu.arch.isSpirV()) { | ||
| 34963 | if (zir_decl.kind == .@"var" and zir_decl.linkage != .@"extern" and !zir_decl.is_threadlocal) { | ||
| 34964 | return sema.failWithOwnedErrorMsg(block, msg: { | ||
| 34965 | const decl_src = block.nodeOffset(.zero); | ||
| 34966 | const msg = try sema.errMsg(decl_src, "SPIR-V target does not support global variables", .{}); | ||
| 34967 | errdefer msg.destroy(gpa); | ||
| 34968 | try sema.errNote(decl_src, msg, "consider using 'threadlocal'", .{}); | ||
| 34969 | break :msg msg; | ||
| 34970 | }); | ||
| 34971 | } | ||
| 34972 | if (zir_decl.is_threadlocal and @"addrspace" != .private and target.os.tag == .vulkan) { | ||
| 34973 | return sema.fail(block, addrspace_src, "threadlocal variables with address space '{t}' are not supported on {t}", .{ @"addrspace", target.os.tag }); | ||
| 34974 | } | ||
| 34975 | } | ||
| 34976 | |||
| 34971 | return .{ | 34977 | return .{ |
| 34972 | .@"align" = @"align", | 34978 | .@"align" = @"align", |
| 34973 | .@"linksection" = @"linksection", | 34979 | .@"linksection" = @"linksection", |
src/codegen/spirv/Assembler.zig+68-158| ... | @@ -1,16 +1,11 @@ | ... | @@ -1,16 +1,11 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const Allocator = std.mem.Allocator; | ||
| 3 | const assert = std.debug.assert; | 2 | const assert = std.debug.assert; |
| 4 | |||
| 5 | const CodeGen = @import("CodeGen.zig"); | 3 | const CodeGen = @import("CodeGen.zig"); |
| 6 | const Decl = @import("CodeGen.zig").Decl; | ||
| 7 | |||
| 8 | const spec = @import("spec.zig"); | 4 | const spec = @import("spec.zig"); |
| 9 | const Opcode = spec.Opcode; | 5 | const Opcode = spec.Opcode; |
| 10 | const Word = spec.Word; | 6 | const Word = spec.Word; |
| 11 | const Id = spec.Id; | 7 | const Id = spec.Id; |
| 12 | const StorageClass = spec.StorageClass; | 8 | const StorageClass = spec.StorageClass; |
| 13 | |||
| 14 | const Assembler = @This(); | 9 | const Assembler = @This(); |
| 15 | 10 | ||
| 16 | cg: *CodeGen, | 11 | cg: *CodeGen, |
| ... | @@ -20,7 +15,10 @@ src: []const u8 = undefined, | ... | @@ -20,7 +15,10 @@ src: []const u8 = undefined, |
| 20 | tokens: std.ArrayList(Token) = .empty, | 15 | tokens: std.ArrayList(Token) = .empty, |
| 21 | current_token: u32 = 0, | 16 | current_token: u32 = 0, |
| 22 | /// The instruction that is currently being parsed or has just been parsed. | 17 | /// The instruction that is currently being parsed or has just been parsed. |
| 23 | inst: struct { | 18 | inst: Instruction = .{}, |
| 19 | value_map: std.array_hash_map.String(AsmValue) = .empty, | ||
| 20 | |||
| 21 | const Instruction = struct { | ||
| 24 | opcode: Opcode = undefined, | 22 | opcode: Opcode = undefined, |
| 25 | operands: std.ArrayList(Operand) = .empty, | 23 | operands: std.ArrayList(Operand) = .empty, |
| 26 | string_bytes: std.ArrayList(u8) = .empty, | 24 | string_bytes: std.ArrayList(u8) = .empty, |
| ... | @@ -35,9 +33,7 @@ inst: struct { | ... | @@ -35,9 +33,7 @@ inst: struct { |
| 35 | } | 33 | } |
| 36 | return null; | 34 | return null; |
| 37 | } | 35 | } |
| 38 | } = .{}, | 36 | }; |
| 39 | value_map: std.array_hash_map.String(AsmValue) = .empty, | ||
| 40 | inst_map: std.array_hash_map.String(void) = .empty, | ||
| 41 | 37 | ||
| 42 | const Operand = union(enum) { | 38 | const Operand = union(enum) { |
| 43 | /// Any 'simple' 32-bit value. This could be a mask or | 39 | /// Any 'simple' 32-bit value. This could be a mask or |
| ... | @@ -68,27 +64,14 @@ pub fn deinit(ass: *Assembler) void { | ... | @@ -68,27 +64,14 @@ pub fn deinit(ass: *Assembler) void { |
| 68 | ass.inst.operands.deinit(gpa); | 64 | ass.inst.operands.deinit(gpa); |
| 69 | ass.inst.string_bytes.deinit(gpa); | 65 | ass.inst.string_bytes.deinit(gpa); |
| 70 | ass.value_map.deinit(gpa); | 66 | ass.value_map.deinit(gpa); |
| 71 | ass.inst_map.deinit(gpa); | ||
| 72 | } | 67 | } |
| 73 | 68 | ||
| 74 | const Error = error{ AssembleFail, OutOfMemory }; | 69 | const Error = error{ AssembleFail, OutOfMemory }; |
| 75 | 70 | ||
| 76 | pub fn assemble(ass: *Assembler, src: []const u8) Error!void { | 71 | pub fn assemble(ass: *Assembler, src: []const u8) Error!void { |
| 77 | const gpa = ass.cg.gpa; | ||
| 78 | |||
| 79 | ass.src = src; | 72 | ass.src = src; |
| 80 | ass.errors.clearRetainingCapacity(); | 73 | ass.errors.clearRetainingCapacity(); |
| 81 | 74 | ||
| 82 | // Populate the opcode map if it isn't already | ||
| 83 | if (ass.inst_map.count() == 0) { | ||
| 84 | const instructions = spec.InstructionSet.core.instructions(); | ||
| 85 | try ass.inst_map.ensureUnusedCapacity(gpa, @intCast(instructions.len)); | ||
| 86 | for (instructions, 0..) |inst, i| { | ||
| 87 | const entry = try ass.inst_map.getOrPut(gpa, inst.name); | ||
| 88 | assert(entry.index == i); | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | try ass.tokenize(); | 75 | try ass.tokenize(); |
| 93 | while (!ass.testToken(.eof)) { | 76 | while (!ass.testToken(.eof)) { |
| 94 | try ass.parseInstruction(); | 77 | try ass.parseInstruction(); |
| ... | @@ -144,18 +127,20 @@ const AsmValue = union(enum) { | ... | @@ -144,18 +127,20 @@ const AsmValue = union(enum) { |
| 144 | /// zero-extended from the float's width to 64 bits. | 127 | /// zero-extended from the float's width to 64 bits. |
| 145 | constant: u64, | 128 | constant: u64, |
| 146 | /// A vector "c" input expanded by `processSpecConstVector`. | 129 | /// A vector "c" input expanded by `processSpecConstVector`. |
| 147 | constant_composite: struct { | 130 | constant_composite: ConstantComposite, |
| 131 | string: []const u8, | ||
| 132 | |||
| 133 | const ConstantComposite = struct { | ||
| 148 | child: Id, | 134 | child: Id, |
| 149 | child_kind: std.lang.TypeId, | 135 | child_kind: std.lang.TypeId, |
| 150 | child_bit_width: u16, | 136 | child_bit_width: u16, |
| 151 | values: []u64, | 137 | values: []u64, |
| 152 | }, | 138 | }; |
| 153 | string: []const u8, | ||
| 154 | 139 | ||
| 155 | /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue | 140 | /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue |
| 156 | /// is of a variant that allows the result to be obtained (not an unresolved | 141 | /// is of a variant that allows the result to be obtained (not an unresolved |
| 157 | /// forward declaration, not in the process of being declared, etc). | 142 | /// forward declaration, not in the process of being declared, etc). |
| 158 | pub fn resultId(value: AsmValue) Id { | 143 | fn resultId(value: AsmValue) Id { |
| 159 | return switch (value) { | 144 | return switch (value) { |
| 160 | .just_declared, | 145 | .just_declared, |
| 161 | .unresolved_forward_reference, | 146 | .unresolved_forward_reference, |
| ... | @@ -221,7 +206,7 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue { | ... | @@ -221,7 +206,7 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue { |
| 221 | const cg = ass.cg; | 206 | const cg = ass.cg; |
| 222 | const gpa = cg.gpa; | 207 | const gpa = cg.gpa; |
| 223 | const operands = ass.inst.operands.items; | 208 | const operands = ass.inst.operands.items; |
| 224 | const section = &cg.sections.globals; | 209 | const section = &cg.module; |
| 225 | const id = switch (ass.inst.opcode) { | 210 | const id = switch (ass.inst.opcode) { |
| 226 | .OpTypeVoid => try cg.voidType(), | 211 | .OpTypeVoid => try cg.voidType(), |
| 227 | .OpTypeBool => try cg.boolType(), | 212 | .OpTypeBool => try cg.boolType(), |
| ... | @@ -339,26 +324,16 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue { | ... | @@ -339,26 +324,16 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue { |
| 339 | /// - Target section is determined from instruction type. | 324 | /// - Target section is determined from instruction type. |
| 340 | fn processGenericInstruction(ass: *Assembler) !?AsmValue { | 325 | fn processGenericInstruction(ass: *Assembler) !?AsmValue { |
| 341 | const cg = ass.cg; | 326 | const cg = ass.cg; |
| 342 | const target = cg.zcu.getTarget(); | ||
| 343 | const operands = ass.inst.operands.items; | 327 | const operands = ass.inst.operands.items; |
| 344 | var maybe_spv_decl_index: ?Decl.Index = null; | ||
| 345 | const section = switch (ass.inst.opcode.class()) { | 328 | const section = switch (ass.inst.opcode.class()) { |
| 346 | .constant_creation => &cg.sections.globals, | 329 | .constant_creation => &cg.module, |
| 347 | .annotation => &cg.sections.annotations, | 330 | .annotation => &cg.module, |
| 348 | .type_declaration => unreachable, // Handled elsewhere. | 331 | .type_declaration => unreachable, // Handled elsewhere. |
| 349 | else => switch (ass.inst.opcode) { | 332 | else => switch (ass.inst.opcode) { |
| 350 | .OpEntryPoint => unreachable, | ||
| 351 | .OpExecutionMode, .OpExecutionModeId => &cg.sections.execution_modes, | ||
| 352 | .OpVariable => section: { | 333 | .OpVariable => section: { |
| 353 | const storage_class: spec.StorageClass = @fromBackingInt(@intCast(operands[2].value)); | 334 | const storage_class: spec.StorageClass = @fromBackingInt(@intCast(operands[2].value)); |
| 354 | if (storage_class == .function) break :section &ass.cg.prologue; | 335 | if (storage_class == .function) break :section &ass.cg.prologue; |
| 355 | maybe_spv_decl_index = try cg.allocDecl(.global); | 336 | break :section &cg.module; |
| 356 | if (!target.cpu.has(.spirv, .v1_4) and storage_class != .input and storage_class != .output) { | ||
| 357 | // Before version 1.4, the interface’s storage classes are limited to the Input and Output | ||
| 358 | break :section &cg.sections.globals; | ||
| 359 | } | ||
| 360 | try ass.cg.decl_deps.append(cg.gpa, maybe_spv_decl_index.?); | ||
| 361 | break :section &cg.sections.globals; | ||
| 362 | }, | 337 | }, |
| 363 | else => &ass.cg.body, | 338 | else => &ass.cg.body, |
| 364 | }, | 339 | }, |
| ... | @@ -388,10 +363,7 @@ fn processGenericInstruction(ass: *Assembler) !?AsmValue { | ... | @@ -388,10 +363,7 @@ fn processGenericInstruction(ass: *Assembler) !?AsmValue { |
| 388 | .value, .literal32 => |word| section.writeWord(word), | 363 | .value, .literal32 => |word| section.writeWord(word), |
| 389 | .literal64 => |dword| section.writeDoubleWord(dword), | 364 | .literal64 => |dword| section.writeDoubleWord(dword), |
| 390 | .result_id => { | 365 | .result_id => { |
| 391 | maybe_result_id = if (maybe_spv_decl_index) |spv_decl_index| | 366 | maybe_result_id = cg.allocId(); |
| 392 | cg.declPtr(spv_decl_index).result_id | ||
| 393 | else | ||
| 394 | cg.allocId(); | ||
| 395 | section.writeOperand(Id, maybe_result_id.?); | 367 | section.writeOperand(Id, maybe_result_id.?); |
| 396 | }, | 368 | }, |
| 397 | .ref_id => |index| { | 369 | .ref_id => |index| { |
| ... | @@ -406,7 +378,8 @@ fn processGenericInstruction(ass: *Assembler) !?AsmValue { | ... | @@ -406,7 +378,8 @@ fn processGenericInstruction(ass: *Assembler) !?AsmValue { |
| 406 | } | 378 | } |
| 407 | 379 | ||
| 408 | const actual_word_count = section.instructions.items.len - first_word; | 380 | const actual_word_count = section.instructions.items.len - first_word; |
| 409 | section.instructions.items[first_word] |= @as(u32, @as(u16, @intCast(actual_word_count))) << 16 | @backingInt(ass.inst.opcode); | 381 | const header: spec.InstructionHeader = .{ .opcode = ass.inst.opcode, .word_count = @intCast(actual_word_count) }; |
| 382 | section.instructions.items[first_word] = @bitCast(header); | ||
| 410 | 383 | ||
| 411 | switch (ass.inst.opcode) { | 384 | switch (ass.inst.opcode) { |
| 412 | .OpKill, | 385 | .OpKill, |
| ... | @@ -454,8 +427,8 @@ fn processSpecConstVector(ass: *Assembler) !?AsmValue { | ... | @@ -454,8 +427,8 @@ fn processSpecConstVector(ass: *Assembler) !?AsmValue { |
| 454 | else => return ass.fail(ass.inst.inst_offset, "%ty must be a type", .{}), | 427 | else => return ass.fail(ass.inst.inst_offset, "%ty must be a type", .{}), |
| 455 | }; | 428 | }; |
| 456 | 429 | ||
| 457 | const globals = &cg.sections.globals; | 430 | const globals = &cg.module; |
| 458 | const annotations = &cg.sections.annotations; | 431 | const annotations = &cg.module; |
| 459 | const literal_words: usize = if (cc.child_bit_width <= @bitSizeOf(Word)) 1 else 2; | 432 | const literal_words: usize = if (cc.child_bit_width <= @bitSizeOf(Word)) 1 else 2; |
| 460 | 433 | ||
| 461 | const elem_ids = try gpa.alloc(Id, cc.values.len); | 434 | const elem_ids = try gpa.alloc(Id, cc.values.len); |
| ... | @@ -465,11 +438,10 @@ fn processSpecConstVector(ass: *Assembler) !?AsmValue { | ... | @@ -465,11 +438,10 @@ fn processSpecConstVector(ass: *Assembler) !?AsmValue { |
| 465 | elem_id_out.* = elem_id; | 438 | elem_id_out.* = elem_id; |
| 466 | 439 | ||
| 467 | switch (cc.child_kind) { | 440 | switch (cc.child_kind) { |
| 468 | .bool => { | 441 | .bool => if (value & 1 != 0) { |
| 469 | const opcode: Opcode = if (value & 1 != 0) .OpSpecConstantTrue else .OpSpecConstantFalse; | 442 | try globals.emit(gpa, .OpSpecConstantTrue, .{ .id_result_type = cc.child, .id_result = elem_id }); |
| 470 | try globals.emitRaw(gpa, opcode, 2); | 443 | } else { |
| 471 | globals.writeOperand(Id, cc.child); | 444 | try globals.emit(gpa, .OpSpecConstantFalse, .{ .id_result_type = cc.child, .id_result = elem_id }); |
| 472 | globals.writeOperand(Id, elem_id); | ||
| 473 | }, | 445 | }, |
| 474 | .int, .float => { | 446 | .int, .float => { |
| 475 | try globals.emitRaw(gpa, .OpSpecConstant, 2 + literal_words); | 447 | try globals.emitRaw(gpa, .OpSpecConstant, 2 + literal_words); |
| ... | @@ -487,10 +459,10 @@ fn processSpecConstVector(ass: *Assembler) !?AsmValue { | ... | @@ -487,10 +459,10 @@ fn processSpecConstVector(ass: *Assembler) !?AsmValue { |
| 487 | const spec_id_word = std.math.cast(u32, spec_id_base + i) orelse { | 459 | const spec_id_word = std.math.cast(u32, spec_id_base + i) orelse { |
| 488 | return ass.fail(ass.inst.inst_offset, "SpecId {} does not fit in 32 bits", .{spec_id_base + i}); | 460 | return ass.fail(ass.inst.inst_offset, "SpecId {} does not fit in 32 bits", .{spec_id_base + i}); |
| 489 | }; | 461 | }; |
| 490 | try annotations.emitRaw(gpa, .OpDecorate, 3); | 462 | try annotations.emit(gpa, .OpDecorate, .{ |
| 491 | annotations.writeOperand(Id, elem_id); | 463 | .target = elem_id, |
| 492 | annotations.writeWord(@backingInt(spec.Decoration.spec_id)); | 464 | .decoration = .{ .spec_id = .{ .specialization_constant_id = spec_id_word } }, |
| 493 | annotations.writeWord(spec_id_word); | 465 | }); |
| 494 | } | 466 | } |
| 495 | 467 | ||
| 496 | const result_id = cg.allocId(); | 468 | const result_id = cg.allocId(); |
| ... | @@ -557,14 +529,13 @@ fn parseInstruction(ass: *Assembler) !void { | ... | @@ -557,14 +529,13 @@ fn parseInstruction(ass: *Assembler) !void { |
| 557 | } | 529 | } |
| 558 | 530 | ||
| 559 | const opcode_text = ass.tokenText(opcode_tok); | 531 | const opcode_text = ass.tokenText(opcode_tok); |
| 560 | const index = ass.inst_map.getIndex(opcode_text) orelse { | 532 | const opcode = std.meta.stringToEnum(spec.Opcode, opcode_text) orelse { |
| 561 | return ass.fail(opcode_tok.start, "invalid opcode '{s}'", .{opcode_text}); | 533 | return ass.fail(opcode_tok.start, "invalid opcode '{s}'", .{opcode_text}); |
| 562 | }; | 534 | }; |
| 563 | 535 | ||
| 564 | const inst = spec.InstructionSet.core.instructions()[index]; | 536 | ass.inst.opcode = opcode; |
| 565 | ass.inst.opcode = @fromBackingInt(@intCast(inst.opcode)); | ||
| 566 | 537 | ||
| 567 | const expected_operands = inst.operands; | 538 | const expected_operands = opcode.operands(); |
| 568 | // This is a loop because the result-id is not always the first operand. | 539 | // This is a loop because the result-id is not always the first operand. |
| 569 | const requires_lhs_result = for (expected_operands) |op| { | 540 | const requires_lhs_result = for (expected_operands) |op| { |
| 570 | if (op.kind == .id_result) break true; | 541 | if (op.kind == .id_result) break true; |
| ... | @@ -614,8 +585,8 @@ fn parseOperand(ass: *Assembler, kind: spec.OperandKind) Error!void { | ... | @@ -614,8 +585,8 @@ fn parseOperand(ass: *Assembler, kind: spec.OperandKind) Error!void { |
| 614 | else => switch (kind) { | 585 | else => switch (kind) { |
| 615 | .literal_integer => try ass.parseLiteralInteger(), | 586 | .literal_integer => try ass.parseLiteralInteger(), |
| 616 | .literal_string => try ass.parseString(), | 587 | .literal_string => try ass.parseString(), |
| 617 | .literal_context_dependent_number => try ass.parseContextDependentNumber(), | 588 | .literal_context_dependent_number => try ass.parseNumber(), |
| 618 | .literal_ext_inst_integer => try ass.parseLiteralExtInstInteger(), | 589 | .literal_ext_inst_integer => try ass.parseLiteralInteger(), |
| 619 | .pair_id_ref_id_ref => try ass.parsePhiSource(), | 590 | .pair_id_ref_id_ref => try ass.parsePhiSource(), |
| 620 | else => return ass.todo("parse operand of type {s}", .{@tagName(kind)}), | 591 | else => return ass.todo("parse operand of type {s}", .{@tagName(kind)}), |
| 621 | }, | 592 | }, |
| ... | @@ -661,12 +632,12 @@ fn parseBitEnum(ass: *Assembler, kind: spec.OperandKind) !void { | ... | @@ -661,12 +632,12 @@ fn parseBitEnum(ass: *Assembler, kind: spec.OperandKind) !void { |
| 661 | if ((mask & enumerant.value) == 0) | 632 | if ((mask & enumerant.value) == 0) |
| 662 | continue; | 633 | continue; |
| 663 | 634 | ||
| 664 | for (enumerant.parameters) |param_kind| { | 635 | for (enumerant.parameters) |param| { |
| 665 | if (ass.isAtInstructionBoundary()) { | 636 | if (ass.isAtInstructionBoundary()) { |
| 666 | return ass.fail(ass.currentToken().start, "missing required parameter for bit flag '{s}'", .{enumerant.name}); | 637 | return ass.fail(ass.currentToken().start, "missing required parameter for bit flag '{s}'", .{enumerant.name}); |
| 667 | } | 638 | } |
| 668 | 639 | ||
| 669 | try ass.parseOperand(param_kind); | 640 | try ass.parseOperand(param.kind); |
| 670 | } | 641 | } |
| 671 | } | 642 | } |
| 672 | } | 643 | } |
| ... | @@ -721,12 +692,12 @@ fn parseValueEnum(ass: *Assembler, kind: spec.OperandKind) !void { | ... | @@ -721,12 +692,12 @@ fn parseValueEnum(ass: *Assembler, kind: spec.OperandKind) !void { |
| 721 | 692 | ||
| 722 | try ass.inst.operands.append(gpa, .{ .value = enumerant.value }); | 693 | try ass.inst.operands.append(gpa, .{ .value = enumerant.value }); |
| 723 | 694 | ||
| 724 | for (enumerant.parameters) |param_kind| { | 695 | for (enumerant.parameters) |param| { |
| 725 | if (ass.isAtInstructionBoundary()) { | 696 | if (ass.isAtInstructionBoundary()) { |
| 726 | return ass.fail(ass.currentToken().start, "missing required parameter for enum variant '{s}'", .{enumerant.name}); | 697 | return ass.fail(ass.currentToken().start, "missing required parameter for enum variant '{s}'", .{enumerant.name}); |
| 727 | } | 698 | } |
| 728 | 699 | ||
| 729 | try ass.parseOperand(param_kind); | 700 | try ass.parseOperand(param.kind); |
| 730 | } | 701 | } |
| 731 | } | 702 | } |
| 732 | 703 | ||
| ... | @@ -746,30 +717,26 @@ fn parseRefId(ass: *Assembler) !void { | ... | @@ -746,30 +717,26 @@ fn parseRefId(ass: *Assembler) !void { |
| 746 | try ass.inst.operands.append(gpa, .{ .ref_id = index }); | 717 | try ass.inst.operands.append(gpa, .{ .ref_id = index }); |
| 747 | } | 718 | } |
| 748 | 719 | ||
| 720 | fn eatPlaceholderConstant(ass: *Assembler) !?u64 { | ||
| 721 | const tok = ass.currentToken(); | ||
| 722 | if (!ass.eatToken(.placeholder)) return null; | ||
| 723 | const name = ass.tokenText(tok)[1..]; | ||
| 724 | const value = ass.value_map.get(name) orelse | ||
| 725 | return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); | ||
| 726 | return switch (value) { | ||
| 727 | .constant => |literal| literal, | ||
| 728 | else => ass.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name}), | ||
| 729 | }; | ||
| 730 | } | ||
| 731 | |||
| 749 | fn parseLiteralInteger(ass: *Assembler) !void { | 732 | fn parseLiteralInteger(ass: *Assembler) !void { |
| 750 | const gpa = ass.cg.gpa; | 733 | const gpa = ass.cg.gpa; |
| 751 | 734 | ||
| 752 | const tok = ass.currentToken(); | 735 | const tok = ass.currentToken(); |
| 753 | if (ass.eatToken(.placeholder)) { | 736 | if (try ass.eatPlaceholderConstant()) |literal| { |
| 754 | const name = ass.tokenText(tok)[1..]; | 737 | const literal32 = std.math.cast(u32, literal) orelse |
| 755 | const value = ass.value_map.get(name) orelse { | 738 | return ass.fail(tok.start, "placeholder value {} does not fit in 32 bits", .{literal}); |
| 756 | return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); | 739 | try ass.inst.operands.append(gpa, .{ .literal32 = literal32 }); |
| 757 | }; | ||
| 758 | switch (value) { | ||
| 759 | .constant => |literal| { | ||
| 760 | const literal32 = std.math.cast(u32, literal) orelse { | ||
| 761 | return ass.fail( | ||
| 762 | tok.start, | ||
| 763 | "placeholder value {} does not fit in 32 bits", | ||
| 764 | .{literal}, | ||
| 765 | ); | ||
| 766 | }; | ||
| 767 | try ass.inst.operands.append(gpa, .{ .literal32 = literal32 }); | ||
| 768 | }, | ||
| 769 | else => { | ||
| 770 | return ass.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name}); | ||
| 771 | }, | ||
| 772 | } | ||
| 773 | return; | 740 | return; |
| 774 | } | 741 | } |
| 775 | 742 | ||
| ... | @@ -786,41 +753,6 @@ fn parseLiteralInteger(ass: *Assembler) !void { | ... | @@ -786,41 +753,6 @@ fn parseLiteralInteger(ass: *Assembler) !void { |
| 786 | try ass.inst.operands.append(gpa, .{ .literal32 = value }); | 753 | try ass.inst.operands.append(gpa, .{ .literal32 = value }); |
| 787 | } | 754 | } |
| 788 | 755 | ||
| 789 | fn parseLiteralExtInstInteger(ass: *Assembler) !void { | ||
| 790 | const gpa = ass.cg.gpa; | ||
| 791 | |||
| 792 | const tok = ass.currentToken(); | ||
| 793 | if (ass.eatToken(.placeholder)) { | ||
| 794 | const name = ass.tokenText(tok)[1..]; | ||
| 795 | const value = ass.value_map.get(name) orelse { | ||
| 796 | return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); | ||
| 797 | }; | ||
| 798 | switch (value) { | ||
| 799 | .constant => |literal| { | ||
| 800 | const literal32 = std.math.cast(u32, literal) orelse { | ||
| 801 | return ass.fail( | ||
| 802 | tok.start, | ||
| 803 | "placeholder value {} does not fit in 32 bits", | ||
| 804 | .{literal}, | ||
| 805 | ); | ||
| 806 | }; | ||
| 807 | try ass.inst.operands.append(gpa, .{ .literal32 = literal32 }); | ||
| 808 | }, | ||
| 809 | else => { | ||
| 810 | return ass.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name}); | ||
| 811 | }, | ||
| 812 | } | ||
| 813 | return; | ||
| 814 | } | ||
| 815 | |||
| 816 | try ass.expectToken(.value); | ||
| 817 | const text = ass.tokenText(tok); | ||
| 818 | const value = std.fmt.parseInt(u32, text, 0) catch { | ||
| 819 | return ass.fail(tok.start, "'{s}' is not a valid 32-bit integer literal", .{text}); | ||
| 820 | }; | ||
| 821 | try ass.inst.operands.append(gpa, .{ .literal32 = value }); | ||
| 822 | } | ||
| 823 | |||
| 824 | fn parseString(ass: *Assembler) !void { | 756 | fn parseString(ass: *Assembler) !void { |
| 825 | const gpa = ass.cg.gpa; | 757 | const gpa = ass.cg.gpa; |
| 826 | 758 | ||
| ... | @@ -844,7 +776,7 @@ fn parseString(ass: *Assembler) !void { | ... | @@ -844,7 +776,7 @@ fn parseString(ass: *Assembler) !void { |
| 844 | try ass.inst.operands.append(gpa, .{ .string = string_offset }); | 776 | try ass.inst.operands.append(gpa, .{ .string = string_offset }); |
| 845 | } | 777 | } |
| 846 | 778 | ||
| 847 | fn parseContextDependentNumber(ass: *Assembler) !void { | 779 | fn parseNumber(ass: *Assembler) !void { |
| 848 | const cg = ass.cg; | 780 | const cg = ass.cg; |
| 849 | assert(ass.inst.opcode == .OpConstant or ass.inst.opcode == .OpSpecConstant); | 781 | assert(ass.inst.opcode == .OpConstant or ass.inst.opcode == .OpSpecConstant); |
| 850 | 782 | ||
| ... | @@ -852,14 +784,14 @@ fn parseContextDependentNumber(ass: *Assembler) !void { | ... | @@ -852,14 +784,14 @@ fn parseContextDependentNumber(ass: *Assembler) !void { |
| 852 | const result = try ass.resolveRef(ass.inst.operands.items[0].ref_id); | 784 | const result = try ass.resolveRef(ass.inst.operands.items[0].ref_id); |
| 853 | const result_id = result.resultId(); | 785 | const result_id = result.resultId(); |
| 854 | 786 | ||
| 855 | const words = cg.sections.globals.instructions.items; | 787 | const words = cg.module.instructions.items; |
| 856 | var offset: usize = 0; | 788 | var offset: usize = 0; |
| 857 | while (offset < words.len) { | 789 | while (offset < words.len) { |
| 858 | const word_count = words[offset] >> 16; | 790 | const header: spec.InstructionHeader = @bitCast(words[offset]); |
| 859 | const opcode: Opcode = @fromBackingInt(@intCast(words[offset] & 0xFFFF)); | 791 | const word_count = header.word_count; |
| 860 | defer offset += word_count; | 792 | defer offset += word_count; |
| 861 | if (word_count == 0) break; | 793 | if (word_count == 0) break; |
| 862 | switch (opcode) { | 794 | switch (header.opcode) { |
| 863 | .OpTypeInt => if (word_count >= 4 and @as(Id, @fromBackingInt(@intCast(words[offset + 1]))) == result_id) { | 795 | .OpTypeInt => if (word_count >= 4 and @as(Id, @fromBackingInt(@intCast(words[offset + 1]))) == result_id) { |
| 864 | const width: u16 = @intCast(words[offset + 2]); | 796 | const width: u16 = @intCast(words[offset + 2]); |
| 865 | const signedness: std.lang.Signedness = if (words[offset + 3] == 0) .unsigned else .signed; | 797 | const signedness: std.lang.Signedness = if (words[offset + 3] == 0) .unsigned else .signed; |
| ... | @@ -885,22 +817,11 @@ fn parseContextDependentInt(ass: *Assembler, signedness: std.lang.Signedness, wi | ... | @@ -885,22 +817,11 @@ fn parseContextDependentInt(ass: *Assembler, signedness: std.lang.Signedness, wi |
| 885 | const gpa = ass.cg.gpa; | 817 | const gpa = ass.cg.gpa; |
| 886 | 818 | ||
| 887 | const tok = ass.currentToken(); | 819 | const tok = ass.currentToken(); |
| 888 | if (ass.eatToken(.placeholder)) { | 820 | if (try ass.eatPlaceholderConstant()) |literal| { |
| 889 | const name = ass.tokenText(tok)[1..]; | 821 | if (width <= @bitSizeOf(spec.Word)) { |
| 890 | const value = ass.value_map.get(name) orelse { | 822 | try ass.inst.operands.append(gpa, .{ .literal32 = @truncate(literal) }); |
| 891 | return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); | 823 | } else { |
| 892 | }; | 824 | try ass.inst.operands.append(gpa, .{ .literal64 = literal }); |
| 893 | switch (value) { | ||
| 894 | .constant => |literal| { | ||
| 895 | if (width <= @bitSizeOf(spec.Word)) { | ||
| 896 | try ass.inst.operands.append(gpa, .{ .literal32 = @truncate(literal) }); | ||
| 897 | } else { | ||
| 898 | try ass.inst.operands.append(gpa, .{ .literal64 = literal }); | ||
| 899 | } | ||
| 900 | }, | ||
| 901 | else => { | ||
| 902 | return ass.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name}); | ||
| 903 | }, | ||
| 904 | } | 825 | } |
| 905 | return; | 826 | return; |
| 906 | } | 827 | } |
| ... | @@ -943,22 +864,11 @@ fn parseContextDependentFloat(ass: *Assembler, comptime width: u16) !void { | ... | @@ -943,22 +864,11 @@ fn parseContextDependentFloat(ass: *Assembler, comptime width: u16) !void { |
| 943 | const Int = @Int(.unsigned, width); | 864 | const Int = @Int(.unsigned, width); |
| 944 | 865 | ||
| 945 | const tok = ass.currentToken(); | 866 | const tok = ass.currentToken(); |
| 946 | if (ass.eatToken(.placeholder)) { | 867 | if (try ass.eatPlaceholderConstant()) |literal| { |
| 947 | const name = ass.tokenText(tok)[1..]; | 868 | if (width <= @bitSizeOf(spec.Word)) { |
| 948 | const value = ass.value_map.get(name) orelse { | 869 | try ass.inst.operands.append(gpa, .{ .literal32 = @truncate(literal) }); |
| 949 | return ass.fail(tok.start, "invalid placeholder '${s}'", .{name}); | 870 | } else { |
| 950 | }; | 871 | try ass.inst.operands.append(gpa, .{ .literal64 = literal }); |
| 951 | switch (value) { | ||
| 952 | .constant => |literal| { | ||
| 953 | if (width <= @bitSizeOf(spec.Word)) { | ||
| 954 | try ass.inst.operands.append(gpa, .{ .literal32 = @truncate(literal) }); | ||
| 955 | } else { | ||
| 956 | try ass.inst.operands.append(gpa, .{ .literal64 = literal }); | ||
| 957 | } | ||
| 958 | }, | ||
| 959 | else => { | ||
| 960 | return ass.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name}); | ||
| 961 | }, | ||
| 962 | } | 872 | } |
| 963 | return; | 873 | return; |
| 964 | } | 874 | } |
src/codegen/spirv/CodeGen.zig+734-1029| ... | @@ -10,32 +10,20 @@ base_line: u32, | ... | @@ -10,32 +10,20 @@ base_line: u32, |
| 10 | 10 | ||
| 11 | // Module-level output (accumulated across the nav's codegen) | 11 | // Module-level output (accumulated across the nav's codegen) |
| 12 | next_result_id: Word = 1, | 12 | next_result_id: Word = 1, |
| 13 | decls: std.ArrayList(Decl) = .empty, | 13 | navs: std.array_hash_map.Auto(InternPool.Nav.Index, Id) = .empty, |
| 14 | decl_deps: std.ArrayList(Decl.Index) = .empty, | 14 | externs: std.array_hash_map.Auto(InternPool.Nav.Index, Mir.Extern) = .empty, |
| 15 | nav_link: std.AutoHashMapUnmanaged(InternPool.Nav.Index, Decl.Index) = .empty, | 15 | entry_points: std.ArrayList(Mir.EntryPoint) = .empty, |
| 16 | uav_link: std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Decl.Index) = .empty, | 16 | error_buffer: ?Id = null, |
| 17 | entry_points: std.array_hash_map.Auto(Id, EntryPoint) = .empty, | ||
| 18 | error_buffer: ?Decl.Index = null, | ||
| 19 | struct_types: std.array_hash_map.Custom(StructType, Id, StructType.HashContext, true) = .empty, | 17 | struct_types: std.array_hash_map.Custom(StructType, Id, StructType.HashContext, true) = .empty, |
| 20 | /// SPIR-V ids of OpVariables whose pointee is a Block struct | 18 | /// SPIR-V ids of OpVariables whose pointee is a Block struct |
| 21 | block_var_ids: std.AutoHashMapUnmanaged(Id, void) = .empty, | 19 | block_var_ids: std.AutoHashMapUnmanaged(Id, void) = .empty, |
| 22 | builtins: std.AutoHashMapUnmanaged(struct { spec.BuiltIn, spec.StorageClass }, Decl.Index) = .empty, | 20 | builtins: std.AutoHashMapUnmanaged(struct { spec.BuiltIn, spec.StorageClass }, Id) = .empty, |
| 23 | sections: struct { | 21 | module: Section = .empty, |
| 24 | // Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module". | 22 | |
| 25 | extended_instruction_set: Section = .{}, | 23 | // Per-function state |
| 26 | memory_model: Section = .{}, | 24 | prologue: Section = .empty, |
| 27 | execution_modes: Section = .{}, | 25 | body: Section = .empty, |
| 28 | debug_strings: Section = .{}, | 26 | args: std.MultiArrayList(struct { id: Id, ty: Id }) = .empty, |
| 29 | debug_names: Section = .{}, | ||
| 30 | annotations: Section = .{}, | ||
| 31 | globals: Section = .{}, | ||
| 32 | functions: Section = .{}, | ||
| 33 | } = .{}, | ||
| 34 | |||
| 35 | // Per-function state (reset between top-level genNav calls) | ||
| 36 | prologue: Section = .{}, | ||
| 37 | body: Section = .{}, | ||
| 38 | args: std.ArrayList(Id) = .empty, | ||
| 39 | next_arg_index: u32 = 0, | 27 | next_arg_index: u32 = 0, |
| 40 | /// Caches the limb extractions for composite integer values so repeated | 28 | /// Caches the limb extractions for composite integer values so repeated |
| 41 | /// arithmetic on the same operand doesn't re-emit `OpCompositeExtract` per | 29 | /// arithmetic on the same operand doesn't re-emit `OpCompositeExtract` per |
| ... | @@ -79,28 +67,6 @@ pub const Repr = enum { | ... | @@ -79,28 +67,6 @@ pub const Repr = enum { |
| 79 | indirect, | 67 | indirect, |
| 80 | }; | 68 | }; |
| 81 | 69 | ||
| 82 | /// A function or global, tracked here so the linker can order globals and build | ||
| 83 | /// per-entry-point interface lists. | ||
| 84 | pub const Decl = struct { | ||
| 85 | pub const Index = enum(u32) { _ }; | ||
| 86 | pub const Kind = enum { func, global, invocation_global }; | ||
| 87 | |||
| 88 | kind: Kind, | ||
| 89 | /// Result-id of the associated OpFunction / OpVariable / InvocationGlobal. | ||
| 90 | result_id: Id, | ||
| 91 | /// Range into `decl_deps` for this decl's dependencies. | ||
| 92 | begin_dep: usize = 0, | ||
| 93 | end_dep: usize = 0, | ||
| 94 | /// Whether an extern-function stub has been emitted. | ||
| 95 | has_extern_stub: bool = false, | ||
| 96 | }; | ||
| 97 | |||
| 98 | pub const EntryPoint = struct { | ||
| 99 | decl_index: Decl.Index, | ||
| 100 | name: []const u8, | ||
| 101 | cc: std.builtin.CallingConvention, | ||
| 102 | }; | ||
| 103 | |||
| 104 | const StructType = struct { | 70 | const StructType = struct { |
| 105 | fields: []const Id, | 71 | fields: []const Id, |
| 106 | ip_index: InternPool.Index, | 72 | ip_index: InternPool.Index, |
| ... | @@ -193,24 +159,16 @@ pub fn deinit(cg: *CodeGen) void { | ... | @@ -193,24 +159,16 @@ pub fn deinit(cg: *CodeGen) void { |
| 193 | cg.prologue.deinit(gpa); | 159 | cg.prologue.deinit(gpa); |
| 194 | cg.body.deinit(gpa); | 160 | cg.body.deinit(gpa); |
| 195 | 161 | ||
| 196 | cg.nav_link.deinit(gpa); | 162 | cg.navs.deinit(gpa); |
| 197 | cg.uav_link.deinit(gpa); | 163 | cg.externs.deinit(gpa); |
| 198 | 164 | ||
| 199 | cg.sections.extended_instruction_set.deinit(gpa); | 165 | cg.module.deinit(gpa); |
| 200 | cg.sections.memory_model.deinit(gpa); | ||
| 201 | cg.sections.execution_modes.deinit(gpa); | ||
| 202 | cg.sections.debug_strings.deinit(gpa); | ||
| 203 | cg.sections.debug_names.deinit(gpa); | ||
| 204 | cg.sections.annotations.deinit(gpa); | ||
| 205 | cg.sections.globals.deinit(gpa); | ||
| 206 | cg.sections.functions.deinit(gpa); | ||
| 207 | 166 | ||
| 208 | cg.struct_types.deinit(gpa); | 167 | cg.struct_types.deinit(gpa); |
| 209 | cg.block_var_ids.deinit(gpa); | 168 | cg.block_var_ids.deinit(gpa); |
| 210 | cg.builtins.deinit(gpa); | 169 | cg.builtins.deinit(gpa); |
| 211 | 170 | ||
| 212 | cg.decls.deinit(gpa); | 171 | for (cg.entry_points.items) |ep| gpa.free(ep.name); |
| 213 | cg.decl_deps.deinit(gpa); | ||
| 214 | cg.entry_points.deinit(gpa); | 172 | cg.entry_points.deinit(gpa); |
| 215 | } | 173 | } |
| 216 | 174 | ||
| ... | @@ -223,7 +181,10 @@ pub fn generate( | ... | @@ -223,7 +181,10 @@ pub fn generate( |
| 223 | ) codegen.Error!Mir { | 181 | ) codegen.Error!Mir { |
| 224 | const zcu = pt.zcu; | 182 | const zcu = pt.zcu; |
| 225 | const gpa = zcu.gpa; | 183 | const gpa = zcu.gpa; |
| 226 | const nav = zcu.funcInfo(func_index).owner_nav; | 184 | const ip = &zcu.intern_pool; |
| 185 | const nav_index = zcu.funcInfo(func_index).owner_nav; | ||
| 186 | const nav = ip.getNav(nav_index); | ||
| 187 | const fn_info = zcu.typeToFunc(.fromInterned(nav.resolved.?.type)).?; | ||
| 227 | 188 | ||
| 228 | var arena = std.heap.ArenaAllocator.init(gpa); | 189 | var arena = std.heap.ArenaAllocator.init(gpa); |
| 229 | defer arena.deinit(); | 190 | defer arena.deinit(); |
| ... | @@ -235,22 +196,60 @@ pub fn generate( | ... | @@ -235,22 +196,60 @@ pub fn generate( |
| 235 | .zcu = zcu, | 196 | .zcu = zcu, |
| 236 | .air = air.*, | 197 | .air = air.*, |
| 237 | .liveness = liveness.*.?, | 198 | .liveness = liveness.*.?, |
| 238 | .owner_nav = nav, | 199 | .owner_nav = nav_index, |
| 239 | .base_line = zcu.navSrcLine(nav), | 200 | .base_line = zcu.navSrcLine(nav_index), |
| 240 | }; | 201 | }; |
| 241 | defer cg.deinit(); | 202 | defer cg.deinit(); |
| 242 | 203 | ||
| 243 | try cg.genNav(true); | 204 | const return_ty_id = try cg.resolveFnReturnType(.fromInterned(fn_info.return_type)); |
| 205 | const is_test = zcu.test_functions.contains(nav_index); | ||
| 206 | const nav_id = try cg.resolveNav(nav_index); | ||
| 207 | const function_id = if (is_test) cg.allocId() else nav_id; | ||
| 208 | |||
| 209 | try cg.args.ensureUnusedCapacity(gpa, fn_info.param_types.len); | ||
| 210 | for (fn_info.param_types.get(ip)) |param_ty_index| { | ||
| 211 | const param_ty: Type = .fromInterned(param_ty_index); | ||
| 212 | if (!param_ty.hasRuntimeBits(zcu)) continue; | ||
| 213 | const param_ty_id = try cg.resolveType(param_ty, .direct); | ||
| 214 | cg.args.appendAssumeCapacity(.{ .id = cg.allocId(), .ty = param_ty_id }); | ||
| 215 | } | ||
| 216 | |||
| 217 | const root_block_id = cg.allocId(); | ||
| 218 | try cg.prologue.emit(gpa, .OpLabel, .{ .id_result = root_block_id }); | ||
| 219 | cg.block_label = root_block_id; | ||
| 220 | _ = try cg.genStructuredBody(.selection, cg.air.getMainBody()); | ||
| 221 | try cg.body.emit(gpa, .OpUnreachable, {}); | ||
| 222 | try cg.body.emit(gpa, .OpFunctionEnd, {}); | ||
| 244 | 223 | ||
| 245 | return cg.serializeToMir(gpa); | 224 | const fn_ty_id = try cg.functionType(return_ty_id, cg.args.items(.ty)); |
| 225 | try cg.module.emit(gpa, .OpFunction, .{ | ||
| 226 | .id_result_type = return_ty_id, | ||
| 227 | .id_result = function_id, | ||
| 228 | .function_control = .{}, | ||
| 229 | .function_type = fn_ty_id, | ||
| 230 | }); | ||
| 231 | for (cg.args.items(.id), cg.args.items(.ty)) |arg_id, arg_ty_id| { | ||
| 232 | try cg.module.emit(gpa, .OpFunctionParameter, .{ | ||
| 233 | .id_result_type = arg_ty_id, | ||
| 234 | .id_result = arg_id, | ||
| 235 | }); | ||
| 236 | } | ||
| 237 | try cg.module.append(gpa, cg.prologue); | ||
| 238 | try cg.module.append(gpa, cg.body); | ||
| 239 | if (is_test) try cg.generateTestEntryPoint(nav.fqn.toSlice(ip), nav_id, function_id); | ||
| 240 | try cg.debugName(function_id, nav.fqn.toSlice(ip)); | ||
| 241 | |||
| 242 | return cg.toOwnedMir(); | ||
| 246 | } | 243 | } |
| 247 | 244 | ||
| 248 | pub fn generateNav( | 245 | pub fn generateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) codegen.Error!Mir { |
| 249 | pt: Zcu.PerThread, | ||
| 250 | nav_index: InternPool.Nav.Index, | ||
| 251 | ) codegen.Error!Mir { | ||
| 252 | const zcu = pt.zcu; | 246 | const zcu = pt.zcu; |
| 253 | const gpa = zcu.gpa; | 247 | const gpa = zcu.gpa; |
| 248 | const ip = &zcu.intern_pool; | ||
| 249 | const target = zcu.getTarget(); | ||
| 250 | const nav = ip.getNav(nav_index); | ||
| 251 | const val = zcu.navValue(nav_index); | ||
| 252 | const ty = val.typeOf(zcu); | ||
| 254 | 253 | ||
| 255 | var arena = std.heap.ArenaAllocator.init(gpa); | 254 | var arena = std.heap.ArenaAllocator.init(gpa); |
| 256 | defer arena.deinit(); | 255 | defer arena.deinit(); |
| ... | @@ -267,116 +266,118 @@ pub fn generateNav( | ... | @@ -267,116 +266,118 @@ pub fn generateNav( |
| 267 | }; | 266 | }; |
| 268 | defer cg.deinit(); | 267 | defer cg.deinit(); |
| 269 | 268 | ||
| 270 | try cg.genNav(false); | 269 | const resolved = nav.resolved.?; |
| 271 | 270 | if (nav.getExtern(ip)) |ext| { | |
| 272 | return cg.serializeToMir(gpa); | 271 | if (!ty.hasRuntimeBits(zcu)) { |
| 273 | } | 272 | const child_ty = if (ty.zigTypeTag(zcu) == .pointer) ty.childType(zcu) else ty; |
| 273 | if (child_ty.zigTypeTag(zcu) != .spirv) return cg.toOwnedMir(); | ||
| 274 | } | ||
| 274 | 275 | ||
| 275 | fn serializeToMir(cg: *CodeGen, gpa: Allocator) codegen.Error!Mir { | 276 | const as = resolved.@"addrspace"; |
| 276 | const owner_entry = cg.nav_link.get(cg.owner_nav); | 277 | const storage_class = cg.storageClass(as); |
| 277 | const owner_decl_index = owner_entry orelse return .{ | 278 | const result_id = try cg.resolveNav(nav_index); |
| 278 | .id_bound = cg.next_result_id, | 279 | const ty_id = try cg.pointeeType(as, ty, true); |
| 279 | .owner_nav = cg.owner_nav, | 280 | const ptr_ty_id = try cg.ptrType(ty_id, storage_class); |
| 280 | .kind = .func, | ||
| 281 | .decl_result_id = .none, | ||
| 282 | .extended_instruction_set = &.{}, | ||
| 283 | .globals = &.{}, | ||
| 284 | .functions = &.{}, | ||
| 285 | .annotations = &.{}, | ||
| 286 | .debug_names = &.{}, | ||
| 287 | .debug_strings = &.{}, | ||
| 288 | .execution_modes = &.{}, | ||
| 289 | .nav_refs = &.{}, | ||
| 290 | .uav_refs = &.{}, | ||
| 291 | .decl_deps = &.{}, | ||
| 292 | .internal_globals = &.{}, | ||
| 293 | .entry_points = &.{}, | ||
| 294 | }; | ||
| 295 | 281 | ||
| 296 | const owner_decl = cg.declPtr(owner_decl_index); | 282 | try cg.module.emit(gpa, .OpVariable, .{ |
| 297 | 283 | .id_result_type = ptr_ty_id, | |
| 298 | var nav_refs: std.ArrayList(Mir.NavRef) = .empty; | 284 | .id_result = result_id, |
| 299 | defer nav_refs.deinit(gpa); | 285 | .storage_class = storage_class, |
| 300 | var nav_it = cg.nav_link.iterator(); | ||
| 301 | while (nav_it.next()) |entry| { | ||
| 302 | if (entry.key_ptr.* == cg.owner_nav) continue; | ||
| 303 | const decl = cg.declPtr(entry.value_ptr.*); | ||
| 304 | try nav_refs.append(gpa, .{ | ||
| 305 | .local_id = decl.result_id, | ||
| 306 | .nav = entry.key_ptr.*, | ||
| 307 | .kind = decl.kind, | ||
| 308 | }); | 286 | }); |
| 309 | } | ||
| 310 | 287 | ||
| 311 | var uav_refs: std.ArrayList(Mir.UavRef) = .empty; | 288 | switch (target.os.tag) { |
| 312 | defer uav_refs.deinit(gpa); | 289 | .vulkan, .opengl => { |
| 313 | var uav_it = cg.uav_link.iterator(); | 290 | switch (storage_class) { |
| 314 | while (uav_it.next()) |entry| { | 291 | .uniform, |
| 315 | const decl = cg.declPtr(entry.value_ptr.*); | 292 | .push_constant, |
| 316 | try uav_refs.append(gpa, .{ | 293 | .storage_buffer, |
| 317 | .local_id = decl.result_id, | 294 | .physical_storage_buffer, |
| 318 | .val = entry.key_ptr.*[0], | 295 | => { |
| 319 | .storage_class = entry.key_ptr.*[1], | 296 | if (ty.hasRuntimeBits(zcu)) { |
| 320 | .kind = decl.kind, | 297 | if (!ty.isSpirvRuntimeArray(zcu)) { |
| 321 | }); | 298 | try cg.decorate(ptr_ty_id, .{ .array_stride = .{ |
| 322 | } | 299 | .array_stride = @intCast(ty.abiSize(zcu)), |
| 300 | } }); | ||
| 301 | } | ||
| 302 | if (!cg.needsLayout(as, ty)) try cg.decorateLayout(ty, ty_id); | ||
| 303 | } | ||
| 304 | if (ext.is_const and storage_class == .storage_buffer) { | ||
| 305 | try cg.decorate(result_id, .non_writable); | ||
| 306 | } | ||
| 307 | }, | ||
| 308 | else => {}, | ||
| 309 | } | ||
| 323 | 310 | ||
| 324 | var decl_deps: std.ArrayList(Mir.DeclDep) = .empty; | 311 | if (ext.decoration) |decoration| switch (decoration) { |
| 325 | defer decl_deps.deinit(gpa); | 312 | .location => |location| { |
| 326 | var internal_globals: std.ArrayList(Id) = .empty; | 313 | try cg.decorate(result_id, .{ |
| 327 | defer internal_globals.deinit(gpa); | 314 | .location = .{ .location = location }, |
| 328 | 315 | }); | |
| 329 | const deps = cg.decl_deps.items[owner_decl.begin_dep..owner_decl.end_dep]; | 316 | }, |
| 330 | for (deps) |dep_index| { | 317 | .flat => |location| { |
| 331 | const dep_decl = cg.declPtr(dep_index); | 318 | try cg.decorate(result_id, .{ .location = .{ .location = location } }); |
| 332 | var found = false; | 319 | try cg.decorate(result_id, .flat); |
| 333 | nav_it.index = 0; | 320 | }, |
| 334 | while (nav_it.next()) |entry| { | 321 | .descriptor => |descriptor| { |
| 335 | if (entry.value_ptr.* == dep_index) { | 322 | try cg.decorate(result_id, .{ .binding = .{ |
| 336 | try decl_deps.append(gpa, .{ | 323 | .binding_point = descriptor.binding, |
| 337 | .kind = dep_decl.kind, | 324 | } }); |
| 338 | .nav = entry.key_ptr.*, | 325 | try cg.decorate(result_id, .{ .descriptor_set = .{ |
| 339 | }); | 326 | .descriptor_set = descriptor.set, |
| 340 | found = true; | 327 | } }); |
| 341 | break; | 328 | }, |
| 342 | } | 329 | }; |
| 330 | }, | ||
| 331 | else => {}, | ||
| 343 | } | 332 | } |
| 344 | if (!found and dep_decl.kind == .global) { | 333 | |
| 345 | try internal_globals.append(gpa, dep_decl.result_id); | 334 | if (std.meta.stringToEnum(spec.BuiltIn, nav.fqn.toSlice(ip))) |built_in| { |
| 335 | try cg.decorate(result_id, .{ .built_in = .{ .built_in = built_in } }); | ||
| 346 | } | 336 | } |
| 347 | } | ||
| 348 | 337 | ||
| 349 | var ep_list: std.ArrayList(Mir.EntryPoint) = .empty; | 338 | try cg.debugName(result_id, nav.fqn.toSlice(ip)); |
| 350 | defer ep_list.deinit(gpa); | 339 | } else if (resolved.@"threadlocal" and ty.hasRuntimeBits(zcu)) { |
| 351 | var ep_it = cg.entry_points.iterator(); | 340 | if (target.os.tag == .opencl) return cg.todo("threadlocal variables on OpenCL", .{}); |
| 352 | while (ep_it.next()) |entry| { | 341 | |
| 353 | const ep = entry.value_ptr; | 342 | const ty_id = try cg.resolveType(ty, .indirect); |
| 354 | const ep_decl = cg.declPtr(ep.decl_index); | 343 | const ptr_ty_id = try cg.ptrType(ty_id, .private); |
| 355 | try ep_list.append(gpa, .{ | 344 | const init_id = try cg.constant(ty, val, .indirect); |
| 356 | .local_id = ep_decl.result_id, | 345 | |
| 357 | .name = try gpa.dupe(u8, ep.name), | 346 | const result_id = try cg.resolveNav(nav_index); |
| 358 | .cc = ep.cc, | 347 | try cg.module.emit(gpa, .OpVariable, .{ |
| 348 | .id_result_type = ptr_ty_id, | ||
| 349 | .id_result = result_id, | ||
| 350 | .storage_class = .private, | ||
| 351 | .initializer = init_id, | ||
| 359 | }); | 352 | }); |
| 353 | try cg.debugName(result_id, nav.fqn.toSlice(ip)); | ||
| 360 | } | 354 | } |
| 355 | return cg.toOwnedMir(); | ||
| 356 | } | ||
| 361 | 357 | ||
| 362 | return .{ | 358 | fn toOwnedMir(cg: *CodeGen) Allocator.Error!Mir { |
| 359 | const gpa = cg.gpa; | ||
| 360 | |||
| 361 | const nav_refs = try gpa.alloc(Mir.NavRef, cg.navs.count()); | ||
| 362 | errdefer gpa.free(nav_refs); | ||
| 363 | for (nav_refs, cg.navs.keys(), cg.navs.values()) |*ref, ref_nav, id| { | ||
| 364 | ref.* = .{ .id = id, .nav = ref_nav }; | ||
| 365 | } | ||
| 366 | |||
| 367 | const externs = try gpa.dupe(Mir.Extern, cg.externs.values()); | ||
| 368 | errdefer gpa.free(externs); | ||
| 369 | |||
| 370 | const entry_points = try cg.entry_points.toOwnedSlice(gpa); | ||
| 371 | const mir: Mir = .{ | ||
| 363 | .id_bound = cg.next_result_id, | 372 | .id_bound = cg.next_result_id, |
| 364 | .owner_nav = cg.owner_nav, | 373 | .module = cg.module, |
| 365 | .kind = owner_decl.kind, | 374 | .nav_refs = nav_refs, |
| 366 | .decl_result_id = owner_decl.result_id, | 375 | .externs = externs, |
| 367 | .extended_instruction_set = try cg.sections.extended_instruction_set.instructions.toOwnedSlice(gpa), | 376 | .entry_points = entry_points, |
| 368 | .globals = try cg.sections.globals.instructions.toOwnedSlice(gpa), | ||
| 369 | .functions = try cg.sections.functions.instructions.toOwnedSlice(gpa), | ||
| 370 | .annotations = try cg.sections.annotations.instructions.toOwnedSlice(gpa), | ||
| 371 | .debug_names = try cg.sections.debug_names.instructions.toOwnedSlice(gpa), | ||
| 372 | .debug_strings = try cg.sections.debug_strings.instructions.toOwnedSlice(gpa), | ||
| 373 | .execution_modes = try cg.sections.execution_modes.instructions.toOwnedSlice(gpa), | ||
| 374 | .nav_refs = try nav_refs.toOwnedSlice(gpa), | ||
| 375 | .uav_refs = try uav_refs.toOwnedSlice(gpa), | ||
| 376 | .decl_deps = try decl_deps.toOwnedSlice(gpa), | ||
| 377 | .internal_globals = try internal_globals.toOwnedSlice(gpa), | ||
| 378 | .entry_points = try ep_list.toOwnedSlice(gpa), | ||
| 379 | }; | 377 | }; |
| 378 | cg.module = .empty; | ||
| 379 | |||
| 380 | return mir; | ||
| 380 | } | 381 | } |
| 381 | 382 | ||
| 382 | fn typeOf(cg: *CodeGen, inst: Air.Inst.Ref) Type { | 383 | fn typeOf(cg: *CodeGen, inst: Air.Inst.Ref) Type { |
| ... | @@ -389,22 +390,50 @@ fn typeOfIndex(cg: *CodeGen, inst: Air.Inst.Index) Type { | ... | @@ -389,22 +390,50 @@ fn typeOfIndex(cg: *CodeGen, inst: Air.Inst.Index) Type { |
| 389 | return cg.air.typeOfIndex(inst, &zcu.intern_pool); | 390 | return cg.air.typeOfIndex(inst, &zcu.intern_pool); |
| 390 | } | 391 | } |
| 391 | 392 | ||
| 392 | /// Does not generate the nav. | 393 | pub fn resolveNav(cg: *CodeGen, nav_index: InternPool.Nav.Index) !Id { |
| 393 | pub fn resolveNav(cg: *CodeGen, ip: *InternPool, nav_index: InternPool.Nav.Index) !Decl.Index { | 394 | const gop = try cg.navs.getOrPut(cg.gpa, nav_index); |
| 394 | const entry = try cg.nav_link.getOrPut(cg.gpa, nav_index); | 395 | if (!gop.found_existing) gop.value_ptr.* = cg.allocId(); |
| 395 | if (!entry.found_existing) { | 396 | return gop.value_ptr.*; |
| 396 | const nav = ip.getNav(nav_index); | 397 | } |
| 397 | // TODO: Extern fn? | 398 | |
| 398 | const kind: Decl.Kind = if (ip.isFunctionType(nav.resolved.?.type)) | 399 | fn resolveExternFn(cg: *CodeGen, nav_index: InternPool.Nav.Index) !Id { |
| 399 | .func | 400 | const gpa = cg.gpa; |
| 400 | else switch (nav.resolved.?.@"addrspace") { | 401 | const zcu = cg.zcu; |
| 401 | .generic => .invocation_global, | 402 | const ip = &zcu.intern_pool; |
| 402 | else => .global, | 403 | if (cg.externs.get(nav_index)) |ext| return ext.id; |
| 403 | }; | 404 | |
| 404 | entry.value_ptr.* = try cg.allocDecl(kind); | 405 | const nav = ip.getNav(nav_index); |
| 406 | const fn_ty: Type = .fromInterned(nav.resolved.?.type); | ||
| 407 | const fn_info = zcu.typeToFunc(fn_ty).?; | ||
| 408 | const return_ty_id = try cg.resolveFnReturnType(.fromInterned(fn_info.return_type)); | ||
| 409 | const fn_ty_id = try cg.resolveType(fn_ty, .direct); | ||
| 410 | const scratch_top = cg.id_scratch.items.len; | ||
| 411 | defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); | ||
| 412 | for (fn_info.param_types.get(ip)) |param_ty_index| { | ||
| 413 | const param_ty: Type = .fromInterned(param_ty_index); | ||
| 414 | if (!param_ty.hasRuntimeBits(zcu)) continue; | ||
| 415 | try cg.id_scratch.append(gpa, try cg.resolveType(param_ty, .direct)); | ||
| 405 | } | 416 | } |
| 406 | 417 | ||
| 407 | return entry.value_ptr.*; | 418 | const id = cg.allocId(); |
| 419 | try cg.externs.putNoClobber(gpa, nav_index, .{ | ||
| 420 | .id = id, | ||
| 421 | .name = nav.getExtern(ip).?.name.toSlice(ip), | ||
| 422 | }); | ||
| 423 | try cg.module.emit(gpa, .OpFunction, .{ | ||
| 424 | .id_result_type = return_ty_id, | ||
| 425 | .id_result = id, | ||
| 426 | .function_control = .{}, | ||
| 427 | .function_type = fn_ty_id, | ||
| 428 | }); | ||
| 429 | for (cg.id_scratch.items[scratch_top..]) |param_ty_id| { | ||
| 430 | try cg.module.emit(gpa, .OpFunctionParameter, .{ | ||
| 431 | .id_result_type = param_ty_id, | ||
| 432 | .id_result = cg.allocId(), | ||
| 433 | }); | ||
| 434 | } | ||
| 435 | try cg.module.emit(gpa, .OpFunctionEnd, {}); | ||
| 436 | return id; | ||
| 408 | } | 437 | } |
| 409 | 438 | ||
| 410 | pub fn allocIds(cg: *CodeGen, n: u32) spec.IdRange { | 439 | pub fn allocIds(cg: *CodeGen, n: u32) spec.IdRange { |
| ... | @@ -420,34 +449,10 @@ pub fn idBound(cg: *const CodeGen) Word { | ... | @@ -420,34 +449,10 @@ pub fn idBound(cg: *const CodeGen) Word { |
| 420 | return cg.next_result_id; | 449 | return cg.next_result_id; |
| 421 | } | 450 | } |
| 422 | 451 | ||
| 423 | pub fn addEntryPointDeps( | ||
| 424 | cg: *CodeGen, | ||
| 425 | decl_index: Decl.Index, | ||
| 426 | seen: *std.bit_set.Dynamic, | ||
| 427 | interface: *std.ArrayList(Id), | ||
| 428 | ) !void { | ||
| 429 | const decl = cg.declPtr(decl_index); | ||
| 430 | const deps = cg.decl_deps.items[decl.begin_dep..decl.end_dep]; | ||
| 431 | |||
| 432 | if (seen.isSet(@backingInt(decl_index))) { | ||
| 433 | return; | ||
| 434 | } | ||
| 435 | |||
| 436 | seen.set(@backingInt(decl_index)); | ||
| 437 | |||
| 438 | if (decl.kind == .global) { | ||
| 439 | try interface.append(cg.gpa, decl.result_id); | ||
| 440 | } | ||
| 441 | |||
| 442 | for (deps) |dep| { | ||
| 443 | try cg.addEntryPointDeps(dep, seen, interface); | ||
| 444 | } | ||
| 445 | } | ||
| 446 | |||
| 447 | pub fn importInstructionSet(cg: *CodeGen, set: spec.InstructionSet) !Id { | 452 | pub fn importInstructionSet(cg: *CodeGen, set: spec.InstructionSet) !Id { |
| 448 | assert(set != .core); | 453 | assert(set != .core); |
| 449 | const result_id = cg.allocId(); | 454 | const result_id = cg.allocId(); |
| 450 | try cg.sections.extended_instruction_set.emit(cg.gpa, .OpExtInstImport, .{ | 455 | try cg.module.emit(cg.gpa, .OpExtInstImport, .{ |
| 451 | .id_result = result_id, | 456 | .id_result = result_id, |
| 452 | .name = @tagName(set), | 457 | .name = @tagName(set), |
| 453 | }); | 458 | }); |
| ... | @@ -456,7 +461,7 @@ pub fn importInstructionSet(cg: *CodeGen, set: spec.InstructionSet) !Id { | ... | @@ -456,7 +461,7 @@ pub fn importInstructionSet(cg: *CodeGen, set: spec.InstructionSet) !Id { |
| 456 | 461 | ||
| 457 | pub fn boolType(cg: *CodeGen) !Id { | 462 | pub fn boolType(cg: *CodeGen) !Id { |
| 458 | const result_id = cg.allocId(); | 463 | const result_id = cg.allocId(); |
| 459 | try cg.sections.globals.emit(cg.gpa, .OpTypeBool, .{ | 464 | try cg.module.emit(cg.gpa, .OpTypeBool, .{ |
| 460 | .id_result = result_id, | 465 | .id_result = result_id, |
| 461 | }); | 466 | }); |
| 462 | return result_id; | 467 | return result_id; |
| ... | @@ -464,20 +469,17 @@ pub fn boolType(cg: *CodeGen) !Id { | ... | @@ -464,20 +469,17 @@ pub fn boolType(cg: *CodeGen) !Id { |
| 464 | 469 | ||
| 465 | pub fn voidType(cg: *CodeGen) !Id { | 470 | pub fn voidType(cg: *CodeGen) !Id { |
| 466 | const result_id = cg.allocId(); | 471 | const result_id = cg.allocId(); |
| 467 | try cg.sections.globals.emit(cg.gpa, .OpTypeVoid, .{ | 472 | try cg.module.emit(cg.gpa, .OpTypeVoid, .{ |
| 468 | .id_result = result_id, | 473 | .id_result = result_id, |
| 469 | }); | 474 | }); |
| 470 | try cg.debugName(result_id, "void"); | 475 | try cg.debugName(result_id, "void"); |
| 471 | return result_id; | 476 | return result_id; |
| 472 | } | 477 | } |
| 473 | 478 | ||
| 474 | pub fn opaqueType(cg: *CodeGen, name: []const u8) !Id { | 479 | pub fn opaqueType(cg: *CodeGen, comptime fmt: []const u8, args: anytype) !Id { |
| 475 | const result_id = cg.allocId(); | 480 | const result_id = cg.allocId(); |
| 476 | try cg.sections.globals.emit(cg.gpa, .OpTypeOpaque, .{ | 481 | try cg.module.emitFormatted(cg.gpa, .OpTypeOpaque, &.{result_id}, fmt, args); |
| 477 | .id_result = result_id, | 482 | try cg.debugNameFmt(result_id, fmt, args); |
| 478 | .literal_string = name, | ||
| 479 | }); | ||
| 480 | try cg.debugName(result_id, name); | ||
| 481 | return result_id; | 483 | return result_id; |
| 482 | } | 484 | } |
| 483 | 485 | ||
| ... | @@ -513,7 +515,7 @@ pub fn intType(cg: *CodeGen, signedness: std.lang.Signedness, bits: u16) !Id { | ... | @@ -513,7 +515,7 @@ pub fn intType(cg: *CodeGen, signedness: std.lang.Signedness, bits: u16) !Id { |
| 513 | const limb_ty = try cg.intType(.unsigned, limb_bits); | 515 | const limb_ty = try cg.intType(.unsigned, limb_bits); |
| 514 | const len_ty = try cg.intType(.unsigned, 32); | 516 | const len_ty = try cg.intType(.unsigned, 32); |
| 515 | const len_id = cg.allocId(); | 517 | const len_id = cg.allocId(); |
| 516 | try cg.sections.globals.emit(cg.gpa, .OpConstant, .{ | 518 | try cg.module.emit(cg.gpa, .OpConstant, .{ |
| 517 | .id_result_type = len_ty, | 519 | .id_result_type = len_ty, |
| 518 | .id_result = len_id, | 520 | .id_result = len_id, |
| 519 | .value = .{ .uint32 = backing_bits / limb_bits }, | 521 | .value = .{ .uint32 = backing_bits / limb_bits }, |
| ... | @@ -522,7 +524,7 @@ pub fn intType(cg: *CodeGen, signedness: std.lang.Signedness, bits: u16) !Id { | ... | @@ -522,7 +524,7 @@ pub fn intType(cg: *CodeGen, signedness: std.lang.Signedness, bits: u16) !Id { |
| 522 | } | 524 | } |
| 523 | 525 | ||
| 524 | const result_id = cg.allocId(); | 526 | const result_id = cg.allocId(); |
| 525 | try cg.sections.globals.emit(cg.gpa, .OpTypeInt, .{ | 527 | try cg.module.emit(cg.gpa, .OpTypeInt, .{ |
| 526 | .id_result = result_id, | 528 | .id_result = result_id, |
| 527 | .width = backing_bits, | 529 | .width = backing_bits, |
| 528 | .signedness = switch (actual_signedness) { | 530 | .signedness = switch (actual_signedness) { |
| ... | @@ -540,7 +542,7 @@ pub fn intType(cg: *CodeGen, signedness: std.lang.Signedness, bits: u16) !Id { | ... | @@ -540,7 +542,7 @@ pub fn intType(cg: *CodeGen, signedness: std.lang.Signedness, bits: u16) !Id { |
| 540 | pub fn floatType(cg: *CodeGen, bits: u16) !Id { | 542 | pub fn floatType(cg: *CodeGen, bits: u16) !Id { |
| 541 | assert(bits > 0); | 543 | assert(bits > 0); |
| 542 | const result_id = cg.allocId(); | 544 | const result_id = cg.allocId(); |
| 543 | try cg.sections.globals.emit(cg.gpa, .OpTypeFloat, .{ | 545 | try cg.module.emit(cg.gpa, .OpTypeFloat, .{ |
| 544 | .id_result = result_id, | 546 | .id_result = result_id, |
| 545 | .width = bits, | 547 | .width = bits, |
| 546 | }); | 548 | }); |
| ... | @@ -550,7 +552,7 @@ pub fn floatType(cg: *CodeGen, bits: u16) !Id { | ... | @@ -550,7 +552,7 @@ pub fn floatType(cg: *CodeGen, bits: u16) !Id { |
| 550 | 552 | ||
| 551 | pub fn vectorType(cg: *CodeGen, len: u32, child_ty_id: Id) !Id { | 553 | pub fn vectorType(cg: *CodeGen, len: u32, child_ty_id: Id) !Id { |
| 552 | const result_id = cg.allocId(); | 554 | const result_id = cg.allocId(); |
| 553 | try cg.sections.globals.emit(cg.gpa, .OpTypeVector, .{ | 555 | try cg.module.emit(cg.gpa, .OpTypeVector, .{ |
| 554 | .id_result = result_id, | 556 | .id_result = result_id, |
| 555 | .component_type = child_ty_id, | 557 | .component_type = child_ty_id, |
| 556 | .component_count = len, | 558 | .component_count = len, |
| ... | @@ -560,7 +562,7 @@ pub fn vectorType(cg: *CodeGen, len: u32, child_ty_id: Id) !Id { | ... | @@ -560,7 +562,7 @@ pub fn vectorType(cg: *CodeGen, len: u32, child_ty_id: Id) !Id { |
| 560 | 562 | ||
| 561 | pub fn arrayType(cg: *CodeGen, len_id: Id, child_ty_id: Id) !Id { | 563 | pub fn arrayType(cg: *CodeGen, len_id: Id, child_ty_id: Id) !Id { |
| 562 | const result_id = cg.allocId(); | 564 | const result_id = cg.allocId(); |
| 563 | try cg.sections.globals.emit(cg.gpa, .OpTypeArray, .{ | 565 | try cg.module.emit(cg.gpa, .OpTypeArray, .{ |
| 564 | .id_result = result_id, | 566 | .id_result = result_id, |
| 565 | .element_type = child_ty_id, | 567 | .element_type = child_ty_id, |
| 566 | .length = len_id, | 568 | .length = len_id, |
| ... | @@ -570,7 +572,7 @@ pub fn arrayType(cg: *CodeGen, len_id: Id, child_ty_id: Id) !Id { | ... | @@ -570,7 +572,7 @@ pub fn arrayType(cg: *CodeGen, len_id: Id, child_ty_id: Id) !Id { |
| 570 | 572 | ||
| 571 | pub fn ptrType(cg: *CodeGen, child_ty_id: Id, storage_class: spec.StorageClass) !Id { | 573 | pub fn ptrType(cg: *CodeGen, child_ty_id: Id, storage_class: spec.StorageClass) !Id { |
| 572 | const result_id = cg.allocId(); | 574 | const result_id = cg.allocId(); |
| 573 | try cg.sections.globals.emit(cg.gpa, .OpTypePointer, .{ | 575 | try cg.module.emit(cg.gpa, .OpTypePointer, .{ |
| 574 | .id_result = result_id, | 576 | .id_result = result_id, |
| 575 | .storage_class = storage_class, | 577 | .storage_class = storage_class, |
| 576 | .type = child_ty_id, | 578 | .type = child_ty_id, |
| ... | @@ -589,7 +591,7 @@ pub fn structType( | ... | @@ -589,7 +591,7 @@ pub fn structType( |
| 589 | if (cg.struct_types.get(.{ .fields = types, .ip_index = actual_ip_index })) |id| return id; | 591 | if (cg.struct_types.get(.{ .fields = types, .ip_index = actual_ip_index })) |id| return id; |
| 590 | const result_id = cg.allocId(); | 592 | const result_id = cg.allocId(); |
| 591 | const types_dup = try cg.arena.dupe(Id, types); | 593 | const types_dup = try cg.arena.dupe(Id, types); |
| 592 | try cg.sections.globals.emit(cg.gpa, .OpTypeStruct, .{ | 594 | try cg.module.emit(cg.gpa, .OpTypeStruct, .{ |
| 593 | .id_result = result_id, | 595 | .id_result = result_id, |
| 594 | .id_ref = types_dup, | 596 | .id_ref = types_dup, |
| 595 | }); | 597 | }); |
| ... | @@ -639,7 +641,7 @@ pub fn layoutType(cg: *CodeGen, ty: Type, is_block_root: bool) Error!Id { | ... | @@ -639,7 +641,7 @@ pub fn layoutType(cg: *CodeGen, ty: Type, is_block_root: bool) Error!Id { |
| 639 | } }); | 641 | } }); |
| 640 | try member_types.append(gpa, try cg.layoutType(field_ty, false)); | 642 | try member_types.append(gpa, try cg.layoutType(field_ty, false)); |
| 641 | } | 643 | } |
| 642 | try cg.sections.globals.emit(gpa, .OpTypeStruct, .{ | 644 | try cg.module.emit(gpa, .OpTypeStruct, .{ |
| 643 | .id_result = id, | 645 | .id_result = id, |
| 644 | .id_ref = member_types.items, | 646 | .id_ref = member_types.items, |
| 645 | }); | 647 | }); |
| ... | @@ -682,7 +684,7 @@ pub fn layoutType(cg: *CodeGen, ty: Type, is_block_root: bool) Error!Id { | ... | @@ -682,7 +684,7 @@ pub fn layoutType(cg: *CodeGen, ty: Type, is_block_root: bool) Error!Id { |
| 682 | try cg.decorate(arr_id, .{ .array_stride = .{ .array_stride = 1 } }); | 684 | try cg.decorate(arr_id, .{ .array_stride = .{ .array_stride = 1 } }); |
| 683 | member_types[layout.padding_index] = arr_id; | 685 | member_types[layout.padding_index] = arr_id; |
| 684 | } | 686 | } |
| 685 | try cg.sections.globals.emit(gpa, .OpTypeStruct, .{ | 687 | try cg.module.emit(gpa, .OpTypeStruct, .{ |
| 686 | .id_result = id, | 688 | .id_result = id, |
| 687 | .id_ref = member_types[0..layout.total_fields], | 689 | .id_ref = member_types[0..layout.total_fields], |
| 688 | }); | 690 | }); |
| ... | @@ -705,7 +707,7 @@ pub fn layoutType(cg: *CodeGen, ty: Type, is_block_root: bool) Error!Id { | ... | @@ -705,7 +707,7 @@ pub fn layoutType(cg: *CodeGen, ty: Type, is_block_root: bool) Error!Id { |
| 705 | const elem_ty = ty.childType(zcu); | 707 | const elem_ty = ty.childType(zcu); |
| 706 | const elem_ty_id = try cg.layoutType(elem_ty, is_block_root); | 708 | const elem_ty_id = try cg.layoutType(elem_ty, is_block_root); |
| 707 | const id = cg.allocId(); | 709 | const id = cg.allocId(); |
| 708 | try cg.sections.globals.emit(gpa, .OpTypeRuntimeArray, .{ | 710 | try cg.module.emit(gpa, .OpTypeRuntimeArray, .{ |
| 709 | .id_result = id, | 711 | .id_result = id, |
| 710 | .element_type = elem_ty_id, | 712 | .element_type = elem_ty_id, |
| 711 | }); | 713 | }); |
| ... | @@ -724,7 +726,7 @@ pub fn layoutType(cg: *CodeGen, ty: Type, is_block_root: bool) Error!Id { | ... | @@ -724,7 +726,7 @@ pub fn layoutType(cg: *CodeGen, ty: Type, is_block_root: bool) Error!Id { |
| 724 | 726 | ||
| 725 | pub fn functionType(cg: *CodeGen, return_ty_id: Id, param_type_ids: []const Id) !Id { | 727 | pub fn functionType(cg: *CodeGen, return_ty_id: Id, param_type_ids: []const Id) !Id { |
| 726 | const result_id = cg.allocId(); | 728 | const result_id = cg.allocId(); |
| 727 | try cg.sections.globals.emit(cg.gpa, .OpTypeFunction, .{ | 729 | try cg.module.emit(cg.gpa, .OpTypeFunction, .{ |
| 728 | .id_result = result_id, | 730 | .id_result = result_id, |
| 729 | .return_type = return_ty_id, | 731 | .return_type = return_ty_id, |
| 730 | .id_ref_2 = param_type_ids, | 732 | .id_ref_2 = param_type_ids, |
| ... | @@ -734,7 +736,7 @@ pub fn functionType(cg: *CodeGen, return_ty_id: Id, param_type_ids: []const Id) | ... | @@ -734,7 +736,7 @@ pub fn functionType(cg: *CodeGen, return_ty_id: Id, param_type_ids: []const Id) |
| 734 | 736 | ||
| 735 | pub fn constUndef(cg: *CodeGen, ty_id: Id) !Id { | 737 | pub fn constUndef(cg: *CodeGen, ty_id: Id) !Id { |
| 736 | const result_id = cg.allocId(); | 738 | const result_id = cg.allocId(); |
| 737 | try cg.sections.globals.emit(cg.gpa, .OpUndef, .{ | 739 | try cg.module.emit(cg.gpa, .OpUndef, .{ |
| 738 | .id_result_type = ty_id, | 740 | .id_result_type = ty_id, |
| 739 | .id_result = result_id, | 741 | .id_result = result_id, |
| 740 | }); | 742 | }); |
| ... | @@ -743,7 +745,7 @@ pub fn constUndef(cg: *CodeGen, ty_id: Id) !Id { | ... | @@ -743,7 +745,7 @@ pub fn constUndef(cg: *CodeGen, ty_id: Id) !Id { |
| 743 | 745 | ||
| 744 | pub fn constNull(cg: *CodeGen, ty_id: Id) !Id { | 746 | pub fn constNull(cg: *CodeGen, ty_id: Id) !Id { |
| 745 | const result_id = cg.allocId(); | 747 | const result_id = cg.allocId(); |
| 746 | try cg.sections.globals.emit(cg.gpa, .OpConstantNull, .{ | 748 | try cg.module.emit(cg.gpa, .OpConstantNull, .{ |
| 747 | .id_result_type = ty_id, | 749 | .id_result_type = ty_id, |
| 748 | .id_result = result_id, | 750 | .id_result = result_id, |
| 749 | }); | 751 | }); |
| ... | @@ -755,7 +757,7 @@ pub fn decorate( | ... | @@ -755,7 +757,7 @@ pub fn decorate( |
| 755 | target: Id, | 757 | target: Id, |
| 756 | decoration: spec.Decoration.Extended, | 758 | decoration: spec.Decoration.Extended, |
| 757 | ) !void { | 759 | ) !void { |
| 758 | try cg.sections.annotations.emit(cg.gpa, .OpDecorate, .{ | 760 | try cg.module.emit(cg.gpa, .OpDecorate, .{ |
| 759 | .target = target, | 761 | .target = target, |
| 760 | .decoration = decoration, | 762 | .decoration = decoration, |
| 761 | }); | 763 | }); |
| ... | @@ -767,29 +769,16 @@ pub fn decorateMember( | ... | @@ -767,29 +769,16 @@ pub fn decorateMember( |
| 767 | member: u32, | 769 | member: u32, |
| 768 | decoration: spec.Decoration.Extended, | 770 | decoration: spec.Decoration.Extended, |
| 769 | ) !void { | 771 | ) !void { |
| 770 | try cg.sections.annotations.emit(cg.gpa, .OpMemberDecorate, .{ | 772 | try cg.module.emit(cg.gpa, .OpMemberDecorate, .{ |
| 771 | .structure_type = structure_type, | 773 | .structure_type = structure_type, |
| 772 | .member = member, | 774 | .member = member, |
| 773 | .decoration = decoration, | 775 | .decoration = decoration, |
| 774 | }); | 776 | }); |
| 775 | } | 777 | } |
| 776 | 778 | ||
| 777 | pub fn allocDecl(cg: *CodeGen, kind: Decl.Kind) !Decl.Index { | ||
| 778 | try cg.decls.append(cg.gpa, .{ | ||
| 779 | .kind = kind, | ||
| 780 | .result_id = cg.allocId(), | ||
| 781 | }); | ||
| 782 | |||
| 783 | return @as(Decl.Index, @fromBackingInt(@intCast(@as(u32, @intCast(cg.decls.items.len - 1))))); | ||
| 784 | } | ||
| 785 | |||
| 786 | pub fn declPtr(cg: *CodeGen, index: Decl.Index) *Decl { | ||
| 787 | return &cg.decls.items[@backingInt(index)]; | ||
| 788 | } | ||
| 789 | |||
| 790 | pub fn debugName(cg: *CodeGen, target: Id, name: []const u8) !void { | 779 | pub fn debugName(cg: *CodeGen, target: Id, name: []const u8) !void { |
| 791 | if (cg.zcu.comp.config.root_strip) return; | 780 | if (cg.zcu.comp.config.root_strip) return; |
| 792 | try cg.sections.debug_names.emit(cg.gpa, .OpName, .{ | 781 | try cg.module.emit(cg.gpa, .OpName, .{ |
| 793 | .target = target, | 782 | .target = target, |
| 794 | .name = name, | 783 | .name = name, |
| 795 | }); | 784 | }); |
| ... | @@ -797,14 +786,12 @@ pub fn debugName(cg: *CodeGen, target: Id, name: []const u8) !void { | ... | @@ -797,14 +786,12 @@ pub fn debugName(cg: *CodeGen, target: Id, name: []const u8) !void { |
| 797 | 786 | ||
| 798 | pub fn debugNameFmt(cg: *CodeGen, target: Id, comptime fmt: []const u8, args: anytype) !void { | 787 | pub fn debugNameFmt(cg: *CodeGen, target: Id, comptime fmt: []const u8, args: anytype) !void { |
| 799 | if (cg.zcu.comp.config.root_strip) return; | 788 | if (cg.zcu.comp.config.root_strip) return; |
| 800 | const name = try std.fmt.allocPrint(cg.gpa, fmt, args); | 789 | try cg.module.emitFormatted(cg.gpa, .OpName, &.{target}, fmt, args); |
| 801 | defer cg.gpa.free(name); | ||
| 802 | try cg.debugName(target, name); | ||
| 803 | } | 790 | } |
| 804 | 791 | ||
| 805 | pub fn memberDebugName(cg: *CodeGen, target: Id, member: u32, name: []const u8) !void { | 792 | pub fn memberDebugName(cg: *CodeGen, target: Id, member: u32, name: []const u8) !void { |
| 806 | if (cg.zcu.comp.config.root_strip) return; | 793 | if (cg.zcu.comp.config.root_strip) return; |
| 807 | try cg.sections.debug_names.emit(cg.gpa, .OpMemberName, .{ | 794 | try cg.module.emit(cg.gpa, .OpMemberName, .{ |
| 808 | .type = target, | 795 | .type = target, |
| 809 | .member = member, | 796 | .member = member, |
| 810 | .name = name, | 797 | .name = name, |
| ... | @@ -827,6 +814,7 @@ pub fn storageClass(cg: *const CodeGen, as: std.lang.AddressSpace) spec.StorageC | ... | @@ -827,6 +814,7 @@ pub fn storageClass(cg: *const CodeGen, as: std.lang.AddressSpace) spec.StorageC |
| 827 | .constant => .uniform_constant, | 814 | .constant => .uniform_constant, |
| 828 | .shared => .workgroup, | 815 | .shared => .workgroup, |
| 829 | .local => .function, | 816 | .local => .function, |
| 817 | .private => .private, | ||
| 830 | .input => .input, | 818 | .input => .input, |
| 831 | .gs, | 819 | .gs, |
| 832 | .fs, | 820 | .fs, |
| ... | @@ -850,228 +838,6 @@ pub fn storageClass(cg: *const CodeGen, as: std.lang.AddressSpace) spec.StorageC | ... | @@ -850,228 +838,6 @@ pub fn storageClass(cg: *const CodeGen, as: std.lang.AddressSpace) spec.StorageC |
| 850 | 838 | ||
| 851 | const Error = codegen.Error; | 839 | const Error = codegen.Error; |
| 852 | 840 | ||
| 853 | pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void { | ||
| 854 | const gpa = cg.gpa; | ||
| 855 | const zcu = cg.zcu; | ||
| 856 | const ip = &zcu.intern_pool; | ||
| 857 | const target = zcu.getTarget(); | ||
| 858 | |||
| 859 | const nav = ip.getNav(cg.owner_nav); | ||
| 860 | const val = zcu.navValue(cg.owner_nav); | ||
| 861 | const ty = val.typeOf(zcu); | ||
| 862 | |||
| 863 | if (!do_codegen and !ty.hasRuntimeBits(zcu)) { | ||
| 864 | const child_ty = if (ty.zigTypeTag(zcu) == .pointer) ty.childType(zcu) else ty; | ||
| 865 | if (child_ty.zigTypeTag(zcu) != .spirv) return; | ||
| 866 | } | ||
| 867 | |||
| 868 | const spv_decl_index = try cg.resolveNav(ip, cg.owner_nav); | ||
| 869 | const decl = cg.declPtr(spv_decl_index); | ||
| 870 | const result_id = decl.result_id; | ||
| 871 | decl.begin_dep = cg.decl_deps.items.len; | ||
| 872 | |||
| 873 | switch (decl.kind) { | ||
| 874 | .func => { | ||
| 875 | if (nav.resolved.?.is_extern_decl) { | ||
| 876 | _ = try cg.resolveType(ty, .direct); | ||
| 877 | try emitExternFnStub(cg, nav, decl, ty); | ||
| 878 | decl.end_dep = cg.decl_deps.items.len; | ||
| 879 | return; | ||
| 880 | } | ||
| 881 | |||
| 882 | const fn_info = zcu.typeToFunc(ty).?; | ||
| 883 | const return_ty_id = try cg.resolveFnReturnType(.fromInterned(fn_info.return_type)); | ||
| 884 | const is_test = zcu.test_functions.contains(cg.owner_nav); | ||
| 885 | |||
| 886 | const func_result_id = if (is_test) cg.allocId() else result_id; | ||
| 887 | const prototype_ty_id = try cg.resolveType(ty, .direct); | ||
| 888 | try cg.prologue.emit(gpa, .OpFunction, .{ | ||
| 889 | .id_result_type = return_ty_id, | ||
| 890 | .id_result = func_result_id, | ||
| 891 | .function_type = prototype_ty_id, | ||
| 892 | // Note: the backend will never be asked to generate an inline function | ||
| 893 | // (this is handled in sema), so we don't need to set function_control here. | ||
| 894 | .function_control = .{}, | ||
| 895 | }); | ||
| 896 | |||
| 897 | try cg.args.ensureUnusedCapacity(gpa, fn_info.param_types.len); | ||
| 898 | for (fn_info.param_types.get(ip)) |param_ty_index| { | ||
| 899 | const param_ty: Type = .fromInterned(param_ty_index); | ||
| 900 | if (!param_ty.hasRuntimeBits(zcu)) continue; | ||
| 901 | |||
| 902 | const param_type_id = try cg.resolveType(param_ty, .direct); | ||
| 903 | const arg_result_id = cg.allocId(); | ||
| 904 | try cg.prologue.emit(gpa, .OpFunctionParameter, .{ | ||
| 905 | .id_result_type = param_type_id, | ||
| 906 | .id_result = arg_result_id, | ||
| 907 | }); | ||
| 908 | cg.args.appendAssumeCapacity(arg_result_id); | ||
| 909 | } | ||
| 910 | |||
| 911 | // TODO: This could probably be done in a better way... | ||
| 912 | const root_block_id = cg.allocId(); | ||
| 913 | |||
| 914 | // The root block of a function declaration should appear before OpVariable instructions, | ||
| 915 | // so it is generated into the function's prologue. | ||
| 916 | try cg.prologue.emit(gpa, .OpLabel, .{ | ||
| 917 | .id_result = root_block_id, | ||
| 918 | }); | ||
| 919 | cg.block_label = root_block_id; | ||
| 920 | |||
| 921 | const main_body = cg.air.getMainBody(); | ||
| 922 | _ = try cg.genStructuredBody(.selection, main_body); | ||
| 923 | // We always expect paths to here to end, but we still need the block | ||
| 924 | // to act as a dummy merge block. | ||
| 925 | try cg.body.emit(gpa, .OpUnreachable, {}); | ||
| 926 | try cg.body.emit(gpa, .OpFunctionEnd, {}); | ||
| 927 | // Append the actual code into the functions section. | ||
| 928 | try cg.sections.functions.append(gpa, cg.prologue); | ||
| 929 | try cg.sections.functions.append(gpa, cg.body); | ||
| 930 | |||
| 931 | // Temporarily generate a test kernel declaration if this is a test function. | ||
| 932 | if (is_test) { | ||
| 933 | try cg.generateTestEntryPoint(nav.fqn.toSlice(ip), spv_decl_index, func_result_id); | ||
| 934 | } | ||
| 935 | |||
| 936 | try cg.debugName(func_result_id, nav.fqn.toSlice(ip)); | ||
| 937 | }, | ||
| 938 | .global => { | ||
| 939 | const key = ip.indexToKey(val.toIntern()).@"extern"; | ||
| 940 | |||
| 941 | const storage_class = cg.storageClass(nav.resolved.?.@"addrspace"); | ||
| 942 | assert(storage_class != .generic); // These should be instance globals | ||
| 943 | |||
| 944 | const as = nav.resolved.?.@"addrspace"; | ||
| 945 | const ty_id = try cg.pointeeType(as, ty, true); | ||
| 946 | const ptr_ty_id = try cg.ptrType(ty_id, storage_class); | ||
| 947 | |||
| 948 | try cg.sections.globals.emit(gpa, .OpVariable, .{ | ||
| 949 | .id_result_type = ptr_ty_id, | ||
| 950 | .id_result = result_id, | ||
| 951 | .storage_class = storage_class, | ||
| 952 | }); | ||
| 953 | |||
| 954 | switch (target.os.tag) { | ||
| 955 | .vulkan, .opengl => { | ||
| 956 | switch (storage_class) { | ||
| 957 | .uniform, | ||
| 958 | .push_constant, | ||
| 959 | .storage_buffer, | ||
| 960 | .physical_storage_buffer, | ||
| 961 | => { | ||
| 962 | if (ty.hasRuntimeBits(zcu)) { | ||
| 963 | if (!ty.isSpirvRuntimeArray(zcu)) { | ||
| 964 | try cg.decorate( | ||
| 965 | ptr_ty_id, | ||
| 966 | .{ .array_stride = .{ .array_stride = @intCast(ty.abiSize(zcu)) } }, | ||
| 967 | ); | ||
| 968 | } | ||
| 969 | if (!cg.needsLayout(as, ty)) try cg.decorateLayout(ty, ty_id); | ||
| 970 | } | ||
| 971 | if (key.is_const and storage_class == .storage_buffer) { | ||
| 972 | try cg.decorate(result_id, .non_writable); | ||
| 973 | } | ||
| 974 | }, | ||
| 975 | else => {}, | ||
| 976 | } | ||
| 977 | |||
| 978 | if (key.decoration) |decoration| switch (decoration) { | ||
| 979 | .location => |location| { | ||
| 980 | if (storage_class != .output and storage_class != .input and storage_class != .uniform_constant) { | ||
| 981 | return cg.fail("storage class must be one of (output, input, uniform_constant) but is {s}", .{@tagName(storage_class)}); | ||
| 982 | } | ||
| 983 | try cg.decorate(result_id, .{ | ||
| 984 | .location = .{ .location = location }, | ||
| 985 | }); | ||
| 986 | }, | ||
| 987 | .flat => |location| { | ||
| 988 | try cg.decorate(result_id, .{ .location = .{ .location = location } }); | ||
| 989 | try cg.decorate(result_id, .flat); | ||
| 990 | }, | ||
| 991 | .descriptor => |descriptor| { | ||
| 992 | if (storage_class != .storage_buffer and storage_class != .uniform and storage_class != .uniform_constant) { | ||
| 993 | return cg.fail("storage class must be one of (storage_buffer, uniform, uniform_constant) but is {s}", .{@tagName(storage_class)}); | ||
| 994 | } | ||
| 995 | try cg.decorate(result_id, .{ | ||
| 996 | .binding = .{ .binding_point = descriptor.binding }, | ||
| 997 | }); | ||
| 998 | |||
| 999 | try cg.decorate(result_id, .{ | ||
| 1000 | .descriptor_set = .{ .descriptor_set = descriptor.set }, | ||
| 1001 | }); | ||
| 1002 | }, | ||
| 1003 | }; | ||
| 1004 | }, | ||
| 1005 | else => {}, | ||
| 1006 | } | ||
| 1007 | |||
| 1008 | if (std.meta.stringToEnum(spec.BuiltIn, nav.fqn.toSlice(ip))) |built_in| { | ||
| 1009 | try cg.decorate(result_id, .{ .built_in = .{ .built_in = built_in } }); | ||
| 1010 | } | ||
| 1011 | |||
| 1012 | try cg.debugName(result_id, nav.fqn.toSlice(ip)); | ||
| 1013 | }, | ||
| 1014 | .invocation_global => { | ||
| 1015 | // `@extern()` produces an invocation_global whose value is a | ||
| 1016 | // comptime-known pointer to an underlying extern symbol's Nav. | ||
| 1017 | // The pointer is inlined at use sites so we don't need a Function-scope wrapper here. | ||
| 1018 | if (ip.indexToKey(val.toIntern()) == .ptr) alias: { | ||
| 1019 | const ptr_key = ip.indexToKey(val.toIntern()).ptr; | ||
| 1020 | if (ptr_key.base_addr != .nav or ptr_key.byte_offset != 0) break :alias; | ||
| 1021 | const underlying_nav = ip.getNav(ptr_key.base_addr.nav); | ||
| 1022 | if (!underlying_nav.resolved.?.is_extern_decl) break :alias; | ||
| 1023 | cg.declPtr(spv_decl_index).end_dep = cg.decl_deps.items.len; | ||
| 1024 | return; | ||
| 1025 | } | ||
| 1026 | |||
| 1027 | const ty_id = try cg.resolveType(ty, .indirect); | ||
| 1028 | const ptr_ty_id = try cg.ptrType(ty_id, .function); | ||
| 1029 | |||
| 1030 | // TODO: Combine with resolveAnonDecl? | ||
| 1031 | const void_ty_id = try cg.resolveType(.void, .direct); | ||
| 1032 | const initializer_proto_ty_id = try cg.functionType(void_ty_id, &.{}); | ||
| 1033 | |||
| 1034 | const initializer_id = cg.allocId(); | ||
| 1035 | try cg.prologue.emit(gpa, .OpFunction, .{ | ||
| 1036 | .id_result_type = try cg.resolveType(.void, .direct), | ||
| 1037 | .id_result = initializer_id, | ||
| 1038 | .function_control = .{}, | ||
| 1039 | .function_type = initializer_proto_ty_id, | ||
| 1040 | }); | ||
| 1041 | |||
| 1042 | const root_block_id = cg.allocId(); | ||
| 1043 | try cg.prologue.emit(gpa, .OpLabel, .{ | ||
| 1044 | .id_result = root_block_id, | ||
| 1045 | }); | ||
| 1046 | cg.block_label = root_block_id; | ||
| 1047 | |||
| 1048 | const val_id = try cg.constant(ty, val, .indirect); | ||
| 1049 | try cg.body.emit(gpa, .OpStore, .{ | ||
| 1050 | .pointer = result_id, | ||
| 1051 | .object = val_id, | ||
| 1052 | }); | ||
| 1053 | |||
| 1054 | try cg.body.emit(gpa, .OpReturn, {}); | ||
| 1055 | try cg.body.emit(gpa, .OpFunctionEnd, {}); | ||
| 1056 | try cg.sections.functions.append(gpa, cg.prologue); | ||
| 1057 | try cg.sections.functions.append(gpa, cg.body); | ||
| 1058 | |||
| 1059 | try cg.debugNameFmt(initializer_id, "initializer of {f}", .{nav.fqn.fmt(ip)}); | ||
| 1060 | try cg.debugName(result_id, nav.fqn.toSlice(ip)); | ||
| 1061 | |||
| 1062 | try cg.sections.globals.emit(gpa, .OpExtInst, .{ | ||
| 1063 | .id_result_type = ptr_ty_id, | ||
| 1064 | .id_result = result_id, | ||
| 1065 | .set = try cg.importInstructionSet(.zig), | ||
| 1066 | .instruction = .{ .inst = @backingInt(spec.Zig.InvocationGlobal) }, | ||
| 1067 | .id_ref_4 = &.{initializer_id}, | ||
| 1068 | }); | ||
| 1069 | }, | ||
| 1070 | } | ||
| 1071 | |||
| 1072 | cg.declPtr(spv_decl_index).end_dep = cg.decl_deps.items.len; | ||
| 1073 | } | ||
| 1074 | |||
| 1075 | fn decorateLayout(cg: *CodeGen, ty: Type, ty_id: spec.Id) Error!void { | 841 | fn decorateLayout(cg: *CodeGen, ty: Type, ty_id: spec.Id) Error!void { |
| 1076 | const zcu = cg.zcu; | 842 | const zcu = cg.zcu; |
| 1077 | const ip = &zcu.intern_pool; | 843 | const ip = &zcu.intern_pool; |
| ... | @@ -1174,123 +940,49 @@ fn importExtendedSet(cg: *CodeGen) !Id { | ... | @@ -1174,123 +940,49 @@ fn importExtendedSet(cg: *CodeGen) !Id { |
| 1174 | /// Fetch the result-id for a previously generated instruction or constant. | 940 | /// Fetch the result-id for a previously generated instruction or constant. |
| 1175 | fn resolve(cg: *CodeGen, inst: Air.Inst.Ref) !Id { | 941 | fn resolve(cg: *CodeGen, inst: Air.Inst.Ref) !Id { |
| 1176 | const zcu = cg.zcu; | 942 | const zcu = cg.zcu; |
| 1177 | const ip = &zcu.intern_pool; | ||
| 1178 | if (inst.toInterned()) |val_ip_index| { | 943 | if (inst.toInterned()) |val_ip_index| { |
| 1179 | const ty = cg.typeOf(inst); | 944 | const ty = cg.typeOf(inst); |
| 1180 | if (ty.zigTypeTag(zcu) == .@"fn") { | 945 | if (ty.zigTypeTag(zcu) == .@"fn") { |
| 1181 | const val_key = zcu.intern_pool.indexToKey(val_ip_index); | 946 | const val_key = zcu.intern_pool.indexToKey(val_ip_index); |
| 1182 | const fn_nav = switch (val_key) { | 947 | return switch (val_key) { |
| 1183 | .@"extern" => |@"extern"| @"extern".owner_nav, | 948 | .@"extern" => |@"extern"| try cg.resolveExternFn(@"extern".owner_nav), |
| 1184 | .func => |func| func.owner_nav, | 949 | .func => |func| try cg.resolveNav(func.owner_nav), |
| 1185 | else => unreachable, | 950 | else => unreachable, |
| 1186 | }; | 951 | }; |
| 1187 | const spv_decl_index = try cg.resolveNav(ip, fn_nav); | ||
| 1188 | try cg.decl_deps.append(cg.gpa, spv_decl_index); | ||
| 1189 | const decl = cg.declPtr(spv_decl_index); | ||
| 1190 | if (val_key == .@"extern") { | ||
| 1191 | const nav = ip.getNav(fn_nav); | ||
| 1192 | const nav_ty: Type = .fromInterned(nav.resolved.?.type); | ||
| 1193 | try emitExternFnStub(cg, nav, decl, nav_ty); | ||
| 1194 | } | ||
| 1195 | return decl.result_id; | ||
| 1196 | } | 952 | } |
| 1197 | 953 | ||
| 1198 | return try cg.constant(ty, .fromInterned(val_ip_index), .direct); | 954 | const val: Value = .fromInterned(val_ip_index); |
| 1199 | } | 955 | if (ty.isPtrAtRuntime(zcu)) { |
| 1200 | const index = inst.toIndex().?; | 956 | if (val.isUndef(zcu)) return try cg.constUndef(try cg.resolveType(ty, .direct)); |
| 1201 | return cg.inst_results.get(index).?; // Assertion means instruction does not dominate usage. | 957 | if (ty.zigTypeTag(zcu) == .optional) { |
| 1202 | } | 958 | const payload = val.optionalValue(zcu) orelse return try cg.constNull(try cg.resolveType(ty, .direct)); |
| 1203 | 959 | return try cg.constantPtr(payload); | |
| 1204 | fn resolveUav(cg: *CodeGen, val: InternPool.Index) !Id { | 960 | } |
| 1205 | const gpa = cg.gpa; | 961 | return try cg.constantPtr(val); |
| 1206 | |||
| 1207 | // TODO: This cannot be a function at this point, but it should probably be handled anyway. | ||
| 1208 | |||
| 1209 | const zcu = cg.zcu; | ||
| 1210 | const ty: Type = .fromInterned(zcu.intern_pool.typeOf(val)); | ||
| 1211 | const ty_id = try cg.resolveType(ty, .indirect); | ||
| 1212 | |||
| 1213 | const spv_decl_index = blk: { | ||
| 1214 | const entry = try cg.uav_link.getOrPut(gpa, .{ val, .function }); | ||
| 1215 | if (entry.found_existing) { | ||
| 1216 | try cg.addFunctionDep(entry.value_ptr.*, .function); | ||
| 1217 | return cg.declPtr(entry.value_ptr.*).result_id; | ||
| 1218 | } | ||
| 1219 | |||
| 1220 | const spv_decl_index = try cg.allocDecl(.invocation_global); | ||
| 1221 | try cg.addFunctionDep(spv_decl_index, .function); | ||
| 1222 | entry.value_ptr.* = spv_decl_index; | ||
| 1223 | break :blk spv_decl_index; | ||
| 1224 | }; | ||
| 1225 | |||
| 1226 | // TODO: At some point we will be able to generate this all constant here, but then all of | ||
| 1227 | // constant() will need to be implemented such that it doesn't generate any at-runtime code. | ||
| 1228 | // NOTE: Because this is a global, we really only want to initialize it once. Therefore the | ||
| 1229 | // constant lowering of this value will need to be deferred to an initializer similar to | ||
| 1230 | // other globals. | ||
| 1231 | |||
| 1232 | const result_id = cg.declPtr(spv_decl_index).result_id; | ||
| 1233 | |||
| 1234 | { | ||
| 1235 | // Save the current state so that we can temporarily generate into a different function. | ||
| 1236 | // TODO: This should probably be made a little more robust. | ||
| 1237 | const func_prologue = cg.prologue; | ||
| 1238 | const func_body = cg.body; | ||
| 1239 | const block_label = cg.block_label; | ||
| 1240 | defer { | ||
| 1241 | cg.prologue = func_prologue; | ||
| 1242 | cg.body = func_body; | ||
| 1243 | cg.block_label = block_label; | ||
| 1244 | } | 962 | } |
| 1245 | 963 | switch (zcu.intern_pool.indexToKey(val_ip_index)) { | |
| 1246 | cg.prologue = .{}; | 964 | .ptr => unreachable, |
| 1247 | cg.body = .{}; | 965 | .slice => |slice| { |
| 1248 | defer { | 966 | const ptr_id = try cg.constantPtr(.fromInterned(slice.ptr)); |
| 1249 | cg.prologue.deinit(gpa); | 967 | const len_id = try cg.constant(.usize, .fromInterned(slice.len), .indirect); |
| 1250 | cg.body.deinit(gpa); | 968 | return try cg.constructComposite(try cg.resolveType(ty, .direct), &.{ ptr_id, len_id }); |
| 969 | }, | ||
| 970 | .opt => { | ||
| 971 | const payload_ty = ty.optionalChild(zcu); | ||
| 972 | if (!payload_ty.isPtrAtRuntime(zcu)) return try cg.constant(ty, val, .direct); | ||
| 973 | const payload_val = val.optionalValue(zcu); | ||
| 974 | const ptr_id = if (payload_val) |payload| | ||
| 975 | try cg.constantPtr(payload) | ||
| 976 | else | ||
| 977 | try cg.constUndef(try cg.resolveType(payload_ty, .direct)); | ||
| 978 | const valid_id = try cg.constBool(payload_val != null, .indirect); | ||
| 979 | return try cg.constructComposite(try cg.resolveType(ty, .direct), &.{ ptr_id, valid_id }); | ||
| 980 | }, | ||
| 981 | else => return try cg.constant(ty, val, .direct), | ||
| 1251 | } | 982 | } |
| 1252 | |||
| 1253 | const void_ty_id = try cg.resolveType(.void, .direct); | ||
| 1254 | const initializer_proto_ty_id = try cg.functionType(void_ty_id, &.{}); | ||
| 1255 | |||
| 1256 | const initializer_id = cg.allocId(); | ||
| 1257 | try cg.prologue.emit(gpa, .OpFunction, .{ | ||
| 1258 | .id_result_type = try cg.resolveType(.void, .direct), | ||
| 1259 | .id_result = initializer_id, | ||
| 1260 | .function_control = .{}, | ||
| 1261 | .function_type = initializer_proto_ty_id, | ||
| 1262 | }); | ||
| 1263 | const root_block_id = cg.allocId(); | ||
| 1264 | try cg.prologue.emit(gpa, .OpLabel, .{ | ||
| 1265 | .id_result = root_block_id, | ||
| 1266 | }); | ||
| 1267 | cg.block_label = root_block_id; | ||
| 1268 | |||
| 1269 | const val_id = try cg.constant(ty, .fromInterned(val), .indirect); | ||
| 1270 | try cg.body.emit(gpa, .OpStore, .{ | ||
| 1271 | .pointer = result_id, | ||
| 1272 | .object = val_id, | ||
| 1273 | }); | ||
| 1274 | |||
| 1275 | try cg.body.emit(gpa, .OpReturn, {}); | ||
| 1276 | try cg.body.emit(gpa, .OpFunctionEnd, {}); | ||
| 1277 | |||
| 1278 | try cg.sections.functions.append(gpa, cg.prologue); | ||
| 1279 | try cg.sections.functions.append(gpa, cg.body); | ||
| 1280 | |||
| 1281 | try cg.debugNameFmt(initializer_id, "initializer of __anon_{d}", .{@backingInt(val)}); | ||
| 1282 | |||
| 1283 | const fn_decl_ptr_ty_id = try cg.ptrType(ty_id, .function); | ||
| 1284 | try cg.sections.globals.emit(gpa, .OpExtInst, .{ | ||
| 1285 | .id_result_type = fn_decl_ptr_ty_id, | ||
| 1286 | .id_result = result_id, | ||
| 1287 | .set = try cg.importInstructionSet(.zig), | ||
| 1288 | .instruction = .{ .inst = @backingInt(spec.Zig.InvocationGlobal) }, | ||
| 1289 | .id_ref_4 = &.{initializer_id}, | ||
| 1290 | }); | ||
| 1291 | } | 983 | } |
| 1292 | 984 | const index = inst.toIndex().?; | |
| 1293 | return result_id; | 985 | return cg.inst_results.get(index).?; // Assertion means instruction does not dominate usage. |
| 1294 | } | 986 | } |
| 1295 | 987 | ||
| 1296 | fn resolvePtr(cg: *CodeGen, ref: Air.Inst.Ref) !Ptr { | 988 | fn resolvePtr(cg: *CodeGen, ref: Air.Inst.Ref) !Ptr { |
| ... | @@ -1299,19 +991,6 @@ fn resolvePtr(cg: *CodeGen, ref: Air.Inst.Ref) !Ptr { | ... | @@ -1299,19 +991,6 @@ fn resolvePtr(cg: *CodeGen, ref: Air.Inst.Ref) !Ptr { |
| 1299 | return .{ .id = id }; | 991 | return .{ .id = id }; |
| 1300 | } | 992 | } |
| 1301 | 993 | ||
| 1302 | fn addFunctionDep(cg: *CodeGen, decl_index: Decl.Index, storage_class: StorageClass) !void { | ||
| 1303 | const gpa = cg.gpa; | ||
| 1304 | const target = cg.zcu.getTarget(); | ||
| 1305 | if (target.cpu.has(.spirv, .v1_4)) { | ||
| 1306 | try cg.decl_deps.append(gpa, decl_index); | ||
| 1307 | } else { | ||
| 1308 | // Before version 1.4, the interface’s storage classes are limited to the Input and Output | ||
| 1309 | if (storage_class == .input or storage_class == .output) { | ||
| 1310 | try cg.decl_deps.append(gpa, decl_index); | ||
| 1311 | } | ||
| 1312 | } | ||
| 1313 | } | ||
| 1314 | |||
| 1315 | /// Start a new SPIR-V block, Emits the label of the new block, and stores which | 994 | /// Start a new SPIR-V block, Emits the label of the new block, and stores which |
| 1316 | /// block we are currently generating. | 995 | /// block we are currently generating. |
| 1317 | /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to | 996 | /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to |
| ... | @@ -1439,7 +1118,7 @@ fn constBool(cg: *CodeGen, value: bool, repr: Repr) !Id { | ... | @@ -1439,7 +1118,7 @@ fn constBool(cg: *CodeGen, value: bool, repr: Repr) !Id { |
| 1439 | const result_ty_id = try cg.boolType(); | 1118 | const result_ty_id = try cg.boolType(); |
| 1440 | const result_id = cg.allocId(); | 1119 | const result_id = cg.allocId(); |
| 1441 | switch (value) { | 1120 | switch (value) { |
| 1442 | inline else => |value_ct| try cg.sections.globals.emit( | 1121 | inline else => |value_ct| try cg.module.emit( |
| 1443 | cg.gpa, | 1122 | cg.gpa, |
| 1444 | if (value_ct) .OpConstantTrue else .OpConstantFalse, | 1123 | if (value_ct) .OpConstantTrue else .OpConstantFalse, |
| 1445 | .{ .id_result_type = result_ty_id, .id_result = result_id }, | 1124 | .{ .id_result_type = result_ty_id, .id_result = result_id }, |
| ... | @@ -1485,7 +1164,7 @@ fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id { | ... | @@ -1485,7 +1164,7 @@ fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id { |
| 1485 | if (i < 2) @as(u32, @truncate(value64 >> @intCast(i * 32))) else fill, | 1164 | if (i < 2) @as(u32, @truncate(value64 >> @intCast(i * 32))) else fill, |
| 1486 | ); | 1165 | ); |
| 1487 | } | 1166 | } |
| 1488 | return cg.constructComposite(result_ty_id, constituents); | 1167 | return cg.constantComposite(result_ty_id, constituents); |
| 1489 | } | 1168 | } |
| 1490 | 1169 | ||
| 1491 | const final_value: spec.LiteralContextDependentNumber = switch (target.os.tag) { | 1170 | const final_value: spec.LiteralContextDependentNumber = switch (target.os.tag) { |
| ... | @@ -1515,14 +1194,29 @@ fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id { | ... | @@ -1515,14 +1194,29 @@ fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id { |
| 1515 | }; | 1194 | }; |
| 1516 | 1195 | ||
| 1517 | const result_id = cg.allocId(); | 1196 | const result_id = cg.allocId(); |
| 1518 | try cg.sections.globals.emit(cg.gpa, .OpConstant, .{ | 1197 | try cg.module.emit(cg.gpa, .OpConstant, .{ |
| 1519 | .id_result_type = result_ty_id, | 1198 | .id_result_type = result_ty_id, |
| 1520 | .id_result = result_id, | 1199 | .id_result = result_id, |
| 1521 | .value = final_value, | 1200 | .value = final_value, |
| 1522 | }); | 1201 | }); |
| 1523 | 1202 | ||
| 1524 | if (!ty.isVector(zcu)) return result_id; | 1203 | if (!ty.isVector(zcu)) return result_id; |
| 1525 | return cg.constructCompositeSplat(ty, result_id); | 1204 | |
| 1205 | const vector_ty_id = try cg.resolveType(ty, .direct); | ||
| 1206 | const constituents = try gpa.alloc(Id, @intCast(ty.arrayLen(zcu))); | ||
| 1207 | defer gpa.free(constituents); | ||
| 1208 | @memset(constituents, result_id); | ||
| 1209 | return cg.constantComposite(vector_ty_id, constituents); | ||
| 1210 | } | ||
| 1211 | |||
| 1212 | fn constantComposite(cg: *CodeGen, result_ty_id: Id, constituents: []const Id) !Id { | ||
| 1213 | const result_id = cg.allocId(); | ||
| 1214 | try cg.module.emit(cg.gpa, .OpConstantComposite, .{ | ||
| 1215 | .id_result_type = result_ty_id, | ||
| 1216 | .id_result = result_id, | ||
| 1217 | .constituents = constituents, | ||
| 1218 | }); | ||
| 1219 | return result_id; | ||
| 1526 | } | 1220 | } |
| 1527 | 1221 | ||
| 1528 | /// Construct a composite value from its constituents. | 1222 | /// Construct a composite value from its constituents. |
| ... | @@ -1541,7 +1235,7 @@ pub fn constructComposite(cg: *CodeGen, result_ty_id: Id, constituents: []const | ... | @@ -1541,7 +1235,7 @@ pub fn constructComposite(cg: *CodeGen, result_ty_id: Id, constituents: []const |
| 1541 | for (fields, constituents, 0..) |field_ty_id, constituent, i| { | 1235 | for (fields, constituents, 0..) |field_ty_id, constituent, i| { |
| 1542 | const field_ptr_ty_id = try cg.ptrType(field_ty_id, .function); | 1236 | const field_ptr_ty_id = try cg.ptrType(field_ty_id, .function); |
| 1543 | const index_id = cg.allocId(); | 1237 | const index_id = cg.allocId(); |
| 1544 | try cg.sections.globals.emit(gpa, .OpConstant, .{ | 1238 | try cg.module.emit(gpa, .OpConstant, .{ |
| 1545 | .id_result_type = u32_ty_id, | 1239 | .id_result_type = u32_ty_id, |
| 1546 | .id_result = index_id, | 1240 | .id_result = index_id, |
| 1547 | .value = .{ .uint32 = @intCast(i) }, | 1241 | .value = .{ .uint32 = @intCast(i) }, |
| ... | @@ -1572,21 +1266,6 @@ pub fn constructComposite(cg: *CodeGen, result_ty_id: Id, constituents: []const | ... | @@ -1572,21 +1266,6 @@ pub fn constructComposite(cg: *CodeGen, result_ty_id: Id, constituents: []const |
| 1572 | 1266 | ||
| 1573 | /// Construct a composite at runtime with all lanes set to the same value. | 1267 | /// Construct a composite at runtime with all lanes set to the same value. |
| 1574 | /// ty must be an aggregate type. | 1268 | /// ty must be an aggregate type. |
| 1575 | fn constructCompositeSplat(cg: *CodeGen, ty: Type, constituent: Id) !Id { | ||
| 1576 | const gpa = cg.gpa; | ||
| 1577 | const zcu = cg.zcu; | ||
| 1578 | const n: usize = @intCast(ty.arrayLen(zcu)); | ||
| 1579 | |||
| 1580 | const scratch_top = cg.id_scratch.items.len; | ||
| 1581 | defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); | ||
| 1582 | |||
| 1583 | const constituents = try cg.id_scratch.addManyAsSlice(gpa, n); | ||
| 1584 | @memset(constituents, constituent); | ||
| 1585 | |||
| 1586 | const result_ty_id = try cg.resolveType(ty, .direct); | ||
| 1587 | return cg.constructComposite(result_ty_id, constituents); | ||
| 1588 | } | ||
| 1589 | |||
| 1590 | /// This function generates a load for a constant in direct (ie, non-memory) representation. | 1269 | /// This function generates a load for a constant in direct (ie, non-memory) representation. |
| 1591 | /// When the constant is simple, it can be generated directly using OpConstant instructions. | 1270 | /// When the constant is simple, it can be generated directly using OpConstant instructions. |
| 1592 | /// When the constant is more complicated however, it needs to be constructed using multiple values. This | 1271 | /// When the constant is more complicated however, it needs to be constructed using multiple values. This |
| ... | @@ -1602,288 +1281,345 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { | ... | @@ -1602,288 +1281,345 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { |
| 1602 | const result_ty_id = try cg.resolveType(ty, repr); | 1281 | const result_ty_id = try cg.resolveType(ty, repr); |
| 1603 | const ip = &zcu.intern_pool; | 1282 | const ip = &zcu.intern_pool; |
| 1604 | 1283 | ||
| 1605 | log.debug("lowering constant: ty = {f}, val = {f}, key = {s}", .{ ty.fmt(pt), val.fmtValue(pt), @tagName(ip.indexToKey(val.toIntern())) }); | 1284 | log.debug("lowering constant: ty = {f}, val = {f}, key = {s}", .{ |
| 1285 | ty.fmt(pt), | ||
| 1286 | val.fmtValue(pt), | ||
| 1287 | @tagName(ip.indexToKey(val.toIntern())), | ||
| 1288 | }); | ||
| 1289 | assert(!ty.isPtrAtRuntime(zcu)); | ||
| 1290 | |||
| 1606 | if (val.isUndef(zcu)) { | 1291 | if (val.isUndef(zcu)) { |
| 1607 | return cg.constUndef(result_ty_id); | 1292 | return cg.constUndef(result_ty_id); |
| 1608 | } | 1293 | } |
| 1609 | 1294 | ||
| 1610 | const cacheable_id = cache: { | 1295 | const cacheable_id = cache: switch (ip.indexToKey(val.toIntern())) { |
| 1611 | switch (ip.indexToKey(val.toIntern())) { | 1296 | .int_type, |
| 1612 | .int_type, | 1297 | .ptr_type, |
| 1613 | .ptr_type, | 1298 | .array_type, |
| 1614 | .array_type, | 1299 | .vector_type, |
| 1615 | .vector_type, | 1300 | .opt_type, |
| 1616 | .opt_type, | 1301 | .anyframe_type, |
| 1617 | .anyframe_type, | 1302 | .error_union_type, |
| 1618 | .error_union_type, | 1303 | .simple_type, |
| 1619 | .simple_type, | 1304 | .struct_type, |
| 1620 | .struct_type, | 1305 | .tuple_type, |
| 1621 | .tuple_type, | 1306 | .union_type, |
| 1622 | .union_type, | 1307 | .opaque_type, |
| 1623 | .opaque_type, | 1308 | .spirv_type, |
| 1624 | .spirv_type, | 1309 | .enum_type, |
| 1625 | .enum_type, | 1310 | .func_type, |
| 1626 | .func_type, | 1311 | .error_set_type, |
| 1627 | .error_set_type, | 1312 | .inferred_error_set_type, |
| 1628 | .inferred_error_set_type, | 1313 | => unreachable, // types, not values |
| 1629 | => unreachable, // types, not values | 1314 | |
| 1630 | 1315 | .undef => unreachable, // handled above | |
| 1631 | .undef => unreachable, // handled above | 1316 | |
| 1632 | 1317 | .@"extern", | |
| 1633 | .@"extern", | 1318 | .func, |
| 1634 | .func, | 1319 | .enum_literal, |
| 1635 | .enum_literal, | 1320 | => unreachable, // non-runtime values |
| 1636 | => unreachable, // non-runtime values | ||
| 1637 | 1321 | ||
| 1638 | .simple_value => |simple_value| switch (simple_value) { | 1322 | .simple_value => |simple_value| switch (simple_value) { |
| 1639 | .void, | 1323 | .void, |
| 1640 | .null, | 1324 | .null, |
| 1641 | .@"unreachable", | 1325 | .@"unreachable", |
| 1642 | => unreachable, // non-runtime values | 1326 | => unreachable, // non-runtime values |
| 1643 | 1327 | ||
| 1644 | .false, .true => break :cache try cg.constBool(val.toBool(), repr), | 1328 | .false, .true => break :cache try cg.constBool(val.toBool(), repr), |
| 1645 | }, | 1329 | }, |
| 1646 | .int => { | 1330 | .int => { |
| 1647 | const int_info = ty.intInfo(zcu); | 1331 | const int_info = ty.intInfo(zcu); |
| 1648 | const backing_bits, const is_big_int = cg.backingIntBits(int_info.bits); | 1332 | const backing_bits, const is_big_int = cg.backingIntBits(int_info.bits); |
| 1649 | if (is_big_int) { | 1333 | if (is_big_int) { |
| 1650 | const limb_bits = cg.bigIntBits(); | 1334 | const limb_bits = cg.bigIntBits(); |
| 1651 | const n_limbs = backing_bits / limb_bits; | 1335 | const n_limbs = backing_bits / limb_bits; |
| 1652 | const big_result_ty_id = try cg.resolveType(ty, .indirect); | 1336 | const big_result_ty_id = try cg.resolveType(ty, .indirect); |
| 1653 | var bigint_space: Value.BigIntSpace = undefined; | 1337 | var bigint_space: Value.BigIntSpace = undefined; |
| 1654 | const bigint = val.toBigInt(&bigint_space, zcu); | 1338 | const bigint = val.toBigInt(&bigint_space, zcu); |
| 1655 | const limb_bytes = try gpa.alloc(u8, backing_bits / 8); | 1339 | const limb_bytes = try gpa.alloc(u8, backing_bits / 8); |
| 1656 | defer gpa.free(limb_bytes); | 1340 | defer gpa.free(limb_bytes); |
| 1657 | bigint.writeTwosComplement(limb_bytes, .little); | 1341 | bigint.writeTwosComplement(limb_bytes, .little); |
| 1658 | const scratch_top = cg.id_scratch.items.len; | 1342 | const scratch_top = cg.id_scratch.items.len; |
| 1659 | defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); | 1343 | defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); |
| 1660 | const constituents = try cg.id_scratch.addManyAsSlice(gpa, n_limbs); | 1344 | const constituents = try cg.id_scratch.addManyAsSlice(gpa, n_limbs); |
| 1661 | switch (limb_bits) { | 1345 | switch (limb_bits) { |
| 1662 | 32 => { | 1346 | 32 => { |
| 1663 | const limbs_u32: []u32 = @ptrCast(@alignCast(limb_bytes)); | 1347 | const limbs_u32: []u32 = @ptrCast(@alignCast(limb_bytes)); |
| 1664 | for (constituents, limbs_u32) |*c, v| { | 1348 | for (constituents, limbs_u32) |*c, v| { |
| 1665 | const host_v = if (builtin.cpu.arch.endian() == .big) @byteSwap(v) else v; | 1349 | const host_v = if (builtin.cpu.arch.endian() == .big) @byteSwap(v) else v; |
| 1666 | c.* = try cg.constInt(.u32, host_v); | 1350 | c.* = try cg.constInt(.u32, host_v); |
| 1667 | } | 1351 | } |
| 1668 | }, | 1352 | }, |
| 1669 | 64 => { | 1353 | 64 => { |
| 1670 | const limbs_u64: []u64 = @ptrCast(@alignCast(limb_bytes)); | 1354 | const limbs_u64: []u64 = @ptrCast(@alignCast(limb_bytes)); |
| 1671 | for (constituents, limbs_u64) |*c, v| { | 1355 | for (constituents, limbs_u64) |*c, v| { |
| 1672 | const host_v = if (builtin.cpu.arch.endian() == .big) @byteSwap(v) else v; | 1356 | const host_v = if (builtin.cpu.arch.endian() == .big) @byteSwap(v) else v; |
| 1673 | c.* = try cg.constInt(.u64, host_v); | 1357 | c.* = try cg.constInt(.u64, host_v); |
| 1674 | } | 1358 | } |
| 1675 | }, | 1359 | }, |
| 1676 | else => unreachable, | 1360 | else => unreachable, |
| 1677 | } | ||
| 1678 | break :cache try cg.constructComposite(big_result_ty_id, constituents); | ||
| 1679 | } | 1361 | } |
| 1680 | if (ty.isSignedInt(zcu)) { | 1362 | break :cache try cg.constantComposite(big_result_ty_id, constituents); |
| 1681 | break :cache try cg.constInt(ty, val.toSignedInt(zcu)); | 1363 | } |
| 1364 | if (ty.isSignedInt(zcu)) { | ||
| 1365 | break :cache try cg.constInt(ty, val.toSignedInt(zcu)); | ||
| 1366 | } else { | ||
| 1367 | break :cache try cg.constInt(ty, val.toUnsignedInt(zcu)); | ||
| 1368 | } | ||
| 1369 | }, | ||
| 1370 | .float => { | ||
| 1371 | const lit: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) { | ||
| 1372 | 16 => .{ .uint32 = @as(u16, @bitCast(val.toFloat(f16, zcu))) }, | ||
| 1373 | 32 => .{ .float32 = val.toFloat(f32, zcu) }, | ||
| 1374 | 64 => .{ .float64 = val.toFloat(f64, zcu) }, | ||
| 1375 | 80, 128 => unreachable, // TODO | ||
| 1376 | else => unreachable, | ||
| 1377 | }; | ||
| 1378 | const lit_id = cg.allocId(); | ||
| 1379 | try cg.module.emit(gpa, .OpConstant, .{ | ||
| 1380 | .id_result_type = result_ty_id, | ||
| 1381 | .id_result = lit_id, | ||
| 1382 | .value = lit, | ||
| 1383 | }); | ||
| 1384 | break :cache lit_id; | ||
| 1385 | }, | ||
| 1386 | .err => |err| { | ||
| 1387 | const value = try pt.getErrorValue(err.name); | ||
| 1388 | break :cache try cg.constInt(ty, value); | ||
| 1389 | }, | ||
| 1390 | .error_union => |error_union| { | ||
| 1391 | // TODO: Error unions may be constructed with constant instructions if the payload type | ||
| 1392 | // allows it. For now, just generate it here regardless. | ||
| 1393 | const err_ty = ty.errorUnionSet(zcu); | ||
| 1394 | const payload_ty = ty.errorUnionPayload(zcu); | ||
| 1395 | const err_val_id = switch (error_union.val) { | ||
| 1396 | .err_name => |err_name| try cg.constInt( | ||
| 1397 | err_ty, | ||
| 1398 | try pt.getErrorValue(err_name), | ||
| 1399 | ), | ||
| 1400 | .payload => try cg.constInt(err_ty, 0), | ||
| 1401 | }; | ||
| 1402 | const eu_layout = cg.errorUnionLayout(payload_ty); | ||
| 1403 | if (!eu_layout.payload_has_bits) { | ||
| 1404 | // We use the error type directly as the type. | ||
| 1405 | break :cache err_val_id; | ||
| 1406 | } | ||
| 1407 | |||
| 1408 | const payload_val_id = switch (error_union.val) { | ||
| 1409 | .err_name => try cg.constant(payload_ty, .undef, .indirect), | ||
| 1410 | .payload => |p| try cg.constant(payload_ty, .fromInterned(p), .indirect), | ||
| 1411 | }; | ||
| 1412 | |||
| 1413 | var constituents: [2]Id = undefined; | ||
| 1414 | var types: [2]Type = undefined; | ||
| 1415 | if (eu_layout.error_first) { | ||
| 1416 | constituents[0] = err_val_id; | ||
| 1417 | constituents[1] = payload_val_id; | ||
| 1418 | types = .{ err_ty, payload_ty }; | ||
| 1419 | } else { | ||
| 1420 | constituents[0] = payload_val_id; | ||
| 1421 | constituents[1] = err_val_id; | ||
| 1422 | types = .{ payload_ty, err_ty }; | ||
| 1423 | } | ||
| 1424 | |||
| 1425 | const comp_ty_id = try cg.resolveType(ty, .direct); | ||
| 1426 | return try cg.constantComposite(comp_ty_id, &constituents); | ||
| 1427 | }, | ||
| 1428 | .enum_tag => { | ||
| 1429 | const int_val = val.backingInt(zcu); | ||
| 1430 | const int_ty = ty.backingIntType(zcu); | ||
| 1431 | break :cache try cg.constant(int_ty, int_val, repr); | ||
| 1432 | }, | ||
| 1433 | .ptr, .slice => unreachable, | ||
| 1434 | .opt => { | ||
| 1435 | const payload_ty = ty.optionalChild(zcu); | ||
| 1436 | const maybe_payload_val = val.optionalValue(zcu); | ||
| 1437 | |||
| 1438 | if (!payload_ty.hasRuntimeBits(zcu)) { | ||
| 1439 | break :cache try cg.constBool(maybe_payload_val != null, .indirect); | ||
| 1440 | } else if (ty.optionalReprIsPayload(zcu)) { | ||
| 1441 | // Optional representation is a nullable pointer or slice. | ||
| 1442 | if (maybe_payload_val) |payload_val| { | ||
| 1443 | return try cg.constant(payload_ty, payload_val, .indirect); | ||
| 1682 | } else { | 1444 | } else { |
| 1683 | break :cache try cg.constInt(ty, val.toUnsignedInt(zcu)); | 1445 | break :cache try cg.constNull(result_ty_id); |
| 1684 | } | ||
| 1685 | }, | ||
| 1686 | .float => { | ||
| 1687 | const lit: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) { | ||
| 1688 | 16 => .{ .uint32 = @as(u16, @bitCast(val.toFloat(f16, zcu))) }, | ||
| 1689 | 32 => .{ .float32 = val.toFloat(f32, zcu) }, | ||
| 1690 | 64 => .{ .float64 = val.toFloat(f64, zcu) }, | ||
| 1691 | 80, 128 => unreachable, // TODO | ||
| 1692 | else => unreachable, | ||
| 1693 | }; | ||
| 1694 | const lit_id = cg.allocId(); | ||
| 1695 | try cg.sections.globals.emit(gpa, .OpConstant, .{ | ||
| 1696 | .id_result_type = result_ty_id, | ||
| 1697 | .id_result = lit_id, | ||
| 1698 | .value = lit, | ||
| 1699 | }); | ||
| 1700 | break :cache lit_id; | ||
| 1701 | }, | ||
| 1702 | .err => |err| { | ||
| 1703 | const value = try pt.getErrorValue(err.name); | ||
| 1704 | break :cache try cg.constInt(ty, value); | ||
| 1705 | }, | ||
| 1706 | .error_union => |error_union| { | ||
| 1707 | // TODO: Error unions may be constructed with constant instructions if the payload type | ||
| 1708 | // allows it. For now, just generate it here regardless. | ||
| 1709 | const err_ty = ty.errorUnionSet(zcu); | ||
| 1710 | const payload_ty = ty.errorUnionPayload(zcu); | ||
| 1711 | const err_val_id = switch (error_union.val) { | ||
| 1712 | .err_name => |err_name| try cg.constInt( | ||
| 1713 | err_ty, | ||
| 1714 | try pt.getErrorValue(err_name), | ||
| 1715 | ), | ||
| 1716 | .payload => try cg.constInt(err_ty, 0), | ||
| 1717 | }; | ||
| 1718 | const eu_layout = cg.errorUnionLayout(payload_ty); | ||
| 1719 | if (!eu_layout.payload_has_bits) { | ||
| 1720 | // We use the error type directly as the type. | ||
| 1721 | break :cache err_val_id; | ||
| 1722 | } | 1446 | } |
| 1447 | } | ||
| 1723 | 1448 | ||
| 1724 | const payload_val_id = switch (error_union.val) { | 1449 | // Optional representation is a structure. |
| 1725 | .err_name => try cg.constant(payload_ty, .undef, .indirect), | 1450 | // { Payload, Bool } |
| 1726 | .payload => |p| try cg.constant(payload_ty, .fromInterned(p), .indirect), | 1451 | |
| 1452 | const has_pl_id = try cg.constBool(maybe_payload_val != null, .indirect); | ||
| 1453 | const payload_id = if (maybe_payload_val) |payload_val| | ||
| 1454 | try cg.constant(payload_ty, payload_val, .indirect) | ||
| 1455 | else | ||
| 1456 | try cg.constUndef(try cg.resolveType(payload_ty, .indirect)); | ||
| 1457 | |||
| 1458 | const comp_ty_id = try cg.resolveType(ty, .direct); | ||
| 1459 | return try cg.constantComposite(comp_ty_id, &.{ payload_id, has_pl_id }); | ||
| 1460 | }, | ||
| 1461 | .aggregate => |aggregate| switch (ip.indexToKey(ty.ip_index)) { | ||
| 1462 | inline .array_type, .vector_type => |array_type, tag| { | ||
| 1463 | const elem_ty: Type = .fromInterned(array_type.child); | ||
| 1464 | |||
| 1465 | const scratch_top = cg.id_scratch.items.len; | ||
| 1466 | defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); | ||
| 1467 | const constituents = try cg.id_scratch.addManyAsSlice(gpa, @intCast(ty.arrayLenIncludingSentinel(zcu))); | ||
| 1468 | |||
| 1469 | const child_repr: Repr = switch (tag) { | ||
| 1470 | .array_type => .indirect, | ||
| 1471 | .vector_type => .direct, | ||
| 1472 | else => unreachable, | ||
| 1727 | }; | 1473 | }; |
| 1728 | 1474 | ||
| 1729 | var constituents: [2]Id = undefined; | 1475 | switch (aggregate.storage) { |
| 1730 | var types: [2]Type = undefined; | 1476 | .bytes => |bytes| { |
| 1731 | if (eu_layout.error_first) { | 1477 | // TODO: This is really space inefficient, perhaps there is a better |
| 1732 | constituents[0] = err_val_id; | 1478 | // way to do it? |
| 1733 | constituents[1] = payload_val_id; | 1479 | for (constituents, bytes.toSlice(constituents.len, ip)) |*constituent, byte| { |
| 1734 | types = .{ err_ty, payload_ty }; | 1480 | constituent.* = try cg.constInt(elem_ty, byte); |
| 1735 | } else { | 1481 | } |
| 1736 | constituents[0] = payload_val_id; | 1482 | }, |
| 1737 | constituents[1] = err_val_id; | 1483 | .elems => |elems| { |
| 1738 | types = .{ payload_ty, err_ty }; | 1484 | for (constituents, elems) |*constituent, elem| { |
| 1485 | constituent.* = try cg.constant(elem_ty, .fromInterned(elem), child_repr); | ||
| 1486 | } | ||
| 1487 | }, | ||
| 1488 | .repeated_elem => |elem| { | ||
| 1489 | @memset(constituents, try cg.constant(elem_ty, .fromInterned(elem), child_repr)); | ||
| 1490 | }, | ||
| 1739 | } | 1491 | } |
| 1740 | 1492 | ||
| 1741 | const comp_ty_id = try cg.resolveType(ty, .direct); | 1493 | const comp_ty_id = try cg.resolveType(ty, .direct); |
| 1742 | return try cg.constructComposite(comp_ty_id, &constituents); | 1494 | return cg.constantComposite(comp_ty_id, constituents); |
| 1743 | }, | ||
| 1744 | .enum_tag => { | ||
| 1745 | const int_val = val.backingInt(zcu); | ||
| 1746 | const int_ty = ty.backingIntType(zcu); | ||
| 1747 | break :cache try cg.constant(int_ty, int_val, repr); | ||
| 1748 | }, | ||
| 1749 | .ptr => return cg.constantPtr(val), | ||
| 1750 | .slice => |slice| { | ||
| 1751 | const ptr_id = try cg.constantPtr(.fromInterned(slice.ptr)); | ||
| 1752 | const len_id = try cg.constant(.usize, .fromInterned(slice.len), .indirect); | ||
| 1753 | const comp_ty_id = try cg.resolveType(ty, .direct); | ||
| 1754 | return try cg.constructComposite(comp_ty_id, &.{ ptr_id, len_id }); | ||
| 1755 | }, | 1495 | }, |
| 1756 | .opt => { | 1496 | .struct_type => { |
| 1757 | const payload_ty = ty.optionalChild(zcu); | 1497 | const struct_type = zcu.typeToStruct(ty).?; |
| 1758 | const maybe_payload_val = val.optionalValue(zcu); | 1498 | assert(struct_type.layout != .@"packed"); // packed structs use `bitpack` |
| 1759 | 1499 | ||
| 1760 | if (!payload_ty.hasRuntimeBits(zcu)) { | 1500 | var types: std.ArrayList(Type) = .empty; |
| 1761 | break :cache try cg.constBool(maybe_payload_val != null, .indirect); | 1501 | defer types.deinit(gpa); |
| 1762 | } else if (ty.optionalReprIsPayload(zcu)) { | 1502 | |
| 1763 | // Optional representation is a nullable pointer or slice. | 1503 | var constituents: std.ArrayList(Id) = .empty; |
| 1764 | if (maybe_payload_val) |payload_val| { | 1504 | defer constituents.deinit(gpa); |
| 1765 | return try cg.constant(payload_ty, payload_val, .indirect); | 1505 | |
| 1766 | } else { | 1506 | var it = struct_type.iterateRuntimeOrder(ip); |
| 1767 | break :cache try cg.constNull(result_ty_id); | 1507 | while (it.next()) |field_index| { |
| 1508 | const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]); | ||
| 1509 | if (!field_ty.hasRuntimeBits(zcu)) { | ||
| 1510 | // This is a zero-bit field - we only needed it for the alignment. | ||
| 1511 | continue; | ||
| 1768 | } | 1512 | } |
| 1769 | } | ||
| 1770 | 1513 | ||
| 1771 | // Optional representation is a structure. | 1514 | // TODO: Padding? |
| 1772 | // { Payload, Bool } | 1515 | const field_val = try val.fieldValue(pt, field_index); |
| 1516 | const field_id = try cg.constant(field_ty, field_val, .indirect); | ||
| 1773 | 1517 | ||
| 1774 | const has_pl_id = try cg.constBool(maybe_payload_val != null, .indirect); | 1518 | try types.append(gpa, field_ty); |
| 1775 | const payload_id = if (maybe_payload_val) |payload_val| | 1519 | try constituents.append(gpa, field_id); |
| 1776 | try cg.constant(payload_ty, payload_val, .indirect) | 1520 | } |
| 1777 | else | ||
| 1778 | try cg.constUndef(try cg.resolveType(payload_ty, .indirect)); | ||
| 1779 | 1521 | ||
| 1780 | const comp_ty_id = try cg.resolveType(ty, .direct); | 1522 | const comp_ty_id = try cg.resolveType(ty, .direct); |
| 1781 | return try cg.constructComposite(comp_ty_id, &.{ payload_id, has_pl_id }); | 1523 | return cg.constantComposite(comp_ty_id, constituents.items); |
| 1782 | }, | 1524 | }, |
| 1783 | .aggregate => |aggregate| switch (ip.indexToKey(ty.ip_index)) { | 1525 | .tuple_type => |tuple| { |
| 1784 | inline .array_type, .vector_type => |array_type, tag| { | 1526 | var constituents: std.ArrayList(Id) = .empty; |
| 1785 | const elem_ty: Type = .fromInterned(array_type.child); | 1527 | defer constituents.deinit(gpa); |
| 1786 | |||
| 1787 | const scratch_top = cg.id_scratch.items.len; | ||
| 1788 | defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); | ||
| 1789 | const constituents = try cg.id_scratch.addManyAsSlice(gpa, @intCast(ty.arrayLenIncludingSentinel(zcu))); | ||
| 1790 | 1528 | ||
| 1791 | const child_repr: Repr = switch (tag) { | 1529 | for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty, field_val, i| { |
| 1792 | .array_type => .indirect, | 1530 | if (field_val != .none) continue; |
| 1793 | .vector_type => .direct, | 1531 | const ft: Type = .fromInterned(field_ty); |
| 1794 | else => unreachable, | 1532 | if (!ft.hasRuntimeBits(zcu)) continue; |
| 1795 | }; | ||
| 1796 | 1533 | ||
| 1797 | switch (aggregate.storage) { | 1534 | const fv = try val.fieldValue(pt, i); |
| 1798 | .bytes => |bytes| { | 1535 | const field_id = try cg.constant(ft, fv, .indirect); |
| 1799 | // TODO: This is really space inefficient, perhaps there is a better | 1536 | try constituents.append(gpa, field_id); |
| 1800 | // way to do it? | 1537 | } |
| 1801 | for (constituents, bytes.toSlice(constituents.len, ip)) |*constituent, byte| { | ||
| 1802 | constituent.* = try cg.constInt(elem_ty, byte); | ||
| 1803 | } | ||
| 1804 | }, | ||
| 1805 | .elems => |elems| { | ||
| 1806 | for (constituents, elems) |*constituent, elem| { | ||
| 1807 | constituent.* = try cg.constant(elem_ty, .fromInterned(elem), child_repr); | ||
| 1808 | } | ||
| 1809 | }, | ||
| 1810 | .repeated_elem => |elem| { | ||
| 1811 | @memset(constituents, try cg.constant(elem_ty, .fromInterned(elem), child_repr)); | ||
| 1812 | }, | ||
| 1813 | } | ||
| 1814 | 1538 | ||
| 1815 | const comp_ty_id = try cg.resolveType(ty, .direct); | 1539 | const comp_ty_id = try cg.resolveType(ty, .direct); |
| 1816 | return cg.constructComposite(comp_ty_id, constituents); | 1540 | return cg.constantComposite(comp_ty_id, constituents.items); |
| 1817 | }, | 1541 | }, |
| 1818 | .struct_type => { | 1542 | else => unreachable, |
| 1819 | const struct_type = zcu.typeToStruct(ty).?; | 1543 | }, |
| 1820 | assert(struct_type.layout != .@"packed"); // packed structs use `bitpack` | 1544 | .un => |un| { |
| 1545 | assert(ty.containerLayout(zcu) != .@"packed"); | ||
| 1546 | if (un.tag == .none) return cg.todo("constant of untagged union", .{}); | ||
| 1821 | 1547 | ||
| 1822 | var types: std.ArrayList(Type) = .empty; | 1548 | const active_field = ty.unionTagFieldIndex(.fromInterned(un.tag), zcu).?; |
| 1823 | defer types.deinit(gpa); | 1549 | const union_obj = zcu.typeToUnion(ty).?; |
| 1550 | const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[active_field]); | ||
| 1551 | if (union_obj.layout == .auto) { | ||
| 1552 | if (ty.unionHasAllZeroBitFieldTypes(zcu)) { | ||
| 1553 | const tag_ty = ty.unionTagTypeRuntime(zcu).?; | ||
| 1554 | break :cache try cg.constant(tag_ty, .fromInterned(un.tag), .indirect); | ||
| 1555 | } | ||
| 1824 | 1556 | ||
| 1825 | var constituents: std.ArrayList(Id) = .empty; | 1557 | const constituents = try gpa.alloc(Id, union_obj.field_types.len + 1); |
| 1826 | defer constituents.deinit(gpa); | 1558 | defer gpa.free(constituents); |
| 1559 | for ( | ||
| 1560 | constituents[0..union_obj.field_types.len], | ||
| 1561 | union_obj.field_types.get(ip), | ||
| 1562 | 0.., | ||
| 1563 | ) |*constituent, member_ty_index, member| { | ||
| 1564 | const member_ty: Type = .fromInterned(member_ty_index); | ||
| 1565 | assert(!member_ty.isPtrAtRuntime(zcu)); | ||
| 1566 | |||
| 1567 | const member_ty_id = if (member_ty.hasRuntimeBits(zcu)) | ||
| 1568 | try cg.resolveType(member_ty, .indirect) | ||
| 1569 | else | ||
| 1570 | try cg.structType(&.{}, null, .none); | ||
| 1571 | constituent.* = if (member == active_field and member_ty.hasRuntimeBits(zcu)) | ||
| 1572 | try cg.constant(member_ty, .fromInterned(un.val), .indirect) | ||
| 1573 | else | ||
| 1574 | try cg.constUndef(member_ty_id); | ||
| 1575 | } | ||
| 1827 | 1576 | ||
| 1828 | var it = struct_type.iterateRuntimeOrder(ip); | 1577 | var len = union_obj.field_types.len; |
| 1829 | while (it.next()) |field_index| { | 1578 | if (ty.unionTagTypeRuntime(zcu)) |tag_ty| { |
| 1830 | const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]); | 1579 | constituents[len] = try cg.constant(tag_ty, .fromInterned(un.tag), .indirect); |
| 1831 | if (!field_ty.hasRuntimeBits(zcu)) { | 1580 | len += 1; |
| 1832 | // This is a zero-bit field - we only needed it for the alignment. | 1581 | } |
| 1833 | continue; | ||
| 1834 | } | ||
| 1835 | 1582 | ||
| 1836 | // TODO: Padding? | 1583 | return cg.constantComposite(result_ty_id, constituents[0..len]); |
| 1837 | const field_val = try val.fieldValue(pt, field_index); | 1584 | } |
| 1838 | const field_id = try cg.constant(field_ty, field_val, .indirect); | ||
| 1839 | 1585 | ||
| 1840 | try types.append(gpa, field_ty); | 1586 | const layout = cg.unionLayout(ty); |
| 1841 | try constituents.append(gpa, field_id); | 1587 | if (field_ty.hasRuntimeBits(zcu) and layout.payload_ty.eql(field_ty)) { |
| 1842 | } | 1588 | const constituents = try gpa.alloc(Id, layout.total_fields); |
| 1589 | defer gpa.free(constituents); | ||
| 1590 | constituents[layout.payload_index] = try cg.constant( | ||
| 1591 | field_ty, | ||
| 1592 | .fromInterned(un.val), | ||
| 1593 | .indirect, | ||
| 1594 | ); | ||
| 1843 | 1595 | ||
| 1844 | const comp_ty_id = try cg.resolveType(ty, .direct); | 1596 | if (layout.payload_padding_size != 0) { |
| 1845 | return try cg.constructComposite(comp_ty_id, constituents.items); | 1597 | const u8_ty_id = try cg.resolveType(.u8, .indirect); |
| 1846 | }, | 1598 | const len_id = try cg.constInt(.u32, layout.payload_padding_size); |
| 1847 | .tuple_type => |tuple| { | 1599 | constituents[layout.payload_padding_index] = try cg.constUndef(try cg.arrayType(len_id, u8_ty_id)); |
| 1848 | var constituents: std.ArrayList(Id) = .empty; | 1600 | } |
| 1849 | defer constituents.deinit(gpa); | ||
| 1850 | 1601 | ||
| 1851 | for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty, field_val, i| { | 1602 | if (layout.padding_size != 0) { |
| 1852 | if (field_val != .none) continue; | 1603 | const u8_ty_id = try cg.resolveType(.u8, .indirect); |
| 1853 | const ft: Type = .fromInterned(field_ty); | 1604 | const len_id = try cg.constInt(.u32, layout.padding_size); |
| 1854 | if (!ft.hasRuntimeBits(zcu)) continue; | 1605 | constituents[layout.padding_index] = try cg.constUndef(try cg.arrayType(len_id, u8_ty_id)); |
| 1606 | } | ||
| 1855 | 1607 | ||
| 1856 | const fv = try val.fieldValue(pt, i); | 1608 | return cg.constantComposite(result_ty_id, constituents); |
| 1857 | const field_id = try cg.constant(ft, fv, .indirect); | 1609 | } |
| 1858 | try constituents.append(gpa, field_id); | ||
| 1859 | } | ||
| 1860 | 1610 | ||
| 1861 | const comp_ty_id = try cg.resolveType(ty, .direct); | 1611 | const payload: ?Id = if (field_ty.hasRuntimeBits(zcu)) |
| 1862 | return try cg.constructComposite(comp_ty_id, constituents.items); | 1612 | try cg.constant(field_ty, .fromInterned(un.val), .direct) |
| 1863 | }, | 1613 | else |
| 1864 | else => unreachable, | 1614 | null; |
| 1865 | }, | 1615 | return try cg.unionInit(ty, active_field, payload); |
| 1866 | .un => |un| { | 1616 | }, |
| 1867 | assert(ty.containerLayout(zcu) != .@"packed"); // packed unions use `bitpack` | 1617 | .bitpack => |bitpack| { |
| 1868 | if (un.tag == .none) { | 1618 | const int_val: Value = .fromInterned(bitpack.backing_int_val); |
| 1869 | @panic("TODO"); | 1619 | break :cache try cg.constant(int_val.typeOf(zcu), int_val, repr); |
| 1870 | } | 1620 | }, |
| 1871 | const active_field = ty.unionTagFieldIndex(.fromInterned(un.tag), zcu).?; | ||
| 1872 | const union_obj = zcu.typeToUnion(ty).?; | ||
| 1873 | const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[active_field]); | ||
| 1874 | const payload = if (field_ty.hasRuntimeBits(zcu)) | ||
| 1875 | try cg.constant(field_ty, .fromInterned(un.val), .direct) | ||
| 1876 | else | ||
| 1877 | null; | ||
| 1878 | return try cg.unionInit(ty, active_field, payload); | ||
| 1879 | }, | ||
| 1880 | .bitpack => |bitpack| { | ||
| 1881 | const int_val: Value = .fromInterned(bitpack.backing_int_val); | ||
| 1882 | break :cache try cg.constant(int_val.typeOf(zcu), int_val, repr); | ||
| 1883 | }, | ||
| 1884 | 1621 | ||
| 1885 | .memoized_call => unreachable, | 1622 | .memoized_call => unreachable, |
| 1886 | } | ||
| 1887 | }; | 1623 | }; |
| 1888 | return cacheable_id; | 1624 | return cacheable_id; |
| 1889 | } | 1625 | } |
| ... | @@ -1942,46 +1678,42 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { | ... | @@ -1942,46 +1678,42 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { |
| 1942 | const nav = ip.getNav(nav_index); | 1678 | const nav = ip.getNav(nav_index); |
| 1943 | const nav_ty: Type = .fromInterned(nav.resolved.?.type); | 1679 | const nav_ty: Type = .fromInterned(nav.resolved.?.type); |
| 1944 | 1680 | ||
| 1945 | switch (nav.resolved.?.value) { | 1681 | const is_extern = nav.getExtern(ip) != null; |
| 1946 | .none => {}, | 1682 | assert(nav.resolved.?.@"const" or is_extern); |
| 1947 | else => |value| switch (ip.indexToKey(value)) { | ||
| 1948 | // TODO: Properly lower function pointers; for now substitute undef. | ||
| 1949 | .func => return try cg.constUndef(ty_id), | ||
| 1950 | .@"extern" => if (ip.isFunctionType(nav_ty.toIntern())) { | ||
| 1951 | const spv_decl_index = try cg.resolveNav(ip, nav_index); | ||
| 1952 | const decl = cg.declPtr(spv_decl_index); | ||
| 1953 | try emitExternFnStub(cg, nav, decl, nav_ty); | ||
| 1954 | return decl.result_id; | ||
| 1955 | }, | ||
| 1956 | else => {}, | ||
| 1957 | }, | ||
| 1958 | } | ||
| 1959 | 1683 | ||
| 1684 | if (ip.isFunctionType(nav_ty.toIntern())) { | ||
| 1685 | if (is_extern) return try cg.resolveExternFn(nav_index); | ||
| 1686 | return try cg.constUndef(ty_id); | ||
| 1687 | } | ||
| 1960 | if (!nav_ty.hasRuntimeBits(zcu) and nav_ty.zigTypeTag(zcu) != .spirv) { | 1688 | if (!nav_ty.hasRuntimeBits(zcu) and nav_ty.zigTypeTag(zcu) != .spirv) { |
| 1961 | return cg.constUndef(ty_id); | 1689 | return cg.constUndef(ty_id); |
| 1962 | } | 1690 | } |
| 1691 | if (!is_extern) { | ||
| 1692 | return cg.todo("pointer to constant '{f}'", .{nav.fqn.fmt(ip)}); | ||
| 1693 | } | ||
| 1963 | 1694 | ||
| 1964 | const spv_decl_index = try cg.resolveNav(ip, nav_index); | 1695 | const as = nav.resolved.?.@"addrspace"; |
| 1965 | const spv_decl = cg.declPtr(spv_decl_index); | 1696 | assert(as != .generic); |
| 1966 | assert(spv_decl.kind != .func); | ||
| 1967 | const storage_class = cg.storageClass(nav.resolved.?.@"addrspace"); | ||
| 1968 | try cg.addFunctionDep(spv_decl_index, storage_class); | ||
| 1969 | 1697 | ||
| 1698 | const storage_class = cg.storageClass(as); | ||
| 1699 | const var_id = try cg.resolveNav(nav_index); | ||
| 1970 | const nav_ty_id = try cg.resolveType(nav_ty, .indirect); | 1700 | const nav_ty_id = try cg.resolveType(nav_ty, .indirect); |
| 1971 | const decl_ptr_ty_id = try cg.ptrType(nav_ty_id, storage_class); | 1701 | const decl_ptr_ty_id = try cg.ptrType(nav_ty_id, storage_class); |
| 1972 | if (cg.needsLayout(nav.resolved.?.@"addrspace", nav_ty)) { | 1702 | if (cg.needsLayout(as, nav_ty)) { |
| 1973 | try cg.block_var_ids.put(gpa, spv_decl.result_id, {}); | 1703 | try cg.block_var_ids.put(gpa, var_id, {}); |
| 1974 | } | 1704 | } |
| 1975 | if (decl_ptr_ty_id == ty_id) return spv_decl.result_id; | 1705 | |
| 1706 | if (decl_ptr_ty_id == ty_id) return var_id; | ||
| 1976 | switch (target.os.tag) { | 1707 | switch (target.os.tag) { |
| 1977 | .vulkan, .opengl => return spv_decl.result_id, | 1708 | .vulkan, .opengl => return var_id, |
| 1978 | else => {}, | 1709 | else => {}, |
| 1979 | } | 1710 | } |
| 1711 | |||
| 1980 | const casted_ptr_id = cg.allocId(); | 1712 | const casted_ptr_id = cg.allocId(); |
| 1981 | try cg.body.emit(gpa, .OpBitcast, .{ | 1713 | try cg.body.emit(gpa, .OpBitcast, .{ |
| 1982 | .id_result_type = ty_id, | 1714 | .id_result_type = ty_id, |
| 1983 | .id_result = casted_ptr_id, | 1715 | .id_result = casted_ptr_id, |
| 1984 | .operand = spv_decl.result_id, | 1716 | .operand = var_id, |
| 1985 | }); | 1717 | }); |
| 1986 | return casted_ptr_id; | 1718 | return casted_ptr_id; |
| 1987 | }, | 1719 | }, |
| ... | @@ -2001,24 +1733,15 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { | ... | @@ -2001,24 +1733,15 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { |
| 2001 | return cg.constUndef(ty_id); | 1733 | return cg.constUndef(ty_id); |
| 2002 | } | 1734 | } |
| 2003 | 1735 | ||
| 2004 | // Uav refs are always generic. | 1736 | if (cg.storageClass(result_ptr_ty.ptrAddressSpace(zcu)) != .function) { |
| 2005 | assert(result_ptr_ty.ptrAddressSpace(zcu) == .generic); | 1737 | return cg.todo("pointer to an anonymous constant of type '{f}'", .{uav_ty.fmt(pt)}); |
| 2006 | const uav_ty_id = try cg.resolveType(uav_ty, .indirect); | ||
| 2007 | const decl_ptr_ty_id = try cg.ptrType(uav_ty_id, .function); | ||
| 2008 | const ptr_id = try cg.resolveUav(uav.val); | ||
| 2009 | |||
| 2010 | if (decl_ptr_ty_id == ty_id) return ptr_id; | ||
| 2011 | switch (target.os.tag) { | ||
| 2012 | .vulkan, .opengl => return ptr_id, | ||
| 2013 | else => {}, | ||
| 2014 | } | 1738 | } |
| 2015 | const casted_ptr_id = cg.allocId(); | 1739 | |
| 2016 | try cg.body.emit(gpa, .OpBitcast, .{ | 1740 | const uav_ty_id = try cg.resolveType(uav_ty, .indirect); |
| 2017 | .id_result_type = ty_id, | 1741 | const ptr_id = try cg.alloc(uav_ty_id, null); |
| 2018 | .id_result = casted_ptr_id, | 1742 | const val_id = try cg.constant(uav_ty, .fromInterned(uav.val), .indirect); |
| 2019 | .operand = ptr_id, | 1743 | try cg.body.emit(gpa, .OpStore, .{ .pointer = ptr_id, .object = val_id }); |
| 2020 | }); | 1744 | return ptr_id; |
| 2021 | return casted_ptr_id; | ||
| 2022 | }, | 1745 | }, |
| 2023 | .eu_payload_ptr => @panic("TODO"), | 1746 | .eu_payload_ptr => @panic("TODO"), |
| 2024 | .opt_payload_ptr => @panic("TODO"), | 1747 | .opt_payload_ptr => @panic("TODO"), |
| ... | @@ -2102,61 +1825,6 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { | ... | @@ -2102,61 +1825,6 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { |
| 2102 | } | 1825 | } |
| 2103 | } | 1826 | } |
| 2104 | 1827 | ||
| 2105 | /// Emit a stub OpFunction/OpFunctionEnd + Import linkage decoration for an | ||
| 2106 | /// extern function so the module is structurally valid. The stub will be | ||
| 2107 | /// replaced by the real definition at link time. | ||
| 2108 | fn emitExternFnStub(cg: *CodeGen, nav: InternPool.Nav, decl: *Decl, fn_ty: Type) !void { | ||
| 2109 | if (decl.has_extern_stub) return; | ||
| 2110 | decl.has_extern_stub = true; | ||
| 2111 | |||
| 2112 | const gpa = cg.gpa; | ||
| 2113 | const zcu = cg.zcu; | ||
| 2114 | const ip = &zcu.intern_pool; | ||
| 2115 | const fn_info = zcu.typeToFunc(fn_ty).?; | ||
| 2116 | const return_ty_id = try cg.resolveFnReturnType(.fromInterned(fn_info.return_type)); | ||
| 2117 | const prototype_ty_id = try cg.resolveType(fn_ty, .direct); | ||
| 2118 | |||
| 2119 | var stub: Section = .{}; | ||
| 2120 | defer stub.deinit(gpa); | ||
| 2121 | try stub.emit(gpa, .OpFunction, .{ | ||
| 2122 | .id_result_type = return_ty_id, | ||
| 2123 | .id_result = decl.result_id, | ||
| 2124 | .function_type = prototype_ty_id, | ||
| 2125 | .function_control = .{}, | ||
| 2126 | }); | ||
| 2127 | for (fn_info.param_types.get(ip)) |param_ty_index| { | ||
| 2128 | const param_ty: Type = .fromInterned(param_ty_index); | ||
| 2129 | if (!param_ty.hasRuntimeBits(zcu)) continue; | ||
| 2130 | const param_type_id = try cg.resolveType(param_ty, .direct); | ||
| 2131 | try stub.emit(gpa, .OpFunctionParameter, .{ | ||
| 2132 | .id_result_type = param_type_id, | ||
| 2133 | .id_result = cg.allocId(), | ||
| 2134 | }); | ||
| 2135 | } | ||
| 2136 | try stub.emit(gpa, .OpFunctionEnd, {}); | ||
| 2137 | try cg.sections.functions.append(gpa, stub); | ||
| 2138 | |||
| 2139 | const extern_name = nav.getExtern(ip).?.name.toSlice(ip); | ||
| 2140 | try cg.sections.annotations.emit(gpa, .OpDecorate, .{ | ||
| 2141 | .target = decl.result_id, | ||
| 2142 | .decoration = .{ .linkage_attributes = .{ | ||
| 2143 | .name = extern_name, | ||
| 2144 | .linkage_type = .import, | ||
| 2145 | } }, | ||
| 2146 | }); | ||
| 2147 | try cg.debugName(decl.result_id, extern_name); | ||
| 2148 | } | ||
| 2149 | |||
| 2150 | fn resolveTypeName(cg: *CodeGen, ty: Type) ![]const u8 { | ||
| 2151 | const gpa = cg.gpa; | ||
| 2152 | var aw: std.Io.Writer.Allocating = .init(gpa); | ||
| 2153 | defer aw.deinit(); | ||
| 2154 | ty.print(&aw.writer, cg.pt, null) catch |err| switch (err) { | ||
| 2155 | error.WriteFailed => return error.OutOfMemory, | ||
| 2156 | }; | ||
| 2157 | return try aw.toOwnedSlice(); | ||
| 2158 | } | ||
| 2159 | |||
| 2160 | /// Generate a union type. Union types are always generated with the | 1828 | /// Generate a union type. Union types are always generated with the |
| 2161 | /// most aligned field active. If the tag alignment is greater | 1829 | /// most aligned field active. If the tag alignment is greater |
| 2162 | /// than that of the payload, a regular union (non-packed, with both tag and | 1830 | /// than that of the payload, a regular union (non-packed, with both tag and |
| ... | @@ -2209,7 +1877,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { | ... | @@ -2209,7 +1877,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { |
| 2209 | .direct => return try cg.voidType(), | 1877 | .direct => return try cg.voidType(), |
| 2210 | .indirect => { | 1878 | .indirect => { |
| 2211 | if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); | 1879 | if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); |
| 2212 | return try cg.opaqueType("void"); | 1880 | return try cg.opaqueType("void", .{}); |
| 2213 | }, | 1881 | }, |
| 2214 | }, | 1882 | }, |
| 2215 | .bool => switch (repr) { | 1883 | .bool => switch (repr) { |
| ... | @@ -2220,7 +1888,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { | ... | @@ -2220,7 +1888,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { |
| 2220 | if (ty.toIntern() == .u0_type) { | 1888 | if (ty.toIntern() == .u0_type) { |
| 2221 | assert(repr == .indirect); | 1889 | assert(repr == .indirect); |
| 2222 | if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); | 1890 | if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); |
| 2223 | return try cg.opaqueType("u0"); | 1891 | return try cg.opaqueType("u0", .{}); |
| 2224 | } | 1892 | } |
| 2225 | const int_info = ty.intInfo(zcu); | 1893 | const int_info = ty.intInfo(zcu); |
| 2226 | return try cg.intType(int_info.signedness, int_info.bits); | 1894 | return try cg.intType(int_info.signedness, int_info.bits); |
| ... | @@ -2250,7 +1918,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { | ... | @@ -2250,7 +1918,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { |
| 2250 | if (!elem_ty.hasRuntimeBits(zcu)) { | 1918 | if (!elem_ty.hasRuntimeBits(zcu)) { |
| 2251 | assert(repr == .indirect); | 1919 | assert(repr == .indirect); |
| 2252 | if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); | 1920 | if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); |
| 2253 | return try cg.opaqueType("zero-sized-array"); | 1921 | return try cg.opaqueType("zero-sized-array", .{}); |
| 2254 | } else if (total_len == 0) { | 1922 | } else if (total_len == 0) { |
| 2255 | // The size of the array would be 0, but that is not allowed in SPIR-V. | 1923 | // The size of the array would be 0, but that is not allowed in SPIR-V. |
| 2256 | // This path can be reached for example when there is a slicing of a pointer | 1924 | // This path can be reached for example when there is a slicing of a pointer |
| ... | @@ -2364,9 +2032,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { | ... | @@ -2364,9 +2032,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { |
| 2364 | null, | 2032 | null, |
| 2365 | .none, | 2033 | .none, |
| 2366 | ); | 2034 | ); |
| 2367 | const type_name = try cg.resolveTypeName(ty); | 2035 | try cg.debugNameFmt(result_id, "{f}", .{ty.fmt(cg.pt)}); |
| 2368 | defer gpa.free(type_name); | ||
| 2369 | try cg.debugName(result_id, type_name); | ||
| 2370 | return result_id; | 2036 | return result_id; |
| 2371 | }, | 2037 | }, |
| 2372 | .struct_type => ip.loadStructType(ty.toIntern()), | 2038 | .struct_type => ip.loadStructType(ty.toIntern()), |
| ... | @@ -2399,9 +2065,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { | ... | @@ -2399,9 +2065,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { |
| 2399 | ty.toIntern(), | 2065 | ty.toIntern(), |
| 2400 | ); | 2066 | ); |
| 2401 | 2067 | ||
| 2402 | const type_name = try cg.resolveTypeName(ty); | 2068 | try cg.debugNameFmt(result_id, "{f}", .{ty.fmt(cg.pt)}); |
| 2403 | defer gpa.free(type_name); | ||
| 2404 | try cg.debugName(result_id, type_name); | ||
| 2405 | 2069 | ||
| 2406 | return result_id; | 2070 | return result_id; |
| 2407 | }, | 2071 | }, |
| ... | @@ -2430,9 +2094,36 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { | ... | @@ -2430,9 +2094,36 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { |
| 2430 | }, | 2094 | }, |
| 2431 | .@"union" => { | 2095 | .@"union" => { |
| 2432 | const union_obj = zcu.typeToUnion(ty).?; | 2096 | const union_obj = zcu.typeToUnion(ty).?; |
| 2433 | if (union_obj.layout == .@"packed") { | 2097 | switch (union_obj.layout) { |
| 2434 | return try cg.intType(.unsigned, @intCast(ty.bitSize(zcu))); | 2098 | .@"extern" => {}, |
| 2099 | .@"packed" => return try cg.intType(.unsigned, @intCast(ty.bitSize(zcu))), | ||
| 2100 | .auto => { | ||
| 2101 | if (ty.unionHasAllZeroBitFieldTypes(zcu)) { | ||
| 2102 | return try cg.resolveType(ty.unionTagTypeRuntime(zcu).?, .indirect); | ||
| 2103 | } | ||
| 2104 | |||
| 2105 | const member_types = try gpa.alloc(Id, union_obj.field_types.len + 1); | ||
| 2106 | defer gpa.free(member_types); | ||
| 2107 | for (member_types[0..union_obj.field_types.len], union_obj.field_types.get(ip)) |*member_ty_id, field_ty_index| { | ||
| 2108 | const field_ty: Type = .fromInterned(field_ty_index); | ||
| 2109 | member_ty_id.* = if (field_ty.hasRuntimeBits(zcu)) | ||
| 2110 | try cg.resolveType(field_ty, .indirect) | ||
| 2111 | else | ||
| 2112 | try cg.structType(&.{}, null, .none); | ||
| 2113 | } | ||
| 2114 | |||
| 2115 | var len = union_obj.field_types.len; | ||
| 2116 | if (ty.unionTagTypeRuntime(zcu)) |tag_ty| { | ||
| 2117 | member_types[len] = try cg.resolveType(tag_ty, .indirect); | ||
| 2118 | len += 1; | ||
| 2119 | } | ||
| 2120 | |||
| 2121 | const result_id = try cg.structType(member_types[0..len], null, .none); | ||
| 2122 | try cg.debugNameFmt(result_id, "{f}", .{ty.fmt(cg.pt)}); | ||
| 2123 | return result_id; | ||
| 2124 | }, | ||
| 2435 | } | 2125 | } |
| 2126 | |||
| 2436 | const layout = cg.unionLayout(ty); | 2127 | const layout = cg.unionLayout(ty); |
| 2437 | if (!layout.has_payload) { | 2128 | if (!layout.has_payload) { |
| 2438 | return try cg.resolveType(.fromInterned(union_obj.enum_tag_type), .indirect); | 2129 | return try cg.resolveType(.fromInterned(union_obj.enum_tag_type), .indirect); |
| ... | @@ -2463,9 +2154,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { | ... | @@ -2463,9 +2154,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { |
| 2463 | member_names[0..layout.total_fields], | 2154 | member_names[0..layout.total_fields], |
| 2464 | .none, | 2155 | .none, |
| 2465 | ); | 2156 | ); |
| 2466 | const type_name = try cg.resolveTypeName(ty); | 2157 | try cg.debugNameFmt(result_id, "{f}", .{ty.fmt(cg.pt)}); |
| 2467 | defer gpa.free(type_name); | ||
| 2468 | try cg.debugName(result_id, type_name); | ||
| 2469 | return result_id; | 2158 | return result_id; |
| 2470 | }, | 2159 | }, |
| 2471 | .error_set => { | 2160 | .error_set => { |
| ... | @@ -2502,18 +2191,16 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { | ... | @@ -2502,18 +2191,16 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { |
| 2502 | }, | 2191 | }, |
| 2503 | .@"opaque" => { | 2192 | .@"opaque" => { |
| 2504 | if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); | 2193 | if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); |
| 2505 | const type_name = try cg.resolveTypeName(ty); | 2194 | return try cg.opaqueType("{f}", .{ty.fmt(cg.pt)}); |
| 2506 | defer gpa.free(type_name); | ||
| 2507 | return try cg.opaqueType(type_name); | ||
| 2508 | }, | 2195 | }, |
| 2509 | .spirv => { | 2196 | .spirv => { |
| 2510 | const spirv_type = ip.loadSpirvType(ty.toIntern()); | 2197 | const spirv_type = ip.loadSpirvType(ty.toIntern()); |
| 2511 | const result_id = cg.allocId(); | 2198 | const result_id = cg.allocId(); |
| 2512 | switch (spirv_type.flags.tag) { | 2199 | switch (spirv_type.flags.tag) { |
| 2513 | .sampler => try cg.sections.globals.emit(gpa, .OpTypeSampler, .{ .id_result = result_id }), | 2200 | .sampler => try cg.module.emit(gpa, .OpTypeSampler, .{ .id_result = result_id }), |
| 2514 | .image => { | 2201 | .image => { |
| 2515 | const sampled_type_id = try cg.resolveType(.fromInterned(spirv_type.ty), .direct); | 2202 | const sampled_type_id = try cg.resolveType(.fromInterned(spirv_type.ty), .direct); |
| 2516 | try cg.sections.globals.emit(gpa, .OpTypeImage, .{ | 2203 | try cg.module.emit(gpa, .OpTypeImage, .{ |
| 2517 | .id_result = result_id, | 2204 | .id_result = result_id, |
| 2518 | .sampled_type = sampled_type_id, | 2205 | .sampled_type = sampled_type_id, |
| 2519 | .dim = switch (spirv_type.flags.dim) { | 2206 | .dim = switch (spirv_type.flags.dim) { |
| ... | @@ -2560,7 +2247,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { | ... | @@ -2560,7 +2247,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { |
| 2560 | }, | 2247 | }, |
| 2561 | .sampled_image => { | 2248 | .sampled_image => { |
| 2562 | const image_ty_id = try cg.resolveType(.fromInterned(spirv_type.ty), .indirect); | 2249 | const image_ty_id = try cg.resolveType(.fromInterned(spirv_type.ty), .indirect); |
| 2563 | try cg.sections.globals.emit(gpa, .OpTypeSampledImage, .{ | 2250 | try cg.module.emit(gpa, .OpTypeSampledImage, .{ |
| 2564 | .id_result = result_id, | 2251 | .id_result = result_id, |
| 2565 | .image_type = image_ty_id, | 2252 | .image_type = image_ty_id, |
| 2566 | }); | 2253 | }); |
| ... | @@ -2568,7 +2255,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { | ... | @@ -2568,7 +2255,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { |
| 2568 | .runtime_array => { | 2255 | .runtime_array => { |
| 2569 | const elem_ty: Type = .fromInterned(spirv_type.ty); | 2256 | const elem_ty: Type = .fromInterned(spirv_type.ty); |
| 2570 | const elem_ty_id = try cg.resolveType(elem_ty, .indirect); | 2257 | const elem_ty_id = try cg.resolveType(elem_ty, .indirect); |
| 2571 | try cg.sections.globals.emit(gpa, .OpTypeRuntimeArray, .{ | 2258 | try cg.module.emit(gpa, .OpTypeRuntimeArray, .{ |
| 2572 | .id_result = result_id, | 2259 | .id_result = result_id, |
| 2573 | .element_type = elem_ty_id, | 2260 | .element_type = elem_ty_id, |
| 2574 | }); | 2261 | }); |
| ... | @@ -4061,7 +3748,7 @@ fn buildWideMul( | ... | @@ -4061,7 +3748,7 @@ fn buildWideMul( |
| 4061 | fn generateTestEntryPoint( | 3748 | fn generateTestEntryPoint( |
| 4062 | cg: *CodeGen, | 3749 | cg: *CodeGen, |
| 4063 | name: []const u8, | 3750 | name: []const u8, |
| 4064 | spv_decl_index: Decl.Index, | 3751 | kernel_id: Id, |
| 4065 | test_id: Id, | 3752 | test_id: Id, |
| 4066 | ) !void { | 3753 | ) !void { |
| 4067 | const gpa = cg.gpa; | 3754 | const gpa = cg.gpa; |
| ... | @@ -4075,9 +3762,8 @@ fn generateTestEntryPoint( | ... | @@ -4075,9 +3762,8 @@ fn generateTestEntryPoint( |
| 4075 | }); | 3762 | }); |
| 4076 | const ptr_anyerror_ty_id = try cg.resolveType(ptr_anyerror_ty, .direct); | 3763 | const ptr_anyerror_ty_id = try cg.resolveType(ptr_anyerror_ty, .direct); |
| 4077 | 3764 | ||
| 4078 | const kernel_id = cg.declPtr(spv_decl_index).result_id; | 3765 | var section: Section = .empty; |
| 4079 | 3766 | defer section.deinit(gpa); | |
| 4080 | const section = &cg.sections.functions; | ||
| 4081 | 3767 | ||
| 4082 | const p_error_id = cg.allocId(); | 3768 | const p_error_id = cg.allocId(); |
| 4083 | switch (target.os.tag) { | 3769 | switch (target.os.tag) { |
| ... | @@ -4103,11 +3789,8 @@ fn generateTestEntryPoint( | ... | @@ -4103,11 +3789,8 @@ fn generateTestEntryPoint( |
| 4103 | }, | 3789 | }, |
| 4104 | .vulkan, .opengl => { | 3790 | .vulkan, .opengl => { |
| 4105 | if (cg.error_buffer == null) { | 3791 | if (cg.error_buffer == null) { |
| 4106 | const spv_err_decl_index = try cg.allocDecl(.global); | ||
| 4107 | const err_buf_result_id = cg.declPtr(spv_err_decl_index).result_id; | ||
| 4108 | |||
| 4109 | const buffer_struct_ty_id = cg.allocId(); | 3792 | const buffer_struct_ty_id = cg.allocId(); |
| 4110 | try cg.sections.globals.emit(gpa, .OpTypeStruct, .{ | 3793 | try cg.module.emit(gpa, .OpTypeStruct, .{ |
| 4111 | .id_result = buffer_struct_ty_id, | 3794 | .id_result = buffer_struct_ty_id, |
| 4112 | .id_ref = &.{anyerror_ty_id}, | 3795 | .id_ref = &.{anyerror_ty_id}, |
| 4113 | }); | 3796 | }); |
| ... | @@ -4116,21 +3799,21 @@ fn generateTestEntryPoint( | ... | @@ -4116,21 +3799,21 @@ fn generateTestEntryPoint( |
| 4116 | try cg.decorateMember(buffer_struct_ty_id, 0, .{ .offset = .{ .byte_offset = 0 } }); | 3799 | try cg.decorateMember(buffer_struct_ty_id, 0, .{ .offset = .{ .byte_offset = 0 } }); |
| 4117 | 3800 | ||
| 4118 | const ptr_buffer_struct_ty_id = cg.allocId(); | 3801 | const ptr_buffer_struct_ty_id = cg.allocId(); |
| 4119 | try cg.sections.globals.emit(gpa, .OpTypePointer, .{ | 3802 | try cg.module.emit(gpa, .OpTypePointer, .{ |
| 4120 | .id_result = ptr_buffer_struct_ty_id, | 3803 | .id_result = ptr_buffer_struct_ty_id, |
| 4121 | .storage_class = cg.storageClass(.global), | 3804 | .storage_class = cg.storageClass(.global), |
| 4122 | .type = buffer_struct_ty_id, | 3805 | .type = buffer_struct_ty_id, |
| 4123 | }); | 3806 | }); |
| 4124 | 3807 | ||
| 4125 | try cg.sections.globals.emit(gpa, .OpVariable, .{ | 3808 | const error_buffer_id = cg.allocId(); |
| 3809 | cg.error_buffer = error_buffer_id; | ||
| 3810 | try cg.module.emit(gpa, .OpVariable, .{ | ||
| 4126 | .id_result_type = ptr_buffer_struct_ty_id, | 3811 | .id_result_type = ptr_buffer_struct_ty_id, |
| 4127 | .id_result = err_buf_result_id, | 3812 | .id_result = error_buffer_id, |
| 4128 | .storage_class = cg.storageClass(.global), | 3813 | .storage_class = cg.storageClass(.global), |
| 4129 | }); | 3814 | }); |
| 4130 | try cg.decorate(err_buf_result_id, .{ .descriptor_set = .{ .descriptor_set = 0 } }); | 3815 | try cg.decorate(error_buffer_id, .{ .descriptor_set = .{ .descriptor_set = 0 } }); |
| 4131 | try cg.decorate(err_buf_result_id, .{ .binding = .{ .binding_point = 0 } }); | 3816 | try cg.decorate(error_buffer_id, .{ .binding = .{ .binding_point = 0 } }); |
| 4132 | |||
| 4133 | cg.error_buffer = spv_err_decl_index; | ||
| 4134 | } | 3817 | } |
| 4135 | 3818 | ||
| 4136 | const void_ty_id = try cg.resolveType(.void, .direct); | 3819 | const void_ty_id = try cg.resolveType(.void, .direct); |
| ... | @@ -4145,10 +3828,7 @@ fn generateTestEntryPoint( | ... | @@ -4145,10 +3828,7 @@ fn generateTestEntryPoint( |
| 4145 | .id_result = cg.allocId(), | 3828 | .id_result = cg.allocId(), |
| 4146 | }); | 3829 | }); |
| 4147 | 3830 | ||
| 4148 | const spv_err_decl_index = cg.error_buffer.?; | 3831 | const buffer_id = cg.error_buffer.?; |
| 4149 | const buffer_id = cg.declPtr(spv_err_decl_index).result_id; | ||
| 4150 | try cg.decl_deps.append(gpa, spv_err_decl_index); | ||
| 4151 | |||
| 4152 | const zero_id = try cg.constInt(.u32, 0); | 3832 | const zero_id = try cg.constInt(.u32, 0); |
| 4153 | try section.emit(gpa, .OpInBoundsAccessChain, .{ | 3833 | try section.emit(gpa, .OpInBoundsAccessChain, .{ |
| 4154 | .id_result_type = ptr_anyerror_ty_id, | 3834 | .id_result_type = ptr_anyerror_ty_id, |
| ... | @@ -4176,17 +3856,17 @@ fn generateTestEntryPoint( | ... | @@ -4176,17 +3856,17 @@ fn generateTestEntryPoint( |
| 4176 | }); | 3856 | }); |
| 4177 | try section.emit(gpa, .OpReturn, {}); | 3857 | try section.emit(gpa, .OpReturn, {}); |
| 4178 | try section.emit(gpa, .OpFunctionEnd, {}); | 3858 | try section.emit(gpa, .OpFunctionEnd, {}); |
| 3859 | try cg.module.append(gpa, section); | ||
| 4179 | 3860 | ||
| 4180 | // Just generate a quick other name because the intel runtime crashes when the entry- | 3861 | // Just generate a quick other name because the intel runtime crashes when the entry- |
| 4181 | // point name is the same as a different OpName. | 3862 | // point name is the same as a different OpName. |
| 4182 | const test_name = try std.fmt.allocPrint(cg.arena, "test {s}", .{name}); | 3863 | const test_name = try std.fmt.allocPrint(gpa, "test {s}", .{name}); |
| 4183 | 3864 | errdefer gpa.free(test_name); | |
| 4184 | const ep_gop = try cg.entry_points.getOrPut(cg.gpa, cg.declPtr(spv_decl_index).result_id); | 3865 | try cg.entry_points.append(gpa, .{ |
| 4185 | ep_gop.value_ptr.* = .{ | 3866 | .id = kernel_id, |
| 4186 | .decl_index = spv_decl_index, | ||
| 4187 | .name = test_name, | 3867 | .name = test_name, |
| 4188 | .cc = .{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }, | 3868 | .cc = .{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }, |
| 4189 | }; | 3869 | }); |
| 4190 | } | 3870 | } |
| 4191 | 3871 | ||
| 4192 | fn intFromBool(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary { | 3872 | fn intFromBool(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary { |
| ... | @@ -4456,6 +4136,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) Error!void { | ... | @@ -4456,6 +4136,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) Error!void { |
| 4456 | .ptr_elem_ptr => try cg.airPtrElemPtr(inst), | 4136 | .ptr_elem_ptr => try cg.airPtrElemPtr(inst), |
| 4457 | .ptr_elem_val => try cg.airPtrElemVal(inst), | 4137 | .ptr_elem_val => try cg.airPtrElemVal(inst), |
| 4458 | .array_elem_val => try cg.airArrayElemVal(inst), | 4138 | .array_elem_val => try cg.airArrayElemVal(inst), |
| 4139 | .runtime_nav_ptr => try cg.airRuntimeNavPtr(inst), | ||
| 4459 | 4140 | ||
| 4460 | .set_union_tag => return cg.airSetUnionTag(inst), | 4141 | .set_union_tag => return cg.airSetUnionTag(inst), |
| 4461 | .get_union_tag => try cg.airGetUnionTag(inst), | 4142 | .get_union_tag => try cg.airGetUnionTag(inst), |
| ... | @@ -5549,12 +5230,17 @@ fn airSelect(cg: *CodeGen, inst: Air.Inst.Index) !?Id { | ... | @@ -5549,12 +5230,17 @@ fn airSelect(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 5549 | } | 5230 | } |
| 5550 | 5231 | ||
| 5551 | fn airSplat(cg: *CodeGen, inst: Air.Inst.Index) !?Id { | 5232 | fn airSplat(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 5233 | const gpa = cg.gpa; | ||
| 5234 | const zcu = cg.zcu; | ||
| 5552 | const ty_op = cg.air.instructions.items(.data)[@backingInt(inst)].ty_op; | 5235 | const ty_op = cg.air.instructions.items(.data)[@backingInt(inst)].ty_op; |
| 5553 | |||
| 5554 | const operand_id = try cg.resolve(ty_op.operand); | 5236 | const operand_id = try cg.resolve(ty_op.operand); |
| 5555 | const result_ty = cg.typeOfIndex(inst); | 5237 | const result_ty = cg.typeOfIndex(inst); |
| 5556 | 5238 | ||
| 5557 | return try cg.constructCompositeSplat(result_ty, operand_id); | 5239 | const scratch_top = cg.id_scratch.items.len; |
| 5240 | defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); | ||
| 5241 | const constituents = try cg.id_scratch.addManyAsSlice(gpa, @intCast(result_ty.arrayLen(zcu))); | ||
| 5242 | @memset(constituents, operand_id); | ||
| 5243 | return try cg.constructComposite(try cg.resolveType(result_ty, .direct), constituents); | ||
| 5558 | } | 5244 | } |
| 5559 | 5245 | ||
| 5560 | fn airReduce(cg: *CodeGen, inst: Air.Inst.Index) !?Id { | 5246 | fn airReduce(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| ... | @@ -6767,6 +6453,11 @@ fn ptrElemPtr(cg: *CodeGen, ptr_ty: Type, ptr_id: Id, index_id: Id) !Id { | ... | @@ -6767,6 +6453,11 @@ fn ptrElemPtr(cg: *CodeGen, ptr_ty: Type, ptr_id: Id, index_id: Id) !Id { |
| 6767 | } | 6453 | } |
| 6768 | } | 6454 | } |
| 6769 | 6455 | ||
| 6456 | fn airRuntimeNavPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { | ||
| 6457 | const ty_nav = cg.air.instructions.items(.data)[@backingInt(inst)].ty_nav; | ||
| 6458 | return try cg.resolveNav(ty_nav.nav); | ||
| 6459 | } | ||
| 6460 | |||
| 6770 | fn airPtrElemPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { | 6461 | fn airPtrElemPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 6771 | const zcu = cg.zcu; | 6462 | const zcu = cg.zcu; |
| 6772 | const ty_pl = cg.air.instructions.items(.data)[@backingInt(inst)].ty_pl; | 6463 | const ty_pl = cg.air.instructions.items(.data)[@backingInt(inst)].ty_pl; |
| ... | @@ -6849,38 +6540,27 @@ fn airSetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !void { | ... | @@ -6849,38 +6540,27 @@ fn airSetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !void { |
| 6849 | const bin_op = cg.air.instructions.items(.data)[@backingInt(inst)].bin_op; | 6540 | const bin_op = cg.air.instructions.items(.data)[@backingInt(inst)].bin_op; |
| 6850 | const un_ptr_ty = cg.typeOf(bin_op.lhs); | 6541 | const un_ptr_ty = cg.typeOf(bin_op.lhs); |
| 6851 | const un_ty = un_ptr_ty.childType(zcu); | 6542 | const un_ty = un_ptr_ty.childType(zcu); |
| 6852 | const layout = cg.unionLayout(un_ty); | 6543 | const tag_ty = un_ty.unionTagTypeRuntime(zcu) orelse return; |
| 6853 | |||
| 6854 | if (layout.tag_size == 0) return; | ||
| 6855 | |||
| 6856 | const tag_ty = un_ty.unionTagTypeRuntime(zcu).?; | ||
| 6857 | const tag_ty_id = try cg.resolveType(tag_ty, .indirect); | 6544 | const tag_ty_id = try cg.resolveType(tag_ty, .indirect); |
| 6858 | const tag_ptr_ty_id = try cg.ptrType(tag_ty_id, cg.storageClass(un_ptr_ty.ptrAddressSpace(zcu))); | 6545 | const tag_ptr_ty_id = try cg.ptrType(tag_ty_id, cg.storageClass(un_ptr_ty.ptrAddressSpace(zcu))); |
| 6859 | |||
| 6860 | const union_ptr_id = try cg.resolve(bin_op.lhs); | 6546 | const union_ptr_id = try cg.resolve(bin_op.lhs); |
| 6861 | const new_tag_id = try cg.resolve(bin_op.rhs); | 6547 | const new_tag_id = try cg.resolve(bin_op.rhs); |
| 6862 | 6548 | const ptr_id = if (un_ty.unionHasAllZeroBitFieldTypes(zcu)) | |
| 6863 | if (!layout.has_payload) { | 6549 | union_ptr_id |
| 6864 | try cg.store(tag_ty, union_ptr_id, new_tag_id, .{ .is_volatile = un_ptr_ty.isVolatilePtr(zcu) }); | 6550 | else |
| 6865 | } else { | 6551 | try cg.accessChain(tag_ptr_ty_id, union_ptr_id, &.{zcu.typeToUnion(un_ty).?.field_types.len}); |
| 6866 | const ptr_id = try cg.accessChain(tag_ptr_ty_id, union_ptr_id, &.{layout.tag_index}); | 6552 | try cg.store(tag_ty, ptr_id, new_tag_id, .{ .is_volatile = un_ptr_ty.isVolatilePtr(zcu) }); |
| 6867 | try cg.store(tag_ty, ptr_id, new_tag_id, .{ .is_volatile = un_ptr_ty.isVolatilePtr(zcu) }); | ||
| 6868 | } | ||
| 6869 | } | 6553 | } |
| 6870 | 6554 | ||
| 6871 | fn airGetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !?Id { | 6555 | fn airGetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 6556 | const zcu = cg.zcu; | ||
| 6872 | const ty_op = cg.air.instructions.items(.data)[@backingInt(inst)].ty_op; | 6557 | const ty_op = cg.air.instructions.items(.data)[@backingInt(inst)].ty_op; |
| 6873 | const un_ty = cg.typeOf(ty_op.operand); | 6558 | const un_ty = cg.typeOf(ty_op.operand); |
| 6874 | 6559 | const tag_ty = un_ty.unionTagTypeRuntime(zcu) orelse return null; | |
| 6875 | const zcu = cg.zcu; | ||
| 6876 | const layout = cg.unionLayout(un_ty); | ||
| 6877 | if (layout.tag_size == 0) return null; | ||
| 6878 | |||
| 6879 | const union_handle = try cg.resolve(ty_op.operand); | 6560 | const union_handle = try cg.resolve(ty_op.operand); |
| 6880 | if (!layout.has_payload) return union_handle; | 6561 | if (un_ty.unionHasAllZeroBitFieldTypes(zcu)) return union_handle; |
| 6881 | 6562 | const tag_member = zcu.typeToUnion(un_ty).?.field_types.len; | |
| 6882 | const tag_ty = un_ty.unionTagTypeRuntime(zcu).?; | 6563 | return try cg.extractField(tag_ty, union_handle, tag_member); |
| 6883 | return try cg.extractField(tag_ty, union_handle, layout.tag_index); | ||
| 6884 | } | 6564 | } |
| 6885 | 6565 | ||
| 6886 | fn unionInit( | 6566 | fn unionInit( |
| ... | @@ -6905,6 +6585,29 @@ fn unionInit( | ... | @@ -6905,6 +6585,29 @@ fn unionInit( |
| 6905 | const payload_ty: Type = .fromInterned(union_ty.field_types.get(ip)[active_field]); | 6585 | const payload_ty: Type = .fromInterned(union_ty.field_types.get(ip)[active_field]); |
| 6906 | 6586 | ||
| 6907 | assert(union_ty.layout != .@"packed"); | 6587 | assert(union_ty.layout != .@"packed"); |
| 6588 | if (union_ty.layout == .auto) { | ||
| 6589 | if (ty.unionHasAllZeroBitFieldTypes(zcu)) { | ||
| 6590 | const runtime_tag_ty = ty.unionTagTypeRuntime(zcu).?; | ||
| 6591 | const tag_val = try pt.enumValueFieldIndex(runtime_tag_ty, active_field); | ||
| 6592 | return try cg.constant(runtime_tag_ty, tag_val, .indirect); | ||
| 6593 | } | ||
| 6594 | const ty_id = try cg.resolveType(ty, .indirect); | ||
| 6595 | const tmp_id = try cg.alloc(ty_id, null); | ||
| 6596 | if (payload) |payload_id| { | ||
| 6597 | const payload_ty_id = try cg.resolveType(payload_ty, .indirect); | ||
| 6598 | const ptr_ty_id = try cg.ptrType(payload_ty_id, .function); | ||
| 6599 | const payload_ptr_id = try cg.accessChain(ptr_ty_id, tmp_id, &.{active_field}); | ||
| 6600 | try cg.store(payload_ty, payload_ptr_id, payload_id, .{}); | ||
| 6601 | } | ||
| 6602 | if (ty.unionTagTypeRuntime(zcu)) |runtime_tag_ty| { | ||
| 6603 | const tag_val = try pt.enumValueFieldIndex(runtime_tag_ty, active_field); | ||
| 6604 | const tag_id = try cg.constant(runtime_tag_ty, tag_val, .indirect); | ||
| 6605 | const tag_ptr_ty_id = try cg.ptrType(try cg.resolveType(runtime_tag_ty, .indirect), .function); | ||
| 6606 | const tag_ptr_id = try cg.accessChain(tag_ptr_ty_id, tmp_id, &.{union_ty.field_types.len}); | ||
| 6607 | try cg.store(runtime_tag_ty, tag_ptr_id, tag_id, .{}); | ||
| 6608 | } | ||
| 6609 | return try cg.load(ty, tmp_id, .{}); | ||
| 6610 | } | ||
| 6908 | 6611 | ||
| 6909 | const tag_int = if (layout.tag_size != 0) blk: { | 6612 | const tag_int = if (layout.tag_size != 0) blk: { |
| 6910 | const tag_val = try pt.enumValueFieldIndex(tag_ty, active_field); | 6613 | const tag_val = try pt.enumValueFieldIndex(tag_ty, active_field); |
| ... | @@ -7033,7 +6736,8 @@ fn airAggFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { | ... | @@ -7033,7 +6736,8 @@ fn airAggFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 7033 | if (field_ty.isInt(zcu)) return result_id; | 6736 | if (field_ty.isInt(zcu)) return result_id; |
| 7034 | return try cg.bitCast(field_ty, int_ty, result_id); | 6737 | return try cg.bitCast(field_ty, int_ty, result_id); |
| 7035 | }, | 6738 | }, |
| 7036 | else => { | 6739 | .auto => return try cg.extractField(field_ty, object_id, field_index), |
| 6740 | .@"extern" => { | ||
| 7037 | // Store, ptr-elem-ptr, pointer-cast, load | 6741 | // Store, ptr-elem-ptr, pointer-cast, load |
| 7038 | const layout = cg.unionLayout(object_ty); | 6742 | const layout = cg.unionLayout(object_ty); |
| 7039 | assert(layout.has_payload); | 6743 | assert(layout.has_payload); |
| ... | @@ -7163,7 +6867,11 @@ fn structFieldPtr( | ... | @@ -7163,7 +6867,11 @@ fn structFieldPtr( |
| 7163 | }, | 6867 | }, |
| 7164 | .@"union" => switch (object_ty.containerLayout(zcu)) { | 6868 | .@"union" => switch (object_ty.containerLayout(zcu)) { |
| 7165 | .@"packed" => return cg.todo("implement field access for packed unions", .{}), | 6869 | .@"packed" => return cg.todo("implement field access for packed unions", .{}), |
| 7166 | .auto, .@"extern" => { | 6870 | .auto => { |
| 6871 | if (!result_ptr_ty.childType(zcu).hasRuntimeBits(zcu)) return try cg.constUndef(result_ty_id); | ||
| 6872 | return try cg.accessChain(result_ty_id, object_ptr, &.{field_index}); | ||
| 6873 | }, | ||
| 6874 | .@"extern" => { | ||
| 7167 | const layout = cg.unionLayout(object_ty); | 6875 | const layout = cg.unionLayout(object_ty); |
| 7168 | if (!layout.has_payload) { | 6876 | if (!layout.has_payload) { |
| 7169 | // Asked to get a pointer to a zero-sized field. Just lower this | 6877 | // Asked to get a pointer to a zero-sized field. Just lower this |
| ... | @@ -7272,7 +6980,7 @@ fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) !?Id { | ... | @@ -7272,7 +6980,7 @@ fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) !?Id { |
| 7272 | 6980 | ||
| 7273 | fn airArg(cg: *CodeGen) Id { | 6981 | fn airArg(cg: *CodeGen) Id { |
| 7274 | defer cg.next_arg_index += 1; | 6982 | defer cg.next_arg_index += 1; |
| 7275 | return cg.args.items[cg.next_arg_index]; | 6983 | return cg.args.items(.id)[cg.next_arg_index]; |
| 7276 | } | 6984 | } |
| 7277 | 6985 | ||
| 7278 | /// Given a slice of incoming block connections, returns the block-id of the next | 6986 | /// Given a slice of incoming block connections, returns the block-id of the next |
| ... | @@ -8669,7 +8377,7 @@ fn airDbgStmt(cg: *CodeGen, inst: Air.Inst.Index) !void { | ... | @@ -8669,7 +8377,7 @@ fn airDbgStmt(cg: *CodeGen, inst: Air.Inst.Index) !void { |
| 8669 | if (zcu.comp.config.root_strip) return; | 8377 | if (zcu.comp.config.root_strip) return; |
| 8670 | 8378 | ||
| 8671 | const path_id = cg.allocId(); | 8379 | const path_id = cg.allocId(); |
| 8672 | try cg.sections.debug_strings.emit(cg.gpa, .OpString, .{ | 8380 | try cg.module.emit(cg.gpa, .OpString, .{ |
| 8673 | .id_result = path_id, | 8381 | .id_result = path_id, |
| 8674 | .string = path, | 8382 | .string = path, |
| 8675 | }); | 8383 | }); |
| ... | @@ -8942,18 +8650,15 @@ fn builtin3D( | ... | @@ -8942,18 +8650,15 @@ fn builtin3D( |
| 8942 | const ptr_ty_id = try cg.ptrType(vec_ty_id, .input); | 8650 | const ptr_ty_id = try cg.ptrType(vec_ty_id, .input); |
| 8943 | const builtins_gop = try cg.builtins.getOrPut(gpa, .{ built_in, .input }); | 8651 | const builtins_gop = try cg.builtins.getOrPut(gpa, .{ built_in, .input }); |
| 8944 | if (!builtins_gop.found_existing) { | 8652 | if (!builtins_gop.found_existing) { |
| 8945 | builtins_gop.value_ptr.* = try cg.allocDecl(.global); | 8653 | builtins_gop.value_ptr.* = cg.allocId(); |
| 8946 | const decl = cg.declPtr(builtins_gop.value_ptr.*); | 8654 | try cg.module.emit(gpa, .OpVariable, .{ |
| 8947 | try cg.sections.globals.emit(gpa, .OpVariable, .{ | ||
| 8948 | .id_result_type = ptr_ty_id, | 8655 | .id_result_type = ptr_ty_id, |
| 8949 | .id_result = decl.result_id, | 8656 | .id_result = builtins_gop.value_ptr.*, |
| 8950 | .storage_class = .input, | 8657 | .storage_class = .input, |
| 8951 | }); | 8658 | }); |
| 8952 | try cg.decorate(decl.result_id, .{ .built_in = .{ .built_in = built_in } }); | 8659 | try cg.decorate(builtins_gop.value_ptr.*, .{ .built_in = .{ .built_in = built_in } }); |
| 8953 | } | 8660 | } |
| 8954 | const spv_decl_index = builtins_gop.value_ptr.*; | 8661 | const ptr_id = builtins_gop.value_ptr.*; |
| 8955 | try cg.decl_deps.append(gpa, spv_decl_index); | ||
| 8956 | const ptr_id = cg.declPtr(spv_decl_index).result_id; | ||
| 8957 | const vec_id = cg.allocId(); | 8662 | const vec_id = cg.allocId(); |
| 8958 | try cg.body.emit(gpa, .OpLoad, .{ | 8663 | try cg.body.emit(gpa, .OpLoad, .{ |
| 8959 | .id_result_type = vec_ty_id, | 8664 | .id_result_type = vec_ty_id, |
src/codegen/spirv/Mir.zig+11-43| ... | @@ -1,71 +1,39 @@ | ... | @@ -1,71 +1,39 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const Allocator = std.mem.Allocator; | 2 | const Allocator = std.mem.Allocator; |
| 3 | |||
| 4 | const spec = @import("spec.zig"); | 3 | const spec = @import("spec.zig"); |
| 5 | const Word = spec.Word; | 4 | const Word = spec.Word; |
| 6 | const Id = spec.Id; | 5 | const Id = spec.Id; |
| 7 | 6 | const Section = @import("Section.zig"); | |
| 8 | const InternPool = @import("../../InternPool.zig"); | 7 | const InternPool = @import("../../InternPool.zig"); |
| 9 | const CodeGen = @import("CodeGen.zig"); | ||
| 10 | |||
| 11 | const Mir = @This(); | 8 | const Mir = @This(); |
| 12 | 9 | ||
| 13 | id_bound: Word, | 10 | id_bound: Word, |
| 14 | owner_nav: InternPool.Nav.Index, | 11 | module: Section, |
| 15 | kind: CodeGen.Decl.Kind, | ||
| 16 | decl_result_id: Id, | ||
| 17 | extended_instruction_set: []const Word, | ||
| 18 | globals: []const Word, | ||
| 19 | functions: []const Word, | ||
| 20 | annotations: []const Word, | ||
| 21 | debug_names: []const Word, | ||
| 22 | debug_strings: []const Word, | ||
| 23 | execution_modes: []const Word, | ||
| 24 | nav_refs: []const NavRef, | 12 | nav_refs: []const NavRef, |
| 25 | uav_refs: []const UavRef, | 13 | externs: []const Extern, |
| 26 | decl_deps: []const DeclDep, | ||
| 27 | internal_globals: []const Id, | ||
| 28 | entry_points: []const EntryPoint, | 14 | entry_points: []const EntryPoint, |
| 29 | 15 | ||
| 30 | pub const NavRef = struct { | 16 | pub const NavRef = struct { |
| 31 | local_id: Id, | 17 | id: Id, |
| 32 | nav: InternPool.Nav.Index, | 18 | nav: InternPool.Nav.Index, |
| 33 | kind: CodeGen.Decl.Kind, | ||
| 34 | }; | 19 | }; |
| 35 | 20 | ||
| 36 | pub const UavRef = struct { | 21 | pub const Extern = struct { |
| 37 | local_id: Id, | 22 | id: Id, |
| 38 | val: InternPool.Index, | 23 | name: []const u8, |
| 39 | storage_class: spec.StorageClass, | ||
| 40 | kind: CodeGen.Decl.Kind, | ||
| 41 | }; | ||
| 42 | |||
| 43 | pub const DeclDep = struct { | ||
| 44 | kind: CodeGen.Decl.Kind, | ||
| 45 | nav: InternPool.Nav.Index, | ||
| 46 | }; | 24 | }; |
| 47 | 25 | ||
| 48 | pub const EntryPoint = struct { | 26 | pub const EntryPoint = struct { |
| 49 | local_id: Id, | 27 | id: Id, |
| 50 | name: []const u8, | 28 | name: []const u8, |
| 51 | cc: std.builtin.CallingConvention, | 29 | cc: std.builtin.CallingConvention, |
| 52 | }; | 30 | }; |
| 53 | 31 | ||
| 54 | pub fn deinit(mir: *Mir, gpa: Allocator) void { | 32 | pub fn deinit(mir: *Mir, gpa: Allocator) void { |
| 55 | gpa.free(mir.extended_instruction_set); | 33 | mir.module.deinit(gpa); |
| 56 | gpa.free(mir.globals); | ||
| 57 | gpa.free(mir.functions); | ||
| 58 | gpa.free(mir.annotations); | ||
| 59 | gpa.free(mir.debug_names); | ||
| 60 | gpa.free(mir.debug_strings); | ||
| 61 | gpa.free(mir.execution_modes); | ||
| 62 | gpa.free(mir.nav_refs); | 34 | gpa.free(mir.nav_refs); |
| 63 | gpa.free(mir.uav_refs); | 35 | gpa.free(mir.externs); |
| 64 | gpa.free(mir.decl_deps); | 36 | for (mir.entry_points) |ep| gpa.free(ep.name); |
| 65 | gpa.free(mir.internal_globals); | ||
| 66 | for (mir.entry_points) |ep| { | ||
| 67 | gpa.free(ep.name); | ||
| 68 | } | ||
| 69 | gpa.free(mir.entry_points); | 37 | gpa.free(mir.entry_points); |
| 70 | mir.* = undefined; | 38 | mir.* = undefined; |
| 71 | } | 39 | } |
src/codegen/spirv/Section.zig+278-136| ... | @@ -1,91 +1,273 @@ | ... | @@ -1,91 +1,273 @@ |
| 1 | //! Represents a section or subsection of instructions in a SPIR-V binary. Instructions can be append | 1 | //! Represents a section or subsection of instructions in a SPIR-V binary. Instructions can be append |
| 2 | //! to separate sections, which can then later be merged into the final binary. | 2 | //! to separate sections, which can then later be merged into the final binary. |
| 3 | const Section = @This(); | ||
| 4 | |||
| 5 | const std = @import("std"); | 3 | const std = @import("std"); |
| 6 | const Allocator = std.mem.Allocator; | 4 | const Allocator = std.mem.Allocator; |
| 7 | const testing = std.testing; | ||
| 8 | |||
| 9 | const spec = @import("spec.zig"); | 5 | const spec = @import("spec.zig"); |
| 6 | const Opcode = spec.Opcode; | ||
| 7 | const InstructionHeader = spec.InstructionHeader; | ||
| 10 | const Word = spec.Word; | 8 | const Word = spec.Word; |
| 11 | const DoubleWord = @Int(.unsigned, @bitSizeOf(Word) * 2); | 9 | const Id = spec.Id; |
| 12 | const Log2Word = std.math.Log2Int(Word); | 10 | const Section = @This(); |
| 13 | 11 | ||
| 14 | const Opcode = spec.Opcode; | 12 | instructions: std.ArrayList(Word), |
| 13 | ids: std.bit_set.Dynamic, | ||
| 14 | |||
| 15 | pub const empty: Section = .{ .instructions = .empty, .ids = .{} }; | ||
| 16 | |||
| 17 | pub const Error = error{ | ||
| 18 | OutOfMemory, | ||
| 19 | UnknownOpcode, | ||
| 20 | InvalidInstruction, | ||
| 21 | InvalidInstructionLength, | ||
| 22 | NestedFunction, | ||
| 23 | UnterminatedFunction, | ||
| 24 | UndefinedId, | ||
| 25 | }; | ||
| 26 | |||
| 27 | pub fn fromWords(gpa: Allocator, words: []const Word, id_bound: Word) Error!Section { | ||
| 28 | var section: Section = .empty; | ||
| 29 | errdefer section.deinit(gpa); | ||
| 30 | try section.writeWords(gpa, words); | ||
| 31 | const type_widths = try gpa.alloc(u16, id_bound); | ||
| 32 | defer gpa.free(type_widths); | ||
| 33 | @memset(type_widths, 0); | ||
| 34 | var instruction_sets: std.array_hash_map.Auto(Word, spec.InstructionSet) = .empty; | ||
| 35 | defer instruction_sets.deinit(gpa); | ||
| 36 | var results: std.bit_set.Dynamic = try .initEmpty(gpa, id_bound); | ||
| 37 | defer results.deinit(gpa); | ||
| 38 | |||
| 39 | var in_function = false; | ||
| 40 | var offset: usize = 0; | ||
| 41 | while (offset < words.len) { | ||
| 42 | const header: InstructionHeader = @bitCast(words[offset]); | ||
| 43 | const len = header.word_count; | ||
| 44 | if (len == 0 or offset + len > words.len) return error.InvalidInstructionLength; | ||
| 45 | |||
| 46 | const inst = words[offset..][0..len]; | ||
| 47 | const op = std.enums.fromInt(Opcode, @as(u16, @truncate(inst[0]))) orelse | ||
| 48 | return error.UnknownOpcode; | ||
| 49 | const end = offset + len; | ||
| 50 | var index = offset + 1; | ||
| 51 | const list: []const spec.Operand = switch (op) { | ||
| 52 | .OpFunction => list: { | ||
| 53 | if (in_function) return error.NestedFunction; | ||
| 54 | in_function = true; | ||
| 55 | break :list op.operands(); | ||
| 56 | }, | ||
| 57 | .OpFunctionEnd => list: { | ||
| 58 | if (!in_function) return error.InvalidInstruction; | ||
| 59 | in_function = false; | ||
| 60 | break :list op.operands(); | ||
| 61 | }, | ||
| 62 | .OpSpecConstantOp => list: { | ||
| 63 | const prefix = op.operands()[0..2]; | ||
| 64 | index = try section.parseOperands(type_widths, offset, end, prefix, index); | ||
| 65 | if (index >= end) return error.InvalidInstruction; | ||
| 66 | const inner = std.enums.fromInt(Opcode, words[index]) orelse | ||
| 67 | return error.InvalidInstruction; | ||
| 68 | index += 1; | ||
| 69 | break :list inner.operands()[2..]; | ||
| 70 | }, | ||
| 71 | .OpExtInst => list: { | ||
| 72 | if (len < 5) return error.InvalidInstruction; | ||
| 73 | const set = instruction_sets.get(inst[3]) orelse return error.InvalidInstruction; | ||
| 74 | const prefix = op.operands()[0..4]; | ||
| 75 | index = try section.parseOperands(type_widths, offset, end, prefix, index); | ||
| 76 | break :list set.operands(inst[4]) orelse return error.InvalidInstruction; | ||
| 77 | }, | ||
| 78 | else => op.operands(), | ||
| 79 | }; | ||
| 80 | const operands_end = try section.parseOperands(type_widths, offset, end, list, index); | ||
| 81 | if (operands_end != end) return error.InvalidInstruction; | ||
| 82 | if (op.resultIndex()) |result_index| { | ||
| 83 | if (results.isSet(inst[result_index])) return error.InvalidInstruction; | ||
| 84 | results.set(inst[result_index]); | ||
| 85 | } | ||
| 86 | |||
| 87 | switch (op) { | ||
| 88 | .OpTypeInt, .OpTypeFloat => { | ||
| 89 | type_widths[inst[1]] = std.math.cast(u16, inst[2]) orelse | ||
| 90 | return error.InvalidInstruction; | ||
| 91 | }, | ||
| 92 | .OpExtInstImport => { | ||
| 93 | const set_name = std.mem.sliceTo(std.mem.sliceAsBytes(inst[2..]), 0); | ||
| 94 | if (std.meta.stringToEnum(spec.InstructionSet, set_name)) |set| { | ||
| 95 | if (set != .core) try instruction_sets.put(gpa, inst[1], set); | ||
| 96 | } | ||
| 97 | }, | ||
| 98 | else => if (op.resultIndex() == 2) { | ||
| 99 | type_widths[inst[2]] = type_widths[inst[1]]; | ||
| 100 | }, | ||
| 101 | } | ||
| 102 | offset = end; | ||
| 103 | } | ||
| 104 | if (in_function) return error.UnterminatedFunction; | ||
| 105 | var it = section.ids.iterator(.{}); | ||
| 106 | while (it.next()) |index| { | ||
| 107 | if (!results.isSet(section.instructions.items[index])) return error.UndefinedId; | ||
| 108 | } | ||
| 15 | 109 | ||
| 16 | instructions: std.ArrayList(Word) = .empty, | 110 | return section; |
| 111 | } | ||
| 17 | 112 | ||
| 18 | pub fn deinit(section: *Section, allocator: Allocator) void { | 113 | pub fn deinit(section: *Section, gpa: Allocator) void { |
| 19 | section.instructions.deinit(allocator); | 114 | section.instructions.deinit(gpa); |
| 115 | section.ids.deinit(gpa); | ||
| 20 | section.* = undefined; | 116 | section.* = undefined; |
| 21 | } | 117 | } |
| 22 | 118 | ||
| 23 | pub fn reset(section: *Section) void { | 119 | pub fn reset(section: *Section) void { |
| 24 | section.instructions.clearRetainingCapacity(); | 120 | section.instructions.clearRetainingCapacity(); |
| 121 | section.ids.unsetAll(); | ||
| 25 | } | 122 | } |
| 26 | 123 | ||
| 27 | pub fn toWords(section: Section) []Word { | 124 | pub fn append(section: *Section, gpa: Allocator, other: Section) error{OutOfMemory}!void { |
| 28 | return section.instructions.items; | 125 | const offset = section.instructions.items.len; |
| 126 | try section.writeWords(gpa, other.instructions.items); | ||
| 127 | var it = other.ids.iterator(.{}); | ||
| 128 | while (it.next()) |index| section.ids.set(offset + index); | ||
| 29 | } | 129 | } |
| 30 | 130 | ||
| 31 | /// Append the instructions from another section into this section. | 131 | pub fn ensureUnusedCapacity(section: *Section, gpa: Allocator, words: usize) !void { |
| 32 | pub fn append(section: *Section, allocator: Allocator, other_section: Section) !void { | 132 | try section.instructions.ensureUnusedCapacity(gpa, words); |
| 33 | try section.instructions.appendSlice(allocator, other_section.instructions.items); | 133 | try section.ids.resize(gpa, section.instructions.capacity, false); |
| 34 | } | 134 | } |
| 35 | 135 | ||
| 36 | pub fn ensureUnusedCapacity( | 136 | fn parseOperands( |
| 37 | section: *Section, | 137 | section: *Section, |
| 38 | allocator: Allocator, | 138 | type_widths: []const u16, |
| 39 | words: usize, | 139 | inst_start: usize, |
| 40 | ) !void { | 140 | inst_end: usize, |
| 41 | try section.instructions.ensureUnusedCapacity(allocator, words); | 141 | operands: []const spec.Operand, |
| 142 | index: usize, | ||
| 143 | ) Error!usize { | ||
| 144 | const words = section.instructions.items; | ||
| 145 | var next = index; | ||
| 146 | var lists: [16][]const spec.Operand = undefined; | ||
| 147 | lists[0] = operands; | ||
| 148 | var depth: usize = 1; | ||
| 149 | while (depth > 0) { | ||
| 150 | const list = &lists[depth - 1]; | ||
| 151 | if (list.len == 0) { | ||
| 152 | depth -= 1; | ||
| 153 | continue; | ||
| 154 | } | ||
| 155 | const entry = list.*[0]; | ||
| 156 | if (next >= inst_end) { | ||
| 157 | if (entry.quantifier == .required) return error.InvalidInstruction; | ||
| 158 | list.* = &.{}; | ||
| 159 | continue; | ||
| 160 | } | ||
| 161 | if (entry.quantifier != .variadic) list.* = list.*[1..]; | ||
| 162 | const single = [_]spec.OperandKind{entry.kind}; | ||
| 163 | const kinds: []const spec.OperandKind = switch (entry.kind.category()) { | ||
| 164 | .composite => entry.kind.bases(), | ||
| 165 | else => &single, | ||
| 166 | }; | ||
| 167 | for (kinds) |base| { | ||
| 168 | if (next >= inst_end) return error.InvalidInstruction; | ||
| 169 | const is_pair_literal = entry.kind == .pair_literal_integer_id_ref; | ||
| 170 | const kind: spec.OperandKind = if (is_pair_literal and base == .literal_integer) | ||
| 171 | .literal_context_dependent_number | ||
| 172 | else | ||
| 173 | base; | ||
| 174 | switch (kind.category()) { | ||
| 175 | .id => { | ||
| 176 | if (words[next] == 0 or words[next] >= type_widths.len) { | ||
| 177 | return error.InvalidInstruction; | ||
| 178 | } | ||
| 179 | section.ids.set(next); | ||
| 180 | next += 1; | ||
| 181 | }, | ||
| 182 | .literal => switch (kind) { | ||
| 183 | .literal_string => { | ||
| 184 | next = for (words[next..inst_end], next + 1..) |word, after| { | ||
| 185 | const bytes: [4]u8 = @bitCast(word); | ||
| 186 | if (std.mem.findScalar(u8, &bytes, 0) != null) break after; | ||
| 187 | } else return error.InvalidInstruction; | ||
| 188 | }, | ||
| 189 | .literal_context_dependent_number => { | ||
| 190 | next += switch (type_widths[words[inst_start + 1]]) { | ||
| 191 | 1...32 => 1, | ||
| 192 | 33...64 => 2, | ||
| 193 | else => return error.InvalidInstruction, | ||
| 194 | }; | ||
| 195 | if (next > inst_end) return error.InvalidInstruction; | ||
| 196 | }, | ||
| 197 | .literal_spec_constant_op_integer => unreachable, | ||
| 198 | else => next += 1, | ||
| 199 | }, | ||
| 200 | .value_enum => { | ||
| 201 | const enumerant = for (kind.enumerants()) |enumerant| { | ||
| 202 | if (enumerant.value == words[next]) break enumerant; | ||
| 203 | } else return error.InvalidInstruction; | ||
| 204 | next += 1; | ||
| 205 | if (enumerant.parameters.len != 0) { | ||
| 206 | if (depth == lists.len) return error.InvalidInstruction; | ||
| 207 | lists[depth] = enumerant.parameters; | ||
| 208 | depth += 1; | ||
| 209 | } | ||
| 210 | }, | ||
| 211 | .bit_enum => { | ||
| 212 | const mask = words[next]; | ||
| 213 | next += 1; | ||
| 214 | const enumerants = kind.enumerants(); | ||
| 215 | var i = enumerants.len; | ||
| 216 | while (i > 0) { | ||
| 217 | i -= 1; | ||
| 218 | const enumerant = enumerants[i]; | ||
| 219 | if (mask & enumerant.value == 0) continue; | ||
| 220 | if (enumerant.parameters.len == 0) continue; | ||
| 221 | if (depth == lists.len) return error.InvalidInstruction; | ||
| 222 | lists[depth] = enumerant.parameters; | ||
| 223 | depth += 1; | ||
| 224 | } | ||
| 225 | }, | ||
| 226 | .composite => unreachable, | ||
| 227 | } | ||
| 228 | } | ||
| 229 | } | ||
| 230 | return next; | ||
| 42 | } | 231 | } |
| 43 | 232 | ||
| 44 | /// Write an instruction and size, operands are to be inserted manually. | ||
| 45 | pub fn emitRaw( | 233 | pub fn emitRaw( |
| 46 | section: *Section, | 234 | section: *Section, |
| 47 | allocator: Allocator, | 235 | gpa: Allocator, |
| 48 | opcode: Opcode, | 236 | opcode: Opcode, |
| 49 | operand_words: usize, | 237 | operand_words: usize, |
| 50 | ) !void { | 238 | ) !void { |
| 51 | const word_count = 1 + operand_words; | 239 | const word_count = 1 + operand_words; |
| 52 | if (word_count > std.math.maxInt(u16)) return error.OutOfMemory; | 240 | if (word_count > std.math.maxInt(u16)) return error.OutOfMemory; |
| 53 | try section.instructions.ensureUnusedCapacity(allocator, word_count); | 241 | try section.ensureUnusedCapacity(gpa, word_count); |
| 54 | section.writeWord((@as(Word, @intCast(word_count)) << 16) | @backingInt(opcode)); | 242 | const header: InstructionHeader = .{ .opcode = opcode, .word_count = @intCast(word_count) }; |
| 55 | } | 243 | section.writeWord(@bitCast(header)); |
| 56 | |||
| 57 | /// Write an entire instruction, including all operands | ||
| 58 | pub fn emitRawInstruction( | ||
| 59 | section: *Section, | ||
| 60 | allocator: Allocator, | ||
| 61 | opcode: Opcode, | ||
| 62 | operands: []const Word, | ||
| 63 | ) !void { | ||
| 64 | try section.emitRaw(allocator, opcode, operands.len); | ||
| 65 | section.writeWords(operands); | ||
| 66 | } | 244 | } |
| 67 | 245 | ||
| 68 | pub fn emitAssumeCapacity( | 246 | pub fn emitFormatted( |
| 69 | section: *Section, | 247 | section: *Section, |
| 70 | comptime opcode: spec.Opcode, | 248 | gpa: Allocator, |
| 71 | operands: opcode.Operands(), | 249 | comptime opcode: Opcode, |
| 250 | ids: []const Id, | ||
| 251 | comptime fmt: []const u8, | ||
| 252 | args: anytype, | ||
| 72 | ) !void { | 253 | ) !void { |
| 73 | const word_count = instructionSize(opcode, operands); | 254 | const len = std.fmt.count(fmt, args); |
| 74 | if (word_count > std.math.maxInt(u16)) return error.OutOfMemory; | 255 | const string_words = len / @sizeOf(Word) + 1; |
| 75 | section.writeWord((@as(Word, @intCast(word_count)) << 16) | @backingInt(opcode)); | 256 | try section.emitRaw(gpa, opcode, ids.len + string_words); |
| 76 | section.writeOperands(opcode.Operands(), operands); | 257 | for (ids) |id| section.writeId(id); |
| 258 | const words = section.instructions.addManyAsSliceAssumeCapacity(string_words); | ||
| 259 | @memset(words, 0); | ||
| 260 | _ = std.fmt.bufPrint(std.mem.sliceAsBytes(words), fmt, args) catch unreachable; | ||
| 261 | for (words) |*word| word.* = std.mem.littleToNative(Word, word.*); | ||
| 77 | } | 262 | } |
| 78 | 263 | ||
| 79 | pub fn emit( | 264 | pub fn emit( |
| 80 | section: *Section, | 265 | section: *Section, |
| 81 | allocator: Allocator, | 266 | gpa: Allocator, |
| 82 | comptime opcode: spec.Opcode, | 267 | comptime opcode: spec.Opcode, |
| 83 | operands: opcode.Operands(), | 268 | operands: opcode.Operands(), |
| 84 | ) !void { | 269 | ) !void { |
| 85 | const word_count = instructionSize(opcode, operands); | 270 | try section.emitRaw(gpa, opcode, opcode.instructionSize(operands) - 1); |
| 86 | if (word_count > std.math.maxInt(u16)) return error.OutOfMemory; | ||
| 87 | try section.instructions.ensureUnusedCapacity(allocator, word_count); | ||
| 88 | section.writeWord((@as(Word, @intCast(word_count)) << 16) | @backingInt(opcode)); | ||
| 89 | section.writeOperands(opcode.Operands(), operands); | 271 | section.writeOperands(opcode.Operands(), operands); |
| 90 | } | 272 | } |
| 91 | 273 | ||
| ... | @@ -93,18 +275,26 @@ pub fn writeWord(section: *Section, word: Word) void { | ... | @@ -93,18 +275,26 @@ pub fn writeWord(section: *Section, word: Word) void { |
| 93 | section.instructions.appendAssumeCapacity(word); | 275 | section.instructions.appendAssumeCapacity(word); |
| 94 | } | 276 | } |
| 95 | 277 | ||
| 96 | pub fn writeWords(section: *Section, words: []const Word) void { | 278 | pub fn writeWords(section: *Section, gpa: Allocator, words: []const Word) error{OutOfMemory}!void { |
| 279 | try section.ensureUnusedCapacity(gpa, words.len); | ||
| 97 | section.instructions.appendSliceAssumeCapacity(words); | 280 | section.instructions.appendSliceAssumeCapacity(words); |
| 98 | } | 281 | } |
| 99 | 282 | ||
| 283 | pub const DoubleWord = @Int(.unsigned, @bitSizeOf(Word) * 2); | ||
| 284 | |||
| 100 | pub fn writeDoubleWord(section: *Section, dword: DoubleWord) void { | 285 | pub fn writeDoubleWord(section: *Section, dword: DoubleWord) void { |
| 101 | section.writeWords(&.{ | 286 | section.instructions.appendSliceAssumeCapacity(&.{ |
| 102 | @truncate(dword), | 287 | @truncate(dword), |
| 103 | @truncate(dword >> @bitSizeOf(Word)), | 288 | @truncate(dword >> @bitSizeOf(Word)), |
| 104 | }); | 289 | }); |
| 105 | } | 290 | } |
| 106 | 291 | ||
| 107 | fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) void { | 292 | pub fn writeId(section: *Section, id: Id) void { |
| 293 | section.ids.set(section.instructions.items.len); | ||
| 294 | section.instructions.appendAssumeCapacity(@backingInt(id)); | ||
| 295 | } | ||
| 296 | |||
| 297 | pub fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) void { | ||
| 108 | const info = switch (@typeInfo(Operands)) { | 298 | const info = switch (@typeInfo(Operands)) { |
| 109 | .@"struct" => |info| info, | 299 | .@"struct" => |info| info, |
| 110 | .void => return, | 300 | .void => return, |
| ... | @@ -118,19 +308,28 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) | ... | @@ -118,19 +308,28 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) |
| 118 | pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void { | 308 | pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void { |
| 119 | switch (Operand) { | 309 | switch (Operand) { |
| 120 | spec.LiteralSpecConstantOpInteger => unreachable, | 310 | spec.LiteralSpecConstantOpInteger => unreachable, |
| 121 | spec.Id => section.writeWord(@backingInt(operand)), | 311 | spec.Id => section.writeId(operand), |
| 122 | spec.LiteralInteger => section.writeWord(operand), | 312 | spec.LiteralInteger => section.writeWord(operand), |
| 123 | spec.LiteralString => section.writeString(operand), | 313 | spec.LiteralString => section.writeString(operand), |
| 124 | spec.LiteralContextDependentNumber => section.writeContextDependentNumber(operand), | 314 | spec.LiteralContextDependentNumber => section.writeNumber(operand), |
| 125 | spec.LiteralExtInstInteger => section.writeWord(operand.inst), | 315 | spec.LiteralExtInstInteger => section.writeWord(operand.inst), |
| 126 | spec.PairLiteralIntegerIdRef => section.writeWords(&.{ operand.value, @fromBackingInt(@intCast(operand.label)) }), | 316 | spec.PairLiteralIntegerIdRef => { |
| 127 | spec.PairIdRefLiteralInteger => section.writeWords(&.{ @backingInt(operand.target), operand.member }), | 317 | section.writeWord(operand.value); |
| 128 | spec.PairIdRefIdRef => section.writeWords(&.{ @backingInt(operand[0]), @backingInt(operand[1]) }), | 318 | section.writeId(operand.label); |
| 319 | }, | ||
| 320 | spec.PairIdRefLiteralInteger => { | ||
| 321 | section.writeId(operand.target); | ||
| 322 | section.writeWord(operand.member); | ||
| 323 | }, | ||
| 324 | spec.PairIdRefIdRef => { | ||
| 325 | section.writeId(operand[0]); | ||
| 326 | section.writeId(operand[1]); | ||
| 327 | }, | ||
| 129 | else => switch (@typeInfo(Operand)) { | 328 | else => switch (@typeInfo(Operand)) { |
| 130 | .@"enum" => section.writeWord(@backingInt(operand)), | 329 | .@"enum" => section.writeWord(@backingInt(operand)), |
| 131 | .optional => |info| if (operand) |child| section.writeOperand(info.child, child), | 330 | .optional => |info| if (operand) |child| section.writeOperand(info.child, child), |
| 132 | .pointer => |info| { | 331 | .pointer => |info| { |
| 133 | std.debug.assert(info.size == .slice); // Should be no other pointer types in the spec. | 332 | std.debug.assert(info.size == .slice); |
| 134 | for (operand) |item| { | 333 | for (operand) |item| { |
| 135 | section.writeOperand(info.child, item); | 334 | section.writeOperand(info.child, item); |
| 136 | } | 335 | } |
| ... | @@ -148,20 +347,21 @@ pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) | ... | @@ -148,20 +347,21 @@ pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) |
| 148 | } | 347 | } |
| 149 | } | 348 | } |
| 150 | 349 | ||
| 151 | fn writeString(section: *Section, str: []const u8) void { | 350 | pub fn writeString(section: *Section, str: []const u8) void { |
| 152 | const zero_terminated_len = str.len + 1; | 351 | const zero_terminated_len = str.len + 1; |
| 153 | var i: usize = 0; | 352 | var i: usize = 0; |
| 154 | while (i < zero_terminated_len) : (i += @sizeOf(Word)) { | 353 | while (i < zero_terminated_len) : (i += @sizeOf(Word)) { |
| 155 | var word: Word = 0; | 354 | var word: Word = 0; |
| 156 | var j: usize = 0; | 355 | var j: usize = 0; |
| 157 | while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) { | 356 | while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) { |
| 158 | word |= @as(Word, str[i + j]) << @as(Log2Word, @intCast(j * @bitSizeOf(u8))); | 357 | const shift: std.math.Log2Int(Word) = @intCast(j * @bitSizeOf(u8)); |
| 358 | word |= @as(Word, str[i + j]) << shift; | ||
| 159 | } | 359 | } |
| 160 | section.instructions.appendAssumeCapacity(word); | 360 | section.instructions.appendAssumeCapacity(word); |
| 161 | } | 361 | } |
| 162 | } | 362 | } |
| 163 | 363 | ||
| 164 | fn writeContextDependentNumber(section: *Section, operand: spec.LiteralContextDependentNumber) void { | 364 | pub fn writeNumber(section: *Section, operand: spec.LiteralContextDependentNumber) void { |
| 165 | switch (operand) { | 365 | switch (operand) { |
| 166 | .int32 => |int| section.writeWord(@bitCast(int)), | 366 | .int32 => |int| section.writeWord(@bitCast(int)), |
| 167 | .uint32 => |int| section.writeWord(@bitCast(int)), | 367 | .uint32 => |int| section.writeWord(@bitCast(int)), |
| ... | @@ -172,17 +372,23 @@ fn writeContextDependentNumber(section: *Section, operand: spec.LiteralContextDe | ... | @@ -172,17 +372,23 @@ fn writeContextDependentNumber(section: *Section, operand: spec.LiteralContextDe |
| 172 | } | 372 | } |
| 173 | } | 373 | } |
| 174 | 374 | ||
| 175 | fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand) void { | 375 | pub fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand) void { |
| 176 | var mask: Word = 0; | 376 | var mask: Word = 0; |
| 177 | const info = @typeInfo(Operand).@"struct"; | 377 | const info = @typeInfo(Operand).@"struct"; |
| 178 | inline for (info.field_names, info.field_types, 0..) |field_name, field_type, bit| { | 378 | comptime var bit: usize = 0; |
| 379 | inline for (info.field_names, info.field_types) |field_name, field_type| { | ||
| 179 | switch (@typeInfo(field_type)) { | 380 | switch (@typeInfo(field_type)) { |
| 180 | .optional => if (@field(operand, field_name) != null) { | 381 | .optional => { |
| 181 | mask |= 1 << @as(u5, @intCast(bit)); | 382 | if (@field(operand, field_name) != null) { |
| 383 | mask |= @as(Word, 1) << @intCast(bit); | ||
| 384 | } | ||
| 385 | bit += 1; | ||
| 182 | }, | 386 | }, |
| 183 | .bool => if (@field(operand, field_name)) { | 387 | .bool => { |
| 184 | mask |= 1 << @as(u5, @intCast(bit)); | 388 | if (@field(operand, field_name)) mask |= @as(Word, 1) << @intCast(bit); |
| 389 | bit += 1; | ||
| 185 | }, | 390 | }, |
| 391 | .int => bit += @bitSizeOf(field_type), | ||
| 186 | else => unreachable, | 392 | else => unreachable, |
| 187 | } | 393 | } |
| 188 | } | 394 | } |
| ... | @@ -191,86 +397,22 @@ fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand | ... | @@ -191,86 +397,22 @@ fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand |
| 191 | 397 | ||
| 192 | inline for (info.field_names, info.field_types) |field_name, field_type| { | 398 | inline for (info.field_names, info.field_types) |field_name, field_type| { |
| 193 | switch (@typeInfo(field_type)) { | 399 | switch (@typeInfo(field_type)) { |
| 194 | .optional => |opt_info| if (@field(operand, field_name)) |child| { | 400 | .optional => |opt_info| { |
| 195 | section.writeOperands(opt_info.child, child); | 401 | if (@field(operand, field_name)) |child| { |
| 402 | section.writeOperands(opt_info.child, child); | ||
| 403 | } | ||
| 196 | }, | 404 | }, |
| 197 | .bool => {}, | 405 | .bool, .int => {}, |
| 198 | else => unreachable, | 406 | else => unreachable, |
| 199 | } | 407 | } |
| 200 | } | 408 | } |
| 201 | } | 409 | } |
| 202 | 410 | ||
| 203 | fn writeExtendedUnion(section: *Section, comptime Operand: type, operand: Operand) void { | 411 | pub fn writeExtendedUnion(section: *Section, comptime Operand: type, operand: Operand) void { |
| 204 | return switch (operand) { | 412 | return switch (operand) { |
| 205 | inline else => |op, tag| { | 413 | inline else => |op, tag| { |
| 206 | section.writeWord(@backingInt(tag)); | 414 | section.writeWord(@backingInt(tag)); |
| 207 | section.writeOperands( | 415 | section.writeOperands(@FieldType(Operand, @tagName(tag)), op); |
| 208 | @FieldType(Operand, @tagName(tag)), | ||
| 209 | op, | ||
| 210 | ); | ||
| 211 | }, | ||
| 212 | }; | ||
| 213 | } | ||
| 214 | |||
| 215 | fn instructionSize(comptime opcode: spec.Opcode, operands: opcode.Operands()) usize { | ||
| 216 | return operandsSize(opcode.Operands(), operands) + 1; | ||
| 217 | } | ||
| 218 | |||
| 219 | fn operandsSize(comptime Operands: type, operands: Operands) usize { | ||
| 220 | const info = switch (@typeInfo(Operands)) { | ||
| 221 | .@"struct" => |info| info, | ||
| 222 | .void => return 0, | ||
| 223 | else => unreachable, | ||
| 224 | }; | ||
| 225 | |||
| 226 | var total: usize = 0; | ||
| 227 | inline for (info.field_names, info.field_types) |field_name, field_type| { | ||
| 228 | total += operandSize(field_type, @field(operands, field_name)); | ||
| 229 | } | ||
| 230 | |||
| 231 | return total; | ||
| 232 | } | ||
| 233 | |||
| 234 | fn operandSize(comptime Operand: type, operand: Operand) usize { | ||
| 235 | return switch (Operand) { | ||
| 236 | spec.LiteralSpecConstantOpInteger => unreachable, | ||
| 237 | spec.Id, spec.LiteralInteger, spec.LiteralExtInstInteger => 1, | ||
| 238 | spec.LiteralString => @divCeil(operand.len + 1, @sizeOf(Word)), | ||
| 239 | spec.LiteralContextDependentNumber => switch (operand) { | ||
| 240 | .int32, .uint32, .float32 => 1, | ||
| 241 | .int64, .uint64, .float64 => 2, | ||
| 242 | }, | ||
| 243 | spec.PairLiteralIntegerIdRef, spec.PairIdRefLiteralInteger, spec.PairIdRefIdRef => 2, | ||
| 244 | else => switch (@typeInfo(Operand)) { | ||
| 245 | .@"enum" => 1, | ||
| 246 | .optional => |info| if (operand) |child| operandSize(info.child, child) else 0, | ||
| 247 | .pointer => |info| blk: { | ||
| 248 | std.debug.assert(info.size == .slice); // Should be no other pointer types in the spec. | ||
| 249 | var total: usize = 0; | ||
| 250 | for (operand) |item| { | ||
| 251 | total += operandSize(info.child, item); | ||
| 252 | } | ||
| 253 | break :blk total; | ||
| 254 | }, | ||
| 255 | .@"struct" => |struct_info| { | ||
| 256 | if (struct_info.layout == .@"packed") return 1; | ||
| 257 | |||
| 258 | var total: usize = 0; | ||
| 259 | inline for (struct_info.field_names, struct_info.field_types) |field_name, field_type| { | ||
| 260 | switch (@typeInfo(field_type)) { | ||
| 261 | .optional => |info| if (@field(operand, field_name)) |child| { | ||
| 262 | total += operandsSize(info.child, child); | ||
| 263 | }, | ||
| 264 | .bool => {}, | ||
| 265 | else => unreachable, | ||
| 266 | } | ||
| 267 | } | ||
| 268 | return total + 1; // Add one for the mask itself. | ||
| 269 | }, | ||
| 270 | .@"union" => switch (operand) { | ||
| 271 | inline else => |op, tag| operandsSize(@FieldType(Operand, @tagName(tag)), op) + 1, | ||
| 272 | }, | ||
| 273 | else => unreachable, | ||
| 274 | }, | 416 | }, |
| 275 | }; | 417 | }; |
| 276 | } | 418 | } |
src/link.zig+2-2| ... | @@ -1319,7 +1319,7 @@ pub const File = struct { | ... | @@ -1319,7 +1319,7 @@ pub const File = struct { |
| 1319 | .macho => MachO, | 1319 | .macho => MachO, |
| 1320 | .c => C, | 1320 | .c => C, |
| 1321 | .wasm => Wasm, | 1321 | .wasm => Wasm, |
| 1322 | .spirv => SpirV, | 1322 | .spirv => Spirv, |
| 1323 | .lld => Lld, | 1323 | .lld => Lld, |
| 1324 | .plan9 => comptime unreachable, | 1324 | .plan9 => comptime unreachable, |
| 1325 | .spork8 => Spork8, | 1325 | .spork8 => Spork8, |
| ... | @@ -1423,7 +1423,7 @@ pub const File = struct { | ... | @@ -1423,7 +1423,7 @@ pub const File = struct { |
| 1423 | pub const Elf = @import("link/Elf.zig"); | 1423 | pub const Elf = @import("link/Elf.zig"); |
| 1424 | pub const Elf2 = @import("link/Elf2.zig"); | 1424 | pub const Elf2 = @import("link/Elf2.zig"); |
| 1425 | pub const MachO = @import("link/MachO.zig"); | 1425 | pub const MachO = @import("link/MachO.zig"); |
| 1426 | pub const SpirV = @import("link/SpirV.zig"); | 1426 | pub const Spirv = @import("link/Spirv.zig"); |
| 1427 | pub const Wasm = @import("link/Wasm.zig"); | 1427 | pub const Wasm = @import("link/Wasm.zig"); |
| 1428 | pub const Dwarf = @import("link/Dwarf.zig"); | 1428 | pub const Dwarf = @import("link/Dwarf.zig"); |
| 1429 | pub const Dwarf2 = @import("link/Dwarf2.zig"); | 1429 | pub const Dwarf2 = @import("link/Dwarf2.zig"); |
src/link/SpirV.zig deleted-1216| ... | @@ -1,1216 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Allocator = std.mem.Allocator; | ||
| 3 | const Path = std.Build.Cache.Path; | ||
| 4 | const assert = std.debug.assert; | ||
| 5 | const log = std.log.scoped(.link); | ||
| 6 | const zig_version = @import("builtin").zig_version; | ||
| 7 | const Zcu = @import("../Zcu.zig"); | ||
| 8 | const InternPool = @import("../InternPool.zig"); | ||
| 9 | const Compilation = @import("../Compilation.zig"); | ||
| 10 | const link = @import("../link.zig"); | ||
| 11 | const Air = @import("../Air.zig"); | ||
| 12 | const Type = @import("../Type.zig"); | ||
| 13 | const codegen = @import("../codegen.zig"); | ||
| 14 | const CodeGen = @import("../codegen/spirv/CodeGen.zig"); | ||
| 15 | const BinaryModule = @import("SpirV/BinaryModule.zig"); | ||
| 16 | const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig"); | ||
| 17 | const dedup_types = @import("SpirV/dedup_types.zig"); | ||
| 18 | const prune_unused = @import("SpirV/prune_unused.zig"); | ||
| 19 | const spec = @import("../codegen/spirv/spec.zig"); | ||
| 20 | const Section = @import("../codegen/spirv/Section.zig"); | ||
| 21 | const Id = spec.Id; | ||
| 22 | const Word = spec.Word; | ||
| 23 | const Mir = @import("../codegen/spirv/Mir.zig"); | ||
| 24 | |||
| 25 | const Linker = @This(); | ||
| 26 | |||
| 27 | base: link.File, | ||
| 28 | fragments: std.array_hash_map.Auto(InternPool.Nav.Index, Mir) = .empty, | ||
| 29 | pending_navs: std.ArrayList(InternPool.Nav.Index) = .empty, | ||
| 30 | entry_points: std.ArrayList(EntryPointDecl) = .empty, | ||
| 31 | external_objects: std.ArrayList(ExternalObject) = .empty, | ||
| 32 | |||
| 33 | const EntryPointDecl = struct { | ||
| 34 | nav: InternPool.Nav.Index, | ||
| 35 | name: []const u8, | ||
| 36 | cc: std.builtin.CallingConvention, | ||
| 37 | }; | ||
| 38 | |||
| 39 | const ExternalObject = struct { | ||
| 40 | instructions: []const Word, | ||
| 41 | id_bound: u32, | ||
| 42 | }; | ||
| 43 | |||
| 44 | pub fn createEmpty( | ||
| 45 | arena: Allocator, | ||
| 46 | comp: *Compilation, | ||
| 47 | emit: Path, | ||
| 48 | options: link.File.OpenOptions, | ||
| 49 | ) !*Linker { | ||
| 50 | const io = comp.io; | ||
| 51 | const target = &comp.root_mod.resolved_target.result; | ||
| 52 | |||
| 53 | assert(!comp.config.use_lld); // Caught by Compilation.Config.resolve | ||
| 54 | assert(!comp.config.use_llvm); // Caught by Compilation.Config.resolve | ||
| 55 | assert(target.ofmt == .spirv); // Caught by Compilation.Config.resolve | ||
| 56 | switch (target.cpu.arch) { | ||
| 57 | .spirv32, .spirv64 => {}, | ||
| 58 | else => unreachable, // Caught by Compilation.Config.resolve. | ||
| 59 | } | ||
| 60 | switch (target.os.tag) { | ||
| 61 | .opencl, .opengl, .vulkan => {}, | ||
| 62 | else => unreachable, // Caught by Compilation.Config.resolve. | ||
| 63 | } | ||
| 64 | |||
| 65 | const linker = try arena.create(Linker); | ||
| 66 | linker.* = .{ | ||
| 67 | .base = .{ | ||
| 68 | .tag = .spirv, | ||
| 69 | .comp = comp, | ||
| 70 | .emit = emit, | ||
| 71 | .gc_sections = options.gc_sections orelse false, | ||
| 72 | .print_gc_sections = options.print_gc_sections, | ||
| 73 | .stack_size = options.stack_size orelse 0, | ||
| 74 | .allow_shlib_undefined = options.allow_shlib_undefined orelse false, | ||
| 75 | .file = null, | ||
| 76 | .build_id = options.build_id, | ||
| 77 | }, | ||
| 78 | }; | ||
| 79 | errdefer linker.deinit(); | ||
| 80 | |||
| 81 | linker.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{ | ||
| 82 | .truncate = true, | ||
| 83 | .read = true, | ||
| 84 | }); | ||
| 85 | |||
| 86 | return linker; | ||
| 87 | } | ||
| 88 | |||
| 89 | pub fn open( | ||
| 90 | arena: Allocator, | ||
| 91 | comp: *Compilation, | ||
| 92 | emit: Path, | ||
| 93 | options: link.File.OpenOptions, | ||
| 94 | ) !*Linker { | ||
| 95 | return createEmpty(arena, comp, emit, options); | ||
| 96 | } | ||
| 97 | |||
| 98 | pub fn deinit(linker: *Linker) void { | ||
| 99 | const gpa = linker.base.comp.gpa; | ||
| 100 | for (linker.fragments.values()) |*mir| { | ||
| 101 | mir.deinit(gpa); | ||
| 102 | } | ||
| 103 | linker.fragments.deinit(gpa); | ||
| 104 | linker.pending_navs.deinit(gpa); | ||
| 105 | linker.entry_points.deinit(gpa); | ||
| 106 | for (linker.external_objects.items) |obj| { | ||
| 107 | gpa.free(obj.instructions); | ||
| 108 | } | ||
| 109 | linker.external_objects.deinit(gpa); | ||
| 110 | } | ||
| 111 | |||
| 112 | pub fn loadInput(linker: *Linker, input: link.Input) !void { | ||
| 113 | switch (input) { | ||
| 114 | .object => |obj| { | ||
| 115 | const comp = linker.base.comp; | ||
| 116 | const gpa = comp.gpa; | ||
| 117 | const io = comp.io; | ||
| 118 | const diags = &comp.link_diags; | ||
| 119 | |||
| 120 | const stat = obj.file.stat(io) catch |err| | ||
| 121 | return diags.fail("failed to stat SPIR-V object '{f}': {t}", .{ obj.path, err }); | ||
| 122 | const file_size = std.math.cast(usize, stat.size) orelse | ||
| 123 | return diags.fail("SPIR-V object '{f}' is too large", .{obj.path}); | ||
| 124 | if (file_size < 5 * @sizeOf(Word)) | ||
| 125 | return diags.fail("SPIR-V object '{f}' is too small to contain a valid header", .{obj.path}); | ||
| 126 | if (file_size % @sizeOf(Word) != 0) | ||
| 127 | return diags.fail("SPIR-V object '{f}' size is not a multiple of the word size", .{obj.path}); | ||
| 128 | |||
| 129 | const word_count = file_size / @sizeOf(Word); | ||
| 130 | const all_words = try gpa.alloc(Word, word_count); | ||
| 131 | defer gpa.free(all_words); | ||
| 132 | |||
| 133 | const bytes = std.mem.sliceAsBytes(all_words); | ||
| 134 | const n_read = obj.file.readPositionalAll(io, bytes, 0) catch |err| | ||
| 135 | return diags.fail("failed to read SPIR-V object '{f}': {t}", .{ obj.path, err }); | ||
| 136 | if (n_read != bytes.len) | ||
| 137 | return diags.fail("SPIR-V object '{f}': incomplete read", .{obj.path}); | ||
| 138 | |||
| 139 | const needs_swap = all_words[0] == @byteSwap(spec.magic_number); | ||
| 140 | if (needs_swap) { | ||
| 141 | for (all_words) |*w| w.* = @byteSwap(w.*); | ||
| 142 | } | ||
| 143 | |||
| 144 | if (all_words[0] != spec.magic_number) | ||
| 145 | return diags.fail("SPIR-V object '{f}': invalid magic number", .{obj.path}); | ||
| 146 | |||
| 147 | const id_bound = all_words[3]; | ||
| 148 | const instructions = try gpa.dupe(Word, all_words[5..]); | ||
| 149 | errdefer gpa.free(instructions); | ||
| 150 | |||
| 151 | // OpCapability instructions appear at the top of the module | ||
| 152 | // so we can stop scanning as soon as we hit anything else. | ||
| 153 | var it: BinaryModule.Instruction.Iterator = .init(instructions, 0); | ||
| 154 | const has_linkage = while (it.next()) |inst| switch (inst.opcode) { | ||
| 155 | .OpCapability => { | ||
| 156 | const cap: spec.Capability = @fromBackingInt(@intCast(inst.operands[0])); | ||
| 157 | if (cap == .linkage) break true; | ||
| 158 | }, | ||
| 159 | else => break false, | ||
| 160 | } else false; | ||
| 161 | if (!has_linkage) { | ||
| 162 | return diags.fail("SPIR-V object '{f}' is missing the Linkage capability and cannot be linked", .{obj.path}); | ||
| 163 | } | ||
| 164 | |||
| 165 | try linker.external_objects.append(gpa, .{ | ||
| 166 | .instructions = instructions, | ||
| 167 | .id_bound = id_bound, | ||
| 168 | }); | ||
| 169 | }, | ||
| 170 | else => { | ||
| 171 | const diags = &linker.base.comp.link_diags; | ||
| 172 | return diags.fail("unsupported link input for SPIR-V target", .{}); | ||
| 173 | }, | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | pub fn updateFunc( | ||
| 178 | linker: *Linker, | ||
| 179 | pt: Zcu.PerThread, | ||
| 180 | func_index: InternPool.Index, | ||
| 181 | mir: *codegen.AnyMir, | ||
| 182 | ) !void { | ||
| 183 | const gpa = linker.base.comp.gpa; | ||
| 184 | const nav = pt.zcu.funcInfo(func_index).owner_nav; | ||
| 185 | |||
| 186 | if (linker.fragments.getPtr(nav)) |existing| { | ||
| 187 | existing.deinit(gpa); | ||
| 188 | } | ||
| 189 | |||
| 190 | try linker.fragments.put(gpa, nav, mir.spirv); | ||
| 191 | mir.spirv = .{ | ||
| 192 | .extended_instruction_set = &.{}, | ||
| 193 | .globals = &.{}, | ||
| 194 | .functions = &.{}, | ||
| 195 | .annotations = &.{}, | ||
| 196 | .debug_names = &.{}, | ||
| 197 | .debug_strings = &.{}, | ||
| 198 | .execution_modes = &.{}, | ||
| 199 | .id_bound = 0, | ||
| 200 | .owner_nav = mir.spirv.owner_nav, | ||
| 201 | .kind = mir.spirv.kind, | ||
| 202 | .decl_result_id = .none, | ||
| 203 | .nav_refs = &.{}, | ||
| 204 | .uav_refs = &.{}, | ||
| 205 | .decl_deps = &.{}, | ||
| 206 | .internal_globals = &.{}, | ||
| 207 | .entry_points = &.{}, | ||
| 208 | }; | ||
| 209 | } | ||
| 210 | |||
| 211 | pub fn updateNav(linker: *Linker, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.Error!void { | ||
| 212 | const ip = &pt.zcu.intern_pool; | ||
| 213 | log.debug("deferring nav {f}({d}) to flush", .{ ip.getNav(nav).fqn.fmt(ip), nav }); | ||
| 214 | |||
| 215 | const gpa = linker.base.comp.gpa; | ||
| 216 | linker.pending_navs.append(gpa, nav) catch return error.OutOfMemory; | ||
| 217 | } | ||
| 218 | |||
| 219 | pub fn updateExports( | ||
| 220 | linker: *Linker, | ||
| 221 | pt: Zcu.PerThread, | ||
| 222 | export_indices: []const Zcu.Export.Index, | ||
| 223 | ) link.Error!void { | ||
| 224 | const zcu = pt.zcu; | ||
| 225 | const ip = &zcu.intern_pool; | ||
| 226 | const gpa = linker.base.comp.gpa; | ||
| 227 | for (export_indices) |exp_index| { | ||
| 228 | const exp = exp_index.ptr(zcu); | ||
| 229 | const nav_index = switch (exp.exported) { | ||
| 230 | .nav => |nav| nav, | ||
| 231 | .uav => @panic("TODO: implement Linker linker code for exporting a constant value"), | ||
| 232 | }; | ||
| 233 | const nav_ty = ip.getNav(nav_index).resolved.?.type; | ||
| 234 | if (ip.isFunctionType(nav_ty)) { | ||
| 235 | const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu); | ||
| 236 | try linker.entry_points.append(gpa, .{ | ||
| 237 | .nav = nav_index, | ||
| 238 | .name = exp.opts.name.toSlice(ip), | ||
| 239 | .cc = cc, | ||
| 240 | }); | ||
| 241 | } | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | pub fn flush( | ||
| 246 | linker: *Linker, | ||
| 247 | arena: Allocator, | ||
| 248 | tid: Zcu.PerThread.Id, | ||
| 249 | prog_node: std.Progress.Node, | ||
| 250 | ) link.Error!void { | ||
| 251 | const sub_prog_node = prog_node.start("Flush Module", 0); | ||
| 252 | defer sub_prog_node.end(); | ||
| 253 | |||
| 254 | const comp = linker.base.comp; | ||
| 255 | const diags = &comp.link_diags; | ||
| 256 | const gpa = comp.gpa; | ||
| 257 | const io = comp.io; | ||
| 258 | |||
| 259 | if (comp.zcu) |zcu| { | ||
| 260 | const active = zcu.activate(tid); | ||
| 261 | defer active.deactivate(); | ||
| 262 | const pt = active.pt; | ||
| 263 | for (linker.pending_navs.items) |nav| { | ||
| 264 | if (linker.fragments.contains(nav)) continue; | ||
| 265 | |||
| 266 | const mir = CodeGen.generateNav(pt, nav) catch |err| switch (err) { | ||
| 267 | error.OutOfMemory => return error.OutOfMemory, | ||
| 268 | error.AlreadyReported => continue, | ||
| 269 | error.Canceled => return error.Canceled, | ||
| 270 | }; | ||
| 271 | |||
| 272 | linker.fragments.put(gpa, nav, mir) catch return error.OutOfMemory; | ||
| 273 | } | ||
| 274 | linker.pending_navs.clearRetainingCapacity(); | ||
| 275 | } | ||
| 276 | |||
| 277 | const merged = mergeFragments(linker, gpa, arena) catch |err| switch (err) { | ||
| 278 | error.OutOfMemory => return error.OutOfMemory, | ||
| 279 | }; | ||
| 280 | |||
| 281 | var binary = linkModule(arena, merged.words, merged.id_bound, sub_prog_node) catch |err| switch (err) { | ||
| 282 | error.OutOfMemory => |e| return e, | ||
| 283 | else => |other| { | ||
| 284 | return diags.fail("error while linking: {s}", .{@errorName(other)}); | ||
| 285 | }, | ||
| 286 | }; | ||
| 287 | defer binary.deinit(arena); | ||
| 288 | |||
| 289 | const header = [_]Word{ | ||
| 290 | spec.magic_number, | ||
| 291 | merged.version.toWord(), | ||
| 292 | merged.generator_id, | ||
| 293 | binary.id_bound, | ||
| 294 | 0, | ||
| 295 | }; | ||
| 296 | |||
| 297 | var file_writer = linker.base.file.?.writer(io, &.{}); | ||
| 298 | file_writer.interface.writeSliceEndian(Word, &header, .little) catch |err| switch (err) { | ||
| 299 | error.WriteFailed => return diags.fail("failed to write: {t}", .{file_writer.err.?}), | ||
| 300 | }; | ||
| 301 | file_writer.interface.writeSliceEndian(Word, binary.instructions, .little) catch |err| switch (err) { | ||
| 302 | error.WriteFailed => return diags.fail("failed to write: {t}", .{file_writer.err.?}), | ||
| 303 | }; | ||
| 304 | file_writer.end() catch |err| switch (err) { | ||
| 305 | error.WriteFailed => return diags.fail("failed to write: {t}", .{file_writer.err.?}), | ||
| 306 | else => |e| return diags.fail("failed to write: {t}", .{e}), | ||
| 307 | }; | ||
| 308 | } | ||
| 309 | |||
| 310 | fn linkModule(arena: Allocator, words: []const Word, id_bound: u32, progress: std.Progress.Node) !BinaryModule { | ||
| 311 | var parser = try BinaryModule.Parser.init(arena); | ||
| 312 | defer parser.deinit(); | ||
| 313 | var binary = try parser.initFromWords(words, id_bound); | ||
| 314 | try prune_unused.run(&parser, &binary); | ||
| 315 | try dedup_types.run(&parser, &binary); | ||
| 316 | try lower_invocation_globals.run(&parser, &binary, progress); | ||
| 317 | return binary; | ||
| 318 | } | ||
| 319 | |||
| 320 | fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOfMemory}!MergedModule { | ||
| 321 | const comp = linker.base.comp; | ||
| 322 | const target = &comp.root_mod.resolved_target.result; | ||
| 323 | const maybe_ip: ?*InternPool = if (comp.zcu) |zcu| &zcu.intern_pool else null; | ||
| 324 | const is_obj = comp.config.output_mode == .Obj; | ||
| 325 | |||
| 326 | var next_id: Word = 1; | ||
| 327 | |||
| 328 | var nav_final_ids: std.AutoHashMapUnmanaged(InternPool.Nav.Index, Id) = .empty; | ||
| 329 | defer nav_final_ids.deinit(gpa); | ||
| 330 | |||
| 331 | var uav_final_ids: std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Id) = .empty; | ||
| 332 | defer uav_final_ids.deinit(gpa); | ||
| 333 | |||
| 334 | var frag_infos: std.ArrayList(FragmentInfo) = .empty; | ||
| 335 | defer frag_infos.deinit(gpa); | ||
| 336 | try frag_infos.ensureTotalCapacity(gpa, @intCast(linker.fragments.count())); | ||
| 337 | |||
| 338 | for (linker.fragments.keys(), linker.fragments.values()) |nav, *mir| { | ||
| 339 | const id_offset = next_id - 1; | ||
| 340 | frag_infos.appendAssumeCapacity(.{ .id_offset = id_offset }); | ||
| 341 | if (mir.decl_result_id != .none) { | ||
| 342 | try nav_final_ids.put(gpa, nav, @fromBackingInt(@intCast(@backingInt(mir.decl_result_id) + id_offset))); | ||
| 343 | } | ||
| 344 | next_id += mir.id_bound - 1; | ||
| 345 | } | ||
| 346 | |||
| 347 | for (linker.fragments.values(), frag_infos.items) |*mir, frag_info| { | ||
| 348 | for (mir.nav_refs) |ref| { | ||
| 349 | if (!nav_final_ids.contains(ref.nav)) { | ||
| 350 | try nav_final_ids.put(gpa, ref.nav, @fromBackingInt(@intCast(@backingInt(ref.local_id) + frag_info.id_offset))); | ||
| 351 | } | ||
| 352 | } | ||
| 353 | for (mir.uav_refs) |ref| { | ||
| 354 | const key = .{ ref.val, ref.storage_class }; | ||
| 355 | if (!uav_final_ids.contains(key)) { | ||
| 356 | try uav_final_ids.put(gpa, key, @fromBackingInt(@intCast(@backingInt(ref.local_id) + frag_info.id_offset))); | ||
| 357 | } | ||
| 358 | } | ||
| 359 | } | ||
| 360 | |||
| 361 | // Resolve Zig extern navs against external objects. | ||
| 362 | var ext_id_offsets: std.ArrayList(Word) = .empty; | ||
| 363 | defer ext_id_offsets.deinit(gpa); | ||
| 364 | try ext_id_offsets.ensureTotalCapacity(gpa, linker.external_objects.items.len); | ||
| 365 | |||
| 366 | var unresolved_extern_count: u32 = 0; | ||
| 367 | var resolved_ids: std.array_hash_map.Auto(Id, void) = .empty; | ||
| 368 | defer resolved_ids.deinit(gpa); | ||
| 369 | |||
| 370 | if (maybe_ip) |ip| { | ||
| 371 | var extern_name_map: std.array_hash_map.String(InternPool.Nav.Index) = .empty; | ||
| 372 | defer extern_name_map.deinit(gpa); | ||
| 373 | |||
| 374 | var nav_it = nav_final_ids.iterator(); | ||
| 375 | while (nav_it.next()) |entry| { | ||
| 376 | const nav = ip.getNav(entry.key_ptr.*); | ||
| 377 | if (!nav.resolved.?.is_extern_decl) continue; | ||
| 378 | const name = if (nav.getExtern(ip)) |e| e.name.toSlice(ip) else nav.fqn.toSlice(ip); | ||
| 379 | try extern_name_map.put(gpa, name, entry.key_ptr.*); | ||
| 380 | } | ||
| 381 | |||
| 382 | for (linker.external_objects.items) |ext_obj| { | ||
| 383 | const id_offset = next_id - 1; | ||
| 384 | ext_id_offsets.appendAssumeCapacity(id_offset); | ||
| 385 | |||
| 386 | var it: BinaryModule.Instruction.Iterator = .init(ext_obj.instructions, 0); | ||
| 387 | while (it.next()) |inst| { | ||
| 388 | const ld = LinkageDecoration.parse(inst) orelse continue; | ||
| 389 | if (ld.linkage_type != .@"export") continue; | ||
| 390 | const remapped_id: Id = @fromBackingInt(@intCast(@backingInt(ld.target_id) + id_offset)); | ||
| 391 | |||
| 392 | if (extern_name_map.get(ld.name)) |nav_index| { | ||
| 393 | log.debug("extern resolve: '{s}' -> ext_fn_id={d}", .{ ld.name, @backingInt(remapped_id) }); | ||
| 394 | nav_final_ids.getPtr(nav_index).?.* = remapped_id; | ||
| 395 | _ = extern_name_map.swapRemove(ld.name); | ||
| 396 | try resolved_ids.put(gpa, remapped_id, {}); | ||
| 397 | } | ||
| 398 | } | ||
| 399 | next_id += ext_obj.id_bound - 1; | ||
| 400 | } | ||
| 401 | |||
| 402 | unresolved_extern_count = @intCast(extern_name_map.count()); | ||
| 403 | } else { | ||
| 404 | for (linker.external_objects.items) |ext_obj| { | ||
| 405 | ext_id_offsets.appendAssumeCapacity(next_id - 1); | ||
| 406 | next_id += ext_obj.id_bound - 1; | ||
| 407 | } | ||
| 408 | } | ||
| 409 | |||
| 410 | var parser = BinaryModule.Parser.init(gpa) catch return error.OutOfMemory; | ||
| 411 | defer parser.deinit(); | ||
| 412 | var sections: Sections = .{}; | ||
| 413 | defer sections.deinit(gpa); | ||
| 414 | |||
| 415 | try mergeZigFragments(linker, gpa, &parser, &sections, frag_infos.items, &nav_final_ids, &uav_final_ids, &resolved_ids, maybe_ip); | ||
| 416 | |||
| 417 | var has_linkage = false; | ||
| 418 | try appendExternalObjects(linker, gpa, &parser, ext_id_offsets.items, &sections, &has_linkage, linker.fragments.count() == 0, is_obj, &resolved_ids); | ||
| 419 | |||
| 420 | if (is_obj) { | ||
| 421 | for (linker.entry_points.items) |ep| { | ||
| 422 | if (ep.cc != .spirv_device) continue; | ||
| 423 | const final_id = nav_final_ids.get(ep.nav) orelse continue; | ||
| 424 | try sections.annotations.emit(gpa, .OpDecorate, .{ | ||
| 425 | .target = final_id, | ||
| 426 | .decoration = .{ .linkage_attributes = .{ .name = ep.name, .linkage_type = .@"export" } }, | ||
| 427 | }); | ||
| 428 | has_linkage = true; | ||
| 429 | } | ||
| 430 | if (unresolved_extern_count > 0) has_linkage = true; | ||
| 431 | } | ||
| 432 | |||
| 433 | var capabilities_section = Section{}; | ||
| 434 | defer capabilities_section.deinit(gpa); | ||
| 435 | var extensions_section = Section{}; | ||
| 436 | defer extensions_section.deinit(gpa); | ||
| 437 | var memory_model_section = Section{}; | ||
| 438 | defer memory_model_section.deinit(gpa); | ||
| 439 | |||
| 440 | try emitPreamble( | ||
| 441 | gpa, | ||
| 442 | target, | ||
| 443 | has_linkage, | ||
| 444 | &capabilities_section, | ||
| 445 | &extensions_section, | ||
| 446 | &memory_model_section, | ||
| 447 | ); | ||
| 448 | try emitEntryPoints( | ||
| 449 | linker, | ||
| 450 | gpa, | ||
| 451 | target, | ||
| 452 | &sections.entry_points, | ||
| 453 | &sections.execution_modes, | ||
| 454 | &nav_final_ids, | ||
| 455 | &uav_final_ids, | ||
| 456 | &frag_infos, | ||
| 457 | ); | ||
| 458 | |||
| 459 | const zig_packed_version = (zig_version.major << 12) | (zig_version.minor << 7) | zig_version.patch; | ||
| 460 | if (maybe_ip) |ip| { | ||
| 461 | try emitSourceInfo(gpa, ip, zig_packed_version, &sections.debug_strings); | ||
| 462 | } | ||
| 463 | |||
| 464 | const version: spec.Version = .{ | ||
| 465 | .major = 1, | ||
| 466 | .minor = blk: { | ||
| 467 | if (target.cpu.has(.spirv, .v1_6)) break :blk 6; | ||
| 468 | if (target.cpu.has(.spirv, .v1_5)) break :blk 5; | ||
| 469 | if (target.cpu.has(.spirv, .v1_4)) break :blk 4; | ||
| 470 | if (target.cpu.has(.spirv, .v1_3)) break :blk 3; | ||
| 471 | if (target.cpu.has(.spirv, .v1_2)) break :blk 2; | ||
| 472 | if (target.cpu.has(.spirv, .v1_1)) break :blk 1; | ||
| 473 | break :blk 0; | ||
| 474 | }, | ||
| 475 | }; | ||
| 476 | |||
| 477 | const buffers = &[_][]const Word{ | ||
| 478 | capabilities_section.toWords(), | ||
| 479 | extensions_section.toWords(), | ||
| 480 | sections.ext_inst.toWords(), | ||
| 481 | memory_model_section.toWords(), | ||
| 482 | sections.entry_points.toWords(), | ||
| 483 | sections.execution_modes.toWords(), | ||
| 484 | sections.debug_strings.toWords(), | ||
| 485 | sections.debug_names.toWords(), | ||
| 486 | sections.annotations.toWords(), | ||
| 487 | sections.globals.toWords(), | ||
| 488 | sections.functions.toWords(), | ||
| 489 | }; | ||
| 490 | |||
| 491 | var total_size: usize = 0; | ||
| 492 | for (buffers) |buffer| total_size += buffer.len; | ||
| 493 | const result = try arena.alloc(Word, total_size); | ||
| 494 | |||
| 495 | var offset: usize = 0; | ||
| 496 | for (buffers) |buffer| { | ||
| 497 | @memcpy(result[offset..][0..buffer.len], buffer); | ||
| 498 | offset += buffer.len; | ||
| 499 | } | ||
| 500 | |||
| 501 | return .{ | ||
| 502 | .words = result, | ||
| 503 | .id_bound = next_id, | ||
| 504 | .version = version, | ||
| 505 | .generator_id = (spec.zig_generator_id << 16) | zig_packed_version, | ||
| 506 | }; | ||
| 507 | } | ||
| 508 | |||
| 509 | fn mergeZigFragments( | ||
| 510 | linker: *Linker, | ||
| 511 | gpa: Allocator, | ||
| 512 | parser: *BinaryModule.Parser, | ||
| 513 | sections: *Sections, | ||
| 514 | frag_infos: []const FragmentInfo, | ||
| 515 | nav_final_ids: *const std.AutoHashMapUnmanaged(InternPool.Nav.Index, Id), | ||
| 516 | uav_final_ids: *const std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Id), | ||
| 517 | resolved_ids: *const std.array_hash_map.Auto(Id, void), | ||
| 518 | maybe_ip: ?*InternPool, | ||
| 519 | ) error{OutOfMemory}!void { | ||
| 520 | for (linker.fragments.values(), frag_infos) |*mir, frag_info| { | ||
| 521 | var id_remap: std.AutoHashMapUnmanaged(Id, Id) = .empty; | ||
| 522 | defer id_remap.deinit(gpa); | ||
| 523 | |||
| 524 | var resolved_local_ids: std.array_hash_map.Auto(Id, void) = .empty; | ||
| 525 | defer resolved_local_ids.deinit(gpa); | ||
| 526 | |||
| 527 | for (mir.nav_refs) |ref| { | ||
| 528 | if (nav_final_ids.get(ref.nav)) |final_id| { | ||
| 529 | try id_remap.put(gpa, ref.local_id, final_id); | ||
| 530 | if (maybe_ip) |ip| { | ||
| 531 | const nav = ip.getNav(ref.nav); | ||
| 532 | if (nav.resolved.?.is_extern_decl and resolved_ids.contains(final_id)) { | ||
| 533 | try resolved_local_ids.put(gpa, ref.local_id, {}); | ||
| 534 | } | ||
| 535 | } | ||
| 536 | } | ||
| 537 | } | ||
| 538 | for (mir.uav_refs) |ref| { | ||
| 539 | if (uav_final_ids.get(.{ ref.val, ref.storage_class })) |final_id| { | ||
| 540 | try id_remap.put(gpa, ref.local_id, final_id); | ||
| 541 | } | ||
| 542 | } | ||
| 543 | |||
| 544 | try remapAndAppend(gpa, &sections.ext_inst, mir.extended_instruction_set, frag_info.id_offset, &id_remap, parser); | ||
| 545 | try remapAndAppend(gpa, &sections.globals, mir.globals, frag_info.id_offset, &id_remap, parser); | ||
| 546 | |||
| 547 | try remapFilteredInsts(gpa, &sections.functions, mir.functions, frag_info.id_offset, &id_remap, parser, &resolved_local_ids, .skip_functions); | ||
| 548 | try remapFilteredInsts(gpa, &sections.annotations, mir.annotations, frag_info.id_offset, &id_remap, parser, &resolved_local_ids, .skip_linkage); | ||
| 549 | try remapFilteredInsts(gpa, &sections.debug_names, mir.debug_names, frag_info.id_offset, &id_remap, parser, &resolved_local_ids, .skip_names); | ||
| 550 | try remapAndAppend(gpa, &sections.debug_strings, mir.debug_strings, frag_info.id_offset, &id_remap, parser); | ||
| 551 | try remapAndAppend(gpa, &sections.execution_modes, mir.execution_modes, frag_info.id_offset, &id_remap, parser); | ||
| 552 | |||
| 553 | for (mir.entry_points) |ep| { | ||
| 554 | try linker.entry_points.append(gpa, .{ .nav = mir.owner_nav, .name = ep.name, .cc = ep.cc }); | ||
| 555 | } | ||
| 556 | } | ||
| 557 | } | ||
| 558 | |||
| 559 | const FilterMode = enum { skip_functions, skip_linkage, skip_names }; | ||
| 560 | |||
| 561 | fn remapFilteredInsts( | ||
| 562 | gpa: Allocator, | ||
| 563 | dest: *Section, | ||
| 564 | words: []const Word, | ||
| 565 | id_offset: Word, | ||
| 566 | id_remap: *const std.AutoHashMapUnmanaged(Id, Id), | ||
| 567 | parser: *BinaryModule.Parser, | ||
| 568 | skip_ids: *const std.array_hash_map.Auto(Id, void), | ||
| 569 | mode: FilterMode, | ||
| 570 | ) error{OutOfMemory}!void { | ||
| 571 | if (words.len == 0) return; | ||
| 572 | var it: BinaryModule.Instruction.Iterator = .init(words, 0); | ||
| 573 | var skip_function = false; | ||
| 574 | while (it.next()) |inst| { | ||
| 575 | switch (mode) { | ||
| 576 | .skip_functions => { | ||
| 577 | if (inst.opcode == .OpFunction) { | ||
| 578 | skip_function = skip_ids.contains(@fromBackingInt(@intCast(inst.operands[1]))); | ||
| 579 | } | ||
| 580 | if (skip_function) { | ||
| 581 | if (inst.opcode == .OpFunctionEnd) skip_function = false; | ||
| 582 | continue; | ||
| 583 | } | ||
| 584 | }, | ||
| 585 | .skip_linkage => { | ||
| 586 | if (LinkageDecoration.parse(inst)) |ld| { | ||
| 587 | if (skip_ids.contains(ld.target_id)) continue; | ||
| 588 | } | ||
| 589 | }, | ||
| 590 | .skip_names => { | ||
| 591 | if (inst.opcode == .OpName and inst.operands.len >= 1) { | ||
| 592 | if (skip_ids.contains(@fromBackingInt(@intCast(inst.operands[0])))) continue; | ||
| 593 | } | ||
| 594 | }, | ||
| 595 | } | ||
| 596 | try remapAndAppendInst(gpa, dest, words, inst, id_offset, id_remap, parser); | ||
| 597 | } | ||
| 598 | } | ||
| 599 | |||
| 600 | fn emitPreamble( | ||
| 601 | gpa: Allocator, | ||
| 602 | target: *const std.Target, | ||
| 603 | has_linkage: bool, | ||
| 604 | capabilities: *Section, | ||
| 605 | extensions: *Section, | ||
| 606 | memory_model: *Section, | ||
| 607 | ) !void { | ||
| 608 | var caps: std.EnumSet(spec.Capability) = .empty; | ||
| 609 | var exts: std.StringHashMapUnmanaged(void) = .empty; | ||
| 610 | defer exts.deinit(gpa); | ||
| 611 | |||
| 612 | switch (target.os.tag) { | ||
| 613 | .opengl, .vulkan => caps.insert(.shader), | ||
| 614 | .opencl, .amdhsa => { | ||
| 615 | caps.insert(.kernel); | ||
| 616 | caps.insert(.addresses); | ||
| 617 | }, | ||
| 618 | else => unreachable, | ||
| 619 | } | ||
| 620 | if (target.cpu.arch == .spirv64) { | ||
| 621 | caps.insert(.int64); | ||
| 622 | if (target.os.tag == .vulkan) { | ||
| 623 | caps.insert(.physical_storage_buffer_addresses); | ||
| 624 | try exts.put(gpa, "SPV_KHR_physical_storage_buffer", {}); | ||
| 625 | } | ||
| 626 | } | ||
| 627 | if (has_linkage) caps.insert(.linkage); | ||
| 628 | |||
| 629 | inline for (@typeInfo(spec.Capability).@"enum".field_names) |cap_name| { | ||
| 630 | if (@hasField(std.Target.spirv.Feature, cap_name)) { | ||
| 631 | if (target.cpu.has(.spirv, @field(std.Target.spirv.Feature, cap_name))) | ||
| 632 | caps.insert(@field(spec.Capability, cap_name)); | ||
| 633 | } | ||
| 634 | } | ||
| 635 | inline for (@typeInfo(spec.Extension).@"enum".field_names) |ext_name| { | ||
| 636 | switch (@field(spec.Extension, ext_name)) { | ||
| 637 | .v1_0, .v1_1, .v1_2, .v1_3, .v1_4, .v1_5, .v1_6 => {}, | ||
| 638 | else => if (@hasField(std.Target.spirv.Feature, ext_name)) { | ||
| 639 | if (target.cpu.has(.spirv, @field(std.Target.spirv.Feature, ext_name))) | ||
| 640 | try exts.put(gpa, ext_name, {}); | ||
| 641 | }, | ||
| 642 | } | ||
| 643 | } | ||
| 644 | |||
| 645 | var cit = caps.iterator(); | ||
| 646 | while (cit.next()) |cap| try capabilities.emit(gpa, .OpCapability, .{ .capability = cap }); | ||
| 647 | var eit = exts.iterator(); | ||
| 648 | while (eit.next()) |e| try extensions.emit(gpa, .OpExtension, .{ .name = e.key_ptr.* }); | ||
| 649 | |||
| 650 | const addressing_model: spec.AddressingModel = switch (target.os.tag) { | ||
| 651 | .opengl => .logical, | ||
| 652 | .vulkan => switch (target.cpu.arch) { | ||
| 653 | .spirv32 => .logical, | ||
| 654 | .spirv64 => .physical_storage_buffer64, | ||
| 655 | else => unreachable, | ||
| 656 | }, | ||
| 657 | .opencl => switch (target.cpu.arch) { | ||
| 658 | .spirv32 => .physical32, | ||
| 659 | .spirv64 => .physical64, | ||
| 660 | else => unreachable, | ||
| 661 | }, | ||
| 662 | .amdhsa => .physical64, | ||
| 663 | else => unreachable, | ||
| 664 | }; | ||
| 665 | try memory_model.emit(gpa, .OpMemoryModel, .{ | ||
| 666 | .addressing_model = addressing_model, | ||
| 667 | .memory_model = switch (target.os.tag) { | ||
| 668 | .opencl => .open_cl, | ||
| 669 | .vulkan, .opengl => .glsl450, | ||
| 670 | .amdhsa => unreachable, // TODO | ||
| 671 | else => unreachable, | ||
| 672 | }, | ||
| 673 | }); | ||
| 674 | } | ||
| 675 | |||
| 676 | fn emitEntryPoints( | ||
| 677 | linker: *Linker, | ||
| 678 | gpa: Allocator, | ||
| 679 | target: *const std.Target, | ||
| 680 | entry_points_section: *Section, | ||
| 681 | execution_modes_section: *Section, | ||
| 682 | nav_final_ids: *const std.AutoHashMapUnmanaged(InternPool.Nav.Index, Id), | ||
| 683 | uav_final_ids: *const std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Id), | ||
| 684 | frag_infos: *const std.ArrayList(FragmentInfo), | ||
| 685 | ) error{OutOfMemory}!void { | ||
| 686 | for (linker.entry_points.items) |ep| { | ||
| 687 | const final_id = nav_final_ids.get(ep.nav) orelse continue; | ||
| 688 | |||
| 689 | var interface: std.ArrayList(Id) = .empty; | ||
| 690 | defer interface.deinit(gpa); | ||
| 691 | var visited: std.AutoHashMapUnmanaged(InternPool.Nav.Index, void) = .empty; | ||
| 692 | defer visited.deinit(gpa); | ||
| 693 | try collectEntryPointInterface(linker, ep.nav, &interface, &visited, nav_final_ids, uav_final_ids, frag_infos, gpa); | ||
| 694 | |||
| 695 | const exec_model: spec.ExecutionModel = switch (target.os.tag) { | ||
| 696 | .vulkan, .opengl => switch (ep.cc) { | ||
| 697 | .spirv_vertex => .vertex, | ||
| 698 | .spirv_fragment => .fragment, | ||
| 699 | .spirv_kernel => .gl_compute, | ||
| 700 | .spirv_task => .task_ext, | ||
| 701 | .spirv_mesh => .mesh_ext, | ||
| 702 | .spirv_device => continue, | ||
| 703 | else => unreachable, | ||
| 704 | }, | ||
| 705 | .opencl => switch (ep.cc) { | ||
| 706 | .spirv_kernel => .kernel, | ||
| 707 | .spirv_device => continue, | ||
| 708 | else => unreachable, | ||
| 709 | }, | ||
| 710 | else => unreachable, | ||
| 711 | }; | ||
| 712 | |||
| 713 | try entry_points_section.emit(gpa, .OpEntryPoint, .{ | ||
| 714 | .execution_model = exec_model, | ||
| 715 | .entry_point = final_id, | ||
| 716 | .name = ep.name, | ||
| 717 | .interface = interface.items, | ||
| 718 | }); | ||
| 719 | |||
| 720 | switch (ep.cc) { | ||
| 721 | .spirv_kernel, .spirv_task => |kernel| { | ||
| 722 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | ||
| 723 | .entry_point = final_id, | ||
| 724 | .mode = .{ .local_size = .{ .x_size = kernel.x, .y_size = kernel.y, .z_size = kernel.z } }, | ||
| 725 | }); | ||
| 726 | }, | ||
| 727 | .spirv_fragment => |fragment| { | ||
| 728 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | ||
| 729 | .entry_point = final_id, | ||
| 730 | .mode = if (target.os.tag == .vulkan) .origin_upper_left else .origin_lower_left, | ||
| 731 | }); | ||
| 732 | if (fragment.pixel_centered_integer) { | ||
| 733 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | ||
| 734 | .entry_point = final_id, | ||
| 735 | .mode = .pixel_center_integer, | ||
| 736 | }); | ||
| 737 | } | ||
| 738 | const exec_mode: ?spec.ExecutionMode.Extended = switch (fragment.depth_assumption) { | ||
| 739 | .none => null, | ||
| 740 | .greater => .depth_greater, | ||
| 741 | .less => .depth_less, | ||
| 742 | .unchanged => .depth_unchanged, | ||
| 743 | }; | ||
| 744 | if (exec_mode) |mode| { | ||
| 745 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | ||
| 746 | .entry_point = final_id, | ||
| 747 | .mode = mode, | ||
| 748 | }); | ||
| 749 | } | ||
| 750 | }, | ||
| 751 | .spirv_mesh => |mesh| { | ||
| 752 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | ||
| 753 | .entry_point = final_id, | ||
| 754 | .mode = .{ .output_vertices = .{ .vertex_count = mesh.max_vertices } }, | ||
| 755 | }); | ||
| 756 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | ||
| 757 | .entry_point = final_id, | ||
| 758 | .mode = .{ .output_primitives_ext = .{ .primitive_count = mesh.max_primitives } }, | ||
| 759 | }); | ||
| 760 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | ||
| 761 | .entry_point = final_id, | ||
| 762 | .mode = .{ .local_size = .{ .x_size = mesh.x, .y_size = mesh.y, .z_size = mesh.z } }, | ||
| 763 | }); | ||
| 764 | try execution_modes_section.emit(gpa, .OpExecutionMode, .{ | ||
| 765 | .entry_point = final_id, | ||
| 766 | .mode = switch (mesh.stage_output) { | ||
| 767 | .output_points => .output_points, | ||
| 768 | .output_lines => .output_lines_ext, | ||
| 769 | .output_triangles => .output_triangles_ext, | ||
| 770 | }, | ||
| 771 | }); | ||
| 772 | }, | ||
| 773 | else => {}, | ||
| 774 | } | ||
| 775 | } | ||
| 776 | } | ||
| 777 | |||
| 778 | fn emitSourceInfo(gpa: Allocator, ip: *InternPool, version: u32, debug_strings: *Section) error{OutOfMemory}!void { | ||
| 779 | var error_info: std.Io.Writer.Allocating = .init(gpa); | ||
| 780 | defer error_info.deinit(); | ||
| 781 | error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory; | ||
| 782 | for (ip.global_error_set.getNamesFromMainThread()) |name| { | ||
| 783 | error_info.writer.writeByte(':') catch return error.OutOfMemory; | ||
| 784 | std.Uri.Component.percentEncode( | ||
| 785 | &error_info.writer, | ||
| 786 | name.toSlice(ip), | ||
| 787 | struct { | ||
| 788 | fn isValidChar(c: u8) bool { | ||
| 789 | return switch (c) { | ||
| 790 | 0, '%', ':' => false, | ||
| 791 | else => true, | ||
| 792 | }; | ||
| 793 | } | ||
| 794 | }.isValidChar, | ||
| 795 | ) catch return error.OutOfMemory; | ||
| 796 | } | ||
| 797 | try debug_strings.emit(gpa, .OpSourceExtension, .{ .extension = error_info.written() }); | ||
| 798 | try debug_strings.emit(gpa, .OpSource, .{ .source_language = .zig, .version = version, .file = null, .source = null }); | ||
| 799 | } | ||
| 800 | |||
| 801 | const MergedModule = struct { | ||
| 802 | words: []const Word, | ||
| 803 | id_bound: Word, | ||
| 804 | version: spec.Version, | ||
| 805 | generator_id: u32, | ||
| 806 | }; | ||
| 807 | |||
| 808 | const FragmentInfo = struct { | ||
| 809 | id_offset: Word, | ||
| 810 | }; | ||
| 811 | |||
| 812 | const LinkageDecoration = struct { | ||
| 813 | target_id: Id, | ||
| 814 | name: []const u8, | ||
| 815 | linkage_type: spec.LinkageType, | ||
| 816 | |||
| 817 | fn parse(inst: BinaryModule.Instruction) ?LinkageDecoration { | ||
| 818 | if (inst.opcode != .OpDecorate) return null; | ||
| 819 | if (inst.operands.len < 3) return null; | ||
| 820 | if (inst.operands[1] != @backingInt(spec.Decoration.linkage_attributes)) return null; | ||
| 821 | return .{ | ||
| 822 | .target_id = @fromBackingInt(@intCast(inst.operands[0])), | ||
| 823 | .name = std.mem.sliceTo(std.mem.sliceAsBytes(inst.operands[2 .. inst.operands.len - 1]), 0), | ||
| 824 | .linkage_type = @fromBackingInt(@intCast(inst.operands[inst.operands.len - 1])), | ||
| 825 | }; | ||
| 826 | } | ||
| 827 | }; | ||
| 828 | |||
| 829 | const Sections = struct { | ||
| 830 | ext_inst: Section = .{}, | ||
| 831 | globals: Section = .{}, | ||
| 832 | functions: Section = .{}, | ||
| 833 | annotations: Section = .{}, | ||
| 834 | debug_names: Section = .{}, | ||
| 835 | debug_strings: Section = .{}, | ||
| 836 | entry_points: Section = .{}, | ||
| 837 | execution_modes: Section = .{}, | ||
| 838 | |||
| 839 | fn deinit(self: *Sections, gpa: Allocator) void { | ||
| 840 | self.ext_inst.deinit(gpa); | ||
| 841 | self.globals.deinit(gpa); | ||
| 842 | self.functions.deinit(gpa); | ||
| 843 | self.annotations.deinit(gpa); | ||
| 844 | self.debug_names.deinit(gpa); | ||
| 845 | self.debug_strings.deinit(gpa); | ||
| 846 | self.entry_points.deinit(gpa); | ||
| 847 | self.execution_modes.deinit(gpa); | ||
| 848 | } | ||
| 849 | |||
| 850 | const SectionClass = enum { ext_inst, debug_name, debug_string, annotation, global }; | ||
| 851 | |||
| 852 | fn classifyPreambleInst(opcode: spec.Opcode) SectionClass { | ||
| 853 | return switch (opcode) { | ||
| 854 | .OpExtInstImport => .ext_inst, | ||
| 855 | .OpName, .OpMemberName => .debug_name, | ||
| 856 | .OpString => .debug_string, | ||
| 857 | .OpDecorate, | ||
| 858 | .OpMemberDecorate, | ||
| 859 | .OpGroupDecorate, | ||
| 860 | .OpGroupMemberDecorate, | ||
| 861 | .OpDecorationGroup, | ||
| 862 | .OpDecorateId, | ||
| 863 | .OpDecorateString, | ||
| 864 | .OpMemberDecorateString, | ||
| 865 | => .annotation, | ||
| 866 | else => .global, | ||
| 867 | }; | ||
| 868 | } | ||
| 869 | |||
| 870 | fn getSection(self: *Sections, class: SectionClass) *Section { | ||
| 871 | return switch (class) { | ||
| 872 | .ext_inst => &self.ext_inst, | ||
| 873 | .debug_name => &self.debug_names, | ||
| 874 | .debug_string => &self.debug_strings, | ||
| 875 | .annotation => &self.annotations, | ||
| 876 | .global => &self.globals, | ||
| 877 | }; | ||
| 878 | } | ||
| 879 | }; | ||
| 880 | |||
| 881 | fn appendExternalObjects( | ||
| 882 | linker: *Linker, | ||
| 883 | gpa: Allocator, | ||
| 884 | parser: *BinaryModule.Parser, | ||
| 885 | ext_id_offsets: []const Word, | ||
| 886 | sections: *Sections, | ||
| 887 | has_linkage: *bool, | ||
| 888 | keep_entry_points: bool, | ||
| 889 | is_obj: bool, | ||
| 890 | resolved_ids: *const std.array_hash_map.Auto(Id, void), | ||
| 891 | ) error{OutOfMemory}!void { | ||
| 892 | var export_map: std.array_hash_map.String(Id) = .empty; | ||
| 893 | defer export_map.deinit(gpa); | ||
| 894 | |||
| 895 | for (linker.external_objects.items, ext_id_offsets) |ext_obj, id_offset| { | ||
| 896 | var it: BinaryModule.Instruction.Iterator = .init(ext_obj.instructions, 0); | ||
| 897 | while (it.next()) |inst| { | ||
| 898 | const ld = LinkageDecoration.parse(inst) orelse continue; | ||
| 899 | if (ld.linkage_type != .@"export") continue; | ||
| 900 | try export_map.put(gpa, ld.name, @fromBackingInt(@intCast(@backingInt(ld.target_id) + id_offset))); | ||
| 901 | } | ||
| 902 | } | ||
| 903 | |||
| 904 | var per_obj_remaps = try gpa.alloc(std.AutoHashMapUnmanaged(Id, Id), linker.external_objects.items.len); | ||
| 905 | defer { | ||
| 906 | for (per_obj_remaps) |*m| m.deinit(gpa); | ||
| 907 | gpa.free(per_obj_remaps); | ||
| 908 | } | ||
| 909 | for (per_obj_remaps) |*m| m.* = .empty; | ||
| 910 | |||
| 911 | var resolved_linkage_ids: std.array_hash_map.Auto(Id, void) = .empty; | ||
| 912 | defer resolved_linkage_ids.deinit(gpa); | ||
| 913 | |||
| 914 | for (resolved_ids.keys()) |id| { | ||
| 915 | try resolved_linkage_ids.put(gpa, id, {}); | ||
| 916 | } | ||
| 917 | |||
| 918 | for (linker.external_objects.items, ext_id_offsets, 0..) |ext_obj, id_offset, obj_idx| { | ||
| 919 | var it: BinaryModule.Instruction.Iterator = .init(ext_obj.instructions, 0); | ||
| 920 | while (it.next()) |inst| { | ||
| 921 | const ld = LinkageDecoration.parse(inst) orelse continue; | ||
| 922 | if (ld.linkage_type != .import) continue; | ||
| 923 | const remapped_import: Id = @fromBackingInt(@intCast(@backingInt(ld.target_id) + id_offset)); | ||
| 924 | |||
| 925 | if (export_map.get(ld.name)) |export_id| { | ||
| 926 | try per_obj_remaps[obj_idx].put(gpa, ld.target_id, export_id); | ||
| 927 | try resolved_linkage_ids.put(gpa, remapped_import, {}); | ||
| 928 | try resolved_linkage_ids.put(gpa, export_id, {}); | ||
| 929 | log.debug("cross-object resolve: '{s}' import={d} -> export={d}", .{ | ||
| 930 | ld.name, @backingInt(remapped_import), @backingInt(export_id), | ||
| 931 | }); | ||
| 932 | } else { | ||
| 933 | has_linkage.* = true; | ||
| 934 | } | ||
| 935 | } | ||
| 936 | } | ||
| 937 | |||
| 938 | for (linker.external_objects.items, ext_id_offsets, 0..) |ext_obj, id_offset, obj_idx| { | ||
| 939 | var binary = parser.initFromWords(ext_obj.instructions, ext_obj.id_bound) catch | ||
| 940 | return error.OutOfMemory; | ||
| 941 | defer binary.deinit(gpa); | ||
| 942 | |||
| 943 | const id_remap = &per_obj_remaps[obj_idx]; | ||
| 944 | |||
| 945 | var preamble_it: BinaryModule.Instruction.Iterator = .init(ext_obj.instructions, 0); | ||
| 946 | while (preamble_it.next()) |inst| { | ||
| 947 | if (inst.offset >= binary.functions_start) break; | ||
| 948 | |||
| 949 | switch (inst.opcode) { | ||
| 950 | .OpCapability, | ||
| 951 | .OpExtension, | ||
| 952 | .OpMemoryModel, | ||
| 953 | .OpSource, | ||
| 954 | .OpSourceExtension, | ||
| 955 | .OpSourceContinued, | ||
| 956 | => continue, | ||
| 957 | .OpEntryPoint => { | ||
| 958 | if (keep_entry_points) | ||
| 959 | try remapAndAppendInst(gpa, &sections.entry_points, ext_obj.instructions, inst, id_offset, id_remap, parser); | ||
| 960 | continue; | ||
| 961 | }, | ||
| 962 | .OpExecutionMode, .OpExecutionModeId => { | ||
| 963 | if (keep_entry_points) | ||
| 964 | try remapAndAppendInst(gpa, &sections.execution_modes, ext_obj.instructions, inst, id_offset, id_remap, parser); | ||
| 965 | continue; | ||
| 966 | }, | ||
| 967 | else => {}, | ||
| 968 | } | ||
| 969 | |||
| 970 | if (LinkageDecoration.parse(inst)) |ld| { | ||
| 971 | const remapped: Id = @fromBackingInt(@intCast(@backingInt(ld.target_id) + id_offset)); | ||
| 972 | if (resolved_linkage_ids.contains(remapped)) { | ||
| 973 | if (ld.linkage_type == .@"export" and is_obj) { | ||
| 974 | has_linkage.* = true; | ||
| 975 | } else { | ||
| 976 | continue; | ||
| 977 | } | ||
| 978 | } | ||
| 979 | } | ||
| 980 | |||
| 981 | if (inst.opcode == .OpName and inst.operands.len >= 1) { | ||
| 982 | if (id_remap.contains(@fromBackingInt(@intCast(inst.operands[0])))) continue; | ||
| 983 | } | ||
| 984 | |||
| 985 | const dest = sections.getSection(Sections.classifyPreambleInst(inst.opcode)); | ||
| 986 | try remapAndAppendInst(gpa, dest, ext_obj.instructions, inst, id_offset, id_remap, parser); | ||
| 987 | } | ||
| 988 | |||
| 989 | var fn_it: BinaryModule.Instruction.Iterator = .init(ext_obj.instructions, binary.functions_start); | ||
| 990 | var skip_function = false; | ||
| 991 | while (fn_it.next()) |inst| { | ||
| 992 | if (inst.opcode == .OpFunction) { | ||
| 993 | skip_function = id_remap.contains(@fromBackingInt(@intCast(inst.operands[1]))); | ||
| 994 | } | ||
| 995 | if (!skip_function) { | ||
| 996 | try remapAndAppendInst(gpa, &sections.functions, ext_obj.instructions, inst, id_offset, id_remap, parser); | ||
| 997 | } | ||
| 998 | if (inst.opcode == .OpFunctionEnd) { | ||
| 999 | skip_function = false; | ||
| 1000 | } | ||
| 1001 | } | ||
| 1002 | } | ||
| 1003 | } | ||
| 1004 | |||
| 1005 | fn collectEntryPointInterface( | ||
| 1006 | linker: *Linker, | ||
| 1007 | nav: InternPool.Nav.Index, | ||
| 1008 | interface: *std.ArrayList(Id), | ||
| 1009 | visited: *std.AutoHashMapUnmanaged(InternPool.Nav.Index, void), | ||
| 1010 | nav_final_ids: *const std.AutoHashMapUnmanaged(InternPool.Nav.Index, Id), | ||
| 1011 | uav_final_ids: *const std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Id), | ||
| 1012 | frag_infos: *const std.ArrayList(FragmentInfo), | ||
| 1013 | gpa: Allocator, | ||
| 1014 | ) error{OutOfMemory}!void { | ||
| 1015 | const visited_gop = try visited.getOrPut(gpa, nav); | ||
| 1016 | if (visited_gop.found_existing) return; | ||
| 1017 | |||
| 1018 | const frag_index = linker.fragments.getIndex(nav) orelse return; | ||
| 1019 | const mir = &linker.fragments.values()[frag_index]; | ||
| 1020 | const id_offset = frag_infos.items[frag_index].id_offset; | ||
| 1021 | |||
| 1022 | if (mir.kind == .global) { | ||
| 1023 | if (nav_final_ids.get(nav)) |final_id| { | ||
| 1024 | try interface.append(gpa, final_id); | ||
| 1025 | } | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | for (mir.uav_refs) |ref| { | ||
| 1029 | if (ref.kind == .global) { | ||
| 1030 | if (uav_final_ids.get(.{ ref.val, ref.storage_class })) |final_id| { | ||
| 1031 | try interface.append(gpa, final_id); | ||
| 1032 | } | ||
| 1033 | } | ||
| 1034 | } | ||
| 1035 | |||
| 1036 | for (mir.internal_globals) |local_id| { | ||
| 1037 | const global_id: Id = @fromBackingInt(@intCast(@backingInt(local_id) + id_offset)); | ||
| 1038 | try interface.append(gpa, global_id); | ||
| 1039 | } | ||
| 1040 | |||
| 1041 | for (mir.decl_deps) |dep| { | ||
| 1042 | try collectEntryPointInterface(linker, dep.nav, interface, visited, nav_final_ids, uav_final_ids, frag_infos, gpa); | ||
| 1043 | } | ||
| 1044 | } | ||
| 1045 | |||
| 1046 | fn remapAndAppend( | ||
| 1047 | gpa: Allocator, | ||
| 1048 | dest: *Section, | ||
| 1049 | words: []const Word, | ||
| 1050 | id_offset: Word, | ||
| 1051 | id_remap: *const std.AutoHashMapUnmanaged(Id, Id), | ||
| 1052 | parser: *BinaryModule.Parser, | ||
| 1053 | ) error{OutOfMemory}!void { | ||
| 1054 | if (words.len == 0) return; | ||
| 1055 | |||
| 1056 | try dest.instructions.ensureUnusedCapacity(gpa, words.len); | ||
| 1057 | |||
| 1058 | var it: BinaryModule.Instruction.Iterator = .init(words, 0); | ||
| 1059 | while (it.next()) |inst| { | ||
| 1060 | try remapAndAppendInst(gpa, dest, words, inst, id_offset, id_remap, parser); | ||
| 1061 | } | ||
| 1062 | } | ||
| 1063 | |||
| 1064 | fn remapAndAppendInst( | ||
| 1065 | gpa: Allocator, | ||
| 1066 | dest: *Section, | ||
| 1067 | words: []const Word, | ||
| 1068 | inst: BinaryModule.Instruction, | ||
| 1069 | id_offset: Word, | ||
| 1070 | id_remap: *const std.AutoHashMapUnmanaged(Id, Id), | ||
| 1071 | parser: *BinaryModule.Parser, | ||
| 1072 | ) error{OutOfMemory}!void { | ||
| 1073 | const inst_words = words[inst.offset..][0..((words[inst.offset] >> 16))]; | ||
| 1074 | try dest.instructions.ensureUnusedCapacity(gpa, inst_words.len); | ||
| 1075 | const dest_start = dest.instructions.items.len; | ||
| 1076 | dest.instructions.appendSliceAssumeCapacity(inst_words); | ||
| 1077 | const inst_slice = dest.instructions.items[dest_start..][0..inst_words.len]; | ||
| 1078 | |||
| 1079 | const inst_spec = parser.getInstSpec(inst.opcode) orelse return; | ||
| 1080 | var offset: usize = 0; | ||
| 1081 | for (inst_spec.operands) |operand| { | ||
| 1082 | const cat = operand.kind.category(); | ||
| 1083 | switch (operand.quantifier) { | ||
| 1084 | .required, .optional => { | ||
| 1085 | if (offset >= inst.operands.len) break; | ||
| 1086 | offset += remapOperand(operand.kind, cat, inst, inst_slice, offset, id_offset, id_remap); | ||
| 1087 | }, | ||
| 1088 | .variadic => { | ||
| 1089 | while (offset < inst.operands.len) { | ||
| 1090 | offset += remapOperand(operand.kind, cat, inst, inst_slice, offset, id_offset, id_remap); | ||
| 1091 | } | ||
| 1092 | }, | ||
| 1093 | } | ||
| 1094 | } | ||
| 1095 | } | ||
| 1096 | |||
| 1097 | fn remapOperand( | ||
| 1098 | kind: spec.OperandKind, | ||
| 1099 | cat: spec.OperandCategory, | ||
| 1100 | inst: BinaryModule.Instruction, | ||
| 1101 | inst_slice: []Word, | ||
| 1102 | offset: usize, | ||
| 1103 | id_offset: Word, | ||
| 1104 | id_remap: *const std.AutoHashMapUnmanaged(Id, Id), | ||
| 1105 | ) usize { | ||
| 1106 | switch (cat) { | ||
| 1107 | .id => { | ||
| 1108 | remapSingleId(&inst_slice[1 + offset], id_offset, id_remap); | ||
| 1109 | return 1; | ||
| 1110 | }, | ||
| 1111 | .literal => return operandLiteralWordCount(kind, inst, offset), | ||
| 1112 | .composite => { | ||
| 1113 | remapCompositeOperand(kind, inst_slice, offset, id_offset, id_remap); | ||
| 1114 | return 2; | ||
| 1115 | }, | ||
| 1116 | .bit_enum => { | ||
| 1117 | const mask = inst_slice[1 + offset]; | ||
| 1118 | var consumed: usize = 1; | ||
| 1119 | for (kind.enumerants()) |e| { | ||
| 1120 | if ((mask & e.value) == 0) continue; | ||
| 1121 | for (e.parameters) |param_kind| { | ||
| 1122 | if (offset + consumed >= inst.operands.len) return consumed; | ||
| 1123 | consumed += remapOperand( | ||
| 1124 | param_kind, | ||
| 1125 | param_kind.category(), | ||
| 1126 | inst, | ||
| 1127 | inst_slice, | ||
| 1128 | offset + consumed, | ||
| 1129 | id_offset, | ||
| 1130 | id_remap, | ||
| 1131 | ); | ||
| 1132 | } | ||
| 1133 | } | ||
| 1134 | return consumed; | ||
| 1135 | }, | ||
| 1136 | .value_enum => { | ||
| 1137 | const value = inst_slice[1 + offset]; | ||
| 1138 | var consumed: usize = 1; | ||
| 1139 | for (kind.enumerants()) |e| { | ||
| 1140 | if (e.value != value) continue; | ||
| 1141 | for (e.parameters) |param_kind| { | ||
| 1142 | if (offset + consumed >= inst.operands.len) return consumed; | ||
| 1143 | consumed += remapOperand( | ||
| 1144 | param_kind, | ||
| 1145 | param_kind.category(), | ||
| 1146 | inst, | ||
| 1147 | inst_slice, | ||
| 1148 | offset + consumed, | ||
| 1149 | id_offset, | ||
| 1150 | id_remap, | ||
| 1151 | ); | ||
| 1152 | } | ||
| 1153 | break; | ||
| 1154 | } | ||
| 1155 | return consumed; | ||
| 1156 | }, | ||
| 1157 | } | ||
| 1158 | } | ||
| 1159 | |||
| 1160 | fn remapCompositeOperand( | ||
| 1161 | kind: spec.OperandKind, | ||
| 1162 | inst_slice: []Word, | ||
| 1163 | offset: usize, | ||
| 1164 | id_offset: Word, | ||
| 1165 | id_remap: *const std.AutoHashMapUnmanaged(Id, Id), | ||
| 1166 | ) void { | ||
| 1167 | switch (kind) { | ||
| 1168 | .pair_literal_integer_id_ref => { | ||
| 1169 | remapSingleId(&inst_slice[1 + offset + 1], id_offset, id_remap); | ||
| 1170 | }, | ||
| 1171 | .pair_id_ref_literal_integer => { | ||
| 1172 | remapSingleId(&inst_slice[1 + offset], id_offset, id_remap); | ||
| 1173 | }, | ||
| 1174 | .pair_id_ref_id_ref => { | ||
| 1175 | remapSingleId(&inst_slice[1 + offset], id_offset, id_remap); | ||
| 1176 | remapSingleId(&inst_slice[1 + offset + 1], id_offset, id_remap); | ||
| 1177 | }, | ||
| 1178 | else => {}, | ||
| 1179 | } | ||
| 1180 | } | ||
| 1181 | |||
| 1182 | fn operandLiteralWordCount(kind: spec.OperandKind, inst: BinaryModule.Instruction, offset: usize) usize { | ||
| 1183 | return switch (kind) { | ||
| 1184 | .literal_integer, .literal_float => 1, | ||
| 1185 | .literal_string => blk: { | ||
| 1186 | var count: usize = 0; | ||
| 1187 | var off = offset; | ||
| 1188 | while (off < inst.operands.len) { | ||
| 1189 | const word = inst.operands[off]; | ||
| 1190 | count += 1; | ||
| 1191 | off += 1; | ||
| 1192 | if (word & 0xFF000000 == 0 or | ||
| 1193 | word & 0x00FF0000 == 0 or | ||
| 1194 | word & 0x0000FF00 == 0 or | ||
| 1195 | word & 0x000000FF == 0) | ||
| 1196 | { | ||
| 1197 | break; | ||
| 1198 | } | ||
| 1199 | } | ||
| 1200 | break :blk count; | ||
| 1201 | }, | ||
| 1202 | .literal_context_dependent_number => inst.operands.len - offset, | ||
| 1203 | .literal_ext_inst_integer => 1, | ||
| 1204 | else => 1, | ||
| 1205 | }; | ||
| 1206 | } | ||
| 1207 | |||
| 1208 | fn remapSingleId(word: *Word, id_offset: Word, id_remap: *const std.AutoHashMapUnmanaged(Id, Id)) void { | ||
| 1209 | const id: Id = @fromBackingInt(@intCast(word.*)); | ||
| 1210 | if (id == .none) return; | ||
| 1211 | if (id_remap.get(id)) |final_id| { | ||
| 1212 | word.* = @backingInt(final_id); | ||
| 1213 | } else { | ||
| 1214 | word.* = @backingInt(id) + id_offset; | ||
| 1215 | } | ||
| 1216 | } | ||
src/link/SpirV/BinaryModule.zig deleted-347| ... | @@ -1,347 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const assert = std.debug.assert; | ||
| 3 | const Allocator = std.mem.Allocator; | ||
| 4 | const log = std.log.scoped(.spirv_parse); | ||
| 5 | |||
| 6 | const spec = @import("../../codegen/spirv/spec.zig"); | ||
| 7 | const Opcode = spec.Opcode; | ||
| 8 | const Word = spec.Word; | ||
| 9 | const InstructionSet = spec.InstructionSet; | ||
| 10 | const ResultId = spec.Id; | ||
| 11 | |||
| 12 | const BinaryModule = @This(); | ||
| 13 | |||
| 14 | /// The result-id bound of this SPIR-V module. | ||
| 15 | id_bound: u32, | ||
| 16 | |||
| 17 | /// The instructions of this module (no header). | ||
| 18 | instructions: []const Word, | ||
| 19 | |||
| 20 | /// Maps OpExtInstImport result-ids to their InstructionSet. | ||
| 21 | ext_inst_map: std.AutoHashMapUnmanaged(ResultId, InstructionSet), | ||
| 22 | |||
| 23 | /// Width of arithmetic types (OpTypeInt/OpTypeFloat). Needed to correctly | ||
| 24 | /// parse operands of Op(Spec)Constant and OpSwitch. | ||
| 25 | arith_type_width: std.AutoHashMapUnmanaged(ResultId, u16), | ||
| 26 | |||
| 27 | functions_start: usize, | ||
| 28 | |||
| 29 | pub fn deinit(bm: *BinaryModule, gpa: Allocator) void { | ||
| 30 | bm.ext_inst_map.deinit(gpa); | ||
| 31 | bm.arith_type_width.deinit(gpa); | ||
| 32 | bm.* = undefined; | ||
| 33 | } | ||
| 34 | |||
| 35 | pub fn iterateInstructions(bm: BinaryModule) Instruction.Iterator { | ||
| 36 | return Instruction.Iterator.init(bm.instructions, 0); | ||
| 37 | } | ||
| 38 | |||
| 39 | pub fn iterateInstructionsFrom(bm: BinaryModule, offset: usize) Instruction.Iterator { | ||
| 40 | return Instruction.Iterator.init(bm.instructions, offset); | ||
| 41 | } | ||
| 42 | |||
| 43 | pub const Instruction = struct { | ||
| 44 | pub const Iterator = struct { | ||
| 45 | words: []const Word, | ||
| 46 | offset: usize = 0, | ||
| 47 | |||
| 48 | pub fn init(words: []const Word, start_offset: usize) Iterator { | ||
| 49 | return .{ .words = words, .offset = start_offset }; | ||
| 50 | } | ||
| 51 | |||
| 52 | pub fn next(it: *Iterator) ?Instruction { | ||
| 53 | if (it.offset >= it.words.len) return null; | ||
| 54 | |||
| 55 | const instruction_len = it.words[it.offset] >> 16; | ||
| 56 | defer it.offset += instruction_len; | ||
| 57 | assert(instruction_len != 0); | ||
| 58 | assert(it.offset < it.words.len); | ||
| 59 | |||
| 60 | return Instruction{ | ||
| 61 | .opcode = @fromBackingInt(@intCast(it.words[it.offset] & 0xFFFF)), | ||
| 62 | .offset = it.offset, | ||
| 63 | .operands = it.words[it.offset..][1..instruction_len], | ||
| 64 | }; | ||
| 65 | } | ||
| 66 | }; | ||
| 67 | |||
| 68 | opcode: Opcode, | ||
| 69 | offset: usize, | ||
| 70 | operands: []const Word, | ||
| 71 | }; | ||
| 72 | |||
| 73 | pub const Parser = struct { | ||
| 74 | gpa: Allocator, | ||
| 75 | opcode_table: std.AutoHashMapUnmanaged(u32, u16) = .empty, | ||
| 76 | |||
| 77 | pub fn init(gpa: Allocator) !Parser { | ||
| 78 | var parser = Parser{ .gpa = gpa }; | ||
| 79 | errdefer parser.deinit(); | ||
| 80 | |||
| 81 | inline for (std.meta.tags(InstructionSet)) |set| { | ||
| 82 | const instructions = set.instructions(); | ||
| 83 | try parser.opcode_table.ensureUnusedCapacity(gpa, @intCast(instructions.len)); | ||
| 84 | for (instructions, 0..) |inst, i| { | ||
| 85 | const entry = parser.opcode_table.getOrPutAssumeCapacity(mapSetAndOpcode(set, @intCast(inst.opcode))); | ||
| 86 | if (!entry.found_existing) { | ||
| 87 | entry.value_ptr.* = @intCast(i); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | return parser; | ||
| 93 | } | ||
| 94 | |||
| 95 | pub fn deinit(parser: *Parser) void { | ||
| 96 | parser.opcode_table.deinit(parser.gpa); | ||
| 97 | } | ||
| 98 | |||
| 99 | fn mapSetAndOpcode(set: InstructionSet, opcode: u16) u32 { | ||
| 100 | return (@as(u32, @backingInt(set)) << 16) | opcode; | ||
| 101 | } | ||
| 102 | |||
| 103 | pub fn getInstSpec(parser: Parser, opcode: Opcode) ?spec.Instruction { | ||
| 104 | const index = parser.opcode_table.get(mapSetAndOpcode(.core, @backingInt(opcode))) orelse return null; | ||
| 105 | return InstructionSet.core.instructions()[index]; | ||
| 106 | } | ||
| 107 | |||
| 108 | /// Build a BinaryModule from raw instruction words (no header). | ||
| 109 | /// Scans for ext_inst_map, arith_type_width, and the functions section offset. | ||
| 110 | pub fn initFromWords(parser: *Parser, words: []const Word, id_bound: u32) !BinaryModule { | ||
| 111 | var binary = BinaryModule{ | ||
| 112 | .id_bound = id_bound, | ||
| 113 | .instructions = words, | ||
| 114 | .ext_inst_map = .{}, | ||
| 115 | .arith_type_width = .{}, | ||
| 116 | .functions_start = undefined, | ||
| 117 | }; | ||
| 118 | |||
| 119 | var maybe_function_section: ?usize = null; | ||
| 120 | var it = binary.iterateInstructions(); | ||
| 121 | while (it.next()) |inst| { | ||
| 122 | const inst_spec = parser.getInstSpec(inst.opcode) orelse continue; | ||
| 123 | const operands = inst.operands; | ||
| 124 | |||
| 125 | switch (inst.opcode) { | ||
| 126 | .OpExtInstImport => { | ||
| 127 | const set_name = std.mem.sliceTo(std.mem.sliceAsBytes(operands[1..]), 0); | ||
| 128 | const set = std.meta.stringToEnum(InstructionSet, set_name) orelse continue; | ||
| 129 | if (set == .core) continue; | ||
| 130 | try binary.ext_inst_map.put(parser.gpa, @fromBackingInt(@intCast(operands[0])), set); | ||
| 131 | }, | ||
| 132 | .OpTypeInt, .OpTypeFloat => { | ||
| 133 | try binary.arith_type_width.put(parser.gpa, @fromBackingInt(@intCast(operands[0])), @intCast(operands[1])); | ||
| 134 | }, | ||
| 135 | .OpFunction => if (maybe_function_section == null) { | ||
| 136 | maybe_function_section = inst.offset; | ||
| 137 | }, | ||
| 138 | else => {}, | ||
| 139 | } | ||
| 140 | |||
| 141 | // propagate arith type widths through instructions that return int/float | ||
| 142 | const spec_operands = inst_spec.operands; | ||
| 143 | if (spec_operands.len >= 2 and | ||
| 144 | spec_operands[0].kind == .id_result_type and | ||
| 145 | spec_operands[1].kind == .id_result) | ||
| 146 | { | ||
| 147 | if (operands.len >= 2) { | ||
| 148 | if (binary.arith_type_width.get(@fromBackingInt(@intCast(operands[0])))) |width| { | ||
| 149 | try binary.arith_type_width.put(parser.gpa, @fromBackingInt(@intCast(operands[1])), width); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | binary.functions_start = maybe_function_section orelse binary.instructions.len; | ||
| 156 | |||
| 157 | return binary; | ||
| 158 | } | ||
| 159 | |||
| 160 | /// Parse offsets in the instruction that contain result-ids. | ||
| 161 | /// Returned offsets are relative to inst.operands. | ||
| 162 | pub fn parseInstructionResultIds( | ||
| 163 | parser: *Parser, | ||
| 164 | binary: BinaryModule, | ||
| 165 | inst: Instruction, | ||
| 166 | offsets: *std.ArrayList(u16), | ||
| 167 | ) !void { | ||
| 168 | const index = parser.opcode_table.get(mapSetAndOpcode(.core, @backingInt(inst.opcode))).?; | ||
| 169 | const operands = InstructionSet.core.instructions()[index].operands; | ||
| 170 | |||
| 171 | var offset: usize = 0; | ||
| 172 | switch (inst.opcode) { | ||
| 173 | .OpSpecConstantOp => { | ||
| 174 | assert(operands[0].kind == .id_result_type); | ||
| 175 | assert(operands[1].kind == .id_result); | ||
| 176 | offset = try parser.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets); | ||
| 177 | |||
| 178 | if (offset >= inst.operands.len) return error.InvalidPhysicalFormat; | ||
| 179 | const spec_opcode = std.math.cast(u16, inst.operands[offset]) orelse return error.InvalidPhysicalFormat; | ||
| 180 | const spec_index = parser.opcode_table.get(mapSetAndOpcode(.core, spec_opcode)) orelse | ||
| 181 | return error.InvalidPhysicalFormat; | ||
| 182 | const spec_operands = InstructionSet.core.instructions()[spec_index].operands; | ||
| 183 | assert(spec_operands[0].kind == .id_result_type); | ||
| 184 | assert(spec_operands[1].kind == .id_result); | ||
| 185 | offset = try parser.parseOperandsResultIds(binary, inst, spec_operands[2..], offset + 1, offsets); | ||
| 186 | }, | ||
| 187 | .OpExtInst => { | ||
| 188 | assert(operands[0].kind == .id_result_type); | ||
| 189 | assert(operands[1].kind == .id_result); | ||
| 190 | offset = try parser.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets); | ||
| 191 | |||
| 192 | if (offset + 1 >= inst.operands.len) return error.InvalidPhysicalFormat; | ||
| 193 | const set_id: ResultId = @fromBackingInt(@intCast(inst.operands[offset])); | ||
| 194 | try offsets.append(parser.gpa, @intCast(offset)); | ||
| 195 | const set = binary.ext_inst_map.get(set_id) orelse { | ||
| 196 | log.err("invalid instruction set {}", .{@backingInt(set_id)}); | ||
| 197 | return error.InvalidId; | ||
| 198 | }; | ||
| 199 | const ext_opcode = std.math.cast(u16, inst.operands[offset + 1]) orelse return error.InvalidPhysicalFormat; | ||
| 200 | const ext_index = parser.opcode_table.get(mapSetAndOpcode(set, ext_opcode)) orelse | ||
| 201 | return error.InvalidPhysicalFormat; | ||
| 202 | const ext_operands = set.instructions()[ext_index].operands; | ||
| 203 | offset = try parser.parseOperandsResultIds(binary, inst, ext_operands, offset + 2, offsets); | ||
| 204 | }, | ||
| 205 | else => { | ||
| 206 | offset = try parser.parseOperandsResultIds(binary, inst, operands, offset, offsets); | ||
| 207 | }, | ||
| 208 | } | ||
| 209 | |||
| 210 | if (offset != inst.operands.len) return error.InvalidPhysicalFormat; | ||
| 211 | } | ||
| 212 | |||
| 213 | fn parseOperandsResultIds( | ||
| 214 | parser: *Parser, | ||
| 215 | binary: BinaryModule, | ||
| 216 | inst: Instruction, | ||
| 217 | operands: []const spec.Operand, | ||
| 218 | start_offset: usize, | ||
| 219 | offsets: *std.ArrayList(u16), | ||
| 220 | ) !usize { | ||
| 221 | var offset = start_offset; | ||
| 222 | for (operands) |operand| { | ||
| 223 | offset = try parser.parseOperandResultIds(binary, inst, operand, offset, offsets); | ||
| 224 | } | ||
| 225 | return offset; | ||
| 226 | } | ||
| 227 | |||
| 228 | fn parseOperandResultIds( | ||
| 229 | parser: *Parser, | ||
| 230 | binary: BinaryModule, | ||
| 231 | inst: Instruction, | ||
| 232 | operand: spec.Operand, | ||
| 233 | start_offset: usize, | ||
| 234 | offsets: *std.ArrayList(u16), | ||
| 235 | ) !usize { | ||
| 236 | var offset = start_offset; | ||
| 237 | switch (operand.quantifier) { | ||
| 238 | .variadic => while (offset < inst.operands.len) { | ||
| 239 | offset = try parser.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets); | ||
| 240 | }, | ||
| 241 | .optional => if (offset < inst.operands.len) { | ||
| 242 | offset = try parser.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets); | ||
| 243 | }, | ||
| 244 | .required => { | ||
| 245 | offset = try parser.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets); | ||
| 246 | }, | ||
| 247 | } | ||
| 248 | return offset; | ||
| 249 | } | ||
| 250 | |||
| 251 | fn parseOperandKindResultIds( | ||
| 252 | parser: *Parser, | ||
| 253 | binary: BinaryModule, | ||
| 254 | inst: Instruction, | ||
| 255 | kind: spec.OperandKind, | ||
| 256 | start_offset: usize, | ||
| 257 | offsets: *std.ArrayList(u16), | ||
| 258 | ) !usize { | ||
| 259 | var offset = start_offset; | ||
| 260 | if (offset >= inst.operands.len) return error.InvalidPhysicalFormat; | ||
| 261 | |||
| 262 | switch (kind.category()) { | ||
| 263 | .bit_enum => { | ||
| 264 | const mask = inst.operands[offset]; | ||
| 265 | offset += 1; | ||
| 266 | for (kind.enumerants()) |enumerant| { | ||
| 267 | if ((mask & enumerant.value) != 0) { | ||
| 268 | for (enumerant.parameters) |param_kind| { | ||
| 269 | offset = try parser.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets); | ||
| 270 | } | ||
| 271 | } | ||
| 272 | } | ||
| 273 | }, | ||
| 274 | .value_enum => { | ||
| 275 | const value = inst.operands[offset]; | ||
| 276 | offset += 1; | ||
| 277 | for (kind.enumerants()) |enumerant| { | ||
| 278 | if (value == enumerant.value) { | ||
| 279 | for (enumerant.parameters) |param_kind| { | ||
| 280 | offset = try parser.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets); | ||
| 281 | } | ||
| 282 | break; | ||
| 283 | } | ||
| 284 | } | ||
| 285 | }, | ||
| 286 | .id => { | ||
| 287 | try offsets.append(parser.gpa, @intCast(offset)); | ||
| 288 | offset += 1; | ||
| 289 | }, | ||
| 290 | else => switch (kind) { | ||
| 291 | .literal_integer, .literal_float => offset += 1, | ||
| 292 | .literal_string => while (true) { | ||
| 293 | if (offset >= inst.operands.len) return error.InvalidPhysicalFormat; | ||
| 294 | const word = inst.operands[offset]; | ||
| 295 | offset += 1; | ||
| 296 | |||
| 297 | if (word & 0xFF000000 == 0 or | ||
| 298 | word & 0x00FF0000 == 0 or | ||
| 299 | word & 0x0000FF00 == 0 or | ||
| 300 | word & 0x000000FF == 0) | ||
| 301 | { | ||
| 302 | break; | ||
| 303 | } | ||
| 304 | }, | ||
| 305 | .literal_context_dependent_number => { | ||
| 306 | assert(inst.opcode == .OpConstant or inst.opcode == .OpSpecConstant); | ||
| 307 | const bit_width = binary.arith_type_width.get(@fromBackingInt(@intCast(inst.operands[0]))) orelse { | ||
| 308 | log.err("invalid LiteralContextDependentNumber type {}", .{inst.operands[0]}); | ||
| 309 | return error.InvalidId; | ||
| 310 | }; | ||
| 311 | offset += switch (bit_width) { | ||
| 312 | 1...32 => 1, | ||
| 313 | 33...64 => 2, | ||
| 314 | else => unreachable, | ||
| 315 | }; | ||
| 316 | }, | ||
| 317 | .literal_ext_inst_integer => unreachable, | ||
| 318 | .literal_spec_constant_op_integer => unreachable, | ||
| 319 | .pair_literal_integer_id_ref => { | ||
| 320 | assert(inst.opcode == .OpSwitch); | ||
| 321 | const bit_width = binary.arith_type_width.get(@fromBackingInt(@intCast(inst.operands[0]))) orelse { | ||
| 322 | log.err("invalid OpSwitch type {}", .{inst.operands[0]}); | ||
| 323 | return error.InvalidId; | ||
| 324 | }; | ||
| 325 | offset += switch (bit_width) { | ||
| 326 | 1...32 => 1, | ||
| 327 | 33...64 => 2, | ||
| 328 | else => unreachable, | ||
| 329 | }; | ||
| 330 | try offsets.append(parser.gpa, @intCast(offset)); | ||
| 331 | offset += 1; | ||
| 332 | }, | ||
| 333 | .pair_id_ref_literal_integer => { | ||
| 334 | try offsets.append(parser.gpa, @intCast(offset)); | ||
| 335 | offset += 2; | ||
| 336 | }, | ||
| 337 | .pair_id_ref_id_ref => { | ||
| 338 | try offsets.append(parser.gpa, @intCast(offset)); | ||
| 339 | try offsets.append(parser.gpa, @intCast(offset + 1)); | ||
| 340 | offset += 2; | ||
| 341 | }, | ||
| 342 | else => unreachable, | ||
| 343 | }, | ||
| 344 | } | ||
| 345 | return offset; | ||
| 346 | } | ||
| 347 | }; | ||
src/link/SpirV/dedup_types.zig deleted-244| ... | @@ -1,244 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Allocator = std.mem.Allocator; | ||
| 3 | const BinaryModule = @import("BinaryModule.zig"); | ||
| 4 | |||
| 5 | const spec = @import("../../codegen/spirv/spec.zig"); | ||
| 6 | const Word = spec.Word; | ||
| 7 | const Id = spec.Id; | ||
| 8 | const Opcode = spec.Opcode; | ||
| 9 | const Instruction = BinaryModule.Instruction; | ||
| 10 | |||
| 11 | /// Deduplicate types and constants in a SPIR-V binary module. | ||
| 12 | /// | ||
| 13 | /// The SPIR-V spec requires that non-aggregate types be unique. | ||
| 14 | /// When merging fragments from parallel codegen, duplicate type definitions | ||
| 15 | /// may exist. This pass identifies structurally identical types/constants, | ||
| 16 | /// keeps one canonical instance, and remaps all references to duplicates. | ||
| 17 | pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void { | ||
| 18 | const gpa = parser.gpa; | ||
| 19 | |||
| 20 | const Decoration = struct { offset: usize, len: usize }; | ||
| 21 | var decorations_by_id: std.array_hash_map.Auto(Id, std.ArrayList(Decoration)) = .empty; | ||
| 22 | defer { | ||
| 23 | for (decorations_by_id.values()) |*list| list.deinit(gpa); | ||
| 24 | decorations_by_id.deinit(gpa); | ||
| 25 | } | ||
| 26 | |||
| 27 | var it = binary.iterateInstructions(); | ||
| 28 | while (it.next()) |inst| { | ||
| 29 | if (inst.offset >= binary.functions_start) break; | ||
| 30 | switch (inst.opcode) { | ||
| 31 | .OpName, .OpMemberName => continue, | ||
| 32 | else => switch (inst.opcode.class()) { | ||
| 33 | .annotation => {}, | ||
| 34 | else => continue, | ||
| 35 | }, | ||
| 36 | } | ||
| 37 | if (inst.operands.len == 0) continue; | ||
| 38 | const target_id: Id = @fromBackingInt(@intCast(inst.operands[0])); | ||
| 39 | |||
| 40 | const gop = try decorations_by_id.getOrPut(gpa, target_id); | ||
| 41 | if (!gop.found_existing) gop.value_ptr.* = .empty; | ||
| 42 | try gop.value_ptr.append(gpa, .{ | ||
| 43 | .offset = inst.offset, | ||
| 44 | .len = 1 + inst.operands.len, | ||
| 45 | }); | ||
| 46 | } | ||
| 47 | |||
| 48 | var canonical_map: std.array_hash_map.Custom(TypeKey, Id, TypeKey.HashContext, true) = .empty; | ||
| 49 | defer { | ||
| 50 | for (canonical_map.keys()) |key| gpa.free(key.words); | ||
| 51 | canonical_map.deinit(gpa); | ||
| 52 | } | ||
| 53 | |||
| 54 | var id_remap: std.AutoHashMapUnmanaged(Id, Id) = .empty; | ||
| 55 | defer id_remap.deinit(gpa); | ||
| 56 | |||
| 57 | var id_offsets: std.ArrayList(u16) = .empty; | ||
| 58 | defer id_offsets.deinit(gpa); | ||
| 59 | |||
| 60 | var key_words: std.ArrayList(Word) = .empty; | ||
| 61 | defer key_words.deinit(gpa); | ||
| 62 | |||
| 63 | var dec_hashes: std.ArrayList(u64) = .empty; | ||
| 64 | defer dec_hashes.deinit(gpa); | ||
| 65 | |||
| 66 | // first pass: build canonical map, identify duplicates | ||
| 67 | it = binary.iterateInstructions(); | ||
| 68 | while (it.next()) |inst| { | ||
| 69 | if (inst.offset >= binary.functions_start) break; | ||
| 70 | if (!canDeduplicate(inst.opcode)) continue; | ||
| 71 | |||
| 72 | const result_id_index: usize = switch (inst.opcode.class()) { | ||
| 73 | .type_declaration, .extension => 0, | ||
| 74 | .constant_creation => 1, | ||
| 75 | else => continue, | ||
| 76 | }; | ||
| 77 | if (result_id_index >= inst.operands.len) continue; | ||
| 78 | const result_id: Id = @fromBackingInt(@intCast(inst.operands[result_id_index])); | ||
| 79 | |||
| 80 | key_words.items.len = 0; | ||
| 81 | try key_words.append(gpa, @backingInt(inst.opcode)); | ||
| 82 | |||
| 83 | id_offsets.items.len = 0; | ||
| 84 | parser.parseInstructionResultIds(binary.*, inst, &id_offsets) catch continue; | ||
| 85 | |||
| 86 | for (inst.operands, 0..) |word, i| { | ||
| 87 | if (i == result_id_index) continue; | ||
| 88 | if (std.mem.findScalar(u16, id_offsets.items, @intCast(i)) != null) { | ||
| 89 | const canonical = id_remap.get(@fromBackingInt(@intCast(word))) orelse @as(Id, @fromBackingInt(@intCast(word))); | ||
| 90 | try key_words.append(gpa, @backingInt(canonical)); | ||
| 91 | } else { | ||
| 92 | try key_words.append(gpa, word); | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | if (decorations_by_id.getPtr(result_id)) |dec_list| { | ||
| 97 | dec_hashes.items.len = 0; | ||
| 98 | for (dec_list.items) |dec| { | ||
| 99 | const dec_words = binary.instructions[dec.offset..][0..dec.len]; | ||
| 100 | var hasher = std.hash.Wyhash.init(0); | ||
| 101 | hasher.update(std.mem.asBytes(&dec_words[0])); | ||
| 102 | for (dec_words[2..]) |w| { | ||
| 103 | const w_val = if (id_remap.get(@fromBackingInt(@intCast(w)))) |c| @backingInt(c) else w; | ||
| 104 | hasher.update(std.mem.asBytes(&w_val)); | ||
| 105 | } | ||
| 106 | try dec_hashes.append(gpa, hasher.final()); | ||
| 107 | } | ||
| 108 | std.mem.sort(u64, dec_hashes.items, {}, std.sort.asc(u64)); | ||
| 109 | var prev: u64 = 0; | ||
| 110 | for (dec_hashes.items) |h| { | ||
| 111 | if (h == prev) continue; | ||
| 112 | prev = h; | ||
| 113 | try key_words.append(gpa, @truncate(h)); | ||
| 114 | try key_words.append(gpa, @truncate(h >> 32)); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | const key = TypeKey{ .words = try gpa.dupe(Word, key_words.items) }; | ||
| 119 | const gop = try canonical_map.getOrPut(gpa, key); | ||
| 120 | if (gop.found_existing) { | ||
| 121 | try id_remap.put(gpa, result_id, gop.value_ptr.*); | ||
| 122 | gpa.free(key.words); | ||
| 123 | } else { | ||
| 124 | gop.value_ptr.* = result_id; | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | if (id_remap.count() == 0) return; | ||
| 129 | |||
| 130 | // second pass: rewrite id references, remove duplicates and redundant annotations | ||
| 131 | var new_words: std.ArrayList(Word) = .empty; | ||
| 132 | defer new_words.deinit(gpa); | ||
| 133 | try new_words.ensureTotalCapacity(gpa, binary.instructions.len); | ||
| 134 | |||
| 135 | var emitted_annotations: std.AutoHashMapUnmanaged(u64, void) = .empty; | ||
| 136 | defer emitted_annotations.deinit(gpa); | ||
| 137 | |||
| 138 | var new_functions_offset: ?usize = null; | ||
| 139 | var max_id: Word = 0; | ||
| 140 | |||
| 141 | it = binary.iterateInstructions(); | ||
| 142 | while (it.next()) |inst| { | ||
| 143 | if (new_functions_offset == null and inst.offset >= binary.functions_start) { | ||
| 144 | new_functions_offset = new_words.items.len; | ||
| 145 | } | ||
| 146 | |||
| 147 | if (canDeduplicate(inst.opcode)) { | ||
| 148 | const result_id_index: usize = switch (inst.opcode.class()) { | ||
| 149 | .type_declaration, .extension => 0, | ||
| 150 | .constant_creation => 1, | ||
| 151 | else => unreachable, | ||
| 152 | }; | ||
| 153 | if (result_id_index < inst.operands.len) { | ||
| 154 | const result_id: Id = @fromBackingInt(@intCast(inst.operands[result_id_index])); | ||
| 155 | if (id_remap.contains(result_id)) continue; | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | switch (inst.opcode.class()) { | ||
| 160 | .annotation, .debug => { | ||
| 161 | if (inst.operands.len > 0) { | ||
| 162 | const target: Id = @fromBackingInt(@intCast(inst.operands[0])); | ||
| 163 | if (id_remap.contains(target)) continue; | ||
| 164 | } | ||
| 165 | }, | ||
| 166 | else => {}, | ||
| 167 | } | ||
| 168 | |||
| 169 | const inst_start = new_words.items.len; | ||
| 170 | new_words.appendAssumeCapacity(binary.instructions[inst.offset]); | ||
| 171 | new_words.appendSliceAssumeCapacity(inst.operands); | ||
| 172 | const inst_slice = new_words.items[inst_start + 1 ..]; | ||
| 173 | |||
| 174 | id_offsets.items.len = 0; | ||
| 175 | parser.parseInstructionResultIds(binary.*, inst, &id_offsets) catch continue; | ||
| 176 | |||
| 177 | const inst_spec = parser.getInstSpec(inst.opcode); | ||
| 178 | const maybe_result_id_index: ?usize = if (inst_spec) |ispec| blk: { | ||
| 179 | break :blk for (0..@min(2, ispec.operands.len)) |i| { | ||
| 180 | if (ispec.operands[i].kind == .id_result) break @intCast(i); | ||
| 181 | } else null; | ||
| 182 | } else null; | ||
| 183 | |||
| 184 | for (inst_slice, 0..) |*word, i| { | ||
| 185 | if (std.mem.findScalar(u16, id_offsets.items, @intCast(i)) == null) continue; | ||
| 186 | max_id = @max(max_id, word.*); | ||
| 187 | if (maybe_result_id_index != null and i == maybe_result_id_index.?) continue; | ||
| 188 | |||
| 189 | if (id_remap.get(@fromBackingInt(@intCast(word.*)))) |canonical| { | ||
| 190 | word.* = @backingInt(canonical); | ||
| 191 | max_id = @max(max_id, word.*); | ||
| 192 | } | ||
| 193 | } | ||
| 194 | |||
| 195 | switch (inst.opcode.class()) { | ||
| 196 | .annotation, .debug => { | ||
| 197 | const ann_words = new_words.items[inst_start..]; | ||
| 198 | const ann_hash = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(ann_words)); | ||
| 199 | const gop = try emitted_annotations.getOrPut(gpa, ann_hash); | ||
| 200 | if (gop.found_existing) { | ||
| 201 | new_words.items.len = inst_start; | ||
| 202 | continue; | ||
| 203 | } | ||
| 204 | }, | ||
| 205 | else => {}, | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | var remap_it = id_remap.iterator(); | ||
| 210 | while (remap_it.next()) |entry| { | ||
| 211 | _ = binary.ext_inst_map.remove(entry.key_ptr.*); | ||
| 212 | _ = binary.arith_type_width.remove(entry.key_ptr.*); | ||
| 213 | } | ||
| 214 | |||
| 215 | binary.instructions = try gpa.dupe(Word, new_words.items); | ||
| 216 | binary.functions_start = new_functions_offset orelse new_words.items.len; | ||
| 217 | binary.id_bound = max_id + 1; | ||
| 218 | } | ||
| 219 | |||
| 220 | fn canDeduplicate(opcode: Opcode) bool { | ||
| 221 | return switch (opcode) { | ||
| 222 | .OpTypeForwardPointer => false, | ||
| 223 | .OpGroupDecorate, .OpGroupMemberDecorate => false, | ||
| 224 | else => switch (opcode.class()) { | ||
| 225 | .type_declaration, .constant_creation => true, | ||
| 226 | .extension => opcode == .OpExtInstImport, | ||
| 227 | else => false, | ||
| 228 | }, | ||
| 229 | }; | ||
| 230 | } | ||
| 231 | |||
| 232 | const TypeKey = struct { | ||
| 233 | words: []const Word, | ||
| 234 | |||
| 235 | const HashContext = struct { | ||
| 236 | pub fn hash(_: @This(), key: TypeKey) u32 { | ||
| 237 | return @truncate(std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(key.words))); | ||
| 238 | } | ||
| 239 | |||
| 240 | pub fn eql(_: @This(), a: TypeKey, b: TypeKey, _: usize) bool { | ||
| 241 | return std.mem.eql(Word, a.words, b.words); | ||
| 242 | } | ||
| 243 | }; | ||
| 244 | }; | ||
src/link/SpirV/lower_invocation_globals.zig deleted-803| ... | @@ -1,803 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Allocator = std.mem.Allocator; | ||
| 3 | const assert = std.debug.assert; | ||
| 4 | const log = std.log.scoped(.spirv_link); | ||
| 5 | |||
| 6 | const BinaryModule = @import("BinaryModule.zig"); | ||
| 7 | const Section = @import("../../codegen/spirv/Section.zig"); | ||
| 8 | const spec = @import("../../codegen/spirv/spec.zig"); | ||
| 9 | const ResultId = spec.Id; | ||
| 10 | const Word = spec.Word; | ||
| 11 | |||
| 12 | /// This structure contains all the stuff that we need to parse from the module in | ||
| 13 | /// order to run this pass, as well as some functions to ease its use. | ||
| 14 | const ModuleInfo = struct { | ||
| 15 | /// Information about a particular function. | ||
| 16 | const Fn = struct { | ||
| 17 | /// The index of the first callee in `callee_store`. | ||
| 18 | first_callee: usize, | ||
| 19 | /// The return type id of this function | ||
| 20 | return_type: ResultId, | ||
| 21 | /// The parameter types of this function | ||
| 22 | param_types: []const ResultId, | ||
| 23 | /// The set of (result-id's of) invocation globals that are accessed | ||
| 24 | /// in this function, or after resolution, that are accessed in this | ||
| 25 | /// function or any of it's callees. | ||
| 26 | invocation_globals: std.array_hash_map.Auto(ResultId, void), | ||
| 27 | }; | ||
| 28 | |||
| 29 | /// Information about a particular invocation global | ||
| 30 | const InvocationGlobal = struct { | ||
| 31 | /// The list of invocation globals that this invocation global | ||
| 32 | /// depends on. | ||
| 33 | dependencies: std.array_hash_map.Auto(ResultId, void), | ||
| 34 | /// The invocation global's type | ||
| 35 | ty: ResultId, | ||
| 36 | /// Initializer function. May be `none`. | ||
| 37 | /// Note that if the initializer is `none`, then `dependencies` is empty. | ||
| 38 | initializer: ResultId, | ||
| 39 | }; | ||
| 40 | |||
| 41 | /// Maps function result-id -> Fn information structure. | ||
| 42 | functions: std.array_hash_map.Auto(ResultId, Fn), | ||
| 43 | /// Set of OpFunction result-ids in this module. | ||
| 44 | entry_points: std.array_hash_map.Auto(ResultId, void), | ||
| 45 | /// For each function, a list of function result-ids that it calls. | ||
| 46 | callee_store: []const ResultId, | ||
| 47 | /// Maps each invocation global result-id to a type-id. | ||
| 48 | invocation_globals: std.array_hash_map.Auto(ResultId, InvocationGlobal), | ||
| 49 | /// Subset of `invocation_globals` reachable from any entry point. | ||
| 50 | live_invocation_globals: std.array_hash_map.Auto(ResultId, void), | ||
| 51 | /// Initializer functions of unreachable invocation globals. Their | ||
| 52 | /// OpFunction...OpFunctionEnd ranges are skipped during rewriteFunctions. | ||
| 53 | dead_initializers: std.array_hash_map.Auto(ResultId, void), | ||
| 54 | |||
| 55 | /// Fetch the list of callees per function. Guaranteed to contain only unique IDs. | ||
| 56 | fn callees(self: ModuleInfo, fn_id: ResultId) []const ResultId { | ||
| 57 | const fn_index = self.functions.getIndex(fn_id).?; | ||
| 58 | const values = self.functions.values(); | ||
| 59 | const first_callee = values[fn_index].first_callee; | ||
| 60 | if (fn_index == values.len - 1) { | ||
| 61 | return self.callee_store[first_callee..]; | ||
| 62 | } else { | ||
| 63 | const next_first_callee = values[fn_index + 1].first_callee; | ||
| 64 | return self.callee_store[first_callee..next_first_callee]; | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | /// Extract most of the required information from the binary. The remaining info is | ||
| 69 | /// constructed by `resolve()`. | ||
| 70 | fn parse( | ||
| 71 | arena: Allocator, | ||
| 72 | parser: *BinaryModule.Parser, | ||
| 73 | binary: BinaryModule, | ||
| 74 | ) !ModuleInfo { | ||
| 75 | var entry_points: std.array_hash_map.Auto(ResultId, void) = .empty; | ||
| 76 | var functions: std.array_hash_map.Auto(ResultId, Fn) = .empty; | ||
| 77 | var fn_types = std.AutoHashMap(ResultId, struct { | ||
| 78 | return_type: ResultId, | ||
| 79 | param_types: []const ResultId, | ||
| 80 | }).init(arena); | ||
| 81 | var calls: std.array_hash_map.Auto(ResultId, void) = .empty; | ||
| 82 | var callee_store: std.ArrayList(ResultId) = .empty; | ||
| 83 | var function_invocation_globals: std.array_hash_map.Auto(ResultId, void) = .empty; | ||
| 84 | var result_id_offsets: std.ArrayList(u16) = .empty; | ||
| 85 | var invocation_globals: std.array_hash_map.Auto(ResultId, InvocationGlobal) = .empty; | ||
| 86 | |||
| 87 | var maybe_current_function: ?ResultId = null; | ||
| 88 | var fn_ty_id: ResultId = undefined; | ||
| 89 | |||
| 90 | var it = binary.iterateInstructions(); | ||
| 91 | while (it.next()) |inst| { | ||
| 92 | result_id_offsets.items.len = 0; | ||
| 93 | try parser.parseInstructionResultIds(binary, inst, &result_id_offsets); | ||
| 94 | |||
| 95 | switch (inst.opcode) { | ||
| 96 | .OpEntryPoint => { | ||
| 97 | const entry_point: ResultId = @fromBackingInt(@intCast(inst.operands[1])); | ||
| 98 | const entry = try entry_points.getOrPut(arena, entry_point); | ||
| 99 | if (entry.found_existing) { | ||
| 100 | log.err("Entry point type {f} has duplicate definition", .{entry_point}); | ||
| 101 | return error.DuplicateId; | ||
| 102 | } | ||
| 103 | }, | ||
| 104 | .OpTypeFunction => { | ||
| 105 | const fn_type: ResultId = @fromBackingInt(@intCast(inst.operands[0])); | ||
| 106 | const return_type: ResultId = @fromBackingInt(@intCast(inst.operands[1])); | ||
| 107 | const param_types: []const ResultId = @ptrCast(inst.operands[2..]); | ||
| 108 | |||
| 109 | const entry = try fn_types.getOrPut(fn_type); | ||
| 110 | if (entry.found_existing) { | ||
| 111 | log.err("Function type {f} has duplicate definition", .{fn_type}); | ||
| 112 | return error.DuplicateId; | ||
| 113 | } | ||
| 114 | |||
| 115 | entry.value_ptr.* = .{ | ||
| 116 | .return_type = return_type, | ||
| 117 | .param_types = param_types, | ||
| 118 | }; | ||
| 119 | }, | ||
| 120 | .OpExtInst => { | ||
| 121 | // Note: format and set are already verified by parseInstructionResultIds(). | ||
| 122 | const global_type: ResultId = @fromBackingInt(@intCast(inst.operands[0])); | ||
| 123 | const result_id: ResultId = @fromBackingInt(@intCast(inst.operands[1])); | ||
| 124 | const set_id: ResultId = @fromBackingInt(@intCast(inst.operands[2])); | ||
| 125 | const set_inst = inst.operands[3]; | ||
| 126 | |||
| 127 | const set = binary.ext_inst_map.get(set_id).?; | ||
| 128 | if (set == .zig and set_inst == 0) { | ||
| 129 | const initializer: ResultId = if (inst.operands.len >= 5) | ||
| 130 | @fromBackingInt(@intCast(inst.operands[4])) | ||
| 131 | else | ||
| 132 | .none; | ||
| 133 | |||
| 134 | try invocation_globals.put(arena, result_id, .{ | ||
| 135 | .dependencies = .{}, | ||
| 136 | .ty = global_type, | ||
| 137 | .initializer = initializer, | ||
| 138 | }); | ||
| 139 | } | ||
| 140 | }, | ||
| 141 | .OpFunction => { | ||
| 142 | if (maybe_current_function) |current_function| { | ||
| 143 | log.err("OpFunction {f} does not have an OpFunctionEnd", .{current_function}); | ||
| 144 | return error.InvalidPhysicalFormat; | ||
| 145 | } | ||
| 146 | |||
| 147 | maybe_current_function = @fromBackingInt(@intCast(inst.operands[1])); | ||
| 148 | fn_ty_id = @fromBackingInt(@intCast(inst.operands[3])); | ||
| 149 | function_invocation_globals.clearRetainingCapacity(); | ||
| 150 | }, | ||
| 151 | .OpFunctionCall => { | ||
| 152 | const callee: ResultId = @fromBackingInt(@intCast(inst.operands[2])); | ||
| 153 | try calls.put(arena, callee, {}); | ||
| 154 | }, | ||
| 155 | .OpFunctionEnd => { | ||
| 156 | const current_function = maybe_current_function orelse { | ||
| 157 | log.err("encountered OpFunctionEnd without corresponding OpFunction", .{}); | ||
| 158 | return error.InvalidPhysicalFormat; | ||
| 159 | }; | ||
| 160 | const entry = try functions.getOrPut(arena, current_function); | ||
| 161 | if (entry.found_existing) { | ||
| 162 | log.err("Function {f} has duplicate definition", .{current_function}); | ||
| 163 | return error.DuplicateId; | ||
| 164 | } | ||
| 165 | |||
| 166 | const first_callee = callee_store.items.len; | ||
| 167 | try callee_store.appendSlice(arena, calls.keys()); | ||
| 168 | |||
| 169 | const fn_type = fn_types.get(fn_ty_id) orelse { | ||
| 170 | log.err("Function {f} has invalid OpFunction type", .{current_function}); | ||
| 171 | return error.InvalidId; | ||
| 172 | }; | ||
| 173 | |||
| 174 | entry.value_ptr.* = .{ | ||
| 175 | .first_callee = first_callee, | ||
| 176 | .return_type = fn_type.return_type, | ||
| 177 | .param_types = fn_type.param_types, | ||
| 178 | .invocation_globals = try function_invocation_globals.clone(arena), | ||
| 179 | }; | ||
| 180 | maybe_current_function = null; | ||
| 181 | calls.clearRetainingCapacity(); | ||
| 182 | }, | ||
| 183 | else => {}, | ||
| 184 | } | ||
| 185 | |||
| 186 | for (result_id_offsets.items) |off| { | ||
| 187 | const result_id: ResultId = @fromBackingInt(@intCast(inst.operands[off])); | ||
| 188 | if (invocation_globals.contains(result_id)) { | ||
| 189 | try function_invocation_globals.put(arena, result_id, {}); | ||
| 190 | } | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | if (maybe_current_function) |current_function| { | ||
| 195 | log.err("OpFunction {f} does not have an OpFunctionEnd", .{current_function}); | ||
| 196 | return error.InvalidPhysicalFormat; | ||
| 197 | } | ||
| 198 | |||
| 199 | return .{ | ||
| 200 | .functions = functions, | ||
| 201 | .entry_points = entry_points, | ||
| 202 | .callee_store = callee_store.items, | ||
| 203 | .invocation_globals = invocation_globals, | ||
| 204 | .live_invocation_globals = .empty, | ||
| 205 | .dead_initializers = .empty, | ||
| 206 | }; | ||
| 207 | } | ||
| 208 | |||
| 209 | /// Derive the remaining info from the structures filled in by parsing. | ||
| 210 | fn resolve(self: *ModuleInfo, arena: Allocator) !void { | ||
| 211 | try self.resolveInvocationGlobalUsage(arena); | ||
| 212 | try self.resolveInvocationGlobalDependencies(arena); | ||
| 213 | try self.resolveLiveSet(arena); | ||
| 214 | } | ||
| 215 | |||
| 216 | fn resolveLiveSet(self: *ModuleInfo, arena: Allocator) !void { | ||
| 217 | for (self.entry_points.keys()) |ep_id| { | ||
| 218 | const ep_info = self.functions.get(ep_id) orelse continue; | ||
| 219 | for (ep_info.invocation_globals.keys()) |g| { | ||
| 220 | try self.live_invocation_globals.put(arena, g, {}); | ||
| 221 | const g_info = self.invocation_globals.get(g).?; | ||
| 222 | for (g_info.dependencies.keys()) |dep| { | ||
| 223 | try self.live_invocation_globals.put(arena, dep, {}); | ||
| 224 | } | ||
| 225 | } | ||
| 226 | } | ||
| 227 | for (self.invocation_globals.keys(), self.invocation_globals.values()) |g, info| { | ||
| 228 | if (info.initializer == .none) continue; | ||
| 229 | if (self.live_invocation_globals.contains(g)) continue; | ||
| 230 | try self.dead_initializers.put(arena, info.initializer, {}); | ||
| 231 | } | ||
| 232 | } | ||
| 233 | |||
| 234 | /// For each function, extend the list of `invocation_globals` with the | ||
| 235 | /// invocation globals that ALL of its dependencies use. | ||
| 236 | fn resolveInvocationGlobalUsage(self: *ModuleInfo, arena: Allocator) !void { | ||
| 237 | var seen: std.bit_set.Dynamic = try .initEmpty(arena, self.functions.count()); | ||
| 238 | |||
| 239 | for (self.functions.keys()) |id| { | ||
| 240 | try self.resolveInvocationGlobalUsageStep(arena, id, &seen); | ||
| 241 | } | ||
| 242 | } | ||
| 243 | |||
| 244 | fn resolveInvocationGlobalUsageStep( | ||
| 245 | self: *ModuleInfo, | ||
| 246 | arena: Allocator, | ||
| 247 | id: ResultId, | ||
| 248 | seen: *std.bit_set.Dynamic, | ||
| 249 | ) !void { | ||
| 250 | const index = self.functions.getIndex(id) orelse { | ||
| 251 | log.err("function calls invalid function {f}", .{id}); | ||
| 252 | return error.InvalidId; | ||
| 253 | }; | ||
| 254 | |||
| 255 | if (seen.isSet(index)) { | ||
| 256 | return; | ||
| 257 | } | ||
| 258 | seen.set(index); | ||
| 259 | |||
| 260 | const info = &self.functions.values()[index]; | ||
| 261 | for (self.callees(id)) |callee| { | ||
| 262 | try self.resolveInvocationGlobalUsageStep(arena, callee, seen); | ||
| 263 | const callee_info = self.functions.get(callee).?; | ||
| 264 | for (callee_info.invocation_globals.keys()) |global| { | ||
| 265 | try info.invocation_globals.put(arena, global, {}); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | /// For each invocation global, populate and fully resolve the `dependencies` set. | ||
| 271 | /// This requires `resolveInvocationGlobalUsage()` to be already done. | ||
| 272 | fn resolveInvocationGlobalDependencies( | ||
| 273 | self: *ModuleInfo, | ||
| 274 | arena: Allocator, | ||
| 275 | ) !void { | ||
| 276 | var seen: std.bit_set.Dynamic = try .initEmpty(arena, self.invocation_globals.count()); | ||
| 277 | |||
| 278 | for (self.invocation_globals.keys()) |id| { | ||
| 279 | try self.resolveInvocationGlobalDependenciesStep(arena, id, &seen); | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | fn resolveInvocationGlobalDependenciesStep( | ||
| 284 | self: *ModuleInfo, | ||
| 285 | arena: Allocator, | ||
| 286 | id: ResultId, | ||
| 287 | seen: *std.bit_set.Dynamic, | ||
| 288 | ) !void { | ||
| 289 | const index = self.invocation_globals.getIndex(id) orelse { | ||
| 290 | log.err("invalid invocation global {f}", .{id}); | ||
| 291 | return error.InvalidId; | ||
| 292 | }; | ||
| 293 | |||
| 294 | if (seen.isSet(index)) { | ||
| 295 | return; | ||
| 296 | } | ||
| 297 | seen.set(index); | ||
| 298 | |||
| 299 | const info = &self.invocation_globals.values()[index]; | ||
| 300 | if (info.initializer == .none) { | ||
| 301 | return; | ||
| 302 | } | ||
| 303 | |||
| 304 | const initializer = self.functions.get(info.initializer) orelse { | ||
| 305 | log.err("invocation global {f} has invalid initializer {f}", .{ id, info.initializer }); | ||
| 306 | return error.InvalidId; | ||
| 307 | }; | ||
| 308 | |||
| 309 | for (initializer.invocation_globals.keys()) |dependency| { | ||
| 310 | if (dependency == id) { | ||
| 311 | // The set of invocation global dependencies includes the dependency itself, | ||
| 312 | // so we need to skip that case. | ||
| 313 | continue; | ||
| 314 | } | ||
| 315 | |||
| 316 | try info.dependencies.put(arena, dependency, {}); | ||
| 317 | try self.resolveInvocationGlobalDependenciesStep(arena, dependency, seen); | ||
| 318 | |||
| 319 | const dep_info = self.invocation_globals.getPtr(dependency).?; | ||
| 320 | |||
| 321 | for (dep_info.dependencies.keys()) |global| { | ||
| 322 | try info.dependencies.put(arena, global, {}); | ||
| 323 | } | ||
| 324 | } | ||
| 325 | } | ||
| 326 | }; | ||
| 327 | |||
| 328 | const ModuleBuilder = struct { | ||
| 329 | const FunctionType = struct { | ||
| 330 | return_type: ResultId, | ||
| 331 | param_types: []const ResultId, | ||
| 332 | |||
| 333 | const Context = struct { | ||
| 334 | pub fn hash(_: @This(), ty: FunctionType) u32 { | ||
| 335 | var hasher = std.hash.Wyhash.init(0); | ||
| 336 | hasher.update(std.mem.asBytes(&ty.return_type)); | ||
| 337 | hasher.update(std.mem.sliceAsBytes(ty.param_types)); | ||
| 338 | return @truncate(hasher.final()); | ||
| 339 | } | ||
| 340 | |||
| 341 | pub fn eql(_: @This(), a: FunctionType, b: FunctionType, _: usize) bool { | ||
| 342 | if (a.return_type != b.return_type) return false; | ||
| 343 | return std.mem.eql(ResultId, a.param_types, b.param_types); | ||
| 344 | } | ||
| 345 | }; | ||
| 346 | }; | ||
| 347 | |||
| 348 | const FunctionNewInfo = struct { | ||
| 349 | /// This is here just so that we don't need to allocate the new | ||
| 350 | /// param_types multiple times. | ||
| 351 | new_function_type: ResultId, | ||
| 352 | /// The first ID of the parameters for the invocation globals. | ||
| 353 | /// Each global is allocate here according to the index in | ||
| 354 | /// `ModuleInfo.Fn.invocation_globals`. | ||
| 355 | global_id_base: u32, | ||
| 356 | |||
| 357 | fn invocationGlobalId(self: FunctionNewInfo, index: usize) ResultId { | ||
| 358 | return @fromBackingInt(@intCast(self.global_id_base + @as(u32, @intCast(index)))); | ||
| 359 | } | ||
| 360 | }; | ||
| 361 | |||
| 362 | arena: Allocator, | ||
| 363 | section: Section, | ||
| 364 | /// The ID bound of the new module. | ||
| 365 | id_bound: u32, | ||
| 366 | /// The first ID of the new entry points. Entry points are allocated from | ||
| 367 | /// here according to their index in `info.entry_points`. | ||
| 368 | entry_point_new_id_base: u32, | ||
| 369 | /// OpName operands saved for invocation globals to re-emit. | ||
| 370 | global_names: std.array_hash_map.Auto(ResultId, []const Word) = .empty, | ||
| 371 | /// A set of all function types in the new program. SPIR-V mandates that these are unique, | ||
| 372 | /// and until a general type deduplication pass is programmed, we just handle it here via this. | ||
| 373 | function_types: std.array_hash_map.Custom(FunctionType, ResultId, FunctionType.Context, true) = .empty, | ||
| 374 | /// Maps functions to new information required for creating the module | ||
| 375 | function_new_info: std.array_hash_map.Auto(ResultId, FunctionNewInfo) = .empty, | ||
| 376 | /// Offset of the functions section in the new binary. | ||
| 377 | new_functions_section: ?usize, | ||
| 378 | |||
| 379 | fn init(arena: Allocator, binary: BinaryModule, info: ModuleInfo) !ModuleBuilder { | ||
| 380 | var self = ModuleBuilder{ | ||
| 381 | .arena = arena, | ||
| 382 | .section = .{}, | ||
| 383 | .id_bound = binary.id_bound, | ||
| 384 | .entry_point_new_id_base = undefined, | ||
| 385 | .new_functions_section = null, | ||
| 386 | }; | ||
| 387 | self.entry_point_new_id_base = @backingInt(self.allocIds(@intCast(info.entry_points.count()))); | ||
| 388 | return self; | ||
| 389 | } | ||
| 390 | |||
| 391 | fn allocId(self: *ModuleBuilder) ResultId { | ||
| 392 | return self.allocIds(1); | ||
| 393 | } | ||
| 394 | |||
| 395 | fn allocIds(self: *ModuleBuilder, n: u32) ResultId { | ||
| 396 | defer self.id_bound += n; | ||
| 397 | return @fromBackingInt(@intCast(self.id_bound)); | ||
| 398 | } | ||
| 399 | |||
| 400 | fn finalize(self: *ModuleBuilder, arena: Allocator, binary: *BinaryModule) !void { | ||
| 401 | binary.id_bound = self.id_bound; | ||
| 402 | binary.instructions = try arena.dupe(Word, self.section.instructions.items); | ||
| 403 | // Nothing is removed in this pass so we don't need to change any of the maps, | ||
| 404 | // just make sure the section is updated. | ||
| 405 | binary.functions_start = self.new_functions_section orelse binary.instructions.len; | ||
| 406 | } | ||
| 407 | |||
| 408 | fn emitGlobalNames(self: *ModuleBuilder, info: ModuleInfo) !void { | ||
| 409 | for (info.functions.keys(), info.functions.values()) |func, fn_info| { | ||
| 410 | if (info.dead_initializers.contains(func)) continue; | ||
| 411 | const new_info = self.function_new_info.get(func) orelse continue; | ||
| 412 | for (fn_info.invocation_globals.keys(), 0..) |global, i| { | ||
| 413 | if (!info.live_invocation_globals.contains(global)) continue; | ||
| 414 | const name_words = self.global_names.get(global) orelse continue; | ||
| 415 | const id = new_info.invocationGlobalId(i); | ||
| 416 | try self.section.emitRaw(self.arena, .OpName, 1 + name_words.len); | ||
| 417 | self.section.writeOperand(ResultId, id); | ||
| 418 | self.section.writeWords(name_words); | ||
| 419 | } | ||
| 420 | } | ||
| 421 | } | ||
| 422 | |||
| 423 | /// Process everything from `binary` up to the first function and emit it into the builder. | ||
| 424 | fn processPreamble(self: *ModuleBuilder, binary: BinaryModule, info: ModuleInfo) !void { | ||
| 425 | var emitted_global_names = false; | ||
| 426 | var it = binary.iterateInstructions(); | ||
| 427 | while (it.next()) |inst| { | ||
| 428 | if (!emitted_global_names) switch (inst.opcode.class()) { | ||
| 429 | .annotation, .type_declaration, .constant_creation => { | ||
| 430 | try self.emitGlobalNames(info); | ||
| 431 | emitted_global_names = true; | ||
| 432 | }, | ||
| 433 | else => {}, | ||
| 434 | }; | ||
| 435 | |||
| 436 | switch (inst.opcode) { | ||
| 437 | .OpName => { | ||
| 438 | const id: ResultId = @fromBackingInt(@intCast(inst.operands[0])); | ||
| 439 | if (info.invocation_globals.contains(id)) { | ||
| 440 | try self.global_names.put(self.arena, id, inst.operands[1..]); | ||
| 441 | continue; | ||
| 442 | } | ||
| 443 | if (info.dead_initializers.contains(id)) continue; | ||
| 444 | }, | ||
| 445 | .OpExtInstImport => { | ||
| 446 | const set_id: ResultId = @fromBackingInt(@intCast(inst.operands[0])); | ||
| 447 | const set = binary.ext_inst_map.get(set_id).?; | ||
| 448 | if (set == .zig) continue; | ||
| 449 | }, | ||
| 450 | .OpExtInst => { | ||
| 451 | const set_id: ResultId = @fromBackingInt(@intCast(inst.operands[2])); | ||
| 452 | const set_inst = inst.operands[3]; | ||
| 453 | const set = binary.ext_inst_map.get(set_id).?; | ||
| 454 | if (set == .zig and set_inst == 0) { | ||
| 455 | continue; | ||
| 456 | } | ||
| 457 | }, | ||
| 458 | .OpEntryPoint => { | ||
| 459 | const original_id: ResultId = @fromBackingInt(@intCast(inst.operands[1])); | ||
| 460 | const fn_info = info.functions.get(original_id).?; | ||
| 461 | if (fn_info.invocation_globals.count() > 0) { | ||
| 462 | const new_id_index = info.entry_points.getIndex(original_id).?; | ||
| 463 | const new_id: ResultId = @fromBackingInt(@intCast(self.entry_point_new_id_base + new_id_index)); | ||
| 464 | try self.section.emitRaw(self.arena, .OpEntryPoint, inst.operands.len); | ||
| 465 | self.section.writeWord(inst.operands[0]); | ||
| 466 | self.section.writeOperand(ResultId, new_id); | ||
| 467 | self.section.writeWords(inst.operands[2..]); | ||
| 468 | } else { | ||
| 469 | try self.section.emitRawInstruction(self.arena, inst.opcode, inst.operands); | ||
| 470 | } | ||
| 471 | continue; | ||
| 472 | }, | ||
| 473 | .OpExecutionMode, .OpExecutionModeId => { | ||
| 474 | const original_id: ResultId = @fromBackingInt(@intCast(inst.operands[0])); | ||
| 475 | const fn_info = info.functions.get(original_id).?; | ||
| 476 | if (fn_info.invocation_globals.count() > 0) { | ||
| 477 | const new_id_index = info.entry_points.getIndex(original_id).?; | ||
| 478 | const new_id: ResultId = @fromBackingInt(@intCast(self.entry_point_new_id_base + new_id_index)); | ||
| 479 | try self.section.emitRaw(self.arena, inst.opcode, inst.operands.len); | ||
| 480 | self.section.writeOperand(ResultId, new_id); | ||
| 481 | self.section.writeWords(inst.operands[1..]); | ||
| 482 | } else { | ||
| 483 | try self.section.emitRawInstruction(self.arena, inst.opcode, inst.operands); | ||
| 484 | } | ||
| 485 | continue; | ||
| 486 | }, | ||
| 487 | .OpTypeFunction => { | ||
| 488 | continue; | ||
| 489 | }, | ||
| 490 | .OpFunction => break, | ||
| 491 | else => {}, | ||
| 492 | } | ||
| 493 | |||
| 494 | try self.section.emitRawInstruction(self.arena, inst.opcode, inst.operands); | ||
| 495 | } | ||
| 496 | |||
| 497 | if (!emitted_global_names) { | ||
| 498 | try self.emitGlobalNames(info); | ||
| 499 | } | ||
| 500 | } | ||
| 501 | |||
| 502 | /// Derive new information required for further emitting this module, | ||
| 503 | fn deriveNewFnInfo(self: *ModuleBuilder, info: ModuleInfo) !void { | ||
| 504 | for (info.functions.keys(), info.functions.values()) |func, fn_info| { | ||
| 505 | const invocation_global_count = fn_info.invocation_globals.count(); | ||
| 506 | const new_param_types = try self.arena.alloc(ResultId, fn_info.param_types.len + invocation_global_count); | ||
| 507 | for (fn_info.invocation_globals.keys(), 0..) |global, i| { | ||
| 508 | new_param_types[i] = info.invocation_globals.get(global).?.ty; | ||
| 509 | } | ||
| 510 | @memcpy(new_param_types[invocation_global_count..], fn_info.param_types); | ||
| 511 | |||
| 512 | const new_type = try self.internFunctionType(fn_info.return_type, new_param_types); | ||
| 513 | try self.function_new_info.put(self.arena, func, .{ | ||
| 514 | .new_function_type = new_type, | ||
| 515 | .global_id_base = @backingInt(self.allocIds(@intCast(invocation_global_count))), | ||
| 516 | }); | ||
| 517 | } | ||
| 518 | } | ||
| 519 | |||
| 520 | /// Emit the new function types, which include the parameters for the invocation globals. | ||
| 521 | /// Currently, this function re-emits ALL function types to ensure that there are | ||
| 522 | /// no duplicates in the final program. | ||
| 523 | /// TODO: The above should be resolved by a generalized deduplication pass, and then | ||
| 524 | /// we only need to emit the new function pointers type here. | ||
| 525 | fn emitFunctionTypes(self: *ModuleBuilder, info: ModuleInfo) !void { | ||
| 526 | // TODO: Handle decorators. Function types usually don't have those | ||
| 527 | // though, but stuff like OpName could be a possibility. | ||
| 528 | |||
| 529 | // Entry points retain their old function type, so make sure to emit | ||
| 530 | // those in the `function_types` set. | ||
| 531 | for (info.entry_points.keys()) |func| { | ||
| 532 | const fn_info = info.functions.get(func).?; | ||
| 533 | _ = try self.internFunctionType(fn_info.return_type, fn_info.param_types); | ||
| 534 | } | ||
| 535 | |||
| 536 | for (self.function_types.keys(), self.function_types.values()) |fn_type, result_id| { | ||
| 537 | try self.section.emit(self.arena, .OpTypeFunction, .{ | ||
| 538 | .id_result = result_id, | ||
| 539 | .return_type = fn_type.return_type, | ||
| 540 | .id_ref_2 = fn_type.param_types, | ||
| 541 | }); | ||
| 542 | } | ||
| 543 | } | ||
| 544 | |||
| 545 | fn internFunctionType(self: *ModuleBuilder, return_type: ResultId, param_types: []const ResultId) !ResultId { | ||
| 546 | const entry = try self.function_types.getOrPut(self.arena, .{ | ||
| 547 | .return_type = return_type, | ||
| 548 | .param_types = param_types, | ||
| 549 | }); | ||
| 550 | |||
| 551 | if (!entry.found_existing) { | ||
| 552 | const new_id = self.allocId(); | ||
| 553 | entry.value_ptr.* = new_id; | ||
| 554 | } | ||
| 555 | |||
| 556 | return entry.value_ptr.*; | ||
| 557 | } | ||
| 558 | |||
| 559 | /// Rewrite the modules functions and emit them with the new parameter types. | ||
| 560 | fn rewriteFunctions( | ||
| 561 | self: *ModuleBuilder, | ||
| 562 | parser: *BinaryModule.Parser, | ||
| 563 | binary: BinaryModule, | ||
| 564 | info: ModuleInfo, | ||
| 565 | ) !void { | ||
| 566 | var result_id_offsets: std.ArrayList(u16) = .empty; | ||
| 567 | var operands: std.ArrayList(u32) = .empty; | ||
| 568 | |||
| 569 | var maybe_current_function: ?ResultId = null; | ||
| 570 | var skip_until_end: bool = false; | ||
| 571 | var it = binary.iterateInstructionsFrom(binary.functions_start); | ||
| 572 | self.new_functions_section = self.section.instructions.items.len; | ||
| 573 | while (it.next()) |inst| { | ||
| 574 | if (skip_until_end) { | ||
| 575 | if (inst.opcode == .OpFunctionEnd) skip_until_end = false; | ||
| 576 | continue; | ||
| 577 | } | ||
| 578 | result_id_offsets.items.len = 0; | ||
| 579 | try parser.parseInstructionResultIds(binary, inst, &result_id_offsets); | ||
| 580 | |||
| 581 | operands.items.len = 0; | ||
| 582 | try operands.appendSlice(self.arena, inst.operands); | ||
| 583 | |||
| 584 | // Replace the result-ids with the global's new result-id if required. | ||
| 585 | for (result_id_offsets.items) |off| { | ||
| 586 | const result_id: ResultId = @fromBackingInt(@intCast(operands.items[off])); | ||
| 587 | if (info.invocation_globals.contains(result_id)) { | ||
| 588 | const func = maybe_current_function.?; | ||
| 589 | const new_info = self.function_new_info.get(func).?; | ||
| 590 | const fn_info = info.functions.get(func).?; | ||
| 591 | const index = fn_info.invocation_globals.getIndex(result_id).?; | ||
| 592 | operands.items[off] = @backingInt(new_info.invocationGlobalId(index)); | ||
| 593 | } | ||
| 594 | } | ||
| 595 | |||
| 596 | switch (inst.opcode) { | ||
| 597 | .OpFunction => { | ||
| 598 | // Re-declare the function with the new parameters. | ||
| 599 | const func: ResultId = @fromBackingInt(@intCast(operands.items[1])); | ||
| 600 | if (info.dead_initializers.contains(func)) { | ||
| 601 | skip_until_end = true; | ||
| 602 | continue; | ||
| 603 | } | ||
| 604 | const fn_info = info.functions.get(func).?; | ||
| 605 | const new_info = self.function_new_info.get(func).?; | ||
| 606 | |||
| 607 | try self.section.emitRaw(self.arena, .OpFunction, 4); | ||
| 608 | self.section.writeOperand(ResultId, fn_info.return_type); | ||
| 609 | self.section.writeOperand(ResultId, func); | ||
| 610 | self.section.writeWord(operands.items[2]); | ||
| 611 | self.section.writeOperand(ResultId, new_info.new_function_type); | ||
| 612 | |||
| 613 | // Emit the OpFunctionParameters for the invocation globals. The functions | ||
| 614 | // actual parameters are emitted unchanged from their original form, so | ||
| 615 | // we don't need to handle those here. | ||
| 616 | |||
| 617 | for (fn_info.invocation_globals.keys(), 0..) |global, index| { | ||
| 618 | const ty = info.invocation_globals.get(global).?.ty; | ||
| 619 | const id = new_info.invocationGlobalId(index); | ||
| 620 | try self.section.emit(self.arena, .OpFunctionParameter, .{ | ||
| 621 | .id_result_type = ty, | ||
| 622 | .id_result = id, | ||
| 623 | }); | ||
| 624 | } | ||
| 625 | |||
| 626 | maybe_current_function = func; | ||
| 627 | }, | ||
| 628 | .OpFunctionCall => { | ||
| 629 | // Add the required invocation globals to the function's new parameter list. | ||
| 630 | const caller = maybe_current_function.?; | ||
| 631 | const callee: ResultId = @fromBackingInt(@intCast(operands.items[2])); | ||
| 632 | const caller_info = info.functions.get(caller).?; | ||
| 633 | const callee_info = info.functions.get(callee).?; | ||
| 634 | const caller_new_info = self.function_new_info.get(caller).?; | ||
| 635 | const total_params = callee_info.invocation_globals.count() + callee_info.param_types.len; | ||
| 636 | |||
| 637 | try self.section.emitRaw(self.arena, .OpFunctionCall, 3 + total_params); | ||
| 638 | self.section.writeWord(operands.items[0]); // Copy result type-id | ||
| 639 | self.section.writeWord(operands.items[1]); // Copy result-id | ||
| 640 | self.section.writeOperand(ResultId, callee); | ||
| 641 | |||
| 642 | // Add the new arguments | ||
| 643 | for (callee_info.invocation_globals.keys()) |global| { | ||
| 644 | const caller_global_index = caller_info.invocation_globals.getIndex(global).?; | ||
| 645 | const id = caller_new_info.invocationGlobalId(caller_global_index); | ||
| 646 | self.section.writeOperand(ResultId, id); | ||
| 647 | } | ||
| 648 | |||
| 649 | // Add the original arguments | ||
| 650 | self.section.writeWords(operands.items[3..]); | ||
| 651 | }, | ||
| 652 | else => { | ||
| 653 | try self.section.emitRawInstruction(self.arena, inst.opcode, operands.items); | ||
| 654 | }, | ||
| 655 | } | ||
| 656 | } | ||
| 657 | } | ||
| 658 | |||
| 659 | fn emitNewEntryPoints(self: *ModuleBuilder, info: ModuleInfo) !void { | ||
| 660 | const arena = self.arena; | ||
| 661 | var all_function_invocation_globals: std.array_hash_map.Auto(ResultId, void) = .empty; | ||
| 662 | |||
| 663 | for (info.entry_points.keys(), 0..) |func, entry_point_index| { | ||
| 664 | const fn_info = info.functions.get(func).?; | ||
| 665 | if (fn_info.invocation_globals.count() == 0) continue; | ||
| 666 | const ep_id: ResultId = @fromBackingInt(@intCast(self.entry_point_new_id_base + @as(u32, @intCast(entry_point_index)))); | ||
| 667 | const fn_type = self.function_types.get(.{ | ||
| 668 | .return_type = fn_info.return_type, | ||
| 669 | .param_types = fn_info.param_types, | ||
| 670 | }).?; | ||
| 671 | |||
| 672 | try self.section.emit(arena, .OpFunction, .{ | ||
| 673 | .id_result_type = fn_info.return_type, | ||
| 674 | .id_result = ep_id, | ||
| 675 | .function_control = .{}, // TODO: Copy the attributes from the original function maybe? | ||
| 676 | .function_type = fn_type, | ||
| 677 | }); | ||
| 678 | |||
| 679 | // Emit OpFunctionParameter instructions for the original kernel's parameters. | ||
| 680 | const params_id_base: u32 = @backingInt(self.allocIds(@intCast(fn_info.param_types.len))); | ||
| 681 | for (fn_info.param_types, 0..) |param_type, i| { | ||
| 682 | const id: ResultId = @fromBackingInt(@intCast(params_id_base + @as(u32, @intCast(i)))); | ||
| 683 | try self.section.emit(arena, .OpFunctionParameter, .{ | ||
| 684 | .id_result_type = param_type, | ||
| 685 | .id_result = id, | ||
| 686 | }); | ||
| 687 | } | ||
| 688 | |||
| 689 | try self.section.emit(arena, .OpLabel, .{ | ||
| 690 | .id_result = self.allocId(), | ||
| 691 | }); | ||
| 692 | |||
| 693 | // Besides the IDs of the main kernel, we also need the | ||
| 694 | // dependencies of the globals. | ||
| 695 | // Just quickly construct that set here. | ||
| 696 | all_function_invocation_globals.clearRetainingCapacity(); | ||
| 697 | for (fn_info.invocation_globals.keys()) |global| { | ||
| 698 | try all_function_invocation_globals.put(arena, global, {}); | ||
| 699 | const global_info = info.invocation_globals.get(global).?; | ||
| 700 | for (global_info.dependencies.keys()) |dependency| { | ||
| 701 | try all_function_invocation_globals.put(arena, dependency, {}); | ||
| 702 | } | ||
| 703 | } | ||
| 704 | |||
| 705 | // Declare the IDs of the invocation globals. | ||
| 706 | const global_id_base: u32 = @backingInt(self.allocIds(@intCast(all_function_invocation_globals.count()))); | ||
| 707 | for (all_function_invocation_globals.keys(), 0..) |global, i| { | ||
| 708 | const global_info = info.invocation_globals.get(global).?; | ||
| 709 | |||
| 710 | const id: ResultId = @fromBackingInt(@intCast(global_id_base + @as(u32, @intCast(i)))); | ||
| 711 | try self.section.emit(arena, .OpVariable, .{ | ||
| 712 | .id_result_type = global_info.ty, | ||
| 713 | .id_result = id, | ||
| 714 | .storage_class = .function, | ||
| 715 | .initializer = null, | ||
| 716 | }); | ||
| 717 | } | ||
| 718 | |||
| 719 | // Call initializers for invocation globals that need it | ||
| 720 | for (all_function_invocation_globals.keys()) |global| { | ||
| 721 | const global_info = info.invocation_globals.get(global).?; | ||
| 722 | if (global_info.initializer == .none) continue; | ||
| 723 | |||
| 724 | const initializer_info = info.functions.get(global_info.initializer).?; | ||
| 725 | assert(initializer_info.param_types.len == 0); | ||
| 726 | |||
| 727 | try self.callWithGlobalsAndLinearParams( | ||
| 728 | &all_function_invocation_globals, | ||
| 729 | global_info.initializer, | ||
| 730 | initializer_info, | ||
| 731 | global_id_base, | ||
| 732 | undefined, | ||
| 733 | ); | ||
| 734 | } | ||
| 735 | |||
| 736 | // Call the main kernel entry | ||
| 737 | try self.callWithGlobalsAndLinearParams( | ||
| 738 | &all_function_invocation_globals, | ||
| 739 | func, | ||
| 740 | fn_info, | ||
| 741 | global_id_base, | ||
| 742 | params_id_base, | ||
| 743 | ); | ||
| 744 | |||
| 745 | try self.section.emit(arena, .OpReturn, {}); | ||
| 746 | try self.section.emit(arena, .OpFunctionEnd, {}); | ||
| 747 | } | ||
| 748 | } | ||
| 749 | |||
| 750 | fn callWithGlobalsAndLinearParams( | ||
| 751 | self: *ModuleBuilder, | ||
| 752 | all_globals: *const std.array_hash_map.Auto(ResultId, void), | ||
| 753 | func: ResultId, | ||
| 754 | callee_info: ModuleInfo.Fn, | ||
| 755 | global_id_base: u32, | ||
| 756 | params_id_base: u32, | ||
| 757 | ) !void { | ||
| 758 | const total_arguments = callee_info.invocation_globals.count() + callee_info.param_types.len; | ||
| 759 | try self.section.emitRaw(self.arena, .OpFunctionCall, 3 + total_arguments); | ||
| 760 | self.section.writeOperand(ResultId, callee_info.return_type); | ||
| 761 | self.section.writeOperand(ResultId, self.allocId()); | ||
| 762 | self.section.writeOperand(ResultId, func); | ||
| 763 | |||
| 764 | // Add the invocation globals | ||
| 765 | for (callee_info.invocation_globals.keys()) |global| { | ||
| 766 | const index = all_globals.getIndex(global).?; | ||
| 767 | const id: ResultId = @fromBackingInt(@intCast(global_id_base + @as(u32, @intCast(index)))); | ||
| 768 | self.section.writeOperand(ResultId, id); | ||
| 769 | } | ||
| 770 | |||
| 771 | // Add the arguments | ||
| 772 | for (0..callee_info.param_types.len) |index| { | ||
| 773 | const id: ResultId = @fromBackingInt(@intCast(params_id_base + @as(u32, @intCast(index)))); | ||
| 774 | self.section.writeOperand(ResultId, id); | ||
| 775 | } | ||
| 776 | } | ||
| 777 | }; | ||
| 778 | |||
| 779 | pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Progress.Node) !void { | ||
| 780 | const sub_node = progress.start("Lower invocation globals", 6); | ||
| 781 | defer sub_node.end(); | ||
| 782 | |||
| 783 | var arena_state = std.heap.ArenaAllocator.init(parser.gpa); | ||
| 784 | defer arena_state.deinit(); | ||
| 785 | const arena = arena_state.allocator(); | ||
| 786 | |||
| 787 | var info = try ModuleInfo.parse(arena, parser, binary.*); | ||
| 788 | try info.resolve(arena); | ||
| 789 | |||
| 790 | var builder = try ModuleBuilder.init(arena, binary.*, info); | ||
| 791 | sub_node.completeOne(); | ||
| 792 | try builder.deriveNewFnInfo(info); | ||
| 793 | sub_node.completeOne(); | ||
| 794 | try builder.processPreamble(binary.*, info); | ||
| 795 | sub_node.completeOne(); | ||
| 796 | try builder.emitFunctionTypes(info); | ||
| 797 | sub_node.completeOne(); | ||
| 798 | try builder.rewriteFunctions(parser, binary.*, info); | ||
| 799 | sub_node.completeOne(); | ||
| 800 | try builder.emitNewEntryPoints(info); | ||
| 801 | sub_node.completeOne(); | ||
| 802 | try builder.finalize(parser.gpa, binary); | ||
| 803 | } | ||
src/link/SpirV/prune_unused.zig deleted-257| ... | @@ -1,257 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const BinaryModule = @import("BinaryModule.zig"); | ||
| 3 | const spec = @import("../../codegen/spirv/spec.zig"); | ||
| 4 | const Opcode = spec.Opcode; | ||
| 5 | const ResultId = spec.Id; | ||
| 6 | const Word = spec.Word; | ||
| 7 | |||
| 8 | pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void { | ||
| 9 | const gpa = parser.gpa; | ||
| 10 | |||
| 11 | // map result-id → index in id_offsets for preamble instructions and function headers | ||
| 12 | var id_to_index: std.AutoHashMapUnmanaged(ResultId, u32) = .empty; | ||
| 13 | defer id_to_index.deinit(gpa); | ||
| 14 | |||
| 15 | // for each indexed instruction, its offset in the binary | ||
| 16 | var code_offsets: std.ArrayList(usize) = .empty; | ||
| 17 | defer code_offsets.deinit(gpa); | ||
| 18 | |||
| 19 | var it = binary.iterateInstructions(); | ||
| 20 | while (it.next()) |inst| { | ||
| 21 | const inst_spec = parser.getInstSpec(inst.opcode) orelse continue; | ||
| 22 | const result_id = getResultId(inst, inst_spec) orelse continue; | ||
| 23 | |||
| 24 | // only index preamble instructions and function headers | ||
| 25 | if (inst.offset < binary.functions_start or inst.opcode == .OpFunction) { | ||
| 26 | const index: u32 = @intCast(code_offsets.items.len); | ||
| 27 | try id_to_index.put(gpa, result_id, index); | ||
| 28 | try code_offsets.append(gpa, inst.offset); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | var alive: std.bit_set.Dynamic = try .initEmpty(gpa, code_offsets.items.len); | ||
| 33 | defer alive.deinit(gpa); | ||
| 34 | |||
| 35 | var id_offset_buf: std.ArrayList(u16) = .empty; | ||
| 36 | defer id_offset_buf.deinit(gpa); | ||
| 37 | |||
| 38 | // Mark non-prunable preamble instructions alive | ||
| 39 | // OpExtInst in the preamble is metadata (e.g. Zig error info) that references | ||
| 40 | // functions. skip it here so it doesn't root dead functions alive. | ||
| 41 | // These instructions are handled as prunable during the rewrite phase. | ||
| 42 | it = binary.iterateInstructions(); | ||
| 43 | while (it.next()) |inst| { | ||
| 44 | if (inst.offset >= binary.functions_start) break; | ||
| 45 | if (canPrune(inst.opcode) or inst.opcode == .OpExtInst) continue; | ||
| 46 | try markAlive( | ||
| 47 | parser, | ||
| 48 | binary.*, | ||
| 49 | inst, | ||
| 50 | &alive, | ||
| 51 | &id_to_index, | ||
| 52 | &code_offsets, | ||
| 53 | &id_offset_buf, | ||
| 54 | ); | ||
| 55 | } | ||
| 56 | |||
| 57 | // mark functions with LinkageAttributes Export alive | ||
| 58 | it = binary.iterateInstructions(); | ||
| 59 | while (it.next()) |inst| { | ||
| 60 | if (inst.offset >= binary.functions_start) break; | ||
| 61 | if (inst.opcode == .OpDecorate and inst.operands.len >= 2 and | ||
| 62 | inst.operands[1] == @backingInt(spec.Decoration.linkage_attributes)) | ||
| 63 | { | ||
| 64 | // Last word after the string is the linkage type; Export = 0. | ||
| 65 | if (inst.operands[inst.operands.len - 1] == @backingInt(spec.LinkageType.@"export")) { | ||
| 66 | const target: ResultId = @fromBackingInt(@intCast(inst.operands[0])); | ||
| 67 | if (id_to_index.get(target)) |index| { | ||
| 68 | alive.set(index); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | // mark alive functions' contents alive | ||
| 75 | it = binary.iterateInstructionsFrom(binary.functions_start); | ||
| 76 | while (it.next()) |inst| { | ||
| 77 | if (inst.opcode == .OpFunction) { | ||
| 78 | const inst_spec = parser.getInstSpec(inst.opcode) orelse continue; | ||
| 79 | const result_id = getResultId(inst, inst_spec) orelse continue; | ||
| 80 | const index = id_to_index.get(result_id) orelse continue; | ||
| 81 | if (!alive.isSet(index)) { | ||
| 82 | // skip dead function | ||
| 83 | while (it.next()) |inner| { | ||
| 84 | if (inner.opcode == .OpFunctionEnd) break; | ||
| 85 | } | ||
| 86 | continue; | ||
| 87 | } | ||
| 88 | |||
| 89 | // mark the function's type operands alive | ||
| 90 | try markAlive(parser, binary.*, inst, &alive, &id_to_index, &code_offsets, &id_offset_buf); | ||
| 91 | continue; | ||
| 92 | } | ||
| 93 | |||
| 94 | // mark operands of alive function contents | ||
| 95 | try markAlive(parser, binary.*, inst, &alive, &id_to_index, &code_offsets, &id_offset_buf); | ||
| 96 | } | ||
| 97 | |||
| 98 | // rewrite | ||
| 99 | var new_words: std.ArrayList(Word) = .empty; | ||
| 100 | defer new_words.deinit(gpa); | ||
| 101 | try new_words.ensureTotalCapacity(gpa, binary.instructions.len); | ||
| 102 | |||
| 103 | var new_functions_start: ?usize = null; | ||
| 104 | |||
| 105 | it = binary.iterateInstructions(); | ||
| 106 | while (it.next()) |inst| { | ||
| 107 | if (inst.offset >= binary.functions_start and inst.opcode == .OpFunction) { | ||
| 108 | const inst_spec = parser.getInstSpec(inst.opcode) orelse continue; | ||
| 109 | const result_id = getResultId(inst, inst_spec) orelse continue; | ||
| 110 | const index = id_to_index.get(result_id) orelse continue; | ||
| 111 | if (!alive.isSet(index)) { | ||
| 112 | while (it.next()) |inner| { | ||
| 113 | if (inner.opcode == .OpFunctionEnd) break; | ||
| 114 | } | ||
| 115 | continue; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | const is_prunable = canPrune(inst.opcode) or | ||
| 120 | (inst.opcode == .OpExtInst and inst.offset < binary.functions_start); | ||
| 121 | if (is_prunable) { | ||
| 122 | const inst_spec = parser.getInstSpec(inst.opcode) orelse { | ||
| 123 | appendInst(&new_words, binary, inst, &new_functions_start); | ||
| 124 | continue; | ||
| 125 | }; | ||
| 126 | |||
| 127 | if (getResultId(inst, inst_spec)) |result_id| { | ||
| 128 | const index = id_to_index.get(result_id) orelse { | ||
| 129 | appendInst(&new_words, binary, inst, &new_functions_start); | ||
| 130 | continue; | ||
| 131 | }; | ||
| 132 | if (!alive.isSet(index)) continue; | ||
| 133 | } else { | ||
| 134 | // annotation-style: emit only if the target id is alive | ||
| 135 | if (inst.operands.len > 0) { | ||
| 136 | const target: ResultId = @fromBackingInt(@intCast(inst.operands[0])); | ||
| 137 | if (id_to_index.get(target)) |idx| { | ||
| 138 | if (!alive.isSet(idx)) continue; | ||
| 139 | } else continue; | ||
| 140 | } | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | appendInst(&new_words, binary, inst, &new_functions_start); | ||
| 145 | } | ||
| 146 | |||
| 147 | { | ||
| 148 | var to_remove: std.ArrayList(ResultId) = .empty; | ||
| 149 | defer to_remove.deinit(gpa); | ||
| 150 | |||
| 151 | var ext_it = binary.ext_inst_map.iterator(); | ||
| 152 | while (ext_it.next()) |entry| { | ||
| 153 | if (id_to_index.get(entry.key_ptr.*)) |index| { | ||
| 154 | if (!alive.isSet(index)) try to_remove.append(gpa, entry.key_ptr.*); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | for (to_remove.items) |id| _ = binary.ext_inst_map.remove(id); | ||
| 158 | |||
| 159 | to_remove.items.len = 0; | ||
| 160 | var arith_it = binary.arith_type_width.iterator(); | ||
| 161 | while (arith_it.next()) |entry| { | ||
| 162 | if (id_to_index.get(entry.key_ptr.*)) |index| { | ||
| 163 | if (!alive.isSet(index)) try to_remove.append(gpa, entry.key_ptr.*); | ||
| 164 | } | ||
| 165 | } | ||
| 166 | for (to_remove.items) |id| _ = binary.arith_type_width.remove(id); | ||
| 167 | } | ||
| 168 | |||
| 169 | binary.instructions = try gpa.dupe(Word, new_words.items); | ||
| 170 | binary.functions_start = new_functions_start orelse new_words.items.len; | ||
| 171 | } | ||
| 172 | |||
| 173 | fn appendInst( | ||
| 174 | new_words: *std.ArrayList(Word), | ||
| 175 | binary: *const BinaryModule, | ||
| 176 | inst: BinaryModule.Instruction, | ||
| 177 | new_functions_start: *?usize, | ||
| 178 | ) void { | ||
| 179 | if (new_functions_start.* == null and inst.offset >= binary.functions_start) { | ||
| 180 | new_functions_start.* = new_words.items.len; | ||
| 181 | } | ||
| 182 | const len = @as(usize, binary.instructions[inst.offset] >> 16); | ||
| 183 | new_words.appendSliceAssumeCapacity(binary.instructions[inst.offset..][0..len]); | ||
| 184 | } | ||
| 185 | |||
| 186 | fn markAlive( | ||
| 187 | parser: *BinaryModule.Parser, | ||
| 188 | binary: BinaryModule, | ||
| 189 | inst: BinaryModule.Instruction, | ||
| 190 | alive: *std.bit_set.Dynamic, | ||
| 191 | id_to_index: *const std.AutoHashMapUnmanaged(ResultId, u32), | ||
| 192 | code_offsets: *const std.ArrayList(usize), | ||
| 193 | id_offset_buf: *std.ArrayList(u16), | ||
| 194 | ) !void { | ||
| 195 | const start = id_offset_buf.items.len; | ||
| 196 | try parser.parseInstructionResultIds(binary, inst, id_offset_buf); | ||
| 197 | const end = id_offset_buf.items.len; | ||
| 198 | |||
| 199 | var i = start; | ||
| 200 | while (i < end) : (i += 1) { | ||
| 201 | const off = id_offset_buf.items[i]; | ||
| 202 | const id: ResultId = @fromBackingInt(@intCast(inst.operands[off])); | ||
| 203 | const index = id_to_index.get(id) orelse continue; | ||
| 204 | if (alive.isSet(index)) continue; | ||
| 205 | alive.set(index); | ||
| 206 | |||
| 207 | const offset = code_offsets.items[index]; | ||
| 208 | const ref_inst = BinaryModule.Instruction{ | ||
| 209 | .opcode = @fromBackingInt(@intCast(binary.instructions[offset] & 0xFFFF)), | ||
| 210 | .offset = offset, | ||
| 211 | .operands = blk: { | ||
| 212 | const l = binary.instructions[offset] >> 16; | ||
| 213 | break :blk binary.instructions[offset..][1..l]; | ||
| 214 | }, | ||
| 215 | }; | ||
| 216 | |||
| 217 | if (ref_inst.opcode == .OpFunction) { | ||
| 218 | var fn_it = binary.iterateInstructionsFrom(ref_inst.offset); | ||
| 219 | _ = fn_it.next(); | ||
| 220 | while (fn_it.next()) |fn_inst| { | ||
| 221 | if (fn_inst.opcode == .OpFunctionEnd) break; | ||
| 222 | try markAlive(parser, binary, fn_inst, alive, id_to_index, code_offsets, id_offset_buf); | ||
| 223 | } | ||
| 224 | try markAlive(parser, binary, ref_inst, alive, id_to_index, code_offsets, id_offset_buf); | ||
| 225 | } else { | ||
| 226 | try markAlive(parser, binary, ref_inst, alive, id_to_index, code_offsets, id_offset_buf); | ||
| 227 | } | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | fn getResultId(inst: BinaryModule.Instruction, inst_spec: spec.Instruction) ?ResultId { | ||
| 232 | for (0..@min(2, inst_spec.operands.len)) |i| { | ||
| 233 | if (inst_spec.operands[i].kind == .id_result) { | ||
| 234 | if (i < inst.operands.len) return @fromBackingInt(@intCast(inst.operands[i])); | ||
| 235 | } | ||
| 236 | } | ||
| 237 | return null; | ||
| 238 | } | ||
| 239 | |||
| 240 | fn canPrune(op: Opcode) bool { | ||
| 241 | return switch (op.class()) { | ||
| 242 | .type_declaration, | ||
| 243 | .constant_creation, | ||
| 244 | .annotation, | ||
| 245 | => true, | ||
| 246 | else => switch (op) { | ||
| 247 | .OpFunction, | ||
| 248 | .OpUndef, | ||
| 249 | .OpString, | ||
| 250 | .OpName, | ||
| 251 | .OpMemberName, | ||
| 252 | .OpVariable, | ||
| 253 | => true, | ||
| 254 | else => false, | ||
| 255 | }, | ||
| 256 | }; | ||
| 257 | } | ||
src/link/Spirv.zig created+252| ... | @@ -0,0 +1,252 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Allocator = std.mem.Allocator; | ||
| 3 | const Path = std.Build.Cache.Path; | ||
| 4 | const assert = std.debug.assert; | ||
| 5 | const Zcu = @import("../Zcu.zig"); | ||
| 6 | const InternPool = @import("../InternPool.zig"); | ||
| 7 | const Compilation = @import("../Compilation.zig"); | ||
| 8 | const link = @import("../link.zig"); | ||
| 9 | const codegen = @import("../codegen.zig"); | ||
| 10 | const CodeGen = @import("../codegen/spirv/CodeGen.zig"); | ||
| 11 | const Mir = @import("../codegen/spirv/Mir.zig"); | ||
| 12 | const spec = @import("../codegen/spirv/spec.zig"); | ||
| 13 | const Section = @import("../codegen/spirv/Section.zig"); | ||
| 14 | const Flush = @import("Spirv/Flush.zig"); | ||
| 15 | const Word = spec.Word; | ||
| 16 | const Linker = @This(); | ||
| 17 | |||
| 18 | base: link.File, | ||
| 19 | mirs: std.array_hash_map.Auto(InternPool.Nav.Index, Mir), | ||
| 20 | objects: std.ArrayList(Object), | ||
| 21 | exports: std.array_hash_map.Auto(InternPool.Nav.Index, InternPool.NullTerminatedString), | ||
| 22 | |||
| 23 | pub const Object = struct { path: Path, mir: Mir }; | ||
| 24 | |||
| 25 | pub fn createEmpty( | ||
| 26 | arena: Allocator, | ||
| 27 | comp: *Compilation, | ||
| 28 | emit: Path, | ||
| 29 | options: link.File.OpenOptions, | ||
| 30 | ) !*Linker { | ||
| 31 | const file = try emit.root_dir.handle.createFile(comp.io, emit.sub_path, .{}); | ||
| 32 | errdefer file.close(comp.io); | ||
| 33 | const linker = try arena.create(Linker); | ||
| 34 | linker.* = .{ | ||
| 35 | .base = .{ | ||
| 36 | .tag = .spirv, | ||
| 37 | .comp = comp, | ||
| 38 | .emit = emit, | ||
| 39 | .gc_sections = options.gc_sections orelse false, | ||
| 40 | .print_gc_sections = options.print_gc_sections, | ||
| 41 | .stack_size = options.stack_size orelse 0, | ||
| 42 | .allow_shlib_undefined = options.allow_shlib_undefined orelse false, | ||
| 43 | .file = file, | ||
| 44 | .build_id = options.build_id, | ||
| 45 | }, | ||
| 46 | .mirs = .empty, | ||
| 47 | .objects = .empty, | ||
| 48 | .exports = .empty, | ||
| 49 | }; | ||
| 50 | return linker; | ||
| 51 | } | ||
| 52 | |||
| 53 | pub fn open( | ||
| 54 | arena: Allocator, | ||
| 55 | comp: *Compilation, | ||
| 56 | emit: Path, | ||
| 57 | options: link.File.OpenOptions, | ||
| 58 | ) !*Linker { | ||
| 59 | return createEmpty(arena, comp, emit, options); | ||
| 60 | } | ||
| 61 | |||
| 62 | pub fn deinit(linker: *Linker) void { | ||
| 63 | const gpa = linker.base.comp.gpa; | ||
| 64 | for (linker.mirs.values()) |*mir| mir.deinit(gpa); | ||
| 65 | linker.mirs.deinit(gpa); | ||
| 66 | for (linker.objects.items) |*object| object.mir.deinit(gpa); | ||
| 67 | linker.objects.deinit(gpa); | ||
| 68 | linker.exports.deinit(gpa); | ||
| 69 | } | ||
| 70 | |||
| 71 | pub fn loadInput(linker: *Linker, input: link.Input) !void { | ||
| 72 | const diags = &linker.base.comp.link_diags; | ||
| 73 | switch (input) { | ||
| 74 | .object => |obj| try linker.loadObject(obj), | ||
| 75 | else => return diags.fail("unsupported link input for SPIR-V target", .{}), | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | fn loadObject(linker: *Linker, obj: link.Input.Object) !void { | ||
| 80 | const comp = linker.base.comp; | ||
| 81 | const gpa = comp.gpa; | ||
| 82 | const io = comp.io; | ||
| 83 | const diags = &comp.link_diags; | ||
| 84 | |||
| 85 | const file_size = obj.file.length(io) catch |err| { | ||
| 86 | return diags.failParse(obj.path, "failed to get file size: {t}", .{err}); | ||
| 87 | }; | ||
| 88 | if (file_size % @sizeOf(Word) != 0) { | ||
| 89 | return diags.failParse(obj.path, "file size is not a multiple of {d}", .{ | ||
| 90 | @sizeOf(Word), | ||
| 91 | }); | ||
| 92 | } | ||
| 93 | |||
| 94 | const words = try gpa.alloc(Word, @intCast(file_size / @sizeOf(Word))); | ||
| 95 | defer gpa.free(words); | ||
| 96 | const bytes = std.mem.sliceAsBytes(words); | ||
| 97 | const n_read = obj.file.readPositionalAll(io, bytes, 0) catch |err| { | ||
| 98 | return diags.failParse(obj.path, "failed to read: {t}", .{err}); | ||
| 99 | }; | ||
| 100 | |||
| 101 | if (n_read != bytes.len) { | ||
| 102 | return diags.failParse(obj.path, "incomplete read", .{}); | ||
| 103 | } | ||
| 104 | const header_len = @sizeOf(spec.Header) / @sizeOf(Word); | ||
| 105 | if (words.len < header_len) { | ||
| 106 | return diags.failParse(obj.path, "header too small", .{}); | ||
| 107 | } | ||
| 108 | const header = std.mem.bytesToValue(spec.Header, std.mem.sliceAsBytes(words[0..header_len])); | ||
| 109 | if (header.magic != spec.magic_number) { | ||
| 110 | return diags.failParse(obj.path, "invalid magic number", .{}); | ||
| 111 | } | ||
| 112 | if (header.id_bound == 0) { | ||
| 113 | return diags.failParse(obj.path, "invalid id bound", .{}); | ||
| 114 | } | ||
| 115 | |||
| 116 | const instructions = words[header_len..]; | ||
| 117 | var module = Section.fromWords(gpa, instructions, header.id_bound) catch |err| switch (err) { | ||
| 118 | error.OutOfMemory => |e| return e, | ||
| 119 | else => return diags.failParse(obj.path, "failed to parse instructions: {t}", .{err}), | ||
| 120 | }; | ||
| 121 | errdefer module.deinit(gpa); | ||
| 122 | try linker.objects.append(gpa, .{ .path = obj.path, .mir = .{ | ||
| 123 | .id_bound = header.id_bound, | ||
| 124 | .module = module, | ||
| 125 | .nav_refs = &.{}, | ||
| 126 | .externs = &.{}, | ||
| 127 | .entry_points = &.{}, | ||
| 128 | } }); | ||
| 129 | } | ||
| 130 | |||
| 131 | pub fn updateFunc( | ||
| 132 | linker: *Linker, | ||
| 133 | pt: Zcu.PerThread, | ||
| 134 | func_index: InternPool.Index, | ||
| 135 | mir: *codegen.AnyMir, | ||
| 136 | ) !void { | ||
| 137 | const gpa = linker.base.comp.gpa; | ||
| 138 | const nav_index = pt.zcu.funcInfo(func_index).owner_nav; | ||
| 139 | const gop = try linker.mirs.getOrPut(gpa, nav_index); | ||
| 140 | if (gop.found_existing) gop.value_ptr.deinit(gpa); | ||
| 141 | gop.value_ptr.* = mir.spirv; | ||
| 142 | mir.spirv = .{ | ||
| 143 | .id_bound = 1, | ||
| 144 | .module = .empty, | ||
| 145 | .nav_refs = &.{}, | ||
| 146 | .externs = &.{}, | ||
| 147 | .entry_points = &.{}, | ||
| 148 | }; | ||
| 149 | } | ||
| 150 | |||
| 151 | pub fn updateNav( | ||
| 152 | linker: *Linker, | ||
| 153 | pt: Zcu.PerThread, | ||
| 154 | nav_index: InternPool.Nav.Index, | ||
| 155 | ) link.Error!void { | ||
| 156 | const gpa = linker.base.comp.gpa; | ||
| 157 | const ip = &pt.zcu.intern_pool; | ||
| 158 | const resolved = ip.getNav(nav_index).resolved.?; | ||
| 159 | switch (ip.indexToKey(resolved.value)) { | ||
| 160 | .func => return, | ||
| 161 | .@"extern" => |@"extern"| if (ip.isFunctionType(@"extern".ty)) return, | ||
| 162 | else => if (!resolved.@"const") assert(resolved.@"threadlocal") else return, | ||
| 163 | } | ||
| 164 | var mir = CodeGen.generateNav(pt, nav_index) catch |err| switch (err) { | ||
| 165 | error.AlreadyReported => return, | ||
| 166 | else => |e| return e, | ||
| 167 | }; | ||
| 168 | errdefer mir.deinit(gpa); | ||
| 169 | const gop = try linker.mirs.getOrPut(gpa, nav_index); | ||
| 170 | if (gop.found_existing) gop.value_ptr.deinit(gpa); | ||
| 171 | gop.value_ptr.* = mir; | ||
| 172 | } | ||
| 173 | |||
| 174 | pub fn updateExports( | ||
| 175 | linker: *Linker, | ||
| 176 | pt: Zcu.PerThread, | ||
| 177 | export_indices: []const Zcu.Export.Index, | ||
| 178 | ) link.Error!void { | ||
| 179 | const zcu = pt.zcu; | ||
| 180 | const ip = &zcu.intern_pool; | ||
| 181 | const gpa = linker.base.comp.gpa; | ||
| 182 | const diags = &linker.base.comp.link_diags; | ||
| 183 | |||
| 184 | linker.exports.clearRetainingCapacity(); | ||
| 185 | try linker.exports.ensureUnusedCapacity(gpa, export_indices.len); | ||
| 186 | for (export_indices) |exp_index| { | ||
| 187 | const exp = exp_index.ptr(zcu); | ||
| 188 | const nav_index = switch (exp.exported) { | ||
| 189 | .nav => |nav| nav, | ||
| 190 | .uav => return diags.fail("cannot export a uav constant for SPIR-V target", .{}), | ||
| 191 | }; | ||
| 192 | const nav = ip.getNav(nav_index); | ||
| 193 | const resolved = nav.resolved.?; | ||
| 194 | if (!ip.isFunctionType(resolved.type) and !resolved.@"threadlocal") { | ||
| 195 | return diags.fail("cannot export constant '{f}' for SPIR-V target", .{ | ||
| 196 | nav.fqn.fmt(ip), | ||
| 197 | }); | ||
| 198 | } | ||
| 199 | linker.exports.putAssumeCapacity(nav_index, exp.opts.name); | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | pub fn flush( | ||
| 204 | linker: *Linker, | ||
| 205 | arena: Allocator, | ||
| 206 | tid: Zcu.PerThread.Id, | ||
| 207 | prog_node: std.Progress.Node, | ||
| 208 | ) link.Error!void { | ||
| 209 | _ = tid; | ||
| 210 | const comp = linker.base.comp; | ||
| 211 | const gpa = comp.gpa; | ||
| 212 | const diags = &comp.link_diags; | ||
| 213 | |||
| 214 | const sub_node = prog_node.start("Flush Module", 1); | ||
| 215 | defer sub_node.end(); | ||
| 216 | |||
| 217 | var f: Flush = .{ | ||
| 218 | .linker = linker, | ||
| 219 | .arena = arena, | ||
| 220 | .id_bound = 1, | ||
| 221 | .navs = .empty, | ||
| 222 | .symbols = .empty, | ||
| 223 | .interned = .empty, | ||
| 224 | .variables = .empty, | ||
| 225 | .function_references = .empty, | ||
| 226 | .entry_points = .empty, | ||
| 227 | .deps = .empty, | ||
| 228 | .scratch = .empty, | ||
| 229 | .extended_instruction_sets = .empty, | ||
| 230 | .object_entry_points = .empty, | ||
| 231 | .object_execution_modes = .empty, | ||
| 232 | .debug_strings = .empty, | ||
| 233 | .names = .empty, | ||
| 234 | .decorations = .empty, | ||
| 235 | .globals = .empty, | ||
| 236 | .declarations = .empty, | ||
| 237 | .functions = .empty, | ||
| 238 | }; | ||
| 239 | var out: Section = .empty; | ||
| 240 | defer out.deinit(gpa); | ||
| 241 | |||
| 242 | try f.emitModule(&out); | ||
| 243 | sub_node.completeOne(); | ||
| 244 | |||
| 245 | var writer = linker.base.file.?.writer(comp.io, &.{}); | ||
| 246 | writer.interface.writeSliceEndian(Word, out.instructions.items, .little) catch { | ||
| 247 | return diags.fail("failed to write {f}: {t}", .{ linker.base.emit, writer.err.? }); | ||
| 248 | }; | ||
| 249 | writer.end() catch |err| { | ||
| 250 | return diags.fail("failed to write {f}: {t}", .{ linker.base.emit, err }); | ||
| 251 | }; | ||
| 252 | } | ||
src/link/Spirv/Flush.zig created+664| ... | @@ -0,0 +1,664 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Allocator = std.mem.Allocator; | ||
| 3 | const Path = std.Build.Cache.Path; | ||
| 4 | const assert = std.debug.assert; | ||
| 5 | const InternPool = @import("../../InternPool.zig"); | ||
| 6 | const link = @import("../../link.zig"); | ||
| 7 | const Type = @import("../../Type.zig"); | ||
| 8 | const Linker = @import("../Spirv.zig"); | ||
| 9 | const Mir = @import("../../codegen/spirv/Mir.zig"); | ||
| 10 | const spec = @import("../../codegen/spirv/spec.zig"); | ||
| 11 | const Section = @import("../../codegen/spirv/Section.zig"); | ||
| 12 | const zig_version = @import("builtin").zig_version; | ||
| 13 | const Id = spec.Id; | ||
| 14 | const Word = spec.Word; | ||
| 15 | const Flush = @This(); | ||
| 16 | |||
| 17 | linker: *Linker, | ||
| 18 | arena: Allocator, | ||
| 19 | id_bound: Word, | ||
| 20 | navs: std.array_hash_map.Auto(InternPool.Nav.Index, Id), | ||
| 21 | symbols: std.array_hash_map.String(Symbol), | ||
| 22 | interned: std.array_hash_map.String(Id), | ||
| 23 | variables: std.array_hash_map.Auto(Id, spec.StorageClass), | ||
| 24 | function_references: std.array_hash_map.Auto(Id, []const Id), | ||
| 25 | entry_points: std.ArrayList(Mir.EntryPoint), | ||
| 26 | deps: std.ArrayList(Id), | ||
| 27 | scratch: Section, | ||
| 28 | extended_instruction_sets: Section, | ||
| 29 | object_entry_points: Section, | ||
| 30 | object_execution_modes: Section, | ||
| 31 | debug_strings: Section, | ||
| 32 | names: Section, | ||
| 33 | decorations: Section, | ||
| 34 | globals: Section, | ||
| 35 | declarations: Section, | ||
| 36 | functions: Section, | ||
| 37 | |||
| 38 | const Symbol = struct { | ||
| 39 | id: Id, | ||
| 40 | defined: bool, | ||
| 41 | declaration: []const Word, | ||
| 42 | }; | ||
| 43 | |||
| 44 | const Annotation = struct { | ||
| 45 | target: Word, | ||
| 46 | class: spec.Class, | ||
| 47 | start: usize, | ||
| 48 | end: usize, | ||
| 49 | }; | ||
| 50 | |||
| 51 | const Input = struct { | ||
| 52 | mir: *const Mir, | ||
| 53 | ids: []Id, | ||
| 54 | marks: []u32, | ||
| 55 | mark: u32, | ||
| 56 | |||
| 57 | fn copy( | ||
| 58 | input: *Input, | ||
| 59 | flush: *Flush, | ||
| 60 | dest: *Section, | ||
| 61 | start: usize, | ||
| 62 | end: usize, | ||
| 63 | result_index: ?usize, | ||
| 64 | ) !void { | ||
| 65 | const words = input.mir.module.instructions.items; | ||
| 66 | try dest.ensureUnusedCapacity(flush.arena, end - start); | ||
| 67 | for (words[start..end], start..) |word, i| { | ||
| 68 | if (i == result_index) { | ||
| 69 | dest.writeWord(0); | ||
| 70 | continue; | ||
| 71 | } | ||
| 72 | if (!input.mir.module.ids.isSet(i)) { | ||
| 73 | dest.writeWord(word); | ||
| 74 | continue; | ||
| 75 | } | ||
| 76 | if (input.marks[word] != input.mark) { | ||
| 77 | input.marks[word] = input.mark; | ||
| 78 | try flush.deps.append(flush.arena, input.ids[word]); | ||
| 79 | } | ||
| 80 | dest.writeWord(@backingInt(input.ids[word])); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | }; | ||
| 84 | |||
| 85 | fn allocId(flush: *Flush) Id { | ||
| 86 | const id: Id = @fromBackingInt(flush.id_bound); | ||
| 87 | flush.id_bound += 1; | ||
| 88 | return id; | ||
| 89 | } | ||
| 90 | |||
| 91 | fn navId(flush: *Flush, nav: InternPool.Nav.Index) !Id { | ||
| 92 | const gop = try flush.navs.getOrPut(flush.arena, nav); | ||
| 93 | if (!gop.found_existing) gop.value_ptr.* = flush.allocId(); | ||
| 94 | return gop.value_ptr.*; | ||
| 95 | } | ||
| 96 | |||
| 97 | pub fn emitModule(flush: *Flush, out: *Section) link.Error!void { | ||
| 98 | const linker = flush.linker; | ||
| 99 | const comp = linker.base.comp; | ||
| 100 | const zcu = comp.zcu.?; | ||
| 101 | const ip = &zcu.intern_pool; | ||
| 102 | const gpa = comp.gpa; | ||
| 103 | const arena = flush.arena; | ||
| 104 | const diags = &comp.link_diags; | ||
| 105 | const target = &comp.root_mod.resolved_target.result; | ||
| 106 | const is_object = comp.config.output_mode == .Obj; | ||
| 107 | |||
| 108 | for (linker.exports.keys(), linker.exports.values()) |nav_index, name| { | ||
| 109 | if (!linker.mirs.contains(nav_index)) { | ||
| 110 | assert(zcu.failed_codegen.contains(nav_index)); | ||
| 111 | return error.AlreadyReported; | ||
| 112 | } | ||
| 113 | |||
| 114 | const id = try flush.navId(nav_index); | ||
| 115 | const nav_ty: Type = .fromInterned(ip.getNav(nav_index).resolved.?.type); | ||
| 116 | if (nav_ty.zigTypeTag(zcu) == .@"fn") { | ||
| 117 | const cc = nav_ty.fnCallingConvention(zcu); | ||
| 118 | if (cc != .spirv_device) { | ||
| 119 | try flush.entry_points.append(arena, .{ | ||
| 120 | .id = id, | ||
| 121 | .name = name.toSlice(ip), | ||
| 122 | .cc = cc, | ||
| 123 | }); | ||
| 124 | continue; | ||
| 125 | } | ||
| 126 | } | ||
| 127 | if (!is_object) continue; | ||
| 128 | |||
| 129 | try flush.symbols.putNoClobber(arena, name.toSlice(ip), .{ | ||
| 130 | .id = id, | ||
| 131 | .defined = true, | ||
| 132 | .declaration = &.{}, | ||
| 133 | }); | ||
| 134 | } | ||
| 135 | |||
| 136 | for (linker.objects.items) |*object| { | ||
| 137 | try flush.load(&object.mir, object.path); | ||
| 138 | } | ||
| 139 | |||
| 140 | const references = try zcu.resolveReferences(); | ||
| 141 | for (linker.mirs.keys(), linker.mirs.values()) |nav_index, *mir| { | ||
| 142 | const nav = ip.getNav(nav_index); | ||
| 143 | if (nav.getExtern(ip) != null) continue; | ||
| 144 | |||
| 145 | const unit: InternPool.AnalUnit = switch (ip.indexToKey(nav.resolved.?.type)) { | ||
| 146 | .func_type => .wrap(.{ .func = zcu.navValue(nav_index).toIntern() }), | ||
| 147 | else => .wrap(.{ .nav_val = nav_index }), | ||
| 148 | }; | ||
| 149 | if (references.contains(unit)) try flush.load(mir, null); | ||
| 150 | } | ||
| 151 | |||
| 152 | for (linker.mirs.keys(), linker.mirs.values()) |nav_index, *mir| { | ||
| 153 | if (ip.getNav(nav_index).getExtern(ip) == null) continue; | ||
| 154 | if (flush.navs.contains(nav_index)) try flush.load(mir, null); | ||
| 155 | } | ||
| 156 | |||
| 157 | var failed = false; | ||
| 158 | for (flush.navs.keys()) |nav_index| { | ||
| 159 | if (linker.mirs.contains(nav_index) or zcu.failed_codegen.contains(nav_index)) continue; | ||
| 160 | diags.addError("undefined reference to '{f}'", .{ip.getNav(nav_index).fqn.fmt(ip)}); | ||
| 161 | failed = true; | ||
| 162 | } | ||
| 163 | for (flush.symbols.keys(), flush.symbols.values()) |name, symbol| { | ||
| 164 | if (is_object) { | ||
| 165 | try flush.decorations.emit(arena, .OpDecorate, .{ | ||
| 166 | .target = symbol.id, | ||
| 167 | .decoration = .{ .linkage_attributes = .{ | ||
| 168 | .name = name, | ||
| 169 | .linkage_type = if (symbol.defined) .@"export" else .import, | ||
| 170 | } }, | ||
| 171 | }); | ||
| 172 | } | ||
| 173 | if (symbol.defined) continue; | ||
| 174 | if (!is_object or symbol.declaration.len == 0) { | ||
| 175 | diags.addError("undefined symbol '{s}'", .{name}); | ||
| 176 | failed = true; | ||
| 177 | continue; | ||
| 178 | } | ||
| 179 | const header: spec.InstructionHeader = @bitCast(symbol.declaration[0]); | ||
| 180 | const section = if (header.opcode == .OpVariable) &flush.globals else &flush.declarations; | ||
| 181 | try section.writeWords(arena, symbol.declaration); | ||
| 182 | } | ||
| 183 | if (failed) return error.AlreadyReported; | ||
| 184 | |||
| 185 | var version: spec.Version = .{ .major = 1, .minor = 0 }; | ||
| 186 | for ([_]std.Target.spirv.Feature{ | ||
| 187 | .v1_1, | ||
| 188 | .v1_2, | ||
| 189 | .v1_3, | ||
| 190 | .v1_4, | ||
| 191 | .v1_5, | ||
| 192 | .v1_6, | ||
| 193 | }) |feature| { | ||
| 194 | if (target.cpu.has(.spirv, feature)) version.minor += 1; | ||
| 195 | } | ||
| 196 | |||
| 197 | const zig_packed_version = (zig_version.major << 12) | (zig_version.minor << 7) | zig_version.patch; | ||
| 198 | const header: spec.Header = .{ | ||
| 199 | .magic = spec.magic_number, | ||
| 200 | .version = version, | ||
| 201 | .generator = .{ .tool = spec.zig_generator_id, .version = zig_packed_version }, | ||
| 202 | .id_bound = flush.id_bound, | ||
| 203 | .schema = 0, | ||
| 204 | }; | ||
| 205 | try out.writeWords(gpa, @ptrCast(@alignCast(&header))); | ||
| 206 | try flush.emitCapabilities(out); | ||
| 207 | try out.writeWords(gpa, flush.extended_instruction_sets.instructions.items); | ||
| 208 | try flush.emitMemoryModel(out); | ||
| 209 | try flush.emitEntryPoints(out); | ||
| 210 | try out.writeWords(gpa, flush.object_entry_points.instructions.items); | ||
| 211 | try flush.emitExecutionModes(out); | ||
| 212 | try out.writeWords(gpa, flush.object_execution_modes.instructions.items); | ||
| 213 | try out.writeWords(gpa, flush.debug_strings.instructions.items); | ||
| 214 | try flush.emitSourceInfo(out, zig_packed_version); | ||
| 215 | try out.writeWords(gpa, flush.names.instructions.items); | ||
| 216 | try out.writeWords(gpa, flush.decorations.instructions.items); | ||
| 217 | try out.writeWords(gpa, flush.globals.instructions.items); | ||
| 218 | try out.writeWords(gpa, flush.declarations.instructions.items); | ||
| 219 | try out.writeWords(gpa, flush.functions.instructions.items); | ||
| 220 | } | ||
| 221 | |||
| 222 | fn load(flush: *Flush, mir: *const Mir, path: ?Path) link.Error!void { | ||
| 223 | const comp = flush.linker.base.comp; | ||
| 224 | const gpa = comp.gpa; | ||
| 225 | const arena = flush.arena; | ||
| 226 | const diags = &comp.link_diags; | ||
| 227 | |||
| 228 | const ids = try gpa.alloc(Id, mir.id_bound); | ||
| 229 | defer gpa.free(ids); | ||
| 230 | ids[0] = .none; | ||
| 231 | for (ids[1..], flush.id_bound..) |*id, i| id.* = @fromBackingInt(@intCast(i)); | ||
| 232 | flush.id_bound += mir.id_bound - 1; | ||
| 233 | |||
| 234 | const marks = try gpa.alloc(u32, mir.id_bound); | ||
| 235 | defer gpa.free(marks); | ||
| 236 | @memset(marks, 0); | ||
| 237 | var input: Input = .{ .mir = mir, .ids = ids, .marks = marks, .mark = 0 }; | ||
| 238 | |||
| 239 | var forward_pointers: std.bit_set.Dynamic = try .initEmpty(gpa, mir.id_bound); | ||
| 240 | defer forward_pointers.deinit(gpa); | ||
| 241 | var imports: std.array_hash_map.Auto(Word, u32) = .empty; | ||
| 242 | defer imports.deinit(gpa); | ||
| 243 | var annotations: std.ArrayList(Annotation) = .empty; | ||
| 244 | defer annotations.deinit(gpa); | ||
| 245 | |||
| 246 | for (mir.nav_refs) |ref| ids[@backingInt(ref.id)] = try flush.navId(ref.nav); | ||
| 247 | for (mir.externs) |ext| { | ||
| 248 | const gop = try flush.symbols.getOrPut(arena, ext.name); | ||
| 249 | if (!gop.found_existing) gop.value_ptr.* = .{ | ||
| 250 | .id = ids[@backingInt(ext.id)], | ||
| 251 | .defined = false, | ||
| 252 | .declaration = &.{}, | ||
| 253 | }; | ||
| 254 | ids[@backingInt(ext.id)] = gop.value_ptr.id; | ||
| 255 | try imports.put(gpa, @backingInt(ext.id), @intCast(gop.index)); | ||
| 256 | } | ||
| 257 | |||
| 258 | const words = mir.module.instructions.items; | ||
| 259 | var offset: usize = 0; | ||
| 260 | while (offset < words.len) { | ||
| 261 | const start = offset; | ||
| 262 | const header: spec.InstructionHeader = @bitCast(words[start]); | ||
| 263 | const inst = words[start..][0..header.word_count]; | ||
| 264 | offset += inst.len; | ||
| 265 | |||
| 266 | if (header.opcode == .OpDecorate) { | ||
| 267 | const target = inst[1]; | ||
| 268 | const decoration: spec.Decoration = @fromBackingInt(inst[2]); | ||
| 269 | if (decoration == .linkage_attributes) { | ||
| 270 | const parameters = inst[3..]; | ||
| 271 | const name_words = parameters[0 .. parameters.len - 1]; | ||
| 272 | const name = std.mem.sliceTo(std.mem.sliceAsBytes(name_words), 0); | ||
| 273 | const linkage: spec.LinkageType = @fromBackingInt(parameters[parameters.len - 1]); | ||
| 274 | const gop = try flush.symbols.getOrPut(arena, name); | ||
| 275 | if (!gop.found_existing) gop.value_ptr.* = .{ | ||
| 276 | .id = ids[target], | ||
| 277 | .defined = false, | ||
| 278 | .declaration = &.{}, | ||
| 279 | }; | ||
| 280 | |||
| 281 | switch (linkage) { | ||
| 282 | .@"export" => { | ||
| 283 | if (gop.value_ptr.defined) { | ||
| 284 | return diags.failParse(path.?, "duplicate export '{s}'", .{name}); | ||
| 285 | } | ||
| 286 | gop.value_ptr.defined = true; | ||
| 287 | }, | ||
| 288 | .import => try imports.put(gpa, target, @intCast(gop.index)), | ||
| 289 | .link_once_odr => { | ||
| 290 | return diags.failParse(path.?, "unsupported linkage '{t}'", .{linkage}); | ||
| 291 | }, | ||
| 292 | } | ||
| 293 | |||
| 294 | ids[target] = gop.value_ptr.id; | ||
| 295 | continue; | ||
| 296 | } | ||
| 297 | } | ||
| 298 | |||
| 299 | const class = header.opcode.class(); | ||
| 300 | const target: Word = switch (header.opcode) { | ||
| 301 | .OpName, .OpMemberName => inst[1], | ||
| 302 | .OpDecorationGroup, .OpGroupDecorate, .OpGroupMemberDecorate => { | ||
| 303 | return diags.failParse(path.?, "decoration groups are not supported", .{}); | ||
| 304 | }, | ||
| 305 | else => if (class == .annotation) inst[1] else continue, | ||
| 306 | }; | ||
| 307 | try annotations.append(gpa, .{ | ||
| 308 | .target = target, | ||
| 309 | .class = class, | ||
| 310 | .start = start, | ||
| 311 | .end = offset, | ||
| 312 | }); | ||
| 313 | } | ||
| 314 | |||
| 315 | const starts = try gpa.alloc(u32, mir.id_bound + 1); | ||
| 316 | defer gpa.free(starts); | ||
| 317 | @memset(starts, 0); | ||
| 318 | |||
| 319 | for (annotations.items) |annotation| starts[annotation.target + 1] += 1; | ||
| 320 | for (1..starts.len) |i| starts[i] += starts[i - 1]; | ||
| 321 | |||
| 322 | const cursors = try gpa.dupe(u32, starts); | ||
| 323 | defer gpa.free(cursors); | ||
| 324 | const by_target = try gpa.alloc(u32, annotations.items.len); | ||
| 325 | defer gpa.free(by_target); | ||
| 326 | for (annotations.items, 0..) |annotation, i| { | ||
| 327 | by_target[cursors[annotation.target]] = @intCast(i); | ||
| 328 | cursors[annotation.target] += 1; | ||
| 329 | } | ||
| 330 | |||
| 331 | var results: std.ArrayList(Word) = .empty; | ||
| 332 | defer results.deinit(gpa); | ||
| 333 | |||
| 334 | offset = 0; | ||
| 335 | while (offset < words.len) { | ||
| 336 | const start = offset; | ||
| 337 | const header: spec.InstructionHeader = @bitCast(words[start]); | ||
| 338 | const inst = words[start..][0..header.word_count]; | ||
| 339 | offset += inst.len; | ||
| 340 | input.mark += 1; | ||
| 341 | flush.deps.clearRetainingCapacity(); | ||
| 342 | |||
| 343 | const result_index = header.opcode.resultIndex() orelse { | ||
| 344 | switch (header.opcode) { | ||
| 345 | .OpEntryPoint => { | ||
| 346 | try input.copy(flush, &flush.object_entry_points, start, offset, null); | ||
| 347 | }, | ||
| 348 | .OpExecutionMode, .OpExecutionModeId => { | ||
| 349 | try input.copy(flush, &flush.object_execution_modes, start, offset, null); | ||
| 350 | }, | ||
| 351 | .OpTypeForwardPointer => { | ||
| 352 | forward_pointers.set(inst[1]); | ||
| 353 | try input.copy(flush, &flush.globals, start, offset, null); | ||
| 354 | }, | ||
| 355 | else => {}, | ||
| 356 | } | ||
| 357 | continue; | ||
| 358 | }; | ||
| 359 | const result = inst[result_index]; | ||
| 360 | results.clearRetainingCapacity(); | ||
| 361 | try results.append(gpa, result); | ||
| 362 | |||
| 363 | var has_body = false; | ||
| 364 | if (header.opcode == .OpFunction) { | ||
| 365 | while (true) { | ||
| 366 | const body: spec.InstructionHeader = @bitCast(words[offset]); | ||
| 367 | if (body.opcode == .OpLabel) has_body = true; | ||
| 368 | if (body.opcode.resultIndex()) |index| { | ||
| 369 | try results.append(gpa, words[offset + index]); | ||
| 370 | } | ||
| 371 | offset += body.word_count; | ||
| 372 | if (body.opcode == .OpFunctionEnd) break; | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | for (results.items) |own| input.marks[own] = input.mark; | ||
| 377 | |||
| 378 | if (imports.get(result)) |symbol_index| { | ||
| 379 | flush.scratch.reset(); | ||
| 380 | try input.copy(flush, &flush.scratch, start, offset, null); | ||
| 381 | const declaration = try arena.dupe(Word, flush.scratch.instructions.items); | ||
| 382 | flush.symbols.values()[symbol_index].declaration = declaration; | ||
| 383 | continue; | ||
| 384 | } | ||
| 385 | |||
| 386 | switch (header.opcode) { | ||
| 387 | .OpFunction => if (!has_body) return diags.failParse(path.?, "function declaration without linkage", .{}), | ||
| 388 | .OpVariable => try flush.variables.put(arena, ids[result], @fromBackingInt(inst[3])), | ||
| 389 | else => if (!forward_pointers.isSet(result)) { | ||
| 390 | flush.scratch.reset(); | ||
| 391 | try input.copy(flush, &flush.scratch, start, offset, start + result_index); | ||
| 392 | for (by_target[starts[result]..starts[result + 1]]) |index| { | ||
| 393 | const annotation = annotations.items[index]; | ||
| 394 | if (annotation.class != .annotation) continue; | ||
| 395 | const target = annotation.start + 1; | ||
| 396 | try input.copy(flush, &flush.scratch, annotation.start, annotation.end, target); | ||
| 397 | } | ||
| 398 | |||
| 399 | const key = std.mem.sliceAsBytes(flush.scratch.instructions.items); | ||
| 400 | const gop = try flush.interned.getOrPut(arena, key); | ||
| 401 | if (gop.found_existing) { | ||
| 402 | ids[result] = gop.value_ptr.*; | ||
| 403 | continue; | ||
| 404 | } | ||
| 405 | gop.key_ptr.* = try arena.dupe(u8, key); | ||
| 406 | gop.value_ptr.* = ids[result]; | ||
| 407 | }, | ||
| 408 | } | ||
| 409 | |||
| 410 | for (results.items) |target| { | ||
| 411 | for (by_target[starts[target]..starts[target + 1]]) |index| { | ||
| 412 | const annotation = annotations.items[index]; | ||
| 413 | const section = switch (annotation.class) { | ||
| 414 | .debug => &flush.names, | ||
| 415 | .annotation => &flush.decorations, | ||
| 416 | else => unreachable, | ||
| 417 | }; | ||
| 418 | try input.copy(flush, section, annotation.start, annotation.end, null); | ||
| 419 | } | ||
| 420 | } | ||
| 421 | |||
| 422 | const section = switch (header.opcode) { | ||
| 423 | .OpFunction => &flush.functions, | ||
| 424 | .OpExtInstImport => &flush.extended_instruction_sets, | ||
| 425 | .OpString => &flush.debug_strings, | ||
| 426 | else => &flush.globals, | ||
| 427 | }; | ||
| 428 | try input.copy(flush, section, start, offset, null); | ||
| 429 | if (header.opcode == .OpFunction) { | ||
| 430 | const references = try arena.dupe(Id, flush.deps.items); | ||
| 431 | try flush.function_references.put(arena, ids[result], references); | ||
| 432 | } | ||
| 433 | } | ||
| 434 | |||
| 435 | for (mir.entry_points) |entry_point| { | ||
| 436 | try flush.entry_points.append(arena, .{ | ||
| 437 | .id = ids[@backingInt(entry_point.id)], | ||
| 438 | .name = entry_point.name, | ||
| 439 | .cc = entry_point.cc, | ||
| 440 | }); | ||
| 441 | } | ||
| 442 | } | ||
| 443 | |||
| 444 | fn emitCapabilities(flush: *Flush, out: *Section) link.Error!void { | ||
| 445 | const comp = flush.linker.base.comp; | ||
| 446 | const target = &comp.root_mod.resolved_target.result; | ||
| 447 | const gpa = comp.gpa; | ||
| 448 | |||
| 449 | switch (target.os.tag) { | ||
| 450 | .opengl, .vulkan => try out.emit(gpa, .OpCapability, .{ .capability = .shader }), | ||
| 451 | .opencl => { | ||
| 452 | try out.emit(gpa, .OpCapability, .{ .capability = .kernel }); | ||
| 453 | try out.emit(gpa, .OpCapability, .{ .capability = .addresses }); | ||
| 454 | }, | ||
| 455 | else => unreachable, | ||
| 456 | } | ||
| 457 | |||
| 458 | if (target.cpu.arch == .spirv64) { | ||
| 459 | if (!target.cpu.has(.spirv, .int64)) { | ||
| 460 | try out.emit(gpa, .OpCapability, .{ .capability = .int64 }); | ||
| 461 | } | ||
| 462 | if (target.os.tag == .vulkan) { | ||
| 463 | try out.emit(gpa, .OpCapability, .{ .capability = .physical_storage_buffer_addresses }); | ||
| 464 | } | ||
| 465 | } | ||
| 466 | |||
| 467 | if (comp.config.output_mode == .Obj and flush.symbols.count() > 0) { | ||
| 468 | try out.emit(gpa, .OpCapability, .{ .capability = .linkage }); | ||
| 469 | } | ||
| 470 | |||
| 471 | inline for (@typeInfo(spec.Capability).@"enum".field_names) |name| { | ||
| 472 | if (@hasField(std.Target.spirv.Feature, name)) { | ||
| 473 | const feature = @field(std.Target.spirv.Feature, name); | ||
| 474 | if (target.cpu.has(.spirv, feature)) { | ||
| 475 | try out.emit(gpa, .OpCapability, .{ .capability = @field(spec.Capability, name) }); | ||
| 476 | } | ||
| 477 | } | ||
| 478 | } | ||
| 479 | |||
| 480 | if (target.cpu.arch == .spirv64 and target.os.tag == .vulkan) { | ||
| 481 | try out.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_physical_storage_buffer" }); | ||
| 482 | } | ||
| 483 | |||
| 484 | inline for (@typeInfo(spec.Extension).@"enum".field_names) |name| { | ||
| 485 | if (@hasField(std.Target.spirv.Feature, name)) { | ||
| 486 | const feature = @field(std.Target.spirv.Feature, name); | ||
| 487 | if (target.cpu.has(.spirv, feature)) try out.emit(gpa, .OpExtension, .{ .name = name }); | ||
| 488 | } | ||
| 489 | } | ||
| 490 | } | ||
| 491 | |||
| 492 | fn emitMemoryModel(flush: *Flush, out: *Section) link.Error!void { | ||
| 493 | const comp = flush.linker.base.comp; | ||
| 494 | const target = &comp.root_mod.resolved_target.result; | ||
| 495 | const addressing_model: spec.AddressingModel = switch (target.os.tag) { | ||
| 496 | .opengl => .logical, | ||
| 497 | .vulkan => switch (target.cpu.arch) { | ||
| 498 | .spirv32 => .logical, | ||
| 499 | .spirv64 => .physical_storage_buffer64, | ||
| 500 | else => unreachable, | ||
| 501 | }, | ||
| 502 | .opencl => switch (target.cpu.arch) { | ||
| 503 | .spirv32 => .physical32, | ||
| 504 | .spirv64 => .physical64, | ||
| 505 | else => unreachable, | ||
| 506 | }, | ||
| 507 | else => unreachable, | ||
| 508 | }; | ||
| 509 | try out.emit(comp.gpa, .OpMemoryModel, .{ | ||
| 510 | .addressing_model = addressing_model, | ||
| 511 | .memory_model = switch (target.os.tag) { | ||
| 512 | .opencl => .open_cl, | ||
| 513 | .vulkan, .opengl => .glsl450, | ||
| 514 | else => unreachable, | ||
| 515 | }, | ||
| 516 | }); | ||
| 517 | } | ||
| 518 | |||
| 519 | fn emitEntryPoints(flush: *Flush, out: *Section) link.Error!void { | ||
| 520 | const comp = flush.linker.base.comp; | ||
| 521 | const gpa = comp.gpa; | ||
| 522 | const arena = flush.arena; | ||
| 523 | const target = &comp.root_mod.resolved_target.result; | ||
| 524 | const v1_4 = target.cpu.has(.spirv, .v1_4); | ||
| 525 | |||
| 526 | var visited: std.bit_set.Dynamic = try .initEmpty(arena, flush.id_bound); | ||
| 527 | var pending: std.ArrayList(Id) = .empty; | ||
| 528 | var interface: std.ArrayList(Id) = .empty; | ||
| 529 | for (flush.entry_points.items) |entry_point| { | ||
| 530 | const execution_model: spec.ExecutionModel = switch (entry_point.cc) { | ||
| 531 | .spirv_kernel => if (target.os.tag == .opencl) .kernel else .gl_compute, | ||
| 532 | .spirv_vertex => .vertex, | ||
| 533 | .spirv_fragment => .fragment, | ||
| 534 | .spirv_task => .task_ext, | ||
| 535 | .spirv_mesh => .mesh_ext, | ||
| 536 | else => unreachable, | ||
| 537 | }; | ||
| 538 | |||
| 539 | visited.unsetAll(); | ||
| 540 | interface.clearRetainingCapacity(); | ||
| 541 | try pending.append(arena, entry_point.id); | ||
| 542 | while (pending.pop()) |id| { | ||
| 543 | if (visited.isSet(@backingInt(id))) continue; | ||
| 544 | visited.set(@backingInt(id)); | ||
| 545 | if (flush.variables.get(id)) |storage_class| { | ||
| 546 | if (storage_class == .function) continue; | ||
| 547 | if (!v1_4 and storage_class != .input and storage_class != .output) continue; | ||
| 548 | try interface.append(arena, id); | ||
| 549 | } else if (flush.function_references.get(id)) |references| { | ||
| 550 | try pending.appendSlice(arena, references); | ||
| 551 | } | ||
| 552 | } | ||
| 553 | |||
| 554 | try out.emit(gpa, .OpEntryPoint, .{ | ||
| 555 | .execution_model = execution_model, | ||
| 556 | .entry_point = entry_point.id, | ||
| 557 | .name = entry_point.name, | ||
| 558 | .interface = interface.items, | ||
| 559 | }); | ||
| 560 | } | ||
| 561 | } | ||
| 562 | |||
| 563 | fn emitExecutionModes(flush: *Flush, out: *Section) link.Error!void { | ||
| 564 | const comp = flush.linker.base.comp; | ||
| 565 | const gpa = comp.gpa; | ||
| 566 | const target = &comp.root_mod.resolved_target.result; | ||
| 567 | |||
| 568 | for (flush.entry_points.items) |entry_point| switch (entry_point.cc) { | ||
| 569 | .spirv_kernel, .spirv_task => |kernel| { | ||
| 570 | try out.emit(gpa, .OpExecutionMode, .{ | ||
| 571 | .entry_point = entry_point.id, | ||
| 572 | .mode = .{ .local_size = .{ | ||
| 573 | .x_size = kernel.x, | ||
| 574 | .y_size = kernel.y, | ||
| 575 | .z_size = kernel.z, | ||
| 576 | } }, | ||
| 577 | }); | ||
| 578 | }, | ||
| 579 | .spirv_fragment => |fragment| { | ||
| 580 | try out.emit(gpa, .OpExecutionMode, .{ | ||
| 581 | .entry_point = entry_point.id, | ||
| 582 | .mode = if (target.os.tag == .vulkan) .origin_upper_left else .origin_lower_left, | ||
| 583 | }); | ||
| 584 | if (fragment.pixel_centered_integer) { | ||
| 585 | try out.emit(gpa, .OpExecutionMode, .{ | ||
| 586 | .entry_point = entry_point.id, | ||
| 587 | .mode = .pixel_center_integer, | ||
| 588 | }); | ||
| 589 | } | ||
| 590 | if (fragment.depth_assumption != .none) { | ||
| 591 | try out.emit(gpa, .OpExecutionMode, .{ | ||
| 592 | .entry_point = entry_point.id, | ||
| 593 | .mode = switch (fragment.depth_assumption) { | ||
| 594 | .none => unreachable, | ||
| 595 | .greater => .depth_greater, | ||
| 596 | .less => .depth_less, | ||
| 597 | .unchanged => .depth_unchanged, | ||
| 598 | }, | ||
| 599 | }); | ||
| 600 | } | ||
| 601 | }, | ||
| 602 | .spirv_mesh => |mesh| { | ||
| 603 | try out.emit(gpa, .OpExecutionMode, .{ | ||
| 604 | .entry_point = entry_point.id, | ||
| 605 | .mode = .{ .output_vertices = .{ .vertex_count = mesh.max_vertices } }, | ||
| 606 | }); | ||
| 607 | try out.emit(gpa, .OpExecutionMode, .{ | ||
| 608 | .entry_point = entry_point.id, | ||
| 609 | .mode = .{ .output_primitives_ext = .{ .primitive_count = mesh.max_primitives } }, | ||
| 610 | }); | ||
| 611 | try out.emit(gpa, .OpExecutionMode, .{ | ||
| 612 | .entry_point = entry_point.id, | ||
| 613 | .mode = .{ .local_size = .{ | ||
| 614 | .x_size = mesh.x, | ||
| 615 | .y_size = mesh.y, | ||
| 616 | .z_size = mesh.z, | ||
| 617 | } }, | ||
| 618 | }); | ||
| 619 | try out.emit(gpa, .OpExecutionMode, .{ | ||
| 620 | .entry_point = entry_point.id, | ||
| 621 | .mode = switch (mesh.stage_output) { | ||
| 622 | .output_points => .output_points, | ||
| 623 | .output_lines => .output_lines_ext, | ||
| 624 | .output_triangles => .output_triangles_ext, | ||
| 625 | }, | ||
| 626 | }); | ||
| 627 | }, | ||
| 628 | else => {}, | ||
| 629 | }; | ||
| 630 | } | ||
| 631 | |||
| 632 | fn emitSourceInfo(flush: *Flush, out: *Section, version: u32) link.Error!void { | ||
| 633 | const comp = flush.linker.base.comp; | ||
| 634 | const gpa = comp.gpa; | ||
| 635 | const zcu = comp.zcu orelse return; | ||
| 636 | const ip = &zcu.intern_pool; | ||
| 637 | |||
| 638 | var error_info: std.Io.Writer.Allocating = .init(gpa); | ||
| 639 | defer error_info.deinit(); | ||
| 640 | error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory; | ||
| 641 | for (ip.global_error_set.getNamesFromMainThread()) |name| { | ||
| 642 | error_info.writer.writeByte(':') catch return error.OutOfMemory; | ||
| 643 | std.Uri.Component.percentEncode( | ||
| 644 | &error_info.writer, | ||
| 645 | name.toSlice(ip), | ||
| 646 | struct { | ||
| 647 | fn isValidChar(c: u8) bool { | ||
| 648 | return switch (c) { | ||
| 649 | 0, '%', ':' => false, | ||
| 650 | else => true, | ||
| 651 | }; | ||
| 652 | } | ||
| 653 | }.isValidChar, | ||
| 654 | ) catch return error.OutOfMemory; | ||
| 655 | } | ||
| 656 | |||
| 657 | try out.emit(gpa, .OpSourceExtension, .{ .extension = error_info.written() }); | ||
| 658 | try out.emit(gpa, .OpSource, .{ | ||
| 659 | .source_language = .zig, | ||
| 660 | .version = version, | ||
| 661 | .file = null, | ||
| 662 | .source = null, | ||
| 663 | }); | ||
| 664 | } | ||
src/target.zig+2| ... | @@ -604,6 +604,7 @@ pub fn defaultAddressSpace( | ... | @@ -604,6 +604,7 @@ pub fn defaultAddressSpace( |
| 604 | // The default address space for functions on AVR is .flash to produce | 604 | // The default address space for functions on AVR is .flash to produce |
| 605 | // correct fixups into progmem. | 605 | // correct fixups into progmem. |
| 606 | if (context == .function and target.cpu.arch == .avr) return .flash; | 606 | if (context == .function and target.cpu.arch == .avr) return .flash; |
| 607 | if (context == .global_mutable and target.os.tag == .vulkan) return .private; | ||
| 607 | return .generic; | 608 | return .generic; |
| 608 | } | 609 | } |
| 609 | 610 | ||
| ... | @@ -649,6 +650,7 @@ pub fn shouldBlockPointerOps(target: *const std.Target, as: AddressSpace) bool { | ... | @@ -649,6 +650,7 @@ pub fn shouldBlockPointerOps(target: *const std.Target, as: AddressSpace) bool { |
| 649 | // Logical pointers that never support operations | 650 | // Logical pointers that never support operations |
| 650 | .constant, | 651 | .constant, |
| 651 | .local, | 652 | .local, |
| 653 | .private, | ||
| 652 | .input, | 654 | .input, |
| 653 | .output, | 655 | .output, |
| 654 | .uniform, | 656 | .uniform, |
test/cases/compile_errors/SpirvType_vulkan_target.zig+3-3| ... | @@ -50,7 +50,7 @@ comptime { | ... | @@ -50,7 +50,7 @@ comptime { |
| 50 | // backend=selfhosted | 50 | // backend=selfhosted |
| 51 | // target=spirv32-vulkan | 51 | // target=spirv32-vulkan |
| 52 | // | 52 | // |
| 53 | // :2:21: error: access qualifier '.read_only' is only valid under the 'opencl' os | 53 | // :2:21: error: access qualifier '.read_only' is only valid under the 'opencl' OS |
| 54 | // :14:21: error: invalid 'sampled' field value 'bool' | 54 | // :14:21: error: invalid 'sampled' field value 'bool' |
| 55 | // :26:21: error: 'void' type for 'sampled' field is only valid under the 'opencl' os | 55 | // :26:21: error: 'void' type for 'sampled' field is only valid under the 'opencl' OS |
| 56 | // :38:21: error: 'sampled' field value must be a 32-bit int, 64-bit int or 32-bit float under the 'vulkan' os | 56 | // :38:21: error: 'sampled' field value must be a 32-bit int, 64-bit int or 32-bit float under the 'vulkan' OS |
test/cases/compile_errors/extern_spirv_decoration_validation.zig+21-4| ... | @@ -1,13 +1,30 @@ | ... | @@ -1,13 +1,30 @@ |
| 1 | const x = @extern(*addrspace(.push_constant) u32, .{ | 1 | const flat = @extern(*addrspace(.push_constant) u32, .{ |
| 2 | .name = "x", | 2 | .name = "flat", |
| 3 | .decoration = .{ .flat = 0 }, | 3 | .decoration = .{ .flat = 0 }, |
| 4 | }); | 4 | }); |
| 5 | const location = @extern(*addrspace(.uniform) u32, .{ | ||
| 6 | .name = "location", | ||
| 7 | .decoration = .{ .location = 0 }, | ||
| 8 | }); | ||
| 9 | const descriptor = @extern(*addrspace(.input) u32, .{ | ||
| 10 | .name = "descriptor", | ||
| 11 | .decoration = .{ .descriptor = .{ .set = 0, .binding = 0 } }, | ||
| 12 | }); | ||
| 13 | |||
| 14 | comptime { | ||
| 15 | _ = flat; | ||
| 16 | } | ||
| 17 | comptime { | ||
| 18 | _ = location; | ||
| 19 | } | ||
| 5 | comptime { | 20 | comptime { |
| 6 | _ = x; | 21 | _ = descriptor; |
| 7 | } | 22 | } |
| 8 | 23 | ||
| 9 | // error | 24 | // error |
| 10 | // backend=selfhosted | 25 | // backend=selfhosted |
| 11 | // target=spirv32-vulkan | 26 | // target=spirv32-vulkan |
| 12 | // | 27 | // |
| 13 | // :1:52: error: "flat" decoration requires "input" or "output" address space | 28 | // :1:55: error: 'flat' decoration requires 'input' or 'output' address space |
| 29 | // :5:53: error: 'location' decoration requires 'input' or 'output' address space | ||
| 30 | // :9:53: error: 'descriptor' decoration requires 'storage_buffer', 'uniform', 'constant' or 'global' address space |
test/cases/compile_errors/spirv_global_var_must_be_threadlocal.zig created+12| ... | @@ -0,0 +1,12 @@ | ||
| 1 | var x: u32 = 0; | ||
| 2 | |||
| 3 | comptime { | ||
| 4 | _ = x; | ||
| 5 | } | ||
| 6 | |||
| 7 | // error | ||
| 8 | // backend=selfhosted | ||
| 9 | // target=spirv32-vulkan | ||
| 10 | // | ||
| 11 | // :1:1: error: SPIR-V target does not support global variables | ||
| 12 | // :1:1: note: consider using 'threadlocal' | ||
test/cases/compile_errors/spirv_vulkan_threadlocal_addrspace.zig created+11| ... | @@ -0,0 +1,11 @@ | ||
| 1 | threadlocal var counter: u32 addrspace(.generic) = 0; | ||
| 2 | |||
| 3 | export fn main() callconv(.kernel) void { | ||
| 4 | counter += 1; | ||
| 5 | } | ||
| 6 | |||
| 7 | // error | ||
| 8 | // backend=selfhosted | ||
| 9 | // target=spirv32-vulkan | ||
| 10 | // | ||
| 11 | // :1:41: error: threadlocal variables with address space 'generic' are not supported on vulkan | ||