#include "moar.h"

/* The code in this file walks the spesh graph, recording facts we discover
 * about each version of each local variable, and propagating the info as it
 * can. */

/* Copies facts from one var to another. */
static void copy_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMuint16 to_orig,
                       MVMuint16 to_i, MVMuint16 from_orig, MVMuint16 from_i) {
    MVMSpeshFacts *tfacts = &g->facts[to_orig][to_i];
    MVMSpeshFacts *ffacts = &g->facts[from_orig][from_i];
    tfacts->flags         = ffacts->flags;
    tfacts->type          = ffacts->type;
    tfacts->decont_type   = ffacts->decont_type;
    tfacts->value         = ffacts->value;
    tfacts->log_guards    = ffacts->log_guards;
    tfacts->num_log_guards = ffacts->num_log_guards;
}

/* Called when one set of facts depend on another, allowing any log guard
 * that is to thank to be marked used as needed later on. */
void MVM_spesh_facts_depend(MVMThreadContext *tc, MVMSpeshGraph *g,
                            MVMSpeshFacts *target, MVMSpeshFacts *source) {
    target->log_guards = source->log_guards;
    target->num_log_guards = source->num_log_guards;
}

/* Handles object-creating instructions. */
static void create_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMuint16 obj_orig,
                         MVMuint16 obj_i, MVMuint16 type_orig, MVMuint16 type_i) {
    MVMSpeshFacts *type_facts = &(g->facts[type_orig][type_i]);
    MVMSpeshFacts *obj_facts  = &(g->facts[obj_orig][obj_i]);

    /* The type is carried. */
    if (type_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE) {
        obj_facts->type   = type_facts->type;
        obj_facts->flags |= MVM_SPESH_FACT_KNOWN_TYPE;
        MVM_spesh_facts_depend(tc, g, obj_facts, type_facts);
    }

    /* We know it's a concrete object. */
    obj_facts->flags |= MVM_SPESH_FACT_CONCRETE;
}

static void create_facts_with_type(MVMThreadContext *tc, MVMSpeshGraph *g,
                                   MVMuint16 obj_orig, MVMuint16 obj_i,
                                   MVMObject *type) {
    MVMSpeshFacts *obj_facts  = &(g->facts[obj_orig][obj_i]);

    /* The type is carried. */
    obj_facts->type   = type;
    obj_facts->flags |= MVM_SPESH_FACT_KNOWN_TYPE;

    /* We know it's a concrete object. */
    obj_facts->flags |= MVM_SPESH_FACT_CONCRETE;
}

/* Adds facts from knowing the exact value being put into an object local. */
static void object_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMuint16 tgt_orig,
                         MVMuint16 tgt_i, MVMObject *obj) {
    /* Ensure it's non-null. */
    if (!obj)
        return;

    /* Set the value itself. */
    g->facts[tgt_orig][tgt_i].value.o  = obj;
    g->facts[tgt_orig][tgt_i].flags   |= MVM_SPESH_FACT_KNOWN_VALUE;

    /* We also know the type. */
    g->facts[tgt_orig][tgt_i].type   = STABLE(obj)->WHAT;
    g->facts[tgt_orig][tgt_i].flags |= MVM_SPESH_FACT_KNOWN_TYPE;

    /* Set concreteness flags. */
    if (IS_CONCRETE(obj))
        g->facts[tgt_orig][tgt_i].flags |= MVM_SPESH_FACT_CONCRETE;
    else
        g->facts[tgt_orig][tgt_i].flags |= MVM_SPESH_FACT_TYPEOBJ;
}
void MVM_spesh_facts_object_facts(MVMThreadContext *tc, MVMSpeshGraph *g,
                                  MVMSpeshOperand tgt, MVMObject *obj) {
    object_facts(tc, g, tgt.reg.orig, tgt.reg.i, obj);
}

