authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-06-15 02:06:47+03:30
committergravatar for alichraghi@noreply.codeberg.orgAli Cheraghi <alichraghi@noreply.codeberg.org> 2026-06-18 13:38:58+02:00
logca57b33ddd308dd98c81a49a1734c549a5fb3dbe
treeff208a358bab464daec087d2777801e708ee653f
parentecb627eb396b550812e709e7f4277df8c88ad062

spirv: object file linking


7 files changed, 724 insertions(+), 243 deletions(-)

src/Compilation.zig+2-1
...@@ -7062,7 +7062,8 @@ pub fn hasObjectExt(filename: []const u8) bool {...@@ -7062,7 +7062,8 @@ pub fn hasObjectExt(filename: []const u8) bool {
7062 return mem.endsWith(u8, filename, ".o") or7062 return mem.endsWith(u8, filename, ".o") or
7063 mem.endsWith(u8, filename, ".lo") or7063 mem.endsWith(u8, filename, ".lo") or
7064 mem.endsWith(u8, filename, ".obj") or7064 mem.endsWith(u8, filename, ".obj") or
7065 mem.endsWith(u8, filename, ".rmeta");7065 mem.endsWith(u8, filename, ".rmeta") or
7066 mem.endsWith(u8, filename, ".spv");
7066}7067}
70677068
7068pub fn hasStaticLibraryExt(filename: []const u8) bool {7069pub fn hasStaticLibraryExt(filename: []const u8) bool {
src/codegen/spirv/CodeGen.zig+67-3
...@@ -384,6 +384,13 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {...@@ -384,6 +384,13 @@ pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {
384384
385 switch (decl.kind) {385 switch (decl.kind) {
386 .func => {386 .func => {
387 if (nav.resolved.?.is_extern_decl) {
388 _ = try cg.resolveType(ty, .direct);
389 try emitExternFnStub(cg, nav, decl, ty);
390 decl.end_dep = cg.module.decl_deps.items.len;
391 return;
392 }
393
387 const fn_info = zcu.typeToFunc(ty).?;394 const fn_info = zcu.typeToFunc(ty).?;
388 const return_ty_id = try cg.resolveFnReturnType(.fromInterned(fn_info.return_type));395 const return_ty_id = try cg.resolveFnReturnType(.fromInterned(fn_info.return_type));
389 const is_test = zcu.test_functions.contains(cg.owner_nav);396 const is_test = zcu.test_functions.contains(cg.owner_nav);
...@@ -677,14 +684,21 @@ fn resolve(cg: *CodeGen, inst: Air.Inst.Ref) !Id {...@@ -677,14 +684,21 @@ fn resolve(cg: *CodeGen, inst: Air.Inst.Ref) !Id {
677 if (inst.toInterned()) |val_ip_index| {684 if (inst.toInterned()) |val_ip_index| {
678 const ty = cg.typeOf(inst);685 const ty = cg.typeOf(inst);
679 if (ty.zigTypeTag(zcu) == .@"fn") {686 if (ty.zigTypeTag(zcu) == .@"fn") {
680 const fn_nav = switch (zcu.intern_pool.indexToKey(val_ip_index)) {687 const val_key = zcu.intern_pool.indexToKey(val_ip_index);
688 const fn_nav = switch (val_key) {
681 .@"extern" => |@"extern"| @"extern".owner_nav,689 .@"extern" => |@"extern"| @"extern".owner_nav,
682 .func => |func| func.owner_nav,690 .func => |func| func.owner_nav,
683 else => unreachable,691 else => unreachable,
684 };692 };
685 const spv_decl_index = try cg.module.resolveNav(ip, fn_nav);693 const spv_decl_index = try cg.module.resolveNav(ip, fn_nav);
686 try cg.module.decl_deps.append(cg.module.gpa, spv_decl_index);694 try cg.module.decl_deps.append(cg.module.gpa, spv_decl_index);
687 return cg.module.declPtr(spv_decl_index).result_id;695 const decl = cg.module.declPtr(spv_decl_index);
696 if (val_key == .@"extern") {
697 const nav = ip.getNav(fn_nav);
698 const nav_ty: Type = .fromInterned(nav.resolved.?.type);
699 try emitExternFnStub(cg, nav, decl, nav_ty);
700 }
701 return decl.result_id;
688 }702 }
689703
690 return try cg.constant(ty, .fromInterned(val_ip_index), .direct);704 return try cg.constant(ty, .fromInterned(val_ip_index), .direct);
...@@ -1434,6 +1448,51 @@ fn constantUavRef(...@@ -1434,6 +1448,51 @@ fn constantUavRef(
1434 }1448 }
1435}1449}
14361450
1451/// Emit a stub OpFunction/OpFunctionEnd + Import linkage decoration for an
1452/// extern function so the module is structurally valid. The stub will be
1453/// replaced by the real definition at link time.
1454fn emitExternFnStub(cg: *CodeGen, nav: InternPool.Nav, decl: *Module.Decl, fn_ty: Type) !void {
1455 if (decl.has_extern_stub) return;
1456 decl.has_extern_stub = true;
1457
1458 const gpa = cg.module.gpa;
1459 const zcu = cg.module.zcu;
1460 const ip = &zcu.intern_pool;
1461 const fn_info = zcu.typeToFunc(fn_ty).?;
1462 const return_ty_id = try cg.resolveFnReturnType(.fromInterned(fn_info.return_type));
1463 const prototype_ty_id = try cg.resolveType(fn_ty, .direct);
1464
1465 var stub: Section = .{};
1466 defer stub.deinit(gpa);
1467 try stub.emit(gpa, .OpFunction, .{
1468 .id_result_type = return_ty_id,
1469 .id_result = decl.result_id,
1470 .function_type = prototype_ty_id,
1471 .function_control = .{},
1472 });
1473 for (fn_info.param_types.get(ip)) |param_ty_index| {
1474 const param_ty: Type = .fromInterned(param_ty_index);
1475 if (!param_ty.hasRuntimeBits(zcu)) continue;
1476 const param_type_id = try cg.resolveType(param_ty, .direct);
1477 try stub.emit(gpa, .OpFunctionParameter, .{
1478 .id_result_type = param_type_id,
1479 .id_result = cg.module.allocId(),
1480 });
1481 }
1482 try stub.emit(gpa, .OpFunctionEnd, {});
1483 try cg.module.sections.functions.append(gpa, stub);
1484
1485 const extern_name = nav.getExtern(ip).?.name.toSlice(ip);
1486 try cg.module.sections.annotations.emit(gpa, .OpDecorate, .{
1487 .target = decl.result_id,
1488 .decoration = .{ .linkage_attributes = .{
1489 .name = extern_name,
1490 .linkage_type = .import,
1491 } },
1492 });
1493 try cg.module.debugName(decl.result_id, extern_name);
1494}
1495
1437fn constantNavRef(cg: *CodeGen, ty: Type, nav_index: InternPool.Nav.Index) !Id {1496fn constantNavRef(cg: *CodeGen, ty: Type, nav_index: InternPool.Nav.Index) !Id {
1438 const zcu = cg.module.zcu;1497 const zcu = cg.module.zcu;
1439 const ip = &zcu.intern_pool;1498 const ip = &zcu.intern_pool;
...@@ -1449,7 +1508,12 @@ fn constantNavRef(cg: *CodeGen, ty: Type, nav_index: InternPool.Nav.Index) !Id {...@@ -1449,7 +1508,12 @@ fn constantNavRef(cg: *CodeGen, ty: Type, nav_index: InternPool.Nav.Index) !Id {
1449 // just generate an empty pointer. Function pointers are represented by a pointer to usize.1508 // just generate an empty pointer. Function pointers are represented by a pointer to usize.
1450 return try cg.module.constUndef(ty_id);1509 return try cg.module.constUndef(ty_id);
1451 },1510 },
1452 .@"extern" => if (ip.isFunctionType(nav_ty.toIntern())) @panic("TODO"),1511 .@"extern" => if (ip.isFunctionType(nav_ty.toIntern())) {
1512 const spv_decl_index = try cg.module.resolveNav(ip, nav_index);
1513 const decl = cg.module.declPtr(spv_decl_index);
1514 try emitExternFnStub(cg, nav, decl, nav_ty);
1515 return decl.result_id;
1516 },
1453 else => {},1517 else => {},
1454 },1518 },
1455 }1519 }
src/codegen/spirv/Module.zig+3
...@@ -130,6 +130,9 @@ pub const Decl = struct {...@@ -130,6 +130,9 @@ pub const Decl = struct {
130 begin_dep: usize = 0,130 begin_dep: usize = 0,
131 /// The past-end offset of the dependencies of this decl in the `decl_deps` array.131 /// The past-end offset of the dependencies of this decl in the `decl_deps` array.
132 end_dep: usize = 0,132 end_dep: usize = 0,
133 /// Whether a stub OpFunction/OpFunctionEnd + Import linkage decoration has
134 /// already been emitted for this extern function decl.
135 has_extern_stub: bool = false,
133};136};
134137
135pub const EntryPoint = struct {138pub const EntryPoint = struct {
src/link.zig+1-1
...@@ -1179,7 +1179,7 @@ pub const File = struct {...@@ -1179,7 +1179,7 @@ pub const File = struct {
1179 if (base.tag == .lld) return;1179 if (base.tag == .lld) return;
1180 assert(!base.post_prelink);1180 assert(!base.post_prelink);
1181 switch (base.tag) {1181 switch (base.tag) {
1182 inline .elf, .elf2, .wasm => |tag| {1182 inline .elf, .elf2, .wasm, .spirv => |tag| {
1183 dev.check(tag.devFeature());1183 dev.check(tag.devFeature());
1184 return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input);1184 return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input);
1185 },1185 },
src/link/SpirV.zig+606-214
...@@ -3,7 +3,7 @@ const Allocator = std.mem.Allocator;...@@ -3,7 +3,7 @@ const Allocator = std.mem.Allocator;
3const Path = std.Build.Cache.Path;3const Path = std.Build.Cache.Path;
4const assert = std.debug.assert;4const assert = std.debug.assert;
5const log = std.log.scoped(.link);5const log = std.log.scoped(.link);
66const zig_version = @import("builtin").zig_version;
7const Zcu = @import("../Zcu.zig");7const Zcu = @import("../Zcu.zig");
8const InternPool = @import("../InternPool.zig");8const InternPool = @import("../InternPool.zig");
9const Compilation = @import("../Compilation.zig");9const Compilation = @import("../Compilation.zig");
...@@ -13,12 +13,10 @@ const Type = @import("../Type.zig");...@@ -13,12 +13,10 @@ const Type = @import("../Type.zig");
13const codegen = @import("../codegen.zig");13const codegen = @import("../codegen.zig");
14const CodeGen = @import("../codegen/spirv/CodeGen.zig");14const CodeGen = @import("../codegen/spirv/CodeGen.zig");
15const Module = @import("../codegen/spirv/Module.zig");15const Module = @import("../codegen/spirv/Module.zig");
16const trace = @import("../tracy.zig").trace;
17const BinaryModule = @import("SpirV/BinaryModule.zig");16const BinaryModule = @import("SpirV/BinaryModule.zig");
18const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");17const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
19const dedup_types = @import("SpirV/dedup_types.zig");18const dedup_types = @import("SpirV/dedup_types.zig");
20const prune_unused = @import("SpirV/prune_unused.zig");19const prune_unused = @import("SpirV/prune_unused.zig");
21
22const spec = @import("../codegen/spirv/spec.zig");20const spec = @import("../codegen/spirv/spec.zig");
23const Section = @import("../codegen/spirv/Section.zig");21const Section = @import("../codegen/spirv/Section.zig");
24const Id = spec.Id;22const Id = spec.Id;
...@@ -31,6 +29,7 @@ base: link.File,...@@ -31,6 +29,7 @@ base: link.File,
31fragments: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, Mir) = .empty,29fragments: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, Mir) = .empty,
32pending_navs: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty,30pending_navs: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty,
33entry_points: std.ArrayListUnmanaged(EntryPointDecl) = .empty,31entry_points: std.ArrayListUnmanaged(EntryPointDecl) = .empty,
32external_objects: std.ArrayListUnmanaged(ExternalObject) = .empty,
3433
35const EntryPointDecl = struct {34const EntryPointDecl = struct {
36 nav: InternPool.Nav.Index,35 nav: InternPool.Nav.Index,
...@@ -38,6 +37,11 @@ const EntryPointDecl = struct {...@@ -38,6 +37,11 @@ const EntryPointDecl = struct {
38 cc: std.builtin.CallingConvention,37 cc: std.builtin.CallingConvention,
39};38};
4039
40const ExternalObject = struct {
41 instructions: []const Word,
42 id_bound: u32,
43};
44
41pub fn createEmpty(45pub fn createEmpty(
42 arena: Allocator,46 arena: Allocator,
43 comp: *Compilation,47 comp: *Compilation,
...@@ -100,6 +104,55 @@ pub fn deinit(linker: *Linker) void {...@@ -100,6 +104,55 @@ pub fn deinit(linker: *Linker) void {
100 linker.fragments.deinit(gpa);104 linker.fragments.deinit(gpa);
101 linker.pending_navs.deinit(gpa);105 linker.pending_navs.deinit(gpa);
102 linker.entry_points.deinit(gpa);106 linker.entry_points.deinit(gpa);
107 for (linker.external_objects.items) |obj| {
108 gpa.free(obj.instructions);
109 }
110 linker.external_objects.deinit(gpa);
111}
112
113pub fn loadInput(linker: *Linker, input: link.Input) !void {
114 switch (input) {
115 .object => |obj| {
116 const comp = linker.base.comp;
117 const gpa = comp.gpa;
118 const io = comp.io;
119 const diags = &comp.link_diags;
120
121 const stat = obj.file.stat(io) catch |err|
122 return diags.fail("failed to stat SPIR-V object '{f}': {t}", .{ obj.path, err });
123 const file_size = std.math.cast(usize, stat.size) orelse
124 return diags.fail("SPIR-V object '{f}' is too large", .{obj.path});
125 if (file_size < 5 * @sizeOf(Word))
126 return diags.fail("SPIR-V object '{f}' is too small to contain a valid header", .{obj.path});
127 if (file_size % @sizeOf(Word) != 0)
128 return diags.fail("SPIR-V object '{f}' size is not a multiple of the word size", .{obj.path});
129
130 const word_count = file_size / @sizeOf(Word);
131 const all_words = try gpa.alloc(Word, word_count);
132 defer gpa.free(all_words);
133
134 const bytes = std.mem.sliceAsBytes(all_words);
135 const n_read = obj.file.readPositionalAll(io, bytes, 0) catch |err|
136 return diags.fail("failed to read SPIR-V object '{f}': {t}", .{ obj.path, err });
137 if (n_read != bytes.len)
138 return diags.fail("SPIR-V object '{f}': incomplete read", .{obj.path});
139
140 if (all_words[0] != spec.magic_number)
141 return diags.fail("SPIR-V object '{f}': invalid magic number", .{obj.path});
142
143 const id_bound = all_words[3];
144 const instructions = try gpa.dupe(Word, all_words[5..]);
145
146 try linker.external_objects.append(gpa, .{
147 .instructions = instructions,
148 .id_bound = id_bound,
149 });
150 },
151 else => {
152 const diags = &linker.base.comp.link_diags;
153 return diags.fail("unsupported link input for SPIR-V target", .{});
154 },
155 }
103}156}
104157
105pub fn updateFunc(158pub fn updateFunc(
...@@ -163,8 +216,6 @@ pub fn updateExports(...@@ -163,8 +216,6 @@ pub fn updateExports(
163 const nav_ty = ip.getNav(nav_index).resolved.?.type;216 const nav_ty = ip.getNav(nav_index).resolved.?.type;
164 if (ip.isFunctionType(nav_ty)) {217 if (ip.isFunctionType(nav_ty)) {
165 const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);218 const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);
166 if (cc == .spirv_device) return;
167
168 for (export_indices) |export_idx| {219 for (export_indices) |export_idx| {
169 const exp = export_idx.ptr(zcu);220 const exp = export_idx.ptr(zcu);
170 try linker.entry_points.append(gpa, .{221 try linker.entry_points.append(gpa, .{
...@@ -182,9 +233,6 @@ pub fn flush(...@@ -182,9 +233,6 @@ pub fn flush(
182 tid: Zcu.PerThread.Id,233 tid: Zcu.PerThread.Id,
183 prog_node: std.Progress.Node,234 prog_node: std.Progress.Node,
184) link.Error!void {235) link.Error!void {
185 const tracy = trace(@src());
186 defer tracy.end();
187
188 const sub_prog_node = prog_node.start("Flush Module", 0);236 const sub_prog_node = prog_node.start("Flush Module", 0);
189 defer sub_prog_node.end();237 defer sub_prog_node.end();
190238
...@@ -193,22 +241,23 @@ pub fn flush(...@@ -193,22 +241,23 @@ pub fn flush(
193 const gpa = comp.gpa;241 const gpa = comp.gpa;
194 const io = comp.io;242 const io = comp.io;
195243
196 const zcu = comp.zcu.?;244 if (comp.zcu) |zcu| {
197 const active = zcu.activate(tid);245 const active = zcu.activate(tid);
198 defer active.deactivate();246 defer active.deactivate();
199 const pt = active.pt;247 const pt = active.pt;
200 for (linker.pending_navs.items) |nav| {248 for (linker.pending_navs.items) |nav| {
201 if (linker.fragments.contains(nav)) continue;249 if (linker.fragments.contains(nav)) continue;
202250
203 const mir = CodeGen.generateNav(pt, nav) catch |err| switch (err) {251 const mir = CodeGen.generateNav(pt, nav) catch |err| switch (err) {
204 error.OutOfMemory => return error.OutOfMemory,252 error.OutOfMemory => return error.OutOfMemory,
205 error.AlreadyReported => continue,253 error.AlreadyReported => continue,
206 error.Canceled => return error.Canceled,254 error.Canceled => return error.Canceled,
207 };255 };
208256
209 linker.fragments.put(gpa, nav, mir) catch return error.OutOfMemory;257 linker.fragments.put(gpa, nav, mir) catch return error.OutOfMemory;
258 }
259 linker.pending_navs.clearRetainingCapacity();
210 }260 }
211 linker.pending_navs.clearRetainingCapacity();
212261
213 const merged = mergeFragments(linker, gpa, arena) catch |err| switch (err) {262 const merged = mergeFragments(linker, gpa, arena) catch |err| switch (err) {
214 error.OutOfMemory => return error.OutOfMemory,263 error.OutOfMemory => return error.OutOfMemory,
...@@ -216,7 +265,19 @@ pub fn flush(...@@ -216,7 +265,19 @@ pub fn flush(
216265
217 var binary = linkModule(arena, merged.words, merged.id_bound, sub_prog_node) catch |err| switch (err) {266 var binary = linkModule(arena, merged.words, merged.id_bound, sub_prog_node) catch |err| switch (err) {
218 error.OutOfMemory => |e| return e,267 error.OutOfMemory => |e| return e,
219 else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}),268 else => |other| {
269 // Uncomment to write the pre-link merged module for debugging
270 // const dbg_header = [_]Word{
271 // spec.magic_number,
272 // merged.version.toWord(),
273 // merged.generator_id,
274 // merged.id_bound,
275 // 0,
276 // };
277 // linker.base.file.?.writeStreamingAll(io, @ptrCast(&dbg_header)) catch {};
278 // linker.base.file.?.writeStreamingAll(io, @ptrCast(merged.words)) catch {};
279 return diags.fail("error while linking: {s}", .{@errorName(other)});
280 },
220 };281 };
221 defer binary.deinit(arena);282 defer binary.deinit(arena);
222283
...@@ -246,8 +307,9 @@ fn linkModule(arena: Allocator, words: []const Word, id_bound: u32, progress: st...@@ -246,8 +307,9 @@ fn linkModule(arena: Allocator, words: []const Word, id_bound: u32, progress: st
246307
247fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOfMemory}!MergedModule {308fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOfMemory}!MergedModule {
248 const comp = linker.base.comp;309 const comp = linker.base.comp;
249 const zcu = comp.zcu.?;310 const target = &comp.root_mod.resolved_target.result;
250 const target = zcu.getTarget();311 const maybe_ip: ?*InternPool = if (comp.zcu) |zcu| &zcu.intern_pool else null;
312 const is_obj = comp.config.output_mode == .Obj;
251313
252 var next_id: Word = 1;314 var next_id: Word = 1;
253315
...@@ -263,14 +325,10 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf...@@ -263,14 +325,10 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
263325
264 for (linker.fragments.keys(), linker.fragments.values()) |nav, *mir| {326 for (linker.fragments.keys(), linker.fragments.values()) |nav, *mir| {
265 const id_offset = next_id - 1;327 const id_offset = next_id - 1;
266 frag_infos.appendAssumeCapacity(.{328 frag_infos.appendAssumeCapacity(.{ .id_offset = id_offset });
267 .id_offset = id_offset,
268 });
269
270 if (mir.decl_result_id != .none) {329 if (mir.decl_result_id != .none) {
271 try nav_final_ids.put(gpa, nav, @enumFromInt(@intFromEnum(mir.decl_result_id) + id_offset));330 try nav_final_ids.put(gpa, nav, @enumFromInt(@intFromEnum(mir.decl_result_id) + id_offset));
272 }331 }
273
274 next_id += mir.id_bound - 1;332 next_id += mir.id_bound - 1;
275 }333 }
276334
...@@ -280,7 +338,6 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf...@@ -280,7 +338,6 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
280 try nav_final_ids.put(gpa, ref.nav, @enumFromInt(@intFromEnum(ref.local_id) + frag_info.id_offset));338 try nav_final_ids.put(gpa, ref.nav, @enumFromInt(@intFromEnum(ref.local_id) + frag_info.id_offset));
281 }339 }
282 }340 }
283
284 for (mir.uav_refs) |ref| {341 for (mir.uav_refs) |ref| {
285 const key = .{ ref.val, ref.storage_class };342 const key = .{ ref.val, ref.storage_class };
286 if (!uav_final_ids.contains(key)) {343 if (!uav_final_ids.contains(key)) {
...@@ -289,123 +346,304 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf...@@ -289,123 +346,304 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
289 }346 }
290 }347 }
291348
349 // Resolve Zig extern navs against external objects.
350 var ext_id_offsets: std.ArrayListUnmanaged(Word) = .empty;
351 defer ext_id_offsets.deinit(gpa);
352 try ext_id_offsets.ensureTotalCapacity(gpa, linker.external_objects.items.len);
353
354 var unresolved_extern_count: u32 = 0;
355 var resolved_ids: std.AutoArrayHashMapUnmanaged(Id, void) = .empty;
356 defer resolved_ids.deinit(gpa);
357
358 if (maybe_ip) |ip| {
359 var extern_name_map: std.StringArrayHashMapUnmanaged(InternPool.Nav.Index) = .empty;
360 defer extern_name_map.deinit(gpa);
361
362 var nav_it = nav_final_ids.iterator();
363 while (nav_it.next()) |entry| {
364 const nav = ip.getNav(entry.key_ptr.*);
365 if (!nav.resolved.?.is_extern_decl) continue;
366 const name = if (nav.getExtern(ip)) |e| e.name.toSlice(ip) else nav.fqn.toSlice(ip);
367 try extern_name_map.put(gpa, name, entry.key_ptr.*);
368 }
369
370 for (linker.external_objects.items) |ext_obj| {
371 const id_offset = next_id - 1;
372 ext_id_offsets.appendAssumeCapacity(id_offset);
373
374 var it: BinaryModule.Instruction.Iterator = .init(ext_obj.instructions, 0);
375 while (it.next()) |inst| {
376 const ld = LinkageDecoration.parse(inst) orelse continue;
377 if (ld.linkage_type != .@"export") continue;
378 const remapped_id: Id = @enumFromInt(@intFromEnum(ld.target_id) + id_offset);
379
380 if (extern_name_map.get(ld.name)) |nav_index| {
381 log.debug("extern resolve: '{s}' -> ext_fn_id={d}", .{ ld.name, @intFromEnum(remapped_id) });
382 nav_final_ids.getPtr(nav_index).?.* = remapped_id;
383 _ = extern_name_map.swapRemove(ld.name);
384 try resolved_ids.put(gpa, remapped_id, {});
385 }
386 }
387 next_id += ext_obj.id_bound - 1;
388 }
389
390 unresolved_extern_count = @intCast(extern_name_map.count());
391 } else {
392 for (linker.external_objects.items) |ext_obj| {
393 ext_id_offsets.appendAssumeCapacity(next_id - 1);
394 next_id += ext_obj.id_bound - 1;
395 }
396 }
397
292 var parser = BinaryModule.Parser.init(gpa) catch return error.OutOfMemory;398 var parser = BinaryModule.Parser.init(gpa) catch return error.OutOfMemory;
293 defer parser.deinit();399 defer parser.deinit();
294 var ext_inst_section = Section{};400 var sections: Sections = .{};
295 defer ext_inst_section.deinit(gpa);401 defer sections.deinit(gpa);
296 var globals_section = Section{};
297 defer globals_section.deinit(gpa);
298 var functions_section = Section{};
299 defer functions_section.deinit(gpa);
300 var annotations_section = Section{};
301 defer annotations_section.deinit(gpa);
302 var debug_names_section = Section{};
303 defer debug_names_section.deinit(gpa);
304 var debug_strings_section = Section{};
305 defer debug_strings_section.deinit(gpa);
306 var execution_modes_section = Section{};
307 defer execution_modes_section.deinit(gpa);
308402
309 for (linker.fragments.values(), frag_infos.items) |*mir, frag_info| {403 try mergeZigFragments(linker, gpa, &parser, &sections, frag_infos.items, &nav_final_ids, &uav_final_ids, &resolved_ids, maybe_ip);
404
405 var has_linkage = false;
406 try appendExternalObjects(linker, gpa, &parser, ext_id_offsets.items, &sections, &has_linkage, linker.fragments.count() == 0, is_obj, &resolved_ids);
407
408 if (is_obj) {
409 for (linker.entry_points.items) |ep| {
410 if (ep.cc != .spirv_device) continue;
411 const final_id = nav_final_ids.get(ep.nav) orelse continue;
412 try sections.annotations.emit(gpa, .OpDecorate, .{
413 .target = final_id,
414 .decoration = .{ .linkage_attributes = .{ .name = ep.name, .linkage_type = .@"export" } },
415 });
416 has_linkage = true;
417 }
418 if (unresolved_extern_count > 0) has_linkage = true;
419 }
420
421 var capabilities_section = Section{};
422 defer capabilities_section.deinit(gpa);
423 var extensions_section = Section{};
424 defer extensions_section.deinit(gpa);
425 var memory_model_section = Section{};
426 defer memory_model_section.deinit(gpa);
427
428 try emitPreamble(
429 gpa,
430 target,
431 has_linkage,
432 &capabilities_section,
433 &extensions_section,
434 &memory_model_section,
435 );
436 try emitEntryPoints(
437 linker,
438 gpa,
439 target,
440 &sections.entry_points,
441 &sections.execution_modes,
442 &nav_final_ids,
443 &uav_final_ids,
444 &frag_infos,
445 );
446
447 const zig_packed_version = (zig_version.major << 12) | (zig_version.minor << 7) | zig_version.patch;
448 if (maybe_ip) |ip| {
449 try emitSourceInfo(gpa, ip, zig_packed_version, &sections.debug_strings);
450 }
451
452 const version: spec.Version = .{
453 .major = 1,
454 .minor = blk: {
455 if (target.cpu.has(.spirv, .v1_6)) break :blk 6;
456 if (target.cpu.has(.spirv, .v1_5)) break :blk 5;
457 if (target.cpu.has(.spirv, .v1_4)) break :blk 4;
458 if (target.cpu.has(.spirv, .v1_3)) break :blk 3;
459 if (target.cpu.has(.spirv, .v1_2)) break :blk 2;
460 if (target.cpu.has(.spirv, .v1_1)) break :blk 1;
461 break :blk 0;
462 },
463 };
464
465 const buffers = &[_][]const Word{
466 capabilities_section.toWords(),
467 extensions_section.toWords(),
468 sections.ext_inst.toWords(),
469 memory_model_section.toWords(),
470 sections.entry_points.toWords(),
471 sections.execution_modes.toWords(),
472 sections.debug_strings.toWords(),
473 sections.debug_names.toWords(),
474 sections.annotations.toWords(),
475 sections.globals.toWords(),
476 sections.functions.toWords(),
477 };
478
479 var total_size: usize = 0;
480 for (buffers) |buffer| total_size += buffer.len;
481 const result = try arena.alloc(Word, total_size);
482
483 var offset: usize = 0;
484 for (buffers) |buffer| {
485 @memcpy(result[offset..][0..buffer.len], buffer);
486 offset += buffer.len;
487 }
488
489 return .{
490 .words = result,
491 .id_bound = next_id,
492 .version = version,
493 .generator_id = (spec.zig_generator_id << 16) | zig_packed_version,
494 };
495}
496
497fn mergeZigFragments(
498 linker: *Linker,
499 gpa: Allocator,
500 parser: *BinaryModule.Parser,
501 sections: *Sections,
502 frag_infos: []const FragmentInfo,
503 nav_final_ids: *const std.AutoHashMapUnmanaged(InternPool.Nav.Index, Id),
504 uav_final_ids: *const std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Id),
505 resolved_ids: *const std.AutoArrayHashMapUnmanaged(Id, void),
506 maybe_ip: ?*InternPool,
507) error{OutOfMemory}!void {
508 for (linker.fragments.values(), frag_infos) |*mir, frag_info| {
310 var id_remap: std.AutoHashMapUnmanaged(Id, Id) = .empty;509 var id_remap: std.AutoHashMapUnmanaged(Id, Id) = .empty;
311 defer id_remap.deinit(gpa);510 defer id_remap.deinit(gpa);
312511
512 var resolved_local_ids: std.AutoArrayHashMapUnmanaged(Id, void) = .empty;
513 defer resolved_local_ids.deinit(gpa);
514
313 for (mir.nav_refs) |ref| {515 for (mir.nav_refs) |ref| {
314 if (nav_final_ids.get(ref.nav)) |final_id| {516 if (nav_final_ids.get(ref.nav)) |final_id| {
315 try id_remap.put(gpa, ref.local_id, final_id);517 try id_remap.put(gpa, ref.local_id, final_id);
518 if (maybe_ip) |ip| {
519 const nav = ip.getNav(ref.nav);
520 if (nav.resolved.?.is_extern_decl and resolved_ids.contains(final_id)) {
521 try resolved_local_ids.put(gpa, ref.local_id, {});
522 }
523 }
316 }524 }
317 }525 }
318
319 for (mir.uav_refs) |ref| {526 for (mir.uav_refs) |ref| {
320 const key = .{ ref.val, ref.storage_class };527 if (uav_final_ids.get(.{ ref.val, ref.storage_class })) |final_id| {
321 if (uav_final_ids.get(key)) |final_id| {
322 try id_remap.put(gpa, ref.local_id, final_id);528 try id_remap.put(gpa, ref.local_id, final_id);
323 }529 }
324 }530 }
325531
326 try remapAndAppend(gpa, &ext_inst_section, mir.extended_instruction_set, frag_info.id_offset, &id_remap, &parser);532 try remapAndAppend(gpa, &sections.ext_inst, mir.extended_instruction_set, frag_info.id_offset, &id_remap, parser);
327 try remapAndAppend(gpa, &globals_section, mir.globals, frag_info.id_offset, &id_remap, &parser);533 try remapAndAppend(gpa, &sections.globals, mir.globals, frag_info.id_offset, &id_remap, parser);
328 try remapAndAppend(gpa, &functions_section, mir.functions, frag_info.id_offset, &id_remap, &parser);534
329 try remapAndAppend(gpa, &annotations_section, mir.annotations, frag_info.id_offset, &id_remap, &parser);535 try remapFilteredInsts(gpa, &sections.functions, mir.functions, frag_info.id_offset, &id_remap, parser, &resolved_local_ids, .skip_functions);
330 try remapAndAppend(gpa, &debug_names_section, mir.debug_names, frag_info.id_offset, &id_remap, &parser);536 try remapFilteredInsts(gpa, &sections.annotations, mir.annotations, frag_info.id_offset, &id_remap, parser, &resolved_local_ids, .skip_linkage);
331 try remapAndAppend(gpa, &debug_strings_section, mir.debug_strings, frag_info.id_offset, &id_remap, &parser);537 try remapFilteredInsts(gpa, &sections.debug_names, mir.debug_names, frag_info.id_offset, &id_remap, parser, &resolved_local_ids, .skip_names);
332 try remapAndAppend(gpa, &execution_modes_section, mir.execution_modes, frag_info.id_offset, &id_remap, &parser);538 try remapAndAppend(gpa, &sections.debug_strings, mir.debug_strings, frag_info.id_offset, &id_remap, parser);
539 try remapAndAppend(gpa, &sections.execution_modes, mir.execution_modes, frag_info.id_offset, &id_remap, parser);
333540
334 for (mir.entry_points) |ep| {541 for (mir.entry_points) |ep| {
335 try linker.entry_points.append(gpa, .{542 try linker.entry_points.append(gpa, .{ .nav = mir.owner_nav, .name = ep.name, .cc = ep.cc });
336 .nav = mir.owner_nav,
337 .name = ep.name,
338 .cc = ep.cc,
339 });
340 }543 }
341 }544 }
545}
342546
343 var capabilities_section = Section{};547const FilterMode = enum { skip_functions, skip_linkage, skip_names };
344 defer capabilities_section.deinit(gpa);
345 var extensions_section = Section{};
346 defer extensions_section.deinit(gpa);
347 var memory_model_section = Section{};
348 defer memory_model_section.deinit(gpa);
349 var entry_points_section = Section{};
350 defer entry_points_section.deinit(gpa);
351548
352 const cap_pairs = [_]struct { cap: spec.Capability, ext: ?[]const u8 }{549fn remapFilteredInsts(
353 .{ .cap = .int8, .ext = null },550 gpa: Allocator,
354 .{ .cap = .int16, .ext = null },551 dest: *Section,
355 };552 words: []const Word,
356 for (cap_pairs) |pair| {553 id_offset: Word,
357 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = pair.cap });554 id_remap: *const std.AutoHashMapUnmanaged(Id, Id),
358 if (pair.ext) |ext| {555 parser: *BinaryModule.Parser,
359 try extensions_section.emit(gpa, .OpExtension, .{ .name = ext });556 skip_ids: *const std.AutoArrayHashMapUnmanaged(Id, void),
557 mode: FilterMode,
558) error{OutOfMemory}!void {
559 if (words.len == 0) return;
560 var it: BinaryModule.Instruction.Iterator = .init(words, 0);
561 var skip_function = false;
562 while (it.next()) |inst| {
563 switch (mode) {
564 .skip_functions => {
565 if (inst.opcode == .OpFunction) {
566 skip_function = skip_ids.contains(@enumFromInt(inst.operands[1]));
567 }
568 if (skip_function) {
569 if (inst.opcode == .OpFunctionEnd) skip_function = false;
570 continue;
571 }
572 },
573 .skip_linkage => {
574 if (LinkageDecoration.parse(inst)) |ld| {
575 if (skip_ids.contains(ld.target_id)) continue;
576 }
577 },
578 .skip_names => {
579 if (inst.opcode == .OpName and inst.operands.len >= 1) {
580 if (skip_ids.contains(@enumFromInt(inst.operands[0]))) continue;
581 }
582 },
360 }583 }
584 try remapAndAppendInst(gpa, dest, words, inst, id_offset, id_remap, parser);
361 }585 }
586}
587
588fn emitPreamble(
589 gpa: Allocator,
590 target: *const std.Target,
591 has_linkage: bool,
592 capabilities: *Section,
593 extensions: *Section,
594 memory_model: *Section,
595) error{OutOfMemory}!void {
596 try capabilities.emit(gpa, .OpCapability, .{ .capability = .int8 });
597 try capabilities.emit(gpa, .OpCapability, .{ .capability = .int16 });
362598
363 switch (target.os.tag) {599 switch (target.os.tag) {
364 .opengl => {600 .opengl => {
365 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .shader });601 try capabilities.emit(gpa, .OpCapability, .{ .capability = .shader });
366 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .matrix });602 try capabilities.emit(gpa, .OpCapability, .{ .capability = .matrix });
367 },603 },
368 .vulkan => {604 .vulkan => {
369 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .shader });605 try capabilities.emit(gpa, .OpCapability, .{ .capability = .shader });
370 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .matrix });606 try capabilities.emit(gpa, .OpCapability, .{ .capability = .matrix });
371 if (target.cpu.arch == .spirv64) {607 if (target.cpu.arch == .spirv64) {
372 try extensions_section.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_physical_storage_buffer" });608 try extensions.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_physical_storage_buffer" });
373 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .physical_storage_buffer_addresses });609 try capabilities.emit(gpa, .OpCapability, .{ .capability = .physical_storage_buffer_addresses });
374 }610 }
375 },611 },
376 .opencl, .amdhsa => {612 .opencl, .amdhsa => {
377 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .kernel });613 try capabilities.emit(gpa, .OpCapability, .{ .capability = .kernel });
378 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .addresses });614 try capabilities.emit(gpa, .OpCapability, .{ .capability = .addresses });
379 },615 },
380 else => unreachable,616 else => unreachable,
381 }617 }
382 if (target.cpu.arch == .spirv64)618 if (target.cpu.arch == .spirv64)
383 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .int64 });619 try capabilities.emit(gpa, .OpCapability, .{ .capability = .int64 });
384 if (target.cpu.has(.spirv, .int64))620 if (target.cpu.has(.spirv, .int64))
385 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .int64 });621 try capabilities.emit(gpa, .OpCapability, .{ .capability = .int64 });
386 if (target.cpu.has(.spirv, .float16)) {622 if (target.cpu.has(.spirv, .float16)) {
387 if (target.os.tag == .opencl) try extensions_section.emit(gpa, .OpExtension, .{ .name = "cl_khr_fp16" });623 if (target.os.tag == .opencl) try extensions.emit(gpa, .OpExtension, .{ .name = "cl_khr_fp16" });
388 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .float16 });624 try capabilities.emit(gpa, .OpCapability, .{ .capability = .float16 });
389 }625 }
390 if (target.cpu.has(.spirv, .float64))626 if (target.cpu.has(.spirv, .float64))
391 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .float64 });627 try capabilities.emit(gpa, .OpCapability, .{ .capability = .float64 });
392 if (target.cpu.has(.spirv, .generic_pointer))628 if (target.cpu.has(.spirv, .generic_pointer))
393 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .generic_pointer });629 try capabilities.emit(gpa, .OpCapability, .{ .capability = .generic_pointer });
394 if (target.cpu.has(.spirv, .vector16))630 if (target.cpu.has(.spirv, .vector16))
395 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .vector16 });631 try capabilities.emit(gpa, .OpCapability, .{ .capability = .vector16 });
396 if (target.cpu.has(.spirv, .storage_push_constant16)) {632 if (target.cpu.has(.spirv, .storage_push_constant16)) {
397 try extensions_section.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_16bit_storage" });633 try extensions.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_16bit_storage" });
398 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .storage_push_constant16 });634 try capabilities.emit(gpa, .OpCapability, .{ .capability = .storage_push_constant16 });
399 }635 }
400 if (target.cpu.has(.spirv, .arbitrary_precision_integers)) {636 if (target.cpu.has(.spirv, .arbitrary_precision_integers)) {
401 try extensions_section.emit(gpa, .OpExtension, .{ .name = "SPV_INTEL_arbitrary_precision_integers" });637 try extensions.emit(gpa, .OpExtension, .{ .name = "SPV_INTEL_arbitrary_precision_integers" });
402 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .arbitrary_precision_integers_intel });638 try capabilities.emit(gpa, .OpCapability, .{ .capability = .arbitrary_precision_integers_intel });
403 }639 }
404 if (target.cpu.has(.spirv, .variable_pointers)) {640 if (target.cpu.has(.spirv, .variable_pointers)) {
405 try extensions_section.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_variable_pointers" });641 try extensions.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_variable_pointers" });
406 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .variable_pointers_storage_buffer });642 try capabilities.emit(gpa, .OpCapability, .{ .capability = .variable_pointers_storage_buffer });
407 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .variable_pointers });643 try capabilities.emit(gpa, .OpCapability, .{ .capability = .variable_pointers });
408 }644 }
645 if (has_linkage)
646 try capabilities.emit(gpa, .OpCapability, .{ .capability = .linkage });
409647
410 const addressing_model: spec.AddressingModel = switch (target.os.tag) {648 const addressing_model: spec.AddressingModel = switch (target.os.tag) {
411 .opengl => .logical,649 .opengl => .logical,
...@@ -414,7 +652,7 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf...@@ -414,7 +652,7 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
414 .amdhsa => .physical64,652 .amdhsa => .physical64,
415 else => unreachable,653 else => unreachable,
416 };654 };
417 try memory_model_section.emit(gpa, .OpMemoryModel, .{655 try memory_model.emit(gpa, .OpMemoryModel, .{
418 .addressing_model = addressing_model,656 .addressing_model = addressing_model,
419 .memory_model = switch (target.os.tag) {657 .memory_model = switch (target.os.tag) {
420 .opencl => .open_cl,658 .opencl => .open_cl,
...@@ -422,17 +660,26 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf...@@ -422,17 +660,26 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
422 else => unreachable,660 else => unreachable,
423 },661 },
424 });662 });
663}
425664
665fn emitEntryPoints(
666 linker: *Linker,
667 gpa: Allocator,
668 target: *const std.Target,
669 entry_points_section: *Section,
670 execution_modes_section: *Section,
671 nav_final_ids: *const std.AutoHashMapUnmanaged(InternPool.Nav.Index, Id),
672 uav_final_ids: *const std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Id),
673 frag_infos: *const std.ArrayList(FragmentInfo),
674) error{OutOfMemory}!void {
426 for (linker.entry_points.items) |ep| {675 for (linker.entry_points.items) |ep| {
427 const final_id = nav_final_ids.get(ep.nav) orelse continue;676 const final_id = nav_final_ids.get(ep.nav) orelse continue;
428677
429 var interface: std.ArrayList(Id) = .empty;678 var interface: std.ArrayList(Id) = .empty;
430 defer interface.deinit(gpa);679 defer interface.deinit(gpa);
431
432 var visited: std.AutoHashMapUnmanaged(InternPool.Nav.Index, void) = .empty;680 var visited: std.AutoHashMapUnmanaged(InternPool.Nav.Index, void) = .empty;
433 defer visited.deinit(gpa);681 defer visited.deinit(gpa);
434682 try collectEntryPointInterface(linker, ep.nav, &interface, &visited, nav_final_ids, uav_final_ids, frag_infos, gpa);
435 try collectEntryPointInterface(linker, ep.nav, &interface, &visited, &nav_final_ids, &uav_final_ids, &frag_infos, gpa);
436683
437 const exec_model: spec.ExecutionModel = switch (target.os.tag) {684 const exec_model: spec.ExecutionModel = switch (target.os.tag) {
438 .vulkan, .opengl => switch (ep.cc) {685 .vulkan, .opengl => switch (ep.cc) {
...@@ -463,11 +710,7 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf...@@ -463,11 +710,7 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
463 .spirv_kernel, .spirv_task => |kernel| {710 .spirv_kernel, .spirv_task => |kernel| {
464 try execution_modes_section.emit(gpa, .OpExecutionMode, .{711 try execution_modes_section.emit(gpa, .OpExecutionMode, .{
465 .entry_point = final_id,712 .entry_point = final_id,
466 .mode = .{ .local_size = .{713 .mode = .{ .local_size = .{ .x_size = kernel.x, .y_size = kernel.y, .z_size = kernel.z } },
467 .x_size = kernel.x,
468 .y_size = kernel.y,
469 .z_size = kernel.z,
470 } },
471 });714 });
472 },715 },
473 .spirv_fragment => |fragment| {716 .spirv_fragment => |fragment| {
...@@ -515,8 +758,9 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf...@@ -515,8 +758,9 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
515 else => {},758 else => {},
516 }759 }
517 }760 }
761}
518762
519 const ip = &zcu.intern_pool;763fn emitSourceInfo(gpa: Allocator, ip: *InternPool, version: u32, debug_strings: *Section) error{OutOfMemory}!void {
520 var error_info: std.Io.Writer.Allocating = .init(gpa);764 var error_info: std.Io.Writer.Allocating = .init(gpa);
521 defer error_info.deinit();765 defer error_info.deinit();
522 error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory;766 error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory;
...@@ -535,66 +779,8 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf...@@ -535,66 +779,8 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
535 }.isValidChar,779 }.isValidChar,
536 ) catch return error.OutOfMemory;780 ) catch return error.OutOfMemory;
537 }781 }
538 try debug_strings_section.emit(gpa, .OpSourceExtension, .{782 try debug_strings.emit(gpa, .OpSourceExtension, .{ .extension = error_info.written() });
539 .extension = error_info.written(),783 try debug_strings.emit(gpa, .OpSource, .{ .source_language = .zig, .version = version, .file = null, .source = null });
540 });
541
542 const zig_version = @import("builtin").zig_version;
543 const zig_spirv_compiler_version = comptime (zig_version.major << 12) | (zig_version.minor << 7) | zig_version.patch;
544 try debug_strings_section.emit(gpa, .OpSource, .{
545 .source_language = .zig,
546 .version = zig_spirv_compiler_version,
547 .file = null,
548 .source = null,
549 });
550
551 const version: spec.Version = .{
552 .major = 1,
553 .minor = blk: {
554 if (target.cpu.has(.spirv, .v1_6)) break :blk 6;
555 if (target.cpu.has(.spirv, .v1_5)) break :blk 5;
556 if (target.cpu.has(.spirv, .v1_4)) break :blk 4;
557 if (target.cpu.has(.spirv, .v1_3)) break :blk 3;
558 if (target.cpu.has(.spirv, .v1_2)) break :blk 2;
559 if (target.cpu.has(.spirv, .v1_1)) break :blk 1;
560 break :blk 0;
561 },
562 };
563
564 const generator_id: u32 = (spec.zig_generator_id << 16) | zig_spirv_compiler_version;
565
566 const buffers = &[_][]const Word{
567 capabilities_section.toWords(),
568 extensions_section.toWords(),
569 ext_inst_section.toWords(),
570 memory_model_section.toWords(),
571 entry_points_section.toWords(),
572 execution_modes_section.toWords(),
573 debug_strings_section.toWords(),
574 debug_names_section.toWords(),
575 annotations_section.toWords(),
576 globals_section.toWords(),
577 functions_section.toWords(),
578 };
579
580 var total_size: usize = 0;
581 for (buffers) |buffer| {
582 total_size += buffer.len;
583 }
584 const result = try arena.alloc(Word, total_size);
585
586 var offset: usize = 0;
587 for (buffers) |buffer| {
588 @memcpy(result[offset..][0..buffer.len], buffer);
589 offset += buffer.len;
590 }
591
592 return .{
593 .words = result,
594 .id_bound = next_id,
595 .version = version,
596 .generator_id = generator_id,
597 };
598}784}
599785
600const MergedModule = struct {786const MergedModule = struct {
...@@ -608,6 +794,199 @@ const FragmentInfo = struct {...@@ -608,6 +794,199 @@ const FragmentInfo = struct {
608 id_offset: Word,794 id_offset: Word,
609};795};
610796
797const LinkageDecoration = struct {
798 target_id: Id,
799 name: []const u8,
800 linkage_type: spec.LinkageType,
801
802 fn parse(inst: BinaryModule.Instruction) ?LinkageDecoration {
803 if (inst.opcode != .OpDecorate) return null;
804 if (inst.operands.len < 3) return null;
805 if (inst.operands[1] != @intFromEnum(spec.Decoration.linkage_attributes)) return null;
806 return .{
807 .target_id = @enumFromInt(inst.operands[0]),
808 .name = std.mem.sliceTo(std.mem.sliceAsBytes(inst.operands[2 .. inst.operands.len - 1]), 0),
809 .linkage_type = @enumFromInt(inst.operands[inst.operands.len - 1]),
810 };
811 }
812};
813
814const Sections = struct {
815 ext_inst: Section = .{},
816 globals: Section = .{},
817 functions: Section = .{},
818 annotations: Section = .{},
819 debug_names: Section = .{},
820 debug_strings: Section = .{},
821 entry_points: Section = .{},
822 execution_modes: Section = .{},
823
824 fn deinit(self: *Sections, gpa: Allocator) void {
825 self.ext_inst.deinit(gpa);
826 self.globals.deinit(gpa);
827 self.functions.deinit(gpa);
828 self.annotations.deinit(gpa);
829 self.debug_names.deinit(gpa);
830 self.debug_strings.deinit(gpa);
831 self.entry_points.deinit(gpa);
832 self.execution_modes.deinit(gpa);
833 }
834
835 const SectionClass = enum { ext_inst, debug_name, debug_string, annotation, global };
836
837 fn classifyPreambleInst(opcode: spec.Opcode) SectionClass {
838 return switch (opcode) {
839 .OpExtInstImport => .ext_inst,
840 .OpName, .OpMemberName => .debug_name,
841 .OpString => .debug_string,
842 .OpDecorate,
843 .OpMemberDecorate,
844 .OpGroupDecorate,
845 .OpGroupMemberDecorate,
846 .OpDecorationGroup,
847 .OpDecorateId,
848 .OpDecorateString,
849 .OpMemberDecorateString,
850 => .annotation,
851 else => .global,
852 };
853 }
854
855 fn getSection(self: *Sections, class: SectionClass) *Section {
856 return switch (class) {
857 .ext_inst => &self.ext_inst,
858 .debug_name => &self.debug_names,
859 .debug_string => &self.debug_strings,
860 .annotation => &self.annotations,
861 .global => &self.globals,
862 };
863 }
864};
865
866fn appendExternalObjects(
867 linker: *Linker,
868 gpa: Allocator,
869 parser: *BinaryModule.Parser,
870 ext_id_offsets: []const Word,
871 sections: *Sections,
872 has_linkage: *bool,
873 keep_entry_points: bool,
874 is_obj: bool,
875 resolved_ids: *const std.AutoArrayHashMapUnmanaged(Id, void),
876) error{OutOfMemory}!void {
877 var export_map: std.StringArrayHashMapUnmanaged(Id) = .empty;
878 defer export_map.deinit(gpa);
879
880 for (linker.external_objects.items, ext_id_offsets) |ext_obj, id_offset| {
881 var it: BinaryModule.Instruction.Iterator = .init(ext_obj.instructions, 0);
882 while (it.next()) |inst| {
883 const ld = LinkageDecoration.parse(inst) orelse continue;
884 if (ld.linkage_type != .@"export") continue;
885 try export_map.put(gpa, ld.name, @enumFromInt(@intFromEnum(ld.target_id) + id_offset));
886 }
887 }
888
889 var per_obj_remaps = try gpa.alloc(std.AutoHashMapUnmanaged(Id, Id), linker.external_objects.items.len);
890 defer {
891 for (per_obj_remaps) |*m| m.deinit(gpa);
892 gpa.free(per_obj_remaps);
893 }
894 for (per_obj_remaps) |*m| m.* = .empty;
895
896 var resolved_linkage_ids: std.AutoArrayHashMapUnmanaged(Id, void) = .empty;
897 defer resolved_linkage_ids.deinit(gpa);
898
899 for (resolved_ids.keys()) |id| {
900 try resolved_linkage_ids.put(gpa, id, {});
901 }
902
903 for (linker.external_objects.items, ext_id_offsets, 0..) |ext_obj, id_offset, obj_idx| {
904 var it: BinaryModule.Instruction.Iterator = .init(ext_obj.instructions, 0);
905 while (it.next()) |inst| {
906 const ld = LinkageDecoration.parse(inst) orelse continue;
907 if (ld.linkage_type != .import) continue;
908 const remapped_import: Id = @enumFromInt(@intFromEnum(ld.target_id) + id_offset);
909
910 if (export_map.get(ld.name)) |export_id| {
911 try per_obj_remaps[obj_idx].put(gpa, ld.target_id, export_id);
912 try resolved_linkage_ids.put(gpa, remapped_import, {});
913 try resolved_linkage_ids.put(gpa, export_id, {});
914 log.debug("cross-object resolve: '{s}' import={d} -> export={d}", .{
915 ld.name, @intFromEnum(remapped_import), @intFromEnum(export_id),
916 });
917 } else {
918 has_linkage.* = true;
919 }
920 }
921 }
922
923 for (linker.external_objects.items, ext_id_offsets, 0..) |ext_obj, id_offset, obj_idx| {
924 var binary = parser.initFromWords(ext_obj.instructions, ext_obj.id_bound) catch
925 return error.OutOfMemory;
926 defer binary.deinit(gpa);
927
928 const id_remap = &per_obj_remaps[obj_idx];
929
930 var preamble_it: BinaryModule.Instruction.Iterator = .init(ext_obj.instructions, 0);
931 while (preamble_it.next()) |inst| {
932 if (inst.offset >= binary.functions_start) break;
933
934 switch (inst.opcode) {
935 .OpCapability,
936 .OpExtension,
937 .OpMemoryModel,
938 .OpSource,
939 .OpSourceExtension,
940 .OpSourceContinued,
941 => continue,
942 .OpEntryPoint => {
943 if (keep_entry_points)
944 try remapAndAppendInst(gpa, &sections.entry_points, ext_obj.instructions, inst, id_offset, id_remap, parser);
945 continue;
946 },
947 .OpExecutionMode, .OpExecutionModeId => {
948 if (keep_entry_points)
949 try remapAndAppendInst(gpa, &sections.execution_modes, ext_obj.instructions, inst, id_offset, id_remap, parser);
950 continue;
951 },
952 else => {},
953 }
954
955 if (LinkageDecoration.parse(inst)) |ld| {
956 const remapped: Id = @enumFromInt(@intFromEnum(ld.target_id) + id_offset);
957 if (resolved_linkage_ids.contains(remapped)) {
958 if (ld.linkage_type == .@"export" and is_obj) {
959 has_linkage.* = true;
960 } else {
961 continue;
962 }
963 }
964 }
965
966 if (inst.opcode == .OpName and inst.operands.len >= 1) {
967 if (id_remap.contains(@enumFromInt(inst.operands[0]))) continue;
968 }
969
970 const dest = sections.getSection(Sections.classifyPreambleInst(inst.opcode));
971 try remapAndAppendInst(gpa, dest, ext_obj.instructions, inst, id_offset, id_remap, parser);
972 }
973
974 var fn_it: BinaryModule.Instruction.Iterator = .init(ext_obj.instructions, binary.functions_start);
975 var skip_function = false;
976 while (fn_it.next()) |inst| {
977 if (inst.opcode == .OpFunction) {
978 skip_function = id_remap.contains(@enumFromInt(inst.operands[1]));
979 }
980 if (!skip_function) {
981 try remapAndAppendInst(gpa, &sections.functions, ext_obj.instructions, inst, id_offset, id_remap, parser);
982 }
983 if (inst.opcode == .OpFunctionEnd) {
984 skip_function = false;
985 }
986 }
987 }
988}
989
611fn collectEntryPointInterface(990fn collectEntryPointInterface(
612 linker: *Linker,991 linker: *Linker,
613 nav: InternPool.Nav.Index,992 nav: InternPool.Nav.Index,
...@@ -665,61 +1044,74 @@ fn remapAndAppend(...@@ -665,61 +1044,74 @@ fn remapAndAppend(
6651044
666 try dest.instructions.ensureUnusedCapacity(gpa, words.len);1045 try dest.instructions.ensureUnusedCapacity(gpa, words.len);
6671046
668 var iter = BinaryModule.Instruction.Iterator.init(words, 0);1047 var it: BinaryModule.Instruction.Iterator = .init(words, 0);
669 while (iter.next()) |inst| {1048 while (it.next()) |inst| {
670 const dest_start = dest.instructions.items.len;1049 try remapAndAppendInst(gpa, dest, words, inst, id_offset, id_remap, parser);
671 const inst_words = words[inst.offset..][0..((words[inst.offset] >> 16))];1050 }
672 dest.instructions.appendSliceAssumeCapacity(inst_words);1051}
673 const inst_slice = dest.instructions.items[dest_start..][0..inst_words.len];1052
6741053fn remapAndAppendInst(
675 const inst_spec = parser.getInstSpec(inst.opcode) orelse continue;1054 gpa: Allocator,
676 var offset: usize = 0;1055 dest: *Section,
677 for (inst_spec.operands) |operand| {1056 words: []const Word,
678 const cat = operand.kind.category();1057 inst: BinaryModule.Instruction,
679 switch (operand.quantifier) {1058 id_offset: Word,
680 .required => {1059 id_remap: *const std.AutoHashMapUnmanaged(Id, Id),
681 if (offset >= inst.operands.len) break;1060 parser: *BinaryModule.Parser,
1061) error{OutOfMemory}!void {
1062 const inst_words = words[inst.offset..][0..((words[inst.offset] >> 16))];
1063 try dest.instructions.ensureUnusedCapacity(gpa, inst_words.len);
1064 const dest_start = dest.instructions.items.len;
1065 dest.instructions.appendSliceAssumeCapacity(inst_words);
1066 const inst_slice = dest.instructions.items[dest_start..][0..inst_words.len];
1067
1068 const inst_spec = parser.getInstSpec(inst.opcode) orelse return;
1069 var offset: usize = 0;
1070 for (inst_spec.operands) |operand| {
1071 const cat = operand.kind.category();
1072 switch (operand.quantifier) {
1073 .required => {
1074 if (offset >= inst.operands.len) break;
1075 if (cat == .id) {
1076 remapSingleId(&inst_slice[1 + offset], id_offset, id_remap);
1077 offset += 1;
1078 } else if (cat == .literal) {
1079 offset += operandLiteralWordCount(operand.kind, inst, offset);
1080 } else if (cat == .composite) {
1081 remapCompositeOperand(operand.kind, inst_slice, offset, id_offset, id_remap);
1082 offset += 2;
1083 } else {
1084 offset += 1;
1085 }
1086 },
1087 .optional => {
1088 if (offset >= inst.operands.len) break;
1089 if (cat == .id) {
1090 remapSingleId(&inst_slice[1 + offset], id_offset, id_remap);
1091 offset += 1;
1092 } else if (cat == .literal) {
1093 offset += operandLiteralWordCount(operand.kind, inst, offset);
1094 } else {
1095 offset += 1;
1096 }
1097 },
1098 .variadic => {
1099 while (offset < inst.operands.len) {
682 if (cat == .id) {1100 if (cat == .id) {
683 remapSingleId(&inst_slice[1 + offset], id_offset, id_remap);1101 remapSingleId(&inst_slice[1 + offset], id_offset, id_remap);
684 offset += 1;1102 offset += 1;
685 } else if (cat == .literal) {1103 } else if (cat == .literal) {
686 offset += operandLiteralWordCount(operand.kind, inst, offset);1104 offset += operandLiteralWordCount(operand.kind, inst, offset);
687 } else if (cat == .composite) {1105 } else if (cat == .composite) {
688 remapCompositeOperand(operand.kind, inst_slice, offset, id_offset, id_remap);1106 if (offset + 1 < inst.operands.len) {
1107 remapCompositeOperand(operand.kind, inst_slice, offset, id_offset, id_remap);
1108 }
689 offset += 2;1109 offset += 2;
690 } else {1110 } else {
691 offset += 1;1111 offset += 1;
692 }1112 }
693 },1113 }
694 .optional => {1114 },
695 if (offset >= inst.operands.len) break;
696 if (cat == .id) {
697 remapSingleId(&inst_slice[1 + offset], id_offset, id_remap);
698 offset += 1;
699 } else if (cat == .literal) {
700 offset += operandLiteralWordCount(operand.kind, inst, offset);
701 } else {
702 offset += 1;
703 }
704 },
705 .variadic => {
706 while (offset < inst.operands.len) {
707 if (cat == .id) {
708 remapSingleId(&inst_slice[1 + offset], id_offset, id_remap);
709 offset += 1;
710 } else if (cat == .literal) {
711 offset += operandLiteralWordCount(operand.kind, inst, offset);
712 } else if (cat == .composite) {
713 if (offset + 1 < inst.operands.len) {
714 remapCompositeOperand(operand.kind, inst_slice, offset, id_offset, id_remap);
715 }
716 offset += 2;
717 } else {
718 offset += 1;
719 }
720 }
721 },
722 }
723 }1115 }
724 }1116 }
725}1117}
src/link/SpirV/dedup_types.zig+4-15
...@@ -14,10 +14,6 @@ const Instruction = BinaryModule.Instruction;...@@ -14,10 +14,6 @@ const Instruction = BinaryModule.Instruction;
14/// When merging fragments from parallel codegen, duplicate type definitions14/// When merging fragments from parallel codegen, duplicate type definitions
15/// may exist. This pass identifies structurally identical types/constants,15/// may exist. This pass identifies structurally identical types/constants,
16/// keeps one canonical instance, and remaps all references to duplicates.16/// keeps one canonical instance, and remaps all references to duplicates.
17///
18/// Decorations and names (OpName, OpMemberName) are included in the
19/// equality check: two types that are structurally identical but have
20/// different decorations or names are NOT considered duplicates.
21pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {17pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
22 const gpa = parser.gpa;18 const gpa = parser.gpa;
2319
...@@ -32,7 +28,7 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -32,7 +28,7 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
32 while (it.next()) |inst| {28 while (it.next()) |inst| {
33 if (inst.offset >= binary.functions_start) break;29 if (inst.offset >= binary.functions_start) break;
34 switch (inst.opcode) {30 switch (inst.opcode) {
35 .OpName, .OpMemberName => {},31 .OpName, .OpMemberName => continue,
36 else => switch (inst.opcode.class()) {32 else => switch (inst.opcode.class()) {
37 .annotation => {},33 .annotation => {},
38 else => continue,34 else => continue,
...@@ -101,18 +97,11 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -101,18 +97,11 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
101 dec_hashes.items.len = 0;97 dec_hashes.items.len = 0;
102 for (dec_list.items) |dec| {98 for (dec_list.items) |dec| {
103 const dec_words = binary.instructions[dec.offset..][0..dec.len];99 const dec_words = binary.instructions[dec.offset..][0..dec.len];
104 const dec_opcode: Opcode = @enumFromInt(dec_words[0] & 0xFFFF);
105 var hasher = std.hash.Wyhash.init(0);100 var hasher = std.hash.Wyhash.init(0);
106 hasher.update(std.mem.asBytes(&dec_words[0]));101 hasher.update(std.mem.asBytes(&dec_words[0]));
107 // OpName/OpMemberName operands are literals (member index, string),102 for (dec_words[2..]) |w| {
108 // not ids — hash them directly without remapping103 const w_val = if (id_remap.get(@enumFromInt(w))) |c| @intFromEnum(c) else w;
109 if (dec_opcode == .OpName or dec_opcode == .OpMemberName) {104 hasher.update(std.mem.asBytes(&w_val));
110 hasher.update(std.mem.sliceAsBytes(dec_words[2..]));
111 } else {
112 for (dec_words[2..]) |w| {
113 const w_val = if (id_remap.get(@enumFromInt(w))) |c| @intFromEnum(c) else w;
114 hasher.update(std.mem.asBytes(&w_val));
115 }
116 }105 }
117 try dec_hashes.append(gpa, hasher.final());106 try dec_hashes.append(gpa, hasher.final());
118 }107 }
src/link/SpirV/prune_unused.zig+41-9
...@@ -35,12 +35,39 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -35,12 +35,39 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
35 var id_offset_buf: std.ArrayList(u16) = .empty;35 var id_offset_buf: std.ArrayList(u16) = .empty;
36 defer id_offset_buf.deinit(gpa);36 defer id_offset_buf.deinit(gpa);
3737
38 // mark non-prunable preamble instructions alive38 // 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.
39 it = binary.iterateInstructions();42 it = binary.iterateInstructions();
40 while (it.next()) |inst| {43 while (it.next()) |inst| {
41 if (inst.offset >= binary.functions_start) break;44 if (inst.offset >= binary.functions_start) break;
42 if (!canPrune(inst.opcode)) {45 if (canPrune(inst.opcode) or inst.opcode == .OpExtInst) continue;
43 markAlive(parser, binary.*, inst, &alive, &id_to_index, &code_offsets, &id_offset_buf) catch {};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] == @intFromEnum(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] == @intFromEnum(spec.LinkageType.@"export")) {
66 const target: ResultId = @enumFromInt(inst.operands[0]);
67 if (id_to_index.get(target)) |index| {
68 alive.set(index);
69 }
70 }
44 }71 }
45 }72 }
4673
...@@ -58,11 +85,15 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -58,11 +85,15 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
58 }85 }
59 continue;86 continue;
60 }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;
61 }92 }
6293
63 // mark operands of alive function contents94 // mark operands of alive function contents
64 if (!canPrune(inst.opcode)) {95 if (!canPrune(inst.opcode)) {
65 markAlive(parser, binary.*, inst, &alive, &id_to_index, &code_offsets, &id_offset_buf) catch {};96 try markAlive(parser, binary.*, inst, &alive, &id_to_index, &code_offsets, &id_offset_buf);
66 }97 }
67 }98 }
6899
...@@ -87,7 +118,9 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -87,7 +118,9 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
87 }118 }
88 }119 }
89120
90 if (canPrune(inst.opcode)) {121 const is_prunable = canPrune(inst.opcode) or
122 (inst.opcode == .OpExtInst and inst.offset < binary.functions_start);
123 if (is_prunable) {
91 const inst_spec = parser.getInstSpec(inst.opcode) orelse {124 const inst_spec = parser.getInstSpec(inst.opcode) orelse {
92 appendInst(&new_words, binary, inst, &new_functions_start);125 appendInst(&new_words, binary, inst, &new_functions_start);
93 continue;126 continue;
...@@ -188,11 +221,11 @@ fn markAlive(...@@ -188,11 +221,11 @@ fn markAlive(
188 _ = fn_it.next();221 _ = fn_it.next();
189 while (fn_it.next()) |fn_inst| {222 while (fn_it.next()) |fn_inst| {
190 if (fn_inst.opcode == .OpFunctionEnd) break;223 if (fn_inst.opcode == .OpFunctionEnd) break;
191 markAlive(parser, binary, fn_inst, alive, id_to_index, code_offsets, id_offset_buf) catch {};224 try markAlive(parser, binary, fn_inst, alive, id_to_index, code_offsets, id_offset_buf);
192 }225 }
193 markAlive(parser, binary, ref_inst, alive, id_to_index, code_offsets, id_offset_buf) catch {};226 try markAlive(parser, binary, ref_inst, alive, id_to_index, code_offsets, id_offset_buf);
194 } else {227 } else {
195 markAlive(parser, binary, ref_inst, alive, id_to_index, code_offsets, id_offset_buf) catch {};228 try markAlive(parser, binary, ref_inst, alive, id_to_index, code_offsets, id_offset_buf);
196 }229 }
197 }230 }
198}231}
...@@ -218,7 +251,6 @@ fn canPrune(op: Opcode) bool {...@@ -218,7 +251,6 @@ fn canPrune(op: Opcode) bool {
218 .OpString,251 .OpString,
219 .OpName,252 .OpName,
220 .OpMemberName,253 .OpMemberName,
221 .OpExtInstImport,
222 .OpVariable,254 .OpVariable,
223 => true,255 => true,
224 else => false,256 else => false,