From 1606717b5fed83ee64ba1a91e55248e07a51afa6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 26 Sep 2023 18:36:57 -0700 Subject: [PATCH] C backend: flatten out some of the long-lived state When the compiler's state lives through multiple Compilation.update() calls, the C backend stores the rendered C source code for each decl code body and forward declarations. With this commit, the state is still stored, but it is managed in one big array list in link/C.zig rather than many array lists, one for each decl. This means simpler serialization and deserialization. --- src/codegen/c.zig | 4 +- src/link/C.zig | 144 ++++++++++++++++++++++++++++++---------------- 2 files changed, 95 insertions(+), 53 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index f6f39152d7..90474a9e28 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -489,9 +489,7 @@ pub const Function = struct { f.blocks.deinit(gpa); f.value_map.deinit(); f.lazy_fns.deinit(gpa); - f.object.code.deinit(); f.object.dg.ctypes.deinit(gpa); - f.object.dg.fwd_decl.deinit(); } fn typeOf(f: *Function, inst: Air.Inst.Ref) Type { @@ -509,6 +507,7 @@ pub const Function = struct { /// It is not available when generating .h file. pub const Object = struct { dg: DeclGen, + /// This is a borrowed reference from `link.C`. code: std.ArrayList(u8), /// Goes before code. Initialized and deinitialized in `genFunc`. code_header: std.ArrayList(u8) = undefined, @@ -525,6 +524,7 @@ pub const DeclGen = struct { module: *Module, decl: ?*Decl, decl_index: Decl.OptionalIndex, + /// This is a borrowed reference from `link.C`. fwd_decl: std.ArrayList(u8), error_msg: ?*Module.ErrorMsg, ctypes: CType.Store, diff --git a/src/link/C.zig b/src/link/C.zig index df7bfba354..8ffe33bb09 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -23,11 +23,39 @@ base: link.File, /// Instead, it tracks all declarations in this table, and iterates over it /// in the flush function, stitching pre-rendered pieces of C code together. decl_table: std.AutoArrayHashMapUnmanaged(Module.Decl.Index, DeclBlock) = .{}, +/// All the string bytes of rendered C code, all squished into one array. +/// While in progress, a separate buffer is used, and then when finished, the +/// buffer is copied into this one. +string_bytes: std.ArrayListUnmanaged(u8) = .{}, + +/// Optimization, `updateDecl` reuses this buffer rather than creating a new +/// one with every call. +fwd_decl_buf: std.ArrayListUnmanaged(u8) = .{}, +/// Optimization, `updateDecl` reuses this buffer rather than creating a new +/// one with every call. +code_buf: std.ArrayListUnmanaged(u8) = .{}, +/// Optimization, `flush` reuses this buffer rather than creating a new +/// one with every call. +lazy_fwd_decl_buf: std.ArrayListUnmanaged(u8) = .{}, +/// Optimization, `flush` reuses this buffer rather than creating a new +/// one with every call. +lazy_code_buf: std.ArrayListUnmanaged(u8) = .{}, + +/// A reference into `string_bytes`. +const String = struct { + start: u32, + len: u32, + + const empty: String = .{ + .start = 0, + .len = 0, + }; +}; /// Per-declaration data. const DeclBlock = struct { - code: std.ArrayListUnmanaged(u8) = .{}, - fwd_decl: std.ArrayListUnmanaged(u8) = .{}, + code: String = String.empty, + fwd_decl: String = String.empty, /// Each `Decl` stores a set of used `CType`s. In `flush()`, we iterate /// over each `Decl` and generate the definition for each used `CType` once. ctypes: codegen.CType.Store = .{}, @@ -37,12 +65,23 @@ const DeclBlock = struct { fn deinit(db: *DeclBlock, gpa: Allocator) void { db.lazy_fns.deinit(gpa); db.ctypes.deinit(gpa); - db.fwd_decl.deinit(gpa); - db.code.deinit(gpa); db.* = undefined; } }; +pub fn getString(this: C, s: String) []const u8 { + return this.string_bytes.items[s.start..][0..s.len]; +} + +pub fn addString(this: *C, s: []const u8) Allocator.Error!String { + const gpa = this.base.allocator; + try this.string_bytes.appendSlice(gpa, s); + return .{ + .start = @intCast(this.string_bytes.items.len - s.len), + .len = @intCast(s.len), + }; +} + pub fn openPath(gpa: Allocator, sub_path: []const u8, options: link.Options) !*C { assert(options.target.ofmt == .c); @@ -78,6 +117,10 @@ pub fn deinit(self: *C) void { db.deinit(gpa); } self.decl_table.deinit(gpa); + + self.string_bytes.deinit(gpa); + self.fwd_decl_buf.deinit(gpa); + self.code_buf.deinit(gpa); } pub fn freeDecl(self: *C, decl_index: Module.Decl.Index) void { @@ -102,12 +145,12 @@ pub fn updateFunc(self: *C, module: *Module, func_index: InternPool.Index, air: } const ctypes = &gop.value_ptr.ctypes; const lazy_fns = &gop.value_ptr.lazy_fns; - const fwd_decl = &gop.value_ptr.fwd_decl; - const code = &gop.value_ptr.code; + const fwd_decl = &self.fwd_decl_buf; + const code = &self.code_buf; ctypes.clearRetainingCapacity(gpa); lazy_fns.clearRetainingCapacity(); - fwd_decl.shrinkRetainingCapacity(0); - code.shrinkRetainingCapacity(0); + fwd_decl.clearRetainingCapacity(); + code.clearRetainingCapacity(); var function: codegen.Function = .{ .value_map = codegen.CValueMap.init(gpa), @@ -131,7 +174,11 @@ pub fn updateFunc(self: *C, module: *Module, func_index: InternPool.Index, air: }; function.object.indent_writer = .{ .underlying_writer = function.object.code.writer() }; - defer function.deinit(); + defer { + fwd_decl.* = function.object.dg.fwd_decl.moveToUnmanaged(); + code.* = function.object.code.moveToUnmanaged(); + function.deinit(); + } codegen.genFunc(&function) catch |err| switch (err) { error.AnalysisFail => { @@ -143,14 +190,13 @@ pub fn updateFunc(self: *C, module: *Module, func_index: InternPool.Index, air: ctypes.* = function.object.dg.ctypes.move(); lazy_fns.* = function.lazy_fns.move(); - fwd_decl.* = function.object.dg.fwd_decl.moveToUnmanaged(); - code.* = function.object.code.moveToUnmanaged(); // Free excess allocated memory for this Decl. ctypes.shrinkAndFree(gpa, ctypes.count()); lazy_fns.shrinkAndFree(gpa, lazy_fns.count()); - fwd_decl.shrinkAndFree(gpa, fwd_decl.items.len); - code.shrinkAndFree(gpa, code.items.len); + + gop.value_ptr.code = try self.addString(function.object.code.items); + gop.value_ptr.fwd_decl = try self.addString(function.object.dg.fwd_decl.items); } pub fn updateDecl(self: *C, module: *Module, decl_index: Module.Decl.Index) !void { @@ -164,11 +210,11 @@ pub fn updateDecl(self: *C, module: *Module, decl_index: Module.Decl.Index) !voi gop.value_ptr.* = .{}; } const ctypes = &gop.value_ptr.ctypes; - const fwd_decl = &gop.value_ptr.fwd_decl; - const code = &gop.value_ptr.code; + const fwd_decl = &self.fwd_decl_buf; + const code = &self.code_buf; ctypes.clearRetainingCapacity(gpa); - fwd_decl.shrinkRetainingCapacity(0); - code.shrinkRetainingCapacity(0); + fwd_decl.clearRetainingCapacity(); + code.clearRetainingCapacity(); const decl = module.declPtr(decl_index); @@ -187,9 +233,9 @@ pub fn updateDecl(self: *C, module: *Module, decl_index: Module.Decl.Index) !voi }; object.indent_writer = .{ .underlying_writer = object.code.writer() }; defer { - object.code.deinit(); object.dg.ctypes.deinit(object.dg.gpa); - object.dg.fwd_decl.deinit(); + fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); + code.* = object.code.moveToUnmanaged(); } codegen.genDecl(&object) catch |err| switch (err) { @@ -201,13 +247,12 @@ pub fn updateDecl(self: *C, module: *Module, decl_index: Module.Decl.Index) !voi }; ctypes.* = object.dg.ctypes.move(); - fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); - code.* = object.code.moveToUnmanaged(); // Free excess allocated memory for this Decl. ctypes.shrinkAndFree(gpa, ctypes.count()); - fwd_decl.shrinkAndFree(gpa, fwd_decl.items.len); - code.shrinkAndFree(gpa, code.items.len); + + gop.value_ptr.code = try self.addString(object.code.items); + gop.value_ptr.fwd_decl = try self.addString(object.dg.fwd_decl.items); } pub fn updateDeclLineNumber(self: *C, module: *Module, decl_index: Module.Decl.Index) !void { @@ -273,7 +318,9 @@ pub fn flushModule(self: *C, _: *Compilation, prog_node: *std.Progress.Node) !vo const lazy_index = f.all_buffers.items.len; f.all_buffers.items.len += 1; - try self.flushErrDecls(&f.lazy_db); + self.lazy_fwd_decl_buf.clearRetainingCapacity(); + self.lazy_code_buf.clearRetainingCapacity(); + try self.flushErrDecls(&f.lazy_ctypes); // `CType`s, forward decls, and non-functions first. // Unlike other backends, the .c code we are emitting is order-dependent. Therefore @@ -306,7 +353,7 @@ pub fn flushModule(self: *C, _: *Compilation, prog_node: *std.Progress.Node) !vo // We need to flush lazy ctypes after flushing all decls but before flushing any decl ctypes. // This ensures that every lazy CType.Index exactly matches the global CType.Index. assert(f.ctypes.count() == 0); - try self.flushCTypes(&f, .none, f.lazy_db.ctypes); + try self.flushCTypes(&f, .none, f.lazy_ctypes); var it = self.decl_table.iterator(); while (it.next()) |entry| @@ -319,16 +366,17 @@ pub fn flushModule(self: *C, _: *Compilation, prog_node: *std.Progress.Node) !vo }; f.file_size += f.ctypes_buf.items.len; + const lazy_fwd_decl_len = self.lazy_fwd_decl_buf.items.len; f.all_buffers.items[lazy_index] = .{ - .iov_base = if (f.lazy_db.fwd_decl.items.len > 0) f.lazy_db.fwd_decl.items.ptr else "", - .iov_len = f.lazy_db.fwd_decl.items.len, + .iov_base = if (lazy_fwd_decl_len > 0) self.lazy_fwd_decl_buf.items.ptr else "", + .iov_len = lazy_fwd_decl_len, }; - f.file_size += f.lazy_db.fwd_decl.items.len; + f.file_size += lazy_fwd_decl_len; // Now the code. try f.all_buffers.ensureUnusedCapacity(gpa, 1 + decl_values.len); - f.appendBufAssumeCapacity(f.lazy_db.code.items); - for (decl_values) |decl| f.appendBufAssumeCapacity(decl.code.items); + f.appendBufAssumeCapacity(self.lazy_code_buf.items); + for (decl_values) |decl| f.appendBufAssumeCapacity(self.getString(decl.code)); const file = self.base.file.?; try file.setEndPos(f.file_size); @@ -342,7 +390,7 @@ const Flush = struct { ctypes_map: std.ArrayListUnmanaged(codegen.CType.Index) = .{}, ctypes_buf: std.ArrayListUnmanaged(u8) = .{}, - lazy_db: DeclBlock = .{}, + lazy_ctypes: codegen.CType.Store = .{}, lazy_fns: LazyFns = .{}, asm_buf: std.ArrayListUnmanaged(u8) = .{}, @@ -364,7 +412,7 @@ const Flush = struct { f.all_buffers.deinit(gpa); f.asm_buf.deinit(gpa); f.lazy_fns.deinit(gpa); - f.lazy_db.deinit(gpa); + f.lazy_ctypes.deinit(gpa); f.ctypes_buf.deinit(gpa); f.ctypes_map.deinit(gpa); f.ctypes.deinit(gpa); @@ -462,12 +510,11 @@ fn flushCTypes( } } -fn flushErrDecls(self: *C, db: *DeclBlock) FlushDeclError!void { +fn flushErrDecls(self: *C, ctypes: *codegen.CType.Store) FlushDeclError!void { const gpa = self.base.allocator; - const fwd_decl = &db.fwd_decl; - const ctypes = &db.ctypes; - const code = &db.code; + const fwd_decl = &self.lazy_fwd_decl_buf; + const code = &self.lazy_code_buf; var object = codegen.Object{ .dg = .{ @@ -484,9 +531,9 @@ fn flushErrDecls(self: *C, db: *DeclBlock) FlushDeclError!void { }; object.indent_writer = .{ .underlying_writer = object.code.writer() }; defer { - object.code.deinit(); object.dg.ctypes.deinit(gpa); - object.dg.fwd_decl.deinit(); + fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); + code.* = object.code.moveToUnmanaged(); } codegen.genErrDecls(&object) catch |err| switch (err) { @@ -494,17 +541,14 @@ fn flushErrDecls(self: *C, db: *DeclBlock) FlushDeclError!void { else => |e| return e, }; - fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); ctypes.* = object.dg.ctypes.move(); - code.* = object.code.moveToUnmanaged(); } -fn flushLazyFn(self: *C, db: *DeclBlock, lazy_fn: codegen.LazyFnMap.Entry) FlushDeclError!void { +fn flushLazyFn(self: *C, ctypes: *codegen.CType.Store, lazy_fn: codegen.LazyFnMap.Entry) FlushDeclError!void { const gpa = self.base.allocator; - const fwd_decl = &db.fwd_decl; - const ctypes = &db.ctypes; - const code = &db.code; + const fwd_decl = &self.lazy_fwd_decl_buf; + const code = &self.lazy_code_buf; var object = codegen.Object{ .dg = .{ @@ -521,9 +565,9 @@ fn flushLazyFn(self: *C, db: *DeclBlock, lazy_fn: codegen.LazyFnMap.Entry) Flush }; object.indent_writer = .{ .underlying_writer = object.code.writer() }; defer { - object.code.deinit(); object.dg.ctypes.deinit(gpa); - object.dg.fwd_decl.deinit(); + fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); + code.* = object.code.moveToUnmanaged(); } codegen.genLazyFn(&object, lazy_fn) catch |err| switch (err) { @@ -531,21 +575,19 @@ fn flushLazyFn(self: *C, db: *DeclBlock, lazy_fn: codegen.LazyFnMap.Entry) Flush else => |e| return e, }; - fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); ctypes.* = object.dg.ctypes.move(); - code.* = object.code.moveToUnmanaged(); } fn flushLazyFns(self: *C, f: *Flush, lazy_fns: codegen.LazyFnMap) FlushDeclError!void { const gpa = self.base.allocator; - try f.lazy_fns.ensureUnusedCapacity(gpa, @as(Flush.LazyFns.Size, @intCast(lazy_fns.count()))); + try f.lazy_fns.ensureUnusedCapacity(gpa, @intCast(lazy_fns.count())); var it = lazy_fns.iterator(); while (it.next()) |entry| { const gop = f.lazy_fns.getOrPutAssumeCapacity(entry.key_ptr.*); if (gop.found_existing) continue; gop.value_ptr.* = {}; - try self.flushLazyFn(&f.lazy_db, entry); + try self.flushLazyFn(&f.lazy_ctypes, entry); } } @@ -573,7 +615,7 @@ fn flushDecl( try self.flushLazyFns(f, decl_block.lazy_fns); try f.all_buffers.ensureUnusedCapacity(gpa, 1); if (!(decl.isExtern(mod) and export_names.contains(decl.name))) - f.appendBufAssumeCapacity(decl_block.fwd_decl.items); + f.appendBufAssumeCapacity(self.getString(decl_block.fwd_decl)); } pub fn flushEmitH(module: *Module) !void {