/* Propagates information relating to decontainerization. */
static void decont_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins,
                         MVMuint16 out_orig, MVMuint16 out_i, MVMuint16 in_orig,
                         MVMuint16 in_i) {
    MVMSpeshFacts *out_facts = &(g->facts[out_orig][out_i]);
    MVMSpeshFacts *in_facts  = &(g->facts[in_orig][in_i]);

    /* If we know the original is decontainerized already, just copy its
     * info. */
    MVMint32 in_flags = in_facts->flags;
    if ((in_flags & MVM_SPESH_FACT_TYPEOBJ) ||
            ((in_flags & MVM_SPESH_FACT_KNOWN_TYPE) &&
            !in_facts->type->st->container_spec)) {
        copy_facts(tc, g, out_orig, out_i, in_orig, in_i);
        return;
    }

    /* We may also know the original was containerized, and have some facts
     * about its contents. */
    if (in_flags & MVM_SPESH_FACT_KNOWN_DECONT_TYPE) {
        out_facts->type = in_facts->decont_type;
        out_facts->flags |= MVM_SPESH_FACT_KNOWN_TYPE;
    }
    if (in_flags & MVM_SPESH_FACT_DECONT_CONCRETE)
        out_facts->flags |= MVM_SPESH_FACT_CONCRETE;
    else if (in_flags & MVM_SPESH_FACT_DECONT_TYPEOBJ)
        out_facts->flags |= MVM_SPESH_FACT_TYPEOBJ;
    if (in_flags & (MVM_SPESH_FACT_KNOWN_DECONT_TYPE |
                    MVM_SPESH_FACT_DECONT_CONCRETE |
                    MVM_SPESH_FACT_DECONT_TYPEOBJ))
        MVM_spesh_facts_depend(tc, g, out_facts, in_facts);
}

/* Looks up a wval and adds information based on it. */
static void wval_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMuint16 tgt_orig,
                       MVMuint16 tgt_i, MVMuint16 dep, MVMint64 idx) {
    MVMCompUnit *cu = g->sf->body.cu;
    if (dep < cu->body.num_scs) {
        MVMSerializationContext *sc = MVM_sc_get_sc(tc, cu, dep);
        if (sc)
            object_facts(tc, g, tgt_orig, tgt_i, MVM_sc_try_get_object(tc, sc, idx));
    }
}
static void wvalfrom_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMuint16 tgt_orig,
                           MVMuint16 tgt_i, MVMuint16 sslot, MVMint64 idx) {
    MVMSerializationContext *sc = (MVMSerializationContext *)g->spesh_slots[sslot];
    if (MVM_sc_is_object_immediately_available(tc, sc, idx)) {
        MVM_sc_get_object(tc, sc, idx);
        object_facts(tc, g, tgt_orig, tgt_i, MVM_sc_try_get_object(tc, sc, idx));
    }
}

/* Let's figure out what exact type of iter we'll get from an iter op */
static void iter_facts(MVMThreadContext *tc, MVMSpeshGraph *g,
                       MVMuint16 out_orig, MVMuint16 out_i,
                       MVMuint16 in_orig, MVMuint16 in_i) {
    MVMSpeshFacts *out_facts = &(g->facts[out_orig][out_i]);
    MVMSpeshFacts *in_facts  = &(g->facts[in_orig][in_i]);

    if (in_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE) {
        switch (REPR(in_facts->type)->ID) {
            case MVM_REPR_ID_VMArray:
                out_facts->type = g->sf->body.cu->body.hll_config->array_iterator_type;
                out_facts->flags |= MVM_SPESH_FACT_ARRAY_ITER;
                break;
            case MVM_REPR_ID_MVMHash:
            case MVM_REPR_ID_MVMContext:
                out_facts->type = g->sf->body.cu->body.hll_config->hash_iterator_type;
                out_facts->flags |= MVM_SPESH_FACT_HASH_ITER;
                break;
            default:
                return;
        }
        out_facts->flags |= MVM_SPESH_FACT_KNOWN_TYPE | MVM_SPESH_FACT_CONCRETE;
    }

}

