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 {
70627062 return mem.endsWith(u8, filename, ".o") or
70637063 mem.endsWith(u8, filename, ".lo") or
70647064 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");
70667067}
70677068
70687069pub 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 {
384384
385385 switch (decl.kind) {
386386 .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
387394 const fn_info = zcu.typeToFunc(ty).?;
388395 const return_ty_id = try cg.resolveFnReturnType(.fromInterned(fn_info.return_type));
389396 const is_test = zcu.test_functions.contains(cg.owner_nav);
......@@ -677,14 +684,21 @@ fn resolve(cg: *CodeGen, inst: Air.Inst.Ref) !Id {
677684 if (inst.toInterned()) |val_ip_index| {
678685 const ty = cg.typeOf(inst);
679686 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) {
681689 .@"extern" => |@"extern"| @"extern".owner_nav,
682690 .func => |func| func.owner_nav,
683691 else => unreachable,
684692 };
685693 const spv_decl_index = try cg.module.resolveNav(ip, fn_nav);
686694 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;
688702 }
689703
690704 return try cg.constant(ty, .fromInterned(val_ip_index), .direct);
......@@ -1434,6 +1448,51 @@ fn constantUavRef(
14341448 }
14351449}
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
14371496fn constantNavRef(cg: *CodeGen, ty: Type, nav_index: InternPool.Nav.Index) !Id {
14381497 const zcu = cg.module.zcu;
14391498 const ip = &zcu.intern_pool;
......@@ -1449,7 +1508,12 @@ fn constantNavRef(cg: *CodeGen, ty: Type, nav_index: InternPool.Nav.Index) !Id {
14491508 // just generate an empty pointer. Function pointers are represented by a pointer to usize.
14501509 return try cg.module.constUndef(ty_id);
14511510 },
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 },
14531517 else => {},
14541518 },
14551519 }
src/codegen/spirv/Module.zig+3
......@@ -130,6 +130,9 @@ pub const Decl = struct {
130130 begin_dep: usize = 0,
131131 /// The past-end offset of the dependencies of this decl in the `decl_deps` array.
132132 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,
133136};
134137
135138pub const EntryPoint = struct {
src/link.zig+1-1
......@@ -1179,7 +1179,7 @@ pub const File = struct {
11791179 if (base.tag == .lld) return;
11801180 assert(!base.post_prelink);
11811181 switch (base.tag) {
1182 inline .elf, .elf2, .wasm => |tag| {
1182 inline .elf, .elf2, .wasm, .spirv => |tag| {
11831183 dev.check(tag.devFeature());
11841184 return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input);
11851185 },
src/link/SpirV.zig+606-214
......@@ -3,7 +3,7 @@ const Allocator = std.mem.Allocator;
33const Path = std.Build.Cache.Path;
44const assert = std.debug.assert;
55const log = std.log.scoped(.link);
6
6const zig_version = @import("builtin").zig_version;
77const Zcu = @import("../Zcu.zig");
88const InternPool = @import("../InternPool.zig");
99const Compilation = @import("../Compilation.zig");
......@@ -13,12 +13,10 @@ const Type = @import("../Type.zig");
1313const codegen = @import("../codegen.zig");
1414const CodeGen = @import("../codegen/spirv/CodeGen.zig");
1515const Module = @import("../codegen/spirv/Module.zig");
16const trace = @import("../tracy.zig").trace;
1716const BinaryModule = @import("SpirV/BinaryModule.zig");
1817const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
1918const dedup_types = @import("SpirV/dedup_types.zig");
2019const prune_unused = @import("SpirV/prune_unused.zig");
21
2220const spec = @import("../codegen/spirv/spec.zig");
2321const Section = @import("../codegen/spirv/Section.zig");
2422const Id = spec.Id;
......@@ -31,6 +29,7 @@ base: link.File,
3129fragments: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, Mir) = .empty,
3230pending_navs: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty,
3331entry_points: std.ArrayListUnmanaged(EntryPointDecl) = .empty,
32external_objects: std.ArrayListUnmanaged(ExternalObject) = .empty,
3433
3534const EntryPointDecl = struct {
3635 nav: InternPool.Nav.Index,
......@@ -38,6 +37,11 @@ const EntryPointDecl = struct {
3837 cc: std.builtin.CallingConvention,
3938};
4039
40const ExternalObject = struct {
41 instructions: []const Word,
42 id_bound: u32,
43};
44
4145pub fn createEmpty(
4246 arena: Allocator,
4347 comp: *Compilation,
......@@ -100,6 +104,55 @@ pub fn deinit(linker: *Linker) void {
100104 linker.fragments.deinit(gpa);
101105 linker.pending_navs.deinit(gpa);
102106 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 }
103156}
104157
105158pub fn updateFunc(
......@@ -163,8 +216,6 @@ pub fn updateExports(
163216 const nav_ty = ip.getNav(nav_index).resolved.?.type;
164217 if (ip.isFunctionType(nav_ty)) {
165218 const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);
166 if (cc == .spirv_device) return;
167
168219 for (export_indices) |export_idx| {
169220 const exp = export_idx.ptr(zcu);
170221 try linker.entry_points.append(gpa, .{
......@@ -182,9 +233,6 @@ pub fn flush(
182233 tid: Zcu.PerThread.Id,
183234 prog_node: std.Progress.Node,
184235) link.Error!void {
185 const tracy = trace(@src());
186 defer tracy.end();
187
188236 const sub_prog_node = prog_node.start("Flush Module", 0);
189237 defer sub_prog_node.end();
190238
......@@ -193,22 +241,23 @@ pub fn flush(
193241 const gpa = comp.gpa;
194242 const io = comp.io;
195243
196 const zcu = comp.zcu.?;
197 const active = zcu.activate(tid);
198 defer active.deactivate();
199 const pt = active.pt;
200 for (linker.pending_navs.items) |nav| {
201 if (linker.fragments.contains(nav)) continue;
202
203 const mir = CodeGen.generateNav(pt, nav) catch |err| switch (err) {
204 error.OutOfMemory => return error.OutOfMemory,
205 error.AlreadyReported => continue,
206 error.Canceled => return error.Canceled,
207 };
244 if (comp.zcu) |zcu| {
245 const active = zcu.activate(tid);
246 defer active.deactivate();
247 const pt = active.pt;
248 for (linker.pending_navs.items) |nav| {
249 if (linker.fragments.contains(nav)) continue;
250
251 const mir = CodeGen.generateNav(pt, nav) catch |err| switch (err) {
252 error.OutOfMemory => return error.OutOfMemory,
253 error.AlreadyReported => continue,
254 error.Canceled => return error.Canceled,
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();
210260 }
211 linker.pending_navs.clearRetainingCapacity();
212261
213262 const merged = mergeFragments(linker, gpa, arena) catch |err| switch (err) {
214263 error.OutOfMemory => return error.OutOfMemory,
......@@ -216,7 +265,19 @@ pub fn flush(
216265
217266 var binary = linkModule(arena, merged.words, merged.id_bound, sub_prog_node) catch |err| switch (err) {
218267 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 },
220281 };
221282 defer binary.deinit(arena);
222283
......@@ -246,8 +307,9 @@ fn linkModule(arena: Allocator, words: []const Word, id_bound: u32, progress: st
246307
247308fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOfMemory}!MergedModule {
248309 const comp = linker.base.comp;
249 const zcu = comp.zcu.?;
250 const target = zcu.getTarget();
310 const target = &comp.root_mod.resolved_target.result;
311 const maybe_ip: ?*InternPool = if (comp.zcu) |zcu| &zcu.intern_pool else null;
312 const is_obj = comp.config.output_mode == .Obj;
251313
252314 var next_id: Word = 1;
253315
......@@ -263,14 +325,10 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
263325
264326 for (linker.fragments.keys(), linker.fragments.values()) |nav, *mir| {
265327 const id_offset = next_id - 1;
266 frag_infos.appendAssumeCapacity(.{
267 .id_offset = id_offset,
268 });
269
328 frag_infos.appendAssumeCapacity(.{ .id_offset = id_offset });
270329 if (mir.decl_result_id != .none) {
271330 try nav_final_ids.put(gpa, nav, @enumFromInt(@intFromEnum(mir.decl_result_id) + id_offset));
272331 }
273
274332 next_id += mir.id_bound - 1;
275333 }
276334
......@@ -280,7 +338,6 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
280338 try nav_final_ids.put(gpa, ref.nav, @enumFromInt(@intFromEnum(ref.local_id) + frag_info.id_offset));
281339 }
282340 }
283
284341 for (mir.uav_refs) |ref| {
285342 const key = .{ ref.val, ref.storage_class };
286343 if (!uav_final_ids.contains(key)) {
......@@ -289,123 +346,304 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
289346 }
290347 }
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
292398 var parser = BinaryModule.Parser.init(gpa) catch return error.OutOfMemory;
293399 defer parser.deinit();
294 var ext_inst_section = Section{};
295 defer ext_inst_section.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);
400 var sections: Sections = .{};
401 defer sections.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| {
310509 var id_remap: std.AutoHashMapUnmanaged(Id, Id) = .empty;
311510 defer id_remap.deinit(gpa);
312511
512 var resolved_local_ids: std.AutoArrayHashMapUnmanaged(Id, void) = .empty;
513 defer resolved_local_ids.deinit(gpa);
514
313515 for (mir.nav_refs) |ref| {
314516 if (nav_final_ids.get(ref.nav)) |final_id| {
315517 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 }
316524 }
317525 }
318
319526 for (mir.uav_refs) |ref| {
320 const key = .{ ref.val, ref.storage_class };
321 if (uav_final_ids.get(key)) |final_id| {
527 if (uav_final_ids.get(.{ ref.val, ref.storage_class })) |final_id| {
322528 try id_remap.put(gpa, ref.local_id, final_id);
323529 }
324530 }
325531
326 try remapAndAppend(gpa, &ext_inst_section, 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);
328 try remapAndAppend(gpa, &functions_section, mir.functions, frag_info.id_offset, &id_remap, &parser);
329 try remapAndAppend(gpa, &annotations_section, mir.annotations, frag_info.id_offset, &id_remap, &parser);
330 try remapAndAppend(gpa, &debug_names_section, mir.debug_names, frag_info.id_offset, &id_remap, &parser);
331 try remapAndAppend(gpa, &debug_strings_section, mir.debug_strings, frag_info.id_offset, &id_remap, &parser);
332 try remapAndAppend(gpa, &execution_modes_section, mir.execution_modes, 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);
533 try remapAndAppend(gpa, &sections.globals, mir.globals, frag_info.id_offset, &id_remap, parser);
534
535 try remapFilteredInsts(gpa, &sections.functions, mir.functions, frag_info.id_offset, &id_remap, parser, &resolved_local_ids, .skip_functions);
536 try remapFilteredInsts(gpa, &sections.annotations, mir.annotations, frag_info.id_offset, &id_remap, parser, &resolved_local_ids, .skip_linkage);
537 try remapFilteredInsts(gpa, &sections.debug_names, mir.debug_names, frag_info.id_offset, &id_remap, parser, &resolved_local_ids, .skip_names);
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
334541 for (mir.entry_points) |ep| {
335 try linker.entry_points.append(gpa, .{
336 .nav = mir.owner_nav,
337 .name = ep.name,
338 .cc = ep.cc,
339 });
542 try linker.entry_points.append(gpa, .{ .nav = mir.owner_nav, .name = ep.name, .cc = ep.cc });
340543 }
341544 }
545}
342546
343 var capabilities_section = Section{};
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);
547const FilterMode = enum { skip_functions, skip_linkage, skip_names };
351548
352 const cap_pairs = [_]struct { cap: spec.Capability, ext: ?[]const u8 }{
353 .{ .cap = .int8, .ext = null },
354 .{ .cap = .int16, .ext = null },
355 };
356 for (cap_pairs) |pair| {
357 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = pair.cap });
358 if (pair.ext) |ext| {
359 try extensions_section.emit(gpa, .OpExtension, .{ .name = ext });
549fn remapFilteredInsts(
550 gpa: Allocator,
551 dest: *Section,
552 words: []const Word,
553 id_offset: Word,
554 id_remap: *const std.AutoHashMapUnmanaged(Id, Id),
555 parser: *BinaryModule.Parser,
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 },
360583 }
584 try remapAndAppendInst(gpa, dest, words, inst, id_offset, id_remap, parser);
361585 }
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
363599 switch (target.os.tag) {
364600 .opengl => {
365 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .shader });
366 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .matrix });
601 try capabilities.emit(gpa, .OpCapability, .{ .capability = .shader });
602 try capabilities.emit(gpa, .OpCapability, .{ .capability = .matrix });
367603 },
368604 .vulkan => {
369 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .shader });
370 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .matrix });
605 try capabilities.emit(gpa, .OpCapability, .{ .capability = .shader });
606 try capabilities.emit(gpa, .OpCapability, .{ .capability = .matrix });
371607 if (target.cpu.arch == .spirv64) {
372 try extensions_section.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_physical_storage_buffer" });
373 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .physical_storage_buffer_addresses });
608 try extensions.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_physical_storage_buffer" });
609 try capabilities.emit(gpa, .OpCapability, .{ .capability = .physical_storage_buffer_addresses });
374610 }
375611 },
376612 .opencl, .amdhsa => {
377 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .kernel });
378 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .addresses });
613 try capabilities.emit(gpa, .OpCapability, .{ .capability = .kernel });
614 try capabilities.emit(gpa, .OpCapability, .{ .capability = .addresses });
379615 },
380616 else => unreachable,
381617 }
382618 if (target.cpu.arch == .spirv64)
383 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .int64 });
619 try capabilities.emit(gpa, .OpCapability, .{ .capability = .int64 });
384620 if (target.cpu.has(.spirv, .int64))
385 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .int64 });
621 try capabilities.emit(gpa, .OpCapability, .{ .capability = .int64 });
386622 if (target.cpu.has(.spirv, .float16)) {
387 if (target.os.tag == .opencl) try extensions_section.emit(gpa, .OpExtension, .{ .name = "cl_khr_fp16" });
388 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .float16 });
623 if (target.os.tag == .opencl) try extensions.emit(gpa, .OpExtension, .{ .name = "cl_khr_fp16" });
624 try capabilities.emit(gpa, .OpCapability, .{ .capability = .float16 });
389625 }
390626 if (target.cpu.has(.spirv, .float64))
391 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .float64 });
627 try capabilities.emit(gpa, .OpCapability, .{ .capability = .float64 });
392628 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 });
394630 if (target.cpu.has(.spirv, .vector16))
395 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .vector16 });
631 try capabilities.emit(gpa, .OpCapability, .{ .capability = .vector16 });
396632 if (target.cpu.has(.spirv, .storage_push_constant16)) {
397 try extensions_section.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_16bit_storage" });
398 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .storage_push_constant16 });
633 try extensions.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_16bit_storage" });
634 try capabilities.emit(gpa, .OpCapability, .{ .capability = .storage_push_constant16 });
399635 }
400636 if (target.cpu.has(.spirv, .arbitrary_precision_integers)) {
401 try extensions_section.emit(gpa, .OpExtension, .{ .name = "SPV_INTEL_arbitrary_precision_integers" });
402 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .arbitrary_precision_integers_intel });
637 try extensions.emit(gpa, .OpExtension, .{ .name = "SPV_INTEL_arbitrary_precision_integers" });
638 try capabilities.emit(gpa, .OpCapability, .{ .capability = .arbitrary_precision_integers_intel });
403639 }
404640 if (target.cpu.has(.spirv, .variable_pointers)) {
405 try extensions_section.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_variable_pointers" });
406 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .variable_pointers_storage_buffer });
407 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .variable_pointers });
641 try extensions.emit(gpa, .OpExtension, .{ .name = "SPV_KHR_variable_pointers" });
642 try capabilities.emit(gpa, .OpCapability, .{ .capability = .variable_pointers_storage_buffer });
643 try capabilities.emit(gpa, .OpCapability, .{ .capability = .variable_pointers });
408644 }
645 if (has_linkage)
646 try capabilities.emit(gpa, .OpCapability, .{ .capability = .linkage });
409647
410648 const addressing_model: spec.AddressingModel = switch (target.os.tag) {
411649 .opengl => .logical,
......@@ -414,7 +652,7 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
414652 .amdhsa => .physical64,
415653 else => unreachable,
416654 };
417 try memory_model_section.emit(gpa, .OpMemoryModel, .{
655 try memory_model.emit(gpa, .OpMemoryModel, .{
418656 .addressing_model = addressing_model,
419657 .memory_model = switch (target.os.tag) {
420658 .opencl => .open_cl,
......@@ -422,17 +660,26 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
422660 else => unreachable,
423661 },
424662 });
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 {
426675 for (linker.entry_points.items) |ep| {
427676 const final_id = nav_final_ids.get(ep.nav) orelse continue;
428677
429678 var interface: std.ArrayList(Id) = .empty;
430679 defer interface.deinit(gpa);
431
432680 var visited: std.AutoHashMapUnmanaged(InternPool.Nav.Index, void) = .empty;
433681 defer visited.deinit(gpa);
434
435 try collectEntryPointInterface(linker, ep.nav, &interface, &visited, &nav_final_ids, &uav_final_ids, &frag_infos, gpa);
682 try collectEntryPointInterface(linker, ep.nav, &interface, &visited, nav_final_ids, uav_final_ids, frag_infos, gpa);
436683
437684 const exec_model: spec.ExecutionModel = switch (target.os.tag) {
438685 .vulkan, .opengl => switch (ep.cc) {
......@@ -463,11 +710,7 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
463710 .spirv_kernel, .spirv_task => |kernel| {
464711 try execution_modes_section.emit(gpa, .OpExecutionMode, .{
465712 .entry_point = final_id,
466 .mode = .{ .local_size = .{
467 .x_size = kernel.x,
468 .y_size = kernel.y,
469 .z_size = kernel.z,
470 } },
713 .mode = .{ .local_size = .{ .x_size = kernel.x, .y_size = kernel.y, .z_size = kernel.z } },
471714 });
472715 },
473716 .spirv_fragment => |fragment| {
......@@ -515,8 +758,9 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
515758 else => {},
516759 }
517760 }
761}
518762
519 const ip = &zcu.intern_pool;
763fn emitSourceInfo(gpa: Allocator, ip: *InternPool, version: u32, debug_strings: *Section) error{OutOfMemory}!void {
520764 var error_info: std.Io.Writer.Allocating = .init(gpa);
521765 defer error_info.deinit();
522766 error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory;
......@@ -535,66 +779,8 @@ fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOf
535779 }.isValidChar,
536780 ) catch return error.OutOfMemory;
537781 }
538 try debug_strings_section.emit(gpa, .OpSourceExtension, .{
539 .extension = error_info.written(),
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 };
782 try debug_strings.emit(gpa, .OpSourceExtension, .{ .extension = error_info.written() });
783 try debug_strings.emit(gpa, .OpSource, .{ .source_language = .zig, .version = version, .file = null, .source = null });
598784}
599785
600786const MergedModule = struct {
......@@ -608,6 +794,199 @@ const FragmentInfo = struct {
608794 id_offset: Word,
609795};
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
611990fn collectEntryPointInterface(
612991 linker: *Linker,
613992 nav: InternPool.Nav.Index,
......@@ -665,61 +1044,74 @@ fn remapAndAppend(
6651044
6661045 try dest.instructions.ensureUnusedCapacity(gpa, words.len);
6671046
668 var iter = BinaryModule.Instruction.Iterator.init(words, 0);
669 while (iter.next()) |inst| {
670 const dest_start = dest.instructions.items.len;
671 const inst_words = words[inst.offset..][0..((words[inst.offset] >> 16))];
672 dest.instructions.appendSliceAssumeCapacity(inst_words);
673 const inst_slice = dest.instructions.items[dest_start..][0..inst_words.len];
674
675 const inst_spec = parser.getInstSpec(inst.opcode) orelse continue;
676 var offset: usize = 0;
677 for (inst_spec.operands) |operand| {
678 const cat = operand.kind.category();
679 switch (operand.quantifier) {
680 .required => {
681 if (offset >= inst.operands.len) break;
1047 var it: BinaryModule.Instruction.Iterator = .init(words, 0);
1048 while (it.next()) |inst| {
1049 try remapAndAppendInst(gpa, dest, words, inst, id_offset, id_remap, parser);
1050 }
1051}
1052
1053fn remapAndAppendInst(
1054 gpa: Allocator,
1055 dest: *Section,
1056 words: []const Word,
1057 inst: BinaryModule.Instruction,
1058 id_offset: Word,
1059 id_remap: *const std.AutoHashMapUnmanaged(Id, Id),
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) {
6821100 if (cat == .id) {
6831101 remapSingleId(&inst_slice[1 + offset], id_offset, id_remap);
6841102 offset += 1;
6851103 } else if (cat == .literal) {
6861104 offset += operandLiteralWordCount(operand.kind, inst, offset);
6871105 } 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 }
6891109 offset += 2;
6901110 } else {
6911111 offset += 1;
6921112 }
693 },
694 .optional => {
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 }
1113 }
1114 },
7231115 }
7241116 }
7251117}
src/link/SpirV/dedup_types.zig+4-15
......@@ -14,10 +14,6 @@ const Instruction = BinaryModule.Instruction;
1414/// When merging fragments from parallel codegen, duplicate type definitions
1515/// may exist. This pass identifies structurally identical types/constants,
1616/// 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.
2117pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
2218 const gpa = parser.gpa;
2319
......@@ -32,7 +28,7 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
3228 while (it.next()) |inst| {
3329 if (inst.offset >= binary.functions_start) break;
3430 switch (inst.opcode) {
35 .OpName, .OpMemberName => {},
31 .OpName, .OpMemberName => continue,
3632 else => switch (inst.opcode.class()) {
3733 .annotation => {},
3834 else => continue,
......@@ -101,18 +97,11 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
10197 dec_hashes.items.len = 0;
10298 for (dec_list.items) |dec| {
10399 const dec_words = binary.instructions[dec.offset..][0..dec.len];
104 const dec_opcode: Opcode = @enumFromInt(dec_words[0] & 0xFFFF);
105100 var hasher = std.hash.Wyhash.init(0);
106101 hasher.update(std.mem.asBytes(&dec_words[0]));
107 // OpName/OpMemberName operands are literals (member index, string),
108 // not ids — hash them directly without remapping
109 if (dec_opcode == .OpName or dec_opcode == .OpMemberName) {
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 }
102 for (dec_words[2..]) |w| {
103 const w_val = if (id_remap.get(@enumFromInt(w))) |c| @intFromEnum(c) else w;
104 hasher.update(std.mem.asBytes(&w_val));
116105 }
117106 try dec_hashes.append(gpa, hasher.final());
118107 }
src/link/SpirV/prune_unused.zig+41-9
......@@ -35,12 +35,39 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
3535 var id_offset_buf: std.ArrayList(u16) = .empty;
3636 defer id_offset_buf.deinit(gpa);
3737
38 // mark non-prunable preamble instructions alive
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.
3942 it = binary.iterateInstructions();
4043 while (it.next()) |inst| {
4144 if (inst.offset >= binary.functions_start) break;
42 if (!canPrune(inst.opcode)) {
43 markAlive(parser, binary.*, inst, &alive, &id_to_index, &code_offsets, &id_offset_buf) catch {};
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] == @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 }
4471 }
4572 }
4673
......@@ -58,11 +85,15 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
5885 }
5986 continue;
6087 }
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;
6192 }
6293
6394 // mark operands of alive function contents
6495 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);
6697 }
6798 }
6899
......@@ -87,7 +118,9 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
87118 }
88119 }
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) {
91124 const inst_spec = parser.getInstSpec(inst.opcode) orelse {
92125 appendInst(&new_words, binary, inst, &new_functions_start);
93126 continue;
......@@ -188,11 +221,11 @@ fn markAlive(
188221 _ = fn_it.next();
189222 while (fn_it.next()) |fn_inst| {
190223 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);
192225 }
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);
194227 } 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);
196229 }
197230 }
198231}
......@@ -218,7 +251,6 @@ fn canPrune(op: Opcode) bool {
218251 .OpString,
219252 .OpName,
220253 .OpMemberName,
221 .OpExtInstImport,
222254 .OpVariable,
223255 => true,
224256 else => false,