Commit 7b8cede61f

Andrew Kelley <andrew@ziglang.org>
2021-01-05 19:08:34
stage2: rework the C backend
* std.ArrayList gains `moveToUnmanaged` and dead code `ArrayListUnmanaged.appendWrite` is deleted. * emit_h state is attached to Module rather than Compilation. * remove the implementation of emit-h because it did not properly integrate with incremental compilation. I will re-implement it in a follow-up commit. * Compilation: use the .codegen_failure tag rather than .dependency_failure tag for when `bin_file.updateDecl` fails. C backend: * Use a CValue tagged union instead of strings for C values. * Cleanly separate state into Object and DeclGen: - Object is present only when generating a .c file - DeclGen is present for both generating a .c and .h * Move some functions into their respective Object/DeclGen namespace. * Forward decls are managed by the incremental compilation frontend; C backend no longer renders function signatures based on callsites. For simplicity, all functions always get forward decls. * Constants are managed by the incremental compilation frontend. C backend no longer has a "constants" section. * Participate in incremental compilation. Each Decl gets an ArrayList for its generated C code and it is updated when the Decl is updated. During flush(), all these are joined together in the output file. * The new CValue tagged union is used to clean up using of assigning to locals without an additional pointer local. * Fix bug with bitcast of non-pointers making the memcpy destination immutable.
1 parent 9360e58
Changed files (9)
lib/std/array_list.zig
@@ -100,10 +100,20 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
 
         /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields
         /// of this ArrayList. This ArrayList retains ownership of underlying memory.
+        /// Deprecated: use `moveToUnmanaged` which has different semantics.
         pub fn toUnmanaged(self: Self) ArrayListAlignedUnmanaged(T, alignment) {
             return .{ .items = self.items, .capacity = self.capacity };
         }
 
+        /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields
+        /// of this ArrayList. Empties this ArrayList.
+        pub fn moveToUnmanaged(self: *Self) ArrayListAlignedUnmanaged(T, alignment) {
+            const allocator = self.allocator;
+            const result = .{ .items = self.items, .capacity = self.capacity };
+            self.* = init(allocator);
+            return result;
+        }
+
         /// The caller owns the returned memory. Empties this ArrayList.
         pub fn toOwnedSlice(self: *Self) Slice {
             const allocator = self.allocator;
@@ -551,14 +561,6 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
             mem.copy(T, self.items[oldlen..], items);
         }
 
-        /// Same as `append` except it returns the number of bytes written, which is always the same
-        /// as `m.len`. The purpose of this function existing is to match `std.io.OutStream` API.
-        /// This function may be called only when `T` is `u8`.
-        fn appendWrite(self: *Self, allocator: *Allocator, m: []const u8) !usize {
-            try self.appendSlice(allocator, m);
-            return m.len;
-        }
-
         /// Append a value to the list `n` times.
         /// Allocates more memory as necessary.
         pub fn appendNTimes(self: *Self, allocator: *Allocator, value: T, n: usize) !void {
@@ -1129,13 +1131,13 @@ test "std.ArrayList/ArrayListUnmanaged: ArrayList(T) of struct T" {
     }
 }
 
