Commit 5913140b6b

Andrew Kelley <andrew@ziglang.org>
2021-09-22 00:08:32
stage2: free Sema's arena after generating machine code
Previously, linker backends or machine code backends were able to hold on to references to inside Sema's temporary arena. However there can be large objects stored there that we want to free after machine code is generated. The primary change in this commit is to use a temporary arena for Sema of function bodies that gets freed after machine code backend finishes handling `updateFunc` (at the same time that Air and Liveness get freed). The other changes in this commit are fixing issues that fell out from the primary change. * The C linker backend is rewritten to handle updateDecl and updateFunc separately. Also, all Decl updates get access to typedefs and fwd_decls, not only functions. * The C linker backend is updated to the new API that does not depend on allocateDeclIndexes and does not have to handle garbage collected decls. * The C linker backend uses an arena for Type/Value objects that `typedefs` references. These can be garbage collected every so often after flush(), however that garbage collection code is not implemented at this time. It will be pretty simple, just allocate a new arena, copy all the Type objects to it, update the keys of the hash map, free the old arena. * Sema: fix a handful of instances of not copying Type/Value objects from the temporary arena into the appropriate Decl arena. * Type: fix some function types not reporting hasCodeGenBits() correctly.
1 parent affd8f8
src/codegen/c.zig
@@ -91,55 +91,76 @@ pub fn fmtIdent(ident: []const u8) std.fmt.Formatter(formatIdent) {
     return .{ .data = ident };
 }
 
-/// This data is available when outputting .c code for a Module.
+/// This data is available when outputting .c code for a `*Module.Fn`.
 /// It is not available when generating .h file.
