#define MVMPhiNodeCacheSize             48
#define MVMPhiNodeCacheSparseBegin      32

/* Top level of a spesh graph, representing a particular static frame (and
 * potentially having others inlined into it). */
struct MVMSpeshGraph {
    /* The static frame this is the spesh graph for. */
    MVMStaticFrame *sf;

    /* The callsite this spesh graph has been tailored to. */
    MVMCallsite *cs;

    /* The bytecode we're building the graph out of. */
    MVMuint8 *bytecode;

    /* Exception handler map for that bytecode. */
    MVMFrameHandler *handlers;

    /* Handlers that have become unreachable due to dead code removal. */
    MVMint8 *unreachable_handlers;

    /* The size of the bytecode we're building the graph out of. */
    MVMuint32 bytecode_size;

    /* Number of exception handlers. */
    MVMuint32 num_handlers;

    /* The entry basic block. */
    MVMSpeshBB *entry;

    /* Gathered facts about each version of a local (top-level array is per
     * local, then array hanging off it is per version). */
    MVMSpeshFacts **facts;

    /* Number of fact entries per local. */
    MVMuint16 *fact_counts;

    /* Log-based guards added. */
    MVMSpeshLogGuard *log_guards;

    /* Number of log-based guards we have. */
    MVMint32 num_log_guards;

    /* Region allocator for spesh nodes */
    MVMRegionAlloc region_alloc;

    /* Values placed in spesh slots. */
    MVMCollectable **spesh_slots;

    /* Number of spesh slots we have used and allocated. */
    MVMuint16 num_spesh_slots;
    MVMuint16 alloc_spesh_slots;

    /* De-opt indexes, as pairs of integers. The first integer, set when we
     * build the graph, is the return address in the original bytecode. The
     * code-gen phase for the specialized bytecode will fill in the second
     * integers afterwards, which are the return address in the specialized
     * bytecode. */
    MVMint32 *deopt_addrs;
    MVMuint32 num_deopt_addrs;
    MVMuint32 alloc_deopt_addrs;

    /* Bit field of named args used to put in place during deopt, since we
     * don't typically don't update the array in specialized code. */
    MVMuint64 deopt_named_used_bit_field;

    /* Deopt points that are considered to be always in use, even if they
     * are not on a deopting instruction. */
    MVMuint32 *always_retained_deopt_idxs;
    MVMuint32 num_always_retained_deopt_idxs;

    /* Deopt information produced by escape analysis and scalar replacement. */
    MVMSpeshPEADeopt deopt_pea;

    /* Table of information about inlines, laid out in order of nesting
     * depth. Thus, going through the table in order and finding when we
     * are within the bounds will show up each call frame that needs to
     * be created in deopt. */
    MVMSpeshInline *inlines;
    MVMuint32 num_inlines;

    /* Table of information about resume initialization arguments, if any. */
    MVM_VECTOR_DECL(MVMSpeshResumeInit, resume_inits);

    /* Number of basic blocks we have. */
    MVMuint32 num_bbs;

    /* The list of local types (only set up if we do inlines). */
    MVMuint16 *local_types;

    /* The list of lexical types (only set up if we do inlines). */
    MVMuint16 *lexical_types;

    /* The total number of locals, accounting for any inlining done and
     * added temporaries. */
    MVMuint16 num_locals;

    /* The total number of lexicals, accounting for any inlining done. */
    MVMuint16 num_lexicals;

    /* Temporary local registers added to aid transformations, along with a
     * count of the number we have and have allocated space for so far. */
    MVMuint16          num_temps;
    MVMuint16          alloc_temps;
    MVMSpeshTemporary *temps;

    /* We need to create new MVMOpInfo structs for each number of
     * arguments a PHI node can take. We cache them here, so that we
     * allocate fewer of them across our spesh alloc blocks.
     */
    MVMOpInfo *phi_infos;

    /* If this graph was formed from a spesh candidate rather than an
     * original static frame, the candidate will be stored here. */
    MVMSpeshCandidate *cand;

    /* Did we specialize on the invocant type? */
    MVMuint8 specialized_on_invocant;

    /* Stored in comment annotations to give an ordering of comments */
    MVMuint32 next_annotation_idx;