-test "std.ArrayList(u8) implements outStream" {
+test "std.ArrayList(u8) implements writer" {
     var buffer = ArrayList(u8).init(std.testing.allocator);
     defer buffer.deinit();
 
     const x: i32 = 42;
     const y: i32 = 1234;
-    try buffer.outStream().print("x: {}\ny: {}\n", .{ x, y });
+    try buffer.writer().print("x: {}\ny: {}\n", .{ x, y });
 
     testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", buffer.items);
 }
src/codegen/c.zig
@@ -1,495 +1,526 @@
 const std = @import("std");
+const mem = std.mem;
+const log = std.log.scoped(.c);
+const Writer = std.ArrayList(u8).Writer;
 
 const link = @import("../link.zig");
 const Module = @import("../Module.zig");
 const Compilation = @import("../Compilation.zig");
-
 const Inst = @import("../ir.zig").Inst;
 const Value = @import("../value.zig").Value;
 const Type = @import("../type.zig").Type;
-
 const C = link.File.C;
 const Decl = Module.Decl;
-const mem = std.mem;
-const log = std.log.scoped(.c);
+const trace = @import("../tracy.zig").trace;
 
-const Writer = std.ArrayList(u8).Writer;
+const Mutability = enum { Const, Mut };
 
-/// Maps a name from Zig source to C. Currently, this will always give the same
-/// output for any given input, sometimes resulting in broken identifiers.
-fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 {
-    return allocator.dupe(u8, name);
-}
+pub const CValue = union(enum) {
+    none: void,
+    /// Index into local_names
+    local: usize,
+    /// Index into local_names, but take the address.
+    local_ref: usize,
+    /// A constant instruction, to be rendered inline.
+    constant: *Inst,
+    /// Index into the parameters
+    arg: usize,
+    /// By-value
+    decl: *Decl,
 
-const Mutability = enum { Const, Mut };
+    pub fn printed(value: CValue, object: *Object) Printed {
+        return .{
+            .value = value,
+            .object = object,
+        };
+    }
+
+    pub const Printed = struct {
+        value: CValue,
+        object: *Object,
+
+        /// TODO this got unwieldly, I want to remove the ability to print this way
+        pub fn format(
+            self: Printed,
+            comptime fmt: []const u8,
+            options: std.fmt.FormatOptions,
+            writer: anytype,
+        ) error{OutOfMemory}!void {
+            if (fmt.len != 0) @compileError("Unknown format string: '" ++ fmt ++ "'");
+            switch (self.value) {
+                .none => unreachable,
+                .local => |i| return std.fmt.format(writer, "t{d}", .{i}),
+                .local_ref => |i| return std.fmt.format(writer, "&t{d}", .{i}),
+                .constant => |inst| {
+                    const o = self.object;
+                    o.dg.renderValue(writer, inst.ty, inst.value().?) catch |err| switch (err) {
+                        error.OutOfMemory => return error.OutOfMemory,
+                        error.AnalysisFail => return,
+                    };
+                },
+                .arg => |i| return std.fmt.format(writer, "a{d}", .{i}),
+                .decl => |decl| return writer.writeAll(mem.span(decl.name)),
+            }
+        }
+    };
+};
 
-fn renderTypeAndName(
-    ctx: *Context,
-    writer: Writer,
-    ty: Type,
-    name: []const u8,
-    mutability: Mutability,
-) error{ OutOfMemory, AnalysisFail }!void {
-    var suffix = std.ArrayList(u8).init(&ctx.arena.allocator);
-
-    var render_ty = ty;
-    while (render_ty.zigTypeTag() == .Array) {
-        const sentinel_bit = @boolToInt(render_ty.sentinel() != null);
-        const c_len = render_ty.arrayLen() + sentinel_bit;
-        try suffix.writer().print("[{d}]", .{c_len});
-        render_ty = render_ty.elemType();
+pub const CValueMap = std.AutoHashMap(*Inst, CValue);
+
+/// 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,
+    gpa: *mem.Allocator,
+    code: std.ArrayList(u8),
+    value_map: CValueMap,
+    next_arg_index: usize = 0,
+    next_local_index: usize = 0,
+
+    fn resolveInst(o: *Object, inst: *Inst) !CValue {
+        if (inst.value()) |_| {
+            return CValue{ .constant = inst };
+        }
+        return o.value_map.get(inst).?; // Instruction does not dominate all uses!
     }
 
-    try renderType(ctx, writer, render_ty);
+    fn allocLocalValue(o: *Object) CValue {
+        const result = o.next_local_index;
+        o.next_local_index += 1;
+        return .{ .local = result };
+    }
 
-    const const_prefix = switch (mutability) {
-        .Const => "const ",
-        .Mut => "",
-    };
-    try writer.print(" {s}{s}{s}", .{ const_prefix, name, suffix.items });
-}
+    fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue {
+        const local_value = o.allocLocalValue();
+        try o.renderTypeAndName(o.code.writer(), ty, local_value, mutability);
+        return local_value;
+    }
 
-fn renderType(
-    ctx: *Context,
-    writer: Writer,
-    t: Type,
-) error{ OutOfMemory, AnalysisFail }!void {
-    switch (t.zigTypeTag()) {
-        .NoReturn => {
-            try writer.writeAll("zig_noreturn void");
-        },
-        .Void => try writer.writeAll("void"),
-        .Bool => try writer.writeAll("bool"),
-        .Int => {
-            switch (t.tag()) {
-                .u8 => try writer.writeAll("uint8_t"),
-                .i8 => try writer.writeAll("int8_t"),
-                .u16 => try writer.writeAll("uint16_t"),
-                .i16 => try writer.writeAll("int16_t"),
-                .u32 => try writer.writeAll("uint32_t"),
-                .i32 => try writer.writeAll("int32_t"),
-                .u64 => try writer.writeAll("uint64_t"),
-                .i64 => try writer.writeAll("int64_t"),
-                .usize => try writer.writeAll("uintptr_t"),
-                .isize => try writer.writeAll("intptr_t"),
-                .c_short => try writer.writeAll("short"),
-                .c_ushort => try writer.writeAll("unsigned short"),
-                .c_int => try writer.writeAll("int"),
-                .c_uint => try writer.writeAll("unsigned int"),
-                .c_long => try writer.writeAll("long"),
-                .c_ulong => try writer.writeAll("unsigned long"),
-                .c_longlong => try writer.writeAll("long long"),
-                .c_ulonglong => try writer.writeAll("unsigned long long"),
-                .int_signed, .int_unsigned => {
-                    const info = t.intInfo(ctx.target);
-                    const sign_prefix = switch (info.signedness) {
-                        .signed => "i",
-                        .unsigned => "",
-                    };
-                    inline for (.{ 8, 16, 32, 64, 128 }) |nbits| {
-                        if (info.bits <= nbits) {
-                            try writer.print("{s}int{d}_t", .{ sign_prefix, nbits });
-                            break;
-                        }
+    fn indent(o: *Object) !void {
+        const indent_size = 4;
+        const indent_level = 1;
+        const indent_amt = indent_size * indent_level;
+        try o.code.writer().writeByteNTimes(' ', indent_amt);
+    }
+
+    fn renderTypeAndName(
+        o: *Object,
+        writer: Writer,
+        ty: Type,
+        name: CValue,
+        mutability: Mutability,
+    ) error{ OutOfMemory, AnalysisFail }!void {
+        var suffix = std.ArrayList(u8).init(o.gpa);
+        defer suffix.deinit();
+
+        var render_ty = ty;
+        while (render_ty.zigTypeTag() == .Array) {
+            const sentinel_bit = @boolToInt(render_ty.sentinel() != null);
+            const c_len = render_ty.arrayLen() + sentinel_bit;
+            try suffix.writer().print("[{d}]", .{c_len});
+            render_ty = render_ty.elemType();
+        }
+
+        try o.dg.renderType(writer, render_ty);
+
+        const const_prefix = switch (mutability) {
+            .Const => "const ",
+            .Mut => "",
+        };
+        try writer.print(" {s}{}{s}", .{ const_prefix, name.printed(o), suffix.items });
+    }
+};
+
+/// This data is available both when outputting .c code and when outputting an .h file.
+const DeclGen = struct {
+    module: *Module,
+    decl: *Decl,
+    fwd_decl: std.ArrayList(u8),
+    error_msg: ?*Compilation.ErrorMsg,
+
+    fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
+        dg.error_msg = try Compilation.ErrorMsg.create(dg.module.gpa, src, format, args);
+        return error.AnalysisFail;
+    }
+
+    fn renderValue(
+        dg: *DeclGen,
+        writer: Writer,
+        t: Type,
+        val: Value,
+    ) error{ OutOfMemory, AnalysisFail }!void {
+        switch (t.zigTypeTag()) {
+            .Int => {
+                if (t.isSignedInt())
+                    return writer.print("{d}", .{val.toSignedInt()});
+                return writer.print("{d}", .{val.toUnsignedInt()});
+            },
+            .Pointer => switch (val.tag()) {
+                .undef, .zero => try writer.writeAll("0"),
+                .one => try writer.writeAll("1"),
+                .decl_ref => {
+                    const decl = val.castTag(.decl_ref).?.data;
+
+                    // Determine if we must pointer cast.
+                    const decl_tv = decl.typed_value.most_recent.typed_value;
+                    if (t.eql(decl_tv.ty)) {
+                        try writer.print("&{s}", .{decl.name});
                     } else {
-                        return ctx.fail(ctx.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{});
+                        try writer.writeAll("(");
+                        try dg.renderType(writer, t);
+                        try writer.print(")&{s}", .{decl.name});
                     }
                 },
+                .function => {
+                    const func = val.castTag(.function).?.data;
+                    try writer.print("{s}", .{func.owner_decl.name});
+                },
+                .extern_fn => {
+                    const decl = val.castTag(.extern_fn).?.data;
+                    try writer.print("{s}", .{decl.name});
+                },
+                else => |e| return dg.fail(
+                    dg.decl.src(),
+                    "TODO: C backend: implement Pointer value {s}",
+                    .{@tagName(e)},
+                ),
+            },
+            .Array => {
+                // First try specific tag representations for more efficiency.
+                switch (val.tag()) {
+                    .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"),
+                    .bytes => {
+                        const bytes = val.castTag(.bytes).?.data;
+                        // TODO: make our own C string escape instead of using {Z}
+                        try writer.print("\"{Z}\"", .{bytes});
+                    },
+                    else => {
+                        // Fall back to generic implementation.
+                        var arena = std.heap.ArenaAllocator.init(dg.module.gpa);
+                        defer arena.deinit();
+
+                        try writer.writeAll("{");
+                        var index: usize = 0;
+                        const len = t.arrayLen();
+                        const elem_ty = t.elemType();
+                        while (index < len) : (index += 1) {
+                            if (index != 0) try writer.writeAll(",");
+                            const elem_val = try val.elemValue(&arena.allocator, index);
+                            try dg.renderValue(writer, elem_ty, elem_val);
+                        }
+                        if (t.sentinel()) |sentinel_val| {
+                            if (index != 0) try writer.writeAll(",");
+                            try dg.renderValue(writer, elem_ty, sentinel_val);
+                        }
+                        try writer.writeAll("}");
+                    },
+                }
+            },
+            else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{
+                @tagName(e),
+            }),
+        }
+    }
+
+    fn renderFunctionSignature(dg: *DeclGen, w: Writer) !void {
+        const tv = dg.decl.typed_value.most_recent.typed_value;
+        // Determine whether the function is globally visible.
+        const is_global = blk: {
+            switch (tv.val.tag()) {
+                .extern_fn => break :blk true,
+                .function => {
+                    const func = tv.val.castTag(.function).?.data;
+                    break :blk dg.module.decl_exports.contains(func.owner_decl);
+                },
                 else => unreachable,
             }
-        },
-        .Pointer => {
-            if (t.isSlice()) {
-                return ctx.fail(ctx.decl.src(), "TODO: C backend: implement slices", .{});
-            } else {
-                try renderType(ctx, writer, t.elemType());
-                try writer.writeAll(" *");
-                if (t.isConstPtr()) {
-                    try writer.writeAll("const ");
-                }
-                if (t.isVolatilePtr()) {
-                    try writer.writeAll("volatile ");
+        };
+        if (!is_global) {
+            try w.writeAll("static ");
+        }
+        try dg.renderType(w, tv.ty.fnReturnType());
+        const decl_name = mem.span(dg.decl.name);
+        try w.print(" {s}(", .{decl_name});
+        var param_len = tv.ty.fnParamLen();
+        if (param_len == 0)
+            try w.writeAll("void")
+        else {
+            var index: usize = 0;
+            while (index < param_len) : (index += 1) {
+                if (index > 0) {
+                    try w.writeAll(", ");
                 }
+                try dg.renderType(w, tv.ty.fnParamType(index));
+                try w.print(" a{d}", .{index});
             }
-        },
-        .Array => {
-            try renderType(ctx, writer, t.elemType());
-            try writer.writeAll(" *");
-        },
-        else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement type {s}", .{
-            @tagName(e),
-        }),
+        }
+        try w.writeByte(')');
     }
-}
 
-fn renderValue(
-    ctx: *Context,
-    writer: Writer,
-    t: Type,
-    val: Value,
-) error{ OutOfMemory, AnalysisFail }!void {
-    switch (t.zigTypeTag()) {
-        .Int => {
-            if (t.isSignedInt())
-                return writer.print("{d}", .{val.toSignedInt()});
-            return writer.print("{d}", .{val.toUnsignedInt()});
-        },
-        .Pointer => switch (val.tag()) {
-            .undef, .zero => try writer.writeAll("0"),
-            .one => try writer.writeAll("1"),
-            .decl_ref => {
-                const decl = val.castTag(.decl_ref).?.data;
-
-                // Determine if we must pointer cast.
-                const decl_tv = decl.typed_value.most_recent.typed_value;
-                if (t.eql(decl_tv.ty)) {
-                    try writer.print("&{s}", .{decl.name});
-                } else {
-                    try writer.writeAll("(");
-                    try renderType(ctx, writer, t);
-                    try writer.print(")&{s}", .{decl.name});
-                }
+    fn renderType(dg: *DeclGen, w: Writer, t: Type) error{ OutOfMemory, AnalysisFail }!void {
+        switch (t.zigTypeTag()) {
+            .NoReturn => {
+                try w.writeAll("zig_noreturn void");
             },
-            .function => {
-                const func = val.castTag(.function).?.data;
-                try writer.print("{s}", .{func.owner_decl.name});
-            },
-            .extern_fn => {
-                const decl = val.castTag(.extern_fn).?.data;
-                try writer.print("{s}", .{decl.name});
+            .Void => try w.writeAll("void"),
+            .Bool => try w.writeAll("bool"),
+            .Int => {
+                switch (t.tag()) {
+                    .u8 => try w.writeAll("uint8_t"),
+                    .i8 => try w.writeAll("int8_t"),
+                    .u16 => try w.writeAll("uint16_t"),
+                    .i16 => try w.writeAll("int16_t"),
+                    .u32 => try w.writeAll("uint32_t"),
+                    .i32 => try w.writeAll("int32_t"),
+                    .u64 => try w.writeAll("uint64_t"),
+                    .i64 => try w.writeAll("int64_t"),
+                    .usize => try w.writeAll("uintptr_t"),
+                    .isize => try w.writeAll("intptr_t"),
+                    .c_short => try w.writeAll("short"),
+                    .c_ushort => try w.writeAll("unsigned short"),
+                    .c_int => try w.writeAll("int"),
+                    .c_uint => try w.writeAll("unsigned int"),
+                    .c_long => try w.writeAll("long"),
+                    .c_ulong => try w.writeAll("unsigned long"),
+                    .c_longlong => try w.writeAll("long long"),
+                    .c_ulonglong => try w.writeAll("unsigned long long"),
+                    .int_signed, .int_unsigned => {
+                        const info = t.intInfo(dg.module.getTarget());
+                        const sign_prefix = switch (info.signedness) {
+                            .signed => "i",
+                            .unsigned => "",
+                        };
+                        inline for (.{ 8, 16, 32, 64, 128 }) |nbits| {
+                            if (info.bits <= nbits) {
+                                try w.print("{s}int{d}_t", .{ sign_prefix, nbits });
+                                break;
+                            }
+                        } else {
+                            return dg.fail(dg.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{});
+                        }
+                    },
+                    else => unreachable,
+                }
             },
-            else => |e| return ctx.fail(
-                ctx.decl.src(),
-                "TODO: C backend: implement Pointer value {s}",
-                .{@tagName(e)},
-            ),
-        },
-        .Array => {
-            // First try specific tag representations for more efficiency.
-            switch (val.tag()) {
-                .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"),
-                .bytes => {
-                    const bytes = val.castTag(.bytes).?.data;
-                    // TODO: make our own C string escape instead of using {Z}
-                    try writer.print("\"{Z}\"", .{bytes});
-                },
-                else => {
-                    // Fall back to generic implementation.
-                    try writer.writeAll("{");
-                    var index: usize = 0;
-                    const len = t.arrayLen();
-                    const elem_ty = t.elemType();
-                    while (index < len) : (index += 1) {
-                        if (index != 0) try writer.writeAll(",");
-                        const elem_val = try val.elemValue(&ctx.arena.allocator, index);
-                        try renderValue(ctx, writer, elem_ty, elem_val);
+            .Pointer => {
+                if (t.isSlice()) {
+                    return dg.fail(dg.decl.src(), "TODO: C backend: implement slices", .{});
+                } else {
+                    try dg.renderType(w, t.elemType());
+                    try w.writeAll(" *");
+                    if (t.isConstPtr()) {
+                        try w.writeAll("const ");
                     }
-                    if (t.sentinel()) |sentinel_val| {
-                        if (index != 0) try writer.writeAll(",");
-                        try renderValue(ctx, writer, elem_ty, sentinel_val);
+                    if (t.isVolatilePtr()) {
+                        try w.writeAll("volatile ");
                     }
-                    try writer.writeAll("}");
-                },
-            }
-        },
-        else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement value {s}", .{
-            @tagName(e),
-        }),
-    }
-}
-
-fn renderFunctionSignature(
-    ctx: *Context,
-    writer: Writer,
-    decl: *Decl,
-) !void {
-    const tv = decl.typed_value.most_recent.typed_value;
-    // Determine whether the function is globally visible.
-    const is_global = blk: {
-        switch (tv.val.tag()) {
-            .extern_fn => break :blk true,
-            .function => {
-                const func = tv.val.castTag(.function).?.data;
-                break :blk ctx.module.decl_exports.contains(func.owner_decl);
+                }
             },
-            else => unreachable,
-        }
-    };
-    if (!is_global) {
-        try writer.writeAll("static ");
-    }
-    try renderType(ctx, writer, tv.ty.fnReturnType());
-    // Use the child allocator directly, as we know the name can be freed before
-    // the rest of the arena.
-    const decl_name = mem.span(decl.name);
-    const name = try map(ctx.arena.child_allocator, decl_name);
-    defer ctx.arena.child_allocator.free(name);
-    try writer.print(" {s}(", .{name});
-    var param_len = tv.ty.fnParamLen();
-    if (param_len == 0)
-        try writer.writeAll("void")
-    else {
-        var index: usize = 0;
-        while (index < param_len) : (index += 1) {
-            if (index > 0) {
-                try writer.writeAll(", ");
-            }
-            try renderType(ctx, writer, tv.ty.fnParamType(index));
-            try writer.print(" arg{d}", .{index});
+            .Array => {
+                try dg.renderType(w, t.elemType());
+                try w.writeAll(" *");
+            },
+            else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{
+                @tagName(e),
+            }),
         }
     }
-    try writer.writeByte(')');
-}
+};
 
-fn indent(file: *C) !void {
-    const indent_size = 4;
-    const indent_level = 1;
-    const indent_amt = indent_size * indent_level;
-    try file.main.writer().writeByteNTimes(' ', indent_amt);
-}
+pub fn genDecl(o: *Object) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
 
-pub fn generate(file: *C, module: *Module, decl: *Decl) !void {
-    const tv = decl.typed_value.most_recent.typed_value;
-
-    var arena = std.heap.ArenaAllocator.init(file.base.allocator);
-    defer arena.deinit();
-    var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator);
-    defer inst_map.deinit();
-    var ctx = Context{
-        .decl = decl,
-        .arena = &arena,
-        .inst_map = &inst_map,
-        .target = file.base.options.target,
-        .header = &file.header,
-        .module = module,
-    };
-    defer {
-        file.error_msg = ctx.error_msg;
-        ctx.deinit();
-    }
+    const tv = o.dg.decl.typed_value.most_recent.typed_value;
 
     if (tv.val.castTag(.function)) |func_payload| {
-        const writer = file.main.writer();
-        try renderFunctionSignature(&ctx, writer, decl);
-
-        try writer.writeAll(" {");
+        const fwd_decl_writer = o.dg.fwd_decl.writer();
+        try o.dg.renderFunctionSignature(fwd_decl_writer);
+        try fwd_decl_writer.writeAll(";\n");
 
         const func: *Module.Fn = func_payload.data;
         const instructions = func.body.instructions;
-        if (instructions.len > 0) {
-            try writer.writeAll("\n");
-            for (instructions) |inst| {
-                if (switch (inst.tag) {
-                    .add => try genBinOp(&ctx, file, inst.castTag(.add).?, "+"),
-                    .alloc => try genAlloc(&ctx, file, inst.castTag(.alloc).?),
-                    .arg => try genArg(&ctx),
-                    .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?),
-                    .block => try genBlock(&ctx, file, inst.castTag(.block).?),
-                    .bitcast => try genBitcast(&ctx, file, inst.castTag(.bitcast).?),
-                    .breakpoint => try genBreakpoint(file, inst.castTag(.breakpoint).?),
-                    .call => try genCall(&ctx, file, inst.castTag(.call).?),
-                    .cmp_eq => try genBinOp(&ctx, file, inst.castTag(.cmp_eq).?, "=="),
-                    .cmp_gt => try genBinOp(&ctx, file, inst.castTag(.cmp_gt).?, ">"),
-                    .cmp_gte => try genBinOp(&ctx, file, inst.castTag(.cmp_gte).?, ">="),
-                    .cmp_lt => try genBinOp(&ctx, file, inst.castTag(.cmp_lt).?, "<"),
-                    .cmp_lte => try genBinOp(&ctx, file, inst.castTag(.cmp_lte).?, "<="),
-                    .cmp_neq => try genBinOp(&ctx, file, inst.castTag(.cmp_neq).?, "!="),
-                    .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?),
-                    .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?),
-                    .load => try genLoad(&ctx, file, inst.castTag(.load).?),
-                    .ret => try genRet(&ctx, file, inst.castTag(.ret).?),
-                    .retvoid => try genRetVoid(file),
-                    .store => try genStore(&ctx, file, inst.castTag(.store).?),
-                    .sub => try genBinOp(&ctx, file, inst.castTag(.sub).?, "-"),
-                    .unreach => try genUnreach(file, inst.castTag(.unreach).?),
-                    else => |e| return ctx.fail(decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
-                }) |name| {
-                    try ctx.inst_map.putNoClobber(inst, name);
-                }
+        const writer = o.code.writer();
+        try o.dg.renderFunctionSignature(writer);
+        if (instructions.len == 0) {
+            try writer.writeAll(" {}\n\n");
+            return;
+        }
+
+        try writer.writeAll(" {");
+
+        try writer.writeAll("\n");
+        for (instructions) |inst| {
+            const result_value = switch (inst.tag) {
+                .add => try genBinOp(o, inst.castTag(.add).?, "+"),
+                .alloc => try genAlloc(o, inst.castTag(.alloc).?),
+                .arg => genArg(o),
+                .assembly => try genAsm(o, inst.castTag(.assembly).?),
+                .block => try genBlock(o, inst.castTag(.block).?),
+                .bitcast => try genBitcast(o, inst.castTag(.bitcast).?),
+                .breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?),
+                .call => try genCall(o, inst.castTag(.call).?),
+                .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, "=="),
+                .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, ">"),
+                .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, ">="),
+                .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, "<"),
+                .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, "<="),
+                .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, "!="),
+                .dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?),
+                .intcast => try genIntCast(o, inst.castTag(.intcast).?),
+                .load => try genLoad(o, inst.castTag(.load).?),
+                .ret => try genRet(o, inst.castTag(.ret).?),
+                .retvoid => try genRetVoid(o),
+                .store => try genStore(o, inst.castTag(.store).?),
+                .sub => try genBinOp(o, inst.castTag(.sub).?, "-"),
+                .unreach => try genUnreach(o, inst.castTag(.unreach).?),
+                else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
+            };
+            switch (result_value) {
+                .none => {},
+                else => try o.value_map.putNoClobber(inst, result_value),
             }
         }
 
         try writer.writeAll("}\n\n");
     } else if (tv.val.tag() == .extern_fn) {
-        return; // handled when referenced
+        const writer = o.code.writer();
+        try o.dg.renderFunctionSignature(writer);
+        try writer.writeAll(";\n");
     } else {
-        const writer = file.constants.writer();
+        const writer = o.code.writer();
         try writer.writeAll("static ");
 
         // TODO ask the Decl if it is const
         // https://github.com/ziglang/zig/issues/7582
 
-        try renderTypeAndName(&ctx, writer, tv.ty, mem.span(decl.name), .Mut);
+        const decl_c_value: CValue = .{ .decl = o.dg.decl };
+        try o.renderTypeAndName(writer, tv.ty, decl_c_value, .Mut);
 
         try writer.writeAll(" = ");
-        try renderValue(&ctx, writer, tv.ty, tv.val);
+        try o.dg.renderValue(writer, tv.ty, tv.val);
         try writer.writeAll(";\n");
     }
 }
 
