authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-10-07 18:10:35+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-10-15 13:59:26+02:00
log28dda3bf898cc338eb0040f3f21799eda1e25be0
treee7a1234e9f5ecd60c01838bdd3e04716f077c23a
parent31ad2d72a756b837b153ae63dfc0e6df608033b4
signaturebadge-check Signed by SSH key SHA256:CQ99aPxq+RueiL9u7z0FEki5Fm7V6T8q4PrEGmINrA4

spirv: put linkery bits in Object

This structure is used to group information that needs to persist between decls in codegen.

2 files changed, 147 insertions(+), 136 deletions(-)

src/codegen/spirv.zig+133-101
...@@ -53,20 +53,141 @@ const Block = struct {...@@ -53,20 +53,141 @@ const Block = struct {
53const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, *Block);53const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, *Block);
5454
55/// Maps Zig decl indices to SPIR-V linking information.55/// Maps Zig decl indices to SPIR-V linking information.
56pub const DeclLinkMap = std.AutoHashMap(Module.Decl.Index, SpvModule.Decl.Index);56pub const DeclLinkMap = std.AutoHashMapUnmanaged(Decl.Index, SpvModule.Decl.Index);
5757
58/// Maps anon decl indices to SPIR-V linking information.58/// Maps anon decl indices to SPIR-V linking information.
59pub const AnonDeclLinkMap = std.AutoHashMap(struct { InternPool.Index, StorageClass }, SpvModule.Decl.Index);59pub const AnonDeclLinkMap = std.AutoHashMapUnmanaged(struct { InternPool.Index, StorageClass }, SpvModule.Decl.Index);
60
61/// This structure holds information that is relevant to the entire compilation,
62/// in contrast to `DeclGen`, which only holds relevant information about a
63/// single decl.
64pub const Object = struct {
65 /// A general-purpose allocator that can be used for any allocation for this Object.
66 gpa: Allocator,
67
68 /// the SPIR-V module that represents the final binary.
69 spv: SpvModule,
70
71 /// The Zig module that this object file is generated for.
72 /// A map of Zig decl indices to SPIR-V decl indices.
73 decl_link: DeclLinkMap = .{},
74
75 /// A map of Zig InternPool indices for anonymous decls to SPIR-V decl indices.
76 anon_decl_link: AnonDeclLinkMap = .{},
77
78 /// A map that maps AIR intern pool indices to SPIR-V cache references (which
79 /// is basically the same thing except for SPIR-V).
80 /// This map is typically only used for structures that are deemed heavy enough
81 /// that it is worth to store them here. The SPIR-V module also interns types,
82 /// and so the main purpose of this map is to avoid recomputation and to
83 /// cache extra information about the type rather than to aid in validity
84 /// of the SPIR-V module.
85 type_map: TypeMap = .{},
86
87 pub fn init(gpa: Allocator) Object {
88 return .{
89 .gpa = gpa,
90 .spv = SpvModule.init(gpa),
91 };
92 }
93
94 pub fn deinit(self: *Object) void {
95 self.spv.deinit();
96 self.decl_link.deinit(self.gpa);
97 self.anon_decl_link.deinit(self.gpa);
98 self.type_map.deinit(self.gpa);
99 }
100
101 fn genDecl(
102 self: *Object,
103 mod: *Module,
104 decl_index: Decl.Index,
105 air: Air,
106 liveness: Liveness,
107 ) !void {
108 var decl_gen = DeclGen{
109 .gpa = self.gpa,
110 .object = self,
111 .module = mod,
112 .spv = &self.spv,
113 .decl_index = decl_index,
114 .air = air,
115 .liveness = liveness,
116 .type_map = &self.type_map,
117 .current_block_label_id = undefined,
118 };
119 defer decl_gen.deinit();
120
121 decl_gen.genDecl() catch |err| switch (err) {
122 error.CodegenFail => {
123 try mod.failed_decls.put(mod.gpa, decl_index, decl_gen.error_msg.?);
124 },
125 else => |other| {
126 // There might be an error that happened *after* self.error_msg
127 // was already allocated, so be sure to free it.
128 if (decl_gen.error_msg) |error_msg| {
129 error_msg.deinit(mod.gpa);
130 }
131
132 return other;
133 },
134 };
135 }
136
137 pub fn updateFunc(
138 self: *Object,
139 mod: *Module,
140 func_index: InternPool.Index,
141 air: Air,
142 liveness: Liveness,
143 ) !void {
144 const decl_index = mod.funcInfo(func_index).owner_decl;
145 // TODO: Separate types for generating decls and functions?
146 try self.genDecl(mod, decl_index, air, liveness);
147 }
148
149 pub fn updateDecl(
150 self: *Object,
151 mod: *Module,
152 decl_index: Decl.Index,
153 ) !void {
154 try self.genDecl(mod, decl_index, undefined, undefined);
155 }
156
157 /// Fetch or allocate a result id for decl index. This function also marks the decl as alive.
158 /// Note: Function does not actually generate the decl, it just allocates an index.
159 pub fn resolveDecl(self: *Object, mod: *Module, decl_index: Decl.Index) !SpvModule.Decl.Index {
160 const decl = mod.declPtr(decl_index);
161 try mod.markDeclAlive(decl);
162
163 const entry = try self.decl_link.getOrPut(self.gpa, decl_index);
164 if (!entry.found_existing) {
165 // TODO: Extern fn?
166 const kind: SpvModule.DeclKind = if (decl.val.isFuncBody(mod))
167 .func
168 else
169 .global;
170
171 entry.value_ptr.* = try self.spv.allocDecl(kind);
172 }
173
174 return entry.value_ptr.*;
175 }
176};
60177
61/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.178/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
62pub const DeclGen = struct {179const DeclGen = struct {
63 /// A general-purpose allocator that can be used for any allocations for this DeclGen.180 /// A general-purpose allocator that can be used for any allocations for this DeclGen.
64 gpa: Allocator,181 gpa: Allocator,
65182
183 /// The object that this decl is generated into.
184 object: *Object,
185
66 /// The Zig module that we are generating decls for.186 /// The Zig module that we are generating decls for.
67 module: *Module,187 module: *Module,
68188
69 /// The SPIR-V module that instructions should be emitted into.189 /// The SPIR-V module that instructions should be emitted into.
190 /// This is the same as `self.object.spv`, repeated here for brevity.
70 spv: *SpvModule,191 spv: *SpvModule,
71192
72 /// The decl we are currently generating code for.193 /// The decl we are currently generating code for.
...@@ -80,30 +201,19 @@ pub const DeclGen = struct {...@@ -80,30 +201,19 @@ pub const DeclGen = struct {
80 /// Note: If the declaration is not a function, this value will be undefined!201 /// Note: If the declaration is not a function, this value will be undefined!
81 liveness: Liveness,202 liveness: Liveness,
82203
83 /// Maps Zig Decl indices to SPIR-V decl indices.
84 decl_link: *DeclLinkMap,
85
86 /// Maps Zig anon decl indices to SPIR-V decl indices.
87 anon_decl_link: *AnonDeclLinkMap,
88
89 /// An array of function argument result-ids. Each index corresponds with the204 /// An array of function argument result-ids. Each index corresponds with the
90 /// function argument of the same index.205 /// function argument of the same index.
91 args: std.ArrayListUnmanaged(IdRef) = .{},206 args: std.ArrayListUnmanaged(IdRef) = .{},
92207
93 /// A counter to keep track of how many `arg` instructions we've seen yet.208 /// A counter to keep track of how many `arg` instructions we've seen yet.
94 next_arg_index: u32,209 next_arg_index: u32 = 0,
95210
96 /// A map keeping track of which instruction generated which result-id.211 /// A map keeping track of which instruction generated which result-id.
97 inst_results: InstMap = .{},212 inst_results: InstMap = .{},
98213
99 /// A map that maps AIR intern pool indices to SPIR-V cache references (which214 /// A map that maps AIR intern pool indices to SPIR-V cache references.
100 /// is basically the same thing except for SPIR-V).215 /// See Object.type_map
101 /// This map is typically only used for structures that are deemed heavy enough216 type_map: *TypeMap,
102 /// that it is worth to store them here. The SPIR-V module also interns types,
103 /// and so the main purpose of this map is to avoid recomputation and to
104 /// cache extra information about the type rather than to aid in validity
105 /// of the SPIR-V module.
106 type_map: TypeMap = .{},
107217
108 /// We need to keep track of result ids for block labels, as well as the 'incoming'218 /// We need to keep track of result ids for block labels, as well as the 'incoming'
109 /// blocks for a block.219 /// blocks for a block.
...@@ -121,7 +231,7 @@ pub const DeclGen = struct {...@@ -121,7 +231,7 @@ pub const DeclGen = struct {
121231
122 /// If `gen` returned `Error.CodegenFail`, this contains an explanatory message.232 /// If `gen` returned `Error.CodegenFail`, this contains an explanatory message.
123 /// Memory is owned by `module.gpa`.233 /// Memory is owned by `module.gpa`.
124 error_msg: ?*Module.ErrorMsg,234 error_msg: ?*Module.ErrorMsg = null,
125235
126 /// Possible errors the `genDecl` function may return.236 /// Possible errors the `genDecl` function may return.
127 const Error = error{ CodegenFail, OutOfMemory };237 const Error = error{ CodegenFail, OutOfMemory };
...@@ -181,67 +291,10 @@ pub const DeclGen = struct {...@@ -181,67 +291,10 @@ pub const DeclGen = struct {
181 indirect,291 indirect,
182 };292 };
183293
184 /// Initialize the common resources of a DeclGen. Some fields are left uninitialized,
185 /// only set when `gen` is called.
186 pub fn init(
187 allocator: Allocator,
188 module: *Module,
189 spv: *SpvModule,
190 decl_link: *DeclLinkMap,
191 anon_decl_link: *AnonDeclLinkMap,
192 ) DeclGen {
193 return .{
194 .gpa = allocator,
195 .module = module,
196 .spv = spv,
197 .decl_index = undefined,
198 .air = undefined,
199 .liveness = undefined,
200 .decl_link = decl_link,
201 .anon_decl_link = anon_decl_link,
202 .next_arg_index = undefined,
203 .current_block_label_id = undefined,
204 .error_msg = undefined,
205 };
206 }
207
208 /// Generate the code for `decl`. If a reportable error occurred during code generation,
209 /// a message is returned by this function. Callee owns the memory. If this function
210 /// returns such a reportable error, it is valid to be called again for a different decl.
211 pub fn gen(self: *DeclGen, decl_index: Decl.Index, air: Air, liveness: Liveness) !?*Module.ErrorMsg {
212 // Reset internal resources, we don't want to re-allocate these.
213 self.decl_index = decl_index;
214 self.air = air;
215 self.liveness = liveness;
216 self.args.items.len = 0;
217 self.next_arg_index = 0;
218 self.inst_results.clearRetainingCapacity();
219 self.blocks.clearRetainingCapacity();
220 self.current_block_label_id = undefined;
221 self.func.reset();
222 self.base_line_stack.items.len = 0;
223 self.error_msg = null;
224
225 self.genDecl() catch |err| switch (err) {
226 error.CodegenFail => return self.error_msg,
227 else => |others| {
228 // There might be an error that happened *after* self.error_msg
229 // was already allocated, so be sure to free it.
230 if (self.error_msg) |error_msg| {
231 error_msg.deinit(self.module.gpa);
232 }
233 return others;
234 },
235 };
236
237 return null;
238 }
239
240 /// Free resources owned by the DeclGen.294 /// Free resources owned by the DeclGen.
241 pub fn deinit(self: *DeclGen) void {295 pub fn deinit(self: *DeclGen) void {
242 self.args.deinit(self.gpa);296 self.args.deinit(self.gpa);
243 self.inst_results.deinit(self.gpa);297 self.inst_results.deinit(self.gpa);
244 self.type_map.deinit(self.gpa);
245 self.blocks.deinit(self.gpa);298 self.blocks.deinit(self.gpa);
246 self.func.deinit(self.gpa);299 self.func.deinit(self.gpa);
247 self.base_line_stack.deinit(self.gpa);300 self.base_line_stack.deinit(self.gpa);
...@@ -277,7 +330,7 @@ pub const DeclGen = struct {...@@ -277,7 +330,7 @@ pub const DeclGen = struct {
277 .func => |func| func.owner_decl,330 .func => |func| func.owner_decl,
278 else => unreachable,331 else => unreachable,
279 };332 };
280 const spv_decl_index = try self.resolveDecl(fn_decl_index);333 const spv_decl_index = try self.object.resolveDecl(mod, fn_decl_index);
281 try self.func.decl_deps.put(self.spv.gpa, spv_decl_index, {});334 try self.func.decl_deps.put(self.spv.gpa, spv_decl_index, {});
282 return self.spv.declPtr(spv_decl_index).result_id;335 return self.spv.declPtr(spv_decl_index).result_id;
283 }336 }
...@@ -288,31 +341,10 @@ pub const DeclGen = struct {...@@ -288,31 +341,10 @@ pub const DeclGen = struct {
288 return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage.341 return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage.
289 }342 }
290343
291 /// Fetch or allocate a result id for decl index. This function also marks the decl as alive.
292 /// Note: Function does not actually generate the decl.
293 fn resolveDecl(self: *DeclGen, decl_index: Module.Decl.Index) !SpvModule.Decl.Index {
294 const mod = self.module;
295 const decl = mod.declPtr(decl_index);
296 try mod.markDeclAlive(decl);
297
298 const entry = try self.decl_link.getOrPut(decl_index);
299 if (!entry.found_existing) {
300 // TODO: Extern fn?
301 const kind: SpvModule.DeclKind = if (decl.val.isFuncBody(mod))
302 .func
303 else
304 .global;
305
306 entry.value_ptr.* = try self.spv.allocDecl(kind);
307 }
308
309 return entry.value_ptr.*;
310 }
311
312 fn resolveAnonDecl(self: *DeclGen, val: InternPool.Index, storage_class: StorageClass) !IdRef {344 fn resolveAnonDecl(self: *DeclGen, val: InternPool.Index, storage_class: StorageClass) !IdRef {
313 // TODO: This cannot be a function at this point, but it should probably be handled anyway.345 // TODO: This cannot be a function at this point, but it should probably be handled anyway.
314 const spv_decl_index = blk: {346 const spv_decl_index = blk: {
315 const entry = try self.anon_decl_link.getOrPut(.{ val, storage_class });347 const entry = try self.object.anon_decl_link.getOrPut(self.object.gpa, .{ val, storage_class });
316 if (entry.found_existing) {348 if (entry.found_existing) {
317 try self.func.decl_deps.put(self.spv.gpa, entry.value_ptr.*, {});349 try self.func.decl_deps.put(self.spv.gpa, entry.value_ptr.*, {});
318 return self.spv.declPtr(entry.value_ptr.*).result_id;350 return self.spv.declPtr(entry.value_ptr.*).result_id;
...@@ -988,7 +1020,7 @@ pub const DeclGen = struct {...@@ -988,7 +1020,7 @@ pub const DeclGen = struct {
988 return self.spv.constUndef(ty_ref);1020 return self.spv.constUndef(ty_ref);
989 }1021 }
9901022
991 const spv_decl_index = try self.resolveDecl(decl_index);1023 const spv_decl_index = try self.object.resolveDecl(mod, decl_index);
9921024
993 const decl_id = self.spv.declPtr(spv_decl_index).result_id;1025 const decl_id = self.spv.declPtr(spv_decl_index).result_id;
994 try self.func.decl_deps.put(self.spv.gpa, spv_decl_index, {});1026 try self.func.decl_deps.put(self.spv.gpa, spv_decl_index, {});
...@@ -1624,7 +1656,7 @@ pub const DeclGen = struct {...@@ -1624,7 +1656,7 @@ pub const DeclGen = struct {
1624 const mod = self.module;1656 const mod = self.module;
1625 const ip = &mod.intern_pool;1657 const ip = &mod.intern_pool;
1626 const decl = mod.declPtr(self.decl_index);1658 const decl = mod.declPtr(self.decl_index);
1627 const spv_decl_index = try self.resolveDecl(self.decl_index);1659 const spv_decl_index = try self.object.resolveDecl(mod, self.decl_index);
16281660
1629 const decl_id = self.spv.declPtr(spv_decl_index).result_id;1661 const decl_id = self.spv.declPtr(spv_decl_index).result_id;
16301662
src/link/SpirV.zig+14-35
...@@ -45,9 +45,7 @@ const IdResult = spec.IdResult;...@@ -45,9 +45,7 @@ const IdResult = spec.IdResult;
4545
46base: link.File,46base: link.File,
4747
48spv: SpvModule,48object: codegen.Object,
49decl_link: codegen.DeclLinkMap,
50anon_decl_link: codegen.AnonDeclLinkMap,
5149
52pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {50pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {
53 const self = try gpa.create(SpirV);51 const self = try gpa.create(SpirV);
...@@ -58,11 +56,8 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {...@@ -58,11 +56,8 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {
58 .file = null,56 .file = null,
59 .allocator = gpa,57 .allocator = gpa,
60 },58 },
61 .spv = undefined,59 .object = codegen.Object.init(gpa),
62 .decl_link = codegen.DeclLinkMap.init(self.base.allocator),
63 .anon_decl_link = codegen.AnonDeclLinkMap.init(self.base.allocator),
64 };60 };
65 self.spv = SpvModule.init(gpa);
66 errdefer self.deinit();61 errdefer self.deinit();
6762
68 // TODO: Figure out where to put all of these63 // TODO: Figure out where to put all of these
...@@ -99,9 +94,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option...@@ -99,9 +94,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
99}94}
10095
101pub fn deinit(self: *SpirV) void {96pub fn deinit(self: *SpirV) void {
102 self.spv.deinit();97 self.object.deinit();
103 self.decl_link.deinit();
104 self.anon_decl_link.deinit();
105}98}
10699
107pub fn updateFunc(self: *SpirV, module: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {100pub fn updateFunc(self: *SpirV, module: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
...@@ -113,12 +106,7 @@ pub fn updateFunc(self: *SpirV, module: *Module, func_index: InternPool.Index, a...@@ -113,12 +106,7 @@ pub fn updateFunc(self: *SpirV, module: *Module, func_index: InternPool.Index, a
113 const decl = module.declPtr(func.owner_decl);106 const decl = module.declPtr(func.owner_decl);
114 log.debug("lowering function {s}", .{module.intern_pool.stringToSlice(decl.name)});107 log.debug("lowering function {s}", .{module.intern_pool.stringToSlice(decl.name)});
115108
116 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link, &self.anon_decl_link);109 try self.object.updateFunc(module, func_index, air, liveness);
117 defer decl_gen.deinit();
118
119 if (try decl_gen.gen(func.owner_decl, air, liveness)) |msg| {
120 try module.failed_decls.put(module.gpa, func.owner_decl, msg);
121 }
122}110}
123111
124pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index) !void {112pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index) !void {
...@@ -129,12 +117,7 @@ pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index)...@@ -129,12 +117,7 @@ pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index)
129 const decl = module.declPtr(decl_index);117 const decl = module.declPtr(decl_index);
130 log.debug("lowering declaration {s}", .{module.intern_pool.stringToSlice(decl.name)});118 log.debug("lowering declaration {s}", .{module.intern_pool.stringToSlice(decl.name)});
131119
132 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link, &self.anon_decl_link);120 try self.object.updateDecl(module, decl_index);
133 defer decl_gen.deinit();
134
135 if (try decl_gen.gen(decl_index, undefined, undefined)) |msg| {
136 try module.failed_decls.put(module.gpa, decl_index, msg);
137 }
138}121}
139122
140pub fn updateDeclExports(123pub fn updateDeclExports(
...@@ -145,15 +128,9 @@ pub fn updateDeclExports(...@@ -145,15 +128,9 @@ pub fn updateDeclExports(
145) !void {128) !void {
146 const decl = mod.declPtr(decl_index);129 const decl = mod.declPtr(decl_index);
147 if (decl.val.isFuncBody(mod) and decl.ty.fnCallingConvention(mod) == .Kernel) {130 if (decl.val.isFuncBody(mod) and decl.ty.fnCallingConvention(mod) == .Kernel) {
148 // TODO: Unify with resolveDecl in spirv.zig.131 const spv_decl_index = try self.object.resolveDecl(mod, decl_index);
149 const entry = try self.decl_link.getOrPut(decl_index);
150 if (!entry.found_existing) {
151 entry.value_ptr.* = try self.spv.allocDecl(.func);
152 }
153 const spv_decl_index = entry.value_ptr.*;
154
155 for (exports) |exp| {132 for (exports) |exp| {
156 try self.spv.declareEntryPoint(spv_decl_index, mod.intern_pool.stringToSlice(exp.opts.name));133 try self.object.spv.declareEntryPoint(spv_decl_index, mod.intern_pool.stringToSlice(exp.opts.name));
157 }134 }
158 }135 }
159136
...@@ -185,15 +162,17 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No...@@ -185,15 +162,17 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No
185 sub_prog_node.activate();162 sub_prog_node.activate();
186 defer sub_prog_node.end();163 defer sub_prog_node.end();
187164
165 const spv = &self.object.spv;
166
188 const target = comp.getTarget();167 const target = comp.getTarget();
189 try writeCapabilities(&self.spv, target);168 try writeCapabilities(spv, target);
190 try writeMemoryModel(&self.spv, target);169 try writeMemoryModel(spv, target);
191170
192 // We need to export the list of error names somewhere so that we can pretty-print them in the171 // We need to export the list of error names somewhere so that we can pretty-print them in the
193 // executor. This is not really an important thing though, so we can just dump it in any old172 // executor. This is not really an important thing though, so we can just dump it in any old
194 // nonsemantic instruction. For now, just put it in OpSourceExtension with a special name.173 // nonsemantic instruction. For now, just put it in OpSourceExtension with a special name.
195174
196 var error_info = std.ArrayList(u8).init(self.spv.gpa);175 var error_info = std.ArrayList(u8).init(self.object.gpa);
197 defer error_info.deinit();176 defer error_info.deinit();
198177
199 try error_info.appendSlice("zig_errors");178 try error_info.appendSlice("zig_errors");
...@@ -209,11 +188,11 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No...@@ -209,11 +188,11 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No
209 defer self.base.allocator.free(escaped_name);188 defer self.base.allocator.free(escaped_name);
210 try error_info.writer().print(":{s}", .{escaped_name});189 try error_info.writer().print(":{s}", .{escaped_name});
211 }190 }
212 try self.spv.sections.debug_strings.emit(self.spv.gpa, .OpSourceExtension, .{191 try spv.sections.debug_strings.emit(spv.gpa, .OpSourceExtension, .{
213 .extension = error_info.items,192 .extension = error_info.items,
214 });193 });
215194
216 try self.spv.flush(self.base.file.?);195 try spv.flush(self.base.file.?);
217}196}
218197
219fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {198fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {