authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-11-24 23:01:32+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-11-24 23:01:32+01:00
log608b5d06eace0ccdce816725b8f0d7a6e9c12b81
tree10bcadb165648f599616789f995c1ec8406cdd93
parent3acb0e30a06d7ef7ece9257bc3423b9c85a12c06
parentdecff512383e8ea8a52583ba0e39617fa24dc79d
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #18097 from Snektron/spirv-structured-codegen

spirv: structured codegen

11 files changed, 654 insertions(+), 118 deletions(-)

src/Compilation.zig+14-1
...@@ -1002,6 +1002,8 @@ pub const InitOptions = struct {...@@ -1002,6 +1002,8 @@ pub const InitOptions = struct {
1002 /// (Windows) PDB output path1002 /// (Windows) PDB output path
1003 pdb_out_path: ?[]const u8 = null,1003 pdb_out_path: ?[]const u8 = null,
1004 error_limit: ?Module.ErrorInt = null,1004 error_limit: ?Module.ErrorInt = null,
1005 /// (SPIR-V) whether to generate a structured control flow graph or not
1006 want_structured_cfg: ?bool = null,
1005};1007};
10061008
1007fn addModuleTableToCacheHash(1009fn addModuleTableToCacheHash(
...@@ -1447,6 +1449,8 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {...@@ -1447,6 +1449,8 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
1447 };1449 };
1448 const formatted_panics = options.formatted_panics orelse (options.optimize_mode == .Debug);1450 const formatted_panics = options.formatted_panics orelse (options.optimize_mode == .Debug);
14491451
1452 const error_limit = options.error_limit orelse (std.math.maxInt(u16) - 1);
1453
1450 // We put everything into the cache hash that *cannot be modified1454 // We put everything into the cache hash that *cannot be modified
1451 // during an incremental update*. For example, one cannot change the1455 // during an incremental update*. For example, one cannot change the
1452 // target between updates, but one can change source files, so the1456 // target between updates, but one can change source files, so the
...@@ -1545,6 +1549,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {...@@ -1545,6 +1549,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
1545 hash.add(options.skip_linker_dependencies);1549 hash.add(options.skip_linker_dependencies);
1546 hash.add(options.parent_compilation_link_libc);1550 hash.add(options.parent_compilation_link_libc);
1547 hash.add(formatted_panics);1551 hash.add(formatted_panics);
1552 hash.add(options.emit_h != null);
1553 hash.add(error_limit);
1554 hash.addOptional(options.want_structured_cfg);
15481555
1549 // In the case of incremental cache mode, this `zig_cache_artifact_directory`1556 // In the case of incremental cache mode, this `zig_cache_artifact_directory`
1550 // is computed based on a hash of non-linker inputs, and it is where all1557 // is computed based on a hash of non-linker inputs, and it is where all
...@@ -1699,7 +1706,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {...@@ -1699,7 +1706,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
1699 .local_zir_cache = local_zir_cache,1706 .local_zir_cache = local_zir_cache,
1700 .emit_h = emit_h,1707 .emit_h = emit_h,
1701 .tmp_hack_arena = std.heap.ArenaAllocator.init(gpa),1708 .tmp_hack_arena = std.heap.ArenaAllocator.init(gpa),
1702 .error_limit = options.error_limit orelse (std.math.maxInt(u16) - 1),1709 .error_limit = error_limit,
1703 };1710 };
1704 try module.init();1711 try module.init();
17051712
...@@ -1958,6 +1965,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {...@@ -1958,6 +1965,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
1958 .force_undefined_symbols = options.force_undefined_symbols,1965 .force_undefined_symbols = options.force_undefined_symbols,
1959 .pdb_source_path = options.pdb_source_path,1966 .pdb_source_path = options.pdb_source_path,
1960 .pdb_out_path = options.pdb_out_path,1967 .pdb_out_path = options.pdb_out_path,
1968 .want_structured_cfg = options.want_structured_cfg,
1961 });1969 });
1962 errdefer bin_file.destroy();1970 errdefer bin_file.destroy();
1963 comp.* = .{1971 comp.* = .{
...@@ -2732,6 +2740,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes...@@ -2732,6 +2740,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
2732 man.hash.add(comp.bin_file.options.valgrind);2740 man.hash.add(comp.bin_file.options.valgrind);
2733 man.hash.add(comp.bin_file.options.single_threaded);2741 man.hash.add(comp.bin_file.options.single_threaded);
2734 man.hash.add(comp.bin_file.options.use_llvm);2742 man.hash.add(comp.bin_file.options.use_llvm);
2743 man.hash.add(comp.bin_file.options.use_lib_llvm);
2735 man.hash.add(comp.bin_file.options.dll_export_fns);2744 man.hash.add(comp.bin_file.options.dll_export_fns);
2736 man.hash.add(comp.bin_file.options.is_test);2745 man.hash.add(comp.bin_file.options.is_test);
2737 man.hash.add(comp.test_evented_io);2746 man.hash.add(comp.test_evented_io);
...@@ -2739,8 +2748,10 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes...@@ -2739,8 +2748,10 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
2739 man.hash.addOptionalBytes(comp.test_name_prefix);2748 man.hash.addOptionalBytes(comp.test_name_prefix);
2740 man.hash.add(comp.bin_file.options.skip_linker_dependencies);2749 man.hash.add(comp.bin_file.options.skip_linker_dependencies);
2741 man.hash.add(comp.bin_file.options.parent_compilation_link_libc);2750 man.hash.add(comp.bin_file.options.parent_compilation_link_libc);
2751 man.hash.add(comp.formatted_panics);
2742 man.hash.add(mod.emit_h != null);2752 man.hash.add(mod.emit_h != null);
2743 man.hash.add(mod.error_limit);2753 man.hash.add(mod.error_limit);
2754 man.hash.addOptional(comp.bin_file.options.want_structured_cfg);
2744 }2755 }
27452756
2746 try man.addOptionalFile(comp.bin_file.options.linker_script);2757 try man.addOptionalFile(comp.bin_file.options.linker_script);
...@@ -6823,6 +6834,7 @@ fn buildOutputFromZig(...@@ -6823,6 +6834,7 @@ fn buildOutputFromZig(
6823 .clang_passthrough_mode = comp.clang_passthrough_mode,6834 .clang_passthrough_mode = comp.clang_passthrough_mode,
6824 .skip_linker_dependencies = true,6835 .skip_linker_dependencies = true,
6825 .parent_compilation_link_libc = comp.bin_file.options.link_libc,6836 .parent_compilation_link_libc = comp.bin_file.options.link_libc,
6837 .want_structured_cfg = comp.bin_file.options.want_structured_cfg,
6826 });6838 });
6827 defer sub_compilation.destroy();6839 defer sub_compilation.destroy();
68286840
...@@ -6903,6 +6915,7 @@ pub fn build_crt_file(...@@ -6903,6 +6915,7 @@ pub fn build_crt_file(
6903 .clang_passthrough_mode = comp.clang_passthrough_mode,6915 .clang_passthrough_mode = comp.clang_passthrough_mode,
6904 .skip_linker_dependencies = true,6916 .skip_linker_dependencies = true,
6905 .parent_compilation_link_libc = comp.bin_file.options.link_libc,6917 .parent_compilation_link_libc = comp.bin_file.options.link_libc,
6918 .want_structured_cfg = comp.bin_file.options.want_structured_cfg,
6906 });6919 });
6907 defer sub_compilation.destroy();6920 defer sub_compilation.destroy();
69086921
src/codegen/spirv.zig+601-109
...@@ -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
...@@ -99,6 +191,14 @@ pub const Object = struct {...@@ -99,6 +191,14 @@ pub const Object = struct {
99 air: Air,191 air: Air,
100 liveness: Liveness,192 liveness: Liveness,
101 ) !void {193 ) !void {
194 const target = mod.getTarget();
195 // We always want a structured control flow in shaders. This option is only relevant
196 // for OpenCL kernels.
197 const want_structured_cfg = switch (target.os.tag) {
198 .opencl => mod.comp.bin_file.options.want_structured_cfg orelse false,
199 else => true,
200 };
201
102 var decl_gen = DeclGen{202 var decl_gen = DeclGen{
103 .gpa = self.gpa,203 .gpa = self.gpa,
104 .object = self,204 .object = self,
...@@ -108,7 +208,11 @@ pub const Object = struct {...@@ -108,7 +208,11 @@ pub const Object = struct {
108 .air = air,208 .air = air,
109 .liveness = liveness,209 .liveness = liveness,
110 .type_map = &self.type_map,210 .type_map = &self.type_map,
111 .current_block_label_id = undefined,211 .control_flow = switch (want_structured_cfg) {
212 true => .{ .structured = .{} },
213 false => .{ .unstructured = .{} },
214 },
215 .current_block_label = undefined,
112 };216 };
113 defer decl_gen.deinit();217 defer decl_gen.deinit();
114218
...@@ -213,12 +317,11 @@ const DeclGen = struct {...@@ -213,12 +317,11 @@ const DeclGen = struct {
213 /// is already in this map, its recursive.317 /// is already in this map, its recursive.
214 wip_pointers: std.AutoHashMapUnmanaged(struct { InternPool.Index, StorageClass }, CacheRef) = .{},318 wip_pointers: std.AutoHashMapUnmanaged(struct { InternPool.Index, StorageClass }, CacheRef) = .{},
215319
216 /// 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.
217 /// blocks for a block.321 control_flow: ControlFlow,
218 blocks: BlockMap = .{},
219322
220 /// The label of the SPIR-V block we are currently generating.323 /// The label of the SPIR-V block we are currently generating.
221 current_block_label_id: IdRef,324 current_block_label: IdRef,
222325
223 /// 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.
224 func: SpvModule.Fn = .{},327 func: SpvModule.Fn = .{},
...@@ -300,7 +403,7 @@ const DeclGen = struct {...@@ -300,7 +403,7 @@ const DeclGen = struct {
300 self.args.deinit(self.gpa);403 self.args.deinit(self.gpa);
301 self.inst_results.deinit(self.gpa);404 self.inst_results.deinit(self.gpa);
302 self.wip_pointers.deinit(self.gpa);405 self.wip_pointers.deinit(self.gpa);
303 self.blocks.deinit(self.gpa);406 self.control_flow.deinit(self.gpa);
304 self.func.deinit(self.gpa);407 self.func.deinit(self.gpa);
305 self.base_line_stack.deinit(self.gpa);408 self.base_line_stack.deinit(self.gpa);
306 }409 }
...@@ -384,10 +487,11 @@ const DeclGen = struct {...@@ -384,10 +487,11 @@ const DeclGen = struct {
384 // TODO: This should probably be made a little more robust.487 // TODO: This should probably be made a little more robust.
385 const func = self.func;488 const func = self.func;
386 defer self.func = func;489 defer self.func = func;
387 const block_label_id = self.current_block_label_id;490 const block_label = self.current_block_label;
388 defer self.current_block_label_id = block_label_id;491 defer self.current_block_label = block_label;
389492
390 self.func = .{};493 self.func = .{};
494 defer self.func.deinit(self.gpa);
391495
392 // TODO: Merge this with genDecl?496 // TODO: Merge this with genDecl?
393 const begin = self.spv.beginGlobal();497 const begin = self.spv.beginGlobal();
...@@ -409,7 +513,7 @@ const DeclGen = struct {...@@ -409,7 +513,7 @@ const DeclGen = struct {
409 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{513 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
410 .id_result = root_block_id,514 .id_result = root_block_id,
411 });515 });
412 self.current_block_label_id = root_block_id;516 self.current_block_label = root_block_id;
413517
414 const val_id = try self.constant(ty, val.toValue(), .indirect);518 const val_id = try self.constant(ty, val.toValue(), .indirect);
415 try self.func.body.emit(self.spv.gpa, .OpStore, .{519 try self.func.body.emit(self.spv.gpa, .OpStore, .{
...@@ -432,9 +536,9 @@ const DeclGen = struct {...@@ -432,9 +536,9 @@ const DeclGen = struct {
432 /// block we are currently generating.536 /// block we are currently generating.
433 /// 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
434 /// keep track of the previous block.538 /// keep track of the previous block.
435 fn beginSpvBlock(self: *DeclGen, label_id: IdResult) !void {539 fn beginSpvBlock(self: *DeclGen, label: IdResult) !void {
436 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 });
437 self.current_block_label_id = label_id;541 self.current_block_label = label;
438 }542 }
439543
440 /// 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
...@@ -1783,13 +1887,22 @@ const DeclGen = struct {...@@ -1783,13 +1887,22 @@ const DeclGen = struct {
1783 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{1887 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
1784 .id_result = root_block_id,1888 .id_result = root_block_id,
1785 });1889 });
1786 self.current_block_label_id = root_block_id;1890 self.current_block_label = root_block_id;
17871891
1788 const main_body = self.air.getMainBody();1892 const main_body = self.air.getMainBody();
1789 try self.genBody(main_body);1893 switch (self.control_flow) {
17901894 .structured => {
1791 // 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 }
1792 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.
1793 try self.spv.addFunction(spv_decl_index, self.func);1906 try self.spv.addFunction(spv_decl_index, self.func);
17941907
1795 const fqn = ip.stringToSlice(try decl.getFullyQualifiedName(self.module));1908 const fqn = ip.stringToSlice(try decl.getFullyQualifiedName(self.module));
...@@ -1847,7 +1960,7 @@ const DeclGen = struct {...@@ -1847,7 +1960,7 @@ const DeclGen = struct {
1847 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{1960 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
1848 .id_result = root_block_id,1961 .id_result = root_block_id,
1849 });1962 });
1850 self.current_block_label_id = root_block_id;1963 self.current_block_label = root_block_id;
18511964
1852 const val_id = try self.constant(decl.ty, init_val, .indirect);1965 const val_id = try self.constant(decl.ty, init_val, .indirect);
1853 try self.func.body.emit(self.spv.gpa, .OpStore, .{1966 try self.func.body.emit(self.spv.gpa, .OpStore, .{
...@@ -3646,6 +3759,154 @@ const DeclGen = struct {...@@ -3646,6 +3759,154 @@ const DeclGen = struct {
3646 return self.args.items[self.next_arg_index];3759 return self.args.items[self.next_arg_index];
3647 }3760 }
36483761
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
3649 fn airBlock(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {3910 fn airBlock(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3650 // 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
3651 // 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.
...@@ -3661,62 +3922,170 @@ const DeclGen = struct {...@@ -3661,62 +3922,170 @@ const DeclGen = struct {
3661 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];
3662 const have_block_result = ty.isFnOrHasRuntimeBitsIgnoreComptime(mod);3923 const have_block_result = ty.isFnOrHasRuntimeBitsIgnoreComptime(mod);
36633924
3664 // 4 chosen as arbitrary initial capacity.3925 const cf = switch (self.control_flow) {
3665 var block = Block{3926 .structured => |*cf| cf,
3666 // Label id is lazily allocated if needed.3927 .unstructured => |*cf| {
3667 .label_id = null,3928 var block = ControlFlow.Unstructured.Block{};
3668 .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 },
3669 };3969 };
3670 defer block.incoming_blocks.deinit(self.gpa);
36713970
3672 try self.blocks.putNoClobber(self.gpa, inst, &block);3971 const maybe_block_result_var_id = if (have_block_result) blk: {
3673 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));
36743977
3675 try self.genBody(body);3978 const next_block = try self.genStructuredBody(.selection, body);
36763979
3677 // 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,
3678 if (block.label_id) |label_id| {3981 // so there always has to be another entry.
3679 try self.beginSpvBlock(label_id);3982 assert(cf.block_stack.items.len > 0);
3680 }
36813983
3682 if (!have_block_result)3984 // Check if the target of the branch was this current block.
3683 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 });
36843995
3685 assert(block.label_id != null);3996 const sblock = cf.block_stack.getLast();
3686 const result_id = self.spv.allocId();
3687 const result_type_id = try self.resolveTypeId(ty);
36883997
3689 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)) {
3690 self.func.body.writeOperand(spec.IdResultType, result_type_id);3999 // If this block is noreturn, this instruction is the last of a block,
3691 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 });
36924025
3693 for (block.incoming_blocks.items) |incoming| {4026 try self.beginSpvBlock(then_label);
3694 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 }
3695 }4044 }
36964045
3697 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;
3698 }4051 }
36994052
3700 fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void {4053 fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void {
4054 const mod = self.module;
3701 const br = self.air.instructions.items(.data)[inst].br;4055 const br = self.air.instructions.items(.data)[inst].br;
3702 const operand_ty = self.typeOf(br.operand);4056 const operand_ty = self.typeOf(br.operand);
3703 const block = self.blocks.get(br.block_inst).?;
37044057
3705 const mod = self.module;4058 switch (self.control_flow) {
3706 if (operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(mod)) {4059 .structured => |*cf| {
3707 const operand_id = try self.resolve(br.operand);4060 if (operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(mod)) {
3708 // 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);
3709 try block.incoming_blocks.append(self.gpa, .{4062 const block_result_var_id = cf.block_results.get(br.block_inst).?;
3710 .src_label_id = self.current_block_label_id,4063 try self.store(operand_ty, block_result_var_id, operand_id, .{});
3711 .break_value_id = operand_id,4064 }
3712 });
3713 }
37144065
3715 if (block.label_id == null) {4066 const block_id_ty_ref = try self.intType(.unsigned, 32);
3716 block.label_id = self.spv.allocId();4067 const next_block = try self.constInt(block_id_ty_ref, br.block_inst);
3717 }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 }
37184081
3719 try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = block.label_id.? });4082 if (block.label == null) {
4083 block.label = self.spv.allocId();
4084 }
4085
4086 try self.func.body.emitBranch(self.spv.gpa, block.label.?);
4087 },
4088 }
3720 }4089 }
37214090
3722 fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {4091 fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {
...@@ -3726,23 +4095,104 @@ const DeclGen = struct {...@@ -3726,23 +4095,104 @@ const DeclGen = struct {
3726 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];
3727 const condition_id = try self.resolve(pl_op.operand);4096 const condition_id = try self.resolve(pl_op.operand);
37284097
3729 // 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();
3730 const then_label_id = self.spv.allocId();4099 const else_label = self.spv.allocId();
3731 const else_label_id = self.spv.allocId();
37324100
3733 // TODO: We can generate OpSelectionMerge here if we know the target block that both of these will resolve to,4101 switch (self.control_flow) {
3734 // but i don't know if those will always resolve to the same block.4102 .structured => {
4103 const merge_label = self.spv.allocId();
37354104
3736 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{4105 try self.func.body.emit(self.spv.gpa, .OpSelectionMerge, .{
3737 .condition = condition_id,4106 .merge_block = merge_label,
3738 .true_label = then_label_id,4107 .selection_control = .{},
3739 .false_label = else_label_id,4108 });
3740 });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 });
37414114
3742 try self.beginSpvBlock(then_label_id);4115 try self.beginSpvBlock(then_label);
3743 try self.genBody(then_body);4116 const then_next = try self.genStructuredBody(.selection, then_body);
3744 try self.beginSpvBlock(else_label_id);4117 const then_incoming = ControlFlow.Structured.Block.Incoming{
3745 try self.genBody(else_body);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 }
4150
4151 fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void {
4152 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
4153 const loop = self.air.extraData(Air.Block, ty_pl.payload);
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 }
3746 }4196 }
37474197
3748 fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {4198 fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
...@@ -3766,22 +4216,6 @@ const DeclGen = struct {...@@ -3766,22 +4216,6 @@ const DeclGen = struct {
3766 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) });
3767 }4217 }
37684218
3769 fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void {
3770 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
3771 const loop = self.air.extraData(Air.Block, ty_pl.payload);
3772 const body = self.air.extra[loop.end..][0..loop.data.body_len];
3773 const loop_label_id = self.spv.allocId();
3774
3775 // Jump to the loop entry point
3776 try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id });
3777
3778 // TODO: Look into OpLoopMerge.
3779 try self.beginSpvBlock(loop_label_id);
3780 try self.genBody(body);
3781
3782 try self.func.body.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id });
3783 }
3784
3785 fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {4219 fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {
3786 const operand = self.air.instructions.items(.data)[inst].un_op;4220 const operand = self.air.instructions.items(.data)[inst].un_op;
3787 const ret_ty = self.typeOf(operand);4221 const ret_ty = self.typeOf(operand);
...@@ -3870,7 +4304,20 @@ const DeclGen = struct {...@@ -3870,7 +4304,20 @@ const DeclGen = struct {
3870 const err_block = self.spv.allocId();4304 const err_block = self.spv.allocId();
3871 const ok_block = self.spv.allocId();4305 const ok_block = self.spv.allocId();
38724306
3873 // 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
3874 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{4321 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
3875 .condition = is_err_id,4322 .condition = is_err_id,
3876 .true_label = err_block,4323 .true_label = err_block,
...@@ -3881,7 +4328,6 @@ const DeclGen = struct {...@@ -3881,7 +4328,6 @@ const DeclGen = struct {
3881 try self.genBody(body);4328 try self.genBody(body);
38824329
3883 try self.beginSpvBlock(ok_block);4330 try self.beginSpvBlock(ok_block);
3884 // Now just extract the payload, if required.
3885 }4331 }
3886 if (self.liveness.isUnused(inst)) {4332 if (self.liveness.isUnused(inst)) {
3887 return null;4333 return null;
...@@ -3890,6 +4336,7 @@ const DeclGen = struct {...@@ -3890,6 +4336,7 @@ const DeclGen = struct {
3890 return null;4336 return null;
3891 }4337 }
38924338
4339 // Now just extract the payload, if required.
3893 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());
3894 }4341 }
38954342
...@@ -4155,9 +4602,8 @@ const DeclGen = struct {...@@ -4155,9 +4602,8 @@ const DeclGen = struct {
4155 // 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
4156 const num_conditions = blk: {4603 const num_conditions = blk: {
4157 var extra_index: usize = switch_br.end;4604 var extra_index: usize = switch_br.end;
4158 var case_i: u32 = 0;
4159 var num_conditions: u32 = 0;4605 var num_conditions: u32 = 0;
4160 while (case_i < num_cases) : (case_i += 1) {4606 for (0..num_cases) |_| {
4161 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);4607 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
4162 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];
4163 extra_index = case.end + case.data.items_len + case_body.len;4609 extra_index = case.end + case.data.items_len + case_body.len;
...@@ -4171,6 +4617,18 @@ const DeclGen = struct {...@@ -4171,6 +4617,18 @@ const DeclGen = struct {
4171 // 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.
4172 const default = self.spv.allocId();4618 const default = self.spv.allocId();
41734619
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
4174 // Emit the instruction before generating the blocks.4632 // Emit the instruction before generating the blocks.
4175 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);
4176 self.func.body.writeOperand(IdRef, cond_indirect);4634 self.func.body.writeOperand(IdRef, cond_indirect);
...@@ -4179,20 +4637,17 @@ const DeclGen = struct {...@@ -4179,20 +4637,17 @@ const DeclGen = struct {
4179 // Emit each of the cases4637 // Emit each of the cases
4180 {4638 {
4181 var extra_index: usize = switch_br.end;4639 var extra_index: usize = switch_br.end;
4182 var case_i: u32 = 0;4640 for (0..num_cases) |case_i| {
4183 while (case_i < num_cases) : (case_i += 1) {
4184 // 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.
4185 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);4642 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
4186 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]));
4187 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];
4188 extra_index = case.end + case.data.items_len + case_body.len;4645 extra_index = case.end + case.data.items_len + case_body.len;
41894646
4190 const label = IdRef{ .id = first_case_label.id + case_i };4647 const label = IdRef{ .id = @intCast(first_case_label.id + case_i) };
41914648
4192 for (items) |item| {4649 for (items) |item| {
4193 const value = (try self.air.value(item, mod)) orelse {4650 const value = (try self.air.value(item, mod)) orelse unreachable;
4194 return self.todo("switch on runtime value???", .{});
4195 };
4196 const int_val = switch (cond_ty.zigTypeTag(mod)) {4651 const int_val = switch (cond_ty.zigTypeTag(mod)) {
4197 .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),
4198 .Enum => blk: {4653 .Enum => blk: {
...@@ -4213,28 +4668,65 @@ const DeclGen = struct {...@@ -4213,28 +4668,65 @@ const DeclGen = struct {
4213 }4668 }
4214 }4669 }
42154670
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
4216 // Now, finally, we can start emitting each of the cases.4678 // Now, finally, we can start emitting each of the cases.
4217 var extra_index: usize = switch_br.end;4679 var extra_index: usize = switch_br.end;
4218 var case_i: u32 = 0;4680 for (0..num_cases) |case_i| {
4219 while (case_i < num_cases) : (case_i += 1) {
4220 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);4681 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
4221 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]));
4222 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];
4223 extra_index = case.end + case.data.items_len + case_body.len;4684 extra_index = case.end + case.data.items_len + case_body.len;
42244685
4225 const label = IdResult{ .id = first_case_label.id + case_i };4686 const label = IdResult{ .id = @intCast(first_case_label.id + case_i) };
42264687
4227 try self.beginSpvBlock(label);4688 try self.beginSpvBlock(label);
4228 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 }
4229 }4703 }
42304704
4231 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];
4232 try self.beginSpvBlock(default);4706 try self.beginSpvBlock(default);
4233 if (else_body.len != 0) {4707 if (else_body.len != 0) {
4234 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 }
4235 } else {4721 } else {
4236 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});4722 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});
4237 }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 }
4238 }4730 }
42394731
4240 fn airUnreach(self: *DeclGen) !void {4732 fn airUnreach(self: *DeclGen) !void {
src/codegen/spirv/Module.zig+1
...@@ -184,6 +184,7 @@ pub fn deinit(self: *Module) void {...@@ -184,6 +184,7 @@ pub fn deinit(self: *Module) void {
184 self.sections.debug_strings.deinit(self.gpa);184 self.sections.debug_strings.deinit(self.gpa);
185 self.sections.debug_names.deinit(self.gpa);185 self.sections.debug_names.deinit(self.gpa);
186 self.sections.annotations.deinit(self.gpa);186 self.sections.annotations.deinit(self.gpa);
187 self.sections.types_globals_constants.deinit(self.gpa);
187 self.sections.functions.deinit(self.gpa);188 self.sections.functions.deinit(self.gpa);
188189
189 self.source_file_names.deinit(self.gpa);190 self.source_file_names.deinit(self.gpa);
src/codegen/spirv/Section.zig+10-7
...@@ -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,
...@@ -198,10 +208,6 @@ fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand...@@ -198,10 +208,6 @@ fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand
198 }208 }
199 }209 }
200210
201 if (mask == 0) {
202 return;
203 }
204
205 section.writeWord(mask);211 section.writeWord(mask);
206212
207 inline for (@typeInfo(Operand).Struct.fields) |field| {213 inline for (@typeInfo(Operand).Struct.fields) |field| {
...@@ -304,9 +310,6 @@ fn extendedMaskSize(comptime Operand: type, operand: Operand) usize {...@@ -304,9 +310,6 @@ fn extendedMaskSize(comptime Operand: type, operand: Operand) usize {
304 else => unreachable,310 else => unreachable,
305 }311 }
306 }312 }
307 if (!any_set) {
308 return 0;
309 }
310 return total + 1; // Add one for the mask itself.313 return total + 1; // Add one for the mask itself.
311}314}
312315
src/link.zig+3
...@@ -268,6 +268,9 @@ pub const Options = struct {...@@ -268,6 +268,9 @@ pub const Options = struct {
268 /// (Windows) .def file to specify when linking268 /// (Windows) .def file to specify when linking
269 module_definition_file: ?[]const u8 = null,269 module_definition_file: ?[]const u8 = null,
270270
271 /// (SPIR-V) whether to generate a structured control flow graph or not
272 want_structured_cfg: ?bool = null,
273
271 pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {274 pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
272 return if (options.use_lld) .Obj else options.output_mode;275 return if (options.use_lld) .Obj else options.output_mode;
273 }276 }
src/main.zig+8-1
...@@ -493,6 +493,8 @@ const usage_build_generic =...@@ -493,6 +493,8 @@ const usage_build_generic =
493 \\ msvc Use msvc include paths (must be present on the system)493 \\ msvc Use msvc include paths (must be present on the system)
494 \\ gnu Use mingw include paths (distributed with Zig)494 \\ gnu Use mingw include paths (distributed with Zig)
495 \\ none Do not use any autodetected include paths495 \\ none Do not use any autodetected include paths
496 \\ -fstructured-cfg (SPIR-V) force SPIR-V kernels to use structured control flow
497 \\ -fno-structured-cfg (SPIR-V) force SPIR-V kernels to not use structured control flow
496 \\498 \\
497 \\Link Options:499 \\Link Options:
498 \\ -l[lib], --library [lib] Link against system library (only if actually used)500 \\ -l[lib], --library [lib] Link against system library (only if actually used)
...@@ -913,7 +915,7 @@ fn buildOutputType(...@@ -913,7 +915,7 @@ fn buildOutputType(
913 var pdb_out_path: ?[]const u8 = null;915 var pdb_out_path: ?[]const u8 = null;
914 var dwarf_format: ?std.dwarf.Format = null;916 var dwarf_format: ?std.dwarf.Format = null;
915 var error_limit: ?Module.ErrorInt = null;917 var error_limit: ?Module.ErrorInt = null;
916918 var want_structured_cfg: ?bool = null;
917 // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.919 // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
918 // This array is populated by zig cc frontend and then has to be converted to zig-style920 // This array is populated by zig cc frontend and then has to be converted to zig-style
919 // CPU features.921 // CPU features.
...@@ -1070,6 +1072,10 @@ fn buildOutputType(...@@ -1070,6 +1072,10 @@ fn buildOutputType(
1070 if (mem.eql(u8, next_arg, "--")) break;1072 if (mem.eql(u8, next_arg, "--")) break;
1071 try extra_rcflags.append(next_arg);1073 try extra_rcflags.append(next_arg);
1072 }1074 }
1075 } else if (mem.startsWith(u8, arg, "-fstructured-cfg")) {
1076 want_structured_cfg = true;
1077 } else if (mem.startsWith(u8, arg, "-fno-structured-cfg")) {
1078 want_structured_cfg = false;
1073 } else if (mem.eql(u8, arg, "--color")) {1079 } else if (mem.eql(u8, arg, "--color")) {
1074 const next_arg = args_iter.next() orelse {1080 const next_arg = args_iter.next() orelse {
1075 fatal("expected [auto|on|off] after --color", .{});1081 fatal("expected [auto|on|off] after --color", .{});
...@@ -3595,6 +3601,7 @@ fn buildOutputType(...@@ -3595,6 +3601,7 @@ fn buildOutputType(
3595 .error_tracing = error_tracing,3601 .error_tracing = error_tracing,
3596 .pdb_out_path = pdb_out_path,3602 .pdb_out_path = pdb_out_path,
3597 .error_limit = error_limit,3603 .error_limit = error_limit,
3604 .want_structured_cfg = want_structured_cfg,
3598 }) catch |err| switch (err) {3605 }) catch |err| switch (err) {
3599 error.LibCUnavailable => {3606 error.LibCUnavailable => {
3600 const target = target_info.target;3607 const target = target_info.target;
test/behavior/align.zig+1
...@@ -27,6 +27,7 @@ test "large alignment of local constant" {...@@ -27,6 +27,7 @@ test "large alignment of local constant" {
27test "slicing array of length 1 can not assume runtime index is always zero" {27test "slicing array of length 1 can not assume runtime index is always zero" {
28 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO28 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
29 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO29 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
30 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
3031
31 var runtime_index: usize = 1;32 var runtime_index: usize = 1;
32 _ = &runtime_index;33 _ = &runtime_index;
test/behavior/basic.zig+2
...@@ -572,6 +572,8 @@ test "comptime cast fn to ptr" {...@@ -572,6 +572,8 @@ test "comptime cast fn to ptr" {
572}572}
573573
574test "equality compare fn ptrs" {574test "equality compare fn ptrs" {
575 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // Uses function pointers
576
575 var a = &emptyFn;577 var a = &emptyFn;
576 _ = &a;578 _ = &a;
577 try expect(a == a);579 try expect(a == a);
test/behavior/slice.zig+1
...@@ -853,6 +853,7 @@ test "slice with dereferenced value" {...@@ -853,6 +853,7 @@ test "slice with dereferenced value" {
853853
854test "empty slice ptr is non null" {854test "empty slice ptr is non null" {
855 if (builtin.zig_backend == .stage2_aarch64 and builtin.os.tag == .macos) return error.SkipZigTest; // TODO855 if (builtin.zig_backend == .stage2_aarch64 and builtin.os.tag == .macos) return error.SkipZigTest; // TODO
856 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // Test assumes `undefined` is non-zero
856857
857 {858 {
858 const empty_slice: []u8 = &[_]u8{};859 const empty_slice: []u8 = &[_]u8{};
test/behavior/struct.zig+1
...@@ -1513,6 +1513,7 @@ test "discarded struct initialization works as expected" {...@@ -1513,6 +1513,7 @@ test "discarded struct initialization works as expected" {
15131513
1514test "function pointer in struct returns the struct" {1514test "function pointer in struct returns the struct" {
1515 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO1515 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1516 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
15161517
1517 const A = struct {1518 const A = struct {
1518 const A = @This();1519 const A = @This();
test/behavior/union.zig+12
...@@ -1929,6 +1929,8 @@ test "inner struct initializer uses union layout" {...@@ -1929,6 +1929,8 @@ test "inner struct initializer uses union layout" {
1929}1929}
19301930
1931test "inner struct initializer uses packed union layout" {1931test "inner struct initializer uses packed union layout" {
1932 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1933
1932 const namespace = struct {1934 const namespace = struct {
1933 const U = packed union {1935 const U = packed union {
1934 a: packed struct {1936 a: packed struct {
...@@ -1953,6 +1955,8 @@ test "inner struct initializer uses packed union layout" {...@@ -1953,6 +1955,8 @@ test "inner struct initializer uses packed union layout" {
1953}1955}
19541956
1955test "extern union initialized via reintepreted struct field initializer" {1957test "extern union initialized via reintepreted struct field initializer" {
1958 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1959
1956 const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd };1960 const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd };
19571961
1958 const U = extern union {1962 const U = extern union {
...@@ -1970,6 +1974,8 @@ test "extern union initialized via reintepreted struct field initializer" {...@@ -1970,6 +1974,8 @@ test "extern union initialized via reintepreted struct field initializer" {
1970}1974}
19711975
1972test "packed union initialized via reintepreted struct field initializer" {1976test "packed union initialized via reintepreted struct field initializer" {
1977 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1978
1973 const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd };1979 const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd };
19741980
1975 const U = packed union {1981 const U = packed union {
...@@ -1988,6 +1994,8 @@ test "packed union initialized via reintepreted struct field initializer" {...@@ -1988,6 +1994,8 @@ test "packed union initialized via reintepreted struct field initializer" {
1988}1994}
19891995
1990test "store of comptime reinterpreted memory to extern union" {1996test "store of comptime reinterpreted memory to extern union" {
1997 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1998
1991 const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd };1999 const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd };
19922000
1993 const U = extern union {2001 const U = extern union {
...@@ -2008,6 +2016,8 @@ test "store of comptime reinterpreted memory to extern union" {...@@ -2008,6 +2016,8 @@ test "store of comptime reinterpreted memory to extern union" {
2008}2016}
20092017
2010test "store of comptime reinterpreted memory to packed union" {2018test "store of comptime reinterpreted memory to packed union" {
2019 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
2020
2011 const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd };2021 const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd };
20122022
2013 const U = packed union {2023 const U = packed union {
...@@ -2063,6 +2073,8 @@ test "pass register-sized field as non-register-sized union" {...@@ -2063,6 +2073,8 @@ test "pass register-sized field as non-register-sized union" {
2063}2073}
20642074
2065test "circular dependency through pointer field of a union" {2075test "circular dependency through pointer field of a union" {
2076 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
2077
2066 const S = struct {2078 const S = struct {
2067 const UnionInner = extern struct {2079 const UnionInner = extern struct {
2068 outer: UnionOuter = std.mem.zeroes(UnionOuter),2080 outer: UnionOuter = std.mem.zeroes(UnionOuter),