authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-28 13:21:37-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-28 13:21:37-07:00
log234d94e42b832dd17eb9144f5523e03ef4fa8eb3
tree700e631cc733543083dc9bd5b6a53e0dedcc1ec5
parent8e93ec6d2491246c841c2f983bb48152b8de2837

C backend: emit decls sorted by dependencies

The C backend is the only backend that requires each decl to be output in an order that satisfies the dependency graph. Here it is implemented with a simple algorithm based on a `remaining_decls` set, using the `dependencies` edges that are already stored for each Decl. This satisfies incremental compilation as well as how `zig test` works, which calls `updateDecl` on `test_functions`.

1 files changed, 102 insertions(+), 57 deletions(-)

src/link/C.zig+102-57
......@@ -26,8 +26,7 @@ decl_table: std.AutoArrayHashMapUnmanaged(*const Module.Decl, DeclBlock) = .{},
2626/// Accumulates allocations and then there is a periodic garbage collection after flush().
2727arena: std.heap.ArenaAllocator,
2828
29/// Per-declaration data. For functions this is the body, and
30/// the forward declaration is stored in the FnBlock.
29/// Per-declaration data.
3130const DeclBlock = struct {
3231 code: std.ArrayListUnmanaged(u8) = .{},
3332 fwd_decl: std.ArrayListUnmanaged(u8) = .{},
......@@ -243,28 +242,26 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
243242 const tracy = trace(@src());
244243 defer tracy.end();
245244
245 const gpa = comp.gpa;
246246 const module = self.base.options.module.?;
247247
248248 // This code path happens exclusively with -ofmt=c. The flush logic for
249249 // emit-h is in `flushEmitH` below.
250250
251 // We collect a list of buffers to write, and write them all at once with pwritev 😎
252 var all_buffers = std.ArrayList(std.os.iovec_const).init(comp.gpa);
253 defer all_buffers.deinit();
251 var f: Flush = .{};
252 defer f.deinit(gpa);
254253
255254 // This is at least enough until we get to the function bodies without error handling.
256 try all_buffers.ensureTotalCapacity(self.decl_table.count() + 2);
255 try f.all_buffers.ensureTotalCapacity(gpa, self.decl_table.count() + 2);
257256
258 var file_size: u64 = zig_h.len;
259 all_buffers.appendAssumeCapacity(.{
257 f.all_buffers.appendAssumeCapacity(.{
260258 .iov_base = zig_h,
261259 .iov_len = zig_h.len,
262260 });
261 f.file_size += zig_h.len;
263262
264 var err_typedef_buf = std.ArrayList(u8).init(comp.gpa);
265 defer err_typedef_buf.deinit();
266 const err_typedef_writer = err_typedef_buf.writer();
267 const err_typedef_item = all_buffers.addOneAssumeCapacity();
263 const err_typedef_writer = f.err_typedef_buf.writer(gpa);
264 const err_typedef_item = f.all_buffers.addOneAssumeCapacity();
268265
269266 render_errors: {
270267 if (module.global_error_set.size == 0) break :render_errors;
......@@ -275,73 +272,121 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
275272 try err_typedef_writer.writeByte('\n');
276273 }
277274
278 var fn_count: usize = 0;
279 var typedefs = std.HashMap(Type, void, Type.HashContext64, std.hash_map.default_max_load_percentage).init(comp.gpa);
280 defer typedefs.deinit();
281
282275 // Typedefs, forward decls, and non-functions first.
283 // TODO: performance investigation: would keeping a list of Decls that we should
284 // generate, rather than querying here, be faster?
276 // Unlike other backends, the .c code we are emitting is order-dependent. Therefore
277 // we must traverse the set of Decls that we are emitting according to their dependencies.
278 // Our strategy is to populate a set of remaining decls, pop Decls one by one,
279 // recursively chasing their dependencies.
280 try f.remaining_decls.ensureUnusedCapacity(gpa, self.decl_table.count());
281
285282 const decl_keys = self.decl_table.keys();
286283 const decl_values = self.decl_table.values();
287 for (decl_keys) |decl, i| {
288 if (!decl.has_tv) continue; // TODO do we really need this branch?
289
290 const decl_block = &decl_values[i];
291
292 if (decl_block.fwd_decl.items.len != 0) {
293 try typedefs.ensureUnusedCapacity(@intCast(u32, decl_block.typedefs.count()));
294 var it = decl_block.typedefs.iterator();
295 while (it.next()) |new| {
296 const gop = typedefs.getOrPutAssumeCapacity(new.key_ptr.*);
297 if (!gop.found_existing) {
298 try err_typedef_writer.writeAll(new.value_ptr.rendered);
299 }
300 }
301 const buf = decl_block.fwd_decl.items;
302 all_buffers.appendAssumeCapacity(.{
303 .iov_base = buf.ptr,
304 .iov_len = buf.len,
305 });
306 file_size += buf.len;
307 }
308 if (decl.getFunction() != null) {
309 fn_count += 1;
310 } else if (decl_block.code.items.len != 0) {
311 const buf = decl_block.code.items;
312 all_buffers.appendAssumeCapacity(.{
313 .iov_base = buf.ptr,
314 .iov_len = buf.len,
315 });
316 file_size += buf.len;
317 }
284 for (decl_keys) |decl| {
285 assert(decl.has_tv);
286 f.remaining_decls.putAssumeCapacityNoClobber(decl, {});
287 }
288
289 while (f.remaining_decls.popOrNull()) |kv| {
290 const decl = kv.key;
291 try flushDecl(self, &f, decl);
318292 }
319293
320294 err_typedef_item.* = .{
321 .iov_base = err_typedef_buf.items.ptr,
322 .iov_len = err_typedef_buf.items.len,
295 .iov_base = f.err_typedef_buf.items.ptr,
296 .iov_len = f.err_typedef_buf.items.len,
323297 };
324 file_size += err_typedef_buf.items.len;
298 f.file_size += f.err_typedef_buf.items.len;
325299
326300 // Now the function bodies.
327 try all_buffers.ensureUnusedCapacity(fn_count);
301 try f.all_buffers.ensureUnusedCapacity(gpa, f.fn_count);
328302 for (decl_keys) |decl, i| {
329303 if (decl.getFunction() != null) {
330304 const decl_block = &decl_values[i];
331305 const buf = decl_block.code.items;
332306 if (buf.len != 0) {
333 all_buffers.appendAssumeCapacity(.{
307 f.all_buffers.appendAssumeCapacity(.{
334308 .iov_base = buf.ptr,
335309 .iov_len = buf.len,
336310 });
337 file_size += buf.len;
311 f.file_size += buf.len;
338312 }
339313 }
340314 }
341315
342316 const file = self.base.file.?;
343 try file.setEndPos(file_size);
344 try file.pwritevAll(all_buffers.items, 0);
317 try file.setEndPos(f.file_size);
318 try file.pwritevAll(f.all_buffers.items, 0);
319}
320
321const Flush = struct {
322 remaining_decls: std.AutoArrayHashMapUnmanaged(*const Module.Decl, void) = .{},
323 typedefs: Typedefs = .{},
324 err_typedef_buf: std.ArrayListUnmanaged(u8) = .{},
325 /// We collect a list of buffers to write, and write them all at once with pwritev 😎
326 all_buffers: std.ArrayListUnmanaged(std.os.iovec_const) = .{},
327 /// Keeps track of the total bytes of `all_buffers`.
328 file_size: u64 = 0,
329 fn_count: usize = 0,
330
331 const Typedefs = std.HashMapUnmanaged(
332 Type,
333 void,
334 Type.HashContext64,
335 std.hash_map.default_max_load_percentage,
336 );
337
338 fn deinit(f: *Flush, gpa: *Allocator) void {
339 f.all_buffers.deinit(gpa);
340 f.err_typedef_buf.deinit(gpa);
341 f.typedefs.deinit(gpa);
342 f.remaining_decls.deinit(gpa);
343 }
344};
345
346const FlushDeclError = error{
347 OutOfMemory,
348};
349
350/// Assumes `decl` was in the `remaining_decls` set, and has already been removed.
351fn flushDecl(self: *C, f: *Flush, decl: *const Module.Decl) FlushDeclError!void {
352 // Before flushing any particular Decl we must ensure its
353 // dependencies are already flushed, so that the order in the .c
354 // file comes out correctly.
355 for (decl.dependencies.keys()) |dep| {
356 if (f.remaining_decls.swapRemove(dep)) {
357 try flushDecl(self, f, dep);
358 }
359 }
360
361 const decl_block = self.decl_table.getPtr(decl).?;
362 const gpa = self.base.allocator;
363
364 if (decl_block.fwd_decl.items.len != 0) {
365 try f.typedefs.ensureUnusedCapacity(gpa, @intCast(u32, decl_block.typedefs.count()));
366 var it = decl_block.typedefs.iterator();
367 while (it.next()) |new| {
368 const gop = f.typedefs.getOrPutAssumeCapacity(new.key_ptr.*);
369 if (!gop.found_existing) {
370 try f.err_typedef_buf.appendSlice(gpa, new.value_ptr.rendered);
371 }
372 }
373 const buf = decl_block.fwd_decl.items;
374 f.all_buffers.appendAssumeCapacity(.{
375 .iov_base = buf.ptr,
376 .iov_len = buf.len,
377 });
378 f.file_size += buf.len;
379 }
380 if (decl.getFunction() != null) {
381 f.fn_count += 1;
382 } else if (decl_block.code.items.len != 0) {
383 const buf = decl_block.code.items;
384 f.all_buffers.appendAssumeCapacity(.{
385 .iov_base = buf.ptr,
386 .iov_len = buf.len,
387 });
388 f.file_size += buf.len;
389 }
345390}
346391
347392pub fn flushEmitH(module: *Module) !void {