/* constant ops on literals give us a specialize-time-known value */
static void literal_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
    MVMSpeshFacts *tgt_facts = &g->facts[ins->operands[0].reg.orig][ins->operands[0].reg.i];
    switch (ins->info->opcode) {
        case MVM_OP_const_i64:
            tgt_facts->value.i = ins->operands[1].lit_i64;
            break;
        case MVM_OP_const_i32:
            tgt_facts->value.i = ins->operands[1].lit_i32;
            break;
        case MVM_OP_const_i16:
            tgt_facts->value.i = ins->operands[1].lit_i16;
            break;
        case MVM_OP_const_i8:
            tgt_facts->value.i = ins->operands[1].lit_i8;
            break;
        case MVM_OP_const_n32:
            tgt_facts->value.n = ins->operands[1].lit_n32;
            break;
        case MVM_OP_const_n64:
            tgt_facts->value.n = ins->operands[1].lit_n64;
            break;
        case MVM_OP_const_i64_32:
            tgt_facts->value.i = ins->operands[1].lit_i32;
            break;
        case MVM_OP_const_i64_16:
            tgt_facts->value.i = ins->operands[1].lit_i16;
            break;
        case MVM_OP_const_s:
            tgt_facts->value.s = MVM_cu_string(tc, g->sf->body.cu,
                ins->operands[1].lit_str_idx);
            break;
        default:
            return;
    }
    tgt_facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
}
static void getstringfrom_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
    MVMCompUnit *dep = (MVMCompUnit *)g->spesh_slots[ins->operands[1].lit_i16];
    MVMuint32 idx = ins->operands[2].lit_ui32;
    MVMString *str = MVM_cu_string(tc, dep, idx);
    MVMSpeshFacts *tgt_facts = &g->facts[ins->operands[0].reg.orig][ins->operands[0].reg.i];
    tgt_facts->value.s = str;
    tgt_facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
}
static void trunc_i16_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
    MVMSpeshFacts *src_facts = &g->facts[ins->operands[1].reg.orig][ins->operands[1].reg.i];
    if (src_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE) {
        MVMSpeshFacts *tgt_facts = &g->facts[ins->operands[0].reg.orig][ins->operands[0].reg.i];
        tgt_facts->value.i = (MVMint16)src_facts->value.i;
        tgt_facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
    }
}
static void coerce_iu_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
    MVMSpeshFacts *src_facts = &g->facts[ins->operands[1].reg.orig][ins->operands[1].reg.i];
    if (src_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE) {
        MVMSpeshFacts *tgt_facts = &g->facts[ins->operands[0].reg.orig][ins->operands[0].reg.i];
        tgt_facts->value.i = (MVMint16)src_facts->value.i;
        tgt_facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
    }
}

/* Discover facts from extops. */
static void discover_extop(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
    MVMExtOpRecord *extops     = g->sf->body.cu->body.extops;
    MVMuint16       num_extops = g->sf->body.cu->body.num_extops;
    MVMuint16       i;
    for (i = 0; i < num_extops; i++) {
        if (extops[i].info == ins->info) {
            /* Found op; call its discovery function, if any. */
            if (extops[i].discover)
                extops[i].discover(tc, g, ins);
            return;
        }
    }
}