-pub fn generateHeader(
-    comp: *Compilation,
-    module: *Module,
-    header: *C.Header,
-    decl: *Decl,
-) error{ AnalysisFail, OutOfMemory }!void {
+pub fn genHeader(comp: *Compilation, dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
     switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {
         .Fn => {
-            var inst_map = std.AutoHashMap(*Inst, []u8).init(comp.gpa);
-            defer inst_map.deinit();
-
-            var arena = std.heap.ArenaAllocator.init(comp.gpa);
-            defer arena.deinit();
-
-            var ctx = Context{
-                .decl = decl,
-                .arena = &arena,
-                .inst_map = &inst_map,
-                .target = comp.getTarget(),
-                .header = header,
-                .module = module,
-            };
-            const writer = header.buf.writer();
-            renderFunctionSignature(&ctx, writer, decl) catch |err| {
-                if (err == error.AnalysisFail) {
-                    try module.failed_decls.put(module.gpa, decl, ctx.error_msg);
-                }
-                return err;
+            dg.renderFunctionSignature() catch |err| switch (err) {
+                error.AnalysisFail => {
+                    try dg.module.failed_decls.put(dg.module.gpa, decl, dg.error_msg.?);
+                    dg.error_msg = null;
+                    return error.AnalysisFail;
+                },
+                else => |e| return e,
             };
-            try writer.writeAll(";\n");
+            try dg.fwd_decl.appendSlice(";\n");
         },
         else => {},
     }
 }
 
-const Context = struct {
-    decl: *Decl,
-    inst_map: *std.AutoHashMap(*Inst, []u8),
-    arena: *std.heap.ArenaAllocator,
-    argdex: usize = 0,
-    unnamed_index: usize = 0,
-    error_msg: *Compilation.ErrorMsg = undefined,
-    target: std.Target,
-    header: *C.Header,
-    module: *Module,
-
-    fn resolveInst(self: *Context, inst: *Inst) ![]u8 {
-        if (inst.value()) |val| {
-            var out = std.ArrayList(u8).init(&self.arena.allocator);
-            try renderValue(self, out.writer(), inst.ty, val);
-            return out.toOwnedSlice();
-        }
-        return self.inst_map.get(inst).?; // Instruction does not dominate all uses!
-    }
-
-    fn name(self: *Context) ![]u8 {
-        const val = try std.fmt.allocPrint(&self.arena.allocator, "__temp_{d}", .{self.unnamed_index});
-        self.unnamed_index += 1;
-        return val;
-    }
-
-    fn fail(self: *Context, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
-        self.error_msg = try Compilation.ErrorMsg.create(self.arena.child_allocator, src, format, args);
-        return error.AnalysisFail;
-    }
-
-    fn deinit(self: *Context) void {
-        self.* = undefined;
-    }
-};
-
-fn genAlloc(ctx: *Context, file: *C, alloc: *Inst.NoOp) !?[]u8 {
-    const writer = file.main.writer();
+fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue {
+    const writer = o.code.writer();
 
     // First line: the variable used as data storage.
-    try indent(file);
-    const local_name = try ctx.name();
+    try o.indent();
     const elem_type = alloc.base.ty.elemType();
     const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut;
-    try renderTypeAndName(ctx, writer, elem_type, local_name, mutability);
+    const local = try o.allocLocal(elem_type, mutability);
     try writer.writeAll(";\n");
 
-    // Second line: a pointer to it so that we can refer to it as the allocation.
-    // One line for the variable, one line for the pointer to the variable, which we return.
-    try indent(file);
-    const ptr_local_name = try ctx.name();
-    try renderTypeAndName(ctx, writer, alloc.base.ty, ptr_local_name, .Const);
-    try writer.print(" = &{s};\n", .{local_name});
-
-    return ptr_local_name;
+    return CValue{ .local_ref = local.local };
 }
 
-fn genArg(ctx: *Context) !?[]u8 {
-    const name = try std.fmt.allocPrint(&ctx.arena.allocator, "arg{d}", .{ctx.argdex});
-    ctx.argdex += 1;
-    return name;
+fn genArg(o: *Object) CValue {
+    const i = o.next_arg_index;
+    o.next_arg_index += 1;
+    return .{ .arg = i };
 }
 
-fn genRetVoid(file: *C) !?[]u8 {
-    try indent(file);
-    try file.main.writer().print("return;\n", .{});
-    return null;
+fn genRetVoid(o: *Object) !CValue {
+    try o.indent();
+    try o.code.writer().print("return;\n", .{});
+    return CValue.none;
 }
 
-fn genLoad(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
-    const operand = try ctx.resolveInst(inst.operand);
-    const writer = file.main.writer();
-    try indent(file);
-    const local_name = try ctx.name();
-    try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const);
-    try writer.print(" = *{s};\n", .{operand});
-    return local_name;
+fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue {
+    const operand = try o.resolveInst(inst.operand);
+    const writer = o.code.writer();
+    try o.indent();
+    const local = try o.allocLocal(inst.base.ty, .Const);
+    switch (operand) {
+        .local_ref => |i| {
+            const wrapped: CValue = .{ .local = i };
+            try writer.print(" = {};\n", .{wrapped.printed(o)});
+        },
+        else => {
+            try writer.print(" = *{};\n", .{operand.printed(o)});
+        },
+    }
+    return local;
 }
 
-fn genRet(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
-    try indent(file);
-    const writer = file.main.writer();
-    try writer.print("return {s};\n", .{try ctx.resolveInst(inst.operand)});
-    return null;
+fn genRet(o: *Object, inst: *Inst.UnOp) !CValue {
+    const operand = try o.resolveInst(inst.operand);
+    try o.indent();
+    try o.code.writer().print("return {};\n", .{operand.printed(o)});
+    return CValue.none;
 }
 
-fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
+fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue {
     if (inst.base.isUnused())
-        return null;
-    try indent(file);
-    const writer = file.main.writer();
-    const name = try ctx.name();
-    const from = try ctx.resolveInst(inst.operand);
+        return CValue.none;
 
-    try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const);
+    const from = try o.resolveInst(inst.operand);
+
+    try o.indent();
+    const writer = o.code.writer();
+    const local = try o.allocLocal(inst.base.ty, .Const);
     try writer.writeAll(" = (");
-    try renderType(ctx, writer, inst.base.ty);
-    try writer.print("){s};\n", .{from});
-    return name;
+    try o.dg.renderType(writer, inst.base.ty);
+    try writer.print("){};\n", .{from.printed(o)});
+    return local;
 }
 
-fn genStore(ctx: *Context, file: *C, inst: *Inst.BinOp) !?[]u8 {
+fn genStore(o: *Object, inst: *Inst.BinOp) !CValue {
     // *a = b;
-    try indent(file);
-    const writer = file.main.writer();
-    const dest_ptr_name = try ctx.resolveInst(inst.lhs);
-    const src_val_name = try ctx.resolveInst(inst.rhs);
-    try writer.print("*{s} = {s};\n", .{ dest_ptr_name, src_val_name });
-    return null;
+    const dest_ptr = try o.resolveInst(inst.lhs);
+    const src_val = try o.resolveInst(inst.rhs);
+
+    try o.indent();
+    const writer = o.code.writer();
+    switch (dest_ptr) {
+        .local_ref => |i| {
+            const dest: CValue = .{ .local = i };
+            try writer.print("{} = {};\n", .{ dest.printed(o), src_val.printed(o) });
+        },
+        else => {
+            try writer.print("*{} = {};\n", .{ dest_ptr.printed(o), src_val.printed(o) });
+        },
+    }
+    return CValue.none;
 }
 
-fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, operator: []const u8) !?[]u8 {
+fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {
     if (inst.base.isUnused())
-        return null;
-    try indent(file);
-    const lhs = try ctx.resolveInst(inst.lhs);
-    const rhs = try ctx.resolveInst(inst.rhs);
-    const writer = file.main.writer();
-    const name = try ctx.name();
-    try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const);
-    try writer.print(" = {s} {s} {s};\n", .{ lhs, operator, rhs });
-    return name;
+        return CValue.none;
+
+    const lhs = try o.resolveInst(inst.lhs);
+    const rhs = try o.resolveInst(inst.rhs);
+
+    try o.indent();
+    const writer = o.code.writer();
+    const local = try o.allocLocal(inst.base.ty, .Const);
+    try writer.print(" = {} {s} {};\n", .{ lhs.printed(o), operator, rhs.printed(o) });
+    return local;
 }
 
-fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
-    try indent(file);
-    const writer = file.main.writer();
-    const header = file.header.buf.writer();
+fn genCall(o: *Object, inst: *Inst.Call) !CValue {
     if (inst.func.castTag(.constant)) |func_inst| {
         const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn|
             extern_fn.data
@@ -501,23 +532,19 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
         const fn_ty = fn_decl.typed_value.most_recent.typed_value.ty;
         const ret_ty = fn_ty.fnReturnType();
         const unused_result = inst.base.isUnused();
-        var result_name: ?[]u8 = null;
+        var result_local: CValue = .none;
+
+        try o.indent();
+        const writer = o.code.writer();
         if (unused_result) {
             if (ret_ty.hasCodeGenBits()) {
                 try writer.print("(void)", .{});
             }
         } else {
-            const local_name = try ctx.name();
-            try renderTypeAndName(ctx, writer, ret_ty, local_name, .Const);
+            result_local = try o.allocLocal(ret_ty, .Const);
             try writer.writeAll(" = ");
-            result_name = local_name;
         }
         const fn_name = mem.spanZ(fn_decl.name);
-        if (file.called.get(fn_name) == null) {
-            try file.called.put(fn_name, {});
-            try renderFunctionSignature(ctx, header, fn_decl);
-            try header.writeAll(";\n");
-        }
         try writer.print("{s}(", .{fn_name});
         if (inst.args.len != 0) {
             for (inst.args) |arg, i| {
@@ -525,87 +552,88 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
                     try writer.writeAll(", ");
                 }
                 if (arg.value()) |val| {
-                    try renderValue(ctx, writer, arg.ty, val);
+                    try o.dg.renderValue(writer, arg.ty, val);
                 } else {
-                    const val = try ctx.resolveInst(arg);
-                    try writer.print("{s}", .{val});
+                    const val = try o.resolveInst(arg);
+                    try writer.print("{}", .{val.printed(o)});
                 }
             }
         }
         try writer.writeAll(");\n");
-        return result_name;
+        return result_local;
     } else {
-        return ctx.fail(ctx.decl.src(), "TODO: C backend: implement function pointers", .{});
+        return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement function pointers", .{});
     }
 }
 
-fn genDbgStmt(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
+fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue {
     // TODO emit #line directive here with line number and filename
-    return null;
+    return CValue.none;
 }
 
-fn genBlock(ctx: *Context, file: *C, inst: *Inst.Block) !?[]u8 {
-    return ctx.fail(ctx.decl.src(), "TODO: C backend: implement blocks", .{});
+fn genBlock(o: *Object, inst: *Inst.Block) !CValue {
+    return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement blocks", .{});
 }
 
-fn genBitcast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
-    const writer = file.main.writer();
-    try indent(file);
-    const local_name = try ctx.name();
-    const operand = try ctx.resolveInst(inst.operand);
-    try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const);
+fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue {
+    const operand = try o.resolveInst(inst.operand);
+
+    const writer = o.code.writer();
+    try o.indent();
     if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) {
+        const local = try o.allocLocal(inst.base.ty, .Const);
         try writer.writeAll(" = (");
-        try renderType(ctx, writer, inst.base.ty);
-        try writer.print("){s};\n", .{operand});
-    } else {
-        try writer.writeAll(";\n");
-        try indent(file);
-        try writer.print("memcpy(&{s}, &{s}, sizeof {s});\n", .{ local_name, operand, local_name });
+        try o.dg.renderType(writer, inst.base.ty);
+        try writer.print("){};\n", .{operand.printed(o)});
+        return local;
     }
-    return local_name;
+
+    const local = try o.allocLocal(inst.base.ty, .Mut);
+    try writer.writeAll(";\n");
+    try o.indent();
+    try writer.print("memcpy(&{}, &{}, sizeof {});\n", .{
+        local.printed(o), operand.printed(o), local.printed(o),
+    });
+    return local;
 }
 
-fn genBreakpoint(file: *C, inst: *Inst.NoOp) !?[]u8 {
-    try indent(file);
-    try file.main.writer().writeAll("zig_breakpoint();\n");
-    return null;
+fn genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue {
+    try o.indent();
+    try o.code.writer().writeAll("zig_breakpoint();\n");
+    return CValue.none;
 }
 
-fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 {
-    try indent(file);
-    try file.main.writer().writeAll("zig_unreachable();\n");
-    return null;
+fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue {
+    try o.indent();
+    try o.code.writer().writeAll("zig_unreachable();\n");
+    return CValue.none;
 }
 
-fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 {
-    try indent(file);
-    const writer = file.main.writer();
+fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
+    if (as.base.isUnused() and !as.is_volatile)
+        return CValue.none;
+
+    const writer = o.code.writer();
     for (as.inputs) |i, index| {
         if (i[0] == '{' and i[i.len - 1] == '}') {
             const reg = i[1 .. i.len - 1];
             const arg = as.args[index];
+            const arg_c_value = try o.resolveInst(arg);
+            try o.indent();
             try writer.writeAll("register ");
-            try renderType(ctx, writer, arg.ty);
-            try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg });
-            // TODO merge constant handling into inst_map as well
-            if (arg.castTag(.constant)) |c| {
-                try renderValue(ctx, writer, arg.ty, c.val);
-                try writer.writeAll(";\n    ");
-            } else {
-                const gop = try ctx.inst_map.getOrPut(arg);
-                if (!gop.found_existing) {
-                    return ctx.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{});
-                }
-                try writer.print("{s};\n    ", .{gop.entry.value});
-            }
+            try o.dg.renderType(writer, arg.ty);
+            try writer.print(" {s}_constant __asm__(\"{s}\") = {};\n", .{
+                reg, reg, arg_c_value.printed(o),
+            });
         } else {
-            return ctx.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{});
+            return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{});
         }
     }
-    try writer.print("__asm {s} (\"{s}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source });
-    if (as.output) |o| {
-        return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{});
+    try o.indent();
+    const volatile_string: []const u8 = if (as.is_volatile) "volatile " else "";
+    try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source });
+    if (as.output) |_| {
+        return o.dg.fail(o.dg.decl.src(), "TODO inline asm output", .{});
     }
     if (as.inputs.len > 0) {
         if (as.output == null) {
@@ -627,5 +655,9 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 {
         }
     }
     try writer.writeAll(");\n");
-    return null;
+
+    if (as.base.isUnused())
+        return CValue.none;
+
+    return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{});
 }
