 tarray, &t));
        case IR_TYPE_tslice:
            return (char *)SLICE_MANGLE;
        default:
            assert(false);
    }
}

// Define all referenced function types.
static void do_fn_types(CTX *ctx, struct ir_fn_type *fnt)
{
    def_type(ctx, fnt->ret_type);
    for (int n = 0; n < fnt->args->members_count; n++)
        def_type(ctx, fnt->args->members[n]->type);
}

static char *def_nested_fn(CTX *ctx, struct ir_function *fn)
{
    char *m = get_mangle(ctx, fn);
    if (m)
        return m;
    assert(ctx->writing_types);
    m = gen_anon_mangle(ctx, "nested_fn");
    add_mangle(ctx, fn, m);
    do_fn_types(ctx, fn->type);
    fprintf(ctx->f, "static ");
    write_fn_type(ctx, fn->type, false, m);
    fprintf(ctx->f, ";\n");
    return m;
}

static char *link_name(CTX *ctx, struct ir_link_name name)
{
    return talloc_asprintf(ctx, "%s%s%s",
                           // For now, out all functions not meant to be
                           // visible by C code into their own namespace.
                           name.is_c? "" : (MANGLE_PREFIX "f_"),
                           // invisible => make name different from possible
                           //              same-named global functions?
                           name.visible? "" : "inv_",
                           name.name);
}

static char *def_fn(CTX *ctx, struct ir_fn_decl *fn)
{
    if (fn->body && fn->body->parent)
        return def_nested_fn(ctx, fn->body);
    char *m = get_mangle(ctx, fn);
    if (m)
        return m;
    assert(ctx->writing_types);
    m = link_name(ctx, fn->name);
    add_mangle(ctx, fn, m);
    do_fn_types(ctx, fn->type);
    if (!fn->name.visible)
        fprintf(ctx->f, "static ");
    write_fn_type(ctx, fn->type, false, m);
    fprintf(ctx->f, ";\n");
    return m;
}

static const char *int_consts[] = {
    "UINT8_C", "UINT16_C", "UINT32_C", "UINT64_C",
    "INT8_C", "INT16_C", "INT32_C", "INT64_C",
};

static char *int_const(struct ir_type t)
{
    return (char *)int_consts[type_int_order(t) + (type_get_sign(t)? 4 : 0)];
}

static void write_const(CTX *ctx, struct ir_const_val v)
{
    char *s = const_unparse(NULL, v);
    if (TEST_UNION(IR_TYPE, tarray, &v.type)) {
        fprintf(ctx->f, "{%s}", s);
    } else if (type_is_integer(v.type)) {
        // Get rid of annoying-but-correct gcc warning. This happens because we
        // negate a value not representable in intmax_t, even though the result
        // is representable, i.e. the only case is INT64_MIN.
        if (type_equals(v.type, type_integer(true, 64))
            && *GET_UNION(VALUE, vuint64, &v.value) == INT64_MIN)
        {
            fprintf(ctx->f, "INT64_MIN");
        } else {
            fprintf(ctx->f, "%s(%s)", int_const(v.type), s);
        }
    } else if (TEST_UNION(IR_TYPE, tslice, &v.type)) {
        if (TEST_UNION0(VALUE, vempty, &v.value)) {
            fprintf(ctx->f, "{0}");
        } else if (type_equals(v.type, TYPE_STRING)) {
            char *raw = *GET_UNION(VALUE, vstring, &v.value);
            fprintf(ctx->f, "{ %s, %zd }", s, raw? strlen(raw) : 0);
        } else {
            assert(false);
        }
    } else {
        fprintf(ctx->f, "%s", s);
    }
    talloc_free(s);
}

static const char *optable[] = {
    [IR_OP_ADD] = "+",
    [IR_OP_SUB] = "-",
    [IR_OP_MUL] = "*",
    [IR_OP_DIV] = "/",
    [IR_OP_MOD] = "%",
    [IR_OP_AND] = "&",
    [IR_OP_OR] = "|",
    [IR_OP_XOR] = "^",
    [IR_OP_SHIFT_R] = ">>",
    [IR_OP_SHIFT_L] = "<<",
    [IR_OP_EQ] = "==",
    [IR_OP_NOT_EQ] = "!=",
    [IR_OP_CMP_LT] = "<",
    [IR_OP_CMP_GT] = ">",
    [IR_OP_CMP_LT_EQ] = "<=",
    [IR_OP_CMP_GT_EQ] = ">=",
};

static char *get_temp(CTX *ctx, int n)
{
    assert(n >= 0);
    while (n >= ctx->tempnames_count) {
        char *s = talloc_asprintf(ctx, "T%d", ctx->tempnames_count);
        BL_TARRAY_APPEND(ctx, ctx->tempnames, ctx->tempnames_count, s);
    }
    return ctx->tempnames[n];
}

static char *gen_inst_inline(CTX *ctx, struct ir_inst *in);

static char *R(CTX *ctx, struct ir_inst *in, int i)
{
    struct ir_inst *r = inst_getuse(in, i);
    if (type_is_void(r->result_type))
        return VOID_VAL;
    if (r->scratch1_i < 0) {
        return gen_inst_inline(ctx, r);
    } else {
        return get_temp(ctx, r->scratch1_i);
    }
}

static int BR(struct ir_inst *in, int i)
{
    return in->branch[i]->index;
}

static void print_reads(CTX *ctx, struct ir_inst *in, int first)
{
    for (int n = first; n < in->read_count; n++) {
        if (n > first)
            P(ctx, ", ");
        P(ctx, "%s", R(ctx, in, n));
    }
}

#define R0 R(ctx, in, 0)
#define R1 R(ctx, in, 1)
static void write_inst(CTX *ctx, struct ir_inst *in, bool inner)
{
    if (in->comment)
        P(ctx, " /* COMMENT: %s */ ", in->comment);
    // @ALL ir_opcode
    switch (in->op) {
        case IR_OP_NOP:
            break;
        case IR_OP_COPY:
            P(ctx, "%s", R0);
            break;
        case IR_OP_GOTO:
            P(ctx, "goto B%d", BR(in, 0));
            break;
        case IR_OP_BRANCH:
            P(ctx, "if (%s) goto B%d; else goto B%d", R0, BR(in, 1), BR(in, 0));
            break;
        case IR_OP_RET:
            if (type_is_void(inst_getuse(in, 0)->result_type)) {
                P(ctx, "return");
            } else {
                P(ctx, "return %s", R0);
            }
            break;
        case IR_OP_ABORT