* Copyright (c) 2021-2026 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 "dynamicContext.h"
#include "checker/types/ets/etsObjectType.h"
#include "checker/types/type.h"
#include "compiler/core/envScope.h"
#include "compiler/core/ETSGen.h"
#include "compiler/core/pandagen.h"
#include "compiler/base/catchTable.h"
#include "ir/expressions/identifier.h"
#include "ir/base/catchClause.h"
#include "ir/statements/blockStatement.h"
#include "ir/statements/breakStatement.h"
#include "ir/statements/continueStatement.h"
#include "ir/statements/returnStatement.h"
#include "ir/statements/tryStatement.h"
#include "ir/statements/labelledStatement.h"
namespace ark::es2panda::compiler {
DynamicContext::DynamicContext(CodeGen *cg, LabelTarget target) : cg_(cg), target_(target), prev_(Cg()->dynamicContext_)
{
Cg()->dynamicContext_ = this;
}
DynamicContext::~DynamicContext()
{
Cg()->dynamicContext_ = prev_;
}
LabelContext::LabelContext(CodeGen *cg, const ir::LabelledStatement *labelledStmt)
: DynamicContext(cg, LabelTarget(labelledStmt->Ident()->Name())), labelledStmt_(labelledStmt)
{
if (!labelledStmt->Body()->IsBlockStatement()) {
return;
}
label_ = cg->AllocLabel();
Target().SetBreakTarget(label_);
}
LabelContext::~LabelContext()
{
if (label_ == nullptr) {
return;
}
Cg()->SetLabel(labelledStmt_, label_);
}
LexEnvContext::LexEnvContext(LoopEnvScope *envScope, PandaGen *pg, LabelTarget target)
: DynamicContext(pg, target), envScope_(envScope)
{
if (!envScope_->HasEnv()) {
return;
}
catchTable_ = Cg()->CreateCatchTable();
ES2PANDA_ASSERT(catchTable_ != nullptr);
const auto &labelSet = catchTable_->LabelSet();
const auto *node = envScope_->Scope()->Node();
Cg()->SetLabel(node, labelSet.TryBegin());
}
LexEnvContext::~LexEnvContext()
{
if (!envScope_->HasEnv()) {
return;
}
const auto &labelSet = catchTable_->LabelSet();
const auto *node = envScope_->Scope()->Node();
Cg()->SetLabel(node, labelSet.TryEnd());
Cg()->Branch(node, labelSet.CatchEnd());
Cg()->SetLabel(node, labelSet.CatchBegin());
AsPandaGen()->PopLexEnv(node);
AsPandaGen()->EmitThrow(node);
Cg()->SetLabel(node, labelSet.CatchEnd());
AsPandaGen()->PopLexEnv(node);
}
PandaGen *LexEnvContext::AsPandaGen() const
{
return static_cast<PandaGen *>(Cg());
}
bool LexEnvContext::HasTryCatch() const
{
return envScope_->HasEnv();
}
void LexEnvContext::AbortContext([[maybe_unused]] ControlFlowChange cfc,
[[maybe_unused]] const util::StringView &targetLabel)
{
if (cfc == ControlFlowChange::CONTINUE || !envScope_->HasEnv()) {
return;
}
const auto *node = envScope_->Scope()->Node();
AsPandaGen()->PopLexEnv(node);
}
IteratorContext::IteratorContext(PandaGen *pg, const Iterator &iterator, LabelTarget target)
: DynamicContext(pg, target), iterator_(iterator), catchTable_(pg->CreateCatchTable())
{
const auto &labelSet = catchTable_->LabelSet();
pg->SetLabel(iterator_.Node(), labelSet.TryBegin());
}
IteratorContext::~IteratorContext()
{
const auto &labelSet = catchTable_->LabelSet();
const auto *node = iterator_.Node();
Cg()->SetLabel(node, labelSet.TryEnd());
Cg()->Branch(node, labelSet.CatchEnd());
Cg()->SetLabel(node, labelSet.CatchBegin());
iterator_.Close(true);
Cg()->SetLabel(node, labelSet.CatchEnd());
}
void IteratorContext::AbortContext([[maybe_unused]] ControlFlowChange cfc,
[[maybe_unused]] const util::StringView &targetLabel)
{
if (cfc == ControlFlowChange::CONTINUE && Target().ContinueLabel() == targetLabel) {
return;
}
iterator_.Close(false);
}
void TryContext::InitFinalizer()
{
ES2PANDA_ASSERT(tryStmt_);
if (!hasFinalizer_ || (tryStmt_->FinallyBlock() == nullptr)) {
return;
}
auto *pg = static_cast<PandaGen *>(Cg());
finalizerRun_ = pg->AllocReg();
pg->StoreConst(tryStmt_, finalizerRun_, Constant::JS_UNDEFINED);
}
void CatchContext::InitCatchTable()
{
auto *pg = static_cast<PandaGen *>(Cg());
catchTable_ = pg->CreateCatchTable();
}
const TryLabelSet &CatchContext::LabelSet() const
{
return catchTable_->LabelSet();
}
bool TryContext::HasFinalizer() const
{
return hasFinalizer_;
}
void TryContext::EmitFinalizer()
{
if (!hasFinalizer_ || inFinalizer_ || (tryStmt_->FinallyBlock() == nullptr)) {
return;
}
auto *pg = static_cast<PandaGen *>(Cg());
inFinalizer_ = true;
tryStmt_->FinallyBlock()->Compile(pg);
inFinalizer_ = false;
}
CatchTable *ETSCatchContext::AddNewCathTable(const util::StringView assemblerType)
{
auto *cg = Cg();
CatchTable *catchTable = cg->CreateCatchTable(assemblerType);
catchTables_.push_back(catchTable);
return catchTable;
}
CatchTable *ETSCatchContext::AddNewCathTable(const util::StringView assemblerType, const LabelPair tryLabelPair)
{
auto *cg = Cg();
CatchTable *catchTable = cg->CreateCatchTable(tryLabelPair, assemblerType);
catchTables_.push_back(catchTable);
return catchTable;
}
void ETSTryContext::EmitFinalizer(
LabelPair trycatchLabelPair,
const ArenaVector<std::pair<compiler::LabelPair, const ir::Statement *>> &finalizerInsertions)
{
ES2PANDA_ASSERT(tryStmt_);
if (!hasFinalizer_ || (tryStmt_->FinallyBlock() == nullptr)) {
return;
}
auto *etsg = static_cast<ETSGen *>(Cg());
CatchTable *finalizerTable = AddNewCathTable("", trycatchLabelPair);
ES2PANDA_ASSERT(finalizerTable != nullptr);
tryStmt_->FinallyBlock()->Compile(etsg);
ES2PANDA_ASSERT(finalizerTable != nullptr);
etsg->Branch(tryStmt_, finalizerTable->LabelSet().CatchEnd());
for (std::pair<compiler::LabelPair, const ir::Statement *> insertion : finalizerInsertions) {
EmitFinalizerInsertion(etsg, insertion.first, insertion.second);
}
etsg->SetLabel(tryStmt_, finalizerTable->LabelSet().CatchBegin());
compiler::VReg error = etsg->StoreError(tryStmt_);
tryStmt_->FinallyBlock()->Compile(etsg);
etsg->LoadAccumulator(tryStmt_, error);
etsg->EmitThrow(tryStmt_, error);
etsg->SetLabel(tryStmt_, finalizerTable->LabelSet().CatchEnd());
}
struct FinalizerReturnState {
bool isReturn;
const ir::ReturnStatement *returnStmt;
const checker::Type *returnType;
bool isReturnVoid;
};
auto GetFinalizerReturnState(ETSGen *etsg, const ir::Statement *statement) -> FinalizerReturnState
{
FinalizerReturnState state {false, nullptr, etsg->ReturnType(), false};
state.isReturn = statement->IsReturnStatement();
if (!state.isReturn) {
return state;
}
state.returnStmt = statement->AsReturnStatement();
state.isReturnVoid = state.returnType->IsETSVoidType();
return state;
}
void ETSTryContext::EmitFinalizerInsertion(ETSGen *etsg, compiler::LabelPair labelPair, const ir::Statement *statement)
{
etsg->SetLabel(tryStmt_, labelPair.Begin());
ES2PANDA_ASSERT(statement != nullptr);
const auto [isReturn, returnStmt, returnType, isReturnVoid] = GetFinalizerReturnState(etsg, statement);
compiler::RegScope rs(etsg);
compiler::VReg res = etsg->AllocReg();
if (isReturn && !isReturnVoid) {
if (returnStmt->Argument() == nullptr) {
etsg->LoadDefaultValue(tryStmt_, returnType);
} else {
etsg->SetAccumulatorType(returnType);
}
etsg->StoreAccumulator(tryStmt_, res);
etsg->SetVRegType(res, returnType);
}
tryStmt_->FinallyBlock()->Compile(etsg);
if (isReturn && !isReturnVoid) {
etsg->SetAccumulatorType(returnType);
etsg->LoadAccumulator(tryStmt_, res);
}
if (labelPair.End() != nullptr) {
etsg->Branch(tryStmt_, labelPair.End());
} else if (isReturn && isReturnVoid) {
if (etsg->IsAsync()) {
etsg->LoadAccumulatorUndefined(tryStmt_);
etsg->ReturnAcc(tryStmt_);
} else {
etsg->EmitReturnVoid(tryStmt_);
}
} else if (isReturn) {
if (etsg->CheckControlFlowChange()) {
etsg->StoreAccumulator(tryStmt_, res);
etsg->ControlFlowChangeBreak();
etsg->LoadAccumulator(tryStmt_, res);
}
etsg->ReturnAcc(tryStmt_);
} else if (statement->IsBreakStatement()) {
compiler::Label *target = etsg->ControlFlowChangeBreak(statement->AsBreakStatement()->Ident());
etsg->Branch(tryStmt_, target);
} else if (statement->IsContinueStatement()) {
compiler::Label *target = etsg->ControlFlowChangeContinue(statement->AsContinueStatement()->Ident());
etsg->Branch(tryStmt_, target);
} else {
ES2PANDA_UNREACHABLE();
}
}
}