src/link/cbe.h → src/link/C/zig.h
@@ -42,3 +42,4 @@
 #define int128_t __int128
 #define uint128_t unsigned __int128
 #include <string.h>
+
src/link/C.zig
@@ -11,45 +11,28 @@ const trace = @import("../tracy.zig").trace;
 const C = @This();
 
 pub const base_tag: link.File.Tag = .c;
+pub const zig_h = @embedFile("C/zig.h");
 
-pub const Header = struct {
-    buf: std.ArrayList(u8),
-    emit_loc: ?Compilation.EmitLoc,
-
-    pub fn init(allocator: *Allocator, emit_loc: ?Compilation.EmitLoc) Header {
-        return .{
-            .buf = std.ArrayList(u8).init(allocator),
-            .emit_loc = emit_loc,
-        };
-    }
-
-    pub fn flush(self: *const Header, writer: anytype) !void {
-        const tracy = trace(@src());
-        defer tracy.end();
+base: link.File,
 
-        try writer.writeAll(@embedFile("cbe.h"));
-        if (self.buf.items.len > 0) {
-            try writer.print("{s}", .{self.buf.items});
-        }
-    }
+/// 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 fn deinit(self: *Header) void {
-        self.buf.deinit();
-        self.* = undefined;
-    }
+    pub const empty: DeclBlock = .{
+        .code = .{},
+    };
 };
 
