diff --git a/ecmascript/compiler/BUILD.gn b/ecmascript/compiler/BUILD.gn index aea743766b772e7796002ae1c422ecb656474099..36d094edc47d181534e4129bf088035fc5ae98c4 100644 --- a/ecmascript/compiler/BUILD.gn +++ b/ecmascript/compiler/BUILD.gn @@ -113,6 +113,7 @@ libark_jsoptimizer_sources = [ "compilation_env.cpp", "compiler_log.cpp", "constant_folding.cpp", + "controlflow_optimization.cpp", "dead_code_elimination.cpp", "debug_info.cpp", "early_elimination.cpp", diff --git a/ecmascript/compiler/aot_compiler.cpp b/ecmascript/compiler/aot_compiler.cpp index 7cc17b50087c2cac2004f172c27f9ce0efb5089f..a853b41d37ec0e78ddc8ff15cf57998b5e60e8e3 100644 --- a/ecmascript/compiler/aot_compiler.cpp +++ b/ecmascript/compiler/aot_compiler.cpp @@ -171,6 +171,7 @@ int Main(const int argc, const char **argv) .EnableInductionVariableAnalysis(cOptions.isEnableInductionVariableAnalysis_) .EnableVerifierPass(cOptions.isEnableVerifierPass_) .EnableMergePoly(cOptions.isEnableMergePoly_) + .EnableControlFlowOptimization(cOptions.isEnableControlFlowOptimization_) .Build(); PassManager passManager(&aotCompilationEnv, diff --git a/ecmascript/compiler/aot_compiler_preprocessor.cpp b/ecmascript/compiler/aot_compiler_preprocessor.cpp index f9b9411d6c08f2fedcb550a4c79e6d9235ff6879..f0ea74691099391e131f1e50452339a39ad9f3fc 100644 --- a/ecmascript/compiler/aot_compiler_preprocessor.cpp +++ b/ecmascript/compiler/aot_compiler_preprocessor.cpp @@ -47,6 +47,7 @@ CompilationOptions::CompilationOptions(JSRuntimeOptions &runtimeOptions) isEnableTypeLowering_ = runtimeOptions.IsEnableTypeLowering(); isEnableEarlyElimination_ = runtimeOptions.IsEnableEarlyElimination(); isEnableLaterElimination_ = runtimeOptions.IsEnableLaterElimination(); + isEnableControlFlowOptimization_ = runtimeOptions.IsEnableControlFlowOptimization(); isEnableValueNumbering_ = runtimeOptions.IsEnableValueNumbering(); isEnableOptInlining_ = runtimeOptions.IsEnableOptInlining(); isEnableOptString_ = runtimeOptions.IsEnableOptString(); diff --git a/ecmascript/compiler/aot_compiler_preprocessor.h b/ecmascript/compiler/aot_compiler_preprocessor.h index 40023aa3cc62b46c3e038208592c1251d0cfc2a2..8596cd3fddbf6da4af1b0a2185c6141b753e7a1d 100644 --- a/ecmascript/compiler/aot_compiler_preprocessor.h +++ b/ecmascript/compiler/aot_compiler_preprocessor.h @@ -75,6 +75,7 @@ struct CompilationOptions { bool isEnableTypeLowering_ {true}; bool isEnableEarlyElimination_ {true}; bool isEnableLaterElimination_ {true}; + bool isEnableControlFlowOptimization_ {true}; bool isEnableValueNumbering_ {true}; bool isEnableOptInlining_ {true}; bool isEnableOptString_ {true}; diff --git a/ecmascript/compiler/call_signature.cpp b/ecmascript/compiler/call_signature.cpp index faff89b72dbcc7b2d74567d243a7a60e56b143d9..aa6fc409a7e8304fc17e718dbea8ea792f52c09b 100644 --- a/ecmascript/compiler/call_signature.cpp +++ b/ecmascript/compiler/call_signature.cpp @@ -14,6 +14,7 @@ */ #include "ecmascript/compiler/call_signature.h" +#include #if defined(__clang__) #pragma clang diagnostic push @@ -966,7 +967,27 @@ DEF_CALL_SIGNATURE(SetSValueWithBarrier) DEF_CALL_SIGNATURE(ASMFastWriteBarrier) { - SETVALUEBARRIER_CALL_ARGS_SIGNATURE_COMMON(ASMFastWriteBarrier); + // 4 : 4 input parameters + CallSignature signature("ASMFastWriteBarrier", 0, 4, + ArgumentsOrder::DEFAULT_ORDER, VariableType::VOID()); + *callSign = signature; + // 4 : 4 input parameters + std::array params = { + VariableType::NATIVE_POINTER(), + VariableType::JS_POINTER(), + VariableType::NATIVE_POINTER(), + VariableType::JS_ANY(), + }; + callSign->SetParameters(params.data()); + callSign->SetGCLeafFunction(true); + callSign->SetCallConv(CallSignature::CallConv::CCallConv); + std::vector paramAttrs = { + CallSignature::ParamAttr::NoAttr, + CallSignature::ParamAttr::NoAttr, + CallSignature::ParamAttr::NoAttr, + CallSignature::ParamAttr::NoAttr, + }; + callSign->SetParamAttr(std::move(paramAttrs)); callSign->SetTargetKind(CallSignature::TargetKind::ASM_CALL_BARRIER_STUB); } diff --git a/ecmascript/compiler/codegen/llvm/llvm_ir_builder.cpp b/ecmascript/compiler/codegen/llvm/llvm_ir_builder.cpp index 0d9475fbbbc78008a3c28917db98ffebc259385d..311ce9ec98dc7b3198de2e77d8b8e7c9a788e1d6 100644 --- a/ecmascript/compiler/codegen/llvm/llvm_ir_builder.cpp +++ b/ecmascript/compiler/codegen/llvm/llvm_ir_builder.cpp @@ -16,6 +16,7 @@ #include "ecmascript/compiler/codegen/llvm/llvm_ir_builder.h" +#include "ecmascript/compiler/share_opcodes.h" #include "ecmascript/deoptimizer/deoptimizer.h" #if defined(__clang__) @@ -706,7 +707,7 @@ void LLVMIRBuilder::VisitRuntimeCall(GateRef gate, const std::vector &i size_t actualNumArgs = 0; GateRef frameState = Circuit::NullGate(); - ComputeArgCountAndExtraInfo(actualNumArgs, frameState, inList, kind); + ComputeArgCountAndExtraInfo(actualNumArgs, frameState, inList, kind, false); std::vector params{glue}; const int index = static_cast(acc_.GetConstantValue(inList[static_cast(CallInputs::TARGET)])); @@ -931,9 +932,11 @@ LLVMValueRef LLVMIRBuilder::GetBuiltinsStubOffset(LLVMValueRef glue) } void LLVMIRBuilder::ComputeArgCountAndExtraInfo(size_t &actualNumArgs, GateRef &frameState, - const std::vector &inList, CallInfoKind kind) + const std::vector &inList, CallInfoKind kind, + bool isAsmCallBarrier) { - if (kind == CallInfoKind::HAS_FRAME_STATE) { + // asm call barrier has reserved framestate + if (kind == CallInfoKind::HAS_FRAME_STATE || isAsmCallBarrier) { actualNumArgs = inList.size() - 1; // 1: frameState frameState = inList.at(actualNumArgs); } else { @@ -1127,7 +1130,7 @@ void LLVMIRBuilder::VisitCall(GateRef gate, const std::vector &inList, int extraParameterCnt = 0; size_t actualNumArgs = 0; GateRef frameState = Circuit::NullGate(); - ComputeArgCountAndExtraInfo(actualNumArgs, frameState, inList, kind); + ComputeArgCountAndExtraInfo(actualNumArgs, frameState, inList, kind, op == OpCode::ASM_CALL_BARRIER); std::vector *paramAttr = calleeDescriptor->GetParamAttr(); // then push the actual parameter for js function call for (size_t paraIdx = firstArg + 1; paraIdx < actualNumArgs; ++paraIdx) { diff --git a/ecmascript/compiler/codegen/llvm/llvm_ir_builder.h b/ecmascript/compiler/codegen/llvm/llvm_ir_builder.h index cef89978e9900bd0358c274f943b9c42ef9919f3..2d0eb4addf7594593910070c0d1f8a183956a620 100644 --- a/ecmascript/compiler/codegen/llvm/llvm_ir_builder.h +++ b/ecmascript/compiler/codegen/llvm/llvm_ir_builder.h @@ -435,7 +435,7 @@ private: CallInfoKind GetCallInfoKind(OpCode op, const std::vector &inList) const; bool GetGCState(GateRef gate, OpCode op, const CallSignature *calleeDescriptor) const; void ComputeArgCountAndExtraInfo(size_t &actualNumArgs, GateRef &frameState, - const std::vector &inList, CallInfoKind kind); + const std::vector &inList, CallInfoKind kind, bool isAsmCallBarrier); void SaveLexicalEnvOnOptJSFuncFrame(LLVMValueRef value); void SaveByteCodePcOnOptJSFuncFrame(LLVMValueRef value); void SaveJSFuncOnOptJSFuncFrame(LLVMValueRef value); diff --git a/ecmascript/compiler/codegen/maple/litecg_ir_builder.cpp b/ecmascript/compiler/codegen/maple/litecg_ir_builder.cpp index fa2e3a04263c356e12feacb6671c32f32ff6b111..74ef58f8c2bb754809069d630a1e96db594ae603 100644 --- a/ecmascript/compiler/codegen/maple/litecg_ir_builder.cpp +++ b/ecmascript/compiler/codegen/maple/litecg_ir_builder.cpp @@ -1697,7 +1697,9 @@ void LiteCGIRBuilder::VisitCall(GateRef gate, const std::vector &inList std::vector paramTypes = lmirBuilder_->LiteCGGetFuncParamTypes(calleeFuncType); bool hasFrameState = (kind == CallInfoKind::HAS_FRAME_STATE); - size_t actualNumArgs = hasFrameState ? (inList.size() - 1) : inList.size(); // 1: frameState + // asm call barrier has reserved framestate + size_t actualNumArgs = (hasFrameState || op == OpCode::ASM_CALL_BARRIER) ? + (inList.size() - 1) : inList.size(); // 1: frameState // then push the actual parameter for js function call for (size_t paraIdx = firstArg + 1; paraIdx < actualNumArgs; ++paraIdx) { diff --git a/ecmascript/compiler/codegen/maple/maple_be/src/cg/aarch64/aarch64_cgfunc.cpp b/ecmascript/compiler/codegen/maple/maple_be/src/cg/aarch64/aarch64_cgfunc.cpp index bc2fbfaa4135874d57ec485823ca6f59245ec291..d8a1aaf18b331a9a322d04d3a3dff724ec932f65 100644 --- a/ecmascript/compiler/codegen/maple/maple_be/src/cg/aarch64/aarch64_cgfunc.cpp +++ b/ecmascript/compiler/codegen/maple/maple_be/src/cg/aarch64/aarch64_cgfunc.cpp @@ -4452,14 +4452,15 @@ RegOperand &AArch64CGFunc::LoadOpndIntoPhysicalRegister(const IntrinsiccallNode void AArch64CGFunc::SelectPureCall(const IntrinsiccallNode &intrnNode) { - DEBUG_ASSERT(intrnNode.NumOpnds() == 6, "must be 6 operands"); // must be 6 operands + // ASMFastWriteBarrier has 5 operands, others must be 6 operands + DEBUG_ASSERT(intrnNode.NumOpnds() == 6 || intrnNode.NumOpnds() == 5, "must be 5 or 6 operands"); // deal with parms ListOperand *srcOpnds = CreateListOpnd(*GetFuncScopeAllocator()); auto &callee = *intrnNode.Opnd(0); auto ptyp = callee.GetPrimType(); RegOperand &calleeReg = LoadIntoRegister(*HandleExpr(intrnNode, callee), ptyp); uint32 i = 1; - for (; i < kSeventhReg; i++) { + for (; i < intrnNode.NumOpnds(); i++) { srcOpnds->PushOpnd(LoadOpndIntoPhysicalRegister(intrnNode, i)); } // R15 is used in asm call diff --git a/ecmascript/compiler/codegen/maple/maple_be/src/cg/x86_64/x64_MPIsel.cpp b/ecmascript/compiler/codegen/maple/maple_be/src/cg/x86_64/x64_MPIsel.cpp index ddae43e9657b6b66bc99a4c90537a7a96106b355..6b7664ddd8719a1aa0198afcfbd894ded205a011 100644 --- a/ecmascript/compiler/codegen/maple/maple_be/src/cg/x86_64/x64_MPIsel.cpp +++ b/ecmascript/compiler/codegen/maple/maple_be/src/cg/x86_64/x64_MPIsel.cpp @@ -376,13 +376,14 @@ void X64MPIsel::SelectOverFlowCall(const IntrinsiccallNode &intrnNode) void X64MPIsel::SelectPureCall(const IntrinsiccallNode &intrnNode) { - DEBUG_ASSERT(intrnNode.NumOpnds() == 6, "must be 6 operands"); // must be 6 operands + // ASMFastWriteBarrier has 5 operands, others must be 6 operands + DEBUG_ASSERT(intrnNode.NumOpnds() == 6 || intrnNode.NumOpnds() == 5, "must be 5 or 6 operands"); ListOperand &srcOpnds = cgFunc->GetOpndBuilder()->CreateList(); auto &callee = *intrnNode.Opnd(0); auto ptyp = callee.GetPrimType(); RegOperand &calleeReg = SelectCopy2Reg(*HandleExpr(intrnNode, callee), ptyp); uint32 i = 1; - for (; i < kSeventhReg; i++) { + for (; i < intrnNode.NumOpnds(); i++) { srcOpnds.PushOpnd(LoadOpndIntoPhysicalRegister(intrnNode, i)); } // R11 is used in asm call diff --git a/ecmascript/compiler/controlflow_optimization.cpp b/ecmascript/compiler/controlflow_optimization.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5619ca999ff9b4af7d2e43e5b57bf16556bcc882 --- /dev/null +++ b/ecmascript/compiler/controlflow_optimization.cpp @@ -0,0 +1,609 @@ +/* + * 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. + */ +#include "controlflow_optimization.h" +#include "ecmascript/compiler/circuit.h" +#include "ecmascript/compiler/gate.h" +#include "ecmascript/compiler/gate_accessor.h" +#include "ecmascript/compiler/graph_editor.h" +#include "ecmascript/compiler/graph_linearizer.h" +#include "ecmascript/compiler/share_opcodes.h" +#include "ecmascript/log_wrapper.h" + +namespace panda::ecmascript::kungfu { + +void ControlFlowOptimization::Initialize() +{ + graphLinearizer_.SetScheduleJSOpcode(); + graphLinearizer_.LinearizeGraph(); + acc_.GetAllGates(gateList_); + circuit_->AdvanceTime(); + for (auto &loopInfo: graphLinearizer_.loops_) { + if (loopInfo.loopExits == nullptr) { + auto loopBegin = loopInfo.loopHead->GetGates().front(); + ASSERT(acc_.GetOpCode(loopBegin) == OpCode::LOOP_BEGIN); + SetGateAndLoopVisited(loopBegin); + continue; + } + for (auto loopExit: *loopInfo.loopExits) { + auto ifTf = loopExit->GetGates().front(); + loopExitBranch_.insert(ifTf); + if (loopInfo.loopExits->size() > 1) { + auto ifBranch = acc_.GetState(ifTf); + updateWorkList_.push(ifBranch); + LOG_COMPILER(DEBUG) << "mark init for loop"; + SetGateAndLoopVisited(ifBranch); + } + } + } +} + +void ControlFlowOptimization::SetLoopExitBranchVisited(GraphLinearizer::LoopInfo *loopInfo) +{ + ASSERT(loopInfo != nullptr); + if (loopInfo->loopExits != nullptr) { + for (auto loopExit: *loopInfo->loopExits) { + auto ifBranch = acc_.GetState(loopExit->GetGates().front()); + if (!acc_.IsVisited(ifBranch)) { + LOG_COMPILER(DEBUG) << "set visited, loopExit Gate front:" << acc_.GetId(ifBranch); + updateWorkList_.push(ifBranch); + acc_.SetVisited(ifBranch); + } + } + } +} + +void ControlFlowOptimization::SetGateAndLoopVisited(GateRef gate) +{ + LOG_COMPILER(DEBUG) << "set gate visited:" << acc_.GetId(gate); + acc_.SetVisited(gate); + auto region = graphLinearizer_.GateToRegion(gate); + if (region == nullptr) { + return; + } + auto loopInfo = graphLinearizer_.GetLoopInfo(region); + while (loopInfo != nullptr) { + auto loopBegin = loopInfo->loopHead->GetGates().front(); + if (acc_.IsVisited(loopBegin)) { + break; + } + LOG_COMPILER(DEBUG) << "set loop visited, loopBegin:" << acc_.GetId(loopBegin); + updateWorkList_.push(loopBegin); + acc_.SetVisited(loopBegin); + SetLoopExitBranchVisited(loopInfo); + loopInfo = loopInfo->outer; + } +} + +bool ControlFlowOptimization::MayBeEliminated(GateRef gate) +{ + auto opcode = acc_.GetOpCode(gate); + switch (opcode) { + case OpCode::IF_BRANCH: + case OpCode::IF_TRUE: + case OpCode::IF_FALSE: + case OpCode::MERGE: + case OpCode::LOOP_BEGIN: + case OpCode::LOOP_BACK: + case OpCode::OVERFLOW_CHECK: + case OpCode::INT32_UNSIGNED_UPPER_BOUND_CHECK: + case OpCode::INT32_CHECK_RIGHT_IS_ZERO: + case OpCode::REMAINDER_IS_NEGATIVE_ZERO: + case OpCode::VALUE_CHECK_NEG_OVERFLOW: + case OpCode::INT32_DIV_WITH_CHECK: + return true; + default: + return false; + } +} + +bool ControlFlowOptimization::IsDependGateWithSideEffect(GateRef gate) +{ + auto opcode = acc_.GetOpCode(gate); + // not state gate + if (acc_.IsState(gate)) { + return false; + } + // not value gate + if (!acc_.GetDependCount(gate)) { + return false; + } + switch (opcode) { + // hcr + case OpCode::LOAD_HCLASS_OPCODE: + // mcr + case OpCode::LOAD_CONST_OFFSET: + case OpCode::LOAD_HCLASS_CONST_OFFSET: + case OpCode::LOAD_HCLASS_FROM_CONSTPOOL: + // lcr + case OpCode::LOAD: + case OpCode::LOAD_WITHOUT_BARRIER: + // other + case OpCode::DEPEND_RELAY: + case OpCode::DEPEND_SELECTOR: + case OpCode::STATE_SPLIT: + return false; + default: + return true; + } +} + +void ControlFlowOptimization::UpdateStateGate(GateRef gate) +{ + // state gate can mark if_branch related gate + auto stateCount = acc_.GetStateCount(gate); + for (size_t i = 0; i < stateCount; ++ i) { + auto gateIn = acc_.GetIn(gate, i); + auto opcode = acc_.GetOpCode(gateIn); + switch (opcode) { + case OpCode::LOOP_BEGIN: + case OpCode::IF_BRANCH: + case OpCode::IF_TRUE: + case OpCode::IF_FALSE: + if (!acc_.IsVisited(gateIn)) { + updateWorkList_.push(gateIn); + LOG_COMPILER(DEBUG) << "update gate " << acc_.GetId(gateIn) << " from " << acc_.GetId(gate); + SetGateAndLoopVisited(gateIn); + } + break; + default: + break; + } + } +} + +void ControlFlowOptimization::UpdateValueGateStateIn(GateRef gate) +{ + auto stateCount = acc_.GetStateCount(gate); + for (size_t i = 0; i < stateCount; ++ i) { + auto gateIn = acc_.GetIn(gate, i); + if (!acc_.IsVisited(gateIn)) { + updateWorkList_.push(gateIn); + LOG_COMPILER(DEBUG) << "update gate " << acc_.GetId(gateIn) << " from " << acc_.GetId(gate); + SetGateAndLoopVisited(gateIn); + } + } +} + +void ControlFlowOptimization::UpdateDependGate(GateRef gate) +{ + // update state path when encountering a state gate + if (acc_.GetStateCount(gate)) { + // stateSplit, dependPhi, dependRelay + auto stateIn = acc_.GetState(gate); + if (!acc_.IsVisited(stateIn)) { + updateWorkList_.push(stateIn); + LOG_COMPILER(DEBUG) << "update gate " << acc_.GetId(stateIn) << " from " << acc_.GetId(gate); + SetGateAndLoopVisited(stateIn); + } + return; + } + size_t inCount = acc_.GetDependCount(gate); + for (size_t i = 0; i < inCount; ++i) { + auto gateIn = acc_.GetDep(gate, i); + if (!acc_.IsVisited(gateIn) && !acc_.IsState(gateIn)) { + updateWorkList_.push(gateIn); + LOG_COMPILER(DEBUG) << "update gate " << acc_.GetId(gateIn) << " from " << acc_.GetId(gate); + SetGateAndLoopVisited(gateIn); + } + } +} + +// mark state use for value gate +void ControlFlowOptimization::UpdateStateUseForValueGate(GateRef gate) +{ + if (acc_.IsState(gate) || acc_.GetDependCount(gate) > 0) { + return; + } + auto uses = acc_.Uses(gate); + for (auto useIt = uses.begin(); useIt != uses.end(); useIt ++) { + if (acc_.IsValueIn(useIt) && acc_.IsState(*useIt) && !acc_.IsVisited(*useIt)) { + updateWorkList_.push(*useIt); + LOG_COMPILER(DEBUG) << "update gate " << acc_.GetId(*useIt) << " from " << acc_.GetId(gate); + SetGateAndLoopVisited(*useIt); + } + } +} + +void ControlFlowOptimization::UpdateGate() +{ + while (!updateWorkList_.empty()) { + auto gate = updateWorkList_.front(); + updateWorkList_.pop(); + auto valueIns = acc_.GetValueIns(gate); + for (auto gateIn: valueIns) { + if (!acc_.IsVisited(gateIn)) { + updateWorkList_.push(gateIn); + LOG_COMPILER(DEBUG) << "update gate " << acc_.GetId(gateIn) << " from " << acc_.GetId(gate); + SetGateAndLoopVisited(gateIn); + } + } + UpdateStateUseForValueGate(gate); + if (acc_.IsState(gate)) { + UpdateStateGate(gate); + } else if (acc_.GetDependCount(gate)) { // value gate have no depend in + UpdateDependGate(gate); + } else { + // value gate can mark state in + UpdateValueGateStateIn(gate); + } + } +} + +void ControlFlowOptimization::Mark() +{ + // 1. mark all gates that can not be eliminated + for (auto gate: gateList_) { + if ((acc_.IsState(gate) && !MayBeEliminated(gate)) || + IsDependGateWithSideEffect(gate) || + (acc_.IsFrameValues(gate) && graphEditor_.FrameValueUsedInCFGTailoring(gate)) || + acc_.GetOpCode(gate) == OpCode::FRAME_ARGS) { + updateWorkList_.push(gate); + LOG_COMPILER(DEBUG) << "mark init"; + SetGateAndLoopVisited(gate); + } + } + + // 2. mark all gates in the graph starting from marked gate + UpdateGate(); +} + +void ControlFlowOptimization::TryEliminateMerge(GateRef gate) +{ + LOG_COMPILER(DEBUG) << "Try eliminate merge gate:" << acc_.GetId(gate); + auto stateCount = acc_.GetStateCount(gate); + if (stateCount > 1) { + return; + } + + auto liveIn = acc_.GetState(gate); + auto uses = acc_.Uses(gate); + for (auto useIt = uses.begin(); useIt != uses.end();) { + auto useOp = acc_.GetOpCode(*useIt); + if (useOp == OpCode::STATE_SPLIT) { + acc_.ReplaceGate(*useIt, circuit_->DeadGate()); + useIt ++; + } else if (acc_.IsState(*useIt)) { + useIt = acc_.ReplaceIn(useIt, liveIn); + } else { + useIt ++; + } + } + acc_.DeleteGate(gate); +} + +void ControlFlowOptimization::EliminateDependSelectorIn(GateRef ifUse) +{ + auto dependUses = acc_.Uses(ifUse); + for (auto dependUseIt = dependUses.begin(); dependUseIt != dependUses.end(); dependUseIt ++) { + if (acc_.IsDependSelector(*dependUseIt)) { + acc_.DecreaseIn(dependUseIt); + break; + } + } +} + +void ControlFlowOptimization::EliminateIfTF(GateRef gate, bool keepEdge) +{ + auto ifUses = acc_.Uses(gate); + auto succGate = Circuit::NullGate(); + size_t stateIndex = -1; + for (auto ifUseIt = ifUses.begin(); ifUseIt != ifUses.end(); ifUseIt ++) { + // delete DependRelay + auto ifUse = *ifUseIt; + if (!acc_.IsState(ifUse)) { + LOG_COMPILER(DEBUG) << "in eliminate IfTF, gate:" << acc_.GetId(gate); + LOG_COMPILER(DEBUG) << "now ifUse:" << acc_.GetId(ifUse); + if (keepEdge) { + acc_.ReplaceGate(ifUse, circuit_->DeadGate()); + } else { + // merge without value phi may be partly eliminated + // The number of merge input should be the same with depend phi + EliminateDependSelectorIn(ifUse); + acc_.DeleteGate(ifUse); + } + } else { + succGate = ifUse; + stateIndex = ifUseIt.GetIndex(); + } + } + // delete IfTrue/IfFalse + if (keepEdge) { + acc_.ReplaceGate(gate, circuit_->DeadGate()); + } else { + if (succGate != Circuit::NullGate()) { + LOG_COMPILER(DEBUG) << "decreaseIn for gate " << acc_.GetId(succGate) << " edge index:" << stateIndex; + acc_.DecreaseIn(succGate, stateIndex); + } + acc_.DeleteGate(gate); + } +} + +bool ControlFlowOptimization::TryEliminateBranch(GateRef gate) +{ + GateRef succGate = Circuit::NullGate(); + auto uses = acc_.Uses(gate); + for (auto use: uses) { + if (loopExitBranch_.count(use)) { + return false; + } + auto ifUses = acc_.Uses(use); + for (auto ifUse: ifUses) { + if (!acc_.IsState(ifUse)) { + continue; + } + if (succGate == Circuit::NullGate()) { + succGate = ifUse; + } else if (acc_.GetOpCode(ifUse) != OpCode::MERGE || succGate != ifUse || acc_.IsVisited(ifUse)) { + // !!! check whether ifUse is visited + // ValuePhi hasn't applied decreaseIn when eliminating its input. + // If not check, the compiler may abort at graph linearizer when getting edge index + // from value phi (e.g. getIndex() - 1 in GetCommonDominatorOfAllUses). + // Branches without state node may also be eliminaed, e.g. x = cond ? y : z + // This may obstruct some redundant branches to be eliminated, for instance, + // switch with useless cases. LLVM may handle the case. + return true; + } + } + } + ASSERT(acc_.GetOpCode(succGate) == OpCode::MERGE); + bool keepEdge = true; + for (auto use: uses) { + EliminateIfTF(use, keepEdge); + keepEdge = false; + } + // delete IfBranch + acc_.ReplaceGate(gate, circuit_->DeadGate()); + TryEliminateMerge(succGate); + LOG_COMPILER(DEBUG) << "eliminate branch successfully"; + return false; +} + +void ControlFlowOptimization::EliminateLoopBranch(GateRef gate) +{ + auto stateIn = acc_.GetState(gate); + auto uses = acc_.Uses(gate); + [[maybe_unused]] bool eliminated = false; + std::vector ifTF; + for (auto useIt = uses.begin(); useIt != uses.end();) { + ifTF.push_back(*useIt); + if (loopExitBranch_.count(*useIt)) { + eliminated = true; + useIt = acc_.ReplaceIn(useIt, stateIn); + } else { + useIt ++; + } + } + ASSERT(eliminated); + acc_.DeleteGate(gate); + for (auto ifGate: ifTF) { + // keep depend wire of loop exit branch + EliminateIfTF(ifGate, loopExitBranch_.count(ifGate)); + } +} + +void ControlFlowOptimization::EliminateLoop(GateRef loopBegin) +{ + auto region = graphLinearizer_.GateToRegion(loopBegin); + auto loopBeginUses = acc_.Uses(loopBegin); + + auto stateCount = acc_.GetStateCount(loopBegin); + auto stateIn = Circuit::NullGate(); + auto loopBack = Circuit::NullGate(); + for (size_t idx = 0; idx < stateCount; ++ idx) { + auto gateIn = acc_.GetIn(loopBegin, idx); + auto opcodeIn = acc_.GetOpCode(gateIn); + if (opcodeIn == OpCode::LOOP_BACK) { + loopBack = gateIn; + } else { + stateIn = gateIn; + } + } + + GateRef curGate = loopBack; + while (curGate != loopBegin) { + auto now = curGate; + auto nowOp = acc_.GetOpCode(curGate); + curGate = acc_.GetState(curGate); + switch (nowOp) { + case OpCode::LOOP_BACK: + acc_.DeleteGate(now); + break; + case OpCode::IF_TRUE: + case OpCode::IF_FALSE: + break; + case OpCode::IF_BRANCH: + EliminateLoopBranch(now); + break; + default: + UNREACHABLE(); + break; + } + } + for (auto useIt = loopBeginUses.begin(); useIt != loopBeginUses.end();) { + if (acc_.IsState(*useIt)) { + useIt = acc_.ReplaceIn(useIt, stateIn); + } else { + useIt ++; + } + } + LOG_COMPILER(DEBUG) << "eliminate loop " << acc_.GetId(loopBegin) << " successfully"; + acc_.DeleteGate(loopBegin); +} + +bool ControlFlowOptimization::AlwaysAliveValue(GateRef gate) +{ + auto opcode = acc_.GetOpCode(gate); + switch (opcode) { + case OpCode::FRAME_ARGS: + case OpCode::ARG: + case OpCode::CIRCUIT_ROOT: + case OpCode::STATE_ENTRY: + case OpCode::DEPEND_ENTRY: + case OpCode::FRAME_STATE: + case OpCode::FRAME_VALUES: + case OpCode::CONSTANT: + case OpCode::RETURN_LIST: + case OpCode::ARG_LIST: + case OpCode::GET_SHARED_CONSTPOOL: + case OpCode::GET_UNSHARED_CONSTPOOL: + return true; + default: + return false; + } +} + +void ControlFlowOptimization::EliminateLastValueOrDepend() +{ + std::queue queue; + for (auto gate: gateList_) { + if (AlwaysAliveValue(gate) || acc_.IsNop(gate) || + acc_.IsState(gate) || acc_.IsVisited(gate)) { + continue; + } + // exclude live depend selector, depend relay, and state split + if (acc_.GetDependCount(gate) && acc_.GetStateCount(gate) && + !acc_.IsNop(acc_.GetState(gate))) { + continue; + } + queue.push(gate); + lastValueFlags_[gate] = MarkCode::VISITED; + } + while (!queue.empty()) { + auto gate = queue.front(); + queue.pop(); + lastValueFlags_[gate] = MarkCode::FINISHED; + auto opcode = acc_.GetOpCode(gate); + LOG_COMPILER(DEBUG) << "delete unmarked gate:" << acc_.GetId(gate); + if (acc_.GetDependCount(gate) == 1) { // is depend gate + acc_.ReplaceGate(gate, optimizedGate_); + } else { // is value gate + acc_.UpdateAllUses(gate, optimizedGate_); + acc_.DeleteGate(gate); + } + } +} + +void ControlFlowOptimization::PrepareForElimination(std::unordered_map &gateUseCount) +{ + for (auto gate: gateList_) { + if (!acc_.IsState(gate)) { + continue; + } + auto uses = acc_.Uses(gate); + for (auto useIt = uses.begin(); useIt != uses.end(); useIt ++) { + if (acc_.GetOpCode(gate) == OpCode::LOOP_BACK && acc_.GetOpCode(*useIt) == OpCode::LOOP_BEGIN) { + continue; + } + gateUseCount[gate] += (acc_.IsStateIn(useIt) && acc_.IsState(*useIt)); + } + } + for (auto gate: gateList_) { + if (acc_.IsState(gate) && gateUseCount[gate] == 0) { + workList_.push(gate); + } + } +} + +void ControlFlowOptimization::EliminateUnmarkedGate(GateRef gate) +{ + bool needUpdate = false; + auto opcode = acc_.GetOpCode(gate); + switch (opcode) { + case OpCode::IF_BRANCH: + needUpdate = TryEliminateBranch(gate); + break; + case OpCode::LOOP_BEGIN: + EliminateLoop(gate); + break; + case OpCode::OVERFLOW_CHECK: + case OpCode::INT32_UNSIGNED_UPPER_BOUND_CHECK: + case OpCode::INT32_CHECK_RIGHT_IS_ZERO: + case OpCode::REMAINDER_IS_NEGATIVE_ZERO: + case OpCode::VALUE_CHECK_NEG_OVERFLOW: + case OpCode::INT32_DIV_WITH_CHECK: + acc_.ReplaceGate(gate, circuit_->DeadGate()); + break; + default: + break; + } + // unmarked branch may not be eliminated, e.g. branch with multi-loopExit loops + // In this case, the unmarked branch should be marked + if (needUpdate) { + updateWorkList_.push(gate); + LOG_COMPILER(DEBUG) << "mark in elimination"; + SetGateAndLoopVisited(gate); + UpdateGate(); + } +} + +void ControlFlowOptimization::TryEliminateUnmarkedGate() +{ + std::unordered_map gateUseCount; + PrepareForElimination(gateUseCount); + while (!workList_.empty()) { + auto gate = workList_.front(); + LOG_COMPILER(DEBUG) << "maybe unmarked gate:" << acc_.GetId(gate); + workList_.pop(); + auto stateCount = acc_.GetStateCount(gate); + for (size_t idx = 0; idx < stateCount; ++ idx) { + auto stateIn = acc_.GetIn(gate, idx); + if (acc_.IsNop(stateIn) || acc_.GetOpCode(stateIn) == OpCode::LOOP_BACK || !acc_.IsState(stateIn)) { + // loopBegin -> loopBack + continue; + } + ASSERT(gateUseCount[stateIn] > 0); + LOG_COMPILER(DEBUG) << "now use count:" << gateUseCount[stateIn] << " stateIn:" << acc_.GetId(stateIn); + gateUseCount[stateIn] --; + if (gateUseCount[stateIn] == 0) { + workList_.push(stateIn); + LOG_COMPILER(DEBUG) << "push gate..."; + } + } + if (acc_.IsVisited(gate)) { + continue; + } + LOG_COMPILER(DEBUG) << "gate not visited, try eliminate"; + EliminateUnmarkedGate(gate); + } + + // delete last value or depend gate with no side effects + EliminateLastValueOrDepend(); +} + +std::string ControlFlowOptimization::GetMethodName() +{ + return methodName_; +} + +void ControlFlowOptimization::Run() +{ + Initialize(); + Mark(); + TryEliminateUnmarkedGate(); + + if (enableLog_) { + LOG_COMPILER(INFO) << ""; + LOG_COMPILER(INFO) << "\033[34m" + << "====================" + << " After ControlFlow Optimization " + << "[" << GetMethodName() << "]" + << "====================" + << "\033[0m"; + circuit_->PrintAllGatesWithBytecode(); + LOG_COMPILER(INFO) << "\033[34m" << "========================= End ==========================" << "\033[0m"; + } +} + +}; \ No newline at end of file diff --git a/ecmascript/compiler/controlflow_optimization.h b/ecmascript/compiler/controlflow_optimization.h new file mode 100644 index 0000000000000000000000000000000000000000..3d88458620635009ddcfbefc7281eaf70bc20d2b --- /dev/null +++ b/ecmascript/compiler/controlflow_optimization.h @@ -0,0 +1,80 @@ +/* + * 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. + */ +#ifndef ECMASCRIPT_CONTROLFLOW_OPTIMIZATION_H +#define ECMASCRIPT_CONTROLFLOW_OPTIMIZATION_H + +#include "ecmascript/compiler/gate.h" +#include "ecmascript/compiler/gate_accessor.h" +#include "ecmascript/compiler/graph_editor.h" +#include "ecmascript/compiler/graph_linearizer.h" +#include "ecmascript/compiler/lcr_gate_meta_data.h" +#include "ecmascript/compiler/share_gate_meta_data.h" +#include + +namespace panda::ecmascript::kungfu { + +class ControlFlowOptimization { +public: + ControlFlowOptimization(Circuit *circuit, Chunk *chunk, bool enableLog, const std::string &name): acc_(circuit), + graphLinearizer_(circuit, enableLog, name, chunk, false, true), + graphEditor_(circuit), circuit_(circuit), enableLog_(enableLog), methodName_(name) { + optimizedGate_ = circuit_->GetConstantGate(MachineType::I64, + JSTaggedValue::VALUE_OPTIMIZED_OUT, + GateType::TaggedValue()); + } + + void Run(); +private: + GateAccessor acc_; + GraphLinearizer graphLinearizer_; + GraphEditor graphEditor_; + Circuit *circuit_; + bool enableLog_; + std::string methodName_; + std::queue workList_; + std::queue updateWorkList_; + std::vector gateList_; + std::unordered_set loopExitBranch_; + std::unordered_map lastValueFlags_; + GateRef optimizedGate_; + + void Initialize(); + void Mark(); + void UpdateGate(); + void UpdateStateGate(GateRef gate); + void UpdateValueGateStateIn(GateRef gate); + void UpdateStateUseForValueGate(GateRef gate); + void UpdateDependGate(GateRef gate); + void TryEliminateUnmarkedGate(); + void EliminateUnmarkedGate(GateRef gate); + bool MayBeEliminated(GateRef gate); + void SetLoopExitBranchVisited(GraphLinearizer::LoopInfo *loopInfo); + void SetGateAndLoopVisited(GateRef gate); + bool TryEliminateBranch(GateRef gate); + void TryEliminateMerge(GateRef gate); + void EliminateLoop(GateRef loopBegin); + void EliminateIfTF(GateRef gate, bool keepEdge = true); + void EliminateDependSelectorIn(GateRef ifUse); + void EliminateLoopBranch(GateRef gate); + bool IsDependGateWithSideEffect(GateRef gate); + void PrepareForElimination(std::unordered_map &gateUseCount); + void EliminateLastValueOrDepend(); + bool AlwaysAliveValue(GateRef gate); + std::string GetMethodName(); +}; + +}; + +#endif \ No newline at end of file diff --git a/ecmascript/compiler/graph_editor.h b/ecmascript/compiler/graph_editor.h index 4d745208e04b6aa78403e4e012ac970ff4894356..e918c4e89e9528079190443add2725eb35940acb 100644 --- a/ecmascript/compiler/graph_editor.h +++ b/ecmascript/compiler/graph_editor.h @@ -32,13 +32,14 @@ public: static void RemoveDeadState(Circuit* circuit, GateRef gate); static void EliminateRedundantPhi(Circuit* circuit, bool enableLog, const std::string& methodName); + bool FrameValueUsedInCFGTailoring(GateRef gate); + private: void ReplaceGate(GateRef gate); void RemoveGate(); void PropagateGate(const Edge& edge); void PropagateMerge(const Edge& edge); void EliminatePhi(); - bool FrameValueUsedInCFGTailoring(GateRef gate); Circuit *circuit_ {nullptr}; GateAccessor acc_; diff --git a/ecmascript/compiler/graph_linearizer.h b/ecmascript/compiler/graph_linearizer.h index 31f9f7f2c3a250f360dc2d89afa475e53f93ecdf..22da309141674dd39d73bd227e377b75eee1c8ad 100644 --- a/ecmascript/compiler/graph_linearizer.h +++ b/ecmascript/compiler/graph_linearizer.h @@ -347,6 +347,10 @@ private: GateRegion* GateToRegion(GateRef gate) const { + auto id = acc_.GetId(gate); + if (id >= gateIdToGateInfo_.size()) { + return nullptr; + } const GateInfo& info = GetGateInfo(gate); return info.region; } @@ -478,6 +482,7 @@ private: friend class LoopInfoBuilder; friend class StateSplitLinearizer; friend class InductionVariableAnalysis; + friend class ControlFlowOptimization; }; }; // namespace panda::ecmascript::kungfu diff --git a/ecmascript/compiler/number_speculative_lowering.cpp b/ecmascript/compiler/number_speculative_lowering.cpp index e5f87c21eac55ca8d9a1a51ade243818949d50ad..09291b5a2f535d1065578cc9bc327bfd3cff1ae4 100644 --- a/ecmascript/compiler/number_speculative_lowering.cpp +++ b/ecmascript/compiler/number_speculative_lowering.cpp @@ -990,7 +990,13 @@ void NumberSpeculativeLowering::VisitStringCompare(GateRef gate) GateRef result; ASSERT(Op == TypedBinOp::TYPED_EQ); - result = builder_.StringEqual(left, right); + if (GetOutputType(left) == TypeInfo::CHAR && GetOutputType(right) == TypeInfo::CHAR) { + result = builder_.Int32Equal(left, right); + } else if (GetOutputType(left) == TypeInfo::CHAR || GetOutputType(right) == TypeInfo::CHAR) { + result = builder_.Boolean(false); + } else { + result = builder_.StringEqual(left, right); + } acc_.SetMachineType(gate, MachineType::I1); acc_.SetGateType(gate, GateType::NJSValue()); diff --git a/ecmascript/compiler/number_speculative_retype.cpp b/ecmascript/compiler/number_speculative_retype.cpp index 8688d04e92f852c5561c5e747a09c82bbdab5542..6138bc3fdb3b6f324f55cb1557a9eace82923bae 100644 --- a/ecmascript/compiler/number_speculative_retype.cpp +++ b/ecmascript/compiler/number_speculative_retype.cpp @@ -15,8 +15,15 @@ #include "ecmascript/compiler/number_speculative_retype.h" +#include "ecmascript/compiler/circuit.h" #include "ecmascript/compiler/circuit_arg_indices.h" #include "ecmascript/compiler/circuit_builder-inl.h" +#include "ecmascript/compiler/gate.h" +#include "ecmascript/compiler/lcr_gate_meta_data.h" +#include "ecmascript/compiler/number_gate_info.h" +#include "ecmascript/compiler/type.h" +#include "ecmascript/log_wrapper.h" +#include "macros.h" namespace panda::ecmascript::kungfu { GateRef NumberSpeculativeRetype::SetOutputType(GateRef gate, GateType gateType) @@ -1038,15 +1045,25 @@ GateRef NumberSpeculativeRetype::CheckAndConvertToBool(GateRef gate, GateType ga } TypeInfo output = GetOutputTypeInfo(gate); + GateRef cGate = convertCache_.GetCachedGate(gate, TaggedTypeInfo::BOOL); + if (cGate != Circuit::NullGate()) { + return cGate; + } switch (output) { case TypeInfo::INT1: return gate; case TypeInfo::INT32: - return builder_.ConvertInt32ToBool(gate); + cGate = builder_.ConvertInt32ToBool(gate); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::BOOL, cGate); + return cGate; case TypeInfo::UINT32: - return builder_.ConvertUInt32ToBool(gate); + cGate = builder_.ConvertUInt32ToBool(gate); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::BOOL, cGate); + return cGate; case TypeInfo::FLOAT64: - return builder_.ConvertFloat64ToBool(gate); + cGate = builder_.ConvertFloat64ToBool(gate); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::BOOL, cGate); + return cGate; case TypeInfo::CHAR: // CHAR refers to string of length 1, while only empty string converts to false in a boolean context. return builder_.Boolean(true); @@ -1245,12 +1262,23 @@ GateRef NumberSpeculativeRetype::CheckAndConvertToInt32(GateRef gate, GateType g return result; } TypeInfo output = GetOutputTypeInfo(gate); + GateRef cGate = Circuit::NullGate(); switch (output) { case TypeInfo::INT1: - result = builder_.ConvertBoolToInt32(gate, support); + cGate = convertCache_.GetCachedGate(gate, TaggedTypeInfo::INT32); + if (cGate == Circuit::NullGate()) { + cGate = builder_.ConvertBoolToInt32(gate, support); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::INT32, cGate); + } + result = cGate; break; case TypeInfo::CHAR: { - result = builder_.ConvertCharToInt32(gate); + cGate = convertCache_.GetCachedGate(gate, TaggedTypeInfo::INT32); + if (cGate == Circuit::NullGate()) { + cGate = builder_.ConvertCharToInt32(gate); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::INT32, cGate); + } + result = cGate; break; } case TypeInfo::INT32: @@ -1267,7 +1295,12 @@ GateRef NumberSpeculativeRetype::CheckAndConvertToInt32(GateRef gate, GateType g if (needCheckFloat64) { result = builder_.CheckFloat64ConvertToInt32Legally(gate); } else { - result = builder_.ConvertFloat64ToInt32(gate); + cGate = convertCache_.GetCachedGate(gate, TaggedTypeInfo::INT32); + if (cGate == Circuit::NullGate()) { + cGate = builder_.ConvertFloat64ToInt32(gate); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::INT32, cGate); + } + result = cGate; } break; } @@ -1302,14 +1335,20 @@ GateRef NumberSpeculativeRetype::CheckBoundAndConvertToInt32(GateRef gate, Conve return result; } TypeInfo output = GetOutputTypeInfo(gate); + result = convertCache_.GetCachedGate(gate, TaggedTypeInfo::INT32); + if (result != Circuit::NullGate()) { + ResizeAndSetTypeInfo(result, TypeInfo::INT32); + return result; + } switch (output) { case TypeInfo::INT1: result = builder_.ConvertBoolToInt32(gate, support); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::INT32, result); break; - case TypeInfo::CHAR: { + case TypeInfo::CHAR: result = builder_.ConvertCharToInt32(gate); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::INT32, result); break; - } case TypeInfo::INT32: return gate; case TypeInfo::UINT32: { @@ -1369,19 +1408,28 @@ GateRef NumberSpeculativeRetype::CheckAndConvertToFloat64(GateRef gate, GateType return result; } TypeInfo output = GetOutputTypeInfo(gate); + result = convertCache_.GetCachedGate(gate, TaggedTypeInfo::FLOAT64); + if (result != Circuit::NullGate()) { + ResizeAndSetTypeInfo(result, TypeInfo::FLOAT64); + return result; + } switch (output) { case TypeInfo::INT1: result = builder_.ConvertBoolToFloat64(gate, ToConvertSupport(convert)); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::FLOAT64, result); break; case TypeInfo::CHAR: { result = builder_.ConvertCharToDouble(gate); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::FLOAT64, result); break; } case TypeInfo::INT32: result = builder_.ConvertInt32ToFloat64(gate); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::FLOAT64, result); break; case TypeInfo::UINT32: result = builder_.ConvertUInt32ToFloat64(gate); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::FLOAT64, result); break; case TypeInfo::FLOAT64: return gate; @@ -1424,24 +1472,46 @@ GateRef NumberSpeculativeRetype::CheckAndConvertToFloat64(GateRef gate, GateType return result; } +GateRef NumberSpeculativeRetype::CheckAndConvertFromBoolToTagged(GateRef gate, GateType gateType, + ConvertToNumber convert) +{ + if (gateType.IsNumberType() && convert != ConvertToNumber::DISABLE) { + GateRef cGate = builder_.ConvertInt32ToTaggedInt(builder_.BooleanToInt32(gate)); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::TAGGED_INT, cGate); + return cGate; + } + GateRef cGate = builder_.ConvertBoolToTaggedBoolean(gate); + convertCache_.SetCachedGate(gate, TaggedTypeInfo::TAGGED_BOOL, cGate); + return cGate; +} + GateRef NumberSpeculativeRetype::CheckAndConvertToTagged(GateRef gate, GateType gateType, ConvertToNumber convert) { TypeInfo output = GetOutputTypeInfo(gate); + TaggedTypeInfo info = GetTaggedTypeOfTaggedConvertion(output); + GateRef cGate = convertCache_.GetCachedGate(gate, info); + if (cGate != Circuit::NullGate()) { + return cGate; + } switch (output) { - case TypeInfo::INT1: { - if (gateType.IsNumberType() && convert != ConvertToNumber::DISABLE) { - return builder_.ConvertInt32ToTaggedInt(builder_.BooleanToInt32(gate)); - } - return builder_.ConvertBoolToTaggedBoolean(gate); - } + case TypeInfo::INT1: + return CheckAndConvertFromBoolToTagged(gate, gateType, convert); case TypeInfo::INT32: - return builder_.ConvertInt32ToTaggedInt(gate); + cGate = builder_.ConvertInt32ToTaggedInt(gate); + convertCache_.SetCachedGate(gate, info, cGate); + return cGate; case TypeInfo::UINT32: - return builder_.ConvertUInt32ToTaggedNumber(gate); + cGate = builder_.ConvertUInt32ToTaggedNumber(gate); + convertCache_.SetCachedGate(gate, info, cGate); + return cGate; case TypeInfo::FLOAT64: - return builder_.ConvertFloat64ToTaggedDouble(gate); + cGate = builder_.ConvertFloat64ToTaggedDouble(gate); + convertCache_.SetCachedGate(gate, info, cGate); + return cGate; case TypeInfo::CHAR: - return builder_.ConvertCharToEcmaString(gate); + cGate = builder_.ConvertCharToEcmaString(gate); + convertCache_.SetCachedGate(gate, info, cGate); + return cGate; case TypeInfo::HOLE_INT: return builder_.CheckHoleIntAndConvertToTaggedInt(gate); case TypeInfo::HOLE_DOUBLE: @@ -1467,24 +1537,72 @@ GateRef NumberSpeculativeRetype::CheckAndConvertToTagged(GateRef gate, GateType } } +NumberSpeculativeRetype::TaggedTypeInfo NumberSpeculativeRetype::GetTaggedTypeOfTaggedConvertion( + TypeInfo output, + GateType gateType, + ConvertToNumber convert +) +{ + switch (output) { + case TypeInfo::INT1: + if (gateType.IsNumberType() && convert != ConvertToNumber::DISABLE) { + return TaggedTypeInfo::TAGGED_INT; + } + return TaggedTypeInfo::TAGGED_BOOL; + case TypeInfo::INT32: + return TaggedTypeInfo::TAGGED_INT; + case TypeInfo::UINT32: + return TaggedTypeInfo::TAGGED_NUMBER; + case TypeInfo::FLOAT64: + return TaggedTypeInfo::TAGGED_DOUBLE; + case TypeInfo::CHAR: + return TaggedTypeInfo::TAGGED_STRING; + case TypeInfo::HOLE_INT: + return TaggedTypeInfo::TAGGED; + case TypeInfo::HOLE_DOUBLE: + return TaggedTypeInfo::TAGGED; + default: + return TaggedTypeInfo::NONE; + } +} + GateRef NumberSpeculativeRetype::ConvertToTagged(GateRef gate) { TypeInfo output = GetOutputTypeInfo(gate); + TaggedTypeInfo info = GetTaggedTypeOfTaggedConvertion(output); + GateRef cGate = convertCache_.GetCachedGate(gate, info); + if (cGate != Circuit::NullGate()) { + return cGate; + } switch (output) { case TypeInfo::INT1: - return builder_.ConvertBoolToTaggedBoolean(gate); + cGate = builder_.ConvertBoolToTaggedBoolean(gate); + convertCache_.SetCachedGate(gate, info, cGate); + return cGate; case TypeInfo::INT32: - return builder_.ConvertInt32ToTaggedInt(gate); + cGate = builder_.ConvertInt32ToTaggedInt(gate); + convertCache_.SetCachedGate(gate, info, cGate); + return cGate; case TypeInfo::UINT32: - return builder_.ConvertUInt32ToTaggedNumber(gate); + cGate = builder_.ConvertUInt32ToTaggedNumber(gate); + convertCache_.SetCachedGate(gate, info, cGate); + return cGate; case TypeInfo::FLOAT64: - return builder_.ConvertFloat64ToTaggedDouble(gate); + cGate = builder_.ConvertFloat64ToTaggedDouble(gate); + convertCache_.SetCachedGate(gate, info, cGate); + return cGate; case TypeInfo::CHAR: - return builder_.ConvertCharToEcmaString(gate); + cGate = builder_.ConvertCharToEcmaString(gate); + convertCache_.SetCachedGate(gate, info, cGate); + return cGate; case TypeInfo::HOLE_INT: - return builder_.ConvertSpecialHoleIntToTagged(gate); + cGate = builder_.ConvertSpecialHoleIntToTagged(gate); + convertCache_.SetCachedGate(gate, info, cGate); + return cGate; case TypeInfo::HOLE_DOUBLE: - return builder_.ConvertSpecialHoleDoubleToTagged(gate); + cGate = builder_.ConvertSpecialHoleDoubleToTagged(gate); + convertCache_.SetCachedGate(gate, info, cGate); + return cGate; case TypeInfo::NONE: case TypeInfo::TAGGED: { return gate; diff --git a/ecmascript/compiler/number_speculative_retype.h b/ecmascript/compiler/number_speculative_retype.h index 9a8293d6a31b99acc9c72b33984b8be9deaa71d2..ea839378249da3d4d368306e8cc3628b4ef3a03d 100644 --- a/ecmascript/compiler/number_speculative_retype.h +++ b/ecmascript/compiler/number_speculative_retype.h @@ -33,12 +33,70 @@ public: }; NumberSpeculativeRetype(Circuit* circuit, Chunk* chunk, ChunkVector& typeInfos) : circuit_(circuit), acc_(circuit), builder_(circuit), - typeInfos_(typeInfos), chunk_(chunk), glue_(acc_.GetGlueFromArgList()) {} + typeInfos_(typeInfos), convertCache_(chunk), chunk_(chunk), glue_(acc_.GetGlueFromArgList()) {} GateRef VisitGate(GateRef gate); void setState(NumberSpeculativeRetype::State state); void ConvertMonoAccessorValueIn(GateRef gate); private: + enum class TaggedTypeInfo { + INT32, + FLOAT64, + BOOL, + TAGGED, + TAGGED_INT, + TAGGED_BOOL, + TAGGED_NUMBER, + TAGGED_DOUBLE, + TAGGED_STRING, + NONE, + }; + + class TypeCache { + public: + TypeCache(Chunk *chunk): cache_(chunk) {} + + GateRef GetCachedGate(TaggedTypeInfo info) + { + if (cache_.count(info)) { + return cache_[info]; + } + return Circuit::NullGate(); + } + + void SetCachedGate(TaggedTypeInfo info, GateRef gate) + { + cache_[info] = gate; + } + + private: + ChunkUnorderedMap cache_; + }; + + class ConvertCache { + public: + ConvertCache(Chunk *chunk): map_(chunk), chunk_(chunk) {} + GateRef GetCachedGate(GateRef gate, TaggedTypeInfo info) + { + if (map_.find(gate) != map_.end()) { + auto &cache = map_.at(gate); + return cache.GetCachedGate(info); + } + return Circuit::NullGate(); + } + + void SetCachedGate(GateRef gate, TaggedTypeInfo info, GateRef convert) + { + if (map_.find(gate) == map_.end()) { + map_.emplace(gate, chunk_); + } + map_.at(gate).SetCachedGate(info, convert); + } + + private: + ChunkUnorderedMap map_; + Chunk *chunk_; + }; enum class OpType { NORMAL, @@ -157,8 +215,12 @@ private: GateRef CheckBoundAndConvertToInt32(GateRef gate, ConvertSupport support = ConvertSupport::ENABLE, OpType type = OpType::NORMAL); + TaggedTypeInfo GetTaggedTypeOfTaggedConvertion(TypeInfo output, + GateType gateType = GateType::Empty(), + ConvertToNumber convert = ConvertToNumber::DISABLE); GateRef CheckAndConvertToFloat64(GateRef gate, GateType gateType, ConvertToNumber convert = ConvertToNumber::BOOL_ONLY); + GateRef CheckAndConvertFromBoolToTagged(GateRef gate, GateType gateType, ConvertToNumber convert); GateRef CheckAndConvertToTagged(GateRef gate, GateType gateType, ConvertToNumber convert); GateRef TryConvertConstantToBool(GateRef gate); GateRef CheckAndConvertToBool(GateRef gate, GateType gateType); @@ -198,6 +260,7 @@ private: GateAccessor acc_; CircuitBuilder builder_; ChunkVector& typeInfos_; + ConvertCache convertCache_; State state_ {0}; [[maybe_unused]] Chunk *chunk_ {nullptr}; GateRef glue_ {Circuit::NullGate()}; diff --git a/ecmascript/compiler/pass.h b/ecmascript/compiler/pass.h index e36c9c3ded1454581182c3f23ad9abe7944018c1..e11901ebc03931dcbc4a89b37722a7c31f59cff5 100644 --- a/ecmascript/compiler/pass.h +++ b/ecmascript/compiler/pass.h @@ -17,6 +17,7 @@ #define ECMASCRIPT_COMPILER_PASS_H #include "ecmascript/compiler/aot_compilation_env.h" +#include "ecmascript/compiler/controlflow_optimization.h" #include "ecmascript/compiler/jit_compilation_env.h" #include "ecmascript/compiler/async_function_lowering.h" #include "ecmascript/compiler/bytecode_circuit_builder.h" @@ -709,6 +710,23 @@ public: } }; +class ControlFlowOptimizationPass { +public: + bool Run(PassData *data) + { + TimeScope timescope("ControlFlowOptimizationPass", data->GetMethodName(), + data->GetMethodOffset(), data->GetLog()); + bool enableLog = data->GetLog()->EnableMethodCIRLog(); + if (data->GetPassOptions()->EnableControlFlowOptimization()) { + Chunk chunk(data->GetNativeAreaAllocator()); + ControlFlowOptimization controlFlowOptimization(data->GetCircuit(), &chunk, + enableLog, data->GetMethodName()); + controlFlowOptimization.Run(); + } + return true; + } +}; + class ValueNumberingPass { public: bool Run(PassData* data) diff --git a/ecmascript/compiler/pass_manager.cpp b/ecmascript/compiler/pass_manager.cpp index 280589e15232183f6ed9c8e02f5e20216b0bc199..0d857de7360d45c31ae0bd05a2edc3a552437193 100644 --- a/ecmascript/compiler/pass_manager.cpp +++ b/ecmascript/compiler/pass_manager.cpp @@ -162,6 +162,7 @@ bool JitPassManager::Compile(JSHandle &profileTypeInfo, pipeline.RunPass(); pipeline.RunPass(); pipeline.RunPass(); + pipeline.RunPass(); if (!compilationEnv_->GetJSOptions().IsEnableJitFastCompile()) { pipeline.RunPass(); } @@ -173,8 +174,10 @@ bool JitPassManager::Compile(JSHandle &profileTypeInfo, pipeline.RunPass(); pipeline.RunPass(); pipeline.RunPass(); + pipeline.RunPass(); pipeline.RunPass(); pipeline.RunPass(); + pipeline.RunPass(); pipeline.RunPass(); if (!compilationEnv_->GetJSOptions().IsEnableJitFastCompile()) { pipeline.RunPass(); @@ -186,6 +189,7 @@ bool JitPassManager::Compile(JSHandle &profileTypeInfo, pipeline.RunPass(); pipeline.RunPass(); pipeline.RunPass(); + pipeline.RunPass(); if (!compilationEnv_->GetJSOptions().IsEnableJitFastCompile() && compilationEnv_->GetJSOptions().IsEnableJitVerifyPass()) { pipeline.RunPass(); @@ -333,6 +337,7 @@ bool PassManager::Compile(JSPandaFile *jsPandaFile, const std::string &fileName, pipeline.RunPass(); pipeline.RunPass(); pipeline.RunPass(); + pipeline.RunPass(); pipeline.RunPass(); pipeline.RunPass(); pipeline.RunPass(); @@ -342,8 +347,10 @@ bool PassManager::Compile(JSPandaFile *jsPandaFile, const std::string &fileName, pipeline.RunPass(); pipeline.RunPass(); pipeline.RunPass(); + pipeline.RunPass(); pipeline.RunPass(); pipeline.RunPass(); + pipeline.RunPass(); pipeline.RunPass(); pipeline.RunPass(); pipeline.RunPass(); @@ -351,6 +358,7 @@ bool PassManager::Compile(JSPandaFile *jsPandaFile, const std::string &fileName, pipeline.RunPass(); pipeline.RunPass(); pipeline.RunPass(); + pipeline.RunPass(); if (passOptions_->EnableVerifierPass()) { pipeline.RunPass(); } diff --git a/ecmascript/compiler/pass_options.h b/ecmascript/compiler/pass_options.h index f3e058a9381b65973277cb5cb210e2e411bee137..0b2ad4e2c78034fb933b677214b7ad7d967e9a58 100644 --- a/ecmascript/compiler/pass_options.h +++ b/ecmascript/compiler/pass_options.h @@ -22,6 +22,7 @@ namespace panda::ecmascript::kungfu { V(TypeLowering, true) \ V(EarlyElimination, true) \ V(LaterElimination, true) \ + V(ControlFlowOptimization, true) \ V(ValueNumbering, false) \ V(OptInlining, false) \ V(OptNoGCCall, false) \ diff --git a/ecmascript/js_runtime_options.cpp b/ecmascript/js_runtime_options.cpp index 5a91a574e44e9a1efef86943ac81f4f8e232ca7e..3ae26650e3deb74cd61f2814eca2c098df99428a 100644 --- a/ecmascript/js_runtime_options.cpp +++ b/ecmascript/js_runtime_options.cpp @@ -102,6 +102,7 @@ const std::string PUBLIC_API HELP_OPTION_MSG = "--compiler-opt-inlining: Enable inlining function for aot compiler: Default: 'true'\n" "--compiler-opt-pgotype: Enable pgo type for aot compiler: Default: 'true'\n" "--compiler-opt-track-field: Enable track field for aot compiler: Default: 'false'\n" + "--compiler-opt-controlflow-optimization: Enable controlflow optimization for aot compiler: Default: 'True'\n" "--entry-point: Full name of entrypoint function. Default: '_GLOBAL::func_main_0'\n" "--force-full-gc: If true trigger full gc, else trigger semi and old gc. Default: 'true'\n" "--framework-abc-file: Snapshot file. Default: 'strip.native.min.abc'\n" @@ -323,6 +324,7 @@ bool JSRuntimeOptions::ParseCommand(const int argc, const char **argv) {"compiler-fast-compile", required_argument, nullptr, OPTION_FAST_AOT_COMPILE_MODE}, {"compiler-opt-loop-peeling", required_argument, nullptr, OPTION_COMPILER_OPT_LOOP_PEELING}, {"compiler-opt-array-onheap-check", required_argument, nullptr, OPTION_COMPILER_OPT_ON_HEAP_CHECK}, + {"compiler-opt-controlflow-opt", required_argument, nullptr, OPTION_COMPILER_OPT_CONTROLFLOW_OPT}, {"compiler-pkg-info", required_argument, nullptr, OPTION_COMPILER_PKG_INFO}, {"compiler-external-pkg-info", required_argument, nullptr, OPTION_COMPILER_EXTERNAL_PKG_INFO}, {"compiler-enable-external-pkg", required_argument, nullptr, OPTION_COMPILER_ENABLE_EXTERNAL_PKG}, @@ -908,6 +910,14 @@ bool JSRuntimeOptions::ParseCommand(const int argc, const char **argv) return false; } break; + case OPTION_COMPILER_OPT_CONTROLFLOW_OPT: + ret = ParseBoolParam(&argBool); + if (ret) { + SetControlFlowOptimization(argBool); + } else { + return false; + } + break; case OPTION_COMPILER_OPT_ARRAY_BOUNDS_CHECK_ELIMINATION: ret = ParseBoolParam(&argBool); if (ret) { diff --git a/ecmascript/js_runtime_options.h b/ecmascript/js_runtime_options.h index f0775e920e2f69fdf5d43a501fec0346c6d575e1..020d9e5e12631fd2d7e0ca57304c1bbb4f7616d4 100644 --- a/ecmascript/js_runtime_options.h +++ b/ecmascript/js_runtime_options.h @@ -242,6 +242,7 @@ enum CommandValues { OPTION_COMPILER_ENABLE_JIT_LAZY_DEOPT, OPTION_COMPILER_ENABLE_LAZY_DEOPT_TRACE, OPTION_ENABLE_LOADING_STUBS_LOG, + OPTION_COMPILER_OPT_CONTROLFLOW_OPT, OPTION_COMPILER_JIT_METHOD_DICHOTOMY, OPTION_COMPILER_JIT_METHOD_PATH, OPTION_COMPILER_ENABLE_MERGE_POLY, @@ -2278,6 +2279,16 @@ public: static bool ParseUint64(const std::string &arg, uint64_t* argUInt64); static bool ParseDouble(const std::string &arg, double* argDouble); + + bool IsEnableControlFlowOptimization() const + { + return enableControlFlowOptimization_; + } + + void SetControlFlowOptimization(bool value) + { + enableControlFlowOptimization_ = value; + } public: static constexpr int32_t MAX_APP_COMPILE_METHOD_SIZE = 4_KB; @@ -2474,6 +2485,7 @@ private: bool enableTypeLowering_ {true}; bool enableEarlyElimination_ {true}; bool enableLaterElimination_ {true}; + bool enableControlFlowOptimization_ {true}; bool enableValueNumbering_ {true}; bool enableOptString_ {true}; bool enableMutantArray_ {false}; diff --git a/test/aottest/BUILD.gn b/test/aottest/BUILD.gn index 08f8ccbd1e9dde8e12c0ebba24ab886958e4ce34..155e21ab12187090bfe28473e8f62e028d888482 100644 --- a/test/aottest/BUILD.gn +++ b/test/aottest/BUILD.gn @@ -40,6 +40,7 @@ group("ark_aot_js_test") { "fallback_type_conversion", "pgo_stobjbyvalue", "typedarray_to_reversed", + "controlflow_opt" ] deps = [] diff --git a/test/aottest/controlflow_opt/BUILD.gn b/test/aottest/controlflow_opt/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..bab424283410f0414feddfd869467ae560007eea --- /dev/null +++ b/test/aottest/controlflow_opt/BUILD.gn @@ -0,0 +1,19 @@ +# 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. + +import("//arkcompiler/ets_runtime/test/test_helper.gni") + +host_aot_js_test_action("controlflow_opt") { + deps = [] + is_enable_pgo = true +} diff --git a/test/aottest/controlflow_opt/controlflow_opt.js b/test/aottest/controlflow_opt/controlflow_opt.js new file mode 100644 index 0000000000000000000000000000000000000000..9620d66ae8296fcef42ac40f442896747cd4f96c --- /dev/null +++ b/test/aottest/controlflow_opt/controlflow_opt.js @@ -0,0 +1,82 @@ +/* + * 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. + */ + +function neg(a, b) { + // ValueCheckNegOverflow + let len = a - 3 + for (let i = 0; i < len; ++ i) { + a += i + let c = -a + } + return b +} + +function shr(a, b) { + // Int32UnsignedUpperBoundCheck + let len = a - 3 + for (let i = 0; i < len; ++ i) { + a += i + let c = a >>> len + } + return b +} + +function mod(a, b) { + // Int32CheckRightIsZero & RemainderIsNegativeZero + let len = a - 3 + for (let i = 0; i < len; ++ i) { + a += i + let c = a % len + } + return b +} + +function div(a, b) { + // Int32DivWithCheck + let len = a - 3 + for (let i = 1; i <= len; ++ i) { + let c = a / i + c += i + } + return b +} + +function test_switch(a) { + let b = 0 + switch (a) { + case 4: + b = a - 1 + break + case 5: + b = a - 2 + break + case 6: // dead code + a -= 3 + break + default: // dead code + a -= 4 + break + } + return b +} + +print(neg(5, -4)) +print(shr(5, 2)) +print(mod(5, 3)) +print(div(6, 4)) +print(test_switch(4)) +print(test_switch(5)) +print(test_switch(6)) +print(test_switch(7)) diff --git a/test/aottest/controlflow_opt/expect_output.txt b/test/aottest/controlflow_opt/expect_output.txt new file mode 100644 index 0000000000000000000000000000000000000000..56b8520930b1c29a6a019cedfeb4fa352ac786b6 --- /dev/null +++ b/test/aottest/controlflow_opt/expect_output.txt @@ -0,0 +1,21 @@ +# 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. + +-4 +2 +3 +4 +3 +3 +0 +0 \ No newline at end of file diff --git a/test/aottest/controlflow_opt/pgo_expect_output.txt b/test/aottest/controlflow_opt/pgo_expect_output.txt new file mode 100644 index 0000000000000000000000000000000000000000..56b8520930b1c29a6a019cedfeb4fa352ac786b6 --- /dev/null +++ b/test/aottest/controlflow_opt/pgo_expect_output.txt @@ -0,0 +1,21 @@ +# 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. + +-4 +2 +3 +4 +3 +3 +0 +0 \ No newline at end of file