/*
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "pandagen.h"

#include <binder/binder.h>
#include <binder/scope.h>
#include <binder/variable.h>
#include <compiler/base/catchTable.h>
#include <compiler/base/lexenv.h>
#include <compiler/base/literals.h>
#include <compiler/core/compilerContext.h>
#include <compiler/core/labelTarget.h>
#include <compiler/core/regAllocator.h>
#include <compiler/function/asyncFunctionBuilder.h>
#include <compiler/function/asyncGeneratorFunctionBuilder.h>
#include <compiler/function/functionBuilder.h>
#include <compiler/function/generatorFunctionBuilder.h>
#include <es2panda.h>
#include <gen/isa.h>
#include <ir/base/classDefinition.h>
#include <ir/base/methodDefinition.h>
#include <ir/base/scriptFunction.h>
#include <ir/base/spreadElement.h>
#include <ir/expressions/callExpression.h>
#include <ir/expressions/functionExpression.h>
#include <ir/expressions/identifier.h>
#include <ir/expressions/literals/numberLiteral.h>
#include <ir/expressions/literals/stringLiteral.h>
#include <ir/expressions/newExpression.h>
#include <ir/statement.h>
#include <typescript/extractor/typeExtractor.h>
#include <util/concurrent.h>
#include <util/helpers.h>
#include <util/patchFix.h>

namespace panda::es2panda::compiler {

// PandaGen

void PandaGen::SetFunctionKind()
{
    if (rootNode_->IsProgram()) {
        funcKind_ = panda::panda_file::FunctionKind::FUNCTION;
        return;
    }

    auto *func = rootNode_->AsScriptFunction();
    if (func->IsConcurrent()) {
        funcKind_ = panda::panda_file::FunctionKind::CONCURRENT_FUNCTION;
        return;
    }

    if (func->IsMethod()) {
        return;
    }

    if (func->IsAsync()) {
        if (func->IsGenerator()) {
            funcKind_ = panda::panda_file::FunctionKind::ASYNC_GENERATOR_FUNCTION;
            return;
        }

        if (func->IsArrow()) {
            funcKind_ = panda::panda_file::FunctionKind::ASYNC_NC_FUNCTION;
            return;
        }

        funcKind_ = panda::panda_file::FunctionKind::ASYNC_FUNCTION;
        return;
    }

    if (func->IsGenerator()) {
        funcKind_ = panda::panda_file::FunctionKind::GENERATOR_FUNCTION;
        return;
    }

    if (func->IsArrow()) {
        funcKind_ = panda::panda_file::FunctionKind::NC_FUNCTION;
        return;
    }

    funcKind_ = panda::panda_file::FunctionKind::FUNCTION;
}

Label *PandaGen::AllocLabel()
{
    std::string id = std::string {Label::PREFIX} + std::to_string(labelId_++);
    return ra_.AllocLabel(std::move(id));
}

bool PandaGen::IsDebug() const
{
    return context_->IsDebug();
}

bool PandaGen::isDebuggerEvaluateExpressionMode() const
{
    return context_->isDebuggerEvaluateExpressionMode();
}

std::string PandaGen::SourceFile() const
{
    return context_->SourceFile();
}

uint32_t PandaGen::ParamCount() const
{
    if (rootNode_->IsProgram()) {
        return 0;
    }

    return rootNode_->AsScriptFunction()->Params().size();
}

uint32_t PandaGen::FormalParametersCount() const
{
    if (rootNode_->IsProgram()) {
        return 0;
    }

    ASSERT(rootNode_->IsScriptFunction());

    return rootNode_->AsScriptFunction()->FormalParamsLength();
}

uint32_t PandaGen::InternalParamCount() const
{
    if (rootNode_->IsProgram() && context_->Binder()->Program()->IsCommonjs()) {
        return binder::Binder::CJS_MANDATORY_PARAMS_NUMBER;
    }
    return ParamCount() + binder::Binder::MANDATORY_PARAMS_NUMBER;
}

const util::StringView &PandaGen::InternalName() const
{
    return topScope_->InternalName();
}

const util::StringView &PandaGen::FunctionName() const
{
    return topScope_->Name();
}

binder::Binder *PandaGen::Binder() const
{
    return context_->Binder();
}

void PandaGen::FunctionInit(CatchTable *catchTable)
{
    if (rootNode_->IsProgram()) {
        builder_ = allocator_->New<FunctionBuilder>(this, catchTable);
        return;
    }

    const ir::ScriptFunction *func = rootNode_->AsScriptFunction();

    if (func->IsAsync()) {
        if (func->IsGenerator()) {
            builder_ = allocator_->New<AsyncGeneratorFunctionBuilder>(this, catchTable);
            return;
        }

        builder_ = allocator_->New<AsyncFunctionBuilder>(this, catchTable);
        return;
    }

    if (func->IsGenerator()) {
        builder_ = allocator_->New<GeneratorFunctionBuilder>(this, catchTable);
        return;
    }

    builder_ = allocator_->New<FunctionBuilder>(this, catchTable);
}

bool PandaGen::FunctionHasFinalizer() const
{
    if (rootNode_->IsProgram()) {
        return false;
    }

    const ir::ScriptFunction *func = rootNode_->AsScriptFunction();

    return func->IsAsync() || func->IsGenerator();
}

bool PandaGen::IsAsyncFunction() const
{
    const ir::ScriptFunction *func = rootNode_->AsScriptFunction();
    return func->IsAsync() && !func->IsGenerator();
}

void PandaGen::FunctionEnter()
{
    builder_->Prepare(rootNode_->AsScriptFunction());
}

void PandaGen::FunctionExit()
{
    builder_->CleanUp(rootNode_->AsScriptFunction());
}

void PandaGen::InitializeLexEnv(const ir::AstNode *node)
{
    FrontAllocator fa(this);

    if (topScope_->NeedLexEnv()) {
        NewLexicalEnv(node, topScope_->LexicalSlots(), topScope_);
    }
}

void PandaGen::CopyFunctionArguments(const ir::AstNode *node)
{
    FrontAllocator fa(this);
    VReg targetReg = totalRegs_;

    for (const auto *param : topScope_->ParamScope()->Params()) {
        if (param->LexicalBound()) {
            StoreLexicalVar(node, 0, param->LexIdx(), targetReg++);
            continue;
        }
        MoveVreg(node, param->Vreg(), targetReg++);
    }

    auto fn = [this](const ir::AstNode *node) {
        // For function type, node here is ScriptFunction or BlockStatement
        if (node->IsScriptFunction()) {
            typedFunc_.first = context_->TypeRecorder()->GetNodeTypeIndex(node);
        }
        // For method 'this' type, node's parent should be FunctionExpression
        if (node->Parent() != nullptr && node->Parent()->Parent() != nullptr) {
            auto method = node->Parent()->Parent();
            if (method->IsMethodDefinition()) {
                auto typeIndex = context_->TypeRecorder()->GetNodeTypeIndex(method->Parent());
                if (!method->AsMethodDefinition()->IsStatic()) {
                    typeIndex = context_->TypeRecorder()->GetClassInst(typeIndex);
                }
                typedFunc_.second = typeIndex;
            }
        }
    };
    if (context_->IsTypeExtractorEnabled()) {
        fn(node);
    }
}

LiteralBuffer *PandaGen::NewLiteralBuffer()
{
    return allocator_->New<LiteralBuffer>(allocator_);
}

int32_t PandaGen::AddLiteralBuffer(LiteralBuffer *buf)
{
    buffStorage_.push_back(buf);
    buf->SetIndex(context_->NewLiteralIndex());
    return buf->Index();
}

int32_t PandaGen::AddLexicalVarNamesForDebugInfo(ArenaMap<uint32_t, std::pair<util::StringView, int>> &lexicalVars)
{
    auto *buf = NewLiteralBuffer();
    buf->Add(Allocator()->New<ir::NumberLiteral>(lexicalVars.size()));
    for (auto &iter : lexicalVars) {
        buf->Add(Allocator()->New<ir::StringLiteral>(iter.second.first));
        buf->Add(Allocator()->New<ir::NumberLiteral>(iter.first));
    }
    return AddLiteralBuffer(buf);
}

void PandaGen::GetFunctionObject(const ir::AstNode *node)
{
    LoadAccFromLexEnv(node, scope_->Find(binder::Binder::MANDATORY_PARAM_FUNC));
}

void PandaGen::GetNewTarget(const ir::AstNode *node)
{
    LoadAccFromLexEnv(node, scope_->Find(binder::Binder::MANDATORY_PARAM_NEW_TARGET));
}

void PandaGen::GetThis(const ir::AstNode *node)
{
    LoadAccFromLexEnv(node, scope_->Find(binder::Binder::MANDATORY_PARAM_THIS));
}

void PandaGen::SetThis(const ir::AstNode *node)
{
    StoreAccToLexEnv(node, scope_->Find(binder::Binder::MANDATORY_PARAM_THIS), true);
}

void PandaGen::LoadVar(const ir::Identifier *node, const binder::ScopeFindResult &result)
{
    auto *var = result.variable;

    if (!var || var->Declaration()->IsDeclare()) {
        TryLoadGlobalByName(node, result.name);
        return;
    }

    if (var->IsGlobalVariable()) {
        LoadGlobalVar(node, var->Name());
        return;
    }

    if (var->IsModuleVariable()) {
        var->HasFlag(binder::VariableFlags::LOCAL_EXPORT) ? LoadLocalModuleVariable(node, var->AsModuleVariable()) :
                                                            LoadExternalModuleVariable(node, var->AsModuleVariable());
        if (var->Declaration()->IsLetOrConstOrClassDecl()) {
            ThrowUndefinedIfHole(node, var->Name());
        }
        return;
    }

    ASSERT(var->IsLocalVariable());

    if (var->Declaration()->IsLetOrConstOrClassDecl() && result.scope->IsGlobalScope()) {
        TryLoadGlobalByName(node, result.name);
        return;
    }

    LoadAccFromLexEnv(node, result);
}

void PandaGen::StoreVar(const ir::AstNode *node, const binder::ScopeFindResult &result, bool isDeclaration)
{
    binder::Variable *var = result.variable;

    if (!var) {
        TryStoreGlobalByName(node, result.name);
        return;
    }

    if (var->IsGlobalVariable()) {
        StoreGlobalVar(node, var->Name());
        return;
    }

    if (var->IsModuleVariable()) {
        if (!isDeclaration && var->Declaration()->IsConstDecl()) {
            ThrowConstAssignment(node, var->Name());
            return;
        }

        if (!isDeclaration &&
            (var->Declaration()->IsLetDecl() || var->Declaration()->IsClassDecl())) {
            RegScope rs(this);
            VReg valueReg = AllocReg();
            StoreAccumulator(node, valueReg);
            LoadLocalModuleVariable(node, var->AsModuleVariable());
            ThrowUndefinedIfHole(node, var->Name());
            LoadAccumulator(node, valueReg);
        }

        StoreModuleVariable(node, var->AsModuleVariable());
        return;
    }

    ASSERT(var->IsLocalVariable());

    if (var->Declaration()->IsLetOrConstOrClassDecl() && result.scope->IsGlobalScope()) {
        if (!isDeclaration) {
            TryStoreGlobalByName(node, var->Name());
        } else if (var->Declaration()->IsLetDecl() || var->Declaration()->IsClassDecl()) {
            StLetOrClassToGlobalRecord(node, var->Name());
        } else if (var->Declaration()->IsConstDecl()) {
            StConstToGlobalRecord(node, var->Name());
        }

        return;
    }

    StoreAccToLexEnv(node, result, isDeclaration);
}

void PandaGen::StoreAccumulator(const ir::AstNode *node, VReg vreg)
{
    ra_.Emit<Sta>(node, vreg);
}

void PandaGen::StoreAccumulatorWithType(const ir::AstNode *node, int64_t typeIndex, VReg vreg)
{
    ra_.EmitWithType<Sta>(node, typeIndex, vreg);
}

void PandaGen::LoadAccFromArgs(const ir::AstNode *node)
{
    const auto *varScope = scope_->AsVariableScope();

    if (!varScope->HasFlag(binder::VariableScopeFlags::USE_ARGS)) {
        return;
    }

    binder::ScopeFindResult res = scope_->Find(binder::Binder::FUNCTION_ARGUMENTS);
    ASSERT(res.scope);

    GetUnmappedArgs(node);
    StoreAccToLexEnv(node, res, true);
}

void PandaGen::LoadObjProperty(const ir::AstNode *node, VReg obj, const Operand &prop)
{
    if (std::holds_alternative<VReg>(prop)) {
        LoadAccumulator(node, std::get<VReg>(prop));
        LoadObjByValue(node, obj);
        return;
    }

    if (std::holds_alternative<int64_t>(prop)) {
        LoadObjByIndex(node, obj, std::get<int64_t>(prop));
        return;
    }

    ASSERT(std::holds_alternative<util::StringView>(prop));
    LoadObjByName(node, obj, std::get<util::StringView>(prop));
}

void PandaGen::StoreObjProperty(const ir::AstNode *node, VReg obj, const Operand &prop)
{
    if (std::holds_alternative<VReg>(prop)) {
        StoreObjByValue(node, obj, std::get<VReg>(prop));
        return;
    }

    if (std::holds_alternative<int64_t>(prop)) {
        StoreObjByIndex(node, obj, std::get<int64_t>(prop));
        return;
    }

    ASSERT(std::holds_alternative<util::StringView>(prop));
    StoreObjByName(node, obj, std::get<util::StringView>(prop));
}

void PandaGen::StoreOwnProperty(const ir::AstNode *node, VReg obj, const Operand &prop, bool nameSetting)
{
    if (std::holds_alternative<VReg>(prop)) {
        StOwnByValue(node, obj, std::get<VReg>(prop), nameSetting);
        return;
    }

    if (std::holds_alternative<int64_t>(prop)) {
        StOwnByIndex(node, obj, std::get<int64_t>(prop));
        return;
    }

    ASSERT(std::holds_alternative<util::StringView>(prop));
    StOwnByName(node, obj, std::get<util::StringView>(prop), nameSetting);
}

constexpr size_t DEBUGGER_GET_SET_ARGS_NUM = 2;

void PandaGen::LoadObjByNameViaDebugger(const ir::AstNode *node, const util::StringView &name,
                                        bool throwUndefinedIfHole)
{
    RegScope rs(this);
    VReg global = AllocReg();
    LoadConst(node, compiler::Constant::JS_GLOBAL);
    StoreAccumulator(node, global);
    LoadObjByName(node, global, "debuggerGetValue");
    VReg debuggerGetValueReg = AllocReg();
    StoreAccumulator(node, debuggerGetValueReg);
    VReg variableReg = AllocReg();
    LoadAccumulatorString(node, name);
    StoreAccumulator(node, variableReg);
    VReg boolFlag = AllocReg();
    if (throwUndefinedIfHole) {
        LoadConst(node, compiler::Constant::JS_TRUE);
    } else {
        LoadConst(node, compiler::Constant::JS_FALSE);
    }
    StoreAccumulator(node, boolFlag);
    Call(node, debuggerGetValueReg, DEBUGGER_GET_SET_ARGS_NUM);
}

void PandaGen::TryLoadGlobalByName(const ir::AstNode *node, const util::StringView &name)
{
    if (isDebuggerEvaluateExpressionMode()) {
        LoadObjByNameViaDebugger(node, name, true);
    } else {
        int64_t typeIndex = extractor::TypeExtractor::GetBuiltinTypeIndex(name);
        if (context_->IsTypeExtractorEnabled() && typeIndex != extractor::TypeRecorder::PRIMITIVETYPE_ANY) {
            ra_.EmitWithType<Tryldglobalbyname>(node, typeIndex, 0, name);
        } else {
            ra_.Emit<Tryldglobalbyname>(node, 0, name);
        }
    }
    strings_.insert(name);
}

void PandaGen::StoreObjByNameViaDebugger(const ir::AstNode *node, const util::StringView &name)
{
    RegScope rs(this);
    VReg valueReg = AllocReg();
    StoreAccumulator(node, valueReg);
    VReg global = AllocReg();
    LoadConst(node, compiler::Constant::JS_GLOBAL);
    StoreAccumulator(node, global);
    LoadObjByName(node, global, "debuggerSetValue");
    VReg debuggerSetValueReg = AllocReg();
    StoreAccumulator(node, debuggerSetValueReg);
    VReg variableReg = AllocReg();
    LoadAccumulatorString(node, name);
    StoreAccumulator(node, variableReg);
    MoveVreg(node, AllocReg(), valueReg);
    Call(node, debuggerSetValueReg, DEBUGGER_GET_SET_ARGS_NUM);
}

void PandaGen::TryStoreGlobalByName(const ir::AstNode *node, const util::StringView &name)
{
    if (isDebuggerEvaluateExpressionMode()) {
        StoreObjByNameViaDebugger(node, name);
    } else {
        ra_.Emit<Trystglobalbyname>(node, 0, name);
    }
    strings_.insert(name);
}

void PandaGen::LoadObjByName(const ir::AstNode *node, VReg obj, const util::StringView &prop)
{
    LoadAccumulator(node, obj); // object is load to acc
    ra_.Emit<Ldobjbyname>(node, 0, prop);
    strings_.insert(prop);
}

void PandaGen::StoreObjByName(const ir::AstNode *node, VReg obj, const util::StringView &prop)
{
    ra_.Emit<Stobjbyname>(node, 0, prop, obj);
    strings_.insert(prop);
}

void PandaGen::LoadObjByIndex(const ir::AstNode *node, VReg obj, int64_t index)
{
    LoadAccumulator(node, obj); // object is load to acc
    if (index <= util::Helpers::MAX_INT16) {
        ra_.Emit<Ldobjbyindex>(node, 0, index);
        return;
    }

    ra_.Emit<WideLdobjbyindex>(node, index);
}

void PandaGen::LoadObjByValue(const ir::AstNode *node, VReg obj)
{
    ra_.Emit<Ldobjbyvalue>(node, 0, obj); // prop is in acc
}

void PandaGen::StoreObjByValue(const ir::AstNode *node, VReg obj, VReg prop)
{
    ra_.Emit<Stobjbyvalue>(node, 0, obj, prop);
}

void PandaGen::StoreObjByIndex(const ir::AstNode *node, VReg obj, int64_t index)
{
    if (index <= util::Helpers::MAX_INT16) {
        ra_.Emit<Stobjbyindex>(node, 0, obj, index);
        return;
    }

    ra_.Emit<WideStobjbyindex>(node, obj, index);
}

void PandaGen::StOwnByName(const ir::AstNode *node, VReg obj, const util::StringView &prop, bool nameSetting)
{
    nameSetting ? ra_.Emit<Stownbynamewithnameset>(node, 0, prop, obj) :
                  ra_.Emit<Stownbyname>(node, 0, prop, obj);
    strings_.insert(prop);
}

void PandaGen::StOwnByValue(const ir::AstNode *node, VReg obj, VReg prop, bool nameSetting)
{
    nameSetting ? ra_.Emit<Stownbyvaluewithnameset>(node, 0, obj, prop) :
                  ra_.Emit<Stownbyvalue>(node, 0, obj, prop);
}

void PandaGen::StOwnByIndex(const ir::AstNode *node, VReg obj, int64_t index)
{
    if (index <= util::Helpers::MAX_INT16) {
        ra_.Emit<Stownbyindex>(node, 0, obj, index);
        return;
    }

    ra_.Emit<WideStownbyindex>(node, obj, index);
}

void PandaGen::DeleteObjProperty(const ir::AstNode *node, VReg obj, const Operand &prop)
{
    if (std::holds_alternative<VReg>(prop)) {
        LoadAccumulator(node, std::get<VReg>(prop));
    } else if (std::holds_alternative<int64_t>(prop)) {
        LoadAccumulatorInt(node, static_cast<size_t>(std::get<int64_t>(prop)));
    } else {
        ASSERT(std::holds_alternative<util::StringView>(prop));
        LoadAccumulatorString(node, std::get<util::StringView>(prop));
    }

    ra_.Emit<Delobjprop>(node, obj); // property is load to acc
}

void PandaGen::LoadAccumulator(const ir::AstNode *node, VReg reg)
{
    ra_.Emit<Lda>(node, reg);
}

void PandaGen::LoadGlobalVar(const ir::AstNode *node, const util::StringView &name)
{
    ra_.Emit<Ldglobalvar>(node, 0, name);
    strings_.insert(name);
}

void PandaGen::StoreGlobalVar(const ir::AstNode *node, const util::StringView &name)
{
    ra_.Emit<Stglobalvar>(node, 0, name);
    strings_.insert(name);
}

void PandaGen::LoadAccFromLexEnv(const ir::AstNode *node, const binder::ScopeFindResult &result)
{
    VirtualLoadVar::Expand(this, node, result);
}

void PandaGen::StoreAccToLexEnv(const ir::AstNode *node, const binder::ScopeFindResult &result, bool isDeclaration)
{
    VirtualStoreVar::Expand(this, node, result, isDeclaration);
}

void PandaGen::LoadAccumulatorString(const ir::AstNode *node, const util::StringView &str)
{
    ra_.Emit<LdaStr>(node, str);
    strings_.insert(str);
}

void PandaGen::LoadAccumulatorFloat(const ir::AstNode *node, double num)
{
    ra_.Emit<Fldai>(node, num);
}

void PandaGen::LoadAccumulatorInt(const ir::AstNode *node, int32_t num)
{
    ra_.Emit<Ldai>(node, num);
}

void PandaGen::LoadAccumulatorInt(const ir::AstNode *node, size_t num)
{
    ra_.Emit<Ldai>(node, static_cast<int64_t>(num));
}

void PandaGen::LoadAccumulatorBigInt(const ir::AstNode *node, const util::StringView &num)
{
    ra_.Emit<Ldbigint>(node, num);
    strings_.insert(num);
}

void PandaGen::StoreConst(const ir::AstNode *node, VReg reg, Constant id)
{
    LoadConst(node, id);
    StoreAccumulator(node, reg);
}

void PandaGen::LoadConst(const ir::AstNode *node, Constant id)
{
    switch (id) {
        case Constant::JS_HOLE: {
            ra_.Emit<Ldhole>(node);
            break;
        }
        case Constant::JS_NAN: {
            ra_.Emit<Ldnan>(node);
            break;
        }
        case Constant::JS_INFINITY: {
            ra_.Emit<Ldinfinity>(node);
            break;
        }
        case Constant::JS_GLOBAL: {
            ra_.Emit<Ldglobal>(node);
            break;
        }
        case Constant::JS_UNDEFINED: {
            ra_.Emit<Ldundefined>(node);
            break;
        }
        case Constant::JS_SYMBOL: {
            ra_.Emit<Ldsymbol>(node);
            break;
        }
        case Constant::JS_NULL: {
            ra_.Emit<Ldnull>(node);
            break;
        }
        case Constant::JS_TRUE: {
            ra_.Emit<Ldtrue>(node);
            break;
        }
        case Constant::JS_FALSE: {
            ra_.Emit<Ldfalse>(node);
            break;
        }
        default: {
            UNREACHABLE();
        }
    }
}

void PandaGen::MoveVreg(const ir::AstNode *node, VReg vd, VReg vs)
{
    ra_.Emit<Mov>(node, vd, vs);
}

void PandaGen::SetLabel([[maybe_unused]] const ir::AstNode *node, Label *label)
{
    ra_.AddLabel(label);
}

void PandaGen::Branch(const ir::AstNode *node, Label *label)
{
    ra_.Emit<Jmp>(node, label);
}

bool PandaGen::CheckControlFlowChange() const
{
    const auto *iter = dynamicContext_;

    while (iter) {
        if (iter->HasFinalizer()) {
            return true;
        }

        iter = iter->Prev();
    }

    return false;
}

Label *PandaGen::ControlFlowChangeBreak(const ir::Identifier *label)
{
    auto *iter = dynamicContext_;

    util::StringView labelName = label ? label->Name() : LabelTarget::BREAK_LABEL;
    Label *breakTarget = nullptr;

    while (iter) {
        iter->AbortContext(ControlFlowChange::BREAK, labelName);

        const auto &labelTargetName = iter->Target().BreakLabel();

        if (iter->Target().BreakTarget()) {
            breakTarget = iter->Target().BreakTarget();
        }

        if (labelTargetName == labelName) {
            break;
        }

        iter = iter->Prev();
    }

    return breakTarget;
}

Label *PandaGen::ControlFlowChangeContinue(const ir::Identifier *label)
{
    auto *iter = dynamicContext_;
    util::StringView labelName = label ? label->Name() : LabelTarget::CONTINUE_LABEL;
    Label *continueTarget = nullptr;

    while (iter) {
        iter->AbortContext(ControlFlowChange::CONTINUE, labelName);

        const auto &labelTargetName = iter->Target().ContinueLabel();

        if (iter->Target().ContinueTarget()) {
            continueTarget = iter->Target().ContinueTarget();
        }

        if (labelTargetName == labelName) {
            break;
        }

        iter = iter->Prev();
    }

    return continueTarget;
}

void PandaGen::ControlFlowChangeReturn()
{
    auto *iter = dynamicContext_;
    while (iter) {
        iter->AbortContext(ControlFlowChange::BREAK, LabelTarget::RETURN_LABEL);
        iter = iter->Prev();
    }
}

void PandaGen::Condition(const ir::AstNode *node, lexer::TokenType op, VReg lhs, Label *ifFalse)
{
    switch (op) {
        case lexer::TokenType::PUNCTUATOR_EQUAL: {
            Equal(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_NOT_EQUAL: {
            NotEqual(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_STRICT_EQUAL: {
            StrictEqual(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL: {
            StrictNotEqual(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_LESS_THAN: {
            LessThan(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_LESS_THAN_EQUAL: {
            LessEqual(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_GREATER_THAN: {
            GreaterThan(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_GREATER_THAN_EQUAL: {
            GreaterEqual(node, lhs);
            break;
        }
        default: {
            UNREACHABLE();
        }
    }

    ra_.Emit<Jeqz>(node, ifFalse);
}

void PandaGen::Unary(const ir::AstNode *node, lexer::TokenType op, VReg operand)
{
    switch (op) {
        case lexer::TokenType::PUNCTUATOR_PLUS: {
            ToNumber(node, operand);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_MINUS: {
            LoadAccumulator(node, operand);
            ra_.Emit<Neg>(node, 0);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_TILDE: {
            LoadAccumulator(node, operand);
            ra_.Emit<Not>(node, 0);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_EXCLAMATION_MARK: {
            Negate(node);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_PLUS_PLUS: {
            LoadAccumulator(node, operand);
            ra_.Emit<Inc>(node, 0);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_MINUS_MINUS: {
            LoadAccumulator(node, operand);
            ra_.Emit<Dec>(node, 0);
            break;
        }
        case lexer::TokenType::KEYW_VOID:
        case lexer::TokenType::KEYW_DELETE: {
            LoadConst(node, Constant::JS_UNDEFINED);
            break;
        }
        default: {
            UNREACHABLE();
        }
    }
}

void PandaGen::Binary(const ir::AstNode *node, lexer::TokenType op, VReg lhs)
{
    switch (op) {
        case lexer::TokenType::PUNCTUATOR_EQUAL: {
            Equal(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_NOT_EQUAL: {
            NotEqual(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_STRICT_EQUAL: {
            StrictEqual(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL: {
            StrictNotEqual(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_LESS_THAN: {
            LessThan(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_LESS_THAN_EQUAL: {
            LessEqual(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_GREATER_THAN: {
            GreaterThan(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_GREATER_THAN_EQUAL: {
            GreaterEqual(node, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_PLUS:
        case lexer::TokenType::PUNCTUATOR_PLUS_EQUAL: {
            ra_.Emit<Add2>(node, 0, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_MINUS:
        case lexer::TokenType::PUNCTUATOR_MINUS_EQUAL: {
            ra_.Emit<Sub2>(node, 0, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_MULTIPLY:
        case lexer::TokenType::PUNCTUATOR_MULTIPLY_EQUAL: {
            ra_.Emit<Mul2>(node, 0, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_DIVIDE:
        case lexer::TokenType::PUNCTUATOR_DIVIDE_EQUAL: {
            ra_.Emit<Div2>(node, 0, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_MOD:
        case lexer::TokenType::PUNCTUATOR_MOD_EQUAL: {
            ra_.Emit<Mod2>(node, 0, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_EXPONENTIATION_EQUAL:
        case lexer::TokenType::PUNCTUATOR_EXPONENTIATION: {
            ra_.Emit<Exp>(node, 0, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_LEFT_SHIFT:
        case lexer::TokenType::PUNCTUATOR_LEFT_SHIFT_EQUAL: {
            ra_.Emit<Shl2>(node, 0, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT:
        case lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT_EQUAL: {
            ra_.Emit<Ashr2>(node, 0, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT:
        case lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT_EQUAL: {
            ra_.Emit<Shr2>(node, 0, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_BITWISE_AND:
        case lexer::TokenType::PUNCTUATOR_BITWISE_AND_EQUAL: {
            ra_.Emit<And2>(node, 0, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_BITWISE_OR:
        case lexer::TokenType::PUNCTUATOR_BITWISE_OR_EQUAL: {
            ra_.Emit<Or2>(node, 0, lhs);
            break;
        }
        case lexer::TokenType::PUNCTUATOR_BITWISE_XOR:
        case lexer::TokenType::PUNCTUATOR_BITWISE_XOR_EQUAL: {
            ra_.Emit<Xor2>(node, 0, lhs);
            break;
        }
        case lexer::TokenType::KEYW_IN: {
            ra_.Emit<Isin>(node, 0, lhs);
            break;
        }
        case lexer::TokenType::KEYW_INSTANCEOF: {
            ra_.Emit<Instanceof>(node, 0, lhs);
            break;
        }
        default: {
            UNREACHABLE();
        }
    }
}

void PandaGen::Equal(const ir::AstNode *node, VReg lhs)
{
    ra_.Emit<Eq>(node, 0, lhs);
}

void PandaGen::NotEqual(const ir::AstNode *node, VReg lhs)
{
    ra_.Emit<Noteq>(node, 0, lhs);
}

void PandaGen::StrictEqual(const ir::AstNode *node, VReg lhs)
{
    ra_.Emit<Stricteq>(node, 0, lhs);
}

void PandaGen::StrictNotEqual(const ir::AstNode *node, VReg lhs)
{
    ra_.Emit<Strictnoteq>(node, 0, lhs);
}

void PandaGen::LessThan(const ir::AstNode *node, VReg lhs)
{
    ra_.Emit<Less>(node, 0, lhs);
}

void PandaGen::LessEqual(const ir::AstNode *node, VReg lhs)
{
    ra_.Emit<Lesseq>(node, 0, lhs);
}

void PandaGen::GreaterThan(const ir::AstNode *node, VReg lhs)
{
    ra_.Emit<Greater>(node, 0, lhs);
}

void PandaGen::GreaterEqual(const ir::AstNode *node, VReg lhs)
{
    ra_.Emit<Greatereq>(node, 0, lhs);
}

void PandaGen::IsTrue(const ir::AstNode *node)
{
    ra_.Emit<Istrue>(node);
}

void PandaGen::BranchIfUndefined(const ir::AstNode *node, Label *target)
{
    RegScope rs(this);
    VReg tmp = AllocReg();
    StoreAccumulator(node, tmp);
    LoadConst(node, Constant::JS_UNDEFINED);
    Equal(node, tmp);
    ra_.Emit<Jnez>(node, target);
}

void PandaGen::BranchIfStrictUndefined(const ir::AstNode *node, class Label *target)
{
    RegScope rs(this);
    VReg tmp = AllocReg();
    StoreAccumulator(node, tmp);
    LoadConst(node, Constant::JS_UNDEFINED);
    StrictEqual(node, tmp);
    ra_.Emit<Jnez>(node, target);
}

void PandaGen::BranchIfNotUndefined(const ir::AstNode *node, Label *target)
{
    RegScope rs(this);
    VReg tmp = AllocReg();
    StoreAccumulator(node, tmp);
    LoadConst(node, Constant::JS_UNDEFINED);
    Equal(node, tmp);
    ra_.Emit<Jeqz>(node, target);
}

void PandaGen::BranchIfStrictNotUndefined(const ir::AstNode *node, class Label *target)
{
    RegScope rs(this);
    VReg tmp = AllocReg();
    StoreAccumulator(node, tmp);
    LoadConst(node, Constant::JS_UNDEFINED);
    StrictEqual(node, tmp);
    ra_.Emit<Jeqz>(node, target);
}

void PandaGen::BranchIfTrue(const ir::AstNode *node, Label *target)
{
    IsTrue(node);
    ra_.Emit<Jnez>(node, target);
}

void PandaGen::BranchIfNotTrue(const ir::AstNode *node, Label *target)
{
    IsTrue(node);
    BranchIfFalse(node, target);
}

void PandaGen::BranchIfFalse(const ir::AstNode *node, Label *target)
{
    ra_.Emit<Isfalse>(node);
    ra_.Emit<Jnez>(node, target);
}

void PandaGen::BranchIfStrictNull(const ir::AstNode *node, class Label *target)
{
    RegScope rs(this);
    VReg tmp = AllocReg();
    StoreAccumulator(node, tmp);
    LoadConst(node, Constant::JS_NULL);
    ra_.Emit<Stricteq>(node, 0, tmp);
    ra_.Emit<Jnez>(node, target);
}

void PandaGen::EmitThrow(const ir::AstNode *node)
{
    ra_.Emit<Throw>(node);
}

void PandaGen::EmitRethrow(const ir::AstNode *node)
{
    RegScope rs(this);
    auto *skipThrow = AllocLabel();
    auto *doThrow = AllocLabel();

    VReg exception = AllocReg();
    StoreAccumulator(node, exception);

    VReg hole = AllocReg();
    StoreConst(node, hole, Constant::JS_HOLE);

    LoadAccumulator(node, exception);
    NotEqual(node, hole);
    ra_.Emit<Jeqz>(node, skipThrow);

    SetLabel(node, doThrow);
    LoadAccumulator(node, exception);
    EmitThrow(node);

    SetLabel(node, skipThrow);
}

void PandaGen::EmitReturn(const ir::AstNode *node)
{
    ra_.Emit<Return>(node);
}

void PandaGen::EmitReturnUndefined(const ir::AstNode *node)
{
    ra_.Emit<Returnundefined>(node);
}

void PandaGen::ImplicitReturn(const ir::AstNode *node)
{
    builder_->ImplicitReturn(node);
}

void PandaGen::DirectReturn(const ir::AstNode *node)
{
    builder_->DirectReturn(node);
}

void PandaGen::ExplicitReturn(const ir::AstNode *node)
{
    builder_->ExplicitReturn(node);
}

void PandaGen::ValidateClassDirectReturn(const ir::AstNode *node)
{
    const ir::ScriptFunction *func = util::Helpers::GetContainingFunction(node);

    if (!func || !func->IsConstructor()) {
        return;
    }

    RegScope rs(this);
    VReg value = AllocReg();
    StoreAccumulator(node, value);

    auto *notUndefined = AllocLabel();
    auto *condEnd = AllocLabel();

    BranchIfStrictNotUndefined(node, notUndefined);
    GetThis(func);
    ThrowIfSuperNotCorrectCall(func, 0);
    Branch(node, condEnd);

    SetLabel(node, notUndefined);
    LoadAccumulator(node, value);

    SetLabel(node, condEnd);
}

void PandaGen::EmitAwait(const ir::AstNode *node)
{
    builder_->Await(node);
}

void PandaGen::CallThis(const ir::AstNode *node, VReg startReg, size_t argCount)
{
    LoadAccumulator(node, startReg); // callee is load to acc
    VReg thisReg = startReg + 1;
    switch (argCount) {
        case 1: { // no args
            ra_.Emit<Callthis0>(node, 0, thisReg);
            break;
        }
        case 2: { // 1 arg
            VReg arg0 = thisReg + 1;
            ra_.Emit<Callthis1>(node, 0, thisReg, arg0);
            break;
        }
        case 3: { // 2 args
            VReg arg0 = thisReg + 1;
            VReg arg1 = arg0 + 1;
            ra_.Emit<Callthis2>(node, 0, thisReg, arg0, arg1);
            break;
        }
        case 4: { // 3 args
            VReg arg0 = thisReg + 1;
            VReg arg1 = arg0 + 1;
            VReg arg2 = arg1 + 1;
            ra_.Emit<Callthis3>(node, 0, thisReg, arg0, arg1, arg2);
            break;
        }
        default: {
            int64_t actualArgs = argCount - 1;
            if (actualArgs <= util::Helpers::MAX_INT8) {
                ra_.EmitRange<Callthisrange>(node, argCount, 0, actualArgs, thisReg);
                break;
            }

            ra_.EmitRange<WideCallthisrange>(node, argCount, actualArgs, thisReg);
            break;
        }
    }
}

void PandaGen::Call(const ir::AstNode *node, VReg startReg, size_t argCount)
{
    LoadAccumulator(node, startReg); // callee is load to acc
    switch (argCount) {
        case 0: { // 0 args
            ra_.Emit<Callarg0>(node, 0);
            break;
        }
        case 1: { // 1 arg
            VReg arg0 = startReg + 1;
            ra_.Emit<Callarg1>(node, 0, arg0);
            break;
        }
        case 2: { // 2 args
            VReg arg0 = startReg + 1;
            VReg arg1 = arg0 + 1;
            ra_.Emit<Callargs2>(node, 0, arg0, arg1);
            break;
        }
        case 3: { // 3 args
            VReg arg0 = startReg + 1;
            VReg arg1 = arg0 + 1;
            VReg arg2 = arg1 + 1;
            ra_.Emit<Callargs3>(node, 0, arg0, arg1, arg2);
            break;
        }
        default: {
            VReg arg0 = startReg + 1;
            if (argCount <= util::Helpers::MAX_INT8) {
                ra_.EmitRange<Callrange>(node, argCount, 0, argCount, arg0);
                break;
            }

            ra_.EmitRange<WideCallrange>(node, argCount, argCount, arg0);
            break;
        }
    }
}

void PandaGen::SuperCall(const ir::AstNode *node, VReg startReg, size_t argCount)
{
    if (RootNode()->AsScriptFunction()->IsArrow()) {
        GetFunctionObject(node); // load funcobj to acc for super call in arrow function
        if (argCount <= util::Helpers::MAX_INT8) {
            ra_.EmitRange<Supercallarrowrange>(node, argCount, 0, static_cast<int64_t>(argCount), startReg);
        } else {
            ra_.EmitRange<WideSupercallarrowrange>(node, argCount, static_cast<int64_t>(argCount), startReg);
        }
        return;
    }

    if (argCount <= util::Helpers::MAX_INT8) {
        // no need to load funcobj to acc for super call in other kinds of functions
        ra_.EmitRange<Supercallthisrange>(node, argCount, 0, static_cast<int64_t>(argCount), startReg);
        return;
    }

    ra_.EmitRange<WideSupercallthisrange>(node, argCount, static_cast<int64_t>(argCount), startReg);
}

void PandaGen::SuperCallSpread(const ir::AstNode *node, VReg vs)
{
    ra_.Emit<Supercallspread>(node, 0, vs);
}

void PandaGen::NotifyConcurrentResult(const ir::AstNode *node)
{
    if (IsConcurrent()) {
        ra_.Emit<CallruntimeNotifyconcurrentresult>(node);
    }
}

void PandaGen::NewObject(const ir::AstNode *node, VReg startReg, size_t argCount)
{
    if (argCount <= util::Helpers::MAX_INT8) {
        ra_.EmitRange<Newobjrange>(node, argCount, 0, static_cast<int64_t>(argCount), startReg);
        return;
    }

    ra_.EmitRange<WideNewobjrange>(node, argCount, static_cast<int64_t>(argCount), startReg);
}

void PandaGen::DefineFunction(const ir::AstNode *node, const ir::ScriptFunction *realNode, const util::StringView &name)
{
    if (realNode->IsOverload() || realNode->Declare()) {
        return;
    }

    auto formalParamCnt = realNode->FormalParamsLength();
    if (realNode->IsMethod()) {
        ra_.Emit<Definemethod>(node, 0, name, static_cast<int64_t>(formalParamCnt));
    } else  {
        ra_.Emit<Definefunc>(node, 0, name, static_cast<int64_t>(formalParamCnt));
    }

    strings_.insert(name);
}

void PandaGen::TypeOf(const ir::AstNode *node)
{
    ra_.Emit<Typeof>(node, 0);
}

void PandaGen::CallSpread(const ir::AstNode *node, VReg func, VReg thisReg, VReg args)
{
    LoadAccumulator(node, func); // callee is load to acc
    ra_.Emit<Apply>(node, 0, thisReg, args);
}

void PandaGen::NewObjSpread(const ir::AstNode *node, VReg obj)
{
    ra_.Emit<Newobjapply>(node, 0, obj);
}

void PandaGen::GetUnmappedArgs(const ir::AstNode *node)
{
    ra_.Emit<Getunmappedargs>(node);
}

void PandaGen::Negate(const ir::AstNode *node)
{
    auto *falseLabel = AllocLabel();
    auto *endLabel = AllocLabel();
    BranchIfTrue(node, falseLabel);
    LoadConst(node, Constant::JS_TRUE);
    Branch(node, endLabel);
    SetLabel(node, falseLabel);
    LoadConst(node, Constant::JS_FALSE);
    SetLabel(node, endLabel);
}

void PandaGen::ToNumber(const ir::AstNode *node, VReg arg)
{
    LoadAccumulator(node, arg);
    ra_.Emit<Tonumber>(node, 0);
}

void PandaGen::ToNumeric(const ir::AstNode *node, VReg arg)
{
    LoadAccumulator(node, arg);
    ra_.Emit<Tonumeric>(node, 0);
}

void PandaGen::CreateGeneratorObj(const ir::AstNode *node, VReg funcObj)
{
    ra_.Emit<Creategeneratorobj>(node, funcObj);
}

void PandaGen::CreateAsyncGeneratorObj(const ir::AstNode *node, VReg funcObj)
{
    ra_.Emit<Createasyncgeneratorobj>(node, funcObj);
}

void PandaGen::CreateIterResultObject(const ir::AstNode *node, VReg value, VReg done)
{
    ra_.Emit<Createiterresultobj>(node, value, done);
}

void PandaGen::SuspendGenerator(const ir::AstNode *node, VReg genObj)
{
    ra_.Emit<Suspendgenerator>(node, genObj); // iterResult is in acc
}

void PandaGen::SuspendAsyncGenerator(const ir::AstNode *node, VReg asyncGenObj)
{
    /*
     *  TODO: suspend async generator
     *  ra_.Emit<EcmaSuspendasyncgenerator>(node, asyncGenObj);
     */
}