-base: link.File,
-
-path: []const u8,
+/// Per-function data.
+pub const FnBlock = struct {
+    fwd_decl: std.ArrayListUnmanaged(u8),
 
-// These are only valid during a flush()!
-header: Header,
-constants: std.ArrayList(u8),
-main: std.ArrayList(u8),
-called: std.StringHashMap(void),
-
-error_msg: *Compilation.ErrorMsg = undefined,
+    pub const empty: FnBlock = .{
+        .fwd_decl = .{},
+    };
+};
 
 pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C {
     assert(options.object_format == .c);
@@ -57,6 +40,14 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
     if (options.use_llvm) return error.LLVMHasNoCBackend;
     if (options.use_lld) return error.LLDHasNoCBackend;
 
+    const file = try options.emit.?.directory.handle.createFile(sub_path, .{
+        .truncate = true,
+        .mode = link.determineMode(options),
+    });
+    errdefer file.close();
+
+    try file.writeAll(zig_h);
+
     var c_file = try allocator.create(C);
     errdefer allocator.destroy(c_file);
 
@@ -64,25 +55,75 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
         .base = .{
             .tag = .c,
             .options = options,
-            .file = null,
+            .file = file,
             .allocator = allocator,
         },
-        .main = undefined,
-        .header = undefined,
-        .constants = undefined,
-        .called = undefined,
-        .path = sub_path,
     };
 
     return c_file;
 }
 
-pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
-    self.error_msg = try Compilation.ErrorMsg.create(self.base.allocator, src, format, args);
-    return error.AnalysisFail;
+pub fn deinit(self: *C) void {
+    const module = self.base.options.module orelse return;
+    for (module.decl_table.items()) |entry| {
+        self.freeDecl(entry.value);
+    }
+}
+
+pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {}
+
+pub fn freeDecl(self: *C, decl: *Module.Decl) void {
+    decl.link.c.code.deinit(self.base.allocator);
+    decl.fn_link.c.fwd_decl.deinit(self.base.allocator);
+}
+
+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 code = &decl.link.c.code;
+    fwd_decl.shrinkRetainingCapacity(0);
+    code.shrinkRetainingCapacity(0);
+
+    var object: codegen.Object = .{
+        .dg = .{
+            .module = module,
+            .error_msg = null,
+            .decl = decl,
+            .fwd_decl = fwd_decl.toManaged(module.gpa),
+        },
+        .gpa = module.gpa,
+        .code = code.toManaged(module.gpa),
+        .value_map = codegen.CValueMap.init(module.gpa),
+    };
+    defer object.value_map.deinit();
+    defer object.code.deinit();
+    defer object.dg.fwd_decl.deinit();
+
+    codegen.genDecl(&object) catch |err| switch (err) {
+        error.AnalysisFail => {},
+        else => |e| return e,
+    };
+    // The code may populate this error without returning error.AnalysisFail.
+    if (object.dg.error_msg) |msg| {
+        try module.failed_decls.put(module.gpa, decl, msg);
+        return;
+    }
+
+    fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
+    code.* = object.code.moveToUnmanaged();
+
+    // Free excess allocated memory for this Decl.
+    fwd_decl.shrink(module.gpa, fwd_decl.items.len);
+    code.shrink(module.gpa, code.items.len);
 }
 
