#include "moar.h"

/* Maximum number of positional args we'll consider for optimization purposes. */
#define MAX_POS_ARGS 8

/* Maximum number of named args we'll consider for optimization purposes. */
#define MAX_NAMED_ARGS 8

/* Adds facts for an object arg. */
static void add_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMint32 slot,
                      MVMSpeshStatsType type_tuple_entry, MVMSpeshIns *arg_ins) {
    /* Add appropriate facts from the arg type tuple. */
    MVMint16 orig = arg_ins->operands[0].reg.orig;
    MVMint16 i = arg_ins->operands[0].reg.i;
    MVMObject *type = type_tuple_entry.type;
    g->facts[orig][i].type = type;
    g->facts[orig][i].flags |= MVM_SPESH_FACT_KNOWN_TYPE;
    if (type_tuple_entry.type_concrete)
        g->facts[orig][i].flags |= MVM_SPESH_FACT_CONCRETE;
    else
        g->facts[orig][i].flags |= MVM_SPESH_FACT_TYPEOBJ;

    /* Add any decontainerized type info. */
    if (type_tuple_entry.decont_type) {
        if (type_tuple_entry.rw_cont)
            g->facts[orig][i].flags |= MVM_SPESH_FACT_RW_CONT;
    }
}

/* Handles a pos arg that needs unboxing. */
static void pos_unbox(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
                      MVMSpeshIns *ins, const MVMOpInfo *unbox_op) {
    MVMSpeshOperand  temp  = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_obj);
    MVMSpeshIns     *unbox = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
    unbox->info            = unbox_op;
    unbox->operands        = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand));
    unbox->operands[0]     = ins->operands[0];
    unbox->operands[1]     = temp;
    ins->info              = MVM_op_get_op(MVM_OP_sp_getarg_o);
    ins->operands[0]       = temp;
    MVM_spesh_manipulate_insert_ins(tc, bb, ins, unbox);
    MVM_spesh_manipulate_release_temp_reg(tc, g, temp);
}

/* Handles a pos arg that needs boxing. */
static void pos_box(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
                    MVMSpeshIns *ins, const MVMOpInfo *hlltype_op, const MVMOpInfo *box_op,
                    const MVMOpInfo *arg_op, MVMuint8 kind) {
    MVMSpeshOperand  temp_bt, temp_arg;
    MVMSpeshIns     *hlltype, *box;

    /* Add HLL type op. */
    temp_bt              = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_obj);
    hlltype              = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
    hlltype->info        = hlltype_op;
    hlltype->operands    = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshOperand));
    hlltype->operands[0] = temp_bt;
    MVM_spesh_manipulate_insert_ins(tc, bb, ins, hlltype);

    /* Add box op. */
    temp_arg         = MVM_spesh_manipulate_get_temp_reg(tc, g, kind);
    box              = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
    box->info        = box_op;
    box->operands    = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
    box->operands[0] = ins->operands[0];
    box->operands[1] = temp_arg;
    box->operands[2] = temp_bt;
    MVM_spesh_manipulate_insert_ins(tc, bb, hlltype, box);

    /* Update instruction to receive unboxed arg. */
    ins->info        = arg_op;
    ins->operands[0] = temp_arg;

    /* Release temporary registers. */
    MVM_spesh_manipulate_release_temp_reg(tc, g, temp_bt);
    MVM_spesh_manipulate_release_temp_reg(tc, g, temp_arg);
}

/* Gets the primitive boxed by a type. */
static MVMuint16 prim_spec(MVMThreadContext *tc, MVMSpeshStatsType *type_tuple, MVMint32 i) {
    MVMObject *type = type_tuple ? type_tuple[i].type : NULL;
    const MVMStorageSpec *ss;
    if (!type)
        return 0;
    ss = REPR(type)->get_storage_spec(tc, STABLE(type));
    if (ss->boxed_primitive)
        return ss->boxed_primitive;
    return 0;
}

static MVMuint8 cmp_prim_spec(MVMThreadContext *tc, MVMSpeshStatsType *type_tuple, MVMint32 i, MVMint32 wanted_prim_spec) {
    MVMObject *type = NULL;
    MVMint32 concrete = 0;
    const MVMStorageSpec *ss;
    if (type_tuple) {
        if (type_tuple[i].decont_type) {
            type = type_tuple[i].decont_type;
            concrete = type_tuple[i].decont_type_concrete;
        }
        else {
            type = type_tuple[i].type;
            concrete = type_tuple[i].type_concrete;
        }
    }
    if (!type || !concrete)
        return 0;
    ss = REPR(type)->get_storage_spec(tc, STABLE(type));
    if (ss->boxed_primitive) {
        return ss->boxed_primitive == wanted_prim_spec;
    }
    if (ss->can_box) {
        if (wanted_prim_spec == MVM_STORAGE_SPEC_BP_INT) {
            return ss->can_box & MVM_STORAGE_SPEC_CAN_BOX_INT;
        }
        else if (wanted_prim_spec == MVM_STORAGE_SPEC_BP_UINT64) {
            return ss->can_box & MVM_STORAGE_SPEC_CAN_BOX_INT;
        }
        else if (wanted_prim_spec == MVM_STORAGE_SPEC_BP_NUM) {
            return ss->can_box & MVM_STORAGE_SPEC_CAN_BOX_NUM;
        }
        else if (wanted_prim_spec == MVM_STORAGE_SPEC_BP_STR) {
            return ss->can_box & MVM_STORAGE_SPEC_CAN_BOX_STR;
        }
        else
            MVM_panic(1, "Spesh error: unexpected wanted_prim_spec %d", wanted_prim_spec);
    }
    return 0;
}

