authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-05-20 19:15:43+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-05-22 16:11:56+02:00
log46184ab85eaf32be6e6fcbaac2202a2d58a37cf7
tree4894de86364818a907c5902c5bff48b776af7df7
parent5edc5f973089aaec5a62c37a3b7d0470a90d45e3

SPIR-V: branching


2 files changed, 152 insertions(+), 6 deletions(-)

src/codegen/spirv.zig+146-6
......@@ -20,6 +20,16 @@ pub const ResultId = u32;
2020pub const TypeMap = std.HashMap(Type, ResultId, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);
2121pub const InstMap = std.AutoHashMap(*Inst, ResultId);
2222
23const IncomingBlock = struct {
24 src_label_id: ResultId,
25 break_value_id: ResultId,
26};
27
28pub const BlockMap = std.AutoHashMap(*Inst.Block, struct {
29 label_id: ResultId,
30 incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock),
31});
32
2333pub fn writeOpcode(code: *std.ArrayList(Word), opcode: Opcode, arg_count: u16) !void {
2434 const word_count: Word = arg_count + 1;
2535 try code.append((word_count << 16) | @enumToInt(opcode));
......@@ -87,6 +97,12 @@ pub const DeclGen = struct {
8797 /// A map keeping track of which instruction generated which result-id.
8898 inst_results: InstMap,
8999
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
90106 /// The decl we are currently generating code for.
91107 decl: *Decl,
92108
......@@ -156,6 +172,11 @@ pub const DeclGen = struct {
156172 return self.inst_results.get(inst).?; // Instruction does not dominate all uses!
157173 }
158174
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
159180 /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need
160181 /// to emulate them in other instructions/types. This function returns, given an integer bit width (signed or unsigned, sign
161182 /// 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 {
325346 else => unreachable,
326347 }
327348 },
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}),
329351 }
330352
331353 return result_id;
......@@ -481,7 +503,7 @@ pub const DeclGen = struct {
481503
482504 // TODO: This could probably be done in a better way...
483505 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);
485507 try self.genBody(func_payload.data.body);
486508
487509 try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionEnd, &[_]Word{});
......@@ -490,7 +512,7 @@ pub const DeclGen = struct {
490512 }
491513 }
492514
493 fn genBody(self: *DeclGen, body: ir.Body) !void {
515 fn genBody(self: *DeclGen, body: ir.Body) Error!void {
494516 for (body.instructions) |inst| {
495517 const maybe_result_id = try self.genInst(inst);
496518 if (maybe_result_id) |result_id|
......@@ -518,16 +540,21 @@ pub const DeclGen = struct {
518540 .not => try self.genUnOp(inst.castTag(.not).?),
519541 .alloc => try self.genAlloc(inst.castTag(.alloc).?),
520542 .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).?),
521546 // TODO: Breakpoints won't be supported in SPIR-V, but the compiler seems to insert them
522547 // throughout the IR.
523548 .breakpoint => null,
549 .condbr => try self.genCondBr(inst.castTag(.condbr).?),
524550 .constant => unreachable,
525551 .dbg_stmt => null,
526552 .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(),
529556 .store => try self.genStore(inst.castTag(.store).?),
530 .unreach => self.genUnreach(),
557 .unreach => try self.genUnreach(),
531558 else => self.fail(inst.src, "TODO: SPIR-V backend: implement inst {s}", .{@tagName(inst.tag)}),
532559 };
533560 }
......@@ -673,6 +700,103 @@ pub const DeclGen = struct {
673700 return self.args.items[self.next_arg_index];
674701 }
675702
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
676800 fn genLoad(self: *DeclGen, inst: *Inst.UnOp) !ResultId {
677801 const operand_id = try self.resolve(inst.operand);
678802
......@@ -689,6 +813,22 @@ pub const DeclGen = struct {
689813 return result_id;
690814 }
691815
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
692832 fn genRet(self: *DeclGen, inst: *Inst.UnOp) !?ResultId {
693833 const operand_id = try self.resolve(inst.operand);
694834 // TODO: This instruction needs to be the last in a block. Is that guaranteed?
src/link/SpirV.zig+6
......@@ -161,12 +161,15 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
161161 .args = std.ArrayList(codegen.Word).init(self.base.allocator),
162162 .next_arg_index = undefined,
163163 .inst_results = codegen.InstMap.init(self.base.allocator),
164 .blocks = codegen.BlockMap.init(self.base.allocator),
165 .current_block_label_id = undefined,
164166 .decl = undefined,
165167 .error_msg = undefined,
166168 };
167169
168170 defer decl_gen.inst_results.deinit();
169171 defer decl_gen.args.deinit();
172 defer decl_gen.blocks.deinit();
170173
171174 for (self.decl_table.items()) |entry| {
172175 const decl = entry.key;
......@@ -175,6 +178,9 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
175178 // Reset the decl_gen, but retain allocated resources.
176179 decl_gen.args.items.len = 0;
177180 decl_gen.next_arg_index = 0;
181 decl_gen.inst_results.clearRetainingCapacity();
182 decl_gen.blocks.clearRetainingCapacity();
183 decl_gen.current_block_label_id = undefined;
178184 decl_gen.decl = decl;
179185 decl_gen.error_msg = null;
180186