-pub fn deinit(self: *C) void {}
+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);
+}
 
 pub fn flush(self: *C, comp: *Compilation) !void {
     return self.flushModule(comp);
@@ -92,41 +133,45 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    self.main = std.ArrayList(u8).init(self.base.allocator);
-    self.header = Header.init(self.base.allocator, null);
-    self.constants = std.ArrayList(u8).init(self.base.allocator);
-    self.called = std.StringHashMap(void).init(self.base.allocator);
-    defer self.main.deinit();
-    defer self.header.deinit();
-    defer self.constants.deinit();
-    defer self.called.deinit();
-
-    const module = self.base.options.module.?;
-    for (self.base.options.module.?.decl_table.entries.items) |kv| {
-        codegen.generate(self, module, kv.value) catch |err| {
-            if (err == error.AnalysisFail) {
-                try module.failed_decls.put(module.gpa, kv.value, self.error_msg);
-            }
-            return err;
-        };
-    }
+    const file = self.base.file.?;
 
-    const file = try self.base.options.emit.?.directory.handle.createFile(self.path, .{ .truncate = true, .read = true, .mode = link.determineMode(self.base.options) });
-    defer file.close();
+    // The header is written upon opening; here we truncate and seek to after the header.
+    // TODO: use writev
+    try file.seekTo(zig_h.len);
+    try file.setEndPos(zig_h.len);
 
-    const writer = file.writer();
-    try self.header.flush(writer);
-    if (self.header.buf.items.len > 0) {
-        try writer.writeByte('\n');
-    }
-    if (self.constants.items.len > 0) {
-        try writer.print("{s}\n", .{self.constants.items});
+    var buffered_writer = std.io.bufferedWriter(file.writer());
+    const writer = buffered_writer.writer();
+
+    const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented;
+
+    // Forward decls and non-functions first.
+    // TODO: use writev
+    for (module.decl_table.items()) |kv| {
+        const decl = kv.value;
+        const decl_tv = decl.typed_value.most_recent.typed_value;
+        if (decl_tv.val.castTag(.function)) |_| {
+            try writer.writeAll(decl.fn_link.c.fwd_decl.items);
+        } else {
+            try writer.writeAll(decl.link.c.code.items);
+        }
     }
-    if (self.main.items.len > 1) {
-        const last_two = self.main.items[self.main.items.len - 2 ..];
-        if (std.mem.eql(u8, last_two, "\n\n")) {
-            self.main.items.len -= 1;
+
+    // Now the function bodies.
+    for (module.decl_table.items()) |kv| {
+        const decl = kv.value;
+        const decl_tv = decl.typed_value.most_recent.typed_value;
+        if (decl_tv.val.castTag(.function)) |_| {
+            try writer.writeAll(decl.link.c.code.items);
         }
     }
-    try writer.writeAll(self.main.items);
+
+    try buffered_writer.flush();
 }
+
+pub fn updateDeclExports(
+    self: *C,
+    module: *Module,
+    decl: *Module.Decl,
+    exports: []const *Module.Export,
+) !void {}
src/Compilation.zig
@@ -138,8 +138,6 @@ emit_llvm_ir: ?EmitLoc,
 emit_analysis: ?EmitLoc,
 emit_docs: ?EmitLoc,
 
-c_header: ?c_link.Header,
-
 work_queue_wait_group: WaitGroup,
 
 pub const InnerError = Module.InnerError;
@@ -866,9 +864,13 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
                 .root_pkg = root_pkg,
                 .root_scope = root_scope,
                 .zig_cache_artifact_directory = zig_cache_artifact_directory,
+                .emit_h = options.emit_h,
             };
             break :blk module;
-        } else null;
+        } else blk: {
+            if (options.emit_h != null) return error.NoZigModuleForCHeader;
+            break :blk null;
+        };
         errdefer if (module) |zm| zm.deinit();
 
         const error_return_tracing = !strip and switch (options.optimize_mode) {
@@ -996,7 +998,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             .local_cache_directory = options.local_cache_directory,
             .global_cache_directory = options.global_cache_directory,
             .bin_file = bin_file,
-            .c_header = if (!use_llvm and options.emit_h != null) c_link.Header.init(gpa, options.emit_h) else null,
             .emit_asm = options.emit_asm,
             .emit_llvm_ir = options.emit_llvm_ir,
             .emit_analysis = options.emit_analysis,
@@ -1218,10 +1219,6 @@ pub fn destroy(self: *Compilation) void {
     }
     self.failed_c_objects.deinit(gpa);
 
-    if (self.c_header) |*header| {
-        header.deinit();
-    }
-
     self.cache_parent.manifest_dir.close();
     if (self.owned_link_dir) |*dir| dir.close();
 
@@ -1325,20 +1322,6 @@ pub fn update(self: *Compilation) !void {
             module.root_scope.unload(self.gpa);
         }
     }
