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(...@@ -27094,11 +27094,9 @@ fn elemPtrOneLayerOnly(
27094 return .fromValue(try ptr_val.ptrElem(index, pt));27094 return .fromValue(try ptr_val.ptrElem(index, pt));
27095 }27095 }
2709627096
27097 try sema.checkLogicalPtrOperation(block, src, indexable_ty);
27098
27099 const result_ty = try indexable_ty.elemPtrType(maybe_index, pt);27097 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);
27102 try sema.validateRuntimeValue(block, indexable_src, indexable);27100 try sema.validateRuntimeValue(block, indexable_src, indexable);
2710327101
27104 if (child_ty.abiSize(zcu) == 0) {27102 if (child_ty.abiSize(zcu) == 0) {
...@@ -27161,7 +27159,7 @@ fn elemVal(...@@ -27161,7 +27159,7 @@ fn elemVal(
27161 return sema.analyzeLoad(block, src, .fromValue(elem_ptr_val), indexable_src);27159 return sema.analyzeLoad(block, src, .fromValue(elem_ptr_val), indexable_src);
27162 }27160 }
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);
27165 switch (child_ty.classify(zcu)) {27163 switch (child_ty.classify(zcu)) {
27166 .runtime => {},27164 .runtime => {},
27167 .one_possible_value => return .fromValue((try child_ty.onePossibleValue(pt)).?),27165 .one_possible_value => return .fromValue((try child_ty.onePossibleValue(pt)).?),
...@@ -27210,11 +27208,9 @@ fn validateRuntimeElemAccess(...@@ -27210,11 +27208,9 @@ fn validateRuntimeElemAccess(
27210 block: *Block,27208 block: *Block,
27211 elem_index_src: LazySrcLoc,27209 elem_index_src: LazySrcLoc,
27212 elem_ty: Type,27210 elem_ty: Type,
27213 parent_ty: Type,
27214 parent_src: LazySrcLoc,27211 parent_src: LazySrcLoc,
27215) CompileError!void {27212) CompileError!void {
27216 const pt = sema.pt;27213 const zcu = sema.pt.zcu;
27217 const zcu = pt.zcu;
2721827214
27219 if (elem_ty.comptimeOnly(zcu)) {27215 if (elem_ty.comptimeOnly(zcu)) {
27220 const msg = msg: {27216 const msg = msg: {
...@@ -27231,14 +27227,6 @@ fn validateRuntimeElemAccess(...@@ -27231,14 +27227,6 @@ fn validateRuntimeElemAccess(
27231 };27227 };
27232 return sema.failWithOwnedErrorMsg(block, msg);27228 return sema.failWithOwnedErrorMsg(block, msg);
27233 }27229 }
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 }
27242}27230}
2724327231
27244/// Validates `elem_index`, and returns a pointer to that field using `structFieldPtrByIndex`.27232/// Validates `elem_index`, and returns a pointer to that field using `structFieldPtrByIndex`.
...@@ -27309,7 +27297,7 @@ fn tupleField(...@@ -27309,7 +27297,7 @@ fn tupleField(
27309 return Air.internedToRef((try tuple_val.fieldValue(pt, field_index)).toIntern());27297 return Air.internedToRef((try tuple_val.fieldValue(pt, field_index)).toIntern());
27310 }27298 }
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
27314 return block.addStructFieldVal(tuple, field_index, field_ty);27302 return block.addStructFieldVal(tuple, field_index, field_ty);
27315}27303}
...@@ -27364,7 +27352,7 @@ fn elemValArray(...@@ -27364,7 +27352,7 @@ fn elemValArray(
27364 if (try elem_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);27352 if (try elem_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
27365 }27353 }
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);
27368 try sema.validateRuntimeValue(block, array_src, array);27356 try sema.validateRuntimeValue(block, array_src, array);
2736927357
27370 if (oob_safety and block.wantSafety()) {27358 if (oob_safety and block.wantSafety()) {
...@@ -27467,7 +27455,7 @@ fn elemPtrVector(...@@ -27467,7 +27455,7 @@ fn elemPtrVector(
27467 };27455 };
2746827456
27469 if (!init) {27457 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);
27471 try sema.validateRuntimeValue(block, vector_ptr_src, vector_ptr);27459 try sema.validateRuntimeValue(block, vector_ptr_src, vector_ptr);
27472 }27460 }
2747327461
...@@ -27540,7 +27528,7 @@ fn elemPtrArray(...@@ -27540,7 +27528,7 @@ fn elemPtrArray(
27540 }27528 }
2754127529
27542 if (!init) {27530 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);
27544 try sema.validateRuntimeValue(block, array_ptr_src, array_ptr);27532 try sema.validateRuntimeValue(block, array_ptr_src, array_ptr);
27545 }27533 }
2754627534
...@@ -27603,7 +27591,7 @@ fn elemValSlice(...@@ -27603,7 +27591,7 @@ fn elemValSlice(
2760327591
27604 if (try elem_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);27592 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);
27607 try sema.validateRuntimeValue(block, slice_src, slice);27595 try sema.validateRuntimeValue(block, slice_src, slice);
2760827596
27609 if (oob_safety and block.wantSafety()) {27597 if (oob_safety and block.wantSafety()) {
...@@ -27663,7 +27651,7 @@ fn elemPtrSlice(...@@ -27663,7 +27651,7 @@ fn elemPtrSlice(
27663 }27651 }
27664 }27652 }
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);
27667 try sema.validateRuntimeValue(block, slice_src, slice);27655 try sema.validateRuntimeValue(block, slice_src, slice);
2766827656
27669 if (oob_safety and block.wantSafety()) {27657 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...@@ -4513,7 +4513,6 @@ pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) Ru
4513 comp.config.use_llvm,4513 comp.config.use_llvm,
4514 )) {4514 )) {
4515 else => unreachable, // assertion failure4515 else => unreachable, // assertion failure
4516 .stage2_spirv,
4517 .stage2_llvm,4516 .stage2_llvm,
4518 => {},4517 => {},
4519 },4518 },
...@@ -4594,18 +4593,6 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e...@@ -4594,18 +4593,6 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e
45944593
4595 const lf = comp.bin_file orelse return error.NoLinkFile;4594 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
4609 return codegen.generateFunction(lf, pt, func_index, air, &liveness);4596 return codegen.generateFunction(lf, pt, func_index, air, &liveness);
4610}4597}
46114598
src/codegen.zig+4
...@@ -101,6 +101,7 @@ pub const AnyMir = union {...@@ -101,6 +101,7 @@ pub const AnyMir = union {
101 x86_64: if (dev.env.supports(.x86_64_backend)) @import("codegen/x86_64/Mir.zig") else noreturn,101 x86_64: if (dev.env.supports(.x86_64_backend)) @import("codegen/x86_64/Mir.zig") else noreturn,
102 wasm: if (dev.env.supports(.wasm_backend)) @import("codegen/wasm/Mir.zig") else noreturn,102 wasm: if (dev.env.supports(.wasm_backend)) @import("codegen/wasm/Mir.zig") else noreturn,
103 c: if (dev.env.supports(.c_backend)) @import("codegen/c.zig").Mir else noreturn,103 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
105 pub inline fn tag(comptime backend: std.lang.CompilerBackend) []const u8 {106 pub inline fn tag(comptime backend: std.lang.CompilerBackend) []const u8 {
106 return switch (backend) {107 return switch (backend) {
...@@ -110,6 +111,7 @@ pub const AnyMir = union {...@@ -110,6 +111,7 @@ pub const AnyMir = union {
110 .stage2_x86_64 => "x86_64",111 .stage2_x86_64 => "x86_64",
111 .stage2_wasm => "wasm",112 .stage2_wasm => "wasm",
112 .stage2_c => "c",113 .stage2_c => "c",
114 .stage2_spirv => "spirv",
113 else => unreachable,115 else => unreachable,
114 };116 };
115 }117 }
...@@ -125,6 +127,7 @@ pub const AnyMir = union {...@@ -125,6 +127,7 @@ pub const AnyMir = union {
125 .stage2_x86_64,127 .stage2_x86_64,
126 .stage2_wasm,128 .stage2_wasm,
127 .stage2_c,129 .stage2_c,
130 .stage2_spirv,
128 => |backend_ct| @field(mir, tag(backend_ct)).deinit(gpa),131 => |backend_ct| @field(mir, tag(backend_ct)).deinit(gpa),
129 }132 }
130 }133 }
...@@ -153,6 +156,7 @@ pub fn generateFunction(...@@ -153,6 +156,7 @@ pub fn generateFunction(
153 .stage2_x86_64,156 .stage2_x86_64,
154 .stage2_wasm,157 .stage2_wasm,
155 .stage2_c,158 .stage2_c,
159 .stage2_spirv,
156 => |backend| {160 => |backend| {
157 dev.check(devFeatureForBackend(backend));161 dev.check(devFeatureForBackend(backend));
158 const CodeGen = importBackend(backend);162 const CodeGen = importBackend(backend);
src/codegen/spirv/CodeGen.zig+194
...@@ -5,6 +5,8 @@ const Signedness = std.lang.Signedness;...@@ -5,6 +5,8 @@ const Signedness = std.lang.Signedness;
5const assert = std.debug.assert;5const assert = std.debug.assert;
6const log = std.log.scoped(.codegen);6const log = std.log.scoped(.codegen);
77
8const link = @import("../../link.zig");
9const codegen = @import("../../codegen.zig");
8const Zcu = @import("../../Zcu.zig");10const Zcu = @import("../../Zcu.zig");
9const Type = @import("../../Type.zig");11const Type = @import("../../Type.zig");
10const Value = @import("../../Value.zig");12const Value = @import("../../Value.zig");
...@@ -12,6 +14,7 @@ const Air = @import("../../Air.zig");...@@ -12,6 +14,7 @@ const Air = @import("../../Air.zig");
12const InternPool = @import("../../InternPool.zig");14const InternPool = @import("../../InternPool.zig");
13const Section = @import("Section.zig");15const Section = @import("Section.zig");
14const Assembler = @import("Assembler.zig");16const Assembler = @import("Assembler.zig");
17const Mir = @import("Mir.zig");
1518
16const spec = @import("spec.zig");19const spec = @import("spec.zig");
17const Opcode = spec.Opcode;20const Opcode = spec.Opcode;
...@@ -169,6 +172,197 @@ pub fn deinit(cg: *CodeGen) void {...@@ -169,6 +172,197 @@ pub fn deinit(cg: *CodeGen) void {
169 cg.body.deinit(gpa);172 cg.body.deinit(gpa);
170}173}
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
172const Error = error{ AlreadyReported, OutOfMemory };366const Error = error{ AlreadyReported, OutOfMemory };
173367
174pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {368pub 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 {...@@ -841,7 +841,6 @@ pub const File = struct {
841 assert(base.comp.zcu.?.llvm_object == null);841 assert(base.comp.zcu.?.llvm_object == null);
842 switch (base.tag) {842 switch (base.tag) {
843 .lld => unreachable,843 .lld => unreachable,
844 .spirv => unreachable, // see corresponding special case in `Zcu.PerThread.runCodegenInner`
845 .plan9 => unreachable,844 .plan9 => unreachable,
846 inline else => |tag| {845 inline else => |tag| {
847 dev.check(tag.devFeature());846 dev.check(tag.devFeature());
src/link/SpirV.zig+619-108
...@@ -10,21 +10,33 @@ const Compilation = @import("../Compilation.zig");...@@ -10,21 +10,33 @@ const Compilation = @import("../Compilation.zig");
10const link = @import("../link.zig");10const link = @import("../link.zig");
11const Air = @import("../Air.zig");11const Air = @import("../Air.zig");
12const Type = @import("../Type.zig");12const Type = @import("../Type.zig");
13const codegen = @import("../codegen.zig");
13const CodeGen = @import("../codegen/spirv/CodeGen.zig");14const CodeGen = @import("../codegen/spirv/CodeGen.zig");
14const Module = @import("../codegen/spirv/Module.zig");15const Module = @import("../codegen/spirv/Module.zig");
15const trace = @import("../tracy.zig").trace;16const trace = @import("../tracy.zig").trace;
16const BinaryModule = @import("SpirV/BinaryModule.zig");17const BinaryModule = @import("SpirV/BinaryModule.zig");
17const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");18const 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
19const spec = @import("../codegen/spirv/spec.zig");22const spec = @import("../codegen/spirv/spec.zig");
23const Section = @import("../codegen/spirv/Section.zig");
20const Id = spec.Id;24const Id = spec.Id;
21const Word = spec.Word;25const Word = spec.Word;
26const Mir = @import("../codegen/spirv/Mir.zig");
2227
23const Linker = @This();28const Linker = @This();
2429
25base: link.File,30base: link.File,
26module: Module,31fragments: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, Mir) = .empty,
27cg: CodeGen,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
29pub fn createEmpty(41pub fn createEmpty(
30 arena: Allocator,42 arena: Allocator,
...@@ -32,7 +44,6 @@ pub fn createEmpty(...@@ -32,7 +44,6 @@ pub fn createEmpty(
32 emit: Path,44 emit: Path,
33 options: link.File.OpenOptions,45 options: link.File.OpenOptions,
34) !*Linker {46) !*Linker {
35 const gpa = comp.gpa;
36 const io = comp.io;47 const io = comp.io;
37 const target = &comp.root_mod.resolved_target.result;48 const target = &comp.root_mod.resolved_target.result;
3849
...@@ -61,21 +72,6 @@ pub fn createEmpty(...@@ -61,21 +72,6 @@ pub fn createEmpty(
61 .file = null,72 .file = null,
62 .build_id = options.build_id,73 .build_id = options.build_id,
63 },74 },
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 },
79 };75 };
80 errdefer linker.deinit();76 errdefer linker.deinit();
8177
...@@ -97,70 +93,55 @@ pub fn open(...@@ -97,70 +93,55 @@ pub fn open(
97}93}
9894
99pub fn deinit(linker: *Linker) void {95pub fn deinit(linker: *Linker) void {
100 linker.cg.deinit();96 const gpa = linker.base.comp.gpa;
101 linker.module.deinit();97 for (linker.fragments.values()) |*mir| {
102}98 mir.deinit(gpa);
10399 }
104fn generate(100 linker.fragments.deinit(gpa);
105 linker: *Linker,101 linker.pending_navs.deinit(gpa);
106 pt: Zcu.PerThread,102 linker.entry_points.deinit(gpa);
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 };
146}103}
147104
148pub fn updateFunc(105pub fn updateFunc(
149 linker: *Linker,106 linker: *Linker,
150 pt: Zcu.PerThread,107 pt: Zcu.PerThread,
151 func_index: InternPool.Index,108 func_index: InternPool.Index,
152 air: *const Air,109 mir: *codegen.AnyMir,
153 liveness: *const ?Air.Liveness,
154) !void {110) !void {
111 const gpa = linker.base.comp.gpa;
155 const nav = pt.zcu.funcInfo(func_index).owner_nav;112 const nav = pt.zcu.funcInfo(func_index).owner_nav;
156 // TODO: Separate types for generating decls and functions?113
157 try linker.generate(pt, nav, air.*, liveness.*.?, true);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 };
158}137}
159138
160pub fn updateNav(linker: *Linker, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.Error!void {139pub fn updateNav(linker: *Linker, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.Error!void {
161 const ip = &pt.zcu.intern_pool;140 const ip = &pt.zcu.intern_pool;
162 log.debug("lowering nav {f}({d})", .{ ip.getNav(nav).fqn.fmt(ip), nav });141 log.debug("deferring nav {f}({d}) to flush", .{ ip.getNav(nav).fqn.fmt(ip), nav });
163 try linker.generate(pt, nav, undefined, undefined, false);142
143 const gpa = linker.base.comp.gpa;
144 linker.pending_navs.append(gpa, nav) catch return error.OutOfMemory;
164}145}
165146
166pub fn updateExports(147pub fn updateExports(
...@@ -171,6 +152,7 @@ pub fn updateExports(...@@ -171,6 +152,7 @@ pub fn updateExports(
171) !void {152) !void {
172 const zcu = pt.zcu;153 const zcu = pt.zcu;
173 const ip = &zcu.intern_pool;154 const ip = &zcu.intern_pool;
155 const gpa = linker.base.comp.gpa;
174 const nav_index = switch (exported) {156 const nav_index = switch (exported) {
175 .nav => |nav| nav,157 .nav => |nav| nav,
176 .uav => |uav| {158 .uav => |uav| {
...@@ -180,21 +162,18 @@ pub fn updateExports(...@@ -180,21 +162,18 @@ pub fn updateExports(
180 };162 };
181 const nav_ty = ip.getNav(nav_index).resolved.?.type;163 const nav_ty = ip.getNav(nav_index).resolved.?.type;
182 if (ip.isFunctionType(nav_ty)) {164 if (ip.isFunctionType(nav_ty)) {
183 const spv_decl_index = try linker.module.resolveNav(ip, nav_index);
184 const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);165 const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);
185 if (cc == .spirv_device) return;166 if (cc == .spirv_device) return;
186167
187 for (export_indices) |export_idx| {168 for (export_indices) |export_idx| {
188 const exp = export_idx.ptr(zcu);169 const exp = export_idx.ptr(zcu);
189 try linker.module.declareEntryPoint(170 try linker.entry_points.append(gpa, .{
190 spv_decl_index,171 .nav = nav_index,
191 exp.opts.name.toSlice(ip),172 .name = exp.opts.name.toSlice(ip),
192 cc,173 .cc = cc,
193 );174 });
194 }175 }
195 }176 }
196
197 // TODO: Export regular functions, variables, etc using Linkage attributes.
198}177}
199178
200pub fn flush(179pub fn flush(
...@@ -203,11 +182,6 @@ pub fn flush(...@@ -203,11 +182,6 @@ pub fn flush(
203 tid: Zcu.PerThread.Id,182 tid: Zcu.PerThread.Id,
204 prog_node: std.Progress.Node,183 prog_node: std.Progress.Node,
205) link.Error!void {184) 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
211 const tracy = trace(@src());185 const tracy = trace(@src());
212 defer tracy.end();186 defer tracy.end();
213187
...@@ -219,19 +193,334 @@ pub fn flush(...@@ -219,19 +193,334 @@ pub fn flush(
219 const gpa = comp.gpa;193 const gpa = comp.gpa;
220 const io = comp.io;194 const io = comp.io;
221195
222 // We need to export the list of error names somewhere so that we can pretty-print them in the196 const zcu = comp.zcu.?;
223 // executor. This is not really an important thing though, so we can just dump it in any old197 const active = zcu.activate(tid);
224 // nonsemantic instruction. For now, just put it in OpSourceExtension with a special name.198 defer active.deactivate();
225 var error_info: std.Io.Writer.Allocating = .init(linker.module.gpa);199 const pt = active.pt;
226 defer error_info.deinit();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();
228 error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory;522 error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory;
229 const ip = &linker.base.comp.zcu.?.intern_pool;
230 for (ip.global_error_set.getNamesFromMainThread()) |name| {523 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.
235 error_info.writer.writeByte(':') catch return error.OutOfMemory;524 error_info.writer.writeByte(':') catch return error.OutOfMemory;
236 std.Uri.Component.percentEncode(525 std.Uri.Component.percentEncode(
237 &error_info.writer,526 &error_info.writer,
...@@ -246,27 +535,249 @@ pub fn flush(...@@ -246,27 +535,249 @@ pub fn flush(
246 }.isValidChar,535 }.isValidChar,
247 ) catch return error.OutOfMemory;536 ) catch return error.OutOfMemory;
248 }537 }
249 try linker.module.sections.debug_strings.emit(gpa, .OpSourceExtension, .{538 try debug_strings_section.emit(gpa, .OpSourceExtension, .{
250 .extension = error_info.written(),539 .extension = error_info.written(),
251 });540 });
252541
253 const module = try linker.module.finalize(arena);542 const zig_version = @import("builtin").zig_version;
254 errdefer arena.free(module);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) {551 const version: spec.Version = .{
257 error.OutOfMemory => |e| return e,552 .major = 1,
258 else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}),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 },
259 };562 };
260563
261 // TODO endianness bug. use file writer and call writeSliceEndian instead564 const generator_id: u32 = (spec.zig_generator_id << 16) | zig_spirv_compiler_version;
262 linker.base.file.?.writeStreamingAll(io, @ptrCast(linked_module)) catch |err|565
263 return diags.fail("failed to write: {t}", .{err});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 };
264}598}
265599
266fn linkModule(arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {600const MergedModule = struct {
267 var parser = try BinaryModule.Parser.init(arena);601 words: []const Word,
268 defer parser.deinit();602 id_bound: Word,
269 var binary = try parser.parse(module);603 version: spec.Version,
270 try lower_invocation_globals.run(&parser, &binary, progress);604 generator_id: u32,
271 return binary.finalize(arena);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 }
272}783}
src/link/SpirV/BinaryModule.zig+82-199
...@@ -11,278 +11,161 @@ const ResultId = spec.Id;...@@ -11,278 +11,161 @@ const ResultId = spec.Id;
1111
12const BinaryModule = @This();12const 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
22/// The result-id bound of this SPIR-V module.14/// The result-id bound of this SPIR-V module.
23id_bound: u32,15id_bound: u32,
2416
25/// The instructions of this module. This does not contain the header.17/// The instructions of this module (no header).
26instructions: []const Word,18instructions: []const Word,
2719
28/// Maps OpExtInstImport result-ids to their InstructionSet.20/// Maps OpExtInstImport result-ids to their InstructionSet.
29ext_inst_map: std.AutoHashMapUnmanaged(ResultId, InstructionSet),21ext_inst_map: std.AutoHashMapUnmanaged(ResultId, InstructionSet),
3022
31/// This map contains the width of arithmetic types (OpTypeInt and23/// Width of arithmetic types (OpTypeInt/OpTypeFloat). Needed to correctly
32/// OpTypeFloat). We need this information to correctly parse the operands24/// parse operands of Op(Spec)Constant and OpSwitch.
33/// of Op(Spec)Constant and OpSwitch.
34arith_type_width: std.AutoHashMapUnmanaged(ResultId, u16),25arith_type_width: std.AutoHashMapUnmanaged(ResultId, u16),
3526
36/// The starting offsets of some sections27functions_start: usize,
37sections: struct {
38 functions: usize,
39},
4028
41pub fn deinit(self: *BinaryModule, a: Allocator) void {29pub fn deinit(bm: *BinaryModule, gpa: Allocator) void {
42 self.ext_inst_map.deinit(a);30 bm.ext_inst_map.deinit(gpa);
43 self.arith_type_width.deinit(a);31 bm.arith_type_width.deinit(gpa);
44 self.* = undefined;32 bm.* = undefined;
45}33}
4634
47pub fn iterateInstructions(self: BinaryModule) Instruction.Iterator {35pub fn iterateInstructions(bm: BinaryModule) Instruction.Iterator {
48 return Instruction.Iterator.init(self.instructions, 0);36 return Instruction.Iterator.init(bm.instructions, 0);
49}37}
5038
51pub fn iterateInstructionsFrom(self: BinaryModule, offset: usize) Instruction.Iterator {39pub fn iterateInstructionsFrom(bm: BinaryModule, offset: usize) Instruction.Iterator {
52 return Instruction.Iterator.init(self.instructions, offset);40 return Instruction.Iterator.init(bm.instructions, offset);
53}41}
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
103pub const Instruction = struct {43pub const Instruction = struct {
104 pub const Iterator = struct {44 pub const Iterator = struct {
105 words: []const Word,45 words: []const Word,
106 index: usize = 0,
107 offset: usize = 0,46 offset: usize = 0,
10847
109 pub fn init(words: []const Word, start_offset: usize) Iterator {48 pub fn init(words: []const Word, start_offset: usize) Iterator {
110 return .{ .words = words, .offset = start_offset };49 return .{ .words = words, .offset = start_offset };
111 }50 }
11251
113 pub fn next(self: *Iterator) ?Instruction {52 pub fn next(it: *Iterator) ?Instruction {
114 if (self.offset >= self.words.len) return null;53 if (it.offset >= it.words.len) return null;
11554
116 const instruction_len = self.words[self.offset] >> 16;55 const instruction_len = it.words[it.offset] >> 16;
117 defer self.offset += instruction_len;56 defer it.offset += instruction_len;
118 defer self.index += 1;
119 assert(instruction_len != 0);57 assert(instruction_len != 0);
120 assert(self.offset < self.words.len);58 assert(it.offset < it.words.len);
12159
122 return Instruction{60 return Instruction{
123 .opcode = @enumFromInt(self.words[self.offset] & 0xFFFF),61 .opcode = @enumFromInt(it.words[it.offset] & 0xFFFF),
124 .index = self.index,62 .offset = it.offset,
125 .offset = self.offset,63 .operands = it.words[it.offset..][1..instruction_len],
126 .operands = self.words[self.offset..][1..instruction_len],
127 };64 };
128 }65 }
129 };66 };
13067
131 /// The opcode for this instruction.
132 opcode: Opcode,68 opcode: Opcode,
133 /// The instruction's index.
134 index: usize,
135 /// The instruction's word offset in the module.
136 offset: usize,69 offset: usize,
137 /// The raw (unparsed) operands for this instruction.
138 operands: []const Word,70 operands: []const Word,
139};71};
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.
145pub const Parser = struct {73pub const Parser = struct {
146 /// The allocator used to allocate this parser's structures,74 gpa: Allocator,
147 /// and also the structures of any parsed module.
148 a: Allocator,
149
150 /// Maps (instruction set, opcode) => instruction index (for instruction set)
151 opcode_table: std.AutoHashMapUnmanaged(u32, u16) = .empty,75 opcode_table: std.AutoHashMapUnmanaged(u32, u16) = .empty,
15276
153 pub fn init(a: Allocator) !Parser {77 pub fn init(gpa: Allocator) !Parser {
154 var self = Parser{78 var parser = Parser{ .gpa = gpa };
155 .a = a,79 errdefer parser.deinit();
156 };
157 errdefer self.deinit();
15880
159 inline for (std.meta.tags(InstructionSet)) |set| {81 inline for (std.meta.tags(InstructionSet)) |set| {
160 const instructions = set.instructions();82 const instructions = set.instructions();
161 try self.opcode_table.ensureUnusedCapacity(a, @intCast(instructions.len));83 try parser.opcode_table.ensureUnusedCapacity(gpa, @intCast(instructions.len));
162 for (instructions, 0..) |inst, i| {84 for (instructions, 0..) |inst, i| {
163 // Note: Some instructions may alias another. In this case we don't really care85 const entry = parser.opcode_table.getOrPutAssumeCapacity(mapSetAndOpcode(set, @intCast(inst.opcode)));
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)));
167 if (!entry.found_existing) {86 if (!entry.found_existing) {
168 entry.value_ptr.* = @intCast(i);87 entry.value_ptr.* = @intCast(i);
169 }88 }
170 }89 }
171 }90 }
17291
173 return self;92 return parser;
174 }93 }
17594
176 pub fn deinit(self: *Parser) void {95 pub fn deinit(parser: *Parser) void {
177 self.opcode_table.deinit(self.a);96 parser.opcode_table.deinit(parser.gpa);
178 }97 }
17998
180 fn mapSetAndOpcode(set: InstructionSet, opcode: u16) u32 {99 fn mapSetAndOpcode(set: InstructionSet, opcode: u16) u32 {
181 return (@as(u32, @intFromEnum(set)) << 16) | opcode;100 return (@as(u32, @intFromEnum(set)) << 16) | opcode;
182 }101 }
183102
184 pub fn getInstSpec(self: Parser, opcode: Opcode) ?spec.Instruction {103 pub fn getInstSpec(parser: Parser, opcode: Opcode) ?spec.Instruction {
185 const index = self.opcode_table.get(mapSetAndOpcode(.core, @intFromEnum(opcode))) orelse return null;104 const index = parser.opcode_table.get(mapSetAndOpcode(.core, @intFromEnum(opcode))) orelse return null;
186 return InstructionSet.core.instructions()[index];105 return InstructionSet.core.instructions()[index];
187 }106 }
188107
189 pub fn parse(self: *Parser, module: []const u32) ParseError!BinaryModule {108 /// Build a BinaryModule from raw instruction words (no header).
190 if (module[0] != spec.magic_number) {109 /// Scans for ext_inst_map, arith_type_width, and the functions section offset.
191 return error.InvalidMagic;110 pub fn initFromWords(parser: *Parser, words: []const Word, id_bound: u32) !BinaryModule {
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
197 var binary = BinaryModule{111 var binary = BinaryModule{
198 .version = @bitCast(module[1]),112 .id_bound = id_bound,
199 .generator_magic = @bitCast(module[2]),113 .instructions = words,
200 .id_bound = module[3],
201 .instructions = module[header_words..],
202 .ext_inst_map = .{},114 .ext_inst_map = .{},
203 .arith_type_width = .{},115 .arith_type_width = .{},
204 .sections = undefined,116 .functions_start = undefined,
205 };117 };
206118
207 var maybe_function_section: ?usize = null;119 var maybe_function_section: ?usize = null;
120 var it = binary.iterateInstructions();
121 while (it.next()) |inst| {
122 const inst_spec = parser.getInstSpec(inst.opcode) orelse continue;
123 const operands = inst.operands;
208124
209 // First pass through the module to verify basic structure and125 switch (inst.opcode) {
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) {
233 .OpExtInstImport => {126 .OpExtInstImport => {
234 const set_name = std.mem.sliceTo(std.mem.sliceAsBytes(operands[1..]), 0);127 const set_name = std.mem.sliceTo(std.mem.sliceAsBytes(operands[1..]), 0);
235 const set = std.meta.stringToEnum(InstructionSet, set_name) orelse {128 const set = std.meta.stringToEnum(InstructionSet, set_name) orelse continue;
236 log.err("invalid instruction set '{s}'", .{set_name});129 if (set == .core) continue;
237 return error.InvalidExtInstImport;130 try binary.ext_inst_map.put(parser.gpa, @enumFromInt(operands[0]), set);
238 };
239 if (set == .core) return error.InvalidExtInstImport;
240 try binary.ext_inst_map.put(self.a, @enumFromInt(operands[0]), set);
241 },131 },
242 .OpTypeInt, .OpTypeFloat => {132 .OpTypeInt, .OpTypeFloat => {
243 const entry = try binary.arith_type_width.getOrPut(self.a, @enumFromInt(operands[0]));133 try binary.arith_type_width.put(parser.gpa, @enumFromInt(operands[0]), @intCast(operands[1]));
244 if (entry.found_existing) return error.DuplicateId;
245 entry.value_ptr.* = std.math.cast(u16, operands[1]) orelse return error.InvalidOperands;
246 },134 },
247 .OpFunction => if (maybe_function_section == null) {135 .OpFunction => if (maybe_function_section == null) {
248 maybe_function_section = offset;136 maybe_function_section = inst.offset;
249 },137 },
250 else => {},138 else => {},
251 }139 }
252140
253 // OpSwitch takes a value as argument, not an OpType... hence we need to populate arith_type_width141 // propagate arith type widths through instructions that return int/float
254 // with ALL operations that return an int or float.
255 const spec_operands = inst_spec.operands;142 const spec_operands = inst_spec.operands;
256 if (spec_operands.len >= 2 and143 if (spec_operands.len >= 2 and
257 spec_operands[0].kind == .id_result_type and144 spec_operands[0].kind == .id_result_type and
258 spec_operands[1].kind == .id_result)145 spec_operands[1].kind == .id_result)
259 {146 {
260 if (operands.len < 2) return error.InvalidOperands;147 if (operands.len >= 2) {
261 if (binary.arith_type_width.get(@enumFromInt(operands[0]))) |width| {148 if (binary.arith_type_width.get(@enumFromInt(operands[0]))) |width| {
262 const entry = try binary.arith_type_width.getOrPut(self.a, @enumFromInt(operands[1]));149 try binary.arith_type_width.put(parser.gpa, @enumFromInt(operands[1]), width);
263 if (entry.found_existing) return error.DuplicateId;150 }
264 entry.value_ptr.* = width;
265 }151 }
266 }152 }
267 }153 }
268154
269 binary.sections = .{155 binary.functions_start = maybe_function_section orelse binary.instructions.len;
270 .functions = maybe_function_section orelse binary.instructions.len,
271 };
272156
273 return binary;157 return binary;
274 }158 }
275159
276 /// Parse offsets in the instruction that contain result-ids.160 /// Parse offsets in the instruction that contain result-ids.
277 /// Returned offsets are relative to inst.operands.161 /// Returned offsets are relative to inst.operands.
278 /// Returns in an arraylist to armortize allocations.
279 pub fn parseInstructionResultIds(162 pub fn parseInstructionResultIds(
280 self: *Parser,163 parser: *Parser,
281 binary: BinaryModule,164 binary: BinaryModule,
282 inst: Instruction,165 inst: Instruction,
283 offsets: *std.array_list.Managed(u16),166 offsets: *std.ArrayList(u16),
284 ) !void {167 ) !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))).?;
286 const operands = InstructionSet.core.instructions()[index].operands;169 const operands = InstructionSet.core.instructions()[index].operands;
287170
288 var offset: usize = 0;171 var offset: usize = 0;
...@@ -290,37 +173,37 @@ pub const Parser = struct {...@@ -290,37 +173,37 @@ pub const Parser = struct {
290 .OpSpecConstantOp => {173 .OpSpecConstantOp => {
291 assert(operands[0].kind == .id_result_type);174 assert(operands[0].kind == .id_result_type);
292 assert(operands[1].kind == .id_result);175 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
295 if (offset >= inst.operands.len) return error.InvalidPhysicalFormat;178 if (offset >= inst.operands.len) return error.InvalidPhysicalFormat;
296 const spec_opcode = std.math.cast(u16, inst.operands[offset]) orelse return error.InvalidPhysicalFormat;179 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)) orelse180 const spec_index = parser.opcode_table.get(mapSetAndOpcode(.core, spec_opcode)) orelse
298 return error.InvalidPhysicalFormat;181 return error.InvalidPhysicalFormat;
299 const spec_operands = InstructionSet.core.instructions()[spec_index].operands;182 const spec_operands = InstructionSet.core.instructions()[spec_index].operands;
300 assert(spec_operands[0].kind == .id_result_type);183 assert(spec_operands[0].kind == .id_result_type);
301 assert(spec_operands[1].kind == .id_result);184 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);
303 },186 },
304 .OpExtInst => {187 .OpExtInst => {
305 assert(operands[0].kind == .id_result_type);188 assert(operands[0].kind == .id_result_type);
306 assert(operands[1].kind == .id_result);189 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
309 if (offset + 1 >= inst.operands.len) return error.InvalidPhysicalFormat;192 if (offset + 1 >= inst.operands.len) return error.InvalidPhysicalFormat;
310 const set_id: ResultId = @enumFromInt(inst.operands[offset]);193 const set_id: ResultId = @enumFromInt(inst.operands[offset]);
311 try offsets.append(@intCast(offset));194 try offsets.append(parser.gpa, @intCast(offset));
312 const set = binary.ext_inst_map.get(set_id) orelse {195 const set = binary.ext_inst_map.get(set_id) orelse {
313 log.err("invalid instruction set {}", .{@intFromEnum(set_id)});196 log.err("invalid instruction set {}", .{@intFromEnum(set_id)});
314 return error.InvalidId;197 return error.InvalidId;
315 };198 };
316 const ext_opcode = std.math.cast(u16, inst.operands[offset + 1]) orelse return error.InvalidPhysicalFormat;199 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)) orelse200 const ext_index = parser.opcode_table.get(mapSetAndOpcode(set, ext_opcode)) orelse
318 return error.InvalidPhysicalFormat;201 return error.InvalidPhysicalFormat;
319 const ext_operands = set.instructions()[ext_index].operands;202 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);
321 },204 },
322 else => {205 else => {
323 offset = try self.parseOperandsResultIds(binary, inst, operands, offset, offsets);206 offset = try parser.parseOperandsResultIds(binary, inst, operands, offset, offsets);
324 },207 },
325 }208 }
326209
...@@ -328,50 +211,50 @@ pub const Parser = struct {...@@ -328,50 +211,50 @@ pub const Parser = struct {
328 }211 }
329212
330 fn parseOperandsResultIds(213 fn parseOperandsResultIds(
331 self: *Parser,214 parser: *Parser,
332 binary: BinaryModule,215 binary: BinaryModule,
333 inst: Instruction,216 inst: Instruction,
334 operands: []const spec.Operand,217 operands: []const spec.Operand,
335 start_offset: usize,218 start_offset: usize,
336 offsets: *std.array_list.Managed(u16),219 offsets: *std.ArrayList(u16),
337 ) !usize {220 ) !usize {
338 var offset = start_offset;221 var offset = start_offset;
339 for (operands) |operand| {222 for (operands) |operand| {
340 offset = try self.parseOperandResultIds(binary, inst, operand, offset, offsets);223 offset = try parser.parseOperandResultIds(binary, inst, operand, offset, offsets);
341 }224 }
342 return offset;225 return offset;
343 }226 }
344227
345 fn parseOperandResultIds(228 fn parseOperandResultIds(
346 self: *Parser,229 parser: *Parser,
347 binary: BinaryModule,230 binary: BinaryModule,
348 inst: Instruction,231 inst: Instruction,
349 operand: spec.Operand,232 operand: spec.Operand,
350 start_offset: usize,233 start_offset: usize,
351 offsets: *std.array_list.Managed(u16),234 offsets: *std.ArrayList(u16),
352 ) !usize {235 ) !usize {
353 var offset = start_offset;236 var offset = start_offset;
354 switch (operand.quantifier) {237 switch (operand.quantifier) {
355 .variadic => while (offset < inst.operands.len) {238 .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);
357 },240 },
358 .optional => if (offset < inst.operands.len) {241 .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);
360 },243 },
361 .required => {244 .required => {
362 offset = try self.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets);245 offset = try parser.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets);
363 },246 },
364 }247 }
365 return offset;248 return offset;
366 }249 }
367250
368 fn parseOperandKindResultIds(251 fn parseOperandKindResultIds(
369 self: *Parser,252 parser: *Parser,
370 binary: BinaryModule,253 binary: BinaryModule,
371 inst: Instruction,254 inst: Instruction,
372 kind: spec.OperandKind,255 kind: spec.OperandKind,
373 start_offset: usize,256 start_offset: usize,
374 offsets: *std.array_list.Managed(u16),257 offsets: *std.ArrayList(u16),
375 ) !usize {258 ) !usize {
376 var offset = start_offset;259 var offset = start_offset;
377 if (offset >= inst.operands.len) return error.InvalidPhysicalFormat;260 if (offset >= inst.operands.len) return error.InvalidPhysicalFormat;
...@@ -383,7 +266,7 @@ pub const Parser = struct {...@@ -383,7 +266,7 @@ pub const Parser = struct {
383 for (kind.enumerants()) |enumerant| {266 for (kind.enumerants()) |enumerant| {
384 if ((mask & enumerant.value) != 0) {267 if ((mask & enumerant.value) != 0) {
385 for (enumerant.parameters) |param_kind| {268 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);
387 }270 }
388 }271 }
389 }272 }
...@@ -394,14 +277,14 @@ pub const Parser = struct {...@@ -394,14 +277,14 @@ pub const Parser = struct {
394 for (kind.enumerants()) |enumerant| {277 for (kind.enumerants()) |enumerant| {
395 if (value == enumerant.value) {278 if (value == enumerant.value) {
396 for (enumerant.parameters) |param_kind| {279 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);
398 }281 }
399 break;282 break;
400 }283 }
401 }284 }
402 },285 },
403 .id => {286 .id => {
404 try offsets.append(@intCast(offset));287 try offsets.append(parser.gpa, @intCast(offset));
405 offset += 1;288 offset += 1;
406 },289 },
407 else => switch (kind) {290 else => switch (kind) {
...@@ -433,7 +316,7 @@ pub const Parser = struct {...@@ -433,7 +316,7 @@ pub const Parser = struct {
433 },316 },
434 .literal_ext_inst_integer => unreachable,317 .literal_ext_inst_integer => unreachable,
435 .literal_spec_constant_op_integer => unreachable,318 .literal_spec_constant_op_integer => unreachable,
436 .pair_literal_integer_id_ref => { // Switch case319 .pair_literal_integer_id_ref => {
437 assert(inst.opcode == .OpSwitch);320 assert(inst.opcode == .OpSwitch);
438 const bit_width = binary.arith_type_width.get(@enumFromInt(inst.operands[0])) orelse {321 const bit_width = binary.arith_type_width.get(@enumFromInt(inst.operands[0])) orelse {
439 log.err("invalid OpSwitch type {}", .{inst.operands[0]});322 log.err("invalid OpSwitch type {}", .{inst.operands[0]});
...@@ -444,16 +327,16 @@ pub const Parser = struct {...@@ -444,16 +327,16 @@ pub const Parser = struct {
444 33...64 => 2,327 33...64 => 2,
445 else => unreachable,328 else => unreachable,
446 };329 };
447 try offsets.append(@intCast(offset));330 try offsets.append(parser.gpa, @intCast(offset));
448 offset += 1;331 offset += 1;
449 },332 },
450 .pair_id_ref_literal_integer => {333 .pair_id_ref_literal_integer => {
451 try offsets.append(@intCast(offset));334 try offsets.append(parser.gpa, @intCast(offset));
452 offset += 2;335 offset += 2;
453 },336 },
454 .pair_id_ref_id_ref => {337 .pair_id_ref_id_ref => {
455 try offsets.append(@intCast(offset));338 try offsets.append(parser.gpa, @intCast(offset));
456 try offsets.append(@intCast(offset + 1));339 try offsets.append(parser.gpa, @intCast(offset + 1));
457 offset += 2;340 offset += 2;
458 },341 },
459 else => unreachable,342 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 {...@@ -71,7 +71,7 @@ const ModuleInfo = struct {
71 arena: Allocator,71 arena: Allocator,
72 parser: *BinaryModule.Parser,72 parser: *BinaryModule.Parser,
73 binary: BinaryModule,73 binary: BinaryModule,
74 ) BinaryModule.ParseError!ModuleInfo {74 ) !ModuleInfo {
75 var entry_points: std.array_hash_map.Auto(ResultId, void) = .empty;75 var entry_points: std.array_hash_map.Auto(ResultId, void) = .empty;
76 var functions: std.array_hash_map.Auto(ResultId, Fn) = .empty;76 var functions: std.array_hash_map.Auto(ResultId, Fn) = .empty;
77 var fn_types = std.AutoHashMap(ResultId, struct {77 var fn_types = std.AutoHashMap(ResultId, struct {
...@@ -79,9 +79,9 @@ const ModuleInfo = struct {...@@ -79,9 +79,9 @@ const ModuleInfo = struct {
79 param_types: []const ResultId,79 param_types: []const ResultId,
80 }).init(arena);80 }).init(arena);
81 var calls: std.array_hash_map.Auto(ResultId, void) = .empty;81 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;
83 var function_invocation_globals: std.array_hash_map.Auto(ResultId, void) = .empty;83 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;
85 var invocation_globals: std.array_hash_map.Auto(ResultId, InvocationGlobal) = .empty;85 var invocation_globals: std.array_hash_map.Auto(ResultId, InvocationGlobal) = .empty;
8686
87 var maybe_current_function: ?ResultId = null;87 var maybe_current_function: ?ResultId = null;
...@@ -164,7 +164,7 @@ const ModuleInfo = struct {...@@ -164,7 +164,7 @@ const ModuleInfo = struct {
164 }164 }
165165
166 const first_callee = callee_store.items.len;166 const first_callee = callee_store.items.len;
167 try callee_store.appendSlice(calls.keys());167 try callee_store.appendSlice(arena, calls.keys());
168168
169 const fn_type = fn_types.get(fn_ty_id) orelse {169 const fn_type = fn_types.get(fn_ty_id) orelse {
170 log.err("Function {f} has invalid OpFunction type", .{current_function});170 log.err("Function {f} has invalid OpFunction type", .{current_function});
...@@ -395,12 +395,12 @@ const ModuleBuilder = struct {...@@ -395,12 +395,12 @@ const ModuleBuilder = struct {
395 return @enumFromInt(self.id_bound);395 return @enumFromInt(self.id_bound);
396 }396 }
397397
398 fn finalize(self: *ModuleBuilder, a: Allocator, binary: *BinaryModule) !void {398 fn finalize(self: *ModuleBuilder, arena: Allocator, binary: *BinaryModule) !void {
399 binary.id_bound = self.id_bound;399 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);
401 // Nothing is removed in this pass so we don't need to change any of the maps,401 // Nothing is removed in this pass so we don't need to change any of the maps,
402 // just make sure the section is updated.402 // 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;
404 }404 }
405405
406 /// Process everything from `binary` up to the first function and emit it into the builder.406 /// Process everything from `binary` up to the first function and emit it into the builder.
...@@ -525,12 +525,12 @@ const ModuleBuilder = struct {...@@ -525,12 +525,12 @@ const ModuleBuilder = struct {
525 binary: BinaryModule,525 binary: BinaryModule,
526 info: ModuleInfo,526 info: ModuleInfo,
527 ) !void {527 ) !void {
528 var result_id_offsets = std.array_list.Managed(u16).init(self.arena);528 var result_id_offsets: std.ArrayList(u16) = .empty;
529 var operands = std.array_list.Managed(u32).init(self.arena);529 var operands: std.ArrayList(u32) = .empty;
530530
531 var maybe_current_function: ?ResultId = null;531 var maybe_current_function: ?ResultId = null;
532 var skip_until_end: bool = false;532 var skip_until_end: bool = false;
533 var it = binary.iterateInstructionsFrom(binary.sections.functions);533 var it = binary.iterateInstructionsFrom(binary.functions_start);
534 self.new_functions_section = self.section.instructions.items.len;534 self.new_functions_section = self.section.instructions.items.len;
535 while (it.next()) |inst| {535 while (it.next()) |inst| {
536 if (skip_until_end) {536 if (skip_until_end) {
...@@ -541,7 +541,7 @@ const ModuleBuilder = struct {...@@ -541,7 +541,7 @@ const ModuleBuilder = struct {
541 try parser.parseInstructionResultIds(binary, inst, &result_id_offsets);541 try parser.parseInstructionResultIds(binary, inst, &result_id_offsets);
542542
543 operands.items.len = 0;543 operands.items.len = 0;
544 try operands.appendSlice(inst.operands);544 try operands.appendSlice(self.arena, inst.operands);
545545
546 // Replace the result-ids with the global's new result-id if required.546 // Replace the result-ids with the global's new result-id if required.
547 for (result_id_offsets.items) |off| {547 for (result_id_offsets.items) |off| {
...@@ -741,14 +741,14 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Pr...@@ -741,14 +741,14 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Pr
741 const sub_node = progress.start("Lower invocation globals", 6);741 const sub_node = progress.start("Lower invocation globals", 6);
742 defer sub_node.end();742 defer sub_node.end();
743743
744 var arena = std.heap.ArenaAllocator.init(parser.a);744 var arena_state = std.heap.ArenaAllocator.init(parser.gpa);
745 defer arena.deinit();745 defer arena_state.deinit();
746 const a = arena.allocator();746 const arena = arena_state.allocator();
747747
748 var info = try ModuleInfo.parse(a, parser, binary.*);748 var info = try ModuleInfo.parse(arena, parser, binary.*);
749 try info.resolve(a);749 try info.resolve(arena);
750750
751 var builder = try ModuleBuilder.init(a, binary.*, info);751 var builder = try ModuleBuilder.init(arena, binary.*, info);
752 sub_node.completeOne();752 sub_node.completeOne();
753 try builder.deriveNewFnInfo(info);753 try builder.deriveNewFnInfo(info);
754 sub_node.completeOne();754 sub_node.completeOne();
...@@ -760,5 +760,5 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Pr...@@ -760,5 +760,5 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Pr
760 sub_node.completeOne();760 sub_node.completeOne();
761 try builder.emitNewEntryPoints(info);761 try builder.emitNewEntryPoints(info);
762 sub_node.completeOne();762 sub_node.completeOne();
763 try builder.finalize(parser.a, binary);763 try builder.finalize(parser.gpa, binary);
764}764}
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...@@ -953,9 +953,7 @@ pub inline fn backendSupportsFeature(backend: std.lang.CompilerBackend, comptime
953 // threads because they would all just be locking the same mutex to953 // threads because they would all just be locking the same mutex to
954 // protect Builder.954 // protect Builder.
955 .stage2_llvm => false,955 .stage2_llvm => false,
956 // Same problem. Frontend needs to allow this backend to run in the956 .stage2_spirv => true,
957 // linker thread.
958 .stage2_spirv => false,
959 // Please do not make any more exceptions. Backends must support957 // Please do not make any more exceptions. Backends must support
960 // being run in a separate thread from now on.958 // being run in a separate thread from now on.
961 else => true,959 else => true,
test/behavior/align.zig+2
...@@ -639,6 +639,8 @@ test "function pointer align mask" {...@@ -639,6 +639,8 @@ test "function pointer align mask" {
639}639}
640640
641test "align expression is implicitly comptime" {641test "align expression is implicitly comptime" {
642 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
643
642 const S = struct {644 const S = struct {
643 fn alignment() usize {645 fn alignment() usize {
644 return 4;646 return 4;
test/behavior/enum.zig+2
...@@ -1318,6 +1318,8 @@ test "switch on an extern enum with negative value" {...@@ -1318,6 +1318,8 @@ test "switch on an extern enum with negative value" {
1318}1318}
13191319
1320test "switch on an enum with small signed tag type" {1320test "switch on an enum with small signed tag type" {
1321 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
1322
1321 const E = enum(i3) {1323 const E = enum(i3) {
1322 y = -2,1324 y = -2,
1323 z = -1,1325 z = -1,
test/behavior/inline_switch.zig+7
...@@ -4,6 +4,7 @@ const builtin = @import("builtin");...@@ -4,6 +4,7 @@ const builtin = @import("builtin");
44
5test "inline scalar prongs" {5test "inline scalar prongs" {
6 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO6 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
7 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
78
8 var x: usize = 0;9 var x: usize = 0;
9 switch (x) {10 switch (x) {
...@@ -18,6 +19,7 @@ test "inline scalar prongs" {...@@ -18,6 +19,7 @@ test "inline scalar prongs" {
1819
19test "inline prong ranges" {20test "inline prong ranges" {
20 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO21 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
22 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
2123
22 var x: usize = 0;24 var x: usize = 0;
23 _ = &x;25 _ = &x;
...@@ -32,6 +34,7 @@ test "inline prong ranges" {...@@ -32,6 +34,7 @@ test "inline prong ranges" {
32const E = enum { a, b, c, d };34const E = enum { a, b, c, d };
33test "inline switch enums" {35test "inline switch enums" {
34 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO36 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
37 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
3538
36 var x: E = .a;39 var x: E = .a;
37 _ = &x;40 _ = &x;
...@@ -71,6 +74,7 @@ test "inline switch unions" {...@@ -71,6 +74,7 @@ test "inline switch unions" {
7174
72test "inline else bool" {75test "inline else bool" {
73 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO76 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
77 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
7478
75 var a = true;79 var a = true;
76 _ = &a;80 _ = &a;
...@@ -82,6 +86,7 @@ test "inline else bool" {...@@ -82,6 +86,7 @@ test "inline else bool" {
8286
83test "inline else error" {87test "inline else error" {
84 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO88 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
89 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
8590
86 const Err = error{ a, b, c };91 const Err = error{ a, b, c };
87 var a = Err.a;92 var a = Err.a;
...@@ -94,6 +99,7 @@ test "inline else error" {...@@ -94,6 +99,7 @@ test "inline else error" {
9499
95test "inline else enum" {100test "inline else enum" {
96 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO101 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
102 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
97103
98 const E2 = enum(u8) { a = 2, b = 3, c = 4, d = 5 };104 const E2 = enum(u8) { a = 2, b = 3, c = 4, d = 5 };
99 var a: E2 = .a;105 var a: E2 = .a;
...@@ -124,6 +130,7 @@ test "inline else int with gaps" {...@@ -124,6 +130,7 @@ test "inline else int with gaps" {
124130
125test "inline else int all values" {131test "inline else int all values" {
126 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO132 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
133 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
127134
128 var a: u2 = 0;135 var a: u2 = 0;
129 _ = &a;136 _ = &a;
test/behavior/ir_block_deps.zig+1
...@@ -20,6 +20,7 @@ fn getErrInt() anyerror!i32 {...@@ -20,6 +20,7 @@ fn getErrInt() anyerror!i32 {
20test "ir block deps" {20test "ir block deps" {
21 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO21 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
22 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO22 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
23 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
2324
24 try expect((foo(1) catch unreachable) == 0);25 try expect((foo(1) catch unreachable) == 0);
25 try expect((foo(2) catch unreachable) == 0);26 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" {...@@ -801,6 +801,7 @@ test "enum value without tag name used as switch item" {
801}801}
802802
803test "switch item sizeof" {803test "switch item sizeof" {
804 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
804 const S = struct {805 const S = struct {
805 fn doTheTest() !void {806 fn doTheTest() !void {
806 var a: usize = 0;807 var a: usize = 0;
test/behavior/tuple.zig+1
...@@ -299,6 +299,7 @@ test "tuple type with void field and a runtime field" {...@@ -299,6 +299,7 @@ test "tuple type with void field and a runtime field" {
299test "branching inside tuple literal" {299test "branching inside tuple literal" {
300 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO300 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
301 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO301 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
302 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
302303
303 const S = struct {304 const S = struct {
304 fn foo(a: anytype) !void {305 fn foo(a: anytype) !void {
test/behavior/union.zig+3
...@@ -885,6 +885,8 @@ test "union no tag with struct member" {...@@ -885,6 +885,8 @@ test "union no tag with struct member" {
885}885}
886886
887test "extern union doesn't trigger field check at comptime" {887test "extern union doesn't trigger field check at comptime" {
888 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
889
888 const U = extern union {890 const U = extern union {
889 x: u32,891 x: u32,
890 y: u8,892 y: u8,
...@@ -1214,6 +1216,7 @@ test "return an extern union from C calling convention" {...@@ -1214,6 +1216,7 @@ test "return an extern union from C calling convention" {
1214test "noreturn field in union" {1216test "noreturn field in union" {
1215 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1217 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1216 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1218 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1219 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
12171220
1218 const U = union(enum) {1221 const U = union(enum) {
1219 a: u32,1222 a: u32,
test/cases/compile_errors/illegal_operation_on_logical_ptr.zig+8-24
...@@ -1,15 +1,3 @@...@@ -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
13export fn intFromPtr() void {1export fn intFromPtr() void {
14 var value: u8 = 0;2 var value: u8 = 0;
15 _ = @intFromPtr(&value);3 _ = @intFromPtr(&value);
...@@ -37,15 +25,11 @@ export fn ptrIntArithmetic() void {...@@ -37,15 +25,11 @@ export fn ptrIntArithmetic() void {
37// error25// error
38// target=spirv64-vulkan26// target=spirv64-vulkan
39//27//
40// :3:8: error: illegal operation on logical pointer of type '[*]u8'28// :3:21: 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-vulkan29// :3:21: 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'30// :8:20: 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-vulkan31// :8:20: 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'32// :16:17: 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-vulkan33// :16:17: 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'34// :22:14: 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-vulkan35// :22:14: 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