From 00f451a449ee130be61b31aeaa85ed176409b803 Mon Sep 17 00:00:00 2001 From: qiuyu22 Date: Tue, 15 Jul 2025 01:45:05 +0800 Subject: [PATCH] bugfix for RegAllocator Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICMKDD Description: Current register allocator uses occupied regs for spill, and then add restore movs after the target ins. If the target ins throws in runtime, then the restore movs will not be executed, and thus unexpected behaviors happen. To fix it, we update the algorithm by reserving regs for spill thus the restore operation is not needed. Tested-by: ninja tests (passed) ets_testrunner (passed) Signed-off-by: qiuyu22 --- ets2panda/compiler/core/ETSGen.cpp | 19 +- ets2panda/compiler/core/ETSGen.h | 128 ++++++-- ets2panda/compiler/core/ETSfunction.cpp | 1 + ets2panda/compiler/core/JSCompiler.cpp | 26 +- ets2panda/compiler/core/codeGen.cpp | 17 ++ ets2panda/compiler/core/codeGen.h | 32 +- ets2panda/compiler/core/emitter.cpp | 13 +- ets2panda/compiler/core/function.cpp | 1 + ets2panda/compiler/core/pandagen.cpp | 70 +++-- ets2panda/compiler/core/pandagen.h | 2 + ets2panda/compiler/core/regAllocator.cpp | 288 ++++++++++++------ ets2panda/compiler/core/regAllocator.h | 16 +- ets2panda/compiler/core/regScope.cpp | 2 +- ets2panda/compiler/core/regSpiller.h | 24 ++ ets2panda/compiler/core/vReg.h | 13 + ets2panda/ir/irnode.h | 21 +- ets2panda/test/unit/CMakeLists.txt | 1 + .../test/unit/reg_allocator/CMakeLists.txt | 16 + .../test/unit/reg_allocator/reg_allocator.cpp | 76 +++++ 19 files changed, 592 insertions(+), 174 deletions(-) create mode 100644 ets2panda/test/unit/reg_allocator/CMakeLists.txt create mode 100644 ets2panda/test/unit/reg_allocator/reg_allocator.cpp diff --git a/ets2panda/compiler/core/ETSGen.cpp b/ets2panda/compiler/core/ETSGen.cpp index 5b08e72fe6..55cb61ab9a 100644 --- a/ets2panda/compiler/core/ETSGen.cpp +++ b/ets2panda/compiler/core/ETSGen.cpp @@ -830,16 +830,17 @@ void ETSGen::EmitFailedTypeCastException(const ir::AstNode *node, const VReg src bool isUndef) { const RegScope rs(this); - const auto errorReg = AllocReg(); + const auto boolReg = AllocReg(); if (isUndef) { - Sa().Emit(node, errorReg, 1.0); + Ra().Emit(node, boolReg, 1.0); } else { - Sa().Emit(node, errorReg, 0.0); + Ra().Emit(node, boolReg, 0.0); } - SetVRegType(errorReg, Checker()->GlobalETSBooleanType()); + SetVRegType(boolReg, Checker()->GlobalETSBooleanType()); LoadAccumulatorString(node, util::UString(target->ToString(), Allocator()).View()); - Ra().Emit(node, Signatures::BUILTIN_RUNTIME_FAILED_TYPE_CAST_EXCEPTION, src, errorReg, dummyReg_, 1); + Ra().Emit(node, Signatures::BUILTIN_RUNTIME_FAILED_TYPE_CAST_EXCEPTION, src, boolReg, dummyReg_, 1); + const auto errorReg = AllocReg(); StoreAccumulator(node, errorReg); EmitThrow(node, errorReg); SetAccumulatorType(nullptr); @@ -1698,7 +1699,7 @@ void ETSGen::ConditionalFloat(const ir::AstNode *node) Ra().Emit(node, zeroReg); } Sa().Emit(node, 0); - Sa().Emit(node, isNaNReg); + Ra().Emit(node, isNaNReg); } void ETSGen::BranchConditionalIfFalse(const ir::AstNode *node, Label *endLabel) @@ -1734,15 +1735,15 @@ void ETSGen::BranchIfNullish(const ir::AstNode *node, Label *ifNullish) Sa().Emit(node, ifNullish); } - Sa().Emit(node, tmpObj); + StoreAccumulator(node, tmpObj); EmitIsNull(node); Sa().Emit(node, notTaken); - Sa().Emit(node, tmpObj); + LoadAccumulator(node, tmpObj); Sa().Emit(node, ifNullish); SetLabel(node, notTaken); - Sa().Emit(node, tmpObj); + LoadAccumulator(node, tmpObj); } } diff --git a/ets2panda/compiler/core/ETSGen.h b/ets2panda/compiler/core/ETSGen.h index 71ce602b2c..c871d6c7ad 100644 --- a/ets2panda/compiler/core/ETSGen.h +++ b/ets2panda/compiler/core/ETSGen.h @@ -325,7 +325,7 @@ public: void EmitAnyIsInstance(const ir::AstNode *node, VReg objReg) { - Sa().Emit(node, objReg); + Ra().Emit(node, objReg); } void CallExact(const ir::AstNode *node, checker::Signature *signature, @@ -595,17 +595,26 @@ private: template void BinaryBitwiseArithmetic(const ir::AstNode *node, VReg lhs); - // NOLINTBEGIN(cppcoreguidelines-macro-usage, readability-container-size-empty, modernize-loop-convert) -#define COMPILE_ARG(idx) \ +#define PREPARE_ARG_WITH_IDX(idx) \ ES2PANDA_ASSERT((idx) < arguments.size()); \ ES2PANDA_ASSERT((idx) < signature->Params().size() || signature->RestVar() != nullptr); \ auto *param##idx = (idx) < signature->Params().size() ? signature->Params()[(idx)] : signature->RestVar(); \ auto *paramType##idx = param##idx->TsType(); \ auto ttctx##idx = TargetTypeContext(this, paramType##idx); \ - arguments[idx]->Compile(this); \ - VReg arg##idx = AllocReg(); \ - ApplyConversion(arguments[idx], nullptr); \ + arguments[idx]->Compile(this) + +// NOLINTBEGIN(cppcoreguidelines-macro-usage, readability-container-size-empty, modernize-loop-convert) +#define COMPILE_ARG_WITH_REG(idx, arg) \ + PREPARE_ARG_WITH_IDX(idx); \ + ApplyConversion(arguments[idx], nullptr); \ + ApplyConversionAndStoreAccumulator(arguments[idx], arg, paramType##idx) + +// NOLINTBEGIN(cppcoreguidelines-macro-usage, readability-container-size-empty, modernize-loop-convert) +#define COMPILE_ARG(idx) \ + PREPARE_ARG_WITH_IDX(idx); \ + VReg arg##idx = AllocReg(); \ + ApplyConversion(arguments[idx], nullptr); \ ApplyConversionAndStoreAccumulator(arguments[idx], arg##idx, paramType##idx) template @@ -639,11 +648,22 @@ private: break; } default: { + auto realArgStart = argStart; + // if start and args are not consecutive, alloc a new reg for start + if (argStart.GetIndex() != NextReg().GetIndex() + 1) { + realArgStart = AllocReg(); + MoveVreg(node, realArgStart, argStart); + } + std::vector args; + args.reserve(arguments.size()); for (size_t idx = 0; idx < arguments.size(); idx++) { - COMPILE_ARG(idx); + args.emplace_back(AllocReg()); + } + for (size_t idx = 0; idx < arguments.size(); idx++) { + COMPILE_ARG_WITH_REG(idx, args[idx]); } - Rra().Emit(node, argStart, arguments.size() + 1, name, argStart); + Rra().Emit(node, realArgStart, arguments.size() + 1, name, realArgStart); break; } } @@ -689,9 +709,13 @@ private: } default: { VReg argStart = NextReg(); - + std::vector args; + args.reserve(arguments.size()); + for (size_t idx = 0; idx < arguments.size(); idx++) { + args.emplace_back(AllocReg()); + } for (size_t idx = 0; idx < arguments.size(); idx++) { - COMPILE_ARG(idx); + COMPILE_ARG_WITH_REG(idx, args[idx]); } Rra().Emit(node, argStart, arguments.size(), signature->InternalName(), argStart); @@ -700,16 +724,26 @@ private: } } #undef COMPILE_ARG +#undef COMPILE_ARG_WITH_REG +#undef PREPARE_ARG_WITH_IDX -#define COMPILE_ARG(idx, shift) \ +#define PREPARE_ARG_WITH_IDX(idx, shift) \ ES2PANDA_ASSERT((idx) < arguments.size()); \ ES2PANDA_ASSERT((idx) + (shift) < signature->Params().size() || signature->RestVar() != nullptr); \ auto *paramType##idx = (idx) + (shift) < signature->Params().size() \ ? signature->Params()[(idx) + (shift)]->TsType() \ : signature->RestVar()->TsType(); \ - auto ttctx##idx = TargetTypeContext(this, paramType##idx); \ - VReg arg##idx = AllocReg(); \ - arguments[idx]->Compile(this); \ + auto ttctx##idx = TargetTypeContext(this, paramType##idx) + +#define COMPILE_ARG_WITH_REG(idx, shift, argReg) \ + PREPARE_ARG_WITH_IDX(idx, shift); \ + arguments[idx]->Compile(this); \ + ApplyConversionAndStoreAccumulator(arguments[idx], argReg, paramType##idx) + +#define COMPILE_ARG(idx, shift) \ + PREPARE_ARG_WITH_IDX(idx, shift); \ + VReg arg##idx = AllocReg(); \ + arguments[idx]->Compile(this); \ ApplyConversionAndStoreAccumulator(arguments[idx], arg##idx, paramType##idx) template @@ -736,11 +770,23 @@ private: break; } default: { + auto argStart = data.obj; + // if start and args are not consecutive, alloc a new reg for start + if (argStart.GetIndex() != NextReg().GetIndex() + 1) { + auto newReg = AllocReg(); + MoveVreg(data.node, newReg, argStart); + argStart = newReg; + } + std::vector args; + args.reserve(arguments.size()); + for (size_t idx = 0; idx < arguments.size(); idx++) { + args.emplace_back(AllocReg()); + } for (size_t idx = 0; idx < arguments.size(); idx++) { - COMPILE_ARG(idx, 2U); + COMPILE_ARG_WITH_REG(idx, 2U, args[idx]); } - Rra().Emit(data.node, data.obj, arguments.size() + 2U, name, data.obj); + Rra().Emit(data.node, argStart, arguments.size() + 2U, name, argStart); break; } } @@ -764,26 +810,48 @@ private: break; } default: { + auto argStart = data.obj; + // if start and args are not consecutive, alloc a new reg for start + if (argStart.GetIndex() != NextReg().GetIndex() + 1) { + auto newReg = AllocReg(); + MoveVreg(data.node, newReg, argStart); + argStart = newReg; + } + std::vector args; + args.reserve(arguments.size()); + for (size_t idx = 0; idx < arguments.size(); idx++) { + args.emplace_back(AllocReg()); + } for (size_t idx = 0; idx < arguments.size(); idx++) { - COMPILE_ARG(idx, 3U); + COMPILE_ARG_WITH_REG(idx, 3U, args[idx]); } - Rra().Emit(data.node, data.obj, arguments.size() + 3U, name, data.obj); + Rra().Emit(data.node, argStart, arguments.size() + 3U, name, argStart); break; } } } #undef COMPILE_ARG +#undef COMPILE_ARG_WITH_REG +#undef PREPARE_ARG_WITH_IDX -#define COMPILE_ANY_ARG(idx) \ +#define PREPARE_ANY_ARG_WITH_IDX(idx) \ ES2PANDA_ASSERT((idx) < arguments.size()); \ auto *param##idx = arguments[idx]; \ auto *paramType##idx = param##idx->TsType(); \ auto ttctx##idx = TargetTypeContext(this, paramType##idx); \ - arguments[idx]->Compile(this); \ - VReg arg##idx = AllocReg(); \ - ApplyConversion(arguments[idx], nullptr); \ + arguments[idx]->Compile(this) + +#define COMPILE_ANY_ARG_WITH_REG(idx, argReg) \ + PREPARE_ANY_ARG_WITH_IDX(idx); \ + ApplyConversion(arguments[idx], nullptr); \ + ApplyConversionAndStoreAccumulator(arguments[idx], argReg, paramType##idx) + +#define COMPILE_ANY_ARG(idx) \ + PREPARE_ANY_ARG_WITH_IDX(idx); \ + VReg arg##idx = AllocReg(); \ + ApplyConversion(arguments[idx], nullptr); \ ApplyConversionAndStoreAccumulator(arguments[idx], arg##idx, paramType##idx) template @@ -805,8 +873,13 @@ private: } default: { VReg argStart = NextReg(); + std::vector args; + args.reserve(arguments.size()); + for (size_t idx = 0; idx < arguments.size(); idx++) { + args.emplace_back(AllocReg()); + } for (size_t idx = 0; idx < arguments.size(); idx++) { - COMPILE_ANY_ARG(idx); + COMPILE_ANY_ARG_WITH_REG(idx, args[idx]); } Rra().Emit(node, argStart, arguments.size(), ident->Name(), athis, argStart, arguments.size()); @@ -831,8 +904,13 @@ private: } default: { VReg argStart = NextReg(); + std::vector args; + args.reserve(arguments.size()); + for (size_t idx = 0; idx < arguments.size(); idx++) { + args.emplace_back(AllocReg()); + } for (size_t idx = 0; idx < arguments.size(); idx++) { - COMPILE_ANY_ARG(idx); + COMPILE_ANY_ARG_WITH_REG(idx, args[idx]); } Rra().Emit(node, argStart, arguments.size(), athis, argStart, arguments.size()); @@ -840,6 +918,8 @@ private: } } #undef COMPILE_ANY_ARG +#undef COMPILE_ANY_ARG_WITH_REG +#undef PREPARE_ANY_ARG_WITH_IDX // NOLINTEND(cppcoreguidelines-macro-usage, readability-container-size-empty, modernize-loop-convert) void ToBinaryResult(const ir::AstNode *node, Label *ifFalse); diff --git a/ets2panda/compiler/core/ETSfunction.cpp b/ets2panda/compiler/core/ETSfunction.cpp index 672f29e5f6..73a087c4e7 100644 --- a/ets2panda/compiler/core/ETSfunction.cpp +++ b/ets2panda/compiler/core/ETSfunction.cpp @@ -174,6 +174,7 @@ void ETSFunction::Compile(ETSGen *etsg) CompileSourceBlock(etsg, etsg->RootNode()->AsBlockStatement()); } + etsg->Ra().AdjustInsRegWhenHasSpill(); etsg->SortCatchTables(); } diff --git a/ets2panda/compiler/core/JSCompiler.cpp b/ets2panda/compiler/core/JSCompiler.cpp index f283dccf56..cedc22a7dd 100644 --- a/ets2panda/compiler/core/JSCompiler.cpp +++ b/ets2panda/compiler/core/JSCompiler.cpp @@ -578,10 +578,15 @@ void CompileSuperExprWithoutSpread(PandaGen *pg, const ir::CallExpression *expr) argStart = pg->NextReg(); } - for (const auto *it : expr->Arguments()) { - compiler::VReg arg = pg->AllocReg(); - it->Compile(pg); - pg->StoreAccumulator(it, arg); + std::vector args; + auto &arguments = expr->Arguments(); + args.reserve(arguments.size()); + for (size_t i = 0; i < arguments.size(); ++i) { + args.emplace_back(pg->AllocReg()); + } + for (size_t i = 0; i < arguments.size(); ++i) { + arguments[i]->Compile(pg); + pg->StoreAccumulator(arguments[i], args[i]); } pg->GetFunctionObject(expr); @@ -785,10 +790,15 @@ void JSCompiler::Compile(const ir::NewExpression *expr) const if (!util::Helpers::ContainSpreadElement(expr->Arguments()) && expr->Arguments().size() < compiler::PandaGen::MAX_RANGE_CALL_ARG) { - for (const auto *it : expr->Arguments()) { - compiler::VReg arg = pg->AllocReg(); - it->Compile(pg); - pg->StoreAccumulator(expr, arg); + std::vector args; + auto &arguments = expr->Arguments(); + args.reserve(arguments.size()); + for (size_t i = 0; i < arguments.size(); ++i) { + args.emplace_back(pg->AllocReg()); + } + for (size_t i = 0; i < arguments.size(); ++i) { + arguments[i]->Compile(pg); + pg->StoreAccumulator(expr, args[i]); } pg->NewObject(expr, ctor, expr->Arguments().size() + 2U); diff --git a/ets2panda/compiler/core/codeGen.cpp b/ets2panda/compiler/core/codeGen.cpp index fd01a1c9f2..70fd51db07 100644 --- a/ets2panda/compiler/core/codeGen.cpp +++ b/ets2panda/compiler/core/codeGen.cpp @@ -101,6 +101,23 @@ void CodeGen::SetVRegType(const VReg vreg, const checker::Type *const type) typeMap_.insert_or_assign(vreg, type); } +void CodeGen::UpdateVRegTypeMapWithSpill(VReg::Index spillRegs) +{ + // since map key is const, we have to make a copy rather than modify regs directly + TypeMap newMap(allocator_->Adapter()); + newMap.reserve(typeMap_.size()); + for (const auto &[reg, type] : typeMap_) { + VReg::Index regIdx = reg.GetIndex(); + // parameter and acc are fixed regs, we should not modify them + if (!reg.IsParameter() && regIdx != std::numeric_limits::max()) { + ES2PANDA_ASSERT(regIdx >= spillRegs); + regIdx = regIdx - spillRegs; + } + newMap.insert_or_assign(VReg(regIdx), type); + } + typeMap_ = newMap; +} + const checker::Type *CodeGen::GetVRegType(const VReg vreg) const { const auto it = typeMap_.find(vreg); diff --git a/ets2panda/compiler/core/codeGen.h b/ets2panda/compiler/core/codeGen.h index 8fbeeb0106..75ae780566 100644 --- a/ets2panda/compiler/core/codeGen.h +++ b/ets2panda/compiler/core/codeGen.h @@ -145,6 +145,7 @@ public: [[noreturn]] static void Unimplemented(); void SetVRegType(VReg vreg, const checker::Type *type); + void UpdateVRegTypeMapWithSpill(VReg::Index spillRegs); [[nodiscard]] virtual const checker::Type *GetVRegType(VReg vreg) const; @@ -154,10 +155,39 @@ public: compiler::AstCompiler *GetAstCompiler() const; + const ArenaVector &GetInsns() const noexcept + { + return insns_; + } + + void SetInsns(const ArenaVector &insns) + { + insns_ = insns; + } + + void AddSpillRegs(VReg::Index spillRegs) + { + ES2PANDA_ASSERT(totalRegs_ >= spillRegs); + totalRegs_ -= spillRegs; + ES2PANDA_ASSERT(usedRegs_ >= spillRegs); + usedRegs_ -= spillRegs; + } + uint32_t GetRegsNum() const + { + uint32_t min = std::min(totalRegs_, usedRegs_); + ES2PANDA_ASSERT(min <= VReg::REG_START); + return static_cast(VReg::REG_START - min); + } + + uint32_t GetIndexOfNextReg() const + { + return usedRegs_; + } + + [[nodiscard]] RegAllocator &Ra() noexcept; protected: [[nodiscard]] SimpleAllocator &Sa() noexcept; [[nodiscard]] const SimpleAllocator &Sa() const noexcept; - [[nodiscard]] RegAllocator &Ra() noexcept; [[nodiscard]] const RegAllocator &Ra() const noexcept; [[nodiscard]] RangeRegAllocator &Rra() noexcept; [[nodiscard]] const RangeRegAllocator &Rra() const noexcept; diff --git a/ets2panda/compiler/core/emitter.cpp b/ets2panda/compiler/core/emitter.cpp index 6586372749..3fb7517712 100644 --- a/ets2panda/compiler/core/emitter.cpp +++ b/ets2panda/compiler/core/emitter.cpp @@ -137,7 +137,7 @@ util::StringView FunctionEmitter::SourceCode() const return cg_->VarBinder()->Program()->SourceCode(); } -static Format MatchFormat(const IRNode *node, const Formats &formats) +static Format MatchFormat(const IRNode *node, const Formats &formats, uint32_t regNum) { std::array regs {}; auto regCnt = node->Registers(®s); @@ -155,7 +155,10 @@ static Format MatchFormat(const IRNode *node, const Formats &formats) } } - if (std::all_of(registers.begin(), registers.end(), [limit](const VReg *reg) { return reg->IsValid(limit); })) { + auto isValidCallback = [limit, regNum](const VReg *reg) { + return reg->IsRegOrParamValid(limit, regNum); + }; + if (std::all_of(registers.begin(), registers.end(), isValidCallback)) { return format; } } @@ -164,7 +167,7 @@ static Format MatchFormat(const IRNode *node, const Formats &formats) return *iter; } -static size_t GetIRNodeWholeLength(const IRNode *node) +static size_t GetIRNodeWholeLength(const IRNode *node, uint32_t regNum) { Formats formats = node->GetFormats(); if (formats.empty()) { @@ -172,7 +175,7 @@ static size_t GetIRNodeWholeLength(const IRNode *node) } size_t len = 1; - const auto format = MatchFormat(node, formats); + const auto format = MatchFormat(node, formats, regNum); for (auto fi : format.GetFormatItem()) { len += fi.BitWidth() / 8U; @@ -213,7 +216,7 @@ void FunctionEmitter::GenInstructionDebugInfo(const IRNode *ins, pandasm::Ins *p pandaIns->insDebug.SetLineNumber(nodeRange.start.line + 1U); if (cg_->IsDebug()) { - size_t insLen = GetIRNodeWholeLength(ins); + size_t insLen = GetIRNodeWholeLength(ins, cg_->GetRegsNum()); if (insLen != 0) { pandaIns->insDebug.SetBoundLeft(offset_); pandaIns->insDebug.SetBoundRight(offset_ + insLen); diff --git a/ets2panda/compiler/core/function.cpp b/ets2panda/compiler/core/function.cpp index e5c05ca78b..2304df5f7c 100644 --- a/ets2panda/compiler/core/function.cpp +++ b/ets2panda/compiler/core/function.cpp @@ -278,6 +278,7 @@ void Function::Compile(PandaGen *pg) } } + pg->Ra().AdjustInsRegWhenHasSpill(); pg->SortCatchTables(); } } // namespace ark::es2panda::compiler diff --git a/ets2panda/compiler/core/pandagen.cpp b/ets2panda/compiler/core/pandagen.cpp index d4602f2bea..427e434e53 100644 --- a/ets2panda/compiler/core/pandagen.cpp +++ b/ets2panda/compiler/core/pandagen.cpp @@ -957,6 +957,42 @@ void PandaGen::Call3Args(const ir::AstNode *n, VReg c, VReg thisR, const ArenaVe } } +void PandaGen::HandleCallRange(const ir::AstNode *node, VReg callee, VReg thisReg, + const ArenaVector &arguments, bool hasThis) +{ + auto startReg = callee; + // if has this, then callee, thisReg, ...args should be consecutive + if (hasThis && (startReg.GetIndex() != thisReg.GetIndex() + 1 || thisReg.GetIndex() != NextReg().GetIndex() + 1)) { + startReg = AllocReg(); + MoveVreg(node, startReg, callee); + auto newThisReg = AllocReg(); + MoveVreg(node, newThisReg, thisReg); + } + // if has no this, then callee, ...args should be consecutive + if (!hasThis && (startReg.GetIndex() != NextReg().GetIndex() + 1)) { + startReg = AllocReg(); + MoveVreg(node, startReg, callee); + } + std::vector args; + args.reserve(arguments.size()); + for (size_t i = 0; i < arguments.size(); ++i) { + args.emplace_back(AllocReg()); + } + for (size_t i = 0; i < arguments.size(); ++i) { + arguments[i]->Compile(this); + StoreAccumulator(arguments[i], args[i]); + } + if (hasThis) { + size_t argCount = arguments.size() + 1; + auto constexpr extraArgs = 2; + Rra().Emit(node, startReg, argCount + extraArgs, static_cast(argCount), + startReg); + } else { + size_t argCount = arguments.size(); + Rra().Emit(node, startReg, argCount + 1, static_cast(argCount), startReg); + } +} + void PandaGen::Call(const ir::AstNode *node, VReg callee, VReg thisReg, const ArenaVector &arguments) { bool hasThis = !thisReg.IsInvalid(); @@ -983,20 +1019,7 @@ void PandaGen::Call(const ir::AstNode *node, VReg callee, VReg thisReg, const Ar } } - for (const auto *it : arguments) { - it->Compile(this); - compiler::VReg arg = AllocReg(); - StoreAccumulator(it, arg); - } - - if (hasThis) { - size_t argCount = arguments.size() + 1; - auto constexpr EXTRA_ARGS = 2; - Rra().Emit(node, callee, argCount + EXTRA_ARGS, static_cast(argCount), callee); - } else { - size_t argCount = arguments.size(); - Rra().Emit(node, callee, argCount + 1, static_cast(argCount), callee); - } + HandleCallRange(node, callee, thisReg, arguments, hasThis); } bool PandaGen::CallArgsTagged(const ir::AstNode *node, VReg callee, VReg thisReg, @@ -1061,7 +1084,7 @@ void PandaGen::CallTagged(const ir::AstNode *node, VReg callee, VReg thisReg, if (hasThis) { Ra().Emit(node, callee, thisReg); } else { - Sa().Emit(node, callee); + Ra().Emit(node, callee); } return; } @@ -1070,20 +1093,7 @@ void PandaGen::CallTagged(const ir::AstNode *node, VReg callee, VReg thisReg, return; } - for (const auto *it : arguments) { - it->Compile(this); - compiler::VReg arg = AllocReg(); - StoreAccumulator(it, arg); - } - - if (hasThis) { - auto constexpr EXTRA_ARGS = 2; - size_t argCount = arguments.size() + EXTRA_ARGS; - Rra().Emit(node, callee, argCount + EXTRA_ARGS, static_cast(argCount), callee); - } else { - size_t argCount = arguments.size() + 1; - Rra().Emit(node, callee, argCount + 1, static_cast(argCount), callee); - } + HandleCallRange(node, callee, thisReg, arguments, hasThis); } void PandaGen::SuperCall(const ir::AstNode *node, VReg startReg, size_t argCount) @@ -1803,7 +1813,7 @@ void PandaGen::DirectEval(const ir::AstNode *node, uint32_t parserStatus) LoadAccumulator(node, evalBindings); StOwnByIndex(node, bindings, index++); - Sa().Emit(node, static_cast(parserStatus), arg0, bindings); + Ra().Emit(node, static_cast(parserStatus), arg0, bindings); } void PandaGen::LoadLexicalContext(const ir::AstNode *node) diff --git a/ets2panda/compiler/core/pandagen.h b/ets2panda/compiler/core/pandagen.h index ba4176d6d3..717a1658d8 100644 --- a/ets2panda/compiler/core/pandagen.h +++ b/ets2panda/compiler/core/pandagen.h @@ -271,6 +271,8 @@ private: void Call3Args(const ir::AstNode *n, VReg c, VReg thisR, const ArenaVector &args, bool hasThis); bool CallArgsTagged(const ir::AstNode *node, VReg callee, VReg thisReg, const ArenaVector &arguments, bool hasThis); + void HandleCallRange(const ir::AstNode *node, VReg callee, VReg thisReg, + const ArenaVector &arguments, bool hasThis); FunctionBuilder *builder_ {}; EnvScope *envScope_ {}; OptionalChain *optionalChain_ {}; diff --git a/ets2panda/compiler/core/regAllocator.cpp b/ets2panda/compiler/core/regAllocator.cpp index 9f17711474..e2d148c744 100644 --- a/ets2panda/compiler/core/regAllocator.cpp +++ b/ets2panda/compiler/core/regAllocator.cpp @@ -84,28 +84,6 @@ const RegSpiller &RegAllocatorBase::Spiller() const noexcept return *spiller_; } -std::pair RegAllocatorBase::RegIndicesValid(const IRNode *const ins, const Span ®isters) -{ - const auto &formats = ins->GetFormats(); - std::size_t limit = 0; - - for (const auto &format : formats) { - for (const auto &formatItem : format.GetFormatItem()) { - if (formatItem.IsVReg()) { - limit = 1U << formatItem.BitWidth(); - break; - } - } - - if (std::all_of(registers.begin(), registers.end(), - [limit](const VReg *const reg) { return reg->IsValid(limit); })) { - return {true, limit}; - } - } - - return {false, limit}; -} - VReg RegAllocatorBase::Spill(IRNode *const ins, const VReg reg) const { const auto [spill_info, origin_type] = spiller_->New(); @@ -142,65 +120,177 @@ void RegAllocatorBase::Restore(const IRNode *const ins) const RegAllocator::RegAllocator(CodeGen *const cg, RegSpiller *const spiller) noexcept : RegAllocatorBase(cg, spiller) {} -void RegAllocator::Run(IRNode *const ins, const int32_t spillMax) +// to be optimized: use ruby to generate code which return result directly to avoid loop +static uint32_t GetLimit(IRNode *ins) +{ + auto formats = ins->GetFormats(); + uint32_t limit = 0; + for (const auto &format : formats) { + for (const auto &formatItem : format.GetFormatItem()) { + if (formatItem.IsVReg()) { + uint32_t tmp = 1 << formatItem.BitWidth(); + limit = std::max(tmp, limit); + } + } + } + return limit; +} +// to be optimized: use ruby to generate code which return result directly to avoid loop +static void GetOperandKind(IRNode *ins, std::vector *regsKind) +{ + ES2PANDA_ASSERT(regsKind != nullptr); + auto formats = ins->GetFormats(); + for (const auto &format : formats) { + for (const auto &formatItem : format.GetFormatItem()) { + if (formatItem.IsVReg()) { + regsKind->push_back(formatItem.Kind()); + } + } + } +} + +static bool CheckRegIndices(IRNode *ins, const Span ®isters, uint32_t regsNum) +{ + uint32_t limit = GetLimit(ins); + return std::all_of(registers.begin(), registers.end(), [limit, regsNum](const VReg *reg) { + return reg->IsRegOrParamValid(limit, regsNum); + }); +} + +void RegAllocator::Run(IRNode *const ins, uint32_t realRegCount) { ES2PANDA_ASSERT(Spiller().Restored()); ES2PANDA_ASSERT(ins != nullptr); std::array regs {}; const auto regCnt = ins->Registers(®s); - const auto registers = - Span(regs.data(), regs.data() + (spillMax == std::numeric_limits::max() ? regCnt : spillMax)); + auto realRegCnt = std::min(realRegCount, static_cast(regCnt)); + if (realRegCnt > 0) { + const auto registers = Span(regs.data(), regs.data() + realRegCnt); + Spiller().UpdateSpillRegCount(realRegCnt); + + if (!Spiller().HasSpill() && !CheckRegIndices(ins, registers, GetCodeGen().GetRegsNum())) { + Spiller().SetHasSpill(); + } + } + ins->SetRealRegCount(realRegCnt); + PushBack(ins); +} - std::array dstRegs {}; - ins->OutRegisters(&dstRegs); +bool RegAllocator::CheckFinalInsNeedSpill() +{ + const auto &insns = GetCodeGen().GetInsns(); + return std::all_of(insns.begin(), insns.end(), [this](IRNode *ins) { + uint32_t checkRegCnt = 0; + if (!ins->IsRangeInst()) { + checkRegCnt = ins->GetRealRegCount(); + } else { + std::array regs {}; + checkRegCnt = ins->Registers(®s); + } + if (checkRegCnt == 0) { + return true; + } + std::array regs {}; + ins->Registers(®s); + const auto registers = Span(regs.data(), regs.data() + checkRegCnt); + return CheckRegIndices(ins, registers, GetCodeGen().TotalRegsNum()); + }); +} - const auto [indices_valid, limit] = RegIndicesValid(ins, registers); - if (indices_valid) { - PushBack(ins); +void RegAllocator::AdjustInsRegWhenHasSpill() +{ + const auto spillRegCount = Spiller().GetSpillRegCount(); + if (spillRegCount == 0 || + // here we need to check again because during allocation, + // used regs is changing and parameter regs may become invalid + (!Spiller().HasSpill() && CheckFinalInsNeedSpill())) { + Spiller().ResetSpill(); return; } + ES2PANDA_ASSERT(spillRegCount + GetCodeGen().GetRegsNum() < VReg::REG_MAX); + GetCodeGen().UpdateVRegTypeMapWithSpill(spillRegCount); + GetCodeGen().AddSpillRegs(spillRegCount); + + ArenaVector newInsns(GetCodeGen().Allocator()->Adapter()); + auto &insns = GetCodeGen().GetInsns(); + const auto funcRegsNum = GetCodeGen().GetRegsNum(); + for (auto *ins: insns) { + std::array regs {}; + auto regCnt = ins->Registers(®s); + if (regCnt == 0) { // skip ins without regs + newInsns.push_back(ins); + continue; + } + // min is for excluding dummy regs + auto registersSize = std::min(regCnt, static_cast(ins->GetRealRegCount())); + auto registers = Span(regs.data(), regs.data() + registersSize); + for (auto *reg : registers) { + ES2PANDA_ASSERT(reg != nullptr); + if (!reg->IsParameter()) { + reg->SetIndex(reg->GetIndex() - spillRegCount); + } + } - const auto rs = Spiller().Start(GetCodeGen()); - - std::unordered_set validRegs; - for (auto *const reg : registers) { - if (!reg->IsValid(limit)) { + if (CheckRegIndices(ins, registers, funcRegsNum)) { + // current ins has no spill, continue iterating + newInsns.push_back(ins); continue; } - validRegs.insert(*reg); + // current ins has spill + const auto limit = GetLimit(ins); + if (ins->IsRangeInst()) { + AdjustRangeInsSpill(ins, newInsns, limit); + } else { + AdjustInsSpill(registers, ins, newInsns, limit); + } } + GetCodeGen().SetInsns(newInsns); + Spiller().ResetSpill(); +} - std::vector dstMoves; - size_t i = 0; - for (auto *const reg : registers) { - auto dstInfo = dstRegs[i++]; - if (reg->IsValid(limit)) { +void RegAllocator::AdjustInsSpill(const Span ®isters, IRNode *ins, ArenaVector &newInsns, + uint32_t limit) +{ + uint32_t idx = 0; + VReg::Index spillIndex = VReg::REG_START; + std::vector> dstRegSpills; + const auto realRegCount = ins->GetRealRegCount(); + + std::vector regsKind; + GetOperandKind(ins, ®sKind); + + for (auto *reg : registers) { + if (idx >= realRegCount) { break; } + if (reg->IsRegOrParamValid(limit, GetCodeGen().GetRegsNum())) { + idx++; continue; } - Spiller().Adjust(validRegs); - - auto r = Spill(ins, *reg); - - if (dstInfo.reg != nullptr) { - dstMoves.push_back(GetCodeGen().AllocMov(ins->Node(), dstInfo, r)); + const auto originReg = *reg; + VReg spillReg(spillIndex); + auto *originRegType = GetCodeGen().GetVRegType(*reg); + GetCodeGen().SetVRegType(originReg, originRegType); + GetCodeGen().SetVRegType(spillReg, originRegType); + ES2PANDA_ASSERT(idx < regsKind.size()); + const auto &kind = regsKind[idx]; + if (kind == OperandKind::SRC_VREG || kind == OperandKind::SRC_DST_VREG) { + auto *mov = GetCodeGen().AllocMov(ins->Node(), spillReg, originReg); + newInsns.push_back(mov); } - - *reg = r; + if (kind == OperandKind::DST_VREG || kind == OperandKind::SRC_DST_VREG) { + dstRegSpills.push_back(std::make_pair(originReg, spillReg)); + } + reg->SetIndex(spillIndex--); + idx++; } - PushBack(ins); - - for (auto *mov : dstMoves) { - PushBack(mov); - } + newInsns.push_back(ins); - while (!Spiller().Restored()) { - Restore(ins); + for (auto spillPair : dstRegSpills) { + auto *mov = GetCodeGen().AllocMov(ins->Node(), spillPair.first, spillPair.second); + newInsns.push_back(mov); } - - Spiller().Finalize(); } // RangeRegAllocator @@ -210,47 +300,65 @@ RangeRegAllocator::RangeRegAllocator(CodeGen *const cg, RegSpiller *const spille { } -void RangeRegAllocator::Run(IRNode *const ins, VReg rangeStart, const std::size_t argCount) +void RangeRegAllocator::Run(IRNode *const ins, [[maybe_unused]] VReg rangeStart, const std::size_t argCount) { ES2PANDA_ASSERT(Spiller().Restored()); ES2PANDA_ASSERT(ins != nullptr); - const auto rangeEnd = rangeStart + argCount; - std::array regs {}; const auto regCnt = ins->Registers(®s); + ES2PANDA_ASSERT(regCnt > 0); const auto registers = Span(regs.data(), regs.data() + regCnt); - if (RegIndicesValid(ins, registers).first) { - PushBack(ins); - return; - } - - const auto rs = Spiller().Start(GetCodeGen()); - - auto regIter = registers.begin(); - const auto regIterEnd = regIter + registers.size() - 1; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - - while (regIter != regIterEnd) { - auto *const reg = *regIter; - - *reg = Spill(ins, *reg); - regIter++; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - } - - auto *const regStartReg = *regIter; - auto reg = rangeStart++; - *regStartReg = Spill(ins, reg); - - while (rangeStart != rangeEnd) { - reg = rangeStart++; - Spill(ins, reg); + const auto realRegCount = regCnt + argCount - 1; // minus 1: because of double counting for start range reg + Spiller().UpdateSpillRegCount(realRegCount); + if (!Spiller().HasSpill() && !CheckRegIndices(ins, registers, GetCodeGen().GetRegsNum())) { + Spiller().SetHasSpill(); } - + ins->SetRealRegCount(realRegCount); PushBack(ins); +} - while (!Spiller().Restored()) { - Restore(ins); +void RegAllocator::AdjustRangeInsSpill(IRNode *ins, ArenaVector &newInsns, uint32_t limit) +{ + const auto realRegCount = ins->GetRealRegCount(); + std::array regs {}; + const auto regCnt = ins->Registers(®s); + ES2PANDA_ASSERT(regCnt >= 1); + const uint32_t insLastRegIdx = regCnt - 1; + VReg::Index spillIndex = VReg::REG_START; + const auto startRegIndex = regs[insLastRegIdx]->GetIndex(); // the last reg is the range start reg + const auto funcRegsNum = GetCodeGen().GetRegsNum(); + for (uint32_t idx = 0; idx < realRegCount; ++idx) { + VReg::Index regIndex; + if (idx <= insLastRegIdx) { + // regs[0] ~ regs[insLastRegIdx]: if not suits for limit, then need to + // record reg for spill, and update reg with spillIndex + auto *currentReg = regs[idx]; + if (!currentReg->IsRegOrParamValid(limit, funcRegsNum)) { + regIndex = currentReg->GetIndex(); + currentReg->SetIndex(spillIndex); + } else if (idx < insLastRegIdx) { + continue; // no need to spill current reg + } else { + ES2PANDA_ASSERT(idx == insLastRegIdx); + ES2PANDA_ASSERT(currentReg->IsRegOrParamValid(limit, funcRegsNum)); + // if range start reg suits for limit, no need to spill all range regs + break; + } + } else { + // obtain range reg index from the start reg + regIndex = startRegIndex + (insLastRegIdx - idx); + } + // do spill + VReg spillReg(spillIndex); + const auto originReg = VReg(regIndex); + auto *originRegType = GetCodeGen().GetVRegType(originReg); + ES2PANDA_ASSERT(originRegType != nullptr); + GetCodeGen().SetVRegType(originReg, originRegType); + GetCodeGen().SetVRegType(spillReg, originRegType); + auto *mov = GetCodeGen().AllocMov(ins->Node(), spillReg, originReg); + newInsns.push_back(mov); + spillIndex--; } - - Spiller().Finalize(); + newInsns.push_back(ins); } } // namespace ark::es2panda::compiler diff --git a/ets2panda/compiler/core/regAllocator.h b/ets2panda/compiler/core/regAllocator.h index 2f48a9a674..18bba7e6f8 100644 --- a/ets2panda/compiler/core/regAllocator.h +++ b/ets2panda/compiler/core/regAllocator.h @@ -33,7 +33,6 @@ public: NO_COPY_SEMANTIC(AllocatorBase); NO_MOVE_SEMANTIC(AllocatorBase); ~AllocatorBase() = default; - protected: void PushBack(IRNode *ins) const; [[nodiscard]] CodeGen &GetCodeGen() noexcept; @@ -88,8 +87,6 @@ protected: VReg Spill(IRNode *ins, VReg reg) const; void Restore(const IRNode *ins) const; - [[nodiscard]] static std::pair RegIndicesValid(const IRNode *ins, const Span ®isters); - private: RegSpiller *spiller_; }; @@ -101,15 +98,23 @@ public: NO_MOVE_SEMANTIC(RegAllocator); ~RegAllocator() = default; - template ::max(), typename... Args> + const constexpr static auto MAX_SPILL_MAX = std::numeric_limits::max(); + + template void Emit(const ir::AstNode *const node, Args &&...args) { auto *const ins = Alloc(node, std::forward(args)...); Run(ins, VALID_VREGS); } + void AdjustInsRegWhenHasSpill(); + private: - void Run(IRNode *ins, int32_t spillMax); + void Run(IRNode *ins, uint32_t realRegCount); + + void AdjustInsSpill(const Span ®isters, IRNode *ins, ArenaVector &newInsns, uint32_t limit); + void AdjustRangeInsSpill(IRNode *ins, ArenaVector &newInsns, uint32_t limit); + bool CheckFinalInsNeedSpill(); }; class RangeRegAllocator final : public RegAllocatorBase { @@ -124,6 +129,7 @@ public: { auto *const ins = Alloc(node, std::forward(args)...); Run(ins, rangeStart, argCount); + ins->SetIsRangeInst(); } private: diff --git a/ets2panda/compiler/core/regScope.cpp b/ets2panda/compiler/core/regScope.cpp index f0ef27d3b8..e2c59886df 100644 --- a/ets2panda/compiler/core/regScope.cpp +++ b/ets2panda/compiler/core/regScope.cpp @@ -31,7 +31,7 @@ RegScope::RegScope(CodeGen *cg) : cg_(cg), regBase_(cg_->usedRegs_) {} RegScope::~RegScope() { cg_->totalRegs_ = std::min(cg_->totalRegs_, cg_->usedRegs_); - cg_->usedRegs_ = regBase_; + cg_->usedRegs_ = std::min(regBase_, cg_->usedRegs_); } void RegScope::DebuggerCloseScope() diff --git a/ets2panda/compiler/core/regSpiller.h b/ets2panda/compiler/core/regSpiller.h index 24326f7b59..1112792bb3 100644 --- a/ets2panda/compiler/core/regSpiller.h +++ b/ets2panda/compiler/core/regSpiller.h @@ -56,6 +56,28 @@ public: [[nodiscard]] CodeGen *GetCodeGen() const noexcept; [[nodiscard]] std::pair New() noexcept; void Adjust(const std::unordered_set ®s) noexcept; + bool HasSpill() const + { + return hasSpill_; + } + void SetHasSpill() + { + hasSpill_ = true; + } + uint32_t GetSpillRegCount() const + { + return spillRegs_; + } + void UpdateSpillRegCount(uint32_t arg) + { + spillRegs_ = std::max(spillRegs_, arg); + } + void ResetSpill() + { + spillRegs_ = 0; + hasSpill_ = false; + spillIndex_ = 0; + } protected: void SetCodeGen(CodeGen &cg) noexcept; @@ -65,6 +87,8 @@ protected: private: CodeGen *cg_ {}; std::uint32_t spillIndex_ {0}; + uint32_t spillRegs_ = 0; + bool hasSpill_ = false; }; class DynamicRegSpiller final : public RegSpiller { diff --git a/ets2panda/compiler/core/vReg.h b/ets2panda/compiler/core/vReg.h index d8a8e76377..3cd27614f5 100644 --- a/ets2panda/compiler/core/vReg.h +++ b/ets2panda/compiler/core/vReg.h @@ -54,9 +54,22 @@ public: [[nodiscard]] constexpr bool IsValid(uint32_t limit) const noexcept { + ES2PANDA_ASSERT(limit <= REG_MAX); return (idx_ >= REG_MAX - limit) && (limit == REG_MAX || !IsParameter()); } + [[nodiscard]] constexpr bool IsRegOrParamValid(uint32_t limit, uint32_t regNum) const noexcept + { + if (IsParameter()) { + ES2PANDA_ASSERT(idx_ >= REG_MAX); + uint32_t paramOrder = idx_ - REG_MAX; + ES2PANDA_ASSERT(paramOrder < INVALID_IDX); + ES2PANDA_ASSERT(regNum < INVALID_IDX - paramOrder); + return paramOrder + regNum < limit; + } + return IsValid(limit); + } + [[nodiscard]] constexpr bool IsParameter() const noexcept { return idx_ >= PARAM_START; diff --git a/ets2panda/ir/irnode.h b/ets2panda/ir/irnode.h index 84f269da1f..c9d5e28aa7 100644 --- a/ets2panda/ir/irnode.h +++ b/ets2panda/ir/irnode.h @@ -151,9 +151,28 @@ public: virtual size_t OutRegisters([[maybe_unused]] std::array *regs) const = 0; virtual void Transform(ark::pandasm::Ins *ins, [[maybe_unused]] ProgramElement *programElement, [[maybe_unused]] uint32_t totalRegs) const = 0; - + // for non-range ins, realRegCount_ is the number of non-dummy regs contained in the ins + // for range ins, realRegCount_ is the number of regs contained in the ins and range regs + uint32_t GetRealRegCount() const + { + return realRegCount_; + } + void SetRealRegCount(uint32_t s) + { + realRegCount_ = s; + } + bool IsRangeInst() const + { + return isRange_; + } + void SetIsRangeInst() + { + isRange_ = true; + } private: const ir::AstNode *node_; + uint32_t realRegCount_ = 0; + bool isRange_ = false; }; } // namespace ark::es2panda::compiler diff --git a/ets2panda/test/unit/CMakeLists.txt b/ets2panda/test/unit/CMakeLists.txt index 0d4c20f3e5..6b783c417f 100644 --- a/ets2panda/test/unit/CMakeLists.txt +++ b/ets2panda/test/unit/CMakeLists.txt @@ -28,6 +28,7 @@ add_subdirectory(annotations) add_subdirectory(lsp) add_subdirectory(relative_path) add_subdirectory(any_ins_test) +add_subdirectory(reg_allocator) ets2panda_add_gtest(es2panda_astdumper_tests CPP_SOURCES ast_dumper_test.cpp diff --git a/ets2panda/test/unit/reg_allocator/CMakeLists.txt b/ets2panda/test/unit/reg_allocator/CMakeLists.txt new file mode 100644 index 0000000000..a9e3a6111c --- /dev/null +++ b/ets2panda/test/unit/reg_allocator/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (c) 2025 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. + +ets2panda_add_gtest(reg_allocator + CPP_SOURCES reg_allocator.cpp +) diff --git a/ets2panda/test/unit/reg_allocator/reg_allocator.cpp b/ets2panda/test/unit/reg_allocator/reg_allocator.cpp new file mode 100644 index 0000000000..d803ee4cd7 --- /dev/null +++ b/ets2panda/test/unit/reg_allocator/reg_allocator.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024-2025 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 +#include +#include +#include +#include "assembly-program.h" +#include "test/utils/asm_test.h" + +namespace ark::es2panda::compiler::test { + +class RegAllocator : public ::test::utils::AsmTest { +public: + RegAllocator() = default; + + ~RegAllocator() override = default; + + void RunAnnotationEmitTest(const std::string_view text) + { + auto program = GetCurrentProgram(text); + EXPECT_NE(program, nullptr); + } + +private: + NO_COPY_SEMANTIC(RegAllocator); + NO_MOVE_SEMANTIC(RegAllocator); +}; + +TEST_F(RegAllocator, TryCatch) +{ + std::string_view text = R"( + function throwable(x: int, throwError: boolean): int { + if (throwError) { + throw new Error('err') + } + return x + } + function foo() { + let x0 = 0 + let x1 = 1 + let x2 = 2 + let x3 = 3 + let x4 = 4 + let x5 = 5 + let x6 = 6 + let x7 = 7 + let x8 = 8 + let x9 = 9 + let x10 = 10 + let x11 = 11 + let x12 = 12 + let x13 = 13 + let x14 = 14 + let x15 = 15 + let ret: int = 16 + try { ret = throwable(x15, true) } catch (e) {} + console.log(ret); + } + )"; + + RunAnnotationEmitTest(text); +} + +} // namespace ark::es2panda::compiler::test -- Gitee