// See the file "COPYING" in the main distribution directory for copyright.

// Methods for dealing with ZAM branches.

#include "zeek/Desc.h"
#include "zeek/Reporter.h"
#include "zeek/script_opt/ZAM/Compile.h"

namespace zeek::detail {

void ZAMCompiler::PushGoTos(GoToSets& gotos) { gotos.emplace_back(); }

void ZAMCompiler::ResolveGoTos(GoToSets& gotos, const InstLabel l, ControlFlowType cft) {
    for ( auto& gi : gotos.back() ) {
        SetGoTo(gi, l);
        if ( cft != CFT_NONE )
            AddCFT(insts1[gi.stmt_num], cft);
    }

    gotos.pop_back();
}

ZAMStmt ZAMCompiler::GenGoTo(GoToSet& v) {
    auto g = GoToStub();
    v.push_back(g.stmt_num);

    return g;
}

ZAMStmt ZAMCompiler::GoToStub() {
    ZInstI z(OP_GOTO_b, 0);
    z.op_type = OP_V_I1;
    return AddInst(z);
}

ZAMStmt ZAMCompiler::GoTo(const InstLabel l) {
    ZInstI inst(OP_GOTO_b, 0);
    inst.target = l;
    inst.target_slot = 1;
    inst.op_type = OP_V_I1;
    return AddInst(inst);
}

InstLabel ZAMCompiler::GoToTarget(const ZAMStmt s) { return insts1[s.stmt_num]; }

InstLabel ZAMCompiler::GoToTargetBeyond(const ZAMStmt s) {
    int n = s.stmt_num;

    if ( n == int(insts1.size()) - 1 ) {
        if ( ! pending_inst )
            pending_inst = new ZInstI();

        return pending_inst;
    }

    return insts1[n + 1];
}

void ZAMCompiler::SetTarget(ZInstI* inst, const InstLabel l, int slot) {
    inst->target = l;
    inst->target_slot = slot;
}

ZInstI* ZAMCompiler::FindLiveTarget(ZInstI* goto_target) {
    if ( goto_target == pending_inst )
        return goto_target;

    int idx = goto_target->inst_num;
    ASSERT(idx >= 0 && idx <= int(insts1.size()));

    while ( idx < int(insts1.size()) && ! insts1[idx]->live )
        ++idx;

    if ( idx == int(insts1.size()) )
        return pending_inst;
    else
        return insts1[idx];
}

void ZAMCompiler::ConcretizeBranch(ZInstI* inst, ZInstI* target, int target_slot) {
    int t; // instruction number of target

    if ( target == pending_inst ) {
        if ( insts2.empty() )
            // We're doing this in the context of concretizing
            // intermediary instructions for dumping them out.
            t = insts1.size();
        else
            t = insts2.size();
    }
    else
        t = target->inst_num;

    switch ( target_slot ) {
        case 1: inst->v1 = t; break;
        case 2: inst->v2 = t; break;
        case 3: inst->v3 = t; break;
        case 4: inst->v4 = t; break;

        default: reporter->InternalError("bad GoTo target");
    }
}

void ZAMCompiler::SetV1(ZAMStmt s, const InstLabel l) {
    auto inst = insts1[s.stmt_num];
    SetTarget(inst, l, 1);
    ASSERT(inst->op_type == OP_V || inst->op_type == OP_V_I1);
    inst->op_type = OP_V_I1;
}

void ZAMCompiler::SetV2(ZAMStmt s, const InstLabel l) {
    auto inst = insts1[s.stmt_num];
    SetTarget(inst, l, 2);

    auto& ot = inst->op_type;

    if ( ot == OP_VV )
        ot = OP_VV_I2;

    else if ( ot == OP_VC || ot == OP_VVC )
        ot = OP_VVC_I2;

    else
        ASSERT(ot == OP_VV_I2 || ot == OP_VV_I1_I2 || ot == OP_VVC_I2);
}

void ZAMCompiler::SetV3(ZAMStmt s, const InstLabel l) {
    auto inst = insts1[s.stmt_num];
    SetTarget(inst, l, 3);

    auto ot = inst->op_type;

    if ( ot == OP_VVV_I2_I3 || ot == OP_VVVC_I3 )
        return;

    ASSERT(ot == OP_VV || ot == OP_VVV || ot == OP_VVV_I3);
    inst->op_type = OP_VVV_I3;
}

void ZAMCompiler::SetV4(ZAMStmt s, const InstLabel l) {
    auto inst = insts1[s.stmt_num];
    SetTarget(inst, l, 4);

    auto ot = inst->op_type;

    ASSERT(ot == OP_VVVV || ot == OP_VVVV_I4 || ot == OP_VVVV_I3_I4);
    if ( ot == OP_VVVV )
        inst->op_type = OP_VVVV_I4;
}

} // namespace zeek::detail
