#include "moar.h"

/* Here we turn a spesh tree back into MoarVM bytecode, after optimizations
 * have been applied to it. */

typedef struct {
    MVM_VECTOR_DECL(MVMint32, idxs);
    MVM_VECTOR_DECL(MVMSpeshIns *, seen_phis);
} AllDeoptUsers;

/* Writer state. */
typedef struct {
    /* Bytecode output buffer. */
    MVMuint8  *bytecode;
    MVMuint32  bytecode_pos;
    MVMuint32  bytecode_alloc;

    /* Offsets where basic blocks are. */
    MVMint32 *bb_offsets;

    /* Fixups we need to do by basic block. */
    MVMint32    *fixup_locations;
    MVMSpeshBB **fixup_bbs;
    MVMuint32    num_fixups;
    MVMuint32    alloc_fixups;

    /* Copied frame handlers (which we'll update offsets of). */
    MVMFrameHandler *handlers;

    /* Persisted deopt usage info, so we can recover it should be produce an
     * inline from this bytecode in the future. */
    MVM_VECTOR_DECL(MVMint32, deopt_usage_info);

    MVM_VECTOR_DECL(MVMint32, deopt_synth_addrs);

    /* Working deopt users state (so we can allocate it once and re-use it). */
    AllDeoptUsers all_deopt_users;
} SpeshWriterState;

/* Write functions; all native endian. */
static void ensure_space(SpeshWriterState *ws, int bytes) {
    if (ws->bytecode_pos + bytes >= ws->bytecode_alloc) {
        ws->bytecode_alloc *= 2;
        ws->bytecode = MVM_realloc(ws->bytecode, ws->bytecode_alloc);
    }
}
static void write_int64(SpeshWriterState *ws, MVMuint64 value) {
    ensure_space(ws, 8);
    memcpy(ws->bytecode + ws->bytecode_pos, &value, 8);
    ws->bytecode_pos += 8;
}
static void write_uint64(SpeshWriterState *ws, MVMuint64 value) {
    ensure_space(ws, 8);
    memcpy(ws->bytecode + ws->bytecode_pos, &value, 8);
    ws->bytecode_pos += 8;
}
static void write_int32(SpeshWriterState *ws, MVMuint32 value) {
    ensure_space(ws, 4);
    memcpy(ws->bytecode + ws->bytecode_pos, &value, 4);
    ws->bytecode_pos += 4;
}
static void write_int16(SpeshWriterState *ws, MVMuint16 value) {
    ensure_space(ws, 2);
    memcpy(ws->bytecode + ws->bytecode_pos, &value, 2);
    ws->bytecode_pos += 2;
}
static void write_int8(SpeshWriterState *ws, MVMuint8 value) {
    ensure_space(ws, 1);
    memcpy(ws->bytecode + ws->bytecode_pos, &value, 1);
    ws->bytecode_pos++;
}
static void write_num32(SpeshWriterState *ws, MVMnum32 value) {
    ensure_space(ws, 4);
    memcpy(ws->bytecode + ws->bytecode_pos, &value, 4);
    ws->bytecode_pos += 4;
}
static void write_num64(SpeshWriterState *ws, MVMnum64 value) {
    ensure_space(ws, 8);
    memcpy(ws->bytecode + ws->bytecode_pos, &value, 8);
    ws->bytecode_pos += 8;
}

/* Deopt user retention logic for the sake of inlining. */
static void collect_deopt_users(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshOperand from,
        AllDeoptUsers *all_deopt_users) {
    MVMSpeshFacts *facts = MVM_spesh_get_facts(tc, g, from);
    MVMSpeshDeoptUseEntry *deopt_users = facts->usage.deopt_users;
    MVMSpeshUseChainEntry *users = facts->usage.users;
    while (deopt_users) {
        MVM_VECTOR_PUSH(all_deopt_users->idxs, deopt_users->deopt_idx);
        deopt_users = deopt_users->next;
    }
    while (users) {
        MVMSpeshIns *ins = users->user;
        if (ins->info->opcode == MVM_SSA_PHI) {
            MVMint32 seen = 0;
            MVMuint32 i;
            for (i = 0; i < MVM_VECTOR_ELEMS(all_deopt_users->seen_phis); i++) {
                if (all_deopt_users->seen_phis[i] == ins) {
                    seen = 1;
                    break;
                }
            }
            if (!seen) {
                MVM_VECTOR_PUSH(all_deopt_users->seen_phis, ins);
                collect_deopt_users(tc, g, ins->operands[0], all_deopt_users);
            }
        }
        users = users->next;
    }
}

