Commit cf7200e8f9

Jacob Young <jacobly0@users.noreply.github.com>
2023-02-19 05:03:11
CBE: remove typedef data structures
Adds a new mechanism for `@tagName` function generation that doesn't piggyback on the removed typedef system.
1 parent 3eed197
Changed files (4)
src/codegen/c/type.zig
@@ -290,11 +290,11 @@ pub const CType = extern union {
             }
         };
 
-        const Promoted = struct {
+        pub const Promoted = struct {
             arena: std.heap.ArenaAllocator,
             set: Set,
 
-            fn gpa(self: *Promoted) Allocator {
+            pub fn gpa(self: *Promoted) Allocator {
                 return self.arena.child_allocator;
             }
 
@@ -345,11 +345,11 @@ pub const CType = extern union {
             }
         };
 
-        fn promote(self: Store, gpa: Allocator) Promoted {
+        pub fn promote(self: Store, gpa: Allocator) Promoted {
             return .{ .arena = self.arena.promote(gpa), .set = self.set };
         }
 
-        fn demote(self: *Store, promoted: Promoted) void {
+        pub fn demote(self: *Store, promoted: Promoted) void {
             self.arena = promoted.arena.state;
             self.set = promoted.set;
         }
@@ -382,17 +382,17 @@ pub const CType = extern union {
             _ = promoted.arena.reset(.retain_capacity);
         }
 
-        pub fn shrinkToFit(self: *Store, gpa: Allocator) void {
-            self.map.shrinkAndFree(gpa, self.map.entries.len);
-        }
-
-        pub fn shrinkAndFree(self: *Store, gpa: Allocator) void {
+        pub fn clearAndFree(self: *Store, gpa: Allocator) void {
             var promoted = self.promote(gpa);
             defer self.demote(promoted);
             promoted.set.map.clearAndFree(gpa);
             _ = promoted.arena.reset(.free_all);
         }
 
+        pub fn shrinkToFit(self: *Store, gpa: Allocator) void {
+            self.set.map.shrinkAndFree(gpa, self.set.map.count());
+        }
+
         pub fn move(self: *Store) Store {
             const moved = self.*;
             self.* = .{};
@@ -1252,8 +1252,8 @@ pub const CType = extern union {
     pub const HashContext64 = struct {
         store: *const Store.Set,
 
-        pub fn hash(_: @This(), cty: CType) u64 {
-            return cty.hash();
+        pub fn hash(self: @This(), cty: CType) u64 {
+            return cty.hash(self.store.*);
         }
         pub fn eql(_: @This(), lhs: CType, rhs: CType) bool {
             return lhs.eql(rhs);
src/codegen/c.zig
@@ -23,7 +23,7 @@ const libcFloatSuffix = target_util.libcFloatSuffix;
 const compilerRtFloatAbbrev = target_util.compilerRtFloatAbbrev;
 const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
 
-const Mutability = enum { Const, ConstArgument, Mut };
+const Mutability = enum { @"const", mut };
 const BigIntLimb = std.math.big.Limb;
 const BigInt = std.math.big.int;
 
@@ -63,12 +63,17 @@ const TypedefKind = enum {
 };
 
 pub const CValueMap = std.AutoHashMap(Air.Inst.Ref, CValue);
-pub const TypedefMap = std.ArrayHashMap(
-    Type,
-    struct { name: []const u8, rendered: []u8 },
-    Type.HashContext32,
-    true,
-);
+
+pub const LazyFnKey = union(enum) {
+    tag_name: Decl.Index,
+};
+pub const LazyFnValue = struct {
+    fn_name: []const u8,
+    data: union {
+        tag_name: Type,
+    },
+};
+pub const LazyFnMap = std.AutoArrayHashMapUnmanaged(LazyFnKey, LazyFnValue);
 
 const LoopDepth = u16;
 const Local = struct {
@@ -83,11 +88,6 @@ const LocalsList = std.ArrayListUnmanaged(LocalIndex);
 const LocalsMap = std.ArrayHashMapUnmanaged(Type, LocalsList, Type.HashContext32, true);
 const LocalsStack = std.ArrayListUnmanaged(LocalsMap);
 
-const FormatTypeAsCIdentContext = struct {
-    ty: Type,
-    mod: *Module,
-};
-
 const ValueRenderLocation = enum {
     FunctionArgument,
     Initializer,
@@ -108,26 +108,6 @@ const BuiltinInfo = enum {
     Bits,
 };
 
-fn formatTypeAsCIdentifier(
-    data: FormatTypeAsCIdentContext,
-    comptime fmt: []const u8,
-    options: std.fmt.FormatOptions,
-    writer: anytype,
-) !void {
-    var stack = std.heap.stackFallback(128, data.mod.gpa);
-    const allocator = stack.get();
-    const str = std.fmt.allocPrint(allocator, "{}", .{data.ty.fmt(data.mod)}) catch "";
-    defer allocator.free(str);
-    return formatIdent(str, fmt, options, writer);
-}
-
-pub fn typeToCIdentifier(ty: Type, mod: *Module) std.fmt.Formatter(formatTypeAsCIdentifier) {
-    return .{ .data = .{
-        .ty = ty,
-        .mod = mod,
-    } };
-}
-
 const reserved_idents = std.ComptimeStringMap(void, .{
     // C language
     .{ "alignas", {
@@ -283,6 +263,7 @@ pub const Function = struct {
     next_arg_index: usize = 0,
     next_block_index: usize = 0,
     object: Object,
+    lazy_fns: LazyFnMap,
     func: *Module.Fn,
     /// All the locals, to be emitted at the top of the function.
     locals: std.ArrayListUnmanaged(Local) = .{},
@@ -319,7 +300,7 @@ pub const Function = struct {
             const gpa = f.object.dg.gpa;
             try f.allocs.put(gpa, decl_c_value.local, true);
             try writer.writeAll("static ");
-            try f.object.dg.renderTypeAndName(writer, ty, decl_c_value, .Const, alignment, .Complete);
+            try f.object.dg.renderTypeAndName(writer, ty, decl_c_value, .@"const", alignment, .Complete);
             try writer.writeAll(" = ");
             try f.object.dg.renderValue(writer, ty, val, .StaticInitializer);
             try writer.writeAll(";\n ");
@@ -353,7 +334,7 @@ pub const Function = struct {
     }
 
     fn allocLocal(f: *Function, inst: Air.Inst.Index, ty: Type) !CValue {
-        const result = try f.allocAlignedLocal(ty, .Mut, 0);
+        const result = try f.allocAlignedLocal(ty, .mut, 0);
         log.debug("%{d}: allocating t{d}", .{ inst, result.local });
         return result;
     }
@@ -448,6 +429,29 @@ pub const Function = struct {
         return f.object.dg.fmtIntLiteral(ty, val);
     }
 
+    fn getTagNameFn(f: *Function, enum_ty: Type) ![]const u8 {
+        const gpa = f.object.dg.gpa;
+        const owner_decl = enum_ty.getOwnerDecl();
+
+        const gop = try f.lazy_fns.getOrPut(gpa, .{ .tag_name = owner_decl });
+        if (!gop.found_existing) {
+            errdefer _ = f.lazy_fns.pop();
+
+            var promoted = f.object.dg.ctypes.promote(gpa);
+            defer f.object.dg.ctypes.demote(promoted);
+            const arena = promoted.arena.allocator();
+
+            gop.value_ptr.* = .{
+                .fn_name = try std.fmt.allocPrint(arena, "zig_tagName_{}__{d}", .{
+                    fmtIdent(mem.span(f.object.dg.module.declPtr(owner_decl).name)),
+                    @enumToInt(owner_decl),
+                }),
+                .data = .{ .tag_name = try enum_ty.copy(arena) },
+            };
+        }
+        return gop.value_ptr.fn_name;
+    }
+
     pub fn deinit(f: *Function) void {
         const gpa = f.object.dg.gpa;
         f.allocs.deinit(gpa);
@@ -458,11 +462,8 @@ pub const Function = struct {
         f.free_locals_stack.deinit(gpa);
         f.blocks.deinit(gpa);
         f.value_map.deinit();
+        f.lazy_fns.deinit(gpa);
         f.object.code.deinit();
-        for (f.object.dg.typedefs.values()) |typedef| {
-            gpa.free(typedef.rendered);
-        }
-        f.object.dg.typedefs.deinit();
         f.object.dg.ctypes.deinit(gpa);
         f.object.dg.fwd_decl.deinit();
         f.arena.deinit();
@@ -492,9 +493,6 @@ pub const DeclGen = struct {
     fwd_decl: std.ArrayList(u8),
     error_msg: ?*Module.ErrorMsg,
     ctypes: CType.Store,
-    /// The key of this map is Type which has references to typedefs_arena.
-    typedefs: TypedefMap,
-    typedefs_arena: std.mem.Allocator,
 
     fn fail(dg: *DeclGen, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
         @setCold(true);
@@ -504,14 +502,6 @@ pub const DeclGen = struct {
         return error.AnalysisFail;
     }
 
-    fn getTypedefName(dg: *DeclGen, t: Type) ?[]const u8 {
-        if (dg.typedefs.get(t)) |typedef| {
-            return typedef.name;
-        } else {
-            return null;
-        }
-    }
-
     fn renderDeclValue(
         dg: *DeclGen,
         writer: anytype,
@@ -1493,7 +1483,7 @@ pub const DeclGen = struct {
             if (!param_type.hasRuntimeBitsIgnoreComptime()) continue;
             if (index > 0) try w.writeAll(", ");
             const name = CValue{ .arg = index };
-            try dg.renderTypeAndName(w, param_type, name, .ConstArgument, 0, kind);
+            try dg.renderTypeAndName(w, param_type, name, .@"const", 0, kind);
             index += 1;
         }
 
@@ -1507,453 +1497,6 @@ pub const DeclGen = struct {
         if (fn_info.alignment > 0 and kind == .Forward) try w.print(" zig_align_fn({})", .{fn_info.alignment});
     }
 
-    fn renderPtrToFnTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
-        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-        defer buffer.deinit();
-        const bw = buffer.writer();
-
-        const fn_info = t.fnInfo();
-
-        const target = dg.module.getTarget();
-        var ret_buf: LowerFnRetTyBuffer = undefined;
-        const ret_ty = lowerFnRetTy(fn_info.return_type, &ret_buf, target);
-
-        try bw.writeAll("typedef ");
-        try dg.renderType(bw, ret_ty, .Forward);
-        try bw.writeAll(" (*");
-        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;
-
-        var params_written: usize = 0;
-        var index: usize = 0;
-        while (index < param_len) : (index += 1) {
-            const param_ty = fn_info.param_types[index];
-            if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue;
-            if (params_written > 0) {
-                try bw.writeAll(", ");
-            }
-            try dg.renderTypeAndName(bw, param_ty, .{ .bytes = "" }, .Mut, 0, .Forward);
-            params_written += 1;
-        }
-
-        if (fn_info.is_var_args) {
-            if (params_written != 0) try bw.writeAll(", ");
-            try bw.writeAll("...");
-        } else if (params_written == 0) {
-            try dg.renderType(bw, Type.void, .Forward);
-        }
-        try bw.writeAll(");\n");
-
-        const rendered = try buffer.toOwnedSlice();
-        errdefer dg.typedefs.allocator.free(rendered);
-        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 },
-        );
-
-        return name;
-    }
-
-    fn renderSliceTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
-        std.debug.assert(t.sentinel() == null); // expected canonical type
-
-        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-        defer buffer.deinit();
-        const bw = buffer.writer();
-
-        var ptr_ty_buf: Type.SlicePtrFieldTypeBuffer = undefined;
-        const ptr_ty = t.slicePtrFieldType(&ptr_ty_buf);
-        const ptr_name = CValue{ .identifier = "ptr" };
-        const len_ty = Type.usize;
-        const len_name = CValue{ .identifier = "len" };
-
-        try bw.writeAll("typedef struct {\n ");
-        try dg.renderTypeAndName(bw, ptr_ty, ptr_name, .Mut, 0, .Complete);
-        try bw.writeAll(";\n ");
-        try dg.renderTypeAndName(bw, len_ty, len_name, .Mut, 0, .Complete);
-
-        try bw.writeAll(";\n} ");
-        const name_begin = buffer.items.len;
-        try bw.print("zig_{c}_{}", .{
-            @as(u8, if (t.isConstPtr()) 'L' else 'M'),
-            typeToCIdentifier(t.childType(), dg.module),
-        });
-        const name_end = buffer.items.len;
-        try bw.writeAll(";\n");
-
-        const rendered = try buffer.toOwnedSlice();
-        errdefer dg.typedefs.allocator.free(rendered);
-        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 },
-        );
-
-        return name;
-    }
-
-    fn renderFwdTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
-        // The forward declaration for T is stored with a key of *const T.
-        const child_ty = t.childType();
-
-        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-        defer buffer.deinit();
-        const bw = buffer.writer();
-
-        const tag = switch (child_ty.zigTypeTag()) {
-            .Struct, .ErrorUnion, .Optional => "struct",
-            .Union => if (child_ty.unionTagTypeSafety()) |_| "struct" else "union",
-            else => unreachable,
-        };
-        try bw.writeAll("typedef ");
-        try bw.writeAll(tag);
-        const name_begin = buffer.items.len + " ".len;
-        try bw.writeAll(" zig_");
-        switch (child_ty.zigTypeTag()) {
-            .Struct, .Union => {
-                var fqn_buf = std.ArrayList(u8).init(dg.typedefs.allocator);
-                defer fqn_buf.deinit();
-
-                const owner_decl_index = child_ty.getOwnerDecl();
-                const owner_decl = dg.module.declPtr(owner_decl_index);
-                try owner_decl.renderFullyQualifiedName(dg.module, fqn_buf.writer());
-
-                try bw.print("S_{}__{d}", .{ fmtIdent(fqn_buf.items), @enumToInt(owner_decl_index) });
-            },
-            .ErrorUnion => {
-                try bw.print("E_{}", .{typeToCIdentifier(child_ty.errorUnionPayload(), dg.module)});
-            },
-            .Optional => {
-                var opt_buf: Type.Payload.ElemType = undefined;
-                try bw.print("Q_{}", .{typeToCIdentifier(child_ty.optionalChild(&opt_buf), dg.module)});
-            },
-            else => unreachable,
-        }
-        const name_end = buffer.items.len;
-        try buffer.ensureUnusedCapacity(" ".len + (name_end - name_begin) + ";\n".len);
-        buffer.appendAssumeCapacity(' ');
-        buffer.appendSliceAssumeCapacity(buffer.items[name_begin..name_end]);
-        buffer.appendSliceAssumeCapacity(";\n");
-
-        const rendered = try buffer.toOwnedSlice();
-        errdefer dg.typedefs.allocator.free(rendered);
-        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 },
-        );
-
-        return name;
-    }
-
-    fn renderStructTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
-        var ptr_pl = Type.Payload.ElemType{ .base = .{ .tag = .single_const_pointer }, .data = t };
-        const ptr_ty = Type.initPayload(&ptr_pl.base);
-        const name = dg.getTypedefName(ptr_ty) orelse
-            try dg.renderFwdTypedef(ptr_ty);
-
-        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-        defer buffer.deinit();
-
-        try buffer.appendSlice("struct ");
-
-        var needs_pack_attr = false;
-        {
-            var it = t.structFields().iterator();
-            while (it.next()) |field| {
-                const field_ty = field.value_ptr.ty;
-                if (!field_ty.hasRuntimeBits()) continue;
-                const alignment = field.value_ptr.abi_align;
-                if (alignment != 0 and alignment < field_ty.abiAlignment(dg.module.getTarget())) {
-                    needs_pack_attr = true;
-                    try buffer.appendSlice("zig_packed(");
-                    break;
-                }
-            }
-        }
-
-        try buffer.appendSlice(name);
-        try buffer.appendSlice(" {\n");
-        {
-            var it = t.structFields().iterator();
-            var empty = true;
-            while (it.next()) |field| {
-                const field_ty = field.value_ptr.ty;
-                if (!field_ty.hasRuntimeBits()) continue;
-
-                const alignment = field.value_ptr.alignment(dg.module.getTarget(), t.containerLayout());
-                const field_name = CValue{ .identifier = field.key_ptr.* };
-                try buffer.append(' ');
-                try dg.renderTypeAndName(buffer.writer(), field_ty, field_name, .Mut, alignment, .Complete);
-                try buffer.appendSlice(";\n");
-
-                empty = false;
-            }
-            if (empty) try buffer.appendSlice(" char empty_struct;\n");
-        }
-        if (needs_pack_attr) try buffer.appendSlice("});\n") else try buffer.appendSlice("};\n");
-
-        const rendered = try buffer.toOwnedSlice();
-        errdefer dg.typedefs.allocator.free(rendered);
-
-        try dg.typedefs.ensureUnusedCapacity(1);
-        dg.typedefs.putAssumeCapacityNoClobber(
-            try t.copy(dg.typedefs_arena),
-            .{ .name = name, .rendered = rendered },
-        );
-
-        return name;
-    }
-
-    fn renderTupleTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
-        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-        defer buffer.deinit();
-
-        try buffer.appendSlice("typedef struct {\n");
-        {
-            const fields = t.tupleFields();
-            var field_id: usize = 0;
-            for (fields.types, 0..) |field_ty, i| {
-                if (!field_ty.hasRuntimeBits() or fields.values[i].tag() != .unreachable_value) continue;
-
-                try buffer.append(' ');
-                try dg.renderTypeAndName(buffer.writer(), field_ty, .{ .field = field_id }, .Mut, 0, .Complete);
-                try buffer.appendSlice(";\n");
-
-                field_id += 1;
-            }
-            if (field_id == 0) try buffer.appendSlice(" char empty_tuple;\n");
-        }
-        const name_begin = buffer.items.len + "} ".len;
-        try buffer.writer().print("}} zig_T_{}_{d};\n", .{ typeToCIdentifier(t, dg.module), @truncate(u16, t.hash(dg.module)) });
-        const name_end = buffer.items.len - ";\n".len;
-
-        const rendered = try buffer.toOwnedSlice();
-        errdefer dg.typedefs.allocator.free(rendered);
-        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 },
-        );
-
-        return name;
-    }
-
-    fn renderUnionTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
-        var ptr_pl = Type.Payload.ElemType{ .base = .{ .tag = .single_const_pointer }, .data = t };
-        const ptr_ty = Type.initPayload(&ptr_pl.base);
-        const name = dg.getTypedefName(ptr_ty) orelse
-            try dg.renderFwdTypedef(ptr_ty);
-
-        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-        defer buffer.deinit();
-
-        try buffer.appendSlice(if (t.unionTagTypeSafety()) |_| "struct " else "union ");
-        try buffer.appendSlice(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 buffer.append(' ');
-                try dg.renderTypeAndName(buffer.writer(), tag_ty, .{ .identifier = "tag" }, .Mut, 0, .Complete);
-                try buffer.appendSlice(";\n");
-            }
-            try buffer.appendSlice(" union {\n");
-            break :indent "  ";
-        } else " ";
-
-        {
-            var it = t.unionFields().iterator();
-            var empty = true;
-            while (it.next()) |field| {
-                const field_ty = field.value_ptr.ty;
-                if (!field_ty.hasRuntimeBits()) continue;
-
-                const alignment = field.value_ptr.abi_align;
-                const field_name = CValue{ .identifier = field.key_ptr.* };
-                try buffer.appendSlice(indent);
-                try dg.renderTypeAndName(buffer.writer(), field_ty, field_name, .Mut, alignment, .Complete);
-                try buffer.appendSlice(";\n");
-
-                empty = false;
-            }
-            if (empty) {
-                try buffer.appendSlice(indent);
-                try buffer.appendSlice("char empty_union;\n");
-            }
-        }
-
-        if (t.unionTagTypeSafety()) |_| try buffer.appendSlice(" } payload;\n");
-        try buffer.appendSlice("};\n");
-
-        const rendered = try buffer.toOwnedSlice();
-        errdefer dg.typedefs.allocator.free(rendered);
-
-        try dg.typedefs.ensureUnusedCapacity(1);
-        dg.typedefs.putAssumeCapacityNoClobber(
-            try t.copy(dg.typedefs_arena),
-            .{ .name = name, .rendered = rendered },
-        );
-
-        return name;
-    }
-
-    fn renderErrorUnionTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
-        assert(t.errorUnionSet().tag() == .anyerror);
-
-        var ptr_pl = Type.Payload.ElemType{ .base = .{ .tag = .single_const_pointer }, .data = t };
-        const ptr_ty = Type.initPayload(&ptr_pl.base);
-        const name = dg.getTypedefName(ptr_ty) orelse
-            try dg.renderFwdTypedef(ptr_ty);
-
-        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-        defer buffer.deinit();
-        const bw = buffer.writer();
-
-        const payload_ty = t.errorUnionPayload();
-        const payload_name = CValue{ .identifier = "payload" };
-        const error_ty = t.errorUnionSet();
-        const error_name = CValue{ .identifier = "error" };
-
-        const target = dg.module.getTarget();
-        const payload_align = payload_ty.abiAlignment(target);
-        const error_align = error_ty.abiAlignment(target);
-        try bw.writeAll("struct ");
-        try bw.writeAll(name);
-        try bw.writeAll(" {\n ");
-        if (error_align > payload_align) {
-            try dg.renderTypeAndName(bw, payload_ty, payload_name, .Mut, 0, .Complete);
-            try bw.writeAll(";\n ");
-            try dg.renderTypeAndName(bw, error_ty, error_name, .Mut, 0, .Complete);
-        } else {
-            try dg.renderTypeAndName(bw, error_ty, error_name, .Mut, 0, .Complete);
-            try bw.writeAll(";\n ");
-            try dg.renderTypeAndName(bw, payload_ty, payload_name, .Mut, 0, .Complete);
-        }
-        try bw.writeAll(";\n};\n");
-
-        const rendered = try buffer.toOwnedSlice();
-        errdefer dg.typedefs.allocator.free(rendered);
-
-        try dg.typedefs.ensureUnusedCapacity(1);
-        dg.typedefs.putAssumeCapacityNoClobber(
-            try t.copy(dg.typedefs_arena),
-            .{ .name = name, .rendered = rendered },
-        );
-
-        return name;
-    }
-
-    fn renderArrayTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
-        const info = t.arrayInfo();
-        std.debug.assert(info.sentinel == null); // expected canonical type
-
-        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-        defer buffer.deinit();
-        const bw = buffer.writer();
-
-        try bw.writeAll("typedef ");
-        try dg.renderType(bw, info.elem_type, .Complete);
-
-        const name_begin = buffer.items.len + " ".len;
-        try bw.print(" zig_A_{}_{d}", .{ typeToCIdentifier(info.elem_type, dg.module), info.len });
-        const name_end = buffer.items.len;
-
-        const c_len = if (info.len > 0) info.len else 1;
-        var c_len_pl: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = c_len };
-        const c_len_val = Value.initPayload(&c_len_pl.base);
-        try bw.print("[{}];\n", .{try dg.fmtIntLiteral(Type.usize, c_len_val)});
-
-        const rendered = try buffer.toOwnedSlice();
-        errdefer dg.typedefs.allocator.free(rendered);
-        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 },
-        );
-
-        return name;
-    }
-
-    fn renderOptionalTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
-        var ptr_pl = Type.Payload.ElemType{ .base = .{ .tag = .single_const_pointer }, .data = t };
-        const ptr_ty = Type.initPayload(&ptr_pl.base);
-        const name = dg.getTypedefName(ptr_ty) orelse
-            try dg.renderFwdTypedef(ptr_ty);
-
-        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-        defer buffer.deinit();
-        const bw = buffer.writer();
-
-        var opt_buf: Type.Payload.ElemType = undefined;
-        const child_ty = t.optionalChild(&opt_buf);
-
-        try bw.writeAll("struct ");
-        try bw.writeAll(name);
-        try bw.writeAll(" {\n");
-        try dg.renderTypeAndName(bw, child_ty, .{ .identifier = "payload" }, .Mut, 0, .Complete);
-        try bw.writeAll(";\n ");
-        try dg.renderTypeAndName(bw, Type.bool, .{ .identifier = "is_null" }, .Mut, 0, .Complete);
-        try bw.writeAll(";\n};\n");
-
-        const rendered = try buffer.toOwnedSlice();
-        errdefer dg.typedefs.allocator.free(rendered);
-
-        try dg.typedefs.ensureUnusedCapacity(1);
-        dg.typedefs.putAssumeCapacityNoClobber(
-            try t.copy(dg.typedefs_arena),
-            .{ .name = name, .rendered = rendered },
-        );
-
-        return name;
-    }
-
-    fn renderOpaqueTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
-        const opaque_ty = t.cast(Type.Payload.Opaque).?.data;
-        const unqualified_name = dg.module.declPtr(opaque_ty.owner_decl).name;
-        const fqn = try opaque_ty.getFullyQualifiedName(dg.module);
-        defer dg.typedefs.allocator.free(fqn);
-
-        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-        defer buffer.deinit();
-
-        try buffer.writer().print("typedef struct { } ", .{fmtIdent(std.mem.span(unqualified_name))});
-
-        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 = try buffer.toOwnedSlice();
-        errdefer dg.typedefs.allocator.free(rendered);
-        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 },
-        );
-
-        return name;
-    }
-
     fn indexToCType(dg: *DeclGen, idx: CType.Index) CType {
         return dg.ctypes.indexToCType(idx);
     }