void PandaGen::GeneratorYield(const ir::AstNode *node, VReg genObj)
{
    LoadAccumulator(node, genObj);
    ra_.Emit<Setgeneratorstate>(node, static_cast<int32_t>(GeneratorState::SUSPENDED_YIELD));
}

void PandaGen::GeneratorComplete(const ir::AstNode *node, VReg genObj)
{
    LoadAccumulator(node, genObj);
    ra_.Emit<Setgeneratorstate>(node, static_cast<int32_t>(GeneratorState::COMPLETED));
}

void PandaGen::ResumeGenerator(const ir::AstNode *node, VReg genObj)
{
    LoadAccumulator(node, genObj);
    ra_.Emit<Resumegenerator>(node);
}

void PandaGen::GetResumeMode(const ir::AstNode *node, VReg genObj)
{
    LoadAccumulator(node, genObj);
    ra_.Emit<Getresumemode>(node);
}

void PandaGen::AsyncFunctionEnter(const ir::AstNode *node)
{
    ra_.Emit<Asyncfunctionenter>(node);
}

void PandaGen::AsyncFunctionAwait(const ir::AstNode *node, VReg asyncFuncObj)
{
    ra_.Emit<Asyncfunctionawaituncaught>(node, asyncFuncObj); // receivedValue is in acc
}