static void insert_getarg_and_box(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
                                  MVMSpeshIns *insert_point, MVMCallsiteFlags arg_flags,
                                  MVMint32 argument_idx, MVMSpeshOperand value_temp) {
    /* Instruction to get value depends on argument type. */
    if ((arg_flags & MVM_CALLSITE_ARG_TYPE_MASK) == MVM_CALLSITE_ARG_OBJ) {
        /* It's already a boxed object, so just fetch it into the value
         * register. */
        MVMSpeshIns *fetch_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
        fetch_ins->info = MVM_op_get_op(MVM_OP_sp_getarg_o);
        fetch_ins->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand));
        fetch_ins->operands[0] = value_temp;
        fetch_ins->operands[1].lit_ui16 = argument_idx;
        MVM_spesh_manipulate_insert_ins(tc, bb, insert_point, fetch_ins);
    }
    else {
        MVMSpeshIns *box_ins, *hlltype_ins, *fetch_ins;

        /* We need to box it. Get a temporary register to box into, and one
         * for the HLL type. */
        MVMSpeshOperand unboxed_temp, box_type_temp;
        MVMuint16 box_op;
        MVMuint16 hlltype_op;
        MVMuint16 fetch_op;
        switch (arg_flags & MVM_CALLSITE_ARG_TYPE_MASK) {
            case MVM_CALLSITE_ARG_INT:
                unboxed_temp = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_int64);
                box_op = MVM_OP_box_i;
                hlltype_op = MVM_OP_hllboxtype_i;
                fetch_op = MVM_OP_sp_getarg_i;
                break;
            case MVM_CALLSITE_ARG_UINT:
                unboxed_temp = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_uint64);
                box_op = MVM_OP_box_u;
                hlltype_op = MVM_OP_hllboxtype_i;
                fetch_op = MVM_OP_sp_getarg_u;
                break;
            case MVM_CALLSITE_ARG_NUM:
                unboxed_temp = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_num64);
                box_op = MVM_OP_box_n;
                hlltype_op = MVM_OP_hllboxtype_n;
                fetch_op = MVM_OP_sp_getarg_n;
                break;
            case MVM_CALLSITE_ARG_STR:
                unboxed_temp = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_str);
                box_op = MVM_OP_box_s;
                hlltype_op = MVM_OP_hllboxtype_s;
                fetch_op = MVM_OP_sp_getarg_s;
                break;
            default:
                MVM_panic(1, "Spesh args: unexpected named argument type %d", arg_flags);
        }

        /* Emit instruction to box value. */
        box_type_temp = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_obj);
        box_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
        box_ins->info = MVM_op_get_op(box_op);
        box_ins->operands = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
        box_ins->operands[0] = value_temp;
        box_ins->operands[1] = unboxed_temp;
        box_ins->operands[2] = box_type_temp;
        MVM_spesh_manipulate_insert_ins(tc, bb, insert_point, box_ins);
        MVM_spesh_get_facts(tc, g, value_temp)->writer = box_ins;

        /* Prepend the instruction get box type. */
        hlltype_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
        hlltype_ins->info = MVM_op_get_op(hlltype_op);
        hlltype_ins->operands = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshOperand));
        hlltype_ins->operands[0] = box_type_temp;
        MVM_spesh_manipulate_insert_ins(tc, bb, insert_point, hlltype_ins);
        MVM_spesh_get_facts(tc, g, box_type_temp)->writer = hlltype_ins;

        /* Prepend fetch instruction. */
        fetch_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
        fetch_ins->info = MVM_op_get_op(fetch_op);
        fetch_ins->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand));
        fetch_ins->operands[0] = unboxed_temp;
        fetch_ins->operands[1].lit_ui16 = argument_idx;
        MVM_spesh_manipulate_insert_ins(tc, bb, insert_point, fetch_ins);
        MVM_spesh_get_facts(tc, g, unboxed_temp)->writer = fetch_ins;

        /* Can release the temporary register now. */
        MVM_spesh_manipulate_release_temp_reg(tc, g, unboxed_temp);
        MVM_spesh_manipulate_release_temp_reg(tc, g, box_type_temp);
    }
}

/* Puts a single named argument into a slurpy hash, boxing if needed. */
static void slurp_named_arg(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
                            MVMSpeshIns *hash_ins, MVMint32 named_idx) {
    MVMSpeshIns *key_ins;

    /* Look up arg flags and name, and compute index. */
    MVMCallsiteFlags flags = g->cs->arg_flags[g->cs->num_pos + named_idx];
    MVMString *name = g->cs->arg_names[named_idx];
    MVMuint16 arg_idx = g->cs->num_pos + named_idx;

    /* Allocate temporary registers for the key and value. */
    MVMSpeshOperand key_temp = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_str);
    MVMSpeshOperand value_temp = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_obj);

    /* Insert bind key instruction after slurpy hash creation instruction (we
     * do it first as below we prepend instructions to obtain the key and the
     * value. */
    MVMSpeshIns *bindkey_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
    bindkey_ins->info = MVM_op_get_op(MVM_OP_bindkey_o);
    bindkey_ins->operands = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
    bindkey_ins->operands[0] = hash_ins->operands[0];
    bindkey_ins->operands[1] = key_temp;
    bindkey_ins->operands[2] = value_temp;
    MVM_spesh_manipulate_insert_ins(tc, bb, hash_ins, bindkey_ins);

    insert_getarg_and_box(tc, g, bb, hash_ins, flags, arg_idx, value_temp);

    /* Insert key fetching instruction; we just store the string in a spesh
     * slot. */
    key_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
    key_ins->info = MVM_op_get_op(MVM_OP_sp_getspeshslot);
    key_ins->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand));
    key_ins->operands[0] = key_temp;
    key_ins->operands[1].lit_i16 = MVM_spesh_add_spesh_slot(tc, g, (MVMCollectable *)name);
    MVM_spesh_manipulate_insert_ins(tc, bb, hash_ins, key_ins);

    /* Release temporary registers after. */
    MVM_spesh_manipulate_release_temp_reg(tc, g, key_temp);
    MVM_spesh_manipulate_release_temp_reg(tc, g, value_temp);
}

static void slurp_positional_arg(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
                                 MVMSpeshIns *array_ins, MVMint32 pos_idx, MVMint32 target_idx) {
    MVMSpeshIns *index_ins;

    /* Look up arg flags and name, and compute index. */
    MVMCallsiteFlags flags = g->cs->arg_flags[pos_idx];

    /* Allocate temporary registers for the index and value. */
    MVMSpeshOperand index_temp = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_int64);
    MVMSpeshOperand value_temp = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_obj);

    /* Insert bind pos instruction after slurpy array creation instruction (we
     * do it first as below we prepend instructions to obtain the index and the
     * value. */
    MVMSpeshIns *bindpos_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
    bindpos_ins->info = MVM_op_get_op(MVM_OP_bindpos_o);
    bindpos_ins->operands = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
    bindpos_ins->operands[0] = array_ins->operands[0];
    bindpos_ins->operands[1] = index_temp;
    bindpos_ins->operands[2] = value_temp;
    MVM_spesh_manipulate_insert_ins(tc, bb, array_ins, bindpos_ins);

    insert_getarg_and_box(tc, g, bb, array_ins, flags, pos_idx, value_temp);

    /* Insert index setting instruction */
    index_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
    index_ins->info = MVM_op_get_op(MVM_OP_const_i64_16);
    index_ins->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand));
    index_ins->operands[0] = index_temp;
    index_ins->operands[1].lit_i16 = target_idx;
    MVM_spesh_manipulate_insert_ins(tc, bb, array_ins, index_ins);

    /* Release temporary registers after. */
    MVM_spesh_manipulate_release_temp_reg(tc, g, index_temp);
    MVM_spesh_manipulate_release_temp_reg(tc, g, value_temp);
}