    /* When creating graphs for inline candidates, track how deep we've
     * recursed, so we don't infinitely recurse.
     *
     * This limit should only cause try_get_graph_from_unspecialized to not
     * recurse deeply, since after an inline graph is created and merged into
     * the inlinee, we run optimize over it again and re-encounter ops that
     * may lead to inline attemps, like runbytecode.
     *
     * See also SPESH_UNSPECIALIZED_INLINE_RECURSION_DEPTH_LIMIT.
     */
    MVMuint16 recursion_depth;
};

/* A temporary register, added to support transformations. */
struct MVMSpeshTemporary {
    /* The number of the local along with the current SSA index. */
    MVMuint16 orig;
    MVMuint16 i;

    /* The SSA index currently loaned out. */
    MVMuint16 used_i;

    /* What kind of register is it? */
    MVMuint16 kind;

    /* Is it currently in use? */
    MVMuint16 in_use;
};

/* A basic block in the graph (sequences of instructions where control will
 * always enter at the start and leave at the end). */
struct MVMSpeshBB {
    /* Head/tail of doubly linked list of instructions. */
    MVMSpeshIns *first_ins;
    MVMSpeshIns *last_ins;

    /* Basic blocks we may go to after this one. */
    MVMSpeshBB **succ;

    /* Basic blocks that we may arrive into this one from. */
    MVMSpeshBB **pred;

    /* Children in the dominator tree. */
    MVMSpeshBB **children;

    /* Dominance frontier set. */
    MVMSpeshBB **df;

    /* Basic blocks that we may go to if we throw. */
    MVMSpeshBB **handler_succ;

    /* Counts for the above, grouped together to avoid alignment holes. */
    MVMuint16    num_succ;
    MVMuint16    num_pred;
    MVMuint16    num_children;
    MVMuint16    num_df;
    MVMuint16    num_handler_succ;

    /* The next basic block in original linear code order. */
    MVMSpeshBB *linear_next;

    /* Index (just an ascending integer along the linear_next chain), used as
     * the block identifier in dominance computation and for debug output. */
    MVMint32 idx;

    /* The block's reverse post-order index, assinged when computing
     * dominance. */
    MVMint32 rpo_idx;

    /* We cache the instruction pointer of the very first instruction so that
     * we can output a line number for every BB */
    MVMuint32 initial_pc;

    /* Is this block an inlining of another one? */
    MVMint8 inlined;

    /* Is this basic block part of a jump list? */
    MVMint8 jumplist;

    /* Is this basic block dead (removed due to being unreachable)? */
    MVMint8 dead;
};

/* The SSA phi instruction. */
#define MVM_SSA_PHI 32767

/* An instruction in the spesh graph. */
struct MVMSpeshIns {
    /* Instruction information. */
    const MVMOpInfo *info;

    /* Operand information. */
    MVMSpeshOperand *operands;

    /* Previous and next instructions, within a basic block boundary. */
    MVMSpeshIns *prev;
    MVMSpeshIns *next;

    /* Any annotations on the instruction. */
    MVMSpeshAnn *annotations;
};

/* Union type of operands in a spesh instruction; the op info and phase of the
 * optimizer we're in determines which of these we look at. */
union MVMSpeshOperand {
    MVMint64     lit_i64;
    MVMint64     lit_ui64;
    MVMint32     lit_i32;
    MVMuint32    lit_ui32;
    MVMint16     lit_i16;
    MVMuint16    lit_ui16;
    MVMint8      lit_i8;
    MVMnum64     lit_n64;
    MVMnum32     lit_n32;
    MVMuint32    lit_str_idx;
    MVMuint16    callsite_idx;
    MVMuint16    coderef_idx;
    MVMuint32    ins_offset;
    MVMSpeshBB  *ins_bb;
    struct {
        MVMuint16 idx;
        MVMuint16 outers;
    } lex;
    struct {
        MVMint32  i;    /* SSA-computed version. */
        MVMuint16 orig; /* Original register number. */
    } reg;
};

/* Annotations base. */
struct MVMSpeshAnn {
    /* The next annotation in the chain, if any. */
    MVMSpeshAnn *next;

    /* The type of annotation we have. */
    MVMint32 type;