void MVM_spesh_facts_guard_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
        MVMSpeshIns *ins) {
    MVMuint16 opcode = ins->info->opcode;

    /* The sslot is always the second-to-last parameter, except for
     * justconc and justtype, which don't have a spesh slot. */
    MVMuint16 sslot = ins->operands[ins->info->num_operands - 2].lit_i16;

    MVMSpeshFacts *facts    = &g->facts[ins->operands[0].reg.orig][ins->operands[0].reg.i];

    if (opcode == MVM_OP_sp_guard
            || opcode == MVM_OP_sp_guardconc
            || opcode == MVM_OP_sp_guardtype) {
        facts->flags |= MVM_SPESH_FACT_KNOWN_TYPE;
        facts->type   = ((MVMSTable *)g->spesh_slots[sslot])->WHAT;
    }
    if (opcode == MVM_OP_sp_guardconc || opcode == MVM_OP_sp_guardjustconc) {
        facts->flags |= MVM_SPESH_FACT_CONCRETE;
    }
    if (opcode == MVM_OP_sp_guardtype || opcode == MVM_OP_sp_guardjusttype) {
        facts->flags |= MVM_SPESH_FACT_TYPEOBJ;
    }
    if (opcode == MVM_OP_sp_guardobj) {
        facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
        facts->value.o = (MVMObject *)g->spesh_slots[sslot];
    }
}