void PandaGen::AsyncFunctionResolve(const ir::AstNode *node, VReg asyncFuncObj)
{
    ra_.Emit<Asyncfunctionresolve>(node, asyncFuncObj); // use retVal in acc
}

void PandaGen::AsyncFunctionReject(const ir::AstNode *node, VReg asyncFuncObj)
{
    ra_.Emit<Asyncfunctionreject>(node, asyncFuncObj); // exception is in acc
}

void PandaGen::AsyncGeneratorResolve(const ir::AstNode *node, VReg asyncGenObj, VReg value, VReg canSuspend)
{
    ra_.Emit<Asyncgeneratorresolve>(node, asyncGenObj, value, canSuspend);
}

void PandaGen::AsyncGeneratorReject(const ir::AstNode *node, VReg asyncGenObj)
{
    ra_.Emit<Asyncgeneratorreject>(node, asyncGenObj); // value is in acc
}

void PandaGen::GetTemplateObject(const ir::AstNode *node, VReg value)
{
    LoadAccumulator(node, value);
    ra_.Emit<Gettemplateobject>(node, 0);
}

void PandaGen::CopyRestArgs(const ir::AstNode *node, uint32_t index)
{
    index <= util::Helpers::MAX_INT8 ? ra_.Emit<Copyrestargs>(node, index) : ra_.Emit<WideCopyrestargs>(node, index);
}