/* Writes instructions within a basic block boundary. */
static void write_instructions(MVMThreadContext *tc, MVMSpeshGraph *g, SpeshWriterState *ws, MVMSpeshBB *bb) {
    MVMSpeshIns *ins = bb->first_ins;
    while (ins) {
        MVMint32 i;

        /* Process any pre-annotations, and note if there were any deopt ones;
         * if so, these should be processed later. */
        MVMSpeshAnn *ann = ins->annotations;
        MVMuint32 has_deopts = 0;
        while (ann) {
            switch (ann->type) {
            case MVM_SPESH_ANN_FH_START:
                ws->handlers[ann->data.frame_handler_index].start_offset =
                    ws->bytecode_pos;
                break;
            case MVM_SPESH_ANN_FH_END:
                ws->handlers[ann->data.frame_handler_index].end_offset =
                    ws->bytecode_pos;
                break;
            case MVM_SPESH_ANN_FH_GOTO:
                ws->handlers[ann->data.frame_handler_index].goto_offset =
                    ws->bytecode_pos;
                break;
            case MVM_SPESH_ANN_DEOPT_ONE_INS:
            case MVM_SPESH_ANN_DEOPT_ALL_INS:
            case MVM_SPESH_ANN_DEOPT_INLINE:
            case MVM_SPESH_ANN_DEOPT_SYNTH:
                has_deopts = 1;
                break;
            case MVM_SPESH_ANN_INLINE_START:
                g->inlines[ann->data.inline_idx].start = ws->bytecode_pos;
                break;
            case MVM_SPESH_ANN_INLINE_END:
                g->inlines[ann->data.inline_idx].end = ws->bytecode_pos;
                break;
            case MVM_SPESH_ANN_DEOPT_OSR:
            case MVM_SPESH_ANN_DEOPT_PRE_INS:
                g->deopt_addrs[2 * ann->data.deopt_idx + 1] = ws->bytecode_pos << 1 | 1;
                break;
            }
            ann = ann->next;
        }

        if (ins->info->opcode != MVM_SSA_PHI) {
            /* Real instruction, not a phi. See if we need to save any deopt
             * usage information at this location. If the thing that is
             * written is consumed by a PHI and those also have deopt users
             * then we need to handle that case also. */
            if (g->facts && ins->info->num_operands >= 1 &&
                    (ins->info->operands[0] & MVM_operand_rw_mask) == MVM_operand_write_reg) {
                collect_deopt_users(tc, g, ins->operands[0], &(ws->all_deopt_users));
                if (MVM_VECTOR_ELEMS(ws->all_deopt_users.idxs)) {
                    MVMint32 count = MVM_VECTOR_ELEMS(ws->all_deopt_users.idxs);
                    MVMint32 i;
                    MVM_VECTOR_PUSH(ws->deopt_usage_info, ws->bytecode_pos);
                    MVM_VECTOR_PUSH(ws->deopt_usage_info, count);
                    for (i = 0; i < count; i++)
                        MVM_VECTOR_PUSH(ws->deopt_usage_info, ws->all_deopt_users.idxs[i]);
                }
                MVM_VECTOR_CLEAR(ws->all_deopt_users.idxs);
                MVM_VECTOR_CLEAR(ws->all_deopt_users.seen_phis);
            }

            /* Emit opcode. */
            if (ins->info->opcode == (MVMuint16)-1) {
                /* Ext op; resolve. */
                MVMExtOpRecord *extops     = g->sf->body.cu->body.extops;
                MVMuint16       num_extops = g->sf->body.cu->body.num_extops;
                MVMint32        found      = 0;
                for (i = 0; i < num_extops; i++) {
                    if (extops[i].info == ins->info) {
                        write_int16(ws, MVM_OP_EXT_BASE + i);
                        found = 1;
                        break;
                    }
                }
                if (!found)
                    MVM_oops(tc, "Spesh: failed to resolve extop in code-gen");
            }
            else {
                /* Core op. */
                write_int16(ws, ins->info->opcode);

                /* sp_resumption needs entries making in the spesh resume init
                 * table. */
                if (ins->info->opcode == MVM_OP_sp_resumption) {
                    /* The state register is simply the register number. */
                    MVMSpeshResumeInit *ri = &(g->resume_inits[ins->operands[1].lit_ui16]);
                    ri->state_register = ins->operands[0].reg.orig;

                    /* For the resume init registers, we only have registers
                     * for args and temps, but not constants. We need to build
                     * a table that has those register numbers in the correct
                     * place. */
                    MVMDispProgramResumption *dpr = &(ri->dp->resumptions[ri->res_idx]);
                    MVMuint16 map_size = dpr->init_callsite->flag_count;
                    ri->init_registers = MVM_malloc(map_size * sizeof(MVMuint16));
                    MVMuint16 num_init_registers = ins->operands[2].lit_ui16;
                    if (dpr->init_values) {
                        MVMuint16 cur_reg = 0;
                        for (i = 0; i < map_size; i++) {
                            switch (dpr->init_values[i].source) {
                                case MVM_DISP_RESUME_INIT_ARG:
                                case MVM_DISP_RESUME_INIT_TEMP:
                                    ri->init_registers[i] = ins->operands[3 + cur_reg].reg.orig;
                                    cur_reg++;
                                    break;
                                default:
                                    ri->init_registers[i] = 0xFFFF;
                                    break;
                            }
                        }
                    }
                    else {
                        /* It's just the initial arguments to the dispatch, so
                         * should match directly with the op registers. */
                        assert(map_size == num_init_registers);
                        for (i = 0; i < num_init_registers; i++)
                            ri->init_registers[i] = ins->operands[3 + i].reg.orig;
                    }
                }
            }

            /* Write out operands. */
            for (i = 0; i < ins->info->num_operands; i++) {
                MVMuint8 flags = ins->info->operands[i];
                MVMuint8 rw    = flags & MVM_operand_rw_mask;
                switch (rw) {
                case MVM_operand_read_reg:
                case MVM_operand_write_reg:
                    write_int16(ws, ins->operands[i].reg.orig);
                    break;
                case MVM_operand_read_lex:
                case MVM_operand_write_lex:
                    write_int16(ws, ins->operands[i].lex.idx);
                    write_int16(ws, ins->operands[i].lex.outers);
                    break;
                case MVM_operand_literal: {
                    MVMuint8 type = flags & MVM_operand_type_mask;
                    switch (type) {
                    case MVM_operand_int8:
                        write_int8(ws, ins->operands[i].lit_i8);
                        break;
                    case MVM_operand_int16:
                        write_int16(ws, ins->operands[i].lit_i16);
                        break;
                    case MVM_operand_uint16:
                        write_int16(ws, ins->operands[i].lit_i16);
                        break;
                    case MVM_operand_int32:
                        write_int32(ws, ins->operands[i].lit_i32);
                        break;
                    case MVM_operand_uint32:
                        write_int32(ws, ins->operands[i].lit_ui32);
                        break;
                    case MVM_operand_int64:
                        write_int64(ws, ins->operands[i].lit_i64);
                        break;
                    case MVM_operand_uint64:
                        write_uint64(ws, ins->operands[i].lit_ui64);
                        break;
                    case MVM_operand_num32:
                        write_num32(ws, ins->operands[i].lit_n32);
                        break;
                    case MVM_operand_num64:
                        write_num64(ws, ins->operands[i].lit_n64);
                        break;
                    case MVM_operand_callsite:
                        write_int16(ws, ins->operands[i].callsite_idx);
                        break;
                    case MVM_operand_coderef:
                        write_int16(ws, ins->operands[i].coderef_idx);
                        break;
                    case MVM_operand_str:
                        write_int32(ws, ins->operands[i].lit_str_idx);
                        break;
                    case MVM_operand_ins: {
                        MVMuint32 bb_idx = ins->operands[i].ins_bb->idx;
                        MVMint32 offset;
                        if (bb_idx >= g->num_bbs)
                            MVM_panic(1, "Spesh codegen: out of range BB index %d", bb_idx);
                        offset = ws->bb_offsets[bb_idx];
                        if (offset >= 0) {
                            /* Already know where it is, so just write it. */
                            write_int32(ws, offset);
                        }
                        else {
                            /* Need to fix it up. */
                            if (ws->num_fixups == ws->alloc_fixups) {
                                ws->alloc_fixups *= 2;
                                ws->fixup_locations = MVM_realloc(ws->fixup_locations,
                                    ws->alloc_fixups * sizeof(MVMint32));
                                ws->fixup_bbs = MVM_realloc(ws->fixup_bbs,
                                    ws->alloc_fixups * sizeof(MVMSpeshBB *));
                            }
                            ws->fixup_locations[ws->num_fixups] = ws->bytecode_pos;
                            ws->fixup_bbs[ws->num_fixups]       = ins->operands[i].ins_bb;
                            write_int32(ws, 0);
                            ws->num_fixups++;
                        }
                        break;
                    }
                    case MVM_operand_spesh_slot:
                        write_int16(ws, ins->operands[i].lit_i16);
                        break;
                    default:
                        MVM_oops(tc,
                            "Spesh: unknown operand type %d in codegen (op %s)",
                            (int)type, ins->info->name);
                    }
                    }
                    break;
                default:
                    MVM_oops(tc, "Spesh: unknown operand type in codegen");
                }
            }
        }

        /* If there were deopt point annotations, update table. */
        if (has_deopts) {
#ifndef NDEBUG
            MVMint32 seen_deopt_idx = 0;
            MVMint32 deopt_idx;
            switch (ins->info->opcode) {
            case MVM_OP_sp_guard:
            case MVM_OP_sp_guardconc:
            case MVM_OP_sp_guardtype:
            case MVM_OP_sp_guardobj:
            case MVM_OP_sp_guardnotobj:
            case MVM_OP_sp_guardhll:
            case MVM_OP_sp_rebless:
                deopt_idx = ins->operands[3].lit_ui32;
                break;
            case MVM_OP_sp_guardsf:
            case MVM_OP_sp_guardsfouter:
            case MVM_OP_sp_guardjustconc:
            case MVM_OP_sp_guardjusttype:
            case MVM_OP_sp_guardnonzero:
                deopt_idx = ins->operands[2].lit_ui32;
                break;
            default:
                deopt_idx = -1;
                break;
            }
#endif
            ann = ins->annotations;
            while (ann) {
                switch (ann->type) {
                case MVM_SPESH_ANN_DEOPT_SYNTH:
                    MVM_VECTOR_PUSH(ws->deopt_synth_addrs, ann->data.deopt_idx);
                    MVM_VECTOR_PUSH(ws->deopt_synth_addrs, ws->bytecode_pos);
                    break;
                case MVM_SPESH_ANN_DEOPT_ONE_INS:
                case MVM_SPESH_ANN_DEOPT_ALL_INS:
                case MVM_SPESH_ANN_DEOPT_INLINE:
                    g->deopt_addrs[2 * ann->data.deopt_idx + 1] = ws->bytecode_pos << 1;
#ifndef NDEBUG
                    if (deopt_idx == ann->data.deopt_idx)
                        seen_deopt_idx = 1;
#endif
                    break;
#ifndef NDEBUG
                case MVM_SPESH_ANN_DEOPT_PRE_INS:
                    if (deopt_idx == ann->data.deopt_idx)
                        seen_deopt_idx = 1;
                break;
#endif
                }
                ann = ann->next;
            }
            assert(deopt_idx < 0 || seen_deopt_idx);
        }

        ins = ins->next;
    }
}