/* Considers logged types and, if they are stable, adds facts and a guard. */
static void log_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
                      MVMSpeshIns *ins, MVMSpeshPlanned *p,
                      MVMSpeshAnn *deopt_one_ann, MVMSpeshAnn *logged_ann) {
    /* See if we have stable type information. For now, we need consistent
     * types, since a mis-match will force a deopt. In the future we may be
     * able to do Basic Block Versioning inspired tricks, like producing two
     * different code paths ahead when there are a small number of options. */
    MVMObject *agg_type = NULL;
    MVMuint32 agg_type_count = 0;
    MVMuint32 agg_type_object = 0;
    MVMuint32 agg_concrete = 0;
    MVMuint32 i;
    for (i = 0; i < p->num_type_stats; i++) {
        MVMSpeshStatsByType *ts = p->type_stats[i];
        MVMuint32 j;
        for (j = 0; j < ts->num_by_offset; j++) {
            if (ts->by_offset[j].bytecode_offset == logged_ann->data.bytecode_offset) {
                /* Go over the logged types. */
                MVMuint32 num_types = ts->by_offset[j].num_types;
                MVMuint32 k;
                for (k = 0; k < num_types; k++) {
                    /* If it's inconsistent with the aggregated type so far,
                     * then first check if the type we're now seeing is either
                     * massively more popular or massively less popular. If
                     * massively less, disregard this one. If massively more,
                     * disregard the previous one. Otherwise, tot up the type
                     * object vs. concrete. */
                    MVMObject *cur_type = ts->by_offset[j].types[k].type;
                    MVMuint32 count = ts->by_offset[j].types[k].count;
                    if (agg_type) {
                        if (agg_type != cur_type) {
                            if (count > 100 * agg_type_count) {
                                /* This one is hugely more popular. */
                                agg_type = cur_type;
                                agg_type_count = 0;
                                agg_concrete = 0;
                                agg_type_object = 0;
                            }
                            else if (agg_type_count > 100 * count) {
                                /* This one is hugely less popular. */
                                continue;
                            }
                            else {
                                /* Unstable types. */
                                return;
                            }
                        }
                    }
                    else {
                        agg_type = cur_type;
                    }
                    agg_type_count += count;
                    if (ts->by_offset[j].types[k].type_concrete)
                        agg_concrete++;
                    else
                        agg_type_object++;
                }

                /* No need to consider searching after this offset. */
                break;
            }
        }
    }
    if (agg_type) {
        MVMSpeshIns *guard;
        MVMSpeshAnn *ann;
        MVMuint16 guard_op;

        /* Generate a new version. We'll use this version for the original,
         * unguarded, value, which we know is the instruction right before
         * the guard, so that makes things rather simple. Thus the facts we
         * will set go on the original register */
        MVMSpeshOperand guard_reg = ins->operands[0];
        MVMSpeshOperand preguard_reg = MVM_spesh_manipulate_new_version(tc, g,
                ins->operands[0].reg.orig);
        MVMSpeshFacts *pre_facts = &g->facts[preguard_reg.reg.orig][preguard_reg.reg.i];
        MVMSpeshFacts *facts = &g->facts[guard_reg.reg.orig][guard_reg.reg.i];
        ins->operands[0] = preguard_reg;
        pre_facts->writer = ins;

        /* Add facts and choose guard op. */
        facts->type = agg_type;
        facts->flags |= MVM_SPESH_FACT_KNOWN_TYPE;
        if (agg_concrete && !agg_type_object) {
            facts->flags |= MVM_SPESH_FACT_CONCRETE;
            guard_op = MVM_OP_sp_guardconc;
        }
        else if (agg_type_object && !agg_concrete) {
            facts->flags |= MVM_SPESH_FACT_TYPEOBJ;
            guard_op = MVM_OP_sp_guardtype;
        }
        else {
            guard_op = MVM_OP_sp_guard;
        }

        /* Insert guard instruction. */
        guard = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
        guard->info = MVM_op_get_op(guard_op);
        guard->operands = MVM_spesh_alloc(tc, g, 4 * sizeof(MVMSpeshOperand));
        guard->operands[0] = guard_reg;
        guard->operands[1] = preguard_reg;
        guard->operands[2].lit_i16 = MVM_spesh_add_spesh_slot_try_reuse(tc, g,
            (MVMCollectable *)agg_type->st);
        guard->operands[3].lit_ui32 = deopt_one_ann->data.deopt_idx;
        if (ins->next)
            MVM_spesh_manipulate_insert_ins(tc, bb, ins, guard);
        else
            MVM_spesh_manipulate_insert_ins(tc, bb->linear_next, NULL, guard);
        facts->writer = guard;
        MVM_spesh_usages_add_by_reg(tc, g, preguard_reg, guard);

        /* Move deopt annotation to the guard instruction. */
        ann = ins->annotations;
        if (ann == deopt_one_ann) {
            ins->annotations = ann->next;
        }
        else {
            while (ann) {
                if (ann->next == deopt_one_ann) {
                    ann->next = deopt_one_ann->next;
                    break;
                }
                ann = ann->next;
            }
        }
        deopt_one_ann->next = NULL;
        guard->annotations = deopt_one_ann;

        /* Copy deopt usages to the preguard register. */
        {
            MVMSpeshDeoptUseEntry *due = facts->usage.deopt_users;
            while (due) {
                MVM_spesh_usages_add_deopt_usage(tc, g, pre_facts, due->deopt_idx);
                due = due->next;
            }
        }

        /* Add entry in log guards table, and mark facts as depending on it. */
        if (g->num_log_guards % 16 == 0) {
            MVMSpeshLogGuard *orig_log_guards = g->log_guards;
            g->log_guards = MVM_spesh_alloc(tc, g,
                (g->num_log_guards + 16) * sizeof(MVMSpeshLogGuard));
            if (orig_log_guards)
                memcpy(g->log_guards, orig_log_guards,
                    g->num_log_guards * sizeof(MVMSpeshLogGuard));
        }
        g->log_guards[g->num_log_guards].ins = guard;
        g->log_guards[g->num_log_guards].bb = ins->next ? bb : bb->linear_next;
        facts->log_guards = MVM_spesh_alloc(tc, g, sizeof(MVMint32));
        facts->log_guards[0] = g->num_log_guards;
        facts->num_log_guards++;
        g->num_log_guards++;
    }
}