void PandaGen::GetPropIterator(const ir::AstNode *node)
{
    ra_.Emit<Getpropiterator>(node);
}

void PandaGen::GetNextPropName(const ir::AstNode *node, VReg iter)
{
    ra_.Emit<Getnextpropname>(node, iter);
}

void PandaGen::CreateEmptyObject(const ir::AstNode *node)
{
    ra_.Emit<Createemptyobject>(node);
}

void PandaGen::CreateObjectWithBuffer(const ir::AstNode *node, uint32_t idx)
{
    ASSERT(util::Helpers::IsInteger<uint32_t>(idx));
    std::string idxStr = std::string(context_->Binder()->Program()->RecordName()) + "_" + std::to_string(idx);
    util::UString litId(idxStr, allocator_);
    ra_.Emit<Createobjectwithbuffer>(node, 0, litId.View());
}

void PandaGen::SetObjectWithProto(const ir::AstNode *node, VReg proto, VReg obj)
{
    LoadAccumulator(node, obj);
    ra_.Emit<Setobjectwithproto>(node, 0, proto);
}

void PandaGen::CopyDataProperties(const ir::AstNode *node, VReg dst)
{
    ra_.Emit<Copydataproperties>(node, dst); // use acc as srcObj
}

void PandaGen::DefineGetterSetterByValue(const ir::AstNode *node, VReg obj, VReg name, VReg getter, VReg setter,
                                         bool setName)
{
    LoadConst(node, setName ? Constant::JS_TRUE : Constant::JS_FALSE);
    ra_.Emit<Definegettersetterbyvalue>(node, obj, name, getter, setter);
}

