| ... | ... | @@ -20,6 +20,16 @@ pub const ResultId = u32; |
| 20 | 20 | pub const TypeMap = std.HashMap(Type, ResultId, Type.hash, Type.eql, std.hash_map.default_max_load_percentage); |
| 21 | 21 | pub const InstMap = std.AutoHashMap(*Inst, ResultId); |
| 22 | 22 | |
| 23 | const IncomingBlock = struct { |
| 24 | src_label_id: ResultId, |
| 25 | break_value_id: ResultId, |
| 26 | }; |
| 27 | |
| 28 | pub const BlockMap = std.AutoHashMap(*Inst.Block, struct { |
| 29 | label_id: ResultId, |
| 30 | incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock), |
| 31 | }); |
| 32 | |
| 23 | 33 | pub fn writeOpcode(code: *std.ArrayList(Word), opcode: Opcode, arg_count: u16) !void { |
| 24 | 34 | const word_count: Word = arg_count + 1; |
| 25 | 35 | try code.append((word_count << 16) | @enumToInt(opcode)); |
| ... | ... | @@ -87,6 +97,12 @@ pub const DeclGen = struct { |
| 87 | 97 | /// A map keeping track of which instruction generated which result-id. |
| 88 | 98 | inst_results: InstMap, |
| 89 | 99 | |
| 100 | /// We need to keep track of result ids for block labels, as well as the 'incoming' blocks for a block. |
| 101 | blocks: BlockMap, |
| 102 | |
| 103 | /// The label of the SPIR-V block we are currently generating. |
| 104 | current_block_label_id: ResultId, |
| 105 | |
| 90 | 106 | /// The decl we are currently generating code for. |
| 91 | 107 | decl: *Decl, |
| 92 | 108 | |
| ... | ... | @@ -156,6 +172,11 @@ pub const DeclGen = struct { |
| 156 | 172 | return self.inst_results.get(inst).?; // Instruction does not dominate all uses! |
| 157 | 173 | } |
| 158 | 174 | |
| 175 | fn beginSPIRVBlock(self: *DeclGen, label_id: ResultId) !void { |
| 176 | try writeInstruction(&self.spv.binary.fn_decls, .OpLabel, &[_]Word{label_id}); |
| 177 | self.current_block_label_id = label_id; |
| 178 | } |
| 179 | |
| 159 | 180 | /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need |
| 160 | 181 | /// to emulate them in other instructions/types. This function returns, given an integer bit width (signed or unsigned, sign |
| 161 | 182 | /// included), the width of the underlying type which represents it, given the enabled features for the current target. |
| ... | ... | @@ -325,7 +346,8 @@ pub const DeclGen = struct { |
| 325 | 346 | else => unreachable, |
| 326 | 347 | } |
| 327 | 348 | }, |
| 328 | | else => return self.fail(src, "TODO: SPIR-V backend: constant generation of type {s}\n", .{ty.zigTypeTag()}), |
| 349 | .Void => unreachable, |
| 350 | else => return self.fail(src, "TODO: SPIR-V backend: constant generation of type {}", .{ty}), |
| 329 | 351 | } |
| 330 | 352 | |
| 331 | 353 | return result_id; |
| ... | ... | @@ -481,7 +503,7 @@ pub const DeclGen = struct { |
| 481 | 503 | |
| 482 | 504 | // TODO: This could probably be done in a better way... |
| 483 | 505 | const root_block_id = self.spv.allocResultId(); |
| 484 | | _ = try writeInstruction(&self.spv.binary.fn_decls, .OpLabel, &[_]Word{root_block_id}); |
| 506 | try self.beginSPIRVBlock(root_block_id); |
| 485 | 507 | try self.genBody(func_payload.data.body); |
| 486 | 508 | |
| 487 | 509 | try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionEnd, &[_]Word{}); |
| ... | ... | @@ -490,7 +512,7 @@ pub const DeclGen = struct { |
| 490 | 512 | } |
| 491 | 513 | } |
| 492 | 514 | |
| 493 | | fn genBody(self: *DeclGen, body: ir.Body) !void { |
| 515 | fn genBody(self: *DeclGen, body: ir.Body) Error!void { |
| 494 | 516 | for (body.instructions) |inst| { |
| 495 | 517 | const maybe_result_id = try self.genInst(inst); |
| 496 | 518 | if (maybe_result_id) |result_id| |
| ... | ... | @@ -518,16 +540,21 @@ pub const DeclGen = struct { |
| 518 | 540 | .not => try self.genUnOp(inst.castTag(.not).?), |
| 519 | 541 | .alloc => try self.genAlloc(inst.castTag(.alloc).?), |
| 520 | 542 | .arg => self.genArg(), |
| 543 | .block => try self.genBlock(inst.castTag(.block).?), |
| 544 | .br => try self.genBr(inst.castTag(.br).?), |
| 545 | .br_void => try self.genBrVoid(inst.castTag(.br_void).?), |
| 521 | 546 | // TODO: Breakpoints won't be supported in SPIR-V, but the compiler seems to insert them |
| 522 | 547 | // throughout the IR. |
| 523 | 548 | .breakpoint => null, |
| 549 | .condbr => try self.genCondBr(inst.castTag(.condbr).?), |
| 524 | 550 | .constant => unreachable, |
| 525 | 551 | .dbg_stmt => null, |
| 526 | 552 | .load => try self.genLoad(inst.castTag(.load).?), |
| 527 | | .ret => self.genRet(inst.castTag(.ret).?), |
| 528 | | .retvoid => self.genRetVoid(), |
| 553 | .loop => try self.genLoop(inst.castTag(.loop).?), |
| 554 | .ret => try self.genRet(inst.castTag(.ret).?), |
| 555 | .retvoid => try self.genRetVoid(), |
| 529 | 556 | .store => try self.genStore(inst.castTag(.store).?), |
| 530 | | .unreach => self.genUnreach(), |
| 557 | .unreach => try self.genUnreach(), |
| 531 | 558 | else => self.fail(inst.src, "TODO: SPIR-V backend: implement inst {s}", .{@tagName(inst.tag)}), |
| 532 | 559 | }; |
| 533 | 560 | } |
| ... | ... | @@ -673,6 +700,103 @@ pub const DeclGen = struct { |
| 673 | 700 | return self.args.items[self.next_arg_index]; |
| 674 | 701 | } |
| 675 | 702 | |
| 703 | fn genBlock(self: *DeclGen, inst: *Inst.Block) !?ResultId { |
| 704 | // In IR, a block doesn't really define an entry point like a block, but more like a scope that breaks can jump out of and |
| 705 | // "return" a value from. This cannot be directly modelled in SPIR-V, so in a block instruction, we're going to split up |
| 706 | // the current block by first generating the code of the block, then a label, and then generate the rest of the current |
| 707 | // ir.Block in a different SPIR-V block. |
| 708 | |
| 709 | const label_id = self.spv.allocResultId(); |
| 710 | |
| 711 | // 4 chosen as arbitrary initial capacity. |
| 712 | var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.module.gpa, 4); |
| 713 | |
| 714 | try self.blocks.putNoClobber(inst, .{ |
| 715 | .label_id = label_id, |
| 716 | .incoming_blocks = &incoming_blocks, |
| 717 | }); |
| 718 | defer { |
| 719 | self.blocks.removeAssertDiscard(inst); |
| 720 | incoming_blocks.deinit(self.module.gpa); |
| 721 | } |
| 722 | |
| 723 | try self.genBody(inst.body); |
| 724 | try self.beginSPIRVBlock(label_id); |
| 725 | |
| 726 | // If this block didn't produce a value, simply return here. |
| 727 | if (!inst.base.ty.hasCodeGenBits()) |
| 728 | return null; |
| 729 | |
| 730 | // Combine the result from the blocks using the Phi instruction. |
| 731 | |
| 732 | const result_id = self.spv.allocResultId(); |
| 733 | |
| 734 | // TODO: OpPhi is limited in the types that it may produce, such as pointers. Figure out which other types |
| 735 | // are not allowed to be created from a phi node, and throw an error for those. For now, genType already throws |
| 736 | // an error for pointers. |
| 737 | const result_type_id = try self.genType(inst.base.src, inst.base.ty); |
| 738 | |
| 739 | try writeOpcode(&self.spv.binary.fn_decls, .OpPhi, 2 + @intCast(u16, incoming_blocks.items.len * 2)); // result type + result + variable/parent... |
| 740 | |
| 741 | for (incoming_blocks.items) |incoming| { |
| 742 | try self.spv.binary.fn_decls.appendSlice(&[_]Word{ incoming.break_value_id, incoming.src_label_id }); |
| 743 | } |
| 744 | |
| 745 | return result_id; |
| 746 | } |
| 747 | |
| 748 | fn genBr(self: *DeclGen, inst: *Inst.Br) !?ResultId { |
| 749 | // TODO: This instruction needs to be the last in a block. Is that guaranteed? |
| 750 | const target = self.blocks.get(inst.block).?; |
| 751 | |
| 752 | // TODO: For some reason, br is emitted with void parameters. |
| 753 | if (inst.operand.ty.hasCodeGenBits()) { |
| 754 | const operand_id = try self.resolve(inst.operand); |
| 755 | // current_block_label_id should not be undefined here, lest there is a br or br_void in the function's body. |
| 756 | try target.incoming_blocks.append(self.module.gpa, .{ |
| 757 | .src_label_id = self.current_block_label_id, |
| 758 | .break_value_id = operand_id |
| 759 | }); |
| 760 | } |
| 761 | |
| 762 | try writeInstruction(&self.spv.binary.fn_decls, .OpBranch, &[_]Word{target.label_id}); |
| 763 | |
| 764 | return null; |
| 765 | } |
| 766 | |
| 767 | fn genBrVoid(self: *DeclGen, inst: *Inst.BrVoid) !?ResultId { |
| 768 | // TODO: This instruction needs to be the last in a block. Is that guaranteed? |
| 769 | const target = self.blocks.get(inst.block).?; |
| 770 | // Don't need to add this to the incoming block list, as there is no value to insert in the phi node anyway. |
| 771 | try writeInstruction(&self.spv.binary.fn_decls, .OpBranch, &[_]Word{target.label_id}); |
| 772 | return null; |
| 773 | } |
| 774 | |
| 775 | fn genCondBr(self: *DeclGen, inst: *Inst.CondBr) !?ResultId { |
| 776 | // TODO: This instruction needs to be the last in a block. Is that guaranteed? |
| 777 | const condition_id = try self.resolve(inst.condition); |
| 778 | |
| 779 | // These will always generate a new SPIR-V block, since they are ir.Body and not ir.Block. |
| 780 | const then_label_id = self.spv.allocResultId(); |
| 781 | const else_label_id = self.spv.allocResultId(); |
| 782 | |
| 783 | // TODO: We can generate OpSelectionMerge here if we know the target block that both of these will resolve to, |
| 784 | // but i don't know if those will always resolve to the same block. |
| 785 | |
| 786 | try writeInstruction(&self.spv.binary.fn_decls, .OpBranchConditional, &[_]Word{ |
| 787 | condition_id, |
| 788 | then_label_id, |
| 789 | else_label_id, |
| 790 | }); |
| 791 | |
| 792 | try self.beginSPIRVBlock(then_label_id); |
| 793 | try self.genBody(inst.then_body); |
| 794 | try self.beginSPIRVBlock(else_label_id); |
| 795 | try self.genBody(inst.else_body); |
| 796 | |
| 797 | return null; |
| 798 | } |
| 799 | |
| 676 | 800 | fn genLoad(self: *DeclGen, inst: *Inst.UnOp) !ResultId { |
| 677 | 801 | const operand_id = try self.resolve(inst.operand); |
| 678 | 802 | |
| ... | ... | @@ -689,6 +813,22 @@ pub const DeclGen = struct { |
| 689 | 813 | return result_id; |
| 690 | 814 | } |
| 691 | 815 | |
| 816 | fn genLoop(self: *DeclGen, inst: *Inst.Loop) !?ResultId { |
| 817 | // TODO: This instruction needs to be the last in a block. Is that guaranteed? |
| 818 | const loop_label_id = self.spv.allocResultId(); |
| 819 | |
| 820 | // Jump to the loop entry point |
| 821 | try writeInstruction(&self.spv.binary.fn_decls, .OpBranch, &[_]Word{ loop_label_id }); |
| 822 | |
| 823 | // TODO: Look into OpLoopMerge. |
| 824 | |
| 825 | try self.beginSPIRVBlock(loop_label_id); |
| 826 | try self.genBody(inst.body); |
| 827 | |
| 828 | try writeInstruction(&self.spv.binary.fn_decls, .OpBranch, &[_]Word{ loop_label_id }); |
| 829 | return null; |
| 830 | } |
| 831 | |
| 692 | 832 | fn genRet(self: *DeclGen, inst: *Inst.UnOp) !?ResultId { |
| 693 | 833 | const operand_id = try self.resolve(inst.operand); |
| 694 | 834 | // TODO: This instruction needs to be the last in a block. Is that guaranteed? |