/* Generate bytecode from a spesh graph. */
MVMSpeshCode * MVM_spesh_codegen(MVMThreadContext *tc, MVMSpeshGraph *g) {
    MVMSpeshCode *res;
    MVMSpeshBB   *bb;
    MVMuint32     i, hanlen;

    /* Initialize writer state. */
    SpeshWriterState *ws     = MVM_malloc(sizeof(SpeshWriterState));
    ws->bytecode_pos    = 0;
    ws->bytecode_alloc  = 1024;
    ws->bytecode        = MVM_malloc(ws->bytecode_alloc);
    ws->bb_offsets      = MVM_malloc(g->num_bbs * sizeof(MVMint32));
    ws->num_fixups      = 0;
    ws->alloc_fixups    = 64;
    ws->fixup_locations = MVM_malloc(ws->alloc_fixups * sizeof(MVMint32));
    ws->fixup_bbs       = MVM_malloc(ws->alloc_fixups * sizeof(MVMSpeshBB *));
    for (i = 0; i < g->num_bbs; i++)
        ws->bb_offsets[i] = -1;
    MVM_VECTOR_INIT(ws->deopt_usage_info, 0);
    MVM_VECTOR_INIT(ws->all_deopt_users.idxs, 0);
    MVM_VECTOR_INIT(ws->all_deopt_users.seen_phis, 0);
    MVM_VECTOR_INIT(ws->deopt_synth_addrs, 0);

    /* Create copy of handlers, and -1 all offsets so we can catch missing
     * updates. */
    hanlen = g->num_handlers * sizeof(MVMFrameHandler);
    if (hanlen) {
        ws->handlers = MVM_malloc(hanlen);
        memcpy(ws->handlers, g->handlers, hanlen);
        for (i = 0; i < g->num_handlers; i++) {
            ws->handlers[i].start_offset = -1;
            ws->handlers[i].end_offset   = -1;
            ws->handlers[i].goto_offset  = -1;
        }
    }
    else {
        ws->handlers = NULL;
    }

    /* -1 all the deopt targets, so we'll easily catch those that don't get
     * mapped if we try to use them. Same for inlines. */
    for (i = 0; i < g->num_deopt_addrs; i++)
        g->deopt_addrs[i * 2 + 1] = -1;
    for (i = 0; i < g->num_inlines; i++) {
        g->inlines[i].start = -1;
        g->inlines[i].end = -1;
    }

    /* Write out each of the basic blocks, in linear order. Skip the first,
     * dummy, block. */
    bb = g->entry->linear_next;
    while (bb) {
        ws->bb_offsets[bb->idx] = ws->bytecode_pos;
        write_instructions(tc, g, ws, bb);
        bb = bb->linear_next;
    }

    /* Fixup labels we were too early for. */
    for (i = 0; i < ws->num_fixups; i++)
        memcpy((ws->bytecode + ws->fixup_locations[i]),
               ws->bb_offsets + ws->fixup_bbs[i]->idx, sizeof(MVMuint32));

    /* Ensure all handlers that are reachable got fixed up. */
    for (i = 0; i < g->num_handlers; i++) {
        if (g->unreachable_handlers && g->unreachable_handlers[i]) {
            ws->handlers[i].start_offset = -1;
            ws->handlers[i].end_offset = -1;
            ws->handlers[i].goto_offset = -1;
        }
        else if (ws->handlers[i].start_offset == (MVMuint32)-1 ||
                 ws->handlers[i].end_offset   == (MVMuint32)-1 ||
                 ws->handlers[i].goto_offset  == (MVMuint32)-1) {
            MVM_oops(tc, "Spesh: failed to fix up handler %d in %s (%d, %d, %d)",
                i,
                MVM_string_utf8_maybe_encode_C_string(tc, g->sf->body.name),
                (int)ws->handlers[i].start_offset,
                (int)ws->handlers[i].end_offset,
                (int)ws->handlers[i].goto_offset);
        }
    }

    /* Ensure all inlines got fixed up. */
    for (i = 0; i < g->num_inlines; i++) {
        if (g->inlines[i].unreachable) {
            g->inlines[i].start = -1;
            g->inlines[i].end = -1;
        }
        else {
            if (g->inlines[i].start == (MVMuint32)-1 || g->inlines[i].end == (MVMuint32)-1)
                MVM_oops(tc, "Spesh: failed to fix up inline %d (%s) %d %d",
                    i,
                    MVM_string_utf8_maybe_encode_C_string(tc, g->inlines[i].sf->body.name),
                    g->inlines[i].start,
                    g->inlines[i].end
                );
        }
    }

    /* Add terminating -1 to the deopt usage info. */
    if (g->facts)
        MVM_VECTOR_PUSH(ws->deopt_usage_info, -1);

    /* Produce result data structure. */
    res                   = MVM_malloc(sizeof(MVMSpeshCode));
    res->bytecode         = ws->bytecode;
    res->bytecode_size    = ws->bytecode_pos;
    res->handlers         = ws->handlers;
    res->deopt_usage_info = ws->deopt_usage_info;
    res->deopt_synths     = ws->deopt_synth_addrs;
    res->num_deopt_synths = ws->deopt_synth_addrs_num / 2; /* 2 values per entry */

    /* Cleanup. */
    MVM_free(ws->bb_offsets);
    MVM_free(ws->fixup_locations);
    MVM_free(ws->fixup_bbs);
    MVM_VECTOR_DESTROY(ws->all_deopt_users.idxs);
    MVM_VECTOR_DESTROY(ws->all_deopt_users.seen_phis);
    MVM_free(ws);

    return res;
}