-
-    // If we've chosen to emit a C header, flush the header to the disk.
-    if (self.c_header) |header| {
-        const header_path = header.emit_loc.?;
-        // If a directory has been provided, write the header there. Otherwise, just write it to the
-        // cache directory.
-        const header_dir = if (header_path.directory) |dir|
-            dir.handle
-        else
-            self.local_cache_directory.handle;
-        const header_file = try header_dir.createFile(header_path.basename, .{});
-        defer header_file.close();
-        try header.flush(header_file.writer());
-    }
 }
 
 /// Having the file open for writing is problematic as far as executing the
@@ -1497,7 +1480,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
                     switch (err) {
                         error.OutOfMemory => return error.OutOfMemory,
                         error.AnalysisFail => {
-                            decl.analysis = .dependency_failure;
+                            decl.analysis = .codegen_failure;
                         },
                         else => {
                             try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
@@ -1512,25 +1495,6 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
                     }
                     return;
                 };
-
-                if (self.c_header) |*header| {
-                    c_codegen.generateHeader(self, module, header, decl) catch |err| switch (err) {
-                        error.OutOfMemory => return error.OutOfMemory,
-                        error.AnalysisFail => {
-                            decl.analysis = .dependency_failure;
-                        },
-                        else => {
-                            try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
-                            module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
-                                module.gpa,
-                                decl.src(),
-                                "unable to generate C header: {s}",
-                                .{@errorName(err)},
-                            ));
-                            decl.analysis = .codegen_failure_retryable;
-                        },
-                    };
-                }
             },
         },
         .analyze_decl => |decl| {
@@ -2998,9 +2962,9 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
     man.hash.add(comp.bin_file.options.function_sections);
     man.hash.add(comp.bin_file.options.is_test);
     man.hash.add(comp.bin_file.options.emit != null);
-    man.hash.add(comp.c_header != null);
-    if (comp.c_header) |header| {
-        man.hash.addEmitLoc(header.emit_loc.?);
+    man.hash.add(mod.emit_h != null);
+    if (mod.emit_h) |emit_h| {
+        man.hash.addEmitLoc(emit_h);
     }
     man.hash.addOptionalEmitLoc(comp.emit_asm);
     man.hash.addOptionalEmitLoc(comp.emit_llvm_ir);
@@ -3105,10 +3069,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
         });
         break :blk try directory.join(arena, &[_][]const u8{bin_basename});
     } else "";
-    if (comp.c_header != null) {
+    if (comp.emit_h != null) {
         log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{});
     }
-    const emit_h_path = try stage1LocPath(arena, if (comp.c_header) |header| header.emit_loc else null, directory);
+    const emit_h_path = try stage1LocPath(arena, mod.emit_h, directory);
     const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory);
     const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory);
     const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory);
src/link.zig
@@ -130,7 +130,7 @@ pub const File = struct {
         elf: Elf.TextBlock,
         coff: Coff.TextBlock,
         macho: MachO.TextBlock,
-        c: void,
+        c: C.DeclBlock,
         wasm: void,
     };
 
@@ -138,7 +138,7 @@ pub const File = struct {
         elf: Elf.SrcFn,
         coff: Coff.SrcFn,
         macho: MachO.SrcFn,
-        c: void,
+        c: C.FnBlock,
         wasm: ?Wasm.FnData,
     };
 
@@ -291,7 +291,7 @@ pub const File = struct {
             .coff => return @fieldParentPtr(Coff, "base", base).updateDecl(module, decl),
             .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
             .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),
-            .c => {},
+            .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
             .wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl),
         }
     }
@@ -301,7 +301,8 @@ pub const File = struct {
             .coff => return @fieldParentPtr(Coff, "base", base).updateDeclLineNumber(module, decl),
             .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
             .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),
-            .c, .wasm => {},
+            .c => return @fieldParentPtr(C, "base", base).updateDeclLineNumber(module, decl),
+            .wasm => {},
         }
     }
 
@@ -312,7 +313,8 @@ pub const File = struct {
             .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, .wasm => {},
+            .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl),
+            .wasm => {},
         }
     }
 
@@ -407,12 +409,13 @@ pub const File = struct {
         }
     }
 
+    /// Called when a Decl is deleted from the Module.
     pub fn freeDecl(base: *File, decl: *Module.Decl) void {
         switch (base.tag) {
             .coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl),
             .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
             .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),
-            .c => {},
+            .c => @fieldParentPtr(C, "base", base).freeDecl(decl),
             .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl),
         }
     }
@@ -432,14 +435,14 @@ pub const File = struct {
     pub fn updateDeclExports(
         base: *File,
         module: *Module,
-        decl: *const Module.Decl,
+        decl: *Module.Decl,
         exports: []const *Module.Export,
     ) !void {
         switch (base.tag) {
             .coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl, exports),
             .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
             .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),
-            .c => return {},
+            .c => return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl, exports),
             .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports),
         }
     }
src/Module.zig
@@ -94,6 +94,8 @@ stage1_flags: packed struct {
     reserved: u2 = 0,
 } = .{},
 
+emit_h: ?Compilation.EmitLoc,
+
 pub const Export = struct {
     options: std.builtin.ExportOptions,
     /// Byte offset into the file that contains the export directive.
@@ -1943,14 +1945,14 @@ fn allocateNewDecl(
             .coff => .{ .coff = link.File.Coff.TextBlock.empty },
             .elf => .{ .elf = link.File.Elf.TextBlock.empty },
             .macho => .{ .macho = link.File.MachO.TextBlock.empty },
-            .c => .{ .c = {} },
+            .c => .{ .c = link.File.C.DeclBlock.empty },
             .wasm => .{ .wasm = {} },
         },
         .fn_link = switch (self.comp.bin_file.tag) {
             .coff => .{ .coff = {} },
             .elf => .{ .elf = link.File.Elf.SrcFn.empty },
             .macho => .{ .macho = link.File.MachO.SrcFn.empty },
-            .c => .{ .c = {} },
+            .c => .{ .c = link.File.C.FnBlock.empty },
             .wasm => .{ .wasm = null },
         },
         .generation = 0,
src/test.zig
@@ -13,7 +13,7 @@ const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_d
 const ThreadPool = @import("ThreadPool.zig");
 const CrossTarget = std.zig.CrossTarget;
 
-const c_header = @embedFile("link/cbe.h");
+const zig_h = link.File.C.zig_h;
 
 test "self-hosted" {
     var ctx = TestContext.init();
@@ -324,11 +324,11 @@ pub const TestContext = struct {
     }
 
     pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
-        ctx.addC(name, target, .Zig).addCompareObjectFile(src, c_header ++ out);
+        ctx.addC(name, target, .Zig).addCompareObjectFile(src, zig_h ++ out);
     }
 
     pub fn h(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
-        ctx.addC(name, target, .Zig).addHeader(src, c_header ++ out);
+        ctx.addC(name, target, .Zig).addHeader(src, zig_h ++ out);
     }
 
     pub fn addCompareOutput(
@@ -700,11 +700,12 @@ pub const TestContext = struct {
                             },
                         }
                     }
-                    if (comp.bin_file.cast(link.File.C)) |c_file| {
-                        std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{
-                            c_file.main.items,
-                        });
-                    }
+                    // TODO print generated C code
+                    //if (comp.bin_file.cast(link.File.C)) |c_file| {
+                    //    std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{
+                    //        c_file.main.items,
+                    //    });
+                    //}
                     std.debug.print("Test failed.\n", .{});
                     std.process.exit(1);
                 }
test/stage2/cbe.zig
@@ -22,8 +22,6 @@ pub fn addCases(ctx: *TestContext) !void {
         , "hello world!" ++ std.cstr.line_sep);
 
         // Now change the message only
-        // TODO fix C backend not supporting updates
-        // https://github.com/ziglang/zig/issues/7589
         case.addCompareOutput(
             \\extern fn puts(s: [*:0]const u8) c_int;
             \\export fn main() c_int {