/* Visits the blocks in dominator tree order, recursively. */
static void add_bb_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
                         MVMSpeshPlanned *p) {
    MVMint32 i;

    /* Look for instructions that provide or propagate facts. */
    MVMSpeshIns *ins = bb->first_ins;
    while (ins) {
        /* See if there's deopt and logged annotations, and if so add logged
         * facts and guards. */
        MVMSpeshAnn *ann = ins->annotations;
        MVMSpeshAnn *ann_deopt_one = NULL;
        MVMSpeshAnn *ann_logged = NULL;
        while (ann) {
            switch (ann->type) {
                case MVM_SPESH_ANN_DEOPT_ONE_INS:
                    ann_deopt_one = ann;
                    break;
                case MVM_SPESH_ANN_LOGGED:
                    ann_logged = ann;
            }
            ann = ann->next;
        }
        if (p && ann_deopt_one && ann_logged)
            log_facts(tc, g, bb, ins, p, ann_deopt_one, ann_logged);

        /* Look for ops that are fact-interesting. */
        switch (ins->info->opcode) {
        case MVM_OP_set:
            copy_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ins->operands[1].reg.orig, ins->operands[1].reg.i);
            break;
        case MVM_OP_create:
            create_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ins->operands[1].reg.orig, ins->operands[1].reg.i);
            break;
        case MVM_OP_sp_fastcreate:
        case MVM_OP_sp_fastbox_i:
        case MVM_OP_sp_fastbox_u:
        case MVM_OP_sp_fastbox_bi:
        case MVM_OP_sp_fastbox_i_ic:
        case MVM_OP_sp_fastbox_u_ic:
        case MVM_OP_sp_fastbox_bi_ic:
        case MVM_OP_sp_add_I:
        case MVM_OP_sp_sub_I:
        case MVM_OP_sp_mul_I:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ((MVMSTable *)g->spesh_slots[ins->operands[2].lit_i16])->WHAT);
            break;
        case MVM_OP_clone:
            copy_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ins->operands[1].reg.orig, ins->operands[1].reg.i);
            break;
        case MVM_OP_box_s:
        case MVM_OP_box_i:
        case MVM_OP_box_n:
        case MVM_OP_box_u: {
                MVMSpeshFacts *target_facts = &(g->facts[ins->operands[0].reg.orig][ins->operands[0].reg.i]);
                create_facts(tc, g,
                    ins->operands[0].reg.orig, ins->operands[0].reg.i,
                    ins->operands[2].reg.orig, ins->operands[2].reg.i);
                target_facts->flags |= MVM_SPESH_FACT_KNOWN_BOX_SRC;
                break;
            }
        case MVM_OP_coerce_sI: {
                create_facts(tc, g,
                    ins->operands[0].reg.orig, ins->operands[0].reg.i,
                    ins->operands[2].reg.orig, ins->operands[2].reg.i);
                break;
            }
        case MVM_OP_add_I:
        case MVM_OP_sub_I:
        case MVM_OP_mul_I:
        case MVM_OP_div_I:
        case MVM_OP_mod_I:
            create_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ins->operands[3].reg.orig, ins->operands[3].reg.i);
            break;
        case MVM_OP_neg_I:
        case MVM_OP_abs_I:
            create_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ins->operands[2].reg.orig, ins->operands[2].reg.i);
            break;
        case MVM_OP_nativecallcast:
            create_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ins->operands[2].reg.orig, ins->operands[2].reg.i);
            break;
        case MVM_OP_trunc_u16:
        case MVM_OP_trunc_i16:
            trunc_i16_facts(tc, g, ins);
            break;
        case MVM_OP_coerce_ui:
        case MVM_OP_coerce_iu:
            trunc_i16_facts(tc, g, ins);
            break;
        case MVM_OP_bootint:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                tc->instance->boot_types.BOOTInt);
            break;
        case MVM_OP_bootnum:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                tc->instance->boot_types.BOOTNum);
            break;
        case MVM_OP_bootstr:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                tc->instance->boot_types.BOOTStr);
            break;
        case MVM_OP_bootarray:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                tc->instance->boot_types.BOOTArray);
            break;
        case MVM_OP_bootintarray:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                tc->instance->boot_types.BOOTIntArray);
            break;
        case MVM_OP_bootnumarray:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                tc->instance->boot_types.BOOTNumArray);
            break;
        case MVM_OP_bootstrarray:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                tc->instance->boot_types.BOOTStrArray);
            break;
        case MVM_OP_boothash:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                tc->instance->boot_types.BOOTHash);
            break;
        case MVM_OP_hllboolfor: {
            MVMSpeshOperand *name_op = &ins->operands[1];
            MVMSpeshFacts *namefacts = &g->facts[name_op->reg.orig][name_op->reg.i];
            if (namefacts->writer && namefacts->writer->info->opcode == MVM_OP_const_s) {
                MVMString *hllname = MVM_cu_string(tc, g->sf->body.cu, ins->operands[1].lit_str_idx);
                MVMHLLConfig *hll = MVM_hll_get_config_for(tc, hllname);
                if (hll->true_value && hll->false_value && STABLE(hll->true_value)->WHAT == STABLE(hll->false_value)->WHAT)
                    create_facts_with_type(tc, g,
                        ins->operands[0].reg.orig, ins->operands[0].reg.i,
                        STABLE(hll->true_value)->WHAT);
            }
            break;
        }
        case MVM_OP_hllbool: {
            MVMHLLConfig *hll = g->sf->body.cu->body.hll_config;
            if (hll->true_value && hll->false_value && STABLE(hll->true_value)->WHAT == STABLE(hll->false_value)->WHAT)
                create_facts_with_type(tc, g,
                    ins->operands[0].reg.orig, ins->operands[0].reg.i,
                    STABLE(hll->true_value)->WHAT);
            break;
        }
        case MVM_OP_hllboxtype_i:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->int_box_type);
            break;
        case MVM_OP_hllboxtype_n:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->num_box_type);
            break;
        case MVM_OP_hllboxtype_s:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->str_box_type);
            break;
        case MVM_OP_hlllist:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->slurpy_array_type);
            break;
        case MVM_OP_hllhash:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->slurpy_hash_type);
            break;
        case MVM_OP_decont:
            decont_facts(tc, g, ins,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ins->operands[1].reg.orig, ins->operands[1].reg.i);
            break;
        case MVM_OP_wval:
            wval_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ins->operands[1].lit_i16, ins->operands[2].lit_i16);
            break;
        case MVM_OP_wval_wide:
            wval_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ins->operands[1].lit_i16, ins->operands[2].lit_i64);
            break;
        case MVM_OP_sp_getwvalfrom:
            wvalfrom_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ins->operands[1].lit_i16, ins->operands[2].lit_i64);
            break;
        case MVM_OP_sp_getspeshslot:
            object_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                (MVMObject *)g->spesh_slots[ins->operands[1].lit_i16]);
            break;
        case MVM_OP_iter:
            iter_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ins->operands[1].reg.orig, ins->operands[1].reg.i);
            break;
        case MVM_OP_newexception:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                tc->instance->boot_types.BOOTException);
            break;
        case MVM_OP_getlexref_i:
        case MVM_OP_getlexref_i32:
        case MVM_OP_getlexref_i16:
        case MVM_OP_getlexref_i8:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->int_lex_ref);
            break;
        case MVM_OP_getlexref_u:
        case MVM_OP_getlexref_u32:
        case MVM_OP_getlexref_u16:
        case MVM_OP_getlexref_u8:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->uint_lex_ref);
            break;
        case MVM_OP_getlexref_n:
        case MVM_OP_getlexref_n32:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->num_lex_ref);
            break;
        case MVM_OP_getlexref_s:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->str_lex_ref);
            break;
        case MVM_OP_getattrref_i:
        case MVM_OP_getattrsref_i:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->int_attr_ref);
            break;
        case MVM_OP_getattrref_u:
        case MVM_OP_getattrsref_u:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->uint_attr_ref);
            break;
        case MVM_OP_getattrref_n:
        case MVM_OP_getattrsref_n:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->num_attr_ref);
            break;
        case MVM_OP_getattrref_s:
        case MVM_OP_getattrsref_s:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->str_attr_ref);
            break;
        case MVM_OP_atposref_i:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->int_pos_ref);
            break;
        case MVM_OP_atposref_u:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->uint_pos_ref);
            break;
        case MVM_OP_atposref_n:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->num_pos_ref);
            break;
        case MVM_OP_atposref_s:
            create_facts_with_type(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                g->sf->body.cu->body.hll_config->str_pos_ref);
            break;

        case MVM_OP_const_i64:
        case MVM_OP_const_i32:
        case MVM_OP_const_i16:
        case MVM_OP_const_i8:
        case MVM_OP_const_n64:
        case MVM_OP_const_n32:
        case MVM_OP_const_i64_32:
        case MVM_OP_const_i64_16:
        case MVM_OP_const_s:
            literal_facts(tc, g, ins);
            break;
        case MVM_OP_sp_getstringfrom:
            getstringfrom_facts(tc, g, ins);
            break;
        case MVM_OP_encode:
            create_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ins->operands[3].reg.orig, ins->operands[3].reg.i);
            break;
        case MVM_OP_encoderep:
            create_facts(tc, g,
                ins->operands[0].reg.orig, ins->operands[0].reg.i,
                ins->operands[4].reg.orig, ins->operands[4].reg.i);
            break;
        case MVM_OP_sp_guard:
        case MVM_OP_sp_guardconc:
        case MVM_OP_sp_guardtype:
        case MVM_OP_sp_guardobj:
        case MVM_OP_sp_guardjustconc:
        case MVM_OP_sp_guardjusttype:
            MVM_spesh_facts_guard_facts(tc, g, bb, ins);
            break;
        default:
            if (ins->info->opcode == (MVMuint16)-1)
                discover_extop(tc, g, ins);
        }
        ins = ins->next;
    }

    /* Visit children. */
    for (i = 0; i < bb->num_children; i++)
        add_bb_facts(tc, g, bb->children[i], p);
}

