authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-11-22 22:16:28+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-11-24 17:50:11+01:00
logdecff512383e8ea8a52583ba0e39617fa24dc79d
tree9921c53dbdb7a1d5b0f552e9d3adb6a7343c8cae
parentb4b1c4df640c9b40c303eef7d0364d01ec490a8e
signaturebadge-check Signed by SSH key SHA256:CQ99aPxq+RueiL9u7z0FEki5Fm7V6T8q4PrEGmINrA4

spirv: structured control flow


2 files changed, 603 insertions(+), 110 deletions(-)

src/codegen/spirv.zig+593-110
...@@ -40,17 +40,109 @@ const SpvTypeInfo = struct {...@@ -40,17 +40,109 @@ const SpvTypeInfo = struct {
4040
41const TypeMap = std.AutoHashMapUnmanaged(InternPool.Index, SpvTypeInfo);41const TypeMap = std.AutoHashMapUnmanaged(InternPool.Index, SpvTypeInfo);
4242
43const IncomingBlock = struct {43const ControlFlow = union(enum) {
44 src_label_id: IdRef,44 const Structured = struct {
45 break_value_id: IdRef,45 /// This type indicates the way that a block is terminated. The
46};46 /// state of a particular block is used to track how a jump from
47 /// inside the block must reach the outside.
48 const Block = union(enum) {
49 const Incoming = struct {
50 src_label: IdRef,
51 /// Instruction that returns an u32 value of the
52 /// `Air.Inst.Index` that control flow should jump to.
53 next_block: IdRef,
54 };
4755
48const Block = struct {56 const SelectionMerge = struct {
49 label_id: ?IdRef,57 /// Incoming block from the `then` label.
50 incoming_blocks: std.ArrayListUnmanaged(IncomingBlock),58 /// Note that hte incoming block from the `else` label is
51};59 /// either given by the next element in the stack.
60 incoming: Incoming,
61 /// The label id of the cond_br's merge block.
62 /// For the top-most element in the stack, this
63 /// value is undefined.
64 merge_block: IdRef,
65 };
66
67 /// For a `selection` type block, we cannot use early exits, and we
68 /// must generate a 'merge ladder' of OpSelection instructions. To that end,
69 /// we keep a stack of the merges that still must be closed at the end of
70 /// a block.
71 ///
72 /// This entire structure basically just resembles a tree like
73 /// a x
74 /// \ /
75 /// b o merge
76 /// \ /
77 /// c o merge
78 /// \ /
79 /// o merge
80 /// /
81 /// o jump to next block
82 selection: struct {
83 /// In order to know which merges we still need to do, we need to keep
84 /// a stack of those.
85 merge_stack: std.ArrayListUnmanaged(SelectionMerge) = .{},
86 },
87 /// For a `loop` type block, we can early-exit the block by
88 /// jumping to the loop exit node, and we don't need to generate
89 /// an entire stack of merges.
90 loop: struct {
91 /// The next block to jump to can be determined from any number
92 /// of conditions that jump to the loop exit.
93 merges: std.ArrayListUnmanaged(Incoming) = .{},
94 /// The label id of the loop's merge block.
95 merge_block: IdRef,
96 },
97
98 fn deinit(self: *Structured.Block, a: Allocator) void {
99 switch (self.*) {
100 .selection => |*merge| merge.merge_stack.deinit(a),
101 .loop => |*merge| merge.merges.deinit(a),
102 }
103 self.* = undefined;
104 }
105 };
106 /// The stack of (structured) blocks that we are currently in. This determines
107 /// how exits from the current block must be handled.
108 block_stack: std.ArrayListUnmanaged(*Structured.Block) = .{},
109 /// Maps `block` inst indices to the variable that the block's result
110 /// value must be written to.
111 block_results: std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef) = .{},
112 };
52113
53const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, *Block);114 const Unstructured = struct {
115 const Incoming = struct {
116 src_label: IdRef,
117 break_value_id: IdRef,
118 };
119
120 const Block = struct {
121 label: ?IdRef = null,
122 incoming_blocks: std.ArrayListUnmanaged(Incoming) = .{},
123 };
124
125 /// We need to keep track of result ids for block labels, as well as the 'incoming'
126 /// blocks for a block.
127 blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, *Block) = .{},
128 };
129
130 structured: Structured,
131 unstructured: Unstructured,
132
133 pub fn deinit(self: *ControlFlow, a: Allocator) void {
134 switch (self.*) {
135 .structured => |*cf| {
136 cf.block_stack.deinit(a);
137 cf.block_results.deinit(a);
138 },
139 .unstructured => |*cf| {
140 cf.blocks.deinit(a);
141 },
142 }
143 self.* = undefined;
144 }
145};
54146
55/// This structure holds information that is relevant to the entire compilation,147/// This structure holds information that is relevant to the entire compilation,
56/// in contrast to `DeclGen`, which only holds relevant information about a148/// in contrast to `DeclGen`, which only holds relevant information about a
...@@ -106,7 +198,6 @@ pub const Object = struct {...@@ -106,7 +198,6 @@ pub const Object = struct {
106 .opencl => mod.comp.bin_file.options.want_structured_cfg orelse false,198 .opencl => mod.comp.bin_file.options.want_structured_cfg orelse false,
107 else => true,199 else => true,
108 };200 };
109 _ = want_structured_cfg;
110201
111 var decl_gen = DeclGen{202 var decl_gen = DeclGen{
112 .gpa = self.gpa,203 .gpa = self.gpa,
...@@ -117,7 +208,11 @@ pub const Object = struct {...@@ -117,7 +208,11 @@ pub const Object = struct {
117 .air = air,208 .air = air,
118 .liveness = liveness,209 .liveness = liveness,
119 .type_map = &self.type_map,210 .type_map = &self.type_map,
120 .current_block_label_id = undefined,211 .control_flow = switch (want_structured_cfg) {
212 true => .{ .structured = .{} },
213 false => .{ .unstructured = .{} },
214 },
215 .current_block_label = undefined,
121 };216 };
122 defer decl_gen.deinit();217 defer decl_gen.deinit();
123218
...@@ -222,12 +317,11 @@ const DeclGen = struct {...@@ -222,12 +317,11 @@ const DeclGen = struct {
222 /// is already in this map, its recursive.317 /// is already in this map, its recursive.
223 wip_pointers: std.AutoHashMapUnmanaged(struct { InternPool.Index, StorageClass }, CacheRef) = .{},318 wip_pointers: std.AutoHashMapUnmanaged(struct { InternPool.Index, StorageClass }, CacheRef) = .{},
224319
225 /// We need to keep track of result ids for block labels, as well as the 'incoming'320 /// This field keeps track of the current state wrt structured or unstructured control flow.
226 /// blocks for a block.321 control_flow: ControlFlow,
227 blocks: BlockMap = .{},
228322
229 /// The label of the SPIR-V block we are currently generating.323 /// The label of the SPIR-V block we are currently generating.
230 current_block_label_id: IdRef,324 current_block_label: IdRef,
231325
232 /// The code (prologue and body) for the function we are currently generating code for.326 /// The code (prologue and body) for the function we are currently generating code for.
233 func: SpvModule.Fn = .{},327 func: SpvModule.Fn = .{},
...@@ -309,7 +403,7 @@ const DeclGen = struct {...@@ -309,7 +403,7 @@ const DeclGen = struct {
309 self.args.deinit(self.gpa);403 self.args.deinit(self.gpa);
310 self.inst_results.deinit(self.gpa);404 self.inst_results.deinit(self.gpa);
311 self.wip_pointers.deinit(self.gpa);405 self.wip_pointers.deinit(self.gpa);
312 self.blocks.deinit(self.gpa);406 self.control_flow.deinit(self.gpa);
313 self.func.deinit(self.gpa);407 self.func.deinit(self.gpa);
314 self.base_line_stack.deinit(self.gpa);408 self.base_line_stack.deinit(self.gpa);
315 }409 }
...@@ -393,10 +487,11 @@ const DeclGen = struct {...@@ -393,10 +487,11 @@ const DeclGen = struct {
393 // TODO: This should probably be made a little more robust.487 // TODO: This should probably be made a little more robust.
394 const func = self.func;488 const func = self.func;
395 defer self.func = func;489 defer self.func = func;
396 const block_label_id = self.current_block_label_id;490 const block_label = self.current_block_label;
397 defer self.current_block_label_id = block_label_id;491 defer self.current_block_label = block_label;
398492
399 self.func = .{};493 self.func = .{};
494 defer self.func.deinit(self.gpa);
400495
401 // TODO: Merge this with genDecl?496 // TODO: Merge this with genDecl?
402 const begin = self.spv.beginGlobal();497 const begin = self.spv.beginGlobal();
...@@ -418,7 +513,7 @@ const DeclGen = struct {...@@ -418,7 +513,7 @@ const DeclGen = struct {
418 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{513 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
419 .id_result = root_block_id,514 .id_result = root_block_id,
420 });515 });
421 self.current_block_label_id = root_block_id;516 self.current_block_label = root_block_id;
422517
423 const val_id = try self.constant(ty, val.toValue(), .indirect);518 const val_id = try self.constant(ty, val.toValue(), .indirect);
424 try self.func.body.emit(self.spv.gpa, .OpStore, .{519 try self.func.body.emit(self.spv.gpa, .OpStore, .{
...@@ -441,9 +536,9 @@ const DeclGen = struct {...@@ -441,9 +536,9 @@ const DeclGen = struct {
441 /// block we are currently generating.536 /// block we are currently generating.
442 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to537 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to
443 /// keep track of the previous block.538 /// keep track of the previous block.
444 fn beginSpvBlock(self: *DeclGen, label_id: IdResult) !void {539 fn beginSpvBlock(self: *DeclGen, label: IdResult) !void {
445 try self.func.body.emit(self.spv.gpa, .OpLabel, .{ .id_result = label_id });540 try self.func.body.emit(self.spv.gpa, .OpLabel, .{ .id_result = label });
446 self.current_block_label_id = label_id;541 self.current_block_label = label;
447 }542 }
448543
449 /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need544 /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need
...@@ -1792,13 +1887,22 @@ const DeclGen = struct {...@@ -1792,13 +1887,22 @@ const DeclGen = struct {
1792 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{1887 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
1793 .id_result = root_block_id,1888 .id_result = root_block_id,
1794 });1889 });
1795 self.current_block_label_id = root_block_id;1890 self.current_block_label = root_block_id;
17961891
1797 const main_body = self.air.getMainBody();1892 const main_body = self.air.getMainBody();
1798 try self.genBody(main_body);1893 switch (self.control_flow) {
17991894 .structured => {
1800 // Append the actual code into the functions section.1895 _ = try self.genStructuredBody(.selection, main_body);
1896 // We always expect paths to here to end, but we still need the block
1897 // to act as a dummy merge block.
1898 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});
1899 },
1900 .unstructured => {
1901 try self.genBody(main_body);
1902 },
1903 }
1801 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});1904 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
1905 // Append the actual code into the functions section.
1802 try self.spv.addFunction(spv_decl_index, self.func);1906 try self.spv.addFunction(spv_decl_index, self.func);
18031907
1804 const fqn = ip.stringToSlice(try decl.getFullyQualifiedName(self.module));1908 const fqn = ip.stringToSlice(try decl.getFullyQualifiedName(self.module));
...@@ -1856,7 +1960,7 @@ const DeclGen = struct {...@@ -1856,7 +1960,7 @@ const DeclGen = struct {
1856 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{1960 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
1857 .id_result = root_block_id,1961 .id_result = root_block_id,
1858 });1962 });
1859 self.current_block_label_id = root_block_id;1963 self.current_block_label = root_block_id;
18601964
1861 const val_id = try self.constant(decl.ty, init_val, .indirect);1965 const val_id = try self.constant(decl.ty, init_val, .indirect);
1862 try self.func.body.emit(self.spv.gpa, .OpStore, .{1966 try self.func.body.emit(self.spv.gpa, .OpStore, .{
...@@ -3655,6 +3759,154 @@ const DeclGen = struct {...@@ -3655,6 +3759,154 @@ const DeclGen = struct {
3655 return self.args.items[self.next_arg_index];3759 return self.args.items[self.next_arg_index];
3656 }3760 }
36573761
3762 /// Given a slice of incoming block connections, returns the block-id of the next
3763 /// block to jump to. This function emits instructions, so it should be emitted
3764 /// inside the merge block of the block.
3765 /// This function should only be called with structured control flow generation.
3766 fn structuredNextBlock(self: *DeclGen, incoming: []const ControlFlow.Structured.Block.Incoming) !IdRef {
3767 assert(self.control_flow == .structured);
3768
3769 const result_id = self.spv.allocId();
3770 const block_id_ty_ref = try self.intType(.unsigned, 32);
3771 try self.func.body.emitRaw(self.spv.gpa, .OpPhi, @intCast(2 + incoming.len * 2)); // result type + result + variable/parent...
3772 self.func.body.writeOperand(spec.IdResultType, self.typeId(block_id_ty_ref));
3773 self.func.body.writeOperand(spec.IdRef, result_id);
3774
3775 for (incoming) |incoming_block| {
3776 self.func.body.writeOperand(spec.PairIdRefIdRef, .{ incoming_block.next_block, incoming_block.src_label });
3777 }
3778
3779 return result_id;
3780 }
3781
3782 /// Jumps to the block with the target block-id. This function must only be called when
3783 /// terminating a body, there should be no instructions after it.
3784 /// This function should only be called with structured control flow generation.
3785 fn structuredBreak(self: *DeclGen, target_block: IdRef) !void {
3786 assert(self.control_flow == .structured);
3787
3788 const sblock = self.control_flow.structured.block_stack.getLast();
3789 const merge_block = switch (sblock.*) {
3790 .selection => |*merge| blk: {
3791 const merge_label = self.spv.allocId();
3792 try merge.merge_stack.append(self.gpa, .{
3793 .incoming = .{
3794 .src_label = self.current_block_label,
3795 .next_block = target_block,
3796 },
3797 .merge_block = merge_label,
3798 });
3799 break :blk merge_label;
3800 },
3801 // Loop blocks do not end in a break. Not through a direct break,
3802 // and also not through another instruction like cond_br or unreachable (these
3803 // situations are replaced by `cond_br` in sema, or there is a `block` instruction
3804 // placed around them).
3805 .loop => unreachable,
3806 };
3807
3808 try self.func.body.emitBranch(self.spv.gpa, merge_block);
3809 }
3810
3811 /// Generate a body in a way that exits the body using only structured constructs.
3812 /// Returns the block-id of the next block to jump to. After this function, a jump
3813 /// should still be emitted to the block that should follow this structured body.
3814 /// This function should only be called with structured control flow generation.
3815 fn genStructuredBody(
3816 self: *DeclGen,
3817 /// This parameter defines the method that this structured body is exited with.
3818 block_merge_type: union(enum) {
3819 /// Using selection; early exits from this body are surrounded with
3820 /// if() statements.
3821 selection,
3822 /// Using loops; loops can be early exited by jumping to the merge block at
3823 /// any time.
3824 loop: struct {
3825 merge_label: IdRef,
3826 continue_label: IdRef,
3827 },
3828 },
3829 body: []const Air.Inst.Index,
3830 ) !IdRef {
3831 assert(self.control_flow == .structured);
3832
3833 var sblock: ControlFlow.Structured.Block = switch (block_merge_type) {
3834 .loop => |merge| .{ .loop = .{
3835 .merge_block = merge.merge_label,
3836 } },
3837 .selection => .{ .selection = .{} },
3838 };
3839 defer sblock.deinit(self.gpa);
3840
3841 {
3842 try self.control_flow.structured.block_stack.append(self.gpa, &sblock);
3843 defer _ = self.control_flow.structured.block_stack.pop();
3844
3845 try self.genBody(body);
3846 }
3847
3848 switch (sblock) {
3849 .selection => |merge| {
3850 // Now generate the merge block for all merges that
3851 // still need to be performed.
3852 const merge_stack = merge.merge_stack.items;
3853
3854 // If no merges on the stack, this block didn't generate any jumps (all paths
3855 // ended with a return or an unreachable). In that case, we don't need to do
3856 // any merging.
3857 if (merge_stack.len == 0) {
3858 // We still need to return a value of a next block to jump to.
3859 // For example, if we have code like
3860 // if (x) {
3861 // if (y) return else return;
3862 // } else {}
3863 // then we still need the outer to have an OpSelectionMerge and consequently
3864 // a phi node. In that case we can just return bogus, since we know that its
3865 // path will never be taken.
3866
3867 // Make sure that we are still in a block when exiting the function.
3868 // TODO: Can we get rid of that?
3869 try self.beginSpvBlock(self.spv.allocId());
3870 const block_id_ty_ref = try self.intType(.unsigned, 32);
3871 return try self.spv.constUndef(block_id_ty_ref);
3872 }
3873
3874 // The top-most merge actually only has a single source, the
3875 // final jump of the block, or the merge block of a sub-block, cond_br,
3876 // or loop. Therefore we just need to generate a block with a jump to the
3877 // next merge block.
3878 try self.beginSpvBlock(merge_stack[merge_stack.len - 1].merge_block);
3879
3880 // Now generate a merge ladder for the remaining merges in the stack.
3881 var incoming = ControlFlow.Structured.Block.Incoming{
3882 .src_label = self.current_block_label,
3883 .next_block = merge_stack[merge_stack.len - 1].incoming.next_block,
3884 };
3885 var i = merge_stack.len - 1;
3886 while (i > 0) {
3887 i -= 1;
3888 const step = merge_stack[i];
3889 try self.func.body.emitBranch(self.spv.gpa, step.merge_block);
3890 try self.beginSpvBlock(step.merge_block);
3891 const next_block = try self.structuredNextBlock(&.{ incoming, step.incoming });
3892 incoming = .{
3893 .src_label = step.merge_block,
3894 .next_block = next_block,
3895 };
3896 }
3897
3898 return incoming.next_block;
3899 },
3900 .loop => |merge| {
3901 // Close the loop by jumping to the continue label
3902 try self.func.body.emitBranch(self.spv.gpa, block_merge_type.loop.continue_label);
3903 // For blocks we must simple merge all the incoming blocks to get the next block.
3904 try self.beginSpvBlock(merge.merge_block);
3905 return try self.structuredNextBlock(merge.merges.items);
3906 },
3907 }
3908 }
3909
3658 fn airBlock(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {3910 fn airBlock(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3659 // In AIR, a block doesn't really define an entry point like a block, but3911 // In AIR, a block doesn't really define an entry point like a block, but
3660 // more like a scope that breaks can jump out of and "return" a value from.3912 // more like a scope that breaks can jump out of and "return" a value from.
...@@ -3670,62 +3922,170 @@ const DeclGen = struct {...@@ -3670,62 +3922,170 @@ const DeclGen = struct {
3670 const body = self.air.extra[extra.end..][0..extra.data.body_len];3922 const body = self.air.extra[extra.end..][0..extra.data.body_len];
3671 const have_block_result = ty.isFnOrHasRuntimeBitsIgnoreComptime(mod);3923 const have_block_result = ty.isFnOrHasRuntimeBitsIgnoreComptime(mod);
36723924
3673 // 4 chosen as arbitrary initial capacity.3925 const cf = switch (self.control_flow) {
3674 var block = Block{3926 .structured => |*cf| cf,
3675 // Label id is lazily allocated if needed.3927 .unstructured => |*cf| {
3676 .label_id = null,3928 var block = ControlFlow.Unstructured.Block{};
3677 .incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.gpa, 4),3929 defer block.incoming_blocks.deinit(self.gpa);
3930
3931 // 4 chosen as arbitrary initial capacity.
3932 try block.incoming_blocks.ensureUnusedCapacity(self.gpa, 4);
3933
3934 try cf.blocks.putNoClobber(self.gpa, inst, &block);
3935 defer assert(cf.blocks.remove(inst));
3936
3937 try self.genBody(body);
3938
3939 // Only begin a new block if there were actually any breaks towards it.
3940 if (block.label) |label| {
3941 try self.beginSpvBlock(label);
3942 }
3943
3944 if (!have_block_result)
3945 return null;
3946
3947 assert(block.label != null);
3948 const result_id = self.spv.allocId();
3949 const result_type_id = try self.resolveTypeId(ty);
3950
3951 try self.func.body.emitRaw(
3952 self.spv.gpa,
3953 .OpPhi,
3954 // result type + result + variable/parent...
3955 2 + @as(u16, @intCast(block.incoming_blocks.items.len * 2)),
3956 );
3957 self.func.body.writeOperand(spec.IdResultType, result_type_id);
3958 self.func.body.writeOperand(spec.IdRef, result_id);
3959
3960 for (block.incoming_blocks.items) |incoming| {
3961 self.func.body.writeOperand(
3962 spec.PairIdRefIdRef,
3963 .{ incoming.break_value_id, incoming.src_label },
3964 );
3965 }
3966
3967 return result_id;
3968 },
3678 };3969 };
3679 defer block.incoming_blocks.deinit(self.gpa);
36803970
3681 try self.blocks.putNoClobber(self.gpa, inst, &block);3971 const maybe_block_result_var_id = if (have_block_result) blk: {
3682 defer assert(self.blocks.remove(inst));3972 const block_result_var_id = try self.alloc(ty, .{ .storage_class = .Function });
3973 try cf.block_results.putNoClobber(self.gpa, inst, block_result_var_id);
3974 break :blk block_result_var_id;
3975 } else null;
3976 defer if (have_block_result) assert(cf.block_results.remove(inst));
36833977
3684 try self.genBody(body);3978 const next_block = try self.genStructuredBody(.selection, body);
36853979
3686 // Only begin a new block if there were actually any breaks towards it.3980 // When encountering a block instruction, we are always at least in the function's scope,
3687 if (block.label_id) |label_id| {3981 // so there always has to be another entry.
3688 try self.beginSpvBlock(label_id);3982 assert(cf.block_stack.items.len > 0);
3689 }
36903983
3691 if (!have_block_result)3984 // Check if the target of the branch was this current block.
3692 return null;3985 const block_id_ty_ref = try self.intType(.unsigned, 32);
3986 const this_block = try self.constInt(block_id_ty_ref, inst);
3987 const jump_to_this_block_id = self.spv.allocId();
3988 const bool_ty_ref = try self.resolveType(Type.bool, .direct);
3989 try self.func.body.emit(self.spv.gpa, .OpIEqual, .{
3990 .id_result_type = self.typeId(bool_ty_ref),
3991 .id_result = jump_to_this_block_id,
3992 .operand_1 = next_block,
3993 .operand_2 = this_block,
3994 });
36933995
3694 assert(block.label_id != null);3996 const sblock = cf.block_stack.getLast();
3695 const result_id = self.spv.allocId();
3696 const result_type_id = try self.resolveTypeId(ty);
36973997
3698 try self.func.body.emitRaw(self.spv.gpa, .OpPhi, 2 + @as(u16, @intCast(block.incoming_blocks.items.len * 2))); // result type + result + variable/parent...3998 if (ty.isNoReturn(mod)) {
3699 self.func.body.writeOperand(spec.IdResultType, result_type_id);3999 // If this block is noreturn, this instruction is the last of a block,
3700 self.func.body.writeOperand(spec.IdRef, result_id);4000 // and we must simply jump to the block's merge unconditionally.
4001 try self.structuredBreak(next_block);
4002 } else {
4003 switch (sblock.*) {
4004 .selection => |*merge| {
4005 // To jump out of a selection block, push a new entry onto its merge stack and
4006 // generate a conditional branch to there and to the instructions following this block.
4007 const merge_label = self.spv.allocId();
4008 const then_label = self.spv.allocId();
4009 try self.func.body.emit(self.spv.gpa, .OpSelectionMerge, .{
4010 .merge_block = merge_label,
4011 .selection_control = .{},
4012 });
4013 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
4014 .condition = jump_to_this_block_id,
4015 .true_label = then_label,
4016 .false_label = merge_label,
4017 });
4018 try merge.merge_stack.append(self.gpa, .{
4019 .incoming = .{
4020 .src_label = self.current_block_label,
4021 .next_block = next_block,
4022 },
4023 .merge_block = merge_label,
4024 });
37014025
3702 for (block.incoming_blocks.items) |incoming| {4026 try self.beginSpvBlock(then_label);
3703 self.func.body.writeOperand(spec.PairIdRefIdRef, .{ incoming.break_value_id, incoming.src_label_id });4027 },
4028 .loop => |*merge| {
4029 // To jump out of a loop block, generate a conditional that exits the block
4030 // to the loop merge if the target ID is not the one of this block.
4031 const continue_label = self.spv.allocId();
4032 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
4033 .condition = jump_to_this_block_id,
4034 .true_label = continue_label,
4035 .false_label = merge.merge_block,
4036 });
4037 try merge.merges.append(self.gpa, .{
4038 .src_label = self.current_block_label,
4039 .next_block = next_block,
4040 });
4041 try self.beginSpvBlock(continue_label);
4042 },
4043 }
3704 }4044 }
37054045
3706 return result_id;4046 if (maybe_block_result_var_id) |block_result_var_id| {
4047 return try self.load(ty, block_result_var_id, .{});
4048 }
4049
4050 return null;
3707 }4051 }
37084052
3709 fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void {4053 fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void {
4054 const mod = self.module;
3710 const br = self.air.instructions.items(.data)[inst].br;4055 const br = self.air.instructions.items(.data)[inst].br;
3711 const operand_ty = self.typeOf(br.operand);4056 const operand_ty = self.typeOf(br.operand);
3712 const block = self.blocks.get(br.block_inst).?;
37134057
3714 const mod = self.module;4058 switch (self.control_flow) {
3715 if (operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(mod)) {4059 .structured => |*cf| {
3716 const operand_id = try self.resolve(br.operand);4060 if (operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(mod)) {
3717 // current_block_label_id should not be undefined here, lest there is a br or br_void in the function's body.4061 const operand_id = try self.resolve(br.operand);
3718 try block.incoming_blocks.append(self.gpa, .{4062 const block_result_var_id = cf.block_results.get(br.block_inst).?;
3719 .src_label_id = self.current_block_label_id,4063 try self.store(operand_ty, block_result_var_id, operand_id, .{});
3720 .break_value_id = operand_id,4064 }
3721 });
3722 }
37234065
3724 if (block.label_id == null) {4066 const block_id_ty_ref = try self.intType(.unsigned, 32);
3725 block.label_id = self.spv.allocId();4067 const next_block = try self.constInt(block_id_ty_ref, br.block_inst);
3726 }4068 try self.structuredBreak(next_block);
4069 },
4070 .unstructured => |cf| {
4071 const block = cf.blocks.get(br.block_inst).?;
4072 if (operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(mod)) {
4073 const operand_id = try self.resolve(br.operand);
4074 // current_block_label should not be undefined here, lest there
4075 // is a br or br_void in the function's body.
4076 try block.incoming_blocks.append(self.gpa, .{
4077 .src_label = self.current_block_label,
4078 .break_value_id = operand_id,
4079 });
4080 }
4081
4082 if (block.label == null) {
4083 block.label = self.spv.allocId();
4084 }
37274085
3728 try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = block.label_id.? });4086 try self.func.body.emitBranch(self.spv.gpa, block.label.?);
4087 },
4088 }
3729 }4089 }
37304090
3731 fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {4091 fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {
...@@ -3735,23 +4095,104 @@ const DeclGen = struct {...@@ -3735,23 +4095,104 @@ const DeclGen = struct {
3735 const else_body = self.air.extra[cond_br.end + then_body.len ..][0..cond_br.data.else_body_len];4095 const else_body = self.air.extra[cond_br.end + then_body.len ..][0..cond_br.data.else_body_len];
3736 const condition_id = try self.resolve(pl_op.operand);4096 const condition_id = try self.resolve(pl_op.operand);
37374097
3738 // These will always generate a new SPIR-V block, since they are ir.Body and not ir.Block.4098 const then_label = self.spv.allocId();
3739 const then_label_id = self.spv.allocId();4099 const else_label = self.spv.allocId();
3740 const else_label_id = self.spv.allocId();
37414100
3742 // TODO: We can generate OpSelectionMerge here if we know the target block that both of these will resolve to,4101 switch (self.control_flow) {
3743 // but i don't know if those will always resolve to the same block.4102 .structured => {
4103 const merge_label = self.spv.allocId();
37444104
3745 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{4105 try self.func.body.emit(self.spv.gpa, .OpSelectionMerge, .{
3746 .condition = condition_id,4106 .merge_block = merge_label,
3747 .true_label = then_label_id,4107 .selection_control = .{},
3748 .false_label = else_label_id,4108 });
3749 });4109 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
4110 .condition = condition_id,
4111 .true_label = then_label,
4112 .false_label = else_label,
4113 });
4114
4115 try self.beginSpvBlock(then_label);
4116 const then_next = try self.genStructuredBody(.selection, then_body);
4117 const then_incoming = ControlFlow.Structured.Block.Incoming{
4118 .src_label = self.current_block_label,
4119 .next_block = then_next,
4120 };
4121 try self.func.body.emitBranch(self.spv.gpa, merge_label);
4122
4123 try self.beginSpvBlock(else_label);
4124 const else_next = try self.genStructuredBody(.selection, else_body);
4125 const else_incoming = ControlFlow.Structured.Block.Incoming{
4126 .src_label = self.current_block_label,
4127 .next_block = else_next,
4128 };
4129 try self.func.body.emitBranch(self.spv.gpa, merge_label);
4130
4131 try self.beginSpvBlock(merge_label);
4132 const next_block = try self.structuredNextBlock(&.{ then_incoming, else_incoming });
4133
4134 try self.structuredBreak(next_block);
4135 },
4136 .unstructured => {
4137 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
4138 .condition = condition_id,
4139 .true_label = then_label,
4140 .false_label = else_label,
4141 });
4142
4143 try self.beginSpvBlock(then_label);
4144 try self.genBody(then_body);
4145 try self.beginSpvBlock(else_label);
4146 try self.genBody(else_body);
4147 },
4148 }
4149 }
37504150
3751 try self.beginSpvBlock(then_label_id);4151 fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void {
3752 try self.genBody(then_body);4152 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
3753 try self.beginSpvBlock(else_label_id);4153 const loop = self.air.extraData(Air.Block, ty_pl.payload);
3754 try self.genBody(else_body);4154 const body = self.air.extra[loop.end..][0..loop.data.body_len];
4155
4156 const body_label = self.spv.allocId();
4157
4158 switch (self.control_flow) {
4159 .structured => {
4160 const header_label = self.spv.allocId();
4161 const merge_label = self.spv.allocId();
4162 const continue_label = self.spv.allocId();
4163
4164 // The back-edge must point to the loop header, so generate a separate block for the
4165 // loop header so that we don't accidentally include some instructions from there
4166 // in the loop.
4167 try self.func.body.emitBranch(self.spv.gpa, header_label);
4168 try self.beginSpvBlock(header_label);
4169
4170 // Emit loop header and jump to loop body
4171 try self.func.body.emit(self.spv.gpa, .OpLoopMerge, .{
4172 .merge_block = merge_label,
4173 .continue_target = continue_label,
4174 .loop_control = .{},
4175 });
4176 try self.func.body.emitBranch(self.spv.gpa, body_label);
4177
4178 try self.beginSpvBlock(body_label);
4179
4180 const next_block = try self.genStructuredBody(.{ .loop = .{
4181 .merge_label = merge_label,
4182 .continue_label = continue_label,
4183 } }, body);
4184 try self.structuredBreak(next_block);
4185
4186 try self.beginSpvBlock(continue_label);
4187 try self.func.body.emitBranch(self.spv.gpa, header_label);
4188 },
4189 .unstructured => {
4190 try self.func.body.emitBranch(self.spv.gpa, body_label);
4191 try self.beginSpvBlock(body_label);
4192 try self.genBody(body);
4193 try self.func.body.emitBranch(self.spv.gpa, body_label);
4194 },
4195 }
3755 }4196 }
37564197
3757 fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {4198 fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
...@@ -3775,22 +4216,6 @@ const DeclGen = struct {...@@ -3775,22 +4216,6 @@ const DeclGen = struct {
3775 try self.store(elem_ty, ptr, value, .{ .is_volatile = ptr_ty.isVolatilePtr(self.module) });4216 try self.store(elem_ty, ptr, value, .{ .is_volatile = ptr_ty.isVolatilePtr(self.module) });
3776 }4217 }
37774218
3778 fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void {
3779 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
3780 const loop = self.air.extraData(Air.Block, ty_pl.payload);
3781 const body = self.air.extra[loop.end..][0..loop.data.body_len];
3782 const loop_label_id = self.spv.allocId();
3783
3784 // Jump to the loop entry point
3785 try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id });
3786
3787 // TODO: Look into OpLoopMerge.
3788 try self.beginSpvBlock(loop_label_id);
3789 try self.genBody(body);
3790
3791 try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id });
3792 }
3793
3794 fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {4219 fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {
3795 const operand = self.air.instructions.items(.data)[inst].un_op;4220 const operand = self.air.instructions.items(.data)[inst].un_op;
3796 const ret_ty = self.typeOf(operand);4221 const ret_ty = self.typeOf(operand);
...@@ -3879,7 +4304,20 @@ const DeclGen = struct {...@@ -3879,7 +4304,20 @@ const DeclGen = struct {
3879 const err_block = self.spv.allocId();4304 const err_block = self.spv.allocId();
3880 const ok_block = self.spv.allocId();4305 const ok_block = self.spv.allocId();
38814306
3882 // TODO: Merge block4307 switch (self.control_flow) {
4308 .structured => {
4309 // According to AIR documentation, this block is guaranteed
4310 // to not break and end in a return instruction. Thus,
4311 // for structured control flow, we can just naively use
4312 // the ok block as the merge block here.
4313 try self.func.body.emit(self.spv.gpa, .OpSelectionMerge, .{
4314 .merge_block = ok_block,
4315 .selection_control = .{},
4316 });
4317 },
4318 .unstructured => {},
4319 }
4320
3883 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{4321 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
3884 .condition = is_err_id,4322 .condition = is_err_id,
3885 .true_label = err_block,4323 .true_label = err_block,
...@@ -3890,7 +4328,6 @@ const DeclGen = struct {...@@ -3890,7 +4328,6 @@ const DeclGen = struct {
3890 try self.genBody(body);4328 try self.genBody(body);
38914329
3892 try self.beginSpvBlock(ok_block);4330 try self.beginSpvBlock(ok_block);
3893 // Now just extract the payload, if required.
3894 }4331 }
3895 if (self.liveness.isUnused(inst)) {4332 if (self.liveness.isUnused(inst)) {
3896 return null;4333 return null;
...@@ -3899,6 +4336,7 @@ const DeclGen = struct {...@@ -3899,6 +4336,7 @@ const DeclGen = struct {
3899 return null;4336 return null;
3900 }4337 }
39014338
4339 // Now just extract the payload, if required.
3902 return try self.extractField(payload_ty, err_union_id, eu_layout.payloadFieldIndex());4340 return try self.extractField(payload_ty, err_union_id, eu_layout.payloadFieldIndex());
3903 }4341 }
39044342
...@@ -4164,9 +4602,8 @@ const DeclGen = struct {...@@ -4164,9 +4602,8 @@ const DeclGen = struct {
4164 // Zig switches are grouped by condition, so we need to loop through all of them4602 // Zig switches are grouped by condition, so we need to loop through all of them
4165 const num_conditions = blk: {4603 const num_conditions = blk: {
4166 var extra_index: usize = switch_br.end;4604 var extra_index: usize = switch_br.end;
4167 var case_i: u32 = 0;
4168 var num_conditions: u32 = 0;4605 var num_conditions: u32 = 0;
4169 while (case_i < num_cases) : (case_i += 1) {4606 for (0..num_cases) |_| {
4170 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);4607 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
4171 const case_body = self.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];4608 const case_body = self.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];
4172 extra_index = case.end + case.data.items_len + case_body.len;4609 extra_index = case.end + case.data.items_len + case_body.len;
...@@ -4180,6 +4617,18 @@ const DeclGen = struct {...@@ -4180,6 +4617,18 @@ const DeclGen = struct {
4180 // We always need the default case - if zig has none, we will generate unreachable there.4617 // We always need the default case - if zig has none, we will generate unreachable there.
4181 const default = self.spv.allocId();4618 const default = self.spv.allocId();
41824619
4620 const merge_label = switch (self.control_flow) {
4621 .structured => self.spv.allocId(),
4622 .unstructured => null,
4623 };
4624
4625 if (self.control_flow == .structured) {
4626 try self.func.body.emit(self.spv.gpa, .OpSelectionMerge, .{
4627 .merge_block = merge_label.?,
4628 .selection_control = .{},
4629 });
4630 }
4631
4183 // Emit the instruction before generating the blocks.4632 // Emit the instruction before generating the blocks.
4184 try self.func.body.emitRaw(self.spv.gpa, .OpSwitch, 2 + (cond_words + 1) * num_conditions);4633 try self.func.body.emitRaw(self.spv.gpa, .OpSwitch, 2 + (cond_words + 1) * num_conditions);
4185 self.func.body.writeOperand(IdRef, cond_indirect);4634 self.func.body.writeOperand(IdRef, cond_indirect);
...@@ -4188,20 +4637,17 @@ const DeclGen = struct {...@@ -4188,20 +4637,17 @@ const DeclGen = struct {
4188 // Emit each of the cases4637 // Emit each of the cases
4189 {4638 {
4190 var extra_index: usize = switch_br.end;4639 var extra_index: usize = switch_br.end;
4191 var case_i: u32 = 0;4640 for (0..num_cases) |case_i| {
4192 while (case_i < num_cases) : (case_i += 1) {
4193 // SPIR-V needs a literal here, which' width depends on the case condition.4641 // SPIR-V needs a literal here, which' width depends on the case condition.
4194 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);4642 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
4195 const items = @as([]const Air.Inst.Ref, @ptrCast(self.air.extra[case.end..][0..case.data.items_len]));4643 const items = @as([]const Air.Inst.Ref, @ptrCast(self.air.extra[case.end..][0..case.data.items_len]));
4196 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];4644 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
4197 extra_index = case.end + case.data.items_len + case_body.len;4645 extra_index = case.end + case.data.items_len + case_body.len;
41984646
4199 const label = IdRef{ .id = first_case_label.id + case_i };4647 const label = IdRef{ .id = @intCast(first_case_label.id + case_i) };
42004648
4201 for (items) |item| {4649 for (items) |item| {
4202 const value = (try self.air.value(item, mod)) orelse {4650 const value = (try self.air.value(item, mod)) orelse unreachable;
4203 return self.todo("switch on runtime value???", .{});
4204 };
4205 const int_val = switch (cond_ty.zigTypeTag(mod)) {4651 const int_val = switch (cond_ty.zigTypeTag(mod)) {
4206 .Bool, .Int => if (cond_ty.isSignedInt(mod)) @as(u64, @bitCast(value.toSignedInt(mod))) else value.toUnsignedInt(mod),4652 .Bool, .Int => if (cond_ty.isSignedInt(mod)) @as(u64, @bitCast(value.toSignedInt(mod))) else value.toUnsignedInt(mod),
4207 .Enum => blk: {4653 .Enum => blk: {
...@@ -4222,28 +4668,65 @@ const DeclGen = struct {...@@ -4222,28 +4668,65 @@ const DeclGen = struct {
4222 }4668 }
4223 }4669 }
42244670
4671 var incoming_structured_blocks = std.ArrayListUnmanaged(ControlFlow.Structured.Block.Incoming){};
4672 defer incoming_structured_blocks.deinit(self.gpa);
4673
4674 if (self.control_flow == .structured) {
4675 try incoming_structured_blocks.ensureUnusedCapacity(self.gpa, num_cases + 1);
4676 }
4677
4225 // Now, finally, we can start emitting each of the cases.4678 // Now, finally, we can start emitting each of the cases.
4226 var extra_index: usize = switch_br.end;4679 var extra_index: usize = switch_br.end;
4227 var case_i: u32 = 0;4680 for (0..num_cases) |case_i| {
4228 while (case_i < num_cases) : (case_i += 1) {
4229 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);4681 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
4230 const items = @as([]const Air.Inst.Ref, @ptrCast(self.air.extra[case.end..][0..case.data.items_len]));4682 const items = @as([]const Air.Inst.Ref, @ptrCast(self.air.extra[case.end..][0..case.data.items_len]));
4231 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];4683 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
4232 extra_index = case.end + case.data.items_len + case_body.len;4684 extra_index = case.end + case.data.items_len + case_body.len;
42334685
4234 const label = IdResult{ .id = first_case_label.id + case_i };4686 const label = IdResult{ .id = @intCast(first_case_label.id + case_i) };
42354687
4236 try self.beginSpvBlock(label);4688 try self.beginSpvBlock(label);
4237 try self.genBody(case_body);4689
4690 switch (self.control_flow) {
4691 .structured => {
4692 const next_block = try self.genStructuredBody(.selection, case_body);
4693 incoming_structured_blocks.appendAssumeCapacity(.{
4694 .src_label = self.current_block_label,
4695 .next_block = next_block,
4696 });
4697 try self.func.body.emitBranch(self.spv.gpa, merge_label.?);
4698 },
4699 .unstructured => {
4700 try self.genBody(case_body);
4701 },
4702 }
4238 }4703 }
42394704
4240 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];4705 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
4241 try self.beginSpvBlock(default);4706 try self.beginSpvBlock(default);
4242 if (else_body.len != 0) {4707 if (else_body.len != 0) {
4243 try self.genBody(else_body);4708 switch (self.control_flow) {
4709 .structured => {
4710 const next_block = try self.genStructuredBody(.selection, else_body);
4711 incoming_structured_blocks.appendAssumeCapacity(.{
4712 .src_label = self.current_block_label,
4713 .next_block = next_block,
4714 });
4715 try self.func.body.emitBranch(self.spv.gpa, merge_label.?);
4716 },
4717 .unstructured => {
4718 try self.genBody(else_body);
4719 },
4720 }
4244 } else {4721 } else {
4245 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});4722 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});
4246 }4723 }
4724
4725 if (self.control_flow == .structured) {
4726 try self.beginSpvBlock(merge_label.?);
4727 const next_block = try self.structuredNextBlock(incoming_structured_blocks.items);
4728 try self.structuredBreak(next_block);
4729 }
4247 }4730 }
42484731
4249 fn airUnreach(self: *DeclGen) !void {4732 fn airUnreach(self: *DeclGen) !void {
src/codegen/spirv/Section.zig+10
...@@ -65,6 +65,16 @@ pub fn emit(...@@ -65,6 +65,16 @@ pub fn emit(
65 section.writeOperands(opcode.Operands(), operands);65 section.writeOperands(opcode.Operands(), operands);
66}66}
6767
68pub fn emitBranch(
69 section: *Section,
70 allocator: Allocator,
71 target_label: spec.IdRef,
72) !void {
73 try section.emit(allocator, .OpBranch, .{
74 .target_label = target_label,
75 });
76}
77
68pub fn emitSpecConstantOp(78pub fn emitSpecConstantOp(
69 section: *Section,79 section: *Section,
70 allocator: Allocator,80 allocator: Allocator,