Commit 1606717b5f

Andrew Kelley <andrew@ziglang.org>
2023-09-27 03:36:57
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.
1 parent 70563ae
Changed files (2)
src
codegen
link
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,
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 {