void PandaGen::CreateEmptyArray(const ir::AstNode *node)
{
    ra_.Emit<Createemptyarray>(node, 0);
}

void PandaGen::CreateArrayWithBuffer(const ir::AstNode *node, uint32_t idx)
{
    ASSERT(util::Helpers::IsInteger<uint32_t>(idx));
    std::string idxStr = std::string(context_->Binder()->Program()->RecordName()) + "_" + std::to_string(idx);
    util::UString litId(idxStr, allocator_);
    ra_.Emit<Createarraywithbuffer>(node, 0, litId.View());
}

void PandaGen::CreateArray(const ir::AstNode *node, const ArenaVector<ir::Expression *> &elements, VReg obj)
{
    if (elements.empty()) {
        CreateEmptyArray(node);
        StoreAccumulator(node, obj);
        return;
    }

    auto *buf = NewLiteralBuffer();

    size_t i = 0;
    // This loop handles constant literal data by collecting it into a literal buffer
    // until a non-constant element is encountered.
    while (i < elements.size() && util::Helpers::IsConstantExpr(elements[i])) {
        buf->Add(elements[i]->AsLiteral());
        i++;
    }

    if (buf->IsEmpty()) {
        CreateEmptyArray(node);
    } else {
        uint32_t bufIdx = AddLiteralBuffer(buf);
        CreateArrayWithBuffer(node, bufIdx);
    }

    StoreAccumulator(node, obj);

    if (i == elements.size()) {
        return;
    }

    bool hasSpread = false;

    // This loop handles array elements until a spread element is encountered
    for (; i < elements.size(); i++) {
        const ir::Expression *elem = elements[i];

        if (elem->IsOmittedExpression()) {
            continue;
        }

        if (elem->IsSpreadElement()) {
            // The next loop will handle arrays that have a spread element
            hasSpread = true;
            break;
        }

        elem->Compile(this);
        StOwnByIndex(elem, obj, i);
    }

    RegScope rs(this);
    VReg idxReg {};

    if (hasSpread) {
        idxReg = AllocReg();
        LoadAccumulatorInt(node, i);
        StoreAccumulator(node, idxReg);
    }

    // This loop handles arrays that contain spread elements
    for (; i < elements.size(); i++) {
        const ir::Expression *elem = elements[i];

        if (elem->IsSpreadElement()) {
            elem->AsSpreadElement()->Argument()->Compile(this);

            StoreArraySpread(elem, obj, idxReg);

            LoadObjByName(node, obj, "length");
            StoreAccumulator(elem, idxReg);
            continue;
        }

        if (!elem->IsOmittedExpression()) {
            elem->Compile(this);
            StOwnByValue(elem, obj, idxReg);
        }

        Unary(elem, lexer::TokenType::PUNCTUATOR_PLUS_PLUS, idxReg);
        StoreAccumulator(elem, idxReg);
    }

    // If the last element is omitted, we also have to update the length property
    if (elements.back()->IsOmittedExpression()) {
        // if there was a spread value then acc already contains the length
        if (!hasSpread) {
            LoadAccumulatorInt(node, i);
        }

        StOwnByName(node, obj, "length");
    }

    LoadAccumulator(node, obj);
}

void PandaGen::StoreArraySpread(const ir::AstNode *node, VReg array, VReg index)
{
    ra_.Emit<Starrayspread>(node, array, index);
}

void PandaGen::ThrowIfNotObject(const ir::AstNode *node, VReg obj)
{
    ra_.Emit<ThrowIfnotobject>(node, obj);
}

void PandaGen::ThrowThrowNotExist(const ir::AstNode *node)
{
    ra_.Emit<ThrowNotexists>(node);
}

void PandaGen::GetIterator(const ir::AstNode *node)
{
    ra_.Emit<Getiterator>(node, 0);
}

void PandaGen::GetAsyncIterator(const ir::AstNode *node)
{
    ra_.Emit<Getasynciterator>(node, 0);
}

void PandaGen::CreateObjectWithExcludedKeys(const ir::AstNode *node, VReg obj, VReg argStart, size_t argCount)
{
    ASSERT(argStart == obj + 1);
    if (argCount == 0) {
        LoadConst(node, Constant::JS_UNDEFINED);
        StoreAccumulator(node, argStart);
    }

    size_t argRegCnt = (argCount == 0 ? argCount : argCount - 1);

    if (argRegCnt <= util::Helpers::MAX_INT8) {
        ra_.EmitRange<Createobjectwithexcludedkeys>(node, argCount, static_cast<int64_t>(argRegCnt), obj, argStart);
        return;
    }

    ra_.EmitRange<WideCreateobjectwithexcludedkeys>(node, argCount, static_cast<int64_t>(argRegCnt), obj, argStart);
}

void PandaGen::ThrowObjectNonCoercible(const ir::AstNode *node)
{
    ra_.Emit<ThrowPatternnoncoercible>(node);
}

void PandaGen::CloseIterator(const ir::AstNode *node, VReg iter)
{
    ra_.Emit<Closeiterator>(node, 0, iter);
}

void PandaGen::DefineClassWithBuffer(const ir::AstNode *node, const util::StringView &ctorId, int32_t litIdx, VReg base)
{
    auto formalParamCnt = node->AsClassDefinition()->Ctor()->Function()->FormalParamsLength();
    std::string idxStr = std::string(context_->Binder()->Program()->RecordName()) + "_" + std::to_string(litIdx);
    util::UString litId(idxStr, allocator_);
    ra_.Emit<Defineclasswithbuffer>(node, 0, ctorId, litId.View(), static_cast<int64_t>(formalParamCnt), base);
    strings_.insert(ctorId);
}

void PandaGen::LoadLocalModuleVariable(const ir::AstNode *node, const binder::ModuleVariable *variable)
{
    auto index = variable->Index();
    index <= util::Helpers::MAX_INT8 ? ra_.Emit<Ldlocalmodulevar>(node, index) :
                                       ra_.Emit<WideLdlocalmodulevar>(node, index);
}

void PandaGen::LoadExternalModuleVariable(const ir::AstNode *node, const binder::ModuleVariable *variable)
{
    auto index = variable->Index();
    if (Context()->IsTypeExtractorEnabled()) {
        const ir::Identifier *identifier = nullptr;
        const ir::AstNode *declareNode = Context()->
                                         TypeExtractor()->GetDeclNodeFromIdentifier(node->AsIdentifier(), &identifier);
        int64_t typeIndex = Context()->TypeRecorder()->GetNodeTypeIndex(declareNode);
        if (typeIndex != extractor::TypeRecorder::PRIMITIVETYPE_ANY) {
            index <= util::Helpers::MAX_INT8 ? ra_.EmitWithType<Ldexternalmodulevar>(node, typeIndex, index) :
                                               ra_.EmitWithType<WideLdexternalmodulevar>(node, typeIndex, index);
            return;
        }
    }
    index <= util::Helpers::MAX_INT8 ? ra_.Emit<Ldexternalmodulevar>(node, index) :
                                       ra_.Emit<WideLdexternalmodulevar>(node, index);
}

void PandaGen::StoreModuleVariable(const ir::AstNode *node, const binder::ModuleVariable *variable)
{
    auto index = variable->Index();
    if (Context()->IsTypeExtractorEnabled()) {
        int64_t typeIndex = Context()->TypeRecorder()->GetNodeTypeIndex(node);
        if (typeIndex != extractor::TypeRecorder::PRIMITIVETYPE_ANY) {
            index <= util::Helpers::MAX_INT8 ? ra_.EmitWithType<Stmodulevar>(node, typeIndex, index) :
                                               ra_.EmitWithType<WideStmodulevar>(node, typeIndex, index);
            return;
        }
    }
    index <= util::Helpers::MAX_INT8 ? ra_.Emit<Stmodulevar>(node, index) :
                                       ra_.Emit<WideStmodulevar>(node, index);
}

void PandaGen::GetModuleNamespace(const ir::AstNode *node, uint32_t index)
{
    index <= util::Helpers::MAX_INT8 ? ra_.Emit<Getmodulenamespace>(node, index) :
                                       ra_.Emit<WideGetmodulenamespace>(node, index);
}

void PandaGen::DynamicImportCall(const ir::AstNode *node)
{
    ra_.Emit<Dynamicimport>(node);
}

void PandaGen::StSuperByName(const ir::AstNode *node, VReg obj, const util::StringView &key)
{
    ra_.Emit<Stsuperbyname>(node, 0, key, obj);
    strings_.insert(key);
}

void PandaGen::LdSuperByName(const ir::AstNode *node, VReg obj, const util::StringView &key)
{
    LoadAccumulator(node, obj); // object is load to acc
    ra_.Emit<Ldsuperbyname>(node, 0, key);
    strings_.insert(key);
}

void PandaGen::StSuperByValue(const ir::AstNode *node, VReg obj, VReg prop)
{
    ra_.Emit<Stsuperbyvalue>(node, 0, obj, prop);
}

void PandaGen::LdSuperByValue(const ir::AstNode *node, VReg obj)
{
    ra_.Emit<Ldsuperbyvalue>(node, 0, obj); // prop is in acc
}

void PandaGen::StoreSuperProperty(const ir::AstNode *node, VReg obj, const Operand &prop)
{
    if (std::holds_alternative<util::StringView>(prop)) {
        StSuperByName(node, obj, std::get<util::StringView>(prop));
        return;
    }

    if (std::holds_alternative<VReg>(prop)) {
        StSuperByValue(node, obj, std::get<VReg>(prop));
        return;
    }

    ASSERT(std::holds_alternative<int64_t>(prop));
    RegScope rs(this);
    VReg property = AllocReg();
    VReg value = AllocReg();

    StoreAccumulator(node, value);
    LoadAccumulatorInt(node, static_cast<size_t>(std::get<int64_t>(prop)));
    StoreAccumulator(node, property);
    LoadAccumulator(node, value);
    StSuperByValue(node, obj, property);
}

void PandaGen::LoadSuperProperty(const ir::AstNode *node, VReg obj, const Operand &prop)
{
    if (std::holds_alternative<util::StringView>(prop)) {
        LdSuperByName(node, obj, std::get<util::StringView>(prop));
        return;
    }

    if (std::holds_alternative<VReg>(prop)) {
        LoadAccumulator(node, std::get<VReg>(prop));
        LdSuperByValue(node, obj);
        return;
    }

    ASSERT(std::holds_alternative<int64_t>(prop));

    LoadAccumulatorInt(node, static_cast<size_t>(std::get<int64_t>(prop)));
    LdSuperByValue(node, obj);
}

void PandaGen::LoadLexicalVar(const ir::AstNode *node, uint32_t level, uint32_t slot)
{
    if ((level > util::Helpers::MAX_INT8) || (slot > util::Helpers::MAX_INT8)) {
        ra_.Emit<WideLdlexvar>(node, level, slot);
        return;
    }

    ra_.Emit<Ldlexvar>(node, level, slot);
}

void PandaGen::LoadLexicalVar(const ir::AstNode *node, uint32_t level, uint32_t slot, const util::StringView &name)
{
    if (context_->PatchFixHelper() && context_->PatchFixHelper()->IsPatchVar(slot)) {
        uint32_t patchSlot = context_->PatchFixHelper()->GetPatchLexicalIdx(std::string(name));
        ra_.Emit<WideLdpatchvar>(node, patchSlot);
        return;
    }

    if ((level > util::Helpers::MAX_INT8) || (slot > util::Helpers::MAX_INT8)) {
        ra_.Emit<WideLdlexvar>(node, level, slot);
        return;
    }

    ra_.Emit<Ldlexvar>(node, level, slot);
}

void PandaGen::StoreLexicalVar(const ir::AstNode *node, uint32_t level, uint32_t slot)
{
    if ((level > util::Helpers::MAX_INT8) || (slot > util::Helpers::MAX_INT8)) {
        ra_.Emit<WideStlexvar>(node, level, slot);
        return;
    }

    ra_.Emit<Stlexvar>(node, level, slot);
}

void PandaGen::StoreLexicalVar(const ir::AstNode *node, uint32_t level, uint32_t slot, VReg value)
{
    LoadAccumulator(node, value);
    if ((level > util::Helpers::MAX_INT8) || (slot > util::Helpers::MAX_INT8)) {
        ra_.Emit<WideStlexvar>(node, level, slot);
        return;
    }

    ra_.Emit<Stlexvar>(node, level, slot);
}