    /* Order in which the annotation was created; only used
     * for comments right now, but put in front of the union
     * as there is a 4 byte hole here otherwise. */
    MVMuint32 order;

    /* Data (meaning depends on type). */
    union {
        MVMuint32 frame_handler_index;
        MVMint32 deopt_idx;
        MVMint32 inline_idx;
        MVMuint32 bytecode_offset;
        struct {
            MVMuint32 filename_string_index;
            MVMuint32 line_number;
        } lineno;
        char *comment;
        MVMSpeshOperand *temps_to_release;
    } data;
};

/* Annotation types. */
#define MVM_SPESH_ANN_FH_START      1
#define MVM_SPESH_ANN_FH_END        2
#define MVM_SPESH_ANN_FH_GOTO       3
#define MVM_SPESH_ANN_DEOPT_ONE_INS 4
#define MVM_SPESH_ANN_DEOPT_ALL_INS 5
#define MVM_SPESH_ANN_INLINE_START  6
#define MVM_SPESH_ANN_INLINE_END    7
#define MVM_SPESH_ANN_DEOPT_INLINE  8
#define MVM_SPESH_ANN_DEOPT_OSR     9
#define MVM_SPESH_ANN_LINENO        10
#define MVM_SPESH_ANN_LOGGED        11
#define MVM_SPESH_ANN_DEOPT_SYNTH   12
#define MVM_SPESH_ANN_CACHED        13
#define MVM_SPESH_ANN_DEOPT_PRE_INS 14
#define MVM_SPESH_ANN_DELAYED_TEMPS 2048
#define MVM_SPESH_ANN_COMMENT       4096

/* Functions to create/destroy the spesh graph. */
MVMSpeshGraph * MVM_spesh_graph_create(MVMThreadContext *tc, MVMStaticFrame *sf,
    MVMuint32 cfg_only, MVMuint32 insert_object_nulls);
MVMSpeshGraph * MVM_spesh_graph_create_from_cand(MVMThreadContext *tc, MVMStaticFrame *sf,
    MVMSpeshCandidate *cand, MVMuint32 cfg_only, MVMSpeshIns ***deopt_usage_ins_out);
MVMSpeshBB * MVM_spesh_graph_linear_prev(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *search);
void MVM_spesh_graph_grow_deopt_table(MVMThreadContext *tc, MVMSpeshGraph *g);
MVMint32 MVM_spesh_graph_add_deopt_annotation(MVMThreadContext *tc, MVMSpeshGraph *g,
    MVMSpeshIns *ins_node, MVMuint32 deopt_target, MVMint32 type);
MVMSpeshBB ** MVM_spesh_graph_reverse_postorder(MVMThreadContext *tc, MVMSpeshGraph *g);
void MVM_spesh_graph_recompute_dominance(MVMThreadContext *tc, MVMSpeshGraph *g);
void MVM_spesh_graph_mark(MVMThreadContext *tc, MVMSpeshGraph *g, MVMGCWorklist *worklist);
void MVM_spesh_graph_describe(MVMThreadContext *tc, MVMSpeshGraph *g, MVMHeapSnapshotState *snapshot);
void MVM_spesh_graph_destroy(MVMThreadContext *tc, MVMSpeshGraph *g);
MVM_PUBLIC void * MVM_spesh_alloc(MVMThreadContext *tc, MVMSpeshGraph *g, size_t bytes);
MVMOpInfo *MVM_spesh_graph_get_phi(MVMThreadContext *tc, MVMSpeshGraph *g, MVMuint32 nrargs);
void MVM_spesh_graph_place_phi(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMint32 n, MVMuint16 var);

MVM_PUBLIC void MVM_spesh_graph_add_comment(MVMThreadContext *tc, MVMSpeshGraph *g,
    MVMSpeshIns *ins, const char *fmt, ...);

MVM_STATIC_INLINE MVMuint32 MVM_spesh_is_inc_dec_op(MVMuint16 opcode) {
    return opcode == MVM_OP_inc_i || opcode == MVM_OP_dec_i ||
           opcode == MVM_OP_inc_u || opcode == MVM_OP_dec_u;
}
int MVM_spesh_graph_ins_ends_bb(MVMThreadContext *tc, const MVMOpInfo *info);
