authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-06-14 19:48:11+03:30
committergravatar for alichraghi@noreply.codeberg.orgAli Cheraghi <alichraghi@noreply.codeberg.org> 2026-06-18 13:38:58+02:00
logc6d178f93de5922163b51cedbf05023623558c6b
tree60c75b3ed435e687efc1a58da47a53feafe2e045
parentcfeab9258739f44b73542952b3ca62c1187669e1

spirv: make codegen multi-threaded

The SPIR-V backend previously ran codegen single-threaded in the linker thread. Now each codegen job creates an `Mir` like other self-hosted backends. Other changes: - Bring back `dedup_types.zig` and `prune_unused.zig` **ISel**s which were originaly removed because codegen was single-threaded at that time and therefor had no use - Clean up `BinaryModule.zig` - Remove `checkLogicalPtrOperation` from `elemPtrOneLayerOnly` in `Sema.zig`. Element access uses `OpAccessChain`, which works on all logical address spaces without `VariablePointers`. the check is only needed for pointer arithmetic.

20 files changed, 1512 insertions(+), 387 deletions(-)

src/Sema.zig+9-21
......@@ -27094,11 +27094,9 @@ fn elemPtrOneLayerOnly(
2709427094 return .fromValue(try ptr_val.ptrElem(index, pt));
2709527095 }
2709627096
27097 try sema.checkLogicalPtrOperation(block, src, indexable_ty);
27098
2709927097 const result_ty = try indexable_ty.elemPtrType(maybe_index, pt);
2710027098
27101 try sema.validateRuntimeElemAccess(block, elem_index_src, result_ty, indexable_ty, indexable_src);
27099 try sema.validateRuntimeElemAccess(block, elem_index_src, result_ty, indexable_src);
2710227100 try sema.validateRuntimeValue(block, indexable_src, indexable);
2710327101
2710427102 if (child_ty.abiSize(zcu) == 0) {
......@@ -27161,7 +27159,7 @@ fn elemVal(
2716127159 return sema.analyzeLoad(block, src, .fromValue(elem_ptr_val), indexable_src);
2716227160 }
2716327161
27164 try sema.validateRuntimeElemAccess(block, elem_index_src, child_ty, indexable_ty, src);
27162 try sema.validateRuntimeElemAccess(block, elem_index_src, child_ty, src);
2716527163 switch (child_ty.classify(zcu)) {
2716627164 .runtime => {},
2716727165 .one_possible_value => return .fromValue((try child_ty.onePossibleValue(pt)).?),
......@@ -27210,11 +27208,9 @@ fn validateRuntimeElemAccess(
2721027208 block: *Block,
2721127209 elem_index_src: LazySrcLoc,
2721227210 elem_ty: Type,
27213 parent_ty: Type,
2721427211 parent_src: LazySrcLoc,
2721527212) CompileError!void {
27216 const pt = sema.pt;
27217 const zcu = pt.zcu;
27213 const zcu = sema.pt.zcu;
2721827214
2721927215 if (elem_ty.comptimeOnly(zcu)) {
2722027216 const msg = msg: {
......@@ -27231,14 +27227,6 @@ fn validateRuntimeElemAccess(
2723127227 };
2723227228 return sema.failWithOwnedErrorMsg(block, msg);
2723327229 }
27234
27235 if (zcu.intern_pool.indexToKey(parent_ty.toIntern()) == .ptr_type) {
27236 const target = zcu.getTarget();
27237 const as = parent_ty.ptrAddressSpace(zcu);
27238 if (target_util.shouldBlockPointerOps(target, as)) {
27239 return sema.fail(block, elem_index_src, "cannot access element of logical pointer '{f}'", .{parent_ty.fmt(pt)});
27240 }
27241 }
2724227230}
2724327231
2724427232/// Validates `elem_index`, and returns a pointer to that field using `structFieldPtrByIndex`.
......@@ -27309,7 +27297,7 @@ fn tupleField(
2730927297 return Air.internedToRef((try tuple_val.fieldValue(pt, field_index)).toIntern());
2731027298 }
2731127299
27312 try sema.validateRuntimeElemAccess(block, field_index_src, field_ty, tuple_ty, tuple_src);
27300 try sema.validateRuntimeElemAccess(block, field_index_src, field_ty, tuple_src);
2731327301
2731427302 return block.addStructFieldVal(tuple, field_index, field_ty);
2731527303}
......@@ -27364,7 +27352,7 @@ fn elemValArray(
2736427352 if (try elem_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
2736527353 }
2736627354
27367 try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, array_ty, array_src);
27355 try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, array_src);
2736827356 try sema.validateRuntimeValue(block, array_src, array);
2736927357
2737027358 if (oob_safety and block.wantSafety()) {
......@@ -27467,7 +27455,7 @@ fn elemPtrVector(
2746727455 };
2746827456
2746927457 if (!init) {
27470 try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, vector_ty, vector_ptr_src);
27458 try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, vector_ptr_src);
2747127459 try sema.validateRuntimeValue(block, vector_ptr_src, vector_ptr);
2747227460 }
2747327461
......@@ -27540,7 +27528,7 @@ fn elemPtrArray(
2754027528 }
2754127529
2754227530 if (!init) {
27543 try sema.validateRuntimeElemAccess(block, elem_index_src, array_ty.childType(zcu), array_ty, array_ptr_src);
27531 try sema.validateRuntimeElemAccess(block, elem_index_src, array_ty.childType(zcu), array_ptr_src);
2754427532 try sema.validateRuntimeValue(block, array_ptr_src, array_ptr);
2754527533 }
2754627534
......@@ -27603,7 +27591,7 @@ fn elemValSlice(
2760327591
2760427592 if (try elem_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
2760527593
27606 try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, slice_ty, slice_src);
27594 try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, slice_src);
2760727595 try sema.validateRuntimeValue(block, slice_src, slice);
2760827596
2760927597 if (oob_safety and block.wantSafety()) {
......@@ -27663,7 +27651,7 @@ fn elemPtrSlice(
2766327651 }
2766427652 }
2766527653
27666 try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ptr_ty, slice_ty, slice_src);
27654 try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ptr_ty, slice_src);
2766727655 try sema.validateRuntimeValue(block, slice_src, slice);
2766827656
2766927657 if (oob_safety and block.wantSafety()) {
src/Zcu/PerThread.zig-13
......@@ -4513,7 +4513,6 @@ pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) Ru
45134513 comp.config.use_llvm,
45144514 )) {
45154515 else => unreachable, // assertion failure
4516 .stage2_spirv,
45174516 .stage2_llvm,
45184517 => {},
45194518 },
......@@ -4594,18 +4593,6 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e
45944593
45954594 const lf = comp.bin_file orelse return error.NoLinkFile;
45964595
4597 // Just like LLVM, the SPIR-V backend can't multi-threaded due to SPIR-V design limitations.
4598 if (lf.cast(.spirv)) |spirv_file| {
4599 assert(zcu.pending_codegen_jobs.load(.monotonic) == 2); // only one codegen at a time (but the value is 2 because 1 is the base)
4600 spirv_file.updateFunc(pt, func_index, air, &liveness) catch |err| {
4601 switch (err) {
4602 error.OutOfMemory => comp.link_diags.setAllocFailure(),
4603 }
4604 return error.AlreadyReported;
4605 };
4606 return error.BackendDoesNotProduceMir;
4607 }
4608
46094596 return codegen.generateFunction(lf, pt, func_index, air, &liveness);
46104597}
46114598
src/codegen.zig+4
......@@ -101,6 +101,7 @@ pub const AnyMir = union {
101101 x86_64: if (dev.env.supports(.x86_64_backend)) @import("codegen/x86_64/Mir.zig") else noreturn,
102102 wasm: if (dev.env.supports(.wasm_backend)) @import("codegen/wasm/Mir.zig") else noreturn,
103103 c: if (dev.env.supports(.c_backend)) @import("codegen/c.zig").Mir else noreturn,
104 spirv: if (dev.env.supports(.spirv_backend)) @import("codegen/spirv/Mir.zig") else noreturn,
104105
105106 pub inline fn tag(comptime backend: std.lang.CompilerBackend) []const u8 {
106107 return switch (backend) {
......@@ -110,6 +111,7 @@ pub const AnyMir = union {
110111 .stage2_x86_64 => "x86_64",
111112 .stage2_wasm => "wasm",
112113 .stage2_c => "c",
114 .stage2_spirv => "spirv",
113115 else => unreachable,
114116 };
115117 }
......@@ -125,6 +127,7 @@ pub const AnyMir = union {
125127 .stage2_x86_64,
126128 .stage2_wasm,
127129 .stage2_c,
130 .stage2_spirv,
128131 => |backend_ct| @field(mir, tag(backend_ct)).deinit(gpa),
129132 }
130133 }
......@@ -153,6 +156,7 @@ pub fn generateFunction(
153156 .stage2_x86_64,
154157 .stage2_wasm,
155158 .stage2_c,
159 .stage2_spirv,
156160 => |backend| {
157161 dev.check(devFeatureForBackend(backend));
158162 const CodeGen = importBackend(backend);
src/codegen/spirv/CodeGen.zig+194
......@@ -5,6 +5,8 @@ const Signedness = std.lang.Signedness;
55const assert = std.debug.assert;
66const log = std.log.scoped(.codegen);
77
8const link = @import("../../link.zig");
9const codegen = @import("../../codegen.zig");
810const Zcu = @import("../../Zcu.zig");
911const Type = @import("../../Type.zig");
1012const Value = @import("../../Value.zig");
......@@ -12,6 +14,7 @@ const Air = @import("../../Air.zig");
1214const InternPool = @import("../../InternPool.zig");
1315const Section = @import("Section.zig");
1416const Assembler = @import("Assembler.zig");
17const Mir = @import("Mir.zig");
1518
1619const spec = @import("spec.zig");
1720const Opcode = spec.Opcode;
......@@ -169,6 +172,197 @@ pub fn deinit(cg: *CodeGen) void {
169172 cg.body.deinit(gpa);
170173}
171174
175pub fn generate(
176 _: *link.File,
177 pt: Zcu.PerThread,
178 func_index: InternPool.Index,
179 air: *const Air,
180 liveness: *const ?Air.Liveness,
181) codegen.Error!Mir {
182 const zcu = pt.zcu;
183 const gpa = zcu.gpa;
184 const nav = zcu.funcInfo(func_index).owner_nav;
185 const structured_cfg = zcu.navFileScope(nav).mod.?.structured_cfg;
186
187 var arena = std.heap.ArenaAllocator.init(gpa);
188 defer arena.deinit();
189 var module: Module = .{
190 .gpa = gpa,
191 .arena = arena.allocator(),
192 .zcu = zcu,
193 };
194 defer module.deinit();
195
196 var cg: CodeGen = .{
197 .pt = pt,
198 .air = air.*,
199 .liveness = liveness.*.?,
200 .owner_nav = nav,
201 .module = &module,
202 .control_flow = switch (structured_cfg) {
203 true => .{ .structured = .{} },
204 false => .{ .unstructured = .{} },
205 },
206 .base_line = zcu.navSrcLine(nav),
207 };
208 defer cg.deinit();
209
210 cg.genNav(true) catch |err| switch (err) {
211 error.AlreadyReported => return error.AlreadyReported,
212 error.OutOfMemory => return error.OutOfMemory,
213 };
214
215 return cg.serializeToMir(gpa);
216}
217
218pub fn generateNav(
219 pt: Zcu.PerThread,
220 nav_index: InternPool.Nav.Index,
221) codegen.Error!Mir {
222 const zcu = pt.zcu;
223 const gpa = zcu.gpa;
224 const structured_cfg = zcu.navFileScope(nav_index).mod.?.structured_cfg;
225
226 var arena = std.heap.ArenaAllocator.init(gpa);
227 defer arena.deinit();
228 var module: Module = .{
229 .gpa = gpa,
230 .arena = arena.allocator(),
231 .zcu = zcu,
232 };
233 defer module.deinit();
234
235 var cg: CodeGen = .{
236 .pt = pt,
237 .air = undefined,
238 .liveness = undefined,
239 .owner_nav = nav_index,
240 .module = &module,
241 .control_flow = switch (structured_cfg) {
242 true => .{ .structured = .{} },
243 false => .{ .unstructured = .{} },
244 },
245 .base_line = zcu.navSrcLine(nav_index),
246 };
247 defer cg.deinit();
248
249 cg.genNav(false) catch |err| switch (err) {
250 error.AlreadyReported => return error.AlreadyReported,
251 error.OutOfMemory => return error.OutOfMemory,
252 };
253
254 return cg.serializeToMir(gpa);
255}
256
257fn serializeToMir(cg: *CodeGen, gpa: Allocator) codegen.Error!Mir {
258 const module = cg.module;
259
260 const owner_entry = module.nav_link.get(cg.owner_nav);
261 const owner_decl_index = owner_entry orelse return .{
262 .id_bound = module.next_result_id,
263 .owner_nav = cg.owner_nav,
264 .kind = .func,
265 .decl_result_id = .none,
266 .extended_instruction_set = &.{},
267 .globals = &.{},
268 .functions = &.{},
269 .annotations = &.{},
270 .debug_names = &.{},
271 .debug_strings = &.{},
272 .execution_modes = &.{},
273 .nav_refs = &.{},
274 .uav_refs = &.{},
275 .decl_deps = &.{},
276 .internal_globals = &.{},
277 .entry_points = &.{},
278 };
279
280 const owner_decl = module.declPtr(owner_decl_index);
281
282 var nav_refs: std.ArrayList(Mir.NavRef) = .empty;
283 defer nav_refs.deinit(gpa);
284 var nav_it = module.nav_link.iterator();
285 while (nav_it.next()) |entry| {
286 if (entry.key_ptr.* == cg.owner_nav) continue;
287 const decl = module.declPtr(entry.value_ptr.*);
288 try nav_refs.append(gpa, .{
289 .local_id = decl.result_id,
290 .nav = entry.key_ptr.*,
291 .kind = decl.kind,
292 });
293 }
294
295 var uav_refs: std.ArrayList(Mir.UavRef) = .empty;
296 defer uav_refs.deinit(gpa);
297 var uav_it = module.uav_link.iterator();
298 while (uav_it.next()) |entry| {
299 const decl = module.declPtr(entry.value_ptr.*);
300 try uav_refs.append(gpa, .{
301 .local_id = decl.result_id,
302 .val = entry.key_ptr.*[0],
303 .storage_class = entry.key_ptr.*[1],
304 .kind = decl.kind,
305 });
306 }
307
308 var decl_deps: std.ArrayList(Mir.DeclDep) = .empty;
309 defer decl_deps.deinit(gpa);
310 var internal_globals: std.ArrayList(Id) = .empty;
311 defer internal_globals.deinit(gpa);
312
313 const deps = module.decl_deps.items[owner_decl.begin_dep..owner_decl.end_dep];
314 for (deps) |dep_index| {
315 const dep_decl = module.declPtr(dep_index);
316 var found = false;
317 nav_it.index = 0;
318 while (nav_it.next()) |entry| {
319 if (entry.value_ptr.* == dep_index) {
320 try decl_deps.append(gpa, .{
321 .kind = dep_decl.kind,
322 .nav = entry.key_ptr.*,
323 });
324 found = true;
325 break;
326 }
327 }
328 if (!found and dep_decl.kind == .global) {
329 try internal_globals.append(gpa, dep_decl.result_id);
330 }
331 }
332
333 var ep_list: std.ArrayList(Mir.EntryPoint) = .empty;
334 defer ep_list.deinit(gpa);
335 var ep_it = module.entry_points.iterator();
336 while (ep_it.next()) |entry| {
337 const ep = entry.value_ptr;
338 const ep_decl = module.declPtr(ep.decl_index);
339 try ep_list.append(gpa, .{
340 .local_id = ep_decl.result_id,
341 .name = try gpa.dupe(u8, ep.name),
342 .cc = ep.cc,
343 });
344 }
345
346 return .{
347 .id_bound = module.next_result_id,
348 .owner_nav = cg.owner_nav,
349 .kind = owner_decl.kind,
350 .decl_result_id = owner_decl.result_id,
351 .extended_instruction_set = try module.sections.extended_instruction_set.instructions.toOwnedSlice(gpa),
352 .globals = try module.sections.globals.instructions.toOwnedSlice(gpa),
353 .functions = try module.sections.functions.instructions.toOwnedSlice(gpa),
354 .annotations = try module.sections.annotations.instructions.toOwnedSlice(gpa),
355 .debug_names = try module.sections.debug_names.instructions.toOwnedSlice(gpa),
356 .debug_strings = try module.sections.debug_strings.instructions.toOwnedSlice(gpa),
357 .execution_modes = try module.sections.execution_modes.instructions.toOwnedSlice(gpa),
358 .nav_refs = try nav_refs.toOwnedSlice(gpa),
359 .uav_refs = try uav_refs.toOwnedSlice(gpa),
360 .decl_deps = try decl_deps.toOwnedSlice(gpa),
361 .internal_globals = try internal_globals.toOwnedSlice(gpa),
362 .entry_points = try ep_list.toOwnedSlice(gpa),
363 };
364}
365
172366const Error = error{ AlreadyReported, OutOfMemory };
173367
174368pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {
src/codegen/spirv/Mir.zig created+71
......@@ -0,0 +1,71 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3
4const spec = @import("spec.zig");
5const Word = spec.Word;
6const Id = spec.Id;
7
8const InternPool = @import("../../InternPool.zig");
9const Module = @import("Module.zig");
10
11const Mir = @This();
12
13id_bound: Word,
14owner_nav: InternPool.Nav.Index,
15kind: Module.Decl.Kind,
16decl_result_id: Id,
17extended_instruction_set: []const Word,
18globals: []const Word,
19functions: []const Word,
20annotations: []const Word,
21debug_names: []const Word,
22debug_strings: []const Word,
23execution_modes: []const Word,
24nav_refs: []const NavRef,
25uav_refs: []const UavRef,
26decl_deps: []const DeclDep,
27internal_globals: []const Id,
28entry_points: []const EntryPoint,
29
30pub const NavRef = struct {
31 local_id: Id,
32 nav: InternPool.Nav.Index,
33 kind: Module.Decl.Kind,
34};
35
36pub const UavRef = struct {
37 local_id: Id,
38 val: InternPool.Index,
39 storage_class: spec.StorageClass,
40 kind: Module.Decl.Kind,
41};
42
43pub const DeclDep = struct {
44 kind: Module.Decl.Kind,
45 nav: InternPool.Nav.Index,
46};
47
48pub const EntryPoint = struct {
49 local_id: Id,
50 name: []const u8,
51 cc: std.builtin.CallingConvention,
52};
53
54pub fn deinit(mir: *Mir, gpa: Allocator) void {
55 gpa.free(mir.extended_instruction_set);
56 gpa.free(mir.globals);
57 gpa.free(mir.functions);
58 gpa.free(mir.annotations);
59 gpa.free(mir.debug_names);
60 gpa.free(mir.debug_strings);
61 gpa.free(mir.execution_modes);
62 gpa.free(mir.nav_refs);
63 gpa.free(mir.uav_refs);
64 gpa.free(mir.decl_deps);
65 gpa.free(mir.internal_globals);
66 for (mir.entry_points) |ep| {
67 gpa.free(ep.name);
68 }
69 gpa.free(mir.entry_points);
70 mir.* = undefined;
71}
src/link.zig-1
......@@ -841,7 +841,6 @@ pub const File = struct {
841841 assert(base.comp.zcu.?.llvm_object == null);
842842 switch (base.tag) {
843843 .lld => unreachable,
844 .spirv => unreachable, // see corresponding special case in `Zcu.PerThread.runCodegenInner`
845844 .plan9 => unreachable,
846845 inline else => |tag| {
847846 dev.check(tag.devFeature());
src/link/SpirV.zig+619-108
......@@ -10,21 +10,33 @@ const Compilation = @import("../Compilation.zig");
1010const link = @import("../link.zig");
1111const Air = @import("../Air.zig");
1212const Type = @import("../Type.zig");
13const codegen = @import("../codegen.zig");
1314const CodeGen = @import("../codegen/spirv/CodeGen.zig");
1415const Module = @import("../codegen/spirv/Module.zig");
1516const trace = @import("../tracy.zig").trace;
1617const BinaryModule = @import("SpirV/BinaryModule.zig");
1718const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
19const dedup_types = @import("SpirV/dedup_types.zig");
20const prune_unused = @import("SpirV/prune_unused.zig");
1821
1922const spec = @import("../codegen/spirv/spec.zig");
23const Section = @import("../codegen/spirv/Section.zig");
2024const Id = spec.Id;
2125const Word = spec.Word;
26const Mir = @import("../codegen/spirv/Mir.zig");
2227
2328const Linker = @This();
2429
2530base: link.File,
26module: Module,
27cg: CodeGen,
31fragments: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, Mir) = .empty,
32pending_navs: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty,
33entry_points: std.ArrayListUnmanaged(EntryPointDecl) = .empty,
34
35const EntryPointDecl = struct {
36 nav: InternPool.Nav.Index,
37 name: []const u8,
38 cc: std.builtin.CallingConvention,
39};
2840
2941pub fn createEmpty(
3042 arena: Allocator,
......@@ -32,7 +44,6 @@ pub fn createEmpty(
3244 emit: Path,
3345 options: link.File.OpenOptions,
3446) !*Linker {
35 const gpa = comp.gpa;
3647 const io = comp.io;
3748 const target = &comp.root_mod.resolved_target.result;
3849
......@@ -61,21 +72,6 @@ pub fn createEmpty(
6172 .file = null,
6273 .build_id = options.build_id,
6374 },
64 .module = .{
65 .gpa = gpa,
66 .arena = arena,
67 .zcu = comp.zcu.?,
68 },
69 .cg = .{
70 // These fields are populated in generate()
71 .pt = undefined,
72 .air = undefined,
73 .liveness = undefined,
74 .owner_nav = undefined,
75 .module = undefined,
76 .control_flow = .{ .structured = .{} },
77 .base_line = undefined,
78 },
7975 };
8076 errdefer linker.deinit();
8177
......@@ -97,70 +93,55 @@ pub fn open(
9793}
9894
9995pub fn deinit(linker: *Linker) void {
100 linker.cg.deinit();
101 linker.module.deinit();
102}
103
104fn generate(
105 linker: *Linker,
106 pt: Zcu.PerThread,
107 nav_index: InternPool.Nav.Index,
108 air: Air,
109 liveness: Air.Liveness,
110 do_codegen: bool,
111) !void {
112 const zcu = pt.zcu;
113 const gpa = zcu.gpa;
114 const structured_cfg = zcu.navFileScope(nav_index).mod.?.structured_cfg;
115
116 linker.cg.control_flow.deinit(gpa);
117 linker.cg.args.clearRetainingCapacity();
118 linker.cg.inst_results.clearRetainingCapacity();
119 linker.cg.id_scratch.clearRetainingCapacity();
120 linker.cg.prologue.reset();
121 linker.cg.body.reset();
122
123 linker.cg = .{
124 .pt = pt,
125 .air = air,
126 .liveness = liveness,
127 .owner_nav = nav_index,
128 .module = &linker.module,
129 .control_flow = switch (structured_cfg) {
130 true => .{ .structured = .{} },
131 false => .{ .unstructured = .{} },
132 },
133 .base_line = zcu.navSrcLine(nav_index),
134
135 .args = linker.cg.args,
136 .inst_results = linker.cg.inst_results,
137 .id_scratch = linker.cg.id_scratch,
138 .prologue = linker.cg.prologue,
139 .body = linker.cg.body,
140 };
141
142 linker.cg.genNav(do_codegen) catch |err| switch (err) {
143 error.AlreadyReported => return,
144 else => |e| return e,
145 };
96 const gpa = linker.base.comp.gpa;
97 for (linker.fragments.values()) |*mir| {
98 mir.deinit(gpa);
99 }
100 linker.fragments.deinit(gpa);
101 linker.pending_navs.deinit(gpa);
102 linker.entry_points.deinit(gpa);
146103}
147104
148105pub fn updateFunc(
149106 linker: *Linker,
150107 pt: Zcu.PerThread,
151108 func_index: InternPool.Index,
152 air: *const Air,
153 liveness: *const ?Air.Liveness,
109 mir: *codegen.AnyMir,
154110) !void {
111 const gpa = linker.base.comp.gpa;
155112 const nav = pt.zcu.funcInfo(func_index).owner_nav;
156 // TODO: Separate types for generating decls and functions?
157 try linker.generate(pt, nav, air.*, liveness.*.?, true);
113
114 if (linker.fragments.getPtr(nav)) |existing| {
115 existing.deinit(gpa);
116 }
117
118 try linker.fragments.put(gpa, nav, mir.spirv);
119 mir.spirv = .{
120 .extended_instruction_set = &.{},
121 .globals = &.{},
122 .functions = &.{},
123 .annotations = &.{},
124 .debug_names = &.{},
125 .debug_strings = &.{},
126 .execution_modes = &.{},
127 .id_bound = 0,
128 .owner_nav = mir.spirv.owner_nav,
129 .kind = mir.spirv.kind,
130 .decl_result_id = .none,
131 .nav_refs = &.{},
132 .uav_refs = &.{},
133 .decl_deps = &.{},
134 .internal_globals = &.{},
135 .entry_points = &.{},
136 };
158137}
159138
160139pub fn updateNav(linker: *Linker, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.Error!void {
161140 const ip = &pt.zcu.intern_pool;
162 log.debug("lowering nav {f}({d})", .{ ip.getNav(nav).fqn.fmt(ip), nav });
163 try linker.generate(pt, nav, undefined, undefined, false);
141 log.debug("deferring nav {f}({d}) to flush", .{ ip.getNav(nav).fqn.fmt(ip), nav });
142
143 const gpa = linker.base.comp.gpa;
144 linker.pending_navs.append(gpa, nav) catch return error.OutOfMemory;
164145}
165146
166147pub fn updateExports(
......@@ -171,6 +152,7 @@ pub fn updateExports(
171152) !void {
172153 const zcu = pt.zcu;
173154 const ip = &zcu.intern_pool;
155 const gpa = linker.base.comp.gpa;
174156 const nav_index = switch (exported) {
175157 .nav => |nav| nav,
176158 .uav => |uav| {
......@@ -180,21 +162,18 @@ pub fn updateExports(
180162 };
181163 const nav_ty = ip.getNav(nav_index).resolved.?.type;
182164 if (ip.isFunctionType(nav_ty)) {
183 const spv_decl_index = try linker.module.resolveNav(ip, nav_index);
184165 const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);
185166 if (cc == .spirv_device) return;
186167
187168 for (export_indices) |export_idx| {
188169 const exp = export_idx.ptr(zcu);
189 try linker.module.declareEntryPoint(
190 spv_decl_index,
191 exp.opts.name.toSlice(ip),
192 cc,
193 );
170 try linker.entry_points.append(gpa, .{
171 .nav = nav_index,
172 .name = exp.opts.name.toSlice(ip),
173 .cc = cc,
174 });
194175 }
195176 }
196
197 // TODO: Export regular functions, variables, etc using Linkage attributes.
198177}
199178
200179pub fn flush(
......@@ -203,11 +182,6 @@ pub fn flush(
203182 tid: Zcu.PerThread.Id,
204183 prog_node: std.Progress.Node,
205184) link.Error!void {
206 // The goal is to never use this because it's only needed if we need to
207 // write to InternPool, but flush is too late to be writing to the
208 // InternPool.
209 _ = tid;
210
211185 const tracy = trace(@src());
212186 defer tracy.end();
213187
......@@ -219,19 +193,334 @@ pub fn flush(
219193 const gpa = comp.gpa;
220194 const io = comp.io;
221195
222 // We need to export the list of error names somewhere so that we can pretty-print them in the
223 // executor. This is not really an important thing though, so we can just dump it in any old
224 // nonsemantic instruction. For now, just put it in OpSourceExtension with a special name.
225 var error_info: std.Io.Writer.Allocating = .init(linker.module.gpa);
226 defer error_info.deinit();
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 };
208
209 linker.fragments.put(gpa, nav, mir) catch return error.OutOfMemory;
210 }
211 linker.pending_navs.clearRetainingCapacity();
212
213 const merged = mergeFragments(linker, gpa, arena) catch |err| switch (err) {
214 error.OutOfMemory => return error.OutOfMemory,
215 };
216
217 var binary = linkModule(arena, merged.words, merged.id_bound, sub_prog_node) catch |err| switch (err) {
218 error.OutOfMemory => |e| return e,
219 else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}),
220 };
221 defer binary.deinit(arena);
222
223 const header = [_]Word{
224 spec.magic_number,
225 merged.version.toWord(),
226 merged.generator_id,
227 binary.id_bound,
228 0,
229 };
230
231 linker.base.file.?.writeStreamingAll(io, @ptrCast(&header)) catch |err|
232 return diags.fail("failed to write: {t}", .{err});
233 linker.base.file.?.writeStreamingAll(io, @ptrCast(binary.instructions)) catch |err|
234 return diags.fail("failed to write: {t}", .{err});
235}
236
237fn linkModule(arena: Allocator, words: []const Word, id_bound: u32, progress: std.Progress.Node) !BinaryModule {
238 var parser = try BinaryModule.Parser.init(arena);
239 defer parser.deinit();
240 var binary = try parser.initFromWords(words, id_bound);
241 try prune_unused.run(&parser, &binary);
242 try dedup_types.run(&parser, &binary);
243 try lower_invocation_globals.run(&parser, &binary, progress);
244 return binary;
245}
246
247fn mergeFragments(linker: *Linker, gpa: Allocator, arena: Allocator) error{OutOfMemory}!MergedModule {
248 const comp = linker.base.comp;
249 const zcu = comp.zcu.?;
250 const target = zcu.getTarget();
251
252 var next_id: Word = 1;
253
254 var nav_final_ids: std.AutoHashMapUnmanaged(InternPool.Nav.Index, Id) = .empty;
255 defer nav_final_ids.deinit(gpa);
256
257 var uav_final_ids: std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Id) = .empty;
258 defer uav_final_ids.deinit(gpa);
259
260 var frag_infos: std.ArrayList(FragmentInfo) = .empty;
261 defer frag_infos.deinit(gpa);
262 try frag_infos.ensureTotalCapacity(gpa, @intCast(linker.fragments.count()));
263
264 for (linker.fragments.keys(), linker.fragments.values()) |nav, *mir| {
265 const id_offset = next_id - 1;
266 frag_infos.appendAssumeCapacity(.{
267 .id_offset = id_offset,
268 });
269
270 if (mir.decl_result_id != .none) {
271 try nav_final_ids.put(gpa, nav, @enumFromInt(@intFromEnum(mir.decl_result_id) + id_offset));
272 }
273
274 next_id += mir.id_bound - 1;
275 }
276
277 for (linker.fragments.values(), frag_infos.items) |*mir, frag_info| {
278 for (mir.nav_refs) |ref| {
279 if (!nav_final_ids.contains(ref.nav)) {
280 try nav_final_ids.put(gpa, ref.nav, @enumFromInt(@intFromEnum(ref.local_id) + frag_info.id_offset));
281 }
282 }
283
284 for (mir.uav_refs) |ref| {
285 const key = .{ ref.val, ref.storage_class };
286 if (!uav_final_ids.contains(key)) {
287 try uav_final_ids.put(gpa, key, @enumFromInt(@intFromEnum(ref.local_id) + frag_info.id_offset));
288 }
289 }
290 }
291
292 var parser = BinaryModule.Parser.init(gpa) catch return error.OutOfMemory;
293 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);
308
309 for (linker.fragments.values(), frag_infos.items) |*mir, frag_info| {
310 var id_remap: std.AutoHashMapUnmanaged(Id, Id) = .empty;
311 defer id_remap.deinit(gpa);
312
313 for (mir.nav_refs) |ref| {
314 if (nav_final_ids.get(ref.nav)) |final_id| {
315 try id_remap.put(gpa, ref.local_id, final_id);
316 }
317 }
318
319 for (mir.uav_refs) |ref| {
320 const key = .{ ref.val, ref.storage_class };
321 if (uav_final_ids.get(key)) |final_id| {
322 try id_remap.put(gpa, ref.local_id, final_id);
323 }
324 }
325
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);
333
334 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 });
340 }
341 }
342
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);
351
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 });
360 }
361 }
362
363 switch (target.os.tag) {
364 .opengl => {
365 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .shader });
366 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .matrix });
367 },
368 .vulkan => {
369 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .shader });
370 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .matrix });
371 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 });
374 }
375 },
376 .opencl, .amdhsa => {
377 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .kernel });
378 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .addresses });
379 },
380 else => unreachable,
381 }
382 if (target.cpu.arch == .spirv64)
383 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .int64 });
384 if (target.cpu.has(.spirv, .int64))
385 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .int64 });
386 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 });
389 }
390 if (target.cpu.has(.spirv, .float64))
391 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .float64 });
392 if (target.cpu.has(.spirv, .generic_pointer))
393 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .generic_pointer });
394 if (target.cpu.has(.spirv, .vector16))
395 try capabilities_section.emit(gpa, .OpCapability, .{ .capability = .vector16 });
396 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 });
399 }
400 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 });
403 }
404 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 });
408 }
409
410 const addressing_model: spec.AddressingModel = switch (target.os.tag) {
411 .opengl => .logical,
412 .vulkan => if (target.cpu.arch == .spirv32) .logical else .physical_storage_buffer64,
413 .opencl => if (target.cpu.arch == .spirv32) .physical32 else .physical64,
414 .amdhsa => .physical64,
415 else => unreachable,
416 };
417 try memory_model_section.emit(gpa, .OpMemoryModel, .{
418 .addressing_model = addressing_model,
419 .memory_model = switch (target.os.tag) {
420 .opencl => .open_cl,
421 .vulkan, .opengl => .glsl450,
422 else => unreachable,
423 },
424 });
425
426 for (linker.entry_points.items) |ep| {
427 const final_id = nav_final_ids.get(ep.nav) orelse continue;
428
429 var interface: std.ArrayList(Id) = .empty;
430 defer interface.deinit(gpa);
431
432 var visited: std.AutoHashMapUnmanaged(InternPool.Nav.Index, void) = .empty;
433 defer visited.deinit(gpa);
227434
435 try collectEntryPointInterface(linker, ep.nav, &interface, &visited, &nav_final_ids, &uav_final_ids, &frag_infos, gpa);
436
437 const exec_model: spec.ExecutionModel = switch (target.os.tag) {
438 .vulkan, .opengl => switch (ep.cc) {
439 .spirv_vertex => .vertex,
440 .spirv_fragment => .fragment,
441 .spirv_kernel => .gl_compute,
442 .spirv_task => .task_ext,
443 .spirv_mesh => .mesh_ext,
444 .spirv_device => continue,
445 else => unreachable,
446 },
447 .opencl => switch (ep.cc) {
448 .spirv_kernel => .kernel,
449 .spirv_device => continue,
450 else => unreachable,
451 },
452 else => unreachable,
453 };
454
455 try entry_points_section.emit(gpa, .OpEntryPoint, .{
456 .execution_model = exec_model,
457 .entry_point = final_id,
458 .name = ep.name,
459 .interface = interface.items,
460 });
461
462 switch (ep.cc) {
463 .spirv_kernel, .spirv_task => |kernel| {
464 try execution_modes_section.emit(gpa, .OpExecutionMode, .{
465 .entry_point = final_id,
466 .mode = .{ .local_size = .{
467 .x_size = kernel.x,
468 .y_size = kernel.y,
469 .z_size = kernel.z,
470 } },
471 });
472 },
473 .spirv_fragment => |fragment| {
474 try execution_modes_section.emit(gpa, .OpExecutionMode, .{
475 .entry_point = final_id,
476 .mode = if (target.os.tag == .vulkan) .origin_upper_left else .origin_lower_left,
477 });
478 if (fragment.pixel_centered_integer) {
479 try execution_modes_section.emit(gpa, .OpExecutionMode, .{
480 .entry_point = final_id,
481 .mode = .pixel_center_integer,
482 });
483 }
484 const exec_mode: ?spec.ExecutionMode.Extended = switch (fragment.depth_assumption) {
485 .none => null,
486 .greater => .depth_greater,
487 .less => .depth_less,
488 .unchanged => .depth_unchanged,
489 };
490 if (exec_mode) |mode| {
491 try execution_modes_section.emit(gpa, .OpExecutionMode, .{
492 .entry_point = final_id,
493 .mode = mode,
494 });
495 }
496 },
497 .spirv_mesh => |mesh| {
498 try execution_modes_section.emit(gpa, .OpExecutionMode, .{
499 .entry_point = final_id,
500 .mode = .{ .output_vertices = .{ .vertex_count = mesh.max_vertices } },
501 });
502 try execution_modes_section.emit(gpa, .OpExecutionMode, .{
503 .entry_point = final_id,
504 .mode = .{ .output_primitives_ext = .{ .primitive_count = mesh.max_primitives } },
505 });
506 try execution_modes_section.emit(gpa, .OpExecutionMode, .{
507 .entry_point = final_id,
508 .mode = switch (mesh.stage_output) {
509 .output_points => .output_points,
510 .output_lines => .output_lines_ext,
511 .output_triangles => .output_triangles_ext,
512 },
513 });
514 },
515 else => {},
516 }
517 }
518
519 const ip = &zcu.intern_pool;
520 var error_info: std.Io.Writer.Allocating = .init(gpa);
521 defer error_info.deinit();
228522 error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory;
229 const ip = &linker.base.comp.zcu.?.intern_pool;
230523 for (ip.global_error_set.getNamesFromMainThread()) |name| {
231 // Errors can contain pretty much any character - to encode them in a string we must escape
232 // them somehow. Easiest here is to use some established scheme, one which also preseves the
233 // name if it contains no strange characters is nice for debugging. URI encoding fits the bill.
234 // We're using : as separator, which is a reserved character.
235524 error_info.writer.writeByte(':') catch return error.OutOfMemory;
236525 std.Uri.Component.percentEncode(
237526 &error_info.writer,
......@@ -246,27 +535,249 @@ pub fn flush(
246535 }.isValidChar,
247536 ) catch return error.OutOfMemory;
248537 }
249 try linker.module.sections.debug_strings.emit(gpa, .OpSourceExtension, .{
538 try debug_strings_section.emit(gpa, .OpSourceExtension, .{
250539 .extension = error_info.written(),
251540 });
252541
253 const module = try linker.module.finalize(arena);
254 errdefer arena.free(module);
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 });
255550
256 const linked_module = linkModule(arena, module, sub_prog_node) catch |err| switch (err) {
257 error.OutOfMemory => |e| return e,
258 else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}),
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 },
259562 };
260563
261 // TODO endianness bug. use file writer and call writeSliceEndian instead
262 linker.base.file.?.writeStreamingAll(io, @ptrCast(linked_module)) catch |err|
263 return diags.fail("failed to write: {t}", .{err});
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 };
264598}
265599
266fn linkModule(arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {
267 var parser = try BinaryModule.Parser.init(arena);
268 defer parser.deinit();
269 var binary = try parser.parse(module);
270 try lower_invocation_globals.run(&parser, &binary, progress);
271 return binary.finalize(arena);
600const MergedModule = struct {
601 words: []const Word,
602 id_bound: Word,
603 version: spec.Version,
604 generator_id: u32,
605};
606
607const FragmentInfo = struct {
608 id_offset: Word,
609};
610
611fn collectEntryPointInterface(
612 linker: *Linker,
613 nav: InternPool.Nav.Index,
614 interface: *std.ArrayList(Id),
615 visited: *std.AutoHashMapUnmanaged(InternPool.Nav.Index, void),
616 nav_final_ids: *const std.AutoHashMapUnmanaged(InternPool.Nav.Index, Id),
617 uav_final_ids: *const std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Id),
618 frag_infos: *const std.ArrayList(FragmentInfo),
619 gpa: Allocator,
620) error{OutOfMemory}!void {
621 const visited_gop = try visited.getOrPut(gpa, nav);
622 if (visited_gop.found_existing) return;
623
624 const frag_index = linker.fragments.getIndex(nav) orelse return;
625 const mir = &linker.fragments.values()[frag_index];
626 const id_offset = frag_infos.items[frag_index].id_offset;
627
628 if (mir.kind == .global) {
629 if (nav_final_ids.get(nav)) |final_id| {
630 try interface.append(gpa, final_id);
631 }
632 }
633
634 for (mir.uav_refs) |ref| {
635 if (ref.kind == .global) {
636 if (uav_final_ids.get(.{ ref.val, ref.storage_class })) |final_id| {
637 try interface.append(gpa, final_id);
638 }
639 }
640 }
641
642 for (mir.internal_globals) |local_id| {
643 const global_id: Id = @enumFromInt(@intFromEnum(local_id) + id_offset);
644 try interface.append(gpa, global_id);
645 }
646
647 for (mir.decl_deps) |dep| {
648 try collectEntryPointInterface(linker, dep.nav, interface, visited, nav_final_ids, uav_final_ids, frag_infos, gpa);
649 }
650
651 for (mir.nav_refs) |ref| {
652 try collectEntryPointInterface(linker, ref.nav, interface, visited, nav_final_ids, uav_final_ids, frag_infos, gpa);
653 }
654}
655
656fn remapAndAppend(
657 gpa: Allocator,
658 dest: *Section,
659 words: []const Word,
660 id_offset: Word,
661 id_remap: *const std.AutoHashMapUnmanaged(Id, Id),
662 parser: *BinaryModule.Parser,
663) error{OutOfMemory}!void {
664 if (words.len == 0) return;
665
666 try dest.instructions.ensureUnusedCapacity(gpa, words.len);
667
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;
682 if (cat == .id) {
683 remapSingleId(&inst_slice[1 + offset], id_offset, id_remap);
684 offset += 1;
685 } else if (cat == .literal) {
686 offset += operandLiteralWordCount(operand.kind, inst, offset);
687 } else if (cat == .composite) {
688 remapCompositeOperand(operand.kind, inst_slice, offset, id_offset, id_remap);
689 offset += 2;
690 } else {
691 offset += 1;
692 }
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 }
723 }
724 }
725}
726
727fn remapCompositeOperand(
728 kind: spec.OperandKind,
729 inst_slice: []Word,
730 offset: usize,
731 id_offset: Word,
732 id_remap: *const std.AutoHashMapUnmanaged(Id, Id),
733) void {
734 switch (kind) {
735 .pair_literal_integer_id_ref => {
736 remapSingleId(&inst_slice[1 + offset + 1], id_offset, id_remap);
737 },
738 .pair_id_ref_literal_integer => {
739 remapSingleId(&inst_slice[1 + offset], id_offset, id_remap);
740 },
741 .pair_id_ref_id_ref => {
742 remapSingleId(&inst_slice[1 + offset], id_offset, id_remap);
743 remapSingleId(&inst_slice[1 + offset + 1], id_offset, id_remap);
744 },
745 else => {},
746 }
747}
748
749fn operandLiteralWordCount(kind: spec.OperandKind, inst: BinaryModule.Instruction, offset: usize) usize {
750 return switch (kind) {
751 .literal_integer, .literal_float => 1,
752 .literal_string => blk: {
753 var count: usize = 0;
754 var off = offset;
755 while (off < inst.operands.len) {
756 const word = inst.operands[off];
757 count += 1;
758 off += 1;
759 if (word & 0xFF000000 == 0 or
760 word & 0x00FF0000 == 0 or
761 word & 0x0000FF00 == 0 or
762 word & 0x000000FF == 0)
763 {
764 break;
765 }
766 }
767 break :blk count;
768 },
769 .literal_context_dependent_number => inst.operands.len - offset,
770 .literal_ext_inst_integer => 1,
771 else => 1,
772 };
773}
774
775fn remapSingleId(word: *Word, id_offset: Word, id_remap: *const std.AutoHashMapUnmanaged(Id, Id)) void {
776 const id: Id = @enumFromInt(word.*);
777 if (id == .none) return;
778 if (id_remap.get(id)) |final_id| {
779 word.* = @intFromEnum(final_id);
780 } else {
781 word.* = @intFromEnum(id) + id_offset;
782 }
272783}
src/link/SpirV/BinaryModule.zig+82-199
......@@ -11,278 +11,161 @@ const ResultId = spec.Id;
1111
1212const BinaryModule = @This();
1313
14pub const header_words = 5;
15
16/// The module SPIR-V version.
17version: spec.Version,
18
19/// The generator magic number.
20generator_magic: u32,
21
2214/// The result-id bound of this SPIR-V module.
2315id_bound: u32,
2416
25/// The instructions of this module. This does not contain the header.
17/// The instructions of this module (no header).
2618instructions: []const Word,
2719
2820/// Maps OpExtInstImport result-ids to their InstructionSet.
2921ext_inst_map: std.AutoHashMapUnmanaged(ResultId, InstructionSet),
3022
31/// This map contains the width of arithmetic types (OpTypeInt and
32/// OpTypeFloat). We need this information to correctly parse the operands
33/// of Op(Spec)Constant and OpSwitch.
23/// Width of arithmetic types (OpTypeInt/OpTypeFloat). Needed to correctly
24/// parse operands of Op(Spec)Constant and OpSwitch.
3425arith_type_width: std.AutoHashMapUnmanaged(ResultId, u16),
3526
36/// The starting offsets of some sections
37sections: struct {
38 functions: usize,
39},
27functions_start: usize,
4028
41pub fn deinit(self: *BinaryModule, a: Allocator) void {
42 self.ext_inst_map.deinit(a);
43 self.arith_type_width.deinit(a);
44 self.* = undefined;
29pub fn deinit(bm: *BinaryModule, gpa: Allocator) void {
30 bm.ext_inst_map.deinit(gpa);
31 bm.arith_type_width.deinit(gpa);
32 bm.* = undefined;
4533}
4634
47pub fn iterateInstructions(self: BinaryModule) Instruction.Iterator {
48 return Instruction.Iterator.init(self.instructions, 0);
35pub fn iterateInstructions(bm: BinaryModule) Instruction.Iterator {
36 return Instruction.Iterator.init(bm.instructions, 0);
4937}
5038
51pub fn iterateInstructionsFrom(self: BinaryModule, offset: usize) Instruction.Iterator {
52 return Instruction.Iterator.init(self.instructions, offset);
39pub fn iterateInstructionsFrom(bm: BinaryModule, offset: usize) Instruction.Iterator {
40 return Instruction.Iterator.init(bm.instructions, offset);
5341}
5442
55pub fn instructionAt(self: BinaryModule, offset: usize) Instruction {
56 var it = self.iterateInstructionsFrom(offset);
57 return it.next().?;
58}
59
60pub fn finalize(self: BinaryModule, a: Allocator) ![]Word {
61 const result = try a.alloc(Word, 5 + self.instructions.len);
62 errdefer a.free(result);
63
64 result[0] = spec.magic_number;
65 result[1] = @bitCast(self.version);
66 result[2] = @bitCast(self.generator_magic);
67 result[3] = self.id_bound;
68 result[4] = 0; // Schema
69
70 @memcpy(result[5..], self.instructions);
71 return result;
72}
73
74/// Errors that can be raised when the module is not correct.
75/// Note that the parser doesn't validate SPIR-V modules by a
76/// long shot. It only yields errors that critically prevent
77/// further analysis of the module.
78pub const ParseError = error{
79 /// Raised when the module doesn't start with the SPIR-V magic.
80 /// This usually means that the module isn't actually SPIR-V.
81 InvalidMagic,
82 /// Raised when the module has an invalid "physical" format:
83 /// For example when the header is incomplete, or an instruction
84 /// has an illegal format.
85 InvalidPhysicalFormat,
86 /// OpExtInstImport was used with an unknown extension string.
87 InvalidExtInstImport,
88 /// The module had an instruction with an invalid (unknown) opcode.
89 InvalidOpcode,
90 /// An instruction's operands did not conform to the SPIR-V specification
91 /// for that instruction.
92 InvalidOperands,
93 /// A result-id was declared more than once.
94 DuplicateId,
95 /// Some ID did not resolve.
96 InvalidId,
97 /// This opcode or instruction is not supported yet.
98 UnsupportedOperation,
99 /// Parser ran out of memory.
100 OutOfMemory,
101};
102
10343pub const Instruction = struct {
10444 pub const Iterator = struct {
10545 words: []const Word,
106 index: usize = 0,
10746 offset: usize = 0,
10847
10948 pub fn init(words: []const Word, start_offset: usize) Iterator {
11049 return .{ .words = words, .offset = start_offset };
11150 }
11251
113 pub fn next(self: *Iterator) ?Instruction {
114 if (self.offset >= self.words.len) return null;
52 pub fn next(it: *Iterator) ?Instruction {
53 if (it.offset >= it.words.len) return null;
11554
116 const instruction_len = self.words[self.offset] >> 16;
117 defer self.offset += instruction_len;
118 defer self.index += 1;
55 const instruction_len = it.words[it.offset] >> 16;
56 defer it.offset += instruction_len;
11957 assert(instruction_len != 0);
120 assert(self.offset < self.words.len);
58 assert(it.offset < it.words.len);
12159
12260 return Instruction{
123 .opcode = @enumFromInt(self.words[self.offset] & 0xFFFF),
124 .index = self.index,
125 .offset = self.offset,
126 .operands = self.words[self.offset..][1..instruction_len],
61 .opcode = @enumFromInt(it.words[it.offset] & 0xFFFF),
62 .offset = it.offset,
63 .operands = it.words[it.offset..][1..instruction_len],
12764 };
12865 }
12966 };
13067
131 /// The opcode for this instruction.
13268 opcode: Opcode,
133 /// The instruction's index.
134 index: usize,
135 /// The instruction's word offset in the module.
13669 offset: usize,
137 /// The raw (unparsed) operands for this instruction.
13870 operands: []const Word,
13971};
14072
141/// This parser contains information (acceleration tables)
142/// that can be persisted across different modules. This is
143/// used to initialize the module, and is also used when
144/// further analyzing it.
14573pub const Parser = struct {
146 /// The allocator used to allocate this parser's structures,
147 /// and also the structures of any parsed module.
148 a: Allocator,
149
150 /// Maps (instruction set, opcode) => instruction index (for instruction set)
74 gpa: Allocator,
15175 opcode_table: std.AutoHashMapUnmanaged(u32, u16) = .empty,
15276
153 pub fn init(a: Allocator) !Parser {
154 var self = Parser{
155 .a = a,
156 };
157 errdefer self.deinit();
77 pub fn init(gpa: Allocator) !Parser {
78 var parser = Parser{ .gpa = gpa };
79 errdefer parser.deinit();
15880
15981 inline for (std.meta.tags(InstructionSet)) |set| {
16082 const instructions = set.instructions();
161 try self.opcode_table.ensureUnusedCapacity(a, @intCast(instructions.len));
83 try parser.opcode_table.ensureUnusedCapacity(gpa, @intCast(instructions.len));
16284 for (instructions, 0..) |inst, i| {
163 // Note: Some instructions may alias another. In this case we don't really care
164 // which one is first: they all (should) have the same operands anyway. Just pick
165 // the first, which is usually the core, KHR or EXT variant.
166 const entry = self.opcode_table.getOrPutAssumeCapacity(mapSetAndOpcode(set, @intCast(inst.opcode)));
85 const entry = parser.opcode_table.getOrPutAssumeCapacity(mapSetAndOpcode(set, @intCast(inst.opcode)));
16786 if (!entry.found_existing) {
16887 entry.value_ptr.* = @intCast(i);
16988 }
17089 }
17190 }
17291
173 return self;
92 return parser;
17493 }
17594
176 pub fn deinit(self: *Parser) void {
177 self.opcode_table.deinit(self.a);
95 pub fn deinit(parser: *Parser) void {
96 parser.opcode_table.deinit(parser.gpa);
17897 }
17998
18099 fn mapSetAndOpcode(set: InstructionSet, opcode: u16) u32 {
181100 return (@as(u32, @intFromEnum(set)) << 16) | opcode;
182101 }
183102
184 pub fn getInstSpec(self: Parser, opcode: Opcode) ?spec.Instruction {
185 const index = self.opcode_table.get(mapSetAndOpcode(.core, @intFromEnum(opcode))) orelse return null;
103 pub fn getInstSpec(parser: Parser, opcode: Opcode) ?spec.Instruction {
104 const index = parser.opcode_table.get(mapSetAndOpcode(.core, @intFromEnum(opcode))) orelse return null;
186105 return InstructionSet.core.instructions()[index];
187106 }
188107
189 pub fn parse(self: *Parser, module: []const u32) ParseError!BinaryModule {
190 if (module[0] != spec.magic_number) {
191 return error.InvalidMagic;
192 } else if (module.len < header_words) {
193 log.err("module only has {}/{} header words", .{ module.len, header_words });
194 return error.InvalidPhysicalFormat;
195 }
196
108 /// Build a BinaryModule from raw instruction words (no header).
109 /// Scans for ext_inst_map, arith_type_width, and the functions section offset.
110 pub fn initFromWords(parser: *Parser, words: []const Word, id_bound: u32) !BinaryModule {
197111 var binary = BinaryModule{
198 .version = @bitCast(module[1]),
199 .generator_magic = @bitCast(module[2]),
200 .id_bound = module[3],
201 .instructions = module[header_words..],
112 .id_bound = id_bound,
113 .instructions = words,
202114 .ext_inst_map = .{},
203115 .arith_type_width = .{},
204 .sections = undefined,
116 .functions_start = undefined,
205117 };
206118
207119 var maybe_function_section: ?usize = null;
120 var it = binary.iterateInstructions();
121 while (it.next()) |inst| {
122 const inst_spec = parser.getInstSpec(inst.opcode) orelse continue;
123 const operands = inst.operands;
208124
209 // First pass through the module to verify basic structure and
210 // to gather some initial stuff for more detailed analysis.
211 // We want to check some stuff that Instruction.Iterator is no good for,
212 // so just iterate manually.
213 var offset: usize = 0;
214 while (offset < binary.instructions.len) {
215 const len = binary.instructions[offset] >> 16;
216 if (len == 0 or len + offset > binary.instructions.len) {
217 log.err("invalid instruction format: len={}, end={}, module len={}", .{ len, len + offset, binary.instructions.len });
218 return error.InvalidPhysicalFormat;
219 }
220 defer offset += len;
221
222 // We can't really efficiently use non-exhaustive enums here, because we would
223 // need to manually write out all valid cases. Since we have this map anyway, just
224 // use that.
225 const opcode: Opcode = @enumFromInt(@as(u16, @truncate(binary.instructions[offset])));
226 const inst_spec = self.getInstSpec(opcode) orelse {
227 log.err("invalid opcode for core set: {}", .{@intFromEnum(opcode)});
228 return error.InvalidOpcode;
229 };
230
231 const operands = binary.instructions[offset..][1..len];
232 switch (opcode) {
125 switch (inst.opcode) {
233126 .OpExtInstImport => {
234127 const set_name = std.mem.sliceTo(std.mem.sliceAsBytes(operands[1..]), 0);
235 const set = std.meta.stringToEnum(InstructionSet, set_name) orelse {
236 log.err("invalid instruction set '{s}'", .{set_name});
237 return error.InvalidExtInstImport;
238 };
239 if (set == .core) return error.InvalidExtInstImport;
240 try binary.ext_inst_map.put(self.a, @enumFromInt(operands[0]), set);
128 const set = std.meta.stringToEnum(InstructionSet, set_name) orelse continue;
129 if (set == .core) continue;
130 try binary.ext_inst_map.put(parser.gpa, @enumFromInt(operands[0]), set);
241131 },
242132 .OpTypeInt, .OpTypeFloat => {
243 const entry = try binary.arith_type_width.getOrPut(self.a, @enumFromInt(operands[0]));
244 if (entry.found_existing) return error.DuplicateId;
245 entry.value_ptr.* = std.math.cast(u16, operands[1]) orelse return error.InvalidOperands;
133 try binary.arith_type_width.put(parser.gpa, @enumFromInt(operands[0]), @intCast(operands[1]));
246134 },
247135 .OpFunction => if (maybe_function_section == null) {
248 maybe_function_section = offset;
136 maybe_function_section = inst.offset;
249137 },
250138 else => {},
251139 }
252140
253 // OpSwitch takes a value as argument, not an OpType... hence we need to populate arith_type_width
254 // with ALL operations that return an int or float.
141 // propagate arith type widths through instructions that return int/float
255142 const spec_operands = inst_spec.operands;
256143 if (spec_operands.len >= 2 and
257144 spec_operands[0].kind == .id_result_type and
258145 spec_operands[1].kind == .id_result)
259146 {
260 if (operands.len < 2) return error.InvalidOperands;
261 if (binary.arith_type_width.get(@enumFromInt(operands[0]))) |width| {
262 const entry = try binary.arith_type_width.getOrPut(self.a, @enumFromInt(operands[1]));
263 if (entry.found_existing) return error.DuplicateId;
264 entry.value_ptr.* = width;
147 if (operands.len >= 2) {
148 if (binary.arith_type_width.get(@enumFromInt(operands[0]))) |width| {
149 try binary.arith_type_width.put(parser.gpa, @enumFromInt(operands[1]), width);
150 }
265151 }
266152 }
267153 }
268154
269 binary.sections = .{
270 .functions = maybe_function_section orelse binary.instructions.len,
271 };
155 binary.functions_start = maybe_function_section orelse binary.instructions.len;
272156
273157 return binary;
274158 }
275159
276160 /// Parse offsets in the instruction that contain result-ids.
277161 /// Returned offsets are relative to inst.operands.
278 /// Returns in an arraylist to armortize allocations.
279162 pub fn parseInstructionResultIds(
280 self: *Parser,
163 parser: *Parser,
281164 binary: BinaryModule,
282165 inst: Instruction,
283 offsets: *std.array_list.Managed(u16),
166 offsets: *std.ArrayList(u16),
284167 ) !void {
285 const index = self.opcode_table.get(mapSetAndOpcode(.core, @intFromEnum(inst.opcode))).?;
168 const index = parser.opcode_table.get(mapSetAndOpcode(.core, @intFromEnum(inst.opcode))).?;
286169 const operands = InstructionSet.core.instructions()[index].operands;
287170
288171 var offset: usize = 0;
......@@ -290,37 +173,37 @@ pub const Parser = struct {
290173 .OpSpecConstantOp => {
291174 assert(operands[0].kind == .id_result_type);
292175 assert(operands[1].kind == .id_result);
293 offset = try self.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets);
176 offset = try parser.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets);
294177
295178 if (offset >= inst.operands.len) return error.InvalidPhysicalFormat;
296179 const spec_opcode = std.math.cast(u16, inst.operands[offset]) orelse return error.InvalidPhysicalFormat;
297 const spec_index = self.opcode_table.get(mapSetAndOpcode(.core, spec_opcode)) orelse
180 const spec_index = parser.opcode_table.get(mapSetAndOpcode(.core, spec_opcode)) orelse
298181 return error.InvalidPhysicalFormat;
299182 const spec_operands = InstructionSet.core.instructions()[spec_index].operands;
300183 assert(spec_operands[0].kind == .id_result_type);
301184 assert(spec_operands[1].kind == .id_result);
302 offset = try self.parseOperandsResultIds(binary, inst, spec_operands[2..], offset + 1, offsets);
185 offset = try parser.parseOperandsResultIds(binary, inst, spec_operands[2..], offset + 1, offsets);
303186 },
304187 .OpExtInst => {
305188 assert(operands[0].kind == .id_result_type);
306189 assert(operands[1].kind == .id_result);
307 offset = try self.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets);
190 offset = try parser.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets);
308191
309192 if (offset + 1 >= inst.operands.len) return error.InvalidPhysicalFormat;
310193 const set_id: ResultId = @enumFromInt(inst.operands[offset]);
311 try offsets.append(@intCast(offset));
194 try offsets.append(parser.gpa, @intCast(offset));
312195 const set = binary.ext_inst_map.get(set_id) orelse {
313196 log.err("invalid instruction set {}", .{@intFromEnum(set_id)});
314197 return error.InvalidId;
315198 };
316199 const ext_opcode = std.math.cast(u16, inst.operands[offset + 1]) orelse return error.InvalidPhysicalFormat;
317 const ext_index = self.opcode_table.get(mapSetAndOpcode(set, ext_opcode)) orelse
200 const ext_index = parser.opcode_table.get(mapSetAndOpcode(set, ext_opcode)) orelse
318201 return error.InvalidPhysicalFormat;
319202 const ext_operands = set.instructions()[ext_index].operands;
320 offset = try self.parseOperandsResultIds(binary, inst, ext_operands, offset + 2, offsets);
203 offset = try parser.parseOperandsResultIds(binary, inst, ext_operands, offset + 2, offsets);
321204 },
322205 else => {
323 offset = try self.parseOperandsResultIds(binary, inst, operands, offset, offsets);
206 offset = try parser.parseOperandsResultIds(binary, inst, operands, offset, offsets);
324207 },
325208 }
326209
......@@ -328,50 +211,50 @@ pub const Parser = struct {
328211 }
329212
330213 fn parseOperandsResultIds(
331 self: *Parser,
214 parser: *Parser,
332215 binary: BinaryModule,
333216 inst: Instruction,
334217 operands: []const spec.Operand,
335218 start_offset: usize,
336 offsets: *std.array_list.Managed(u16),
219 offsets: *std.ArrayList(u16),
337220 ) !usize {
338221 var offset = start_offset;
339222 for (operands) |operand| {
340 offset = try self.parseOperandResultIds(binary, inst, operand, offset, offsets);
223 offset = try parser.parseOperandResultIds(binary, inst, operand, offset, offsets);
341224 }
342225 return offset;
343226 }
344227
345228 fn parseOperandResultIds(
346 self: *Parser,
229 parser: *Parser,
347230 binary: BinaryModule,
348231 inst: Instruction,
349232 operand: spec.Operand,
350233 start_offset: usize,
351 offsets: *std.array_list.Managed(u16),
234 offsets: *std.ArrayList(u16),
352235 ) !usize {
353236 var offset = start_offset;
354237 switch (operand.quantifier) {
355238 .variadic => while (offset < inst.operands.len) {
356 offset = try self.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets);
239 offset = try parser.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets);
357240 },
358241 .optional => if (offset < inst.operands.len) {
359 offset = try self.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets);
242 offset = try parser.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets);
360243 },
361244 .required => {
362 offset = try self.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets);
245 offset = try parser.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets);
363246 },
364247 }
365248 return offset;
366249 }
367250
368251 fn parseOperandKindResultIds(
369 self: *Parser,
252 parser: *Parser,
370253 binary: BinaryModule,
371254 inst: Instruction,
372255 kind: spec.OperandKind,
373256 start_offset: usize,
374 offsets: *std.array_list.Managed(u16),
257 offsets: *std.ArrayList(u16),
375258 ) !usize {
376259 var offset = start_offset;
377260 if (offset >= inst.operands.len) return error.InvalidPhysicalFormat;
......@@ -383,7 +266,7 @@ pub const Parser = struct {
383266 for (kind.enumerants()) |enumerant| {
384267 if ((mask & enumerant.value) != 0) {
385268 for (enumerant.parameters) |param_kind| {
386 offset = try self.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets);
269 offset = try parser.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets);
387270 }
388271 }
389272 }
......@@ -394,14 +277,14 @@ pub const Parser = struct {
394277 for (kind.enumerants()) |enumerant| {
395278 if (value == enumerant.value) {
396279 for (enumerant.parameters) |param_kind| {
397 offset = try self.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets);
280 offset = try parser.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets);
398281 }
399282 break;
400283 }
401284 }
402285 },
403286 .id => {
404 try offsets.append(@intCast(offset));
287 try offsets.append(parser.gpa, @intCast(offset));
405288 offset += 1;
406289 },
407290 else => switch (kind) {
......@@ -433,7 +316,7 @@ pub const Parser = struct {
433316 },
434317 .literal_ext_inst_integer => unreachable,
435318 .literal_spec_constant_op_integer => unreachable,
436 .pair_literal_integer_id_ref => { // Switch case
319 .pair_literal_integer_id_ref => {
437320 assert(inst.opcode == .OpSwitch);
438321 const bit_width = binary.arith_type_width.get(@enumFromInt(inst.operands[0])) orelse {
439322 log.err("invalid OpSwitch type {}", .{inst.operands[0]});
......@@ -444,16 +327,16 @@ pub const Parser = struct {
444327 33...64 => 2,
445328 else => unreachable,
446329 };
447 try offsets.append(@intCast(offset));
330 try offsets.append(parser.gpa, @intCast(offset));
448331 offset += 1;
449332 },
450333 .pair_id_ref_literal_integer => {
451 try offsets.append(@intCast(offset));
334 try offsets.append(parser.gpa, @intCast(offset));
452335 offset += 2;
453336 },
454337 .pair_id_ref_id_ref => {
455 try offsets.append(@intCast(offset));
456 try offsets.append(@intCast(offset + 1));
338 try offsets.append(parser.gpa, @intCast(offset));
339 try offsets.append(parser.gpa, @intCast(offset + 1));
457340 offset += 2;
458341 },
459342 else => unreachable,
src/link/SpirV/dedup_types.zig created+255
......@@ -0,0 +1,255 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const BinaryModule = @import("BinaryModule.zig");
4
5const spec = @import("../../codegen/spirv/spec.zig");
6const Word = spec.Word;
7const Id = spec.Id;
8const Opcode = spec.Opcode;
9const Instruction = BinaryModule.Instruction;
10
11/// Deduplicate types and constants in a SPIR-V binary module.
12///
13/// The SPIR-V spec requires that non-aggregate types be unique.
14/// When merging fragments from parallel codegen, duplicate type definitions
15/// may exist. This pass identifies structurally identical types/constants,
16/// keeps one canonical instance, and remaps all references to duplicates.
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 {
22 const gpa = parser.gpa;
23
24 const Decoration = struct { offset: usize, len: usize };
25 var decorations_by_id: std.array_hash_map.Auto(Id, std.ArrayList(Decoration)) = .empty;
26 defer {
27 for (decorations_by_id.values()) |*list| list.deinit(gpa);
28 decorations_by_id.deinit(gpa);
29 }
30
31 var it = binary.iterateInstructions();
32 while (it.next()) |inst| {
33 if (inst.offset >= binary.functions_start) break;
34 switch (inst.opcode) {
35 .OpName, .OpMemberName => {},
36 else => switch (inst.opcode.class()) {
37 .annotation => {},
38 else => continue,
39 },
40 }
41 if (inst.operands.len == 0) continue;
42 const target_id: Id = @enumFromInt(inst.operands[0]);
43
44 const gop = try decorations_by_id.getOrPut(gpa, target_id);
45 if (!gop.found_existing) gop.value_ptr.* = .empty;
46 try gop.value_ptr.append(gpa, .{
47 .offset = inst.offset,
48 .len = 1 + inst.operands.len,
49 });
50 }
51
52 var canonical_map: std.array_hash_map.Custom(TypeKey, Id, TypeKey.HashContext, true) = .empty;
53 defer {
54 for (canonical_map.keys()) |key| gpa.free(key.words);
55 canonical_map.deinit(gpa);
56 }
57
58 var id_remap: std.AutoHashMapUnmanaged(Id, Id) = .empty;
59 defer id_remap.deinit(gpa);
60
61 var id_offsets: std.ArrayList(u16) = .empty;
62 defer id_offsets.deinit(gpa);
63
64 var key_words: std.ArrayList(Word) = .empty;
65 defer key_words.deinit(gpa);
66
67 var dec_hashes: std.ArrayList(u64) = .empty;
68 defer dec_hashes.deinit(gpa);
69
70 // first pass: build canonical map, identify duplicates
71 it = binary.iterateInstructions();
72 while (it.next()) |inst| {
73 if (inst.offset >= binary.functions_start) break;
74 if (!canDeduplicate(inst.opcode)) continue;
75
76 const result_id_index: usize = switch (inst.opcode.class()) {
77 .type_declaration, .extension => 0,
78 .constant_creation => 1,
79 else => continue,
80 };
81 if (result_id_index >= inst.operands.len) continue;
82 const result_id: Id = @enumFromInt(inst.operands[result_id_index]);
83
84 key_words.items.len = 0;
85 try key_words.append(gpa, @intFromEnum(inst.opcode));
86
87 id_offsets.items.len = 0;
88 parser.parseInstructionResultIds(binary.*, inst, &id_offsets) catch continue;
89
90 for (inst.operands, 0..) |word, i| {
91 if (i == result_id_index) continue;
92 if (std.mem.indexOfScalar(u16, id_offsets.items, @intCast(i)) != null) {
93 const canonical = id_remap.get(@enumFromInt(word)) orelse @as(Id, @enumFromInt(word));
94 try key_words.append(gpa, @intFromEnum(canonical));
95 } else {
96 try key_words.append(gpa, word);
97 }
98 }
99
100 if (decorations_by_id.getPtr(result_id)) |dec_list| {
101 dec_hashes.items.len = 0;
102 for (dec_list.items) |dec| {
103 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);
106 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 }
116 }
117 try dec_hashes.append(gpa, hasher.final());
118 }
119 std.mem.sort(u64, dec_hashes.items, {}, std.sort.asc(u64));
120 var prev: u64 = 0;
121 for (dec_hashes.items) |h| {
122 if (h == prev) continue;
123 prev = h;
124 try key_words.append(gpa, @truncate(h));
125 try key_words.append(gpa, @truncate(h >> 32));
126 }
127 }
128
129 const key = TypeKey{ .words = try gpa.dupe(Word, key_words.items) };
130 const gop = try canonical_map.getOrPut(gpa, key);
131 if (gop.found_existing) {
132 try id_remap.put(gpa, result_id, gop.value_ptr.*);
133 gpa.free(key.words);
134 } else {
135 gop.value_ptr.* = result_id;
136 }
137 }
138
139 if (id_remap.count() == 0) return;
140
141 // second pass: rewrite id references, remove duplicates and redundant annotations
142 var new_words: std.ArrayList(Word) = .empty;
143 defer new_words.deinit(gpa);
144 try new_words.ensureTotalCapacity(gpa, binary.instructions.len);
145
146 var emitted_annotations: std.AutoHashMapUnmanaged(u64, void) = .empty;
147 defer emitted_annotations.deinit(gpa);
148
149 var new_functions_offset: ?usize = null;
150 var max_id: Word = 0;
151
152 it = binary.iterateInstructions();
153 while (it.next()) |inst| {
154 if (new_functions_offset == null and inst.offset >= binary.functions_start) {
155 new_functions_offset = new_words.items.len;
156 }
157
158 if (canDeduplicate(inst.opcode)) {
159 const result_id_index: usize = switch (inst.opcode.class()) {
160 .type_declaration, .extension => 0,
161 .constant_creation => 1,
162 else => unreachable,
163 };
164 if (result_id_index < inst.operands.len) {
165 const result_id: Id = @enumFromInt(inst.operands[result_id_index]);
166 if (id_remap.contains(result_id)) continue;
167 }
168 }
169
170 switch (inst.opcode.class()) {
171 .annotation, .debug => {
172 if (inst.operands.len > 0) {
173 const target: Id = @enumFromInt(inst.operands[0]);
174 if (id_remap.contains(target)) continue;
175 }
176 },
177 else => {},
178 }
179
180 const inst_start = new_words.items.len;
181 new_words.appendAssumeCapacity(binary.instructions[inst.offset]);
182 new_words.appendSliceAssumeCapacity(inst.operands);
183 const inst_slice = new_words.items[inst_start + 1 ..];
184
185 id_offsets.items.len = 0;
186 parser.parseInstructionResultIds(binary.*, inst, &id_offsets) catch continue;
187
188 const inst_spec = parser.getInstSpec(inst.opcode);
189 const maybe_result_id_index: ?usize = if (inst_spec) |ispec| blk: {
190 break :blk for (0..@min(2, ispec.operands.len)) |i| {
191 if (ispec.operands[i].kind == .id_result) break @intCast(i);
192 } else null;
193 } else null;
194
195 for (inst_slice, 0..) |*word, i| {
196 if (std.mem.indexOfScalar(u16, id_offsets.items, @intCast(i)) == null) continue;
197 max_id = @max(max_id, word.*);
198 if (maybe_result_id_index != null and i == maybe_result_id_index.?) continue;
199
200 if (id_remap.get(@enumFromInt(word.*))) |canonical| {
201 word.* = @intFromEnum(canonical);
202 max_id = @max(max_id, word.*);
203 }
204 }
205
206 switch (inst.opcode.class()) {
207 .annotation, .debug => {
208 const ann_words = new_words.items[inst_start..];
209 const ann_hash = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(ann_words));
210 const gop = try emitted_annotations.getOrPut(gpa, ann_hash);
211 if (gop.found_existing) {
212 new_words.items.len = inst_start;
213 continue;
214 }
215 },
216 else => {},
217 }
218 }
219
220 var remap_it = id_remap.iterator();
221 while (remap_it.next()) |entry| {
222 _ = binary.ext_inst_map.remove(entry.key_ptr.*);
223 _ = binary.arith_type_width.remove(entry.key_ptr.*);
224 }
225
226 binary.instructions = try gpa.dupe(Word, new_words.items);
227 binary.functions_start = new_functions_offset orelse new_words.items.len;
228 binary.id_bound = max_id + 1;
229}
230
231fn canDeduplicate(opcode: Opcode) bool {
232 return switch (opcode) {
233 .OpTypeForwardPointer => false,
234 .OpGroupDecorate, .OpGroupMemberDecorate => false,
235 else => switch (opcode.class()) {
236 .type_declaration, .constant_creation => true,
237 .extension => opcode == .OpExtInstImport,
238 else => false,
239 },
240 };
241}
242
243const TypeKey = struct {
244 words: []const Word,
245
246 const HashContext = struct {
247 pub fn hash(_: @This(), key: TypeKey) u32 {
248 return @truncate(std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(key.words)));
249 }
250
251 pub fn eql(_: @This(), a: TypeKey, b: TypeKey, _: usize) bool {
252 return std.mem.eql(Word, a.words, b.words);
253 }
254 };
255};
src/link/SpirV/lower_invocation_globals.zig+18-18
......@@ -71,7 +71,7 @@ const ModuleInfo = struct {
7171 arena: Allocator,
7272 parser: *BinaryModule.Parser,
7373 binary: BinaryModule,
74 ) BinaryModule.ParseError!ModuleInfo {
74 ) !ModuleInfo {
7575 var entry_points: std.array_hash_map.Auto(ResultId, void) = .empty;
7676 var functions: std.array_hash_map.Auto(ResultId, Fn) = .empty;
7777 var fn_types = std.AutoHashMap(ResultId, struct {
......@@ -79,9 +79,9 @@ const ModuleInfo = struct {
7979 param_types: []const ResultId,
8080 }).init(arena);
8181 var calls: std.array_hash_map.Auto(ResultId, void) = .empty;
82 var callee_store = std.array_list.Managed(ResultId).init(arena);
82 var callee_store: std.ArrayList(ResultId) = .empty;
8383 var function_invocation_globals: std.array_hash_map.Auto(ResultId, void) = .empty;
84 var result_id_offsets = std.array_list.Managed(u16).init(arena);
84 var result_id_offsets: std.ArrayList(u16) = .empty;
8585 var invocation_globals: std.array_hash_map.Auto(ResultId, InvocationGlobal) = .empty;
8686
8787 var maybe_current_function: ?ResultId = null;
......@@ -164,7 +164,7 @@ const ModuleInfo = struct {
164164 }
165165
166166 const first_callee = callee_store.items.len;
167 try callee_store.appendSlice(calls.keys());
167 try callee_store.appendSlice(arena, calls.keys());
168168
169169 const fn_type = fn_types.get(fn_ty_id) orelse {
170170 log.err("Function {f} has invalid OpFunction type", .{current_function});
......@@ -395,12 +395,12 @@ const ModuleBuilder = struct {
395395 return @enumFromInt(self.id_bound);
396396 }
397397
398 fn finalize(self: *ModuleBuilder, a: Allocator, binary: *BinaryModule) !void {
398 fn finalize(self: *ModuleBuilder, arena: Allocator, binary: *BinaryModule) !void {
399399 binary.id_bound = self.id_bound;
400 binary.instructions = try a.dupe(Word, self.section.instructions.items);
400 binary.instructions = try arena.dupe(Word, self.section.instructions.items);
401401 // Nothing is removed in this pass so we don't need to change any of the maps,
402402 // just make sure the section is updated.
403 binary.sections.functions = self.new_functions_section orelse binary.instructions.len;
403 binary.functions_start = self.new_functions_section orelse binary.instructions.len;
404404 }
405405
406406 /// Process everything from `binary` up to the first function and emit it into the builder.
......@@ -525,12 +525,12 @@ const ModuleBuilder = struct {
525525 binary: BinaryModule,
526526 info: ModuleInfo,
527527 ) !void {
528 var result_id_offsets = std.array_list.Managed(u16).init(self.arena);
529 var operands = std.array_list.Managed(u32).init(self.arena);
528 var result_id_offsets: std.ArrayList(u16) = .empty;
529 var operands: std.ArrayList(u32) = .empty;
530530
531531 var maybe_current_function: ?ResultId = null;
532532 var skip_until_end: bool = false;
533 var it = binary.iterateInstructionsFrom(binary.sections.functions);
533 var it = binary.iterateInstructionsFrom(binary.functions_start);
534534 self.new_functions_section = self.section.instructions.items.len;
535535 while (it.next()) |inst| {
536536 if (skip_until_end) {
......@@ -541,7 +541,7 @@ const ModuleBuilder = struct {
541541 try parser.parseInstructionResultIds(binary, inst, &result_id_offsets);
542542
543543 operands.items.len = 0;
544 try operands.appendSlice(inst.operands);
544 try operands.appendSlice(self.arena, inst.operands);
545545
546546 // Replace the result-ids with the global's new result-id if required.
547547 for (result_id_offsets.items) |off| {
......@@ -741,14 +741,14 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Pr
741741 const sub_node = progress.start("Lower invocation globals", 6);
742742 defer sub_node.end();
743743
744 var arena = std.heap.ArenaAllocator.init(parser.a);
745 defer arena.deinit();
746 const a = arena.allocator();
744 var arena_state = std.heap.ArenaAllocator.init(parser.gpa);
745 defer arena_state.deinit();
746 const arena = arena_state.allocator();
747747
748 var info = try ModuleInfo.parse(a, parser, binary.*);
749 try info.resolve(a);
748 var info = try ModuleInfo.parse(arena, parser, binary.*);
749 try info.resolve(arena);
750750
751 var builder = try ModuleBuilder.init(a, binary.*, info);
751 var builder = try ModuleBuilder.init(arena, binary.*, info);
752752 sub_node.completeOne();
753753 try builder.deriveNewFnInfo(info);
754754 sub_node.completeOne();
......@@ -760,5 +760,5 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Pr
760760 sub_node.completeOne();
761761 try builder.emitNewEntryPoints(info);
762762 sub_node.completeOne();
763 try builder.finalize(parser.a, binary);
763 try builder.finalize(parser.gpa, binary);
764764}
src/link/SpirV/prune_unused.zig created+234
......@@ -0,0 +1,234 @@
1const std = @import("std");
2const BinaryModule = @import("BinaryModule.zig");
3const spec = @import("../../codegen/spirv/spec.zig");
4const Opcode = spec.Opcode;
5const ResultId = spec.Id;
6const Word = spec.Word;
7
8pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
9 const gpa = parser.gpa;
10
11 // map result-id → index in id_offsets for preamble instructions and function headers
12 var id_to_index: std.AutoHashMapUnmanaged(ResultId, u32) = .empty;
13 defer id_to_index.deinit(gpa);
14
15 // for each indexed instruction, its offset in the binary
16 var code_offsets: std.ArrayList(usize) = .empty;
17 defer code_offsets.deinit(gpa);
18
19 var it = binary.iterateInstructions();
20 while (it.next()) |inst| {
21 const inst_spec = parser.getInstSpec(inst.opcode) orelse continue;
22 const result_id = getResultId(inst, inst_spec) orelse continue;
23
24 // only index preamble instructions and function headers
25 if (inst.offset < binary.functions_start or inst.opcode == .OpFunction) {
26 const index: u32 = @intCast(code_offsets.items.len);
27 try id_to_index.put(gpa, result_id, index);
28 try code_offsets.append(gpa, inst.offset);
29 }
30 }
31
32 var alive: std.bit_set.Dynamic = try .initEmpty(gpa, code_offsets.items.len);
33 defer alive.deinit(gpa);
34
35 var id_offset_buf: std.ArrayList(u16) = .empty;
36 defer id_offset_buf.deinit(gpa);
37
38 // mark non-prunable preamble instructions alive
39 it = binary.iterateInstructions();
40 while (it.next()) |inst| {
41 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 {};
44 }
45 }
46
47 // mark alive functions' contents alive
48 it = binary.iterateInstructionsFrom(binary.functions_start);
49 while (it.next()) |inst| {
50 if (inst.opcode == .OpFunction) {
51 const inst_spec = parser.getInstSpec(inst.opcode) orelse continue;
52 const result_id = getResultId(inst, inst_spec) orelse continue;
53 const index = id_to_index.get(result_id) orelse continue;
54 if (!alive.isSet(index)) {
55 // skip dead function
56 while (it.next()) |inner| {
57 if (inner.opcode == .OpFunctionEnd) break;
58 }
59 continue;
60 }
61 }
62
63 // mark operands of alive function contents
64 if (!canPrune(inst.opcode)) {
65 markAlive(parser, binary.*, inst, &alive, &id_to_index, &code_offsets, &id_offset_buf) catch {};
66 }
67 }
68
69 // rewrite
70 var new_words: std.ArrayList(Word) = .empty;
71 defer new_words.deinit(gpa);
72 try new_words.ensureTotalCapacity(gpa, binary.instructions.len);
73
74 var new_functions_start: ?usize = null;
75
76 it = binary.iterateInstructions();
77 while (it.next()) |inst| {
78 if (inst.offset >= binary.functions_start and inst.opcode == .OpFunction) {
79 const inst_spec = parser.getInstSpec(inst.opcode) orelse continue;
80 const result_id = getResultId(inst, inst_spec) orelse continue;
81 const index = id_to_index.get(result_id) orelse continue;
82 if (!alive.isSet(index)) {
83 while (it.next()) |inner| {
84 if (inner.opcode == .OpFunctionEnd) break;
85 }
86 continue;
87 }
88 }
89
90 if (canPrune(inst.opcode)) {
91 const inst_spec = parser.getInstSpec(inst.opcode) orelse {
92 appendInst(&new_words, binary, inst, &new_functions_start);
93 continue;
94 };
95
96 if (getResultId(inst, inst_spec)) |result_id| {
97 const index = id_to_index.get(result_id) orelse {
98 appendInst(&new_words, binary, inst, &new_functions_start);
99 continue;
100 };
101 if (!alive.isSet(index)) continue;
102 } else {
103 // annotation-style: emit only if all id operands are alive
104 id_offset_buf.items.len = 0;
105 parser.parseInstructionResultIds(binary.*, inst, &id_offset_buf) catch continue;
106 var all_alive = true;
107 for (id_offset_buf.items) |off| {
108 const id: ResultId = @enumFromInt(inst.operands[off]);
109 if (id_to_index.get(id)) |idx| {
110 if (!alive.isSet(idx)) {
111 all_alive = false;
112 break;
113 }
114 }
115 }
116 if (!all_alive) continue;
117 }
118 }
119
120 appendInst(&new_words, binary, inst, &new_functions_start);
121 }
122
123 {
124 var to_remove: std.ArrayList(ResultId) = .empty;
125 defer to_remove.deinit(gpa);
126
127 var ext_it = binary.ext_inst_map.iterator();
128 while (ext_it.next()) |entry| {
129 if (id_to_index.get(entry.key_ptr.*)) |index| {
130 if (!alive.isSet(index)) try to_remove.append(gpa, entry.key_ptr.*);
131 }
132 }
133 for (to_remove.items) |id| _ = binary.ext_inst_map.remove(id);
134
135 to_remove.items.len = 0;
136 var arith_it = binary.arith_type_width.iterator();
137 while (arith_it.next()) |entry| {
138 if (id_to_index.get(entry.key_ptr.*)) |index| {
139 if (!alive.isSet(index)) try to_remove.append(gpa, entry.key_ptr.*);
140 }
141 }
142 for (to_remove.items) |id| _ = binary.arith_type_width.remove(id);
143 }
144
145 binary.instructions = try gpa.dupe(Word, new_words.items);
146 binary.functions_start = new_functions_start orelse new_words.items.len;
147}
148
149fn appendInst(
150 new_words: *std.ArrayList(Word),
151 binary: *const BinaryModule,
152 inst: BinaryModule.Instruction,
153 new_functions_start: *?usize,
154) void {
155 if (new_functions_start.* == null and inst.offset >= binary.functions_start) {
156 new_functions_start.* = new_words.items.len;
157 }
158 const len = @as(usize, binary.instructions[inst.offset] >> 16);
159 new_words.appendSliceAssumeCapacity(binary.instructions[inst.offset..][0..len]);
160}
161
162fn markAlive(
163 parser: *BinaryModule.Parser,
164 binary: BinaryModule,
165 inst: BinaryModule.Instruction,
166 alive: *std.DynamicBitSetUnmanaged,
167 id_to_index: *const std.AutoHashMapUnmanaged(ResultId, u32),
168 code_offsets: *const std.ArrayList(usize),
169 id_offset_buf: *std.ArrayList(u16),
170) !void {
171 const start = id_offset_buf.items.len;
172 try parser.parseInstructionResultIds(binary, inst, id_offset_buf);
173 const end = id_offset_buf.items.len;
174
175 var i = start;
176 while (i < end) : (i += 1) {
177 const off = id_offset_buf.items[i];
178 const id: ResultId = @enumFromInt(inst.operands[off]);
179 const index = id_to_index.get(id) orelse continue;
180 if (alive.isSet(index)) continue;
181 alive.set(index);
182
183 const offset = code_offsets.items[index];
184 const ref_inst = BinaryModule.Instruction{
185 .opcode = @enumFromInt(binary.instructions[offset] & 0xFFFF),
186 .offset = offset,
187 .operands = blk: {
188 const l = binary.instructions[offset] >> 16;
189 break :blk binary.instructions[offset..][1..l];
190 },
191 };
192
193 if (ref_inst.opcode == .OpFunction) {
194 var fn_it = binary.iterateInstructionsFrom(ref_inst.offset);
195 _ = fn_it.next();
196 while (fn_it.next()) |fn_inst| {
197 if (fn_inst.opcode == .OpFunctionEnd) break;
198 markAlive(parser, binary, fn_inst, alive, id_to_index, code_offsets, id_offset_buf) catch {};
199 }
200 markAlive(parser, binary, ref_inst, alive, id_to_index, code_offsets, id_offset_buf) catch {};
201 } else {
202 markAlive(parser, binary, ref_inst, alive, id_to_index, code_offsets, id_offset_buf) catch {};
203 }
204 }
205}
206
207fn getResultId(inst: BinaryModule.Instruction, inst_spec: spec.Instruction) ?ResultId {
208 for (0..@min(2, inst_spec.operands.len)) |i| {
209 if (inst_spec.operands[i].kind == .id_result) {
210 if (i < inst.operands.len) return @enumFromInt(inst.operands[i]);
211 }
212 }
213 return null;
214}
215
216fn canPrune(op: Opcode) bool {
217 return switch (op.class()) {
218 .type_declaration,
219 .constant_creation,
220 .annotation,
221 => true,
222 else => switch (op) {
223 .OpFunction,
224 .OpUndef,
225 .OpString,
226 .OpName,
227 .OpMemberName,
228 .OpExtInstImport,
229 .OpVariable,
230 => true,
231 else => false,
232 },
233 };
234}
src/target.zig+1-3
......@@ -953,9 +953,7 @@ pub inline fn backendSupportsFeature(backend: std.lang.CompilerBackend, comptime
953953 // threads because they would all just be locking the same mutex to
954954 // protect Builder.
955955 .stage2_llvm => false,
956 // Same problem. Frontend needs to allow this backend to run in the
957 // linker thread.
958 .stage2_spirv => false,
956 .stage2_spirv => true,
959957 // Please do not make any more exceptions. Backends must support
960958 // being run in a separate thread from now on.
961959 else => true,
test/behavior/align.zig+2
......@@ -639,6 +639,8 @@ test "function pointer align mask" {
639639}
640640
641641test "align expression is implicitly comptime" {
642 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
643
642644 const S = struct {
643645 fn alignment() usize {
644646 return 4;
test/behavior/enum.zig+2
......@@ -1318,6 +1318,8 @@ test "switch on an extern enum with negative value" {
13181318}
13191319
13201320test "switch on an enum with small signed tag type" {
1321 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
1322
13211323 const E = enum(i3) {
13221324 y = -2,
13231325 z = -1,
test/behavior/inline_switch.zig+7
......@@ -4,6 +4,7 @@ const builtin = @import("builtin");
44
55test "inline scalar prongs" {
66 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
7 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
78
89 var x: usize = 0;
910 switch (x) {
......@@ -18,6 +19,7 @@ test "inline scalar prongs" {
1819
1920test "inline prong ranges" {
2021 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
22 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
2123
2224 var x: usize = 0;
2325 _ = &x;
......@@ -32,6 +34,7 @@ test "inline prong ranges" {
3234const E = enum { a, b, c, d };
3335test "inline switch enums" {
3436 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
37 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
3538
3639 var x: E = .a;
3740 _ = &x;
......@@ -71,6 +74,7 @@ test "inline switch unions" {
7174
7275test "inline else bool" {
7376 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
77 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
7478
7579 var a = true;
7680 _ = &a;
......@@ -82,6 +86,7 @@ test "inline else bool" {
8286
8387test "inline else error" {
8488 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
89 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
8590
8691 const Err = error{ a, b, c };
8792 var a = Err.a;
......@@ -94,6 +99,7 @@ test "inline else error" {
9499
95100test "inline else enum" {
96101 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
102 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
97103
98104 const E2 = enum(u8) { a = 2, b = 3, c = 4, d = 5 };
99105 var a: E2 = .a;
......@@ -124,6 +130,7 @@ test "inline else int with gaps" {
124130
125131test "inline else int all values" {
126132 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
133 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
127134
128135 var a: u2 = 0;
129136 _ = &a;
test/behavior/ir_block_deps.zig+1
......@@ -20,6 +20,7 @@ fn getErrInt() anyerror!i32 {
2020test "ir block deps" {
2121 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
2222 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
23 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
2324
2425 try expect((foo(1) catch unreachable) == 0);
2526 try expect((foo(2) catch unreachable) == 0);
test/behavior/switch.zig+1
......@@ -801,6 +801,7 @@ test "enum value without tag name used as switch item" {
801801}
802802
803803test "switch item sizeof" {
804 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
804805 const S = struct {
805806 fn doTheTest() !void {
806807 var a: usize = 0;
test/behavior/tuple.zig+1
......@@ -299,6 +299,7 @@ test "tuple type with void field and a runtime field" {
299299test "branching inside tuple literal" {
300300 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
301301 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
302 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
302303
303304 const S = struct {
304305 fn foo(a: anytype) !void {
test/behavior/union.zig+3
......@@ -885,6 +885,8 @@ test "union no tag with struct member" {
885885}
886886
887887test "extern union doesn't trigger field check at comptime" {
888 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
889
888890 const U = extern union {
889891 x: u32,
890892 y: u8,
......@@ -1214,6 +1216,7 @@ test "return an extern union from C calling convention" {
12141216test "noreturn field in union" {
12151217 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
12161218 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1219 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
12171220
12181221 const U = union(enum) {
12191222 a: u32,
test/cases/compile_errors/illegal_operation_on_logical_ptr.zig+8-24
......@@ -1,15 +1,3 @@
1export fn elemPtr() void {
2 var ptr: [*]u8 = undefined;
3 ptr[0] = 0;
4}
5
6export fn elemVal() void {
7 var ptr: [*]u8 = undefined;
8 var val = ptr[0];
9 _ = &ptr;
10 _ = &val;
11}
12
131export fn intFromPtr() void {
142 var value: u8 = 0;
153 _ = @intFromPtr(&value);
......@@ -37,15 +25,11 @@ export fn ptrIntArithmetic() void {
3725// error
3826// target=spirv64-vulkan
3927//
40// :3:8: error: illegal operation on logical pointer of type '[*]u8'
41// :3:8: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan
42// :8:18: error: illegal operation on logical pointer of type '[*]u8'
43// :8:18: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan
44// :15:21: error: illegal operation on logical pointer of type '*u8'
45// :15:21: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan
46// :20:20: error: illegal operation on logical pointer of type '*u8'
47// :20:20: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan
48// :28:17: error: illegal operation on logical pointer of type '*u8'
49// :28:17: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan
50// :34:14: error: illegal operation on logical pointer of type '[*]u8'
51// :34:14: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan
28// :3:21: error: illegal operation on logical pointer of type '*u8'
29// :3:21: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan
30// :8:20: error: illegal operation on logical pointer of type '*u8'
31// :8:20: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan
32// :16:17: error: illegal operation on logical pointer of type '*u8'
33// :16:17: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan
34// :22:14: error: illegal operation on logical pointer of type '[*]u8'
35// :22:14: note: cannot perform arithmetic on pointers with address space 'generic' on target spirv-vulkan