diff --git a/ets2panda/BUILD.gn b/ets2panda/BUILD.gn index c6d3e1c296bf4989619d483b2601b55054760d66..b7599122dd05e803d76e524b0db9309749591157 100644 --- a/ets2panda/BUILD.gn +++ b/ets2panda/BUILD.gn @@ -231,6 +231,7 @@ libes2panda_sources = [ "compiler/lowering/ets/optionalLowering.cpp", "compiler/lowering/ets/packageImplicitImport.cpp", "compiler/lowering/ets/partialExportClassGen.cpp", + "compiler/lowering/ets/primitiveConversionPhase.cpp", "compiler/lowering/ets/promiseVoid.cpp", "compiler/lowering/ets/recordLowering.cpp", "compiler/lowering/ets/resizableArrayLowering.cpp", diff --git a/ets2panda/CMakeLists.txt b/ets2panda/CMakeLists.txt index 9ebc4eb9f2e29e91ea29de00145fa8bff77f9d7d..c1d0164457b00edad959ff33a363cdfbe3596e0a 100644 --- a/ets2panda/CMakeLists.txt +++ b/ets2panda/CMakeLists.txt @@ -315,6 +315,7 @@ set(ES2PANDA_LIB_SRC compiler/lowering/ets/enumPostCheckLowering.cpp compiler/lowering/ets/enumPropertiesInAnnotationsLowering.cpp compiler/lowering/ets/setJumpTarget.cpp + compiler/lowering/ets/primitiveConversionPhase.cpp compiler/lowering/ets/unboxLowering.cpp ir/astDump.cpp ir/srcDump.cpp diff --git a/ets2panda/compiler/lowering/ets/primitiveConversionPhase.cpp b/ets2panda/compiler/lowering/ets/primitiveConversionPhase.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e7ae632373ad9b4657442813416175836472a788 --- /dev/null +++ b/ets2panda/compiler/lowering/ets/primitiveConversionPhase.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2021-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 "compiler/lowering/util.h" +#include "primitiveConversionPhase.h" + +namespace ark::es2panda::compiler { + +// Transform calls of the form `x.toY()`, where `x: X` and X and Y are (boxed) primitive types, +// into static calls of the form `X.toY(x)`, which will then be represented in the bytecode as single +// instructions. +// The code relies on the fact that there are no other pairs of methods in the boxed primitive classes, +// such that one is virtual and takes no arguments, the other one has the same name, is static and takes +// an object of the containing type as parameter. + +static ir::Expression *ConvertCallIfNeeded(checker::ETSChecker *checker, ir::CallExpression *call) +{ + if (!call->Arguments().empty()) { + return call; + } + + auto *callee = call->Callee(); + if (!callee->IsMemberExpression() || + callee->AsMemberExpression()->Kind() != ir::MemberExpressionKind::PROPERTY_ACCESS || + !callee->AsMemberExpression()->Property()->IsIdentifier()) { + return call; + } + + auto *calleeObj = callee->AsMemberExpression()->Object(); + auto *calleePropId = callee->AsMemberExpression()->Property()->AsIdentifier(); + + if (calleePropId->Variable()->HasFlag(varbinder::VariableFlags::STATIC)) { + return call; + } + + auto *calleeObjType = calleeObj->TsType(); + if (!calleeObjType->IsETSObjectType() || !calleeObjType->AsETSObjectType()->IsBoxedPrimitive()) { + return call; + } + + auto *staticMethodVar = calleeObjType->AsETSObjectType()->GetProperty( + calleePropId->Name(), checker::PropertySearchFlags::SEARCH_STATIC_METHOD); + if (staticMethodVar == nullptr) { + return call; + } + auto *staticSig = staticMethodVar->TsType()->AsETSFunctionType()->CallSignatures()[0]; + if (staticSig->Params().size() != 1 || + !checker->Relation()->IsIdenticalTo(staticSig->Params()[0]->TsType(), calleeObjType) || + !checker->Relation()->IsIdenticalTo(staticSig->ReturnType(), call->TsType())) { + return call; + } + + /* Now that we know that we deal with a conversion call, replace it with a static call, + except that when the call is `x.toX()`, we can just return `x`. + */ + + auto *allocator = checker->Allocator(); + + if (checker->Relation()->IsIdenticalTo(calleeObjType, call->TsType())) { + calleeObj->SetParent(call->Parent()); + return calleeObj; + } + + auto args = ArenaVector {allocator->Adapter()}; + args.push_back(calleeObj); + + calleePropId->SetVariable(staticMethodVar); + calleePropId->SetTsType(staticMethodVar->TsType()); + auto *newCallee = util::NodeAllocator::ForceSetParent( + allocator, allocator->New(calleeObjType, allocator), calleePropId, + ir::MemberExpressionKind::PROPERTY_ACCESS, false, false); + newCallee->SetTsType(staticMethodVar->TsType()); + newCallee->SetObjectType(calleeObjType->AsETSObjectType()); + auto *newCall = + util::NodeAllocator::ForceSetParent(allocator, newCallee, std::move(args), nullptr, false); + newCall->SetParent(call->Parent()); + + CheckLoweredNode(checker->VarBinder()->AsETSBinder(), checker, newCall); + + return newCall; +} + +bool PrimitiveConversionPhase::PerformForModule(public_lib::Context *ctx, parser::Program *program) +{ + program->Ast()->TransformChildrenRecursively( + [&](ir::AstNode *ast) -> ir::AstNode * { + if (ast->IsCallExpression()) { + return ConvertCallIfNeeded(ctx->checker->AsETSChecker(), ast->AsCallExpression()); + } + return ast; + }, + "PrimitiveConversion"); + return true; +} + +} // namespace ark::es2panda::compiler diff --git a/ets2panda/compiler/lowering/ets/primitiveConversionPhase.h b/ets2panda/compiler/lowering/ets/primitiveConversionPhase.h new file mode 100644 index 0000000000000000000000000000000000000000..2fdd77510eb20489af22ff65be0ab25014774080 --- /dev/null +++ b/ets2panda/compiler/lowering/ets/primitiveConversionPhase.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021-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. + */ + +#ifndef ES2PANDA_COMPILER_LOWERING_PRIMITIVE_CONVERSION_PHASE_H +#define ES2PANDA_COMPILER_LOWERING_PRIMITIVE_CONVERSION_PHASE_H + +#include "compiler/lowering/phase.h" + +namespace ark::es2panda::compiler { + +class PrimitiveConversionPhase : public PhaseForBodies { +public: + std::string_view Name() const override + { + return "PrimitiveConversion"; + } + + bool PerformForModule(public_lib::Context *ctx, parser::Program *program) override; + // bool PostconditionForModule(public_lib::Context *ctx, const parser::Program *program) override; +}; + +} // namespace ark::es2panda::compiler + +#endif diff --git a/ets2panda/compiler/lowering/ets/unboxLowering.cpp b/ets2panda/compiler/lowering/ets/unboxLowering.cpp index c1f52962e2889ad3c309baa1aea4bca1316640e6..2c6b5366bdd9c0f9e3f6f969f2420ee28e9a8d64 100644 --- a/ets2panda/compiler/lowering/ets/unboxLowering.cpp +++ b/ets2panda/compiler/lowering/ets/unboxLowering.cpp @@ -324,6 +324,15 @@ static void HandleDeclarationNode(UnboxContext *uctx, ir::AstNode *ast) /// uctx->handled.insert(ast); } +static util::StringView UnboxerMethodName(checker::Type *unboxedType) +{ + if (unboxedType->IsETSIntEnumType() || unboxedType->IsETSStringEnumType()) { + return "unbox"; + } else { + return "unboxed"; + } +} + static ir::Expression *InsertUnboxing(UnboxContext *uctx, ir::Expression *expr) { auto *boxedType = expr->TsType(); @@ -335,13 +344,17 @@ static ir::Expression *InsertUnboxing(UnboxContext *uctx, ir::Expression *expr) auto *allocator = uctx->checker->Allocator(); - auto *arenaString = allocator->New(allocator->Adapter()); - if (unboxedType->IsETSIntEnumType() || unboxedType->IsETSStringEnumType()) { - arenaString->append("unbox"); - } else { - arenaString->append("unboxed"); + // Avoid unboxing application right on top of boxing. + if (expr->IsETSNewClassInstanceExpression() && + expr->AsETSNewClassInstanceExpression()->GetArguments().size() == 1 && + uctx->checker->Relation()->IsIdenticalTo(expr->AsETSNewClassInstanceExpression()->GetArguments()[0]->TsType(), + unboxedType)) { + auto *ret = expr->AsETSNewClassInstanceExpression()->GetArguments()[0]; + ret->SetParent(parent); + return ret; } - auto *methodId = allocator->New(util::StringView(arenaString), allocator); + + auto *methodId = allocator->New(UnboxerMethodName(unboxedType), allocator); auto *mexpr = util::NodeAllocator::ForceSetParent( allocator, expr, methodId, ir::MemberExpressionKind::PROPERTY_ACCESS, false, false); auto *call = util::NodeAllocator::ForceSetParent( @@ -406,6 +419,19 @@ static ir::Expression *InsertBoxing(UnboxContext *uctx, ir::Expression *expr) auto *boxedType = uctx->checker->MaybeBoxType(unboxedType); auto *parent = expr->Parent(); + // Avoid boxing application right on top of unboxing. + if (expr->IsCallExpression() && expr->AsCallExpression()->Arguments().empty() && + expr->AsCallExpression()->Callee()->IsMemberExpression() && + expr->AsCallExpression()->Callee()->AsMemberExpression()->Property()->IsIdentifier() && + expr->AsCallExpression()->Callee()->AsMemberExpression()->Property()->AsIdentifier()->Name() == + UnboxerMethodName(unboxedType) && + uctx->checker->Relation()->IsIdenticalTo( + expr->AsCallExpression()->Callee()->AsMemberExpression()->Object()->TsType(), boxedType)) { + auto *ret = expr->AsCallExpression()->Callee()->AsMemberExpression()->Object(); + ret->SetParent(parent); + return ret; + } + auto *allocator = uctx->checker->Allocator(); auto args = ArenaVector(allocator->Adapter()); diff --git a/ets2panda/compiler/lowering/phase.cpp b/ets2panda/compiler/lowering/phase.cpp index ccde916faa202cff736ff48d00255c7008f0b1df..54878d141df0055a4d85df294ac534803281596c 100644 --- a/ets2panda/compiler/lowering/phase.cpp +++ b/ets2panda/compiler/lowering/phase.cpp @@ -49,6 +49,7 @@ #include "compiler/lowering/ets/optionalLowering.h" #include "compiler/lowering/ets/packageImplicitImport.h" #include "compiler/lowering/ets/partialExportClassGen.h" +#include "compiler/lowering/ets/primitiveConversionPhase.h" #include "compiler/lowering/ets/promiseVoid.h" #include "compiler/lowering/ets/recordLowering.h" #include "compiler/lowering/ets/resizableArrayLowering.h" @@ -118,6 +119,7 @@ static TypeFromLowering g_typeFromLowering; static ResizableArrayConvert g_resizableArrayConvert; static RestArgsLowering g_restArgsLowering; static InsertOptionalParametersAnnotation g_insertOptionalParametersAnnotation; +static PrimitiveConversionPhase g_primitiveConversionPhase; static UnboxPhase g_unboxPhase; static PluginPhase g_pluginsAfterParse {"plugins-after-parse", ES2PANDA_STATE_PARSED, &util::Plugin::AfterParse}; static PluginPhase g_pluginsAfterBind {"plugins-after-bind", ES2PANDA_STATE_BOUND, &util::Plugin::AfterBind}; @@ -189,6 +191,7 @@ std::vector GetETSPhaseList() &g_optionalArgumentsLowering, // #22952 could be moved to earlier phase &g_genericBridgesLowering, &g_typeFromLowering, + &g_primitiveConversionPhase, &g_unboxPhase, &g_pluginsAfterLowerings, // pluginsAfterLowerings has to come at the very end, nothing should go after it };