blob d4227ddf (249085B) - Raw
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.ArrayList(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.ArrayList(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.ArrayList(*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.ArrayList(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.ArrayList(Id) = .empty, 155 inst_results: std.AutoHashMapUnmanaged(Air.Inst.Index, Id) = .empty, 156 id_scratch: std.ArrayList(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 key = ip.indexToKey(val.toIntern()).@"extern"; 258 259 const storage_class = cg.module.storageClass(nav.getAddrspace()); 260 assert(storage_class != .generic); // These should be instance globals 261 262 const ty_id = try cg.resolveType(ty, .indirect); 263 const ptr_ty_id = try cg.module.ptrType(ty_id, storage_class); 264 265 try cg.module.sections.globals.emit(gpa, .OpVariable, .{ 266 .id_result_type = ptr_ty_id, 267 .id_result = result_id, 268 .storage_class = storage_class, 269 }); 270 271 switch (target.os.tag) { 272 .vulkan, .opengl => { 273 if (ty.zigTypeTag(zcu) == .@"struct") { 274 switch (storage_class) { 275 .uniform, .push_constant => try cg.module.decorate(ty_id, .block), 276 else => {}, 277 } 278 } 279 280 try cg.module.decorate(ptr_ty_id, .{ 281 .array_stride = .{ .array_stride = @intCast(ty.abiSize(zcu)) }, 282 }); 283 284 if (key.decoration) |decoration| switch (decoration) { 285 .location => |location| { 286 if (storage_class != .output and storage_class != .input and storage_class != .uniform_constant) { 287 return cg.fail("storage class must be one of (output, input, uniform_constant) but is {s}", .{@tagName(storage_class)}); 288 } 289 try cg.module.decorate(result_id, .{ 290 .location = .{ .location = location }, 291 }); 292 }, 293 .descriptor => |descriptor| { 294 if (storage_class != .storage_buffer and storage_class != .uniform and storage_class != .uniform_constant) { 295 return cg.fail("storage class must be one of (storage_buffer, uniform, uniform_constant) but is {s}", .{@tagName(storage_class)}); 296 } 297 try cg.module.decorate(result_id, .{ 298 .binding = .{ .binding_point = descriptor.binding }, 299 }); 300 301 try cg.module.decorate(result_id, .{ 302 .descriptor_set = .{ .descriptor_set = descriptor.set }, 303 }); 304 }, 305 }; 306 }, 307 else => {}, 308 } 309 310 if (std.meta.stringToEnum(spec.BuiltIn, nav.fqn.toSlice(ip))) |builtin| { 311 try cg.module.decorate(result_id, .{ .built_in = .{ .built_in = builtin } }); 312 } 313 314 try cg.module.debugName(result_id, nav.fqn.toSlice(ip)); 315 }, 316 .invocation_global => { 317 const maybe_init_val: ?Value = switch (ip.indexToKey(val.toIntern())) { 318 .func => unreachable, 319 .variable => |variable| .fromInterned(variable.init), 320 .@"extern" => null, 321 else => val, 322 }; 323 324 const ty_id = try cg.resolveType(ty, .indirect); 325 const ptr_ty_id = try cg.module.ptrType(ty_id, .function); 326 327 if (maybe_init_val) |init_val| { 328 // TODO: Combine with resolveAnonDecl? 329 const void_ty_id = try cg.resolveType(.void, .direct); 330 const initializer_proto_ty_id = try cg.module.functionType(void_ty_id, &.{}); 331 332 const initializer_id = cg.module.allocId(); 333 try cg.prologue.emit(gpa, .OpFunction, .{ 334 .id_result_type = try cg.resolveType(.void, .direct), 335 .id_result = initializer_id, 336 .function_control = .{}, 337 .function_type = initializer_proto_ty_id, 338 }); 339 340 const root_block_id = cg.module.allocId(); 341 try cg.prologue.emit(gpa, .OpLabel, .{ 342 .id_result = root_block_id, 343 }); 344 cg.block_label = root_block_id; 345 346 const val_id = try cg.constant(ty, init_val, .indirect); 347 try cg.body.emit(gpa, .OpStore, .{ 348 .pointer = result_id, 349 .object = val_id, 350 }); 351 352 try cg.body.emit(gpa, .OpReturn, {}); 353 try cg.body.emit(gpa, .OpFunctionEnd, {}); 354 try cg.module.sections.functions.append(gpa, cg.prologue); 355 try cg.module.sections.functions.append(gpa, cg.body); 356 357 try cg.module.debugNameFmt(initializer_id, "initializer of {f}", .{nav.fqn.fmt(ip)}); 358 359 try cg.module.sections.globals.emit(gpa, .OpExtInst, .{ 360 .id_result_type = ptr_ty_id, 361 .id_result = result_id, 362 .set = try cg.module.importInstructionSet(.zig), 363 .instruction = .{ .inst = @intFromEnum(spec.Zig.InvocationGlobal) }, 364 .id_ref_4 = &.{initializer_id}, 365 }); 366 } else { 367 try cg.module.sections.globals.emit(gpa, .OpExtInst, .{ 368 .id_result_type = ptr_ty_id, 369 .id_result = result_id, 370 .set = try cg.module.importInstructionSet(.zig), 371 .instruction = .{ .inst = @intFromEnum(spec.Zig.InvocationGlobal) }, 372 .id_ref_4 = &.{}, 373 }); 374 } 375 }, 376 } 377 378 cg.module.declPtr(spv_decl_index).end_dep = cg.module.decl_deps.items.len; 379 } 380 381 pub fn fail(cg: *CodeGen, comptime format: []const u8, args: anytype) Error { 382 @branchHint(.cold); 383 const zcu = cg.module.zcu; 384 const src_loc = zcu.navSrcLoc(cg.owner_nav); 385 assert(cg.error_msg == null); 386 cg.error_msg = try Zcu.ErrorMsg.create(zcu.gpa, src_loc, format, args); 387 return error.CodegenFail; 388 } 389 390 pub fn todo(cg: *CodeGen, comptime format: []const u8, args: anytype) Error { 391 return cg.fail("TODO (SPIR-V): " ++ format, args); 392 } 393 394 /// This imports the "default" extended instruction set for the target 395 /// For OpenCL, OpenCL.std.100. For Vulkan and OpenGL, GLSL.std.450. 396 fn importExtendedSet(cg: *CodeGen) !Id { 397 const target = cg.module.zcu.getTarget(); 398 return switch (target.os.tag) { 399 .opencl, .amdhsa => try cg.module.importInstructionSet(.@"OpenCL.std"), 400 .vulkan, .opengl => try cg.module.importInstructionSet(.@"GLSL.std.450"), 401 else => unreachable, 402 }; 403 } 404 405 /// Fetch the result-id for a previously generated instruction or constant. 406 fn resolve(cg: *CodeGen, inst: Air.Inst.Ref) !Id { 407 const pt = cg.pt; 408 const zcu = cg.module.zcu; 409 const ip = &zcu.intern_pool; 410 if (try cg.air.value(inst, pt)) |val| { 411 const ty = cg.typeOf(inst); 412 if (ty.zigTypeTag(zcu) == .@"fn") { 413 const fn_nav = switch (zcu.intern_pool.indexToKey(val.ip_index)) { 414 .@"extern" => |@"extern"| @"extern".owner_nav, 415 .func => |func| func.owner_nav, 416 else => unreachable, 417 }; 418 const spv_decl_index = try cg.module.resolveNav(ip, fn_nav); 419 try cg.module.decl_deps.append(cg.module.gpa, spv_decl_index); 420 return cg.module.declPtr(spv_decl_index).result_id; 421 } 422 423 return try cg.constant(ty, val, .direct); 424 } 425 const index = inst.toIndex().?; 426 return cg.inst_results.get(index).?; // Assertion means instruction does not dominate usage. 427 } 428 429 fn resolveUav(cg: *CodeGen, val: InternPool.Index) !Id { 430 const gpa = cg.module.gpa; 431 432 // TODO: This cannot be a function at this point, but it should probably be handled anyway. 433 434 const zcu = cg.module.zcu; 435 const ty: Type = .fromInterned(zcu.intern_pool.typeOf(val)); 436 const ty_id = try cg.resolveType(ty, .indirect); 437 438 const spv_decl_index = blk: { 439 const entry = try cg.module.uav_link.getOrPut(gpa, .{ val, .function }); 440 if (entry.found_existing) { 441 try cg.addFunctionDep(entry.value_ptr.*, .function); 442 return cg.module.declPtr(entry.value_ptr.*).result_id; 443 } 444 445 const spv_decl_index = try cg.module.allocDecl(.invocation_global); 446 try cg.addFunctionDep(spv_decl_index, .function); 447 entry.value_ptr.* = spv_decl_index; 448 break :blk spv_decl_index; 449 }; 450 451 // TODO: At some point we will be able to generate this all constant here, but then all of 452 // constant() will need to be implemented such that it doesn't generate any at-runtime code. 453 // NOTE: Because this is a global, we really only want to initialize it once. Therefore the 454 // constant lowering of this value will need to be deferred to an initializer similar to 455 // other globals. 456 457 const result_id = cg.module.declPtr(spv_decl_index).result_id; 458 459 { 460 // Save the current state so that we can temporarily generate into a different function. 461 // TODO: This should probably be made a little more robust. 462 const func_prologue = cg.prologue; 463 const func_body = cg.body; 464 const block_label = cg.block_label; 465 defer { 466 cg.prologue = func_prologue; 467 cg.body = func_body; 468 cg.block_label = block_label; 469 } 470 471 cg.prologue = .{}; 472 cg.body = .{}; 473 defer { 474 cg.prologue.deinit(gpa); 475 cg.body.deinit(gpa); 476 } 477 478 const void_ty_id = try cg.resolveType(.void, .direct); 479 const initializer_proto_ty_id = try cg.module.functionType(void_ty_id, &.{}); 480 481 const initializer_id = cg.module.allocId(); 482 try cg.prologue.emit(gpa, .OpFunction, .{ 483 .id_result_type = try cg.resolveType(.void, .direct), 484 .id_result = initializer_id, 485 .function_control = .{}, 486 .function_type = initializer_proto_ty_id, 487 }); 488 const root_block_id = cg.module.allocId(); 489 try cg.prologue.emit(gpa, .OpLabel, .{ 490 .id_result = root_block_id, 491 }); 492 cg.block_label = root_block_id; 493 494 const val_id = try cg.constant(ty, .fromInterned(val), .indirect); 495 try cg.body.emit(gpa, .OpStore, .{ 496 .pointer = result_id, 497 .object = val_id, 498 }); 499 500 try cg.body.emit(gpa, .OpReturn, {}); 501 try cg.body.emit(gpa, .OpFunctionEnd, {}); 502 503 try cg.module.sections.functions.append(gpa, cg.prologue); 504 try cg.module.sections.functions.append(gpa, cg.body); 505 506 try cg.module.debugNameFmt(initializer_id, "initializer of __anon_{d}", .{@intFromEnum(val)}); 507 508 const fn_decl_ptr_ty_id = try cg.module.ptrType(ty_id, .function); 509 try cg.module.sections.globals.emit(gpa, .OpExtInst, .{ 510 .id_result_type = fn_decl_ptr_ty_id, 511 .id_result = result_id, 512 .set = try cg.module.importInstructionSet(.zig), 513 .instruction = .{ .inst = @intFromEnum(spec.Zig.InvocationGlobal) }, 514 .id_ref_4 = &.{initializer_id}, 515 }); 516 } 517 518 return result_id; 519 } 520 521 fn addFunctionDep(cg: *CodeGen, decl_index: Module.Decl.Index, storage_class: StorageClass) !void { 522 const gpa = cg.module.gpa; 523 const target = cg.module.zcu.getTarget(); 524 if (target.cpu.has(.spirv, .v1_4)) { 525 try cg.module.decl_deps.append(gpa, decl_index); 526 } else { 527 // Before version 1.4, the interface’s storage classes are limited to the Input and Output 528 if (storage_class == .input or storage_class == .output) { 529 try cg.module.decl_deps.append(gpa, decl_index); 530 } 531 } 532 } 533 534 /// Start a new SPIR-V block, Emits the label of the new block, and stores which 535 /// block we are currently generating. 536 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to 537 /// keep track of the previous block. 538 fn beginSpvBlock(cg: *CodeGen, label: Id) !void { 539 try cg.body.emit(cg.module.gpa, .OpLabel, .{ .id_result = label }); 540 cg.block_label = label; 541 } 542 543 /// Return the amount of bits in the largest supported integer type. This is either 32 (always supported), or 64 (if 544 /// the Int64 capability is enabled). 545 /// Note: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits). 546 /// In theory that could also be used, but since the spec says that it only guarantees support up to 32-bit ints there 547 /// is no way of knowing whether those are actually supported. 548 /// TODO: Maybe this should be cached? 549 fn largestSupportedIntBits(cg: *CodeGen) u16 { 550 const target = cg.module.zcu.getTarget(); 551 if (target.cpu.has(.spirv, .int64) or target.cpu.arch == .spirv64) { 552 return 64; 553 } 554 return 32; 555 } 556 557 const ArithmeticTypeInfo = struct { 558 const Class = enum { 559 bool, 560 /// A regular, **native**, integer. 561 /// This is only returned when the backend supports this int as a native type (when 562 /// the relevant capability is enabled). 563 integer, 564 /// A regular float. These are all required to be natively supported. Floating points 565 /// for which the relevant capability is not enabled are not emulated. 566 float, 567 /// An integer of a 'strange' size (which' bit size is not the same as its backing 568 /// type. **Note**: this may **also** include power-of-2 integers for which the 569 /// relevant capability is not enabled), but still within the limits of the largest 570 /// natively supported integer type. 571 strange_integer, 572 /// An integer with more bits than the largest natively supported integer type. 573 composite_integer, 574 }; 575 576 /// A classification of the inner type. 577 /// These scenarios will all have to be handled slightly different. 578 class: Class, 579 /// The number of bits in the inner type. 580 /// This is the actual number of bits of the type, not the size of the backing integer. 581 bits: u16, 582 /// The number of bits required to store the type. 583 /// For `integer` and `float`, this is equal to `bits`. 584 /// For `strange_integer` and `bool` this is the size of the backing integer. 585 /// For `composite_integer` this is the elements count. 586 backing_bits: u16, 587 /// Null if this type is a scalar, or the length of the vector otherwise. 588 vector_len: ?u32, 589 /// Whether the inner type is signed. Only relevant for integers. 590 signedness: std.builtin.Signedness, 591 }; 592 593 fn arithmeticTypeInfo(cg: *CodeGen, ty: Type) ArithmeticTypeInfo { 594 const zcu = cg.module.zcu; 595 const target = cg.module.zcu.getTarget(); 596 var scalar_ty = ty.scalarType(zcu); 597 if (scalar_ty.zigTypeTag(zcu) == .@"enum") { 598 scalar_ty = scalar_ty.intTagType(zcu); 599 } 600 const vector_len = if (ty.isVector(zcu)) ty.vectorLen(zcu) else null; 601 return switch (scalar_ty.zigTypeTag(zcu)) { 602 .bool => .{ 603 .bits = 1, // Doesn't matter for this class. 604 .backing_bits = cg.module.backingIntBits(1).@"0", 605 .vector_len = vector_len, 606 .signedness = .unsigned, // Technically, but doesn't matter for this class. 607 .class = .bool, 608 }, 609 .float => .{ 610 .bits = scalar_ty.floatBits(target), 611 .backing_bits = scalar_ty.floatBits(target), // TODO: F80? 612 .vector_len = vector_len, 613 .signedness = .signed, // Technically, but doesn't matter for this class. 614 .class = .float, 615 }, 616 .int => blk: { 617 const int_info = scalar_ty.intInfo(zcu); 618 // TODO: Maybe it's useful to also return this value. 619 const backing_bits, const big_int = cg.module.backingIntBits(int_info.bits); 620 break :blk .{ 621 .bits = int_info.bits, 622 .backing_bits = backing_bits, 623 .vector_len = vector_len, 624 .signedness = int_info.signedness, 625 .class = class: { 626 if (big_int) break :class .composite_integer; 627 break :class if (backing_bits == int_info.bits) .integer else .strange_integer; 628 }, 629 }; 630 }, 631 .@"enum" => unreachable, 632 .vector => unreachable, 633 else => unreachable, // Unhandled arithmetic type 634 }; 635 } 636 637 /// Checks whether the type can be directly translated to SPIR-V vectors 638 fn isSpvVector(cg: *CodeGen, ty: Type) bool { 639 const zcu = cg.module.zcu; 640 const target = cg.module.zcu.getTarget(); 641 if (ty.zigTypeTag(zcu) != .vector) return false; 642 643 // TODO: This check must be expanded for types that can be represented 644 // as integers (enums / packed structs?) and types that are represented 645 // by multiple SPIR-V values. 646 const scalar_ty = ty.scalarType(zcu); 647 switch (scalar_ty.zigTypeTag(zcu)) { 648 .bool, 649 .int, 650 .float, 651 => {}, 652 else => return false, 653 } 654 655 const elem_ty = ty.childType(zcu); 656 const len = ty.vectorLen(zcu); 657 658 if (elem_ty.isNumeric(zcu) or elem_ty.toIntern() == .bool_type) { 659 if (len > 1 and len <= 4) return true; 660 if (target.cpu.has(.spirv, .vector16)) return (len == 8 or len == 16); 661 } 662 663 return false; 664 } 665 666 /// Emits a bool constant in a particular representation. 667 fn constBool(cg: *CodeGen, value: bool, repr: Repr) !Id { 668 return switch (repr) { 669 .indirect => cg.constInt(.u1, @intFromBool(value)), 670 .direct => cg.module.constBool(value), 671 }; 672 } 673 674 /// Emits an integer constant. 675 /// This function, unlike Module.constInt, takes care to bitcast 676 /// the value to an unsigned int first for Kernels. 677 fn constInt(cg: *CodeGen, ty: Type, value: anytype) !Id { 678 const zcu = cg.module.zcu; 679 const target = cg.module.zcu.getTarget(); 680 const scalar_ty = ty.scalarType(zcu); 681 const int_info = scalar_ty.intInfo(zcu); 682 // Use backing bits so that negatives are sign extended 683 const backing_bits, const big_int = cg.module.backingIntBits(int_info.bits); 684 assert(backing_bits != 0); // u0 is comptime 685 686 const result_ty_id = try cg.resolveType(scalar_ty, .indirect); 687 const signedness: Signedness = switch (@typeInfo(@TypeOf(value))) { 688 .int => |int| int.signedness, 689 .comptime_int => if (value < 0) .signed else .unsigned, 690 else => unreachable, 691 }; 692 if (@sizeOf(@TypeOf(value)) >= 4 and big_int) { 693 const value64: u64 = switch (signedness) { 694 .signed => @bitCast(@as(i64, @intCast(value))), 695 .unsigned => @as(u64, @intCast(value)), 696 }; 697 assert(backing_bits == 64); 698 return cg.constructComposite(result_ty_id, &.{ 699 try cg.constInt(.u32, @as(u32, @truncate(value64))), 700 try cg.constInt(.u32, @as(u32, @truncate(value64 << 32))), 701 }); 702 } 703 704 const final_value: spec.LiteralContextDependentNumber = switch (target.os.tag) { 705 .opencl, .amdhsa => blk: { 706 const value64: u64 = switch (signedness) { 707 .signed => @bitCast(@as(i64, @intCast(value))), 708 .unsigned => @as(u64, @intCast(value)), 709 }; 710 711 // Manually truncate the value to the right amount of bits. 712 const truncated_value = if (backing_bits == 64) 713 value64 714 else 715 value64 & (@as(u64, 1) << @intCast(backing_bits)) - 1; 716 717 break :blk switch (backing_bits) { 718 1...32 => .{ .uint32 = @truncate(truncated_value) }, 719 33...64 => .{ .uint64 = truncated_value }, 720 else => unreachable, 721 }; 722 }, 723 else => switch (backing_bits) { 724 1...32 => if (signedness == .signed) .{ .int32 = @intCast(value) } else .{ .uint32 = @intCast(value) }, 725 33...64 => if (signedness == .signed) .{ .int64 = value } else .{ .uint64 = value }, 726 else => unreachable, 727 }, 728 }; 729 730 const result_id = try cg.module.constant(result_ty_id, final_value); 731 732 if (!ty.isVector(zcu)) return result_id; 733 return cg.constructCompositeSplat(ty, result_id); 734 } 735 736 pub fn constructComposite(cg: *CodeGen, result_ty_id: Id, constituents: []const Id) !Id { 737 const gpa = cg.module.gpa; 738 const result_id = cg.module.allocId(); 739 try cg.body.emit(gpa, .OpCompositeConstruct, .{ 740 .id_result_type = result_ty_id, 741 .id_result = result_id, 742 .constituents = constituents, 743 }); 744 return result_id; 745 } 746 747 /// Construct a composite at runtime with all lanes set to the same value. 748 /// ty must be an aggregate type. 749 fn constructCompositeSplat(cg: *CodeGen, ty: Type, constituent: Id) !Id { 750 const gpa = cg.module.gpa; 751 const zcu = cg.module.zcu; 752 const n: usize = @intCast(ty.arrayLen(zcu)); 753 754 const scratch_top = cg.id_scratch.items.len; 755 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); 756 757 const constituents = try cg.id_scratch.addManyAsSlice(gpa, n); 758 @memset(constituents, constituent); 759 760 const result_ty_id = try cg.resolveType(ty, .direct); 761 return cg.constructComposite(result_ty_id, constituents); 762 } 763 764 /// This function generates a load for a constant in direct (ie, non-memory) representation. 765 /// When the constant is simple, it can be generated directly using OpConstant instructions. 766 /// When the constant is more complicated however, it needs to be constructed using multiple values. This 767 /// is done by emitting a sequence of instructions that initialize the value. 768 // 769 /// This function should only be called during function code generation. 770 fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { 771 const gpa = cg.module.gpa; 772 773 // Note: Using intern_map can only be used with constants that DO NOT generate any runtime code!! 774 // Ideally that should be all constants in the future, or it should be cleaned up somehow. For 775 // now, only use the intern_map on case-by-case basis by breaking to :cache. 776 if (cg.module.intern_map.get(.{ val.toIntern(), repr })) |id| { 777 return id; 778 } 779 780 const pt = cg.pt; 781 const zcu = cg.module.zcu; 782 const target = cg.module.zcu.getTarget(); 783 const result_ty_id = try cg.resolveType(ty, repr); 784 const ip = &zcu.intern_pool; 785 786 log.debug("lowering constant: ty = {f}, val = {f}, key = {s}", .{ ty.fmt(pt), val.fmtValue(pt), @tagName(ip.indexToKey(val.toIntern())) }); 787 if (val.isUndef(zcu)) { 788 return cg.module.constUndef(result_ty_id); 789 } 790 791 const cacheable_id = cache: { 792 switch (ip.indexToKey(val.toIntern())) { 793 .int_type, 794 .ptr_type, 795 .array_type, 796 .vector_type, 797 .opt_type, 798 .anyframe_type, 799 .error_union_type, 800 .simple_type, 801 .struct_type, 802 .tuple_type, 803 .union_type, 804 .opaque_type, 805 .enum_type, 806 .func_type, 807 .error_set_type, 808 .inferred_error_set_type, 809 => unreachable, // types, not values 810 811 .undef => unreachable, // handled above 812 813 .variable, 814 .@"extern", 815 .func, 816 .enum_literal, 817 .empty_enum_value, 818 => unreachable, // non-runtime values 819 820 .simple_value => |simple_value| switch (simple_value) { 821 .undefined, 822 .void, 823 .null, 824 .empty_tuple, 825 .@"unreachable", 826 => unreachable, // non-runtime values 827 828 .false, .true => break :cache try cg.constBool(val.toBool(), repr), 829 }, 830 .int => { 831 if (ty.isSignedInt(zcu)) { 832 break :cache try cg.constInt(ty, val.toSignedInt(zcu)); 833 } else { 834 break :cache try cg.constInt(ty, val.toUnsignedInt(zcu)); 835 } 836 }, 837 .float => { 838 const lit: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) { 839 16 => .{ .uint32 = @as(u16, @bitCast(val.toFloat(f16, zcu))) }, 840 32 => .{ .float32 = val.toFloat(f32, zcu) }, 841 64 => .{ .float64 = val.toFloat(f64, zcu) }, 842 80, 128 => unreachable, // TODO 843 else => unreachable, 844 }; 845 break :cache try cg.module.constant(result_ty_id, lit); 846 }, 847 .err => |err| { 848 const value = try pt.getErrorValue(err.name); 849 break :cache try cg.constInt(ty, value); 850 }, 851 .error_union => |error_union| { 852 // TODO: Error unions may be constructed with constant instructions if the payload type 853 // allows it. For now, just generate it here regardless. 854 const err_ty = ty.errorUnionSet(zcu); 855 const payload_ty = ty.errorUnionPayload(zcu); 856 const err_val_id = switch (error_union.val) { 857 .err_name => |err_name| try cg.constInt( 858 err_ty, 859 try pt.getErrorValue(err_name), 860 ), 861 .payload => try cg.constInt(err_ty, 0), 862 }; 863 const eu_layout = cg.errorUnionLayout(payload_ty); 864 if (!eu_layout.payload_has_bits) { 865 // We use the error type directly as the type. 866 break :cache err_val_id; 867 } 868 869 const payload_val_id = switch (error_union.val) { 870 .err_name => try cg.constant(payload_ty, .undef, .indirect), 871 .payload => |p| try cg.constant(payload_ty, .fromInterned(p), .indirect), 872 }; 873 874 var constituents: [2]Id = undefined; 875 var types: [2]Type = undefined; 876 if (eu_layout.error_first) { 877 constituents[0] = err_val_id; 878 constituents[1] = payload_val_id; 879 types = .{ err_ty, payload_ty }; 880 } else { 881 constituents[0] = payload_val_id; 882 constituents[1] = err_val_id; 883 types = .{ payload_ty, err_ty }; 884 } 885 886 const comp_ty_id = try cg.resolveType(ty, .direct); 887 return try cg.constructComposite(comp_ty_id, &constituents); 888 }, 889 .enum_tag => { 890 const int_val = try val.intFromEnum(ty, pt); 891 const int_ty = ty.intTagType(zcu); 892 break :cache try cg.constant(int_ty, int_val, repr); 893 }, 894 .ptr => return cg.constantPtr(val), 895 .slice => |slice| { 896 const ptr_id = try cg.constantPtr(.fromInterned(slice.ptr)); 897 const len_id = try cg.constant(.usize, .fromInterned(slice.len), .indirect); 898 const comp_ty_id = try cg.resolveType(ty, .direct); 899 return try cg.constructComposite(comp_ty_id, &.{ ptr_id, len_id }); 900 }, 901 .opt => { 902 const payload_ty = ty.optionalChild(zcu); 903 const maybe_payload_val = val.optionalValue(zcu); 904 905 if (!payload_ty.hasRuntimeBits(zcu)) { 906 break :cache try cg.constBool(maybe_payload_val != null, .indirect); 907 } else if (ty.optionalReprIsPayload(zcu)) { 908 // Optional representation is a nullable pointer or slice. 909 if (maybe_payload_val) |payload_val| { 910 return try cg.constant(payload_ty, payload_val, .indirect); 911 } else { 912 break :cache try cg.module.constNull(result_ty_id); 913 } 914 } 915 916 // Optional representation is a structure. 917 // { Payload, Bool } 918 919 const has_pl_id = try cg.constBool(maybe_payload_val != null, .indirect); 920 const payload_id = if (maybe_payload_val) |payload_val| 921 try cg.constant(payload_ty, payload_val, .indirect) 922 else 923 try cg.module.constUndef(try cg.resolveType(payload_ty, .indirect)); 924 925 const comp_ty_id = try cg.resolveType(ty, .direct); 926 return try cg.constructComposite(comp_ty_id, &.{ payload_id, has_pl_id }); 927 }, 928 .aggregate => |aggregate| switch (ip.indexToKey(ty.ip_index)) { 929 inline .array_type, .vector_type => |array_type, tag| { 930 const elem_ty: Type = .fromInterned(array_type.child); 931 932 const scratch_top = cg.id_scratch.items.len; 933 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); 934 const constituents = try cg.id_scratch.addManyAsSlice(gpa, @intCast(ty.arrayLenIncludingSentinel(zcu))); 935 936 const child_repr: Repr = switch (tag) { 937 .array_type => .indirect, 938 .vector_type => .direct, 939 else => unreachable, 940 }; 941 942 switch (aggregate.storage) { 943 .bytes => |bytes| { 944 // TODO: This is really space inefficient, perhaps there is a better 945 // way to do it? 946 for (constituents, bytes.toSlice(constituents.len, ip)) |*constituent, byte| { 947 constituent.* = try cg.constInt(elem_ty, byte); 948 } 949 }, 950 .elems => |elems| { 951 for (constituents, elems) |*constituent, elem| { 952 constituent.* = try cg.constant(elem_ty, .fromInterned(elem), child_repr); 953 } 954 }, 955 .repeated_elem => |elem| { 956 @memset(constituents, try cg.constant(elem_ty, .fromInterned(elem), child_repr)); 957 }, 958 } 959 960 const comp_ty_id = try cg.resolveType(ty, .direct); 961 return cg.constructComposite(comp_ty_id, constituents); 962 }, 963 .struct_type => { 964 const struct_type = zcu.typeToStruct(ty).?; 965 966 if (struct_type.layout == .@"packed") { 967 // TODO: composite int 968 // TODO: endianness 969 const bits: u16 = @intCast(ty.bitSize(zcu)); 970 const bytes = std.mem.alignForward(u16, cg.module.backingIntBits(bits).@"0", 8) / 8; 971 var limbs: [8]u8 = undefined; 972 @memset(&limbs, 0); 973 val.writeToPackedMemory(ty, pt, limbs[0..bytes], 0) catch unreachable; 974 const backing_ty: Type = .fromInterned(struct_type.backingIntTypeUnordered(ip)); 975 return try cg.constInt(backing_ty, @as(u64, @bitCast(limbs))); 976 } 977 978 var types = std.array_list.Managed(Type).init(gpa); 979 defer types.deinit(); 980 981 var constituents = std.array_list.Managed(Id).init(gpa); 982 defer constituents.deinit(); 983 984 var it = struct_type.iterateRuntimeOrder(ip); 985 while (it.next()) |field_index| { 986 const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]); 987 if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) { 988 // This is a zero-bit field - we only needed it for the alignment. 989 continue; 990 } 991 992 // TODO: Padding? 993 const field_val = try val.fieldValue(pt, field_index); 994 const field_id = try cg.constant(field_ty, field_val, .indirect); 995 996 try types.append(field_ty); 997 try constituents.append(field_id); 998 } 999 1000 const comp_ty_id = try cg.resolveType(ty, .direct); 1001 return try cg.constructComposite(comp_ty_id, constituents.items); 1002 }, 1003 .tuple_type => return cg.todo("implement tuple types", .{}), 1004 else => unreachable, 1005 }, 1006 .un => |un| { 1007 if (un.tag == .none) { 1008 assert(ty.containerLayout(zcu) == .@"packed"); // TODO 1009 const int_ty = try pt.intType(.unsigned, @intCast(ty.bitSize(zcu))); 1010 return try cg.constInt(int_ty, Value.toUnsignedInt(.fromInterned(un.val), zcu)); 1011 } 1012 const active_field = ty.unionTagFieldIndex(.fromInterned(un.tag), zcu).?; 1013 const union_obj = zcu.typeToUnion(ty).?; 1014 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[active_field]); 1015 const payload = if (field_ty.hasRuntimeBitsIgnoreComptime(zcu)) 1016 try cg.constant(field_ty, .fromInterned(un.val), .direct) 1017 else 1018 null; 1019 return try cg.unionInit(ty, active_field, payload); 1020 }, 1021 .memoized_call => unreachable, 1022 } 1023 }; 1024 1025 try cg.module.intern_map.putNoClobber(gpa, .{ val.toIntern(), repr }, cacheable_id); 1026 1027 return cacheable_id; 1028 } 1029 1030 fn constantPtr(cg: *CodeGen, ptr_val: Value) !Id { 1031 const pt = cg.pt; 1032 const zcu = cg.module.zcu; 1033 const gpa = cg.module.gpa; 1034 1035 if (ptr_val.isUndef(zcu)) { 1036 const result_ty = ptr_val.typeOf(zcu); 1037 const result_ty_id = try cg.resolveType(result_ty, .direct); 1038 return cg.module.constUndef(result_ty_id); 1039 } 1040 1041 var arena = std.heap.ArenaAllocator.init(gpa); 1042 defer arena.deinit(); 1043 1044 const derivation = try ptr_val.pointerDerivation(arena.allocator(), pt); 1045 return cg.derivePtr(derivation); 1046 } 1047 1048 fn derivePtr(cg: *CodeGen, derivation: Value.PointerDeriveStep) !Id { 1049 const gpa = cg.module.gpa; 1050 const pt = cg.pt; 1051 const zcu = cg.module.zcu; 1052 const target = zcu.getTarget(); 1053 switch (derivation) { 1054 .comptime_alloc_ptr, .comptime_field_ptr => unreachable, 1055 .int => |int| { 1056 if (target.os.tag != .opencl) { 1057 if (int.ptr_ty.ptrAddressSpace(zcu) != .physical_storage_buffer) { 1058 return cg.fail( 1059 "cannot cast integer to pointer with address space '{s}'", 1060 .{@tagName(int.ptr_ty.ptrAddressSpace(zcu))}, 1061 ); 1062 } 1063 } 1064 const result_ty_id = try cg.resolveType(int.ptr_ty, .direct); 1065 // TODO: This can probably be an OpSpecConstantOp Bitcast, but 1066 // that is not implemented by Mesa yet. Therefore, just generate it 1067 // as a runtime operation. 1068 const result_ptr_id = cg.module.allocId(); 1069 const value_id = try cg.constInt(.usize, int.addr); 1070 try cg.body.emit(gpa, .OpConvertUToPtr, .{ 1071 .id_result_type = result_ty_id, 1072 .id_result = result_ptr_id, 1073 .integer_value = value_id, 1074 }); 1075 return result_ptr_id; 1076 }, 1077 .nav_ptr => |nav| { 1078 const result_ptr_ty = try pt.navPtrType(nav); 1079 return cg.constantNavRef(result_ptr_ty, nav); 1080 }, 1081 .uav_ptr => |uav| { 1082 const result_ptr_ty: Type = .fromInterned(uav.orig_ty); 1083 return cg.constantUavRef(result_ptr_ty, uav); 1084 }, 1085 .eu_payload_ptr => @panic("TODO"), 1086 .opt_payload_ptr => @panic("TODO"), 1087 .field_ptr => |field| { 1088 const parent_ptr_id = try cg.derivePtr(field.parent.*); 1089 const parent_ptr_ty = try field.parent.ptrType(pt); 1090 return cg.structFieldPtr(field.result_ptr_ty, parent_ptr_ty, parent_ptr_id, field.field_idx); 1091 }, 1092 .elem_ptr => |elem| { 1093 const parent_ptr_id = try cg.derivePtr(elem.parent.*); 1094 const parent_ptr_ty = try elem.parent.ptrType(pt); 1095 const index_id = try cg.constInt(.usize, elem.elem_idx); 1096 return cg.ptrElemPtr(parent_ptr_ty, parent_ptr_id, index_id); 1097 }, 1098 .offset_and_cast => |oac| { 1099 const parent_ptr_id = try cg.derivePtr(oac.parent.*); 1100 const parent_ptr_ty = try oac.parent.ptrType(pt); 1101 const result_ty_id = try cg.resolveType(oac.new_ptr_ty, .direct); 1102 const child_size = oac.new_ptr_ty.childType(zcu).abiSize(zcu); 1103 1104 if (parent_ptr_ty.childType(zcu).isVector(zcu) and oac.byte_offset % child_size == 0) { 1105 // Vector element ptr accesses are derived as offset_and_cast. 1106 // We can just use OpAccessChain. 1107 return cg.accessChain( 1108 result_ty_id, 1109 parent_ptr_id, 1110 &.{@intCast(@divExact(oac.byte_offset, child_size))}, 1111 ); 1112 } 1113 1114 if (oac.byte_offset == 0) { 1115 // Allow changing the pointer type child only to restructure arrays. 1116 // e.g. [3][2]T to T is fine, as is [2]T -> [2][1]T. 1117 const result_ptr_id = cg.module.allocId(); 1118 try cg.body.emit(gpa, .OpBitcast, .{ 1119 .id_result_type = result_ty_id, 1120 .id_result = result_ptr_id, 1121 .operand = parent_ptr_id, 1122 }); 1123 return result_ptr_id; 1124 } 1125 1126 return cg.fail("cannot perform pointer cast: '{f}' to '{f}'", .{ 1127 parent_ptr_ty.fmt(pt), 1128 oac.new_ptr_ty.fmt(pt), 1129 }); 1130 }, 1131 } 1132 } 1133 1134 fn constantUavRef( 1135 cg: *CodeGen, 1136 ty: Type, 1137 uav: InternPool.Key.Ptr.BaseAddr.Uav, 1138 ) !Id { 1139 // TODO: Merge this function with constantDeclRef. 1140 1141 const zcu = cg.module.zcu; 1142 const ip = &zcu.intern_pool; 1143 const ty_id = try cg.resolveType(ty, .direct); 1144 const uav_ty: Type = .fromInterned(ip.typeOf(uav.val)); 1145 1146 switch (ip.indexToKey(uav.val)) { 1147 .func => unreachable, // TODO 1148 .@"extern" => assert(!ip.isFunctionType(uav_ty.toIntern())), 1149 else => {}, 1150 } 1151 1152 // const is_fn_body = decl_ty.zigTypeTag(zcu) == .@"fn"; 1153 if (!uav_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) { 1154 // Pointer to nothing - return undefined 1155 return cg.module.constUndef(ty_id); 1156 } 1157 1158 // Uav refs are always generic. 1159 assert(ty.ptrAddressSpace(zcu) == .generic); 1160 const uav_ty_id = try cg.resolveType(uav_ty, .indirect); 1161 const decl_ptr_ty_id = try cg.module.ptrType(uav_ty_id, .function); 1162 const ptr_id = try cg.resolveUav(uav.val); 1163 1164 if (decl_ptr_ty_id != ty_id) { 1165 // Differing pointer types, insert a cast. 1166 const casted_ptr_id = cg.module.allocId(); 1167 try cg.body.emit(cg.module.gpa, .OpBitcast, .{ 1168 .id_result_type = ty_id, 1169 .id_result = casted_ptr_id, 1170 .operand = ptr_id, 1171 }); 1172 return casted_ptr_id; 1173 } else { 1174 return ptr_id; 1175 } 1176 } 1177 1178 fn constantNavRef(cg: *CodeGen, ty: Type, nav_index: InternPool.Nav.Index) !Id { 1179 const zcu = cg.module.zcu; 1180 const ip = &zcu.intern_pool; 1181 const ty_id = try cg.resolveType(ty, .direct); 1182 const nav = ip.getNav(nav_index); 1183 const nav_ty: Type = .fromInterned(nav.typeOf(ip)); 1184 1185 switch (nav.status) { 1186 .unresolved => unreachable, 1187 .type_resolved => {}, // this is not a function or extern 1188 .fully_resolved => |r| switch (ip.indexToKey(r.val)) { 1189 .func => { 1190 // TODO: Properly lower function pointers. For now we are going to hack around it and 1191 // just generate an empty pointer. Function pointers are represented by a pointer to usize. 1192 return try cg.module.constUndef(ty_id); 1193 }, 1194 .@"extern" => if (ip.isFunctionType(nav_ty.toIntern())) @panic("TODO"), 1195 else => {}, 1196 }, 1197 } 1198 1199 if (!nav_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) { 1200 // Pointer to nothing - return undefined. 1201 return cg.module.constUndef(ty_id); 1202 } 1203 1204 const spv_decl_index = try cg.module.resolveNav(ip, nav_index); 1205 const spv_decl = cg.module.declPtr(spv_decl_index); 1206 const spv_decl_result_id = spv_decl.result_id; 1207 assert(spv_decl.kind != .func); 1208 1209 const storage_class = cg.module.storageClass(nav.getAddrspace()); 1210 try cg.addFunctionDep(spv_decl_index, storage_class); 1211 1212 const nav_ty_id = try cg.resolveType(nav_ty, .indirect); 1213 const decl_ptr_ty_id = try cg.module.ptrType(nav_ty_id, storage_class); 1214 1215 if (decl_ptr_ty_id != ty_id) { 1216 // Differing pointer types, insert a cast. 1217 const casted_ptr_id = cg.module.allocId(); 1218 try cg.body.emit(cg.module.gpa, .OpBitcast, .{ 1219 .id_result_type = ty_id, 1220 .id_result = casted_ptr_id, 1221 .operand = spv_decl_result_id, 1222 }); 1223 return casted_ptr_id; 1224 } 1225 1226 return spv_decl_result_id; 1227 } 1228 1229 // Turn a Zig type's name into a cache reference. 1230 fn resolveTypeName(cg: *CodeGen, ty: Type) ![]const u8 { 1231 const gpa = cg.module.gpa; 1232 var aw: std.Io.Writer.Allocating = .init(gpa); 1233 defer aw.deinit(); 1234 ty.print(&aw.writer, cg.pt, null) catch |err| switch (err) { 1235 error.WriteFailed => return error.OutOfMemory, 1236 }; 1237 return try aw.toOwnedSlice(); 1238 } 1239 1240 /// Generate a union type. Union types are always generated with the 1241 /// most aligned field active. If the tag alignment is greater 1242 /// than that of the payload, a regular union (non-packed, with both tag and 1243 /// payload), will be generated as follows: 1244 /// struct { 1245 /// tag: TagType, 1246 /// payload: MostAlignedFieldType, 1247 /// payload_padding: [payload_size - @sizeOf(MostAlignedFieldType)]u8, 1248 /// padding: [padding_size]u8, 1249 /// } 1250 /// If the payload alignment is greater than that of the tag: 1251 /// struct { 1252 /// payload: MostAlignedFieldType, 1253 /// payload_padding: [payload_size - @sizeOf(MostAlignedFieldType)]u8, 1254 /// tag: TagType, 1255 /// padding: [padding_size]u8, 1256 /// } 1257 /// If any of the fields' size is 0, it will be omitted. 1258 fn resolveUnionType(cg: *CodeGen, ty: Type) !Id { 1259 const gpa = cg.module.gpa; 1260 const zcu = cg.module.zcu; 1261 const ip = &zcu.intern_pool; 1262 const union_obj = zcu.typeToUnion(ty).?; 1263 1264 if (union_obj.flagsUnordered(ip).layout == .@"packed") { 1265 return try cg.module.intType(.unsigned, @intCast(ty.bitSize(zcu))); 1266 } 1267 1268 const layout = cg.unionLayout(ty); 1269 if (!layout.has_payload) { 1270 // No payload, so represent this as just the tag type. 1271 return try cg.resolveType(.fromInterned(union_obj.enum_tag_ty), .indirect); 1272 } 1273 1274 var member_types: [4]Id = undefined; 1275 var member_names: [4][]const u8 = undefined; 1276 1277 const u8_ty_id = try cg.resolveType(.u8, .direct); 1278 1279 if (layout.tag_size != 0) { 1280 const tag_ty_id = try cg.resolveType(.fromInterned(union_obj.enum_tag_ty), .indirect); 1281 member_types[layout.tag_index] = tag_ty_id; 1282 member_names[layout.tag_index] = "(tag)"; 1283 } 1284 1285 if (layout.payload_size != 0) { 1286 const payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect); 1287 member_types[layout.payload_index] = payload_ty_id; 1288 member_names[layout.payload_index] = "(payload)"; 1289 } 1290 1291 if (layout.payload_padding_size != 0) { 1292 const len_id = try cg.constInt(.u32, layout.payload_padding_size); 1293 const payload_padding_ty_id = try cg.module.arrayType(len_id, u8_ty_id); 1294 member_types[layout.payload_padding_index] = payload_padding_ty_id; 1295 member_names[layout.payload_padding_index] = "(payload padding)"; 1296 } 1297 1298 if (layout.padding_size != 0) { 1299 const len_id = try cg.constInt(.u32, layout.padding_size); 1300 const padding_ty_id = try cg.module.arrayType(len_id, u8_ty_id); 1301 member_types[layout.padding_index] = padding_ty_id; 1302 member_names[layout.padding_index] = "(padding)"; 1303 } 1304 1305 const result_id = try cg.module.structType( 1306 member_types[0..layout.total_fields], 1307 member_names[0..layout.total_fields], 1308 null, 1309 .none, 1310 ); 1311 1312 const type_name = try cg.resolveTypeName(ty); 1313 defer gpa.free(type_name); 1314 try cg.module.debugName(result_id, type_name); 1315 1316 return result_id; 1317 } 1318 1319 fn resolveFnReturnType(cg: *CodeGen, ret_ty: Type) !Id { 1320 const zcu = cg.module.zcu; 1321 if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) { 1322 // If the return type is an error set or an error union, then we make this 1323 // anyerror return type instead, so that it can be coerced into a function 1324 // pointer type which has anyerror as the return type. 1325 if (ret_ty.isError(zcu)) { 1326 return cg.resolveType(.anyerror, .direct); 1327 } else { 1328 return cg.resolveType(.void, .direct); 1329 } 1330 } 1331 1332 return try cg.resolveType(ret_ty, .direct); 1333 } 1334 1335 fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { 1336 const gpa = cg.module.gpa; 1337 const pt = cg.pt; 1338 const zcu = cg.module.zcu; 1339 const ip = &zcu.intern_pool; 1340 const target = cg.module.zcu.getTarget(); 1341 1342 log.debug("resolveType: ty = {f}", .{ty.fmt(pt)}); 1343 1344 switch (ty.zigTypeTag(zcu)) { 1345 .noreturn => { 1346 assert(repr == .direct); 1347 return try cg.module.voidType(); 1348 }, 1349 .void => switch (repr) { 1350 .direct => return try cg.module.voidType(), 1351 .indirect => { 1352 if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); 1353 return try cg.module.opaqueType("void"); 1354 }, 1355 }, 1356 .bool => switch (repr) { 1357 .direct => return try cg.module.boolType(), 1358 .indirect => return try cg.resolveType(.u1, .indirect), 1359 }, 1360 .int => { 1361 const int_info = ty.intInfo(zcu); 1362 if (int_info.bits == 0) { 1363 assert(repr == .indirect); 1364 if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); 1365 return try cg.module.opaqueType("u0"); 1366 } 1367 return try cg.module.intType(int_info.signedness, int_info.bits); 1368 }, 1369 .@"enum" => return try cg.resolveType(ty.intTagType(zcu), repr), 1370 .float => { 1371 const bits = ty.floatBits(target); 1372 const supported = switch (bits) { 1373 16 => target.cpu.has(.spirv, .float16), 1374 32 => true, 1375 64 => target.cpu.has(.spirv, .float64), 1376 else => false, 1377 }; 1378 1379 if (!supported) { 1380 return cg.fail( 1381 "floating point width of {} bits is not supported for the current SPIR-V feature set", 1382 .{bits}, 1383 ); 1384 } 1385 1386 return try cg.module.floatType(bits); 1387 }, 1388 .array => { 1389 const elem_ty = ty.childType(zcu); 1390 const elem_ty_id = try cg.resolveType(elem_ty, .indirect); 1391 const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel(zcu)) orelse { 1392 return cg.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel(zcu)}); 1393 }; 1394 1395 if (!elem_ty.hasRuntimeBitsIgnoreComptime(zcu)) { 1396 assert(repr == .indirect); 1397 if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); 1398 return try cg.module.opaqueType("zero-sized-array"); 1399 } else if (total_len == 0) { 1400 // The size of the array would be 0, but that is not allowed in SPIR-V. 1401 // This path can be reached for example when there is a slicing of a pointer 1402 // that produces a zero-length array. In all cases where this type can be generated, 1403 // this should be an indirect path. 1404 assert(repr == .indirect); 1405 // In this case, we have an array of a non-zero sized type. In this case, 1406 // generate an array of 1 element instead, so that ptr_elem_ptr instructions 1407 // can be lowered to ptrAccessChain instead of manually performing the math. 1408 const len_id = try cg.constInt(.u32, 1); 1409 return try cg.module.arrayType(len_id, elem_ty_id); 1410 } else { 1411 const total_len_id = try cg.constInt(.u32, total_len); 1412 const result_id = try cg.module.arrayType(total_len_id, elem_ty_id); 1413 switch (target.os.tag) { 1414 .vulkan, .opengl => { 1415 try cg.module.decorate(result_id, .{ 1416 .array_stride = .{ 1417 .array_stride = @intCast(elem_ty.abiSize(zcu)), 1418 }, 1419 }); 1420 }, 1421 else => {}, 1422 } 1423 return result_id; 1424 } 1425 }, 1426 .vector => { 1427 const elem_ty = ty.childType(zcu); 1428 const elem_ty_id = try cg.resolveType(elem_ty, repr); 1429 const len = ty.vectorLen(zcu); 1430 if (cg.isSpvVector(ty)) return try cg.module.vectorType(len, elem_ty_id); 1431 const len_id = try cg.constInt(.u32, len); 1432 return try cg.module.arrayType(len_id, elem_ty_id); 1433 }, 1434 .@"fn" => switch (repr) { 1435 .direct => { 1436 const fn_info = zcu.typeToFunc(ty).?; 1437 1438 comptime assert(zig_call_abi_ver == 3); 1439 assert(!fn_info.is_var_args); 1440 switch (fn_info.cc) { 1441 .auto, 1442 .spirv_kernel, 1443 .spirv_fragment, 1444 .spirv_vertex, 1445 .spirv_device, 1446 => {}, 1447 else => unreachable, 1448 } 1449 1450 const return_ty_id = try cg.resolveFnReturnType(.fromInterned(fn_info.return_type)); 1451 1452 const scratch_top = cg.id_scratch.items.len; 1453 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); 1454 const param_ty_ids = try cg.id_scratch.addManyAsSlice(gpa, fn_info.param_types.len); 1455 1456 var param_index: usize = 0; 1457 for (fn_info.param_types.get(ip)) |param_ty_index| { 1458 const param_ty: Type = .fromInterned(param_ty_index); 1459 if (!param_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue; 1460 1461 param_ty_ids[param_index] = try cg.resolveType(param_ty, .direct); 1462 param_index += 1; 1463 } 1464 1465 return try cg.module.functionType(return_ty_id, param_ty_ids[0..param_index]); 1466 }, 1467 .indirect => { 1468 // TODO: Represent function pointers properly. 1469 // For now, just use an usize type. 1470 return try cg.resolveType(.usize, .indirect); 1471 }, 1472 }, 1473 .pointer => { 1474 const ptr_info = ty.ptrInfo(zcu); 1475 1476 const child_ty: Type = .fromInterned(ptr_info.child); 1477 const child_ty_id = try cg.resolveType(child_ty, .indirect); 1478 const storage_class = cg.module.storageClass(ptr_info.flags.address_space); 1479 const ptr_ty_id = try cg.module.ptrType(child_ty_id, storage_class); 1480 1481 if (ptr_info.flags.size != .slice) { 1482 return ptr_ty_id; 1483 } 1484 1485 const size_ty_id = try cg.resolveType(.usize, .direct); 1486 return try cg.module.structType( 1487 &.{ ptr_ty_id, size_ty_id }, 1488 &.{ "ptr", "len" }, 1489 null, 1490 .none, 1491 ); 1492 }, 1493 .@"struct" => { 1494 const struct_type = switch (ip.indexToKey(ty.toIntern())) { 1495 .tuple_type => |tuple| { 1496 const scratch_top = cg.id_scratch.items.len; 1497 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); 1498 const member_types = try cg.id_scratch.addManyAsSlice(gpa, tuple.values.len); 1499 1500 var member_index: usize = 0; 1501 for (tuple.types.get(ip), tuple.values.get(ip)) |field_ty, field_val| { 1502 if (field_val != .none or !Type.fromInterned(field_ty).hasRuntimeBits(zcu)) continue; 1503 1504 member_types[member_index] = try cg.resolveType(.fromInterned(field_ty), .indirect); 1505 member_index += 1; 1506 } 1507 1508 const result_id = try cg.module.structType( 1509 member_types[0..member_index], 1510 null, 1511 null, 1512 .none, 1513 ); 1514 const type_name = try cg.resolveTypeName(ty); 1515 defer gpa.free(type_name); 1516 try cg.module.debugName(result_id, type_name); 1517 return result_id; 1518 }, 1519 .struct_type => ip.loadStructType(ty.toIntern()), 1520 else => unreachable, 1521 }; 1522 1523 if (struct_type.layout == .@"packed") { 1524 return try cg.resolveType(.fromInterned(struct_type.backingIntTypeUnordered(ip)), .direct); 1525 } 1526 1527 var member_types = std.array_list.Managed(Id).init(gpa); 1528 defer member_types.deinit(); 1529 1530 var member_names = std.array_list.Managed([]const u8).init(gpa); 1531 defer member_names.deinit(); 1532 1533 var member_offsets = std.array_list.Managed(u32).init(gpa); 1534 defer member_offsets.deinit(); 1535 1536 var it = struct_type.iterateRuntimeOrder(ip); 1537 while (it.next()) |field_index| { 1538 const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]); 1539 if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue; 1540 1541 const field_name = struct_type.fieldName(ip, field_index); 1542 try member_types.append(try cg.resolveType(field_ty, .indirect)); 1543 try member_names.append(field_name.toSlice(ip)); 1544 try member_offsets.append(@intCast(ty.structFieldOffset(field_index, zcu))); 1545 } 1546 1547 const result_id = try cg.module.structType( 1548 member_types.items, 1549 member_names.items, 1550 member_offsets.items, 1551 ty.toIntern(), 1552 ); 1553 1554 const type_name = try cg.resolveTypeName(ty); 1555 defer gpa.free(type_name); 1556 try cg.module.debugName(result_id, type_name); 1557 1558 return result_id; 1559 }, 1560 .optional => { 1561 const payload_ty = ty.optionalChild(zcu); 1562 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) { 1563 // Just use a bool. 1564 // Note: Always generate the bool with indirect format, to save on some sanity 1565 // Perform the conversion to a direct bool when the field is extracted. 1566 return try cg.resolveType(.bool, .indirect); 1567 } 1568 1569 const payload_ty_id = try cg.resolveType(payload_ty, .indirect); 1570 if (ty.optionalReprIsPayload(zcu)) { 1571 // Optional is actually a pointer or a slice. 1572 return payload_ty_id; 1573 } 1574 1575 const bool_ty_id = try cg.resolveType(.bool, .indirect); 1576 1577 return try cg.module.structType( 1578 &.{ payload_ty_id, bool_ty_id }, 1579 &.{ "payload", "valid" }, 1580 null, 1581 .none, 1582 ); 1583 }, 1584 .@"union" => return try cg.resolveUnionType(ty), 1585 .error_set => { 1586 const err_int_ty = try pt.errorIntType(); 1587 return try cg.resolveType(err_int_ty, repr); 1588 }, 1589 .error_union => { 1590 const payload_ty = ty.errorUnionPayload(zcu); 1591 const err_ty = ty.errorUnionSet(zcu); 1592 const error_ty_id = try cg.resolveType(err_ty, .indirect); 1593 1594 const eu_layout = cg.errorUnionLayout(payload_ty); 1595 if (!eu_layout.payload_has_bits) { 1596 return error_ty_id; 1597 } 1598 1599 const payload_ty_id = try cg.resolveType(payload_ty, .indirect); 1600 1601 var member_types: [2]Id = undefined; 1602 var member_names: [2][]const u8 = undefined; 1603 if (eu_layout.error_first) { 1604 // Put the error first 1605 member_types = .{ error_ty_id, payload_ty_id }; 1606 member_names = .{ "error", "payload" }; 1607 // TODO: ABI padding? 1608 } else { 1609 // Put the payload first. 1610 member_types = .{ payload_ty_id, error_ty_id }; 1611 member_names = .{ "payload", "error" }; 1612 // TODO: ABI padding? 1613 } 1614 1615 return try cg.module.structType(&member_types, &member_names, null, .none); 1616 }, 1617 .@"opaque" => { 1618 if (target.os.tag != .opencl) return cg.fail("cannot generate opaque type", .{}); 1619 const type_name = try cg.resolveTypeName(ty); 1620 defer gpa.free(type_name); 1621 return try cg.module.opaqueType(type_name); 1622 }, 1623 1624 .null, 1625 .undefined, 1626 .enum_literal, 1627 .comptime_float, 1628 .comptime_int, 1629 .type, 1630 => unreachable, // Must be comptime. 1631 1632 .frame, .@"anyframe" => unreachable, // TODO 1633 } 1634 } 1635 1636 const ErrorUnionLayout = struct { 1637 payload_has_bits: bool, 1638 error_first: bool, 1639 1640 fn errorFieldIndex(cg: @This()) u32 { 1641 assert(cg.payload_has_bits); 1642 return if (cg.error_first) 0 else 1; 1643 } 1644 1645 fn payloadFieldIndex(cg: @This()) u32 { 1646 assert(cg.payload_has_bits); 1647 return if (cg.error_first) 1 else 0; 1648 } 1649 }; 1650 1651 fn errorUnionLayout(cg: *CodeGen, payload_ty: Type) ErrorUnionLayout { 1652 const zcu = cg.module.zcu; 1653 1654 const error_align = Type.abiAlignment(.anyerror, zcu); 1655 const payload_align = payload_ty.abiAlignment(zcu); 1656 1657 const error_first = error_align.compare(.gt, payload_align); 1658 return .{ 1659 .payload_has_bits = payload_ty.hasRuntimeBitsIgnoreComptime(zcu), 1660 .error_first = error_first, 1661 }; 1662 } 1663 1664 const UnionLayout = struct { 1665 /// If false, this union is represented 1666 /// by only an integer of the tag type. 1667 has_payload: bool, 1668 tag_size: u32, 1669 tag_index: u32, 1670 /// Note: This is the size of the payload type itcg, NOT the size of the ENTIRE payload. 1671 /// Use `has_payload` instead!! 1672 payload_ty: Type, 1673 payload_size: u32, 1674 payload_index: u32, 1675 payload_padding_size: u32, 1676 payload_padding_index: u32, 1677 padding_size: u32, 1678 padding_index: u32, 1679 total_fields: u32, 1680 }; 1681 1682 fn unionLayout(cg: *CodeGen, ty: Type) UnionLayout { 1683 const zcu = cg.module.zcu; 1684 const ip = &zcu.intern_pool; 1685 const layout = ty.unionGetLayout(zcu); 1686 const union_obj = zcu.typeToUnion(ty).?; 1687 1688 var union_layout: UnionLayout = .{ 1689 .has_payload = layout.payload_size != 0, 1690 .tag_size = @intCast(layout.tag_size), 1691 .tag_index = undefined, 1692 .payload_ty = undefined, 1693 .payload_size = undefined, 1694 .payload_index = undefined, 1695 .payload_padding_size = undefined, 1696 .payload_padding_index = undefined, 1697 .padding_size = @intCast(layout.padding), 1698 .padding_index = undefined, 1699 .total_fields = undefined, 1700 }; 1701 1702 if (union_layout.has_payload) { 1703 const most_aligned_field = layout.most_aligned_field; 1704 const most_aligned_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[most_aligned_field]); 1705 union_layout.payload_ty = most_aligned_field_ty; 1706 union_layout.payload_size = @intCast(most_aligned_field_ty.abiSize(zcu)); 1707 } else { 1708 union_layout.payload_size = 0; 1709 } 1710 1711 union_layout.payload_padding_size = @intCast(layout.payload_size - union_layout.payload_size); 1712 1713 const tag_first = layout.tag_align.compare(.gte, layout.payload_align); 1714 var field_index: u32 = 0; 1715 1716 if (union_layout.tag_size != 0 and tag_first) { 1717 union_layout.tag_index = field_index; 1718 field_index += 1; 1719 } 1720 1721 if (union_layout.payload_size != 0) { 1722 union_layout.payload_index = field_index; 1723 field_index += 1; 1724 } 1725 1726 if (union_layout.payload_padding_size != 0) { 1727 union_layout.payload_padding_index = field_index; 1728 field_index += 1; 1729 } 1730 1731 if (union_layout.tag_size != 0 and !tag_first) { 1732 union_layout.tag_index = field_index; 1733 field_index += 1; 1734 } 1735 1736 if (union_layout.padding_size != 0) { 1737 union_layout.padding_index = field_index; 1738 field_index += 1; 1739 } 1740 1741 union_layout.total_fields = field_index; 1742 1743 return union_layout; 1744 } 1745 1746 /// This structure represents a "temporary" value: Something we are currently 1747 /// operating on. It typically lives no longer than the function that 1748 /// implements a particular AIR operation. These are used to easier 1749 /// implement vectorizable operations (see Vectorization and the build* 1750 /// functions), and typically are only used for vectors of primitive types. 1751 const Temporary = struct { 1752 /// The type of the temporary. This is here mainly 1753 /// for easier bookkeeping. Because we will never really 1754 /// store Temporaries, they only cause extra stack space, 1755 /// therefore no real storage is wasted. 1756 ty: Type, 1757 /// The value that this temporary holds. This is not necessarily 1758 /// a value that is actually usable, or a single value: It is virtual 1759 /// until materialize() is called, at which point is turned into 1760 /// the usual SPIR-V representation of `cg.ty`. 1761 value: Temporary.Value, 1762 1763 const Value = union(enum) { 1764 singleton: Id, 1765 exploded_vector: IdRange, 1766 }; 1767 1768 fn init(ty: Type, singleton: Id) Temporary { 1769 return .{ .ty = ty, .value = .{ .singleton = singleton } }; 1770 } 1771 1772 fn materialize(temp: Temporary, cg: *CodeGen) !Id { 1773 const gpa = cg.module.gpa; 1774 const zcu = cg.module.zcu; 1775 switch (temp.value) { 1776 .singleton => |id| return id, 1777 .exploded_vector => |range| { 1778 assert(temp.ty.isVector(zcu)); 1779 assert(temp.ty.vectorLen(zcu) == range.len); 1780 1781 const scratch_top = cg.id_scratch.items.len; 1782 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); 1783 const constituents = try cg.id_scratch.addManyAsSlice(gpa, range.len); 1784 for (constituents, 0..range.len) |*id, i| { 1785 id.* = range.at(i); 1786 } 1787 1788 const result_ty_id = try cg.resolveType(temp.ty, .direct); 1789 return cg.constructComposite(result_ty_id, constituents); 1790 }, 1791 } 1792 } 1793 1794 fn vectorization(temp: Temporary, cg: *CodeGen) Vectorization { 1795 return .fromType(temp.ty, cg); 1796 } 1797 1798 fn pun(temp: Temporary, new_ty: Type) Temporary { 1799 return .{ 1800 .ty = new_ty, 1801 .value = temp.value, 1802 }; 1803 } 1804 1805 /// 'Explode' a temporary into separate elements. This turns a vector 1806 /// into a bag of elements. 1807 fn explode(temp: Temporary, cg: *CodeGen) !IdRange { 1808 const zcu = cg.module.zcu; 1809 1810 // If the value is a scalar, then this is a no-op. 1811 if (!temp.ty.isVector(zcu)) { 1812 return switch (temp.value) { 1813 .singleton => |id| .{ .base = @intFromEnum(id), .len = 1 }, 1814 .exploded_vector => |range| range, 1815 }; 1816 } 1817 1818 const ty_id = try cg.resolveType(temp.ty.scalarType(zcu), .direct); 1819 const n = temp.ty.vectorLen(zcu); 1820 const results = cg.module.allocIds(n); 1821 1822 const id = switch (temp.value) { 1823 .singleton => |id| id, 1824 .exploded_vector => |range| return range, 1825 }; 1826 1827 for (0..n) |i| { 1828 const indexes = [_]u32{@intCast(i)}; 1829 try cg.body.emit(cg.module.gpa, .OpCompositeExtract, .{ 1830 .id_result_type = ty_id, 1831 .id_result = results.at(i), 1832 .composite = id, 1833 .indexes = &indexes, 1834 }); 1835 } 1836 1837 return results; 1838 } 1839 }; 1840 1841 /// Initialize a `Temporary` from an AIR value. 1842 fn temporary(cg: *CodeGen, inst: Air.Inst.Ref) !Temporary { 1843 return .{ 1844 .ty = cg.typeOf(inst), 1845 .value = .{ .singleton = try cg.resolve(inst) }, 1846 }; 1847 } 1848 1849 /// This union describes how a particular operation should be vectorized. 1850 /// That depends on the operation and number of components of the inputs. 1851 const Vectorization = union(enum) { 1852 /// This is an operation between scalars. 1853 scalar, 1854 /// This operation is unrolled into separate operations. 1855 /// Inputs may still be SPIR-V vectors, for example, 1856 /// when the operation can't be vectorized in SPIR-V. 1857 /// Value is number of components. 1858 unrolled: u32, 1859 1860 /// Derive a vectorization from a particular type 1861 fn fromType(ty: Type, cg: *CodeGen) Vectorization { 1862 const zcu = cg.module.zcu; 1863 if (!ty.isVector(zcu)) return .scalar; 1864 return .{ .unrolled = ty.vectorLen(zcu) }; 1865 } 1866 1867 /// Given two vectorization methods, compute a "unification": a fallback 1868 /// that works for both, according to the following rules: 1869 /// - Scalars may broadcast 1870 /// - SPIR-V vectorized operations will unroll 1871 /// - Prefer scalar > unrolled 1872 fn unify(a: Vectorization, b: Vectorization) Vectorization { 1873 if (a == .scalar and b == .scalar) return .scalar; 1874 if (a == .unrolled or b == .unrolled) { 1875 if (a == .unrolled and b == .unrolled) assert(a.components() == b.components()); 1876 if (a == .unrolled) return .{ .unrolled = a.components() }; 1877 return .{ .unrolled = b.components() }; 1878 } 1879 unreachable; 1880 } 1881 1882 /// Query the number of components that inputs of this operation have. 1883 /// Note: for broadcasting scalars, this returns the number of elements 1884 /// that the broadcasted vector would have. 1885 fn components(vec: Vectorization) u32 { 1886 return switch (vec) { 1887 .scalar => 1, 1888 .unrolled => |n| n, 1889 }; 1890 } 1891 1892 /// Turns `ty` into the result-type of the entire operation. 1893 /// `ty` may be a scalar or vector, it doesn't matter. 1894 fn resultType(vec: Vectorization, cg: *CodeGen, ty: Type) !Type { 1895 const pt = cg.pt; 1896 const zcu = cg.module.zcu; 1897 const scalar_ty = ty.scalarType(zcu); 1898 return switch (vec) { 1899 .scalar => scalar_ty, 1900 .unrolled => |n| try pt.vectorType(.{ .len = n, .child = scalar_ty.toIntern() }), 1901 }; 1902 } 1903 1904 /// Before a temporary can be used, some setup may need to be one. This function implements 1905 /// this setup, and returns a new type that holds the relevant information on how to access 1906 /// elements of the input. 1907 fn prepare(vec: Vectorization, cg: *CodeGen, tmp: Temporary) !PreparedOperand { 1908 const zcu = cg.module.zcu; 1909 const is_vector = tmp.ty.isVector(zcu); 1910 const value: PreparedOperand.Value = switch (tmp.value) { 1911 .singleton => |id| switch (vec) { 1912 .scalar => blk: { 1913 assert(!is_vector); 1914 break :blk .{ .scalar = id }; 1915 }, 1916 .unrolled => blk: { 1917 if (is_vector) break :blk .{ .vector_exploded = try tmp.explode(cg) }; 1918 break :blk .{ .scalar_broadcast = id }; 1919 }, 1920 }, 1921 .exploded_vector => |range| switch (vec) { 1922 .scalar => unreachable, 1923 .unrolled => |n| blk: { 1924 assert(range.len == n); 1925 break :blk .{ .vector_exploded = range }; 1926 }, 1927 }, 1928 }; 1929 1930 return .{ 1931 .ty = tmp.ty, 1932 .value = value, 1933 }; 1934 } 1935 1936 /// Finalize the results of an operation back into a temporary. `results` is 1937 /// a list of result-ids of the operation. 1938 fn finalize(vec: Vectorization, ty: Type, results: IdRange) Temporary { 1939 assert(vec.components() == results.len); 1940 return .{ 1941 .ty = ty, 1942 .value = switch (vec) { 1943 .scalar => .{ .singleton = results.at(0) }, 1944 .unrolled => .{ .exploded_vector = results }, 1945 }, 1946 }; 1947 } 1948 1949 /// This struct represents an operand that has gone through some setup, and is 1950 /// ready to be used as part of an operation. 1951 const PreparedOperand = struct { 1952 ty: Type, 1953 value: PreparedOperand.Value, 1954 1955 /// The types of value that a prepared operand can hold internally. Depends 1956 /// on the operation and input value. 1957 const Value = union(enum) { 1958 /// A single scalar value that is used by a scalar operation. 1959 scalar: Id, 1960 /// A single scalar that is broadcasted in an unrolled operation. 1961 scalar_broadcast: Id, 1962 /// A vector represented by a consecutive list of IDs that is used in an unrolled operation. 1963 vector_exploded: IdRange, 1964 }; 1965 1966 /// Query the value at a particular index of the operation. Note that 1967 /// the index is *not* the component/lane, but the index of the *operation*. 1968 fn at(op: PreparedOperand, i: usize) Id { 1969 switch (op.value) { 1970 .scalar => |id| { 1971 assert(i == 0); 1972 return id; 1973 }, 1974 .scalar_broadcast => |id| return id, 1975 .vector_exploded => |range| return range.at(i), 1976 } 1977 } 1978 }; 1979 }; 1980 1981 /// A utility function to compute the vectorization style of 1982 /// a list of values. These values may be any of the following: 1983 /// - A `Vectorization` instance 1984 /// - A Type, in which case the vectorization is computed via `Vectorization.fromType`. 1985 /// - A Temporary, in which case the vectorization is computed via `Temporary.vectorization`. 1986 fn vectorization(cg: *CodeGen, args: anytype) Vectorization { 1987 var v: Vectorization = undefined; 1988 assert(args.len >= 1); 1989 inline for (args, 0..) |arg, i| { 1990 const iv: Vectorization = switch (@TypeOf(arg)) { 1991 Vectorization => arg, 1992 Type => Vectorization.fromType(arg, cg), 1993 Temporary => arg.vectorization(cg), 1994 else => @compileError("invalid type"), 1995 }; 1996 if (i == 0) { 1997 v = iv; 1998 } else { 1999 v = v.unify(iv); 2000 } 2001 } 2002 return v; 2003 } 2004 2005 /// This function builds an OpSConvert of OpUConvert depending on the 2006 /// signedness of the types. 2007 fn buildConvert(cg: *CodeGen, dst_ty: Type, src: Temporary) !Temporary { 2008 const zcu = cg.module.zcu; 2009 2010 const dst_ty_id = try cg.resolveType(dst_ty.scalarType(zcu), .direct); 2011 const src_ty_id = try cg.resolveType(src.ty.scalarType(zcu), .direct); 2012 2013 const v = cg.vectorization(.{ dst_ty, src }); 2014 const result_ty = try v.resultType(cg, dst_ty); 2015 2016 // We can directly compare integers, because those type-IDs are cached. 2017 if (dst_ty_id == src_ty_id) { 2018 // Nothing to do, type-pun to the right value. 2019 // Note, Caller guarantees that the types fit (or caller will normalize after), 2020 // so we don't have to normalize here. 2021 // Note, dst_ty may be a scalar type even if we expect a vector, so we have to 2022 // convert to the right type here. 2023 return src.pun(result_ty); 2024 } 2025 2026 const ops = v.components(); 2027 const results = cg.module.allocIds(ops); 2028 2029 const op_result_ty = dst_ty.scalarType(zcu); 2030 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); 2031 2032 const opcode: Opcode = blk: { 2033 if (dst_ty.scalarType(zcu).isAnyFloat()) break :blk .OpFConvert; 2034 if (dst_ty.scalarType(zcu).isSignedInt(zcu)) break :blk .OpSConvert; 2035 break :blk .OpUConvert; 2036 }; 2037 2038 const op_src = try v.prepare(cg, src); 2039 2040 for (0..ops) |i| { 2041 try cg.body.emitRaw(cg.module.gpa, opcode, 3); 2042 cg.body.writeOperand(Id, op_result_ty_id); 2043 cg.body.writeOperand(Id, results.at(i)); 2044 cg.body.writeOperand(Id, op_src.at(i)); 2045 } 2046 2047 return v.finalize(result_ty, results); 2048 } 2049 2050 fn buildFma(cg: *CodeGen, a: Temporary, b: Temporary, c: Temporary) !Temporary { 2051 const zcu = cg.module.zcu; 2052 const target = cg.module.zcu.getTarget(); 2053 2054 const v = cg.vectorization(.{ a, b, c }); 2055 const ops = v.components(); 2056 const results = cg.module.allocIds(ops); 2057 2058 const op_result_ty = a.ty.scalarType(zcu); 2059 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); 2060 const result_ty = try v.resultType(cg, a.ty); 2061 2062 const op_a = try v.prepare(cg, a); 2063 const op_b = try v.prepare(cg, b); 2064 const op_c = try v.prepare(cg, c); 2065 2066 const set = try cg.importExtendedSet(); 2067 const opcode: u32 = switch (target.os.tag) { 2068 .opencl => @intFromEnum(spec.OpenClOpcode.fma), 2069 // NOTE: Vulkan's FMA instruction does *NOT* produce the right values! 2070 // its precision guarantees do NOT match zigs and it does NOT match OpenCLs! 2071 // it needs to be emulated! 2072 .vulkan, .opengl => @intFromEnum(spec.GlslOpcode.Fma), 2073 else => unreachable, 2074 }; 2075 2076 for (0..ops) |i| { 2077 try cg.body.emit(cg.module.gpa, .OpExtInst, .{ 2078 .id_result_type = op_result_ty_id, 2079 .id_result = results.at(i), 2080 .set = set, 2081 .instruction = .{ .inst = opcode }, 2082 .id_ref_4 = &.{ op_a.at(i), op_b.at(i), op_c.at(i) }, 2083 }); 2084 } 2085 2086 return v.finalize(result_ty, results); 2087 } 2088 2089 fn buildSelect(cg: *CodeGen, condition: Temporary, lhs: Temporary, rhs: Temporary) !Temporary { 2090 const zcu = cg.module.zcu; 2091 2092 const v = cg.vectorization(.{ condition, lhs, rhs }); 2093 const ops = v.components(); 2094 const results = cg.module.allocIds(ops); 2095 2096 const op_result_ty = lhs.ty.scalarType(zcu); 2097 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); 2098 const result_ty = try v.resultType(cg, lhs.ty); 2099 2100 assert(condition.ty.scalarType(zcu).zigTypeTag(zcu) == .bool); 2101 2102 const cond = try v.prepare(cg, condition); 2103 const object_1 = try v.prepare(cg, lhs); 2104 const object_2 = try v.prepare(cg, rhs); 2105 2106 for (0..ops) |i| { 2107 try cg.body.emit(cg.module.gpa, .OpSelect, .{ 2108 .id_result_type = op_result_ty_id, 2109 .id_result = results.at(i), 2110 .condition = cond.at(i), 2111 .object_1 = object_1.at(i), 2112 .object_2 = object_2.at(i), 2113 }); 2114 } 2115 2116 return v.finalize(result_ty, results); 2117 } 2118 2119 fn buildCmp(cg: *CodeGen, opcode: Opcode, lhs: Temporary, rhs: Temporary) !Temporary { 2120 const v = cg.vectorization(.{ lhs, rhs }); 2121 const ops = v.components(); 2122 const results = cg.module.allocIds(ops); 2123 2124 const op_result_ty: Type = .bool; 2125 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); 2126 const result_ty = try v.resultType(cg, Type.bool); 2127 2128 const op_lhs = try v.prepare(cg, lhs); 2129 const op_rhs = try v.prepare(cg, rhs); 2130 2131 for (0..ops) |i| { 2132 try cg.body.emitRaw(cg.module.gpa, opcode, 4); 2133 cg.body.writeOperand(Id, op_result_ty_id); 2134 cg.body.writeOperand(Id, results.at(i)); 2135 cg.body.writeOperand(Id, op_lhs.at(i)); 2136 cg.body.writeOperand(Id, op_rhs.at(i)); 2137 } 2138 2139 return v.finalize(result_ty, results); 2140 } 2141 2142 const UnaryOp = enum { 2143 l_not, 2144 bit_not, 2145 i_neg, 2146 f_neg, 2147 i_abs, 2148 f_abs, 2149 clz, 2150 ctz, 2151 floor, 2152 ceil, 2153 trunc, 2154 round, 2155 sqrt, 2156 sin, 2157 cos, 2158 tan, 2159 exp, 2160 exp2, 2161 log, 2162 log2, 2163 log10, 2164 2165 pub fn extInstOpcode(op: UnaryOp, target: *const std.Target) ?u32 { 2166 return switch (target.os.tag) { 2167 .opencl => @intFromEnum(@as(spec.OpenClOpcode, switch (op) { 2168 .i_abs => .s_abs, 2169 .f_abs => .fabs, 2170 .clz => .clz, 2171 .ctz => .ctz, 2172 .floor => .floor, 2173 .ceil => .ceil, 2174 .trunc => .trunc, 2175 .round => .round, 2176 .sqrt => .sqrt, 2177 .sin => .sin, 2178 .cos => .cos, 2179 .tan => .tan, 2180 .exp => .exp, 2181 .exp2 => .exp2, 2182 .log => .log, 2183 .log2 => .log2, 2184 .log10 => .log10, 2185 else => return null, 2186 })), 2187 // Note: We'll need to check these for floating point accuracy 2188 // Vulkan does not put tight requirements on these, for correction 2189 // we might want to emulate them at some point. 2190 .vulkan, .opengl => @intFromEnum(@as(spec.GlslOpcode, switch (op) { 2191 .i_abs => .SAbs, 2192 .f_abs => .FAbs, 2193 .floor => .Floor, 2194 .ceil => .Ceil, 2195 .trunc => .Trunc, 2196 .round => .Round, 2197 .sin => .Sin, 2198 .cos => .Cos, 2199 .tan => .Tan, 2200 .sqrt => .Sqrt, 2201 .exp => .Exp, 2202 .exp2 => .Exp2, 2203 .log => .Log, 2204 .log2 => .Log2, 2205 else => return null, 2206 })), 2207 else => unreachable, 2208 }; 2209 } 2210 }; 2211 2212 fn buildUnary(cg: *CodeGen, op: UnaryOp, operand: Temporary) !Temporary { 2213 const zcu = cg.module.zcu; 2214 const target = cg.module.zcu.getTarget(); 2215 const v = cg.vectorization(.{operand}); 2216 const ops = v.components(); 2217 const results = cg.module.allocIds(ops); 2218 const op_result_ty = operand.ty.scalarType(zcu); 2219 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); 2220 const result_ty = try v.resultType(cg, operand.ty); 2221 const op_operand = try v.prepare(cg, operand); 2222 2223 if (op.extInstOpcode(target)) |opcode| { 2224 const set = try cg.importExtendedSet(); 2225 for (0..ops) |i| { 2226 try cg.body.emit(cg.module.gpa, .OpExtInst, .{ 2227 .id_result_type = op_result_ty_id, 2228 .id_result = results.at(i), 2229 .set = set, 2230 .instruction = .{ .inst = opcode }, 2231 .id_ref_4 = &.{op_operand.at(i)}, 2232 }); 2233 } 2234 } else { 2235 const opcode: Opcode = switch (op) { 2236 .l_not => .OpLogicalNot, 2237 .bit_not => .OpNot, 2238 .i_neg => .OpSNegate, 2239 .f_neg => .OpFNegate, 2240 else => return cg.todo( 2241 "implement unary operation '{s}' for {s} os", 2242 .{ @tagName(op), @tagName(target.os.tag) }, 2243 ), 2244 }; 2245 for (0..ops) |i| { 2246 try cg.body.emitRaw(cg.module.gpa, opcode, 3); 2247 cg.body.writeOperand(Id, op_result_ty_id); 2248 cg.body.writeOperand(Id, results.at(i)); 2249 cg.body.writeOperand(Id, op_operand.at(i)); 2250 } 2251 } 2252 2253 return v.finalize(result_ty, results); 2254 } 2255 2256 fn buildBinary(cg: *CodeGen, opcode: Opcode, lhs: Temporary, rhs: Temporary) !Temporary { 2257 const zcu = cg.module.zcu; 2258 2259 const v = cg.vectorization(.{ lhs, rhs }); 2260 const ops = v.components(); 2261 const results = cg.module.allocIds(ops); 2262 2263 const op_result_ty = lhs.ty.scalarType(zcu); 2264 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); 2265 const result_ty = try v.resultType(cg, lhs.ty); 2266 2267 const op_lhs = try v.prepare(cg, lhs); 2268 const op_rhs = try v.prepare(cg, rhs); 2269 2270 for (0..ops) |i| { 2271 try cg.body.emitRaw(cg.module.gpa, opcode, 4); 2272 cg.body.writeOperand(Id, op_result_ty_id); 2273 cg.body.writeOperand(Id, results.at(i)); 2274 cg.body.writeOperand(Id, op_lhs.at(i)); 2275 cg.body.writeOperand(Id, op_rhs.at(i)); 2276 } 2277 2278 return v.finalize(result_ty, results); 2279 } 2280 2281 /// This function builds an extended multiplication, either OpSMulExtended or OpUMulExtended on Vulkan, 2282 /// or OpIMul and s_mul_hi or u_mul_hi on OpenCL. 2283 fn buildWideMul( 2284 cg: *CodeGen, 2285 signedness: std.builtin.Signedness, 2286 lhs: Temporary, 2287 rhs: Temporary, 2288 ) !struct { Temporary, Temporary } { 2289 const pt = cg.pt; 2290 const zcu = cg.module.zcu; 2291 const comp = zcu.comp; 2292 const gpa = comp.gpa; 2293 const io = comp.io; 2294 const target = cg.module.zcu.getTarget(); 2295 const ip = &zcu.intern_pool; 2296 2297 const v = lhs.vectorization(cg).unify(rhs.vectorization(cg)); 2298 const ops = v.components(); 2299 2300 const arith_op_ty = lhs.ty.scalarType(zcu); 2301 const arith_op_ty_id = try cg.resolveType(arith_op_ty, .direct); 2302 2303 const lhs_op = try v.prepare(cg, lhs); 2304 const rhs_op = try v.prepare(cg, rhs); 2305 2306 const value_results = cg.module.allocIds(ops); 2307 const overflow_results = cg.module.allocIds(ops); 2308 2309 switch (target.os.tag) { 2310 .opencl => { 2311 // Currently, SPIRV-LLVM-Translator based backends cannot deal with OpSMulExtended and 2312 // OpUMulExtended. For these we will use the OpenCL s_mul_hi to compute the high-order bits 2313 // instead. 2314 const set = try cg.importExtendedSet(); 2315 const overflow_inst: spec.OpenClOpcode = switch (signedness) { 2316 .signed => .s_mul_hi, 2317 .unsigned => .u_mul_hi, 2318 }; 2319 2320 for (0..ops) |i| { 2321 try cg.body.emit(gpa, .OpIMul, .{ 2322 .id_result_type = arith_op_ty_id, 2323 .id_result = value_results.at(i), 2324 .operand_1 = lhs_op.at(i), 2325 .operand_2 = rhs_op.at(i), 2326 }); 2327 2328 try cg.body.emit(gpa, .OpExtInst, .{ 2329 .id_result_type = arith_op_ty_id, 2330 .id_result = overflow_results.at(i), 2331 .set = set, 2332 .instruction = .{ .inst = @intFromEnum(overflow_inst) }, 2333 .id_ref_4 = &.{ lhs_op.at(i), rhs_op.at(i) }, 2334 }); 2335 } 2336 }, 2337 .vulkan, .opengl => { 2338 // Operations return a struct{T, T} 2339 // where T is maybe vectorized. 2340 const op_result_ty: Type = .fromInterned(try ip.getTupleType(gpa, io, pt.tid, .{ 2341 .types = &.{ arith_op_ty.toIntern(), arith_op_ty.toIntern() }, 2342 .values = &.{ .none, .none }, 2343 })); 2344 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); 2345 2346 const opcode: Opcode = switch (signedness) { 2347 .signed => .OpSMulExtended, 2348 .unsigned => .OpUMulExtended, 2349 }; 2350 2351 for (0..ops) |i| { 2352 const op_result = cg.module.allocId(); 2353 2354 try cg.body.emitRaw(gpa, opcode, 4); 2355 cg.body.writeOperand(Id, op_result_ty_id); 2356 cg.body.writeOperand(Id, op_result); 2357 cg.body.writeOperand(Id, lhs_op.at(i)); 2358 cg.body.writeOperand(Id, rhs_op.at(i)); 2359 2360 // The above operation returns a struct. We might want to expand 2361 // Temporary to deal with the fact that these are structs eventually, 2362 // but for now, take the struct apart and return two separate vectors. 2363 2364 try cg.body.emit(gpa, .OpCompositeExtract, .{ 2365 .id_result_type = arith_op_ty_id, 2366 .id_result = value_results.at(i), 2367 .composite = op_result, 2368 .indexes = &.{0}, 2369 }); 2370 2371 try cg.body.emit(gpa, .OpCompositeExtract, .{ 2372 .id_result_type = arith_op_ty_id, 2373 .id_result = overflow_results.at(i), 2374 .composite = op_result, 2375 .indexes = &.{1}, 2376 }); 2377 } 2378 }, 2379 else => unreachable, 2380 } 2381 2382 const result_ty = try v.resultType(cg, lhs.ty); 2383 return .{ 2384 v.finalize(result_ty, value_results), 2385 v.finalize(result_ty, overflow_results), 2386 }; 2387 } 2388 2389 /// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure. 2390 /// In order to be able to run tests, we "temporarily" lower test kernels into separate entry- 2391 /// points. The test executor will then be able to invoke these to run the tests. 2392 /// Note that tests are lowered according to std.builtin.TestFn, which is `fn () anyerror!void`. 2393 /// (anyerror!void has the same layout as anyerror). 2394 /// Each test declaration generates a function like. 2395 /// %anyerror = OpTypeInt 0 16 2396 /// %p_invocation_globals_struct_ty = ... 2397 /// %p_anyerror = OpTypePointer CrossWorkgroup %anyerror 2398 /// %K = OpTypeFunction %void %p_invocation_globals_struct_ty %p_anyerror 2399 /// 2400 /// %test = OpFunction %void %K 2401 /// %p_invocation_globals = OpFunctionParameter p_invocation_globals_struct_ty 2402 /// %p_err = OpFunctionParameter %p_anyerror 2403 /// %lbl = OpLabel 2404 /// %result = OpFunctionCall %anyerror %func %p_invocation_globals 2405 /// OpStore %p_err %result 2406 /// OpFunctionEnd 2407 /// TODO is to also write out the error as a function call parameter, and to somehow fetch 2408 /// the name of an error in the text executor. 2409 fn generateTestEntryPoint( 2410 cg: *CodeGen, 2411 name: []const u8, 2412 spv_decl_index: Module.Decl.Index, 2413 test_id: Id, 2414 ) !void { 2415 const gpa = cg.module.gpa; 2416 const zcu = cg.module.zcu; 2417 const target = cg.module.zcu.getTarget(); 2418 2419 const anyerror_ty_id = try cg.resolveType(.anyerror, .direct); 2420 const ptr_anyerror_ty = try cg.pt.ptrType(.{ 2421 .child = .anyerror_type, 2422 .flags = .{ .address_space = .global }, 2423 }); 2424 const ptr_anyerror_ty_id = try cg.resolveType(ptr_anyerror_ty, .direct); 2425 2426 const kernel_id = cg.module.declPtr(spv_decl_index).result_id; 2427 2428 const section = &cg.module.sections.functions; 2429 2430 const p_error_id = cg.module.allocId(); 2431 switch (target.os.tag) { 2432 .opencl, .amdhsa => { 2433 const void_ty_id = try cg.resolveType(.void, .direct); 2434 const kernel_proto_ty_id = try cg.module.functionType(void_ty_id, &.{ptr_anyerror_ty_id}); 2435 2436 try section.emit(gpa, .OpFunction, .{ 2437 .id_result_type = try cg.resolveType(.void, .direct), 2438 .id_result = kernel_id, 2439 .function_control = .{}, 2440 .function_type = kernel_proto_ty_id, 2441 }); 2442 2443 try section.emit(gpa, .OpFunctionParameter, .{ 2444 .id_result_type = ptr_anyerror_ty_id, 2445 .id_result = p_error_id, 2446 }); 2447 2448 try section.emit(gpa, .OpLabel, .{ 2449 .id_result = cg.module.allocId(), 2450 }); 2451 }, 2452 .vulkan, .opengl => { 2453 if (cg.module.error_buffer == null) { 2454 const spv_err_decl_index = try cg.module.allocDecl(.global); 2455 const err_buf_result_id = cg.module.declPtr(spv_err_decl_index).result_id; 2456 2457 const buffer_struct_ty_id = try cg.module.structType( 2458 &.{anyerror_ty_id}, 2459 &.{"error_out"}, 2460 null, 2461 .none, 2462 ); 2463 try cg.module.decorate(buffer_struct_ty_id, .block); 2464 try cg.module.decorateMember(buffer_struct_ty_id, 0, .{ .offset = .{ .byte_offset = 0 } }); 2465 2466 const ptr_buffer_struct_ty_id = cg.module.allocId(); 2467 try cg.module.sections.globals.emit(gpa, .OpTypePointer, .{ 2468 .id_result = ptr_buffer_struct_ty_id, 2469 .storage_class = cg.module.storageClass(.global), 2470 .type = buffer_struct_ty_id, 2471 }); 2472 2473 try cg.module.sections.globals.emit(gpa, .OpVariable, .{ 2474 .id_result_type = ptr_buffer_struct_ty_id, 2475 .id_result = err_buf_result_id, 2476 .storage_class = cg.module.storageClass(.global), 2477 }); 2478 try cg.module.decorate(err_buf_result_id, .{ .descriptor_set = .{ .descriptor_set = 0 } }); 2479 try cg.module.decorate(err_buf_result_id, .{ .binding = .{ .binding_point = 0 } }); 2480 2481 cg.module.error_buffer = spv_err_decl_index; 2482 } 2483 2484 try cg.module.sections.execution_modes.emit(gpa, .OpExecutionMode, .{ 2485 .entry_point = kernel_id, 2486 .mode = .{ .local_size = .{ 2487 .x_size = 1, 2488 .y_size = 1, 2489 .z_size = 1, 2490 } }, 2491 }); 2492 2493 const void_ty_id = try cg.resolveType(.void, .direct); 2494 const kernel_proto_ty_id = try cg.module.functionType(void_ty_id, &.{}); 2495 try section.emit(gpa, .OpFunction, .{ 2496 .id_result_type = try cg.resolveType(.void, .direct), 2497 .id_result = kernel_id, 2498 .function_control = .{}, 2499 .function_type = kernel_proto_ty_id, 2500 }); 2501 try section.emit(gpa, .OpLabel, .{ 2502 .id_result = cg.module.allocId(), 2503 }); 2504 2505 const spv_err_decl_index = cg.module.error_buffer.?; 2506 const buffer_id = cg.module.declPtr(spv_err_decl_index).result_id; 2507 try cg.module.decl_deps.append(gpa, spv_err_decl_index); 2508 2509 const zero_id = try cg.constInt(.u32, 0); 2510 try section.emit(gpa, .OpInBoundsAccessChain, .{ 2511 .id_result_type = ptr_anyerror_ty_id, 2512 .id_result = p_error_id, 2513 .base = buffer_id, 2514 .indexes = &.{zero_id}, 2515 }); 2516 }, 2517 else => unreachable, 2518 } 2519 2520 const error_id = cg.module.allocId(); 2521 try section.emit(gpa, .OpFunctionCall, .{ 2522 .id_result_type = anyerror_ty_id, 2523 .id_result = error_id, 2524 .function = test_id, 2525 }); 2526 // Note: Convert to direct not required. 2527 try section.emit(gpa, .OpStore, .{ 2528 .pointer = p_error_id, 2529 .object = error_id, 2530 .memory_access = .{ 2531 .aligned = .{ .literal_integer = @intCast(Type.abiAlignment(.anyerror, zcu).toByteUnits().?) }, 2532 }, 2533 }); 2534 try section.emit(gpa, .OpReturn, {}); 2535 try section.emit(gpa, .OpFunctionEnd, {}); 2536 2537 // Just generate a quick other name because the intel runtime crashes when the entry- 2538 // point name is the same as a different OpName. 2539 const test_name = try std.fmt.allocPrint(cg.module.arena, "test {s}", .{name}); 2540 2541 const execution_mode: spec.ExecutionModel = switch (target.os.tag) { 2542 .vulkan, .opengl => .gl_compute, 2543 .opencl, .amdhsa => .kernel, 2544 else => unreachable, 2545 }; 2546 2547 try cg.module.declareEntryPoint(spv_decl_index, test_name, execution_mode, null); 2548 } 2549 2550 fn intFromBool(cg: *CodeGen, value: Temporary, result_ty: Type) !Temporary { 2551 const zero_id = try cg.constInt(result_ty, 0); 2552 const one_id = try cg.constInt(result_ty, 1); 2553 2554 return try cg.buildSelect( 2555 value, 2556 Temporary.init(result_ty, one_id), 2557 Temporary.init(result_ty, zero_id), 2558 ); 2559 } 2560 2561 /// Convert representation from indirect (in memory) to direct (in 'register') 2562 /// This converts the argument type from resolveType(ty, .indirect) to resolveType(ty, .direct). 2563 fn convertToDirect(cg: *CodeGen, ty: Type, operand_id: Id) !Id { 2564 const pt = cg.pt; 2565 const zcu = cg.module.zcu; 2566 switch (ty.scalarType(zcu).zigTypeTag(zcu)) { 2567 .bool => { 2568 const false_id = try cg.constBool(false, .indirect); 2569 const operand_ty = blk: { 2570 if (!ty.isVector(zcu)) break :blk Type.u1; 2571 break :blk try pt.vectorType(.{ 2572 .len = ty.vectorLen(zcu), 2573 .child = .u1_type, 2574 }); 2575 }; 2576 2577 const result = try cg.buildCmp( 2578 .OpINotEqual, 2579 Temporary.init(operand_ty, operand_id), 2580 Temporary.init(.u1, false_id), 2581 ); 2582 return try result.materialize(cg); 2583 }, 2584 else => return operand_id, 2585 } 2586 } 2587 2588 /// Convert representation from direct (in 'register) to direct (in memory) 2589 /// This converts the argument type from resolveType(ty, .direct) to resolveType(ty, .indirect). 2590 fn convertToIndirect(cg: *CodeGen, ty: Type, operand_id: Id) !Id { 2591 const zcu = cg.module.zcu; 2592 switch (ty.scalarType(zcu).zigTypeTag(zcu)) { 2593 .bool => { 2594 const result = try cg.intFromBool(.init(ty, operand_id), .u1); 2595 return try result.materialize(cg); 2596 }, 2597 else => return operand_id, 2598 } 2599 } 2600 2601 fn extractField(cg: *CodeGen, result_ty: Type, object: Id, field: u32) !Id { 2602 const result_ty_id = try cg.resolveType(result_ty, .indirect); 2603 const result_id = cg.module.allocId(); 2604 const indexes = [_]u32{field}; 2605 try cg.body.emit(cg.module.gpa, .OpCompositeExtract, .{ 2606 .id_result_type = result_ty_id, 2607 .id_result = result_id, 2608 .composite = object, 2609 .indexes = &indexes, 2610 }); 2611 // Convert bools; direct structs have their field types as indirect values. 2612 return try cg.convertToDirect(result_ty, result_id); 2613 } 2614 2615 fn extractVectorComponent(cg: *CodeGen, result_ty: Type, vector_id: Id, field: u32) !Id { 2616 const result_ty_id = try cg.resolveType(result_ty, .direct); 2617 const result_id = cg.module.allocId(); 2618 const indexes = [_]u32{field}; 2619 try cg.body.emit(cg.module.gpa, .OpCompositeExtract, .{ 2620 .id_result_type = result_ty_id, 2621 .id_result = result_id, 2622 .composite = vector_id, 2623 .indexes = &indexes, 2624 }); 2625 // Vector components are already stored in direct representation. 2626 return result_id; 2627 } 2628 2629 const MemoryOptions = struct { 2630 is_volatile: bool = false, 2631 }; 2632 2633 fn load(cg: *CodeGen, value_ty: Type, ptr_id: Id, options: MemoryOptions) !Id { 2634 const zcu = cg.module.zcu; 2635 const alignment: u32 = @intCast(value_ty.abiAlignment(zcu).toByteUnits().?); 2636 const indirect_value_ty_id = try cg.resolveType(value_ty, .indirect); 2637 const result_id = cg.module.allocId(); 2638 const access: spec.MemoryAccess.Extended = .{ 2639 .@"volatile" = options.is_volatile, 2640 .aligned = .{ .literal_integer = alignment }, 2641 }; 2642 try cg.body.emit(cg.module.gpa, .OpLoad, .{ 2643 .id_result_type = indirect_value_ty_id, 2644 .id_result = result_id, 2645 .pointer = ptr_id, 2646 .memory_access = access, 2647 }); 2648 return try cg.convertToDirect(value_ty, result_id); 2649 } 2650 2651 fn store(cg: *CodeGen, value_ty: Type, ptr_id: Id, value_id: Id, options: MemoryOptions) !void { 2652 const indirect_value_id = try cg.convertToIndirect(value_ty, value_id); 2653 const access: spec.MemoryAccess.Extended = .{ .@"volatile" = options.is_volatile }; 2654 try cg.body.emit(cg.module.gpa, .OpStore, .{ 2655 .pointer = ptr_id, 2656 .object = indirect_value_id, 2657 .memory_access = access, 2658 }); 2659 } 2660 2661 fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) !void { 2662 for (body) |inst| { 2663 try cg.genInst(inst); 2664 } 2665 } 2666 2667 fn genInst(cg: *CodeGen, inst: Air.Inst.Index) Error!void { 2668 const gpa = cg.module.gpa; 2669 const zcu = cg.module.zcu; 2670 const ip = &zcu.intern_pool; 2671 if (cg.liveness.isUnused(inst) and !cg.air.mustLower(inst, ip)) 2672 return; 2673 2674 const air_tags = cg.air.instructions.items(.tag); 2675 const maybe_result_id: ?Id = switch (air_tags[@intFromEnum(inst)]) { 2676 // zig fmt: off 2677 .add, .add_wrap, .add_optimized => try cg.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd), 2678 .sub, .sub_wrap, .sub_optimized => try cg.airArithOp(inst, .OpFSub, .OpISub, .OpISub), 2679 .mul, .mul_wrap, .mul_optimized => try cg.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul), 2680 2681 .sqrt => try cg.airUnOpSimple(inst, .sqrt), 2682 .sin => try cg.airUnOpSimple(inst, .sin), 2683 .cos => try cg.airUnOpSimple(inst, .cos), 2684 .tan => try cg.airUnOpSimple(inst, .tan), 2685 .exp => try cg.airUnOpSimple(inst, .exp), 2686 .exp2 => try cg.airUnOpSimple(inst, .exp2), 2687 .log => try cg.airUnOpSimple(inst, .log), 2688 .log2 => try cg.airUnOpSimple(inst, .log2), 2689 .log10 => try cg.airUnOpSimple(inst, .log10), 2690 .abs => try cg.airAbs(inst), 2691 .floor => try cg.airUnOpSimple(inst, .floor), 2692 .ceil => try cg.airUnOpSimple(inst, .ceil), 2693 .round => try cg.airUnOpSimple(inst, .round), 2694 .trunc_float => try cg.airUnOpSimple(inst, .trunc), 2695 .neg, .neg_optimized => try cg.airUnOpSimple(inst, .f_neg), 2696 2697 .div_float, .div_float_optimized => try cg.airArithOp(inst, .OpFDiv, .OpSDiv, .OpUDiv), 2698 .div_floor, .div_floor_optimized => try cg.airDivFloor(inst), 2699 .div_trunc, .div_trunc_optimized => try cg.airDivTrunc(inst), 2700 2701 .rem, .rem_optimized => try cg.airArithOp(inst, .OpFRem, .OpSRem, .OpUMod), 2702 .mod, .mod_optimized => try cg.airArithOp(inst, .OpFMod, .OpSMod, .OpUMod), 2703 2704 .add_with_overflow => try cg.airAddSubOverflow(inst, .OpIAdd, .OpULessThan, .OpSLessThan), 2705 .sub_with_overflow => try cg.airAddSubOverflow(inst, .OpISub, .OpUGreaterThan, .OpSGreaterThan), 2706 .mul_with_overflow => try cg.airMulOverflow(inst), 2707 .shl_with_overflow => try cg.airShlOverflow(inst), 2708 2709 .mul_add => try cg.airMulAdd(inst), 2710 2711 .ctz => try cg.airClzCtz(inst, .ctz), 2712 .clz => try cg.airClzCtz(inst, .clz), 2713 2714 .select => try cg.airSelect(inst), 2715 2716 .splat => try cg.airSplat(inst), 2717 .reduce, .reduce_optimized => try cg.airReduce(inst), 2718 .shuffle_one => try cg.airShuffleOne(inst), 2719 .shuffle_two => try cg.airShuffleTwo(inst), 2720 2721 .ptr_add => try cg.airPtrAdd(inst), 2722 .ptr_sub => try cg.airPtrSub(inst), 2723 2724 .bit_and => try cg.airBinOpSimple(inst, .OpBitwiseAnd), 2725 .bit_or => try cg.airBinOpSimple(inst, .OpBitwiseOr), 2726 .xor => try cg.airBinOpSimple(inst, .OpBitwiseXor), 2727 .bool_and => try cg.airBinOpSimple(inst, .OpLogicalAnd), 2728 .bool_or => try cg.airBinOpSimple(inst, .OpLogicalOr), 2729 2730 .shl, .shl_exact => try cg.airShift(inst, .OpShiftLeftLogical, .OpShiftLeftLogical), 2731 .shr, .shr_exact => try cg.airShift(inst, .OpShiftRightLogical, .OpShiftRightArithmetic), 2732 2733 .min => try cg.airMinMax(inst, .min), 2734 .max => try cg.airMinMax(inst, .max), 2735 2736 .bitcast => try cg.airBitCast(inst), 2737 .intcast, .trunc => try cg.airIntCast(inst), 2738 .float_from_int => try cg.airFloatFromInt(inst), 2739 .int_from_float => try cg.airIntFromFloat(inst), 2740 .fpext, .fptrunc => try cg.airFloatCast(inst), 2741 .not => try cg.airNot(inst), 2742 2743 .array_to_slice => try cg.airArrayToSlice(inst), 2744 .slice => try cg.airSlice(inst), 2745 .aggregate_init => try cg.airAggregateInit(inst), 2746 .memcpy => return cg.airMemcpy(inst), 2747 .memmove => return cg.airMemmove(inst), 2748 2749 .slice_ptr => try cg.airSliceField(inst, 0), 2750 .slice_len => try cg.airSliceField(inst, 1), 2751 .slice_elem_ptr => try cg.airSliceElemPtr(inst), 2752 .slice_elem_val => try cg.airSliceElemVal(inst), 2753 .ptr_elem_ptr => try cg.airPtrElemPtr(inst), 2754 .ptr_elem_val => try cg.airPtrElemVal(inst), 2755 .array_elem_val => try cg.airArrayElemVal(inst), 2756 2757 .set_union_tag => return cg.airSetUnionTag(inst), 2758 .get_union_tag => try cg.airGetUnionTag(inst), 2759 .union_init => try cg.airUnionInit(inst), 2760 2761 .struct_field_val => try cg.airStructFieldVal(inst), 2762 .field_parent_ptr => try cg.airFieldParentPtr(inst), 2763 2764 .struct_field_ptr_index_0 => try cg.airStructFieldPtrIndex(inst, 0), 2765 .struct_field_ptr_index_1 => try cg.airStructFieldPtrIndex(inst, 1), 2766 .struct_field_ptr_index_2 => try cg.airStructFieldPtrIndex(inst, 2), 2767 .struct_field_ptr_index_3 => try cg.airStructFieldPtrIndex(inst, 3), 2768 2769 .cmp_eq => try cg.airCmp(inst, .eq), 2770 .cmp_neq => try cg.airCmp(inst, .neq), 2771 .cmp_gt => try cg.airCmp(inst, .gt), 2772 .cmp_gte => try cg.airCmp(inst, .gte), 2773 .cmp_lt => try cg.airCmp(inst, .lt), 2774 .cmp_lte => try cg.airCmp(inst, .lte), 2775 .cmp_vector => try cg.airVectorCmp(inst), 2776 2777 .arg => cg.airArg(), 2778 .alloc => try cg.airAlloc(inst), 2779 // TODO: We probably need to have a special implementation of this for the C abi. 2780 .ret_ptr => try cg.airAlloc(inst), 2781 .block => try cg.airBlock(inst), 2782 2783 .load => try cg.airLoad(inst), 2784 .store, .store_safe => return cg.airStore(inst), 2785 2786 .br => return cg.airBr(inst), 2787 // For now just ignore this instruction. This effectively falls back on the old implementation, 2788 // this doesn't change anything for us. 2789 .repeat => return, 2790 .breakpoint => return, 2791 .cond_br => return cg.airCondBr(inst), 2792 .loop => return cg.airLoop(inst), 2793 .ret => return cg.airRet(inst), 2794 .ret_safe => return cg.airRet(inst), // TODO 2795 .ret_load => return cg.airRetLoad(inst), 2796 .@"try" => try cg.airTry(inst), 2797 .switch_br => return cg.airSwitchBr(inst), 2798 .unreach, .trap => return cg.airUnreach(), 2799 2800 .dbg_empty_stmt => return, 2801 .dbg_stmt => return cg.airDbgStmt(inst), 2802 .dbg_inline_block => try cg.airDbgInlineBlock(inst), 2803 .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => return cg.airDbgVar(inst), 2804 2805 .unwrap_errunion_err => try cg.airErrUnionErr(inst), 2806 .unwrap_errunion_payload => try cg.airErrUnionPayload(inst), 2807 .wrap_errunion_err => try cg.airWrapErrUnionErr(inst), 2808 .wrap_errunion_payload => try cg.airWrapErrUnionPayload(inst), 2809 2810 .is_null => try cg.airIsNull(inst, false, .is_null), 2811 .is_non_null => try cg.airIsNull(inst, false, .is_non_null), 2812 .is_null_ptr => try cg.airIsNull(inst, true, .is_null), 2813 .is_non_null_ptr => try cg.airIsNull(inst, true, .is_non_null), 2814 .is_err => try cg.airIsErr(inst, .is_err), 2815 .is_non_err => try cg.airIsErr(inst, .is_non_err), 2816 2817 .optional_payload => try cg.airUnwrapOptional(inst), 2818 .optional_payload_ptr => try cg.airUnwrapOptionalPtr(inst), 2819 .wrap_optional => try cg.airWrapOptional(inst), 2820 2821 .assembly => try cg.airAssembly(inst), 2822 2823 .call => try cg.airCall(inst, .auto), 2824 .call_always_tail => try cg.airCall(inst, .always_tail), 2825 .call_never_tail => try cg.airCall(inst, .never_tail), 2826 .call_never_inline => try cg.airCall(inst, .never_inline), 2827 2828 .work_item_id => try cg.airWorkItemId(inst), 2829 .work_group_size => try cg.airWorkGroupSize(inst), 2830 .work_group_id => try cg.airWorkGroupId(inst), 2831 2832 // zig fmt: on 2833 2834 else => |tag| return cg.todo("implement AIR tag {s}", .{@tagName(tag)}), 2835 }; 2836 2837 const result_id = maybe_result_id orelse return; 2838 try cg.inst_results.putNoClobber(gpa, inst, result_id); 2839 } 2840 2841 fn airBinOpSimple(cg: *CodeGen, inst: Air.Inst.Index, op: Opcode) !?Id { 2842 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 2843 const lhs = try cg.temporary(bin_op.lhs); 2844 const rhs = try cg.temporary(bin_op.rhs); 2845 2846 const result = try cg.buildBinary(op, lhs, rhs); 2847 return try result.materialize(cg); 2848 } 2849 2850 fn airShift(cg: *CodeGen, inst: Air.Inst.Index, unsigned: Opcode, signed: Opcode) !?Id { 2851 const zcu = cg.module.zcu; 2852 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 2853 2854 if (cg.typeOf(bin_op.lhs).isVector(zcu) and !cg.typeOf(bin_op.rhs).isVector(zcu)) { 2855 return cg.fail("vector shift with scalar rhs", .{}); 2856 } 2857 2858 const base = try cg.temporary(bin_op.lhs); 2859 const shift = try cg.temporary(bin_op.rhs); 2860 2861 const result_ty = cg.typeOfIndex(inst); 2862 2863 const info = cg.arithmeticTypeInfo(result_ty); 2864 switch (info.class) { 2865 .composite_integer => return cg.todo("shift ops for composite integers", .{}), 2866 .integer, .strange_integer => {}, 2867 .float, .bool => unreachable, 2868 } 2869 2870 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that, 2871 // so just manually upcast it if required. 2872 2873 // Note: The sign may differ here between the shift and the base type, in case 2874 // of an arithmetic right shift. SPIR-V still expects the same type, 2875 // so in that case we have to cast convert to signed. 2876 const casted_shift = try cg.buildConvert(base.ty.scalarType(zcu), shift); 2877 2878 const shifted = switch (info.signedness) { 2879 .unsigned => try cg.buildBinary(unsigned, base, casted_shift), 2880 .signed => try cg.buildBinary(signed, base, casted_shift), 2881 }; 2882 2883 const result = try cg.normalize(shifted, info); 2884 return try result.materialize(cg); 2885 } 2886 2887 const MinMax = enum { 2888 min, 2889 max, 2890 2891 pub fn extInstOpcode( 2892 op: MinMax, 2893 target: *const std.Target, 2894 info: ArithmeticTypeInfo, 2895 ) u32 { 2896 return switch (target.os.tag) { 2897 .opencl => @intFromEnum(@as(spec.OpenClOpcode, switch (info.class) { 2898 .float => switch (op) { 2899 .min => .fmin, 2900 .max => .fmax, 2901 }, 2902 .integer, .strange_integer, .composite_integer => switch (info.signedness) { 2903 .signed => switch (op) { 2904 .min => .s_min, 2905 .max => .s_max, 2906 }, 2907 .unsigned => switch (op) { 2908 .min => .u_min, 2909 .max => .u_max, 2910 }, 2911 }, 2912 .bool => unreachable, 2913 })), 2914 .vulkan, .opengl => @intFromEnum(@as(spec.GlslOpcode, switch (info.class) { 2915 .float => switch (op) { 2916 .min => .FMin, 2917 .max => .FMax, 2918 }, 2919 .integer, .strange_integer, .composite_integer => switch (info.signedness) { 2920 .signed => switch (op) { 2921 .min => .SMin, 2922 .max => .SMax, 2923 }, 2924 .unsigned => switch (op) { 2925 .min => .UMin, 2926 .max => .UMax, 2927 }, 2928 }, 2929 .bool => unreachable, 2930 })), 2931 else => unreachable, 2932 }; 2933 } 2934 }; 2935 2936 fn airMinMax(cg: *CodeGen, inst: Air.Inst.Index, op: MinMax) !?Id { 2937 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 2938 2939 const lhs = try cg.temporary(bin_op.lhs); 2940 const rhs = try cg.temporary(bin_op.rhs); 2941 2942 const result = try cg.minMax(lhs, rhs, op); 2943 return try result.materialize(cg); 2944 } 2945 2946 fn minMax(cg: *CodeGen, lhs: Temporary, rhs: Temporary, op: MinMax) !Temporary { 2947 const zcu = cg.module.zcu; 2948 const target = zcu.getTarget(); 2949 const info = cg.arithmeticTypeInfo(lhs.ty); 2950 2951 const v = cg.vectorization(.{ lhs, rhs }); 2952 const ops = v.components(); 2953 const results = cg.module.allocIds(ops); 2954 2955 const op_result_ty = lhs.ty.scalarType(zcu); 2956 const op_result_ty_id = try cg.resolveType(op_result_ty, .direct); 2957 const result_ty = try v.resultType(cg, lhs.ty); 2958 2959 const op_lhs = try v.prepare(cg, lhs); 2960 const op_rhs = try v.prepare(cg, rhs); 2961 2962 const set = try cg.importExtendedSet(); 2963 const opcode = op.extInstOpcode(target, info); 2964 for (0..ops) |i| { 2965 try cg.body.emit(cg.module.gpa, .OpExtInst, .{ 2966 .id_result_type = op_result_ty_id, 2967 .id_result = results.at(i), 2968 .set = set, 2969 .instruction = .{ .inst = opcode }, 2970 .id_ref_4 = &.{ op_lhs.at(i), op_rhs.at(i) }, 2971 }); 2972 } 2973 2974 return v.finalize(result_ty, results); 2975 } 2976 2977 /// This function normalizes values to a canonical representation 2978 /// after some arithmetic operation. This mostly consists of wrapping 2979 /// behavior for strange integers: 2980 /// - Unsigned integers are bitwise masked with a mask that only passes 2981 /// the valid bits through. 2982 /// - Signed integers are also sign extended if they are negative. 2983 /// All other values are returned unmodified (this makes strange integer 2984 /// wrapping easier to use in generic operations). 2985 fn normalize(cg: *CodeGen, value: Temporary, info: ArithmeticTypeInfo) !Temporary { 2986 const zcu = cg.module.zcu; 2987 const ty = value.ty; 2988 switch (info.class) { 2989 .composite_integer, .integer, .bool, .float => return value, 2990 .strange_integer => switch (info.signedness) { 2991 .unsigned => { 2992 const mask_value = @as(u64, std.math.maxInt(u64)) >> @as(u6, @intCast(64 - info.bits)); 2993 const mask_id = try cg.constInt(ty.scalarType(zcu), mask_value); 2994 return try cg.buildBinary(.OpBitwiseAnd, value, Temporary.init(ty.scalarType(zcu), mask_id)); 2995 }, 2996 .signed => { 2997 // Shift left and right so that we can copy the sight bit that way. 2998 const shift_amt_id = try cg.constInt(ty.scalarType(zcu), info.backing_bits - info.bits); 2999 const shift_amt: Temporary = .init(ty.scalarType(zcu), shift_amt_id); 3000 const left = try cg.buildBinary(.OpShiftLeftLogical, value, shift_amt); 3001 return try cg.buildBinary(.OpShiftRightArithmetic, left, shift_amt); 3002 }, 3003 }, 3004 } 3005 } 3006 3007 fn airDivFloor(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3008 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 3009 3010 const lhs = try cg.temporary(bin_op.lhs); 3011 const rhs = try cg.temporary(bin_op.rhs); 3012 3013 const info = cg.arithmeticTypeInfo(lhs.ty); 3014 switch (info.class) { 3015 .composite_integer => unreachable, // TODO 3016 .integer, .strange_integer => { 3017 switch (info.signedness) { 3018 .unsigned => { 3019 const result = try cg.buildBinary(.OpUDiv, lhs, rhs); 3020 return try result.materialize(cg); 3021 }, 3022 .signed => {}, 3023 } 3024 3025 // For signed integers: 3026 // (a / b) - (a % b != 0 && a < 0 != b < 0); 3027 // There shouldn't be any overflow issues. 3028 3029 const div = try cg.buildBinary(.OpSDiv, lhs, rhs); 3030 const rem = try cg.buildBinary(.OpSRem, lhs, rhs); 3031 const zero: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, 0)); 3032 const rem_non_zero = try cg.buildCmp(.OpINotEqual, rem, zero); 3033 const lhs_rhs_xor = try cg.buildBinary(.OpBitwiseXor, lhs, rhs); 3034 const signs_differ = try cg.buildCmp(.OpSLessThan, lhs_rhs_xor, zero); 3035 const adjust = try cg.buildBinary(.OpLogicalAnd, rem_non_zero, signs_differ); 3036 const result = try cg.buildBinary(.OpISub, div, try cg.intFromBool(adjust, div.ty)); 3037 return try result.materialize(cg); 3038 }, 3039 .float => { 3040 const div = try cg.buildBinary(.OpFDiv, lhs, rhs); 3041 const result = try cg.buildUnary(.floor, div); 3042 return try result.materialize(cg); 3043 }, 3044 .bool => unreachable, 3045 } 3046 } 3047 3048 fn airDivTrunc(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3049 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 3050 const lhs = try cg.temporary(bin_op.lhs); 3051 const rhs = try cg.temporary(bin_op.rhs); 3052 const info = cg.arithmeticTypeInfo(lhs.ty); 3053 switch (info.class) { 3054 .composite_integer => unreachable, // TODO 3055 .integer, .strange_integer => switch (info.signedness) { 3056 .unsigned => { 3057 const result = try cg.buildBinary(.OpUDiv, lhs, rhs); 3058 return try result.materialize(cg); 3059 }, 3060 .signed => { 3061 const result = try cg.buildBinary(.OpSDiv, lhs, rhs); 3062 return try result.materialize(cg); 3063 }, 3064 }, 3065 .float => { 3066 const div = try cg.buildBinary(.OpFDiv, lhs, rhs); 3067 const result = try cg.buildUnary(.trunc, div); 3068 return try result.materialize(cg); 3069 }, 3070 .bool => unreachable, 3071 } 3072 } 3073 3074 fn airUnOpSimple(cg: *CodeGen, inst: Air.Inst.Index, op: UnaryOp) !?Id { 3075 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 3076 const operand = try cg.temporary(un_op); 3077 const result = try cg.buildUnary(op, operand); 3078 return try result.materialize(cg); 3079 } 3080 3081 fn airArithOp( 3082 cg: *CodeGen, 3083 inst: Air.Inst.Index, 3084 comptime fop: Opcode, 3085 comptime sop: Opcode, 3086 comptime uop: Opcode, 3087 ) !?Id { 3088 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 3089 const lhs = try cg.temporary(bin_op.lhs); 3090 const rhs = try cg.temporary(bin_op.rhs); 3091 const info = cg.arithmeticTypeInfo(lhs.ty); 3092 const result = switch (info.class) { 3093 .composite_integer => unreachable, // TODO 3094 .integer, .strange_integer => switch (info.signedness) { 3095 .signed => try cg.buildBinary(sop, lhs, rhs), 3096 .unsigned => try cg.buildBinary(uop, lhs, rhs), 3097 }, 3098 .float => try cg.buildBinary(fop, lhs, rhs), 3099 .bool => unreachable, 3100 }; 3101 return try result.materialize(cg); 3102 } 3103 3104 fn airAbs(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3105 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 3106 const operand = try cg.temporary(ty_op.operand); 3107 // Note: operand_ty may be signed, while ty is always unsigned! 3108 const result_ty = cg.typeOfIndex(inst); 3109 const result = try cg.abs(result_ty, operand); 3110 return try result.materialize(cg); 3111 } 3112 3113 fn abs(cg: *CodeGen, result_ty: Type, value: Temporary) !Temporary { 3114 const zcu = cg.module.zcu; 3115 const target = cg.module.zcu.getTarget(); 3116 const operand_info = cg.arithmeticTypeInfo(value.ty); 3117 switch (operand_info.class) { 3118 .float => return try cg.buildUnary(.f_abs, value), 3119 .integer, .strange_integer => { 3120 const abs_value = try cg.buildUnary(.i_abs, value); 3121 switch (target.os.tag) { 3122 .vulkan, .opengl => { 3123 if (value.ty.intInfo(zcu).signedness == .signed) { 3124 return cg.todo("perform bitcast after @abs", .{}); 3125 } 3126 }, 3127 else => {}, 3128 } 3129 return try cg.normalize(abs_value, cg.arithmeticTypeInfo(result_ty)); 3130 }, 3131 .composite_integer => unreachable, // TODO 3132 .bool => unreachable, 3133 } 3134 } 3135 3136 fn airAddSubOverflow( 3137 cg: *CodeGen, 3138 inst: Air.Inst.Index, 3139 comptime add: Opcode, 3140 u_opcode: Opcode, 3141 s_opcode: Opcode, 3142 ) !?Id { 3143 // Note: OpIAddCarry and OpISubBorrow are not really useful here: For unsigned numbers, 3144 // there is in both cases only one extra operation required. For signed operations, 3145 // the overflow bit is set then going from 0x80.. to 0x00.., but this doesn't actually 3146 // normally set a carry bit. So the SPIR-V overflow operations are not particularly 3147 // useful here. 3148 3149 _ = s_opcode; 3150 3151 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 3152 const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data; 3153 const lhs = try cg.temporary(extra.lhs); 3154 const rhs = try cg.temporary(extra.rhs); 3155 const result_ty = cg.typeOfIndex(inst); 3156 3157 const info = cg.arithmeticTypeInfo(lhs.ty); 3158 switch (info.class) { 3159 .composite_integer => unreachable, // TODO 3160 .strange_integer, .integer => {}, 3161 .float, .bool => unreachable, 3162 } 3163 3164 const sum = try cg.buildBinary(add, lhs, rhs); 3165 const result = try cg.normalize(sum, info); 3166 const overflowed = switch (info.signedness) { 3167 // Overflow happened if the result is smaller than either of the operands. It doesn't matter which. 3168 // For subtraction the conditions need to be swapped. 3169 .unsigned => try cg.buildCmp(u_opcode, result, lhs), 3170 // For signed operations, we check the signs of the operands and the result. 3171 .signed => blk: { 3172 // Signed overflow detection using the sign bits of the operands and the result. 3173 // For addition (a + b), overflow occurs if the operands have the same sign 3174 // and the result's sign is different from the operands' sign. 3175 // (sign(a) == sign(b)) && (sign(a) != sign(result)) 3176 // For subtraction (a - b), overflow occurs if the operands have different signs 3177 // and the result's sign is different from the minuend's (a's) sign. 3178 // (sign(a) != sign(b)) && (sign(a) != sign(result)) 3179 const zero: Temporary = .init(rhs.ty, try cg.constInt(rhs.ty, 0)); 3180 const lhs_is_neg = try cg.buildCmp(.OpSLessThan, lhs, zero); 3181 const rhs_is_neg = try cg.buildCmp(.OpSLessThan, rhs, zero); 3182 const result_is_neg = try cg.buildCmp(.OpSLessThan, result, zero); 3183 const signs_match = try cg.buildCmp(.OpLogicalEqual, lhs_is_neg, rhs_is_neg); 3184 const result_sign_differs = try cg.buildCmp(.OpLogicalNotEqual, lhs_is_neg, result_is_neg); 3185 const overflow_condition = switch (add) { 3186 .OpIAdd => signs_match, 3187 .OpISub => try cg.buildUnary(.l_not, signs_match), 3188 else => unreachable, 3189 }; 3190 break :blk try cg.buildCmp(.OpLogicalAnd, overflow_condition, result_sign_differs); 3191 }, 3192 }; 3193 3194 const ov = try cg.intFromBool(overflowed, .u1); 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 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 3202 const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data; 3203 const lhs = try cg.temporary(extra.lhs); 3204 const rhs = try cg.temporary(extra.rhs); 3205 const result_ty = cg.typeOfIndex(inst); 3206 3207 const info = cg.arithmeticTypeInfo(lhs.ty); 3208 switch (info.class) { 3209 .composite_integer => unreachable, // TODO 3210 .strange_integer, .integer => {}, 3211 .float, .bool => unreachable, 3212 } 3213 3214 // There are 3 cases which we have to deal with: 3215 // - If info.bits < 32 / 2, we will upcast to 32 and check the higher bits 3216 // - If info.bits > 32 / 2, we have to use extended multiplication 3217 // - Additionally, if info.bits != 32, we'll have to check the high bits 3218 // of the result too. 3219 3220 const largest_int_bits = cg.largestSupportedIntBits(); 3221 // If non-null, the number of bits that the multiplication should be performed in. If 3222 // null, we have to use wide multiplication. 3223 const maybe_op_ty_bits: ?u16 = switch (info.bits) { 3224 0 => unreachable, 3225 1...16 => 32, 3226 17...32 => if (largest_int_bits > 32) 64 else null, // Upcast if we can. 3227 33...64 => null, // Always use wide multiplication. 3228 else => unreachable, // TODO: Composite integers 3229 }; 3230 3231 const result, const overflowed = switch (info.signedness) { 3232 .unsigned => blk: { 3233 if (maybe_op_ty_bits) |op_ty_bits| { 3234 const op_ty = try pt.intType(.unsigned, op_ty_bits); 3235 const casted_lhs = try cg.buildConvert(op_ty, lhs); 3236 const casted_rhs = try cg.buildConvert(op_ty, rhs); 3237 const full_result = try cg.buildBinary(.OpIMul, casted_lhs, casted_rhs); 3238 const low_bits = try cg.buildConvert(lhs.ty, full_result); 3239 const result = try cg.normalize(low_bits, info); 3240 // Shift the result bits away to get the overflow bits. 3241 const shift: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, info.bits)); 3242 const overflow = try cg.buildBinary(.OpShiftRightLogical, full_result, shift); 3243 // Directly check if its zero in the op_ty without converting first. 3244 const zero: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, 0)); 3245 const overflowed = try cg.buildCmp(.OpINotEqual, zero, overflow); 3246 break :blk .{ result, overflowed }; 3247 } 3248 3249 const low_bits, const high_bits = try cg.buildWideMul(.unsigned, lhs, rhs); 3250 3251 // Truncate the result, if required. 3252 const result = try cg.normalize(low_bits, info); 3253 3254 // Overflow happened if the high-bits of the result are non-zero OR if the 3255 // high bits of the low word of the result (those outside the range of the 3256 // int) are nonzero. 3257 const zero: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, 0)); 3258 const high_overflowed = try cg.buildCmp(.OpINotEqual, zero, high_bits); 3259 3260 // If no overflow bits in low_bits, no extra work needs to be done. 3261 if (info.backing_bits == info.bits) break :blk .{ result, high_overflowed }; 3262 3263 // Shift the result bits away to get the overflow bits. 3264 const shift: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, info.bits)); 3265 const low_overflow = try cg.buildBinary(.OpShiftRightLogical, low_bits, shift); 3266 const low_overflowed = try cg.buildCmp(.OpINotEqual, zero, low_overflow); 3267 3268 const overflowed = try cg.buildCmp(.OpLogicalOr, low_overflowed, high_overflowed); 3269 3270 break :blk .{ result, overflowed }; 3271 }, 3272 .signed => blk: { 3273 // - lhs >= 0, rhxs >= 0: expect positive; overflow should be 0 3274 // - lhs == 0 : expect positive; overflow should be 0 3275 // - rhs == 0: expect positive; overflow should be 0 3276 // - lhs > 0, rhs < 0: expect negative; overflow should be -1 3277 // - lhs < 0, rhs > 0: expect negative; overflow should be -1 3278 // - lhs <= 0, rhs <= 0: expect positive; overflow should be 0 3279 // ------ 3280 // overflow should be -1 when 3281 // (lhs > 0 && rhs < 0) || (lhs < 0 && rhs > 0) 3282 3283 const zero: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, 0)); 3284 const lhs_negative = try cg.buildCmp(.OpSLessThan, lhs, zero); 3285 const rhs_negative = try cg.buildCmp(.OpSLessThan, rhs, zero); 3286 const lhs_positive = try cg.buildCmp(.OpSGreaterThan, lhs, zero); 3287 const rhs_positive = try cg.buildCmp(.OpSGreaterThan, rhs, zero); 3288 3289 // Set to `true` if we expect -1. 3290 const expected_overflow_bit = try cg.buildBinary( 3291 .OpLogicalOr, 3292 try cg.buildCmp(.OpLogicalAnd, lhs_positive, rhs_negative), 3293 try cg.buildCmp(.OpLogicalAnd, lhs_negative, rhs_positive), 3294 ); 3295 3296 if (maybe_op_ty_bits) |op_ty_bits| { 3297 const op_ty = try pt.intType(.signed, op_ty_bits); 3298 // Assume normalized; sign bit is set. We want a sign extend. 3299 const casted_lhs = try cg.buildConvert(op_ty, lhs); 3300 const casted_rhs = try cg.buildConvert(op_ty, rhs); 3301 3302 const full_result = try cg.buildBinary(.OpIMul, casted_lhs, casted_rhs); 3303 3304 // Truncate to the result type. 3305 const low_bits = try cg.buildConvert(lhs.ty, full_result); 3306 const result = try cg.normalize(low_bits, info); 3307 3308 // Now, we need to check the overflow bits AND the sign 3309 // bit for the expected overflow bits. 3310 // To do that, shift out everything bit the sign bit and 3311 // then check what remains. 3312 const shift: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, info.bits - 1)); 3313 // Use SRA so that any sign bits are duplicated. Now we can just check if ALL bits are set 3314 // for negative cases. 3315 const overflow = try cg.buildBinary(.OpShiftRightArithmetic, full_result, shift); 3316 3317 const long_all_set: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, -1)); 3318 const long_zero: Temporary = .init(full_result.ty, try cg.constInt(full_result.ty, 0)); 3319 const mask = try cg.buildSelect(expected_overflow_bit, long_all_set, long_zero); 3320 3321 const overflowed = try cg.buildCmp(.OpINotEqual, mask, overflow); 3322 3323 break :blk .{ result, overflowed }; 3324 } 3325 3326 const low_bits, const high_bits = try cg.buildWideMul(.signed, lhs, rhs); 3327 3328 // Truncate result if required. 3329 const result = try cg.normalize(low_bits, info); 3330 3331 const all_set: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, -1)); 3332 const mask = try cg.buildSelect(expected_overflow_bit, all_set, zero); 3333 3334 // Like with unsigned, overflow happened if high_bits are not the ones we expect, 3335 // and we also need to check some ones from the low bits. 3336 3337 const high_overflowed = try cg.buildCmp(.OpINotEqual, mask, high_bits); 3338 3339 // If no overflow bits in low_bits, no extra work needs to be done. 3340 // Careful, we still have to check the sign bit, so this branch 3341 // only goes for i33 and such. 3342 if (info.backing_bits == info.bits + 1) break :blk .{ result, high_overflowed }; 3343 3344 // Shift the result bits away to get the overflow bits. 3345 const shift: Temporary = .init(lhs.ty, try cg.constInt(lhs.ty, info.bits - 1)); 3346 // Use SRA so that any sign bits are duplicated. Now we can just check if ALL bits are set 3347 // for negative cases. 3348 const low_overflow = try cg.buildBinary(.OpShiftRightArithmetic, low_bits, shift); 3349 const low_overflowed = try cg.buildCmp(.OpINotEqual, mask, low_overflow); 3350 3351 const overflowed = try cg.buildCmp(.OpLogicalOr, low_overflowed, high_overflowed); 3352 3353 break :blk .{ result, overflowed }; 3354 }, 3355 }; 3356 3357 const ov = try cg.intFromBool(overflowed, .u1); 3358 3359 const result_ty_id = try cg.resolveType(result_ty, .direct); 3360 return try cg.constructComposite(result_ty_id, &.{ try result.materialize(cg), try ov.materialize(cg) }); 3361 } 3362 3363 fn airShlOverflow(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3364 const zcu = cg.module.zcu; 3365 3366 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 3367 const extra = cg.air.extraData(Air.Bin, ty_pl.payload).data; 3368 3369 if (cg.typeOf(extra.lhs).isVector(zcu) and !cg.typeOf(extra.rhs).isVector(zcu)) { 3370 return cg.fail("vector shift with scalar rhs", .{}); 3371 } 3372 3373 const base = try cg.temporary(extra.lhs); 3374 const shift = try cg.temporary(extra.rhs); 3375 3376 const result_ty = cg.typeOfIndex(inst); 3377 3378 const info = cg.arithmeticTypeInfo(base.ty); 3379 switch (info.class) { 3380 .composite_integer => unreachable, // TODO 3381 .integer, .strange_integer => {}, 3382 .float, .bool => unreachable, 3383 } 3384 3385 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that, 3386 // so just manually upcast it if required. 3387 const casted_shift = try cg.buildConvert(base.ty.scalarType(zcu), shift); 3388 3389 const left = try cg.buildBinary(.OpShiftLeftLogical, base, casted_shift); 3390 const result = try cg.normalize(left, info); 3391 3392 const right = switch (info.signedness) { 3393 .unsigned => try cg.buildBinary(.OpShiftRightLogical, result, casted_shift), 3394 .signed => try cg.buildBinary(.OpShiftRightArithmetic, result, casted_shift), 3395 }; 3396 3397 const overflowed = try cg.buildCmp(.OpINotEqual, base, right); 3398 const ov = try cg.intFromBool(overflowed, .u1); 3399 3400 const result_ty_id = try cg.resolveType(result_ty, .direct); 3401 return try cg.constructComposite(result_ty_id, &.{ try result.materialize(cg), try ov.materialize(cg) }); 3402 } 3403 3404 fn airMulAdd(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3405 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 3406 const extra = cg.air.extraData(Air.Bin, pl_op.payload).data; 3407 3408 const a = try cg.temporary(extra.lhs); 3409 const b = try cg.temporary(extra.rhs); 3410 const c = try cg.temporary(pl_op.operand); 3411 3412 const result_ty = cg.typeOfIndex(inst); 3413 const info = cg.arithmeticTypeInfo(result_ty); 3414 assert(info.class == .float); // .mul_add is only emitted for floats 3415 3416 const result = try cg.buildFma(a, b, c); 3417 return try result.materialize(cg); 3418 } 3419 3420 fn airClzCtz(cg: *CodeGen, inst: Air.Inst.Index, op: UnaryOp) !?Id { 3421 if (cg.liveness.isUnused(inst)) return null; 3422 3423 const zcu = cg.module.zcu; 3424 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 3425 const operand = try cg.temporary(ty_op.operand); 3426 3427 const scalar_result_ty = cg.typeOfIndex(inst).scalarType(zcu); 3428 3429 const info = cg.arithmeticTypeInfo(operand.ty); 3430 switch (info.class) { 3431 .composite_integer => unreachable, // TODO 3432 .integer, .strange_integer => {}, 3433 .float, .bool => unreachable, 3434 } 3435 3436 const count = try cg.buildUnary(op, operand); 3437 3438 // Result of OpenCL ctz/clz returns operand.ty, and we want result_ty. 3439 // result_ty is always large enough to hold the result, so we might have to down 3440 // cast it. 3441 const result = try cg.buildConvert(scalar_result_ty, count); 3442 return try result.materialize(cg); 3443 } 3444 3445 fn airSelect(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3446 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 3447 const extra = cg.air.extraData(Air.Bin, pl_op.payload).data; 3448 const pred = try cg.temporary(pl_op.operand); 3449 const a = try cg.temporary(extra.lhs); 3450 const b = try cg.temporary(extra.rhs); 3451 3452 const result = try cg.buildSelect(pred, a, b); 3453 return try result.materialize(cg); 3454 } 3455 3456 fn airSplat(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3457 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 3458 3459 const operand_id = try cg.resolve(ty_op.operand); 3460 const result_ty = cg.typeOfIndex(inst); 3461 3462 return try cg.constructCompositeSplat(result_ty, operand_id); 3463 } 3464 3465 fn airReduce(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3466 const zcu = cg.module.zcu; 3467 const reduce = cg.air.instructions.items(.data)[@intFromEnum(inst)].reduce; 3468 const operand = try cg.resolve(reduce.operand); 3469 const operand_ty = cg.typeOf(reduce.operand); 3470 const scalar_ty = operand_ty.scalarType(zcu); 3471 const scalar_ty_id = try cg.resolveType(scalar_ty, .direct); 3472 const info = cg.arithmeticTypeInfo(operand_ty); 3473 const len = operand_ty.vectorLen(zcu); 3474 const first = try cg.extractVectorComponent(scalar_ty, operand, 0); 3475 3476 switch (reduce.operation) { 3477 .Min, .Max => |op| { 3478 var result: Temporary = .init(scalar_ty, first); 3479 const cmp_op: MinMax = switch (op) { 3480 .Max => .max, 3481 .Min => .min, 3482 else => unreachable, 3483 }; 3484 for (1..len) |i| { 3485 const lhs = result; 3486 const rhs_id = try cg.extractVectorComponent(scalar_ty, operand, @intCast(i)); 3487 const rhs: Temporary = .init(scalar_ty, rhs_id); 3488 3489 result = try cg.minMax(lhs, rhs, cmp_op); 3490 } 3491 3492 return try result.materialize(cg); 3493 }, 3494 else => {}, 3495 } 3496 3497 var result_id = first; 3498 3499 const opcode: Opcode = switch (info.class) { 3500 .bool => switch (reduce.operation) { 3501 .And => .OpLogicalAnd, 3502 .Or => .OpLogicalOr, 3503 .Xor => .OpLogicalNotEqual, 3504 else => unreachable, 3505 }, 3506 .strange_integer, .integer => switch (reduce.operation) { 3507 .And => .OpBitwiseAnd, 3508 .Or => .OpBitwiseOr, 3509 .Xor => .OpBitwiseXor, 3510 .Add => .OpIAdd, 3511 .Mul => .OpIMul, 3512 else => unreachable, 3513 }, 3514 .float => switch (reduce.operation) { 3515 .Add => .OpFAdd, 3516 .Mul => .OpFMul, 3517 else => unreachable, 3518 }, 3519 .composite_integer => unreachable, // TODO 3520 }; 3521 3522 for (1..len) |i| { 3523 const lhs = result_id; 3524 const rhs = try cg.extractVectorComponent(scalar_ty, operand, @intCast(i)); 3525 result_id = cg.module.allocId(); 3526 3527 try cg.body.emitRaw(cg.module.gpa, opcode, 4); 3528 cg.body.writeOperand(Id, scalar_ty_id); 3529 cg.body.writeOperand(Id, result_id); 3530 cg.body.writeOperand(Id, lhs); 3531 cg.body.writeOperand(Id, rhs); 3532 } 3533 3534 return result_id; 3535 } 3536 3537 fn airShuffleOne(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3538 const zcu = cg.module.zcu; 3539 const gpa = zcu.gpa; 3540 3541 const unwrapped = cg.air.unwrapShuffleOne(zcu, inst); 3542 const mask = unwrapped.mask; 3543 const result_ty = unwrapped.result_ty; 3544 const elem_ty = result_ty.childType(zcu); 3545 const operand = try cg.resolve(unwrapped.operand); 3546 3547 const scratch_top = cg.id_scratch.items.len; 3548 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); 3549 const constituents = try cg.id_scratch.addManyAsSlice(gpa, mask.len); 3550 3551 for (constituents, mask) |*id, mask_elem| { 3552 id.* = switch (mask_elem.unwrap()) { 3553 .elem => |idx| try cg.extractVectorComponent(elem_ty, operand, idx), 3554 .value => |val| try cg.constant(elem_ty, .fromInterned(val), .direct), 3555 }; 3556 } 3557 3558 const result_ty_id = try cg.resolveType(result_ty, .direct); 3559 return try cg.constructComposite(result_ty_id, constituents); 3560 } 3561 3562 fn airShuffleTwo(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3563 const zcu = cg.module.zcu; 3564 const gpa = zcu.gpa; 3565 3566 const unwrapped = cg.air.unwrapShuffleTwo(zcu, inst); 3567 const mask = unwrapped.mask; 3568 const result_ty = unwrapped.result_ty; 3569 const elem_ty = result_ty.childType(zcu); 3570 const elem_ty_id = try cg.resolveType(elem_ty, .direct); 3571 const operand_a = try cg.resolve(unwrapped.operand_a); 3572 const operand_b = try cg.resolve(unwrapped.operand_b); 3573 3574 const scratch_top = cg.id_scratch.items.len; 3575 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); 3576 const constituents = try cg.id_scratch.addManyAsSlice(gpa, mask.len); 3577 3578 for (constituents, mask) |*id, mask_elem| { 3579 id.* = switch (mask_elem.unwrap()) { 3580 .a_elem => |idx| try cg.extractVectorComponent(elem_ty, operand_a, idx), 3581 .b_elem => |idx| try cg.extractVectorComponent(elem_ty, operand_b, idx), 3582 .undef => try cg.module.constUndef(elem_ty_id), 3583 }; 3584 } 3585 3586 const result_ty_id = try cg.resolveType(result_ty, .direct); 3587 return try cg.constructComposite(result_ty_id, constituents); 3588 } 3589 3590 fn accessChainId( 3591 cg: *CodeGen, 3592 result_ty_id: Id, 3593 base: Id, 3594 indices: []const Id, 3595 ) !Id { 3596 const result_id = cg.module.allocId(); 3597 try cg.body.emit(cg.module.gpa, .OpInBoundsAccessChain, .{ 3598 .id_result_type = result_ty_id, 3599 .id_result = result_id, 3600 .base = base, 3601 .indexes = indices, 3602 }); 3603 return result_id; 3604 } 3605 3606 /// AccessChain is essentially PtrAccessChain with 0 as initial argument. The effective 3607 /// difference lies in whether the resulting type of the first dereference will be the 3608 /// same as that of the base pointer, or that of a dereferenced base pointer. AccessChain 3609 /// is the latter and PtrAccessChain is the former. 3610 fn accessChain( 3611 cg: *CodeGen, 3612 result_ty_id: Id, 3613 base: Id, 3614 indices: []const u32, 3615 ) !Id { 3616 const gpa = cg.module.gpa; 3617 const scratch_top = cg.id_scratch.items.len; 3618 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); 3619 const ids = try cg.id_scratch.addManyAsSlice(gpa, indices.len); 3620 for (indices, ids) |index, *id| { 3621 id.* = try cg.constInt(.u32, index); 3622 } 3623 return try cg.accessChainId(result_ty_id, base, ids); 3624 } 3625 3626 fn ptrAccessChain( 3627 cg: *CodeGen, 3628 result_ty_id: Id, 3629 base: Id, 3630 element: Id, 3631 indices: []const u32, 3632 ) !Id { 3633 const gpa = cg.module.gpa; 3634 const target = cg.module.zcu.getTarget(); 3635 3636 const scratch_top = cg.id_scratch.items.len; 3637 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); 3638 const ids = try cg.id_scratch.addManyAsSlice(gpa, indices.len); 3639 for (indices, ids) |index, *id| { 3640 id.* = try cg.constInt(.u32, index); 3641 } 3642 3643 const result_id = cg.module.allocId(); 3644 switch (target.os.tag) { 3645 .opencl, .amdhsa => { 3646 try cg.body.emit(gpa, .OpInBoundsPtrAccessChain, .{ 3647 .id_result_type = result_ty_id, 3648 .id_result = result_id, 3649 .base = base, 3650 .element = element, 3651 .indexes = ids, 3652 }); 3653 }, 3654 .vulkan, .opengl => { 3655 try cg.body.emit(gpa, .OpPtrAccessChain, .{ 3656 .id_result_type = result_ty_id, 3657 .id_result = result_id, 3658 .base = base, 3659 .element = element, 3660 .indexes = ids, 3661 }); 3662 }, 3663 else => unreachable, 3664 } 3665 return result_id; 3666 } 3667 3668 fn ptrAdd(cg: *CodeGen, result_ty: Type, ptr_ty: Type, ptr_id: Id, offset_id: Id) !Id { 3669 const zcu = cg.module.zcu; 3670 const result_ty_id = try cg.resolveType(result_ty, .direct); 3671 3672 switch (ptr_ty.ptrSize(zcu)) { 3673 .one => { 3674 // Pointer to array 3675 // TODO: Is this correct? 3676 return try cg.accessChainId(result_ty_id, ptr_id, &.{offset_id}); 3677 }, 3678 .c, .many => { 3679 return try cg.ptrAccessChain(result_ty_id, ptr_id, offset_id, &.{}); 3680 }, 3681 .slice => { 3682 // TODO: This is probably incorrect. A slice should be returned here, though this is what llvm does. 3683 const slice_ptr_id = try cg.extractField(result_ty, ptr_id, 0); 3684 return try cg.ptrAccessChain(result_ty_id, slice_ptr_id, offset_id, &.{}); 3685 }, 3686 } 3687 } 3688 3689 fn airPtrAdd(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3690 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 3691 const bin_op = cg.air.extraData(Air.Bin, ty_pl.payload).data; 3692 const ptr_id = try cg.resolve(bin_op.lhs); 3693 const offset_id = try cg.resolve(bin_op.rhs); 3694 const ptr_ty = cg.typeOf(bin_op.lhs); 3695 const result_ty = cg.typeOfIndex(inst); 3696 3697 return try cg.ptrAdd(result_ty, ptr_ty, ptr_id, offset_id); 3698 } 3699 3700 fn airPtrSub(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3701 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 3702 const bin_op = cg.air.extraData(Air.Bin, ty_pl.payload).data; 3703 const ptr_id = try cg.resolve(bin_op.lhs); 3704 const ptr_ty = cg.typeOf(bin_op.lhs); 3705 const offset_id = try cg.resolve(bin_op.rhs); 3706 const offset_ty = cg.typeOf(bin_op.rhs); 3707 const offset_ty_id = try cg.resolveType(offset_ty, .direct); 3708 const result_ty = cg.typeOfIndex(inst); 3709 3710 const negative_offset_id = cg.module.allocId(); 3711 try cg.body.emit(cg.module.gpa, .OpSNegate, .{ 3712 .id_result_type = offset_ty_id, 3713 .id_result = negative_offset_id, 3714 .operand = offset_id, 3715 }); 3716 return try cg.ptrAdd(result_ty, ptr_ty, ptr_id, negative_offset_id); 3717 } 3718 3719 fn cmp( 3720 cg: *CodeGen, 3721 op: std.math.CompareOperator, 3722 lhs: Temporary, 3723 rhs: Temporary, 3724 ) !Temporary { 3725 const gpa = cg.module.gpa; 3726 const pt = cg.pt; 3727 const zcu = cg.module.zcu; 3728 const ip = &zcu.intern_pool; 3729 const scalar_ty = lhs.ty.scalarType(zcu); 3730 const is_vector = lhs.ty.isVector(zcu); 3731 3732 switch (scalar_ty.zigTypeTag(zcu)) { 3733 .int, .bool, .float => {}, 3734 .@"enum" => { 3735 assert(!is_vector); 3736 const ty = lhs.ty.intTagType(zcu); 3737 return try cg.cmp(op, lhs.pun(ty), rhs.pun(ty)); 3738 }, 3739 .@"struct" => { 3740 const struct_ty = zcu.typeToPackedStruct(scalar_ty).?; 3741 const ty: Type = .fromInterned(struct_ty.backingIntTypeUnordered(ip)); 3742 return try cg.cmp(op, lhs.pun(ty), rhs.pun(ty)); 3743 }, 3744 .error_set => { 3745 assert(!is_vector); 3746 const err_int_ty = try pt.errorIntType(); 3747 return try cg.cmp(op, lhs.pun(err_int_ty), rhs.pun(err_int_ty)); 3748 }, 3749 .pointer => { 3750 assert(!is_vector); 3751 // Note that while SPIR-V offers OpPtrEqual and OpPtrNotEqual, they are 3752 // currently not implemented in the SPIR-V LLVM translator. Thus, we emit these using 3753 // OpConvertPtrToU... 3754 3755 const usize_ty_id = try cg.resolveType(.usize, .direct); 3756 3757 const lhs_int_id = cg.module.allocId(); 3758 try cg.body.emit(gpa, .OpConvertPtrToU, .{ 3759 .id_result_type = usize_ty_id, 3760 .id_result = lhs_int_id, 3761 .pointer = try lhs.materialize(cg), 3762 }); 3763 3764 const rhs_int_id = cg.module.allocId(); 3765 try cg.body.emit(gpa, .OpConvertPtrToU, .{ 3766 .id_result_type = usize_ty_id, 3767 .id_result = rhs_int_id, 3768 .pointer = try rhs.materialize(cg), 3769 }); 3770 3771 const lhs_int: Temporary = .init(.usize, lhs_int_id); 3772 const rhs_int: Temporary = .init(.usize, rhs_int_id); 3773 return try cg.cmp(op, lhs_int, rhs_int); 3774 }, 3775 .optional => { 3776 assert(!is_vector); 3777 3778 const ty = lhs.ty; 3779 3780 const payload_ty = ty.optionalChild(zcu); 3781 if (ty.optionalReprIsPayload(zcu)) { 3782 assert(payload_ty.hasRuntimeBitsIgnoreComptime(zcu)); 3783 assert(!payload_ty.isSlice(zcu)); 3784 3785 return try cg.cmp(op, lhs.pun(payload_ty), rhs.pun(payload_ty)); 3786 } 3787 3788 const lhs_id = try lhs.materialize(cg); 3789 const rhs_id = try rhs.materialize(cg); 3790 3791 const lhs_valid_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) 3792 try cg.extractField(.bool, lhs_id, 1) 3793 else 3794 try cg.convertToDirect(.bool, lhs_id); 3795 3796 const rhs_valid_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) 3797 try cg.extractField(.bool, rhs_id, 1) 3798 else 3799 try cg.convertToDirect(.bool, rhs_id); 3800 3801 const lhs_valid: Temporary = .init(.bool, lhs_valid_id); 3802 const rhs_valid: Temporary = .init(.bool, rhs_valid_id); 3803 3804 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) { 3805 return try cg.cmp(op, lhs_valid, rhs_valid); 3806 } 3807 3808 // a = lhs_valid 3809 // b = rhs_valid 3810 // c = lhs_pl == rhs_pl 3811 // 3812 // For op == .eq we have: 3813 // a == b && a -> c 3814 // = a == b && (!a || c) 3815 // 3816 // For op == .neq we have 3817 // a == b && a -> c 3818 // = !(a == b && a -> c) 3819 // = a != b || !(a -> c 3820 // = a != b || !(!a || c) 3821 // = a != b || a && !c 3822 3823 const lhs_pl_id = try cg.extractField(payload_ty, lhs_id, 0); 3824 const rhs_pl_id = try cg.extractField(payload_ty, rhs_id, 0); 3825 3826 const lhs_pl: Temporary = .init(payload_ty, lhs_pl_id); 3827 const rhs_pl: Temporary = .init(payload_ty, rhs_pl_id); 3828 3829 return switch (op) { 3830 .eq => try cg.buildBinary( 3831 .OpLogicalAnd, 3832 try cg.cmp(.eq, lhs_valid, rhs_valid), 3833 try cg.buildBinary( 3834 .OpLogicalOr, 3835 try cg.buildUnary(.l_not, lhs_valid), 3836 try cg.cmp(.eq, lhs_pl, rhs_pl), 3837 ), 3838 ), 3839 .neq => try cg.buildBinary( 3840 .OpLogicalOr, 3841 try cg.cmp(.neq, lhs_valid, rhs_valid), 3842 try cg.buildBinary( 3843 .OpLogicalAnd, 3844 lhs_valid, 3845 try cg.cmp(.neq, lhs_pl, rhs_pl), 3846 ), 3847 ), 3848 else => unreachable, 3849 }; 3850 }, 3851 else => |ty| return cg.todo("implement cmp operation for '{s}' type", .{@tagName(ty)}), 3852 } 3853 3854 const info = cg.arithmeticTypeInfo(scalar_ty); 3855 const pred: Opcode = switch (info.class) { 3856 .composite_integer => unreachable, // TODO 3857 .float => switch (op) { 3858 .eq => .OpFOrdEqual, 3859 .neq => .OpFUnordNotEqual, 3860 .lt => .OpFOrdLessThan, 3861 .lte => .OpFOrdLessThanEqual, 3862 .gt => .OpFOrdGreaterThan, 3863 .gte => .OpFOrdGreaterThanEqual, 3864 }, 3865 .bool => switch (op) { 3866 .eq => .OpLogicalEqual, 3867 .neq => .OpLogicalNotEqual, 3868 else => unreachable, 3869 }, 3870 .integer, .strange_integer => switch (info.signedness) { 3871 .signed => switch (op) { 3872 .eq => .OpIEqual, 3873 .neq => .OpINotEqual, 3874 .lt => .OpSLessThan, 3875 .lte => .OpSLessThanEqual, 3876 .gt => .OpSGreaterThan, 3877 .gte => .OpSGreaterThanEqual, 3878 }, 3879 .unsigned => switch (op) { 3880 .eq => .OpIEqual, 3881 .neq => .OpINotEqual, 3882 .lt => .OpULessThan, 3883 .lte => .OpULessThanEqual, 3884 .gt => .OpUGreaterThan, 3885 .gte => .OpUGreaterThanEqual, 3886 }, 3887 }, 3888 }; 3889 3890 return try cg.buildCmp(pred, lhs, rhs); 3891 } 3892 3893 fn airCmp( 3894 cg: *CodeGen, 3895 inst: Air.Inst.Index, 3896 comptime op: std.math.CompareOperator, 3897 ) !?Id { 3898 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 3899 const lhs = try cg.temporary(bin_op.lhs); 3900 const rhs = try cg.temporary(bin_op.rhs); 3901 3902 const result = try cg.cmp(op, lhs, rhs); 3903 return try result.materialize(cg); 3904 } 3905 3906 fn airVectorCmp(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3907 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 3908 const vec_cmp = cg.air.extraData(Air.VectorCmp, ty_pl.payload).data; 3909 const lhs = try cg.temporary(vec_cmp.lhs); 3910 const rhs = try cg.temporary(vec_cmp.rhs); 3911 const op = vec_cmp.compareOperator(); 3912 3913 const result = try cg.cmp(op, lhs, rhs); 3914 return try result.materialize(cg); 3915 } 3916 3917 /// Bitcast one type to another. Note: both types, input, output are expected in **direct** representation. 3918 fn bitCast( 3919 cg: *CodeGen, 3920 dst_ty: Type, 3921 src_ty: Type, 3922 src_id: Id, 3923 ) !Id { 3924 const gpa = cg.module.gpa; 3925 const zcu = cg.module.zcu; 3926 const target = zcu.getTarget(); 3927 const src_ty_id = try cg.resolveType(src_ty, .direct); 3928 const dst_ty_id = try cg.resolveType(dst_ty, .direct); 3929 3930 const result_id = blk: { 3931 if (src_ty_id == dst_ty_id) break :blk src_id; 3932 3933 // TODO: Some more cases are missing here 3934 // See fn bitCast in llvm.zig 3935 3936 if (src_ty.zigTypeTag(zcu) == .int and dst_ty.isPtrAtRuntime(zcu)) { 3937 if (target.os.tag != .opencl) { 3938 if (dst_ty.ptrAddressSpace(zcu) != .physical_storage_buffer) { 3939 return cg.fail( 3940 "cannot cast integer to pointer with address space '{s}'", 3941 .{@tagName(dst_ty.ptrAddressSpace(zcu))}, 3942 ); 3943 } 3944 } 3945 3946 const result_id = cg.module.allocId(); 3947 try cg.body.emit(gpa, .OpConvertUToPtr, .{ 3948 .id_result_type = dst_ty_id, 3949 .id_result = result_id, 3950 .integer_value = src_id, 3951 }); 3952 break :blk result_id; 3953 } 3954 3955 // We can only use OpBitcast for specific conversions: between numerical types, and 3956 // between pointers. If the resolved spir-v types fall into this category then emit OpBitcast, 3957 // otherwise use a temporary and perform a pointer cast. 3958 const can_bitcast = (src_ty.isNumeric(zcu) and dst_ty.isNumeric(zcu)) or (src_ty.isPtrAtRuntime(zcu) and dst_ty.isPtrAtRuntime(zcu)); 3959 if (can_bitcast) { 3960 const result_id = cg.module.allocId(); 3961 try cg.body.emit(gpa, .OpBitcast, .{ 3962 .id_result_type = dst_ty_id, 3963 .id_result = result_id, 3964 .operand = src_id, 3965 }); 3966 3967 break :blk result_id; 3968 } 3969 3970 const dst_ptr_ty_id = try cg.module.ptrType(dst_ty_id, .function); 3971 3972 const src_ty_indirect_id = try cg.resolveType(src_ty, .indirect); 3973 const tmp_id = try cg.alloc(src_ty_indirect_id, null); 3974 try cg.store(src_ty, tmp_id, src_id, .{}); 3975 const casted_ptr_id = cg.module.allocId(); 3976 try cg.body.emit(gpa, .OpBitcast, .{ 3977 .id_result_type = dst_ptr_ty_id, 3978 .id_result = casted_ptr_id, 3979 .operand = tmp_id, 3980 }); 3981 break :blk try cg.load(dst_ty, casted_ptr_id, .{}); 3982 }; 3983 3984 // Because strange integers use sign-extended representation, we may need to normalize 3985 // the result here. 3986 // TODO: This detail could cause stuff like @as(*const i1, @ptrCast(&@as(u1, 1))) to break 3987 // should we change the representation of strange integers? 3988 if (dst_ty.zigTypeTag(zcu) == .int) { 3989 const info = cg.arithmeticTypeInfo(dst_ty); 3990 const result = try cg.normalize(Temporary.init(dst_ty, result_id), info); 3991 return try result.materialize(cg); 3992 } 3993 3994 return result_id; 3995 } 3996 3997 fn airBitCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 3998 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 3999 const operand_ty = cg.typeOf(ty_op.operand); 4000 const result_ty = cg.typeOfIndex(inst); 4001 if (operand_ty.toIntern() == .bool_type) { 4002 const operand = try cg.temporary(ty_op.operand); 4003 const result = try cg.intFromBool(operand, .u1); 4004 return try result.materialize(cg); 4005 } 4006 const operand_id = try cg.resolve(ty_op.operand); 4007 return try cg.bitCast(result_ty, operand_ty, operand_id); 4008 } 4009 4010 fn airIntCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4011 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4012 const src = try cg.temporary(ty_op.operand); 4013 const dst_ty = cg.typeOfIndex(inst); 4014 4015 const src_info = cg.arithmeticTypeInfo(src.ty); 4016 const dst_info = cg.arithmeticTypeInfo(dst_ty); 4017 4018 if (src_info.backing_bits == dst_info.backing_bits) { 4019 return try src.materialize(cg); 4020 } 4021 4022 const converted = try cg.buildConvert(dst_ty, src); 4023 4024 // Make sure to normalize the result if shrinking. 4025 // Because strange ints are sign extended in their backing 4026 // type, we don't need to normalize when growing the type. The 4027 // representation is already the same. 4028 const result = if (dst_info.bits < src_info.bits) 4029 try cg.normalize(converted, dst_info) 4030 else 4031 converted; 4032 4033 return try result.materialize(cg); 4034 } 4035 4036 fn intFromPtr(cg: *CodeGen, operand_id: Id) !Id { 4037 const result_type_id = try cg.resolveType(.usize, .direct); 4038 const result_id = cg.module.allocId(); 4039 try cg.body.emit(cg.module.gpa, .OpConvertPtrToU, .{ 4040 .id_result_type = result_type_id, 4041 .id_result = result_id, 4042 .pointer = operand_id, 4043 }); 4044 return result_id; 4045 } 4046 4047 fn airFloatFromInt(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4048 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4049 const operand_ty = cg.typeOf(ty_op.operand); 4050 const operand_id = try cg.resolve(ty_op.operand); 4051 const result_ty = cg.typeOfIndex(inst); 4052 return try cg.floatFromInt(result_ty, operand_ty, operand_id); 4053 } 4054 4055 fn floatFromInt(cg: *CodeGen, result_ty: Type, operand_ty: Type, operand_id: Id) !Id { 4056 const gpa = cg.module.gpa; 4057 const operand_info = cg.arithmeticTypeInfo(operand_ty); 4058 const result_id = cg.module.allocId(); 4059 const result_ty_id = try cg.resolveType(result_ty, .direct); 4060 switch (operand_info.signedness) { 4061 .signed => try cg.body.emit(gpa, .OpConvertSToF, .{ 4062 .id_result_type = result_ty_id, 4063 .id_result = result_id, 4064 .signed_value = operand_id, 4065 }), 4066 .unsigned => try cg.body.emit(gpa, .OpConvertUToF, .{ 4067 .id_result_type = result_ty_id, 4068 .id_result = result_id, 4069 .unsigned_value = operand_id, 4070 }), 4071 } 4072 return result_id; 4073 } 4074 4075 fn airIntFromFloat(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4076 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4077 const operand_id = try cg.resolve(ty_op.operand); 4078 const result_ty = cg.typeOfIndex(inst); 4079 return try cg.intFromFloat(result_ty, operand_id); 4080 } 4081 4082 fn intFromFloat(cg: *CodeGen, result_ty: Type, operand_id: Id) !Id { 4083 const gpa = cg.module.gpa; 4084 const result_info = cg.arithmeticTypeInfo(result_ty); 4085 const result_ty_id = try cg.resolveType(result_ty, .direct); 4086 const result_id = cg.module.allocId(); 4087 switch (result_info.signedness) { 4088 .signed => try cg.body.emit(gpa, .OpConvertFToS, .{ 4089 .id_result_type = result_ty_id, 4090 .id_result = result_id, 4091 .float_value = operand_id, 4092 }), 4093 .unsigned => try cg.body.emit(gpa, .OpConvertFToU, .{ 4094 .id_result_type = result_ty_id, 4095 .id_result = result_id, 4096 .float_value = operand_id, 4097 }), 4098 } 4099 return result_id; 4100 } 4101 4102 fn airFloatCast(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4103 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4104 const operand = try cg.temporary(ty_op.operand); 4105 const dest_ty = cg.typeOfIndex(inst); 4106 const result = try cg.buildConvert(dest_ty, operand); 4107 return try result.materialize(cg); 4108 } 4109 4110 fn airNot(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4111 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4112 const operand = try cg.temporary(ty_op.operand); 4113 const result_ty = cg.typeOfIndex(inst); 4114 const info = cg.arithmeticTypeInfo(result_ty); 4115 4116 const result = switch (info.class) { 4117 .bool => try cg.buildUnary(.l_not, operand), 4118 .float => unreachable, 4119 .composite_integer => unreachable, // TODO 4120 .strange_integer, .integer => blk: { 4121 const complement = try cg.buildUnary(.bit_not, operand); 4122 break :blk try cg.normalize(complement, info); 4123 }, 4124 }; 4125 4126 return try result.materialize(cg); 4127 } 4128 4129 fn airArrayToSlice(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4130 const zcu = cg.module.zcu; 4131 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4132 const array_ptr_ty = cg.typeOf(ty_op.operand); 4133 const array_ty = array_ptr_ty.childType(zcu); 4134 const slice_ty = cg.typeOfIndex(inst); 4135 const elem_ptr_ty = slice_ty.slicePtrFieldType(zcu); 4136 4137 const elem_ptr_ty_id = try cg.resolveType(elem_ptr_ty, .direct); 4138 4139 const array_ptr_id = try cg.resolve(ty_op.operand); 4140 const len_id = try cg.constInt(.usize, array_ty.arrayLen(zcu)); 4141 4142 const elem_ptr_id = if (!array_ty.hasRuntimeBitsIgnoreComptime(zcu)) 4143 // Note: The pointer is something like *opaque{}, so we need to bitcast it to the element type. 4144 try cg.bitCast(elem_ptr_ty, array_ptr_ty, array_ptr_id) 4145 else 4146 // Convert the pointer-to-array to a pointer to the first element. 4147 try cg.accessChain(elem_ptr_ty_id, array_ptr_id, &.{0}); 4148 4149 const slice_ty_id = try cg.resolveType(slice_ty, .direct); 4150 return try cg.constructComposite(slice_ty_id, &.{ elem_ptr_id, len_id }); 4151 } 4152 4153 fn airSlice(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4154 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 4155 const bin_op = cg.air.extraData(Air.Bin, ty_pl.payload).data; 4156 const ptr_id = try cg.resolve(bin_op.lhs); 4157 const len_id = try cg.resolve(bin_op.rhs); 4158 const slice_ty = cg.typeOfIndex(inst); 4159 const slice_ty_id = try cg.resolveType(slice_ty, .direct); 4160 return try cg.constructComposite(slice_ty_id, &.{ ptr_id, len_id }); 4161 } 4162 4163 fn airAggregateInit(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4164 const gpa = cg.module.gpa; 4165 const pt = cg.pt; 4166 const zcu = cg.module.zcu; 4167 const ip = &zcu.intern_pool; 4168 const target = cg.module.zcu.getTarget(); 4169 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 4170 const result_ty = cg.typeOfIndex(inst); 4171 const len: usize = @intCast(result_ty.arrayLen(zcu)); 4172 const elements: []const Air.Inst.Ref = @ptrCast(cg.air.extra.items[ty_pl.payload..][0..len]); 4173 4174 switch (result_ty.zigTypeTag(zcu)) { 4175 .@"struct" => { 4176 if (zcu.typeToPackedStruct(result_ty)) |struct_type| { 4177 comptime assert(Type.packed_struct_layout_version == 2); 4178 const backing_int_ty: Type = .fromInterned(struct_type.backingIntTypeUnordered(ip)); 4179 var running_int_id = try cg.constInt(backing_int_ty, 0); 4180 var running_bits: u16 = 0; 4181 for (struct_type.field_types.get(ip), elements) |field_ty_ip, element| { 4182 const field_ty: Type = .fromInterned(field_ty_ip); 4183 if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue; 4184 const field_id = try cg.resolve(element); 4185 const ty_bit_size: u16 = @intCast(field_ty.bitSize(zcu)); 4186 const field_int_ty = try cg.pt.intType(.unsigned, ty_bit_size); 4187 const field_int_id = blk: { 4188 if (field_ty.isPtrAtRuntime(zcu)) { 4189 assert(target.cpu.arch == .spirv64 and 4190 field_ty.ptrAddressSpace(zcu) == .storage_buffer); 4191 break :blk try cg.intFromPtr(field_id); 4192 } 4193 break :blk try cg.bitCast(field_int_ty, field_ty, field_id); 4194 }; 4195 const shift_rhs = try cg.constInt(backing_int_ty, running_bits); 4196 const extended_int_conv = try cg.buildConvert(backing_int_ty, .{ 4197 .ty = field_int_ty, 4198 .value = .{ .singleton = field_int_id }, 4199 }); 4200 const shifted = try cg.buildBinary(.OpShiftLeftLogical, extended_int_conv, .{ 4201 .ty = backing_int_ty, 4202 .value = .{ .singleton = shift_rhs }, 4203 }); 4204 const running_int_tmp = try cg.buildBinary( 4205 .OpBitwiseOr, 4206 .{ .ty = backing_int_ty, .value = .{ .singleton = running_int_id } }, 4207 shifted, 4208 ); 4209 running_int_id = try running_int_tmp.materialize(cg); 4210 running_bits += ty_bit_size; 4211 } 4212 return running_int_id; 4213 } 4214 4215 const scratch_top = cg.id_scratch.items.len; 4216 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); 4217 const constituents = try cg.id_scratch.addManyAsSlice(gpa, elements.len); 4218 4219 const types = try gpa.alloc(Type, elements.len); 4220 defer gpa.free(types); 4221 4222 var index: usize = 0; 4223 4224 switch (ip.indexToKey(result_ty.toIntern())) { 4225 .tuple_type => |tuple| { 4226 for (tuple.types.get(ip), elements, 0..) |field_ty, element, i| { 4227 if ((try result_ty.structFieldValueComptime(pt, i)) != null) continue; 4228 assert(Type.fromInterned(field_ty).hasRuntimeBits(zcu)); 4229 4230 const id = try cg.resolve(element); 4231 types[index] = .fromInterned(field_ty); 4232 constituents[index] = try cg.convertToIndirect(.fromInterned(field_ty), id); 4233 index += 1; 4234 } 4235 }, 4236 .struct_type => { 4237 const struct_type = ip.loadStructType(result_ty.toIntern()); 4238 var it = struct_type.iterateRuntimeOrder(ip); 4239 for (elements, 0..) |element, i| { 4240 const field_index = it.next().?; 4241 if ((try result_ty.structFieldValueComptime(pt, i)) != null) continue; 4242 const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]); 4243 assert(field_ty.hasRuntimeBitsIgnoreComptime(zcu)); 4244 4245 const id = try cg.resolve(element); 4246 types[index] = field_ty; 4247 constituents[index] = try cg.convertToIndirect(field_ty, id); 4248 index += 1; 4249 } 4250 }, 4251 else => unreachable, 4252 } 4253 4254 const result_ty_id = try cg.resolveType(result_ty, .direct); 4255 return try cg.constructComposite(result_ty_id, constituents[0..index]); 4256 }, 4257 .vector => { 4258 const n_elems = result_ty.vectorLen(zcu); 4259 const scratch_top = cg.id_scratch.items.len; 4260 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); 4261 const elem_ids = try cg.id_scratch.addManyAsSlice(gpa, n_elems); 4262 4263 for (elements, 0..) |element, i| { 4264 elem_ids[i] = try cg.resolve(element); 4265 } 4266 4267 const result_ty_id = try cg.resolveType(result_ty, .direct); 4268 return try cg.constructComposite(result_ty_id, elem_ids); 4269 }, 4270 .array => { 4271 const array_info = result_ty.arrayInfo(zcu); 4272 const n_elems: usize = @intCast(result_ty.arrayLenIncludingSentinel(zcu)); 4273 const scratch_top = cg.id_scratch.items.len; 4274 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); 4275 const elem_ids = try cg.id_scratch.addManyAsSlice(gpa, n_elems); 4276 4277 for (elements, 0..) |element, i| { 4278 const id = try cg.resolve(element); 4279 elem_ids[i] = try cg.convertToIndirect(array_info.elem_type, id); 4280 } 4281 4282 if (array_info.sentinel) |sentinel_val| { 4283 elem_ids[n_elems - 1] = try cg.constant(array_info.elem_type, sentinel_val, .indirect); 4284 } 4285 4286 const result_ty_id = try cg.resolveType(result_ty, .direct); 4287 return try cg.constructComposite(result_ty_id, elem_ids); 4288 }, 4289 else => unreachable, 4290 } 4291 } 4292 4293 fn sliceOrArrayLen(cg: *CodeGen, operand_id: Id, ty: Type) !Id { 4294 const zcu = cg.module.zcu; 4295 switch (ty.ptrSize(zcu)) { 4296 .slice => return cg.extractField(.usize, operand_id, 1), 4297 .one => { 4298 const array_ty = ty.childType(zcu); 4299 const elem_ty = array_ty.childType(zcu); 4300 const abi_size = elem_ty.abiSize(zcu); 4301 const size = array_ty.arrayLenIncludingSentinel(zcu) * abi_size; 4302 return try cg.constInt(.usize, size); 4303 }, 4304 .many, .c => unreachable, 4305 } 4306 } 4307 4308 fn sliceOrArrayPtr(cg: *CodeGen, operand_id: Id, ty: Type) !Id { 4309 const zcu = cg.module.zcu; 4310 if (ty.isSlice(zcu)) { 4311 const ptr_ty = ty.slicePtrFieldType(zcu); 4312 return cg.extractField(ptr_ty, operand_id, 0); 4313 } 4314 return operand_id; 4315 } 4316 4317 fn airMemcpy(cg: *CodeGen, inst: Air.Inst.Index) !void { 4318 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 4319 const dest_slice = try cg.resolve(bin_op.lhs); 4320 const src_slice = try cg.resolve(bin_op.rhs); 4321 const dest_ty = cg.typeOf(bin_op.lhs); 4322 const src_ty = cg.typeOf(bin_op.rhs); 4323 const dest_ptr = try cg.sliceOrArrayPtr(dest_slice, dest_ty); 4324 const src_ptr = try cg.sliceOrArrayPtr(src_slice, src_ty); 4325 const len = try cg.sliceOrArrayLen(dest_slice, dest_ty); 4326 try cg.body.emit(cg.module.gpa, .OpCopyMemorySized, .{ 4327 .target = dest_ptr, 4328 .source = src_ptr, 4329 .size = len, 4330 }); 4331 } 4332 4333 fn airMemmove(cg: *CodeGen, inst: Air.Inst.Index) !void { 4334 _ = inst; 4335 return cg.fail("TODO implement airMemcpy for spirv", .{}); 4336 } 4337 4338 fn airSliceField(cg: *CodeGen, inst: Air.Inst.Index, field: u32) !?Id { 4339 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4340 const field_ty = cg.typeOfIndex(inst); 4341 const operand_id = try cg.resolve(ty_op.operand); 4342 return try cg.extractField(field_ty, operand_id, field); 4343 } 4344 4345 fn airSliceElemPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4346 const zcu = cg.module.zcu; 4347 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 4348 const bin_op = cg.air.extraData(Air.Bin, ty_pl.payload).data; 4349 const slice_ty = cg.typeOf(bin_op.lhs); 4350 if (!slice_ty.isVolatilePtr(zcu) and cg.liveness.isUnused(inst)) return null; 4351 4352 const slice_id = try cg.resolve(bin_op.lhs); 4353 const index_id = try cg.resolve(bin_op.rhs); 4354 4355 const ptr_ty = cg.typeOfIndex(inst); 4356 const ptr_ty_id = try cg.resolveType(ptr_ty, .direct); 4357 4358 const slice_ptr = try cg.extractField(ptr_ty, slice_id, 0); 4359 return try cg.ptrAccessChain(ptr_ty_id, slice_ptr, index_id, &.{}); 4360 } 4361 4362 fn airSliceElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4363 const zcu = cg.module.zcu; 4364 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 4365 const slice_ty = cg.typeOf(bin_op.lhs); 4366 if (!slice_ty.isVolatilePtr(zcu) and cg.liveness.isUnused(inst)) return null; 4367 4368 const slice_id = try cg.resolve(bin_op.lhs); 4369 const index_id = try cg.resolve(bin_op.rhs); 4370 4371 const ptr_ty = slice_ty.slicePtrFieldType(zcu); 4372 const ptr_ty_id = try cg.resolveType(ptr_ty, .direct); 4373 4374 const slice_ptr = try cg.extractField(ptr_ty, slice_id, 0); 4375 const elem_ptr = try cg.ptrAccessChain(ptr_ty_id, slice_ptr, index_id, &.{}); 4376 return try cg.load(slice_ty.childType(zcu), elem_ptr, .{ .is_volatile = slice_ty.isVolatilePtr(zcu) }); 4377 } 4378 4379 fn ptrElemPtr(cg: *CodeGen, ptr_ty: Type, ptr_id: Id, index_id: Id) !Id { 4380 const zcu = cg.module.zcu; 4381 // Construct new pointer type for the resulting pointer 4382 const elem_ty = ptr_ty.elemType2(zcu); // use elemType() so that we get T for *[N]T. 4383 const elem_ty_id = try cg.resolveType(elem_ty, .indirect); 4384 const elem_ptr_ty_id = try cg.module.ptrType(elem_ty_id, cg.module.storageClass(ptr_ty.ptrAddressSpace(zcu))); 4385 if (ptr_ty.isSinglePointer(zcu)) { 4386 // Pointer-to-array. In this case, the resulting pointer is not of the same type 4387 // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain. 4388 return try cg.accessChainId(elem_ptr_ty_id, ptr_id, &.{index_id}); 4389 } else { 4390 // Resulting pointer type is the same as the ptr_ty, so use ptrAccessChain 4391 return try cg.ptrAccessChain(elem_ptr_ty_id, ptr_id, index_id, &.{}); 4392 } 4393 } 4394 4395 fn airPtrElemPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4396 const zcu = cg.module.zcu; 4397 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 4398 const bin_op = cg.air.extraData(Air.Bin, ty_pl.payload).data; 4399 const src_ptr_ty = cg.typeOf(bin_op.lhs); 4400 const elem_ty = src_ptr_ty.childType(zcu); 4401 const ptr_id = try cg.resolve(bin_op.lhs); 4402 4403 if (!elem_ty.hasRuntimeBitsIgnoreComptime(zcu)) { 4404 const dst_ptr_ty = cg.typeOfIndex(inst); 4405 return try cg.bitCast(dst_ptr_ty, src_ptr_ty, ptr_id); 4406 } 4407 4408 const index_id = try cg.resolve(bin_op.rhs); 4409 return try cg.ptrElemPtr(src_ptr_ty, ptr_id, index_id); 4410 } 4411 4412 fn airArrayElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4413 const gpa = cg.module.gpa; 4414 const zcu = cg.module.zcu; 4415 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 4416 const array_ty = cg.typeOf(bin_op.lhs); 4417 const elem_ty = array_ty.childType(zcu); 4418 const array_id = try cg.resolve(bin_op.lhs); 4419 const index_id = try cg.resolve(bin_op.rhs); 4420 4421 // SPIR-V doesn't have an array indexing function for some damn reason. 4422 // For now, just generate a temporary and use that. 4423 // TODO: This backend probably also should use isByRef from llvm... 4424 4425 const is_vector = array_ty.isVector(zcu); 4426 const elem_repr: Repr = if (is_vector) .direct else .indirect; 4427 const array_ty_id = try cg.resolveType(array_ty, .direct); 4428 const elem_ty_id = try cg.resolveType(elem_ty, elem_repr); 4429 const ptr_array_ty_id = try cg.module.ptrType(array_ty_id, .function); 4430 const ptr_elem_ty_id = try cg.module.ptrType(elem_ty_id, .function); 4431 4432 const tmp_id = cg.module.allocId(); 4433 try cg.prologue.emit(gpa, .OpVariable, .{ 4434 .id_result_type = ptr_array_ty_id, 4435 .id_result = tmp_id, 4436 .storage_class = .function, 4437 }); 4438 4439 try cg.body.emit(gpa, .OpStore, .{ 4440 .pointer = tmp_id, 4441 .object = array_id, 4442 }); 4443 4444 const elem_ptr_id = try cg.accessChainId(ptr_elem_ty_id, tmp_id, &.{index_id}); 4445 4446 const result_id = cg.module.allocId(); 4447 try cg.body.emit(gpa, .OpLoad, .{ 4448 .id_result_type = try cg.resolveType(elem_ty, elem_repr), 4449 .id_result = result_id, 4450 .pointer = elem_ptr_id, 4451 }); 4452 4453 if (is_vector) { 4454 // Result is already in direct representation 4455 return result_id; 4456 } 4457 4458 // This is an array type; the elements are stored in indirect representation. 4459 // We have to convert the type to direct. 4460 4461 return try cg.convertToDirect(elem_ty, result_id); 4462 } 4463 4464 fn airPtrElemVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4465 const zcu = cg.module.zcu; 4466 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 4467 const ptr_ty = cg.typeOf(bin_op.lhs); 4468 const elem_ty = cg.typeOfIndex(inst); 4469 const ptr_id = try cg.resolve(bin_op.lhs); 4470 const index_id = try cg.resolve(bin_op.rhs); 4471 const elem_ptr_id = try cg.ptrElemPtr(ptr_ty, ptr_id, index_id); 4472 return try cg.load(elem_ty, elem_ptr_id, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) }); 4473 } 4474 4475 fn airSetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !void { 4476 const zcu = cg.module.zcu; 4477 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 4478 const un_ptr_ty = cg.typeOf(bin_op.lhs); 4479 const un_ty = un_ptr_ty.childType(zcu); 4480 const layout = cg.unionLayout(un_ty); 4481 4482 if (layout.tag_size == 0) return; 4483 4484 const tag_ty = un_ty.unionTagTypeSafety(zcu).?; 4485 const tag_ty_id = try cg.resolveType(tag_ty, .indirect); 4486 const tag_ptr_ty_id = try cg.module.ptrType(tag_ty_id, cg.module.storageClass(un_ptr_ty.ptrAddressSpace(zcu))); 4487 4488 const union_ptr_id = try cg.resolve(bin_op.lhs); 4489 const new_tag_id = try cg.resolve(bin_op.rhs); 4490 4491 if (!layout.has_payload) { 4492 try cg.store(tag_ty, union_ptr_id, new_tag_id, .{ .is_volatile = un_ptr_ty.isVolatilePtr(zcu) }); 4493 } else { 4494 const ptr_id = try cg.accessChain(tag_ptr_ty_id, union_ptr_id, &.{layout.tag_index}); 4495 try cg.store(tag_ty, ptr_id, new_tag_id, .{ .is_volatile = un_ptr_ty.isVolatilePtr(zcu) }); 4496 } 4497 } 4498 4499 fn airGetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4500 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4501 const un_ty = cg.typeOf(ty_op.operand); 4502 4503 const zcu = cg.module.zcu; 4504 const layout = cg.unionLayout(un_ty); 4505 if (layout.tag_size == 0) return null; 4506 4507 const union_handle = try cg.resolve(ty_op.operand); 4508 if (!layout.has_payload) return union_handle; 4509 4510 const tag_ty = un_ty.unionTagTypeSafety(zcu).?; 4511 return try cg.extractField(tag_ty, union_handle, layout.tag_index); 4512 } 4513 4514 fn unionInit( 4515 cg: *CodeGen, 4516 ty: Type, 4517 active_field: u32, 4518 payload: ?Id, 4519 ) !Id { 4520 // To initialize a union, generate a temporary variable with the 4521 // union type, then get the field pointer and pointer-cast it to the 4522 // right type to store it. Finally load the entire union. 4523 4524 // Note: The result here is not cached, because it generates runtime code. 4525 4526 const pt = cg.pt; 4527 const zcu = cg.module.zcu; 4528 const ip = &zcu.intern_pool; 4529 const union_ty = zcu.typeToUnion(ty).?; 4530 const tag_ty: Type = .fromInterned(union_ty.enum_tag_ty); 4531 4532 const layout = cg.unionLayout(ty); 4533 const payload_ty: Type = .fromInterned(union_ty.field_types.get(ip)[active_field]); 4534 4535 if (union_ty.flagsUnordered(ip).layout == .@"packed") { 4536 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) { 4537 const int_ty = try pt.intType(.unsigned, @intCast(ty.bitSize(zcu))); 4538 return cg.constInt(int_ty, 0); 4539 } 4540 4541 assert(payload != null); 4542 if (payload_ty.isInt(zcu)) { 4543 if (ty.bitSize(zcu) == payload_ty.bitSize(zcu)) { 4544 return cg.bitCast(ty, payload_ty, payload.?); 4545 } 4546 4547 const trunc = try cg.buildConvert(ty, .{ .ty = payload_ty, .value = .{ .singleton = payload.? } }); 4548 return try trunc.materialize(cg); 4549 } 4550 4551 const payload_int_ty = try pt.intType(.unsigned, @intCast(payload_ty.bitSize(zcu))); 4552 const payload_int = if (payload_ty.ip_index == .bool_type) 4553 try cg.convertToIndirect(payload_ty, payload.?) 4554 else 4555 try cg.bitCast(payload_int_ty, payload_ty, payload.?); 4556 const trunc = try cg.buildConvert(ty, .{ .ty = payload_int_ty, .value = .{ .singleton = payload_int } }); 4557 return try trunc.materialize(cg); 4558 } 4559 4560 const tag_int = if (layout.tag_size != 0) blk: { 4561 const tag_val = try pt.enumValueFieldIndex(tag_ty, active_field); 4562 const tag_int_val = try tag_val.intFromEnum(tag_ty, pt); 4563 break :blk tag_int_val.toUnsignedInt(zcu); 4564 } else 0; 4565 4566 if (!layout.has_payload) { 4567 return try cg.constInt(tag_ty, tag_int); 4568 } 4569 4570 const ty_id = try cg.resolveType(ty, .indirect); 4571 const tmp_id = try cg.alloc(ty_id, null); 4572 4573 if (layout.tag_size != 0) { 4574 const tag_ty_id = try cg.resolveType(tag_ty, .indirect); 4575 const tag_ptr_ty_id = try cg.module.ptrType(tag_ty_id, .function); 4576 const ptr_id = try cg.accessChain(tag_ptr_ty_id, tmp_id, &.{@as(u32, @intCast(layout.tag_index))}); 4577 const tag_id = try cg.constInt(tag_ty, tag_int); 4578 try cg.store(tag_ty, ptr_id, tag_id, .{}); 4579 } 4580 4581 if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) { 4582 const layout_payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect); 4583 const pl_ptr_ty_id = try cg.module.ptrType(layout_payload_ty_id, .function); 4584 const pl_ptr_id = try cg.accessChain(pl_ptr_ty_id, tmp_id, &.{layout.payload_index}); 4585 const active_pl_ptr_id = if (!layout.payload_ty.eql(payload_ty, zcu)) blk: { 4586 const payload_ty_id = try cg.resolveType(payload_ty, .indirect); 4587 const active_pl_ptr_ty_id = try cg.module.ptrType(payload_ty_id, .function); 4588 const active_pl_ptr_id = cg.module.allocId(); 4589 try cg.body.emit(cg.module.gpa, .OpBitcast, .{ 4590 .id_result_type = active_pl_ptr_ty_id, 4591 .id_result = active_pl_ptr_id, 4592 .operand = pl_ptr_id, 4593 }); 4594 break :blk active_pl_ptr_id; 4595 } else pl_ptr_id; 4596 4597 try cg.store(payload_ty, active_pl_ptr_id, payload.?, .{}); 4598 } else { 4599 assert(payload == null); 4600 } 4601 4602 // Just leave the padding fields uninitialized... 4603 // TODO: Or should we initialize them with undef explicitly? 4604 4605 return try cg.load(ty, tmp_id, .{}); 4606 } 4607 4608 fn airUnionInit(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4609 const zcu = cg.module.zcu; 4610 const ip = &zcu.intern_pool; 4611 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 4612 const extra = cg.air.extraData(Air.UnionInit, ty_pl.payload).data; 4613 const ty = cg.typeOfIndex(inst); 4614 4615 const union_obj = zcu.typeToUnion(ty).?; 4616 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[extra.field_index]); 4617 const payload = if (field_ty.hasRuntimeBitsIgnoreComptime(zcu)) 4618 try cg.resolve(extra.init) 4619 else 4620 null; 4621 return try cg.unionInit(ty, extra.field_index, payload); 4622 } 4623 4624 fn airStructFieldVal(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4625 const pt = cg.pt; 4626 const zcu = cg.module.zcu; 4627 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 4628 const struct_field = cg.air.extraData(Air.StructField, ty_pl.payload).data; 4629 4630 const object_ty = cg.typeOf(struct_field.struct_operand); 4631 const object_id = try cg.resolve(struct_field.struct_operand); 4632 const field_index = struct_field.field_index; 4633 const field_ty = object_ty.fieldType(field_index, zcu); 4634 4635 if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) return null; 4636 4637 switch (object_ty.zigTypeTag(zcu)) { 4638 .@"struct" => switch (object_ty.containerLayout(zcu)) { 4639 .@"packed" => { 4640 const struct_ty = zcu.typeToPackedStruct(object_ty).?; 4641 const struct_backing_int_bits = cg.module.backingIntBits(@intCast(object_ty.bitSize(zcu))).@"0"; 4642 const bit_offset = zcu.structPackedFieldBitOffset(struct_ty, field_index); 4643 // We use the same int type the packed struct is backed by, because even though it would 4644 // be valid SPIR-V to use an smaller type like u16, some implementations like PoCL will complain. 4645 const bit_offset_id = try cg.constInt(object_ty, bit_offset); 4646 const signedness = if (field_ty.isInt(zcu)) field_ty.intInfo(zcu).signedness else .unsigned; 4647 const field_bit_size: u16 = @intCast(field_ty.bitSize(zcu)); 4648 const field_int_ty = try pt.intType(signedness, field_bit_size); 4649 const shift_lhs: Temporary = .{ .ty = object_ty, .value = .{ .singleton = object_id } }; 4650 const shift = try cg.buildBinary(.OpShiftRightLogical, shift_lhs, .{ .ty = object_ty, .value = .{ .singleton = bit_offset_id } }); 4651 const mask_id = try cg.constInt(object_ty, (@as(u64, 1) << @as(u6, @intCast(field_bit_size))) - 1); 4652 const masked = try cg.buildBinary(.OpBitwiseAnd, shift, .{ .ty = object_ty, .value = .{ .singleton = mask_id } }); 4653 const result_id = blk: { 4654 if (cg.module.backingIntBits(field_bit_size).@"0" == struct_backing_int_bits) 4655 break :blk try cg.bitCast(field_int_ty, object_ty, try masked.materialize(cg)); 4656 const trunc = try cg.buildConvert(field_int_ty, masked); 4657 break :blk try trunc.materialize(cg); 4658 }; 4659 if (field_ty.ip_index == .bool_type) return try cg.convertToDirect(.bool, result_id); 4660 if (field_ty.isInt(zcu)) return result_id; 4661 return try cg.bitCast(field_ty, field_int_ty, result_id); 4662 }, 4663 else => return try cg.extractField(field_ty, object_id, field_index), 4664 }, 4665 .@"union" => switch (object_ty.containerLayout(zcu)) { 4666 .@"packed" => { 4667 const backing_int_ty = try pt.intType(.unsigned, @intCast(object_ty.bitSize(zcu))); 4668 const signedness = if (field_ty.isInt(zcu)) field_ty.intInfo(zcu).signedness else .unsigned; 4669 const field_bit_size: u16 = @intCast(field_ty.bitSize(zcu)); 4670 const int_ty = try pt.intType(signedness, field_bit_size); 4671 const mask_id = try cg.constInt(backing_int_ty, (@as(u64, 1) << @as(u6, @intCast(field_bit_size))) - 1); 4672 const masked = try cg.buildBinary( 4673 .OpBitwiseAnd, 4674 .{ .ty = backing_int_ty, .value = .{ .singleton = object_id } }, 4675 .{ .ty = backing_int_ty, .value = .{ .singleton = mask_id } }, 4676 ); 4677 const result_id = blk: { 4678 if (cg.module.backingIntBits(field_bit_size).@"0" == cg.module.backingIntBits(@intCast(backing_int_ty.bitSize(zcu))).@"0") 4679 break :blk try cg.bitCast(int_ty, backing_int_ty, try masked.materialize(cg)); 4680 const trunc = try cg.buildConvert(int_ty, masked); 4681 break :blk try trunc.materialize(cg); 4682 }; 4683 if (field_ty.ip_index == .bool_type) return try cg.convertToDirect(.bool, result_id); 4684 if (field_ty.isInt(zcu)) return result_id; 4685 return try cg.bitCast(field_ty, int_ty, result_id); 4686 }, 4687 else => { 4688 // Store, ptr-elem-ptr, pointer-cast, load 4689 const layout = cg.unionLayout(object_ty); 4690 assert(layout.has_payload); 4691 4692 const object_ty_id = try cg.resolveType(object_ty, .indirect); 4693 const tmp_id = try cg.alloc(object_ty_id, null); 4694 try cg.store(object_ty, tmp_id, object_id, .{}); 4695 4696 const layout_payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect); 4697 const pl_ptr_ty_id = try cg.module.ptrType(layout_payload_ty_id, .function); 4698 const pl_ptr_id = try cg.accessChain(pl_ptr_ty_id, tmp_id, &.{layout.payload_index}); 4699 4700 const field_ty_id = try cg.resolveType(field_ty, .indirect); 4701 const active_pl_ptr_ty_id = try cg.module.ptrType(field_ty_id, .function); 4702 const active_pl_ptr_id = cg.module.allocId(); 4703 try cg.body.emit(cg.module.gpa, .OpBitcast, .{ 4704 .id_result_type = active_pl_ptr_ty_id, 4705 .id_result = active_pl_ptr_id, 4706 .operand = pl_ptr_id, 4707 }); 4708 return try cg.load(field_ty, active_pl_ptr_id, .{}); 4709 }, 4710 }, 4711 else => unreachable, 4712 } 4713 } 4714 4715 fn airFieldParentPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4716 const zcu = cg.module.zcu; 4717 const target = zcu.getTarget(); 4718 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 4719 const extra = cg.air.extraData(Air.FieldParentPtr, ty_pl.payload).data; 4720 4721 const parent_ptr_ty = ty_pl.ty.toType(); 4722 const parent_ty = parent_ptr_ty.childType(zcu); 4723 const result_ty_id = try cg.resolveType(parent_ptr_ty, .indirect); 4724 4725 const field_ptr = try cg.resolve(extra.field_ptr); 4726 const field_ptr_ty = cg.typeOf(extra.field_ptr); 4727 const field_ptr_int = try cg.intFromPtr(field_ptr); 4728 const field_offset = parent_ty.structFieldOffset(extra.field_index, zcu); 4729 4730 const base_ptr_int = base_ptr_int: { 4731 if (field_offset == 0) break :base_ptr_int field_ptr_int; 4732 4733 const field_offset_id = try cg.constInt(.usize, field_offset); 4734 const field_ptr_tmp: Temporary = .init(.usize, field_ptr_int); 4735 const field_offset_tmp: Temporary = .init(.usize, field_offset_id); 4736 const result = try cg.buildBinary(.OpISub, field_ptr_tmp, field_offset_tmp); 4737 break :base_ptr_int try result.materialize(cg); 4738 }; 4739 4740 if (target.os.tag != .opencl) { 4741 if (field_ptr_ty.ptrAddressSpace(zcu) != .physical_storage_buffer) { 4742 return cg.fail( 4743 "cannot cast integer to pointer with address space '{s}'", 4744 .{@tagName(field_ptr_ty.ptrAddressSpace(zcu))}, 4745 ); 4746 } 4747 } 4748 4749 const base_ptr = cg.module.allocId(); 4750 try cg.body.emit(cg.module.gpa, .OpConvertUToPtr, .{ 4751 .id_result_type = result_ty_id, 4752 .id_result = base_ptr, 4753 .integer_value = base_ptr_int, 4754 }); 4755 4756 return base_ptr; 4757 } 4758 4759 fn structFieldPtr( 4760 cg: *CodeGen, 4761 result_ptr_ty: Type, 4762 object_ptr_ty: Type, 4763 object_ptr: Id, 4764 field_index: u32, 4765 ) !Id { 4766 const result_ty_id = try cg.resolveType(result_ptr_ty, .direct); 4767 4768 const zcu = cg.module.zcu; 4769 const object_ty = object_ptr_ty.childType(zcu); 4770 switch (object_ty.zigTypeTag(zcu)) { 4771 .pointer => { 4772 assert(object_ty.isSlice(zcu)); 4773 return cg.accessChain(result_ty_id, object_ptr, &.{field_index}); 4774 }, 4775 .@"struct" => switch (object_ty.containerLayout(zcu)) { 4776 .@"packed" => return cg.todo("implement field access for packed structs", .{}), 4777 else => { 4778 return try cg.accessChain(result_ty_id, object_ptr, &.{field_index}); 4779 }, 4780 }, 4781 .@"union" => { 4782 const layout = cg.unionLayout(object_ty); 4783 if (!layout.has_payload) { 4784 // Asked to get a pointer to a zero-sized field. Just lower this 4785 // to undefined, there is no reason to make it be a valid pointer. 4786 return try cg.module.constUndef(result_ty_id); 4787 } 4788 4789 const storage_class = cg.module.storageClass(object_ptr_ty.ptrAddressSpace(zcu)); 4790 const layout_payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect); 4791 const pl_ptr_ty_id = try cg.module.ptrType(layout_payload_ty_id, storage_class); 4792 const pl_ptr_id = blk: { 4793 if (object_ty.containerLayout(zcu) == .@"packed") break :blk object_ptr; 4794 break :blk try cg.accessChain(pl_ptr_ty_id, object_ptr, &.{layout.payload_index}); 4795 }; 4796 4797 const active_pl_ptr_id = cg.module.allocId(); 4798 try cg.body.emit(cg.module.gpa, .OpBitcast, .{ 4799 .id_result_type = result_ty_id, 4800 .id_result = active_pl_ptr_id, 4801 .operand = pl_ptr_id, 4802 }); 4803 return active_pl_ptr_id; 4804 }, 4805 else => unreachable, 4806 } 4807 } 4808 4809 fn airStructFieldPtrIndex(cg: *CodeGen, inst: Air.Inst.Index, field_index: u32) !?Id { 4810 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 4811 const struct_ptr = try cg.resolve(ty_op.operand); 4812 const struct_ptr_ty = cg.typeOf(ty_op.operand); 4813 const result_ptr_ty = cg.typeOfIndex(inst); 4814 return try cg.structFieldPtr(result_ptr_ty, struct_ptr_ty, struct_ptr, field_index); 4815 } 4816 4817 fn alloc(cg: *CodeGen, ty_id: Id, initializer: ?Id) !Id { 4818 const ptr_ty_id = try cg.module.ptrType(ty_id, .function); 4819 const result_id = cg.module.allocId(); 4820 try cg.prologue.emit(cg.module.gpa, .OpVariable, .{ 4821 .id_result_type = ptr_ty_id, 4822 .id_result = result_id, 4823 .storage_class = .function, 4824 .initializer = initializer, 4825 }); 4826 return result_id; 4827 } 4828 4829 fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 4830 const zcu = cg.module.zcu; 4831 const target = zcu.getTarget(); 4832 const ptr_ty = cg.typeOfIndex(inst); 4833 const child_ty = ptr_ty.childType(zcu); 4834 const child_ty_id = try cg.resolveType(child_ty, .indirect); 4835 const ptr_align = ptr_ty.ptrAlignment(zcu); 4836 const result_id = try cg.alloc(child_ty_id, null); 4837 if (ptr_align != child_ty.abiAlignment(zcu)) { 4838 if (target.os.tag != .opencl) return cg.fail("cannot apply alignment to variables", .{}); 4839 try cg.module.decorate(result_id, .{ 4840 .alignment = .{ .alignment = @intCast(ptr_align.toByteUnits().?) }, 4841 }); 4842 } 4843 return result_id; 4844 } 4845 4846 fn airArg(cg: *CodeGen) Id { 4847 defer cg.next_arg_index += 1; 4848 return cg.args.items[cg.next_arg_index]; 4849 } 4850 4851 /// Given a slice of incoming block connections, returns the block-id of the next 4852 /// block to jump to. This function emits instructions, so it should be emitted 4853 /// inside the merge block of the block. 4854 /// This function should only be called with structured control flow generation. 4855 fn structuredNextBlock(cg: *CodeGen, incoming: []const ControlFlow.Structured.Block.Incoming) !Id { 4856 assert(cg.control_flow == .structured); 4857 4858 const result_id = cg.module.allocId(); 4859 const block_id_ty_id = try cg.resolveType(.u32, .direct); 4860 try cg.body.emitRaw(cg.module.gpa, .OpPhi, @intCast(2 + incoming.len * 2)); // result type + result + variable/parent... 4861 cg.body.writeOperand(Id, block_id_ty_id); 4862 cg.body.writeOperand(Id, result_id); 4863 4864 for (incoming) |incoming_block| { 4865 cg.body.writeOperand(spec.PairIdRefIdRef, .{ incoming_block.next_block, incoming_block.src_label }); 4866 } 4867 4868 return result_id; 4869 } 4870 4871 /// Jumps to the block with the target block-id. This function must only be called when 4872 /// terminating a body, there should be no instructions after it. 4873 /// This function should only be called with structured control flow generation. 4874 fn structuredBreak(cg: *CodeGen, target_block: Id) !void { 4875 assert(cg.control_flow == .structured); 4876 4877 const gpa = cg.module.gpa; 4878 const sblock = cg.control_flow.structured.block_stack.getLast(); 4879 const merge_block = switch (sblock.*) { 4880 .selection => |*merge| blk: { 4881 const merge_label = cg.module.allocId(); 4882 try merge.merge_stack.append(gpa, .{ 4883 .incoming = .{ 4884 .src_label = cg.block_label, 4885 .next_block = target_block, 4886 }, 4887 .merge_block = merge_label, 4888 }); 4889 break :blk merge_label; 4890 }, 4891 // Loop blocks do not end in a break. Not through a direct break, 4892 // and also not through another instruction like cond_br or unreachable (these 4893 // situations are replaced by `cond_br` in sema, or there is a `block` instruction 4894 // placed around them). 4895 .loop => unreachable, 4896 }; 4897 4898 try cg.body.emit(gpa, .OpBranch, .{ .target_label = merge_block }); 4899 } 4900 4901 /// Generate a body in a way that exits the body using only structured constructs. 4902 /// Returns the block-id of the next block to jump to. After this function, a jump 4903 /// should still be emitted to the block that should follow this structured body. 4904 /// This function should only be called with structured control flow generation. 4905 fn genStructuredBody( 4906 cg: *CodeGen, 4907 /// This parameter defines the method that this structured body is exited with. 4908 block_merge_type: union(enum) { 4909 /// Using selection; early exits from this body are surrounded with 4910 /// if() statements. 4911 selection, 4912 /// Using loops; loops can be early exited by jumping to the merge block at 4913 /// any time. 4914 loop: struct { 4915 merge_label: Id, 4916 continue_label: Id, 4917 }, 4918 }, 4919 body: []const Air.Inst.Index, 4920 ) !Id { 4921 assert(cg.control_flow == .structured); 4922 4923 const gpa = cg.module.gpa; 4924 4925 var sblock: ControlFlow.Structured.Block = switch (block_merge_type) { 4926 .loop => |merge| .{ .loop = .{ 4927 .merge_block = merge.merge_label, 4928 } }, 4929 .selection => .{ .selection = .{} }, 4930 }; 4931 defer sblock.deinit(gpa); 4932 4933 { 4934 try cg.control_flow.structured.block_stack.append(gpa, &sblock); 4935 defer _ = cg.control_flow.structured.block_stack.pop(); 4936 4937 try cg.genBody(body); 4938 } 4939 4940 switch (sblock) { 4941 .selection => |merge| { 4942 // Now generate the merge block for all merges that 4943 // still need to be performed. 4944 const merge_stack = merge.merge_stack.items; 4945 4946 // If no merges on the stack, this block didn't generate any jumps (all paths 4947 // ended with a return or an unreachable). In that case, we don't need to do 4948 // any merging. 4949 if (merge_stack.len == 0) { 4950 // We still need to return a value of a next block to jump to. 4951 // For example, if we have code like 4952 // if (x) { 4953 // if (y) return else return; 4954 // } else {} 4955 // then we still need the outer to have an OpSelectionMerge and consequently 4956 // a phi node. In that case we can just return bogus, since we know that its 4957 // path will never be taken. 4958 4959 // Make sure that we are still in a block when exiting the function. 4960 // TODO: Can we get rid of that? 4961 try cg.beginSpvBlock(cg.module.allocId()); 4962 const block_id_ty_id = try cg.resolveType(.u32, .direct); 4963 return try cg.module.constUndef(block_id_ty_id); 4964 } 4965 4966 // The top-most merge actually only has a single source, the 4967 // final jump of the block, or the merge block of a sub-block, cond_br, 4968 // or loop. Therefore we just need to generate a block with a jump to the 4969 // next merge block. 4970 try cg.beginSpvBlock(merge_stack[merge_stack.len - 1].merge_block); 4971 4972 // Now generate a merge ladder for the remaining merges in the stack. 4973 var incoming: ControlFlow.Structured.Block.Incoming = .{ 4974 .src_label = cg.block_label, 4975 .next_block = merge_stack[merge_stack.len - 1].incoming.next_block, 4976 }; 4977 var i = merge_stack.len - 1; 4978 while (i > 0) { 4979 i -= 1; 4980 const step = merge_stack[i]; 4981 4982 try cg.body.emit(gpa, .OpBranch, .{ .target_label = step.merge_block }); 4983 try cg.beginSpvBlock(step.merge_block); 4984 const next_block = try cg.structuredNextBlock(&.{ incoming, step.incoming }); 4985 incoming = .{ 4986 .src_label = step.merge_block, 4987 .next_block = next_block, 4988 }; 4989 } 4990 4991 return incoming.next_block; 4992 }, 4993 .loop => |merge| { 4994 // Close the loop by jumping to the continue label 4995 4996 try cg.body.emit(gpa, .OpBranch, .{ .target_label = block_merge_type.loop.continue_label }); 4997 // For blocks we must simple merge all the incoming blocks to get the next block. 4998 try cg.beginSpvBlock(merge.merge_block); 4999 return try cg.structuredNextBlock(merge.merges.items); 5000 }, 5001 } 5002 } 5003 5004 fn airBlock(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 5005 const inst_datas = cg.air.instructions.items(.data); 5006 const extra = cg.air.extraData(Air.Block, inst_datas[@intFromEnum(inst)].ty_pl.payload); 5007 return cg.lowerBlock(inst, @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.body_len])); 5008 } 5009 5010 fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, body: []const Air.Inst.Index) !?Id { 5011 // In AIR, a block doesn't really define an entry point like a block, but 5012 // more like a scope that breaks can jump out of and "return" a value from. 5013 // This cannot be directly modelled in SPIR-V, so in a block instruction, 5014 // we're going to split up the current block by first generating the code 5015 // of the block, then a label, and then generate the rest of the current 5016 // ir.Block in a different SPIR-V block. 5017 5018 const gpa = cg.module.gpa; 5019 const zcu = cg.module.zcu; 5020 const ty = cg.typeOfIndex(inst); 5021 const have_block_result = ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu); 5022 5023 const cf = switch (cg.control_flow) { 5024 .structured => |*cf| cf, 5025 .unstructured => |*cf| { 5026 var block: ControlFlow.Unstructured.Block = .{}; 5027 defer block.incoming_blocks.deinit(gpa); 5028 5029 // 4 chosen as arbitrary initial capacity. 5030 try block.incoming_blocks.ensureUnusedCapacity(gpa, 4); 5031 5032 try cf.blocks.putNoClobber(gpa, inst, &block); 5033 defer assert(cf.blocks.remove(inst)); 5034 5035 try cg.genBody(body); 5036 5037 // Only begin a new block if there were actually any breaks towards it. 5038 if (block.label) |label| { 5039 try cg.beginSpvBlock(label); 5040 } 5041 5042 if (!have_block_result) 5043 return null; 5044 5045 assert(block.label != null); 5046 const result_id = cg.module.allocId(); 5047 const result_type_id = try cg.resolveType(ty, .direct); 5048 5049 try cg.body.emitRaw( 5050 gpa, 5051 .OpPhi, 5052 // result type + result + variable/parent... 5053 2 + @as(u16, @intCast(block.incoming_blocks.items.len * 2)), 5054 ); 5055 cg.body.writeOperand(Id, result_type_id); 5056 cg.body.writeOperand(Id, result_id); 5057 5058 for (block.incoming_blocks.items) |incoming| { 5059 cg.body.writeOperand( 5060 spec.PairIdRefIdRef, 5061 .{ incoming.break_value_id, incoming.src_label }, 5062 ); 5063 } 5064 5065 return result_id; 5066 }, 5067 }; 5068 5069 const maybe_block_result_var_id = if (have_block_result) blk: { 5070 const ty_id = try cg.resolveType(ty, .indirect); 5071 const block_result_var_id = try cg.alloc(ty_id, null); 5072 try cf.block_results.putNoClobber(gpa, inst, block_result_var_id); 5073 break :blk block_result_var_id; 5074 } else null; 5075 defer if (have_block_result) assert(cf.block_results.remove(inst)); 5076 5077 const next_block = try cg.genStructuredBody(.selection, body); 5078 5079 // When encountering a block instruction, we are always at least in the function's scope, 5080 // so there always has to be another entry. 5081 assert(cf.block_stack.items.len > 0); 5082 5083 // Check if the target of the branch was this current block. 5084 const this_block = try cg.constInt(.u32, @intFromEnum(inst)); 5085 const jump_to_this_block_id = cg.module.allocId(); 5086 const bool_ty_id = try cg.resolveType(.bool, .direct); 5087 try cg.body.emit(gpa, .OpIEqual, .{ 5088 .id_result_type = bool_ty_id, 5089 .id_result = jump_to_this_block_id, 5090 .operand_1 = next_block, 5091 .operand_2 = this_block, 5092 }); 5093 5094 const sblock = cf.block_stack.getLast(); 5095 5096 if (ty.isNoReturn(zcu)) { 5097 // If this block is noreturn, this instruction is the last of a block, 5098 // and we must simply jump to the block's merge unconditionally. 5099 try cg.structuredBreak(next_block); 5100 } else { 5101 switch (sblock.*) { 5102 .selection => |*merge| { 5103 // To jump out of a selection block, push a new entry onto its merge stack and 5104 // generate a conditional branch to there and to the instructions following this block. 5105 const merge_label = cg.module.allocId(); 5106 const then_label = cg.module.allocId(); 5107 try cg.body.emit(gpa, .OpSelectionMerge, .{ 5108 .merge_block = merge_label, 5109 .selection_control = .{}, 5110 }); 5111 try cg.body.emit(gpa, .OpBranchConditional, .{ 5112 .condition = jump_to_this_block_id, 5113 .true_label = then_label, 5114 .false_label = merge_label, 5115 }); 5116 try merge.merge_stack.append(gpa, .{ 5117 .incoming = .{ 5118 .src_label = cg.block_label, 5119 .next_block = next_block, 5120 }, 5121 .merge_block = merge_label, 5122 }); 5123 5124 try cg.beginSpvBlock(then_label); 5125 }, 5126 .loop => |*merge| { 5127 // To jump out of a loop block, generate a conditional that exits the block 5128 // to the loop merge if the target ID is not the one of this block. 5129 const continue_label = cg.module.allocId(); 5130 try cg.body.emit(gpa, .OpBranchConditional, .{ 5131 .condition = jump_to_this_block_id, 5132 .true_label = continue_label, 5133 .false_label = merge.merge_block, 5134 }); 5135 try merge.merges.append(gpa, .{ 5136 .src_label = cg.block_label, 5137 .next_block = next_block, 5138 }); 5139 try cg.beginSpvBlock(continue_label); 5140 }, 5141 } 5142 } 5143 5144 if (maybe_block_result_var_id) |block_result_var_id| { 5145 return try cg.load(ty, block_result_var_id, .{}); 5146 } 5147 5148 return null; 5149 } 5150 5151 fn airBr(cg: *CodeGen, inst: Air.Inst.Index) !void { 5152 const gpa = cg.module.gpa; 5153 const zcu = cg.module.zcu; 5154 const br = cg.air.instructions.items(.data)[@intFromEnum(inst)].br; 5155 const operand_ty = cg.typeOf(br.operand); 5156 5157 switch (cg.control_flow) { 5158 .structured => |*cf| { 5159 if (operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) { 5160 const operand_id = try cg.resolve(br.operand); 5161 const block_result_var_id = cf.block_results.get(br.block_inst).?; 5162 try cg.store(operand_ty, block_result_var_id, operand_id, .{}); 5163 } 5164 5165 const next_block = try cg.constInt(.u32, @intFromEnum(br.block_inst)); 5166 try cg.structuredBreak(next_block); 5167 }, 5168 .unstructured => |cf| { 5169 const block = cf.blocks.get(br.block_inst).?; 5170 if (operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) { 5171 const operand_id = try cg.resolve(br.operand); 5172 // block_label should not be undefined here, lest there 5173 // is a br or br_void in the function's body. 5174 try block.incoming_blocks.append(gpa, .{ 5175 .src_label = cg.block_label, 5176 .break_value_id = operand_id, 5177 }); 5178 } 5179 5180 if (block.label == null) { 5181 block.label = cg.module.allocId(); 5182 } 5183 5184 try cg.body.emit(gpa, .OpBranch, .{ .target_label = block.label.? }); 5185 }, 5186 } 5187 } 5188 5189 fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) !void { 5190 const gpa = cg.module.gpa; 5191 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 5192 const cond_br = cg.air.extraData(Air.CondBr, pl_op.payload); 5193 const then_body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[cond_br.end..][0..cond_br.data.then_body_len]); 5194 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]); 5195 const condition_id = try cg.resolve(pl_op.operand); 5196 5197 const then_label = cg.module.allocId(); 5198 const else_label = cg.module.allocId(); 5199 5200 switch (cg.control_flow) { 5201 .structured => { 5202 const merge_label = cg.module.allocId(); 5203 5204 try cg.body.emit(gpa, .OpSelectionMerge, .{ 5205 .merge_block = merge_label, 5206 .selection_control = .{}, 5207 }); 5208 try cg.body.emit(gpa, .OpBranchConditional, .{ 5209 .condition = condition_id, 5210 .true_label = then_label, 5211 .false_label = else_label, 5212 }); 5213 5214 try cg.beginSpvBlock(then_label); 5215 const then_next = try cg.genStructuredBody(.selection, then_body); 5216 const then_incoming: ControlFlow.Structured.Block.Incoming = .{ 5217 .src_label = cg.block_label, 5218 .next_block = then_next, 5219 }; 5220 5221 try cg.body.emit(gpa, .OpBranch, .{ .target_label = merge_label }); 5222 5223 try cg.beginSpvBlock(else_label); 5224 const else_next = try cg.genStructuredBody(.selection, else_body); 5225 const else_incoming: ControlFlow.Structured.Block.Incoming = .{ 5226 .src_label = cg.block_label, 5227 .next_block = else_next, 5228 }; 5229 5230 try cg.body.emit(gpa, .OpBranch, .{ .target_label = merge_label }); 5231 5232 try cg.beginSpvBlock(merge_label); 5233 const next_block = try cg.structuredNextBlock(&.{ then_incoming, else_incoming }); 5234 5235 try cg.structuredBreak(next_block); 5236 }, 5237 .unstructured => { 5238 try cg.body.emit(gpa, .OpBranchConditional, .{ 5239 .condition = condition_id, 5240 .true_label = then_label, 5241 .false_label = else_label, 5242 }); 5243 5244 try cg.beginSpvBlock(then_label); 5245 try cg.genBody(then_body); 5246 try cg.beginSpvBlock(else_label); 5247 try cg.genBody(else_body); 5248 }, 5249 } 5250 } 5251 5252 fn airLoop(cg: *CodeGen, inst: Air.Inst.Index) !void { 5253 const gpa = cg.module.gpa; 5254 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 5255 const loop = cg.air.extraData(Air.Block, ty_pl.payload); 5256 const body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[loop.end..][0..loop.data.body_len]); 5257 5258 const body_label = cg.module.allocId(); 5259 5260 switch (cg.control_flow) { 5261 .structured => { 5262 const header_label = cg.module.allocId(); 5263 const merge_label = cg.module.allocId(); 5264 const continue_label = cg.module.allocId(); 5265 5266 // The back-edge must point to the loop header, so generate a separate block for the 5267 // loop header so that we don't accidentally include some instructions from there 5268 // in the loop. 5269 5270 try cg.body.emit(gpa, .OpBranch, .{ .target_label = header_label }); 5271 try cg.beginSpvBlock(header_label); 5272 5273 // Emit loop header and jump to loop body 5274 try cg.body.emit(gpa, .OpLoopMerge, .{ 5275 .merge_block = merge_label, 5276 .continue_target = continue_label, 5277 .loop_control = .{}, 5278 }); 5279 5280 try cg.body.emit(gpa, .OpBranch, .{ .target_label = body_label }); 5281 5282 try cg.beginSpvBlock(body_label); 5283 5284 const next_block = try cg.genStructuredBody(.{ .loop = .{ 5285 .merge_label = merge_label, 5286 .continue_label = continue_label, 5287 } }, body); 5288 try cg.structuredBreak(next_block); 5289 5290 try cg.beginSpvBlock(continue_label); 5291 5292 try cg.body.emit(gpa, .OpBranch, .{ .target_label = header_label }); 5293 }, 5294 .unstructured => { 5295 try cg.body.emit(gpa, .OpBranch, .{ .target_label = body_label }); 5296 try cg.beginSpvBlock(body_label); 5297 try cg.genBody(body); 5298 5299 try cg.body.emit(gpa, .OpBranch, .{ .target_label = body_label }); 5300 }, 5301 } 5302 } 5303 5304 fn airLoad(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 5305 const zcu = cg.module.zcu; 5306 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5307 const ptr_ty = cg.typeOf(ty_op.operand); 5308 const elem_ty = cg.typeOfIndex(inst); 5309 const operand = try cg.resolve(ty_op.operand); 5310 if (!ptr_ty.isVolatilePtr(zcu) and cg.liveness.isUnused(inst)) return null; 5311 5312 return try cg.load(elem_ty, operand, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) }); 5313 } 5314 5315 fn airStore(cg: *CodeGen, inst: Air.Inst.Index) !void { 5316 const zcu = cg.module.zcu; 5317 const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; 5318 const ptr_ty = cg.typeOf(bin_op.lhs); 5319 const elem_ty = ptr_ty.childType(zcu); 5320 const ptr = try cg.resolve(bin_op.lhs); 5321 const value = try cg.resolve(bin_op.rhs); 5322 5323 try cg.store(elem_ty, ptr, value, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) }); 5324 } 5325 5326 fn airRet(cg: *CodeGen, inst: Air.Inst.Index) !void { 5327 const gpa = cg.module.gpa; 5328 const zcu = cg.module.zcu; 5329 const operand = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 5330 const ret_ty = cg.typeOf(operand); 5331 if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) { 5332 const fn_info = zcu.typeToFunc(zcu.navValue(cg.owner_nav).typeOf(zcu)).?; 5333 if (Type.fromInterned(fn_info.return_type).isError(zcu)) { 5334 // Functions with an empty error set are emitted with an error code 5335 // return type and return zero so they can be function pointers coerced 5336 // to functions that return anyerror. 5337 const no_err_id = try cg.constInt(.anyerror, 0); 5338 return try cg.body.emit(gpa, .OpReturnValue, .{ .value = no_err_id }); 5339 } else { 5340 return try cg.body.emit(gpa, .OpReturn, {}); 5341 } 5342 } 5343 5344 const operand_id = try cg.resolve(operand); 5345 try cg.body.emit(gpa, .OpReturnValue, .{ .value = operand_id }); 5346 } 5347 5348 fn airRetLoad(cg: *CodeGen, inst: Air.Inst.Index) !void { 5349 const gpa = cg.module.gpa; 5350 const zcu = cg.module.zcu; 5351 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 5352 const ptr_ty = cg.typeOf(un_op); 5353 const ret_ty = ptr_ty.childType(zcu); 5354 5355 if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) { 5356 const fn_info = zcu.typeToFunc(zcu.navValue(cg.owner_nav).typeOf(zcu)).?; 5357 if (Type.fromInterned(fn_info.return_type).isError(zcu)) { 5358 // Functions with an empty error set are emitted with an error code 5359 // return type and return zero so they can be function pointers coerced 5360 // to functions that return anyerror. 5361 const no_err_id = try cg.constInt(.anyerror, 0); 5362 return try cg.body.emit(gpa, .OpReturnValue, .{ .value = no_err_id }); 5363 } else { 5364 return try cg.body.emit(gpa, .OpReturn, {}); 5365 } 5366 } 5367 5368 const ptr = try cg.resolve(un_op); 5369 const value = try cg.load(ret_ty, ptr, .{ .is_volatile = ptr_ty.isVolatilePtr(zcu) }); 5370 try cg.body.emit(gpa, .OpReturnValue, .{ 5371 .value = value, 5372 }); 5373 } 5374 5375 fn airTry(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 5376 const gpa = cg.module.gpa; 5377 const zcu = cg.module.zcu; 5378 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 5379 const err_union_id = try cg.resolve(pl_op.operand); 5380 const extra = cg.air.extraData(Air.Try, pl_op.payload); 5381 const body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.body_len]); 5382 5383 const err_union_ty = cg.typeOf(pl_op.operand); 5384 const payload_ty = cg.typeOfIndex(inst); 5385 5386 const bool_ty_id = try cg.resolveType(.bool, .direct); 5387 5388 const eu_layout = cg.errorUnionLayout(payload_ty); 5389 5390 if (!err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) { 5391 const err_id = if (eu_layout.payload_has_bits) 5392 try cg.extractField(.anyerror, err_union_id, eu_layout.errorFieldIndex()) 5393 else 5394 err_union_id; 5395 5396 const zero_id = try cg.constInt(.anyerror, 0); 5397 const is_err_id = cg.module.allocId(); 5398 try cg.body.emit(gpa, .OpINotEqual, .{ 5399 .id_result_type = bool_ty_id, 5400 .id_result = is_err_id, 5401 .operand_1 = err_id, 5402 .operand_2 = zero_id, 5403 }); 5404 5405 // When there is an error, we must evaluate `body`. Otherwise we must continue 5406 // with the current body. 5407 // Just generate a new block here, then generate a new block inline for the remainder of the body. 5408 5409 const err_block = cg.module.allocId(); 5410 const ok_block = cg.module.allocId(); 5411 5412 switch (cg.control_flow) { 5413 .structured => { 5414 // According to AIR documentation, this block is guaranteed 5415 // to not break and end in a return instruction. Thus, 5416 // for structured control flow, we can just naively use 5417 // the ok block as the merge block here. 5418 try cg.body.emit(gpa, .OpSelectionMerge, .{ 5419 .merge_block = ok_block, 5420 .selection_control = .{}, 5421 }); 5422 }, 5423 .unstructured => {}, 5424 } 5425 5426 try cg.body.emit(gpa, .OpBranchConditional, .{ 5427 .condition = is_err_id, 5428 .true_label = err_block, 5429 .false_label = ok_block, 5430 }); 5431 5432 try cg.beginSpvBlock(err_block); 5433 try cg.genBody(body); 5434 5435 try cg.beginSpvBlock(ok_block); 5436 } 5437 5438 if (!eu_layout.payload_has_bits) { 5439 return null; 5440 } 5441 5442 // Now just extract the payload, if required. 5443 return try cg.extractField(payload_ty, err_union_id, eu_layout.payloadFieldIndex()); 5444 } 5445 5446 fn airErrUnionErr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 5447 const zcu = cg.module.zcu; 5448 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5449 const operand_id = try cg.resolve(ty_op.operand); 5450 const err_union_ty = cg.typeOf(ty_op.operand); 5451 const err_ty_id = try cg.resolveType(.anyerror, .direct); 5452 5453 if (err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) { 5454 // No error possible, so just return undefined. 5455 return try cg.module.constUndef(err_ty_id); 5456 } 5457 5458 const payload_ty = err_union_ty.errorUnionPayload(zcu); 5459 const eu_layout = cg.errorUnionLayout(payload_ty); 5460 5461 if (!eu_layout.payload_has_bits) { 5462 // If no payload, error union is represented by error set. 5463 return operand_id; 5464 } 5465 5466 return try cg.extractField(.anyerror, operand_id, eu_layout.errorFieldIndex()); 5467 } 5468 5469 fn airErrUnionPayload(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 5470 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5471 const operand_id = try cg.resolve(ty_op.operand); 5472 const payload_ty = cg.typeOfIndex(inst); 5473 const eu_layout = cg.errorUnionLayout(payload_ty); 5474 5475 if (!eu_layout.payload_has_bits) { 5476 return null; // No error possible. 5477 } 5478 5479 return try cg.extractField(payload_ty, operand_id, eu_layout.payloadFieldIndex()); 5480 } 5481 5482 fn airWrapErrUnionErr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 5483 const zcu = cg.module.zcu; 5484 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5485 const err_union_ty = cg.typeOfIndex(inst); 5486 const payload_ty = err_union_ty.errorUnionPayload(zcu); 5487 const operand_id = try cg.resolve(ty_op.operand); 5488 const eu_layout = cg.errorUnionLayout(payload_ty); 5489 5490 if (!eu_layout.payload_has_bits) { 5491 return operand_id; 5492 } 5493 5494 const payload_ty_id = try cg.resolveType(payload_ty, .indirect); 5495 5496 var members: [2]Id = undefined; 5497 members[eu_layout.errorFieldIndex()] = operand_id; 5498 members[eu_layout.payloadFieldIndex()] = try cg.module.constUndef(payload_ty_id); 5499 5500 var types: [2]Type = undefined; 5501 types[eu_layout.errorFieldIndex()] = .anyerror; 5502 types[eu_layout.payloadFieldIndex()] = payload_ty; 5503 5504 const err_union_ty_id = try cg.resolveType(err_union_ty, .direct); 5505 return try cg.constructComposite(err_union_ty_id, &members); 5506 } 5507 5508 fn airWrapErrUnionPayload(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 5509 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5510 const err_union_ty = cg.typeOfIndex(inst); 5511 const operand_id = try cg.resolve(ty_op.operand); 5512 const payload_ty = cg.typeOf(ty_op.operand); 5513 const eu_layout = cg.errorUnionLayout(payload_ty); 5514 5515 if (!eu_layout.payload_has_bits) { 5516 return try cg.constInt(.anyerror, 0); 5517 } 5518 5519 var members: [2]Id = undefined; 5520 members[eu_layout.errorFieldIndex()] = try cg.constInt(.anyerror, 0); 5521 members[eu_layout.payloadFieldIndex()] = try cg.convertToIndirect(payload_ty, operand_id); 5522 5523 var types: [2]Type = undefined; 5524 types[eu_layout.errorFieldIndex()] = .anyerror; 5525 types[eu_layout.payloadFieldIndex()] = payload_ty; 5526 5527 const err_union_ty_id = try cg.resolveType(err_union_ty, .direct); 5528 return try cg.constructComposite(err_union_ty_id, &members); 5529 } 5530 5531 fn airIsNull(cg: *CodeGen, inst: Air.Inst.Index, is_pointer: bool, pred: enum { is_null, is_non_null }) !?Id { 5532 const zcu = cg.module.zcu; 5533 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 5534 const operand_id = try cg.resolve(un_op); 5535 const operand_ty = cg.typeOf(un_op); 5536 const optional_ty = if (is_pointer) operand_ty.childType(zcu) else operand_ty; 5537 const payload_ty = optional_ty.optionalChild(zcu); 5538 5539 const bool_ty_id = try cg.resolveType(.bool, .direct); 5540 5541 if (optional_ty.optionalReprIsPayload(zcu)) { 5542 // Pointer payload represents nullability: pointer or slice. 5543 const loaded_id = if (is_pointer) 5544 try cg.load(optional_ty, operand_id, .{}) 5545 else 5546 operand_id; 5547 5548 const ptr_ty = if (payload_ty.isSlice(zcu)) 5549 payload_ty.slicePtrFieldType(zcu) 5550 else 5551 payload_ty; 5552 5553 const ptr_id = if (payload_ty.isSlice(zcu)) 5554 try cg.extractField(ptr_ty, loaded_id, 0) 5555 else 5556 loaded_id; 5557 5558 const ptr_ty_id = try cg.resolveType(ptr_ty, .direct); 5559 const null_id = try cg.module.constNull(ptr_ty_id); 5560 const null_tmp: Temporary = .init(ptr_ty, null_id); 5561 const ptr: Temporary = .init(ptr_ty, ptr_id); 5562 5563 const op: std.math.CompareOperator = switch (pred) { 5564 .is_null => .eq, 5565 .is_non_null => .neq, 5566 }; 5567 const result = try cg.cmp(op, ptr, null_tmp); 5568 return try result.materialize(cg); 5569 } 5570 5571 const is_non_null_id = blk: { 5572 if (is_pointer) { 5573 if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) { 5574 const storage_class = cg.module.storageClass(operand_ty.ptrAddressSpace(zcu)); 5575 const bool_indirect_ty_id = try cg.resolveType(.bool, .indirect); 5576 const bool_ptr_ty_id = try cg.module.ptrType(bool_indirect_ty_id, storage_class); 5577 const tag_ptr_id = try cg.accessChain(bool_ptr_ty_id, operand_id, &.{1}); 5578 break :blk try cg.load(.bool, tag_ptr_id, .{}); 5579 } 5580 5581 break :blk try cg.load(.bool, operand_id, .{}); 5582 } 5583 5584 break :blk if (payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) 5585 try cg.extractField(.bool, operand_id, 1) 5586 else 5587 // Optional representation is bool indicating whether the optional is set 5588 // Optionals with no payload are represented as an (indirect) bool, so convert 5589 // it back to the direct bool here. 5590 try cg.convertToDirect(.bool, operand_id); 5591 }; 5592 5593 return switch (pred) { 5594 .is_null => blk: { 5595 // Invert condition 5596 const result_id = cg.module.allocId(); 5597 try cg.body.emit(cg.module.gpa, .OpLogicalNot, .{ 5598 .id_result_type = bool_ty_id, 5599 .id_result = result_id, 5600 .operand = is_non_null_id, 5601 }); 5602 break :blk result_id; 5603 }, 5604 .is_non_null => is_non_null_id, 5605 }; 5606 } 5607 5608 fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, pred: enum { is_err, is_non_err }) !?Id { 5609 const zcu = cg.module.zcu; 5610 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op; 5611 const operand_id = try cg.resolve(un_op); 5612 const err_union_ty = cg.typeOf(un_op); 5613 5614 if (err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) { 5615 return try cg.constBool(pred == .is_non_err, .direct); 5616 } 5617 5618 const payload_ty = err_union_ty.errorUnionPayload(zcu); 5619 const eu_layout = cg.errorUnionLayout(payload_ty); 5620 const bool_ty_id = try cg.resolveType(.bool, .direct); 5621 5622 const error_id = if (!eu_layout.payload_has_bits) 5623 operand_id 5624 else 5625 try cg.extractField(.anyerror, operand_id, eu_layout.errorFieldIndex()); 5626 5627 const result_id = cg.module.allocId(); 5628 switch (pred) { 5629 inline else => |pred_ct| try cg.body.emit( 5630 cg.module.gpa, 5631 switch (pred_ct) { 5632 .is_err => .OpINotEqual, 5633 .is_non_err => .OpIEqual, 5634 }, 5635 .{ 5636 .id_result_type = bool_ty_id, 5637 .id_result = result_id, 5638 .operand_1 = error_id, 5639 .operand_2 = try cg.constInt(.anyerror, 0), 5640 }, 5641 ), 5642 } 5643 return result_id; 5644 } 5645 5646 fn airUnwrapOptional(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 5647 const zcu = cg.module.zcu; 5648 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5649 const operand_id = try cg.resolve(ty_op.operand); 5650 const optional_ty = cg.typeOf(ty_op.operand); 5651 const payload_ty = cg.typeOfIndex(inst); 5652 5653 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) return null; 5654 5655 if (optional_ty.optionalReprIsPayload(zcu)) { 5656 return operand_id; 5657 } 5658 5659 return try cg.extractField(payload_ty, operand_id, 0); 5660 } 5661 5662 fn airUnwrapOptionalPtr(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 5663 const zcu = cg.module.zcu; 5664 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5665 const operand_id = try cg.resolve(ty_op.operand); 5666 const operand_ty = cg.typeOf(ty_op.operand); 5667 const optional_ty = operand_ty.childType(zcu); 5668 const payload_ty = optional_ty.optionalChild(zcu); 5669 const result_ty = cg.typeOfIndex(inst); 5670 const result_ty_id = try cg.resolveType(result_ty, .direct); 5671 5672 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) { 5673 // There is no payload, but we still need to return a valid pointer. 5674 // We can just return anything here, so just return a pointer to the operand. 5675 return try cg.bitCast(result_ty, operand_ty, operand_id); 5676 } 5677 5678 if (optional_ty.optionalReprIsPayload(zcu)) { 5679 // They are the same value. 5680 return try cg.bitCast(result_ty, operand_ty, operand_id); 5681 } 5682 5683 return try cg.accessChain(result_ty_id, operand_id, &.{0}); 5684 } 5685 5686 fn airWrapOptional(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 5687 const zcu = cg.module.zcu; 5688 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; 5689 const payload_ty = cg.typeOf(ty_op.operand); 5690 5691 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) { 5692 return try cg.constBool(true, .indirect); 5693 } 5694 5695 const operand_id = try cg.resolve(ty_op.operand); 5696 5697 const optional_ty = cg.typeOfIndex(inst); 5698 if (optional_ty.optionalReprIsPayload(zcu)) { 5699 return operand_id; 5700 } 5701 5702 const payload_id = try cg.convertToIndirect(payload_ty, operand_id); 5703 const members = [_]Id{ payload_id, try cg.constBool(true, .indirect) }; 5704 const optional_ty_id = try cg.resolveType(optional_ty, .direct); 5705 return try cg.constructComposite(optional_ty_id, &members); 5706 } 5707 5708 fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { 5709 const gpa = cg.module.gpa; 5710 const pt = cg.pt; 5711 const zcu = cg.module.zcu; 5712 const target = cg.module.zcu.getTarget(); 5713 const switch_br = cg.air.unwrapSwitch(inst); 5714 const cond_ty = cg.typeOf(switch_br.operand); 5715 const cond = try cg.resolve(switch_br.operand); 5716 var cond_indirect = try cg.convertToIndirect(cond_ty, cond); 5717 5718 const cond_words: u32 = switch (cond_ty.zigTypeTag(zcu)) { 5719 .bool, .error_set => 1, 5720 .int => blk: { 5721 const bits = cond_ty.intInfo(zcu).bits; 5722 const backing_bits, const big_int = cg.module.backingIntBits(bits); 5723 if (big_int) return cg.todo("implement composite int switch", .{}); 5724 break :blk if (backing_bits <= 32) 1 else 2; 5725 }, 5726 .@"enum" => blk: { 5727 const int_ty = cond_ty.intTagType(zcu); 5728 const int_info = int_ty.intInfo(zcu); 5729 const backing_bits, const big_int = cg.module.backingIntBits(int_info.bits); 5730 if (big_int) return cg.todo("implement composite int switch", .{}); 5731 break :blk if (backing_bits <= 32) 1 else 2; 5732 }, 5733 .pointer => blk: { 5734 cond_indirect = try cg.intFromPtr(cond_indirect); 5735 break :blk target.ptrBitWidth() / 32; 5736 }, 5737 // TODO: Figure out which types apply here, and work around them as we can only do integers. 5738 else => return cg.todo("implement switch for type {s}", .{@tagName(cond_ty.zigTypeTag(zcu))}), 5739 }; 5740 5741 const num_cases = switch_br.cases_len; 5742 5743 // Compute the total number of arms that we need. 5744 // Zig switches are grouped by condition, so we need to loop through all of them 5745 const num_conditions = blk: { 5746 var num_conditions: u32 = 0; 5747 var it = switch_br.iterateCases(); 5748 while (it.next()) |case| { 5749 if (case.ranges.len > 0) return cg.todo("switch with ranges", .{}); 5750 num_conditions += @intCast(case.items.len); 5751 } 5752 break :blk num_conditions; 5753 }; 5754 5755 // First, pre-allocate the labels for the cases. 5756 const case_labels = cg.module.allocIds(num_cases); 5757 // We always need the default case - if zig has none, we will generate unreachable there. 5758 const default = cg.module.allocId(); 5759 5760 const merge_label = switch (cg.control_flow) { 5761 .structured => cg.module.allocId(), 5762 .unstructured => null, 5763 }; 5764 5765 if (cg.control_flow == .structured) { 5766 try cg.body.emit(gpa, .OpSelectionMerge, .{ 5767 .merge_block = merge_label.?, 5768 .selection_control = .{}, 5769 }); 5770 } 5771 5772 // Emit the instruction before generating the blocks. 5773 try cg.body.emitRaw(gpa, .OpSwitch, 2 + (cond_words + 1) * num_conditions); 5774 cg.body.writeOperand(Id, cond_indirect); 5775 cg.body.writeOperand(Id, default); 5776 5777 // Emit each of the cases 5778 { 5779 var it = switch_br.iterateCases(); 5780 while (it.next()) |case| { 5781 // SPIR-V needs a literal here, which' width depends on the case condition. 5782 const label = case_labels.at(case.idx); 5783 5784 for (case.items) |item| { 5785 const value = (try cg.air.value(item, pt)) orelse unreachable; 5786 const int_val: u64 = switch (cond_ty.zigTypeTag(zcu)) { 5787 .bool, .int => if (cond_ty.isSignedInt(zcu)) @bitCast(value.toSignedInt(zcu)) else value.toUnsignedInt(zcu), 5788 .@"enum" => blk: { 5789 // TODO: figure out of cond_ty is correct (something with enum literals) 5790 break :blk (try value.intFromEnum(cond_ty, pt)).toUnsignedInt(zcu); // TODO: composite integer constants 5791 }, 5792 .error_set => value.getErrorInt(zcu), 5793 .pointer => value.toUnsignedInt(zcu), 5794 else => unreachable, 5795 }; 5796 const int_lit: spec.LiteralContextDependentNumber = switch (cond_words) { 5797 1 => .{ .uint32 = @intCast(int_val) }, 5798 2 => .{ .uint64 = int_val }, 5799 else => unreachable, 5800 }; 5801 cg.body.writeOperand(spec.LiteralContextDependentNumber, int_lit); 5802 cg.body.writeOperand(Id, label); 5803 } 5804 } 5805 } 5806 5807 var incoming_structured_blocks: std.ArrayList(ControlFlow.Structured.Block.Incoming) = .empty; 5808 defer incoming_structured_blocks.deinit(gpa); 5809 5810 if (cg.control_flow == .structured) { 5811 try incoming_structured_blocks.ensureUnusedCapacity(gpa, num_cases + 1); 5812 } 5813 5814 // Now, finally, we can start emitting each of the cases. 5815 var it = switch_br.iterateCases(); 5816 while (it.next()) |case| { 5817 const label = case_labels.at(case.idx); 5818 5819 try cg.beginSpvBlock(label); 5820 5821 switch (cg.control_flow) { 5822 .structured => { 5823 const next_block = try cg.genStructuredBody(.selection, case.body); 5824 incoming_structured_blocks.appendAssumeCapacity(.{ 5825 .src_label = cg.block_label, 5826 .next_block = next_block, 5827 }); 5828 5829 try cg.body.emit(gpa, .OpBranch, .{ .target_label = merge_label.? }); 5830 }, 5831 .unstructured => { 5832 try cg.genBody(case.body); 5833 }, 5834 } 5835 } 5836 5837 const else_body = it.elseBody(); 5838 try cg.beginSpvBlock(default); 5839 if (else_body.len != 0) { 5840 switch (cg.control_flow) { 5841 .structured => { 5842 const next_block = try cg.genStructuredBody(.selection, else_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(else_body); 5852 }, 5853 } 5854 } else { 5855 try cg.body.emit(gpa, .OpUnreachable, {}); 5856 } 5857 5858 if (cg.control_flow == .structured) { 5859 try cg.beginSpvBlock(merge_label.?); 5860 const next_block = try cg.structuredNextBlock(incoming_structured_blocks.items); 5861 try cg.structuredBreak(next_block); 5862 } 5863 } 5864 5865 fn airUnreach(cg: *CodeGen) !void { 5866 try cg.body.emit(cg.module.gpa, .OpUnreachable, {}); 5867 } 5868 5869 fn airDbgStmt(cg: *CodeGen, inst: Air.Inst.Index) !void { 5870 const zcu = cg.module.zcu; 5871 const dbg_stmt = cg.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt; 5872 const path = zcu.navFileScope(cg.owner_nav).sub_file_path; 5873 5874 if (zcu.comp.config.root_strip) return; 5875 5876 try cg.body.emit(cg.module.gpa, .OpLine, .{ 5877 .file = try cg.module.debugString(path), 5878 .line = cg.base_line + dbg_stmt.line + 1, 5879 .column = dbg_stmt.column + 1, 5880 }); 5881 } 5882 5883 fn airDbgInlineBlock(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 5884 const zcu = cg.module.zcu; 5885 const inst_datas = cg.air.instructions.items(.data); 5886 const extra = cg.air.extraData(Air.DbgInlineBlock, inst_datas[@intFromEnum(inst)].ty_pl.payload); 5887 const old_base_line = cg.base_line; 5888 defer cg.base_line = old_base_line; 5889 cg.base_line = zcu.navSrcLine(zcu.funcInfo(extra.data.func).owner_nav); 5890 return cg.lowerBlock(inst, @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.body_len])); 5891 } 5892 5893 fn airDbgVar(cg: *CodeGen, inst: Air.Inst.Index) !void { 5894 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 5895 const target_id = try cg.resolve(pl_op.operand); 5896 const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload); 5897 try cg.module.debugName(target_id, name.toSlice(cg.air)); 5898 } 5899 5900 fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 5901 const gpa = cg.module.gpa; 5902 const zcu = cg.module.zcu; 5903 const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; 5904 const extra = cg.air.extraData(Air.Asm, ty_pl.payload); 5905 5906 const is_volatile = extra.data.flags.is_volatile; 5907 const outputs_len = extra.data.flags.outputs_len; 5908 5909 if (!is_volatile and cg.liveness.isUnused(inst)) return null; 5910 5911 var extra_i: usize = extra.end; 5912 const outputs: []const Air.Inst.Ref = @ptrCast(cg.air.extra.items[extra_i..][0..outputs_len]); 5913 extra_i += outputs.len; 5914 const inputs: []const Air.Inst.Ref = @ptrCast(cg.air.extra.items[extra_i..][0..extra.data.inputs_len]); 5915 extra_i += inputs.len; 5916 5917 if (outputs.len > 1) { 5918 return cg.todo("implement inline asm with more than 1 output", .{}); 5919 } 5920 5921 var ass: Assembler = .{ .cg = cg }; 5922 defer ass.deinit(); 5923 5924 var output_extra_i = extra_i; 5925 for (outputs) |output| { 5926 if (output != .none) { 5927 return cg.todo("implement inline asm with non-returned output", .{}); 5928 } 5929 const extra_bytes = std.mem.sliceAsBytes(cg.air.extra.items[extra_i..]); 5930 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(cg.air.extra.items[extra_i..]), 0); 5931 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); 5932 extra_i += (constraint.len + name.len + (2 + 3)) / 4; 5933 // TODO: Record output and use it somewhere. 5934 } 5935 5936 for (inputs) |input| { 5937 const extra_bytes = std.mem.sliceAsBytes(cg.air.extra.items[extra_i..]); 5938 const constraint = std.mem.sliceTo(extra_bytes, 0); 5939 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); 5940 // This equation accounts for the fact that even if we have exactly 4 bytes 5941 // for the string, we still use the next u32 for the null terminator. 5942 extra_i += (constraint.len + name.len + (2 + 3)) / 4; 5943 5944 const input_ty = cg.typeOf(input); 5945 5946 if (std.mem.eql(u8, constraint, "c")) { 5947 // constant 5948 const val = (try cg.air.value(input, cg.pt)) orelse { 5949 return cg.fail("assembly inputs with 'c' constraint have to be compile-time known", .{}); 5950 }; 5951 5952 // TODO: This entire function should be handled a bit better... 5953 const ip = &zcu.intern_pool; 5954 switch (ip.indexToKey(val.toIntern())) { 5955 .int_type, 5956 .ptr_type, 5957 .array_type, 5958 .vector_type, 5959 .opt_type, 5960 .anyframe_type, 5961 .error_union_type, 5962 .simple_type, 5963 .struct_type, 5964 .union_type, 5965 .opaque_type, 5966 .enum_type, 5967 .func_type, 5968 .error_set_type, 5969 .inferred_error_set_type, 5970 => unreachable, // types, not values 5971 5972 .undef => return cg.fail("assembly input with 'c' constraint cannot be undefined", .{}), 5973 5974 .int => try ass.value_map.put(gpa, name, .{ .constant = @intCast(val.toUnsignedInt(zcu)) }), 5975 .enum_literal => |str| try ass.value_map.put(gpa, name, .{ .string = str.toSlice(ip) }), 5976 5977 else => unreachable, // TODO 5978 } 5979 } else if (std.mem.eql(u8, constraint, "t")) { 5980 // type 5981 if (input_ty.zigTypeTag(zcu) == .type) { 5982 // This assembly input is a type instead of a value. 5983 // That's fine for now, just make sure to resolve it as such. 5984 const val = (try cg.air.value(input, cg.pt)).?; 5985 const ty_id = try cg.resolveType(val.toType(), .direct); 5986 try ass.value_map.put(gpa, name, .{ .ty = ty_id }); 5987 } else { 5988 const ty_id = try cg.resolveType(input_ty, .direct); 5989 try ass.value_map.put(gpa, name, .{ .ty = ty_id }); 5990 } 5991 } else { 5992 if (input_ty.zigTypeTag(zcu) == .type) { 5993 return cg.fail("use the 't' constraint to supply types to SPIR-V inline assembly", .{}); 5994 } 5995 5996 const val_id = try cg.resolve(input); 5997 try ass.value_map.put(gpa, name, .{ .value = val_id }); 5998 } 5999 } 6000 6001 // TODO: do something with clobbers 6002 _ = extra.data.clobbers; 6003 6004 const asm_source = std.mem.sliceAsBytes(cg.air.extra.items[extra_i..])[0..extra.data.source_len]; 6005 6006 ass.assemble(asm_source) catch |err| switch (err) { 6007 error.AssembleFail => { 6008 // TODO: For now the compiler only supports a single error message per decl, 6009 // so to translate the possible multiple errors from the assembler, emit 6010 // them as notes here. 6011 // TODO: Translate proper error locations. 6012 assert(ass.errors.items.len != 0); 6013 assert(cg.error_msg == null); 6014 const src_loc = zcu.navSrcLoc(cg.owner_nav); 6015 cg.error_msg = try Zcu.ErrorMsg.create(zcu.gpa, src_loc, "failed to assemble SPIR-V inline assembly", .{}); 6016 const notes = try zcu.gpa.alloc(Zcu.ErrorMsg, ass.errors.items.len); 6017 6018 // Sub-scope to prevent `return error.CodegenFail` from running the errdefers. 6019 { 6020 errdefer zcu.gpa.free(notes); 6021 var i: usize = 0; 6022 errdefer for (notes[0..i]) |*note| { 6023 note.deinit(zcu.gpa); 6024 }; 6025 6026 while (i < ass.errors.items.len) : (i += 1) { 6027 notes[i] = try Zcu.ErrorMsg.init(zcu.gpa, src_loc, "{s}", .{ass.errors.items[i].msg}); 6028 } 6029 } 6030 cg.error_msg.?.notes = notes; 6031 return error.CodegenFail; 6032 }, 6033 else => |others| return others, 6034 }; 6035 6036 for (outputs) |output| { 6037 _ = output; 6038 const extra_bytes = std.mem.sliceAsBytes(cg.air.extra.items[output_extra_i..]); 6039 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(cg.air.extra.items[output_extra_i..]), 0); 6040 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); 6041 output_extra_i += (constraint.len + name.len + (2 + 3)) / 4; 6042 6043 const result = ass.value_map.get(name) orelse return { 6044 return cg.fail("invalid asm output '{s}'", .{name}); 6045 }; 6046 6047 switch (result) { 6048 .just_declared, .unresolved_forward_reference => unreachable, 6049 .ty => return cg.fail("cannot return spir-v type as value from assembly", .{}), 6050 .value => |ref| return ref, 6051 .constant, .string => return cg.fail("cannot return constant from assembly", .{}), 6052 } 6053 6054 // TODO: Multiple results 6055 // TODO: Check that the output type from assembly is the same as the type actually expected by Zig. 6056 } 6057 6058 return null; 6059 } 6060 6061 fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !?Id { 6062 _ = modifier; 6063 6064 const gpa = cg.module.gpa; 6065 const zcu = cg.module.zcu; 6066 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 6067 const extra = cg.air.extraData(Air.Call, pl_op.payload); 6068 const args: []const Air.Inst.Ref = @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.args_len]); 6069 const callee_ty = cg.typeOf(pl_op.operand); 6070 const zig_fn_ty = switch (callee_ty.zigTypeTag(zcu)) { 6071 .@"fn" => callee_ty, 6072 .pointer => return cg.fail("cannot call function pointers", .{}), 6073 else => unreachable, 6074 }; 6075 const fn_info = zcu.typeToFunc(zig_fn_ty).?; 6076 const return_type = fn_info.return_type; 6077 6078 const result_type_id = try cg.resolveFnReturnType(.fromInterned(return_type)); 6079 const result_id = cg.module.allocId(); 6080 const callee_id = try cg.resolve(pl_op.operand); 6081 6082 comptime assert(zig_call_abi_ver == 3); 6083 6084 const scratch_top = cg.id_scratch.items.len; 6085 defer cg.id_scratch.shrinkRetainingCapacity(scratch_top); 6086 const params = try cg.id_scratch.addManyAsSlice(gpa, args.len); 6087 6088 var n_params: usize = 0; 6089 for (args) |arg| { 6090 // Note: resolve() might emit instructions, so we need to call it 6091 // before starting to emit OpFunctionCall instructions. Hence the 6092 // temporary params buffer. 6093 const arg_ty = cg.typeOf(arg); 6094 if (!arg_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue; 6095 const arg_id = try cg.resolve(arg); 6096 6097 params[n_params] = arg_id; 6098 n_params += 1; 6099 } 6100 6101 try cg.body.emit(gpa, .OpFunctionCall, .{ 6102 .id_result_type = result_type_id, 6103 .id_result = result_id, 6104 .function = callee_id, 6105 .id_ref_3 = params[0..n_params], 6106 }); 6107 6108 if (cg.liveness.isUnused(inst) or !Type.fromInterned(return_type).hasRuntimeBitsIgnoreComptime(zcu)) { 6109 return null; 6110 } 6111 6112 return result_id; 6113 } 6114 6115 fn builtin3D( 6116 cg: *CodeGen, 6117 result_ty: Type, 6118 builtin: spec.BuiltIn, 6119 dimension: u32, 6120 out_of_range_value: anytype, 6121 ) !Id { 6122 const gpa = cg.module.gpa; 6123 if (dimension >= 3) return try cg.constInt(result_ty, out_of_range_value); 6124 const u32_ty_id = try cg.module.intType(.unsigned, 32); 6125 const vec_ty_id = try cg.module.vectorType(3, u32_ty_id); 6126 const ptr_ty_id = try cg.module.ptrType(vec_ty_id, .input); 6127 const spv_decl_index = try cg.module.builtin(ptr_ty_id, builtin, .input); 6128 try cg.module.decl_deps.append(gpa, spv_decl_index); 6129 const ptr_id = cg.module.declPtr(spv_decl_index).result_id; 6130 const vec_id = cg.module.allocId(); 6131 try cg.body.emit(gpa, .OpLoad, .{ 6132 .id_result_type = vec_ty_id, 6133 .id_result = vec_id, 6134 .pointer = ptr_id, 6135 }); 6136 return try cg.extractVectorComponent(result_ty, vec_id, dimension); 6137 } 6138 6139 fn airWorkItemId(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 6140 if (cg.liveness.isUnused(inst)) return null; 6141 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 6142 const dimension = pl_op.payload; 6143 return try cg.builtin3D(.u32, .local_invocation_id, dimension, 0); 6144 } 6145 6146 // TODO: this must be an OpConstant/OpSpec but even then the driver crashes. 6147 fn airWorkGroupSize(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 6148 if (cg.liveness.isUnused(inst)) return null; 6149 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 6150 const dimension = pl_op.payload; 6151 return try cg.builtin3D(.u32, .workgroup_size, dimension, 0); 6152 } 6153 6154 fn airWorkGroupId(cg: *CodeGen, inst: Air.Inst.Index) !?Id { 6155 if (cg.liveness.isUnused(inst)) return null; 6156 const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; 6157 const dimension = pl_op.payload; 6158 return try cg.builtin3D(.u32, .workgroup_id, dimension, 0); 6159 } 6160 6161 fn typeOf(cg: *CodeGen, inst: Air.Inst.Ref) Type { 6162 const zcu = cg.module.zcu; 6163 return cg.air.typeOf(inst, &zcu.intern_pool); 6164 } 6165 6166 fn typeOfIndex(cg: *CodeGen, inst: Air.Inst.Index) Type { 6167 const zcu = cg.module.zcu; 6168 return cg.air.typeOfIndex(inst, &zcu.intern_pool); 6169 }