/* Takes information about the incoming callsite and arguments, and performs
 * various optimizations based on that information. */
void MVM_spesh_args(MVMThreadContext *tc, MVMSpeshGraph *g, MVMCallsite *cs,
                    MVMSpeshStatsType *type_tuple) {
    /* We need to identify the various arg-related instructions in the graph,
     * then manipulate them as a whole. */
    MVMSpeshIns  *checkarity_ins     = NULL;
    MVMSpeshBB   *checkarity_bb      = NULL;
    MVMSpeshIns  *paramnamesused_ins = NULL;
    MVMSpeshBB   *paramnamesused_bb  = NULL;
    MVMSpeshIns  *param_sn_ins       = NULL;
    MVMSpeshBB   *param_sn_bb        = NULL;
    MVMSpeshIns  *param_sp_ins       = NULL;
    MVMSpeshBB   *param_sp_bb        = NULL;

    MVMSpeshIns **pos_ins    = MVM_calloc(MAX_POS_ARGS, sizeof(MVMSpeshIns *));
    MVMSpeshBB  **pos_bb     = MVM_calloc(MAX_POS_ARGS, sizeof(MVMSpeshBB *));
    MVMuint8     *pos_added  = MVM_calloc(MAX_POS_ARGS, sizeof(MVMuint8));
    MVMSpeshIns **named_ins  = MVM_calloc(MAX_NAMED_ARGS, sizeof(MVMSpeshIns *));
    MVMSpeshBB  **named_bb   = MVM_calloc(MAX_NAMED_ARGS, sizeof(MVMSpeshBB *));
    MVMint32      req_max    = -1;
    MVMint32      opt_min    = -1;
    MVMint32      opt_max    = -1;
    MVMint32      num_named  = 0;
    MVMint32      named_used = 0;
    MVMint32      cs_flags   = cs->flag_count;
    MVMint32    named_passed = cs_flags - cs->num_pos;

    /* We use a bit field to track named argument use; on deopt we will put it
     * into the deoptimized frame. */
    MVMuint64 named_used_bit_field = 0;

    MVMSpeshBB *bb = g->entry;
    g->cs = cs;

    /* Walk through the graph, looking for arg related instructions. */
    while (bb) {
        MVMSpeshIns *ins = bb->first_ins;
        while (ins) {
            switch (ins->info->opcode) {
            case MVM_OP_checkarity:
                if (checkarity_ins)
                    goto cleanup; /* Dupe; weird; bail out! */
                checkarity_ins = ins;
                checkarity_bb  = bb;
                break;
            case MVM_OP_param_rp_i:
            case MVM_OP_param_rp_u:
            case MVM_OP_param_rp_n:
            case MVM_OP_param_rp_s:
            case MVM_OP_param_rp_o: {
                /* Required positional. */
                MVMint16 idx = ins->operands[1].lit_i16;
                if (idx < 0 || idx >= MAX_POS_ARGS)
                    goto cleanup;
                if (pos_ins[idx]) { /* Dupe; weird. */
                    MVM_spesh_graph_add_comment(tc, g, ins,
                            "bailed argument spesh: duplicate access to argument %d", idx);
                    goto cleanup;
                }
                pos_ins[idx] = ins;
                pos_bb[idx]  = bb;
                if (idx > req_max)
                    req_max = idx;
                break;
            }
            case MVM_OP_param_op_i:
            case MVM_OP_param_op_u:
            case MVM_OP_param_op_n:
            case MVM_OP_param_op_s:
            case MVM_OP_param_op_o: {
                /* Optional Positional int/num/string/object */
                MVMint16 idx = ins->operands[1].lit_i16;
                if (idx < 0 || idx >= MAX_POS_ARGS)
                    goto cleanup;
                if (pos_ins[idx]) { /* Dupe; weird. */
                    MVM_spesh_graph_add_comment(tc, g, ins,
                            "bailed argument spesh: duplicate access to argument %d", idx);
                    goto cleanup;
                }
                pos_ins[idx] = ins;
                pos_bb[idx]  = bb;
                if (idx > opt_max)
                    opt_max = idx;
                if (opt_min == -1 || idx < opt_min)
                    opt_min = idx;
                break;
            }
            case MVM_OP_param_on_i:
            case MVM_OP_param_on_u:
            case MVM_OP_param_on_n:
            case MVM_OP_param_on_s:
            case MVM_OP_param_on_o:
            case MVM_OP_param_rn_i:
            case MVM_OP_param_rn_u:
            case MVM_OP_param_rn_n:
            case MVM_OP_param_rn_s:
            case MVM_OP_param_rn_o:
                /* Named (optional or required). */
                if (num_named == MAX_NAMED_ARGS)
                    goto cleanup;
                named_ins[num_named] = ins;
                named_bb[num_named]  = bb;
                num_named++;
                break;
            case MVM_OP_param_sp:
                if (param_sp_ins) { /* Dupe; weird. */
                    MVM_spesh_graph_add_comment(tc, g, ins,
                            "bailed argument spesh: duplicate slurpy positional argument");
                    goto cleanup;
                }
                param_sp_ins = ins;
                param_sp_bb = bb;
                break;
            case MVM_OP_param_sn:
                if (param_sn_ins) { /* Dupe; weird. */
                    MVM_spesh_graph_add_comment(tc, g, ins,
                            "bailed argument spesh: duplicate slurpy named argument");
                    goto cleanup;
                }
                param_sn_ins = ins;
                param_sn_bb = bb;
                break;
            case MVM_OP_usecapture:
            case MVM_OP_savecapture:
                /* Require full args processing context for now; bail. */
                MVM_spesh_graph_add_comment(tc, g, ins,
                        "bailed argument spesh: support for %s NYI", ins->info->name);
                goto cleanup;
            case MVM_OP_paramnamesused:
                if (paramnamesused_ins)
                    goto cleanup; /* Dupe; weird; bail out! */
                paramnamesused_ins = ins;
                paramnamesused_bb  = bb;
                break;
            case MVM_OP_param_rn2_i:
            case MVM_OP_param_rn2_n:
            case MVM_OP_param_rn2_s:
            case MVM_OP_param_rn2_o:
            case MVM_OP_param_on2_i:
            case MVM_OP_param_on2_n:
            case MVM_OP_param_on2_s:
            case MVM_OP_param_on2_o:
            case MVM_OP_param_rn2_u:
            case MVM_OP_param_on2_u:
                /* Don't understand how to specialize these yet. */
                MVM_spesh_graph_add_comment(tc, g, ins,
                        "bailed argument spesh: support for %s NYI", ins->info->name);
                goto cleanup;
            default:
                break;
            }
            ins = ins->next;
        }
        bb = bb->linear_next;
    }

    /* If we didn't find a checkarity instruction, bail. */
    if (!checkarity_ins) {
        MVM_spesh_graph_add_comment(tc, g, g->entry->first_ins,
                "bailed argument spesh: no checkarity instruction found");
        goto cleanup;
    }

    /* If required and optional aren't contiguous, bail. */
    if (opt_min >= 0 && req_max + 1 != opt_min) {
        MVM_spesh_graph_add_comment(tc, g, g->entry->first_ins,
                "bailed argument spesh: required and optional args aren't contiguous");
        goto cleanup;
    }

    /* If the number of passed args is in range... */
    if (cs->num_pos >= req_max + 1 && (opt_max < 0 || cs->num_pos <= opt_max + 1)) {
        /* Ensure we've got all the arg fetch instructions we need, and that
         * types match or it's a box/unbox. */
        MVMint32 i;
        for (i = 0; i < cs->num_pos && i < MAX_POS_ARGS; i++) {
            MVMCallsiteEntry arg_flag = cs->arg_flags[i];
            MVMCallsiteEntry arg_type = arg_flag & MVM_CALLSITE_ARG_TYPE_MASK;
            if (pos_ins[i]) {
                switch (pos_ins[i]->info->opcode) {
                case MVM_OP_param_rp_i:
                case MVM_OP_param_op_i:
                    if (arg_type != MVM_CALLSITE_ARG_INT)
                        if (arg_type != MVM_CALLSITE_ARG_OBJ ||
                                !cmp_prim_spec(tc, type_tuple, i, MVM_STORAGE_SPEC_BP_INT)) {
                            MVM_spesh_graph_add_comment(tc, g, pos_ins[i],
                                    "bailed argument spesh: expected arg flag %d to be int or box an int; type at position was %s", i, type_tuple && type_tuple[i].type ? MVM_6model_get_debug_name(tc, type_tuple[i].type) : "null type tuple");
                            goto cleanup;
                        }
                    break;
                case MVM_OP_param_rp_u:
                case MVM_OP_param_op_u:
                    if (arg_type != MVM_CALLSITE_ARG_UINT)
                        if (arg_type != MVM_CALLSITE_ARG_OBJ ||
                                !cmp_prim_spec(tc, type_tuple, i, MVM_STORAGE_SPEC_BP_UINT64)) {
                            MVM_spesh_graph_add_comment(tc, g, pos_ins[i],
                                    "bailed argument spesh: expected arg flag %d to be uint or box a uint; type at position was %s", i, type_tuple && type_tuple[i].type ? MVM_6model_get_debug_name(tc, type_tuple[i].type) : "null type tuple");
                            goto cleanup;
                        }
                    break;
                case MVM_OP_param_rp_n:
                case MVM_OP_param_op_n:
                    if (arg_type != MVM_CALLSITE_ARG_NUM)
                        if (arg_type != MVM_CALLSITE_ARG_OBJ ||
                                !cmp_prim_spec(tc, type_tuple, i, MVM_STORAGE_SPEC_BP_NUM)) {
                            MVM_spesh_graph_add_comment(tc, g, pos_ins[i],
                                    "bailed argument spesh: expected arg flag %d to be num or box a num", i);
                            goto cleanup;
                        }
                    break;
                case MVM_OP_param_rp_s:
                case MVM_OP_param_op_s:
                    if (arg_type != MVM_CALLSITE_ARG_STR)
                        if (arg_type != MVM_CALLSITE_ARG_OBJ ||
                                !cmp_prim_spec(tc, type_tuple, i, MVM_STORAGE_SPEC_BP_STR)) {
                            MVM_spesh_graph_add_comment(tc, g, pos_ins[i],
                                    "bailed argument spesh: expected arg flag %d to be str or box a str type at position was %s", i, type_tuple && type_tuple[i].type ? MVM_6model_get_debug_name(tc, type_tuple[i].type) : "null type tuple");
                            goto cleanup;
                        }
                    break;
                case MVM_OP_param_rp_o:
                case MVM_OP_param_op_o:
                    if (arg_type != MVM_CALLSITE_ARG_OBJ && arg_type != MVM_CALLSITE_ARG_INT &&
                        arg_type != MVM_CALLSITE_ARG_NUM && arg_type != MVM_CALLSITE_ARG_STR &&
                        arg_type != MVM_CALLSITE_ARG_UINT) {
                        MVM_spesh_graph_add_comment(tc, g, pos_ins[i],
                                "bailed argument spesh: expected arg flag %d to be obj or int or num or str", i);
                        goto cleanup;
                    }
                    break;
                default:
                    break;
                }
            }
            else if (!pos_ins[i] && !param_sp_ins) {
                MVM_spesh_graph_add_comment(tc, g, g->entry->first_ins,
                        "bailed argument spesh: no positional arg fetch op found for %d", i);
                goto cleanup;
            }
        }

        /* Check the argument types of named arguments is something we know how
         * to handle. */
        for (i = 0; i < num_named; i++) {
            /* Get the flag of the passed arg, if any. */
            MVMString *arg_name       = MVM_spesh_get_string(tc, g, named_ins[i]->operands[1]);
            MVMint32   cur_idx        = 0;
            MVMint32   cur_named      = 0;
            MVMuint8   found_flag     = 0;
            MVMint32   found_flag_idx = -1;
            MVMint32   found_idx      = -1;
            MVMint32   j;
            for (j = 0; j < cs_flags; j++) {
                if (cs->arg_flags[j] & MVM_CALLSITE_ARG_NAMED) {
                    if (MVM_string_equal(tc, arg_name, cs->arg_names[cur_named])) {
                        /* Found it. */
                        found_flag_idx = j;
                        found_flag = cs->arg_flags[j] & MVM_CALLSITE_ARG_TYPE_MASK;
                        found_idx  = cur_idx;
                        break;
                    }
                    cur_named++;
                }
                cur_idx++;
            }

            /* Now go by op. */
            switch (named_ins[i]->info->opcode) {
            case MVM_OP_param_rn_i:
                if (found_idx == -1) {
                    MVM_spesh_graph_add_comment(tc, g, named_ins[i],
                            "bailed argument spesh: required named argument not found!");
                    goto cleanup;
                }
                if (!(found_flag & MVM_CALLSITE_ARG_INT)
                        && !(found_flag & MVM_CALLSITE_ARG_OBJ
                            && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_INT)) {
                    MVM_spesh_graph_add_comment(tc, g, named_ins[i],
                            "bailed argument spesh: unhandled coercion");
                    goto cleanup;
                }
                break;
            case MVM_OP_param_rn_u:
                if (found_idx == -1) {
                    MVM_spesh_graph_add_comment(tc, g, named_ins[i],
                            "bailed argument spesh: required named argument not found!");
                    goto cleanup;
                }
                if (!(found_flag & MVM_CALLSITE_ARG_UINT)
                        && !(found_flag & MVM_CALLSITE_ARG_OBJ
                            && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_UINT64)) {
                    MVM_spesh_graph_add_comment(tc, g, named_ins[i],
                            "bailed argument spesh: unhandled coercion");
                    goto cleanup;
                }
                break;
            case MVM_OP_param_rn_n:
                if (found_idx == -1) {
                    MVM_spesh_graph_add_comment(tc, g, named_ins[i],
                            "bailed argument spesh: required named argument not found!");
                    goto cleanup;
                }
                if (!(found_flag & MVM_CALLSITE_ARG_NUM)
                        && !(found_flag & MVM_CALLSITE_ARG_OBJ
                            && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_NUM)) {
                    MVM_spesh_graph_add_comment(tc, g, named_ins[i],
                            "bailed argument spesh: unhandled coercion");
                    goto cleanup;
                }
                break;
            case MVM_OP_param_rn_s:
                if (found_idx == -1) {
                    MVM_spesh_graph_add_comment(tc, g, named_ins[i],
                            "bailed argument spesh: required named argument not found!");
                    goto cleanup;
                }
                if (!(found_flag & MVM_CALLSITE_ARG_STR)
                        && !(found_flag & MVM_CALLSITE_ARG_OBJ
                            && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_STR)) {
                    MVM_spesh_graph_add_comment(tc, g, named_ins[i],
                            "bailed argument spesh: unhandled coercion");
                    goto cleanup;
                }
                break;
            case MVM_OP_param_rn_o:
                if (found_idx == -1) {
                    MVM_spesh_graph_add_comment(tc, g, named_ins[i],
                            "bailed argument spesh: required named argument not found!");
                    goto cleanup;
                }
                break;
            case MVM_OP_param_on_i:
                if (found_idx != -1 && !(found_flag & MVM_CALLSITE_ARG_INT)
                        && !(found_flag & MVM_CALLSITE_ARG_OBJ
                            && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_INT)) {
                    MVM_spesh_graph_add_comment(tc, g, named_ins[i],
                            "bailed argument spesh: unhandled coercion");
                    goto cleanup;
                }
                break;
            case MVM_OP_param_on_u:
                if (found_idx != -1 && !(found_flag & MVM_CALLSITE_ARG_UINT)
                        && !(found_flag & MVM_CALLSITE_ARG_OBJ
                            && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_UINT64)) {
                    MVM_spesh_graph_add_comment(tc, g, named_ins[i],
                            "bailed argument spesh: unhandled coercion");
                    goto cleanup;
                }
                break;
            case MVM_OP_param_on_n:
                if (found_idx != -1 && !(found_flag & MVM_CALLSITE_ARG_NUM)
                        && !(found_flag & MVM_CALLSITE_ARG_OBJ
                            && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_NUM)) {
                    MVM_spesh_graph_add_comment(tc, g, named_ins[i],
                            "bailed argument spesh: unhandled coercion");
                    goto cleanup;
                }
                break;
            case MVM_OP_param_on_s:
                if (found_idx != -1 && !(found_flag & MVM_CALLSITE_ARG_STR)
                        && !(found_flag & MVM_CALLSITE_ARG_OBJ
                            && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_STR)) {
                    MVM_spesh_graph_add_comment(tc, g, named_ins[i],
                            "bailed argument spesh: unhandled coercion");
                    goto cleanup;
                }
                break;
            case MVM_OP_param_on_o:
                /* Everything can coerce to obj, so don't need to do anything, but leaving this
                 * case here for completeness. */
            default:
                break;
            }
        }

        /* We can optimize. Toss checkarity. */
        MVM_spesh_manipulate_delete_ins(tc, g, checkarity_bb, checkarity_ins);

        /* Re-write the passed required positionals to spesh ops, and add any
         * facts. */
        for (i = 0; i < cs->num_pos && i < MAX_POS_ARGS; i++) {
            MVMCallsiteEntry arg_flag = cs->arg_flags[i];
            MVMCallsiteEntry arg_type = arg_flag & MVM_CALLSITE_ARG_TYPE_MASK;
            if (!pos_ins[i])
                continue;
            switch (pos_ins[i]->info->opcode) {
            case MVM_OP_param_rp_i:
            case MVM_OP_param_op_i:
                if (arg_type == MVM_CALLSITE_ARG_INT) {
                    pos_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_i);
                }
                else {
                    pos_unbox(tc, g, pos_bb[i], pos_ins[i], MVM_op_get_op(MVM_OP_decont_i));
                    pos_added[i]++;
                    if (type_tuple && type_tuple[i].type)
                        add_facts(tc, g, i, type_tuple[i], pos_ins[i]);
                }
                break;
            case MVM_OP_param_rp_u:
            case MVM_OP_param_op_u:
                if (arg_type == MVM_CALLSITE_ARG_UINT) {
                    pos_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_u);
                }
                else {
                    pos_unbox(tc, g, pos_bb[i], pos_ins[i], MVM_op_get_op(MVM_OP_decont_u));
                    pos_added[i]++;
                    if (type_tuple && type_tuple[i].type)
                        add_facts(tc, g, i, type_tuple[i], pos_ins[i]);
                }
                break;
            case MVM_OP_param_rp_n:
            case MVM_OP_param_op_n:
                if (arg_type == MVM_CALLSITE_ARG_NUM) {
                    pos_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_n);
                }
                else {
                    pos_unbox(tc, g, pos_bb[i], pos_ins[i], MVM_op_get_op(MVM_OP_decont_n));
                    pos_added[i]++;
                    if (type_tuple && type_tuple[i].type)
                        add_facts(tc, g, i, type_tuple[i], pos_ins[i]);
                }
                break;
            case MVM_OP_param_rp_s:
            case MVM_OP_param_op_s:
                if (arg_type == MVM_CALLSITE_ARG_STR) {
                    pos_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_s);
                }
                else {
                    pos_unbox(tc, g, pos_bb[i], pos_ins[i], MVM_op_get_op(MVM_OP_decont_s));
                    pos_added[i]++;
                    if (type_tuple && type_tuple[i].type)
                        add_facts(tc, g, i, type_tuple[i], pos_ins[i]);
                }
                break;
            case MVM_OP_param_rp_o:
            case MVM_OP_param_op_o:
                if (arg_type == MVM_CALLSITE_ARG_OBJ) {
                    pos_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_o);
                    if (type_tuple && type_tuple[i].type) {
                        add_facts(tc, g, i, type_tuple[i], pos_ins[i]);
                        if (i == 0)
                            g->specialized_on_invocant = 1;
                    }
                }
                else if (arg_type == MVM_CALLSITE_ARG_INT) {
                    pos_box(tc, g, pos_bb[i], pos_ins[i],
                        MVM_op_get_op(MVM_OP_hllboxtype_i), MVM_op_get_op(MVM_OP_box_i),
                        MVM_op_get_op(MVM_OP_sp_getarg_i), MVM_reg_int64);
                    pos_added[i] += 2;
                }
                else if (arg_type == MVM_CALLSITE_ARG_UINT) {
                    pos_box(tc, g, pos_bb[i], pos_ins[i],
                        MVM_op_get_op(MVM_OP_hllboxtype_i), MVM_op_get_op(MVM_OP_box_u),
                        MVM_op_get_op(MVM_OP_sp_getarg_u), MVM_reg_uint64);
                    pos_added[i] += 2;
                }
                else if (arg_type == MVM_CALLSITE_ARG_NUM) {
                    pos_box(tc, g, pos_bb[i], pos_ins[i],
                        MVM_op_get_op(MVM_OP_hllboxtype_n), MVM_op_get_op(MVM_OP_box_n),
                        MVM_op_get_op(MVM_OP_sp_getarg_n), MVM_reg_num64);
                    pos_added[i] += 2;
                }
                else if (arg_type == MVM_CALLSITE_ARG_STR) {
                    pos_box(tc, g, pos_bb[i], pos_ins[i],
                        MVM_op_get_op(MVM_OP_hllboxtype_s), MVM_op_get_op(MVM_OP_box_s),
                        MVM_op_get_op(MVM_OP_sp_getarg_s), MVM_reg_str);
                    pos_added[i] += 2;
                }
                break;
            default:
                break;
            }
            pos_ins[i]->operands[1].lit_i16 = (MVMint16)i;
        }

        /* Now consider any optionals. */
        if (opt_min >= 0) {
            for (i = opt_min; i <= opt_max; i++) {
                MVMuint8 passed = i < cs->num_pos;
                if (passed) {
                    /* If we know the argument has been passed, then add a goto
                     * to the "passed" code. */
                    MVMSpeshIns *after = pos_ins[i];
                    while (pos_added[i]--)
                        after = after->next;
                    MVM_spesh_manipulate_insert_goto(tc, g, pos_bb[i], after,
                        pos_ins[i]->operands[2].ins_bb);

                    /* Inserting an unconditional goto makes the linear_next BB
                    * unreachable, so we remove it from the succ list. */
                    MVM_spesh_manipulate_remove_successor(tc, pos_bb[i],
                        pos_bb[i]->linear_next);
                } else {
                    /* If we didn't pass this, just fall through the original
                    * operation and we'll get the default value set. */
                    MVM_spesh_manipulate_delete_ins(tc, g, pos_bb[i], pos_ins[i]);
                    MVM_spesh_manipulate_remove_successor(tc, pos_bb[i],
                        pos_ins[i]->operands[2].ins_bb);
                }
            }
        }

        /* Now consider any nameds. */
        for (i = 0; i < num_named; i++) {
            /* See if the arg was passed. */
            MVMString *arg_name       = MVM_spesh_get_string(tc, g, named_ins[i]->operands[1]);
            MVMint32   cur_idx        = 0;
            MVMint32   cur_named      = 0;
            MVMuint8   found_flag     = 0;
            MVMint32   found_idx      = -1;
            MVMint32   found_flag_idx = -1;
            MVMint32   j;
            for (j = 0; j < cs_flags; j++) {
                if (cs->arg_flags[j] & MVM_CALLSITE_ARG_NAMED) {
                    if (MVM_string_equal(tc, arg_name, cs->arg_names[cur_named])) {
                        /* Found it. */
                        found_flag_idx = j;
                        found_flag = cs->arg_flags[j];
                        found_idx  = cur_idx;
                        break;
                    }
                    cur_named++;
                }
                cur_idx++;
            }

            /* Now go by instruction. */
            switch (named_ins[i]->info->opcode) {
            case MVM_OP_param_rn_i:
                if (found_flag & MVM_CALLSITE_ARG_INT) {
                    named_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_i);
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                }
                else if (found_flag & MVM_CALLSITE_ARG_OBJ
                        && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_INT) {
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    pos_unbox(tc, g, named_bb[i], named_ins[i], MVM_op_get_op(MVM_OP_decont_i));
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                }
                named_used++;
                break;
            case MVM_OP_param_rn_u:
                if (found_flag & MVM_CALLSITE_ARG_UINT) {
                    named_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_u);
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                }
                else if (found_flag & MVM_CALLSITE_ARG_OBJ
                        && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_UINT64) {
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    pos_unbox(tc, g, named_bb[i], named_ins[i], MVM_op_get_op(MVM_OP_decont_u));
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                }
                named_used++;
                break;
            case MVM_OP_param_rn_n:
                if (found_flag & MVM_CALLSITE_ARG_NUM) {
                    named_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_n);
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                }
                else if (found_flag & MVM_CALLSITE_ARG_OBJ
                        && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_NUM) {
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    pos_unbox(tc, g, named_bb[i], named_ins[i], MVM_op_get_op(MVM_OP_decont_n));
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                }
                named_used++;
                break;
            case MVM_OP_param_rn_s:
                if (found_flag & MVM_CALLSITE_ARG_STR) {
                    named_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_s);
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                }
                else if (found_flag & MVM_CALLSITE_ARG_OBJ
                        && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_STR) {
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    pos_unbox(tc, g, named_bb[i], named_ins[i], MVM_op_get_op(MVM_OP_decont_s));
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                }
                named_used++;
                break;
            case MVM_OP_param_rn_o:
                if (found_flag & MVM_CALLSITE_ARG_OBJ) {
                    MVMuint16 arg_idx = found_idx;
                    named_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_o);
                    named_ins[i]->operands[1].lit_i16 = arg_idx;
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                    if (type_tuple && type_tuple[found_flag_idx].type)
                        add_facts(tc, g, arg_idx, type_tuple[found_flag_idx], named_ins[i]);
                }
                else if (found_flag & (MVM_CALLSITE_ARG_INT | MVM_CALLSITE_ARG_UINT | MVM_CALLSITE_ARG_NUM | MVM_CALLSITE_ARG_STR)) {
                    MVMuint16 arg_idx = found_idx;
                    named_ins[i]->operands[1].lit_i16 = arg_idx;
                    if (found_flag & MVM_CALLSITE_ARG_INT)
                        pos_box(tc, g, named_bb[i], named_ins[i],
                            MVM_op_get_op(MVM_OP_hllboxtype_i), MVM_op_get_op(MVM_OP_box_i),
                            MVM_op_get_op(MVM_OP_sp_getarg_i), MVM_reg_int64);
                    else if (found_flag & MVM_CALLSITE_ARG_UINT)
                        pos_box(tc, g, named_bb[i], named_ins[i],
                            MVM_op_get_op(MVM_OP_hllboxtype_i), MVM_op_get_op(MVM_OP_box_u),
                            MVM_op_get_op(MVM_OP_sp_getarg_u), MVM_reg_uint64);
                    else if (found_flag & MVM_CALLSITE_ARG_NUM)
                        pos_box(tc, g, named_bb[i], named_ins[i],
                            MVM_op_get_op(MVM_OP_hllboxtype_n), MVM_op_get_op(MVM_OP_box_n),
                            MVM_op_get_op(MVM_OP_sp_getarg_n), MVM_reg_num64);
                    else if (found_flag & MVM_CALLSITE_ARG_STR)
                        pos_box(tc, g, named_bb[i], named_ins[i],
                            MVM_op_get_op(MVM_OP_hllboxtype_s), MVM_op_get_op(MVM_OP_box_s),
                            MVM_op_get_op(MVM_OP_sp_getarg_s), MVM_reg_str);
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                }
                named_used++;
                break;
            case MVM_OP_param_on_i:
                if (found_idx == -1) {
                    MVM_spesh_manipulate_delete_ins(tc, g, named_bb[i], named_ins[i]);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i], named_ins[i]->operands[2].ins_bb);
                }
                else if (found_flag & MVM_CALLSITE_ARG_INT) {
                    named_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_i);
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    MVM_spesh_manipulate_insert_goto(tc, g, named_bb[i], named_ins[i],
                        named_ins[i]->operands[2].ins_bb);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i],
                        named_bb[i]->linear_next);
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                    named_used++;
                }
                else if (found_flag & MVM_CALLSITE_ARG_OBJ
                        && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_INT) {
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    pos_unbox(tc, g, named_bb[i], named_ins[i], MVM_op_get_op(MVM_OP_decont_i));
                    MVM_spesh_manipulate_insert_goto(tc, g, named_bb[i], named_ins[i]->next,
                        named_ins[i]->operands[2].ins_bb);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i],
                        named_bb[i]->linear_next);
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                    named_used++;
                }
                break;
            case MVM_OP_param_on_u:
                if (found_idx == -1) {
                    MVM_spesh_manipulate_delete_ins(tc, g, named_bb[i], named_ins[i]);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i], named_ins[i]->operands[2].ins_bb);
                }
                else if (found_flag & MVM_CALLSITE_ARG_UINT) {
                    named_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_u);
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    MVM_spesh_manipulate_insert_goto(tc, g, named_bb[i], named_ins[i],
                        named_ins[i]->operands[2].ins_bb);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i],
                        named_bb[i]->linear_next);
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                    named_used++;
                }
                else if (found_flag & MVM_CALLSITE_ARG_OBJ
                        && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_UINT64) {
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    pos_unbox(tc, g, named_bb[i], named_ins[i], MVM_op_get_op(MVM_OP_decont_u));
                    MVM_spesh_manipulate_insert_goto(tc, g, named_bb[i], named_ins[i]->next,
                        named_ins[i]->operands[2].ins_bb);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i],
                        named_bb[i]->linear_next);
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                    named_used++;
                }
                break;
            case MVM_OP_param_on_n:
                if (found_idx == -1) {
                    MVM_spesh_manipulate_delete_ins(tc, g, named_bb[i], named_ins[i]);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i], named_ins[i]->operands[2].ins_bb);
                }
                else if (found_flag & MVM_CALLSITE_ARG_NUM) {
                    named_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_n);
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    MVM_spesh_manipulate_insert_goto(tc, g, named_bb[i], named_ins[i],
                        named_ins[i]->operands[2].ins_bb);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i],
                        named_bb[i]->linear_next);
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                    named_used++;
                }
                else if (found_flag & MVM_CALLSITE_ARG_OBJ
                        && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_NUM) {
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    pos_unbox(tc, g, named_bb[i], named_ins[i], MVM_op_get_op(MVM_OP_decont_n));
                    MVM_spesh_manipulate_insert_goto(tc, g, named_bb[i], named_ins[i]->next,
                        named_ins[i]->operands[2].ins_bb);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i],
                        named_bb[i]->linear_next);
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                    named_used++;
                }
                break;
            case MVM_OP_param_on_s:
                if (found_idx == -1) {
                    MVM_spesh_manipulate_delete_ins(tc, g, named_bb[i], named_ins[i]);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i], named_ins[i]->operands[2].ins_bb);
                }
                else if (found_flag & MVM_CALLSITE_ARG_STR) {
                    named_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_s);
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    MVM_spesh_manipulate_insert_goto(tc, g, named_bb[i], named_ins[i],
                        named_ins[i]->operands[2].ins_bb);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i],
                        named_bb[i]->linear_next);
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                    named_used++;
                }
                else if (found_flag & MVM_CALLSITE_ARG_OBJ
                        && prim_spec(tc, type_tuple, found_flag_idx) == MVM_STORAGE_SPEC_BP_STR) {
                    named_ins[i]->operands[1].lit_i16 = found_idx;
                    pos_unbox(tc, g, named_bb[i], named_ins[i], MVM_op_get_op(MVM_OP_decont_s));
                    MVM_spesh_manipulate_insert_goto(tc, g, named_bb[i], named_ins[i]->next,
                        named_ins[i]->operands[2].ins_bb);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i],
                        named_bb[i]->linear_next);
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                    named_used++;
                }
                break;
            case MVM_OP_param_on_o:
                if (found_idx == -1) {
                    MVM_spesh_manipulate_delete_ins(tc, g, named_bb[i], named_ins[i]);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i], named_ins[i]->operands[2].ins_bb);
                }
                else if (found_flag & MVM_CALLSITE_ARG_OBJ) {
                    MVMuint16 arg_idx = found_idx;
                    named_ins[i]->info = MVM_op_get_op(MVM_OP_sp_getarg_o);
                    named_ins[i]->operands[1].lit_i16 = arg_idx;
                    MVM_spesh_manipulate_insert_goto(tc, g, named_bb[i], named_ins[i],
                        named_ins[i]->operands[2].ins_bb);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i],
                        named_bb[i]->linear_next);
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                    if (type_tuple && type_tuple[found_flag_idx].type)
                        add_facts(tc, g, arg_idx, type_tuple[found_flag_idx], named_ins[i]);
                    named_used++;
                }
                else if (found_flag & (MVM_CALLSITE_ARG_INT | MVM_CALLSITE_ARG_UINT | MVM_CALLSITE_ARG_NUM | MVM_CALLSITE_ARG_STR)) {
                    MVMuint16 arg_idx = found_idx;
                    named_ins[i]->operands[1].lit_i16 = arg_idx;
                    if (found_flag & MVM_CALLSITE_ARG_INT)
                        pos_box(tc, g, named_bb[i], named_ins[i],
                            MVM_op_get_op(MVM_OP_hllboxtype_i), MVM_op_get_op(MVM_OP_box_i),
                            MVM_op_get_op(MVM_OP_sp_getarg_i), MVM_reg_int64);
                    else if (found_flag & MVM_CALLSITE_ARG_UINT)
                        pos_box(tc, g, named_bb[i], named_ins[i],
                            MVM_op_get_op(MVM_OP_hllboxtype_i), MVM_op_get_op(MVM_OP_box_u),
                            MVM_op_get_op(MVM_OP_sp_getarg_u), MVM_reg_uint64);
                    else if (found_flag & MVM_CALLSITE_ARG_NUM)
                        pos_box(tc, g, named_bb[i], named_ins[i],
                            MVM_op_get_op(MVM_OP_hllboxtype_n), MVM_op_get_op(MVM_OP_box_n),
                            MVM_op_get_op(MVM_OP_sp_getarg_n), MVM_reg_num64);
                    else if (found_flag & MVM_CALLSITE_ARG_STR)
                        pos_box(tc, g, named_bb[i], named_ins[i],
                            MVM_op_get_op(MVM_OP_hllboxtype_s), MVM_op_get_op(MVM_OP_box_s),
                            MVM_op_get_op(MVM_OP_sp_getarg_s), MVM_reg_str);
                    MVM_spesh_manipulate_insert_goto(tc, g, named_bb[i], named_ins[i]->next->next,
                        named_ins[i]->operands[2].ins_bb);
                    MVM_spesh_manipulate_remove_successor(tc, named_bb[i],
                        named_bb[i]->linear_next);
                    named_used_bit_field |= (MVMuint64)1 << cur_named;
                    named_used++;
                }
                break;
            default:
                break;
            }
        }

        /* If we have an instruction to check all nameds were used... */
        if (paramnamesused_ins) {
            /* Delete it if they were. */
            if (named_passed == named_used) {
                MVM_spesh_manipulate_delete_ins(tc, g, paramnamesused_bb, paramnamesused_ins);
            }

            /* Otherwise, we have unexpected named arguments. Turn it into an
             * error. */
            else {
                MVMuint16 i;
                for (i = 0; i < named_passed; i++) {
                    if (!(named_used_bit_field & ((MVMuint64)1 << i))) {
                        paramnamesused_ins->info = MVM_op_get_op(MVM_OP_sp_paramnamesused);
                        paramnamesused_ins->operands = MVM_spesh_alloc(tc,
                            g, sizeof(MVMSpeshOperand));
                        paramnamesused_ins->operands[0].lit_i16 = MVM_spesh_add_spesh_slot(tc,
                            g, (MVMCollectable *)g->cs->arg_names[i]);
                        break;
                    }
                }
            }
        }

        /* If we have a slurpy hash and it's not too huge ... */
        if (param_sn_ins && named_passed < 2000) {
            /* Construct it as a hash. */
            MVMObject *hash_type = g->sf->body.cu->body.hll_config->slurpy_hash_type;
            if (REPR(hash_type)->ID == MVM_REPR_ID_MVMHash && !REPR(hash_type)->initialize) {
                MVMSpeshOperand target    = param_sn_ins->operands[0];
                param_sn_ins->info        = MVM_op_get_op(MVM_OP_sp_fastcreate);
                param_sn_ins->operands    = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
                param_sn_ins->operands[0] = target;
                param_sn_ins->operands[1].lit_i16 = sizeof(MVMHash);
                param_sn_ins->operands[2].lit_i16 = MVM_spesh_add_spesh_slot(tc, g,
                    (MVMCollectable *)STABLE(hash_type));
                MVM_spesh_graph_add_comment(tc, g, param_sn_ins, "argument spesh: slurpy nameds optimized");
            }
            else {
                MVM_oops(tc, "Arg spesh: slurpy hash type was not a VMHash as expected");
            }

            /* Populate it with unused named args, if needed, boxing them on
             * the way. */
            if (named_passed > named_used)
                for (i = 0; i < named_passed; i++)
                    if (!(named_used_bit_field & ((MVMuint64)1 << i)))
                        slurp_named_arg(tc, g, param_sn_bb, param_sn_ins, i);
        }
        else if (named_passed >= 2000) {
            MVM_spesh_graph_add_comment(tc, g, param_sn_ins, "argument spesh: refused to optimize with %d > 2000 named args", named_passed);
        }

        /* If we have a slurpy array and it's not too huge ... */
        if (param_sp_ins && cs->num_pos < 2000) {
            MVMint32 start_slurping_at = param_sp_ins->operands[1].lit_i16;
            /* Construct it as an array. */
            MVMObject *array_type = g->sf->body.cu->body.hll_config->slurpy_array_type;
            if (REPR(array_type)->ID == MVM_REPR_ID_VMArray && !REPR(array_type)->initialize) {
                MVMSpeshOperand target    = param_sp_ins->operands[0];
                param_sp_ins->info        = MVM_op_get_op(MVM_OP_sp_fastcreate);
                param_sp_ins->operands    = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
                param_sp_ins->operands[0] = target;
                param_sp_ins->operands[1].lit_i16 = sizeof(MVMArray);
                param_sp_ins->operands[2].lit_i16 = MVM_spesh_add_spesh_slot(tc, g,
                    (MVMCollectable *)STABLE(array_type));
                MVM_spesh_graph_add_comment(tc, g, param_sp_ins, "argument spesh: slurpy positional optimized");
            }
            else {
                MVM_oops(tc, "Arg spesh: slurpy array type was not a VMArray as expected");
            }

            /* Populate it with all args after the numeral argument */
            for (i = cs->num_pos - 1; i >= start_slurping_at; i--)
                slurp_positional_arg(tc, g, param_sp_bb, param_sp_ins, i, i - start_slurping_at);
        }
        else if (cs->num_pos >= 2000) {
            MVM_spesh_graph_add_comment(tc, g, param_sp_ins, "argument spesh: refused to optimize with %d > 2000 positional args", cs->num_pos);
        }

        /* Stash the named used bit field in the graph; will need to make it
         * into the candidate and all the way to deopt. */
        g->deopt_named_used_bit_field = named_used_bit_field;
    }

  cleanup:
    MVM_free(pos_ins);
    MVM_free(pos_bb);
    MVM_free(pos_added);
    MVM_free(named_ins);
    MVM_free(named_bb);
}