/* Exception handlers that use a block to store the handler must not have the
 * instructions that install the block eliminated. This tweaks the usage of
 * them. */
static void tweak_block_handler_usage(MVMThreadContext *tc, MVMSpeshGraph *g) {
    MVMuint32 i;
    for (i = 0; i < g->sf->body.num_handlers; i++) {
        if (g->sf->body.handlers[i].action == MVM_EX_ACTION_INVOKE) {
            MVMSpeshOperand operand;
            operand.reg.orig = g->sf->body.handlers[i].block_reg;
            operand.reg.i = 1;
            MVM_spesh_usages_add_for_handler_by_reg(tc, g, operand);
        }
        if (g->sf->body.handlers[i].category_mask & MVM_EX_CAT_LABELED) {
            MVMSpeshOperand operand;
            operand.reg.orig = g->sf->body.handlers[i].label_reg;
            operand.reg.i = 1;
            MVM_spesh_usages_add_for_handler_by_reg(tc, g, operand);
        }
    }
}

/* Kicks off fact discovery from the top of the (dominator) tree. */
void MVM_spesh_facts_discover(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshPlanned *p,
        MVMuint32 is_specialized) {
    /* Set up normal usage information. */
    MVM_spesh_usages_create_usage(tc, g);
    tweak_block_handler_usage(tc, g);

    /* We do an initial dead instruction pass before then computing the deopt
     * usages. This dead instrution elimination pass acts also as a PHI prune,
     * since we have numerous dead PHI instructions that can cause bogus
     * deopt retentions, as well as increase the amount of work that the
     * deopt usage algorithm has to do. Note that we don't do this for an
     * already specialized inlinee, since information was already discarded. */
    if (!is_specialized) {
        MVM_spesh_eliminate_dead_ins(tc, g);
        MVM_spesh_usages_create_deopt_usage(tc, g);
    }

    /* Finally, collect facts. */
    add_bb_facts(tc, g, g->entry, p);
}
