authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-07 20:55:50-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-08-07 20:55:50-07:00
log3fb86841cc65437c65a6d599117833e260ea797c
tree2f4df8b5da41e8df00ba0989ca72beb26566b6c1
parent5998a8cebe3973d70c258b2a1440c5c3252d3539
parentcd4b03c5ed1bc5b48ad9c353679b309ead75551d
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #24661 from alichraghi/spv4

spirv: refactor and remove deduplication ISel

17 files changed, 8302 insertions(+), 12059 deletions(-)

CMakeLists.txt-10
......@@ -553,11 +553,6 @@ set(ZIG_STAGE2_SOURCES
553553 src/codegen/c/Type.zig
554554 src/codegen/llvm.zig
555555 src/codegen/llvm/bindings.zig
556 src/codegen/spirv.zig
557 src/codegen/spirv/Assembler.zig
558 src/codegen/spirv/Module.zig
559 src/codegen/spirv/Section.zig
560 src/codegen/spirv/spec.zig
561556 src/crash_report.zig
562557 src/dev.zig
563558 src/libs/freebsd.zig
......@@ -620,11 +615,6 @@ set(ZIG_STAGE2_SOURCES
620615 src/link/Plan9.zig
621616 src/link/Plan9/aout.zig
622617 src/link/Queue.zig
623 src/link/SpirV.zig
624 src/link/SpirV/BinaryModule.zig
625 src/link/SpirV/deduplicate.zig
626 src/link/SpirV/lower_invocation_globals.zig
627 src/link/SpirV/prune_unused.zig
628618 src/link/StringTable.zig
629619 src/link/Wasm.zig
630620 src/link/Wasm/Archive.zig
lib/std/Build/Watch.zig+7-1
......@@ -177,7 +177,13 @@ const Os = switch (builtin.os.tag) {
177177 const gop = try w.dir_table.getOrPut(gpa, path);
178178 if (!gop.found_existing) {
179179 var mount_id: MountId = undefined;
180 const dir_handle = try Os.getDirHandle(gpa, path, &mount_id);
180 const dir_handle = Os.getDirHandle(gpa, path, &mount_id) catch |err| switch (err) {
181 error.FileNotFound => {
182 std.debug.assert(w.dir_table.swapRemove(path));
183 continue;
184 },
185 else => return err,
186 };
181187 const fan_fd = blk: {
182188 const fd_gop = try w.os.poll_fds.getOrPut(gpa, mount_id);
183189 if (!fd_gop.found_existing) {
src/Zcu.zig+2-3
......@@ -3651,9 +3651,8 @@ pub fn errorSetBits(zcu: *const Zcu) u16 {
36513651
36523652 if (zcu.error_limit == 0) return 0;
36533653 if (target.cpu.arch.isSpirV()) {
3654 if (!target.cpu.has(.spirv, .storage_push_constant16)) {
3655 return 32;
3656 }
3654 // As expected by https://github.com/Snektron/zig-spirv-test-executor
3655 if (zcu.comp.config.is_test) return 32;
36573656 }
36583657
36593658 return @as(u16, std.math.log2_int(ErrorInt, zcu.error_limit)) + 1;
src/Zcu/PerThread.zig+2-5
......@@ -4459,13 +4459,10 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e
44594459
44604460 const lf = comp.bin_file orelse return error.NoLinkFile;
44614461
4462 // TODO: self-hosted codegen should always have a type of MIR; codegen should produce that MIR,
4463 // and the linker should consume it. However, our SPIR-V backend is currently tightly coupled
4464 // with our SPIR-V linker, so needs to work more like the LLVM backend. This should be fixed to
4465 // unblock threaded codegen for SPIR-V.
4462 // Just like LLVM, the SPIR-V backend can't multi-threaded due to SPIR-V design limitations.
44664463 if (lf.cast(.spirv)) |spirv_file| {
44674464 assert(pt.tid == .main); // SPIR-V has a lot of shared state
4468 spirv_file.object.updateFunc(pt, func_index, air, &liveness) catch |err| {
4465 spirv_file.updateFunc(pt, func_index, air, &liveness) catch |err| {
44694466 switch (err) {
44704467 error.OutOfMemory => comp.link_diags.setAllocFailure(),
44714468 }
src/codegen.zig+1-1
......@@ -57,7 +57,7 @@ fn importBackend(comptime backend: std.builtin.CompilerBackend) type {
5757 .stage2_powerpc => unreachable,
5858 .stage2_riscv64 => @import("arch/riscv64/CodeGen.zig"),
5959 .stage2_sparc64 => @import("arch/sparc64/CodeGen.zig"),
60 .stage2_spirv => @import("codegen/spirv.zig"),
60 .stage2_spirv => @import("codegen/spirv/CodeGen.zig"),
6161 .stage2_wasm => @import("arch/wasm/CodeGen.zig"),
6262 .stage2_x86, .stage2_x86_64 => @import("arch/x86_64/CodeGen.zig"),
6363 _ => unreachable,
src/codegen/spirv.zig deleted-6658
......@@ -1,6658 +0,0 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const Target = std.Target;
4const log = std.log.scoped(.codegen);
5const assert = std.debug.assert;
6const Signedness = std.builtin.Signedness;
7
8const Zcu = @import("../Zcu.zig");
9const Decl = Zcu.Decl;
10const Type = @import("../Type.zig");
11const Value = @import("../Value.zig");
12const Air = @import("../Air.zig");
13const InternPool = @import("../InternPool.zig");
14
15const spec = @import("spirv/spec.zig");
16const Opcode = spec.Opcode;
17const Word = spec.Word;
18const Id = spec.Id;
19const StorageClass = spec.StorageClass;
20
21const SpvModule = @import("spirv/Module.zig");
22const IdRange = SpvModule.IdRange;
23
24const SpvSection = @import("spirv/Section.zig");
25const SpvAssembler = @import("spirv/Assembler.zig");
26
27const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, Id);
28
29pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
30 return comptime &.initMany(&.{
31 .expand_intcast_safe,
32 .expand_int_from_float_safe,
33 .expand_int_from_float_optimized_safe,
34 .expand_add_safe,
35 .expand_sub_safe,
36 .expand_mul_safe,
37 });
38}
39
40pub const zig_call_abi_ver = 3;
41pub const big_int_bits = 32;
42
43const InternMap = std.AutoHashMapUnmanaged(struct { InternPool.Index, NavGen.Repr }, Id);
44const PtrTypeMap = std.AutoHashMapUnmanaged(
45 struct { InternPool.Index, StorageClass, NavGen.Repr },
46 struct { ty_id: Id, fwd_emitted: bool },
47);
48
49const ControlFlow = union(enum) {
50 const Structured = struct {
51 /// This type indicates the way that a block is terminated. The
52 /// state of a particular block is used to track how a jump from
53 /// inside the block must reach the outside.
54 const Block = union(enum) {
55 const Incoming = struct {
56 src_label: Id,
57 /// Instruction that returns an u32 value of the
58 /// `Air.Inst.Index` that control flow should jump to.
59 next_block: Id,
60 };
61
62 const SelectionMerge = struct {
63 /// Incoming block from the `then` label.
64 /// Note that hte incoming block from the `else` label is
65 /// either given by the next element in the stack.
66 incoming: Incoming,
67 /// The label id of the cond_br's merge block.
68 /// For the top-most element in the stack, this
69 /// value is undefined.
70 merge_block: Id,
71 };
72
73 /// For a `selection` type block, we cannot use early exits, and we
74 /// must generate a 'merge ladder' of OpSelection instructions. To that end,
75 /// we keep a stack of the merges that still must be closed at the end of
76 /// a block.
77 ///
78 /// This entire structure basically just resembles a tree like
79 /// a x
80 /// \ /
81 /// b o merge
82 /// \ /
83 /// c o merge
84 /// \ /
85 /// o merge
86 /// /
87 /// o jump to next block
88 selection: struct {
89 /// In order to know which merges we still need to do, we need to keep
90 /// a stack of those.
91 merge_stack: std.ArrayListUnmanaged(SelectionMerge) = .empty,
92 },
93 /// For a `loop` type block, we can early-exit the block by
94 /// jumping to the loop exit node, and we don't need to generate
95 /// an entire stack of merges.
96 loop: struct {
97 /// The next block to jump to can be determined from any number
98 /// of conditions that jump to the loop exit.
99 merges: std.ArrayListUnmanaged(Incoming) = .empty,
100 /// The label id of the loop's merge block.
101 merge_block: Id,
102 },
103
104 fn deinit(self: *Structured.Block, a: Allocator) void {
105 switch (self.*) {
106 .selection => |*merge| merge.merge_stack.deinit(a),
107 .loop => |*merge| merge.merges.deinit(a),
108 }
109 self.* = undefined;
110 }
111 };
112 /// The stack of (structured) blocks that we are currently in. This determines
113 /// how exits from the current block must be handled.
114 block_stack: std.ArrayListUnmanaged(*Structured.Block) = .empty,
115 /// Maps `block` inst indices to the variable that the block's result
116 /// value must be written to.
117 block_results: std.AutoHashMapUnmanaged(Air.Inst.Index, Id) = .empty,
118 };
119
120 const Unstructured = struct {
121 const Incoming = struct {
122 src_label: Id,
123 break_value_id: Id,
124 };
125
126 const Block = struct {
127 label: ?Id = null,
128 incoming_blocks: std.ArrayListUnmanaged(Incoming) = .empty,
129 };
130
131 /// We need to keep track of result ids for block labels, as well as the 'incoming'
132 /// blocks for a block.
133 blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, *Block) = .empty,
134 };
135
136 structured: Structured,
137 unstructured: Unstructured,
138
139 pub fn deinit(self: *ControlFlow, a: Allocator) void {
140 switch (self.*) {
141 .structured => |*cf| {
142 cf.block_stack.deinit(a);
143 cf.block_results.deinit(a);
144 },
145 .unstructured => |*cf| {
146 cf.blocks.deinit(a);
147 },
148 }
149 self.* = undefined;
150 }
151};
152
153/// This structure holds information that is relevant to the entire compilation,
154/// in contrast to `NavGen`, which only holds relevant information about a
155/// single decl.
156pub const Object = struct {
157 /// A general-purpose allocator that can be used for any allocation for this Object.
158 gpa: Allocator,
159
160 /// the SPIR-V module that represents the final binary.
161 spv: SpvModule,
162
163 /// The Zig module that this object file is generated for.
164 /// A map of Zig decl indices to SPIR-V decl indices.
165 nav_link: std.AutoHashMapUnmanaged(InternPool.Nav.Index, SpvModule.Decl.Index) = .empty,
166
167 /// A map of Zig InternPool indices for anonymous decls to SPIR-V decl indices.
168 uav_link: std.AutoHashMapUnmanaged(struct { InternPool.Index, StorageClass }, SpvModule.Decl.Index) = .empty,
169
170 /// A map that maps AIR intern pool indices to SPIR-V result-ids.
171 intern_map: InternMap = .empty,
172
173 /// This map serves a dual purpose:
174 /// - It keeps track of pointers that are currently being emitted, so that we can tell
175 /// if they are recursive and need an OpTypeForwardPointer.
176 /// - It caches pointers by child-type. This is required because sometimes we rely on
177 /// ID-equality for pointers, and pointers constructed via `ptrType()` aren't interned
178 /// via the usual `intern_map` mechanism.
179 ptr_types: PtrTypeMap = .{},
180
181 /// For test declarations for Vulkan, we have to add a buffer.
182 /// We only need to generate this once, this holds the link information
183 /// related to that.
184 error_buffer: ?SpvModule.Decl.Index = null,
185
186 pub fn init(gpa: Allocator, target: *const std.Target) Object {
187 return .{
188 .gpa = gpa,
189 .spv = SpvModule.init(gpa, target),
190 };
191 }
192
193 pub fn deinit(self: *Object) void {
194 self.spv.deinit();
195 self.nav_link.deinit(self.gpa);
196 self.uav_link.deinit(self.gpa);
197 self.intern_map.deinit(self.gpa);
198 self.ptr_types.deinit(self.gpa);
199 }
200
201 fn genNav(
202 self: *Object,
203 pt: Zcu.PerThread,
204 nav_index: InternPool.Nav.Index,
205 air: Air,
206 liveness: Air.Liveness,
207 do_codegen: bool,
208 ) !void {
209 const zcu = pt.zcu;
210 const gpa = zcu.gpa;
211 const structured_cfg = zcu.navFileScope(nav_index).mod.?.structured_cfg;
212
213 var nav_gen = NavGen{
214 .gpa = gpa,
215 .object = self,
216 .pt = pt,
217 .spv = &self.spv,
218 .owner_nav = nav_index,
219 .air = air,
220 .liveness = liveness,
221 .intern_map = &self.intern_map,
222 .ptr_types = &self.ptr_types,
223 .control_flow = switch (structured_cfg) {
224 true => .{ .structured = .{} },
225 false => .{ .unstructured = .{} },
226 },
227 .current_block_label = undefined,
228 .base_line = zcu.navSrcLine(nav_index),
229 };
230 defer nav_gen.deinit();
231
232 nav_gen.genNav(do_codegen) catch |err| switch (err) {
233 error.CodegenFail => switch (zcu.codegenFailMsg(nav_index, nav_gen.error_msg.?)) {
234 error.CodegenFail => {},
235 error.OutOfMemory => |e| return e,
236 },
237 else => |other| {
238 // There might be an error that happened *after* self.error_msg
239 // was already allocated, so be sure to free it.
240 if (nav_gen.error_msg) |error_msg| {
241 error_msg.deinit(gpa);
242 }
243
244 return other;
245 },
246 };
247 }
248
249 pub fn updateFunc(
250 self: *Object,
251 pt: Zcu.PerThread,
252 func_index: InternPool.Index,
253 air: *const Air,
254 liveness: *const ?Air.Liveness,
255 ) !void {
256 const nav = pt.zcu.funcInfo(func_index).owner_nav;
257 // TODO: Separate types for generating decls and functions?
258 try self.genNav(pt, nav, air.*, liveness.*.?, true);
259 }
260
261 pub fn updateNav(
262 self: *Object,
263 pt: Zcu.PerThread,
264 nav: InternPool.Nav.Index,
265 ) !void {
266 try self.genNav(pt, nav, undefined, undefined, false);
267 }
268
269 /// Fetch or allocate a result id for nav index. This function also marks the nav as alive.
270 /// Note: Function does not actually generate the nav, it just allocates an index.
271 pub fn resolveNav(self: *Object, zcu: *Zcu, nav_index: InternPool.Nav.Index) !SpvModule.Decl.Index {
272 const ip = &zcu.intern_pool;
273 const entry = try self.nav_link.getOrPut(self.gpa, nav_index);
274 if (!entry.found_existing) {
275 const nav = ip.getNav(nav_index);
276 // TODO: Extern fn?
277 const kind: SpvModule.Decl.Kind = if (ip.isFunctionType(nav.typeOf(ip)))
278 .func
279 else switch (nav.getAddrspace()) {
280 .generic => .invocation_global,
281 else => .global,
282 };
283
284 entry.value_ptr.* = try self.spv.allocDecl(kind);
285 }
286
287 return entry.value_ptr.*;
288 }
289};
290
291/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
292const NavGen = struct {
293 /// A general-purpose allocator that can be used for any allocations for this NavGen.
294 gpa: Allocator,
295
296 /// The object that this decl is generated into.
297 object: *Object,
298
299 /// The Zig module that we are generating decls for.
300 pt: Zcu.PerThread,
301
302 /// The SPIR-V module that instructions should be emitted into.
303 /// This is the same as `self.object.spv`, repeated here for brevity.
304 spv: *SpvModule,
305
306 /// The decl we are currently generating code for.
307 owner_nav: InternPool.Nav.Index,
308
309 /// The intermediate code of the declaration we are currently generating. Note: If
310 /// the declaration is not a function, this value will be undefined!
311 air: Air,
312
313 /// The liveness analysis of the intermediate code for the declaration we are currently generating.
314 /// Note: If the declaration is not a function, this value will be undefined!
315 liveness: Air.Liveness,
316
317 /// An array of function argument result-ids. Each index corresponds with the
318 /// function argument of the same index.
319 args: std.ArrayListUnmanaged(Id) = .empty,
320
321 /// A counter to keep track of how many `arg` instructions we've seen yet.
322 next_arg_index: u32 = 0,
323
324 /// A map keeping track of which instruction generated which result-id.
325 inst_results: InstMap = .empty,
326
327 /// A map that maps AIR intern pool indices to SPIR-V result-ids.
328 /// See `Object.intern_map`.
329 intern_map: *InternMap,
330
331 /// Module's pointer types, see `Object.ptr_types`.
332 ptr_types: *PtrTypeMap,
333
334 /// This field keeps track of the current state wrt structured or unstructured control flow.
335 control_flow: ControlFlow,
336
337 /// The label of the SPIR-V block we are currently generating.
338 current_block_label: Id,
339
340 /// The code (prologue and body) for the function we are currently generating code for.
341 func: SpvModule.Fn = .{},
342
343 /// The base offset of the current decl, which is what `dbg_stmt` is relative to.
344 base_line: u32,
345
346 /// If `gen` returned `Error.CodegenFail`, this contains an explanatory message.
347 /// Memory is owned by `module.gpa`.
348 error_msg: ?*Zcu.ErrorMsg = null,
349
350 /// Possible errors the `genDecl` function may return.
351 const Error = error{ CodegenFail, OutOfMemory };
352
353 /// This structure is used to return information about a type typically used for
354 /// arithmetic operations. These types may either be integers, floats, or a vector
355 /// of these. If the type is a scalar, 'inner type' refers to the
356 /// scalar type. Otherwise, if its a vector, it refers to the vector's element type.
357 const ArithmeticTypeInfo = struct {
358 /// A classification of the inner type.
359 const Class = enum {
360 /// A boolean.
361 bool,
362
363 /// A regular, **native**, integer.
364 /// This is only returned when the backend supports this int as a native type (when
365 /// the relevant capability is enabled).
366 integer,
367
368 /// A regular float. These are all required to be natively supported. Floating points
369 /// for which the relevant capability is not enabled are not emulated.
370 float,
371
372 /// An integer of a 'strange' size (which' bit size is not the same as its backing
373 /// type. **Note**: this may **also** include power-of-2 integers for which the
374 /// relevant capability is not enabled), but still within the limits of the largest
375 /// natively supported integer type.
376 strange_integer,
377
378 /// An integer with more bits than the largest natively supported integer type.
379 composite_integer,
380 };
381
382 /// The number of bits in the inner type.
383 /// This is the actual number of bits of the type, not the size of the backing integer.
384 bits: u16,
385
386 /// The number of bits required to store the type.
387 /// For `integer` and `float`, this is equal to `bits`.
388 /// For `strange_integer` and `bool` this is the size of the backing integer.
389 /// For `composite_integer` this is the elements count.
390 backing_bits: u16,
391
392 /// Null if this type is a scalar, or the length
393 /// of the vector otherwise.
394 vector_len: ?u32,
395
396 /// Whether the inner type is signed. Only relevant for integers.
397 signedness: std.builtin.Signedness,
398
399 /// A classification of the inner type. These scenarios
400 /// will all have to be handled slightly different.
401 class: Class,
402 };
403
404 /// Data can be lowered into in two basic representations: indirect, which is when
405 /// a type is stored in memory, and direct, which is how a type is stored when its
406 /// a direct SPIR-V value.
407 const Repr = enum {
408 /// A SPIR-V value as it would be used in operations.
409 direct,
410 /// A SPIR-V value as it is stored in memory.
411 indirect,
412 };
413
414 /// Free resources owned by the NavGen.
415 pub fn deinit(self: *NavGen) void {
416 self.args.deinit(self.gpa);
417 self.inst_results.deinit(self.gpa);
418 self.control_flow.deinit(self.gpa);
419 self.func.deinit(self.gpa);
420 }
421
422 pub fn fail(self: *NavGen, comptime format: []const u8, args: anytype) Error {
423 @branchHint(.cold);
424 const zcu = self.pt.zcu;
425 const src_loc = zcu.navSrcLoc(self.owner_nav);
426 assert(self.error_msg == null);
427 self.error_msg = try Zcu.ErrorMsg.create(zcu.gpa, src_loc, format, args);
428 return error.CodegenFail;
429 }
430
431 pub fn todo(self: *NavGen, comptime format: []const u8, args: anytype) Error {
432 return self.fail("TODO (SPIR-V): " ++ format, args);
433 }
434
435 /// This imports the "default" extended instruction set for the target
436 /// For OpenCL, OpenCL.std.100. For Vulkan and OpenGL, GLSL.std.450.
437 fn importExtendedSet(self: *NavGen) !Id {
438 const target = self.spv.target;
439 return switch (target.os.tag) {
440 .opencl, .amdhsa => try self.spv.importInstructionSet(.open_cl_std),
441 .vulkan, .opengl => try self.spv.importInstructionSet(.glsl_std_450),
442 else => unreachable,
443 };
444 }
445
446 /// Fetch the result-id for a previously generated instruction or constant.
447 fn resolve(self: *NavGen, inst: Air.Inst.Ref) !Id {
448 const pt = self.pt;
449 const zcu = pt.zcu;
450 if (try self.air.value(inst, pt)) |val| {
451 const ty = self.typeOf(inst);
452 if (ty.zigTypeTag(zcu) == .@"fn") {
453 const fn_nav = switch (zcu.intern_pool.indexToKey(val.ip_index)) {
454 .@"extern" => |@"extern"| @"extern".owner_nav,
455 .func => |func| func.owner_nav,
456 else => unreachable,
457 };
458 const spv_decl_index = try self.object.resolveNav(zcu, fn_nav);
459 try self.func.decl_deps.put(self.spv.gpa, spv_decl_index, {});
460 return self.spv.declPtr(spv_decl_index).result_id;
461 }
462
463 return try self.constant(ty, val, .direct);
464 }
465 const index = inst.toIndex().?;
466 return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage.
467 }
468
469 fn resolveUav(self: *NavGen, val: InternPool.Index) !Id {
470 // TODO: This cannot be a function at this point, but it should probably be handled anyway.
471
472 const zcu = self.pt.zcu;
473 const ty = Type.fromInterned(zcu.intern_pool.typeOf(val));
474 const decl_ptr_ty_id = try self.ptrType(ty, self.spvStorageClass(.generic), .indirect);
475
476 const spv_decl_index = blk: {
477 const entry = try self.object.uav_link.getOrPut(self.object.gpa, .{ val, .function });
478 if (entry.found_existing) {
479 try self.addFunctionDep(entry.value_ptr.*, .function);
480
481 const result_id = self.spv.declPtr(entry.value_ptr.*).result_id;
482 return try self.castToGeneric(decl_ptr_ty_id, result_id);
483 }
484
485 const spv_decl_index = try self.spv.allocDecl(.invocation_global);
486 try self.addFunctionDep(spv_decl_index, .function);
487 entry.value_ptr.* = spv_decl_index;
488 break :blk spv_decl_index;
489 };
490
491 // TODO: At some point we will be able to generate this all constant here, but then all of
492 // constant() will need to be implemented such that it doesn't generate any at-runtime code.
493 // NOTE: Because this is a global, we really only want to initialize it once. Therefore the
494 // constant lowering of this value will need to be deferred to an initializer similar to
495 // other globals.
496
497 const result_id = self.spv.declPtr(spv_decl_index).result_id;
498
499 {
500 // Save the current state so that we can temporarily generate into a different function.
501 // TODO: This should probably be made a little more robust.
502 const func = self.func;
503 defer self.func = func;
504 const block_label = self.current_block_label;
505 defer self.current_block_label = block_label;
506
507 self.func = .{};
508 defer self.func.deinit(self.gpa);
509
510 const initializer_proto_ty_id = try self.functionType(Type.void, &.{});
511
512 const initializer_id = self.spv.allocId();
513 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{
514 .id_result_type = try self.resolveType(Type.void, .direct),
515 .id_result = initializer_id,
516 .function_control = .{},
517 .function_type = initializer_proto_ty_id,
518 });
519 const root_block_id = self.spv.allocId();
520 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
521 .id_result = root_block_id,
522 });
523 self.current_block_label = root_block_id;
524
525 const val_id = try self.constant(ty, Value.fromInterned(val), .indirect);
526 try self.func.body.emit(self.spv.gpa, .OpStore, .{
527 .pointer = result_id,
528 .object = val_id,
529 });
530
531 try self.func.body.emit(self.spv.gpa, .OpReturn, {});
532 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
533 try self.spv.addFunction(spv_decl_index, self.func);
534
535 try self.spv.debugNameFmt(initializer_id, "initializer of __anon_{d}", .{@intFromEnum(val)});
536
537 const fn_decl_ptr_ty_id = try self.ptrType(ty, .function, .indirect);
538 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpExtInst, .{
539 .id_result_type = fn_decl_ptr_ty_id,
540 .id_result = result_id,
541 .set = try self.spv.importInstructionSet(.zig),
542 .instruction = .{ .inst = 0 }, // TODO: Put this definition somewhere...
543 .id_ref_4 = &.{initializer_id},
544 });
545 }
546
547 return try self.castToGeneric(decl_ptr_ty_id, result_id);
548 }
549
550 fn addFunctionDep(self: *NavGen, decl_index: SpvModule.Decl.Index, storage_class: StorageClass) !void {
551 if (self.spv.version.minor < 4) {
552 // Before version 1.4, the interface’s storage classes are limited to the Input and Output
553 if (storage_class == .input or storage_class == .output) {
554 try self.func.decl_deps.put(self.spv.gpa, decl_index, {});
555 }
556 } else {
557 try self.func.decl_deps.put(self.spv.gpa, decl_index, {});
558 }
559 }
560
561 fn castToGeneric(self: *NavGen, type_id: Id, ptr_id: Id) !Id {
562 if (self.spv.hasFeature(.generic_pointer)) {
563 const result_id = self.spv.allocId();
564 try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{
565 .id_result_type = type_id,
566 .id_result = result_id,
567 .pointer = ptr_id,
568 });
569 return result_id;
570 }
571
572 return ptr_id;
573 }
574
575 /// Start a new SPIR-V block, Emits the label of the new block, and stores which
576 /// block we are currently generating.
577 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to
578 /// keep track of the previous block.
579 fn beginSpvBlock(self: *NavGen, label: Id) !void {
580 try self.func.body.emit(self.spv.gpa, .OpLabel, .{ .id_result = label });
581 self.current_block_label = label;
582 }
583
584 /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need
585 /// to emulate them in other instructions/types. This function returns, given an integer bit width (signed or unsigned, sign
586 /// included), the width of the underlying type which represents it, given the enabled features for the current target.
587 /// If the result is `null`, the largest type the target platform supports natively is not able to perform computations using
588 /// that size. In this case, multiple elements of the largest type should be used.
589 /// The backing type will be chosen as the smallest supported integer larger or equal to it in number of bits.
590 /// The result is valid to be used with OpTypeInt.
591 /// TODO: Should the result of this function be cached?
592 fn backingIntBits(self: *NavGen, bits: u16) struct { u16, bool } {
593 // The backend will never be asked to compiler a 0-bit integer, so we won't have to handle those in this function.
594 assert(bits != 0);
595
596 if (self.spv.hasFeature(.arbitrary_precision_integers) and bits <= 32) {
597 return .{ bits, false };
598 }
599
600 // We require Int8 and Int16 capabilities and benefit Int64 when available.
601 // 32-bit integers are always supported (see spec, 2.16.1, Data rules).
602 const ints = [_]struct { bits: u16, enabled: bool }{
603 .{ .bits = 8, .enabled = true },
604 .{ .bits = 16, .enabled = true },
605 .{ .bits = 32, .enabled = true },
606 .{
607 .bits = 64,
608 .enabled = self.spv.hasFeature(.int64) or self.spv.target.cpu.arch == .spirv64,
609 },
610 };
611
612 for (ints) |int| {
613 if (bits <= int.bits and int.enabled) return .{ int.bits, false };
614 }
615
616 // Big int
617 return .{ std.mem.alignForward(u16, bits, big_int_bits), true };
618 }
619
620 /// Return the amount of bits in the largest supported integer type. This is either 32 (always supported), or 64 (if
621 /// the Int64 capability is enabled).
622 /// Note: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits).
623 /// In theory that could also be used, but since the spec says that it only guarantees support up to 32-bit ints there
624 /// is no way of knowing whether those are actually supported.
625 /// TODO: Maybe this should be cached?
626 fn largestSupportedIntBits(self: *NavGen) u16 {
627 if (self.spv.hasFeature(.int64) or self.spv.target.cpu.arch == .spirv64) {
628 return 64;
629 }
630 return 32;
631 }
632
633 fn arithmeticTypeInfo(self: *NavGen, ty: Type) ArithmeticTypeInfo {
634 const zcu = self.pt.zcu;
635 const target = self.spv.target;
636 var scalar_ty = ty.scalarType(zcu);
637 if (scalar_ty.zigTypeTag(zcu) == .@"enum") {
638 scalar_ty = scalar_ty.intTagType(zcu);
639 }
640 const vector_len = if (ty.isVector(zcu)) ty.vectorLen(zcu) else null;
641 return switch (scalar_ty.zigTypeTag(zcu)) {
642 .bool => .{
643 .bits = 1, // Doesn't matter for this class.
644 .backing_bits = self.backingIntBits(1).@"0",
645 .vector_len = vector_len,
646 .signedness = .unsigned, // Technically, but doesn't matter for this class.
647 .class = .bool,
648 },
649 .float => .{
650 .bits = scalar_ty.floatBits(target),
651 .backing_bits = scalar_ty.floatBits(target), // TODO: F80?
652 .vector_len = vector_len,
653 .signedness = .signed, // Technically, but doesn't matter for this class.
654 .class = .float,
655 },
656 .int => blk: {
657 const int_info = scalar_ty.intInfo(zcu);
658 // TODO: Maybe it's useful to also return this value.
659 const backing_bits, const big_int = self.backingIntBits(int_info.bits);
660 break :blk .{
661 .bits = int_info.bits,
662 .backing_bits = backing_bits,
663 .vector_len = vector_len,
664 .signedness = int_info.signedness,
665 .class = class: {
666 if (big_int) break :class .composite_integer;
667 break :class if (backing_bits == int_info.bits) .integer else .strange_integer;
668 },
669 };
670 },
671 .@"enum" => unreachable,
672 .vector => unreachable,
673 else => unreachable, // Unhandled arithmetic type
674 };
675 }
676
677 /// Checks whether the type can be directly translated to SPIR-V vectors
678 fn isSpvVector(self: *NavGen, ty: Type) bool {
679 const zcu = self.pt.zcu;
680 if (ty.zigTypeTag(zcu) != .vector) return false;
681
682 // TODO: This check must be expanded for types that can be represented
683 // as integers (enums / packed structs?) and types that are represented
684 // by multiple SPIR-V values.
685 const scalar_ty = ty.scalarType(zcu);
686 switch (scalar_ty.zigTypeTag(zcu)) {
687 .bool,
688 .int,
689 .float,
690 => {},
691 else => return false,
692 }
693
694 const elem_ty = ty.childType(zcu);
695 const len = ty.vectorLen(zcu);
696
697 if (elem_ty.isNumeric(zcu) or elem_ty.toIntern() == .bool_type) {
698 if (len > 1 and len <= 4) return true;
699 if (self.spv.hasFeature(.vector16)) return (len == 8 or len == 16);
700 }
701
702 return false;
703 }
704
705 /// Emits a bool constant in a particular representation.
706 fn constBool(self: *NavGen, value: bool, repr: Repr) !Id {
707 return switch (repr) {
708 .indirect => self.constInt(Type.u1, @intFromBool(value)),
709 .direct => self.spv.constBool(value),
710 };
711 }
712
713 /// Emits an integer constant.
714 /// This function, unlike SpvModule.constInt, takes care to bitcast
715 /// the value to an unsigned int first for Kernels.
716 fn constInt(self: *NavGen, ty: Type, value: anytype) !Id {
717 const zcu = self.pt.zcu;
718 const scalar_ty = ty.scalarType(zcu);
719 const int_info = scalar_ty.intInfo(zcu);
720 // Use backing bits so that negatives are sign extended
721 const backing_bits, const big_int = self.backingIntBits(int_info.bits);
722 assert(backing_bits != 0); // u0 is comptime
723
724 const result_ty_id = try self.resolveType(scalar_ty, .indirect);
725 const signedness: Signedness = switch (@typeInfo(@TypeOf(value))) {
726 .int => |int| int.signedness,
727 .comptime_int => if (value < 0) .signed else .unsigned,
728 else => unreachable,
729 };
730 if (@sizeOf(@TypeOf(value)) >= 4 and big_int) {
731 const value64: u64 = switch (signedness) {
732 .signed => @bitCast(@as(i64, @intCast(value))),
733 .unsigned => @as(u64, @intCast(value)),
734 };
735 assert(backing_bits == 64);
736 return self.constructComposite(result_ty_id, &.{
737 try self.constInt(.u32, @as(u32, @truncate(value64))),
738 try self.constInt(.u32, @as(u32, @truncate(value64 << 32))),
739 });
740 }
741
742 const final_value: spec.LiteralContextDependentNumber = switch (self.spv.target.os.tag) {
743 .opencl, .amdhsa => blk: {
744 const value64: u64 = switch (signedness) {
745 .signed => @bitCast(@as(i64, @intCast(value))),
746 .unsigned => @as(u64, @intCast(value)),
747 };
748
749 // Manually truncate the value to the right amount of bits.
750 const truncated_value = if (backing_bits == 64)
751 value64
752 else
753 value64 & (@as(u64, 1) << @intCast(backing_bits)) - 1;
754
755 break :blk switch (backing_bits) {
756 1...32 => .{ .uint32 = @truncate(truncated_value) },
757 33...64 => .{ .uint64 = truncated_value },
758 else => unreachable,
759 };
760 },
761 else => switch (backing_bits) {
762 1...32 => if (signedness == .signed) .{ .int32 = @intCast(value) } else .{ .uint32 = @intCast(value) },
763 33...64 => if (signedness == .signed) .{ .int64 = value } else .{ .uint64 = value },
764 else => unreachable,
765 },
766 };
767
768 const result_id = try self.spv.constant(result_ty_id, final_value);
769
770 if (!ty.isVector(zcu)) return result_id;
771 return self.constructCompositeSplat(ty, result_id);
772 }
773
774 pub fn constructComposite(self: *NavGen, result_ty_id: Id, constituents: []const Id) !Id {
775 const result_id = self.spv.allocId();
776 try self.func.body.emit(self.gpa, .OpCompositeConstruct, .{
777 .id_result_type = result_ty_id,
778 .id_result = result_id,
779 .constituents = constituents,
780 });
781 return result_id;
782 }
783
784 /// Construct a composite at runtime with all lanes set to the same value.
785 /// ty must be an aggregate type.
786 fn constructCompositeSplat(self: *NavGen, ty: Type, constituent: Id) !Id {
787 const zcu = self.pt.zcu;
788 const n: usize = @intCast(ty.arrayLen(zcu));
789
790 const constituents = try self.gpa.alloc(Id, n);
791 defer self.gpa.free(constituents);
792 @memset(constituents, constituent);
793
794 const result_ty_id = try self.resolveType(ty, .direct);
795 return self.constructComposite(result_ty_id, constituents);
796 }
797
798 /// This function generates a load for a constant in direct (ie, non-memory) representation.
799 /// When the constant is simple, it can be generated directly using OpConstant instructions.
800 /// When the constant is more complicated however, it needs to be constructed using multiple values. This
801 /// is done by emitting a sequence of instructions that initialize the value.
802 //
803 /// This function should only be called during function code generation.
804 fn constant(self: *NavGen, ty: Type, val: Value, repr: Repr) !Id {
805 // Note: Using intern_map can only be used with constants that DO NOT generate any runtime code!!
806 // Ideally that should be all constants in the future, or it should be cleaned up somehow. For
807 // now, only use the intern_map on case-by-case basis by breaking to :cache.
808 if (self.intern_map.get(.{ val.toIntern(), repr })) |id| {
809 return id;
810 }
811
812 const pt = self.pt;
813 const zcu = pt.zcu;
814 const target = self.spv.target;
815 const result_ty_id = try self.resolveType(ty, repr);
816 const ip = &zcu.intern_pool;
817
818 log.debug("lowering constant: ty = {f}, val = {f}, key = {s}", .{ ty.fmt(pt), val.fmtValue(pt), @tagName(ip.indexToKey(val.toIntern())) });
819 if (val.isUndefDeep(zcu)) {
820 return self.spv.constUndef(result_ty_id);
821 }
822
823 const cacheable_id = cache: {
824 switch (ip.indexToKey(val.toIntern())) {
825 .int_type,
826 .ptr_type,
827 .array_type,
828 .vector_type,
829 .opt_type,
830 .anyframe_type,
831 .error_union_type,
832 .simple_type,
833 .struct_type,
834 .tuple_type,
835 .union_type,
836 .opaque_type,
837 .enum_type,
838 .func_type,
839 .error_set_type,
840 .inferred_error_set_type,
841 => unreachable, // types, not values
842
843 .undef => unreachable, // handled above
844
845 .variable,
846 .@"extern",
847 .func,
848 .enum_literal,
849 .empty_enum_value,
850 => unreachable, // non-runtime values
851
852 .simple_value => |simple_value| switch (simple_value) {
853 .undefined,
854 .void,
855 .null,
856 .empty_tuple,
857 .@"unreachable",
858 => unreachable, // non-runtime values
859
860 .false, .true => break :cache try self.constBool(val.toBool(), repr),
861 },
862 .int => {
863 if (ty.isSignedInt(zcu)) {
864 break :cache try self.constInt(ty, val.toSignedInt(zcu));
865 } else {
866 break :cache try self.constInt(ty, val.toUnsignedInt(zcu));
867 }
868 },
869 .float => {
870 const lit: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) {
871 16 => .{ .uint32 = @as(u16, @bitCast(val.toFloat(f16, zcu))) },
872 32 => .{ .float32 = val.toFloat(f32, zcu) },
873 64 => .{ .float64 = val.toFloat(f64, zcu) },
874 80, 128 => unreachable, // TODO
875 else => unreachable,
876 };
877 break :cache try self.spv.constant(result_ty_id, lit);
878 },
879 .err => |err| {
880 const value = try pt.getErrorValue(err.name);
881 break :cache try self.constInt(ty, value);
882 },
883 .error_union => |error_union| {
884 // TODO: Error unions may be constructed with constant instructions if the payload type
885 // allows it. For now, just generate it here regardless.
886 const err_int_ty = try pt.errorIntType();
887 const err_ty = switch (error_union.val) {
888 .err_name => ty.errorUnionSet(zcu),
889 .payload => err_int_ty,
890 };
891 const err_val = switch (error_union.val) {
892 .err_name => |err_name| Value.fromInterned(try pt.intern(.{ .err = .{
893 .ty = ty.errorUnionSet(zcu).toIntern(),
894 .name = err_name,
895 } })),
896 .payload => try pt.intValue(err_int_ty, 0),
897 };
898 const payload_ty = ty.errorUnionPayload(zcu);
899 const eu_layout = self.errorUnionLayout(payload_ty);
900 if (!eu_layout.payload_has_bits) {
901 // We use the error type directly as the type.
902 break :cache try self.constant(err_ty, err_val, .indirect);
903 }
904
905 const payload_val = Value.fromInterned(switch (error_union.val) {
906 .err_name => try pt.intern(.{ .undef = payload_ty.toIntern() }),
907 .payload => |payload| payload,
908 });
909
910 var constituents: [2]Id = undefined;
911 var types: [2]Type = undefined;
912 if (eu_layout.error_first) {
913 constituents[0] = try self.constant(err_ty, err_val, .indirect);
914 constituents[1] = try self.constant(payload_ty, payload_val, .indirect);
915 types = .{ err_ty, payload_ty };
916 } else {
917 constituents[0] = try self.constant(payload_ty, payload_val, .indirect);
918 constituents[1] = try self.constant(err_ty, err_val, .indirect);
919 types = .{ payload_ty, err_ty };
920 }
921
922 const comp_ty_id = try self.resolveType(ty, .direct);
923 return try self.constructComposite(comp_ty_id, &constituents);
924 },
925 .enum_tag => {
926 const int_val = try val.intFromEnum(ty, pt);
927 const int_ty = ty.intTagType(zcu);
928 break :cache try self.constant(int_ty, int_val, repr);
929 },
930 .ptr => return self.constantPtr(val),
931 .slice => |slice| {
932 const ptr_id = try self.constantPtr(Value.fromInterned(slice.ptr));
933 const len_id = try self.constant(Type.usize, Value.fromInterned(slice.len), .indirect);
934 const comp_ty_id = try self.resolveType(ty, .direct);
935 return try self.constructComposite(comp_ty_id, &.{ ptr_id, len_id });
936 },
937 .opt => {
938 const payload_ty = ty.optionalChild(zcu);
939 const maybe_payload_val = val.optionalValue(zcu);
940
941 if (!payload_ty.hasRuntimeBits(zcu)) {
942 break :cache try self.constBool(maybe_payload_val != null, .indirect);
943 } else if (ty.optionalReprIsPayload(zcu)) {
944 // Optional representation is a nullable pointer or slice.
945 if (maybe_payload_val) |payload_val| {
946 return try self.constant(payload_ty, payload_val, .indirect);
947 } else {
948 break :cache try self.spv.constNull(result_ty_id);
949 }
950 }
951
952 // Optional representation is a structure.
953 // { Payload, Bool }
954
955 const has_pl_id = try self.constBool(maybe_payload_val != null, .indirect);
956 const payload_id = if (maybe_payload_val) |payload_val|
957 try self.constant(payload_ty, payload_val, .indirect)
958 else
959 try self.spv.constUndef(try self.resolveType(payload_ty, .indirect));
960
961 const comp_ty_id = try self.resolveType(ty, .direct);
962 return try self.constructComposite(comp_ty_id, &.{ payload_id, has_pl_id });
963 },
964 .aggregate => |aggregate| switch (ip.indexToKey(ty.ip_index)) {
965 inline .array_type, .vector_type => |array_type, tag| {
966 const elem_ty = Type.fromInterned(array_type.child);
967
968 const constituents = try self.gpa.alloc(Id, @intCast(ty.arrayLenIncludingSentinel(zcu)));
969 defer self.gpa.free(constituents);
970
971 const child_repr: Repr = switch (tag) {
972 .array_type => .indirect,
973 .vector_type => .direct,
974 else => unreachable,
975 };
976
977 switch (aggregate.storage) {
978 .bytes => |bytes| {
979 // TODO: This is really space inefficient, perhaps there is a better
980 // way to do it?
981 for (constituents, bytes.toSlice(constituents.len, ip)) |*constituent, byte| {
982 constituent.* = try self.constInt(elem_ty, byte);
983 }
984 },
985 .elems => |elems| {
986 for (constituents, elems) |*constituent, elem| {
987 constituent.* = try self.constant(elem_ty, Value.fromInterned(elem), child_repr);
988 }
989 },
990 .repeated_elem => |elem| {
991 @memset(constituents, try self.constant(elem_ty, Value.fromInterned(elem), child_repr));
992 },
993 }
994
995 const comp_ty_id = try self.resolveType(ty, .direct);
996 return self.constructComposite(comp_ty_id, constituents);
997 },
998 .struct_type => {
999 const struct_type = zcu.typeToStruct(ty).?;
1000
1001 if (struct_type.layout == .@"packed") {
1002 // TODO: composite int
1003 // TODO: endianness
1004 const bits: u16 = @intCast(ty.bitSize(zcu));
1005 const bytes = std.mem.alignForward(u16, self.backingIntBits(bits).@"0", 8) / 8;
1006 var limbs: [8]u8 = undefined;
1007 @memset(&limbs, 0);
1008 val.writeToPackedMemory(ty, pt, limbs[0..bytes], 0) catch unreachable;
1009 const backing_ty = Type.fromInterned(struct_type.backingIntTypeUnordered(ip));
1010 return try self.constInt(backing_ty, @as(u64, @bitCast(limbs)));
1011 }
1012
1013 var types = std.ArrayList(Type).init(self.gpa);
1014 defer types.deinit();
1015
1016 var constituents = std.ArrayList(Id).init(self.gpa);
1017 defer constituents.deinit();
1018
1019 var it = struct_type.iterateRuntimeOrder(ip);
1020 while (it.next()) |field_index| {
1021 const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[field_index]);
1022 if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
1023 // This is a zero-bit field - we only needed it for the alignment.
1024 continue;
1025 }
1026
1027 // TODO: Padding?
1028 const field_val = try val.fieldValue(pt, field_index);
1029 const field_id = try self.constant(field_ty, field_val, .indirect);
1030
1031 try types.append(field_ty);
1032 try constituents.append(field_id);
1033 }
1034
1035 const comp_ty_id = try self.resolveType(ty, .direct);
1036 return try self.constructComposite(comp_ty_id, constituents.items);
1037 },
1038 .tuple_type => return self.todo("implement tuple types", .{}),
1039 else => unreachable,
1040 },
1041 .un => |un| {
1042 if (un.tag == .none) {
1043 assert(ty.containerLayout(zcu) == .@"packed"); // TODO
1044 const int_ty = try pt.intType(.unsigned, @intCast(ty.bitSize(zcu)));
1045 return try self.constant(int_ty, Value.fromInterned(un.val), .direct);
1046 }
1047 const active_field = ty.unionTagFieldIndex(Value.fromInterned(un.tag), zcu).?;
1048 const union_obj = zcu.typeToUnion(ty).?;
1049 const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[active_field]);
1050 const payload = if (field_ty.hasRuntimeBitsIgnoreComptime(zcu))
1051 try self.constant(field_ty, Value.fromInterned(un.val), .direct)
1052 else
1053 null;
1054 return try self.unionInit(ty, active_field, payload);
1055 },
1056 .memoized_call => unreachable,
1057 }
1058 };
1059
1060 try self.intern_map.putNoClobber(self.gpa, .{ val.toIntern(), repr }, cacheable_id);
1061
1062 return cacheable_id;
1063 }
1064
1065 fn constantPtr(self: *NavGen, ptr_val: Value) Error!Id {
1066 const pt = self.pt;
1067
1068 if (ptr_val.isUndef(pt.zcu)) {
1069 const result_ty = ptr_val.typeOf(pt.zcu);
1070 const result_ty_id = try self.resolveType(result_ty, .direct);
1071 return self.spv.constUndef(result_ty_id);
1072 }
1073
1074 var arena = std.heap.ArenaAllocator.init(self.gpa);
1075 defer arena.deinit();
1076
1077 const derivation = try ptr_val.pointerDerivation(arena.allocator(), pt);
1078 return self.derivePtr(derivation);
1079 }
1080
1081 fn derivePtr(self: *NavGen, derivation: Value.PointerDeriveStep) Error!Id {
1082 const pt = self.pt;
1083 const zcu = pt.zcu;
1084 switch (derivation) {
1085 .comptime_alloc_ptr, .comptime_field_ptr => unreachable,
1086 .int => |int| {
1087 const result_ty_id = try self.resolveType(int.ptr_ty, .direct);
1088 // TODO: This can probably be an OpSpecConstantOp Bitcast, but
1089 // that is not implemented by Mesa yet. Therefore, just generate it
1090 // as a runtime operation.
1091 const result_ptr_id = self.spv.allocId();
1092 try self.func.body.emit(self.spv.gpa, .OpConvertUToPtr, .{
1093 .id_result_type = result_ty_id,
1094 .id_result = result_ptr_id,
1095 .integer_value = try self.constant(Type.usize, try pt.intValue(Type.usize, int.addr), .direct),
1096 });
1097 return result_ptr_id;
1098 },
1099 .nav_ptr => |nav| {
1100 const result_ptr_ty = try pt.navPtrType(nav);
1101 return self.constantNavRef(result_ptr_ty, nav);
1102 },
1103 .uav_ptr => |uav| {
1104 const result_ptr_ty = Type.fromInterned(uav.orig_ty);
1105 return self.constantUavRef(result_ptr_ty, uav);
1106 },
1107 .eu_payload_ptr => @panic("TODO"),
1108 .opt_payload_ptr => @panic("TODO"),
1109 .field_ptr => |field| {
1110 const parent_ptr_id = try self.derivePtr(field.parent.*);
1111 const parent_ptr_ty = try field.parent.ptrType(pt);
1112 return self.structFieldPtr(field.result_ptr_ty, parent_ptr_ty, parent_ptr_id, field.field_idx);
1113 },
1114 .elem_ptr => |elem| {
1115 const parent_ptr_id = try self.derivePtr(elem.parent.*);
1116 const parent_ptr_ty = try elem.parent.ptrType(pt);
1117 const index_id = try self.constInt(Type.usize, elem.elem_idx);
1118 return self.ptrElemPtr(parent_ptr_ty, parent_ptr_id, index_id);
1119 },
1120 .offset_and_cast => |oac| {
1121 const parent_ptr_id = try self.derivePtr(oac.parent.*);
1122 const parent_ptr_ty = try oac.parent.ptrType(pt);
1123 const result_ty_id = try self.resolveType(oac.new_ptr_ty, .direct);
1124 const child_size = oac.new_ptr_ty.childType(zcu).abiSize(zcu);
1125
1126 if (parent_ptr_ty.childType(zcu).isVector(zcu) and oac.byte_offset % child_size == 0) {
1127 // Vector element ptr accesses are derived as offset_and_cast.
1128 // We can just use OpAccessChain.
1129 return self.accessChain(
1130 result_ty_id,
1131 parent_ptr_id,
1132 &.{@intCast(@divExact(oac.byte_offset, child_size))},
1133 );
1134 }
1135
1136 if (oac.byte_offset == 0) {
1137 // Allow changing the pointer type child only to restructure arrays.
1138 // e.g. [3][2]T to T is fine, as is [2]T -> [2][1]T.
1139 const result_ptr_id = self.spv.allocId();
1140 try self.func.body.emit(self.spv.gpa, .OpBitcast, .{
1141 .id_result_type = result_ty_id,
1142 .id_result = result_ptr_id,
1143 .operand = parent_ptr_id,
1144 });
1145 return result_ptr_id;
1146 }
1147
1148 return self.fail("cannot perform pointer cast: '{f}' to '{f}'", .{
1149 parent_ptr_ty.fmt(pt),
1150 oac.new_ptr_ty.fmt(pt),
1151 });
1152 },
1153 }
1154 }
1155
1156 fn constantUavRef(
1157 self: *NavGen,
1158 ty: Type,
1159 uav: InternPool.Key.Ptr.BaseAddr.Uav,
1160 ) !Id {
1161 // TODO: Merge this function with constantDeclRef.
1162
1163 const pt = self.pt;
1164 const zcu = pt.zcu;
1165 const ip = &zcu.intern_pool;
1166 const ty_id = try self.resolveType(ty, .direct);
1167 const uav_ty = Type.fromInterned(ip.typeOf(uav.val));
1168
1169 switch (ip.indexToKey(uav.val)) {
1170 .func => unreachable, // TODO
1171 .@"extern" => assert(!ip.isFunctionType(uav_ty.toIntern())),
1172 else => {},
1173 }
1174
1175 // const is_fn_body = decl_ty.zigTypeTag(zcu) == .@"fn";
1176 if (!uav_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) {
1177 // Pointer to nothing - return undefined
1178 return self.spv.constUndef(ty_id);
1179 }
1180
1181 // Uav refs are always generic.
1182 assert(ty.ptrAddressSpace(zcu) == .generic);
1183 const decl_ptr_ty_id = try self.ptrType(uav_ty, .generic, .indirect);
1184 const ptr_id = try self.resolveUav(uav.val);
1185
1186 if (decl_ptr_ty_id != ty_id) {
1187 // Differing pointer types, insert a cast.
1188 const casted_ptr_id = self.spv.allocId();
1189 try self.func.body.emit(self.spv.gpa, .OpBitcast, .{
1190 .id_result_type = ty_id,
1191 .id_result = casted_ptr_id,
1192 .operand = ptr_id,
1193 });
1194 return casted_ptr_id;
1195 } else {
1196 return ptr_id;
1197 }
1198 }
1199
1200 fn constantNavRef(self: *NavGen, ty: Type, nav_index: InternPool.Nav.Index) !Id {
1201 const pt = self.pt;
1202 const zcu = pt.zcu;
1203 const ip = &zcu.intern_pool;
1204 const ty_id = try self.resolveType(ty, .direct);
1205 const nav = ip.getNav(nav_index);
1206 const nav_ty: Type = .fromInterned(nav.typeOf(ip));
1207
1208 switch (nav.status) {
1209 .unresolved => unreachable,
1210 .type_resolved => {}, // this is not a function or extern
1211 .fully_resolved => |r| switch (ip.indexToKey(r.val)) {
1212 .func => {
1213 // TODO: Properly lower function pointers. For now we are going to hack around it and
1214 // just generate an empty pointer. Function pointers are represented by a pointer to usize.
1215 return try self.spv.constUndef(ty_id);
1216 },
1217 .@"extern" => if (ip.isFunctionType(nav_ty.toIntern())) @panic("TODO"),
1218 else => {},
1219 },
1220 }
1221
1222 if (!nav_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) {
1223 // Pointer to nothing - return undefined.
1224 return self.spv.constUndef(ty_id);
1225 }
1226
1227 const spv_decl_index = try self.object.resolveNav(zcu, nav_index);
1228 const spv_decl = self.spv.declPtr(spv_decl_index);
1229
1230 const decl_id = switch (spv_decl.kind) {
1231 .func => unreachable, // TODO: Is this possible?
1232 .global, .invocation_global => spv_decl.result_id,
1233 };
1234
1235 const storage_class = self.spvStorageClass(nav.getAddrspace());
1236 try self.addFunctionDep(spv_decl_index, storage_class);
1237
1238 const decl_ptr_ty_id = try self.ptrType(nav_ty, storage_class, .indirect);
1239
1240 const ptr_id = switch (storage_class) {
1241 .generic => try self.castToGeneric(decl_ptr_ty_id, decl_id),
1242 else => decl_id,
1243 };
1244
1245 if (decl_ptr_ty_id != ty_id) {
1246 // Differing pointer types, insert a cast.
1247 const casted_ptr_id = self.spv.allocId();
1248 try self.func.body.emit(self.spv.gpa, .OpBitcast, .{
1249 .id_result_type = ty_id,
1250 .id_result = casted_ptr_id,
1251 .operand = ptr_id,
1252 });
1253 return casted_ptr_id;
1254 } else {
1255 return ptr_id;
1256 }
1257 }
1258
1259 // Turn a Zig type's name into a cache reference.
1260 fn resolveTypeName(self: *NavGen, ty: Type) ![]const u8 {
1261 var aw: std.io.Writer.Allocating = .init(self.gpa);
1262 defer aw.deinit();
1263 ty.print(&aw.writer, self.pt) catch |err| switch (err) {
1264 error.WriteFailed => return error.OutOfMemory,
1265 };
1266 return try aw.toOwnedSlice();
1267 }
1268
1269 /// Create an integer type suitable for storing at least 'bits' bits.
1270 /// The integer type that is returned by this function is the type that is used to perform
1271 /// actual operations (as well as store) a Zig type of a particular number of bits. To create
1272 /// a type with an exact size, use SpvModule.intType.
1273 fn intType(self: *NavGen, signedness: std.builtin.Signedness, bits: u16) !Id {
1274 const backing_bits, const big_int = self.backingIntBits(bits);
1275 if (big_int) {
1276 if (backing_bits > 64) {
1277 return self.fail("composite integers larger than 64bit aren't supported", .{});
1278 }
1279 const int_ty = try self.resolveType(.u32, .direct);
1280 return self.arrayType(backing_bits / big_int_bits, int_ty);
1281 }
1282
1283 return switch (self.spv.target.os.tag) {
1284 // Kernel only supports unsigned ints.
1285 .opencl, .amdhsa => return self.spv.intType(.unsigned, backing_bits),
1286 else => self.spv.intType(signedness, backing_bits),
1287 };
1288 }
1289
1290 fn arrayType(self: *NavGen, len: u32, child_ty: Id) !Id {
1291 const len_id = try self.constInt(Type.u32, len);
1292 return self.spv.arrayType(len_id, child_ty);
1293 }
1294
1295 fn ptrType(self: *NavGen, child_ty: Type, storage_class: StorageClass, child_repr: Repr) !Id {
1296 const zcu = self.pt.zcu;
1297 const ip = &zcu.intern_pool;
1298 const key = .{ child_ty.toIntern(), storage_class, child_repr };
1299 const entry = try self.ptr_types.getOrPut(self.gpa, key);
1300 if (entry.found_existing) {
1301 const fwd_id = entry.value_ptr.ty_id;
1302 if (!entry.value_ptr.fwd_emitted) {
1303 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypeForwardPointer, .{
1304 .pointer_type = fwd_id,
1305 .storage_class = storage_class,
1306 });
1307 entry.value_ptr.fwd_emitted = true;
1308 }
1309 return fwd_id;
1310 }
1311
1312 const result_id = self.spv.allocId();
1313 entry.value_ptr.* = .{
1314 .ty_id = result_id,
1315 .fwd_emitted = false,
1316 };
1317
1318 const child_ty_id = try self.resolveType(child_ty, child_repr);
1319
1320 switch (self.spv.target.os.tag) {
1321 .vulkan, .opengl => {
1322 if (child_ty.zigTypeTag(zcu) == .@"struct") {
1323 switch (storage_class) {
1324 .uniform, .push_constant => try self.spv.decorate(child_ty_id, .block),
1325 else => {},
1326 }
1327 }
1328
1329 switch (ip.indexToKey(child_ty.toIntern())) {
1330 .func_type, .opaque_type => {},
1331 else => {
1332 try self.spv.decorate(result_id, .{ .array_stride = .{ .array_stride = @intCast(child_ty.abiSize(zcu)) } });
1333 },
1334 }
1335 },
1336 else => {},
1337 }
1338
1339 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{
1340 .id_result = result_id,
1341 .storage_class = storage_class,
1342 .type = child_ty_id,
1343 });
1344
1345 self.ptr_types.getPtr(key).?.fwd_emitted = true;
1346
1347 return result_id;
1348 }
1349
1350 fn functionType(self: *NavGen, return_ty: Type, param_types: []const Type) !Id {
1351 const return_ty_id = try self.resolveFnReturnType(return_ty);
1352 const param_ids = try self.gpa.alloc(Id, param_types.len);
1353 defer self.gpa.free(param_ids);
1354
1355 for (param_types, param_ids) |param_ty, *param_id| {
1356 param_id.* = try self.resolveType(param_ty, .direct);
1357 }
1358
1359 return self.spv.functionType(return_ty_id, param_ids);
1360 }
1361
1362 /// Generate a union type. Union types are always generated with the
1363 /// most aligned field active. If the tag alignment is greater
1364 /// than that of the payload, a regular union (non-packed, with both tag and
1365 /// payload), will be generated as follows:
1366 /// struct {
1367 /// tag: TagType,
1368 /// payload: MostAlignedFieldType,
1369 /// payload_padding: [payload_size - @sizeOf(MostAlignedFieldType)]u8,
1370 /// padding: [padding_size]u8,
1371 /// }
1372 /// If the payload alignment is greater than that of the tag:
1373 /// struct {
1374 /// payload: MostAlignedFieldType,
1375 /// payload_padding: [payload_size - @sizeOf(MostAlignedFieldType)]u8,
1376 /// tag: TagType,
1377 /// padding: [padding_size]u8,
1378 /// }
1379 /// If any of the fields' size is 0, it will be omitted.
1380 fn resolveUnionType(self: *NavGen, ty: Type) !Id {
1381 const zcu = self.pt.zcu;
1382 const ip = &zcu.intern_pool;
1383 const union_obj = zcu.typeToUnion(ty).?;
1384
1385 if (union_obj.flagsUnordered(ip).layout == .@"packed") {
1386 return try self.intType(.unsigned, @intCast(ty.bitSize(zcu)));
1387 }
1388
1389 const layout = self.unionLayout(ty);
1390 if (!layout.has_payload) {
1391 // No payload, so represent this as just the tag type.
1392 return try self.resolveType(Type.fromInterned(union_obj.enum_tag_ty), .indirect);
1393 }
1394
1395 var member_types: [4]Id = undefined;
1396 var member_names: [4][]const u8 = undefined;
1397
1398 const u8_ty_id = try self.resolveType(Type.u8, .direct);
1399
1400 if (layout.tag_size != 0) {
1401 const tag_ty_id = try self.resolveType(Type.fromInterned(union_obj.enum_tag_ty), .indirect);
1402 member_types[layout.tag_index] = tag_ty_id;
1403 member_names[layout.tag_index] = "(tag)";
1404 }
1405
1406 if (layout.payload_size != 0) {
1407 const payload_ty_id = try self.resolveType(layout.payload_ty, .indirect);
1408 member_types[layout.payload_index] = payload_ty_id;
1409 member_names[layout.payload_index] = "(payload)";
1410 }
1411
1412 if (layout.payload_padding_size != 0) {
1413 const payload_padding_ty_id = try self.arrayType(@intCast(layout.payload_padding_size), u8_ty_id);
1414 member_types[layout.payload_padding_index] = payload_padding_ty_id;
1415 member_names[layout.payload_padding_index] = "(payload padding)";
1416 }
1417
1418 if (layout.padding_size != 0) {
1419 const padding_ty_id = try self.arrayType(@intCast(layout.padding_size), u8_ty_id);
1420 member_types[layout.padding_index] = padding_ty_id;
1421 member_names[layout.padding_index] = "(padding)";
1422 }
1423
1424 const result_id = self.spv.allocId();
1425 try self.spv.structType(result_id, member_types[0..layout.total_fields], member_names[0..layout.total_fields]);
1426
1427 const type_name = try self.resolveTypeName(ty);
1428 defer self.gpa.free(type_name);
1429 try self.spv.debugName(result_id, type_name);
1430
1431 return result_id;
1432 }
1433
1434 fn resolveFnReturnType(self: *NavGen, ret_ty: Type) !Id {
1435 const zcu = self.pt.zcu;
1436 if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
1437 // If the return type is an error set or an error union, then we make this
1438 // anyerror return type instead, so that it can be coerced into a function
1439 // pointer type which has anyerror as the return type.
1440 if (ret_ty.isError(zcu)) {
1441 return self.resolveType(Type.anyerror, .direct);
1442 } else {
1443 return self.resolveType(Type.void, .direct);
1444 }
1445 }
1446
1447 return try self.resolveType(ret_ty, .direct);
1448 }
1449
1450 /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
1451 fn resolveType(self: *NavGen, ty: Type, repr: Repr) Error!Id {
1452 if (self.intern_map.get(.{ ty.toIntern(), repr })) |id| {
1453 return id;
1454 }
1455
1456 const id = try self.resolveTypeInner(ty, repr);
1457 try self.intern_map.put(self.gpa, .{ ty.toIntern(), repr }, id);
1458 return id;
1459 }
1460
1461 fn resolveTypeInner(self: *NavGen, ty: Type, repr: Repr) Error!Id {
1462 const pt = self.pt;
1463 const zcu = pt.zcu;
1464 const ip = &zcu.intern_pool;
1465 log.debug("resolveType: ty = {f}", .{ty.fmt(pt)});
1466 const target = self.spv.target;
1467
1468 const section = &self.spv.sections.types_globals_constants;
1469
1470 switch (ty.zigTypeTag(zcu)) {
1471 .noreturn => {
1472 assert(repr == .direct);
1473 return try self.spv.voidType();
1474 },
1475 .void => switch (repr) {
1476 .direct => {
1477 return try self.spv.voidType();
1478 },
1479 // Pointers to void
1480 .indirect => {
1481 const result_id = self.spv.allocId();
1482 try section.emit(self.spv.gpa, .OpTypeOpaque, .{
1483 .id_result = result_id,
1484 .literal_string = "void",
1485 });
1486 return result_id;
1487 },
1488 },
1489 .bool => switch (repr) {
1490 .direct => return try self.spv.boolType(),
1491 .indirect => return try self.resolveType(Type.u1, .indirect),
1492 },
1493 .int => {
1494 const int_info = ty.intInfo(zcu);
1495 if (int_info.bits == 0) {
1496 // Some times, the backend will be asked to generate a pointer to i0. OpTypeInt
1497 // with 0 bits is invalid, so return an opaque type in this case.
1498 assert(repr == .indirect);
1499 const result_id = self.spv.allocId();
1500 try section.emit(self.spv.gpa, .OpTypeOpaque, .{
1501 .id_result = result_id,
1502 .literal_string = "u0",
1503 });
1504 return result_id;
1505 }
1506 return try self.intType(int_info.signedness, int_info.bits);
1507 },
1508 .@"enum" => {
1509 const tag_ty = ty.intTagType(zcu);
1510 return try self.resolveType(tag_ty, repr);
1511 },
1512 .float => {
1513 // We can (and want) not really emulate floating points with other floating point types like with the integer types,
1514 // so if the float is not supported, just return an error.
1515 const bits = ty.floatBits(target);
1516 const supported = switch (bits) {
1517 16 => self.spv.hasFeature(.float16),
1518 // 32-bit floats are always supported (see spec, 2.16.1, Data rules).
1519 32 => true,
1520 64 => self.spv.hasFeature(.float64),
1521 else => false,
1522 };
1523
1524 if (!supported) {
1525 return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
1526 }
1527
1528 return try self.spv.floatType(bits);
1529 },
1530 .array => {
1531 const elem_ty = ty.childType(zcu);
1532 const elem_ty_id = try self.resolveType(elem_ty, .indirect);
1533 const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel(zcu)) orelse {
1534 return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel(zcu)});
1535 };
1536
1537 if (!elem_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
1538 // The size of the array would be 0, but that is not allowed in SPIR-V.
1539 // This path can be reached when the backend is asked to generate a pointer to
1540 // an array of some zero-bit type. This should always be an indirect path.
1541 assert(repr == .indirect);
1542
1543 // We cannot use the child type here, so just use an opaque type.
1544 const result_id = self.spv.allocId();
1545 try section.emit(self.spv.gpa, .OpTypeOpaque, .{
1546 .id_result = result_id,
1547 .literal_string = "zero-sized array",
1548 });
1549 return result_id;
1550 } else if (total_len == 0) {
1551 // The size of the array would be 0, but that is not allowed in SPIR-V.
1552 // This path can be reached for example when there is a slicing of a pointer
1553 // that produces a zero-length array. In all cases where this type can be generated,
1554 // this should be an indirect path.
1555 assert(repr == .indirect);
1556
1557 // In this case, we have an array of a non-zero sized type. In this case,
1558 // generate an array of 1 element instead, so that ptr_elem_ptr instructions
1559 // can be lowered to ptrAccessChain instead of manually performing the math.
1560 return try self.arrayType(1, elem_ty_id);
1561 } else {
1562 const result_id = try self.arrayType(total_len, elem_ty_id);
1563 switch (self.spv.target.os.tag) {
1564 .vulkan, .opengl => {
1565 try self.spv.decorate(result_id, .{ .array_stride = .{
1566 .array_stride = @intCast(elem_ty.abiSize(zcu)),
1567 } });
1568 },
1569 else => {},
1570 }
1571 return result_id;
1572 }
1573 },
1574 .vector => {
1575 const elem_ty = ty.childType(zcu);
1576 const elem_ty_id = try self.resolveType(elem_ty, repr);
1577 const len = ty.vectorLen(zcu);
1578
1579 if (self.isSpvVector(ty)) {
1580 return try self.spv.vectorType(len, elem_ty_id);
1581 } else {
1582 return try self.arrayType(len, elem_ty_id);
1583 }
1584 },
1585 .@"fn" => switch (repr) {
1586 .direct => {
1587 const fn_info = zcu.typeToFunc(ty).?;
1588
1589 comptime assert(zig_call_abi_ver == 3);
1590 switch (fn_info.cc) {
1591 .auto,
1592 .spirv_kernel,
1593 .spirv_fragment,
1594 .spirv_vertex,
1595 .spirv_device,
1596 => {},
1597 else => unreachable,
1598 }
1599
1600 // Guaranteed by callConvSupportsVarArgs, there are no SPIR-V CCs which support
1601 // varargs.
1602 assert(!fn_info.is_var_args);
1603
1604 // Note: Logic is different from functionType().
1605 const param_ty_ids = try self.gpa.alloc(Id, fn_info.param_types.len);
1606 defer self.gpa.free(param_ty_ids);
1607 var param_index: usize = 0;
1608 for (fn_info.param_types.get(ip)) |param_ty_index| {
1609 const param_ty = Type.fromInterned(param_ty_index);
1610 if (!param_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;
1611
1612 param_ty_ids[param_index] = try self.resolveType(param_ty, .direct);
1613 param_index += 1;
1614 }
1615
1616 const return_ty_id = try self.resolveFnReturnType(Type.fromInterned(fn_info.return_type));
1617
1618 const result_id = self.spv.allocId();
1619 try section.emit(self.spv.gpa, .OpTypeFunction, .{
1620 .id_result = result_id,
1621 .return_type = return_ty_id,
1622 .id_ref_2 = param_ty_ids[0..param_index],
1623 });
1624
1625 return result_id;
1626 },
1627 .indirect => {
1628 // TODO: Represent function pointers properly.
1629 // For now, just use an usize type.
1630 return try self.resolveType(Type.usize, .indirect);
1631 },
1632 },
1633 .pointer => {
1634 const ptr_info = ty.ptrInfo(zcu);
1635
1636 const child_ty = Type.fromInterned(ptr_info.child);
1637 const storage_class = self.spvStorageClass(ptr_info.flags.address_space);
1638 const ptr_ty_id = try self.ptrType(child_ty, storage_class, .indirect);
1639
1640 if (ptr_info.flags.size != .slice) {
1641 return ptr_ty_id;
1642 }
1643
1644 const size_ty_id = try self.resolveType(Type.usize, .direct);
1645 const result_id = self.spv.allocId();
1646 try self.spv.structType(
1647 result_id,
1648 &.{ ptr_ty_id, size_ty_id },
1649 &.{ "ptr", "len" },
1650 );
1651 return result_id;
1652 },
1653 .@"struct" => {
1654 const struct_type = switch (ip.indexToKey(ty.toIntern())) {
1655 .tuple_type => |tuple| {
1656 const member_types = try self.gpa.alloc(Id, tuple.values.len);
1657 defer self.gpa.free(member_types);
1658
1659 var member_index: usize = 0;
1660 for (tuple.types.get(ip), tuple.values.get(ip)) |field_ty, field_val| {
1661 if (field_val != .none or !Type.fromInterned(field_ty).hasRuntimeBits(zcu)) continue;
1662
1663 member_types[member_index] = try self.resolveType(Type.fromInterned(field_ty), .indirect);
1664 member_index += 1;
1665 }
1666
1667 const result_id = self.spv.allocId();
1668 try self.spv.structType(result_id, member_types[0..member_index], null);
1669
1670 const type_name = try self.resolveTypeName(ty);
1671 defer self.gpa.free(type_name);
1672 try self.spv.debugName(result_id, type_name);
1673
1674 return result_id;
1675 },
1676 .struct_type => ip.loadStructType(ty.toIntern()),
1677 else => unreachable,
1678 };
1679
1680 if (struct_type.layout == .@"packed") {
1681 return try self.resolveType(Type.fromInterned(struct_type.backingIntTypeUnordered(ip)), .direct);
1682 }
1683
1684 var member_types = std.ArrayList(Id).init(self.gpa);
1685 defer member_types.deinit();
1686
1687 var member_names = std.ArrayList([]const u8).init(self.gpa);
1688 defer member_names.deinit();
1689
1690 var index: u32 = 0;
1691 var it = struct_type.iterateRuntimeOrder(ip);
1692 const result_id = self.spv.allocId();
1693 while (it.next()) |field_index| {
1694 const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[field_index]);
1695 if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
1696 // This is a zero-bit field - we only needed it for the alignment.
1697 continue;
1698 }
1699
1700 switch (self.spv.target.os.tag) {
1701 .vulkan, .opengl => {
1702 try self.spv.decorateMember(result_id, index, .{ .offset = .{
1703 .byte_offset = @intCast(ty.structFieldOffset(field_index, zcu)),
1704 } });
1705 },
1706 else => {},
1707 }
1708
1709 const field_name = struct_type.fieldName(ip, field_index).unwrap() orelse
1710 try ip.getOrPutStringFmt(zcu.gpa, pt.tid, "{d}", .{field_index}, .no_embedded_nulls);
1711 try member_types.append(try self.resolveType(field_ty, .indirect));
1712 try member_names.append(field_name.toSlice(ip));
1713
1714 index += 1;
1715 }
1716
1717 try self.spv.structType(result_id, member_types.items, member_names.items);
1718
1719 const type_name = try self.resolveTypeName(ty);
1720 defer self.gpa.free(type_name);
1721 try self.spv.debugName(result_id, type_name);
1722
1723 return result_id;
1724 },
1725 .optional => {
1726 const payload_ty = ty.optionalChild(zcu);
1727 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
1728 // Just use a bool.
1729 // Note: Always generate the bool with indirect format, to save on some sanity
1730 // Perform the conversion to a direct bool when the field is extracted.
1731 return try self.resolveType(Type.bool, .indirect);
1732 }
1733
1734 const payload_ty_id = try self.resolveType(payload_ty, .indirect);
1735 if (ty.optionalReprIsPayload(zcu)) {
1736 // Optional is actually a pointer or a slice.
1737 return payload_ty_id;
1738 }
1739
1740 const bool_ty_id = try self.resolveType(Type.bool, .indirect);
1741
1742 const result_id = self.spv.allocId();
1743 try self.spv.structType(
1744 result_id,
1745 &.{ payload_ty_id, bool_ty_id },
1746 &.{ "payload", "valid" },
1747 );
1748 return result_id;
1749 },
1750 .@"union" => return try self.resolveUnionType(ty),
1751 .error_set => {
1752 const err_int_ty = try pt.errorIntType();
1753 return try self.resolveType(err_int_ty, repr);
1754 },
1755 .error_union => {
1756 const payload_ty = ty.errorUnionPayload(zcu);
1757 const error_ty_id = try self.resolveType(Type.anyerror, .indirect);
1758
1759 const eu_layout = self.errorUnionLayout(payload_ty);
1760 if (!eu_layout.payload_has_bits) {
1761 return error_ty_id;
1762 }
1763
1764 const payload_ty_id = try self.resolveType(payload_ty, .indirect);
1765
1766 var member_types: [2]Id = undefined;
1767 var member_names: [2][]const u8 = undefined;
1768 if (eu_layout.error_first) {
1769 // Put the error first
1770 member_types = .{ error_ty_id, payload_ty_id };
1771 member_names = .{ "error", "payload" };
1772 // TODO: ABI padding?
1773 } else {
1774 // Put the payload first.
1775 member_types = .{ payload_ty_id, error_ty_id };
1776 member_names = .{ "payload", "error" };
1777 // TODO: ABI padding?
1778 }
1779
1780 const result_id = self.spv.allocId();
1781 try self.spv.structType(result_id, &member_types, &member_names);
1782 return result_id;
1783 },
1784 .@"opaque" => {
1785 const type_name = try self.resolveTypeName(ty);
1786 defer self.gpa.free(type_name);
1787
1788 const result_id = self.spv.allocId();
1789 try section.emit(self.spv.gpa, .OpTypeOpaque, .{
1790 .id_result = result_id,
1791 .literal_string = type_name,
1792 });
1793 return result_id;
1794 },
1795
1796 .null,
1797 .undefined,
1798 .enum_literal,
1799 .comptime_float,
1800 .comptime_int,
1801 .type,
1802 => unreachable, // Must be comptime.
1803
1804 .frame, .@"anyframe" => unreachable, // TODO
1805 }
1806 }
1807
1808 fn spvStorageClass(self: *NavGen, as: std.builtin.AddressSpace) StorageClass {
1809 return switch (as) {
1810 .generic => if (self.spv.hasFeature(.generic_pointer)) .generic else .function,
1811 .global => switch (self.spv.target.os.tag) {
1812 .opencl, .amdhsa => .cross_workgroup,
1813 else => .storage_buffer,
1814 },
1815 .push_constant => {
1816 return .push_constant;
1817 },
1818 .output => {
1819 return .output;
1820 },
1821 .uniform => {
1822 return .uniform;
1823 },
1824 .storage_buffer => {
1825 return .storage_buffer;
1826 },
1827 .physical_storage_buffer => {
1828 return .physical_storage_buffer;
1829 },
1830 .constant => .uniform_constant,
1831 .shared => .workgroup,
1832 .local => .function,
1833 .input => .input,
1834 .gs,
1835 .fs,
1836 .ss,
1837 .param,
1838 .flash,
1839 .flash1,
1840 .flash2,
1841 .flash3,
1842 .flash4,
1843 .flash5,
1844 .cog,
1845 .lut,
1846 .hub,
1847 => unreachable,
1848 };
1849 }
1850
1851 const ErrorUnionLayout = struct {
1852 payload_has_bits: bool,
1853 error_first: bool,
1854
1855 fn errorFieldIndex(self: @This()) u32 {
1856 assert(self.payload_has_bits);
1857 return if (self.error_first) 0 else 1;
1858 }
1859
1860 fn payloadFieldIndex(self: @This()) u32 {
1861 assert(self.payload_has_bits);
1862 return if (self.error_first) 1 else 0;
1863 }
1864 };
1865
1866 fn errorUnionLayout(self: *NavGen, payload_ty: Type) ErrorUnionLayout {
1867 const pt = self.pt;
1868 const zcu = pt.zcu;
1869
1870 const error_align = Type.anyerror.abiAlignment(zcu);
1871 const payload_align = payload_ty.abiAlignment(zcu);
1872
1873 const error_first = error_align.compare(.gt, payload_align);
1874 return .{
1875 .payload_has_bits = payload_ty.hasRuntimeBitsIgnoreComptime(zcu),
1876 .error_first = error_first,
1877 };
1878 }
1879
1880 const UnionLayout = struct {
1881 /// If false, this union is represented
1882 /// by only an integer of the tag type.
1883 has_payload: bool,
1884 tag_size: u32,
1885 tag_index: u32,
1886 /// Note: This is the size of the payload type itself, NOT the size of the ENTIRE payload.
1887 /// Use `has_payload` instead!!
1888 payload_ty: Type,
1889 payload_size: u32,
1890 payload_index: u32,
1891 payload_padding_size: u32,
1892 payload_padding_index: u32,
1893 padding_size: u32,
1894 padding_index: u32,
1895 total_fields: u32,
1896 };
1897
1898 fn unionLayout(self: *NavGen, ty: Type) UnionLayout {
1899 const pt = self.pt;
1900 const zcu = pt.zcu;
1901 const ip = &zcu.intern_pool;
1902 const layout = ty.unionGetLayout(zcu);
1903 const union_obj = zcu.typeToUnion(ty).?;
1904
1905 var union_layout = UnionLayout{
1906 .has_payload = layout.payload_size != 0,
1907 .tag_size = @intCast(layout.tag_size),
1908 .tag_index = undefined,
1909 .payload_ty = undefined,
1910 .payload_size = undefined,
1911 .payload_index = undefined,
1912 .payload_padding_size = undefined,
1913 .payload_padding_index = undefined,
1914 .padding_size = @intCast(layout.padding),
1915 .padding_index = undefined,
1916 .total_fields = undefined,
1917 };
1918
1919 if (union_layout.has_payload) {
1920 const most_aligned_field = layout.most_aligned_field;
1921 const most_aligned_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[most_aligned_field]);
1922 union_layout.payload_ty = most_aligned_field_ty;
1923 union_layout.payload_size = @intCast(most_aligned_field_ty.abiSize(zcu));
1924 } else {
1925 union_layout.payload_size = 0;
1926 }
1927
1928 union_layout.payload_padding_size = @intCast(layout.payload_size - union_layout.payload_size);
1929
1930 const tag_first = layout.tag_align.compare(.gte, layout.payload_align);
1931 var field_index: u32 = 0;
1932
1933 if (union_layout.tag_size != 0 and tag_first) {
1934 union_layout.tag_index = field_index;
1935 field_index += 1;
1936 }
1937
1938 if (union_layout.payload_size != 0) {
1939 union_layout.payload_index = field_index;
1940 field_index += 1;
1941 }
1942
1943 if (union_layout.payload_padding_size != 0) {
1944 union_layout.payload_padding_index = field_index;
1945 field_index += 1;
1946 }
1947
1948 if (union_layout.tag_size != 0 and !tag_first) {
1949 union_layout.tag_index = field_index;
1950 field_index += 1;
1951 }
1952
1953 if (union_layout.padding_size != 0) {
1954 union_layout.padding_index = field_index;
1955 field_index += 1;
1956 }
1957
1958 union_layout.total_fields = field_index;
1959
1960 return union_layout;
1961 }
1962
1963 /// This structure represents a "temporary" value: Something we are currently
1964 /// operating on. It typically lives no longer than the function that
1965 /// implements a particular AIR operation. These are used to easier
1966 /// implement vectorizable operations (see Vectorization and the build*
1967 /// functions), and typically are only used for vectors of primitive types.
1968 const Temporary = struct {
1969 /// The type of the temporary. This is here mainly
1970 /// for easier bookkeeping. Because we will never really
1971 /// store Temporaries, they only cause extra stack space,
1972 /// therefore no real storage is wasted.
1973 ty: Type,
1974 /// The value that this temporary holds. This is not necessarily
1975 /// a value that is actually usable, or a single value: It is virtual
1976 /// until materialize() is called, at which point is turned into
1977 /// the usual SPIR-V representation of `self.ty`.
1978 value: Temporary.Value,
1979
1980 const Value = union(enum) {
1981 singleton: Id,
1982 exploded_vector: IdRange,
1983 };
1984
1985 fn init(ty: Type, singleton: Id) Temporary {
1986 return .{ .ty = ty, .value = .{ .singleton = singleton } };
1987 }
1988
1989 fn materialize(self: Temporary, ng: *NavGen) !Id {
1990 const zcu = ng.pt.zcu;
1991 switch (self.value) {
1992 .singleton => |id| return id,
1993 .exploded_vector => |range| {
1994 assert(self.ty.isVector(zcu));
1995 assert(self.ty.vectorLen(zcu) == range.len);
1996 const constituents = try ng.gpa.alloc(Id, range.len);
1997 defer ng.gpa.free(constituents);
1998 for (constituents, 0..range.len) |*id, i| {
1999 id.* = range.at(i);
2000 }
2001 const result_ty_id = try ng.resolveType(self.ty, .direct);
2002 return ng.constructComposite(result_ty_id, constituents);
2003 },
2004 }
2005 }
2006
2007 fn vectorization(self: Temporary, ng: *NavGen) Vectorization {
2008 return Vectorization.fromType(self.ty, ng);
2009 }
2010
2011 fn pun(self: Temporary, new_ty: Type) Temporary {
2012 return .{
2013 .ty = new_ty,
2014 .value = self.value,
2015 };
2016 }
2017
2018 /// 'Explode' a temporary into separate elements. This turns a vector
2019 /// into a bag of elements.
2020 fn explode(self: Temporary, ng: *NavGen) !IdRange {
2021 const zcu = ng.pt.zcu;
2022
2023 // If the value is a scalar, then this is a no-op.
2024 if (!self.ty.isVector(zcu)) {
2025 return switch (self.value) {
2026 .singleton => |id| .{ .base = @intFromEnum(id), .len = 1 },
2027 .exploded_vector => |range| range,
2028 };
2029 }
2030
2031 const ty_id = try ng.resolveType(self.ty.scalarType(zcu), .direct);
2032 const n = self.ty.vectorLen(zcu);
2033 const results = ng.spv.allocIds(n);
2034
2035 const id = switch (self.value) {
2036 .singleton => |id| id,
2037 .exploded_vector => |range| return range,
2038 };
2039
2040 for (0..n) |i| {
2041 const indexes = [_]u32{@intCast(i)};
2042 try ng.func.body.emit(ng.spv.gpa, .OpCompositeExtract, .{
2043 .id_result_type = ty_id,
2044 .id_result = results.at(i),
2045 .composite = id,
2046 .indexes = &indexes,
2047 });
2048 }
2049
2050 return results;
2051 }
2052 };
2053
2054 /// Initialize a `Temporary` from an AIR value.
2055 fn temporary(self: *NavGen, inst: Air.Inst.Ref) !Temporary {
2056 return .{
2057 .ty = self.typeOf(inst),
2058 .value = .{ .singleton = try self.resolve(inst) },
2059 };
2060 }
2061
2062 /// This union describes how a particular operation should be vectorized.
2063 /// That depends on the operation and number of components of the inputs.
2064 const Vectorization = union(enum) {
2065 /// This is an operation between scalars.
2066 scalar,
2067 /// This operation is unrolled into separate operations.
2068 /// Inputs may still be SPIR-V vectors, for example,
2069 /// when the operation can't be vectorized in SPIR-V.
2070 /// Value is number of components.
2071 unrolled: u32,
2072
2073 /// Derive a vectorization from a particular type
2074 fn fromType(ty: Type, ng: *NavGen) Vectorization {
2075 const zcu = ng.pt.zcu;
2076 if (!ty.isVector(zcu)) return .scalar;
2077 return .{ .unrolled = ty.vectorLen(zcu) };
2078 }
2079
2080 /// Given two vectorization methods, compute a "unification": a fallback
2081 /// that works for both, according to the following rules:
2082 /// - Scalars may broadcast
2083 /// - SPIR-V vectorized operations will unroll
2084 /// - Prefer scalar > unrolled
2085 fn unify(a: Vectorization, b: Vectorization) Vectorization {
2086 if (a == .scalar and b == .scalar) return .scalar;
2087 if (a == .unrolled or b == .unrolled) {
2088 if (a == .unrolled and b == .unrolled) assert(a.components() == b.components());
2089 if (a == .unrolled) return .{ .unrolled = a.components() };
2090 return .{ .unrolled = b.components() };
2091 }
2092 unreachable;
2093 }
2094
2095 /// Query the number of components that inputs of this operation have.
2096 /// Note: for broadcasting scalars, this returns the number of elements
2097 /// that the broadcasted vector would have.
2098 fn components(self: Vectorization) u32 {
2099 return switch (self) {
2100 .scalar => 1,
2101 .unrolled => |n| n,
2102 };
2103 }
2104
2105 /// Turns `ty` into the result-type of the entire operation.
2106 /// `ty` may be a scalar or vector, it doesn't matter.
2107 fn resultType(self: Vectorization, ng: *NavGen, ty: Type) !Type {
2108 const pt = ng.pt;
2109 const scalar_ty = ty.scalarType(pt.zcu);
2110 return switch (self) {
2111 .scalar => scalar_ty,
2112 .unrolled => |n| try pt.vectorType(.{ .len = n, .child = scalar_ty.toIntern() }),
2113 };
2114 }
2115
2116 /// Before a temporary can be used, some setup may need to be one. This function implements
2117 /// this setup, and returns a new type that holds the relevant information on how to access
2118 /// elements of the input.
2119 fn prepare(self: Vectorization, ng: *NavGen, tmp: Temporary) !PreparedOperand {
2120 const pt = ng.pt;
2121 const is_vector = tmp.ty.isVector(pt.zcu);
2122 const value: PreparedOperand.Value = switch (tmp.value) {
2123 .singleton => |id| switch (self) {
2124 .scalar => blk: {
2125 assert(!is_vector);
2126 break :blk .{ .scalar = id };
2127 },
2128 .unrolled => blk: {
2129 if (is_vector) break :blk .{ .vector_exploded = try tmp.explode(ng) };
2130 break :blk .{ .scalar_broadcast = id };
2131 },
2132 },
2133 .exploded_vector => |range| switch (self) {
2134 .scalar => unreachable,
2135 .unrolled => |n| blk: {
2136 assert(range.len == n);
2137 break :blk .{ .vector_exploded = range };
2138 },
2139 },
2140 };
2141
2142 return .{
2143 .ty = tmp.ty,
2144 .value = value,
2145 };
2146 }
2147
2148 /// Finalize the results of an operation back into a temporary. `results` is
2149 /// a list of result-ids of the operation.
2150 fn finalize(self: Vectorization, ty: Type, results: IdRange) Temporary {
2151 assert(self.components() == results.len);
2152 return .{
2153 .ty = ty,
2154 .value = switch (self) {
2155 .scalar => .{ .singleton = results.at(0) },
2156 .unrolled => .{ .exploded_vector = results },
2157 },
2158 };
2159 }
2160
2161 /// This struct represents an operand that has gone through some setup, and is
2162 /// ready to be used as part of an operation.
2163 const PreparedOperand = struct {
2164 ty: Type,
2165 value: PreparedOperand.Value,
2166
2167 /// The types of value that a prepared operand can hold internally. Depends
2168 /// on the operation and input value.
2169 const Value = union(enum) {
2170 /// A single scalar value that is used by a scalar operation.
2171 scalar: Id,
2172 /// A single scalar that is broadcasted in an unrolled operation.
2173 scalar_broadcast: Id,
2174 /// A vector represented by a consecutive list of IDs that is used in an unrolled operation.
2175 vector_exploded: IdRange,
2176 };
2177
2178 /// Query the value at a particular index of the operation. Note that
2179 /// the index is *not* the component/lane, but the index of the *operation*.
2180 fn at(self: PreparedOperand, i: usize) Id {
2181 switch (self.value) {
2182 .scalar => |id| {
2183 assert(i == 0);
2184 return id;
2185 },
2186 .scalar_broadcast => |id| return id,
2187 .vector_exploded => |range| return range.at(i),
2188 }
2189 }
2190 };
2191 };
2192
2193 /// A utility function to compute the vectorization style of
2194 /// a list of values. These values may be any of the following:
2195 /// - A `Vectorization` instance
2196 /// - A Type, in which case the vectorization is computed via `Vectorization.fromType`.
2197 /// - A Temporary, in which case the vectorization is computed via `Temporary.vectorization`.
2198 fn vectorization(self: *NavGen, args: anytype) Vectorization {
2199 var v: Vectorization = undefined;
2200 assert(args.len >= 1);
2201 inline for (args, 0..) |arg, i| {
2202 const iv: Vectorization = switch (@TypeOf(arg)) {
2203 Vectorization => arg,
2204 Type => Vectorization.fromType(arg, self),
2205 Temporary => arg.vectorization(self),
2206 else => @compileError("invalid type"),
2207 };
2208 if (i == 0) {
2209 v = iv;
2210 } else {
2211 v = v.unify(iv);
2212 }
2213 }
2214 return v;
2215 }
2216
2217 /// This function builds an OpSConvert of OpUConvert depending on the
2218 /// signedness of the types.
2219 fn buildConvert(self: *NavGen, dst_ty: Type, src: Temporary) !Temporary {
2220 const zcu = self.pt.zcu;
2221
2222 const dst_ty_id = try self.resolveType(dst_ty.scalarType(zcu), .direct);
2223 const src_ty_id = try self.resolveType(src.ty.scalarType(zcu), .direct);
2224
2225 const v = self.vectorization(.{ dst_ty, src });
2226 const result_ty = try v.resultType(self, dst_ty);
2227
2228 // We can directly compare integers, because those type-IDs are cached.
2229 if (dst_ty_id == src_ty_id) {
2230 // Nothing to do, type-pun to the right value.
2231 // Note, Caller guarantees that the types fit (or caller will normalize after),
2232 // so we don't have to normalize here.
2233 // Note, dst_ty may be a scalar type even if we expect a vector, so we have to
2234 // convert to the right type here.
2235 return src.pun(result_ty);
2236 }
2237
2238 const ops = v.components();
2239 const results = self.spv.allocIds(ops);
2240
2241 const op_result_ty = dst_ty.scalarType(zcu);
2242 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2243
2244 const opcode: Opcode = blk: {
2245 if (dst_ty.scalarType(zcu).isAnyFloat()) break :blk .OpFConvert;
2246 if (dst_ty.scalarType(zcu).isSignedInt(zcu)) break :blk .OpSConvert;
2247 break :blk .OpUConvert;
2248 };
2249
2250 const op_src = try v.prepare(self, src);
2251
2252 for (0..ops) |i| {
2253 try self.func.body.emitRaw(self.spv.gpa, opcode, 3);
2254 self.func.body.writeOperand(spec.Id, op_result_ty_id);
2255 self.func.body.writeOperand(Id, results.at(i));
2256 self.func.body.writeOperand(Id, op_src.at(i));
2257 }
2258
2259 return v.finalize(result_ty, results);
2260 }
2261
2262 fn buildFma(self: *NavGen, a: Temporary, b: Temporary, c: Temporary) !Temporary {
2263 const zcu = self.pt.zcu;
2264 const target = self.spv.target;
2265
2266 const v = self.vectorization(.{ a, b, c });
2267 const ops = v.components();
2268 const results = self.spv.allocIds(ops);
2269
2270 const op_result_ty = a.ty.scalarType(zcu);
2271 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2272 const result_ty = try v.resultType(self, a.ty);
2273
2274 const op_a = try v.prepare(self, a);
2275 const op_b = try v.prepare(self, b);
2276 const op_c = try v.prepare(self, c);
2277
2278 const set = try self.importExtendedSet();
2279
2280 // TODO: Put these numbers in some definition
2281 const instruction: u32 = switch (target.os.tag) {
2282 .opencl => 26, // fma
2283 // NOTE: Vulkan's FMA instruction does *NOT* produce the right values!
2284 // its precision guarantees do NOT match zigs and it does NOT match OpenCLs!
2285 // it needs to be emulated!
2286 .vulkan, .opengl => return self.todo("implement fma operation for {s} os", .{@tagName(target.os.tag)}),
2287 else => unreachable,
2288 };
2289
2290 for (0..ops) |i| {
2291 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2292 .id_result_type = op_result_ty_id,
2293 .id_result = results.at(i),
2294 .set = set,
2295 .instruction = .{ .inst = instruction },
2296 .id_ref_4 = &.{ op_a.at(i), op_b.at(i), op_c.at(i) },
2297 });
2298 }
2299
2300 return v.finalize(result_ty, results);
2301 }
2302
2303 fn buildSelect(self: *NavGen, condition: Temporary, lhs: Temporary, rhs: Temporary) !Temporary {
2304 const zcu = self.pt.zcu;
2305
2306 const v = self.vectorization(.{ condition, lhs, rhs });
2307 const ops = v.components();
2308 const results = self.spv.allocIds(ops);
2309
2310 const op_result_ty = lhs.ty.scalarType(zcu);
2311 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2312 const result_ty = try v.resultType(self, lhs.ty);
2313
2314 assert(condition.ty.scalarType(zcu).zigTypeTag(zcu) == .bool);
2315
2316 const cond = try v.prepare(self, condition);
2317 const object_1 = try v.prepare(self, lhs);
2318 const object_2 = try v.prepare(self, rhs);
2319
2320 for (0..ops) |i| {
2321 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
2322 .id_result_type = op_result_ty_id,
2323 .id_result = results.at(i),
2324 .condition = cond.at(i),
2325 .object_1 = object_1.at(i),
2326 .object_2 = object_2.at(i),
2327 });
2328 }
2329
2330 return v.finalize(result_ty, results);
2331 }
2332
2333 const CmpPredicate = enum {
2334 l_eq,
2335 l_ne,
2336 i_ne,
2337 i_eq,
2338 s_lt,
2339 s_gt,
2340 s_le,
2341 s_ge,
2342 u_lt,
2343 u_gt,
2344 u_le,
2345 u_ge,
2346 f_oeq,
2347 f_une,
2348 f_olt,
2349 f_ole,
2350 f_ogt,
2351 f_oge,
2352 };
2353
2354 fn buildCmp(self: *NavGen, pred: CmpPredicate, lhs: Temporary, rhs: Temporary) !Temporary {
2355 const v = self.vectorization(.{ lhs, rhs });
2356 const ops = v.components();
2357 const results = self.spv.allocIds(ops);
2358
2359 const op_result_ty: Type = .bool;
2360 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2361 const result_ty = try v.resultType(self, Type.bool);
2362
2363 const op_lhs = try v.prepare(self, lhs);
2364 const op_rhs = try v.prepare(self, rhs);
2365
2366 const opcode: Opcode = switch (pred) {
2367 .l_eq => .OpLogicalEqual,
2368 .l_ne => .OpLogicalNotEqual,
2369 .i_eq => .OpIEqual,
2370 .i_ne => .OpINotEqual,
2371 .s_lt => .OpSLessThan,
2372 .s_gt => .OpSGreaterThan,
2373 .s_le => .OpSLessThanEqual,
2374 .s_ge => .OpSGreaterThanEqual,
2375 .u_lt => .OpULessThan,
2376 .u_gt => .OpUGreaterThan,
2377 .u_le => .OpULessThanEqual,
2378 .u_ge => .OpUGreaterThanEqual,
2379 .f_oeq => .OpFOrdEqual,
2380 .f_une => .OpFUnordNotEqual,
2381 .f_olt => .OpFOrdLessThan,
2382 .f_ole => .OpFOrdLessThanEqual,
2383 .f_ogt => .OpFOrdGreaterThan,
2384 .f_oge => .OpFOrdGreaterThanEqual,
2385 };
2386
2387 for (0..ops) |i| {
2388 try self.func.body.emitRaw(self.spv.gpa, opcode, 4);
2389 self.func.body.writeOperand(spec.Id, op_result_ty_id);
2390 self.func.body.writeOperand(Id, results.at(i));
2391 self.func.body.writeOperand(Id, op_lhs.at(i));
2392 self.func.body.writeOperand(Id, op_rhs.at(i));
2393 }
2394
2395 return v.finalize(result_ty, results);
2396 }
2397
2398 const UnaryOp = enum {
2399 l_not,
2400 bit_not,
2401 i_neg,
2402 f_neg,
2403 i_abs,
2404 f_abs,
2405 clz,
2406 ctz,
2407 floor,
2408 ceil,
2409 trunc,
2410 round,
2411 sqrt,
2412 sin,
2413 cos,
2414 tan,
2415 exp,
2416 exp2,
2417 log,
2418 log2,
2419 log10,
2420 };
2421
2422 fn buildUnary(self: *NavGen, op: UnaryOp, operand: Temporary) !Temporary {
2423 const zcu = self.pt.zcu;
2424 const target = self.spv.target;
2425 const v = self.vectorization(.{operand});
2426 const ops = v.components();
2427 const results = self.spv.allocIds(ops);
2428 const op_result_ty = operand.ty.scalarType(zcu);
2429 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2430 const result_ty = try v.resultType(self, operand.ty);
2431
2432 const op_operand = try v.prepare(self, operand);
2433
2434 if (switch (op) {
2435 .l_not => .OpLogicalNot,
2436 .bit_not => .OpNot,
2437 .i_neg => .OpSNegate,
2438 .f_neg => .OpFNegate,
2439 else => @as(?Opcode, null),
2440 }) |opcode| {
2441 for (0..ops) |i| {
2442 try self.func.body.emitRaw(self.spv.gpa, opcode, 3);
2443 self.func.body.writeOperand(spec.Id, op_result_ty_id);
2444 self.func.body.writeOperand(Id, results.at(i));
2445 self.func.body.writeOperand(Id, op_operand.at(i));
2446 }
2447 } else {
2448 const set = try self.importExtendedSet();
2449 const extinst: u32 = switch (target.os.tag) {
2450 .opencl => switch (op) {
2451 .i_abs => 141, // s_abs
2452 .f_abs => 23, // fabs
2453 .clz => 151, // clz
2454 .ctz => 152, // ctz
2455 .floor => 25, // floor
2456 .ceil => 12, // ceil
2457 .trunc => 66, // trunc
2458 .round => 55, // round
2459 .sqrt => 61, // sqrt
2460 .sin => 57, // sin
2461 .cos => 14, // cos
2462 .tan => 62, // tan
2463 .exp => 19, // exp
2464 .exp2 => 20, // exp2
2465 .log => 37, // log
2466 .log2 => 38, // log2
2467 .log10 => 39, // log10
2468 else => unreachable,
2469 },
2470 // Note: We'll need to check these for floating point accuracy
2471 // Vulkan does not put tight requirements on these, for correction
2472 // we might want to emulate them at some point.
2473 .vulkan, .opengl => switch (op) {
2474 .i_abs => 5, // SAbs
2475 .f_abs => 4, // FAbs
2476 .floor => 8, // Floor
2477 .ceil => 9, // Ceil
2478 .trunc => 3, // Trunc
2479 .round => 1, // Round
2480 .clz,
2481 .ctz,
2482 .sqrt,
2483 .sin,
2484 .cos,
2485 .tan,
2486 .exp,
2487 .exp2,
2488 .log,
2489 .log2,
2490 .log10,
2491 => return self.todo("implement unary operation '{s}' for {s} os", .{ @tagName(op), @tagName(target.os.tag) }),
2492 else => unreachable,
2493 },
2494 else => unreachable,
2495 };
2496
2497 for (0..ops) |i| {
2498 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2499 .id_result_type = op_result_ty_id,
2500 .id_result = results.at(i),
2501 .set = set,
2502 .instruction = .{ .inst = extinst },
2503 .id_ref_4 = &.{op_operand.at(i)},
2504 });
2505 }
2506 }
2507
2508 return v.finalize(result_ty, results);
2509 }
2510
2511 const BinaryOp = enum {
2512 i_add,
2513 f_add,
2514 i_sub,
2515 f_sub,
2516 i_mul,
2517 f_mul,
2518 s_div,
2519 u_div,
2520 f_div,
2521 s_rem,
2522 f_rem,
2523 s_mod,
2524 u_mod,
2525 f_mod,
2526 srl,
2527 sra,
2528 sll,
2529 bit_and,
2530 bit_or,
2531 bit_xor,
2532 f_max,
2533 s_max,
2534 u_max,
2535 f_min,
2536 s_min,
2537 u_min,
2538 l_and,
2539 l_or,
2540 };
2541
2542 fn buildBinary(self: *NavGen, op: BinaryOp, lhs: Temporary, rhs: Temporary) !Temporary {
2543 const zcu = self.pt.zcu;
2544 const target = self.spv.target;
2545
2546 const v = self.vectorization(.{ lhs, rhs });
2547 const ops = v.components();
2548 const results = self.spv.allocIds(ops);
2549
2550 const op_result_ty = lhs.ty.scalarType(zcu);
2551 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2552 const result_ty = try v.resultType(self, lhs.ty);
2553
2554 const op_lhs = try v.prepare(self, lhs);
2555 const op_rhs = try v.prepare(self, rhs);
2556
2557 if (switch (op) {
2558 .i_add => .OpIAdd,
2559 .f_add => .OpFAdd,
2560 .i_sub => .OpISub,
2561 .f_sub => .OpFSub,
2562 .i_mul => .OpIMul,
2563 .f_mul => .OpFMul,
2564 .s_div => .OpSDiv,
2565 .u_div => .OpUDiv,
2566 .f_div => .OpFDiv,
2567 .s_rem => .OpSRem,
2568 .f_rem => .OpFRem,
2569 .s_mod => .OpSMod,
2570 .u_mod => .OpUMod,
2571 .f_mod => .OpFMod,
2572 .srl => .OpShiftRightLogical,
2573 .sra => .OpShiftRightArithmetic,
2574 .sll => .OpShiftLeftLogical,
2575 .bit_and => .OpBitwiseAnd,
2576 .bit_or => .OpBitwiseOr,
2577 .bit_xor => .OpBitwiseXor,
2578 .l_and => .OpLogicalAnd,
2579 .l_or => .OpLogicalOr,
2580 else => @as(?Opcode, null),
2581 }) |opcode| {
2582 for (0..ops) |i| {
2583 try self.func.body.emitRaw(self.spv.gpa, opcode, 4);
2584 self.func.body.writeOperand(spec.Id, op_result_ty_id);
2585 self.func.body.writeOperand(Id, results.at(i));
2586 self.func.body.writeOperand(Id, op_lhs.at(i));
2587 self.func.body.writeOperand(Id, op_rhs.at(i));
2588 }
2589 } else {
2590 const set = try self.importExtendedSet();
2591
2592 // TODO: Put these numbers in some definition
2593 const extinst: u32 = switch (target.os.tag) {
2594 .opencl => switch (op) {
2595 .f_max => 27, // fmax
2596 .s_max => 156, // s_max
2597 .u_max => 157, // u_max
2598 .f_min => 28, // fmin
2599 .s_min => 158, // s_min
2600 .u_min => 159, // u_min
2601 else => unreachable,
2602 },
2603 .vulkan, .opengl => switch (op) {
2604 .f_max => 40, // FMax
2605 .s_max => 42, // SMax
2606 .u_max => 41, // UMax
2607 .f_min => 37, // FMin
2608 .s_min => 39, // SMin
2609 .u_min => 38, // UMin
2610 else => unreachable,
2611 },
2612 else => unreachable,
2613 };
2614
2615 for (0..ops) |i| {
2616 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2617 .id_result_type = op_result_ty_id,
2618 .id_result = results.at(i),
2619 .set = set,
2620 .instruction = .{ .inst = extinst },
2621 .id_ref_4 = &.{ op_lhs.at(i), op_rhs.at(i) },
2622 });
2623 }
2624 }
2625
2626 return v.finalize(result_ty, results);
2627 }
2628
2629 /// This function builds an extended multiplication, either OpSMulExtended or OpUMulExtended on Vulkan,
2630 /// or OpIMul and s_mul_hi or u_mul_hi on OpenCL.
2631 fn buildWideMul(
2632 self: *NavGen,
2633 op: enum {
2634 s_mul_extended,
2635 u_mul_extended,
2636 },
2637 lhs: Temporary,
2638 rhs: Temporary,
2639 ) !struct { Temporary, Temporary } {
2640 const pt = self.pt;
2641 const zcu = pt.zcu;
2642 const target = self.spv.target;
2643 const ip = &zcu.intern_pool;
2644
2645 const v = lhs.vectorization(self).unify(rhs.vectorization(self));
2646 const ops = v.components();
2647
2648 const arith_op_ty = lhs.ty.scalarType(zcu);
2649 const arith_op_ty_id = try self.resolveType(arith_op_ty, .direct);
2650
2651 const lhs_op = try v.prepare(self, lhs);
2652 const rhs_op = try v.prepare(self, rhs);
2653
2654 const value_results = self.spv.allocIds(ops);
2655 const overflow_results = self.spv.allocIds(ops);
2656
2657 switch (target.os.tag) {
2658 .opencl => {
2659 // Currently, SPIRV-LLVM-Translator based backends cannot deal with OpSMulExtended and
2660 // OpUMulExtended. For these we will use the OpenCL s_mul_hi to compute the high-order bits
2661 // instead.
2662 const set = try self.importExtendedSet();
2663 const overflow_inst: u32 = switch (op) {
2664 .s_mul_extended => 160, // s_mul_hi
2665 .u_mul_extended => 203, // u_mul_hi
2666 };
2667
2668 for (0..ops) |i| {
2669 try self.func.body.emit(self.spv.gpa, .OpIMul, .{
2670 .id_result_type = arith_op_ty_id,
2671 .id_result = value_results.at(i),
2672 .operand_1 = lhs_op.at(i),
2673 .operand_2 = rhs_op.at(i),
2674 });
2675
2676 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2677 .id_result_type = arith_op_ty_id,
2678 .id_result = overflow_results.at(i),
2679 .set = set,
2680 .instruction = .{ .inst = overflow_inst },
2681 .id_ref_4 = &.{ lhs_op.at(i), rhs_op.at(i) },
2682 });
2683 }
2684 },
2685 .vulkan, .opengl => {
2686 // Operations return a struct{T, T}
2687 // where T is maybe vectorized.
2688 const op_result_ty: Type = .fromInterned(try ip.getTupleType(zcu.gpa, pt.tid, .{
2689 .types = &.{ arith_op_ty.toIntern(), arith_op_ty.toIntern() },
2690 .values = &.{ .none, .none },
2691 }));
2692 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2693
2694 const opcode: Opcode = switch (op) {
2695 .s_mul_extended => .OpSMulExtended,
2696 .u_mul_extended => .OpUMulExtended,
2697 };
2698
2699 for (0..ops) |i| {
2700 const op_result = self.spv.allocId();
2701
2702 try self.func.body.emitRaw(self.spv.gpa, opcode, 4);
2703 self.func.body.writeOperand(spec.Id, op_result_ty_id);
2704 self.func.body.writeOperand(Id, op_result);
2705 self.func.body.writeOperand(Id, lhs_op.at(i));
2706 self.func.body.writeOperand(Id, rhs_op.at(i));
2707
2708 // The above operation returns a struct. We might want to expand
2709 // Temporary to deal with the fact that these are structs eventually,
2710 // but for now, take the struct apart and return two separate vectors.
2711
2712 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
2713 .id_result_type = arith_op_ty_id,
2714 .id_result = value_results.at(i),
2715 .composite = op_result,
2716 .indexes = &.{0},
2717 });
2718
2719 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
2720 .id_result_type = arith_op_ty_id,
2721 .id_result = overflow_results.at(i),
2722 .composite = op_result,
2723 .indexes = &.{1},
2724 });
2725 }
2726 },
2727 else => unreachable,
2728 }
2729
2730 const result_ty = try v.resultType(self, lhs.ty);
2731 return .{
2732 v.finalize(result_ty, value_results),
2733 v.finalize(result_ty, overflow_results),
2734 };
2735 }
2736
2737 /// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure.
2738 /// In order to be able to run tests, we "temporarily" lower test kernels into separate entry-
2739 /// points. The test executor will then be able to invoke these to run the tests.
2740 /// Note that tests are lowered according to std.builtin.TestFn, which is `fn () anyerror!void`.
2741 /// (anyerror!void has the same layout as anyerror).
2742 /// Each test declaration generates a function like.
2743 /// %anyerror = OpTypeInt 0 16
2744 /// %p_invocation_globals_struct_ty = ...
2745 /// %p_anyerror = OpTypePointer CrossWorkgroup %anyerror
2746 /// %K = OpTypeFunction %void %p_invocation_globals_struct_ty %p_anyerror
2747 ///
2748 /// %test = OpFunction %void %K
2749 /// %p_invocation_globals = OpFunctionParameter p_invocation_globals_struct_ty
2750 /// %p_err = OpFunctionParameter %p_anyerror
2751 /// %lbl = OpLabel
2752 /// %result = OpFunctionCall %anyerror %func %p_invocation_globals
2753 /// OpStore %p_err %result
2754 /// OpFunctionEnd
2755 /// TODO is to also write out the error as a function call parameter, and to somehow fetch
2756 /// the name of an error in the text executor.
2757 fn generateTestEntryPoint(self: *NavGen, name: []const u8, spv_test_decl_index: SpvModule.Decl.Index) !void {
2758 const zcu = self.pt.zcu;
2759 const target = self.spv.target;
2760
2761 const anyerror_ty_id = try self.resolveType(Type.anyerror, .direct);
2762 const ptr_anyerror_ty = try self.pt.ptrType(.{
2763 .child = Type.anyerror.toIntern(),
2764 .flags = .{ .address_space = .global },
2765 });
2766 const ptr_anyerror_ty_id = try self.resolveType(ptr_anyerror_ty, .direct);
2767
2768 const spv_decl_index = try self.spv.allocDecl(.func);
2769 const kernel_id = self.spv.declPtr(spv_decl_index).result_id;
2770
2771 var decl_deps = std.ArrayList(SpvModule.Decl.Index).init(self.gpa);
2772 defer decl_deps.deinit();
2773 try decl_deps.append(spv_test_decl_index);
2774
2775 const section = &self.spv.sections.functions;
2776
2777 const p_error_id = self.spv.allocId();
2778 switch (target.os.tag) {
2779 .opencl, .amdhsa => {
2780 const kernel_proto_ty_id = try self.functionType(Type.void, &.{ptr_anyerror_ty});
2781
2782 try section.emit(self.spv.gpa, .OpFunction, .{
2783 .id_result_type = try self.resolveType(Type.void, .direct),
2784 .id_result = kernel_id,
2785 .function_control = .{},
2786 .function_type = kernel_proto_ty_id,
2787 });
2788
2789 try section.emit(self.spv.gpa, .OpFunctionParameter, .{
2790 .id_result_type = ptr_anyerror_ty_id,
2791 .id_result = p_error_id,
2792 });
2793
2794 try section.emit(self.spv.gpa, .OpLabel, .{
2795 .id_result = self.spv.allocId(),
2796 });
2797 },
2798 .vulkan, .opengl => {
2799 if (self.object.error_buffer == null) {
2800 const spv_err_decl_index = try self.spv.allocDecl(.global);
2801 try self.spv.declareDeclDeps(spv_err_decl_index, &.{});
2802
2803 const buffer_struct_ty_id = self.spv.allocId();
2804 try self.spv.structType(buffer_struct_ty_id, &.{anyerror_ty_id}, &.{"error_out"});
2805 try self.spv.decorate(buffer_struct_ty_id, .block);
2806 try self.spv.decorateMember(buffer_struct_ty_id, 0, .{ .offset = .{ .byte_offset = 0 } });
2807
2808 const ptr_buffer_struct_ty_id = self.spv.allocId();
2809 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{
2810 .id_result = ptr_buffer_struct_ty_id,
2811 .storage_class = self.spvStorageClass(.global),
2812 .type = buffer_struct_ty_id,
2813 });
2814
2815 const buffer_struct_id = self.spv.declPtr(spv_err_decl_index).result_id;
2816 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpVariable, .{
2817 .id_result_type = ptr_buffer_struct_ty_id,
2818 .id_result = buffer_struct_id,
2819 .storage_class = self.spvStorageClass(.global),
2820 });
2821 try self.spv.decorate(buffer_struct_id, .{ .descriptor_set = .{ .descriptor_set = 0 } });
2822 try self.spv.decorate(buffer_struct_id, .{ .binding = .{ .binding_point = 0 } });
2823
2824 self.object.error_buffer = spv_err_decl_index;
2825 }
2826
2827 try self.spv.sections.execution_modes.emit(self.spv.gpa, .OpExecutionMode, .{
2828 .entry_point = kernel_id,
2829 .mode = .{ .local_size = .{
2830 .x_size = 1,
2831 .y_size = 1,
2832 .z_size = 1,
2833 } },
2834 });
2835
2836 const kernel_proto_ty_id = try self.functionType(Type.void, &.{});
2837 try section.emit(self.spv.gpa, .OpFunction, .{
2838 .id_result_type = try self.resolveType(Type.void, .direct),
2839 .id_result = kernel_id,
2840 .function_control = .{},
2841 .function_type = kernel_proto_ty_id,
2842 });
2843 try section.emit(self.spv.gpa, .OpLabel, .{
2844 .id_result = self.spv.allocId(),
2845 });
2846
2847 const spv_err_decl_index = self.object.error_buffer.?;
2848 const buffer_id = self.spv.declPtr(spv_err_decl_index).result_id;
2849 try decl_deps.append(spv_err_decl_index);
2850
2851 const zero_id = try self.constInt(Type.u32, 0);
2852 try section.emit(self.spv.gpa, .OpInBoundsAccessChain, .{
2853 .id_result_type = ptr_anyerror_ty_id,
2854 .id_result = p_error_id,
2855 .base = buffer_id,
2856 .indexes = &.{zero_id},
2857 });
2858 },
2859 else => unreachable,
2860 }
2861
2862 const test_id = self.spv.declPtr(spv_test_decl_index).result_id;
2863 const error_id = self.spv.allocId();
2864 try section.emit(self.spv.gpa, .OpFunctionCall, .{
2865 .id_result_type = anyerror_ty_id,
2866 .id_result = error_id,
2867 .function = test_id,
2868 });
2869 // Note: Convert to direct not required.
2870 try section.emit(self.spv.gpa, .OpStore, .{
2871 .pointer = p_error_id,
2872 .object = error_id,
2873 .memory_access = .{
2874 .aligned = .{ .literal_integer = @intCast(Type.abiAlignment(.anyerror, zcu).toByteUnits().?) },
2875 },
2876 });
2877 try section.emit(self.spv.gpa, .OpReturn, {});
2878 try section.emit(self.spv.gpa, .OpFunctionEnd, {});
2879
2880 // Just generate a quick other name because the intel runtime crashes when the entry-
2881 // point name is the same as a different OpName.
2882 const test_name = try std.fmt.allocPrint(self.gpa, "test {s}", .{name});
2883 defer self.gpa.free(test_name);
2884
2885 const execution_mode: spec.ExecutionModel = switch (target.os.tag) {
2886 .vulkan, .opengl => .gl_compute,
2887 .opencl, .amdhsa => .kernel,
2888 else => unreachable,
2889 };
2890
2891 try self.spv.declareDeclDeps(spv_decl_index, decl_deps.items);
2892 try self.spv.declareEntryPoint(spv_decl_index, test_name, execution_mode, null);
2893 }
2894
2895 fn genNav(self: *NavGen, do_codegen: bool) !void {
2896 const pt = self.pt;
2897 const zcu = pt.zcu;
2898 const ip = &zcu.intern_pool;
2899
2900 const nav = ip.getNav(self.owner_nav);
2901 const val = zcu.navValue(self.owner_nav);
2902 const ty = val.typeOf(zcu);
2903
2904 if (!do_codegen and !ty.hasRuntimeBits(zcu)) {
2905 return;
2906 }
2907
2908 const spv_decl_index = try self.object.resolveNav(zcu, self.owner_nav);
2909 const result_id = self.spv.declPtr(spv_decl_index).result_id;
2910
2911 switch (self.spv.declPtr(spv_decl_index).kind) {
2912 .func => {
2913 const fn_info = zcu.typeToFunc(ty).?;
2914 const return_ty_id = try self.resolveFnReturnType(Type.fromInterned(fn_info.return_type));
2915
2916 const prototype_ty_id = try self.resolveType(ty, .direct);
2917 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{
2918 .id_result_type = return_ty_id,
2919 .id_result = result_id,
2920 .function_type = prototype_ty_id,
2921 // Note: the backend will never be asked to generate an inline function
2922 // (this is handled in sema), so we don't need to set function_control here.
2923 .function_control = .{},
2924 });
2925
2926 comptime assert(zig_call_abi_ver == 3);
2927 try self.args.ensureUnusedCapacity(self.gpa, fn_info.param_types.len);
2928 for (fn_info.param_types.get(ip)) |param_ty_index| {
2929 const param_ty = Type.fromInterned(param_ty_index);
2930 if (!param_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;
2931
2932 const param_type_id = try self.resolveType(param_ty, .direct);
2933 const arg_result_id = self.spv.allocId();
2934 try self.func.prologue.emit(self.spv.gpa, .OpFunctionParameter, .{
2935 .id_result_type = param_type_id,
2936 .id_result = arg_result_id,
2937 });
2938 self.args.appendAssumeCapacity(arg_result_id);
2939 }
2940
2941 // TODO: This could probably be done in a better way...
2942 const root_block_id = self.spv.allocId();
2943
2944 // The root block of a function declaration should appear before OpVariable instructions,
2945 // so it is generated into the function's prologue.
2946 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
2947 .id_result = root_block_id,
2948 });
2949 self.current_block_label = root_block_id;
2950
2951 const main_body = self.air.getMainBody();
2952 switch (self.control_flow) {
2953 .structured => {
2954 _ = try self.genStructuredBody(.selection, main_body);
2955 // We always expect paths to here to end, but we still need the block
2956 // to act as a dummy merge block.
2957 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});
2958 },
2959 .unstructured => {
2960 try self.genBody(main_body);
2961 },
2962 }
2963 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
2964 // Append the actual code into the functions section.
2965 try self.spv.addFunction(spv_decl_index, self.func);
2966
2967 try self.spv.debugName(result_id, nav.fqn.toSlice(ip));
2968
2969 // Temporarily generate a test kernel declaration if this is a test function.
2970 if (self.pt.zcu.test_functions.contains(self.owner_nav)) {
2971 try self.generateTestEntryPoint(nav.fqn.toSlice(ip), spv_decl_index);
2972 }
2973 },
2974 .global => {
2975 const maybe_init_val: ?Value = switch (ip.indexToKey(val.toIntern())) {
2976 .func => unreachable,
2977 .variable => |variable| Value.fromInterned(variable.init),
2978 .@"extern" => null,
2979 else => val,
2980 };
2981 assert(maybe_init_val == null); // TODO
2982
2983 const storage_class = self.spvStorageClass(nav.getAddrspace());
2984 assert(storage_class != .generic); // These should be instance globals
2985
2986 const ptr_ty_id = try self.ptrType(ty, storage_class, .indirect);
2987
2988 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpVariable, .{
2989 .id_result_type = ptr_ty_id,
2990 .id_result = result_id,
2991 .storage_class = storage_class,
2992 });
2993
2994 if (std.meta.stringToEnum(spec.BuiltIn, nav.fqn.toSlice(ip))) |builtin| {
2995 try self.spv.decorate(result_id, .{ .built_in = .{ .built_in = builtin } });
2996 }
2997
2998 try self.spv.debugName(result_id, nav.fqn.toSlice(ip));
2999 try self.spv.declareDeclDeps(spv_decl_index, &.{});
3000 },
3001 .invocation_global => {
3002 const maybe_init_val: ?Value = switch (ip.indexToKey(val.toIntern())) {
3003 .func => unreachable,
3004 .variable => |variable| Value.fromInterned(variable.init),
3005 .@"extern" => null,
3006 else => val,
3007 };
3008
3009 try self.spv.declareDeclDeps(spv_decl_index, &.{});
3010
3011 const ptr_ty_id = try self.ptrType(ty, .function, .indirect);
3012
3013 if (maybe_init_val) |init_val| {
3014 // TODO: Combine with resolveAnonDecl?
3015 const initializer_proto_ty_id = try self.functionType(Type.void, &.{});
3016
3017 const initializer_id = self.spv.allocId();
3018 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{
3019 .id_result_type = try self.resolveType(Type.void, .direct),
3020 .id_result = initializer_id,
3021 .function_control = .{},
3022 .function_type = initializer_proto_ty_id,
3023 });
3024
3025 const root_block_id = self.spv.allocId();
3026 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
3027 .id_result = root_block_id,
3028 });
3029 self.current_block_label = root_block_id;
3030
3031 const val_id = try self.constant(ty, init_val, .indirect);
3032 try self.func.body.emit(self.spv.gpa, .OpStore, .{
3033 .pointer = result_id,
3034 .object = val_id,
3035 });
3036
3037 try self.func.body.emit(self.spv.gpa, .OpReturn, {});
3038 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
3039 try self.spv.addFunction(spv_decl_index, self.func);
3040
3041 try self.spv.debugNameFmt(initializer_id, "initializer of {f}", .{nav.fqn.fmt(ip)});
3042
3043 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpExtInst, .{
3044 .id_result_type = ptr_ty_id,
3045 .id_result = result_id,
3046 .set = try self.spv.importInstructionSet(.zig),
3047 .instruction = .{ .inst = 0 }, // TODO: Put this definition somewhere...
3048 .id_ref_4 = &.{initializer_id},
3049 });
3050 } else {
3051 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpExtInst, .{
3052 .id_result_type = ptr_ty_id,
3053 .id_result = result_id,
3054 .set = try self.spv.importInstructionSet(.zig),
3055 .instruction = .{ .inst = 0 }, // TODO: Put this definition somewhere...
3056 .id_ref_4 = &.{},
3057 });
3058 }
3059 },
3060 }
3061 }
3062
3063 fn intFromBool(self: *NavGen, value: Temporary) !Temporary {
3064 return try self.intFromBool2(value, Type.u1);
3065 }
3066
3067 fn intFromBool2(self: *NavGen, value: Temporary, result_ty: Type) !Temporary {
3068 const zero_id = try self.constInt(result_ty, 0);
3069 const one_id = try self.constInt(result_ty, 1);
3070
3071 return try self.buildSelect(
3072 value,
3073 Temporary.init(result_ty, one_id),
3074 Temporary.init(result_ty, zero_id),
3075 );
3076 }
3077
3078 /// Convert representation from indirect (in memory) to direct (in 'register')
3079 /// This converts the argument type from resolveType(ty, .indirect) to resolveType(ty, .direct).
3080 fn convertToDirect(self: *NavGen, ty: Type, operand_id: Id) !Id {
3081 const pt = self.pt;
3082 const zcu = pt.zcu;
3083 switch (ty.scalarType(zcu).zigTypeTag(zcu)) {
3084 .bool => {
3085 const false_id = try self.constBool(false, .indirect);
3086 const operand_ty = blk: {
3087 if (!ty.isVector(pt.zcu)) break :blk Type.u1;
3088 break :blk try pt.vectorType(.{
3089 .len = ty.vectorLen(pt.zcu),
3090 .child = Type.u1.toIntern(),
3091 });
3092 };
3093
3094 const result = try self.buildCmp(
3095 .i_ne,
3096 Temporary.init(operand_ty, operand_id),
3097 Temporary.init(Type.u1, false_id),
3098 );
3099 return try result.materialize(self);
3100 },
3101 else => return operand_id,
3102 }
3103 }
3104
3105 /// Convert representation from direct (in 'register) to direct (in memory)
3106 /// This converts the argument type from resolveType(ty, .direct) to resolveType(ty, .indirect).
3107 fn convertToIndirect(self: *NavGen, ty: Type, operand_id: Id) !Id {
3108 const zcu = self.pt.zcu;
3109 switch (ty.scalarType(zcu).zigTypeTag(zcu)) {
3110 .bool => {
3111 const result = try self.intFromBool(Temporary.init(ty, operand_id));
3112 return try result.materialize(self);
3113 },
3114 else => return operand_id,
3115 }
3116 }
3117
3118 fn extractField(self: *NavGen, result_ty: Type, object: Id, field: u32) !Id {
3119 const result_ty_id = try self.resolveType(result_ty, .indirect);
3120 const result_id = self.spv.allocId();
3121 const indexes = [_]u32{field};
3122 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
3123 .id_result_type = result_ty_id,
3124 .id_result = result_id,
3125 .composite = object,
3126 .indexes = &indexes,
3127 });
3128 // Convert bools; direct structs have their field types as indirect values.
3129 return try self.convertToDirect(result_ty, result_id);
3130 }
3131
3132 fn extractVectorComponent(self: *NavGen, result_ty: Type, vector_id: Id, field: u32) !Id {
3133 const result_ty_id = try self.resolveType(result_ty, .direct);
3134 const result_id = self.spv.allocId();
3135 const indexes = [_]u32{field};
3136 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
3137 .id_result_type = result_ty_id,
3138 .id_result = result_id,
3139 .composite = vector_id,
3140 .indexes = &indexes,
3141 });
3142 // Vector components are already stored in direct representation.
3143 return result_id;
3144 }
3145
3146 const MemoryOptions = struct {
3147 is_volatile: bool = false,
3148 };
3149
3150 fn load(self: *NavGen, value_ty: Type, ptr_id: Id, options: MemoryOptions) !Id {
3151 const zcu = self.pt.zcu;
3152 const alignment: u32 = @intCast(value_ty.abiAlignment(zcu).toByteUnits().?);
3153 const indirect_value_ty_id = try self.resolveType(value_ty, .indirect);
3154 const result_id = self.spv.allocId();
3155 const access: spec.MemoryAccess.Extended = .{
3156 .@"volatile" = options.is_volatile,
3157 .aligned = .{ .literal_integer = alignment },
3158 };
3159 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
3160 .id_result_type = indirect_value_ty_id,
3161 .id_result = result_id,
3162 .pointer = ptr_id,
3163 .memory_access = access,
3164 });
3165 return try self.convertToDirect(value_ty, result_id);
3166 }
3167
3168 fn store(self: *NavGen, value_ty: Type, ptr_id: Id, value_id: Id, options: MemoryOptions) !void {
3169 const indirect_value_id = try self.convertToIndirect(value_ty, value_id);
3170 const access: spec.MemoryAccess.Extended = .{ .@"volatile" = options.is_volatile };
3171 try self.func.body.emit(self.spv.gpa, .OpStore, .{
3172 .pointer = ptr_id,
3173 .object = indirect_value_id,
3174 .memory_access = access,
3175 });
3176 }
3177
3178 fn genBody(self: *NavGen, body: []const Air.Inst.Index) Error!void {
3179 for (body) |inst| {
3180 try self.genInst(inst);
3181 }
3182 }
3183
3184 fn genInst(self: *NavGen, inst: Air.Inst.Index) !void {
3185 const zcu = self.pt.zcu;
3186 const ip = &zcu.intern_pool;
3187 if (self.liveness.isUnused(inst) and !self.air.mustLower(inst, ip))
3188 return;
3189
3190 const air_tags = self.air.instructions.items(.tag);
3191 const maybe_result_id: ?Id = switch (air_tags[@intFromEnum(inst)]) {
3192 // zig fmt: off
3193 .add, .add_wrap, .add_optimized => try self.airArithOp(inst, .f_add, .i_add, .i_add),
3194 .sub, .sub_wrap, .sub_optimized => try self.airArithOp(inst, .f_sub, .i_sub, .i_sub),
3195 .mul, .mul_wrap, .mul_optimized => try self.airArithOp(inst, .f_mul, .i_mul, .i_mul),
3196
3197 .sqrt => try self.airUnOpSimple(inst, .sqrt),
3198 .sin => try self.airUnOpSimple(inst, .sin),
3199 .cos => try self.airUnOpSimple(inst, .cos),
3200 .tan => try self.airUnOpSimple(inst, .tan),
3201 .exp => try self.airUnOpSimple(inst, .exp),
3202 .exp2 => try self.airUnOpSimple(inst, .exp2),
3203 .log => try self.airUnOpSimple(inst, .log),
3204 .log2 => try self.airUnOpSimple(inst, .log2),
3205 .log10 => try self.airUnOpSimple(inst, .log10),
3206 .abs => try self.airAbs(inst),
3207 .floor => try self.airUnOpSimple(inst, .floor),
3208 .ceil => try self.airUnOpSimple(inst, .ceil),
3209 .round => try self.airUnOpSimple(inst, .round),
3210 .trunc_float => try self.airUnOpSimple(inst, .trunc),
3211 .neg, .neg_optimized => try self.airUnOpSimple(inst, .f_neg),
3212
3213 .div_float, .div_float_optimized => try self.airArithOp(inst, .f_div, .s_div, .u_div),
3214 .div_floor, .div_floor_optimized => try self.airDivFloor(inst),
3215 .div_trunc, .div_trunc_optimized => try self.airDivTrunc(inst),
3216
3217 .rem, .rem_optimized => try self.airArithOp(inst, .f_rem, .s_rem, .u_mod),
3218 .mod, .mod_optimized => try self.airArithOp(inst, .f_mod, .s_mod, .u_mod),
3219
3220 .add_with_overflow => try self.airAddSubOverflow(inst, .i_add, .u_lt, .s_lt),
3221 .sub_with_overflow => try self.airAddSubOverflow(inst, .i_sub, .u_gt, .s_gt),
3222 .mul_with_overflow => try self.airMulOverflow(inst),
3223 .shl_with_overflow => try self.airShlOverflow(inst),
3224
3225 .mul_add => try self.airMulAdd(inst),
3226
3227 .ctz => try self.airClzCtz(inst, .ctz),
3228 .clz => try self.airClzCtz(inst, .clz),
3229
3230 .select => try self.airSelect(inst),
3231
3232 .splat => try self.airSplat(inst),
3233 .reduce, .reduce_optimized => try self.airReduce(inst),
3234 .shuffle_one => try self.airShuffleOne(inst),
3235 .shuffle_two => try self.airShuffleTwo(inst),
3236
3237 .ptr_add => try self.airPtrAdd(inst),
3238 .ptr_sub => try self.airPtrSub(inst),
3239
3240 .bit_and => try self.airBinOpSimple(inst, .bit_and),
3241 .bit_or => try self.airBinOpSimple(inst, .bit_or),
3242 .xor => try self.airBinOpSimple(inst, .bit_xor),
3243 .bool_and => try self.airBinOpSimple(inst, .l_and),
3244 .bool_or => try self.airBinOpSimple(inst, .l_or),
3245
3246 .shl, .shl_exact => try self.airShift(inst, .sll, .sll),
3247 .shr, .shr_exact => try self.airShift(inst, .srl, .sra),
3248
3249 .min => try self.airMinMax(inst, .min),
3250 .max => try self.airMinMax(inst, .max),
3251
3252 .bitcast => try self.airBitCast(inst),
3253 .intcast, .trunc => try self.airIntCast(inst),
3254 .float_from_int => try self.airFloatFromInt(inst),
3255 .int_from_float => try self.airIntFromFloat(inst),
3256 .fpext, .fptrunc => try self.airFloatCast(inst),
3257 .not => try self.airNot(inst),
3258
3259 .array_to_slice => try self.airArrayToSlice(inst),
3260 .slice => try self.airSlice(inst),
3261 .aggregate_init => try self.airAggregateInit(inst),
3262 .memcpy => return self.airMemcpy(inst),
3263 .memmove => return self.airMemmove(inst),
3264
3265 .slice_ptr => try self.airSliceField(inst, 0),
3266 .slice_len => try self.airSliceField(inst, 1),
3267 .slice_elem_ptr => try self.airSliceElemPtr(inst),
3268 .slice_elem_val => try self.airSliceElemVal(inst),
3269 .ptr_elem_ptr => try self.airPtrElemPtr(inst),
3270 .ptr_elem_val => try self.airPtrElemVal(inst),
3271 .array_elem_val => try self.airArrayElemVal(inst),
3272
3273 .vector_store_elem => return self.airVectorStoreElem(inst),
3274
3275 .set_union_tag => return self.airSetUnionTag(inst),
3276 .get_union_tag => try self.airGetUnionTag(inst),
3277 .union_init => try self.airUnionInit(inst),
3278
3279 .struct_field_val => try self.airStructFieldVal(inst),
3280 .field_parent_ptr => try self.airFieldParentPtr(inst),
3281
3282 .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
3283 .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
3284 .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
3285 .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
3286
3287 .cmp_eq => try self.airCmp(inst, .eq),
3288 .cmp_neq => try self.airCmp(inst, .neq),
3289 .cmp_gt => try self.airCmp(inst, .gt),
3290 .cmp_gte => try self.airCmp(inst, .gte),
3291 .cmp_lt => try self.airCmp(inst, .lt),
3292 .cmp_lte => try self.airCmp(inst, .lte),
3293 .cmp_vector => try self.airVectorCmp(inst),
3294
3295 .arg => self.airArg(),
3296 .alloc => try self.airAlloc(inst),
3297 // TODO: We probably need to have a special implementation of this for the C abi.
3298 .ret_ptr => try self.airAlloc(inst),
3299 .block => try self.airBlock(inst),
3300
3301 .load => try self.airLoad(inst),
3302 .store, .store_safe => return self.airStore(inst),
3303
3304 .br => return self.airBr(inst),
3305 // For now just ignore this instruction. This effectively falls back on the old implementation,
3306 // this doesn't change anything for us.
3307 .repeat => return,
3308 .breakpoint => return,
3309 .cond_br => return self.airCondBr(inst),
3310 .loop => return self.airLoop(inst),
3311 .ret => return self.airRet(inst),
3312 .ret_safe => return self.airRet(inst), // TODO
3313 .ret_load => return self.airRetLoad(inst),
3314 .@"try" => try self.airTry(inst),
3315 .switch_br => return self.airSwitchBr(inst),
3316 .unreach, .trap => return self.airUnreach(),
3317
3318 .dbg_empty_stmt => return,
3319 .dbg_stmt => return self.airDbgStmt(inst),
3320 .dbg_inline_block => try self.airDbgInlineBlock(inst),
3321 .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => return self.airDbgVar(inst),
3322
3323 .unwrap_errunion_err => try self.airErrUnionErr(inst),
3324 .unwrap_errunion_payload => try self.airErrUnionPayload(inst),
3325 .wrap_errunion_err => try self.airWrapErrUnionErr(inst),
3326 .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
3327
3328 .is_null => try self.airIsNull(inst, false, .is_null),
3329 .is_non_null => try self.airIsNull(inst, false, .is_non_null),
3330 .is_null_ptr => try self.airIsNull(inst, true, .is_null),
3331 .is_non_null_ptr => try self.airIsNull(inst, true, .is_non_null),
3332 .is_err => try self.airIsErr(inst, .is_err),
3333 .is_non_err => try self.airIsErr(inst, .is_non_err),
3334
3335 .optional_payload => try self.airUnwrapOptional(inst),
3336 .optional_payload_ptr => try self.airUnwrapOptionalPtr(inst),
3337 .wrap_optional => try self.airWrapOptional(inst),
3338
3339 .assembly => try self.airAssembly(inst),
3340
3341 .call => try self.airCall(inst, .auto),
3342 .call_always_tail => try self.airCall(inst, .always_tail),
3343 .call_never_tail => try self.airCall(inst, .never_tail),
3344 .call_never_inline => try self.airCall(inst, .never_inline),
3345
3346 .work_item_id => try self.airWorkItemId(inst),
3347 .work_group_size => try self.airWorkGroupSize(inst),
3348 .work_group_id => try self.airWorkGroupId(inst),
3349
3350 // zig fmt: on
3351
3352 else => |tag| return self.todo("implement AIR tag {s}", .{@tagName(tag)}),
3353 };
3354
3355 const result_id = maybe_result_id orelse return;
3356 try self.inst_results.putNoClobber(self.gpa, inst, result_id);
3357 }
3358
3359 fn airBinOpSimple(self: *NavGen, inst: Air.Inst.Index, op: BinaryOp) !?Id {
3360 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3361 const lhs = try self.temporary(bin_op.lhs);
3362 const rhs = try self.temporary(bin_op.rhs);
3363
3364 const result = try self.buildBinary(op, lhs, rhs);
3365 return try result.materialize(self);
3366 }
3367
3368 fn airShift(self: *NavGen, inst: Air.Inst.Index, unsigned: BinaryOp, signed: BinaryOp) !?Id {
3369 const zcu = self.pt.zcu;
3370 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3371
3372 if (self.typeOf(bin_op.lhs).isVector(zcu) and !self.typeOf(bin_op.rhs).isVector(zcu)) {
3373 return self.fail("vector shift with scalar rhs", .{});
3374 }
3375
3376 const base = try self.temporary(bin_op.lhs);
3377 const shift = try self.temporary(bin_op.rhs);
3378
3379 const result_ty = self.typeOfIndex(inst);
3380
3381 const info = self.arithmeticTypeInfo(result_ty);
3382 switch (info.class) {
3383 .composite_integer => return self.todo("shift ops for composite integers", .{}),
3384 .integer, .strange_integer => {},
3385 .float, .bool => unreachable,
3386 }
3387
3388 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that,
3389 // so just manually upcast it if required.
3390
3391 // Note: The sign may differ here between the shift and the base type, in case
3392 // of an arithmetic right shift. SPIR-V still expects the same type,
3393 // so in that case we have to cast convert to signed.
3394 const casted_shift = try self.buildConvert(base.ty.scalarType(zcu), shift);
3395
3396 const shifted = switch (info.signedness) {
3397 .unsigned => try self.buildBinary(unsigned, base, casted_shift),
3398 .signed => try self.buildBinary(signed, base, casted_shift),
3399 };
3400
3401 const result = try self.normalize(shifted, info);
3402 return try result.materialize(self);
3403 }
3404
3405 const MinMax = enum { min, max };
3406
3407 fn airMinMax(self: *NavGen, inst: Air.Inst.Index, op: MinMax) !?Id {
3408 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3409
3410 const lhs = try self.temporary(bin_op.lhs);
3411 const rhs = try self.temporary(bin_op.rhs);
3412
3413 const result = try self.minMax(lhs, rhs, op);
3414 return try result.materialize(self);
3415 }
3416
3417 fn minMax(self: *NavGen, lhs: Temporary, rhs: Temporary, op: MinMax) !Temporary {
3418 const info = self.arithmeticTypeInfo(lhs.ty);
3419
3420 const binop: BinaryOp = switch (info.class) {
3421 .float => switch (op) {
3422 .min => .f_min,
3423 .max => .f_max,
3424 },
3425 .integer, .strange_integer => switch (info.signedness) {
3426 .signed => switch (op) {
3427 .min => .s_min,
3428 .max => .s_max,
3429 },
3430 .unsigned => switch (op) {
3431 .min => .u_min,
3432 .max => .u_max,
3433 },
3434 },
3435 .composite_integer => unreachable, // TODO
3436 .bool => unreachable,
3437 };
3438
3439 return try self.buildBinary(binop, lhs, rhs);
3440 }
3441
3442 /// This function normalizes values to a canonical representation
3443 /// after some arithmetic operation. This mostly consists of wrapping
3444 /// behavior for strange integers:
3445 /// - Unsigned integers are bitwise masked with a mask that only passes
3446 /// the valid bits through.
3447 /// - Signed integers are also sign extended if they are negative.
3448 /// All other values are returned unmodified (this makes strange integer
3449 /// wrapping easier to use in generic operations).
3450 fn normalize(self: *NavGen, value: Temporary, info: ArithmeticTypeInfo) !Temporary {
3451 const zcu = self.pt.zcu;
3452 const ty = value.ty;
3453 switch (info.class) {
3454 .composite_integer, .integer, .bool, .float => return value,
3455 .strange_integer => switch (info.signedness) {
3456 .unsigned => {
3457 const mask_value = if (info.bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1;
3458 const mask_id = try self.constInt(ty.scalarType(zcu), mask_value);
3459 return try self.buildBinary(.bit_and, value, Temporary.init(ty.scalarType(zcu), mask_id));
3460 },
3461 .signed => {
3462 // Shift left and right so that we can copy the sight bit that way.
3463 const shift_amt_id = try self.constInt(ty.scalarType(zcu), info.backing_bits - info.bits);
3464 const shift_amt = Temporary.init(ty.scalarType(zcu), shift_amt_id);
3465 const left = try self.buildBinary(.sll, value, shift_amt);
3466 return try self.buildBinary(.sra, left, shift_amt);
3467 },
3468 },
3469 }
3470 }
3471
3472 fn airDivFloor(self: *NavGen, inst: Air.Inst.Index) !?Id {
3473 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3474
3475 const lhs = try self.temporary(bin_op.lhs);
3476 const rhs = try self.temporary(bin_op.rhs);
3477
3478 const info = self.arithmeticTypeInfo(lhs.ty);
3479 switch (info.class) {
3480 .composite_integer => unreachable, // TODO
3481 .integer, .strange_integer => {
3482 switch (info.signedness) {
3483 .unsigned => {
3484 const result = try self.buildBinary(.u_div, lhs, rhs);
3485 return try result.materialize(self);
3486 },
3487 .signed => {},
3488 }
3489
3490 // For signed integers:
3491 // (a / b) - (a % b != 0 && a < 0 != b < 0);
3492 // There shouldn't be any overflow issues.
3493
3494 const div = try self.buildBinary(.s_div, lhs, rhs);
3495 const rem = try self.buildBinary(.s_rem, lhs, rhs);
3496
3497 const zero = Temporary.init(lhs.ty, try self.constInt(lhs.ty, 0));
3498
3499 const rem_is_not_zero = try self.buildCmp(.i_ne, rem, zero);
3500
3501 const result_negative = try self.buildCmp(
3502 .l_ne,
3503 try self.buildCmp(.s_lt, lhs, zero),
3504 try self.buildCmp(.s_lt, rhs, zero),
3505 );
3506 const rem_is_not_zero_and_result_is_negative = try self.buildBinary(
3507 .l_and,
3508 rem_is_not_zero,
3509 result_negative,
3510 );
3511
3512 const result = try self.buildBinary(
3513 .i_sub,
3514 div,
3515 try self.intFromBool2(rem_is_not_zero_and_result_is_negative, div.ty),
3516 );
3517
3518 return try result.materialize(self);
3519 },
3520 .float => {
3521 const div = try self.buildBinary(.f_div, lhs, rhs);
3522 const result = try self.buildUnary(.floor, div);
3523 return try result.materialize(self);
3524 },
3525 .bool => unreachable,
3526 }
3527 }
3528
3529 fn airDivTrunc(self: *NavGen, inst: Air.Inst.Index) !?Id {
3530 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3531
3532 const lhs = try self.temporary(bin_op.lhs);
3533 const rhs = try self.temporary(bin_op.rhs);
3534
3535 const info = self.arithmeticTypeInfo(lhs.ty);
3536 switch (info.class) {
3537 .composite_integer => unreachable, // TODO
3538 .integer, .strange_integer => switch (info.signedness) {
3539 .unsigned => {
3540 const result = try self.buildBinary(.u_div, lhs, rhs);
3541 return try result.materialize(self);
3542 },
3543 .signed => {
3544 const result = try self.buildBinary(.s_div, lhs, rhs);
3545 return try result.materialize(self);
3546 },
3547 },
3548 .float => {
3549 const div = try self.buildBinary(.f_div, lhs, rhs);
3550 const result = try self.buildUnary(.trunc, div);
3551 return try result.materialize(self);
3552 },
3553 .bool => unreachable,
3554 }
3555 }
3556
3557 fn airUnOpSimple(self: *NavGen, inst: Air.Inst.Index, op: UnaryOp) !?Id {
3558 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
3559 const operand = try self.temporary(un_op);
3560 const result = try self.buildUnary(op, operand);
3561 return try result.materialize(self);
3562 }
3563
3564 fn airArithOp(
3565 self: *NavGen,
3566 inst: Air.Inst.Index,
3567 comptime fop: BinaryOp,
3568 comptime sop: BinaryOp,
3569 comptime uop: BinaryOp,
3570 ) !?Id {
3571 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3572
3573 const lhs = try self.temporary(bin_op.lhs);
3574 const rhs = try self.temporary(bin_op.rhs);
3575
3576 const info = self.arithmeticTypeInfo(lhs.ty);
3577
3578 const result = switch (info.class) {
3579 .composite_integer => unreachable, // TODO
3580 .integer, .strange_integer => switch (info.signedness) {
3581 .signed => try self.buildBinary(sop, lhs, rhs),
3582 .unsigned => try self.buildBinary(uop, lhs, rhs),
3583 },
3584 .float => try self.buildBinary(fop, lhs, rhs),
3585 .bool => unreachable,
3586 };
3587
3588 return try result.materialize(self);
3589 }
3590
3591 fn airAbs(self: *NavGen, inst: Air.Inst.Index) !?Id {
3592 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3593 const operand = try self.temporary(ty_op.operand);
3594 // Note: operand_ty may be signed, while ty is always unsigned!
3595 const result_ty = self.typeOfIndex(inst);
3596 const result = try self.abs(result_ty, operand);
3597 return try result.materialize(self);
3598 }
3599
3600 fn abs(self: *NavGen, result_ty: Type, value: Temporary) !Temporary {
3601 const zcu = self.pt.zcu;
3602 const operand_info = self.arithmeticTypeInfo(value.ty);
3603
3604 switch (operand_info.class) {
3605 .float => return try self.buildUnary(.f_abs, value),
3606 .integer, .strange_integer => {
3607 const abs_value = try self.buildUnary(.i_abs, value);
3608
3609 switch (self.spv.target.os.tag) {
3610 .vulkan, .opengl => {
3611 if (value.ty.intInfo(zcu).signedness == .signed) {
3612 return self.todo("perform bitcast after @abs", .{});
3613 }
3614 },
3615 else => {},
3616 }
3617
3618 return try self.normalize(abs_value, self.arithmeticTypeInfo(result_ty));
3619 },
3620 .composite_integer => unreachable, // TODO
3621 .bool => unreachable,
3622 }
3623 }
3624
3625 fn airAddSubOverflow(
3626 self: *NavGen,
3627 inst: Air.Inst.Index,
3628 comptime add: BinaryOp,
3629 comptime ucmp: CmpPredicate,
3630 comptime scmp: CmpPredicate,
3631 ) !?Id {
3632 _ = scmp;
3633 // Note: OpIAddCarry and OpISubBorrow are not really useful here: For unsigned numbers,
3634 // there is in both cases only one extra operation required. For signed operations,
3635 // the overflow bit is set then going from 0x80.. to 0x00.., but this doesn't actually
3636 // normally set a carry bit. So the SPIR-V overflow operations are not particularly
3637 // useful here.
3638
3639 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3640 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
3641
3642 const lhs = try self.temporary(extra.lhs);
3643 const rhs = try self.temporary(extra.rhs);
3644
3645 const result_ty = self.typeOfIndex(inst);
3646
3647 const info = self.arithmeticTypeInfo(lhs.ty);
3648 switch (info.class) {
3649 .composite_integer => unreachable, // TODO
3650 .strange_integer, .integer => {},
3651 .float, .bool => unreachable,
3652 }
3653
3654 const sum = try self.buildBinary(add, lhs, rhs);
3655 const result = try self.normalize(sum, info);
3656
3657 const overflowed = switch (info.signedness) {
3658 // Overflow happened if the result is smaller than either of the operands. It doesn't matter which.
3659 // For subtraction the conditions need to be swapped.
3660 .unsigned => try self.buildCmp(ucmp, result, lhs),
3661 // For signed operations, we check the signs of the operands and the result.
3662 .signed => blk: {
3663 // Signed overflow detection using the sign bits of the operands and the result.
3664 // For addition (a + b), overflow occurs if the operands have the same sign
3665 // and the result's sign is different from the operands' sign.
3666 // (sign(a) == sign(b)) && (sign(a) != sign(result))
3667 // For subtraction (a - b), overflow occurs if the operands have different signs
3668 // and the result's sign is different from the minuend's (a's) sign.
3669 // (sign(a) != sign(b)) && (sign(a) != sign(result))
3670 const zero = Temporary.init(rhs.ty, try self.constInt(rhs.ty, 0));
3671
3672 const lhs_is_neg = try self.buildCmp(.s_lt, lhs, zero);
3673 const rhs_is_neg = try self.buildCmp(.s_lt, rhs, zero);
3674 const result_is_neg = try self.buildCmp(.s_lt, result, zero);
3675
3676 const signs_match = try self.buildCmp(.l_eq, lhs_is_neg, rhs_is_neg);
3677 const result_sign_differs = try self.buildCmp(.l_ne, lhs_is_neg, result_is_neg);
3678
3679 const overflow_condition = if (add == .i_add)
3680 signs_match
3681 else // .i_sub
3682 try self.buildUnary(.l_not, signs_match);
3683
3684 break :blk try self.buildBinary(.l_and, overflow_condition, result_sign_differs);
3685 },
3686 };
3687
3688 const ov = try self.intFromBool(overflowed);
3689
3690 const result_ty_id = try self.resolveType(result_ty, .direct);
3691 return try self.constructComposite(result_ty_id, &.{ try result.materialize(self), try ov.materialize(self) });
3692 }
3693
3694 fn airMulOverflow(self: *NavGen, inst: Air.Inst.Index) !?Id {
3695 const pt = self.pt;
3696
3697 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3698 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
3699
3700 const lhs = try self.temporary(extra.lhs);
3701 const rhs = try self.temporary(extra.rhs);
3702
3703 const result_ty = self.typeOfIndex(inst);
3704
3705 const info = self.arithmeticTypeInfo(lhs.ty);
3706 switch (info.class) {
3707 .composite_integer => unreachable, // TODO
3708 .strange_integer, .integer => {},
3709 .float, .bool => unreachable,
3710 }
3711
3712 // There are 3 cases which we have to deal with:
3713 // - If info.bits < 32 / 2, we will upcast to 32 and check the higher bits
3714 // - If info.bits > 32 / 2, we have to use extended multiplication
3715 // - Additionally, if info.bits != 32, we'll have to check the high bits
3716 // of the result too.
3717
3718 const largest_int_bits = self.largestSupportedIntBits();
3719 // If non-null, the number of bits that the multiplication should be performed in. If
3720 // null, we have to use wide multiplication.
3721 const maybe_op_ty_bits: ?u16 = switch (info.bits) {
3722 0 => unreachable,
3723 1...16 => 32,
3724 17...32 => if (largest_int_bits > 32) 64 else null, // Upcast if we can.
3725 33...64 => null, // Always use wide multiplication.
3726 else => unreachable, // TODO: Composite integers
3727 };
3728
3729 const result, const overflowed = switch (info.signedness) {
3730 .unsigned => blk: {
3731 if (maybe_op_ty_bits) |op_ty_bits| {
3732 const op_ty = try pt.intType(.unsigned, op_ty_bits);
3733 const casted_lhs = try self.buildConvert(op_ty, lhs);
3734 const casted_rhs = try self.buildConvert(op_ty, rhs);
3735
3736 const full_result = try self.buildBinary(.i_mul, casted_lhs, casted_rhs);
3737
3738 const low_bits = try self.buildConvert(lhs.ty, full_result);
3739 const result = try self.normalize(low_bits, info);
3740
3741 // Shift the result bits away to get the overflow bits.
3742 const shift = Temporary.init(full_result.ty, try self.constInt(full_result.ty, info.bits));
3743 const overflow = try self.buildBinary(.srl, full_result, shift);
3744
3745 // Directly check if its zero in the op_ty without converting first.
3746 const zero = Temporary.init(full_result.ty, try self.constInt(full_result.ty, 0));
3747 const overflowed = try self.buildCmp(.i_ne, zero, overflow);
3748
3749 break :blk .{ result, overflowed };
3750 }
3751
3752 const low_bits, const high_bits = try self.buildWideMul(.u_mul_extended, lhs, rhs);
3753
3754 // Truncate the result, if required.
3755 const result = try self.normalize(low_bits, info);
3756
3757 // Overflow happened if the high-bits of the result are non-zero OR if the
3758 // high bits of the low word of the result (those outside the range of the
3759 // int) are nonzero.
3760 const zero = Temporary.init(lhs.ty, try self.constInt(lhs.ty, 0));
3761 const high_overflowed = try self.buildCmp(.i_ne, zero, high_bits);
3762
3763 // If no overflow bits in low_bits, no extra work needs to be done.
3764 if (info.backing_bits == info.bits) break :blk .{ result, high_overflowed };
3765
3766 // Shift the result bits away to get the overflow bits.
3767 const shift = Temporary.init(lhs.ty, try self.constInt(lhs.ty, info.bits));
3768 const low_overflow = try self.buildBinary(.srl, low_bits, shift);
3769 const low_overflowed = try self.buildCmp(.i_ne, zero, low_overflow);
3770
3771 const overflowed = try self.buildBinary(.l_or, low_overflowed, high_overflowed);
3772
3773 break :blk .{ result, overflowed };
3774 },
3775 .signed => blk: {
3776 // - lhs >= 0, rhxs >= 0: expect positive; overflow should be 0
3777 // - lhs == 0 : expect positive; overflow should be 0
3778 // - rhs == 0: expect positive; overflow should be 0
3779 // - lhs > 0, rhs < 0: expect negative; overflow should be -1
3780 // - lhs < 0, rhs > 0: expect negative; overflow should be -1
3781 // - lhs <= 0, rhs <= 0: expect positive; overflow should be 0
3782 // ------
3783 // overflow should be -1 when
3784 // (lhs > 0 && rhs < 0) || (lhs < 0 && rhs > 0)
3785
3786 const zero = Temporary.init(lhs.ty, try self.constInt(lhs.ty, 0));
3787 const lhs_negative = try self.buildCmp(.s_lt, lhs, zero);
3788 const rhs_negative = try self.buildCmp(.s_lt, rhs, zero);
3789 const lhs_positive = try self.buildCmp(.s_gt, lhs, zero);
3790 const rhs_positive = try self.buildCmp(.s_gt, rhs, zero);
3791
3792 // Set to `true` if we expect -1.
3793 const expected_overflow_bit = try self.buildBinary(
3794 .l_or,
3795 try self.buildBinary(.l_and, lhs_positive, rhs_negative),
3796 try self.buildBinary(.l_and, lhs_negative, rhs_positive),
3797 );
3798
3799 if (maybe_op_ty_bits) |op_ty_bits| {
3800 const op_ty = try pt.intType(.signed, op_ty_bits);
3801 // Assume normalized; sign bit is set. We want a sign extend.
3802 const casted_lhs = try self.buildConvert(op_ty, lhs);
3803 const casted_rhs = try self.buildConvert(op_ty, rhs);
3804
3805 const full_result = try self.buildBinary(.i_mul, casted_lhs, casted_rhs);
3806
3807 // Truncate to the result type.
3808 const low_bits = try self.buildConvert(lhs.ty, full_result);
3809 const result = try self.normalize(low_bits, info);
3810
3811 // Now, we need to check the overflow bits AND the sign
3812 // bit for the expected overflow bits.
3813 // To do that, shift out everything bit the sign bit and
3814 // then check what remains.
3815 const shift = Temporary.init(full_result.ty, try self.constInt(full_result.ty, info.bits - 1));
3816 // Use SRA so that any sign bits are duplicated. Now we can just check if ALL bits are set
3817 // for negative cases.
3818 const overflow = try self.buildBinary(.sra, full_result, shift);
3819
3820 const long_all_set = Temporary.init(full_result.ty, try self.constInt(full_result.ty, -1));
3821 const long_zero = Temporary.init(full_result.ty, try self.constInt(full_result.ty, 0));
3822 const mask = try self.buildSelect(expected_overflow_bit, long_all_set, long_zero);
3823
3824 const overflowed = try self.buildCmp(.i_ne, mask, overflow);
3825
3826 break :blk .{ result, overflowed };
3827 }
3828
3829 const low_bits, const high_bits = try self.buildWideMul(.s_mul_extended, lhs, rhs);
3830
3831 // Truncate result if required.
3832 const result = try self.normalize(low_bits, info);
3833
3834 const all_set = Temporary.init(lhs.ty, try self.constInt(lhs.ty, -1));
3835 const mask = try self.buildSelect(expected_overflow_bit, all_set, zero);
3836
3837 // Like with unsigned, overflow happened if high_bits are not the ones we expect,
3838 // and we also need to check some ones from the low bits.
3839
3840 const high_overflowed = try self.buildCmp(.i_ne, mask, high_bits);
3841
3842 // If no overflow bits in low_bits, no extra work needs to be done.
3843 // Careful, we still have to check the sign bit, so this branch
3844 // only goes for i33 and such.
3845 if (info.backing_bits == info.bits + 1) break :blk .{ result, high_overflowed };
3846
3847 // Shift the result bits away to get the overflow bits.
3848 const shift = Temporary.init(lhs.ty, try self.constInt(lhs.ty, info.bits - 1));
3849 // Use SRA so that any sign bits are duplicated. Now we can just check if ALL bits are set
3850 // for negative cases.
3851 const low_overflow = try self.buildBinary(.sra, low_bits, shift);
3852 const low_overflowed = try self.buildCmp(.i_ne, mask, low_overflow);
3853
3854 const overflowed = try self.buildBinary(.l_or, low_overflowed, high_overflowed);
3855
3856 break :blk .{ result, overflowed };
3857 },
3858 };
3859
3860 const ov = try self.intFromBool(overflowed);
3861
3862 const result_ty_id = try self.resolveType(result_ty, .direct);
3863 return try self.constructComposite(result_ty_id, &.{ try result.materialize(self), try ov.materialize(self) });
3864 }
3865
3866 fn airShlOverflow(self: *NavGen, inst: Air.Inst.Index) !?Id {
3867 const zcu = self.pt.zcu;
3868
3869 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3870 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
3871
3872 if (self.typeOf(extra.lhs).isVector(zcu) and !self.typeOf(extra.rhs).isVector(zcu)) {
3873 return self.fail("vector shift with scalar rhs", .{});
3874 }
3875
3876 const base = try self.temporary(extra.lhs);
3877 const shift = try self.temporary(extra.rhs);
3878
3879 const result_ty = self.typeOfIndex(inst);
3880
3881 const info = self.arithmeticTypeInfo(base.ty);
3882 switch (info.class) {
3883 .composite_integer => unreachable, // TODO
3884 .integer, .strange_integer => {},
3885 .float, .bool => unreachable,
3886 }
3887
3888 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that,
3889 // so just manually upcast it if required.
3890 const casted_shift = try self.buildConvert(base.ty.scalarType(zcu), shift);
3891
3892 const left = try self.buildBinary(.sll, base, casted_shift);
3893 const result = try self.normalize(left, info);
3894
3895 const right = switch (info.signedness) {
3896 .unsigned => try self.buildBinary(.srl, result, casted_shift),
3897 .signed => try self.buildBinary(.sra, result, casted_shift),
3898 };
3899
3900 const overflowed = try self.buildCmp(.i_ne, base, right);
3901 const ov = try self.intFromBool(overflowed);
3902
3903 const result_ty_id = try self.resolveType(result_ty, .direct);
3904 return try self.constructComposite(result_ty_id, &.{ try result.materialize(self), try ov.materialize(self) });
3905 }
3906
3907 fn airMulAdd(self: *NavGen, inst: Air.Inst.Index) !?Id {
3908 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
3909 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
3910
3911 const a = try self.temporary(extra.lhs);
3912 const b = try self.temporary(extra.rhs);
3913 const c = try self.temporary(pl_op.operand);
3914
3915 const result_ty = self.typeOfIndex(inst);
3916 const info = self.arithmeticTypeInfo(result_ty);
3917 assert(info.class == .float); // .mul_add is only emitted for floats
3918
3919 const result = try self.buildFma(a, b, c);
3920 return try result.materialize(self);
3921 }
3922
3923 fn airClzCtz(self: *NavGen, inst: Air.Inst.Index, op: UnaryOp) !?Id {
3924 if (self.liveness.isUnused(inst)) return null;
3925
3926 const zcu = self.pt.zcu;
3927 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3928 const operand = try self.temporary(ty_op.operand);
3929
3930 const scalar_result_ty = self.typeOfIndex(inst).scalarType(zcu);
3931
3932 const info = self.arithmeticTypeInfo(operand.ty);
3933 switch (info.class) {
3934 .composite_integer => unreachable, // TODO
3935 .integer, .strange_integer => {},
3936 .float, .bool => unreachable,
3937 }
3938
3939 const count = try self.buildUnary(op, operand);
3940
3941 // Result of OpenCL ctz/clz returns operand.ty, and we want result_ty.
3942 // result_ty is always large enough to hold the result, so we might have to down
3943 // cast it.
3944 const result = try self.buildConvert(scalar_result_ty, count);
3945 return try result.materialize(self);
3946 }
3947
3948 fn airSelect(self: *NavGen, inst: Air.Inst.Index) !?Id {
3949 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
3950 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
3951 const pred = try self.temporary(pl_op.operand);
3952 const a = try self.temporary(extra.lhs);
3953 const b = try self.temporary(extra.rhs);
3954
3955 const result = try self.buildSelect(pred, a, b);
3956 return try result.materialize(self);
3957 }
3958
3959 fn airSplat(self: *NavGen, inst: Air.Inst.Index) !?Id {
3960 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3961
3962 const operand_id = try self.resolve(ty_op.operand);
3963 const result_ty = self.typeOfIndex(inst);
3964
3965 return try self.constructCompositeSplat(result_ty, operand_id);
3966 }
3967
3968 fn airReduce(self: *NavGen, inst: Air.Inst.Index) !?Id {
3969 const zcu = self.pt.zcu;
3970 const reduce = self.air.instructions.items(.data)[@intFromEnum(inst)].reduce;
3971 const operand = try self.resolve(reduce.operand);
3972 const operand_ty = self.typeOf(reduce.operand);
3973 const scalar_ty = operand_ty.scalarType(zcu);
3974 const scalar_ty_id = try self.resolveType(scalar_ty, .direct);
3975 const info = self.arithmeticTypeInfo(operand_ty);
3976 const len = operand_ty.vectorLen(zcu);
3977 const first = try self.extractVectorComponent(scalar_ty, operand, 0);
3978
3979 switch (reduce.operation) {
3980 .Min, .Max => |op| {
3981 var result = Temporary.init(scalar_ty, first);
3982 const cmp_op: MinMax = switch (op) {
3983 .Max => .max,
3984 .Min => .min,
3985 else => unreachable,
3986 };
3987 for (1..len) |i| {
3988 const lhs = result;
3989 const rhs_id = try self.extractVectorComponent(scalar_ty, operand, @intCast(i));
3990 const rhs = Temporary.init(scalar_ty, rhs_id);
3991
3992 result = try self.minMax(lhs, rhs, cmp_op);
3993 }
3994
3995 return try result.materialize(self);
3996 },
3997 else => {},
3998 }
3999
4000 var result_id = first;
4001
4002 const opcode: Opcode = switch (info.class) {
4003 .bool => switch (reduce.operation) {
4004 .And => .OpLogicalAnd,
4005 .Or => .OpLogicalOr,
4006 .Xor => .OpLogicalNotEqual,
4007 else => unreachable,
4008 },
4009 .strange_integer, .integer => switch (reduce.operation) {
4010 .And => .OpBitwiseAnd,
4011 .Or => .OpBitwiseOr,
4012 .Xor => .OpBitwiseXor,
4013 .Add => .OpIAdd,
4014 .Mul => .OpIMul,
4015 else => unreachable,
4016 },
4017 .float => switch (reduce.operation) {
4018 .Add => .OpFAdd,
4019 .Mul => .OpFMul,
4020 else => unreachable,
4021 },
4022 .composite_integer => unreachable, // TODO
4023 };
4024
4025 for (1..len) |i| {
4026 const lhs = result_id;
4027 const rhs = try self.extractVectorComponent(scalar_ty, operand, @intCast(i));
4028 result_id = self.spv.allocId();
4029
4030 try self.func.body.emitRaw(self.spv.gpa, opcode, 4);
4031 self.func.body.writeOperand(spec.Id, scalar_ty_id);
4032 self.func.body.writeOperand(spec.Id, result_id);
4033 self.func.body.writeOperand(spec.Id, lhs);
4034 self.func.body.writeOperand(spec.Id, rhs);
4035 }
4036
4037 return result_id;
4038 }
4039
4040 fn airShuffleOne(ng: *NavGen, inst: Air.Inst.Index) !?Id {
4041 const pt = ng.pt;
4042 const zcu = pt.zcu;
4043 const gpa = zcu.gpa;
4044
4045 const unwrapped = ng.air.unwrapShuffleOne(zcu, inst);
4046 const mask = unwrapped.mask;
4047 const result_ty = unwrapped.result_ty;
4048 const elem_ty = result_ty.childType(zcu);
4049 const operand = try ng.resolve(unwrapped.operand);
4050
4051 const constituents = try gpa.alloc(Id, mask.len);
4052 defer gpa.free(constituents);
4053
4054 for (constituents, mask) |*id, mask_elem| {
4055 id.* = switch (mask_elem.unwrap()) {
4056 .elem => |idx| try ng.extractVectorComponent(elem_ty, operand, idx),
4057 .value => |val| try ng.constant(elem_ty, .fromInterned(val), .direct),
4058 };
4059 }
4060
4061 const result_ty_id = try ng.resolveType(result_ty, .direct);
4062 return try ng.constructComposite(result_ty_id, constituents);
4063 }
4064
4065 fn airShuffleTwo(ng: *NavGen, inst: Air.Inst.Index) !?Id {
4066 const pt = ng.pt;
4067 const zcu = pt.zcu;
4068 const gpa = zcu.gpa;
4069
4070 const unwrapped = ng.air.unwrapShuffleTwo(zcu, inst);
4071 const mask = unwrapped.mask;
4072 const result_ty = unwrapped.result_ty;
4073 const elem_ty = result_ty.childType(zcu);
4074 const elem_ty_id = try ng.resolveType(elem_ty, .direct);
4075 const operand_a = try ng.resolve(unwrapped.operand_a);
4076 const operand_b = try ng.resolve(unwrapped.operand_b);
4077
4078 const constituents = try gpa.alloc(Id, mask.len);
4079 defer gpa.free(constituents);
4080
4081 for (constituents, mask) |*id, mask_elem| {
4082 id.* = switch (mask_elem.unwrap()) {
4083 .a_elem => |idx| try ng.extractVectorComponent(elem_ty, operand_a, idx),
4084 .b_elem => |idx| try ng.extractVectorComponent(elem_ty, operand_b, idx),
4085 .undef => try ng.spv.constUndef(elem_ty_id),
4086 };
4087 }
4088
4089 const result_ty_id = try ng.resolveType(result_ty, .direct);
4090 return try ng.constructComposite(result_ty_id, constituents);
4091 }
4092
4093 fn indicesToIds(self: *NavGen, indices: []const u32) ![]Id {
4094 const ids = try self.gpa.alloc(Id, indices.len);
4095 errdefer self.gpa.free(ids);
4096 for (indices, ids) |index, *id| {
4097 id.* = try self.constInt(Type.u32, index);
4098 }
4099
4100 return ids;
4101 }
4102
4103 fn accessChainId(
4104 self: *NavGen,
4105 result_ty_id: Id,
4106 base: Id,
4107 indices: []const Id,
4108 ) !Id {
4109 const result_id = self.spv.allocId();
4110 try self.func.body.emit(self.spv.gpa, .OpInBoundsAccessChain, .{
4111 .id_result_type = result_ty_id,
4112 .id_result = result_id,
4113 .base = base,
4114 .indexes = indices,
4115 });
4116 return result_id;
4117 }
4118
4119 /// AccessChain is essentially PtrAccessChain with 0 as initial argument. The effective
4120 /// difference lies in whether the resulting type of the first dereference will be the
4121 /// same as that of the base pointer, or that of a dereferenced base pointer. AccessChain
4122 /// is the latter and PtrAccessChain is the former.
4123 fn accessChain(
4124 self: *NavGen,
4125 result_ty_id: Id,
4126 base: Id,
4127 indices: []const u32,
4128 ) !Id {
4129 const ids = try self.indicesToIds(indices);
4130 defer self.gpa.free(ids);
4131 return try self.accessChainId(result_ty_id, base, ids);
4132 }
4133
4134 fn ptrAccessChain(
4135 self: *NavGen,
4136 result_ty_id: Id,
4137 base: Id,
4138 element: Id,
4139 indices: []const u32,
4140 ) !Id {
4141 const ids = try self.indicesToIds(indices);
4142 defer self.gpa.free(ids);
4143
4144 const result_id = self.spv.allocId();
4145 switch (self.spv.target.os.tag) {
4146 .opencl, .amdhsa => {
4147 try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{
4148 .id_result_type = result_ty_id,
4149 .id_result = result_id,
4150 .base = base,
4151 .element = element,
4152 .indexes = ids,
4153 });
4154 },
4155 else => {
4156 try self.func.body.emit(self.spv.gpa, .OpPtrAccessChain, .{
4157 .id_result_type = result_ty_id,
4158 .id_result = result_id,
4159 .base = base,
4160 .element = element,
4161 .indexes = ids,
4162 });
4163 },
4164 }
4165 return result_id;
4166 }
4167
4168 fn ptrAdd(self: *NavGen, result_ty: Type, ptr_ty: Type, ptr_id: Id, offset_id: Id) !Id {
4169 const zcu = self.pt.zcu;
4170 const result_ty_id = try self.resolveType(result_ty, .direct);
4171
4172 switch (ptr_ty.ptrSize(zcu)) {
4173 .one => {
4174 // Pointer to array
4175 // TODO: Is this correct?
4176 return try self.accessChainId(result_ty_id, ptr_id, &.{offset_id});
4177 },
4178 .c, .many => {
4179 return try self.ptrAccessChain(result_ty_id, ptr_id, offset_id, &.{});
4180 },
4181 .slice => {
4182 // TODO: This is probably incorrect. A slice should be returned here, though this is what llvm does.
4183 const slice_ptr_id = try self.extractField(result_ty, ptr_id, 0);
4184 return try self.ptrAccessChain(result_ty_id, slice_ptr_id, offset_id, &.{});
4185 },
4186 }
4187 }
4188
4189 fn airPtrAdd(self: *NavGen, inst: Air.Inst.Index) !?Id {
4190 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4191 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
4192 const ptr_id = try self.resolve(bin_op.lhs);
4193 const offset_id = try self.resolve(bin_op.rhs);
4194 const ptr_ty = self.typeOf(bin_op.lhs);
4195 const result_ty = self.typeOfIndex(inst);
4196
4197 return try self.ptrAdd(result_ty, ptr_ty, ptr_id, offset_id);
4198 }
4199
4200 fn airPtrSub(self: *NavGen, inst: Air.Inst.Index) !?Id {
4201 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4202 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
4203 const ptr_id = try self.resolve(bin_op.lhs);
4204 const ptr_ty = self.typeOf(bin_op.lhs);
4205 const offset_id = try self.resolve(bin_op.rhs);
4206 const offset_ty = self.typeOf(bin_op.rhs);
4207 const offset_ty_id = try self.resolveType(offset_ty, .direct);
4208 const result_ty = self.typeOfIndex(inst);
4209
4210 const negative_offset_id = self.spv.allocId();
4211 try self.func.body.emit(self.spv.gpa, .OpSNegate, .{
4212 .id_result_type = offset_ty_id,
4213 .id_result = negative_offset_id,
4214 .operand = offset_id,
4215 });
4216 return try self.ptrAdd(result_ty, ptr_ty, ptr_id, negative_offset_id);
4217 }
4218
4219 fn cmp(
4220 self: *NavGen,
4221 op: std.math.CompareOperator,
4222 lhs: Temporary,
4223 rhs: Temporary,
4224 ) !Temporary {
4225 const pt = self.pt;
4226 const zcu = pt.zcu;
4227 const ip = &zcu.intern_pool;
4228 const scalar_ty = lhs.ty.scalarType(zcu);
4229 const is_vector = lhs.ty.isVector(zcu);
4230
4231 switch (scalar_ty.zigTypeTag(zcu)) {
4232 .int, .bool, .float => {},
4233 .@"enum" => {
4234 assert(!is_vector);
4235 const ty = lhs.ty.intTagType(zcu);
4236 return try self.cmp(op, lhs.pun(ty), rhs.pun(ty));
4237 },
4238 .@"struct" => {
4239 const struct_ty = zcu.typeToPackedStruct(scalar_ty).?;
4240 const ty = Type.fromInterned(struct_ty.backingIntTypeUnordered(ip));
4241 return try self.cmp(op, lhs.pun(ty), rhs.pun(ty));
4242 },
4243 .error_set => {
4244 assert(!is_vector);
4245 const err_int_ty = try pt.errorIntType();
4246 return try self.cmp(op, lhs.pun(err_int_ty), rhs.pun(err_int_ty));
4247 },
4248 .pointer => {
4249 assert(!is_vector);
4250 // Note that while SPIR-V offers OpPtrEqual and OpPtrNotEqual, they are
4251 // currently not implemented in the SPIR-V LLVM translator. Thus, we emit these using
4252 // OpConvertPtrToU...
4253
4254 const usize_ty_id = try self.resolveType(Type.usize, .direct);
4255
4256 const lhs_int_id = self.spv.allocId();
4257 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{
4258 .id_result_type = usize_ty_id,
4259 .id_result = lhs_int_id,
4260 .pointer = try lhs.materialize(self),
4261 });
4262
4263 const rhs_int_id = self.spv.allocId();
4264 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{
4265 .id_result_type = usize_ty_id,
4266 .id_result = rhs_int_id,
4267 .pointer = try rhs.materialize(self),
4268 });
4269
4270 const lhs_int = Temporary.init(Type.usize, lhs_int_id);
4271 const rhs_int = Temporary.init(Type.usize, rhs_int_id);
4272 return try self.cmp(op, lhs_int, rhs_int);
4273 },
4274 .optional => {
4275 assert(!is_vector);
4276
4277 const ty = lhs.ty;
4278
4279 const payload_ty = ty.optionalChild(zcu);
4280 if (ty.optionalReprIsPayload(zcu)) {
4281 assert(payload_ty.hasRuntimeBitsIgnoreComptime(zcu));
4282 assert(!payload_ty.isSlice(zcu));
4283
4284 return try self.cmp(op, lhs.pun(payload_ty), rhs.pun(payload_ty));
4285 }
4286
4287 const lhs_id = try lhs.materialize(self);
4288 const rhs_id = try rhs.materialize(self);
4289
4290 const lhs_valid_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu))
4291 try self.extractField(Type.bool, lhs_id, 1)
4292 else
4293 try self.convertToDirect(Type.bool, lhs_id);
4294
4295 const rhs_valid_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu))
4296 try self.extractField(Type.bool, rhs_id, 1)
4297 else
4298 try self.convertToDirect(Type.bool, rhs_id);
4299
4300 const lhs_valid = Temporary.init(Type.bool, lhs_valid_id);
4301 const rhs_valid = Temporary.init(Type.bool, rhs_valid_id);
4302
4303 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
4304 return try self.cmp(op, lhs_valid, rhs_valid);
4305 }
4306
4307 // a = lhs_valid
4308 // b = rhs_valid
4309 // c = lhs_pl == rhs_pl
4310 //
4311 // For op == .eq we have:
4312 // a == b && a -> c
4313 // = a == b && (!a || c)
4314 //
4315 // For op == .neq we have
4316 // a == b && a -> c
4317 // = !(a == b && a -> c)
4318 // = a != b || !(a -> c
4319 // = a != b || !(!a || c)
4320 // = a != b || a && !c
4321
4322 const lhs_pl_id = try self.extractField(payload_ty, lhs_id, 0);
4323 const rhs_pl_id = try self.extractField(payload_ty, rhs_id, 0);
4324
4325 const lhs_pl = Temporary.init(payload_ty, lhs_pl_id);
4326 const rhs_pl = Temporary.init(payload_ty, rhs_pl_id);
4327
4328 return switch (op) {
4329 .eq => try self.buildBinary(
4330 .l_and,
4331 try self.cmp(.eq, lhs_valid, rhs_valid),
4332 try self.buildBinary(
4333 .l_or,
4334 try self.buildUnary(.l_not, lhs_valid),
4335 try self.cmp(.eq, lhs_pl, rhs_pl),
4336 ),
4337 ),
4338 .neq => try self.buildBinary(
4339 .l_or,
4340 try self.cmp(.neq, lhs_valid, rhs_valid),
4341 try self.buildBinary(
4342 .l_and,
4343 lhs_valid,
4344 try self.cmp(.neq, lhs_pl, rhs_pl),
4345 ),
4346 ),
4347 else => unreachable,
4348 };
4349 },
4350 else => |ty| return self.todo("implement cmp operation for '{s}' type", .{@tagName(ty)}),
4351 }
4352
4353 const info = self.arithmeticTypeInfo(scalar_ty);
4354 const pred: CmpPredicate = switch (info.class) {
4355 .composite_integer => unreachable, // TODO
4356 .float => switch (op) {
4357 .eq => .f_oeq,
4358 .neq => .f_une,
4359 .lt => .f_olt,
4360 .lte => .f_ole,
4361 .gt => .f_ogt,
4362 .gte => .f_oge,
4363 },
4364 .bool => switch (op) {
4365 .eq => .l_eq,
4366 .neq => .l_ne,
4367 else => unreachable,
4368 },
4369 .integer, .strange_integer => switch (info.signedness) {
4370 .signed => switch (op) {
4371 .eq => .i_eq,
4372 .neq => .i_ne,
4373 .lt => .s_lt,
4374 .lte => .s_le,
4375 .gt => .s_gt,
4376 .gte => .s_ge,
4377 },
4378 .unsigned => switch (op) {
4379 .eq => .i_eq,
4380 .neq => .i_ne,
4381 .lt => .u_lt,
4382 .lte => .u_le,
4383 .gt => .u_gt,
4384 .gte => .u_ge,
4385 },
4386 },
4387 };
4388
4389 return try self.buildCmp(pred, lhs, rhs);
4390 }
4391
4392 fn airCmp(
4393 self: *NavGen,
4394 inst: Air.Inst.Index,
4395 comptime op: std.math.CompareOperator,
4396 ) !?Id {
4397 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
4398 const lhs = try self.temporary(bin_op.lhs);
4399 const rhs = try self.temporary(bin_op.rhs);
4400
4401 const result = try self.cmp(op, lhs, rhs);
4402 return try result.materialize(self);
4403 }
4404
4405 fn airVectorCmp(self: *NavGen, inst: Air.Inst.Index) !?Id {
4406 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4407 const vec_cmp = self.air.extraData(Air.VectorCmp, ty_pl.payload).data;
4408 const lhs = try self.temporary(vec_cmp.lhs);
4409 const rhs = try self.temporary(vec_cmp.rhs);
4410 const op = vec_cmp.compareOperator();
4411
4412 const result = try self.cmp(op, lhs, rhs);
4413 return try result.materialize(self);
4414 }
4415
4416 /// Bitcast one type to another. Note: both types, input, output are expected in **direct** representation.
4417 fn bitCast(
4418 self: *NavGen,
4419 dst_ty: Type,
4420 src_ty: Type,
4421 src_id: Id,
4422 ) !Id {
4423 const zcu = self.pt.zcu;
4424 const src_ty_id = try self.resolveType(src_ty, .direct);
4425 const dst_ty_id = try self.resolveType(dst_ty, .direct);
4426
4427 const result_id = blk: {
4428 if (src_ty_id == dst_ty_id) break :blk src_id;
4429
4430 // TODO: Some more cases are missing here
4431 // See fn bitCast in llvm.zig
4432
4433 if (src_ty.zigTypeTag(zcu) == .int and dst_ty.isPtrAtRuntime(zcu)) {
4434 const result_id = self.spv.allocId();
4435 try self.func.body.emit(self.spv.gpa, .OpConvertUToPtr, .{
4436 .id_result_type = dst_ty_id,
4437 .id_result = result_id,
4438 .integer_value = src_id,
4439 });
4440 break :blk result_id;
4441 }
4442
4443 // We can only use OpBitcast for specific conversions: between numerical types, and
4444 // between pointers. If the resolved spir-v types fall into this category then emit OpBitcast,
4445 // otherwise use a temporary and perform a pointer cast.
4446 const can_bitcast = (src_ty.isNumeric(zcu) and dst_ty.isNumeric(zcu)) or (src_ty.isPtrAtRuntime(zcu) and dst_ty.isPtrAtRuntime(zcu));
4447 if (can_bitcast) {
4448 const result_id = self.spv.allocId();
4449 try self.func.body.emit(self.spv.gpa, .OpBitcast, .{
4450 .id_result_type = dst_ty_id,
4451 .id_result = result_id,
4452 .operand = src_id,
4453 });
4454
4455 break :blk result_id;
4456 }
4457
4458 const dst_ptr_ty_id = try self.ptrType(dst_ty, .function, .indirect);
4459
4460 const tmp_id = try self.alloc(src_ty, .{ .storage_class = .function });
4461 try self.store(src_ty, tmp_id, src_id, .{});
4462 const casted_ptr_id = self.spv.allocId();
4463 try self.func.body.emit(self.spv.gpa, .OpBitcast, .{
4464 .id_result_type = dst_ptr_ty_id,
4465 .id_result = casted_ptr_id,
4466 .operand = tmp_id,
4467 });
4468 break :blk try self.load(dst_ty, casted_ptr_id, .{});
4469 };
4470
4471 // Because strange integers use sign-extended representation, we may need to normalize
4472 // the result here.
4473 // TODO: This detail could cause stuff like @as(*const i1, @ptrCast(&@as(u1, 1))) to break
4474 // should we change the representation of strange integers?
4475 if (dst_ty.zigTypeTag(zcu) == .int) {
4476 const info = self.arithmeticTypeInfo(dst_ty);
4477 const result = try self.normalize(Temporary.init(dst_ty, result_id), info);
4478 return try result.materialize(self);
4479 }
4480
4481 return result_id;
4482 }
4483
4484 fn airBitCast(self: *NavGen, inst: Air.Inst.Index) !?Id {
4485 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4486 const operand_ty = self.typeOf(ty_op.operand);
4487 const result_ty = self.typeOfIndex(inst);
4488 if (operand_ty.toIntern() == .bool_type) {
4489 const operand = try self.temporary(ty_op.operand);
4490 const result = try self.intFromBool(operand);
4491 return try result.materialize(self);
4492 }
4493 const operand_id = try self.resolve(ty_op.operand);
4494 return try self.bitCast(result_ty, operand_ty, operand_id);
4495 }
4496
4497 fn airIntCast(self: *NavGen, inst: Air.Inst.Index) !?Id {
4498 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4499 const src = try self.temporary(ty_op.operand);
4500 const dst_ty = self.typeOfIndex(inst);
4501
4502 const src_info = self.arithmeticTypeInfo(src.ty);
4503 const dst_info = self.arithmeticTypeInfo(dst_ty);
4504
4505 if (src_info.backing_bits == dst_info.backing_bits) {
4506 return try src.materialize(self);
4507 }
4508
4509 const converted = try self.buildConvert(dst_ty, src);
4510
4511 // Make sure to normalize the result if shrinking.
4512 // Because strange ints are sign extended in their backing
4513 // type, we don't need to normalize when growing the type. The
4514 // representation is already the same.
4515 const result = if (dst_info.bits < src_info.bits)
4516 try self.normalize(converted, dst_info)
4517 else
4518 converted;
4519
4520 return try result.materialize(self);
4521 }
4522
4523 fn intFromPtr(self: *NavGen, operand_id: Id) !Id {
4524 const result_type_id = try self.resolveType(Type.usize, .direct);
4525 const result_id = self.spv.allocId();
4526 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{
4527 .id_result_type = result_type_id,
4528 .id_result = result_id,
4529 .pointer = operand_id,
4530 });
4531 return result_id;
4532 }
4533
4534 fn airFloatFromInt(self: *NavGen, inst: Air.Inst.Index) !?Id {
4535 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4536 const operand_ty = self.typeOf(ty_op.operand);
4537 const operand_id = try self.resolve(ty_op.operand);
4538 const result_ty = self.typeOfIndex(inst);
4539 return try self.floatFromInt(result_ty, operand_ty, operand_id);
4540 }
4541
4542 fn floatFromInt(self: *NavGen, result_ty: Type, operand_ty: Type, operand_id: Id) !Id {
4543 const operand_info = self.arithmeticTypeInfo(operand_ty);
4544 const result_id = self.spv.allocId();
4545 const result_ty_id = try self.resolveType(result_ty, .direct);
4546 switch (operand_info.signedness) {
4547 .signed => try self.func.body.emit(self.spv.gpa, .OpConvertSToF, .{
4548 .id_result_type = result_ty_id,
4549 .id_result = result_id,
4550 .signed_value = operand_id,
4551 }),
4552 .unsigned => try self.func.body.emit(self.spv.gpa, .OpConvertUToF, .{
4553 .id_result_type = result_ty_id,
4554 .id_result = result_id,
4555 .unsigned_value = operand_id,
4556 }),
4557 }
4558 return result_id;
4559 }
4560
4561 fn airIntFromFloat(self: *NavGen, inst: Air.Inst.Index) !?Id {
4562 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4563 const operand_id = try self.resolve(ty_op.operand);
4564 const result_ty = self.typeOfIndex(inst);
4565 return try self.intFromFloat(result_ty, operand_id);
4566 }
4567
4568 fn intFromFloat(self: *NavGen, result_ty: Type, operand_id: Id) !Id {
4569 const result_info = self.arithmeticTypeInfo(result_ty);
4570 const result_ty_id = try self.resolveType(result_ty, .direct);
4571 const result_id = self.spv.allocId();
4572 switch (result_info.signedness) {
4573 .signed => try self.func.body.emit(self.spv.gpa, .OpConvertFToS, .{
4574 .id_result_type = result_ty_id,
4575 .id_result = result_id,
4576 .float_value = operand_id,
4577 }),
4578 .unsigned => try self.func.body.emit(self.spv.gpa, .OpConvertFToU, .{
4579 .id_result_type = result_ty_id,
4580 .id_result = result_id,
4581 .float_value = operand_id,
4582 }),
4583 }
4584 return result_id;
4585 }
4586
4587 fn airFloatCast(self: *NavGen, inst: Air.Inst.Index) !?Id {
4588 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4589 const operand = try self.temporary(ty_op.operand);
4590 const dest_ty = self.typeOfIndex(inst);
4591 const result = try self.buildConvert(dest_ty, operand);
4592 return try result.materialize(self);
4593 }
4594
4595 fn airNot(self: *NavGen, inst: Air.Inst.Index) !?Id {
4596 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4597 const operand = try self.temporary(ty_op.operand);
4598 const result_ty = self.typeOfIndex(inst);
4599 const info = self.arithmeticTypeInfo(result_ty);
4600
4601 const result = switch (info.class) {
4602 .bool => try self.buildUnary(.l_not, operand),
4603 .float => unreachable,
4604 .composite_integer => unreachable, // TODO
4605 .strange_integer, .integer => blk: {
4606 const complement = try self.buildUnary(.bit_not, operand);
4607 break :blk try self.normalize(complement, info);
4608 },
4609 };
4610
4611 return try result.materialize(self);
4612 }
4613
4614 fn airArrayToSlice(self: *NavGen, inst: Air.Inst.Index) !?Id {
4615 const pt = self.pt;
4616 const zcu = pt.zcu;
4617 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4618 const array_ptr_ty = self.typeOf(ty_op.operand);
4619 const array_ty = array_ptr_ty.childType(zcu);
4620 const slice_ty = self.typeOfIndex(inst);
4621 const elem_ptr_ty = slice_ty.slicePtrFieldType(zcu);
4622
4623 const elem_ptr_ty_id = try self.resolveType(elem_ptr_ty, .direct);
4624
4625 const array_ptr_id = try self.resolve(ty_op.operand);
4626 const len_id = try self.constInt(Type.usize, array_ty.arrayLen(zcu));
4627
4628 const elem_ptr_id = if (!array_ty.hasRuntimeBitsIgnoreComptime(zcu))
4629 // Note: The pointer is something like *opaque{}, so we need to bitcast it to the element type.
4630 try self.bitCast(elem_ptr_ty, array_ptr_ty, array_ptr_id)
4631 else
4632 // Convert the pointer-to-array to a pointer to the first element.
4633 try self.accessChain(elem_ptr_ty_id, array_ptr_id, &.{0});
4634
4635 const slice_ty_id = try self.resolveType(slice_ty, .direct);
4636 return try self.constructComposite(slice_ty_id, &.{ elem_ptr_id, len_id });
4637 }
4638
4639 fn airSlice(self: *NavGen, inst: Air.Inst.Index) !?Id {
4640 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4641 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
4642 const ptr_id = try self.resolve(bin_op.lhs);
4643 const len_id = try self.resolve(bin_op.rhs);
4644 const slice_ty = self.typeOfIndex(inst);
4645 const slice_ty_id = try self.resolveType(slice_ty, .direct);
4646 return try self.constructComposite(slice_ty_id, &.{ ptr_id, len_id });
4647 }
4648
4649 fn airAggregateInit(self: *NavGen, inst: Air.Inst.Index) !?Id {
4650 const pt = self.pt;
4651 const zcu = pt.zcu;
4652 const ip = &zcu.intern_pool;
4653 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4654 const result_ty = self.typeOfIndex(inst);
4655 const len: usize = @intCast(result_ty.arrayLen(zcu));
4656 const elements: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[ty_pl.payload..][0..len]);
4657
4658 switch (result_ty.zigTypeTag(zcu)) {
4659 .@"struct" => {
4660 if (zcu.typeToPackedStruct(result_ty)) |struct_type| {
4661 comptime assert(Type.packed_struct_layout_version == 2);
4662 const backing_int_ty = Type.fromInterned(struct_type.backingIntTypeUnordered(ip));
4663 var running_int_id = try self.constInt(backing_int_ty, 0);
4664 var running_bits: u16 = 0;
4665 for (struct_type.field_types.get(ip), elements) |field_ty_ip, element| {
4666 const field_ty = Type.fromInterned(field_ty_ip);
4667 if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;
4668 const field_id = try self.resolve(element);
4669 const ty_bit_size: u16 = @intCast(field_ty.bitSize(zcu));
4670 const field_int_ty = try self.pt.intType(.unsigned, ty_bit_size);
4671 const field_int_id = blk: {
4672 if (field_ty.isPtrAtRuntime(zcu)) {
4673 assert(self.spv.target.cpu.arch == .spirv64 and
4674 field_ty.ptrAddressSpace(zcu) == .storage_buffer);
4675 break :blk try self.intFromPtr(field_id);
4676 }
4677 break :blk try self.bitCast(field_int_ty, field_ty, field_id);
4678 };
4679 const shift_rhs = try self.constInt(backing_int_ty, running_bits);
4680 const extended_int_conv = try self.buildConvert(backing_int_ty, .{
4681 .ty = field_int_ty,
4682 .value = .{ .singleton = field_int_id },
4683 });
4684 const shifted = try self.buildBinary(.sll, extended_int_conv, .{
4685 .ty = backing_int_ty,
4686 .value = .{ .singleton = shift_rhs },
4687 });
4688 const running_int_tmp = try self.buildBinary(
4689 .bit_or,
4690 .{ .ty = backing_int_ty, .value = .{ .singleton = running_int_id } },
4691 shifted,
4692 );
4693 running_int_id = try running_int_tmp.materialize(self);
4694 running_bits += ty_bit_size;
4695 }
4696 return running_int_id;
4697 }
4698
4699 const types = try self.gpa.alloc(Type, elements.len);
4700 defer self.gpa.free(types);
4701 const constituents = try self.gpa.alloc(Id, elements.len);
4702 defer self.gpa.free(constituents);
4703 var index: usize = 0;
4704
4705 switch (ip.indexToKey(result_ty.toIntern())) {
4706 .tuple_type => |tuple| {
4707 for (tuple.types.get(ip), elements, 0..) |field_ty, element, i| {
4708 if ((try result_ty.structFieldValueComptime(pt, i)) != null) continue;
4709 assert(Type.fromInterned(field_ty).hasRuntimeBits(zcu));
4710
4711 const id = try self.resolve(element);
4712 types[index] = Type.fromInterned(field_ty);
4713 constituents[index] = try self.convertToIndirect(Type.fromInterned(field_ty), id);
4714 index += 1;
4715 }
4716 },
4717 .struct_type => {
4718 const struct_type = ip.loadStructType(result_ty.toIntern());
4719 var it = struct_type.iterateRuntimeOrder(ip);
4720 for (elements, 0..) |element, i| {
4721 const field_index = it.next().?;
4722 if ((try result_ty.structFieldValueComptime(pt, i)) != null) continue;
4723 const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[field_index]);
4724 assert(field_ty.hasRuntimeBitsIgnoreComptime(zcu));
4725
4726 const id = try self.resolve(element);
4727 types[index] = field_ty;
4728 constituents[index] = try self.convertToIndirect(field_ty, id);
4729 index += 1;
4730 }
4731 },
4732 else => unreachable,
4733 }
4734
4735 const result_ty_id = try self.resolveType(result_ty, .direct);
4736 return try self.constructComposite(result_ty_id, constituents[0..index]);
4737 },
4738 .vector => {
4739 const n_elems = result_ty.vectorLen(zcu);
4740 const elem_ids = try self.gpa.alloc(Id, n_elems);
4741 defer self.gpa.free(elem_ids);
4742
4743 for (elements, 0..) |element, i| {
4744 elem_ids[i] = try self.resolve(element);
4745 }
4746
4747 const result_ty_id = try self.resolveType(result_ty, .direct);
4748 return try self.constructComposite(result_ty_id, elem_ids);
4749 },
4750 .array => {
4751 const array_info = result_ty.arrayInfo(zcu);
4752 const n_elems: usize = @intCast(result_ty.arrayLenIncludingSentinel(zcu));
4753 const elem_ids = try self.gpa.alloc(Id, n_elems);
4754 defer self.gpa.free(elem_ids);
4755
4756 for (elements, 0..) |element, i| {
4757 const id = try self.resolve(element);
4758 elem_ids[i] = try self.convertToIndirect(array_info.elem_type, id);
4759 }
4760
4761 if (array_info.sentinel) |sentinel_val| {
4762 elem_ids[n_elems - 1] = try self.constant(array_info.elem_type, sentinel_val, .indirect);
4763 }
4764
4765 const result_ty_id = try self.resolveType(result_ty, .direct);
4766 return try self.constructComposite(result_ty_id, elem_ids);
4767 },
4768 else => unreachable,
4769 }
4770 }
4771
4772 fn sliceOrArrayLen(self: *NavGen, operand_id: Id, ty: Type) !Id {
4773 const pt = self.pt;
4774 const zcu = pt.zcu;
4775 switch (ty.ptrSize(zcu)) {
4776 .slice => return self.extractField(Type.usize, operand_id, 1),
4777 .one => {
4778 const array_ty = ty.childType(zcu);
4779 const elem_ty = array_ty.childType(zcu);
4780 const abi_size = elem_ty.abiSize(zcu);
4781 const size = array_ty.arrayLenIncludingSentinel(zcu) * abi_size;
4782 return try self.constInt(Type.usize, size);
4783 },
4784 .many, .c => unreachable,
4785 }
4786 }
4787
4788 fn sliceOrArrayPtr(self: *NavGen, operand_id: Id, ty: Type) !Id {
4789 const zcu = self.pt.zcu;
4790 if (ty.isSlice(zcu)) {
4791 const ptr_ty = ty.slicePtrFieldType(zcu);
4792 return self.extractField(ptr_ty, operand_id, 0);
4793 }
4794 return operand_id;
4795 }
4796
4797 fn airMemcpy(self: *NavGen, inst: Air.Inst.Index) !void {
4798 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
4799 const dest_slice = try self.resolve(bin_op.lhs);
4800 const src_slice = try self.resolve(bin_op.rhs);
4801 const dest_ty = self.typeOf(bin_op.lhs);
4802 const src_ty = self.typeOf(bin_op.rhs);
4803 const dest_ptr = try self.sliceOrArrayPtr(dest_slice, dest_ty);
4804 const src_ptr = try self.sliceOrArrayPtr(src_slice, src_ty);
4805 const len = try self.sliceOrArrayLen(dest_slice, dest_ty);
4806 try self.func.body.emit(self.spv.gpa, .OpCopyMemorySized, .{
4807 .target = dest_ptr,
4808 .source = src_ptr,
4809 .size = len,
4810 });
4811 }
4812
4813 fn airMemmove(self: *NavGen, inst: Air.Inst.Index) !void {
4814 _ = inst;
4815 return self.fail("TODO implement airMemcpy for spirv", .{});
4816 }
4817
4818 fn airSliceField(self: *NavGen, inst: Air.Inst.Index, field: u32) !?Id {
4819 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4820 const field_ty = self.typeOfIndex(inst);
4821 const operand_id = try self.resolve(ty_op.operand);
4822 return try self.extractField(field_ty, operand_id, field);
4823 }
4824
4825 fn airSliceElemPtr(self: *NavGen, inst: Air.Inst.Index) !?Id {
4826 const zcu = self.pt.zcu;
4827 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4828 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
4829 const slice_ty = self.typeOf(bin_op.lhs);
4830 if (!slice_ty.isVolatilePtr(zcu) and self.liveness.isUnused(inst)) return null;
4831
4832 const slice_id = try self.resolve(bin_op.lhs);
4833 const index_id = try self.resolve(bin_op.rhs);
4834
4835 const ptr_ty = self.typeOfIndex(inst);
4836 const ptr_ty_id = try self.resolveType(ptr_ty, .direct);
4837
4838 const slice_ptr = try self.extractField(ptr_ty, slice_id, 0);
4839 return try self.ptrAccessChain(ptr_ty_id, slice_ptr, index_id, &.{});
4840 }
4841
4842 fn airSliceElemVal(self: *NavGen, inst: Air.Inst.Index) !?Id {
4843 const zcu = self.pt.zcu;
4844 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
4845 const slice_ty = self.typeOf(bin_op.lhs);
4846 if (!slice_ty.isVolatilePtr(zcu) and self.liveness.isUnused(inst)) return null;
4847
4848 const slice_id = try self.resolve(bin_op.lhs);
4849 const index_id = try self.resolve(bin_op.rhs);
4850
4851 const ptr_ty = slice_ty.slicePtrFieldType(zcu);
4852 const ptr_ty_id = try self.resolveType(ptr_ty, .direct);
4853
4854 const slice_ptr = try self.extractField(ptr_ty, slice_id, 0);
4855 const elem_ptr = try self.ptrAccessChain(ptr_ty_id, slice_ptr, index_id, &.{});
4856 return try self.load(slice_ty.childType(zcu), elem_ptr, .{ .is_volatile = slice_ty.isVolatilePtr(zcu) });
4857 }
4858
4859 fn ptrElemPtr(self: *NavGen, ptr_ty: Type, ptr_id: Id, index_id: Id) !Id {
4860 const zcu = self.pt.zcu;
4861 // Construct new pointer type for the resulting pointer
4862 const elem_ty = ptr_ty.elemType2(zcu); // use elemType() so that we get T for *[N]T.
4863 const elem_ptr_ty_id = try self.ptrType(elem_ty, self.spvStorageClass(ptr_ty.ptrAddressSpace(zcu)), .indirect);
4864 if (ptr_ty.isSinglePointer(zcu)) {
4865 // Pointer-to-array. In this case, the resulting pointer is not of the same type
4866 // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain.
4867 return try self.accessChainId(elem_ptr_ty_id, ptr_id, &.{index_id});
4868 } else {
4869 // Resulting pointer type is the same as the ptr_ty, so use ptrAccessChain
4870 return try self.ptrAccessChain(elem_ptr_ty_id, ptr_id, index_id, &.{});
4871 }
4872 }
4873
4874 fn airPtrElemPtr(self: *NavGen, inst: Air.Inst.Index) !?Id {
4875 const pt = self.pt;
4876 const zcu = pt.zcu;
4877 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4878 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
4879 const src_ptr_ty = self.typeOf(bin_op.lhs);
4880 const elem_ty = src_ptr_ty.childType(zcu);
4881 const ptr_id = try self.resolve(bin_op.lhs);
4882
4883 if (!elem_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
4884 const dst_ptr_ty = self.typeOfIndex(inst);
4885 return try self.bitCast(dst_ptr_ty, src_ptr_ty, ptr_id);
4886 }
4887
4888 const index_id = try self.resolve(bin_op.rhs);
4889 return try self.ptrElemPtr(src_ptr_ty, ptr_id, index_id);
4890 }
4891
4892 fn airArrayElemVal(self: *NavGen, inst: Air.Inst.Index) !?Id {
4893 const zcu = self.pt.zcu;
4894 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
4895 const array_ty = self.typeOf(bin_op.lhs);
4896 const elem_ty = array_ty.childType(zcu);
4897 const array_id = try self.resolve(bin_op.lhs);
4898 const index_id = try self.resolve(bin_op.rhs);
4899
4900 // SPIR-V doesn't have an array indexing function for some damn reason.
4901 // For now, just generate a temporary and use that.
4902 // TODO: This backend probably also should use isByRef from llvm...
4903
4904 const is_vector = array_ty.isVector(zcu);
4905
4906 const elem_repr: Repr = if (is_vector) .direct else .indirect;
4907 const ptr_array_ty_id = try self.ptrType(array_ty, .function, .direct);
4908 const ptr_elem_ty_id = try self.ptrType(elem_ty, .function, elem_repr);
4909
4910 const tmp_id = self.spv.allocId();
4911 try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{
4912 .id_result_type = ptr_array_ty_id,
4913 .id_result = tmp_id,
4914 .storage_class = .function,
4915 });
4916
4917 try self.func.body.emit(self.spv.gpa, .OpStore, .{
4918 .pointer = tmp_id,
4919 .object = array_id,
4920 });
4921
4922 const elem_ptr_id = try self.accessChainId(ptr_elem_ty_id, tmp_id, &.{index_id});
4923
4924 const result_id = self.spv.allocId();
4925 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
4926 .id_result_type = try self.resolveType(elem_ty, elem_repr),
4927 .id_result = result_id,
4928 .pointer = elem_ptr_id,
4929 });
4930
4931 if (is_vector) {
4932 // Result is already in direct representation
4933 return result_id;
4934 }
4935
4936 // This is an array type; the elements are stored in indirect representation.
4937 // We have to convert the type to direct.
4938
4939 return try self.convertToDirect(elem_ty, result_id);
4940 }
4941
4942 fn airPtrElemVal(self: *NavGen, inst: Air.Inst.Index) !?Id {
4943 const zcu = self.pt.zcu;
4944 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
4945 const ptr_ty = self.typeOf(bin_op.lhs);
4946 const elem_ty = self.typeOfIndex(inst);
4947 const ptr_id = try self.resolve(bin_op.lhs);
4948 const index_id = try self.resolve(bin_op.rhs);
4949 const elem_ptr_id = try self.ptrElemPtr(ptr_ty, ptr_id, index_id);
4950 return try self.load(elem_ty, elem_ptr_id, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) });
4951 }
4952
4953 fn airVectorStoreElem(self: *NavGen, inst: Air.Inst.Index) !void {
4954 const zcu = self.pt.zcu;
4955 const data = self.air.instructions.items(.data)[@intFromEnum(inst)].vector_store_elem;
4956 const extra = self.air.extraData(Air.Bin, data.payload).data;
4957
4958 const vector_ptr_ty = self.typeOf(data.vector_ptr);
4959 const vector_ty = vector_ptr_ty.childType(zcu);
4960 const scalar_ty = vector_ty.scalarType(zcu);
4961
4962 const storage_class = self.spvStorageClass(vector_ptr_ty.ptrAddressSpace(zcu));
4963 const scalar_ptr_ty_id = try self.ptrType(scalar_ty, storage_class, .indirect);
4964
4965 const vector_ptr = try self.resolve(data.vector_ptr);
4966 const index = try self.resolve(extra.lhs);
4967 const operand = try self.resolve(extra.rhs);
4968
4969 const elem_ptr_id = try self.accessChainId(scalar_ptr_ty_id, vector_ptr, &.{index});
4970 try self.store(scalar_ty, elem_ptr_id, operand, .{
4971 .is_volatile = vector_ptr_ty.isVolatilePtr(zcu),
4972 });
4973 }
4974
4975 fn airSetUnionTag(self: *NavGen, inst: Air.Inst.Index) !void {
4976 const zcu = self.pt.zcu;
4977 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
4978 const un_ptr_ty = self.typeOf(bin_op.lhs);
4979 const un_ty = un_ptr_ty.childType(zcu);
4980 const layout = self.unionLayout(un_ty);
4981
4982 if (layout.tag_size == 0) return;
4983
4984 const tag_ty = un_ty.unionTagTypeSafety(zcu).?;
4985 const tag_ptr_ty_id = try self.ptrType(tag_ty, self.spvStorageClass(un_ptr_ty.ptrAddressSpace(zcu)), .indirect);
4986
4987 const union_ptr_id = try self.resolve(bin_op.lhs);
4988 const new_tag_id = try self.resolve(bin_op.rhs);
4989
4990 if (!layout.has_payload) {
4991 try self.store(tag_ty, union_ptr_id, new_tag_id, .{ .is_volatile = un_ptr_ty.isVolatilePtr(zcu) });
4992 } else {
4993 const ptr_id = try self.accessChain(tag_ptr_ty_id, union_ptr_id, &.{layout.tag_index});
4994 try self.store(tag_ty, ptr_id, new_tag_id, .{ .is_volatile = un_ptr_ty.isVolatilePtr(zcu) });
4995 }
4996 }
4997
4998 fn airGetUnionTag(self: *NavGen, inst: Air.Inst.Index) !?Id {
4999 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5000 const un_ty = self.typeOf(ty_op.operand);
5001
5002 const zcu = self.pt.zcu;
5003 const layout = self.unionLayout(un_ty);
5004 if (layout.tag_size == 0) return null;
5005
5006 const union_handle = try self.resolve(ty_op.operand);
5007 if (!layout.has_payload) return union_handle;
5008
5009 const tag_ty = un_ty.unionTagTypeSafety(zcu).?;
5010 return try self.extractField(tag_ty, union_handle, layout.tag_index);
5011 }
5012
5013 fn unionInit(
5014 self: *NavGen,
5015 ty: Type,
5016 active_field: u32,
5017 payload: ?Id,
5018 ) !Id {
5019 // To initialize a union, generate a temporary variable with the
5020 // union type, then get the field pointer and pointer-cast it to the
5021 // right type to store it. Finally load the entire union.
5022
5023 // Note: The result here is not cached, because it generates runtime code.
5024
5025 const pt = self.pt;
5026 const zcu = pt.zcu;
5027 const ip = &zcu.intern_pool;
5028 const union_ty = zcu.typeToUnion(ty).?;
5029 const tag_ty = Type.fromInterned(union_ty.enum_tag_ty);
5030
5031 const layout = self.unionLayout(ty);
5032 const payload_ty = Type.fromInterned(union_ty.field_types.get(ip)[active_field]);
5033
5034 if (union_ty.flagsUnordered(ip).layout == .@"packed") {
5035 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
5036 const int_ty = try pt.intType(.unsigned, @intCast(ty.bitSize(zcu)));
5037 return self.constInt(int_ty, 0);
5038 }
5039
5040 assert(payload != null);
5041 if (payload_ty.isInt(zcu)) {
5042 if (ty.bitSize(zcu) == payload_ty.bitSize(zcu)) {
5043 return self.bitCast(ty, payload_ty, payload.?);
5044 }
5045
5046 const trunc = try self.buildConvert(ty, .{ .ty = payload_ty, .value = .{ .singleton = payload.? } });
5047 return try trunc.materialize(self);
5048 }
5049
5050 const payload_int_ty = try pt.intType(.unsigned, @intCast(payload_ty.bitSize(zcu)));
5051 const payload_int = if (payload_ty.ip_index == .bool_type)
5052 try self.convertToIndirect(payload_ty, payload.?)
5053 else
5054 try self.bitCast(payload_int_ty, payload_ty, payload.?);
5055 const trunc = try self.buildConvert(ty, .{ .ty = payload_int_ty, .value = .{ .singleton = payload_int } });
5056 return try trunc.materialize(self);
5057 }
5058
5059 const tag_int = if (layout.tag_size != 0) blk: {
5060 const tag_val = try pt.enumValueFieldIndex(tag_ty, active_field);
5061 const tag_int_val = try tag_val.intFromEnum(tag_ty, pt);
5062 break :blk tag_int_val.toUnsignedInt(zcu);
5063 } else 0;
5064
5065 if (!layout.has_payload) {
5066 return try self.constInt(tag_ty, tag_int);
5067 }
5068
5069 const tmp_id = try self.alloc(ty, .{ .storage_class = .function });
5070
5071 if (layout.tag_size != 0) {
5072 const tag_ptr_ty_id = try self.ptrType(tag_ty, .function, .indirect);
5073 const ptr_id = try self.accessChain(tag_ptr_ty_id, tmp_id, &.{@as(u32, @intCast(layout.tag_index))});
5074 const tag_id = try self.constInt(tag_ty, tag_int);
5075 try self.store(tag_ty, ptr_id, tag_id, .{});
5076 }
5077
5078 if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
5079 const pl_ptr_ty_id = try self.ptrType(layout.payload_ty, .function, .indirect);
5080 const pl_ptr_id = try self.accessChain(pl_ptr_ty_id, tmp_id, &.{layout.payload_index});
5081 const active_pl_ptr_id = if (!layout.payload_ty.eql(payload_ty, zcu)) blk: {
5082 const active_pl_ptr_ty_id = try self.ptrType(payload_ty, .function, .indirect);
5083 const active_pl_ptr_id = self.spv.allocId();
5084 try self.func.body.emit(self.spv.gpa, .OpBitcast, .{
5085 .id_result_type = active_pl_ptr_ty_id,
5086 .id_result = active_pl_ptr_id,
5087 .operand = pl_ptr_id,
5088 });
5089 break :blk active_pl_ptr_id;
5090 } else pl_ptr_id;
5091
5092 try self.store(payload_ty, active_pl_ptr_id, payload.?, .{});
5093 } else {
5094 assert(payload == null);
5095 }
5096
5097 // Just leave the padding fields uninitialized...
5098 // TODO: Or should we initialize them with undef explicitly?
5099
5100 return try self.load(ty, tmp_id, .{});
5101 }
5102
5103 fn airUnionInit(self: *NavGen, inst: Air.Inst.Index) !?Id {
5104 const pt = self.pt;
5105 const zcu = pt.zcu;
5106 const ip = &zcu.intern_pool;
5107 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
5108 const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data;
5109 const ty = self.typeOfIndex(inst);
5110
5111 const union_obj = zcu.typeToUnion(ty).?;
5112 const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[extra.field_index]);
5113 const payload = if (field_ty.hasRuntimeBitsIgnoreComptime(zcu))
5114 try self.resolve(extra.init)
5115 else
5116 null;
5117 return try self.unionInit(ty, extra.field_index, payload);
5118 }
5119
5120 fn airStructFieldVal(self: *NavGen, inst: Air.Inst.Index) !?Id {
5121 const pt = self.pt;
5122 const zcu = pt.zcu;
5123 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
5124 const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;
5125
5126 const object_ty = self.typeOf(struct_field.struct_operand);
5127 const object_id = try self.resolve(struct_field.struct_operand);
5128 const field_index = struct_field.field_index;
5129 const field_ty = object_ty.fieldType(field_index, zcu);
5130
5131 if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) return null;
5132
5133 switch (object_ty.zigTypeTag(zcu)) {
5134 .@"struct" => switch (object_ty.containerLayout(zcu)) {
5135 .@"packed" => {
5136 const struct_ty = zcu.typeToPackedStruct(object_ty).?;
5137 const bit_offset = zcu.structPackedFieldBitOffset(struct_ty, field_index);
5138 const bit_offset_id = try self.constInt(.u16, bit_offset);
5139 const signedness = if (field_ty.isInt(zcu)) field_ty.intInfo(zcu).signedness else .unsigned;
5140 const field_bit_size: u16 = @intCast(field_ty.bitSize(zcu));
5141 const field_int_ty = try pt.intType(signedness, field_bit_size);
5142 const shift_lhs: Temporary = .{ .ty = object_ty, .value = .{ .singleton = object_id } };
5143 const shift = try self.buildBinary(.srl, shift_lhs, .{ .ty = .u16, .value = .{ .singleton = bit_offset_id } });
5144 const mask_id = try self.constInt(object_ty, (@as(u64, 1) << @as(u6, @intCast(field_bit_size))) - 1);
5145 const masked = try self.buildBinary(.bit_and, shift, .{ .ty = object_ty, .value = .{ .singleton = mask_id } });
5146 const result_id = blk: {
5147 if (self.backingIntBits(field_bit_size).@"0" == self.backingIntBits(@intCast(object_ty.bitSize(zcu))).@"0")
5148 break :blk try self.bitCast(field_int_ty, object_ty, try masked.materialize(self));
5149 const trunc = try self.buildConvert(field_int_ty, masked);
5150 break :blk try trunc.materialize(self);
5151 };
5152 if (field_ty.ip_index == .bool_type) return try self.convertToDirect(.bool, result_id);
5153 if (field_ty.isInt(zcu)) return result_id;
5154 return try self.bitCast(field_ty, field_int_ty, result_id);
5155 },
5156 else => return try self.extractField(field_ty, object_id, field_index),
5157 },
5158 .@"union" => switch (object_ty.containerLayout(zcu)) {
5159 .@"packed" => {
5160 const backing_int_ty = try pt.intType(.unsigned, @intCast(object_ty.bitSize(zcu)));
5161 const signedness = if (field_ty.isInt(zcu)) field_ty.intInfo(zcu).signedness else .unsigned;
5162 const field_bit_size: u16 = @intCast(field_ty.bitSize(zcu));
5163 const int_ty = try pt.intType(signedness, field_bit_size);
5164 const mask_id = try self.constInt(backing_int_ty, (@as(u64, 1) << @as(u6, @intCast(field_bit_size))) - 1);
5165 const masked = try self.buildBinary(
5166 .bit_and,
5167 .{ .ty = backing_int_ty, .value = .{ .singleton = object_id } },
5168 .{ .ty = backing_int_ty, .value = .{ .singleton = mask_id } },
5169 );
5170 const result_id = blk: {
5171 if (self.backingIntBits(field_bit_size).@"0" == self.backingIntBits(@intCast(backing_int_ty.bitSize(zcu))).@"0")
5172 break :blk try self.bitCast(int_ty, backing_int_ty, try masked.materialize(self));
5173 const trunc = try self.buildConvert(int_ty, masked);
5174 break :blk try trunc.materialize(self);
5175 };
5176 if (field_ty.ip_index == .bool_type) return try self.convertToDirect(.bool, result_id);
5177 if (field_ty.isInt(zcu)) return result_id;
5178 return try self.bitCast(field_ty, int_ty, result_id);
5179 },
5180 else => {
5181 // Store, ptr-elem-ptr, pointer-cast, load
5182 const layout = self.unionLayout(object_ty);
5183 assert(layout.has_payload);
5184
5185 const tmp_id = try self.alloc(object_ty, .{ .storage_class = .function });
5186 try self.store(object_ty, tmp_id, object_id, .{});
5187
5188 const pl_ptr_ty_id = try self.ptrType(layout.payload_ty, .function, .indirect);
5189 const pl_ptr_id = try self.accessChain(pl_ptr_ty_id, tmp_id, &.{layout.payload_index});
5190
5191 const active_pl_ptr_ty_id = try self.ptrType(field_ty, .function, .indirect);
5192 const active_pl_ptr_id = self.spv.allocId();
5193 try self.func.body.emit(self.spv.gpa, .OpBitcast, .{
5194 .id_result_type = active_pl_ptr_ty_id,
5195 .id_result = active_pl_ptr_id,
5196 .operand = pl_ptr_id,
5197 });
5198 return try self.load(field_ty, active_pl_ptr_id, .{});
5199 },
5200 },
5201 else => unreachable,
5202 }
5203 }
5204
5205 fn airFieldParentPtr(self: *NavGen, inst: Air.Inst.Index) !?Id {
5206 const pt = self.pt;
5207 const zcu = pt.zcu;
5208 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
5209 const extra = self.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;
5210
5211 const parent_ty = ty_pl.ty.toType().childType(zcu);
5212 const result_ty_id = try self.resolveType(ty_pl.ty.toType(), .indirect);
5213
5214 const field_ptr = try self.resolve(extra.field_ptr);
5215 const field_ptr_int = try self.intFromPtr(field_ptr);
5216 const field_offset = parent_ty.structFieldOffset(extra.field_index, zcu);
5217
5218 const base_ptr_int = base_ptr_int: {
5219 if (field_offset == 0) break :base_ptr_int field_ptr_int;
5220
5221 const field_offset_id = try self.constInt(Type.usize, field_offset);
5222 const field_ptr_tmp = Temporary.init(Type.usize, field_ptr_int);
5223 const field_offset_tmp = Temporary.init(Type.usize, field_offset_id);
5224 const result = try self.buildBinary(.i_sub, field_ptr_tmp, field_offset_tmp);
5225 break :base_ptr_int try result.materialize(self);
5226 };
5227
5228 const base_ptr = self.spv.allocId();
5229 try self.func.body.emit(self.spv.gpa, .OpConvertUToPtr, .{
5230 .id_result_type = result_ty_id,
5231 .id_result = base_ptr,
5232 .integer_value = base_ptr_int,
5233 });
5234
5235 return base_ptr;
5236 }
5237
5238 fn structFieldPtr(
5239 self: *NavGen,
5240 result_ptr_ty: Type,
5241 object_ptr_ty: Type,
5242 object_ptr: Id,
5243 field_index: u32,
5244 ) !Id {
5245 const result_ty_id = try self.resolveType(result_ptr_ty, .direct);
5246
5247 const zcu = self.pt.zcu;
5248 const object_ty = object_ptr_ty.childType(zcu);
5249 switch (object_ty.zigTypeTag(zcu)) {
5250 .pointer => {
5251 assert(object_ty.isSlice(zcu));
5252 return self.accessChain(result_ty_id, object_ptr, &.{field_index});
5253 },
5254 .@"struct" => switch (object_ty.containerLayout(zcu)) {
5255 .@"packed" => return self.todo("implement field access for packed structs", .{}),
5256 else => {
5257 return try self.accessChain(result_ty_id, object_ptr, &.{field_index});
5258 },
5259 },
5260 .@"union" => {
5261 const layout = self.unionLayout(object_ty);
5262 if (!layout.has_payload) {
5263 // Asked to get a pointer to a zero-sized field. Just lower this
5264 // to undefined, there is no reason to make it be a valid pointer.
5265 return try self.spv.constUndef(result_ty_id);
5266 }
5267
5268 const storage_class = self.spvStorageClass(object_ptr_ty.ptrAddressSpace(zcu));
5269 const pl_ptr_ty_id = try self.ptrType(layout.payload_ty, storage_class, .indirect);
5270 const pl_ptr_id = blk: {
5271 if (object_ty.containerLayout(zcu) == .@"packed") break :blk object_ptr;
5272 break :blk try self.accessChain(pl_ptr_ty_id, object_ptr, &.{layout.payload_index});
5273 };
5274
5275 const active_pl_ptr_id = self.spv.allocId();
5276 try self.func.body.emit(self.spv.gpa, .OpBitcast, .{
5277 .id_result_type = result_ty_id,
5278 .id_result = active_pl_ptr_id,
5279 .operand = pl_ptr_id,
5280 });
5281 return active_pl_ptr_id;
5282 },
5283 else => unreachable,
5284 }
5285 }
5286
5287 fn airStructFieldPtrIndex(self: *NavGen, inst: Air.Inst.Index, field_index: u32) !?Id {
5288 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5289 const struct_ptr = try self.resolve(ty_op.operand);
5290 const struct_ptr_ty = self.typeOf(ty_op.operand);
5291 const result_ptr_ty = self.typeOfIndex(inst);
5292 return try self.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index);
5293 }
5294
5295 const AllocOptions = struct {
5296 initializer: ?Id = null,
5297 /// The final storage class of the pointer. This may be either `.Generic` or `.Function`.
5298 /// In either case, the local is allocated in the `.Function` storage class, and optionally
5299 /// cast back to `.Generic`.
5300 storage_class: StorageClass,
5301 };
5302
5303 // Allocate a function-local variable, with possible initializer.
5304 // This function returns a pointer to a variable of type `ty`,
5305 // which is in the Generic address space. The variable is actually
5306 // placed in the Function address space.
5307 fn alloc(
5308 self: *NavGen,
5309 ty: Type,
5310 options: AllocOptions,
5311 ) !Id {
5312 const ptr_fn_ty_id = try self.ptrType(ty, .function, .indirect);
5313
5314 // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to
5315 // directly generate them into func.prologue instead of the body.
5316 const var_id = self.spv.allocId();
5317 try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{
5318 .id_result_type = ptr_fn_ty_id,
5319 .id_result = var_id,
5320 .storage_class = .function,
5321 .initializer = options.initializer,
5322 });
5323
5324 switch (self.spv.target.os.tag) {
5325 .vulkan, .opengl => return var_id,
5326 else => {},
5327 }
5328
5329 switch (options.storage_class) {
5330 .generic => {
5331 const ptr_gn_ty_id = try self.ptrType(ty, .generic, .indirect);
5332 // Convert to a generic pointer
5333 return self.castToGeneric(ptr_gn_ty_id, var_id);
5334 },
5335 .function => return var_id,
5336 else => unreachable,
5337 }
5338 }
5339
5340 fn airAlloc(self: *NavGen, inst: Air.Inst.Index) !?Id {
5341 const zcu = self.pt.zcu;
5342 const ptr_ty = self.typeOfIndex(inst);
5343 const child_ty = ptr_ty.childType(zcu);
5344 return try self.alloc(child_ty, .{
5345 .storage_class = self.spvStorageClass(ptr_ty.ptrAddressSpace(zcu)),
5346 });
5347 }
5348
5349 fn airArg(self: *NavGen) Id {
5350 defer self.next_arg_index += 1;
5351 return self.args.items[self.next_arg_index];
5352 }
5353
5354 /// Given a slice of incoming block connections, returns the block-id of the next
5355 /// block to jump to. This function emits instructions, so it should be emitted
5356 /// inside the merge block of the block.
5357 /// This function should only be called with structured control flow generation.
5358 fn structuredNextBlock(self: *NavGen, incoming: []const ControlFlow.Structured.Block.Incoming) !Id {
5359 assert(self.control_flow == .structured);
5360
5361 const result_id = self.spv.allocId();
5362 const block_id_ty_id = try self.resolveType(Type.u32, .direct);
5363 try self.func.body.emitRaw(self.spv.gpa, .OpPhi, @intCast(2 + incoming.len * 2)); // result type + result + variable/parent...
5364 self.func.body.writeOperand(spec.Id, block_id_ty_id);
5365 self.func.body.writeOperand(spec.Id, result_id);
5366
5367 for (incoming) |incoming_block| {
5368 self.func.body.writeOperand(spec.PairIdRefIdRef, .{ incoming_block.next_block, incoming_block.src_label });
5369 }
5370
5371 return result_id;
5372 }
5373
5374 /// Jumps to the block with the target block-id. This function must only be called when
5375 /// terminating a body, there should be no instructions after it.
5376 /// This function should only be called with structured control flow generation.
5377 fn structuredBreak(self: *NavGen, target_block: Id) !void {
5378 assert(self.control_flow == .structured);
5379
5380 const sblock = self.control_flow.structured.block_stack.getLast();
5381 const merge_block = switch (sblock.*) {
5382 .selection => |*merge| blk: {
5383 const merge_label = self.spv.allocId();
5384 try merge.merge_stack.append(self.gpa, .{
5385 .incoming = .{
5386 .src_label = self.current_block_label,
5387 .next_block = target_block,
5388 },
5389 .merge_block = merge_label,
5390 });
5391 break :blk merge_label;
5392 },
5393 // Loop blocks do not end in a break. Not through a direct break,
5394 // and also not through another instruction like cond_br or unreachable (these
5395 // situations are replaced by `cond_br` in sema, or there is a `block` instruction
5396 // placed around them).
5397 .loop => unreachable,
5398 };
5399
5400 try self.func.body.emitBranch(self.spv.gpa, merge_block);
5401 }
5402
5403 /// Generate a body in a way that exits the body using only structured constructs.
5404 /// Returns the block-id of the next block to jump to. After this function, a jump
5405 /// should still be emitted to the block that should follow this structured body.
5406 /// This function should only be called with structured control flow generation.
5407 fn genStructuredBody(
5408 self: *NavGen,
5409 /// This parameter defines the method that this structured body is exited with.
5410 block_merge_type: union(enum) {
5411 /// Using selection; early exits from this body are surrounded with
5412 /// if() statements.
5413 selection,
5414 /// Using loops; loops can be early exited by jumping to the merge block at
5415 /// any time.
5416 loop: struct {
5417 merge_label: Id,
5418 continue_label: Id,
5419 },
5420 },
5421 body: []const Air.Inst.Index,
5422 ) !Id {
5423 assert(self.control_flow == .structured);
5424
5425 var sblock: ControlFlow.Structured.Block = switch (block_merge_type) {
5426 .loop => |merge| .{ .loop = .{
5427 .merge_block = merge.merge_label,
5428 } },
5429 .selection => .{ .selection = .{} },
5430 };
5431 defer sblock.deinit(self.gpa);
5432
5433 {
5434 try self.control_flow.structured.block_stack.append(self.gpa, &sblock);
5435 defer _ = self.control_flow.structured.block_stack.pop();
5436
5437 try self.genBody(body);
5438 }
5439
5440 switch (sblock) {
5441 .selection => |merge| {
5442 // Now generate the merge block for all merges that
5443 // still need to be performed.
5444 const merge_stack = merge.merge_stack.items;
5445
5446 // If no merges on the stack, this block didn't generate any jumps (all paths
5447 // ended with a return or an unreachable). In that case, we don't need to do
5448 // any merging.
5449 if (merge_stack.len == 0) {
5450 // We still need to return a value of a next block to jump to.
5451 // For example, if we have code like
5452 // if (x) {
5453 // if (y) return else return;
5454 // } else {}
5455 // then we still need the outer to have an OpSelectionMerge and consequently
5456 // a phi node. In that case we can just return bogus, since we know that its
5457 // path will never be taken.
5458
5459 // Make sure that we are still in a block when exiting the function.
5460 // TODO: Can we get rid of that?
5461 try self.beginSpvBlock(self.spv.allocId());
5462 const block_id_ty_id = try self.resolveType(Type.u32, .direct);
5463 return try self.spv.constUndef(block_id_ty_id);
5464 }
5465
5466 // The top-most merge actually only has a single source, the
5467 // final jump of the block, or the merge block of a sub-block, cond_br,
5468 // or loop. Therefore we just need to generate a block with a jump to the
5469 // next merge block.
5470 try self.beginSpvBlock(merge_stack[merge_stack.len - 1].merge_block);
5471
5472 // Now generate a merge ladder for the remaining merges in the stack.
5473 var incoming = ControlFlow.Structured.Block.Incoming{
5474 .src_label = self.current_block_label,
5475 .next_block = merge_stack[merge_stack.len - 1].incoming.next_block,
5476 };
5477 var i = merge_stack.len - 1;
5478 while (i > 0) {
5479 i -= 1;
5480 const step = merge_stack[i];
5481 try self.func.body.emitBranch(self.spv.gpa, step.merge_block);
5482 try self.beginSpvBlock(step.merge_block);
5483 const next_block = try self.structuredNextBlock(&.{ incoming, step.incoming });
5484 incoming = .{
5485 .src_label = step.merge_block,
5486 .next_block = next_block,
5487 };
5488 }
5489
5490 return incoming.next_block;
5491 },
5492 .loop => |merge| {
5493 // Close the loop by jumping to the continue label
5494 try self.func.body.emitBranch(self.spv.gpa, block_merge_type.loop.continue_label);
5495 // For blocks we must simple merge all the incoming blocks to get the next block.
5496 try self.beginSpvBlock(merge.merge_block);
5497 return try self.structuredNextBlock(merge.merges.items);
5498 },
5499 }
5500 }
5501
5502 fn airBlock(self: *NavGen, inst: Air.Inst.Index) !?Id {
5503 const inst_datas = self.air.instructions.items(.data);
5504 const extra = self.air.extraData(Air.Block, inst_datas[@intFromEnum(inst)].ty_pl.payload);
5505 return self.lowerBlock(inst, @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]));
5506 }
5507
5508 fn lowerBlock(self: *NavGen, inst: Air.Inst.Index, body: []const Air.Inst.Index) !?Id {
5509 // In AIR, a block doesn't really define an entry point like a block, but
5510 // more like a scope that breaks can jump out of and "return" a value from.
5511 // This cannot be directly modelled in SPIR-V, so in a block instruction,
5512 // we're going to split up the current block by first generating the code
5513 // of the block, then a label, and then generate the rest of the current
5514 // ir.Block in a different SPIR-V block.
5515
5516 const pt = self.pt;
5517 const zcu = pt.zcu;
5518 const ty = self.typeOfIndex(inst);
5519 const have_block_result = ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu);
5520
5521 const cf = switch (self.control_flow) {
5522 .structured => |*cf| cf,
5523 .unstructured => |*cf| {
5524 var block = ControlFlow.Unstructured.Block{};
5525 defer block.incoming_blocks.deinit(self.gpa);
5526
5527 // 4 chosen as arbitrary initial capacity.
5528 try block.incoming_blocks.ensureUnusedCapacity(self.gpa, 4);
5529
5530 try cf.blocks.putNoClobber(self.gpa, inst, &block);
5531 defer assert(cf.blocks.remove(inst));
5532
5533 try self.genBody(body);
5534
5535 // Only begin a new block if there were actually any breaks towards it.
5536 if (block.label) |label| {
5537 try self.beginSpvBlock(label);
5538 }
5539
5540 if (!have_block_result)
5541 return null;
5542
5543 assert(block.label != null);
5544 const result_id = self.spv.allocId();
5545 const result_type_id = try self.resolveType(ty, .direct);
5546
5547 try self.func.body.emitRaw(
5548 self.spv.gpa,
5549 .OpPhi,
5550 // result type + result + variable/parent...
5551 2 + @as(u16, @intCast(block.incoming_blocks.items.len * 2)),
5552 );
5553 self.func.body.writeOperand(spec.Id, result_type_id);
5554 self.func.body.writeOperand(spec.Id, result_id);
5555
5556 for (block.incoming_blocks.items) |incoming| {
5557 self.func.body.writeOperand(
5558 spec.PairIdRefIdRef,
5559 .{ incoming.break_value_id, incoming.src_label },
5560 );
5561 }
5562
5563 return result_id;
5564 },
5565 };
5566
5567 const maybe_block_result_var_id = if (have_block_result) blk: {
5568 const block_result_var_id = try self.alloc(ty, .{ .storage_class = .function });
5569 try cf.block_results.putNoClobber(self.gpa, inst, block_result_var_id);
5570 break :blk block_result_var_id;
5571 } else null;
5572 defer if (have_block_result) assert(cf.block_results.remove(inst));
5573
5574 const next_block = try self.genStructuredBody(.selection, body);
5575
5576 // When encountering a block instruction, we are always at least in the function's scope,
5577 // so there always has to be another entry.
5578 assert(cf.block_stack.items.len > 0);
5579
5580 // Check if the target of the branch was this current block.
5581 const this_block = try self.constInt(Type.u32, @intFromEnum(inst));
5582 const jump_to_this_block_id = self.spv.allocId();
5583 const bool_ty_id = try self.resolveType(Type.bool, .direct);
5584 try self.func.body.emit(self.spv.gpa, .OpIEqual, .{
5585 .id_result_type = bool_ty_id,
5586 .id_result = jump_to_this_block_id,
5587 .operand_1 = next_block,
5588 .operand_2 = this_block,
5589 });
5590
5591 const sblock = cf.block_stack.getLast();
5592
5593 if (ty.isNoReturn(zcu)) {
5594 // If this block is noreturn, this instruction is the last of a block,
5595 // and we must simply jump to the block's merge unconditionally.
5596 try self.structuredBreak(next_block);
5597 } else {
5598 switch (sblock.*) {
5599 .selection => |*merge| {
5600 // To jump out of a selection block, push a new entry onto its merge stack and
5601 // generate a conditional branch to there and to the instructions following this block.
5602 const merge_label = self.spv.allocId();
5603 const then_label = self.spv.allocId();
5604 try self.func.body.emit(self.spv.gpa, .OpSelectionMerge, .{
5605 .merge_block = merge_label,
5606 .selection_control = .{},
5607 });
5608 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
5609 .condition = jump_to_this_block_id,
5610 .true_label = then_label,
5611 .false_label = merge_label,
5612 });
5613 try merge.merge_stack.append(self.gpa, .{
5614 .incoming = .{
5615 .src_label = self.current_block_label,
5616 .next_block = next_block,
5617 },
5618 .merge_block = merge_label,
5619 });
5620
5621 try self.beginSpvBlock(then_label);
5622 },
5623 .loop => |*merge| {
5624 // To jump out of a loop block, generate a conditional that exits the block
5625 // to the loop merge if the target ID is not the one of this block.
5626 const continue_label = self.spv.allocId();
5627 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
5628 .condition = jump_to_this_block_id,
5629 .true_label = continue_label,
5630 .false_label = merge.merge_block,
5631 });
5632 try merge.merges.append(self.gpa, .{
5633 .src_label = self.current_block_label,
5634 .next_block = next_block,
5635 });
5636 try self.beginSpvBlock(continue_label);
5637 },
5638 }
5639 }
5640
5641 if (maybe_block_result_var_id) |block_result_var_id| {
5642 return try self.load(ty, block_result_var_id, .{});
5643 }
5644
5645 return null;
5646 }
5647
5648 fn airBr(self: *NavGen, inst: Air.Inst.Index) !void {
5649 const zcu = self.pt.zcu;
5650 const br = self.air.instructions.items(.data)[@intFromEnum(inst)].br;
5651 const operand_ty = self.typeOf(br.operand);
5652
5653 switch (self.control_flow) {
5654 .structured => |*cf| {
5655 if (operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) {
5656 const operand_id = try self.resolve(br.operand);
5657 const block_result_var_id = cf.block_results.get(br.block_inst).?;
5658 try self.store(operand_ty, block_result_var_id, operand_id, .{});
5659 }
5660
5661 const next_block = try self.constInt(Type.u32, @intFromEnum(br.block_inst));
5662 try self.structuredBreak(next_block);
5663 },
5664 .unstructured => |cf| {
5665 const block = cf.blocks.get(br.block_inst).?;
5666 if (operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) {
5667 const operand_id = try self.resolve(br.operand);
5668 // current_block_label should not be undefined here, lest there
5669 // is a br or br_void in the function's body.
5670 try block.incoming_blocks.append(self.gpa, .{
5671 .src_label = self.current_block_label,
5672 .break_value_id = operand_id,
5673 });
5674 }
5675
5676 if (block.label == null) {
5677 block.label = self.spv.allocId();
5678 }
5679
5680 try self.func.body.emitBranch(self.spv.gpa, block.label.?);
5681 },
5682 }
5683 }
5684
5685 fn airCondBr(self: *NavGen, inst: Air.Inst.Index) !void {
5686 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
5687 const cond_br = self.air.extraData(Air.CondBr, pl_op.payload);
5688 const then_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[cond_br.end..][0..cond_br.data.then_body_len]);
5689 const else_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[cond_br.end + then_body.len ..][0..cond_br.data.else_body_len]);
5690 const condition_id = try self.resolve(pl_op.operand);
5691
5692 const then_label = self.spv.allocId();
5693 const else_label = self.spv.allocId();
5694
5695 switch (self.control_flow) {
5696 .structured => {
5697 const merge_label = self.spv.allocId();
5698
5699 try self.func.body.emit(self.spv.gpa, .OpSelectionMerge, .{
5700 .merge_block = merge_label,
5701 .selection_control = .{},
5702 });
5703 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
5704 .condition = condition_id,
5705 .true_label = then_label,
5706 .false_label = else_label,
5707 });
5708
5709 try self.beginSpvBlock(then_label);
5710 const then_next = try self.genStructuredBody(.selection, then_body);
5711 const then_incoming = ControlFlow.Structured.Block.Incoming{
5712 .src_label = self.current_block_label,
5713 .next_block = then_next,
5714 };
5715 try self.func.body.emitBranch(self.spv.gpa, merge_label);
5716
5717 try self.beginSpvBlock(else_label);
5718 const else_next = try self.genStructuredBody(.selection, else_body);
5719 const else_incoming = ControlFlow.Structured.Block.Incoming{
5720 .src_label = self.current_block_label,
5721 .next_block = else_next,
5722 };
5723 try self.func.body.emitBranch(self.spv.gpa, merge_label);
5724
5725 try self.beginSpvBlock(merge_label);
5726 const next_block = try self.structuredNextBlock(&.{ then_incoming, else_incoming });
5727
5728 try self.structuredBreak(next_block);
5729 },
5730 .unstructured => {
5731 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
5732 .condition = condition_id,
5733 .true_label = then_label,
5734 .false_label = else_label,
5735 });
5736
5737 try self.beginSpvBlock(then_label);
5738 try self.genBody(then_body);
5739 try self.beginSpvBlock(else_label);
5740 try self.genBody(else_body);
5741 },
5742 }
5743 }
5744
5745 fn airLoop(self: *NavGen, inst: Air.Inst.Index) !void {
5746 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
5747 const loop = self.air.extraData(Air.Block, ty_pl.payload);
5748 const body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[loop.end..][0..loop.data.body_len]);
5749
5750 const body_label = self.spv.allocId();
5751
5752 switch (self.control_flow) {
5753 .structured => {
5754 const header_label = self.spv.allocId();
5755 const merge_label = self.spv.allocId();
5756 const continue_label = self.spv.allocId();
5757
5758 // The back-edge must point to the loop header, so generate a separate block for the
5759 // loop header so that we don't accidentally include some instructions from there
5760 // in the loop.
5761 try self.func.body.emitBranch(self.spv.gpa, header_label);
5762 try self.beginSpvBlock(header_label);
5763
5764 // Emit loop header and jump to loop body
5765 try self.func.body.emit(self.spv.gpa, .OpLoopMerge, .{
5766 .merge_block = merge_label,
5767 .continue_target = continue_label,
5768 .loop_control = .{},
5769 });
5770 try self.func.body.emitBranch(self.spv.gpa, body_label);
5771
5772 try self.beginSpvBlock(body_label);
5773
5774 const next_block = try self.genStructuredBody(.{ .loop = .{
5775 .merge_label = merge_label,
5776 .continue_label = continue_label,
5777 } }, body);
5778 try self.structuredBreak(next_block);
5779
5780 try self.beginSpvBlock(continue_label);
5781 try self.func.body.emitBranch(self.spv.gpa, header_label);
5782 },
5783 .unstructured => {
5784 try self.func.body.emitBranch(self.spv.gpa, body_label);
5785 try self.beginSpvBlock(body_label);
5786 try self.genBody(body);
5787 try self.func.body.emitBranch(self.spv.gpa, body_label);
5788 },
5789 }
5790 }
5791
5792 fn airLoad(self: *NavGen, inst: Air.Inst.Index) !?Id {
5793 const zcu = self.pt.zcu;
5794 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5795 const ptr_ty = self.typeOf(ty_op.operand);
5796 const elem_ty = self.typeOfIndex(inst);
5797 const operand = try self.resolve(ty_op.operand);
5798 if (!ptr_ty.isVolatilePtr(zcu) and self.liveness.isUnused(inst)) return null;
5799
5800 return try self.load(elem_ty, operand, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) });
5801 }
5802
5803 fn airStore(self: *NavGen, inst: Air.Inst.Index) !void {
5804 const zcu = self.pt.zcu;
5805 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
5806 const ptr_ty = self.typeOf(bin_op.lhs);
5807 const elem_ty = ptr_ty.childType(zcu);
5808 const ptr = try self.resolve(bin_op.lhs);
5809 const value = try self.resolve(bin_op.rhs);
5810
5811 try self.store(elem_ty, ptr, value, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) });
5812 }
5813
5814 fn airRet(self: *NavGen, inst: Air.Inst.Index) !void {
5815 const pt = self.pt;
5816 const zcu = pt.zcu;
5817 const operand = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
5818 const ret_ty = self.typeOf(operand);
5819 if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
5820 const fn_info = zcu.typeToFunc(zcu.navValue(self.owner_nav).typeOf(zcu)).?;
5821 if (Type.fromInterned(fn_info.return_type).isError(zcu)) {
5822 // Functions with an empty error set are emitted with an error code
5823 // return type and return zero so they can be function pointers coerced
5824 // to functions that return anyerror.
5825 const no_err_id = try self.constInt(Type.anyerror, 0);
5826 return try self.func.body.emit(self.spv.gpa, .OpReturnValue, .{ .value = no_err_id });
5827 } else {
5828 return try self.func.body.emit(self.spv.gpa, .OpReturn, {});
5829 }
5830 }
5831
5832 const operand_id = try self.resolve(operand);
5833 try self.func.body.emit(self.spv.gpa, .OpReturnValue, .{ .value = operand_id });
5834 }
5835
5836 fn airRetLoad(self: *NavGen, inst: Air.Inst.Index) !void {
5837 const pt = self.pt;
5838 const zcu = pt.zcu;
5839 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
5840 const ptr_ty = self.typeOf(un_op);
5841 const ret_ty = ptr_ty.childType(zcu);
5842
5843 if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
5844 const fn_info = zcu.typeToFunc(zcu.navValue(self.owner_nav).typeOf(zcu)).?;
5845 if (Type.fromInterned(fn_info.return_type).isError(zcu)) {
5846 // Functions with an empty error set are emitted with an error code
5847 // return type and return zero so they can be function pointers coerced
5848 // to functions that return anyerror.
5849 const no_err_id = try self.constInt(Type.anyerror, 0);
5850 return try self.func.body.emit(self.spv.gpa, .OpReturnValue, .{ .value = no_err_id });
5851 } else {
5852 return try self.func.body.emit(self.spv.gpa, .OpReturn, {});
5853 }
5854 }
5855
5856 const ptr = try self.resolve(un_op);
5857 const value = try self.load(ret_ty, ptr, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) });
5858 try self.func.body.emit(self.spv.gpa, .OpReturnValue, .{
5859 .value = value,
5860 });
5861 }
5862
5863 fn airTry(self: *NavGen, inst: Air.Inst.Index) !?Id {
5864 const zcu = self.pt.zcu;
5865 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
5866 const err_union_id = try self.resolve(pl_op.operand);
5867 const extra = self.air.extraData(Air.Try, pl_op.payload);
5868 const body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]);
5869
5870 const err_union_ty = self.typeOf(pl_op.operand);
5871 const payload_ty = self.typeOfIndex(inst);
5872
5873 const bool_ty_id = try self.resolveType(Type.bool, .direct);
5874
5875 const eu_layout = self.errorUnionLayout(payload_ty);
5876
5877 if (!err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {
5878 const err_id = if (eu_layout.payload_has_bits)
5879 try self.extractField(Type.anyerror, err_union_id, eu_layout.errorFieldIndex())
5880 else
5881 err_union_id;
5882
5883 const zero_id = try self.constInt(Type.anyerror, 0);
5884 const is_err_id = self.spv.allocId();
5885 try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{
5886 .id_result_type = bool_ty_id,
5887 .id_result = is_err_id,
5888 .operand_1 = err_id,
5889 .operand_2 = zero_id,
5890 });
5891
5892 // When there is an error, we must evaluate `body`. Otherwise we must continue
5893 // with the current body.
5894 // Just generate a new block here, then generate a new block inline for the remainder of the body.
5895
5896 const err_block = self.spv.allocId();
5897 const ok_block = self.spv.allocId();
5898
5899 switch (self.control_flow) {
5900 .structured => {
5901 // According to AIR documentation, this block is guaranteed
5902 // to not break and end in a return instruction. Thus,
5903 // for structured control flow, we can just naively use
5904 // the ok block as the merge block here.
5905 try self.func.body.emit(self.spv.gpa, .OpSelectionMerge, .{
5906 .merge_block = ok_block,
5907 .selection_control = .{},
5908 });
5909 },
5910 .unstructured => {},
5911 }
5912
5913 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
5914 .condition = is_err_id,
5915 .true_label = err_block,
5916 .false_label = ok_block,
5917 });
5918
5919 try self.beginSpvBlock(err_block);
5920 try self.genBody(body);
5921
5922 try self.beginSpvBlock(ok_block);
5923 }
5924
5925 if (!eu_layout.payload_has_bits) {
5926 return null;
5927 }
5928
5929 // Now just extract the payload, if required.
5930 return try self.extractField(payload_ty, err_union_id, eu_layout.payloadFieldIndex());
5931 }
5932
5933 fn airErrUnionErr(self: *NavGen, inst: Air.Inst.Index) !?Id {
5934 const zcu = self.pt.zcu;
5935 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5936 const operand_id = try self.resolve(ty_op.operand);
5937 const err_union_ty = self.typeOf(ty_op.operand);
5938 const err_ty_id = try self.resolveType(Type.anyerror, .direct);
5939
5940 if (err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {
5941 // No error possible, so just return undefined.
5942 return try self.spv.constUndef(err_ty_id);
5943 }
5944
5945 const payload_ty = err_union_ty.errorUnionPayload(zcu);
5946 const eu_layout = self.errorUnionLayout(payload_ty);
5947
5948 if (!eu_layout.payload_has_bits) {
5949 // If no payload, error union is represented by error set.
5950 return operand_id;
5951 }
5952
5953 return try self.extractField(Type.anyerror, operand_id, eu_layout.errorFieldIndex());
5954 }
5955
5956 fn airErrUnionPayload(self: *NavGen, inst: Air.Inst.Index) !?Id {
5957 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5958 const operand_id = try self.resolve(ty_op.operand);
5959 const payload_ty = self.typeOfIndex(inst);
5960 const eu_layout = self.errorUnionLayout(payload_ty);
5961
5962 if (!eu_layout.payload_has_bits) {
5963 return null; // No error possible.
5964 }
5965
5966 return try self.extractField(payload_ty, operand_id, eu_layout.payloadFieldIndex());
5967 }
5968
5969 fn airWrapErrUnionErr(self: *NavGen, inst: Air.Inst.Index) !?Id {
5970 const zcu = self.pt.zcu;
5971 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5972 const err_union_ty = self.typeOfIndex(inst);
5973 const payload_ty = err_union_ty.errorUnionPayload(zcu);
5974 const operand_id = try self.resolve(ty_op.operand);
5975 const eu_layout = self.errorUnionLayout(payload_ty);
5976
5977 if (!eu_layout.payload_has_bits) {
5978 return operand_id;
5979 }
5980
5981 const payload_ty_id = try self.resolveType(payload_ty, .indirect);
5982
5983 var members: [2]Id = undefined;
5984 members[eu_layout.errorFieldIndex()] = operand_id;
5985 members[eu_layout.payloadFieldIndex()] = try self.spv.constUndef(payload_ty_id);
5986
5987 var types: [2]Type = undefined;
5988 types[eu_layout.errorFieldIndex()] = Type.anyerror;
5989 types[eu_layout.payloadFieldIndex()] = payload_ty;
5990
5991 const err_union_ty_id = try self.resolveType(err_union_ty, .direct);
5992 return try self.constructComposite(err_union_ty_id, &members);
5993 }
5994
5995 fn airWrapErrUnionPayload(self: *NavGen, inst: Air.Inst.Index) !?Id {
5996 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5997 const err_union_ty = self.typeOfIndex(inst);
5998 const operand_id = try self.resolve(ty_op.operand);
5999 const payload_ty = self.typeOf(ty_op.operand);
6000 const eu_layout = self.errorUnionLayout(payload_ty);
6001
6002 if (!eu_layout.payload_has_bits) {
6003 return try self.constInt(Type.anyerror, 0);
6004 }
6005
6006 var members: [2]Id = undefined;
6007 members[eu_layout.errorFieldIndex()] = try self.constInt(Type.anyerror, 0);
6008 members[eu_layout.payloadFieldIndex()] = try self.convertToIndirect(payload_ty, operand_id);
6009
6010 var types: [2]Type = undefined;
6011 types[eu_layout.errorFieldIndex()] = Type.anyerror;
6012 types[eu_layout.payloadFieldIndex()] = payload_ty;
6013
6014 const err_union_ty_id = try self.resolveType(err_union_ty, .direct);
6015 return try self.constructComposite(err_union_ty_id, &members);
6016 }
6017
6018 fn airIsNull(self: *NavGen, inst: Air.Inst.Index, is_pointer: bool, pred: enum { is_null, is_non_null }) !?Id {
6019 const pt = self.pt;
6020 const zcu = pt.zcu;
6021 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
6022 const operand_id = try self.resolve(un_op);
6023 const operand_ty = self.typeOf(un_op);
6024 const optional_ty = if (is_pointer) operand_ty.childType(zcu) else operand_ty;
6025 const payload_ty = optional_ty.optionalChild(zcu);
6026
6027 const bool_ty_id = try self.resolveType(Type.bool, .direct);
6028
6029 if (optional_ty.optionalReprIsPayload(zcu)) {
6030 // Pointer payload represents nullability: pointer or slice.
6031 const loaded_id = if (is_pointer)
6032 try self.load(optional_ty, operand_id, .{})
6033 else
6034 operand_id;
6035
6036 const ptr_ty = if (payload_ty.isSlice(zcu))
6037 payload_ty.slicePtrFieldType(zcu)
6038 else
6039 payload_ty;
6040
6041 const ptr_id = if (payload_ty.isSlice(zcu))
6042 try self.extractField(ptr_ty, loaded_id, 0)
6043 else
6044 loaded_id;
6045
6046 const ptr_ty_id = try self.resolveType(ptr_ty, .direct);
6047 const null_id = try self.spv.constNull(ptr_ty_id);
6048 const null_tmp = Temporary.init(ptr_ty, null_id);
6049 const ptr = Temporary.init(ptr_ty, ptr_id);
6050
6051 const op: std.math.CompareOperator = switch (pred) {
6052 .is_null => .eq,
6053 .is_non_null => .neq,
6054 };
6055 const result = try self.cmp(op, ptr, null_tmp);
6056 return try result.materialize(self);
6057 }
6058
6059 const is_non_null_id = blk: {
6060 if (is_pointer) {
6061 if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
6062 const storage_class = self.spvStorageClass(operand_ty.ptrAddressSpace(zcu));
6063 const bool_ptr_ty_id = try self.ptrType(Type.bool, storage_class, .indirect);
6064 const tag_ptr_id = try self.accessChain(bool_ptr_ty_id, operand_id, &.{1});
6065 break :blk try self.load(Type.bool, tag_ptr_id, .{});
6066 }
6067
6068 break :blk try self.load(Type.bool, operand_id, .{});
6069 }
6070
6071 break :blk if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu))
6072 try self.extractField(Type.bool, operand_id, 1)
6073 else
6074 // Optional representation is bool indicating whether the optional is set
6075 // Optionals with no payload are represented as an (indirect) bool, so convert
6076 // it back to the direct bool here.
6077 try self.convertToDirect(Type.bool, operand_id);
6078 };
6079
6080 return switch (pred) {
6081 .is_null => blk: {
6082 // Invert condition
6083 const result_id = self.spv.allocId();
6084 try self.func.body.emit(self.spv.gpa, .OpLogicalNot, .{
6085 .id_result_type = bool_ty_id,
6086 .id_result = result_id,
6087 .operand = is_non_null_id,
6088 });
6089 break :blk result_id;
6090 },
6091 .is_non_null => is_non_null_id,
6092 };
6093 }
6094
6095 fn airIsErr(self: *NavGen, inst: Air.Inst.Index, pred: enum { is_err, is_non_err }) !?Id {
6096 const zcu = self.pt.zcu;
6097 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
6098 const operand_id = try self.resolve(un_op);
6099 const err_union_ty = self.typeOf(un_op);
6100
6101 if (err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {
6102 return try self.constBool(pred == .is_non_err, .direct);
6103 }
6104
6105 const payload_ty = err_union_ty.errorUnionPayload(zcu);
6106 const eu_layout = self.errorUnionLayout(payload_ty);
6107 const bool_ty_id = try self.resolveType(Type.bool, .direct);
6108
6109 const error_id = if (!eu_layout.payload_has_bits)
6110 operand_id
6111 else
6112 try self.extractField(Type.anyerror, operand_id, eu_layout.errorFieldIndex());
6113
6114 const result_id = self.spv.allocId();
6115 switch (pred) {
6116 inline else => |pred_ct| try self.func.body.emit(
6117 self.spv.gpa,
6118 switch (pred_ct) {
6119 .is_err => .OpINotEqual,
6120 .is_non_err => .OpIEqual,
6121 },
6122 .{
6123 .id_result_type = bool_ty_id,
6124 .id_result = result_id,
6125 .operand_1 = error_id,
6126 .operand_2 = try self.constInt(Type.anyerror, 0),
6127 },
6128 ),
6129 }
6130 return result_id;
6131 }
6132
6133 fn airUnwrapOptional(self: *NavGen, inst: Air.Inst.Index) !?Id {
6134 const pt = self.pt;
6135 const zcu = pt.zcu;
6136 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
6137 const operand_id = try self.resolve(ty_op.operand);
6138 const optional_ty = self.typeOf(ty_op.operand);
6139 const payload_ty = self.typeOfIndex(inst);
6140
6141 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) return null;
6142
6143 if (optional_ty.optionalReprIsPayload(zcu)) {
6144 return operand_id;
6145 }
6146
6147 return try self.extractField(payload_ty, operand_id, 0);
6148 }
6149
6150 fn airUnwrapOptionalPtr(self: *NavGen, inst: Air.Inst.Index) !?Id {
6151 const pt = self.pt;
6152 const zcu = pt.zcu;
6153 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
6154 const operand_id = try self.resolve(ty_op.operand);
6155 const operand_ty = self.typeOf(ty_op.operand);
6156 const optional_ty = operand_ty.childType(zcu);
6157 const payload_ty = optional_ty.optionalChild(zcu);
6158 const result_ty = self.typeOfIndex(inst);
6159 const result_ty_id = try self.resolveType(result_ty, .direct);
6160
6161 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
6162 // There is no payload, but we still need to return a valid pointer.
6163 // We can just return anything here, so just return a pointer to the operand.
6164 return try self.bitCast(result_ty, operand_ty, operand_id);
6165 }
6166
6167 if (optional_ty.optionalReprIsPayload(zcu)) {
6168 // They are the same value.
6169 return try self.bitCast(result_ty, operand_ty, operand_id);
6170 }
6171
6172 return try self.accessChain(result_ty_id, operand_id, &.{0});
6173 }
6174
6175 fn airWrapOptional(self: *NavGen, inst: Air.Inst.Index) !?Id {
6176 const pt = self.pt;
6177 const zcu = pt.zcu;
6178 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
6179 const payload_ty = self.typeOf(ty_op.operand);
6180
6181 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
6182 return try self.constBool(true, .indirect);
6183 }
6184
6185 const operand_id = try self.resolve(ty_op.operand);
6186
6187 const optional_ty = self.typeOfIndex(inst);
6188 if (optional_ty.optionalReprIsPayload(zcu)) {
6189 return operand_id;
6190 }
6191
6192 const payload_id = try self.convertToIndirect(payload_ty, operand_id);
6193 const members = [_]Id{ payload_id, try self.constBool(true, .indirect) };
6194 const optional_ty_id = try self.resolveType(optional_ty, .direct);
6195 return try self.constructComposite(optional_ty_id, &members);
6196 }
6197
6198 fn airSwitchBr(self: *NavGen, inst: Air.Inst.Index) !void {
6199 const pt = self.pt;
6200 const zcu = pt.zcu;
6201 const target = self.spv.target;
6202 const switch_br = self.air.unwrapSwitch(inst);
6203 const cond_ty = self.typeOf(switch_br.operand);
6204 const cond = try self.resolve(switch_br.operand);
6205 var cond_indirect = try self.convertToIndirect(cond_ty, cond);
6206
6207 const cond_words: u32 = switch (cond_ty.zigTypeTag(zcu)) {
6208 .bool, .error_set => 1,
6209 .int => blk: {
6210 const bits = cond_ty.intInfo(zcu).bits;
6211 const backing_bits, const big_int = self.backingIntBits(bits);
6212 if (big_int) return self.todo("implement composite int switch", .{});
6213 break :blk if (backing_bits <= 32) 1 else 2;
6214 },
6215 .@"enum" => blk: {
6216 const int_ty = cond_ty.intTagType(zcu);
6217 const int_info = int_ty.intInfo(zcu);
6218 const backing_bits, const big_int = self.backingIntBits(int_info.bits);
6219 if (big_int) return self.todo("implement composite int switch", .{});
6220 break :blk if (backing_bits <= 32) 1 else 2;
6221 },
6222 .pointer => blk: {
6223 cond_indirect = try self.intFromPtr(cond_indirect);
6224 break :blk target.ptrBitWidth() / 32;
6225 },
6226 // TODO: Figure out which types apply here, and work around them as we can only do integers.
6227 else => return self.todo("implement switch for type {s}", .{@tagName(cond_ty.zigTypeTag(zcu))}),
6228 };
6229
6230 const num_cases = switch_br.cases_len;
6231
6232 // Compute the total number of arms that we need.
6233 // Zig switches are grouped by condition, so we need to loop through all of them
6234 const num_conditions = blk: {
6235 var num_conditions: u32 = 0;
6236 var it = switch_br.iterateCases();
6237 while (it.next()) |case| {
6238 if (case.ranges.len > 0) return self.todo("switch with ranges", .{});
6239 num_conditions += @intCast(case.items.len);
6240 }
6241 break :blk num_conditions;
6242 };
6243
6244 // First, pre-allocate the labels for the cases.
6245 const case_labels = self.spv.allocIds(num_cases);
6246 // We always need the default case - if zig has none, we will generate unreachable there.
6247 const default = self.spv.allocId();
6248
6249 const merge_label = switch (self.control_flow) {
6250 .structured => self.spv.allocId(),
6251 .unstructured => null,
6252 };
6253
6254 if (self.control_flow == .structured) {
6255 try self.func.body.emit(self.spv.gpa, .OpSelectionMerge, .{
6256 .merge_block = merge_label.?,
6257 .selection_control = .{},
6258 });
6259 }
6260
6261 // Emit the instruction before generating the blocks.
6262 try self.func.body.emitRaw(self.spv.gpa, .OpSwitch, 2 + (cond_words + 1) * num_conditions);
6263 self.func.body.writeOperand(Id, cond_indirect);
6264 self.func.body.writeOperand(Id, default);
6265
6266 // Emit each of the cases
6267 {
6268 var it = switch_br.iterateCases();
6269 while (it.next()) |case| {
6270 // SPIR-V needs a literal here, which' width depends on the case condition.
6271 const label = case_labels.at(case.idx);
6272
6273 for (case.items) |item| {
6274 const value = (try self.air.value(item, pt)) orelse unreachable;
6275 const int_val: u64 = switch (cond_ty.zigTypeTag(zcu)) {
6276 .bool, .int => if (cond_ty.isSignedInt(zcu)) @bitCast(value.toSignedInt(zcu)) else value.toUnsignedInt(zcu),
6277 .@"enum" => blk: {
6278 // TODO: figure out of cond_ty is correct (something with enum literals)
6279 break :blk (try value.intFromEnum(cond_ty, pt)).toUnsignedInt(zcu); // TODO: composite integer constants
6280 },
6281 .error_set => value.getErrorInt(zcu),
6282 .pointer => value.toUnsignedInt(zcu),
6283 else => unreachable,
6284 };
6285 const int_lit: spec.LiteralContextDependentNumber = switch (cond_words) {
6286 1 => .{ .uint32 = @intCast(int_val) },
6287 2 => .{ .uint64 = int_val },
6288 else => unreachable,
6289 };
6290 self.func.body.writeOperand(spec.LiteralContextDependentNumber, int_lit);
6291 self.func.body.writeOperand(Id, label);
6292 }
6293 }
6294 }
6295
6296 var incoming_structured_blocks: std.ArrayListUnmanaged(ControlFlow.Structured.Block.Incoming) = .empty;
6297 defer incoming_structured_blocks.deinit(self.gpa);
6298
6299 if (self.control_flow == .structured) {
6300 try incoming_structured_blocks.ensureUnusedCapacity(self.gpa, num_cases + 1);
6301 }
6302
6303 // Now, finally, we can start emitting each of the cases.
6304 var it = switch_br.iterateCases();
6305 while (it.next()) |case| {
6306 const label = case_labels.at(case.idx);
6307
6308 try self.beginSpvBlock(label);
6309
6310 switch (self.control_flow) {
6311 .structured => {
6312 const next_block = try self.genStructuredBody(.selection, case.body);
6313 incoming_structured_blocks.appendAssumeCapacity(.{
6314 .src_label = self.current_block_label,
6315 .next_block = next_block,
6316 });
6317 try self.func.body.emitBranch(self.spv.gpa, merge_label.?);
6318 },
6319 .unstructured => {
6320 try self.genBody(case.body);
6321 },
6322 }
6323 }
6324
6325 const else_body = it.elseBody();
6326 try self.beginSpvBlock(default);
6327 if (else_body.len != 0) {
6328 switch (self.control_flow) {
6329 .structured => {
6330 const next_block = try self.genStructuredBody(.selection, else_body);
6331 incoming_structured_blocks.appendAssumeCapacity(.{
6332 .src_label = self.current_block_label,
6333 .next_block = next_block,
6334 });
6335 try self.func.body.emitBranch(self.spv.gpa, merge_label.?);
6336 },
6337 .unstructured => {
6338 try self.genBody(else_body);
6339 },
6340 }
6341 } else {
6342 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});
6343 }
6344
6345 if (self.control_flow == .structured) {
6346 try self.beginSpvBlock(merge_label.?);
6347 const next_block = try self.structuredNextBlock(incoming_structured_blocks.items);
6348 try self.structuredBreak(next_block);
6349 }
6350 }
6351
6352 fn airUnreach(self: *NavGen) !void {
6353 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});
6354 }
6355
6356 fn airDbgStmt(self: *NavGen, inst: Air.Inst.Index) !void {
6357 const pt = self.pt;
6358 const zcu = pt.zcu;
6359 const dbg_stmt = self.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt;
6360 const path = zcu.navFileScope(self.owner_nav).sub_file_path;
6361 try self.func.body.emit(self.spv.gpa, .OpLine, .{
6362 .file = try self.spv.resolveString(path),
6363 .line = self.base_line + dbg_stmt.line + 1,
6364 .column = dbg_stmt.column + 1,
6365 });
6366 }
6367
6368 fn airDbgInlineBlock(self: *NavGen, inst: Air.Inst.Index) !?Id {
6369 const zcu = self.pt.zcu;
6370 const inst_datas = self.air.instructions.items(.data);
6371 const extra = self.air.extraData(Air.DbgInlineBlock, inst_datas[@intFromEnum(inst)].ty_pl.payload);
6372 const old_base_line = self.base_line;
6373 defer self.base_line = old_base_line;
6374 self.base_line = zcu.navSrcLine(zcu.funcInfo(extra.data.func).owner_nav);
6375 return self.lowerBlock(inst, @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]));
6376 }
6377
6378 fn airDbgVar(self: *NavGen, inst: Air.Inst.Index) !void {
6379 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6380 const target_id = try self.resolve(pl_op.operand);
6381 const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload);
6382 try self.spv.debugName(target_id, name.toSlice(self.air));
6383 }
6384
6385 fn airAssembly(self: *NavGen, inst: Air.Inst.Index) !?Id {
6386 const zcu = self.pt.zcu;
6387 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
6388 const extra = self.air.extraData(Air.Asm, ty_pl.payload);
6389
6390 const is_volatile = extra.data.flags.is_volatile;
6391 const outputs_len = extra.data.flags.outputs_len;
6392
6393 if (!is_volatile and self.liveness.isUnused(inst)) return null;
6394
6395 var extra_i: usize = extra.end;
6396 const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..outputs_len]);
6397 extra_i += outputs.len;
6398 const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]);
6399 extra_i += inputs.len;
6400
6401 if (outputs.len > 1) {
6402 return self.todo("implement inline asm with more than 1 output", .{});
6403 }
6404
6405 var as: SpvAssembler = .{
6406 .gpa = self.gpa,
6407 .spv = self.spv,
6408 .func = &self.func,
6409 };
6410 defer as.deinit();
6411
6412 var output_extra_i = extra_i;
6413 for (outputs) |output| {
6414 if (output != .none) {
6415 return self.todo("implement inline asm with non-returned output", .{});
6416 }
6417 const extra_bytes = std.mem.sliceAsBytes(self.air.extra.items[extra_i..]);
6418 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0);
6419 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
6420 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
6421 // TODO: Record output and use it somewhere.
6422 }
6423
6424 for (inputs) |input| {
6425 const extra_bytes = std.mem.sliceAsBytes(self.air.extra.items[extra_i..]);
6426 const constraint = std.mem.sliceTo(extra_bytes, 0);
6427 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
6428 // This equation accounts for the fact that even if we have exactly 4 bytes
6429 // for the string, we still use the next u32 for the null terminator.
6430 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
6431
6432 const input_ty = self.typeOf(input);
6433
6434 if (std.mem.eql(u8, constraint, "c")) {
6435 // constant
6436 const val = (try self.air.value(input, self.pt)) orelse {
6437 return self.fail("assembly inputs with 'c' constraint have to be compile-time known", .{});
6438 };
6439
6440 // TODO: This entire function should be handled a bit better...
6441 const ip = &zcu.intern_pool;
6442 switch (ip.indexToKey(val.toIntern())) {
6443 .int_type,
6444 .ptr_type,
6445 .array_type,
6446 .vector_type,
6447 .opt_type,
6448 .anyframe_type,
6449 .error_union_type,
6450 .simple_type,
6451 .struct_type,
6452 .union_type,
6453 .opaque_type,
6454 .enum_type,
6455 .func_type,
6456 .error_set_type,
6457 .inferred_error_set_type,
6458 => unreachable, // types, not values
6459
6460 .undef => return self.fail("assembly input with 'c' constraint cannot be undefined", .{}),
6461
6462 .int => try as.value_map.put(as.gpa, name, .{ .constant = @intCast(val.toUnsignedInt(zcu)) }),
6463 .enum_literal => |str| try as.value_map.put(as.gpa, name, .{ .string = str.toSlice(ip) }),
6464
6465 else => unreachable, // TODO
6466 }
6467 } else if (std.mem.eql(u8, constraint, "t")) {
6468 // type
6469 if (input_ty.zigTypeTag(zcu) == .type) {
6470 // This assembly input is a type instead of a value.
6471 // That's fine for now, just make sure to resolve it as such.
6472 const val = (try self.air.value(input, self.pt)).?;
6473 const ty_id = try self.resolveType(val.toType(), .direct);
6474 try as.value_map.put(as.gpa, name, .{ .ty = ty_id });
6475 } else {
6476 const ty_id = try self.resolveType(input_ty, .direct);
6477 try as.value_map.put(as.gpa, name, .{ .ty = ty_id });
6478 }
6479 } else {
6480 if (input_ty.zigTypeTag(zcu) == .type) {
6481 return self.fail("use the 't' constraint to supply types to SPIR-V inline assembly", .{});
6482 }
6483
6484 const val_id = try self.resolve(input);
6485 try as.value_map.put(as.gpa, name, .{ .value = val_id });
6486 }
6487 }
6488
6489 // TODO: do something with clobbers
6490 _ = extra.data.clobbers;
6491
6492 const asm_source = std.mem.sliceAsBytes(self.air.extra.items[extra_i..])[0..extra.data.source_len];
6493
6494 as.assemble(asm_source) catch |err| switch (err) {
6495 error.AssembleFail => {
6496 // TODO: For now the compiler only supports a single error message per decl,
6497 // so to translate the possible multiple errors from the assembler, emit
6498 // them as notes here.
6499 // TODO: Translate proper error locations.
6500 assert(as.errors.items.len != 0);
6501 assert(self.error_msg == null);
6502 const src_loc = zcu.navSrcLoc(self.owner_nav);
6503 self.error_msg = try Zcu.ErrorMsg.create(zcu.gpa, src_loc, "failed to assemble SPIR-V inline assembly", .{});
6504 const notes = try zcu.gpa.alloc(Zcu.ErrorMsg, as.errors.items.len);
6505
6506 // Sub-scope to prevent `return error.CodegenFail` from running the errdefers.
6507 {
6508 errdefer zcu.gpa.free(notes);
6509 var i: usize = 0;
6510 errdefer for (notes[0..i]) |*note| {
6511 note.deinit(zcu.gpa);
6512 };
6513
6514 while (i < as.errors.items.len) : (i += 1) {
6515 notes[i] = try Zcu.ErrorMsg.init(zcu.gpa, src_loc, "{s}", .{as.errors.items[i].msg});
6516 }
6517 }
6518 self.error_msg.?.notes = notes;
6519 return error.CodegenFail;
6520 },
6521 else => |others| return others,
6522 };
6523
6524 for (outputs) |output| {
6525 _ = output;
6526 const extra_bytes = std.mem.sliceAsBytes(self.air.extra.items[output_extra_i..]);
6527 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[output_extra_i..]), 0);
6528 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
6529 output_extra_i += (constraint.len + name.len + (2 + 3)) / 4;
6530
6531 const result = as.value_map.get(name) orelse return {
6532 return self.fail("invalid asm output '{s}'", .{name});
6533 };
6534
6535 switch (result) {
6536 .just_declared, .unresolved_forward_reference => unreachable,
6537 .ty => return self.fail("cannot return spir-v type as value from assembly", .{}),
6538 .value => |ref| return ref,
6539 .constant, .string => return self.fail("cannot return constant from assembly", .{}),
6540 }
6541
6542 // TODO: Multiple results
6543 // TODO: Check that the output type from assembly is the same as the type actually expected by Zig.
6544 }
6545
6546 return null;
6547 }
6548
6549 fn airCall(self: *NavGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !?Id {
6550 _ = modifier;
6551
6552 const pt = self.pt;
6553 const zcu = pt.zcu;
6554 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6555 const extra = self.air.extraData(Air.Call, pl_op.payload);
6556 const args: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.args_len]);
6557 const callee_ty = self.typeOf(pl_op.operand);
6558 const zig_fn_ty = switch (callee_ty.zigTypeTag(zcu)) {
6559 .@"fn" => callee_ty,
6560 .pointer => return self.fail("cannot call function pointers", .{}),
6561 else => unreachable,
6562 };
6563 const fn_info = zcu.typeToFunc(zig_fn_ty).?;
6564 const return_type = fn_info.return_type;
6565
6566 const result_type_id = try self.resolveFnReturnType(Type.fromInterned(return_type));
6567 const result_id = self.spv.allocId();
6568 const callee_id = try self.resolve(pl_op.operand);
6569
6570 comptime assert(zig_call_abi_ver == 3);
6571 const params = try self.gpa.alloc(spec.Id, args.len);
6572 defer self.gpa.free(params);
6573 var n_params: usize = 0;
6574 for (args) |arg| {
6575 // Note: resolve() might emit instructions, so we need to call it
6576 // before starting to emit OpFunctionCall instructions. Hence the
6577 // temporary params buffer.
6578 const arg_ty = self.typeOf(arg);
6579 if (!arg_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;
6580 const arg_id = try self.resolve(arg);
6581
6582 params[n_params] = arg_id;
6583 n_params += 1;
6584 }
6585
6586 try self.func.body.emit(self.spv.gpa, .OpFunctionCall, .{
6587 .id_result_type = result_type_id,
6588 .id_result = result_id,
6589 .function = callee_id,
6590 .id_ref_3 = params[0..n_params],
6591 });
6592
6593 if (self.liveness.isUnused(inst) or !Type.fromInterned(return_type).hasRuntimeBitsIgnoreComptime(zcu)) {
6594 return null;
6595 }
6596
6597 return result_id;
6598 }
6599
6600 fn builtin3D(self: *NavGen, result_ty: Type, builtin: spec.BuiltIn, dimension: u32, out_of_range_value: anytype) !Id {
6601 if (dimension >= 3) {
6602 return try self.constInt(result_ty, out_of_range_value);
6603 }
6604 const vec_ty = try self.pt.vectorType(.{
6605 .len = 3,
6606 .child = result_ty.toIntern(),
6607 });
6608 const ptr_ty_id = try self.ptrType(vec_ty, .input, .indirect);
6609 const spv_decl_index = try self.spv.builtin(ptr_ty_id, builtin);
6610 try self.func.decl_deps.put(self.spv.gpa, spv_decl_index, {});
6611 const ptr = self.spv.declPtr(spv_decl_index).result_id;
6612 const vec = try self.load(vec_ty, ptr, .{});
6613 return try self.extractVectorComponent(result_ty, vec, dimension);
6614 }
6615
6616 fn airWorkItemId(self: *NavGen, inst: Air.Inst.Index) !?Id {
6617 if (self.liveness.isUnused(inst)) return null;
6618 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6619 const dimension = pl_op.payload;
6620 // TODO: Should we make these builtins return usize?
6621 const result_id = try self.builtin3D(Type.u64, .local_invocation_id, dimension, 0);
6622 const tmp = Temporary.init(Type.u64, result_id);
6623 const result = try self.buildConvert(Type.u32, tmp);
6624 return try result.materialize(self);
6625 }
6626
6627 fn airWorkGroupSize(self: *NavGen, inst: Air.Inst.Index) !?Id {
6628 if (self.liveness.isUnused(inst)) return null;
6629 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6630 const dimension = pl_op.payload;
6631 // TODO: Should we make these builtins return usize?
6632 const result_id = try self.builtin3D(Type.u64, .workgroup_size, dimension, 0);
6633 const tmp = Temporary.init(Type.u64, result_id);
6634 const result = try self.buildConvert(Type.u32, tmp);
6635 return try result.materialize(self);
6636 }
6637
6638 fn airWorkGroupId(self: *NavGen, inst: Air.Inst.Index) !?Id {
6639 if (self.liveness.isUnused(inst)) return null;
6640 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6641 const dimension = pl_op.payload;
6642 // TODO: Should we make these builtins return usize?
6643 const result_id = try self.builtin3D(Type.u64, .workgroup_id, dimension, 0);
6644 const tmp = Temporary.init(Type.u64, result_id);
6645 const result = try self.buildConvert(Type.u32, tmp);
6646 return try result.materialize(self);
6647 }
6648
6649 fn typeOf(self: *NavGen, inst: Air.Inst.Ref) Type {
6650 const zcu = self.pt.zcu;
6651 return self.air.typeOf(inst, &zcu.intern_pool);
6652 }
6653
6654 fn typeOfIndex(self: *NavGen, inst: Air.Inst.Index) Type {
6655 const zcu = self.pt.zcu;
6656 return self.air.typeOfIndex(inst, &zcu.intern_pool);
6657 }
6658};
src/codegen/spirv/Assembler.zig+466-533
......@@ -1,147 +1,146 @@
1const Assembler = @This();
2
31const std = @import("std");
42const Allocator = std.mem.Allocator;
53const assert = std.debug.assert;
64
5const CodeGen = @import("CodeGen.zig");
6const Decl = @import("Module.zig").Decl;
7
78const spec = @import("spec.zig");
89const Opcode = spec.Opcode;
910const Word = spec.Word;
1011const Id = spec.Id;
1112const StorageClass = spec.StorageClass;
1213
13const SpvModule = @import("Module.zig");
14
15/// Represents a token in the assembly template.
16const Token = struct {
17 tag: Tag,
18 start: u32,
19 end: u32,
14const Assembler = @This();
2015
21 const Tag = enum {
22 /// Returned when there was no more input to match.
23 eof,
24 /// %identifier
25 result_id,
26 /// %identifier when appearing on the LHS of an equals sign.
27 /// While not technically a token, its relatively easy to resolve
28 /// this during lexical analysis and relieves a bunch of headaches
29 /// during parsing.
30 result_id_assign,
31 /// Mask, int, or float. These are grouped together as some
32 /// SPIR-V enumerants look a bit like integers as well (for example
33 /// "3D"), and so it is easier to just interpret them as the expected
34 /// type when resolving an instruction's operands.
35 value,
36 /// An enumerant that looks like an opcode, that is, OpXxxx.
37 /// Not necessarily a *valid* opcode.
38 opcode,
39 /// String literals.
40 /// Note, this token is also returned for unterminated
41 /// strings. In this case the closing " is not present.
42 string,
43 /// |.
44 pipe,
45 /// =.
46 equals,
47 /// $identifier. This is used (for now) for constant values, like integers.
48 /// These can be used in place of a normal `value`.
49 placeholder,
16cg: *CodeGen,
17errors: std.ArrayListUnmanaged(ErrorMsg) = .empty,
18src: []const u8 = undefined,
19/// `ass.src` tokenized.
20tokens: std.ArrayListUnmanaged(Token) = .empty,
21current_token: u32 = 0,
22/// The instruction that is currently being parsed or has just been parsed.
23inst: struct {
24 opcode: Opcode = undefined,
25 operands: std.ArrayListUnmanaged(Operand) = .empty,
26 string_bytes: std.ArrayListUnmanaged(u8) = .empty,
5027
51 fn name(self: Tag) []const u8 {
52 return switch (self) {
53 .eof => "<end of input>",
54 .result_id => "<result-id>",
55 .result_id_assign => "<assigned result-id>",
56 .value => "<value>",
57 .opcode => "<opcode>",
58 .string => "<string literal>",
59 .pipe => "'|'",
60 .equals => "'='",
61 .placeholder => "<placeholder>",
62 };
28 fn result(ass: @This()) ?AsmValue.Ref {
29 for (ass.operands.items[0..@min(ass.operands.items.len, 2)]) |op| {
30 switch (op) {
31 .result_id => |index| return index,
32 else => {},
33 }
6334 }
64 };
65};
35 return null;
36 }
37} = .{},
38value_map: std.StringArrayHashMapUnmanaged(AsmValue) = .{},
39inst_map: std.StringArrayHashMapUnmanaged(void) = .empty,
6640
67/// This union represents utility information for a decoded operand.
68/// Note that this union only needs to maintain a minimal amount of
69/// bookkeeping: these values are enough to either decode the operands
70/// into a spec type, or emit it directly into its binary form.
7141const Operand = union(enum) {
7242 /// Any 'simple' 32-bit value. This could be a mask or
7343 /// enumerant, etc, depending on the operands.
7444 value: u32,
75
76 /// An int- or float literal encoded as 1 word. This may be
77 /// a 32-bit literal or smaller, already in the proper format:
78 /// the opper bits are 0 for floats and unsigned ints, and sign-extended
79 /// for signed ints.
45 /// An int- or float literal encoded as 1 word.
8046 literal32: u32,
81
82 /// An int- or float literal encoded as 2 words. This may be a 33-bit
83 /// to 64 bit literal, already in the proper format:
84 /// the opper bits are 0 for floats and unsigned ints, and sign-extended
85 /// for signed ints.
47 /// An int- or float literal encoded as 2 words.
8648 literal64: u64,
87
88 /// A result-id which is assigned to in this instruction. If present,
89 /// this is the first operand of the instruction.
49 /// A result-id which is assigned to in this instruction.
50 /// If present, this is the first operand of the instruction.
9051 result_id: AsmValue.Ref,
91
9252 /// A result-id which referred to (not assigned to) in this instruction.
9353 ref_id: AsmValue.Ref,
94
9554 /// Offset into `inst.string_bytes`. The string ends at the next zero-terminator.
9655 string: u32,
9756};
9857
99/// A structure representing an error message that the assembler may return, when
100/// the assembly source is not syntactically or semantically correct.
58pub fn deinit(ass: *Assembler) void {
59 const gpa = ass.cg.module.gpa;
60 for (ass.errors.items) |err| gpa.free(err.msg);
61 ass.tokens.deinit(gpa);
62 ass.errors.deinit(gpa);
63 ass.inst.operands.deinit(gpa);
64 ass.inst.string_bytes.deinit(gpa);
65 ass.value_map.deinit(gpa);
66 ass.inst_map.deinit(gpa);
67}
68
69const Error = error{ AssembleFail, OutOfMemory };
70
71pub fn assemble(ass: *Assembler, src: []const u8) Error!void {
72 const gpa = ass.cg.module.gpa;
73
74 ass.src = src;
75 ass.errors.clearRetainingCapacity();
76
77 // Populate the opcode map if it isn't already
78 if (ass.inst_map.count() == 0) {
79 const instructions = spec.InstructionSet.core.instructions();
80 try ass.inst_map.ensureUnusedCapacity(gpa, @intCast(instructions.len));
81 for (spec.InstructionSet.core.instructions(), 0..) |inst, i| {
82 const entry = try ass.inst_map.getOrPut(gpa, inst.name);
83 assert(entry.index == i);
84 }
85 }
86
87 try ass.tokenize();
88 while (!ass.testToken(.eof)) {
89 try ass.parseInstruction();
90 try ass.processInstruction();
91 }
92
93 if (ass.errors.items.len > 0) return error.AssembleFail;
94}
95
10196const ErrorMsg = struct {
10297 /// The offset in bytes from the start of `src` that this error occured.
10398 byte_offset: u32,
104 /// An explanatory error message.
105 /// Memory is owned by `self.gpa`. TODO: Maybe allocate this with an arena
106 /// allocator if it is needed elsewhere?
10799 msg: []const u8,
108100};
109101
110/// Possible errors the `assemble` function may return.
111const Error = error{ AssembleFail, OutOfMemory };
102fn addError(ass: *Assembler, offset: u32, comptime fmt: []const u8, args: anytype) !void {
103 const gpa = ass.cg.module.gpa;
104 const msg = try std.fmt.allocPrint(gpa, fmt, args);
105 errdefer gpa.free(msg);
106 try ass.errors.append(gpa, .{
107 .byte_offset = offset,
108 .msg = msg,
109 });
110}
111
112fn fail(ass: *Assembler, offset: u32, comptime fmt: []const u8, args: anytype) Error {
113 try ass.addError(offset, fmt, args);
114 return error.AssembleFail;
115}
116
117fn todo(ass: *Assembler, comptime fmt: []const u8, args: anytype) Error {
118 return ass.fail(0, "todo: " ++ fmt, args);
119}
112120
113/// This union is used to keep track of results of spir-v instructions. This can either be just a plain
114/// result-id, in the case of most instructions, or for example a type that is constructed from
115/// an OpTypeXxx instruction.
116121const AsmValue = union(enum) {
117 /// The results are stored in an array hash map, and can be referred to either by name (without the %),
118 /// or by values of this index type.
122 /// The results are stored in an array hash map, and can be referred
123 /// to either by name (without the %), or by values of this index type.
119124 pub const Ref = u32;
120125
121 /// This result-value is the RHS of the current instruction.
126 /// The RHS of the current instruction.
122127 just_declared,
123
124 /// This is used as placeholder for ref-ids of which the result-id is not yet known.
128 /// A placeholder for ref-ids of which the result-id is not yet known.
125129 /// It will be further resolved at a later stage to a more concrete forward reference.
126130 unresolved_forward_reference,
127
128 /// This result-value is a normal result produced by a different instruction.
131 /// A normal result produced by a different instruction.
129132 value: Id,
130
131 /// This result-value represents a type registered into the module's type system.
133 /// A type registered into the module's type system.
132134 ty: Id,
133
134 /// This is a pre-supplied constant integer value.
135 /// A pre-supplied constant integer value.
135136 constant: u32,
136
137 /// This is a pre-supplied constant string value.
138137 string: []const u8,
139138
140139 /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue
141140 /// is of a variant that allows the result to be obtained (not an unresolved
142141 /// forward declaration, not in the process of being declared, etc).
143 pub fn resultId(self: AsmValue) Id {
144 return switch (self) {
142 pub fn resultId(value: AsmValue) Id {
143 return switch (value) {
145144 .just_declared,
146145 .unresolved_forward_reference,
147146 // TODO: Lower this value as constant?
......@@ -154,226 +153,101 @@ const AsmValue = union(enum) {
154153 }
155154};
156155
157/// This map type maps results to values. Results can be addressed either by name (without the %), or by
158/// AsmValue.Ref in AsmValueMap.keys/.values.
159const AsmValueMap = std.StringArrayHashMapUnmanaged(AsmValue);
160
161/// An allocator used for common allocations.
162gpa: Allocator,
163
164/// A list of errors that occured during processing the assembly.
165errors: std.ArrayListUnmanaged(ErrorMsg) = .empty,
166
167/// The source code that is being assembled.
168/// This is set when calling `assemble()`.
169src: []const u8 = undefined,
170
171/// The module that this assembly is associated to.
172/// Instructions like OpType*, OpDecorate, etc are emitted into this module.
173spv: *SpvModule,
174
175/// The function that the function-specific instructions should be emitted to.
176func: *SpvModule.Fn,
177
178/// `self.src` tokenized.
179tokens: std.ArrayListUnmanaged(Token) = .empty,
180
181/// The token that is next during parsing.
182current_token: u32 = 0,
183
184/// This field groups the properties of the instruction that is currently
185/// being parsed or has just been parsed.
186inst: struct {
187 /// The opcode of the current instruction.
188 opcode: Opcode = undefined,
189 /// Operands of the current instruction.
190 operands: std.ArrayListUnmanaged(Operand) = .empty,
191 /// This is where string data resides. Strings are zero-terminated.
192 string_bytes: std.ArrayListUnmanaged(u8) = .empty,
193
194 /// Return a reference to the result of this instruction, if any.
195 fn result(self: @This()) ?AsmValue.Ref {
196 // The result, if present, is either the first or second
197 // operand of an instruction.
198 for (self.operands.items[0..@min(self.operands.items.len, 2)]) |op| {
199 switch (op) {
200 .result_id => |index| return index,
201 else => {},
202 }
203 }
204 return null;
205 }
206} = .{},
207
208/// This map maps results to their tracked values.
209value_map: AsmValueMap = .{},
210
211/// This set is used to quickly transform from an opcode name to the
212/// index in its instruction set. The index of the key is the
213/// index in `spec.InstructionSet.core.instructions()`.
214instruction_map: std.StringArrayHashMapUnmanaged(void) = .empty,
215
216/// Free the resources owned by this assembler.
217pub fn deinit(self: *Assembler) void {
218 for (self.errors.items) |err| {
219 self.gpa.free(err.msg);
220 }
221 self.tokens.deinit(self.gpa);
222 self.errors.deinit(self.gpa);
223 self.inst.operands.deinit(self.gpa);
224 self.inst.string_bytes.deinit(self.gpa);
225 self.value_map.deinit(self.gpa);
226 self.instruction_map.deinit(self.gpa);
227}
228
229pub fn assemble(self: *Assembler, src: []const u8) Error!void {
230 self.src = src;
231 self.errors.clearRetainingCapacity();
232
233 // Populate the opcode map if it isn't already
234 if (self.instruction_map.count() == 0) {
235 const instructions = spec.InstructionSet.core.instructions();
236 try self.instruction_map.ensureUnusedCapacity(self.gpa, @intCast(instructions.len));
237 for (spec.InstructionSet.core.instructions(), 0..) |inst, i| {
238 const entry = try self.instruction_map.getOrPut(self.gpa, inst.name);
239 assert(entry.index == i);
240 }
241 }
242
243 try self.tokenize();
244 while (!self.testToken(.eof)) {
245 try self.parseInstruction();
246 try self.processInstruction();
247 }
248 if (self.errors.items.len > 0)
249 return error.AssembleFail;
250}
251
252fn addError(self: *Assembler, offset: u32, comptime fmt: []const u8, args: anytype) !void {
253 const msg = try std.fmt.allocPrint(self.gpa, fmt, args);
254 errdefer self.gpa.free(msg);
255 try self.errors.append(self.gpa, .{
256 .byte_offset = offset,
257 .msg = msg,
258 });
259}
260
261fn fail(self: *Assembler, offset: u32, comptime fmt: []const u8, args: anytype) Error {
262 try self.addError(offset, fmt, args);
263 return error.AssembleFail;
264}
265
266fn todo(self: *Assembler, comptime fmt: []const u8, args: anytype) Error {
267 return self.fail(0, "todo: " ++ fmt, args);
268}
269
270/// Attempt to process the instruction currently in `self.inst`.
156/// Attempt to process the instruction currently in `ass.inst`.
271157/// This for example emits the instruction in the module or function, or
272158/// records type definitions.
273159/// If this function returns `error.AssembleFail`, an explanatory
274/// error message has already been emitted into `self.errors`.
275fn processInstruction(self: *Assembler) !void {
276 const result: AsmValue = switch (self.inst.opcode) {
160/// error message has already been emitted into `ass.errors`.
161fn processInstruction(ass: *Assembler) !void {
162 const module = ass.cg.module;
163 const result: AsmValue = switch (ass.inst.opcode) {
277164 .OpEntryPoint => {
278 return self.fail(0, "cannot export entry points via OpEntryPoint, export the kernel using callconv(.kernel)", .{});
165 return ass.fail(ass.currentToken().start, "cannot export entry points in assembly", .{});
166 },
167 .OpExecutionMode, .OpExecutionModeId => {
168 return ass.fail(ass.currentToken().start, "cannot set execution mode in assembly", .{});
279169 },
280170 .OpCapability => {
281 try self.spv.addCapability(@enumFromInt(self.inst.operands.items[0].value));
171 try module.addCapability(@enumFromInt(ass.inst.operands.items[0].value));
282172 return;
283173 },
284174 .OpExtension => {
285 const ext_name_offset = self.inst.operands.items[0].string;
286 const ext_name = std.mem.sliceTo(self.inst.string_bytes.items[ext_name_offset..], 0);
287 try self.spv.addExtension(ext_name);
175 const ext_name_offset = ass.inst.operands.items[0].string;
176 const ext_name = std.mem.sliceTo(ass.inst.string_bytes.items[ext_name_offset..], 0);
177 try module.addExtension(ext_name);
288178 return;
289179 },
290180 .OpExtInstImport => blk: {
291 const set_name_offset = self.inst.operands.items[1].string;
292 const set_name = std.mem.sliceTo(self.inst.string_bytes.items[set_name_offset..], 0);
181 const set_name_offset = ass.inst.operands.items[1].string;
182 const set_name = std.mem.sliceTo(ass.inst.string_bytes.items[set_name_offset..], 0);
293183 const set_tag = std.meta.stringToEnum(spec.InstructionSet, set_name) orelse {
294 return self.fail(set_name_offset, "unknown instruction set: {s}", .{set_name});
184 return ass.fail(set_name_offset, "unknown instruction set: {s}", .{set_name});
295185 };
296 break :blk .{ .value = try self.spv.importInstructionSet(set_tag) };
186 break :blk .{ .value = try module.importInstructionSet(set_tag) };
297187 },
298 .OpExecutionMode, .OpExecutionModeId => {
299 assert(try self.processGenericInstruction() == null);
300 const entry_point_id = try self.resolveRefId(self.inst.operands.items[0].ref_id);
301 const exec_mode: spec.ExecutionMode = @enumFromInt(self.inst.operands.items[1].value);
302 const gop = try self.spv.entry_points.getOrPut(self.gpa, entry_point_id);
303 if (!gop.found_existing) {
304 gop.value_ptr.* = .{};
305 } else if (gop.value_ptr.exec_mode != null) {
306 return self.fail(
307 self.currentToken().start,
308 "cannot set execution mode more than once to any entry point",
309 .{},
310 );
311 }
312 gop.value_ptr.exec_mode = exec_mode;
313 return;
314 },
315 else => switch (self.inst.opcode.class()) {
316 .type_declaration => try self.processTypeInstruction(),
317 else => (try self.processGenericInstruction()) orelse return,
188 else => switch (ass.inst.opcode.class()) {
189 .type_declaration => try ass.processTypeInstruction(),
190 else => (try ass.processGenericInstruction()) orelse return,
318191 },
319192 };
320193
321 const result_ref = self.inst.result().?;
322 switch (self.value_map.values()[result_ref]) {
323 .just_declared => self.value_map.values()[result_ref] = result,
194 const result_ref = ass.inst.result().?;
195 switch (ass.value_map.values()[result_ref]) {
196 .just_declared => ass.value_map.values()[result_ref] = result,
324197 else => {
325198 // TODO: Improve source location.
326 const name = self.value_map.keys()[result_ref];
327 return self.fail(0, "duplicate definition of %{s}", .{name});
199 const name = ass.value_map.keys()[result_ref];
200 return ass.fail(0, "duplicate definition of %{s}", .{name});
328201 },
329202 }
330203}
331204
332/// Record `self.inst` into the module's type system, and return the AsmValue that
333/// refers to the result.
334fn processTypeInstruction(self: *Assembler) !AsmValue {
335 const operands = self.inst.operands.items;
336 const section = &self.spv.sections.types_globals_constants;
337 const id = switch (self.inst.opcode) {
338 .OpTypeVoid => try self.spv.voidType(),
339 .OpTypeBool => try self.spv.boolType(),
205fn processTypeInstruction(ass: *Assembler) !AsmValue {
206 const cg = ass.cg;
207 const gpa = cg.module.gpa;
208 const module = cg.module;
209 const operands = ass.inst.operands.items;
210 const section = &module.sections.globals;
211 const id = switch (ass.inst.opcode) {
212 .OpTypeVoid => try module.voidType(),
213 .OpTypeBool => try module.boolType(),
340214 .OpTypeInt => blk: {
341215 const signedness: std.builtin.Signedness = switch (operands[2].literal32) {
342216 0 => .unsigned,
343217 1 => .signed,
344218 else => {
345219 // TODO: Improve source location.
346 return self.fail(0, "{} is not a valid signedness (expected 0 or 1)", .{operands[2].literal32});
220 return ass.fail(0, "{} is not a valid signedness (expected 0 or 1)", .{operands[2].literal32});
347221 },
348222 };
349223 const width = std.math.cast(u16, operands[1].literal32) orelse {
350 return self.fail(0, "int type of {} bits is too large", .{operands[1].literal32});
224 return ass.fail(0, "int type of {} bits is too large", .{operands[1].literal32});
351225 };
352 break :blk try self.spv.intType(signedness, width);
226 break :blk try module.intType(signedness, width);
353227 },
354228 .OpTypeFloat => blk: {
355229 const bits = operands[1].literal32;
356230 switch (bits) {
357231 16, 32, 64 => {},
358232 else => {
359 return self.fail(0, "{} is not a valid bit count for floats (expected 16, 32 or 64)", .{bits});
233 return ass.fail(0, "{} is not a valid bit count for floats (expected 16, 32 or 64)", .{bits});
360234 },
361235 }
362 break :blk try self.spv.floatType(@intCast(bits));
236 break :blk try module.floatType(@intCast(bits));
363237 },
364238 .OpTypeVector => blk: {
365 const child_type = try self.resolveRefId(operands[1].ref_id);
366 break :blk try self.spv.vectorType(operands[2].literal32, child_type);
239 const child_type = try ass.resolveRefId(operands[1].ref_id);
240 break :blk try module.vectorType(operands[2].literal32, child_type);
367241 },
368242 .OpTypeArray => {
369243 // TODO: The length of an OpTypeArray is determined by a constant (which may be a spec constant),
370244 // and so some consideration must be taken when entering this in the type system.
371 return self.todo("process OpTypeArray", .{});
245 return ass.todo("process OpTypeArray", .{});
372246 },
373247 .OpTypeRuntimeArray => blk: {
374 const element_type = try self.resolveRefId(operands[1].ref_id);
375 const result_id = self.spv.allocId();
376 try section.emit(self.spv.gpa, .OpTypeRuntimeArray, .{
248 const element_type = try ass.resolveRefId(operands[1].ref_id);
249 const result_id = module.allocId();
250 try section.emit(module.gpa, .OpTypeRuntimeArray, .{
377251 .id_result = result_id,
378252 .element_type = element_type,
379253 });
......@@ -381,9 +255,9 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
381255 },
382256 .OpTypePointer => blk: {
383257 const storage_class: StorageClass = @enumFromInt(operands[1].value);
384 const child_type = try self.resolveRefId(operands[2].ref_id);
385 const result_id = self.spv.allocId();
386 try section.emit(self.spv.gpa, .OpTypePointer, .{
258 const child_type = try ass.resolveRefId(operands[2].ref_id);
259 const result_id = module.allocId();
260 try section.emit(module.gpa, .OpTypePointer, .{
387261 .id_result = result_id,
388262 .storage_class = storage_class,
389263 .type = child_type,
......@@ -391,17 +265,16 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
391265 break :blk result_id;
392266 },
393267 .OpTypeStruct => blk: {
394 const ids = try self.gpa.alloc(Id, operands[1..].len);
395 defer self.gpa.free(ids);
396 for (operands[1..], ids) |op, *id| id.* = try self.resolveRefId(op.ref_id);
397 const result_id = self.spv.allocId();
398 try self.spv.structType(result_id, ids, null);
399 break :blk result_id;
268 const scratch_top = cg.id_scratch.items.len;
269 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
270 const ids = try cg.id_scratch.addManyAsSlice(gpa, operands[1..].len);
271 for (operands[1..], ids) |op, *id| id.* = try ass.resolveRefId(op.ref_id);
272 break :blk try module.structType(ids, null, null, .none);
400273 },
401274 .OpTypeImage => blk: {
402 const sampled_type = try self.resolveRefId(operands[1].ref_id);
403 const result_id = self.spv.allocId();
404 try section.emit(self.gpa, .OpTypeImage, .{
275 const sampled_type = try ass.resolveRefId(operands[1].ref_id);
276 const result_id = module.allocId();
277 try section.emit(gpa, .OpTypeImage, .{
405278 .id_result = result_id,
406279 .sampled_type = sampled_type,
407280 .dim = @enumFromInt(operands[2].value),
......@@ -414,187 +287,178 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
414287 break :blk result_id;
415288 },
416289 .OpTypeSampler => blk: {
417 const result_id = self.spv.allocId();
418 try section.emit(self.gpa, .OpTypeSampler, .{ .id_result = result_id });
290 const result_id = module.allocId();
291 try section.emit(gpa, .OpTypeSampler, .{ .id_result = result_id });
419292 break :blk result_id;
420293 },
421294 .OpTypeSampledImage => blk: {
422 const image_type = try self.resolveRefId(operands[1].ref_id);
423 const result_id = self.spv.allocId();
424 try section.emit(self.gpa, .OpTypeSampledImage, .{ .id_result = result_id, .image_type = image_type });
295 const image_type = try ass.resolveRefId(operands[1].ref_id);
296 const result_id = module.allocId();
297 try section.emit(gpa, .OpTypeSampledImage, .{ .id_result = result_id, .image_type = image_type });
425298 break :blk result_id;
426299 },
427300 .OpTypeFunction => blk: {
428301 const param_operands = operands[2..];
429 const return_type = try self.resolveRefId(operands[1].ref_id);
302 const return_type = try ass.resolveRefId(operands[1].ref_id);
303
304 const scratch_top = cg.id_scratch.items.len;
305 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
306 const param_types = try cg.id_scratch.addManyAsSlice(gpa, param_operands.len);
430307
431 const param_types = try self.spv.gpa.alloc(Id, param_operands.len);
432 defer self.spv.gpa.free(param_types);
433308 for (param_types, param_operands) |*param, operand| {
434 param.* = try self.resolveRefId(operand.ref_id);
309 param.* = try ass.resolveRefId(operand.ref_id);
435310 }
436 const result_id = self.spv.allocId();
437 try section.emit(self.spv.gpa, .OpTypeFunction, .{
311 const result_id = module.allocId();
312 try section.emit(module.gpa, .OpTypeFunction, .{
438313 .id_result = result_id,
439314 .return_type = return_type,
440315 .id_ref_2 = param_types,
441316 });
442317 break :blk result_id;
443318 },
444 else => return self.todo("process type instruction {s}", .{@tagName(self.inst.opcode)}),
319 else => return ass.todo("process type instruction {s}", .{@tagName(ass.inst.opcode)}),
445320 };
446321
447 return AsmValue{ .ty = id };
322 return .{ .ty = id };
448323}
449324
450/// Emit `self.inst` into `self.spv` and `self.func`, and return the AsmValue
451/// that this produces (if any). This function processes common instructions:
452325/// - No forward references are allowed in operands.
453326/// - Target section is determined from instruction type.
454/// - Function-local instructions are emitted in `self.func`.
455fn processGenericInstruction(self: *Assembler) !?AsmValue {
456 const operands = self.inst.operands.items;
457 var maybe_spv_decl_index: ?SpvModule.Decl.Index = null;
458 const section = switch (self.inst.opcode.class()) {
459 .constant_creation => &self.spv.sections.types_globals_constants,
460 .annotation => &self.spv.sections.annotations,
327fn processGenericInstruction(ass: *Assembler) !?AsmValue {
328 const module = ass.cg.module;
329 const target = module.zcu.getTarget();
330 const operands = ass.inst.operands.items;
331 var maybe_spv_decl_index: ?Decl.Index = null;
332 const section = switch (ass.inst.opcode.class()) {
333 .constant_creation => &module.sections.globals,
334 .annotation => &module.sections.annotations,
461335 .type_declaration => unreachable, // Handled elsewhere.
462 else => switch (self.inst.opcode) {
336 else => switch (ass.inst.opcode) {
463337 .OpEntryPoint => unreachable,
464 .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes,
338 .OpExecutionMode, .OpExecutionModeId => &module.sections.execution_modes,
465339 .OpVariable => section: {
466340 const storage_class: spec.StorageClass = @enumFromInt(operands[2].value);
467 if (storage_class == .function) break :section &self.func.prologue;
468 maybe_spv_decl_index = try self.spv.allocDecl(.global);
469 if (self.spv.version.minor < 4 and storage_class != .input and storage_class != .output) {
341 if (storage_class == .function) break :section &ass.cg.prologue;
342 maybe_spv_decl_index = try module.allocDecl(.global);
343 if (!target.cpu.has(.spirv, .v1_4) and storage_class != .input and storage_class != .output) {
470344 // Before version 1.4, the interface’s storage classes are limited to the Input and Output
471 break :section &self.spv.sections.types_globals_constants;
345 break :section &module.sections.globals;
472346 }
473 try self.func.decl_deps.put(self.spv.gpa, maybe_spv_decl_index.?, {});
474 // TODO: In theory this can be non-empty if there is an initializer which depends on another global...
475 try self.spv.declareDeclDeps(maybe_spv_decl_index.?, &.{});
476 break :section &self.spv.sections.types_globals_constants;
347 try ass.cg.module.decl_deps.append(module.gpa, maybe_spv_decl_index.?);
348 break :section &module.sections.globals;
477349 },
478 // Default case - to be worked out further.
479 else => &self.func.body,
350 else => &ass.cg.body,
480351 },
481352 };
482353
483354 var maybe_result_id: ?Id = null;
484355 const first_word = section.instructions.items.len;
485 // At this point we're not quite sure how many operands this instruction is going to have,
486 // so insert 0 and patch up the actual opcode word later.
487 try section.ensureUnusedCapacity(self.spv.gpa, 1);
356 // At this point we're not quite sure how many operands this instruction is
357 // going to have, so insert 0 and patch up the actual opcode word later.
358 try section.ensureUnusedCapacity(module.gpa, 1);
488359 section.writeWord(0);
489360
490361 for (operands) |operand| {
491362 switch (operand) {
492363 .value, .literal32 => |word| {
493 try section.ensureUnusedCapacity(self.spv.gpa, 1);
364 try section.ensureUnusedCapacity(module.gpa, 1);
494365 section.writeWord(word);
495366 },
496367 .literal64 => |dword| {
497 try section.ensureUnusedCapacity(self.spv.gpa, 2);
368 try section.ensureUnusedCapacity(module.gpa, 2);
498369 section.writeDoubleWord(dword);
499370 },
500371 .result_id => {
501372 maybe_result_id = if (maybe_spv_decl_index) |spv_decl_index|
502 self.spv.declPtr(spv_decl_index).result_id
373 module.declPtr(spv_decl_index).result_id
503374 else
504 self.spv.allocId();
505 try section.ensureUnusedCapacity(self.spv.gpa, 1);
375 module.allocId();
376 try section.ensureUnusedCapacity(module.gpa, 1);
506377 section.writeOperand(Id, maybe_result_id.?);
507378 },
508379 .ref_id => |index| {
509 const result = try self.resolveRef(index);
510 try section.ensureUnusedCapacity(self.spv.gpa, 1);
380 const result = try ass.resolveRef(index);
381 try section.ensureUnusedCapacity(module.gpa, 1);
511382 section.writeOperand(spec.Id, result.resultId());
512383 },
513384 .string => |offset| {
514 const text = std.mem.sliceTo(self.inst.string_bytes.items[offset..], 0);
385 const text = std.mem.sliceTo(ass.inst.string_bytes.items[offset..], 0);
515386 const size = std.math.divCeil(usize, text.len + 1, @sizeOf(Word)) catch unreachable;
516 try section.ensureUnusedCapacity(self.spv.gpa, size);
387 try section.ensureUnusedCapacity(module.gpa, size);
517388 section.writeOperand(spec.LiteralString, text);
518389 },
519390 }
520391 }
521392
522393 const actual_word_count = section.instructions.items.len - first_word;
523 section.instructions.items[first_word] |= @as(u32, @as(u16, @intCast(actual_word_count))) << 16 | @intFromEnum(self.inst.opcode);
394 section.instructions.items[first_word] |= @as(u32, @as(u16, @intCast(actual_word_count))) << 16 | @intFromEnum(ass.inst.opcode);
524395
525 if (maybe_result_id) |result| {
526 return AsmValue{ .value = result };
527 }
396 if (maybe_result_id) |result| return .{ .value = result };
528397 return null;
529398}
530399
531/// Resolve a value reference. This function makes sure that the reference is
532/// not self-referential, but it does allow the result to be forward declared.
533fn resolveMaybeForwardRef(self: *Assembler, ref: AsmValue.Ref) !AsmValue {
534 const value = self.value_map.values()[ref];
400fn resolveMaybeForwardRef(ass: *Assembler, ref: AsmValue.Ref) !AsmValue {
401 const value = ass.value_map.values()[ref];
535402 switch (value) {
536403 .just_declared => {
537 const name = self.value_map.keys()[ref];
404 const name = ass.value_map.keys()[ref];
538405 // TODO: Improve source location.
539 return self.fail(0, "self-referential parameter %{s}", .{name});
406 return ass.fail(0, "ass-referential parameter %{s}", .{name});
540407 },
541408 else => return value,
542409 }
543410}
544411
545/// Resolve a value reference. This function
546/// makes sure that the result is not self-referential, nor that it is forward declared.
547fn resolveRef(self: *Assembler, ref: AsmValue.Ref) !AsmValue {
548 const value = try self.resolveMaybeForwardRef(ref);
412fn resolveRef(ass: *Assembler, ref: AsmValue.Ref) !AsmValue {
413 const value = try ass.resolveMaybeForwardRef(ref);
549414 switch (value) {
550415 .just_declared => unreachable,
551416 .unresolved_forward_reference => {
552 const name = self.value_map.keys()[ref];
417 const name = ass.value_map.keys()[ref];
553418 // TODO: Improve source location.
554 return self.fail(0, "reference to undeclared result-id %{s}", .{name});
419 return ass.fail(0, "reference to undeclared result-id %{s}", .{name});
555420 },
556421 else => return value,
557422 }
558423}
559424
560fn resolveRefId(self: *Assembler, ref: AsmValue.Ref) !Id {
561 const value = try self.resolveRef(ref);
425fn resolveRefId(ass: *Assembler, ref: AsmValue.Ref) !Id {
426 const value = try ass.resolveRef(ref);
562427 return value.resultId();
563428}
564429
565/// Attempt to parse an instruction into `self.inst`.
566/// If this function returns `error.AssembleFail`, an explanatory
567/// error message has been emitted into `self.errors`.
568fn parseInstruction(self: *Assembler) !void {
569 self.inst.opcode = undefined;
570 self.inst.operands.clearRetainingCapacity();
571 self.inst.string_bytes.clearRetainingCapacity();
572
573 const lhs_result_tok = self.currentToken();
574 const maybe_lhs_result: ?AsmValue.Ref = if (self.eatToken(.result_id_assign)) blk: {
575 const name = self.tokenText(lhs_result_tok)[1..];
576 const entry = try self.value_map.getOrPut(self.gpa, name);
577 try self.expectToken(.equals);
430fn parseInstruction(ass: *Assembler) !void {
431 const gpa = ass.cg.module.gpa;
432
433 ass.inst.opcode = undefined;
434 ass.inst.operands.clearRetainingCapacity();
435 ass.inst.string_bytes.clearRetainingCapacity();
436
437 const lhs_result_tok = ass.currentToken();
438 const maybe_lhs_result: ?AsmValue.Ref = if (ass.eatToken(.result_id_assign)) blk: {
439 const name = ass.tokenText(lhs_result_tok)[1..];
440 const entry = try ass.value_map.getOrPut(gpa, name);
441 try ass.expectToken(.equals);
578442 if (!entry.found_existing) {
579443 entry.value_ptr.* = .just_declared;
580444 }
581445 break :blk @intCast(entry.index);
582446 } else null;
583447
584 const opcode_tok = self.currentToken();
448 const opcode_tok = ass.currentToken();
585449 if (maybe_lhs_result != null) {
586 try self.expectToken(.opcode);
587 } else if (!self.eatToken(.opcode)) {
588 return self.fail(opcode_tok.start, "expected start of instruction, found {s}", .{opcode_tok.tag.name()});
450 try ass.expectToken(.opcode);
451 } else if (!ass.eatToken(.opcode)) {
452 return ass.fail(opcode_tok.start, "expected start of instruction, found {s}", .{opcode_tok.tag.name()});
589453 }
590454
591 const opcode_text = self.tokenText(opcode_tok);
592 const index = self.instruction_map.getIndex(opcode_text) orelse {
593 return self.fail(opcode_tok.start, "invalid opcode '{s}'", .{opcode_text});
455 const opcode_text = ass.tokenText(opcode_tok);
456 const index = ass.inst_map.getIndex(opcode_text) orelse {
457 return ass.fail(opcode_tok.start, "invalid opcode '{s}'", .{opcode_text});
594458 };
595459
596460 const inst = spec.InstructionSet.core.instructions()[index];
597 self.inst.opcode = @enumFromInt(inst.opcode);
461 ass.inst.opcode = @enumFromInt(inst.opcode);
598462
599463 const expected_operands = inst.operands;
600464 // This is a loop because the result-id is not always the first operand.
......@@ -603,66 +467,67 @@ fn parseInstruction(self: *Assembler) !void {
603467 } else false;
604468
605469 if (requires_lhs_result and maybe_lhs_result == null) {
606 return self.fail(opcode_tok.start, "opcode '{s}' expects result on left-hand side", .{@tagName(self.inst.opcode)});
470 return ass.fail(opcode_tok.start, "opcode '{s}' expects result on left-hand side", .{@tagName(ass.inst.opcode)});
607471 } else if (!requires_lhs_result and maybe_lhs_result != null) {
608 return self.fail(
472 return ass.fail(
609473 lhs_result_tok.start,
610474 "opcode '{s}' does not expect a result-id on the left-hand side",
611 .{@tagName(self.inst.opcode)},
475 .{@tagName(ass.inst.opcode)},
612476 );
613477 }
614478
615479 for (expected_operands) |operand| {
616480 if (operand.kind == .id_result) {
617 try self.inst.operands.append(self.gpa, .{ .result_id = maybe_lhs_result.? });
481 try ass.inst.operands.append(gpa, .{ .result_id = maybe_lhs_result.? });
618482 continue;
619483 }
620484
621485 switch (operand.quantifier) {
622 .required => if (self.isAtInstructionBoundary()) {
623 return self.fail(
624 self.currentToken().start,
486 .required => if (ass.isAtInstructionBoundary()) {
487 return ass.fail(
488 ass.currentToken().start,
625489 "missing required operand", // TODO: Operand name?
626490 .{},
627491 );
628492 } else {
629 try self.parseOperand(operand.kind);
493 try ass.parseOperand(operand.kind);
630494 },
631 .optional => if (!self.isAtInstructionBoundary()) {
632 try self.parseOperand(operand.kind);
495 .optional => if (!ass.isAtInstructionBoundary()) {
496 try ass.parseOperand(operand.kind);
633497 },
634 .variadic => while (!self.isAtInstructionBoundary()) {
635 try self.parseOperand(operand.kind);
498 .variadic => while (!ass.isAtInstructionBoundary()) {
499 try ass.parseOperand(operand.kind);
636500 },
637501 }
638502 }
639503}
640504
641/// Parse a single operand of a particular type.
642fn parseOperand(self: *Assembler, kind: spec.OperandKind) Error!void {
505fn parseOperand(ass: *Assembler, kind: spec.OperandKind) Error!void {
643506 switch (kind.category()) {
644 .bit_enum => try self.parseBitEnum(kind),
645 .value_enum => try self.parseValueEnum(kind),
646 .id => try self.parseRefId(),
507 .bit_enum => try ass.parseBitEnum(kind),
508 .value_enum => try ass.parseValueEnum(kind),
509 .id => try ass.parseRefId(),
647510 else => switch (kind) {
648 .literal_integer => try self.parseLiteralInteger(),
649 .literal_string => try self.parseString(),
650 .literal_context_dependent_number => try self.parseContextDependentNumber(),
651 .literal_ext_inst_integer => try self.parseLiteralExtInstInteger(),
652 .pair_id_ref_id_ref => try self.parsePhiSource(),
653 else => return self.todo("parse operand of type {s}", .{@tagName(kind)}),
511 .literal_integer => try ass.parseLiteralInteger(),
512 .literal_string => try ass.parseString(),
513 .literal_context_dependent_number => try ass.parseContextDependentNumber(),
514 .literal_ext_inst_integer => try ass.parseLiteralExtInstInteger(),
515 .pair_id_ref_id_ref => try ass.parsePhiSource(),
516 else => return ass.todo("parse operand of type {s}", .{@tagName(kind)}),
654517 },
655518 }
656519}
657520
658521/// Also handles parsing any required extra operands.
659fn parseBitEnum(self: *Assembler, kind: spec.OperandKind) !void {
660 var tok = self.currentToken();
661 try self.expectToken(.value);
522fn parseBitEnum(ass: *Assembler, kind: spec.OperandKind) !void {
523 const gpa = ass.cg.module.gpa;
524
525 var tok = ass.currentToken();
526 try ass.expectToken(.value);
662527
663 var text = self.tokenText(tok);
528 var text = ass.tokenText(tok);
664529 if (std.mem.eql(u8, text, "None")) {
665 try self.inst.operands.append(self.gpa, .{ .value = 0 });
530 try ass.inst.operands.append(gpa, .{ .value = 0 });
666531 return;
667532 }
668533
......@@ -673,18 +538,18 @@ fn parseBitEnum(self: *Assembler, kind: spec.OperandKind) !void {
673538 if (std.mem.eql(u8, enumerant.name, text))
674539 break enumerant;
675540 } else {
676 return self.fail(tok.start, "'{s}' is not a valid flag for bitmask {s}", .{ text, @tagName(kind) });
541 return ass.fail(tok.start, "'{s}' is not a valid flag for bitmask {s}", .{ text, @tagName(kind) });
677542 };
678543 mask |= enumerant.value;
679 if (!self.eatToken(.pipe))
544 if (!ass.eatToken(.pipe))
680545 break;
681546
682 tok = self.currentToken();
683 try self.expectToken(.value);
684 text = self.tokenText(tok);
547 tok = ass.currentToken();
548 try ass.expectToken(.value);
549 text = ass.tokenText(tok);
685550 }
686551
687 try self.inst.operands.append(self.gpa, .{ .value = mask });
552 try ass.inst.operands.append(gpa, .{ .value = mask });
688553
689554 // Assume values are sorted.
690555 // TODO: ensure in generator.
......@@ -693,43 +558,45 @@ fn parseBitEnum(self: *Assembler, kind: spec.OperandKind) !void {
693558 continue;
694559
695560 for (enumerant.parameters) |param_kind| {
696 if (self.isAtInstructionBoundary()) {
697 return self.fail(self.currentToken().start, "missing required parameter for bit flag '{s}'", .{enumerant.name});
561 if (ass.isAtInstructionBoundary()) {
562 return ass.fail(ass.currentToken().start, "missing required parameter for bit flag '{s}'", .{enumerant.name});
698563 }
699564
700 try self.parseOperand(param_kind);
565 try ass.parseOperand(param_kind);
701566 }
702567 }
703568}
704569
705570/// Also handles parsing any required extra operands.
706fn parseValueEnum(self: *Assembler, kind: spec.OperandKind) !void {
707 const tok = self.currentToken();
708 if (self.eatToken(.placeholder)) {
709 const name = self.tokenText(tok)[1..];
710 const value = self.value_map.get(name) orelse {
711 return self.fail(tok.start, "invalid placeholder '${s}'", .{name});
571fn parseValueEnum(ass: *Assembler, kind: spec.OperandKind) !void {
572 const gpa = ass.cg.module.gpa;
573
574 const tok = ass.currentToken();
575 if (ass.eatToken(.placeholder)) {
576 const name = ass.tokenText(tok)[1..];
577 const value = ass.value_map.get(name) orelse {
578 return ass.fail(tok.start, "invalid placeholder '${s}'", .{name});
712579 };
713580 switch (value) {
714581 .constant => |literal32| {
715 try self.inst.operands.append(self.gpa, .{ .value = literal32 });
582 try ass.inst.operands.append(gpa, .{ .value = literal32 });
716583 },
717584 .string => |str| {
718585 const enumerant = for (kind.enumerants()) |enumerant| {
719586 if (std.mem.eql(u8, enumerant.name, str)) break enumerant;
720587 } else {
721 return self.fail(tok.start, "'{s}' is not a valid value for enumeration {s}", .{ str, @tagName(kind) });
588 return ass.fail(tok.start, "'{s}' is not a valid value for enumeration {s}", .{ str, @tagName(kind) });
722589 };
723 try self.inst.operands.append(self.gpa, .{ .value = enumerant.value });
590 try ass.inst.operands.append(gpa, .{ .value = enumerant.value });
724591 },
725 else => return self.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name}),
592 else => return ass.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name}),
726593 }
727594 return;
728595 }
729596
730 try self.expectToken(.value);
597 try ass.expectToken(.value);
731598
732 const text = self.tokenText(tok);
599 const text = ass.tokenText(tok);
733600 const int_value = std.fmt.parseInt(u32, text, 0) catch null;
734601 const enumerant = for (kind.enumerants()) |enumerant| {
735602 if (int_value) |v| {
......@@ -738,182 +605,194 @@ fn parseValueEnum(self: *Assembler, kind: spec.OperandKind) !void {
738605 if (std.mem.eql(u8, enumerant.name, text)) break enumerant;
739606 }
740607 } else {
741 return self.fail(tok.start, "'{s}' is not a valid value for enumeration {s}", .{ text, @tagName(kind) });
608 return ass.fail(tok.start, "'{s}' is not a valid value for enumeration {s}", .{ text, @tagName(kind) });
742609 };
743610
744 try self.inst.operands.append(self.gpa, .{ .value = enumerant.value });
611 try ass.inst.operands.append(gpa, .{ .value = enumerant.value });
745612
746613 for (enumerant.parameters) |param_kind| {
747 if (self.isAtInstructionBoundary()) {
748 return self.fail(self.currentToken().start, "missing required parameter for enum variant '{s}'", .{enumerant.name});
614 if (ass.isAtInstructionBoundary()) {
615 return ass.fail(ass.currentToken().start, "missing required parameter for enum variant '{s}'", .{enumerant.name});
749616 }
750617
751 try self.parseOperand(param_kind);
618 try ass.parseOperand(param_kind);
752619 }
753620}
754621
755fn parseRefId(self: *Assembler) !void {
756 const tok = self.currentToken();
757 try self.expectToken(.result_id);
622fn parseRefId(ass: *Assembler) !void {
623 const gpa = ass.cg.module.gpa;
624
625 const tok = ass.currentToken();
626 try ass.expectToken(.result_id);
758627
759 const name = self.tokenText(tok)[1..];
760 const entry = try self.value_map.getOrPut(self.gpa, name);
628 const name = ass.tokenText(tok)[1..];
629 const entry = try ass.value_map.getOrPut(gpa, name);
761630 if (!entry.found_existing) {
762631 entry.value_ptr.* = .unresolved_forward_reference;
763632 }
764633
765634 const index: AsmValue.Ref = @intCast(entry.index);
766 try self.inst.operands.append(self.gpa, .{ .ref_id = index });
635 try ass.inst.operands.append(gpa, .{ .ref_id = index });
767636}
768637
769fn parseLiteralInteger(self: *Assembler) !void {
770 const tok = self.currentToken();
771 if (self.eatToken(.placeholder)) {
772 const name = self.tokenText(tok)[1..];
773 const value = self.value_map.get(name) orelse {
774 return self.fail(tok.start, "invalid placeholder '${s}'", .{name});
638fn parseLiteralInteger(ass: *Assembler) !void {
639 const gpa = ass.cg.module.gpa;
640
641 const tok = ass.currentToken();
642 if (ass.eatToken(.placeholder)) {
643 const name = ass.tokenText(tok)[1..];
644 const value = ass.value_map.get(name) orelse {
645 return ass.fail(tok.start, "invalid placeholder '${s}'", .{name});
775646 };
776647 switch (value) {
777648 .constant => |literal32| {
778 try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 });
649 try ass.inst.operands.append(gpa, .{ .literal32 = literal32 });
779650 },
780651 else => {
781 return self.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
652 return ass.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
782653 },
783654 }
784655 return;
785656 }
786657
787 try self.expectToken(.value);
658 try ass.expectToken(.value);
788659 // According to the SPIR-V machine readable grammar, a LiteralInteger
789660 // may consist of one or more words. From the SPIR-V docs it seems like there
790661 // only one instruction where multiple words are allowed, the literals that make up the
791662 // switch cases of OpSwitch. This case is handled separately, and so we just assume
792663 // everything is a 32-bit integer in this function.
793 const text = self.tokenText(tok);
664 const text = ass.tokenText(tok);
794665 const value = std.fmt.parseInt(u32, text, 0) catch {
795 return self.fail(tok.start, "'{s}' is not a valid 32-bit integer literal", .{text});
666 return ass.fail(tok.start, "'{s}' is not a valid 32-bit integer literal", .{text});
796667 };
797 try self.inst.operands.append(self.gpa, .{ .literal32 = value });
668 try ass.inst.operands.append(gpa, .{ .literal32 = value });
798669}
799670
800fn parseLiteralExtInstInteger(self: *Assembler) !void {
801 const tok = self.currentToken();
802 if (self.eatToken(.placeholder)) {
803 const name = self.tokenText(tok)[1..];
804 const value = self.value_map.get(name) orelse {
805 return self.fail(tok.start, "invalid placeholder '${s}'", .{name});
671fn parseLiteralExtInstInteger(ass: *Assembler) !void {
672 const gpa = ass.cg.module.gpa;
673
674 const tok = ass.currentToken();
675 if (ass.eatToken(.placeholder)) {
676 const name = ass.tokenText(tok)[1..];
677 const value = ass.value_map.get(name) orelse {
678 return ass.fail(tok.start, "invalid placeholder '${s}'", .{name});
806679 };
807680 switch (value) {
808681 .constant => |literal32| {
809 try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 });
682 try ass.inst.operands.append(gpa, .{ .literal32 = literal32 });
810683 },
811684 else => {
812 return self.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
685 return ass.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
813686 },
814687 }
815688 return;
816689 }
817690
818 try self.expectToken(.value);
819 const text = self.tokenText(tok);
691 try ass.expectToken(.value);
692 const text = ass.tokenText(tok);
820693 const value = std.fmt.parseInt(u32, text, 0) catch {
821 return self.fail(tok.start, "'{s}' is not a valid 32-bit integer literal", .{text});
694 return ass.fail(tok.start, "'{s}' is not a valid 32-bit integer literal", .{text});
822695 };
823 try self.inst.operands.append(self.gpa, .{ .literal32 = value });
696 try ass.inst.operands.append(gpa, .{ .literal32 = value });
824697}
825698
826fn parseString(self: *Assembler) !void {
827 const tok = self.currentToken();
828 try self.expectToken(.string);
699fn parseString(ass: *Assembler) !void {
700 const gpa = ass.cg.module.gpa;
701
702 const tok = ass.currentToken();
703 try ass.expectToken(.string);
829704 // Note, the string might not have a closing quote. In this case,
830705 // an error is already emitted but we are trying to continue processing
831706 // anyway, so in this function we have to deal with that situation.
832 const text = self.tokenText(tok);
707 const text = ass.tokenText(tok);
833708 assert(text.len > 0 and text[0] == '"');
834709 const literal = if (text.len != 1 and text[text.len - 1] == '"')
835710 text[1 .. text.len - 1]
836711 else
837712 text[1..];
838713
839 const string_offset: u32 = @intCast(self.inst.string_bytes.items.len);
840 try self.inst.string_bytes.ensureUnusedCapacity(self.gpa, literal.len + 1);
841 self.inst.string_bytes.appendSliceAssumeCapacity(literal);
842 self.inst.string_bytes.appendAssumeCapacity(0);
714 const string_offset: u32 = @intCast(ass.inst.string_bytes.items.len);
715 try ass.inst.string_bytes.ensureUnusedCapacity(gpa, literal.len + 1);
716 ass.inst.string_bytes.appendSliceAssumeCapacity(literal);
717 ass.inst.string_bytes.appendAssumeCapacity(0);
843718
844 try self.inst.operands.append(self.gpa, .{ .string = string_offset });
719 try ass.inst.operands.append(gpa, .{ .string = string_offset });
845720}
846721
847fn parseContextDependentNumber(self: *Assembler) !void {
722fn parseContextDependentNumber(ass: *Assembler) !void {
723 const module = ass.cg.module;
724
848725 // For context dependent numbers, the actual type to parse is determined by the instruction.
849726 // Currently, this operand appears in OpConstant and OpSpecConstant, where the too-be-parsed type
850727 // is determined by the result type. That means that in this instructions we have to resolve the
851728 // operand type early and look at the result to see how we need to proceed.
852 assert(self.inst.opcode == .OpConstant or self.inst.opcode == .OpSpecConstant);
729 assert(ass.inst.opcode == .OpConstant or ass.inst.opcode == .OpSpecConstant);
853730
854 const tok = self.currentToken();
855 const result = try self.resolveRef(self.inst.operands.items[0].ref_id);
731 const tok = ass.currentToken();
732 const result = try ass.resolveRef(ass.inst.operands.items[0].ref_id);
856733 const result_id = result.resultId();
857734 // We are going to cheat a little bit: The types we are interested in, int and float,
858 // are added to the module and cached via self.spv.intType and self.spv.floatType. Therefore,
735 // are added to the module and cached via module.intType and module.floatType. Therefore,
859736 // we can determine the width of these types by directly checking the cache.
860737 // This only works if the Assembler and codegen both use spv.intType and spv.floatType though.
861738 // We don't expect there to be many of these types, so just look it up every time.
862739 // TODO: Count be improved to be a little bit more efficent.
863740
864741 {
865 var it = self.spv.cache.int_types.iterator();
742 var it = module.cache.int_types.iterator();
866743 while (it.next()) |entry| {
867744 const id = entry.value_ptr.*;
868745 if (id != result_id) continue;
869746 const info = entry.key_ptr.*;
870 return try self.parseContextDependentInt(info.signedness, info.bits);
747 return try ass.parseContextDependentInt(info.signedness, info.bits);
871748 }
872749 }
873750
874751 {
875 var it = self.spv.cache.float_types.iterator();
752 var it = module.cache.float_types.iterator();
876753 while (it.next()) |entry| {
877754 const id = entry.value_ptr.*;
878755 if (id != result_id) continue;
879756 const info = entry.key_ptr.*;
880757 switch (info.bits) {
881 16 => try self.parseContextDependentFloat(16),
882 32 => try self.parseContextDependentFloat(32),
883 64 => try self.parseContextDependentFloat(64),
884 else => return self.fail(tok.start, "cannot parse {}-bit info literal", .{info.bits}),
758 16 => try ass.parseContextDependentFloat(16),
759 32 => try ass.parseContextDependentFloat(32),
760 64 => try ass.parseContextDependentFloat(64),
761 else => return ass.fail(tok.start, "cannot parse {}-bit info literal", .{info.bits}),
885762 }
886763 }
887764 }
888765
889 return self.fail(tok.start, "cannot parse literal constant", .{});
766 return ass.fail(tok.start, "cannot parse literal constant", .{});
890767}
891768
892fn parseContextDependentInt(self: *Assembler, signedness: std.builtin.Signedness, width: u32) !void {
893 const tok = self.currentToken();
894 if (self.eatToken(.placeholder)) {
895 const name = self.tokenText(tok)[1..];
896 const value = self.value_map.get(name) orelse {
897 return self.fail(tok.start, "invalid placeholder '${s}'", .{name});
769fn parseContextDependentInt(ass: *Assembler, signedness: std.builtin.Signedness, width: u32) !void {
770 const gpa = ass.cg.module.gpa;
771
772 const tok = ass.currentToken();
773 if (ass.eatToken(.placeholder)) {
774 const name = ass.tokenText(tok)[1..];
775 const value = ass.value_map.get(name) orelse {
776 return ass.fail(tok.start, "invalid placeholder '${s}'", .{name});
898777 };
899778 switch (value) {
900779 .constant => |literal32| {
901 try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 });
780 try ass.inst.operands.append(gpa, .{ .literal32 = literal32 });
902781 },
903782 else => {
904 return self.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
783 return ass.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
905784 },
906785 }
907786 return;
908787 }
909788
910 try self.expectToken(.value);
789 try ass.expectToken(.value);
911790
912791 if (width == 0 or width > 2 * @bitSizeOf(spec.Word)) {
913 return self.fail(tok.start, "cannot parse {}-bit integer literal", .{width});
792 return ass.fail(tok.start, "cannot parse {}-bit integer literal", .{width});
914793 }
915794
916 const text = self.tokenText(tok);
795 const text = ass.tokenText(tok);
917796 invalid: {
918797 // Just parse the integer as the next larger integer type, and check if it overflows afterwards.
919798 const int = std.fmt.parseInt(i128, text, 0) catch break :invalid;
......@@ -928,112 +807,166 @@ fn parseContextDependentInt(self: *Assembler, signedness: std.builtin.Signedness
928807
929808 // Note, we store the sign-extended version here.
930809 if (width <= @bitSizeOf(spec.Word)) {
931 try self.inst.operands.append(self.gpa, .{ .literal32 = @truncate(@as(u128, @bitCast(int))) });
810 try ass.inst.operands.append(gpa, .{ .literal32 = @truncate(@as(u128, @bitCast(int))) });
932811 } else {
933 try self.inst.operands.append(self.gpa, .{ .literal64 = @truncate(@as(u128, @bitCast(int))) });
812 try ass.inst.operands.append(gpa, .{ .literal64 = @truncate(@as(u128, @bitCast(int))) });
934813 }
935814 return;
936815 }
937816
938 return self.fail(tok.start, "'{s}' is not a valid {s} {}-bit int literal", .{ text, @tagName(signedness), width });
817 return ass.fail(tok.start, "'{s}' is not a valid {s} {}-bit int literal", .{ text, @tagName(signedness), width });
939818}
940819
941fn parseContextDependentFloat(self: *Assembler, comptime width: u16) !void {
820fn parseContextDependentFloat(ass: *Assembler, comptime width: u16) !void {
821 const gpa = ass.cg.module.gpa;
822
942823 const Float = std.meta.Float(width);
943824 const Int = std.meta.Int(.unsigned, width);
944825
945 const tok = self.currentToken();
946 try self.expectToken(.value);
826 const tok = ass.currentToken();
827 try ass.expectToken(.value);
947828
948 const text = self.tokenText(tok);
829 const text = ass.tokenText(tok);
949830
950831 const value = std.fmt.parseFloat(Float, text) catch {
951 return self.fail(tok.start, "'{s}' is not a valid {}-bit float literal", .{ text, width });
832 return ass.fail(tok.start, "'{s}' is not a valid {}-bit float literal", .{ text, width });
952833 };
953834
954835 const float_bits: Int = @bitCast(value);
955836 if (width <= @bitSizeOf(spec.Word)) {
956 try self.inst.operands.append(self.gpa, .{ .literal32 = float_bits });
837 try ass.inst.operands.append(gpa, .{ .literal32 = float_bits });
957838 } else {
958839 assert(width <= 2 * @bitSizeOf(spec.Word));
959 try self.inst.operands.append(self.gpa, .{ .literal64 = float_bits });
840 try ass.inst.operands.append(gpa, .{ .literal64 = float_bits });
960841 }
961842}
962843
963fn parsePhiSource(self: *Assembler) !void {
964 try self.parseRefId();
965 if (self.isAtInstructionBoundary()) {
966 return self.fail(self.currentToken().start, "missing phi block parent", .{});
844fn parsePhiSource(ass: *Assembler) !void {
845 try ass.parseRefId();
846 if (ass.isAtInstructionBoundary()) {
847 return ass.fail(ass.currentToken().start, "missing phi block parent", .{});
967848 }
968 try self.parseRefId();
849 try ass.parseRefId();
969850}
970851
971/// Returns whether the `current_token` cursor is currently pointing
972/// at the start of a new instruction.
973fn isAtInstructionBoundary(self: Assembler) bool {
974 return switch (self.currentToken().tag) {
852/// Returns whether the `current_token` cursor
853/// is currently pointing at the start of a new instruction.
854fn isAtInstructionBoundary(ass: Assembler) bool {
855 return switch (ass.currentToken().tag) {
975856 .opcode, .result_id_assign, .eof => true,
976857 else => false,
977858 };
978859}
979860
980fn expectToken(self: *Assembler, tag: Token.Tag) !void {
981 if (self.eatToken(tag))
861fn expectToken(ass: *Assembler, tag: Token.Tag) !void {
862 if (ass.eatToken(tag))
982863 return;
983864
984 return self.fail(self.currentToken().start, "unexpected {s}, expected {s}", .{
985 self.currentToken().tag.name(),
865 return ass.fail(ass.currentToken().start, "unexpected {s}, expected {s}", .{
866 ass.currentToken().tag.name(),
986867 tag.name(),
987868 });
988869}
989870
990fn eatToken(self: *Assembler, tag: Token.Tag) bool {
991 if (self.testToken(tag)) {
992 self.current_token += 1;
871fn eatToken(ass: *Assembler, tag: Token.Tag) bool {
872 if (ass.testToken(tag)) {
873 ass.current_token += 1;
993874 return true;
994875 }
995876 return false;
996877}
997878
998fn testToken(self: Assembler, tag: Token.Tag) bool {
999 return self.currentToken().tag == tag;
879fn testToken(ass: Assembler, tag: Token.Tag) bool {
880 return ass.currentToken().tag == tag;
1000881}
1001882
1002fn currentToken(self: Assembler) Token {
1003 return self.tokens.items[self.current_token];
883fn currentToken(ass: Assembler) Token {
884 return ass.tokens.items[ass.current_token];
1004885}
1005886
1006fn tokenText(self: Assembler, tok: Token) []const u8 {
1007 return self.src[tok.start..tok.end];
887fn tokenText(ass: Assembler, tok: Token) []const u8 {
888 return ass.src[tok.start..tok.end];
1008889}
1009890
1010/// Tokenize `self.src` and put the tokens in `self.tokens`.
1011/// Any errors encountered are appended to `self.errors`.
1012fn tokenize(self: *Assembler) !void {
1013 self.tokens.clearRetainingCapacity();
891/// Tokenize `ass.src` and put the tokens in `ass.tokens`.
892/// Any errors encountered are appended to `ass.errors`.
893fn tokenize(ass: *Assembler) !void {
894 const gpa = ass.cg.module.gpa;
895
896 ass.tokens.clearRetainingCapacity();
1014897
1015898 var offset: u32 = 0;
1016899 while (true) {
1017 const tok = try self.nextToken(offset);
900 const tok = try ass.nextToken(offset);
1018901 // Resolve result-id assignment now.
1019 // Note: If the previous token wasn't a result-id, just ignore it,
902 // NOTE: If the previous token wasn't a result-id, just ignore it,
1020903 // we will catch it while parsing.
1021 if (tok.tag == .equals and self.tokens.items[self.tokens.items.len - 1].tag == .result_id) {
1022 self.tokens.items[self.tokens.items.len - 1].tag = .result_id_assign;
904 if (tok.tag == .equals and ass.tokens.items[ass.tokens.items.len - 1].tag == .result_id) {
905 ass.tokens.items[ass.tokens.items.len - 1].tag = .result_id_assign;
1023906 }
1024 try self.tokens.append(self.gpa, tok);
907 try ass.tokens.append(gpa, tok);
1025908 if (tok.tag == .eof)
1026909 break;
1027910 offset = tok.end;
1028911 }
1029912}
1030913
914const Token = struct {
915 tag: Tag,
916 start: u32,
917 end: u32,
918
919 const Tag = enum {
920 /// Returned when there was no more input to match.
921 eof,
922 /// %identifier
923 result_id,
924 /// %identifier when appearing on the LHS of an equals sign.
925 /// While not technically a token, its relatively easy to resolve
926 /// this during lexical analysis and relieves a bunch of headaches
927 /// during parsing.
928 result_id_assign,
929 /// Mask, int, or float. These are grouped together as some
930 /// SPIR-V enumerants look a bit like integers as well (for example
931 /// "3D"), and so it is easier to just interpret them as the expected
932 /// type when resolving an instruction's operands.
933 value,
934 /// An enumerant that looks like an opcode, that is, OpXxxx.
935 /// Not necessarily a *valid* opcode.
936 opcode,
937 /// String literals.
938 /// Note, this token is also returned for unterminated
939 /// strings. In this case the closing " is not present.
940 string,
941 /// |.
942 pipe,
943 /// =.
944 equals,
945 /// $identifier. This is used (for now) for constant values, like integers.
946 /// These can be used in place of a normal `value`.
947 placeholder,
948
949 fn name(tag: Tag) []const u8 {
950 return switch (tag) {
951 .eof => "<end of input>",
952 .result_id => "<result-id>",
953 .result_id_assign => "<assigned result-id>",
954 .value => "<value>",
955 .opcode => "<opcode>",
956 .string => "<string literal>",
957 .pipe => "'|'",
958 .equals => "'='",
959 .placeholder => "<placeholder>",
960 };
961 }
962 };
963};
964
1031965/// Retrieve the next token from the input. This function will assert
1032966/// that the token is surrounded by whitespace if required, but will not
1033967/// interpret the token yet.
1034/// Note: This function doesn't handle .result_id_assign - this is handled in
1035/// tokenize().
1036fn nextToken(self: *Assembler, start_offset: u32) !Token {
968/// NOTE: This function doesn't handle .result_id_assign - this is handled in tokenize().
969fn nextToken(ass: *Assembler, start_offset: u32) !Token {
1037970 // We generally separate the input into the following types:
1038971 // - Whitespace. Generally ignored, but also used as delimiter for some
1039972 // tokens.
......@@ -1059,8 +992,8 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
1059992 var token_start = start_offset;
1060993 var offset = start_offset;
1061994 var tag = Token.Tag.eof;
1062 while (offset < self.src.len) : (offset += 1) {
1063 const c = self.src[offset];
995 while (offset < ass.src.len) : (offset += 1) {
996 const c = ass.src[offset];
1064997 switch (state) {
1065998 .start => switch (c) {
1066999 ' ', '\t', '\r', '\n' => token_start = offset + 1,
......@@ -1093,7 +1026,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
10931026 },
10941027 .value => switch (c) {
10951028 '"' => {
1096 try self.addError(offset, "unexpected string literal", .{});
1029 try ass.addError(offset, "unexpected string literal", .{});
10971030 // The user most likely just forgot a delimiter here - keep
10981031 // the tag as value.
10991032 break;
......@@ -1105,7 +1038,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
11051038 '_', 'a'...'z', 'A'...'Z', '0'...'9' => {},
11061039 ' ', '\t', '\r', '\n', '=', '|' => break,
11071040 else => {
1108 try self.addError(offset, "illegal character in result-id or placeholder", .{});
1041 try ass.addError(offset, "illegal character in result-id or placeholder", .{});
11091042 // Again, probably a forgotten delimiter here.
11101043 break;
11111044 },
......@@ -1118,7 +1051,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
11181051 .string_end => switch (c) {
11191052 ' ', '\t', '\r', '\n', '=', '|' => break,
11201053 else => {
1121 try self.addError(offset, "unexpected character after string literal", .{});
1054 try ass.addError(offset, "unexpected character after string literal", .{});
11221055 // The token is still unmistakibly a string.
11231056 break;
11241057 },
......@@ -1128,7 +1061,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
11281061 }
11291062 }
11301063
1131 var tok = Token{
1064 var tok: Token = .{
11321065 .tag = tag,
11331066 .start = token_start,
11341067 .end = offset,
......@@ -1136,13 +1069,13 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
11361069
11371070 switch (state) {
11381071 .string, .escape => {
1139 try self.addError(token_start, "unterminated string", .{});
1072 try ass.addError(token_start, "unterminated string", .{});
11401073 },
11411074 .result_id => if (offset - token_start == 1) {
1142 try self.addError(token_start, "result-id must have at least one name character", .{});
1075 try ass.addError(token_start, "result-id must have at least one name character", .{});
11431076 },
11441077 .value => {
1145 const text = self.tokenText(tok);
1078 const text = ass.tokenText(tok);
11461079 const prefix = "Op";
11471080 const looks_like_opcode = text.len > prefix.len and
11481081 std.mem.startsWith(u8, text, prefix) and
src/codegen/spirv/CodeGen.zig created+6188
......@@ -0,0 +1,6188 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const Target = std.Target;
4const Signedness = std.builtin.Signedness;
5const assert = std.debug.assert;
6const log = std.log.scoped(.codegen);
7
8const Zcu = @import("../../Zcu.zig");
9const Type = @import("../../Type.zig");
10const Value = @import("../../Value.zig");
11const Air = @import("../../Air.zig");
12const InternPool = @import("../../InternPool.zig");
13const Section = @import("Section.zig");
14const Assembler = @import("Assembler.zig");
15
16const spec = @import("spec.zig");
17const Opcode = spec.Opcode;
18const Word = spec.Word;
19const Id = spec.Id;
20const IdRange = spec.IdRange;
21const StorageClass = spec.StorageClass;
22
23const Module = @import("Module.zig");
24const Decl = Module.Decl;
25const Repr = Module.Repr;
26const InternMap = Module.InternMap;
27const PtrTypeMap = Module.PtrTypeMap;
28
29const CodeGen = @This();
30
31pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
32 return comptime &.initMany(&.{
33 .expand_intcast_safe,
34 .expand_int_from_float_safe,
35 .expand_int_from_float_optimized_safe,
36 .expand_add_safe,
37 .expand_sub_safe,
38 .expand_mul_safe,
39 });
40}
41
42pub const zig_call_abi_ver = 3;
43
44const ControlFlow = union(enum) {
45 const Structured = struct {
46 /// This type indicates the way that a block is terminated. The
47 /// state of a particular block is used to track how a jump from
48 /// inside the block must reach the outside.
49 const Block = union(enum) {
50 const Incoming = struct {
51 src_label: Id,
52 /// Instruction that returns an u32 value of the
53 /// `Air.Inst.Index` that control flow should jump to.
54 next_block: Id,
55 };
56
57 const SelectionMerge = struct {
58 /// Incoming block from the `then` label.
59 /// Note that hte incoming block from the `else` label is
60 /// either given by the next element in the stack.
61 incoming: Incoming,
62 /// The label id of the cond_br's merge block.
63 /// For the top-most element in the stack, this
64 /// value is undefined.
65 merge_block: Id,
66 };
67
68 /// For a `selection` type block, we cannot use early exits, and we
69 /// must generate a 'merge ladder' of OpSelection instructions. To that end,
70 /// we keep a stack of the merges that still must be closed at the end of
71 /// a block.
72 ///
73 /// This entire structure basically just resembles a tree like
74 /// a x
75 /// \ /
76 /// b o merge
77 /// \ /
78 /// c o merge
79 /// \ /
80 /// o merge
81 /// /
82 /// o jump to next block
83 selection: struct {
84 /// In order to know which merges we still need to do, we need to keep
85 /// a stack of those.
86 merge_stack: std.ArrayListUnmanaged(SelectionMerge) = .empty,
87 },
88 /// For a `loop` type block, we can early-exit the block by
89 /// jumping to the loop exit node, and we don't need to generate
90 /// an entire stack of merges.
91 loop: struct {
92 /// The next block to jump to can be determined from any number
93 /// of conditions that jump to the loop exit.
94 merges: std.ArrayListUnmanaged(Incoming) = .empty,
95 /// The label id of the loop's merge block.
96 merge_block: Id,
97 },
98
99 fn deinit(block: *Structured.Block, gpa: Allocator) void {
100 switch (block.*) {
101 .selection => |*merge| merge.merge_stack.deinit(gpa),
102 .loop => |*merge| merge.merges.deinit(gpa),
103 }
104 block.* = undefined;
105 }
106 };
107 /// This determines how exits from the current block must be handled.
108 block_stack: std.ArrayListUnmanaged(*Structured.Block) = .empty,
109 block_results: std.AutoHashMapUnmanaged(Air.Inst.Index, Id) = .empty,
110 };
111
112 const Unstructured = struct {
113 const Incoming = struct {
114 src_label: Id,
115 break_value_id: Id,
116 };
117
118 const Block = struct {
119 label: ?Id = null,
120 incoming_blocks: std.ArrayListUnmanaged(Incoming) = .empty,
121 };
122
123 /// We need to keep track of result ids for block labels, as well as the 'incoming'
124 /// blocks for a block.
125 blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, *Block) = .empty,
126 };
127
128 structured: Structured,
129 unstructured: Unstructured,
130
131 pub fn deinit(cg: *ControlFlow, gpa: Allocator) void {
132 switch (cg.*) {
133 .structured => |*cf| {
134 cf.block_stack.deinit(gpa);
135 cf.block_results.deinit(gpa);
136 },
137 .unstructured => |*cf| {
138 cf.blocks.deinit(gpa);
139 },
140 }
141 cg.* = undefined;
142 }
143};
144
145pt: Zcu.PerThread,
146air: Air,
147liveness: Air.Liveness,
148owner_nav: InternPool.Nav.Index,
149module: *Module,
150control_flow: ControlFlow,
151base_line: u32,
152block_label: Id = .none,
153next_arg_index: u32 = 0,
154args: std.ArrayListUnmanaged(Id) = .empty,
155inst_results: std.AutoHashMapUnmanaged(Air.Inst.Index, Id) = .empty,
156id_scratch: std.ArrayListUnmanaged(Id) = .empty,
157prologue: Section = .{},
158body: Section = .{},
159error_msg: ?*Zcu.ErrorMsg = null,
160
161pub fn deinit(cg: *CodeGen) void {
162 const gpa = cg.module.gpa;
163 cg.control_flow.deinit(gpa);
164 cg.args.deinit(gpa);
165 cg.inst_results.deinit(gpa);
166 cg.id_scratch.deinit(gpa);
167 cg.prologue.deinit(gpa);
168 cg.body.deinit(gpa);
169}
170
171const Error = error{ CodegenFail, OutOfMemory };
172
173pub fn genNav(cg: *CodeGen, do_codegen: bool) Error!void {
174 const gpa = cg.module.gpa;
175 const zcu = cg.module.zcu;
176 const ip = &zcu.intern_pool;
177 const target = zcu.getTarget();
178
179 const nav = ip.getNav(cg.owner_nav);
180 const val = zcu.navValue(cg.owner_nav);
181 const ty = val.typeOf(zcu);
182
183 if (!do_codegen and !ty.hasRuntimeBits(zcu)) return;
184
185 const spv_decl_index = try cg.module.resolveNav(ip, cg.owner_nav);
186 const decl = cg.module.declPtr(spv_decl_index);
187 const result_id = decl.result_id;
188 decl.begin_dep = cg.module.decl_deps.items.len;
189
190 switch (decl.kind) {
191 .func => {
192 const fn_info = zcu.typeToFunc(ty).?;
193 const return_ty_id = try cg.resolveFnReturnType(.fromInterned(fn_info.return_type));
194 const is_test = zcu.test_functions.contains(cg.owner_nav);
195
196 const func_result_id = if (is_test) cg.module.allocId() else result_id;
197 const prototype_ty_id = try cg.resolveType(ty, .direct);
198 try cg.prologue.emit(gpa, .OpFunction, .{
199 .id_result_type = return_ty_id,
200 .id_result = func_result_id,
201 .function_type = prototype_ty_id,
202 // Note: the backend will never be asked to generate an inline function
203 // (this is handled in sema), so we don't need to set function_control here.
204 .function_control = .{},
205 });
206
207 comptime assert(zig_call_abi_ver == 3);
208 try cg.args.ensureUnusedCapacity(gpa, fn_info.param_types.len);
209 for (fn_info.param_types.get(ip)) |param_ty_index| {
210 const param_ty: Type = .fromInterned(param_ty_index);
211 if (!param_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;
212
213 const param_type_id = try cg.resolveType(param_ty, .direct);
214 const arg_result_id = cg.module.allocId();
215 try cg.prologue.emit(gpa, .OpFunctionParameter, .{
216 .id_result_type = param_type_id,
217 .id_result = arg_result_id,
218 });
219 cg.args.appendAssumeCapacity(arg_result_id);
220 }
221
222 // TODO: This could probably be done in a better way...
223 const root_block_id = cg.module.allocId();
224
225 // The root block of a function declaration should appear before OpVariable instructions,
226 // so it is generated into the function's prologue.
227 try cg.prologue.emit(gpa, .OpLabel, .{
228 .id_result = root_block_id,
229 });
230 cg.block_label = root_block_id;
231
232 const main_body = cg.air.getMainBody();
233 switch (cg.control_flow) {
234 .structured => {
235 _ = try cg.genStructuredBody(.selection, main_body);
236 // We always expect paths to here to end, but we still need the block
237 // to act as a dummy merge block.
238 try cg.body.emit(gpa, .OpUnreachable, {});
239 },
240 .unstructured => {
241 try cg.genBody(main_body);
242 },
243 }
244 try cg.body.emit(gpa, .OpFunctionEnd, {});
245 // Append the actual code into the functions section.
246 try cg.module.sections.functions.append(gpa, cg.prologue);
247 try cg.module.sections.functions.append(gpa, cg.body);
248
249 // Temporarily generate a test kernel declaration if this is a test function.
250 if (is_test) {
251 try cg.generateTestEntryPoint(nav.fqn.toSlice(ip), spv_decl_index, func_result_id);
252 }
253
254 try cg.module.debugName(func_result_id, nav.fqn.toSlice(ip));
255 },
256 .global => {
257 const maybe_init_val: ?Value = switch (ip.indexToKey(val.toIntern())) {
258 .func => unreachable,
259 .variable => |variable| .fromInterned(variable.init),
260 .@"extern" => null,
261 else => val,
262 };
263 assert(maybe_init_val == null); // TODO
264
265 const storage_class = cg.module.storageClass(nav.getAddrspace());
266 assert(storage_class != .generic); // These should be instance globals
267
268 const ty_id = try cg.resolveType(ty, .indirect);
269 const ptr_ty_id = try cg.module.ptrType(ty_id, storage_class);
270
271 try cg.module.sections.globals.emit(gpa, .OpVariable, .{
272 .id_result_type = ptr_ty_id,
273 .id_result = result_id,
274 .storage_class = storage_class,
275 });
276
277 switch (target.os.tag) {
278 .vulkan, .opengl => {
279 if (ty.zigTypeTag(zcu) == .@"struct") {
280 switch (storage_class) {
281 .uniform, .push_constant => try cg.module.decorate(ty_id, .block),
282 else => {},
283 }
284 }
285
286 switch (ip.indexToKey(ty.toIntern())) {
287 .func_type, .opaque_type => {},
288 else => {
289 try cg.module.decorate(ptr_ty_id, .{
290 .array_stride = .{ .array_stride = @intCast(ty.abiSize(zcu)) },
291 });
292 },
293 }
294 },
295 else => {},
296 }
297
298 if (std.meta.stringToEnum(spec.BuiltIn, nav.fqn.toSlice(ip))) |builtin| {
299 try cg.module.decorate(result_id, .{ .built_in = .{ .built_in = builtin } });
300 }
301
302 try cg.module.debugName(result_id, nav.fqn.toSlice(ip));
303 },
304 .invocation_global => {
305 const maybe_init_val: ?Value = switch (ip.indexToKey(val.toIntern())) {
306 .func => unreachable,
307 .variable => |variable| .fromInterned(variable.init),
308 .@"extern" => null,
309 else => val,
310 };
311
312 const ty_id = try cg.resolveType(ty, .indirect);
313 const ptr_ty_id = try cg.module.ptrType(ty_id, .function);
314
315 if (maybe_init_val) |init_val| {
316 // TODO: Combine with resolveAnonDecl?
317 const void_ty_id = try cg.resolveType(.void, .direct);
318 const initializer_proto_ty_id = try cg.module.functionType(void_ty_id, &.{});
319
320 const initializer_id = cg.module.allocId();
321 try cg.prologue.emit(gpa, .OpFunction, .{
322 .id_result_type = try cg.resolveType(.void, .direct),
323 .id_result = initializer_id,
324 .function_control = .{},
325 .function_type = initializer_proto_ty_id,
326 });
327
328 const root_block_id = cg.module.allocId();
329 try cg.prologue.emit(gpa, .OpLabel, .{
330 .id_result = root_block_id,
331 });
332 cg.block_label = root_block_id;
333
334 const val_id = try cg.constant(ty, init_val, .indirect);
335 try cg.body.emit(gpa, .OpStore, .{
336 .pointer = result_id,
337 .object = val_id,
338 });
339
340 try cg.body.emit(gpa, .OpReturn, {});
341 try cg.body.emit(gpa, .OpFunctionEnd, {});
342 try cg.module.sections.functions.append(gpa, cg.prologue);
343 try cg.module.sections.functions.append(gpa, cg.body);
344
345 try cg.module.debugNameFmt(initializer_id, "initializer of {f}", .{nav.fqn.fmt(ip)});
346
347 try cg.module.sections.globals.emit(gpa, .OpExtInst, .{
348 .id_result_type = ptr_ty_id,
349 .id_result = result_id,
350 .set = try cg.module.importInstructionSet(.zig),
351 .instruction = .{ .inst = 0 }, // TODO: Put this definition somewhere...
352 .id_ref_4 = &.{initializer_id},
353 });
354 } else {
355 try cg.module.sections.globals.emit(gpa, .OpExtInst, .{
356 .id_result_type = ptr_ty_id,
357 .id_result = result_id,
358 .set = try cg.module.importInstructionSet(.zig),
359 .instruction = .{ .inst = 0 }, // TODO: Put this definition somewhere...
360 .id_ref_4 = &.{},
361 });
362 }
363 },
364 }
365
366 cg.module.declPtr(spv_decl_index).end_dep = cg.module.decl_deps.items.len;
367}
368
369pub fn fail(cg: *CodeGen, comptime format: []const u8, args: anytype) Error {
370 @branchHint(.cold);
371 const zcu = cg.module.zcu;
372 const src_loc = zcu.navSrcLoc(cg.owner_nav);
373 assert(cg.error_msg == null);
374 cg.error_msg = try Zcu.ErrorMsg.create(zcu.gpa, src_loc, format, args);
375 return error.CodegenFail;
376}
377
378pub fn todo(cg: *CodeGen, comptime format: []const u8, args: anytype) Error {
379 return cg.fail("TODO (SPIR-V): " ++ format, args);
380}
381
382/// This imports the "default" extended instruction set for the target
383/// For OpenCL, OpenCL.std.100. For Vulkan and OpenGL, GLSL.std.450.
384fn importExtendedSet(cg: *CodeGen) !Id {
385 const target = cg.module.zcu.getTarget();
386 return switch (target.os.tag) {
387 .opencl, .amdhsa => try cg.module.importInstructionSet(.@"OpenCL.std"),
388 .vulkan, .opengl => try cg.module.importInstructionSet(.@"GLSL.std.450"),
389 else => unreachable,
390 };
391}
392
393/// Fetch the result-id for a previously generated instruction or constant.
394fn resolve(cg: *CodeGen, inst: Air.Inst.Ref) !Id {
395 const pt = cg.pt;
396 const zcu = cg.module.zcu;
397 const ip = &zcu.intern_pool;
398 if (try cg.air.value(inst, pt)) |val| {
399 const ty = cg.typeOf(inst);
400 if (ty.zigTypeTag(zcu) == .@"fn") {
401 const fn_nav = switch (zcu.intern_pool.indexToKey(val.ip_index)) {
402 .@"extern" => |@"extern"| @"extern".owner_nav,
403 .func => |func| func.owner_nav,
404 else => unreachable,
405 };
406 const spv_decl_index = try cg.module.resolveNav(ip, fn_nav);
407 try cg.module.decl_deps.append(cg.module.gpa, spv_decl_index);
408 return cg.module.declPtr(spv_decl_index).result_id;
409 }
410
411 return try cg.constant(ty, val, .direct);
412 }
413 const index = inst.toIndex().?;
414 return cg.inst_results.get(index).?; // Assertion means instruction does not dominate usage.
415}
416
417fn resolveUav(cg: *CodeGen, val: InternPool.Index) !Id {
418 const gpa = cg.module.gpa;
419
420 // TODO: This cannot be a function at this point, but it should probably be handled anyway.
421
422 const zcu = cg.module.zcu;
423 const ty: Type = .fromInterned(zcu.intern_pool.typeOf(val));
424 const ty_id = try cg.resolveType(ty, .indirect);
425
426 const spv_decl_index = blk: {
427 const entry = try cg.module.uav_link.getOrPut(gpa, .{ val, .function });
428 if (entry.found_existing) {
429 try cg.addFunctionDep(entry.value_ptr.*, .function);
430 return cg.module.declPtr(entry.value_ptr.*).result_id;
431 }
432
433 const spv_decl_index = try cg.module.allocDecl(.invocation_global);
434 try cg.addFunctionDep(spv_decl_index, .function);
435 entry.value_ptr.* = spv_decl_index;
436 break :blk spv_decl_index;
437 };
438
439 // TODO: At some point we will be able to generate this all constant here, but then all of
440 // constant() will need to be implemented such that it doesn't generate any at-runtime code.
441 // NOTE: Because this is a global, we really only want to initialize it once. Therefore the
442 // constant lowering of this value will need to be deferred to an initializer similar to
443 // other globals.
444
445 const result_id = cg.module.declPtr(spv_decl_index).result_id;
446
447 {
448 // Save the current state so that we can temporarily generate into a different function.
449 // TODO: This should probably be made a little more robust.
450 const func_prologue = cg.prologue;
451 const func_body = cg.body;
452 const block_label = cg.block_label;
453 defer {
454 cg.prologue = func_prologue;
455 cg.body = func_body;
456 cg.block_label = block_label;
457 }
458
459 cg.prologue = .{};
460 cg.body = .{};
461 defer {
462 cg.prologue.deinit(gpa);
463 cg.body.deinit(gpa);
464 }
465
466 const void_ty_id = try cg.resolveType(.void, .direct);
467 const initializer_proto_ty_id = try cg.module.functionType(void_ty_id, &.{});
468
469 const initializer_id = cg.module.allocId();
470 try cg.prologue.emit(gpa, .OpFunction, .{
471 .id_result_type = try cg.resolveType(.void, .direct),
472 .id_result = initializer_id,
473 .function_control = .{},
474 .function_type = initializer_proto_ty_id,
475 });
476 const root_block_id = cg.module.allocId();
477 try cg.prologue.emit(gpa, .OpLabel, .{
478 .id_result = root_block_id,
479 });
480 cg.block_label = root_block_id;
481
482 const val_id = try cg.constant(ty, .fromInterned(val), .indirect);
483 try cg.body.emit(gpa, .OpStore, .{
484 .pointer = result_id,
485 .object = val_id,
486 });
487
488 try cg.body.emit(gpa, .OpReturn, {});
489 try cg.body.emit(gpa, .OpFunctionEnd, {});
490
491 try cg.module.sections.functions.append(gpa, cg.prologue);
492 try cg.module.sections.functions.append(gpa, cg.body);
493
494 try cg.module.debugNameFmt(initializer_id, "initializer of __anon_{d}", .{@intFromEnum(val)});
495
496 const fn_decl_ptr_ty_id = try cg.module.ptrType(ty_id, .function);
497 try cg.module.sections.globals.emit(gpa, .OpExtInst, .{
498 .id_result_type = fn_decl_ptr_ty_id,
499 .id_result = result_id,
500 .set = try cg.module.importInstructionSet(.zig),
501 .instruction = .{ .inst = 0 }, // TODO: Put this definition somewhere...
502 .id_ref_4 = &.{initializer_id},
503 });
504 }
505
506 return result_id;
507}
508
509fn addFunctionDep(cg: *CodeGen, decl_index: Module.Decl.Index, storage_class: StorageClass) !void {
510 const gpa = cg.module.gpa;
511 const target = cg.module.zcu.getTarget();
512 if (target.cpu.has(.spirv, .v1_4)) {
513 try cg.module.decl_deps.append(gpa, decl_index);
514 } else {
515 // Before version 1.4, the interface’s storage classes are limited to the Input and Output
516 if (storage_class == .input or storage_class == .output) {
517 try cg.module.decl_deps.append(gpa, decl_index);
518 }
519 }
520}
521
522/// Start a new SPIR-V block, Emits the label of the new block, and stores which
523/// block we are currently generating.
524/// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to
525/// keep track of the previous block.
526fn beginSpvBlock(cg: *CodeGen, label: Id) !void {
527 try cg.body.emit(cg.module.gpa, .OpLabel, .{ .id_result = label });
528 cg.block_label = label;
529}
530
531/// Return the amount of bits in the largest supported integer type. This is either 32 (always supported), or 64 (if
532/// the Int64 capability is enabled).
533/// Note: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits).
534/// In theory that could also be used, but since the spec says that it only guarantees support up to 32-bit ints there
535/// is no way of knowing whether those are actually supported.
536/// TODO: Maybe this should be cached?
537fn largestSupportedIntBits(cg: *CodeGen) u16 {
538 const target = cg.module.zcu.getTarget();
539 if (target.cpu.has(.spirv, .int64) or target.cpu.arch == .spirv64) {
540 return 64;
541 }
542 return 32;
543}
544
545const ArithmeticTypeInfo = struct {
546 const Class = enum {
547 bool,
548 /// A regular, **native**, integer.
549 /// This is only returned when the backend supports this int as a native type (when
550 /// the relevant capability is enabled).
551 integer,
552 /// A regular float. These are all required to be natively supported. Floating points
553 /// for which the relevant capability is not enabled are not emulated.
554 float,
555 /// An integer of a 'strange' size (which' bit size is not the same as its backing
556 /// type. **Note**: this may **also** include power-of-2 integers for which the
557 /// relevant capability is not enabled), but still within the limits of the largest
558 /// natively supported integer type.
559 strange_integer,
560 /// An integer with more bits than the largest natively supported integer type.
561 composite_integer,
562 };
563
564 /// A classification of the inner type.
565 /// These scenarios will all have to be handled slightly different.
566 class: Class,
567 /// The number of bits in the inner type.
568 /// This is the actual number of bits of the type, not the size of the backing integer.
569 bits: u16,
570 /// The number of bits required to store the type.
571 /// For `integer` and `float`, this is equal to `bits`.
572 /// For `strange_integer` and `bool` this is the size of the backing integer.
573 /// For `composite_integer` this is the elements count.
574 backing_bits: u16,
575 /// Null if this type is a scalar, or the length of the vector otherwise.
576 vector_len: ?u32,
577 /// Whether the inner type is signed. Only relevant for integers.
578 signedness: std.builtin.Signedness,
579};
580
581fn arithmeticTypeInfo(cg: *CodeGen, ty: Type) ArithmeticTypeInfo {
582 const zcu = cg.module.zcu;
583 const target = cg.module.zcu.getTarget();
584 var scalar_ty = ty.scalarType(zcu);
585 if (scalar_ty.zigTypeTag(zcu) == .@"enum") {
586 scalar_ty = scalar_ty.intTagType(zcu);
587 }
588 const vector_len = if (ty.isVector(zcu)) ty.vectorLen(zcu) else null;
589 return switch (scalar_ty.zigTypeTag(zcu)) {
590 .bool => .{
591 .bits = 1, // Doesn't matter for this class.
592 .backing_bits = cg.module.backingIntBits(1).@"0",
593 .vector_len = vector_len,
594 .signedness = .unsigned, // Technically, but doesn't matter for this class.
595 .class = .bool,
596 },
597 .float => .{
598 .bits = scalar_ty.floatBits(target),
599 .backing_bits = scalar_ty.floatBits(target), // TODO: F80?
600 .vector_len = vector_len,
601 .signedness = .signed, // Technically, but doesn't matter for this class.
602 .class = .float,
603 },
604 .int => blk: {
605 const int_info = scalar_ty.intInfo(zcu);
606 // TODO: Maybe it's useful to also return this value.
607 const backing_bits, const big_int = cg.module.backingIntBits(int_info.bits);
608 break :blk .{
609 .bits = int_info.bits,
610 .backing_bits = backing_bits,
611 .vector_len = vector_len,
612 .signedness = int_info.signedness,
613 .class = class: {
614 if (big_int) break :class .composite_integer;
615 break :class if (backing_bits == int_info.bits) .integer else .strange_integer;
616 },
617 };
618 },
619 .@"enum" => unreachable,
620 .vector => unreachable,
621 else => unreachable, // Unhandled arithmetic type
622 };
623}
624
625/// Checks whether the type can be directly translated to SPIR-V vectors
626fn isSpvVector(cg: *CodeGen, ty: Type) bool {
627 const zcu = cg.module.zcu;
628 const target = cg.module.zcu.getTarget();
629 if (ty.zigTypeTag(zcu) != .vector) return false;
630
631 // TODO: This check must be expanded for types that can be represented
632 // as integers (enums / packed structs?) and types that are represented
633 // by multiple SPIR-V values.
634 const scalar_ty = ty.scalarType(zcu);
635 switch (scalar_ty.zigTypeTag(zcu)) {
636 .bool,
637 .int,
638 .float,
639 => {},
640 else => return false,
641 }
642
643 const elem_ty = ty.childType(zcu);
644 const len = ty.vectorLen(zcu);
645
646 if (elem_ty.isNumeric(zcu) or elem_ty.toIntern() == .bool_type) {
647 if (len > 1 and len <= 4) return true;
648 if (target.cpu.has(.spirv, .vector16)) return (len == 8 or len == 16);
649 }
650
651 return false;
652}
653
654/// Emits a bool constant in a particular representation.
655fn constBool(cg: *CodeGen, value: bool, repr: Repr) !Id {
656 return switch (repr) {
657 .indirect => cg.constInt(.u1, @intFromBool(value)),
658 .direct => cg.module.constBool(value),
659 };
660}
661
662/// Emits an integer constant.
663/// This function, unlike Module.constInt, takes care to bitcast
664/// the value to an unsigned int first for Kernels.
665fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id {
666 const zcu = cg.module.zcu;
667 const target = cg.module.zcu.getTarget();
668 const scalar_ty = ty.scalarType(zcu);
669 const int_info = scalar_ty.intInfo(zcu);
670 // Use backing bits so that negatives are sign extended
671 const backing_bits, const big_int = cg.module.backingIntBits(int_info.bits);
672 assert(backing_bits != 0); // u0 is comptime
673
674 const result_ty_id = try cg.resolveType(scalar_ty, .indirect);
675 const signedness: Signedness = switch (@typeInfo(@TypeOf(value))) {
676 .int => |int| int.signedness,
677 .comptime_int => if (value < 0) .signed else .unsigned,
678 else => unreachable,
679 };
680 if (@sizeOf(@TypeOf(value)) >= 4 and big_int) {
681 const value64: u64 = switch (signedness) {
682 .signed => @bitCast(@as(i64, @intCast(value))),
683 .unsigned => @as(u64, @intCast(value)),
684 };
685 assert(backing_bits == 64);
686 return cg.constructComposite(result_ty_id, &.{
687 try cg.constInt(.u32, @as(u32, @truncate(value64))),
688 try cg.constInt(.u32, @as(u32, @truncate(value64 << 32))),
689 });
690 }
691
692 const final_value: spec.LiteralContextDependentNumber = switch (target.os.tag) {
693 .opencl, .amdhsa => blk: {
694 const value64: u64 = switch (signedness) {
695 .signed => @bitCast(@as(i64, @intCast(value))),
696 .unsigned => @as(u64, @intCast(value)),
697 };
698
699 // Manually truncate the value to the right amount of bits.
700 const truncated_value = if (backing_bits == 64)
701 value64
702 else
703 value64 & (@as(u64, 1) << @intCast(backing_bits)) - 1;
704
705 break :blk switch (backing_bits) {
706 1...32 => .{ .uint32 = @truncate(truncated_value) },
707 33...64 => .{ .uint64 = truncated_value },
708 else => unreachable,
709 };
710 },
711 else => switch (backing_bits) {
712 1...32 => if (signedness == .signed) .{ .int32 = @intCast(value) } else .{ .uint32 = @intCast(value) },
713 33...64 => if (signedness == .signed) .{ .int64 = value } else .{ .uint64 = value },
714 else => unreachable,
715 },
716 };
717
718 const result_id = try cg.module.constant(result_ty_id, final_value);
719
720 if (!ty.isVector(zcu)) return result_id;
721 return cg.constructCompositeSplat(ty, result_id);
722}
723
724pub fn constructComposite(cg: *CodeGen, result_ty_id: Id, constituents: []const Id) !Id {
725 const gpa = cg.module.gpa;
726 const result_id = cg.module.allocId();
727 try cg.body.emit(gpa, .OpCompositeConstruct, .{
728 .id_result_type = result_ty_id,
729 .id_result = result_id,
730 .constituents = constituents,
731 });
732 return result_id;
733}
734
735/// Construct a composite at runtime with all lanes set to the same value.
736/// ty must be an aggregate type.
737fn constructCompositeSplat(cg: *CodeGen, ty: Type, constituent: Id) !Id {
738 const gpa = cg.module.gpa;
739 const zcu = cg.module.zcu;
740 const n: usize = @intCast(ty.arrayLen(zcu));
741
742 const scratch_top = cg.id_scratch.items.len;
743 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
744
745 const constituents = try cg.id_scratch.addManyAsSlice(gpa, n);
746 @memset(constituents, constituent);
747
748 const result_ty_id = try cg.resolveType(ty, .direct);
749 return cg.constructComposite(result_ty_id, constituents);
750}
751
752/// This function generates a load for a constant in direct (ie, non-memory) representation.
753/// When the constant is simple, it can be generated directly using OpConstant instructions.
754/// When the constant is more complicated however, it needs to be constructed using multiple values. This
755/// is done by emitting a sequence of instructions that initialize the value.
756//
757/// This function should only be called during function code generation.
758fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id {
759 const gpa = cg.module.gpa;
760
761 // Note: Using intern_map can only be used with constants that DO NOT generate any runtime code!!
762 // Ideally that should be all constants in the future, or it should be cleaned up somehow. For
763 // now, only use the intern_map on case-by-case basis by breaking to :cache.
764 if (cg.module.intern_map.get(.{ val.toIntern(), repr })) |id| {
765 return id;
766 }
767
768 const pt = cg.pt;
769 const zcu = cg.module.zcu;
770 const target = cg.module.zcu.getTarget();
771 const result_ty_id = try cg.resolveType(ty, repr);
772 const ip = &zcu.intern_pool;
773
774 log.debug("lowering constant: ty = {f}, val = {f}, key = {s}", .{ ty.fmt(pt), val.fmtValue(pt), @tagName(ip.indexToKey(val.toIntern())) });
775 if (val.isUndefDeep(zcu)) {
776 return cg.module.constUndef(result_ty_id);
777 }
778
779 const cacheable_id = cache: {
780 switch (ip.indexToKey(val.toIntern())) {
781 .int_type,
782 .ptr_type,
783 .array_type,
784 .vector_type,
785 .opt_type,
786 .anyframe_type,
787 .error_union_type,
788 .simple_type,
789 .struct_type,
790 .tuple_type,
791 .union_type,
792 .opaque_type,
793 .enum_type,
794 .func_type,
795 .error_set_type,
796 .inferred_error_set_type,
797 => unreachable, // types, not values
798
799 .undef => unreachable, // handled above
800
801 .variable,
802 .@"extern",
803 .func,
804 .enum_literal,
805 .empty_enum_value,
806 => unreachable, // non-runtime values
807
808 .simple_value => |simple_value| switch (simple_value) {
809 .undefined,
810 .void,
811 .null,
812 .empty_tuple,
813 .@"unreachable",
814 => unreachable, // non-runtime values
815
816 .false, .true => break :cache try cg.constBool(val.toBool(), repr),
817 },
818 .int => {
819 if (ty.isSignedInt(zcu)) {
820 break :cache try cg.constInt(ty, val.toSignedInt(zcu));
821 } else {
822 break :cache try cg.constInt(ty, val.toUnsignedInt(zcu));
823 }
824 },
825 .float => {
826 const lit: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) {
827 16 => .{ .uint32 = @as(u16, @bitCast(val.toFloat(f16, zcu))) },
828 32 => .{ .float32 = val.toFloat(f32, zcu) },
829 64 => .{ .float64 = val.toFloat(f64, zcu) },
830 80, 128 => unreachable, // TODO
831 else => unreachable,
832 };
833 break :cache try cg.module.constant(result_ty_id, lit);
834 },
835 .err => |err| {
836 const value = try pt.getErrorValue(err.name);
837 break :cache try cg.constInt(ty, value);
838 },
839 .error_union => |error_union| {
840 // TODO: Error unions may be constructed with constant instructions if the payload type
841 // allows it. For now, just generate it here regardless.
842 const err_ty = ty.errorUnionSet(zcu);
843 const payload_ty = ty.errorUnionPayload(zcu);
844 const err_val_id = switch (error_union.val) {
845 .err_name => |err_name| try cg.constInt(
846 err_ty,
847 try pt.getErrorValue(err_name),
848 ),
849 .payload => try cg.constInt(err_ty, 0),
850 };
851 const eu_layout = cg.errorUnionLayout(payload_ty);
852 if (!eu_layout.payload_has_bits) {
853 // We use the error type directly as the type.
854 break :cache err_val_id;
855 }
856
857 const payload_val_id = switch (error_union.val) {
858 .err_name => try cg.constant(payload_ty, .undef, .indirect),
859 .payload => |p| try cg.constant(payload_ty, .fromInterned(p), .indirect),
860 };
861
862 var constituents: [2]Id = undefined;
863 var types: [2]Type = undefined;
864 if (eu_layout.error_first) {
865 constituents[0] = err_val_id;
866 constituents[1] = payload_val_id;
867 types = .{ err_ty, payload_ty };
868 } else {
869 constituents[0] = payload_val_id;
870 constituents[1] = err_val_id;
871 types = .{ payload_ty, err_ty };
872 }
873
874 const comp_ty_id = try cg.resolveType(ty, .direct);
875 return try cg.constructComposite(comp_ty_id, &constituents);
876 },
877 .enum_tag => {
878 const int_val = try val.intFromEnum(ty, pt);
879 const int_ty = ty.intTagType(zcu);
880 break :cache try cg.constant(int_ty, int_val, repr);
881 },
882 .ptr => return cg.constantPtr(val),
883 .slice => |slice| {
884 const ptr_id = try cg.constantPtr(.fromInterned(slice.ptr));
885 const len_id = try cg.constant(.usize, .fromInterned(slice.len), .indirect);
886 const comp_ty_id = try cg.resolveType(ty, .direct);
887 return try cg.constructComposite(comp_ty_id, &.{ ptr_id, len_id });
888 },
889 .opt => {
890 const payload_ty = ty.optionalChild(zcu);
891 const maybe_payload_val = val.optionalValue(zcu);
892
893 if (!payload_ty.hasRuntimeBits(zcu)) {
894 break :cache try cg.constBool(maybe_payload_val != null, .indirect);
895 } else if (ty.optionalReprIsPayload(zcu)) {
896 // Optional representation is a nullable pointer or slice.
897 if (maybe_payload_val) |payload_val| {
898 return try cg.constant(payload_ty, payload_val, .indirect);
899 } else {
900 break :cache try cg.module.constNull(result_ty_id);
901 }
902 }
903
904 // Optional representation is a structure.
905 // { Payload, Bool }
906
907 const has_pl_id = try cg.constBool(maybe_payload_val != null, .indirect);
908 const payload_id = if (maybe_payload_val) |payload_val|
909 try cg.constant(payload_ty, payload_val, .indirect)
910 else
911 try cg.module.constUndef(try cg.resolveType(payload_ty, .indirect));
912
913 const comp_ty_id = try cg.resolveType(ty, .direct);
914 return try cg.constructComposite(comp_ty_id, &.{ payload_id, has_pl_id });
915 },
916 .aggregate => |aggregate| switch (ip.indexToKey(ty.ip_index)) {
917 inline .array_type, .vector_type => |array_type, tag| {
918 const elem_ty: Type = .fromInterned(array_type.child);
919
920 const scratch_top = cg.id_scratch.items.len;
921 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
922 const constituents = try cg.id_scratch.addManyAsSlice(gpa, @intCast(ty.arrayLenIncludingSentinel(zcu)));
923
924 const child_repr: Repr = switch (tag) {
925 .array_type => .indirect,
926 .vector_type => .direct,
927 else => unreachable,
928 };
929
930 switch (aggregate.storage) {
931 .bytes => |bytes| {
932 // TODO: This is really space inefficient, perhaps there is a better
933 // way to do it?
934 for (constituents, bytes.toSlice(constituents.len, ip)) |*constituent, byte| {
935 constituent.* = try cg.constInt(elem_ty, byte);
936 }
937 },
938 .elems => |elems| {
939 for (constituents, elems) |*constituent, elem| {
940 constituent.* = try cg.constant(elem_ty, .fromInterned(elem), child_repr);
941 }
942 },
943 .repeated_elem => |elem| {
944 @memset(constituents, try cg.constant(elem_ty, .fromInterned(elem), child_repr));
945 },
946 }
947
948 const comp_ty_id = try cg.resolveType(ty, .direct);
949 return cg.constructComposite(comp_ty_id, constituents);
950 },
951 .struct_type => {
952 const struct_type = zcu.typeToStruct(ty).?;
953
954 if (struct_type.layout == .@"packed") {
955 // TODO: composite int
956 // TODO: endianness
957 const bits: u16 = @intCast(ty.bitSize(zcu));
958 const bytes = std.mem.alignForward(u16, cg.module.backingIntBits(bits).@"0", 8) / 8;
959 var limbs: [8]u8 = undefined;
960 @memset(&limbs, 0);
961 val.writeToPackedMemory(ty, pt, limbs[0..bytes], 0) catch unreachable;
962 const backing_ty: Type = .fromInterned(struct_type.backingIntTypeUnordered(ip));
963 return try cg.constInt(backing_ty, @as(u64, @bitCast(limbs)));
964 }
965
966 var types = std.ArrayList(Type).init(gpa);
967 defer types.deinit();
968
969 var constituents = std.ArrayList(Id).init(gpa);
970 defer constituents.deinit();
971
972 var it = struct_type.iterateRuntimeOrder(ip);
973 while (it.next()) |field_index| {
974 const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]);
975 if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
976 // This is a zero-bit field - we only needed it for the alignment.
977 continue;
978 }
979
980 // TODO: Padding?
981 const field_val = try val.fieldValue(pt, field_index);
982 const field_id = try cg.constant(field_ty, field_val, .indirect);
983
984 try types.append(field_ty);
985 try constituents.append(field_id);
986 }
987
988 const comp_ty_id = try cg.resolveType(ty, .direct);
989 return try cg.constructComposite(comp_ty_id, constituents.items);
990 },
991 .tuple_type => return cg.todo("implement tuple types", .{}),
992 else => unreachable,
993 },
994 .un => |un| {
995 if (un.tag == .none) {
996 assert(ty.containerLayout(zcu) == .@"packed"); // TODO
997 const int_ty = try pt.intType(.unsigned, @intCast(ty.bitSize(zcu)));
998 return try cg.constInt(int_ty, Value.toUnsignedInt(.fromInterned(un.val), zcu));
999 }
1000 const active_field = ty.unionTagFieldIndex(.fromInterned(un.tag), zcu).?;
1001 const union_obj = zcu.typeToUnion(ty).?;
1002 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[active_field]);
1003 const payload = if (field_ty.hasRuntimeBitsIgnoreComptime(zcu))
1004 try cg.constant(field_ty, .fromInterned(un.val), .direct)
1005 else
1006 null;
1007 return try cg.unionInit(ty, active_field, payload);
1008 },
1009 .memoized_call => unreachable,
1010 }
1011 };
1012
1013 try cg.module.intern_map.putNoClobber(gpa, .{ val.toIntern(), repr }, cacheable_id);
1014
1015 return cacheable_id;
1016}
1017
1018fn constantPtr(cg: *CodeGen, ptr_val: Value) !Id {
1019 const pt = cg.pt;
1020 const zcu = cg.module.zcu;
1021 const gpa = cg.module.gpa;
1022
1023 if (ptr_val.isUndef(zcu)) {
1024 const result_ty = ptr_val.typeOf(zcu);
1025 const result_ty_id = try cg.resolveType(result_ty, .direct);
1026 return cg.module.constUndef(result_ty_id);
1027 }
1028
1029 var arena = std.heap.ArenaAllocator.init(gpa);
1030 defer arena.deinit();
1031
1032 const derivation = try ptr_val.pointerDerivation(arena.allocator(), pt);
1033 return cg.derivePtr(derivation);
1034}
1035
1036fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id {
1037 const gpa = cg.module.gpa;
1038 const pt = cg.pt;
1039 const zcu = cg.module.zcu;
1040 switch (derivation) {
1041 .comptime_alloc_ptr, .comptime_field_ptr => unreachable,
1042 .int => |int| {
1043 const result_ty_id = try cg.resolveType(int.ptr_ty, .direct);
1044 // TODO: This can probably be an OpSpecConstantOp Bitcast, but
1045 // that is not implemented by Mesa yet. Therefore, just generate it
1046 // as a runtime operation.
1047 const result_ptr_id = cg.module.allocId();
1048 const value_id = try cg.constInt(.usize, int.addr);
1049 try cg.body.emit(gpa, .OpConvertUToPtr, .{
1050 .id_result_type = result_ty_id,
1051 .id_result = result_ptr_id,
1052 .integer_value = value_id,
1053 });
1054 return result_ptr_id;
1055 },
1056 .nav_ptr => |nav| {
1057 const result_ptr_ty = try pt.navPtrType(nav);
1058 return cg.constantNavRef(result_ptr_ty, nav);
1059 },
1060 .uav_ptr => |uav| {
1061 const result_ptr_ty: Type = .fromInterned(uav.orig_ty);
1062 return cg.constantUavRef(result_ptr_ty, uav);
1063 },
1064 .eu_payload_ptr => @panic("TODO"),
1065 .opt_payload_ptr => @panic("TODO"),
1066 .field_ptr => |field| {
1067 const parent_ptr_id = try cg.derivePtr(field.parent.*);
1068 const parent_ptr_ty = try field.parent.ptrType(pt);
1069 return cg.structFieldPtr(field.result_ptr_ty, parent_ptr_ty, parent_ptr_id, field.field_idx);
1070 },
1071 .elem_ptr => |elem| {
1072 const parent_ptr_id = try cg.derivePtr(elem.parent.*);
1073 const parent_ptr_ty = try elem.parent.ptrType(pt);
1074 const index_id = try cg.constInt(.usize, elem.elem_idx);
1075 return cg.ptrElemPtr(parent_ptr_ty, parent_ptr_id, index_id);
1076 },
1077 .offset_and_cast => |oac| {
1078 const parent_ptr_id = try cg.derivePtr(oac.parent.*);
1079 const parent_ptr_ty = try oac.parent.ptrType(pt);
1080 const result_ty_id = try cg.resolveType(oac.new_ptr_ty, .direct);
1081 const child_size = oac.new_ptr_ty.childType(zcu).abiSize(zcu);
1082
1083 if (parent_ptr_ty.childType(zcu).isVector(zcu) and oac.byte_offset % child_size == 0) {
1084 // Vector element ptr accesses are derived as offset_and_cast.
1085 // We can just use OpAccessChain.
1086 return cg.accessChain(
1087 result_ty_id,
1088 parent_ptr_id,
1089 &.{@intCast(@divExact(oac.byte_offset, child_size))},
1090 );
1091 }
1092
1093 if (oac.byte_offset == 0) {
1094 // Allow changing the pointer type child only to restructure arrays.
1095 // e.g. [3][2]T to T is fine, as is [2]T -> [2][1]T.
1096 const result_ptr_id = cg.module.allocId();
1097 try cg.body.emit(gpa, .OpBitcast, .{
1098 .id_result_type = result_ty_id,
1099 .id_result = result_ptr_id,
1100 .operand = parent_ptr_id,
1101 });
1102 return result_ptr_id;
1103 }
1104
1105 return cg.fail("cannot perform pointer cast: '{f}' to '{f}'", .{
1106 parent_ptr_ty.fmt(pt),
1107 oac.new_ptr_ty.fmt(pt),
1108 });
1109 },
1110 }
1111}
1112
1113fn constantUavRef(
1114 cg: *CodeGen,
1115 ty: Type,
1116 uav: InternPool.Key.Ptr.BaseAddr.Uav,
1117) !Id {
1118 // TODO: Merge this function with constantDeclRef.
1119
1120 const zcu = cg.module.zcu;
1121 const ip = &zcu.intern_pool;
1122 const ty_id = try cg.resolveType(ty, .direct);
1123 const uav_ty: Type = .fromInterned(ip.typeOf(uav.val));
1124
1125 switch (ip.indexToKey(uav.val)) {
1126 .func => unreachable, // TODO
1127 .@"extern" => assert(!ip.isFunctionType(uav_ty.toIntern())),
1128 else => {},
1129 }
1130
1131 // const is_fn_body = decl_ty.zigTypeTag(zcu) == .@"fn";
1132 if (!uav_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) {
1133 // Pointer to nothing - return undefined
1134 return cg.module.constUndef(ty_id);
1135 }
1136
1137 // Uav refs are always generic.
1138 assert(ty.ptrAddressSpace(zcu) == .generic);
1139 const uav_ty_id = try cg.resolveType(uav_ty, .indirect);
1140 const decl_ptr_ty_id = try cg.module.ptrType(uav_ty_id, .generic);
1141 const ptr_id = try cg.resolveUav(uav.val);
1142
1143 if (decl_ptr_ty_id != ty_id) {
1144 // Differing pointer types, insert a cast.
1145 const casted_ptr_id = cg.module.allocId();
1146 try cg.body.emit(cg.module.gpa, .OpBitcast, .{
1147 .id_result_type = ty_id,
1148 .id_result = casted_ptr_id,
1149 .operand = ptr_id,
1150 });
1151 return casted_ptr_id;
1152 } else {
1153 return ptr_id;
1154 }
1155}
1156
1157fn constantNavRef(cg: *CodeGen, ty: Type, nav_index: InternPool.Nav.Index) !Id {
1158 const zcu = cg.module.zcu;
1159 const ip = &zcu.intern_pool;
1160 const ty_id = try cg.resolveType(ty, .direct);
1161 const nav = ip.getNav(nav_index);
1162 const nav_ty: Type = .fromInterned(nav.typeOf(ip));
1163
1164 switch (nav.status) {
1165 .unresolved => unreachable,
1166 .type_resolved => {}, // this is not a function or extern
1167 .fully_resolved => |r| switch (ip.indexToKey(r.val)) {
1168 .func => {
1169 // TODO: Properly lower function pointers. For now we are going to hack around it and
1170 // just generate an empty pointer. Function pointers are represented by a pointer to usize.
1171 return try cg.module.constUndef(ty_id);
1172 },
1173 .@"extern" => if (ip.isFunctionType(nav_ty.toIntern())) @panic("TODO"),
1174 else => {},
1175 },
1176 }
1177
1178 if (!nav_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) {
1179 // Pointer to nothing - return undefined.
1180 return cg.module.constUndef(ty_id);
1181 }
1182
1183 const spv_decl_index = try cg.module.resolveNav(ip, nav_index);
1184 const spv_decl = cg.module.declPtr(spv_decl_index);
1185 const spv_decl_result_id = spv_decl.result_id;
1186 assert(spv_decl.kind != .func);
1187
1188 const storage_class = cg.module.storageClass(nav.getAddrspace());
1189 try cg.addFunctionDep(spv_decl_index, storage_class);
1190
1191 const nav_ty_id = try cg.resolveType(nav_ty, .indirect);
1192 const decl_ptr_ty_id = try cg.module.ptrType(nav_ty_id, storage_class);
1193
1194 if (decl_ptr_ty_id != ty_id) {
1195 // Differing pointer types, insert a cast.
1196 const casted_ptr_id = cg.module.allocId();
1197 try cg.body.emit(cg.module.gpa, .OpBitcast, .{
1198 .id_result_type = ty_id,
1199 .id_result = casted_ptr_id,
1200 .operand = spv_decl_result_id,
1201 });
1202 return casted_ptr_id;
1203 }
1204
1205 return spv_decl_result_id;
1206}
1207
1208// Turn a Zig type's name into a cache reference.
1209fn resolveTypeName(cg: *CodeGen, ty: Type) ![]const u8 {
1210 const gpa = cg.module.gpa;
1211 var aw: std.io.Writer.Allocating = .init(gpa);
1212 defer aw.deinit();
1213 ty.print(&aw.writer, cg.pt) catch |err| switch (err) {
1214 error.WriteFailed => return error.OutOfMemory,
1215 };
1216 return try aw.toOwnedSlice();
1217}
1218
1219/// Generate a union type. Union types are always generated with the
1220/// most aligned field active. If the tag alignment is greater
1221/// than that of the payload, a regular union (non-packed, with both tag and
1222/// payload), will be generated as follows:
1223/// struct {
1224/// tag: TagType,
1225/// payload: MostAlignedFieldType,
1226/// payload_padding: [payload_size - @sizeOf(MostAlignedFieldType)]u8,
1227/// padding: [padding_size]u8,
1228/// }
1229/// If the payload alignment is greater than that of the tag:
1230/// struct {
1231/// payload: MostAlignedFieldType,
1232/// payload_padding: [payload_size - @sizeOf(MostAlignedFieldType)]u8,
1233/// tag: TagType,
1234/// padding: [padding_size]u8,
1235/// }
1236/// If any of the fields' size is 0, it will be omitted.
1237fn resolveUnionType(cg: *CodeGen, ty: Type) !Id {
1238 const gpa = cg.module.gpa;
1239 const zcu = cg.module.zcu;
1240 const ip = &zcu.intern_pool;
1241 const union_obj = zcu.typeToUnion(ty).?;
1242
1243 if (union_obj.flagsUnordered(ip).layout == .@"packed") {
1244 return try cg.module.intType(.unsigned, @intCast(ty.bitSize(zcu)));
1245 }
1246
1247 const layout = cg.unionLayout(ty);
1248 if (!layout.has_payload) {
1249 // No payload, so represent this as just the tag type.
1250 return try cg.resolveType(.fromInterned(union_obj.enum_tag_ty), .indirect);
1251 }
1252
1253 var member_types: [4]Id = undefined;
1254 var member_names: [4][]const u8 = undefined;
1255
1256 const u8_ty_id = try cg.resolveType(.u8, .direct);
1257
1258 if (layout.tag_size != 0) {
1259 const tag_ty_id = try cg.resolveType(.fromInterned(union_obj.enum_tag_ty), .indirect);
1260 member_types[layout.tag_index] = tag_ty_id;
1261 member_names[layout.tag_index] = "(tag)";
1262 }
1263
1264 if (layout.payload_size != 0) {
1265 const payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect);
1266 member_types[layout.payload_index] = payload_ty_id;
1267 member_names[layout.payload_index] = "(payload)";
1268 }
1269
1270 if (layout.payload_padding_size != 0) {
1271 const len_id = try cg.constInt(.u32, layout.payload_padding_size);
1272 const payload_padding_ty_id = try cg.module.arrayType(len_id, u8_ty_id);
1273 member_types[layout.payload_padding_index] = payload_padding_ty_id;
1274 member_names[layout.payload_padding_index] = "(payload padding)";
1275 }
1276
1277 if (layout.padding_size != 0) {
1278 const len_id = try cg.constInt(.u32, layout.padding_size);
1279 const padding_ty_id = try cg.module.arrayType(len_id, u8_ty_id);
1280 member_types[layout.padding_index] = padding_ty_id;
1281 member_names[layout.padding_index] = "(padding)";
1282 }
1283
1284 const result_id = try cg.module.structType(
1285 member_types[0..layout.total_fields],
1286 member_names[0..layout.total_fields],
1287 null,
1288 .none,
1289 );
1290
1291 const type_name = try cg.resolveTypeName(ty);
1292 defer gpa.free(type_name);
1293 try cg.module.debugName(result_id, type_name);
1294
1295 return result_id;
1296}
1297
1298fn resolveFnReturnType(cg: *CodeGen, ret_ty: Type) !Id {
1299 const zcu = cg.module.zcu;
1300 if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
1301 // If the return type is an error set or an error union, then we make this
1302 // anyerror return type instead, so that it can be coerced into a function
1303 // pointer type which has anyerror as the return type.
1304 if (ret_ty.isError(zcu)) {
1305 return cg.resolveType(.anyerror, .direct);
1306 } else {
1307 return cg.resolveType(.void, .direct);
1308 }
1309 }
1310
1311 return try cg.resolveType(ret_ty, .direct);
1312}
1313
1314fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id {
1315 const gpa = cg.module.gpa;
1316 const pt = cg.pt;
1317 const zcu = cg.module.zcu;
1318 const ip = &zcu.intern_pool;
1319 const target = cg.module.zcu.getTarget();
1320
1321 log.debug("resolveType: ty = {f}", .{ty.fmt(pt)});
1322
1323 switch (ty.zigTypeTag(zcu)) {
1324 .noreturn => {
1325 assert(repr == .direct);
1326 return try cg.module.voidType();
1327 },
1328 .void => switch (repr) {
1329 .direct => return try cg.module.voidType(),
1330 .indirect => return try cg.module.opaqueType("void"),
1331 },
1332 .bool => switch (repr) {
1333 .direct => return try cg.module.boolType(),
1334 .indirect => return try cg.resolveType(.u1, .indirect),
1335 },
1336 .int => {
1337 const int_info = ty.intInfo(zcu);
1338 if (int_info.bits == 0) {
1339 assert(repr == .indirect);
1340 return try cg.module.opaqueType("u0");
1341 }
1342 return try cg.module.intType(int_info.signedness, int_info.bits);
1343 },
1344 .@"enum" => return try cg.resolveType(ty.intTagType(zcu), repr),
1345 .float => {
1346 const bits = ty.floatBits(target);
1347 const supported = switch (bits) {
1348 16 => target.cpu.has(.spirv, .float16),
1349 32 => true,
1350 64 => target.cpu.has(.spirv, .float64),
1351 else => false,
1352 };
1353
1354 if (!supported) {
1355 return cg.fail(
1356 "floating point width of {} bits is not supported for the current SPIR-V feature set",
1357 .{bits},
1358 );
1359 }
1360
1361 return try cg.module.floatType(bits);
1362 },
1363 .array => {
1364 const elem_ty = ty.childType(zcu);
1365 const elem_ty_id = try cg.resolveType(elem_ty, .indirect);
1366 const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel(zcu)) orelse {
1367 return cg.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel(zcu)});
1368 };
1369
1370 if (!elem_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
1371 assert(repr == .indirect);
1372 return try cg.module.opaqueType("zero-sized-array");
1373 } else if (total_len == 0) {
1374 // The size of the array would be 0, but that is not allowed in SPIR-V.
1375 // This path can be reached for example when there is a slicing of a pointer
1376 // that produces a zero-length array. In all cases where this type can be generated,
1377 // this should be an indirect path.
1378 assert(repr == .indirect);
1379 // In this case, we have an array of a non-zero sized type. In this case,
1380 // generate an array of 1 element instead, so that ptr_elem_ptr instructions
1381 // can be lowered to ptrAccessChain instead of manually performing the math.
1382 const len_id = try cg.constInt(.u32, 1);
1383 return try cg.module.arrayType(len_id, elem_ty_id);
1384 } else {
1385 const total_len_id = try cg.constInt(.u32, total_len);
1386 const result_id = try cg.module.arrayType(total_len_id, elem_ty_id);
1387 switch (target.os.tag) {
1388 .vulkan, .opengl => {
1389 try cg.module.decorate(result_id, .{
1390 .array_stride = .{
1391 .array_stride = @intCast(elem_ty.abiSize(zcu)),
1392 },
1393 });
1394 },
1395 else => {},
1396 }
1397 return result_id;
1398 }
1399 },
1400 .vector => {
1401 const elem_ty = ty.childType(zcu);
1402 const elem_ty_id = try cg.resolveType(elem_ty, repr);
1403 const len = ty.vectorLen(zcu);
1404 if (cg.isSpvVector(ty)) return try cg.module.vectorType(len, elem_ty_id);
1405 const len_id = try cg.constInt(.u32, len);
1406 return try cg.module.arrayType(len_id, elem_ty_id);
1407 },
1408 .@"fn" => switch (repr) {
1409 .direct => {
1410 const fn_info = zcu.typeToFunc(ty).?;
1411
1412 comptime assert(zig_call_abi_ver == 3);
1413 assert(!fn_info.is_var_args);
1414 switch (fn_info.cc) {
1415 .auto,
1416 .spirv_kernel,
1417 .spirv_fragment,
1418 .spirv_vertex,
1419 .spirv_device,
1420 => {},
1421 else => unreachable,
1422 }
1423
1424 const return_ty_id = try cg.resolveFnReturnType(.fromInterned(fn_info.return_type));
1425
1426 const scratch_top = cg.id_scratch.items.len;
1427 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
1428 const param_ty_ids = try cg.id_scratch.addManyAsSlice(gpa, fn_info.param_types.len);
1429
1430 var param_index: usize = 0;
1431 for (fn_info.param_types.get(ip)) |param_ty_index| {
1432 const param_ty: Type = .fromInterned(param_ty_index);
1433 if (!param_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;
1434
1435 param_ty_ids[param_index] = try cg.resolveType(param_ty, .direct);
1436 param_index += 1;
1437 }
1438
1439 return try cg.module.functionType(return_ty_id, param_ty_ids[0..param_index]);
1440 },
1441 .indirect => {
1442 // TODO: Represent function pointers properly.
1443 // For now, just use an usize type.
1444 return try cg.resolveType(.usize, .indirect);
1445 },
1446 },
1447 .pointer => {
1448 const ptr_info = ty.ptrInfo(zcu);
1449
1450 const child_ty: Type = .fromInterned(ptr_info.child);
1451 const child_ty_id = try cg.resolveType(child_ty, .indirect);
1452 const storage_class = cg.module.storageClass(ptr_info.flags.address_space);
1453 const ptr_ty_id = try cg.module.ptrType(child_ty_id, storage_class);
1454
1455 if (ptr_info.flags.size != .slice) {
1456 return ptr_ty_id;
1457 }
1458
1459 const size_ty_id = try cg.resolveType(.usize, .direct);
1460 return try cg.module.structType(
1461 &.{ ptr_ty_id, size_ty_id },
1462 &.{ "ptr", "len" },
1463 null,
1464 .none,
1465 );
1466 },
1467 .@"struct" => {
1468 const struct_type = switch (ip.indexToKey(ty.toIntern())) {
1469 .tuple_type => |tuple| {
1470 const scratch_top = cg.id_scratch.items.len;
1471 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
1472 const member_types = try cg.id_scratch.addManyAsSlice(gpa, tuple.values.len);
1473
1474 var member_index: usize = 0;
1475 for (tuple.types.get(ip), tuple.values.get(ip)) |field_ty, field_val| {
1476 if (field_val != .none or !Type.fromInterned(field_ty).hasRuntimeBits(zcu)) continue;
1477
1478 member_types[member_index] = try cg.resolveType(.fromInterned(field_ty), .indirect);
1479 member_index += 1;
1480 }
1481
1482 const result_id = try cg.module.structType(
1483 member_types[0..member_index],
1484 null,
1485 null,
1486 .none,
1487 );
1488 const type_name = try cg.resolveTypeName(ty);
1489 defer gpa.free(type_name);
1490 try cg.module.debugName(result_id, type_name);
1491 return result_id;
1492 },
1493 .struct_type => ip.loadStructType(ty.toIntern()),
1494 else => unreachable,
1495 };
1496
1497 if (struct_type.layout == .@"packed") {
1498 return try cg.resolveType(.fromInterned(struct_type.backingIntTypeUnordered(ip)), .direct);
1499 }
1500
1501 var member_types = std.ArrayList(Id).init(gpa);
1502 defer member_types.deinit();
1503
1504 var member_names = std.ArrayList([]const u8).init(gpa);
1505 defer member_names.deinit();
1506
1507 var member_offsets = std.ArrayList(u32).init(gpa);
1508 defer member_offsets.deinit();
1509
1510 var it = struct_type.iterateRuntimeOrder(ip);
1511 while (it.next()) |field_index| {
1512 const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]);
1513 if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;
1514
1515 const field_name = struct_type.fieldName(ip, field_index).unwrap() orelse
1516 try ip.getOrPutStringFmt(zcu.gpa, pt.tid, "{d}", .{field_index}, .no_embedded_nulls);
1517 try member_types.append(try cg.resolveType(field_ty, .indirect));
1518 try member_names.append(field_name.toSlice(ip));
1519 try member_offsets.append(@intCast(ty.structFieldOffset(field_index, zcu)));
1520 }
1521
1522 const result_id = try cg.module.structType(
1523 member_types.items,
1524 member_names.items,
1525 member_offsets.items,
1526 ty.toIntern(),
1527 );
1528
1529 const type_name = try cg.resolveTypeName(ty);
1530 defer gpa.free(type_name);
1531 try cg.module.debugName(result_id, type_name);
1532
1533 return result_id;
1534 },
1535 .optional => {
1536 const payload_ty = ty.optionalChild(zcu);
1537 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
1538 // Just use a bool.
1539 // Note: Always generate the bool with indirect format, to save on some sanity
1540 // Perform the conversion to a direct bool when the field is extracted.
1541 return try cg.resolveType(.bool, .indirect);
1542 }
1543
1544 const payload_ty_id = try cg.resolveType(payload_ty, .indirect);
1545 if (ty.optionalReprIsPayload(zcu)) {
1546 // Optional is actually a pointer or a slice.
1547 return payload_ty_id;
1548 }
1549
1550 const bool_ty_id = try cg.resolveType(.bool, .indirect);
1551
1552 return try cg.module.structType(
1553 &.{ payload_ty_id, bool_ty_id },
1554 &.{ "payload", "valid" },
1555 null,
1556 .none,
1557 );
1558 },
1559 .@"union" => return try cg.resolveUnionType(ty),
1560 .error_set => {
1561 const err_int_ty = try pt.errorIntType();
1562 return try cg.resolveType(err_int_ty, repr);
1563 },
1564 .error_union => {
1565 const payload_ty = ty.errorUnionPayload(zcu);
1566 const err_ty = ty.errorUnionSet(zcu);
1567 const error_ty_id = try cg.resolveType(err_ty, .indirect);
1568
1569 const eu_layout = cg.errorUnionLayout(payload_ty);
1570 if (!eu_layout.payload_has_bits) {
1571 return error_ty_id;
1572 }
1573
1574 const payload_ty_id = try cg.resolveType(payload_ty, .indirect);
1575
1576 var member_types: [2]Id = undefined;
1577 var member_names: [2][]const u8 = undefined;
1578 if (eu_layout.error_first) {
1579 // Put the error first
1580 member_types = .{ error_ty_id, payload_ty_id };
1581 member_names = .{ "error", "payload" };
1582 // TODO: ABI padding?
1583 } else {
1584 // Put the payload first.
1585 member_types = .{ payload_ty_id, error_ty_id };
1586 member_names = .{ "payload", "error" };
1587 // TODO: ABI padding?
1588 }
1589
1590 return try cg.module.structType(&member_types, &member_names, null, .none);
1591 },
1592 .@"opaque" => {
1593 const type_name = try cg.resolveTypeName(ty);
1594 defer gpa.free(type_name);
1595 return try cg.module.opaqueType(type_name);
1596 },
1597
1598 .null,
1599 .undefined,
1600 .enum_literal,
1601 .comptime_float,
1602 .comptime_int,
1603 .type,
1604 => unreachable, // Must be comptime.
1605
1606 .frame, .@"anyframe" => unreachable, // TODO
1607 }
1608}
1609
1610const ErrorUnionLayout = struct {
1611 payload_has_bits: bool,
1612 error_first: bool,
1613
1614 fn errorFieldIndex(cg: @This()) u32 {
1615 assert(cg.payload_has_bits);
1616 return if (cg.error_first) 0 else 1;
1617 }
1618
1619 fn payloadFieldIndex(cg: @This()) u32 {
1620 assert(cg.payload_has_bits);
1621 return if (cg.error_first) 1 else 0;
1622 }
1623};
1624
1625fn errorUnionLayout(cg: *CodeGen, payload_ty: Type) ErrorUnionLayout {
1626 const zcu = cg.module.zcu;
1627
1628 const error_align = Type.abiAlignment(.anyerror, zcu);
1629 const payload_align = payload_ty.abiAlignment(zcu);
1630
1631 const error_first = error_align.compare(.gt, payload_align);
1632 return .{
1633 .payload_has_bits = payload_ty.hasRuntimeBitsIgnoreComptime(zcu),
1634 .error_first = error_first,
1635 };
1636}
1637
1638const UnionLayout = struct {
1639 /// If false, this union is represented
1640 /// by only an integer of the tag type.
1641 has_payload: bool,
1642 tag_size: u32,
1643 tag_index: u32,
1644 /// Note: This is the size of the payload type itcg, NOT the size of the ENTIRE payload.
1645 /// Use `has_payload` instead!!
1646 payload_ty: Type,
1647 payload_size: u32,
1648 payload_index: u32,
1649 payload_padding_size: u32,
1650 payload_padding_index: u32,
1651 padding_size: u32,
1652 padding_index: u32,
1653 total_fields: u32,
1654};
1655
1656fn unionLayout(cg: *CodeGen, ty: Type) UnionLayout {
1657 const zcu = cg.module.zcu;
1658 const ip = &zcu.intern_pool;
1659 const layout = ty.unionGetLayout(zcu);
1660 const union_obj = zcu.typeToUnion(ty).?;
1661
1662 var union_layout: UnionLayout = .{
1663 .has_payload = layout.payload_size != 0,
1664 .tag_size = @intCast(layout.tag_size),
1665 .tag_index = undefined,
1666 .payload_ty = undefined,
1667 .payload_size = undefined,
1668 .payload_index = undefined,
1669 .payload_padding_size = undefined,
1670 .payload_padding_index = undefined,
1671 .padding_size = @intCast(layout.padding),
1672 .padding_index = undefined,
1673 .total_fields = undefined,
1674 };
1675
1676 if (union_layout.has_payload) {
1677 const most_aligned_field = layout.most_aligned_field;
1678 const most_aligned_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[most_aligned_field]);
1679 union_layout.payload_ty = most_aligned_field_ty;
1680 union_layout.payload_size = @intCast(most_aligned_field_ty.abiSize(zcu));
1681 } else {
1682 union_layout.payload_size = 0;
1683 }
1684
1685 union_layout.payload_padding_size = @intCast(layout.payload_size - union_layout.payload_size);
1686
1687 const tag_first = layout.tag_align.compare(.gte, layout.payload_align);
1688 var field_index: u32 = 0;
1689
1690 if (union_layout.tag_size != 0 and tag_first) {
1691 union_layout.tag_index = field_index;
1692 field_index += 1;
1693 }
1694
1695 if (union_layout.payload_size != 0) {
1696 union_layout.payload_index = field_index;
1697 field_index += 1;
1698 }
1699
1700 if (union_layout.payload_padding_size != 0) {
1701 union_layout.payload_padding_index = field_index;
1702 field_index += 1;
1703 }
1704
1705 if (union_layout.tag_size != 0 and !tag_first) {
1706 union_layout.tag_index = field_index;
1707 field_index += 1;
1708 }
1709
1710 if (union_layout.padding_size != 0) {
1711 union_layout.padding_index = field_index;
1712 field_index += 1;
1713 }
1714
1715 union_layout.total_fields = field_index;
1716
1717 return union_layout;
1718}
1719
1720/// This structure represents a "temporary" value: Something we are currently
1721/// operating on. It typically lives no longer than the function that
1722/// implements a particular AIR operation. These are used to easier
1723/// implement vectorizable operations (see Vectorization and the build*
1724/// functions), and typically are only used for vectors of primitive types.
1725const Temporary = struct {
1726 /// The type of the temporary. This is here mainly
1727 /// for easier bookkeeping. Because we will never really
1728 /// store Temporaries, they only cause extra stack space,
1729 /// therefore no real storage is wasted.
1730 ty: Type,
1731 /// The value that this temporary holds. This is not necessarily
1732 /// a value that is actually usable, or a single value: It is virtual
1733 /// until materialize() is called, at which point is turned into
1734 /// the usual SPIR-V representation of `cg.ty`.
1735 value: Temporary.Value,
1736
1737 const Value = union(enum) {
1738 singleton: Id,
1739 exploded_vector: IdRange,
1740 };
1741
1742 fn init(ty: Type, singleton: Id) Temporary {
1743 return .{ .ty = ty, .value = .{ .singleton = singleton } };
1744 }
1745
1746 fn materialize(temp: Temporary, cg: *CodeGen) !Id {
1747 const gpa = cg.module.gpa;
1748 const zcu = cg.module.zcu;
1749 switch (temp.value) {
1750 .singleton => |id| return id,
1751 .exploded_vector => |range| {
1752 assert(temp.ty.isVector(zcu));
1753 assert(temp.ty.vectorLen(zcu) == range.len);
1754
1755 const scratch_top = cg.id_scratch.items.len;
1756 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
1757 const constituents = try cg.id_scratch.addManyAsSlice(gpa, range.len);
1758 for (constituents, 0..range.len) |*id, i| {
1759 id.* = range.at(i);
1760 }
1761
1762 const result_ty_id = try cg.resolveType(temp.ty, .direct);
1763 return cg.constructComposite(result_ty_id, constituents);
1764 },
1765 }
1766 }
1767
1768 fn vectorization(temp: Temporary, cg: *CodeGen) Vectorization {
1769 return .fromType(temp.ty, cg);
1770 }
1771
1772 fn pun(temp: Temporary, new_ty: Type) Temporary {
1773 return .{
1774 .ty = new_ty,
1775 .value = temp.value,
1776 };
1777 }
1778
1779 /// 'Explode' a temporary into separate elements. This turns a vector
1780 /// into a bag of elements.
1781 fn explode(temp: Temporary, cg: *CodeGen) !IdRange {
1782 const zcu = cg.module.zcu;
1783
1784 // If the value is a scalar, then this is a no-op.
1785 if (!temp.ty.isVector(zcu)) {
1786 return switch (temp.value) {
1787 .singleton => |id| .{ .base = @intFromEnum(id), .len = 1 },
1788 .exploded_vector => |range| range,
1789 };
1790 }
1791
1792 const ty_id = try cg.resolveType(temp.ty.scalarType(zcu), .direct);
1793 const n = temp.ty.vectorLen(zcu);
1794 const results = cg.module.allocIds(n);
1795
1796 const id = switch (temp.value) {
1797 .singleton => |id| id,
1798 .exploded_vector => |range| return range,
1799 };
1800
1801 for (0..n) |i| {
1802 const indexes = [_]u32{@intCast(i)};
1803 try cg.body.emit(cg.module.gpa, .OpCompositeExtract, .{
1804 .id_result_type = ty_id,
1805 .id_result = results.at(i),
1806 .composite = id,
1807 .indexes = &indexes,
1808 });
1809 }
1810
1811 return results;
1812 }
1813};
1814
1815/// Initialize a `Temporary` from an AIR value.
1816fn temporary(cg: *CodeGen, inst: Air.Inst.Ref) !Temporary {
1817 return .{
1818 .ty = cg.typeOf(inst),
1819 .value = .{ .singleton = try cg.resolve(inst) },
1820 };
1821}
1822
1823/// This union describes how a particular operation should be vectorized.
1824/// That depends on the operation and number of components of the inputs.
1825const Vectorization = union(enum) {
1826 /// This is an operation between scalars.
1827 scalar,
1828 /// This operation is unrolled into separate operations.
1829 /// Inputs may still be SPIR-V vectors, for example,
1830 /// when the operation can't be vectorized in SPIR-V.
1831 /// Value is number of components.
1832 unrolled: u32,
1833
1834 /// Derive a vectorization from a particular type
1835 fn fromType(ty: Type, cg: *CodeGen) Vectorization {
1836 const zcu = cg.module.zcu;
1837 if (!ty.isVector(zcu)) return .scalar;
1838 return .{ .unrolled = ty.vectorLen(zcu) };
1839 }
1840
1841 /// Given two vectorization methods, compute a "unification": a fallback
1842 /// that works for both, according to the following rules:
1843 /// - Scalars may broadcast
1844 /// - SPIR-V vectorized operations will unroll
1845 /// - Prefer scalar > unrolled
1846 fn unify(a: Vectorization, b: Vectorization) Vectorization {
1847 if (a == .scalar and b == .scalar) return .scalar;
1848 if (a == .unrolled or b == .unrolled) {
1849 if (a == .unrolled and b == .unrolled) assert(a.components() == b.components());
1850 if (a == .unrolled) return .{ .unrolled = a.components() };
1851 return .{ .unrolled = b.components() };
1852 }
1853 unreachable;
1854 }
1855
1856 /// Query the number of components that inputs of this operation have.
1857 /// Note: for broadcasting scalars, this returns the number of elements
1858 /// that the broadcasted vector would have.
1859 fn components(vec: Vectorization) u32 {
1860 return switch (vec) {
1861 .scalar => 1,
1862 .unrolled => |n| n,
1863 };
1864 }
1865
1866 /// Turns `ty` into the result-type of the entire operation.
1867 /// `ty` may be a scalar or vector, it doesn't matter.
1868 fn resultType(vec: Vectorization, cg: *CodeGen, ty: Type) !Type {
1869 const pt = cg.pt;
1870 const zcu = cg.module.zcu;
1871 const scalar_ty = ty.scalarType(zcu);
1872 return switch (vec) {
1873 .scalar => scalar_ty,
1874 .unrolled => |n| try pt.vectorType(.{ .len = n, .child = scalar_ty.toIntern() }),
1875 };
1876 }
1877
1878 /// Before a temporary can be used, some setup may need to be one. This function implements
1879 /// this setup, and returns a new type that holds the relevant information on how to access
1880 /// elements of the input.
1881 fn prepare(vec: Vectorization, cg: *CodeGen, tmp: Temporary) !PreparedOperand {
1882 const zcu = cg.module.zcu;
1883 const is_vector = tmp.ty.isVector(zcu);
1884 const value: PreparedOperand.Value = switch (tmp.value) {
1885 .singleton => |id| switch (vec) {
1886 .scalar => blk: {
1887 assert(!is_vector);
1888 break :blk .{ .scalar = id };
1889 },
1890 .unrolled => blk: {
1891 if (is_vector) break :blk .{ .vector_exploded = try tmp.explode(cg) };
1892 break :blk .{ .scalar_broadcast = id };
1893 },
1894 },
1895 .exploded_vector => |range| switch (vec) {
1896 .scalar => unreachable,
1897 .unrolled => |n| blk: {
1898 assert(range.len == n);
1899 break :blk .{ .vector_exploded = range };
1900 },
1901 },
1902 };
1903
1904 return .{
1905 .ty = tmp.ty,
1906 .value = value,
1907 };
1908 }
1909
1910 /// Finalize the results of an operation back into a temporary. `results` is
1911 /// a list of result-ids of the operation.
1912 fn finalize(vec: Vectorization, ty: Type, results: IdRange) Temporary {
1913 assert(vec.components() == results.len);
1914 return .{
1915 .ty = ty,
1916 .value = switch (vec) {
1917 .scalar => .{ .singleton = results.at(0) },
1918 .unrolled => .{ .exploded_vector = results },
1919 },
1920 };
1921 }
1922
1923 /// This struct represents an operand that has gone through some setup, and is
1924 /// ready to be used as part of an operation.
1925 const PreparedOperand = struct {
1926 ty: Type,
1927 value: PreparedOperand.Value,
1928
1929 /// The types of value that a prepared operand can hold internally. Depends
1930 /// on the operation and input value.
1931 const Value = union(enum) {
1932 /// A single scalar value that is used by a scalar operation.
1933 scalar: Id,
1934 /// A single scalar that is broadcasted in an unrolled operation.
1935 scalar_broadcast: Id,
1936 /// A vector represented by a consecutive list of IDs that is used in an unrolled operation.
1937 vector_exploded: IdRange,
1938 };
1939
1940 /// Query the value at a particular index of the operation. Note that
1941 /// the index is *not* the component/lane, but the index of the *operation*.
1942 fn at(op: PreparedOperand, i: usize) Id {
1943 switch (op.value) {
1944 .scalar => |id| {
1945 assert(i == 0);
1946 return id;
1947 },
1948 .scalar_broadcast => |id| return id,
1949 .vector_exploded => |range| return range.at(i),
1950 }
1951 }
1952 };
1953};
1954
1955/// A utility function to compute the vectorization style of
1956/// a list of values. These values may be any of the following:
1957/// - A `Vectorization` instance
1958/// - A Type, in which case the vectorization is computed via `Vectorization.fromType`.
1959/// - A Temporary, in which case the vectorization is computed via `Temporary.vectorization`.
1960fn vectorization(cg: *CodeGen, args: anytype) Vectorization {
1961 var v: Vectorization = undefined;
1962 assert(args.len >= 1);
1963 inline for (args, 0..) |arg, i| {
1964 const iv: Vectorization = switch (@TypeOf(arg)) {
1965 Vectorization => arg,
1966 Type => Vectorization.fromType(arg, cg),
1967 Temporary => arg.vectorization(cg),
1968 else => @compileError("invalid type"),
1969 };
1970 if (i == 0) {
1971 v = iv;
1972 } else {
1973 v = v.unify(iv);
1974 }
1975 }
1976 return v;
1977}
1978
1979/// This function builds an OpSConvert of OpUConvert depending on the
1980/// signedness of the types.
1981fn buildConvert(cg: *CodeGen, dst_ty: Type, src: Temporary) !Temporary {
1982 const zcu = cg.module.zcu;
1983
1984 const dst_ty_id = try cg.resolveType(dst_ty.scalarType(zcu), .direct);
1985 const src_ty_id = try cg.resolveType(src.ty.scalarType(zcu), .direct);
1986
1987 const v = cg.vectorization(.{ dst_ty, src });
1988 const result_ty = try v.resultType(cg, dst_ty);
1989
1990 // We can directly compare integers, because those type-IDs are cached.
1991 if (dst_ty_id == src_ty_id) {
1992 // Nothing to do, type-pun to the right value.
1993 // Note, Caller guarantees that the types fit (or caller will normalize after),
1994 // so we don't have to normalize here.
1995 // Note, dst_ty may be a scalar type even if we expect a vector, so we have to
1996 // convert to the right type here.
1997 return src.pun(result_ty);
1998 }
1999
2000 const ops = v.components();
2001 const results = cg.module.allocIds(ops);
2002
2003 const op_result_ty = dst_ty.scalarType(zcu);
2004 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct);
2005
2006 const opcode: Opcode = blk: {
2007 if (dst_ty.scalarType(zcu).isAnyFloat()) break :blk .OpFConvert;
2008 if (dst_ty.scalarType(zcu).isSignedInt(zcu)) break :blk .OpSConvert;
2009 break :blk .OpUConvert;
2010 };
2011
2012 const op_src = try v.prepare(cg, src);
2013
2014 for (0..ops) |i| {
2015 try cg.body.emitRaw(cg.module.gpa, opcode, 3);
2016 cg.body.writeOperand(Id, op_result_ty_id);
2017 cg.body.writeOperand(Id, results.at(i));
2018 cg.body.writeOperand(Id, op_src.at(i));
2019 }
2020
2021 return v.finalize(result_ty, results);
2022}
2023
2024fn buildFma(cg: *CodeGen, a: Temporary, b: Temporary, c: Temporary) !Temporary {
2025 const zcu = cg.module.zcu;
2026 const target = cg.module.zcu.getTarget();
2027
2028 const v = cg.vectorization(.{ a, b, c });
2029 const ops = v.components();
2030 const results = cg.module.allocIds(ops);
2031
2032 const op_result_ty = a.ty.scalarType(zcu);
2033 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct);
2034 const result_ty = try v.resultType(cg, a.ty);
2035
2036 const op_a = try v.prepare(cg, a);
2037 const op_b = try v.prepare(cg, b);
2038 const op_c = try v.prepare(cg, c);
2039
2040 const set = try cg.importExtendedSet();
2041 const opcode: u32 = switch (target.os.tag) {
2042 .opencl => @intFromEnum(spec.OpenClOpcode.fma),
2043 // NOTE: Vulkan's FMA instruction does *NOT* produce the right values!
2044 // its precision guarantees do NOT match zigs and it does NOT match OpenCLs!
2045 // it needs to be emulated!
2046 .vulkan, .opengl => @intFromEnum(spec.GlslOpcode.Fma),
2047 else => unreachable,
2048 };
2049
2050 for (0..ops) |i| {
2051 try cg.body.emit(cg.module.gpa, .OpExtInst, .{
2052 .id_result_type = op_result_ty_id,
2053 .id_result = results.at(i),
2054 .set = set,
2055 .instruction = .{ .inst = opcode },
2056 .id_ref_4 = &.{ op_a.at(i), op_b.at(i), op_c.at(i) },
2057 });
2058 }
2059
2060 return v.finalize(result_ty, results);
2061}
2062
2063fn buildSelect(cg: *CodeGen, condition: Temporary, lhs: Temporary, rhs: Temporary) !Temporary {
2064 const zcu = cg.module.zcu;
2065
2066 const v = cg.vectorization(.{ condition, lhs, rhs });
2067 const ops = v.components();
2068 const results = cg.module.allocIds(ops);
2069
2070 const op_result_ty = lhs.ty.scalarType(zcu);
2071 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct);
2072 const result_ty = try v.resultType(cg, lhs.ty);
2073
2074 assert(condition.ty.scalarType(zcu).zigTypeTag(zcu) == .bool);
2075
2076 const cond = try v.prepare(cg, condition);
2077 const object_1 = try v.prepare(cg, lhs);
2078 const object_2 = try v.prepare(cg, rhs);
2079
2080 for (0..ops) |i| {
2081 try cg.body.emit(cg.module.gpa, .OpSelect, .{
2082 .id_result_type = op_result_ty_id,
2083 .id_result = results.at(i),
2084 .condition = cond.at(i),
2085 .object_1 = object_1.at(i),
2086 .object_2 = object_2.at(i),
2087 });
2088 }
2089
2090 return v.finalize(result_ty, results);
2091}
2092
2093fn buildCmp(cg: *CodeGen, opcode: Opcode, lhs: Temporary, rhs: Temporary) !Temporary {
2094 const v = cg.vectorization(.{ lhs, rhs });
2095 const ops = v.components();
2096 const results = cg.module.allocIds(ops);
2097
2098 const op_result_ty: Type = .bool;
2099 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct);
2100 const result_ty = try v.resultType(cg, Type.bool);
2101
2102 const op_lhs = try v.prepare(cg, lhs);
2103 const op_rhs = try v.prepare(cg, rhs);
2104
2105 for (0..ops) |i| {
2106 try cg.body.emitRaw(cg.module.gpa, opcode, 4);
2107 cg.body.writeOperand(Id, op_result_ty_id);
2108 cg.body.writeOperand(Id, results.at(i));
2109 cg.body.writeOperand(Id, op_lhs.at(i));
2110 cg.body.writeOperand(Id, op_rhs.at(i));
2111 }
2112
2113 return v.finalize(result_ty, results);
2114}
2115
2116const UnaryOp = enum {
2117 l_not,
2118 bit_not,
2119 i_neg,
2120 f_neg,
2121 i_abs,
2122 f_abs,
2123 clz,
2124 ctz,
2125 floor,
2126 ceil,
2127 trunc,
2128 round,
2129 sqrt,
2130 sin,
2131 cos,
2132 tan,
2133 exp,
2134 exp2,
2135 log,
2136 log2,
2137 log10,
2138
2139 pub fn extInstOpcode(op: UnaryOp, target: *const std.Target) ?u32 {
2140 return switch (target.os.tag) {
2141 .opencl => @intFromEnum(@as(spec.OpenClOpcode, switch (op) {
2142 .i_abs => .s_abs,
2143 .f_abs => .fabs,
2144 .clz => .clz,
2145 .ctz => .ctz,
2146 .floor => .floor,
2147 .ceil => .ceil,
2148 .trunc => .trunc,
2149 .round => .round,
2150 .sqrt => .sqrt,
2151 .sin => .sin,
2152 .cos => .cos,
2153 .tan => .tan,
2154 .exp => .exp,
2155 .exp2 => .exp2,
2156 .log => .log,
2157 .log2 => .log2,
2158 .log10 => .log10,
2159 else => return null,
2160 })),
2161 // Note: We'll need to check these for floating point accuracy
2162 // Vulkan does not put tight requirements on these, for correction
2163 // we might want to emulate them at some point.
2164 .vulkan, .opengl => @intFromEnum(@as(spec.GlslOpcode, switch (op) {
2165 .i_abs => .SAbs,
2166 .f_abs => .FAbs,
2167 .floor => .Floor,
2168 .ceil => .Ceil,
2169 .trunc => .Trunc,
2170 .round => .Round,
2171 else => return null,
2172 })),
2173 else => unreachable,
2174 };
2175 }
2176};
2177
2178fn buildUnary(cg: *CodeGen, op: UnaryOp, operand: Temporary) !Temporary {
2179 const zcu = cg.module.zcu;
2180 const target = cg.module.zcu.getTarget();
2181 const v = cg.vectorization(.{operand});
2182 const ops = v.components();
2183 const results = cg.module.allocIds(ops);
2184 const op_result_ty = operand.ty.scalarType(zcu);
2185 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct);
2186 const result_ty = try v.resultType(cg, operand.ty);
2187 const op_operand = try v.prepare(cg, operand);
2188
2189 if (op.extInstOpcode(target)) |opcode| {
2190 const set = try cg.importExtendedSet();
2191 for (0..ops) |i| {
2192 try cg.body.emit(cg.module.gpa, .OpExtInst, .{
2193 .id_result_type = op_result_ty_id,
2194 .id_result = results.at(i),
2195 .set = set,
2196 .instruction = .{ .inst = opcode },
2197 .id_ref_4 = &.{op_operand.at(i)},
2198 });
2199 }
2200 } else {
2201 const opcode: Opcode = switch (op) {
2202 .l_not => .OpLogicalNot,
2203 .bit_not => .OpNot,
2204 .i_neg => .OpSNegate,
2205 .f_neg => .OpFNegate,
2206 else => return cg.todo(
2207 "implement unary operation '{s}' for {s} os",
2208 .{ @tagName(op), @tagName(target.os.tag) },
2209 ),
2210 };
2211 for (0..ops) |i| {
2212 try cg.body.emitRaw(cg.module.gpa, opcode, 3);
2213 cg.body.writeOperand(Id, op_result_ty_id);
2214 cg.body.writeOperand(Id, results.at(i));
2215 cg.body.writeOperand(Id, op_operand.at(i));
2216 }
2217 }
2218
2219 return v.finalize(result_ty, results);
2220}
2221
2222fn buildBinary(cg: *CodeGen, opcode: Opcode, lhs: Temporary, rhs: Temporary) !Temporary {
2223 const zcu = cg.module.zcu;
2224
2225 const v = cg.vectorization(.{ lhs, rhs });
2226 const ops = v.components();
2227 const results = cg.module.allocIds(ops);
2228
2229 const op_result_ty = lhs.ty.scalarType(zcu);
2230 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct);
2231 const result_ty = try v.resultType(cg, lhs.ty);
2232
2233 const op_lhs = try v.prepare(cg, lhs);
2234 const op_rhs = try v.prepare(cg, rhs);
2235
2236 for (0..ops) |i| {
2237 try cg.body.emitRaw(cg.module.gpa, opcode, 4);
2238 cg.body.writeOperand(Id, op_result_ty_id);
2239 cg.body.writeOperand(Id, results.at(i));
2240 cg.body.writeOperand(Id, op_lhs.at(i));
2241 cg.body.writeOperand(Id, op_rhs.at(i));
2242 }
2243
2244 return v.finalize(result_ty, results);
2245}
2246
2247/// This function builds an extended multiplication, either OpSMulExtended or OpUMulExtended on Vulkan,
2248/// or OpIMul and s_mul_hi or u_mul_hi on OpenCL.
2249fn buildWideMul(
2250 cg: *CodeGen,
2251 signedness: std.builtin.Signedness,
2252 lhs: Temporary,
2253 rhs: Temporary,
2254) !struct { Temporary, Temporary } {
2255 const pt = cg.pt;
2256 const zcu = cg.module.zcu;
2257 const target = cg.module.zcu.getTarget();
2258 const ip = &zcu.intern_pool;
2259
2260 const v = lhs.vectorization(cg).unify(rhs.vectorization(cg));
2261 const ops = v.components();
2262
2263 const arith_op_ty = lhs.ty.scalarType(zcu);
2264 const arith_op_ty_id = try cg.resolveType(arith_op_ty, .direct);
2265
2266 const lhs_op = try v.prepare(cg, lhs);
2267 const rhs_op = try v.prepare(cg, rhs);
2268
2269 const value_results = cg.module.allocIds(ops);
2270 const overflow_results = cg.module.allocIds(ops);
2271
2272 switch (target.os.tag) {
2273 .opencl => {
2274 // Currently, SPIRV-LLVM-Translator based backends cannot deal with OpSMulExtended and
2275 // OpUMulExtended. For these we will use the OpenCL s_mul_hi to compute the high-order bits
2276 // instead.
2277 const set = try cg.importExtendedSet();
2278 const overflow_inst: spec.OpenClOpcode = switch (signedness) {
2279 .signed => .s_mul_hi,
2280 .unsigned => .u_mul_hi,
2281 };
2282
2283 for (0..ops) |i| {
2284 try cg.body.emit(cg.module.gpa, .OpIMul, .{
2285 .id_result_type = arith_op_ty_id,
2286 .id_result = value_results.at(i),
2287 .operand_1 = lhs_op.at(i),
2288 .operand_2 = rhs_op.at(i),
2289 });
2290
2291 try cg.body.emit(cg.module.gpa, .OpExtInst, .{
2292 .id_result_type = arith_op_ty_id,
2293 .id_result = overflow_results.at(i),
2294 .set = set,
2295 .instruction = .{ .inst = @intFromEnum(overflow_inst) },
2296 .id_ref_4 = &.{ lhs_op.at(i), rhs_op.at(i) },
2297 });
2298 }
2299 },
2300 .vulkan, .opengl => {
2301 // Operations return a struct{T, T}
2302 // where T is maybe vectorized.
2303 const op_result_ty: Type = .fromInterned(try ip.getTupleType(zcu.gpa, pt.tid, .{
2304 .types = &.{ arith_op_ty.toIntern(), arith_op_ty.toIntern() },
2305 .values = &.{ .none, .none },
2306 }));
2307 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct);
2308
2309 const opcode: Opcode = switch (signedness) {
2310 .signed => .OpSMulExtended,
2311 .unsigned => .OpUMulExtended,
2312 };
2313
2314 for (0..ops) |i| {
2315 const op_result = cg.module.allocId();
2316
2317 try cg.body.emitRaw(cg.module.gpa, opcode, 4);
2318 cg.body.writeOperand(Id, op_result_ty_id);
2319 cg.body.writeOperand(Id, op_result);
2320 cg.body.writeOperand(Id, lhs_op.at(i));
2321 cg.body.writeOperand(Id, rhs_op.at(i));
2322
2323 // The above operation returns a struct. We might want to expand
2324 // Temporary to deal with the fact that these are structs eventually,
2325 // but for now, take the struct apart and return two separate vectors.
2326
2327 try cg.body.emit(cg.module.gpa, .OpCompositeExtract, .{
2328 .id_result_type = arith_op_ty_id,
2329 .id_result = value_results.at(i),
2330 .composite = op_result,
2331 .indexes = &.{0},
2332 });
2333
2334 try cg.body.emit(cg.module.gpa, .OpCompositeExtract, .{
2335 .id_result_type = arith_op_ty_id,
2336 .id_result = overflow_results.at(i),
2337 .composite = op_result,
2338 .indexes = &.{1},
2339 });
2340 }
2341 },
2342 else => unreachable,
2343 }
2344
2345 const result_ty = try v.resultType(cg, lhs.ty);
2346 return .{
2347 v.finalize(result_ty, value_results),
2348 v.finalize(result_ty, overflow_results),
2349 };
2350}
2351
2352/// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure.
2353/// In order to be able to run tests, we "temporarily" lower test kernels into separate entry-
2354/// points. The test executor will then be able to invoke these to run the tests.
2355/// Note that tests are lowered according to std.builtin.TestFn, which is `fn () anyerror!void`.
2356/// (anyerror!void has the same layout as anyerror).
2357/// Each test declaration generates a function like.
2358/// %anyerror = OpTypeInt 0 16
2359/// %p_invocation_globals_struct_ty = ...
2360/// %p_anyerror = OpTypePointer CrossWorkgroup %anyerror
2361/// %K = OpTypeFunction %void %p_invocation_globals_struct_ty %p_anyerror
2362///
2363/// %test = OpFunction %void %K
2364/// %p_invocation_globals = OpFunctionParameter p_invocation_globals_struct_ty
2365/// %p_err = OpFunctionParameter %p_anyerror
2366/// %lbl = OpLabel
2367/// %result = OpFunctionCall %anyerror %func %p_invocation_globals
2368/// OpStore %p_err %result
2369/// OpFunctionEnd
2370/// TODO is to also write out the error as a function call parameter, and to somehow fetch
2371/// the name of an error in the text executor.
2372fn generateTestEntryPoint(
2373 cg: *CodeGen,
2374 name: []const u8,
2375 spv_decl_index: Module.Decl.Index,
2376 test_id: Id,
2377) !void {
2378 const gpa = cg.module.gpa;
2379 const zcu = cg.module.zcu;
2380 const target = cg.module.zcu.getTarget();
2381
2382 const anyerror_ty_id = try cg.resolveType(.anyerror, .direct);
2383 const ptr_anyerror_ty = try cg.pt.ptrType(.{
2384 .child = .anyerror_type,
2385 .flags = .{ .address_space = .global },
2386 });
2387 const ptr_anyerror_ty_id = try cg.resolveType(ptr_anyerror_ty, .direct);
2388
2389 const kernel_id = cg.module.declPtr(spv_decl_index).result_id;
2390
2391 const section = &cg.module.sections.functions;
2392
2393 const p_error_id = cg.module.allocId();
2394 switch (target.os.tag) {
2395 .opencl, .amdhsa => {
2396 const void_ty_id = try cg.resolveType(.void, .direct);
2397 const kernel_proto_ty_id = try cg.module.functionType(void_ty_id, &.{ptr_anyerror_ty_id});
2398
2399 try section.emit(gpa, .OpFunction, .{
2400 .id_result_type = try cg.resolveType(.void, .direct),
2401 .id_result = kernel_id,
2402 .function_control = .{},
2403 .function_type = kernel_proto_ty_id,
2404 });
2405
2406 try section.emit(gpa, .OpFunctionParameter, .{
2407 .id_result_type = ptr_anyerror_ty_id,
2408 .id_result = p_error_id,
2409 });
2410
2411 try section.emit(gpa, .OpLabel, .{
2412 .id_result = cg.module.allocId(),
2413 });
2414 },
2415 .vulkan, .opengl => {
2416 if (cg.module.error_buffer == null) {
2417 const spv_err_decl_index = try cg.module.allocDecl(.global);
2418 const err_buf_result_id = cg.module.declPtr(spv_err_decl_index).result_id;
2419
2420 const buffer_struct_ty_id = try cg.module.structType(
2421 &.{anyerror_ty_id},
2422 &.{"error_out"},
2423 null,
2424 .none,
2425 );
2426 try cg.module.decorate(buffer_struct_ty_id, .block);
2427 try cg.module.decorateMember(buffer_struct_ty_id, 0, .{ .offset = .{ .byte_offset = 0 } });
2428
2429 const ptr_buffer_struct_ty_id = cg.module.allocId();
2430 try cg.module.sections.globals.emit(gpa, .OpTypePointer, .{
2431 .id_result = ptr_buffer_struct_ty_id,
2432 .storage_class = cg.module.storageClass(.global),
2433 .type = buffer_struct_ty_id,
2434 });
2435
2436 try cg.module.sections.globals.emit(gpa, .OpVariable, .{
2437 .id_result_type = ptr_buffer_struct_ty_id,
2438 .id_result = err_buf_result_id,
2439 .storage_class = cg.module.storageClass(.global),
2440 });
2441 try cg.module.decorate(err_buf_result_id, .{ .descriptor_set = .{ .descriptor_set = 0 } });
2442 try cg.module.decorate(err_buf_result_id, .{ .binding = .{ .binding_point = 0 } });
2443
2444 cg.module.error_buffer = spv_err_decl_index;
2445 }
2446
2447 try cg.module.sections.execution_modes.emit(gpa, .OpExecutionMode, .{
2448 .entry_point = kernel_id,
2449 .mode = .{ .local_size = .{
2450 .x_size = 1,
2451 .y_size = 1,
2452 .z_size = 1,
2453 } },
2454 });
2455
2456 const void_ty_id = try cg.resolveType(.void, .direct);
2457 const kernel_proto_ty_id = try cg.module.functionType(void_ty_id, &.{});
2458 try section.emit(gpa, .OpFunction, .{
2459 .id_result_type = try cg.resolveType(.void, .direct),
2460 .id_result = kernel_id,
2461 .function_control = .{},
2462 .function_type = kernel_proto_ty_id,
2463 });
2464 try section.emit(gpa, .OpLabel, .{
2465 .id_result = cg.module.allocId(),
2466 });
2467
2468 const spv_err_decl_index = cg.module.error_buffer.?;
2469 const buffer_id = cg.module.declPtr(spv_err_decl_index).result_id;
2470 try cg.module.decl_deps.append(gpa, spv_err_decl_index);
2471
2472 const zero_id = try cg.constInt(.u32, 0);
2473 try section.emit(gpa, .OpInBoundsAccessChain, .{
2474 .id_result_type = ptr_anyerror_ty_id,
2475 .id_result = p_error_id,
2476 .base = buffer_id,
2477 .indexes = &.{zero_id},
2478 });
2479 },
2480 else => unreachable,
2481 }
2482
2483 const error_id = cg.module.allocId();
2484 try section.emit(gpa, .OpFunctionCall, .{
2485 .id_result_type = anyerror_ty_id,
2486 .id_result = error_id,
2487 .function = test_id,
2488 });
2489 // Note: Convert to direct not required.
2490 try section.emit(gpa, .OpStore, .{
2491 .pointer = p_error_id,
2492 .object = error_id,
2493 .memory_access = .{
2494 .aligned = .{ .literal_integer = @intCast(Type.abiAlignment(.anyerror, zcu).toByteUnits().?) },
2495 },
2496 });
2497 try section.emit(gpa, .OpReturn, {});
2498 try section.emit(gpa, .OpFunctionEnd, {});
2499
2500 // Just generate a quick other name because the intel runtime crashes when the entry-
2501 // point name is the same as a different OpName.
2502 const test_name = try std.fmt.allocPrint(cg.module.arena, "test {s}", .{name});
2503
2504 const execution_mode: spec.ExecutionModel = switch (target.os.tag) {
2505 .vulkan, .opengl => .gl_compute,
2506 .opencl, .amdhsa => .kernel,
2507 else => unreachable,
2508 };
2509
2510 try cg.module.declareEntryPoint(spv_decl_index, test_name, execution_mode, null);
2511}
2512
2513fn intFromBool(cg: *CodeGen, value: Temporary) !Temporary {
2514 return try cg.intFromBool2(value, Type.u1);
2515}
2516
2517fn intFromBool2(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary {
2518 const zero_id = try cg.constInt(result_ty, 0);
2519 const one_id = try cg.constInt(result_ty, 1);
2520
2521 return try cg.buildSelect(
2522 value,
2523 Temporary.init(result_ty, one_id),
2524 Temporary.init(result_ty, zero_id),
2525 );
2526}
2527
2528/// Convert representation from indirect (in memory) to direct (in 'register')
2529/// This converts the argument type from resolveType(ty, .indirect) to resolveType(ty, .direct).
2530fn convertToDirect(cg: *CodeGen, ty: Type, operand_id: Id) !Id {
2531 const pt = cg.pt;
2532 const zcu = cg.module.zcu;
2533 switch (ty.scalarType(zcu).zigTypeTag(zcu)) {
2534 .bool => {
2535 const false_id = try cg.constBool(false, .indirect);
2536 const operand_ty = blk: {
2537 if (!ty.isVector(zcu)) break :blk Type.u1;
2538 break :blk try pt.vectorType(.{
2539 .len = ty.vectorLen(zcu),
2540 .child = .u1_type,
2541 });
2542 };
2543
2544 const result = try cg.buildCmp(
2545 .OpINotEqual,
2546 Temporary.init(operand_ty, operand_id),
2547 Temporary.init(.u1, false_id),
2548 );
2549 return try result.materialize(cg);
2550 },
2551 else => return operand_id,
2552 }
2553}
2554
2555/// Convert representation from direct (in 'register) to direct (in memory)
2556/// This converts the argument type from resolveType(ty, .direct) to resolveType(ty, .indirect).
2557fn convertToIndirect(cg: *CodeGen, ty: Type, operand_id: Id) !Id {
2558 const zcu = cg.module.zcu;
2559 switch (ty.scalarType(zcu).zigTypeTag(zcu)) {
2560 .bool => {
2561 const result = try cg.intFromBool(Temporary.init(ty, operand_id));
2562 return try result.materialize(cg);
2563 },
2564 else => return operand_id,
2565 }
2566}
2567
2568fn extractField(cg: *CodeGen, result_ty: Type, object: Id, field: u32) !Id {
2569 const result_ty_id = try cg.resolveType(result_ty, .indirect);
2570 const result_id = cg.module.allocId();
2571 const indexes = [_]u32{field};
2572 try cg.body.emit(cg.module.gpa, .OpCompositeExtract, .{
2573 .id_result_type = result_ty_id,
2574 .id_result = result_id,
2575 .composite = object,
2576 .indexes = &indexes,
2577 });
2578 // Convert bools; direct structs have their field types as indirect values.
2579 return try cg.convertToDirect(result_ty, result_id);
2580}
2581
2582fn extractVectorComponent(cg: *CodeGen, result_ty: Type, vector_id: Id, field: u32) !Id {
2583 const result_ty_id = try cg.resolveType(result_ty, .direct);
2584 const result_id = cg.module.allocId();
2585 const indexes = [_]u32{field};
2586 try cg.body.emit(cg.module.gpa, .OpCompositeExtract, .{
2587 .id_result_type = result_ty_id,
2588 .id_result = result_id,
2589 .composite = vector_id,
2590 .indexes = &indexes,
2591 });
2592 // Vector components are already stored in direct representation.
2593 return result_id;
2594}
2595
2596const MemoryOptions = struct {
2597 is_volatile: bool = false,
2598};
2599
2600fn load(cg: *CodeGen, value_ty: Type, ptr_id: Id, options: MemoryOptions) !Id {
2601 const zcu = cg.module.zcu;
2602 const alignment: u32 = @intCast(value_ty.abiAlignment(zcu).toByteUnits().?);
2603 const indirect_value_ty_id = try cg.resolveType(value_ty, .indirect);
2604 const result_id = cg.module.allocId();
2605 const access: spec.MemoryAccess.Extended = .{
2606 .@"volatile" = options.is_volatile,
2607 .aligned = .{ .literal_integer = alignment },
2608 };
2609 try cg.body.emit(cg.module.gpa, .OpLoad, .{
2610 .id_result_type = indirect_value_ty_id,
2611 .id_result = result_id,
2612 .pointer = ptr_id,
2613 .memory_access = access,
2614 });
2615 return try cg.convertToDirect(value_ty, result_id);
2616}
2617
2618fn store(cg: *CodeGen, value_ty: Type, ptr_id: Id, value_id: Id, options: MemoryOptions) !void {
2619 const indirect_value_id = try cg.convertToIndirect(value_ty, value_id);
2620 const access: spec.MemoryAccess.Extended = .{ .@"volatile" = options.is_volatile };
2621 try cg.body.emit(cg.module.gpa, .OpStore, .{
2622 .pointer = ptr_id,
2623 .object = indirect_value_id,
2624 .memory_access = access,
2625 });
2626}
2627
2628fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) !void {
2629 for (body) |inst| {
2630 try cg.genInst(inst);
2631 }
2632}
2633
2634fn genInst(cg: *CodeGen, inst: Air.Inst.Index) Error!void {
2635 const gpa = cg.module.gpa;
2636 const zcu = cg.module.zcu;
2637 const ip = &zcu.intern_pool;
2638 if (cg.liveness.isUnused(inst) and !cg.air.mustLower(inst, ip))
2639 return;
2640
2641 const air_tags = cg.air.instructions.items(.tag);
2642 const maybe_result_id: ?Id = switch (air_tags[@intFromEnum(inst)]) {
2643 // zig fmt: off
2644 .add, .add_wrap, .add_optimized => try cg.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd),
2645 .sub, .sub_wrap, .sub_optimized => try cg.airArithOp(inst, .OpFSub, .OpISub, .OpISub),
2646 .mul, .mul_wrap, .mul_optimized => try cg.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul),
2647
2648 .sqrt => try cg.airUnOpSimple(inst, .sqrt),
2649 .sin => try cg.airUnOpSimple(inst, .sin),
2650 .cos => try cg.airUnOpSimple(inst, .cos),
2651 .tan => try cg.airUnOpSimple(inst, .tan),
2652 .exp => try cg.airUnOpSimple(inst, .exp),
2653 .exp2 => try cg.airUnOpSimple(inst, .exp2),
2654 .log => try cg.airUnOpSimple(inst, .log),
2655 .log2 => try cg.airUnOpSimple(inst, .log2),
2656 .log10 => try cg.airUnOpSimple(inst, .log10),
2657 .abs => try cg.airAbs(inst),
2658 .floor => try cg.airUnOpSimple(inst, .floor),
2659 .ceil => try cg.airUnOpSimple(inst, .ceil),
2660 .round => try cg.airUnOpSimple(inst, .round),
2661 .trunc_float => try cg.airUnOpSimple(inst, .trunc),
2662 .neg, .neg_optimized => try cg.airUnOpSimple(inst, .f_neg),
2663
2664 .div_float, .div_float_optimized => try cg.airArithOp(inst, .OpFDiv, .OpSDiv, .OpUDiv),
2665 .div_floor, .div_floor_optimized => try cg.airDivFloor(inst),
2666 .div_trunc, .div_trunc_optimized => try cg.airDivTrunc(inst),
2667
2668 .rem, .rem_optimized => try cg.airArithOp(inst, .OpFRem, .OpSRem, .OpUMod),
2669 .mod, .mod_optimized => try cg.airArithOp(inst, .OpFMod, .OpSMod, .OpUMod),
2670
2671 .add_with_overflow => try cg.airAddSubOverflow(inst, .OpIAdd, .OpULessThan, .OpSLessThan),
2672 .sub_with_overflow => try cg.airAddSubOverflow(inst, .OpISub, .OpUGreaterThan, .OpSGreaterThan),
2673 .mul_with_overflow => try cg.airMulOverflow(inst),
2674 .shl_with_overflow => try cg.airShlOverflow(inst),
2675
2676 .mul_add => try cg.airMulAdd(inst),
2677
2678 .ctz => try cg.airClzCtz(inst, .ctz),
2679 .clz => try cg.airClzCtz(inst, .clz),
2680
2681 .select => try cg.airSelect(inst),
2682
2683 .splat => try cg.airSplat(inst),
2684 .reduce, .reduce_optimized => try cg.airReduce(inst),
2685 .shuffle_one => try cg.airShuffleOne(inst),
2686 .shuffle_two => try cg.airShuffleTwo(inst),
2687
2688 .ptr_add => try cg.airPtrAdd(inst),
2689 .ptr_sub => try cg.airPtrSub(inst),
2690
2691 .bit_and => try cg.airBinOpSimple(inst, .OpBitwiseAnd),
2692 .bit_or => try cg.airBinOpSimple(inst, .OpBitwiseOr),
2693 .xor => try cg.airBinOpSimple(inst, .OpBitwiseXor),
2694 .bool_and => try cg.airBinOpSimple(inst, .OpLogicalAnd),
2695 .bool_or => try cg.airBinOpSimple(inst, .OpLogicalOr),
2696
2697 .shl, .shl_exact => try cg.airShift(inst, .OpShiftLeftLogical, .OpShiftLeftLogical),
2698 .shr, .shr_exact => try cg.airShift(inst, .OpShiftRightLogical, .OpShiftRightArithmetic),
2699
2700 .min => try cg.airMinMax(inst, .min),
2701 .max => try cg.airMinMax(inst, .max),
2702
2703 .bitcast => try cg.airBitCast(inst),
2704 .intcast, .trunc => try cg.airIntCast(inst),
2705 .float_from_int => try cg.airFloatFromInt(inst),
2706 .int_from_float => try cg.airIntFromFloat(inst),
2707 .fpext, .fptrunc => try cg.airFloatCast(inst),
2708 .not => try cg.airNot(inst),
2709
2710 .array_to_slice => try cg.airArrayToSlice(inst),
2711 .slice => try cg.airSlice(inst),
2712 .aggregate_init => try cg.airAggregateInit(inst),
2713 .memcpy => return cg.airMemcpy(inst),
2714 .memmove => return cg.airMemmove(inst),
2715
2716 .slice_ptr => try cg.airSliceField(inst, 0),
2717 .slice_len => try cg.airSliceField(inst, 1),
2718 .slice_elem_ptr => try cg.airSliceElemPtr(inst),
2719 .slice_elem_val => try cg.airSliceElemVal(inst),
2720 .ptr_elem_ptr => try cg.airPtrElemPtr(inst),
2721 .ptr_elem_val => try cg.airPtrElemVal(inst),
2722 .array_elem_val => try cg.airArrayElemVal(inst),
2723
2724 .vector_store_elem => return cg.airVectorStoreElem(inst),
2725
2726 .set_union_tag => return cg.airSetUnionTag(inst),
2727 .get_union_tag => try cg.airGetUnionTag(inst),
2728 .union_init => try cg.airUnionInit(inst),
2729
2730 .struct_field_val => try cg.airStructFieldVal(inst),
2731 .field_parent_ptr => try cg.airFieldParentPtr(inst),
2732
2733 .struct_field_ptr_index_0 => try cg.airStructFieldPtrIndex(inst, 0),
2734 .struct_field_ptr_index_1 => try cg.airStructFieldPtrIndex(inst, 1),
2735 .struct_field_ptr_index_2 => try cg.airStructFieldPtrIndex(inst, 2),
2736 .struct_field_ptr_index_3 => try cg.airStructFieldPtrIndex(inst, 3),
2737
2738 .cmp_eq => try cg.airCmp(inst, .eq),
2739 .cmp_neq => try cg.airCmp(inst, .neq),
2740 .cmp_gt => try cg.airCmp(inst, .gt),
2741 .cmp_gte => try cg.airCmp(inst, .gte),
2742 .cmp_lt => try cg.airCmp(inst, .lt),
2743 .cmp_lte => try cg.airCmp(inst, .lte),
2744 .cmp_vector => try cg.airVectorCmp(inst),
2745
2746 .arg => cg.airArg(),
2747 .alloc => try cg.airAlloc(inst),
2748 // TODO: We probably need to have a special implementation of this for the C abi.
2749 .ret_ptr => try cg.airAlloc(inst),
2750 .block => try cg.airBlock(inst),
2751
2752 .load => try cg.airLoad(inst),
2753 .store, .store_safe => return cg.airStore(inst),
2754
2755 .br => return cg.airBr(inst),
2756 // For now just ignore this instruction. This effectively falls back on the old implementation,
2757 // this doesn't change anything for us.
2758 .repeat => return,
2759 .breakpoint => return,
2760 .cond_br => return cg.airCondBr(inst),
2761 .loop => return cg.airLoop(inst),
2762 .ret => return cg.airRet(inst),
2763 .ret_safe => return cg.airRet(inst), // TODO
2764 .ret_load => return cg.airRetLoad(inst),
2765 .@"try" => try cg.airTry(inst),
2766 .switch_br => return cg.airSwitchBr(inst),
2767 .unreach, .trap => return cg.airUnreach(),
2768
2769 .dbg_empty_stmt => return,
2770 .dbg_stmt => return cg.airDbgStmt(inst),
2771 .dbg_inline_block => try cg.airDbgInlineBlock(inst),
2772 .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => return cg.airDbgVar(inst),
2773
2774 .unwrap_errunion_err => try cg.airErrUnionErr(inst),
2775 .unwrap_errunion_payload => try cg.airErrUnionPayload(inst),
2776 .wrap_errunion_err => try cg.airWrapErrUnionErr(inst),
2777 .wrap_errunion_payload => try cg.airWrapErrUnionPayload(inst),
2778
2779 .is_null => try cg.airIsNull(inst, false, .is_null),
2780 .is_non_null => try cg.airIsNull(inst, false, .is_non_null),
2781 .is_null_ptr => try cg.airIsNull(inst, true, .is_null),
2782 .is_non_null_ptr => try cg.airIsNull(inst, true, .is_non_null),
2783 .is_err => try cg.airIsErr(inst, .is_err),
2784 .is_non_err => try cg.airIsErr(inst, .is_non_err),
2785
2786 .optional_payload => try cg.airUnwrapOptional(inst),
2787 .optional_payload_ptr => try cg.airUnwrapOptionalPtr(inst),
2788 .wrap_optional => try cg.airWrapOptional(inst),
2789
2790 .assembly => try cg.airAssembly(inst),
2791
2792 .call => try cg.airCall(inst, .auto),
2793 .call_always_tail => try cg.airCall(inst, .always_tail),
2794 .call_never_tail => try cg.airCall(inst, .never_tail),
2795 .call_never_inline => try cg.airCall(inst, .never_inline),
2796
2797 .work_item_id => try cg.airWorkItemId(inst),
2798 .work_group_size => try cg.airWorkGroupSize(inst),
2799 .work_group_id => try cg.airWorkGroupId(inst),
2800
2801 // zig fmt: on
2802
2803 else => |tag| return cg.todo("implement AIR tag {s}", .{@tagName(tag)}),
2804 };
2805
2806 const result_id = maybe_result_id orelse return;
2807 try cg.inst_results.putNoClobber(gpa, inst, result_id);
2808}
2809
2810fn airBinOpSimple(cg: *CodeGen, inst: Air.Inst.Index, op: Opcode) !?Id {
2811 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2812 const lhs = try cg.temporary(bin_op.lhs);
2813 const rhs = try cg.temporary(bin_op.rhs);
2814
2815 const result = try cg.buildBinary(op, lhs, rhs);
2816 return try result.materialize(cg);
2817}
2818
2819fn airShift(cg: *CodeGen, inst: Air.Inst.Index, unsigned: Opcode, signed: Opcode) !?Id {
2820 const zcu = cg.module.zcu;
2821 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2822
2823 if (cg.typeOf(bin_op.lhs).isVector(zcu) and !cg.typeOf(bin_op.rhs).isVector(zcu)) {
2824 return cg.fail("vector shift with scalar rhs", .{});
2825 }
2826
2827 const base = try cg.temporary(bin_op.lhs);
2828 const shift = try cg.temporary(bin_op.rhs);
2829
2830 const result_ty = cg.typeOfIndex(inst);
2831
2832 const info = cg.arithmeticTypeInfo(result_ty);
2833 switch (info.class) {
2834 .composite_integer => return cg.todo("shift ops for composite integers", .{}),
2835 .integer, .strange_integer => {},
2836 .float, .bool => unreachable,
2837 }
2838
2839 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that,
2840 // so just manually upcast it if required.
2841
2842 // Note: The sign may differ here between the shift and the base type, in case
2843 // of an arithmetic right shift. SPIR-V still expects the same type,
2844 // so in that case we have to cast convert to signed.
2845 const casted_shift = try cg.buildConvert(base.ty.scalarType(zcu), shift);
2846
2847 const shifted = switch (info.signedness) {
2848 .unsigned => try cg.buildBinary(unsigned, base, casted_shift),
2849 .signed => try cg.buildBinary(signed, base, casted_shift),
2850 };
2851
2852 const result = try cg.normalize(shifted, info);
2853 return try result.materialize(cg);
2854}
2855
2856const MinMax = enum {
2857 min,
2858 max,
2859
2860 pub fn extInstOpcode(
2861 op: MinMax,
2862 target: *const std.Target,
2863 info: ArithmeticTypeInfo,
2864 ) u32 {
2865 return switch (target.os.tag) {
2866 .opencl => @intFromEnum(@as(spec.OpenClOpcode, switch (info.class) {
2867 .float => switch (op) {
2868 .min => .fmin,
2869 .max => .fmax,
2870 },
2871 .integer, .strange_integer, .composite_integer => switch (info.signedness) {
2872 .signed => switch (op) {
2873 .min => .s_min,
2874 .max => .s_max,
2875 },
2876 .unsigned => switch (op) {
2877 .min => .u_min,
2878 .max => .u_max,
2879 },
2880 },
2881 .bool => unreachable,
2882 })),
2883 .vulkan, .opengl => @intFromEnum(@as(spec.GlslOpcode, switch (info.class) {
2884 .float => switch (op) {
2885 .min => .FMin,
2886 .max => .FMax,
2887 },
2888 .integer, .strange_integer, .composite_integer => switch (info.signedness) {
2889 .signed => switch (op) {
2890 .min => .SMin,
2891 .max => .SMax,
2892 },
2893 .unsigned => switch (op) {
2894 .min => .UMin,
2895 .max => .UMax,
2896 },
2897 },
2898 .bool => unreachable,
2899 })),
2900 else => unreachable,
2901 };
2902 }
2903};
2904
2905fn airMinMax(cg: *CodeGen, inst: Air.Inst.Index, op: MinMax) !?Id {
2906 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2907
2908 const lhs = try cg.temporary(bin_op.lhs);
2909 const rhs = try cg.temporary(bin_op.rhs);
2910
2911 const result = try cg.minMax(lhs, rhs, op);
2912 return try result.materialize(cg);
2913}
2914
2915fn minMax(cg: *CodeGen, lhs: Temporary, rhs: Temporary, op: MinMax) !Temporary {
2916 const zcu = cg.module.zcu;
2917 const target = zcu.getTarget();
2918 const info = cg.arithmeticTypeInfo(lhs.ty);
2919
2920 const v = cg.vectorization(.{ lhs, rhs });
2921 const ops = v.components();
2922 const results = cg.module.allocIds(ops);
2923
2924 const op_result_ty = lhs.ty.scalarType(zcu);
2925 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct);
2926 const result_ty = try v.resultType(cg, lhs.ty);
2927
2928 const op_lhs = try v.prepare(cg, lhs);
2929 const op_rhs = try v.prepare(cg, rhs);
2930
2931 const set = try cg.importExtendedSet();
2932 const opcode = op.extInstOpcode(target, info);
2933 for (0..ops) |i| {
2934 try cg.body.emit(cg.module.gpa, .OpExtInst, .{
2935 .id_result_type = op_result_ty_id,
2936 .id_result = results.at(i),
2937 .set = set,
2938 .instruction = .{ .inst = opcode },
2939 .id_ref_4 = &.{ op_lhs.at(i), op_rhs.at(i) },
2940 });
2941 }
2942
2943 return v.finalize(result_ty, results);
2944}
2945
2946/// This function normalizes values to a canonical representation
2947/// after some arithmetic operation. This mostly consists of wrapping
2948/// behavior for strange integers:
2949/// - Unsigned integers are bitwise masked with a mask that only passes
2950/// the valid bits through.
2951/// - Signed integers are also sign extended if they are negative.
2952/// All other values are returned unmodified (this makes strange integer
2953/// wrapping easier to use in generic operations).
2954fn normalize(cg: *CodeGen, value: Temporary, info: ArithmeticTypeInfo) !Temporary {
2955 const zcu = cg.module.zcu;
2956 const ty = value.ty;
2957 switch (info.class) {
2958 .composite_integer, .integer, .bool, .float => return value,
2959 .strange_integer => switch (info.signedness) {
2960 .unsigned => {
2961 const mask_value = if (info.bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1;
2962 const mask_id = try cg.constInt(ty.scalarType(zcu), mask_value);
2963 return try cg.buildBinary(.OpBitwiseAnd, value, Temporary.init(ty.scalarType(zcu), mask_id));
2964 },
2965 .signed => {
2966 // Shift left and right so that we can copy the sight bit that way.
2967 const shift_amt_id = try cg.constInt(ty.scalarType(zcu), info.backing_bits - info.bits);
2968 const shift_amt: Temporary = .init(ty.scalarType(zcu), shift_amt_id);
2969 const left = try cg.buildBinary(.OpShiftLeftLogical, value, shift_amt);
2970 return try cg.buildBinary(.OpShiftRightArithmetic, left, shift_amt);
2971 },
2972 },
2973 }
2974}
2975
2976fn airDivFloor(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
2977 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2978
2979 const lhs = try cg.temporary(bin_op.lhs);
2980 const rhs = try cg.temporary(bin_op.rhs);
2981
2982 const info = cg.arithmeticTypeInfo(lhs.ty);
2983 switch (info.class) {
2984 .composite_integer => unreachable, // TODO
2985 .integer, .strange_integer => {
2986 switch (info.signedness) {
2987 .unsigned => {
2988 const result = try cg.buildBinary(.OpUDiv, lhs, rhs);
2989 return try result.materialize(cg);
2990 },
2991 .signed => {},
2992 }
2993
2994 // For signed integers:
2995 // (a / b) - (a % b != 0 && a < 0 != b < 0);
2996 // There shouldn't be any overflow issues.
2997
2998 const div = try cg.buildBinary(.OpSDiv, lhs, rhs);
2999 const rem = try cg.buildBinary(.OpSRem, lhs, rhs);
3000
3001 const zero: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, 0));
3002
3003 const rem_is_not_zero = try cg.buildCmp(.OpINotEqual, rem, zero);
3004
3005 const result_negative = try cg.buildCmp(
3006 .OpLogicalNotEqual,
3007 try cg.buildCmp(.OpSLessThan, lhs, zero),
3008 try cg.buildCmp(.OpSLessThan, rhs, zero),
3009 );
3010 const rem_is_not_zero_and_result_is_negative = try cg.buildBinary(
3011 .OpLogicalAnd,
3012 rem_is_not_zero,
3013 result_negative,
3014 );
3015
3016 const result = try cg.buildBinary(
3017 .OpISub,
3018 div,
3019 try cg.intFromBool2(rem_is_not_zero_and_result_is_negative, div.ty),
3020 );
3021
3022 return try result.materialize(cg);
3023 },
3024 .float => {
3025 const div = try cg.buildBinary(.OpFDiv, lhs, rhs);
3026 const result = try cg.buildUnary(.floor, div);
3027 return try result.materialize(cg);
3028 },
3029 .bool => unreachable,
3030 }
3031}
3032
3033fn airDivTrunc(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3034 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3035
3036 const lhs = try cg.temporary(bin_op.lhs);
3037 const rhs = try cg.temporary(bin_op.rhs);
3038
3039 const info = cg.arithmeticTypeInfo(lhs.ty);
3040 switch (info.class) {
3041 .composite_integer => unreachable, // TODO
3042 .integer, .strange_integer => switch (info.signedness) {
3043 .unsigned => {
3044 const result = try cg.buildBinary(.OpUDiv, lhs, rhs);
3045 return try result.materialize(cg);
3046 },
3047 .signed => {
3048 const result = try cg.buildBinary(.OpSDiv, lhs, rhs);
3049 return try result.materialize(cg);
3050 },
3051 },
3052 .float => {
3053 const div = try cg.buildBinary(.OpFDiv, lhs, rhs);
3054 const result = try cg.buildUnary(.trunc, div);
3055 return try result.materialize(cg);
3056 },
3057 .bool => unreachable,
3058 }
3059}
3060
3061fn airUnOpSimple(cg: *CodeGen, inst: Air.Inst.Index, op: UnaryOp) !?Id {
3062 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
3063 const operand = try cg.temporary(un_op);
3064 const result = try cg.buildUnary(op, operand);
3065 return try result.materialize(cg);
3066}
3067
3068fn airArithOp(
3069 cg: *CodeGen,
3070 inst: Air.Inst.Index,
3071 comptime fop: Opcode,
3072 comptime sop: Opcode,
3073 comptime uop: Opcode,
3074) !?Id {
3075 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3076
3077 const lhs = try cg.temporary(bin_op.lhs);
3078 const rhs = try cg.temporary(bin_op.rhs);
3079
3080 const info = cg.arithmeticTypeInfo(lhs.ty);
3081
3082 const result = switch (info.class) {
3083 .composite_integer => unreachable, // TODO
3084 .integer, .strange_integer => switch (info.signedness) {
3085 .signed => try cg.buildBinary(sop, lhs, rhs),
3086 .unsigned => try cg.buildBinary(uop, lhs, rhs),
3087 },
3088 .float => try cg.buildBinary(fop, lhs, rhs),
3089 .bool => unreachable,
3090 };
3091
3092 return try result.materialize(cg);
3093}
3094
3095fn airAbs(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3096 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3097 const operand = try cg.temporary(ty_op.operand);
3098 // Note: operand_ty may be signed, while ty is always unsigned!
3099 const result_ty = cg.typeOfIndex(inst);
3100 const result = try cg.abs(result_ty, operand);
3101 return try result.materialize(cg);
3102}
3103
3104fn abs(cg: *CodeGen, result_ty: Type, value: Temporary) !Temporary {
3105 const zcu = cg.module.zcu;
3106 const target = cg.module.zcu.getTarget();
3107 const operand_info = cg.arithmeticTypeInfo(value.ty);
3108
3109 switch (operand_info.class) {
3110 .float => return try cg.buildUnary(.f_abs, value),
3111 .integer, .strange_integer => {
3112 const abs_value = try cg.buildUnary(.i_abs, value);
3113
3114 switch (target.os.tag) {
3115 .vulkan, .opengl => {
3116 if (value.ty.intInfo(zcu).signedness == .signed) {
3117 return cg.todo("perform bitcast after @abs", .{});
3118 }
3119 },
3120 else => {},
3121 }
3122
3123 return try cg.normalize(abs_value, cg.arithmeticTypeInfo(result_ty));
3124 },
3125 .composite_integer => unreachable, // TODO
3126 .bool => unreachable,
3127 }
3128}
3129
3130fn airAddSubOverflow(
3131 cg: *CodeGen,
3132 inst: Air.Inst.Index,
3133 comptime add: Opcode,
3134 u_opcode: Opcode,
3135 s_opcode: Opcode,
3136) !?Id {
3137 _ = s_opcode;
3138 // Note: OpIAddCarry and OpISubBorrow are not really useful here: For unsigned numbers,
3139 // there is in both cases only one extra operation required. For signed operations,
3140 // the overflow bit is set then going from 0x80.. to 0x00.., but this doesn't actually
3141 // normally set a carry bit. So the SPIR-V overflow operations are not particularly
3142 // useful here.
3143
3144 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3145 const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data;
3146
3147 const lhs = try cg.temporary(extra.lhs);
3148 const rhs = try cg.temporary(extra.rhs);
3149
3150 const result_ty = cg.typeOfIndex(inst);
3151
3152 const info = cg.arithmeticTypeInfo(lhs.ty);
3153 switch (info.class) {
3154 .composite_integer => unreachable, // TODO
3155 .strange_integer, .integer => {},
3156 .float, .bool => unreachable,
3157 }
3158
3159 const sum = try cg.buildBinary(add, lhs, rhs);
3160 const result = try cg.normalize(sum, info);
3161
3162 const overflowed = switch (info.signedness) {
3163 // Overflow happened if the result is smaller than either of the operands. It doesn't matter which.
3164 // For subtraction the conditions need to be swapped.
3165 .unsigned => try cg.buildCmp(u_opcode, result, lhs),
3166 // For signed operations, we check the signs of the operands and the result.
3167 .signed => blk: {
3168 // Signed overflow detection using the sign bits of the operands and the result.
3169 // For addition (a + b), overflow occurs if the operands have the same sign
3170 // and the result's sign is different from the operands' sign.
3171 // (sign(a) == sign(b)) && (sign(a) != sign(result))
3172 // For subtraction (a - b), overflow occurs if the operands have different signs
3173 // and the result's sign is different from the minuend's (a's) sign.
3174 // (sign(a) != sign(b)) && (sign(a) != sign(result))
3175 const zero: Temporary = .init(rhs.ty, try cg.constInt(rhs.ty, 0));
3176
3177 const lhs_is_neg = try cg.buildCmp(.OpSLessThan, lhs, zero);
3178 const rhs_is_neg = try cg.buildCmp(.OpSLessThan, rhs, zero);
3179 const result_is_neg = try cg.buildCmp(.OpSLessThan, result, zero);
3180
3181 const signs_match = try cg.buildCmp(.OpLogicalEqual, lhs_is_neg, rhs_is_neg);
3182 const result_sign_differs = try cg.buildCmp(.OpLogicalNotEqual, lhs_is_neg, result_is_neg);
3183
3184 const overflow_condition = if (add == .OpIAdd)
3185 signs_match
3186 else // .OpISub
3187 try cg.buildUnary(.l_not, signs_match);
3188
3189 break :blk try cg.buildCmp(.OpLogicalAnd, overflow_condition, result_sign_differs);
3190 },
3191 };
3192
3193 const ov = try cg.intFromBool(overflowed);
3194
3195 const result_ty_id = try cg.resolveType(result_ty, .direct);
3196 return try cg.constructComposite(result_ty_id, &.{ try result.materialize(cg), try ov.materialize(cg) });
3197}
3198
3199fn airMulOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3200 const pt = cg.pt;
3201
3202 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3203 const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data;
3204
3205 const lhs = try cg.temporary(extra.lhs);
3206 const rhs = try cg.temporary(extra.rhs);
3207
3208 const result_ty = cg.typeOfIndex(inst);
3209
3210 const info = cg.arithmeticTypeInfo(lhs.ty);
3211 switch (info.class) {
3212 .composite_integer => unreachable, // TODO
3213 .strange_integer, .integer => {},
3214 .float, .bool => unreachable,
3215 }
3216
3217 // There are 3 cases which we have to deal with:
3218 // - If info.bits < 32 / 2, we will upcast to 32 and check the higher bits
3219 // - If info.bits > 32 / 2, we have to use extended multiplication
3220 // - Additionally, if info.bits != 32, we'll have to check the high bits
3221 // of the result too.
3222
3223 const largest_int_bits = cg.largestSupportedIntBits();
3224 // If non-null, the number of bits that the multiplication should be performed in. If
3225 // null, we have to use wide multiplication.
3226 const maybe_op_ty_bits: ?u16 = switch (info.bits) {
3227 0 => unreachable,
3228 1...16 => 32,
3229 17...32 => if (largest_int_bits > 32) 64 else null, // Upcast if we can.
3230 33...64 => null, // Always use wide multiplication.
3231 else => unreachable, // TODO: Composite integers
3232 };
3233
3234 const result, const overflowed = switch (info.signedness) {
3235 .unsigned => blk: {
3236 if (maybe_op_ty_bits) |op_ty_bits| {
3237 const op_ty = try pt.intType(.unsigned, op_ty_bits);
3238 const casted_lhs = try cg.buildConvert(op_ty, lhs);
3239 const casted_rhs = try cg.buildConvert(op_ty, rhs);
3240
3241 const full_result = try cg.buildBinary(.OpIMul, casted_lhs, casted_rhs);
3242
3243 const low_bits = try cg.buildConvert(lhs.ty, full_result);
3244 const result = try cg.normalize(low_bits, info);
3245
3246 // Shift the result bits away to get the overflow bits.
3247 const shift: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, info.bits));
3248 const overflow = try cg.buildBinary(.OpShiftRightLogical, full_result, shift);
3249
3250 // Directly check if its zero in the op_ty without converting first.
3251 const zero: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, 0));
3252 const overflowed = try cg.buildCmp(.OpINotEqual, zero, overflow);
3253
3254 break :blk .{ result, overflowed };
3255 }
3256
3257 const low_bits, const high_bits = try cg.buildWideMul(.unsigned, lhs, rhs);
3258
3259 // Truncate the result, if required.
3260 const result = try cg.normalize(low_bits, info);
3261
3262 // Overflow happened if the high-bits of the result are non-zero OR if the
3263 // high bits of the low word of the result (those outside the range of the
3264 // int) are nonzero.
3265 const zero: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, 0));
3266 const high_overflowed = try cg.buildCmp(.OpINotEqual, zero, high_bits);
3267
3268 // If no overflow bits in low_bits, no extra work needs to be done.
3269 if (info.backing_bits == info.bits) break :blk .{ result, high_overflowed };
3270
3271 // Shift the result bits away to get the overflow bits.
3272 const shift: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, info.bits));
3273 const low_overflow = try cg.buildBinary(.OpShiftRightLogical, low_bits, shift);
3274 const low_overflowed = try cg.buildCmp(.OpINotEqual, zero, low_overflow);
3275
3276 const overflowed = try cg.buildCmp(.OpLogicalOr, low_overflowed, high_overflowed);
3277
3278 break :blk .{ result, overflowed };
3279 },
3280 .signed => blk: {
3281 // - lhs >= 0, rhxs >= 0: expect positive; overflow should be 0
3282 // - lhs == 0 : expect positive; overflow should be 0
3283 // - rhs == 0: expect positive; overflow should be 0
3284 // - lhs > 0, rhs < 0: expect negative; overflow should be -1
3285 // - lhs < 0, rhs > 0: expect negative; overflow should be -1
3286 // - lhs <= 0, rhs <= 0: expect positive; overflow should be 0
3287 // ------
3288 // overflow should be -1 when
3289 // (lhs > 0 && rhs < 0) || (lhs < 0 && rhs > 0)
3290
3291 const zero: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, 0));
3292 const lhs_negative = try cg.buildCmp(.OpSLessThan, lhs, zero);
3293 const rhs_negative = try cg.buildCmp(.OpSLessThan, rhs, zero);
3294 const lhs_positive = try cg.buildCmp(.OpSGreaterThan, lhs, zero);
3295 const rhs_positive = try cg.buildCmp(.OpSGreaterThan, rhs, zero);
3296
3297 // Set to `true` if we expect -1.
3298 const expected_overflow_bit = try cg.buildBinary(
3299 .OpLogicalOr,
3300 try cg.buildCmp(.OpLogicalAnd, lhs_positive, rhs_negative),
3301 try cg.buildCmp(.OpLogicalAnd, lhs_negative, rhs_positive),
3302 );
3303
3304 if (maybe_op_ty_bits) |op_ty_bits| {
3305 const op_ty = try pt.intType(.signed, op_ty_bits);
3306 // Assume normalized; sign bit is set. We want a sign extend.
3307 const casted_lhs = try cg.buildConvert(op_ty, lhs);
3308 const casted_rhs = try cg.buildConvert(op_ty, rhs);
3309
3310 const full_result = try cg.buildBinary(.OpIMul, casted_lhs, casted_rhs);
3311
3312 // Truncate to the result type.
3313 const low_bits = try cg.buildConvert(lhs.ty, full_result);
3314 const result = try cg.normalize(low_bits, info);
3315
3316 // Now, we need to check the overflow bits AND the sign
3317 // bit for the expected overflow bits.
3318 // To do that, shift out everything bit the sign bit and
3319 // then check what remains.
3320 const shift: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, info.bits - 1));
3321 // Use SRA so that any sign bits are duplicated. Now we can just check if ALL bits are set
3322 // for negative cases.
3323 const overflow = try cg.buildBinary(.OpShiftRightArithmetic, full_result, shift);
3324
3325 const long_all_set: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, -1));
3326 const long_zero: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, 0));
3327 const mask = try cg.buildSelect(expected_overflow_bit, long_all_set, long_zero);
3328
3329 const overflowed = try cg.buildCmp(.OpINotEqual, mask, overflow);
3330
3331 break :blk .{ result, overflowed };
3332 }
3333
3334 const low_bits, const high_bits = try cg.buildWideMul(.signed, lhs, rhs);
3335
3336 // Truncate result if required.
3337 const result = try cg.normalize(low_bits, info);
3338
3339 const all_set: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, -1));
3340 const mask = try cg.buildSelect(expected_overflow_bit, all_set, zero);
3341
3342 // Like with unsigned, overflow happened if high_bits are not the ones we expect,
3343 // and we also need to check some ones from the low bits.
3344
3345 const high_overflowed = try cg.buildCmp(.OpINotEqual, mask, high_bits);
3346
3347 // If no overflow bits in low_bits, no extra work needs to be done.
3348 // Careful, we still have to check the sign bit, so this branch
3349 // only goes for i33 and such.
3350 if (info.backing_bits == info.bits + 1) break :blk .{ result, high_overflowed };
3351
3352 // Shift the result bits away to get the overflow bits.
3353 const shift: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, info.bits - 1));
3354 // Use SRA so that any sign bits are duplicated. Now we can just check if ALL bits are set
3355 // for negative cases.
3356 const low_overflow = try cg.buildBinary(.OpShiftRightArithmetic, low_bits, shift);
3357 const low_overflowed = try cg.buildCmp(.OpINotEqual, mask, low_overflow);
3358
3359 const overflowed = try cg.buildCmp(.OpLogicalOr, low_overflowed, high_overflowed);
3360
3361 break :blk .{ result, overflowed };
3362 },
3363 };
3364
3365 const ov = try cg.intFromBool(overflowed);
3366
3367 const result_ty_id = try cg.resolveType(result_ty, .direct);
3368 return try cg.constructComposite(result_ty_id, &.{ try result.materialize(cg), try ov.materialize(cg) });
3369}
3370
3371fn airShlOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3372 const zcu = cg.module.zcu;
3373
3374 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3375 const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data;
3376
3377 if (cg.typeOf(extra.lhs).isVector(zcu) and !cg.typeOf(extra.rhs).isVector(zcu)) {
3378 return cg.fail("vector shift with scalar rhs", .{});
3379 }
3380
3381 const base = try cg.temporary(extra.lhs);
3382 const shift = try cg.temporary(extra.rhs);
3383
3384 const result_ty = cg.typeOfIndex(inst);
3385
3386 const info = cg.arithmeticTypeInfo(base.ty);
3387 switch (info.class) {
3388 .composite_integer => unreachable, // TODO
3389 .integer, .strange_integer => {},
3390 .float, .bool => unreachable,
3391 }
3392
3393 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that,
3394 // so just manually upcast it if required.
3395 const casted_shift = try cg.buildConvert(base.ty.scalarType(zcu), shift);
3396
3397 const left = try cg.buildBinary(.OpShiftLeftLogical, base, casted_shift);
3398 const result = try cg.normalize(left, info);
3399
3400 const right = switch (info.signedness) {
3401 .unsigned => try cg.buildBinary(.OpShiftRightLogical, result, casted_shift),
3402 .signed => try cg.buildBinary(.OpShiftRightArithmetic, result, casted_shift),
3403 };
3404
3405 const overflowed = try cg.buildCmp(.OpINotEqual, base, right);
3406 const ov = try cg.intFromBool(overflowed);
3407
3408 const result_ty_id = try cg.resolveType(result_ty, .direct);
3409 return try cg.constructComposite(result_ty_id, &.{ try result.materialize(cg), try ov.materialize(cg) });
3410}
3411
3412fn airMulAdd(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3413 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
3414 const extra = cg.air.extraData(Air.Bin, pl_op.payload).data;
3415
3416 const a = try cg.temporary(extra.lhs);
3417 const b = try cg.temporary(extra.rhs);
3418 const c = try cg.temporary(pl_op.operand);
3419
3420 const result_ty = cg.typeOfIndex(inst);
3421 const info = cg.arithmeticTypeInfo(result_ty);
3422 assert(info.class == .float); // .mul_add is only emitted for floats
3423
3424 const result = try cg.buildFma(a, b, c);
3425 return try result.materialize(cg);
3426}
3427
3428fn airClzCtz(cg: *CodeGen, inst: Air.Inst.Index, op: UnaryOp) !?Id {
3429 if (cg.liveness.isUnused(inst)) return null;
3430
3431 const zcu = cg.module.zcu;
3432 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3433 const operand = try cg.temporary(ty_op.operand);
3434
3435 const scalar_result_ty = cg.typeOfIndex(inst).scalarType(zcu);
3436
3437 const info = cg.arithmeticTypeInfo(operand.ty);
3438 switch (info.class) {
3439 .composite_integer => unreachable, // TODO
3440 .integer, .strange_integer => {},
3441 .float, .bool => unreachable,
3442 }
3443
3444 const count = try cg.buildUnary(op, operand);
3445
3446 // Result of OpenCL ctz/clz returns operand.ty, and we want result_ty.
3447 // result_ty is always large enough to hold the result, so we might have to down
3448 // cast it.
3449 const result = try cg.buildConvert(scalar_result_ty, count);
3450 return try result.materialize(cg);
3451}
3452
3453fn airSelect(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3454 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
3455 const extra = cg.air.extraData(Air.Bin, pl_op.payload).data;
3456 const pred = try cg.temporary(pl_op.operand);
3457 const a = try cg.temporary(extra.lhs);
3458 const b = try cg.temporary(extra.rhs);
3459
3460 const result = try cg.buildSelect(pred, a, b);
3461 return try result.materialize(cg);
3462}
3463
3464fn airSplat(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3465 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3466
3467 const operand_id = try cg.resolve(ty_op.operand);
3468 const result_ty = cg.typeOfIndex(inst);
3469
3470 return try cg.constructCompositeSplat(result_ty, operand_id);
3471}
3472
3473fn airReduce(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3474 const zcu = cg.module.zcu;
3475 const reduce = cg.air.instructions.items(.data)[@intFromEnum(inst)].reduce;
3476 const operand = try cg.resolve(reduce.operand);
3477 const operand_ty = cg.typeOf(reduce.operand);
3478 const scalar_ty = operand_ty.scalarType(zcu);
3479 const scalar_ty_id = try cg.resolveType(scalar_ty, .direct);
3480 const info = cg.arithmeticTypeInfo(operand_ty);
3481 const len = operand_ty.vectorLen(zcu);
3482 const first = try cg.extractVectorComponent(scalar_ty, operand, 0);
3483
3484 switch (reduce.operation) {
3485 .Min, .Max => |op| {
3486 var result: Temporary = .init(scalar_ty, first);
3487 const cmp_op: MinMax = switch (op) {
3488 .Max => .max,
3489 .Min => .min,
3490 else => unreachable,
3491 };
3492 for (1..len) |i| {
3493 const lhs = result;
3494 const rhs_id = try cg.extractVectorComponent(scalar_ty, operand, @intCast(i));
3495 const rhs: Temporary = .init(scalar_ty, rhs_id);
3496
3497 result = try cg.minMax(lhs, rhs, cmp_op);
3498 }
3499
3500 return try result.materialize(cg);
3501 },
3502 else => {},
3503 }
3504
3505 var result_id = first;
3506
3507 const opcode: Opcode = switch (info.class) {
3508 .bool => switch (reduce.operation) {
3509 .And => .OpLogicalAnd,
3510 .Or => .OpLogicalOr,
3511 .Xor => .OpLogicalNotEqual,
3512 else => unreachable,
3513 },
3514 .strange_integer, .integer => switch (reduce.operation) {
3515 .And => .OpBitwiseAnd,
3516 .Or => .OpBitwiseOr,
3517 .Xor => .OpBitwiseXor,
3518 .Add => .OpIAdd,
3519 .Mul => .OpIMul,
3520 else => unreachable,
3521 },
3522 .float => switch (reduce.operation) {
3523 .Add => .OpFAdd,
3524 .Mul => .OpFMul,
3525 else => unreachable,
3526 },
3527 .composite_integer => unreachable, // TODO
3528 };
3529
3530 for (1..len) |i| {
3531 const lhs = result_id;
3532 const rhs = try cg.extractVectorComponent(scalar_ty, operand, @intCast(i));
3533 result_id = cg.module.allocId();
3534
3535 try cg.body.emitRaw(cg.module.gpa, opcode, 4);
3536 cg.body.writeOperand(Id, scalar_ty_id);
3537 cg.body.writeOperand(Id, result_id);
3538 cg.body.writeOperand(Id, lhs);
3539 cg.body.writeOperand(Id, rhs);
3540 }
3541
3542 return result_id;
3543}
3544
3545fn airShuffleOne(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3546 const zcu = cg.module.zcu;
3547 const gpa = zcu.gpa;
3548
3549 const unwrapped = cg.air.unwrapShuffleOne(zcu, inst);
3550 const mask = unwrapped.mask;
3551 const result_ty = unwrapped.result_ty;
3552 const elem_ty = result_ty.childType(zcu);
3553 const operand = try cg.resolve(unwrapped.operand);
3554
3555 const scratch_top = cg.id_scratch.items.len;
3556 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
3557 const constituents = try cg.id_scratch.addManyAsSlice(gpa, mask.len);
3558
3559 for (constituents, mask) |*id, mask_elem| {
3560 id.* = switch (mask_elem.unwrap()) {
3561 .elem => |idx| try cg.extractVectorComponent(elem_ty, operand, idx),
3562 .value => |val| try cg.constant(elem_ty, .fromInterned(val), .direct),
3563 };
3564 }
3565
3566 const result_ty_id = try cg.resolveType(result_ty, .direct);
3567 return try cg.constructComposite(result_ty_id, constituents);
3568}
3569
3570fn airShuffleTwo(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3571 const zcu = cg.module.zcu;
3572 const gpa = zcu.gpa;
3573
3574 const unwrapped = cg.air.unwrapShuffleTwo(zcu, inst);
3575 const mask = unwrapped.mask;
3576 const result_ty = unwrapped.result_ty;
3577 const elem_ty = result_ty.childType(zcu);
3578 const elem_ty_id = try cg.resolveType(elem_ty, .direct);
3579 const operand_a = try cg.resolve(unwrapped.operand_a);
3580 const operand_b = try cg.resolve(unwrapped.operand_b);
3581
3582 const scratch_top = cg.id_scratch.items.len;
3583 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
3584 const constituents = try cg.id_scratch.addManyAsSlice(gpa, mask.len);
3585
3586 for (constituents, mask) |*id, mask_elem| {
3587 id.* = switch (mask_elem.unwrap()) {
3588 .a_elem => |idx| try cg.extractVectorComponent(elem_ty, operand_a, idx),
3589 .b_elem => |idx| try cg.extractVectorComponent(elem_ty, operand_b, idx),
3590 .undef => try cg.module.constUndef(elem_ty_id),
3591 };
3592 }
3593
3594 const result_ty_id = try cg.resolveType(result_ty, .direct);
3595 return try cg.constructComposite(result_ty_id, constituents);
3596}
3597
3598fn accessChainId(
3599 cg: *CodeGen,
3600 result_ty_id: Id,
3601 base: Id,
3602 indices: []const Id,
3603) !Id {
3604 const result_id = cg.module.allocId();
3605 try cg.body.emit(cg.module.gpa, .OpInBoundsAccessChain, .{
3606 .id_result_type = result_ty_id,
3607 .id_result = result_id,
3608 .base = base,
3609 .indexes = indices,
3610 });
3611 return result_id;
3612}
3613
3614/// AccessChain is essentially PtrAccessChain with 0 as initial argument. The effective
3615/// difference lies in whether the resulting type of the first dereference will be the
3616/// same as that of the base pointer, or that of a dereferenced base pointer. AccessChain
3617/// is the latter and PtrAccessChain is the former.
3618fn accessChain(
3619 cg: *CodeGen,
3620 result_ty_id: Id,
3621 base: Id,
3622 indices: []const u32,
3623) !Id {
3624 const gpa = cg.module.gpa;
3625 const scratch_top = cg.id_scratch.items.len;
3626 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
3627 const ids = try cg.id_scratch.addManyAsSlice(gpa, indices.len);
3628 for (indices, ids) |index, *id| {
3629 id.* = try cg.constInt(.u32, index);
3630 }
3631 return try cg.accessChainId(result_ty_id, base, ids);
3632}
3633
3634fn ptrAccessChain(
3635 cg: *CodeGen,
3636 result_ty_id: Id,
3637 base: Id,
3638 element: Id,
3639 indices: []const u32,
3640) !Id {
3641 const gpa = cg.module.gpa;
3642 const target = cg.module.zcu.getTarget();
3643
3644 const scratch_top = cg.id_scratch.items.len;
3645 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
3646 const ids = try cg.id_scratch.addManyAsSlice(gpa, indices.len);
3647 for (indices, ids) |index, *id| {
3648 id.* = try cg.constInt(.u32, index);
3649 }
3650
3651 const result_id = cg.module.allocId();
3652 switch (target.os.tag) {
3653 .opencl, .amdhsa => {
3654 try cg.body.emit(gpa, .OpInBoundsPtrAccessChain, .{
3655 .id_result_type = result_ty_id,
3656 .id_result = result_id,
3657 .base = base,
3658 .element = element,
3659 .indexes = ids,
3660 });
3661 },
3662 .vulkan, .opengl => {
3663 try cg.body.emit(gpa, .OpPtrAccessChain, .{
3664 .id_result_type = result_ty_id,
3665 .id_result = result_id,
3666 .base = base,
3667 .element = element,
3668 .indexes = ids,
3669 });
3670 },
3671 else => unreachable,
3672 }
3673 return result_id;
3674}
3675
3676fn ptrAdd(cg: *CodeGen, result_ty: Type, ptr_ty: Type, ptr_id: Id, offset_id: Id) !Id {
3677 const zcu = cg.module.zcu;
3678 const result_ty_id = try cg.resolveType(result_ty, .direct);
3679
3680 switch (ptr_ty.ptrSize(zcu)) {
3681 .one => {
3682 // Pointer to array
3683 // TODO: Is this correct?
3684 return try cg.accessChainId(result_ty_id, ptr_id, &.{offset_id});
3685 },
3686 .c, .many => {
3687 return try cg.ptrAccessChain(result_ty_id, ptr_id, offset_id, &.{});
3688 },
3689 .slice => {
3690 // TODO: This is probably incorrect. A slice should be returned here, though this is what llvm does.
3691 const slice_ptr_id = try cg.extractField(result_ty, ptr_id, 0);
3692 return try cg.ptrAccessChain(result_ty_id, slice_ptr_id, offset_id, &.{});
3693 },
3694 }
3695}
3696
3697fn airPtrAdd(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3698 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3699 const bin_op = cg.air.extraData(Air.Bin, ty_pl.payload).data;
3700 const ptr_id = try cg.resolve(bin_op.lhs);
3701 const offset_id = try cg.resolve(bin_op.rhs);
3702 const ptr_ty = cg.typeOf(bin_op.lhs);
3703 const result_ty = cg.typeOfIndex(inst);
3704
3705 return try cg.ptrAdd(result_ty, ptr_ty, ptr_id, offset_id);
3706}
3707
3708fn airPtrSub(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3709 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3710 const bin_op = cg.air.extraData(Air.Bin, ty_pl.payload).data;
3711 const ptr_id = try cg.resolve(bin_op.lhs);
3712 const ptr_ty = cg.typeOf(bin_op.lhs);
3713 const offset_id = try cg.resolve(bin_op.rhs);
3714 const offset_ty = cg.typeOf(bin_op.rhs);
3715 const offset_ty_id = try cg.resolveType(offset_ty, .direct);
3716 const result_ty = cg.typeOfIndex(inst);
3717
3718 const negative_offset_id = cg.module.allocId();
3719 try cg.body.emit(cg.module.gpa, .OpSNegate, .{
3720 .id_result_type = offset_ty_id,
3721 .id_result = negative_offset_id,
3722 .operand = offset_id,
3723 });
3724 return try cg.ptrAdd(result_ty, ptr_ty, ptr_id, negative_offset_id);
3725}
3726
3727fn cmp(
3728 cg: *CodeGen,
3729 op: std.math.CompareOperator,
3730 lhs: Temporary,
3731 rhs: Temporary,
3732) !Temporary {
3733 const gpa = cg.module.gpa;
3734 const pt = cg.pt;
3735 const zcu = cg.module.zcu;
3736 const ip = &zcu.intern_pool;
3737 const scalar_ty = lhs.ty.scalarType(zcu);
3738 const is_vector = lhs.ty.isVector(zcu);
3739
3740 switch (scalar_ty.zigTypeTag(zcu)) {
3741 .int, .bool, .float => {},
3742 .@"enum" => {
3743 assert(!is_vector);
3744 const ty = lhs.ty.intTagType(zcu);
3745 return try cg.cmp(op, lhs.pun(ty), rhs.pun(ty));
3746 },
3747 .@"struct" => {
3748 const struct_ty = zcu.typeToPackedStruct(scalar_ty).?;
3749 const ty: Type = .fromInterned(struct_ty.backingIntTypeUnordered(ip));
3750 return try cg.cmp(op, lhs.pun(ty), rhs.pun(ty));
3751 },
3752 .error_set => {
3753 assert(!is_vector);
3754 const err_int_ty = try pt.errorIntType();
3755 return try cg.cmp(op, lhs.pun(err_int_ty), rhs.pun(err_int_ty));
3756 },
3757 .pointer => {
3758 assert(!is_vector);
3759 // Note that while SPIR-V offers OpPtrEqual and OpPtrNotEqual, they are
3760 // currently not implemented in the SPIR-V LLVM translator. Thus, we emit these using
3761 // OpConvertPtrToU...
3762
3763 const usize_ty_id = try cg.resolveType(.usize, .direct);
3764
3765 const lhs_int_id = cg.module.allocId();
3766 try cg.body.emit(gpa, .OpConvertPtrToU, .{
3767 .id_result_type = usize_ty_id,
3768 .id_result = lhs_int_id,
3769 .pointer = try lhs.materialize(cg),
3770 });
3771
3772 const rhs_int_id = cg.module.allocId();
3773 try cg.body.emit(gpa, .OpConvertPtrToU, .{
3774 .id_result_type = usize_ty_id,
3775 .id_result = rhs_int_id,
3776 .pointer = try rhs.materialize(cg),
3777 });
3778
3779 const lhs_int: Temporary = .init(.usize, lhs_int_id);
3780 const rhs_int: Temporary = .init(.usize, rhs_int_id);
3781 return try cg.cmp(op, lhs_int, rhs_int);
3782 },
3783 .optional => {
3784 assert(!is_vector);
3785
3786 const ty = lhs.ty;
3787
3788 const payload_ty = ty.optionalChild(zcu);
3789 if (ty.optionalReprIsPayload(zcu)) {
3790 assert(payload_ty.hasRuntimeBitsIgnoreComptime(zcu));
3791 assert(!payload_ty.isSlice(zcu));
3792
3793 return try cg.cmp(op, lhs.pun(payload_ty), rhs.pun(payload_ty));
3794 }
3795
3796 const lhs_id = try lhs.materialize(cg);
3797 const rhs_id = try rhs.materialize(cg);
3798
3799 const lhs_valid_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu))
3800 try cg.extractField(.bool, lhs_id, 1)
3801 else
3802 try cg.convertToDirect(.bool, lhs_id);
3803
3804 const rhs_valid_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu))
3805 try cg.extractField(.bool, rhs_id, 1)
3806 else
3807 try cg.convertToDirect(.bool, rhs_id);
3808
3809 const lhs_valid: Temporary = .init(.bool, lhs_valid_id);
3810 const rhs_valid: Temporary = .init(.bool, rhs_valid_id);
3811
3812 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
3813 return try cg.cmp(op, lhs_valid, rhs_valid);
3814 }
3815
3816 // a = lhs_valid
3817 // b = rhs_valid
3818 // c = lhs_pl == rhs_pl
3819 //
3820 // For op == .eq we have:
3821 // a == b && a -> c
3822 // = a == b && (!a || c)
3823 //
3824 // For op == .neq we have
3825 // a == b && a -> c
3826 // = !(a == b && a -> c)
3827 // = a != b || !(a -> c
3828 // = a != b || !(!a || c)
3829 // = a != b || a && !c
3830
3831 const lhs_pl_id = try cg.extractField(payload_ty, lhs_id, 0);
3832 const rhs_pl_id = try cg.extractField(payload_ty, rhs_id, 0);
3833
3834 const lhs_pl: Temporary = .init(payload_ty, lhs_pl_id);
3835 const rhs_pl: Temporary = .init(payload_ty, rhs_pl_id);
3836
3837 return switch (op) {
3838 .eq => try cg.buildBinary(
3839 .OpLogicalAnd,
3840 try cg.cmp(.eq, lhs_valid, rhs_valid),
3841 try cg.buildBinary(
3842 .OpLogicalOr,
3843 try cg.buildUnary(.l_not, lhs_valid),
3844 try cg.cmp(.eq, lhs_pl, rhs_pl),
3845 ),
3846 ),
3847 .neq => try cg.buildBinary(
3848 .OpLogicalOr,
3849 try cg.cmp(.neq, lhs_valid, rhs_valid),
3850 try cg.buildBinary(
3851 .OpLogicalAnd,
3852 lhs_valid,
3853 try cg.cmp(.neq, lhs_pl, rhs_pl),
3854 ),
3855 ),
3856 else => unreachable,
3857 };
3858 },
3859 else => |ty| return cg.todo("implement cmp operation for '{s}' type", .{@tagName(ty)}),
3860 }
3861
3862 const info = cg.arithmeticTypeInfo(scalar_ty);
3863 const pred: Opcode = switch (info.class) {
3864 .composite_integer => unreachable, // TODO
3865 .float => switch (op) {
3866 .eq => .OpFOrdEqual,
3867 .neq => .OpFUnordNotEqual,
3868 .lt => .OpFOrdLessThan,
3869 .lte => .OpFOrdLessThanEqual,
3870 .gt => .OpFOrdGreaterThan,
3871 .gte => .OpFOrdGreaterThanEqual,
3872 },
3873 .bool => switch (op) {
3874 .eq => .OpLogicalEqual,
3875 .neq => .OpLogicalNotEqual,
3876 else => unreachable,
3877 },
3878 .integer, .strange_integer => switch (info.signedness) {
3879 .signed => switch (op) {
3880 .eq => .OpIEqual,
3881 .neq => .OpINotEqual,
3882 .lt => .OpSLessThan,
3883 .lte => .OpSLessThanEqual,
3884 .gt => .OpSGreaterThan,
3885 .gte => .OpSGreaterThanEqual,
3886 },
3887 .unsigned => switch (op) {
3888 .eq => .OpIEqual,
3889 .neq => .OpINotEqual,
3890 .lt => .OpULessThan,
3891 .lte => .OpULessThanEqual,
3892 .gt => .OpUGreaterThan,
3893 .gte => .OpUGreaterThanEqual,
3894 },
3895 },
3896 };
3897
3898 return try cg.buildCmp(pred, lhs, rhs);
3899}
3900
3901fn airCmp(
3902 cg: *CodeGen,
3903 inst: Air.Inst.Index,
3904 comptime op: std.math.CompareOperator,
3905) !?Id {
3906 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3907 const lhs = try cg.temporary(bin_op.lhs);
3908 const rhs = try cg.temporary(bin_op.rhs);
3909
3910 const result = try cg.cmp(op, lhs, rhs);
3911 return try result.materialize(cg);
3912}
3913
3914fn airVectorCmp(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3915 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3916 const vec_cmp = cg.air.extraData(Air.VectorCmp, ty_pl.payload).data;
3917 const lhs = try cg.temporary(vec_cmp.lhs);
3918 const rhs = try cg.temporary(vec_cmp.rhs);
3919 const op = vec_cmp.compareOperator();
3920
3921 const result = try cg.cmp(op, lhs, rhs);
3922 return try result.materialize(cg);
3923}
3924
3925/// Bitcast one type to another. Note: both types, input, output are expected in **direct** representation.
3926fn bitCast(
3927 cg: *CodeGen,
3928 dst_ty: Type,
3929 src_ty: Type,
3930 src_id: Id,
3931) !Id {
3932 const gpa = cg.module.gpa;
3933 const zcu = cg.module.zcu;
3934 const src_ty_id = try cg.resolveType(src_ty, .direct);
3935 const dst_ty_id = try cg.resolveType(dst_ty, .direct);
3936
3937 const result_id = blk: {
3938 if (src_ty_id == dst_ty_id) break :blk src_id;
3939
3940 // TODO: Some more cases are missing here
3941 // See fn bitCast in llvm.zig
3942
3943 if (src_ty.zigTypeTag(zcu) == .int and dst_ty.isPtrAtRuntime(zcu)) {
3944 const result_id = cg.module.allocId();
3945 try cg.body.emit(gpa, .OpConvertUToPtr, .{
3946 .id_result_type = dst_ty_id,
3947 .id_result = result_id,
3948 .integer_value = src_id,
3949 });
3950 break :blk result_id;
3951 }
3952
3953 // We can only use OpBitcast for specific conversions: between numerical types, and
3954 // between pointers. If the resolved spir-v types fall into this category then emit OpBitcast,
3955 // otherwise use a temporary and perform a pointer cast.
3956 const can_bitcast = (src_ty.isNumeric(zcu) and dst_ty.isNumeric(zcu)) or (src_ty.isPtrAtRuntime(zcu) and dst_ty.isPtrAtRuntime(zcu));
3957 if (can_bitcast) {
3958 const result_id = cg.module.allocId();
3959 try cg.body.emit(gpa, .OpBitcast, .{
3960 .id_result_type = dst_ty_id,
3961 .id_result = result_id,
3962 .operand = src_id,
3963 });
3964
3965 break :blk result_id;
3966 }
3967
3968 const dst_ptr_ty_id = try cg.module.ptrType(dst_ty_id, .function);
3969
3970 const tmp_id = try cg.alloc(src_ty, .{ .storage_class = .function });
3971 try cg.store(src_ty, tmp_id, src_id, .{});
3972 const casted_ptr_id = cg.module.allocId();
3973 try cg.body.emit(gpa, .OpBitcast, .{
3974 .id_result_type = dst_ptr_ty_id,
3975 .id_result = casted_ptr_id,
3976 .operand = tmp_id,
3977 });
3978 break :blk try cg.load(dst_ty, casted_ptr_id, .{});
3979 };
3980
3981 // Because strange integers use sign-extended representation, we may need to normalize
3982 // the result here.
3983 // TODO: This detail could cause stuff like @as(*const i1, @ptrCast(&@as(u1, 1))) to break
3984 // should we change the representation of strange integers?
3985 if (dst_ty.zigTypeTag(zcu) == .int) {
3986 const info = cg.arithmeticTypeInfo(dst_ty);
3987 const result = try cg.normalize(Temporary.init(dst_ty, result_id), info);
3988 return try result.materialize(cg);
3989 }
3990
3991 return result_id;
3992}
3993
3994fn airBitCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
3995 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3996 const operand_ty = cg.typeOf(ty_op.operand);
3997 const result_ty = cg.typeOfIndex(inst);
3998 if (operand_ty.toIntern() == .bool_type) {
3999 const operand = try cg.temporary(ty_op.operand);
4000 const result = try cg.intFromBool(operand);
4001 return try result.materialize(cg);
4002 }
4003 const operand_id = try cg.resolve(ty_op.operand);
4004 return try cg.bitCast(result_ty, operand_ty, operand_id);
4005}
4006
4007fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4008 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4009 const src = try cg.temporary(ty_op.operand);
4010 const dst_ty = cg.typeOfIndex(inst);
4011
4012 const src_info = cg.arithmeticTypeInfo(src.ty);
4013 const dst_info = cg.arithmeticTypeInfo(dst_ty);
4014
4015 if (src_info.backing_bits == dst_info.backing_bits) {
4016 return try src.materialize(cg);
4017 }
4018
4019 const converted = try cg.buildConvert(dst_ty, src);
4020
4021 // Make sure to normalize the result if shrinking.
4022 // Because strange ints are sign extended in their backing
4023 // type, we don't need to normalize when growing the type. The
4024 // representation is already the same.
4025 const result = if (dst_info.bits < src_info.bits)
4026 try cg.normalize(converted, dst_info)
4027 else
4028 converted;
4029
4030 return try result.materialize(cg);
4031}
4032
4033fn intFromPtr(cg: *CodeGen, operand_id: Id) !Id {
4034 const result_type_id = try cg.resolveType(.usize, .direct);
4035 const result_id = cg.module.allocId();
4036 try cg.body.emit(cg.module.gpa, .OpConvertPtrToU, .{
4037 .id_result_type = result_type_id,
4038 .id_result = result_id,
4039 .pointer = operand_id,
4040 });
4041 return result_id;
4042}
4043
4044fn airFloatFromInt(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4045 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4046 const operand_ty = cg.typeOf(ty_op.operand);
4047 const operand_id = try cg.resolve(ty_op.operand);
4048 const result_ty = cg.typeOfIndex(inst);
4049 return try cg.floatFromInt(result_ty, operand_ty, operand_id);
4050}
4051
4052fn floatFromInt(cg: *CodeGen, result_ty: Type, operand_ty: Type, operand_id: Id) !Id {
4053 const gpa = cg.module.gpa;
4054 const operand_info = cg.arithmeticTypeInfo(operand_ty);
4055 const result_id = cg.module.allocId();
4056 const result_ty_id = try cg.resolveType(result_ty, .direct);
4057 switch (operand_info.signedness) {
4058 .signed => try cg.body.emit(gpa, .OpConvertSToF, .{
4059 .id_result_type = result_ty_id,
4060 .id_result = result_id,
4061 .signed_value = operand_id,
4062 }),
4063 .unsigned => try cg.body.emit(gpa, .OpConvertUToF, .{
4064 .id_result_type = result_ty_id,
4065 .id_result = result_id,
4066 .unsigned_value = operand_id,
4067 }),
4068 }
4069 return result_id;
4070}
4071
4072fn airIntFromFloat(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4073 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4074 const operand_id = try cg.resolve(ty_op.operand);
4075 const result_ty = cg.typeOfIndex(inst);
4076 return try cg.intFromFloat(result_ty, operand_id);
4077}
4078
4079fn intFromFloat(cg: *CodeGen, result_ty: Type, operand_id: Id) !Id {
4080 const gpa = cg.module.gpa;
4081 const result_info = cg.arithmeticTypeInfo(result_ty);
4082 const result_ty_id = try cg.resolveType(result_ty, .direct);
4083 const result_id = cg.module.allocId();
4084 switch (result_info.signedness) {
4085 .signed => try cg.body.emit(gpa, .OpConvertFToS, .{
4086 .id_result_type = result_ty_id,
4087 .id_result = result_id,
4088 .float_value = operand_id,
4089 }),
4090 .unsigned => try cg.body.emit(gpa, .OpConvertFToU, .{
4091 .id_result_type = result_ty_id,
4092 .id_result = result_id,
4093 .float_value = operand_id,
4094 }),
4095 }
4096 return result_id;
4097}
4098
4099fn airFloatCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4100 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4101 const operand = try cg.temporary(ty_op.operand);
4102 const dest_ty = cg.typeOfIndex(inst);
4103 const result = try cg.buildConvert(dest_ty, operand);
4104 return try result.materialize(cg);
4105}
4106
4107fn airNot(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4108 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4109 const operand = try cg.temporary(ty_op.operand);
4110 const result_ty = cg.typeOfIndex(inst);
4111 const info = cg.arithmeticTypeInfo(result_ty);
4112
4113 const result = switch (info.class) {
4114 .bool => try cg.buildUnary(.l_not, operand),
4115 .float => unreachable,
4116 .composite_integer => unreachable, // TODO
4117 .strange_integer, .integer => blk: {
4118 const complement = try cg.buildUnary(.bit_not, operand);
4119 break :blk try cg.normalize(complement, info);
4120 },
4121 };
4122
4123 return try result.materialize(cg);
4124}
4125
4126fn airArrayToSlice(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4127 const zcu = cg.module.zcu;
4128 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4129 const array_ptr_ty = cg.typeOf(ty_op.operand);
4130 const array_ty = array_ptr_ty.childType(zcu);
4131 const slice_ty = cg.typeOfIndex(inst);
4132 const elem_ptr_ty = slice_ty.slicePtrFieldType(zcu);
4133
4134 const elem_ptr_ty_id = try cg.resolveType(elem_ptr_ty, .direct);
4135
4136 const array_ptr_id = try cg.resolve(ty_op.operand);
4137 const len_id = try cg.constInt(.usize, array_ty.arrayLen(zcu));
4138
4139 const elem_ptr_id = if (!array_ty.hasRuntimeBitsIgnoreComptime(zcu))
4140 // Note: The pointer is something like *opaque{}, so we need to bitcast it to the element type.
4141 try cg.bitCast(elem_ptr_ty, array_ptr_ty, array_ptr_id)
4142 else
4143 // Convert the pointer-to-array to a pointer to the first element.
4144 try cg.accessChain(elem_ptr_ty_id, array_ptr_id, &.{0});
4145
4146 const slice_ty_id = try cg.resolveType(slice_ty, .direct);
4147 return try cg.constructComposite(slice_ty_id, &.{ elem_ptr_id, len_id });
4148}
4149
4150fn airSlice(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4151 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4152 const bin_op = cg.air.extraData(Air.Bin, ty_pl.payload).data;
4153 const ptr_id = try cg.resolve(bin_op.lhs);
4154 const len_id = try cg.resolve(bin_op.rhs);
4155 const slice_ty = cg.typeOfIndex(inst);
4156 const slice_ty_id = try cg.resolveType(slice_ty, .direct);
4157 return try cg.constructComposite(slice_ty_id, &.{ ptr_id, len_id });
4158}
4159
4160fn airAggregateInit(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4161 const gpa = cg.module.gpa;
4162 const pt = cg.pt;
4163 const zcu = cg.module.zcu;
4164 const ip = &zcu.intern_pool;
4165 const target = cg.module.zcu.getTarget();
4166 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4167 const result_ty = cg.typeOfIndex(inst);
4168 const len: usize = @intCast(result_ty.arrayLen(zcu));
4169 const elements: []const Air.Inst.Ref = @ptrCast(cg.air.extra.items[ty_pl.payload..][0..len]);
4170
4171 switch (result_ty.zigTypeTag(zcu)) {
4172 .@"struct" => {
4173 if (zcu.typeToPackedStruct(result_ty)) |struct_type| {
4174 comptime assert(Type.packed_struct_layout_version == 2);
4175 const backing_int_ty: Type = .fromInterned(struct_type.backingIntTypeUnordered(ip));
4176 var running_int_id = try cg.constInt(backing_int_ty, 0);
4177 var running_bits: u16 = 0;
4178 for (struct_type.field_types.get(ip), elements) |field_ty_ip, element| {
4179 const field_ty: Type = .fromInterned(field_ty_ip);
4180 if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;
4181 const field_id = try cg.resolve(element);
4182 const ty_bit_size: u16 = @intCast(field_ty.bitSize(zcu));
4183 const field_int_ty = try cg.pt.intType(.unsigned, ty_bit_size);
4184 const field_int_id = blk: {
4185 if (field_ty.isPtrAtRuntime(zcu)) {
4186 assert(target.cpu.arch == .spirv64 and
4187 field_ty.ptrAddressSpace(zcu) == .storage_buffer);
4188 break :blk try cg.intFromPtr(field_id);
4189 }
4190 break :blk try cg.bitCast(field_int_ty, field_ty, field_id);
4191 };
4192 const shift_rhs = try cg.constInt(backing_int_ty, running_bits);
4193 const extended_int_conv = try cg.buildConvert(backing_int_ty, .{
4194 .ty = field_int_ty,
4195 .value = .{ .singleton = field_int_id },
4196 });
4197 const shifted = try cg.buildBinary(.OpShiftLeftLogical, extended_int_conv, .{
4198 .ty = backing_int_ty,
4199 .value = .{ .singleton = shift_rhs },
4200 });
4201 const running_int_tmp = try cg.buildBinary(
4202 .OpBitwiseOr,
4203 .{ .ty = backing_int_ty, .value = .{ .singleton = running_int_id } },
4204 shifted,
4205 );
4206 running_int_id = try running_int_tmp.materialize(cg);
4207 running_bits += ty_bit_size;
4208 }
4209 return running_int_id;
4210 }
4211
4212 const scratch_top = cg.id_scratch.items.len;
4213 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
4214 const constituents = try cg.id_scratch.addManyAsSlice(gpa, elements.len);
4215
4216 const types = try gpa.alloc(Type, elements.len);
4217 defer gpa.free(types);
4218
4219 var index: usize = 0;
4220
4221 switch (ip.indexToKey(result_ty.toIntern())) {
4222 .tuple_type => |tuple| {
4223 for (tuple.types.get(ip), elements, 0..) |field_ty, element, i| {
4224 if ((try result_ty.structFieldValueComptime(pt, i)) != null) continue;
4225 assert(Type.fromInterned(field_ty).hasRuntimeBits(zcu));
4226
4227 const id = try cg.resolve(element);
4228 types[index] = .fromInterned(field_ty);
4229 constituents[index] = try cg.convertToIndirect(.fromInterned(field_ty), id);
4230 index += 1;
4231 }
4232 },
4233 .struct_type => {
4234 const struct_type = ip.loadStructType(result_ty.toIntern());
4235 var it = struct_type.iterateRuntimeOrder(ip);
4236 for (elements, 0..) |element, i| {
4237 const field_index = it.next().?;
4238 if ((try result_ty.structFieldValueComptime(pt, i)) != null) continue;
4239 const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]);
4240 assert(field_ty.hasRuntimeBitsIgnoreComptime(zcu));
4241
4242 const id = try cg.resolve(element);
4243 types[index] = field_ty;
4244 constituents[index] = try cg.convertToIndirect(field_ty, id);
4245 index += 1;
4246 }
4247 },
4248 else => unreachable,
4249 }
4250
4251 const result_ty_id = try cg.resolveType(result_ty, .direct);
4252 return try cg.constructComposite(result_ty_id, constituents[0..index]);
4253 },
4254 .vector => {
4255 const n_elems = result_ty.vectorLen(zcu);
4256 const scratch_top = cg.id_scratch.items.len;
4257 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
4258 const elem_ids = try cg.id_scratch.addManyAsSlice(gpa, n_elems);
4259
4260 for (elements, 0..) |element, i| {
4261 elem_ids[i] = try cg.resolve(element);
4262 }
4263
4264 const result_ty_id = try cg.resolveType(result_ty, .direct);
4265 return try cg.constructComposite(result_ty_id, elem_ids);
4266 },
4267 .array => {
4268 const array_info = result_ty.arrayInfo(zcu);
4269 const n_elems: usize = @intCast(result_ty.arrayLenIncludingSentinel(zcu));
4270 const scratch_top = cg.id_scratch.items.len;
4271 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
4272 const elem_ids = try cg.id_scratch.addManyAsSlice(gpa, n_elems);
4273
4274 for (elements, 0..) |element, i| {
4275 const id = try cg.resolve(element);
4276 elem_ids[i] = try cg.convertToIndirect(array_info.elem_type, id);
4277 }
4278
4279 if (array_info.sentinel) |sentinel_val| {
4280 elem_ids[n_elems - 1] = try cg.constant(array_info.elem_type, sentinel_val, .indirect);
4281 }
4282
4283 const result_ty_id = try cg.resolveType(result_ty, .direct);
4284 return try cg.constructComposite(result_ty_id, elem_ids);
4285 },
4286 else => unreachable,
4287 }
4288}
4289
4290fn sliceOrArrayLen(cg: *CodeGen, operand_id: Id, ty: Type) !Id {
4291 const zcu = cg.module.zcu;
4292 switch (ty.ptrSize(zcu)) {
4293 .slice => return cg.extractField(.usize, operand_id, 1),
4294 .one => {
4295 const array_ty = ty.childType(zcu);
4296 const elem_ty = array_ty.childType(zcu);
4297 const abi_size = elem_ty.abiSize(zcu);
4298 const size = array_ty.arrayLenIncludingSentinel(zcu) * abi_size;
4299 return try cg.constInt(.usize, size);
4300 },
4301 .many, .c => unreachable,
4302 }
4303}
4304
4305fn sliceOrArrayPtr(cg: *CodeGen, operand_id: Id, ty: Type) !Id {
4306 const zcu = cg.module.zcu;
4307 if (ty.isSlice(zcu)) {
4308 const ptr_ty = ty.slicePtrFieldType(zcu);
4309 return cg.extractField(ptr_ty, operand_id, 0);
4310 }
4311 return operand_id;
4312}
4313
4314fn airMemcpy(cg: *CodeGen, inst: Air.Inst.Index) !void {
4315 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
4316 const dest_slice = try cg.resolve(bin_op.lhs);
4317 const src_slice = try cg.resolve(bin_op.rhs);
4318 const dest_ty = cg.typeOf(bin_op.lhs);
4319 const src_ty = cg.typeOf(bin_op.rhs);
4320 const dest_ptr = try cg.sliceOrArrayPtr(dest_slice, dest_ty);
4321 const src_ptr = try cg.sliceOrArrayPtr(src_slice, src_ty);
4322 const len = try cg.sliceOrArrayLen(dest_slice, dest_ty);
4323 try cg.body.emit(cg.module.gpa, .OpCopyMemorySized, .{
4324 .target = dest_ptr,
4325 .source = src_ptr,
4326 .size = len,
4327 });
4328}
4329
4330fn airMemmove(cg: *CodeGen, inst: Air.Inst.Index) !void {
4331 _ = inst;
4332 return cg.fail("TODO implement airMemcpy for spirv", .{});
4333}
4334
4335fn airSliceField(cg: *CodeGen, inst: Air.Inst.Index, field: u32) !?Id {
4336 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4337 const field_ty = cg.typeOfIndex(inst);
4338 const operand_id = try cg.resolve(ty_op.operand);
4339 return try cg.extractField(field_ty, operand_id, field);
4340}
4341
4342fn airSliceElemPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4343 const zcu = cg.module.zcu;
4344 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4345 const bin_op = cg.air.extraData(Air.Bin, ty_pl.payload).data;
4346 const slice_ty = cg.typeOf(bin_op.lhs);
4347 if (!slice_ty.isVolatilePtr(zcu) and cg.liveness.isUnused(inst)) return null;
4348
4349 const slice_id = try cg.resolve(bin_op.lhs);
4350 const index_id = try cg.resolve(bin_op.rhs);
4351
4352 const ptr_ty = cg.typeOfIndex(inst);
4353 const ptr_ty_id = try cg.resolveType(ptr_ty, .direct);
4354
4355 const slice_ptr = try cg.extractField(ptr_ty, slice_id, 0);
4356 return try cg.ptrAccessChain(ptr_ty_id, slice_ptr, index_id, &.{});
4357}
4358
4359fn airSliceElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4360 const zcu = cg.module.zcu;
4361 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
4362 const slice_ty = cg.typeOf(bin_op.lhs);
4363 if (!slice_ty.isVolatilePtr(zcu) and cg.liveness.isUnused(inst)) return null;
4364
4365 const slice_id = try cg.resolve(bin_op.lhs);
4366 const index_id = try cg.resolve(bin_op.rhs);
4367
4368 const ptr_ty = slice_ty.slicePtrFieldType(zcu);
4369 const ptr_ty_id = try cg.resolveType(ptr_ty, .direct);
4370
4371 const slice_ptr = try cg.extractField(ptr_ty, slice_id, 0);
4372 const elem_ptr = try cg.ptrAccessChain(ptr_ty_id, slice_ptr, index_id, &.{});
4373 return try cg.load(slice_ty.childType(zcu), elem_ptr, .{ .is_volatile = slice_ty.isVolatilePtr(zcu) });
4374}
4375
4376fn ptrElemPtr(cg: *CodeGen, ptr_ty: Type, ptr_id: Id, index_id: Id) !Id {
4377 const zcu = cg.module.zcu;
4378 // Construct new pointer type for the resulting pointer
4379 const elem_ty = ptr_ty.elemType2(zcu); // use elemType() so that we get T for *[N]T.
4380 const elem_ty_id = try cg.resolveType(elem_ty, .indirect);
4381 const elem_ptr_ty_id = try cg.module.ptrType(elem_ty_id, cg.module.storageClass(ptr_ty.ptrAddressSpace(zcu)));
4382 if (ptr_ty.isSinglePointer(zcu)) {
4383 // Pointer-to-array. In this case, the resulting pointer is not of the same type
4384 // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain.
4385 return try cg.accessChainId(elem_ptr_ty_id, ptr_id, &.{index_id});
4386 } else {
4387 // Resulting pointer type is the same as the ptr_ty, so use ptrAccessChain
4388 return try cg.ptrAccessChain(elem_ptr_ty_id, ptr_id, index_id, &.{});
4389 }
4390}
4391
4392fn airPtrElemPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4393 const zcu = cg.module.zcu;
4394 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4395 const bin_op = cg.air.extraData(Air.Bin, ty_pl.payload).data;
4396 const src_ptr_ty = cg.typeOf(bin_op.lhs);
4397 const elem_ty = src_ptr_ty.childType(zcu);
4398 const ptr_id = try cg.resolve(bin_op.lhs);
4399
4400 if (!elem_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
4401 const dst_ptr_ty = cg.typeOfIndex(inst);
4402 return try cg.bitCast(dst_ptr_ty, src_ptr_ty, ptr_id);
4403 }
4404
4405 const index_id = try cg.resolve(bin_op.rhs);
4406 return try cg.ptrElemPtr(src_ptr_ty, ptr_id, index_id);
4407}
4408
4409fn airArrayElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4410 const gpa = cg.module.gpa;
4411 const zcu = cg.module.zcu;
4412 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
4413 const array_ty = cg.typeOf(bin_op.lhs);
4414 const elem_ty = array_ty.childType(zcu);
4415 const array_id = try cg.resolve(bin_op.lhs);
4416 const index_id = try cg.resolve(bin_op.rhs);
4417
4418 // SPIR-V doesn't have an array indexing function for some damn reason.
4419 // For now, just generate a temporary and use that.
4420 // TODO: This backend probably also should use isByRef from llvm...
4421
4422 const is_vector = array_ty.isVector(zcu);
4423
4424 const elem_repr: Repr = if (is_vector) .direct else .indirect;
4425 const array_ty_id = try cg.resolveType(array_ty, .direct);
4426 const elem_ty_id = try cg.resolveType(elem_ty, elem_repr);
4427 const ptr_array_ty_id = try cg.module.ptrType(array_ty_id, .function);
4428 const ptr_elem_ty_id = try cg.module.ptrType(elem_ty_id, .function);
4429
4430 const tmp_id = cg.module.allocId();
4431 try cg.prologue.emit(gpa, .OpVariable, .{
4432 .id_result_type = ptr_array_ty_id,
4433 .id_result = tmp_id,
4434 .storage_class = .function,
4435 });
4436
4437 try cg.body.emit(gpa, .OpStore, .{
4438 .pointer = tmp_id,
4439 .object = array_id,
4440 });
4441
4442 const elem_ptr_id = try cg.accessChainId(ptr_elem_ty_id, tmp_id, &.{index_id});
4443
4444 const result_id = cg.module.allocId();
4445 try cg.body.emit(gpa, .OpLoad, .{
4446 .id_result_type = try cg.resolveType(elem_ty, elem_repr),
4447 .id_result = result_id,
4448 .pointer = elem_ptr_id,
4449 });
4450
4451 if (is_vector) {
4452 // Result is already in direct representation
4453 return result_id;
4454 }
4455
4456 // This is an array type; the elements are stored in indirect representation.
4457 // We have to convert the type to direct.
4458
4459 return try cg.convertToDirect(elem_ty, result_id);
4460}
4461
4462fn airPtrElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4463 const zcu = cg.module.zcu;
4464 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
4465 const ptr_ty = cg.typeOf(bin_op.lhs);
4466 const elem_ty = cg.typeOfIndex(inst);
4467 const ptr_id = try cg.resolve(bin_op.lhs);
4468 const index_id = try cg.resolve(bin_op.rhs);
4469 const elem_ptr_id = try cg.ptrElemPtr(ptr_ty, ptr_id, index_id);
4470 return try cg.load(elem_ty, elem_ptr_id, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) });
4471}
4472
4473fn airVectorStoreElem(cg: *CodeGen, inst: Air.Inst.Index) !void {
4474 const zcu = cg.module.zcu;
4475 const data = cg.air.instructions.items(.data)[@intFromEnum(inst)].vector_store_elem;
4476 const extra = cg.air.extraData(Air.Bin, data.payload).data;
4477
4478 const vector_ptr_ty = cg.typeOf(data.vector_ptr);
4479 const vector_ty = vector_ptr_ty.childType(zcu);
4480 const scalar_ty = vector_ty.scalarType(zcu);
4481
4482 const scalar_ty_id = try cg.resolveType(scalar_ty, .indirect);
4483 const storage_class = cg.module.storageClass(vector_ptr_ty.ptrAddressSpace(zcu));
4484 const scalar_ptr_ty_id = try cg.module.ptrType(scalar_ty_id, storage_class);
4485
4486 const vector_ptr = try cg.resolve(data.vector_ptr);
4487 const index = try cg.resolve(extra.lhs);
4488 const operand = try cg.resolve(extra.rhs);
4489
4490 const elem_ptr_id = try cg.accessChainId(scalar_ptr_ty_id, vector_ptr, &.{index});
4491 try cg.store(scalar_ty, elem_ptr_id, operand, .{
4492 .is_volatile = vector_ptr_ty.isVolatilePtr(zcu),
4493 });
4494}
4495
4496fn airSetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !void {
4497 const zcu = cg.module.zcu;
4498 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
4499 const un_ptr_ty = cg.typeOf(bin_op.lhs);
4500 const un_ty = un_ptr_ty.childType(zcu);
4501 const layout = cg.unionLayout(un_ty);
4502
4503 if (layout.tag_size == 0) return;
4504
4505 const tag_ty = un_ty.unionTagTypeSafety(zcu).?;
4506 const tag_ty_id = try cg.resolveType(tag_ty, .indirect);
4507 const tag_ptr_ty_id = try cg.module.ptrType(tag_ty_id, cg.module.storageClass(un_ptr_ty.ptrAddressSpace(zcu)));
4508
4509 const union_ptr_id = try cg.resolve(bin_op.lhs);
4510 const new_tag_id = try cg.resolve(bin_op.rhs);
4511
4512 if (!layout.has_payload) {
4513 try cg.store(tag_ty, union_ptr_id, new_tag_id, .{ .is_volatile = un_ptr_ty.isVolatilePtr(zcu) });
4514 } else {
4515 const ptr_id = try cg.accessChain(tag_ptr_ty_id, union_ptr_id, &.{layout.tag_index});
4516 try cg.store(tag_ty, ptr_id, new_tag_id, .{ .is_volatile = un_ptr_ty.isVolatilePtr(zcu) });
4517 }
4518}
4519
4520fn airGetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4521 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4522 const un_ty = cg.typeOf(ty_op.operand);
4523
4524 const zcu = cg.module.zcu;
4525 const layout = cg.unionLayout(un_ty);
4526 if (layout.tag_size == 0) return null;
4527
4528 const union_handle = try cg.resolve(ty_op.operand);
4529 if (!layout.has_payload) return union_handle;
4530
4531 const tag_ty = un_ty.unionTagTypeSafety(zcu).?;
4532 return try cg.extractField(tag_ty, union_handle, layout.tag_index);
4533}
4534
4535fn unionInit(
4536 cg: *CodeGen,
4537 ty: Type,
4538 active_field: u32,
4539 payload: ?Id,
4540) !Id {
4541 // To initialize a union, generate a temporary variable with the
4542 // union type, then get the field pointer and pointer-cast it to the
4543 // right type to store it. Finally load the entire union.
4544
4545 // Note: The result here is not cached, because it generates runtime code.
4546
4547 const pt = cg.pt;
4548 const zcu = cg.module.zcu;
4549 const ip = &zcu.intern_pool;
4550 const union_ty = zcu.typeToUnion(ty).?;
4551 const tag_ty: Type = .fromInterned(union_ty.enum_tag_ty);
4552
4553 const layout = cg.unionLayout(ty);
4554 const payload_ty: Type = .fromInterned(union_ty.field_types.get(ip)[active_field]);
4555
4556 if (union_ty.flagsUnordered(ip).layout == .@"packed") {
4557 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
4558 const int_ty = try pt.intType(.unsigned, @intCast(ty.bitSize(zcu)));
4559 return cg.constInt(int_ty, 0);
4560 }
4561
4562 assert(payload != null);
4563 if (payload_ty.isInt(zcu)) {
4564 if (ty.bitSize(zcu) == payload_ty.bitSize(zcu)) {
4565 return cg.bitCast(ty, payload_ty, payload.?);
4566 }
4567
4568 const trunc = try cg.buildConvert(ty, .{ .ty = payload_ty, .value = .{ .singleton = payload.? } });
4569 return try trunc.materialize(cg);
4570 }
4571
4572 const payload_int_ty = try pt.intType(.unsigned, @intCast(payload_ty.bitSize(zcu)));
4573 const payload_int = if (payload_ty.ip_index == .bool_type)
4574 try cg.convertToIndirect(payload_ty, payload.?)
4575 else
4576 try cg.bitCast(payload_int_ty, payload_ty, payload.?);
4577 const trunc = try cg.buildConvert(ty, .{ .ty = payload_int_ty, .value = .{ .singleton = payload_int } });
4578 return try trunc.materialize(cg);
4579 }
4580
4581 const tag_int = if (layout.tag_size != 0) blk: {
4582 const tag_val = try pt.enumValueFieldIndex(tag_ty, active_field);
4583 const tag_int_val = try tag_val.intFromEnum(tag_ty, pt);
4584 break :blk tag_int_val.toUnsignedInt(zcu);
4585 } else 0;
4586
4587 if (!layout.has_payload) {
4588 return try cg.constInt(tag_ty, tag_int);
4589 }
4590
4591 const tmp_id = try cg.alloc(ty, .{ .storage_class = .function });
4592
4593 if (layout.tag_size != 0) {
4594 const tag_ty_id = try cg.resolveType(tag_ty, .indirect);
4595 const tag_ptr_ty_id = try cg.module.ptrType(tag_ty_id, .function);
4596 const ptr_id = try cg.accessChain(tag_ptr_ty_id, tmp_id, &.{@as(u32, @intCast(layout.tag_index))});
4597 const tag_id = try cg.constInt(tag_ty, tag_int);
4598 try cg.store(tag_ty, ptr_id, tag_id, .{});
4599 }
4600
4601 if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
4602 const layout_payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect);
4603 const pl_ptr_ty_id = try cg.module.ptrType(layout_payload_ty_id, .function);
4604 const pl_ptr_id = try cg.accessChain(pl_ptr_ty_id, tmp_id, &.{layout.payload_index});
4605 const active_pl_ptr_id = if (!layout.payload_ty.eql(payload_ty, zcu)) blk: {
4606 const payload_ty_id = try cg.resolveType(payload_ty, .indirect);
4607 const active_pl_ptr_ty_id = try cg.module.ptrType(payload_ty_id, .function);
4608 const active_pl_ptr_id = cg.module.allocId();
4609 try cg.body.emit(cg.module.gpa, .OpBitcast, .{
4610 .id_result_type = active_pl_ptr_ty_id,
4611 .id_result = active_pl_ptr_id,
4612 .operand = pl_ptr_id,
4613 });
4614 break :blk active_pl_ptr_id;
4615 } else pl_ptr_id;
4616
4617 try cg.store(payload_ty, active_pl_ptr_id, payload.?, .{});
4618 } else {
4619 assert(payload == null);
4620 }
4621
4622 // Just leave the padding fields uninitialized...
4623 // TODO: Or should we initialize them with undef explicitly?
4624
4625 return try cg.load(ty, tmp_id, .{});
4626}
4627
4628fn airUnionInit(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4629 const zcu = cg.module.zcu;
4630 const ip = &zcu.intern_pool;
4631 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4632 const extra = cg.air.extraData(Air.UnionInit, ty_pl.payload).data;
4633 const ty = cg.typeOfIndex(inst);
4634
4635 const union_obj = zcu.typeToUnion(ty).?;
4636 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[extra.field_index]);
4637 const payload = if (field_ty.hasRuntimeBitsIgnoreComptime(zcu))
4638 try cg.resolve(extra.init)
4639 else
4640 null;
4641 return try cg.unionInit(ty, extra.field_index, payload);
4642}
4643
4644fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4645 const pt = cg.pt;
4646 const zcu = cg.module.zcu;
4647 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4648 const struct_field = cg.air.extraData(Air.StructField, ty_pl.payload).data;
4649
4650 const object_ty = cg.typeOf(struct_field.struct_operand);
4651 const object_id = try cg.resolve(struct_field.struct_operand);
4652 const field_index = struct_field.field_index;
4653 const field_ty = object_ty.fieldType(field_index, zcu);
4654
4655 if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) return null;
4656
4657 switch (object_ty.zigTypeTag(zcu)) {
4658 .@"struct" => switch (object_ty.containerLayout(zcu)) {
4659 .@"packed" => {
4660 const struct_ty = zcu.typeToPackedStruct(object_ty).?;
4661 const struct_backing_int_bits = cg.module.backingIntBits(@intCast(object_ty.bitSize(zcu))).@"0";
4662 const bit_offset = zcu.structPackedFieldBitOffset(struct_ty, field_index);
4663 // We use the same int type the packed struct is backed by, because even though it would
4664 // be valid SPIR-V to use an smaller type like u16, some implementations like PoCL will complain.
4665 const bit_offset_id = try cg.constInt(object_ty, bit_offset);
4666 const signedness = if (field_ty.isInt(zcu)) field_ty.intInfo(zcu).signedness else .unsigned;
4667 const field_bit_size: u16 = @intCast(field_ty.bitSize(zcu));
4668 const field_int_ty = try pt.intType(signedness, field_bit_size);
4669 const shift_lhs: Temporary = .{ .ty = object_ty, .value = .{ .singleton = object_id } };
4670 const shift = try cg.buildBinary(.OpShiftRightLogical, shift_lhs, .{ .ty = object_ty, .value = .{ .singleton = bit_offset_id } });
4671 const mask_id = try cg.constInt(object_ty, (@as(u64, 1) << @as(u6, @intCast(field_bit_size))) - 1);
4672 const masked = try cg.buildBinary(.OpBitwiseAnd, shift, .{ .ty = object_ty, .value = .{ .singleton = mask_id } });
4673 const result_id = blk: {
4674 if (cg.module.backingIntBits(field_bit_size).@"0" == struct_backing_int_bits)
4675 break :blk try cg.bitCast(field_int_ty, object_ty, try masked.materialize(cg));
4676 const trunc = try cg.buildConvert(field_int_ty, masked);
4677 break :blk try trunc.materialize(cg);
4678 };
4679 if (field_ty.ip_index == .bool_type) return try cg.convertToDirect(.bool, result_id);
4680 if (field_ty.isInt(zcu)) return result_id;
4681 return try cg.bitCast(field_ty, field_int_ty, result_id);
4682 },
4683 else => return try cg.extractField(field_ty, object_id, field_index),
4684 },
4685 .@"union" => switch (object_ty.containerLayout(zcu)) {
4686 .@"packed" => {
4687 const backing_int_ty = try pt.intType(.unsigned, @intCast(object_ty.bitSize(zcu)));
4688 const signedness = if (field_ty.isInt(zcu)) field_ty.intInfo(zcu).signedness else .unsigned;
4689 const field_bit_size: u16 = @intCast(field_ty.bitSize(zcu));
4690 const int_ty = try pt.intType(signedness, field_bit_size);
4691 const mask_id = try cg.constInt(backing_int_ty, (@as(u64, 1) << @as(u6, @intCast(field_bit_size))) - 1);
4692 const masked = try cg.buildBinary(
4693 .OpBitwiseAnd,
4694 .{ .ty = backing_int_ty, .value = .{ .singleton = object_id } },
4695 .{ .ty = backing_int_ty, .value = .{ .singleton = mask_id } },
4696 );
4697 const result_id = blk: {
4698 if (cg.module.backingIntBits(field_bit_size).@"0" == cg.module.backingIntBits(@intCast(backing_int_ty.bitSize(zcu))).@"0")
4699 break :blk try cg.bitCast(int_ty, backing_int_ty, try masked.materialize(cg));
4700 const trunc = try cg.buildConvert(int_ty, masked);
4701 break :blk try trunc.materialize(cg);
4702 };
4703 if (field_ty.ip_index == .bool_type) return try cg.convertToDirect(.bool, result_id);
4704 if (field_ty.isInt(zcu)) return result_id;
4705 return try cg.bitCast(field_ty, int_ty, result_id);
4706 },
4707 else => {
4708 // Store, ptr-elem-ptr, pointer-cast, load
4709 const layout = cg.unionLayout(object_ty);
4710 assert(layout.has_payload);
4711
4712 const tmp_id = try cg.alloc(object_ty, .{ .storage_class = .function });
4713 try cg.store(object_ty, tmp_id, object_id, .{});
4714
4715 const layout_payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect);
4716 const pl_ptr_ty_id = try cg.module.ptrType(layout_payload_ty_id, .function);
4717 const pl_ptr_id = try cg.accessChain(pl_ptr_ty_id, tmp_id, &.{layout.payload_index});
4718
4719 const field_ty_id = try cg.resolveType(field_ty, .indirect);
4720 const active_pl_ptr_ty_id = try cg.module.ptrType(field_ty_id, .function);
4721 const active_pl_ptr_id = cg.module.allocId();
4722 try cg.body.emit(cg.module.gpa, .OpBitcast, .{
4723 .id_result_type = active_pl_ptr_ty_id,
4724 .id_result = active_pl_ptr_id,
4725 .operand = pl_ptr_id,
4726 });
4727 return try cg.load(field_ty, active_pl_ptr_id, .{});
4728 },
4729 },
4730 else => unreachable,
4731 }
4732}
4733
4734fn airFieldParentPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4735 const zcu = cg.module.zcu;
4736 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4737 const extra = cg.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;
4738
4739 const parent_ty = ty_pl.ty.toType().childType(zcu);
4740 const result_ty_id = try cg.resolveType(ty_pl.ty.toType(), .indirect);
4741
4742 const field_ptr = try cg.resolve(extra.field_ptr);
4743 const field_ptr_int = try cg.intFromPtr(field_ptr);
4744 const field_offset = parent_ty.structFieldOffset(extra.field_index, zcu);
4745
4746 const base_ptr_int = base_ptr_int: {
4747 if (field_offset == 0) break :base_ptr_int field_ptr_int;
4748
4749 const field_offset_id = try cg.constInt(.usize, field_offset);
4750 const field_ptr_tmp: Temporary = .init(.usize, field_ptr_int);
4751 const field_offset_tmp: Temporary = .init(.usize, field_offset_id);
4752 const result = try cg.buildBinary(.OpISub, field_ptr_tmp, field_offset_tmp);
4753 break :base_ptr_int try result.materialize(cg);
4754 };
4755
4756 const base_ptr = cg.module.allocId();
4757 try cg.body.emit(cg.module.gpa, .OpConvertUToPtr, .{
4758 .id_result_type = result_ty_id,
4759 .id_result = base_ptr,
4760 .integer_value = base_ptr_int,
4761 });
4762
4763 return base_ptr;
4764}
4765
4766fn structFieldPtr(
4767 cg: *CodeGen,
4768 result_ptr_ty: Type,
4769 object_ptr_ty: Type,
4770 object_ptr: Id,
4771 field_index: u32,
4772) !Id {
4773 const result_ty_id = try cg.resolveType(result_ptr_ty, .direct);
4774
4775 const zcu = cg.module.zcu;
4776 const object_ty = object_ptr_ty.childType(zcu);
4777 switch (object_ty.zigTypeTag(zcu)) {
4778 .pointer => {
4779 assert(object_ty.isSlice(zcu));
4780 return cg.accessChain(result_ty_id, object_ptr, &.{field_index});
4781 },
4782 .@"struct" => switch (object_ty.containerLayout(zcu)) {
4783 .@"packed" => return cg.todo("implement field access for packed structs", .{}),
4784 else => {
4785 return try cg.accessChain(result_ty_id, object_ptr, &.{field_index});
4786 },
4787 },
4788 .@"union" => {
4789 const layout = cg.unionLayout(object_ty);
4790 if (!layout.has_payload) {
4791 // Asked to get a pointer to a zero-sized field. Just lower this
4792 // to undefined, there is no reason to make it be a valid pointer.
4793 return try cg.module.constUndef(result_ty_id);
4794 }
4795
4796 const storage_class = cg.module.storageClass(object_ptr_ty.ptrAddressSpace(zcu));
4797 const layout_payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect);
4798 const pl_ptr_ty_id = try cg.module.ptrType(layout_payload_ty_id, storage_class);
4799 const pl_ptr_id = blk: {
4800 if (object_ty.containerLayout(zcu) == .@"packed") break :blk object_ptr;
4801 break :blk try cg.accessChain(pl_ptr_ty_id, object_ptr, &.{layout.payload_index});
4802 };
4803
4804 const active_pl_ptr_id = cg.module.allocId();
4805 try cg.body.emit(cg.module.gpa, .OpBitcast, .{
4806 .id_result_type = result_ty_id,
4807 .id_result = active_pl_ptr_id,
4808 .operand = pl_ptr_id,
4809 });
4810 return active_pl_ptr_id;
4811 },
4812 else => unreachable,
4813 }
4814}
4815
4816fn airStructFieldPtrIndex(cg: *CodeGen, inst: Air.Inst.Index, field_index: u32) !?Id {
4817 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4818 const struct_ptr = try cg.resolve(ty_op.operand);
4819 const struct_ptr_ty = cg.typeOf(ty_op.operand);
4820 const result_ptr_ty = cg.typeOfIndex(inst);
4821 return try cg.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index);
4822}
4823
4824const AllocOptions = struct {
4825 initializer: ?Id = null,
4826 /// The final storage class of the pointer. This may be either `.Generic` or `.Function`.
4827 /// In either case, the local is allocated in the `.Function` storage class, and optionally
4828 /// cast back to `.Generic`.
4829 storage_class: StorageClass,
4830};
4831
4832// Allocate a function-local variable, with possible initializer.
4833// This function returns a pointer to a variable of type `ty`,
4834// which is in the Generic address space. The variable is actually
4835// placed in the Function address space.
4836fn alloc(
4837 cg: *CodeGen,
4838 ty: Type,
4839 options: AllocOptions,
4840) !Id {
4841 const ty_id = try cg.resolveType(ty, .indirect);
4842 const ptr_fn_ty_id = try cg.module.ptrType(ty_id, .function);
4843
4844 // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to
4845 // directly generate them into func.prologue instead of the body.
4846 const var_id = cg.module.allocId();
4847 try cg.prologue.emit(cg.module.gpa, .OpVariable, .{
4848 .id_result_type = ptr_fn_ty_id,
4849 .id_result = var_id,
4850 .storage_class = .function,
4851 .initializer = options.initializer,
4852 });
4853
4854 return var_id;
4855}
4856
4857fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
4858 const zcu = cg.module.zcu;
4859 const ptr_ty = cg.typeOfIndex(inst);
4860 const child_ty = ptr_ty.childType(zcu);
4861 return try cg.alloc(child_ty, .{
4862 .storage_class = cg.module.storageClass(ptr_ty.ptrAddressSpace(zcu)),
4863 });
4864}
4865
4866fn airArg(cg: *CodeGen) Id {
4867 defer cg.next_arg_index += 1;
4868 return cg.args.items[cg.next_arg_index];
4869}
4870
4871/// Given a slice of incoming block connections, returns the block-id of the next
4872/// block to jump to. This function emits instructions, so it should be emitted
4873/// inside the merge block of the block.
4874/// This function should only be called with structured control flow generation.
4875fn structuredNextBlock(cg: *CodeGen, incoming: []const ControlFlow.Structured.Block.Incoming) !Id {
4876 assert(cg.control_flow == .structured);
4877
4878 const result_id = cg.module.allocId();
4879 const block_id_ty_id = try cg.resolveType(.u32, .direct);
4880 try cg.body.emitRaw(cg.module.gpa, .OpPhi, @intCast(2 + incoming.len * 2)); // result type + result + variable/parent...
4881 cg.body.writeOperand(Id, block_id_ty_id);
4882 cg.body.writeOperand(Id, result_id);
4883
4884 for (incoming) |incoming_block| {
4885 cg.body.writeOperand(spec.PairIdRefIdRef, .{ incoming_block.next_block, incoming_block.src_label });
4886 }
4887
4888 return result_id;
4889}
4890
4891/// Jumps to the block with the target block-id. This function must only be called when
4892/// terminating a body, there should be no instructions after it.
4893/// This function should only be called with structured control flow generation.
4894fn structuredBreak(cg: *CodeGen, target_block: Id) !void {
4895 assert(cg.control_flow == .structured);
4896
4897 const gpa = cg.module.gpa;
4898 const sblock = cg.control_flow.structured.block_stack.getLast();
4899 const merge_block = switch (sblock.*) {
4900 .selection => |*merge| blk: {
4901 const merge_label = cg.module.allocId();
4902 try merge.merge_stack.append(gpa, .{
4903 .incoming = .{
4904 .src_label = cg.block_label,
4905 .next_block = target_block,
4906 },
4907 .merge_block = merge_label,
4908 });
4909 break :blk merge_label;
4910 },
4911 // Loop blocks do not end in a break. Not through a direct break,
4912 // and also not through another instruction like cond_br or unreachable (these
4913 // situations are replaced by `cond_br` in sema, or there is a `block` instruction
4914 // placed around them).
4915 .loop => unreachable,
4916 };
4917
4918 try cg.body.emit(gpa, .OpBranch, .{ .target_label = merge_block });
4919}
4920
4921/// Generate a body in a way that exits the body using only structured constructs.
4922/// Returns the block-id of the next block to jump to. After this function, a jump
4923/// should still be emitted to the block that should follow this structured body.
4924/// This function should only be called with structured control flow generation.
4925fn genStructuredBody(
4926 cg: *CodeGen,
4927 /// This parameter defines the method that this structured body is exited with.
4928 block_merge_type: union(enum) {
4929 /// Using selection; early exits from this body are surrounded with
4930 /// if() statements.
4931 selection,
4932 /// Using loops; loops can be early exited by jumping to the merge block at
4933 /// any time.
4934 loop: struct {
4935 merge_label: Id,
4936 continue_label: Id,
4937 },
4938 },
4939 body: []const Air.Inst.Index,
4940) !Id {
4941 assert(cg.control_flow == .structured);
4942
4943 const gpa = cg.module.gpa;
4944
4945 var sblock: ControlFlow.Structured.Block = switch (block_merge_type) {
4946 .loop => |merge| .{ .loop = .{
4947 .merge_block = merge.merge_label,
4948 } },
4949 .selection => .{ .selection = .{} },
4950 };
4951 defer sblock.deinit(gpa);
4952
4953 {
4954 try cg.control_flow.structured.block_stack.append(gpa, &sblock);
4955 defer _ = cg.control_flow.structured.block_stack.pop();
4956
4957 try cg.genBody(body);
4958 }
4959
4960 switch (sblock) {
4961 .selection => |merge| {
4962 // Now generate the merge block for all merges that
4963 // still need to be performed.
4964 const merge_stack = merge.merge_stack.items;
4965
4966 // If no merges on the stack, this block didn't generate any jumps (all paths
4967 // ended with a return or an unreachable). In that case, we don't need to do
4968 // any merging.
4969 if (merge_stack.len == 0) {
4970 // We still need to return a value of a next block to jump to.
4971 // For example, if we have code like
4972 // if (x) {
4973 // if (y) return else return;
4974 // } else {}
4975 // then we still need the outer to have an OpSelectionMerge and consequently
4976 // a phi node. In that case we can just return bogus, since we know that its
4977 // path will never be taken.
4978
4979 // Make sure that we are still in a block when exiting the function.
4980 // TODO: Can we get rid of that?
4981 try cg.beginSpvBlock(cg.module.allocId());
4982 const block_id_ty_id = try cg.resolveType(.u32, .direct);
4983 return try cg.module.constUndef(block_id_ty_id);
4984 }
4985
4986 // The top-most merge actually only has a single source, the
4987 // final jump of the block, or the merge block of a sub-block, cond_br,
4988 // or loop. Therefore we just need to generate a block with a jump to the
4989 // next merge block.
4990 try cg.beginSpvBlock(merge_stack[merge_stack.len - 1].merge_block);
4991
4992 // Now generate a merge ladder for the remaining merges in the stack.
4993 var incoming: ControlFlow.Structured.Block.Incoming = .{
4994 .src_label = cg.block_label,
4995 .next_block = merge_stack[merge_stack.len - 1].incoming.next_block,
4996 };
4997 var i = merge_stack.len - 1;
4998 while (i > 0) {
4999 i -= 1;
5000 const step = merge_stack[i];
5001
5002 try cg.body.emit(gpa, .OpBranch, .{ .target_label = step.merge_block });
5003 try cg.beginSpvBlock(step.merge_block);
5004 const next_block = try cg.structuredNextBlock(&.{ incoming, step.incoming });
5005 incoming = .{
5006 .src_label = step.merge_block,
5007 .next_block = next_block,
5008 };
5009 }
5010
5011 return incoming.next_block;
5012 },
5013 .loop => |merge| {
5014 // Close the loop by jumping to the continue label
5015
5016 try cg.body.emit(gpa, .OpBranch, .{ .target_label = block_merge_type.loop.continue_label });
5017 // For blocks we must simple merge all the incoming blocks to get the next block.
5018 try cg.beginSpvBlock(merge.merge_block);
5019 return try cg.structuredNextBlock(merge.merges.items);
5020 },
5021 }
5022}
5023
5024fn airBlock(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
5025 const inst_datas = cg.air.instructions.items(.data);
5026 const extra = cg.air.extraData(Air.Block, inst_datas[@intFromEnum(inst)].ty_pl.payload);
5027 return cg.lowerBlock(inst, @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.body_len]));
5028}
5029
5030fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, body: []const Air.Inst.Index) !?Id {
5031 // In AIR, a block doesn't really define an entry point like a block, but
5032 // more like a scope that breaks can jump out of and "return" a value from.
5033 // This cannot be directly modelled in SPIR-V, so in a block instruction,
5034 // we're going to split up the current block by first generating the code
5035 // of the block, then a label, and then generate the rest of the current
5036 // ir.Block in a different SPIR-V block.
5037
5038 const gpa = cg.module.gpa;
5039 const zcu = cg.module.zcu;
5040 const ty = cg.typeOfIndex(inst);
5041 const have_block_result = ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu);
5042
5043 const cf = switch (cg.control_flow) {
5044 .structured => |*cf| cf,
5045 .unstructured => |*cf| {
5046 var block: ControlFlow.Unstructured.Block = .{};
5047 defer block.incoming_blocks.deinit(gpa);
5048
5049 // 4 chosen as arbitrary initial capacity.
5050 try block.incoming_blocks.ensureUnusedCapacity(gpa, 4);
5051
5052 try cf.blocks.putNoClobber(gpa, inst, &block);
5053 defer assert(cf.blocks.remove(inst));
5054
5055 try cg.genBody(body);
5056
5057 // Only begin a new block if there were actually any breaks towards it.
5058 if (block.label) |label| {
5059 try cg.beginSpvBlock(label);
5060 }
5061
5062 if (!have_block_result)
5063 return null;
5064
5065 assert(block.label != null);
5066 const result_id = cg.module.allocId();
5067 const result_type_id = try cg.resolveType(ty, .direct);
5068
5069 try cg.body.emitRaw(
5070 gpa,
5071 .OpPhi,
5072 // result type + result + variable/parent...
5073 2 + @as(u16, @intCast(block.incoming_blocks.items.len * 2)),
5074 );
5075 cg.body.writeOperand(Id, result_type_id);
5076 cg.body.writeOperand(Id, result_id);
5077
5078 for (block.incoming_blocks.items) |incoming| {
5079 cg.body.writeOperand(
5080 spec.PairIdRefIdRef,
5081 .{ incoming.break_value_id, incoming.src_label },
5082 );
5083 }
5084
5085 return result_id;
5086 },
5087 };
5088
5089 const maybe_block_result_var_id = if (have_block_result) blk: {
5090 const block_result_var_id = try cg.alloc(ty, .{ .storage_class = .function });
5091 try cf.block_results.putNoClobber(gpa, inst, block_result_var_id);
5092 break :blk block_result_var_id;
5093 } else null;
5094 defer if (have_block_result) assert(cf.block_results.remove(inst));
5095
5096 const next_block = try cg.genStructuredBody(.selection, body);
5097
5098 // When encountering a block instruction, we are always at least in the function's scope,
5099 // so there always has to be another entry.
5100 assert(cf.block_stack.items.len > 0);
5101
5102 // Check if the target of the branch was this current block.
5103 const this_block = try cg.constInt(.u32, @intFromEnum(inst));
5104 const jump_to_this_block_id = cg.module.allocId();
5105 const bool_ty_id = try cg.resolveType(.bool, .direct);
5106 try cg.body.emit(gpa, .OpIEqual, .{
5107 .id_result_type = bool_ty_id,
5108 .id_result = jump_to_this_block_id,
5109 .operand_1 = next_block,
5110 .operand_2 = this_block,
5111 });
5112
5113 const sblock = cf.block_stack.getLast();
5114
5115 if (ty.isNoReturn(zcu)) {
5116 // If this block is noreturn, this instruction is the last of a block,
5117 // and we must simply jump to the block's merge unconditionally.
5118 try cg.structuredBreak(next_block);
5119 } else {
5120 switch (sblock.*) {
5121 .selection => |*merge| {
5122 // To jump out of a selection block, push a new entry onto its merge stack and
5123 // generate a conditional branch to there and to the instructions following this block.
5124 const merge_label = cg.module.allocId();
5125 const then_label = cg.module.allocId();
5126 try cg.body.emit(gpa, .OpSelectionMerge, .{
5127 .merge_block = merge_label,
5128 .selection_control = .{},
5129 });
5130 try cg.body.emit(gpa, .OpBranchConditional, .{
5131 .condition = jump_to_this_block_id,
5132 .true_label = then_label,
5133 .false_label = merge_label,
5134 });
5135 try merge.merge_stack.append(gpa, .{
5136 .incoming = .{
5137 .src_label = cg.block_label,
5138 .next_block = next_block,
5139 },
5140 .merge_block = merge_label,
5141 });
5142
5143 try cg.beginSpvBlock(then_label);
5144 },
5145 .loop => |*merge| {
5146 // To jump out of a loop block, generate a conditional that exits the block
5147 // to the loop merge if the target ID is not the one of this block.
5148 const continue_label = cg.module.allocId();
5149 try cg.body.emit(gpa, .OpBranchConditional, .{
5150 .condition = jump_to_this_block_id,
5151 .true_label = continue_label,
5152 .false_label = merge.merge_block,
5153 });
5154 try merge.merges.append(gpa, .{
5155 .src_label = cg.block_label,
5156 .next_block = next_block,
5157 });
5158 try cg.beginSpvBlock(continue_label);
5159 },
5160 }
5161 }
5162
5163 if (maybe_block_result_var_id) |block_result_var_id| {
5164 return try cg.load(ty, block_result_var_id, .{});
5165 }
5166
5167 return null;
5168}
5169
5170fn airBr(cg: *CodeGen, inst: Air.Inst.Index) !void {
5171 const gpa = cg.module.gpa;
5172 const zcu = cg.module.zcu;
5173 const br = cg.air.instructions.items(.data)[@intFromEnum(inst)].br;
5174 const operand_ty = cg.typeOf(br.operand);
5175
5176 switch (cg.control_flow) {
5177 .structured => |*cf| {
5178 if (operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) {
5179 const operand_id = try cg.resolve(br.operand);
5180 const block_result_var_id = cf.block_results.get(br.block_inst).?;
5181 try cg.store(operand_ty, block_result_var_id, operand_id, .{});
5182 }
5183
5184 const next_block = try cg.constInt(.u32, @intFromEnum(br.block_inst));
5185 try cg.structuredBreak(next_block);
5186 },
5187 .unstructured => |cf| {
5188 const block = cf.blocks.get(br.block_inst).?;
5189 if (operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) {
5190 const operand_id = try cg.resolve(br.operand);
5191 // block_label should not be undefined here, lest there
5192 // is a br or br_void in the function's body.
5193 try block.incoming_blocks.append(gpa, .{
5194 .src_label = cg.block_label,
5195 .break_value_id = operand_id,
5196 });
5197 }
5198
5199 if (block.label == null) {
5200 block.label = cg.module.allocId();
5201 }
5202
5203 try cg.body.emit(gpa, .OpBranch, .{ .target_label = block.label.? });
5204 },
5205 }
5206}
5207
5208fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) !void {
5209 const gpa = cg.module.gpa;
5210 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
5211 const cond_br = cg.air.extraData(Air.CondBr, pl_op.payload);
5212 const then_body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[cond_br.end..][0..cond_br.data.then_body_len]);
5213 const else_body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[cond_br.end + then_body.len ..][0..cond_br.data.else_body_len]);
5214 const condition_id = try cg.resolve(pl_op.operand);
5215
5216 const then_label = cg.module.allocId();
5217 const else_label = cg.module.allocId();
5218
5219 switch (cg.control_flow) {
5220 .structured => {
5221 const merge_label = cg.module.allocId();
5222
5223 try cg.body.emit(gpa, .OpSelectionMerge, .{
5224 .merge_block = merge_label,
5225 .selection_control = .{},
5226 });
5227 try cg.body.emit(gpa, .OpBranchConditional, .{
5228 .condition = condition_id,
5229 .true_label = then_label,
5230 .false_label = else_label,
5231 });
5232
5233 try cg.beginSpvBlock(then_label);
5234 const then_next = try cg.genStructuredBody(.selection, then_body);
5235 const then_incoming: ControlFlow.Structured.Block.Incoming = .{
5236 .src_label = cg.block_label,
5237 .next_block = then_next,
5238 };
5239
5240 try cg.body.emit(gpa, .OpBranch, .{ .target_label = merge_label });
5241
5242 try cg.beginSpvBlock(else_label);
5243 const else_next = try cg.genStructuredBody(.selection, else_body);
5244 const else_incoming: ControlFlow.Structured.Block.Incoming = .{
5245 .src_label = cg.block_label,
5246 .next_block = else_next,
5247 };
5248
5249 try cg.body.emit(gpa, .OpBranch, .{ .target_label = merge_label });
5250
5251 try cg.beginSpvBlock(merge_label);
5252 const next_block = try cg.structuredNextBlock(&.{ then_incoming, else_incoming });
5253
5254 try cg.structuredBreak(next_block);
5255 },
5256 .unstructured => {
5257 try cg.body.emit(gpa, .OpBranchConditional, .{
5258 .condition = condition_id,
5259 .true_label = then_label,
5260 .false_label = else_label,
5261 });
5262
5263 try cg.beginSpvBlock(then_label);
5264 try cg.genBody(then_body);
5265 try cg.beginSpvBlock(else_label);
5266 try cg.genBody(else_body);
5267 },
5268 }
5269}
5270
5271fn airLoop(cg: *CodeGen, inst: Air.Inst.Index) !void {
5272 const gpa = cg.module.gpa;
5273 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
5274 const loop = cg.air.extraData(Air.Block, ty_pl.payload);
5275 const body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[loop.end..][0..loop.data.body_len]);
5276
5277 const body_label = cg.module.allocId();
5278
5279 switch (cg.control_flow) {
5280 .structured => {
5281 const header_label = cg.module.allocId();
5282 const merge_label = cg.module.allocId();
5283 const continue_label = cg.module.allocId();
5284
5285 // The back-edge must point to the loop header, so generate a separate block for the
5286 // loop header so that we don't accidentally include some instructions from there
5287 // in the loop.
5288
5289 try cg.body.emit(gpa, .OpBranch, .{ .target_label = header_label });
5290 try cg.beginSpvBlock(header_label);
5291
5292 // Emit loop header and jump to loop body
5293 try cg.body.emit(gpa, .OpLoopMerge, .{
5294 .merge_block = merge_label,
5295 .continue_target = continue_label,
5296 .loop_control = .{},
5297 });
5298
5299 try cg.body.emit(gpa, .OpBranch, .{ .target_label = body_label });
5300
5301 try cg.beginSpvBlock(body_label);
5302
5303 const next_block = try cg.genStructuredBody(.{ .loop = .{
5304 .merge_label = merge_label,
5305 .continue_label = continue_label,
5306 } }, body);
5307 try cg.structuredBreak(next_block);
5308
5309 try cg.beginSpvBlock(continue_label);
5310
5311 try cg.body.emit(gpa, .OpBranch, .{ .target_label = header_label });
5312 },
5313 .unstructured => {
5314 try cg.body.emit(gpa, .OpBranch, .{ .target_label = body_label });
5315 try cg.beginSpvBlock(body_label);
5316 try cg.genBody(body);
5317
5318 try cg.body.emit(gpa, .OpBranch, .{ .target_label = body_label });
5319 },
5320 }
5321}
5322
5323fn airLoad(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
5324 const zcu = cg.module.zcu;
5325 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5326 const ptr_ty = cg.typeOf(ty_op.operand);
5327 const elem_ty = cg.typeOfIndex(inst);
5328 const operand = try cg.resolve(ty_op.operand);
5329 if (!ptr_ty.isVolatilePtr(zcu) and cg.liveness.isUnused(inst)) return null;
5330
5331 return try cg.load(elem_ty, operand, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) });
5332}
5333
5334fn airStore(cg: *CodeGen, inst: Air.Inst.Index) !void {
5335 const zcu = cg.module.zcu;
5336 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
5337 const ptr_ty = cg.typeOf(bin_op.lhs);
5338 const elem_ty = ptr_ty.childType(zcu);
5339 const ptr = try cg.resolve(bin_op.lhs);
5340 const value = try cg.resolve(bin_op.rhs);
5341
5342 try cg.store(elem_ty, ptr, value, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) });
5343}
5344
5345fn airRet(cg: *CodeGen, inst: Air.Inst.Index) !void {
5346 const gpa = cg.module.gpa;
5347 const zcu = cg.module.zcu;
5348 const operand = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
5349 const ret_ty = cg.typeOf(operand);
5350 if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
5351 const fn_info = zcu.typeToFunc(zcu.navValue(cg.owner_nav).typeOf(zcu)).?;
5352 if (Type.fromInterned(fn_info.return_type).isError(zcu)) {
5353 // Functions with an empty error set are emitted with an error code
5354 // return type and return zero so they can be function pointers coerced
5355 // to functions that return anyerror.
5356 const no_err_id = try cg.constInt(.anyerror, 0);
5357 return try cg.body.emit(gpa, .OpReturnValue, .{ .value = no_err_id });
5358 } else {
5359 return try cg.body.emit(gpa, .OpReturn, {});
5360 }
5361 }
5362
5363 const operand_id = try cg.resolve(operand);
5364 try cg.body.emit(gpa, .OpReturnValue, .{ .value = operand_id });
5365}
5366
5367fn airRetLoad(cg: *CodeGen, inst: Air.Inst.Index) !void {
5368 const gpa = cg.module.gpa;
5369 const zcu = cg.module.zcu;
5370 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
5371 const ptr_ty = cg.typeOf(un_op);
5372 const ret_ty = ptr_ty.childType(zcu);
5373
5374 if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
5375 const fn_info = zcu.typeToFunc(zcu.navValue(cg.owner_nav).typeOf(zcu)).?;
5376 if (Type.fromInterned(fn_info.return_type).isError(zcu)) {
5377 // Functions with an empty error set are emitted with an error code
5378 // return type and return zero so they can be function pointers coerced
5379 // to functions that return anyerror.
5380 const no_err_id = try cg.constInt(.anyerror, 0);
5381 return try cg.body.emit(gpa, .OpReturnValue, .{ .value = no_err_id });
5382 } else {
5383 return try cg.body.emit(gpa, .OpReturn, {});
5384 }
5385 }
5386
5387 const ptr = try cg.resolve(un_op);
5388 const value = try cg.load(ret_ty, ptr, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) });
5389 try cg.body.emit(gpa, .OpReturnValue, .{
5390 .value = value,
5391 });
5392}
5393
5394fn airTry(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
5395 const gpa = cg.module.gpa;
5396 const zcu = cg.module.zcu;
5397 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
5398 const err_union_id = try cg.resolve(pl_op.operand);
5399 const extra = cg.air.extraData(Air.Try, pl_op.payload);
5400 const body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.body_len]);
5401
5402 const err_union_ty = cg.typeOf(pl_op.operand);
5403 const payload_ty = cg.typeOfIndex(inst);
5404
5405 const bool_ty_id = try cg.resolveType(.bool, .direct);
5406
5407 const eu_layout = cg.errorUnionLayout(payload_ty);
5408
5409 if (!err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {
5410 const err_id = if (eu_layout.payload_has_bits)
5411 try cg.extractField(.anyerror, err_union_id, eu_layout.errorFieldIndex())
5412 else
5413 err_union_id;
5414
5415 const zero_id = try cg.constInt(.anyerror, 0);
5416 const is_err_id = cg.module.allocId();
5417 try cg.body.emit(gpa, .OpINotEqual, .{
5418 .id_result_type = bool_ty_id,
5419 .id_result = is_err_id,
5420 .operand_1 = err_id,
5421 .operand_2 = zero_id,
5422 });
5423
5424 // When there is an error, we must evaluate `body`. Otherwise we must continue
5425 // with the current body.
5426 // Just generate a new block here, then generate a new block inline for the remainder of the body.
5427
5428 const err_block = cg.module.allocId();
5429 const ok_block = cg.module.allocId();
5430
5431 switch (cg.control_flow) {
5432 .structured => {
5433 // According to AIR documentation, this block is guaranteed
5434 // to not break and end in a return instruction. Thus,
5435 // for structured control flow, we can just naively use
5436 // the ok block as the merge block here.
5437 try cg.body.emit(gpa, .OpSelectionMerge, .{
5438 .merge_block = ok_block,
5439 .selection_control = .{},
5440 });
5441 },
5442 .unstructured => {},
5443 }
5444
5445 try cg.body.emit(gpa, .OpBranchConditional, .{
5446 .condition = is_err_id,
5447 .true_label = err_block,
5448 .false_label = ok_block,
5449 });
5450
5451 try cg.beginSpvBlock(err_block);
5452 try cg.genBody(body);
5453
5454 try cg.beginSpvBlock(ok_block);
5455 }
5456
5457 if (!eu_layout.payload_has_bits) {
5458 return null;
5459 }
5460
5461 // Now just extract the payload, if required.
5462 return try cg.extractField(payload_ty, err_union_id, eu_layout.payloadFieldIndex());
5463}
5464
5465fn airErrUnionErr(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
5466 const zcu = cg.module.zcu;
5467 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5468 const operand_id = try cg.resolve(ty_op.operand);
5469 const err_union_ty = cg.typeOf(ty_op.operand);
5470 const err_ty_id = try cg.resolveType(.anyerror, .direct);
5471
5472 if (err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {
5473 // No error possible, so just return undefined.
5474 return try cg.module.constUndef(err_ty_id);
5475 }
5476
5477 const payload_ty = err_union_ty.errorUnionPayload(zcu);
5478 const eu_layout = cg.errorUnionLayout(payload_ty);
5479
5480 if (!eu_layout.payload_has_bits) {
5481 // If no payload, error union is represented by error set.
5482 return operand_id;
5483 }
5484
5485 return try cg.extractField(.anyerror, operand_id, eu_layout.errorFieldIndex());
5486}
5487
5488fn airErrUnionPayload(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
5489 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5490 const operand_id = try cg.resolve(ty_op.operand);
5491 const payload_ty = cg.typeOfIndex(inst);
5492 const eu_layout = cg.errorUnionLayout(payload_ty);
5493
5494 if (!eu_layout.payload_has_bits) {
5495 return null; // No error possible.
5496 }
5497
5498 return try cg.extractField(payload_ty, operand_id, eu_layout.payloadFieldIndex());
5499}
5500
5501fn airWrapErrUnionErr(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
5502 const zcu = cg.module.zcu;
5503 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5504 const err_union_ty = cg.typeOfIndex(inst);
5505 const payload_ty = err_union_ty.errorUnionPayload(zcu);
5506 const operand_id = try cg.resolve(ty_op.operand);
5507 const eu_layout = cg.errorUnionLayout(payload_ty);
5508
5509 if (!eu_layout.payload_has_bits) {
5510 return operand_id;
5511 }
5512
5513 const payload_ty_id = try cg.resolveType(payload_ty, .indirect);
5514
5515 var members: [2]Id = undefined;
5516 members[eu_layout.errorFieldIndex()] = operand_id;
5517 members[eu_layout.payloadFieldIndex()] = try cg.module.constUndef(payload_ty_id);
5518
5519 var types: [2]Type = undefined;
5520 types[eu_layout.errorFieldIndex()] = .anyerror;
5521 types[eu_layout.payloadFieldIndex()] = payload_ty;
5522
5523 const err_union_ty_id = try cg.resolveType(err_union_ty, .direct);
5524 return try cg.constructComposite(err_union_ty_id, &members);
5525}
5526
5527fn airWrapErrUnionPayload(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
5528 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5529 const err_union_ty = cg.typeOfIndex(inst);
5530 const operand_id = try cg.resolve(ty_op.operand);
5531 const payload_ty = cg.typeOf(ty_op.operand);
5532 const eu_layout = cg.errorUnionLayout(payload_ty);
5533
5534 if (!eu_layout.payload_has_bits) {
5535 return try cg.constInt(.anyerror, 0);
5536 }
5537
5538 var members: [2]Id = undefined;
5539 members[eu_layout.errorFieldIndex()] = try cg.constInt(.anyerror, 0);
5540 members[eu_layout.payloadFieldIndex()] = try cg.convertToIndirect(payload_ty, operand_id);
5541
5542 var types: [2]Type = undefined;
5543 types[eu_layout.errorFieldIndex()] = .anyerror;
5544 types[eu_layout.payloadFieldIndex()] = payload_ty;
5545
5546 const err_union_ty_id = try cg.resolveType(err_union_ty, .direct);
5547 return try cg.constructComposite(err_union_ty_id, &members);
5548}
5549
5550fn airIsNull(cg: *CodeGen, inst: Air.Inst.Index, is_pointer: bool, pred: enum { is_null, is_non_null }) !?Id {
5551 const zcu = cg.module.zcu;
5552 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
5553 const operand_id = try cg.resolve(un_op);
5554 const operand_ty = cg.typeOf(un_op);
5555 const optional_ty = if (is_pointer) operand_ty.childType(zcu) else operand_ty;
5556 const payload_ty = optional_ty.optionalChild(zcu);
5557
5558 const bool_ty_id = try cg.resolveType(.bool, .direct);
5559
5560 if (optional_ty.optionalReprIsPayload(zcu)) {
5561 // Pointer payload represents nullability: pointer or slice.
5562 const loaded_id = if (is_pointer)
5563 try cg.load(optional_ty, operand_id, .{})
5564 else
5565 operand_id;
5566
5567 const ptr_ty = if (payload_ty.isSlice(zcu))
5568 payload_ty.slicePtrFieldType(zcu)
5569 else
5570 payload_ty;
5571
5572 const ptr_id = if (payload_ty.isSlice(zcu))
5573 try cg.extractField(ptr_ty, loaded_id, 0)
5574 else
5575 loaded_id;
5576
5577 const ptr_ty_id = try cg.resolveType(ptr_ty, .direct);
5578 const null_id = try cg.module.constNull(ptr_ty_id);
5579 const null_tmp: Temporary = .init(ptr_ty, null_id);
5580 const ptr: Temporary = .init(ptr_ty, ptr_id);
5581
5582 const op: std.math.CompareOperator = switch (pred) {
5583 .is_null => .eq,
5584 .is_non_null => .neq,
5585 };
5586 const result = try cg.cmp(op, ptr, null_tmp);
5587 return try result.materialize(cg);
5588 }
5589
5590 const is_non_null_id = blk: {
5591 if (is_pointer) {
5592 if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
5593 const storage_class = cg.module.storageClass(operand_ty.ptrAddressSpace(zcu));
5594 const bool_indirect_ty_id = try cg.resolveType(.bool, .indirect);
5595 const bool_ptr_ty_id = try cg.module.ptrType(bool_indirect_ty_id, storage_class);
5596 const tag_ptr_id = try cg.accessChain(bool_ptr_ty_id, operand_id, &.{1});
5597 break :blk try cg.load(.bool, tag_ptr_id, .{});
5598 }
5599
5600 break :blk try cg.load(.bool, operand_id, .{});
5601 }
5602
5603 break :blk if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu))
5604 try cg.extractField(.bool, operand_id, 1)
5605 else
5606 // Optional representation is bool indicating whether the optional is set
5607 // Optionals with no payload are represented as an (indirect) bool, so convert
5608 // it back to the direct bool here.
5609 try cg.convertToDirect(.bool, operand_id);
5610 };
5611
5612 return switch (pred) {
5613 .is_null => blk: {
5614 // Invert condition
5615 const result_id = cg.module.allocId();
5616 try cg.body.emit(cg.module.gpa, .OpLogicalNot, .{
5617 .id_result_type = bool_ty_id,
5618 .id_result = result_id,
5619 .operand = is_non_null_id,
5620 });
5621 break :blk result_id;
5622 },
5623 .is_non_null => is_non_null_id,
5624 };
5625}
5626
5627fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, pred: enum { is_err, is_non_err }) !?Id {
5628 const zcu = cg.module.zcu;
5629 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
5630 const operand_id = try cg.resolve(un_op);
5631 const err_union_ty = cg.typeOf(un_op);
5632
5633 if (err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {
5634 return try cg.constBool(pred == .is_non_err, .direct);
5635 }
5636
5637 const payload_ty = err_union_ty.errorUnionPayload(zcu);
5638 const eu_layout = cg.errorUnionLayout(payload_ty);
5639 const bool_ty_id = try cg.resolveType(.bool, .direct);
5640
5641 const error_id = if (!eu_layout.payload_has_bits)
5642 operand_id
5643 else
5644 try cg.extractField(.anyerror, operand_id, eu_layout.errorFieldIndex());
5645
5646 const result_id = cg.module.allocId();
5647 switch (pred) {
5648 inline else => |pred_ct| try cg.body.emit(
5649 cg.module.gpa,
5650 switch (pred_ct) {
5651 .is_err => .OpINotEqual,
5652 .is_non_err => .OpIEqual,
5653 },
5654 .{
5655 .id_result_type = bool_ty_id,
5656 .id_result = result_id,
5657 .operand_1 = error_id,
5658 .operand_2 = try cg.constInt(.anyerror, 0),
5659 },
5660 ),
5661 }
5662 return result_id;
5663}
5664
5665fn airUnwrapOptional(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
5666 const zcu = cg.module.zcu;
5667 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5668 const operand_id = try cg.resolve(ty_op.operand);
5669 const optional_ty = cg.typeOf(ty_op.operand);
5670 const payload_ty = cg.typeOfIndex(inst);
5671
5672 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) return null;
5673
5674 if (optional_ty.optionalReprIsPayload(zcu)) {
5675 return operand_id;
5676 }
5677
5678 return try cg.extractField(payload_ty, operand_id, 0);
5679}
5680
5681fn airUnwrapOptionalPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
5682 const zcu = cg.module.zcu;
5683 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5684 const operand_id = try cg.resolve(ty_op.operand);
5685 const operand_ty = cg.typeOf(ty_op.operand);
5686 const optional_ty = operand_ty.childType(zcu);
5687 const payload_ty = optional_ty.optionalChild(zcu);
5688 const result_ty = cg.typeOfIndex(inst);
5689 const result_ty_id = try cg.resolveType(result_ty, .direct);
5690
5691 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
5692 // There is no payload, but we still need to return a valid pointer.
5693 // We can just return anything here, so just return a pointer to the operand.
5694 return try cg.bitCast(result_ty, operand_ty, operand_id);
5695 }
5696
5697 if (optional_ty.optionalReprIsPayload(zcu)) {
5698 // They are the same value.
5699 return try cg.bitCast(result_ty, operand_ty, operand_id);
5700 }
5701
5702 return try cg.accessChain(result_ty_id, operand_id, &.{0});
5703}
5704
5705fn airWrapOptional(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
5706 const zcu = cg.module.zcu;
5707 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5708 const payload_ty = cg.typeOf(ty_op.operand);
5709
5710 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
5711 return try cg.constBool(true, .indirect);
5712 }
5713
5714 const operand_id = try cg.resolve(ty_op.operand);
5715
5716 const optional_ty = cg.typeOfIndex(inst);
5717 if (optional_ty.optionalReprIsPayload(zcu)) {
5718 return operand_id;
5719 }
5720
5721 const payload_id = try cg.convertToIndirect(payload_ty, operand_id);
5722 const members = [_]Id{ payload_id, try cg.constBool(true, .indirect) };
5723 const optional_ty_id = try cg.resolveType(optional_ty, .direct);
5724 return try cg.constructComposite(optional_ty_id, &members);
5725}
5726
5727fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void {
5728 const gpa = cg.module.gpa;
5729 const pt = cg.pt;
5730 const zcu = cg.module.zcu;
5731 const target = cg.module.zcu.getTarget();
5732 const switch_br = cg.air.unwrapSwitch(inst);
5733 const cond_ty = cg.typeOf(switch_br.operand);
5734 const cond = try cg.resolve(switch_br.operand);
5735 var cond_indirect = try cg.convertToIndirect(cond_ty, cond);
5736
5737 const cond_words: u32 = switch (cond_ty.zigTypeTag(zcu)) {
5738 .bool, .error_set => 1,
5739 .int => blk: {
5740 const bits = cond_ty.intInfo(zcu).bits;
5741 const backing_bits, const big_int = cg.module.backingIntBits(bits);
5742 if (big_int) return cg.todo("implement composite int switch", .{});
5743 break :blk if (backing_bits <= 32) 1 else 2;
5744 },
5745 .@"enum" => blk: {
5746 const int_ty = cond_ty.intTagType(zcu);
5747 const int_info = int_ty.intInfo(zcu);
5748 const backing_bits, const big_int = cg.module.backingIntBits(int_info.bits);
5749 if (big_int) return cg.todo("implement composite int switch", .{});
5750 break :blk if (backing_bits <= 32) 1 else 2;
5751 },
5752 .pointer => blk: {
5753 cond_indirect = try cg.intFromPtr(cond_indirect);
5754 break :blk target.ptrBitWidth() / 32;
5755 },
5756 // TODO: Figure out which types apply here, and work around them as we can only do integers.
5757 else => return cg.todo("implement switch for type {s}", .{@tagName(cond_ty.zigTypeTag(zcu))}),
5758 };
5759
5760 const num_cases = switch_br.cases_len;
5761
5762 // Compute the total number of arms that we need.
5763 // Zig switches are grouped by condition, so we need to loop through all of them
5764 const num_conditions = blk: {
5765 var num_conditions: u32 = 0;
5766 var it = switch_br.iterateCases();
5767 while (it.next()) |case| {
5768 if (case.ranges.len > 0) return cg.todo("switch with ranges", .{});
5769 num_conditions += @intCast(case.items.len);
5770 }
5771 break :blk num_conditions;
5772 };
5773
5774 // First, pre-allocate the labels for the cases.
5775 const case_labels = cg.module.allocIds(num_cases);
5776 // We always need the default case - if zig has none, we will generate unreachable there.
5777 const default = cg.module.allocId();
5778
5779 const merge_label = switch (cg.control_flow) {
5780 .structured => cg.module.allocId(),
5781 .unstructured => null,
5782 };
5783
5784 if (cg.control_flow == .structured) {
5785 try cg.body.emit(gpa, .OpSelectionMerge, .{
5786 .merge_block = merge_label.?,
5787 .selection_control = .{},
5788 });
5789 }
5790
5791 // Emit the instruction before generating the blocks.
5792 try cg.body.emitRaw(gpa, .OpSwitch, 2 + (cond_words + 1) * num_conditions);
5793 cg.body.writeOperand(Id, cond_indirect);
5794 cg.body.writeOperand(Id, default);
5795
5796 // Emit each of the cases
5797 {
5798 var it = switch_br.iterateCases();
5799 while (it.next()) |case| {
5800 // SPIR-V needs a literal here, which' width depends on the case condition.
5801 const label = case_labels.at(case.idx);
5802
5803 for (case.items) |item| {
5804 const value = (try cg.air.value(item, pt)) orelse unreachable;
5805 const int_val: u64 = switch (cond_ty.zigTypeTag(zcu)) {
5806 .bool, .int => if (cond_ty.isSignedInt(zcu)) @bitCast(value.toSignedInt(zcu)) else value.toUnsignedInt(zcu),
5807 .@"enum" => blk: {
5808 // TODO: figure out of cond_ty is correct (something with enum literals)
5809 break :blk (try value.intFromEnum(cond_ty, pt)).toUnsignedInt(zcu); // TODO: composite integer constants
5810 },
5811 .error_set => value.getErrorInt(zcu),
5812 .pointer => value.toUnsignedInt(zcu),
5813 else => unreachable,
5814 };
5815 const int_lit: spec.LiteralContextDependentNumber = switch (cond_words) {
5816 1 => .{ .uint32 = @intCast(int_val) },
5817 2 => .{ .uint64 = int_val },
5818 else => unreachable,
5819 };
5820 cg.body.writeOperand(spec.LiteralContextDependentNumber, int_lit);
5821 cg.body.writeOperand(Id, label);
5822 }
5823 }
5824 }
5825
5826 var incoming_structured_blocks: std.ArrayListUnmanaged(ControlFlow.Structured.Block.Incoming) = .empty;
5827 defer incoming_structured_blocks.deinit(gpa);
5828
5829 if (cg.control_flow == .structured) {
5830 try incoming_structured_blocks.ensureUnusedCapacity(gpa, num_cases + 1);
5831 }
5832
5833 // Now, finally, we can start emitting each of the cases.
5834 var it = switch_br.iterateCases();
5835 while (it.next()) |case| {
5836 const label = case_labels.at(case.idx);
5837
5838 try cg.beginSpvBlock(label);
5839
5840 switch (cg.control_flow) {
5841 .structured => {
5842 const next_block = try cg.genStructuredBody(.selection, case.body);
5843 incoming_structured_blocks.appendAssumeCapacity(.{
5844 .src_label = cg.block_label,
5845 .next_block = next_block,
5846 });
5847
5848 try cg.body.emit(gpa, .OpBranch, .{ .target_label = merge_label.? });
5849 },
5850 .unstructured => {
5851 try cg.genBody(case.body);
5852 },
5853 }
5854 }
5855
5856 const else_body = it.elseBody();
5857 try cg.beginSpvBlock(default);
5858 if (else_body.len != 0) {
5859 switch (cg.control_flow) {
5860 .structured => {
5861 const next_block = try cg.genStructuredBody(.selection, else_body);
5862 incoming_structured_blocks.appendAssumeCapacity(.{
5863 .src_label = cg.block_label,
5864 .next_block = next_block,
5865 });
5866
5867 try cg.body.emit(gpa, .OpBranch, .{ .target_label = merge_label.? });
5868 },
5869 .unstructured => {
5870 try cg.genBody(else_body);
5871 },
5872 }
5873 } else {
5874 try cg.body.emit(gpa, .OpUnreachable, {});
5875 }
5876
5877 if (cg.control_flow == .structured) {
5878 try cg.beginSpvBlock(merge_label.?);
5879 const next_block = try cg.structuredNextBlock(incoming_structured_blocks.items);
5880 try cg.structuredBreak(next_block);
5881 }
5882}
5883
5884fn airUnreach(cg: *CodeGen) !void {
5885 try cg.body.emit(cg.module.gpa, .OpUnreachable, {});
5886}
5887
5888fn airDbgStmt(cg: *CodeGen, inst: Air.Inst.Index) !void {
5889 const zcu = cg.module.zcu;
5890 const dbg_stmt = cg.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt;
5891 const path = zcu.navFileScope(cg.owner_nav).sub_file_path;
5892
5893 if (zcu.comp.config.root_strip) return;
5894
5895 try cg.body.emit(cg.module.gpa, .OpLine, .{
5896 .file = try cg.module.debugString(path),
5897 .line = cg.base_line + dbg_stmt.line + 1,
5898 .column = dbg_stmt.column + 1,
5899 });
5900}
5901
5902fn airDbgInlineBlock(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
5903 const zcu = cg.module.zcu;
5904 const inst_datas = cg.air.instructions.items(.data);
5905 const extra = cg.air.extraData(Air.DbgInlineBlock, inst_datas[@intFromEnum(inst)].ty_pl.payload);
5906 const old_base_line = cg.base_line;
5907 defer cg.base_line = old_base_line;
5908 cg.base_line = zcu.navSrcLine(zcu.funcInfo(extra.data.func).owner_nav);
5909 return cg.lowerBlock(inst, @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.body_len]));
5910}
5911
5912fn airDbgVar(cg: *CodeGen, inst: Air.Inst.Index) !void {
5913 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
5914 const target_id = try cg.resolve(pl_op.operand);
5915 const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload);
5916 try cg.module.debugName(target_id, name.toSlice(cg.air));
5917}
5918
5919fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
5920 const gpa = cg.module.gpa;
5921 const zcu = cg.module.zcu;
5922 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
5923 const extra = cg.air.extraData(Air.Asm, ty_pl.payload);
5924
5925 const is_volatile = extra.data.flags.is_volatile;
5926 const outputs_len = extra.data.flags.outputs_len;
5927
5928 if (!is_volatile and cg.liveness.isUnused(inst)) return null;
5929
5930 var extra_i: usize = extra.end;
5931 const outputs: []const Air.Inst.Ref = @ptrCast(cg.air.extra.items[extra_i..][0..outputs_len]);
5932 extra_i += outputs.len;
5933 const inputs: []const Air.Inst.Ref = @ptrCast(cg.air.extra.items[extra_i..][0..extra.data.inputs_len]);
5934 extra_i += inputs.len;
5935
5936 if (outputs.len > 1) {
5937 return cg.todo("implement inline asm with more than 1 output", .{});
5938 }
5939
5940 var ass: Assembler = .{ .cg = cg };
5941 defer ass.deinit();
5942
5943 var output_extra_i = extra_i;
5944 for (outputs) |output| {
5945 if (output != .none) {
5946 return cg.todo("implement inline asm with non-returned output", .{});
5947 }
5948 const extra_bytes = std.mem.sliceAsBytes(cg.air.extra.items[extra_i..]);
5949 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(cg.air.extra.items[extra_i..]), 0);
5950 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
5951 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
5952 // TODO: Record output and use it somewhere.
5953 }
5954
5955 for (inputs) |input| {
5956 const extra_bytes = std.mem.sliceAsBytes(cg.air.extra.items[extra_i..]);
5957 const constraint = std.mem.sliceTo(extra_bytes, 0);
5958 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
5959 // This equation accounts for the fact that even if we have exactly 4 bytes
5960 // for the string, we still use the next u32 for the null terminator.
5961 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
5962
5963 const input_ty = cg.typeOf(input);
5964
5965 if (std.mem.eql(u8, constraint, "c")) {
5966 // constant
5967 const val = (try cg.air.value(input, cg.pt)) orelse {
5968 return cg.fail("assembly inputs with 'c' constraint have to be compile-time known", .{});
5969 };
5970
5971 // TODO: This entire function should be handled a bit better...
5972 const ip = &zcu.intern_pool;
5973 switch (ip.indexToKey(val.toIntern())) {
5974 .int_type,
5975 .ptr_type,
5976 .array_type,
5977 .vector_type,
5978 .opt_type,
5979 .anyframe_type,
5980 .error_union_type,
5981 .simple_type,
5982 .struct_type,
5983 .union_type,
5984 .opaque_type,
5985 .enum_type,
5986 .func_type,
5987 .error_set_type,
5988 .inferred_error_set_type,
5989 => unreachable, // types, not values
5990
5991 .undef => return cg.fail("assembly input with 'c' constraint cannot be undefined", .{}),
5992
5993 .int => try ass.value_map.put(gpa, name, .{ .constant = @intCast(val.toUnsignedInt(zcu)) }),
5994 .enum_literal => |str| try ass.value_map.put(gpa, name, .{ .string = str.toSlice(ip) }),
5995
5996 else => unreachable, // TODO
5997 }
5998 } else if (std.mem.eql(u8, constraint, "t")) {
5999 // type
6000 if (input_ty.zigTypeTag(zcu) == .type) {
6001 // This assembly input is a type instead of a value.
6002 // That's fine for now, just make sure to resolve it as such.
6003 const val = (try cg.air.value(input, cg.pt)).?;
6004 const ty_id = try cg.resolveType(val.toType(), .direct);
6005 try ass.value_map.put(gpa, name, .{ .ty = ty_id });
6006 } else {
6007 const ty_id = try cg.resolveType(input_ty, .direct);
6008 try ass.value_map.put(gpa, name, .{ .ty = ty_id });
6009 }
6010 } else {
6011 if (input_ty.zigTypeTag(zcu) == .type) {
6012 return cg.fail("use the 't' constraint to supply types to SPIR-V inline assembly", .{});
6013 }
6014
6015 const val_id = try cg.resolve(input);
6016 try ass.value_map.put(gpa, name, .{ .value = val_id });
6017 }
6018 }
6019
6020 // TODO: do something with clobbers
6021 _ = extra.data.clobbers;
6022
6023 const asm_source = std.mem.sliceAsBytes(cg.air.extra.items[extra_i..])[0..extra.data.source_len];
6024
6025 ass.assemble(asm_source) catch |err| switch (err) {
6026 error.AssembleFail => {
6027 // TODO: For now the compiler only supports a single error message per decl,
6028 // so to translate the possible multiple errors from the assembler, emit
6029 // them as notes here.
6030 // TODO: Translate proper error locations.
6031 assert(ass.errors.items.len != 0);
6032 assert(cg.error_msg == null);
6033 const src_loc = zcu.navSrcLoc(cg.owner_nav);
6034 cg.error_msg = try Zcu.ErrorMsg.create(zcu.gpa, src_loc, "failed to assemble SPIR-V inline assembly", .{});
6035 const notes = try zcu.gpa.alloc(Zcu.ErrorMsg, ass.errors.items.len);
6036
6037 // Sub-scope to prevent `return error.CodegenFail` from running the errdefers.
6038 {
6039 errdefer zcu.gpa.free(notes);
6040 var i: usize = 0;
6041 errdefer for (notes[0..i]) |*note| {
6042 note.deinit(zcu.gpa);
6043 };
6044
6045 while (i < ass.errors.items.len) : (i += 1) {
6046 notes[i] = try Zcu.ErrorMsg.init(zcu.gpa, src_loc, "{s}", .{ass.errors.items[i].msg});
6047 }
6048 }
6049 cg.error_msg.?.notes = notes;
6050 return error.CodegenFail;
6051 },
6052 else => |others| return others,
6053 };
6054
6055 for (outputs) |output| {
6056 _ = output;
6057 const extra_bytes = std.mem.sliceAsBytes(cg.air.extra.items[output_extra_i..]);
6058 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(cg.air.extra.items[output_extra_i..]), 0);
6059 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
6060 output_extra_i += (constraint.len + name.len + (2 + 3)) / 4;
6061
6062 const result = ass.value_map.get(name) orelse return {
6063 return cg.fail("invalid asm output '{s}'", .{name});
6064 };
6065
6066 switch (result) {
6067 .just_declared, .unresolved_forward_reference => unreachable,
6068 .ty => return cg.fail("cannot return spir-v type as value from assembly", .{}),
6069 .value => |ref| return ref,
6070 .constant, .string => return cg.fail("cannot return constant from assembly", .{}),
6071 }
6072
6073 // TODO: Multiple results
6074 // TODO: Check that the output type from assembly is the same as the type actually expected by Zig.
6075 }
6076
6077 return null;
6078}
6079
6080fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !?Id {
6081 _ = modifier;
6082
6083 const gpa = cg.module.gpa;
6084 const zcu = cg.module.zcu;
6085 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6086 const extra = cg.air.extraData(Air.Call, pl_op.payload);
6087 const args: []const Air.Inst.Ref = @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.args_len]);
6088 const callee_ty = cg.typeOf(pl_op.operand);
6089 const zig_fn_ty = switch (callee_ty.zigTypeTag(zcu)) {
6090 .@"fn" => callee_ty,
6091 .pointer => return cg.fail("cannot call function pointers", .{}),
6092 else => unreachable,
6093 };
6094 const fn_info = zcu.typeToFunc(zig_fn_ty).?;
6095 const return_type = fn_info.return_type;
6096
6097 const result_type_id = try cg.resolveFnReturnType(.fromInterned(return_type));
6098 const result_id = cg.module.allocId();
6099 const callee_id = try cg.resolve(pl_op.operand);
6100
6101 comptime assert(zig_call_abi_ver == 3);
6102
6103 const scratch_top = cg.id_scratch.items.len;
6104 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top);
6105 const params = try cg.id_scratch.addManyAsSlice(gpa, args.len);
6106
6107 var n_params: usize = 0;
6108 for (args) |arg| {
6109 // Note: resolve() might emit instructions, so we need to call it
6110 // before starting to emit OpFunctionCall instructions. Hence the
6111 // temporary params buffer.
6112 const arg_ty = cg.typeOf(arg);
6113 if (!arg_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;
6114 const arg_id = try cg.resolve(arg);
6115
6116 params[n_params] = arg_id;
6117 n_params += 1;
6118 }
6119
6120 try cg.body.emit(gpa, .OpFunctionCall, .{
6121 .id_result_type = result_type_id,
6122 .id_result = result_id,
6123 .function = callee_id,
6124 .id_ref_3 = params[0..n_params],
6125 });
6126
6127 if (cg.liveness.isUnused(inst) or !Type.fromInterned(return_type).hasRuntimeBitsIgnoreComptime(zcu)) {
6128 return null;
6129 }
6130
6131 return result_id;
6132}
6133
6134fn builtin3D(
6135 cg: *CodeGen,
6136 result_ty: Type,
6137 builtin: spec.BuiltIn,
6138 dimension: u32,
6139 out_of_range_value: anytype,
6140) !Id {
6141 const gpa = cg.module.gpa;
6142 if (dimension >= 3) return try cg.constInt(result_ty, out_of_range_value);
6143 const u32_ty_id = try cg.module.intType(.unsigned, 32);
6144 const vec_ty_id = try cg.module.vectorType(3, u32_ty_id);
6145 const ptr_ty_id = try cg.module.ptrType(vec_ty_id, .input);
6146 const spv_decl_index = try cg.module.builtin(ptr_ty_id, builtin, .input);
6147 try cg.module.decl_deps.append(gpa, spv_decl_index);
6148 const ptr_id = cg.module.declPtr(spv_decl_index).result_id;
6149 const vec_id = cg.module.allocId();
6150 try cg.body.emit(gpa, .OpLoad, .{
6151 .id_result_type = vec_ty_id,
6152 .id_result = vec_id,
6153 .pointer = ptr_id,
6154 });
6155 return try cg.extractVectorComponent(result_ty, vec_id, dimension);
6156}
6157
6158fn airWorkItemId(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
6159 if (cg.liveness.isUnused(inst)) return null;
6160 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6161 const dimension = pl_op.payload;
6162 return try cg.builtin3D(.u32, .local_invocation_id, dimension, 0);
6163}
6164
6165// TODO: this must be an OpConstant/OpSpec but even then the driver crashes.
6166fn airWorkGroupSize(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
6167 if (cg.liveness.isUnused(inst)) return null;
6168 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6169 const dimension = pl_op.payload;
6170 return try cg.builtin3D(.u32, .workgroup_id, dimension, 0);
6171}
6172
6173fn airWorkGroupId(cg: *CodeGen, inst: Air.Inst.Index) !?Id {
6174 if (cg.liveness.isUnused(inst)) return null;
6175 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6176 const dimension = pl_op.payload;
6177 return try cg.builtin3D(.u32, .workgroup_id, dimension, 0);
6178}
6179
6180fn typeOf(cg: *CodeGen, inst: Air.Inst.Ref) Type {
6181 const zcu = cg.module.zcu;
6182 return cg.air.typeOf(inst, &zcu.intern_pool);
6183}
6184
6185fn typeOfIndex(cg: *CodeGen, inst: Air.Inst.Index) Type {
6186 const zcu = cg.module.zcu;
6187 return cg.air.typeOfIndex(inst, &zcu.intern_pool);
6188}
src/codegen/spirv/Module.zig+582-423
......@@ -1,57 +1,100 @@
1//! This structure represents a SPIR-V (sections) module being compiled, and keeps track of all relevant information.
2//! That includes the actual instructions, the current result-id bound, and data structures for querying result-id's
3//! of data which needs to be persistent over different calls to Decl code generation.
1//! This structure represents a SPIR-V (sections) module being compiled, and keeps
2//! track of all relevant information. That includes the actual instructions, the
3//! current result-id bound, and data structures for querying result-id's of data
4//! which needs to be persistent over different calls to Decl code generation.
45//!
5//! A SPIR-V binary module supports both little- and big endian layout. The layout is detected by the magic word in the
6//! header. Therefore, we can ignore any byte order throughout the implementation, and just use the host byte order,
7//! and make this a problem for the consumer.
8const Module = @This();
9
6//! A SPIR-V binary module supports both little- and big endian layout. The layout
7//! is detected by the magic word in the header. Therefore, we can ignore any byte
8//! order throughout the implementation, and just use the host byte order, and make
9//! this a problem for the consumer.
1010const std = @import("std");
1111const Allocator = std.mem.Allocator;
1212const assert = std.debug.assert;
13const autoHashStrat = std.hash.autoHashStrat;
14const Wyhash = std.hash.Wyhash;
1513
14const Zcu = @import("../../Zcu.zig");
15const InternPool = @import("../../InternPool.zig");
16const Section = @import("Section.zig");
1617const spec = @import("spec.zig");
1718const Word = spec.Word;
1819const Id = spec.Id;
1920
20const Section = @import("Section.zig");
21const Module = @This();
2122
22/// This structure represents a function that isc in-progress of being emitted.
23/// Commonly, the contents of this structure will be merged with the appropriate
24/// sections of the module and re-used. Note that the SPIR-V module system makes
25/// no attempt of compacting result-id's, so any Fn instance should ultimately
26/// be merged into the module it's result-id's are allocated from.
27pub const Fn = struct {
28 /// The prologue of this function; this section contains the function's
29 /// OpFunction, OpFunctionParameter, OpLabel and OpVariable instructions, and
30 /// is separated from the actual function contents as OpVariable instructions
31 /// must appear in the first block of a function definition.
32 prologue: Section = .{},
33 /// The code of the body of this function.
34 /// This section should also contain the OpFunctionEnd instruction marking
35 /// the end of this function definition.
36 body: Section = .{},
37 /// The decl dependencies that this function depends on.
38 decl_deps: std.AutoArrayHashMapUnmanaged(Decl.Index, void) = .empty,
39
40 /// Reset this function without deallocating resources, so that
41 /// it may be used to emit code for another function.
42 pub fn reset(self: *Fn) void {
43 self.prologue.reset();
44 self.body.reset();
45 self.decl_deps.clearRetainingCapacity();
46 }
23gpa: Allocator,
24arena: Allocator,
25zcu: *Zcu,
26nav_link: std.AutoHashMapUnmanaged(InternPool.Nav.Index, Decl.Index) = .empty,
27uav_link: std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Decl.Index) = .empty,
28intern_map: std.AutoHashMapUnmanaged(struct { InternPool.Index, Repr }, Id) = .empty,
29decls: std.ArrayListUnmanaged(Decl) = .empty,
30decl_deps: std.ArrayListUnmanaged(Decl.Index) = .empty,
31entry_points: std.AutoArrayHashMapUnmanaged(Id, EntryPoint) = .empty,
32/// This map serves a dual purpose:
33/// - It keeps track of pointers that are currently being emitted, so that we can tell
34/// if they are recursive and need an OpTypeForwardPointer.
35/// - It caches pointers by child-type. This is required because sometimes we rely on
36/// ID-equality for pointers, and pointers constructed via `ptrType()` aren't interned
37/// via the usual `intern_map` mechanism.
38ptr_types: std.AutoHashMapUnmanaged(struct { Id, spec.StorageClass }, Id) = .{},
39/// For test declarations compiled for Vulkan target, we have to add a buffer.
40/// We only need to generate this once, this holds the link information related to that.
41error_buffer: ?Decl.Index = null,
42/// SPIR-V instructions return result-ids.
43/// This variable holds the module-wide counter for these.
44next_result_id: Word = 1,
45/// Some types shouldn't be emitted more than one time, but cannot be caught by
46/// the `intern_map` during codegen. Sometimes, IDs are compared to check if
47/// types are the same, so we can't delay until the dedup pass. Therefore,
48/// this is an ad-hoc structure to cache types where required.
49/// According to the SPIR-V specification, section 2.8, this includes all non-aggregate
50/// non-pointer types.
51/// Additionally, this is used for other values which can be cached, for example,
52/// built-in variables.
53cache: struct {
54 bool_type: ?Id = null,
55 void_type: ?Id = null,
56 opaque_types: std.StringHashMapUnmanaged(Id) = .empty,
57 int_types: std.AutoHashMapUnmanaged(std.builtin.Type.Int, Id) = .empty,
58 float_types: std.AutoHashMapUnmanaged(std.builtin.Type.Float, Id) = .empty,
59 vector_types: std.AutoHashMapUnmanaged(struct { Id, u32 }, Id) = .empty,
60 array_types: std.AutoHashMapUnmanaged(struct { Id, Id }, Id) = .empty,
61 struct_types: std.ArrayHashMapUnmanaged(StructType, Id, StructType.HashContext, true) = .empty,
62 fn_types: std.ArrayHashMapUnmanaged(FnType, Id, FnType.HashContext, true) = .empty,
4763
48 /// Free the resources owned by this function.
49 pub fn deinit(self: *Fn, a: Allocator) void {
50 self.prologue.deinit(a);
51 self.body.deinit(a);
52 self.decl_deps.deinit(a);
53 self.* = undefined;
54 }
64 capabilities: std.AutoHashMapUnmanaged(spec.Capability, void) = .empty,
65 extensions: std.StringHashMapUnmanaged(void) = .empty,
66 extended_instruction_set: std.AutoHashMapUnmanaged(spec.InstructionSet, Id) = .empty,
67 decorations: std.AutoHashMapUnmanaged(struct { Id, spec.Decoration }, void) = .empty,
68 builtins: std.AutoHashMapUnmanaged(struct { spec.BuiltIn, spec.StorageClass }, Decl.Index) = .empty,
69 strings: std.StringArrayHashMapUnmanaged(Id) = .empty,
70
71 bool_const: [2]?Id = .{ null, null },
72 constants: std.ArrayHashMapUnmanaged(Constant, Id, Constant.HashContext, true) = .empty,
73} = .{},
74/// Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module".
75sections: struct {
76 capabilities: Section = .{},
77 extensions: Section = .{},
78 extended_instruction_set: Section = .{},
79 memory_model: Section = .{},
80 execution_modes: Section = .{},
81 debug_strings: Section = .{},
82 debug_names: Section = .{},
83 annotations: Section = .{},
84 globals: Section = .{},
85 functions: Section = .{},
86} = .{},
87
88pub const big_int_bits = 32;
89
90/// Data can be lowered into in two basic representations: indirect, which is when
91/// a type is stored in memory, and direct, which is how a type is stored when its
92/// a direct SPIR-V value.
93pub const Repr = enum {
94 /// A SPIR-V value as it would be used in operations.
95 direct,
96 /// A SPIR-V value as it is stored in memory.
97 indirect,
5598};
5699
57100/// Declarations, both functions and globals, can have dependencies. These are used for 2 things:
......@@ -82,201 +125,166 @@ pub const Decl = struct {
82125 /// - For `invocation_global`, this is the result-id of the associated InvocationGlobal instruction.
83126 result_id: Id,
84127 /// The offset of the first dependency of this decl in the `decl_deps` array.
85 begin_dep: u32,
128 begin_dep: usize = 0,
86129 /// The past-end offset of the dependencies of this decl in the `decl_deps` array.
87 end_dep: u32,
130 end_dep: usize = 0,
88131};
89132
90133/// This models a kernel entry point.
91134pub const EntryPoint = struct {
92135 /// The declaration that should be exported.
93 decl_index: ?Decl.Index = null,
136 decl_index: Decl.Index,
94137 /// The name of the kernel to be exported.
95 name: ?[]const u8 = null,
138 name: []const u8,
96139 /// Calling Convention
97 exec_model: ?spec.ExecutionModel = null,
140 exec_model: spec.ExecutionModel,
98141 exec_mode: ?spec.ExecutionMode = null,
99142};
100143
101/// A general-purpose allocator which may be used to allocate resources for this module
102gpa: Allocator,
103
104/// Arena for things that need to live for the length of this program.
105arena: std.heap.ArenaAllocator,
106
107/// Target info
108target: *const std.Target,
144const StructType = struct {
145 fields: []const Id,
146 ip_index: InternPool.Index,
109147
110/// The target SPIR-V version
111version: spec.Version,
112
113/// Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module".
114sections: struct {
115 /// Capability instructions
116 capabilities: Section = .{},
117 /// OpExtension instructions
118 extensions: Section = .{},
119 /// OpExtInstImport
120 extended_instruction_set: Section = .{},
121 /// memory model defined by target
122 memory_model: Section = .{},
123 /// OpEntryPoint instructions - Handled by `self.entry_points`.
124 /// OpExecutionMode and OpExecutionModeId instructions.
125 execution_modes: Section = .{},
126 /// OpString, OpSourcExtension, OpSource, OpSourceContinued.
127 debug_strings: Section = .{},
128 // OpName, OpMemberName.
129 debug_names: Section = .{},
130 // OpModuleProcessed - skip for now.
131 /// Annotation instructions (OpDecorate etc).
132 annotations: Section = .{},
133 /// Type declarations, constants, global variables
134 /// From this section, OpLine and OpNoLine is allowed.
135 /// According to the SPIR-V documentation, this section normally
136 /// also holds type and constant instructions. These are managed
137 /// via the cache instead, which is the sole structure that
138 /// manages that section. These will be inserted between this and
139 /// the previous section when emitting the final binary.
140 /// TODO: Do we need this section? Globals are also managed with another mechanism.
141 types_globals_constants: Section = .{},
142 // Functions without a body - skip for now.
143 /// Regular function definitions.
144 functions: Section = .{},
145} = .{},
146
147/// SPIR-V instructions return result-ids. This variable holds the module-wide counter for these.
148next_result_id: Word,
149
150/// Cache for results of OpString instructions.
151strings: std.StringArrayHashMapUnmanaged(Id) = .empty,
152
153/// Some types shouldn't be emitted more than one time, but cannot be caught by
154/// the `intern_map` during codegen. Sometimes, IDs are compared to check if
155/// types are the same, so we can't delay until the dedup pass. Therefore,
156/// this is an ad-hoc structure to cache types where required.
157/// According to the SPIR-V specification, section 2.8, this includes all non-aggregate
158/// non-pointer types.
159/// Additionally, this is used for other values which can be cached, for example,
160/// built-in variables.
161cache: struct {
162 bool_type: ?Id = null,
163 void_type: ?Id = null,
164 int_types: std.AutoHashMapUnmanaged(std.builtin.Type.Int, Id) = .empty,
165 float_types: std.AutoHashMapUnmanaged(std.builtin.Type.Float, Id) = .empty,
166 vector_types: std.AutoHashMapUnmanaged(struct { Id, u32 }, Id) = .empty,
167 array_types: std.AutoHashMapUnmanaged(struct { Id, Id }, Id) = .empty,
168
169 capabilities: std.AutoHashMapUnmanaged(spec.Capability, void) = .empty,
170 extensions: std.StringHashMapUnmanaged(void) = .empty,
171 extended_instruction_set: std.AutoHashMapUnmanaged(spec.InstructionSet, Id) = .empty,
172 decorations: std.AutoHashMapUnmanaged(struct { Id, spec.Decoration }, void) = .empty,
173 builtins: std.AutoHashMapUnmanaged(struct { Id, spec.BuiltIn }, Decl.Index) = .empty,
174
175 bool_const: [2]?Id = .{ null, null },
176} = .{},
177
178/// Set of Decls, referred to by Decl.Index.
179decls: std.ArrayListUnmanaged(Decl) = .empty,
180
181/// List of dependencies, per decl. This list holds all the dependencies, sliced by the
182/// begin_dep and end_dep in `self.decls`.
183decl_deps: std.ArrayListUnmanaged(Decl.Index) = .empty,
184
185/// The list of entry points that should be exported from this module.
186entry_points: std.AutoArrayHashMapUnmanaged(Id, EntryPoint) = .empty,
187
188pub fn init(gpa: Allocator, target: *const std.Target) Module {
189 const version_minor: u8 = blk: {
190 // Prefer higher versions
191 if (target.cpu.has(.spirv, .v1_6)) break :blk 6;
192 if (target.cpu.has(.spirv, .v1_5)) break :blk 5;
193 if (target.cpu.has(.spirv, .v1_4)) break :blk 4;
194 if (target.cpu.has(.spirv, .v1_3)) break :blk 3;
195 if (target.cpu.has(.spirv, .v1_2)) break :blk 2;
196 if (target.cpu.has(.spirv, .v1_1)) break :blk 1;
197 break :blk 0;
198 };
148 const HashContext = struct {
149 pub fn hash(_: @This(), ty: StructType) u32 {
150 var hasher = std.hash.Wyhash.init(0);
151 hasher.update(std.mem.sliceAsBytes(ty.fields));
152 hasher.update(std.mem.asBytes(&ty.ip_index));
153 return @truncate(hasher.final());
154 }
199155
200 return .{
201 .gpa = gpa,
202 .arena = std.heap.ArenaAllocator.init(gpa),
203 .target = target,
204 .version = .{ .major = 1, .minor = version_minor },
205 .next_result_id = 1, // 0 is an invalid SPIR-V result id, so start counting at 1.
156 pub fn eql(_: @This(), a: StructType, b: StructType, _: usize) bool {
157 return a.ip_index == b.ip_index and std.mem.eql(Id, a.fields, b.fields);
158 }
206159 };
207}
208
209pub fn deinit(self: *Module) void {
210 self.sections.capabilities.deinit(self.gpa);
211 self.sections.extensions.deinit(self.gpa);
212 self.sections.extended_instruction_set.deinit(self.gpa);
213 self.sections.memory_model.deinit(self.gpa);
214 self.sections.execution_modes.deinit(self.gpa);
215 self.sections.debug_strings.deinit(self.gpa);
216 self.sections.debug_names.deinit(self.gpa);
217 self.sections.annotations.deinit(self.gpa);
218 self.sections.types_globals_constants.deinit(self.gpa);
219 self.sections.functions.deinit(self.gpa);
220
221 self.strings.deinit(self.gpa);
160};
222161
223 self.cache.int_types.deinit(self.gpa);
224 self.cache.float_types.deinit(self.gpa);
225 self.cache.vector_types.deinit(self.gpa);
226 self.cache.array_types.deinit(self.gpa);
227 self.cache.capabilities.deinit(self.gpa);
228 self.cache.extensions.deinit(self.gpa);
229 self.cache.extended_instruction_set.deinit(self.gpa);
230 self.cache.decorations.deinit(self.gpa);
231 self.cache.builtins.deinit(self.gpa);
162const FnType = struct {
163 return_ty: Id,
164 params: []const Id,
232165
233 self.decls.deinit(self.gpa);
234 self.decl_deps.deinit(self.gpa);
235 self.entry_points.deinit(self.gpa);
166 const HashContext = struct {
167 pub fn hash(_: @This(), ty: FnType) u32 {
168 var hasher = std.hash.Wyhash.init(0);
169 hasher.update(std.mem.asBytes(&ty.return_ty));
170 hasher.update(std.mem.sliceAsBytes(ty.params));
171 return @truncate(hasher.final());
172 }
236173
237 self.arena.deinit();
174 pub fn eql(_: @This(), a: FnType, b: FnType, _: usize) bool {
175 return a.return_ty == b.return_ty and
176 std.mem.eql(Id, a.params, b.params);
177 }
178 };
179};
238180
239 self.* = undefined;
240}
181const Constant = struct {
182 ty: Id,
183 value: spec.LiteralContextDependentNumber,
184
185 const HashContext = struct {
186 pub fn hash(_: @This(), value: Constant) u32 {
187 const Tag = @typeInfo(spec.LiteralContextDependentNumber).@"union".tag_type.?;
188 var hasher = std.hash.Wyhash.init(0);
189 hasher.update(std.mem.asBytes(&value.ty));
190 hasher.update(std.mem.asBytes(&@as(Tag, value.value)));
191 switch (value.value) {
192 inline else => |v| hasher.update(std.mem.asBytes(&v)),
193 }
194 return @truncate(hasher.final());
195 }
241196
242pub const IdRange = struct {
243 base: u32,
244 len: u32,
197 pub fn eql(_: @This(), a: Constant, b: Constant, _: usize) bool {
198 if (a.ty != b.ty) return false;
199 const Tag = @typeInfo(spec.LiteralContextDependentNumber).@"union".tag_type.?;
200 if (@as(Tag, a.value) != @as(Tag, b.value)) return false;
201 return switch (a.value) {
202 inline else => |v, tag| v == @field(b.value, @tagName(tag)),
203 };
204 }
205 };
206};
245207
246 pub fn at(range: IdRange, i: usize) Id {
247 assert(i < range.len);
248 return @enumFromInt(range.base + i);
208pub fn deinit(module: *Module) void {
209 module.nav_link.deinit(module.gpa);
210 module.uav_link.deinit(module.gpa);
211 module.intern_map.deinit(module.gpa);
212 module.ptr_types.deinit(module.gpa);
213
214 module.sections.capabilities.deinit(module.gpa);
215 module.sections.extensions.deinit(module.gpa);
216 module.sections.extended_instruction_set.deinit(module.gpa);
217 module.sections.memory_model.deinit(module.gpa);
218 module.sections.execution_modes.deinit(module.gpa);
219 module.sections.debug_strings.deinit(module.gpa);
220 module.sections.debug_names.deinit(module.gpa);
221 module.sections.annotations.deinit(module.gpa);
222 module.sections.globals.deinit(module.gpa);
223 module.sections.functions.deinit(module.gpa);
224
225 module.cache.opaque_types.deinit(module.gpa);
226 module.cache.int_types.deinit(module.gpa);
227 module.cache.float_types.deinit(module.gpa);
228 module.cache.vector_types.deinit(module.gpa);
229 module.cache.array_types.deinit(module.gpa);
230 module.cache.struct_types.deinit(module.gpa);
231 module.cache.fn_types.deinit(module.gpa);
232 module.cache.capabilities.deinit(module.gpa);
233 module.cache.extensions.deinit(module.gpa);
234 module.cache.extended_instruction_set.deinit(module.gpa);
235 module.cache.decorations.deinit(module.gpa);
236 module.cache.builtins.deinit(module.gpa);
237 module.cache.strings.deinit(module.gpa);
238
239 module.cache.constants.deinit(module.gpa);
240
241 module.decls.deinit(module.gpa);
242 module.decl_deps.deinit(module.gpa);
243 module.entry_points.deinit(module.gpa);
244
245 module.* = undefined;
246}
247
248/// Fetch or allocate a result id for nav index. This function also marks the nav as alive.
249/// Note: Function does not actually generate the nav, it just allocates an index.
250pub fn resolveNav(module: *Module, ip: *InternPool, nav_index: InternPool.Nav.Index) !Decl.Index {
251 const entry = try module.nav_link.getOrPut(module.gpa, nav_index);
252 if (!entry.found_existing) {
253 const nav = ip.getNav(nav_index);
254 // TODO: Extern fn?
255 const kind: Decl.Kind = if (ip.isFunctionType(nav.typeOf(ip)))
256 .func
257 else switch (nav.getAddrspace()) {
258 .generic => .invocation_global,
259 else => .global,
260 };
261 entry.value_ptr.* = try module.allocDecl(kind);
249262 }
250};
251263
252pub fn allocIds(self: *Module, n: u32) IdRange {
253 defer self.next_result_id += n;
254 return .{
255 .base = self.next_result_id,
256 .len = n,
257 };
264 return entry.value_ptr.*;
258265}
259266
260pub fn allocId(self: *Module) Id {
261 return self.allocIds(1).at(0);
267pub fn allocIds(module: *Module, n: u32) spec.IdRange {
268 defer module.next_result_id += n;
269 return .{ .base = module.next_result_id, .len = n };
262270}
263271
264pub fn idBound(self: Module) Word {
265 return self.next_result_id;
272pub fn allocId(module: *Module) Id {
273 return module.allocIds(1).at(0);
266274}
267275
268pub fn hasFeature(self: *Module, feature: std.Target.spirv.Feature) bool {
269 return self.target.cpu.has(.spirv, feature);
276pub fn idBound(module: Module) Word {
277 return module.next_result_id;
270278}
271279
272fn addEntryPointDeps(
273 self: *Module,
280pub fn addEntryPointDeps(
281 module: *Module,
274282 decl_index: Decl.Index,
275283 seen: *std.DynamicBitSetUnmanaged,
276284 interface: *std.ArrayList(Id),
277285) !void {
278 const decl = self.declPtr(decl_index);
279 const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep];
286 const decl = module.declPtr(decl_index);
287 const deps = module.decl_deps.items[decl.begin_dep..decl.end_dep];
280288
281289 if (seen.isSet(@intFromEnum(decl_index))) {
282290 return;
......@@ -289,36 +297,38 @@ fn addEntryPointDeps(
289297 }
290298
291299 for (deps) |dep| {
292 try self.addEntryPointDeps(dep, seen, interface);
300 try module.addEntryPointDeps(dep, seen, interface);
293301 }
294302}
295303
296fn entryPoints(self: *Module) !Section {
304fn entryPoints(module: *Module) !Section {
305 const target = module.zcu.getTarget();
306
297307 var entry_points = Section{};
298 errdefer entry_points.deinit(self.gpa);
308 errdefer entry_points.deinit(module.gpa);
299309
300 var interface = std.ArrayList(Id).init(self.gpa);
310 var interface = std.ArrayList(Id).init(module.gpa);
301311 defer interface.deinit();
302312
303 var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len);
304 defer seen.deinit(self.gpa);
313 var seen = try std.DynamicBitSetUnmanaged.initEmpty(module.gpa, module.decls.items.len);
314 defer seen.deinit(module.gpa);
305315
306 for (self.entry_points.keys(), self.entry_points.values()) |entry_point_id, entry_point| {
316 for (module.entry_points.keys(), module.entry_points.values()) |entry_point_id, entry_point| {
307317 interface.items.len = 0;
308 seen.setRangeValue(.{ .start = 0, .end = self.decls.items.len }, false);
318 seen.setRangeValue(.{ .start = 0, .end = module.decls.items.len }, false);
309319
310 try self.addEntryPointDeps(entry_point.decl_index.?, &seen, &interface);
311 try entry_points.emit(self.gpa, .OpEntryPoint, .{
312 .execution_model = entry_point.exec_model.?,
320 try module.addEntryPointDeps(entry_point.decl_index, &seen, &interface);
321 try entry_points.emit(module.gpa, .OpEntryPoint, .{
322 .execution_model = entry_point.exec_model,
313323 .entry_point = entry_point_id,
314 .name = entry_point.name.?,
324 .name = entry_point.name,
315325 .interface = interface.items,
316326 });
317327
318328 if (entry_point.exec_mode == null and entry_point.exec_model == .fragment) {
319 switch (self.target.os.tag) {
329 switch (target.os.tag) {
320330 .vulkan, .opengl => |tag| {
321 try self.sections.execution_modes.emit(self.gpa, .OpExecutionMode, .{
331 try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
322332 .entry_point = entry_point_id,
323333 .mode = if (tag == .vulkan) .origin_upper_left else .origin_lower_left,
324334 });
......@@ -332,83 +342,100 @@ fn entryPoints(self: *Module) !Section {
332342 return entry_points;
333343}
334344
335pub fn finalize(self: *Module, a: Allocator) ![]Word {
345pub fn finalize(module: *Module, gpa: Allocator) ![]Word {
346 const target = module.zcu.getTarget();
347
336348 // Emit capabilities and extensions
337 switch (self.target.os.tag) {
349 switch (target.os.tag) {
338350 .opengl => {
339 try self.addCapability(.shader);
340 try self.addCapability(.matrix);
351 try module.addCapability(.shader);
352 try module.addCapability(.matrix);
341353 },
342354 .vulkan => {
343 try self.addCapability(.shader);
344 try self.addCapability(.matrix);
345 if (self.target.cpu.arch == .spirv64) {
346 try self.addExtension("SPV_KHR_physical_storage_buffer");
347 try self.addCapability(.physical_storage_buffer_addresses);
355 try module.addCapability(.shader);
356 try module.addCapability(.matrix);
357 if (target.cpu.arch == .spirv64) {
358 try module.addExtension("SPV_KHR_physical_storage_buffer");
359 try module.addCapability(.physical_storage_buffer_addresses);
348360 }
349361 },
350362 .opencl, .amdhsa => {
351 try self.addCapability(.kernel);
352 try self.addCapability(.addresses);
363 try module.addCapability(.kernel);
364 try module.addCapability(.addresses);
353365 },
354366 else => unreachable,
355367 }
356 if (self.target.cpu.arch == .spirv64) try self.addCapability(.int64);
357 if (self.target.cpu.has(.spirv, .int64)) try self.addCapability(.int64);
358 if (self.target.cpu.has(.spirv, .float16)) try self.addCapability(.float16);
359 if (self.target.cpu.has(.spirv, .float64)) try self.addCapability(.float64);
360 if (self.target.cpu.has(.spirv, .generic_pointer)) try self.addCapability(.generic_pointer);
361 if (self.target.cpu.has(.spirv, .vector16)) try self.addCapability(.vector16);
362 if (self.target.cpu.has(.spirv, .storage_push_constant16)) {
363 try self.addExtension("SPV_KHR_16bit_storage");
364 try self.addCapability(.storage_push_constant16);
368 if (target.cpu.arch == .spirv64) try module.addCapability(.int64);
369 if (target.cpu.has(.spirv, .int64)) try module.addCapability(.int64);
370 if (target.cpu.has(.spirv, .float16)) {
371 if (target.os.tag == .opencl) try module.addExtension("cl_khr_fp16");
372 try module.addCapability(.float16);
365373 }
366 if (self.target.cpu.has(.spirv, .arbitrary_precision_integers)) {
367 try self.addExtension("SPV_INTEL_arbitrary_precision_integers");
368 try self.addCapability(.arbitrary_precision_integers_intel);
374 if (target.cpu.has(.spirv, .float64)) try module.addCapability(.float64);
375 if (target.cpu.has(.spirv, .generic_pointer)) try module.addCapability(.generic_pointer);
376 if (target.cpu.has(.spirv, .vector16)) try module.addCapability(.vector16);
377 if (target.cpu.has(.spirv, .storage_push_constant16)) {
378 try module.addExtension("SPV_KHR_16bit_storage");
379 try module.addCapability(.storage_push_constant16);
369380 }
370 if (self.target.cpu.has(.spirv, .variable_pointers)) {
371 try self.addExtension("SPV_KHR_variable_pointers");
372 try self.addCapability(.variable_pointers_storage_buffer);
373 try self.addCapability(.variable_pointers);
381 if (target.cpu.has(.spirv, .arbitrary_precision_integers)) {
382 try module.addExtension("SPV_INTEL_arbitrary_precision_integers");
383 try module.addCapability(.arbitrary_precision_integers_intel);
384 }
385 if (target.cpu.has(.spirv, .variable_pointers)) {
386 try module.addExtension("SPV_KHR_variable_pointers");
387 try module.addCapability(.variable_pointers_storage_buffer);
388 try module.addCapability(.variable_pointers);
374389 }
375390 // These are well supported
376 try self.addCapability(.int8);
377 try self.addCapability(.int16);
391 try module.addCapability(.int8);
392 try module.addCapability(.int16);
378393
379394 // Emit memory model
380 const addressing_model: spec.AddressingModel = switch (self.target.os.tag) {
395 const addressing_model: spec.AddressingModel = switch (target.os.tag) {
381396 .opengl => .logical,
382 .vulkan => if (self.target.cpu.arch == .spirv32) .logical else .physical_storage_buffer64,
383 .opencl => if (self.target.cpu.arch == .spirv32) .physical32 else .physical64,
397 .vulkan => if (target.cpu.arch == .spirv32) .logical else .physical_storage_buffer64,
398 .opencl => if (target.cpu.arch == .spirv32) .physical32 else .physical64,
384399 .amdhsa => .physical64,
385400 else => unreachable,
386401 };
387 try self.sections.memory_model.emit(self.gpa, .OpMemoryModel, .{
402 try module.sections.memory_model.emit(module.gpa, .OpMemoryModel, .{
388403 .addressing_model = addressing_model,
389 .memory_model = switch (self.target.os.tag) {
404 .memory_model = switch (target.os.tag) {
390405 .opencl => .open_cl,
391406 .vulkan, .opengl => .glsl450,
392407 else => unreachable,
393408 },
394409 });
395410
396 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
397 // TODO: Audit calls to allocId() in this function to make it idempotent.
398 var entry_points = try self.entryPoints();
399 defer entry_points.deinit(self.gpa);
411 var entry_points = try module.entryPoints();
412 defer entry_points.deinit(module.gpa);
413
414 const version: spec.Version = .{
415 .major = 1,
416 .minor = blk: {
417 // Prefer higher versions
418 if (target.cpu.has(.spirv, .v1_6)) break :blk 6;
419 if (target.cpu.has(.spirv, .v1_5)) break :blk 5;
420 if (target.cpu.has(.spirv, .v1_4)) break :blk 4;
421 if (target.cpu.has(.spirv, .v1_3)) break :blk 3;
422 if (target.cpu.has(.spirv, .v1_2)) break :blk 2;
423 if (target.cpu.has(.spirv, .v1_1)) break :blk 1;
424 break :blk 0;
425 },
426 };
400427
401428 const header = [_]Word{
402429 spec.magic_number,
403 self.version.toWord(),
430 version.toWord(),
404431 spec.zig_generator_id,
405 self.idBound(),
432 module.idBound(),
406433 0, // Schema (currently reserved for future use)
407434 };
408435
409436 var source = Section{};
410 defer source.deinit(self.gpa);
411 try self.sections.debug_strings.emit(self.gpa, .OpSource, .{
437 defer source.deinit(module.gpa);
438 try module.sections.debug_strings.emit(module.gpa, .OpSource, .{
412439 .source_language = .zig,
413440 .version = 0,
414441 // We cannot emit these because the Khronos translator does not parse this instruction
......@@ -421,26 +448,26 @@ pub fn finalize(self: *Module, a: Allocator) ![]Word {
421448 // Note: needs to be kept in order according to section 2.3!
422449 const buffers = &[_][]const Word{
423450 &header,
424 self.sections.capabilities.toWords(),
425 self.sections.extensions.toWords(),
426 self.sections.extended_instruction_set.toWords(),
427 self.sections.memory_model.toWords(),
451 module.sections.capabilities.toWords(),
452 module.sections.extensions.toWords(),
453 module.sections.extended_instruction_set.toWords(),
454 module.sections.memory_model.toWords(),
428455 entry_points.toWords(),
429 self.sections.execution_modes.toWords(),
456 module.sections.execution_modes.toWords(),
430457 source.toWords(),
431 self.sections.debug_strings.toWords(),
432 self.sections.debug_names.toWords(),
433 self.sections.annotations.toWords(),
434 self.sections.types_globals_constants.toWords(),
435 self.sections.functions.toWords(),
458 module.sections.debug_strings.toWords(),
459 module.sections.debug_names.toWords(),
460 module.sections.annotations.toWords(),
461 module.sections.globals.toWords(),
462 module.sections.functions.toWords(),
436463 };
437464
438465 var total_result_size: usize = 0;
439466 for (buffers) |buffer| {
440467 total_result_size += buffer.len;
441468 }
442 const result = try a.alloc(Word, total_result_size);
443 errdefer a.free(result);
469 const result = try gpa.alloc(Word, total_result_size);
470 errdefer comptime unreachable;
444471
445472 var offset: usize = 0;
446473 for (buffers) |buffer| {
......@@ -451,34 +478,27 @@ pub fn finalize(self: *Module, a: Allocator) ![]Word {
451478 return result;
452479}
453480
454/// Merge the sections making up a function declaration into this module.
455pub fn addFunction(self: *Module, decl_index: Decl.Index, func: Fn) !void {
456 try self.sections.functions.append(self.gpa, func.prologue);
457 try self.sections.functions.append(self.gpa, func.body);
458 try self.declareDeclDeps(decl_index, func.decl_deps.keys());
459}
460
461pub fn addCapability(self: *Module, cap: spec.Capability) !void {
462 const entry = try self.cache.capabilities.getOrPut(self.gpa, cap);
481pub fn addCapability(module: *Module, cap: spec.Capability) !void {
482 const entry = try module.cache.capabilities.getOrPut(module.gpa, cap);
463483 if (entry.found_existing) return;
464 try self.sections.capabilities.emit(self.gpa, .OpCapability, .{ .capability = cap });
484 try module.sections.capabilities.emit(module.gpa, .OpCapability, .{ .capability = cap });
465485}
466486
467pub fn addExtension(self: *Module, ext: []const u8) !void {
468 const entry = try self.cache.extensions.getOrPut(self.gpa, ext);
487pub fn addExtension(module: *Module, ext: []const u8) !void {
488 const entry = try module.cache.extensions.getOrPut(module.gpa, ext);
469489 if (entry.found_existing) return;
470 try self.sections.extensions.emit(self.gpa, .OpExtension, .{ .name = ext });
490 try module.sections.extensions.emit(module.gpa, .OpExtension, .{ .name = ext });
471491}
472492
473493/// Imports or returns the existing id of an extended instruction set
474pub fn importInstructionSet(self: *Module, set: spec.InstructionSet) !Id {
494pub fn importInstructionSet(module: *Module, set: spec.InstructionSet) !Id {
475495 assert(set != .core);
476496
477 const gop = try self.cache.extended_instruction_set.getOrPut(self.gpa, set);
497 const gop = try module.cache.extended_instruction_set.getOrPut(module.gpa, set);
478498 if (gop.found_existing) return gop.value_ptr.*;
479499
480 const result_id = self.allocId();
481 try self.sections.extended_instruction_set.emit(self.gpa, .OpExtInstImport, .{
500 const result_id = module.allocId();
501 try module.sections.extended_instruction_set.emit(module.gpa, .OpExtInstImport, .{
482502 .id_result = result_id,
483503 .name = @tagName(set),
484504 });
......@@ -487,104 +507,130 @@ pub fn importInstructionSet(self: *Module, set: spec.InstructionSet) !Id {
487507 return result_id;
488508}
489509
490/// Fetch the result-id of an instruction corresponding to a string.
491pub fn resolveString(self: *Module, string: []const u8) !Id {
492 if (self.strings.get(string)) |id| {
493 return id;
494 }
495
496 const id = self.allocId();
497 try self.strings.put(self.gpa, try self.arena.allocator().dupe(u8, string), id);
498
499 try self.sections.debug_strings.emit(self.gpa, .OpString, .{
500 .id_result = id,
501 .string = string,
502 });
503
504 return id;
505}
510pub fn boolType(module: *Module) !Id {
511 if (module.cache.bool_type) |id| return id;
506512
507pub fn structType(self: *Module, result_id: Id, types: []const Id, maybe_names: ?[]const []const u8) !void {
508 try self.sections.types_globals_constants.emit(self.gpa, .OpTypeStruct, .{
513 const result_id = module.allocId();
514 try module.sections.globals.emit(module.gpa, .OpTypeBool, .{
509515 .id_result = result_id,
510 .id_ref = types,
511516 });
512
513 if (maybe_names) |names| {
514 assert(names.len == types.len);
515 for (names, 0..) |name, i| {
516 try self.memberDebugName(result_id, @intCast(i), name);
517 }
518 }
517 module.cache.bool_type = result_id;
518 return result_id;
519519}
520520
521pub fn boolType(self: *Module) !Id {
522 if (self.cache.bool_type) |id| return id;
521pub fn voidType(module: *Module) !Id {
522 if (module.cache.void_type) |id| return id;
523523
524 const result_id = self.allocId();
525 try self.sections.types_globals_constants.emit(self.gpa, .OpTypeBool, .{
524 const result_id = module.allocId();
525 try module.sections.globals.emit(module.gpa, .OpTypeVoid, .{
526526 .id_result = result_id,
527527 });
528 self.cache.bool_type = result_id;
528 module.cache.void_type = result_id;
529 try module.debugName(result_id, "void");
529530 return result_id;
530531}
531532
532pub fn voidType(self: *Module) !Id {
533 if (self.cache.void_type) |id| return id;
534
535 const result_id = self.allocId();
536 try self.sections.types_globals_constants.emit(self.gpa, .OpTypeVoid, .{
533pub fn opaqueType(module: *Module, name: []const u8) !Id {
534 if (module.cache.opaque_types.get(name)) |id| return id;
535 const result_id = module.allocId();
536 const name_dup = try module.arena.dupe(u8, name);
537 try module.sections.globals.emit(module.gpa, .OpTypeOpaque, .{
537538 .id_result = result_id,
539 .literal_string = name_dup,
538540 });
539 self.cache.void_type = result_id;
540 try self.debugName(result_id, "void");
541 try module.debugName(result_id, name_dup);
542 try module.cache.opaque_types.put(module.gpa, name_dup, result_id);
541543 return result_id;
542544}
543545
544pub fn intType(self: *Module, signedness: std.builtin.Signedness, bits: u16) !Id {
546pub fn backingIntBits(module: *Module, bits: u16) struct { u16, bool } {
547 assert(bits != 0);
548 const target = module.zcu.getTarget();
549
550 if (target.cpu.has(.spirv, .arbitrary_precision_integers) and bits <= 32) {
551 return .{ bits, false };
552 }
553
554 // We require Int8 and Int16 capabilities and benefit Int64 when available.
555 // 32-bit integers are always supported (see spec, 2.16.1, Data rules).
556 const ints = [_]struct { bits: u16, enabled: bool }{
557 .{ .bits = 8, .enabled = true },
558 .{ .bits = 16, .enabled = true },
559 .{ .bits = 32, .enabled = true },
560 .{
561 .bits = 64,
562 .enabled = target.cpu.has(.spirv, .int64) or target.cpu.arch == .spirv64,
563 },
564 };
565
566 for (ints) |int| {
567 if (bits <= int.bits and int.enabled) return .{ int.bits, false };
568 }
569
570 // Big int
571 return .{ std.mem.alignForward(u16, bits, big_int_bits), true };
572}
573
574pub fn intType(module: *Module, signedness: std.builtin.Signedness, bits: u16) !Id {
545575 assert(bits > 0);
546 const entry = try self.cache.int_types.getOrPut(self.gpa, .{ .signedness = signedness, .bits = bits });
576
577 const target = module.zcu.getTarget();
578 const actual_signedness = switch (target.os.tag) {
579 // Kernel only supports unsigned ints.
580 .opencl, .amdhsa => .unsigned,
581 else => signedness,
582 };
583 const backing_bits, const big_int = module.backingIntBits(bits);
584 if (big_int) {
585 // TODO: support composite integers larger than 64 bit
586 assert(backing_bits <= 64);
587 const u32_ty = try module.intType(.unsigned, 32);
588 const len_id = try module.constant(u32_ty, .{ .uint32 = backing_bits / big_int_bits });
589 return module.arrayType(len_id, u32_ty);
590 }
591
592 const entry = try module.cache.int_types.getOrPut(module.gpa, .{ .signedness = actual_signedness, .bits = backing_bits });
547593 if (!entry.found_existing) {
548 const result_id = self.allocId();
594 const result_id = module.allocId();
549595 entry.value_ptr.* = result_id;
550 try self.sections.types_globals_constants.emit(self.gpa, .OpTypeInt, .{
596 try module.sections.globals.emit(module.gpa, .OpTypeInt, .{
551597 .id_result = result_id,
552 .width = bits,
553 .signedness = switch (signedness) {
598 .width = backing_bits,
599 .signedness = switch (actual_signedness) {
554600 .signed => 1,
555601 .unsigned => 0,
556602 },
557603 });
558604
559 switch (signedness) {
560 .signed => try self.debugNameFmt(result_id, "i{}", .{bits}),
561 .unsigned => try self.debugNameFmt(result_id, "u{}", .{bits}),
605 switch (actual_signedness) {
606 .signed => try module.debugNameFmt(result_id, "i{}", .{backing_bits}),
607 .unsigned => try module.debugNameFmt(result_id, "u{}", .{backing_bits}),
562608 }
563609 }
564610 return entry.value_ptr.*;
565611}
566612
567pub fn floatType(self: *Module, bits: u16) !Id {
613pub fn floatType(module: *Module, bits: u16) !Id {
568614 assert(bits > 0);
569 const entry = try self.cache.float_types.getOrPut(self.gpa, .{ .bits = bits });
615 const entry = try module.cache.float_types.getOrPut(module.gpa, .{ .bits = bits });
570616 if (!entry.found_existing) {
571 const result_id = self.allocId();
617 const result_id = module.allocId();
572618 entry.value_ptr.* = result_id;
573 try self.sections.types_globals_constants.emit(self.gpa, .OpTypeFloat, .{
619 try module.sections.globals.emit(module.gpa, .OpTypeFloat, .{
574620 .id_result = result_id,
575621 .width = bits,
576622 });
577 try self.debugNameFmt(result_id, "f{}", .{bits});
623 try module.debugNameFmt(result_id, "f{}", .{bits});
578624 }
579625 return entry.value_ptr.*;
580626}
581627
582pub fn vectorType(self: *Module, len: u32, child_ty_id: Id) !Id {
583 const entry = try self.cache.vector_types.getOrPut(self.gpa, .{ child_ty_id, len });
628pub fn vectorType(module: *Module, len: u32, child_ty_id: Id) !Id {
629 const entry = try module.cache.vector_types.getOrPut(module.gpa, .{ child_ty_id, len });
584630 if (!entry.found_existing) {
585 const result_id = self.allocId();
631 const result_id = module.allocId();
586632 entry.value_ptr.* = result_id;
587 try self.sections.types_globals_constants.emit(self.gpa, .OpTypeVector, .{
633 try module.sections.globals.emit(module.gpa, .OpTypeVector, .{
588634 .id_result = result_id,
589635 .component_type = child_ty_id,
590636 .component_count = len,
......@@ -593,12 +639,12 @@ pub fn vectorType(self: *Module, len: u32, child_ty_id: Id) !Id {
593639 return entry.value_ptr.*;
594640}
595641
596pub fn arrayType(self: *Module, len_id: Id, child_ty_id: Id) !Id {
597 const entry = try self.cache.array_types.getOrPut(self.gpa, .{ child_ty_id, len_id });
642pub fn arrayType(module: *Module, len_id: Id, child_ty_id: Id) !Id {
643 const entry = try module.cache.array_types.getOrPut(module.gpa, .{ child_ty_id, len_id });
598644 if (!entry.found_existing) {
599 const result_id = self.allocId();
645 const result_id = module.allocId();
600646 entry.value_ptr.* = result_id;
601 try self.sections.types_globals_constants.emit(self.gpa, .OpTypeArray, .{
647 try module.sections.globals.emit(module.gpa, .OpTypeArray, .{
602648 .id_result = result_id,
603649 .element_type = child_ty_id,
604650 .length = len_id,
......@@ -607,37 +653,114 @@ pub fn arrayType(self: *Module, len_id: Id, child_ty_id: Id) !Id {
607653 return entry.value_ptr.*;
608654}
609655
610pub fn functionType(self: *Module, return_ty_id: Id, param_type_ids: []const Id) !Id {
611 const result_id = self.allocId();
612 try self.sections.types_globals_constants.emit(self.gpa, .OpTypeFunction, .{
656pub fn ptrType(module: *Module, child_ty_id: Id, storage_class: spec.StorageClass) !Id {
657 const key = .{ child_ty_id, storage_class };
658 const gop = try module.ptr_types.getOrPut(module.gpa, key);
659 if (!gop.found_existing) {
660 gop.value_ptr.* = module.allocId();
661 try module.sections.globals.emit(module.gpa, .OpTypePointer, .{
662 .id_result = gop.value_ptr.*,
663 .storage_class = storage_class,
664 .type = child_ty_id,
665 });
666 return gop.value_ptr.*;
667 }
668 return gop.value_ptr.*;
669}
670
671pub fn structType(
672 module: *Module,
673 types: []const Id,
674 maybe_names: ?[]const []const u8,
675 maybe_offsets: ?[]const u32,
676 ip_index: InternPool.Index,
677) !Id {
678 const target = module.zcu.getTarget();
679
680 if (module.cache.struct_types.get(.{ .fields = types, .ip_index = ip_index })) |id| return id;
681 const result_id = module.allocId();
682 const types_dup = try module.arena.dupe(Id, types);
683 try module.sections.globals.emit(module.gpa, .OpTypeStruct, .{
613684 .id_result = result_id,
614 .return_type = return_ty_id,
615 .id_ref_2 = param_type_ids,
685 .id_ref = types_dup,
616686 });
687
688 if (maybe_names) |names| {
689 assert(names.len == types.len);
690 for (names, 0..) |name, i| {
691 try module.memberDebugName(result_id, @intCast(i), name);
692 }
693 }
694
695 switch (target.os.tag) {
696 .vulkan, .opengl => {
697 if (maybe_offsets) |offsets| {
698 assert(offsets.len == types.len);
699 for (offsets, 0..) |offset, i| {
700 try module.decorateMember(
701 result_id,
702 @intCast(i),
703 .{ .offset = .{ .byte_offset = offset } },
704 );
705 }
706 }
707 },
708 else => {},
709 }
710
711 try module.cache.struct_types.put(
712 module.gpa,
713 .{
714 .fields = types_dup,
715 .ip_index = if (module.zcu.comp.config.root_strip) .none else ip_index,
716 },
717 result_id,
718 );
617719 return result_id;
618720}
619721
620pub fn constant(self: *Module, result_ty_id: Id, value: spec.LiteralContextDependentNumber) !Id {
621 const result_id = self.allocId();
622 const section = &self.sections.types_globals_constants;
623 try section.emit(self.gpa, .OpConstant, .{
624 .id_result_type = result_ty_id,
722pub fn functionType(module: *Module, return_ty_id: Id, param_type_ids: []const Id) !Id {
723 if (module.cache.fn_types.get(.{
724 .return_ty = return_ty_id,
725 .params = param_type_ids,
726 })) |id| return id;
727 const result_id = module.allocId();
728 const params_dup = try module.arena.dupe(Id, param_type_ids);
729 try module.sections.globals.emit(module.gpa, .OpTypeFunction, .{
625730 .id_result = result_id,
626 .value = value,
731 .return_type = return_ty_id,
732 .id_ref_2 = params_dup,
627733 });
734 try module.cache.fn_types.put(module.gpa, .{
735 .return_ty = return_ty_id,
736 .params = params_dup,
737 }, result_id);
628738 return result_id;
629739}
630740
631pub fn constBool(self: *Module, value: bool) !Id {
632 if (self.cache.bool_const[@intFromBool(value)]) |b| return b;
741pub fn constant(module: *Module, ty_id: Id, value: spec.LiteralContextDependentNumber) !Id {
742 const gop = try module.cache.constants.getOrPut(module.gpa, .{ .ty = ty_id, .value = value });
743 if (!gop.found_existing) {
744 gop.value_ptr.* = module.allocId();
745 try module.sections.globals.emit(module.gpa, .OpConstant, .{
746 .id_result_type = ty_id,
747 .id_result = gop.value_ptr.*,
748 .value = value,
749 });
750 }
751 return gop.value_ptr.*;
752}
753
754pub fn constBool(module: *Module, value: bool) !Id {
755 if (module.cache.bool_const[@intFromBool(value)]) |b| return b;
633756
634 const result_ty_id = try self.boolType();
635 const result_id = self.allocId();
636 self.cache.bool_const[@intFromBool(value)] = result_id;
757 const result_ty_id = try module.boolType();
758 const result_id = module.allocId();
759 module.cache.bool_const[@intFromBool(value)] = result_id;
637760
638761 switch (value) {
639 inline else => |value_ct| try self.sections.types_globals_constants.emit(
640 self.gpa,
762 inline else => |value_ct| try module.sections.globals.emit(
763 module.gpa,
641764 if (value_ct) .OpConstantTrue else .OpConstantFalse,
642765 .{
643766 .id_result_type = result_ty_id,
......@@ -649,37 +772,40 @@ pub fn constBool(self: *Module, value: bool) !Id {
649772 return result_id;
650773}
651774
652/// Return a pointer to a builtin variable. `result_ty_id` must be a **pointer**
653/// with storage class `.Input`.
654pub fn builtin(self: *Module, result_ty_id: Id, spirv_builtin: spec.BuiltIn) !Decl.Index {
655 const entry = try self.cache.builtins.getOrPut(self.gpa, .{ result_ty_id, spirv_builtin });
656 if (!entry.found_existing) {
657 const decl_index = try self.allocDecl(.global);
658 const result_id = self.declPtr(decl_index).result_id;
659 entry.value_ptr.* = decl_index;
660 try self.sections.types_globals_constants.emit(self.gpa, .OpVariable, .{
775pub fn builtin(
776 module: *Module,
777 result_ty_id: Id,
778 spirv_builtin: spec.BuiltIn,
779 storage_class: spec.StorageClass,
780) !Decl.Index {
781 const gop = try module.cache.builtins.getOrPut(module.gpa, .{ spirv_builtin, storage_class });
782 if (!gop.found_existing) {
783 const decl_index = try module.allocDecl(.global);
784 const decl = module.declPtr(decl_index);
785
786 gop.value_ptr.* = decl_index;
787 try module.sections.globals.emit(module.gpa, .OpVariable, .{
661788 .id_result_type = result_ty_id,
662 .id_result = result_id,
663 .storage_class = .input,
789 .id_result = decl.result_id,
790 .storage_class = storage_class,
664791 });
665 try self.decorate(result_id, .{ .built_in = .{ .built_in = spirv_builtin } });
666 try self.declareDeclDeps(decl_index, &.{});
792 try module.decorate(decl.result_id, .{ .built_in = .{ .built_in = spirv_builtin } });
667793 }
668 return entry.value_ptr.*;
794 return gop.value_ptr.*;
669795}
670796
671pub fn constUndef(self: *Module, ty_id: Id) !Id {
672 const result_id = self.allocId();
673 try self.sections.types_globals_constants.emit(self.gpa, .OpUndef, .{
797pub fn constUndef(module: *Module, ty_id: Id) !Id {
798 const result_id = module.allocId();
799 try module.sections.globals.emit(module.gpa, .OpUndef, .{
674800 .id_result_type = ty_id,
675801 .id_result = result_id,
676802 });
677803 return result_id;
678804}
679805
680pub fn constNull(self: *Module, ty_id: Id) !Id {
681 const result_id = self.allocId();
682 try self.sections.types_globals_constants.emit(self.gpa, .OpConstantNull, .{
806pub fn constNull(module: *Module, ty_id: Id) !Id {
807 const result_id = module.allocId();
808 try module.sections.globals.emit(module.gpa, .OpConstantNull, .{
683809 .id_result_type = ty_id,
684810 .id_result = result_id,
685811 });
......@@ -688,13 +814,13 @@ pub fn constNull(self: *Module, ty_id: Id) !Id {
688814
689815/// Decorate a result-id.
690816pub fn decorate(
691 self: *Module,
817 module: *Module,
692818 target: Id,
693819 decoration: spec.Decoration.Extended,
694820) !void {
695 const entry = try self.cache.decorations.getOrPut(self.gpa, .{ target, decoration });
696 if (!entry.found_existing) {
697 try self.sections.annotations.emit(self.gpa, .OpDecorate, .{
821 const gop = try module.cache.decorations.getOrPut(module.gpa, .{ target, decoration });
822 if (!gop.found_existing) {
823 try module.sections.annotations.emit(module.gpa, .OpDecorate, .{
698824 .target = target,
699825 .decoration = decoration,
700826 });
......@@ -704,79 +830,112 @@ pub fn decorate(
704830/// Decorate a result-id which is a member of some struct.
705831/// We really don't have to and shouldn't need to cache this.
706832pub fn decorateMember(
707 self: *Module,
833 module: *Module,
708834 structure_type: Id,
709835 member: u32,
710836 decoration: spec.Decoration.Extended,
711837) !void {
712 try self.sections.annotations.emit(self.gpa, .OpMemberDecorate, .{
838 try module.sections.annotations.emit(module.gpa, .OpMemberDecorate, .{
713839 .structure_type = structure_type,
714840 .member = member,
715841 .decoration = decoration,
716842 });
717843}
718844
719pub fn allocDecl(self: *Module, kind: Decl.Kind) !Decl.Index {
720 try self.decls.append(self.gpa, .{
845pub fn allocDecl(module: *Module, kind: Decl.Kind) !Decl.Index {
846 try module.decls.append(module.gpa, .{
721847 .kind = kind,
722 .result_id = self.allocId(),
723 .begin_dep = undefined,
724 .end_dep = undefined,
848 .result_id = module.allocId(),
725849 });
726850
727 return @as(Decl.Index, @enumFromInt(@as(u32, @intCast(self.decls.items.len - 1))));
728}
729
730pub fn declPtr(self: *Module, index: Decl.Index) *Decl {
731 return &self.decls.items[@intFromEnum(index)];
851 return @as(Decl.Index, @enumFromInt(@as(u32, @intCast(module.decls.items.len - 1))));
732852}
733853
734/// Declare ALL dependencies for a decl.
735pub fn declareDeclDeps(self: *Module, decl_index: Decl.Index, deps: []const Decl.Index) !void {
736 const begin_dep: u32 = @intCast(self.decl_deps.items.len);
737 try self.decl_deps.appendSlice(self.gpa, deps);
738 const end_dep: u32 = @intCast(self.decl_deps.items.len);
739
740 const decl = self.declPtr(decl_index);
741 decl.begin_dep = begin_dep;
742 decl.end_dep = end_dep;
854pub fn declPtr(module: *Module, index: Decl.Index) *Decl {
855 return &module.decls.items[@intFromEnum(index)];
743856}
744857
745858/// Declare a SPIR-V function as an entry point. This causes an extra wrapper
746859/// function to be generated, which is then exported as the real entry point. The purpose of this
747860/// wrapper is to allocate and initialize the structure holding the instance globals.
748861pub fn declareEntryPoint(
749 self: *Module,
862 module: *Module,
750863 decl_index: Decl.Index,
751864 name: []const u8,
752865 exec_model: spec.ExecutionModel,
753866 exec_mode: ?spec.ExecutionMode,
754867) !void {
755 const gop = try self.entry_points.getOrPut(self.gpa, self.declPtr(decl_index).result_id);
868 const gop = try module.entry_points.getOrPut(module.gpa, module.declPtr(decl_index).result_id);
756869 gop.value_ptr.decl_index = decl_index;
757 gop.value_ptr.name = try self.arena.allocator().dupe(u8, name);
870 gop.value_ptr.name = name;
758871 gop.value_ptr.exec_model = exec_model;
759872 // Might've been set by assembler
760873 if (!gop.found_existing) gop.value_ptr.exec_mode = exec_mode;
761874}
762875
763pub fn debugName(self: *Module, target: Id, name: []const u8) !void {
764 try self.sections.debug_names.emit(self.gpa, .OpName, .{
876pub fn debugName(module: *Module, target: Id, name: []const u8) !void {
877 try module.sections.debug_names.emit(module.gpa, .OpName, .{
765878 .target = target,
766879 .name = name,
767880 });
768881}
769882
770pub fn debugNameFmt(self: *Module, target: Id, comptime fmt: []const u8, args: anytype) !void {
771 const name = try std.fmt.allocPrint(self.gpa, fmt, args);
772 defer self.gpa.free(name);
773 try self.debugName(target, name);
883pub fn debugNameFmt(module: *Module, target: Id, comptime fmt: []const u8, args: anytype) !void {
884 const name = try std.fmt.allocPrint(module.gpa, fmt, args);
885 defer module.gpa.free(name);
886 try module.debugName(target, name);
774887}
775888
776pub fn memberDebugName(self: *Module, target: Id, member: u32, name: []const u8) !void {
777 try self.sections.debug_names.emit(self.gpa, .OpMemberName, .{
889pub fn memberDebugName(module: *Module, target: Id, member: u32, name: []const u8) !void {
890 try module.sections.debug_names.emit(module.gpa, .OpMemberName, .{
778891 .type = target,
779892 .member = member,
780893 .name = name,
781894 });
782895}
896
897pub fn debugString(module: *Module, string: []const u8) !Id {
898 const entry = try module.cache.strings.getOrPut(module.gpa, string);
899 if (!entry.found_existing) {
900 entry.value_ptr.* = module.allocId();
901 try module.sections.debug_strings.emit(module.gpa, .OpString, .{
902 .id_result = entry.value_ptr.*,
903 .string = string,
904 });
905 }
906 return entry.value_ptr.*;
907}
908
909pub fn storageClass(module: *Module, as: std.builtin.AddressSpace) spec.StorageClass {
910 const target = module.zcu.getTarget();
911 return switch (as) {
912 .generic => .function,
913 .global => switch (target.os.tag) {
914 .opencl, .amdhsa => .cross_workgroup,
915 else => .storage_buffer,
916 },
917 .push_constant => .push_constant,
918 .output => .output,
919 .uniform => .uniform,
920 .storage_buffer => .storage_buffer,
921 .physical_storage_buffer => .physical_storage_buffer,
922 .constant => .uniform_constant,
923 .shared => .workgroup,
924 .local => .function,
925 .input => .input,
926 .gs,
927 .fs,
928 .ss,
929 .param,
930 .flash,
931 .flash1,
932 .flash2,
933 .flash3,
934 .flash4,
935 .flash5,
936 .cog,
937 .lut,
938 .hub,
939 => unreachable,
940 };
941}
src/codegen/spirv/Section.zig+46-205
......@@ -13,8 +13,6 @@ const Log2Word = std.math.Log2Int(Word);
1313
1414const Opcode = spec.Opcode;
1515
16/// The instructions in this section. Memory is owned by the Module
17/// externally associated to this Section.
1816instructions: std.ArrayListUnmanaged(Word) = .empty,
1917
2018pub fn deinit(section: *Section, allocator: Allocator) void {
......@@ -22,9 +20,8 @@ pub fn deinit(section: *Section, allocator: Allocator) void {
2220 section.* = undefined;
2321}
2422
25/// Clear the instructions in this section
2623pub fn reset(section: *Section) void {
27 section.instructions.items.len = 0;
24 section.instructions.clearRetainingCapacity();
2825}
2926
3027pub fn toWords(section: Section) []Word {
......@@ -36,9 +33,12 @@ pub fn append(section: *Section, allocator: Allocator, other_section: Section) !
3633 try section.instructions.appendSlice(allocator, other_section.instructions.items);
3734}
3835
39/// Ensure capacity of at least `capacity` more words in this section.
40pub fn ensureUnusedCapacity(section: *Section, allocator: Allocator, capacity: usize) !void {
41 try section.instructions.ensureUnusedCapacity(allocator, capacity);
36pub fn ensureUnusedCapacity(
37 section: *Section,
38 allocator: Allocator,
39 words: usize,
40) !void {
41 try section.instructions.ensureUnusedCapacity(allocator, words);
4242}
4343
4444/// Write an instruction and size, operands are to be inserted manually.
......@@ -46,7 +46,7 @@ pub fn emitRaw(
4646 section: *Section,
4747 allocator: Allocator,
4848 opcode: Opcode,
49 operand_words: usize, // opcode itself not included
49 operand_words: usize,
5050) !void {
5151 const word_count = 1 + operand_words;
5252 try section.instructions.ensureUnusedCapacity(allocator, word_count);
......@@ -64,45 +64,26 @@ pub fn emitRawInstruction(
6464 section.writeWords(operands);
6565}
6666
67pub fn emit(
67pub fn emitAssumeCapacity(
6868 section: *Section,
69 allocator: Allocator,
7069 comptime opcode: spec.Opcode,
7170 operands: opcode.Operands(),
7271) !void {
7372 const word_count = instructionSize(opcode, operands);
74 try section.instructions.ensureUnusedCapacity(allocator, word_count);
7573 section.writeWord(@as(Word, @intCast(word_count << 16)) | @intFromEnum(opcode));
7674 section.writeOperands(opcode.Operands(), operands);
7775}
7876
79pub fn emitBranch(
80 section: *Section,
81 allocator: Allocator,
82 target_label: spec.Id,
83) !void {
84 try section.emit(allocator, .OpBranch, .{
85 .target_label = target_label,
86 });
87}
88
89pub fn emitSpecConstantOp(
77pub fn emit(
9078 section: *Section,
9179 allocator: Allocator,
9280 comptime opcode: spec.Opcode,
9381 operands: opcode.Operands(),
9482) !void {
95 const word_count = operandsSize(opcode.Operands(), operands);
96 try section.emitRaw(allocator, .OpSpecConstantOp, 1 + word_count);
97 section.writeOperand(spec.Id, operands.id_result_type);
98 section.writeOperand(spec.Id, operands.id_result);
99 section.writeOperand(Opcode, opcode);
100
101 const fields = @typeInfo(opcode.Operands()).@"struct".fields;
102 // First 2 fields are always id_result_type and id_result.
103 inline for (fields[2..]) |field| {
104 section.writeOperand(field.type, @field(operands, field.name));
105 }
83 const word_count = instructionSize(opcode, operands);
84 try section.instructions.ensureUnusedCapacity(allocator, word_count);
85 section.writeWord(@as(Word, @intCast(word_count << 16)) | @intFromEnum(opcode));
86 section.writeOperands(opcode.Operands(), operands);
10687}
10788
10889pub fn writeWord(section: *Section, word: Word) void {
......@@ -126,7 +107,6 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)
126107 .void => return,
127108 else => unreachable,
128109 };
129
130110 inline for (fields) |field| {
131111 section.writeOperand(field.type, @field(operands, field.name));
132112 }
......@@ -134,30 +114,18 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)
134114
135115pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
136116 switch (Operand) {
117 spec.LiteralSpecConstantOpInteger => unreachable,
137118 spec.Id => section.writeWord(@intFromEnum(operand)),
138
139119 spec.LiteralInteger => section.writeWord(operand),
140
141120 spec.LiteralString => section.writeString(operand),
142
143121 spec.LiteralContextDependentNumber => section.writeContextDependentNumber(operand),
144
145122 spec.LiteralExtInstInteger => section.writeWord(operand.inst),
146
147 // TODO: Where this type is used (OpSpecConstantOp) is currently not correct in the spec json,
148 // so it most likely needs to be altered into something that can actually describe the entire
149 // instruction in which it is used.
150 spec.LiteralSpecConstantOpInteger => section.writeWord(@intFromEnum(operand.opcode)),
151
152123 spec.PairLiteralIntegerIdRef => section.writeWords(&.{ operand.value, @enumFromInt(operand.label) }),
153124 spec.PairIdRefLiteralInteger => section.writeWords(&.{ @intFromEnum(operand.target), operand.member }),
154125 spec.PairIdRefIdRef => section.writeWords(&.{ @intFromEnum(operand[0]), @intFromEnum(operand[1]) }),
155
156126 else => switch (@typeInfo(Operand)) {
157127 .@"enum" => section.writeWord(@intFromEnum(operand)),
158 .optional => |info| if (operand) |child| {
159 section.writeOperand(info.child, child);
160 },
128 .optional => |info| if (operand) |child| section.writeOperand(info.child, child),
161129 .pointer => |info| {
162130 std.debug.assert(info.size == .slice); // Should be no other pointer types in the spec.
163131 for (operand) |item| {
......@@ -178,18 +146,14 @@ pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand)
178146}
179147
180148fn writeString(section: *Section, str: []const u8) void {
181 // TODO: Not actually sure whether this is correct for big-endian.
182 // See https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#Literal
183149 const zero_terminated_len = str.len + 1;
184150 var i: usize = 0;
185151 while (i < zero_terminated_len) : (i += @sizeOf(Word)) {
186152 var word: Word = 0;
187
188153 var j: usize = 0;
189154 while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) {
190155 word |= @as(Word, str[i + j]) << @as(Log2Word, @intCast(j * @bitSizeOf(u8)));
191156 }
192
193157 section.instructions.appendAssumeCapacity(word);
194158 }
195159}
......@@ -233,20 +197,19 @@ fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand
233197}
234198
235199fn writeExtendedUnion(section: *Section, comptime Operand: type, operand: Operand) void {
236 const tag = std.meta.activeTag(operand);
237 section.writeWord(@intFromEnum(tag));
238
239 inline for (@typeInfo(Operand).@"union".fields) |field| {
240 if (@field(Operand, field.name) == tag) {
241 section.writeOperands(field.type, @field(operand, field.name));
242 return;
243 }
244 }
245 unreachable;
200 return switch (operand) {
201 inline else => |op, tag| {
202 section.writeWord(@intFromEnum(tag));
203 section.writeOperands(
204 @FieldType(Operand, @tagName(tag)),
205 op,
206 );
207 },
208 };
246209}
247210
248211fn instructionSize(comptime opcode: spec.Opcode, operands: opcode.Operands()) usize {
249 return 1 + operandsSize(opcode.Operands(), operands);
212 return operandsSize(opcode.Operands(), operands) + 1;
250213}
251214
252215fn operandsSize(comptime Operands: type, operands: Operands) usize {
......@@ -266,28 +229,14 @@ fn operandsSize(comptime Operands: type, operands: Operands) usize {
266229
267230fn operandSize(comptime Operand: type, operand: Operand) usize {
268231 return switch (Operand) {
269 spec.Id,
270 spec.LiteralInteger,
271 spec.LiteralExtInstInteger,
272 => 1,
273
274 spec.LiteralString => std.math.divCeil(usize, operand.len + 1, @sizeOf(Word)) catch unreachable, // Add one for zero-terminator
275
232 spec.LiteralSpecConstantOpInteger => unreachable,
233 spec.Id, spec.LiteralInteger, spec.LiteralExtInstInteger => 1,
234 spec.LiteralString => std.math.divCeil(usize, operand.len + 1, @sizeOf(Word)) catch unreachable,
276235 spec.LiteralContextDependentNumber => switch (operand) {
277236 .int32, .uint32, .float32 => 1,
278237 .int64, .uint64, .float64 => 2,
279238 },
280
281 // TODO: Where this type is used (OpSpecConstantOp) is currently not correct in the spec
282 // json, so it most likely needs to be altered into something that can actually
283 // describe the entire insturction in which it is used.
284 spec.LiteralSpecConstantOpInteger => 1,
285
286 spec.PairLiteralIntegerIdRef,
287 spec.PairIdRefLiteralInteger,
288 spec.PairIdRefIdRef,
289 => 2,
290
239 spec.PairLiteralIntegerIdRef, spec.PairIdRefLiteralInteger, spec.PairIdRefIdRef => 2,
291240 else => switch (@typeInfo(Operand)) {
292241 .@"enum" => 1,
293242 .optional => |info| if (operand) |child| operandSize(info.child, child) else 0,
......@@ -299,133 +248,25 @@ fn operandSize(comptime Operand: type, operand: Operand) usize {
299248 }
300249 break :blk total;
301250 },
302 .@"struct" => |info| if (info.layout == .@"packed") 1 else extendedMaskSize(Operand, operand),
303 .@"union" => extendedUnionSize(Operand, operand),
304 else => unreachable,
305 },
306 };
307}
251 .@"struct" => |struct_info| {
252 if (struct_info.layout == .@"packed") return 1;
308253
309fn extendedMaskSize(comptime Operand: type, operand: Operand) usize {
310 var total: usize = 0;
311 var any_set = false;
312 inline for (@typeInfo(Operand).@"struct".fields) |field| {
313 switch (@typeInfo(field.type)) {
314 .optional => |info| if (@field(operand, field.name)) |child| {
315 total += operandsSize(info.child, child);
316 any_set = true;
254 var total: usize = 0;
255 inline for (@typeInfo(Operand).@"struct".fields) |field| {
256 switch (@typeInfo(field.type)) {
257 .optional => |info| if (@field(operand, field.name)) |child| {
258 total += operandsSize(info.child, child);
259 },
260 .bool => {},
261 else => unreachable,
262 }
263 }
264 return total + 1; // Add one for the mask itself.
317265 },
318 .bool => if (@field(operand, field.name)) {
319 any_set = true;
266 .@"union" => switch (operand) {
267 inline else => |op, tag| operandsSize(@FieldType(Operand, @tagName(tag)), op) + 1,
320268 },
321269 else => unreachable,
322 }
323 }
324 return total + 1; // Add one for the mask itself.
325}
326
327fn extendedUnionSize(comptime Operand: type, operand: Operand) usize {
328 const tag = std.meta.activeTag(operand);
329 inline for (@typeInfo(Operand).@"union".fields) |field| {
330 if (@field(Operand, field.name) == tag) {
331 // Add one for the tag itself.
332 return 1 + operandsSize(field.type, @field(operand, field.name));
333 }
334 }
335 unreachable;
336}
337
338test "SPIR-V Section emit() - no operands" {
339 var section = Section{};
340 defer section.deinit(std.testing.allocator);
341
342 try section.emit(std.testing.allocator, .OpNop, {});
343
344 try testing.expect(section.instructions.items[0] == (@as(Word, 1) << 16) | @intFromEnum(Opcode.OpNop));
345}
346
347test "SPIR-V Section emit() - simple" {
348 var section = Section{};
349 defer section.deinit(std.testing.allocator);
350
351 try section.emit(std.testing.allocator, .OpUndef, .{
352 .id_result_type = @enumFromInt(0),
353 .id_result = @enumFromInt(1),
354 });
355
356 try testing.expectEqualSlices(Word, &.{
357 (@as(Word, 3) << 16) | @intFromEnum(Opcode.OpUndef),
358 0,
359 1,
360 }, section.instructions.items);
361}
362
363test "SPIR-V Section emit() - string" {
364 var section = Section{};
365 defer section.deinit(std.testing.allocator);
366
367 try section.emit(std.testing.allocator, .OpSource, .{
368 .source_language = .Unknown,
369 .version = 123,
370 .file = @enumFromInt(256),
371 .source = "pub fn main() void {}",
372 });
373
374 try testing.expectEqualSlices(Word, &.{
375 (@as(Word, 10) << 16) | @intFromEnum(Opcode.OpSource),
376 @intFromEnum(spec.SourceLanguage.Unknown),
377 123,
378 456,
379 std.mem.bytesToValue(Word, "pub "),
380 std.mem.bytesToValue(Word, "fn m"),
381 std.mem.bytesToValue(Word, "ain("),
382 std.mem.bytesToValue(Word, ") vo"),
383 std.mem.bytesToValue(Word, "id {"),
384 std.mem.bytesToValue(Word, "}\x00\x00\x00"),
385 }, section.instructions.items);
386}
387
388test "SPIR-V Section emit() - extended mask" {
389 var section = Section{};
390 defer section.deinit(std.testing.allocator);
391
392 try section.emit(std.testing.allocator, .OpLoopMerge, .{
393 .merge_block = @enumFromInt(10),
394 .continue_target = @enumFromInt(20),
395 .loop_control = .{
396 .Unroll = true,
397 .DependencyLength = .{
398 .literal_integer = 2,
399 },
400 },
401 });
402
403 try testing.expectEqualSlices(Word, &.{
404 (@as(Word, 5) << 16) | @intFromEnum(Opcode.OpLoopMerge),
405 10,
406 20,
407 @as(Word, @bitCast(spec.LoopControl{ .Unroll = true, .DependencyLength = true })),
408 2,
409 }, section.instructions.items);
410}
411
412test "SPIR-V Section emit() - extended union" {
413 var section = Section{};
414 defer section.deinit(std.testing.allocator);
415
416 try section.emit(std.testing.allocator, .OpExecutionMode, .{
417 .entry_point = @enumFromInt(888),
418 .mode = .{
419 .LocalSize = .{ .x_size = 4, .y_size = 8, .z_size = 16 },
420270 },
421 });
422
423 try testing.expectEqualSlices(Word, &.{
424 (@as(Word, 6) << 16) | @intFromEnum(Opcode.OpExecutionMode),
425 888,
426 @intFromEnum(spec.ExecutionMode.LocalSize),
427 4,
428 8,
429 16,
430 }, section.instructions.items);
271 };
431272}
src/codegen/spirv/spec.zig+794-3530
......@@ -26,6 +26,16 @@ pub const Id = enum(Word) {
2626 }
2727};
2828
29pub const IdRange = struct {
30 base: u32,
31 len: u32,
32
33 pub fn at(range: IdRange, i: usize) Id {
34 std.debug.assert(i < range.len);
35 return @enumFromInt(range.base + i);
36 }
37};
38
2939pub const LiteralInteger = Word;
3040pub const LiteralFloat = Word;
3141pub const LiteralString = []const u8;
......@@ -181,25 +191,6 @@ pub const OperandKind = enum {
181191 pair_id_ref_literal_integer,
182192 pair_id_ref_id_ref,
183193 tensor_operands,
184 debug_info_debug_info_flags,
185 debug_info_debug_base_type_attribute_encoding,
186 debug_info_debug_composite_type,
187 debug_info_debug_type_qualifier,
188 debug_info_debug_operation,
189 open_cl_debug_info_100_debug_info_flags,
190 open_cl_debug_info_100_debug_base_type_attribute_encoding,
191 open_cl_debug_info_100_debug_composite_type,
192 open_cl_debug_info_100_debug_type_qualifier,
193 open_cl_debug_info_100_debug_operation,
194 open_cl_debug_info_100_debug_imported_entity,
195 non_semantic_clspv_reflection_6_kernel_property_flags,
196 non_semantic_shader_debug_info_100_debug_info_flags,
197 non_semantic_shader_debug_info_100_build_identifier_flags,
198 non_semantic_shader_debug_info_100_debug_base_type_attribute_encoding,
199 non_semantic_shader_debug_info_100_debug_composite_type,
200 non_semantic_shader_debug_info_100_debug_type_qualifier,
201 non_semantic_shader_debug_info_100_debug_operation,
202 non_semantic_shader_debug_info_100_debug_imported_entity,
203194
204195 pub fn category(self: OperandKind) OperandCategory {
205196 return switch (self) {
......@@ -275,25 +266,6 @@ pub const OperandKind = enum {
275266 .pair_id_ref_literal_integer => .composite,
276267 .pair_id_ref_id_ref => .composite,
277268 .tensor_operands => .bit_enum,
278 .debug_info_debug_info_flags => .bit_enum,
279 .debug_info_debug_base_type_attribute_encoding => .value_enum,
280 .debug_info_debug_composite_type => .value_enum,
281 .debug_info_debug_type_qualifier => .value_enum,
282 .debug_info_debug_operation => .value_enum,
283 .open_cl_debug_info_100_debug_info_flags => .bit_enum,
284 .open_cl_debug_info_100_debug_base_type_attribute_encoding => .value_enum,
285 .open_cl_debug_info_100_debug_composite_type => .value_enum,
286 .open_cl_debug_info_100_debug_type_qualifier => .value_enum,
287 .open_cl_debug_info_100_debug_operation => .value_enum,
288 .open_cl_debug_info_100_debug_imported_entity => .value_enum,
289 .non_semantic_clspv_reflection_6_kernel_property_flags => .bit_enum,
290 .non_semantic_shader_debug_info_100_debug_info_flags => .bit_enum,
291 .non_semantic_shader_debug_info_100_build_identifier_flags => .bit_enum,
292 .non_semantic_shader_debug_info_100_debug_base_type_attribute_encoding => .value_enum,
293 .non_semantic_shader_debug_info_100_debug_composite_type => .value_enum,
294 .non_semantic_shader_debug_info_100_debug_type_qualifier => .value_enum,
295 .non_semantic_shader_debug_info_100_debug_operation => .value_enum,
296 .non_semantic_shader_debug_info_100_debug_imported_entity => .value_enum,
297269 };
298270 }
299271 pub fn enumerants(self: OperandKind) []const Enumerant {
......@@ -1465,178 +1437,10 @@ pub const OperandKind = enum {
14651437 .{ .name = "MakeElementVisibleARM", .value = 0x0008, .parameters = &.{.id_ref} },
14661438 .{ .name = "NonPrivateElementARM", .value = 0x0010, .parameters = &.{} },
14671439 },
1468 .debug_info_debug_info_flags => &.{
1469 .{ .name = "FlagIsProtected", .value = 0x01, .parameters = &.{} },
1470 .{ .name = "FlagIsPrivate", .value = 0x02, .parameters = &.{} },
1471 .{ .name = "FlagIsPublic", .value = 0x03, .parameters = &.{} },
1472 .{ .name = "FlagIsLocal", .value = 0x04, .parameters = &.{} },
1473 .{ .name = "FlagIsDefinition", .value = 0x08, .parameters = &.{} },
1474 .{ .name = "FlagFwdDecl", .value = 0x10, .parameters = &.{} },
1475 .{ .name = "FlagArtificial", .value = 0x20, .parameters = &.{} },
1476 .{ .name = "FlagExplicit", .value = 0x40, .parameters = &.{} },
1477 .{ .name = "FlagPrototyped", .value = 0x80, .parameters = &.{} },
1478 .{ .name = "FlagObjectPointer", .value = 0x100, .parameters = &.{} },
1479 .{ .name = "FlagStaticMember", .value = 0x200, .parameters = &.{} },
1480 .{ .name = "FlagIndirectVariable", .value = 0x400, .parameters = &.{} },
1481 .{ .name = "FlagLValueReference", .value = 0x800, .parameters = &.{} },
1482 .{ .name = "FlagRValueReference", .value = 0x1000, .parameters = &.{} },
1483 .{ .name = "FlagIsOptimized", .value = 0x2000, .parameters = &.{} },
1484 },
1485 .debug_info_debug_base_type_attribute_encoding => &.{
1486 .{ .name = "Unspecified", .value = 0, .parameters = &.{} },
1487 .{ .name = "Address", .value = 1, .parameters = &.{} },
1488 .{ .name = "Boolean", .value = 2, .parameters = &.{} },
1489 .{ .name = "Float", .value = 4, .parameters = &.{} },
1490 .{ .name = "Signed", .value = 5, .parameters = &.{} },
1491 .{ .name = "SignedChar", .value = 6, .parameters = &.{} },
1492 .{ .name = "Unsigned", .value = 7, .parameters = &.{} },
1493 .{ .name = "UnsignedChar", .value = 8, .parameters = &.{} },
1494 },
1495 .debug_info_debug_composite_type => &.{
1496 .{ .name = "Class", .value = 0, .parameters = &.{} },
1497 .{ .name = "Structure", .value = 1, .parameters = &.{} },
1498 .{ .name = "Union", .value = 2, .parameters = &.{} },
1499 },
1500 .debug_info_debug_type_qualifier => &.{
1501 .{ .name = "ConstType", .value = 0, .parameters = &.{} },
1502 .{ .name = "VolatileType", .value = 1, .parameters = &.{} },
1503 .{ .name = "RestrictType", .value = 2, .parameters = &.{} },
1504 },
1505 .debug_info_debug_operation => &.{
1506 .{ .name = "Deref", .value = 0, .parameters = &.{} },
1507 .{ .name = "Plus", .value = 1, .parameters = &.{} },
1508 .{ .name = "Minus", .value = 2, .parameters = &.{} },
1509 .{ .name = "PlusUconst", .value = 3, .parameters = &.{.literal_integer} },
1510 .{ .name = "BitPiece", .value = 4, .parameters = &.{ .literal_integer, .literal_integer } },
1511 .{ .name = "Swap", .value = 5, .parameters = &.{} },
1512 .{ .name = "Xderef", .value = 6, .parameters = &.{} },
1513 .{ .name = "StackValue", .value = 7, .parameters = &.{} },
1514 .{ .name = "Constu", .value = 8, .parameters = &.{.literal_integer} },
1515 },
1516 .open_cl_debug_info_100_debug_info_flags => &.{
1517 .{ .name = "FlagIsProtected", .value = 0x01, .parameters = &.{} },
1518 .{ .name = "FlagIsPrivate", .value = 0x02, .parameters = &.{} },
1519 .{ .name = "FlagIsPublic", .value = 0x03, .parameters = &.{} },
1520 .{ .name = "FlagIsLocal", .value = 0x04, .parameters = &.{} },
1521 .{ .name = "FlagIsDefinition", .value = 0x08, .parameters = &.{} },
1522 .{ .name = "FlagFwdDecl", .value = 0x10, .parameters = &.{} },
1523 .{ .name = "FlagArtificial", .value = 0x20, .parameters = &.{} },
1524 .{ .name = "FlagExplicit", .value = 0x40, .parameters = &.{} },
1525 .{ .name = "FlagPrototyped", .value = 0x80, .parameters = &.{} },
1526 .{ .name = "FlagObjectPointer", .value = 0x100, .parameters = &.{} },
1527 .{ .name = "FlagStaticMember", .value = 0x200, .parameters = &.{} },
1528 .{ .name = "FlagIndirectVariable", .value = 0x400, .parameters = &.{} },
1529 .{ .name = "FlagLValueReference", .value = 0x800, .parameters = &.{} },
1530 .{ .name = "FlagRValueReference", .value = 0x1000, .parameters = &.{} },
1531 .{ .name = "FlagIsOptimized", .value = 0x2000, .parameters = &.{} },
1532 .{ .name = "FlagIsEnumClass", .value = 0x4000, .parameters = &.{} },
1533 .{ .name = "FlagTypePassByValue", .value = 0x8000, .parameters = &.{} },
1534 .{ .name = "FlagTypePassByReference", .value = 0x10000, .parameters = &.{} },
1535 },
1536 .open_cl_debug_info_100_debug_base_type_attribute_encoding => &.{
1537 .{ .name = "Unspecified", .value = 0, .parameters = &.{} },
1538 .{ .name = "Address", .value = 1, .parameters = &.{} },
1539 .{ .name = "Boolean", .value = 2, .parameters = &.{} },
1540 .{ .name = "Float", .value = 3, .parameters = &.{} },
1541 .{ .name = "Signed", .value = 4, .parameters = &.{} },
1542 .{ .name = "SignedChar", .value = 5, .parameters = &.{} },
1543 .{ .name = "Unsigned", .value = 6, .parameters = &.{} },
1544 .{ .name = "UnsignedChar", .value = 7, .parameters = &.{} },
1545 },
1546 .open_cl_debug_info_100_debug_composite_type => &.{
1547 .{ .name = "Class", .value = 0, .parameters = &.{} },
1548 .{ .name = "Structure", .value = 1, .parameters = &.{} },
1549 .{ .name = "Union", .value = 2, .parameters = &.{} },
1550 },
1551 .open_cl_debug_info_100_debug_type_qualifier => &.{
1552 .{ .name = "ConstType", .value = 0, .parameters = &.{} },
1553 .{ .name = "VolatileType", .value = 1, .parameters = &.{} },
1554 .{ .name = "RestrictType", .value = 2, .parameters = &.{} },
1555 .{ .name = "AtomicType", .value = 3, .parameters = &.{} },
1556 },
1557 .open_cl_debug_info_100_debug_operation => &.{
1558 .{ .name = "Deref", .value = 0, .parameters = &.{} },
1559 .{ .name = "Plus", .value = 1, .parameters = &.{} },
1560 .{ .name = "Minus", .value = 2, .parameters = &.{} },
1561 .{ .name = "PlusUconst", .value = 3, .parameters = &.{.literal_integer} },
1562 .{ .name = "BitPiece", .value = 4, .parameters = &.{ .literal_integer, .literal_integer } },
1563 .{ .name = "Swap", .value = 5, .parameters = &.{} },
1564 .{ .name = "Xderef", .value = 6, .parameters = &.{} },
1565 .{ .name = "StackValue", .value = 7, .parameters = &.{} },
1566 .{ .name = "Constu", .value = 8, .parameters = &.{.literal_integer} },
1567 .{ .name = "Fragment", .value = 9, .parameters = &.{ .literal_integer, .literal_integer } },
1568 },
1569 .open_cl_debug_info_100_debug_imported_entity => &.{
1570 .{ .name = "ImportedModule", .value = 0, .parameters = &.{} },
1571 .{ .name = "ImportedDeclaration", .value = 1, .parameters = &.{} },
1572 },
1573 .non_semantic_clspv_reflection_6_kernel_property_flags => &.{
1574 .{ .name = "MayUsePrintf", .value = 0x1, .parameters = &.{} },
1575 },
1576 .non_semantic_shader_debug_info_100_debug_info_flags => &.{
1577 .{ .name = "FlagIsProtected", .value = 0x01, .parameters = &.{} },
1578 .{ .name = "FlagIsPrivate", .value = 0x02, .parameters = &.{} },
1579 .{ .name = "FlagIsPublic", .value = 0x03, .parameters = &.{} },
1580 .{ .name = "FlagIsLocal", .value = 0x04, .parameters = &.{} },
1581 .{ .name = "FlagIsDefinition", .value = 0x08, .parameters = &.{} },
1582 .{ .name = "FlagFwdDecl", .value = 0x10, .parameters = &.{} },
1583 .{ .name = "FlagArtificial", .value = 0x20, .parameters = &.{} },
1584 .{ .name = "FlagExplicit", .value = 0x40, .parameters = &.{} },
1585 .{ .name = "FlagPrototyped", .value = 0x80, .parameters = &.{} },
1586 .{ .name = "FlagObjectPointer", .value = 0x100, .parameters = &.{} },
1587 .{ .name = "FlagStaticMember", .value = 0x200, .parameters = &.{} },
1588 .{ .name = "FlagIndirectVariable", .value = 0x400, .parameters = &.{} },
1589 .{ .name = "FlagLValueReference", .value = 0x800, .parameters = &.{} },
1590 .{ .name = "FlagRValueReference", .value = 0x1000, .parameters = &.{} },
1591 .{ .name = "FlagIsOptimized", .value = 0x2000, .parameters = &.{} },
1592 .{ .name = "FlagIsEnumClass", .value = 0x4000, .parameters = &.{} },
1593 .{ .name = "FlagTypePassByValue", .value = 0x8000, .parameters = &.{} },
1594 .{ .name = "FlagTypePassByReference", .value = 0x10000, .parameters = &.{} },
1595 .{ .name = "FlagUnknownPhysicalLayout", .value = 0x20000, .parameters = &.{} },
1596 },
1597 .non_semantic_shader_debug_info_100_build_identifier_flags => &.{
1598 .{ .name = "IdentifierPossibleDuplicates", .value = 0x01, .parameters = &.{} },
1599 },
1600 .non_semantic_shader_debug_info_100_debug_base_type_attribute_encoding => &.{
1601 .{ .name = "Unspecified", .value = 0, .parameters = &.{} },
1602 .{ .name = "Address", .value = 1, .parameters = &.{} },
1603 .{ .name = "Boolean", .value = 2, .parameters = &.{} },
1604 .{ .name = "Float", .value = 3, .parameters = &.{} },
1605 .{ .name = "Signed", .value = 4, .parameters = &.{} },
1606 .{ .name = "SignedChar", .value = 5, .parameters = &.{} },
1607 .{ .name = "Unsigned", .value = 6, .parameters = &.{} },
1608 .{ .name = "UnsignedChar", .value = 7, .parameters = &.{} },
1609 },
1610 .non_semantic_shader_debug_info_100_debug_composite_type => &.{
1611 .{ .name = "Class", .value = 0, .parameters = &.{} },
1612 .{ .name = "Structure", .value = 1, .parameters = &.{} },
1613 .{ .name = "Union", .value = 2, .parameters = &.{} },
1614 },
1615 .non_semantic_shader_debug_info_100_debug_type_qualifier => &.{
1616 .{ .name = "ConstType", .value = 0, .parameters = &.{} },
1617 .{ .name = "VolatileType", .value = 1, .parameters = &.{} },
1618 .{ .name = "RestrictType", .value = 2, .parameters = &.{} },
1619 .{ .name = "AtomicType", .value = 3, .parameters = &.{} },
1620 },
1621 .non_semantic_shader_debug_info_100_debug_operation => &.{
1622 .{ .name = "Deref", .value = 0, .parameters = &.{} },
1623 .{ .name = "Plus", .value = 1, .parameters = &.{} },
1624 .{ .name = "Minus", .value = 2, .parameters = &.{} },
1625 .{ .name = "PlusUconst", .value = 3, .parameters = &.{.id_ref} },
1626 .{ .name = "BitPiece", .value = 4, .parameters = &.{ .id_ref, .id_ref } },
1627 .{ .name = "Swap", .value = 5, .parameters = &.{} },
1628 .{ .name = "Xderef", .value = 6, .parameters = &.{} },
1629 .{ .name = "StackValue", .value = 7, .parameters = &.{} },
1630 .{ .name = "Constu", .value = 8, .parameters = &.{.id_ref} },
1631 .{ .name = "Fragment", .value = 9, .parameters = &.{ .id_ref, .id_ref } },
1632 },
1633 .non_semantic_shader_debug_info_100_debug_imported_entity => &.{
1634 .{ .name = "ImportedModule", .value = 0, .parameters = &.{} },
1635 .{ .name = "ImportedDeclaration", .value = 1, .parameters = &.{} },
1636 },
16371440 };
16381441 }
16391442};
1443
16401444pub const Opcode = enum(u16) {
16411445 OpNop = 0,
16421446 OpUndef = 1,
......@@ -3523,6 +3327,259 @@ pub const Opcode = enum(u16) {
35233327 };
35243328 }
35253329};
3330
3331pub const GlslOpcode = enum(u16) {
3332 Round = 1,
3333 RoundEven = 2,
3334 Trunc = 3,
3335 FAbs = 4,
3336 SAbs = 5,
3337 FSign = 6,
3338 SSign = 7,
3339 Floor = 8,
3340 Ceil = 9,
3341 Fract = 10,
3342 Radians = 11,
3343 Degrees = 12,
3344 Sin = 13,
3345 Cos = 14,
3346 Tan = 15,
3347 Asin = 16,
3348 Acos = 17,
3349 Atan = 18,
3350 Sinh = 19,
3351 Cosh = 20,
3352 Tanh = 21,
3353 Asinh = 22,
3354 Acosh = 23,
3355 Atanh = 24,
3356 Atan2 = 25,
3357 Pow = 26,
3358 Exp = 27,
3359 Log = 28,
3360 Exp2 = 29,
3361 Log2 = 30,
3362 Sqrt = 31,
3363 InverseSqrt = 32,
3364 Determinant = 33,
3365 MatrixInverse = 34,
3366 Modf = 35,
3367 ModfStruct = 36,
3368 FMin = 37,
3369 UMin = 38,
3370 SMin = 39,
3371 FMax = 40,
3372 UMax = 41,
3373 SMax = 42,
3374 FClamp = 43,
3375 UClamp = 44,
3376 SClamp = 45,
3377 FMix = 46,
3378 IMix = 47,
3379 Step = 48,
3380 SmoothStep = 49,
3381 Fma = 50,
3382 Frexp = 51,
3383 FrexpStruct = 52,
3384 Ldexp = 53,
3385 PackSnorm4x8 = 54,
3386 PackUnorm4x8 = 55,
3387 PackSnorm2x16 = 56,
3388 PackUnorm2x16 = 57,
3389 PackHalf2x16 = 58,
3390 PackDouble2x32 = 59,
3391 UnpackSnorm2x16 = 60,
3392 UnpackUnorm2x16 = 61,
3393 UnpackHalf2x16 = 62,
3394 UnpackSnorm4x8 = 63,
3395 UnpackUnorm4x8 = 64,
3396 UnpackDouble2x32 = 65,
3397 Length = 66,
3398 Distance = 67,
3399 Cross = 68,
3400 Normalize = 69,
3401 FaceForward = 70,
3402 Reflect = 71,
3403 Refract = 72,
3404 FindILsb = 73,
3405 FindSMsb = 74,
3406 FindUMsb = 75,
3407 InterpolateAtCentroid = 76,
3408 InterpolateAtSample = 77,
3409 InterpolateAtOffset = 78,
3410 NMin = 79,
3411 NMax = 80,
3412 NClamp = 81,
3413};
3414
3415pub const OpenClOpcode = enum(u16) {
3416 acos = 0,
3417 acosh = 1,
3418 acospi = 2,
3419 asin = 3,
3420 asinh = 4,
3421 asinpi = 5,
3422 atan = 6,
3423 atan2 = 7,
3424 atanh = 8,
3425 atanpi = 9,
3426 atan2pi = 10,
3427 cbrt = 11,
3428 ceil = 12,
3429 copysign = 13,
3430 cos = 14,
3431 cosh = 15,
3432 cospi = 16,
3433 erfc = 17,
3434 erf = 18,
3435 exp = 19,
3436 exp2 = 20,
3437 exp10 = 21,
3438 expm1 = 22,
3439 fabs = 23,
3440 fdim = 24,
3441 floor = 25,
3442 fma = 26,
3443 fmax = 27,
3444 fmin = 28,
3445 fmod = 29,
3446 fract = 30,
3447 frexp = 31,
3448 hypot = 32,
3449 ilogb = 33,
3450 ldexp = 34,
3451 lgamma = 35,
3452 lgamma_r = 36,
3453 log = 37,
3454 log2 = 38,
3455 log10 = 39,
3456 log1p = 40,
3457 logb = 41,
3458 mad = 42,
3459 maxmag = 43,
3460 minmag = 44,
3461 modf = 45,
3462 nan = 46,
3463 nextafter = 47,
3464 pow = 48,
3465 pown = 49,
3466 powr = 50,
3467 remainder = 51,
3468 remquo = 52,
3469 rint = 53,
3470 rootn = 54,
3471 round = 55,
3472 rsqrt = 56,
3473 sin = 57,
3474 sincos = 58,
3475 sinh = 59,
3476 sinpi = 60,
3477 sqrt = 61,
3478 tan = 62,
3479 tanh = 63,
3480 tanpi = 64,
3481 tgamma = 65,
3482 trunc = 66,
3483 half_cos = 67,
3484 half_divide = 68,
3485 half_exp = 69,
3486 half_exp2 = 70,
3487 half_exp10 = 71,
3488 half_log = 72,
3489 half_log2 = 73,
3490 half_log10 = 74,
3491 half_powr = 75,
3492 half_recip = 76,
3493 half_rsqrt = 77,
3494 half_sin = 78,
3495 half_sqrt = 79,
3496 half_tan = 80,
3497 native_cos = 81,
3498 native_divide = 82,
3499 native_exp = 83,
3500 native_exp2 = 84,
3501 native_exp10 = 85,
3502 native_log = 86,
3503 native_log2 = 87,
3504 native_log10 = 88,
3505 native_powr = 89,
3506 native_recip = 90,
3507 native_rsqrt = 91,
3508 native_sin = 92,
3509 native_sqrt = 93,
3510 native_tan = 94,
3511 fclamp = 95,
3512 degrees = 96,
3513 fmax_common = 97,
3514 fmin_common = 98,
3515 mix = 99,
3516 radians = 100,
3517 step = 101,
3518 smoothstep = 102,
3519 sign = 103,
3520 cross = 104,
3521 distance = 105,
3522 length = 106,
3523 normalize = 107,
3524 fast_distance = 108,
3525 fast_length = 109,
3526 fast_normalize = 110,
3527 s_abs = 141,
3528 s_abs_diff = 142,
3529 s_add_sat = 143,
3530 u_add_sat = 144,
3531 s_hadd = 145,
3532 u_hadd = 146,
3533 s_rhadd = 147,
3534 u_rhadd = 148,
3535 s_clamp = 149,
3536 u_clamp = 150,
3537 clz = 151,
3538 ctz = 152,
3539 s_mad_hi = 153,
3540 u_mad_sat = 154,
3541 s_mad_sat = 155,
3542 s_max = 156,
3543 u_max = 157,
3544 s_min = 158,
3545 u_min = 159,
3546 s_mul_hi = 160,
3547 rotate = 161,
3548 s_sub_sat = 162,
3549 u_sub_sat = 163,
3550 u_upsample = 164,
3551 s_upsample = 165,
3552 popcount = 166,
3553 s_mad24 = 167,
3554 u_mad24 = 168,
3555 s_mul24 = 169,
3556 u_mul24 = 170,
3557 vloadn = 171,
3558 vstoren = 172,
3559 vload_half = 173,
3560 vload_halfn = 174,
3561 vstore_half = 175,
3562 vstore_half_r = 176,
3563 vstore_halfn = 177,
3564 vstore_halfn_r = 178,
3565 vloada_halfn = 179,
3566 vstorea_halfn = 180,
3567 vstorea_halfn_r = 181,
3568 shuffle = 182,
3569 shuffle2 = 183,
3570 printf = 184,
3571 prefetch = 185,
3572 bitselect = 186,
3573 select = 187,
3574 u_abs = 201,
3575 u_abs_diff = 202,
3576 u_mul_hi = 203,
3577 u_mad_hi = 204,
3578};
3579
3580pub const Zig = enum(u16) {
3581 InvocationGlobal = 0,
3582};
35263583pub const ImageOperands = packed struct {
35273584 bias: bool = false,
35283585 lod: bool = false,
......@@ -5484,335 +5541,10 @@ pub const TensorOperands = packed struct {
54845541 _reserved_bit_31: bool = false,
54855542 };
54865543};
5487pub const @"DebugInfo.DebugInfoFlags" = packed struct {
5488 flag_is_protected: bool = false,
5489 flag_is_private: bool = false,
5490 flag_is_local: bool = false,
5491 flag_is_definition: bool = false,
5492 flag_fwd_decl: bool = false,
5493 flag_artificial: bool = false,
5494 flag_explicit: bool = false,
5495 flag_prototyped: bool = false,
5496 flag_object_pointer: bool = false,
5497 flag_static_member: bool = false,
5498 flag_indirect_variable: bool = false,
5499 flag_l_value_reference: bool = false,
5500 flag_r_value_reference: bool = false,
5501 flag_is_optimized: bool = false,
5502 _reserved_bit_14: bool = false,
5503 _reserved_bit_15: bool = false,
5504 _reserved_bit_16: bool = false,
5505 _reserved_bit_17: bool = false,
5506 _reserved_bit_18: bool = false,
5507 _reserved_bit_19: bool = false,
5508 _reserved_bit_20: bool = false,
5509 _reserved_bit_21: bool = false,
5510 _reserved_bit_22: bool = false,
5511 _reserved_bit_23: bool = false,
5512 _reserved_bit_24: bool = false,
5513 _reserved_bit_25: bool = false,
5514 _reserved_bit_26: bool = false,
5515 _reserved_bit_27: bool = false,
5516 _reserved_bit_28: bool = false,
5517 _reserved_bit_29: bool = false,
5518 _reserved_bit_30: bool = false,
5519 _reserved_bit_31: bool = false,
5520};
5521pub const @"DebugInfo.DebugBaseTypeAttributeEncoding" = enum(u32) {
5522 unspecified = 0,
5523 address = 1,
5524 boolean = 2,
5525 float = 4,
5526 signed = 5,
5527 signed_char = 6,
5528 unsigned = 7,
5529 unsigned_char = 8,
5530};
5531pub const @"DebugInfo.DebugCompositeType" = enum(u32) {
5532 class = 0,
5533 structure = 1,
5534 @"union" = 2,
5535};
5536pub const @"DebugInfo.DebugTypeQualifier" = enum(u32) {
5537 const_type = 0,
5538 volatile_type = 1,
5539 restrict_type = 2,
5540};
5541pub const @"DebugInfo.DebugOperation" = enum(u32) {
5542 deref = 0,
5543 plus = 1,
5544 minus = 2,
5545 plus_uconst = 3,
5546 bit_piece = 4,
5547 swap = 5,
5548 xderef = 6,
5549 stack_value = 7,
5550 constu = 8,
5551
5552 pub const Extended = union(@"DebugInfo.DebugOperation") {
5553 deref,
5554 plus,
5555 minus,
5556 plus_uconst: struct { literal_integer: LiteralInteger },
5557 bit_piece: struct { literal_integer_0: LiteralInteger, literal_integer_1: LiteralInteger },
5558 swap,
5559 xderef,
5560 stack_value,
5561 constu: struct { literal_integer: LiteralInteger },
5562 };
5563};
5564pub const @"OpenCL.DebugInfo.100.DebugInfoFlags" = packed struct {
5565 flag_is_protected: bool = false,
5566 flag_is_private: bool = false,
5567 flag_is_local: bool = false,
5568 flag_is_definition: bool = false,
5569 flag_fwd_decl: bool = false,
5570 flag_artificial: bool = false,
5571 flag_explicit: bool = false,
5572 flag_prototyped: bool = false,
5573 flag_object_pointer: bool = false,
5574 flag_static_member: bool = false,
5575 flag_indirect_variable: bool = false,
5576 flag_l_value_reference: bool = false,
5577 flag_r_value_reference: bool = false,
5578 flag_is_optimized: bool = false,
5579 flag_is_enum_class: bool = false,
5580 flag_type_pass_by_value: bool = false,
5581 flag_type_pass_by_reference: bool = false,
5582 _reserved_bit_17: bool = false,
5583 _reserved_bit_18: bool = false,
5584 _reserved_bit_19: bool = false,
5585 _reserved_bit_20: bool = false,
5586 _reserved_bit_21: bool = false,
5587 _reserved_bit_22: bool = false,
5588 _reserved_bit_23: bool = false,
5589 _reserved_bit_24: bool = false,
5590 _reserved_bit_25: bool = false,
5591 _reserved_bit_26: bool = false,
5592 _reserved_bit_27: bool = false,
5593 _reserved_bit_28: bool = false,
5594 _reserved_bit_29: bool = false,
5595 _reserved_bit_30: bool = false,
5596 _reserved_bit_31: bool = false,
5597};
5598pub const @"OpenCL.DebugInfo.100.DebugBaseTypeAttributeEncoding" = enum(u32) {
5599 unspecified = 0,
5600 address = 1,
5601 boolean = 2,
5602 float = 3,
5603 signed = 4,
5604 signed_char = 5,
5605 unsigned = 6,
5606 unsigned_char = 7,
5607};
5608pub const @"OpenCL.DebugInfo.100.DebugCompositeType" = enum(u32) {
5609 class = 0,
5610 structure = 1,
5611 @"union" = 2,
5612};
5613pub const @"OpenCL.DebugInfo.100.DebugTypeQualifier" = enum(u32) {
5614 const_type = 0,
5615 volatile_type = 1,
5616 restrict_type = 2,
5617 atomic_type = 3,
5618};
5619pub const @"OpenCL.DebugInfo.100.DebugOperation" = enum(u32) {
5620 deref = 0,
5621 plus = 1,
5622 minus = 2,
5623 plus_uconst = 3,
5624 bit_piece = 4,
5625 swap = 5,
5626 xderef = 6,
5627 stack_value = 7,
5628 constu = 8,
5629 fragment = 9,
5630
5631 pub const Extended = union(@"OpenCL.DebugInfo.100.DebugOperation") {
5632 deref,
5633 plus,
5634 minus,
5635 plus_uconst: struct { literal_integer: LiteralInteger },
5636 bit_piece: struct { literal_integer_0: LiteralInteger, literal_integer_1: LiteralInteger },
5637 swap,
5638 xderef,
5639 stack_value,
5640 constu: struct { literal_integer: LiteralInteger },
5641 fragment: struct { literal_integer_0: LiteralInteger, literal_integer_1: LiteralInteger },
5642 };
5643};
5644pub const @"OpenCL.DebugInfo.100.DebugImportedEntity" = enum(u32) {
5645 imported_module = 0,
5646 imported_declaration = 1,
5647};
5648pub const @"NonSemantic.ClspvReflection.6.KernelPropertyFlags" = packed struct {
5649 may_use_printf: bool = false,
5650 _reserved_bit_1: bool = false,
5651 _reserved_bit_2: bool = false,
5652 _reserved_bit_3: bool = false,
5653 _reserved_bit_4: bool = false,
5654 _reserved_bit_5: bool = false,
5655 _reserved_bit_6: bool = false,
5656 _reserved_bit_7: bool = false,
5657 _reserved_bit_8: bool = false,
5658 _reserved_bit_9: bool = false,
5659 _reserved_bit_10: bool = false,
5660 _reserved_bit_11: bool = false,
5661 _reserved_bit_12: bool = false,
5662 _reserved_bit_13: bool = false,
5663 _reserved_bit_14: bool = false,
5664 _reserved_bit_15: bool = false,
5665 _reserved_bit_16: bool = false,
5666 _reserved_bit_17: bool = false,
5667 _reserved_bit_18: bool = false,
5668 _reserved_bit_19: bool = false,
5669 _reserved_bit_20: bool = false,
5670 _reserved_bit_21: bool = false,
5671 _reserved_bit_22: bool = false,
5672 _reserved_bit_23: bool = false,
5673 _reserved_bit_24: bool = false,
5674 _reserved_bit_25: bool = false,
5675 _reserved_bit_26: bool = false,
5676 _reserved_bit_27: bool = false,
5677 _reserved_bit_28: bool = false,
5678 _reserved_bit_29: bool = false,
5679 _reserved_bit_30: bool = false,
5680 _reserved_bit_31: bool = false,
5681};
5682pub const @"NonSemantic.Shader.DebugInfo.100.DebugInfoFlags" = packed struct {
5683 flag_is_protected: bool = false,
5684 flag_is_private: bool = false,
5685 flag_is_local: bool = false,
5686 flag_is_definition: bool = false,
5687 flag_fwd_decl: bool = false,
5688 flag_artificial: bool = false,
5689 flag_explicit: bool = false,
5690 flag_prototyped: bool = false,
5691 flag_object_pointer: bool = false,
5692 flag_static_member: bool = false,
5693 flag_indirect_variable: bool = false,
5694 flag_l_value_reference: bool = false,
5695 flag_r_value_reference: bool = false,
5696 flag_is_optimized: bool = false,
5697 flag_is_enum_class: bool = false,
5698 flag_type_pass_by_value: bool = false,
5699 flag_type_pass_by_reference: bool = false,
5700 flag_unknown_physical_layout: bool = false,
5701 _reserved_bit_18: bool = false,
5702 _reserved_bit_19: bool = false,
5703 _reserved_bit_20: bool = false,
5704 _reserved_bit_21: bool = false,
5705 _reserved_bit_22: bool = false,
5706 _reserved_bit_23: bool = false,
5707 _reserved_bit_24: bool = false,
5708 _reserved_bit_25: bool = false,
5709 _reserved_bit_26: bool = false,
5710 _reserved_bit_27: bool = false,
5711 _reserved_bit_28: bool = false,
5712 _reserved_bit_29: bool = false,
5713 _reserved_bit_30: bool = false,
5714 _reserved_bit_31: bool = false,
5715};
5716pub const @"NonSemantic.Shader.DebugInfo.100.BuildIdentifierFlags" = packed struct {
5717 identifier_possible_duplicates: bool = false,
5718 _reserved_bit_1: bool = false,
5719 _reserved_bit_2: bool = false,
5720 _reserved_bit_3: bool = false,
5721 _reserved_bit_4: bool = false,
5722 _reserved_bit_5: bool = false,
5723 _reserved_bit_6: bool = false,
5724 _reserved_bit_7: bool = false,
5725 _reserved_bit_8: bool = false,
5726 _reserved_bit_9: bool = false,
5727 _reserved_bit_10: bool = false,
5728 _reserved_bit_11: bool = false,
5729 _reserved_bit_12: bool = false,
5730 _reserved_bit_13: bool = false,
5731 _reserved_bit_14: bool = false,
5732 _reserved_bit_15: bool = false,
5733 _reserved_bit_16: bool = false,
5734 _reserved_bit_17: bool = false,
5735 _reserved_bit_18: bool = false,
5736 _reserved_bit_19: bool = false,
5737 _reserved_bit_20: bool = false,
5738 _reserved_bit_21: bool = false,
5739 _reserved_bit_22: bool = false,
5740 _reserved_bit_23: bool = false,
5741 _reserved_bit_24: bool = false,
5742 _reserved_bit_25: bool = false,
5743 _reserved_bit_26: bool = false,
5744 _reserved_bit_27: bool = false,
5745 _reserved_bit_28: bool = false,
5746 _reserved_bit_29: bool = false,
5747 _reserved_bit_30: bool = false,
5748 _reserved_bit_31: bool = false,
5749};
5750pub const @"NonSemantic.Shader.DebugInfo.100.DebugBaseTypeAttributeEncoding" = enum(u32) {
5751 unspecified = 0,
5752 address = 1,
5753 boolean = 2,
5754 float = 3,
5755 signed = 4,
5756 signed_char = 5,
5757 unsigned = 6,
5758 unsigned_char = 7,
5759};
5760pub const @"NonSemantic.Shader.DebugInfo.100.DebugCompositeType" = enum(u32) {
5761 class = 0,
5762 structure = 1,
5763 @"union" = 2,
5764};
5765pub const @"NonSemantic.Shader.DebugInfo.100.DebugTypeQualifier" = enum(u32) {
5766 const_type = 0,
5767 volatile_type = 1,
5768 restrict_type = 2,
5769 atomic_type = 3,
5770};
5771pub const @"NonSemantic.Shader.DebugInfo.100.DebugOperation" = enum(u32) {
5772 deref = 0,
5773 plus = 1,
5774 minus = 2,
5775 plus_uconst = 3,
5776 bit_piece = 4,
5777 swap = 5,
5778 xderef = 6,
5779 stack_value = 7,
5780 constu = 8,
5781 fragment = 9,
5782
5783 pub const Extended = union(@"NonSemantic.Shader.DebugInfo.100.DebugOperation") {
5784 deref,
5785 plus,
5786 minus,
5787 plus_uconst: struct { id_ref: Id },
5788 bit_piece: struct { id_ref_0: Id, id_ref_1: Id },
5789 swap,
5790 xderef,
5791 stack_value,
5792 constu: struct { id_ref: Id },
5793 fragment: struct { id_ref_0: Id, id_ref_1: Id },
5794 };
5795};
5796pub const @"NonSemantic.Shader.DebugInfo.100.DebugImportedEntity" = enum(u32) {
5797 imported_module = 0,
5798 imported_declaration = 1,
5799};
58005544pub const InstructionSet = enum {
58015545 core,
5802 spv_amd_shader_trinary_minmax,
5803 spv_ext_inst_type_tosa_001000_1,
5804 non_semantic_vksp_reflection,
5805 spv_amd_shader_explicit_vertex_parameter,
5806 debug_info,
5807 non_semantic_debug_break,
5808 open_cl_debug_info_100,
5809 non_semantic_clspv_reflection_6,
5810 glsl_std_450,
5811 spv_amd_shader_ballot,
5812 non_semantic_debug_printf,
5813 spv_amd_gcn_shader,
5814 open_cl_std,
5815 non_semantic_shader_debug_info_100,
5546 @"GLSL.std.450",
5547 @"OpenCL.std",
58165548 zig,
58175549
58185550 pub fn instructions(self: InstructionSet) []const Instruction {
......@@ -14078,480 +13810,321 @@ pub const InstructionSet = enum {
1407813810 },
1407913811 },
1408013812 },
14081 .spv_amd_shader_trinary_minmax => &.{
13813 .@"GLSL.std.450" => &.{
1408213814 .{
14083 .name = "FMin3AMD",
13815 .name = "Round",
1408413816 .opcode = 1,
1408513817 .operands = &.{
1408613818 .{ .kind = .id_ref, .quantifier = .required },
14087 .{ .kind = .id_ref, .quantifier = .required },
14088 .{ .kind = .id_ref, .quantifier = .required },
1408913819 },
1409013820 },
1409113821 .{
14092 .name = "UMin3AMD",
13822 .name = "RoundEven",
1409313823 .opcode = 2,
1409413824 .operands = &.{
1409513825 .{ .kind = .id_ref, .quantifier = .required },
14096 .{ .kind = .id_ref, .quantifier = .required },
14097 .{ .kind = .id_ref, .quantifier = .required },
1409813826 },
1409913827 },
1410013828 .{
14101 .name = "SMin3AMD",
13829 .name = "Trunc",
1410213830 .opcode = 3,
1410313831 .operands = &.{
1410413832 .{ .kind = .id_ref, .quantifier = .required },
14105 .{ .kind = .id_ref, .quantifier = .required },
14106 .{ .kind = .id_ref, .quantifier = .required },
1410713833 },
1410813834 },
1410913835 .{
14110 .name = "FMax3AMD",
13836 .name = "FAbs",
1411113837 .opcode = 4,
1411213838 .operands = &.{
1411313839 .{ .kind = .id_ref, .quantifier = .required },
14114 .{ .kind = .id_ref, .quantifier = .required },
14115 .{ .kind = .id_ref, .quantifier = .required },
1411613840 },
1411713841 },
1411813842 .{
14119 .name = "UMax3AMD",
13843 .name = "SAbs",
1412013844 .opcode = 5,
1412113845 .operands = &.{
1412213846 .{ .kind = .id_ref, .quantifier = .required },
14123 .{ .kind = .id_ref, .quantifier = .required },
14124 .{ .kind = .id_ref, .quantifier = .required },
1412513847 },
1412613848 },
1412713849 .{
14128 .name = "SMax3AMD",
13850 .name = "FSign",
1412913851 .opcode = 6,
1413013852 .operands = &.{
1413113853 .{ .kind = .id_ref, .quantifier = .required },
14132 .{ .kind = .id_ref, .quantifier = .required },
14133 .{ .kind = .id_ref, .quantifier = .required },
1413413854 },
1413513855 },
1413613856 .{
14137 .name = "FMid3AMD",
13857 .name = "SSign",
1413813858 .opcode = 7,
1413913859 .operands = &.{
1414013860 .{ .kind = .id_ref, .quantifier = .required },
14141 .{ .kind = .id_ref, .quantifier = .required },
14142 .{ .kind = .id_ref, .quantifier = .required },
1414313861 },
1414413862 },
1414513863 .{
14146 .name = "UMid3AMD",
13864 .name = "Floor",
1414713865 .opcode = 8,
1414813866 .operands = &.{
1414913867 .{ .kind = .id_ref, .quantifier = .required },
14150 .{ .kind = .id_ref, .quantifier = .required },
14151 .{ .kind = .id_ref, .quantifier = .required },
1415213868 },
1415313869 },
1415413870 .{
14155 .name = "SMid3AMD",
13871 .name = "Ceil",
1415613872 .opcode = 9,
1415713873 .operands = &.{
1415813874 .{ .kind = .id_ref, .quantifier = .required },
14159 .{ .kind = .id_ref, .quantifier = .required },
14160 .{ .kind = .id_ref, .quantifier = .required },
1416113875 },
1416213876 },
14163 },
14164 .spv_ext_inst_type_tosa_001000_1 => &.{
1416513877 .{
14166 .name = "ARGMAX",
14167 .opcode = 0,
13878 .name = "Fract",
13879 .opcode = 10,
1416813880 .operands = &.{
1416913881 .{ .kind = .id_ref, .quantifier = .required },
14170 .{ .kind = .id_ref, .quantifier = .required },
14171 .{ .kind = .id_ref, .quantifier = .required },
1417213882 },
1417313883 },
1417413884 .{
14175 .name = "AVG_POOL2D",
14176 .opcode = 1,
13885 .name = "Radians",
13886 .opcode = 11,
1417713887 .operands = &.{
1417813888 .{ .kind = .id_ref, .quantifier = .required },
14179 .{ .kind = .id_ref, .quantifier = .required },
14180 .{ .kind = .id_ref, .quantifier = .required },
14181 .{ .kind = .id_ref, .quantifier = .required },
14182 .{ .kind = .id_ref, .quantifier = .required },
14183 .{ .kind = .id_ref, .quantifier = .required },
14184 .{ .kind = .id_ref, .quantifier = .required },
1418513889 },
1418613890 },
1418713891 .{
14188 .name = "CONV2D",
14189 .opcode = 2,
13892 .name = "Degrees",
13893 .opcode = 12,
1419013894 .operands = &.{
1419113895 .{ .kind = .id_ref, .quantifier = .required },
14192 .{ .kind = .id_ref, .quantifier = .required },
14193 .{ .kind = .id_ref, .quantifier = .required },
14194 .{ .kind = .id_ref, .quantifier = .required },
14195 .{ .kind = .id_ref, .quantifier = .required },
14196 .{ .kind = .id_ref, .quantifier = .required },
14197 .{ .kind = .id_ref, .quantifier = .required },
14198 .{ .kind = .id_ref, .quantifier = .required },
14199 .{ .kind = .id_ref, .quantifier = .required },
14200 .{ .kind = .id_ref, .quantifier = .required },
1420113896 },
1420213897 },
1420313898 .{
14204 .name = "CONV3D",
14205 .opcode = 3,
13899 .name = "Sin",
13900 .opcode = 13,
1420613901 .operands = &.{
1420713902 .{ .kind = .id_ref, .quantifier = .required },
14208 .{ .kind = .id_ref, .quantifier = .required },
14209 .{ .kind = .id_ref, .quantifier = .required },
14210 .{ .kind = .id_ref, .quantifier = .required },
14211 .{ .kind = .id_ref, .quantifier = .required },
14212 .{ .kind = .id_ref, .quantifier = .required },
14213 .{ .kind = .id_ref, .quantifier = .required },
14214 .{ .kind = .id_ref, .quantifier = .required },
14215 .{ .kind = .id_ref, .quantifier = .required },
14216 .{ .kind = .id_ref, .quantifier = .required },
1421713903 },
1421813904 },
1421913905 .{
14220 .name = "DEPTHWISE_CONV2D",
14221 .opcode = 4,
13906 .name = "Cos",
13907 .opcode = 14,
1422213908 .operands = &.{
1422313909 .{ .kind = .id_ref, .quantifier = .required },
14224 .{ .kind = .id_ref, .quantifier = .required },
14225 .{ .kind = .id_ref, .quantifier = .required },
14226 .{ .kind = .id_ref, .quantifier = .required },
14227 .{ .kind = .id_ref, .quantifier = .required },
14228 .{ .kind = .id_ref, .quantifier = .required },
14229 .{ .kind = .id_ref, .quantifier = .required },
14230 .{ .kind = .id_ref, .quantifier = .required },
14231 .{ .kind = .id_ref, .quantifier = .required },
14232 .{ .kind = .id_ref, .quantifier = .required },
1423313910 },
1423413911 },
1423513912 .{
14236 .name = "FFT2D",
14237 .opcode = 5,
13913 .name = "Tan",
13914 .opcode = 15,
1423813915 .operands = &.{
1423913916 .{ .kind = .id_ref, .quantifier = .required },
14240 .{ .kind = .id_ref, .quantifier = .required },
14241 .{ .kind = .id_ref, .quantifier = .required },
14242 .{ .kind = .id_ref, .quantifier = .required },
1424313917 },
1424413918 },
1424513919 .{
14246 .name = "MATMUL",
14247 .opcode = 6,
13920 .name = "Asin",
13921 .opcode = 16,
1424813922 .operands = &.{
1424913923 .{ .kind = .id_ref, .quantifier = .required },
14250 .{ .kind = .id_ref, .quantifier = .required },
14251 .{ .kind = .id_ref, .quantifier = .required },
14252 .{ .kind = .id_ref, .quantifier = .required },
1425313924 },
1425413925 },
1425513926 .{
14256 .name = "MAX_POOL2D",
14257 .opcode = 7,
14258 .operands = &.{
14259 .{ .kind = .id_ref, .quantifier = .required },
14260 .{ .kind = .id_ref, .quantifier = .required },
14261 .{ .kind = .id_ref, .quantifier = .required },
14262 .{ .kind = .id_ref, .quantifier = .required },
14263 .{ .kind = .id_ref, .quantifier = .required },
14264 },
14265 },
14266 .{
14267 .name = "RFFT2D",
14268 .opcode = 8,
14269 .operands = &.{
14270 .{ .kind = .id_ref, .quantifier = .required },
14271 .{ .kind = .id_ref, .quantifier = .required },
14272 },
14273 },
14274 .{
14275 .name = "TRANSPOSE_CONV2D",
14276 .opcode = 9,
14277 .operands = &.{
14278 .{ .kind = .id_ref, .quantifier = .required },
14279 .{ .kind = .id_ref, .quantifier = .required },
14280 .{ .kind = .id_ref, .quantifier = .required },
14281 .{ .kind = .id_ref, .quantifier = .required },
14282 .{ .kind = .id_ref, .quantifier = .required },
14283 .{ .kind = .id_ref, .quantifier = .required },
14284 .{ .kind = .id_ref, .quantifier = .required },
14285 .{ .kind = .id_ref, .quantifier = .required },
14286 .{ .kind = .id_ref, .quantifier = .required },
14287 },
14288 },
14289 .{
14290 .name = "CLAMP",
14291 .opcode = 10,
14292 .operands = &.{
14293 .{ .kind = .id_ref, .quantifier = .required },
14294 .{ .kind = .id_ref, .quantifier = .required },
14295 .{ .kind = .id_ref, .quantifier = .required },
14296 .{ .kind = .id_ref, .quantifier = .required },
14297 },
14298 },
14299 .{
14300 .name = "ERF",
14301 .opcode = 11,
14302 .operands = &.{
14303 .{ .kind = .id_ref, .quantifier = .required },
14304 },
14305 },
14306 .{
14307 .name = "SIGMOID",
14308 .opcode = 12,
14309 .operands = &.{
14310 .{ .kind = .id_ref, .quantifier = .required },
14311 },
14312 },
14313 .{
14314 .name = "TANH",
14315 .opcode = 13,
14316 .operands = &.{
14317 .{ .kind = .id_ref, .quantifier = .required },
14318 },
14319 },
14320 .{
14321 .name = "ADD",
14322 .opcode = 14,
14323 .operands = &.{
14324 .{ .kind = .id_ref, .quantifier = .required },
14325 .{ .kind = .id_ref, .quantifier = .required },
14326 },
14327 },
14328 .{
14329 .name = "ARITHMETIC_RIGHT_SHIFT",
14330 .opcode = 15,
14331 .operands = &.{
14332 .{ .kind = .id_ref, .quantifier = .required },
14333 .{ .kind = .id_ref, .quantifier = .required },
14334 .{ .kind = .id_ref, .quantifier = .required },
14335 },
14336 },
14337 .{
14338 .name = "BITWISE_AND",
14339 .opcode = 16,
14340 .operands = &.{
14341 .{ .kind = .id_ref, .quantifier = .required },
14342 .{ .kind = .id_ref, .quantifier = .required },
14343 },
14344 },
14345 .{
14346 .name = "BITWISE_OR",
13927 .name = "Acos",
1434713928 .opcode = 17,
1434813929 .operands = &.{
1434913930 .{ .kind = .id_ref, .quantifier = .required },
14350 .{ .kind = .id_ref, .quantifier = .required },
1435113931 },
1435213932 },
1435313933 .{
14354 .name = "BITWISE_XOR",
13934 .name = "Atan",
1435513935 .opcode = 18,
1435613936 .operands = &.{
1435713937 .{ .kind = .id_ref, .quantifier = .required },
14358 .{ .kind = .id_ref, .quantifier = .required },
1435913938 },
1436013939 },
1436113940 .{
14362 .name = "INTDIV",
13941 .name = "Sinh",
1436313942 .opcode = 19,
1436413943 .operands = &.{
1436513944 .{ .kind = .id_ref, .quantifier = .required },
14366 .{ .kind = .id_ref, .quantifier = .required },
1436713945 },
1436813946 },
1436913947 .{
14370 .name = "LOGICAL_AND",
13948 .name = "Cosh",
1437113949 .opcode = 20,
1437213950 .operands = &.{
1437313951 .{ .kind = .id_ref, .quantifier = .required },
14374 .{ .kind = .id_ref, .quantifier = .required },
1437513952 },
1437613953 },
1437713954 .{
14378 .name = "LOGICAL_LEFT_SHIFT",
13955 .name = "Tanh",
1437913956 .opcode = 21,
1438013957 .operands = &.{
1438113958 .{ .kind = .id_ref, .quantifier = .required },
14382 .{ .kind = .id_ref, .quantifier = .required },
1438313959 },
1438413960 },
1438513961 .{
14386 .name = "LOGICAL_RIGHT_SHIFT",
13962 .name = "Asinh",
1438713963 .opcode = 22,
1438813964 .operands = &.{
1438913965 .{ .kind = .id_ref, .quantifier = .required },
14390 .{ .kind = .id_ref, .quantifier = .required },
1439113966 },
1439213967 },
1439313968 .{
14394 .name = "LOGICAL_OR",
13969 .name = "Acosh",
1439513970 .opcode = 23,
1439613971 .operands = &.{
1439713972 .{ .kind = .id_ref, .quantifier = .required },
14398 .{ .kind = .id_ref, .quantifier = .required },
1439913973 },
1440013974 },
1440113975 .{
14402 .name = "LOGICAL_XOR",
13976 .name = "Atanh",
1440313977 .opcode = 24,
1440413978 .operands = &.{
1440513979 .{ .kind = .id_ref, .quantifier = .required },
14406 .{ .kind = .id_ref, .quantifier = .required },
1440713980 },
1440813981 },
1440913982 .{
14410 .name = "MAXIMUM",
13983 .name = "Atan2",
1441113984 .opcode = 25,
1441213985 .operands = &.{
1441313986 .{ .kind = .id_ref, .quantifier = .required },
1441413987 .{ .kind = .id_ref, .quantifier = .required },
14415 .{ .kind = .id_ref, .quantifier = .required },
1441613988 },
1441713989 },
1441813990 .{
14419 .name = "MINIMUM",
13991 .name = "Pow",
1442013992 .opcode = 26,
1442113993 .operands = &.{
1442213994 .{ .kind = .id_ref, .quantifier = .required },
1442313995 .{ .kind = .id_ref, .quantifier = .required },
14424 .{ .kind = .id_ref, .quantifier = .required },
1442513996 },
1442613997 },
1442713998 .{
14428 .name = "MUL",
13999 .name = "Exp",
1442914000 .opcode = 27,
1443014001 .operands = &.{
1443114002 .{ .kind = .id_ref, .quantifier = .required },
14432 .{ .kind = .id_ref, .quantifier = .required },
14433 .{ .kind = .id_ref, .quantifier = .required },
1443414003 },
1443514004 },
1443614005 .{
14437 .name = "POW",
14006 .name = "Log",
1443814007 .opcode = 28,
1443914008 .operands = &.{
1444014009 .{ .kind = .id_ref, .quantifier = .required },
14441 .{ .kind = .id_ref, .quantifier = .required },
1444214010 },
1444314011 },
1444414012 .{
14445 .name = "SUB",
14013 .name = "Exp2",
1444614014 .opcode = 29,
1444714015 .operands = &.{
1444814016 .{ .kind = .id_ref, .quantifier = .required },
14449 .{ .kind = .id_ref, .quantifier = .required },
1445014017 },
1445114018 },
1445214019 .{
14453 .name = "TABLE",
14020 .name = "Log2",
1445414021 .opcode = 30,
1445514022 .operands = &.{
1445614023 .{ .kind = .id_ref, .quantifier = .required },
14457 .{ .kind = .id_ref, .quantifier = .required },
1445814024 },
1445914025 },
1446014026 .{
14461 .name = "ABS",
14027 .name = "Sqrt",
1446214028 .opcode = 31,
1446314029 .operands = &.{
1446414030 .{ .kind = .id_ref, .quantifier = .required },
1446514031 },
1446614032 },
1446714033 .{
14468 .name = "BITWISE_NOT",
14034 .name = "InverseSqrt",
1446914035 .opcode = 32,
1447014036 .operands = &.{
1447114037 .{ .kind = .id_ref, .quantifier = .required },
1447214038 },
1447314039 },
1447414040 .{
14475 .name = "CEIL",
14041 .name = "Determinant",
1447614042 .opcode = 33,
1447714043 .operands = &.{
1447814044 .{ .kind = .id_ref, .quantifier = .required },
1447914045 },
1448014046 },
1448114047 .{
14482 .name = "CLZ",
14048 .name = "MatrixInverse",
1448314049 .opcode = 34,
1448414050 .operands = &.{
1448514051 .{ .kind = .id_ref, .quantifier = .required },
1448614052 },
1448714053 },
1448814054 .{
14489 .name = "COS",
14055 .name = "Modf",
1449014056 .opcode = 35,
1449114057 .operands = &.{
1449214058 .{ .kind = .id_ref, .quantifier = .required },
14059 .{ .kind = .id_ref, .quantifier = .required },
1449314060 },
1449414061 },
1449514062 .{
14496 .name = "EXP",
14063 .name = "ModfStruct",
1449714064 .opcode = 36,
1449814065 .operands = &.{
1449914066 .{ .kind = .id_ref, .quantifier = .required },
1450014067 },
1450114068 },
1450214069 .{
14503 .name = "FLOOR",
14070 .name = "FMin",
1450414071 .opcode = 37,
1450514072 .operands = &.{
1450614073 .{ .kind = .id_ref, .quantifier = .required },
14074 .{ .kind = .id_ref, .quantifier = .required },
1450714075 },
1450814076 },
1450914077 .{
14510 .name = "LOG",
14078 .name = "UMin",
1451114079 .opcode = 38,
1451214080 .operands = &.{
1451314081 .{ .kind = .id_ref, .quantifier = .required },
14082 .{ .kind = .id_ref, .quantifier = .required },
1451414083 },
1451514084 },
1451614085 .{
14517 .name = "LOGICAL_NOT",
14086 .name = "SMin",
1451814087 .opcode = 39,
1451914088 .operands = &.{
1452014089 .{ .kind = .id_ref, .quantifier = .required },
14090 .{ .kind = .id_ref, .quantifier = .required },
1452114091 },
1452214092 },
1452314093 .{
14524 .name = "NEGATE",
14094 .name = "FMax",
1452514095 .opcode = 40,
1452614096 .operands = &.{
1452714097 .{ .kind = .id_ref, .quantifier = .required },
1452814098 .{ .kind = .id_ref, .quantifier = .required },
14529 .{ .kind = .id_ref, .quantifier = .required },
1453014099 },
1453114100 },
1453214101 .{
14533 .name = "RECIPROCAL",
14102 .name = "UMax",
1453414103 .opcode = 41,
1453514104 .operands = &.{
1453614105 .{ .kind = .id_ref, .quantifier = .required },
14106 .{ .kind = .id_ref, .quantifier = .required },
1453714107 },
1453814108 },
1453914109 .{
14540 .name = "RSQRT",
14110 .name = "SMax",
1454114111 .opcode = 42,
1454214112 .operands = &.{
1454314113 .{ .kind = .id_ref, .quantifier = .required },
14114 .{ .kind = .id_ref, .quantifier = .required },
1454414115 },
1454514116 },
1454614117 .{
14547 .name = "SIN",
14118 .name = "FClamp",
1454814119 .opcode = 43,
1454914120 .operands = &.{
1455014121 .{ .kind = .id_ref, .quantifier = .required },
14122 .{ .kind = .id_ref, .quantifier = .required },
14123 .{ .kind = .id_ref, .quantifier = .required },
1455114124 },
1455214125 },
1455314126 .{
14554 .name = "SELECT",
14127 .name = "UClamp",
1455514128 .opcode = 44,
1455614129 .operands = &.{
1455714130 .{ .kind = .id_ref, .quantifier = .required },
......@@ -14560,31 +14133,34 @@ pub const InstructionSet = enum {
1456014133 },
1456114134 },
1456214135 .{
14563 .name = "EQUAL",
14136 .name = "SClamp",
1456414137 .opcode = 45,
1456514138 .operands = &.{
1456614139 .{ .kind = .id_ref, .quantifier = .required },
1456714140 .{ .kind = .id_ref, .quantifier = .required },
14141 .{ .kind = .id_ref, .quantifier = .required },
1456814142 },
1456914143 },
1457014144 .{
14571 .name = "GREATER",
14145 .name = "FMix",
1457214146 .opcode = 46,
1457314147 .operands = &.{
1457414148 .{ .kind = .id_ref, .quantifier = .required },
1457514149 .{ .kind = .id_ref, .quantifier = .required },
14150 .{ .kind = .id_ref, .quantifier = .required },
1457614151 },
1457714152 },
1457814153 .{
14579 .name = "GREATER_EQUAL",
14154 .name = "IMix",
1458014155 .opcode = 47,
1458114156 .operands = &.{
1458214157 .{ .kind = .id_ref, .quantifier = .required },
1458314158 .{ .kind = .id_ref, .quantifier = .required },
14159 .{ .kind = .id_ref, .quantifier = .required },
1458414160 },
1458514161 },
1458614162 .{
14587 .name = "REDUCE_ALL",
14163 .name = "Step",
1458814164 .opcode = 48,
1458914165 .operands = &.{
1459014166 .{ .kind = .id_ref, .quantifier = .required },
......@@ -14592,15 +14168,16 @@ pub const InstructionSet = enum {
1459214168 },
1459314169 },
1459414170 .{
14595 .name = "REDUCE_ANY",
14171 .name = "SmoothStep",
1459614172 .opcode = 49,
1459714173 .operands = &.{
1459814174 .{ .kind = .id_ref, .quantifier = .required },
1459914175 .{ .kind = .id_ref, .quantifier = .required },
14176 .{ .kind = .id_ref, .quantifier = .required },
1460014177 },
1460114178 },
1460214179 .{
14603 .name = "REDUCE_MAX",
14180 .name = "Fma",
1460414181 .opcode = 50,
1460514182 .operands = &.{
1460614183 .{ .kind = .id_ref, .quantifier = .required },
......@@ -14609,24 +14186,22 @@ pub const InstructionSet = enum {
1460914186 },
1461014187 },
1461114188 .{
14612 .name = "REDUCE_MIN",
14189 .name = "Frexp",
1461314190 .opcode = 51,
1461414191 .operands = &.{
1461514192 .{ .kind = .id_ref, .quantifier = .required },
1461614193 .{ .kind = .id_ref, .quantifier = .required },
14617 .{ .kind = .id_ref, .quantifier = .required },
1461814194 },
1461914195 },
1462014196 .{
14621 .name = "REDUCE_PRODUCT",
14197 .name = "FrexpStruct",
1462214198 .opcode = 52,
1462314199 .operands = &.{
1462414200 .{ .kind = .id_ref, .quantifier = .required },
14625 .{ .kind = .id_ref, .quantifier = .required },
1462614201 },
1462714202 },
1462814203 .{
14629 .name = "REDUCE_SUM",
14204 .name = "Ldexp",
1463014205 .opcode = 53,
1463114206 .operands = &.{
1463214207 .{ .kind = .id_ref, .quantifier = .required },
......@@ -14634,3368 +14209,1112 @@ pub const InstructionSet = enum {
1463414209 },
1463514210 },
1463614211 .{
14637 .name = "CONCAT",
14212 .name = "PackSnorm4x8",
1463814213 .opcode = 54,
1463914214 .operands = &.{
1464014215 .{ .kind = .id_ref, .quantifier = .required },
14641 .{ .kind = .id_ref, .quantifier = .variadic },
1464214216 },
1464314217 },
1464414218 .{
14645 .name = "PAD",
14219 .name = "PackUnorm4x8",
1464614220 .opcode = 55,
1464714221 .operands = &.{
1464814222 .{ .kind = .id_ref, .quantifier = .required },
14649 .{ .kind = .id_ref, .quantifier = .required },
14650 .{ .kind = .id_ref, .quantifier = .required },
1465114223 },
1465214224 },
1465314225 .{
14654 .name = "RESHAPE",
14226 .name = "PackSnorm2x16",
1465514227 .opcode = 56,
1465614228 .operands = &.{
1465714229 .{ .kind = .id_ref, .quantifier = .required },
14658 .{ .kind = .id_ref, .quantifier = .required },
1465914230 },
1466014231 },
1466114232 .{
14662 .name = "REVERSE",
14233 .name = "PackUnorm2x16",
1466314234 .opcode = 57,
1466414235 .operands = &.{
1466514236 .{ .kind = .id_ref, .quantifier = .required },
14666 .{ .kind = .id_ref, .quantifier = .required },
1466714237 },
1466814238 },
1466914239 .{
14670 .name = "SLICE",
14240 .name = "PackHalf2x16",
1467114241 .opcode = 58,
1467214242 .operands = &.{
1467314243 .{ .kind = .id_ref, .quantifier = .required },
14674 .{ .kind = .id_ref, .quantifier = .required },
14675 .{ .kind = .id_ref, .quantifier = .required },
1467614244 },
1467714245 },
1467814246 .{
14679 .name = "TILE",
14247 .name = "PackDouble2x32",
1468014248 .opcode = 59,
1468114249 .operands = &.{
1468214250 .{ .kind = .id_ref, .quantifier = .required },
14683 .{ .kind = .id_ref, .quantifier = .required },
1468414251 },
1468514252 },
1468614253 .{
14687 .name = "TRANSPOSE",
14254 .name = "UnpackSnorm2x16",
1468814255 .opcode = 60,
1468914256 .operands = &.{
1469014257 .{ .kind = .id_ref, .quantifier = .required },
14691 .{ .kind = .id_ref, .quantifier = .required },
1469214258 },
1469314259 },
1469414260 .{
14695 .name = "GATHER",
14261 .name = "UnpackUnorm2x16",
1469614262 .opcode = 61,
1469714263 .operands = &.{
1469814264 .{ .kind = .id_ref, .quantifier = .required },
14699 .{ .kind = .id_ref, .quantifier = .required },
1470014265 },
1470114266 },
1470214267 .{
14703 .name = "SCATTER",
14268 .name = "UnpackHalf2x16",
1470414269 .opcode = 62,
1470514270 .operands = &.{
1470614271 .{ .kind = .id_ref, .quantifier = .required },
14707 .{ .kind = .id_ref, .quantifier = .required },
14708 .{ .kind = .id_ref, .quantifier = .required },
1470914272 },
1471014273 },
1471114274 .{
14712 .name = "RESIZE",
14275 .name = "UnpackSnorm4x8",
1471314276 .opcode = 63,
1471414277 .operands = &.{
1471514278 .{ .kind = .id_ref, .quantifier = .required },
14716 .{ .kind = .id_ref, .quantifier = .required },
14717 .{ .kind = .id_ref, .quantifier = .required },
14718 .{ .kind = .id_ref, .quantifier = .required },
14719 .{ .kind = .id_ref, .quantifier = .required },
1472014279 },
1472114280 },
1472214281 .{
14723 .name = "CAST",
14282 .name = "UnpackUnorm4x8",
1472414283 .opcode = 64,
1472514284 .operands = &.{
1472614285 .{ .kind = .id_ref, .quantifier = .required },
1472714286 },
1472814287 },
1472914288 .{
14730 .name = "RESCALE",
14289 .name = "UnpackDouble2x32",
1473114290 .opcode = 65,
1473214291 .operands = &.{
1473314292 .{ .kind = .id_ref, .quantifier = .required },
14293 },
14294 },
14295 .{
14296 .name = "Length",
14297 .opcode = 66,
14298 .operands = &.{
1473414299 .{ .kind = .id_ref, .quantifier = .required },
14300 },
14301 },
14302 .{
14303 .name = "Distance",
14304 .opcode = 67,
14305 .operands = &.{
1473514306 .{ .kind = .id_ref, .quantifier = .required },
1473614307 .{ .kind = .id_ref, .quantifier = .required },
14308 },
14309 },
14310 .{
14311 .name = "Cross",
14312 .opcode = 68,
14313 .operands = &.{
1473714314 .{ .kind = .id_ref, .quantifier = .required },
1473814315 .{ .kind = .id_ref, .quantifier = .required },
14316 },
14317 },
14318 .{
14319 .name = "Normalize",
14320 .opcode = 69,
14321 .operands = &.{
1473914322 .{ .kind = .id_ref, .quantifier = .required },
14323 },
14324 },
14325 .{
14326 .name = "FaceForward",
14327 .opcode = 70,
14328 .operands = &.{
1474014329 .{ .kind = .id_ref, .quantifier = .required },
1474114330 .{ .kind = .id_ref, .quantifier = .required },
1474214331 .{ .kind = .id_ref, .quantifier = .required },
1474314332 },
1474414333 },
14745 },
14746 .non_semantic_vksp_reflection => &.{
1474714334 .{
14748 .name = "Configuration",
14749 .opcode = 1,
14335 .name = "Reflect",
14336 .opcode = 71,
1475014337 .operands = &.{
1475114338 .{ .kind = .id_ref, .quantifier = .required },
1475214339 .{ .kind = .id_ref, .quantifier = .required },
14340 },
14341 },
14342 .{
14343 .name = "Refract",
14344 .opcode = 72,
14345 .operands = &.{
1475314346 .{ .kind = .id_ref, .quantifier = .required },
1475414347 .{ .kind = .id_ref, .quantifier = .required },
1475514348 .{ .kind = .id_ref, .quantifier = .required },
14349 },
14350 },
14351 .{
14352 .name = "FindILsb",
14353 .opcode = 73,
14354 .operands = &.{
1475614355 .{ .kind = .id_ref, .quantifier = .required },
14757 .{ .kind = .id_ref, .quantifier = .required },
14758 .{ .kind = .id_ref, .quantifier = .required },
14356 },
14357 },
14358 .{
14359 .name = "FindSMsb",
14360 .opcode = 74,
14361 .operands = &.{
1475914362 .{ .kind = .id_ref, .quantifier = .required },
1476014363 },
1476114364 },
1476214365 .{
14763 .name = "StartCounter",
14764 .opcode = 2,
14366 .name = "FindUMsb",
14367 .opcode = 75,
1476514368 .operands = &.{
1476614369 .{ .kind = .id_ref, .quantifier = .required },
1476714370 },
1476814371 },
1476914372 .{
14770 .name = "StopCounter",
14771 .opcode = 3,
14373 .name = "InterpolateAtCentroid",
14374 .opcode = 76,
1477214375 .operands = &.{
1477314376 .{ .kind = .id_ref, .quantifier = .required },
1477414377 },
1477514378 },
1477614379 .{
14777 .name = "PushConstants",
14778 .opcode = 4,
14380 .name = "InterpolateAtSample",
14381 .opcode = 77,
1477914382 .operands = &.{
1478014383 .{ .kind = .id_ref, .quantifier = .required },
1478114384 .{ .kind = .id_ref, .quantifier = .required },
14385 },
14386 },
14387 .{
14388 .name = "InterpolateAtOffset",
14389 .opcode = 78,
14390 .operands = &.{
1478214391 .{ .kind = .id_ref, .quantifier = .required },
1478314392 .{ .kind = .id_ref, .quantifier = .required },
1478414393 },
1478514394 },
1478614395 .{
14787 .name = "SpecializationMapEntry",
14788 .opcode = 5,
14396 .name = "NMin",
14397 .opcode = 79,
1478914398 .operands = &.{
1479014399 .{ .kind = .id_ref, .quantifier = .required },
1479114400 .{ .kind = .id_ref, .quantifier = .required },
14792 .{ .kind = .id_ref, .quantifier = .required },
1479314401 },
1479414402 },
1479514403 .{
14796 .name = "DescriptorSetBuffer",
14797 .opcode = 6,
14404 .name = "NMax",
14405 .opcode = 80,
1479814406 .operands = &.{
1479914407 .{ .kind = .id_ref, .quantifier = .required },
1480014408 .{ .kind = .id_ref, .quantifier = .required },
14801 .{ .kind = .id_ref, .quantifier = .required },
14802 .{ .kind = .id_ref, .quantifier = .required },
14803 .{ .kind = .id_ref, .quantifier = .required },
14804 .{ .kind = .id_ref, .quantifier = .required },
14805 .{ .kind = .id_ref, .quantifier = .required },
14806 .{ .kind = .id_ref, .quantifier = .required },
14807 .{ .kind = .id_ref, .quantifier = .required },
14808 .{ .kind = .id_ref, .quantifier = .required },
14809 .{ .kind = .id_ref, .quantifier = .required },
14810 .{ .kind = .id_ref, .quantifier = .required },
14811 .{ .kind = .id_ref, .quantifier = .required },
14812 .{ .kind = .id_ref, .quantifier = .required },
14813 .{ .kind = .id_ref, .quantifier = .required },
14814 },
14815 },
14816 .{
14817 .name = "DescriptorSetImage",
14818 .opcode = 7,
14819 .operands = &.{
14820 .{ .kind = .id_ref, .quantifier = .required },
14821 .{ .kind = .id_ref, .quantifier = .required },
14822 .{ .kind = .id_ref, .quantifier = .required },
14823 .{ .kind = .id_ref, .quantifier = .required },
14824 .{ .kind = .id_ref, .quantifier = .required },
14825 .{ .kind = .id_ref, .quantifier = .required },
14826 .{ .kind = .id_ref, .quantifier = .required },
14827 .{ .kind = .id_ref, .quantifier = .required },
14828 .{ .kind = .id_ref, .quantifier = .required },
14829 .{ .kind = .id_ref, .quantifier = .required },
14830 .{ .kind = .id_ref, .quantifier = .required },
14831 .{ .kind = .id_ref, .quantifier = .required },
14832 .{ .kind = .id_ref, .quantifier = .required },
14833 .{ .kind = .id_ref, .quantifier = .required },
14834 .{ .kind = .id_ref, .quantifier = .required },
14835 .{ .kind = .id_ref, .quantifier = .required },
14836 .{ .kind = .id_ref, .quantifier = .required },
14837 .{ .kind = .id_ref, .quantifier = .required },
14838 .{ .kind = .id_ref, .quantifier = .required },
14839 .{ .kind = .id_ref, .quantifier = .required },
14840 .{ .kind = .id_ref, .quantifier = .required },
14841 .{ .kind = .id_ref, .quantifier = .required },
14842 .{ .kind = .id_ref, .quantifier = .required },
14843 .{ .kind = .id_ref, .quantifier = .required },
14844 .{ .kind = .id_ref, .quantifier = .required },
14845 .{ .kind = .id_ref, .quantifier = .required },
14846 .{ .kind = .id_ref, .quantifier = .required },
14847 .{ .kind = .id_ref, .quantifier = .required },
14848 .{ .kind = .id_ref, .quantifier = .required },
14849 .{ .kind = .id_ref, .quantifier = .required },
14850 .{ .kind = .id_ref, .quantifier = .required },
14851 .{ .kind = .id_ref, .quantifier = .required },
14852 .{ .kind = .id_ref, .quantifier = .required },
14853 },
14854 },
14855 .{
14856 .name = "DescriptorSetSampler",
14857 .opcode = 8,
14858 .operands = &.{
14859 .{ .kind = .id_ref, .quantifier = .required },
14860 .{ .kind = .id_ref, .quantifier = .required },
14861 .{ .kind = .id_ref, .quantifier = .required },
14862 .{ .kind = .id_ref, .quantifier = .required },
14863 .{ .kind = .id_ref, .quantifier = .required },
14864 .{ .kind = .id_ref, .quantifier = .required },
14865 .{ .kind = .id_ref, .quantifier = .required },
14866 .{ .kind = .id_ref, .quantifier = .required },
14867 .{ .kind = .id_ref, .quantifier = .required },
14868 .{ .kind = .id_ref, .quantifier = .required },
14869 .{ .kind = .id_ref, .quantifier = .required },
14870 .{ .kind = .id_ref, .quantifier = .required },
14871 .{ .kind = .id_ref, .quantifier = .required },
14872 .{ .kind = .id_ref, .quantifier = .required },
14873 .{ .kind = .id_ref, .quantifier = .required },
14874 .{ .kind = .id_ref, .quantifier = .required },
14409 },
14410 },
14411 .{
14412 .name = "NClamp",
14413 .opcode = 81,
14414 .operands = &.{
1487514415 .{ .kind = .id_ref, .quantifier = .required },
1487614416 .{ .kind = .id_ref, .quantifier = .required },
1487714417 .{ .kind = .id_ref, .quantifier = .required },
1487814418 },
1487914419 },
1488014420 },
14881 .spv_amd_shader_explicit_vertex_parameter => &.{
14421 .@"OpenCL.std" => &.{
1488214422 .{
14883 .name = "InterpolateAtVertexAMD",
14884 .opcode = 1,
14423 .name = "acos",
14424 .opcode = 0,
1488514425 .operands = &.{
1488614426 .{ .kind = .id_ref, .quantifier = .required },
14887 .{ .kind = .id_ref, .quantifier = .required },
1488814427 },
1488914428 },
14890 },
14891 .debug_info => &.{
14892 .{
14893 .name = "DebugInfoNone",
14894 .opcode = 0,
14895 .operands = &.{},
14896 },
1489714429 .{
14898 .name = "DebugCompilationUnit",
14430 .name = "acosh",
1489914431 .opcode = 1,
1490014432 .operands = &.{
1490114433 .{ .kind = .id_ref, .quantifier = .required },
14902 .{ .kind = .literal_integer, .quantifier = .required },
14903 .{ .kind = .literal_integer, .quantifier = .required },
1490414434 },
1490514435 },
1490614436 .{
14907 .name = "DebugTypeBasic",
14437 .name = "acospi",
1490814438 .opcode = 2,
1490914439 .operands = &.{
1491014440 .{ .kind = .id_ref, .quantifier = .required },
14911 .{ .kind = .id_ref, .quantifier = .required },
14912 .{ .kind = .debug_info_debug_base_type_attribute_encoding, .quantifier = .required },
1491314441 },
1491414442 },
1491514443 .{
14916 .name = "DebugTypePointer",
14444 .name = "asin",
1491714445 .opcode = 3,
1491814446 .operands = &.{
1491914447 .{ .kind = .id_ref, .quantifier = .required },
14920 .{ .kind = .storage_class, .quantifier = .required },
14921 .{ .kind = .debug_info_debug_info_flags, .quantifier = .required },
1492214448 },
1492314449 },
1492414450 .{
14925 .name = "DebugTypeQualifier",
14451 .name = "asinh",
1492614452 .opcode = 4,
1492714453 .operands = &.{
1492814454 .{ .kind = .id_ref, .quantifier = .required },
14929 .{ .kind = .debug_info_debug_type_qualifier, .quantifier = .required },
1493014455 },
1493114456 },
1493214457 .{
14933 .name = "DebugTypeArray",
14458 .name = "asinpi",
1493414459 .opcode = 5,
1493514460 .operands = &.{
1493614461 .{ .kind = .id_ref, .quantifier = .required },
14937 .{ .kind = .id_ref, .quantifier = .variadic },
1493814462 },
1493914463 },
1494014464 .{
14941 .name = "DebugTypeVector",
14465 .name = "atan",
1494214466 .opcode = 6,
1494314467 .operands = &.{
1494414468 .{ .kind = .id_ref, .quantifier = .required },
14945 .{ .kind = .literal_integer, .quantifier = .required },
1494614469 },
1494714470 },
1494814471 .{
14949 .name = "DebugTypedef",
14472 .name = "atan2",
1495014473 .opcode = 7,
1495114474 .operands = &.{
1495214475 .{ .kind = .id_ref, .quantifier = .required },
1495314476 .{ .kind = .id_ref, .quantifier = .required },
14954 .{ .kind = .id_ref, .quantifier = .required },
14955 .{ .kind = .literal_integer, .quantifier = .required },
14956 .{ .kind = .literal_integer, .quantifier = .required },
14957 .{ .kind = .id_ref, .quantifier = .required },
1495814477 },
1495914478 },
1496014479 .{
14961 .name = "DebugTypeFunction",
14480 .name = "atanh",
1496214481 .opcode = 8,
1496314482 .operands = &.{
1496414483 .{ .kind = .id_ref, .quantifier = .required },
14965 .{ .kind = .id_ref, .quantifier = .variadic },
1496614484 },
1496714485 },
1496814486 .{
14969 .name = "DebugTypeEnum",
14487 .name = "atanpi",
1497014488 .opcode = 9,
1497114489 .operands = &.{
1497214490 .{ .kind = .id_ref, .quantifier = .required },
14973 .{ .kind = .id_ref, .quantifier = .required },
14974 .{ .kind = .id_ref, .quantifier = .required },
14975 .{ .kind = .literal_integer, .quantifier = .required },
14976 .{ .kind = .literal_integer, .quantifier = .required },
14977 .{ .kind = .id_ref, .quantifier = .required },
14978 .{ .kind = .id_ref, .quantifier = .required },
14979 .{ .kind = .debug_info_debug_info_flags, .quantifier = .required },
14980 .{ .kind = .pair_id_ref_id_ref, .quantifier = .variadic },
1498114491 },
1498214492 },
1498314493 .{
14984 .name = "DebugTypeComposite",
14494 .name = "atan2pi",
1498514495 .opcode = 10,
1498614496 .operands = &.{
1498714497 .{ .kind = .id_ref, .quantifier = .required },
14988 .{ .kind = .debug_info_debug_composite_type, .quantifier = .required },
14989 .{ .kind = .id_ref, .quantifier = .required },
14990 .{ .kind = .literal_integer, .quantifier = .required },
14991 .{ .kind = .literal_integer, .quantifier = .required },
14992 .{ .kind = .id_ref, .quantifier = .required },
14993 .{ .kind = .id_ref, .quantifier = .required },
14994 .{ .kind = .debug_info_debug_info_flags, .quantifier = .required },
14995 .{ .kind = .id_ref, .quantifier = .variadic },
14996 },
14997 },
14998 .{
14999 .name = "DebugTypeMember",
15000 .opcode = 11,
15001 .operands = &.{
15002 .{ .kind = .id_ref, .quantifier = .required },
15003 .{ .kind = .id_ref, .quantifier = .required },
15004 .{ .kind = .id_ref, .quantifier = .required },
15005 .{ .kind = .literal_integer, .quantifier = .required },
15006 .{ .kind = .literal_integer, .quantifier = .required },
15007 .{ .kind = .id_ref, .quantifier = .required },
15008 .{ .kind = .id_ref, .quantifier = .required },
15009 .{ .kind = .id_ref, .quantifier = .required },
15010 .{ .kind = .debug_info_debug_info_flags, .quantifier = .required },
15011 .{ .kind = .id_ref, .quantifier = .optional },
15012 },
15013 },
15014 .{
15015 .name = "DebugTypeInheritance",
15016 .opcode = 12,
15017 .operands = &.{
15018 .{ .kind = .id_ref, .quantifier = .required },
15019 .{ .kind = .id_ref, .quantifier = .required },
15020 .{ .kind = .id_ref, .quantifier = .required },
15021 .{ .kind = .id_ref, .quantifier = .required },
15022 .{ .kind = .debug_info_debug_info_flags, .quantifier = .required },
15023 },
15024 },
15025 .{
15026 .name = "DebugTypePtrToMember",
15027 .opcode = 13,
15028 .operands = &.{
15029 .{ .kind = .id_ref, .quantifier = .required },
15030 .{ .kind = .id_ref, .quantifier = .required },
15031 },
15032 },
15033 .{
15034 .name = "DebugTypeTemplate",
15035 .opcode = 14,
15036 .operands = &.{
15037 .{ .kind = .id_ref, .quantifier = .required },
15038 .{ .kind = .id_ref, .quantifier = .variadic },
15039 },
15040 },
15041 .{
15042 .name = "DebugTypeTemplateParameter",
15043 .opcode = 15,
15044 .operands = &.{
15045 .{ .kind = .id_ref, .quantifier = .required },
15046 .{ .kind = .id_ref, .quantifier = .required },
15047 .{ .kind = .id_ref, .quantifier = .required },
15048 .{ .kind = .id_ref, .quantifier = .required },
15049 .{ .kind = .literal_integer, .quantifier = .required },
15050 .{ .kind = .literal_integer, .quantifier = .required },
15051 },
15052 },
15053 .{
15054 .name = "DebugTypeTemplateTemplateParameter",
15055 .opcode = 16,
15056 .operands = &.{
15057 .{ .kind = .id_ref, .quantifier = .required },
15058 .{ .kind = .id_ref, .quantifier = .required },
15059 .{ .kind = .id_ref, .quantifier = .required },
15060 .{ .kind = .literal_integer, .quantifier = .required },
15061 .{ .kind = .literal_integer, .quantifier = .required },
15062 },
15063 },
15064 .{
15065 .name = "DebugTypeTemplateParameterPack",
15066 .opcode = 17,
15067 .operands = &.{
15068 .{ .kind = .id_ref, .quantifier = .required },
15069 .{ .kind = .id_ref, .quantifier = .required },
15070 .{ .kind = .literal_integer, .quantifier = .required },
15071 .{ .kind = .literal_integer, .quantifier = .required },
15072 .{ .kind = .id_ref, .quantifier = .variadic },
15073 },
15074 },
15075 .{
15076 .name = "DebugGlobalVariable",
15077 .opcode = 18,
15078 .operands = &.{
15079 .{ .kind = .id_ref, .quantifier = .required },
15080 .{ .kind = .id_ref, .quantifier = .required },
15081 .{ .kind = .id_ref, .quantifier = .required },
15082 .{ .kind = .literal_integer, .quantifier = .required },
15083 .{ .kind = .literal_integer, .quantifier = .required },
15084 .{ .kind = .id_ref, .quantifier = .required },
15085 .{ .kind = .id_ref, .quantifier = .required },
15086 .{ .kind = .id_ref, .quantifier = .required },
15087 .{ .kind = .debug_info_debug_info_flags, .quantifier = .required },
15088 .{ .kind = .id_ref, .quantifier = .optional },
15089 },
15090 },
15091 .{
15092 .name = "DebugFunctionDeclaration",
15093 .opcode = 19,
15094 .operands = &.{
15095 .{ .kind = .id_ref, .quantifier = .required },
15096 .{ .kind = .id_ref, .quantifier = .required },
15097 .{ .kind = .id_ref, .quantifier = .required },
15098 .{ .kind = .literal_integer, .quantifier = .required },
15099 .{ .kind = .literal_integer, .quantifier = .required },
15100 .{ .kind = .id_ref, .quantifier = .required },
15101 .{ .kind = .id_ref, .quantifier = .required },
15102 .{ .kind = .debug_info_debug_info_flags, .quantifier = .required },
15103 },
15104 },
15105 .{
15106 .name = "DebugFunction",
15107 .opcode = 20,
15108 .operands = &.{
15109 .{ .kind = .id_ref, .quantifier = .required },
15110 .{ .kind = .id_ref, .quantifier = .required },
15111 .{ .kind = .id_ref, .quantifier = .required },
15112 .{ .kind = .literal_integer, .quantifier = .required },
15113 .{ .kind = .literal_integer, .quantifier = .required },
15114 .{ .kind = .id_ref, .quantifier = .required },
15115 .{ .kind = .id_ref, .quantifier = .required },
15116 .{ .kind = .debug_info_debug_info_flags, .quantifier = .required },
15117 .{ .kind = .literal_integer, .quantifier = .required },
15118 .{ .kind = .id_ref, .quantifier = .required },
15119 .{ .kind = .id_ref, .quantifier = .optional },
15120 },
15121 },
15122 .{
15123 .name = "DebugLexicalBlock",
15124 .opcode = 21,
15125 .operands = &.{
15126 .{ .kind = .id_ref, .quantifier = .required },
15127 .{ .kind = .literal_integer, .quantifier = .required },
15128 .{ .kind = .literal_integer, .quantifier = .required },
15129 .{ .kind = .id_ref, .quantifier = .required },
15130 .{ .kind = .id_ref, .quantifier = .optional },
15131 },
15132 },
15133 .{
15134 .name = "DebugLexicalBlockDiscriminator",
15135 .opcode = 22,
15136 .operands = &.{
15137 .{ .kind = .id_ref, .quantifier = .required },
15138 .{ .kind = .literal_integer, .quantifier = .required },
15139 .{ .kind = .id_ref, .quantifier = .required },
15140 },
15141 },
15142 .{
15143 .name = "DebugScope",
15144 .opcode = 23,
15145 .operands = &.{
15146 .{ .kind = .id_ref, .quantifier = .required },
15147 .{ .kind = .id_ref, .quantifier = .optional },
15148 },
15149 },
15150 .{
15151 .name = "DebugNoScope",
15152 .opcode = 24,
15153 .operands = &.{},
15154 },
15155 .{
15156 .name = "DebugInlinedAt",
15157 .opcode = 25,
15158 .operands = &.{
15159 .{ .kind = .literal_integer, .quantifier = .required },
15160 .{ .kind = .id_ref, .quantifier = .required },
15161 .{ .kind = .id_ref, .quantifier = .optional },
15162 },
15163 },
15164 .{
15165 .name = "DebugLocalVariable",
15166 .opcode = 26,
15167 .operands = &.{
15168 .{ .kind = .id_ref, .quantifier = .required },
15169 .{ .kind = .id_ref, .quantifier = .required },
15170 .{ .kind = .id_ref, .quantifier = .required },
15171 .{ .kind = .literal_integer, .quantifier = .required },
15172 .{ .kind = .literal_integer, .quantifier = .required },
15173 .{ .kind = .id_ref, .quantifier = .required },
15174 .{ .kind = .literal_integer, .quantifier = .optional },
15175 },
15176 },
15177 .{
15178 .name = "DebugInlinedVariable",
15179 .opcode = 27,
15180 .operands = &.{
15181 .{ .kind = .id_ref, .quantifier = .required },
15182 .{ .kind = .id_ref, .quantifier = .required },
15183 },
15184 },
15185 .{
15186 .name = "DebugDeclare",
15187 .opcode = 28,
15188 .operands = &.{
15189 .{ .kind = .id_ref, .quantifier = .required },
15190 .{ .kind = .id_ref, .quantifier = .required },
15191 .{ .kind = .id_ref, .quantifier = .required },
15192 },
15193 },
15194 .{
15195 .name = "DebugValue",
15196 .opcode = 29,
15197 .operands = &.{
15198 .{ .kind = .id_ref, .quantifier = .required },
15199 .{ .kind = .id_ref, .quantifier = .required },
15200 .{ .kind = .id_ref, .quantifier = .variadic },
15201 },
15202 },
15203 .{
15204 .name = "DebugOperation",
15205 .opcode = 30,
15206 .operands = &.{
15207 .{ .kind = .debug_info_debug_operation, .quantifier = .required },
15208 .{ .kind = .literal_integer, .quantifier = .variadic },
15209 },
15210 },
15211 .{
15212 .name = "DebugExpression",
15213 .opcode = 31,
15214 .operands = &.{
15215 .{ .kind = .id_ref, .quantifier = .variadic },
15216 },
15217 },
15218 .{
15219 .name = "DebugMacroDef",
15220 .opcode = 32,
15221 .operands = &.{
15222 .{ .kind = .id_ref, .quantifier = .required },
15223 .{ .kind = .literal_integer, .quantifier = .required },
15224 .{ .kind = .id_ref, .quantifier = .required },
15225 .{ .kind = .id_ref, .quantifier = .optional },
15226 },
15227 },
15228 .{
15229 .name = "DebugMacroUndef",
15230 .opcode = 33,
15231 .operands = &.{
15232 .{ .kind = .id_ref, .quantifier = .required },
15233 .{ .kind = .literal_integer, .quantifier = .required },
15234 .{ .kind = .id_ref, .quantifier = .required },
15235 },
15236 },
15237 },
15238 .non_semantic_debug_break => &.{
15239 .{
15240 .name = "DebugBreak",
15241 .opcode = 1,
15242 .operands = &.{},
15243 },
15244 },
15245 .open_cl_debug_info_100 => &.{
15246 .{
15247 .name = "DebugInfoNone",
15248 .opcode = 0,
15249 .operands = &.{},
15250 },
15251 .{
15252 .name = "DebugCompilationUnit",
15253 .opcode = 1,
15254 .operands = &.{
15255 .{ .kind = .literal_integer, .quantifier = .required },
15256 .{ .kind = .literal_integer, .quantifier = .required },
15257 .{ .kind = .id_ref, .quantifier = .required },
15258 .{ .kind = .source_language, .quantifier = .required },
15259 },
15260 },
15261 .{
15262 .name = "DebugTypeBasic",
15263 .opcode = 2,
15264 .operands = &.{
15265 .{ .kind = .id_ref, .quantifier = .required },
15266 .{ .kind = .id_ref, .quantifier = .required },
15267 .{ .kind = .open_cl_debug_info_100_debug_base_type_attribute_encoding, .quantifier = .required },
15268 },
15269 },
15270 .{
15271 .name = "DebugTypePointer",
15272 .opcode = 3,
15273 .operands = &.{
15274 .{ .kind = .id_ref, .quantifier = .required },
15275 .{ .kind = .storage_class, .quantifier = .required },
15276 .{ .kind = .open_cl_debug_info_100_debug_info_flags, .quantifier = .required },
15277 },
15278 },
15279 .{
15280 .name = "DebugTypeQualifier",
15281 .opcode = 4,
15282 .operands = &.{
15283 .{ .kind = .id_ref, .quantifier = .required },
15284 .{ .kind = .open_cl_debug_info_100_debug_type_qualifier, .quantifier = .required },
15285 },
15286 },
15287 .{
15288 .name = "DebugTypeArray",
15289 .opcode = 5,
15290 .operands = &.{
15291 .{ .kind = .id_ref, .quantifier = .required },
15292 .{ .kind = .id_ref, .quantifier = .variadic },
15293 },
15294 },
15295 .{
15296 .name = "DebugTypeVector",
15297 .opcode = 6,
15298 .operands = &.{
15299 .{ .kind = .id_ref, .quantifier = .required },
15300 .{ .kind = .literal_integer, .quantifier = .required },
15301 },
15302 },
15303 .{
15304 .name = "DebugTypedef",
15305 .opcode = 7,
15306 .operands = &.{
15307 .{ .kind = .id_ref, .quantifier = .required },
15308 .{ .kind = .id_ref, .quantifier = .required },
15309 .{ .kind = .id_ref, .quantifier = .required },
15310 .{ .kind = .literal_integer, .quantifier = .required },
15311 .{ .kind = .literal_integer, .quantifier = .required },
15312 .{ .kind = .id_ref, .quantifier = .required },
15313 },
15314 },
15315 .{
15316 .name = "DebugTypeFunction",
15317 .opcode = 8,
15318 .operands = &.{
15319 .{ .kind = .open_cl_debug_info_100_debug_info_flags, .quantifier = .required },
15320 .{ .kind = .id_ref, .quantifier = .required },
15321 .{ .kind = .id_ref, .quantifier = .variadic },
15322 },
15323 },
15324 .{
15325 .name = "DebugTypeEnum",
15326 .opcode = 9,
15327 .operands = &.{
15328 .{ .kind = .id_ref, .quantifier = .required },
15329 .{ .kind = .id_ref, .quantifier = .required },
15330 .{ .kind = .id_ref, .quantifier = .required },
15331 .{ .kind = .literal_integer, .quantifier = .required },
15332 .{ .kind = .literal_integer, .quantifier = .required },
15333 .{ .kind = .id_ref, .quantifier = .required },
15334 .{ .kind = .id_ref, .quantifier = .required },
15335 .{ .kind = .open_cl_debug_info_100_debug_info_flags, .quantifier = .required },
15336 .{ .kind = .pair_id_ref_id_ref, .quantifier = .variadic },
15337 },
15338 },
15339 .{
15340 .name = "DebugTypeComposite",
15341 .opcode = 10,
15342 .operands = &.{
15343 .{ .kind = .id_ref, .quantifier = .required },
15344 .{ .kind = .open_cl_debug_info_100_debug_composite_type, .quantifier = .required },
15345 .{ .kind = .id_ref, .quantifier = .required },
15346 .{ .kind = .literal_integer, .quantifier = .required },
15347 .{ .kind = .literal_integer, .quantifier = .required },
15348 .{ .kind = .id_ref, .quantifier = .required },
15349 .{ .kind = .id_ref, .quantifier = .required },
15350 .{ .kind = .id_ref, .quantifier = .required },
15351 .{ .kind = .open_cl_debug_info_100_debug_info_flags, .quantifier = .required },
15352 .{ .kind = .id_ref, .quantifier = .variadic },
15353 },
15354 },
15355 .{
15356 .name = "DebugTypeMember",
15357 .opcode = 11,
15358 .operands = &.{
15359 .{ .kind = .id_ref, .quantifier = .required },
15360 .{ .kind = .id_ref, .quantifier = .required },
15361 .{ .kind = .id_ref, .quantifier = .required },
15362 .{ .kind = .literal_integer, .quantifier = .required },
15363 .{ .kind = .literal_integer, .quantifier = .required },
15364 .{ .kind = .id_ref, .quantifier = .required },
15365 .{ .kind = .id_ref, .quantifier = .required },
15366 .{ .kind = .id_ref, .quantifier = .required },
15367 .{ .kind = .open_cl_debug_info_100_debug_info_flags, .quantifier = .required },
15368 .{ .kind = .id_ref, .quantifier = .optional },
15369 },
15370 },
15371 .{
15372 .name = "DebugTypeInheritance",
15373 .opcode = 12,
15374 .operands = &.{
15375 .{ .kind = .id_ref, .quantifier = .required },
15376 .{ .kind = .id_ref, .quantifier = .required },
15377 .{ .kind = .id_ref, .quantifier = .required },
15378 .{ .kind = .id_ref, .quantifier = .required },
15379 .{ .kind = .open_cl_debug_info_100_debug_info_flags, .quantifier = .required },
15380 },
15381 },
15382 .{
15383 .name = "DebugTypePtrToMember",
15384 .opcode = 13,
15385 .operands = &.{
15386 .{ .kind = .id_ref, .quantifier = .required },
15387 .{ .kind = .id_ref, .quantifier = .required },
15388 },
15389 },
15390 .{
15391 .name = "DebugTypeTemplate",
15392 .opcode = 14,
15393 .operands = &.{
15394 .{ .kind = .id_ref, .quantifier = .required },
15395 .{ .kind = .id_ref, .quantifier = .variadic },
15396 },
15397 },
15398 .{
15399 .name = "DebugTypeTemplateParameter",
15400 .opcode = 15,
15401 .operands = &.{
15402 .{ .kind = .id_ref, .quantifier = .required },
15403 .{ .kind = .id_ref, .quantifier = .required },
15404 .{ .kind = .id_ref, .quantifier = .required },
15405 .{ .kind = .id_ref, .quantifier = .required },
15406 .{ .kind = .literal_integer, .quantifier = .required },
15407 .{ .kind = .literal_integer, .quantifier = .required },
15408 },
15409 },
15410 .{
15411 .name = "DebugTypeTemplateTemplateParameter",
15412 .opcode = 16,
15413 .operands = &.{
15414 .{ .kind = .id_ref, .quantifier = .required },
15415 .{ .kind = .id_ref, .quantifier = .required },
15416 .{ .kind = .id_ref, .quantifier = .required },
15417 .{ .kind = .literal_integer, .quantifier = .required },
15418 .{ .kind = .literal_integer, .quantifier = .required },
15419 },
15420 },
15421 .{
15422 .name = "DebugTypeTemplateParameterPack",
15423 .opcode = 17,
15424 .operands = &.{
15425 .{ .kind = .id_ref, .quantifier = .required },
15426 .{ .kind = .id_ref, .quantifier = .required },
15427 .{ .kind = .literal_integer, .quantifier = .required },
15428 .{ .kind = .literal_integer, .quantifier = .required },
15429 .{ .kind = .id_ref, .quantifier = .variadic },
15430 },
15431 },
15432 .{
15433 .name = "DebugGlobalVariable",
15434 .opcode = 18,
15435 .operands = &.{
15436 .{ .kind = .id_ref, .quantifier = .required },
15437 .{ .kind = .id_ref, .quantifier = .required },
15438 .{ .kind = .id_ref, .quantifier = .required },
15439 .{ .kind = .literal_integer, .quantifier = .required },
15440 .{ .kind = .literal_integer, .quantifier = .required },
15441 .{ .kind = .id_ref, .quantifier = .required },
15442 .{ .kind = .id_ref, .quantifier = .required },
15443 .{ .kind = .id_ref, .quantifier = .required },
15444 .{ .kind = .open_cl_debug_info_100_debug_info_flags, .quantifier = .required },
15445 .{ .kind = .id_ref, .quantifier = .optional },
15446 },
15447 },
15448 .{
15449 .name = "DebugFunctionDeclaration",
15450 .opcode = 19,
15451 .operands = &.{
15452 .{ .kind = .id_ref, .quantifier = .required },
15453 .{ .kind = .id_ref, .quantifier = .required },
15454 .{ .kind = .id_ref, .quantifier = .required },
15455 .{ .kind = .literal_integer, .quantifier = .required },
15456 .{ .kind = .literal_integer, .quantifier = .required },
15457 .{ .kind = .id_ref, .quantifier = .required },
15458 .{ .kind = .id_ref, .quantifier = .required },
15459 .{ .kind = .open_cl_debug_info_100_debug_info_flags, .quantifier = .required },
15460 },
15461 },
15462 .{
15463 .name = "DebugFunction",
15464 .opcode = 20,
15465 .operands = &.{
15466 .{ .kind = .id_ref, .quantifier = .required },
15467 .{ .kind = .id_ref, .quantifier = .required },
15468 .{ .kind = .id_ref, .quantifier = .required },
15469 .{ .kind = .literal_integer, .quantifier = .required },
15470 .{ .kind = .literal_integer, .quantifier = .required },
15471 .{ .kind = .id_ref, .quantifier = .required },
15472 .{ .kind = .id_ref, .quantifier = .required },
15473 .{ .kind = .open_cl_debug_info_100_debug_info_flags, .quantifier = .required },
15474 .{ .kind = .literal_integer, .quantifier = .required },
15475 .{ .kind = .id_ref, .quantifier = .required },
15476 .{ .kind = .id_ref, .quantifier = .optional },
15477 },
15478 },
15479 .{
15480 .name = "DebugLexicalBlock",
15481 .opcode = 21,
15482 .operands = &.{
15483 .{ .kind = .id_ref, .quantifier = .required },
15484 .{ .kind = .literal_integer, .quantifier = .required },
15485 .{ .kind = .literal_integer, .quantifier = .required },
15486 .{ .kind = .id_ref, .quantifier = .required },
15487 .{ .kind = .id_ref, .quantifier = .optional },
15488 },
15489 },
15490 .{
15491 .name = "DebugLexicalBlockDiscriminator",
15492 .opcode = 22,
15493 .operands = &.{
15494 .{ .kind = .id_ref, .quantifier = .required },
15495 .{ .kind = .literal_integer, .quantifier = .required },
15496 .{ .kind = .id_ref, .quantifier = .required },
15497 },
15498 },
15499 .{
15500 .name = "DebugScope",
15501 .opcode = 23,
15502 .operands = &.{
15503 .{ .kind = .id_ref, .quantifier = .required },
15504 .{ .kind = .id_ref, .quantifier = .optional },
15505 },
15506 },
15507 .{
15508 .name = "DebugNoScope",
15509 .opcode = 24,
15510 .operands = &.{},
15511 },
15512 .{
15513 .name = "DebugInlinedAt",
15514 .opcode = 25,
15515 .operands = &.{
15516 .{ .kind = .literal_integer, .quantifier = .required },
15517 .{ .kind = .id_ref, .quantifier = .required },
15518 .{ .kind = .id_ref, .quantifier = .optional },
15519 },
15520 },
15521 .{
15522 .name = "DebugLocalVariable",
15523 .opcode = 26,
15524 .operands = &.{
15525 .{ .kind = .id_ref, .quantifier = .required },
15526 .{ .kind = .id_ref, .quantifier = .required },
15527 .{ .kind = .id_ref, .quantifier = .required },
15528 .{ .kind = .literal_integer, .quantifier = .required },
15529 .{ .kind = .literal_integer, .quantifier = .required },
15530 .{ .kind = .id_ref, .quantifier = .required },
15531 .{ .kind = .open_cl_debug_info_100_debug_info_flags, .quantifier = .required },
15532 .{ .kind = .literal_integer, .quantifier = .optional },
15533 },
15534 },
15535 .{
15536 .name = "DebugInlinedVariable",
15537 .opcode = 27,
15538 .operands = &.{
15539 .{ .kind = .id_ref, .quantifier = .required },
15540 .{ .kind = .id_ref, .quantifier = .required },
15541 },
15542 },
15543 .{
15544 .name = "DebugDeclare",
15545 .opcode = 28,
15546 .operands = &.{
15547 .{ .kind = .id_ref, .quantifier = .required },
15548 .{ .kind = .id_ref, .quantifier = .required },
15549 .{ .kind = .id_ref, .quantifier = .required },
15550 },
15551 },
15552 .{
15553 .name = "DebugValue",
15554 .opcode = 29,
15555 .operands = &.{
15556 .{ .kind = .id_ref, .quantifier = .required },
15557 .{ .kind = .id_ref, .quantifier = .required },
15558 .{ .kind = .id_ref, .quantifier = .required },
15559 .{ .kind = .id_ref, .quantifier = .variadic },
15560 },
15561 },
15562 .{
15563 .name = "DebugOperation",
15564 .opcode = 30,
15565 .operands = &.{
15566 .{ .kind = .open_cl_debug_info_100_debug_operation, .quantifier = .required },
15567 .{ .kind = .literal_integer, .quantifier = .variadic },
15568 },
15569 },
15570 .{
15571 .name = "DebugExpression",
15572 .opcode = 31,
15573 .operands = &.{
15574 .{ .kind = .id_ref, .quantifier = .variadic },
15575 },
15576 },
15577 .{
15578 .name = "DebugMacroDef",
15579 .opcode = 32,
15580 .operands = &.{
15581 .{ .kind = .id_ref, .quantifier = .required },
15582 .{ .kind = .literal_integer, .quantifier = .required },
15583 .{ .kind = .id_ref, .quantifier = .required },
15584 .{ .kind = .id_ref, .quantifier = .optional },
15585 },
15586 },
15587 .{
15588 .name = "DebugMacroUndef",
15589 .opcode = 33,
15590 .operands = &.{
15591 .{ .kind = .id_ref, .quantifier = .required },
15592 .{ .kind = .literal_integer, .quantifier = .required },
15593 .{ .kind = .id_ref, .quantifier = .required },
15594 },
15595 },
15596 .{
15597 .name = "DebugImportedEntity",
15598 .opcode = 34,
15599 .operands = &.{
15600 .{ .kind = .id_ref, .quantifier = .required },
15601 .{ .kind = .open_cl_debug_info_100_debug_imported_entity, .quantifier = .required },
15602 .{ .kind = .id_ref, .quantifier = .required },
15603 .{ .kind = .id_ref, .quantifier = .required },
15604 .{ .kind = .literal_integer, .quantifier = .required },
15605 .{ .kind = .literal_integer, .quantifier = .required },
15606 .{ .kind = .id_ref, .quantifier = .required },
15607 },
15608 },
15609 .{
15610 .name = "DebugSource",
15611 .opcode = 35,
15612 .operands = &.{
15613 .{ .kind = .id_ref, .quantifier = .required },
15614 .{ .kind = .id_ref, .quantifier = .optional },
15615 },
15616 },
15617 .{
15618 .name = "DebugModuleINTEL",
15619 .opcode = 36,
15620 .operands = &.{
15621 .{ .kind = .id_ref, .quantifier = .required },
15622 .{ .kind = .id_ref, .quantifier = .required },
15623 .{ .kind = .id_ref, .quantifier = .required },
15624 .{ .kind = .literal_integer, .quantifier = .required },
15625 .{ .kind = .id_ref, .quantifier = .required },
15626 .{ .kind = .id_ref, .quantifier = .required },
15627 .{ .kind = .id_ref, .quantifier = .required },
15628 .{ .kind = .literal_integer, .quantifier = .required },
15629 },
15630 },
15631 },
15632 .non_semantic_clspv_reflection_6 => &.{
15633 .{
15634 .name = "Kernel",
15635 .opcode = 1,
15636 .operands = &.{
15637 .{ .kind = .id_ref, .quantifier = .required },
15638 .{ .kind = .id_ref, .quantifier = .required },
15639 .{ .kind = .id_ref, .quantifier = .optional },
15640 .{ .kind = .id_ref, .quantifier = .optional },
15641 .{ .kind = .id_ref, .quantifier = .optional },
15642 },
15643 },
15644 .{
15645 .name = "ArgumentInfo",
15646 .opcode = 2,
15647 .operands = &.{
15648 .{ .kind = .id_ref, .quantifier = .required },
15649 .{ .kind = .id_ref, .quantifier = .optional },
15650 .{ .kind = .id_ref, .quantifier = .optional },
15651 .{ .kind = .id_ref, .quantifier = .optional },
15652 .{ .kind = .id_ref, .quantifier = .optional },
15653 },
15654 },
15655 .{
15656 .name = "ArgumentStorageBuffer",
15657 .opcode = 3,
15658 .operands = &.{
15659 .{ .kind = .id_ref, .quantifier = .required },
15660 .{ .kind = .id_ref, .quantifier = .required },
15661 .{ .kind = .id_ref, .quantifier = .required },
15662 .{ .kind = .id_ref, .quantifier = .required },
15663 .{ .kind = .id_ref, .quantifier = .optional },
15664 },
15665 },
15666 .{
15667 .name = "ArgumentUniform",
15668 .opcode = 4,
15669 .operands = &.{
15670 .{ .kind = .id_ref, .quantifier = .required },
15671 .{ .kind = .id_ref, .quantifier = .required },
15672 .{ .kind = .id_ref, .quantifier = .required },
15673 .{ .kind = .id_ref, .quantifier = .required },
15674 .{ .kind = .id_ref, .quantifier = .optional },
15675 },
15676 },
15677 .{
15678 .name = "ArgumentPodStorageBuffer",
15679 .opcode = 5,
15680 .operands = &.{
15681 .{ .kind = .id_ref, .quantifier = .required },
15682 .{ .kind = .id_ref, .quantifier = .required },
15683 .{ .kind = .id_ref, .quantifier = .required },
15684 .{ .kind = .id_ref, .quantifier = .required },
15685 .{ .kind = .id_ref, .quantifier = .required },
15686 .{ .kind = .id_ref, .quantifier = .required },
15687 .{ .kind = .id_ref, .quantifier = .optional },
15688 },
15689 },
15690 .{
15691 .name = "ArgumentPodUniform",
15692 .opcode = 6,
15693 .operands = &.{
15694 .{ .kind = .id_ref, .quantifier = .required },
15695 .{ .kind = .id_ref, .quantifier = .required },
15696 .{ .kind = .id_ref, .quantifier = .required },
15697 .{ .kind = .id_ref, .quantifier = .required },
15698 .{ .kind = .id_ref, .quantifier = .required },
15699 .{ .kind = .id_ref, .quantifier = .required },
15700 .{ .kind = .id_ref, .quantifier = .optional },
15701 },
15702 },
15703 .{
15704 .name = "ArgumentPodPushConstant",
15705 .opcode = 7,
15706 .operands = &.{
15707 .{ .kind = .id_ref, .quantifier = .required },
15708 .{ .kind = .id_ref, .quantifier = .required },
15709 .{ .kind = .id_ref, .quantifier = .required },
15710 .{ .kind = .id_ref, .quantifier = .required },
15711 .{ .kind = .id_ref, .quantifier = .optional },
15712 },
15713 },
15714 .{
15715 .name = "ArgumentSampledImage",
15716 .opcode = 8,
15717 .operands = &.{
15718 .{ .kind = .id_ref, .quantifier = .required },
15719 .{ .kind = .id_ref, .quantifier = .required },
15720 .{ .kind = .id_ref, .quantifier = .required },
15721 .{ .kind = .id_ref, .quantifier = .required },
15722 .{ .kind = .id_ref, .quantifier = .optional },
15723 },
15724 },
15725 .{
15726 .name = "ArgumentStorageImage",
15727 .opcode = 9,
15728 .operands = &.{
15729 .{ .kind = .id_ref, .quantifier = .required },
15730 .{ .kind = .id_ref, .quantifier = .required },
15731 .{ .kind = .id_ref, .quantifier = .required },
15732 .{ .kind = .id_ref, .quantifier = .required },
15733 .{ .kind = .id_ref, .quantifier = .optional },
15734 },
15735 },
15736 .{
15737 .name = "ArgumentSampler",
15738 .opcode = 10,
15739 .operands = &.{
15740 .{ .kind = .id_ref, .quantifier = .required },
15741 .{ .kind = .id_ref, .quantifier = .required },
15742 .{ .kind = .id_ref, .quantifier = .required },
15743 .{ .kind = .id_ref, .quantifier = .required },
15744 .{ .kind = .id_ref, .quantifier = .optional },
15745 },
15746 },
15747 .{
15748 .name = "ArgumentWorkgroup",
15749 .opcode = 11,
15750 .operands = &.{
15751 .{ .kind = .id_ref, .quantifier = .required },
15752 .{ .kind = .id_ref, .quantifier = .required },
15753 .{ .kind = .id_ref, .quantifier = .required },
15754 .{ .kind = .id_ref, .quantifier = .required },
15755 .{ .kind = .id_ref, .quantifier = .optional },
15756 },
15757 },
15758 .{
15759 .name = "SpecConstantWorkgroupSize",
15760 .opcode = 12,
15761 .operands = &.{
15762 .{ .kind = .id_ref, .quantifier = .required },
15763 .{ .kind = .id_ref, .quantifier = .required },
15764 .{ .kind = .id_ref, .quantifier = .required },
15765 },
15766 },
15767 .{
15768 .name = "SpecConstantGlobalOffset",
15769 .opcode = 13,
15770 .operands = &.{
15771 .{ .kind = .id_ref, .quantifier = .required },
15772 .{ .kind = .id_ref, .quantifier = .required },
15773 .{ .kind = .id_ref, .quantifier = .required },
15774 },
15775 },
15776 .{
15777 .name = "SpecConstantWorkDim",
15778 .opcode = 14,
15779 .operands = &.{
15780 .{ .kind = .id_ref, .quantifier = .required },
15781 },
15782 },
15783 .{
15784 .name = "PushConstantGlobalOffset",
15785 .opcode = 15,
15786 .operands = &.{
15787 .{ .kind = .id_ref, .quantifier = .required },
15788 .{ .kind = .id_ref, .quantifier = .required },
15789 },
15790 },
15791 .{
15792 .name = "PushConstantEnqueuedLocalSize",
15793 .opcode = 16,
15794 .operands = &.{
15795 .{ .kind = .id_ref, .quantifier = .required },
15796 .{ .kind = .id_ref, .quantifier = .required },
15797 },
15798 },
15799 .{
15800 .name = "PushConstantGlobalSize",
15801 .opcode = 17,
15802 .operands = &.{
15803 .{ .kind = .id_ref, .quantifier = .required },
15804 .{ .kind = .id_ref, .quantifier = .required },
15805 },
15806 },
15807 .{
15808 .name = "PushConstantRegionOffset",
15809 .opcode = 18,
15810 .operands = &.{
15811 .{ .kind = .id_ref, .quantifier = .required },
15812 .{ .kind = .id_ref, .quantifier = .required },
15813 },
15814 },
15815 .{
15816 .name = "PushConstantNumWorkgroups",
15817 .opcode = 19,
15818 .operands = &.{
15819 .{ .kind = .id_ref, .quantifier = .required },
15820 .{ .kind = .id_ref, .quantifier = .required },
15821 },
15822 },
15823 .{
15824 .name = "PushConstantRegionGroupOffset",
15825 .opcode = 20,
15826 .operands = &.{
15827 .{ .kind = .id_ref, .quantifier = .required },
15828 .{ .kind = .id_ref, .quantifier = .required },
15829 },
15830 },
15831 .{
15832 .name = "ConstantDataStorageBuffer",
15833 .opcode = 21,
15834 .operands = &.{
15835 .{ .kind = .id_ref, .quantifier = .required },
15836 .{ .kind = .id_ref, .quantifier = .required },
15837 .{ .kind = .id_ref, .quantifier = .required },
15838 },
15839 },
15840 .{
15841 .name = "ConstantDataUniform",
15842 .opcode = 22,
15843 .operands = &.{
15844 .{ .kind = .id_ref, .quantifier = .required },
15845 .{ .kind = .id_ref, .quantifier = .required },
15846 .{ .kind = .id_ref, .quantifier = .required },
15847 },
15848 },
15849 .{
15850 .name = "LiteralSampler",
15851 .opcode = 23,
15852 .operands = &.{
15853 .{ .kind = .id_ref, .quantifier = .required },
15854 .{ .kind = .id_ref, .quantifier = .required },
15855 .{ .kind = .id_ref, .quantifier = .required },
15856 },
15857 },
15858 .{
15859 .name = "PropertyRequiredWorkgroupSize",
15860 .opcode = 24,
15861 .operands = &.{
15862 .{ .kind = .id_ref, .quantifier = .required },
15863 .{ .kind = .id_ref, .quantifier = .required },
15864 .{ .kind = .id_ref, .quantifier = .required },
15865 .{ .kind = .id_ref, .quantifier = .required },
15866 },
15867 },
15868 .{
15869 .name = "SpecConstantSubgroupMaxSize",
15870 .opcode = 25,
15871 .operands = &.{
15872 .{ .kind = .id_ref, .quantifier = .required },
15873 },
15874 },
15875 .{
15876 .name = "ArgumentPointerPushConstant",
15877 .opcode = 26,
15878 .operands = &.{
15879 .{ .kind = .id_ref, .quantifier = .required },
15880 .{ .kind = .id_ref, .quantifier = .required },
15881 .{ .kind = .id_ref, .quantifier = .required },
15882 .{ .kind = .id_ref, .quantifier = .required },
15883 .{ .kind = .id_ref, .quantifier = .optional },
15884 },
15885 },
15886 .{
15887 .name = "ArgumentPointerUniform",
15888 .opcode = 27,
15889 .operands = &.{
15890 .{ .kind = .id_ref, .quantifier = .required },
15891 .{ .kind = .id_ref, .quantifier = .required },
15892 .{ .kind = .id_ref, .quantifier = .required },
15893 .{ .kind = .id_ref, .quantifier = .required },
15894 .{ .kind = .id_ref, .quantifier = .required },
15895 .{ .kind = .id_ref, .quantifier = .required },
15896 .{ .kind = .id_ref, .quantifier = .optional },
15897 },
15898 },
15899 .{
15900 .name = "ProgramScopeVariablesStorageBuffer",
15901 .opcode = 28,
15902 .operands = &.{
15903 .{ .kind = .id_ref, .quantifier = .required },
15904 .{ .kind = .id_ref, .quantifier = .required },
15905 .{ .kind = .id_ref, .quantifier = .required },
15906 },
15907 },
15908 .{
15909 .name = "ProgramScopeVariablePointerRelocation",
15910 .opcode = 29,
15911 .operands = &.{
15912 .{ .kind = .id_ref, .quantifier = .required },
15913 .{ .kind = .id_ref, .quantifier = .required },
15914 .{ .kind = .id_ref, .quantifier = .required },
15915 },
15916 },
15917 .{
15918 .name = "ImageArgumentInfoChannelOrderPushConstant",
15919 .opcode = 30,
15920 .operands = &.{
15921 .{ .kind = .id_ref, .quantifier = .required },
15922 .{ .kind = .id_ref, .quantifier = .required },
15923 .{ .kind = .id_ref, .quantifier = .required },
15924 .{ .kind = .id_ref, .quantifier = .required },
15925 },
15926 },
15927 .{
15928 .name = "ImageArgumentInfoChannelDataTypePushConstant",
15929 .opcode = 31,
15930 .operands = &.{
15931 .{ .kind = .id_ref, .quantifier = .required },
15932 .{ .kind = .id_ref, .quantifier = .required },
15933 .{ .kind = .id_ref, .quantifier = .required },
15934 .{ .kind = .id_ref, .quantifier = .required },
15935 },
15936 },
15937 .{
15938 .name = "ImageArgumentInfoChannelOrderUniform",
15939 .opcode = 32,
15940 .operands = &.{
15941 .{ .kind = .id_ref, .quantifier = .required },
15942 .{ .kind = .id_ref, .quantifier = .required },
15943 .{ .kind = .id_ref, .quantifier = .required },
15944 .{ .kind = .id_ref, .quantifier = .required },
15945 .{ .kind = .id_ref, .quantifier = .required },
15946 .{ .kind = .id_ref, .quantifier = .required },
15947 },
15948 },
15949 .{
15950 .name = "ImageArgumentInfoChannelDataTypeUniform",
15951 .opcode = 33,
15952 .operands = &.{
15953 .{ .kind = .id_ref, .quantifier = .required },
15954 .{ .kind = .id_ref, .quantifier = .required },
15955 .{ .kind = .id_ref, .quantifier = .required },
15956 .{ .kind = .id_ref, .quantifier = .required },
15957 .{ .kind = .id_ref, .quantifier = .required },
15958 .{ .kind = .id_ref, .quantifier = .required },
15959 },
15960 },
15961 .{
15962 .name = "ArgumentStorageTexelBuffer",
15963 .opcode = 34,
15964 .operands = &.{
15965 .{ .kind = .id_ref, .quantifier = .required },
15966 .{ .kind = .id_ref, .quantifier = .required },
15967 .{ .kind = .id_ref, .quantifier = .required },
15968 .{ .kind = .id_ref, .quantifier = .required },
15969 .{ .kind = .id_ref, .quantifier = .optional },
15970 },
15971 },
15972 .{
15973 .name = "ArgumentUniformTexelBuffer",
15974 .opcode = 35,
15975 .operands = &.{
15976 .{ .kind = .id_ref, .quantifier = .required },
15977 .{ .kind = .id_ref, .quantifier = .required },
15978 .{ .kind = .id_ref, .quantifier = .required },
15979 .{ .kind = .id_ref, .quantifier = .required },
15980 .{ .kind = .id_ref, .quantifier = .optional },
15981 },
15982 },
15983 .{
15984 .name = "ConstantDataPointerPushConstant",
15985 .opcode = 36,
15986 .operands = &.{
15987 .{ .kind = .id_ref, .quantifier = .required },
15988 .{ .kind = .id_ref, .quantifier = .required },
15989 .{ .kind = .id_ref, .quantifier = .required },
15990 },
15991 },
15992 .{
15993 .name = "ProgramScopeVariablePointerPushConstant",
15994 .opcode = 37,
15995 .operands = &.{
15996 .{ .kind = .id_ref, .quantifier = .required },
15997 .{ .kind = .id_ref, .quantifier = .required },
15998 .{ .kind = .id_ref, .quantifier = .required },
15999 },
16000 },
16001 .{
16002 .name = "PrintfInfo",
16003 .opcode = 38,
16004 .operands = &.{
16005 .{ .kind = .id_ref, .quantifier = .required },
16006 .{ .kind = .id_ref, .quantifier = .required },
16007 .{ .kind = .id_ref, .quantifier = .variadic },
16008 },
16009 },
16010 .{
16011 .name = "PrintfBufferStorageBuffer",
16012 .opcode = 39,
16013 .operands = &.{
16014 .{ .kind = .id_ref, .quantifier = .required },
16015 .{ .kind = .id_ref, .quantifier = .required },
16016 .{ .kind = .id_ref, .quantifier = .required },
16017 },
16018 },
16019 .{
16020 .name = "PrintfBufferPointerPushConstant",
16021 .opcode = 40,
16022 .operands = &.{
16023 .{ .kind = .id_ref, .quantifier = .required },
16024 .{ .kind = .id_ref, .quantifier = .required },
16025 .{ .kind = .id_ref, .quantifier = .required },
16026 },
16027 },
16028 .{
16029 .name = "NormalizedSamplerMaskPushConstant",
16030 .opcode = 41,
16031 .operands = &.{
16032 .{ .kind = .id_ref, .quantifier = .required },
16033 .{ .kind = .id_ref, .quantifier = .required },
16034 .{ .kind = .id_ref, .quantifier = .required },
16035 .{ .kind = .id_ref, .quantifier = .required },
16036 },
16037 },
16038 .{
16039 .name = "WorkgroupVariableSize",
16040 .opcode = 42,
16041 .operands = &.{
16042 .{ .kind = .id_ref, .quantifier = .required },
16043 .{ .kind = .id_ref, .quantifier = .required },
16044 },
16045 },
16046 },
16047 .glsl_std_450 => &.{
16048 .{
16049 .name = "Round",
16050 .opcode = 1,
16051 .operands = &.{
16052 .{ .kind = .id_ref, .quantifier = .required },
16053 },
16054 },
16055 .{
16056 .name = "RoundEven",
16057 .opcode = 2,
16058 .operands = &.{
16059 .{ .kind = .id_ref, .quantifier = .required },
16060 },
16061 },
16062 .{
16063 .name = "Trunc",
16064 .opcode = 3,
16065 .operands = &.{
16066 .{ .kind = .id_ref, .quantifier = .required },
16067 },
16068 },
16069 .{
16070 .name = "FAbs",
16071 .opcode = 4,
16072 .operands = &.{
16073 .{ .kind = .id_ref, .quantifier = .required },
16074 },
16075 },
16076 .{
16077 .name = "SAbs",
16078 .opcode = 5,
16079 .operands = &.{
16080 .{ .kind = .id_ref, .quantifier = .required },
16081 },
16082 },
16083 .{
16084 .name = "FSign",
16085 .opcode = 6,
16086 .operands = &.{
16087 .{ .kind = .id_ref, .quantifier = .required },
16088 },
16089 },
16090 .{
16091 .name = "SSign",
16092 .opcode = 7,
16093 .operands = &.{
16094 .{ .kind = .id_ref, .quantifier = .required },
16095 },
16096 },
16097 .{
16098 .name = "Floor",
16099 .opcode = 8,
16100 .operands = &.{
16101 .{ .kind = .id_ref, .quantifier = .required },
16102 },
16103 },
16104 .{
16105 .name = "Ceil",
16106 .opcode = 9,
16107 .operands = &.{
16108 .{ .kind = .id_ref, .quantifier = .required },
16109 },
16110 },
16111 .{
16112 .name = "Fract",
16113 .opcode = 10,
16114 .operands = &.{
16115 .{ .kind = .id_ref, .quantifier = .required },
16116 },
16117 },
16118 .{
16119 .name = "Radians",
16120 .opcode = 11,
16121 .operands = &.{
16122 .{ .kind = .id_ref, .quantifier = .required },
16123 },
16124 },
16125 .{
16126 .name = "Degrees",
16127 .opcode = 12,
16128 .operands = &.{
16129 .{ .kind = .id_ref, .quantifier = .required },
16130 },
16131 },
16132 .{
16133 .name = "Sin",
16134 .opcode = 13,
16135 .operands = &.{
16136 .{ .kind = .id_ref, .quantifier = .required },
16137 },
16138 },
16139 .{
16140 .name = "Cos",
16141 .opcode = 14,
16142 .operands = &.{
16143 .{ .kind = .id_ref, .quantifier = .required },
16144 },
16145 },
16146 .{
16147 .name = "Tan",
16148 .opcode = 15,
16149 .operands = &.{
16150 .{ .kind = .id_ref, .quantifier = .required },
16151 },
16152 },
16153 .{
16154 .name = "Asin",
16155 .opcode = 16,
16156 .operands = &.{
16157 .{ .kind = .id_ref, .quantifier = .required },
16158 },
16159 },
16160 .{
16161 .name = "Acos",
16162 .opcode = 17,
16163 .operands = &.{
16164 .{ .kind = .id_ref, .quantifier = .required },
16165 },
16166 },
16167 .{
16168 .name = "Atan",
16169 .opcode = 18,
16170 .operands = &.{
16171 .{ .kind = .id_ref, .quantifier = .required },
16172 },
16173 },
16174 .{
16175 .name = "Sinh",
16176 .opcode = 19,
16177 .operands = &.{
16178 .{ .kind = .id_ref, .quantifier = .required },
16179 },
16180 },
16181 .{
16182 .name = "Cosh",
16183 .opcode = 20,
16184 .operands = &.{
16185 .{ .kind = .id_ref, .quantifier = .required },
16186 },
16187 },
16188 .{
16189 .name = "Tanh",
16190 .opcode = 21,
16191 .operands = &.{
16192 .{ .kind = .id_ref, .quantifier = .required },
16193 },
16194 },
16195 .{
16196 .name = "Asinh",
16197 .opcode = 22,
16198 .operands = &.{
16199 .{ .kind = .id_ref, .quantifier = .required },
16200 },
16201 },
16202 .{
16203 .name = "Acosh",
16204 .opcode = 23,
16205 .operands = &.{
16206 .{ .kind = .id_ref, .quantifier = .required },
16207 },
16208 },
16209 .{
16210 .name = "Atanh",
16211 .opcode = 24,
16212 .operands = &.{
16213 .{ .kind = .id_ref, .quantifier = .required },
16214 },
16215 },
16216 .{
16217 .name = "Atan2",
16218 .opcode = 25,
16219 .operands = &.{
16220 .{ .kind = .id_ref, .quantifier = .required },
16221 .{ .kind = .id_ref, .quantifier = .required },
16222 },
16223 },
16224 .{
16225 .name = "Pow",
16226 .opcode = 26,
16227 .operands = &.{
16228 .{ .kind = .id_ref, .quantifier = .required },
16229 .{ .kind = .id_ref, .quantifier = .required },
16230 },
16231 },
16232 .{
16233 .name = "Exp",
16234 .opcode = 27,
16235 .operands = &.{
16236 .{ .kind = .id_ref, .quantifier = .required },
16237 },
16238 },
16239 .{
16240 .name = "Log",
16241 .opcode = 28,
16242 .operands = &.{
16243 .{ .kind = .id_ref, .quantifier = .required },
16244 },
16245 },
16246 .{
16247 .name = "Exp2",
16248 .opcode = 29,
16249 .operands = &.{
16250 .{ .kind = .id_ref, .quantifier = .required },
16251 },
16252 },
16253 .{
16254 .name = "Log2",
16255 .opcode = 30,
16256 .operands = &.{
16257 .{ .kind = .id_ref, .quantifier = .required },
16258 },
16259 },
16260 .{
16261 .name = "Sqrt",
16262 .opcode = 31,
16263 .operands = &.{
16264 .{ .kind = .id_ref, .quantifier = .required },
16265 },
16266 },
16267 .{
16268 .name = "InverseSqrt",
16269 .opcode = 32,
16270 .operands = &.{
16271 .{ .kind = .id_ref, .quantifier = .required },
16272 },
16273 },
16274 .{
16275 .name = "Determinant",
16276 .opcode = 33,
16277 .operands = &.{
16278 .{ .kind = .id_ref, .quantifier = .required },
16279 },
16280 },
16281 .{
16282 .name = "MatrixInverse",
16283 .opcode = 34,
16284 .operands = &.{
16285 .{ .kind = .id_ref, .quantifier = .required },
16286 },
16287 },
16288 .{
16289 .name = "Modf",
16290 .opcode = 35,
16291 .operands = &.{
16292 .{ .kind = .id_ref, .quantifier = .required },
16293 .{ .kind = .id_ref, .quantifier = .required },
16294 },
16295 },
16296 .{
16297 .name = "ModfStruct",
16298 .opcode = 36,
16299 .operands = &.{
16300 .{ .kind = .id_ref, .quantifier = .required },
16301 },
16302 },
16303 .{
16304 .name = "FMin",
16305 .opcode = 37,
16306 .operands = &.{
16307 .{ .kind = .id_ref, .quantifier = .required },
16308 .{ .kind = .id_ref, .quantifier = .required },
16309 },
16310 },
16311 .{
16312 .name = "UMin",
16313 .opcode = 38,
16314 .operands = &.{
16315 .{ .kind = .id_ref, .quantifier = .required },
16316 .{ .kind = .id_ref, .quantifier = .required },
16317 },
16318 },
16319 .{
16320 .name = "SMin",
16321 .opcode = 39,
16322 .operands = &.{
16323 .{ .kind = .id_ref, .quantifier = .required },
16324 .{ .kind = .id_ref, .quantifier = .required },
16325 },
16326 },
16327 .{
16328 .name = "FMax",
16329 .opcode = 40,
16330 .operands = &.{
16331 .{ .kind = .id_ref, .quantifier = .required },
16332 .{ .kind = .id_ref, .quantifier = .required },
16333 },
16334 },
16335 .{
16336 .name = "UMax",
16337 .opcode = 41,
16338 .operands = &.{
16339 .{ .kind = .id_ref, .quantifier = .required },
16340 .{ .kind = .id_ref, .quantifier = .required },
16341 },
16342 },
16343 .{
16344 .name = "SMax",
16345 .opcode = 42,
16346 .operands = &.{
16347 .{ .kind = .id_ref, .quantifier = .required },
16348 .{ .kind = .id_ref, .quantifier = .required },
16349 },
16350 },
16351 .{
16352 .name = "FClamp",
16353 .opcode = 43,
16354 .operands = &.{
16355 .{ .kind = .id_ref, .quantifier = .required },
16356 .{ .kind = .id_ref, .quantifier = .required },
16357 .{ .kind = .id_ref, .quantifier = .required },
16358 },
16359 },
16360 .{
16361 .name = "UClamp",
16362 .opcode = 44,
16363 .operands = &.{
16364 .{ .kind = .id_ref, .quantifier = .required },
16365 .{ .kind = .id_ref, .quantifier = .required },
16366 .{ .kind = .id_ref, .quantifier = .required },
16367 },
16368 },
16369 .{
16370 .name = "SClamp",
16371 .opcode = 45,
16372 .operands = &.{
16373 .{ .kind = .id_ref, .quantifier = .required },
16374 .{ .kind = .id_ref, .quantifier = .required },
16375 .{ .kind = .id_ref, .quantifier = .required },
16376 },
16377 },
16378 .{
16379 .name = "FMix",
16380 .opcode = 46,
16381 .operands = &.{
16382 .{ .kind = .id_ref, .quantifier = .required },
16383 .{ .kind = .id_ref, .quantifier = .required },
16384 .{ .kind = .id_ref, .quantifier = .required },
16385 },
16386 },
16387 .{
16388 .name = "IMix",
16389 .opcode = 47,
16390 .operands = &.{
16391 .{ .kind = .id_ref, .quantifier = .required },
16392 .{ .kind = .id_ref, .quantifier = .required },
16393 .{ .kind = .id_ref, .quantifier = .required },
16394 },
16395 },
16396 .{
16397 .name = "Step",
16398 .opcode = 48,
16399 .operands = &.{
16400 .{ .kind = .id_ref, .quantifier = .required },
16401 .{ .kind = .id_ref, .quantifier = .required },
16402 },
16403 },
16404 .{
16405 .name = "SmoothStep",
16406 .opcode = 49,
16407 .operands = &.{
16408 .{ .kind = .id_ref, .quantifier = .required },
16409 .{ .kind = .id_ref, .quantifier = .required },
16410 .{ .kind = .id_ref, .quantifier = .required },
16411 },
16412 },
16413 .{
16414 .name = "Fma",
16415 .opcode = 50,
16416 .operands = &.{
16417 .{ .kind = .id_ref, .quantifier = .required },
16418 .{ .kind = .id_ref, .quantifier = .required },
16419 .{ .kind = .id_ref, .quantifier = .required },
16420 },
16421 },
16422 .{
16423 .name = "Frexp",
16424 .opcode = 51,
16425 .operands = &.{
16426 .{ .kind = .id_ref, .quantifier = .required },
16427 .{ .kind = .id_ref, .quantifier = .required },
16428 },
16429 },
16430 .{
16431 .name = "FrexpStruct",
16432 .opcode = 52,
16433 .operands = &.{
16434 .{ .kind = .id_ref, .quantifier = .required },
16435 },
16436 },
16437 .{
16438 .name = "Ldexp",
16439 .opcode = 53,
16440 .operands = &.{
16441 .{ .kind = .id_ref, .quantifier = .required },
16442 .{ .kind = .id_ref, .quantifier = .required },
16443 },
16444 },
16445 .{
16446 .name = "PackSnorm4x8",
16447 .opcode = 54,
16448 .operands = &.{
16449 .{ .kind = .id_ref, .quantifier = .required },
16450 },
16451 },
16452 .{
16453 .name = "PackUnorm4x8",
16454 .opcode = 55,
16455 .operands = &.{
16456 .{ .kind = .id_ref, .quantifier = .required },
16457 },
16458 },
16459 .{
16460 .name = "PackSnorm2x16",
16461 .opcode = 56,
16462 .operands = &.{
16463 .{ .kind = .id_ref, .quantifier = .required },
16464 },
16465 },
16466 .{
16467 .name = "PackUnorm2x16",
16468 .opcode = 57,
16469 .operands = &.{
16470 .{ .kind = .id_ref, .quantifier = .required },
16471 },
16472 },
16473 .{
16474 .name = "PackHalf2x16",
16475 .opcode = 58,
16476 .operands = &.{
16477 .{ .kind = .id_ref, .quantifier = .required },
16478 },
16479 },
16480 .{
16481 .name = "PackDouble2x32",
16482 .opcode = 59,
16483 .operands = &.{
16484 .{ .kind = .id_ref, .quantifier = .required },
16485 },
16486 },
16487 .{
16488 .name = "UnpackSnorm2x16",
16489 .opcode = 60,
16490 .operands = &.{
16491 .{ .kind = .id_ref, .quantifier = .required },
16492 },
16493 },
16494 .{
16495 .name = "UnpackUnorm2x16",
16496 .opcode = 61,
16497 .operands = &.{
16498 .{ .kind = .id_ref, .quantifier = .required },
16499 },
16500 },
16501 .{
16502 .name = "UnpackHalf2x16",
16503 .opcode = 62,
16504 .operands = &.{
16505 .{ .kind = .id_ref, .quantifier = .required },
16506 },
16507 },
16508 .{
16509 .name = "UnpackSnorm4x8",
16510 .opcode = 63,
16511 .operands = &.{
16512 .{ .kind = .id_ref, .quantifier = .required },
16513 },
16514 },
16515 .{
16516 .name = "UnpackUnorm4x8",
16517 .opcode = 64,
16518 .operands = &.{
16519 .{ .kind = .id_ref, .quantifier = .required },
16520 },
16521 },
16522 .{
16523 .name = "UnpackDouble2x32",
16524 .opcode = 65,
16525 .operands = &.{
16526 .{ .kind = .id_ref, .quantifier = .required },
16527 },
16528 },
16529 .{
16530 .name = "Length",
16531 .opcode = 66,
16532 .operands = &.{
16533 .{ .kind = .id_ref, .quantifier = .required },
16534 },
16535 },
16536 .{
16537 .name = "Distance",
16538 .opcode = 67,
16539 .operands = &.{
16540 .{ .kind = .id_ref, .quantifier = .required },
16541 .{ .kind = .id_ref, .quantifier = .required },
16542 },
16543 },
16544 .{
16545 .name = "Cross",
16546 .opcode = 68,
16547 .operands = &.{
16548 .{ .kind = .id_ref, .quantifier = .required },
16549 .{ .kind = .id_ref, .quantifier = .required },
16550 },
16551 },
16552 .{
16553 .name = "Normalize",
16554 .opcode = 69,
16555 .operands = &.{
16556 .{ .kind = .id_ref, .quantifier = .required },
16557 },
16558 },
16559 .{
16560 .name = "FaceForward",
16561 .opcode = 70,
16562 .operands = &.{
16563 .{ .kind = .id_ref, .quantifier = .required },
16564 .{ .kind = .id_ref, .quantifier = .required },
16565 .{ .kind = .id_ref, .quantifier = .required },
16566 },
16567 },
16568 .{
16569 .name = "Reflect",
16570 .opcode = 71,
16571 .operands = &.{
16572 .{ .kind = .id_ref, .quantifier = .required },
16573 .{ .kind = .id_ref, .quantifier = .required },
16574 },
16575 },
16576 .{
16577 .name = "Refract",
16578 .opcode = 72,
16579 .operands = &.{
16580 .{ .kind = .id_ref, .quantifier = .required },
16581 .{ .kind = .id_ref, .quantifier = .required },
16582 .{ .kind = .id_ref, .quantifier = .required },
16583 },
16584 },
16585 .{
16586 .name = "FindILsb",
16587 .opcode = 73,
16588 .operands = &.{
16589 .{ .kind = .id_ref, .quantifier = .required },
16590 },
16591 },
16592 .{
16593 .name = "FindSMsb",
16594 .opcode = 74,
16595 .operands = &.{
16596 .{ .kind = .id_ref, .quantifier = .required },
16597 },
16598 },
16599 .{
16600 .name = "FindUMsb",
16601 .opcode = 75,
16602 .operands = &.{
16603 .{ .kind = .id_ref, .quantifier = .required },
16604 },
16605 },
16606 .{
16607 .name = "InterpolateAtCentroid",
16608 .opcode = 76,
16609 .operands = &.{
16610 .{ .kind = .id_ref, .quantifier = .required },
16611 },
16612 },
16613 .{
16614 .name = "InterpolateAtSample",
16615 .opcode = 77,
16616 .operands = &.{
16617 .{ .kind = .id_ref, .quantifier = .required },
16618 .{ .kind = .id_ref, .quantifier = .required },
16619 },
16620 },
16621 .{
16622 .name = "InterpolateAtOffset",
16623 .opcode = 78,
16624 .operands = &.{
16625 .{ .kind = .id_ref, .quantifier = .required },
16626 .{ .kind = .id_ref, .quantifier = .required },
16627 },
16628 },
16629 .{
16630 .name = "NMin",
16631 .opcode = 79,
16632 .operands = &.{
16633 .{ .kind = .id_ref, .quantifier = .required },
16634 .{ .kind = .id_ref, .quantifier = .required },
16635 },
16636 },
16637 .{
16638 .name = "NMax",
16639 .opcode = 80,
16640 .operands = &.{
16641 .{ .kind = .id_ref, .quantifier = .required },
16642 .{ .kind = .id_ref, .quantifier = .required },
16643 },
16644 },
16645 .{
16646 .name = "NClamp",
16647 .opcode = 81,
16648 .operands = &.{
16649 .{ .kind = .id_ref, .quantifier = .required },
16650 .{ .kind = .id_ref, .quantifier = .required },
16651 .{ .kind = .id_ref, .quantifier = .required },
16652 },
16653 },
16654 },
16655 .spv_amd_shader_ballot => &.{
16656 .{
16657 .name = "SwizzleInvocationsAMD",
16658 .opcode = 1,
16659 .operands = &.{
16660 .{ .kind = .id_ref, .quantifier = .required },
16661 .{ .kind = .id_ref, .quantifier = .required },
16662 },
16663 },
16664 .{
16665 .name = "SwizzleInvocationsMaskedAMD",
16666 .opcode = 2,
16667 .operands = &.{
16668 .{ .kind = .id_ref, .quantifier = .required },
16669 .{ .kind = .id_ref, .quantifier = .required },
16670 },
16671 },
16672 .{
16673 .name = "WriteInvocationAMD",
16674 .opcode = 3,
16675 .operands = &.{
16676 .{ .kind = .id_ref, .quantifier = .required },
16677 .{ .kind = .id_ref, .quantifier = .required },
16678 .{ .kind = .id_ref, .quantifier = .required },
16679 },
16680 },
16681 .{
16682 .name = "MbcntAMD",
16683 .opcode = 4,
16684 .operands = &.{
16685 .{ .kind = .id_ref, .quantifier = .required },
16686 },
16687 },
16688 },
16689 .non_semantic_debug_printf => &.{
16690 .{
16691 .name = "DebugPrintf",
16692 .opcode = 1,
16693 .operands = &.{
16694 .{ .kind = .id_ref, .quantifier = .required },
16695 .{ .kind = .id_ref, .quantifier = .variadic },
16696 },
16697 },
16698 },
16699 .spv_amd_gcn_shader => &.{
16700 .{
16701 .name = "CubeFaceIndexAMD",
16702 .opcode = 1,
16703 .operands = &.{
16704 .{ .kind = .id_ref, .quantifier = .required },
16705 },
16706 },
16707 .{
16708 .name = "CubeFaceCoordAMD",
16709 .opcode = 2,
16710 .operands = &.{
16711 .{ .kind = .id_ref, .quantifier = .required },
16712 },
16713 },
16714 .{
16715 .name = "TimeAMD",
16716 .opcode = 3,
16717 .operands = &.{},
16718 },
16719 },
16720 .open_cl_std => &.{
16721 .{
16722 .name = "acos",
16723 .opcode = 0,
16724 .operands = &.{
16725 .{ .kind = .id_ref, .quantifier = .required },
16726 },
16727 },
16728 .{
16729 .name = "acosh",
16730 .opcode = 1,
16731 .operands = &.{
16732 .{ .kind = .id_ref, .quantifier = .required },
16733 },
16734 },
16735 .{
16736 .name = "acospi",
16737 .opcode = 2,
16738 .operands = &.{
16739 .{ .kind = .id_ref, .quantifier = .required },
16740 },
16741 },
16742 .{
16743 .name = "asin",
16744 .opcode = 3,
16745 .operands = &.{
16746 .{ .kind = .id_ref, .quantifier = .required },
16747 },
16748 },
16749 .{
16750 .name = "asinh",
16751 .opcode = 4,
16752 .operands = &.{
16753 .{ .kind = .id_ref, .quantifier = .required },
16754 },
16755 },
16756 .{
16757 .name = "asinpi",
16758 .opcode = 5,
16759 .operands = &.{
16760 .{ .kind = .id_ref, .quantifier = .required },
16761 },
16762 },
16763 .{
16764 .name = "atan",
16765 .opcode = 6,
16766 .operands = &.{
16767 .{ .kind = .id_ref, .quantifier = .required },
16768 },
16769 },
16770 .{
16771 .name = "atan2",
16772 .opcode = 7,
16773 .operands = &.{
16774 .{ .kind = .id_ref, .quantifier = .required },
16775 .{ .kind = .id_ref, .quantifier = .required },
16776 },
16777 },
16778 .{
16779 .name = "atanh",
16780 .opcode = 8,
16781 .operands = &.{
16782 .{ .kind = .id_ref, .quantifier = .required },
16783 },
16784 },
16785 .{
16786 .name = "atanpi",
16787 .opcode = 9,
16788 .operands = &.{
16789 .{ .kind = .id_ref, .quantifier = .required },
16790 },
16791 },
16792 .{
16793 .name = "atan2pi",
16794 .opcode = 10,
16795 .operands = &.{
16796 .{ .kind = .id_ref, .quantifier = .required },
16797 .{ .kind = .id_ref, .quantifier = .required },
16798 },
16799 },
16800 .{
16801 .name = "cbrt",
16802 .opcode = 11,
16803 .operands = &.{
16804 .{ .kind = .id_ref, .quantifier = .required },
16805 },
16806 },
16807 .{
16808 .name = "ceil",
16809 .opcode = 12,
16810 .operands = &.{
16811 .{ .kind = .id_ref, .quantifier = .required },
16812 },
16813 },
16814 .{
16815 .name = "copysign",
16816 .opcode = 13,
16817 .operands = &.{
16818 .{ .kind = .id_ref, .quantifier = .required },
16819 .{ .kind = .id_ref, .quantifier = .required },
16820 },
16821 },
16822 .{
16823 .name = "cos",
16824 .opcode = 14,
16825 .operands = &.{
16826 .{ .kind = .id_ref, .quantifier = .required },
16827 },
16828 },
16829 .{
16830 .name = "cosh",
16831 .opcode = 15,
16832 .operands = &.{
16833 .{ .kind = .id_ref, .quantifier = .required },
16834 },
16835 },
16836 .{
16837 .name = "cospi",
16838 .opcode = 16,
16839 .operands = &.{
16840 .{ .kind = .id_ref, .quantifier = .required },
16841 },
16842 },
16843 .{
16844 .name = "erfc",
16845 .opcode = 17,
16846 .operands = &.{
16847 .{ .kind = .id_ref, .quantifier = .required },
16848 },
16849 },
16850 .{
16851 .name = "erf",
16852 .opcode = 18,
16853 .operands = &.{
16854 .{ .kind = .id_ref, .quantifier = .required },
16855 },
16856 },
16857 .{
16858 .name = "exp",
16859 .opcode = 19,
16860 .operands = &.{
16861 .{ .kind = .id_ref, .quantifier = .required },
16862 },
16863 },
16864 .{
16865 .name = "exp2",
16866 .opcode = 20,
16867 .operands = &.{
16868 .{ .kind = .id_ref, .quantifier = .required },
16869 },
16870 },
16871 .{
16872 .name = "exp10",
16873 .opcode = 21,
16874 .operands = &.{
16875 .{ .kind = .id_ref, .quantifier = .required },
16876 },
16877 },
16878 .{
16879 .name = "expm1",
16880 .opcode = 22,
16881 .operands = &.{
16882 .{ .kind = .id_ref, .quantifier = .required },
16883 },
16884 },
16885 .{
16886 .name = "fabs",
16887 .opcode = 23,
16888 .operands = &.{
16889 .{ .kind = .id_ref, .quantifier = .required },
16890 },
16891 },
16892 .{
16893 .name = "fdim",
16894 .opcode = 24,
16895 .operands = &.{
16896 .{ .kind = .id_ref, .quantifier = .required },
16897 .{ .kind = .id_ref, .quantifier = .required },
16898 },
16899 },
16900 .{
16901 .name = "floor",
16902 .opcode = 25,
16903 .operands = &.{
16904 .{ .kind = .id_ref, .quantifier = .required },
16905 },
16906 },
16907 .{
16908 .name = "fma",
16909 .opcode = 26,
16910 .operands = &.{
16911 .{ .kind = .id_ref, .quantifier = .required },
16912 .{ .kind = .id_ref, .quantifier = .required },
16913 .{ .kind = .id_ref, .quantifier = .required },
16914 },
16915 },
16916 .{
16917 .name = "fmax",
16918 .opcode = 27,
16919 .operands = &.{
16920 .{ .kind = .id_ref, .quantifier = .required },
16921 .{ .kind = .id_ref, .quantifier = .required },
16922 },
16923 },
16924 .{
16925 .name = "fmin",
16926 .opcode = 28,
16927 .operands = &.{
16928 .{ .kind = .id_ref, .quantifier = .required },
16929 .{ .kind = .id_ref, .quantifier = .required },
16930 },
16931 },
16932 .{
16933 .name = "fmod",
16934 .opcode = 29,
16935 .operands = &.{
16936 .{ .kind = .id_ref, .quantifier = .required },
16937 .{ .kind = .id_ref, .quantifier = .required },
16938 },
16939 },
16940 .{
16941 .name = "fract",
16942 .opcode = 30,
16943 .operands = &.{
16944 .{ .kind = .id_ref, .quantifier = .required },
16945 .{ .kind = .id_ref, .quantifier = .required },
16946 },
16947 },
16948 .{
16949 .name = "frexp",
16950 .opcode = 31,
16951 .operands = &.{
16952 .{ .kind = .id_ref, .quantifier = .required },
16953 .{ .kind = .id_ref, .quantifier = .required },
16954 },
16955 },
16956 .{
16957 .name = "hypot",
16958 .opcode = 32,
16959 .operands = &.{
16960 .{ .kind = .id_ref, .quantifier = .required },
16961 .{ .kind = .id_ref, .quantifier = .required },
16962 },
16963 },
16964 .{
16965 .name = "ilogb",
16966 .opcode = 33,
16967 .operands = &.{
16968 .{ .kind = .id_ref, .quantifier = .required },
16969 },
16970 },
16971 .{
16972 .name = "ldexp",
16973 .opcode = 34,
16974 .operands = &.{
16975 .{ .kind = .id_ref, .quantifier = .required },
16976 .{ .kind = .id_ref, .quantifier = .required },
16977 },
16978 },
16979 .{
16980 .name = "lgamma",
16981 .opcode = 35,
16982 .operands = &.{
16983 .{ .kind = .id_ref, .quantifier = .required },
16984 },
16985 },
16986 .{
16987 .name = "lgamma_r",
16988 .opcode = 36,
16989 .operands = &.{
16990 .{ .kind = .id_ref, .quantifier = .required },
16991 .{ .kind = .id_ref, .quantifier = .required },
16992 },
16993 },
16994 .{
16995 .name = "log",
16996 .opcode = 37,
16997 .operands = &.{
16998 .{ .kind = .id_ref, .quantifier = .required },
16999 },
17000 },
17001 .{
17002 .name = "log2",
17003 .opcode = 38,
17004 .operands = &.{
17005 .{ .kind = .id_ref, .quantifier = .required },
17006 },
17007 },
17008 .{
17009 .name = "log10",
17010 .opcode = 39,
17011 .operands = &.{
17012 .{ .kind = .id_ref, .quantifier = .required },
17013 },
17014 },
17015 .{
17016 .name = "log1p",
17017 .opcode = 40,
17018 .operands = &.{
17019 .{ .kind = .id_ref, .quantifier = .required },
17020 },
17021 },
17022 .{
17023 .name = "logb",
17024 .opcode = 41,
17025 .operands = &.{
17026 .{ .kind = .id_ref, .quantifier = .required },
17027 },
17028 },
17029 .{
17030 .name = "mad",
17031 .opcode = 42,
17032 .operands = &.{
17033 .{ .kind = .id_ref, .quantifier = .required },
17034 .{ .kind = .id_ref, .quantifier = .required },
17035 .{ .kind = .id_ref, .quantifier = .required },
17036 },
17037 },
17038 .{
17039 .name = "maxmag",
17040 .opcode = 43,
17041 .operands = &.{
17042 .{ .kind = .id_ref, .quantifier = .required },
17043 .{ .kind = .id_ref, .quantifier = .required },
17044 },
17045 },
17046 .{
17047 .name = "minmag",
17048 .opcode = 44,
17049 .operands = &.{
17050 .{ .kind = .id_ref, .quantifier = .required },
17051 .{ .kind = .id_ref, .quantifier = .required },
17052 },
17053 },
17054 .{
17055 .name = "modf",
17056 .opcode = 45,
17057 .operands = &.{
17058 .{ .kind = .id_ref, .quantifier = .required },
17059 .{ .kind = .id_ref, .quantifier = .required },
17060 },
17061 },
17062 .{
17063 .name = "nan",
17064 .opcode = 46,
17065 .operands = &.{
17066 .{ .kind = .id_ref, .quantifier = .required },
17067 },
17068 },
17069 .{
17070 .name = "nextafter",
17071 .opcode = 47,
17072 .operands = &.{
17073 .{ .kind = .id_ref, .quantifier = .required },
17074 .{ .kind = .id_ref, .quantifier = .required },
17075 },
17076 },
17077 .{
17078 .name = "pow",
17079 .opcode = 48,
17080 .operands = &.{
17081 .{ .kind = .id_ref, .quantifier = .required },
17082 .{ .kind = .id_ref, .quantifier = .required },
17083 },
17084 },
17085 .{
17086 .name = "pown",
17087 .opcode = 49,
17088 .operands = &.{
17089 .{ .kind = .id_ref, .quantifier = .required },
17090 .{ .kind = .id_ref, .quantifier = .required },
17091 },
17092 },
17093 .{
17094 .name = "powr",
17095 .opcode = 50,
17096 .operands = &.{
17097 .{ .kind = .id_ref, .quantifier = .required },
17098 .{ .kind = .id_ref, .quantifier = .required },
17099 },
17100 },
17101 .{
17102 .name = "remainder",
17103 .opcode = 51,
17104 .operands = &.{
17105 .{ .kind = .id_ref, .quantifier = .required },
17106 .{ .kind = .id_ref, .quantifier = .required },
17107 },
17108 },
17109 .{
17110 .name = "remquo",
17111 .opcode = 52,
17112 .operands = &.{
17113 .{ .kind = .id_ref, .quantifier = .required },
17114 .{ .kind = .id_ref, .quantifier = .required },
17115 .{ .kind = .id_ref, .quantifier = .required },
17116 },
17117 },
17118 .{
17119 .name = "rint",
17120 .opcode = 53,
17121 .operands = &.{
17122 .{ .kind = .id_ref, .quantifier = .required },
17123 },
17124 },
17125 .{
17126 .name = "rootn",
17127 .opcode = 54,
17128 .operands = &.{
17129 .{ .kind = .id_ref, .quantifier = .required },
17130 .{ .kind = .id_ref, .quantifier = .required },
17131 },
17132 },
17133 .{
17134 .name = "round",
17135 .opcode = 55,
17136 .operands = &.{
1713714498 .{ .kind = .id_ref, .quantifier = .required },
1713814499 },
1713914500 },
1714014501 .{
17141 .name = "rsqrt",
17142 .opcode = 56,
14502 .name = "cbrt",
14503 .opcode = 11,
1714314504 .operands = &.{
1714414505 .{ .kind = .id_ref, .quantifier = .required },
1714514506 },
1714614507 },
1714714508 .{
17148 .name = "sin",
17149 .opcode = 57,
14509 .name = "ceil",
14510 .opcode = 12,
1715014511 .operands = &.{
1715114512 .{ .kind = .id_ref, .quantifier = .required },
1715214513 },
1715314514 },
1715414515 .{
17155 .name = "sincos",
17156 .opcode = 58,
14516 .name = "copysign",
14517 .opcode = 13,
1715714518 .operands = &.{
1715814519 .{ .kind = .id_ref, .quantifier = .required },
1715914520 .{ .kind = .id_ref, .quantifier = .required },
1716014521 },
1716114522 },
1716214523 .{
17163 .name = "sinh",
17164 .opcode = 59,
14524 .name = "cos",
14525 .opcode = 14,
1716514526 .operands = &.{
1716614527 .{ .kind = .id_ref, .quantifier = .required },
1716714528 },
1716814529 },
1716914530 .{
17170 .name = "sinpi",
17171 .opcode = 60,
14531 .name = "cosh",
14532 .opcode = 15,
1717214533 .operands = &.{
1717314534 .{ .kind = .id_ref, .quantifier = .required },
1717414535 },
1717514536 },
1717614537 .{
17177 .name = "sqrt",
17178 .opcode = 61,
14538 .name = "cospi",
14539 .opcode = 16,
1717914540 .operands = &.{
1718014541 .{ .kind = .id_ref, .quantifier = .required },
1718114542 },
1718214543 },
1718314544 .{
17184 .name = "tan",
17185 .opcode = 62,
14545 .name = "erfc",
14546 .opcode = 17,
1718614547 .operands = &.{
1718714548 .{ .kind = .id_ref, .quantifier = .required },
1718814549 },
1718914550 },
1719014551 .{
17191 .name = "tanh",
17192 .opcode = 63,
14552 .name = "erf",
14553 .opcode = 18,
1719314554 .operands = &.{
1719414555 .{ .kind = .id_ref, .quantifier = .required },
1719514556 },
1719614557 },
1719714558 .{
17198 .name = "tanpi",
17199 .opcode = 64,
14559 .name = "exp",
14560 .opcode = 19,
1720014561 .operands = &.{
1720114562 .{ .kind = .id_ref, .quantifier = .required },
1720214563 },
1720314564 },
1720414565 .{
17205 .name = "tgamma",
17206 .opcode = 65,
14566 .name = "exp2",
14567 .opcode = 20,
1720714568 .operands = &.{
1720814569 .{ .kind = .id_ref, .quantifier = .required },
1720914570 },
1721014571 },
1721114572 .{
17212 .name = "trunc",
17213 .opcode = 66,
14573 .name = "exp10",
14574 .opcode = 21,
1721414575 .operands = &.{
1721514576 .{ .kind = .id_ref, .quantifier = .required },
1721614577 },
1721714578 },
1721814579 .{
17219 .name = "half_cos",
17220 .opcode = 67,
14580 .name = "expm1",
14581 .opcode = 22,
1722114582 .operands = &.{
1722214583 .{ .kind = .id_ref, .quantifier = .required },
1722314584 },
1722414585 },
1722514586 .{
17226 .name = "half_divide",
17227 .opcode = 68,
14587 .name = "fabs",
14588 .opcode = 23,
1722814589 .operands = &.{
1722914590 .{ .kind = .id_ref, .quantifier = .required },
17230 .{ .kind = .id_ref, .quantifier = .required },
1723114591 },
1723214592 },
1723314593 .{
17234 .name = "half_exp",
17235 .opcode = 69,
14594 .name = "fdim",
14595 .opcode = 24,
1723614596 .operands = &.{
1723714597 .{ .kind = .id_ref, .quantifier = .required },
14598 .{ .kind = .id_ref, .quantifier = .required },
1723814599 },
1723914600 },
1724014601 .{
17241 .name = "half_exp2",
17242 .opcode = 70,
14602 .name = "floor",
14603 .opcode = 25,
1724314604 .operands = &.{
1724414605 .{ .kind = .id_ref, .quantifier = .required },
1724514606 },
1724614607 },
1724714608 .{
17248 .name = "half_exp10",
17249 .opcode = 71,
14609 .name = "fma",
14610 .opcode = 26,
1725014611 .operands = &.{
1725114612 .{ .kind = .id_ref, .quantifier = .required },
14613 .{ .kind = .id_ref, .quantifier = .required },
14614 .{ .kind = .id_ref, .quantifier = .required },
1725214615 },
1725314616 },
1725414617 .{
17255 .name = "half_log",
17256 .opcode = 72,
14618 .name = "fmax",
14619 .opcode = 27,
1725714620 .operands = &.{
1725814621 .{ .kind = .id_ref, .quantifier = .required },
14622 .{ .kind = .id_ref, .quantifier = .required },
1725914623 },
1726014624 },
1726114625 .{
17262 .name = "half_log2",
17263 .opcode = 73,
14626 .name = "fmin",
14627 .opcode = 28,
1726414628 .operands = &.{
1726514629 .{ .kind = .id_ref, .quantifier = .required },
14630 .{ .kind = .id_ref, .quantifier = .required },
1726614631 },
1726714632 },
1726814633 .{
17269 .name = "half_log10",
17270 .opcode = 74,
14634 .name = "fmod",
14635 .opcode = 29,
1727114636 .operands = &.{
1727214637 .{ .kind = .id_ref, .quantifier = .required },
14638 .{ .kind = .id_ref, .quantifier = .required },
1727314639 },
1727414640 },
1727514641 .{
17276 .name = "half_powr",
17277 .opcode = 75,
14642 .name = "fract",
14643 .opcode = 30,
1727814644 .operands = &.{
1727914645 .{ .kind = .id_ref, .quantifier = .required },
1728014646 .{ .kind = .id_ref, .quantifier = .required },
1728114647 },
1728214648 },
1728314649 .{
17284 .name = "half_recip",
17285 .opcode = 76,
14650 .name = "frexp",
14651 .opcode = 31,
1728614652 .operands = &.{
1728714653 .{ .kind = .id_ref, .quantifier = .required },
14654 .{ .kind = .id_ref, .quantifier = .required },
1728814655 },
1728914656 },
1729014657 .{
17291 .name = "half_rsqrt",
17292 .opcode = 77,
14658 .name = "hypot",
14659 .opcode = 32,
1729314660 .operands = &.{
1729414661 .{ .kind = .id_ref, .quantifier = .required },
14662 .{ .kind = .id_ref, .quantifier = .required },
1729514663 },
1729614664 },
1729714665 .{
17298 .name = "half_sin",
17299 .opcode = 78,
14666 .name = "ilogb",
14667 .opcode = 33,
1730014668 .operands = &.{
1730114669 .{ .kind = .id_ref, .quantifier = .required },
1730214670 },
1730314671 },
1730414672 .{
17305 .name = "half_sqrt",
17306 .opcode = 79,
14673 .name = "ldexp",
14674 .opcode = 34,
1730714675 .operands = &.{
1730814676 .{ .kind = .id_ref, .quantifier = .required },
14677 .{ .kind = .id_ref, .quantifier = .required },
1730914678 },
1731014679 },
1731114680 .{
17312 .name = "half_tan",
17313 .opcode = 80,
14681 .name = "lgamma",
14682 .opcode = 35,
1731414683 .operands = &.{
1731514684 .{ .kind = .id_ref, .quantifier = .required },
1731614685 },
1731714686 },
1731814687 .{
17319 .name = "native_cos",
17320 .opcode = 81,
14688 .name = "lgamma_r",
14689 .opcode = 36,
1732114690 .operands = &.{
1732214691 .{ .kind = .id_ref, .quantifier = .required },
14692 .{ .kind = .id_ref, .quantifier = .required },
1732314693 },
1732414694 },
1732514695 .{
17326 .name = "native_divide",
17327 .opcode = 82,
14696 .name = "log",
14697 .opcode = 37,
1732814698 .operands = &.{
1732914699 .{ .kind = .id_ref, .quantifier = .required },
17330 .{ .kind = .id_ref, .quantifier = .required },
1733114700 },
1733214701 },
1733314702 .{
17334 .name = "native_exp",
17335 .opcode = 83,
14703 .name = "log2",
14704 .opcode = 38,
1733614705 .operands = &.{
1733714706 .{ .kind = .id_ref, .quantifier = .required },
1733814707 },
1733914708 },
1734014709 .{
17341 .name = "native_exp2",
17342 .opcode = 84,
14710 .name = "log10",
14711 .opcode = 39,
1734314712 .operands = &.{
1734414713 .{ .kind = .id_ref, .quantifier = .required },
1734514714 },
1734614715 },
1734714716 .{
17348 .name = "native_exp10",
17349 .opcode = 85,
14717 .name = "log1p",
14718 .opcode = 40,
1735014719 .operands = &.{
1735114720 .{ .kind = .id_ref, .quantifier = .required },
1735214721 },
1735314722 },
1735414723 .{
17355 .name = "native_log",
17356 .opcode = 86,
14724 .name = "logb",
14725 .opcode = 41,
1735714726 .operands = &.{
1735814727 .{ .kind = .id_ref, .quantifier = .required },
1735914728 },
1736014729 },
1736114730 .{
17362 .name = "native_log2",
17363 .opcode = 87,
14731 .name = "mad",
14732 .opcode = 42,
1736414733 .operands = &.{
1736514734 .{ .kind = .id_ref, .quantifier = .required },
14735 .{ .kind = .id_ref, .quantifier = .required },
14736 .{ .kind = .id_ref, .quantifier = .required },
1736614737 },
1736714738 },
1736814739 .{
17369 .name = "native_log10",
17370 .opcode = 88,
14740 .name = "maxmag",
14741 .opcode = 43,
1737114742 .operands = &.{
1737214743 .{ .kind = .id_ref, .quantifier = .required },
14744 .{ .kind = .id_ref, .quantifier = .required },
1737314745 },
1737414746 },
1737514747 .{
17376 .name = "native_powr",
17377 .opcode = 89,
14748 .name = "minmag",
14749 .opcode = 44,
1737814750 .operands = &.{
1737914751 .{ .kind = .id_ref, .quantifier = .required },
1738014752 .{ .kind = .id_ref, .quantifier = .required },
1738114753 },
1738214754 },
1738314755 .{
17384 .name = "native_recip",
17385 .opcode = 90,
14756 .name = "modf",
14757 .opcode = 45,
1738614758 .operands = &.{
1738714759 .{ .kind = .id_ref, .quantifier = .required },
14760 .{ .kind = .id_ref, .quantifier = .required },
1738814761 },
1738914762 },
1739014763 .{
17391 .name = "native_rsqrt",
17392 .opcode = 91,
14764 .name = "nan",
14765 .opcode = 46,
1739314766 .operands = &.{
1739414767 .{ .kind = .id_ref, .quantifier = .required },
1739514768 },
1739614769 },
1739714770 .{
17398 .name = "native_sin",
17399 .opcode = 92,
14771 .name = "nextafter",
14772 .opcode = 47,
1740014773 .operands = &.{
1740114774 .{ .kind = .id_ref, .quantifier = .required },
14775 .{ .kind = .id_ref, .quantifier = .required },
1740214776 },
1740314777 },
1740414778 .{
17405 .name = "native_sqrt",
17406 .opcode = 93,
14779 .name = "pow",
14780 .opcode = 48,
1740714781 .operands = &.{
1740814782 .{ .kind = .id_ref, .quantifier = .required },
14783 .{ .kind = .id_ref, .quantifier = .required },
1740914784 },
1741014785 },
1741114786 .{
17412 .name = "native_tan",
17413 .opcode = 94,
14787 .name = "pown",
14788 .opcode = 49,
1741414789 .operands = &.{
1741514790 .{ .kind = .id_ref, .quantifier = .required },
14791 .{ .kind = .id_ref, .quantifier = .required },
1741614792 },
1741714793 },
1741814794 .{
17419 .name = "fclamp",
17420 .opcode = 95,
14795 .name = "powr",
14796 .opcode = 50,
1742114797 .operands = &.{
1742214798 .{ .kind = .id_ref, .quantifier = .required },
1742314799 .{ .kind = .id_ref, .quantifier = .required },
17424 .{ .kind = .id_ref, .quantifier = .required },
1742514800 },
1742614801 },
1742714802 .{
17428 .name = "degrees",
17429 .opcode = 96,
14803 .name = "remainder",
14804 .opcode = 51,
1743014805 .operands = &.{
1743114806 .{ .kind = .id_ref, .quantifier = .required },
14807 .{ .kind = .id_ref, .quantifier = .required },
1743214808 },
1743314809 },
1743414810 .{
17435 .name = "fmax_common",
17436 .opcode = 97,
14811 .name = "remquo",
14812 .opcode = 52,
1743714813 .operands = &.{
1743814814 .{ .kind = .id_ref, .quantifier = .required },
1743914815 .{ .kind = .id_ref, .quantifier = .required },
14816 .{ .kind = .id_ref, .quantifier = .required },
1744014817 },
1744114818 },
1744214819 .{
17443 .name = "fmin_common",
17444 .opcode = 98,
14820 .name = "rint",
14821 .opcode = 53,
1744514822 .operands = &.{
1744614823 .{ .kind = .id_ref, .quantifier = .required },
17447 .{ .kind = .id_ref, .quantifier = .required },
1744814824 },
1744914825 },
1745014826 .{
17451 .name = "mix",
17452 .opcode = 99,
14827 .name = "rootn",
14828 .opcode = 54,
1745314829 .operands = &.{
1745414830 .{ .kind = .id_ref, .quantifier = .required },
1745514831 .{ .kind = .id_ref, .quantifier = .required },
17456 .{ .kind = .id_ref, .quantifier = .required },
1745714832 },
1745814833 },
1745914834 .{
17460 .name = "radians",
17461 .opcode = 100,
14835 .name = "round",
14836 .opcode = 55,
1746214837 .operands = &.{
1746314838 .{ .kind = .id_ref, .quantifier = .required },
1746414839 },
1746514840 },
1746614841 .{
17467 .name = "step",
17468 .opcode = 101,
14842 .name = "rsqrt",
14843 .opcode = 56,
1746914844 .operands = &.{
1747014845 .{ .kind = .id_ref, .quantifier = .required },
17471 .{ .kind = .id_ref, .quantifier = .required },
1747214846 },
1747314847 },
1747414848 .{
17475 .name = "smoothstep",
17476 .opcode = 102,
14849 .name = "sin",
14850 .opcode = 57,
1747714851 .operands = &.{
1747814852 .{ .kind = .id_ref, .quantifier = .required },
14853 },
14854 },
14855 .{
14856 .name = "sincos",
14857 .opcode = 58,
14858 .operands = &.{
1747914859 .{ .kind = .id_ref, .quantifier = .required },
1748014860 .{ .kind = .id_ref, .quantifier = .required },
1748114861 },
1748214862 },
1748314863 .{
17484 .name = "sign",
17485 .opcode = 103,
14864 .name = "sinh",
14865 .opcode = 59,
1748614866 .operands = &.{
1748714867 .{ .kind = .id_ref, .quantifier = .required },
1748814868 },
1748914869 },
1749014870 .{
17491 .name = "cross",
17492 .opcode = 104,
14871 .name = "sinpi",
14872 .opcode = 60,
1749314873 .operands = &.{
1749414874 .{ .kind = .id_ref, .quantifier = .required },
17495 .{ .kind = .id_ref, .quantifier = .required },
1749614875 },
1749714876 },
1749814877 .{
17499 .name = "distance",
17500 .opcode = 105,
14878 .name = "sqrt",
14879 .opcode = 61,
1750114880 .operands = &.{
1750214881 .{ .kind = .id_ref, .quantifier = .required },
17503 .{ .kind = .id_ref, .quantifier = .required },
1750414882 },
1750514883 },
1750614884 .{
17507 .name = "length",
17508 .opcode = 106,
14885 .name = "tan",
14886 .opcode = 62,
1750914887 .operands = &.{
1751014888 .{ .kind = .id_ref, .quantifier = .required },
1751114889 },
1751214890 },
1751314891 .{
17514 .name = "normalize",
17515 .opcode = 107,
14892 .name = "tanh",
14893 .opcode = 63,
1751614894 .operands = &.{
1751714895 .{ .kind = .id_ref, .quantifier = .required },
1751814896 },
1751914897 },
1752014898 .{
17521 .name = "fast_distance",
17522 .opcode = 108,
14899 .name = "tanpi",
14900 .opcode = 64,
1752314901 .operands = &.{
1752414902 .{ .kind = .id_ref, .quantifier = .required },
17525 .{ .kind = .id_ref, .quantifier = .required },
1752614903 },
1752714904 },
1752814905 .{
17529 .name = "fast_length",
17530 .opcode = 109,
14906 .name = "tgamma",
14907 .opcode = 65,
1753114908 .operands = &.{
1753214909 .{ .kind = .id_ref, .quantifier = .required },
1753314910 },
1753414911 },
1753514912 .{
17536 .name = "fast_normalize",
17537 .opcode = 110,
14913 .name = "trunc",
14914 .opcode = 66,
1753814915 .operands = &.{
1753914916 .{ .kind = .id_ref, .quantifier = .required },
1754014917 },
1754114918 },
1754214919 .{
17543 .name = "s_abs",
17544 .opcode = 141,
14920 .name = "half_cos",
14921 .opcode = 67,
1754514922 .operands = &.{
1754614923 .{ .kind = .id_ref, .quantifier = .required },
1754714924 },
1754814925 },
1754914926 .{
17550 .name = "s_abs_diff",
17551 .opcode = 142,
14927 .name = "half_divide",
14928 .opcode = 68,
1755214929 .operands = &.{
1755314930 .{ .kind = .id_ref, .quantifier = .required },
1755414931 .{ .kind = .id_ref, .quantifier = .required },
1755514932 },
1755614933 },
1755714934 .{
17558 .name = "s_add_sat",
17559 .opcode = 143,
14935 .name = "half_exp",
14936 .opcode = 69,
1756014937 .operands = &.{
1756114938 .{ .kind = .id_ref, .quantifier = .required },
17562 .{ .kind = .id_ref, .quantifier = .required },
1756314939 },
1756414940 },
1756514941 .{
17566 .name = "u_add_sat",
17567 .opcode = 144,
14942 .name = "half_exp2",
14943 .opcode = 70,
1756814944 .operands = &.{
1756914945 .{ .kind = .id_ref, .quantifier = .required },
17570 .{ .kind = .id_ref, .quantifier = .required },
1757114946 },
1757214947 },
1757314948 .{
17574 .name = "s_hadd",
17575 .opcode = 145,
14949 .name = "half_exp10",
14950 .opcode = 71,
1757614951 .operands = &.{
1757714952 .{ .kind = .id_ref, .quantifier = .required },
17578 .{ .kind = .id_ref, .quantifier = .required },
1757914953 },
1758014954 },
1758114955 .{
17582 .name = "u_hadd",
17583 .opcode = 146,
14956 .name = "half_log",
14957 .opcode = 72,
1758414958 .operands = &.{
1758514959 .{ .kind = .id_ref, .quantifier = .required },
17586 .{ .kind = .id_ref, .quantifier = .required },
1758714960 },
1758814961 },
1758914962 .{
17590 .name = "s_rhadd",
17591 .opcode = 147,
14963 .name = "half_log2",
14964 .opcode = 73,
1759214965 .operands = &.{
1759314966 .{ .kind = .id_ref, .quantifier = .required },
17594 .{ .kind = .id_ref, .quantifier = .required },
1759514967 },
1759614968 },
1759714969 .{
17598 .name = "u_rhadd",
17599 .opcode = 148,
14970 .name = "half_log10",
14971 .opcode = 74,
1760014972 .operands = &.{
1760114973 .{ .kind = .id_ref, .quantifier = .required },
17602 .{ .kind = .id_ref, .quantifier = .required },
1760314974 },
1760414975 },
1760514976 .{
17606 .name = "s_clamp",
17607 .opcode = 149,
14977 .name = "half_powr",
14978 .opcode = 75,
1760814979 .operands = &.{
1760914980 .{ .kind = .id_ref, .quantifier = .required },
1761014981 .{ .kind = .id_ref, .quantifier = .required },
17611 .{ .kind = .id_ref, .quantifier = .required },
1761214982 },
1761314983 },
1761414984 .{
17615 .name = "u_clamp",
17616 .opcode = 150,
14985 .name = "half_recip",
14986 .opcode = 76,
1761714987 .operands = &.{
1761814988 .{ .kind = .id_ref, .quantifier = .required },
17619 .{ .kind = .id_ref, .quantifier = .required },
17620 .{ .kind = .id_ref, .quantifier = .required },
1762114989 },
1762214990 },
1762314991 .{
17624 .name = "clz",
17625 .opcode = 151,
14992 .name = "half_rsqrt",
14993 .opcode = 77,
1762614994 .operands = &.{
1762714995 .{ .kind = .id_ref, .quantifier = .required },
1762814996 },
1762914997 },
1763014998 .{
17631 .name = "ctz",
17632 .opcode = 152,
14999 .name = "half_sin",
15000 .opcode = 78,
1763315001 .operands = &.{
1763415002 .{ .kind = .id_ref, .quantifier = .required },
1763515003 },
1763615004 },
1763715005 .{
17638 .name = "s_mad_hi",
17639 .opcode = 153,
15006 .name = "half_sqrt",
15007 .opcode = 79,
1764015008 .operands = &.{
1764115009 .{ .kind = .id_ref, .quantifier = .required },
17642 .{ .kind = .id_ref, .quantifier = .required },
17643 .{ .kind = .id_ref, .quantifier = .required },
1764415010 },
1764515011 },
1764615012 .{
17647 .name = "u_mad_sat",
17648 .opcode = 154,
15013 .name = "half_tan",
15014 .opcode = 80,
1764915015 .operands = &.{
1765015016 .{ .kind = .id_ref, .quantifier = .required },
17651 .{ .kind = .id_ref, .quantifier = .required },
17652 .{ .kind = .id_ref, .quantifier = .required },
1765315017 },
1765415018 },
1765515019 .{
17656 .name = "s_mad_sat",
17657 .opcode = 155,
15020 .name = "native_cos",
15021 .opcode = 81,
1765815022 .operands = &.{
1765915023 .{ .kind = .id_ref, .quantifier = .required },
17660 .{ .kind = .id_ref, .quantifier = .required },
17661 .{ .kind = .id_ref, .quantifier = .required },
1766215024 },
1766315025 },
1766415026 .{
17665 .name = "s_max",
17666 .opcode = 156,
15027 .name = "native_divide",
15028 .opcode = 82,
1766715029 .operands = &.{
1766815030 .{ .kind = .id_ref, .quantifier = .required },
1766915031 .{ .kind = .id_ref, .quantifier = .required },
1767015032 },
1767115033 },
1767215034 .{
17673 .name = "u_max",
17674 .opcode = 157,
15035 .name = "native_exp",
15036 .opcode = 83,
1767515037 .operands = &.{
1767615038 .{ .kind = .id_ref, .quantifier = .required },
17677 .{ .kind = .id_ref, .quantifier = .required },
1767815039 },
1767915040 },
1768015041 .{
17681 .name = "s_min",
17682 .opcode = 158,
15042 .name = "native_exp2",
15043 .opcode = 84,
1768315044 .operands = &.{
1768415045 .{ .kind = .id_ref, .quantifier = .required },
17685 .{ .kind = .id_ref, .quantifier = .required },
1768615046 },
1768715047 },
1768815048 .{
17689 .name = "u_min",
17690 .opcode = 159,
15049 .name = "native_exp10",
15050 .opcode = 85,
1769115051 .operands = &.{
1769215052 .{ .kind = .id_ref, .quantifier = .required },
17693 .{ .kind = .id_ref, .quantifier = .required },
1769415053 },
1769515054 },
1769615055 .{
17697 .name = "s_mul_hi",
17698 .opcode = 160,
15056 .name = "native_log",
15057 .opcode = 86,
1769915058 .operands = &.{
1770015059 .{ .kind = .id_ref, .quantifier = .required },
17701 .{ .kind = .id_ref, .quantifier = .required },
1770215060 },
1770315061 },
1770415062 .{
17705 .name = "rotate",
17706 .opcode = 161,
15063 .name = "native_log2",
15064 .opcode = 87,
1770715065 .operands = &.{
1770815066 .{ .kind = .id_ref, .quantifier = .required },
17709 .{ .kind = .id_ref, .quantifier = .required },
1771015067 },
1771115068 },
1771215069 .{
17713 .name = "s_sub_sat",
17714 .opcode = 162,
15070 .name = "native_log10",
15071 .opcode = 88,
1771515072 .operands = &.{
1771615073 .{ .kind = .id_ref, .quantifier = .required },
17717 .{ .kind = .id_ref, .quantifier = .required },
1771815074 },
1771915075 },
1772015076 .{
17721 .name = "u_sub_sat",
17722 .opcode = 163,
15077 .name = "native_powr",
15078 .opcode = 89,
1772315079 .operands = &.{
1772415080 .{ .kind = .id_ref, .quantifier = .required },
1772515081 .{ .kind = .id_ref, .quantifier = .required },
1772615082 },
1772715083 },
1772815084 .{
17729 .name = "u_upsample",
17730 .opcode = 164,
15085 .name = "native_recip",
15086 .opcode = 90,
1773115087 .operands = &.{
1773215088 .{ .kind = .id_ref, .quantifier = .required },
17733 .{ .kind = .id_ref, .quantifier = .required },
1773415089 },
1773515090 },
1773615091 .{
17737 .name = "s_upsample",
17738 .opcode = 165,
15092 .name = "native_rsqrt",
15093 .opcode = 91,
1773915094 .operands = &.{
1774015095 .{ .kind = .id_ref, .quantifier = .required },
17741 .{ .kind = .id_ref, .quantifier = .required },
1774215096 },
1774315097 },
1774415098 .{
17745 .name = "popcount",
17746 .opcode = 166,
15099 .name = "native_sin",
15100 .opcode = 92,
1774715101 .operands = &.{
1774815102 .{ .kind = .id_ref, .quantifier = .required },
1774915103 },
1775015104 },
1775115105 .{
17752 .name = "s_mad24",
17753 .opcode = 167,
15106 .name = "native_sqrt",
15107 .opcode = 93,
1775415108 .operands = &.{
1775515109 .{ .kind = .id_ref, .quantifier = .required },
17756 .{ .kind = .id_ref, .quantifier = .required },
17757 .{ .kind = .id_ref, .quantifier = .required },
1775815110 },
1775915111 },
1776015112 .{
17761 .name = "u_mad24",
17762 .opcode = 168,
15113 .name = "native_tan",
15114 .opcode = 94,
1776315115 .operands = &.{
1776415116 .{ .kind = .id_ref, .quantifier = .required },
17765 .{ .kind = .id_ref, .quantifier = .required },
17766 .{ .kind = .id_ref, .quantifier = .required },
1776715117 },
1776815118 },
1776915119 .{
17770 .name = "s_mul24",
17771 .opcode = 169,
15120 .name = "fclamp",
15121 .opcode = 95,
1777215122 .operands = &.{
1777315123 .{ .kind = .id_ref, .quantifier = .required },
1777415124 .{ .kind = .id_ref, .quantifier = .required },
15125 .{ .kind = .id_ref, .quantifier = .required },
1777515126 },
1777615127 },
1777715128 .{
17778 .name = "u_mul24",
17779 .opcode = 170,
15129 .name = "degrees",
15130 .opcode = 96,
1778015131 .operands = &.{
1778115132 .{ .kind = .id_ref, .quantifier = .required },
17782 .{ .kind = .id_ref, .quantifier = .required },
1778315133 },
1778415134 },
1778515135 .{
17786 .name = "vloadn",
17787 .opcode = 171,
15136 .name = "fmax_common",
15137 .opcode = 97,
1778815138 .operands = &.{
1778915139 .{ .kind = .id_ref, .quantifier = .required },
1779015140 .{ .kind = .id_ref, .quantifier = .required },
17791 .{ .kind = .literal_integer, .quantifier = .required },
1779215141 },
1779315142 },
1779415143 .{
17795 .name = "vstoren",
17796 .opcode = 172,
15144 .name = "fmin_common",
15145 .opcode = 98,
1779715146 .operands = &.{
1779815147 .{ .kind = .id_ref, .quantifier = .required },
1779915148 .{ .kind = .id_ref, .quantifier = .required },
17800 .{ .kind = .id_ref, .quantifier = .required },
1780115149 },
1780215150 },
1780315151 .{
17804 .name = "vload_half",
17805 .opcode = 173,
15152 .name = "mix",
15153 .opcode = 99,
1780615154 .operands = &.{
1780715155 .{ .kind = .id_ref, .quantifier = .required },
1780815156 .{ .kind = .id_ref, .quantifier = .required },
15157 .{ .kind = .id_ref, .quantifier = .required },
1780915158 },
1781015159 },
1781115160 .{
17812 .name = "vload_halfn",
17813 .opcode = 174,
15161 .name = "radians",
15162 .opcode = 100,
1781415163 .operands = &.{
1781515164 .{ .kind = .id_ref, .quantifier = .required },
17816 .{ .kind = .id_ref, .quantifier = .required },
17817 .{ .kind = .literal_integer, .quantifier = .required },
1781815165 },
1781915166 },
1782015167 .{
17821 .name = "vstore_half",
17822 .opcode = 175,
15168 .name = "step",
15169 .opcode = 101,
1782315170 .operands = &.{
1782415171 .{ .kind = .id_ref, .quantifier = .required },
1782515172 .{ .kind = .id_ref, .quantifier = .required },
17826 .{ .kind = .id_ref, .quantifier = .required },
1782715173 },
1782815174 },
1782915175 .{
17830 .name = "vstore_half_r",
17831 .opcode = 176,
15176 .name = "smoothstep",
15177 .opcode = 102,
1783215178 .operands = &.{
1783315179 .{ .kind = .id_ref, .quantifier = .required },
1783415180 .{ .kind = .id_ref, .quantifier = .required },
1783515181 .{ .kind = .id_ref, .quantifier = .required },
17836 .{ .kind = .fp_rounding_mode, .quantifier = .required },
1783715182 },
1783815183 },
1783915184 .{
17840 .name = "vstore_halfn",
17841 .opcode = 177,
15185 .name = "sign",
15186 .opcode = 103,
1784215187 .operands = &.{
1784315188 .{ .kind = .id_ref, .quantifier = .required },
17844 .{ .kind = .id_ref, .quantifier = .required },
17845 .{ .kind = .id_ref, .quantifier = .required },
1784615189 },
1784715190 },
1784815191 .{
17849 .name = "vstore_halfn_r",
17850 .opcode = 178,
15192 .name = "cross",
15193 .opcode = 104,
1785115194 .operands = &.{
1785215195 .{ .kind = .id_ref, .quantifier = .required },
1785315196 .{ .kind = .id_ref, .quantifier = .required },
17854 .{ .kind = .id_ref, .quantifier = .required },
17855 .{ .kind = .fp_rounding_mode, .quantifier = .required },
1785615197 },
1785715198 },
1785815199 .{
17859 .name = "vloada_halfn",
17860 .opcode = 179,
15200 .name = "distance",
15201 .opcode = 105,
1786115202 .operands = &.{
1786215203 .{ .kind = .id_ref, .quantifier = .required },
1786315204 .{ .kind = .id_ref, .quantifier = .required },
17864 .{ .kind = .literal_integer, .quantifier = .required },
1786515205 },
1786615206 },
1786715207 .{
17868 .name = "vstorea_halfn",
17869 .opcode = 180,
15208 .name = "length",
15209 .opcode = 106,
1787015210 .operands = &.{
1787115211 .{ .kind = .id_ref, .quantifier = .required },
17872 .{ .kind = .id_ref, .quantifier = .required },
17873 .{ .kind = .id_ref, .quantifier = .required },
1787415212 },
1787515213 },
1787615214 .{
17877 .name = "vstorea_halfn_r",
17878 .opcode = 181,
15215 .name = "normalize",
15216 .opcode = 107,
1787915217 .operands = &.{
1788015218 .{ .kind = .id_ref, .quantifier = .required },
17881 .{ .kind = .id_ref, .quantifier = .required },
17882 .{ .kind = .id_ref, .quantifier = .required },
17883 .{ .kind = .fp_rounding_mode, .quantifier = .required },
1788415219 },
1788515220 },
1788615221 .{
17887 .name = "shuffle",
17888 .opcode = 182,
15222 .name = "fast_distance",
15223 .opcode = 108,
1788915224 .operands = &.{
1789015225 .{ .kind = .id_ref, .quantifier = .required },
1789115226 .{ .kind = .id_ref, .quantifier = .required },
1789215227 },
1789315228 },
1789415229 .{
17895 .name = "shuffle2",
17896 .opcode = 183,
15230 .name = "fast_length",
15231 .opcode = 109,
1789715232 .operands = &.{
1789815233 .{ .kind = .id_ref, .quantifier = .required },
17899 .{ .kind = .id_ref, .quantifier = .required },
17900 .{ .kind = .id_ref, .quantifier = .required },
1790115234 },
1790215235 },
1790315236 .{
17904 .name = "printf",
17905 .opcode = 184,
15237 .name = "fast_normalize",
15238 .opcode = 110,
1790615239 .operands = &.{
1790715240 .{ .kind = .id_ref, .quantifier = .required },
17908 .{ .kind = .id_ref, .quantifier = .variadic },
1790915241 },
1791015242 },
1791115243 .{
17912 .name = "prefetch",
17913 .opcode = 185,
15244 .name = "s_abs",
15245 .opcode = 141,
1791415246 .operands = &.{
1791515247 .{ .kind = .id_ref, .quantifier = .required },
17916 .{ .kind = .id_ref, .quantifier = .required },
1791715248 },
1791815249 },
1791915250 .{
17920 .name = "bitselect",
17921 .opcode = 186,
15251 .name = "s_abs_diff",
15252 .opcode = 142,
1792215253 .operands = &.{
1792315254 .{ .kind = .id_ref, .quantifier = .required },
1792415255 .{ .kind = .id_ref, .quantifier = .required },
17925 .{ .kind = .id_ref, .quantifier = .required },
1792615256 },
1792715257 },
1792815258 .{
17929 .name = "select",
17930 .opcode = 187,
15259 .name = "s_add_sat",
15260 .opcode = 143,
1793115261 .operands = &.{
1793215262 .{ .kind = .id_ref, .quantifier = .required },
1793315263 .{ .kind = .id_ref, .quantifier = .required },
17934 .{ .kind = .id_ref, .quantifier = .required },
1793515264 },
1793615265 },
1793715266 .{
17938 .name = "u_abs",
17939 .opcode = 201,
15267 .name = "u_add_sat",
15268 .opcode = 144,
1794015269 .operands = &.{
1794115270 .{ .kind = .id_ref, .quantifier = .required },
15271 .{ .kind = .id_ref, .quantifier = .required },
1794215272 },
1794315273 },
1794415274 .{
17945 .name = "u_abs_diff",
17946 .opcode = 202,
15275 .name = "s_hadd",
15276 .opcode = 145,
1794715277 .operands = &.{
1794815278 .{ .kind = .id_ref, .quantifier = .required },
1794915279 .{ .kind = .id_ref, .quantifier = .required },
1795015280 },
1795115281 },
1795215282 .{
17953 .name = "u_mul_hi",
17954 .opcode = 203,
15283 .name = "u_hadd",
15284 .opcode = 146,
1795515285 .operands = &.{
1795615286 .{ .kind = .id_ref, .quantifier = .required },
1795715287 .{ .kind = .id_ref, .quantifier = .required },
1795815288 },
1795915289 },
1796015290 .{
17961 .name = "u_mad_hi",
17962 .opcode = 204,
15291 .name = "s_rhadd",
15292 .opcode = 147,
1796315293 .operands = &.{
1796415294 .{ .kind = .id_ref, .quantifier = .required },
1796515295 .{ .kind = .id_ref, .quantifier = .required },
17966 .{ .kind = .id_ref, .quantifier = .required },
1796715296 },
1796815297 },
17969 },
17970 .non_semantic_shader_debug_info_100 => &.{
17971 .{
17972 .name = "DebugInfoNone",
17973 .opcode = 0,
17974 .operands = &.{},
17975 },
1797615298 .{
17977 .name = "DebugCompilationUnit",
17978 .opcode = 1,
15299 .name = "u_rhadd",
15300 .opcode = 148,
1797915301 .operands = &.{
1798015302 .{ .kind = .id_ref, .quantifier = .required },
1798115303 .{ .kind = .id_ref, .quantifier = .required },
17982 .{ .kind = .id_ref, .quantifier = .required },
17983 .{ .kind = .id_ref, .quantifier = .required },
1798415304 },
1798515305 },
1798615306 .{
17987 .name = "DebugTypeBasic",
17988 .opcode = 2,
15307 .name = "s_clamp",
15308 .opcode = 149,
1798915309 .operands = &.{
1799015310 .{ .kind = .id_ref, .quantifier = .required },
1799115311 .{ .kind = .id_ref, .quantifier = .required },
1799215312 .{ .kind = .id_ref, .quantifier = .required },
17993 .{ .kind = .id_ref, .quantifier = .required },
1799415313 },
1799515314 },
1799615315 .{
17997 .name = "DebugTypePointer",
17998 .opcode = 3,
15316 .name = "u_clamp",
15317 .opcode = 150,
1799915318 .operands = &.{
1800015319 .{ .kind = .id_ref, .quantifier = .required },
1800115320 .{ .kind = .id_ref, .quantifier = .required },
......@@ -18003,216 +15322,179 @@ pub const InstructionSet = enum {
1800315322 },
1800415323 },
1800515324 .{
18006 .name = "DebugTypeQualifier",
18007 .opcode = 4,
15325 .name = "clz",
15326 .opcode = 151,
1800815327 .operands = &.{
1800915328 .{ .kind = .id_ref, .quantifier = .required },
18010 .{ .kind = .id_ref, .quantifier = .required },
1801115329 },
1801215330 },
1801315331 .{
18014 .name = "DebugTypeArray",
18015 .opcode = 5,
15332 .name = "ctz",
15333 .opcode = 152,
1801615334 .operands = &.{
1801715335 .{ .kind = .id_ref, .quantifier = .required },
18018 .{ .kind = .id_ref, .quantifier = .variadic },
1801915336 },
1802015337 },
1802115338 .{
18022 .name = "DebugTypeVector",
18023 .opcode = 6,
15339 .name = "s_mad_hi",
15340 .opcode = 153,
1802415341 .operands = &.{
1802515342 .{ .kind = .id_ref, .quantifier = .required },
1802615343 .{ .kind = .id_ref, .quantifier = .required },
15344 .{ .kind = .id_ref, .quantifier = .required },
1802715345 },
1802815346 },
1802915347 .{
18030 .name = "DebugTypedef",
18031 .opcode = 7,
15348 .name = "u_mad_sat",
15349 .opcode = 154,
1803215350 .operands = &.{
1803315351 .{ .kind = .id_ref, .quantifier = .required },
1803415352 .{ .kind = .id_ref, .quantifier = .required },
1803515353 .{ .kind = .id_ref, .quantifier = .required },
15354 },
15355 },
15356 .{
15357 .name = "s_mad_sat",
15358 .opcode = 155,
15359 .operands = &.{
1803615360 .{ .kind = .id_ref, .quantifier = .required },
1803715361 .{ .kind = .id_ref, .quantifier = .required },
1803815362 .{ .kind = .id_ref, .quantifier = .required },
1803915363 },
1804015364 },
1804115365 .{
18042 .name = "DebugTypeFunction",
18043 .opcode = 8,
15366 .name = "s_max",
15367 .opcode = 156,
1804415368 .operands = &.{
1804515369 .{ .kind = .id_ref, .quantifier = .required },
1804615370 .{ .kind = .id_ref, .quantifier = .required },
18047 .{ .kind = .id_ref, .quantifier = .variadic },
1804815371 },
1804915372 },
1805015373 .{
18051 .name = "DebugTypeEnum",
18052 .opcode = 9,
15374 .name = "u_max",
15375 .opcode = 157,
1805315376 .operands = &.{
1805415377 .{ .kind = .id_ref, .quantifier = .required },
1805515378 .{ .kind = .id_ref, .quantifier = .required },
18056 .{ .kind = .id_ref, .quantifier = .required },
18057 .{ .kind = .id_ref, .quantifier = .required },
18058 .{ .kind = .id_ref, .quantifier = .required },
18059 .{ .kind = .id_ref, .quantifier = .required },
18060 .{ .kind = .id_ref, .quantifier = .required },
18061 .{ .kind = .id_ref, .quantifier = .required },
18062 .{ .kind = .pair_id_ref_id_ref, .quantifier = .variadic },
1806315379 },
1806415380 },
1806515381 .{
18066 .name = "DebugTypeComposite",
18067 .opcode = 10,
15382 .name = "s_min",
15383 .opcode = 158,
1806815384 .operands = &.{
1806915385 .{ .kind = .id_ref, .quantifier = .required },
1807015386 .{ .kind = .id_ref, .quantifier = .required },
18071 .{ .kind = .id_ref, .quantifier = .required },
18072 .{ .kind = .id_ref, .quantifier = .required },
18073 .{ .kind = .id_ref, .quantifier = .required },
18074 .{ .kind = .id_ref, .quantifier = .required },
18075 .{ .kind = .id_ref, .quantifier = .required },
18076 .{ .kind = .id_ref, .quantifier = .required },
18077 .{ .kind = .id_ref, .quantifier = .required },
18078 .{ .kind = .id_ref, .quantifier = .variadic },
1807915387 },
1808015388 },
1808115389 .{
18082 .name = "DebugTypeMember",
18083 .opcode = 11,
15390 .name = "u_min",
15391 .opcode = 159,
1808415392 .operands = &.{
1808515393 .{ .kind = .id_ref, .quantifier = .required },
1808615394 .{ .kind = .id_ref, .quantifier = .required },
18087 .{ .kind = .id_ref, .quantifier = .required },
18088 .{ .kind = .id_ref, .quantifier = .required },
18089 .{ .kind = .id_ref, .quantifier = .required },
18090 .{ .kind = .id_ref, .quantifier = .required },
18091 .{ .kind = .id_ref, .quantifier = .required },
18092 .{ .kind = .id_ref, .quantifier = .required },
18093 .{ .kind = .id_ref, .quantifier = .optional },
1809415395 },
1809515396 },
1809615397 .{
18097 .name = "DebugTypeInheritance",
18098 .opcode = 12,
15398 .name = "s_mul_hi",
15399 .opcode = 160,
1809915400 .operands = &.{
1810015401 .{ .kind = .id_ref, .quantifier = .required },
1810115402 .{ .kind = .id_ref, .quantifier = .required },
18102 .{ .kind = .id_ref, .quantifier = .required },
18103 .{ .kind = .id_ref, .quantifier = .required },
1810415403 },
1810515404 },
1810615405 .{
18107 .name = "DebugTypePtrToMember",
18108 .opcode = 13,
15406 .name = "rotate",
15407 .opcode = 161,
1810915408 .operands = &.{
1811015409 .{ .kind = .id_ref, .quantifier = .required },
1811115410 .{ .kind = .id_ref, .quantifier = .required },
1811215411 },
1811315412 },
1811415413 .{
18115 .name = "DebugTypeTemplate",
18116 .opcode = 14,
15414 .name = "s_sub_sat",
15415 .opcode = 162,
1811715416 .operands = &.{
1811815417 .{ .kind = .id_ref, .quantifier = .required },
18119 .{ .kind = .id_ref, .quantifier = .variadic },
15418 .{ .kind = .id_ref, .quantifier = .required },
1812015419 },
1812115420 },
1812215421 .{
18123 .name = "DebugTypeTemplateParameter",
18124 .opcode = 15,
15422 .name = "u_sub_sat",
15423 .opcode = 163,
1812515424 .operands = &.{
1812615425 .{ .kind = .id_ref, .quantifier = .required },
1812715426 .{ .kind = .id_ref, .quantifier = .required },
18128 .{ .kind = .id_ref, .quantifier = .required },
18129 .{ .kind = .id_ref, .quantifier = .required },
18130 .{ .kind = .id_ref, .quantifier = .required },
18131 .{ .kind = .id_ref, .quantifier = .required },
1813215427 },
1813315428 },
1813415429 .{
18135 .name = "DebugTypeTemplateTemplateParameter",
18136 .opcode = 16,
15430 .name = "u_upsample",
15431 .opcode = 164,
1813715432 .operands = &.{
1813815433 .{ .kind = .id_ref, .quantifier = .required },
1813915434 .{ .kind = .id_ref, .quantifier = .required },
18140 .{ .kind = .id_ref, .quantifier = .required },
18141 .{ .kind = .id_ref, .quantifier = .required },
18142 .{ .kind = .id_ref, .quantifier = .required },
1814315435 },
1814415436 },
1814515437 .{
18146 .name = "DebugTypeTemplateParameterPack",
18147 .opcode = 17,
15438 .name = "s_upsample",
15439 .opcode = 165,
1814815440 .operands = &.{
1814915441 .{ .kind = .id_ref, .quantifier = .required },
1815015442 .{ .kind = .id_ref, .quantifier = .required },
18151 .{ .kind = .id_ref, .quantifier = .required },
18152 .{ .kind = .id_ref, .quantifier = .required },
18153 .{ .kind = .id_ref, .quantifier = .variadic },
1815415443 },
1815515444 },
1815615445 .{
18157 .name = "DebugGlobalVariable",
18158 .opcode = 18,
15446 .name = "popcount",
15447 .opcode = 166,
1815915448 .operands = &.{
1816015449 .{ .kind = .id_ref, .quantifier = .required },
18161 .{ .kind = .id_ref, .quantifier = .required },
18162 .{ .kind = .id_ref, .quantifier = .required },
18163 .{ .kind = .id_ref, .quantifier = .required },
18164 .{ .kind = .id_ref, .quantifier = .required },
18165 .{ .kind = .id_ref, .quantifier = .required },
18166 .{ .kind = .id_ref, .quantifier = .required },
18167 .{ .kind = .id_ref, .quantifier = .required },
18168 .{ .kind = .id_ref, .quantifier = .required },
18169 .{ .kind = .id_ref, .quantifier = .optional },
1817015450 },
1817115451 },
1817215452 .{
18173 .name = "DebugFunctionDeclaration",
18174 .opcode = 19,
15453 .name = "s_mad24",
15454 .opcode = 167,
1817515455 .operands = &.{
1817615456 .{ .kind = .id_ref, .quantifier = .required },
1817715457 .{ .kind = .id_ref, .quantifier = .required },
1817815458 .{ .kind = .id_ref, .quantifier = .required },
18179 .{ .kind = .id_ref, .quantifier = .required },
18180 .{ .kind = .id_ref, .quantifier = .required },
18181 .{ .kind = .id_ref, .quantifier = .required },
18182 .{ .kind = .id_ref, .quantifier = .required },
18183 .{ .kind = .id_ref, .quantifier = .required },
1818415459 },
1818515460 },
1818615461 .{
18187 .name = "DebugFunction",
18188 .opcode = 20,
15462 .name = "u_mad24",
15463 .opcode = 168,
1818915464 .operands = &.{
1819015465 .{ .kind = .id_ref, .quantifier = .required },
1819115466 .{ .kind = .id_ref, .quantifier = .required },
1819215467 .{ .kind = .id_ref, .quantifier = .required },
15468 },
15469 },
15470 .{
15471 .name = "s_mul24",
15472 .opcode = 169,
15473 .operands = &.{
1819315474 .{ .kind = .id_ref, .quantifier = .required },
1819415475 .{ .kind = .id_ref, .quantifier = .required },
18195 .{ .kind = .id_ref, .quantifier = .required },
18196 .{ .kind = .id_ref, .quantifier = .required },
18197 .{ .kind = .id_ref, .quantifier = .required },
18198 .{ .kind = .id_ref, .quantifier = .required },
18199 .{ .kind = .id_ref, .quantifier = .optional },
1820015476 },
1820115477 },
1820215478 .{
18203 .name = "DebugLexicalBlock",
18204 .opcode = 21,
15479 .name = "u_mul24",
15480 .opcode = 170,
1820515481 .operands = &.{
1820615482 .{ .kind = .id_ref, .quantifier = .required },
1820715483 .{ .kind = .id_ref, .quantifier = .required },
15484 },
15485 },
15486 .{
15487 .name = "vloadn",
15488 .opcode = 171,
15489 .operands = &.{
1820815490 .{ .kind = .id_ref, .quantifier = .required },
1820915491 .{ .kind = .id_ref, .quantifier = .required },
18210 .{ .kind = .id_ref, .quantifier = .optional },
15492 .{ .kind = .literal_integer, .quantifier = .required },
1821115493 },
1821215494 },
1821315495 .{
18214 .name = "DebugLexicalBlockDiscriminator",
18215 .opcode = 22,
15496 .name = "vstoren",
15497 .opcode = 172,
1821615498 .operands = &.{
1821715499 .{ .kind = .id_ref, .quantifier = .required },
1821815500 .{ .kind = .id_ref, .quantifier = .required },
......@@ -18220,183 +15502,165 @@ pub const InstructionSet = enum {
1822015502 },
1822115503 },
1822215504 .{
18223 .name = "DebugScope",
18224 .opcode = 23,
15505 .name = "vload_half",
15506 .opcode = 173,
1822515507 .operands = &.{
1822615508 .{ .kind = .id_ref, .quantifier = .required },
18227 .{ .kind = .id_ref, .quantifier = .optional },
15509 .{ .kind = .id_ref, .quantifier = .required },
1822815510 },
1822915511 },
1823015512 .{
18231 .name = "DebugNoScope",
18232 .opcode = 24,
18233 .operands = &.{},
18234 },
18235 .{
18236 .name = "DebugInlinedAt",
18237 .opcode = 25,
15513 .name = "vload_halfn",
15514 .opcode = 174,
1823815515 .operands = &.{
1823915516 .{ .kind = .id_ref, .quantifier = .required },
1824015517 .{ .kind = .id_ref, .quantifier = .required },
18241 .{ .kind = .id_ref, .quantifier = .optional },
15518 .{ .kind = .literal_integer, .quantifier = .required },
1824215519 },
1824315520 },
1824415521 .{
18245 .name = "DebugLocalVariable",
18246 .opcode = 26,
15522 .name = "vstore_half",
15523 .opcode = 175,
1824715524 .operands = &.{
1824815525 .{ .kind = .id_ref, .quantifier = .required },
1824915526 .{ .kind = .id_ref, .quantifier = .required },
1825015527 .{ .kind = .id_ref, .quantifier = .required },
18251 .{ .kind = .id_ref, .quantifier = .required },
18252 .{ .kind = .id_ref, .quantifier = .required },
18253 .{ .kind = .id_ref, .quantifier = .required },
18254 .{ .kind = .id_ref, .quantifier = .required },
18255 .{ .kind = .id_ref, .quantifier = .optional },
1825615528 },
1825715529 },
1825815530 .{
18259 .name = "DebugInlinedVariable",
18260 .opcode = 27,
15531 .name = "vstore_half_r",
15532 .opcode = 176,
1826115533 .operands = &.{
1826215534 .{ .kind = .id_ref, .quantifier = .required },
1826315535 .{ .kind = .id_ref, .quantifier = .required },
15536 .{ .kind = .id_ref, .quantifier = .required },
15537 .{ .kind = .fp_rounding_mode, .quantifier = .required },
1826415538 },
1826515539 },
1826615540 .{
18267 .name = "DebugDeclare",
18268 .opcode = 28,
15541 .name = "vstore_halfn",
15542 .opcode = 177,
1826915543 .operands = &.{
1827015544 .{ .kind = .id_ref, .quantifier = .required },
1827115545 .{ .kind = .id_ref, .quantifier = .required },
1827215546 .{ .kind = .id_ref, .quantifier = .required },
18273 .{ .kind = .id_ref, .quantifier = .variadic },
1827415547 },
1827515548 },
1827615549 .{
18277 .name = "DebugValue",
18278 .opcode = 29,
15550 .name = "vstore_halfn_r",
15551 .opcode = 178,
1827915552 .operands = &.{
1828015553 .{ .kind = .id_ref, .quantifier = .required },
1828115554 .{ .kind = .id_ref, .quantifier = .required },
1828215555 .{ .kind = .id_ref, .quantifier = .required },
18283 .{ .kind = .id_ref, .quantifier = .variadic },
15556 .{ .kind = .fp_rounding_mode, .quantifier = .required },
1828415557 },
1828515558 },
1828615559 .{
18287 .name = "DebugOperation",
18288 .opcode = 30,
15560 .name = "vloada_halfn",
15561 .opcode = 179,
1828915562 .operands = &.{
1829015563 .{ .kind = .id_ref, .quantifier = .required },
18291 .{ .kind = .id_ref, .quantifier = .variadic },
15564 .{ .kind = .id_ref, .quantifier = .required },
15565 .{ .kind = .literal_integer, .quantifier = .required },
1829215566 },
1829315567 },
1829415568 .{
18295 .name = "DebugExpression",
18296 .opcode = 31,
15569 .name = "vstorea_halfn",
15570 .opcode = 180,
1829715571 .operands = &.{
18298 .{ .kind = .id_ref, .quantifier = .variadic },
15572 .{ .kind = .id_ref, .quantifier = .required },
15573 .{ .kind = .id_ref, .quantifier = .required },
15574 .{ .kind = .id_ref, .quantifier = .required },
1829915575 },
1830015576 },
1830115577 .{
18302 .name = "DebugMacroDef",
18303 .opcode = 32,
15578 .name = "vstorea_halfn_r",
15579 .opcode = 181,
1830415580 .operands = &.{
1830515581 .{ .kind = .id_ref, .quantifier = .required },
1830615582 .{ .kind = .id_ref, .quantifier = .required },
1830715583 .{ .kind = .id_ref, .quantifier = .required },
18308 .{ .kind = .id_ref, .quantifier = .optional },
15584 .{ .kind = .fp_rounding_mode, .quantifier = .required },
1830915585 },
1831015586 },
1831115587 .{
18312 .name = "DebugMacroUndef",
18313 .opcode = 33,
15588 .name = "shuffle",
15589 .opcode = 182,
1831415590 .operands = &.{
1831515591 .{ .kind = .id_ref, .quantifier = .required },
1831615592 .{ .kind = .id_ref, .quantifier = .required },
18317 .{ .kind = .id_ref, .quantifier = .required },
1831815593 },
1831915594 },
1832015595 .{
18321 .name = "DebugImportedEntity",
18322 .opcode = 34,
15596 .name = "shuffle2",
15597 .opcode = 183,
1832315598 .operands = &.{
1832415599 .{ .kind = .id_ref, .quantifier = .required },
1832515600 .{ .kind = .id_ref, .quantifier = .required },
1832615601 .{ .kind = .id_ref, .quantifier = .required },
18327 .{ .kind = .id_ref, .quantifier = .required },
18328 .{ .kind = .id_ref, .quantifier = .required },
18329 .{ .kind = .id_ref, .quantifier = .required },
18330 .{ .kind = .id_ref, .quantifier = .required },
1833115602 },
1833215603 },
1833315604 .{
18334 .name = "DebugSource",
18335 .opcode = 35,
15605 .name = "printf",
15606 .opcode = 184,
1833615607 .operands = &.{
1833715608 .{ .kind = .id_ref, .quantifier = .required },
18338 .{ .kind = .id_ref, .quantifier = .optional },
15609 .{ .kind = .id_ref, .quantifier = .variadic },
1833915610 },
1834015611 },
1834115612 .{
18342 .name = "DebugFunctionDefinition",
18343 .opcode = 101,
15613 .name = "prefetch",
15614 .opcode = 185,
1834415615 .operands = &.{
1834515616 .{ .kind = .id_ref, .quantifier = .required },
1834615617 .{ .kind = .id_ref, .quantifier = .required },
1834715618 },
1834815619 },
1834915620 .{
18350 .name = "DebugSourceContinued",
18351 .opcode = 102,
15621 .name = "bitselect",
15622 .opcode = 186,
1835215623 .operands = &.{
1835315624 .{ .kind = .id_ref, .quantifier = .required },
15625 .{ .kind = .id_ref, .quantifier = .required },
15626 .{ .kind = .id_ref, .quantifier = .required },
1835415627 },
1835515628 },
1835615629 .{
18357 .name = "DebugLine",
18358 .opcode = 103,
15630 .name = "select",
15631 .opcode = 187,
1835915632 .operands = &.{
1836015633 .{ .kind = .id_ref, .quantifier = .required },
1836115634 .{ .kind = .id_ref, .quantifier = .required },
1836215635 .{ .kind = .id_ref, .quantifier = .required },
18363 .{ .kind = .id_ref, .quantifier = .required },
18364 .{ .kind = .id_ref, .quantifier = .required },
1836515636 },
1836615637 },
1836715638 .{
18368 .name = "DebugNoLine",
18369 .opcode = 104,
18370 .operands = &.{},
18371 },
18372 .{
18373 .name = "DebugBuildIdentifier",
18374 .opcode = 105,
15639 .name = "u_abs",
15640 .opcode = 201,
1837515641 .operands = &.{
1837615642 .{ .kind = .id_ref, .quantifier = .required },
18377 .{ .kind = .id_ref, .quantifier = .required },
1837815643 },
1837915644 },
1838015645 .{
18381 .name = "DebugStoragePath",
18382 .opcode = 106,
15646 .name = "u_abs_diff",
15647 .opcode = 202,
1838315648 .operands = &.{
1838415649 .{ .kind = .id_ref, .quantifier = .required },
15650 .{ .kind = .id_ref, .quantifier = .required },
1838515651 },
1838615652 },
1838715653 .{
18388 .name = "DebugEntryPoint",
18389 .opcode = 107,
15654 .name = "u_mul_hi",
15655 .opcode = 203,
1839015656 .operands = &.{
1839115657 .{ .kind = .id_ref, .quantifier = .required },
1839215658 .{ .kind = .id_ref, .quantifier = .required },
18393 .{ .kind = .id_ref, .quantifier = .required },
18394 .{ .kind = .id_ref, .quantifier = .required },
1839515659 },
1839615660 },
1839715661 .{
18398 .name = "DebugTypeMatrix",
18399 .opcode = 108,
15662 .name = "u_mad_hi",
15663 .opcode = 204,
1840015664 .operands = &.{
1840115665 .{ .kind = .id_ref, .quantifier = .required },
1840215666 .{ .kind = .id_ref, .quantifier = .required },
src/dev.zig+1
......@@ -191,6 +191,7 @@ pub const Env = enum {
191191 .spirv => switch (feature) {
192192 .spirv_backend,
193193 .spirv_linker,
194 .legalize,
194195 => true,
195196 else => Env.sema.supports(feature),
196197 },
src/link/SpirV.zig+117-75
......@@ -1,62 +1,36 @@
1//! SPIR-V Spec documentation: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html
2//! According to above documentation, a SPIR-V module has the following logical layout:
3//! Header.
4//! OpCapability instructions.
5//! OpExtension instructions.
6//! OpExtInstImport instructions.
7//! A single OpMemoryModel instruction.
8//! All entry points, declared with OpEntryPoint instructions.
9//! All execution-mode declarators; OpExecutionMode and OpExecutionModeId instructions.
10//! Debug instructions:
11//! - First, OpString, OpSourceExtension, OpSource, OpSourceContinued (no forward references).
12//! - OpName and OpMemberName instructions.
13//! - OpModuleProcessed instructions.
14//! All annotation (decoration) instructions.
15//! All type declaration instructions, constant instructions, global variable declarations, (preferably) OpUndef instructions.
16//! All function declarations without a body (extern functions presumably).
17//! All regular functions.
18
19// Because SPIR-V requires re-compilation anyway, and so hot swapping will not work
20// anyway, we simply generate all the code in flush. This keeps
21// things considerably simpler.
22
23const SpirV = @This();
24
251const std = @import("std");
262const Allocator = std.mem.Allocator;
3const Path = std.Build.Cache.Path;
274const assert = std.debug.assert;
285const log = std.log.scoped(.link);
29const Path = std.Build.Cache.Path;
306
317const Zcu = @import("../Zcu.zig");
328const InternPool = @import("../InternPool.zig");
339const Compilation = @import("../Compilation.zig");
3410const link = @import("../link.zig");
35const codegen = @import("../codegen/spirv.zig");
36const trace = @import("../tracy.zig").trace;
37const build_options = @import("build_options");
3811const Air = @import("../Air.zig");
3912const Type = @import("../Type.zig");
40const Value = @import("../Value.zig");
13const BinaryModule = @import("SpirV/BinaryModule.zig");
14const CodeGen = @import("../codegen/spirv/CodeGen.zig");
15const Module = @import("../codegen/spirv/Module.zig");
16const trace = @import("../tracy.zig").trace;
4117
42const SpvModule = @import("../codegen/spirv/Module.zig");
43const Section = @import("../codegen/spirv/Section.zig");
4418const spec = @import("../codegen/spirv/spec.zig");
4519const Id = spec.Id;
4620const Word = spec.Word;
4721
48const BinaryModule = @import("SpirV/BinaryModule.zig");
22const Linker = @This();
4923
5024base: link.File,
51
52object: codegen.Object,
25module: Module,
26cg: CodeGen,
5327
5428pub fn createEmpty(
5529 arena: Allocator,
5630 comp: *Compilation,
5731 emit: Path,
5832 options: link.File.OpenOptions,
59) !*SpirV {
33) !*Linker {
6034 const gpa = comp.gpa;
6135 const target = &comp.root_mod.resolved_target.result;
6236
......@@ -72,8 +46,8 @@ pub fn createEmpty(
7246 else => unreachable, // Caught by Compilation.Config.resolve.
7347 }
7448
75 const self = try arena.create(SpirV);
76 self.* = .{
49 const linker = try arena.create(Linker);
50 linker.* = .{
7751 .base = .{
7852 .tag = .spirv,
7953 .comp = comp,
......@@ -85,17 +59,30 @@ pub fn createEmpty(
8559 .file = null,
8660 .build_id = options.build_id,
8761 },
88 .object = codegen.Object.init(gpa, comp.getTarget()),
62 .module = .{
63 .gpa = gpa,
64 .arena = arena,
65 .zcu = comp.zcu.?,
66 },
67 .cg = .{
68 // These fields are populated in generate()
69 .pt = undefined,
70 .air = undefined,
71 .liveness = undefined,
72 .owner_nav = undefined,
73 .module = undefined,
74 .control_flow = .{ .structured = .{} },
75 .base_line = undefined,
76 },
8977 };
90 errdefer self.deinit();
78 errdefer linker.deinit();
9179
92 // TODO: read the file and keep valid parts instead of truncating
93 self.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
80 linker.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
9481 .truncate = true,
9582 .read = true,
9683 });
9784
98 return self;
85 return linker;
9986}
10087
10188pub fn open(
......@@ -103,27 +90,90 @@ pub fn open(
10390 comp: *Compilation,
10491 emit: Path,
10592 options: link.File.OpenOptions,
106) !*SpirV {
93) !*Linker {
10794 return createEmpty(arena, comp, emit, options);
10895}
10996
110pub fn deinit(self: *SpirV) void {
111 self.object.deinit();
97pub fn deinit(linker: *Linker) void {
98 linker.cg.deinit();
99 linker.module.deinit();
112100}
113101
114pub fn updateNav(self: *SpirV, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void {
115 if (build_options.skip_non_native) {
116 @panic("Attempted to compile for architecture that was disabled by build configuration");
117 }
102fn generate(
103 linker: *Linker,
104 pt: Zcu.PerThread,
105 nav_index: InternPool.Nav.Index,
106 air: Air,
107 liveness: Air.Liveness,
108 do_codegen: bool,
109) !void {
110 const zcu = pt.zcu;
111 const gpa = zcu.gpa;
112 const structured_cfg = zcu.navFileScope(nav_index).mod.?.structured_cfg;
113
114 linker.cg.control_flow.deinit(gpa);
115 linker.cg.args.clearRetainingCapacity();
116 linker.cg.inst_results.clearRetainingCapacity();
117 linker.cg.id_scratch.clearRetainingCapacity();
118 linker.cg.prologue.reset();
119 linker.cg.body.reset();
120
121 linker.cg = .{
122 .pt = pt,
123 .air = air,
124 .liveness = liveness,
125 .owner_nav = nav_index,
126 .module = &linker.module,
127 .control_flow = switch (structured_cfg) {
128 true => .{ .structured = .{} },
129 false => .{ .unstructured = .{} },
130 },
131 .base_line = zcu.navSrcLine(nav_index),
118132
133 .args = linker.cg.args,
134 .inst_results = linker.cg.inst_results,
135 .id_scratch = linker.cg.id_scratch,
136 .prologue = linker.cg.prologue,
137 .body = linker.cg.body,
138 };
139
140 linker.cg.genNav(do_codegen) catch |err| switch (err) {
141 error.CodegenFail => switch (zcu.codegenFailMsg(nav_index, linker.cg.error_msg.?)) {
142 error.CodegenFail => {},
143 error.OutOfMemory => |e| return e,
144 },
145 else => |other| {
146 // There might be an error that happened *after* linker.error_msg
147 // was already allocated, so be sure to free it.
148 if (linker.cg.error_msg) |error_msg| {
149 error_msg.deinit(gpa);
150 }
151
152 return other;
153 },
154 };
155}
156
157pub fn updateFunc(
158 linker: *Linker,
159 pt: Zcu.PerThread,
160 func_index: InternPool.Index,
161 air: *const Air,
162 liveness: *const ?Air.Liveness,
163) !void {
164 const nav = pt.zcu.funcInfo(func_index).owner_nav;
165 // TODO: Separate types for generating decls and functions?
166 try linker.generate(pt, nav, air.*, liveness.*.?, true);
167}
168
169pub fn updateNav(linker: *Linker, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void {
119170 const ip = &pt.zcu.intern_pool;
120171 log.debug("lowering nav {f}({d})", .{ ip.getNav(nav).fqn.fmt(ip), nav });
121
122 try self.object.updateNav(pt, nav);
172 try linker.generate(pt, nav, undefined, undefined, false);
123173}
124174
125175pub fn updateExports(
126 self: *SpirV,
176 linker: *Linker,
127177 pt: Zcu.PerThread,
128178 exported: Zcu.Exported,
129179 export_indices: []const Zcu.Export.Index,
......@@ -134,13 +184,13 @@ pub fn updateExports(
134184 .nav => |nav| nav,
135185 .uav => |uav| {
136186 _ = uav;
137 @panic("TODO: implement SpirV linker code for exporting a constant value");
187 @panic("TODO: implement Linker linker code for exporting a constant value");
138188 },
139189 };
140190 const nav_ty = ip.getNav(nav_index).typeOf(ip);
141191 const target = zcu.getTarget();
142192 if (ip.isFunctionType(nav_ty)) {
143 const spv_decl_index = try self.object.resolveNav(zcu, nav_index);
193 const spv_decl_index = try linker.module.resolveNav(ip, nav_index);
144194 const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);
145195 const exec_model: spec.ExecutionModel = switch (target.os.tag) {
146196 .vulkan, .opengl => switch (cc) {
......@@ -162,7 +212,7 @@ pub fn updateExports(
162212
163213 for (export_indices) |export_idx| {
164214 const exp = export_idx.ptr(zcu);
165 try self.object.spv.declareEntryPoint(
215 try linker.module.declareEntryPoint(
166216 spv_decl_index,
167217 exp.opts.name.toSlice(ip),
168218 exec_model,
......@@ -175,7 +225,7 @@ pub fn updateExports(
175225}
176226
177227pub fn flush(
178 self: *SpirV,
228 linker: *Linker,
179229 arena: Allocator,
180230 tid: Zcu.PerThread.Id,
181231 prog_node: std.Progress.Node,
......@@ -185,35 +235,29 @@ pub fn flush(
185235 // InternPool.
186236 _ = tid;
187237
188 if (build_options.skip_non_native) {
189 @panic("Attempted to compile for architecture that was disabled by build configuration");
190 }
191
192238 const tracy = trace(@src());
193239 defer tracy.end();
194240
195241 const sub_prog_node = prog_node.start("Flush Module", 0);
196242 defer sub_prog_node.end();
197243
198 const comp = self.base.comp;
199 const spv = &self.object.spv;
244 const comp = linker.base.comp;
200245 const diags = &comp.link_diags;
201246 const gpa = comp.gpa;
202247
203248 // We need to export the list of error names somewhere so that we can pretty-print them in the
204249 // executor. This is not really an important thing though, so we can just dump it in any old
205250 // nonsemantic instruction. For now, just put it in OpSourceExtension with a special name.
206 var error_info: std.io.Writer.Allocating = .init(self.object.gpa);
251 var error_info: std.io.Writer.Allocating = .init(linker.module.gpa);
207252 defer error_info.deinit();
208253
209254 error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory;
210 const ip = &self.base.comp.zcu.?.intern_pool;
255 const ip = &linker.base.comp.zcu.?.intern_pool;
211256 for (ip.global_error_set.getNamesFromMainThread()) |name| {
212257 // Errors can contain pretty much any character - to encode them in a string we must escape
213258 // them somehow. Easiest here is to use some established scheme, one which also preseves the
214259 // name if it contains no strange characters is nice for debugging. URI encoding fits the bill.
215260 // We're using : as separator, which is a reserved character.
216
217261 error_info.writer.writeByte(':') catch return error.OutOfMemory;
218262 std.Uri.Component.percentEncode(
219263 &error_info.writer,
......@@ -228,36 +272,34 @@ pub fn flush(
228272 }.isValidChar,
229273 ) catch return error.OutOfMemory;
230274 }
231 try spv.sections.debug_strings.emit(gpa, .OpSourceExtension, .{
275 try linker.module.sections.debug_strings.emit(gpa, .OpSourceExtension, .{
232276 .extension = error_info.getWritten(),
233277 });
234278
235 const module = try spv.finalize(arena);
279 const module = try linker.module.finalize(arena);
236280 errdefer arena.free(module);
237281
238 const linked_module = self.linkModule(arena, module, sub_prog_node) catch |err| switch (err) {
282 const linked_module = linker.linkModule(arena, module, sub_prog_node) catch |err| switch (err) {
239283 error.OutOfMemory => return error.OutOfMemory,
240284 else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}),
241285 };
242286
243 self.base.file.?.writeAll(std.mem.sliceAsBytes(linked_module)) catch |err|
287 linker.base.file.?.writeAll(std.mem.sliceAsBytes(linked_module)) catch |err|
244288 return diags.fail("failed to write: {s}", .{@errorName(err)});
245289}
246290
247fn linkModule(self: *SpirV, a: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {
248 _ = self;
291fn linkModule(linker: *Linker, arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {
292 _ = linker;
249293
250294 const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
251295 const prune_unused = @import("SpirV/prune_unused.zig");
252 const dedup = @import("SpirV/deduplicate.zig");
253296
254 var parser = try BinaryModule.Parser.init(a);
297 var parser = try BinaryModule.Parser.init(arena);
255298 defer parser.deinit();
256299 var binary = try parser.parse(module);
257300
258301 try lower_invocation_globals.run(&parser, &binary, progress);
259302 try prune_unused.run(&parser, &binary, progress);
260 try dedup.run(&parser, &binary, progress);
261303
262 return binary.finalize(a);
304 return binary.finalize(arena);
263305}
src/link/SpirV/deduplicate.zig deleted-553
......@@ -1,553 +0,0 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const log = std.log.scoped(.spirv_link);
4const assert = std.debug.assert;
5
6const BinaryModule = @import("BinaryModule.zig");
7const Section = @import("../../codegen/spirv/Section.zig");
8const spec = @import("../../codegen/spirv/spec.zig");
9const Opcode = spec.Opcode;
10const ResultId = spec.Id;
11const Word = spec.Word;
12
13fn canDeduplicate(opcode: Opcode) bool {
14 return switch (opcode) {
15 .OpTypeForwardPointer => false, // Don't need to handle these
16 .OpGroupDecorate, .OpGroupMemberDecorate => {
17 // These are deprecated, so don't bother supporting them for now.
18 return false;
19 },
20 // Debug decoration-style instructions
21 .OpName, .OpMemberName => true,
22 else => switch (opcode.class()) {
23 .type_declaration,
24 .constant_creation,
25 .annotation,
26 => true,
27 else => false,
28 },
29 };
30}
31
32const ModuleInfo = struct {
33 /// This models a type, decoration or constant instruction
34 /// and its dependencies.
35 const Entity = struct {
36 /// The type that this entity represents. This is just
37 /// the instruction opcode.
38 kind: Opcode,
39 /// The offset of this entity's operands, in
40 /// `binary.instructions`.
41 first_operand: u32,
42 /// The number of operands in this entity
43 num_operands: u16,
44 /// The (first_operand-relative) offset of the result-id,
45 /// or the entity that is affected by this entity if this entity
46 /// is a decoration.
47 result_id_index: u16,
48 /// The first decoration in `self.decorations`.
49 first_decoration: u32,
50
51 fn operands(self: Entity, binary: *const BinaryModule) []const Word {
52 return binary.instructions[self.first_operand..][0..self.num_operands];
53 }
54 };
55
56 /// Maps result-id to Entity's
57 entities: std.AutoArrayHashMapUnmanaged(ResultId, Entity),
58 /// A bit set that keeps track of which operands are result-ids.
59 /// Note: This also includes any result-id!
60 /// Because we need these values when recoding the module anyway,
61 /// it contains the status of ALL operands in the module.
62 operand_is_id: std.DynamicBitSetUnmanaged,
63 /// Store of decorations for each entity.
64 decorations: []const Entity,
65
66 pub fn parse(
67 arena: Allocator,
68 parser: *BinaryModule.Parser,
69 binary: BinaryModule,
70 ) !ModuleInfo {
71 var entities = std.AutoArrayHashMap(ResultId, Entity).init(arena);
72 var id_offsets = std.ArrayList(u16).init(arena);
73 var operand_is_id = try std.DynamicBitSetUnmanaged.initEmpty(arena, binary.instructions.len);
74 var decorations = std.MultiArrayList(struct { target_id: ResultId, entity: Entity }){};
75
76 var it = binary.iterateInstructions();
77 while (it.next()) |inst| {
78 id_offsets.items.len = 0;
79 try parser.parseInstructionResultIds(binary, inst, &id_offsets);
80
81 const first_operand_offset: u32 = @intCast(inst.offset + 1);
82 for (id_offsets.items) |offset| {
83 operand_is_id.set(first_operand_offset + offset);
84 }
85
86 if (!canDeduplicate(inst.opcode)) continue;
87
88 const result_id_index: u16 = switch (inst.opcode.class()) {
89 .type_declaration, .annotation, .debug => 0,
90 .constant_creation => 1,
91 else => unreachable,
92 };
93
94 const result_id: ResultId = @enumFromInt(inst.operands[id_offsets.items[result_id_index]]);
95 const entity = Entity{
96 .kind = inst.opcode,
97 .first_operand = first_operand_offset,
98 .num_operands = @intCast(inst.operands.len),
99 .result_id_index = result_id_index,
100 .first_decoration = undefined, // Filled in later
101 };
102
103 switch (inst.opcode.class()) {
104 .annotation, .debug => {
105 try decorations.append(arena, .{
106 .target_id = result_id,
107 .entity = entity,
108 });
109 },
110 .type_declaration, .constant_creation => {
111 const entry = try entities.getOrPut(result_id);
112 if (entry.found_existing) {
113 log.err("type or constant {f} has duplicate definition", .{result_id});
114 return error.DuplicateId;
115 }
116 entry.value_ptr.* = entity;
117 },
118 else => unreachable,
119 }
120 }
121
122 // Sort decorations by the index of the result-id in `entities.
123 // This ensures not only that the decorations of a particular reuslt-id
124 // are continuous, but the subsequences also appear in the same order as in `entities`.
125
126 const SortContext = struct {
127 entities: std.AutoArrayHashMapUnmanaged(ResultId, Entity),
128 ids: []const ResultId,
129
130 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
131 // If any index is not in the entities set, its because its not a
132 // deduplicatable result-id. Those should be considered largest and
133 // float to the end.
134 const entity_index_a = ctx.entities.getIndex(ctx.ids[a_index]) orelse return false;
135 const entity_index_b = ctx.entities.getIndex(ctx.ids[b_index]) orelse return true;
136
137 return entity_index_a < entity_index_b;
138 }
139 };
140
141 decorations.sort(SortContext{
142 .entities = entities.unmanaged,
143 .ids = decorations.items(.target_id),
144 });
145
146 // Now go through the decorations and add the offsets to the entities list.
147 var decoration_i: u32 = 0;
148 const target_ids = decorations.items(.target_id);
149 for (entities.keys(), entities.values()) |id, *entity| {
150 entity.first_decoration = decoration_i;
151
152 // Scan ahead to the next decoration
153 while (decoration_i < target_ids.len and target_ids[decoration_i] == id) {
154 decoration_i += 1;
155 }
156 }
157
158 return .{
159 .entities = entities.unmanaged,
160 .operand_is_id = operand_is_id,
161 // There may be unrelated decorations at the end, so make sure to
162 // slice those off.
163 .decorations = decorations.items(.entity)[0..decoration_i],
164 };
165 }
166
167 fn entityDecorationsByIndex(self: ModuleInfo, index: usize) []const Entity {
168 const values = self.entities.values();
169 const first_decoration = values[index].first_decoration;
170 if (index == values.len - 1) {
171 return self.decorations[first_decoration..];
172 } else {
173 const next_first_decoration = values[index + 1].first_decoration;
174 return self.decorations[first_decoration..next_first_decoration];
175 }
176 }
177};
178
179const EntityContext = struct {
180 a: Allocator,
181 ptr_map_a: std.AutoArrayHashMapUnmanaged(ResultId, void) = .empty,
182 ptr_map_b: std.AutoArrayHashMapUnmanaged(ResultId, void) = .empty,
183 info: *const ModuleInfo,
184 binary: *const BinaryModule,
185
186 fn deinit(self: *EntityContext) void {
187 self.ptr_map_a.deinit(self.a);
188 self.ptr_map_b.deinit(self.a);
189
190 self.* = undefined;
191 }
192
193 fn equalizeMapCapacity(self: *EntityContext) !void {
194 const cap = @max(self.ptr_map_a.capacity(), self.ptr_map_b.capacity());
195 try self.ptr_map_a.ensureTotalCapacity(self.a, cap);
196 try self.ptr_map_b.ensureTotalCapacity(self.a, cap);
197 }
198
199 fn hash(self: *EntityContext, id: ResultId) !u64 {
200 var hasher = std.hash.Wyhash.init(0);
201 self.ptr_map_a.clearRetainingCapacity();
202 try self.hashInner(&hasher, id);
203 return hasher.final();
204 }
205
206 fn hashInner(self: *EntityContext, hasher: *std.hash.Wyhash, id: ResultId) error{OutOfMemory}!void {
207 const index = self.info.entities.getIndex(id) orelse {
208 // Index unknown, the type or constant may depend on another result-id
209 // that couldn't be deduplicated and so it wasn't added to info.entities.
210 // In this case, just has the ID itself.
211 std.hash.autoHash(hasher, id);
212 return;
213 };
214
215 const entity = self.info.entities.values()[index];
216
217 // If the current pointer is recursive, don't immediately add it to the map. This is to ensure that
218 // if the current pointer is already recursive, it gets the same hash a pointer that points to the
219 // same child but has a different result-id.
220 if (entity.kind == .OpTypePointer) {
221 // This may be either a pointer that is forward-referenced in the future,
222 // or a forward reference to a pointer.
223 // Note: We use the **struct** here instead of the pointer itself, to avoid an edge case like this:
224 //
225 // A - C*'
226 // \
227 // C - C*'
228 // /
229 // B - C*"
230 //
231 // In this case, hashing A goes like
232 // A -> C*' -> C -> C*' recursion
233 // And hashing B goes like
234 // B -> C*" -> C -> C*' -> C -> C*' recursion
235 // The are several calls to ptrType in codegen that may C*' and C*" to be generated as separate
236 // types. This is not a problem for C itself though - this can only be generated through resolveType()
237 // and so ensures equality by Zig's type system. Technically the above problem is still present, but it
238 // would only be present in a structure such as
239 //
240 // A - C*' - C'
241 // \
242 // C*" - C - C*
243 // /
244 // B
245 //
246 // where there is a duplicate definition of struct C. Resolving this requires a much more time consuming
247 // algorithm though, and because we don't expect any correctness issues with it, we leave that for now.
248
249 // TODO: Do we need to mind the storage class here? Its going to be recursive regardless, right?
250 const struct_id: ResultId = @enumFromInt(entity.operands(self.binary)[2]);
251 const entry = try self.ptr_map_a.getOrPut(self.a, struct_id);
252 if (entry.found_existing) {
253 // Pointer already seen. Hash the index instead of recursing into its children.
254 std.hash.autoHash(hasher, entry.index);
255 return;
256 }
257 }
258
259 try self.hashEntity(hasher, entity);
260
261 // Process decorations.
262 const decorations = self.info.entityDecorationsByIndex(index);
263 for (decorations) |decoration| {
264 try self.hashEntity(hasher, decoration);
265 }
266
267 if (entity.kind == .OpTypePointer) {
268 const struct_id: ResultId = @enumFromInt(entity.operands(self.binary)[2]);
269 assert(self.ptr_map_a.swapRemove(struct_id));
270 }
271 }
272
273 fn hashEntity(self: *EntityContext, hasher: *std.hash.Wyhash, entity: ModuleInfo.Entity) !void {
274 std.hash.autoHash(hasher, entity.kind);
275 // Process operands
276 const operands = entity.operands(self.binary);
277 for (operands, 0..) |operand, i| {
278 if (i == entity.result_id_index) {
279 // Not relevant, skip...
280 continue;
281 } else if (self.info.operand_is_id.isSet(entity.first_operand + i)) {
282 // Operand is ID
283 try self.hashInner(hasher, @enumFromInt(operand));
284 } else {
285 // Operand is merely data
286 std.hash.autoHash(hasher, operand);
287 }
288 }
289 }
290
291 fn eql(self: *EntityContext, a: ResultId, b: ResultId) !bool {
292 self.ptr_map_a.clearRetainingCapacity();
293 self.ptr_map_b.clearRetainingCapacity();
294
295 return try self.eqlInner(a, b);
296 }
297
298 fn eqlInner(self: *EntityContext, id_a: ResultId, id_b: ResultId) error{OutOfMemory}!bool {
299 const maybe_index_a = self.info.entities.getIndex(id_a);
300 const maybe_index_b = self.info.entities.getIndex(id_b);
301
302 if (maybe_index_a == null and maybe_index_b == null) {
303 // Both indices unknown. In this case the type or constant
304 // may depend on another result-id that couldn't be deduplicated
305 // (so it wasn't added to info.entities). In this case, that particular
306 // result-id should be the same one.
307 return id_a == id_b;
308 }
309
310 const index_a = maybe_index_a orelse return false;
311 const index_b = maybe_index_b orelse return false;
312
313 const entity_a = self.info.entities.values()[index_a];
314 const entity_b = self.info.entities.values()[index_b];
315
316 if (entity_a.kind != entity_b.kind) {
317 return false;
318 }
319
320 if (entity_a.kind == .OpTypePointer) {
321 // May be a forward reference, or should be saved as a potential
322 // forward reference in the future. Whatever the case, it should
323 // be the same for both a and b.
324 const struct_id_a: ResultId = @enumFromInt(entity_a.operands(self.binary)[2]);
325 const struct_id_b: ResultId = @enumFromInt(entity_b.operands(self.binary)[2]);
326
327 const entry_a = try self.ptr_map_a.getOrPut(self.a, struct_id_a);
328 const entry_b = try self.ptr_map_b.getOrPut(self.a, struct_id_b);
329
330 if (entry_a.found_existing != entry_b.found_existing) return false;
331 if (entry_a.index != entry_b.index) return false;
332
333 if (entry_a.found_existing) {
334 // No need to recurse.
335 return true;
336 }
337 }
338
339 if (!try self.eqlEntities(entity_a, entity_b)) {
340 return false;
341 }
342
343 // Compare decorations.
344 const decorations_a = self.info.entityDecorationsByIndex(index_a);
345 const decorations_b = self.info.entityDecorationsByIndex(index_b);
346 if (decorations_a.len != decorations_b.len) {
347 return false;
348 }
349
350 for (decorations_a, decorations_b) |decoration_a, decoration_b| {
351 if (!try self.eqlEntities(decoration_a, decoration_b)) {
352 return false;
353 }
354 }
355
356 if (entity_a.kind == .OpTypePointer) {
357 const struct_id_a: ResultId = @enumFromInt(entity_a.operands(self.binary)[2]);
358 const struct_id_b: ResultId = @enumFromInt(entity_b.operands(self.binary)[2]);
359
360 assert(self.ptr_map_a.swapRemove(struct_id_a));
361 assert(self.ptr_map_b.swapRemove(struct_id_b));
362 }
363
364 return true;
365 }
366
367 fn eqlEntities(self: *EntityContext, entity_a: ModuleInfo.Entity, entity_b: ModuleInfo.Entity) !bool {
368 if (entity_a.kind != entity_b.kind) {
369 return false;
370 } else if (entity_a.result_id_index != entity_a.result_id_index) {
371 return false;
372 }
373
374 const operands_a = entity_a.operands(self.binary);
375 const operands_b = entity_b.operands(self.binary);
376
377 // Note: returns false for operands that have explicit defaults in optional operands... oh well
378 if (operands_a.len != operands_b.len) {
379 return false;
380 }
381
382 for (operands_a, operands_b, 0..) |operand_a, operand_b, i| {
383 const a_is_id = self.info.operand_is_id.isSet(entity_a.first_operand + i);
384 const b_is_id = self.info.operand_is_id.isSet(entity_b.first_operand + i);
385 if (a_is_id != b_is_id) {
386 return false;
387 } else if (i == entity_a.result_id_index) {
388 // result-id for both...
389 continue;
390 } else if (a_is_id) {
391 // Both are IDs, so recurse.
392 if (!try self.eqlInner(@enumFromInt(operand_a), @enumFromInt(operand_b))) {
393 return false;
394 }
395 } else if (operand_a != operand_b) {
396 return false;
397 }
398 }
399
400 return true;
401 }
402};
403
404/// This struct is a wrapper around EntityContext that adapts it for
405/// use in a hash map. Because EntityContext allocates, it cannot be
406/// used. This wrapper simply assumes that the maps have been allocated
407/// the max amount of memory they are going to use.
408/// This is done by pre-hashing all keys.
409const EntityHashContext = struct {
410 entity_context: *EntityContext,
411
412 pub fn hash(self: EntityHashContext, key: ResultId) u64 {
413 return self.entity_context.hash(key) catch unreachable;
414 }
415
416 pub fn eql(self: EntityHashContext, a: ResultId, b: ResultId) bool {
417 return self.entity_context.eql(a, b) catch unreachable;
418 }
419};
420
421pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Progress.Node) !void {
422 const sub_node = progress.start("deduplicate", 0);
423 defer sub_node.end();
424
425 var arena = std.heap.ArenaAllocator.init(parser.a);
426 defer arena.deinit();
427 const a = arena.allocator();
428
429 const info = try ModuleInfo.parse(a, parser, binary.*);
430
431 // Hash all keys once so that the maps can be allocated the right size.
432 var ctx = EntityContext{
433 .a = a,
434 .info = &info,
435 .binary = binary,
436 };
437
438 for (info.entities.keys()) |id| {
439 _ = try ctx.hash(id);
440 }
441
442 // hash only uses ptr_map_a, so allocate ptr_map_b too
443 try ctx.equalizeMapCapacity();
444
445 // Figure out which entities can be deduplicated.
446 var map = std.HashMap(ResultId, void, EntityHashContext, 80).initContext(a, .{
447 .entity_context = &ctx,
448 });
449 var replace = std.AutoArrayHashMap(ResultId, ResultId).init(a);
450 for (info.entities.keys()) |id| {
451 const entry = try map.getOrPut(id);
452 if (entry.found_existing) {
453 try replace.putNoClobber(id, entry.key_ptr.*);
454 }
455 }
456
457 sub_node.setEstimatedTotalItems(binary.instructions.len);
458
459 // Now process the module, and replace instructions where needed.
460 var section = Section{};
461 var it = binary.iterateInstructions();
462 var new_functions_section: ?usize = null;
463 var new_operands = std.ArrayList(u32).init(a);
464 var emitted_ptrs = std.AutoHashMap(ResultId, void).init(a);
465 while (it.next()) |inst| {
466 defer sub_node.setCompletedItems(inst.offset);
467
468 // Result-id can only be the first or second operand
469 const inst_spec = parser.getInstSpec(inst.opcode).?;
470
471 const maybe_result_id_offset: ?u16 = for (0..2) |i| {
472 if (inst_spec.operands.len > i and inst_spec.operands[i].kind == .id_result) {
473 break @intCast(i);
474 }
475 } else null;
476
477 if (maybe_result_id_offset) |offset| {
478 const result_id: ResultId = @enumFromInt(inst.operands[offset]);
479 if (replace.contains(result_id)) continue;
480 }
481
482 switch (inst.opcode) {
483 .OpFunction => if (new_functions_section == null) {
484 new_functions_section = section.instructions.items.len;
485 },
486 .OpTypeForwardPointer => continue, // We re-emit these where needed
487 else => {},
488 }
489
490 switch (inst.opcode.class()) {
491 .annotation, .debug => {
492 // For decoration-style instructions, only emit them
493 // if the target is not removed.
494 const target: ResultId = @enumFromInt(inst.operands[0]);
495 if (replace.contains(target)) continue;
496 },
497 else => {},
498 }
499
500 // Re-emit the instruction, but replace all the IDs.
501
502 new_operands.items.len = 0;
503 try new_operands.appendSlice(inst.operands);
504
505 for (new_operands.items, 0..) |*operand, i| {
506 const is_id = info.operand_is_id.isSet(inst.offset + 1 + i);
507 if (!is_id) continue;
508
509 if (replace.get(@enumFromInt(operand.*))) |new_id| {
510 operand.* = @intFromEnum(new_id);
511 }
512
513 if (maybe_result_id_offset == null or maybe_result_id_offset.? != i) {
514 // Only emit forward pointers before type, constant, and global instructions.
515 // Debug and Annotation instructions don't need the forward pointer, and it
516 // messes up the logical layout of the module.
517 switch (inst.opcode.class()) {
518 .type_declaration, .constant_creation, .memory => {},
519 else => continue,
520 }
521
522 const id: ResultId = @enumFromInt(operand.*);
523 const index = info.entities.getIndex(id) orelse continue;
524 const entity = info.entities.values()[index];
525 if (entity.kind == .OpTypePointer and !emitted_ptrs.contains(id)) {
526 // Grab the pointer's storage class from its operands in the original
527 // module.
528 const storage_class: spec.StorageClass = @enumFromInt(entity.operands(binary)[1]);
529 try section.emit(a, .OpTypeForwardPointer, .{
530 .pointer_type = id,
531 .storage_class = storage_class,
532 });
533 try emitted_ptrs.put(id, {});
534 }
535 }
536 }
537
538 if (inst.opcode == .OpTypePointer) {
539 const result_id: ResultId = @enumFromInt(new_operands.items[maybe_result_id_offset.?]);
540 try emitted_ptrs.put(result_id, {});
541 }
542
543 try section.emitRawInstruction(a, inst.opcode, new_operands.items);
544 }
545
546 for (replace.keys()) |key| {
547 _ = binary.ext_inst_map.remove(key);
548 _ = binary.arith_type_width.remove(key);
549 }
550
551 binary.instructions = try parser.a.dupe(Word, section.toWords());
552 binary.sections.functions = new_functions_section orelse binary.instructions.len;
553}
test/behavior/packed-union.zig+1
......@@ -140,6 +140,7 @@ test "packed union initialized with a runtime value" {
140140 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
141141 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
142142 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
143 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
143144
144145 const Fields = packed struct {
145146 timestamp: u50,
test/behavior/slice.zig+2
......@@ -1036,6 +1036,8 @@ test "sentinel-terminated 0-length slices" {
10361036}
10371037
10381038test "peer slices keep abi alignment with empty struct" {
1039 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
1040
10391041 var cond: bool = undefined;
10401042 cond = false;
10411043 const slice = if (cond) &[1]u32{42} else &.{};
tools/gen_spirv_spec.zig+93-62
......@@ -12,6 +12,7 @@ const ExtendedStructSet = std.StringHashMap(void);
1212
1313const Extension = struct {
1414 name: []const u8,
15 opcode_name: []const u8,
1516 spec: ExtensionRegistry,
1617};
1718
......@@ -44,23 +45,11 @@ const OperandKindMap = std.ArrayHashMap(StringPair, OperandKind, StringPairConte
4445
4546/// Khronos made it so that these names are not defined explicitly, so
4647/// we need to hardcode it (like they did).
47/// See https://github.com/KhronosGroup/SPIRV-Registry/
48const set_names = std.StaticStringMap([]const u8).initComptime(.{
49 .{ "opencl.std.100", "OpenCL.std" },
50 .{ "glsl.std.450", "GLSL.std.450" },
51 .{ "opencl.debuginfo.100", "OpenCL.DebugInfo.100" },
52 .{ "spv-amd-shader-ballot", "SPV_AMD_shader_ballot" },
53 .{ "nonsemantic.shader.debuginfo.100", "NonSemantic.Shader.DebugInfo.100" },
54 .{ "nonsemantic.vkspreflection", "NonSemantic.VkspReflection" },
55 .{ "nonsemantic.clspvreflection", "NonSemantic.ClspvReflection.6" }, // This version needs to be handled manually
56 .{ "spv-amd-gcn-shader", "SPV_AMD_gcn_shader" },
57 .{ "spv-amd-shader-trinary-minmax", "SPV_AMD_shader_trinary_minmax" },
58 .{ "debuginfo", "DebugInfo" },
59 .{ "nonsemantic.debugprintf", "NonSemantic.DebugPrintf" },
60 .{ "spv-amd-shader-explicit-vertex-parameter", "SPV_AMD_shader_explicit_vertex_parameter" },
61 .{ "nonsemantic.debugbreak", "NonSemantic.DebugBreak" },
62 .{ "tosa.001000.1", "SPV_EXT_INST_TYPE_TOSA_001000_1" },
63 .{ "zig", "zig" },
48/// See https://github.com/KhronosGroup/SPIRV-Registry
49const set_names = std.StaticStringMap(struct { []const u8, []const u8 }).initComptime(.{
50 .{ "opencl.std.100", .{ "OpenCL.std", "OpenClOpcode" } },
51 .{ "glsl.std.450", .{ "GLSL.std.450", "GlslOpcode" } },
52 .{ "zig", .{ "zig", "Zig" } },
6453});
6554
6655var arena = std.heap.ArenaAllocator.init(std.heap.smp_allocator);
......@@ -78,7 +67,7 @@ pub fn main() !void {
7867 const dir = try std.fs.cwd().openDir(json_path, .{ .iterate = true });
7968
8069 const core_spec = try readRegistry(CoreRegistry, dir, "spirv.core.grammar.json");
81 std.sort.block(Instruction, core_spec.instructions, CmpInst{}, CmpInst.lt);
70 std.mem.sortUnstable(Instruction, core_spec.instructions, CmpInst{}, CmpInst.lt);
8271
8372 var exts = std.ArrayList(Extension).init(allocator);
8473
......@@ -134,14 +123,24 @@ fn readExtRegistry(exts: *std.ArrayList(Extension), dir: std.fs.Dir, sub_path: [
134123 const name = filename["extinst.".len .. filename.len - ".grammar.json".len];
135124 const spec = try readRegistry(ExtensionRegistry, dir, sub_path);
136125
126 const set_name = set_names.get(name) orelse {
127 std.log.info("ignored instruction set '{s}'", .{name});
128 return;
129 };
130
137131 std.sort.block(Instruction, spec.instructions, CmpInst{}, CmpInst.lt);
138132
139 try exts.append(.{ .name = set_names.get(name).?, .spec = spec });
133 try exts.append(.{
134 .name = set_name.@"0",
135 .opcode_name = set_name.@"1",
136 .spec = spec,
137 });
140138}
141139
142140fn readRegistry(comptime RegistryType: type, dir: std.fs.Dir, path: []const u8) !RegistryType {
143141 const spec = try dir.readFileAlloc(allocator, path, std.math.maxInt(usize));
144142 // Required for json parsing.
143 // TODO: ALI
145144 @setEvalBranchQuota(10000);
146145
147146 var scanner = std.json.Scanner.initCompleteInput(allocator, spec);
......@@ -191,7 +190,11 @@ fn tagPriorityScore(tag: []const u8) usize {
191190 }
192191}
193192
194fn render(writer: *std.io.Writer, registry: CoreRegistry, extensions: []const Extension) !void {
193fn render(
194 writer: *std.io.Writer,
195 registry: CoreRegistry,
196 extensions: []const Extension,
197) !void {
195198 try writer.writeAll(
196199 \\//! This file is auto-generated by tools/gen_spirv_spec.zig.
197200 \\
......@@ -221,6 +224,16 @@ fn render(writer: *std.io.Writer, registry: CoreRegistry, extensions: []const Ex
221224 \\ }
222225 \\};
223226 \\
227 \\pub const IdRange = struct {
228 \\ base: u32,
229 \\ len: u32,
230 \\
231 \\ pub fn at(range: IdRange, i: usize) Id {
232 \\ std.debug.assert(i < range.len);
233 \\ return @enumFromInt(range.base + i);
234 \\ }
235 \\};
236 \\
224237 \\pub const LiteralInteger = Word;
225238 \\pub const LiteralFloat = Word;
226239 \\pub const LiteralString = []const u8;
......@@ -307,13 +320,18 @@ fn render(writer: *std.io.Writer, registry: CoreRegistry, extensions: []const Ex
307320 // Note: extensions don't seem to have class.
308321 try renderClass(writer, registry.instructions);
309322 try renderOperandKind(writer, all_operand_kinds.values());
310 try renderOpcodes(writer, registry.instructions, extended_structs);
323
324 try renderOpcodes(writer, "Opcode", true, registry.instructions, extended_structs);
325 for (extensions) |ext| {
326 try renderOpcodes(writer, ext.opcode_name, false, ext.spec.instructions, extended_structs);
327 }
328
311329 try renderOperandKinds(writer, all_operand_kinds.values(), extended_structs);
312330 try renderInstructionSet(writer, registry, extensions, all_operand_kinds);
313331}
314332
315333fn renderInstructionSet(
316 writer: anytype,
334 writer: *std.io.Writer,
317335 core: CoreRegistry,
318336 extensions: []const Extension,
319337 all_operand_kinds: OperandKindMap,
......@@ -324,7 +342,7 @@ fn renderInstructionSet(
324342 );
325343
326344 for (extensions) |ext| {
327 try writer.print("{f},\n", .{formatId(ext.name)});
345 try writer.print("{f},\n", .{std.zig.fmtId(ext.name)});
328346 }
329347
330348 try writer.writeAll(
......@@ -348,7 +366,7 @@ fn renderInstructionSet(
348366}
349367
350368fn renderInstructionsCase(
351 writer: anytype,
369 writer: *std.io.Writer,
352370 set_name: []const u8,
353371 instructions: []const Instruction,
354372 all_operand_kinds: OperandKindMap,
......@@ -357,7 +375,7 @@ fn renderInstructionsCase(
357375 // but there aren't so many total aliases and that would add more overhead in total. We will
358376 // just filter those out when needed.
359377
360 try writer.print(".{f} => &.{{\n", .{formatId(set_name)});
378 try writer.print(".{f} => &.{{\n", .{std.zig.fmtId(set_name)});
361379
362380 for (instructions) |inst| {
363381 try writer.print(
......@@ -395,7 +413,7 @@ fn renderInstructionsCase(
395413 );
396414}
397415
398fn renderClass(writer: anytype, instructions: []const Instruction) !void {
416fn renderClass(writer: *std.io.Writer, instructions: []const Instruction) !void {
399417 var class_map = std.StringArrayHashMap(void).init(allocator);
400418
401419 for (instructions) |inst| {
......@@ -444,7 +462,7 @@ fn formatId(identifier: []const u8) std.fmt.Alt(Formatter, Formatter.format) {
444462 return .{ .data = .{ .data = identifier } };
445463}
446464
447fn renderOperandKind(writer: anytype, operands: []const OperandKind) !void {
465fn renderOperandKind(writer: *std.io.Writer, operands: []const OperandKind) !void {
448466 try writer.writeAll(
449467 \\pub const OperandKind = enum {
450468 \\ opcode,
......@@ -500,7 +518,7 @@ fn renderOperandKind(writer: anytype, operands: []const OperandKind) !void {
500518 try writer.writeAll("};\n}\n};\n");
501519}
502520
503fn renderEnumerant(writer: anytype, enumerant: Enumerant) !void {
521fn renderEnumerant(writer: *std.io.Writer, enumerant: Enumerant) !void {
504522 try writer.print(".{{.name = \"{s}\", .value = ", .{enumerant.enumerant});
505523 switch (enumerant.value) {
506524 .bitflag => |flag| try writer.writeAll(flag),
......@@ -517,7 +535,9 @@ fn renderEnumerant(writer: anytype, enumerant: Enumerant) !void {
517535}
518536
519537fn renderOpcodes(
520 writer: anytype,
538 writer: *std.io.Writer,
539 opcode_type_name: []const u8,
540 want_operands: bool,
521541 instructions: []const Instruction,
522542 extended_structs: ExtendedStructSet,
523543) !void {
......@@ -528,7 +548,9 @@ fn renderOpcodes(
528548 try aliases.ensureTotalCapacity(instructions.len);
529549
530550 for (instructions, 0..) |inst, i| {
531 if (std.mem.eql(u8, inst.class.?, "@exclude")) continue;
551 if (inst.class) |class| {
552 if (std.mem.eql(u8, class, "@exclude")) continue;
553 }
532554
533555 const result = inst_map.getOrPutAssumeCapacity(inst.opcode);
534556 if (!result.found_existing) {
......@@ -552,58 +574,67 @@ fn renderOpcodes(
552574
553575 const instructions_indices = inst_map.values();
554576
555 try writer.writeAll("pub const Opcode = enum(u16) {\n");
577 try writer.print("\npub const {f} = enum(u16) {{\n", .{std.zig.fmtId(opcode_type_name)});
556578 for (instructions_indices) |i| {
557579 const inst = instructions[i];
558580 try writer.print("{f} = {},\n", .{ std.zig.fmtId(inst.opname), inst.opcode });
559581 }
560582
561 try writer.writeAll(
562 \\
563 );
583 try writer.writeAll("\n");
564584
565585 for (aliases.items) |alias| {
566 try writer.print("pub const {f} = Opcode.{f};\n", .{
586 try writer.print("pub const {f} = {f}.{f};\n", .{
567587 formatId(instructions[alias.inst].opname),
588 std.zig.fmtId(opcode_type_name),
568589 formatId(instructions[alias.alias].opname),
569590 });
570591 }
571592
572 try writer.writeAll(
573 \\
574 \\pub fn Operands(comptime self: Opcode) type {
575 \\ return switch (self) {
576 \\
577 );
593 if (want_operands) {
594 try writer.print(
595 \\
596 \\pub fn Operands(comptime self: {f}) type {{
597 \\ return switch (self) {{
598 \\
599 , .{std.zig.fmtId(opcode_type_name)});
578600
579 for (instructions_indices) |i| {
580 const inst = instructions[i];
581 try renderOperand(writer, .instruction, inst.opname, inst.operands, extended_structs, false);
582 }
601 for (instructions_indices) |i| {
602 const inst = instructions[i];
603 try renderOperand(writer, .instruction, inst.opname, inst.operands, extended_structs, false);
604 }
583605
584 try writer.writeAll(
585 \\ };
586 \\}
587 \\pub fn class(self: Opcode) Class {
588 \\ return switch (self) {
589 \\
590 );
606 try writer.writeAll(
607 \\ };
608 \\}
609 \\
610 );
591611
592 for (instructions_indices) |i| {
593 const inst = instructions[i];
594 try writer.print(".{f} => .{f},\n", .{ std.zig.fmtId(inst.opname), formatId(inst.class.?) });
612 try writer.print(
613 \\pub fn class(self: {f}) Class {{
614 \\ return switch (self) {{
615 \\
616 , .{std.zig.fmtId(opcode_type_name)});
617
618 for (instructions_indices) |i| {
619 const inst = instructions[i];
620 try writer.print(".{f} => .{f},\n", .{ std.zig.fmtId(inst.opname), formatId(inst.class.?) });
621 }
622
623 try writer.writeAll(
624 \\ };
625 \\}
626 \\
627 );
595628 }
596629
597630 try writer.writeAll(
598 \\ };
599 \\}
600631 \\};
601632 \\
602633 );
603634}
604635
605636fn renderOperandKinds(
606 writer: anytype,
637 writer: *std.io.Writer,
607638 kinds: []const OperandKind,
608639 extended_structs: ExtendedStructSet,
609640) !void {
......@@ -617,7 +648,7 @@ fn renderOperandKinds(
617648}
618649
619650fn renderValueEnum(
620 writer: anytype,
651 writer: *std.io.Writer,
621652 enumeration: OperandKind,
622653 extended_structs: ExtendedStructSet,
623654) !void {
......@@ -695,7 +726,7 @@ fn renderValueEnum(
695726}
696727
697728fn renderBitEnum(
698 writer: anytype,
729 writer: *std.io.Writer,
699730 enumeration: OperandKind,
700731 extended_structs: ExtendedStructSet,
701732) !void {
......@@ -778,7 +809,7 @@ fn renderBitEnum(
778809}
779810
780811fn renderOperand(
781 writer: anytype,
812 writer: *std.io.Writer,
782813 kind: enum {
783814 @"union",
784815 instruction,
......@@ -862,7 +893,7 @@ fn renderOperand(
862893 try writer.writeAll(",\n");
863894}
864895
865fn renderFieldName(writer: anytype, operands: []const Operand, field_index: usize) !void {
896fn renderFieldName(writer: *std.io.Writer, operands: []const Operand, field_index: usize) !void {
866897 const operand = operands[field_index];
867898
868899 derive_from_kind: {