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 {
10021002 /// (Windows) PDB output path
10031003 pdb_out_path: ?[]const u8 = null,
10041004 error_limit: ?Module.ErrorInt = null,
1005 /// (SPIR-V) whether to generate a structured control flow graph or not
1006 want_structured_cfg: ?bool = null,
10051007};
10061008
10071009fn addModuleTableToCacheHash(
......@@ -1447,6 +1449,8 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
14471449 };
14481450 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
14501454 // We put everything into the cache hash that *cannot be modified
14511455 // during an incremental update*. For example, one cannot change the
14521456 // target between updates, but one can change source files, so the
......@@ -1545,6 +1549,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
15451549 hash.add(options.skip_linker_dependencies);
15461550 hash.add(options.parent_compilation_link_libc);
15471551 hash.add(formatted_panics);
1552 hash.add(options.emit_h != null);
1553 hash.add(error_limit);
1554 hash.addOptional(options.want_structured_cfg);
15481555
15491556 // In the case of incremental cache mode, this `zig_cache_artifact_directory`
15501557 // 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 {
16991706 .local_zir_cache = local_zir_cache,
17001707 .emit_h = emit_h,
17011708 .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,
17031710 };
17041711 try module.init();
17051712
......@@ -1958,6 +1965,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
19581965 .force_undefined_symbols = options.force_undefined_symbols,
19591966 .pdb_source_path = options.pdb_source_path,
19601967 .pdb_out_path = options.pdb_out_path,
1968 .want_structured_cfg = options.want_structured_cfg,
19611969 });
19621970 errdefer bin_file.destroy();
19631971 comp.* = .{
......@@ -2732,6 +2740,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
27322740 man.hash.add(comp.bin_file.options.valgrind);
27332741 man.hash.add(comp.bin_file.options.single_threaded);
27342742 man.hash.add(comp.bin_file.options.use_llvm);
2743 man.hash.add(comp.bin_file.options.use_lib_llvm);
27352744 man.hash.add(comp.bin_file.options.dll_export_fns);
27362745 man.hash.add(comp.bin_file.options.is_test);
27372746 man.hash.add(comp.test_evented_io);
......@@ -2739,8 +2748,10 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
27392748 man.hash.addOptionalBytes(comp.test_name_prefix);
27402749 man.hash.add(comp.bin_file.options.skip_linker_dependencies);
27412750 man.hash.add(comp.bin_file.options.parent_compilation_link_libc);
2751 man.hash.add(comp.formatted_panics);
27422752 man.hash.add(mod.emit_h != null);
27432753 man.hash.add(mod.error_limit);
2754 man.hash.addOptional(comp.bin_file.options.want_structured_cfg);
27442755 }
27452756
27462757 try man.addOptionalFile(comp.bin_file.options.linker_script);
......@@ -6823,6 +6834,7 @@ fn buildOutputFromZig(
68236834 .clang_passthrough_mode = comp.clang_passthrough_mode,
68246835 .skip_linker_dependencies = true,
68256836 .parent_compilation_link_libc = comp.bin_file.options.link_libc,
6837 .want_structured_cfg = comp.bin_file.options.want_structured_cfg,
68266838 });
68276839 defer sub_compilation.destroy();
68286840
......@@ -6903,6 +6915,7 @@ pub fn build_crt_file(
69036915 .clang_passthrough_mode = comp.clang_passthrough_mode,
69046916 .skip_linker_dependencies = true,
69056917 .parent_compilation_link_libc = comp.bin_file.options.link_libc,
6918 .want_structured_cfg = comp.bin_file.options.want_structured_cfg,
69066919 });
69076920 defer sub_compilation.destroy();
69086921
src/codegen/spirv.zig+601-109
......@@ -40,17 +40,109 @@ const SpvTypeInfo = struct {
4040
4141const TypeMap = std.AutoHashMapUnmanaged(InternPool.Index, SpvTypeInfo);
4242
43const IncomingBlock = struct {
44 src_label_id: IdRef,
45 break_value_id: IdRef,
46};
43const ControlFlow = union(enum) {
44 const Structured = struct {
45 /// This type indicates the way that a block is terminated. The
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 {
49 label_id: ?IdRef,
50 incoming_blocks: std.ArrayListUnmanaged(IncomingBlock),
51};
56 const SelectionMerge = struct {
57 /// Incoming block from the `then` label.
58 /// Note that hte incoming block from the `else` label is
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
55147/// This structure holds information that is relevant to the entire compilation,
56148/// in contrast to `DeclGen`, which only holds relevant information about a
......@@ -99,6 +191,14 @@ pub const Object = struct {
99191 air: Air,
100192 liveness: Liveness,
101193 ) !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
102202 var decl_gen = DeclGen{
103203 .gpa = self.gpa,
104204 .object = self,
......@@ -108,7 +208,11 @@ pub const Object = struct {
108208 .air = air,
109209 .liveness = liveness,
110210 .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,
112216 };
113217 defer decl_gen.deinit();
114218
......@@ -213,12 +317,11 @@ const DeclGen = struct {
213317 /// is already in this map, its recursive.
214318 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'
217 /// blocks for a block.
218 blocks: BlockMap = .{},
320 /// This field keeps track of the current state wrt structured or unstructured control flow.
321 control_flow: ControlFlow,
219322
220323 /// The label of the SPIR-V block we are currently generating.
221 current_block_label_id: IdRef,
324 current_block_label: IdRef,
222325
223326 /// The code (prologue and body) for the function we are currently generating code for.
224327 func: SpvModule.Fn = .{},
......@@ -300,7 +403,7 @@ const DeclGen = struct {
300403 self.args.deinit(self.gpa);
301404 self.inst_results.deinit(self.gpa);
302405 self.wip_pointers.deinit(self.gpa);
303 self.blocks.deinit(self.gpa);
406 self.control_flow.deinit(self.gpa);
304407 self.func.deinit(self.gpa);
305408 self.base_line_stack.deinit(self.gpa);
306409 }
......@@ -384,10 +487,11 @@ const DeclGen = struct {
384487 // TODO: This should probably be made a little more robust.
385488 const func = self.func;
386489 defer self.func = func;
387 const block_label_id = self.current_block_label_id;
388 defer self.current_block_label_id = block_label_id;
490 const block_label = self.current_block_label;
491 defer self.current_block_label = block_label;
389492
390493 self.func = .{};
494 defer self.func.deinit(self.gpa);
391495
392496 // TODO: Merge this with genDecl?
393497 const begin = self.spv.beginGlobal();
......@@ -409,7 +513,7 @@ const DeclGen = struct {
409513 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
410514 .id_result = root_block_id,
411515 });
412 self.current_block_label_id = root_block_id;
516 self.current_block_label = root_block_id;
413517
414518 const val_id = try self.constant(ty, val.toValue(), .indirect);
415519 try self.func.body.emit(self.spv.gpa, .OpStore, .{
......@@ -432,9 +536,9 @@ const DeclGen = struct {
432536 /// block we are currently generating.
433537 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to
434538 /// keep track of the previous block.
435 fn beginSpvBlock(self: *DeclGen, label_id: IdResult) !void {
436 try self.func.body.emit(self.spv.gpa, .OpLabel, .{ .id_result = label_id });
437 self.current_block_label_id = label_id;
539 fn beginSpvBlock(self: *DeclGen, label: IdResult) !void {
540 try self.func.body.emit(self.spv.gpa, .OpLabel, .{ .id_result = label });
541 self.current_block_label = label;
438542 }
439543
440544 /// 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 {
17831887 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
17841888 .id_result = root_block_id,
17851889 });
1786 self.current_block_label_id = root_block_id;
1890 self.current_block_label = root_block_id;
17871891
17881892 const main_body = self.air.getMainBody();
1789 try self.genBody(main_body);
1790
1791 // Append the actual code into the functions section.
1893 switch (self.control_flow) {
1894 .structured => {
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 }
17921904 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
1905 // Append the actual code into the functions section.
17931906 try self.spv.addFunction(spv_decl_index, self.func);
17941907
17951908 const fqn = ip.stringToSlice(try decl.getFullyQualifiedName(self.module));
......@@ -1847,7 +1960,7 @@ const DeclGen = struct {
18471960 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
18481961 .id_result = root_block_id,
18491962 });
1850 self.current_block_label_id = root_block_id;
1963 self.current_block_label = root_block_id;
18511964
18521965 const val_id = try self.constant(decl.ty, init_val, .indirect);
18531966 try self.func.body.emit(self.spv.gpa, .OpStore, .{
......@@ -3646,6 +3759,154 @@ const DeclGen = struct {
36463759 return self.args.items[self.next_arg_index];
36473760 }
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
36493910 fn airBlock(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
36503911 // In AIR, a block doesn't really define an entry point like a block, but
36513912 // more like a scope that breaks can jump out of and "return" a value from.
......@@ -3661,62 +3922,170 @@ const DeclGen = struct {
36613922 const body = self.air.extra[extra.end..][0..extra.data.body_len];
36623923 const have_block_result = ty.isFnOrHasRuntimeBitsIgnoreComptime(mod);
36633924
3664 // 4 chosen as arbitrary initial capacity.
3665 var block = Block{
3666 // Label id is lazily allocated if needed.
3667 .label_id = null,
3668 .incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.gpa, 4),
3925 const cf = switch (self.control_flow) {
3926 .structured => |*cf| cf,
3927 .unstructured => |*cf| {
3928 var block = ControlFlow.Unstructured.Block{};
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 },
36693969 };
3670 defer block.incoming_blocks.deinit(self.gpa);
36713970
3672 try self.blocks.putNoClobber(self.gpa, inst, &block);
3673 defer assert(self.blocks.remove(inst));
3971 const maybe_block_result_var_id = if (have_block_result) blk: {
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.
3678 if (block.label_id) |label_id| {
3679 try self.beginSpvBlock(label_id);
3680 }
3980 // When encountering a block instruction, we are always at least in the function's scope,
3981 // so there always has to be another entry.
3982 assert(cf.block_stack.items.len > 0);
36813983
3682 if (!have_block_result)
3683 return null;
3984 // Check if the target of the branch was this current block.
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);
3686 const result_id = self.spv.allocId();
3687 const result_type_id = try self.resolveTypeId(ty);
3996 const sblock = cf.block_stack.getLast();
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...
3690 self.func.body.writeOperand(spec.IdResultType, result_type_id);
3691 self.func.body.writeOperand(spec.IdRef, result_id);
3998 if (ty.isNoReturn(mod)) {
3999 // If this block is noreturn, this instruction is the last of a block,
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| {
3694 self.func.body.writeOperand(spec.PairIdRefIdRef, .{ incoming.break_value_id, incoming.src_label_id });
4026 try self.beginSpvBlock(then_label);
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 }
36954044 }
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;
36984051 }
36994052
37004053 fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void {
4054 const mod = self.module;
37014055 const br = self.air.instructions.items(.data)[inst].br;
37024056 const operand_ty = self.typeOf(br.operand);
3703 const block = self.blocks.get(br.block_inst).?;
37044057
3705 const mod = self.module;
3706 if (operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(mod)) {
3707 const operand_id = try self.resolve(br.operand);
3708 // current_block_label_id should not be undefined here, lest there is a br or br_void in the function's body.
3709 try block.incoming_blocks.append(self.gpa, .{
3710 .src_label_id = self.current_block_label_id,
3711 .break_value_id = operand_id,
3712 });
3713 }
4058 switch (self.control_flow) {
4059 .structured => |*cf| {
4060 if (operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(mod)) {
4061 const operand_id = try self.resolve(br.operand);
4062 const block_result_var_id = cf.block_results.get(br.block_inst).?;
4063 try self.store(operand_ty, block_result_var_id, operand_id, .{});
4064 }
37144065
3715 if (block.label_id == null) {
3716 block.label_id = self.spv.allocId();
3717 }
4066 const block_id_ty_ref = try self.intType(.unsigned, 32);
4067 const next_block = try self.constInt(block_id_ty_ref, br.block_inst);
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 }
37204089 }
37214090
37224091 fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {
......@@ -3726,23 +4095,104 @@ const DeclGen = struct {
37264095 const else_body = self.air.extra[cond_br.end + then_body.len ..][0..cond_br.data.else_body_len];
37274096 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.
3730 const then_label_id = self.spv.allocId();
3731 const else_label_id = self.spv.allocId();
4098 const then_label = self.spv.allocId();
4099 const else_label = self.spv.allocId();
37324100
3733 // TODO: We can generate OpSelectionMerge here if we know the target block that both of these will resolve to,
3734 // but i don't know if those will always resolve to the same block.
4101 switch (self.control_flow) {
4102 .structured => {
4103 const merge_label = self.spv.allocId();
37354104
3736 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
3737 .condition = condition_id,
3738 .true_label = then_label_id,
3739 .false_label = else_label_id,
3740 });
4105 try self.func.body.emit(self.spv.gpa, .OpSelectionMerge, .{
4106 .merge_block = merge_label,
4107 .selection_control = .{},
4108 });
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);
3743 try self.genBody(then_body);
3744 try self.beginSpvBlock(else_label_id);
3745 try self.genBody(else_body);
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 }
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 }
37464196 }
37474197
37484198 fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
......@@ -3766,22 +4216,6 @@ const DeclGen = struct {
37664216 try self.store(elem_ty, ptr, value, .{ .is_volatile = ptr_ty.isVolatilePtr(self.module) });
37674217 }
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
37854219 fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {
37864220 const operand = self.air.instructions.items(.data)[inst].un_op;
37874221 const ret_ty = self.typeOf(operand);
......@@ -3870,7 +4304,20 @@ const DeclGen = struct {
38704304 const err_block = self.spv.allocId();
38714305 const ok_block = self.spv.allocId();
38724306
3873 // TODO: Merge block
4307 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
38744321 try self.func.body.emit(self.spv.gpa, .OpBranchConditional, .{
38754322 .condition = is_err_id,
38764323 .true_label = err_block,
......@@ -3881,7 +4328,6 @@ const DeclGen = struct {
38814328 try self.genBody(body);
38824329
38834330 try self.beginSpvBlock(ok_block);
3884 // Now just extract the payload, if required.
38854331 }
38864332 if (self.liveness.isUnused(inst)) {
38874333 return null;
......@@ -3890,6 +4336,7 @@ const DeclGen = struct {
38904336 return null;
38914337 }
38924338
4339 // Now just extract the payload, if required.
38934340 return try self.extractField(payload_ty, err_union_id, eu_layout.payloadFieldIndex());
38944341 }
38954342
......@@ -4155,9 +4602,8 @@ const DeclGen = struct {
41554602 // Zig switches are grouped by condition, so we need to loop through all of them
41564603 const num_conditions = blk: {
41574604 var extra_index: usize = switch_br.end;
4158 var case_i: u32 = 0;
41594605 var num_conditions: u32 = 0;
4160 while (case_i < num_cases) : (case_i += 1) {
4606 for (0..num_cases) |_| {
41614607 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
41624608 const case_body = self.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];
41634609 extra_index = case.end + case.data.items_len + case_body.len;
......@@ -4171,6 +4617,18 @@ const DeclGen = struct {
41714617 // We always need the default case - if zig has none, we will generate unreachable there.
41724618 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
41744632 // Emit the instruction before generating the blocks.
41754633 try self.func.body.emitRaw(self.spv.gpa, .OpSwitch, 2 + (cond_words + 1) * num_conditions);
41764634 self.func.body.writeOperand(IdRef, cond_indirect);
......@@ -4179,20 +4637,17 @@ const DeclGen = struct {
41794637 // Emit each of the cases
41804638 {
41814639 var extra_index: usize = switch_br.end;
4182 var case_i: u32 = 0;
4183 while (case_i < num_cases) : (case_i += 1) {
4640 for (0..num_cases) |case_i| {
41844641 // SPIR-V needs a literal here, which' width depends on the case condition.
41854642 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
41864643 const items = @as([]const Air.Inst.Ref, @ptrCast(self.air.extra[case.end..][0..case.data.items_len]));
41874644 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
41884645 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
41924649 for (items) |item| {
4193 const value = (try self.air.value(item, mod)) orelse {
4194 return self.todo("switch on runtime value???", .{});
4195 };
4650 const value = (try self.air.value(item, mod)) orelse unreachable;
41964651 const int_val = switch (cond_ty.zigTypeTag(mod)) {
41974652 .Bool, .Int => if (cond_ty.isSignedInt(mod)) @as(u64, @bitCast(value.toSignedInt(mod))) else value.toUnsignedInt(mod),
41984653 .Enum => blk: {
......@@ -4213,28 +4668,65 @@ const DeclGen = struct {
42134668 }
42144669 }
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
42164678 // Now, finally, we can start emitting each of the cases.
42174679 var extra_index: usize = switch_br.end;
4218 var case_i: u32 = 0;
4219 while (case_i < num_cases) : (case_i += 1) {
4680 for (0..num_cases) |case_i| {
42204681 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
42214682 const items = @as([]const Air.Inst.Ref, @ptrCast(self.air.extra[case.end..][0..case.data.items_len]));
42224683 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
42234684 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
42274688 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 }
42294703 }
42304704
42314705 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
42324706 try self.beginSpvBlock(default);
42334707 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 }
42354721 } else {
42364722 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});
42374723 }
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 }
42384730 }
42394731
42404732 fn airUnreach(self: *DeclGen) !void {
src/codegen/spirv/Module.zig+1
......@@ -184,6 +184,7 @@ pub fn deinit(self: *Module) void {
184184 self.sections.debug_strings.deinit(self.gpa);
185185 self.sections.debug_names.deinit(self.gpa);
186186 self.sections.annotations.deinit(self.gpa);
187 self.sections.types_globals_constants.deinit(self.gpa);
187188 self.sections.functions.deinit(self.gpa);
188189
189190 self.source_file_names.deinit(self.gpa);
src/codegen/spirv/Section.zig+10-7
......@@ -65,6 +65,16 @@ pub fn emit(
6565 section.writeOperands(opcode.Operands(), operands);
6666}
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
6878pub fn emitSpecConstantOp(
6979 section: *Section,
7080 allocator: Allocator,
......@@ -198,10 +208,6 @@ fn writeExtendedMask(section: *Section, comptime Operand: type, operand: Operand
198208 }
199209 }
200210
201 if (mask == 0) {
202 return;
203 }
204
205211 section.writeWord(mask);
206212
207213 inline for (@typeInfo(Operand).Struct.fields) |field| {
......@@ -304,9 +310,6 @@ fn extendedMaskSize(comptime Operand: type, operand: Operand) usize {
304310 else => unreachable,
305311 }
306312 }
307 if (!any_set) {
308 return 0;
309 }
310313 return total + 1; // Add one for the mask itself.
311314}
312315
src/link.zig+3
......@@ -268,6 +268,9 @@ pub const Options = struct {
268268 /// (Windows) .def file to specify when linking
269269 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
271274 pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
272275 return if (options.use_lld) .Obj else options.output_mode;
273276 }
src/main.zig+8-1
......@@ -493,6 +493,8 @@ const usage_build_generic =
493493 \\ msvc Use msvc include paths (must be present on the system)
494494 \\ gnu Use mingw include paths (distributed with Zig)
495495 \\ 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
496498 \\
497499 \\Link Options:
498500 \\ -l[lib], --library [lib] Link against system library (only if actually used)
......@@ -913,7 +915,7 @@ fn buildOutputType(
913915 var pdb_out_path: ?[]const u8 = null;
914916 var dwarf_format: ?std.dwarf.Format = null;
915917 var error_limit: ?Module.ErrorInt = null;
916
918 var want_structured_cfg: ?bool = null;
917919 // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
918920 // This array is populated by zig cc frontend and then has to be converted to zig-style
919921 // CPU features.
......@@ -1070,6 +1072,10 @@ fn buildOutputType(
10701072 if (mem.eql(u8, next_arg, "--")) break;
10711073 try extra_rcflags.append(next_arg);
10721074 }
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;
10731079 } else if (mem.eql(u8, arg, "--color")) {
10741080 const next_arg = args_iter.next() orelse {
10751081 fatal("expected [auto|on|off] after --color", .{});
......@@ -3595,6 +3601,7 @@ fn buildOutputType(
35953601 .error_tracing = error_tracing,
35963602 .pdb_out_path = pdb_out_path,
35973603 .error_limit = error_limit,
3604 .want_structured_cfg = want_structured_cfg,
35983605 }) catch |err| switch (err) {
35993606 error.LibCUnavailable => {
36003607 const target = target_info.target;
test/behavior/align.zig+1
......@@ -27,6 +27,7 @@ test "large alignment of local constant" {
2727test "slicing array of length 1 can not assume runtime index is always zero" {
2828 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
2929 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
30 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
3031
3132 var runtime_index: usize = 1;
3233 _ = &runtime_index;
test/behavior/basic.zig+2
......@@ -572,6 +572,8 @@ test "comptime cast fn to ptr" {
572572}
573573
574574test "equality compare fn ptrs" {
575 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // Uses function pointers
576
575577 var a = &emptyFn;
576578 _ = &a;
577579 try expect(a == a);
test/behavior/slice.zig+1
......@@ -853,6 +853,7 @@ test "slice with dereferenced value" {
853853
854854test "empty slice ptr is non null" {
855855 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
857858 {
858859 const empty_slice: []u8 = &[_]u8{};
test/behavior/struct.zig+1
......@@ -1513,6 +1513,7 @@ test "discarded struct initialization works as expected" {
15131513
15141514test "function pointer in struct returns the struct" {
15151515 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1516 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
15161517
15171518 const A = struct {
15181519 const A = @This();
test/behavior/union.zig+12
......@@ -1929,6 +1929,8 @@ test "inner struct initializer uses union layout" {
19291929}
19301930
19311931test "inner struct initializer uses packed union layout" {
1932 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1933
19321934 const namespace = struct {
19331935 const U = packed union {
19341936 a: packed struct {
......@@ -1953,6 +1955,8 @@ test "inner struct initializer uses packed union layout" {
19531955}
19541956
19551957test "extern union initialized via reintepreted struct field initializer" {
1958 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1959
19561960 const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd };
19571961
19581962 const U = extern union {
......@@ -1970,6 +1974,8 @@ test "extern union initialized via reintepreted struct field initializer" {
19701974}
19711975
19721976test "packed union initialized via reintepreted struct field initializer" {
1977 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1978
19731979 const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd };
19741980
19751981 const U = packed union {
......@@ -1988,6 +1994,8 @@ test "packed union initialized via reintepreted struct field initializer" {
19881994}
19891995
19901996test "store of comptime reinterpreted memory to extern union" {
1997 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1998
19911999 const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd };
19922000
19932001 const U = extern union {
......@@ -2008,6 +2016,8 @@ test "store of comptime reinterpreted memory to extern union" {
20082016}
20092017
20102018test "store of comptime reinterpreted memory to packed union" {
2019 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
2020
20112021 const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd };
20122022
20132023 const U = packed union {
......@@ -2063,6 +2073,8 @@ test "pass register-sized field as non-register-sized union" {
20632073}
20642074
20652075test "circular dependency through pointer field of a union" {
2076 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
2077
20662078 const S = struct {
20672079 const UnionInner = extern struct {
20682080 outer: UnionOuter = std.mem.zeroes(UnionOuter),