authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-09-13 16:39:59+03:30
committergravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-09-18 16:36:23+03:30
logb3727d9bd1abc193bed55d31754737980b9087e1
treed15b7ecf280430dbf0003ac9a8428b2a0c7ba9b4
parent80fe9b2b7fc981166cbfe98165e17dad6107cb34

spirv: rewrite linker

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(
23922392 .lut => arch == .propeller and std.Target.propeller.featureSetHas(target.cpu.features, .p2),
23932393
23942394 .global, .local, .shared => is_gpu,
2395 .private => is_spirv,
23952396 .constant => (is_gpu and (context == null or context == .constant)) or
23962397 (is_spirv and (context == null or context == .constant or context == .pointer)),
23972398 .param => is_nvptx,
lib/std/lang.zig+1
......@@ -591,6 +591,7 @@ pub const AddressSpace = enum(u5) {
591591 param,
592592 shared,
593593 local,
594 private,
594595 input,
595596 output,
596597 uniform,
src/Sema.zig+36-30
......@@ -20914,13 +20914,8 @@ fn zirReifySpirvType(
2091420914 const depth = try sema.interpretStdLangType(block, operand_src, depth_val, std.lang.Type.Spirv.Image.Depth);
2091520915 const access = try sema.interpretStdLangType(block, operand_src, access_val, std.lang.Type.Spirv.Image.Access);
2091620916
20917 switch (target.os.tag) {
20918 .opencl => if (access == .unknown) {
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 },
20917 if (target.os.tag == .opencl and access == .unknown) {
20918 return sema.fail(block, operand_src, "'access' field must be specified under the 'opencl' OS", .{});
2092420919 }
2092520920
2092620921 const arrayed = try sema.interpretStdLangType(block, operand_src, arrayed_val, bool);
......@@ -20930,31 +20925,24 @@ fn zirReifySpirvType(
2093020925 const usage_tag = try sema.interpretStdLangType(block, operand_src, usage_tag_val, @typeInfo(std.lang.Type.Spirv.Image.Usage).@"union".tag_type.?);
2093120926
2093220927 switch (target.os.tag) {
20933 .vulkan => {
20934 if (usage_tag == .unknown) {
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 }
20928 .vulkan => if (usage_tag == .unknown) {
20929 return sema.fail(block, operand_src, "'usage' must be '.sampled' or '.storage' under the 'vulkan' OS", .{});
2094220930 },
2094320931 .opencl => {
2094420932 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", .{});
2094620934 }
2094720935 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", .{});
2094920937 }
2095020938 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", .{});
2095220940 }
2095320941 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", .{});
2095520943 }
2095620944 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", .{});
2095820946 }
2095920947 },
2096020948 else => {},
......@@ -20965,10 +20953,10 @@ fn zirReifySpirvType(
2096520953 const sampled_type = usage_val.unionPayload(zcu).toType();
2096620954
2096720955 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});
2096920957 }
2097020958 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});
2097220960 }
2097320961
2097420962 if (sampled_type.toIntern() != .void_type and
......@@ -20984,7 +20972,7 @@ fn zirReifySpirvType(
2098420972 return sema.fail(
2098520973 block,
2098620974 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",
2098820976 .{usage_tag},
2098920977 );
2099020978 }
......@@ -21005,7 +20993,7 @@ fn zirReifySpirvType(
2100520993 return sema.fail(
2100620994 block,
2100720995 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",
2100920997 .{ format, usage_tag, sampled_type.fmt(pt) },
2101020998 );
2101120999 }
......@@ -21058,7 +21046,7 @@ fn zirReifySpirvType(
2105821046 elem_ty.zigTypeTag(zcu) == .spirv and
2105921047 ip.loadSpirvType(elem_ty.toIntern()).flags.tag == .runtime_array)
2106021048 {
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", .{});
2106221050 }
2106321051 break :blk .{
2106421052 .ty = union_val.val,
......@@ -25315,11 +25303,14 @@ fn zirBuiltinExtern(
2531525303 }
2531625304
2531725305 if (options.decoration) |decoration| switch (decoration) {
25318 .flat => switch (ptr_info.flags.address_space) {
25306 .location, .flat => switch (ptr_info.flags.address_space) {
2531925307 .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", .{}),
2532125313 },
25322 .location, .descriptor => {},
2532325314 };
2532425315
2532525316 const target = zcu.getTarget();
......@@ -34913,6 +34904,7 @@ pub fn resolveNavPtrModifiers(
3491334904 const gpa = comp.gpa;
3491434905 const io = comp.io;
3491534906 const ip = &zcu.intern_pool;
34907 const target = zcu.getTarget();
3491634908
3491734909 const align_src = block.src(.{ .node_offset_var_decl_align = .zero });
3491834910 const section_src = block.src(.{ .node_offset_var_decl_section = .zero });
......@@ -34944,7 +34936,6 @@ pub fn resolveNavPtrModifiers(
3494434936 else => .constant,
3494534937 },
3494634938 };
34947 const target = zcu.getTarget();
3494834939 const addrspace_body = zir_decl.addrspace_body orelse {
3494934940 if (zir_decl.linkage == .@"extern" and
3495034941 target.cpu.arch.isSpirV() and
......@@ -34968,6 +34959,21 @@ pub fn resolveNavPtrModifiers(
3496834959 break :as try sema.analyzeAsAddressSpace(block, addrspace_src, addrspace_ref, addrspace_ctx);
3496934960 };
3497034961
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
3497134977 return .{
3497234978 .@"align" = @"align",
3497334979 .@"linksection" = @"linksection",
src/codegen/spirv/Assembler.zig+68-158
......@@ -1,16 +1,11 @@
11const std = @import("std");
2const Allocator = std.mem.Allocator;
32const assert = std.debug.assert;
4
53const CodeGen = @import("CodeGen.zig");
6const Decl = @import("CodeGen.zig").Decl;
7
84const spec = @import("spec.zig");
95const Opcode = spec.Opcode;
106const Word = spec.Word;
117const Id = spec.Id;
128const StorageClass = spec.StorageClass;
13
149const Assembler = @This();
1510
1611cg: *CodeGen,
......@@ -20,7 +15,10 @@ src: []const u8 = undefined,
2015tokens: std.ArrayList(Token) = .empty,
2116current_token: u32 = 0,
2217/// The instruction that is currently being parsed or has just been parsed.
23inst: struct {
18inst: Instruction = .{},
19value_map: std.array_hash_map.String(AsmValue) = .empty,
20
21const Instruction = struct {
2422 opcode: Opcode = undefined,
2523 operands: std.ArrayList(Operand) = .empty,
2624 string_bytes: std.ArrayList(u8) = .empty,
......@@ -35,9 +33,7 @@ inst: struct {
3533 }
3634 return null;
3735 }
38} = .{},
39value_map: std.array_hash_map.String(AsmValue) = .empty,
40inst_map: std.array_hash_map.String(void) = .empty,
36};
4137
4238const Operand = union(enum) {
4339 /// Any 'simple' 32-bit value. This could be a mask or
......@@ -68,27 +64,14 @@ pub fn deinit(ass: *Assembler) void {
6864 ass.inst.operands.deinit(gpa);
6965 ass.inst.string_bytes.deinit(gpa);
7066 ass.value_map.deinit(gpa);
71 ass.inst_map.deinit(gpa);
7267}
7368
7469const Error = error{ AssembleFail, OutOfMemory };
7570
7671pub fn assemble(ass: *Assembler, src: []const u8) Error!void {
77 const gpa = ass.cg.gpa;
78
7972 ass.src = src;
8073 ass.errors.clearRetainingCapacity();
8174
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
9275 try ass.tokenize();
9376 while (!ass.testToken(.eof)) {
9477 try ass.parseInstruction();
......@@ -144,18 +127,20 @@ const AsmValue = union(enum) {
144127 /// zero-extended from the float's width to 64 bits.
145128 constant: u64,
146129 /// A vector "c" input expanded by `processSpecConstVector`.
147 constant_composite: struct {
130 constant_composite: ConstantComposite,
131 string: []const u8,
132
133 const ConstantComposite = struct {
148134 child: Id,
149135 child_kind: std.lang.TypeId,
150136 child_bit_width: u16,
151137 values: []u64,
152 },
153 string: []const u8,
138 };
154139
155140 /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue
156141 /// is of a variant that allows the result to be obtained (not an unresolved
157142 /// forward declaration, not in the process of being declared, etc).
158 pub fn resultId(value: AsmValue) Id {
143 fn resultId(value: AsmValue) Id {
159144 return switch (value) {
160145 .just_declared,
161146 .unresolved_forward_reference,
......@@ -221,7 +206,7 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue {
221206 const cg = ass.cg;
222207 const gpa = cg.gpa;
223208 const operands = ass.inst.operands.items;
224 const section = &cg.sections.globals;
209 const section = &cg.module;
225210 const id = switch (ass.inst.opcode) {
226211 .OpTypeVoid => try cg.voidType(),
227212 .OpTypeBool => try cg.boolType(),
......@@ -339,26 +324,16 @@ fn processTypeInstruction(ass: *Assembler) !AsmValue {
339324/// - Target section is determined from instruction type.
340325fn processGenericInstruction(ass: *Assembler) !?AsmValue {
341326 const cg = ass.cg;
342 const target = cg.zcu.getTarget();
343327 const operands = ass.inst.operands.items;
344 var maybe_spv_decl_index: ?Decl.Index = null;
345328 const section = switch (ass.inst.opcode.class()) {
346 .constant_creation => &cg.sections.globals,
347 .annotation => &cg.sections.annotations,
329 .constant_creation => &cg.module,
330 .annotation => &cg.module,
348331 .type_declaration => unreachable, // Handled elsewhere.
349332 else => switch (ass.inst.opcode) {
350 .OpEntryPoint => unreachable,
351 .OpExecutionMode, .OpExecutionModeId => &cg.sections.execution_modes,
352333 .OpVariable => section: {
353334 const storage_class: spec.StorageClass = @fromBackingInt(@intCast(operands[2].value));
354335 if (storage_class == .function) break :section &ass.cg.prologue;
355 maybe_spv_decl_index = try cg.allocDecl(.global);
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;
336 break :section &cg.module;
362337 },
363338 else => &ass.cg.body,
364339 },
......@@ -388,10 +363,7 @@ fn processGenericInstruction(ass: *Assembler) !?AsmValue {
388363 .value, .literal32 => |word| section.writeWord(word),
389364 .literal64 => |dword| section.writeDoubleWord(dword),
390365 .result_id => {
391 maybe_result_id = if (maybe_spv_decl_index) |spv_decl_index|
392 cg.declPtr(spv_decl_index).result_id
393 else
394 cg.allocId();
366 maybe_result_id = cg.allocId();
395367 section.writeOperand(Id, maybe_result_id.?);
396368 },
397369 .ref_id => |index| {
......@@ -406,7 +378,8 @@ fn processGenericInstruction(ass: *Assembler) !?AsmValue {
406378 }
407379
408380 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);
410383
411384 switch (ass.inst.opcode) {
412385 .OpKill,
......@@ -454,8 +427,8 @@ fn processSpecConstVector(ass: *Assembler) !?AsmValue {
454427 else => return ass.fail(ass.inst.inst_offset, "%ty must be a type", .{}),
455428 };
456429
457 const globals = &cg.sections.globals;
458 const annotations = &cg.sections.annotations;
430 const globals = &cg.module;
431 const annotations = &cg.module;
459432 const literal_words: usize = if (cc.child_bit_width <= @bitSizeOf(Word)) 1 else 2;
460433
461434 const elem_ids = try gpa.alloc(Id, cc.values.len);
......@@ -465,11 +438,10 @@ fn processSpecConstVector(ass: *Assembler) !?AsmValue {
465438 elem_id_out.* = elem_id;
466439
467440 switch (cc.child_kind) {
468 .bool => {
469 const opcode: Opcode = if (value & 1 != 0) .OpSpecConstantTrue else .OpSpecConstantFalse;
470 try globals.emitRaw(gpa, opcode, 2);
471 globals.writeOperand(Id, cc.child);
472 globals.writeOperand(Id, elem_id);
441 .bool => if (value & 1 != 0) {
442 try globals.emit(gpa, .OpSpecConstantTrue, .{ .id_result_type = cc.child, .id_result = elem_id });
443 } else {
444 try globals.emit(gpa, .OpSpecConstantFalse, .{ .id_result_type = cc.child, .id_result = elem_id });
473445 },
474446 .int, .float => {
475447 try globals.emitRaw(gpa, .OpSpecConstant, 2 + literal_words);
......@@ -487,10 +459,10 @@ fn processSpecConstVector(ass: *Assembler) !?AsmValue {
487459 const spec_id_word = std.math.cast(u32, spec_id_base + i) orelse {
488460 return ass.fail(ass.inst.inst_offset, "SpecId {} does not fit in 32 bits", .{spec_id_base + i});
489461 };
490 try annotations.emitRaw(gpa, .OpDecorate, 3);
491 annotations.writeOperand(Id, elem_id);
492 annotations.writeWord(@backingInt(spec.Decoration.spec_id));
493 annotations.writeWord(spec_id_word);
462 try annotations.emit(gpa, .OpDecorate, .{
463 .target = elem_id,
464 .decoration = .{ .spec_id = .{ .specialization_constant_id = spec_id_word } },
465 });
494466 }
495467
496468 const result_id = cg.allocId();
......@@ -557,14 +529,13 @@ fn parseInstruction(ass: *Assembler) !void {
557529 }
558530
559531 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 {
561533 return ass.fail(opcode_tok.start, "invalid opcode '{s}'", .{opcode_text});
562534 };
563535
564 const inst = spec.InstructionSet.core.instructions()[index];
565 ass.inst.opcode = @fromBackingInt(@intCast(inst.opcode));
536 ass.inst.opcode = opcode;
566537
567 const expected_operands = inst.operands;
538 const expected_operands = opcode.operands();
568539 // This is a loop because the result-id is not always the first operand.
569540 const requires_lhs_result = for (expected_operands) |op| {
570541 if (op.kind == .id_result) break true;
......@@ -614,8 +585,8 @@ fn parseOperand(ass: *Assembler, kind: spec.OperandKind) Error!void {
614585 else => switch (kind) {
615586 .literal_integer => try ass.parseLiteralInteger(),
616587 .literal_string => try ass.parseString(),
617 .literal_context_dependent_number => try ass.parseContextDependentNumber(),
618 .literal_ext_inst_integer => try ass.parseLiteralExtInstInteger(),
588 .literal_context_dependent_number => try ass.parseNumber(),
589 .literal_ext_inst_integer => try ass.parseLiteralInteger(),
619590 .pair_id_ref_id_ref => try ass.parsePhiSource(),
620591 else => return ass.todo("parse operand of type {s}", .{@tagName(kind)}),
621592 },
......@@ -661,12 +632,12 @@ fn parseBitEnum(ass: *Assembler, kind: spec.OperandKind) !void {
661632 if ((mask & enumerant.value) == 0)
662633 continue;
663634
664 for (enumerant.parameters) |param_kind| {
635 for (enumerant.parameters) |param| {
665636 if (ass.isAtInstructionBoundary()) {
666637 return ass.fail(ass.currentToken().start, "missing required parameter for bit flag '{s}'", .{enumerant.name});
667638 }
668639
669 try ass.parseOperand(param_kind);
640 try ass.parseOperand(param.kind);
670641 }
671642 }
672643}
......@@ -721,12 +692,12 @@ fn parseValueEnum(ass: *Assembler, kind: spec.OperandKind) !void {
721692
722693 try ass.inst.operands.append(gpa, .{ .value = enumerant.value });
723694
724 for (enumerant.parameters) |param_kind| {
695 for (enumerant.parameters) |param| {
725696 if (ass.isAtInstructionBoundary()) {
726697 return ass.fail(ass.currentToken().start, "missing required parameter for enum variant '{s}'", .{enumerant.name});
727698 }
728699
729 try ass.parseOperand(param_kind);
700 try ass.parseOperand(param.kind);
730701 }
731702}
732703
......@@ -746,30 +717,26 @@ fn parseRefId(ass: *Assembler) !void {
746717 try ass.inst.operands.append(gpa, .{ .ref_id = index });
747718}
748719
720fn 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
749732fn parseLiteralInteger(ass: *Assembler) !void {
750733 const gpa = ass.cg.gpa;
751734
752735 const tok = ass.currentToken();
753 if (ass.eatToken(.placeholder)) {
754 const name = ass.tokenText(tok)[1..];
755 const value = ass.value_map.get(name) orelse {
756 return ass.fail(tok.start, "invalid placeholder '${s}'", .{name});
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 }
736 if (try ass.eatPlaceholderConstant()) |literal| {
737 const literal32 = std.math.cast(u32, literal) orelse
738 return ass.fail(tok.start, "placeholder value {} does not fit in 32 bits", .{literal});
739 try ass.inst.operands.append(gpa, .{ .literal32 = literal32 });
773740 return;
774741 }
775742
......@@ -786,41 +753,6 @@ fn parseLiteralInteger(ass: *Assembler) !void {
786753 try ass.inst.operands.append(gpa, .{ .literal32 = value });
787754}
788755
789fn 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
824756fn parseString(ass: *Assembler) !void {
825757 const gpa = ass.cg.gpa;
826758
......@@ -844,7 +776,7 @@ fn parseString(ass: *Assembler) !void {
844776 try ass.inst.operands.append(gpa, .{ .string = string_offset });
845777}
846778
847fn parseContextDependentNumber(ass: *Assembler) !void {
779fn parseNumber(ass: *Assembler) !void {
848780 const cg = ass.cg;
849781 assert(ass.inst.opcode == .OpConstant or ass.inst.opcode == .OpSpecConstant);
850782
......@@ -852,14 +784,14 @@ fn parseContextDependentNumber(ass: *Assembler) !void {
852784 const result = try ass.resolveRef(ass.inst.operands.items[0].ref_id);
853785 const result_id = result.resultId();
854786
855 const words = cg.sections.globals.instructions.items;
787 const words = cg.module.instructions.items;
856788 var offset: usize = 0;
857789 while (offset < words.len) {
858 const word_count = words[offset] >> 16;
859 const opcode: Opcode = @fromBackingInt(@intCast(words[offset] & 0xFFFF));
790 const header: spec.InstructionHeader = @bitCast(words[offset]);
791 const word_count = header.word_count;
860792 defer offset += word_count;
861793 if (word_count == 0) break;
862 switch (opcode) {
794 switch (header.opcode) {
863795 .OpTypeInt => if (word_count >= 4 and @as(Id, @fromBackingInt(@intCast(words[offset + 1]))) == result_id) {
864796 const width: u16 = @intCast(words[offset + 2]);
865797 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
885817 const gpa = ass.cg.gpa;
886818
887819 const tok = ass.currentToken();
888 if (ass.eatToken(.placeholder)) {
889 const name = ass.tokenText(tok)[1..];
890 const value = ass.value_map.get(name) orelse {
891 return ass.fail(tok.start, "invalid placeholder '${s}'", .{name});
892 };
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 },
820 if (try ass.eatPlaceholderConstant()) |literal| {
821 if (width <= @bitSizeOf(spec.Word)) {
822 try ass.inst.operands.append(gpa, .{ .literal32 = @truncate(literal) });
823 } else {
824 try ass.inst.operands.append(gpa, .{ .literal64 = literal });
904825 }
905826 return;
906827 }
......@@ -943,22 +864,11 @@ fn parseContextDependentFloat(ass: *Assembler, comptime width: u16) !void {
943864 const Int = @Int(.unsigned, width);
944865
945866 const tok = ass.currentToken();
946 if (ass.eatToken(.placeholder)) {
947 const name = ass.tokenText(tok)[1..];
948 const value = ass.value_map.get(name) orelse {
949 return ass.fail(tok.start, "invalid placeholder '${s}'", .{name});
950 };
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 },
867 if (try ass.eatPlaceholderConstant()) |literal| {
868 if (width <= @bitSizeOf(spec.Word)) {
869 try ass.inst.operands.append(gpa, .{ .literal32 = @truncate(literal) });
870 } else {
871 try ass.inst.operands.append(gpa, .{ .literal64 = literal });
962872 }
963873 return;
964874 }
src/codegen/spirv/CodeGen.zig+734-1029
......@@ -10,32 +10,20 @@ base_line: u32,
1010
1111// Module-level output (accumulated across the nav's codegen)
1212next_result_id: Word = 1,
13decls: std.ArrayList(Decl) = .empty,
14decl_deps: std.ArrayList(Decl.Index) = .empty,
15nav_link: std.AutoHashMapUnmanaged(InternPool.Nav.Index, Decl.Index) = .empty,
16uav_link: std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Decl.Index) = .empty,
17entry_points: std.array_hash_map.Auto(Id, EntryPoint) = .empty,
18error_buffer: ?Decl.Index = null,
13navs: std.array_hash_map.Auto(InternPool.Nav.Index, Id) = .empty,
14externs: std.array_hash_map.Auto(InternPool.Nav.Index, Mir.Extern) = .empty,
15entry_points: std.ArrayList(Mir.EntryPoint) = .empty,
16error_buffer: ?Id = null,
1917struct_types: std.array_hash_map.Custom(StructType, Id, StructType.HashContext, true) = .empty,
2018/// SPIR-V ids of OpVariables whose pointee is a Block struct
2119block_var_ids: std.AutoHashMapUnmanaged(Id, void) = .empty,
22builtins: std.AutoHashMapUnmanaged(struct { spec.BuiltIn, spec.StorageClass }, Decl.Index) = .empty,
23sections: struct {
24 // Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module".
25 extended_instruction_set: Section = .{},
26 memory_model: Section = .{},
27 execution_modes: Section = .{},
28 debug_strings: Section = .{},
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)
36prologue: Section = .{},
37body: Section = .{},
38args: std.ArrayList(Id) = .empty,
20builtins: std.AutoHashMapUnmanaged(struct { spec.BuiltIn, spec.StorageClass }, Id) = .empty,
21module: Section = .empty,
22
23// Per-function state
24prologue: Section = .empty,
25body: Section = .empty,
26args: std.MultiArrayList(struct { id: Id, ty: Id }) = .empty,
3927next_arg_index: u32 = 0,
4028/// Caches the limb extractions for composite integer values so repeated
4129/// arithmetic on the same operand doesn't re-emit `OpCompositeExtract` per
......@@ -79,28 +67,6 @@ pub const Repr = enum {
7967 indirect,
8068};
8169
82/// A function or global, tracked here so the linker can order globals and build
83/// per-entry-point interface lists.
84pub 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
98pub const EntryPoint = struct {
99 decl_index: Decl.Index,
100 name: []const u8,
101 cc: std.builtin.CallingConvention,
102};
103
10470const StructType = struct {
10571 fields: []const Id,
10672 ip_index: InternPool.Index,
......@@ -193,24 +159,16 @@ pub fn deinit(cg: *CodeGen) void {
193159 cg.prologue.deinit(gpa);
194160 cg.body.deinit(gpa);
195161
196 cg.nav_link.deinit(gpa);
197 cg.uav_link.deinit(gpa);
162 cg.navs.deinit(gpa);
163 cg.externs.deinit(gpa);
198164
199 cg.sections.extended_instruction_set.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);
165 cg.module.deinit(gpa);
207166
208167 cg.struct_types.deinit(gpa);
209168 cg.block_var_ids.deinit(gpa);
210169 cg.builtins.deinit(gpa);
211170
212 cg.decls.deinit(gpa);
213 cg.decl_deps.deinit(gpa);
171 for (cg.entry_points.items) |ep| gpa.free(ep.name);
214172 cg.entry_points.deinit(gpa);
215173}
216174
......@@ -223,7 +181,10 @@ pub fn generate(
223181) codegen.Error!Mir {
224182 const zcu = pt.zcu;
225183 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)).?;
227188
228189 var arena = std.heap.ArenaAllocator.init(gpa);
229190 defer arena.deinit();
......@@ -235,22 +196,60 @@ pub fn generate(
235196 .zcu = zcu,
236197 .air = air.*,
237198 .liveness = liveness.*.?,
238 .owner_nav = nav,
239 .base_line = zcu.navSrcLine(nav),
199 .owner_nav = nav_index,
200 .base_line = zcu.navSrcLine(nav_index),
240201 };
241202 defer cg.deinit();
242203
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, {});
244223
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();
246243}
247244
248pub fn generateNav(
249 pt: Zcu.PerThread,
250 nav_index: InternPool.Nav.Index,
251) codegen.Error!Mir {
245pub fn generateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) codegen.Error!Mir {
252246 const zcu = pt.zcu;
253247 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);
254253
255254 var arena = std.heap.ArenaAllocator.init(gpa);
256255 defer arena.deinit();
......@@ -267,116 +266,118 @@ pub fn generateNav(
267266 };
268267 defer cg.deinit();
269268
270 try cg.genNav(false);
271
272 return cg.serializeToMir(gpa);
273}
269 const resolved = nav.resolved.?;
270 if (nav.getExtern(ip)) |ext| {
271 if (!ty.hasRuntimeBits(zcu)) {
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 }
274275
275fn serializeToMir(cg: *CodeGen, gpa: Allocator) codegen.Error!Mir {
276 const owner_entry = cg.nav_link.get(cg.owner_nav);
277 const owner_decl_index = owner_entry orelse return .{
278 .id_bound = cg.next_result_id,
279 .owner_nav = cg.owner_nav,
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 };
276 const as = resolved.@"addrspace";
277 const storage_class = cg.storageClass(as);
278 const result_id = try cg.resolveNav(nav_index);
279 const ty_id = try cg.pointeeType(as, ty, true);
280 const ptr_ty_id = try cg.ptrType(ty_id, storage_class);
295281
296 const owner_decl = cg.declPtr(owner_decl_index);
297
298 var nav_refs: std.ArrayList(Mir.NavRef) = .empty;
299 defer nav_refs.deinit(gpa);
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,
282 try cg.module.emit(gpa, .OpVariable, .{
283 .id_result_type = ptr_ty_id,
284 .id_result = result_id,
285 .storage_class = storage_class,
308286 });
309 }
310287
311 var uav_refs: std.ArrayList(Mir.UavRef) = .empty;
312 defer uav_refs.deinit(gpa);
313 var uav_it = cg.uav_link.iterator();
314 while (uav_it.next()) |entry| {
315 const decl = cg.declPtr(entry.value_ptr.*);
316 try uav_refs.append(gpa, .{
317 .local_id = decl.result_id,
318 .val = entry.key_ptr.*[0],
319 .storage_class = entry.key_ptr.*[1],
320 .kind = decl.kind,
321 });
322 }
288 switch (target.os.tag) {
289 .vulkan, .opengl => {
290 switch (storage_class) {
291 .uniform,
292 .push_constant,
293 .storage_buffer,
294 .physical_storage_buffer,
295 => {
296 if (ty.hasRuntimeBits(zcu)) {
297 if (!ty.isSpirvRuntimeArray(zcu)) {
298 try cg.decorate(ptr_ty_id, .{ .array_stride = .{
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 }
323310
324 var decl_deps: std.ArrayList(Mir.DeclDep) = .empty;
325 defer decl_deps.deinit(gpa);
326 var internal_globals: std.ArrayList(Id) = .empty;
327 defer internal_globals.deinit(gpa);
328
329 const deps = cg.decl_deps.items[owner_decl.begin_dep..owner_decl.end_dep];
330 for (deps) |dep_index| {
331 const dep_decl = cg.declPtr(dep_index);
332 var found = false;
333 nav_it.index = 0;
334 while (nav_it.next()) |entry| {
335 if (entry.value_ptr.* == dep_index) {
336 try decl_deps.append(gpa, .{
337 .kind = dep_decl.kind,
338 .nav = entry.key_ptr.*,
339 });
340 found = true;
341 break;
342 }
311 if (ext.decoration) |decoration| switch (decoration) {
312 .location => |location| {
313 try cg.decorate(result_id, .{
314 .location = .{ .location = location },
315 });
316 },
317 .flat => |location| {
318 try cg.decorate(result_id, .{ .location = .{ .location = location } });
319 try cg.decorate(result_id, .flat);
320 },
321 .descriptor => |descriptor| {
322 try cg.decorate(result_id, .{ .binding = .{
323 .binding_point = descriptor.binding,
324 } });
325 try cg.decorate(result_id, .{ .descriptor_set = .{
326 .descriptor_set = descriptor.set,
327 } });
328 },
329 };
330 },
331 else => {},
343332 }
344 if (!found and dep_decl.kind == .global) {
345 try internal_globals.append(gpa, dep_decl.result_id);
333
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 } });
346336 }
347 }
348337
349 var ep_list: std.ArrayList(Mir.EntryPoint) = .empty;
350 defer ep_list.deinit(gpa);
351 var ep_it = cg.entry_points.iterator();
352 while (ep_it.next()) |entry| {
353 const ep = entry.value_ptr;
354 const ep_decl = cg.declPtr(ep.decl_index);
355 try ep_list.append(gpa, .{
356 .local_id = ep_decl.result_id,
357 .name = try gpa.dupe(u8, ep.name),
358 .cc = ep.cc,
338 try cg.debugName(result_id, nav.fqn.toSlice(ip));
339 } else if (resolved.@"threadlocal" and ty.hasRuntimeBits(zcu)) {
340 if (target.os.tag == .opencl) return cg.todo("threadlocal variables on OpenCL", .{});
341
342 const ty_id = try cg.resolveType(ty, .indirect);
343 const ptr_ty_id = try cg.ptrType(ty_id, .private);
344 const init_id = try cg.constant(ty, val, .indirect);
345
346 const result_id = try cg.resolveNav(nav_index);
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,
359352 });
353 try cg.debugName(result_id, nav.fqn.toSlice(ip));
360354 }
355 return cg.toOwnedMir();
356}
361357
362 return .{
358fn 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 = .{
363372 .id_bound = cg.next_result_id,
364 .owner_nav = cg.owner_nav,
365 .kind = owner_decl.kind,
366 .decl_result_id = owner_decl.result_id,
367 .extended_instruction_set = try cg.sections.extended_instruction_set.instructions.toOwnedSlice(gpa),
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),
373 .module = cg.module,
374 .nav_refs = nav_refs,
375 .externs = externs,
376 .entry_points = entry_points,
379377 };
378 cg.module = .empty;
379
380 return mir;
380381}
381382
382383fn typeOf(cg: *CodeGen, inst: Air.Inst.Ref) Type {
......@@ -389,22 +390,50 @@ fn typeOfIndex(cg: *CodeGen, inst: Air.Inst.Index) Type {
389390 return cg.air.typeOfIndex(inst, &zcu.intern_pool);
390391}
391392
392/// Does not generate the nav.
393pub fn resolveNav(cg: *CodeGen, ip: *InternPool, nav_index: InternPool.Nav.Index) !Decl.Index {
394 const entry = try cg.nav_link.getOrPut(cg.gpa, nav_index);
395 if (!entry.found_existing) {
396 const nav = ip.getNav(nav_index);
397 // TODO: Extern fn?
398 const kind: Decl.Kind = if (ip.isFunctionType(nav.resolved.?.type))
399 .func
400 else switch (nav.resolved.?.@"addrspace") {
401 .generic => .invocation_global,
402 else => .global,
403 };
404 entry.value_ptr.* = try cg.allocDecl(kind);
393pub fn resolveNav(cg: *CodeGen, nav_index: InternPool.Nav.Index) !Id {
394 const gop = try cg.navs.getOrPut(cg.gpa, nav_index);
395 if (!gop.found_existing) gop.value_ptr.* = cg.allocId();
396 return gop.value_ptr.*;
397}
398
399fn resolveExternFn(cg: *CodeGen, nav_index: InternPool.Nav.Index) !Id {
400 const gpa = cg.gpa;
401 const zcu = cg.zcu;
402 const ip = &zcu.intern_pool;
403 if (cg.externs.get(nav_index)) |ext| return ext.id;
404
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));
405416 }
406417
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;
408437}
409438
410439pub fn allocIds(cg: *CodeGen, n: u32) spec.IdRange {
......@@ -420,34 +449,10 @@ pub fn idBound(cg: *const CodeGen) Word {
420449 return cg.next_result_id;
421450}
422451
423pub 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
447452pub fn importInstructionSet(cg: *CodeGen, set: spec.InstructionSet) !Id {
448453 assert(set != .core);
449454 const result_id = cg.allocId();
450 try cg.sections.extended_instruction_set.emit(cg.gpa, .OpExtInstImport, .{
455 try cg.module.emit(cg.gpa, .OpExtInstImport, .{
451456 .id_result = result_id,
452457 .name = @tagName(set),
453458 });
......@@ -456,7 +461,7 @@ pub fn importInstructionSet(cg: *CodeGen, set: spec.InstructionSet) !Id {
456461
457462pub fn boolType(cg: *CodeGen) !Id {
458463 const result_id = cg.allocId();
459 try cg.sections.globals.emit(cg.gpa, .OpTypeBool, .{
464 try cg.module.emit(cg.gpa, .OpTypeBool, .{
460465 .id_result = result_id,
461466 });
462467 return result_id;
......@@ -464,20 +469,17 @@ pub fn boolType(cg: *CodeGen) !Id {
464469
465470pub fn voidType(cg: *CodeGen) !Id {
466471 const result_id = cg.allocId();
467 try cg.sections.globals.emit(cg.gpa, .OpTypeVoid, .{
472 try cg.module.emit(cg.gpa, .OpTypeVoid, .{
468473 .id_result = result_id,
469474 });
470475 try cg.debugName(result_id, "void");
471476 return result_id;
472477}
473478
474pub fn opaqueType(cg: *CodeGen, name: []const u8) !Id {
479pub fn opaqueType(cg: *CodeGen, comptime fmt: []const u8, args: anytype) !Id {
475480 const result_id = cg.allocId();
476 try cg.sections.globals.emit(cg.gpa, .OpTypeOpaque, .{
477 .id_result = result_id,
478 .literal_string = name,
479 });
480 try cg.debugName(result_id, name);
481 try cg.module.emitFormatted(cg.gpa, .OpTypeOpaque, &.{result_id}, fmt, args);
482 try cg.debugNameFmt(result_id, fmt, args);
481483 return result_id;
482484}
483485
......@@ -513,7 +515,7 @@ pub fn intType(cg: *CodeGen, signedness: std.lang.Signedness, bits: u16) !Id {
513515 const limb_ty = try cg.intType(.unsigned, limb_bits);
514516 const len_ty = try cg.intType(.unsigned, 32);
515517 const len_id = cg.allocId();
516 try cg.sections.globals.emit(cg.gpa, .OpConstant, .{
518 try cg.module.emit(cg.gpa, .OpConstant, .{
517519 .id_result_type = len_ty,
518520 .id_result = len_id,
519521 .value = .{ .uint32 = backing_bits / limb_bits },
......@@ -522,7 +524,7 @@ pub fn intType(cg: *CodeGen, signedness: std.lang.Signedness, bits: u16) !Id {
522524 }
523525
524526 const result_id = cg.allocId();
525 try cg.sections.globals.emit(cg.gpa, .OpTypeInt, .{
527 try cg.module.emit(cg.gpa, .OpTypeInt, .{
526528 .id_result = result_id,
527529 .width = backing_bits,
528530 .signedness = switch (actual_signedness) {
......@@ -540,7 +542,7 @@ pub fn intType(cg: *CodeGen, signedness: std.lang.Signedness, bits: u16) !Id {
540542pub fn floatType(cg: *CodeGen, bits: u16) !Id {
541543 assert(bits > 0);
542544 const result_id = cg.allocId();
543 try cg.sections.globals.emit(cg.gpa, .OpTypeFloat, .{
545 try cg.module.emit(cg.gpa, .OpTypeFloat, .{
544546 .id_result = result_id,
545547 .width = bits,
546548 });
......@@ -550,7 +552,7 @@ pub fn floatType(cg: *CodeGen, bits: u16) !Id {
550552
551553pub fn vectorType(cg: *CodeGen, len: u32, child_ty_id: Id) !Id {
552554 const result_id = cg.allocId();
553 try cg.sections.globals.emit(cg.gpa, .OpTypeVector, .{
555 try cg.module.emit(cg.gpa, .OpTypeVector, .{
554556 .id_result = result_id,
555557 .component_type = child_ty_id,
556558 .component_count = len,
......@@ -560,7 +562,7 @@ pub fn vectorType(cg: *CodeGen, len: u32, child_ty_id: Id) !Id {
560562
561563pub fn arrayType(cg: *CodeGen, len_id: Id, child_ty_id: Id) !Id {
562564 const result_id = cg.allocId();
563 try cg.sections.globals.emit(cg.gpa, .OpTypeArray, .{
565 try cg.module.emit(cg.gpa, .OpTypeArray, .{
564566 .id_result = result_id,
565567 .element_type = child_ty_id,
566568 .length = len_id,
......@@ -570,7 +572,7 @@ pub fn arrayType(cg: *CodeGen, len_id: Id, child_ty_id: Id) !Id {
570572
571573pub fn ptrType(cg: *CodeGen, child_ty_id: Id, storage_class: spec.StorageClass) !Id {
572574 const result_id = cg.allocId();
573 try cg.sections.globals.emit(cg.gpa, .OpTypePointer, .{
575 try cg.module.emit(cg.gpa, .OpTypePointer, .{
574576 .id_result = result_id,
575577 .storage_class = storage_class,
576578 .type = child_ty_id,
......@@ -589,7 +591,7 @@ pub fn structType(
589591 if (cg.struct_types.get(.{ .fields = types, .ip_index = actual_ip_index })) |id| return id;
590592 const result_id = cg.allocId();
591593 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, .{
593595 .id_result = result_id,
594596 .id_ref = types_dup,
595597 });
......@@ -639,7 +641,7 @@ pub fn layoutType(cg: *CodeGen, ty: Type, is_block_root: bool) Error!Id {
639641 } });
640642 try member_types.append(gpa, try cg.layoutType(field_ty, false));
641643 }
642 try cg.sections.globals.emit(gpa, .OpTypeStruct, .{
644 try cg.module.emit(gpa, .OpTypeStruct, .{
643645 .id_result = id,
644646 .id_ref = member_types.items,
645647 });
......@@ -682,7 +684,7 @@ pub fn layoutType(cg: *CodeGen, ty: Type, is_block_root: bool) Error!Id {
682684 try cg.decorate(arr_id, .{ .array_stride = .{ .array_stride = 1 } });
683685 member_types[layout.padding_index] = arr_id;
684686 }
685 try cg.sections.globals.emit(gpa, .OpTypeStruct, .{
687 try cg.module.emit(gpa, .OpTypeStruct, .{
686688 .id_result = id,
687689 .id_ref = member_types[0..layout.total_fields],
688690 });
......@@ -705,7 +707,7 @@ pub fn layoutType(cg: *CodeGen, ty: Type, is_block_root: bool) Error!Id {
705707 const elem_ty = ty.childType(zcu);
706708 const elem_ty_id = try cg.layoutType(elem_ty, is_block_root);
707709 const id = cg.allocId();
708 try cg.sections.globals.emit(gpa, .OpTypeRuntimeArray, .{
710 try cg.module.emit(gpa, .OpTypeRuntimeArray, .{
709711 .id_result = id,
710712 .element_type = elem_ty_id,
711713 });
......@@ -724,7 +726,7 @@ pub fn layoutType(cg: *CodeGen, ty: Type, is_block_root: bool) Error!Id {
724726
725727pub fn functionType(cg: *CodeGen, return_ty_id: Id, param_type_ids: []const Id) !Id {
726728 const result_id = cg.allocId();
727 try cg.sections.globals.emit(cg.gpa, .OpTypeFunction, .{
729 try cg.module.emit(cg.gpa, .OpTypeFunction, .{
728730 .id_result = result_id,
729731 .return_type = return_ty_id,
730732 .id_ref_2 = param_type_ids,
......@@ -734,7 +736,7 @@ pub fn functionType(cg: *CodeGen, return_ty_id: Id, param_type_ids: []const Id)
734736
735737pub fn constUndef(cg: *CodeGen, ty_id: Id) !Id {
736738 const result_id = cg.allocId();
737 try cg.sections.globals.emit(cg.gpa, .OpUndef, .{
739 try cg.module.emit(cg.gpa, .OpUndef, .{
738740 .id_result_type = ty_id,
739741 .id_result = result_id,
740742 });
......@@ -743,7 +745,7 @@ pub fn constUndef(cg: *CodeGen, ty_id: Id) !Id {
743745
744746pub fn constNull(cg: *CodeGen, ty_id: Id) !Id {
745747 const result_id = cg.allocId();
746 try cg.sections.globals.emit(cg.gpa, .OpConstantNull, .{
748 try cg.module.emit(cg.gpa, .OpConstantNull, .{
747749 .id_result_type = ty_id,
748750 .id_result = result_id,
749751 });
......@@ -755,7 +757,7 @@ pub fn decorate(
755757 target: Id,
756758 decoration: spec.Decoration.Extended,
757759) !void {
758 try cg.sections.annotations.emit(cg.gpa, .OpDecorate, .{
760 try cg.module.emit(cg.gpa, .OpDecorate, .{
759761 .target = target,
760762 .decoration = decoration,
761763 });
......@@ -767,29 +769,16 @@ pub fn decorateMember(
767769 member: u32,
768770 decoration: spec.Decoration.Extended,
769771) !void {
770 try cg.sections.annotations.emit(cg.gpa, .OpMemberDecorate, .{
772 try cg.module.emit(cg.gpa, .OpMemberDecorate, .{
771773 .structure_type = structure_type,
772774 .member = member,
773775 .decoration = decoration,
774776 });
775777}
776778
777pub 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
786pub fn declPtr(cg: *CodeGen, index: Decl.Index) *Decl {
787 return &cg.decls.items[@backingInt(index)];
788}
789
790779pub fn debugName(cg: *CodeGen, target: Id, name: []const u8) !void {
791780 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, .{
793782 .target = target,
794783 .name = name,
795784 });
......@@ -797,14 +786,12 @@ pub fn debugName(cg: *CodeGen, target: Id, name: []const u8) !void {
797786
798787pub fn debugNameFmt(cg: *CodeGen, target: Id, comptime fmt: []const u8, args: anytype) !void {
799788 if (cg.zcu.comp.config.root_strip) return;
800 const name = try std.fmt.allocPrint(cg.gpa, fmt, args);
801 defer cg.gpa.free(name);
802 try cg.debugName(target, name);
789 try cg.module.emitFormatted(cg.gpa, .OpName, &.{target}, fmt, args);
803790}
804791
805792pub fn memberDebugName(cg: *CodeGen, target: Id, member: u32, name: []const u8) !void {
806793 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, .{
808795 .type = target,
809796 .member = member,
810797 .name = name,
......@@ -827,6 +814,7 @@ pub fn storageClass(cg: *const CodeGen, as: std.lang.AddressSpace) spec.StorageC
827814 .constant => .uniform_constant,
828815 .shared => .workgroup,
829816 .local => .function,
817 .private => .private,
830818 .input => .input,
831819 .gs,
832820 .fs,
......@@ -850,228 +838,6 @@ pub fn storageClass(cg: *const CodeGen, as: std.lang.AddressSpace) spec.StorageC
850838
851839const Error = codegen.Error;
852840
853pub 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
1075841fn decorateLayout(cg: *CodeGen, ty: Type, ty_id: spec.Id) Error!void {
1076842 const zcu = cg.zcu;
1077843 const ip = &zcu.intern_pool;
......@@ -1174,123 +940,49 @@ fn importExtendedSet(cg: *CodeGen) !Id {
1174940/// Fetch the result-id for a previously generated instruction or constant.
1175941fn resolve(cg: *CodeGen, inst: Air.Inst.Ref) !Id {
1176942 const zcu = cg.zcu;
1177 const ip = &zcu.intern_pool;
1178943 if (inst.toInterned()) |val_ip_index| {
1179944 const ty = cg.typeOf(inst);
1180945 if (ty.zigTypeTag(zcu) == .@"fn") {
1181946 const val_key = zcu.intern_pool.indexToKey(val_ip_index);
1182 const fn_nav = switch (val_key) {
1183 .@"extern" => |@"extern"| @"extern".owner_nav,
1184 .func => |func| func.owner_nav,
947 return switch (val_key) {
948 .@"extern" => |@"extern"| try cg.resolveExternFn(@"extern".owner_nav),
949 .func => |func| try cg.resolveNav(func.owner_nav),
1185950 else => unreachable,
1186951 };
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;
1196952 }
1197953
1198 return try cg.constant(ty, .fromInterned(val_ip_index), .direct);
1199 }
1200 const index = inst.toIndex().?;
1201 return cg.inst_results.get(index).?; // Assertion means instruction does not dominate usage.
1202}
1203
1204fn resolveUav(cg: *CodeGen, val: InternPool.Index) !Id {
1205 const gpa = cg.gpa;
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;
954 const val: Value = .fromInterned(val_ip_index);
955 if (ty.isPtrAtRuntime(zcu)) {
956 if (val.isUndef(zcu)) return try cg.constUndef(try cg.resolveType(ty, .direct));
957 if (ty.zigTypeTag(zcu) == .optional) {
958 const payload = val.optionalValue(zcu) orelse return try cg.constNull(try cg.resolveType(ty, .direct));
959 return try cg.constantPtr(payload);
960 }
961 return try cg.constantPtr(val);
1244962 }
1245
1246 cg.prologue = .{};
1247 cg.body = .{};
1248 defer {
1249 cg.prologue.deinit(gpa);
1250 cg.body.deinit(gpa);
963 switch (zcu.intern_pool.indexToKey(val_ip_index)) {
964 .ptr => unreachable,
965 .slice => |slice| {
966 const ptr_id = try cg.constantPtr(.fromInterned(slice.ptr));
967 const len_id = try cg.constant(.usize, .fromInterned(slice.len), .indirect);
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),
1251982 }
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 });
1291983 }
1292
1293 return result_id;
984 const index = inst.toIndex().?;
985 return cg.inst_results.get(index).?; // Assertion means instruction does not dominate usage.
1294986}
1295987
1296988fn resolvePtr(cg: *CodeGen, ref: Air.Inst.Ref) !Ptr {
......@@ -1299,19 +991,6 @@ fn resolvePtr(cg: *CodeGen, ref: Air.Inst.Ref) !Ptr {
1299991 return .{ .id = id };
1300992}
1301993
1302fn 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
1315994/// Start a new SPIR-V block, Emits the label of the new block, and stores which
1316995/// block we are currently generating.
1317996/// 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 {
14391118 const result_ty_id = try cg.boolType();
14401119 const result_id = cg.allocId();
14411120 switch (value) {
1442 inline else => |value_ct| try cg.sections.globals.emit(
1121 inline else => |value_ct| try cg.module.emit(
14431122 cg.gpa,
14441123 if (value_ct) .OpConstantTrue else .OpConstantFalse,
14451124 .{ .id_result_type = result_ty_id, .id_result = result_id },
......@@ -1485,7 +1164,7 @@ fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id {
14851164 if (i < 2) @as(u32, @truncate(value64 >> @intCast(i * 32))) else fill,
14861165 );
14871166 }
1488 return cg.constructComposite(result_ty_id, constituents);
1167 return cg.constantComposite(result_ty_id, constituents);
14891168 }
14901169
14911170 const final_value: spec.LiteralContextDependentNumber = switch (target.os.tag) {
......@@ -1515,14 +1194,29 @@ fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id {
15151194 };
15161195
15171196 const result_id = cg.allocId();
1518 try cg.sections.globals.emit(cg.gpa, .OpConstant, .{
1197 try cg.module.emit(cg.gpa, .OpConstant, .{
15191198 .id_result_type = result_ty_id,
15201199 .id_result = result_id,
15211200 .value = final_value,
15221201 });
15231202
15241203 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
1212fn 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;
15261220}
15271221
15281222/// Construct a composite value from its constituents.
......@@ -1541,7 +1235,7 @@ pub fn constructComposite(cg: *CodeGen, result_ty_id: Id, constituents: []const
15411235 for (fields, constituents, 0..) |field_ty_id, constituent, i| {
15421236 const field_ptr_ty_id = try cg.ptrType(field_ty_id, .function);
15431237 const index_id = cg.allocId();
1544 try cg.sections.globals.emit(gpa, .OpConstant, .{
1238 try cg.module.emit(gpa, .OpConstant, .{
15451239 .id_result_type = u32_ty_id,
15461240 .id_result = index_id,
15471241 .value = .{ .uint32 = @intCast(i) },
......@@ -1572,21 +1266,6 @@ pub fn constructComposite(cg: *CodeGen, result_ty_id: Id, constituents: []const
15721266
15731267/// Construct a composite at runtime with all lanes set to the same value.
15741268/// ty must be an aggregate type.
1575fn 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
15901269/// This function generates a load for a constant in direct (ie, non-memory) representation.
15911270/// When the constant is simple, it can be generated directly using OpConstant instructions.
15921271/// 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 {
16021281 const result_ty_id = try cg.resolveType(ty, repr);
16031282 const ip = &zcu.intern_pool;
16041283
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
16061291 if (val.isUndef(zcu)) {
16071292 return cg.constUndef(result_ty_id);
16081293 }
16091294
1610 const cacheable_id = cache: {
1611 switch (ip.indexToKey(val.toIntern())) {
1612 .int_type,
1613 .ptr_type,
1614 .array_type,
1615 .vector_type,
1616 .opt_type,
1617 .anyframe_type,
1618 .error_union_type,
1619 .simple_type,
1620 .struct_type,
1621 .tuple_type,
1622 .union_type,
1623 .opaque_type,
1624 .spirv_type,
1625 .enum_type,
1626 .func_type,
1627 .error_set_type,
1628 .inferred_error_set_type,
1629 => unreachable, // types, not values
1630
1631 .undef => unreachable, // handled above
1632
1633 .@"extern",
1634 .func,
1635 .enum_literal,
1636 => unreachable, // non-runtime values
1295 const cacheable_id = cache: switch (ip.indexToKey(val.toIntern())) {
1296 .int_type,
1297 .ptr_type,
1298 .array_type,
1299 .vector_type,
1300 .opt_type,
1301 .anyframe_type,
1302 .error_union_type,
1303 .simple_type,
1304 .struct_type,
1305 .tuple_type,
1306 .union_type,
1307 .opaque_type,
1308 .spirv_type,
1309 .enum_type,
1310 .func_type,
1311 .error_set_type,
1312 .inferred_error_set_type,
1313 => unreachable, // types, not values
1314
1315 .undef => unreachable, // handled above
1316
1317 .@"extern",
1318 .func,
1319 .enum_literal,
1320 => unreachable, // non-runtime values
16371321
1638 .simple_value => |simple_value| switch (simple_value) {
1639 .void,
1640 .null,
1641 .@"unreachable",
1642 => unreachable, // non-runtime values
1322 .simple_value => |simple_value| switch (simple_value) {
1323 .void,
1324 .null,
1325 .@"unreachable",
1326 => unreachable, // non-runtime values
16431327
1644 .false, .true => break :cache try cg.constBool(val.toBool(), repr),
1645 },
1646 .int => {
1647 const int_info = ty.intInfo(zcu);
1648 const backing_bits, const is_big_int = cg.backingIntBits(int_info.bits);
1649 if (is_big_int) {
1650 const limb_bits = cg.bigIntBits();
1651 const n_limbs = backing_bits / limb_bits;
1652 const big_result_ty_id = try cg.resolveType(ty, .indirect);
1653 var bigint_space: Value.BigIntSpace = undefined;
1654 const bigint = val.toBigInt(&bigint_space, zcu);
1655 const limb_bytes = try gpa.alloc(u8, backing_bits / 8);
1656 defer gpa.free(limb_bytes);
1657 bigint.writeTwosComplement(limb_bytes, .little);
1658 const scratch_top = cg.id_scratch.items.len;
1659 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
1660 const constituents = try cg.id_scratch.addManyAsSlice(gpa, n_limbs);
1661 switch (limb_bits) {
1662 32 => {
1663 const limbs_u32: []u32 = @ptrCast(@alignCast(limb_bytes));
1664 for (constituents, limbs_u32) |*c, v| {
1665 const host_v = if (builtin.cpu.arch.endian() == .big) @byteSwap(v) else v;
1666 c.* = try cg.constInt(.u32, host_v);
1667 }
1668 },
1669 64 => {
1670 const limbs_u64: []u64 = @ptrCast(@alignCast(limb_bytes));
1671 for (constituents, limbs_u64) |*c, v| {
1672 const host_v = if (builtin.cpu.arch.endian() == .big) @byteSwap(v) else v;
1673 c.* = try cg.constInt(.u64, host_v);
1674 }
1675 },
1676 else => unreachable,
1677 }
1678 break :cache try cg.constructComposite(big_result_ty_id, constituents);
1328 .false, .true => break :cache try cg.constBool(val.toBool(), repr),
1329 },
1330 .int => {
1331 const int_info = ty.intInfo(zcu);
1332 const backing_bits, const is_big_int = cg.backingIntBits(int_info.bits);
1333 if (is_big_int) {
1334 const limb_bits = cg.bigIntBits();
1335 const n_limbs = backing_bits / limb_bits;
1336 const big_result_ty_id = try cg.resolveType(ty, .indirect);
1337 var bigint_space: Value.BigIntSpace = undefined;
1338 const bigint = val.toBigInt(&bigint_space, zcu);
1339 const limb_bytes = try gpa.alloc(u8, backing_bits / 8);
1340 defer gpa.free(limb_bytes);
1341 bigint.writeTwosComplement(limb_bytes, .little);
1342 const scratch_top = cg.id_scratch.items.len;
1343 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
1344 const constituents = try cg.id_scratch.addManyAsSlice(gpa, n_limbs);
1345 switch (limb_bits) {
1346 32 => {
1347 const limbs_u32: []u32 = @ptrCast(@alignCast(limb_bytes));
1348 for (constituents, limbs_u32) |*c, v| {
1349 const host_v = if (builtin.cpu.arch.endian() == .big) @byteSwap(v) else v;
1350 c.* = try cg.constInt(.u32, host_v);
1351 }
1352 },
1353 64 => {
1354 const limbs_u64: []u64 = @ptrCast(@alignCast(limb_bytes));
1355 for (constituents, limbs_u64) |*c, v| {
1356 const host_v = if (builtin.cpu.arch.endian() == .big) @byteSwap(v) else v;
1357 c.* = try cg.constInt(.u64, host_v);
1358 }
1359 },
1360 else => unreachable,
16791361 }
1680 if (ty.isSignedInt(zcu)) {
1681 break :cache try cg.constInt(ty, val.toSignedInt(zcu));
1362 break :cache try cg.constantComposite(big_result_ty_id, constituents);
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);
16821444 } else {
1683 break :cache try cg.constInt(ty, val.toUnsignedInt(zcu));
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;
1445 break :cache try cg.constNull(result_ty_id);
17221446 }
1447 }
17231448
1724 const payload_val_id = switch (error_union.val) {
1725 .err_name => try cg.constant(payload_ty, .undef, .indirect),
1726 .payload => |p| try cg.constant(payload_ty, .fromInterned(p), .indirect),
1449 // Optional representation is a structure.
1450 // { Payload, Bool }
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,
17271473 };
17281474
1729 var constituents: [2]Id = undefined;
1730 var types: [2]Type = undefined;
1731 if (eu_layout.error_first) {
1732 constituents[0] = err_val_id;
1733 constituents[1] = payload_val_id;
1734 types = .{ err_ty, payload_ty };
1735 } else {
1736 constituents[0] = payload_val_id;
1737 constituents[1] = err_val_id;
1738 types = .{ payload_ty, err_ty };
1475 switch (aggregate.storage) {
1476 .bytes => |bytes| {
1477 // TODO: This is really space inefficient, perhaps there is a better
1478 // way to do it?
1479 for (constituents, bytes.toSlice(constituents.len, ip)) |*constituent, byte| {
1480 constituent.* = try cg.constInt(elem_ty, byte);
1481 }
1482 },
1483 .elems => |elems| {
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 },
17391491 }
17401492
17411493 const comp_ty_id = try cg.resolveType(ty, .direct);
1742 return try cg.constructComposite(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 });
1494 return cg.constantComposite(comp_ty_id, constituents);
17551495 },
1756 .opt => {
1757 const payload_ty = ty.optionalChild(zcu);
1758 const maybe_payload_val = val.optionalValue(zcu);
1759
1760 if (!payload_ty.hasRuntimeBits(zcu)) {
1761 break :cache try cg.constBool(maybe_payload_val != null, .indirect);
1762 } else if (ty.optionalReprIsPayload(zcu)) {
1763 // Optional representation is a nullable pointer or slice.
1764 if (maybe_payload_val) |payload_val| {
1765 return try cg.constant(payload_ty, payload_val, .indirect);
1766 } else {
1767 break :cache try cg.constNull(result_ty_id);
1496 .struct_type => {
1497 const struct_type = zcu.typeToStruct(ty).?;
1498 assert(struct_type.layout != .@"packed"); // packed structs use `bitpack`
1499
1500 var types: std.ArrayList(Type) = .empty;
1501 defer types.deinit(gpa);
1502
1503 var constituents: std.ArrayList(Id) = .empty;
1504 defer constituents.deinit(gpa);
1505
1506 var it = struct_type.iterateRuntimeOrder(ip);
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;
17681512 }
1769 }
17701513
1771 // Optional representation is a structure.
1772 // { Payload, Bool }
1514 // TODO: Padding?
1515 const field_val = try val.fieldValue(pt, field_index);
1516 const field_id = try cg.constant(field_ty, field_val, .indirect);
17731517
1774 const has_pl_id = try cg.constBool(maybe_payload_val != null, .indirect);
1775 const payload_id = if (maybe_payload_val) |payload_val|
1776 try cg.constant(payload_ty, payload_val, .indirect)
1777 else
1778 try cg.constUndef(try cg.resolveType(payload_ty, .indirect));
1518 try types.append(gpa, field_ty);
1519 try constituents.append(gpa, field_id);
1520 }
17791521
17801522 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);
17821524 },
1783 .aggregate => |aggregate| switch (ip.indexToKey(ty.ip_index)) {
1784 inline .array_type, .vector_type => |array_type, tag| {
1785 const elem_ty: Type = .fromInterned(array_type.child);
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)));
1525 .tuple_type => |tuple| {
1526 var constituents: std.ArrayList(Id) = .empty;
1527 defer constituents.deinit(gpa);
17901528
1791 const child_repr: Repr = switch (tag) {
1792 .array_type => .indirect,
1793 .vector_type => .direct,
1794 else => unreachable,
1795 };
1529 for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty, field_val, i| {
1530 if (field_val != .none) continue;
1531 const ft: Type = .fromInterned(field_ty);
1532 if (!ft.hasRuntimeBits(zcu)) continue;
17961533
1797 switch (aggregate.storage) {
1798 .bytes => |bytes| {
1799 // TODO: This is really space inefficient, perhaps there is a better
1800 // way to do it?
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 }
1534 const fv = try val.fieldValue(pt, i);
1535 const field_id = try cg.constant(ft, fv, .indirect);
1536 try constituents.append(gpa, field_id);
1537 }
18141538
1815 const comp_ty_id = try cg.resolveType(ty, .direct);
1816 return cg.constructComposite(comp_ty_id, constituents);
1817 },
1818 .struct_type => {
1819 const struct_type = zcu.typeToStruct(ty).?;
1820 assert(struct_type.layout != .@"packed"); // packed structs use `bitpack`
1539 const comp_ty_id = try cg.resolveType(ty, .direct);
1540 return cg.constantComposite(comp_ty_id, constituents.items);
1541 },
1542 else => unreachable,
1543 },
1544 .un => |un| {
1545 assert(ty.containerLayout(zcu) != .@"packed");
1546 if (un.tag == .none) return cg.todo("constant of untagged union", .{});
18211547
1822 var types: std.ArrayList(Type) = .empty;
1823 defer types.deinit(gpa);
1548 const active_field = ty.unionTagFieldIndex(.fromInterned(un.tag), zcu).?;
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 }
18241556
1825 var constituents: std.ArrayList(Id) = .empty;
1826 defer constituents.deinit(gpa);
1557 const constituents = try gpa.alloc(Id, union_obj.field_types.len + 1);
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 }
18271576
1828 var it = struct_type.iterateRuntimeOrder(ip);
1829 while (it.next()) |field_index| {
1830 const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]);
1831 if (!field_ty.hasRuntimeBits(zcu)) {
1832 // This is a zero-bit field - we only needed it for the alignment.
1833 continue;
1834 }
1577 var len = union_obj.field_types.len;
1578 if (ty.unionTagTypeRuntime(zcu)) |tag_ty| {
1579 constituents[len] = try cg.constant(tag_ty, .fromInterned(un.tag), .indirect);
1580 len += 1;
1581 }
18351582
1836 // TODO: Padding?
1837 const field_val = try val.fieldValue(pt, field_index);
1838 const field_id = try cg.constant(field_ty, field_val, .indirect);
1583 return cg.constantComposite(result_ty_id, constituents[0..len]);
1584 }
18391585
1840 try types.append(gpa, field_ty);
1841 try constituents.append(gpa, field_id);
1842 }
1586 const layout = cg.unionLayout(ty);
1587 if (field_ty.hasRuntimeBits(zcu) and layout.payload_ty.eql(field_ty)) {
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 );
18431595
1844 const comp_ty_id = try cg.resolveType(ty, .direct);
1845 return try cg.constructComposite(comp_ty_id, constituents.items);
1846 },
1847 .tuple_type => |tuple| {
1848 var constituents: std.ArrayList(Id) = .empty;
1849 defer constituents.deinit(gpa);
1596 if (layout.payload_padding_size != 0) {
1597 const u8_ty_id = try cg.resolveType(.u8, .indirect);
1598 const len_id = try cg.constInt(.u32, layout.payload_padding_size);
1599 constituents[layout.payload_padding_index] = try cg.constUndef(try cg.arrayType(len_id, u8_ty_id));
1600 }
18501601
1851 for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty, field_val, i| {
1852 if (field_val != .none) continue;
1853 const ft: Type = .fromInterned(field_ty);
1854 if (!ft.hasRuntimeBits(zcu)) continue;
1602 if (layout.padding_size != 0) {
1603 const u8_ty_id = try cg.resolveType(.u8, .indirect);
1604 const len_id = try cg.constInt(.u32, layout.padding_size);
1605 constituents[layout.padding_index] = try cg.constUndef(try cg.arrayType(len_id, u8_ty_id));
1606 }
18551607
1856 const fv = try val.fieldValue(pt, i);
1857 const field_id = try cg.constant(ft, fv, .indirect);
1858 try constituents.append(gpa, field_id);
1859 }
1608 return cg.constantComposite(result_ty_id, constituents);
1609 }
18601610
1861 const comp_ty_id = try cg.resolveType(ty, .direct);
1862 return try cg.constructComposite(comp_ty_id, constituents.items);
1863 },
1864 else => unreachable,
1865 },
1866 .un => |un| {
1867 assert(ty.containerLayout(zcu) != .@"packed"); // packed unions use `bitpack`
1868 if (un.tag == .none) {
1869 @panic("TODO");
1870 }
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 },
1611 const payload: ?Id = if (field_ty.hasRuntimeBits(zcu))
1612 try cg.constant(field_ty, .fromInterned(un.val), .direct)
1613 else
1614 null;
1615 return try cg.unionInit(ty, active_field, payload);
1616 },
1617 .bitpack => |bitpack| {
1618 const int_val: Value = .fromInterned(bitpack.backing_int_val);
1619 break :cache try cg.constant(int_val.typeOf(zcu), int_val, repr);
1620 },
18841621
1885 .memoized_call => unreachable,
1886 }
1622 .memoized_call => unreachable,
18871623 };
18881624 return cacheable_id;
18891625}
......@@ -1942,46 +1678,42 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {
19421678 const nav = ip.getNav(nav_index);
19431679 const nav_ty: Type = .fromInterned(nav.resolved.?.type);
19441680
1945 switch (nav.resolved.?.value) {
1946 .none => {},
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 }
1681 const is_extern = nav.getExtern(ip) != null;
1682 assert(nav.resolved.?.@"const" or is_extern);
19591683
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 }
19601688 if (!nav_ty.hasRuntimeBits(zcu) and nav_ty.zigTypeTag(zcu) != .spirv) {
19611689 return cg.constUndef(ty_id);
19621690 }
1691 if (!is_extern) {
1692 return cg.todo("pointer to constant '{f}'", .{nav.fqn.fmt(ip)});
1693 }
19631694
1964 const spv_decl_index = try cg.resolveNav(ip, nav_index);
1965 const spv_decl = cg.declPtr(spv_decl_index);
1966 assert(spv_decl.kind != .func);
1967 const storage_class = cg.storageClass(nav.resolved.?.@"addrspace");
1968 try cg.addFunctionDep(spv_decl_index, storage_class);
1695 const as = nav.resolved.?.@"addrspace";
1696 assert(as != .generic);
19691697
1698 const storage_class = cg.storageClass(as);
1699 const var_id = try cg.resolveNav(nav_index);
19701700 const nav_ty_id = try cg.resolveType(nav_ty, .indirect);
19711701 const decl_ptr_ty_id = try cg.ptrType(nav_ty_id, storage_class);
1972 if (cg.needsLayout(nav.resolved.?.@"addrspace", nav_ty)) {
1973 try cg.block_var_ids.put(gpa, spv_decl.result_id, {});
1702 if (cg.needsLayout(as, nav_ty)) {
1703 try cg.block_var_ids.put(gpa, var_id, {});
19741704 }
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;
19761707 switch (target.os.tag) {
1977 .vulkan, .opengl => return spv_decl.result_id,
1708 .vulkan, .opengl => return var_id,
19781709 else => {},
19791710 }
1711
19801712 const casted_ptr_id = cg.allocId();
19811713 try cg.body.emit(gpa, .OpBitcast, .{
19821714 .id_result_type = ty_id,
19831715 .id_result = casted_ptr_id,
1984 .operand = spv_decl.result_id,
1716 .operand = var_id,
19851717 });
19861718 return casted_ptr_id;
19871719 },
......@@ -2001,24 +1733,15 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {
20011733 return cg.constUndef(ty_id);
20021734 }
20031735
2004 // Uav refs are always generic.
2005 assert(result_ptr_ty.ptrAddressSpace(zcu) == .generic);
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 => {},
1736 if (cg.storageClass(result_ptr_ty.ptrAddressSpace(zcu)) != .function) {
1737 return cg.todo("pointer to an anonymous constant of type '{f}'", .{uav_ty.fmt(pt)});
20141738 }
2015 const casted_ptr_id = cg.allocId();
2016 try cg.body.emit(gpa, .OpBitcast, .{
2017 .id_result_type = ty_id,
2018 .id_result = casted_ptr_id,
2019 .operand = ptr_id,
2020 });
2021 return casted_ptr_id;
1739
1740 const uav_ty_id = try cg.resolveType(uav_ty, .indirect);
1741 const ptr_id = try cg.alloc(uav_ty_id, null);
1742 const val_id = try cg.constant(uav_ty, .fromInterned(uav.val), .indirect);
1743 try cg.body.emit(gpa, .OpStore, .{ .pointer = ptr_id, .object = val_id });
1744 return ptr_id;
20221745 },
20231746 .eu_payload_ptr => @panic("TODO"),
20241747 .opt_payload_ptr => @panic("TODO"),
......@@ -2102,61 +1825,6 @@ fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {
21021825 }
21031826}
21041827
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.
2108fn 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
2150fn 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
21601828/// Generate a union type. Union types are always generated with the
21611829/// most aligned field active. If the tag alignment is greater
21621830/// 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 {
22091877 .direct => return try cg.voidType(),
22101878 .indirect => {
22111879 if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{});
2212 return try cg.opaqueType("void");
1880 return try cg.opaqueType("void", .{});
22131881 },
22141882 },
22151883 .bool => switch (repr) {
......@@ -2220,7 +1888,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
22201888 if (ty.toIntern() == .u0_type) {
22211889 assert(repr == .indirect);
22221890 if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{});
2223 return try cg.opaqueType("u0");
1891 return try cg.opaqueType("u0", .{});
22241892 }
22251893 const int_info = ty.intInfo(zcu);
22261894 return try cg.intType(int_info.signedness, int_info.bits);
......@@ -2250,7 +1918,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
22501918 if (!elem_ty.hasRuntimeBits(zcu)) {
22511919 assert(repr == .indirect);
22521920 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", .{});
22541922 } else if (total_len == 0) {
22551923 // The size of the array would be 0, but that is not allowed in SPIR-V.
22561924 // 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 {
23642032 null,
23652033 .none,
23662034 );
2367 const type_name = try cg.resolveTypeName(ty);
2368 defer gpa.free(type_name);
2369 try cg.debugName(result_id, type_name);
2035 try cg.debugNameFmt(result_id, "{f}", .{ty.fmt(cg.pt)});
23702036 return result_id;
23712037 },
23722038 .struct_type => ip.loadStructType(ty.toIntern()),
......@@ -2399,9 +2065,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
23992065 ty.toIntern(),
24002066 );
24012067
2402 const type_name = try cg.resolveTypeName(ty);
2403 defer gpa.free(type_name);
2404 try cg.debugName(result_id, type_name);
2068 try cg.debugNameFmt(result_id, "{f}", .{ty.fmt(cg.pt)});
24052069
24062070 return result_id;
24072071 },
......@@ -2430,9 +2094,36 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
24302094 },
24312095 .@"union" => {
24322096 const union_obj = zcu.typeToUnion(ty).?;
2433 if (union_obj.layout == .@"packed") {
2434 return try cg.intType(.unsigned, @intCast(ty.bitSize(zcu)));
2097 switch (union_obj.layout) {
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 },
24352125 }
2126
24362127 const layout = cg.unionLayout(ty);
24372128 if (!layout.has_payload) {
24382129 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 {
24632154 member_names[0..layout.total_fields],
24642155 .none,
24652156 );
2466 const type_name = try cg.resolveTypeName(ty);
2467 defer gpa.free(type_name);
2468 try cg.debugName(result_id, type_name);
2157 try cg.debugNameFmt(result_id, "{f}", .{ty.fmt(cg.pt)});
24692158 return result_id;
24702159 },
24712160 .error_set => {
......@@ -2502,18 +2191,16 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
25022191 },
25032192 .@"opaque" => {
25042193 if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{});
2505 const type_name = try cg.resolveTypeName(ty);
2506 defer gpa.free(type_name);
2507 return try cg.opaqueType(type_name);
2194 return try cg.opaqueType("{f}", .{ty.fmt(cg.pt)});
25082195 },
25092196 .spirv => {
25102197 const spirv_type = ip.loadSpirvType(ty.toIntern());
25112198 const result_id = cg.allocId();
25122199 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 }),
25142201 .image => {
25152202 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, .{
25172204 .id_result = result_id,
25182205 .sampled_type = sampled_type_id,
25192206 .dim = switch (spirv_type.flags.dim) {
......@@ -2560,7 +2247,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
25602247 },
25612248 .sampled_image => {
25622249 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, .{
25642251 .id_result = result_id,
25652252 .image_type = image_ty_id,
25662253 });
......@@ -2568,7 +2255,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
25682255 .runtime_array => {
25692256 const elem_ty: Type = .fromInterned(spirv_type.ty);
25702257 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, .{
25722259 .id_result = result_id,
25732260 .element_type = elem_ty_id,
25742261 });
......@@ -4061,7 +3748,7 @@ fn buildWideMul(
40613748fn generateTestEntryPoint(
40623749 cg: *CodeGen,
40633750 name: []const u8,
4064 spv_decl_index: Decl.Index,
3751 kernel_id: Id,
40653752 test_id: Id,
40663753) !void {
40673754 const gpa = cg.gpa;
......@@ -4075,9 +3762,8 @@ fn generateTestEntryPoint(
40753762 });
40763763 const ptr_anyerror_ty_id = try cg.resolveType(ptr_anyerror_ty, .direct);
40773764
4078 const kernel_id = cg.declPtr(spv_decl_index).result_id;
4079
4080 const section = &cg.sections.functions;
3765 var section: Section = .empty;
3766 defer section.deinit(gpa);
40813767
40823768 const p_error_id = cg.allocId();
40833769 switch (target.os.tag) {
......@@ -4103,11 +3789,8 @@ fn generateTestEntryPoint(
41033789 },
41043790 .vulkan, .opengl => {
41053791 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
41093792 const buffer_struct_ty_id = cg.allocId();
4110 try cg.sections.globals.emit(gpa, .OpTypeStruct, .{
3793 try cg.module.emit(gpa, .OpTypeStruct, .{
41113794 .id_result = buffer_struct_ty_id,
41123795 .id_ref = &.{anyerror_ty_id},
41133796 });
......@@ -4116,21 +3799,21 @@ fn generateTestEntryPoint(
41163799 try cg.decorateMember(buffer_struct_ty_id, 0, .{ .offset = .{ .byte_offset = 0 } });
41173800
41183801 const ptr_buffer_struct_ty_id = cg.allocId();
4119 try cg.sections.globals.emit(gpa, .OpTypePointer, .{
3802 try cg.module.emit(gpa, .OpTypePointer, .{
41203803 .id_result = ptr_buffer_struct_ty_id,
41213804 .storage_class = cg.storageClass(.global),
41223805 .type = buffer_struct_ty_id,
41233806 });
41243807
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, .{
41263811 .id_result_type = ptr_buffer_struct_ty_id,
4127 .id_result = err_buf_result_id,
3812 .id_result = error_buffer_id,
41283813 .storage_class = cg.storageClass(.global),
41293814 });
4130 try cg.decorate(err_buf_result_id, .{ .descriptor_set = .{ .descriptor_set = 0 } });
4131 try cg.decorate(err_buf_result_id, .{ .binding = .{ .binding_point = 0 } });
4132
4133 cg.error_buffer = spv_err_decl_index;
3815 try cg.decorate(error_buffer_id, .{ .descriptor_set = .{ .descriptor_set = 0 } });
3816 try cg.decorate(error_buffer_id, .{ .binding = .{ .binding_point = 0 } });
41343817 }
41353818
41363819 const void_ty_id = try cg.resolveType(.void, .direct);
......@@ -4145,10 +3828,7 @@ fn generateTestEntryPoint(
41453828 .id_result = cg.allocId(),
41463829 });
41473830
4148 const spv_err_decl_index = 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
3831 const buffer_id = cg.error_buffer.?;
41523832 const zero_id = try cg.constInt(.u32, 0);
41533833 try section.emit(gpa, .OpInBoundsAccessChain, .{
41543834 .id_result_type = ptr_anyerror_ty_id,
......@@ -4176,17 +3856,17 @@ fn generateTestEntryPoint(
41763856 });
41773857 try section.emit(gpa, .OpReturn, {});
41783858 try section.emit(gpa, .OpFunctionEnd, {});
3859 try cg.module.append(gpa, section);
41793860
41803861 // Just generate a quick other name because the intel runtime crashes when the entry-
41813862 // point name is the same as a different OpName.
4182 const test_name = try std.fmt.allocPrint(cg.arena, "test {s}", .{name});
4183
4184 const ep_gop = try cg.entry_points.getOrPut(cg.gpa, cg.declPtr(spv_decl_index).result_id);
4185 ep_gop.value_ptr.* = .{
4186 .decl_index = spv_decl_index,
3863 const test_name = try std.fmt.allocPrint(gpa, "test {s}", .{name});
3864 errdefer gpa.free(test_name);
3865 try cg.entry_points.append(gpa, .{
3866 .id = kernel_id,
41873867 .name = test_name,
41883868 .cc = .{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } },
4189 };
3869 });
41903870}
41913871
41923872fn intFromBool(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary {
......@@ -4456,6 +4136,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) Error!void {
44564136 .ptr_elem_ptr => try cg.airPtrElemPtr(inst),
44574137 .ptr_elem_val => try cg.airPtrElemVal(inst),
44584138 .array_elem_val => try cg.airArrayElemVal(inst),
4139 .runtime_nav_ptr => try cg.airRuntimeNavPtr(inst),
44594140
44604141 .set_union_tag => return cg.airSetUnionTag(inst),
44614142 .get_union_tag => try cg.airGetUnionTag(inst),
......@@ -5549,12 +5230,17 @@ fn airSelect(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
55495230}
55505231
55515232fn airSplat(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
5233 const gpa = cg.gpa;
5234 const zcu = cg.zcu;
55525235 const ty_op = cg.air.instructions.items(.data)[@backingInt(inst)].ty_op;
5553
55545236 const operand_id = try cg.resolve(ty_op.operand);
55555237 const result_ty = cg.typeOfIndex(inst);
55565238
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);
55585244}
55595245
55605246fn 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 {
67676453 }
67686454}
67696455
6456fn 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
67706461fn airPtrElemPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
67716462 const zcu = cg.zcu;
67726463 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 {
68496540 const bin_op = cg.air.instructions.items(.data)[@backingInt(inst)].bin_op;
68506541 const un_ptr_ty = cg.typeOf(bin_op.lhs);
68516542 const un_ty = un_ptr_ty.childType(zcu);
6852 const layout = cg.unionLayout(un_ty);
6853
6854 if (layout.tag_size == 0) return;
6855
6856 const tag_ty = un_ty.unionTagTypeRuntime(zcu).?;
6543 const tag_ty = un_ty.unionTagTypeRuntime(zcu) orelse return;
68576544 const tag_ty_id = try cg.resolveType(tag_ty, .indirect);
68586545 const tag_ptr_ty_id = try cg.ptrType(tag_ty_id, cg.storageClass(un_ptr_ty.ptrAddressSpace(zcu)));
6859
68606546 const union_ptr_id = try cg.resolve(bin_op.lhs);
68616547 const new_tag_id = try cg.resolve(bin_op.rhs);
6862
6863 if (!layout.has_payload) {
6864 try cg.store(tag_ty, union_ptr_id, new_tag_id, .{ .is_volatile = un_ptr_ty.isVolatilePtr(zcu) });
6865 } else {
6866 const ptr_id = try cg.accessChain(tag_ptr_ty_id, union_ptr_id, &.{layout.tag_index});
6867 try cg.store(tag_ty, ptr_id, new_tag_id, .{ .is_volatile = un_ptr_ty.isVolatilePtr(zcu) });
6868 }
6548 const ptr_id = if (un_ty.unionHasAllZeroBitFieldTypes(zcu))
6549 union_ptr_id
6550 else
6551 try cg.accessChain(tag_ptr_ty_id, union_ptr_id, &.{zcu.typeToUnion(un_ty).?.field_types.len});
6552 try cg.store(tag_ty, ptr_id, new_tag_id, .{ .is_volatile = un_ptr_ty.isVolatilePtr(zcu) });
68696553}
68706554
68716555fn airGetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
6556 const zcu = cg.zcu;
68726557 const ty_op = cg.air.instructions.items(.data)[@backingInt(inst)].ty_op;
68736558 const un_ty = cg.typeOf(ty_op.operand);
6874
6875 const zcu = cg.zcu;
6876 const layout = cg.unionLayout(un_ty);
6877 if (layout.tag_size == 0) return null;
6878
6559 const tag_ty = un_ty.unionTagTypeRuntime(zcu) orelse return null;
68796560 const union_handle = try cg.resolve(ty_op.operand);
6880 if (!layout.has_payload) return union_handle;
6881
6882 const tag_ty = un_ty.unionTagTypeRuntime(zcu).?;
6883 return try cg.extractField(tag_ty, union_handle, layout.tag_index);
6561 if (un_ty.unionHasAllZeroBitFieldTypes(zcu)) return union_handle;
6562 const tag_member = zcu.typeToUnion(un_ty).?.field_types.len;
6563 return try cg.extractField(tag_ty, union_handle, tag_member);
68846564}
68856565
68866566fn unionInit(
......@@ -6905,6 +6585,29 @@ fn unionInit(
69056585 const payload_ty: Type = .fromInterned(union_ty.field_types.get(ip)[active_field]);
69066586
69076587 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 }
69086611
69096612 const tag_int = if (layout.tag_size != 0) blk: {
69106613 const tag_val = try pt.enumValueFieldIndex(tag_ty, active_field);
......@@ -7033,7 +6736,8 @@ fn airAggFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
70336736 if (field_ty.isInt(zcu)) return result_id;
70346737 return try cg.bitCast(field_ty, int_ty, result_id);
70356738 },
7036 else => {
6739 .auto => return try cg.extractField(field_ty, object_id, field_index),
6740 .@"extern" => {
70376741 // Store, ptr-elem-ptr, pointer-cast, load
70386742 const layout = cg.unionLayout(object_ty);
70396743 assert(layout.has_payload);
......@@ -7163,7 +6867,11 @@ fn structFieldPtr(
71636867 },
71646868 .@"union" => switch (object_ty.containerLayout(zcu)) {
71656869 .@"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" => {
71676875 const layout = cg.unionLayout(object_ty);
71686876 if (!layout.has_payload) {
71696877 // 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 {
72726980
72736981fn airArg(cg: *CodeGen) Id {
72746982 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];
72766984}
72776985
72786986/// 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 {
86698377 if (zcu.comp.config.root_strip) return;
86708378
86718379 const path_id = cg.allocId();
8672 try cg.sections.debug_strings.emit(cg.gpa, .OpString, .{
8380 try cg.module.emit(cg.gpa, .OpString, .{
86738381 .id_result = path_id,
86748382 .string = path,
86758383 });
......@@ -8942,18 +8650,15 @@ fn builtin3D(
89428650 const ptr_ty_id = try cg.ptrType(vec_ty_id, .input);
89438651 const builtins_gop = try cg.builtins.getOrPut(gpa, .{ built_in, .input });
89448652 if (!builtins_gop.found_existing) {
8945 builtins_gop.value_ptr.* = try cg.allocDecl(.global);
8946 const decl = cg.declPtr(builtins_gop.value_ptr.*);
8947 try cg.sections.globals.emit(gpa, .OpVariable, .{
8653 builtins_gop.value_ptr.* = cg.allocId();
8654 try cg.module.emit(gpa, .OpVariable, .{
89488655 .id_result_type = ptr_ty_id,
8949 .id_result = decl.result_id,
8656 .id_result = builtins_gop.value_ptr.*,
89508657 .storage_class = .input,
89518658 });
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 } });
89538660 }
8954 const spv_decl_index = 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;
8661 const ptr_id = builtins_gop.value_ptr.*;
89578662 const vec_id = cg.allocId();
89588663 try cg.body.emit(gpa, .OpLoad, .{
89598664 .id_result_type = vec_ty_id,
src/codegen/spirv/Mir.zig+11-43
......@@ -1,71 +1,39 @@
11const std = @import("std");
22const Allocator = std.mem.Allocator;
3
43const spec = @import("spec.zig");
54const Word = spec.Word;
65const Id = spec.Id;
7
6const Section = @import("Section.zig");
87const InternPool = @import("../../InternPool.zig");
9const CodeGen = @import("CodeGen.zig");
10
118const Mir = @This();
129
1310id_bound: Word,
14owner_nav: InternPool.Nav.Index,
15kind: CodeGen.Decl.Kind,
16decl_result_id: Id,
17extended_instruction_set: []const Word,
18globals: []const Word,
19functions: []const Word,
20annotations: []const Word,
21debug_names: []const Word,
22debug_strings: []const Word,
23execution_modes: []const Word,
11module: Section,
2412nav_refs: []const NavRef,
25uav_refs: []const UavRef,
26decl_deps: []const DeclDep,
27internal_globals: []const Id,
13externs: []const Extern,
2814entry_points: []const EntryPoint,
2915
3016pub const NavRef = struct {
31 local_id: Id,
17 id: Id,
3218 nav: InternPool.Nav.Index,
33 kind: CodeGen.Decl.Kind,
3419};
3520
36pub const UavRef = struct {
37 local_id: Id,
38 val: InternPool.Index,
39 storage_class: spec.StorageClass,
40 kind: CodeGen.Decl.Kind,
41};
42
43pub const DeclDep = struct {
44 kind: CodeGen.Decl.Kind,
45 nav: InternPool.Nav.Index,
21pub const Extern = struct {
22 id: Id,
23 name: []const u8,
4624};
4725
4826pub const EntryPoint = struct {
49 local_id: Id,
27 id: Id,
5028 name: []const u8,
5129 cc: std.builtin.CallingConvention,
5230};
5331
5432pub fn deinit(mir: *Mir, gpa: Allocator) void {
55 gpa.free(mir.extended_instruction_set);
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);
33 mir.module.deinit(gpa);
6234 gpa.free(mir.nav_refs);
63 gpa.free(mir.uav_refs);
64 gpa.free(mir.decl_deps);
65 gpa.free(mir.internal_globals);
66 for (mir.entry_points) |ep| {
67 gpa.free(ep.name);
68 }
35 gpa.free(mir.externs);
36 for (mir.entry_points) |ep| gpa.free(ep.name);
6937 gpa.free(mir.entry_points);
7038 mir.* = undefined;
7139}
src/codegen/spirv/Section.zig+278-136
......@@ -1,91 +1,273 @@
11//! Represents a section or subsection of instructions in a SPIR-V binary. Instructions can be append
22//! to separate sections, which can then later be merged into the final binary.
3const Section = @This();
4
53const std = @import("std");
64const Allocator = std.mem.Allocator;
7const testing = std.testing;
8
95const spec = @import("spec.zig");
6const Opcode = spec.Opcode;
7const InstructionHeader = spec.InstructionHeader;
108const Word = spec.Word;
11const DoubleWord = @Int(.unsigned, @bitSizeOf(Word) * 2);
12const Log2Word = std.math.Log2Int(Word);
9const Id = spec.Id;
10const Section = @This();
1311
14const Opcode = spec.Opcode;
12instructions: std.ArrayList(Word),
13ids: std.bit_set.Dynamic,
14
15pub const empty: Section = .{ .instructions = .empty, .ids = .{} };
16
17pub const Error = error{
18 OutOfMemory,
19 UnknownOpcode,
20 InvalidInstruction,
21 InvalidInstructionLength,
22 NestedFunction,
23 UnterminatedFunction,
24 UndefinedId,
25};
26
27pub 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 }
15109
16instructions: std.ArrayList(Word) = .empty,
110 return section;
111}
17112
18pub fn deinit(section: *Section, allocator: Allocator) void {
19 section.instructions.deinit(allocator);
113pub fn deinit(section: *Section, gpa: Allocator) void {
114 section.instructions.deinit(gpa);
115 section.ids.deinit(gpa);
20116 section.* = undefined;
21117}
22118
23119pub fn reset(section: *Section) void {
24120 section.instructions.clearRetainingCapacity();
121 section.ids.unsetAll();
25122}
26123
27pub fn toWords(section: Section) []Word {
28 return section.instructions.items;
124pub fn append(section: *Section, gpa: Allocator, other: Section) error{OutOfMemory}!void {
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);
29129}
30130
31/// Append the instructions from another section into this section.
32pub fn append(section: *Section, allocator: Allocator, other_section: Section) !void {
33 try section.instructions.appendSlice(allocator, other_section.instructions.items);
131pub fn ensureUnusedCapacity(section: *Section, gpa: Allocator, words: usize) !void {
132 try section.instructions.ensureUnusedCapacity(gpa, words);
133 try section.ids.resize(gpa, section.instructions.capacity, false);
34134}
35135
36pub fn ensureUnusedCapacity(
136fn parseOperands(
37137 section: *Section,
38 allocator: Allocator,
39 words: usize,
40) !void {
41 try section.instructions.ensureUnusedCapacity(allocator, words);
138 type_widths: []const u16,
139 inst_start: usize,
140 inst_end: usize,
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;
42231}
43232
44/// Write an instruction and size, operands are to be inserted manually.
45233pub fn emitRaw(
46234 section: *Section,
47 allocator: Allocator,
235 gpa: Allocator,
48236 opcode: Opcode,
49237 operand_words: usize,
50238) !void {
51239 const word_count = 1 + operand_words;
52240 if (word_count > std.math.maxInt(u16)) return error.OutOfMemory;
53 try section.instructions.ensureUnusedCapacity(allocator, word_count);
54 section.writeWord((@as(Word, @intCast(word_count)) << 16) | @backingInt(opcode));
55}
56
57/// Write an entire instruction, including all operands
58pub 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);
241 try section.ensureUnusedCapacity(gpa, word_count);
242 const header: InstructionHeader = .{ .opcode = opcode, .word_count = @intCast(word_count) };
243 section.writeWord(@bitCast(header));
66244}
67245
68pub fn emitAssumeCapacity(
246pub fn emitFormatted(
69247 section: *Section,
70 comptime opcode: spec.Opcode,
71 operands: opcode.Operands(),
248 gpa: Allocator,
249 comptime opcode: Opcode,
250 ids: []const Id,
251 comptime fmt: []const u8,
252 args: anytype,
72253) !void {
73 const word_count = instructionSize(opcode, operands);
74 if (word_count > std.math.maxInt(u16)) return error.OutOfMemory;
75 section.writeWord((@as(Word, @intCast(word_count)) << 16) | @backingInt(opcode));
76 section.writeOperands(opcode.Operands(), operands);
254 const len = std.fmt.count(fmt, args);
255 const string_words = len / @sizeOf(Word) + 1;
256 try section.emitRaw(gpa, opcode, ids.len + string_words);
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.*);
77262}
78263
79264pub fn emit(
80265 section: *Section,
81 allocator: Allocator,
266 gpa: Allocator,
82267 comptime opcode: spec.Opcode,
83268 operands: opcode.Operands(),
84269) !void {
85 const word_count = instructionSize(opcode, operands);
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));
270 try section.emitRaw(gpa, opcode, opcode.instructionSize(operands) - 1);
89271 section.writeOperands(opcode.Operands(), operands);
90272}
91273
......@@ -93,18 +275,26 @@ pub fn writeWord(section: *Section, word: Word) void {
93275 section.instructions.appendAssumeCapacity(word);
94276}
95277
96pub fn writeWords(section: *Section, words: []const Word) void {
278pub fn writeWords(section: *Section, gpa: Allocator, words: []const Word) error{OutOfMemory}!void {
279 try section.ensureUnusedCapacity(gpa, words.len);
97280 section.instructions.appendSliceAssumeCapacity(words);
98281}
99282
283pub const DoubleWord = @Int(.unsigned, @bitSizeOf(Word) * 2);
284
100285pub fn writeDoubleWord(section: *Section, dword: DoubleWord) void {
101 section.writeWords(&.{
286 section.instructions.appendSliceAssumeCapacity(&.{
102287 @truncate(dword),
103288 @truncate(dword >> @bitSizeOf(Word)),
104289 });
105290}
106291
107fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) void {
292pub fn writeId(section: *Section, id: Id) void {
293 section.ids.set(section.instructions.items.len);
294 section.instructions.appendAssumeCapacity(@backingInt(id));
295}
296
297pub fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) void {
108298 const info = switch (@typeInfo(Operands)) {
109299 .@"struct" => |info| info,
110300 .void => return,
......@@ -118,19 +308,28 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)
118308pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
119309 switch (Operand) {
120310 spec.LiteralSpecConstantOpInteger => unreachable,
121 spec.Id => section.writeWord(@backingInt(operand)),
311 spec.Id => section.writeId(operand),
122312 spec.LiteralInteger => section.writeWord(operand),
123313 spec.LiteralString => section.writeString(operand),
124 spec.LiteralContextDependentNumber => section.writeContextDependentNumber(operand),
314 spec.LiteralContextDependentNumber => section.writeNumber(operand),
125315 spec.LiteralExtInstInteger => section.writeWord(operand.inst),
126 spec.PairLiteralIntegerIdRef => section.writeWords(&.{ operand.value, @fromBackingInt(@intCast(operand.label)) }),
127 spec.PairIdRefLiteralInteger => section.writeWords(&.{ @backingInt(operand.target), operand.member }),
128 spec.PairIdRefIdRef => section.writeWords(&.{ @backingInt(operand[0]), @backingInt(operand[1]) }),
316 spec.PairLiteralIntegerIdRef => {
317 section.writeWord(operand.value);
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 },
129328 else => switch (@typeInfo(Operand)) {
130329 .@"enum" => section.writeWord(@backingInt(operand)),
131330 .optional => |info| if (operand) |child| section.writeOperand(info.child, child),
132331 .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);
134333 for (operand) |item| {
135334 section.writeOperand(info.child, item);
136335 }
......@@ -148,20 +347,21 @@ pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand)
148347 }
149348}
150349
151fn writeString(section: *Section, str: []const u8) void {
350pub fn writeString(section: *Section, str: []const u8) void {
152351 const zero_terminated_len = str.len + 1;
153352 var i: usize = 0;
154353 while (i < zero_terminated_len) : (i += @sizeOf(Word)) {
155354 var word: Word = 0;
156355 var j: usize = 0;
157356 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;
159359 }
160360 section.instructions.appendAssumeCapacity(word);
161361 }
162362}
163363
164fn writeContextDependentNumber(section: *Section, operand: spec.LiteralContextDependentNumber) void {
364pub fn writeNumber(section: *Section, operand: spec.LiteralContextDependentNumber) void {
165365 switch (operand) {
166366 .int32 => |int| section.writeWord(@bitCast(int)),
167367 .uint32 => |int| section.writeWord(@bitCast(int)),
......@@ -172,17 +372,23 @@ fn writeContextDependentNumber(section: *Section, operand: spec.LiteralContextDe
172372 }
173373}
174374
175fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand) void {
375pub fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand) void {
176376 var mask: Word = 0;
177377 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| {
179380 switch (@typeInfo(field_type)) {
180 .optional => if (@field(operand, field_name) != null) {
181 mask |= 1 << @as(u5, @intCast(bit));
381 .optional => {
382 if (@field(operand, field_name) != null) {
383 mask |= @as(Word, 1) << @intCast(bit);
384 }
385 bit += 1;
182386 },
183 .bool => if (@field(operand, field_name)) {
184 mask |= 1 << @as(u5, @intCast(bit));
387 .bool => {
388 if (@field(operand, field_name)) mask |= @as(Word, 1) << @intCast(bit);
389 bit += 1;
185390 },
391 .int => bit += @bitSizeOf(field_type),
186392 else => unreachable,
187393 }
188394 }
......@@ -191,86 +397,22 @@ fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand
191397
192398 inline for (info.field_names, info.field_types) |field_name, field_type| {
193399 switch (@typeInfo(field_type)) {
194 .optional => |opt_info| if (@field(operand, field_name)) |child| {
195 section.writeOperands(opt_info.child, child);
400 .optional => |opt_info| {
401 if (@field(operand, field_name)) |child| {
402 section.writeOperands(opt_info.child, child);
403 }
196404 },
197 .bool => {},
405 .bool, .int => {},
198406 else => unreachable,
199407 }
200408 }
201409}
202410
203fn writeExtendedUnion(section: *Section, comptime Operand: type, operand: Operand) void {
411pub fn writeExtendedUnion(section: *Section, comptime Operand: type, operand: Operand) void {
204412 return switch (operand) {
205413 inline else => |op, tag| {
206414 section.writeWord(@backingInt(tag));
207 section.writeOperands(
208 @FieldType(Operand, @tagName(tag)),
209 op,
210 );
211 },
212 };
213}
214
215fn instructionSize(comptime opcode: spec.Opcode, operands: opcode.Operands()) usize {
216 return operandsSize(opcode.Operands(), operands) + 1;
217}
218
219fn 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
234fn 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,
415 section.writeOperands(@FieldType(Operand, @tagName(tag)), op);
274416 },
275417 };
276418}
src/link.zig+2-2
......@@ -1319,7 +1319,7 @@ pub const File = struct {
13191319 .macho => MachO,
13201320 .c => C,
13211321 .wasm => Wasm,
1322 .spirv => SpirV,
1322 .spirv => Spirv,
13231323 .lld => Lld,
13241324 .plan9 => comptime unreachable,
13251325 .spork8 => Spork8,
......@@ -1423,7 +1423,7 @@ pub const File = struct {
14231423 pub const Elf = @import("link/Elf.zig");
14241424 pub const Elf2 = @import("link/Elf2.zig");
14251425 pub const MachO = @import("link/MachO.zig");
1426 pub const SpirV = @import("link/SpirV.zig");
1426 pub const Spirv = @import("link/Spirv.zig");
14271427 pub const Wasm = @import("link/Wasm.zig");
14281428 pub const Dwarf = @import("link/Dwarf.zig");
14291429 pub const Dwarf2 = @import("link/Dwarf2.zig");
src/link/SpirV.zig deleted-1216
......@@ -1,1216 +0,0 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const Path = std.Build.Cache.Path;
4const assert = std.debug.assert;
5const log = std.log.scoped(.link);
6const zig_version = @import("builtin").zig_version;
7const Zcu = @import("../Zcu.zig");
8const InternPool = @import("../InternPool.zig");
9const Compilation = @import("../Compilation.zig");
10const link = @import("../link.zig");
11const Air = @import("../Air.zig");
12const Type = @import("../Type.zig");
13const codegen = @import("../codegen.zig");
14const CodeGen = @import("../codegen/spirv/CodeGen.zig");
15const BinaryModule = @import("SpirV/BinaryModule.zig");
16const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
17const dedup_types = @import("SpirV/dedup_types.zig");
18const prune_unused = @import("SpirV/prune_unused.zig");
19const spec = @import("../codegen/spirv/spec.zig");
20const Section = @import("../codegen/spirv/Section.zig");
21const Id = spec.Id;
22const Word = spec.Word;
23const Mir = @import("../codegen/spirv/Mir.zig");
24
25const Linker = @This();
26
27base: link.File,
28fragments: std.array_hash_map.Auto(InternPool.Nav.Index, Mir) = .empty,
29pending_navs: std.ArrayList(InternPool.Nav.Index) = .empty,
30entry_points: std.ArrayList(EntryPointDecl) = .empty,
31external_objects: std.ArrayList(ExternalObject) = .empty,
32
33const EntryPointDecl = struct {
34 nav: InternPool.Nav.Index,
35 name: []const u8,
36 cc: std.builtin.CallingConvention,
37};
38
39const ExternalObject = struct {
40 instructions: []const Word,
41 id_bound: u32,
42};
43
44pub 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
89pub 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
98pub 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
112pub 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
177pub 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
211pub 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
219pub 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
245pub 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
310fn 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
320fn 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
509fn 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
559const FilterMode = enum { skip_functions, skip_linkage, skip_names };
560
561fn 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
600fn 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
676fn 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
778fn 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
801const MergedModule = struct {
802 words: []const Word,
803 id_bound: Word,
804 version: spec.Version,
805 generator_id: u32,
806};
807
808const FragmentInfo = struct {
809 id_offset: Word,
810};
811
812const 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
829const 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
881fn 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
1005fn 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
1046fn 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
1064fn 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
1097fn 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
1160fn 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
1182fn 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
1208fn 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 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const Allocator = std.mem.Allocator;
4const log = std.log.scoped(.spirv_parse);
5
6const spec = @import("../../codegen/spirv/spec.zig");
7const Opcode = spec.Opcode;
8const Word = spec.Word;
9const InstructionSet = spec.InstructionSet;
10const ResultId = spec.Id;
11
12const BinaryModule = @This();
13
14/// The result-id bound of this SPIR-V module.
15id_bound: u32,
16
17/// The instructions of this module (no header).
18instructions: []const Word,
19
20/// Maps OpExtInstImport result-ids to their InstructionSet.
21ext_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.
25arith_type_width: std.AutoHashMapUnmanaged(ResultId, u16),
26
27functions_start: usize,
28
29pub 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
35pub fn iterateInstructions(bm: BinaryModule) Instruction.Iterator {
36 return Instruction.Iterator.init(bm.instructions, 0);
37}
38
39pub fn iterateInstructionsFrom(bm: BinaryModule, offset: usize) Instruction.Iterator {
40 return Instruction.Iterator.init(bm.instructions, offset);
41}
42
43pub 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
73pub 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 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const BinaryModule = @import("BinaryModule.zig");
4
5const spec = @import("../../codegen/spirv/spec.zig");
6const Word = spec.Word;
7const Id = spec.Id;
8const Opcode = spec.Opcode;
9const 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.
17pub 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
220fn 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
232const 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 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const assert = std.debug.assert;
4const log = std.log.scoped(.spirv_link);
5
6const BinaryModule = @import("BinaryModule.zig");
7const Section = @import("../../codegen/spirv/Section.zig");
8const spec = @import("../../codegen/spirv/spec.zig");
9const ResultId = spec.Id;
10const 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.
14const 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
328const 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
779pub 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 @@
1const std = @import("std");
2const BinaryModule = @import("BinaryModule.zig");
3const spec = @import("../../codegen/spirv/spec.zig");
4const Opcode = spec.Opcode;
5const ResultId = spec.Id;
6const Word = spec.Word;
7
8pub 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
173fn 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
186fn 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
231fn 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
240fn 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 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const Path = std.Build.Cache.Path;
4const assert = std.debug.assert;
5const Zcu = @import("../Zcu.zig");
6const InternPool = @import("../InternPool.zig");
7const Compilation = @import("../Compilation.zig");
8const link = @import("../link.zig");
9const codegen = @import("../codegen.zig");
10const CodeGen = @import("../codegen/spirv/CodeGen.zig");
11const Mir = @import("../codegen/spirv/Mir.zig");
12const spec = @import("../codegen/spirv/spec.zig");
13const Section = @import("../codegen/spirv/Section.zig");
14const Flush = @import("Spirv/Flush.zig");
15const Word = spec.Word;
16const Linker = @This();
17
18base: link.File,
19mirs: std.array_hash_map.Auto(InternPool.Nav.Index, Mir),
20objects: std.ArrayList(Object),
21exports: std.array_hash_map.Auto(InternPool.Nav.Index, InternPool.NullTerminatedString),
22
23pub const Object = struct { path: Path, mir: Mir };
24
25pub 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
53pub 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
62pub 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
71pub 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
79fn 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
131pub 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
151pub 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
174pub 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
203pub 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 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const Path = std.Build.Cache.Path;
4const assert = std.debug.assert;
5const InternPool = @import("../../InternPool.zig");
6const link = @import("../../link.zig");
7const Type = @import("../../Type.zig");
8const Linker = @import("../Spirv.zig");
9const Mir = @import("../../codegen/spirv/Mir.zig");
10const spec = @import("../../codegen/spirv/spec.zig");
11const Section = @import("../../codegen/spirv/Section.zig");
12const zig_version = @import("builtin").zig_version;
13const Id = spec.Id;
14const Word = spec.Word;
15const Flush = @This();
16
17linker: *Linker,
18arena: Allocator,
19id_bound: Word,
20navs: std.array_hash_map.Auto(InternPool.Nav.Index, Id),
21symbols: std.array_hash_map.String(Symbol),
22interned: std.array_hash_map.String(Id),
23variables: std.array_hash_map.Auto(Id, spec.StorageClass),
24function_references: std.array_hash_map.Auto(Id, []const Id),
25entry_points: std.ArrayList(Mir.EntryPoint),
26deps: std.ArrayList(Id),
27scratch: Section,
28extended_instruction_sets: Section,
29object_entry_points: Section,
30object_execution_modes: Section,
31debug_strings: Section,
32names: Section,
33decorations: Section,
34globals: Section,
35declarations: Section,
36functions: Section,
37
38const Symbol = struct {
39 id: Id,
40 defined: bool,
41 declaration: []const Word,
42};
43
44const Annotation = struct {
45 target: Word,
46 class: spec.Class,
47 start: usize,
48 end: usize,
49};
50
51const 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
85fn allocId(flush: *Flush) Id {
86 const id: Id = @fromBackingInt(flush.id_bound);
87 flush.id_bound += 1;
88 return id;
89}
90
91fn 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
97pub 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
222fn 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
444fn 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
492fn 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
519fn 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
563fn 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
632fn 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(
604604 // The default address space for functions on AVR is .flash to produce
605605 // correct fixups into progmem.
606606 if (context == .function and target.cpu.arch == .avr) return .flash;
607 if (context == .global_mutable and target.os.tag == .vulkan) return .private;
607608 return .generic;
608609}
609610
......@@ -649,6 +650,7 @@ pub fn shouldBlockPointerOps(target: *const std.Target, as: AddressSpace) bool {
649650 // Logical pointers that never support operations
650651 .constant,
651652 .local,
653 .private,
652654 .input,
653655 .output,
654656 .uniform,
test/cases/compile_errors/SpirvType_vulkan_target.zig+3-3
......@@ -50,7 +50,7 @@ comptime {
5050// backend=selfhosted
5151// target=spirv32-vulkan
5252//
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
5454// :14:21: error: invalid 'sampled' field value 'bool'
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
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
test/cases/compile_errors/extern_spirv_decoration_validation.zig+21-4
......@@ -1,13 +1,30 @@
1const x = @extern(*addrspace(.push_constant) u32, .{
2 .name = "x",
1const flat = @extern(*addrspace(.push_constant) u32, .{
2 .name = "flat",
33 .decoration = .{ .flat = 0 },
44});
5const location = @extern(*addrspace(.uniform) u32, .{
6 .name = "location",
7 .decoration = .{ .location = 0 },
8});
9const descriptor = @extern(*addrspace(.input) u32, .{
10 .name = "descriptor",
11 .decoration = .{ .descriptor = .{ .set = 0, .binding = 0 } },
12});
13
14comptime {
15 _ = flat;
16}
17comptime {
18 _ = location;
19}
520comptime {
6 _ = x;
21 _ = descriptor;
722}
823
924// error
1025// backend=selfhosted
1126// target=spirv32-vulkan
1227//
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 @@
1var x: u32 = 0;
2
3comptime {
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 @@
1threadlocal var counter: u32 addrspace(.generic) = 0;
2
3export 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