const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const mem = std.mem;
const log = std.log.scoped(.c);
const Allocator = mem.Allocator;

const dev = @import("../dev.zig");
const link = @import("../link.zig");
const Zcu = @import("../Zcu.zig");
const Module = @import("../Package/Module.zig");
const Compilation = @import("../Compilation.zig");
const Value = @import("../Value.zig");
const Type = @import("../Type.zig");
const C = link.File.C;
const Decl = Zcu.Decl;
const trace = @import("../tracy.zig").trace;
const Air = @import("../Air.zig");
const InternPool = @import("../InternPool.zig");
const Alignment = InternPool.Alignment;

const BigIntLimb = std.math.big.Limb;
const BigInt = std.math.big.int;

pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
    return comptime switch (dev.env.supports(.legalize)) {
        inline false, true => |supports_legalize| &.init(.{
            // we don't currently ask zig1 to use safe optimization modes
            .expand_intcast_safe = supports_legalize,
            .expand_int_from_float_safe = supports_legalize,
            .expand_int_from_float_optimized_safe = supports_legalize,
            .expand_add_safe = supports_legalize,
            .expand_sub_safe = supports_legalize,
            .expand_mul_safe = supports_legalize,

            .expand_packed_load = true,
            .expand_packed_store = true,
            .expand_packed_struct_field_val = true,
        }),
    };
}

/// For most backends, MIR is basically a sequence of machine code instructions, perhaps with some
/// "pseudo instructions" thrown in. For the C backend, it is instead the generated C code for a
/// single function. We also need to track some information to get merged into the global `link.C`
/// state, including:
/// * The UAVs used, so declarations can be emitted in `flush`
/// * The types used, so declarations can be emitted in `flush`
/// * The lazy functions used, so definitions can be emitted in `flush`
pub const Mir = struct {
    /// This map contains all the UAVs we saw generating this function.
    /// `link.C` will merge them into its `uavs`/`aligned_uavs` fields.
    /// Key is the value of the UAV; value is the UAV's alignment, or
    /// `.none` for natural alignment. The specified alignment is never
    /// less than the natural alignment.
    uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, Alignment),
    // These remaining fields are essentially just an owned version of `link.C.AvBlock`.
    code: []u8,
    fwd_decl: []u8,
    ctype_pool: CType.Pool,
    lazy_fns: LazyFnMap,

    pub fn deinit(mir: *Mir, gpa: Allocator) void {
        mir.uavs.deinit(gpa);
        gpa.free(mir.code);
        gpa.free(mir.fwd_decl);
        mir.ctype_pool.deinit(gpa);
        mir.lazy_fns.deinit(gpa);
    }
};

pub const CType = @import("c/Type.zig");

pub const CValue = union(enum) {
    none: void,
    new_local: LocalIndex,
    local: LocalIndex,
    /// Address of a local.
    local_ref: LocalIndex,
    /// A constant instruction, to be rendered inline.
    constant: Value,
    /// Index into the parameters
    arg: usize,
    /// The array field of a parameter
    arg_array: usize,
    /// Index into a tuple's fields
    field: usize,
    /// By-value
    nav: InternPool.Nav.Index,
    nav_ref: InternPool.Nav.Index,
    /// An undefined value (cannot be dereferenced)
    undef: Type,
    /// Rendered as an identifier (using fmtIdent)
    identifier: []const u8,
    /// Rendered as "payload." followed by as identifier (using fmtIdent)
    payload_identifier: []const u8,
    /// Rendered with fmtCTypePoolString
    ctype_pool_string: CType.Pool.String,

    fn eql(lhs: CValue, rhs: CValue) bool {
        return switch (lhs) {
            .none => rhs == .none,
            .new_local, .local => |lhs_local| switch (rhs) {
                .new_local, .local => |rhs_local| lhs_local == rhs_local,
                else => false,
            },
            .local_ref => |lhs_local| switch (rhs) {
                .local_ref => |rhs_local| lhs_local == rhs_local,
                else => false,
            },
            .constant => |lhs_val| switch (rhs) {
                .constant => |rhs_val| lhs_val.toIntern() == rhs_val.toIntern(),
                else => false,
            },
            .arg => |lhs_arg_index| switch (rhs) {
                .arg => |rhs_arg_index| lhs_arg_index == rhs_arg_index,
                else => false,
            },
            .arg_array => |lhs_arg_index| switch (rhs) {
                .arg_array => |rhs_arg_index| lhs_arg_index == rhs_arg_index,
                else => false,
            },
            .field => |lhs_field_index| switch (rhs) {
                .field => |rhs_field_index| lhs_field_index == rhs_field_index,
                else => false,
            },
            .nav => |lhs_nav| switch (rhs) {
                .nav => |rhs_nav| lhs_nav == rhs_nav,
                else => false,
            },
            .nav_ref => |lhs_nav| switch (rhs) {
                .nav_ref => |rhs_nav| lhs_nav == rhs_nav,
                else => false,
            },
            .undef => |lhs_ty| switch (rhs) {
                .undef => |rhs_ty| lhs_ty.toIntern() == rhs_ty.toIntern(),
                else => false,
            },
            .identifier => |lhs_id| switch (rhs) {
                .identifier => |rhs_id| std.mem.eql(u8, lhs_id, rhs_id),
                else => false,
            },
            .payload_identifier => |lhs_id| switch (rhs) {
                .payload_identifier => |rhs_id| std.mem.eql(u8, lhs_id, rhs_id),
                else => false,
            },
            .ctype_pool_string => |lhs_str| switch (rhs) {
                .ctype_pool_string => |rhs_str| lhs_str.index == rhs_str.index,
                else => false,
            },
        };
    }
};

const BlockData = struct {
    block_id: u32,
    result: CValue,
};

pub const CValueMap = std.AutoHashMap(Air.Inst.Ref, CValue);

pub const LazyFnKey = union(enum) {
    tag_name: InternPool.Index,
    never_tail: InternPool.Nav.Index,
    never_inline: InternPool.Nav.Index,
};
pub const LazyFnValue = struct {
    fn_name: CType.Pool.String,
};
pub const LazyFnMap = std.AutoArrayHashMapUnmanaged(LazyFnKey, LazyFnValue);

const Local = struct {
    ctype: CType,
    flags: packed struct(u32) {
        alignas: CType.AlignAs,
        _: u20 = undefined,
    },

    fn getType(local: Local) LocalType {
        return .{ .ctype = local.ctype, .alignas = local.flags.alignas };
    }
};

const LocalIndex = u16;
const LocalType = struct { ctype: CType, alignas: CType.AlignAs };
const LocalsList = std.AutoArrayHashMapUnmanaged(LocalIndex, void);
const LocalsMap = std.AutoArrayHashMapUnmanaged(LocalType, LocalsList);

const ValueRenderLocation = enum {
    FunctionArgument,
    Initializer,
    StaticInitializer,
    Other,

    fn isInitializer(loc: ValueRenderLocation) bool {
        return switch (loc) {
            .Initializer, .StaticInitializer => true,
            else => false,
        };
    }

    fn toCTypeKind(loc: ValueRenderLocation) CType.Kind {
        return switch (loc) {
            .FunctionArgument => .parameter,
            .Initializer, .Other => .complete,
            .StaticInitializer => .global,
        };
    }
};

const BuiltinInfo = enum { none, bits };

const reserved_idents = std.StaticStringMap(void).initComptime(.{
    // C language
    .{ "alignas", {
        @setEvalBranchQuota(4000);
    } },
    .{ "alignof", {} },
    .{ "asm", {} },
    .{ "atomic_bool", {} },
    .{ "atomic_char", {} },
    .{ "atomic_char16_t", {} },
    .{ "atomic_char32_t", {} },
    .{ "atomic_int", {} },
    .{ "atomic_int_fast16_t", {} },
    .{ "atomic_int_fast32_t", {} },
    .{ "atomic_int_fast64_t", {} },
    .{ "atomic_int_fast8_t", {} },
    .{ "atomic_int_least16_t", {} },
    .{ "atomic_int_least32_t", {} },
    .{ "atomic_int_least64_t", {} },
    .{ "atomic_int_least8_t", {} },
    .{ "atomic_intmax_t", {} },
    .{ "atomic_intptr_t", {} },
    .{ "atomic_llong", {} },
    .{ "atomic_long", {} },
    .{ "atomic_ptrdiff_t", {} },
    .{ "atomic_schar", {} },
    .{ "atomic_short", {} },
    .{ "atomic_size_t", {} },
    .{ "atomic_uchar", {} },
    .{ "atomic_uint", {} },
    .{ "atomic_uint_fast16_t", {} },
    .{ "atomic_uint_fast32_t", {} },
    .{ "atomic_uint_fast64_t", {} },
    .{ "atomic_uint_fast8_t", {} },
    .{ "atomic_uint_least16_t", {} },
    .{ "atomic_uint_least32_t", {} },
    .{ "atomic_uint_least64_t", {} },
    .{ "atomic_uint_least8_t", {} },
    .{ "atomic_uintmax_t", {} },
    .{ "atomic_uintptr_t", {} },
    .{ "atomic_ullong", {} },
    .{ "atomic_ulong", {} },
    .{ "atomic_ushort", {} },
    .{ "atomic_wchar_t", {} },
    .{ "auto", {} },
    .{ "break", {} },
    .{ "case", {} },
    .{ "char", {} },
    .{ "complex", {} },
    .{ "const", {} },
    .{ "continue", {} },
    .{ "default", {} },
    .{ "do", {} },
    .{ "double", {} },
    .{ "else", {} },
    .{ "enum", {} },
    .{ "extern", {} },
    .{ "float", {} },
    .{ "for", {} },
    .{ "fortran", {} },
    .{ "goto", {} },
    .{ "if", {} },
    .{ "imaginary", {} },
    .{ "inline", {} },
    .{ "int", {} },
    .{ "int16_t", {} },
    .{ "int32_t", {} },
    .{ "int64_t", {} },
    .{ "int8_t", {} },
    .{ "intptr_t", {} },
    .{ "long", {} },
    .{ "noreturn", {} },
    .{ "register", {} },
    .{ "restrict", {} },
    .{ "return", {} },
    .{ "short", {} },
    .{ "signed", {} },
    .{ "size_t", {} },
    .{ "sizeof", {} },
    .{ "ssize_t", {} },
    .{ "static", {} },
    .{ "static_assert", {} },
    .{ "struct", {} },
    .{ "switch", {} },
    .{ "thread_local", {} },
    .{ "typedef", {} },
    .{ "typeof", {} },
    .{ "uint16_t", {} },
    .{ "uint32_t", {} },
    .{ "uint64_t", {} },
    .{ "uint8_t", {} },
    .{ "uintptr_t", {} },
    .{ "union", {} },
    .{ "unsigned", {} },
    .{ "void", {} },
    .{ "volatile", {} },
    .{ "while", {} },

    // stdarg.h
    .{ "va_start", {} },
    .{ "va_arg", {} },
    .{ "va_end", {} },
    .{ "va_copy", {} },

    // stdbool.h
    .{ "bool", {} },
    .{ "false", {} },
    .{ "true", {} },

    // stddef.h
    .{ "offsetof", {} },

    // windows.h
    .{ "max", {} },
    .{ "min", {} },
});

fn isReservedIdent(ident: []const u8) bool {
    if (ident.len >= 2 and ident[0] == '_') { // C language
        switch (ident[1]) {
            'A'...'Z', '_' => return true,
            else => return false,
        }
    } else if (mem.startsWith(u8, ident, "DUMMYSTRUCTNAME") or
        mem.startsWith(u8, ident, "DUMMYUNIONNAME"))
    { // windows.h
        return true;
    } else return reserved_idents.has(ident);
}

fn formatIdentSolo(ident: []const u8, writer: *std.io.Writer) std.io.Writer.Error!void {
    return formatIdentOptions(ident, writer, true);
}

fn formatIdentUnsolo(ident: []const u8, writer: *std.io.Writer) std.io.Writer.Error!void {
    return formatIdentOptions(ident, writer, false);
}

fn formatIdentOptions(ident: []const u8, writer: *std.io.Writer, solo: bool) std.io.Writer.Error!void {
    if (solo and isReservedIdent(ident)) {
        try writer.writeAll("zig_e_");
    }
    for (ident, 0..) |c, i| {
        switch (c) {
            'a'...'z', 'A'...'Z', '_' => try writer.writeByte(c),
            '.' => try writer.writeByte('_'),
            '0'...'9' => if (i == 0) {
                try writer.print("_{x:2}", .{c});
            } else {
                try writer.writeByte(c);
            },
            else => try writer.print("_{x:2}", .{c}),
        }
    }
}

pub fn fmtIdentSolo(ident: []const u8) std.fmt.Formatter([]const u8, formatIdentSolo) {
    return .{ .data = ident };
}

pub fn fmtIdentUnsolo(ident: []const u8) std.fmt.Formatter([]const u8, formatIdentUnsolo) {
    return .{ .data = ident };
}

const CTypePoolStringFormatData = struct {
    ctype_pool_string: CType.Pool.String,
    ctype_pool: *const CType.Pool,
    solo: bool,
};
fn formatCTypePoolString(data: CTypePoolStringFormatData, writer: *std.io.Writer) std.io.Writer.Error!void {
    if (data.ctype_pool_string.toSlice(data.ctype_pool)) |slice|
        try formatIdentOptions(slice, writer, data.solo)
    else
        try writer.print("{}", .{data.ctype_pool_string.fmt(data.ctype_pool)});
}
pub fn fmtCTypePoolString(
    ctype_pool_string: CType.Pool.String,
    ctype_pool: *const CType.Pool,
    solo: bool,
) std.fmt.Formatter(CTypePoolStringFormatData, formatCTypePoolString) {
    return .{ .data = .{
        .ctype_pool_string = ctype_pool_string,
        .ctype_pool = ctype_pool,
        .solo = solo,
    } };
}

// Returns true if `formatIdent` would make any edits to ident.
// This must be kept in sync with `formatIdent`.
pub fn isMangledIdent(ident: []const u8, solo: bool) bool {
    if (solo and isReservedIdent(ident)) return true;
    for (ident, 0..) |c, i| {
        switch (c) {
            'a'...'z', 'A'...'Z', '_' => {},
            '0'...'9' => if (i == 0) return true,
            else => return true,
        }
    }
    return false;
}

/// This data is available when outputting .c code for a `InternPool.Index`
/// that corresponds to `func`.
/// It is not available when generating .h file.
pub const Function = struct {
    air: Air,
    liveness: Air.Liveness,
    value_map: CValueMap,
    blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .empty,
    next_arg_index: u32 = 0,
    next_block_index: u32 = 0,
    object: Object,
    lazy_fns: LazyFnMap,
    func_index: InternPool.Index,
    /// All the locals, to be emitted at the top of the function.
    locals: std.ArrayListUnmanaged(Local) = .empty,
    /// Which locals are available for reuse, based on Type.
    free_locals_map: LocalsMap = .{},
    /// Locals which will not be freed by Liveness. This is used after a
    /// Function body is lowered in order to make `free_locals_map` have
    /// 100% of the locals within so that it can be used to render the block
    /// of variable declarations at the top of a function, sorted descending
    /// by type alignment.
    /// The value is whether the alloc needs to be emitted in the header.
    allocs: std.AutoArrayHashMapUnmanaged(LocalIndex, bool) = .empty,
    /// Maps from `loop_switch_br` instructions to the allocated local used
    /// for the switch cond. Dispatches should set this local to the new cond.
    loop_switch_conds: std.AutoHashMapUnmanaged(Air.Inst.Index, LocalIndex) = .empty,

    fn resolveInst(f: *Function, ref: Air.Inst.Ref) !CValue {
        const gop = try f.value_map.getOrPut(ref);
        if (gop.found_existing) return gop.value_ptr.*;

        const pt = f.object.dg.pt;
        const val = (try f.air.value(ref, pt)).?;
        const ty = f.typeOf(ref);

        const result: CValue = if (lowersToArray(ty, pt)) result: {
            const writer = f.object.codeHeaderWriter();
            const decl_c_value = try f.allocLocalValue(.{
                .ctype = try f.ctypeFromType(ty, .complete),
                .alignas = CType.AlignAs.fromAbiAlignment(ty.abiAlignment(pt.zcu)),
            });
            const gpa = f.object.dg.gpa;
            try f.allocs.put(gpa, decl_c_value.new_local, false);
            try writer.writeAll("static ");
            try f.object.dg.renderTypeAndName(writer, ty, decl_c_value, Const, .none, .complete);
            try writer.writeAll(" = ");
            try f.object.dg.renderValue(writer, val, .StaticInitializer);
            try writer.writeAll(";\n ");
            break :result .{ .local = decl_c_value.new_local };
        } else .{ .constant = val };

        gop.value_ptr.* = result;
        return result;
    }

    fn wantSafety(f: *Function) bool {
        return switch (f.object.dg.pt.zcu.optimizeMode()) {
            .Debug, .ReleaseSafe => true,
            .ReleaseFast, .ReleaseSmall => false,
        };
    }

    /// Skips the reuse logic. This function should be used for any persistent allocation, i.e.
    /// those which go into `allocs`. This function does not add the resulting local into `allocs`;
    /// that responsibility lies with the caller.
    fn allocLocalValue(f: *Function, local_type: LocalType) !CValue {
        try f.locals.ensureUnusedCapacity(f.object.dg.gpa, 1);
        defer f.locals.appendAssumeCapacity(.{
            .ctype = local_type.ctype,
            .flags = .{ .alignas = local_type.alignas },
        });
        return .{ .new_local = @intCast(f.locals.items.len) };
    }

    fn allocLocal(f: *Function, inst: ?Air.Inst.Index, ty: Type) !CValue {
        return f.allocAlignedLocal(inst, .{
            .ctype = try f.ctypeFromType(ty, .complete),
            .alignas = CType.AlignAs.fromAbiAlignment(ty.abiAlignment(f.object.dg.pt.zcu)),
        });
    }

    /// Only allocates the local; does not print anything. Will attempt to re-use locals, so should
    /// not be used for persistent locals (i.e. those in `allocs`).
    fn allocAlignedLocal(f: *Function, inst: ?Air.Inst.Index, local_type: LocalType) !CValue {
        const result: CValue = result: {
            if (f.free_locals_map.getPtr(local_type)) |locals_list| {
                if (locals_list.pop()) |local_entry| {
                    break :result .{ .new_local = local_entry.key };
                }
            }
            break :result try f.allocLocalValue(local_type);
        };
        if (inst) |i| {
            log.debug("%{d}: allocating t{d}", .{ i, result.new_local });
        } else {
            log.debug("allocating t{d}", .{result.new_local});
        }
        return result;
    }

    fn writeCValue(f: *Function, w: anytype, c_value: CValue, location: ValueRenderLocation) !void {
        switch (c_value) {
            .none => unreachable,
            .new_local, .local => |i| try w.print("t{d}", .{i}),
            .local_ref => |i| try w.print("&t{d}", .{i}),
            .constant => |val| try f.object.dg.renderValue(w, val, location),
            .arg => |i| try w.print("a{d}", .{i}),
            .arg_array => |i| try f.writeCValueMember(w, .{ .arg = i }, .{ .identifier = "array" }),
            .undef => |ty| try f.object.dg.renderUndefValue(w, ty, location),
            else => try f.object.dg.writeCValue(w, c_value),
        }
    }

    fn writeCValueDeref(f: *Function, w: anytype, c_value: CValue) !void {
        switch (c_value) {
            .none => unreachable,
            .new_local, .local, .constant => {
                try w.writeAll("(*");
                try f.writeCValue(w, c_value, .Other);
                try w.writeByte(')');
            },
            .local_ref => |i| try w.print("t{d}", .{i}),
            .arg => |i| try w.print("(*a{d})", .{i}),
            .arg_array => |i| {
                try w.writeAll("(*");
                try f.writeCValueMember(w, .{ .arg = i }, .{ .identifier = "array" });
                try w.writeByte(')');
            },
            else => try f.object.dg.writeCValueDeref(w, c_value),
        }
    }

    fn writeCValueMember(
        f: *Function,
        writer: anytype,
        c_value: CValue,
        member: CValue,
    ) error{ OutOfMemory, AnalysisFail }!void {
        switch (c_value) {
            .new_local, .local, .local_ref, .constant, .arg, .arg_array => {
                try f.writeCValue(writer, c_value, .Other);
                try writer.writeByte('.');
                try f.writeCValue(writer, member, .Other);
            },
            else => return f.object.dg.writeCValueMember(writer, c_value, member),
        }
    }

    fn writeCValueDerefMember(f: *Function, writer: anytype, c_value: CValue, member: CValue) !void {
        switch (c_value) {
            .new_local, .local, .arg, .arg_array => {
                try f.writeCValue(writer, c_value, .Other);
                try writer.writeAll("->");
            },
            .constant => {
                try writer.writeByte('(');
                try f.writeCValue(writer, c_value, .Other);
                try writer.writeAll(")->");
            },
            .local_ref => {
                try f.writeCValueDeref(writer, c_value);
                try writer.writeByte('.');
            },
            else => return f.object.dg.writeCValueDerefMember(writer, c_value, member),
        }
        try f.writeCValue(writer, member, .Other);
    }

    fn fail(f: *Function, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
        return f.object.dg.fail(format, args);
    }

    fn ctypeFromType(f: *Function, ty: Type, kind: CType.Kind) !CType {
        return f.object.dg.ctypeFromType(ty, kind);
    }

    fn byteSize(f: *Function, ctype: CType) u64 {
        return f.object.dg.byteSize(ctype);
    }

    fn renderType(f: *Function, w: anytype, ctype: Type) !void {
        return f.object.dg.renderType(w, ctype);
    }

    fn renderCType(f: *Function, w: anytype, ctype: CType) !void {
        return f.object.dg.renderCType(w, ctype);
    }

    fn renderIntCast(f: *Function, w: anytype, dest_ty: Type, src: CValue, v: Vectorize, src_ty: Type, location: ValueRenderLocation) !void {
        return f.object.dg.renderIntCast(w, dest_ty, .{ .c_value = .{ .f = f, .value = src, .v = v } }, src_ty, location);
    }

    fn fmtIntLiteralDec(f: *Function, val: Value) !std.fmt.Formatter(FormatIntLiteralContext, formatIntLiteral) {
        return f.object.dg.fmtIntLiteralDec(val, .Other);
    }

    fn fmtIntLiteralHex(f: *Function, val: Value) !std.fmt.Formatter(FormatIntLiteralContext, formatIntLiteral) {
        return f.object.dg.fmtIntLiteralHex(val, .Other);
    }

    fn getLazyFnName(f: *Function, key: LazyFnKey) ![]const u8 {
        const gpa = f.object.dg.gpa;
        const pt = f.object.dg.pt;
        const zcu = pt.zcu;
        const ip = &zcu.intern_pool;
        const ctype_pool = &f.object.dg.ctype_pool;

        const gop = try f.lazy_fns.getOrPut(gpa, key);
        if (!gop.found_existing) {
            errdefer _ = f.lazy_fns.pop();

            gop.value_ptr.* = .{
                .fn_name = switch (key) {
                    .tag_name,
                    => |enum_ty| try ctype_pool.fmt(gpa, "zig_{s}_{f}__{d}", .{
                        @tagName(key),
                        fmtIdentUnsolo(ip.loadEnumType(enum_ty).name.toSlice(ip)),
                        @intFromEnum(enum_ty),
                    }),
                    .never_tail,
                    .never_inline,
                    => |owner_nav| try ctype_pool.fmt(gpa, "zig_{s}_{f}__{d}", .{
                        @tagName(key),
                        fmtIdentUnsolo(ip.getNav(owner_nav).name.toSlice(ip)),
                        @intFromEnum(owner_nav),
                    }),
                },
            };
        }
        return gop.value_ptr.fn_name.toSlice(ctype_pool).?;
    }

    pub fn deinit(f: *Function) void {
        const gpa = f.object.dg.gpa;
        f.allocs.deinit(gpa);
        f.locals.deinit(gpa);
        deinitFreeLocalsMap(gpa, &f.free_locals_map);
        f.blocks.deinit(gpa);
        f.value_map.deinit();
        f.lazy_fns.deinit(gpa);
        f.loop_switch_conds.deinit(gpa);
    }

    fn typeOf(f: *Function, inst: Air.Inst.Ref) Type {
        return f.air.typeOf(inst, &f.object.dg.pt.zcu.intern_pool);
    }

    fn typeOfIndex(f: *Function, inst: Air.Inst.Index) Type {
        return f.air.typeOfIndex(inst, &f.object.dg.pt.zcu.intern_pool);
    }

    fn copyCValue(f: *Function, ctype: CType, dst: CValue, src: CValue) !void {
        switch (dst) {
            .new_local, .local => |dst_local_index| switch (src) {
                .new_local, .local => |src_local_index| if (dst_local_index == src_local_index) return,
                else => {},
            },
            else => {},
        }
        const writer = f.object.writer();
        const a = try Assignment.start(f, writer, ctype);
        try f.writeCValue(writer, dst, .Other);
        try a.assign(f, writer);
        try f.writeCValue(writer, src, .Other);
        try a.end(f, writer);
    }

    fn moveCValue(f: *Function, inst: Air.Inst.Index, ty: Type, src: CValue) !CValue {
        switch (src) {
            // Move the freshly allocated local to be owned by this instruction,
            // by returning it here instead of freeing it.
            .new_local => return src,
            else => {
                try freeCValue(f, inst, src);
                const dst = try f.allocLocal(inst, ty);
                try f.copyCValue(try f.ctypeFromType(ty, .complete), dst, src);
                return dst;
            },
        }
    }

    fn freeCValue(f: *Function, inst: ?Air.Inst.Index, val: CValue) !void {
        switch (val) {
            .new_local => |local_index| try freeLocal(f, inst, local_index, null),
            else => {},
        }
    }
};

/// This data is available when outputting .c code for a `Zcu`.
/// It is not available when generating .h file.
pub const Object = struct {
    dg: DeclGen,
    /// This is a borrowed reference from `link.C`.
    code: std.ArrayList(u8),
    /// Goes before code. Initialized and deinitialized in `genFunc`.
    code_header: std.ArrayList(u8) = undefined,
    indent_writer: IndentWriter(std.ArrayList(u8).Writer),

    fn writer(o: *Object) IndentWriter(std.ArrayList(u8).Writer).Writer {
        return o.indent_writer.writer();
    }

    fn codeHeaderWriter(o: *Object) ArrayListWriter {
        return arrayListWriter(&o.code_header);
    }
};

/// This data is available both when outputting .c code and when outputting an .h file.
pub const DeclGen = struct {
    gpa: Allocator,
    pt: Zcu.PerThread,
    mod: *Module,
    pass: Pass,
    is_naked_fn: bool,
    expected_block: ?u32,
    /// This is a borrowed reference from `link.C`.
    fwd_decl: std.ArrayList(u8),
    error_msg: ?*Zcu.ErrorMsg,
    ctype_pool: CType.Pool,
    scratch: std.ArrayListUnmanaged(u32),
    /// This map contains all the UAVs we saw generating this function.
    /// `link.C` will merge them into its `uavs`/`aligned_uavs` fields.
    /// Key is the value of the UAV; value is the UAV's alignment, or
    /// `.none` for natural alignment. The specified alignment is never
    /// less than the natural alignment.
    uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, Alignment),

    pub const Pass = union(enum) {
        nav: InternPool.Nav.Index,
        uav: InternPool.Index,
        flush,
    };

    fn fwdDeclWriter(dg: *DeclGen) ArrayListWriter {
        return arrayListWriter(&dg.fwd_decl);
    }

    fn fail(dg: *DeclGen, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
        @branchHint(.cold);
        const zcu = dg.pt.zcu;
        const src_loc = zcu.navSrcLoc(dg.pass.nav);
        dg.error_msg = try Zcu.ErrorMsg.create(dg.gpa, src_loc, format, args);
        return error.AnalysisFail;
    }

    fn renderUav(
        dg: *DeclGen,
        writer: anytype,
        uav: InternPool.Key.Ptr.BaseAddr.Uav,
        location: ValueRenderLocation,
    ) error{ OutOfMemory, AnalysisFail }!void {
        const pt = dg.pt;
        const zcu = pt.zcu;
        const ip = &zcu.intern_pool;
        const ctype_pool = &dg.ctype_pool;
        const uav_val = Value.fromInterned(uav.val);
        const uav_ty = uav_val.typeOf(zcu);

        // Render an undefined pointer if we have a pointer to a zero-bit or comptime type.
        const ptr_ty: Type = .fromInterned(uav.orig_ty);
        if (ptr_ty.isPtrAtRuntime(zcu) and !uav_ty.isFnOrHasRuntimeBits(zcu)) {
            return dg.writeCValue(writer, .{ .undef = ptr_ty });
        }

        // Chase function values in order to be able to reference the original function.
        switch (ip.indexToKey(uav.val)) {
            .variable => unreachable,
            .func => |func| return dg.renderNav(writer, func.owner_nav, location),
            .@"extern" => |@"extern"| return dg.renderNav(writer, @"extern".owner_nav, location),
            else => {},
        }

        // We shouldn't cast C function pointers as this is UB (when you call
        // them).  The analysis until now should ensure that the C function
        // pointers are compatible.  If they are not, then there is a bug
        // somewhere and we should let the C compiler tell us about it.
        const ptr_ctype = try dg.ctypeFromType(ptr_ty, .complete);
        const elem_ctype = ptr_ctype.info(ctype_pool).pointer.elem_ctype;
        const uav_ctype = try dg.ctypeFromType(uav_ty, .complete);
        const need_cast = !elem_ctype.eql(uav_ctype) and
            (elem_ctype.info(ctype_pool) != .function or uav_ctype.info(ctype_pool) != .function);
        if (need_cast) {
            try writer.writeAll("((");
            try dg.renderCType(writer, ptr_ctype);
            try writer.writeByte(')');
        }
        try writer.writeByte('&');
        try renderUavName(writer, uav_val);
        if (need_cast) try writer.writeByte(')');

        // Indicate that the anon decl should be rendered to the output so that
        // our reference above is not undefined.
        const ptr_type = ip.indexToKey(uav.orig_ty).ptr_type;
        const gop = try dg.uavs.getOrPut(dg.gpa, uav.val);
        if (!gop.found_existing) gop.value_ptr.* = .none;
        // If there is an explicit alignment, greater than the current one, use it.
        // Note that we intentionally start at `.none`, so `gop.value_ptr.*` is never
        // underaligned, so we don't need to worry about the `.none` case here.
        if (ptr_type.flags.alignment != .none) {
            // Resolve the current alignment so we can choose the bigger one.
            const cur_alignment: Alignment = if (gop.value_ptr.* == .none) abi: {
                break :abi Type.fromInterned(ptr_type.child).abiAlignment(zcu);
            } else gop.value_ptr.*;
            gop.value_ptr.* = cur_alignment.maxStrict(ptr_type.flags.alignment);
        }
    }

    fn renderNav(
        dg: *DeclGen,
        writer: anytype,
        nav_index: InternPool.Nav.Index,
        location: ValueRenderLocation,
    ) error{ OutOfMemory, AnalysisFail }!void {
        _ = location;
        const pt = dg.pt;
        const zcu = pt.zcu;
        const ip = &zcu.intern_pool;
        const ctype_pool = &dg.ctype_pool;

        // Chase function values in order to be able to reference the original function.
        const owner_nav = switch (ip.getNav(nav_index).status) {
            .unresolved => unreachable,
            .type_resolved => nav_index, // this can't be an extern or a function
            .fully_resolved => |r| switch (ip.indexToKey(r.val)) {
                .func => |f| f.owner_nav,
                .@"extern" => |e| e.owner_nav,
                else => nav_index,
            },
        };

        // Render an undefined pointer if we have a pointer to a zero-bit or comptime type.
        const nav_ty: Type = .fromInterned(ip.getNav(owner_nav).typeOf(ip));
        const ptr_ty = try pt.navPtrType(owner_nav);
        if (!nav_ty.isFnOrHasRuntimeBits(zcu)) {
            return dg.writeCValue(writer, .{ .undef = ptr_ty });
        }

        // We shouldn't cast C function pointers as this is UB (when you call
        // them).  The analysis until now should ensure that the C function
        // pointers are compatible.  If they are not, then there is a bug
        // somewhere and we should let the C compiler tell us about it.
        const ctype = try dg.ctypeFromType(ptr_ty, .complete);
        const elem_ctype = ctype.info(ctype_pool).pointer.elem_ctype;
        const nav_ctype = try dg.ctypeFromType(nav_ty, .complete);
        const need_cast = !elem_ctype.eql(nav_ctype) and
            (elem_ctype.info(ctype_pool) != .function or nav_ctype.info(ctype_pool) != .function);
        if (need_cast) {
            try writer.writeAll("((");
            try dg.renderCType(writer, ctype);
            try writer.writeByte(')');
        }
        try writer.writeByte('&');
        try dg.renderNavName(writer, owner_nav);
        if (need_cast) try writer.writeByte(')');
    }

    fn renderPointer(
        dg: *DeclGen,
        writer: anytype,
        derivation: Value.PointerDeriveStep,
        location: ValueRenderLocation,
    ) error{ OutOfMemory, AnalysisFail }!void {
        const pt = dg.pt;
        const zcu = pt.zcu;
        switch (derivation) {
            .comptime_alloc_ptr, .comptime_field_ptr => unreachable,
            .int => |int| {
                const ptr_ctype = try dg.ctypeFromType(int.ptr_ty, .complete);
                const addr_val = try pt.intValue(.usize, int.addr);
                try writer.writeByte('(');
                try dg.renderCType(writer, ptr_ctype);
                try writer.print("){f}", .{try dg.fmtIntLiteralHex(addr_val, .Other)});
            },

            .nav_ptr => |nav| try dg.renderNav(writer, nav, location),
            .uav_ptr => |uav| try dg.renderUav(writer, uav, location),

            inline .eu_payload_ptr, .opt_payload_ptr => |info| {
                try writer.writeAll("&(");
                try dg.renderPointer(writer, info.parent.*, location);
                try writer.writeAll(")->payload");
            },

            .field_ptr => |field| {
                const parent_ptr_ty = try field.parent.ptrType(pt);

                // Ensure complete type definition is available before accessing fields.
                _ = try dg.ctypeFromType(parent_ptr_ty.childType(zcu), .complete);

                switch (fieldLocation(parent_ptr_ty, field.result_ptr_ty, field.field_idx, pt)) {
                    .begin => {
                        const ptr_ctype = try dg.ctypeFromType(field.result_ptr_ty, .complete);
                        try writer.writeByte('(');
                        try dg.renderCType(writer, ptr_ctype);
                        try writer.writeByte(')');
                        try dg.renderPointer(writer, field.parent.*, location);
                    },
                    .field => |name| {
                        try writer.writeAll("&(");
                        try dg.renderPointer(writer, field.parent.*, location);
                        try writer.writeAll(")->");
                        try dg.writeCValue(writer, name);
                    },
                    .byte_offset => |byte_offset| {
                        const ptr_ctype = try dg.ctypeFromType(field.result_ptr_ty, .complete);
                        try writer.writeByte('(');
                        try dg.renderCType(writer, ptr_ctype);
                        try writer.writeByte(')');
                        const offset_val = try pt.intValue(.usize, byte_offset);
                        try writer.writeAll("((char *)");
                        try dg.renderPointer(writer, field.parent.*, location);
                        try writer.print(" + {f})", .{try dg.fmtIntLiteralDec(offset_val, .Other)});
                    },
                }
            },

            .elem_ptr => |elem| if (!(try elem.parent.ptrType(pt)).childType(zcu).hasRuntimeBits(zcu)) {
                // Element type is zero-bit, so lowers to `void`. The index is irrelevant; just cast the pointer.
                const ptr_ctype = try dg.ctypeFromType(elem.result_ptr_ty, .complete);
                try writer.writeByte('(');
                try dg.renderCType(writer, ptr_ctype);
                try writer.writeByte(')');
                try dg.renderPointer(writer, elem.parent.*, location);
            } else {
                const index_val = try pt.intValue(.usize, elem.elem_idx);
                // We want to do pointer arithmetic on a pointer to the element type.
                // We might have a pointer-to-array. In this case, we must cast first.
                const result_ctype = try dg.ctypeFromType(elem.result_ptr_ty, .complete);
                const parent_ctype = try dg.ctypeFromType(try elem.parent.ptrType(pt), .complete);
                if (result_ctype.eql(parent_ctype)) {
                    // The pointer already has an appropriate type - just do the arithmetic.
                    try writer.writeByte('(');
                    try dg.renderPointer(writer, elem.parent.*, location);
                    try writer.print(" + {f})", .{try dg.fmtIntLiteralDec(index_val, .Other)});
                } else {
                    // We probably have an array pointer `T (*)[n]`. Cast to an element pointer,
                    // and *then* apply the index.
                    try writer.writeAll("((");
                    try dg.renderCType(writer, result_ctype);
                    try writer.writeByte(')');
                    try dg.renderPointer(writer, elem.parent.*, location);
                    try writer.print(" + {f})", .{try dg.fmtIntLiteralDec(index_val, .Other)});
                }
            },

            .offset_and_cast => |oac| {
                const ptr_ctype = try dg.ctypeFromType(oac.new_ptr_ty, .complete);
                try writer.writeByte('(');
                try dg.renderCType(writer, ptr_ctype);
                try writer.writeByte(')');
                if (oac.byte_offset == 0) {
                    try dg.renderPointer(writer, oac.parent.*, location);
                } else {
                    const offset_val = try pt.intValue(.usize, oac.byte_offset);
                    try writer.writeAll("((char *)");
                    try dg.renderPointer(writer, oac.parent.*, location);
                    try writer.print(" + {f})", .{try dg.fmtIntLiteralDec(offset_val, .Other)});
                }
            },
        }
    }

    fn renderErrorName(dg: *DeclGen, writer: anytype, err_name: InternPool.NullTerminatedString) !void {
        const ip = &dg.pt.zcu.intern_pool;
        try writer.print("zig_error_{}", .{fmtIdentUnsolo(err_name.toSlice(ip))});
    }

    fn renderValue(
        dg: *DeclGen,
        writer: anytype,
        val: Value,
        location: ValueRenderLocation,
    ) error{ OutOfMemory, AnalysisFail }!void {
        const pt = dg.pt;
        const zcu = pt.zcu;
        const ip = &zcu.intern_pool;
        const target = &dg.mod.resolved_target.result;
        const ctype_pool = &dg.ctype_pool;

        const initializer_type: ValueRenderLocation = switch (location) {
            .StaticInitializer => .StaticInitializer,
            else => .Initializer,
        };

        const ty = val.typeOf(zcu);
        if (val.isUndefDeep(zcu)) return dg.renderUndefValue(writer, ty, location);
        const ctype = try dg.ctypeFromType(ty, location.toCTypeKind());
        switch (ip.indexToKey(val.toIntern())) {
            // types, not values
            .int_type,
            .ptr_type,
            .array_type,
            .vector_type,
            .opt_type,
            .anyframe_type,
            .error_union_type,
            .simple_type,
            .struct_type,
            .tuple_type,
            .union_type,
            .opaque_type,
            .enum_type,
            .func_type,
            .error_set_type,
            .inferred_error_set_type,
            // memoization, not values
            .memoized_call,
            => unreachable,

            .undef => unreachable, // handled above
            .simple_value => |simple_value| switch (simple_value) {
                // non-runtime values
                .undefined => unreachable,
                .void => unreachable,
                .null => unreachable,
                .empty_tuple => unreachable,
                .@"unreachable" => unreachable,

                .false => try writer.writeAll("false"),
                .true => try writer.writeAll("true"),
            },
            .variable,
            .@"extern",
            .func,
            .enum_literal,
            .empty_enum_value,
            => unreachable, // non-runtime values
            .int => |int| switch (int.storage) {
                .u64, .i64, .big_int => try writer.print("{f}", .{try dg.fmtIntLiteralDec(val, location)}),
                .lazy_align, .lazy_size => {
                    try writer.writeAll("((");
                    try dg.renderCType(writer, ctype);
                    try writer.print("){f})", .{try dg.fmtIntLiteralHex(
                        try pt.intValue(.usize, val.toUnsignedInt(zcu)),
                        .Other,
                    )});
                },
            },
            .err => |err| try dg.renderErrorName(writer, err.name),
            .error_union => |error_union| switch (ctype.info(ctype_pool)) {
                .basic => switch (error_union.val) {
                    .err_name => |err_name| try dg.renderErrorName(writer, err_name),
                    .payload => try writer.writeAll("0"),
                },
                .pointer, .aligned, .array, .vector, .fwd_decl, .function => unreachable,
                .aggregate => |aggregate| {
                    if (!location.isInitializer()) {
                        try writer.writeByte('(');
                        try dg.renderCType(writer, ctype);
                        try writer.writeByte(')');
                    }
                    try writer.writeByte('{');
                    for (0..aggregate.fields.len) |field_index| {
                        if (field_index > 0) try writer.writeByte(',');
                        switch (aggregate.fields.at(field_index, ctype_pool).name.index) {
                            .@"error" => switch (error_union.val) {
                                .err_name => |err_name| try dg.renderErrorName(writer, err_name),
                                .payload => try writer.writeByte('0'),
                            },
                            .payload => switch (error_union.val) {
                                .err_name => try dg.renderUndefValue(
                                    writer,
                                    ty.errorUnionPayload(zcu),
                                    initializer_type,
                                ),
                                .payload => |payload| try dg.renderValue(
                                    writer,
                                    Value.fromInterned(payload),
                                    initializer_type,
                                ),
                            },
                            else => unreachable,
                        }
                    }
                    try writer.writeByte('}');
                },
            },
            .enum_tag => |enum_tag| try dg.renderValue(writer, Value.fromInterned(enum_tag.int), location),
            .float => {
                const bits = ty.floatBits(target);
                const f128_val = val.toFloat(f128, zcu);

                // All unsigned ints matching float types are pre-allocated.
                const repr_ty = pt.intType(.unsigned, bits) catch unreachable;

                assert(bits <= 128);
                var repr_val_limbs: [BigInt.calcTwosCompLimbCount(128)]BigIntLimb = undefined;
                var repr_val_big = BigInt.Mutable{
                    .limbs = &repr_val_limbs,
                    .len = undefined,
                    .positive = undefined,
                };

                switch (bits) {
                    16 => repr_val_big.set(@as(u16, @bitCast(val.toFloat(f16, zcu)))),
                    32 => repr_val_big.set(@as(u32, @bitCast(val.toFloat(f32, zcu)))),
                    64 => repr_val_big.set(@as(u64, @bitCast(val.toFloat(f64, zcu)))),
                    80 => repr_val_big.set(@as(u80, @bitCast(val.toFloat(f80, zcu)))),
                    128 => repr_val_big.set(@as(u128, @bitCast(f128_val))),
                    else => unreachable,
                }

                var empty = true;
                if (std.math.isFinite(f128_val)) {
                    try writer.writeAll("zig_make_");
                    try dg.renderTypeForBuiltinFnName(writer, ty);
                    try writer.writeByte('(');
                    switch (bits) {
                        16 => try writer.print("{x}", .{val.toFloat(f16, zcu)}),
                        32 => try writer.print("{x}", .{val.toFloat(f32, zcu)}),
                        64 => try writer.print("{x}", .{val.toFloat(f64, zcu)}),
                        80 => try writer.print("{x}", .{val.toFloat(f80, zcu)}),
                        128 => try writer.print("{x}", .{f128_val}),
                        else => unreachable,
                    }
                    try writer.writeAll(", ");
                    empty = false;
                } else {
                    // isSignalNan is equivalent to isNan currently, and MSVC doesn't have nans, so prefer nan
                    const operation = if (std.math.isNan(f128_val))
                        "nan"
                    else if (std.math.isSignalNan(f128_val))
                        "nans"
                    else if (std.math.isInf(f128_val))
                        "inf"
                    else
                        unreachable;

                    if (location == .StaticInitializer) {
                        if (!std.math.isNan(f128_val) and std.math.isSignalNan(f128_val))
                            return dg.fail("TODO: C backend: implement nans rendering in static initializers", .{});

                        // MSVC doesn't have a way to define a custom or signaling NaN value in a constant expression

                        // TODO: Re-enable this check, otherwise we're writing qnan bit patterns on msvc incorrectly
                        // if (std.math.isNan(f128_val) and f128_val != std.math.nan(f128))
                        //     return dg.fail("Only quiet nans are supported in global variable initializers", .{});
                    }

                    try writer.writeAll("zig_");
                    try writer.writeAll(if (location == .StaticInitializer) "init" else "make");
                    try writer.writeAll("_special_");
                    try dg.renderTypeForBuiltinFnName(writer, ty);
                    try writer.writeByte('(');
                    if (std.math.signbit(f128_val)) try writer.writeByte('-');
                    try writer.writeAll(", ");
                    try writer.writeAll(operation);
                    try writer.writeAll(", ");
                    if (std.math.isNan(f128_val)) switch (bits) {
                        // We only actually need to pass the significand, but it will get
                        // properly masked anyway, so just pass the whole value.
                        16 => try writer.print("\"0x{x}\"", .{@as(u16, @bitCast(val.toFloat(f16, zcu)))}),
                        32 => try writer.print("\"0x{x}\"", .{@as(u32, @bitCast(val.toFloat(f32, zcu)))}),
                        64 => try writer.print("\"0x{x}\"", .{@as(u64, @bitCast(val.toFloat(f64, zcu)))}),
                        80 => try writer.print("\"0x{x}\"", .{@as(u80, @bitCast(val.toFloat(f80, zcu)))}),
                        128 => try writer.print("\"0x{x}\"", .{@as(u128, @bitCast(f128_val))}),
                        else => unreachable,
                    };
                    try writer.writeAll(", ");
                    empty = false;
                }
                try writer.print("{f}", .{try dg.fmtIntLiteralHex(
                    try pt.intValue_big(repr_ty, repr_val_big.toConst()),
                    location,
                )});
                if (!empty) try writer.writeByte(')');
            },
            .slice => |slice| {
                const aggregate = ctype.info(ctype_pool).aggregate;
                if (!location.isInitializer()) {
                    try writer.writeByte('(');
                    try dg.renderCType(writer, ctype);
                    try writer.writeByte(')');
                }
                try writer.writeByte('{');
                for (0..aggregate.fields.len) |field_index| {
                    if (field_index > 0) try writer.writeByte(',');
                    try dg.renderValue(writer, Value.fromInterned(
                        switch (aggregate.fields.at(field_index, ctype_pool).name.index) {
                            .ptr => slice.ptr,
                            .len => slice.len,
                            else => unreachable,
                        },
                    ), initializer_type);
                }
                try writer.writeByte('}');
            },
            .ptr => {
                var arena = std.heap.ArenaAllocator.init(zcu.gpa);
                defer arena.deinit();
                const derivation = try val.pointerDerivation(arena.allocator(), pt);
                try dg.renderPointer(writer, derivation, location);
            },
            .opt => |opt| switch (ctype.info(ctype_pool)) {
                .basic => if (ctype.isBool()) try writer.writeAll(switch (opt.val) {
                    .none => "true",
                    else => "false",
                }) else switch (opt.val) {
                    .none => try writer.writeAll("0"),
                    else => |payload| switch (ip.indexToKey(payload)) {
                        .undef => |err_ty| try dg.renderUndefValue(
                            writer,
                            .fromInterned(err_ty),
                            location,
                        ),
                        .err => |err| try dg.renderErrorName(writer, err.name),
                        else => unreachable,
                    },
                },
                .pointer => switch (opt.val) {
                    .none => try writer.writeAll("NULL"),
                    else => |payload| try dg.renderValue(writer, Value.fromInterned(payload), location),
                },
                .aligned, .array, .vector, .fwd_decl, .function => unreachable,
                .aggregate => |aggregate| {
                    switch (opt.val) {
                        .none => {},
                        else => |payload| switch (aggregate.fields.at(0, ctype_pool).name.index) {
                            .is_null, .payload => {},
                            .ptr, .len => return dg.renderValue(
                                writer,
                                Value.fromInterned(payload),
                                location,
                            ),
                            else => unreachable,
                        },
                    }
                    if (!location.isInitializer()) {
                        try writer.writeByte('(');
                        try dg.renderCType(writer, ctype);
                        try writer.writeByte(')');
                    }
                    try writer.writeByte('{');
                    for (0..aggregate.fields.len) |field_index| {
                        if (field_index > 0) try writer.writeByte(',');
                        switch (aggregate.fields.at(field_index, ctype_pool).name.index) {
                            .is_null => try writer.writeAll(switch (opt.val) {
                                .none => "true",
                                else => "false",
                            }),
                            .payload => switch (opt.val) {
                                .none => try dg.renderUndefValue(
                                    writer,
                                    ty.optionalChild(zcu),
                                    initializer_type,
                                ),
                                else => |payload| try dg.renderValue(
                                    writer,
                                    Value.fromInterned(payload),
                                    initializer_type,
                                ),
                            },
                            .ptr => try writer.writeAll("NULL"),
                            .len => try dg.renderUndefValue(writer, .usize, initializer_type),
                            else => unreachable,
                        }
                    }
                    try writer.writeByte('}');
                },
            },
            .aggregate => switch (ip.indexToKey(ty.toIntern())) {
                .array_type, .vector_type => {
                    if (location == .FunctionArgument) {
                        try writer.writeByte('(');
                        try dg.renderCType(writer, ctype);
                        try writer.writeByte(')');
                    }
                    const ai = ty.arrayInfo(zcu);
                    if (ai.elem_type.eql(.u8, zcu)) {
                        var literal: StringLiteral = .init(writer, ty.arrayLenIncludingSentinel(zcu));
                        try literal.start();
                        var index: usize = 0;
                        while (index < ai.len) : (index += 1) {
                            const elem_val = try val.elemValue(pt, index);
                            const elem_val_u8: u8 = if (elem_val.isUndef(zcu))
                                undefPattern(u8)
                            else
                                @intCast(elem_val.toUnsignedInt(zcu));
                            try literal.writeChar(elem_val_u8);
                        }
                        if (ai.sentinel) |s| {
                            const s_u8: u8 = @intCast(s.toUnsignedInt(zcu));
                            if (s_u8 != 0) try literal.writeChar(s_u8);
                        }
                        try literal.end();
                    } else {
                        try writer.writeByte('{');
                        var index: usize = 0;
                        while (index < ai.len) : (index += 1) {
                            if (index != 0) try writer.writeByte(',');
                            const elem_val = try val.elemValue(pt, index);
                            try dg.renderValue(writer, elem_val, initializer_type);
                        }
                        if (ai.sentinel) |s| {
                            if (index != 0) try writer.writeByte(',');
                            try dg.renderValue(writer, s, initializer_type);
                        }
                        try writer.writeByte('}');
                    }
                },
                .tuple_type => |tuple| {
                    if (!location.isInitializer()) {
                        try writer.writeByte('(');
                        try dg.renderCType(writer, ctype);
                        try writer.writeByte(')');
                    }

                    try writer.writeByte('{');
                    var empty = true;
                    for (0..tuple.types.len) |field_index| {
                        const comptime_val = tuple.values.get(ip)[field_index];
                        if (comptime_val != .none) continue;
                        const field_ty: Type = .fromInterned(tuple.types.get(ip)[field_index]);
                        if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;

                        if (!empty) try writer.writeByte(',');

                        const field_val = Value.fromInterned(
                            switch (ip.indexToKey(val.toIntern()).aggregate.storage) {
                                .bytes => |bytes| try pt.intern(.{ .int = .{
                                    .ty = field_ty.toIntern(),
                                    .storage = .{ .u64 = bytes.at(field_index, ip) },
                                } }),
                                .elems => |elems| elems[field_index],
                                .repeated_elem => |elem| elem,
                            },
                        );
                        try dg.renderValue(writer, field_val, initializer_type);

                        empty = false;
                    }
                    try writer.writeByte('}');
                },
                .struct_type => {
                    const loaded_struct = ip.loadStructType(ty.toIntern());
                    switch (loaded_struct.layout) {
                        .auto, .@"extern" => {
                            if (!location.isInitializer()) {
                                try writer.writeByte('(');
                                try dg.renderCType(writer, ctype);
                                try writer.writeByte(')');
                            }

                            try writer.writeByte('{');
                            var field_it = loaded_struct.iterateRuntimeOrder(ip);
                            var need_comma = false;
                            while (field_it.next()) |field_index| {
                                const field_ty: Type = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
                                if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;

                                if (need_comma) try writer.writeByte(',');
                                need_comma = true;
                                const field_val = switch (ip.indexToKey(val.toIntern()).aggregate.storage) {
                                    .bytes => |bytes| try pt.intern(.{ .int = .{
                                        .ty = field_ty.toIntern(),
                                        .storage = .{ .u64 = bytes.at(field_index, ip) },
                                    } }),
                                    .elems => |elems| elems[field_index],
                                    .repeated_elem => |elem| elem,
                                };
                                try dg.renderValue(writer, Value.fromInterned(field_val), initializer_type);
                            }
                            try writer.writeByte('}');
                        },
                        .@"packed" => {
                            const int_info = ty.intInfo(zcu);

                            const bits = Type.smallestUnsignedBits(int_info.bits - 1);
                            const bit_offset_ty = try pt.intType(.unsigned, bits);

                            var bit_offset: u64 = 0;
                            var eff_num_fields: usize = 0;

                            for (0..loaded_struct.field_types.len) |field_index| {
                                const field_ty: Type = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
                                if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;
                                eff_num_fields += 1;
                            }

                            if (eff_num_fields == 0) {
                                try writer.writeByte('(');
                                try dg.renderUndefValue(writer, ty, location);
                                try writer.writeByte(')');
                            } else if (ty.bitSize(zcu) > 64) {
                                // zig_or_u128(zig_or_u128(zig_shl_u128(a, a_off), zig_shl_u128(b, b_off)), zig_shl_u128(c, c_off))
                                var num_or = eff_num_fields - 1;
                                while (num_or > 0) : (num_or -= 1) {
                                    try writer.writeAll("zig_or_");
                                    try dg.renderTypeForBuiltinFnName(writer, ty);
                                    try writer.writeByte('(');
                                }

                                var eff_index: usize = 0;
                                var needs_closing_paren = false;
                                for (0..loaded_struct.field_types.len) |field_index| {
                                    const field_ty: Type = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
                                    if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;

                                    const field_val = switch (ip.indexToKey(val.toIntern()).aggregate.storage) {
                                        .bytes => |bytes| try pt.intern(.{ .int = .{
                                            .ty = field_ty.toIntern(),
                                            .storage = .{ .u64 = bytes.at(field_index, ip) },
                                        } }),
                                        .elems => |elems| elems[field_index],
                                        .repeated_elem => |elem| elem,
                                    };
                                    const cast_context = IntCastContext{ .value = .{ .value = Value.fromInterned(field_val) } };
                                    if (bit_offset != 0) {
                                        try writer.writeAll("zig_shl_");
                                        try dg.renderTypeForBuiltinFnName(writer, ty);
                                        try writer.writeByte('(');
                                        try dg.renderIntCast(writer, ty, cast_context, field_ty, .FunctionArgument);
                                        try writer.writeAll(", ");
                                        try dg.renderValue(writer, try pt.intValue(bit_offset_ty, bit_offset), .FunctionArgument);
                                        try writer.writeByte(')');
                                    } else {
                                        try dg.renderIntCast(writer, ty, cast_context, field_ty, .FunctionArgument);
                                    }

                                    if (needs_closing_paren) try writer.writeByte(')');
                                    if (eff_index != eff_num_fields - 1) try writer.writeAll(", ");

                                    bit_offset += field_ty.bitSize(zcu);
                                    needs_closing_paren = true;
                                    eff_index += 1;
                                }
                            } else {
                                try writer.writeByte('(');
                                // a << a_off | b << b_off | c << c_off
                                var empty = true;
                                for (0..loaded_struct.field_types.len) |field_index| {
                                    const field_ty: Type = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
                                    if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;

                                    if (!empty) try writer.writeAll(" | ");
                                    try writer.writeByte('(');
                                    try dg.renderCType(writer, ctype);
                                    try writer.writeByte(')');

                                    const field_val = switch (ip.indexToKey(val.toIntern()).aggregate.storage) {
                                        .bytes => |bytes| try pt.intern(.{ .int = .{
                                            .ty = field_ty.toIntern(),
                                            .storage = .{ .u64 = bytes.at(field_index, ip) },
                                        } }),
                                        .elems => |elems| elems[field_index],
                                        .repeated_elem => |elem| elem,
                                    };

                                    const field_int_info: std.builtin.Type.Int = if (field_ty.isAbiInt(zcu))
                                        field_ty.intInfo(zcu)
                                    else
                                        .{ .signedness = .unsigned, .bits = undefined };
                                    switch (field_int_info.signedness) {
                                        .signed => {
                                            try writer.writeByte('(');
                                            try dg.renderValue(writer, Value.fromInterned(field_val), .Other);
                                            try writer.writeAll(" & ");
                                            const field_uint_ty = try pt.intType(.unsigned, field_int_info.bits);
                                            try dg.renderValue(writer, try field_uint_ty.maxIntScalar(pt, field_uint_ty), .Other);
                                            try writer.writeByte(')');
                                        },
                                        .unsigned => try dg.renderValue(writer, Value.fromInterned(field_val), .Other),
                                    }
                                    if (bit_offset != 0) {
                                        try writer.writeAll(" << ");
                                        try dg.renderValue(writer, try pt.intValue(bit_offset_ty, bit_offset), .FunctionArgument);
                                    }

                                    bit_offset += field_ty.bitSize(zcu);
                                    empty = false;
                                }
                                try writer.writeByte(')');
                            }
                        },
                    }
                },
                else => unreachable,
            },
            .un => |un| {
                const loaded_union = ip.loadUnionType(ty.toIntern());
                if (un.tag == .none) {
                    const backing_ty = try ty.unionBackingType(pt);
                    switch (loaded_union.flagsUnordered(ip).layout) {
                        .@"packed" => {
                            if (!location.isInitializer()) {
                                try writer.writeByte('(');
                                try dg.renderType(writer, backing_ty);
                                try writer.writeByte(')');
                            }
                            try dg.renderValue(writer, Value.fromInterned(un.val), location);
                        },
                        .@"extern" => {
                            if (location == .StaticInitializer) {
                                return dg.fail("TODO: C backend: implement extern union backing type rendering in static initializers", .{});
                            }

                            const ptr_ty = try pt.singleConstPtrType(ty);
                            try writer.writeAll("*((");
                            try dg.renderType(writer, ptr_ty);
                            try writer.writeAll(")(");
                            try dg.renderType(writer, backing_ty);
                            try writer.writeAll("){");
                            try dg.renderValue(writer, Value.fromInterned(un.val), location);
                            try writer.writeAll("})");
                        },
                        else => unreachable,
                    }
                } else {
                    if (!location.isInitializer()) {
                        try writer.writeByte('(');
                        try dg.renderCType(writer, ctype);
                        try writer.writeByte(')');
                    }

                    const field_index = zcu.unionTagFieldIndex(loaded_union, Value.fromInterned(un.tag)).?;
                    const field_ty: Type = .fromInterned(loaded_union.field_types.get(ip)[field_index]);
                    const field_name = loaded_union.loadTagType(ip).names.get(ip)[field_index];
                    if (loaded_union.flagsUnordered(ip).layout == .@"packed") {
                        if (field_ty.hasRuntimeBits(zcu)) {
                            if (field_ty.isPtrAtRuntime(zcu)) {
                                try writer.writeByte('(');
                                try dg.renderCType(writer, ctype);
                                try writer.writeByte(')');
                            } else if (field_ty.zigTypeTag(zcu) == .float) {
                                try writer.writeByte('(');
                                try dg.renderCType(writer, ctype);
                                try writer.writeByte(')');
                            }
                            try dg.renderValue(writer, Value.fromInterned(un.val), location);
                        } else try writer.writeAll("0");
                        return;
                    }

                    const has_tag = loaded_union.hasTag(ip);
                    if (has_tag) try writer.writeByte('{');
                    const aggregate = ctype.info(ctype_pool).aggregate;
                    for (0..if (has_tag) aggregate.fields.len else 1) |outer_field_index| {
                        if (outer_field_index > 0) try writer.writeByte(',');
                        switch (if (has_tag)
                            aggregate.fields.at(outer_field_index, ctype_pool).name.index
                        else
                            .payload) {
                            .tag => try dg.renderValue(
                                writer,
                                Value.fromInterned(un.tag),
                                initializer_type,
                            ),
                            .payload => {
                                try writer.writeByte('{');
                                if (field_ty.hasRuntimeBits(zcu)) {
                                    try writer.print(" .{f} = ", .{fmtIdentSolo(field_name.toSlice(ip))});
                                    try dg.renderValue(
                                        writer,
                                        Value.fromInterned(un.val),
                                        initializer_type,
                                    );
                                    try writer.writeByte(' ');
                                } else for (0..loaded_union.field_types.len) |inner_field_index| {
                                    const inner_field_ty: Type = .fromInterned(
                                        loaded_union.field_types.get(ip)[inner_field_index],
                                    );
                                    if (!inner_field_ty.hasRuntimeBits(zcu)) continue;
                                    try dg.renderUndefValue(writer, inner_field_ty, initializer_type);
                                    break;
                                }
                                try writer.writeByte('}');
                            },
                            else => unreachable,
                        }
                    }
                    if (has_tag) try writer.writeByte('}');
                }
            },
        }
    }

    fn renderUndefValue(
        dg: *DeclGen,
        writer: anytype,
        ty: Type,
        location: ValueRenderLocation,
    ) error{ OutOfMemory, AnalysisFail }!void {
        const pt = dg.pt;
        const zcu = pt.zcu;
        const ip = &zcu.intern_pool;
        const target = &dg.mod.resolved_target.result;
        const ctype_pool = &dg.ctype_pool;

        const initializer_type: ValueRenderLocation = switch (location) {
            .StaticInitializer => .StaticInitializer,
            else => .Initializer,
        };

        const safety_on = switch (zcu.optimizeMode()) {
            .Debug, .ReleaseSafe => true,
            .ReleaseFast, .ReleaseSmall => false,
        };

        const ctype = try dg.ctypeFromType(ty, location.toCTypeKind());
        switch (ty.toIntern()) {
            .c_longdouble_type,
            .f16_type,
            .f32_type,
            .f64_type,
            .f80_type,
            .f128_type,
            => {
                const bits = ty.floatBits(target);
                // All unsigned ints matching float types are pre-allocated.
                const repr_ty = dg.pt.intType(.unsigned, bits) catch unreachable;

                try writer.writeAll("zig_make_");
                try dg.renderTypeForBuiltinFnName(writer, ty);
                try writer.writeByte('(');
                switch (bits) {
                    16 => try writer.print("{x}", .{@as(f16, @bitCast(undefPattern(i16)))}),
                    32 => try writer.print("{x}", .{@as(f32, @bitCast(undefPattern(i32)))}),
                    64 => try writer.print("{x}", .{@as(f64, @bitCast(undefPattern(i64)))}),
                    80 => try writer.print("{x}", .{@as(f80, @bitCast(undefPattern(i80)))}),
                    128 => try writer.print("{x}", .{@as(f128, @bitCast(undefPattern(i128)))}),
                    else => unreachable,
                }
                try writer.writeAll(", ");
                try dg.renderUndefValue(writer, repr_ty, .FunctionArgument);
                return writer.writeByte(')');
            },
            .bool_type => try writer.writeAll(if (safety_on) "0xaa" else "false"),
            else => switch (ip.indexToKey(ty.toIntern())) {
                .simple_type,
                .int_type,
                .enum_type,
                .error_set_type,
                .inferred_error_set_type,
                => return writer.print("{f}", .{
                    try dg.fmtIntLiteralHex(try pt.undefValue(ty), location),
                }),
                .ptr_type => |ptr_type| switch (ptr_type.flags.size) {
                    .one, .many, .c => {
                        try writer.writeAll("((");
                        try dg.renderCType(writer, ctype);
                        return writer.print("){f})", .{
                            try dg.fmtIntLiteralHex(.undef_usize, .Other),
                        });
                    },
                    .slice => {
                        if (!location.isInitializer()) {
                            try writer.writeByte('(');
                            try dg.renderCType(writer, ctype);
                            try writer.writeByte(')');
                        }

                        try writer.writeAll("{(");
                        const ptr_ty = ty.slicePtrFieldType(zcu);
                        try dg.renderType(writer, ptr_ty);
                        return writer.print("){f}, {0fx}}}", .{
                            try dg.fmtIntLiteralHex(.undef_usize, .Other),
                        });
                    },
                },
                .opt_type => |child_type| switch (ctype.info(ctype_pool)) {
                    .basic, .pointer => try dg.renderUndefValue(
                        writer,
                        .fromInterned(if (ctype.isBool()) .bool_type else child_type),
                        location,
                    ),
                    .aligned, .array, .vector, .fwd_decl, .function => unreachable,
                    .aggregate => |aggregate| {
                        switch (aggregate.fields.at(0, ctype_pool).name.index) {
                            .is_null, .payload => {},
                            .ptr, .len => return dg.renderUndefValue(
                                writer,
                                .fromInterned(child_type),
                                location,
                            ),
                            else => unreachable,
                        }
                        if (!location.isInitializer()) {
                            try writer.writeByte('(');
                            try dg.renderCType(writer, ctype);
                            try writer.writeByte(')');
                        }
                        try writer.writeByte('{');
                        for (0..aggregate.fields.len) |field_index| {
                            if (field_index > 0) try writer.writeByte(',');
                            try dg.renderUndefValue(writer, .fromInterned(
                                switch (aggregate.fields.at(field_index, ctype_pool).name.index) {
                                    .is_null => .bool_type,
                                    .payload => child_type,
                                    else => unreachable,
                                },
                            ), initializer_type);
                        }
                        try writer.writeByte('}');
                    },
                },
                .struct_type => {
                    const loaded_struct = ip.loadStructType(ty.toIntern());
                    switch (loaded_struct.layout) {
                        .auto, .@"extern" => {
                            if (!location.isInitializer()) {
                                try writer.writeByte('(');
                                try dg.renderCType(writer, ctype);
                                try writer.writeByte(')');
                            }

                            try writer.writeByte('{');
                            var field_it = loaded_struct.iterateRuntimeOrder(ip);
                            var need_comma = false;
                            while (field_it.next()) |field_index| {
                                const field_ty: Type = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
                                if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;

                                if (need_comma) try writer.writeByte(',');
                                need_comma = true;
                                try dg.renderUndefValue(writer, field_ty, initializer_type);
                            }
                            return writer.writeByte('}');
                        },
                        .@"packed" => return writer.print("{f}", .{
                            try dg.fmtIntLiteralHex(try pt.undefValue(ty), .Other),
                        }),
                    }
                },
                .tuple_type => |tuple_info| {
                    if (!location.isInitializer()) {
                        try writer.writeByte('(');
                        try dg.renderCType(writer, ctype);
                        try writer.writeByte(')');
                    }

                    try writer.writeByte('{');
                    var need_comma = false;
                    for (0..tuple_info.types.len) |field_index| {
                        if (tuple_info.values.get(ip)[field_index] != .none) continue;
                        const field_ty: Type = .fromInterned(tuple_info.types.get(ip)[field_index]);
                        if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;

                        if (need_comma) try writer.writeByte(',');
                        need_comma = true;
                        try dg.renderUndefValue(writer, field_ty, initializer_type);
                    }
                    return writer.writeByte('}');
                },
                .union_type => {
                    const loaded_union = ip.loadUnionType(ty.toIntern());
                    switch (loaded_union.flagsUnordered(ip).layout) {
                        .auto, .@"extern" => {
                            if (!location.isInitializer()) {
                                try writer.writeByte('(');
                                try dg.renderCType(writer, ctype);
                                try writer.writeByte(')');
                            }

                            const has_tag = loaded_union.hasTag(ip);
                            if (has_tag) try writer.writeByte('{');
                            const aggregate = ctype.info(ctype_pool).aggregate;
                            for (0..if (has_tag) aggregate.fields.len else 1) |outer_field_index| {
                                if (outer_field_index > 0) try writer.writeByte(',');
                                switch (if (has_tag)
                                    aggregate.fields.at(outer_field_index, ctype_pool).name.index
                                else
                                    .payload) {
                                    .tag => try dg.renderUndefValue(
                                        writer,
                                        .fromInterned(loaded_union.enum_tag_ty),
                                        initializer_type,
                                    ),
                                    .payload => {
                                        try writer.writeByte('{');
                                        for (0..loaded_union.field_types.len) |inner_field_index| {
                                            const inner_field_ty: Type = .fromInterned(
                                                loaded_union.field_types.get(ip)[inner_field_index],
                                            );
                                            if (!inner_field_ty.hasRuntimeBits(pt.zcu)) continue;
                                            try dg.renderUndefValue(
                                                writer,
                                                inner_field_ty,
                                                initializer_type,
                                            );
                                            break;
                                        }
                                        try writer.writeByte('}');
                                    },
                                    else => unreachable,
                                }
                            }
                            if (has_tag) try writer.writeByte('}');
                        },
                        .@"packed" => return writer.print("{f}", .{
                            try dg.fmtIntLiteralHex(try pt.undefValue(ty), .Other),
                        }),
                    }
                },
                .error_union_type => |error_union_type| switch (ctype.info(ctype_pool)) {
                    .basic => try dg.renderUndefValue(
                        writer,
                        .fromInterned(error_union_type.error_set_type),
                        location,
                    ),
                    .pointer, .aligned, .array, .vector, .fwd_decl, .function => unreachable,
                    .aggregate => |aggregate| {
                        if (!location.isInitializer()) {
                            try writer.writeByte('(');
                            try dg.renderCType(writer, ctype);
                            try writer.writeByte(')');
                        }
                        try writer.writeByte('{');
                        for (0..aggregate.fields.len) |field_index| {
                            if (field_index > 0) try writer.writeByte(',');
                            try dg.renderUndefValue(
                                writer,
                                .fromInterned(
                                    switch (aggregate.fields.at(field_index, ctype_pool).name.index) {
                                        .@"error" => error_union_type.error_set_type,
                                        .payload => error_union_type.payload_type,
                                        else => unreachable,
                                    },
                                ),
                                initializer_type,
                            );
                        }
                        try writer.writeByte('}');
                    },
                },
                .array_type, .vector_type => {
                    const ai = ty.arrayInfo(zcu);
                    if (ai.elem_type.eql(.u8, zcu)) {
                        const c_len = ty.arrayLenIncludingSentinel(zcu);
                        var literal: StringLiteral = .init(writer, c_len);
                        try literal.start();
                        var index: u64 = 0;
                        while (index < c_len) : (index += 1)
                            try literal.writeChar(0xaa);
                        return literal.end();
                    } else {
                        if (!location.isInitializer()) {
                            try writer.writeByte('(');
                            try dg.renderCType(writer, ctype);
                            try writer.writeByte(')');
                        }

                        try writer.writeByte('{');
                        const c_len = ty.arrayLenIncludingSentinel(zcu);
                        var index: u64 = 0;
                        while (index < c_len) : (index += 1) {
                            if (index > 0) try writer.writeAll(", ");
                            try dg.renderUndefValue(writer, ty.childType(zcu), initializer_type);
                        }
                        return writer.writeByte('}');
                    }
                },
                .anyframe_type,
                .opaque_type,
                .func_type,
                => unreachable,

                .undef,
                .simple_value,
                .variable,
                .@"extern",
                .func,
                .int,
                .err,
                .error_union,
                .enum_literal,
                .enum_tag,
                .empty_enum_value,
                .float,
                .ptr,
                .slice,
                .opt,
                .aggregate,
                .un,
                .memoized_call,
                => unreachable, // values, not types
            },
        }
    }

    fn renderFunctionSignature(
        dg: *DeclGen,
        w: anytype,
        fn_val: Value,
        fn_align: InternPool.Alignment,
        kind: CType.Kind,
        name: union(enum) {
            nav: InternPool.Nav.Index,
            fmt_ctype_pool_string: std.fmt.Formatter(CTypePoolStringFormatData, formatCTypePoolString),
            @"export": struct {
                main_name: InternPool.NullTerminatedString,
                extern_name: InternPool.NullTerminatedString,
            },
        },
    ) !void {
        const zcu = dg.pt.zcu;
        const ip = &zcu.intern_pool;

        const fn_ty = fn_val.typeOf(zcu);
        const fn_ctype = try dg.ctypeFromType(fn_ty, kind);

        const fn_info = zcu.typeToFunc(fn_ty).?;
        if (fn_info.cc == .naked) {
            switch (kind) {
                .forward => try w.writeAll("zig_naked_decl "),
                .complete => try w.writeAll("zig_naked "),
                else => unreachable,
            }
        }

        if (fn_val.getFunction(zcu)) |func| {
            const func_analysis = func.analysisUnordered(ip);

            if (func_analysis.branch_hint == .cold)
                try w.writeAll("zig_cold ");

            if (kind == .complete and func_analysis.disable_intrinsics or dg.mod.no_builtin)
                try w.writeAll("zig_no_builtin ");
        }

        if (fn_info.return_type == .noreturn_type) try w.writeAll("zig_noreturn ");

        var trailing = try renderTypePrefix(dg.pass, &dg.ctype_pool, zcu, w, fn_ctype, .suffix, .{});

        if (toCallingConvention(fn_info.cc, zcu)) |call_conv| {
            try w.print("{}zig_callconv({s})", .{ trailing, call_conv });
            trailing = .maybe_space;
        }

        try w.print("{}", .{trailing});
        switch (name) {
            .nav => |nav| try dg.renderNavName(w, nav),
            .fmt_ctype_pool_string => |fmt| try w.print("{f}", .{fmt}),
            .@"export" => |@"export"| try w.print("{f}", .{fmtIdentSolo(@"export".extern_name.toSlice(ip))}),
        }

        try renderTypeSuffix(
            dg.pass,
            &dg.ctype_pool,
            zcu,
            w,
            fn_ctype,
            .suffix,
            CQualifiers.init(.{ .@"const" = switch (kind) {
                .forward => false,
                .complete => true,
                else => unreachable,
            } }),
        );

        switch (kind) {
            .forward => {
                if (fn_align.toByteUnits()) |a| try w.print(" zig_align_fn({})", .{a});
                switch (name) {
                    .nav, .fmt_ctype_pool_string => {},
                    .@"export" => |@"export"| {
                        const extern_name = @"export".extern_name.toSlice(ip);
                        const is_mangled = isMangledIdent(extern_name, true);
                        const is_export = @"export".extern_name != @"export".main_name;
                        if (is_mangled and is_export) {
                            try w.print(" zig_mangled_export({f}, {f}, {f})", .{
                                fmtIdentSolo(extern_name),
                                fmtStringLiteral(extern_name, null),
                                fmtStringLiteral(@"export".main_name.toSlice(ip), null),
                            });
                        } else if (is_mangled) {
                            try w.print(" zig_mangled({f}, {f})", .{
                                fmtIdentSolo(extern_name), fmtStringLiteral(extern_name, null),
                            });
                        } else if (is_export) {
                            try w.print(" zig_export({f}, {f})", .{
                                fmtStringLiteral(@"export".main_name.toSlice(ip), null),
                                fmtStringLiteral(extern_name, null),
                            });
                        }
                    },
                }
            },
            .complete => {},
            else => unreachable,
        }
    }

    fn ctypeFromType(dg: *DeclGen, ty: Type, kind: CType.Kind) !CType {
        defer std.debug.assert(dg.scratch.items.len == 0);
        return dg.ctype_pool.fromType(dg.gpa, &dg.scratch, ty, dg.pt, dg.mod, kind);
    }

    fn byteSize(dg: *DeclGen, ctype: CType) u64 {
        return ctype.byteSize(&dg.ctype_pool, dg.mod);
    }

    /// Renders a type as a single identifier, generating intermediate typedefs
    /// if necessary.
    ///
    /// This is guaranteed to be valid in both typedefs and declarations/definitions.
    ///
    /// There are three type formats in total that we support rendering:
    ///   | Function            | Example 1 (*u8) | Example 2 ([10]*u8) |
    ///   |---------------------|-----------------|---------------------|
    ///   | `renderTypeAndName` | "uint8_t *name" | "uint8_t *name[10]" |
    ///   | `renderType`        | "uint8_t *"     | "uint8_t *[10]"     |
    ///
    fn renderType(dg: *DeclGen, w: anytype, t: Type) error{OutOfMemory}!void {
        try dg.renderCType(w, try dg.ctypeFromType(t, .complete));
    }

    fn renderCType(dg: *DeclGen, w: anytype, ctype: CType) error{OutOfMemory}!void {
        _ = try renderTypePrefix(dg.pass, &dg.ctype_pool, dg.pt.zcu, w, ctype, .suffix, .{});
        try renderTypeSuffix(dg.pass, &dg.ctype_pool, dg.pt.zcu, w, ctype, .suffix, .{});
    }

    const IntCastContext = union(enum) {
        c_value: struct {
            f: *Function,
            value: CValue,
            v: Vectorize,
        },
        value: struct {
            value: Value,
        },

        pub fn writeValue(self: *const IntCastContext, dg: *DeclGen, w: anytype, location: ValueRenderLocation) !void {
            switch (self.*) {
                .c_value => |v| {
                    try v.f.writeCValue(w, v.value, location);
                    try v.v.elem(v.f, w);
                },
                .value => |v| try dg.renderValue(w, v.value, location),
            }
        }
    };
    fn intCastIsNoop(dg: *DeclGen, dest_ty: Type, src_ty: Type) bool {
        const pt = dg.pt;
        const zcu = pt.zcu;
        const dest_bits = dest_ty.bitSize(zcu);
        const dest_int_info = dest_ty.intInfo(pt.zcu);

        const src_is_ptr = src_ty.isPtrAtRuntime(pt.zcu);
        const src_eff_ty: Type = if (src_is_ptr) switch (dest_int_info.signedness) {
            .unsigned => .usize,
            .signed => .isize,
        } else src_ty;

        const src_bits = src_eff_ty.bitSize(zcu);
        const src_int_info = if (src_eff_ty.isAbiInt(pt.zcu)) src_eff_ty.intInfo(pt.zcu) else null;
        if (dest_bits <= 64 and src_bits <= 64) {
            const needs_cast = src_int_info == null or
                (toCIntBits(dest_int_info.bits) != toCIntBits(src_int_info.?.bits) or
                    dest_int_info.signedness != src_int_info.?.signedness);
            return !needs_cast and !src_is_ptr;
        } else return false;
    }
    /// Renders a cast to an int type, from either an int or a pointer.
    ///
    /// Some platforms don't have 128 bit integers, so we need to use
    /// the zig_make_ and zig_lo_ macros in those cases.
    ///
    ///   | Dest type bits   | Src type         | Result
    ///   |------------------|------------------|---------------------------|
    ///   | < 64 bit integer | pointer          | (zig_<dest_ty>)(zig_<u|i>size)src
    ///   | < 64 bit integer | < 64 bit integer | (zig_<dest_ty>)src
    ///   | < 64 bit integer | > 64 bit integer | zig_lo(src)
    ///   | > 64 bit integer | pointer          | zig_make_<dest_ty>(0, (zig_<u|i>size)src)
    ///   | > 64 bit integer | < 64 bit integer | zig_make_<dest_ty>(0, src)
    ///   | > 64 bit integer | > 64 bit integer | zig_make_<dest_ty>(zig_hi_<src_ty>(src), zig_lo_<src_ty>(src))
    fn renderIntCast(
        dg: *DeclGen,
        w: anytype,
        dest_ty: Type,
        context: IntCastContext,
        src_ty: Type,
        location: ValueRenderLocation,
    ) !void {
        const pt = dg.pt;
        const zcu = pt.zcu;
        const dest_bits = dest_ty.bitSize(zcu);
        const dest_int_info = dest_ty.intInfo(zcu);

        const src_is_ptr = src_ty.isPtrAtRuntime(zcu);
        const src_eff_ty: Type = if (src_is_ptr) switch (dest_int_info.signedness) {
            .unsigned => .usize,
            .signed => .isize,
        } else src_ty;

        const src_bits = src_eff_ty.bitSize(zcu);
        const src_int_info = if (src_eff_ty.isAbiInt(zcu)) src_eff_ty.intInfo(zcu) else null;
        if (dest_bits <= 64 and src_bits <= 64) {
            const needs_cast = src_int_info == null or
                (toCIntBits(dest_int_info.bits) != toCIntBits(src_int_info.?.bits) or
                    dest_int_info.signedness != src_int_info.?.signedness);

            if (needs_cast) {
                try w.writeByte('(');
                try dg.renderType(w, dest_ty);
                try w.writeByte(')');
            }
            if (src_is_ptr) {
                try w.writeByte('(');
                try dg.renderType(w, src_eff_ty);
                try w.writeByte(')');
            }
            try context.writeValue(dg, w, location);
        } else if (dest_bits <= 64 and src_bits > 64) {
            assert(!src_is_ptr);
            if (dest_bits < 64) {
                try w.writeByte('(');
                try dg.renderType(w, dest_ty);
                try w.writeByte(')');
            }
            try w.writeAll("zig_lo_");
            try dg.renderTypeForBuiltinFnName(w, src_eff_ty);
            try w.writeByte('(');
            try context.writeValue(dg, w, .FunctionArgument);
            try w.writeByte(')');
        } else if (dest_bits > 64 and src_bits <= 64) {
            try w.writeAll("zig_make_");
            try dg.renderTypeForBuiltinFnName(w, dest_ty);
            try w.writeAll("(0, ");
            if (src_is_ptr) {
                try w.writeByte('(');
                try dg.renderType(w, src_eff_ty);
                try w.writeByte(')');
            }
            try context.writeValue(dg, w, .FunctionArgument);
            try w.writeByte(')');
        } else {
            assert(!src_is_ptr);
            try w.writeAll("zig_make_");
            try dg.renderTypeForBuiltinFnName(w, dest_ty);
            try w.writeAll("(zig_hi_");
            try dg.renderTypeForBuiltinFnName(w, src_eff_ty);
            try w.writeByte('(');
            try context.writeValue(dg, w, .FunctionArgument);
            try w.writeAll("), zig_lo_");
            try dg.renderTypeForBuiltinFnName(w, src_eff_ty);
            try w.writeByte('(');
            try context.writeValue(dg, w, .FunctionArgument);
            try w.writeAll("))");
        }
    }

    /// Renders a type and name in field declaration/definition format.
    ///
    /// There are three type formats in total that we support rendering:
    ///   | Function            | Example 1 (*u8) | Example 2 ([10]*u8) |
    ///   |---------------------|-----------------|---------------------|
    ///   | `renderTypeAndName` | "uint8_t *name" | "uint8_t *name[10]" |
    ///   | `renderType`        | "uint8_t *"     | "uint8_t *[10]"     |
    ///
    fn renderTypeAndName(
        dg: *DeclGen,
        w: anytype,
        ty: Type,
        name: CValue,
        qualifiers: CQualifiers,
        alignment: Alignment,
        kind: CType.Kind,
    ) error{ OutOfMemory, AnalysisFail }!void {
        try dg.renderCTypeAndName(
            w,
            try dg.ctypeFromType(ty, kind),
            name,
            qualifiers,
            CType.AlignAs.fromAlignment(.{
                .@"align" = alignment,
                .abi = ty.abiAlignment(dg.pt.zcu),
            }),
        );
    }

    fn renderCTypeAndName(
        dg: *DeclGen,
        w: anytype,
        ctype: CType,
        name: CValue,
        qualifiers: CQualifiers,
        alignas: CType.AlignAs,
    ) error{ OutOfMemory, AnalysisFail }!void {
        const zcu = dg.pt.zcu;
        switch (alignas.abiOrder()) {
            .lt => try w.print("zig_under_align({}) ", .{alignas.toByteUnits()}),
            .eq => {},
            .gt => try w.print("zig_align({}) ", .{alignas.toByteUnits()}),
        }

        try w.print("{}", .{
            try renderTypePrefix(dg.pass, &dg.ctype_pool, zcu, w, ctype, .suffix, qualifiers),
        });
        try dg.writeName(w, name);
        try renderTypeSuffix(dg.pass, &dg.ctype_pool, zcu, w, ctype, .suffix, .{});
    }

    fn writeName(dg: *DeclGen, w: anytype, c_value: CValue) !void {
        switch (c_value) {
            .new_local, .local => |i| try w.print("t{d}", .{i}),
            .constant => |uav| try renderUavName(w, uav),
            .nav => |nav| try dg.renderNavName(w, nav),
            .identifier => |ident| try w.print("{f}", .{fmtIdentSolo(ident)}),
            else => unreachable,
        }
    }

    fn writeCValue(dg: *DeclGen, w: anytype, c_value: CValue) !void {
        switch (c_value) {
            .none, .new_local, .local, .local_ref => unreachable,
            .constant => |uav| try renderUavName(w, uav),
            .arg, .arg_array => unreachable,
            .field => |i| try w.print("f{d}", .{i}),
            .nav => |nav| try dg.renderNavName(w, nav),
            .nav_ref => |nav| {
                try w.writeByte('&');
                try dg.renderNavName(w, nav);
            },
            .undef => |ty| try dg.renderUndefValue(w, ty, .Other),
            .identifier => |ident| try w.print("{f}", .{fmtIdentSolo(ident)}),
            .payload_identifier => |ident| try w.print("{f}.{f}", .{
                fmtIdentSolo("payload"),
                fmtIdentSolo(ident),
            }),
            .ctype_pool_string => |string| try w.print("{f}", .{
                fmtCTypePoolString(string, &dg.ctype_pool, true),
            }),
        }
    }

    fn writeCValueDeref(dg: *DeclGen, w: anytype, c_value: CValue) !void {
        switch (c_value) {
            .none,
            .new_local,
            .local,
            .local_ref,
            .constant,
            .arg,
            .arg_array,
            .ctype_pool_string,
            => unreachable,
            .field => |i| try w.print("f{d}", .{i}),
            .nav => |nav| {
                try w.writeAll("(*");
                try dg.renderNavName(w, nav);
                try w.writeByte(')');
            },
            .nav_ref => |nav| try dg.renderNavName(w, nav),
            .undef => unreachable,
            .identifier => |ident| try w.print("(*{f})", .{fmtIdentSolo(ident)}),
            .payload_identifier => |ident| try w.print("(*{f}.{f})", .{
                fmtIdentSolo("payload"),
                fmtIdentSolo(ident),
            }),
        }
    }

    fn writeCValueMember(
        dg: *DeclGen,
        writer: anytype,
        c_value: CValue,
        member: CValue,
    ) error{ OutOfMemory, AnalysisFail }!void {
        try dg.writeCValue(writer, c_value);
        try writer.writeByte('.');
        try dg.writeCValue(writer, member);
    }

    fn writeCValueDerefMember(dg: *DeclGen, writer: anytype, c_value: CValue, member: CValue) !void {
        switch (c_value) {
            .none,
            .new_local,
            .local,
            .local_ref,
            .constant,
            .field,
            .undef,
            .arg,
            .arg_array,
            .ctype_pool_string,
            => unreachable,
            .nav, .identifier, .payload_identifier => {
                try dg.writeCValue(writer, c_value);
                try writer.writeAll("->");
            },
            .nav_ref => {
                try dg.writeCValueDeref(writer, c_value);
                try writer.writeByte('.');
            },
        }
        try dg.writeCValue(writer, member);
    }

    fn renderFwdDecl(
        dg: *DeclGen,
        nav_index: InternPool.Nav.Index,
        flags: packed struct {
            is_const: bool,
            is_threadlocal: bool,
            linkage: std.builtin.GlobalLinkage,
            visibility: std.builtin.SymbolVisibility,
        },
    ) !void {
        const zcu = dg.pt.zcu;
        const ip = &zcu.intern_pool;
        const nav = ip.getNav(nav_index);
        const fwd = dg.fwdDeclWriter();
        try fwd.writeAll(switch (flags.linkage) {
            .internal => "static ",
            .strong, .weak, .link_once => "zig_extern ",
        });
        switch (flags.linkage) {
            .internal, .strong => {},
            .weak => try fwd.writeAll("zig_weak_linkage "),
            .link_once => return dg.fail("TODO: CBE: implement linkonce linkage?", .{}),
        }
        switch (flags.linkage) {
            .internal => {},
            .strong, .weak, .link_once => try fwd.print("zig_visibility({s}) ", .{@tagName(flags.visibility)}),
        }
        if (flags.is_threadlocal and !dg.mod.single_threaded) try fwd.writeAll("zig_threadlocal ");
        try dg.renderTypeAndName(
            fwd,
            .fromInterned(nav.typeOf(ip)),
            .{ .nav = nav_index },
            CQualifiers.init(.{ .@"const" = flags.is_const }),
            nav.getAlignment(),
            .complete,
        );
        try fwd.writeAll(";\n");
    }

    fn renderNavName(dg: *DeclGen, writer: anytype, nav_index: InternPool.Nav.Index) !void {
        const zcu = dg.pt.zcu;
        const ip = &zcu.intern_pool;
        const nav = ip.getNav(nav_index);
        if (nav.getExtern(ip)) |@"extern"| {
            try writer.print("{f}", .{
                fmtIdentSolo(ip.getNav(@"extern".owner_nav).name.toSlice(ip)),
            });
        } else {
            // MSVC has a limit of 4095 character token length limit, and fmtIdent can (worst case),
            // expand to 3x the length of its input, but let's cut it off at a much shorter limit.
            const fqn_slice = ip.getNav(nav_index).fqn.toSlice(ip);
            try writer.print("{}__{d}", .{
                fmtIdentUnsolo(fqn_slice[0..@min(fqn_slice.len, 100)]),
                @intFromEnum(nav_index),
            });
        }
    }

    fn renderUavName(writer: anytype, uav: Value) !void {
        try writer.print("__anon_{d}", .{@intFromEnum(uav.toIntern())});
    }

    fn renderTypeForBuiltinFnName(dg: *DeclGen, writer: anytype, ty: Type) !void {
        try dg.renderCTypeForBuiltinFnName(writer, try dg.ctypeFromType(ty, .complete));
    }

    fn renderCTypeForBuiltinFnName(dg: *DeclGen, writer: anytype, ctype: CType) !void {
        switch (ctype.info(&dg.ctype_pool)) {
            else => |ctype_info| try writer.print("{c}{d}", .{
                if (ctype.isBool())
                    signAbbrev(.unsigned)
                else if (ctype.isInteger())
                    signAbbrev(ctype.signedness(dg.mod))
                else if (ctype.isFloat())
                    @as(u8, 'f')
                else if (ctype_info == .pointer)
                    @as(u8, 'p')
                else
                    return dg.fail("TODO: CBE: implement renderTypeForBuiltinFnName for {s} type", .{@tagName(ctype_info)}),
                if (ctype.isFloat()) ctype.floatActiveBits(dg.mod) else dg.byteSize(ctype) * 8,
            }),
            .array => try writer.writeAll("big"),
        }
    }

    fn renderBuiltinInfo(dg: *DeclGen, writer: anytype, ty: Type, info: BuiltinInfo) !void {
        const ctype = try dg.ctypeFromType(ty, .complete);
        const is_big = ctype.info(&dg.ctype_pool) == .array;
        switch (info) {
            .none => if (!is_big) return,
            .bits => {},
        }

        const pt = dg.pt;
        const zcu = pt.zcu;
        const int_info: std.builtin.Type.Int = if (ty.isAbiInt(zcu)) ty.intInfo(zcu) else .{
            .signedness = .unsigned,
            .bits = @intCast(ty.bitSize(zcu)),
        };

        if (is_big) try writer.print(", {}", .{int_info.signedness == .signed});
        try writer.print(", {f}", .{try dg.fmtIntLiteralDec(
            try pt.intValue(if (is_big) .u16 else .u8, int_info.bits),
            .FunctionArgument,
        )});
    }

    fn fmtIntLiteral(
        dg: *DeclGen,
        val: Value,
        loc: ValueRenderLocation,
        base: u8,
        case: std.fmt.Case,
    ) !std.fmt.Formatter(FormatIntLiteralContext, formatIntLiteral) {
        const zcu = dg.pt.zcu;
        const kind = loc.toCTypeKind();
        const ty = val.typeOf(zcu);
        return .{ .data = .{
            .dg = dg,
            .int_info = ty.intInfo(zcu),
            .kind = kind,
            .ctype = try dg.ctypeFromType(ty, kind),
            .val = val,
            .base = base,
            .case = case,
        } };
    }

    fn fmtIntLiteralDec(
        dg: *DeclGen,
        val: Value,
        loc: ValueRenderLocation,
    ) !std.fmt.Formatter(FormatIntLiteralContext, formatIntLiteral) {
        return fmtIntLiteral(dg, val, loc, 10, .lower);
    }

    fn fmtIntLiteralHex(
        dg: *DeclGen,
        val: Value,
        loc: ValueRenderLocation,
    ) !std.fmt.Formatter(FormatIntLiteralContext, formatIntLiteral) {
        return fmtIntLiteral(dg, val, loc, 16, .lower);
    }
};

const CTypeFix = enum { prefix, suffix };
const CQualifiers = std.enums.EnumSet(enum { @"const", @"volatile", restrict });
const Const = CQualifiers.init(.{ .@"const" = true });
const RenderCTypeTrailing = enum {
    no_space,
    maybe_space,

    pub fn format(
        self: @This(),
        comptime fmt: []const u8,
        _: std.fmt.FormatOptions,
        w: anytype,
    ) @TypeOf(w).Error!void {
        if (fmt.len != 0)
            @compileError("invalid format string '" ++ fmt ++ "' for type '" ++
                @typeName(@This()) ++ "'");
        comptime assert(fmt.len == 0);
        switch (self) {
            .no_space => {},
            .maybe_space => try w.writeByte(' '),
        }
    }
};
fn renderAlignedTypeName(w: anytype, ctype: CType) !void {
    try w.print("anon__aligned_{d}", .{@intFromEnum(ctype.index)});
}
fn renderFwdDeclTypeName(
    zcu: *Zcu,
    w: anytype,
    ctype: CType,
    fwd_decl: CType.Info.FwdDecl,
    attributes: []const u8,
) !void {
    const ip = &zcu.intern_pool;
    try w.print("{s} {s}", .{ @tagName(fwd_decl.tag), attributes });
    switch (fwd_decl.name) {
        .anon => try w.print("anon__lazy_{d}", .{@intFromEnum(ctype.index)}),
        .index => |index| try w.print("{}__{d}", .{
            fmtIdentUnsolo(Type.fromInterned(index).containerTypeName(ip).toSlice(&zcu.intern_pool)),
            @intFromEnum(index),
        }),
    }
}
fn renderTypePrefix(
    pass: DeclGen.Pass,
    ctype_pool: *const CType.Pool,
    zcu: *Zcu,
    w: anytype,
    ctype: CType,
    parent_fix: CTypeFix,
    qualifiers: CQualifiers,
) @TypeOf(w).Error!RenderCTypeTrailing {
    var trailing = RenderCTypeTrailing.maybe_space;
    switch (ctype.info(ctype_pool)) {
        .basic => |basic_info| try w.writeAll(@tagName(basic_info)),

        .pointer => |pointer_info| {
            try w.print("{}*", .{try renderTypePrefix(
                pass,
                ctype_pool,
                zcu,
                w,
                pointer_info.elem_ctype,
                .prefix,
                CQualifiers.init(.{
                    .@"const" = pointer_info.@"const",
                    .@"volatile" = pointer_info.@"volatile",
                }),
            )});
            trailing = .no_space;
        },

        .aligned => switch (pass) {
            .nav => |nav| try w.print("nav__{d}_{d}", .{
                @intFromEnum(nav), @intFromEnum(ctype.index),
            }),
            .uav => |uav| try w.print("uav__{d}_{d}", .{
                @intFromEnum(uav), @intFromEnum(ctype.index),
            }),
            .flush => try renderAlignedTypeName(w, ctype),
        },

        .array, .vector => |sequence_info| {
            const child_trailing = try renderTypePrefix(
                pass,
                ctype_pool,
                zcu,
                w,
                sequence_info.elem_ctype,
                .suffix,
                qualifiers,
            );
            switch (parent_fix) {
                .prefix => {
                    try w.print("{}(", .{child_trailing});
                    return .no_space;
                },
                .suffix => return child_trailing,
            }
        },

        .fwd_decl => |fwd_decl_info| switch (fwd_decl_info.name) {
            .anon => switch (pass) {
                .nav => |nav| try w.print("nav__{d}_{d}", .{
                    @intFromEnum(nav), @intFromEnum(ctype.index),
                }),
                .uav => |uav| try w.print("uav__{d}_{d}", .{
                    @intFromEnum(uav), @intFromEnum(ctype.index),
                }),
                .flush => try renderFwdDeclTypeName(zcu, w, ctype, fwd_decl_info, ""),
            },
            .index => try renderFwdDeclTypeName(zcu, w, ctype, fwd_decl_info, ""),
        },

        .aggregate => |aggregate_info| switch (aggregate_info.name) {
            .anon => {
                try w.print("{s} {s}", .{
                    @tagName(aggregate_info.tag),
                    if (aggregate_info.@"packed") "zig_packed(" else "",
                });
                try renderFields(zcu, w, ctype_pool, aggregate_info, 1);
                if (aggregate_info.@"packed") try w.writeByte(')');
            },
            .fwd_decl => |fwd_decl| return renderTypePrefix(
                pass,
                ctype_pool,
                zcu,
                w,
                fwd_decl,
                parent_fix,
                qualifiers,
            ),
        },

        .function => |function_info| {
            const child_trailing = try renderTypePrefix(
                pass,
                ctype_pool,
                zcu,
                w,
                function_info.return_ctype,
                .suffix,
                .{},
            );
            switch (parent_fix) {
                .prefix => {
                    try w.print("{}(", .{child_trailing});
                    return .no_space;
                },
                .suffix => return child_trailing,
            }
        },
    }
    var qualifier_it = qualifiers.iterator();
    while (qualifier_it.next()) |qualifier| {
        try w.print("{}{s}", .{ trailing, @tagName(qualifier) });
        trailing = .maybe_space;
    }
    return trailing;
}
fn renderTypeSuffix(
    pass: DeclGen.Pass,
    ctype_pool: *const CType.Pool,
    zcu: *Zcu,
    w: anytype,
    ctype: CType,
    parent_fix: CTypeFix,
    qualifiers: CQualifiers,
) @TypeOf(w).Error!void {
    switch (ctype.info(ctype_pool)) {
        .basic, .aligned, .fwd_decl, .aggregate => {},
        .pointer => |pointer_info| try renderTypeSuffix(
            pass,
            ctype_pool,
            zcu,
            w,
            pointer_info.elem_ctype,
            .prefix,
            .{},
        ),
        .array, .vector => |sequence_info| {
            switch (parent_fix) {
                .prefix => try w.writeByte(')'),
                .suffix => {},
            }

            try w.print("[{}]", .{sequence_info.len});
            try renderTypeSuffix(pass, ctype_pool, zcu, w, sequence_info.elem_ctype, .suffix, .{});
        },
        .function => |function_info| {
            switch (parent_fix) {
                .prefix => try w.writeByte(')'),
                .suffix => {},
            }

            try w.writeByte('(');
            var need_comma = false;
            for (0..function_info.param_ctypes.len) |param_index| {
                const param_type = function_info.param_ctypes.at(param_index, ctype_pool);
                if (need_comma) try w.writeAll(", ");
                need_comma = true;
                const trailing =
                    try renderTypePrefix(pass, ctype_pool, zcu, w, param_type, .suffix, qualifiers);
                if (qualifiers.contains(.@"const")) try w.print("{}a{d}", .{ trailing, param_index });
                try renderTypeSuffix(pass, ctype_pool, zcu, w, param_type, .suffix, .{});
            }
            if (function_info.varargs) {
                if (need_comma) try w.writeAll(", ");
                need_comma = true;
                try w.writeAll("...");
            }
            if (!need_comma) try w.writeAll("void");
            try w.writeByte(')');

            try renderTypeSuffix(pass, ctype_pool, zcu, w, function_info.return_ctype, .suffix, .{});
        },
    }
}
fn renderFields(
    zcu: *Zcu,
    writer: anytype,
    ctype_pool: *const CType.Pool,
    aggregate_info: CType.Info.Aggregate,
    indent: usize,
) !void {
    try writer.writeAll("{\n");
    for (0..aggregate_info.fields.len) |field_index| {
        const field_info = aggregate_info.fields.at(field_index, ctype_pool);
        try writer.writeByteNTimes(' ', indent + 1);
        switch (field_info.alignas.abiOrder()) {
            .lt => {
                std.debug.assert(aggregate_info.@"packed");
                if (field_info.alignas.@"align" != .@"1") try writer.print("zig_under_align({}) ", .{
                    field_info.alignas.toByteUnits(),
                });
            },
            .eq => if (aggregate_info.@"packed" and field_info.alignas.@"align" != .@"1")
                try writer.print("zig_align({}) ", .{field_info.alignas.toByteUnits()}),
            .gt => {
                std.debug.assert(field_info.alignas.@"align" != .@"1");
                try writer.print("zig_align({}) ", .{field_info.alignas.toByteUnits()});
            },
        }
        const trailing = try renderTypePrefix(
            .flush,
            ctype_pool,
            zcu,
            writer,
            field_info.ctype,
            .suffix,
            .{},
        );
        try writer.print("{}{f}", .{ trailing, fmtCTypePoolString(field_info.name, ctype_pool, true) });
        try renderTypeSuffix(.flush, ctype_pool, zcu, writer, field_info.ctype, .suffix, .{});
        try writer.writeAll(";\n");
    }
    try writer.writeByteNTimes(' ', indent);
    try writer.writeByte('}');
}

pub fn genTypeDecl(
    zcu: *Zcu,
    writer: anytype,
    global_ctype_pool: *const CType.Pool,
    global_ctype: CType,
    pass: DeclGen.Pass,
    decl_ctype_pool: *const CType.Pool,
    decl_ctype: CType,
    found_existing: bool,
) !void {
    switch (global_ctype.info(global_ctype_pool)) {
        .basic, .pointer, .array, .vector, .function => {},
        .aligned => |aligned_info| {
            if (!found_existing) {
                std.debug.assert(aligned_info.alignas.abiOrder().compare(.lt));
                try writer.print("typedef zig_under_align({d}) ", .{aligned_info.alignas.toByteUnits()});
                try writer.print("{}", .{try renderTypePrefix(
                    .flush,
                    global_ctype_pool,
                    zcu,
                    writer,
                    aligned_info.ctype,
                    .suffix,
                    .{},
                )});
                try renderAlignedTypeName(writer, global_ctype);
                try renderTypeSuffix(.flush, global_ctype_pool, zcu, writer, aligned_info.ctype, .suffix, .{});
                try writer.writeAll(";\n");
            }
            switch (pass) {
                .nav, .uav => {
                    try writer.writeAll("typedef ");
                    _ = try renderTypePrefix(.flush, global_ctype_pool, zcu, writer, global_ctype, .suffix, .{});
                    try writer.writeByte(' ');
                    _ = try renderTypePrefix(pass, decl_ctype_pool, zcu, writer, decl_ctype, .suffix, .{});
                    try writer.writeAll(";\n");
                },
                .flush => {},
            }
        },
        .fwd_decl => |fwd_decl_info| switch (fwd_decl_info.name) {
            .anon => switch (pass) {
                .nav, .uav => {
                    try writer.writeAll("typedef ");
                    _ = try renderTypePrefix(.flush, global_ctype_pool, zcu, writer, global_ctype, .suffix, .{});
                    try writer.writeByte(' ');
                    _ = try renderTypePrefix(pass, decl_ctype_pool, zcu, writer, decl_ctype, .suffix, .{});
                    try writer.writeAll(";\n");
                },
                .flush => {},
            },
            .index => |index| if (!found_existing) {
                const ip = &zcu.intern_pool;
                const ty: Type = .fromInterned(index);
                _ = try renderTypePrefix(.flush, global_ctype_pool, zcu, writer, global_ctype, .suffix, .{});
                try writer.writeByte(';');
                const file_scope = ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip);
                if (!zcu.fileByIndex(file_scope).mod.?.strip) try writer.print(" /* {} */", .{
                    ty.containerTypeName(ip).fmt(ip),
                });
                try writer.writeByte('\n');
            },
        },
        .aggregate => |aggregate_info| switch (aggregate_info.name) {
            .anon => {},
            .fwd_decl => |fwd_decl| if (!found_existing) {
                try renderFwdDeclTypeName(
                    zcu,
                    writer,
                    fwd_decl,
                    fwd_decl.info(global_ctype_pool).fwd_decl,
                    if (aggregate_info.@"packed") "zig_packed(" else "",
                );
                try writer.writeByte(' ');
                try renderFields(zcu, writer, global_ctype_pool, aggregate_info, 0);
                if (aggregate_info.@"packed") try writer.writeByte(')');
                try writer.writeAll(";\n");
            },
        },
    }
}

pub fn genGlobalAsm(zcu: *Zcu, writer: anytype) !void {
    for (zcu.global_assembly.values()) |asm_source| {
        try writer.print("__asm({f});\n", .{fmtStringLiteral(asm_source, null)});
    }
}

pub fn genErrDecls(o: *Object) !void {
    const pt = o.dg.pt;
    const zcu = pt.zcu;
    const ip = &zcu.intern_pool;
    const writer = o.writer();

    var max_name_len: usize = 0;
    // do not generate an invalid empty enum when the global error set is empty
    const names = ip.global_error_set.getNamesFromMainThread();
    if (names.len > 0) {
        try writer.writeAll("enum {\n");
        o.indent_writer.pushIndent();
        for (names, 1..) |name_nts, value| {
            const name = name_nts.toSlice(ip);
            max_name_len = @max(name.len, max_name_len);
            const err_val = try pt.intern(.{ .err = .{
                .ty = .anyerror_type,
                .name = name_nts,
            } });
            try o.dg.renderValue(writer, Value.fromInterned(err_val), .Other);
            try writer.print(" = {d}u,\n", .{value});
        }
        o.indent_writer.popIndent();
        try writer.writeAll("};\n");
    }
    const array_identifier = "zig_errorName";
    const name_prefix = array_identifier ++ "_";
    const name_buf = try o.dg.gpa.alloc(u8, name_prefix.len + max_name_len);
    defer o.dg.gpa.free(name_buf);

    @memcpy(name_buf[0..name_prefix.len], name_prefix);
    for (names) |name| {
        const name_slice = name.toSlice(ip);
        @memcpy(name_buf[name_prefix.len..][0..name_slice.len], name_slice);
        const identifier = name_buf[0 .. name_prefix.len + name_slice.len];

        const name_ty = try pt.arrayType(.{
            .len = name_slice.len,
            .child = .u8_type,
            .sentinel = .zero_u8,
        });
        const name_val = try pt.intern(.{ .aggregate = .{
            .ty = name_ty.toIntern(),
            .storage = .{ .bytes = name.toString() },
        } });

        try writer.writeAll("static ");
        try o.dg.renderTypeAndName(
            writer,
            name_ty,
            .{ .identifier = identifier },
            Const,
            .none,
            .complete,
        );
        try writer.writeAll(" = ");
        try o.dg.renderValue(writer, Value.fromInterned(name_val), .StaticInitializer);
        try writer.writeAll(";\n");
    }

    const name_array_ty = try pt.arrayType(.{
        .len = 1 + names.len,
        .child = .slice_const_u8_sentinel_0_type,
    });

    try writer.writeAll("static ");
    try o.dg.renderTypeAndName(
        writer,
        name_array_ty,
        .{ .identifier = array_identifier },
        Const,
        .none,
        .complete,
    );
    try writer.writeAll(" = {");
    for (names, 1..) |name_nts, val| {
        const name = name_nts.toSlice(ip);
        if (val > 1) try writer.writeAll(", ");
        try writer.print("{{" ++ name_prefix ++ "{f}, {f}}}", .{
            fmtIdentUnsolo(name),
            try o.dg.fmtIntLiteralDec(try pt.intValue(.usize, name.len), .StaticInitializer),
        });
    }
    try writer.writeAll("};\n");
}

pub fn genLazyFn(o: *Object, lazy_ctype_pool: *const CType.Pool, lazy_fn: LazyFnMap.Entry) !void {
    const pt = o.dg.pt;
    const zcu = pt.zcu;
    const ip = &zcu.intern_pool;
    const ctype_pool = &o.dg.ctype_pool;
    const w = o.writer();
    const key = lazy_fn.key_ptr.*;
    const val = lazy_fn.value_ptr;
    switch (key) {
        .tag_name => |enum_ty_ip| {
            const enum_ty: Type = .fromInterned(enum_ty_ip);
            const name_slice_ty: Type = .slice_const_u8_sentinel_0;

            try w.writeAll("static ");
            try o.dg.renderType(w, name_slice_ty);
            try w.print(" {}(", .{val.fn_name.fmt(lazy_ctype_pool)});
            try o.dg.renderTypeAndName(w, enum_ty, .{ .identifier = "tag" }, Const, .none, .complete);
            try w.writeAll(") {\n switch (tag) {\n");
            const tag_names = enum_ty.enumFields(zcu);
            for (0..tag_names.len) |tag_index| {
                const tag_name = tag_names.get(ip)[tag_index];
                const tag_name_len = tag_name.length(ip);
                const tag_val = try pt.enumValueFieldIndex(enum_ty, @intCast(tag_index));

                const name_ty = try pt.arrayType(.{
                    .len = tag_name_len,
                    .child = .u8_type,
                    .sentinel = .zero_u8,
                });
                const name_val = try pt.intern(.{ .aggregate = .{
                    .ty = name_ty.toIntern(),
                    .storage = .{ .bytes = tag_name.toString() },
                } });

                try w.print("  case {f}: {{\n   static ", .{
                    try o.dg.fmtIntLiteralDec(try tag_val.intFromEnum(enum_ty, pt), .Other),
                });
                try o.dg.renderTypeAndName(w, name_ty, .{ .identifier = "name" }, Const, .none, .complete);
                try w.writeAll(" = ");
                try o.dg.renderValue(w, Value.fromInterned(name_val), .StaticInitializer);
                try w.writeAll(";\n   return (");
                try o.dg.renderType(w, name_slice_ty);
                try w.print("){{{f}, {f}}};\n", .{
                    fmtIdentUnsolo("name"),
                    try o.dg.fmtIntLiteralDec(try pt.intValue(.usize, tag_name_len), .Other),
                });

                try w.writeAll("  }\n");
            }
            try w.writeAll(" }\n while (");
            try o.dg.renderValue(w, Value.true, .Other);
            try w.writeAll(") ");
            _ = try airBreakpoint(w);
            try w.writeAll("}\n");
        },
        .never_tail, .never_inline => |fn_nav_index| {
            const fn_val = zcu.navValue(fn_nav_index);
            const fn_ctype = try o.dg.ctypeFromType(fn_val.typeOf(zcu), .complete);
            const fn_info = fn_ctype.info(ctype_pool).function;
            const fn_name = fmtCTypePoolString(val.fn_name, lazy_ctype_pool, true);

            const fwd = o.dg.fwdDeclWriter();
            try fwd.print("static zig_{s} ", .{@tagName(key)});
            try o.dg.renderFunctionSignature(fwd, fn_val, ip.getNav(fn_nav_index).getAlignment(), .forward, .{
                .fmt_ctype_pool_string = fn_name,
            });
            try fwd.writeAll(";\n");

            try w.print("zig_{s} ", .{@tagName(key)});
            try o.dg.renderFunctionSignature(w, fn_val, .none, .complete, .{
                .fmt_ctype_pool_string = fn_name,
            });
            try w.writeAll(" {\n return ");
            try o.dg.renderNavName(w, fn_nav_index);
            try w.writeByte('(');
            for (0..fn_info.param_ctypes.len) |arg| {
                if (arg > 0) try w.writeAll(", ");
                try w.print("a{d}", .{arg});
            }
            try w.writeAll(");\n}\n");
        },
    }
}

pub fn generate(
    lf: *link.File,
    pt: Zcu.PerThread,
    src_loc: Zcu.LazySrcLoc,
    func_index: InternPool.Index,
    air: *const Air,
    liveness: *const Air.Liveness,
) @import("../codegen.zig").CodeGenError!Mir {
    const zcu = pt.zcu;
    const gpa = zcu.gpa;

    _ = src_loc;
    assert(lf.tag == .c);

    const func = zcu.funcInfo(func_index);

    var function: Function = .{
        .value_map = .init(gpa),
        .air = air.*,
        .liveness = liveness.*,
        .func_index = func_index,
        .object = .{
            .dg = .{
                .gpa = gpa,
                .pt = pt,
                .mod = zcu.navFileScope(func.owner_nav).mod.?,
                .error_msg = null,
                .pass = .{ .nav = func.owner_nav },
                .is_naked_fn = Type.fromInterned(func.ty).fnCallingConvention(zcu) == .naked,
                .expected_block = null,
                .fwd_decl = .init(gpa),
                .ctype_pool = .empty,
                .scratch = .empty,
                .uavs = .empty,
            },
            .code = .init(gpa),
            .indent_writer = undefined, // set later so we can get a pointer to object.code
        },
        .lazy_fns = .empty,
    };
    defer {
        function.object.code.deinit();
        function.object.dg.fwd_decl.deinit();
        function.object.dg.ctype_pool.deinit(gpa);
        function.object.dg.scratch.deinit(gpa);
        function.object.dg.uavs.deinit(gpa);
        function.deinit();
    }
    try function.object.dg.ctype_pool.init(gpa);
    function.object.indent_writer = .{ .underlying_writer = function.object.code.writer() };

    genFunc(&function) catch |err| switch (err) {
        error.AnalysisFail => return zcu.codegenFailMsg(func.owner_nav, function.object.dg.error_msg.?),
        error.OutOfMemory => |e| return e,
    };

    var mir: Mir = .{
        .uavs = .empty,
        .code = &.{},
        .fwd_decl = &.{},
        .ctype_pool = .empty,
        .lazy_fns = .empty,
    };
    errdefer mir.deinit(gpa);
    mir.uavs = function.object.dg.uavs.move();
    mir.code = try function.object.code.toOwnedSlice();
    mir.fwd_decl = try function.object.dg.fwd_decl.toOwnedSlice();
    mir.ctype_pool = function.object.dg.ctype_pool.move();
    mir.lazy_fns = function.lazy_fns.move();
    return mir;
}

fn genFunc(f: *Function) !void {
    const tracy = trace(@src());
    defer tracy.end();

    const o = &f.object;
    const zcu = o.dg.pt.zcu;
    const ip = &zcu.intern_pool;
    const gpa = o.dg.gpa;
    const nav_index = o.dg.pass.nav;
    const nav_val = zcu.navValue(nav_index);
    const nav = ip.getNav(nav_index);

    o.code_header = std.ArrayList(u8).init(gpa);
    defer o.code_header.deinit();

    const fwd = o.dg.fwdDeclWriter();
    try fwd.writeAll("static ");
    try o.dg.renderFunctionSignature(
        fwd,
        nav_val,
        nav.status.fully_resolved.alignment,
        .forward,
        .{ .nav = nav_index },
    );
    try fwd.writeAll(";\n");

    if (nav.status.fully_resolved.@"linksection".toSlice(ip)) |s|
        try o.writer().print("zig_linksection_fn({f}) ", .{fmtStringLiteral(s, null)});
    try o.dg.renderFunctionSignature(
        o.writer(),
        nav_val,
        .none,
        .complete,
        .{ .nav = nav_index },
    );
    try o.writer().writeByte(' ');

    // In case we need to use the header, populate it with a copy of the function
    // signature here. We anticipate a brace, newline, and space.
    try o.code_header.ensureUnusedCapacity(o.code.items.len + 3);
    o.code_header.appendSliceAssumeCapacity(o.code.items);
    o.code_header.appendSliceAssumeCapacity("{\n ");
    const empty_header_len = o.code_header.items.len;

    f.free_locals_map.clearRetainingCapacity();

    const main_body = f.air.getMainBody();
    try genBodyResolveState(f, undefined, &.{}, main_body, false);
    try o.indent_writer.insertNewline();
    if (o.dg.expected_block) |_|
        return f.fail("runtime code not allowed in naked function", .{});

    // Take advantage of the free_locals map to bucket locals per type. All
    // locals corresponding to AIR instructions should be in there due to
    // Liveness analysis, however, locals from alloc instructions will be
    // missing. These are added now to complete the map. Then we can sort by
    // alignment, descending.
    const free_locals = &f.free_locals_map;
    assert(f.value_map.count() == 0); // there must not be any unfreed locals
    for (f.allocs.keys(), f.allocs.values()) |local_index, should_emit| {
        if (!should_emit) continue;
        const local = f.locals.items[local_index];
        log.debug("inserting local {d} into free_locals", .{local_index});
        const gop = try free_locals.getOrPut(gpa, local.getType());
        if (!gop.found_existing) gop.value_ptr.* = .{};
        try gop.value_ptr.putNoClobber(gpa, local_index, {});
    }

    const SortContext = struct {
        keys: []const LocalType,

        pub fn lessThan(ctx: @This(), lhs_index: usize, rhs_index: usize) bool {
            const lhs_ty = ctx.keys[lhs_index];
            const rhs_ty = ctx.keys[rhs_index];
            return lhs_ty.alignas.order(rhs_ty.alignas).compare(.gt);
        }
    };
    free_locals.sort(SortContext{ .keys = free_locals.keys() });

    const w = o.codeHeaderWriter();
    for (free_locals.values()) |list| {
        for (list.keys()) |local_index| {
            const local = f.locals.items[local_index];
            try o.dg.renderCTypeAndName(w, local.ctype, .{ .local = local_index }, .{}, local.flags.alignas);
            try w.writeAll(";\n ");
        }
    }

    // If we have a header to insert, append the body to the header
    // and then return the result, freeing the body.
    if (o.code_header.items.len > empty_header_len) {
        try o.code_header.appendSlice(o.code.items[empty_header_len..]);
        mem.swap(std.ArrayList(u8), &o.code, &o.code_header);
    }
}

pub fn genDecl(o: *Object) !void {
    const tracy = trace(@src());
    defer tracy.end();

    const pt = o.dg.pt;
    const zcu = pt.zcu;
    const ip = &zcu.intern_pool;
    const nav = ip.getNav(o.dg.pass.nav);
    const nav_ty: Type = .fromInterned(nav.typeOf(ip));

    if (!nav_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) return;
    switch (ip.indexToKey(nav.status.fully_resolved.val)) {
        .@"extern" => |@"extern"| {
            if (!ip.isFunctionType(nav_ty.toIntern())) return o.dg.renderFwdDecl(o.dg.pass.nav, .{
                .is_const = @"extern".is_const,
                .is_threadlocal = @"extern".is_threadlocal,
                .linkage = @"extern".linkage,
                .visibility = @"extern".visibility,
            });

            const fwd = o.dg.fwdDeclWriter();
            try fwd.writeAll("zig_extern ");
            try o.dg.renderFunctionSignature(
                fwd,
                Value.fromInterned(nav.status.fully_resolved.val),
                nav.status.fully_resolved.alignment,
                .forward,
                .{ .@"export" = .{
                    .main_name = nav.name,
                    .extern_name = nav.name,
                } },
            );
            try fwd.writeAll(";\n");
        },
        .variable => |variable| {
            try o.dg.renderFwdDecl(o.dg.pass.nav, .{
                .is_const = false,
                .is_threadlocal = variable.is_threadlocal,
                .linkage = .internal,
                .visibility = .default,
            });
            const w = o.writer();
            if (variable.is_threadlocal and !o.dg.mod.single_threaded) try w.writeAll("zig_threadlocal ");
            if (nav.status.fully_resolved.@"linksection".toSlice(&zcu.intern_pool)) |s|
                try w.print("zig_linksection({f}) ", .{fmtStringLiteral(s, null)});
            try o.dg.renderTypeAndName(
                w,
                nav_ty,
                .{ .nav = o.dg.pass.nav },
                .{},
                nav.status.fully_resolved.alignment,
                .complete,
            );
            try w.writeAll(" = ");
            try o.dg.renderValue(w, Value.fromInterned(variable.init), .StaticInitializer);
            try w.writeByte(';');
            try o.indent_writer.insertNewline();
        },
        else => try genDeclValue(
            o,
            Value.fromInterned(nav.status.fully_resolved.val),
            .{ .nav = o.dg.pass.nav },
            nav.status.fully_resolved.alignment,
            nav.status.fully_resolved.@"linksection",
        ),
    }
}

pub fn genDeclValue(
    o: *Object,
    val: Value,
    decl_c_value: CValue,
    alignment: Alignment,
    @"linksection": InternPool.OptionalNullTerminatedString,
) !void {
    const zcu = o.dg.pt.zcu;
    const ty = val.typeOf(zcu);

    const fwd = o.dg.fwdDeclWriter();
    try fwd.writeAll("static ");
    try o.dg.renderTypeAndName(fwd, ty, decl_c_value, Const, alignment, .complete);
    try fwd.writeAll(";\n");

    const w = o.writer();
    if (@"linksection".toSlice(&zcu.intern_pool)) |s|
        try w.print("zig_linksection({f}) ", .{fmtStringLiteral(s, null)});
    try o.dg.renderTypeAndName(w, ty, decl_c_value, Const, alignment, .complete);
    try w.writeAll(" = ");
    try o.dg.renderValue(w, val, .StaticInitializer);
    try w.writeAll(";\n");
}

pub fn genExports(dg: *DeclGen, exported: Zcu.Exported, export_indices: []const Zcu.Export.Index) !void {
    const zcu = dg.pt.zcu;
    const ip = &zcu.intern_pool;
    const fwd = dg.fwdDeclWriter();

    const main_name = export_indices[0].ptr(zcu).opts.name;
    try fwd.writeAll("#define ");
    switch (exported) {
        .nav => |nav| try dg.renderNavName(fwd, nav),
        .uav => |uav| try DeclGen.renderUavName(fwd, Value.fromInterned(uav)),
    }
    try fwd.writeByte(' ');
    try fwd.print("{f}", .{fmtIdentSolo(main_name.toSlice(ip))});
    try fwd.writeByte('\n');

    const exported_val = exported.getValue(zcu);
    if (ip.isFunctionType(exported_val.typeOf(zcu).toIntern())) return for (export_indices) |export_index| {
        const @"export" = export_index.ptr(zcu);
        try fwd.writeAll("zig_extern ");
        if (@"export".opts.linkage == .weak) try fwd.writeAll("zig_weak_linkage_fn ");
        try dg.renderFunctionSignature(
            fwd,
            exported.getValue(zcu),
            exported.getAlign(zcu),
            .forward,
            .{ .@"export" = .{
                .main_name = main_name,
                .extern_name = @"export".opts.name,
            } },
        );
        try fwd.writeAll(";\n");
    };
    const is_const = switch (ip.indexToKey(exported_val.toIntern())) {
        .func => unreachable,
        .@"extern" => |@"extern"| @"extern".is_const,
        .variable => false,
        else => true,
    };
    for (export_indices) |export_index| {
        const @"export" = export_index.ptr(zcu);
        try fwd.writeAll("zig_extern ");
        if (@"export".opts.linkage == .weak) try fwd.writeAll("zig_weak_linkage ");
        if (@"export".opts.section.toSlice(ip)) |s| try fwd.print("zig_linksection({f}) ", .{
            fmtStringLiteral(s, null),
        });
        const extern_name = @"export".opts.name.toSlice(ip);
        const is_mangled = isMangledIdent(extern_name, true);
        const is_export = @"export".opts.name != main_name;
        try dg.renderTypeAndName(
            fwd,
            exported.getValue(zcu).typeOf(zcu),
            .{ .identifier = extern_name },
            CQualifiers.init(.{ .@"const" = is_const }),
            exported.getAlign(zcu),
            .complete,
        );
        if (is_mangled and is_export) {
            try fwd.print(" zig_mangled_export({f}, {f}, {f})", .{
                fmtIdentSolo(extern_name),
                fmtStringLiteral(extern_name, null),
                fmtStringLiteral(main_name.toSlice(ip), null),
            });
        } else if (is_mangled) {
            try fwd.print(" zig_mangled({f}, {f})", .{
                fmtIdentSolo(extern_name), fmtStringLiteral(extern_name, null),
            });
        } else if (is_export) {
            try fwd.print(" zig_export({f}, {f})", .{
                fmtStringLiteral(main_name.toSlice(ip), null),
                fmtStringLiteral(extern_name, null),
            });
        }
        try fwd.writeAll(";\n");
    }
}

/// Generate code for an entire body which ends with a `noreturn` instruction. The states of
/// `value_map` and `free_locals_map` are undefined after the generation, and new locals may not
/// have been added to `free_locals_map`. For a version of this function that restores this state,
/// see `genBodyResolveState`.
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("{}");
    } else {
        try writer.writeAll("{\n");
        f.object.indent_writer.pushIndent();
        try genBodyInner(f, body);
        f.object.indent_writer.popIndent();
        try writer.writeByte('}');
    }
}

/// Generate code for an entire body which ends with a `noreturn` instruction. The states of
/// `value_map` and `free_locals_map` are restored to their original values, and any non-allocated
/// locals introduced within the body are correctly added to `free_locals_map`. Operands in
/// `leading_deaths` have their deaths processed before the body is generated.
/// A scope is introduced (using braces) only if `inner` is `false`.
/// If `leading_deaths` is empty, `inst` may be `undefined`.
fn genBodyResolveState(f: *Function, inst: Air.Inst.Index, leading_deaths: []const Air.Inst.Index, body: []const Air.Inst.Index, inner: bool) error{ AnalysisFail, OutOfMemory }!void {
    if (body.len == 0) {
        // Don't go to the expense of cloning everything!
        if (!inner) try f.object.writer().writeAll("{}");
        return;
    }

    // TODO: we can probably avoid the copies in some other common cases too.

    const gpa = f.object.dg.gpa;

    // Save the original value_map and free_locals_map so that we can restore them after the body.
    var old_value_map = try f.value_map.clone();
    defer old_value_map.deinit();
    var old_free_locals = try cloneFreeLocalsMap(gpa, &f.free_locals_map);
    defer deinitFreeLocalsMap(gpa, &old_free_locals);

    // Remember how many locals there were before entering the body so that we can free any that
    // were newly introduced. Any new locals must necessarily be logically free after the then
    // branch is complete.
    const pre_locals_len: LocalIndex = @intCast(f.locals.items.len);

    for (leading_deaths) |death| {
        try die(f, inst, death.toRef());
    }

    if (inner) {
        try genBodyInner(f, body);
    } else {
        try genBody(f, body);
    }

    f.value_map.deinit();
    f.value_map = old_value_map.move();
    deinitFreeLocalsMap(gpa, &f.free_locals_map);
    f.free_locals_map = old_free_locals.move();

    // Now, use the lengths we stored earlier to detect any locals the body generated, and free
    // them, unless they were used to store allocs.

    for (pre_locals_len..f.locals.items.len) |local_i| {
        const local_index: LocalIndex = @intCast(local_i);
        if (f.allocs.contains(local_index)) {
            continue;
        }
        try freeLocal(f, inst, local_index, null);
    }
}

fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfMemory }!void {
    const zcu = f.object.dg.pt.zcu;
    const ip = &zcu.intern_pool;
    const air_tags = f.air.instructions.items(.tag);
    const air_datas = f.air.instructions.items(.data);

    for (body) |inst| {
        if (f.object.dg.expected_block) |_|
            return f.fail("runtime code not allowed in naked function", .{});
        if (f.liveness.isUnused(inst) and !f.air.mustLower(inst, ip))
            continue;

        const result_value = switch (air_tags[@intFromEnum(inst)]) {
            // zig fmt: off
            .inferred_alloc, .inferred_alloc_comptime => unreachable,

            .arg      => try airArg(f, inst),

            .breakpoint => try airBreakpoint(f.object.writer()),
            .ret_addr   => try airRetAddr(f, inst),
            .frame_addr => try airFrameAddress(f, inst),

            .ptr_add => try airPtrAddSub(f, inst, '+'),
            .ptr_sub => try airPtrAddSub(f, inst, '-'),

            // TODO use a different strategy for add, sub, mul, div
            // that communicates to the optimizer that wrapping is UB.
            .add => try airBinOp(f, inst, "+", "add", .none),
            .sub => try airBinOp(f, inst, "-", "sub", .none),
            .mul => try airBinOp(f, inst, "*", "mul", .none),

            .neg => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "neg", .none),
            .div_float => try airBinBuiltinCall(f, inst, "div", .none),

            .div_trunc, .div_exact => try airBinOp(f, inst, "/", "div_trunc", .none),
            .rem => blk: {
                const bin_op = air_datas[@intFromEnum(inst)].bin_op;
                const lhs_scalar_ty = f.typeOf(bin_op.lhs).scalarType(zcu);
                // For binary operations @TypeOf(lhs)==@TypeOf(rhs),
                // so we only check one.
                break :blk if (lhs_scalar_ty.isInt(zcu))
                    try airBinOp(f, inst, "%", "rem", .none)
                else
                    try airBinBuiltinCall(f, inst, "fmod", .none);
            },
            .div_floor => try airBinBuiltinCall(f, inst, "div_floor", .none),
            .mod       => try airBinBuiltinCall(f, inst, "mod", .none),
            .abs       => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "abs", .none),

            .add_wrap => try airBinBuiltinCall(f, inst, "addw", .bits),
            .sub_wrap => try airBinBuiltinCall(f, inst, "subw", .bits),
            .mul_wrap => try airBinBuiltinCall(f, inst, "mulw", .bits),

            .add_sat => try airBinBuiltinCall(f, inst, "adds", .bits),
            .sub_sat => try airBinBuiltinCall(f, inst, "subs", .bits),
            .mul_sat => try airBinBuiltinCall(f, inst, "muls", .bits),
            .shl_sat => try airBinBuiltinCall(f, inst, "shls", .bits),

            .sqrt        => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "sqrt", .none),
            .sin         => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "sin", .none),
            .cos         => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "cos", .none),
            .tan         => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "tan", .none),
            .exp         => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "exp", .none),
            .exp2        => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "exp2", .none),
            .log         => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "log", .none),
            .log2        => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "log2", .none),
            .log10       => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "log10", .none),
            .floor       => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "floor", .none),
            .ceil        => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "ceil", .none),
            .round       => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "round", .none),
            .trunc_float => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].un_op, "trunc", .none),

            .mul_add => try airMulAdd(f, inst),

            .add_with_overflow => try airOverflow(f, inst, "add", .bits),
            .sub_with_overflow => try airOverflow(f, inst, "sub", .bits),
            .mul_with_overflow => try airOverflow(f, inst, "mul", .bits),
            .shl_with_overflow => try airOverflow(f, inst, "shl", .bits),

            .min => try airMinMax(f, inst, '<', "min"),
            .max => try airMinMax(f, inst, '>', "max"),

            .slice => try airSlice(f, inst),

            .cmp_gt  => try airCmpOp(f, inst, air_datas[@intFromEnum(inst)].bin_op, .gt),
            .cmp_gte => try airCmpOp(f, inst, air_datas[@intFromEnum(inst)].bin_op, .gte),
            .cmp_lt  => try airCmpOp(f, inst, air_datas[@intFromEnum(inst)].bin_op, .lt),
            .cmp_lte => try airCmpOp(f, inst, air_datas[@intFromEnum(inst)].bin_op, .lte),

            .cmp_eq  => try airEquality(f, inst, .eq),
            .cmp_neq => try airEquality(f, inst, .neq),

            .cmp_vector => blk: {
                const ty_pl = air_datas[@intFromEnum(inst)].ty_pl;
                const extra = f.air.extraData(Air.VectorCmp, ty_pl.payload).data;
                break :blk try airCmpOp(f, inst, extra, extra.compareOperator());
            },
            .cmp_lt_errors_len => try airCmpLtErrorsLen(f, inst),

            // bool_and and bool_or are non-short-circuit operations
            .bool_and, .bit_and => try airBinOp(f, inst, "&",  "and", .none),
            .bool_or,  .bit_or  => try airBinOp(f, inst, "|",  "or",  .none),
            .xor                => try airBinOp(f, inst, "^",  "xor", .none),
            .shr, .shr_exact    => try airBinBuiltinCall(f, inst, "shr", .none),
            .shl,               => try airBinBuiltinCall(f, inst, "shlw", .bits),
            .shl_exact          => try airBinOp(f, inst, "<<", "shl", .none),
            .not                => try airNot  (f, inst),

            .optional_payload         => try airOptionalPayload(f, inst, false),
            .optional_payload_ptr     => try airOptionalPayload(f, inst, true),
            .optional_payload_ptr_set => try airOptionalPayloadPtrSet(f, inst),
            .wrap_optional            => try airWrapOptional(f, inst),

            .is_err          => try airIsErr(f, inst, false, "!="),
            .is_non_err      => try airIsErr(f, inst, false, "=="),
            .is_err_ptr      => try airIsErr(f, inst, true, "!="),
            .is_non_err_ptr  => try airIsErr(f, inst, true, "=="),

            .is_null         => try airIsNull(f, inst, .eq, false),
            .is_non_null     => try airIsNull(f, inst, .neq, false),
            .is_null_ptr     => try airIsNull(f, inst, .eq, true),
            .is_non_null_ptr => try airIsNull(f, inst, .neq, true),

            .alloc            => try airAlloc(f, inst),
            .ret_ptr          => try airRetPtr(f, inst),
            .assembly         => try airAsm(f, inst),
            .bitcast          => try airBitcast(f, inst),
            .intcast          => try airIntCast(f, inst),
            .trunc            => try airTrunc(f, inst),
            .load             => try airLoad(f, inst),
            .store            => try airStore(f, inst, false),
            .store_safe       => try airStore(f, inst, true),
            .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),
            .memset           => try airMemset(f, inst, false),
            .memset_safe      => try airMemset(f, inst, true),
            .memcpy           => try airMemcpy(f, inst, "memcpy("),
            .memmove          => try airMemcpy(f, inst, "memmove("),
            .set_union_tag    => try airSetUnionTag(f, inst),
            .get_union_tag    => try airGetUnionTag(f, inst),
            .clz              => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "clz", .bits),
            .ctz              => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "ctz", .bits),
            .popcount         => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "popcount", .bits),
            .byte_swap        => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "byte_swap", .bits),
            .bit_reverse      => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "bit_reverse", .bits),
            .tag_name         => try airTagName(f, inst),
            .error_name       => try airErrorName(f, inst),
            .splat            => try airSplat(f, inst),
            .select           => try airSelect(f, inst),
            .shuffle_one      => try airShuffleOne(f, inst),
            .shuffle_two      => try airShuffleTwo(f, inst),
            .reduce           => try airReduce(f, inst),
            .aggregate_init   => try airAggregateInit(f, inst),
            .union_init       => try airUnionInit(f, inst),
            .prefetch         => try airPrefetch(f, inst),
            .addrspace_cast   => return f.fail("TODO: C backend: implement addrspace_cast", .{}),

            .@"try"       => try airTry(f, inst),
            .try_cold     => try airTry(f, inst),
            .try_ptr      => try airTryPtr(f, inst),
            .try_ptr_cold => try airTryPtr(f, inst),

            .dbg_stmt => try airDbgStmt(f, inst),
            .dbg_empty_stmt => try airDbgEmptyStmt(f, inst),
            .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => try airDbgVar(f, inst),

            .float_from_int,
            .int_from_float,
            .fptrunc,
            .fpext,
            => try airFloatCast(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(.seq_cst)),

            .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),

            .field_parent_ptr => try airFieldParentPtr(f, inst),

            .struct_field_val => try airStructFieldVal(f, inst),
            .slice_ptr        => try airSliceField(f, inst, false, "ptr"),
            .slice_len        => try airSliceField(f, inst, false, "len"),

            .ptr_slice_ptr_ptr => try airSliceField(f, inst, true, "ptr"),
            .ptr_slice_len_ptr => try airSliceField(f, inst, true, "len"),

            .ptr_elem_val       => try airPtrElemVal(f, inst),
            .ptr_elem_ptr       => try airPtrElemPtr(f, inst),
            .slice_elem_val     => try airSliceElemVal(f, inst),
            .slice_elem_ptr     => try airSliceElemPtr(f, inst),
            .array_elem_val     => try airArrayElemVal(f, inst),

            .unwrap_errunion_payload     => try airUnwrapErrUnionPay(f, inst, false),
            .unwrap_errunion_payload_ptr => try airUnwrapErrUnionPay(f, inst, true),
            .unwrap_errunion_err         => try airUnwrapErrUnionErr(f, inst),
            .unwrap_errunion_err_ptr     => try airUnwrapErrUnionErr(f, inst),
            .wrap_errunion_payload       => try airWrapErrUnionPay(f, inst),
            .wrap_errunion_err           => try airWrapErrUnionErr(f, inst),
            .errunion_payload_ptr_set    => try airErrUnionPayloadPtrSet(f, inst),
            .err_return_trace            => try airErrReturnTrace(f, inst),
            .set_err_return_trace        => try airSetErrReturnTrace(f, inst),
            .save_err_return_trace_index => try airSaveErrReturnTraceIndex(f, inst),

            .wasm_memory_size => try airWasmMemorySize(f, inst),
            .wasm_memory_grow => try airWasmMemoryGrow(f, inst),

            .add_optimized,
            .sub_optimized,
            .mul_optimized,
            .div_float_optimized,
            .div_trunc_optimized,
            .div_floor_optimized,
            .div_exact_optimized,
            .rem_optimized,
            .mod_optimized,
            .neg_optimized,
            .cmp_lt_optimized,
            .cmp_lte_optimized,
            .cmp_eq_optimized,
            .cmp_gte_optimized,
            .cmp_gt_optimized,
            .cmp_neq_optimized,
            .cmp_vector_optimized,
            .reduce_optimized,
            .int_from_float_optimized,
            => return f.fail("TODO implement optimized float mode", .{}),

            .add_safe,
            .sub_safe,
            .mul_safe,
            .intcast_safe,
            .int_from_float_safe,
            .int_from_float_optimized_safe,
            => return f.fail("TODO implement safety_checked_instructions", .{}),

            .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),
            .error_set_has_value => return f.fail("TODO: C backend: implement error_set_has_value", .{}),
            .vector_store_elem => return f.fail("TODO: C backend: implement vector_store_elem", .{}),

            .runtime_nav_ptr => try airRuntimeNavPtr(f, inst),

            .c_va_start => try airCVaStart(f, inst),
            .c_va_arg => try airCVaArg(f, inst),
            .c_va_end => try airCVaEnd(f, inst),
            .c_va_copy => try airCVaCopy(f, inst),

            .work_item_id,
            .work_group_size,
            .work_group_id,
            => unreachable,

            // Instructions that are known to always be `noreturn` based on their tag.
            .br              => return airBr(f, inst),
            .repeat          => return airRepeat(f, inst),
            .switch_dispatch => return airSwitchDispatch(f, inst),
            .cond_br         => return airCondBr(f, inst),
            .switch_br       => return airSwitchBr(f, inst, false),
            .loop_switch_br  => return airSwitchBr(f, inst, true),
            .loop            => return airLoop(f, inst),
            .ret             => return airRet(f, inst, false),
            .ret_safe        => return airRet(f, inst, false), // TODO
            .ret_load        => return airRet(f, inst, true),
            .trap            => return airTrap(f, f.object.writer()),
            .unreach         => return airUnreach(f),

            // Instructions which may be `noreturn`.
            .block => res: {
                const res = try airBlock(f, inst);
                if (f.typeOfIndex(inst).isNoReturn(zcu)) return;
                break :res res;
            },
            .dbg_inline_block => res: {
                const res = try airDbgInlineBlock(f, inst);
                if (f.typeOfIndex(inst).isNoReturn(zcu)) return;
                break :res res;
            },
            // TODO: calls should be in this category! The AIR we emit for them is a bit weird.
            // The instruction has type `noreturn`, but there are instructions (and maybe a safety
            // check) following nonetheless. The `unreachable` or safety check should be emitted by
            // backends instead.
            .call              => try airCall(f, inst, .auto),
            .call_always_tail  => .none,
            .call_never_tail   => try airCall(f, inst, .never_tail),
            .call_never_inline => try airCall(f, inst, .never_inline),

            // zig fmt: on
        };
        if (result_value == .new_local) {
            log.debug("map %{d} to t{d}", .{ inst, result_value.new_local });
        }
        try f.value_map.putNoClobber(inst.toRef(), switch (result_value) {
            .none => continue,
            .new_local => |local_index| .{ .local = local_index },
            else => result_value,
        });
    }
    unreachable;
}

fn airSliceField(f: *Function, inst: Air.Inst.Index, is_ptr: bool, field_name: []const u8) !CValue {
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const inst_ty = f.typeOfIndex(inst);
    const operand = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);
    if (is_ptr) {
        try writer.writeByte('&');
        try f.writeCValueDerefMember(writer, operand, .{ .identifier = field_name });
    } else try f.writeCValueMember(writer, operand, .{ .identifier = field_name });
    try a.end(f, writer);
    return local;
}

fn airPtrElemVal(f: *Function, inst: Air.Inst.Index) !CValue {
    const zcu = f.object.dg.pt.zcu;
    const inst_ty = f.typeOfIndex(inst);
    const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
    if (!inst_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
        try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
        return .none;
    }

    const ptr = try f.resolveInst(bin_op.lhs);
    const index = try f.resolveInst(bin_op.rhs);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);
    try f.writeCValue(writer, ptr, .Other);
    try writer.writeByte('[');
    try f.writeCValue(writer, index, .Other);
    try writer.writeByte(']');
    try a.end(f, writer);
    return local;
}

fn airPtrElemPtr(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data;

    const inst_ty = f.typeOfIndex(inst);
    const ptr_ty = f.typeOf(bin_op.lhs);
    const elem_has_bits = ptr_ty.elemType2(zcu).hasRuntimeBitsIgnoreComptime(zcu);

    const ptr = try f.resolveInst(bin_op.lhs);
    const index = try f.resolveInst(bin_op.rhs);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);
    try writer.writeByte('(');
    try f.renderType(writer, inst_ty);
    try writer.writeByte(')');
    if (elem_has_bits) try writer.writeByte('&');
    if (elem_has_bits and ptr_ty.ptrSize(zcu) == .one) {
        // It's a pointer to an array, so we need to de-reference.
        try f.writeCValueDeref(writer, ptr);
    } else try f.writeCValue(writer, ptr, .Other);
    if (elem_has_bits) {
        try writer.writeByte('[');
        try f.writeCValue(writer, index, .Other);
        try writer.writeByte(']');
    }
    try a.end(f, writer);
    return local;
}

fn airSliceElemVal(f: *Function, inst: Air.Inst.Index) !CValue {
    const zcu = f.object.dg.pt.zcu;
    const inst_ty = f.typeOfIndex(inst);
    const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
    if (!inst_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
        try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
        return .none;
    }

    const slice = try f.resolveInst(bin_op.lhs);
    const index = try f.resolveInst(bin_op.rhs);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);
    try f.writeCValueMember(writer, slice, .{ .identifier = "ptr" });
    try writer.writeByte('[');
    try f.writeCValue(writer, index, .Other);
    try writer.writeByte(']');
    try a.end(f, writer);
    return local;
}

fn airSliceElemPtr(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data;

    const inst_ty = f.typeOfIndex(inst);
    const slice_ty = f.typeOf(bin_op.lhs);
    const elem_ty = slice_ty.elemType2(zcu);
    const elem_has_bits = elem_ty.hasRuntimeBitsIgnoreComptime(zcu);

    const slice = try f.resolveInst(bin_op.lhs);
    const index = try f.resolveInst(bin_op.rhs);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);
    if (elem_has_bits) try writer.writeByte('&');
    try f.writeCValueMember(writer, slice, .{ .identifier = "ptr" });
    if (elem_has_bits) {
        try writer.writeByte('[');
        try f.writeCValue(writer, index, .Other);
        try writer.writeByte(']');
    }
    try a.end(f, writer);
    return local;
}

fn airArrayElemVal(f: *Function, inst: Air.Inst.Index) !CValue {
    const zcu = f.object.dg.pt.zcu;
    const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
    const inst_ty = f.typeOfIndex(inst);
    if (!inst_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
        try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
        return .none;
    }

    const array = try f.resolveInst(bin_op.lhs);
    const index = try f.resolveInst(bin_op.rhs);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);
    try f.writeCValue(writer, array, .Other);
    try writer.writeByte('[');
    try f.writeCValue(writer, index, .Other);
    try writer.writeByte(']');
    try a.end(f, writer);
    return local;
}

fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const inst_ty = f.typeOfIndex(inst);
    const elem_ty = inst_ty.childType(zcu);
    if (!elem_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) return .{ .undef = inst_ty };

    const local = try f.allocLocalValue(.{
        .ctype = try f.ctypeFromType(elem_ty, .complete),
        .alignas = CType.AlignAs.fromAlignment(.{
            .@"align" = inst_ty.ptrInfo(zcu).flags.alignment,
            .abi = elem_ty.abiAlignment(zcu),
        }),
    });
    log.debug("%{d}: allocated unfreeable t{d}", .{ inst, local.new_local });
    try f.allocs.put(zcu.gpa, local.new_local, true);
    return .{ .local_ref = local.new_local };
}

fn airRetPtr(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const inst_ty = f.typeOfIndex(inst);
    const elem_ty = inst_ty.childType(zcu);
    if (!elem_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) return .{ .undef = inst_ty };

    const local = try f.allocLocalValue(.{
        .ctype = try f.ctypeFromType(elem_ty, .complete),
        .alignas = CType.AlignAs.fromAlignment(.{
            .@"align" = inst_ty.ptrInfo(zcu).flags.alignment,
            .abi = elem_ty.abiAlignment(zcu),
        }),
    });
    log.debug("%{d}: allocated unfreeable t{d}", .{ inst, local.new_local });
    try f.allocs.put(zcu.gpa, local.new_local, true);
    return .{ .local_ref = local.new_local };
}

fn airArg(f: *Function, inst: Air.Inst.Index) !CValue {
    const inst_ty = f.typeOfIndex(inst);
    const inst_ctype = try f.ctypeFromType(inst_ty, .parameter);

    const i = f.next_arg_index;
    f.next_arg_index += 1;
    const result: CValue = if (inst_ctype.eql(try f.ctypeFromType(inst_ty, .complete)))
        .{ .arg = i }
    else
        .{ .arg_array = i };

    if (f.liveness.isUnused(inst)) {
        const writer = f.object.writer();
        try writer.writeByte('(');
        try f.renderType(writer, .void);
        try writer.writeByte(')');
        try f.writeCValue(writer, result, .Other);
        try writer.writeAll(";\n");
        return .none;
    }

    return result;
}

fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const ptr_ty = f.typeOf(ty_op.operand);
    const ptr_scalar_ty = ptr_ty.scalarType(zcu);
    const ptr_info = ptr_scalar_ty.ptrInfo(zcu);
    const src_ty: Type = .fromInterned(ptr_info.child);

    if (!src_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
        try reap(f, inst, &.{ty_op.operand});
        return .none;
    }

    const operand = try f.resolveInst(ty_op.operand);

    try reap(f, inst, &.{ty_op.operand});

    const is_aligned = if (ptr_info.flags.alignment != .none)
        ptr_info.flags.alignment.order(src_ty.abiAlignment(zcu)).compare(.gte)
    else
        true;
    const is_array = lowersToArray(src_ty, pt);
    const need_memcpy = !is_aligned or is_array;

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, src_ty);
    const v = try Vectorize.start(f, inst, writer, ptr_ty);

    if (need_memcpy) {
        try writer.writeAll("memcpy(");
        if (!is_array) try writer.writeByte('&');
        try f.writeCValue(writer, local, .Other);
        try v.elem(f, writer);
        try writer.writeAll(", (const char *)");
        try f.writeCValue(writer, operand, .Other);
        try v.elem(f, writer);
        try writer.writeAll(", sizeof(");
        try f.renderType(writer, src_ty);
        try writer.writeAll("))");
    } else if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) {
        const host_bits: u16 = ptr_info.packed_offset.host_size * 8;
        const host_ty = try pt.intType(.unsigned, host_bits);

        const bit_offset_ty = try pt.intType(.unsigned, Type.smallestUnsignedBits(host_bits - 1));
        const bit_offset_val = try pt.intValue(bit_offset_ty, ptr_info.packed_offset.bit_offset);

        const field_ty = try pt.intType(.unsigned, @as(u16, @intCast(src_ty.bitSize(zcu))));

        try f.writeCValue(writer, local, .Other);
        try v.elem(f, writer);
        try writer.writeAll(" = (");
        try f.renderType(writer, src_ty);
        try writer.writeAll(")zig_wrap_");
        try f.object.dg.renderTypeForBuiltinFnName(writer, field_ty);
        try writer.writeAll("((");
        try f.renderType(writer, field_ty);
        try writer.writeByte(')');
        const cant_cast = host_ty.isInt(zcu) and host_ty.bitSize(zcu) > 64;
        if (cant_cast) {
            if (field_ty.bitSize(zcu) > 64) return f.fail("TODO: C backend: implement casting between types > 64 bits", .{});
            try writer.writeAll("zig_lo_");
            try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty);
            try writer.writeByte('(');
        }
        try writer.writeAll("zig_shr_");
        try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty);
        try writer.writeByte('(');
        try f.writeCValueDeref(writer, operand);
        try v.elem(f, writer);
        try writer.print(", {f})", .{try f.fmtIntLiteralDec(bit_offset_val)});
        if (cant_cast) try writer.writeByte(')');
        try f.object.dg.renderBuiltinInfo(writer, field_ty, .bits);
        try writer.writeByte(')');
    } else {
        try f.writeCValue(writer, local, .Other);
        try v.elem(f, writer);
        try writer.writeAll(" = ");
        try f.writeCValueDeref(writer, operand);
        try v.elem(f, writer);
    }
    try writer.writeAll(";\n");
    try v.end(f, inst, writer);

    return local;
}

fn airRet(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !void {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const un_op = f.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
    const writer = f.object.writer();
    const op_inst = un_op.toIndex();
    const op_ty = f.typeOf(un_op);
    const ret_ty = if (is_ptr) op_ty.childType(zcu) else op_ty;
    const ret_ctype = try f.ctypeFromType(ret_ty, .parameter);

    if (op_inst != null and f.air.instructions.items(.tag)[@intFromEnum(op_inst.?)] == .call_always_tail) {
        try reap(f, inst, &.{un_op});
        _ = try airCall(f, op_inst.?, .always_tail);
    } else if (ret_ctype.index != .void) {
        const operand = try f.resolveInst(un_op);
        try reap(f, inst, &.{un_op});
        var deref = is_ptr;
        const is_array = lowersToArray(ret_ty, pt);
        const ret_val = if (is_array) ret_val: {
            const array_local = try f.allocAlignedLocal(inst, .{
                .ctype = ret_ctype,
                .alignas = CType.AlignAs.fromAbiAlignment(ret_ty.abiAlignment(zcu)),
            });
            try writer.writeAll("memcpy(");
            try f.writeCValueMember(writer, array_local, .{ .identifier = "array" });
            try writer.writeAll(", ");
            if (deref)
                try f.writeCValueDeref(writer, operand)
            else
                try f.writeCValue(writer, operand, .FunctionArgument);
            deref = false;
            try writer.writeAll(", sizeof(");
            try f.renderType(writer, ret_ty);
            try writer.writeAll("));\n");
            break :ret_val array_local;
        } else operand;

        try writer.writeAll("return ");
        if (deref)
            try f.writeCValueDeref(writer, ret_val)
        else
            try f.writeCValue(writer, ret_val, .Other);
        try writer.writeAll(";\n");
        if (is_array) {
            try freeLocal(f, inst, ret_val.new_local, null);
        }
    } else {
        try reap(f, inst, &.{un_op});
        // Not even allowed to return void in a naked function.
        if (!f.object.dg.is_naked_fn) try writer.writeAll("return;\n");
    }
}

fn airIntCast(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const operand = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});

    const inst_ty = f.typeOfIndex(inst);
    const inst_scalar_ty = inst_ty.scalarType(zcu);
    const operand_ty = f.typeOf(ty_op.operand);
    const scalar_ty = operand_ty.scalarType(zcu);

    if (f.object.dg.intCastIsNoop(inst_scalar_ty, scalar_ty)) return f.moveCValue(inst, inst_ty, operand);

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const v = try Vectorize.start(f, inst, writer, operand_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(scalar_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try v.elem(f, writer);
    try a.assign(f, writer);
    try f.renderIntCast(writer, inst_scalar_ty, operand, v, scalar_ty, .Other);
    try a.end(f, writer);
    try v.end(f, inst, writer);
    return local;
}

fn airTrunc(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const operand = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});

    const inst_ty = f.typeOfIndex(inst);
    const inst_scalar_ty = inst_ty.scalarType(zcu);
    const dest_int_info = inst_scalar_ty.intInfo(zcu);
    const dest_bits = dest_int_info.bits;
    const dest_c_bits = toCIntBits(dest_bits) orelse
        return f.fail("TODO: C backend: implement integer types larger than 128 bits", .{});
    const operand_ty = f.typeOf(ty_op.operand);
    const scalar_ty = operand_ty.scalarType(zcu);
    const scalar_int_info = scalar_ty.intInfo(zcu);

    const need_cast = dest_c_bits < 64;
    const need_lo = scalar_int_info.bits > 64 and dest_bits <= 64;
    const need_mask = dest_bits < 8 or !std.math.isPowerOfTwo(dest_bits);
    if (!need_cast and !need_lo and !need_mask) return f.moveCValue(inst, inst_ty, operand);

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const v = try Vectorize.start(f, inst, writer, operand_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_scalar_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try v.elem(f, writer);
    try a.assign(f, writer);
    if (need_cast) {
        try writer.writeByte('(');
        try f.renderType(writer, inst_scalar_ty);
        try writer.writeByte(')');
    }
    if (need_lo) {
        try writer.writeAll("zig_lo_");
        try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty);
        try writer.writeByte('(');
    }
    if (!need_mask) {
        try f.writeCValue(writer, operand, .Other);
        try v.elem(f, writer);
    } else switch (dest_int_info.signedness) {
        .unsigned => {
            try writer.writeAll("zig_and_");
            try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty);
            try writer.writeByte('(');
            try f.writeCValue(writer, operand, .FunctionArgument);
            try v.elem(f, writer);
            try writer.print(", {f})", .{
                try f.fmtIntLiteralHex(try inst_scalar_ty.maxIntScalar(pt, scalar_ty)),
            });
        },
        .signed => {
            const c_bits = toCIntBits(scalar_int_info.bits) orelse
                return f.fail("TODO: C backend: implement integer types larger than 128 bits", .{});
            const shift_val = try pt.intValue(.u8, c_bits - dest_bits);

            try writer.writeAll("zig_shr_");
            try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty);
            if (c_bits == 128) {
                try writer.print("(zig_bitCast_i{d}(", .{c_bits});
            } else {
                try writer.print("((int{d}_t)", .{c_bits});
            }
            try writer.print("zig_shl_u{d}(", .{c_bits});
            if (c_bits == 128) {
                try writer.print("zig_bitCast_u{d}(", .{c_bits});
            } else {
                try writer.print("(uint{d}_t)", .{c_bits});
            }
            try f.writeCValue(writer, operand, .FunctionArgument);
            try v.elem(f, writer);
            if (c_bits == 128) try writer.writeByte(')');
            try writer.print(", {f})", .{try f.fmtIntLiteralDec(shift_val)});
            if (c_bits == 128) try writer.writeByte(')');
            try writer.print(", {f})", .{try f.fmtIntLiteralDec(shift_val)});
        },
    }
    if (need_lo) try writer.writeByte(')');
    try a.end(f, writer);
    try v.end(f, inst, writer);
    return local;
}

fn airStore(f: *Function, inst: Air.Inst.Index, safety: bool) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    // *a = b;
    const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;

    const ptr_ty = f.typeOf(bin_op.lhs);
    const ptr_scalar_ty = ptr_ty.scalarType(zcu);
    const ptr_info = ptr_scalar_ty.ptrInfo(zcu);

    const ptr_val = try f.resolveInst(bin_op.lhs);
    const src_ty = f.typeOf(bin_op.rhs);

    const val_is_undef = if (try f.air.value(bin_op.rhs, pt)) |v| v.isUndefDeep(zcu) else false;

    if (val_is_undef) {
        try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
        if (safety and ptr_info.packed_offset.host_size == 0) {
            const writer = f.object.writer();
            try writer.writeAll("memset(");
            try f.writeCValue(writer, ptr_val, .FunctionArgument);
            try writer.writeAll(", 0xaa, sizeof(");
            try f.renderType(writer, .fromInterned(ptr_info.child));
            try writer.writeAll("));\n");
        }
        return .none;
    }

    const is_aligned = if (ptr_info.flags.alignment != .none)
        ptr_info.flags.alignment.order(src_ty.abiAlignment(zcu)).compare(.gte)
    else
        true;
    const is_array = lowersToArray(.fromInterned(ptr_info.child), pt);
    const need_memcpy = !is_aligned or is_array;

    const src_val = try f.resolveInst(bin_op.rhs);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const src_scalar_ctype = try f.ctypeFromType(src_ty.scalarType(zcu), .complete);
    const writer = f.object.writer();
    if (need_memcpy) {
        // For this memcpy to safely work we need the rhs to have the same
        // underlying type as the lhs (i.e. they must both be arrays of the same underlying type).
        assert(src_ty.eql(.fromInterned(ptr_info.child), zcu));

        // If the source is a constant, writeCValue will emit a brace initialization
        // so work around this by initializing into new local.
        // TODO this should be done by manually initializing elements of the dest array
        const array_src = if (src_val == .constant) blk: {
            const new_local = try f.allocLocal(inst, src_ty);
            try f.writeCValue(writer, new_local, .Other);
            try writer.writeAll(" = ");
            try f.writeCValue(writer, src_val, .Other);
            try writer.writeAll(";\n");

            break :blk new_local;
        } else src_val;

        const v = try Vectorize.start(f, inst, writer, ptr_ty);
        try writer.writeAll("memcpy((char *)");
        try f.writeCValue(writer, ptr_val, .FunctionArgument);
        try v.elem(f, writer);
        try writer.writeAll(", ");
        if (!is_array) try writer.writeByte('&');
        try f.writeCValue(writer, array_src, .FunctionArgument);
        try v.elem(f, writer);
        try writer.writeAll(", sizeof(");
        try f.renderType(writer, src_ty);
        try writer.writeAll("))");
        try f.freeCValue(inst, array_src);
        try writer.writeAll(";\n");
        try v.end(f, inst, writer);
    } else if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) {
        const host_bits = ptr_info.packed_offset.host_size * 8;
        const host_ty = try pt.intType(.unsigned, host_bits);

        const bit_offset_ty = try pt.intType(.unsigned, Type.smallestUnsignedBits(host_bits - 1));
        const bit_offset_val = try pt.intValue(bit_offset_ty, ptr_info.packed_offset.bit_offset);

        const src_bits = src_ty.bitSize(zcu);

        const ExpectedContents = [BigInt.Managed.default_capacity]BigIntLimb;
        var stack align(@alignOf(ExpectedContents)) =
            std.heap.stackFallback(@sizeOf(ExpectedContents), f.object.dg.gpa);

        var mask = try BigInt.Managed.initCapacity(stack.get(), BigInt.calcTwosCompLimbCount(host_bits));
        defer mask.deinit();

        try mask.setTwosCompIntLimit(.max, .unsigned, @as(usize, @intCast(src_bits)));
        try mask.shiftLeft(&mask, ptr_info.packed_offset.bit_offset);
        try mask.bitNotWrap(&mask, .unsigned, host_bits);

        const mask_val = try pt.intValue_big(host_ty, mask.toConst());

        const v = try Vectorize.start(f, inst, writer, ptr_ty);
        const a = try Assignment.start(f, writer, src_scalar_ctype);
        try f.writeCValueDeref(writer, ptr_val);
        try v.elem(f, writer);
        try a.assign(f, writer);
        try writer.writeAll("zig_or_");
        try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty);
        try writer.writeAll("(zig_and_");
        try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty);
        try writer.writeByte('(');
        try f.writeCValueDeref(writer, ptr_val);
        try v.elem(f, writer);
        try writer.print(", {f}), zig_shl_", .{try f.fmtIntLiteralHex(mask_val)});
        try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty);
        try writer.writeByte('(');
        const cant_cast = host_ty.isInt(zcu) and host_ty.bitSize(zcu) > 64;
        if (cant_cast) {
            if (src_ty.bitSize(zcu) > 64) return f.fail("TODO: C backend: implement casting between types > 64 bits", .{});
            try writer.writeAll("zig_make_");
            try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty);
            try writer.writeAll("(0, ");
        } else {
            try writer.writeByte('(');
            try f.renderType(writer, host_ty);
            try writer.writeByte(')');
        }

        if (src_ty.isPtrAtRuntime(zcu)) {
            try writer.writeByte('(');
            try f.renderType(writer, .usize);
            try writer.writeByte(')');
        }
        try f.writeCValue(writer, src_val, .Other);
        try v.elem(f, writer);
        if (cant_cast) try writer.writeByte(')');
        try writer.print(", {f}))", .{try f.fmtIntLiteralDec(bit_offset_val)});
        try a.end(f, writer);
        try v.end(f, inst, writer);
    } else {
        switch (ptr_val) {
            .local_ref => |ptr_local_index| switch (src_val) {
                .new_local, .local => |src_local_index| if (ptr_local_index == src_local_index)
                    return .none,
                else => {},
            },
            else => {},
        }
        const v = try Vectorize.start(f, inst, writer, ptr_ty);
        const a = try Assignment.start(f, writer, src_scalar_ctype);
        try f.writeCValueDeref(writer, ptr_val);
        try v.elem(f, writer);
        try a.assign(f, writer);
        try f.writeCValue(writer, src_val, .Other);
        try v.elem(f, writer);
        try a.end(f, writer);
        try v.end(f, inst, writer);
    }
    return .none;
}

fn airOverflow(f: *Function, inst: Air.Inst.Index, operation: []const u8, info: BuiltinInfo) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data;

    const lhs = try f.resolveInst(bin_op.lhs);
    const rhs = try f.resolveInst(bin_op.rhs);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const inst_ty = f.typeOfIndex(inst);
    const operand_ty = f.typeOf(bin_op.lhs);
    const scalar_ty = operand_ty.scalarType(zcu);

    const w = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const v = try Vectorize.start(f, inst, w, operand_ty);
    try f.writeCValueMember(w, local, .{ .field = 1 });
    try v.elem(f, w);
    try w.writeAll(" = zig_");
    try w.writeAll(operation);
    try w.writeAll("o_");
    try f.object.dg.renderTypeForBuiltinFnName(w, scalar_ty);
    try w.writeAll("(&");
    try f.writeCValueMember(w, local, .{ .field = 0 });
    try v.elem(f, w);
    try w.writeAll(", ");
    try f.writeCValue(w, lhs, .FunctionArgument);
    try v.elem(f, w);
    try w.writeAll(", ");
    try f.writeCValue(w, rhs, .FunctionArgument);
    if (f.typeOf(bin_op.rhs).isVector(zcu)) try v.elem(f, w);
    try f.object.dg.renderBuiltinInfo(w, scalar_ty, info);
    try w.writeAll(");\n");
    try v.end(f, inst, w);

    return local;
}

fn airNot(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
    const operand_ty = f.typeOf(ty_op.operand);
    const scalar_ty = operand_ty.scalarType(zcu);
    if (scalar_ty.toIntern() != .bool_type) return try airUnBuiltinCall(f, inst, ty_op.operand, "not", .bits);

    const op = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});

    const inst_ty = f.typeOfIndex(inst);

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const v = try Vectorize.start(f, inst, writer, operand_ty);
    try f.writeCValue(writer, local, .Other);
    try v.elem(f, writer);
    try writer.writeAll(" = ");
    try writer.writeByte('!');
    try f.writeCValue(writer, op, .Other);
    try v.elem(f, writer);
    try writer.writeAll(";\n");
    try v.end(f, inst, writer);

    return local;
}

fn airBinOp(
    f: *Function,
    inst: Air.Inst.Index,
    operator: []const u8,
    operation: []const u8,
    info: BuiltinInfo,
) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
    const operand_ty = f.typeOf(bin_op.lhs);
    const scalar_ty = operand_ty.scalarType(zcu);
    if ((scalar_ty.isInt(zcu) and scalar_ty.bitSize(zcu) > 64) or scalar_ty.isRuntimeFloat())
        return try airBinBuiltinCall(f, inst, operation, info);

    const lhs = try f.resolveInst(bin_op.lhs);
    const rhs = try f.resolveInst(bin_op.rhs);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const inst_ty = f.typeOfIndex(inst);

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const v = try Vectorize.start(f, inst, writer, operand_ty);
    try f.writeCValue(writer, local, .Other);
    try v.elem(f, writer);
    try writer.writeAll(" = ");
    try f.writeCValue(writer, lhs, .Other);
    try v.elem(f, writer);
    try writer.writeByte(' ');
    try writer.writeAll(operator);
    try writer.writeByte(' ');
    try f.writeCValue(writer, rhs, .Other);
    try v.elem(f, writer);
    try writer.writeAll(";\n");
    try v.end(f, inst, writer);

    return local;
}

fn airCmpOp(
    f: *Function,
    inst: Air.Inst.Index,
    data: anytype,
    operator: std.math.CompareOperator,
) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const lhs_ty = f.typeOf(data.lhs);
    const scalar_ty = lhs_ty.scalarType(zcu);

    const scalar_bits = scalar_ty.bitSize(zcu);
    if (scalar_ty.isInt(zcu) and scalar_bits > 64)
        return airCmpBuiltinCall(
            f,
            inst,
            data,
            operator,
            .cmp,
            if (scalar_bits > 128) .bits else .none,
        );
    if (scalar_ty.isRuntimeFloat())
        return airCmpBuiltinCall(f, inst, data, operator, .operator, .none);

    const inst_ty = f.typeOfIndex(inst);
    const lhs = try f.resolveInst(data.lhs);
    const rhs = try f.resolveInst(data.rhs);
    try reap(f, inst, &.{ data.lhs, data.rhs });

    const rhs_ty = f.typeOf(data.rhs);
    const need_cast = lhs_ty.isSinglePointer(zcu) or rhs_ty.isSinglePointer(zcu);
    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const v = try Vectorize.start(f, inst, writer, lhs_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(scalar_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try v.elem(f, writer);
    try a.assign(f, writer);
    if (lhs != .undef and lhs.eql(rhs)) try writer.writeAll(switch (operator) {
        .lt, .neq, .gt => "false",
        .lte, .eq, .gte => "true",
    }) else {
        if (need_cast) try writer.writeAll("(void*)");
        try f.writeCValue(writer, lhs, .Other);
        try v.elem(f, writer);
        try writer.writeAll(compareOperatorC(operator));
        if (need_cast) try writer.writeAll("(void*)");
        try f.writeCValue(writer, rhs, .Other);
        try v.elem(f, writer);
    }
    try a.end(f, writer);
    try v.end(f, inst, writer);

    return local;
}

fn airEquality(
    f: *Function,
    inst: Air.Inst.Index,
    operator: std.math.CompareOperator,
) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ctype_pool = &f.object.dg.ctype_pool;
    const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;

    const operand_ty = f.typeOf(bin_op.lhs);
    const operand_bits = operand_ty.bitSize(zcu);
    if (operand_ty.isAbiInt(zcu) and operand_bits > 64)
        return airCmpBuiltinCall(
            f,
            inst,
            bin_op,
            operator,
            .cmp,
            if (operand_bits > 128) .bits else .none,
        );
    if (operand_ty.isRuntimeFloat())
        return airCmpBuiltinCall(f, inst, bin_op, operator, .operator, .none);

    const lhs = try f.resolveInst(bin_op.lhs);
    const rhs = try f.resolveInst(bin_op.rhs);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, .bool);
    const a = try Assignment.start(f, writer, .bool);
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);

    const operand_ctype = try f.ctypeFromType(operand_ty, .complete);
    if (lhs != .undef and lhs.eql(rhs)) try writer.writeAll(switch (operator) {
        .lt, .lte, .gte, .gt => unreachable,
        .neq => "false",
        .eq => "true",
    }) else switch (operand_ctype.info(ctype_pool)) {
        .basic, .pointer => {
            try f.writeCValue(writer, lhs, .Other);
            try writer.writeAll(compareOperatorC(operator));
            try f.writeCValue(writer, rhs, .Other);
        },
        .aligned, .array, .vector, .fwd_decl, .function => unreachable,
        .aggregate => |aggregate| if (aggregate.fields.len == 2 and
            (aggregate.fields.at(0, ctype_pool).name.index == .is_null or
                aggregate.fields.at(1, ctype_pool).name.index == .is_null))
        {
            try f.writeCValueMember(writer, lhs, .{ .identifier = "is_null" });
            try writer.writeAll(" || ");
            try f.writeCValueMember(writer, rhs, .{ .identifier = "is_null" });
            try writer.writeAll(" ? ");
            try f.writeCValueMember(writer, lhs, .{ .identifier = "is_null" });
            try writer.writeAll(compareOperatorC(operator));
            try f.writeCValueMember(writer, rhs, .{ .identifier = "is_null" });
            try writer.writeAll(" : ");
            try f.writeCValueMember(writer, lhs, .{ .identifier = "payload" });
            try writer.writeAll(compareOperatorC(operator));
            try f.writeCValueMember(writer, rhs, .{ .identifier = "payload" });
        } else for (0..aggregate.fields.len) |field_index| {
            if (field_index > 0) try writer.writeAll(switch (operator) {
                .lt, .lte, .gte, .gt => unreachable,
                .eq => " && ",
                .neq => " || ",
            });
            const field_name: CValue = .{
                .ctype_pool_string = aggregate.fields.at(field_index, ctype_pool).name,
            };
            try f.writeCValueMember(writer, lhs, field_name);
            try writer.writeAll(compareOperatorC(operator));
            try f.writeCValueMember(writer, rhs, field_name);
        },
    }
    try a.end(f, writer);

    return local;
}

fn airCmpLtErrorsLen(f: *Function, inst: Air.Inst.Index) !CValue {
    const un_op = f.air.instructions.items(.data)[@intFromEnum(inst)].un_op;

    const operand = try f.resolveInst(un_op);
    try reap(f, inst, &.{un_op});

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, .bool);
    try f.writeCValue(writer, local, .Other);
    try writer.writeAll(" = ");
    try f.writeCValue(writer, operand, .Other);
    try writer.print(" < sizeof({f}) / sizeof(*{0f});\n", .{fmtIdentSolo("zig_errorName")});
    return local;
}

fn airPtrAddSub(f: *Function, inst: Air.Inst.Index, operator: u8) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data;

    const lhs = try f.resolveInst(bin_op.lhs);
    const rhs = try f.resolveInst(bin_op.rhs);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const inst_ty = f.typeOfIndex(inst);
    const inst_scalar_ty = inst_ty.scalarType(zcu);
    const elem_ty = inst_scalar_ty.elemType2(zcu);
    if (!elem_ty.hasRuntimeBitsIgnoreComptime(zcu)) return f.moveCValue(inst, inst_ty, lhs);
    const inst_scalar_ctype = try f.ctypeFromType(inst_scalar_ty, .complete);

    const local = try f.allocLocal(inst, inst_ty);
    const writer = f.object.writer();
    const v = try Vectorize.start(f, inst, writer, inst_ty);
    const a = try Assignment.start(f, writer, inst_scalar_ctype);
    try f.writeCValue(writer, local, .Other);
    try v.elem(f, writer);
    try a.assign(f, writer);
    // We must convert to and from integer types to prevent UB if the operation
    // results in a NULL pointer, or if LHS is NULL. The operation is only UB
    // if the result is NULL and then dereferenced.
    try writer.writeByte('(');
    try f.renderCType(writer, inst_scalar_ctype);
    try writer.writeAll(")(((uintptr_t)");
    try f.writeCValue(writer, lhs, .Other);
    try v.elem(f, writer);
    try writer.writeAll(") ");
    try writer.writeByte(operator);
    try writer.writeAll(" (");
    try f.writeCValue(writer, rhs, .Other);
    try v.elem(f, writer);
    try writer.writeAll("*sizeof(");
    try f.renderType(writer, elem_ty);
    try writer.writeAll(")))");
    try a.end(f, writer);
    try v.end(f, inst, writer);
    return local;
}

fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: u8, operation: []const u8) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;

    const inst_ty = f.typeOfIndex(inst);
    const inst_scalar_ty = inst_ty.scalarType(zcu);

    if ((inst_scalar_ty.isInt(zcu) and inst_scalar_ty.bitSize(zcu) > 64) or inst_scalar_ty.isRuntimeFloat())
        return try airBinBuiltinCall(f, inst, operation, .none);

    const lhs = try f.resolveInst(bin_op.lhs);
    const rhs = try f.resolveInst(bin_op.rhs);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const v = try Vectorize.start(f, inst, writer, inst_ty);
    try f.writeCValue(writer, local, .Other);
    try v.elem(f, writer);
    // (lhs <> rhs) ? lhs : rhs
    try writer.writeAll(" = (");
    try f.writeCValue(writer, lhs, .Other);
    try v.elem(f, writer);
    try writer.writeByte(' ');
    try writer.writeByte(operator);
    try writer.writeByte(' ');
    try f.writeCValue(writer, rhs, .Other);
    try v.elem(f, writer);
    try writer.writeAll(") ? ");
    try f.writeCValue(writer, lhs, .Other);
    try v.elem(f, writer);
    try writer.writeAll(" : ");
    try f.writeCValue(writer, rhs, .Other);
    try v.elem(f, writer);
    try writer.writeAll(";\n");
    try v.end(f, inst, writer);

    return local;
}

fn airSlice(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data;

    const ptr = try f.resolveInst(bin_op.lhs);
    const len = try f.resolveInst(bin_op.rhs);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const inst_ty = f.typeOfIndex(inst);
    const ptr_ty = inst_ty.slicePtrFieldType(zcu);

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    {
        const a = try Assignment.start(f, writer, try f.ctypeFromType(ptr_ty, .complete));
        try f.writeCValueMember(writer, local, .{ .identifier = "ptr" });
        try a.assign(f, writer);
        try f.writeCValue(writer, ptr, .Other);
        try a.end(f, writer);
    }
    {
        const a = try Assignment.start(f, writer, .usize);
        try f.writeCValueMember(writer, local, .{ .identifier = "len" });
        try a.assign(f, writer);
        try f.writeCValue(writer, len, .Other);
        try a.end(f, writer);
    }
    return local;
}

fn airCall(
    f: *Function,
    inst: Air.Inst.Index,
    modifier: std.builtin.CallModifier,
) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ip = &zcu.intern_pool;
    // Not even allowed to call panic in a naked function.
    if (f.object.dg.is_naked_fn) return .none;

    const gpa = f.object.dg.gpa;
    const writer = f.object.writer();

    const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
    const extra = f.air.extraData(Air.Call, pl_op.payload);
    const args: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[extra.end..][0..extra.data.args_len]);

    const resolved_args = try gpa.alloc(CValue, args.len);
    defer gpa.free(resolved_args);
    for (resolved_args, args) |*resolved_arg, arg| {
        const arg_ty = f.typeOf(arg);
        const arg_ctype = try f.ctypeFromType(arg_ty, .parameter);
        if (arg_ctype.index == .void) {
            resolved_arg.* = .none;
            continue;
        }
        resolved_arg.* = try f.resolveInst(arg);
        if (!arg_ctype.eql(try f.ctypeFromType(arg_ty, .complete))) {
            const array_local = try f.allocAlignedLocal(inst, .{
                .ctype = arg_ctype,
                .alignas = CType.AlignAs.fromAbiAlignment(arg_ty.abiAlignment(zcu)),
            });
            try writer.writeAll("memcpy(");
            try f.writeCValueMember(writer, array_local, .{ .identifier = "array" });
            try writer.writeAll(", ");
            try f.writeCValue(writer, resolved_arg.*, .FunctionArgument);
            try writer.writeAll(", sizeof(");
            try f.renderCType(writer, arg_ctype);
            try writer.writeAll("));\n");
            resolved_arg.* = array_local;
        }
    }

    const callee = try f.resolveInst(pl_op.operand);

    {
        var bt = iterateBigTomb(f, inst);
        try bt.feed(pl_op.operand);
        for (args) |arg| try bt.feed(arg);
    }

    const callee_ty = f.typeOf(pl_op.operand);
    const callee_is_ptr = switch (callee_ty.zigTypeTag(zcu)) {
        .@"fn" => false,
        .pointer => true,
        else => unreachable,
    };
    const fn_info = zcu.typeToFunc(if (callee_is_ptr) callee_ty.childType(zcu) else callee_ty).?;
    const ret_ty: Type = .fromInterned(fn_info.return_type);
    const ret_ctype: CType = if (ret_ty.isNoReturn(zcu))
        .void
    else
        try f.ctypeFromType(ret_ty, .parameter);

    const result_local = result: {
        if (modifier == .always_tail) {
            try writer.writeAll("zig_always_tail return ");
            break :result .none;
        } else if (ret_ctype.index == .void) {
            break :result .none;
        } else if (f.liveness.isUnused(inst)) {
            try writer.writeByte('(');
            try f.renderCType(writer, .void);
            try writer.writeByte(')');
            break :result .none;
        } else {
            const local = try f.allocAlignedLocal(inst, .{
                .ctype = ret_ctype,
                .alignas = CType.AlignAs.fromAbiAlignment(ret_ty.abiAlignment(zcu)),
            });
            try f.writeCValue(writer, local, .Other);
            try writer.writeAll(" = ");
            break :result local;
        }
    };

    callee: {
        known: {
            const callee_val = (try f.air.value(pl_op.operand, pt)) orelse break :known;
            const fn_nav, const need_cast = switch (ip.indexToKey(callee_val.toIntern())) {
                .@"extern" => |@"extern"| .{ @"extern".owner_nav, false },
                .func => |func| .{ func.owner_nav, Type.fromInterned(func.ty).fnCallingConvention(zcu) != .naked and
                    Type.fromInterned(func.uncoerced_ty).fnCallingConvention(zcu) == .naked },
                .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) {
                    .nav => |nav| .{ nav, Type.fromInterned(ptr.ty).childType(zcu).fnCallingConvention(zcu) != .naked and
                        zcu.navValue(nav).typeOf(zcu).fnCallingConvention(zcu) == .naked },
                    else => break :known,
                } else break :known,
                else => break :known,
            };
            if (need_cast) {
                try writer.writeAll("((");
                try f.renderType(writer, if (callee_is_ptr) callee_ty else try pt.singleConstPtrType(callee_ty));
                try writer.writeByte(')');
                if (!callee_is_ptr) try writer.writeByte('&');
            }
            switch (modifier) {
                .auto, .always_tail => try f.object.dg.renderNavName(writer, fn_nav),
                inline .never_tail, .never_inline => |m| try writer.writeAll(try f.getLazyFnName(@unionInit(LazyFnKey, @tagName(m), fn_nav))),
                else => unreachable,
            }
            if (need_cast) try writer.writeByte(')');
            break :callee;
        }
        switch (modifier) {
            .auto, .always_tail => {},
            .never_tail => return f.fail("CBE: runtime callee with never_tail attribute unsupported", .{}),
            .never_inline => return f.fail("CBE: runtime callee with never_inline attribute unsupported", .{}),
            else => unreachable,
        }
        // Fall back to function pointer call.
        try f.writeCValue(writer, callee, .Other);
    }

    try writer.writeByte('(');
    var need_comma = false;
    for (resolved_args) |resolved_arg| {
        if (resolved_arg == .none) continue;
        if (need_comma) try writer.writeAll(", ");
        need_comma = true;
        try f.writeCValue(writer, resolved_arg, .FunctionArgument);
        try f.freeCValue(inst, resolved_arg);
    }
    try writer.writeAll(");\n");

    const result = result: {
        if (result_local == .none or !lowersToArray(ret_ty, pt))
            break :result result_local;

        const array_local = try f.allocLocal(inst, ret_ty);
        try writer.writeAll("memcpy(");
        try f.writeCValue(writer, array_local, .FunctionArgument);
        try writer.writeAll(", ");
        try f.writeCValueMember(writer, result_local, .{ .identifier = "array" });
        try writer.writeAll(", sizeof(");
        try f.renderType(writer, ret_ty);
        try writer.writeAll("));\n");
        try freeLocal(f, inst, result_local.new_local, null);
        break :result array_local;
    };

    return result;
}

fn airDbgStmt(f: *Function, inst: Air.Inst.Index) !CValue {
    const dbg_stmt = f.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt;
    const writer = f.object.writer();
    // TODO re-evaluate whether to emit these or not. If we naively emit
    // these directives, the output file will report bogus line numbers because
    // every newline after the #line directive adds one to the line.
    // We also don't print the filename yet, so the output is strictly unhelpful.
    // If we wanted to go this route, we would need to go all the way and not output
    // newlines until the next dbg_stmt occurs.
    // Perhaps an additional compilation option is in order?
    //try writer.print("#line {d}\n", .{dbg_stmt.line + 1});
    try writer.print("/* file:{d}:{d} */\n", .{ dbg_stmt.line + 1, dbg_stmt.column + 1 });
    return .none;
}

fn airDbgEmptyStmt(f: *Function, _: Air.Inst.Index) !CValue {
    try f.object.writer().writeAll("(void)0;\n");
    return .none;
}

fn airDbgInlineBlock(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ip = &zcu.intern_pool;
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const extra = f.air.extraData(Air.DbgInlineBlock, ty_pl.payload);
    const owner_nav = ip.getNav(zcu.funcInfo(extra.data.func).owner_nav);
    const writer = f.object.writer();
    try writer.print("/* inline:{} */\n", .{owner_nav.fqn.fmt(&zcu.intern_pool)});
    return lowerBlock(f, inst, @ptrCast(f.air.extra.items[extra.end..][0..extra.data.body_len]));
}

fn airDbgVar(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const tag = f.air.instructions.items(.tag)[@intFromEnum(inst)];
    const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
    const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload);
    const operand_is_undef = if (try f.air.value(pl_op.operand, pt)) |v| v.isUndefDeep(zcu) else false;
    if (!operand_is_undef) _ = try f.resolveInst(pl_op.operand);

    try reap(f, inst, &.{pl_op.operand});
    const writer = f.object.writer();
    try writer.print("/* {s}:{s} */\n", .{ @tagName(tag), name.toSlice(f.air) });
    return .none;
}

fn airBlock(f: *Function, inst: Air.Inst.Index) !CValue {
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const extra = f.air.extraData(Air.Block, ty_pl.payload);
    return lowerBlock(f, inst, @ptrCast(f.air.extra.items[extra.end..][0..extra.data.body_len]));
}

fn lowerBlock(f: *Function, inst: Air.Inst.Index, body: []const Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const liveness_block = f.liveness.getBlock(inst);

    const block_id = f.next_block_index;
    f.next_block_index += 1;
    const writer = f.object.writer();

    const inst_ty = f.typeOfIndex(inst);
    const result = if (inst_ty.hasRuntimeBitsIgnoreComptime(zcu) and !f.liveness.isUnused(inst))
        try f.allocLocal(inst, inst_ty)
    else
        .none;

    try f.blocks.putNoClobber(f.object.dg.gpa, inst, .{
        .block_id = block_id,
        .result = result,
    });

    try genBodyResolveState(f, inst, &.{}, body, true);

    assert(f.blocks.remove(inst));

    // The body might result in some values we had beforehand being killed
    for (liveness_block.deaths) |death| {
        try die(f, inst, death.toRef());
    }

    try f.object.indent_writer.insertNewline();

    // noreturn blocks have no `br` instructions reaching them, so we don't want a label
    if (f.object.dg.is_naked_fn) {
        if (f.object.dg.expected_block) |expected_block| {
            if (block_id != expected_block)
                return f.fail("runtime code not allowed in naked function", .{});
            f.object.dg.expected_block = null;
        }
    } else if (!f.typeOfIndex(inst).isNoReturn(zcu)) {
        // label must be followed by an expression, include an empty one.
        try writer.print("zig_block_{d}:;\n", .{block_id});
    }

    return result;
}

fn airTry(f: *Function, inst: Air.Inst.Index) !CValue {
    const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
    const extra = f.air.extraData(Air.Try, pl_op.payload);
    const body: []const Air.Inst.Index = @ptrCast(f.air.extra.items[extra.end..][0..extra.data.body_len]);
    const err_union_ty = f.typeOf(pl_op.operand);
    return lowerTry(f, inst, pl_op.operand, body, err_union_ty, false);
}

fn airTryPtr(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const extra = f.air.extraData(Air.TryPtr, ty_pl.payload);
    const body: []const Air.Inst.Index = @ptrCast(f.air.extra.items[extra.end..][0..extra.data.body_len]);
    const err_union_ty = f.typeOf(extra.data.ptr).childType(zcu);
    return lowerTry(f, inst, extra.data.ptr, body, err_union_ty, true);
}

fn lowerTry(
    f: *Function,
    inst: Air.Inst.Index,
    operand: Air.Inst.Ref,
    body: []const Air.Inst.Index,
    err_union_ty: Type,
    is_ptr: bool,
) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const err_union = try f.resolveInst(operand);
    const inst_ty = f.typeOfIndex(inst);
    const liveness_condbr = f.liveness.getCondBr(inst);
    const writer = f.object.writer();
    const payload_ty = err_union_ty.errorUnionPayload(zcu);
    const payload_has_bits = payload_ty.hasRuntimeBitsIgnoreComptime(zcu);

    if (!err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {
        try writer.writeAll("if (");
        if (!payload_has_bits) {
            if (is_ptr)
                try f.writeCValueDeref(writer, err_union)
            else
                try f.writeCValue(writer, err_union, .Other);
        } else {
            // Reap the operand so that it can be reused inside genBody.
            // Remember we must avoid calling reap() twice for the same operand
            // in this function.
            try reap(f, inst, &.{operand});
            if (is_ptr)
                try f.writeCValueDerefMember(writer, err_union, .{ .identifier = "error" })
            else
                try f.writeCValueMember(writer, err_union, .{ .identifier = "error" });
        }
        try writer.writeAll(") ");

        try genBodyResolveState(f, inst, liveness_condbr.else_deaths, body, false);
        try f.object.indent_writer.insertNewline();
        if (f.object.dg.expected_block) |_|
            return f.fail("runtime code not allowed in naked function", .{});
    }

    // Now we have the "then branch" (in terms of the liveness data); process any deaths.
    for (liveness_condbr.then_deaths) |death| {
        try die(f, inst, death.toRef());
    }

    if (!payload_has_bits) {
        if (!is_ptr) {
            return .none;
        } else {
            return err_union;
        }
    }

    try reap(f, inst, &.{operand});

    if (f.liveness.isUnused(inst)) return .none;

    const local = try f.allocLocal(inst, inst_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);
    if (is_ptr) {
        try writer.writeByte('&');
        try f.writeCValueDerefMember(writer, err_union, .{ .identifier = "payload" });
    } else try f.writeCValueMember(writer, err_union, .{ .identifier = "payload" });
    try a.end(f, writer);
    return local;
}

fn airBr(f: *Function, inst: Air.Inst.Index) !void {
    const branch = f.air.instructions.items(.data)[@intFromEnum(inst)].br;
    const block = f.blocks.get(branch.block_inst).?;
    const result = block.result;
    const writer = f.object.writer();

    if (f.object.dg.is_naked_fn) {
        if (result != .none) return f.fail("runtime code not allowed in naked function", .{});
        f.object.dg.expected_block = block.block_id;
        return;
    }

    // If result is .none then the value of the block is unused.
    if (result != .none) {
        const operand_ty = f.typeOf(branch.operand);
        const operand = try f.resolveInst(branch.operand);
        try reap(f, inst, &.{branch.operand});

        const a = try Assignment.start(f, writer, try f.ctypeFromType(operand_ty, .complete));
        try f.writeCValue(writer, result, .Other);
        try a.assign(f, writer);
        try f.writeCValue(writer, operand, .Other);
        try a.end(f, writer);
    }

    try writer.print("goto zig_block_{d};\n", .{block.block_id});
}

fn airRepeat(f: *Function, inst: Air.Inst.Index) !void {
    const repeat = f.air.instructions.items(.data)[@intFromEnum(inst)].repeat;
    const writer = f.object.writer();
    try writer.print("goto zig_loop_{d};\n", .{@intFromEnum(repeat.loop_inst)});
}

fn airSwitchDispatch(f: *Function, inst: Air.Inst.Index) !void {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const br = f.air.instructions.items(.data)[@intFromEnum(inst)].br;
    const writer = f.object.writer();

    if (try f.air.value(br.operand, pt)) |cond_val| {
        // Comptime-known dispatch. Iterate the cases to find the correct
        // one, and branch directly to the corresponding case.
        const switch_br = f.air.unwrapSwitch(br.block_inst);
        var it = switch_br.iterateCases();
        const target_case_idx: u32 = target: while (it.next()) |case| {
            for (case.items) |item| {
                const val = Value.fromInterned(item.toInterned().?);
                if (cond_val.compareHetero(.eq, val, zcu)) break :target case.idx;
            }
            for (case.ranges) |range| {
                const low = Value.fromInterned(range[0].toInterned().?);
                const high = Value.fromInterned(range[1].toInterned().?);
                if (cond_val.compareHetero(.gte, low, zcu) and
                    cond_val.compareHetero(.lte, high, zcu))
                {
                    break :target case.idx;
                }
            }
        } else switch_br.cases_len;
        try writer.print("goto zig_switch_{d}_dispatch_{d};\n", .{ @intFromEnum(br.block_inst), target_case_idx });
        return;
    }

    // Runtime-known dispatch. Set the switch condition, and branch back.
    const cond = try f.resolveInst(br.operand);
    const cond_local = f.loop_switch_conds.get(br.block_inst).?;
    try f.writeCValue(writer, .{ .local = cond_local }, .Other);
    try writer.writeAll(" = ");
    try f.writeCValue(writer, cond, .Other);
    try writer.writeAll(";\n");
    try writer.print("goto zig_switch_{d}_loop;", .{@intFromEnum(br.block_inst)});
}

fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue {
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
    const inst_ty = f.typeOfIndex(inst);

    const operand = try f.resolveInst(ty_op.operand);
    const operand_ty = f.typeOf(ty_op.operand);

    const bitcasted = try bitcast(f, inst_ty, operand, operand_ty);
    try reap(f, inst, &.{ty_op.operand});
    return f.moveCValue(inst, inst_ty, bitcasted);
}

fn bitcast(f: *Function, dest_ty: Type, operand: CValue, operand_ty: Type) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const target = &f.object.dg.mod.resolved_target.result;
    const ctype_pool = &f.object.dg.ctype_pool;
    const writer = f.object.writer();

    if (operand_ty.isAbiInt(zcu) and dest_ty.isAbiInt(zcu)) {
        const src_info = dest_ty.intInfo(zcu);
        const dest_info = operand_ty.intInfo(zcu);
        if (src_info.signedness == dest_info.signedness and
            src_info.bits == dest_info.bits) return operand;
    }

    if (dest_ty.isPtrAtRuntime(zcu) or operand_ty.isPtrAtRuntime(zcu)) {
        const local = try f.allocLocal(null, dest_ty);
        try f.writeCValue(writer, local, .Other);
        try writer.writeAll(" = (");
        try f.renderType(writer, dest_ty);
        try writer.writeByte(')');
        try f.writeCValue(writer, operand, .Other);
        try writer.writeAll(";\n");
        return local;
    }

    const operand_lval = if (operand == .constant) blk: {
        const operand_local = try f.allocLocal(null, operand_ty);
        try f.writeCValue(writer, operand_local, .Other);
        try writer.writeAll(" = ");
        try f.writeCValue(writer, operand, .Other);
        try writer.writeAll(";\n");
        break :blk operand_local;
    } else operand;

    const local = try f.allocLocal(null, dest_ty);
    try writer.writeAll("memcpy(&");
    try f.writeCValue(writer, local, .Other);
    try writer.writeAll(", &");
    try f.writeCValue(writer, operand_lval, .Other);
    try writer.writeAll(", sizeof(");
    try f.renderType(
        writer,
        if (dest_ty.abiSize(zcu) <= operand_ty.abiSize(zcu)) dest_ty else operand_ty,
    );
    try writer.writeAll("));\n");

    // Ensure padding bits have the expected value.
    if (dest_ty.isAbiInt(zcu)) {
        const dest_ctype = try f.ctypeFromType(dest_ty, .complete);
        const dest_info = dest_ty.intInfo(zcu);
        var bits: u16 = dest_info.bits;
        var wrap_ctype: ?CType = null;
        var need_bitcasts = false;

        try f.writeCValue(writer, local, .Other);
        switch (dest_ctype.info(ctype_pool)) {
            else => {},
            .array => |array_info| {
                try writer.print("[{d}]", .{switch (target.cpu.arch.endian()) {
                    .little => array_info.len - 1,
                    .big => 0,
                }});
                wrap_ctype = array_info.elem_ctype.toSignedness(dest_info.signedness);
                need_bitcasts = wrap_ctype.?.index == .zig_i128;
                bits -= 1;
                bits %= @as(u16, @intCast(f.byteSize(array_info.elem_ctype) * 8));
                bits += 1;
            },
        }
        try writer.writeAll(" = ");
        if (need_bitcasts) {
            try writer.writeAll("zig_bitCast_");
            try f.object.dg.renderCTypeForBuiltinFnName(writer, wrap_ctype.?.toUnsigned());
            try writer.writeByte('(');
        }
        try writer.writeAll("zig_wrap_");
        const info_ty = try pt.intType(dest_info.signedness, bits);
        if (wrap_ctype) |ctype|
            try f.object.dg.renderCTypeForBuiltinFnName(writer, ctype)
        else
            try f.object.dg.renderTypeForBuiltinFnName(writer, info_ty);
        try writer.writeByte('(');
        if (need_bitcasts) {
            try writer.writeAll("zig_bitCast_");
            try f.object.dg.renderCTypeForBuiltinFnName(writer, wrap_ctype.?);
            try writer.writeByte('(');
        }
        try f.writeCValue(writer, local, .Other);
        switch (dest_ctype.info(ctype_pool)) {
            else => {},
            .array => |array_info| try writer.print("[{d}]", .{
                switch (target.cpu.arch.endian()) {
                    .little => array_info.len - 1,
                    .big => 0,
                },
            }),
        }
        if (need_bitcasts) try writer.writeByte(')');
        try f.object.dg.renderBuiltinInfo(writer, info_ty, .bits);
        if (need_bitcasts) try writer.writeByte(')');
        try writer.writeAll(");\n");
    }

    try f.freeCValue(null, operand_lval);
    return local;
}

fn airTrap(f: *Function, writer: anytype) !void {
    // Not even allowed to call trap in a naked function.
    if (f.object.dg.is_naked_fn) return;
    try writer.writeAll("zig_trap();\n");
}

fn airBreakpoint(writer: anytype) !CValue {
    try writer.writeAll("zig_breakpoint();\n");
    return .none;
}

fn airRetAddr(f: *Function, inst: Air.Inst.Index) !CValue {
    const writer = f.object.writer();
    const local = try f.allocLocal(inst, .usize);
    try f.writeCValue(writer, local, .Other);
    try writer.writeAll(" = (");
    try f.renderType(writer, .usize);
    try writer.writeAll(")zig_return_address();\n");
    return local;
}

fn airFrameAddress(f: *Function, inst: Air.Inst.Index) !CValue {
    const writer = f.object.writer();
    const local = try f.allocLocal(inst, .usize);
    try f.writeCValue(writer, local, .Other);
    try writer.writeAll(" = (");
    try f.renderType(writer, .usize);
    try writer.writeAll(")zig_frame_address();\n");
    return local;
}

fn airUnreach(f: *Function) !void {
    // Not even allowed to call unreachable in a naked function.
    if (f.object.dg.is_naked_fn) return;
    try f.object.writer().writeAll("zig_unreachable();\n");
}

fn airLoop(f: *Function, inst: Air.Inst.Index) !void {
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const loop = f.air.extraData(Air.Block, ty_pl.payload);
    const body: []const Air.Inst.Index = @ptrCast(f.air.extra.items[loop.end..][0..loop.data.body_len]);
    const writer = f.object.writer();

    // `repeat` instructions matching this loop will branch to
    // this label. Since we need a label for arbitrary `repeat`
    // anyway, there's actually no need to use a "real" looping
    // construct at all!
    try writer.print("zig_loop_{d}:\n", .{@intFromEnum(inst)});
    try genBodyInner(f, body); // no need to restore state, we're noreturn
}

fn airCondBr(f: *Function, inst: Air.Inst.Index) !void {
    const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
    const cond = try f.resolveInst(pl_op.operand);
    try reap(f, inst, &.{pl_op.operand});
    const extra = f.air.extraData(Air.CondBr, pl_op.payload);
    const then_body: []const Air.Inst.Index = @ptrCast(f.air.extra.items[extra.end..][0..extra.data.then_body_len]);
    const else_body: []const Air.Inst.Index = @ptrCast(f.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]);
    const liveness_condbr = f.liveness.getCondBr(inst);
    const writer = f.object.writer();

    try writer.writeAll("if (");
    try f.writeCValue(writer, cond, .Other);
    try writer.writeAll(") ");

    try genBodyResolveState(f, inst, liveness_condbr.then_deaths, then_body, false);
    try writer.writeByte('\n');
    if (else_body.len > 0) if (f.object.dg.expected_block) |_|
        return f.fail("runtime code not allowed in naked function", .{});

    // We don't need to use `genBodyResolveState` for the else block, because this instruction is
    // noreturn so must terminate a body, therefore we don't need to leave `value_map` or
    // `free_locals_map` well defined (our parent is responsible for doing that).

    for (liveness_condbr.else_deaths) |death| {
        try die(f, inst, death.toRef());
    }

    // We never actually need an else block, because our branches are noreturn so must (for
    // instance) `br` to a block (label).

    try genBodyInner(f, else_body);
}

fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const gpa = f.object.dg.gpa;
    const switch_br = f.air.unwrapSwitch(inst);
    const init_condition = try f.resolveInst(switch_br.operand);
    try reap(f, inst, &.{switch_br.operand});
    const condition_ty = f.typeOf(switch_br.operand);
    const writer = f.object.writer();

    // For dispatches, we will create a local alloc to contain the condition value.
    // This may not result in optimal codegen for switch loops, but it minimizes the
    // amount of C code we generate, which is probably more desirable here (and is simpler).
    const condition = if (is_dispatch_loop) cond: {
        const new_local = try f.allocLocal(inst, condition_ty);
        try f.copyCValue(try f.ctypeFromType(condition_ty, .complete), new_local, init_condition);
        try writer.print("zig_switch_{d}_loop:\n", .{@intFromEnum(inst)});
        try f.loop_switch_conds.put(gpa, inst, new_local.new_local);
        break :cond new_local;
    } else init_condition;

    defer if (is_dispatch_loop) {
        assert(f.loop_switch_conds.remove(inst));
    };

    try writer.writeAll("switch (");

    const lowered_condition_ty: Type = if (condition_ty.toIntern() == .bool_type)
        .u1
    else if (condition_ty.isPtrAtRuntime(zcu))
        .usize
    else
        condition_ty;
    if (condition_ty.toIntern() != lowered_condition_ty.toIntern()) {
        try writer.writeByte('(');
        try f.renderType(writer, lowered_condition_ty);
        try writer.writeByte(')');
    }
    try f.writeCValue(writer, condition, .Other);
    try writer.writeAll(") {");
    f.object.indent_writer.pushIndent();

    const liveness = try f.liveness.getSwitchBr(gpa, inst, switch_br.cases_len + 1);
    defer gpa.free(liveness.deaths);

    var any_range_cases = false;
    var it = switch_br.iterateCases();
    while (it.next()) |case| {
        if (case.ranges.len > 0) {
            any_range_cases = true;
            continue;
        }
        for (case.items) |item| {
            try f.object.indent_writer.insertNewline();
            try writer.writeAll("case ");
            const item_value = try f.air.value(item, pt);
            // If `item_value` is a pointer with a known integer address, print the address
            // with no cast to avoid a warning.
            write_val: {
                if (condition_ty.isPtrAtRuntime(zcu)) {
                    if (item_value.?.getUnsignedInt(zcu)) |item_int| {
                        try writer.print("{f}", .{try f.fmtIntLiteralDec(try pt.intValue(lowered_condition_ty, item_int))});
                        break :write_val;
                    }
                }
                if (condition_ty.isPtrAtRuntime(zcu)) {
                    try writer.writeByte('(');
                    try f.renderType(writer, .usize);
                    try writer.writeByte(')');
                }
                try f.object.dg.renderValue(writer, (try f.air.value(item, pt)).?, .Other);
            }
            try writer.writeByte(':');
        }
        try writer.writeAll(" {\n");
        f.object.indent_writer.pushIndent();
        if (is_dispatch_loop) {
            try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx });
        }
        try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true);
        f.object.indent_writer.popIndent();
        try writer.writeByte('}');
        if (f.object.dg.expected_block) |_|
            return f.fail("runtime code not allowed in naked function", .{});

        // The case body must be noreturn so we don't need to insert a break.
    }

    const else_body = it.elseBody();
    try f.object.indent_writer.insertNewline();

    try writer.writeAll("default: ");
    if (any_range_cases) {
        // We will iterate the cases again to handle those with ranges, and generate
        // code using conditions rather than switch cases for such cases.
        it = switch_br.iterateCases();
        while (it.next()) |case| {
            if (case.ranges.len == 0) continue; // handled above

            try writer.writeAll("if (");
            for (case.items, 0..) |item, item_i| {
                if (item_i != 0) try writer.writeAll(" || ");
                try f.writeCValue(writer, condition, .Other);
                try writer.writeAll(" == ");
                try f.object.dg.renderValue(writer, (try f.air.value(item, pt)).?, .Other);
            }
            for (case.ranges, 0..) |range, range_i| {
                if (case.items.len != 0 or range_i != 0) try writer.writeAll(" || ");
                // "(x >= lower && x <= upper)"
                try writer.writeByte('(');
                try f.writeCValue(writer, condition, .Other);
                try writer.writeAll(" >= ");
                try f.object.dg.renderValue(writer, (try f.air.value(range[0], pt)).?, .Other);
                try writer.writeAll(" && ");
                try f.writeCValue(writer, condition, .Other);
                try writer.writeAll(" <= ");
                try f.object.dg.renderValue(writer, (try f.air.value(range[1], pt)).?, .Other);
                try writer.writeByte(')');
            }
            try writer.writeAll(") {\n");
            f.object.indent_writer.pushIndent();
            if (is_dispatch_loop) {
                try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx });
            }
            try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true);
            f.object.indent_writer.popIndent();
            try writer.writeByte('}');
            if (f.object.dg.expected_block) |_|
                return f.fail("runtime code not allowed in naked function", .{});
        }
    }
    if (is_dispatch_loop) {
        try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), switch_br.cases_len });
    }
    if (else_body.len > 0) {
        // Note that this must be the last case, so we do not need to use `genBodyResolveState` since
        // the parent block will do it (because the case body is noreturn).
        for (liveness.deaths[liveness.deaths.len - 1]) |death| {
            try die(f, inst, death.toRef());
        }
        try genBody(f, else_body);
        if (f.object.dg.expected_block) |_|
            return f.fail("runtime code not allowed in naked function", .{});
    } else {
        try writer.writeAll("zig_unreachable();");
    }
    try f.object.indent_writer.insertNewline();

    f.object.indent_writer.popIndent();
    try writer.writeAll("}\n");
}

fn asmInputNeedsLocal(f: *Function, constraint: []const u8, value: CValue) bool {
    const dg = f.object.dg;
    const target = &dg.mod.resolved_target.result;
    return switch (constraint[0]) {
        '{' => true,
        'i', 'r' => false,
        'I' => !target.cpu.arch.isArm(),
        else => switch (value) {
            .constant => |val| switch (dg.pt.zcu.intern_pool.indexToKey(val.toIntern())) {
                .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) {
                    .nav => false,
                    else => true,
                } else true,
                else => true,
            },
            else => false,
        },
    };
}

fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const extra = f.air.extraData(Air.Asm, ty_pl.payload);
    const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
    const clobbers_len: u31 = @truncate(extra.data.flags);
    const gpa = f.object.dg.gpa;
    var extra_i: usize = extra.end;
    const outputs: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[extra_i..][0..extra.data.outputs_len]);
    extra_i += outputs.len;
    const inputs: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[extra_i..][0..extra.data.inputs_len]);
    extra_i += inputs.len;

    const result = result: {
        const writer = f.object.writer();
        const inst_ty = f.typeOfIndex(inst);
        const inst_local = if (inst_ty.hasRuntimeBitsIgnoreComptime(zcu)) local: {
            const inst_local = try f.allocLocalValue(.{
                .ctype = try f.ctypeFromType(inst_ty, .complete),
                .alignas = CType.AlignAs.fromAbiAlignment(inst_ty.abiAlignment(zcu)),
            });
            if (f.wantSafety()) {
                try f.writeCValue(writer, inst_local, .Other);
                try writer.writeAll(" = ");
                try f.writeCValue(writer, .{ .undef = inst_ty }, .Other);
                try writer.writeAll(";\n");
            }
            break :local inst_local;
        } else .none;

        const locals_begin: LocalIndex = @intCast(f.locals.items.len);
        const constraints_extra_begin = extra_i;
        for (outputs) |output| {
            const extra_bytes = mem.sliceAsBytes(f.air.extra.items[extra_i..]);
            const constraint = mem.sliceTo(extra_bytes, 0);
            const name = mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
            // This equation accounts for the fact that even if we have exactly 4 bytes
            // for the string, we still use the next u32 for the null terminator.
            extra_i += (constraint.len + name.len + (2 + 3)) / 4;

            if (constraint.len < 2 or constraint[0] != '=' or
                (constraint[1] == '{' and constraint[constraint.len - 1] != '}'))
            {
                return f.fail("CBE: constraint not supported: '{s}'", .{constraint});
            }

            const is_reg = constraint[1] == '{';
            if (is_reg) {
                const output_ty = if (output == .none) inst_ty else f.typeOf(output).childType(zcu);
                try writer.writeAll("register ");
                const output_local = try f.allocLocalValue(.{
                    .ctype = try f.ctypeFromType(output_ty, .complete),
                    .alignas = CType.AlignAs.fromAbiAlignment(output_ty.abiAlignment(zcu)),
                });
                try f.allocs.put(gpa, output_local.new_local, false);
                try f.object.dg.renderTypeAndName(writer, output_ty, output_local, .{}, .none, .complete);
                try writer.writeAll(" __asm(\"");
                try writer.writeAll(constraint["={".len .. constraint.len - "}".len]);
                try writer.writeAll("\")");
                if (f.wantSafety()) {
                    try writer.writeAll(" = ");
                    try f.writeCValue(writer, .{ .undef = output_ty }, .Other);
                }
                try writer.writeAll(";\n");
            }
        }
        for (inputs) |input| {
            const extra_bytes = mem.sliceAsBytes(f.air.extra.items[extra_i..]);
            const constraint = mem.sliceTo(extra_bytes, 0);
            const name = mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
            // This equation accounts for the fact that even if we have exactly 4 bytes
            // for the string, we still use the next u32 for the null terminator.
            extra_i += (constraint.len + name.len + (2 + 3)) / 4;

            if (constraint.len < 1 or mem.indexOfScalar(u8, "=+&%", constraint[0]) != null or
                (constraint[0] == '{' and constraint[constraint.len - 1] != '}'))
            {
                return f.fail("CBE: constraint not supported: '{s}'", .{constraint});
            }

            const is_reg = constraint[0] == '{';
            const input_val = try f.resolveInst(input);
            if (asmInputNeedsLocal(f, constraint, input_val)) {
                const input_ty = f.typeOf(input);
                if (is_reg) try writer.writeAll("register ");
                const input_local = try f.allocLocalValue(.{
                    .ctype = try f.ctypeFromType(input_ty, .complete),
                    .alignas = CType.AlignAs.fromAbiAlignment(input_ty.abiAlignment(zcu)),
                });
                try f.allocs.put(gpa, input_local.new_local, false);
                try f.object.dg.renderTypeAndName(writer, input_ty, input_local, Const, .none, .complete);
                if (is_reg) {
                    try writer.writeAll(" __asm(\"");
                    try writer.writeAll(constraint["{".len .. constraint.len - "}".len]);
                    try writer.writeAll("\")");
                }
                try writer.writeAll(" = ");
                try f.writeCValue(writer, input_val, .Other);
                try writer.writeAll(";\n");
            }
        }
        for (0..clobbers_len) |_| {
            const clobber = mem.sliceTo(mem.sliceAsBytes(f.air.extra.items[extra_i..]), 0);
            // This equation accounts for the fact that even if we have exactly 4 bytes
            // for the string, we still use the next u32 for the null terminator.
            extra_i += clobber.len / 4 + 1;
        }

        {
            const asm_source = mem.sliceAsBytes(f.air.extra.items[extra_i..])[0..extra.data.source_len];

            var stack = std.heap.stackFallback(256, f.object.dg.gpa);
            const allocator = stack.get();
            const fixed_asm_source = try allocator.alloc(u8, asm_source.len);
            defer allocator.free(fixed_asm_source);

            var src_i: usize = 0;
            var dst_i: usize = 0;
            while (true) {
                const literal = mem.sliceTo(asm_source[src_i..], '%');
                src_i += literal.len;

                @memcpy(fixed_asm_source[dst_i..][0..literal.len], literal);
                dst_i += literal.len;

                if (src_i >= asm_source.len) break;

                src_i += 1;
                if (src_i >= asm_source.len)
                    return f.fail("CBE: invalid inline asm string '{s}'", .{asm_source});

                fixed_asm_source[dst_i] = '%';
                dst_i += 1;

                if (asm_source[src_i] != '[') {
                    // This also handles %%
                    fixed_asm_source[dst_i] = asm_source[src_i];
                    src_i += 1;
                    dst_i += 1;
                    continue;
                }

                const desc = mem.sliceTo(asm_source[src_i..], ']');
                if (mem.indexOfScalar(u8, desc, ':')) |colon| {
                    const name = desc[0..colon];
                    const modifier = desc[colon + 1 ..];

                    @memcpy(fixed_asm_source[dst_i..][0..modifier.len], modifier);
                    dst_i += modifier.len;
                    @memcpy(fixed_asm_source[dst_i..][0..name.len], name);
                    dst_i += name.len;

                    src_i += desc.len;
                    if (src_i >= asm_source.len)
                        return f.fail("CBE: invalid inline asm string '{s}'", .{asm_source});
                }
            }

            try writer.writeAll("__asm");
            if (is_volatile) try writer.writeAll(" volatile");
            try writer.print("({f}", .{fmtStringLiteral(fixed_asm_source[0..dst_i], null)});
        }

        extra_i = constraints_extra_begin;
        var locals_index = locals_begin;
        try writer.writeByte(':');
        for (outputs, 0..) |output, index| {
            const extra_bytes = mem.sliceAsBytes(f.air.extra.items[extra_i..]);
            const constraint = mem.sliceTo(extra_bytes, 0);
            const name = mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
            // This equation accounts for the fact that even if we have exactly 4 bytes
            // for the string, we still use the next u32 for the null terminator.
            extra_i += (constraint.len + name.len + (2 + 3)) / 4;

            if (index > 0) try writer.writeByte(',');
            try writer.writeByte(' ');
            if (!mem.eql(u8, name, "_")) try writer.print("[{s}]", .{name});
            const is_reg = constraint[1] == '{';
            try writer.print("{f}(", .{fmtStringLiteral(if (is_reg) "=r" else constraint, null)});
            if (is_reg) {
                try f.writeCValue(writer, .{ .local = locals_index }, .Other);
                locals_index += 1;
            } else if (output == .none) {
                try f.writeCValue(writer, inst_local, .FunctionArgument);
            } else {
                try f.writeCValueDeref(writer, try f.resolveInst(output));
            }
            try writer.writeByte(')');
        }
        try writer.writeByte(':');
        for (inputs, 0..) |input, index| {
            const extra_bytes = mem.sliceAsBytes(f.air.extra.items[extra_i..]);
            const constraint = mem.sliceTo(extra_bytes, 0);
            const name = mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
            // This equation accounts for the fact that even if we have exactly 4 bytes
            // for the string, we still use the next u32 for the null terminator.
            extra_i += (constraint.len + name.len + (2 + 3)) / 4;

            if (index > 0) try writer.writeByte(',');
            try writer.writeByte(' ');
            if (!mem.eql(u8, name, "_")) try writer.print("[{s}]", .{name});

            const is_reg = constraint[0] == '{';
            const input_val = try f.resolveInst(input);
            try writer.print("{f}(", .{fmtStringLiteral(if (is_reg) "r" else constraint, null)});
            try f.writeCValue(writer, if (asmInputNeedsLocal(f, constraint, input_val)) local: {
                const input_local_idx = locals_index;
                locals_index += 1;
                break :local .{ .local = input_local_idx };
            } else input_val, .Other);
            try writer.writeByte(')');
        }
        try writer.writeByte(':');
        for (0..clobbers_len) |clobber_i| {
            const clobber = mem.sliceTo(mem.sliceAsBytes(f.air.extra.items[extra_i..]), 0);
            // This equation accounts for the fact that even if we have exactly 4 bytes
            // for the string, we still use the next u32 for the null terminator.
            extra_i += clobber.len / 4 + 1;

            if (clobber.len == 0) continue;

            if (clobber_i > 0) try writer.writeByte(',');
            try writer.print(" {f}", .{fmtStringLiteral(clobber, null)});
        }
        try writer.writeAll(");\n");

        extra_i = constraints_extra_begin;
        locals_index = locals_begin;
        for (outputs) |output| {
            const extra_bytes = mem.sliceAsBytes(f.air.extra.items[extra_i..]);
            const constraint = mem.sliceTo(extra_bytes, 0);
            const name = mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
            // This equation accounts for the fact that even if we have exactly 4 bytes
            // for the string, we still use the next u32 for the null terminator.
            extra_i += (constraint.len + name.len + (2 + 3)) / 4;

            const is_reg = constraint[1] == '{';
            if (is_reg) {
                try f.writeCValueDeref(writer, if (output == .none)
                    .{ .local_ref = inst_local.new_local }
                else
                    try f.resolveInst(output));
                try writer.writeAll(" = ");
                try f.writeCValue(writer, .{ .local = locals_index }, .Other);
                locals_index += 1;
                try writer.writeAll(";\n");
            }
        }

        break :result if (f.liveness.isUnused(inst)) .none else inst_local;
    };

    var bt = iterateBigTomb(f, inst);
    for (outputs) |output| {
        if (output == .none) continue;
        try bt.feed(output);
    }
    for (inputs) |input| {
        try bt.feed(input);
    }

    return result;
}

fn airIsNull(
    f: *Function,
    inst: Air.Inst.Index,
    operator: std.math.CompareOperator,
    is_ptr: bool,
) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ctype_pool = &f.object.dg.ctype_pool;
    const un_op = f.air.instructions.items(.data)[@intFromEnum(inst)].un_op;

    const writer = f.object.writer();
    const operand = try f.resolveInst(un_op);
    try reap(f, inst, &.{un_op});

    const local = try f.allocLocal(inst, .bool);
    const a = try Assignment.start(f, writer, .bool);
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);

    const operand_ty = f.typeOf(un_op);
    const optional_ty = if (is_ptr) operand_ty.childType(zcu) else operand_ty;
    const opt_ctype = try f.ctypeFromType(optional_ty, .complete);
    const rhs = switch (opt_ctype.info(ctype_pool)) {
        .basic, .pointer => rhs: {
            if (is_ptr)
                try f.writeCValueDeref(writer, operand)
            else
                try f.writeCValue(writer, operand, .Other);
            break :rhs if (opt_ctype.isBool())
                "true"
            else if (opt_ctype.isInteger())
                "0"
            else
                "NULL";
        },
        .aligned, .array, .vector, .fwd_decl, .function => unreachable,
        .aggregate => |aggregate| switch (aggregate.fields.at(0, ctype_pool).name.index) {
            .is_null, .payload => rhs: {
                if (is_ptr)
                    try f.writeCValueDerefMember(writer, operand, .{ .identifier = "is_null" })
                else
                    try f.writeCValueMember(writer, operand, .{ .identifier = "is_null" });
                break :rhs "true";
            },
            .ptr, .len => rhs: {
                if (is_ptr)
                    try f.writeCValueDerefMember(writer, operand, .{ .identifier = "ptr" })
                else
                    try f.writeCValueMember(writer, operand, .{ .identifier = "ptr" });
                break :rhs "NULL";
            },
            else => unreachable,
        },
    };
    try writer.writeAll(compareOperatorC(operator));
    try writer.writeAll(rhs);
    try a.end(f, writer);
    return local;
}

fn airOptionalPayload(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ctype_pool = &f.object.dg.ctype_pool;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const inst_ty = f.typeOfIndex(inst);
    const operand_ty = f.typeOf(ty_op.operand);
    const opt_ty = if (is_ptr) operand_ty.childType(zcu) else operand_ty;
    const opt_ctype = try f.ctypeFromType(opt_ty, .complete);
    if (opt_ctype.isBool()) return if (is_ptr) .{ .undef = inst_ty } else .none;

    const operand = try f.resolveInst(ty_op.operand);
    switch (opt_ctype.info(ctype_pool)) {
        .basic, .pointer => return f.moveCValue(inst, inst_ty, operand),
        .aligned, .array, .vector, .fwd_decl, .function => unreachable,
        .aggregate => |aggregate| switch (aggregate.fields.at(0, ctype_pool).name.index) {
            .is_null, .payload => {
                const writer = f.object.writer();
                const local = try f.allocLocal(inst, inst_ty);
                const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_ty, .complete));
                try f.writeCValue(writer, local, .Other);
                try a.assign(f, writer);
                if (is_ptr) {
                    try writer.writeByte('&');
                    try f.writeCValueDerefMember(writer, operand, .{ .identifier = "payload" });
                } else try f.writeCValueMember(writer, operand, .{ .identifier = "payload" });
                try a.end(f, writer);
                return local;
            },
            .ptr, .len => return f.moveCValue(inst, inst_ty, operand),
            else => unreachable,
        },
    }
}

fn airOptionalPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
    const writer = f.object.writer();
    const operand = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});
    const operand_ty = f.typeOf(ty_op.operand);

    const inst_ty = f.typeOfIndex(inst);
    const opt_ctype = try f.ctypeFromType(operand_ty.childType(zcu), .complete);
    switch (opt_ctype.info(&f.object.dg.ctype_pool)) {
        .basic => {
            const a = try Assignment.start(f, writer, opt_ctype);
            try f.writeCValueDeref(writer, operand);
            try a.assign(f, writer);
            try f.object.dg.renderValue(writer, Value.false, .Other);
            try a.end(f, writer);
            return .none;
        },
        .pointer => {
            if (f.liveness.isUnused(inst)) return .none;
            const local = try f.allocLocal(inst, inst_ty);
            const a = try Assignment.start(f, writer, opt_ctype);
            try f.writeCValue(writer, local, .Other);
            try a.assign(f, writer);
            try f.writeCValue(writer, operand, .Other);
            try a.end(f, writer);
            return local;
        },
        .aligned, .array, .vector, .fwd_decl, .function => unreachable,
        .aggregate => {
            {
                const a = try Assignment.start(f, writer, opt_ctype);
                try f.writeCValueDerefMember(writer, operand, .{ .identifier = "is_null" });
                try a.assign(f, writer);
                try f.object.dg.renderValue(writer, Value.false, .Other);
                try a.end(f, writer);
            }
            if (f.liveness.isUnused(inst)) return .none;
            const local = try f.allocLocal(inst, inst_ty);
            const a = try Assignment.start(f, writer, opt_ctype);
            try f.writeCValue(writer, local, .Other);
            try a.assign(f, writer);
            try writer.writeByte('&');
            try f.writeCValueDerefMember(writer, operand, .{ .identifier = "payload" });
            try a.end(f, writer);
            return local;
        },
    }
}

fn fieldLocation(
    container_ptr_ty: Type,
    field_ptr_ty: Type,
    field_index: u32,
    pt: Zcu.PerThread,
) union(enum) {
    begin: void,
    field: CValue,
    byte_offset: u64,
} {
    const zcu = pt.zcu;
    const ip = &zcu.intern_pool;
    const container_ty: Type = .fromInterned(ip.indexToKey(container_ptr_ty.toIntern()).ptr_type.child);
    switch (ip.indexToKey(container_ty.toIntern())) {
        .struct_type => {
            const loaded_struct = ip.loadStructType(container_ty.toIntern());
            return switch (loaded_struct.layout) {
                .auto, .@"extern" => if (!container_ty.hasRuntimeBitsIgnoreComptime(zcu))
                    .begin
                else if (!field_ptr_ty.childType(zcu).hasRuntimeBitsIgnoreComptime(zcu))
                    .{ .byte_offset = loaded_struct.offsets.get(ip)[field_index] }
                else
                    .{ .field = if (loaded_struct.fieldName(ip, field_index).unwrap()) |field_name|
                        .{ .identifier = field_name.toSlice(ip) }
                    else
                        .{ .field = field_index } },
                .@"packed" => if (field_ptr_ty.ptrInfo(zcu).packed_offset.host_size == 0)
                    .{ .byte_offset = @divExact(pt.structPackedFieldBitOffset(loaded_struct, field_index) +
                        container_ptr_ty.ptrInfo(zcu).packed_offset.bit_offset, 8) }
                else
                    .begin,
            };
        },
        .tuple_type => return if (!container_ty.hasRuntimeBitsIgnoreComptime(zcu))
            .begin
        else if (!field_ptr_ty.childType(zcu).hasRuntimeBitsIgnoreComptime(zcu))
            .{ .byte_offset = container_ty.structFieldOffset(field_index, zcu) }
        else
            .{ .field = .{ .field = field_index } },
        .union_type => {
            const loaded_union = ip.loadUnionType(container_ty.toIntern());
            switch (loaded_union.flagsUnordered(ip).layout) {
                .auto, .@"extern" => {
                    const field_ty: Type = .fromInterned(loaded_union.field_types.get(ip)[field_index]);
                    if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu))
                        return if (loaded_union.hasTag(ip) and !container_ty.unionHasAllZeroBitFieldTypes(zcu))
                            .{ .field = .{ .identifier = "payload" } }
                        else
                            .begin;
                    const field_name = loaded_union.loadTagType(ip).names.get(ip)[field_index];
                    return .{ .field = if (loaded_union.hasTag(ip))
                        .{ .payload_identifier = field_name.toSlice(ip) }
                    else
                        .{ .identifier = field_name.toSlice(ip) } };
                },
                .@"packed" => return .begin,
            }
        },
        .ptr_type => |ptr_info| switch (ptr_info.flags.size) {
            .one, .many, .c => unreachable,
            .slice => switch (field_index) {
                0 => return .{ .field = .{ .identifier = "ptr" } },
                1 => return .{ .field = .{ .identifier = "len" } },
                else => unreachable,
            },
        },
        else => unreachable,
    }
}

fn airStructFieldPtr(f: *Function, inst: Air.Inst.Index) !CValue {
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const extra = f.air.extraData(Air.StructField, ty_pl.payload).data;

    const container_ptr_val = try f.resolveInst(extra.struct_operand);
    try reap(f, inst, &.{extra.struct_operand});
    const container_ptr_ty = f.typeOf(extra.struct_operand);
    return fieldPtr(f, inst, container_ptr_ty, container_ptr_val, extra.field_index);
}

fn airStructFieldPtrIndex(f: *Function, inst: Air.Inst.Index, index: u8) !CValue {
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const container_ptr_val = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});
    const container_ptr_ty = f.typeOf(ty_op.operand);
    return fieldPtr(f, inst, container_ptr_ty, container_ptr_val, index);
}

fn airFieldParentPtr(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const extra = f.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;

    const container_ptr_ty = f.typeOfIndex(inst);
    const container_ty = container_ptr_ty.childType(zcu);

    const field_ptr_ty = f.typeOf(extra.field_ptr);
    const field_ptr_val = try f.resolveInst(extra.field_ptr);
    try reap(f, inst, &.{extra.field_ptr});

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, container_ptr_ty);
    try f.writeCValue(writer, local, .Other);
    try writer.writeAll(" = (");
    try f.renderType(writer, container_ptr_ty);
    try writer.writeByte(')');

    switch (fieldLocation(container_ptr_ty, field_ptr_ty, extra.field_index, pt)) {
        .begin => try f.writeCValue(writer, field_ptr_val, .Other),
        .field => |field| {
            const u8_ptr_ty = try pt.adjustPtrTypeChild(field_ptr_ty, .u8);

            try writer.writeAll("((");
            try f.renderType(writer, u8_ptr_ty);
            try writer.writeByte(')');
            try f.writeCValue(writer, field_ptr_val, .Other);
            try writer.writeAll(" - offsetof(");
            try f.renderType(writer, container_ty);
            try writer.writeAll(", ");
            try f.writeCValue(writer, field, .Other);
            try writer.writeAll("))");
        },
        .byte_offset => |byte_offset| {
            const u8_ptr_ty = try pt.adjustPtrTypeChild(field_ptr_ty, .u8);

            try writer.writeAll("((");
            try f.renderType(writer, u8_ptr_ty);
            try writer.writeByte(')');
            try f.writeCValue(writer, field_ptr_val, .Other);
            try writer.print(" - {f})", .{
                try f.fmtIntLiteralDec(try pt.intValue(.usize, byte_offset)),
            });
        },
    }

    try writer.writeAll(";\n");
    return local;
}

fn fieldPtr(
    f: *Function,
    inst: Air.Inst.Index,
    container_ptr_ty: Type,
    container_ptr_val: CValue,
    field_index: u32,
) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const container_ty = container_ptr_ty.childType(zcu);
    const field_ptr_ty = f.typeOfIndex(inst);

    // Ensure complete type definition is visible before accessing fields.
    _ = try f.ctypeFromType(container_ty, .complete);

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, field_ptr_ty);
    try f.writeCValue(writer, local, .Other);
    try writer.writeAll(" = (");
    try f.renderType(writer, field_ptr_ty);
    try writer.writeByte(')');

    switch (fieldLocation(container_ptr_ty, field_ptr_ty, field_index, pt)) {
        .begin => try f.writeCValue(writer, container_ptr_val, .Other),
        .field => |field| {
            try writer.writeByte('&');
            try f.writeCValueDerefMember(writer, container_ptr_val, field);
        },
        .byte_offset => |byte_offset| {
            const u8_ptr_ty = try pt.adjustPtrTypeChild(field_ptr_ty, .u8);

            try writer.writeAll("((");
            try f.renderType(writer, u8_ptr_ty);
            try writer.writeByte(')');
            try f.writeCValue(writer, container_ptr_val, .Other);
            try writer.print(" + {f})", .{
                try f.fmtIntLiteralDec(try pt.intValue(.usize, byte_offset)),
            });
        },
    }

    try writer.writeAll(";\n");
    return local;
}

fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ip = &zcu.intern_pool;
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const extra = f.air.extraData(Air.StructField, ty_pl.payload).data;

    const inst_ty = f.typeOfIndex(inst);
    if (!inst_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
        try reap(f, inst, &.{extra.struct_operand});
        return .none;
    }

    const struct_byval = try f.resolveInst(extra.struct_operand);
    try reap(f, inst, &.{extra.struct_operand});
    const struct_ty = f.typeOf(extra.struct_operand);
    const writer = f.object.writer();

    // Ensure complete type definition is visible before accessing fields.
    _ = try f.ctypeFromType(struct_ty, .complete);

    const field_name: CValue = switch (ip.indexToKey(struct_ty.toIntern())) {
        .struct_type => field_name: {
            const loaded_struct = ip.loadStructType(struct_ty.toIntern());
            switch (loaded_struct.layout) {
                .auto, .@"extern" => break :field_name if (loaded_struct.fieldName(ip, extra.field_index).unwrap()) |field_name|
                    .{ .identifier = field_name.toSlice(ip) }
                else
                    .{ .field = extra.field_index },
                .@"packed" => {
                    const int_info = struct_ty.intInfo(zcu);

                    const bit_offset_ty = try pt.intType(.unsigned, Type.smallestUnsignedBits(int_info.bits - 1));

                    const bit_offset = pt.structPackedFieldBitOffset(loaded_struct, extra.field_index);

                    const field_int_signedness = if (inst_ty.isAbiInt(zcu))
                        inst_ty.intInfo(zcu).signedness
                    else
                        .unsigned;
                    const field_int_ty = try pt.intType(field_int_signedness, @as(u16, @intCast(inst_ty.bitSize(zcu))));

                    const temp_local = try f.allocLocal(inst, field_int_ty);
                    try f.writeCValue(writer, temp_local, .Other);
                    try writer.writeAll(" = zig_wrap_");
                    try f.object.dg.renderTypeForBuiltinFnName(writer, field_int_ty);
                    try writer.writeAll("((");
                    try f.renderType(writer, field_int_ty);
                    try writer.writeByte(')');
                    const cant_cast = int_info.bits > 64;
                    if (cant_cast) {
                        if (field_int_ty.bitSize(zcu) > 64) return f.fail("TODO: C backend: implement casting between types > 64 bits", .{});
                        try writer.writeAll("zig_lo_");
                        try f.object.dg.renderTypeForBuiltinFnName(writer, struct_ty);
                        try writer.writeByte('(');
                    }
                    if (bit_offset > 0) {
                        try writer.writeAll("zig_shr_");
                        try f.object.dg.renderTypeForBuiltinFnName(writer, struct_ty);
                        try writer.writeByte('(');
                    }
                    try f.writeCValue(writer, struct_byval, .Other);
                    if (bit_offset > 0) try writer.print(", {f})", .{
                        try f.fmtIntLiteralDec(try pt.intValue(bit_offset_ty, bit_offset)),
                    });
                    if (cant_cast) try writer.writeByte(')');
                    try f.object.dg.renderBuiltinInfo(writer, field_int_ty, .bits);
                    try writer.writeAll(");\n");
                    if (inst_ty.eql(field_int_ty, zcu)) return temp_local;

                    const local = try f.allocLocal(inst, inst_ty);
                    if (local.new_local != temp_local.new_local) {
                        try writer.writeAll("memcpy(");
                        try f.writeCValue(writer, .{ .local_ref = local.new_local }, .FunctionArgument);
                        try writer.writeAll(", ");
                        try f.writeCValue(writer, .{ .local_ref = temp_local.new_local }, .FunctionArgument);
                        try writer.writeAll(", sizeof(");
                        try f.renderType(writer, inst_ty);
                        try writer.writeAll("));\n");
                    }
                    try freeLocal(f, inst, temp_local.new_local, null);
                    return local;
                },
            }
        },
        .tuple_type => .{ .field = extra.field_index },
        .union_type => field_name: {
            const loaded_union = ip.loadUnionType(struct_ty.toIntern());
            switch (loaded_union.flagsUnordered(ip).layout) {
                .auto, .@"extern" => {
                    const name = loaded_union.loadTagType(ip).names.get(ip)[extra.field_index];
                    break :field_name if (loaded_union.hasTag(ip))
                        .{ .payload_identifier = name.toSlice(ip) }
                    else
                        .{ .identifier = name.toSlice(ip) };
                },
                .@"packed" => {
                    const operand_lval = if (struct_byval == .constant) blk: {
                        const operand_local = try f.allocLocal(inst, struct_ty);
                        try f.writeCValue(writer, operand_local, .Other);
                        try writer.writeAll(" = ");
                        try f.writeCValue(writer, struct_byval, .Other);
                        try writer.writeAll(";\n");
                        break :blk operand_local;
                    } else struct_byval;
                    const local = try f.allocLocal(inst, inst_ty);
                    if (switch (local) {
                        .new_local, .local => |local_index| switch (operand_lval) {
                            .new_local, .local => |operand_local_index| local_index != operand_local_index,
                            else => true,
                        },
                        else => true,
                    }) {
                        try writer.writeAll("memcpy(&");
                        try f.writeCValue(writer, local, .Other);
                        try writer.writeAll(", &");
                        try f.writeCValue(writer, operand_lval, .Other);
                        try writer.writeAll(", sizeof(");
                        try f.renderType(writer, inst_ty);
                        try writer.writeAll("));\n");
                    }
                    try f.freeCValue(inst, operand_lval);
                    return local;
                },
            }
        },
        else => unreachable,
    };

    const local = try f.allocLocal(inst, inst_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);
    try f.writeCValueMember(writer, struct_byval, field_name);
    try a.end(f, writer);
    return local;
}

/// *(E!T) -> E
/// Note that the result is never a pointer.
fn airUnwrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const inst_ty = f.typeOfIndex(inst);
    const operand = try f.resolveInst(ty_op.operand);
    const operand_ty = f.typeOf(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});

    const operand_is_ptr = operand_ty.zigTypeTag(zcu) == .pointer;
    const error_union_ty = if (operand_is_ptr) operand_ty.childType(zcu) else operand_ty;
    const error_ty = error_union_ty.errorUnionSet(zcu);
    const payload_ty = error_union_ty.errorUnionPayload(zcu);
    const local = try f.allocLocal(inst, inst_ty);

    if (!payload_ty.hasRuntimeBits(zcu) and operand == .local and operand.local == local.new_local) {
        // The store will be 'x = x'; elide it.
        return local;
    }

    const writer = f.object.writer();
    try f.writeCValue(writer, local, .Other);
    try writer.writeAll(" = ");

    if (!payload_ty.hasRuntimeBits(zcu))
        try f.writeCValue(writer, operand, .Other)
    else if (error_ty.errorSetIsEmpty(zcu))
        try writer.print("{f}", .{
            try f.fmtIntLiteralDec(try pt.intValue(try pt.errorIntType(), 0)),
        })
    else if (operand_is_ptr)
        try f.writeCValueDerefMember(writer, operand, .{ .identifier = "error" })
    else
        try f.writeCValueMember(writer, operand, .{ .identifier = "error" });
    try writer.writeAll(";\n");
    return local;
}

fn airUnwrapErrUnionPay(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const inst_ty = f.typeOfIndex(inst);
    const operand = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});
    const operand_ty = f.typeOf(ty_op.operand);
    const error_union_ty = if (is_ptr) operand_ty.childType(zcu) else operand_ty;

    const writer = f.object.writer();
    if (!error_union_ty.errorUnionPayload(zcu).hasRuntimeBits(zcu)) {
        if (!is_ptr) return .none;

        const local = try f.allocLocal(inst, inst_ty);
        try f.writeCValue(writer, local, .Other);
        try writer.writeAll(" = (");
        try f.renderType(writer, inst_ty);
        try writer.writeByte(')');
        try f.writeCValue(writer, operand, .Other);
        try writer.writeAll(";\n");
        return local;
    }

    const local = try f.allocLocal(inst, inst_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);
    if (is_ptr) {
        try writer.writeByte('&');
        try f.writeCValueDerefMember(writer, operand, .{ .identifier = "payload" });
    } else try f.writeCValueMember(writer, operand, .{ .identifier = "payload" });
    try a.end(f, writer);
    return local;
}

fn airWrapOptional(f: *Function, inst: Air.Inst.Index) !CValue {
    const ctype_pool = &f.object.dg.ctype_pool;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const inst_ty = f.typeOfIndex(inst);
    const inst_ctype = try f.ctypeFromType(inst_ty, .complete);
    if (inst_ctype.isBool()) return .{ .constant = Value.true };

    const operand = try f.resolveInst(ty_op.operand);
    switch (inst_ctype.info(ctype_pool)) {
        .basic, .pointer => return f.moveCValue(inst, inst_ty, operand),
        .aligned, .array, .vector, .fwd_decl, .function => unreachable,
        .aggregate => |aggregate| switch (aggregate.fields.at(0, ctype_pool).name.index) {
            .is_null, .payload => {
                const operand_ctype = try f.ctypeFromType(f.typeOf(ty_op.operand), .complete);
                const writer = f.object.writer();
                const local = try f.allocLocal(inst, inst_ty);
                {
                    const a = try Assignment.start(f, writer, .bool);
                    try f.writeCValueMember(writer, local, .{ .identifier = "is_null" });
                    try a.assign(f, writer);
                    try writer.writeAll("false");
                    try a.end(f, writer);
                }
                {
                    const a = try Assignment.start(f, writer, operand_ctype);
                    try f.writeCValueMember(writer, local, .{ .identifier = "payload" });
                    try a.assign(f, writer);
                    try f.writeCValue(writer, operand, .Other);
                    try a.end(f, writer);
                }
                return local;
            },
            .ptr, .len => return f.moveCValue(inst, inst_ty, operand),
            else => unreachable,
        },
    }
}

fn airWrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const inst_ty = f.typeOfIndex(inst);
    const payload_ty = inst_ty.errorUnionPayload(zcu);
    const repr_is_err = !payload_ty.hasRuntimeBitsIgnoreComptime(zcu);
    const err_ty = inst_ty.errorUnionSet(zcu);
    const err = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);

    if (repr_is_err and err == .local and err.local == local.new_local) {
        // The store will be 'x = x'; elide it.
        return local;
    }

    if (!repr_is_err) {
        const a = try Assignment.start(f, writer, try f.ctypeFromType(payload_ty, .complete));
        try f.writeCValueMember(writer, local, .{ .identifier = "payload" });
        try a.assign(f, writer);
        try f.object.dg.renderUndefValue(writer, payload_ty, .Other);
        try a.end(f, writer);
    }
    {
        const a = try Assignment.start(f, writer, try f.ctypeFromType(err_ty, .complete));
        if (repr_is_err)
            try f.writeCValue(writer, local, .Other)
        else
            try f.writeCValueMember(writer, local, .{ .identifier = "error" });
        try a.assign(f, writer);
        try f.writeCValue(writer, err, .Other);
        try a.end(f, writer);
    }
    return local;
}

fn airErrUnionPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const writer = f.object.writer();
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
    const inst_ty = f.typeOfIndex(inst);
    const operand = try f.resolveInst(ty_op.operand);
    const operand_ty = f.typeOf(ty_op.operand);
    const error_union_ty = operand_ty.childType(zcu);

    const payload_ty = error_union_ty.errorUnionPayload(zcu);
    const err_int_ty = try pt.errorIntType();
    const no_err = try pt.intValue(err_int_ty, 0);
    try reap(f, inst, &.{ty_op.operand});

    // First, set the non-error value.
    if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
        const a = try Assignment.start(f, writer, try f.ctypeFromType(operand_ty, .complete));
        try f.writeCValueDeref(writer, operand);
        try a.assign(f, writer);
        try writer.print("{f}", .{try f.fmtIntLiteralDec(no_err)});
        try a.end(f, writer);
        return .none;
    }
    {
        const a = try Assignment.start(f, writer, try f.ctypeFromType(err_int_ty, .complete));
        try f.writeCValueDerefMember(writer, operand, .{ .identifier = "error" });
        try a.assign(f, writer);
        try writer.print("{f}", .{try f.fmtIntLiteralDec(no_err)});
        try a.end(f, writer);
    }

    // Then return the payload pointer (only if it is used)
    if (f.liveness.isUnused(inst)) return .none;

    const local = try f.allocLocal(inst, inst_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);
    try writer.writeByte('&');
    try f.writeCValueDerefMember(writer, operand, .{ .identifier = "payload" });
    try a.end(f, writer);
    return local;
}

fn airErrReturnTrace(f: *Function, inst: Air.Inst.Index) !CValue {
    _ = inst;
    return f.fail("TODO: C backend: implement airErrReturnTrace", .{});
}

fn airSetErrReturnTrace(f: *Function, inst: Air.Inst.Index) !CValue {
    _ = inst;
    return f.fail("TODO: C backend: implement airSetErrReturnTrace", .{});
}

fn airSaveErrReturnTraceIndex(f: *Function, inst: Air.Inst.Index) !CValue {
    _ = inst;
    return f.fail("TODO: C backend: implement airSaveErrReturnTraceIndex", .{});
}

fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const inst_ty = f.typeOfIndex(inst);
    const payload_ty = inst_ty.errorUnionPayload(zcu);
    const payload = try f.resolveInst(ty_op.operand);
    const repr_is_err = !payload_ty.hasRuntimeBitsIgnoreComptime(zcu);
    const err_ty = inst_ty.errorUnionSet(zcu);
    try reap(f, inst, &.{ty_op.operand});

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    if (!repr_is_err) {
        const a = try Assignment.start(f, writer, try f.ctypeFromType(payload_ty, .complete));
        try f.writeCValueMember(writer, local, .{ .identifier = "payload" });
        try a.assign(f, writer);
        try f.writeCValue(writer, payload, .Other);
        try a.end(f, writer);
    }
    {
        const a = try Assignment.start(f, writer, try f.ctypeFromType(err_ty, .complete));
        if (repr_is_err)
            try f.writeCValue(writer, local, .Other)
        else
            try f.writeCValueMember(writer, local, .{ .identifier = "error" });
        try a.assign(f, writer);
        try f.object.dg.renderValue(writer, try pt.intValue(try pt.errorIntType(), 0), .Other);
        try a.end(f, writer);
    }
    return local;
}

fn airIsErr(f: *Function, inst: Air.Inst.Index, is_ptr: bool, operator: []const u8) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const un_op = f.air.instructions.items(.data)[@intFromEnum(inst)].un_op;

    const writer = f.object.writer();
    const operand = try f.resolveInst(un_op);
    try reap(f, inst, &.{un_op});
    const operand_ty = f.typeOf(un_op);
    const local = try f.allocLocal(inst, .bool);
    const err_union_ty = if (is_ptr) operand_ty.childType(zcu) else operand_ty;
    const payload_ty = err_union_ty.errorUnionPayload(zcu);
    const error_ty = err_union_ty.errorUnionSet(zcu);

    const a = try Assignment.start(f, writer, .bool);
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);
    const err_int_ty = try pt.errorIntType();
    if (!error_ty.errorSetIsEmpty(zcu))
        if (payload_ty.hasRuntimeBits(zcu))
            if (is_ptr)
                try f.writeCValueDerefMember(writer, operand, .{ .identifier = "error" })
            else
                try f.writeCValueMember(writer, operand, .{ .identifier = "error" })
        else
            try f.writeCValue(writer, operand, .Other)
    else
        try f.object.dg.renderValue(writer, try pt.intValue(err_int_ty, 0), .Other);
    try writer.writeByte(' ');
    try writer.writeAll(operator);
    try writer.writeByte(' ');
    try f.object.dg.renderValue(writer, try pt.intValue(err_int_ty, 0), .Other);
    try a.end(f, writer);
    return local;
}

fn airArrayToSlice(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ctype_pool = &f.object.dg.ctype_pool;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const operand = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});
    const inst_ty = f.typeOfIndex(inst);
    const ptr_ty = inst_ty.slicePtrFieldType(zcu);
    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const operand_ty = f.typeOf(ty_op.operand);
    const array_ty = operand_ty.childType(zcu);

    {
        const a = try Assignment.start(f, writer, try f.ctypeFromType(ptr_ty, .complete));
        try f.writeCValueMember(writer, local, .{ .identifier = "ptr" });
        try a.assign(f, writer);
        if (operand == .undef) {
            try f.writeCValue(writer, .{ .undef = inst_ty.slicePtrFieldType(zcu) }, .Other);
        } else {
            const ptr_ctype = try f.ctypeFromType(ptr_ty, .complete);
            const ptr_child_ctype = ptr_ctype.info(ctype_pool).pointer.elem_ctype;
            const elem_ty = array_ty.childType(zcu);
            const elem_ctype = try f.ctypeFromType(elem_ty, .complete);
            if (!ptr_child_ctype.eql(elem_ctype)) {
                try writer.writeByte('(');
                try f.renderCType(writer, ptr_ctype);
                try writer.writeByte(')');
            }
            const operand_ctype = try f.ctypeFromType(operand_ty, .complete);
            const operand_child_ctype = operand_ctype.info(ctype_pool).pointer.elem_ctype;
            if (operand_child_ctype.info(ctype_pool) == .array) {
                try writer.writeByte('&');
                try f.writeCValueDeref(writer, operand);
                try writer.print("[{f}]", .{try f.fmtIntLiteralDec(.zero_usize)});
            } else try f.writeCValue(writer, operand, .Other);
        }
        try a.end(f, writer);
    }
    {
        const a = try Assignment.start(f, writer, .usize);
        try f.writeCValueMember(writer, local, .{ .identifier = "len" });
        try a.assign(f, writer);
        try writer.print("{f}", .{
            try f.fmtIntLiteralDec(try pt.intValue(.usize, array_ty.arrayLen(zcu))),
        });
        try a.end(f, writer);
    }

    return local;
}

fn airFloatCast(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const inst_ty = f.typeOfIndex(inst);
    const inst_scalar_ty = inst_ty.scalarType(zcu);
    const operand = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});
    const operand_ty = f.typeOf(ty_op.operand);
    const scalar_ty = operand_ty.scalarType(zcu);
    const target = &f.object.dg.mod.resolved_target.result;
    const operation = if (inst_scalar_ty.isRuntimeFloat() and scalar_ty.isRuntimeFloat())
        if (inst_scalar_ty.floatBits(target) < scalar_ty.floatBits(target)) "trunc" else "extend"
    else if (inst_scalar_ty.isInt(zcu) and scalar_ty.isRuntimeFloat())
        if (inst_scalar_ty.isSignedInt(zcu)) "fix" else "fixuns"
    else if (inst_scalar_ty.isRuntimeFloat() and scalar_ty.isInt(zcu))
        if (scalar_ty.isSignedInt(zcu)) "float" else "floatun"
    else
        unreachable;

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const v = try Vectorize.start(f, inst, writer, operand_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(scalar_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try v.elem(f, writer);
    try a.assign(f, writer);
    if (inst_scalar_ty.isInt(zcu) and scalar_ty.isRuntimeFloat()) {
        try writer.writeAll("zig_wrap_");
        try f.object.dg.renderTypeForBuiltinFnName(writer, inst_scalar_ty);
        try writer.writeByte('(');
    }
    try writer.writeAll("zig_");
    try writer.writeAll(operation);
    try writer.writeAll(compilerRtAbbrev(scalar_ty, zcu, target));
    try writer.writeAll(compilerRtAbbrev(inst_scalar_ty, zcu, target));
    try writer.writeByte('(');
    try f.writeCValue(writer, operand, .FunctionArgument);
    try v.elem(f, writer);
    try writer.writeByte(')');
    if (inst_scalar_ty.isInt(zcu) and scalar_ty.isRuntimeFloat()) {
        try f.object.dg.renderBuiltinInfo(writer, inst_scalar_ty, .bits);
        try writer.writeByte(')');
    }
    try a.end(f, writer);
    try v.end(f, inst, writer);

    return local;
}

fn airUnBuiltinCall(
    f: *Function,
    inst: Air.Inst.Index,
    operand_ref: Air.Inst.Ref,
    operation: []const u8,
    info: BuiltinInfo,
) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;

    const operand = try f.resolveInst(operand_ref);
    try reap(f, inst, &.{operand_ref});
    const inst_ty = f.typeOfIndex(inst);
    const inst_scalar_ty = inst_ty.scalarType(zcu);
    const operand_ty = f.typeOf(operand_ref);
    const scalar_ty = operand_ty.scalarType(zcu);

    const inst_scalar_ctype = try f.ctypeFromType(inst_scalar_ty, .complete);
    const ref_ret = inst_scalar_ctype.info(&f.object.dg.ctype_pool) == .array;

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const v = try Vectorize.start(f, inst, writer, operand_ty);
    if (!ref_ret) {
        try f.writeCValue(writer, local, .Other);
        try v.elem(f, writer);
        try writer.writeAll(" = ");
    }
    try writer.print("zig_{s}_", .{operation});
    try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty);
    try writer.writeByte('(');
    if (ref_ret) {
        try f.writeCValue(writer, local, .FunctionArgument);
        try v.elem(f, writer);
        try writer.writeAll(", ");
    }
    try f.writeCValue(writer, operand, .FunctionArgument);
    try v.elem(f, writer);
    try f.object.dg.renderBuiltinInfo(writer, scalar_ty, info);
    try writer.writeAll(");\n");
    try v.end(f, inst, writer);

    return local;
}

fn airBinBuiltinCall(
    f: *Function,
    inst: Air.Inst.Index,
    operation: []const u8,
    info: BuiltinInfo,
) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;

    const operand_ty = f.typeOf(bin_op.lhs);
    const operand_ctype = try f.ctypeFromType(operand_ty, .complete);
    const is_big = operand_ctype.info(&f.object.dg.ctype_pool) == .array;

    const lhs = try f.resolveInst(bin_op.lhs);
    const rhs = try f.resolveInst(bin_op.rhs);
    if (!is_big) try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const inst_ty = f.typeOfIndex(inst);
    const inst_scalar_ty = inst_ty.scalarType(zcu);
    const scalar_ty = operand_ty.scalarType(zcu);

    const inst_scalar_ctype = try f.ctypeFromType(inst_scalar_ty, .complete);
    const ref_ret = inst_scalar_ctype.info(&f.object.dg.ctype_pool) == .array;

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    if (is_big) try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
    const v = try Vectorize.start(f, inst, writer, operand_ty);
    if (!ref_ret) {
        try f.writeCValue(writer, local, .Other);
        try v.elem(f, writer);
        try writer.writeAll(" = ");
    }
    try writer.print("zig_{s}_", .{operation});
    try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty);
    try writer.writeByte('(');
    if (ref_ret) {
        try f.writeCValue(writer, local, .FunctionArgument);
        try v.elem(f, writer);
        try writer.writeAll(", ");
    }
    try f.writeCValue(writer, lhs, .FunctionArgument);
    try v.elem(f, writer);
    try writer.writeAll(", ");
    try f.writeCValue(writer, rhs, .FunctionArgument);
    if (f.typeOf(bin_op.rhs).isVector(zcu)) try v.elem(f, writer);
    try f.object.dg.renderBuiltinInfo(writer, scalar_ty, info);
    try writer.writeAll(");\n");
    try v.end(f, inst, writer);

    return local;
}

fn airCmpBuiltinCall(
    f: *Function,
    inst: Air.Inst.Index,
    data: anytype,
    operator: std.math.CompareOperator,
    operation: enum { cmp, operator },
    info: BuiltinInfo,
) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const lhs = try f.resolveInst(data.lhs);
    const rhs = try f.resolveInst(data.rhs);
    try reap(f, inst, &.{ data.lhs, data.rhs });

    const inst_ty = f.typeOfIndex(inst);
    const inst_scalar_ty = inst_ty.scalarType(zcu);
    const operand_ty = f.typeOf(data.lhs);
    const scalar_ty = operand_ty.scalarType(zcu);

    const inst_scalar_ctype = try f.ctypeFromType(inst_scalar_ty, .complete);
    const ref_ret = inst_scalar_ctype.info(&f.object.dg.ctype_pool) == .array;

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const v = try Vectorize.start(f, inst, writer, operand_ty);
    if (!ref_ret) {
        try f.writeCValue(writer, local, .Other);
        try v.elem(f, writer);
        try writer.writeAll(" = ");
    }
    try writer.print("zig_{s}_", .{switch (operation) {
        else => @tagName(operation),
        .operator => compareOperatorAbbrev(operator),
    }});
    try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty);
    try writer.writeByte('(');
    if (ref_ret) {
        try f.writeCValue(writer, local, .FunctionArgument);
        try v.elem(f, writer);
        try writer.writeAll(", ");
    }
    try f.writeCValue(writer, lhs, .FunctionArgument);
    try v.elem(f, writer);
    try writer.writeAll(", ");
    try f.writeCValue(writer, rhs, .FunctionArgument);
    try v.elem(f, writer);
    try f.object.dg.renderBuiltinInfo(writer, scalar_ty, info);
    try writer.writeByte(')');
    if (!ref_ret) try writer.print("{s}{f}", .{
        compareOperatorC(operator),
        try f.fmtIntLiteralDec(try pt.intValue(.i32, 0)),
    });
    try writer.writeAll(";\n");
    try v.end(f, inst, writer);

    return local;
}

fn airCmpxchg(f: *Function, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const extra = f.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
    const inst_ty = f.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 ptr_ty = f.typeOf(extra.ptr);
    const ty = ptr_ty.childType(zcu);
    const ctype = try f.ctypeFromType(ty, .complete);

    const writer = f.object.writer();
    const new_value_mat = try Materialize.start(f, inst, ty, new_value);
    try reap(f, inst, &.{ extra.ptr, extra.expected_value, extra.new_value });

    const repr_ty = if (ty.isRuntimeFloat())
        pt.intType(.unsigned, @as(u16, @intCast(ty.abiSize(zcu) * 8))) catch unreachable
    else
        ty;

    const local = try f.allocLocal(inst, inst_ty);
    if (inst_ty.isPtrLikeOptional(zcu)) {
        {
            const a = try Assignment.start(f, writer, ctype);
            try f.writeCValue(writer, local, .Other);
            try a.assign(f, writer);
            try f.writeCValue(writer, expected_value, .Other);
            try a.end(f, writer);
        }

        try writer.writeAll("if (");
        try writer.print("zig_cmpxchg_{s}((zig_atomic(", .{flavor});
        try f.renderType(writer, ty);
        try writer.writeByte(')');
        if (ptr_ty.isVolatilePtr(zcu)) try writer.writeAll(" volatile");
        try writer.writeAll(" *)");
        try f.writeCValue(writer, ptr, .Other);
        try writer.writeAll(", ");
        try f.writeCValue(writer, local, .FunctionArgument);
        try writer.writeAll(", ");
        try new_value_mat.mat(f, writer);
        try writer.writeAll(", ");
        try writeMemoryOrder(writer, extra.successOrder());
        try writer.writeAll(", ");
        try writeMemoryOrder(writer, extra.failureOrder());
        try writer.writeAll(", ");
        try f.object.dg.renderTypeForBuiltinFnName(writer, ty);
        try writer.writeAll(", ");
        try f.renderType(writer, repr_ty);
        try writer.writeByte(')');
        try writer.writeAll(") {\n");
        f.object.indent_writer.pushIndent();
        {
            const a = try Assignment.start(f, writer, ctype);
            try f.writeCValue(writer, local, .Other);
            try a.assign(f, writer);
            try writer.writeAll("NULL");
            try a.end(f, writer);
        }
        f.object.indent_writer.popIndent();
        try writer.writeAll("}\n");
    } else {
        {
            const a = try Assignment.start(f, writer, ctype);
            try f.writeCValueMember(writer, local, .{ .identifier = "payload" });
            try a.assign(f, writer);
            try f.writeCValue(writer, expected_value, .Other);
            try a.end(f, writer);
        }
        {
            const a = try Assignment.start(f, writer, .bool);
            try f.writeCValueMember(writer, local, .{ .identifier = "is_null" });
            try a.assign(f, writer);
            try writer.print("zig_cmpxchg_{s}((zig_atomic(", .{flavor});
            try f.renderType(writer, ty);
            try writer.writeByte(')');
            if (ptr_ty.isVolatilePtr(zcu)) try writer.writeAll(" volatile");
            try writer.writeAll(" *)");
            try f.writeCValue(writer, ptr, .Other);
            try writer.writeAll(", ");
            try f.writeCValueMember(writer, local, .{ .identifier = "payload" });
            try writer.writeAll(", ");
            try new_value_mat.mat(f, writer);
            try writer.writeAll(", ");
            try writeMemoryOrder(writer, extra.successOrder());
            try writer.writeAll(", ");
            try writeMemoryOrder(writer, extra.failureOrder());
            try writer.writeAll(", ");
            try f.object.dg.renderTypeForBuiltinFnName(writer, ty);
            try writer.writeAll(", ");
            try f.renderType(writer, repr_ty);
            try writer.writeByte(')');
            try a.end(f, writer);
        }
    }
    try new_value_mat.end(f, inst);

    if (f.liveness.isUnused(inst)) {
        try freeLocal(f, inst, local.new_local, null);
        return .none;
    }

    return local;
}

fn airAtomicRmw(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
    const extra = f.air.extraData(Air.AtomicRmw, pl_op.payload).data;
    const inst_ty = f.typeOfIndex(inst);
    const ptr_ty = f.typeOf(pl_op.operand);
    const ty = ptr_ty.childType(zcu);
    const ptr = try f.resolveInst(pl_op.operand);
    const operand = try f.resolveInst(extra.operand);

    const writer = f.object.writer();
    const operand_mat = try Materialize.start(f, inst, ty, operand);
    try reap(f, inst, &.{ pl_op.operand, extra.operand });

    const repr_bits: u16 = @intCast(ty.abiSize(zcu) * 8);
    const is_float = ty.isRuntimeFloat();
    const is_128 = repr_bits == 128;
    const repr_ty = if (is_float) pt.intType(.unsigned, repr_bits) catch unreachable else ty;

    const local = try f.allocLocal(inst, inst_ty);
    try writer.print("zig_atomicrmw_{s}", .{toAtomicRmwSuffix(extra.op())});
    if (is_float) try writer.writeAll("_float") else if (is_128) try writer.writeAll("_int128");
    try writer.writeByte('(');
    try f.writeCValue(writer, local, .Other);
    try writer.writeAll(", (");
    const use_atomic = switch (extra.op()) {
        else => true,
        // These are missing from stdatomic.h, so no atomic types unless a fallback is used.
        .Nand, .Min, .Max => is_float or is_128,
    };
    if (use_atomic) try writer.writeAll("zig_atomic(");
    try f.renderType(writer, ty);
    if (use_atomic) try writer.writeByte(')');
    if (ptr_ty.isVolatilePtr(zcu)) try writer.writeAll(" volatile");
    try writer.writeAll(" *)");
    try f.writeCValue(writer, ptr, .Other);
    try writer.writeAll(", ");
    try operand_mat.mat(f, writer);
    try writer.writeAll(", ");
    try writeMemoryOrder(writer, extra.ordering());
    try writer.writeAll(", ");
    try f.object.dg.renderTypeForBuiltinFnName(writer, ty);
    try writer.writeAll(", ");
    try f.renderType(writer, repr_ty);
    try writer.writeAll(");\n");
    try operand_mat.end(f, inst);

    if (f.liveness.isUnused(inst)) {
        try freeLocal(f, inst, local.new_local, null);
        return .none;
    }

    return local;
}

fn airAtomicLoad(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const atomic_load = f.air.instructions.items(.data)[@intFromEnum(inst)].atomic_load;
    const ptr = try f.resolveInst(atomic_load.ptr);
    try reap(f, inst, &.{atomic_load.ptr});
    const ptr_ty = f.typeOf(atomic_load.ptr);
    const ty = ptr_ty.childType(zcu);

    const repr_ty = if (ty.isRuntimeFloat())
        pt.intType(.unsigned, @as(u16, @intCast(ty.abiSize(zcu) * 8))) catch unreachable
    else
        ty;

    const inst_ty = f.typeOfIndex(inst);
    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);

    try writer.writeAll("zig_atomic_load(");
    try f.writeCValue(writer, local, .Other);
    try writer.writeAll(", (zig_atomic(");
    try f.renderType(writer, ty);
    try writer.writeByte(')');
    if (ptr_ty.isVolatilePtr(zcu)) try writer.writeAll(" volatile");
    try writer.writeAll(" *)");
    try f.writeCValue(writer, ptr, .Other);
    try writer.writeAll(", ");
    try writeMemoryOrder(writer, atomic_load.order);
    try writer.writeAll(", ");
    try f.object.dg.renderTypeForBuiltinFnName(writer, ty);
    try writer.writeAll(", ");
    try f.renderType(writer, repr_ty);
    try writer.writeAll(");\n");

    return local;
}

fn airAtomicStore(f: *Function, inst: Air.Inst.Index, order: [*:0]const u8) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
    const ptr_ty = f.typeOf(bin_op.lhs);
    const ty = ptr_ty.childType(zcu);
    const ptr = try f.resolveInst(bin_op.lhs);
    const element = try f.resolveInst(bin_op.rhs);

    const writer = f.object.writer();
    const element_mat = try Materialize.start(f, inst, ty, element);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const repr_ty = if (ty.isRuntimeFloat())
        pt.intType(.unsigned, @as(u16, @intCast(ty.abiSize(zcu) * 8))) catch unreachable
    else
        ty;

    try writer.writeAll("zig_atomic_store((zig_atomic(");
    try f.renderType(writer, ty);
    try writer.writeByte(')');
    if (ptr_ty.isVolatilePtr(zcu)) try writer.writeAll(" volatile");
    try writer.writeAll(" *)");
    try f.writeCValue(writer, ptr, .Other);
    try writer.writeAll(", ");
    try element_mat.mat(f, writer);
    try writer.print(", {s}, ", .{order});
    try f.object.dg.renderTypeForBuiltinFnName(writer, ty);
    try writer.writeAll(", ");
    try f.renderType(writer, repr_ty);
    try writer.writeAll(");\n");
    try element_mat.end(f, inst);

    return .none;
}

fn writeSliceOrPtr(f: *Function, writer: anytype, ptr: CValue, ptr_ty: Type) !void {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    if (ptr_ty.isSlice(zcu)) {
        try f.writeCValueMember(writer, ptr, .{ .identifier = "ptr" });
    } else {
        try f.writeCValue(writer, ptr, .FunctionArgument);
    }
}

fn airMemset(f: *Function, inst: Air.Inst.Index, safety: bool) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
    const dest_ty = f.typeOf(bin_op.lhs);
    const dest_slice = try f.resolveInst(bin_op.lhs);
    const value = try f.resolveInst(bin_op.rhs);
    const elem_ty = f.typeOf(bin_op.rhs);
    const elem_abi_size = elem_ty.abiSize(zcu);
    const val_is_undef = if (try f.air.value(bin_op.rhs, pt)) |val| val.isUndefDeep(zcu) else false;
    const writer = f.object.writer();

    if (val_is_undef) {
        if (!safety) {
            try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
            return .none;
        }

        try writer.writeAll("memset(");
        switch (dest_ty.ptrSize(zcu)) {
            .slice => {
                try f.writeCValueMember(writer, dest_slice, .{ .identifier = "ptr" });
                try writer.writeAll(", 0xaa, ");
                try f.writeCValueMember(writer, dest_slice, .{ .identifier = "len" });
                if (elem_abi_size > 1) {
                    try writer.print(" * {d});\n", .{elem_abi_size});
                } else {
                    try writer.writeAll(");\n");
                }
            },
            .one => {
                const array_ty = dest_ty.childType(zcu);
                const len = array_ty.arrayLen(zcu) * elem_abi_size;

                try f.writeCValue(writer, dest_slice, .FunctionArgument);
                try writer.print(", 0xaa, {d});\n", .{len});
            },
            .many, .c => unreachable,
        }
        try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
        return .none;
    }

    if (elem_abi_size > 1 or dest_ty.isVolatilePtr(zcu)) {
        // For the assignment in this loop, the array pointer needs to get
        // casted to a regular pointer, otherwise an error like this occurs:
        // error: array type 'uint32_t[20]' (aka 'unsigned int[20]') is not assignable
        const elem_ptr_ty = try pt.ptrType(.{
            .child = elem_ty.toIntern(),
            .flags = .{
                .size = .c,
            },
        });

        const index = try f.allocLocal(inst, .usize);

        try writer.writeAll("for (");
        try f.writeCValue(writer, index, .Other);
        try writer.writeAll(" = ");
        try f.object.dg.renderValue(writer, .zero_usize, .Other);
        try writer.writeAll("; ");
        try f.writeCValue(writer, index, .Other);
        try writer.writeAll(" != ");
        switch (dest_ty.ptrSize(zcu)) {
            .slice => {
                try f.writeCValueMember(writer, dest_slice, .{ .identifier = "len" });
            },
            .one => {
                const array_ty = dest_ty.childType(zcu);
                try writer.print("{d}", .{array_ty.arrayLen(zcu)});
            },
            .many, .c => unreachable,
        }
        try writer.writeAll("; ++");
        try f.writeCValue(writer, index, .Other);
        try writer.writeAll(") ");

        const a = try Assignment.start(f, writer, try f.ctypeFromType(elem_ty, .complete));
        try writer.writeAll("((");
        try f.renderType(writer, elem_ptr_ty);
        try writer.writeByte(')');
        try writeSliceOrPtr(f, writer, dest_slice, dest_ty);
        try writer.writeAll(")[");
        try f.writeCValue(writer, index, .Other);
        try writer.writeByte(']');
        try a.assign(f, writer);
        try f.writeCValue(writer, value, .Other);
        try a.end(f, writer);

        try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
        try freeLocal(f, inst, index.new_local, null);

        return .none;
    }

    const bitcasted = try bitcast(f, .u8, value, elem_ty);

    try writer.writeAll("memset(");
    switch (dest_ty.ptrSize(zcu)) {
        .slice => {
            try f.writeCValueMember(writer, dest_slice, .{ .identifier = "ptr" });
            try writer.writeAll(", ");
            try f.writeCValue(writer, bitcasted, .FunctionArgument);
            try writer.writeAll(", ");
            try f.writeCValueMember(writer, dest_slice, .{ .identifier = "len" });
            try writer.writeAll(");\n");
        },
        .one => {
            const array_ty = dest_ty.childType(zcu);
            const len = array_ty.arrayLen(zcu) * elem_abi_size;

            try f.writeCValue(writer, dest_slice, .FunctionArgument);
            try writer.writeAll(", ");
            try f.writeCValue(writer, bitcasted, .FunctionArgument);
            try writer.print(", {d});\n", .{len});
        },
        .many, .c => unreachable,
    }
    try f.freeCValue(inst, bitcasted);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
    return .none;
}

fn airMemcpy(f: *Function, inst: Air.Inst.Index, function_paren: []const u8) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
    const dest_ptr = try f.resolveInst(bin_op.lhs);
    const src_ptr = try f.resolveInst(bin_op.rhs);
    const dest_ty = f.typeOf(bin_op.lhs);
    const src_ty = f.typeOf(bin_op.rhs);
    const writer = f.object.writer();

    if (dest_ty.ptrSize(zcu) != .one) {
        try writer.writeAll("if (");
        try writeArrayLen(f, writer, dest_ptr, dest_ty);
        try writer.writeAll(" != 0) ");
    }
    try writer.writeAll(function_paren);
    try writeSliceOrPtr(f, writer, dest_ptr, dest_ty);
    try writer.writeAll(", ");
    try writeSliceOrPtr(f, writer, src_ptr, src_ty);
    try writer.writeAll(", ");
    try writeArrayLen(f, writer, dest_ptr, dest_ty);
    try writer.writeAll(" * sizeof(");
    try f.renderType(writer, dest_ty.elemType2(zcu));
    try writer.writeAll("));\n");

    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
    return .none;
}

fn writeArrayLen(f: *Function, writer: ArrayListWriter, dest_ptr: CValue, dest_ty: Type) !void {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    switch (dest_ty.ptrSize(zcu)) {
        .one => try writer.print("{f}", .{
            try f.fmtIntLiteralDec(try pt.intValue(.usize, dest_ty.childType(zcu).arrayLen(zcu))),
        }),
        .many, .c => unreachable,
        .slice => try f.writeCValueMember(writer, dest_ptr, .{ .identifier = "len" }),
    }
}

fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
    const union_ptr = try f.resolveInst(bin_op.lhs);
    const new_tag = try f.resolveInst(bin_op.rhs);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });

    const union_ty = f.typeOf(bin_op.lhs).childType(zcu);
    const layout = union_ty.unionGetLayout(zcu);
    if (layout.tag_size == 0) return .none;
    const tag_ty = union_ty.unionTagTypeSafety(zcu).?;

    const writer = f.object.writer();
    const a = try Assignment.start(f, writer, try f.ctypeFromType(tag_ty, .complete));
    try f.writeCValueDerefMember(writer, union_ptr, .{ .identifier = "tag" });
    try a.assign(f, writer);
    try f.writeCValue(writer, new_tag, .Other);
    try a.end(f, writer);
    return .none;
}

fn airGetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const operand = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});

    const union_ty = f.typeOf(ty_op.operand);
    const layout = union_ty.unionGetLayout(zcu);
    if (layout.tag_size == 0) return .none;

    const inst_ty = f.typeOfIndex(inst);
    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try a.assign(f, writer);
    try f.writeCValueMember(writer, operand, .{ .identifier = "tag" });
    try a.end(f, writer);
    return local;
}

fn airTagName(f: *Function, inst: Air.Inst.Index) !CValue {
    const un_op = f.air.instructions.items(.data)[@intFromEnum(inst)].un_op;

    const inst_ty = f.typeOfIndex(inst);
    const enum_ty = f.typeOf(un_op);
    const operand = try f.resolveInst(un_op);
    try reap(f, inst, &.{un_op});

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    try f.writeCValue(writer, local, .Other);
    try writer.print(" = {s}(", .{
        try f.getLazyFnName(.{ .tag_name = enum_ty.toIntern() }),
    });
    try f.writeCValue(writer, operand, .Other);
    try writer.writeAll(");\n");

    return local;
}

fn airErrorName(f: *Function, inst: Air.Inst.Index) !CValue {
    const un_op = f.air.instructions.items(.data)[@intFromEnum(inst)].un_op;

    const writer = f.object.writer();
    const inst_ty = f.typeOfIndex(inst);
    const operand = try f.resolveInst(un_op);
    try reap(f, inst, &.{un_op});
    const local = try f.allocLocal(inst, inst_ty);
    try f.writeCValue(writer, local, .Other);

    try writer.writeAll(" = zig_errorName[");
    try f.writeCValue(writer, operand, .Other);
    try writer.writeAll(" - 1];\n");
    return local;
}

fn airSplat(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const operand = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});

    const inst_ty = f.typeOfIndex(inst);
    const inst_scalar_ty = inst_ty.scalarType(zcu);

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const v = try Vectorize.start(f, inst, writer, inst_ty);
    const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_scalar_ty, .complete));
    try f.writeCValue(writer, local, .Other);
    try v.elem(f, writer);
    try a.assign(f, writer);
    try f.writeCValue(writer, operand, .Other);
    try a.end(f, writer);
    try v.end(f, inst, writer);

    return local;
}

fn airSelect(f: *Function, inst: Air.Inst.Index) !CValue {
    const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
    const extra = f.air.extraData(Air.Bin, pl_op.payload).data;

    const pred = try f.resolveInst(pl_op.operand);
    const lhs = try f.resolveInst(extra.lhs);
    const rhs = try f.resolveInst(extra.rhs);
    try reap(f, inst, &.{ pl_op.operand, extra.lhs, extra.rhs });

    const inst_ty = f.typeOfIndex(inst);

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const v = try Vectorize.start(f, inst, writer, inst_ty);
    try f.writeCValue(writer, local, .Other);
    try v.elem(f, writer);
    try writer.writeAll(" = ");
    try f.writeCValue(writer, pred, .Other);
    try v.elem(f, writer);
    try writer.writeAll(" ? ");
    try f.writeCValue(writer, lhs, .Other);
    try v.elem(f, writer);
    try writer.writeAll(" : ");
    try f.writeCValue(writer, rhs, .Other);
    try v.elem(f, writer);
    try writer.writeAll(";\n");
    try v.end(f, inst, writer);

    return local;
}

fn airShuffleOne(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;

    const unwrapped = f.air.unwrapShuffleOne(zcu, inst);
    const mask = unwrapped.mask;
    const operand = try f.resolveInst(unwrapped.operand);
    const inst_ty = unwrapped.result_ty;

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    try reap(f, inst, &.{unwrapped.operand}); // local cannot alias operand
    for (mask, 0..) |mask_elem, out_idx| {
        try f.writeCValue(writer, local, .Other);
        try writer.writeByte('[');
        try f.object.dg.renderValue(writer, try pt.intValue(.usize, out_idx), .Other);
        try writer.writeAll("] = ");
        switch (mask_elem.unwrap()) {
            .elem => |src_idx| {
                try f.writeCValue(writer, operand, .Other);
                try writer.writeByte('[');
                try f.object.dg.renderValue(writer, try pt.intValue(.usize, src_idx), .Other);
                try writer.writeByte(']');
            },
            .value => |val| try f.object.dg.renderValue(writer, .fromInterned(val), .Other),
        }
        try writer.writeAll(";\n");
    }

    return local;
}

fn airShuffleTwo(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;

    const unwrapped = f.air.unwrapShuffleTwo(zcu, inst);
    const mask = unwrapped.mask;
    const operand_a = try f.resolveInst(unwrapped.operand_a);
    const operand_b = try f.resolveInst(unwrapped.operand_b);
    const inst_ty = unwrapped.result_ty;
    const elem_ty = inst_ty.childType(zcu);

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    try reap(f, inst, &.{ unwrapped.operand_a, unwrapped.operand_b }); // local cannot alias operands
    for (mask, 0..) |mask_elem, out_idx| {
        try f.writeCValue(writer, local, .Other);
        try writer.writeByte('[');
        try f.object.dg.renderValue(writer, try pt.intValue(.usize, out_idx), .Other);
        try writer.writeAll("] = ");
        switch (mask_elem.unwrap()) {
            .a_elem => |src_idx| {
                try f.writeCValue(writer, operand_a, .Other);
                try writer.writeByte('[');
                try f.object.dg.renderValue(writer, try pt.intValue(.usize, src_idx), .Other);
                try writer.writeByte(']');
            },
            .b_elem => |src_idx| {
                try f.writeCValue(writer, operand_b, .Other);
                try writer.writeByte('[');
                try f.object.dg.renderValue(writer, try pt.intValue(.usize, src_idx), .Other);
                try writer.writeByte(']');
            },
            .undef => try f.object.dg.renderUndefValue(writer, elem_ty, .Other),
        }
        try writer.writeAll(";\n");
    }

    return local;
}

fn airReduce(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const reduce = f.air.instructions.items(.data)[@intFromEnum(inst)].reduce;

    const scalar_ty = f.typeOfIndex(inst);
    const operand = try f.resolveInst(reduce.operand);
    try reap(f, inst, &.{reduce.operand});
    const operand_ty = f.typeOf(reduce.operand);
    const writer = f.object.writer();

    const use_operator = scalar_ty.bitSize(zcu) <= 64;
    const op: union(enum) {
        const Func = struct { operation: []const u8, info: BuiltinInfo = .none };
        builtin: Func,
        infix: []const u8,
        ternary: []const u8,
    } = switch (reduce.operation) {
        .And => if (use_operator) .{ .infix = " &= " } else .{ .builtin = .{ .operation = "and" } },
        .Or => if (use_operator) .{ .infix = " |= " } else .{ .builtin = .{ .operation = "or" } },
        .Xor => if (use_operator) .{ .infix = " ^= " } else .{ .builtin = .{ .operation = "xor" } },
        .Min => switch (scalar_ty.zigTypeTag(zcu)) {
            .int => if (use_operator) .{ .ternary = " < " } else .{ .builtin = .{ .operation = "min" } },
            .float => .{ .builtin = .{ .operation = "min" } },
            else => unreachable,
        },
        .Max => switch (scalar_ty.zigTypeTag(zcu)) {
            .int => if (use_operator) .{ .ternary = " > " } else .{ .builtin = .{ .operation = "max" } },
            .float => .{ .builtin = .{ .operation = "max" } },
            else => unreachable,
        },
        .Add => switch (scalar_ty.zigTypeTag(zcu)) {
            .int => if (use_operator) .{ .infix = " += " } else .{ .builtin = .{ .operation = "addw", .info = .bits } },
            .float => .{ .builtin = .{ .operation = "add" } },
            else => unreachable,
        },
        .Mul => switch (scalar_ty.zigTypeTag(zcu)) {
            .int => if (use_operator) .{ .infix = " *= " } else .{ .builtin = .{ .operation = "mulw", .info = .bits } },
            .float => .{ .builtin = .{ .operation = "mul" } },
            else => unreachable,
        },
    };

    // Reduce a vector by repeatedly applying a function to produce an
    // accumulated result.
    //
    // Equivalent to:
    //   reduce: {
    //     var accum: T = init;
    //     for (vec) |elem| {
    //       accum = func(accum, elem);
    //     }
    //     break :reduce accum;
    //   }

    const accum = try f.allocLocal(inst, scalar_ty);
    try f.writeCValue(writer, accum, .Other);
    try writer.writeAll(" = ");

    try f.object.dg.renderValue(writer, switch (reduce.operation) {
        .Or, .Xor => switch (scalar_ty.zigTypeTag(zcu)) {
            .bool => Value.false,
            .int => try pt.intValue(scalar_ty, 0),
            else => unreachable,
        },
        .And => switch (scalar_ty.zigTypeTag(zcu)) {
            .bool => Value.true,
            .int => switch (scalar_ty.intInfo(zcu).signedness) {
                .unsigned => try scalar_ty.maxIntScalar(pt, scalar_ty),
                .signed => try pt.intValue(scalar_ty, -1),
            },
            else => unreachable,
        },
        .Add => switch (scalar_ty.zigTypeTag(zcu)) {
            .int => try pt.intValue(scalar_ty, 0),
            .float => try pt.floatValue(scalar_ty, 0.0),
            else => unreachable,
        },
        .Mul => switch (scalar_ty.zigTypeTag(zcu)) {
            .int => try pt.intValue(scalar_ty, 1),
            .float => try pt.floatValue(scalar_ty, 1.0),
            else => unreachable,
        },
        .Min => switch (scalar_ty.zigTypeTag(zcu)) {
            .bool => Value.true,
            .int => try scalar_ty.maxIntScalar(pt, scalar_ty),
            .float => try pt.floatValue(scalar_ty, std.math.nan(f128)),
            else => unreachable,
        },
        .Max => switch (scalar_ty.zigTypeTag(zcu)) {
            .bool => Value.false,
            .int => try scalar_ty.minIntScalar(pt, scalar_ty),
            .float => try pt.floatValue(scalar_ty, std.math.nan(f128)),
            else => unreachable,
        },
    }, .Other);
    try writer.writeAll(";\n");

    const v = try Vectorize.start(f, inst, writer, operand_ty);
    try f.writeCValue(writer, accum, .Other);
    switch (op) {
        .builtin => |func| {
            try writer.print(" = zig_{s}_", .{func.operation});
            try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty);
            try writer.writeByte('(');
            try f.writeCValue(writer, accum, .FunctionArgument);
            try writer.writeAll(", ");
            try f.writeCValue(writer, operand, .Other);
            try v.elem(f, writer);
            try f.object.dg.renderBuiltinInfo(writer, scalar_ty, func.info);
            try writer.writeByte(')');
        },
        .infix => |ass| {
            try writer.writeAll(ass);
            try f.writeCValue(writer, operand, .Other);
            try v.elem(f, writer);
        },
        .ternary => |cmp| {
            try writer.writeAll(" = ");
            try f.writeCValue(writer, accum, .Other);
            try writer.writeAll(cmp);
            try f.writeCValue(writer, operand, .Other);
            try v.elem(f, writer);
            try writer.writeAll(" ? ");
            try f.writeCValue(writer, accum, .Other);
            try writer.writeAll(" : ");
            try f.writeCValue(writer, operand, .Other);
            try v.elem(f, writer);
        },
    }
    try writer.writeAll(";\n");
    try v.end(f, inst, writer);

    return accum;
}

fn airAggregateInit(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ip = &zcu.intern_pool;
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const inst_ty = f.typeOfIndex(inst);
    const len: usize = @intCast(inst_ty.arrayLen(zcu));
    const elements: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[ty_pl.payload..][0..len]);
    const gpa = f.object.dg.gpa;
    const resolved_elements = try gpa.alloc(CValue, elements.len);
    defer gpa.free(resolved_elements);
    for (resolved_elements, elements) |*resolved_element, element| {
        resolved_element.* = try f.resolveInst(element);
    }
    {
        var bt = iterateBigTomb(f, inst);
        for (elements) |element| {
            try bt.feed(element);
        }
    }

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    switch (ip.indexToKey(inst_ty.toIntern())) {
        inline .array_type, .vector_type => |info, tag| {
            const a: Assignment = .{
                .ctype = try f.ctypeFromType(.fromInterned(info.child), .complete),
            };
            for (resolved_elements, 0..) |element, i| {
                try a.restart(f, writer);
                try f.writeCValue(writer, local, .Other);
                try writer.print("[{d}]", .{i});
                try a.assign(f, writer);
                try f.writeCValue(writer, element, .Other);
                try a.end(f, writer);
            }
            if (tag == .array_type and info.sentinel != .none) {
                try a.restart(f, writer);
                try f.writeCValue(writer, local, .Other);
                try writer.print("[{d}]", .{info.len});
                try a.assign(f, writer);
                try f.object.dg.renderValue(writer, Value.fromInterned(info.sentinel), .Other);
                try a.end(f, writer);
            }
        },
        .struct_type => {
            const loaded_struct = ip.loadStructType(inst_ty.toIntern());
            switch (loaded_struct.layout) {
                .auto, .@"extern" => {
                    var field_it = loaded_struct.iterateRuntimeOrder(ip);
                    while (field_it.next()) |field_index| {
                        const field_ty: Type = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
                        if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;

                        const a = try Assignment.start(f, writer, try f.ctypeFromType(field_ty, .complete));
                        try f.writeCValueMember(writer, local, if (loaded_struct.fieldName(ip, field_index).unwrap()) |field_name|
                            .{ .identifier = field_name.toSlice(ip) }
                        else
                            .{ .field = field_index });
                        try a.assign(f, writer);
                        try f.writeCValue(writer, resolved_elements[field_index], .Other);
                        try a.end(f, writer);
                    }
                },
                .@"packed" => {
                    try f.writeCValue(writer, local, .Other);
                    try writer.writeAll(" = ");

                    const backing_int_ty: Type = .fromInterned(loaded_struct.backingIntTypeUnordered(ip));
                    const int_info = backing_int_ty.intInfo(zcu);

                    const bit_offset_ty = try pt.intType(.unsigned, Type.smallestUnsignedBits(int_info.bits - 1));

                    var bit_offset: u64 = 0;

                    var empty = true;
                    for (0..elements.len) |field_index| {
                        if (inst_ty.structFieldIsComptime(field_index, zcu)) continue;
                        const field_ty = inst_ty.fieldType(field_index, zcu);
                        if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;

                        if (!empty) {
                            try writer.writeAll("zig_or_");
                            try f.object.dg.renderTypeForBuiltinFnName(writer, inst_ty);
                            try writer.writeByte('(');
                        }
                        empty = false;
                    }
                    empty = true;
                    for (resolved_elements, 0..) |element, field_index| {
                        if (inst_ty.structFieldIsComptime(field_index, zcu)) continue;
                        const field_ty = inst_ty.fieldType(field_index, zcu);
                        if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;

                        if (!empty) try writer.writeAll(", ");
                        // TODO: Skip this entire shift if val is 0?
                        try writer.writeAll("zig_shlw_");
                        try f.object.dg.renderTypeForBuiltinFnName(writer, inst_ty);
                        try writer.writeByte('(');

                        if (field_ty.isAbiInt(zcu)) {
                            try writer.writeAll("zig_and_");
                            try f.object.dg.renderTypeForBuiltinFnName(writer, inst_ty);
                            try writer.writeByte('(');
                        }

                        if (inst_ty.isAbiInt(zcu) and (field_ty.isAbiInt(zcu) or field_ty.isPtrAtRuntime(zcu))) {
                            try f.renderIntCast(writer, inst_ty, element, .{}, field_ty, .FunctionArgument);
                        } else {
                            try writer.writeByte('(');
                            try f.renderType(writer, inst_ty);
                            try writer.writeByte(')');
                            if (field_ty.isPtrAtRuntime(zcu)) {
                                try writer.writeByte('(');
                                try f.renderType(writer, switch (int_info.signedness) {
                                    .unsigned => .usize,
                                    .signed => .isize,
                                });
                                try writer.writeByte(')');
                            }
                            try f.writeCValue(writer, element, .Other);
                        }

                        if (field_ty.isAbiInt(zcu)) {
                            try writer.writeAll(", ");
                            const field_int_info = field_ty.intInfo(zcu);
                            const field_mask = if (int_info.signedness == .signed and int_info.bits == field_int_info.bits)
                                try pt.intValue(backing_int_ty, -1)
                            else
                                try (try pt.intType(.unsigned, field_int_info.bits)).maxIntScalar(pt, backing_int_ty);
                            try f.object.dg.renderValue(writer, field_mask, .FunctionArgument);
                            try writer.writeByte(')');
                        }

                        try writer.print(", {f}", .{
                            try f.fmtIntLiteralDec(try pt.intValue(bit_offset_ty, bit_offset)),
                        });
                        try f.object.dg.renderBuiltinInfo(writer, inst_ty, .bits);
                        try writer.writeByte(')');
                        if (!empty) try writer.writeByte(')');

                        bit_offset += field_ty.bitSize(zcu);
                        empty = false;
                    }
                    try writer.writeAll(";\n");
                },
            }
        },
        .tuple_type => |tuple_info| for (0..tuple_info.types.len) |field_index| {
            if (tuple_info.values.get(ip)[field_index] != .none) continue;
            const field_ty: Type = .fromInterned(tuple_info.types.get(ip)[field_index]);
            if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;

            const a = try Assignment.start(f, writer, try f.ctypeFromType(field_ty, .complete));
            try f.writeCValueMember(writer, local, .{ .field = field_index });
            try a.assign(f, writer);
            try f.writeCValue(writer, resolved_elements[field_index], .Other);
            try a.end(f, writer);
        },
        else => unreachable,
    }

    return local;
}

fn airUnionInit(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const ip = &zcu.intern_pool;
    const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
    const extra = f.air.extraData(Air.UnionInit, ty_pl.payload).data;

    const union_ty = f.typeOfIndex(inst);
    const loaded_union = ip.loadUnionType(union_ty.toIntern());
    const field_name = loaded_union.loadTagType(ip).names.get(ip)[extra.field_index];
    const payload_ty = f.typeOf(extra.init);
    const payload = try f.resolveInst(extra.init);
    try reap(f, inst, &.{extra.init});

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, union_ty);
    if (loaded_union.flagsUnordered(ip).layout == .@"packed") return f.moveCValue(inst, union_ty, payload);

    const field: CValue = if (union_ty.unionTagTypeSafety(zcu)) |tag_ty| field: {
        const layout = union_ty.unionGetLayout(zcu);
        if (layout.tag_size != 0) {
            const field_index = tag_ty.enumFieldIndex(field_name, zcu).?;
            const tag_val = try pt.enumValueFieldIndex(tag_ty, field_index);

            const a = try Assignment.start(f, writer, try f.ctypeFromType(tag_ty, .complete));
            try f.writeCValueMember(writer, local, .{ .identifier = "tag" });
            try a.assign(f, writer);
            try writer.print("{f}", .{try f.fmtIntLiteralDec(try tag_val.intFromEnum(tag_ty, pt))});
            try a.end(f, writer);
        }
        break :field .{ .payload_identifier = field_name.toSlice(ip) };
    } else .{ .identifier = field_name.toSlice(ip) };

    const a = try Assignment.start(f, writer, try f.ctypeFromType(payload_ty, .complete));
    try f.writeCValueMember(writer, local, field);
    try a.assign(f, writer);
    try f.writeCValue(writer, payload, .Other);
    try a.end(f, writer);
    return local;
}

fn airPrefetch(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const prefetch = f.air.instructions.items(.data)[@intFromEnum(inst)].prefetch;

    const ptr_ty = f.typeOf(prefetch.ptr);
    const ptr = try f.resolveInst(prefetch.ptr);
    try reap(f, inst, &.{prefetch.ptr});

    const writer = f.object.writer();
    switch (prefetch.cache) {
        .data => {
            try writer.writeAll("zig_prefetch(");
            if (ptr_ty.isSlice(zcu))
                try f.writeCValueMember(writer, ptr, .{ .identifier = "ptr" })
            else
                try f.writeCValue(writer, ptr, .FunctionArgument);
            try writer.print(", {d}, {d});\n", .{ @intFromEnum(prefetch.rw), prefetch.locality });
        },
        // The available prefetch intrinsics do not accept a cache argument; only
        // address, rw, and locality.
        .instruction => {},
    }

    return .none;
}

fn airWasmMemorySize(f: *Function, inst: Air.Inst.Index) !CValue {
    const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;

    const writer = f.object.writer();
    const inst_ty = f.typeOfIndex(inst);
    const local = try f.allocLocal(inst, inst_ty);
    try f.writeCValue(writer, local, .Other);

    try writer.writeAll(" = ");
    try writer.print("zig_wasm_memory_size({d});\n", .{pl_op.payload});

    return local;
}

fn airWasmMemoryGrow(f: *Function, inst: Air.Inst.Index) !CValue {
    const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;

    const writer = f.object.writer();
    const inst_ty = f.typeOfIndex(inst);
    const operand = try f.resolveInst(pl_op.operand);
    try reap(f, inst, &.{pl_op.operand});
    const local = try f.allocLocal(inst, inst_ty);
    try f.writeCValue(writer, local, .Other);

    try writer.writeAll(" = ");
    try writer.print("zig_wasm_memory_grow({d}, ", .{pl_op.payload});
    try f.writeCValue(writer, operand, .FunctionArgument);
    try writer.writeAll(");\n");
    return local;
}

fn airMulAdd(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
    const bin_op = f.air.extraData(Air.Bin, pl_op.payload).data;

    const mulend1 = try f.resolveInst(bin_op.lhs);
    const mulend2 = try f.resolveInst(bin_op.rhs);
    const addend = try f.resolveInst(pl_op.operand);
    try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs, pl_op.operand });

    const inst_ty = f.typeOfIndex(inst);
    const inst_scalar_ty = inst_ty.scalarType(zcu);

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    const v = try Vectorize.start(f, inst, writer, inst_ty);
    try f.writeCValue(writer, local, .Other);
    try v.elem(f, writer);
    try writer.writeAll(" = zig_fma_");
    try f.object.dg.renderTypeForBuiltinFnName(writer, inst_scalar_ty);
    try writer.writeByte('(');
    try f.writeCValue(writer, mulend1, .FunctionArgument);
    try v.elem(f, writer);
    try writer.writeAll(", ");
    try f.writeCValue(writer, mulend2, .FunctionArgument);
    try v.elem(f, writer);
    try writer.writeAll(", ");
    try f.writeCValue(writer, addend, .FunctionArgument);
    try v.elem(f, writer);
    try writer.writeAll(");\n");
    try v.end(f, inst, writer);

    return local;
}

fn airRuntimeNavPtr(f: *Function, inst: Air.Inst.Index) !CValue {
    const ty_nav = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_nav;
    const writer = f.object.writer();
    const local = try f.allocLocal(inst, .fromInterned(ty_nav.ty));
    try f.writeCValue(writer, local, .Other);
    try writer.writeAll(" = ");
    try f.object.dg.renderNav(writer, ty_nav.nav, .Other);
    try writer.writeAll(";\n");
    return local;
}

fn airCVaStart(f: *Function, inst: Air.Inst.Index) !CValue {
    const pt = f.object.dg.pt;
    const zcu = pt.zcu;
    const inst_ty = f.typeOfIndex(inst);
    const function_ty = zcu.navValue(f.object.dg.pass.nav).typeOf(zcu);
    const function_info = (try f.ctypeFromType(function_ty, .complete)).info(&f.object.dg.ctype_pool).function;
    assert(function_info.varargs);

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    try writer.writeAll("va_start(*(va_list *)&");
    try f.writeCValue(writer, local, .Other);
    if (function_info.param_ctypes.len > 0) {
        try writer.writeAll(", ");
        try f.writeCValue(writer, .{ .arg = function_info.param_ctypes.len - 1 }, .FunctionArgument);
    }
    try writer.writeAll(");\n");
    return local;
}

fn airCVaArg(f: *Function, inst: Air.Inst.Index) !CValue {
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const inst_ty = f.typeOfIndex(inst);
    const va_list = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    try f.writeCValue(writer, local, .Other);
    try writer.writeAll(" = va_arg(*(va_list *)");
    try f.writeCValue(writer, va_list, .Other);
    try writer.writeAll(", ");
    try f.renderType(writer, ty_op.ty.toType());
    try writer.writeAll(");\n");
    return local;
}

fn airCVaEnd(f: *Function, inst: Air.Inst.Index) !CValue {
    const un_op = f.air.instructions.items(.data)[@intFromEnum(inst)].un_op;

    const va_list = try f.resolveInst(un_op);
    try reap(f, inst, &.{un_op});

    const writer = f.object.writer();
    try writer.writeAll("va_end(*(va_list *)");
    try f.writeCValue(writer, va_list, .Other);
    try writer.writeAll(");\n");
    return .none;
}

fn airCVaCopy(f: *Function, inst: Air.Inst.Index) !CValue {
    const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;

    const inst_ty = f.typeOfIndex(inst);
    const va_list = try f.resolveInst(ty_op.operand);
    try reap(f, inst, &.{ty_op.operand});

    const writer = f.object.writer();
    const local = try f.allocLocal(inst, inst_ty);
    try writer.writeAll("va_copy(*(va_list *)&");
    try f.writeCValue(writer, local, .Other);
    try writer.writeAll(", *(va_list *)");
    try f.writeCValue(writer, va_list, .Other);
    try writer.writeAll(");\n");
    return local;
}

fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 {
    return switch (order) {
        // Note: unordered is actually even less atomic than relaxed
        .unordered, .monotonic => "zig_memory_order_relaxed",
        .acquire => "zig_memory_order_acquire",
        .release => "zig_memory_order_release",
        .acq_rel => "zig_memory_order_acq_rel",
        .seq_cst => "zig_memory_order_seq_cst",
    };
}

fn writeMemoryOrder(w: anytype, order: std.builtin.AtomicOrder) !void {
    return w.writeAll(toMemoryOrder(order));
}

fn toCallingConvention(cc: std.builtin.CallingConvention, zcu: *Zcu) ?[]const u8 {
    if (zcu.getTarget().cCallingConvention()) |ccc| {
        if (cc.eql(ccc)) {
            return null;
        }
    }
    return switch (cc) {
        .auto, .naked => null,

        .x86_64_sysv, .x86_sysv => "sysv_abi",
        .x86_64_win, .x86_win => "ms_abi",
        .x86_stdcall => "stdcall",
        .x86_fastcall => "fastcall",
        .x86_thiscall => "thiscall",

        .x86_vectorcall,
        .x86_64_vectorcall,
        => "vectorcall",

        .x86_64_regcall_v3_sysv,
        .x86_64_regcall_v4_win,
        .x86_regcall_v3,
        .x86_regcall_v4_win,
        => "regcall",

        .aarch64_vfabi => "aarch64_vector_pcs",
        .aarch64_vfabi_sve => "aarch64_sve_pcs",

        .arm_aapcs => "pcs(\"aapcs\")",
        .arm_aapcs_vfp => "pcs(\"aapcs-vfp\")",

        .arm_interrupt => |opts| switch (opts.type) {
            .generic => "interrupt",
            .irq => "interrupt(\"IRQ\")",
            .fiq => "interrupt(\"FIQ\")",
            .swi => "interrupt(\"SWI\")",
            .abort => "interrupt(\"ABORT\")",
            .undef => "interrupt(\"UNDEF\")",
        },

        .avr_signal => "signal",

        .mips_interrupt,
        .mips64_interrupt,
        => |opts| switch (opts.mode) {
            inline else => |m| "interrupt(\"" ++ @tagName(m) ++ "\")",
        },

        .riscv64_lp64_v, .riscv32_ilp32_v => "riscv_vector_cc",

        .riscv32_interrupt,
        .riscv64_interrupt,
        => |opts| switch (opts.mode) {
            inline else => |m| "interrupt(\"" ++ @tagName(m) ++ "\")",
        },

        .m68k_rtd => "m68k_rtd",

        .avr_interrupt,
        .csky_interrupt,
        .m68k_interrupt,
        .x86_interrupt,
        .x86_64_interrupt,
        => "interrupt",

        else => unreachable, // `Zcu.callconvSupported`
    };
}

fn toAtomicRmwSuffix(order: std.builtin.AtomicRmwOp) []const u8 {
    return switch (order) {
        .Xchg => "xchg",
        .Add => "add",
        .Sub => "sub",
        .And => "and",
        .Nand => "nand",
        .Or => "or",
        .Xor => "xor",
        .Max => "max",
        .Min => "min",
    };
}

const ArrayListWriter = ErrorOnlyGenericWriter(std.ArrayList(u8).Writer.Error);

fn arrayListWriter(list: *std.ArrayList(u8)) ArrayListWriter {
    return .{ .context = .{
        .context = list,
        .writeFn = struct {
            fn write(context: *const anyopaque, bytes: []const u8) anyerror!usize {
                const l: *std.ArrayList(u8) = @alignCast(@constCast(@ptrCast(context)));
                return l.writer().write(bytes);
            }
        }.write,
    } };
}

fn IndentWriter(comptime UnderlyingWriter: type) type {
    return struct {
        const Self = @This();
        pub const Error = UnderlyingWriter.Error;
        pub const Writer = ErrorOnlyGenericWriter(Error);

        pub const indent_delta = 1;

        underlying_writer: UnderlyingWriter,
        indent_count: usize = 0,
        current_line_empty: bool = true,

        pub fn writer(self: *Self) Writer {
            return .{ .context = .{
                .context = self,
                .writeFn = writeAny,
            } };
        }

        pub fn write(self: *Self, bytes: []const u8) Error!usize {
            if (bytes.len == 0) return 0;

            const current_indent = self.indent_count * Self.indent_delta;
            if (self.current_line_empty and current_indent > 0) {
                try self.underlying_writer.writeByteNTimes(' ', current_indent);
            }
            self.current_line_empty = false;

            return self.writeNoIndent(bytes);
        }

        fn writeAny(context: *const anyopaque, bytes: []const u8) anyerror!usize {
            const self: *Self = @alignCast(@constCast(@ptrCast(context)));
            return self.write(bytes);
        }

        pub fn insertNewline(self: *Self) Error!void {
            _ = try self.writeNoIndent("\n");
        }

        pub fn pushIndent(self: *Self) void {
            self.indent_count += 1;
        }

        pub fn popIndent(self: *Self) void {
            assert(self.indent_count != 0);
            self.indent_count -= 1;
        }

        fn writeNoIndent(self: *Self, bytes: []const u8) Error!usize {
            if (bytes.len == 0) return 0;

            try self.underlying_writer.writeAll(bytes);
            if (bytes[bytes.len - 1] == '\n') {
                self.current_line_empty = true;
            }
            return bytes.len;
        }
    };
}

/// A wrapper around `std.io.AnyWriter` that maintains a generic error set while
/// erasing the rest of the implementation. This is intended to avoid duplicate
/// generic instantiations for writer types which share the same error set, while
/// maintaining ease of error handling.
fn ErrorOnlyGenericWriter(comptime Error: type) type {
    return std.io.GenericWriter(std.io.AnyWriter, Error, struct {
        fn write(context: std.io.AnyWriter, bytes: []const u8) Error!usize {
            return @errorCast(context.write(bytes));
        }
    }.write);
}

fn toCIntBits(zig_bits: u32) ?u32 {
    for (&[_]u8{ 8, 16, 32, 64, 128 }) |c_bits| {
        if (zig_bits <= c_bits) {
            return c_bits;
        }
    }
    return null;
}

fn signAbbrev(signedness: std.builtin.Signedness) u8 {
    return switch (signedness) {
        .signed => 'i',
        .unsigned => 'u',
    };
}

fn compilerRtAbbrev(ty: Type, zcu: *Zcu, target: *const std.Target) []const u8 {
    return if (ty.isInt(zcu)) switch (ty.intInfo(zcu).bits) {
        1...32 => "si",
        33...64 => "di",
        65...128 => "ti",
        else => unreachable,
    } else if (ty.isRuntimeFloat()) switch (ty.floatBits(target)) {
        16 => "hf",
        32 => "sf",
        64 => "df",
        80 => "xf",
        128 => "tf",
        else => unreachable,
    } else unreachable;
}

fn compareOperatorAbbrev(operator: std.math.CompareOperator) []const u8 {
    return switch (operator) {
        .lt => "lt",
        .lte => "le",
        .eq => "eq",
        .gte => "ge",
        .gt => "gt",
        .neq => "ne",
    };
}

fn compareOperatorC(operator: std.math.CompareOperator) []const u8 {
    return switch (operator) {
        .lt => " < ",
        .lte => " <= ",
        .eq => " == ",
        .gte => " >= ",
        .gt => " > ",
        .neq => " != ",
    };
}

const StringLiteral = struct {
    len: usize,
    cur_len: usize,
    start_count: usize,
    writer: *std.io.Writer,

    // MSVC throws C2078 if an array of size 65536 or greater is initialized with a string literal,
    // regardless of the length of the string literal initializing it. Array initializer syntax is
    // used instead.
    // C99 only requires 4095.
    const max_string_initializer_len = @min(65535, 4095);

    // MSVC has a length limit of 16380 per string literal (before concatenation)
    // C99 only requires 4095.
    const max_char_len = 4;
    const max_literal_len = @min(16380 - max_char_len, 4095);

    fn init(writer: *std.io.Writer, len: usize) StringLiteral {
        return .{
            .cur_len = 0,
            .len = len,
            .start_count = writer.count,
            .writer = writer,
        };
    }

    pub fn start(sl: *StringLiteral) std.io.Writer.Error!void {
        if (sl.len <= max_string_initializer_len) {
            try sl.writer.writeByte('\"');
        } else {
            try sl.writer.writeByte('{');
        }
    }

    pub fn end(sl: *StringLiteral) std.io.Writer.Error!void {
        if (sl.len <= max_string_initializer_len) {
            try sl.writer.writeByte('\"');
        } else {
            try sl.writer.writeByte('}');
        }
    }

    fn writeStringLiteralChar(sl: *StringLiteral, c: u8) std.io.Writer.Error!void {
        switch (c) {
            7 => try sl.writer.writeAll("\\a"),
            8 => try sl.writer.writeAll("\\b"),
            '\t' => try sl.writer.writeAll("\\t"),
            '\n' => try sl.writer.writeAll("\\n"),
            11 => try sl.writer.writeAll("\\v"),
            12 => try sl.writer.writeAll("\\f"),
            '\r' => try sl.writer.writeAll("\\r"),
            '"', '\'', '?', '\\' => try sl.writer.print("\\{c}", .{c}),
            else => switch (c) {
                ' '...'~' => try sl.writer.writeByte(c),
                else => try sl.writer.print("\\{o:0>3}", .{c}),
            },
        }
    }

    pub fn writeChar(sl: *StringLiteral, c: u8) std.io.Writer.Error!void {
        if (sl.len <= max_string_initializer_len) {
            if (sl.cur_len == 0 and sl.writer.count - sl.start_count > 1)
                try sl.writer.writeAll("\"\"");

            const count = sl.writer.count;
            try sl.writeStringLiteralChar(c);
            const char_len = sl.writer.count - count;
            assert(char_len <= max_char_len);
            sl.cur_len += char_len;

            if (sl.cur_len >= max_literal_len) sl.cur_len = 0;
        } else {
            if (sl.writer.count - sl.start_count > 1) try sl.writer.writeByte(',');
            try sl.writer.print("'\\x{x}'", .{c});
        }
    }
};

const FormatStringContext = struct {
    str: []const u8,
    sentinel: ?u8,
};

fn formatStringLiteral(data: FormatStringContext, writer: *std.io.Writer) std.io.Writer.Error!void {
    var literal: StringLiteral = .init(writer, data.str.len + @intFromBool(data.sentinel != null));
    try literal.start();
    for (data.str) |c| try literal.writeChar(c);
    if (data.sentinel) |sentinel| if (sentinel != 0) try literal.writeChar(sentinel);
    try literal.end();
}

fn fmtStringLiteral(str: []const u8, sentinel: ?u8) std.fmt.Formatter(FormatStringContext, formatStringLiteral) {
    return .{ .data = .{ .str = str, .sentinel = sentinel } };
}

fn undefPattern(comptime IntType: type) IntType {
    const int_info = @typeInfo(IntType).int;
    const UnsignedType = std.meta.Int(.unsigned, int_info.bits);
    return @bitCast(@as(UnsignedType, (1 << (int_info.bits | 1)) / 3));
}

const FormatIntLiteralContext = struct {
    dg: *DeclGen,
    int_info: InternPool.Key.IntType,
    kind: CType.Kind,
    ctype: CType,
    val: Value,
    base: u8,
    case: std.fmt.Case,
};
fn formatIntLiteral(data: FormatIntLiteralContext, writer: *std.io.Writer) std.io.Writer.Error!void {
    const pt = data.dg.pt;
    const zcu = pt.zcu;
    const target = &data.dg.mod.resolved_target.result;
    const ctype_pool = &data.dg.ctype_pool;

    const ExpectedContents = struct {
        const base = 10;
        const bits = 128;
        const limbs_count = BigInt.calcTwosCompLimbCount(bits);

        undef_limbs: [limbs_count]BigIntLimb,
        wrap_limbs: [limbs_count]BigIntLimb,
        to_string_buf: [bits]u8,
        to_string_limbs: [BigInt.calcToStringLimbsBufferLen(limbs_count, base)]BigIntLimb,
    };
    var stack align(@alignOf(ExpectedContents)) =
        std.heap.stackFallback(@sizeOf(ExpectedContents), data.dg.gpa);
    const allocator = stack.get();

    var undef_limbs: []BigIntLimb = &.{};
    defer allocator.free(undef_limbs);

    var int_buf: Value.BigIntSpace = undefined;
    const int = if (data.val.isUndefDeep(zcu)) blk: {
        undef_limbs = try oom(allocator.alloc(BigIntLimb, BigInt.calcTwosCompLimbCount(data.int_info.bits)));
        @memset(undef_limbs, undefPattern(BigIntLimb));

        var undef_int = BigInt.Mutable{
            .limbs = undef_limbs,
            .len = undef_limbs.len,
            .positive = true,
        };
        undef_int.truncate(undef_int.toConst(), data.int_info.signedness, data.int_info.bits);
        break :blk undef_int.toConst();
    } else data.val.toBigInt(&int_buf, zcu);
    assert(int.fitsInTwosComp(data.int_info.signedness, data.int_info.bits));

    const c_bits: usize = @intCast(data.ctype.byteSize(ctype_pool, data.dg.mod) * 8);
    var one_limbs: [BigInt.calcLimbLen(1)]BigIntLimb = undefined;
    const one = BigInt.Mutable.init(&one_limbs, 1).toConst();

    var wrap = BigInt.Mutable{
        .limbs = try oom(allocator.alloc(BigIntLimb, BigInt.calcTwosCompLimbCount(c_bits))),
        .len = undefined,
        .positive = undefined,
    };
    defer allocator.free(wrap.limbs);

    const c_limb_info: struct {
        ctype: CType,
        count: usize,
        endian: std.builtin.Endian,
        homogeneous: bool,
    } = switch (data.ctype.info(ctype_pool)) {
        .basic => |basic_info| switch (basic_info) {
            else => .{
                .ctype = .void,
                .count = 1,
                .endian = .little,
                .homogeneous = true,
            },
            .zig_u128, .zig_i128 => .{
                .ctype = .u64,
                .count = 2,
                .endian = .big,
                .homogeneous = false,
            },
        },
        .array => |array_info| .{
            .ctype = array_info.elem_ctype,
            .count = @intCast(array_info.len),
            .endian = target.cpu.arch.endian(),
            .homogeneous = true,
        },
        else => unreachable,
    };
    if (c_limb_info.count == 1) {
        if (wrap.addWrap(int, one, data.int_info.signedness, c_bits) or
            data.int_info.signedness == .signed and wrap.subWrap(int, one, data.int_info.signedness, c_bits))
            return writer.print("{s}_{s}", .{
                data.ctype.getStandardDefineAbbrev() orelse return writer.print("zig_{s}Int_{c}{d}", .{
                    if (int.positive) "max" else "min", signAbbrev(data.int_info.signedness), c_bits,
                }),
                if (int.positive) "MAX" else "MIN",
            });

        if (!int.positive) try writer.writeByte('-');
        try data.ctype.renderLiteralPrefix(writer, data.kind, ctype_pool);

        switch (data.base) {
            2 => try writer.writeAll("0b"),
            8 => try writer.writeByte('0'),
            10 => {},
            16 => try writer.writeAll("0x"),
            else => unreachable,
        }
        const string = try oom(int.abs().toStringAlloc(allocator, data.base, data.case));
        defer allocator.free(string);
        try writer.writeAll(string);
    } else {
        try data.ctype.renderLiteralPrefix(writer, data.kind, ctype_pool);
        wrap.truncate(int, .unsigned, c_bits);
        @memset(wrap.limbs[wrap.len..], 0);
        wrap.len = wrap.limbs.len;
        const limbs_per_c_limb = @divExact(wrap.len, c_limb_info.count);

        var c_limb_int_info: std.builtin.Type.Int = .{
            .signedness = undefined,
            .bits = @intCast(@divExact(c_bits, c_limb_info.count)),
        };
        var c_limb_ctype: CType = undefined;

        var limb_offset: usize = 0;
        const most_significant_limb_i = wrap.len - limbs_per_c_limb;
        while (limb_offset < wrap.len) : (limb_offset += limbs_per_c_limb) {
            const limb_i = switch (c_limb_info.endian) {
                .little => limb_offset,
                .big => most_significant_limb_i - limb_offset,
            };
            var c_limb_mut = BigInt.Mutable{
                .limbs = wrap.limbs[limb_i..][0..limbs_per_c_limb],
                .len = undefined,
                .positive = true,
            };
            c_limb_mut.normalize(limbs_per_c_limb);

            if (limb_i == most_significant_limb_i and
                !c_limb_info.homogeneous and data.int_info.signedness == .signed)
            {
                // most significant limb is actually signed
                c_limb_int_info.signedness = .signed;
                c_limb_ctype = c_limb_info.ctype.toSigned();

                c_limb_mut.truncate(
                    c_limb_mut.toConst(),
                    .signed,
                    data.int_info.bits - limb_i * @bitSizeOf(BigIntLimb),
                );
            } else {
                c_limb_int_info.signedness = .unsigned;
                c_limb_ctype = c_limb_info.ctype;
            }

            if (limb_offset > 0) try writer.writeAll(", ");
            try formatIntLiteral(.{
                .dg = data.dg,
                .int_info = c_limb_int_info,
                .kind = data.kind,
                .ctype = c_limb_ctype,
                .val = try oom(pt.intValue_big(.comptime_int, c_limb_mut.toConst())),
                .base = data.base,
                .case = data.case,
            }, writer);
        }
    }
    try data.ctype.renderLiteralSuffix(writer, ctype_pool);
}

const Materialize = struct {
    local: CValue,

    pub fn start(f: *Function, inst: Air.Inst.Index, ty: Type, value: CValue) !Materialize {
        return .{ .local = switch (value) {
            .local_ref, .constant, .nav_ref, .undef => try f.moveCValue(inst, ty, value),
            .new_local => |local| .{ .local = local },
            else => value,
        } };
    }

    pub fn mat(self: Materialize, f: *Function, writer: anytype) !void {
        try f.writeCValue(writer, self.local, .Other);
    }

    pub fn end(self: Materialize, f: *Function, inst: Air.Inst.Index) !void {
        try f.freeCValue(inst, self.local);
    }
};

const Assignment = struct {
    ctype: CType,

    pub fn start(f: *Function, writer: anytype, ctype: CType) !Assignment {
        const self: Assignment = .{ .ctype = ctype };
        try self.restart(f, writer);
        return self;
    }

    pub fn restart(self: Assignment, f: *Function, writer: anytype) !void {
        switch (self.strategy(f)) {
            .assign => {},
            .memcpy => try writer.writeAll("memcpy("),
        }
    }

    pub fn assign(self: Assignment, f: *Function, writer: anytype) !void {
        switch (self.strategy(f)) {
            .assign => try writer.writeAll(" = "),
            .memcpy => try writer.writeAll(", "),
        }
    }

    pub fn end(self: Assignment, f: *Function, writer: anytype) !void {
        switch (self.strategy(f)) {
            .assign => {},
            .memcpy => {
                try writer.writeAll(", sizeof(");
                try f.renderCType(writer, self.ctype);
                try writer.writeAll("))");
            },
        }
        try writer.writeAll(";\n");
    }

    fn strategy(self: Assignment, f: *Function) enum { assign, memcpy } {
        return switch (self.ctype.info(&f.object.dg.ctype_pool)) {
            else => .assign,
            .array, .vector => .memcpy,
        };
    }
};

const Vectorize = struct {
    index: CValue = .none,

    pub fn start(f: *Function, inst: Air.Inst.Index, writer: anytype, ty: Type) !Vectorize {
        const pt = f.object.dg.pt;
        const zcu = pt.zcu;
        return if (ty.zigTypeTag(zcu) == .vector) index: {
            const local = try f.allocLocal(inst, .usize);

            try writer.writeAll("for (");
            try f.writeCValue(writer, local, .Other);
            try writer.print(" = {f}; ", .{try f.fmtIntLiteralDec(.zero_usize)});
            try f.writeCValue(writer, local, .Other);
            try writer.print(" < {f}; ", .{try f.fmtIntLiteralDec(try pt.intValue(.usize, ty.vectorLen(zcu)))});
            try f.writeCValue(writer, local, .Other);
            try writer.print(" += {f}) {{\n", .{try f.fmtIntLiteralDec(.one_usize)});
            f.object.indent_writer.pushIndent();

            break :index .{ .index = local };
        } else .{};
    }

    pub fn elem(self: Vectorize, f: *Function, writer: anytype) !void {
        if (self.index != .none) {
            try writer.writeByte('[');
            try f.writeCValue(writer, self.index, .Other);
            try writer.writeByte(']');
        }
    }

    pub fn end(self: Vectorize, f: *Function, inst: Air.Inst.Index, writer: anytype) !void {
        if (self.index != .none) {
            f.object.indent_writer.popIndent();
            try writer.writeAll("}\n");
            try freeLocal(f, inst, self.index.new_local, null);
        }
    }
};

fn lowersToArray(ty: Type, pt: Zcu.PerThread) bool {
    const zcu = pt.zcu;
    return switch (ty.zigTypeTag(zcu)) {
        .array, .vector => return true,
        else => return ty.isAbiInt(zcu) and toCIntBits(@as(u32, @intCast(ty.bitSize(zcu)))) == null,
    };
}

fn reap(f: *Function, inst: Air.Inst.Index, operands: []const Air.Inst.Ref) !void {
    assert(operands.len <= Air.Liveness.bpi - 1);
    var tomb_bits = f.liveness.getTombBits(inst);
    for (operands) |operand| {
        const dies = @as(u1, @truncate(tomb_bits)) != 0;
        tomb_bits >>= 1;
        if (!dies) continue;
        try die(f, inst, operand);
    }
}

fn die(f: *Function, inst: Air.Inst.Index, ref: Air.Inst.Ref) !void {
    const ref_inst = ref.toIndex() orelse return;
    const c_value = (f.value_map.fetchRemove(ref) orelse return).value;
    const local_index = switch (c_value) {
        .new_local, .local => |l| l,
        else => return,
    };
    try freeLocal(f, inst, local_index, ref_inst);
}

fn freeLocal(f: *Function, inst: ?Air.Inst.Index, local_index: LocalIndex, ref_inst: ?Air.Inst.Index) !void {
    const gpa = f.object.dg.gpa;
    const local = &f.locals.items[local_index];
    if (inst) |i| {
        if (ref_inst) |operand| {
            log.debug("%{d}: freeing t{d} (operand %{d})", .{ @intFromEnum(i), local_index, operand });
        } else {
            log.debug("%{d}: freeing t{d}", .{ @intFromEnum(i), local_index });
        }
    } else {
        if (ref_inst) |operand| {
            log.debug("freeing t{d} (operand %{d})", .{ local_index, operand });
        } else {
            log.debug("freeing t{d}", .{local_index});
        }
    }
    const gop = try f.free_locals_map.getOrPut(gpa, local.getType());
    if (!gop.found_existing) gop.value_ptr.* = .{};
    if (std.debug.runtime_safety) {
        // If this trips, an unfreeable allocation was attempted to be freed.
        assert(!f.allocs.contains(local_index));
    }
    // If this trips, it means a local is being inserted into the
    // free_locals map while it already exists in the map, which is not
    // allowed.
    try gop.value_ptr.putNoClobber(gpa, local_index, {});
}

const BigTomb = struct {
    f: *Function,
    inst: Air.Inst.Index,
    lbt: Air.Liveness.BigTomb,

    fn feed(bt: *BigTomb, op_ref: Air.Inst.Ref) !void {
        const dies = bt.lbt.feed();
        if (!dies) return;
        try die(bt.f, bt.inst, op_ref);
    }
};

fn iterateBigTomb(f: *Function, inst: Air.Inst.Index) BigTomb {
    return .{
        .f = f,
        .inst = inst,
        .lbt = f.liveness.iterateBigTomb(inst),
    };
}

/// A naive clone of this map would create copies of the ArrayList which is
/// stored in the values. This function additionally clones the values.
fn cloneFreeLocalsMap(gpa: Allocator, map: *LocalsMap) !LocalsMap {
    var cloned = try map.clone(gpa);
    const values = cloned.values();
    var i: usize = 0;
    errdefer {
        cloned.deinit(gpa);
        while (i > 0) {
            i -= 1;
            values[i].deinit(gpa);
        }
    }
    while (i < values.len) : (i += 1) {
        values[i] = try values[i].clone(gpa);
    }
    return cloned;
}

fn deinitFreeLocalsMap(gpa: Allocator, map: *LocalsMap) void {
    for (map.values()) |*value| {
        value.deinit(gpa);
    }
    map.deinit(gpa);
}

fn oom(x: anytype) error{WriteFailed}!@typeInfo(@TypeOf(x)).error_union.payload {
    return x catch |err| switch (err) {
        error.OutOfMemory => error.WriteFailed,
    };
}