@@ -2408,31 +1951,27 @@ pub const DeclGen = struct {
         const idx = try dg.typeToIndex(ty);
         try w.print("{}", .{try dg.renderTypePrefix(w, idx, .suffix, CQualifiers.init(.{
             .@"const" = switch (mutability) {
-                .Const, .ConstArgument => true,
-                .Mut => false,
+                .mut => false,
+                .@"const" => true,
             },
         }))});
         try dg.writeCValue(w, name);
         try dg.renderTypeSuffix(w, idx, .suffix);
     }
 
-    fn renderTagNameFn(dg: *DeclGen, enum_ty: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
-        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-        defer buffer.deinit();
-        const bw = buffer.writer();
-
+    fn renderTagNameFn(dg: *DeclGen, w: anytype, fn_name: []const u8, enum_ty: Type) !void {
         const name_slice_ty = Type.initTag(.const_slice_u8_sentinel_0);
 
-        try buffer.appendSlice("static ");
-        try dg.renderType(bw, name_slice_ty, .Complete);
-        const name_begin = buffer.items.len + " ".len;
-        try bw.print(" zig_tagName_{}_{d}(", .{ typeToCIdentifier(enum_ty, dg.module), @enumToInt(enum_ty.getOwnerDecl()) });
-        const name_end = buffer.items.len - "(".len;
-        try dg.renderTypeAndName(bw, enum_ty, .{ .identifier = "tag" }, .Const, 0, .Complete);
-        try buffer.appendSlice(") {\n switch (tag) {\n");
+        try w.writeAll("static ");
+        try dg.renderType(w, name_slice_ty, .Complete);
+        try w.writeByte(' ');
+        try w.writeAll(fn_name);
+        try w.writeByte('(');
+        try dg.renderTypeAndName(w, enum_ty, .{ .identifier = "tag" }, .@"const", 0, .Complete);
+        try w.writeAll(") {\n switch (tag) {\n");
         for (enum_ty.enumFields().keys(), 0..) |name, index| {
-            const name_z = try dg.typedefs.allocator.dupeZ(u8, name);
-            defer dg.typedefs.allocator.free(name_z);
+            const name_z = try dg.gpa.dupeZ(u8, name);
+            defer dg.gpa.free(name_z);
             const name_bytes = name_z[0 .. name_z.len + 1];
 
             var tag_pl: Value.Payload.U32 = .{
@@ -2453,40 +1992,23 @@ pub const DeclGen = struct {
             var len_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = name.len };
             const len_val = Value.initPayload(&len_pl.base);
 
-            try bw.print("  case {}: {{\n   static ", .{try dg.fmtIntLiteral(enum_ty, int_val)});
-            try dg.renderTypeAndName(bw, name_ty, .{ .identifier = "name" }, .Const, 0, .Complete);
-            try buffer.appendSlice(" = ");
-            try dg.renderValue(bw, name_ty, name_val, .Initializer);
-            try buffer.appendSlice(";\n   return (");
-            try dg.renderTypecast(bw, name_slice_ty);
-            try bw.print("){{{}, {}}};\n", .{
+            try w.print("  case {}: {{\n   static ", .{try dg.fmtIntLiteral(enum_ty, int_val)});
+            try dg.renderTypeAndName(w, name_ty, .{ .identifier = "name" }, .@"const", 0, .Complete);
+            try w.writeAll(" = ");
+            try dg.renderValue(w, name_ty, name_val, .Initializer);
+            try w.writeAll(";\n   return (");
+            try dg.renderTypecast(w, name_slice_ty);
+            try w.print("){{{}, {}}};\n", .{
                 fmtIdent("name"), try dg.fmtIntLiteral(Type.usize, len_val),
             });
 
-            try buffer.appendSlice("  }\n");
+            try w.writeAll("  }\n");
         }
-        try buffer.appendSlice(" }\n while (");
-        try dg.renderValue(bw, Type.bool, Value.true, .Other);
-        try buffer.appendSlice(") ");
-        _ = try airBreakpoint(bw);
-        try buffer.appendSlice("}\n");
-
-        const rendered = try buffer.toOwnedSlice();
-        errdefer dg.typedefs.allocator.free(rendered);
-        const name = rendered[name_begin..name_end];
-
-        try dg.typedefs.ensureUnusedCapacity(1);
-        dg.typedefs.putAssumeCapacityNoClobber(
-            try enum_ty.copy(dg.typedefs_arena),
-            .{ .name = name, .rendered = rendered },
-        );
-
-        return name;
-    }
-
-    fn getTagNameFn(dg: *DeclGen, enum_ty: Type) ![]const u8 {
-        return dg.getTypedefName(enum_ty) orelse
-            try dg.renderTagNameFn(enum_ty);
+        try w.writeAll(" }\n while (");
+        try dg.renderValue(w, Type.bool, Value.true, .Other);
+        try w.writeAll(") ");
+        _ = try airBreakpoint(w);
+        try w.writeAll("}\n");
     }
 
     fn declIsGlobal(dg: *DeclGen, tv: TypedValue) bool {
@@ -2724,7 +2246,7 @@ pub fn genErrDecls(o: *Object) !void {
         const name_val = Value.initPayload(&name_pl.base);
 
         try writer.writeAll("static ");
-        try o.dg.renderTypeAndName(writer, name_ty, .{ .identifier = identifier }, .Const, 0, .Complete);
+        try o.dg.renderTypeAndName(writer, name_ty, .{ .identifier = identifier }, .@"const", 0, .Complete);
         try writer.writeAll(" = ");
         try o.dg.renderValue(writer, name_ty, name_val, .StaticInitializer);
         try writer.writeAll(";\n");
@@ -2737,7 +2259,7 @@ pub fn genErrDecls(o: *Object) !void {
     const name_array_ty = Type.initPayload(&name_array_ty_pl.base);
 
     try writer.writeAll("static ");
-    try o.dg.renderTypeAndName(writer, name_array_ty, .{ .identifier = name_prefix }, .Const, 0, .Complete);
+    try o.dg.renderTypeAndName(writer, name_array_ty, .{ .identifier = name_prefix }, .@"const", 0, .Complete);
     try writer.writeAll(" = {");
     for (o.dg.module.error_name_list.items, 0..) |name, value| {
         if (value != 0) try writer.writeByte(',');
@@ -2767,6 +2289,17 @@ fn genExports(o: *Object) !void {
     };
 }
 
+pub fn genLazyFn(o: *Object, lazy_fn: LazyFnMap.Entry) !void {
+    const writer = o.writer();
+    switch (lazy_fn.key_ptr.*) {
+        .tag_name => _ = try o.dg.renderTagNameFn(
+            writer,
+            lazy_fn.value_ptr.fn_name,
+            lazy_fn.value_ptr.data.tag_name,
+        ),
+    }
+}
+
 pub fn genFunc(f: *Function) !void {
     const tracy = trace(@src());
     defer tracy.end();
@@ -2845,7 +2378,7 @@ pub fn genFunc(f: *Function) !void {
                 w,
                 local.ty,
                 .{ .local = local_index },
-                .Mut,
+                .mut,
                 local.alignment,
                 .Complete,
             );
@@ -2886,7 +2419,7 @@ pub fn genDecl(o: *Object) !void {
 
         try fwd_decl_writer.writeAll(if (is_global) "zig_extern " else "static ");
         if (variable.is_threadlocal) try fwd_decl_writer.writeAll("zig_threadlocal ");
-        try o.dg.renderTypeAndName(fwd_decl_writer, o.dg.decl.ty, decl_c_value, .Mut, o.dg.decl.@"align", .Complete);
+        try o.dg.renderTypeAndName(fwd_decl_writer, o.dg.decl.ty, decl_c_value, .mut, o.dg.decl.@"align", .Complete);
         try fwd_decl_writer.writeAll(";\n");
         try genExports(o);
 
@@ -2896,7 +2429,7 @@ pub fn genDecl(o: *Object) !void {
         if (!is_global) try w.writeAll("static ");
         if (variable.is_threadlocal) try w.writeAll("zig_threadlocal ");
         if (o.dg.decl.@"linksection") |section| try w.print("zig_linksection(\"{s}\", ", .{section});
-        try o.dg.renderTypeAndName(w, o.dg.decl.ty, decl_c_value, .Mut, o.dg.decl.@"align", .Complete);
+        try o.dg.renderTypeAndName(w, o.dg.decl.ty, decl_c_value, .mut, o.dg.decl.@"align", .Complete);
         if (o.dg.decl.@"linksection" != null) try w.writeAll(", read, write)");
         try w.writeAll(" = ");
         try o.dg.renderValue(w, tv.ty, variable.init, .StaticInitializer);
@@ -2908,13 +2441,13 @@ pub fn genDecl(o: *Object) !void {
         const decl_c_value: CValue = .{ .decl = o.dg.decl_index };
 
         try fwd_decl_writer.writeAll(if (is_global) "zig_extern " else "static ");
-        try o.dg.renderTypeAndName(fwd_decl_writer, tv.ty, decl_c_value, .Const, o.dg.decl.@"align", .Complete);
+        try o.dg.renderTypeAndName(fwd_decl_writer, tv.ty, decl_c_value, .@"const", o.dg.decl.@"align", .Complete);
         try fwd_decl_writer.writeAll(";\n");
 
         const w = o.writer();
         if (!is_global) try w.writeAll("static ");
         if (o.dg.decl.@"linksection") |section| try w.print("zig_linksection(\"{s}\", ", .{section});
-        try o.dg.renderTypeAndName(w, tv.ty, decl_c_value, .Const, o.dg.decl.@"align", .Complete);
+        try o.dg.renderTypeAndName(w, tv.ty, decl_c_value, .@"const", o.dg.decl.@"align", .Complete);
         if (o.dg.decl.@"linksection" != null) try w.writeAll(", read)");
         try w.writeAll(" = ");
         try o.dg.renderValue(w, tv.ty, tv.val, .StaticInitializer);
@@ -3443,7 +2976,7 @@ fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue {
         return CValue{ .undef = inst_ty };
     }
 
-    const mutability: Mutability = if (inst_ty.isConstPtr()) .Const else .Mut;
+    const mutability: Mutability = if (inst_ty.isConstPtr()) .@"const" else .mut;
     const target = f.object.dg.module.getTarget();
     const local = try f.allocAlignedLocal(elem_type, mutability, inst_ty.ptrAlignment(target));
     log.debug("%{d}: allocated unfreeable t{d}", .{ inst, local.local });
@@ -3460,7 +2993,7 @@ fn airRetPtr(f: *Function, inst: Air.Inst.Index) !CValue {
         return CValue{ .undef = inst_ty };
     }
 
-    const mutability: Mutability = if (inst_ty.isConstPtr()) .Const else .Mut;
+    const mutability: Mutability = if (inst_ty.isConstPtr()) .@"const" else .mut;
     const target = f.object.dg.module.getTarget();
     const local = try f.allocAlignedLocal(elem_ty, mutability, inst_ty.ptrAlignment(target));
     log.debug("%{d}: allocated unfreeable t{d}", .{ inst, local.local });
@@ -4937,7 +4470,7 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
                     writer,
                     output_ty,
                     local_value,
-                    .Mut,
+                    .mut,
                     alignment,
                     .Complete,
                 );
@@ -4976,7 +4509,7 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
                     writer,
                     input_ty,
                     local_value,
-                    .Const,
+                    .@"const",
                     alignment,
                     .Complete,
                 );
@@ -6474,7 +6007,7 @@ fn airTagName(f: *Function, inst: Air.Inst.Index) !CValue {
     const writer = f.object.writer();
     const local = try f.allocLocal(inst, inst_ty);
     try f.writeCValue(writer, local, .Other);
-    try writer.print(" = {s}(", .{try f.object.dg.getTagNameFn(enum_ty)});
+    try writer.print(" = {s}(", .{try f.getTagNameFn(enum_ty)});
     try f.writeCValue(writer, operand, .Other);
     try writer.writeAll(");\n");
 
src/link/C.zig
@@ -22,26 +22,19 @@ 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) = .{},
-/// Stores Type/Value data for `typedefs` to reference.
-/// Accumulates allocations and then there is a periodic garbage collection after flush().
-arena: std.heap.ArenaAllocator,
 
 /// Per-declaration data.
 const DeclBlock = struct {
     code: std.ArrayListUnmanaged(u8) = .{},
     fwd_decl: std.ArrayListUnmanaged(u8) = .{},
+    /// 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 = .{},
-    /// Each Decl stores a mapping of Zig Types to corresponding C types, for every
-    /// Zig Type used by the Decl. In flush(), we iterate over each Decl
-    /// and emit the typedef code for all types, making sure to not emit the same thing twice.
-    /// Any arena memory the Type points to lives in the `arena` field of `C`.
-    typedefs: codegen.TypedefMap.Unmanaged = .{},
+    /// Key and Value storage use the ctype arena.
+    lazy_fns: codegen.LazyFnMap = .{},
 
     fn deinit(db: *DeclBlock, gpa: Allocator) void {
-        for (db.typedefs.values()) |typedef| {
-            gpa.free(typedef.rendered);
-        }
-        db.typedefs.deinit(gpa);
+        db.lazy_fns.deinit(gpa);
         db.ctypes.deinit(gpa);
         db.fwd_decl.deinit(gpa);
         db.code.deinit(gpa);
@@ -66,7 +59,6 @@ pub fn openPath(gpa: Allocator, sub_path: []const u8, options: link.Options) !*C
     errdefer gpa.destroy(c_file);
 
     c_file.* = C{
-        .arena = std.heap.ArenaAllocator.init(gpa),
         .base = .{
             .tag = .c,
             .options = options,
@@ -85,8 +77,6 @@ pub fn deinit(self: *C) void {
         db.deinit(gpa);
     }
     self.decl_table.deinit(gpa);
-
-    self.arena.deinit();
 }
 
 pub fn freeDecl(self: *C, decl_index: Module.Decl.Index) void {
@@ -101,44 +91,42 @@ pub fn updateFunc(self: *C, module: *Module, func: *Module.Fn, air: Air, livenes
     const tracy = trace(@src());
     defer tracy.end();
 
+    const gpa = self.base.allocator;
+
     const decl_index = func.owner_decl;
-    const gop = try self.decl_table.getOrPut(self.base.allocator, decl_index);
+    const gop = try self.decl_table.getOrPut(gpa, decl_index);
     if (!gop.found_existing) {
         gop.value_ptr.* = .{};
     }
-    const fwd_decl = &gop.value_ptr.fwd_decl;
     const ctypes = &gop.value_ptr.ctypes;
-    const typedefs = &gop.value_ptr.typedefs;
+    const lazy_fns = &gop.value_ptr.lazy_fns;
+    const fwd_decl = &gop.value_ptr.fwd_decl;
     const code = &gop.value_ptr.code;
+    ctypes.clearRetainingCapacity(gpa);
+    lazy_fns.clearRetainingCapacity();
     fwd_decl.shrinkRetainingCapacity(0);
-    ctypes.clearRetainingCapacity(module.gpa);
-    for (typedefs.values()) |typedef| {
-        module.gpa.free(typedef.rendered);
-    }
-    typedefs.clearRetainingCapacity();
     code.shrinkRetainingCapacity(0);
 
     var function: codegen.Function = .{
-        .value_map = codegen.CValueMap.init(module.gpa),
+        .value_map = codegen.CValueMap.init(gpa),
         .air = air,
         .liveness = liveness,
         .func = func,
         .object = .{
             .dg = .{
-                .gpa = module.gpa,
+                .gpa = gpa,
                 .module = module,
                 .error_msg = null,
                 .decl_index = decl_index,
                 .decl = module.declPtr(decl_index),
-                .fwd_decl = fwd_decl.toManaged(module.gpa),
+                .fwd_decl = fwd_decl.toManaged(gpa),
                 .ctypes = ctypes.*,
-                .typedefs = typedefs.promoteContext(module.gpa, .{ .mod = module }),
-                .typedefs_arena = self.arena.allocator(),
             },
-            .code = code.toManaged(module.gpa),
+            .code = code.toManaged(gpa),
             .indent_writer = undefined, // set later so we can get a pointer to object.code
         },
-        .arena = std.heap.ArenaAllocator.init(module.gpa),
+        .lazy_fns = lazy_fns.*,
+        .arena = std.heap.ArenaAllocator.init(gpa),
     };
 
     function.object.indent_writer = .{ .underlying_writer = function.object.code.writer() };
@@ -146,91 +134,79 @@ pub fn updateFunc(self: *C, module: *Module, func: *Module.Fn, air: Air, livenes
 
     codegen.genFunc(&function) catch |err| switch (err) {
         error.AnalysisFail => {
-            try module.failed_decls.put(module.gpa, decl_index, function.object.dg.error_msg.?);
+            try module.failed_decls.put(gpa, decl_index, function.object.dg.error_msg.?);
             return;
         },
         else => |e| return e,
     };
 
-    fwd_decl.* = function.object.dg.fwd_decl.moveToUnmanaged();
     ctypes.* = function.object.dg.ctypes.move();
-    typedefs.* = function.object.dg.typedefs.unmanaged;
-    function.object.dg.typedefs.unmanaged = .{};
+    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.
-    fwd_decl.shrinkAndFree(module.gpa, fwd_decl.items.len);
-    code.shrinkAndFree(module.gpa, code.items.len);
-    ctypes.shrinkAndFree(module.gpa);
+    ctypes.shrinkToFit(gpa);
+    lazy_fns.shrinkAndFree(gpa, lazy_fns.count());
+    fwd_decl.shrinkAndFree(gpa, fwd_decl.items.len);
+    code.shrinkAndFree(gpa, code.items.len);
 }
 
 pub fn updateDecl(self: *C, module: *Module, decl_index: Module.Decl.Index) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const gop = try self.decl_table.getOrPut(self.base.allocator, decl_index);
+    const gpa = self.base.allocator;
+
+    const gop = try self.decl_table.getOrPut(gpa, decl_index);
     if (!gop.found_existing) {
         gop.value_ptr.* = .{};
     }
-    const fwd_decl = &gop.value_ptr.fwd_decl;
     const ctypes = &gop.value_ptr.ctypes;
-    const typedefs = &gop.value_ptr.typedefs;
+    const fwd_decl = &gop.value_ptr.fwd_decl;
     const code = &gop.value_ptr.code;
+    ctypes.clearRetainingCapacity(gpa);
     fwd_decl.shrinkRetainingCapacity(0);
-    ctypes.clearRetainingCapacity(module.gpa);
-    for (typedefs.values()) |value| {
-        module.gpa.free(value.rendered);
-    }
-    typedefs.clearRetainingCapacity();
     code.shrinkRetainingCapacity(0);
 
     const decl = module.declPtr(decl_index);
 
     var object: codegen.Object = .{
         .dg = .{
-            .gpa = module.gpa,
+            .gpa = gpa,
             .module = module,
             .error_msg = null,
             .decl_index = decl_index,
             .decl = decl,
-            .fwd_decl = fwd_decl.toManaged(module.gpa),
+            .fwd_decl = fwd_decl.toManaged(gpa),
             .ctypes = ctypes.*,
-            .typedefs = typedefs.promoteContext(module.gpa, .{ .mod = module }),
-            .typedefs_arena = self.arena.allocator(),
         },
-        .code = code.toManaged(module.gpa),
+        .code = code.toManaged(gpa),
         .indent_writer = undefined, // set later so we can get a pointer to object.code
     };
     object.indent_writer = .{ .underlying_writer = object.code.writer() };
     defer {
         object.code.deinit();
-        for (object.dg.typedefs.values()) |typedef| {
-            module.gpa.free(typedef.rendered);
-        }
-        object.dg.typedefs.deinit();
         object.dg.ctypes.deinit(object.dg.gpa);
         object.dg.fwd_decl.deinit();
     }
 
     codegen.genDecl(&object) catch |err| switch (err) {
         error.AnalysisFail => {
-            try module.failed_decls.put(module.gpa, decl_index, object.dg.error_msg.?);
+            try module.failed_decls.put(gpa, decl_index, object.dg.error_msg.?);
             return;
         },
         else => |e| return e,
     };
 
+    ctypes.* = object.dg.ctypes.move();
     fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
-    ctypes.* = object.dg.ctypes;
-    object.dg.ctypes = .{};
-    typedefs.* = object.dg.typedefs.unmanaged;
-    object.dg.typedefs.unmanaged = .{};
     code.* = object.code.moveToUnmanaged();
 
     // Free excess allocated memory for this Decl.
-    fwd_decl.shrinkAndFree(module.gpa, fwd_decl.items.len);
-    code.shrinkAndFree(module.gpa, code.items.len);
-    ctypes.shrinkAndFree(module.gpa);
+    ctypes.shrinkToFit(gpa);
+    fwd_decl.shrinkAndFree(gpa, fwd_decl.items.len);
+    code.shrinkAndFree(gpa, code.items.len);
 }
 
 pub fn updateDeclLineNumber(self: *C, module: *Module, decl_index: Module.Decl.Index) !void {
@@ -260,7 +236,7 @@ pub fn flushModule(self: *C, comp: *Compilation, prog_node: *std.Progress.Node)
     sub_prog_node.activate();
     defer sub_prog_node.end();
 
-    const gpa = comp.gpa;
+    const gpa = self.base.allocator;
     const module = self.base.options.module.?;
 
     // This code path happens exclusively with -ofmt=c. The flush logic for
@@ -271,19 +247,17 @@ pub fn flushModule(self: *C, comp: *Compilation, prog_node: *std.Progress.Node)
 
     const abi_define = abiDefine(comp);
 
-    // Covers defines, zig.h, typedef, and asm.
-    var buf_count: usize = 2;
-    if (abi_define != null) buf_count += 1;
-    try f.all_buffers.ensureUnusedCapacity(gpa, buf_count);
+    // Covers defines, zig.h, ctypes, asm.
+    try f.all_buffers.ensureUnusedCapacity(gpa, 4);
 
     if (abi_define) |buf| f.appendBufAssumeCapacity(buf);
     f.appendBufAssumeCapacity(zig_h);
 
-    const typedef_index = f.all_buffers.items.len;
+    const ctypes_index = f.all_buffers.items.len;
     f.all_buffers.items.len += 1;
 
     {
-        var asm_buf = f.asm_buf.toManaged(module.gpa);
+        var asm_buf = f.asm_buf.toManaged(gpa);
         defer asm_buf.deinit();
 
         try codegen.genGlobalAsm(module, &asm_buf);
@@ -294,7 +268,7 @@ pub fn flushModule(self: *C, comp: *Compilation, prog_node: *std.Progress.Node)
 
     try self.flushErrDecls(&f);
 
-    // Typedefs, forward decls, and non-functions first.
+    // `CType`s, forward decls, and non-functions first.
     // Unlike other backends, the .c code we are emitting is order-dependent. Therefore
     // we must traverse the set of Decls that we are emitting according to their dependencies.
     // Our strategy is to populate a set of remaining decls, pop Decls one by one,
@@ -321,11 +295,11 @@ pub fn flushModule(self: *C, comp: *Compilation, prog_node: *std.Progress.Node)
         }
     }
 
-    f.all_buffers.items[typedef_index] = .{
-        .iov_base = if (f.typedef_buf.items.len > 0) f.typedef_buf.items.ptr else "",
-        .iov_len = f.typedef_buf.items.len,
+    f.all_buffers.items[ctypes_index] = .{
+        .iov_base = if (f.ctypes_buf.items.len > 0) f.ctypes_buf.items.ptr else "",
+        .iov_len = f.ctypes_buf.items.len,
     };
-    f.file_size += f.typedef_buf.items.len;
+    f.file_size += f.ctypes_buf.items.len;
 
     // Now the code.
     try f.all_buffers.ensureUnusedCapacity(gpa, decl_values.len);
@@ -338,31 +312,23 @@ 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) = .{},
 
-    ctypes: CTypes = .{},
-    typedefs: Typedefs = .{},
-    typedef_buf: std.ArrayListUnmanaged(u8) = .{},
+    ctypes: codegen.CType.Store = .{},
+    ctypes_map: std.ArrayListUnmanaged(codegen.CType.Index) = .{},
+    ctypes_buf: std.ArrayListUnmanaged(u8) = .{},
+
+    err_decls: DeclBlock = .{},
+
+    lazy_fns: LazyFns = .{},
+
     asm_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`.
     file_size: u64 = 0,
 
-    const CTypes = std.ArrayHashMapUnmanaged(
-        codegen.CType,
-        void,
-        codegen.CType.HashContext32,
-        true,
-    );
-
-    const Typedefs = std.HashMapUnmanaged(
-        Type,
-        void,
-        Type.HashContext64,
-        std.hash_map.default_max_load_percentage,
-    );
+    const LazyFns = std.AutoHashMapUnmanaged(codegen.LazyFnKey, DeclBlock);
 
     fn appendBufAssumeCapacity(f: *Flush, buf: []const u8) void {
         if (buf.len == 0) return;
@@ -372,11 +338,14 @@ const Flush = struct {
 
     fn deinit(f: *Flush, gpa: Allocator) void {
         f.all_buffers.deinit(gpa);
-        f.typedef_buf.deinit(gpa);
-        f.typedefs.deinit(gpa);
+        var lazy_fns_it = f.lazy_fns.valueIterator();
+        while (lazy_fns_it.next()) |db| db.deinit(gpa);
+        f.lazy_fns.deinit(gpa);
+        f.err_decls.deinit(gpa);
+        f.ctypes_buf.deinit(gpa);
+        f.ctypes_map.deinit(gpa);
         f.ctypes.deinit(gpa);
         f.remaining_decls.deinit(gpa);
-        f.err_decls.deinit(gpa);
     }
 };
 
@@ -384,56 +353,36 @@ const FlushDeclError = error{
     OutOfMemory,
 };
 
-fn flushTypedefs(self: *C, f: *Flush, typedefs: codegen.TypedefMap.Unmanaged) FlushDeclError!void {
-    if (typedefs.count() == 0) return;
-    const gpa = self.base.allocator;
-    const module = self.base.options.module.?;
-
-    try f.typedefs.ensureUnusedCapacityContext(gpa, @intCast(u32, typedefs.count()), .{
-        .mod = module,
-    });
-    var it = typedefs.iterator();
-    while (it.next()) |new| {
-        const gop = f.typedefs.getOrPutAssumeCapacityContext(new.key_ptr.*, .{
-            .mod = module,
-        });
-        if (!gop.found_existing) {
-            try f.typedef_buf.appendSlice(gpa, new.value_ptr.rendered);
-        }
-    }
+fn flushCTypes(self: *C, f: *Flush, ctypes: codegen.CType.Store) FlushDeclError!void {
+    _ = self;
+    _ = f;
+    _ = ctypes;
 }
 
 fn flushErrDecls(self: *C, f: *Flush) FlushDeclError!void {
-    const module = self.base.options.module.?;
+    const gpa = self.base.allocator;
 
     const fwd_decl = &f.err_decls.fwd_decl;
     const ctypes = &f.err_decls.ctypes;
-    const typedefs = &f.err_decls.typedefs;
     const code = &f.err_decls.code;
 
     var object = codegen.Object{
         .dg = .{
-            .gpa = module.gpa,
-            .module = module,
+            .gpa = gpa,
+            .module = self.base.options.module.?,
             .error_msg = null,
             .decl_index = undefined,
             .decl = undefined,
-            .fwd_decl = fwd_decl.toManaged(module.gpa),
+            .fwd_decl = fwd_decl.toManaged(gpa),
             .ctypes = ctypes.*,
-            .typedefs = typedefs.promoteContext(module.gpa, .{ .mod = module }),
-            .typedefs_arena = self.arena.allocator(),
         },
-        .code = code.toManaged(module.gpa),
+        .code = code.toManaged(gpa),
         .indent_writer = undefined, // set later so we can get a pointer to object.code
     };
     object.indent_writer = .{ .underlying_writer = object.code.writer() };
     defer {
         object.code.deinit();
-        object.dg.ctypes.deinit(module.gpa);
-        for (object.dg.typedefs.values()) |typedef| {
-            module.gpa.free(typedef.rendered);
-        }
-        object.dg.typedefs.deinit();
+        object.dg.ctypes.deinit(gpa);
         object.dg.fwd_decl.deinit();
     }
 
@@ -443,16 +392,75 @@ fn flushErrDecls(self: *C, f: *Flush) FlushDeclError!void {
     };
 
     fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
-    typedefs.* = object.dg.typedefs.unmanaged;
-    object.dg.typedefs.unmanaged = .{};
+    ctypes.* = object.dg.ctypes.move();
+    code.* = object.code.moveToUnmanaged();
+
+    try self.flushCTypes(f, ctypes.*);
+    try f.all_buffers.ensureUnusedCapacity(gpa, 2);
+    f.appendBufAssumeCapacity(fwd_decl.items);
+    f.appendBufAssumeCapacity(code.items);
+}
+
+fn flushLazyFn(
+    self: *C,
+    f: *Flush,
+    db: *DeclBlock,
+    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;
+
+    var object = codegen.Object{
+        .dg = .{
+            .gpa = gpa,
+            .module = self.base.options.module.?,
+            .error_msg = null,
+            .decl_index = undefined,
+            .decl = undefined,
+            .fwd_decl = fwd_decl.toManaged(gpa),
+            .ctypes = ctypes.*,
+        },
+        .code = code.toManaged(gpa),
+        .indent_writer = undefined, // set later so we can get a pointer to object.code
+    };
+    object.indent_writer = .{ .underlying_writer = object.code.writer() };
+    defer {
+        object.code.deinit();
+        object.dg.ctypes.deinit(gpa);
+        object.dg.fwd_decl.deinit();
+    }
+
+    codegen.genLazyFn(&object, lazy_fn) catch |err| switch (err) {
+        error.AnalysisFail => unreachable,
+        else => |e| return e,
+    };
+
+    fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
+    ctypes.* = object.dg.ctypes.move();
     code.* = object.code.moveToUnmanaged();
 
-    try self.flushTypedefs(f, typedefs.*);
-    try f.all_buffers.ensureUnusedCapacity(self.base.allocator, 1);
+    try self.flushCTypes(f, ctypes.*);
+    try f.all_buffers.ensureUnusedCapacity(gpa, 2);
     f.appendBufAssumeCapacity(fwd_decl.items);
     f.appendBufAssumeCapacity(code.items);
 }
 
+fn flushLazyFns(self: *C, f: *Flush, lazy_fns: codegen.LazyFnMap) FlushDeclError!void {
+    const gpa = self.base.allocator;
+    try f.lazy_fns.ensureUnusedCapacity(gpa, @intCast(Flush.LazyFns.Size, 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, gop.value_ptr, entry);
+    }
+}
+
 /// Assumes `decl` was in the `remaining_decls` set, and has already been removed.
 fn flushDecl(
     self: *C,
@@ -460,8 +468,8 @@ fn flushDecl(
     decl_index: Module.Decl.Index,
     export_names: std.StringHashMapUnmanaged(void),
 ) FlushDeclError!void {
-    const module = self.base.options.module.?;
-    const decl = module.declPtr(decl_index);
+    const gpa = self.base.allocator;
+    const decl = self.base.options.module.?.declPtr(decl_index);
     // Before flushing any particular Decl we must ensure its
     // dependencies are already flushed, so that the order in the .c
     // file comes out correctly.
@@ -472,10 +480,10 @@ fn flushDecl(
     }
 
     const decl_block = self.decl_table.getPtr(decl_index).?;
-    const gpa = self.base.allocator;
 
-    try self.flushTypedefs(f, decl_block.typedefs);
-    try f.all_buffers.ensureUnusedCapacity(gpa, 2);
+    try self.flushCTypes(f, decl_block.ctypes);
+    try self.flushLazyFns(f, decl_block.lazy_fns);
+    try f.all_buffers.ensureUnusedCapacity(gpa, 1);
     if (!(decl.isExtern() and export_names.contains(mem.span(decl.name))))
         f.appendBufAssumeCapacity(decl_block.fwd_decl.items);
 }
src/Compilation.zig
@@ -3277,14 +3277,9 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
                         .decl = decl,
                         .fwd_decl = fwd_decl.toManaged(gpa),
                         .ctypes = .{},
-                        .typedefs = c_codegen.TypedefMap.initContext(gpa, .{ .mod = module }),
-                        .typedefs_arena = ctypes_arena.allocator(),
                     };
                     defer {
-                        for (dg.typedefs.values()) |typedef| {
-                            module.gpa.free(typedef.rendered);
-                        }
-                        dg.typedefs.deinit();
+                        dg.ctypes.deinit(gpa);
                         dg.fwd_decl.deinit();
                     }