| author | |
| committer | |
| log | 3fb86841cc65437c65a6d599117833e260ea797c |
| tree | 2f4df8b5da41e8df00ba0989ca72beb26566b6c1 |
| parent | 5998a8cebe3973d70c258b2a1440c5c3252d3539 |
| parent | cd4b03c5ed1bc5b48ad9c353679b309ead75551d |
| signature |
spirv: refactor and remove deduplication ISel17 files changed, 8302 insertions(+), 12059 deletions(-)
CMakeLists.txt-10| ... | ... | @@ -553,11 +553,6 @@ set(ZIG_STAGE2_SOURCES |
| 553 | 553 | src/codegen/c/Type.zig |
| 554 | 554 | src/codegen/llvm.zig |
| 555 | 555 | 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 | |
| 561 | 556 | src/crash_report.zig |
| 562 | 557 | src/dev.zig |
| 563 | 558 | src/libs/freebsd.zig |
| ... | ... | @@ -620,11 +615,6 @@ set(ZIG_STAGE2_SOURCES |
| 620 | 615 | src/link/Plan9.zig |
| 621 | 616 | src/link/Plan9/aout.zig |
| 622 | 617 | 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 | |
| 628 | 618 | src/link/StringTable.zig |
| 629 | 619 | src/link/Wasm.zig |
| 630 | 620 | src/link/Wasm/Archive.zig |
lib/std/Build/Watch.zig+7-1| ... | ... | @@ -177,7 +177,13 @@ const Os = switch (builtin.os.tag) { |
| 177 | 177 | const gop = try w.dir_table.getOrPut(gpa, path); |
| 178 | 178 | if (!gop.found_existing) { |
| 179 | 179 | 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 | }; | |
| 181 | 187 | const fan_fd = blk: { |
| 182 | 188 | const fd_gop = try w.os.poll_fds.getOrPut(gpa, mount_id); |
| 183 | 189 | if (!fd_gop.found_existing) { |
src/Zcu.zig+2-3| ... | ... | @@ -3651,9 +3651,8 @@ pub fn errorSetBits(zcu: *const Zcu) u16 { |
| 3651 | 3651 | |
| 3652 | 3652 | if (zcu.error_limit == 0) return 0; |
| 3653 | 3653 | 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; | |
| 3657 | 3656 | } |
| 3658 | 3657 | |
| 3659 | 3658 | 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 |
| 4459 | 4459 | |
| 4460 | 4460 | const lf = comp.bin_file orelse return error.NoLinkFile; |
| 4461 | 4461 | |
| 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. | |
| 4466 | 4463 | if (lf.cast(.spirv)) |spirv_file| { |
| 4467 | 4464 | 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| { | |
| 4469 | 4466 | switch (err) { |
| 4470 | 4467 | error.OutOfMemory => comp.link_diags.setAllocFailure(), |
| 4471 | 4468 | } |
src/codegen.zig+1-1| ... | ... | @@ -57,7 +57,7 @@ fn importBackend(comptime backend: std.builtin.CompilerBackend) type { |
| 57 | 57 | .stage2_powerpc => unreachable, |
| 58 | 58 | .stage2_riscv64 => @import("arch/riscv64/CodeGen.zig"), |
| 59 | 59 | .stage2_sparc64 => @import("arch/sparc64/CodeGen.zig"), |
| 60 | .stage2_spirv => @import("codegen/spirv.zig"), | |
| 60 | .stage2_spirv => @import("codegen/spirv/CodeGen.zig"), | |
| 61 | 61 | .stage2_wasm => @import("arch/wasm/CodeGen.zig"), |
| 62 | 62 | .stage2_x86, .stage2_x86_64 => @import("arch/x86_64/CodeGen.zig"), |
| 63 | 63 | _ => unreachable, |
src/codegen/spirv.zig deleted-6658| ... | ... | @@ -1,6658 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Allocator = std.mem.Allocator; | |
| 3 | const Target = std.Target; | |
| 4 | const log = std.log.scoped(.codegen); | |
| 5 | const assert = std.debug.assert; | |
| 6 | const Signedness = std.builtin.Signedness; | |
| 7 | ||
| 8 | const Zcu = @import("../Zcu.zig"); | |
| 9 | const Decl = Zcu.Decl; | |
| 10 | const Type = @import("../Type.zig"); | |
| 11 | const Value = @import("../Value.zig"); | |
| 12 | const Air = @import("../Air.zig"); | |
| 13 | const InternPool = @import("../InternPool.zig"); | |
| 14 | ||
| 15 | const spec = @import("spirv/spec.zig"); | |
| 16 | const Opcode = spec.Opcode; | |
| 17 | const Word = spec.Word; | |
| 18 | const Id = spec.Id; | |
| 19 | const StorageClass = spec.StorageClass; | |
| 20 | ||
| 21 | const SpvModule = @import("spirv/Module.zig"); | |
| 22 | const IdRange = SpvModule.IdRange; | |
| 23 | ||
| 24 | const SpvSection = @import("spirv/Section.zig"); | |
| 25 | const SpvAssembler = @import("spirv/Assembler.zig"); | |
| 26 | ||
| 27 | const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, Id); | |
| 28 | ||
| 29 | pub 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 | ||
| 40 | pub const zig_call_abi_ver = 3; | |
| 41 | pub const big_int_bits = 32; | |
| 42 | ||
| 43 | const InternMap = std.AutoHashMapUnmanaged(struct { InternPool.Index, NavGen.Repr }, Id); | |
| 44 | const PtrTypeMap = std.AutoHashMapUnmanaged( | |
| 45 | struct { InternPool.Index, StorageClass, NavGen.Repr }, | |
| 46 | struct { ty_id: Id, fwd_emitted: bool }, | |
| 47 | ); | |
| 48 | ||
| 49 | const 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. | |
| 156 | pub 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. | |
| 292 | const 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 @@ |
| 1 | const Assembler = @This(); | |
| 2 | ||
| 3 | 1 | const std = @import("std"); |
| 4 | 2 | const Allocator = std.mem.Allocator; |
| 5 | 3 | const assert = std.debug.assert; |
| 6 | 4 | |
| 5 | const CodeGen = @import("CodeGen.zig"); | |
| 6 | const Decl = @import("Module.zig").Decl; | |
| 7 | ||
| 7 | 8 | const spec = @import("spec.zig"); |
| 8 | 9 | const Opcode = spec.Opcode; |
| 9 | 10 | const Word = spec.Word; |
| 10 | 11 | const Id = spec.Id; |
| 11 | 12 | const StorageClass = spec.StorageClass; |
| 12 | 13 | |
| 13 | const SpvModule = @import("Module.zig"); | |
| 14 | ||
| 15 | /// Represents a token in the assembly template. | |
| 16 | const Token = struct { | |
| 17 | tag: Tag, | |
| 18 | start: u32, | |
| 19 | end: u32, | |
| 14 | const Assembler = @This(); | |
| 20 | 15 | |
| 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, | |
| 16 | cg: *CodeGen, | |
| 17 | errors: std.ArrayListUnmanaged(ErrorMsg) = .empty, | |
| 18 | src: []const u8 = undefined, | |
| 19 | /// `ass.src` tokenized. | |
| 20 | tokens: std.ArrayListUnmanaged(Token) = .empty, | |
| 21 | current_token: u32 = 0, | |
| 22 | /// The instruction that is currently being parsed or has just been parsed. | |
| 23 | inst: struct { | |
| 24 | opcode: Opcode = undefined, | |
| 25 | operands: std.ArrayListUnmanaged(Operand) = .empty, | |
| 26 | string_bytes: std.ArrayListUnmanaged(u8) = .empty, | |
| 50 | 27 | |
| 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 | } | |
| 63 | 34 | } |
| 64 | }; | |
| 65 | }; | |
| 35 | return null; | |
| 36 | } | |
| 37 | } = .{}, | |
| 38 | value_map: std.StringArrayHashMapUnmanaged(AsmValue) = .{}, | |
| 39 | inst_map: std.StringArrayHashMapUnmanaged(void) = .empty, | |
| 66 | 40 | |
| 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. | |
| 71 | 41 | const Operand = union(enum) { |
| 72 | 42 | /// Any 'simple' 32-bit value. This could be a mask or |
| 73 | 43 | /// enumerant, etc, depending on the operands. |
| 74 | 44 | 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. | |
| 80 | 46 | 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. | |
| 86 | 48 | 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. | |
| 90 | 51 | result_id: AsmValue.Ref, |
| 91 | ||
| 92 | 52 | /// A result-id which referred to (not assigned to) in this instruction. |
| 93 | 53 | ref_id: AsmValue.Ref, |
| 94 | ||
| 95 | 54 | /// Offset into `inst.string_bytes`. The string ends at the next zero-terminator. |
| 96 | 55 | string: u32, |
| 97 | 56 | }; |
| 98 | 57 | |
| 99 | /// A structure representing an error message that the assembler may return, when | |
| 100 | /// the assembly source is not syntactically or semantically correct. | |
| 58 | pub 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 | ||
| 69 | const Error = error{ AssembleFail, OutOfMemory }; | |
| 70 | ||
| 71 | pub 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 | ||
| 101 | 96 | const ErrorMsg = struct { |
| 102 | 97 | /// The offset in bytes from the start of `src` that this error occured. |
| 103 | 98 | 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? | |
| 107 | 99 | msg: []const u8, |
| 108 | 100 | }; |
| 109 | 101 | |
| 110 | /// Possible errors the `assemble` function may return. | |
| 111 | const Error = error{ AssembleFail, OutOfMemory }; | |
| 102 | fn 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 | ||
| 112 | fn 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 | ||
| 117 | fn todo(ass: *Assembler, comptime fmt: []const u8, args: anytype) Error { | |
| 118 | return ass.fail(0, "todo: " ++ fmt, args); | |
| 119 | } | |
| 112 | 120 | |
| 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. | |
| 116 | 121 | const 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. | |
| 119 | 124 | pub const Ref = u32; |
| 120 | 125 | |
| 121 | /// This result-value is the RHS of the current instruction. | |
| 126 | /// The RHS of the current instruction. | |
| 122 | 127 | 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. | |
| 125 | 129 | /// It will be further resolved at a later stage to a more concrete forward reference. |
| 126 | 130 | 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. | |
| 129 | 132 | 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. | |
| 132 | 134 | ty: Id, |
| 133 | ||
| 134 | /// This is a pre-supplied constant integer value. | |
| 135 | /// A pre-supplied constant integer value. | |
| 135 | 136 | constant: u32, |
| 136 | ||
| 137 | /// This is a pre-supplied constant string value. | |
| 138 | 137 | string: []const u8, |
| 139 | 138 | |
| 140 | 139 | /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue |
| 141 | 140 | /// is of a variant that allows the result to be obtained (not an unresolved |
| 142 | 141 | /// 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) { | |
| 145 | 144 | .just_declared, |
| 146 | 145 | .unresolved_forward_reference, |
| 147 | 146 | // TODO: Lower this value as constant? |
| ... | ... | @@ -154,226 +153,101 @@ const AsmValue = union(enum) { |
| 154 | 153 | } |
| 155 | 154 | }; |
| 156 | 155 | |
| 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. | |
| 159 | const AsmValueMap = std.StringArrayHashMapUnmanaged(AsmValue); | |
| 160 | ||
| 161 | /// An allocator used for common allocations. | |
| 162 | gpa: Allocator, | |
| 163 | ||
| 164 | /// A list of errors that occured during processing the assembly. | |
| 165 | errors: std.ArrayListUnmanaged(ErrorMsg) = .empty, | |
| 166 | ||
| 167 | /// The source code that is being assembled. | |
| 168 | /// This is set when calling `assemble()`. | |
| 169 | src: []const u8 = undefined, | |
| 170 | ||
| 171 | /// The module that this assembly is associated to. | |
| 172 | /// Instructions like OpType*, OpDecorate, etc are emitted into this module. | |
| 173 | spv: *SpvModule, | |
| 174 | ||
| 175 | /// The function that the function-specific instructions should be emitted to. | |
| 176 | func: *SpvModule.Fn, | |
| 177 | ||
| 178 | /// `self.src` tokenized. | |
| 179 | tokens: std.ArrayListUnmanaged(Token) = .empty, | |
| 180 | ||
| 181 | /// The token that is next during parsing. | |
| 182 | current_token: u32 = 0, | |
| 183 | ||
| 184 | /// This field groups the properties of the instruction that is currently | |
| 185 | /// being parsed or has just been parsed. | |
| 186 | inst: 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. | |
| 209 | value_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()`. | |
| 214 | instruction_map: std.StringArrayHashMapUnmanaged(void) = .empty, | |
| 215 | ||
| 216 | /// Free the resources owned by this assembler. | |
| 217 | pub 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 | ||
| 229 | pub 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 | ||
| 252 | fn 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 | ||
| 261 | fn 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 | ||
| 266 | fn 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`. | |
| 271 | 157 | /// This for example emits the instruction in the module or function, or |
| 272 | 158 | /// records type definitions. |
| 273 | 159 | /// If this function returns `error.AssembleFail`, an explanatory |
| 274 | /// error message has already been emitted into `self.errors`. | |
| 275 | fn processInstruction(self: *Assembler) !void { | |
| 276 | const result: AsmValue = switch (self.inst.opcode) { | |
| 160 | /// error message has already been emitted into `ass.errors`. | |
| 161 | fn processInstruction(ass: *Assembler) !void { | |
| 162 | const module = ass.cg.module; | |
| 163 | const result: AsmValue = switch (ass.inst.opcode) { | |
| 277 | 164 | .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", .{}); | |
| 279 | 169 | }, |
| 280 | 170 | .OpCapability => { |
| 281 | try self.spv.addCapability(@enumFromInt(self.inst.operands.items[0].value)); | |
| 171 | try module.addCapability(@enumFromInt(ass.inst.operands.items[0].value)); | |
| 282 | 172 | return; |
| 283 | 173 | }, |
| 284 | 174 | .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); | |
| 288 | 178 | return; |
| 289 | 179 | }, |
| 290 | 180 | .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); | |
| 293 | 183 | 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}); | |
| 295 | 185 | }; |
| 296 | break :blk .{ .value = try self.spv.importInstructionSet(set_tag) }; | |
| 186 | break :blk .{ .value = try module.importInstructionSet(set_tag) }; | |
| 297 | 187 | }, |
| 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, | |
| 318 | 191 | }, |
| 319 | 192 | }; |
| 320 | 193 | |
| 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, | |
| 324 | 197 | else => { |
| 325 | 198 | // 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}); | |
| 328 | 201 | }, |
| 329 | 202 | } |
| 330 | 203 | } |
| 331 | 204 | |
| 332 | /// Record `self.inst` into the module's type system, and return the AsmValue that | |
| 333 | /// refers to the result. | |
| 334 | fn 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(), | |
| 205 | fn 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(), | |
| 340 | 214 | .OpTypeInt => blk: { |
| 341 | 215 | const signedness: std.builtin.Signedness = switch (operands[2].literal32) { |
| 342 | 216 | 0 => .unsigned, |
| 343 | 217 | 1 => .signed, |
| 344 | 218 | else => { |
| 345 | 219 | // 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}); | |
| 347 | 221 | }, |
| 348 | 222 | }; |
| 349 | 223 | 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}); | |
| 351 | 225 | }; |
| 352 | break :blk try self.spv.intType(signedness, width); | |
| 226 | break :blk try module.intType(signedness, width); | |
| 353 | 227 | }, |
| 354 | 228 | .OpTypeFloat => blk: { |
| 355 | 229 | const bits = operands[1].literal32; |
| 356 | 230 | switch (bits) { |
| 357 | 231 | 16, 32, 64 => {}, |
| 358 | 232 | 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}); | |
| 360 | 234 | }, |
| 361 | 235 | } |
| 362 | break :blk try self.spv.floatType(@intCast(bits)); | |
| 236 | break :blk try module.floatType(@intCast(bits)); | |
| 363 | 237 | }, |
| 364 | 238 | .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); | |
| 367 | 241 | }, |
| 368 | 242 | .OpTypeArray => { |
| 369 | 243 | // TODO: The length of an OpTypeArray is determined by a constant (which may be a spec constant), |
| 370 | 244 | // 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", .{}); | |
| 372 | 246 | }, |
| 373 | 247 | .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, .{ | |
| 377 | 251 | .id_result = result_id, |
| 378 | 252 | .element_type = element_type, |
| 379 | 253 | }); |
| ... | ... | @@ -381,9 +255,9 @@ fn processTypeInstruction(self: *Assembler) !AsmValue { |
| 381 | 255 | }, |
| 382 | 256 | .OpTypePointer => blk: { |
| 383 | 257 | 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, .{ | |
| 387 | 261 | .id_result = result_id, |
| 388 | 262 | .storage_class = storage_class, |
| 389 | 263 | .type = child_type, |
| ... | ... | @@ -391,17 +265,16 @@ fn processTypeInstruction(self: *Assembler) !AsmValue { |
| 391 | 265 | break :blk result_id; |
| 392 | 266 | }, |
| 393 | 267 | .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); | |
| 400 | 273 | }, |
| 401 | 274 | .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, .{ | |
| 405 | 278 | .id_result = result_id, |
| 406 | 279 | .sampled_type = sampled_type, |
| 407 | 280 | .dim = @enumFromInt(operands[2].value), |
| ... | ... | @@ -414,187 +287,178 @@ fn processTypeInstruction(self: *Assembler) !AsmValue { |
| 414 | 287 | break :blk result_id; |
| 415 | 288 | }, |
| 416 | 289 | .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 }); | |
| 419 | 292 | break :blk result_id; |
| 420 | 293 | }, |
| 421 | 294 | .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 }); | |
| 425 | 298 | break :blk result_id; |
| 426 | 299 | }, |
| 427 | 300 | .OpTypeFunction => blk: { |
| 428 | 301 | 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); | |
| 430 | 307 | |
| 431 | const param_types = try self.spv.gpa.alloc(Id, param_operands.len); | |
| 432 | defer self.spv.gpa.free(param_types); | |
| 433 | 308 | for (param_types, param_operands) |*param, operand| { |
| 434 | param.* = try self.resolveRefId(operand.ref_id); | |
| 309 | param.* = try ass.resolveRefId(operand.ref_id); | |
| 435 | 310 | } |
| 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, .{ | |
| 438 | 313 | .id_result = result_id, |
| 439 | 314 | .return_type = return_type, |
| 440 | 315 | .id_ref_2 = param_types, |
| 441 | 316 | }); |
| 442 | 317 | break :blk result_id; |
| 443 | 318 | }, |
| 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)}), | |
| 445 | 320 | }; |
| 446 | 321 | |
| 447 | return AsmValue{ .ty = id }; | |
| 322 | return .{ .ty = id }; | |
| 448 | 323 | } |
| 449 | 324 | |
| 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: | |
| 452 | 325 | /// - No forward references are allowed in operands. |
| 453 | 326 | /// - Target section is determined from instruction type. |
| 454 | /// - Function-local instructions are emitted in `self.func`. | |
| 455 | fn 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, | |
| 327 | fn 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, | |
| 461 | 335 | .type_declaration => unreachable, // Handled elsewhere. |
| 462 | else => switch (self.inst.opcode) { | |
| 336 | else => switch (ass.inst.opcode) { | |
| 463 | 337 | .OpEntryPoint => unreachable, |
| 464 | .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes, | |
| 338 | .OpExecutionMode, .OpExecutionModeId => &module.sections.execution_modes, | |
| 465 | 339 | .OpVariable => section: { |
| 466 | 340 | 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) { | |
| 470 | 344 | // 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; | |
| 472 | 346 | } |
| 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; | |
| 477 | 349 | }, |
| 478 | // Default case - to be worked out further. | |
| 479 | else => &self.func.body, | |
| 350 | else => &ass.cg.body, | |
| 480 | 351 | }, |
| 481 | 352 | }; |
| 482 | 353 | |
| 483 | 354 | var maybe_result_id: ?Id = null; |
| 484 | 355 | 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); | |
| 488 | 359 | section.writeWord(0); |
| 489 | 360 | |
| 490 | 361 | for (operands) |operand| { |
| 491 | 362 | switch (operand) { |
| 492 | 363 | .value, .literal32 => |word| { |
| 493 | try section.ensureUnusedCapacity(self.spv.gpa, 1); | |
| 364 | try section.ensureUnusedCapacity(module.gpa, 1); | |
| 494 | 365 | section.writeWord(word); |
| 495 | 366 | }, |
| 496 | 367 | .literal64 => |dword| { |
| 497 | try section.ensureUnusedCapacity(self.spv.gpa, 2); | |
| 368 | try section.ensureUnusedCapacity(module.gpa, 2); | |
| 498 | 369 | section.writeDoubleWord(dword); |
| 499 | 370 | }, |
| 500 | 371 | .result_id => { |
| 501 | 372 | 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 | |
| 503 | 374 | else |
| 504 | self.spv.allocId(); | |
| 505 | try section.ensureUnusedCapacity(self.spv.gpa, 1); | |
| 375 | module.allocId(); | |
| 376 | try section.ensureUnusedCapacity(module.gpa, 1); | |
| 506 | 377 | section.writeOperand(Id, maybe_result_id.?); |
| 507 | 378 | }, |
| 508 | 379 | .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); | |
| 511 | 382 | section.writeOperand(spec.Id, result.resultId()); |
| 512 | 383 | }, |
| 513 | 384 | .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); | |
| 515 | 386 | 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); | |
| 517 | 388 | section.writeOperand(spec.LiteralString, text); |
| 518 | 389 | }, |
| 519 | 390 | } |
| 520 | 391 | } |
| 521 | 392 | |
| 522 | 393 | 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); | |
| 524 | 395 | |
| 525 | if (maybe_result_id) |result| { | |
| 526 | return AsmValue{ .value = result }; | |
| 527 | } | |
| 396 | if (maybe_result_id) |result| return .{ .value = result }; | |
| 528 | 397 | return null; |
| 529 | 398 | } |
| 530 | 399 | |
| 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. | |
| 533 | fn resolveMaybeForwardRef(self: *Assembler, ref: AsmValue.Ref) !AsmValue { | |
| 534 | const value = self.value_map.values()[ref]; | |
| 400 | fn resolveMaybeForwardRef(ass: *Assembler, ref: AsmValue.Ref) !AsmValue { | |
| 401 | const value = ass.value_map.values()[ref]; | |
| 535 | 402 | switch (value) { |
| 536 | 403 | .just_declared => { |
| 537 | const name = self.value_map.keys()[ref]; | |
| 404 | const name = ass.value_map.keys()[ref]; | |
| 538 | 405 | // TODO: Improve source location. |
| 539 | return self.fail(0, "self-referential parameter %{s}", .{name}); | |
| 406 | return ass.fail(0, "ass-referential parameter %{s}", .{name}); | |
| 540 | 407 | }, |
| 541 | 408 | else => return value, |
| 542 | 409 | } |
| 543 | 410 | } |
| 544 | 411 | |
| 545 | /// Resolve a value reference. This function | |
| 546 | /// makes sure that the result is not self-referential, nor that it is forward declared. | |
| 547 | fn resolveRef(self: *Assembler, ref: AsmValue.Ref) !AsmValue { | |
| 548 | const value = try self.resolveMaybeForwardRef(ref); | |
| 412 | fn resolveRef(ass: *Assembler, ref: AsmValue.Ref) !AsmValue { | |
| 413 | const value = try ass.resolveMaybeForwardRef(ref); | |
| 549 | 414 | switch (value) { |
| 550 | 415 | .just_declared => unreachable, |
| 551 | 416 | .unresolved_forward_reference => { |
| 552 | const name = self.value_map.keys()[ref]; | |
| 417 | const name = ass.value_map.keys()[ref]; | |
| 553 | 418 | // 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}); | |
| 555 | 420 | }, |
| 556 | 421 | else => return value, |
| 557 | 422 | } |
| 558 | 423 | } |
| 559 | 424 | |
| 560 | fn resolveRefId(self: *Assembler, ref: AsmValue.Ref) !Id { | |
| 561 | const value = try self.resolveRef(ref); | |
| 425 | fn resolveRefId(ass: *Assembler, ref: AsmValue.Ref) !Id { | |
| 426 | const value = try ass.resolveRef(ref); | |
| 562 | 427 | return value.resultId(); |
| 563 | 428 | } |
| 564 | 429 | |
| 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`. | |
| 568 | fn 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); | |
| 430 | fn 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); | |
| 578 | 442 | if (!entry.found_existing) { |
| 579 | 443 | entry.value_ptr.* = .just_declared; |
| 580 | 444 | } |
| 581 | 445 | break :blk @intCast(entry.index); |
| 582 | 446 | } else null; |
| 583 | 447 | |
| 584 | const opcode_tok = self.currentToken(); | |
| 448 | const opcode_tok = ass.currentToken(); | |
| 585 | 449 | 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()}); | |
| 589 | 453 | } |
| 590 | 454 | |
| 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}); | |
| 594 | 458 | }; |
| 595 | 459 | |
| 596 | 460 | const inst = spec.InstructionSet.core.instructions()[index]; |
| 597 | self.inst.opcode = @enumFromInt(inst.opcode); | |
| 461 | ass.inst.opcode = @enumFromInt(inst.opcode); | |
| 598 | 462 | |
| 599 | 463 | const expected_operands = inst.operands; |
| 600 | 464 | // This is a loop because the result-id is not always the first operand. |
| ... | ... | @@ -603,66 +467,67 @@ fn parseInstruction(self: *Assembler) !void { |
| 603 | 467 | } else false; |
| 604 | 468 | |
| 605 | 469 | 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)}); | |
| 607 | 471 | } else if (!requires_lhs_result and maybe_lhs_result != null) { |
| 608 | return self.fail( | |
| 472 | return ass.fail( | |
| 609 | 473 | lhs_result_tok.start, |
| 610 | 474 | "opcode '{s}' does not expect a result-id on the left-hand side", |
| 611 | .{@tagName(self.inst.opcode)}, | |
| 475 | .{@tagName(ass.inst.opcode)}, | |
| 612 | 476 | ); |
| 613 | 477 | } |
| 614 | 478 | |
| 615 | 479 | for (expected_operands) |operand| { |
| 616 | 480 | 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.? }); | |
| 618 | 482 | continue; |
| 619 | 483 | } |
| 620 | 484 | |
| 621 | 485 | 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, | |
| 625 | 489 | "missing required operand", // TODO: Operand name? |
| 626 | 490 | .{}, |
| 627 | 491 | ); |
| 628 | 492 | } else { |
| 629 | try self.parseOperand(operand.kind); | |
| 493 | try ass.parseOperand(operand.kind); | |
| 630 | 494 | }, |
| 631 | .optional => if (!self.isAtInstructionBoundary()) { | |
| 632 | try self.parseOperand(operand.kind); | |
| 495 | .optional => if (!ass.isAtInstructionBoundary()) { | |
| 496 | try ass.parseOperand(operand.kind); | |
| 633 | 497 | }, |
| 634 | .variadic => while (!self.isAtInstructionBoundary()) { | |
| 635 | try self.parseOperand(operand.kind); | |
| 498 | .variadic => while (!ass.isAtInstructionBoundary()) { | |
| 499 | try ass.parseOperand(operand.kind); | |
| 636 | 500 | }, |
| 637 | 501 | } |
| 638 | 502 | } |
| 639 | 503 | } |
| 640 | 504 | |
| 641 | /// Parse a single operand of a particular type. | |
| 642 | fn parseOperand(self: *Assembler, kind: spec.OperandKind) Error!void { | |
| 505 | fn parseOperand(ass: *Assembler, kind: spec.OperandKind) Error!void { | |
| 643 | 506 | 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(), | |
| 647 | 510 | 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)}), | |
| 654 | 517 | }, |
| 655 | 518 | } |
| 656 | 519 | } |
| 657 | 520 | |
| 658 | 521 | /// Also handles parsing any required extra operands. |
| 659 | fn parseBitEnum(self: *Assembler, kind: spec.OperandKind) !void { | |
| 660 | var tok = self.currentToken(); | |
| 661 | try self.expectToken(.value); | |
| 522 | fn 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); | |
| 662 | 527 | |
| 663 | var text = self.tokenText(tok); | |
| 528 | var text = ass.tokenText(tok); | |
| 664 | 529 | 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 }); | |
| 666 | 531 | return; |
| 667 | 532 | } |
| 668 | 533 | |
| ... | ... | @@ -673,18 +538,18 @@ fn parseBitEnum(self: *Assembler, kind: spec.OperandKind) !void { |
| 673 | 538 | if (std.mem.eql(u8, enumerant.name, text)) |
| 674 | 539 | break enumerant; |
| 675 | 540 | } 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) }); | |
| 677 | 542 | }; |
| 678 | 543 | mask |= enumerant.value; |
| 679 | if (!self.eatToken(.pipe)) | |
| 544 | if (!ass.eatToken(.pipe)) | |
| 680 | 545 | break; |
| 681 | 546 | |
| 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); | |
| 685 | 550 | } |
| 686 | 551 | |
| 687 | try self.inst.operands.append(self.gpa, .{ .value = mask }); | |
| 552 | try ass.inst.operands.append(gpa, .{ .value = mask }); | |
| 688 | 553 | |
| 689 | 554 | // Assume values are sorted. |
| 690 | 555 | // TODO: ensure in generator. |
| ... | ... | @@ -693,43 +558,45 @@ fn parseBitEnum(self: *Assembler, kind: spec.OperandKind) !void { |
| 693 | 558 | continue; |
| 694 | 559 | |
| 695 | 560 | 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}); | |
| 698 | 563 | } |
| 699 | 564 | |
| 700 | try self.parseOperand(param_kind); | |
| 565 | try ass.parseOperand(param_kind); | |
| 701 | 566 | } |
| 702 | 567 | } |
| 703 | 568 | } |
| 704 | 569 | |
| 705 | 570 | /// Also handles parsing any required extra operands. |
| 706 | fn 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}); | |
| 571 | fn 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}); | |
| 712 | 579 | }; |
| 713 | 580 | switch (value) { |
| 714 | 581 | .constant => |literal32| { |
| 715 | try self.inst.operands.append(self.gpa, .{ .value = literal32 }); | |
| 582 | try ass.inst.operands.append(gpa, .{ .value = literal32 }); | |
| 716 | 583 | }, |
| 717 | 584 | .string => |str| { |
| 718 | 585 | const enumerant = for (kind.enumerants()) |enumerant| { |
| 719 | 586 | if (std.mem.eql(u8, enumerant.name, str)) break enumerant; |
| 720 | 587 | } 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) }); | |
| 722 | 589 | }; |
| 723 | try self.inst.operands.append(self.gpa, .{ .value = enumerant.value }); | |
| 590 | try ass.inst.operands.append(gpa, .{ .value = enumerant.value }); | |
| 724 | 591 | }, |
| 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}), | |
| 726 | 593 | } |
| 727 | 594 | return; |
| 728 | 595 | } |
| 729 | 596 | |
| 730 | try self.expectToken(.value); | |
| 597 | try ass.expectToken(.value); | |
| 731 | 598 | |
| 732 | const text = self.tokenText(tok); | |
| 599 | const text = ass.tokenText(tok); | |
| 733 | 600 | const int_value = std.fmt.parseInt(u32, text, 0) catch null; |
| 734 | 601 | const enumerant = for (kind.enumerants()) |enumerant| { |
| 735 | 602 | if (int_value) |v| { |
| ... | ... | @@ -738,182 +605,194 @@ fn parseValueEnum(self: *Assembler, kind: spec.OperandKind) !void { |
| 738 | 605 | if (std.mem.eql(u8, enumerant.name, text)) break enumerant; |
| 739 | 606 | } |
| 740 | 607 | } 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) }); | |
| 742 | 609 | }; |
| 743 | 610 | |
| 744 | try self.inst.operands.append(self.gpa, .{ .value = enumerant.value }); | |
| 611 | try ass.inst.operands.append(gpa, .{ .value = enumerant.value }); | |
| 745 | 612 | |
| 746 | 613 | 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}); | |
| 749 | 616 | } |
| 750 | 617 | |
| 751 | try self.parseOperand(param_kind); | |
| 618 | try ass.parseOperand(param_kind); | |
| 752 | 619 | } |
| 753 | 620 | } |
| 754 | 621 | |
| 755 | fn parseRefId(self: *Assembler) !void { | |
| 756 | const tok = self.currentToken(); | |
| 757 | try self.expectToken(.result_id); | |
| 622 | fn parseRefId(ass: *Assembler) !void { | |
| 623 | const gpa = ass.cg.module.gpa; | |
| 624 | ||
| 625 | const tok = ass.currentToken(); | |
| 626 | try ass.expectToken(.result_id); | |
| 758 | 627 | |
| 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); | |
| 761 | 630 | if (!entry.found_existing) { |
| 762 | 631 | entry.value_ptr.* = .unresolved_forward_reference; |
| 763 | 632 | } |
| 764 | 633 | |
| 765 | 634 | 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 }); | |
| 767 | 636 | } |
| 768 | 637 | |
| 769 | fn 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}); | |
| 638 | fn 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}); | |
| 775 | 646 | }; |
| 776 | 647 | switch (value) { |
| 777 | 648 | .constant => |literal32| { |
| 778 | try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 }); | |
| 649 | try ass.inst.operands.append(gpa, .{ .literal32 = literal32 }); | |
| 779 | 650 | }, |
| 780 | 651 | 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}); | |
| 782 | 653 | }, |
| 783 | 654 | } |
| 784 | 655 | return; |
| 785 | 656 | } |
| 786 | 657 | |
| 787 | try self.expectToken(.value); | |
| 658 | try ass.expectToken(.value); | |
| 788 | 659 | // According to the SPIR-V machine readable grammar, a LiteralInteger |
| 789 | 660 | // may consist of one or more words. From the SPIR-V docs it seems like there |
| 790 | 661 | // only one instruction where multiple words are allowed, the literals that make up the |
| 791 | 662 | // switch cases of OpSwitch. This case is handled separately, and so we just assume |
| 792 | 663 | // everything is a 32-bit integer in this function. |
| 793 | const text = self.tokenText(tok); | |
| 664 | const text = ass.tokenText(tok); | |
| 794 | 665 | 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}); | |
| 796 | 667 | }; |
| 797 | try self.inst.operands.append(self.gpa, .{ .literal32 = value }); | |
| 668 | try ass.inst.operands.append(gpa, .{ .literal32 = value }); | |
| 798 | 669 | } |
| 799 | 670 | |
| 800 | fn 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}); | |
| 671 | fn 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}); | |
| 806 | 679 | }; |
| 807 | 680 | switch (value) { |
| 808 | 681 | .constant => |literal32| { |
| 809 | try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 }); | |
| 682 | try ass.inst.operands.append(gpa, .{ .literal32 = literal32 }); | |
| 810 | 683 | }, |
| 811 | 684 | 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}); | |
| 813 | 686 | }, |
| 814 | 687 | } |
| 815 | 688 | return; |
| 816 | 689 | } |
| 817 | 690 | |
| 818 | try self.expectToken(.value); | |
| 819 | const text = self.tokenText(tok); | |
| 691 | try ass.expectToken(.value); | |
| 692 | const text = ass.tokenText(tok); | |
| 820 | 693 | 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}); | |
| 822 | 695 | }; |
| 823 | try self.inst.operands.append(self.gpa, .{ .literal32 = value }); | |
| 696 | try ass.inst.operands.append(gpa, .{ .literal32 = value }); | |
| 824 | 697 | } |
| 825 | 698 | |
| 826 | fn parseString(self: *Assembler) !void { | |
| 827 | const tok = self.currentToken(); | |
| 828 | try self.expectToken(.string); | |
| 699 | fn parseString(ass: *Assembler) !void { | |
| 700 | const gpa = ass.cg.module.gpa; | |
| 701 | ||
| 702 | const tok = ass.currentToken(); | |
| 703 | try ass.expectToken(.string); | |
| 829 | 704 | // Note, the string might not have a closing quote. In this case, |
| 830 | 705 | // an error is already emitted but we are trying to continue processing |
| 831 | 706 | // anyway, so in this function we have to deal with that situation. |
| 832 | const text = self.tokenText(tok); | |
| 707 | const text = ass.tokenText(tok); | |
| 833 | 708 | assert(text.len > 0 and text[0] == '"'); |
| 834 | 709 | const literal = if (text.len != 1 and text[text.len - 1] == '"') |
| 835 | 710 | text[1 .. text.len - 1] |
| 836 | 711 | else |
| 837 | 712 | text[1..]; |
| 838 | 713 | |
| 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); | |
| 843 | 718 | |
| 844 | try self.inst.operands.append(self.gpa, .{ .string = string_offset }); | |
| 719 | try ass.inst.operands.append(gpa, .{ .string = string_offset }); | |
| 845 | 720 | } |
| 846 | 721 | |
| 847 | fn parseContextDependentNumber(self: *Assembler) !void { | |
| 722 | fn parseContextDependentNumber(ass: *Assembler) !void { | |
| 723 | const module = ass.cg.module; | |
| 724 | ||
| 848 | 725 | // For context dependent numbers, the actual type to parse is determined by the instruction. |
| 849 | 726 | // Currently, this operand appears in OpConstant and OpSpecConstant, where the too-be-parsed type |
| 850 | 727 | // is determined by the result type. That means that in this instructions we have to resolve the |
| 851 | 728 | // 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); | |
| 853 | 730 | |
| 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); | |
| 856 | 733 | const result_id = result.resultId(); |
| 857 | 734 | // 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, | |
| 859 | 736 | // we can determine the width of these types by directly checking the cache. |
| 860 | 737 | // This only works if the Assembler and codegen both use spv.intType and spv.floatType though. |
| 861 | 738 | // We don't expect there to be many of these types, so just look it up every time. |
| 862 | 739 | // TODO: Count be improved to be a little bit more efficent. |
| 863 | 740 | |
| 864 | 741 | { |
| 865 | var it = self.spv.cache.int_types.iterator(); | |
| 742 | var it = module.cache.int_types.iterator(); | |
| 866 | 743 | while (it.next()) |entry| { |
| 867 | 744 | const id = entry.value_ptr.*; |
| 868 | 745 | if (id != result_id) continue; |
| 869 | 746 | const info = entry.key_ptr.*; |
| 870 | return try self.parseContextDependentInt(info.signedness, info.bits); | |
| 747 | return try ass.parseContextDependentInt(info.signedness, info.bits); | |
| 871 | 748 | } |
| 872 | 749 | } |
| 873 | 750 | |
| 874 | 751 | { |
| 875 | var it = self.spv.cache.float_types.iterator(); | |
| 752 | var it = module.cache.float_types.iterator(); | |
| 876 | 753 | while (it.next()) |entry| { |
| 877 | 754 | const id = entry.value_ptr.*; |
| 878 | 755 | if (id != result_id) continue; |
| 879 | 756 | const info = entry.key_ptr.*; |
| 880 | 757 | 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}), | |
| 885 | 762 | } |
| 886 | 763 | } |
| 887 | 764 | } |
| 888 | 765 | |
| 889 | return self.fail(tok.start, "cannot parse literal constant", .{}); | |
| 766 | return ass.fail(tok.start, "cannot parse literal constant", .{}); | |
| 890 | 767 | } |
| 891 | 768 | |
| 892 | fn 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}); | |
| 769 | fn 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}); | |
| 898 | 777 | }; |
| 899 | 778 | switch (value) { |
| 900 | 779 | .constant => |literal32| { |
| 901 | try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 }); | |
| 780 | try ass.inst.operands.append(gpa, .{ .literal32 = literal32 }); | |
| 902 | 781 | }, |
| 903 | 782 | 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}); | |
| 905 | 784 | }, |
| 906 | 785 | } |
| 907 | 786 | return; |
| 908 | 787 | } |
| 909 | 788 | |
| 910 | try self.expectToken(.value); | |
| 789 | try ass.expectToken(.value); | |
| 911 | 790 | |
| 912 | 791 | 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}); | |
| 914 | 793 | } |
| 915 | 794 | |
| 916 | const text = self.tokenText(tok); | |
| 795 | const text = ass.tokenText(tok); | |
| 917 | 796 | invalid: { |
| 918 | 797 | // Just parse the integer as the next larger integer type, and check if it overflows afterwards. |
| 919 | 798 | const int = std.fmt.parseInt(i128, text, 0) catch break :invalid; |
| ... | ... | @@ -928,112 +807,166 @@ fn parseContextDependentInt(self: *Assembler, signedness: std.builtin.Signedness |
| 928 | 807 | |
| 929 | 808 | // Note, we store the sign-extended version here. |
| 930 | 809 | 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))) }); | |
| 932 | 811 | } 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))) }); | |
| 934 | 813 | } |
| 935 | 814 | return; |
| 936 | 815 | } |
| 937 | 816 | |
| 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 }); | |
| 939 | 818 | } |
| 940 | 819 | |
| 941 | fn parseContextDependentFloat(self: *Assembler, comptime width: u16) !void { | |
| 820 | fn parseContextDependentFloat(ass: *Assembler, comptime width: u16) !void { | |
| 821 | const gpa = ass.cg.module.gpa; | |
| 822 | ||
| 942 | 823 | const Float = std.meta.Float(width); |
| 943 | 824 | const Int = std.meta.Int(.unsigned, width); |
| 944 | 825 | |
| 945 | const tok = self.currentToken(); | |
| 946 | try self.expectToken(.value); | |
| 826 | const tok = ass.currentToken(); | |
| 827 | try ass.expectToken(.value); | |
| 947 | 828 | |
| 948 | const text = self.tokenText(tok); | |
| 829 | const text = ass.tokenText(tok); | |
| 949 | 830 | |
| 950 | 831 | 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 }); | |
| 952 | 833 | }; |
| 953 | 834 | |
| 954 | 835 | const float_bits: Int = @bitCast(value); |
| 955 | 836 | 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 }); | |
| 957 | 838 | } else { |
| 958 | 839 | 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 }); | |
| 960 | 841 | } |
| 961 | 842 | } |
| 962 | 843 | |
| 963 | fn parsePhiSource(self: *Assembler) !void { | |
| 964 | try self.parseRefId(); | |
| 965 | if (self.isAtInstructionBoundary()) { | |
| 966 | return self.fail(self.currentToken().start, "missing phi block parent", .{}); | |
| 844 | fn parsePhiSource(ass: *Assembler) !void { | |
| 845 | try ass.parseRefId(); | |
| 846 | if (ass.isAtInstructionBoundary()) { | |
| 847 | return ass.fail(ass.currentToken().start, "missing phi block parent", .{}); | |
| 967 | 848 | } |
| 968 | try self.parseRefId(); | |
| 849 | try ass.parseRefId(); | |
| 969 | 850 | } |
| 970 | 851 | |
| 971 | /// Returns whether the `current_token` cursor is currently pointing | |
| 972 | /// at the start of a new instruction. | |
| 973 | fn 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. | |
| 854 | fn isAtInstructionBoundary(ass: Assembler) bool { | |
| 855 | return switch (ass.currentToken().tag) { | |
| 975 | 856 | .opcode, .result_id_assign, .eof => true, |
| 976 | 857 | else => false, |
| 977 | 858 | }; |
| 978 | 859 | } |
| 979 | 860 | |
| 980 | fn expectToken(self: *Assembler, tag: Token.Tag) !void { | |
| 981 | if (self.eatToken(tag)) | |
| 861 | fn expectToken(ass: *Assembler, tag: Token.Tag) !void { | |
| 862 | if (ass.eatToken(tag)) | |
| 982 | 863 | return; |
| 983 | 864 | |
| 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(), | |
| 986 | 867 | tag.name(), |
| 987 | 868 | }); |
| 988 | 869 | } |
| 989 | 870 | |
| 990 | fn eatToken(self: *Assembler, tag: Token.Tag) bool { | |
| 991 | if (self.testToken(tag)) { | |
| 992 | self.current_token += 1; | |
| 871 | fn eatToken(ass: *Assembler, tag: Token.Tag) bool { | |
| 872 | if (ass.testToken(tag)) { | |
| 873 | ass.current_token += 1; | |
| 993 | 874 | return true; |
| 994 | 875 | } |
| 995 | 876 | return false; |
| 996 | 877 | } |
| 997 | 878 | |
| 998 | fn testToken(self: Assembler, tag: Token.Tag) bool { | |
| 999 | return self.currentToken().tag == tag; | |
| 879 | fn testToken(ass: Assembler, tag: Token.Tag) bool { | |
| 880 | return ass.currentToken().tag == tag; | |
| 1000 | 881 | } |
| 1001 | 882 | |
| 1002 | fn currentToken(self: Assembler) Token { | |
| 1003 | return self.tokens.items[self.current_token]; | |
| 883 | fn currentToken(ass: Assembler) Token { | |
| 884 | return ass.tokens.items[ass.current_token]; | |
| 1004 | 885 | } |
| 1005 | 886 | |
| 1006 | fn tokenText(self: Assembler, tok: Token) []const u8 { | |
| 1007 | return self.src[tok.start..tok.end]; | |
| 887 | fn tokenText(ass: Assembler, tok: Token) []const u8 { | |
| 888 | return ass.src[tok.start..tok.end]; | |
| 1008 | 889 | } |
| 1009 | 890 | |
| 1010 | /// Tokenize `self.src` and put the tokens in `self.tokens`. | |
| 1011 | /// Any errors encountered are appended to `self.errors`. | |
| 1012 | fn 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`. | |
| 893 | fn tokenize(ass: *Assembler) !void { | |
| 894 | const gpa = ass.cg.module.gpa; | |
| 895 | ||
| 896 | ass.tokens.clearRetainingCapacity(); | |
| 1014 | 897 | |
| 1015 | 898 | var offset: u32 = 0; |
| 1016 | 899 | while (true) { |
| 1017 | const tok = try self.nextToken(offset); | |
| 900 | const tok = try ass.nextToken(offset); | |
| 1018 | 901 | // 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, | |
| 1020 | 903 | // 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; | |
| 1023 | 906 | } |
| 1024 | try self.tokens.append(self.gpa, tok); | |
| 907 | try ass.tokens.append(gpa, tok); | |
| 1025 | 908 | if (tok.tag == .eof) |
| 1026 | 909 | break; |
| 1027 | 910 | offset = tok.end; |
| 1028 | 911 | } |
| 1029 | 912 | } |
| 1030 | 913 | |
| 914 | const 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 | ||
| 1031 | 965 | /// Retrieve the next token from the input. This function will assert |
| 1032 | 966 | /// that the token is surrounded by whitespace if required, but will not |
| 1033 | 967 | /// interpret the token yet. |
| 1034 | /// Note: This function doesn't handle .result_id_assign - this is handled in | |
| 1035 | /// tokenize(). | |
| 1036 | fn nextToken(self: *Assembler, start_offset: u32) !Token { | |
| 968 | /// NOTE: This function doesn't handle .result_id_assign - this is handled in tokenize(). | |
| 969 | fn nextToken(ass: *Assembler, start_offset: u32) !Token { | |
| 1037 | 970 | // We generally separate the input into the following types: |
| 1038 | 971 | // - Whitespace. Generally ignored, but also used as delimiter for some |
| 1039 | 972 | // tokens. |
| ... | ... | @@ -1059,8 +992,8 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token { |
| 1059 | 992 | var token_start = start_offset; |
| 1060 | 993 | var offset = start_offset; |
| 1061 | 994 | 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]; | |
| 1064 | 997 | switch (state) { |
| 1065 | 998 | .start => switch (c) { |
| 1066 | 999 | ' ', '\t', '\r', '\n' => token_start = offset + 1, |
| ... | ... | @@ -1093,7 +1026,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token { |
| 1093 | 1026 | }, |
| 1094 | 1027 | .value => switch (c) { |
| 1095 | 1028 | '"' => { |
| 1096 | try self.addError(offset, "unexpected string literal", .{}); | |
| 1029 | try ass.addError(offset, "unexpected string literal", .{}); | |
| 1097 | 1030 | // The user most likely just forgot a delimiter here - keep |
| 1098 | 1031 | // the tag as value. |
| 1099 | 1032 | break; |
| ... | ... | @@ -1105,7 +1038,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token { |
| 1105 | 1038 | '_', 'a'...'z', 'A'...'Z', '0'...'9' => {}, |
| 1106 | 1039 | ' ', '\t', '\r', '\n', '=', '|' => break, |
| 1107 | 1040 | 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", .{}); | |
| 1109 | 1042 | // Again, probably a forgotten delimiter here. |
| 1110 | 1043 | break; |
| 1111 | 1044 | }, |
| ... | ... | @@ -1118,7 +1051,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token { |
| 1118 | 1051 | .string_end => switch (c) { |
| 1119 | 1052 | ' ', '\t', '\r', '\n', '=', '|' => break, |
| 1120 | 1053 | else => { |
| 1121 | try self.addError(offset, "unexpected character after string literal", .{}); | |
| 1054 | try ass.addError(offset, "unexpected character after string literal", .{}); | |
| 1122 | 1055 | // The token is still unmistakibly a string. |
| 1123 | 1056 | break; |
| 1124 | 1057 | }, |
| ... | ... | @@ -1128,7 +1061,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token { |
| 1128 | 1061 | } |
| 1129 | 1062 | } |
| 1130 | 1063 | |
| 1131 | var tok = Token{ | |
| 1064 | var tok: Token = .{ | |
| 1132 | 1065 | .tag = tag, |
| 1133 | 1066 | .start = token_start, |
| 1134 | 1067 | .end = offset, |
| ... | ... | @@ -1136,13 +1069,13 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token { |
| 1136 | 1069 | |
| 1137 | 1070 | switch (state) { |
| 1138 | 1071 | .string, .escape => { |
| 1139 | try self.addError(token_start, "unterminated string", .{}); | |
| 1072 | try ass.addError(token_start, "unterminated string", .{}); | |
| 1140 | 1073 | }, |
| 1141 | 1074 | .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", .{}); | |
| 1143 | 1076 | }, |
| 1144 | 1077 | .value => { |
| 1145 | const text = self.tokenText(tok); | |
| 1078 | const text = ass.tokenText(tok); | |
| 1146 | 1079 | const prefix = "Op"; |
| 1147 | 1080 | const looks_like_opcode = text.len > prefix.len and |
| 1148 | 1081 | std.mem.startsWith(u8, text, prefix) and |
src/codegen/spirv/CodeGen.zig created+6188| ... | ... | @@ -0,0 +1,6188 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Allocator = std.mem.Allocator; | |
| 3 | const Target = std.Target; | |
| 4 | const Signedness = std.builtin.Signedness; | |
| 5 | const assert = std.debug.assert; | |
| 6 | const log = std.log.scoped(.codegen); | |
| 7 | ||
| 8 | const Zcu = @import("../../Zcu.zig"); | |
| 9 | const Type = @import("../../Type.zig"); | |
| 10 | const Value = @import("../../Value.zig"); | |
| 11 | const Air = @import("../../Air.zig"); | |
| 12 | const InternPool = @import("../../InternPool.zig"); | |
| 13 | const Section = @import("Section.zig"); | |
| 14 | const Assembler = @import("Assembler.zig"); | |
| 15 | ||
| 16 | const spec = @import("spec.zig"); | |
| 17 | const Opcode = spec.Opcode; | |
| 18 | const Word = spec.Word; | |
| 19 | const Id = spec.Id; | |
| 20 | const IdRange = spec.IdRange; | |
| 21 | const StorageClass = spec.StorageClass; | |
| 22 | ||
| 23 | const Module = @import("Module.zig"); | |
| 24 | const Decl = Module.Decl; | |
| 25 | const Repr = Module.Repr; | |
| 26 | const InternMap = Module.InternMap; | |
| 27 | const PtrTypeMap = Module.PtrTypeMap; | |
| 28 | ||
| 29 | const CodeGen = @This(); | |
| 30 | ||
| 31 | pub 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 | ||
| 42 | pub const zig_call_abi_ver = 3; | |
| 43 | ||
| 44 | const 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 | ||
| 145 | pt: Zcu.PerThread, | |
| 146 | air: Air, | |
| 147 | liveness: Air.Liveness, | |
| 148 | owner_nav: InternPool.Nav.Index, | |
| 149 | module: *Module, | |
| 150 | control_flow: ControlFlow, | |
| 151 | base_line: u32, | |
| 152 | block_label: Id = .none, | |
| 153 | next_arg_index: u32 = 0, | |
| 154 | args: std.ArrayListUnmanaged(Id) = .empty, | |
| 155 | inst_results: std.AutoHashMapUnmanaged(Air.Inst.Index, Id) = .empty, | |
| 156 | id_scratch: std.ArrayListUnmanaged(Id) = .empty, | |
| 157 | prologue: Section = .{}, | |
| 158 | body: Section = .{}, | |
| 159 | error_msg: ?*Zcu.ErrorMsg = null, | |
| 160 | ||
| 161 | pub 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 | ||
| 171 | const Error = error{ CodegenFail, OutOfMemory }; | |
| 172 | ||
| 173 | pub 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 | ||
| 369 | pub 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 | ||
| 378 | pub 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. | |
| 384 | fn 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. | |
| 394 | fn 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 | ||
| 417 | fn 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 | ||
| 509 | fn 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. | |
| 526 | fn 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? | |
| 537 | fn 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 | ||
| 545 | const 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 | ||
| 581 | fn 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 | |
| 626 | fn 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. | |
| 655 | fn 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. | |
| 665 | fn 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 | ||
| 724 | pub 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. | |
| 737 | fn 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. | |
| 758 | fn 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 | ||
| 1018 | fn 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 | ||
| 1036 | fn 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 | ||
| 1113 | fn 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 | ||
| 1157 | fn 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. | |
| 1209 | fn 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. | |
| 1237 | fn 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 | ||
| 1298 | fn 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 | ||
| 1314 | fn 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 | ||
| 1610 | const 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 | ||
| 1625 | fn 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 | ||
| 1638 | const 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 | ||
| 1656 | fn 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. | |
| 1725 | const 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. | |
| 1816 | fn 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. | |
| 1825 | const 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`. | |
| 1960 | fn 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. | |
| 1981 | fn 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 | ||
| 2024 | fn 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 | ||
| 2063 | fn 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 | ||
| 2093 | fn 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 | ||
| 2116 | const 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 | ||
| 2178 | fn 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 | ||
| 2222 | fn 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. | |
| 2249 | fn 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. | |
| 2372 | fn 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 | ||
| 2513 | fn intFromBool(cg: *CodeGen, value: Temporary) !Temporary { | |
| 2514 | return try cg.intFromBool2(value, Type.u1); | |
| 2515 | } | |
| 2516 | ||
| 2517 | fn 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). | |
| 2530 | fn 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). | |
| 2557 | fn 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 | ||
| 2568 | fn 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 | ||
| 2582 | fn 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 | ||
| 2596 | const MemoryOptions = struct { | |
| 2597 | is_volatile: bool = false, | |
| 2598 | }; | |
| 2599 | ||
| 2600 | fn 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 | ||
| 2618 | fn 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 | ||
| 2628 | fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) !void { | |
| 2629 | for (body) |inst| { | |
| 2630 | try cg.genInst(inst); | |
| 2631 | } | |
| 2632 | } | |
| 2633 | ||
| 2634 | fn 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 | ||
| 2810 | fn 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 | ||
| 2819 | fn 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 | ||
| 2856 | const 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 | ||
| 2905 | fn 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 | ||
| 2915 | fn 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). | |
| 2954 | fn 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 | ||
| 2976 | fn 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 | ||
| 3033 | fn 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 | ||
| 3061 | fn 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 | ||
| 3068 | fn 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 | ||
| 3095 | fn 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 | ||
| 3104 | fn 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 | ||
| 3130 | fn 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 | ||
| 3199 | fn 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 | ||
| 3371 | fn 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 | ||
| 3412 | fn 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 | ||
| 3428 | fn 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 | ||
| 3453 | fn 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 | ||
| 3464 | fn 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 | ||
| 3473 | fn 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 | ||
| 3545 | fn 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 | ||
| 3570 | fn 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 | ||
| 3598 | fn 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. | |
| 3618 | fn 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 | ||
| 3634 | fn 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 | ||
| 3676 | fn 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 | ||
| 3697 | fn 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 | ||
| 3708 | fn 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 | ||
| 3727 | fn 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 | ||
| 3901 | fn 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 | ||
| 3914 | fn 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. | |
| 3926 | fn 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 | ||
| 3994 | fn 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 | ||
| 4007 | fn 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 | ||
| 4033 | fn 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 | ||
| 4044 | fn 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 | ||
| 4052 | fn 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 | ||
| 4072 | fn 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 | ||
| 4079 | fn 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 | ||
| 4099 | fn 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 | ||
| 4107 | fn 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 | ||
| 4126 | fn 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 | ||
| 4150 | fn 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 | ||
| 4160 | fn 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 | ||
| 4290 | fn 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 | ||
| 4305 | fn 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 | ||
| 4314 | fn 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 | ||
| 4330 | fn airMemmove(cg: *CodeGen, inst: Air.Inst.Index) !void { | |
| 4331 | _ = inst; | |
| 4332 | return cg.fail("TODO implement airMemcpy for spirv", .{}); | |
| 4333 | } | |
| 4334 | ||
| 4335 | fn 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 | ||
| 4342 | fn 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 | ||
| 4359 | fn 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 | ||
| 4376 | fn 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 | ||
| 4392 | fn 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 | ||
| 4409 | fn 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 | ||
| 4462 | fn 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 | ||
| 4473 | fn 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 | ||
| 4496 | fn 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 | ||
| 4520 | fn 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 | ||
| 4535 | fn 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 | ||
| 4628 | fn 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 | ||
| 4644 | fn 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 | ||
| 4734 | fn 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 | ||
| 4766 | fn 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 | ||
| 4816 | fn 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 | ||
| 4824 | const 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. | |
| 4836 | fn 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 | ||
| 4857 | fn 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 | ||
| 4866 | fn 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. | |
| 4875 | fn 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. | |
| 4894 | fn 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. | |
| 4925 | fn 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 | ||
| 5024 | fn 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 | ||
| 5030 | fn 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 | ||
| 5170 | fn 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 | ||
| 5208 | fn 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 | ||
| 5271 | fn 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 | ||
| 5323 | fn 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 | ||
| 5334 | fn 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 | ||
| 5345 | fn 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 | ||
| 5367 | fn 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 | ||
| 5394 | fn 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 | ||
| 5465 | fn 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 | ||
| 5488 | fn 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 | ||
| 5501 | fn 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 | ||
| 5527 | fn 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 | ||
| 5550 | fn 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 | ||
| 5627 | fn 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 | ||
| 5665 | fn 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 | ||
| 5681 | fn 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 | ||
| 5705 | fn 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 | ||
| 5727 | fn 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 | ||
| 5884 | fn airUnreach(cg: *CodeGen) !void { | |
| 5885 | try cg.body.emit(cg.module.gpa, .OpUnreachable, {}); | |
| 5886 | } | |
| 5887 | ||
| 5888 | fn 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 | ||
| 5902 | fn 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 | ||
| 5912 | fn 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 | ||
| 5919 | fn 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 | ||
| 6080 | fn 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 | ||
| 6134 | fn 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 | ||
| 6158 | fn 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. | |
| 6166 | fn 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 | ||
| 6173 | fn 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 | ||
| 6180 | fn 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 | ||
| 6185 | fn 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. | |
| 4 | 5 | //! |
| 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. | |
| 8 | const 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. | |
| 10 | 10 | const std = @import("std"); |
| 11 | 11 | const Allocator = std.mem.Allocator; |
| 12 | 12 | const assert = std.debug.assert; |
| 13 | const autoHashStrat = std.hash.autoHashStrat; | |
| 14 | const Wyhash = std.hash.Wyhash; | |
| 15 | 13 | |
| 14 | const Zcu = @import("../../Zcu.zig"); | |
| 15 | const InternPool = @import("../../InternPool.zig"); | |
| 16 | const Section = @import("Section.zig"); | |
| 16 | 17 | const spec = @import("spec.zig"); |
| 17 | 18 | const Word = spec.Word; |
| 18 | 19 | const Id = spec.Id; |
| 19 | 20 | |
| 20 | const Section = @import("Section.zig"); | |
| 21 | const Module = @This(); | |
| 21 | 22 | |
| 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. | |
| 27 | pub 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 | } | |
| 23 | gpa: Allocator, | |
| 24 | arena: Allocator, | |
| 25 | zcu: *Zcu, | |
| 26 | nav_link: std.AutoHashMapUnmanaged(InternPool.Nav.Index, Decl.Index) = .empty, | |
| 27 | uav_link: std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Decl.Index) = .empty, | |
| 28 | intern_map: std.AutoHashMapUnmanaged(struct { InternPool.Index, Repr }, Id) = .empty, | |
| 29 | decls: std.ArrayListUnmanaged(Decl) = .empty, | |
| 30 | decl_deps: std.ArrayListUnmanaged(Decl.Index) = .empty, | |
| 31 | entry_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. | |
| 38 | ptr_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. | |
| 41 | error_buffer: ?Decl.Index = null, | |
| 42 | /// SPIR-V instructions return result-ids. | |
| 43 | /// This variable holds the module-wide counter for these. | |
| 44 | next_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. | |
| 53 | cache: 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, | |
| 47 | 63 | |
| 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". | |
| 75 | sections: 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 | ||
| 88 | pub 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. | |
| 93 | pub 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, | |
| 55 | 98 | }; |
| 56 | 99 | |
| 57 | 100 | /// Declarations, both functions and globals, can have dependencies. These are used for 2 things: |
| ... | ... | @@ -82,201 +125,166 @@ pub const Decl = struct { |
| 82 | 125 | /// - For `invocation_global`, this is the result-id of the associated InvocationGlobal instruction. |
| 83 | 126 | result_id: Id, |
| 84 | 127 | /// The offset of the first dependency of this decl in the `decl_deps` array. |
| 85 | begin_dep: u32, | |
| 128 | begin_dep: usize = 0, | |
| 86 | 129 | /// The past-end offset of the dependencies of this decl in the `decl_deps` array. |
| 87 | end_dep: u32, | |
| 130 | end_dep: usize = 0, | |
| 88 | 131 | }; |
| 89 | 132 | |
| 90 | 133 | /// This models a kernel entry point. |
| 91 | 134 | pub const EntryPoint = struct { |
| 92 | 135 | /// The declaration that should be exported. |
| 93 | decl_index: ?Decl.Index = null, | |
| 136 | decl_index: Decl.Index, | |
| 94 | 137 | /// The name of the kernel to be exported. |
| 95 | name: ?[]const u8 = null, | |
| 138 | name: []const u8, | |
| 96 | 139 | /// Calling Convention |
| 97 | exec_model: ?spec.ExecutionModel = null, | |
| 140 | exec_model: spec.ExecutionModel, | |
| 98 | 141 | exec_mode: ?spec.ExecutionMode = null, |
| 99 | 142 | }; |
| 100 | 143 | |
| 101 | /// A general-purpose allocator which may be used to allocate resources for this module | |
| 102 | gpa: Allocator, | |
| 103 | ||
| 104 | /// Arena for things that need to live for the length of this program. | |
| 105 | arena: std.heap.ArenaAllocator, | |
| 106 | ||
| 107 | /// Target info | |
| 108 | target: *const std.Target, | |
| 144 | const StructType = struct { | |
| 145 | fields: []const Id, | |
| 146 | ip_index: InternPool.Index, | |
| 109 | 147 | |
| 110 | /// The target SPIR-V version | |
| 111 | version: spec.Version, | |
| 112 | ||
| 113 | /// Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module". | |
| 114 | sections: 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. | |
| 148 | next_result_id: Word, | |
| 149 | ||
| 150 | /// Cache for results of OpString instructions. | |
| 151 | strings: 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. | |
| 161 | cache: 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. | |
| 179 | decls: 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`. | |
| 183 | decl_deps: std.ArrayListUnmanaged(Decl.Index) = .empty, | |
| 184 | ||
| 185 | /// The list of entry points that should be exported from this module. | |
| 186 | entry_points: std.AutoArrayHashMapUnmanaged(Id, EntryPoint) = .empty, | |
| 187 | ||
| 188 | pub 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 | } | |
| 199 | 155 | |
| 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 | } | |
| 206 | 159 | }; |
| 207 | } | |
| 208 | ||
| 209 | pub 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 | }; | |
| 222 | 161 | |
| 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); | |
| 162 | const FnType = struct { | |
| 163 | return_ty: Id, | |
| 164 | params: []const Id, | |
| 232 | 165 | |
| 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 | } | |
| 236 | 173 | |
| 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 | }; | |
| 238 | 180 | |
| 239 | self.* = undefined; | |
| 240 | } | |
| 181 | const 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 | } | |
| 241 | 196 | |
| 242 | pub 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 | }; | |
| 245 | 207 | |
| 246 | pub fn at(range: IdRange, i: usize) Id { | |
| 247 | assert(i < range.len); | |
| 248 | return @enumFromInt(range.base + i); | |
| 208 | pub 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. | |
| 250 | pub 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); | |
| 249 | 262 | } |
| 250 | }; | |
| 251 | 263 | |
| 252 | pub 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.*; | |
| 258 | 265 | } |
| 259 | 266 | |
| 260 | pub fn allocId(self: *Module) Id { | |
| 261 | return self.allocIds(1).at(0); | |
| 267 | pub fn allocIds(module: *Module, n: u32) spec.IdRange { | |
| 268 | defer module.next_result_id += n; | |
| 269 | return .{ .base = module.next_result_id, .len = n }; | |
| 262 | 270 | } |
| 263 | 271 | |
| 264 | pub fn idBound(self: Module) Word { | |
| 265 | return self.next_result_id; | |
| 272 | pub fn allocId(module: *Module) Id { | |
| 273 | return module.allocIds(1).at(0); | |
| 266 | 274 | } |
| 267 | 275 | |
| 268 | pub fn hasFeature(self: *Module, feature: std.Target.spirv.Feature) bool { | |
| 269 | return self.target.cpu.has(.spirv, feature); | |
| 276 | pub fn idBound(module: Module) Word { | |
| 277 | return module.next_result_id; | |
| 270 | 278 | } |
| 271 | 279 | |
| 272 | fn addEntryPointDeps( | |
| 273 | self: *Module, | |
| 280 | pub fn addEntryPointDeps( | |
| 281 | module: *Module, | |
| 274 | 282 | decl_index: Decl.Index, |
| 275 | 283 | seen: *std.DynamicBitSetUnmanaged, |
| 276 | 284 | interface: *std.ArrayList(Id), |
| 277 | 285 | ) !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]; | |
| 280 | 288 | |
| 281 | 289 | if (seen.isSet(@intFromEnum(decl_index))) { |
| 282 | 290 | return; |
| ... | ... | @@ -289,36 +297,38 @@ fn addEntryPointDeps( |
| 289 | 297 | } |
| 290 | 298 | |
| 291 | 299 | for (deps) |dep| { |
| 292 | try self.addEntryPointDeps(dep, seen, interface); | |
| 300 | try module.addEntryPointDeps(dep, seen, interface); | |
| 293 | 301 | } |
| 294 | 302 | } |
| 295 | 303 | |
| 296 | fn entryPoints(self: *Module) !Section { | |
| 304 | fn entryPoints(module: *Module) !Section { | |
| 305 | const target = module.zcu.getTarget(); | |
| 306 | ||
| 297 | 307 | var entry_points = Section{}; |
| 298 | errdefer entry_points.deinit(self.gpa); | |
| 308 | errdefer entry_points.deinit(module.gpa); | |
| 299 | 309 | |
| 300 | var interface = std.ArrayList(Id).init(self.gpa); | |
| 310 | var interface = std.ArrayList(Id).init(module.gpa); | |
| 301 | 311 | defer interface.deinit(); |
| 302 | 312 | |
| 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); | |
| 305 | 315 | |
| 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| { | |
| 307 | 317 | 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); | |
| 309 | 319 | |
| 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, | |
| 313 | 323 | .entry_point = entry_point_id, |
| 314 | .name = entry_point.name.?, | |
| 324 | .name = entry_point.name, | |
| 315 | 325 | .interface = interface.items, |
| 316 | 326 | }); |
| 317 | 327 | |
| 318 | 328 | if (entry_point.exec_mode == null and entry_point.exec_model == .fragment) { |
| 319 | switch (self.target.os.tag) { | |
| 329 | switch (target.os.tag) { | |
| 320 | 330 | .vulkan, .opengl => |tag| { |
| 321 | try self.sections.execution_modes.emit(self.gpa, .OpExecutionMode, .{ | |
| 331 | try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{ | |
| 322 | 332 | .entry_point = entry_point_id, |
| 323 | 333 | .mode = if (tag == .vulkan) .origin_upper_left else .origin_lower_left, |
| 324 | 334 | }); |
| ... | ... | @@ -332,83 +342,100 @@ fn entryPoints(self: *Module) !Section { |
| 332 | 342 | return entry_points; |
| 333 | 343 | } |
| 334 | 344 | |
| 335 | pub fn finalize(self: *Module, a: Allocator) ![]Word { | |
| 345 | pub fn finalize(module: *Module, gpa: Allocator) ![]Word { | |
| 346 | const target = module.zcu.getTarget(); | |
| 347 | ||
| 336 | 348 | // Emit capabilities and extensions |
| 337 | switch (self.target.os.tag) { | |
| 349 | switch (target.os.tag) { | |
| 338 | 350 | .opengl => { |
| 339 | try self.addCapability(.shader); | |
| 340 | try self.addCapability(.matrix); | |
| 351 | try module.addCapability(.shader); | |
| 352 | try module.addCapability(.matrix); | |
| 341 | 353 | }, |
| 342 | 354 | .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); | |
| 348 | 360 | } |
| 349 | 361 | }, |
| 350 | 362 | .opencl, .amdhsa => { |
| 351 | try self.addCapability(.kernel); | |
| 352 | try self.addCapability(.addresses); | |
| 363 | try module.addCapability(.kernel); | |
| 364 | try module.addCapability(.addresses); | |
| 353 | 365 | }, |
| 354 | 366 | else => unreachable, |
| 355 | 367 | } |
| 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); | |
| 365 | 373 | } |
| 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); | |
| 369 | 380 | } |
| 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); | |
| 374 | 389 | } |
| 375 | 390 | // These are well supported |
| 376 | try self.addCapability(.int8); | |
| 377 | try self.addCapability(.int16); | |
| 391 | try module.addCapability(.int8); | |
| 392 | try module.addCapability(.int16); | |
| 378 | 393 | |
| 379 | 394 | // Emit memory model |
| 380 | const addressing_model: spec.AddressingModel = switch (self.target.os.tag) { | |
| 395 | const addressing_model: spec.AddressingModel = switch (target.os.tag) { | |
| 381 | 396 | .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, | |
| 384 | 399 | .amdhsa => .physical64, |
| 385 | 400 | else => unreachable, |
| 386 | 401 | }; |
| 387 | try self.sections.memory_model.emit(self.gpa, .OpMemoryModel, .{ | |
| 402 | try module.sections.memory_model.emit(module.gpa, .OpMemoryModel, .{ | |
| 388 | 403 | .addressing_model = addressing_model, |
| 389 | .memory_model = switch (self.target.os.tag) { | |
| 404 | .memory_model = switch (target.os.tag) { | |
| 390 | 405 | .opencl => .open_cl, |
| 391 | 406 | .vulkan, .opengl => .glsl450, |
| 392 | 407 | else => unreachable, |
| 393 | 408 | }, |
| 394 | 409 | }); |
| 395 | 410 | |
| 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 | }; | |
| 400 | 427 | |
| 401 | 428 | const header = [_]Word{ |
| 402 | 429 | spec.magic_number, |
| 403 | self.version.toWord(), | |
| 430 | version.toWord(), | |
| 404 | 431 | spec.zig_generator_id, |
| 405 | self.idBound(), | |
| 432 | module.idBound(), | |
| 406 | 433 | 0, // Schema (currently reserved for future use) |
| 407 | 434 | }; |
| 408 | 435 | |
| 409 | 436 | 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, .{ | |
| 412 | 439 | .source_language = .zig, |
| 413 | 440 | .version = 0, |
| 414 | 441 | // 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 { |
| 421 | 448 | // Note: needs to be kept in order according to section 2.3! |
| 422 | 449 | const buffers = &[_][]const Word{ |
| 423 | 450 | &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(), | |
| 428 | 455 | entry_points.toWords(), |
| 429 | self.sections.execution_modes.toWords(), | |
| 456 | module.sections.execution_modes.toWords(), | |
| 430 | 457 | 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(), | |
| 436 | 463 | }; |
| 437 | 464 | |
| 438 | 465 | var total_result_size: usize = 0; |
| 439 | 466 | for (buffers) |buffer| { |
| 440 | 467 | total_result_size += buffer.len; |
| 441 | 468 | } |
| 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; | |
| 444 | 471 | |
| 445 | 472 | var offset: usize = 0; |
| 446 | 473 | for (buffers) |buffer| { |
| ... | ... | @@ -451,34 +478,27 @@ pub fn finalize(self: *Module, a: Allocator) ![]Word { |
| 451 | 478 | return result; |
| 452 | 479 | } |
| 453 | 480 | |
| 454 | /// Merge the sections making up a function declaration into this module. | |
| 455 | pub 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 | ||
| 461 | pub fn addCapability(self: *Module, cap: spec.Capability) !void { | |
| 462 | const entry = try self.cache.capabilities.getOrPut(self.gpa, cap); | |
| 481 | pub fn addCapability(module: *Module, cap: spec.Capability) !void { | |
| 482 | const entry = try module.cache.capabilities.getOrPut(module.gpa, cap); | |
| 463 | 483 | 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 }); | |
| 465 | 485 | } |
| 466 | 486 | |
| 467 | pub fn addExtension(self: *Module, ext: []const u8) !void { | |
| 468 | const entry = try self.cache.extensions.getOrPut(self.gpa, ext); | |
| 487 | pub fn addExtension(module: *Module, ext: []const u8) !void { | |
| 488 | const entry = try module.cache.extensions.getOrPut(module.gpa, ext); | |
| 469 | 489 | 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 }); | |
| 471 | 491 | } |
| 472 | 492 | |
| 473 | 493 | /// Imports or returns the existing id of an extended instruction set |
| 474 | pub fn importInstructionSet(self: *Module, set: spec.InstructionSet) !Id { | |
| 494 | pub fn importInstructionSet(module: *Module, set: spec.InstructionSet) !Id { | |
| 475 | 495 | assert(set != .core); |
| 476 | 496 | |
| 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); | |
| 478 | 498 | if (gop.found_existing) return gop.value_ptr.*; |
| 479 | 499 | |
| 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, .{ | |
| 482 | 502 | .id_result = result_id, |
| 483 | 503 | .name = @tagName(set), |
| 484 | 504 | }); |
| ... | ... | @@ -487,104 +507,130 @@ pub fn importInstructionSet(self: *Module, set: spec.InstructionSet) !Id { |
| 487 | 507 | return result_id; |
| 488 | 508 | } |
| 489 | 509 | |
| 490 | /// Fetch the result-id of an instruction corresponding to a string. | |
| 491 | pub 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 | } | |
| 510 | pub fn boolType(module: *Module) !Id { | |
| 511 | if (module.cache.bool_type) |id| return id; | |
| 506 | 512 | |
| 507 | pub 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, .{ | |
| 509 | 515 | .id_result = result_id, |
| 510 | .id_ref = types, | |
| 511 | 516 | }); |
| 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; | |
| 519 | 519 | } |
| 520 | 520 | |
| 521 | pub fn boolType(self: *Module) !Id { | |
| 522 | if (self.cache.bool_type) |id| return id; | |
| 521 | pub fn voidType(module: *Module) !Id { | |
| 522 | if (module.cache.void_type) |id| return id; | |
| 523 | 523 | |
| 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, .{ | |
| 526 | 526 | .id_result = result_id, |
| 527 | 527 | }); |
| 528 | self.cache.bool_type = result_id; | |
| 528 | module.cache.void_type = result_id; | |
| 529 | try module.debugName(result_id, "void"); | |
| 529 | 530 | return result_id; |
| 530 | 531 | } |
| 531 | 532 | |
| 532 | pub 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, .{ | |
| 533 | pub 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, .{ | |
| 537 | 538 | .id_result = result_id, |
| 539 | .literal_string = name_dup, | |
| 538 | 540 | }); |
| 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); | |
| 541 | 543 | return result_id; |
| 542 | 544 | } |
| 543 | 545 | |
| 544 | pub fn intType(self: *Module, signedness: std.builtin.Signedness, bits: u16) !Id { | |
| 546 | pub 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 | ||
| 574 | pub fn intType(module: *Module, signedness: std.builtin.Signedness, bits: u16) !Id { | |
| 545 | 575 | 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 }); | |
| 547 | 593 | if (!entry.found_existing) { |
| 548 | const result_id = self.allocId(); | |
| 594 | const result_id = module.allocId(); | |
| 549 | 595 | 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, .{ | |
| 551 | 597 | .id_result = result_id, |
| 552 | .width = bits, | |
| 553 | .signedness = switch (signedness) { | |
| 598 | .width = backing_bits, | |
| 599 | .signedness = switch (actual_signedness) { | |
| 554 | 600 | .signed => 1, |
| 555 | 601 | .unsigned => 0, |
| 556 | 602 | }, |
| 557 | 603 | }); |
| 558 | 604 | |
| 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}), | |
| 562 | 608 | } |
| 563 | 609 | } |
| 564 | 610 | return entry.value_ptr.*; |
| 565 | 611 | } |
| 566 | 612 | |
| 567 | pub fn floatType(self: *Module, bits: u16) !Id { | |
| 613 | pub fn floatType(module: *Module, bits: u16) !Id { | |
| 568 | 614 | 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 }); | |
| 570 | 616 | if (!entry.found_existing) { |
| 571 | const result_id = self.allocId(); | |
| 617 | const result_id = module.allocId(); | |
| 572 | 618 | 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, .{ | |
| 574 | 620 | .id_result = result_id, |
| 575 | 621 | .width = bits, |
| 576 | 622 | }); |
| 577 | try self.debugNameFmt(result_id, "f{}", .{bits}); | |
| 623 | try module.debugNameFmt(result_id, "f{}", .{bits}); | |
| 578 | 624 | } |
| 579 | 625 | return entry.value_ptr.*; |
| 580 | 626 | } |
| 581 | 627 | |
| 582 | pub 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 }); | |
| 628 | pub 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 }); | |
| 584 | 630 | if (!entry.found_existing) { |
| 585 | const result_id = self.allocId(); | |
| 631 | const result_id = module.allocId(); | |
| 586 | 632 | 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, .{ | |
| 588 | 634 | .id_result = result_id, |
| 589 | 635 | .component_type = child_ty_id, |
| 590 | 636 | .component_count = len, |
| ... | ... | @@ -593,12 +639,12 @@ pub fn vectorType(self: *Module, len: u32, child_ty_id: Id) !Id { |
| 593 | 639 | return entry.value_ptr.*; |
| 594 | 640 | } |
| 595 | 641 | |
| 596 | pub 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 }); | |
| 642 | pub 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 }); | |
| 598 | 644 | if (!entry.found_existing) { |
| 599 | const result_id = self.allocId(); | |
| 645 | const result_id = module.allocId(); | |
| 600 | 646 | 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, .{ | |
| 602 | 648 | .id_result = result_id, |
| 603 | 649 | .element_type = child_ty_id, |
| 604 | 650 | .length = len_id, |
| ... | ... | @@ -607,37 +653,114 @@ pub fn arrayType(self: *Module, len_id: Id, child_ty_id: Id) !Id { |
| 607 | 653 | return entry.value_ptr.*; |
| 608 | 654 | } |
| 609 | 655 | |
| 610 | pub 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, .{ | |
| 656 | pub 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 | ||
| 671 | pub 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, .{ | |
| 613 | 684 | .id_result = result_id, |
| 614 | .return_type = return_ty_id, | |
| 615 | .id_ref_2 = param_type_ids, | |
| 685 | .id_ref = types_dup, | |
| 616 | 686 | }); |
| 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 | ); | |
| 617 | 719 | return result_id; |
| 618 | 720 | } |
| 619 | 721 | |
| 620 | pub 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, | |
| 722 | pub 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, .{ | |
| 625 | 730 | .id_result = result_id, |
| 626 | .value = value, | |
| 731 | .return_type = return_ty_id, | |
| 732 | .id_ref_2 = params_dup, | |
| 627 | 733 | }); |
| 734 | try module.cache.fn_types.put(module.gpa, .{ | |
| 735 | .return_ty = return_ty_id, | |
| 736 | .params = params_dup, | |
| 737 | }, result_id); | |
| 628 | 738 | return result_id; |
| 629 | 739 | } |
| 630 | 740 | |
| 631 | pub fn constBool(self: *Module, value: bool) !Id { | |
| 632 | if (self.cache.bool_const[@intFromBool(value)]) |b| return b; | |
| 741 | pub 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 | ||
| 754 | pub fn constBool(module: *Module, value: bool) !Id { | |
| 755 | if (module.cache.bool_const[@intFromBool(value)]) |b| return b; | |
| 633 | 756 | |
| 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; | |
| 637 | 760 | |
| 638 | 761 | 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, | |
| 641 | 764 | if (value_ct) .OpConstantTrue else .OpConstantFalse, |
| 642 | 765 | .{ |
| 643 | 766 | .id_result_type = result_ty_id, |
| ... | ... | @@ -649,37 +772,40 @@ pub fn constBool(self: *Module, value: bool) !Id { |
| 649 | 772 | return result_id; |
| 650 | 773 | } |
| 651 | 774 | |
| 652 | /// Return a pointer to a builtin variable. `result_ty_id` must be a **pointer** | |
| 653 | /// with storage class `.Input`. | |
| 654 | pub 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, .{ | |
| 775 | pub 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, .{ | |
| 661 | 788 | .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, | |
| 664 | 791 | }); |
| 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 } }); | |
| 667 | 793 | } |
| 668 | return entry.value_ptr.*; | |
| 794 | return gop.value_ptr.*; | |
| 669 | 795 | } |
| 670 | 796 | |
| 671 | pub 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, .{ | |
| 797 | pub fn constUndef(module: *Module, ty_id: Id) !Id { | |
| 798 | const result_id = module.allocId(); | |
| 799 | try module.sections.globals.emit(module.gpa, .OpUndef, .{ | |
| 674 | 800 | .id_result_type = ty_id, |
| 675 | 801 | .id_result = result_id, |
| 676 | 802 | }); |
| 677 | 803 | return result_id; |
| 678 | 804 | } |
| 679 | 805 | |
| 680 | pub 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, .{ | |
| 806 | pub fn constNull(module: *Module, ty_id: Id) !Id { | |
| 807 | const result_id = module.allocId(); | |
| 808 | try module.sections.globals.emit(module.gpa, .OpConstantNull, .{ | |
| 683 | 809 | .id_result_type = ty_id, |
| 684 | 810 | .id_result = result_id, |
| 685 | 811 | }); |
| ... | ... | @@ -688,13 +814,13 @@ pub fn constNull(self: *Module, ty_id: Id) !Id { |
| 688 | 814 | |
| 689 | 815 | /// Decorate a result-id. |
| 690 | 816 | pub fn decorate( |
| 691 | self: *Module, | |
| 817 | module: *Module, | |
| 692 | 818 | target: Id, |
| 693 | 819 | decoration: spec.Decoration.Extended, |
| 694 | 820 | ) !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, .{ | |
| 698 | 824 | .target = target, |
| 699 | 825 | .decoration = decoration, |
| 700 | 826 | }); |
| ... | ... | @@ -704,79 +830,112 @@ pub fn decorate( |
| 704 | 830 | /// Decorate a result-id which is a member of some struct. |
| 705 | 831 | /// We really don't have to and shouldn't need to cache this. |
| 706 | 832 | pub fn decorateMember( |
| 707 | self: *Module, | |
| 833 | module: *Module, | |
| 708 | 834 | structure_type: Id, |
| 709 | 835 | member: u32, |
| 710 | 836 | decoration: spec.Decoration.Extended, |
| 711 | 837 | ) !void { |
| 712 | try self.sections.annotations.emit(self.gpa, .OpMemberDecorate, .{ | |
| 838 | try module.sections.annotations.emit(module.gpa, .OpMemberDecorate, .{ | |
| 713 | 839 | .structure_type = structure_type, |
| 714 | 840 | .member = member, |
| 715 | 841 | .decoration = decoration, |
| 716 | 842 | }); |
| 717 | 843 | } |
| 718 | 844 | |
| 719 | pub fn allocDecl(self: *Module, kind: Decl.Kind) !Decl.Index { | |
| 720 | try self.decls.append(self.gpa, .{ | |
| 845 | pub fn allocDecl(module: *Module, kind: Decl.Kind) !Decl.Index { | |
| 846 | try module.decls.append(module.gpa, .{ | |
| 721 | 847 | .kind = kind, |
| 722 | .result_id = self.allocId(), | |
| 723 | .begin_dep = undefined, | |
| 724 | .end_dep = undefined, | |
| 848 | .result_id = module.allocId(), | |
| 725 | 849 | }); |
| 726 | 850 | |
| 727 | return @as(Decl.Index, @enumFromInt(@as(u32, @intCast(self.decls.items.len - 1)))); | |
| 728 | } | |
| 729 | ||
| 730 | pub 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)))); | |
| 732 | 852 | } |
| 733 | 853 | |
| 734 | /// Declare ALL dependencies for a decl. | |
| 735 | pub 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; | |
| 854 | pub fn declPtr(module: *Module, index: Decl.Index) *Decl { | |
| 855 | return &module.decls.items[@intFromEnum(index)]; | |
| 743 | 856 | } |
| 744 | 857 | |
| 745 | 858 | /// Declare a SPIR-V function as an entry point. This causes an extra wrapper |
| 746 | 859 | /// function to be generated, which is then exported as the real entry point. The purpose of this |
| 747 | 860 | /// wrapper is to allocate and initialize the structure holding the instance globals. |
| 748 | 861 | pub fn declareEntryPoint( |
| 749 | self: *Module, | |
| 862 | module: *Module, | |
| 750 | 863 | decl_index: Decl.Index, |
| 751 | 864 | name: []const u8, |
| 752 | 865 | exec_model: spec.ExecutionModel, |
| 753 | 866 | exec_mode: ?spec.ExecutionMode, |
| 754 | 867 | ) !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); | |
| 756 | 869 | 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; | |
| 758 | 871 | gop.value_ptr.exec_model = exec_model; |
| 759 | 872 | // Might've been set by assembler |
| 760 | 873 | if (!gop.found_existing) gop.value_ptr.exec_mode = exec_mode; |
| 761 | 874 | } |
| 762 | 875 | |
| 763 | pub fn debugName(self: *Module, target: Id, name: []const u8) !void { | |
| 764 | try self.sections.debug_names.emit(self.gpa, .OpName, .{ | |
| 876 | pub fn debugName(module: *Module, target: Id, name: []const u8) !void { | |
| 877 | try module.sections.debug_names.emit(module.gpa, .OpName, .{ | |
| 765 | 878 | .target = target, |
| 766 | 879 | .name = name, |
| 767 | 880 | }); |
| 768 | 881 | } |
| 769 | 882 | |
| 770 | pub 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); | |
| 883 | pub 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); | |
| 774 | 887 | } |
| 775 | 888 | |
| 776 | pub fn memberDebugName(self: *Module, target: Id, member: u32, name: []const u8) !void { | |
| 777 | try self.sections.debug_names.emit(self.gpa, .OpMemberName, .{ | |
| 889 | pub fn memberDebugName(module: *Module, target: Id, member: u32, name: []const u8) !void { | |
| 890 | try module.sections.debug_names.emit(module.gpa, .OpMemberName, .{ | |
| 778 | 891 | .type = target, |
| 779 | 892 | .member = member, |
| 780 | 893 | .name = name, |
| 781 | 894 | }); |
| 782 | 895 | } |
| 896 | ||
| 897 | pub 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 | ||
| 909 | pub 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); |
| 13 | 13 | |
| 14 | 14 | const Opcode = spec.Opcode; |
| 15 | 15 | |
| 16 | /// The instructions in this section. Memory is owned by the Module | |
| 17 | /// externally associated to this Section. | |
| 18 | 16 | instructions: std.ArrayListUnmanaged(Word) = .empty, |
| 19 | 17 | |
| 20 | 18 | pub fn deinit(section: *Section, allocator: Allocator) void { |
| ... | ... | @@ -22,9 +20,8 @@ pub fn deinit(section: *Section, allocator: Allocator) void { |
| 22 | 20 | section.* = undefined; |
| 23 | 21 | } |
| 24 | 22 | |
| 25 | /// Clear the instructions in this section | |
| 26 | 23 | pub fn reset(section: *Section) void { |
| 27 | section.instructions.items.len = 0; | |
| 24 | section.instructions.clearRetainingCapacity(); | |
| 28 | 25 | } |
| 29 | 26 | |
| 30 | 27 | pub fn toWords(section: Section) []Word { |
| ... | ... | @@ -36,9 +33,12 @@ pub fn append(section: *Section, allocator: Allocator, other_section: Section) ! |
| 36 | 33 | try section.instructions.appendSlice(allocator, other_section.instructions.items); |
| 37 | 34 | } |
| 38 | 35 | |
| 39 | /// Ensure capacity of at least `capacity` more words in this section. | |
| 40 | pub fn ensureUnusedCapacity(section: *Section, allocator: Allocator, capacity: usize) !void { | |
| 41 | try section.instructions.ensureUnusedCapacity(allocator, capacity); | |
| 36 | pub fn ensureUnusedCapacity( | |
| 37 | section: *Section, | |
| 38 | allocator: Allocator, | |
| 39 | words: usize, | |
| 40 | ) !void { | |
| 41 | try section.instructions.ensureUnusedCapacity(allocator, words); | |
| 42 | 42 | } |
| 43 | 43 | |
| 44 | 44 | /// Write an instruction and size, operands are to be inserted manually. |
| ... | ... | @@ -46,7 +46,7 @@ pub fn emitRaw( |
| 46 | 46 | section: *Section, |
| 47 | 47 | allocator: Allocator, |
| 48 | 48 | opcode: Opcode, |
| 49 | operand_words: usize, // opcode itself not included | |
| 49 | operand_words: usize, | |
| 50 | 50 | ) !void { |
| 51 | 51 | const word_count = 1 + operand_words; |
| 52 | 52 | try section.instructions.ensureUnusedCapacity(allocator, word_count); |
| ... | ... | @@ -64,45 +64,26 @@ pub fn emitRawInstruction( |
| 64 | 64 | section.writeWords(operands); |
| 65 | 65 | } |
| 66 | 66 | |
| 67 | pub fn emit( | |
| 67 | pub fn emitAssumeCapacity( | |
| 68 | 68 | section: *Section, |
| 69 | allocator: Allocator, | |
| 70 | 69 | comptime opcode: spec.Opcode, |
| 71 | 70 | operands: opcode.Operands(), |
| 72 | 71 | ) !void { |
| 73 | 72 | const word_count = instructionSize(opcode, operands); |
| 74 | try section.instructions.ensureUnusedCapacity(allocator, word_count); | |
| 75 | 73 | section.writeWord(@as(Word, @intCast(word_count << 16)) | @intFromEnum(opcode)); |
| 76 | 74 | section.writeOperands(opcode.Operands(), operands); |
| 77 | 75 | } |
| 78 | 76 | |
| 79 | pub 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 | ||
| 89 | pub fn emitSpecConstantOp( | |
| 77 | pub fn emit( | |
| 90 | 78 | section: *Section, |
| 91 | 79 | allocator: Allocator, |
| 92 | 80 | comptime opcode: spec.Opcode, |
| 93 | 81 | operands: opcode.Operands(), |
| 94 | 82 | ) !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); | |
| 106 | 87 | } |
| 107 | 88 | |
| 108 | 89 | pub fn writeWord(section: *Section, word: Word) void { |
| ... | ... | @@ -126,7 +107,6 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) |
| 126 | 107 | .void => return, |
| 127 | 108 | else => unreachable, |
| 128 | 109 | }; |
| 129 | ||
| 130 | 110 | inline for (fields) |field| { |
| 131 | 111 | section.writeOperand(field.type, @field(operands, field.name)); |
| 132 | 112 | } |
| ... | ... | @@ -134,30 +114,18 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) |
| 134 | 114 | |
| 135 | 115 | pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void { |
| 136 | 116 | switch (Operand) { |
| 117 | spec.LiteralSpecConstantOpInteger => unreachable, | |
| 137 | 118 | spec.Id => section.writeWord(@intFromEnum(operand)), |
| 138 | ||
| 139 | 119 | spec.LiteralInteger => section.writeWord(operand), |
| 140 | ||
| 141 | 120 | spec.LiteralString => section.writeString(operand), |
| 142 | ||
| 143 | 121 | spec.LiteralContextDependentNumber => section.writeContextDependentNumber(operand), |
| 144 | ||
| 145 | 122 | 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 | ||
| 152 | 123 | spec.PairLiteralIntegerIdRef => section.writeWords(&.{ operand.value, @enumFromInt(operand.label) }), |
| 153 | 124 | spec.PairIdRefLiteralInteger => section.writeWords(&.{ @intFromEnum(operand.target), operand.member }), |
| 154 | 125 | spec.PairIdRefIdRef => section.writeWords(&.{ @intFromEnum(operand[0]), @intFromEnum(operand[1]) }), |
| 155 | ||
| 156 | 126 | else => switch (@typeInfo(Operand)) { |
| 157 | 127 | .@"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), | |
| 161 | 129 | .pointer => |info| { |
| 162 | 130 | std.debug.assert(info.size == .slice); // Should be no other pointer types in the spec. |
| 163 | 131 | for (operand) |item| { |
| ... | ... | @@ -178,18 +146,14 @@ pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) |
| 178 | 146 | } |
| 179 | 147 | |
| 180 | 148 | fn 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 | |
| 183 | 149 | const zero_terminated_len = str.len + 1; |
| 184 | 150 | var i: usize = 0; |
| 185 | 151 | while (i < zero_terminated_len) : (i += @sizeOf(Word)) { |
| 186 | 152 | var word: Word = 0; |
| 187 | ||
| 188 | 153 | var j: usize = 0; |
| 189 | 154 | while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) { |
| 190 | 155 | word |= @as(Word, str[i + j]) << @as(Log2Word, @intCast(j * @bitSizeOf(u8))); |
| 191 | 156 | } |
| 192 | ||
| 193 | 157 | section.instructions.appendAssumeCapacity(word); |
| 194 | 158 | } |
| 195 | 159 | } |
| ... | ... | @@ -233,20 +197,19 @@ fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand |
| 233 | 197 | } |
| 234 | 198 | |
| 235 | 199 | fn 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 | }; | |
| 246 | 209 | } |
| 247 | 210 | |
| 248 | 211 | fn instructionSize(comptime opcode: spec.Opcode, operands: opcode.Operands()) usize { |
| 249 | return 1 + operandsSize(opcode.Operands(), operands); | |
| 212 | return operandsSize(opcode.Operands(), operands) + 1; | |
| 250 | 213 | } |
| 251 | 214 | |
| 252 | 215 | fn operandsSize(comptime Operands: type, operands: Operands) usize { |
| ... | ... | @@ -266,28 +229,14 @@ fn operandsSize(comptime Operands: type, operands: Operands) usize { |
| 266 | 229 | |
| 267 | 230 | fn operandSize(comptime Operand: type, operand: Operand) usize { |
| 268 | 231 | 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, | |
| 276 | 235 | spec.LiteralContextDependentNumber => switch (operand) { |
| 277 | 236 | .int32, .uint32, .float32 => 1, |
| 278 | 237 | .int64, .uint64, .float64 => 2, |
| 279 | 238 | }, |
| 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, | |
| 291 | 240 | else => switch (@typeInfo(Operand)) { |
| 292 | 241 | .@"enum" => 1, |
| 293 | 242 | .optional => |info| if (operand) |child| operandSize(info.child, child) else 0, |
| ... | ... | @@ -299,133 +248,25 @@ fn operandSize(comptime Operand: type, operand: Operand) usize { |
| 299 | 248 | } |
| 300 | 249 | break :blk total; |
| 301 | 250 | }, |
| 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; | |
| 308 | 253 | |
| 309 | fn 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. | |
| 317 | 265 | }, |
| 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, | |
| 320 | 268 | }, |
| 321 | 269 | else => unreachable, |
| 322 | } | |
| 323 | } | |
| 324 | return total + 1; // Add one for the mask itself. | |
| 325 | } | |
| 326 | ||
| 327 | fn 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 | ||
| 338 | test "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 | ||
| 347 | test "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 | ||
| 363 | test "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 | ||
| 388 | test "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 | ||
| 412 | test "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 }, | |
| 420 | 270 | }, |
| 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 | }; | |
| 431 | 272 | } |
src/codegen/spirv/spec.zig+794-3530| ... | ... | @@ -26,6 +26,16 @@ pub const Id = enum(Word) { |
| 26 | 26 | } |
| 27 | 27 | }; |
| 28 | 28 | |
| 29 | pub 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 | ||
| 29 | 39 | pub const LiteralInteger = Word; |
| 30 | 40 | pub const LiteralFloat = Word; |
| 31 | 41 | pub const LiteralString = []const u8; |
| ... | ... | @@ -181,25 +191,6 @@ pub const OperandKind = enum { |
| 181 | 191 | pair_id_ref_literal_integer, |
| 182 | 192 | pair_id_ref_id_ref, |
| 183 | 193 | 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, | |
| 203 | 194 | |
| 204 | 195 | pub fn category(self: OperandKind) OperandCategory { |
| 205 | 196 | return switch (self) { |
| ... | ... | @@ -275,25 +266,6 @@ pub const OperandKind = enum { |
| 275 | 266 | .pair_id_ref_literal_integer => .composite, |
| 276 | 267 | .pair_id_ref_id_ref => .composite, |
| 277 | 268 | .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, | |
| 297 | 269 | }; |
| 298 | 270 | } |
| 299 | 271 | pub fn enumerants(self: OperandKind) []const Enumerant { |
| ... | ... | @@ -1465,178 +1437,10 @@ pub const OperandKind = enum { |
| 1465 | 1437 | .{ .name = "MakeElementVisibleARM", .value = 0x0008, .parameters = &.{.id_ref} }, |
| 1466 | 1438 | .{ .name = "NonPrivateElementARM", .value = 0x0010, .parameters = &.{} }, |
| 1467 | 1439 | }, |
| 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 | }, | |
| 1637 | 1440 | }; |
| 1638 | 1441 | } |
| 1639 | 1442 | }; |
| 1443 | ||
| 1640 | 1444 | pub const Opcode = enum(u16) { |
| 1641 | 1445 | OpNop = 0, |
| 1642 | 1446 | OpUndef = 1, |
| ... | ... | @@ -3523,6 +3327,259 @@ pub const Opcode = enum(u16) { |
| 3523 | 3327 | }; |
| 3524 | 3328 | } |
| 3525 | 3329 | }; |
| 3330 | ||
| 3331 | pub 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 | ||
| 3415 | pub 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 | ||
| 3580 | pub const Zig = enum(u16) { | |
| 3581 | InvocationGlobal = 0, | |
| 3582 | }; | |
| 3526 | 3583 | pub const ImageOperands = packed struct { |
| 3527 | 3584 | bias: bool = false, |
| 3528 | 3585 | lod: bool = false, |
| ... | ... | @@ -5484,335 +5541,10 @@ pub const TensorOperands = packed struct { |
| 5484 | 5541 | _reserved_bit_31: bool = false, |
| 5485 | 5542 | }; |
| 5486 | 5543 | }; |
| 5487 | pub 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 | }; | |
| 5521 | pub 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 | }; | |
| 5531 | pub const @"DebugInfo.DebugCompositeType" = enum(u32) { | |
| 5532 | class = 0, | |
| 5533 | structure = 1, | |
| 5534 | @"union" = 2, | |
| 5535 | }; | |
| 5536 | pub const @"DebugInfo.DebugTypeQualifier" = enum(u32) { | |
| 5537 | const_type = 0, | |
| 5538 | volatile_type = 1, | |
| 5539 | restrict_type = 2, | |
| 5540 | }; | |
| 5541 | pub 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 | }; | |
| 5564 | pub 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 | }; | |
| 5598 | pub 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 | }; | |
| 5608 | pub const @"OpenCL.DebugInfo.100.DebugCompositeType" = enum(u32) { | |
| 5609 | class = 0, | |
| 5610 | structure = 1, | |
| 5611 | @"union" = 2, | |
| 5612 | }; | |
| 5613 | pub const @"OpenCL.DebugInfo.100.DebugTypeQualifier" = enum(u32) { | |
| 5614 | const_type = 0, | |
| 5615 | volatile_type = 1, | |
| 5616 | restrict_type = 2, | |
| 5617 | atomic_type = 3, | |
| 5618 | }; | |
| 5619 | pub 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 | }; | |
| 5644 | pub const @"OpenCL.DebugInfo.100.DebugImportedEntity" = enum(u32) { | |
| 5645 | imported_module = 0, | |
| 5646 | imported_declaration = 1, | |
| 5647 | }; | |
| 5648 | pub 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 | }; | |
| 5682 | pub 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 | }; | |
| 5716 | pub 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 | }; | |
| 5750 | pub 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 | }; | |
| 5760 | pub const @"NonSemantic.Shader.DebugInfo.100.DebugCompositeType" = enum(u32) { | |
| 5761 | class = 0, | |
| 5762 | structure = 1, | |
| 5763 | @"union" = 2, | |
| 5764 | }; | |
| 5765 | pub 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 | }; | |
| 5771 | pub 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 | }; | |
| 5796 | pub const @"NonSemantic.Shader.DebugInfo.100.DebugImportedEntity" = enum(u32) { | |
| 5797 | imported_module = 0, | |
| 5798 | imported_declaration = 1, | |
| 5799 | }; | |
| 5800 | 5544 | pub const InstructionSet = enum { |
| 5801 | 5545 | 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", | |
| 5816 | 5548 | zig, |
| 5817 | 5549 | |
| 5818 | 5550 | pub fn instructions(self: InstructionSet) []const Instruction { |
| ... | ... | @@ -14078,480 +13810,321 @@ pub const InstructionSet = enum { |
| 14078 | 13810 | }, |
| 14079 | 13811 | }, |
| 14080 | 13812 | }, |
| 14081 | .spv_amd_shader_trinary_minmax => &.{ | |
| 13813 | .@"GLSL.std.450" => &.{ | |
| 14082 | 13814 | .{ |
| 14083 | .name = "FMin3AMD", | |
| 13815 | .name = "Round", | |
| 14084 | 13816 | .opcode = 1, |
| 14085 | 13817 | .operands = &.{ |
| 14086 | 13818 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14087 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14088 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14089 | 13819 | }, |
| 14090 | 13820 | }, |
| 14091 | 13821 | .{ |
| 14092 | .name = "UMin3AMD", | |
| 13822 | .name = "RoundEven", | |
| 14093 | 13823 | .opcode = 2, |
| 14094 | 13824 | .operands = &.{ |
| 14095 | 13825 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14096 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14097 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14098 | 13826 | }, |
| 14099 | 13827 | }, |
| 14100 | 13828 | .{ |
| 14101 | .name = "SMin3AMD", | |
| 13829 | .name = "Trunc", | |
| 14102 | 13830 | .opcode = 3, |
| 14103 | 13831 | .operands = &.{ |
| 14104 | 13832 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14105 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14106 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14107 | 13833 | }, |
| 14108 | 13834 | }, |
| 14109 | 13835 | .{ |
| 14110 | .name = "FMax3AMD", | |
| 13836 | .name = "FAbs", | |
| 14111 | 13837 | .opcode = 4, |
| 14112 | 13838 | .operands = &.{ |
| 14113 | 13839 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14114 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14115 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14116 | 13840 | }, |
| 14117 | 13841 | }, |
| 14118 | 13842 | .{ |
| 14119 | .name = "UMax3AMD", | |
| 13843 | .name = "SAbs", | |
| 14120 | 13844 | .opcode = 5, |
| 14121 | 13845 | .operands = &.{ |
| 14122 | 13846 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14123 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14124 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14125 | 13847 | }, |
| 14126 | 13848 | }, |
| 14127 | 13849 | .{ |
| 14128 | .name = "SMax3AMD", | |
| 13850 | .name = "FSign", | |
| 14129 | 13851 | .opcode = 6, |
| 14130 | 13852 | .operands = &.{ |
| 14131 | 13853 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14132 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14133 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14134 | 13854 | }, |
| 14135 | 13855 | }, |
| 14136 | 13856 | .{ |
| 14137 | .name = "FMid3AMD", | |
| 13857 | .name = "SSign", | |
| 14138 | 13858 | .opcode = 7, |
| 14139 | 13859 | .operands = &.{ |
| 14140 | 13860 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14141 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14142 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14143 | 13861 | }, |
| 14144 | 13862 | }, |
| 14145 | 13863 | .{ |
| 14146 | .name = "UMid3AMD", | |
| 13864 | .name = "Floor", | |
| 14147 | 13865 | .opcode = 8, |
| 14148 | 13866 | .operands = &.{ |
| 14149 | 13867 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14150 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14151 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14152 | 13868 | }, |
| 14153 | 13869 | }, |
| 14154 | 13870 | .{ |
| 14155 | .name = "SMid3AMD", | |
| 13871 | .name = "Ceil", | |
| 14156 | 13872 | .opcode = 9, |
| 14157 | 13873 | .operands = &.{ |
| 14158 | 13874 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14159 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14160 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14161 | 13875 | }, |
| 14162 | 13876 | }, |
| 14163 | }, | |
| 14164 | .spv_ext_inst_type_tosa_001000_1 => &.{ | |
| 14165 | 13877 | .{ |
| 14166 | .name = "ARGMAX", | |
| 14167 | .opcode = 0, | |
| 13878 | .name = "Fract", | |
| 13879 | .opcode = 10, | |
| 14168 | 13880 | .operands = &.{ |
| 14169 | 13881 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14170 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14171 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14172 | 13882 | }, |
| 14173 | 13883 | }, |
| 14174 | 13884 | .{ |
| 14175 | .name = "AVG_POOL2D", | |
| 14176 | .opcode = 1, | |
| 13885 | .name = "Radians", | |
| 13886 | .opcode = 11, | |
| 14177 | 13887 | .operands = &.{ |
| 14178 | 13888 | .{ .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 }, | |
| 14185 | 13889 | }, |
| 14186 | 13890 | }, |
| 14187 | 13891 | .{ |
| 14188 | .name = "CONV2D", | |
| 14189 | .opcode = 2, | |
| 13892 | .name = "Degrees", | |
| 13893 | .opcode = 12, | |
| 14190 | 13894 | .operands = &.{ |
| 14191 | 13895 | .{ .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 }, | |
| 14201 | 13896 | }, |
| 14202 | 13897 | }, |
| 14203 | 13898 | .{ |
| 14204 | .name = "CONV3D", | |
| 14205 | .opcode = 3, | |
| 13899 | .name = "Sin", | |
| 13900 | .opcode = 13, | |
| 14206 | 13901 | .operands = &.{ |
| 14207 | 13902 | .{ .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 }, | |
| 14217 | 13903 | }, |
| 14218 | 13904 | }, |
| 14219 | 13905 | .{ |
| 14220 | .name = "DEPTHWISE_CONV2D", | |
| 14221 | .opcode = 4, | |
| 13906 | .name = "Cos", | |
| 13907 | .opcode = 14, | |
| 14222 | 13908 | .operands = &.{ |
| 14223 | 13909 | .{ .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 }, | |
| 14233 | 13910 | }, |
| 14234 | 13911 | }, |
| 14235 | 13912 | .{ |
| 14236 | .name = "FFT2D", | |
| 14237 | .opcode = 5, | |
| 13913 | .name = "Tan", | |
| 13914 | .opcode = 15, | |
| 14238 | 13915 | .operands = &.{ |
| 14239 | 13916 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14240 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14241 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14242 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14243 | 13917 | }, |
| 14244 | 13918 | }, |
| 14245 | 13919 | .{ |
| 14246 | .name = "MATMUL", | |
| 14247 | .opcode = 6, | |
| 13920 | .name = "Asin", | |
| 13921 | .opcode = 16, | |
| 14248 | 13922 | .operands = &.{ |
| 14249 | 13923 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14250 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14251 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14252 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14253 | 13924 | }, |
| 14254 | 13925 | }, |
| 14255 | 13926 | .{ |
| 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", | |
| 14347 | 13928 | .opcode = 17, |
| 14348 | 13929 | .operands = &.{ |
| 14349 | 13930 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14350 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14351 | 13931 | }, |
| 14352 | 13932 | }, |
| 14353 | 13933 | .{ |
| 14354 | .name = "BITWISE_XOR", | |
| 13934 | .name = "Atan", | |
| 14355 | 13935 | .opcode = 18, |
| 14356 | 13936 | .operands = &.{ |
| 14357 | 13937 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14358 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14359 | 13938 | }, |
| 14360 | 13939 | }, |
| 14361 | 13940 | .{ |
| 14362 | .name = "INTDIV", | |
| 13941 | .name = "Sinh", | |
| 14363 | 13942 | .opcode = 19, |
| 14364 | 13943 | .operands = &.{ |
| 14365 | 13944 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14366 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14367 | 13945 | }, |
| 14368 | 13946 | }, |
| 14369 | 13947 | .{ |
| 14370 | .name = "LOGICAL_AND", | |
| 13948 | .name = "Cosh", | |
| 14371 | 13949 | .opcode = 20, |
| 14372 | 13950 | .operands = &.{ |
| 14373 | 13951 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14374 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14375 | 13952 | }, |
| 14376 | 13953 | }, |
| 14377 | 13954 | .{ |
| 14378 | .name = "LOGICAL_LEFT_SHIFT", | |
| 13955 | .name = "Tanh", | |
| 14379 | 13956 | .opcode = 21, |
| 14380 | 13957 | .operands = &.{ |
| 14381 | 13958 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14382 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14383 | 13959 | }, |
| 14384 | 13960 | }, |
| 14385 | 13961 | .{ |
| 14386 | .name = "LOGICAL_RIGHT_SHIFT", | |
| 13962 | .name = "Asinh", | |
| 14387 | 13963 | .opcode = 22, |
| 14388 | 13964 | .operands = &.{ |
| 14389 | 13965 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14390 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14391 | 13966 | }, |
| 14392 | 13967 | }, |
| 14393 | 13968 | .{ |
| 14394 | .name = "LOGICAL_OR", | |
| 13969 | .name = "Acosh", | |
| 14395 | 13970 | .opcode = 23, |
| 14396 | 13971 | .operands = &.{ |
| 14397 | 13972 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14398 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14399 | 13973 | }, |
| 14400 | 13974 | }, |
| 14401 | 13975 | .{ |
| 14402 | .name = "LOGICAL_XOR", | |
| 13976 | .name = "Atanh", | |
| 14403 | 13977 | .opcode = 24, |
| 14404 | 13978 | .operands = &.{ |
| 14405 | 13979 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14406 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14407 | 13980 | }, |
| 14408 | 13981 | }, |
| 14409 | 13982 | .{ |
| 14410 | .name = "MAXIMUM", | |
| 13983 | .name = "Atan2", | |
| 14411 | 13984 | .opcode = 25, |
| 14412 | 13985 | .operands = &.{ |
| 14413 | 13986 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14414 | 13987 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14415 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14416 | 13988 | }, |
| 14417 | 13989 | }, |
| 14418 | 13990 | .{ |
| 14419 | .name = "MINIMUM", | |
| 13991 | .name = "Pow", | |
| 14420 | 13992 | .opcode = 26, |
| 14421 | 13993 | .operands = &.{ |
| 14422 | 13994 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14423 | 13995 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14424 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14425 | 13996 | }, |
| 14426 | 13997 | }, |
| 14427 | 13998 | .{ |
| 14428 | .name = "MUL", | |
| 13999 | .name = "Exp", | |
| 14429 | 14000 | .opcode = 27, |
| 14430 | 14001 | .operands = &.{ |
| 14431 | 14002 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14432 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14433 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14434 | 14003 | }, |
| 14435 | 14004 | }, |
| 14436 | 14005 | .{ |
| 14437 | .name = "POW", | |
| 14006 | .name = "Log", | |
| 14438 | 14007 | .opcode = 28, |
| 14439 | 14008 | .operands = &.{ |
| 14440 | 14009 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14441 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14442 | 14010 | }, |
| 14443 | 14011 | }, |
| 14444 | 14012 | .{ |
| 14445 | .name = "SUB", | |
| 14013 | .name = "Exp2", | |
| 14446 | 14014 | .opcode = 29, |
| 14447 | 14015 | .operands = &.{ |
| 14448 | 14016 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14449 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14450 | 14017 | }, |
| 14451 | 14018 | }, |
| 14452 | 14019 | .{ |
| 14453 | .name = "TABLE", | |
| 14020 | .name = "Log2", | |
| 14454 | 14021 | .opcode = 30, |
| 14455 | 14022 | .operands = &.{ |
| 14456 | 14023 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14457 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14458 | 14024 | }, |
| 14459 | 14025 | }, |
| 14460 | 14026 | .{ |
| 14461 | .name = "ABS", | |
| 14027 | .name = "Sqrt", | |
| 14462 | 14028 | .opcode = 31, |
| 14463 | 14029 | .operands = &.{ |
| 14464 | 14030 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14465 | 14031 | }, |
| 14466 | 14032 | }, |
| 14467 | 14033 | .{ |
| 14468 | .name = "BITWISE_NOT", | |
| 14034 | .name = "InverseSqrt", | |
| 14469 | 14035 | .opcode = 32, |
| 14470 | 14036 | .operands = &.{ |
| 14471 | 14037 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14472 | 14038 | }, |
| 14473 | 14039 | }, |
| 14474 | 14040 | .{ |
| 14475 | .name = "CEIL", | |
| 14041 | .name = "Determinant", | |
| 14476 | 14042 | .opcode = 33, |
| 14477 | 14043 | .operands = &.{ |
| 14478 | 14044 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14479 | 14045 | }, |
| 14480 | 14046 | }, |
| 14481 | 14047 | .{ |
| 14482 | .name = "CLZ", | |
| 14048 | .name = "MatrixInverse", | |
| 14483 | 14049 | .opcode = 34, |
| 14484 | 14050 | .operands = &.{ |
| 14485 | 14051 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14486 | 14052 | }, |
| 14487 | 14053 | }, |
| 14488 | 14054 | .{ |
| 14489 | .name = "COS", | |
| 14055 | .name = "Modf", | |
| 14490 | 14056 | .opcode = 35, |
| 14491 | 14057 | .operands = &.{ |
| 14492 | 14058 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14059 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14493 | 14060 | }, |
| 14494 | 14061 | }, |
| 14495 | 14062 | .{ |
| 14496 | .name = "EXP", | |
| 14063 | .name = "ModfStruct", | |
| 14497 | 14064 | .opcode = 36, |
| 14498 | 14065 | .operands = &.{ |
| 14499 | 14066 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14500 | 14067 | }, |
| 14501 | 14068 | }, |
| 14502 | 14069 | .{ |
| 14503 | .name = "FLOOR", | |
| 14070 | .name = "FMin", | |
| 14504 | 14071 | .opcode = 37, |
| 14505 | 14072 | .operands = &.{ |
| 14506 | 14073 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14074 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14507 | 14075 | }, |
| 14508 | 14076 | }, |
| 14509 | 14077 | .{ |
| 14510 | .name = "LOG", | |
| 14078 | .name = "UMin", | |
| 14511 | 14079 | .opcode = 38, |
| 14512 | 14080 | .operands = &.{ |
| 14513 | 14081 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14082 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14514 | 14083 | }, |
| 14515 | 14084 | }, |
| 14516 | 14085 | .{ |
| 14517 | .name = "LOGICAL_NOT", | |
| 14086 | .name = "SMin", | |
| 14518 | 14087 | .opcode = 39, |
| 14519 | 14088 | .operands = &.{ |
| 14520 | 14089 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14090 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14521 | 14091 | }, |
| 14522 | 14092 | }, |
| 14523 | 14093 | .{ |
| 14524 | .name = "NEGATE", | |
| 14094 | .name = "FMax", | |
| 14525 | 14095 | .opcode = 40, |
| 14526 | 14096 | .operands = &.{ |
| 14527 | 14097 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14528 | 14098 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14529 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14530 | 14099 | }, |
| 14531 | 14100 | }, |
| 14532 | 14101 | .{ |
| 14533 | .name = "RECIPROCAL", | |
| 14102 | .name = "UMax", | |
| 14534 | 14103 | .opcode = 41, |
| 14535 | 14104 | .operands = &.{ |
| 14536 | 14105 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14106 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14537 | 14107 | }, |
| 14538 | 14108 | }, |
| 14539 | 14109 | .{ |
| 14540 | .name = "RSQRT", | |
| 14110 | .name = "SMax", | |
| 14541 | 14111 | .opcode = 42, |
| 14542 | 14112 | .operands = &.{ |
| 14543 | 14113 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14114 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14544 | 14115 | }, |
| 14545 | 14116 | }, |
| 14546 | 14117 | .{ |
| 14547 | .name = "SIN", | |
| 14118 | .name = "FClamp", | |
| 14548 | 14119 | .opcode = 43, |
| 14549 | 14120 | .operands = &.{ |
| 14550 | 14121 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14122 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14123 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14551 | 14124 | }, |
| 14552 | 14125 | }, |
| 14553 | 14126 | .{ |
| 14554 | .name = "SELECT", | |
| 14127 | .name = "UClamp", | |
| 14555 | 14128 | .opcode = 44, |
| 14556 | 14129 | .operands = &.{ |
| 14557 | 14130 | .{ .kind = .id_ref, .quantifier = .required }, |
| ... | ... | @@ -14560,31 +14133,34 @@ pub const InstructionSet = enum { |
| 14560 | 14133 | }, |
| 14561 | 14134 | }, |
| 14562 | 14135 | .{ |
| 14563 | .name = "EQUAL", | |
| 14136 | .name = "SClamp", | |
| 14564 | 14137 | .opcode = 45, |
| 14565 | 14138 | .operands = &.{ |
| 14566 | 14139 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14567 | 14140 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14141 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14568 | 14142 | }, |
| 14569 | 14143 | }, |
| 14570 | 14144 | .{ |
| 14571 | .name = "GREATER", | |
| 14145 | .name = "FMix", | |
| 14572 | 14146 | .opcode = 46, |
| 14573 | 14147 | .operands = &.{ |
| 14574 | 14148 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14575 | 14149 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14150 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14576 | 14151 | }, |
| 14577 | 14152 | }, |
| 14578 | 14153 | .{ |
| 14579 | .name = "GREATER_EQUAL", | |
| 14154 | .name = "IMix", | |
| 14580 | 14155 | .opcode = 47, |
| 14581 | 14156 | .operands = &.{ |
| 14582 | 14157 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14583 | 14158 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14159 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14584 | 14160 | }, |
| 14585 | 14161 | }, |
| 14586 | 14162 | .{ |
| 14587 | .name = "REDUCE_ALL", | |
| 14163 | .name = "Step", | |
| 14588 | 14164 | .opcode = 48, |
| 14589 | 14165 | .operands = &.{ |
| 14590 | 14166 | .{ .kind = .id_ref, .quantifier = .required }, |
| ... | ... | @@ -14592,15 +14168,16 @@ pub const InstructionSet = enum { |
| 14592 | 14168 | }, |
| 14593 | 14169 | }, |
| 14594 | 14170 | .{ |
| 14595 | .name = "REDUCE_ANY", | |
| 14171 | .name = "SmoothStep", | |
| 14596 | 14172 | .opcode = 49, |
| 14597 | 14173 | .operands = &.{ |
| 14598 | 14174 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14599 | 14175 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14176 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14600 | 14177 | }, |
| 14601 | 14178 | }, |
| 14602 | 14179 | .{ |
| 14603 | .name = "REDUCE_MAX", | |
| 14180 | .name = "Fma", | |
| 14604 | 14181 | .opcode = 50, |
| 14605 | 14182 | .operands = &.{ |
| 14606 | 14183 | .{ .kind = .id_ref, .quantifier = .required }, |
| ... | ... | @@ -14609,24 +14186,22 @@ pub const InstructionSet = enum { |
| 14609 | 14186 | }, |
| 14610 | 14187 | }, |
| 14611 | 14188 | .{ |
| 14612 | .name = "REDUCE_MIN", | |
| 14189 | .name = "Frexp", | |
| 14613 | 14190 | .opcode = 51, |
| 14614 | 14191 | .operands = &.{ |
| 14615 | 14192 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14616 | 14193 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14617 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14618 | 14194 | }, |
| 14619 | 14195 | }, |
| 14620 | 14196 | .{ |
| 14621 | .name = "REDUCE_PRODUCT", | |
| 14197 | .name = "FrexpStruct", | |
| 14622 | 14198 | .opcode = 52, |
| 14623 | 14199 | .operands = &.{ |
| 14624 | 14200 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14625 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14626 | 14201 | }, |
| 14627 | 14202 | }, |
| 14628 | 14203 | .{ |
| 14629 | .name = "REDUCE_SUM", | |
| 14204 | .name = "Ldexp", | |
| 14630 | 14205 | .opcode = 53, |
| 14631 | 14206 | .operands = &.{ |
| 14632 | 14207 | .{ .kind = .id_ref, .quantifier = .required }, |
| ... | ... | @@ -14634,3368 +14209,1112 @@ pub const InstructionSet = enum { |
| 14634 | 14209 | }, |
| 14635 | 14210 | }, |
| 14636 | 14211 | .{ |
| 14637 | .name = "CONCAT", | |
| 14212 | .name = "PackSnorm4x8", | |
| 14638 | 14213 | .opcode = 54, |
| 14639 | 14214 | .operands = &.{ |
| 14640 | 14215 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14641 | .{ .kind = .id_ref, .quantifier = .variadic }, | |
| 14642 | 14216 | }, |
| 14643 | 14217 | }, |
| 14644 | 14218 | .{ |
| 14645 | .name = "PAD", | |
| 14219 | .name = "PackUnorm4x8", | |
| 14646 | 14220 | .opcode = 55, |
| 14647 | 14221 | .operands = &.{ |
| 14648 | 14222 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14649 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14650 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14651 | 14223 | }, |
| 14652 | 14224 | }, |
| 14653 | 14225 | .{ |
| 14654 | .name = "RESHAPE", | |
| 14226 | .name = "PackSnorm2x16", | |
| 14655 | 14227 | .opcode = 56, |
| 14656 | 14228 | .operands = &.{ |
| 14657 | 14229 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14658 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14659 | 14230 | }, |
| 14660 | 14231 | }, |
| 14661 | 14232 | .{ |
| 14662 | .name = "REVERSE", | |
| 14233 | .name = "PackUnorm2x16", | |
| 14663 | 14234 | .opcode = 57, |
| 14664 | 14235 | .operands = &.{ |
| 14665 | 14236 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14666 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14667 | 14237 | }, |
| 14668 | 14238 | }, |
| 14669 | 14239 | .{ |
| 14670 | .name = "SLICE", | |
| 14240 | .name = "PackHalf2x16", | |
| 14671 | 14241 | .opcode = 58, |
| 14672 | 14242 | .operands = &.{ |
| 14673 | 14243 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14674 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14675 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14676 | 14244 | }, |
| 14677 | 14245 | }, |
| 14678 | 14246 | .{ |
| 14679 | .name = "TILE", | |
| 14247 | .name = "PackDouble2x32", | |
| 14680 | 14248 | .opcode = 59, |
| 14681 | 14249 | .operands = &.{ |
| 14682 | 14250 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14683 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14684 | 14251 | }, |
| 14685 | 14252 | }, |
| 14686 | 14253 | .{ |
| 14687 | .name = "TRANSPOSE", | |
| 14254 | .name = "UnpackSnorm2x16", | |
| 14688 | 14255 | .opcode = 60, |
| 14689 | 14256 | .operands = &.{ |
| 14690 | 14257 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14691 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14692 | 14258 | }, |
| 14693 | 14259 | }, |
| 14694 | 14260 | .{ |
| 14695 | .name = "GATHER", | |
| 14261 | .name = "UnpackUnorm2x16", | |
| 14696 | 14262 | .opcode = 61, |
| 14697 | 14263 | .operands = &.{ |
| 14698 | 14264 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14699 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14700 | 14265 | }, |
| 14701 | 14266 | }, |
| 14702 | 14267 | .{ |
| 14703 | .name = "SCATTER", | |
| 14268 | .name = "UnpackHalf2x16", | |
| 14704 | 14269 | .opcode = 62, |
| 14705 | 14270 | .operands = &.{ |
| 14706 | 14271 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14707 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14708 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14709 | 14272 | }, |
| 14710 | 14273 | }, |
| 14711 | 14274 | .{ |
| 14712 | .name = "RESIZE", | |
| 14275 | .name = "UnpackSnorm4x8", | |
| 14713 | 14276 | .opcode = 63, |
| 14714 | 14277 | .operands = &.{ |
| 14715 | 14278 | .{ .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 }, | |
| 14720 | 14279 | }, |
| 14721 | 14280 | }, |
| 14722 | 14281 | .{ |
| 14723 | .name = "CAST", | |
| 14282 | .name = "UnpackUnorm4x8", | |
| 14724 | 14283 | .opcode = 64, |
| 14725 | 14284 | .operands = &.{ |
| 14726 | 14285 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14727 | 14286 | }, |
| 14728 | 14287 | }, |
| 14729 | 14288 | .{ |
| 14730 | .name = "RESCALE", | |
| 14289 | .name = "UnpackDouble2x32", | |
| 14731 | 14290 | .opcode = 65, |
| 14732 | 14291 | .operands = &.{ |
| 14733 | 14292 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14293 | }, | |
| 14294 | }, | |
| 14295 | .{ | |
| 14296 | .name = "Length", | |
| 14297 | .opcode = 66, | |
| 14298 | .operands = &.{ | |
| 14734 | 14299 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14300 | }, | |
| 14301 | }, | |
| 14302 | .{ | |
| 14303 | .name = "Distance", | |
| 14304 | .opcode = 67, | |
| 14305 | .operands = &.{ | |
| 14735 | 14306 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14736 | 14307 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14308 | }, | |
| 14309 | }, | |
| 14310 | .{ | |
| 14311 | .name = "Cross", | |
| 14312 | .opcode = 68, | |
| 14313 | .operands = &.{ | |
| 14737 | 14314 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14738 | 14315 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14316 | }, | |
| 14317 | }, | |
| 14318 | .{ | |
| 14319 | .name = "Normalize", | |
| 14320 | .opcode = 69, | |
| 14321 | .operands = &.{ | |
| 14739 | 14322 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14323 | }, | |
| 14324 | }, | |
| 14325 | .{ | |
| 14326 | .name = "FaceForward", | |
| 14327 | .opcode = 70, | |
| 14328 | .operands = &.{ | |
| 14740 | 14329 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14741 | 14330 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14742 | 14331 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14743 | 14332 | }, |
| 14744 | 14333 | }, |
| 14745 | }, | |
| 14746 | .non_semantic_vksp_reflection => &.{ | |
| 14747 | 14334 | .{ |
| 14748 | .name = "Configuration", | |
| 14749 | .opcode = 1, | |
| 14335 | .name = "Reflect", | |
| 14336 | .opcode = 71, | |
| 14750 | 14337 | .operands = &.{ |
| 14751 | 14338 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14752 | 14339 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14340 | }, | |
| 14341 | }, | |
| 14342 | .{ | |
| 14343 | .name = "Refract", | |
| 14344 | .opcode = 72, | |
| 14345 | .operands = &.{ | |
| 14753 | 14346 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14754 | 14347 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14755 | 14348 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14349 | }, | |
| 14350 | }, | |
| 14351 | .{ | |
| 14352 | .name = "FindILsb", | |
| 14353 | .opcode = 73, | |
| 14354 | .operands = &.{ | |
| 14756 | 14355 | .{ .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 = &.{ | |
| 14759 | 14362 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14760 | 14363 | }, |
| 14761 | 14364 | }, |
| 14762 | 14365 | .{ |
| 14763 | .name = "StartCounter", | |
| 14764 | .opcode = 2, | |
| 14366 | .name = "FindUMsb", | |
| 14367 | .opcode = 75, | |
| 14765 | 14368 | .operands = &.{ |
| 14766 | 14369 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14767 | 14370 | }, |
| 14768 | 14371 | }, |
| 14769 | 14372 | .{ |
| 14770 | .name = "StopCounter", | |
| 14771 | .opcode = 3, | |
| 14373 | .name = "InterpolateAtCentroid", | |
| 14374 | .opcode = 76, | |
| 14772 | 14375 | .operands = &.{ |
| 14773 | 14376 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14774 | 14377 | }, |
| 14775 | 14378 | }, |
| 14776 | 14379 | .{ |
| 14777 | .name = "PushConstants", | |
| 14778 | .opcode = 4, | |
| 14380 | .name = "InterpolateAtSample", | |
| 14381 | .opcode = 77, | |
| 14779 | 14382 | .operands = &.{ |
| 14780 | 14383 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14781 | 14384 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14385 | }, | |
| 14386 | }, | |
| 14387 | .{ | |
| 14388 | .name = "InterpolateAtOffset", | |
| 14389 | .opcode = 78, | |
| 14390 | .operands = &.{ | |
| 14782 | 14391 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14783 | 14392 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14784 | 14393 | }, |
| 14785 | 14394 | }, |
| 14786 | 14395 | .{ |
| 14787 | .name = "SpecializationMapEntry", | |
| 14788 | .opcode = 5, | |
| 14396 | .name = "NMin", | |
| 14397 | .opcode = 79, | |
| 14789 | 14398 | .operands = &.{ |
| 14790 | 14399 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14791 | 14400 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14792 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14793 | 14401 | }, |
| 14794 | 14402 | }, |
| 14795 | 14403 | .{ |
| 14796 | .name = "DescriptorSetBuffer", | |
| 14797 | .opcode = 6, | |
| 14404 | .name = "NMax", | |
| 14405 | .opcode = 80, | |
| 14798 | 14406 | .operands = &.{ |
| 14799 | 14407 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14800 | 14408 | .{ .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 = &.{ | |
| 14875 | 14415 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14876 | 14416 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14877 | 14417 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14878 | 14418 | }, |
| 14879 | 14419 | }, |
| 14880 | 14420 | }, |
| 14881 | .spv_amd_shader_explicit_vertex_parameter => &.{ | |
| 14421 | .@"OpenCL.std" => &.{ | |
| 14882 | 14422 | .{ |
| 14883 | .name = "InterpolateAtVertexAMD", | |
| 14884 | .opcode = 1, | |
| 14423 | .name = "acos", | |
| 14424 | .opcode = 0, | |
| 14885 | 14425 | .operands = &.{ |
| 14886 | 14426 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14887 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14888 | 14427 | }, |
| 14889 | 14428 | }, |
| 14890 | }, | |
| 14891 | .debug_info => &.{ | |
| 14892 | .{ | |
| 14893 | .name = "DebugInfoNone", | |
| 14894 | .opcode = 0, | |
| 14895 | .operands = &.{}, | |
| 14896 | }, | |
| 14897 | 14429 | .{ |
| 14898 | .name = "DebugCompilationUnit", | |
| 14430 | .name = "acosh", | |
| 14899 | 14431 | .opcode = 1, |
| 14900 | 14432 | .operands = &.{ |
| 14901 | 14433 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14902 | .{ .kind = .literal_integer, .quantifier = .required }, | |
| 14903 | .{ .kind = .literal_integer, .quantifier = .required }, | |
| 14904 | 14434 | }, |
| 14905 | 14435 | }, |
| 14906 | 14436 | .{ |
| 14907 | .name = "DebugTypeBasic", | |
| 14437 | .name = "acospi", | |
| 14908 | 14438 | .opcode = 2, |
| 14909 | 14439 | .operands = &.{ |
| 14910 | 14440 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14911 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14912 | .{ .kind = .debug_info_debug_base_type_attribute_encoding, .quantifier = .required }, | |
| 14913 | 14441 | }, |
| 14914 | 14442 | }, |
| 14915 | 14443 | .{ |
| 14916 | .name = "DebugTypePointer", | |
| 14444 | .name = "asin", | |
| 14917 | 14445 | .opcode = 3, |
| 14918 | 14446 | .operands = &.{ |
| 14919 | 14447 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14920 | .{ .kind = .storage_class, .quantifier = .required }, | |
| 14921 | .{ .kind = .debug_info_debug_info_flags, .quantifier = .required }, | |
| 14922 | 14448 | }, |
| 14923 | 14449 | }, |
| 14924 | 14450 | .{ |
| 14925 | .name = "DebugTypeQualifier", | |
| 14451 | .name = "asinh", | |
| 14926 | 14452 | .opcode = 4, |
| 14927 | 14453 | .operands = &.{ |
| 14928 | 14454 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14929 | .{ .kind = .debug_info_debug_type_qualifier, .quantifier = .required }, | |
| 14930 | 14455 | }, |
| 14931 | 14456 | }, |
| 14932 | 14457 | .{ |
| 14933 | .name = "DebugTypeArray", | |
| 14458 | .name = "asinpi", | |
| 14934 | 14459 | .opcode = 5, |
| 14935 | 14460 | .operands = &.{ |
| 14936 | 14461 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14937 | .{ .kind = .id_ref, .quantifier = .variadic }, | |
| 14938 | 14462 | }, |
| 14939 | 14463 | }, |
| 14940 | 14464 | .{ |
| 14941 | .name = "DebugTypeVector", | |
| 14465 | .name = "atan", | |
| 14942 | 14466 | .opcode = 6, |
| 14943 | 14467 | .operands = &.{ |
| 14944 | 14468 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14945 | .{ .kind = .literal_integer, .quantifier = .required }, | |
| 14946 | 14469 | }, |
| 14947 | 14470 | }, |
| 14948 | 14471 | .{ |
| 14949 | .name = "DebugTypedef", | |
| 14472 | .name = "atan2", | |
| 14950 | 14473 | .opcode = 7, |
| 14951 | 14474 | .operands = &.{ |
| 14952 | 14475 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14953 | 14476 | .{ .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 }, | |
| 14958 | 14477 | }, |
| 14959 | 14478 | }, |
| 14960 | 14479 | .{ |
| 14961 | .name = "DebugTypeFunction", | |
| 14480 | .name = "atanh", | |
| 14962 | 14481 | .opcode = 8, |
| 14963 | 14482 | .operands = &.{ |
| 14964 | 14483 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14965 | .{ .kind = .id_ref, .quantifier = .variadic }, | |
| 14966 | 14484 | }, |
| 14967 | 14485 | }, |
| 14968 | 14486 | .{ |
| 14969 | .name = "DebugTypeEnum", | |
| 14487 | .name = "atanpi", | |
| 14970 | 14488 | .opcode = 9, |
| 14971 | 14489 | .operands = &.{ |
| 14972 | 14490 | .{ .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 }, | |
| 14981 | 14491 | }, |
| 14982 | 14492 | }, |
| 14983 | 14493 | .{ |
| 14984 | .name = "DebugTypeComposite", | |
| 14494 | .name = "atan2pi", | |
| 14985 | 14495 | .opcode = 10, |
| 14986 | 14496 | .operands = &.{ |
| 14987 | 14497 | .{ .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 = &.{ | |
| 17137 | 14498 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17138 | 14499 | }, |
| 17139 | 14500 | }, |
| 17140 | 14501 | .{ |
| 17141 | .name = "rsqrt", | |
| 17142 | .opcode = 56, | |
| 14502 | .name = "cbrt", | |
| 14503 | .opcode = 11, | |
| 17143 | 14504 | .operands = &.{ |
| 17144 | 14505 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17145 | 14506 | }, |
| 17146 | 14507 | }, |
| 17147 | 14508 | .{ |
| 17148 | .name = "sin", | |
| 17149 | .opcode = 57, | |
| 14509 | .name = "ceil", | |
| 14510 | .opcode = 12, | |
| 17150 | 14511 | .operands = &.{ |
| 17151 | 14512 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17152 | 14513 | }, |
| 17153 | 14514 | }, |
| 17154 | 14515 | .{ |
| 17155 | .name = "sincos", | |
| 17156 | .opcode = 58, | |
| 14516 | .name = "copysign", | |
| 14517 | .opcode = 13, | |
| 17157 | 14518 | .operands = &.{ |
| 17158 | 14519 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17159 | 14520 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17160 | 14521 | }, |
| 17161 | 14522 | }, |
| 17162 | 14523 | .{ |
| 17163 | .name = "sinh", | |
| 17164 | .opcode = 59, | |
| 14524 | .name = "cos", | |
| 14525 | .opcode = 14, | |
| 17165 | 14526 | .operands = &.{ |
| 17166 | 14527 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17167 | 14528 | }, |
| 17168 | 14529 | }, |
| 17169 | 14530 | .{ |
| 17170 | .name = "sinpi", | |
| 17171 | .opcode = 60, | |
| 14531 | .name = "cosh", | |
| 14532 | .opcode = 15, | |
| 17172 | 14533 | .operands = &.{ |
| 17173 | 14534 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17174 | 14535 | }, |
| 17175 | 14536 | }, |
| 17176 | 14537 | .{ |
| 17177 | .name = "sqrt", | |
| 17178 | .opcode = 61, | |
| 14538 | .name = "cospi", | |
| 14539 | .opcode = 16, | |
| 17179 | 14540 | .operands = &.{ |
| 17180 | 14541 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17181 | 14542 | }, |
| 17182 | 14543 | }, |
| 17183 | 14544 | .{ |
| 17184 | .name = "tan", | |
| 17185 | .opcode = 62, | |
| 14545 | .name = "erfc", | |
| 14546 | .opcode = 17, | |
| 17186 | 14547 | .operands = &.{ |
| 17187 | 14548 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17188 | 14549 | }, |
| 17189 | 14550 | }, |
| 17190 | 14551 | .{ |
| 17191 | .name = "tanh", | |
| 17192 | .opcode = 63, | |
| 14552 | .name = "erf", | |
| 14553 | .opcode = 18, | |
| 17193 | 14554 | .operands = &.{ |
| 17194 | 14555 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17195 | 14556 | }, |
| 17196 | 14557 | }, |
| 17197 | 14558 | .{ |
| 17198 | .name = "tanpi", | |
| 17199 | .opcode = 64, | |
| 14559 | .name = "exp", | |
| 14560 | .opcode = 19, | |
| 17200 | 14561 | .operands = &.{ |
| 17201 | 14562 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17202 | 14563 | }, |
| 17203 | 14564 | }, |
| 17204 | 14565 | .{ |
| 17205 | .name = "tgamma", | |
| 17206 | .opcode = 65, | |
| 14566 | .name = "exp2", | |
| 14567 | .opcode = 20, | |
| 17207 | 14568 | .operands = &.{ |
| 17208 | 14569 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17209 | 14570 | }, |
| 17210 | 14571 | }, |
| 17211 | 14572 | .{ |
| 17212 | .name = "trunc", | |
| 17213 | .opcode = 66, | |
| 14573 | .name = "exp10", | |
| 14574 | .opcode = 21, | |
| 17214 | 14575 | .operands = &.{ |
| 17215 | 14576 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17216 | 14577 | }, |
| 17217 | 14578 | }, |
| 17218 | 14579 | .{ |
| 17219 | .name = "half_cos", | |
| 17220 | .opcode = 67, | |
| 14580 | .name = "expm1", | |
| 14581 | .opcode = 22, | |
| 17221 | 14582 | .operands = &.{ |
| 17222 | 14583 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17223 | 14584 | }, |
| 17224 | 14585 | }, |
| 17225 | 14586 | .{ |
| 17226 | .name = "half_divide", | |
| 17227 | .opcode = 68, | |
| 14587 | .name = "fabs", | |
| 14588 | .opcode = 23, | |
| 17228 | 14589 | .operands = &.{ |
| 17229 | 14590 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17230 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17231 | 14591 | }, |
| 17232 | 14592 | }, |
| 17233 | 14593 | .{ |
| 17234 | .name = "half_exp", | |
| 17235 | .opcode = 69, | |
| 14594 | .name = "fdim", | |
| 14595 | .opcode = 24, | |
| 17236 | 14596 | .operands = &.{ |
| 17237 | 14597 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14598 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17238 | 14599 | }, |
| 17239 | 14600 | }, |
| 17240 | 14601 | .{ |
| 17241 | .name = "half_exp2", | |
| 17242 | .opcode = 70, | |
| 14602 | .name = "floor", | |
| 14603 | .opcode = 25, | |
| 17243 | 14604 | .operands = &.{ |
| 17244 | 14605 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17245 | 14606 | }, |
| 17246 | 14607 | }, |
| 17247 | 14608 | .{ |
| 17248 | .name = "half_exp10", | |
| 17249 | .opcode = 71, | |
| 14609 | .name = "fma", | |
| 14610 | .opcode = 26, | |
| 17250 | 14611 | .operands = &.{ |
| 17251 | 14612 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14613 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14614 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17252 | 14615 | }, |
| 17253 | 14616 | }, |
| 17254 | 14617 | .{ |
| 17255 | .name = "half_log", | |
| 17256 | .opcode = 72, | |
| 14618 | .name = "fmax", | |
| 14619 | .opcode = 27, | |
| 17257 | 14620 | .operands = &.{ |
| 17258 | 14621 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14622 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17259 | 14623 | }, |
| 17260 | 14624 | }, |
| 17261 | 14625 | .{ |
| 17262 | .name = "half_log2", | |
| 17263 | .opcode = 73, | |
| 14626 | .name = "fmin", | |
| 14627 | .opcode = 28, | |
| 17264 | 14628 | .operands = &.{ |
| 17265 | 14629 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14630 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17266 | 14631 | }, |
| 17267 | 14632 | }, |
| 17268 | 14633 | .{ |
| 17269 | .name = "half_log10", | |
| 17270 | .opcode = 74, | |
| 14634 | .name = "fmod", | |
| 14635 | .opcode = 29, | |
| 17271 | 14636 | .operands = &.{ |
| 17272 | 14637 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14638 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17273 | 14639 | }, |
| 17274 | 14640 | }, |
| 17275 | 14641 | .{ |
| 17276 | .name = "half_powr", | |
| 17277 | .opcode = 75, | |
| 14642 | .name = "fract", | |
| 14643 | .opcode = 30, | |
| 17278 | 14644 | .operands = &.{ |
| 17279 | 14645 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17280 | 14646 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17281 | 14647 | }, |
| 17282 | 14648 | }, |
| 17283 | 14649 | .{ |
| 17284 | .name = "half_recip", | |
| 17285 | .opcode = 76, | |
| 14650 | .name = "frexp", | |
| 14651 | .opcode = 31, | |
| 17286 | 14652 | .operands = &.{ |
| 17287 | 14653 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14654 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17288 | 14655 | }, |
| 17289 | 14656 | }, |
| 17290 | 14657 | .{ |
| 17291 | .name = "half_rsqrt", | |
| 17292 | .opcode = 77, | |
| 14658 | .name = "hypot", | |
| 14659 | .opcode = 32, | |
| 17293 | 14660 | .operands = &.{ |
| 17294 | 14661 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14662 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17295 | 14663 | }, |
| 17296 | 14664 | }, |
| 17297 | 14665 | .{ |
| 17298 | .name = "half_sin", | |
| 17299 | .opcode = 78, | |
| 14666 | .name = "ilogb", | |
| 14667 | .opcode = 33, | |
| 17300 | 14668 | .operands = &.{ |
| 17301 | 14669 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17302 | 14670 | }, |
| 17303 | 14671 | }, |
| 17304 | 14672 | .{ |
| 17305 | .name = "half_sqrt", | |
| 17306 | .opcode = 79, | |
| 14673 | .name = "ldexp", | |
| 14674 | .opcode = 34, | |
| 17307 | 14675 | .operands = &.{ |
| 17308 | 14676 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14677 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17309 | 14678 | }, |
| 17310 | 14679 | }, |
| 17311 | 14680 | .{ |
| 17312 | .name = "half_tan", | |
| 17313 | .opcode = 80, | |
| 14681 | .name = "lgamma", | |
| 14682 | .opcode = 35, | |
| 17314 | 14683 | .operands = &.{ |
| 17315 | 14684 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17316 | 14685 | }, |
| 17317 | 14686 | }, |
| 17318 | 14687 | .{ |
| 17319 | .name = "native_cos", | |
| 17320 | .opcode = 81, | |
| 14688 | .name = "lgamma_r", | |
| 14689 | .opcode = 36, | |
| 17321 | 14690 | .operands = &.{ |
| 17322 | 14691 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14692 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17323 | 14693 | }, |
| 17324 | 14694 | }, |
| 17325 | 14695 | .{ |
| 17326 | .name = "native_divide", | |
| 17327 | .opcode = 82, | |
| 14696 | .name = "log", | |
| 14697 | .opcode = 37, | |
| 17328 | 14698 | .operands = &.{ |
| 17329 | 14699 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17330 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17331 | 14700 | }, |
| 17332 | 14701 | }, |
| 17333 | 14702 | .{ |
| 17334 | .name = "native_exp", | |
| 17335 | .opcode = 83, | |
| 14703 | .name = "log2", | |
| 14704 | .opcode = 38, | |
| 17336 | 14705 | .operands = &.{ |
| 17337 | 14706 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17338 | 14707 | }, |
| 17339 | 14708 | }, |
| 17340 | 14709 | .{ |
| 17341 | .name = "native_exp2", | |
| 17342 | .opcode = 84, | |
| 14710 | .name = "log10", | |
| 14711 | .opcode = 39, | |
| 17343 | 14712 | .operands = &.{ |
| 17344 | 14713 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17345 | 14714 | }, |
| 17346 | 14715 | }, |
| 17347 | 14716 | .{ |
| 17348 | .name = "native_exp10", | |
| 17349 | .opcode = 85, | |
| 14717 | .name = "log1p", | |
| 14718 | .opcode = 40, | |
| 17350 | 14719 | .operands = &.{ |
| 17351 | 14720 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17352 | 14721 | }, |
| 17353 | 14722 | }, |
| 17354 | 14723 | .{ |
| 17355 | .name = "native_log", | |
| 17356 | .opcode = 86, | |
| 14724 | .name = "logb", | |
| 14725 | .opcode = 41, | |
| 17357 | 14726 | .operands = &.{ |
| 17358 | 14727 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17359 | 14728 | }, |
| 17360 | 14729 | }, |
| 17361 | 14730 | .{ |
| 17362 | .name = "native_log2", | |
| 17363 | .opcode = 87, | |
| 14731 | .name = "mad", | |
| 14732 | .opcode = 42, | |
| 17364 | 14733 | .operands = &.{ |
| 17365 | 14734 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14735 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 14736 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17366 | 14737 | }, |
| 17367 | 14738 | }, |
| 17368 | 14739 | .{ |
| 17369 | .name = "native_log10", | |
| 17370 | .opcode = 88, | |
| 14740 | .name = "maxmag", | |
| 14741 | .opcode = 43, | |
| 17371 | 14742 | .operands = &.{ |
| 17372 | 14743 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14744 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17373 | 14745 | }, |
| 17374 | 14746 | }, |
| 17375 | 14747 | .{ |
| 17376 | .name = "native_powr", | |
| 17377 | .opcode = 89, | |
| 14748 | .name = "minmag", | |
| 14749 | .opcode = 44, | |
| 17378 | 14750 | .operands = &.{ |
| 17379 | 14751 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17380 | 14752 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17381 | 14753 | }, |
| 17382 | 14754 | }, |
| 17383 | 14755 | .{ |
| 17384 | .name = "native_recip", | |
| 17385 | .opcode = 90, | |
| 14756 | .name = "modf", | |
| 14757 | .opcode = 45, | |
| 17386 | 14758 | .operands = &.{ |
| 17387 | 14759 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14760 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17388 | 14761 | }, |
| 17389 | 14762 | }, |
| 17390 | 14763 | .{ |
| 17391 | .name = "native_rsqrt", | |
| 17392 | .opcode = 91, | |
| 14764 | .name = "nan", | |
| 14765 | .opcode = 46, | |
| 17393 | 14766 | .operands = &.{ |
| 17394 | 14767 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17395 | 14768 | }, |
| 17396 | 14769 | }, |
| 17397 | 14770 | .{ |
| 17398 | .name = "native_sin", | |
| 17399 | .opcode = 92, | |
| 14771 | .name = "nextafter", | |
| 14772 | .opcode = 47, | |
| 17400 | 14773 | .operands = &.{ |
| 17401 | 14774 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14775 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17402 | 14776 | }, |
| 17403 | 14777 | }, |
| 17404 | 14778 | .{ |
| 17405 | .name = "native_sqrt", | |
| 17406 | .opcode = 93, | |
| 14779 | .name = "pow", | |
| 14780 | .opcode = 48, | |
| 17407 | 14781 | .operands = &.{ |
| 17408 | 14782 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14783 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17409 | 14784 | }, |
| 17410 | 14785 | }, |
| 17411 | 14786 | .{ |
| 17412 | .name = "native_tan", | |
| 17413 | .opcode = 94, | |
| 14787 | .name = "pown", | |
| 14788 | .opcode = 49, | |
| 17414 | 14789 | .operands = &.{ |
| 17415 | 14790 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14791 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17416 | 14792 | }, |
| 17417 | 14793 | }, |
| 17418 | 14794 | .{ |
| 17419 | .name = "fclamp", | |
| 17420 | .opcode = 95, | |
| 14795 | .name = "powr", | |
| 14796 | .opcode = 50, | |
| 17421 | 14797 | .operands = &.{ |
| 17422 | 14798 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17423 | 14799 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17424 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17425 | 14800 | }, |
| 17426 | 14801 | }, |
| 17427 | 14802 | .{ |
| 17428 | .name = "degrees", | |
| 17429 | .opcode = 96, | |
| 14803 | .name = "remainder", | |
| 14804 | .opcode = 51, | |
| 17430 | 14805 | .operands = &.{ |
| 17431 | 14806 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14807 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17432 | 14808 | }, |
| 17433 | 14809 | }, |
| 17434 | 14810 | .{ |
| 17435 | .name = "fmax_common", | |
| 17436 | .opcode = 97, | |
| 14811 | .name = "remquo", | |
| 14812 | .opcode = 52, | |
| 17437 | 14813 | .operands = &.{ |
| 17438 | 14814 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17439 | 14815 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14816 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17440 | 14817 | }, |
| 17441 | 14818 | }, |
| 17442 | 14819 | .{ |
| 17443 | .name = "fmin_common", | |
| 17444 | .opcode = 98, | |
| 14820 | .name = "rint", | |
| 14821 | .opcode = 53, | |
| 17445 | 14822 | .operands = &.{ |
| 17446 | 14823 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17447 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17448 | 14824 | }, |
| 17449 | 14825 | }, |
| 17450 | 14826 | .{ |
| 17451 | .name = "mix", | |
| 17452 | .opcode = 99, | |
| 14827 | .name = "rootn", | |
| 14828 | .opcode = 54, | |
| 17453 | 14829 | .operands = &.{ |
| 17454 | 14830 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17455 | 14831 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17456 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17457 | 14832 | }, |
| 17458 | 14833 | }, |
| 17459 | 14834 | .{ |
| 17460 | .name = "radians", | |
| 17461 | .opcode = 100, | |
| 14835 | .name = "round", | |
| 14836 | .opcode = 55, | |
| 17462 | 14837 | .operands = &.{ |
| 17463 | 14838 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17464 | 14839 | }, |
| 17465 | 14840 | }, |
| 17466 | 14841 | .{ |
| 17467 | .name = "step", | |
| 17468 | .opcode = 101, | |
| 14842 | .name = "rsqrt", | |
| 14843 | .opcode = 56, | |
| 17469 | 14844 | .operands = &.{ |
| 17470 | 14845 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17471 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17472 | 14846 | }, |
| 17473 | 14847 | }, |
| 17474 | 14848 | .{ |
| 17475 | .name = "smoothstep", | |
| 17476 | .opcode = 102, | |
| 14849 | .name = "sin", | |
| 14850 | .opcode = 57, | |
| 17477 | 14851 | .operands = &.{ |
| 17478 | 14852 | .{ .kind = .id_ref, .quantifier = .required }, |
| 14853 | }, | |
| 14854 | }, | |
| 14855 | .{ | |
| 14856 | .name = "sincos", | |
| 14857 | .opcode = 58, | |
| 14858 | .operands = &.{ | |
| 17479 | 14859 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17480 | 14860 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17481 | 14861 | }, |
| 17482 | 14862 | }, |
| 17483 | 14863 | .{ |
| 17484 | .name = "sign", | |
| 17485 | .opcode = 103, | |
| 14864 | .name = "sinh", | |
| 14865 | .opcode = 59, | |
| 17486 | 14866 | .operands = &.{ |
| 17487 | 14867 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17488 | 14868 | }, |
| 17489 | 14869 | }, |
| 17490 | 14870 | .{ |
| 17491 | .name = "cross", | |
| 17492 | .opcode = 104, | |
| 14871 | .name = "sinpi", | |
| 14872 | .opcode = 60, | |
| 17493 | 14873 | .operands = &.{ |
| 17494 | 14874 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17495 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17496 | 14875 | }, |
| 17497 | 14876 | }, |
| 17498 | 14877 | .{ |
| 17499 | .name = "distance", | |
| 17500 | .opcode = 105, | |
| 14878 | .name = "sqrt", | |
| 14879 | .opcode = 61, | |
| 17501 | 14880 | .operands = &.{ |
| 17502 | 14881 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17503 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17504 | 14882 | }, |
| 17505 | 14883 | }, |
| 17506 | 14884 | .{ |
| 17507 | .name = "length", | |
| 17508 | .opcode = 106, | |
| 14885 | .name = "tan", | |
| 14886 | .opcode = 62, | |
| 17509 | 14887 | .operands = &.{ |
| 17510 | 14888 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17511 | 14889 | }, |
| 17512 | 14890 | }, |
| 17513 | 14891 | .{ |
| 17514 | .name = "normalize", | |
| 17515 | .opcode = 107, | |
| 14892 | .name = "tanh", | |
| 14893 | .opcode = 63, | |
| 17516 | 14894 | .operands = &.{ |
| 17517 | 14895 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17518 | 14896 | }, |
| 17519 | 14897 | }, |
| 17520 | 14898 | .{ |
| 17521 | .name = "fast_distance", | |
| 17522 | .opcode = 108, | |
| 14899 | .name = "tanpi", | |
| 14900 | .opcode = 64, | |
| 17523 | 14901 | .operands = &.{ |
| 17524 | 14902 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17525 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17526 | 14903 | }, |
| 17527 | 14904 | }, |
| 17528 | 14905 | .{ |
| 17529 | .name = "fast_length", | |
| 17530 | .opcode = 109, | |
| 14906 | .name = "tgamma", | |
| 14907 | .opcode = 65, | |
| 17531 | 14908 | .operands = &.{ |
| 17532 | 14909 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17533 | 14910 | }, |
| 17534 | 14911 | }, |
| 17535 | 14912 | .{ |
| 17536 | .name = "fast_normalize", | |
| 17537 | .opcode = 110, | |
| 14913 | .name = "trunc", | |
| 14914 | .opcode = 66, | |
| 17538 | 14915 | .operands = &.{ |
| 17539 | 14916 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17540 | 14917 | }, |
| 17541 | 14918 | }, |
| 17542 | 14919 | .{ |
| 17543 | .name = "s_abs", | |
| 17544 | .opcode = 141, | |
| 14920 | .name = "half_cos", | |
| 14921 | .opcode = 67, | |
| 17545 | 14922 | .operands = &.{ |
| 17546 | 14923 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17547 | 14924 | }, |
| 17548 | 14925 | }, |
| 17549 | 14926 | .{ |
| 17550 | .name = "s_abs_diff", | |
| 17551 | .opcode = 142, | |
| 14927 | .name = "half_divide", | |
| 14928 | .opcode = 68, | |
| 17552 | 14929 | .operands = &.{ |
| 17553 | 14930 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17554 | 14931 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17555 | 14932 | }, |
| 17556 | 14933 | }, |
| 17557 | 14934 | .{ |
| 17558 | .name = "s_add_sat", | |
| 17559 | .opcode = 143, | |
| 14935 | .name = "half_exp", | |
| 14936 | .opcode = 69, | |
| 17560 | 14937 | .operands = &.{ |
| 17561 | 14938 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17562 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17563 | 14939 | }, |
| 17564 | 14940 | }, |
| 17565 | 14941 | .{ |
| 17566 | .name = "u_add_sat", | |
| 17567 | .opcode = 144, | |
| 14942 | .name = "half_exp2", | |
| 14943 | .opcode = 70, | |
| 17568 | 14944 | .operands = &.{ |
| 17569 | 14945 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17570 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17571 | 14946 | }, |
| 17572 | 14947 | }, |
| 17573 | 14948 | .{ |
| 17574 | .name = "s_hadd", | |
| 17575 | .opcode = 145, | |
| 14949 | .name = "half_exp10", | |
| 14950 | .opcode = 71, | |
| 17576 | 14951 | .operands = &.{ |
| 17577 | 14952 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17578 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17579 | 14953 | }, |
| 17580 | 14954 | }, |
| 17581 | 14955 | .{ |
| 17582 | .name = "u_hadd", | |
| 17583 | .opcode = 146, | |
| 14956 | .name = "half_log", | |
| 14957 | .opcode = 72, | |
| 17584 | 14958 | .operands = &.{ |
| 17585 | 14959 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17586 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17587 | 14960 | }, |
| 17588 | 14961 | }, |
| 17589 | 14962 | .{ |
| 17590 | .name = "s_rhadd", | |
| 17591 | .opcode = 147, | |
| 14963 | .name = "half_log2", | |
| 14964 | .opcode = 73, | |
| 17592 | 14965 | .operands = &.{ |
| 17593 | 14966 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17594 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17595 | 14967 | }, |
| 17596 | 14968 | }, |
| 17597 | 14969 | .{ |
| 17598 | .name = "u_rhadd", | |
| 17599 | .opcode = 148, | |
| 14970 | .name = "half_log10", | |
| 14971 | .opcode = 74, | |
| 17600 | 14972 | .operands = &.{ |
| 17601 | 14973 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17602 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17603 | 14974 | }, |
| 17604 | 14975 | }, |
| 17605 | 14976 | .{ |
| 17606 | .name = "s_clamp", | |
| 17607 | .opcode = 149, | |
| 14977 | .name = "half_powr", | |
| 14978 | .opcode = 75, | |
| 17608 | 14979 | .operands = &.{ |
| 17609 | 14980 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17610 | 14981 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17611 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17612 | 14982 | }, |
| 17613 | 14983 | }, |
| 17614 | 14984 | .{ |
| 17615 | .name = "u_clamp", | |
| 17616 | .opcode = 150, | |
| 14985 | .name = "half_recip", | |
| 14986 | .opcode = 76, | |
| 17617 | 14987 | .operands = &.{ |
| 17618 | 14988 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17619 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17620 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17621 | 14989 | }, |
| 17622 | 14990 | }, |
| 17623 | 14991 | .{ |
| 17624 | .name = "clz", | |
| 17625 | .opcode = 151, | |
| 14992 | .name = "half_rsqrt", | |
| 14993 | .opcode = 77, | |
| 17626 | 14994 | .operands = &.{ |
| 17627 | 14995 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17628 | 14996 | }, |
| 17629 | 14997 | }, |
| 17630 | 14998 | .{ |
| 17631 | .name = "ctz", | |
| 17632 | .opcode = 152, | |
| 14999 | .name = "half_sin", | |
| 15000 | .opcode = 78, | |
| 17633 | 15001 | .operands = &.{ |
| 17634 | 15002 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17635 | 15003 | }, |
| 17636 | 15004 | }, |
| 17637 | 15005 | .{ |
| 17638 | .name = "s_mad_hi", | |
| 17639 | .opcode = 153, | |
| 15006 | .name = "half_sqrt", | |
| 15007 | .opcode = 79, | |
| 17640 | 15008 | .operands = &.{ |
| 17641 | 15009 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17642 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17643 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17644 | 15010 | }, |
| 17645 | 15011 | }, |
| 17646 | 15012 | .{ |
| 17647 | .name = "u_mad_sat", | |
| 17648 | .opcode = 154, | |
| 15013 | .name = "half_tan", | |
| 15014 | .opcode = 80, | |
| 17649 | 15015 | .operands = &.{ |
| 17650 | 15016 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17651 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17652 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17653 | 15017 | }, |
| 17654 | 15018 | }, |
| 17655 | 15019 | .{ |
| 17656 | .name = "s_mad_sat", | |
| 17657 | .opcode = 155, | |
| 15020 | .name = "native_cos", | |
| 15021 | .opcode = 81, | |
| 17658 | 15022 | .operands = &.{ |
| 17659 | 15023 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17660 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17661 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17662 | 15024 | }, |
| 17663 | 15025 | }, |
| 17664 | 15026 | .{ |
| 17665 | .name = "s_max", | |
| 17666 | .opcode = 156, | |
| 15027 | .name = "native_divide", | |
| 15028 | .opcode = 82, | |
| 17667 | 15029 | .operands = &.{ |
| 17668 | 15030 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17669 | 15031 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17670 | 15032 | }, |
| 17671 | 15033 | }, |
| 17672 | 15034 | .{ |
| 17673 | .name = "u_max", | |
| 17674 | .opcode = 157, | |
| 15035 | .name = "native_exp", | |
| 15036 | .opcode = 83, | |
| 17675 | 15037 | .operands = &.{ |
| 17676 | 15038 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17677 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17678 | 15039 | }, |
| 17679 | 15040 | }, |
| 17680 | 15041 | .{ |
| 17681 | .name = "s_min", | |
| 17682 | .opcode = 158, | |
| 15042 | .name = "native_exp2", | |
| 15043 | .opcode = 84, | |
| 17683 | 15044 | .operands = &.{ |
| 17684 | 15045 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17685 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17686 | 15046 | }, |
| 17687 | 15047 | }, |
| 17688 | 15048 | .{ |
| 17689 | .name = "u_min", | |
| 17690 | .opcode = 159, | |
| 15049 | .name = "native_exp10", | |
| 15050 | .opcode = 85, | |
| 17691 | 15051 | .operands = &.{ |
| 17692 | 15052 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17693 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17694 | 15053 | }, |
| 17695 | 15054 | }, |
| 17696 | 15055 | .{ |
| 17697 | .name = "s_mul_hi", | |
| 17698 | .opcode = 160, | |
| 15056 | .name = "native_log", | |
| 15057 | .opcode = 86, | |
| 17699 | 15058 | .operands = &.{ |
| 17700 | 15059 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17701 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17702 | 15060 | }, |
| 17703 | 15061 | }, |
| 17704 | 15062 | .{ |
| 17705 | .name = "rotate", | |
| 17706 | .opcode = 161, | |
| 15063 | .name = "native_log2", | |
| 15064 | .opcode = 87, | |
| 17707 | 15065 | .operands = &.{ |
| 17708 | 15066 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17709 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17710 | 15067 | }, |
| 17711 | 15068 | }, |
| 17712 | 15069 | .{ |
| 17713 | .name = "s_sub_sat", | |
| 17714 | .opcode = 162, | |
| 15070 | .name = "native_log10", | |
| 15071 | .opcode = 88, | |
| 17715 | 15072 | .operands = &.{ |
| 17716 | 15073 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17717 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17718 | 15074 | }, |
| 17719 | 15075 | }, |
| 17720 | 15076 | .{ |
| 17721 | .name = "u_sub_sat", | |
| 17722 | .opcode = 163, | |
| 15077 | .name = "native_powr", | |
| 15078 | .opcode = 89, | |
| 17723 | 15079 | .operands = &.{ |
| 17724 | 15080 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17725 | 15081 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17726 | 15082 | }, |
| 17727 | 15083 | }, |
| 17728 | 15084 | .{ |
| 17729 | .name = "u_upsample", | |
| 17730 | .opcode = 164, | |
| 15085 | .name = "native_recip", | |
| 15086 | .opcode = 90, | |
| 17731 | 15087 | .operands = &.{ |
| 17732 | 15088 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17733 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17734 | 15089 | }, |
| 17735 | 15090 | }, |
| 17736 | 15091 | .{ |
| 17737 | .name = "s_upsample", | |
| 17738 | .opcode = 165, | |
| 15092 | .name = "native_rsqrt", | |
| 15093 | .opcode = 91, | |
| 17739 | 15094 | .operands = &.{ |
| 17740 | 15095 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17741 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17742 | 15096 | }, |
| 17743 | 15097 | }, |
| 17744 | 15098 | .{ |
| 17745 | .name = "popcount", | |
| 17746 | .opcode = 166, | |
| 15099 | .name = "native_sin", | |
| 15100 | .opcode = 92, | |
| 17747 | 15101 | .operands = &.{ |
| 17748 | 15102 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17749 | 15103 | }, |
| 17750 | 15104 | }, |
| 17751 | 15105 | .{ |
| 17752 | .name = "s_mad24", | |
| 17753 | .opcode = 167, | |
| 15106 | .name = "native_sqrt", | |
| 15107 | .opcode = 93, | |
| 17754 | 15108 | .operands = &.{ |
| 17755 | 15109 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17756 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17757 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17758 | 15110 | }, |
| 17759 | 15111 | }, |
| 17760 | 15112 | .{ |
| 17761 | .name = "u_mad24", | |
| 17762 | .opcode = 168, | |
| 15113 | .name = "native_tan", | |
| 15114 | .opcode = 94, | |
| 17763 | 15115 | .operands = &.{ |
| 17764 | 15116 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17765 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17766 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17767 | 15117 | }, |
| 17768 | 15118 | }, |
| 17769 | 15119 | .{ |
| 17770 | .name = "s_mul24", | |
| 17771 | .opcode = 169, | |
| 15120 | .name = "fclamp", | |
| 15121 | .opcode = 95, | |
| 17772 | 15122 | .operands = &.{ |
| 17773 | 15123 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17774 | 15124 | .{ .kind = .id_ref, .quantifier = .required }, |
| 15125 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17775 | 15126 | }, |
| 17776 | 15127 | }, |
| 17777 | 15128 | .{ |
| 17778 | .name = "u_mul24", | |
| 17779 | .opcode = 170, | |
| 15129 | .name = "degrees", | |
| 15130 | .opcode = 96, | |
| 17780 | 15131 | .operands = &.{ |
| 17781 | 15132 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17782 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17783 | 15133 | }, |
| 17784 | 15134 | }, |
| 17785 | 15135 | .{ |
| 17786 | .name = "vloadn", | |
| 17787 | .opcode = 171, | |
| 15136 | .name = "fmax_common", | |
| 15137 | .opcode = 97, | |
| 17788 | 15138 | .operands = &.{ |
| 17789 | 15139 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17790 | 15140 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17791 | .{ .kind = .literal_integer, .quantifier = .required }, | |
| 17792 | 15141 | }, |
| 17793 | 15142 | }, |
| 17794 | 15143 | .{ |
| 17795 | .name = "vstoren", | |
| 17796 | .opcode = 172, | |
| 15144 | .name = "fmin_common", | |
| 15145 | .opcode = 98, | |
| 17797 | 15146 | .operands = &.{ |
| 17798 | 15147 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17799 | 15148 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17800 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17801 | 15149 | }, |
| 17802 | 15150 | }, |
| 17803 | 15151 | .{ |
| 17804 | .name = "vload_half", | |
| 17805 | .opcode = 173, | |
| 15152 | .name = "mix", | |
| 15153 | .opcode = 99, | |
| 17806 | 15154 | .operands = &.{ |
| 17807 | 15155 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17808 | 15156 | .{ .kind = .id_ref, .quantifier = .required }, |
| 15157 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17809 | 15158 | }, |
| 17810 | 15159 | }, |
| 17811 | 15160 | .{ |
| 17812 | .name = "vload_halfn", | |
| 17813 | .opcode = 174, | |
| 15161 | .name = "radians", | |
| 15162 | .opcode = 100, | |
| 17814 | 15163 | .operands = &.{ |
| 17815 | 15164 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17816 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17817 | .{ .kind = .literal_integer, .quantifier = .required }, | |
| 17818 | 15165 | }, |
| 17819 | 15166 | }, |
| 17820 | 15167 | .{ |
| 17821 | .name = "vstore_half", | |
| 17822 | .opcode = 175, | |
| 15168 | .name = "step", | |
| 15169 | .opcode = 101, | |
| 17823 | 15170 | .operands = &.{ |
| 17824 | 15171 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17825 | 15172 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17826 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17827 | 15173 | }, |
| 17828 | 15174 | }, |
| 17829 | 15175 | .{ |
| 17830 | .name = "vstore_half_r", | |
| 17831 | .opcode = 176, | |
| 15176 | .name = "smoothstep", | |
| 15177 | .opcode = 102, | |
| 17832 | 15178 | .operands = &.{ |
| 17833 | 15179 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17834 | 15180 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17835 | 15181 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17836 | .{ .kind = .fp_rounding_mode, .quantifier = .required }, | |
| 17837 | 15182 | }, |
| 17838 | 15183 | }, |
| 17839 | 15184 | .{ |
| 17840 | .name = "vstore_halfn", | |
| 17841 | .opcode = 177, | |
| 15185 | .name = "sign", | |
| 15186 | .opcode = 103, | |
| 17842 | 15187 | .operands = &.{ |
| 17843 | 15188 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17844 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17845 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17846 | 15189 | }, |
| 17847 | 15190 | }, |
| 17848 | 15191 | .{ |
| 17849 | .name = "vstore_halfn_r", | |
| 17850 | .opcode = 178, | |
| 15192 | .name = "cross", | |
| 15193 | .opcode = 104, | |
| 17851 | 15194 | .operands = &.{ |
| 17852 | 15195 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17853 | 15196 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17854 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17855 | .{ .kind = .fp_rounding_mode, .quantifier = .required }, | |
| 17856 | 15197 | }, |
| 17857 | 15198 | }, |
| 17858 | 15199 | .{ |
| 17859 | .name = "vloada_halfn", | |
| 17860 | .opcode = 179, | |
| 15200 | .name = "distance", | |
| 15201 | .opcode = 105, | |
| 17861 | 15202 | .operands = &.{ |
| 17862 | 15203 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17863 | 15204 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17864 | .{ .kind = .literal_integer, .quantifier = .required }, | |
| 17865 | 15205 | }, |
| 17866 | 15206 | }, |
| 17867 | 15207 | .{ |
| 17868 | .name = "vstorea_halfn", | |
| 17869 | .opcode = 180, | |
| 15208 | .name = "length", | |
| 15209 | .opcode = 106, | |
| 17870 | 15210 | .operands = &.{ |
| 17871 | 15211 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17872 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17873 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17874 | 15212 | }, |
| 17875 | 15213 | }, |
| 17876 | 15214 | .{ |
| 17877 | .name = "vstorea_halfn_r", | |
| 17878 | .opcode = 181, | |
| 15215 | .name = "normalize", | |
| 15216 | .opcode = 107, | |
| 17879 | 15217 | .operands = &.{ |
| 17880 | 15218 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17881 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17882 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17883 | .{ .kind = .fp_rounding_mode, .quantifier = .required }, | |
| 17884 | 15219 | }, |
| 17885 | 15220 | }, |
| 17886 | 15221 | .{ |
| 17887 | .name = "shuffle", | |
| 17888 | .opcode = 182, | |
| 15222 | .name = "fast_distance", | |
| 15223 | .opcode = 108, | |
| 17889 | 15224 | .operands = &.{ |
| 17890 | 15225 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17891 | 15226 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17892 | 15227 | }, |
| 17893 | 15228 | }, |
| 17894 | 15229 | .{ |
| 17895 | .name = "shuffle2", | |
| 17896 | .opcode = 183, | |
| 15230 | .name = "fast_length", | |
| 15231 | .opcode = 109, | |
| 17897 | 15232 | .operands = &.{ |
| 17898 | 15233 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17899 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17900 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17901 | 15234 | }, |
| 17902 | 15235 | }, |
| 17903 | 15236 | .{ |
| 17904 | .name = "printf", | |
| 17905 | .opcode = 184, | |
| 15237 | .name = "fast_normalize", | |
| 15238 | .opcode = 110, | |
| 17906 | 15239 | .operands = &.{ |
| 17907 | 15240 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17908 | .{ .kind = .id_ref, .quantifier = .variadic }, | |
| 17909 | 15241 | }, |
| 17910 | 15242 | }, |
| 17911 | 15243 | .{ |
| 17912 | .name = "prefetch", | |
| 17913 | .opcode = 185, | |
| 15244 | .name = "s_abs", | |
| 15245 | .opcode = 141, | |
| 17914 | 15246 | .operands = &.{ |
| 17915 | 15247 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17916 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17917 | 15248 | }, |
| 17918 | 15249 | }, |
| 17919 | 15250 | .{ |
| 17920 | .name = "bitselect", | |
| 17921 | .opcode = 186, | |
| 15251 | .name = "s_abs_diff", | |
| 15252 | .opcode = 142, | |
| 17922 | 15253 | .operands = &.{ |
| 17923 | 15254 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17924 | 15255 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17925 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17926 | 15256 | }, |
| 17927 | 15257 | }, |
| 17928 | 15258 | .{ |
| 17929 | .name = "select", | |
| 17930 | .opcode = 187, | |
| 15259 | .name = "s_add_sat", | |
| 15260 | .opcode = 143, | |
| 17931 | 15261 | .operands = &.{ |
| 17932 | 15262 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17933 | 15263 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17934 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17935 | 15264 | }, |
| 17936 | 15265 | }, |
| 17937 | 15266 | .{ |
| 17938 | .name = "u_abs", | |
| 17939 | .opcode = 201, | |
| 15267 | .name = "u_add_sat", | |
| 15268 | .opcode = 144, | |
| 17940 | 15269 | .operands = &.{ |
| 17941 | 15270 | .{ .kind = .id_ref, .quantifier = .required }, |
| 15271 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17942 | 15272 | }, |
| 17943 | 15273 | }, |
| 17944 | 15274 | .{ |
| 17945 | .name = "u_abs_diff", | |
| 17946 | .opcode = 202, | |
| 15275 | .name = "s_hadd", | |
| 15276 | .opcode = 145, | |
| 17947 | 15277 | .operands = &.{ |
| 17948 | 15278 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17949 | 15279 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17950 | 15280 | }, |
| 17951 | 15281 | }, |
| 17952 | 15282 | .{ |
| 17953 | .name = "u_mul_hi", | |
| 17954 | .opcode = 203, | |
| 15283 | .name = "u_hadd", | |
| 15284 | .opcode = 146, | |
| 17955 | 15285 | .operands = &.{ |
| 17956 | 15286 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17957 | 15287 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17958 | 15288 | }, |
| 17959 | 15289 | }, |
| 17960 | 15290 | .{ |
| 17961 | .name = "u_mad_hi", | |
| 17962 | .opcode = 204, | |
| 15291 | .name = "s_rhadd", | |
| 15292 | .opcode = 147, | |
| 17963 | 15293 | .operands = &.{ |
| 17964 | 15294 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17965 | 15295 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17966 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17967 | 15296 | }, |
| 17968 | 15297 | }, |
| 17969 | }, | |
| 17970 | .non_semantic_shader_debug_info_100 => &.{ | |
| 17971 | .{ | |
| 17972 | .name = "DebugInfoNone", | |
| 17973 | .opcode = 0, | |
| 17974 | .operands = &.{}, | |
| 17975 | }, | |
| 17976 | 15298 | .{ |
| 17977 | .name = "DebugCompilationUnit", | |
| 17978 | .opcode = 1, | |
| 15299 | .name = "u_rhadd", | |
| 15300 | .opcode = 148, | |
| 17979 | 15301 | .operands = &.{ |
| 17980 | 15302 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17981 | 15303 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17982 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17983 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17984 | 15304 | }, |
| 17985 | 15305 | }, |
| 17986 | 15306 | .{ |
| 17987 | .name = "DebugTypeBasic", | |
| 17988 | .opcode = 2, | |
| 15307 | .name = "s_clamp", | |
| 15308 | .opcode = 149, | |
| 17989 | 15309 | .operands = &.{ |
| 17990 | 15310 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17991 | 15311 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17992 | 15312 | .{ .kind = .id_ref, .quantifier = .required }, |
| 17993 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 17994 | 15313 | }, |
| 17995 | 15314 | }, |
| 17996 | 15315 | .{ |
| 17997 | .name = "DebugTypePointer", | |
| 17998 | .opcode = 3, | |
| 15316 | .name = "u_clamp", | |
| 15317 | .opcode = 150, | |
| 17999 | 15318 | .operands = &.{ |
| 18000 | 15319 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18001 | 15320 | .{ .kind = .id_ref, .quantifier = .required }, |
| ... | ... | @@ -18003,216 +15322,179 @@ pub const InstructionSet = enum { |
| 18003 | 15322 | }, |
| 18004 | 15323 | }, |
| 18005 | 15324 | .{ |
| 18006 | .name = "DebugTypeQualifier", | |
| 18007 | .opcode = 4, | |
| 15325 | .name = "clz", | |
| 15326 | .opcode = 151, | |
| 18008 | 15327 | .operands = &.{ |
| 18009 | 15328 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18010 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18011 | 15329 | }, |
| 18012 | 15330 | }, |
| 18013 | 15331 | .{ |
| 18014 | .name = "DebugTypeArray", | |
| 18015 | .opcode = 5, | |
| 15332 | .name = "ctz", | |
| 15333 | .opcode = 152, | |
| 18016 | 15334 | .operands = &.{ |
| 18017 | 15335 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18018 | .{ .kind = .id_ref, .quantifier = .variadic }, | |
| 18019 | 15336 | }, |
| 18020 | 15337 | }, |
| 18021 | 15338 | .{ |
| 18022 | .name = "DebugTypeVector", | |
| 18023 | .opcode = 6, | |
| 15339 | .name = "s_mad_hi", | |
| 15340 | .opcode = 153, | |
| 18024 | 15341 | .operands = &.{ |
| 18025 | 15342 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18026 | 15343 | .{ .kind = .id_ref, .quantifier = .required }, |
| 15344 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18027 | 15345 | }, |
| 18028 | 15346 | }, |
| 18029 | 15347 | .{ |
| 18030 | .name = "DebugTypedef", | |
| 18031 | .opcode = 7, | |
| 15348 | .name = "u_mad_sat", | |
| 15349 | .opcode = 154, | |
| 18032 | 15350 | .operands = &.{ |
| 18033 | 15351 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18034 | 15352 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18035 | 15353 | .{ .kind = .id_ref, .quantifier = .required }, |
| 15354 | }, | |
| 15355 | }, | |
| 15356 | .{ | |
| 15357 | .name = "s_mad_sat", | |
| 15358 | .opcode = 155, | |
| 15359 | .operands = &.{ | |
| 18036 | 15360 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18037 | 15361 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18038 | 15362 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18039 | 15363 | }, |
| 18040 | 15364 | }, |
| 18041 | 15365 | .{ |
| 18042 | .name = "DebugTypeFunction", | |
| 18043 | .opcode = 8, | |
| 15366 | .name = "s_max", | |
| 15367 | .opcode = 156, | |
| 18044 | 15368 | .operands = &.{ |
| 18045 | 15369 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18046 | 15370 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18047 | .{ .kind = .id_ref, .quantifier = .variadic }, | |
| 18048 | 15371 | }, |
| 18049 | 15372 | }, |
| 18050 | 15373 | .{ |
| 18051 | .name = "DebugTypeEnum", | |
| 18052 | .opcode = 9, | |
| 15374 | .name = "u_max", | |
| 15375 | .opcode = 157, | |
| 18053 | 15376 | .operands = &.{ |
| 18054 | 15377 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18055 | 15378 | .{ .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 }, | |
| 18063 | 15379 | }, |
| 18064 | 15380 | }, |
| 18065 | 15381 | .{ |
| 18066 | .name = "DebugTypeComposite", | |
| 18067 | .opcode = 10, | |
| 15382 | .name = "s_min", | |
| 15383 | .opcode = 158, | |
| 18068 | 15384 | .operands = &.{ |
| 18069 | 15385 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18070 | 15386 | .{ .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 }, | |
| 18079 | 15387 | }, |
| 18080 | 15388 | }, |
| 18081 | 15389 | .{ |
| 18082 | .name = "DebugTypeMember", | |
| 18083 | .opcode = 11, | |
| 15390 | .name = "u_min", | |
| 15391 | .opcode = 159, | |
| 18084 | 15392 | .operands = &.{ |
| 18085 | 15393 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18086 | 15394 | .{ .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 }, | |
| 18094 | 15395 | }, |
| 18095 | 15396 | }, |
| 18096 | 15397 | .{ |
| 18097 | .name = "DebugTypeInheritance", | |
| 18098 | .opcode = 12, | |
| 15398 | .name = "s_mul_hi", | |
| 15399 | .opcode = 160, | |
| 18099 | 15400 | .operands = &.{ |
| 18100 | 15401 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18101 | 15402 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18102 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18103 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18104 | 15403 | }, |
| 18105 | 15404 | }, |
| 18106 | 15405 | .{ |
| 18107 | .name = "DebugTypePtrToMember", | |
| 18108 | .opcode = 13, | |
| 15406 | .name = "rotate", | |
| 15407 | .opcode = 161, | |
| 18109 | 15408 | .operands = &.{ |
| 18110 | 15409 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18111 | 15410 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18112 | 15411 | }, |
| 18113 | 15412 | }, |
| 18114 | 15413 | .{ |
| 18115 | .name = "DebugTypeTemplate", | |
| 18116 | .opcode = 14, | |
| 15414 | .name = "s_sub_sat", | |
| 15415 | .opcode = 162, | |
| 18117 | 15416 | .operands = &.{ |
| 18118 | 15417 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18119 | .{ .kind = .id_ref, .quantifier = .variadic }, | |
| 15418 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18120 | 15419 | }, |
| 18121 | 15420 | }, |
| 18122 | 15421 | .{ |
| 18123 | .name = "DebugTypeTemplateParameter", | |
| 18124 | .opcode = 15, | |
| 15422 | .name = "u_sub_sat", | |
| 15423 | .opcode = 163, | |
| 18125 | 15424 | .operands = &.{ |
| 18126 | 15425 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18127 | 15426 | .{ .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 }, | |
| 18132 | 15427 | }, |
| 18133 | 15428 | }, |
| 18134 | 15429 | .{ |
| 18135 | .name = "DebugTypeTemplateTemplateParameter", | |
| 18136 | .opcode = 16, | |
| 15430 | .name = "u_upsample", | |
| 15431 | .opcode = 164, | |
| 18137 | 15432 | .operands = &.{ |
| 18138 | 15433 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18139 | 15434 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18140 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18141 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18142 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18143 | 15435 | }, |
| 18144 | 15436 | }, |
| 18145 | 15437 | .{ |
| 18146 | .name = "DebugTypeTemplateParameterPack", | |
| 18147 | .opcode = 17, | |
| 15438 | .name = "s_upsample", | |
| 15439 | .opcode = 165, | |
| 18148 | 15440 | .operands = &.{ |
| 18149 | 15441 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18150 | 15442 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18151 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18152 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18153 | .{ .kind = .id_ref, .quantifier = .variadic }, | |
| 18154 | 15443 | }, |
| 18155 | 15444 | }, |
| 18156 | 15445 | .{ |
| 18157 | .name = "DebugGlobalVariable", | |
| 18158 | .opcode = 18, | |
| 15446 | .name = "popcount", | |
| 15447 | .opcode = 166, | |
| 18159 | 15448 | .operands = &.{ |
| 18160 | 15449 | .{ .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 }, | |
| 18170 | 15450 | }, |
| 18171 | 15451 | }, |
| 18172 | 15452 | .{ |
| 18173 | .name = "DebugFunctionDeclaration", | |
| 18174 | .opcode = 19, | |
| 15453 | .name = "s_mad24", | |
| 15454 | .opcode = 167, | |
| 18175 | 15455 | .operands = &.{ |
| 18176 | 15456 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18177 | 15457 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18178 | 15458 | .{ .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 }, | |
| 18184 | 15459 | }, |
| 18185 | 15460 | }, |
| 18186 | 15461 | .{ |
| 18187 | .name = "DebugFunction", | |
| 18188 | .opcode = 20, | |
| 15462 | .name = "u_mad24", | |
| 15463 | .opcode = 168, | |
| 18189 | 15464 | .operands = &.{ |
| 18190 | 15465 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18191 | 15466 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18192 | 15467 | .{ .kind = .id_ref, .quantifier = .required }, |
| 15468 | }, | |
| 15469 | }, | |
| 15470 | .{ | |
| 15471 | .name = "s_mul24", | |
| 15472 | .opcode = 169, | |
| 15473 | .operands = &.{ | |
| 18193 | 15474 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18194 | 15475 | .{ .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 }, | |
| 18200 | 15476 | }, |
| 18201 | 15477 | }, |
| 18202 | 15478 | .{ |
| 18203 | .name = "DebugLexicalBlock", | |
| 18204 | .opcode = 21, | |
| 15479 | .name = "u_mul24", | |
| 15480 | .opcode = 170, | |
| 18205 | 15481 | .operands = &.{ |
| 18206 | 15482 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18207 | 15483 | .{ .kind = .id_ref, .quantifier = .required }, |
| 15484 | }, | |
| 15485 | }, | |
| 15486 | .{ | |
| 15487 | .name = "vloadn", | |
| 15488 | .opcode = 171, | |
| 15489 | .operands = &.{ | |
| 18208 | 15490 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18209 | 15491 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18210 | .{ .kind = .id_ref, .quantifier = .optional }, | |
| 15492 | .{ .kind = .literal_integer, .quantifier = .required }, | |
| 18211 | 15493 | }, |
| 18212 | 15494 | }, |
| 18213 | 15495 | .{ |
| 18214 | .name = "DebugLexicalBlockDiscriminator", | |
| 18215 | .opcode = 22, | |
| 15496 | .name = "vstoren", | |
| 15497 | .opcode = 172, | |
| 18216 | 15498 | .operands = &.{ |
| 18217 | 15499 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18218 | 15500 | .{ .kind = .id_ref, .quantifier = .required }, |
| ... | ... | @@ -18220,183 +15502,165 @@ pub const InstructionSet = enum { |
| 18220 | 15502 | }, |
| 18221 | 15503 | }, |
| 18222 | 15504 | .{ |
| 18223 | .name = "DebugScope", | |
| 18224 | .opcode = 23, | |
| 15505 | .name = "vload_half", | |
| 15506 | .opcode = 173, | |
| 18225 | 15507 | .operands = &.{ |
| 18226 | 15508 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18227 | .{ .kind = .id_ref, .quantifier = .optional }, | |
| 15509 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18228 | 15510 | }, |
| 18229 | 15511 | }, |
| 18230 | 15512 | .{ |
| 18231 | .name = "DebugNoScope", | |
| 18232 | .opcode = 24, | |
| 18233 | .operands = &.{}, | |
| 18234 | }, | |
| 18235 | .{ | |
| 18236 | .name = "DebugInlinedAt", | |
| 18237 | .opcode = 25, | |
| 15513 | .name = "vload_halfn", | |
| 15514 | .opcode = 174, | |
| 18238 | 15515 | .operands = &.{ |
| 18239 | 15516 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18240 | 15517 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18241 | .{ .kind = .id_ref, .quantifier = .optional }, | |
| 15518 | .{ .kind = .literal_integer, .quantifier = .required }, | |
| 18242 | 15519 | }, |
| 18243 | 15520 | }, |
| 18244 | 15521 | .{ |
| 18245 | .name = "DebugLocalVariable", | |
| 18246 | .opcode = 26, | |
| 15522 | .name = "vstore_half", | |
| 15523 | .opcode = 175, | |
| 18247 | 15524 | .operands = &.{ |
| 18248 | 15525 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18249 | 15526 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18250 | 15527 | .{ .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 }, | |
| 18256 | 15528 | }, |
| 18257 | 15529 | }, |
| 18258 | 15530 | .{ |
| 18259 | .name = "DebugInlinedVariable", | |
| 18260 | .opcode = 27, | |
| 15531 | .name = "vstore_half_r", | |
| 15532 | .opcode = 176, | |
| 18261 | 15533 | .operands = &.{ |
| 18262 | 15534 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18263 | 15535 | .{ .kind = .id_ref, .quantifier = .required }, |
| 15536 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 15537 | .{ .kind = .fp_rounding_mode, .quantifier = .required }, | |
| 18264 | 15538 | }, |
| 18265 | 15539 | }, |
| 18266 | 15540 | .{ |
| 18267 | .name = "DebugDeclare", | |
| 18268 | .opcode = 28, | |
| 15541 | .name = "vstore_halfn", | |
| 15542 | .opcode = 177, | |
| 18269 | 15543 | .operands = &.{ |
| 18270 | 15544 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18271 | 15545 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18272 | 15546 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18273 | .{ .kind = .id_ref, .quantifier = .variadic }, | |
| 18274 | 15547 | }, |
| 18275 | 15548 | }, |
| 18276 | 15549 | .{ |
| 18277 | .name = "DebugValue", | |
| 18278 | .opcode = 29, | |
| 15550 | .name = "vstore_halfn_r", | |
| 15551 | .opcode = 178, | |
| 18279 | 15552 | .operands = &.{ |
| 18280 | 15553 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18281 | 15554 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18282 | 15555 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18283 | .{ .kind = .id_ref, .quantifier = .variadic }, | |
| 15556 | .{ .kind = .fp_rounding_mode, .quantifier = .required }, | |
| 18284 | 15557 | }, |
| 18285 | 15558 | }, |
| 18286 | 15559 | .{ |
| 18287 | .name = "DebugOperation", | |
| 18288 | .opcode = 30, | |
| 15560 | .name = "vloada_halfn", | |
| 15561 | .opcode = 179, | |
| 18289 | 15562 | .operands = &.{ |
| 18290 | 15563 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18291 | .{ .kind = .id_ref, .quantifier = .variadic }, | |
| 15564 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 15565 | .{ .kind = .literal_integer, .quantifier = .required }, | |
| 18292 | 15566 | }, |
| 18293 | 15567 | }, |
| 18294 | 15568 | .{ |
| 18295 | .name = "DebugExpression", | |
| 18296 | .opcode = 31, | |
| 15569 | .name = "vstorea_halfn", | |
| 15570 | .opcode = 180, | |
| 18297 | 15571 | .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 }, | |
| 18299 | 15575 | }, |
| 18300 | 15576 | }, |
| 18301 | 15577 | .{ |
| 18302 | .name = "DebugMacroDef", | |
| 18303 | .opcode = 32, | |
| 15578 | .name = "vstorea_halfn_r", | |
| 15579 | .opcode = 181, | |
| 18304 | 15580 | .operands = &.{ |
| 18305 | 15581 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18306 | 15582 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18307 | 15583 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18308 | .{ .kind = .id_ref, .quantifier = .optional }, | |
| 15584 | .{ .kind = .fp_rounding_mode, .quantifier = .required }, | |
| 18309 | 15585 | }, |
| 18310 | 15586 | }, |
| 18311 | 15587 | .{ |
| 18312 | .name = "DebugMacroUndef", | |
| 18313 | .opcode = 33, | |
| 15588 | .name = "shuffle", | |
| 15589 | .opcode = 182, | |
| 18314 | 15590 | .operands = &.{ |
| 18315 | 15591 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18316 | 15592 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18317 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18318 | 15593 | }, |
| 18319 | 15594 | }, |
| 18320 | 15595 | .{ |
| 18321 | .name = "DebugImportedEntity", | |
| 18322 | .opcode = 34, | |
| 15596 | .name = "shuffle2", | |
| 15597 | .opcode = 183, | |
| 18323 | 15598 | .operands = &.{ |
| 18324 | 15599 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18325 | 15600 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18326 | 15601 | .{ .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 }, | |
| 18331 | 15602 | }, |
| 18332 | 15603 | }, |
| 18333 | 15604 | .{ |
| 18334 | .name = "DebugSource", | |
| 18335 | .opcode = 35, | |
| 15605 | .name = "printf", | |
| 15606 | .opcode = 184, | |
| 18336 | 15607 | .operands = &.{ |
| 18337 | 15608 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18338 | .{ .kind = .id_ref, .quantifier = .optional }, | |
| 15609 | .{ .kind = .id_ref, .quantifier = .variadic }, | |
| 18339 | 15610 | }, |
| 18340 | 15611 | }, |
| 18341 | 15612 | .{ |
| 18342 | .name = "DebugFunctionDefinition", | |
| 18343 | .opcode = 101, | |
| 15613 | .name = "prefetch", | |
| 15614 | .opcode = 185, | |
| 18344 | 15615 | .operands = &.{ |
| 18345 | 15616 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18346 | 15617 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18347 | 15618 | }, |
| 18348 | 15619 | }, |
| 18349 | 15620 | .{ |
| 18350 | .name = "DebugSourceContinued", | |
| 18351 | .opcode = 102, | |
| 15621 | .name = "bitselect", | |
| 15622 | .opcode = 186, | |
| 18352 | 15623 | .operands = &.{ |
| 18353 | 15624 | .{ .kind = .id_ref, .quantifier = .required }, |
| 15625 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 15626 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18354 | 15627 | }, |
| 18355 | 15628 | }, |
| 18356 | 15629 | .{ |
| 18357 | .name = "DebugLine", | |
| 18358 | .opcode = 103, | |
| 15630 | .name = "select", | |
| 15631 | .opcode = 187, | |
| 18359 | 15632 | .operands = &.{ |
| 18360 | 15633 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18361 | 15634 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18362 | 15635 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18363 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18364 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18365 | 15636 | }, |
| 18366 | 15637 | }, |
| 18367 | 15638 | .{ |
| 18368 | .name = "DebugNoLine", | |
| 18369 | .opcode = 104, | |
| 18370 | .operands = &.{}, | |
| 18371 | }, | |
| 18372 | .{ | |
| 18373 | .name = "DebugBuildIdentifier", | |
| 18374 | .opcode = 105, | |
| 15639 | .name = "u_abs", | |
| 15640 | .opcode = 201, | |
| 18375 | 15641 | .operands = &.{ |
| 18376 | 15642 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18377 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18378 | 15643 | }, |
| 18379 | 15644 | }, |
| 18380 | 15645 | .{ |
| 18381 | .name = "DebugStoragePath", | |
| 18382 | .opcode = 106, | |
| 15646 | .name = "u_abs_diff", | |
| 15647 | .opcode = 202, | |
| 18383 | 15648 | .operands = &.{ |
| 18384 | 15649 | .{ .kind = .id_ref, .quantifier = .required }, |
| 15650 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18385 | 15651 | }, |
| 18386 | 15652 | }, |
| 18387 | 15653 | .{ |
| 18388 | .name = "DebugEntryPoint", | |
| 18389 | .opcode = 107, | |
| 15654 | .name = "u_mul_hi", | |
| 15655 | .opcode = 203, | |
| 18390 | 15656 | .operands = &.{ |
| 18391 | 15657 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18392 | 15658 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18393 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18394 | .{ .kind = .id_ref, .quantifier = .required }, | |
| 18395 | 15659 | }, |
| 18396 | 15660 | }, |
| 18397 | 15661 | .{ |
| 18398 | .name = "DebugTypeMatrix", | |
| 18399 | .opcode = 108, | |
| 15662 | .name = "u_mad_hi", | |
| 15663 | .opcode = 204, | |
| 18400 | 15664 | .operands = &.{ |
| 18401 | 15665 | .{ .kind = .id_ref, .quantifier = .required }, |
| 18402 | 15666 | .{ .kind = .id_ref, .quantifier = .required }, |
src/dev.zig+1| ... | ... | @@ -191,6 +191,7 @@ pub const Env = enum { |
| 191 | 191 | .spirv => switch (feature) { |
| 192 | 192 | .spirv_backend, |
| 193 | 193 | .spirv_linker, |
| 194 | .legalize, | |
| 194 | 195 | => true, |
| 195 | 196 | else => Env.sema.supports(feature), |
| 196 | 197 | }, |
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 | ||
| 23 | const SpirV = @This(); | |
| 24 | ||
| 25 | 1 | const std = @import("std"); |
| 26 | 2 | const Allocator = std.mem.Allocator; |
| 3 | const Path = std.Build.Cache.Path; | |
| 27 | 4 | const assert = std.debug.assert; |
| 28 | 5 | const log = std.log.scoped(.link); |
| 29 | const Path = std.Build.Cache.Path; | |
| 30 | 6 | |
| 31 | 7 | const Zcu = @import("../Zcu.zig"); |
| 32 | 8 | const InternPool = @import("../InternPool.zig"); |
| 33 | 9 | const Compilation = @import("../Compilation.zig"); |
| 34 | 10 | const link = @import("../link.zig"); |
| 35 | const codegen = @import("../codegen/spirv.zig"); | |
| 36 | const trace = @import("../tracy.zig").trace; | |
| 37 | const build_options = @import("build_options"); | |
| 38 | 11 | const Air = @import("../Air.zig"); |
| 39 | 12 | const Type = @import("../Type.zig"); |
| 40 | const Value = @import("../Value.zig"); | |
| 13 | const BinaryModule = @import("SpirV/BinaryModule.zig"); | |
| 14 | const CodeGen = @import("../codegen/spirv/CodeGen.zig"); | |
| 15 | const Module = @import("../codegen/spirv/Module.zig"); | |
| 16 | const trace = @import("../tracy.zig").trace; | |
| 41 | 17 | |
| 42 | const SpvModule = @import("../codegen/spirv/Module.zig"); | |
| 43 | const Section = @import("../codegen/spirv/Section.zig"); | |
| 44 | 18 | const spec = @import("../codegen/spirv/spec.zig"); |
| 45 | 19 | const Id = spec.Id; |
| 46 | 20 | const Word = spec.Word; |
| 47 | 21 | |
| 48 | const BinaryModule = @import("SpirV/BinaryModule.zig"); | |
| 22 | const Linker = @This(); | |
| 49 | 23 | |
| 50 | 24 | base: link.File, |
| 51 | ||
| 52 | object: codegen.Object, | |
| 25 | module: Module, | |
| 26 | cg: CodeGen, | |
| 53 | 27 | |
| 54 | 28 | pub fn createEmpty( |
| 55 | 29 | arena: Allocator, |
| 56 | 30 | comp: *Compilation, |
| 57 | 31 | emit: Path, |
| 58 | 32 | options: link.File.OpenOptions, |
| 59 | ) !*SpirV { | |
| 33 | ) !*Linker { | |
| 60 | 34 | const gpa = comp.gpa; |
| 61 | 35 | const target = &comp.root_mod.resolved_target.result; |
| 62 | 36 | |
| ... | ... | @@ -72,8 +46,8 @@ pub fn createEmpty( |
| 72 | 46 | else => unreachable, // Caught by Compilation.Config.resolve. |
| 73 | 47 | } |
| 74 | 48 | |
| 75 | const self = try arena.create(SpirV); | |
| 76 | self.* = .{ | |
| 49 | const linker = try arena.create(Linker); | |
| 50 | linker.* = .{ | |
| 77 | 51 | .base = .{ |
| 78 | 52 | .tag = .spirv, |
| 79 | 53 | .comp = comp, |
| ... | ... | @@ -85,17 +59,30 @@ pub fn createEmpty( |
| 85 | 59 | .file = null, |
| 86 | 60 | .build_id = options.build_id, |
| 87 | 61 | }, |
| 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 | }, | |
| 89 | 77 | }; |
| 90 | errdefer self.deinit(); | |
| 78 | errdefer linker.deinit(); | |
| 91 | 79 | |
| 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, .{ | |
| 94 | 81 | .truncate = true, |
| 95 | 82 | .read = true, |
| 96 | 83 | }); |
| 97 | 84 | |
| 98 | return self; | |
| 85 | return linker; | |
| 99 | 86 | } |
| 100 | 87 | |
| 101 | 88 | pub fn open( |
| ... | ... | @@ -103,27 +90,90 @@ pub fn open( |
| 103 | 90 | comp: *Compilation, |
| 104 | 91 | emit: Path, |
| 105 | 92 | options: link.File.OpenOptions, |
| 106 | ) !*SpirV { | |
| 93 | ) !*Linker { | |
| 107 | 94 | return createEmpty(arena, comp, emit, options); |
| 108 | 95 | } |
| 109 | 96 | |
| 110 | pub fn deinit(self: *SpirV) void { | |
| 111 | self.object.deinit(); | |
| 97 | pub fn deinit(linker: *Linker) void { | |
| 98 | linker.cg.deinit(); | |
| 99 | linker.module.deinit(); | |
| 112 | 100 | } |
| 113 | 101 | |
| 114 | pub 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 | } | |
| 102 | fn 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), | |
| 118 | 132 | |
| 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 | ||
| 157 | pub 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 | ||
| 169 | pub fn updateNav(linker: *Linker, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void { | |
| 119 | 170 | const ip = &pt.zcu.intern_pool; |
| 120 | 171 | 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); | |
| 123 | 173 | } |
| 124 | 174 | |
| 125 | 175 | pub fn updateExports( |
| 126 | self: *SpirV, | |
| 176 | linker: *Linker, | |
| 127 | 177 | pt: Zcu.PerThread, |
| 128 | 178 | exported: Zcu.Exported, |
| 129 | 179 | export_indices: []const Zcu.Export.Index, |
| ... | ... | @@ -134,13 +184,13 @@ pub fn updateExports( |
| 134 | 184 | .nav => |nav| nav, |
| 135 | 185 | .uav => |uav| { |
| 136 | 186 | _ = 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"); | |
| 138 | 188 | }, |
| 139 | 189 | }; |
| 140 | 190 | const nav_ty = ip.getNav(nav_index).typeOf(ip); |
| 141 | 191 | const target = zcu.getTarget(); |
| 142 | 192 | 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); | |
| 144 | 194 | const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu); |
| 145 | 195 | const exec_model: spec.ExecutionModel = switch (target.os.tag) { |
| 146 | 196 | .vulkan, .opengl => switch (cc) { |
| ... | ... | @@ -162,7 +212,7 @@ pub fn updateExports( |
| 162 | 212 | |
| 163 | 213 | for (export_indices) |export_idx| { |
| 164 | 214 | const exp = export_idx.ptr(zcu); |
| 165 | try self.object.spv.declareEntryPoint( | |
| 215 | try linker.module.declareEntryPoint( | |
| 166 | 216 | spv_decl_index, |
| 167 | 217 | exp.opts.name.toSlice(ip), |
| 168 | 218 | exec_model, |
| ... | ... | @@ -175,7 +225,7 @@ pub fn updateExports( |
| 175 | 225 | } |
| 176 | 226 | |
| 177 | 227 | pub fn flush( |
| 178 | self: *SpirV, | |
| 228 | linker: *Linker, | |
| 179 | 229 | arena: Allocator, |
| 180 | 230 | tid: Zcu.PerThread.Id, |
| 181 | 231 | prog_node: std.Progress.Node, |
| ... | ... | @@ -185,35 +235,29 @@ pub fn flush( |
| 185 | 235 | // InternPool. |
| 186 | 236 | _ = tid; |
| 187 | 237 | |
| 188 | if (build_options.skip_non_native) { | |
| 189 | @panic("Attempted to compile for architecture that was disabled by build configuration"); | |
| 190 | } | |
| 191 | ||
| 192 | 238 | const tracy = trace(@src()); |
| 193 | 239 | defer tracy.end(); |
| 194 | 240 | |
| 195 | 241 | const sub_prog_node = prog_node.start("Flush Module", 0); |
| 196 | 242 | defer sub_prog_node.end(); |
| 197 | 243 | |
| 198 | const comp = self.base.comp; | |
| 199 | const spv = &self.object.spv; | |
| 244 | const comp = linker.base.comp; | |
| 200 | 245 | const diags = &comp.link_diags; |
| 201 | 246 | const gpa = comp.gpa; |
| 202 | 247 | |
| 203 | 248 | // We need to export the list of error names somewhere so that we can pretty-print them in the |
| 204 | 249 | // executor. This is not really an important thing though, so we can just dump it in any old |
| 205 | 250 | // 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); | |
| 207 | 252 | defer error_info.deinit(); |
| 208 | 253 | |
| 209 | 254 | 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; | |
| 211 | 256 | for (ip.global_error_set.getNamesFromMainThread()) |name| { |
| 212 | 257 | // Errors can contain pretty much any character - to encode them in a string we must escape |
| 213 | 258 | // them somehow. Easiest here is to use some established scheme, one which also preseves the |
| 214 | 259 | // name if it contains no strange characters is nice for debugging. URI encoding fits the bill. |
| 215 | 260 | // We're using : as separator, which is a reserved character. |
| 216 | ||
| 217 | 261 | error_info.writer.writeByte(':') catch return error.OutOfMemory; |
| 218 | 262 | std.Uri.Component.percentEncode( |
| 219 | 263 | &error_info.writer, |
| ... | ... | @@ -228,36 +272,34 @@ pub fn flush( |
| 228 | 272 | }.isValidChar, |
| 229 | 273 | ) catch return error.OutOfMemory; |
| 230 | 274 | } |
| 231 | try spv.sections.debug_strings.emit(gpa, .OpSourceExtension, .{ | |
| 275 | try linker.module.sections.debug_strings.emit(gpa, .OpSourceExtension, .{ | |
| 232 | 276 | .extension = error_info.getWritten(), |
| 233 | 277 | }); |
| 234 | 278 | |
| 235 | const module = try spv.finalize(arena); | |
| 279 | const module = try linker.module.finalize(arena); | |
| 236 | 280 | errdefer arena.free(module); |
| 237 | 281 | |
| 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) { | |
| 239 | 283 | error.OutOfMemory => return error.OutOfMemory, |
| 240 | 284 | else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}), |
| 241 | 285 | }; |
| 242 | 286 | |
| 243 | self.base.file.?.writeAll(std.mem.sliceAsBytes(linked_module)) catch |err| | |
| 287 | linker.base.file.?.writeAll(std.mem.sliceAsBytes(linked_module)) catch |err| | |
| 244 | 288 | return diags.fail("failed to write: {s}", .{@errorName(err)}); |
| 245 | 289 | } |
| 246 | 290 | |
| 247 | fn linkModule(self: *SpirV, a: Allocator, module: []Word, progress: std.Progress.Node) ![]Word { | |
| 248 | _ = self; | |
| 291 | fn linkModule(linker: *Linker, arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word { | |
| 292 | _ = linker; | |
| 249 | 293 | |
| 250 | 294 | const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig"); |
| 251 | 295 | const prune_unused = @import("SpirV/prune_unused.zig"); |
| 252 | const dedup = @import("SpirV/deduplicate.zig"); | |
| 253 | 296 | |
| 254 | var parser = try BinaryModule.Parser.init(a); | |
| 297 | var parser = try BinaryModule.Parser.init(arena); | |
| 255 | 298 | defer parser.deinit(); |
| 256 | 299 | var binary = try parser.parse(module); |
| 257 | 300 | |
| 258 | 301 | try lower_invocation_globals.run(&parser, &binary, progress); |
| 259 | 302 | try prune_unused.run(&parser, &binary, progress); |
| 260 | try dedup.run(&parser, &binary, progress); | |
| 261 | 303 | |
| 262 | return binary.finalize(a); | |
| 304 | return binary.finalize(arena); | |
| 263 | 305 | } |
src/link/SpirV/deduplicate.zig deleted-553| ... | ... | @@ -1,553 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Allocator = std.mem.Allocator; | |
| 3 | const log = std.log.scoped(.spirv_link); | |
| 4 | const assert = std.debug.assert; | |
| 5 | ||
| 6 | const BinaryModule = @import("BinaryModule.zig"); | |
| 7 | const Section = @import("../../codegen/spirv/Section.zig"); | |
| 8 | const spec = @import("../../codegen/spirv/spec.zig"); | |
| 9 | const Opcode = spec.Opcode; | |
| 10 | const ResultId = spec.Id; | |
| 11 | const Word = spec.Word; | |
| 12 | ||
| 13 | fn 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 | ||
| 32 | const 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 | ||
| 179 | const 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. | |
| 409 | const 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 | ||
| 421 | pub 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" { |
| 140 | 140 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 141 | 141 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 142 | 142 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; |
| 143 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 143 | 144 | |
| 144 | 145 | const Fields = packed struct { |
| 145 | 146 | timestamp: u50, |
test/behavior/slice.zig+2| ... | ... | @@ -1036,6 +1036,8 @@ test "sentinel-terminated 0-length slices" { |
| 1036 | 1036 | } |
| 1037 | 1037 | |
| 1038 | 1038 | test "peer slices keep abi alignment with empty struct" { |
| 1039 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | |
| 1040 | ||
| 1039 | 1041 | var cond: bool = undefined; |
| 1040 | 1042 | cond = false; |
| 1041 | 1043 | const slice = if (cond) &[1]u32{42} else &.{}; |
tools/gen_spirv_spec.zig+93-62| ... | ... | @@ -12,6 +12,7 @@ const ExtendedStructSet = std.StringHashMap(void); |
| 12 | 12 | |
| 13 | 13 | const Extension = struct { |
| 14 | 14 | name: []const u8, |
| 15 | opcode_name: []const u8, | |
| 15 | 16 | spec: ExtensionRegistry, |
| 16 | 17 | }; |
| 17 | 18 | |
| ... | ... | @@ -44,23 +45,11 @@ const OperandKindMap = std.ArrayHashMap(StringPair, OperandKind, StringPairConte |
| 44 | 45 | |
| 45 | 46 | /// Khronos made it so that these names are not defined explicitly, so |
| 46 | 47 | /// we need to hardcode it (like they did). |
| 47 | /// See https://github.com/KhronosGroup/SPIRV-Registry/ | |
| 48 | const 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 | |
| 49 | const 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" } }, | |
| 64 | 53 | }); |
| 65 | 54 | |
| 66 | 55 | var arena = std.heap.ArenaAllocator.init(std.heap.smp_allocator); |
| ... | ... | @@ -78,7 +67,7 @@ pub fn main() !void { |
| 78 | 67 | const dir = try std.fs.cwd().openDir(json_path, .{ .iterate = true }); |
| 79 | 68 | |
| 80 | 69 | 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); | |
| 82 | 71 | |
| 83 | 72 | var exts = std.ArrayList(Extension).init(allocator); |
| 84 | 73 | |
| ... | ... | @@ -134,14 +123,24 @@ fn readExtRegistry(exts: *std.ArrayList(Extension), dir: std.fs.Dir, sub_path: [ |
| 134 | 123 | const name = filename["extinst.".len .. filename.len - ".grammar.json".len]; |
| 135 | 124 | const spec = try readRegistry(ExtensionRegistry, dir, sub_path); |
| 136 | 125 | |
| 126 | const set_name = set_names.get(name) orelse { | |
| 127 | std.log.info("ignored instruction set '{s}'", .{name}); | |
| 128 | return; | |
| 129 | }; | |
| 130 | ||
| 137 | 131 | std.sort.block(Instruction, spec.instructions, CmpInst{}, CmpInst.lt); |
| 138 | 132 | |
| 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 | }); | |
| 140 | 138 | } |
| 141 | 139 | |
| 142 | 140 | fn readRegistry(comptime RegistryType: type, dir: std.fs.Dir, path: []const u8) !RegistryType { |
| 143 | 141 | const spec = try dir.readFileAlloc(allocator, path, std.math.maxInt(usize)); |
| 144 | 142 | // Required for json parsing. |
| 143 | // TODO: ALI | |
| 145 | 144 | @setEvalBranchQuota(10000); |
| 146 | 145 | |
| 147 | 146 | var scanner = std.json.Scanner.initCompleteInput(allocator, spec); |
| ... | ... | @@ -191,7 +190,11 @@ fn tagPriorityScore(tag: []const u8) usize { |
| 191 | 190 | } |
| 192 | 191 | } |
| 193 | 192 | |
| 194 | fn render(writer: *std.io.Writer, registry: CoreRegistry, extensions: []const Extension) !void { | |
| 193 | fn render( | |
| 194 | writer: *std.io.Writer, | |
| 195 | registry: CoreRegistry, | |
| 196 | extensions: []const Extension, | |
| 197 | ) !void { | |
| 195 | 198 | try writer.writeAll( |
| 196 | 199 | \\//! This file is auto-generated by tools/gen_spirv_spec.zig. |
| 197 | 200 | \\ |
| ... | ... | @@ -221,6 +224,16 @@ fn render(writer: *std.io.Writer, registry: CoreRegistry, extensions: []const Ex |
| 221 | 224 | \\ } |
| 222 | 225 | \\}; |
| 223 | 226 | \\ |
| 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 | \\ | |
| 224 | 237 | \\pub const LiteralInteger = Word; |
| 225 | 238 | \\pub const LiteralFloat = Word; |
| 226 | 239 | \\pub const LiteralString = []const u8; |
| ... | ... | @@ -307,13 +320,18 @@ fn render(writer: *std.io.Writer, registry: CoreRegistry, extensions: []const Ex |
| 307 | 320 | // Note: extensions don't seem to have class. |
| 308 | 321 | try renderClass(writer, registry.instructions); |
| 309 | 322 | 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 | ||
| 311 | 329 | try renderOperandKinds(writer, all_operand_kinds.values(), extended_structs); |
| 312 | 330 | try renderInstructionSet(writer, registry, extensions, all_operand_kinds); |
| 313 | 331 | } |
| 314 | 332 | |
| 315 | 333 | fn renderInstructionSet( |
| 316 | writer: anytype, | |
| 334 | writer: *std.io.Writer, | |
| 317 | 335 | core: CoreRegistry, |
| 318 | 336 | extensions: []const Extension, |
| 319 | 337 | all_operand_kinds: OperandKindMap, |
| ... | ... | @@ -324,7 +342,7 @@ fn renderInstructionSet( |
| 324 | 342 | ); |
| 325 | 343 | |
| 326 | 344 | for (extensions) |ext| { |
| 327 | try writer.print("{f},\n", .{formatId(ext.name)}); | |
| 345 | try writer.print("{f},\n", .{std.zig.fmtId(ext.name)}); | |
| 328 | 346 | } |
| 329 | 347 | |
| 330 | 348 | try writer.writeAll( |
| ... | ... | @@ -348,7 +366,7 @@ fn renderInstructionSet( |
| 348 | 366 | } |
| 349 | 367 | |
| 350 | 368 | fn renderInstructionsCase( |
| 351 | writer: anytype, | |
| 369 | writer: *std.io.Writer, | |
| 352 | 370 | set_name: []const u8, |
| 353 | 371 | instructions: []const Instruction, |
| 354 | 372 | all_operand_kinds: OperandKindMap, |
| ... | ... | @@ -357,7 +375,7 @@ fn renderInstructionsCase( |
| 357 | 375 | // but there aren't so many total aliases and that would add more overhead in total. We will |
| 358 | 376 | // just filter those out when needed. |
| 359 | 377 | |
| 360 | try writer.print(".{f} => &.{{\n", .{formatId(set_name)}); | |
| 378 | try writer.print(".{f} => &.{{\n", .{std.zig.fmtId(set_name)}); | |
| 361 | 379 | |
| 362 | 380 | for (instructions) |inst| { |
| 363 | 381 | try writer.print( |
| ... | ... | @@ -395,7 +413,7 @@ fn renderInstructionsCase( |
| 395 | 413 | ); |
| 396 | 414 | } |
| 397 | 415 | |
| 398 | fn renderClass(writer: anytype, instructions: []const Instruction) !void { | |
| 416 | fn renderClass(writer: *std.io.Writer, instructions: []const Instruction) !void { | |
| 399 | 417 | var class_map = std.StringArrayHashMap(void).init(allocator); |
| 400 | 418 | |
| 401 | 419 | for (instructions) |inst| { |
| ... | ... | @@ -444,7 +462,7 @@ fn formatId(identifier: []const u8) std.fmt.Alt(Formatter, Formatter.format) { |
| 444 | 462 | return .{ .data = .{ .data = identifier } }; |
| 445 | 463 | } |
| 446 | 464 | |
| 447 | fn renderOperandKind(writer: anytype, operands: []const OperandKind) !void { | |
| 465 | fn renderOperandKind(writer: *std.io.Writer, operands: []const OperandKind) !void { | |
| 448 | 466 | try writer.writeAll( |
| 449 | 467 | \\pub const OperandKind = enum { |
| 450 | 468 | \\ opcode, |
| ... | ... | @@ -500,7 +518,7 @@ fn renderOperandKind(writer: anytype, operands: []const OperandKind) !void { |
| 500 | 518 | try writer.writeAll("};\n}\n};\n"); |
| 501 | 519 | } |
| 502 | 520 | |
| 503 | fn renderEnumerant(writer: anytype, enumerant: Enumerant) !void { | |
| 521 | fn renderEnumerant(writer: *std.io.Writer, enumerant: Enumerant) !void { | |
| 504 | 522 | try writer.print(".{{.name = \"{s}\", .value = ", .{enumerant.enumerant}); |
| 505 | 523 | switch (enumerant.value) { |
| 506 | 524 | .bitflag => |flag| try writer.writeAll(flag), |
| ... | ... | @@ -517,7 +535,9 @@ fn renderEnumerant(writer: anytype, enumerant: Enumerant) !void { |
| 517 | 535 | } |
| 518 | 536 | |
| 519 | 537 | fn renderOpcodes( |
| 520 | writer: anytype, | |
| 538 | writer: *std.io.Writer, | |
| 539 | opcode_type_name: []const u8, | |
| 540 | want_operands: bool, | |
| 521 | 541 | instructions: []const Instruction, |
| 522 | 542 | extended_structs: ExtendedStructSet, |
| 523 | 543 | ) !void { |
| ... | ... | @@ -528,7 +548,9 @@ fn renderOpcodes( |
| 528 | 548 | try aliases.ensureTotalCapacity(instructions.len); |
| 529 | 549 | |
| 530 | 550 | 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 | } | |
| 532 | 554 | |
| 533 | 555 | const result = inst_map.getOrPutAssumeCapacity(inst.opcode); |
| 534 | 556 | if (!result.found_existing) { |
| ... | ... | @@ -552,58 +574,67 @@ fn renderOpcodes( |
| 552 | 574 | |
| 553 | 575 | const instructions_indices = inst_map.values(); |
| 554 | 576 | |
| 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)}); | |
| 556 | 578 | for (instructions_indices) |i| { |
| 557 | 579 | const inst = instructions[i]; |
| 558 | 580 | try writer.print("{f} = {},\n", .{ std.zig.fmtId(inst.opname), inst.opcode }); |
| 559 | 581 | } |
| 560 | 582 | |
| 561 | try writer.writeAll( | |
| 562 | \\ | |
| 563 | ); | |
| 583 | try writer.writeAll("\n"); | |
| 564 | 584 | |
| 565 | 585 | for (aliases.items) |alias| { |
| 566 | try writer.print("pub const {f} = Opcode.{f};\n", .{ | |
| 586 | try writer.print("pub const {f} = {f}.{f};\n", .{ | |
| 567 | 587 | formatId(instructions[alias.inst].opname), |
| 588 | std.zig.fmtId(opcode_type_name), | |
| 568 | 589 | formatId(instructions[alias.alias].opname), |
| 569 | 590 | }); |
| 570 | 591 | } |
| 571 | 592 | |
| 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)}); | |
| 578 | 600 | |
| 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 | } | |
| 583 | 605 | |
| 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 | ); | |
| 591 | 611 | |
| 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 | ); | |
| 595 | 628 | } |
| 596 | 629 | |
| 597 | 630 | try writer.writeAll( |
| 598 | \\ }; | |
| 599 | \\} | |
| 600 | 631 | \\}; |
| 601 | 632 | \\ |
| 602 | 633 | ); |
| 603 | 634 | } |
| 604 | 635 | |
| 605 | 636 | fn renderOperandKinds( |
| 606 | writer: anytype, | |
| 637 | writer: *std.io.Writer, | |
| 607 | 638 | kinds: []const OperandKind, |
| 608 | 639 | extended_structs: ExtendedStructSet, |
| 609 | 640 | ) !void { |
| ... | ... | @@ -617,7 +648,7 @@ fn renderOperandKinds( |
| 617 | 648 | } |
| 618 | 649 | |
| 619 | 650 | fn renderValueEnum( |
| 620 | writer: anytype, | |
| 651 | writer: *std.io.Writer, | |
| 621 | 652 | enumeration: OperandKind, |
| 622 | 653 | extended_structs: ExtendedStructSet, |
| 623 | 654 | ) !void { |
| ... | ... | @@ -695,7 +726,7 @@ fn renderValueEnum( |
| 695 | 726 | } |
| 696 | 727 | |
| 697 | 728 | fn renderBitEnum( |
| 698 | writer: anytype, | |
| 729 | writer: *std.io.Writer, | |
| 699 | 730 | enumeration: OperandKind, |
| 700 | 731 | extended_structs: ExtendedStructSet, |
| 701 | 732 | ) !void { |
| ... | ... | @@ -778,7 +809,7 @@ fn renderBitEnum( |
| 778 | 809 | } |
| 779 | 810 | |
| 780 | 811 | fn renderOperand( |
| 781 | writer: anytype, | |
| 812 | writer: *std.io.Writer, | |
| 782 | 813 | kind: enum { |
| 783 | 814 | @"union", |
| 784 | 815 | instruction, |
| ... | ... | @@ -862,7 +893,7 @@ fn renderOperand( |
| 862 | 893 | try writer.writeAll(",\n"); |
| 863 | 894 | } |
| 864 | 895 | |
| 865 | fn renderFieldName(writer: anytype, operands: []const Operand, field_index: usize) !void { | |
| 896 | fn renderFieldName(writer: *std.io.Writer, operands: []const Operand, field_index: usize) !void { | |
| 866 | 897 | const operand = operands[field_index]; |
| 867 | 898 | |
| 868 | 899 | derive_from_kind: { |