Commit 6a4266d62a

Jacob Young <jacobly0@users.noreply.github.com>
2022-10-08 14:54:49
cbe: fix infinite recursion on recursive types
1 parent 7c9a9a0
lib/include/zig.h
@@ -162,6 +162,7 @@
 #include <stdint.h>
 #include <stddef.h>
 #include <limits.h>
+#include <math.h>
 
 #define int128_t __int128
 #define uint128_t unsigned __int128
src/codegen/c.zig
@@ -372,8 +372,8 @@ pub const DeclGen = struct {
     }
 
     fn getTypedefName(dg: *DeclGen, t: Type) ?[]const u8 {
-        if (dg.typedefs.get(t)) |some| {
-            return some.name;
+        if (dg.typedefs.get(t)) |typedef| {
+            return typedef.name;
         } else {
             return null;
         }
@@ -1025,9 +1025,10 @@ pub const DeclGen = struct {
         try dg.renderType(bw, fn_info.return_type);
         try bw.writeAll(" (*");
 
-        const name_start = buffer.items.len;
-        try bw.print("zig_F_{})(", .{typeToCIdentifier(t, dg.module)});
-        const name_end = buffer.items.len - 2;
+        const name_begin = buffer.items.len;
+        try bw.print("zig_F_{}", .{typeToCIdentifier(t, dg.module)});
+        const name_end = buffer.items.len;
+        try bw.writeAll(")(");
 
         const param_len = fn_info.param_types.len;
 
@@ -1052,7 +1053,7 @@ pub const DeclGen = struct {
 
         const rendered = buffer.toOwnedSlice();
         errdefer dg.typedefs.allocator.free(rendered);
-        const name = rendered[name_start..name_end];
+        const name = rendered[name_begin..name_end];
 
         try dg.typedefs.ensureUnusedCapacity(1);
         dg.typedefs.putAssumeCapacityNoClobber(
@@ -1079,12 +1080,11 @@ pub const DeclGen = struct {
         const child_type = t.childType();
 
         try bw.writeAll("; size_t len; } ");
-        const name_index = buffer.items.len;
-        if (t.isConstPtr()) {
-            try bw.print("zig_L_{}", .{typeToCIdentifier(child_type, dg.module)});
-        } else {
-            try bw.print("zig_M_{}", .{typeToCIdentifier(child_type, dg.module)});
-        }
+        const name_begin = buffer.items.len;
+        try bw.print("zig_{c}_{}", .{
+            @as(u8, if (t.isConstPtr()) 'L' else 'M'),
+            typeToCIdentifier(child_type, dg.module),
+        });
         if (ptr_sentinel) |s| {
             var sentinel_buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
             defer sentinel_buffer.deinit();
@@ -1092,11 +1092,12 @@ pub const DeclGen = struct {
             try dg.renderValue(sentinel_buffer.writer(), child_type, s, .Identifier);
             try bw.print("_s_{}", .{fmtIdent(sentinel_buffer.items)});
         }
+        const name_end = buffer.items.len;
         try bw.writeAll(";\n");
 
         const rendered = buffer.toOwnedSlice();
         errdefer dg.typedefs.allocator.free(rendered);
-        const name = rendered[name_index .. rendered.len - 2];
+        const name = rendered[name_begin..name_end];
 
         try dg.typedefs.ensureUnusedCapacity(1);
         dg.typedefs.putAssumeCapacityNoClobber(
@@ -1115,7 +1116,30 @@ pub const DeclGen = struct {
         var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
         defer buffer.deinit();
 
-        try buffer.appendSlice("typedef struct {\n");
+        const tag = "struct";
+        const tagged_name_begin = buffer.items.len + "typedef ".len + tag.len + " ".len;
+        try buffer.writer().print("typedef " ++ tag ++ " zig_S_{} ", .{fmtIdent(fqn)});
+        const tagged_name_end = buffer.items.len - " ".len;
+        try buffer.ensureUnusedCapacity(tagged_name_end - tagged_name_begin + ";\n".len);
+        const name_begin = buffer.items.len;
+        buffer.appendSliceAssumeCapacity(buffer.items[tagged_name_begin..tagged_name_end]);
+        const name_end = buffer.items.len;
+        buffer.appendSliceAssumeCapacity(";\n");
+
+        const rendered = buffer.toOwnedSlice();
+        errdefer dg.typedefs.allocator.free(rendered);
+        const tagged_name = rendered[tagged_name_begin..tagged_name_end];
+        const name = rendered[name_begin..name_end];
+
+        try dg.typedefs.ensureUnusedCapacity(1);
+        dg.typedefs.putAssumeCapacityNoClobber(
+            try t.copy(dg.typedefs_arena),
+            .{ .name = name, .rendered = rendered },
+        );
+
+        try buffer.appendSlice(tag ++ " ");
+        try buffer.appendSlice(tagged_name);
+        try buffer.appendSlice(" {\n");
         {
             var it = struct_obj.fields.iterator();
             var empty = true;
@@ -1124,68 +1148,63 @@ pub const DeclGen = struct {
                 if (!field_ty.hasRuntimeBits()) continue;
 
                 const alignment = entry.value_ptr.abi_align;
-                const name: CValue = .{ .identifier = entry.key_ptr.* };
+                const field_name: CValue = .{ .identifier = entry.key_ptr.* };
                 try buffer.append(' ');
-                try dg.renderTypeAndName(buffer.writer(), field_ty, name, .Mut, alignment);
+                try dg.renderTypeAndName(buffer.writer(), field_ty, field_name, .Mut, alignment);
                 try buffer.appendSlice(";\n");
 
                 empty = false;
             }
             if (empty) try buffer.appendSlice(" char empty_struct;\n");
         }
-        try buffer.appendSlice("} ");
+        try buffer.appendSlice("};\n");
 
-        const name_start = buffer.items.len;
-        try buffer.writer().print("zig_S_{};\n", .{fmtIdent(fqn)});
-
-        const rendered = buffer.toOwnedSlice();
-        errdefer dg.typedefs.allocator.free(rendered);
-        const name = rendered[name_start .. rendered.len - 2];
+        const rendered_body = buffer.toOwnedSlice();
+        errdefer dg.typedefs.allocator.free(rendered_body);
 
+        // We need to add another item to the TypedefMap, so we need a distinct
+        // type that is not used anywhere, but is still uniquely associated with
+        // this type, so use an empty struct which references our unique decls.
         try dg.typedefs.ensureUnusedCapacity(1);
         dg.typedefs.putAssumeCapacityNoClobber(
-            try t.copy(dg.typedefs_arena),
-            .{ .name = name, .rendered = rendered },
+            try Type.Tag.empty_struct.create(dg.typedefs_arena, &struct_obj.namespace),
+            .{ .name = undefined, .rendered = rendered_body },
         );
 
         return name;
     }
 
     fn renderTupleTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
-        const tuple = t.tupleFields();
-
         var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
         defer buffer.deinit();
-        const writer = buffer.writer();
 
         try buffer.appendSlice("typedef struct {\n");
         {
+            const fields = t.tupleFields();
             var empty = true;
-            for (tuple.types) |field_ty, i| {
+            for (fields.types) |field_ty, i| {
                 if (!field_ty.hasRuntimeBits()) continue;
-                const val = tuple.values[i];
+                const val = fields.values[i];
                 if (val.tag() != .unreachable_value) continue;
 
-                var name = std.ArrayList(u8).init(dg.gpa);
-                defer name.deinit();
-                try name.writer().print("field_{d}", .{i});
+                const field_name = try std.fmt.allocPrint(dg.typedefs.allocator, "field_{d}", .{i});
+                defer dg.typedefs.allocator.free(field_name);
 
                 try buffer.append(' ');
-                try dg.renderTypeAndName(writer, field_ty, .{ .bytes = name.items }, .Mut, 0);
+                try dg.renderTypeAndName(buffer.writer(), field_ty, .{ .identifier = field_name }, .Mut, 0);
                 try buffer.appendSlice(";\n");
 
                 empty = false;
             }
             if (empty) try buffer.appendSlice(" char empty_tuple;\n");
         }
-        try buffer.appendSlice("} ");
-
-        const name_start = buffer.items.len;
-        try writer.print("zig_T_{};\n", .{typeToCIdentifier(t, dg.module)});
+        const name_begin = buffer.items.len + "} ".len;
+        try buffer.writer().print("}} zig_T_{};\n", .{typeToCIdentifier(t, dg.module)});
+        const name_end = buffer.items.len - ";\n".len;
 
         const rendered = buffer.toOwnedSlice();
         errdefer dg.typedefs.allocator.free(rendered);
-        const name = rendered[name_start .. rendered.len - 2];
+        const name = rendered[name_begin..name_end];
 
         try dg.typedefs.ensureUnusedCapacity(1);
         dg.typedefs.putAssumeCapacityNoClobber(
@@ -1197,62 +1216,85 @@ pub const DeclGen = struct {
     }
 
     fn renderUnionTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
-        const union_ty = t.cast(Type.Payload.Union).?.data;
-        const fqn = try union_ty.getFullyQualifiedName(dg.module);
+        const union_obj = t.cast(Type.Payload.Union).?.data;
+        const fqn = try union_obj.getFullyQualifiedName(dg.module);
         defer dg.typedefs.allocator.free(fqn);
 
-        const target = dg.module.getTarget();
-        const layout = t.unionGetLayout(target);
-
         var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
         defer buffer.deinit();
 
-        try buffer.appendSlice("typedef ");
-        if (t.unionTagTypeSafety()) |tag_ty| {
-            const name: CValue = .{ .bytes = "tag" };
-            try buffer.appendSlice("struct {\n ");
+        const tag: []const u8 = if (t.unionTagTypeSafety()) |_| "struct" else "union";
+        const tagged_name_begin = buffer.items.len + "typedef ".len + tag.len + " ".len;
+        try buffer.writer().print("typedef {s} zig_S_{} ", .{ tag, fmtIdent(fqn) });
+        const tagged_name_end = buffer.items.len - " ".len;
+        try buffer.ensureUnusedCapacity(tagged_name_end - tagged_name_begin + ";\n".len);
+        const name_begin = buffer.items.len;
+        buffer.appendSliceAssumeCapacity(buffer.items[tagged_name_begin..tagged_name_end]);
+        const name_end = buffer.items.len;
+        buffer.appendSliceAssumeCapacity(";\n");
+
+        const rendered = buffer.toOwnedSlice();
+        errdefer dg.typedefs.allocator.free(rendered);
+        const tagged_name = rendered[tagged_name_begin..tagged_name_end];
+        const name = rendered[name_begin..name_end];
+
+        try dg.typedefs.ensureUnusedCapacity(1);
+        dg.typedefs.putAssumeCapacityNoClobber(
+            try t.copy(dg.typedefs_arena),
+            .{ .name = name, .rendered = rendered },
+        );
+
+        try buffer.appendSlice(tag);
+        try buffer.append(' ');
+        try buffer.appendSlice(tagged_name);
+        try buffer.appendSlice(" {\n");
+
+        const indent = if (t.unionTagTypeSafety()) |tag_ty| indent: {
+            const target = dg.module.getTarget();
+            const layout = t.unionGetLayout(target);
             if (layout.tag_size != 0) {
-                try dg.renderTypeAndName(buffer.writer(), tag_ty, name, .Mut, 0);
+                try buffer.append(' ');
+                try dg.renderTypeAndName(buffer.writer(), tag_ty, .{ .identifier = "tag" }, .Mut, 0);
                 try buffer.appendSlice(";\n");
             }
-        }
+            try buffer.appendSlice(" union {\n");
+            break :indent "  ";
+        } else " ";
 
-        try buffer.appendSlice("union {\n");
-        const fields = t.unionFields();
         {
-            var it = fields.iterator();
+            var it = t.unionFields().iterator();
             var empty = true;
             while (it.next()) |entry| {
                 const field_ty = entry.value_ptr.ty;
                 if (!field_ty.hasRuntimeBits()) continue;
 
                 const alignment = entry.value_ptr.abi_align;
-                const name: CValue = .{ .identifier = entry.key_ptr.* };
-                try buffer.append(' ');
-                try dg.renderTypeAndName(buffer.writer(), field_ty, name, .Mut, alignment);
+                const field_name: CValue = .{ .identifier = entry.key_ptr.* };
+                try buffer.appendSlice(indent);
+                try dg.renderTypeAndName(buffer.writer(), field_ty, field_name, .Mut, alignment);
                 try buffer.appendSlice(";\n");
 
                 empty = false;
             }
-            if (empty) try buffer.appendSlice(" char empty_union;\n");
-        }
-        try buffer.appendSlice("} ");
-
-        if (t.unionTagTypeSafety()) |_| {
-            try buffer.appendSlice("payload;\n} ");
+            if (empty) {
+                try buffer.appendSlice(indent);
+                try buffer.appendSlice("char empty_union;\n");
+            }
         }
 
-        const name_start = buffer.items.len;
-        try buffer.writer().print("zig_U_{};\n", .{fmtIdent(fqn)});
+        if (t.unionTagTypeSafety()) |_| try buffer.appendSlice(" } payload;\n");
+        try buffer.appendSlice("};\n");
 
-        const rendered = buffer.toOwnedSlice();
-        errdefer dg.typedefs.allocator.free(rendered);
-        const name = rendered[name_start .. rendered.len - 2];
+        const rendered_body = buffer.toOwnedSlice();
+        errdefer dg.typedefs.allocator.free(rendered_body);
 
+        // We need to add another item to the TypedefMap, so we need a distinct
+        // type that is not used anywhere, but is still uniquely associated with
+        // this type, so use an empty struct which references our unique decls.
         try dg.typedefs.ensureUnusedCapacity(1);
         dg.typedefs.putAssumeCapacityNoClobber(
-            try t.copy(dg.typedefs_arena),
-            .{ .name = name, .rendered = rendered },
+            try Type.Tag.empty_struct.create(dg.typedefs_arena, &union_obj.namespace),
+            .{ .name = undefined, .rendered = rendered_body },
         );
 
         return name;
@@ -1280,21 +1322,22 @@ pub const DeclGen = struct {
             try bw.writeAll("; } ");
         }
 
-        const name_index = buffer.items.len;
+        const name_begin = buffer.items.len;
         if (error_ty.castTag(.error_set_inferred)) |inf_err_set_payload| {
             const func = inf_err_set_payload.data.func;
             try bw.writeAll("zig_E_");
             try dg.renderDeclName(bw, func.owner_decl);
-            try bw.writeAll(";\n");
         } else {
-            try bw.print("zig_E_{}_{};\n", .{
+            try bw.print("zig_E_{}_{}", .{
                 typeToCIdentifier(error_ty, dg.module), typeToCIdentifier(payload_ty, dg.module),
             });
         }
+        const name_end = buffer.items.len;
+        try bw.writeAll(";\n");
 
         const rendered = buffer.toOwnedSlice();
         errdefer dg.typedefs.allocator.free(rendered);
-        const name = rendered[name_index .. rendered.len - 2];
+        const name = rendered[name_begin..name_end];
 
         try dg.typedefs.ensureUnusedCapacity(1);
         dg.typedefs.putAssumeCapacityNoClobber(
@@ -1315,7 +1358,7 @@ pub const DeclGen = struct {
         try bw.writeAll("typedef ");
         try dg.renderType(bw, elem_type);
 
-        const name_start = buffer.items.len + 1;
+        const name_begin = buffer.items.len + " ".len;
         try bw.print(" zig_A_{}_{d}", .{ typeToCIdentifier(elem_type, dg.module), t.arrayLen() });
         if (t.sentinel()) |s| {
             var sentinel_buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
@@ -1331,7 +1374,7 @@ pub const DeclGen = struct {
 
         const rendered = buffer.toOwnedSlice();
         errdefer dg.typedefs.allocator.free(rendered);
-        const name = rendered[name_start..name_end];
+        const name = rendered[name_begin..name_end];
 
         try dg.typedefs.ensureUnusedCapacity(1);
         dg.typedefs.putAssumeCapacityNoClobber(
@@ -1351,12 +1394,15 @@ pub const DeclGen = struct {
         const payload_name = CValue{ .bytes = "payload" };
         try dg.renderTypeAndName(bw, child_type, payload_name, .Mut, 0);
         try bw.writeAll("; bool is_null; } ");
-        const name_index = buffer.items.len;
-        try bw.print("zig_Q_{};\n", .{typeToCIdentifier(child_type, dg.module)});
+
+        const name_begin = buffer.items.len;
+        try bw.print("zig_Q_{}", .{typeToCIdentifier(child_type, dg.module)});
+        const name_end = buffer.items.len;
+        try bw.writeAll(";\n");
 
         const rendered = buffer.toOwnedSlice();
         errdefer dg.typedefs.allocator.free(rendered);
-        const name = rendered[name_index .. rendered.len - 2];
+        const name = rendered[name_begin..name_end];
 
         try dg.typedefs.ensureUnusedCapacity(1);
         dg.typedefs.putAssumeCapacityNoClobber(
@@ -1378,12 +1424,14 @@ pub const DeclGen = struct {
 
         try buffer.writer().print("typedef struct { } ", .{fmtIdent(std.mem.span(unqualified_name))});
 
-        const name_start = buffer.items.len;
-        try buffer.writer().print("zig_O_{};\n", .{fmtIdent(fqn)});
+        const name_begin = buffer.items.len;
+        try buffer.writer().print("zig_O_{}", .{fmtIdent(fqn)});
+        const name_end = buffer.items.len;
+        try buffer.appendSlice(";\n");
 
         const rendered = buffer.toOwnedSlice();
         errdefer dg.typedefs.allocator.free(rendered);
-        const name = rendered[name_start .. rendered.len - 2];
+        const name = rendered[name_begin..name_end];
 
         try dg.typedefs.ensureUnusedCapacity(1);
         dg.typedefs.putAssumeCapacityNoClobber(
@@ -1530,7 +1578,7 @@ pub const DeclGen = struct {
                 return w.writeAll(name);
             },
             .Struct => {
-                const name = dg.getTypedefName(t) orelse if (t.isTuple() or t.tag() == .anon_struct)
+                const name = dg.getTypedefName(t) orelse if (t.isTupleOrAnonStruct())
                     try dg.renderTupleTypedef(t)
                 else
                     try dg.renderStructTypedef(t);
@@ -3597,8 +3645,8 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc
     var field_name: []const u8 = undefined;
     var field_val_ty: Type = undefined;
 
-    var buf = std.ArrayList(u8).init(f.object.dg.gpa);
-    defer buf.deinit();
+    var field_name_buf: []const u8 = "";
+    defer f.object.dg.gpa.free(field_name_buf);
     switch (struct_ty.tag()) {
         .@"struct" => {
             const fields = struct_ty.structFields();
@@ -3614,8 +3662,8 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc
             const tuple = struct_ty.tupleFields();
             if (tuple.values[index].tag() != .unreachable_value) return CValue.none;
 
-            try buf.writer().print("field_{d}", .{index});
-            field_name = buf.items;
+            field_name_buf = try std.fmt.allocPrint(f.object.dg.gpa, "field_{d}", .{index});
+            field_name = field_name_buf;
             field_val_ty = tuple.types[index];
         },
         else => unreachable,
@@ -3648,8 +3696,8 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
     const writer = f.object.writer();
     const struct_byval = try f.resolveInst(extra.struct_operand);
     const struct_ty = f.air.typeOf(extra.struct_operand);
-    var buf = std.ArrayList(u8).init(f.object.dg.gpa);
-    defer buf.deinit();
+    var field_name_buf: []const u8 = "";
+    defer f.object.dg.gpa.free(field_name_buf);
     const field_name = switch (struct_ty.tag()) {
         .@"struct" => struct_ty.structFields().keys()[extra.field_index],
         .@"union", .union_safety_tagged, .union_tagged => struct_ty.unionFields().keys()[extra.field_index],
@@ -3657,8 +3705,8 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
             const tuple = struct_ty.tupleFields();
             if (tuple.values[extra.field_index].tag() != .unreachable_value) return CValue.none;
 
-            try buf.writer().print("field_{d}", .{extra.field_index});
-            break :blk buf.items;
+            field_name_buf = try std.fmt.allocPrint(f.object.dg.gpa, "field_{d}", .{extra.field_index});
+            break :blk field_name_buf;
         },
         else => unreachable,
     };
@@ -4125,8 +4173,9 @@ fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {
     const layout = union_ty.unionGetLayout(target);
     if (layout.tag_size == 0) return CValue.none;
 
+    try writer.writeByte('(');
     try f.writeCValue(writer, union_ptr);
-    try writer.writeAll("->tag = ");
+    try writer.writeAll(")->tag = ");
     try f.writeCValue(writer, new_tag);
     try writer.writeAll(";\n");
 
src/link/C.zig
@@ -108,10 +108,8 @@ pub fn updateFunc(self: *C, module: *Module, func: *Module.Fn, air: Air, livenes
     const typedefs = &gop.value_ptr.typedefs;
     const code = &gop.value_ptr.code;
     fwd_decl.shrinkRetainingCapacity(0);
-    {
-        for (typedefs.values()) |value| {
-            module.gpa.free(value.rendered);
-        }
+    for (typedefs.values()) |typedef| {
+        module.gpa.free(typedef.rendered);
     }
     typedefs.clearRetainingCapacity();
     code.shrinkRetainingCapacity(0);
@@ -139,14 +137,14 @@ pub fn updateFunc(self: *C, module: *Module, func: *Module.Fn, air: Air, livenes
 
     function.object.indent_writer = .{ .underlying_writer = function.object.code.writer() };
     defer {
-        function.value_map.deinit();
         function.blocks.deinit(module.gpa);
+        function.value_map.deinit();
         function.object.code.deinit();
-        function.object.dg.fwd_decl.deinit();
-        for (function.object.dg.typedefs.values()) |value| {
-            module.gpa.free(value.rendered);
+        for (function.object.dg.typedefs.values()) |typedef| {
+            module.gpa.free(typedef.rendered);
         }
         function.object.dg.typedefs.deinit();
+        function.object.dg.fwd_decl.deinit();
     }
 
     codegen.genFunc(&function) catch |err| switch (err) {
@@ -179,10 +177,8 @@ pub fn updateDecl(self: *C, module: *Module, decl_index: Module.Decl.Index) !voi
     const typedefs = &gop.value_ptr.typedefs;
     const code = &gop.value_ptr.code;
     fwd_decl.shrinkRetainingCapacity(0);
-    {
-        for (typedefs.values()) |value| {
-            module.gpa.free(value.rendered);
-        }
+    for (typedefs.values()) |value| {
+        module.gpa.free(value.rendered);
     }
     typedefs.clearRetainingCapacity();
     code.shrinkRetainingCapacity(0);
@@ -206,11 +202,11 @@ 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.fwd_decl.deinit();
-        for (object.dg.typedefs.values()) |value| {
-            module.gpa.free(value.rendered);
+        for (object.dg.typedefs.values()) |typedef| {
+            module.gpa.free(typedef.rendered);
         }
         object.dg.typedefs.deinit();
+        object.dg.fwd_decl.deinit();
     }
 
     codegen.genDecl(&object) catch |err| switch (err) {
@@ -307,10 +303,10 @@ pub fn flushModule(self: *C, comp: *Compilation, prog_node: *std.Progress.Node)
 }
 
 const Flush = struct {
+    err_decls: DeclBlock = .{},
     remaining_decls: std.AutoArrayHashMapUnmanaged(Module.Decl.Index, void) = .{},
     typedefs: Typedefs = .{},
     typedef_buf: std.ArrayListUnmanaged(u8) = .{},
-    err_buf: std.ArrayListUnmanaged(u8) = .{},
     /// We collect a list of buffers to write, and write them all at once with pwritev ๐Ÿ˜Ž
     all_buffers: std.ArrayListUnmanaged(std.os.iovec_const) = .{},
     /// Keeps track of the total bytes of `all_buffers`.
@@ -332,10 +328,10 @@ const Flush = struct {
 
     fn deinit(f: *Flush, gpa: Allocator) void {
         f.all_buffers.deinit(gpa);
-        f.err_buf.deinit(gpa);
         f.typedef_buf.deinit(gpa);
         f.typedefs.deinit(gpa);
         f.remaining_decls.deinit(gpa);
+        f.err_decls.deinit(gpa);
     }
 };
 
@@ -365,6 +361,10 @@ fn flushTypedefs(self: *C, f: *Flush, typedefs: codegen.TypedefMap.Unmanaged) Fl
 fn flushErrDecls(self: *C, f: *Flush) FlushDeclError!void {
     const module = self.base.options.module.?;
 
+    const fwd_decl = &f.err_decls.fwd_decl;
+    const typedefs = &f.err_decls.typedefs;
+    const code = &f.err_decls.code;
+
     var object = codegen.Object{
         .dg = .{
             .gpa = module.gpa,
@@ -372,20 +372,21 @@ fn flushErrDecls(self: *C, f: *Flush) FlushDeclError!void {
             .error_msg = null,
             .decl_index = undefined,
             .decl = undefined,
-            .fwd_decl = undefined,
-            .typedefs = codegen.TypedefMap.initContext(module.gpa, .{ .mod = module }),
+            .fwd_decl = fwd_decl.toManaged(module.gpa),
+            .typedefs = typedefs.promoteContext(module.gpa, .{ .mod = module }),
             .typedefs_arena = self.arena.allocator(),
         },
-        .code = f.err_buf.toManaged(module.gpa),
+        .code = code.toManaged(module.gpa),
         .indent_writer = undefined, // set later so we can get a pointer to object.code
     };
     object.indent_writer = .{ .underlying_writer = object.code.writer() };
     defer {
-        f.err_buf = object.code.moveToUnmanaged();
-        for (object.dg.typedefs.values()) |value| {
-            module.gpa.free(value.rendered);
+        object.code.deinit();
+        for (object.dg.typedefs.values()) |typedef| {
+            module.gpa.free(typedef.rendered);
         }
         object.dg.typedefs.deinit();
+        object.dg.fwd_decl.deinit();
     }
 
     codegen.genErrDecls(&object) catch |err| switch (err) {
@@ -393,11 +394,15 @@ fn flushErrDecls(self: *C, f: *Flush) FlushDeclError!void {
         else => |e| return e,
     };
 
-    const gpa = self.base.allocator;
+    fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
+    typedefs.* = object.dg.typedefs.unmanaged;
+    object.dg.typedefs.unmanaged = .{};
+    code.* = object.code.moveToUnmanaged();
 
-    try self.flushTypedefs(f, object.dg.typedefs.unmanaged);
-    try f.all_buffers.ensureUnusedCapacity(gpa, 1);
-    f.appendBufAssumeCapacity(object.code.items);
+    try self.flushTypedefs(f, typedefs.*);
+    try f.all_buffers.ensureUnusedCapacity(self.base.allocator, 1);
+    f.appendBufAssumeCapacity(fwd_decl.items);
+    f.appendBufAssumeCapacity(code.items);
 }
 
 /// Assumes `decl` was in the `remaining_decls` set, and has already been removed.
src/Compilation.zig
@@ -3099,13 +3099,16 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
                         .decl_index = decl_index,
                         .decl = decl,
                         .fwd_decl = fwd_decl.toManaged(gpa),
-                        .typedefs = c_codegen.TypedefMap.initContext(gpa, .{
-                            .mod = module,
-                        }),
+                        .typedefs = c_codegen.TypedefMap.initContext(gpa, .{ .mod = module }),
                         .typedefs_arena = typedefs_arena.allocator(),
                     };
-                    defer dg.fwd_decl.deinit();
-                    defer dg.typedefs.deinit();
+                    defer {
+                        for (dg.typedefs.values()) |typedef| {
+                            module.gpa.free(typedef.rendered);
+                        }
+                        dg.typedefs.deinit();
+                        dg.fwd_decl.deinit();
+                    }
 
                     c_codegen.genHeader(&dg) catch |err| switch (err) {
                         error.AnalysisFail => {
test/behavior/bugs/1310.zig
@@ -24,6 +24,5 @@ fn agent_callback(_vm: [*]VM, options: [*]u8) callconv(.C) i32 {
 
 test "fixed" {
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
     try expect(agent_callback(undefined, undefined) == 11);
 }
test/behavior/bugs/1500.zig
@@ -6,7 +6,6 @@ const A = struct {
 const B = *const fn (A) void;
 
 test "allow these dependencies" {
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
     var a: A = undefined;
     var b: B = undefined;
     if (false) {
test/behavior/bugs/2006.zig
@@ -6,7 +6,6 @@ const S = struct {
     p: *S,
 };
 test "bug 2006" {
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
     var a: S = undefined;
     a = S{ .p = undefined };
     try expect(@sizeOf(S) != 0);
test/behavior/align.zig
@@ -476,7 +476,6 @@ test "read 128-bit field from default aligned struct in global memory" {
 }
 
 test "struct field explicit alignment" {
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
test/behavior/atomics.zig
@@ -33,7 +33,6 @@ fn testCmpxchg() !void {
 }
 
 test "fence" {
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
test/behavior/basic.zig
@@ -590,7 +590,6 @@ test "equality compare fn ptrs" {
 
 test "self reference through fn ptr field" {
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
 
     const S = struct {
         const A = struct {
test/behavior/cast.zig
@@ -410,7 +410,6 @@ fn testCastIntToErr(err: anyerror) !void {
 
 test "peer resolve array and const slice" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
 
@@ -546,7 +545,6 @@ fn testPeerErrorAndArray2(x: u8) anyerror![]const u8 {
 
 test "single-item pointer of array to slice to unknown length pointer" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
 
     try testCastPtrOfArrayToSliceAndPtr();
     comptime try testCastPtrOfArrayToSliceAndPtr();
@@ -575,7 +573,6 @@ fn testCastPtrOfArrayToSliceAndPtr() !void {
 
 test "cast *[1][*]const u8 to [*]const ?[*]const u8" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
 
     const window_name = [1][*]const u8{"window name"};
@@ -1235,7 +1232,6 @@ test "cast from array reference to fn: runtime fn ptr" {
 
 test "*const [N]null u8 to ?[]const u8" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
 
     const S = struct {
test/behavior/floatop.zig
@@ -21,7 +21,6 @@ fn epsForType(comptime T: type) T {
 
 test "floating point comparisons" {
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
 
     try testFloatComparisons();
@@ -91,7 +90,6 @@ fn testDifferentSizedFloatComparisons() !void {
 
 test "negative f128 floatToInt at compile-time" {
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
 
test/behavior/generics.zig
@@ -204,7 +204,6 @@ fn foo2(arg: anytype) bool {
 }
 
 test "generic struct" {
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
     var a1 = GenNode(i32){
         .value = 13,
         .next = null,
test/behavior/math.zig
@@ -603,7 +603,6 @@ fn should_not_be_zero(x: f128) !void {
 }
 
 test "128-bit multiplication" {
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
@@ -650,8 +649,6 @@ test "@addWithOverflow" {
 }
 
 test "small int addition" {
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
-
     var x: u2 = 0;
     try expect(x == 0);
 
test/behavior/maximum_minimum.zig
@@ -6,7 +6,6 @@ const expectEqual = std.testing.expectEqual;
 
 test "@max" {
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
 
@@ -55,7 +54,6 @@ test "@min" {
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
 
     const S = struct {
         fn doTheTest() !void {
test/behavior/muladd.zig
@@ -2,7 +2,6 @@ const builtin = @import("builtin");
 const expect = @import("std").testing.expect;
 
 test "@mulAdd" {
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
test/behavior/packed-struct.zig
@@ -293,7 +293,6 @@ test "regular in irregular packed struct" {
 test "byte-aligned field pointer offsets" {
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
test/behavior/saturating_arithmetic.zig
@@ -239,7 +239,6 @@ test "saturating shl uses the LHS type" {
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
 
     const lhs_const: u8 = 1;
     var lhs_var: u8 = 1;
test/behavior/struct.zig
@@ -284,7 +284,6 @@ const Val = struct {
 
 test "struct point to self" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
     var root: Node = undefined;
@@ -393,7 +392,6 @@ const APackedStruct = packed struct {
 test "packed struct" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
test/behavior/struct_contains_null_ptr_itself.zig
@@ -5,7 +5,6 @@ const builtin = @import("builtin");
 test "struct contains null pointer which contains original struct" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
     var x: ?*NodeLineComment = null;
     try expect(x == null);
 }
test/behavior/switch_prong_implicit_cast.zig
@@ -18,7 +18,6 @@ test "switch prong implicit cast" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
     const result = switch (foo(2) catch unreachable) {
         FormValue.One => false,
         FormValue.Two => |x| x,
test/behavior/union.zig
@@ -1186,7 +1186,6 @@ test "comptime equality of extern unions with same tag" {
 }
 
 test "union tag is set when initiated as a temporary value at runtime" {
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
 
@@ -1350,7 +1349,6 @@ test "@unionInit uses tag value instead of field index" {
 }
 
 test "union field ptr - zero sized payload" {
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
 
@@ -1364,7 +1362,6 @@ test "union field ptr - zero sized payload" {
 }
 
 test "union field ptr - zero sized field" {
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
 
test/behavior/vector.zig
@@ -811,7 +811,6 @@ test "vector reduce operation" {
 test "vector @reduce comptime" {
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO