authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-12-02 00:22:26+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-04-09 01:51:51+02:00
logc9db6e43af366215cd8bf494ea54ad14e4b97cc0
treedbcb84fc48154347f36e108255780ec2506cb433
parent34b98ee372f8974e21cd34b3ca7588b13d93e31a
signaturelock-open Commit is signed but in an unrecognized format.

spirv: generate code directly in updateFunc/updateDecl

This cloneAir/cloneLiveness idea used to ignore Zig's internals has proven buggy. Instead, just generate the code directly from updateFunc and updateDecl as the other backends do, but pretend that Zig is not an incremental compiler. The SPIR-V backend will for the time being not support this.

2 files changed, 54 insertions(+), 148 deletions(-)

src/codegen/spirv.zig+23-7
...@@ -37,6 +37,8 @@ pub const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct {...@@ -37,6 +37,8 @@ pub const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
37 incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock),37 incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock),
38});38});
3939
40pub const DeclMap = std.AutoHashMap(Module.Decl.Index, IdResult);
41
40/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.42/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
41pub const DeclGen = struct {43pub const DeclGen = struct {
42 /// A general-purpose allocator that can be used for any allocations for this DeclGen.44 /// A general-purpose allocator that can be used for any allocations for this DeclGen.
...@@ -59,7 +61,8 @@ pub const DeclGen = struct {...@@ -59,7 +61,8 @@ pub const DeclGen = struct {
59 /// Note: If the declaration is not a function, this value will be undefined!61 /// Note: If the declaration is not a function, this value will be undefined!
60 liveness: Liveness,62 liveness: Liveness,
6163
62 ids: *const std.AutoHashMap(Decl.Index, IdResult),64 /// Maps Zig Decl indices to SPIR-V result indices.
65 decl_ids: *DeclMap,
6366
64 /// An array of function argument result-ids. Each index corresponds with the67 /// An array of function argument result-ids. Each index corresponds with the
65 /// function argument of the same index.68 /// function argument of the same index.
...@@ -149,7 +152,7 @@ pub const DeclGen = struct {...@@ -149,7 +152,7 @@ pub const DeclGen = struct {
149 allocator: Allocator,152 allocator: Allocator,
150 module: *Module,153 module: *Module,
151 spv: *SpvModule,154 spv: *SpvModule,
152 ids: *const std.AutoHashMap(Decl.Index, IdResult),155 decl_ids: *DeclMap,
153 ) DeclGen {156 ) DeclGen {
154 return .{157 return .{
155 .gpa = allocator,158 .gpa = allocator,
...@@ -158,7 +161,7 @@ pub const DeclGen = struct {...@@ -158,7 +161,7 @@ pub const DeclGen = struct {
158 .decl_index = undefined,161 .decl_index = undefined,
159 .air = undefined,162 .air = undefined,
160 .liveness = undefined,163 .liveness = undefined,
161 .ids = ids,164 .decl_ids = decl_ids,
162 .next_arg_index = undefined,165 .next_arg_index = undefined,
163 .current_block_label_id = undefined,166 .current_block_label_id = undefined,
164 .error_msg = undefined,167 .error_msg = undefined,
...@@ -232,9 +235,7 @@ pub const DeclGen = struct {...@@ -232,9 +235,7 @@ pub const DeclGen = struct {
232 .function => val.castTag(.function).?.data.owner_decl,235 .function => val.castTag(.function).?.data.owner_decl,
233 else => unreachable,236 else => unreachable,
234 };237 };
235 const decl = self.module.declPtr(fn_decl_index);238 return try self.resolveDecl(fn_decl_index);
236 self.module.markDeclAlive(decl);
237 return self.ids.get(fn_decl_index).?;
238 }239 }
239240
240 const result_id = self.spv.allocId();241 const result_id = self.spv.allocId();
...@@ -245,6 +246,21 @@ pub const DeclGen = struct {...@@ -245,6 +246,21 @@ pub const DeclGen = struct {
245 return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage.246 return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage.
246 }247 }
247248
249 /// Fetch or allocate a result id for decl index. This function also marks the decl as alive.
250 /// Note: Function does not actually generate the decl.
251 fn resolveDecl(self: *DeclGen, decl_index: Module.Decl.Index) !IdResult {
252 const decl = self.module.declPtr(decl_index);
253 self.module.markDeclAlive(decl);
254
255 const entry = try self.decl_ids.getOrPut(decl_index);
256 if (entry.found_existing) {
257 return entry.value_ptr.*;
258 }
259 const result_id = self.spv.allocId();
260 entry.value_ptr.* = result_id;
261 return result_id;
262 }
263
248 /// Start a new SPIR-V block, Emits the label of the new block, and stores which264 /// Start a new SPIR-V block, Emits the label of the new block, and stores which
249 /// block we are currently generating.265 /// block we are currently generating.
250 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to266 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to
...@@ -767,8 +783,8 @@ pub const DeclGen = struct {...@@ -767,8 +783,8 @@ pub const DeclGen = struct {
767 }783 }
768784
769 fn genDecl(self: *DeclGen) !void {785 fn genDecl(self: *DeclGen) !void {
770 const result_id = self.ids.get(self.decl_index).?;
771 const decl = self.module.declPtr(self.decl_index);786 const decl = self.module.declPtr(self.decl_index);
787 const result_id = try self.resolveDecl(self.decl_index);
772788
773 if (decl.val.castTag(.function)) |_| {789 if (decl.val.castTag(.function)) |_| {
774 assert(decl.ty.zigTypeTag() == .Fn);790 assert(decl.ty.zigTypeTag() == .Fn);
src/link/SpirV.zig+31-141
...@@ -44,34 +44,25 @@ const IdResult = spec.IdResult;...@@ -44,34 +44,25 @@ const IdResult = spec.IdResult;
4444
45base: link.File,45base: link.File,
4646
47/// This linker backend does not try to incrementally link output SPIR-V code.47spv: SpvModule,
48/// Instead, it tracks all declarations in this table, and iterates over it48spv_arena: ArenaAllocator,
49/// in the flush function.49decl_ids: codegen.DeclMap,
50decl_table: std.AutoArrayHashMapUnmanaged(Module.Decl.Index, DeclGenContext) = .{},
51
52const DeclGenContext = struct {
53 air: Air,
54 air_arena: ArenaAllocator.State,
55 liveness: Liveness,
56
57 fn deinit(self: *DeclGenContext, gpa: Allocator) void {
58 self.air.deinit(gpa);
59 self.liveness.deinit(gpa);
60 self.air_arena.promote(gpa).deinit();
61 self.* = undefined;
62 }
63};
6450
65pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {51pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {
66 const spirv = try gpa.create(SpirV);52 const self = try gpa.create(SpirV);
67 spirv.* = .{53 self.* = .{
68 .base = .{54 .base = .{
69 .tag = .spirv,55 .tag = .spirv,
70 .options = options,56 .options = options,
71 .file = null,57 .file = null,
72 .allocator = gpa,58 .allocator = gpa,
73 },59 },
60 .spv = undefined,
61 .spv_arena = ArenaAllocator.init(gpa),
62 .decl_ids = codegen.DeclMap.init(self.base.allocator),
74 };63 };
64 self.spv = SpvModule.init(gpa, self.spv_arena.allocator());
65 errdefer self.deinit();
7566
76 // TODO: Figure out where to put all of these67 // TODO: Figure out where to put all of these
77 switch (options.target.cpu.arch) {68 switch (options.target.cpu.arch) {
...@@ -88,7 +79,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {...@@ -88,7 +79,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {
88 return error.TODOAbiNotSupported;79 return error.TODOAbiNotSupported;
89 }80 }
9081
91 return spirv;82 return self;
92}83}
9384
94pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*SpirV {85pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*SpirV {
...@@ -107,44 +98,35 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option...@@ -107,44 +98,35 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
107}98}
10899
109pub fn deinit(self: *SpirV) void {100pub fn deinit(self: *SpirV) void {
110 self.decl_table.deinit(self.base.allocator);101 self.spv.deinit();
102 self.spv_arena.deinit();
103 self.decl_ids.deinit();
111}104}
112105
113pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {106pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
114 if (build_options.skip_non_native) {107 if (build_options.skip_non_native) {
115 @panic("Attempted to compile for architecture that was disabled by build configuration");108 @panic("Attempted to compile for architecture that was disabled by build configuration");
116 }109 }
117 _ = module;
118
119 // Keep track of all decls so we can iterate over them on flush().
120 const result = try self.decl_table.getOrPut(self.base.allocator, func.owner_decl);
121 if (result.found_existing) {
122 result.value_ptr.deinit(self.base.allocator);
123 }
124
125 var arena = ArenaAllocator.init(self.base.allocator);
126 errdefer arena.deinit();
127110
128 var new_air = try cloneAir(air, self.base.allocator, arena.allocator());111 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_ids);
129 errdefer new_air.deinit(self.base.allocator);112 defer decl_gen.deinit();
130
131 var new_liveness = try cloneLiveness(liveness, self.base.allocator);
132 errdefer new_liveness.deinit(self.base.allocator);
133113
134 result.value_ptr.* = .{114 if (try decl_gen.gen(func.owner_decl, air, liveness)) |msg| {
135 .air = new_air,115 try module.failed_decls.put(module.gpa, func.owner_decl, msg);
136 .air_arena = arena.state,116 }
137 .liveness = new_liveness,
138 };
139}117}
140118
141pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index) !void {119pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index) !void {
142 if (build_options.skip_non_native) {120 if (build_options.skip_non_native) {
143 @panic("Attempted to compile for architecture that was disabled by build configuration");121 @panic("Attempted to compile for architecture that was disabled by build configuration");
144 }122 }
145 _ = module;123
146 // Keep track of all decls so we can iterate over them on flush().124 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_ids);
147 _ = try self.decl_table.getOrPut(self.base.allocator, decl_index);125 defer decl_gen.deinit();
126
127 if (try decl_gen.gen(decl_index, undefined, undefined)) |msg| {
128 try module.failed_decls.put(module.gpa, decl_index, msg);
129 }
148}130}
149131
150pub fn updateDeclExports(132pub fn updateDeclExports(
...@@ -160,13 +142,8 @@ pub fn updateDeclExports(...@@ -160,13 +142,8 @@ pub fn updateDeclExports(
160}142}
161143
162pub fn freeDecl(self: *SpirV, decl_index: Module.Decl.Index) void {144pub fn freeDecl(self: *SpirV, decl_index: Module.Decl.Index) void {
163 if (self.decl_table.getIndex(decl_index)) |index| {145 _ = self;
164 const module = self.base.options.module.?;146 _ = decl_index;
165 const decl = module.declPtr(decl_index);
166 if (decl.val.tag() == .function) {
167 self.decl_table.values()[index].deinit(self.base.allocator);
168 }
169 }
170}147}
171148
172pub fn flush(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {149pub fn flush(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
...@@ -189,56 +166,11 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No...@@ -189,56 +166,11 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No
189 sub_prog_node.activate();166 sub_prog_node.activate();
190 defer sub_prog_node.end();167 defer sub_prog_node.end();
191168
192 const module = self.base.options.module.?;
193 const target = comp.getTarget();169 const target = comp.getTarget();
170 try writeCapabilities(&self.spv, target);
171 try writeMemoryModel(&self.spv, target);
194172
195 var arena = std.heap.ArenaAllocator.init(self.base.allocator);173 try self.spv.flush(self.base.file.?);
196 defer arena.deinit();
197
198 var spv = SpvModule.init(self.base.allocator, arena.allocator());
199 defer spv.deinit();
200
201 // Allocate an ID for every declaration before generating code,
202 // so that we can access them before processing them.
203 // TODO: We're allocating an ID unconditionally now, are there
204 // declarations which don't generate a result?
205 var ids = std.AutoHashMap(Module.Decl.Index, IdResult).init(self.base.allocator);
206 defer ids.deinit();
207 try ids.ensureTotalCapacity(@intCast(u32, self.decl_table.count()));
208
209 for (self.decl_table.keys()) |decl_index| {
210 const decl = module.declPtr(decl_index);
211 if (decl.has_tv) {
212 ids.putAssumeCapacityNoClobber(decl_index, spv.allocId());
213 }
214 }
215
216 // Now, actually generate the code for all declarations.
217 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &spv, &ids);
218 defer decl_gen.deinit();
219
220 var it = self.decl_table.iterator();
221 while (it.next()) |entry| {
222 const decl_index = entry.key_ptr.*;
223 const decl = module.declPtr(decl_index);
224 if (!decl.has_tv) continue;
225
226 const air = entry.value_ptr.air;
227 const liveness = entry.value_ptr.liveness;
228
229 log.debug("generating code for {s}", .{decl.name});
230
231 // Note, if `decl` is not a function, air/liveness may be undefined.
232 if (try decl_gen.gen(decl_index, air, liveness)) |msg| {
233 try module.failed_decls.put(module.gpa, decl_index, msg);
234 return; // TODO: Attempt to generate more decls?
235 }
236 }
237
238 try writeCapabilities(&spv, target);
239 try writeMemoryModel(&spv, target);
240
241 try spv.flush(self.base.file.?);
242}174}
243175
244fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {176fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {
...@@ -281,45 +213,3 @@ fn writeMemoryModel(spv: *SpvModule, target: std.Target) !void {...@@ -281,45 +213,3 @@ fn writeMemoryModel(spv: *SpvModule, target: std.Target) !void {
281 .memory_model = memory_model,213 .memory_model = memory_model,
282 });214 });
283}215}
284
285fn cloneLiveness(l: Liveness, gpa: Allocator) !Liveness {
286 const tomb_bits = try gpa.dupe(usize, l.tomb_bits);
287 errdefer gpa.free(tomb_bits);
288
289 const extra = try gpa.dupe(u32, l.extra);
290 errdefer gpa.free(extra);
291
292 return Liveness{
293 .tomb_bits = tomb_bits,
294 .extra = extra,
295 .special = try l.special.clone(gpa),
296 };
297}
298
299fn cloneAir(air: Air, gpa: Allocator, air_arena: Allocator) !Air {
300 const values = try gpa.alloc(Value, air.values.len);
301 errdefer gpa.free(values);
302
303 for (values, 0..) |*value, i| {
304 value.* = try air.values[i].copy(air_arena);
305 }
306
307 var instructions = try air.instructions.toMultiArrayList().clone(gpa);
308 errdefer instructions.deinit(gpa);
309
310 const air_tags = instructions.items(.tag);
311 const air_datas = instructions.items(.data);
312
313 for (air_tags, 0..) |tag, i| {
314 switch (tag) {
315 .alloc, .ret_ptr, .const_ty => air_datas[i].ty = try air_datas[i].ty.copy(air_arena),
316 else => {},
317 }
318 }
319
320 return Air{
321 .instructions = instructions.slice(),
322 .extra = try gpa.dupe(u32, air.extra),
323 .values = values,
324 };
325}