void PandaGen::StoreLexicalVar(const ir::AstNode *node, uint32_t level, uint32_t slot,
    const binder::LocalVariable *local)
{
    if (context_->PatchFixHelper() && context_->PatchFixHelper()->IsPatchVar(slot)) {
        uint32_t patchSlot = context_->PatchFixHelper()->GetPatchLexicalIdx(std::string(local->Name()));
        ra_.Emit<WideStpatchvar>(node, patchSlot);
        return;
    }
    RegScope rs(this);
    VReg value = AllocReg();
    if (context_->IsTypeExtractorEnabled()) {
        auto fn = [&node, &level, &slot, &local, &value, this](auto typeIndex, const auto &tag) {
            if (typeIndex != extractor::TypeRecorder::PRIMITIVETYPE_ANY) {
                StoreAccumulatorWithType(node, typeIndex, value);
                DCOUT << "[LOG]Lexical vreg in " << tag << " has type index: " << local->Name() <<
                    "@" << local << " | " << typeIndex << std::endl;
                StoreLexicalVar(node, level, slot, value);
                return true;
            }
            return false;
        };
        if (fn(context_->TypeRecorder()->GetVariableTypeIndex(local), "variable")) {
            return;
        }
        if (fn(context_->TypeRecorder()->GetNodeTypeIndex(node), "declnode")) {
            return;
        }
        DCOUT << "[WARNING]Lexical vreg lose type index: " << local->Name() << "@" << local << std::endl;
    }
    StoreAccumulator(node, value);
    StoreLexicalVar(node, level, slot, value);
}

void PandaGen::ThrowIfSuperNotCorrectCall(const ir::AstNode *node, int64_t num)
{
    ra_.Emit<ThrowIfsupernotcorrectcall>(node, num);
}

void PandaGen::ThrowUndefinedIfHole(const ir::AstNode *node, const util::StringView &name)
{
    ra_.Emit<ThrowUndefinedifholewithname>(node, name);
    strings_.insert(name);
}

void PandaGen::ThrowConstAssignment(const ir::AstNode *node, const util::StringView &name)
{
    RegScope rs(this);
    LoadAccumulatorString(node, name);
    VReg nameReg = AllocReg();
    StoreAccumulator(node, nameReg);
    ra_.Emit<ThrowConstassignment>(node, nameReg);
    strings_.insert(name);
}

void PandaGen::PopLexEnv(const ir::AstNode *node)
{
    ra_.Emit<Poplexenv>(node);
}

void PandaGen::CopyLexEnv(const ir::AstNode *node)
{
    /*
     *  TODO: add copy lexenv to optimize the loop env creation
     *  ra_.Emit<EcmaCopylexenvdyn>(node);
     */
}

void PandaGen::NewLexicalEnv(const ir::AstNode *node, uint32_t num, binder::VariableScope *scope)
{
    if (IsDebug()) {
        int32_t scopeInfoIdx = AddLexicalVarNamesForDebugInfo(scope->GetLexicalVarNameAndTypes());
        NewLexEnvWithScopeInfo(node, num, scopeInfoIdx);
        return;
    }

    NewLexEnv(node, num);
}

void PandaGen::NewLexEnv(const ir::AstNode *node, uint32_t num)
{
    num <= util::Helpers::MAX_INT8 ? ra_.Emit<Newlexenv>(node, num) : ra_.Emit<WideNewlexenv>(node, num);
}

void PandaGen::NewLexEnvWithScopeInfo(const ir::AstNode *node, uint32_t num, int32_t scopeInfoIdx)
{
    std::string idxStr = std::string(context_->Binder()->Program()->RecordName()) + "_" + std::to_string(scopeInfoIdx);
    util::UString litId(idxStr, allocator_);
    num <= util::Helpers::MAX_INT8 ? ra_.Emit<Newlexenvwithname>(node, num, litId.View()) :
                                     ra_.Emit<WideNewlexenvwithname>(node, num, litId.View());
}

uint32_t PandaGen::TryDepth() const
{
    const auto *iter = dynamicContext_;
    uint32_t depth = 0;

    while (iter) {
        if (iter->HasTryCatch()) {
            depth++;
        }

        iter = iter->Prev();
    }

    return depth;
}

CatchTable *PandaGen::CreateCatchTable()
{
    auto *catchTable = allocator_->New<CatchTable>(this, TryDepth());
    catchList_.push_back(catchTable);
    return catchTable;
}

void PandaGen::SortCatchTables()
{
    std::sort(catchList_.begin(), catchList_.end(),
              [](const CatchTable *a, const CatchTable *b) { return b->Depth() < a->Depth(); });
}

Operand PandaGen::ToNamedPropertyKey(const ir::Expression *prop, bool isComputed)
{
    VReg res {0};

    if (isComputed) {
        return res;
    }

    if (prop->IsIdentifier()) {
        return prop->AsIdentifier()->Name();
    }

    if (prop->IsStringLiteral()) {
        const util::StringView &str = prop->AsStringLiteral()->Str();

        /* TODO(dbatyai): remove this when runtime handles __proto__ as property name correctly */
        if (str.Is("__proto__")) {
            return res;
        }

        int64_t index = util::Helpers::GetIndex(str);
        if (index != util::Helpers::INVALID_INDEX) {
            return index;
        }

        return str;
    }

    if (prop->IsNumberLiteral()) {
        auto num = prop->AsNumberLiteral()->Number<double>();
        if (util::Helpers::IsIndex(num)) {
            return static_cast<int64_t>(num);
        }

        return util::Helpers::ToStringView(allocator_, num);
    }

    return res;
}

Operand PandaGen::ToPropertyKey(const ir::Expression *prop, bool isComputed)
{
    Operand op = ToNamedPropertyKey(prop, isComputed);
    if (std::holds_alternative<util::StringView>(op) || (std::holds_alternative<int64_t>(op) &&
        (std::get<int64_t>(op) <= util::Helpers::MAX_INT32))) {
        return op;
    }

    VReg propReg = AllocReg();

    /**
     * Store index to vreg when index > MAX_INT32 to simplify ASM interpreter If byindex-related instructions support
     * index > MAX_INT32, ASM interpreter will have to add a judgment whether index needs more than 32 bits which will
     * cause inefficiency of it since cases when index > MAX_INT32 can be quite rare
     **/
    if (std::holds_alternative<int64_t>(op) && (std::get<int64_t>(op) > util::Helpers::MAX_INT32)) {
        LoadAccumulatorFloat(prop, std::get<int64_t>(op));
        StoreAccumulator(prop, propReg);
        return propReg;
    }

    ASSERT(std::holds_alternative<VReg>(op));
    prop->Compile(this);
    StoreAccumulator(prop, propReg);

    return propReg;
}

VReg PandaGen::LoadPropertyKey(const ir::Expression *prop, bool isComputed)
{
    Operand op = ToNamedPropertyKey(prop, isComputed);
    if (std::holds_alternative<util::StringView>(op)) {
        LoadAccumulatorString(prop, std::get<util::StringView>(op));
    } else if (std::holds_alternative<int64_t>(op)) {
        LoadAccumulatorInt(prop, static_cast<size_t>(std::get<int64_t>(op)));
    } else {
        prop->Compile(this);
    }

    VReg propReg = AllocReg();
    StoreAccumulator(prop, propReg);

    return propReg;
}

void PandaGen::StLetOrClassToGlobalRecord(const ir::AstNode *node, const util::StringView &name)
{
    ra_.Emit<Sttoglobalrecord>(node, 0, name);
    strings_.insert(name);
}

void PandaGen::StConstToGlobalRecord(const ir::AstNode *node, const util::StringView &name)
{
    ra_.Emit<Stconsttoglobalrecord>(node, 0, name);
    strings_.insert(name);
}

bool PandaGen::TryCompileFunctionCallOrNewExpression(const ir::Expression *expr)
{
    ASSERT(expr->IsCallExpression() || expr->IsNewExpression());
    const auto *callee = expr->IsCallExpression() ? expr->AsCallExpression()->Callee() :
                         expr->AsNewExpression()->Callee();

    if (!callee->IsIdentifier()) {
        return false;
    }

    if (callee->AsIdentifier()->Name().Is("Function")) {
        auto arguments = expr->IsCallExpression() ? expr->AsCallExpression()->Arguments() :
                         expr->AsNewExpression()->Arguments();
        if (arguments.empty()) {
            return false;
        }

        auto *arg = arguments[arguments.size() - 1];
        if (!arg->IsStringLiteral()) {
            return false;
        }

        if (std::regex_match(arg->AsStringLiteral()->Str().Mutf8(), std::regex(" *return +this[;]? *$"))) {
            LoadConst(arg, Constant::JS_GLOBAL);
            return true;
        }
    }

    return false;
}

}  // namespace panda::es2panda::compiler