| ... | @@ -20,6 +20,16 @@ pub const ResultId = u32; | ... | @@ -20,6 +20,16 @@ pub const ResultId = u32; |
| 20 | pub const TypeMap = std.HashMap(Type, ResultId, Type.hash, Type.eql, std.hash_map.default_max_load_percentage); | 20 | pub const TypeMap = std.HashMap(Type, ResultId, Type.hash, Type.eql, std.hash_map.default_max_load_percentage); |
| 21 | pub const InstMap = std.AutoHashMap(*Inst, ResultId); | 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 | pub fn writeOpcode(code: *std.ArrayList(Word), opcode: Opcode, arg_count: u16) !void { | 33 | pub fn writeOpcode(code: *std.ArrayList(Word), opcode: Opcode, arg_count: u16) !void { |
| 24 | const word_count: Word = arg_count + 1; | 34 | const word_count: Word = arg_count + 1; |
| 25 | try code.append((word_count << 16) | @enumToInt(opcode)); | 35 | try code.append((word_count << 16) | @enumToInt(opcode)); |
| ... | @@ -87,6 +97,12 @@ pub const DeclGen = struct { | ... | @@ -87,6 +97,12 @@ pub const DeclGen = struct { |
| 87 | /// A map keeping track of which instruction generated which result-id. | 97 | /// A map keeping track of which instruction generated which result-id. |
| 88 | inst_results: InstMap, | 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 | /// The decl we are currently generating code for. | 106 | /// The decl we are currently generating code for. |
| 91 | decl: *Decl, | 107 | decl: *Decl, |
| 92 | | 108 | |
| ... | @@ -156,6 +172,11 @@ pub const DeclGen = struct { | ... | @@ -156,6 +172,11 @@ pub const DeclGen = struct { |
| 156 | return self.inst_results.get(inst).?; // Instruction does not dominate all uses! | 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 | /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need | 180 | /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need |
| 160 | /// to emulate them in other instructions/types. This function returns, given an integer bit width (signed or unsigned, sign | 181 | /// to emulate them in other instructions/types. This function returns, given an integer bit width (signed or unsigned, sign |
| 161 | /// included), the width of the underlying type which represents it, given the enabled features for the current target. | 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,7 +346,8 @@ pub const DeclGen = struct { |
| 325 | else => unreachable, | 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 | return result_id; | 353 | return result_id; |
| ... | @@ -481,7 +503,7 @@ pub const DeclGen = struct { | ... | @@ -481,7 +503,7 @@ pub const DeclGen = struct { |
| 481 | | 503 | |
| 482 | // TODO: This could probably be done in a better way... | 504 | // TODO: This could probably be done in a better way... |
| 483 | const root_block_id = self.spv.allocResultId(); | 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 | try self.genBody(func_payload.data.body); | 507 | try self.genBody(func_payload.data.body); |
| 486 | | 508 | |
| 487 | try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionEnd, &[_]Word{}); | 509 | try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionEnd, &[_]Word{}); |
| ... | @@ -490,7 +512,7 @@ pub const DeclGen = struct { | ... | @@ -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 | for (body.instructions) |inst| { | 516 | for (body.instructions) |inst| { |
| 495 | const maybe_result_id = try self.genInst(inst); | 517 | const maybe_result_id = try self.genInst(inst); |
| 496 | if (maybe_result_id) |result_id| | 518 | if (maybe_result_id) |result_id| |
| ... | @@ -518,16 +540,21 @@ pub const DeclGen = struct { | ... | @@ -518,16 +540,21 @@ pub const DeclGen = struct { |
| 518 | .not => try self.genUnOp(inst.castTag(.not).?), | 540 | .not => try self.genUnOp(inst.castTag(.not).?), |
| 519 | .alloc => try self.genAlloc(inst.castTag(.alloc).?), | 541 | .alloc => try self.genAlloc(inst.castTag(.alloc).?), |
| 520 | .arg => self.genArg(), | 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 | // TODO: Breakpoints won't be supported in SPIR-V, but the compiler seems to insert them | 546 | // TODO: Breakpoints won't be supported in SPIR-V, but the compiler seems to insert them |
| 522 | // throughout the IR. | 547 | // throughout the IR. |
| 523 | .breakpoint => null, | 548 | .breakpoint => null, |
| | 549 | .condbr => try self.genCondBr(inst.castTag(.condbr).?), |
| 524 | .constant => unreachable, | 550 | .constant => unreachable, |
| 525 | .dbg_stmt => null, | 551 | .dbg_stmt => null, |
| 526 | .load => try self.genLoad(inst.castTag(.load).?), | 552 | .load => try self.genLoad(inst.castTag(.load).?), |
| 527 | .ret => self.genRet(inst.castTag(.ret).?), | 553 | .loop => try self.genLoop(inst.castTag(.loop).?), |
| 528 | .retvoid => self.genRetVoid(), | 554 | .ret => try self.genRet(inst.castTag(.ret).?), |
| | 555 | .retvoid => try self.genRetVoid(), |
| 529 | .store => try self.genStore(inst.castTag(.store).?), | 556 | .store => try self.genStore(inst.castTag(.store).?), |
| 530 | .unreach => self.genUnreach(), | 557 | .unreach => try self.genUnreach(), |
| 531 | else => self.fail(inst.src, "TODO: SPIR-V backend: implement inst {s}", .{@tagName(inst.tag)}), | 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,6 +700,103 @@ pub const DeclGen = struct { |
| 673 | return self.args.items[self.next_arg_index]; | 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 | fn genLoad(self: *DeclGen, inst: *Inst.UnOp) !ResultId { | 800 | fn genLoad(self: *DeclGen, inst: *Inst.UnOp) !ResultId { |
| 677 | const operand_id = try self.resolve(inst.operand); | 801 | const operand_id = try self.resolve(inst.operand); |
| 678 | | 802 | |
| ... | @@ -689,6 +813,22 @@ pub const DeclGen = struct { | ... | @@ -689,6 +813,22 @@ pub const DeclGen = struct { |
| 689 | return result_id; | 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 | fn genRet(self: *DeclGen, inst: *Inst.UnOp) !?ResultId { | 832 | fn genRet(self: *DeclGen, inst: *Inst.UnOp) !?ResultId { |
| 693 | const operand_id = try self.resolve(inst.operand); | 833 | const operand_id = try self.resolve(inst.operand); |
| 694 | // TODO: This instruction needs to be the last in a block. Is that guaranteed? | 834 | // TODO: This instruction needs to be the last in a block. Is that guaranteed? |