-pub const Object = struct {
-    dg: DeclGen,
+pub const Function = struct {
     air: Air,
     liveness: Liveness,
-    gpa: *mem.Allocator,
-    code: std.ArrayList(u8),
     value_map: CValueMap,
     blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .{},
     next_arg_index: usize = 0,
     next_local_index: usize = 0,
     next_block_index: usize = 0,
-    indent_writer: IndentWriter(std.ArrayList(u8).Writer),
+    object: Object,
+    func: *Module.Fn,
 
-    fn resolveInst(o: *Object, inst: Air.Inst.Ref) !CValue {
-        if (o.air.value(inst)) |_| {
+    fn resolveInst(f: *Function, inst: Air.Inst.Ref) !CValue {
+        if (f.air.value(inst)) |_| {
             return CValue{ .constant = inst };
         }
         const index = Air.refToIndex(inst).?;
-        return o.value_map.get(index).?; // Assertion means instruction does not dominate usage.
+        return f.value_map.get(index).?; // Assertion means instruction does not dominate usage.
     }
 
-    fn allocLocalValue(o: *Object) CValue {
-        const result = o.next_local_index;
-        o.next_local_index += 1;
+    fn allocLocalValue(f: *Function) CValue {
+        const result = f.next_local_index;
+        f.next_local_index += 1;
         return .{ .local = result };
     }
 
-    fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue {
-        const local_value = o.allocLocalValue();
-        try o.renderTypeAndName(o.writer(), ty, local_value, mutability);
+    fn allocLocal(f: *Function, ty: Type, mutability: Mutability) !CValue {
+        const local_value = f.allocLocalValue();
+        try f.object.renderTypeAndName(f.object.writer(), ty, local_value, mutability);
         return local_value;
     }
 
+    fn writeCValue(f: *Function, w: anytype, c_value: CValue) !void {
+        switch (c_value) {
+            .constant => |inst| {
+                const ty = f.air.typeOf(inst);
+                const val = f.air.value(inst).?;
+                return f.object.dg.renderValue(w, ty, val);
+            },
+            else => return Object.writeCValue(w, c_value),
+        }
+    }
+
+    fn fail(f: *Function, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
+        return f.object.dg.fail(format, args);
+    }
+
+    fn renderType(f: *Function, w: anytype, t: Type) !void {
+        return f.object.dg.renderType(w, t);
+    }
+};
+
+/// This data is available when outputting .c code for a `Module`.
+/// It is not available when generating .h file.
+pub const Object = struct {
+    dg: DeclGen,
+    code: std.ArrayList(u8),
+    indent_writer: IndentWriter(std.ArrayList(u8).Writer),
+
     fn writer(o: *Object) IndentWriter(std.ArrayList(u8).Writer).Writer {
         return o.indent_writer.writer();
     }
 
-    fn writeCValue(o: *Object, w: anytype, c_value: CValue) !void {
+    fn writeCValue(w: anytype, c_value: CValue) !void {
         switch (c_value) {
             .none => unreachable,
             .local => |i| return w.print("t{d}", .{i}),
             .local_ref => |i| return w.print("&t{d}", .{i}),
-            .constant => |inst| {
-                const ty = o.air.typeOf(inst);
-                const val = o.air.value(inst).?;
-                return o.dg.renderValue(w, ty, val);
-            },
+            .constant => unreachable,
             .arg => |i| return w.print("a{d}", .{i}),
             .decl => |decl| return w.writeAll(mem.span(decl.name)),
             .decl_ref => |decl| return w.print("&{s}", .{decl.name}),
@@ -153,7 +174,7 @@ pub const Object = struct {
         name: CValue,
         mutability: Mutability,
     ) error{ OutOfMemory, AnalysisFail }!void {
-        var suffix = std.ArrayList(u8).init(o.gpa);
+        var suffix = std.ArrayList(u8).init(o.dg.gpa);
         defer suffix.deinit();
 
         var render_ty = ty;
@@ -177,7 +198,7 @@ pub const Object = struct {
                 .Const => try w.writeAll("const "),
                 .Mut => {},
             }
-            try o.writeCValue(w, name);
+            try writeCValue(w, name);
             try w.writeAll(")(");
             const param_len = render_ty.fnParamLen();
             const is_var_args = render_ty.fnIsVarArgs();
@@ -205,7 +226,7 @@ pub const Object = struct {
                 .Mut => "",
             };
             try w.print(" {s}", .{const_prefix});
-            try o.writeCValue(w, name);
+            try writeCValue(w, name);
         }
         try w.writeAll(suffix.items);
     }
@@ -213,11 +234,14 @@ pub const Object = struct {
 
 /// This data is available both when outputting .c code and when outputting an .h file.
 pub const DeclGen = struct {
+    gpa: *std.mem.Allocator,
     module: *Module,
     decl: *Decl,
     fwd_decl: std.ArrayList(u8),
     error_msg: ?*Module.ErrorMsg,
+    /// 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);
@@ -545,7 +569,10 @@ pub const DeclGen = struct {
 
                     try dg.typedefs.ensureUnusedCapacity(1);
                     try w.writeAll(name);
-                    dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
+                    dg.typedefs.putAssumeCapacityNoClobber(
+                        try t.copy(dg.typedefs_arena),
+                        .{ .name = name, .rendered = rendered },
+                    );
                 } else {
                     try dg.renderType(w, t.elemType());
                     try w.writeAll(" *");
@@ -586,7 +613,10 @@ pub const DeclGen = struct {
 
                 try dg.typedefs.ensureUnusedCapacity(1);
                 try w.writeAll(name);
-                dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
+                dg.typedefs.putAssumeCapacityNoClobber(
+                    try t.copy(dg.typedefs_arena),
+                    .{ .name = name, .rendered = rendered },
+                );
             },
             .ErrorSet => {
                 comptime std.debug.assert(Type.initTag(.anyerror).abiSize(std.Target.current) == 2);
@@ -626,7 +656,10 @@ pub const DeclGen = struct {
 
                 try dg.typedefs.ensureUnusedCapacity(1);
                 try w.writeAll(name);
-                dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
+                dg.typedefs.putAssumeCapacityNoClobber(
+                    try t.copy(dg.typedefs_arena),
+                    .{ .name = name, .rendered = rendered },
+                );
             },
             .Struct => {
                 if (dg.typedefs.get(t)) |some| {
@@ -659,7 +692,10 @@ pub const DeclGen = struct {
 
                 try dg.typedefs.ensureUnusedCapacity(1);
                 try w.writeAll(name);
-                dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
+                dg.typedefs.putAssumeCapacityNoClobber(
+                    try t.copy(dg.typedefs_arena),
+                    .{ .name = name, .rendered = rendered },
+                );
             },
             .Enum => {
                 // For enums, we simply use the integer tag type.
@@ -724,6 +760,29 @@ pub const DeclGen = struct {
     }
 };
 
+pub fn genFunc(f: *Function) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const o = &f.object;
+    const is_global = o.dg.module.decl_exports.contains(f.func.owner_decl);
+    const fwd_decl_writer = o.dg.fwd_decl.writer();
+    if (is_global) {
+        try fwd_decl_writer.writeAll("ZIG_EXTERN_C ");
+    }
+    try o.dg.renderFunctionSignature(fwd_decl_writer, is_global);
+    try fwd_decl_writer.writeAll(";\n");
+
+    try o.indent_writer.insertNewline();
+    try o.dg.renderFunctionSignature(o.writer(), is_global);
+
+    try o.writer().writeByte(' ');
+    const main_body = f.air.getMainBody();
+    try genBody(f, main_body);
+
+    try o.indent_writer.insertNewline();
+}
+
 pub fn genDecl(o: *Object) !void {
     const tracy = trace(@src());
     defer tracy.end();
@@ -732,28 +791,6 @@ pub fn genDecl(o: *Object) !void {
         .ty = o.dg.decl.ty,
         .val = o.dg.decl.val,
     };
-    if (tv.val.castTag(.function)) |func_payload| {
-        const func: *Module.Fn = func_payload.data;
-        if (func.owner_decl == o.dg.decl) {
-            const is_global = o.dg.declIsGlobal(tv);
-            const fwd_decl_writer = o.dg.fwd_decl.writer();
-            if (is_global) {
-                try fwd_decl_writer.writeAll("ZIG_EXTERN_C ");
-            }
-            try o.dg.renderFunctionSignature(fwd_decl_writer, is_global);
-            try fwd_decl_writer.writeAll(";\n");
-
-            try o.indent_writer.insertNewline();
-            try o.dg.renderFunctionSignature(o.writer(), is_global);
-
-            try o.writer().writeByte(' ');
-            const main_body = o.air.getMainBody();
-            try genBody(o, main_body);
-
-            try o.indent_writer.insertNewline();
-            return;
-        }
-    }
     if (tv.val.tag() == .extern_fn) {
         const writer = o.writer();
         try writer.writeAll("ZIG_EXTERN_C ");
@@ -821,250 +858,250 @@ pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void {
     }
 }
 
-fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfMemory }!void {
-    const writer = o.writer();
+fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfMemory }!void {
+    const writer = f.object.writer();
     if (body.len == 0) {
         try writer.writeAll("{}");
         return;
     }
 
     try writer.writeAll("{\n");
-    o.indent_writer.pushIndent();
+    f.object.indent_writer.pushIndent();
 
-    const air_tags = o.air.instructions.items(.tag);
+    const air_tags = f.air.instructions.items(.tag);
 
     for (body) |inst| {
         const result_value = switch (air_tags[inst]) {
             // zig fmt: off
             .constant => unreachable, // excluded from function bodies
             .const_ty => unreachable, // excluded from function bodies
-            .arg      => airArg(o),
+            .arg      => airArg(f),
 
-            .breakpoint => try airBreakpoint(o),
-            .unreach    => try airUnreach(o),
-            .fence      => try airFence(o, inst),
+            .breakpoint => try airBreakpoint(f),
+            .unreach    => try airUnreach(f),
+            .fence      => try airFence(f, inst),
 
             // TODO use a different strategy for add that communicates to the optimizer
             // that wrapping is UB.
-            .add, .ptr_add => try airBinOp( o, inst, " + "),
-            .addwrap       => try airWrapOp(o, inst, " + ", "addw_"),
+            .add, .ptr_add => try airBinOp( f, inst, " + "),
+            .addwrap       => try airWrapOp(f, inst, " + ", "addw_"),
             // TODO use a different strategy for sub that communicates to the optimizer
             // that wrapping is UB.
-            .sub, .ptr_sub => try airBinOp( o, inst, " - "),
-            .subwrap       => try airWrapOp(o, inst, " - ", "subw_"),
+            .sub, .ptr_sub => try airBinOp( f, inst, " - "),
+            .subwrap       => try airWrapOp(f, inst, " - ", "subw_"),
             // TODO use a different strategy for mul that communicates to the optimizer
             // that wrapping is UB.
-            .mul           => try airBinOp( o, inst, " * "),
-            .mulwrap       => try airWrapOp(o, inst, " * ", "mulw_"),
+            .mul           => try airBinOp( f, inst, " * "),
+            .mulwrap       => try airWrapOp(f, inst, " * ", "mulw_"),
             // TODO use a different strategy for div that communicates to the optimizer
             // that wrapping is UB.
-            .div           => try airBinOp( o, inst, " / "),
-            .rem           => try airBinOp( o, inst, " % "),
+            .div           => try airBinOp( f, inst, " / "),
+            .rem           => try airBinOp( f, inst, " % "),
 
-            .cmp_eq  => try airBinOp(o, inst, " == "),
-            .cmp_gt  => try airBinOp(o, inst, " > "),
-            .cmp_gte => try airBinOp(o, inst, " >= "),
-            .cmp_lt  => try airBinOp(o, inst, " < "),
-            .cmp_lte => try airBinOp(o, inst, " <= "),
-            .cmp_neq => try airBinOp(o, inst, " != "),
+            .cmp_eq  => try airBinOp(f, inst, " == "),
+            .cmp_gt  => try airBinOp(f, inst, " > "),
+            .cmp_gte => try airBinOp(f, inst, " >= "),
+            .cmp_lt  => try airBinOp(f, inst, " < "),
+            .cmp_lte => try airBinOp(f, inst, " <= "),
+            .cmp_neq => try airBinOp(f, inst, " != "),
 
             // bool_and and bool_or are non-short-circuit operations
-            .bool_and   => try airBinOp(o, inst, " & "),
-            .bool_or    => try airBinOp(o, inst, " | "),
-            .bit_and    => try airBinOp(o, inst, " & "),
-            .bit_or     => try airBinOp(o, inst, " | "),
-            .xor        => try airBinOp(o, inst, " ^ "),
-
-            .shr        => try airBinOp(o, inst, " >> "),
-            .shl        => try airBinOp(o, inst, " << "),
-
-            .not        => try airNot(  o, inst),
-
-            .optional_payload     => try airOptionalPayload(o, inst),
-            .optional_payload_ptr => try airOptionalPayload(o, inst),
-
-            .is_err          => try airIsErr(o, inst, "", ".", "!="),
-            .is_non_err      => try airIsErr(o, inst, "", ".", "=="),
-            .is_err_ptr      => try airIsErr(o, inst, "*", "->", "!="),
-            .is_non_err_ptr  => try airIsErr(o, inst, "*", "->", "=="),
-
-            .is_null         => try airIsNull(o, inst, "==", ""),
-            .is_non_null     => try airIsNull(o, inst, "!=", ""),
-            .is_null_ptr     => try airIsNull(o, inst, "==", "[0]"),
-            .is_non_null_ptr => try airIsNull(o, inst, "!=", "[0]"),
-
-            .alloc            => try airAlloc(o, inst),
-            .assembly         => try airAsm(o, inst),
-            .block            => try airBlock(o, inst),
-            .bitcast          => try airBitcast(o, inst),
-            .call             => try airCall(o, inst),
-            .dbg_stmt         => try airDbgStmt(o, inst),
-            .intcast          => try airIntCast(o, inst),
-            .trunc            => try airTrunc(o, inst),
-            .bool_to_int      => try airBoolToInt(o, inst),
-            .load             => try airLoad(o, inst),
-            .ret              => try airRet(o, inst),
-            .store            => try airStore(o, inst),
-            .loop             => try airLoop(o, inst),
-            .cond_br          => try airCondBr(o, inst),
-            .br               => try airBr(o, inst),
-            .switch_br        => try airSwitchBr(o, inst),
-            .wrap_optional    => try airWrapOptional(o, inst),
-            .struct_field_ptr => try airStructFieldPtr(o, inst),
-            .array_to_slice   => try airArrayToSlice(o, inst),
-            .cmpxchg_weak     => try airCmpxchg(o, inst, "weak"),
-            .cmpxchg_strong   => try airCmpxchg(o, inst, "strong"),
-            .atomic_rmw       => try airAtomicRmw(o, inst),
-            .atomic_load      => try airAtomicLoad(o, inst),
-
-            .int_to_float, .float_to_int => try airSimpleCast(o, inst),
-
-            .atomic_store_unordered => try airAtomicStore(o, inst, toMemoryOrder(.Unordered)),
-            .atomic_store_monotonic => try airAtomicStore(o, inst, toMemoryOrder(.Monotonic)),
-            .atomic_store_release   => try airAtomicStore(o, inst, toMemoryOrder(.Release)),
-            .atomic_store_seq_cst   => try airAtomicStore(o, inst, toMemoryOrder(.SeqCst)),
-
-            .struct_field_ptr_index_0 => try airStructFieldPtrIndex(o, inst, 0),
-            .struct_field_ptr_index_1 => try airStructFieldPtrIndex(o, inst, 1),
-            .struct_field_ptr_index_2 => try airStructFieldPtrIndex(o, inst, 2),
-            .struct_field_ptr_index_3 => try airStructFieldPtrIndex(o, inst, 3),
-
-            .struct_field_val => try airStructFieldVal(o, inst),
-            .slice_ptr        => try airSliceField(o, inst, ".ptr;\n"),
-            .slice_len        => try airSliceField(o, inst, ".len;\n"),
-
-            .ptr_elem_val       => try airPtrElemVal(o, inst, "["),
-            .ptr_ptr_elem_val   => try airPtrElemVal(o, inst, "[0]["),
-            .ptr_elem_ptr       => try airPtrElemPtr(o, inst),
-            .slice_elem_val     => try airSliceElemVal(o, inst, "["),
-            .ptr_slice_elem_val => try airSliceElemVal(o, inst, "[0]["),
-
-            .unwrap_errunion_payload     => try airUnwrapErrUnionPay(o, inst),
-            .unwrap_errunion_err         => try airUnwrapErrUnionErr(o, inst),
-            .unwrap_errunion_payload_ptr => try airUnwrapErrUnionPay(o, inst),
-            .unwrap_errunion_err_ptr     => try airUnwrapErrUnionErr(o, inst),
-            .wrap_errunion_payload       => try airWrapErrUnionPay(o, inst),
-            .wrap_errunion_err           => try airWrapErrUnionErr(o, inst),
-
-            .ptrtoint  => return o.dg.fail("TODO: C backend: implement codegen for ptrtoint", .{}),
-            .floatcast => return o.dg.fail("TODO: C backend: implement codegen for floatcast", .{}),
+            .bool_and   => try airBinOp(f, inst, " & "),
+            .bool_or    => try airBinOp(f, inst, " | "),
+            .bit_and    => try airBinOp(f, inst, " & "),
+            .bit_or     => try airBinOp(f, inst, " | "),
+            .xor        => try airBinOp(f, inst, " ^ "),
+
+            .shr        => try airBinOp(f, inst, " >> "),
+            .shl        => try airBinOp(f, inst, " << "),
+
+            .not        => try airNot(  f, inst),
+
+            .optional_payload     => try airOptionalPayload(f, inst),
+            .optional_payload_ptr => try airOptionalPayload(f, inst),
+
+            .is_err          => try airIsErr(f, inst, "", ".", "!="),
+            .is_non_err      => try airIsErr(f, inst, "", ".", "=="),
+            .is_err_ptr      => try airIsErr(f, inst, "*", "->", "!="),
+            .is_non_err_ptr  => try airIsErr(f, inst, "*", "->", "=="),
+
+            .is_null         => try airIsNull(f, inst, "==", ""),
+            .is_non_null     => try airIsNull(f, inst, "!=", ""),
+            .is_null_ptr     => try airIsNull(f, inst, "==", "[0]"),
+            .is_non_null_ptr => try airIsNull(f, inst, "!=", "[0]"),
+
+            .alloc            => try airAlloc(f, inst),
+            .assembly         => try airAsm(f, inst),
+            .block            => try airBlock(f, inst),
+            .bitcast          => try airBitcast(f, inst),
+            .call             => try airCall(f, inst),
+            .dbg_stmt         => try airDbgStmt(f, inst),
+            .intcast          => try airIntCast(f, inst),
+            .trunc            => try airTrunc(f, inst),
+            .bool_to_int      => try airBoolToInt(f, inst),
+            .load             => try airLoad(f, inst),
+            .ret              => try airRet(f, inst),
+            .store            => try airStore(f, inst),
+            .loop             => try airLoop(f, inst),
+            .cond_br          => try airCondBr(f, inst),
+            .br               => try airBr(f, inst),
+            .switch_br        => try airSwitchBr(f, inst),
+            .wrap_optional    => try airWrapOptional(f, inst),
+            .struct_field_ptr => try airStructFieldPtr(f, inst),
+            .array_to_slice   => try airArrayToSlice(f, inst),
+            .cmpxchg_weak     => try airCmpxchg(f, inst, "weak"),
+            .cmpxchg_strong   => try airCmpxchg(f, inst, "strong"),
+            .atomic_rmw       => try airAtomicRmw(f, inst),
+            .atomic_load      => try airAtomicLoad(f, inst),
+
+            .int_to_float, .float_to_int => try airSimpleCast(f, inst),
+
+            .atomic_store_unordered => try airAtomicStore(f, inst, toMemoryOrder(.Unordered)),
+            .atomic_store_monotonic => try airAtomicStore(f, inst, toMemoryOrder(.Monotonic)),
+            .atomic_store_release   => try airAtomicStore(f, inst, toMemoryOrder(.Release)),
+            .atomic_store_seq_cst   => try airAtomicStore(f, inst, toMemoryOrder(.SeqCst)),
+
+            .struct_field_ptr_index_0 => try airStructFieldPtrIndex(f, inst, 0),
+            .struct_field_ptr_index_1 => try airStructFieldPtrIndex(f, inst, 1),
+            .struct_field_ptr_index_2 => try airStructFieldPtrIndex(f, inst, 2),
+            .struct_field_ptr_index_3 => try airStructFieldPtrIndex(f, inst, 3),
+
+            .struct_field_val => try airStructFieldVal(f, inst),
+            .slice_ptr        => try airSliceField(f, inst, ".ptr;\n"),
+            .slice_len        => try airSliceField(f, inst, ".len;\n"),
+
+            .ptr_elem_val       => try airPtrElemVal(f, inst, "["),
+            .ptr_ptr_elem_val   => try airPtrElemVal(f, inst, "[0]["),
+            .ptr_elem_ptr       => try airPtrElemPtr(f, inst),
+            .slice_elem_val     => try airSliceElemVal(f, inst, "["),
+            .ptr_slice_elem_val => try airSliceElemVal(f, inst, "[0]["),
+
+            .unwrap_errunion_payload     => try airUnwrapErrUnionPay(f, inst),
+            .unwrap_errunion_err         => try airUnwrapErrUnionErr(f, inst),
+            .unwrap_errunion_payload_ptr => try airUnwrapErrUnionPay(f, inst),
+            .unwrap_errunion_err_ptr     => try airUnwrapErrUnionErr(f, inst),
+            .wrap_errunion_payload       => try airWrapErrUnionPay(f, inst),
+            .wrap_errunion_err           => try airWrapErrUnionErr(f, inst),
+
+            .ptrtoint  => return f.fail("TODO: C backend: implement codegen for ptrtoint", .{}),
+            .floatcast => return f.fail("TODO: C backend: implement codegen for floatcast", .{}),
             // zig fmt: on
         };
         switch (result_value) {
             .none => {},
-            else => try o.value_map.putNoClobber(inst, result_value),
+            else => try f.value_map.putNoClobber(inst, result_value),
         }
     }
 
-    o.indent_writer.popIndent();
+    f.object.indent_writer.popIndent();
     try writer.writeAll("}");
 }
 
-fn airSliceField(o: *Object, inst: Air.Inst.Index, suffix: []const u8) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airSliceField(f: *Function, inst: Air.Inst.Index, suffix: []const u8) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const operand = try o.resolveInst(ty_op.operand);
-    const writer = o.writer();
-    const local = try o.allocLocal(Type.initTag(.usize), .Const);
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const operand = try f.resolveInst(ty_op.operand);
+    const writer = f.object.writer();
+    const local = try f.allocLocal(Type.initTag(.usize), .Const);
     try writer.writeAll(" = ");
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
     try writer.writeAll(suffix);
     return local;
 }
 
-fn airPtrElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue {
+fn airPtrElemVal(f: *Function, inst: Air.Inst.Index, prefix: []const u8) !CValue {
     const is_volatile = false; // TODO
-    if (!is_volatile and o.liveness.isUnused(inst))
+    if (!is_volatile and f.liveness.isUnused(inst))
         return CValue.none;
 
     _ = prefix;
-    return o.dg.fail("TODO: C backend: airPtrElemVal", .{});
+    return f.fail("TODO: C backend: airPtrElemVal", .{});
 }
 
-fn airPtrElemPtr(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airPtrElemPtr(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    return o.dg.fail("TODO: C backend: airPtrElemPtr", .{});
+    return f.fail("TODO: C backend: airPtrElemPtr", .{});
 }
 
-fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue {
+fn airSliceElemVal(f: *Function, inst: Air.Inst.Index, prefix: []const u8) !CValue {
     const is_volatile = false; // TODO
-    if (!is_volatile and o.liveness.isUnused(inst))
+    if (!is_volatile and f.liveness.isUnused(inst))
         return CValue.none;
 
-    const bin_op = o.air.instructions.items(.data)[inst].bin_op;
-    const slice = try o.resolveInst(bin_op.lhs);
-    const index = try o.resolveInst(bin_op.rhs);
-    const writer = o.writer();
-    const local = try o.allocLocal(o.air.typeOfIndex(inst), .Const);
+    const bin_op = f.air.instructions.items(.data)[inst].bin_op;
+    const slice = try f.resolveInst(bin_op.lhs);
+    const index = try f.resolveInst(bin_op.rhs);
+    const writer = f.object.writer();
+    const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const);
     try writer.writeAll(" = ");
-    try o.writeCValue(writer, slice);
+    try f.writeCValue(writer, slice);
     try writer.writeAll(prefix);
-    try o.writeCValue(writer, index);
+    try f.writeCValue(writer, index);
     try writer.writeAll("];\n");
     return local;
 }
 
-fn airAlloc(o: *Object, inst: Air.Inst.Index) !CValue {
-    const writer = o.writer();
-    const inst_ty = o.air.typeOfIndex(inst);
+fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue {
+    const writer = f.object.writer();
+    const inst_ty = f.air.typeOfIndex(inst);
 
     // First line: the variable used as data storage.
     const elem_type = inst_ty.elemType();
     const mutability: Mutability = if (inst_ty.isConstPtr()) .Const else .Mut;
-    const local = try o.allocLocal(elem_type, mutability);
+    const local = try f.allocLocal(elem_type, mutability);
     try writer.writeAll(";\n");
 
     return CValue{ .local_ref = local.local };
 }
 
-fn airArg(o: *Object) CValue {
-    const i = o.next_arg_index;
-    o.next_arg_index += 1;
+fn airArg(f: *Function) CValue {
+    const i = f.next_arg_index;
+    f.next_arg_index += 1;
     return .{ .arg = i };
 }
 
-fn airLoad(o: *Object, inst: Air.Inst.Index) !CValue {
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const is_volatile = o.air.typeOf(ty_op.operand).isVolatilePtr();
-    if (!is_volatile and o.liveness.isUnused(inst))
+fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue {
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const is_volatile = f.air.typeOf(ty_op.operand).isVolatilePtr();
+    if (!is_volatile and f.liveness.isUnused(inst))
         return CValue.none;
-    const inst_ty = o.air.typeOfIndex(inst);
-    const operand = try o.resolveInst(ty_op.operand);
-    const writer = o.writer();
-    const local = try o.allocLocal(inst_ty, .Const);
+    const inst_ty = f.air.typeOfIndex(inst);
+    const operand = try f.resolveInst(ty_op.operand);
+    const writer = f.object.writer();
+    const local = try f.allocLocal(inst_ty, .Const);
     switch (operand) {
         .local_ref => |i| {
             const wrapped: CValue = .{ .local = i };
             try writer.writeAll(" = ");
-            try o.writeCValue(writer, wrapped);
+            try f.writeCValue(writer, wrapped);
             try writer.writeAll(";\n");
         },
         .decl_ref => |decl| {
             const wrapped: CValue = .{ .decl = decl };
             try writer.writeAll(" = ");
-            try o.writeCValue(writer, wrapped);
+            try f.writeCValue(writer, wrapped);
             try writer.writeAll(";\n");
         },
         else => {
             try writer.writeAll(" = *");
-            try o.writeCValue(writer, operand);
+            try f.writeCValue(writer, operand);
             try writer.writeAll(";\n");
         },
     }
     return local;
 }
 
-fn airRet(o: *Object, inst: Air.Inst.Index) !CValue {
-    const un_op = o.air.instructions.items(.data)[inst].un_op;
-    const writer = o.writer();
-    if (o.air.typeOf(un_op).hasCodeGenBits()) {
-        const operand = try o.resolveInst(un_op);
+fn airRet(f: *Function, inst: Air.Inst.Index) !CValue {
+    const un_op = f.air.instructions.items(.data)[inst].un_op;
+    const writer = f.object.writer();
+    if (f.air.typeOf(un_op).hasCodeGenBits()) {
+        const operand = try f.resolveInst(un_op);
         try writer.writeAll("return ");
-        try o.writeCValue(writer, operand);
+        try f.writeCValue(writer, operand);
         try writer.writeAll(";\n");
     } else {
         try writer.writeAll("return;\n");
@@ -1072,75 +1109,75 @@ fn airRet(o: *Object, inst: Air.Inst.Index) !CValue {
     return CValue.none;
 }
 
-fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airIntCast(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const operand = try o.resolveInst(ty_op.operand);
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const operand = try f.resolveInst(ty_op.operand);
 
-    const writer = o.writer();
-    const inst_ty = o.air.typeOfIndex(inst);
-    const local = try o.allocLocal(inst_ty, .Const);
+    const writer = f.object.writer();
+    const inst_ty = f.air.typeOfIndex(inst);
+    const local = try f.allocLocal(inst_ty, .Const);
     try writer.writeAll(" = (");
-    try o.dg.renderType(writer, inst_ty);
+    try f.renderType(writer, inst_ty);
     try writer.writeAll(")");
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
     try writer.writeAll(";\n");
     return local;
 }
 
-fn airTrunc(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airTrunc(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const operand = try o.resolveInst(ty_op.operand);
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const operand = try f.resolveInst(ty_op.operand);
     _ = operand;
-    return o.dg.fail("TODO: C backend: airTrunc", .{});
+    return f.fail("TODO: C backend: airTrunc", .{});
 }
 
-fn airBoolToInt(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airBoolToInt(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
-    const un_op = o.air.instructions.items(.data)[inst].un_op;
-    const writer = o.writer();
-    const inst_ty = o.air.typeOfIndex(inst);
-    const operand = try o.resolveInst(un_op);
-    const local = try o.allocLocal(inst_ty, .Const);
+    const un_op = f.air.instructions.items(.data)[inst].un_op;
+    const writer = f.object.writer();
+    const inst_ty = f.air.typeOfIndex(inst);
+    const operand = try f.resolveInst(un_op);
+    const local = try f.allocLocal(inst_ty, .Const);
     try writer.writeAll(" = ");
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
     try writer.writeAll(";\n");
     return local;
 }
 
-fn airStore(o: *Object, inst: Air.Inst.Index) !CValue {
+fn airStore(f: *Function, inst: Air.Inst.Index) !CValue {
     // *a = b;
-    const bin_op = o.air.instructions.items(.data)[inst].bin_op;
-    const dest_ptr = try o.resolveInst(bin_op.lhs);
-    const src_val = try o.resolveInst(bin_op.rhs);
+    const bin_op = f.air.instructions.items(.data)[inst].bin_op;
+    const dest_ptr = try f.resolveInst(bin_op.lhs);
+    const src_val = try f.resolveInst(bin_op.rhs);
 
-    const writer = o.writer();
+    const writer = f.object.writer();
     switch (dest_ptr) {
         .local_ref => |i| {
             const dest: CValue = .{ .local = i };
-            try o.writeCValue(writer, dest);
+            try f.writeCValue(writer, dest);
             try writer.writeAll(" = ");
-            try o.writeCValue(writer, src_val);
+            try f.writeCValue(writer, src_val);
             try writer.writeAll(";\n");
         },
         .decl_ref => |decl| {
             const dest: CValue = .{ .decl = decl };
-            try o.writeCValue(writer, dest);
+            try f.writeCValue(writer, dest);
             try writer.writeAll(" = ");
-            try o.writeCValue(writer, src_val);
+            try f.writeCValue(writer, src_val);
             try writer.writeAll(";\n");
         },
         else => {
             try writer.writeAll("*");
-            try o.writeCValue(writer, dest_ptr);
+            try f.writeCValue(writer, dest_ptr);
             try writer.writeAll(" = ");
-            try o.writeCValue(writer, src_val);
+            try f.writeCValue(writer, src_val);
             try writer.writeAll(";\n");
         },
     }
@@ -1148,17 +1185,17 @@ fn airStore(o: *Object, inst: Air.Inst.Index) !CValue {
 }
 
 fn airWrapOp(
-    o: *Object,
+    f: *Function,
     inst: Air.Inst.Index,
     str_op: [*:0]const u8,
     fn_op: [*:0]const u8,
 ) !CValue {
-    if (o.liveness.isUnused(inst))
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const bin_op = o.air.instructions.items(.data)[inst].bin_op;
-    const inst_ty = o.air.typeOfIndex(inst);
-    const int_info = inst_ty.intInfo(o.dg.module.getTarget());
+    const bin_op = f.air.instructions.items(.data)[inst].bin_op;
+    const inst_ty = f.air.typeOfIndex(inst);
+    const int_info = inst_ty.intInfo(f.object.dg.module.getTarget());
     const bits = int_info.bits;
 
     // if it's an unsigned int with non-arbitrary bit size then we can just add
@@ -1168,12 +1205,12 @@ fn airWrapOp(
             else => false,
         };
         if (ok_bits or inst_ty.tag() != .int_unsigned) {
-            return try airBinOp(o, inst, str_op);
+            return try airBinOp(f, inst, str_op);
         }
     }
 
     if (bits > 64) {
-        return o.dg.fail("TODO: C backend: airWrapOp for large integers", .{});
+        return f.fail("TODO: C backend: airWrapOp for large integers", .{});
     }
 
     var min_buf: [80]u8 = undefined;
@@ -1220,11 +1257,11 @@ fn airWrapOp(
         },
     };
 
-    const lhs = try o.resolveInst(bin_op.lhs);
-    const rhs = try o.resolveInst(bin_op.rhs);
-    const w = o.writer();
+    const lhs = try f.resolveInst(bin_op.lhs);
+    const rhs = try f.resolveInst(bin_op.rhs);
+    const w = f.object.writer();
 
-    const ret = try o.allocLocal(inst_ty, .Mut);
+    const ret = try f.allocLocal(inst_ty, .Mut);
     try w.print(" = zig_{s}", .{fn_op});
 
     switch (inst_ty.tag()) {
@@ -1250,71 +1287,71 @@ fn airWrapOp(
     }
 
     try w.writeByte('(');
-    try o.writeCValue(w, lhs);
+    try f.writeCValue(w, lhs);
     try w.writeAll(", ");
-    try o.writeCValue(w, rhs);
+    try f.writeCValue(w, rhs);
 
     if (int_info.signedness == .signed) {
         try w.print(", {s}", .{min});
     }
 
     try w.print(", {s});", .{max});
-    try o.indent_writer.insertNewline();
+    try f.object.indent_writer.insertNewline();
 
     return ret;
 }
 
-fn airNot(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airNot(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const op = try o.resolveInst(ty_op.operand);
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const op = try f.resolveInst(ty_op.operand);
 
-    const writer = o.writer();
-    const inst_ty = o.air.typeOfIndex(inst);
-    const local = try o.allocLocal(inst_ty, .Const);
+    const writer = f.object.writer();
+    const inst_ty = f.air.typeOfIndex(inst);
+    const local = try f.allocLocal(inst_ty, .Const);
 
     try writer.writeAll(" = ");
     if (inst_ty.zigTypeTag() == .Bool)
         try writer.writeAll("!")
     else
         try writer.writeAll("~");
-    try o.writeCValue(writer, op);
+    try f.writeCValue(writer, op);
     try writer.writeAll(";\n");
 
     return local;
 }
 
-fn airBinOp(o: *Object, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airBinOp(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const bin_op = o.air.instructions.items(.data)[inst].bin_op;
-    const lhs = try o.resolveInst(bin_op.lhs);
-    const rhs = try o.resolveInst(bin_op.rhs);
+    const bin_op = f.air.instructions.items(.data)[inst].bin_op;
+    const lhs = try f.resolveInst(bin_op.lhs);
+    const rhs = try f.resolveInst(bin_op.rhs);
 
-    const writer = o.writer();
-    const inst_ty = o.air.typeOfIndex(inst);
-    const local = try o.allocLocal(inst_ty, .Const);
+    const writer = f.object.writer();
+    const inst_ty = f.air.typeOfIndex(inst);
+    const local = try f.allocLocal(inst_ty, .Const);
 
     try writer.writeAll(" = ");
-    try o.writeCValue(writer, lhs);
+    try f.writeCValue(writer, lhs);
     try writer.print("{s}", .{operator});
-    try o.writeCValue(writer, rhs);
+    try f.writeCValue(writer, rhs);
     try writer.writeAll(";\n");
 
     return local;
 }
 
-fn airCall(o: *Object, inst: Air.Inst.Index) !CValue {
-    const pl_op = o.air.instructions.items(.data)[inst].pl_op;
-    const extra = o.air.extraData(Air.Call, pl_op.payload);
-    const args = @bitCast([]const Air.Inst.Ref, o.air.extra[extra.end..][0..extra.data.args_len]);
-    const fn_ty = o.air.typeOf(pl_op.operand);
+fn airCall(f: *Function, inst: Air.Inst.Index) !CValue {
+    const pl_op = f.air.instructions.items(.data)[inst].pl_op;
+    const extra = f.air.extraData(Air.Call, pl_op.payload);
+    const args = @bitCast([]const Air.Inst.Ref, f.air.extra[extra.end..][0..extra.data.args_len]);
+    const fn_ty = f.air.typeOf(pl_op.operand);
     const ret_ty = fn_ty.fnReturnType();
-    const unused_result = o.liveness.isUnused(inst);
-    const writer = o.writer();
+    const unused_result = f.liveness.isUnused(inst);
+    const writer = f.object.writer();
 
     var result_local: CValue = .none;
     if (unused_result) {
@@ -1322,11 +1359,11 @@ fn airCall(o: *Object, inst: Air.Inst.Index) !CValue {
             try writer.print("(void)", .{});
         }
     } else {
-        result_local = try o.allocLocal(ret_ty, .Const);
+        result_local = try f.allocLocal(ret_ty, .Const);
         try writer.writeAll(" = ");
     }
 
-    if (o.air.value(pl_op.operand)) |func_val| {
+    if (f.air.value(pl_op.operand)) |func_val| {
         const fn_decl = if (func_val.castTag(.extern_fn)) |extern_fn|
             extern_fn.data
         else if (func_val.castTag(.function)) |func_payload|
@@ -1336,8 +1373,8 @@ fn airCall(o: *Object, inst: Air.Inst.Index) !CValue {
 
         try writer.writeAll(mem.spanZ(fn_decl.name));
     } else {
-        const callee = try o.resolveInst(pl_op.operand);
-        try o.writeCValue(writer, callee);
+        const callee = try f.resolveInst(pl_op.operand);
+        try f.writeCValue(writer, callee);
     }
 
     try writer.writeAll("(");
@@ -1345,113 +1382,113 @@ fn airCall(o: *Object, inst: Air.Inst.Index) !CValue {
         if (i != 0) {
             try writer.writeAll(", ");
         }
-        if (o.air.value(arg)) |val| {
-            try o.dg.renderValue(writer, o.air.typeOf(arg), val);
+        if (f.air.value(arg)) |val| {
+            try f.object.dg.renderValue(writer, f.air.typeOf(arg), val);
         } else {
-            const val = try o.resolveInst(arg);
-            try o.writeCValue(writer, val);
+            const val = try f.resolveInst(arg);
+            try f.writeCValue(writer, val);
         }
     }
     try writer.writeAll(");\n");
     return result_local;
 }
 
-fn airDbgStmt(o: *Object, inst: Air.Inst.Index) !CValue {
-    const dbg_stmt = o.air.instructions.items(.data)[inst].dbg_stmt;
-    const writer = o.writer();
+fn airDbgStmt(f: *Function, inst: Air.Inst.Index) !CValue {
+    const dbg_stmt = f.air.instructions.items(.data)[inst].dbg_stmt;
+    const writer = f.object.writer();
     try writer.print("#line {d}\n", .{dbg_stmt.line + 1});
     return CValue.none;
 }
 
-fn airBlock(o: *Object, inst: Air.Inst.Index) !CValue {
-    const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
-    const extra = o.air.extraData(Air.Block, ty_pl.payload);
-    const body = o.air.extra[extra.end..][0..extra.data.body_len];
+fn airBlock(f: *Function, inst: Air.Inst.Index) !CValue {
+    const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
+    const extra = f.air.extraData(Air.Block, ty_pl.payload);
+    const body = f.air.extra[extra.end..][0..extra.data.body_len];
 
-    const block_id: usize = o.next_block_index;
-    o.next_block_index += 1;
-    const writer = o.writer();
+    const block_id: usize = f.next_block_index;
+    f.next_block_index += 1;
+    const writer = f.object.writer();
 
-    const inst_ty = o.air.typeOfIndex(inst);
-    const result = if (inst_ty.tag() != .void and !o.liveness.isUnused(inst)) blk: {
+    const inst_ty = f.air.typeOfIndex(inst);
+    const result = if (inst_ty.tag() != .void and !f.liveness.isUnused(inst)) blk: {
         // allocate a location for the result
-        const local = try o.allocLocal(inst_ty, .Mut);
+        const local = try f.allocLocal(inst_ty, .Mut);
         try writer.writeAll(";\n");
         break :blk local;
     } else CValue{ .none = {} };
 
-    try o.blocks.putNoClobber(o.gpa, inst, .{
+    try f.blocks.putNoClobber(f.object.dg.gpa, inst, .{
         .block_id = block_id,
         .result = result,
     });
 
-    try genBody(o, body);
-    try o.indent_writer.insertNewline();
+    try genBody(f, body);
+    try f.object.indent_writer.insertNewline();
     // label must be followed by an expression, add an empty one.
     try writer.print("zig_block_{d}:;\n", .{block_id});
     return result;
 }
 
-fn airBr(o: *Object, inst: Air.Inst.Index) !CValue {
-    const branch = o.air.instructions.items(.data)[inst].br;
-    const block = o.blocks.get(branch.block_inst).?;
+fn airBr(f: *Function, inst: Air.Inst.Index) !CValue {
+    const branch = f.air.instructions.items(.data)[inst].br;
+    const block = f.blocks.get(branch.block_inst).?;
     const result = block.result;
-    const writer = o.writer();
+    const writer = f.object.writer();
 
     // If result is .none then the value of the block is unused.
     if (result != .none) {
-        const operand = try o.resolveInst(branch.operand);
-        try o.writeCValue(writer, result);
+        const operand = try f.resolveInst(branch.operand);
+        try f.writeCValue(writer, result);
         try writer.writeAll(" = ");
-        try o.writeCValue(writer, operand);
+        try f.writeCValue(writer, operand);
         try writer.writeAll(";\n");
     }
 
-    try o.writer().print("goto zig_block_{d};\n", .{block.block_id});
+    try f.object.writer().print("goto zig_block_{d};\n", .{block.block_id});
     return CValue.none;
 }
 
-fn airBitcast(o: *Object, inst: Air.Inst.Index) !CValue {
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const operand = try o.resolveInst(ty_op.operand);
+fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue {
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const operand = try f.resolveInst(ty_op.operand);
 
-    const writer = o.writer();
-    const inst_ty = o.air.typeOfIndex(inst);
+    const writer = f.object.writer();
+    const inst_ty = f.air.typeOfIndex(inst);
     if (inst_ty.zigTypeTag() == .Pointer and
-        o.air.typeOf(ty_op.operand).zigTypeTag() == .Pointer)
+        f.air.typeOf(ty_op.operand).zigTypeTag() == .Pointer)
     {
-        const local = try o.allocLocal(inst_ty, .Const);
+        const local = try f.allocLocal(inst_ty, .Const);
         try writer.writeAll(" = (");
-        try o.dg.renderType(writer, inst_ty);
+        try f.renderType(writer, inst_ty);
 
         try writer.writeAll(")");
-        try o.writeCValue(writer, operand);
+        try f.writeCValue(writer, operand);
         try writer.writeAll(";\n");
         return local;
     }
 
-    const local = try o.allocLocal(inst_ty, .Mut);
+    const local = try f.allocLocal(inst_ty, .Mut);
     try writer.writeAll(";\n");
 
     try writer.writeAll("memcpy(&");
-    try o.writeCValue(writer, local);
+    try f.writeCValue(writer, local);
     try writer.writeAll(", &");
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
     try writer.writeAll(", sizeof ");
-    try o.writeCValue(writer, local);
+    try f.writeCValue(writer, local);
     try writer.writeAll(");\n");
 
     return local;
 }
 
-fn airBreakpoint(o: *Object) !CValue {
-    try o.writer().writeAll("zig_breakpoint();\n");
+fn airBreakpoint(f: *Function) !CValue {
+    try f.object.writer().writeAll("zig_breakpoint();\n");
     return CValue.none;
 }
 
-fn airFence(o: *Object, inst: Air.Inst.Index) !CValue {
-    const atomic_order = o.air.instructions.items(.data)[inst].fence;
-    const writer = o.writer();
+fn airFence(f: *Function, inst: Air.Inst.Index) !CValue {
+    const atomic_order = f.air.instructions.items(.data)[inst].fence;
+    const writer = f.object.writer();
 
     try writer.writeAll("zig_fence(");
     try writeMemoryOrder(writer, atomic_order);
@@ -1460,85 +1497,85 @@ fn airFence(o: *Object, inst: Air.Inst.Index) !CValue {
     return CValue.none;
 }
 
-fn airUnreach(o: *Object) !CValue {
-    try o.writer().writeAll("zig_unreachable();\n");
+fn airUnreach(f: *Function) !CValue {
+    try f.object.writer().writeAll("zig_unreachable();\n");
     return CValue.none;
 }
 
-fn airLoop(o: *Object, inst: Air.Inst.Index) !CValue {
-    const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
-    const loop = o.air.extraData(Air.Block, ty_pl.payload);
-    const body = o.air.extra[loop.end..][0..loop.data.body_len];
-    try o.writer().writeAll("while (true) ");
-    try genBody(o, body);
-    try o.indent_writer.insertNewline();
+fn airLoop(f: *Function, inst: Air.Inst.Index) !CValue {
+    const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
+    const loop = f.air.extraData(Air.Block, ty_pl.payload);
+    const body = f.air.extra[loop.end..][0..loop.data.body_len];
+    try f.object.writer().writeAll("while (true) ");
+    try genBody(f, body);
+    try f.object.indent_writer.insertNewline();
     return CValue.none;
 }
 
-fn airCondBr(o: *Object, inst: Air.Inst.Index) !CValue {
-    const pl_op = o.air.instructions.items(.data)[inst].pl_op;
-    const cond = try o.resolveInst(pl_op.operand);
-    const extra = o.air.extraData(Air.CondBr, pl_op.payload);
-    const then_body = o.air.extra[extra.end..][0..extra.data.then_body_len];
-    const else_body = o.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
-    const writer = o.writer();
+fn airCondBr(f: *Function, inst: Air.Inst.Index) !CValue {
+    const pl_op = f.air.instructions.items(.data)[inst].pl_op;
+    const cond = try f.resolveInst(pl_op.operand);
+    const extra = f.air.extraData(Air.CondBr, pl_op.payload);
+    const then_body = f.air.extra[extra.end..][0..extra.data.then_body_len];
+    const else_body = f.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
+    const writer = f.object.writer();
 
     try writer.writeAll("if (");
-    try o.writeCValue(writer, cond);
+    try f.writeCValue(writer, cond);
     try writer.writeAll(") ");
-    try genBody(o, then_body);
+    try genBody(f, then_body);
     try writer.writeAll(" else ");
-    try genBody(o, else_body);
-    try o.indent_writer.insertNewline();
+    try genBody(f, else_body);
+    try f.object.indent_writer.insertNewline();
 
     return CValue.none;
 }
 
-fn airSwitchBr(o: *Object, inst: Air.Inst.Index) !CValue {
-    const pl_op = o.air.instructions.items(.data)[inst].pl_op;
-    const condition = try o.resolveInst(pl_op.operand);
-    const condition_ty = o.air.typeOf(pl_op.operand);
-    const switch_br = o.air.extraData(Air.SwitchBr, pl_op.payload);
-    const writer = o.writer();
+fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !CValue {
+    const pl_op = f.air.instructions.items(.data)[inst].pl_op;
+    const condition = try f.resolveInst(pl_op.operand);
+    const condition_ty = f.air.typeOf(pl_op.operand);
+    const switch_br = f.air.extraData(Air.SwitchBr, pl_op.payload);
+    const writer = f.object.writer();
 
     try writer.writeAll("switch (");
-    try o.writeCValue(writer, condition);
+    try f.writeCValue(writer, condition);
     try writer.writeAll(") {");
-    o.indent_writer.pushIndent();
+    f.object.indent_writer.pushIndent();
 
     var extra_index: usize = switch_br.end;
     var case_i: u32 = 0;
     while (case_i < switch_br.data.cases_len) : (case_i += 1) {
-        const case = o.air.extraData(Air.SwitchBr.Case, extra_index);
-        const items = @bitCast([]const Air.Inst.Ref, o.air.extra[case.end..][0..case.data.items_len]);
-        const case_body = o.air.extra[case.end + items.len ..][0..case.data.body_len];
+        const case = f.air.extraData(Air.SwitchBr.Case, extra_index);
+        const items = @bitCast([]const Air.Inst.Ref, f.air.extra[case.end..][0..case.data.items_len]);
+        const case_body = f.air.extra[case.end + items.len ..][0..case.data.body_len];
         extra_index = case.end + case.data.items_len + case_body.len;
 
         for (items) |item| {
-            try o.indent_writer.insertNewline();
+            try f.object.indent_writer.insertNewline();
             try writer.writeAll("case ");
-            try o.dg.renderValue(writer, condition_ty, o.air.value(item).?);
+            try f.object.dg.renderValue(writer, condition_ty, f.air.value(item).?);
             try writer.writeAll(": ");
         }
         // The case body must be noreturn so we don't need to insert a break.
-        try genBody(o, case_body);
+        try genBody(f, case_body);
     }
 
-    const else_body = o.air.extra[extra_index..][0..switch_br.data.else_body_len];
-    try o.indent_writer.insertNewline();
+    const else_body = f.air.extra[extra_index..][0..switch_br.data.else_body_len];
+    try f.object.indent_writer.insertNewline();
     try writer.writeAll("default: ");
-    try genBody(o, else_body);
-    try o.indent_writer.insertNewline();
+    try genBody(f, else_body);
+    try f.object.indent_writer.insertNewline();
 
-    o.indent_writer.popIndent();
+    f.object.indent_writer.popIndent();
     try writer.writeAll("}\n");
     return CValue.none;
 }
 
-fn airAsm(o: *Object, inst: Air.Inst.Index) !CValue {
-    const air_datas = o.air.instructions.items(.data);
-    const air_extra = o.air.extraData(Air.Asm, air_datas[inst].ty_pl.payload);
-    const zir = o.dg.decl.namespace.file_scope.zir;
+fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
+    const air_datas = f.air.instructions.items(.data);
+    const air_extra = f.air.extraData(Air.Asm, air_datas[inst].ty_pl.payload);
+    const zir = f.object.dg.decl.namespace.file_scope.zir;
     const extended = zir.instructions.items(.data)[air_extra.data.zir_index].extended;
     const zir_extra = zir.extraData(Zir.Inst.Asm, extended.operand);
     const asm_source = zir.nullTerminatedString(zir_extra.data.asm_source);
@@ -1547,14 +1584,14 @@ fn airAsm(o: *Object, inst: Air.Inst.Index) !CValue {
     const clobbers_len = @truncate(u5, extended.small >> 10);
     _ = clobbers_len; // TODO honor these
     const is_volatile = @truncate(u1, extended.small >> 15) != 0;
-    const outputs = @bitCast([]const Air.Inst.Ref, o.air.extra[air_extra.end..][0..outputs_len]);
-    const args = @bitCast([]const Air.Inst.Ref, o.air.extra[air_extra.end + outputs.len ..][0..args_len]);
+    const outputs = @bitCast([]const Air.Inst.Ref, f.air.extra[air_extra.end..][0..outputs_len]);
+    const args = @bitCast([]const Air.Inst.Ref, f.air.extra[air_extra.end + outputs.len ..][0..args_len]);
 
     if (outputs_len > 1) {
-        return o.dg.fail("TODO implement codegen for asm with more than 1 output", .{});
+        return f.fail("TODO implement codegen for asm with more than 1 output", .{});
     }
 
-    if (o.liveness.isUnused(inst) and !is_volatile)
+    if (f.liveness.isUnused(inst) and !is_volatile)
         return CValue.none;
 
     var extra_i: usize = zir_extra.end;
@@ -1569,28 +1606,28 @@ fn airAsm(o: *Object, inst: Air.Inst.Index) !CValue {
     };
     const args_extra_begin = extra_i;
 
-    const writer = o.writer();
+    const writer = f.object.writer();
     for (args) |arg| {
         const input = zir.extraData(Zir.Inst.Asm.Input, extra_i);
         extra_i = input.end;
         const constraint = zir.nullTerminatedString(input.data.constraint);
         if (constraint[0] == '{' and constraint[constraint.len - 1] == '}') {
             const reg = constraint[1 .. constraint.len - 1];
-            const arg_c_value = try o.resolveInst(arg);
+            const arg_c_value = try f.resolveInst(arg);
             try writer.writeAll("register ");
-            try o.dg.renderType(writer, o.air.typeOf(arg));
+            try f.renderType(writer, f.air.typeOf(arg));
 
             try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg });
-            try o.writeCValue(writer, arg_c_value);
+            try f.writeCValue(writer, arg_c_value);
             try writer.writeAll(";\n");
         } else {
-            return o.dg.fail("TODO non-explicit inline asm regs", .{});
+            return f.fail("TODO non-explicit inline asm regs", .{});
         }
     }
     const volatile_string: []const u8 = if (is_volatile) "volatile " else "";
     try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, asm_source });
     if (output_constraint) |_| {
-        return o.dg.fail("TODO: CBE inline asm output", .{});
+        return f.fail("TODO: CBE inline asm output", .{});
     }
     if (args.len > 0) {
         if (output_constraint == null) {
@@ -1616,30 +1653,30 @@ fn airAsm(o: *Object, inst: Air.Inst.Index) !CValue {
     }
     try writer.writeAll(");\n");
 
-    if (o.liveness.isUnused(inst))
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    return o.dg.fail("TODO: C backend: inline asm expression result used", .{});
+    return f.fail("TODO: C backend: inline asm expression result used", .{});
 }
 
 fn airIsNull(
-    o: *Object,
+    f: *Function,
     inst: Air.Inst.Index,
     operator: [*:0]const u8,
     deref_suffix: [*:0]const u8,
 ) !CValue {
-    if (o.liveness.isUnused(inst))
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const un_op = o.air.instructions.items(.data)[inst].un_op;
-    const writer = o.writer();
-    const operand = try o.resolveInst(un_op);
+    const un_op = f.air.instructions.items(.data)[inst].un_op;
+    const writer = f.object.writer();
+    const operand = try f.resolveInst(un_op);
 
-    const local = try o.allocLocal(Type.initTag(.bool), .Const);
+    const local = try f.allocLocal(Type.initTag(.bool), .Const);
     try writer.writeAll(" = (");
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
 
-    if (o.air.typeOf(un_op).isPtrLikeOptional()) {
+    if (f.air.typeOf(un_op).isPtrLikeOptional()) {
         // operand is a regular pointer, test `operand !=/== NULL`
         try writer.print("){s} {s} NULL;\n", .{ deref_suffix, operator });
     } else {
@@ -1648,14 +1685,14 @@ fn airIsNull(
     return local;
 }
 
-fn airOptionalPayload(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airOptionalPayload(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const writer = o.writer();
-    const operand = try o.resolveInst(ty_op.operand);
-    const operand_ty = o.air.typeOf(ty_op.operand);
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const writer = f.object.writer();
+    const operand = try f.resolveInst(ty_op.operand);
+    const operand_ty = f.air.typeOf(ty_op.operand);
 
     const opt_ty = if (operand_ty.zigTypeTag() == .Pointer)
         operand_ty.elemType()
@@ -1668,98 +1705,98 @@ fn airOptionalPayload(o: *Object, inst: Air.Inst.Index) !CValue {
         return operand;
     }
 
-    const inst_ty = o.air.typeOfIndex(inst);
+    const inst_ty = f.air.typeOfIndex(inst);
     const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else ".";
     const maybe_addrof = if (inst_ty.zigTypeTag() == .Pointer) "&" else "";
 
-    const local = try o.allocLocal(inst_ty, .Const);
+    const local = try f.allocLocal(inst_ty, .Const);
     try writer.print(" = {s}(", .{maybe_addrof});
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
 
     try writer.print("){s}payload;\n", .{maybe_deref});
     return local;
 }
 
-fn airStructFieldPtr(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airStructFieldPtr(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         // TODO this @as is needed because of a stage1 bug
         return @as(CValue, CValue.none);
 
-    const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
-    const extra = o.air.extraData(Air.StructField, ty_pl.payload).data;
-    const struct_ptr = try o.resolveInst(extra.struct_operand);
-    const struct_ptr_ty = o.air.typeOf(extra.struct_operand);
-    return structFieldPtr(o, inst, struct_ptr_ty, struct_ptr, extra.field_index);
+    const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
+    const extra = f.air.extraData(Air.StructField, ty_pl.payload).data;
+    const struct_ptr = try f.resolveInst(extra.struct_operand);
+    const struct_ptr_ty = f.air.typeOf(extra.struct_operand);
+    return structFieldPtr(f, inst, struct_ptr_ty, struct_ptr, extra.field_index);
 }
 
-fn airStructFieldPtrIndex(o: *Object, inst: Air.Inst.Index, index: u8) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airStructFieldPtrIndex(f: *Function, inst: Air.Inst.Index, index: u8) !CValue {
+    if (f.liveness.isUnused(inst))
         // TODO this @as is needed because of a stage1 bug
         return @as(CValue, CValue.none);
 
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const struct_ptr = try o.resolveInst(ty_op.operand);
-    const struct_ptr_ty = o.air.typeOf(ty_op.operand);
-    return structFieldPtr(o, inst, struct_ptr_ty, struct_ptr, index);
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const struct_ptr = try f.resolveInst(ty_op.operand);
+    const struct_ptr_ty = f.air.typeOf(ty_op.operand);
+    return structFieldPtr(f, inst, struct_ptr_ty, struct_ptr, index);
 }
 
-fn structFieldPtr(o: *Object, inst: Air.Inst.Index, struct_ptr_ty: Type, struct_ptr: CValue, index: u32) !CValue {
-    const writer = o.writer();
+fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struct_ptr: CValue, index: u32) !CValue {
+    const writer = f.object.writer();
     const struct_obj = struct_ptr_ty.elemType().castTag(.@"struct").?.data;
     const field_name = struct_obj.fields.keys()[index];
 
-    const inst_ty = o.air.typeOfIndex(inst);
-    const local = try o.allocLocal(inst_ty, .Const);
+    const inst_ty = f.air.typeOfIndex(inst);
+    const local = try f.allocLocal(inst_ty, .Const);
     switch (struct_ptr) {
         .local_ref => |i| {
             try writer.print(" = &t{d}.{};\n", .{ i, fmtIdent(field_name) });
         },
         else => {
             try writer.writeAll(" = &");
-            try o.writeCValue(writer, struct_ptr);
+            try f.writeCValue(writer, struct_ptr);
             try writer.print("->{};\n", .{fmtIdent(field_name)});
         },
     }
     return local;
 }
 
-fn airStructFieldVal(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
-    const extra = o.air.extraData(Air.StructField, ty_pl.payload).data;
-    const writer = o.writer();
-    const struct_byval = try o.resolveInst(extra.struct_operand);
-    const struct_ty = o.air.typeOf(extra.struct_operand);
+    const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
+    const extra = f.air.extraData(Air.StructField, ty_pl.payload).data;
+    const writer = f.object.writer();
+    const struct_byval = try f.resolveInst(extra.struct_operand);
+    const struct_ty = f.air.typeOf(extra.struct_operand);
     const struct_obj = struct_ty.castTag(.@"struct").?.data;
     const field_name = struct_obj.fields.keys()[extra.field_index];
 
-    const inst_ty = o.air.typeOfIndex(inst);
-    const local = try o.allocLocal(inst_ty, .Const);
+    const inst_ty = f.air.typeOfIndex(inst);
+    const local = try f.allocLocal(inst_ty, .Const);
     try writer.writeAll(" = ");
-    try o.writeCValue(writer, struct_byval);
+    try f.writeCValue(writer, struct_byval);
     try writer.print(".{};\n", .{fmtIdent(field_name)});
     return local;
 }
 
 // *(E!T) -> E NOT *E
-fn airUnwrapErrUnionErr(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airUnwrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const inst_ty = o.air.typeOfIndex(inst);
-    const writer = o.writer();
-    const operand = try o.resolveInst(ty_op.operand);
-    const operand_ty = o.air.typeOf(ty_op.operand);
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const inst_ty = f.air.typeOfIndex(inst);
+    const writer = f.object.writer();
+    const operand = try f.resolveInst(ty_op.operand);
+    const operand_ty = f.air.typeOf(ty_op.operand);
 
     const payload_ty = operand_ty.errorUnionPayload();
     if (!payload_ty.hasCodeGenBits()) {
         if (operand_ty.zigTypeTag() == .Pointer) {
-            const local = try o.allocLocal(inst_ty, .Const);
+            const local = try f.allocLocal(inst_ty, .Const);
             try writer.writeAll(" = *");
-            try o.writeCValue(writer, operand);
+            try f.writeCValue(writer, operand);
             try writer.writeAll(";\n");
             return local;
         } else {
@@ -1769,172 +1806,172 @@ fn airUnwrapErrUnionErr(o: *Object, inst: Air.Inst.Index) !CValue {
 
     const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else ".";
 
-    const local = try o.allocLocal(inst_ty, .Const);
+    const local = try f.allocLocal(inst_ty, .Const);
     try writer.writeAll(" = (");
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
 
     try writer.print("){s}error;\n", .{maybe_deref});
     return local;
 }
 
-fn airUnwrapErrUnionPay(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airUnwrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const writer = o.writer();
-    const operand = try o.resolveInst(ty_op.operand);
-    const operand_ty = o.air.typeOf(ty_op.operand);
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const writer = f.object.writer();
+    const operand = try f.resolveInst(ty_op.operand);
+    const operand_ty = f.air.typeOf(ty_op.operand);
 
     const payload_ty = operand_ty.errorUnionPayload();
     if (!payload_ty.hasCodeGenBits()) {
         return CValue.none;
     }
 
-    const inst_ty = o.air.typeOfIndex(inst);
+    const inst_ty = f.air.typeOfIndex(inst);
     const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else ".";
     const maybe_addrof = if (inst_ty.zigTypeTag() == .Pointer) "&" else "";
 
-    const local = try o.allocLocal(inst_ty, .Const);
+    const local = try f.allocLocal(inst_ty, .Const);
     try writer.print(" = {s}(", .{maybe_addrof});
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
 
     try writer.print("){s}payload;\n", .{maybe_deref});
     return local;
 }
 
-fn airWrapOptional(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airWrapOptional(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const writer = o.writer();
-    const operand = try o.resolveInst(ty_op.operand);
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const writer = f.object.writer();
+    const operand = try f.resolveInst(ty_op.operand);
 
-    const inst_ty = o.air.typeOfIndex(inst);
+    const inst_ty = f.air.typeOfIndex(inst);
     if (inst_ty.isPtrLikeOptional()) {
         // the operand is just a regular pointer, no need to do anything special.
         return operand;
     }
 
     // .wrap_optional is used to convert non-optionals into optionals so it can never be null.
-    const local = try o.allocLocal(inst_ty, .Const);
+    const local = try f.allocLocal(inst_ty, .Const);
     try writer.writeAll(" = { .is_null = false, .payload =");
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
     try writer.writeAll("};\n");
     return local;
 }
-fn airWrapErrUnionErr(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airWrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const writer = o.writer();
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const operand = try o.resolveInst(ty_op.operand);
+    const writer = f.object.writer();
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const operand = try f.resolveInst(ty_op.operand);
 
-    const inst_ty = o.air.typeOfIndex(inst);
-    const local = try o.allocLocal(inst_ty, .Const);
+    const inst_ty = f.air.typeOfIndex(inst);
+    const local = try f.allocLocal(inst_ty, .Const);
     try writer.writeAll(" = { .error = ");
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
     try writer.writeAll(" };\n");
     return local;
 }
 
-fn airWrapErrUnionPay(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const writer = o.writer();
-    const operand = try o.resolveInst(ty_op.operand);
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const writer = f.object.writer();
+    const operand = try f.resolveInst(ty_op.operand);
 
-    const inst_ty = o.air.typeOfIndex(inst);
-    const local = try o.allocLocal(inst_ty, .Const);
+    const inst_ty = f.air.typeOfIndex(inst);
+    const local = try f.allocLocal(inst_ty, .Const);
     try writer.writeAll(" = { .error = 0, .payload = ");
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
     try writer.writeAll(" };\n");
     return local;
 }
 
 fn airIsErr(
-    o: *Object,
+    f: *Function,
     inst: Air.Inst.Index,
     deref_prefix: [*:0]const u8,
     deref_suffix: [*:0]const u8,
     op_str: [*:0]const u8,
 ) !CValue {
-    if (o.liveness.isUnused(inst))
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const un_op = o.air.instructions.items(.data)[inst].un_op;
-    const writer = o.writer();
-    const operand = try o.resolveInst(un_op);
-    const operand_ty = o.air.typeOf(un_op);
-    const local = try o.allocLocal(Type.initTag(.bool), .Const);
+    const un_op = f.air.instructions.items(.data)[inst].un_op;
+    const writer = f.object.writer();
+    const operand = try f.resolveInst(un_op);
+    const operand_ty = f.air.typeOf(un_op);
+    const local = try f.allocLocal(Type.initTag(.bool), .Const);
     const payload_ty = operand_ty.errorUnionPayload();
     if (!payload_ty.hasCodeGenBits()) {
         try writer.print(" = {s}", .{deref_prefix});
-        try o.writeCValue(writer, operand);
+        try f.writeCValue(writer, operand);
         try writer.print(" {s} 0;\n", .{op_str});
     } else {
         try writer.writeAll(" = ");
-        try o.writeCValue(writer, operand);
+        try f.writeCValue(writer, operand);
         try writer.print("{s}error {s} 0;\n", .{ deref_suffix, op_str });
     }
     return local;
 }
 
-fn airArrayToSlice(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airArrayToSlice(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const inst_ty = o.air.typeOfIndex(inst);
-    const local = try o.allocLocal(inst_ty, .Const);
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const writer = o.writer();
-    const operand = try o.resolveInst(ty_op.operand);
-    const array_len = o.air.typeOf(ty_op.operand).elemType().arrayLen();
+    const inst_ty = f.air.typeOfIndex(inst);
+    const local = try f.allocLocal(inst_ty, .Const);
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const writer = f.object.writer();
+    const operand = try f.resolveInst(ty_op.operand);
+    const array_len = f.air.typeOf(ty_op.operand).elemType().arrayLen();
 
     try writer.writeAll(" = { .ptr = ");
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
     try writer.print(", .len = {d} }};\n", .{array_len});
     return local;
 }
 
 /// Emits a local variable with the result type and initializes it
 /// with the operand.
-fn airSimpleCast(o: *Object, inst: Air.Inst.Index) !CValue {
-    if (o.liveness.isUnused(inst))
+fn airSimpleCast(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst))
         return CValue.none;
 
-    const inst_ty = o.air.typeOfIndex(inst);
-    const local = try o.allocLocal(inst_ty, .Const);
-    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
-    const writer = o.writer();
-    const operand = try o.resolveInst(ty_op.operand);
+    const inst_ty = f.air.typeOfIndex(inst);
+    const local = try f.allocLocal(inst_ty, .Const);
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const writer = f.object.writer();
+    const operand = try f.resolveInst(ty_op.operand);
 
     try writer.writeAll(" = ");
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
     try writer.writeAll(";\n");
     return local;
 }
 
-fn airCmpxchg(o: *Object, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue {
-    const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
-    const extra = o.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
-    const inst_ty = o.air.typeOfIndex(inst);
-    const ptr = try o.resolveInst(extra.ptr);
-    const expected_value = try o.resolveInst(extra.expected_value);
-    const new_value = try o.resolveInst(extra.new_value);
-    const local = try o.allocLocal(inst_ty, .Const);
-    const writer = o.writer();
+fn airCmpxchg(f: *Function, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue {
+    const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
+    const extra = f.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
+    const inst_ty = f.air.typeOfIndex(inst);
+    const ptr = try f.resolveInst(extra.ptr);
+    const expected_value = try f.resolveInst(extra.expected_value);
+    const new_value = try f.resolveInst(extra.new_value);
+    const local = try f.allocLocal(inst_ty, .Const);
+    const writer = f.object.writer();
 
     try writer.print(" = zig_cmpxchg_{s}(", .{flavor});
-    try o.writeCValue(writer, ptr);
+    try f.writeCValue(writer, ptr);
     try writer.writeAll(", ");
-    try o.writeCValue(writer, expected_value);
+    try f.writeCValue(writer, expected_value);
     try writer.writeAll(", ");
-    try o.writeCValue(writer, new_value);
+    try f.writeCValue(writer, new_value);
     try writer.writeAll(", ");
     try writeMemoryOrder(writer, extra.successOrder());
     try writer.writeAll(", ");
@@ -1944,19 +1981,19 @@ fn airCmpxchg(o: *Object, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue {
     return local;
 }
 
-fn airAtomicRmw(o: *Object, inst: Air.Inst.Index) !CValue {
-    const pl_op = o.air.instructions.items(.data)[inst].pl_op;
-    const extra = o.air.extraData(Air.AtomicRmw, pl_op.payload).data;
-    const inst_ty = o.air.typeOfIndex(inst);
-    const ptr = try o.resolveInst(pl_op.operand);
-    const operand = try o.resolveInst(extra.operand);
-    const local = try o.allocLocal(inst_ty, .Const);
-    const writer = o.writer();
+fn airAtomicRmw(f: *Function, inst: Air.Inst.Index) !CValue {
+    const pl_op = f.air.instructions.items(.data)[inst].pl_op;
+    const extra = f.air.extraData(Air.AtomicRmw, pl_op.payload).data;
+    const inst_ty = f.air.typeOfIndex(inst);
+    const ptr = try f.resolveInst(pl_op.operand);
+    const operand = try f.resolveInst(extra.operand);
+    const local = try f.allocLocal(inst_ty, .Const);
+    const writer = f.object.writer();
 
     try writer.print(" = zig_atomicrmw_{s}(", .{toAtomicRmwSuffix(extra.op())});
-    try o.writeCValue(writer, ptr);
+    try f.writeCValue(writer, ptr);
     try writer.writeAll(", ");
-    try o.writeCValue(writer, operand);
+    try f.writeCValue(writer, operand);
     try writer.writeAll(", ");
     try writeMemoryOrder(writer, extra.ordering());
     try writer.writeAll(");\n");
@@ -1964,15 +2001,15 @@ fn airAtomicRmw(o: *Object, inst: Air.Inst.Index) !CValue {
     return local;
 }
 
-fn airAtomicLoad(o: *Object, inst: Air.Inst.Index) !CValue {
-    const atomic_load = o.air.instructions.items(.data)[inst].atomic_load;
-    const inst_ty = o.air.typeOfIndex(inst);
-    const ptr = try o.resolveInst(atomic_load.ptr);
-    const local = try o.allocLocal(inst_ty, .Const);
-    const writer = o.writer();
+fn airAtomicLoad(f: *Function, inst: Air.Inst.Index) !CValue {
+    const atomic_load = f.air.instructions.items(.data)[inst].atomic_load;
+    const inst_ty = f.air.typeOfIndex(inst);
+    const ptr = try f.resolveInst(atomic_load.ptr);
+    const local = try f.allocLocal(inst_ty, .Const);
+    const writer = f.object.writer();
 
     try writer.writeAll(" = zig_atomic_load(");
-    try o.writeCValue(writer, ptr);
+    try f.writeCValue(writer, ptr);
     try writer.writeAll(", ");
     try writeMemoryOrder(writer, atomic_load.order);
     try writer.writeAll(");\n");
@@ -1980,18 +2017,18 @@ fn airAtomicLoad(o: *Object, inst: Air.Inst.Index) !CValue {
     return local;
 }
 
-fn airAtomicStore(o: *Object, inst: Air.Inst.Index, order: [*:0]const u8) !CValue {
-    const bin_op = o.air.instructions.items(.data)[inst].bin_op;
-    const ptr = try o.resolveInst(bin_op.lhs);
-    const element = try o.resolveInst(bin_op.rhs);
-    const inst_ty = o.air.typeOfIndex(inst);
-    const local = try o.allocLocal(inst_ty, .Const);
-    const writer = o.writer();
+fn airAtomicStore(f: *Function, inst: Air.Inst.Index, order: [*:0]const u8) !CValue {
+    const bin_op = f.air.instructions.items(.data)[inst].bin_op;
+    const ptr = try f.resolveInst(bin_op.lhs);
+    const element = try f.resolveInst(bin_op.rhs);
+    const inst_ty = f.air.typeOfIndex(inst);
+    const local = try f.allocLocal(inst_ty, .Const);
+    const writer = f.object.writer();
 
     try writer.writeAll(" = zig_atomic_store(");
-    try o.writeCValue(writer, ptr);
+    try f.writeCValue(writer, ptr);
     try writer.writeAll(", ");
-    try o.writeCValue(writer, element);
+    try f.writeCValue(writer, element);
     try writer.print(", {s});\n", .{order});
 
     return local;
src/link/C.zig
@@ -21,30 +21,34 @@ base: link.File,
 /// This linker backend does not try to incrementally link output C source code.
 /// 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, void) = .{},
+decl_table: std.AutoArrayHashMapUnmanaged(*const Module.Decl, 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. For functions this is the body, and
 /// the forward declaration is stored in the FnBlock.
-pub const DeclBlock = struct {
-    code: std.ArrayListUnmanaged(u8),
-
-    pub const empty: DeclBlock = .{
-        .code = .{},
-    };
-};
-
-/// Per-function data.
-pub const FnBlock = struct {
-    fwd_decl: std.ArrayListUnmanaged(u8),
-    typedefs: codegen.TypedefMap.Unmanaged,
-
-    pub const empty: FnBlock = .{
-        .fwd_decl = .{},
-        .typedefs = .{},
-    };
+const DeclBlock = struct {
+    code: std.ArrayListUnmanaged(u8) = .{},
+    fwd_decl: std.ArrayListUnmanaged(u8) = .{},
+    /// 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 = .{},
+
+    fn deinit(db: *DeclBlock, gpa: *Allocator) void {
+        db.code.deinit(gpa);
+        db.fwd_decl.deinit(gpa);
+        for (db.typedefs.values()) |typedef| {
+            gpa.free(typedef.rendered);
+        }
+        db.typedefs.deinit(gpa);
+        db.* = undefined;
+    }
 };
 
-pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C {
+pub fn openPath(gpa: *Allocator, sub_path: []const u8, options: link.Options) !*C {
     assert(options.object_format == .c);
 
     if (options.use_llvm) return error.LLVMHasNoCBackend;
@@ -57,15 +61,16 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
     });
     errdefer file.close();
 
-    var c_file = try allocator.create(C);
-    errdefer allocator.destroy(c_file);
+    var c_file = try gpa.create(C);
+    errdefer gpa.destroy(c_file);
 
     c_file.* = C{
+        .arena = std.heap.ArenaAllocator.init(gpa),
         .base = .{
             .tag = .c,
             .options = options,
             .file = file,
-            .allocator = allocator,
+            .allocator = gpa,
         },
     };
 
@@ -73,38 +78,105 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
 }
 
 pub fn deinit(self: *C) void {
-    for (self.decl_table.keys()) |key| {
-        deinitDecl(self.base.allocator, key);
+    const gpa = self.base.allocator;
+
+    for (self.decl_table.values()) |*db| {
+        db.deinit(gpa);
     }
-    self.decl_table.deinit(self.base.allocator);
-}
+    self.decl_table.deinit(gpa);
 
-pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {
-    _ = self;
-    _ = decl;
+    self.arena.deinit();
 }
 
 pub fn freeDecl(self: *C, decl: *Module.Decl) void {
-    _ = self.decl_table.swapRemove(decl);
-    deinitDecl(self.base.allocator, decl);
+    const gpa = self.base.allocator;
+    if (self.decl_table.fetchSwapRemove(decl)) |*kv| {
+        kv.value.deinit(gpa);
+    }
 }
 
-fn deinitDecl(gpa: *Allocator, decl: *Module.Decl) void {
-    decl.link.c.code.deinit(gpa);
-    decl.fn_link.c.fwd_decl.deinit(gpa);
-    for (decl.fn_link.c.typedefs.values()) |value| {
-        gpa.free(value.rendered);
+pub fn updateFunc(self: *C, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const decl = func.owner_decl;
+    const gop = try self.decl_table.getOrPut(self.base.allocator, decl);
+    if (!gop.found_existing) {
+        gop.value_ptr.* = .{};
+    }
+    const fwd_decl = &gop.value_ptr.fwd_decl;
+    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);
+        }
+    }
+    typedefs.clearRetainingCapacity();
+    code.shrinkRetainingCapacity(0);
+
+    var function: codegen.Function = .{
+        .value_map = codegen.CValueMap.init(module.gpa),
+        .air = air,
+        .liveness = liveness,
+        .func = func,
+        .object = .{
+            .dg = .{
+                .gpa = module.gpa,
+                .module = module,
+                .error_msg = null,
+                .decl = decl,
+                .fwd_decl = fwd_decl.toManaged(module.gpa),
+                .typedefs = typedefs.promote(module.gpa),
+                .typedefs_arena = &self.arena.allocator,
+            },
+            .code = code.toManaged(module.gpa),
+            .indent_writer = undefined, // set later so we can get a pointer to object.code
+        },
+    };
+
+    function.object.indent_writer = .{ .underlying_writer = function.object.code.writer() };
+    defer {
+        function.value_map.deinit();
+        function.blocks.deinit(module.gpa);
+        function.object.code.deinit();
+        function.object.dg.fwd_decl.deinit();
+        for (function.object.dg.typedefs.values()) |value| {
+            module.gpa.free(value.rendered);
+        }
+        function.object.dg.typedefs.deinit();
     }
-    decl.fn_link.c.typedefs.deinit(gpa);
+
+    codegen.genFunc(&function) catch |err| switch (err) {
+        error.AnalysisFail => {
+            try module.failed_decls.put(module.gpa, decl, function.object.dg.error_msg.?);
+            return;
+        },
+        else => |e| return e,
+    };
+
+    fwd_decl.* = function.object.dg.fwd_decl.moveToUnmanaged();
+    typedefs.* = function.object.dg.typedefs.unmanaged;
+    function.object.dg.typedefs.unmanaged = .{};
+    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);
 }
 
-pub fn finishUpdateDecl(self: *C, module: *Module, decl: *Module.Decl, air: Air, liveness: Liveness) !void {
-    // Keep track of all decls so we can iterate over them on flush().
-    _ = try self.decl_table.getOrPut(self.base.allocator, decl);
+pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
 
-    const fwd_decl = &decl.fn_link.c.fwd_decl;
-    const typedefs = &decl.fn_link.c.typedefs;
-    const code = &decl.link.c.code;
+    const gop = try self.decl_table.getOrPut(self.base.allocator, decl);
+    if (!gop.found_existing) {
+        gop.value_ptr.* = .{};
+    }
+    const fwd_decl = &gop.value_ptr.fwd_decl;
+    const typedefs = &gop.value_ptr.typedefs;
+    const code = &gop.value_ptr.code;
     fwd_decl.shrinkRetainingCapacity(0);
     {
         for (typedefs.values()) |value| {
@@ -116,23 +188,19 @@ pub fn finishUpdateDecl(self: *C, module: *Module, decl: *Module.Decl, air: Air,
 
     var object: codegen.Object = .{
         .dg = .{
+            .gpa = module.gpa,
             .module = module,
             .error_msg = null,
             .decl = decl,
             .fwd_decl = fwd_decl.toManaged(module.gpa),
             .typedefs = typedefs.promote(module.gpa),
+            .typedefs_arena = &self.arena.allocator,
         },
-        .gpa = module.gpa,
         .code = code.toManaged(module.gpa),
-        .value_map = codegen.CValueMap.init(module.gpa),
         .indent_writer = undefined, // set later so we can get a pointer to object.code
-        .air = air,
-        .liveness = liveness,
     };
     object.indent_writer = .{ .underlying_writer = object.code.writer() };
     defer {
-        object.value_map.deinit();
-        object.blocks.deinit(module.gpa);
         object.code.deinit();
         object.dg.fwd_decl.deinit();
         for (object.dg.typedefs.values()) |value| {
@@ -159,24 +227,12 @@ pub fn finishUpdateDecl(self: *C, module: *Module, decl: *Module.Decl, air: Air,
     code.shrinkAndFree(module.gpa, code.items.len);
 }
 
-pub fn updateFunc(self: *C, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    return self.finishUpdateDecl(module, func.owner_decl, air, liveness);
-}
-
-pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    return self.finishUpdateDecl(module, decl, undefined, undefined);
-}
-
 pub fn updateDeclLineNumber(self: *C, module: *Module, decl: *Module.Decl) !void {
     // The C backend does not have the ability to fix line numbers without re-generating
     // the entire Decl.
-    return self.updateDecl(module, decl);
+    _ = self;
+    _ = module;
+    _ = decl;
 }
 
 pub fn flush(self: *C, comp: *Compilation) !void {
@@ -223,32 +279,42 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
     var typedefs = std.HashMap(Type, void, Type.HashContext64, std.hash_map.default_max_load_percentage).init(comp.gpa);
     defer typedefs.deinit();
 
-    // Typedefs, forward decls and non-functions first.
+    // Typedefs, forward decls, and non-functions first.
     // TODO: performance investigation: would keeping a list of Decls that we should
     // generate, rather than querying here, be faster?
-    for (self.decl_table.keys()) |decl| {
-        if (!decl.has_tv) continue;
-        const buf = buf: {
-            if (decl.val.castTag(.function)) |_| {
-                try typedefs.ensureUnusedCapacity(@intCast(u32, decl.fn_link.c.typedefs.count()));
-                var it = decl.fn_link.c.typedefs.iterator();
-                while (it.next()) |new| {
-                    const gop = typedefs.getOrPutAssumeCapacity(new.key_ptr.*);
-                    if (!gop.found_existing) {
-                        try err_typedef_writer.writeAll(new.value_ptr.rendered);
-                    }
+    const decl_keys = self.decl_table.keys();
+    const decl_values = self.decl_table.values();
+    for (decl_keys) |decl, i| {
+        if (!decl.has_tv) continue; // TODO do we really need this branch?
+
+        const decl_block = &decl_values[i];
+
+        if (decl_block.fwd_decl.items.len != 0) {
+            try typedefs.ensureUnusedCapacity(@intCast(u32, decl_block.typedefs.count()));
+            var it = decl_block.typedefs.iterator();
+            while (it.next()) |new| {
+                const gop = typedefs.getOrPutAssumeCapacity(new.key_ptr.*);
+                if (!gop.found_existing) {
+                    try err_typedef_writer.writeAll(new.value_ptr.rendered);
                 }
-                fn_count += 1;
-                break :buf decl.fn_link.c.fwd_decl.items;
-            } else {
-                break :buf decl.link.c.code.items;
             }
-        };
-        all_buffers.appendAssumeCapacity(.{
-            .iov_base = buf.ptr,
-            .iov_len = buf.len,
-        });
-        file_size += buf.len;
+            const buf = decl_block.fwd_decl.items;
+            all_buffers.appendAssumeCapacity(.{
+                .iov_base = buf.ptr,
+                .iov_len = buf.len,
+            });
+            file_size += buf.len;
+        }
+        if (decl.getFunction() != null) {
+            fn_count += 1;
+        } else if (decl_block.code.items.len != 0) {
+            const buf = decl_block.code.items;
+            all_buffers.appendAssumeCapacity(.{
+                .iov_base = buf.ptr,
+                .iov_len = buf.len,
+            });
+            file_size += buf.len;
+        }
     }
 
     err_typedef_item.* = .{
@@ -259,15 +325,17 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
 
     // Now the function bodies.
     try all_buffers.ensureUnusedCapacity(fn_count);
-    for (self.decl_table.keys()) |decl| {
-        if (!decl.has_tv) continue;
-        if (decl.val.castTag(.function)) |_| {
-            const buf = decl.link.c.code.items;
-            all_buffers.appendAssumeCapacity(.{
-                .iov_base = buf.ptr,
-                .iov_len = buf.len,
-            });
-            file_size += buf.len;
+    for (decl_keys) |decl, i| {
+        if (decl.getFunction() != null) {
+            const decl_block = &decl_values[i];
+            const buf = decl_block.code.items;
+            if (buf.len != 0) {
+                all_buffers.appendAssumeCapacity(.{
+                    .iov_base = buf.ptr,
+                    .iov_len = buf.len,
+                });
+                file_size += buf.len;
+            }
         }
     }
 
src/Compilation.zig
@@ -2145,7 +2145,11 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
                 const module = self.bin_file.options.module.?;
                 const decl = func.owner_decl;
 
-                var air = module.analyzeFnBody(decl, func) catch |err| switch (err) {
+                var tmp_arena = std.heap.ArenaAllocator.init(gpa);
+                defer tmp_arena.deinit();
+                const sema_arena = &tmp_arena.allocator;
+
+                var air = module.analyzeFnBody(decl, func, sema_arena) catch |err| switch (err) {
                     error.AnalysisFail => {
                         assert(func.state != .in_progress);
                         continue;
@@ -2207,16 +2211,20 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
                 const decl_emit_h = decl.getEmitH(module);
                 const fwd_decl = &decl_emit_h.fwd_decl;
                 fwd_decl.shrinkRetainingCapacity(0);
+                var typedefs_arena = std.heap.ArenaAllocator.init(gpa);
+                defer typedefs_arena.deinit();
 
                 var dg: c_codegen.DeclGen = .{
+                    .gpa = gpa,
                     .module = module,
                     .error_msg = null,
                     .decl = decl,
                     .fwd_decl = fwd_decl.toManaged(gpa),
-                    // we don't want to emit optionals and error unions to headers since they have no ABI
-                    .typedefs = undefined,
+                    .typedefs = c_codegen.TypedefMap.init(gpa),
+                    .typedefs_arena = &typedefs_arena.allocator,
                 };
                 defer dg.fwd_decl.deinit();
+                defer dg.typedefs.deinit();
 
                 c_codegen.genHeader(&dg) catch |err| switch (err) {
                     error.AnalysisFail => {
src/link.zig
@@ -149,7 +149,7 @@ pub const File = struct {
         coff: Coff.TextBlock,
         macho: MachO.TextBlock,
         plan9: Plan9.DeclBlock,
-        c: C.DeclBlock,
+        c: void,
         wasm: Wasm.DeclBlock,
         spirv: void,
     };
@@ -159,7 +159,7 @@ pub const File = struct {
         coff: Coff.SrcFn,
         macho: MachO.SrcFn,
         plan9: void,
-        c: C.FnBlock,
+        c: void,
         wasm: Wasm.FnData,
         spirv: SpirV.FnData,
     };
@@ -372,16 +372,18 @@ pub const File = struct {
 
     /// Must be called before any call to updateDecl or updateDeclExports for
     /// any given Decl.
+    /// TODO we're transitioning to deleting this function and instead having
+    /// each linker backend notice the first time updateDecl or updateFunc is called, or
+    /// a callee referenced from AIR.
     pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void {
         log.debug("allocateDeclIndexes {*} ({s})", .{ decl, decl.name });
         switch (base.tag) {
             .coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl),
             .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
             .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
-            .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl),
             .wasm => return @fieldParentPtr(Wasm, "base", base).allocateDeclIndexes(decl),
             .plan9 => return @fieldParentPtr(Plan9, "base", base).allocateDeclIndexes(decl),
-            .spirv => {},
+            .c, .spirv => {},
         }
     }
 
src/Module.zig
@@ -610,7 +610,7 @@ pub const Decl = struct {
 
     /// If the Decl has a value and it is a function, return it,
     /// otherwise null.
-    pub fn getFunction(decl: *Decl) ?*Fn {
+    pub fn getFunction(decl: *const Decl) ?*Fn {
         if (!decl.owns_tv) return null;
         const func = (decl.val.castTag(.function) orelse return null).data;
         assert(func.owner_decl == decl);
@@ -3789,7 +3789,7 @@ pub fn clearDecl(
                 .elf => .{ .elf = link.File.Elf.TextBlock.empty },
                 .macho => .{ .macho = link.File.MachO.TextBlock.empty },
                 .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty },
-                .c => .{ .c = link.File.C.DeclBlock.empty },
+                .c => .{ .c = {} },
                 .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty },
                 .spirv => .{ .spirv = {} },
             };
@@ -3798,7 +3798,7 @@ pub fn clearDecl(
                 .elf => .{ .elf = link.File.Elf.SrcFn.empty },
                 .macho => .{ .macho = link.File.MachO.SrcFn.empty },
                 .plan9 => .{ .plan9 = {} },
-                .c => .{ .c = link.File.C.FnBlock.empty },
+                .c => .{ .c = {} },
                 .wasm => .{ .wasm = link.File.Wasm.FnData.empty },
                 .spirv => .{ .spirv = .{} },
             };
@@ -3828,10 +3828,13 @@ pub fn deleteUnusedDecl(mod: *Module, decl: *Decl) void {
     // about the Decl in the first place.
     // Until then, we did call `allocateDeclIndexes` on this anonymous Decl and so we
     // must call `freeDecl` in the linker backend now.
-    if (decl.has_tv) {
-        if (decl.ty.hasCodeGenBits()) {
-            mod.comp.bin_file.freeDecl(decl);
-        }
+    switch (mod.comp.bin_file.tag) {
+        .c => {}, // this linker backend has already migrated to the new API
+        else => if (decl.has_tv) {
+            if (decl.ty.hasCodeGenBits()) {
+                mod.comp.bin_file.freeDecl(decl);
+            }
+        },
     }
 
     const dependants = decl.dependants.keys();
@@ -3893,22 +3896,16 @@ fn deleteDeclExports(mod: *Module, decl: *Decl) void {
     mod.gpa.free(kv.value);
 }
 
-pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air {
+pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn, arena: *Allocator) SemaError!Air {
     const tracy = trace(@src());
     defer tracy.end();
 
     const gpa = mod.gpa;
 
-    // Use the Decl's arena for function memory.
-    var arena = decl.value_arena.?.promote(gpa);
-    defer decl.value_arena.?.* = arena.state;
-
-    const fn_ty = decl.ty;
-
     var sema: Sema = .{
         .mod = mod,
         .gpa = gpa,
-        .arena = &arena.allocator,
+        .arena = arena,
         .code = decl.namespace.file_scope.zir,
         .owner_decl = decl,
         .namespace = decl.namespace,
@@ -3942,6 +3939,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air {
     // This could be a generic function instantiation, however, in which case we need to
     // map the comptime parameters to constant values and only emit arg AIR instructions
     // for the runtime ones.
+    const fn_ty = decl.ty;
     const runtime_params_len = @intCast(u32, fn_ty.fnParamLen());
     try inner_block.instructions.ensureTotalCapacity(gpa, runtime_params_len);
     try sema.air_instructions.ensureUnusedCapacity(gpa, fn_info.total_params_len * 2); // * 2 for the `addType`
@@ -4072,7 +4070,7 @@ pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast.
             .elf => .{ .elf = link.File.Elf.TextBlock.empty },
             .macho => .{ .macho = link.File.MachO.TextBlock.empty },
             .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty },
-            .c => .{ .c = link.File.C.DeclBlock.empty },
+            .c => .{ .c = {} },
             .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty },
             .spirv => .{ .spirv = {} },
         },
@@ -4081,7 +4079,7 @@ pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast.
             .elf => .{ .elf = link.File.Elf.SrcFn.empty },
             .macho => .{ .macho = link.File.MachO.SrcFn.empty },
             .plan9 => .{ .plan9 = {} },
-            .c => .{ .c = link.File.C.FnBlock.empty },
+            .c => .{ .c = {} },
             .wasm => .{ .wasm = link.File.Wasm.FnData.empty },
             .spirv => .{ .spirv = .{} },
         },
src/Sema.zig
@@ -2999,6 +2999,8 @@ fn analyzeCall(
 
                 // TODO: check whether any external comptime memory was mutated by the
                 // comptime function call. If so, then do not memoize the call here.
+                // TODO: re-evaluate whether memoized_calls needs its own arena. I think
+                // it should be fine to use the Decl arena for the function.
                 {
                     var arena_allocator = std.heap.ArenaAllocator.init(gpa);
                     errdefer arena_allocator.deinit();
@@ -3009,7 +3011,7 @@ fn analyzeCall(
                     }
 
                     try mod.memoized_calls.put(gpa, memoized_call_key, .{
-                        .val = result_val,
+                        .val = try result_val.copy(arena),
                         .arena = arena_allocator.state,
                     });
                     delete_memoized_call_key = false;
@@ -5876,10 +5878,7 @@ fn zirArrayCat(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
                 else
                     try Type.Tag.array.create(anon_decl.arena(), .{ .len = final_len, .elem_type = lhs_info.elem_type });
                 const val = try Value.Tag.array.create(anon_decl.arena(), buf);
-                return sema.analyzeDeclRef(try anon_decl.finish(
-                    ty,
-                    val,
-                ));
+                return sema.analyzeDeclRef(try anon_decl.finish(ty, val));
             }
             return sema.mod.fail(&block.base, lhs_src, "TODO array_cat more types of Values", .{});
         } else {
@@ -5941,10 +5940,7 @@ fn zirArrayMul(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
                 }
             }
             const val = try Value.Tag.array.create(anon_decl.arena(), buf);
-            return sema.analyzeDeclRef(try anon_decl.finish(
-                final_ty,
-                val,
-            ));
+            return sema.analyzeDeclRef(try anon_decl.finish(final_ty, val));
         }
         return sema.mod.fail(&block.base, lhs_src, "TODO array_mul more types of Values", .{});
     }
@@ -9979,7 +9975,7 @@ fn analyzeRef(
         var anon_decl = try block.startAnonDecl();
         defer anon_decl.deinit();
         return sema.analyzeDeclRef(try anon_decl.finish(
-            operand_ty,
+            try operand_ty.copy(anon_decl.arena()),
             try val.copy(anon_decl.arena()),
         ));
     }
src/type.zig
@@ -1366,10 +1366,6 @@ pub const Type = extern union {
             .f128,
             .bool,
             .anyerror,
-            .fn_noreturn_no_args,
-            .fn_void_no_args,
-            .fn_naked_noreturn_no_args,
-            .fn_ccc_void_no_args,
             .single_const_pointer_to_comptime_int,
             .const_slice_u8,
             .array_u8_sentinel_0,
@@ -1397,6 +1393,12 @@ pub const Type = extern union {
 
             .function => !self.castTag(.function).?.data.is_generic,
 
+            .fn_noreturn_no_args,
+            .fn_void_no_args,
+            .fn_naked_noreturn_no_args,
+            .fn_ccc_void_no_args,
+            => true,
+
             .@"struct" => {
                 // TODO introduce lazy value mechanism
                 const struct_obj = self.castTag(.@"struct").?.data;