diff --git a/static_core/libllvmbackend/BUILD.gn b/static_core/libllvmbackend/BUILD.gn index 395a68e9632c18dff2b1e563e5edec22067a1590..1e1298c64fa6572e063295acb9f1321737c4e375 100644 --- a/static_core/libllvmbackend/BUILD.gn +++ b/static_core/libllvmbackend/BUILD.gn @@ -85,6 +85,7 @@ if (is_llvmbackend) { "transforms/passes/gep_propagation.cpp", "transforms/passes/infer_flags.cpp", "transforms/passes/inline_devirt.cpp", + "transforms/passes/heap_alloc_sink.cpp", "transforms/passes/inline_ir/cleanup_inline_module.cpp", "transforms/passes/inline_ir/discard_inline_module.cpp", "transforms/passes/inline_ir/inline_ir_utils.cpp", diff --git a/static_core/libllvmbackend/CMakeLists.txt b/static_core/libllvmbackend/CMakeLists.txt index 30c6db24d929abfb1a5e3aeae8be16aa57bc4e45..f96f28f3f45df478cebb4a1e81c58ae31c35899f 100644 --- a/static_core/libllvmbackend/CMakeLists.txt +++ b/static_core/libllvmbackend/CMakeLists.txt @@ -74,6 +74,7 @@ set(SOURCES transforms/passes/panda_runtime_lowering.cpp transforms/passes/propagate_lenarray.cpp transforms/passes/prune_deopt.cpp + transforms/passes/heap_alloc_sink.cpp transforms/runtime_calls.cpp utils.cpp ) diff --git a/static_core/libllvmbackend/transforms/llvm_optimizer.cpp b/static_core/libllvmbackend/transforms/llvm_optimizer.cpp index 4bfdcc3f47f16148a12c253a918e4ca328b5feeb..9ef72921921d895d8dd7cfac28bb3c769c0dc20c 100644 --- a/static_core/libllvmbackend/transforms/llvm_optimizer.cpp +++ b/static_core/libllvmbackend/transforms/llvm_optimizer.cpp @@ -35,6 +35,7 @@ #include "passes/loop_peeling.h" #include "passes/propagate_lenarray.h" #include "passes/check_external.h" +#include "passes/heap_alloc_sink.h" #include "passes/inline_ir/cleanup_inline_module.h" #include "passes/inline_ir/discard_inline_module.h" diff --git a/static_core/libllvmbackend/transforms/passes/heap_alloc_sink.cpp b/static_core/libllvmbackend/transforms/passes/heap_alloc_sink.cpp new file mode 100644 index 0000000000000000000000000000000000000000..091ea5e04e346451ed27470133262a06609970e1 --- /dev/null +++ b/static_core/libllvmbackend/transforms/passes/heap_alloc_sink.cpp @@ -0,0 +1,631 @@ +/* + * Copyright (c) 2023-2024 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "heap_alloc_sink.h" +#include "prune_deopt.h" +#include "transforms/builtins.h" + +using llvm::AllocFnKind; +using llvm::Attribute; +using llvm::AttributeList; +using llvm::BasicBlock; +using llvm::CallBase; +using llvm::CallInst; +using llvm::DominatorTree; +using llvm::dyn_cast; +using llvm::Function; +using llvm::Instruction; +using llvm::isa; +using llvm::LLVMContext; +using llvm::Loop; +using llvm::LoopInfo; +using llvm::OperandBundleDef; +using llvm::OperandBundleUse; +using llvm::PHINode; +using llvm::Use; +using llvm::Value; + +namespace ark::llvmbackend::passes { +HeapAllocSink::HeapAllocSink(LLVMArkInterface *arkInterface) : arkInterface_ {arkInterface} {} + +HeapAllocSink HeapAllocSink::Create(LLVMArkInterface *arkInterface, + [[maybe_unused]] const ark::llvmbackend::LLVMCompilerOptions *options) +{ + return HeapAllocSink(arkInterface); +} + +void HeapAllocSink::MoveAllocInsideBlock(CallInst *alloc, BasicBlock *blockForOpt) +{ + auto insertionPt = blockForOpt->getFirstInsertionPt(); + alloc->moveBefore(*blockForOpt, insertionPt); +} + +bool HeapAllocSink::LegalToChangeDeoptBundle(CallInst *callInst) +{ + constexpr std::array BUILTINS = { + ark::llvmbackend::builtins::BUILTIN_SECTION, ark::llvmbackend::builtins::LEN_ARRAY_BUILTIN, + ark::llvmbackend::builtins::KEEP_THIS_BUILTIN, ark::llvmbackend::builtins::LOAD_CLASS_BUILTIN, + ark::llvmbackend::builtins::LOAD_INIT_CLASS_BUILTIN, ark::llvmbackend::builtins::PRE_WRB_BUILTIN, + ark::llvmbackend::builtins::PRE_WRB_GCADR_BUILTIN, ark::llvmbackend::builtins::POST_WRB_BUILTIN, + ark::llvmbackend::builtins::POST_WRB_GCADR_BUILTIN, ark::llvmbackend::builtins::LOAD_STRING_BUILTIN, + ark::llvmbackend::builtins::RESOLVE_VIRTUAL_BUILTIN, ark::llvmbackend::builtins::BARRIER_RETURN_VOID_BUILTIN, + }; + Function *calledFunc = callInst->getCalledFunction(); + if (calledFunc != nullptr) { + const auto &funcName = calledFunc->getName(); + for (const auto &builtin : BUILTINS) { + if (funcName.equals(builtin)) { + return true; + } + } + } + auto bundle = callInst->getOperandBundle(llvm::LLVMContext::OB_deopt); + if (bundle != llvm::None) { + bool pruneDeoptWillDeleteDeoptBundle = + !PruneDeopt::IsCaughtDeoptimization(bundle->Inputs) && !callInst->hasFnAttr("may-deoptimize"); + if (!pruneDeoptWillDeleteDeoptBundle) { + return false; + } + } + + return true; +} + +bool HeapAllocSink::IsLegalToMove(CallInst *alloc, BasicBlock *basicBlock, DominatorTree &dominatorTree) +{ + std::vector blocksToCheck; + for (const auto &user : alloc->users()) { + auto userInst = dyn_cast(user); + if (userInst != nullptr) { + if (isa(userInst)) { + auto phiNode = dyn_cast(userInst); + std::vector phiBlocksAccessAlloc = FindBasicBlocksAccessingAllocInPHINode(phiNode, alloc); + blocksToCheck.insert(blocksToCheck.end(), phiBlocksAccessAlloc.begin(), phiBlocksAccessAlloc.end()); + } else { + blocksToCheck.emplace_back(userInst->getParent()); + } + } + } + + if (basicBlock != alloc->getParent()) { + if (dominatorTree.dominates(basicBlock, alloc->getParent())) { + return false; + } + } + + for (const auto &basicBlockToCheck : blocksToCheck) { + if (!dominatorTree.dominates(basicBlock, basicBlockToCheck)) { + return false; + } + } + + for (const auto &operand : alloc->operands()) { + auto operandInst = dyn_cast(operand); + if (operandInst != nullptr) { + if (!dominatorTree.dominates(operandInst->getParent(), basicBlock)) { + return false; + } + } + } + + return true; +} + +std::vector HeapAllocSink::GetCallsWithHeapKind(Function &func, const AllocFnKind &allocFnKind) +{ + std::vector allocs; + for (BasicBlock &block : func) { + for (Instruction &inst : block) { + auto *callInst = dyn_cast(&inst); + if (callInst == nullptr) { + continue; + } + if (IsAllocKind(callInst, allocFnKind)) { + allocs.push_back(callInst); + } + } + } + + return allocs; +} + +bool HeapAllocSink::AttributesContainAllocKind(bool hasAllocKind, const AttributeList &attributes, + const AllocFnKind &allocFnKind) +{ + bool correctKind = false; + if (hasAllocKind) { + Attribute allocAttribute = attributes.getFnAttr(Attribute::AllocKind); + AllocFnKind kind = allocAttribute.getAllocKind(); + correctKind = ((static_cast(kind) & static_cast(allocFnKind)) != 0U); + } + + return correctKind; +} + +bool HeapAllocSink::IsAllocKind(const CallInst *callInst, const AllocFnKind &allocFnKind) +{ + Function *calledFunc = callInst->getCalledFunction(); + AttributeList funcAttributes = AttributeList(); + bool funcHasAllocKind = false; + if (calledFunc != nullptr) { + funcAttributes = calledFunc->getAttributes(); + funcHasAllocKind = funcAttributes.hasFnAttr(Attribute::AllocKind); + } + + AttributeList callInstAttributes = callInst->getAttributes(); + bool callInsthasAllocKind = callInstAttributes.hasFnAttr(Attribute::AllocKind); + + bool callInstIsAllocKind = AttributesContainAllocKind(callInsthasAllocKind, callInstAttributes, allocFnKind); + bool calledFuncIsAllocKind = AttributesContainAllocKind(funcHasAllocKind, funcAttributes, allocFnKind); + + return callInstIsAllocKind || calledFuncIsAllocKind; +} + +bool HeapAllocSink::BlockAccessAllocMemory(BasicBlock *block, CallInst *alloc) +{ + if (block == nullptr || alloc == nullptr) { + return false; + } + + for (const Instruction &inst : *block) { + for (size_t i = 0; i < inst.getNumOperands(); ++i) { + const Value *operandValue = inst.getOperand(i); + + if (operandValue == alloc) { + return true; + } + } + } + + return false; +} + +BasicBlock *HeapAllocSink::GetBasicBlockForOptForAlloc( + CallInst *alloc, LoopInfo &loopInfo, DominatorTree &dominatorTree, + const std::set &basicBlocksNotInLoop, + const std::set> &basicBlocksAndTheirLoopHeaders) +{ + BasicBlock *needOptBlock; + Loop *loopOfAlloc = loopInfo.getLoopFor(alloc->getParent()); + std::set availableBlocksForOpt = basicBlocksNotInLoop; + if (loopOfAlloc != nullptr) { + UpdateAvailableBlocksForOpt(loopOfAlloc, availableBlocksForOpt, basicBlocksAndTheirLoopHeaders); + } + needOptBlock = NeedOptimization(availableBlocksForOpt, alloc, dominatorTree); + + return needOptBlock; +} + +std::set> HeapAllocSink::GetAllocsAndBasicBlocksForOpt( + Function &func, const std::vector &allocs, LoopInfo &loopInfo, DominatorTree &dominatorTree) +{ + std::set basicBlocksNotInLoop; + std::set> basicBlocksAndTheirLoopHeaders; + + ClassifyBasicBlocks(func, basicBlocksNotInLoop, basicBlocksAndTheirLoopHeaders, loopInfo); + + std::set> canBeOptimizedSet; + + for (auto &alloc : allocs) { + BasicBlock *needOptBlock = GetBasicBlockForOptForAlloc(alloc, loopInfo, dominatorTree, basicBlocksNotInLoop, + basicBlocksAndTheirLoopHeaders); + if (needOptBlock != nullptr) { + auto pair = std::make_pair(alloc, needOptBlock); + canBeOptimizedSet.insert(pair); + } + } + + return canBeOptimizedSet; +} + +void HeapAllocSink::ClassifyBasicBlocks(Function &func, std::set &basicBlocksNotInLoop, + std::set> &basicBlocksAndTheirLoopHeaders, + LoopInfo &loopInfo) +{ + for (auto &basicBlock : func) { + auto *loop = loopInfo.getLoopFor(&basicBlock); + if (loop == nullptr) { + basicBlocksNotInLoop.insert(&basicBlock); + } else { + BasicBlock *headerOfLoop = loop->getHeader(); + basicBlocksAndTheirLoopHeaders.insert(std::make_pair(&basicBlock, headerOfLoop)); + } + } +} + +void HeapAllocSink::UpdateAvailableBlocksForOpt( + const Loop *loopOfAlloc, std::set &availableBlocksForOpt, + const std::set> &basicBlocksAndTheirLoopHeaders) +{ + BasicBlock *headerOfInitialLoop = loopOfAlloc->getHeader(); + for (const auto &basicBlockAndLoopHeader : basicBlocksAndTheirLoopHeaders) { + if (headerOfInitialLoop == basicBlockAndLoopHeader.second) { + availableBlocksForOpt.insert(basicBlockAndLoopHeader.first); + } + } +} + +BasicBlock *HeapAllocSink::NeedOptimization(const std::set &availableBlocksForOpt, CallInst *alloc, + DominatorTree &dominatorTree) +{ + BasicBlock *blockForOptimization = nullptr; + auto allocBasicBlock = alloc->getParent(); + + BasicBlock *potentialBlockForOpt = FindPotentialBlockForOpt(alloc, dominatorTree); + + bool dominatedByBlockForOpt = dominatorTree.dominates(alloc, potentialBlockForOpt); + + bool blockInAvailableBlocksForOpt = availableBlocksForOpt.find(potentialBlockForOpt) != availableBlocksForOpt.end(); + if ((potentialBlockForOpt != nullptr) && potentialBlockForOpt != allocBasicBlock && blockInAvailableBlocksForOpt && + dominatedByBlockForOpt) { + blockForOptimization = potentialBlockForOpt; + } + + return blockForOptimization; +} + +bool HeapAllocSink::TryToOptimize(const std::set> &canBeOptimizedSet, + DominatorTree &dominatorTree) +{ + bool changed = false; + for (const auto &[alloc, blockForOpt] : canBeOptimizedSet) { + if (IsLegalToMove(alloc, blockForOpt, dominatorTree)) { + MoveAllocInsideBlock(alloc, blockForOpt); + changed = true; + } + } + + return changed; +} + +BasicBlock *HeapAllocSink::FindPotentialBlockForOpt(CallInst *alloc, const DominatorTree &dominatorTree) +{ + BasicBlock *dominatingBasicBlock = nullptr; + for (const auto &user : alloc->users()) { + auto *currentUserInst = llvm::cast(user); + BasicBlock *currentUserBlock; + + if (isa(currentUserInst)) { + auto phiNode = dyn_cast(currentUserInst); + currentUserBlock = GetBasicBlockForOptForPHINode(phiNode, alloc, dominatorTree); + } else { + currentUserBlock = currentUserInst->getParent(); + } + + dominatingBasicBlock = LowestCommonAncestor(currentUserBlock, dominatingBasicBlock, dominatorTree); + } + + return dominatingBasicBlock; +} + +std::vector HeapAllocSink::FindBasicBlocksAccessingAllocInPHINode(PHINode *phiNode, CallInst *alloc) +{ + std::vector basicBlocksAllocAccess; + unsigned int numOfValues = phiNode->getNumIncomingValues(); + for (size_t i = 0; i < numOfValues; ++i) { + if (phiNode->getIncomingValue(i) == alloc) { + basicBlocksAllocAccess.emplace_back(phiNode->getIncomingBlock(i)); + } + } + + return basicBlocksAllocAccess; +} + +bool HeapAllocSink::DeoptBundleContainsValue(const OperandBundleDef &deoptBundle, const Value *allocValue) +{ + for (const Value *value : deoptBundle.inputs()) { + if (value == allocValue) { + return true; + } + } + + return false; +} + +BasicBlock *HeapAllocSink::GetBasicBlockForOptForPHINode(PHINode *phiNode, CallInst *alloc, + const DominatorTree &dominatorTree) +{ + BasicBlock *lowestCommonAncestor = nullptr; + std::vector basicBlocksAllocAccess = FindBasicBlocksAccessingAllocInPHINode(phiNode, alloc); + for (const auto &basicBlock : basicBlocksAllocAccess) { + lowestCommonAncestor = LowestCommonAncestor(basicBlock, lowestCommonAncestor, dominatorTree); + } + + return lowestCommonAncestor; +} + +BasicBlock *HeapAllocSink::LowestCommonAncestor(BasicBlock *newBasicBlock, BasicBlock *lowestCommonAncestor, + const DominatorTree &dominatorTree) +{ + if (lowestCommonAncestor == nullptr) { + lowestCommonAncestor = newBasicBlock; + } + BasicBlock *dominatingBasicBlock = dominatorTree.findNearestCommonDominator(newBasicBlock, lowestCommonAncestor); + + return dominatingBasicBlock; +} + +std::map> HeapAllocSink::GetCallsWithAllocInDeoptBundle( + const std::vector &allocs) +{ + std::map> bundlesWithAllocsMap; + + for (size_t i = 0; i < allocs.size(); ++i) { + auto alloc = allocs[i]; + for (const auto &user : alloc->users()) { + if (!isa(user)) { + continue; + } + auto currentUserCallInst = dyn_cast(user); + auto deoptBundle = currentUserCallInst->getOperandBundle(LLVMContext::OB_deopt); + if (!deoptBundle || !LegalToChangeDeoptBundle(currentUserCallInst)) { + continue; + } + if (!DeoptBundleContainsValue(GetOperandBundleDefFromUse(deoptBundle.value()), alloc)) { + continue; + } + + if (bundlesWithAllocsMap.find(currentUserCallInst) != bundlesWithAllocsMap.end()) { + bundlesWithAllocsMap[currentUserCallInst].emplace_back(i); + } else { + bundlesWithAllocsMap[currentUserCallInst] = {i}; + } + } + } + + return bundlesWithAllocsMap; +} + +void HeapAllocSink::RemoveAllocFromDeoptBundleForAllCalls( + std::map> &deoptBundlesToAllocMap, std::vector &allocs, + std::vector &oldDeoptBundles) +{ + for (auto &deoptBundleToAllocMap : deoptBundlesToAllocMap) { + std::set allocValues = GetAllocValuesFromIdxs(allocs, deoptBundleToAllocMap.second); + RemoveHeapAllocFromDeoptBundle(deoptBundleToAllocMap.first, allocValues, allocs, oldDeoptBundles); + } +} + +std::set HeapAllocSink::GetAllocValuesFromIdxs(const std::vector &allocs, + const std::vector &allocIdxs) +{ + std::set allocValues; + for (const auto &idx : allocIdxs) { + allocValues.insert((allocs[idx])); + } + + return allocValues; +} + +OperandBundleDef HeapAllocSink::GetNewDeoptBundleWithoutAllocs(const std::vector &deoptBundle, + const std::set &allocValues) +{ + std::vector bundleValuesWithoutAlloc; + + // NOTE: In deopt, bundle allocation is always at the VREG_VALUE index, + // there are three other indexes associated with this index in deopt bundle, + // VREG_IDX, VREG_TYPE, VREG_VALUE + // they need to be removed + constexpr size_t VREG_BUNDLE_SIZE = 3; + for (int i = deoptBundle.size() - 1; i >= 0; i--) { + Value *value = deoptBundle[i]; + if (allocValues.find(value) != allocValues.end()) { + i -= VREG_BUNDLE_SIZE - 1; + } else { + bundleValuesWithoutAlloc.push_back(value); + } + } + + std::reverse(bundleValuesWithoutAlloc.begin(), bundleValuesWithoutAlloc.end()); + OperandBundleDef newDeoptBundle("deopt", bundleValuesWithoutAlloc); + + return newDeoptBundle; +} + +CallBase *HeapAllocSink::ChangeDeoptBundle(CallInst *callInstWithDeoptBundle, const OperandBundleDef &newDeoptBundle) +{ + CallBase *tmpCall = + CallBase::removeOperandBundle(callInstWithDeoptBundle, LLVMContext::OB_deopt, callInstWithDeoptBundle); + + CallBase *newCall = + CallBase::addOperandBundle(tmpCall, LLVMContext::OB_deopt, newDeoptBundle, callInstWithDeoptBundle); + + newCall->copyMetadata(*callInstWithDeoptBundle); + + tmpCall->eraseFromParent(); + + return newCall; +} + +OperandBundleDef HeapAllocSink::GetOperandBundleDefFromUse(const OperandBundleUse &operandBundleUse) +{ + std::vector bundleValuesWithoutAlloc = GetDeoptVec(operandBundleUse); + for (Value *value : operandBundleUse.Inputs) { + bundleValuesWithoutAlloc.push_back(value); + } + OperandBundleDef newDeoptBundle("deopt", bundleValuesWithoutAlloc); + + return newDeoptBundle; +} + +void HeapAllocSink::UpdateVectorOfAllocs(std::vector &allAllocs, CallInst *oldCallNoLongerValid, + CallBase *newCallValid) +{ + auto oldCallInAllocsFound = std::find(allAllocs.begin(), allAllocs.end(), oldCallNoLongerValid); + if (oldCallInAllocsFound != allAllocs.end()) { + *oldCallInAllocsFound = dyn_cast(newCallValid); + } +} + +void HeapAllocSink::UpdateOldDeoptBundles(std::vector &oldDeoptBundles, + CallInst *oldCallNoLongerValid, CallBase *newCallValid) +{ + for (auto &callWithDeopt : oldDeoptBundles) { + auto newCall = dyn_cast(newCallValid); + if (callWithDeopt.callInst == oldCallNoLongerValid) { + callWithDeopt.callInst = newCall; + } + + for (auto &value : callWithDeopt.oldDeoptBundle) { + if (!isa(value)) { + continue; + } + + if (value == oldCallNoLongerValid) { + value = newCall; + } + } + } +} + +std::vector HeapAllocSink::GetDeoptVec(const OperandBundleUse &deoptBundle) +{ + std::vector bundleValues; + for (Value *value : deoptBundle.Inputs) { + bundleValues.push_back(value); + } + + return bundleValues; +} + +void HeapAllocSink::AddOldDeoptBundle(std::vector &oldDeoptBundles, + CallInst *oldInstWithDeoptBundle) +{ + bool found = false; + for (auto &callWithDeopt : oldDeoptBundles) { + if (callWithDeopt.callInst == oldInstWithDeoptBundle) { + found = true; + } + } + + if (!found) { + OperandBundleUse operandUse = oldInstWithDeoptBundle->getOperandBundle(LLVMContext::OB_deopt).value(); + std::vector deoptBundleVec = GetDeoptVec(operandUse); + + oldDeoptBundles.emplace_back(CallWithDeoptBundleWithAlocs(oldInstWithDeoptBundle, deoptBundleVec)); + } +} + +void HeapAllocSink::ReplaceOldCallWithNewOne(CallInst *oldCallNoLongerValid, CallBase *newCall) +{ + oldCallNoLongerValid->replaceAllUsesWith(newCall); + oldCallNoLongerValid->eraseFromParent(); +} + +void HeapAllocSink::RemoveHeapAllocFromDeoptBundle(CallInst *callInstWithDeoptBundle, + const std::set &allocValues, + std::vector &allAllocs, + std::vector &oldDeoptBundles) +{ + auto deoptBundleUse = callInstWithDeoptBundle->getOperandBundle(LLVMContext::OB_deopt).value(); + std::vector deoptBundle = GetDeoptVec(deoptBundleUse); + OperandBundleDef newDeoptBundle = GetNewDeoptBundleWithoutAllocs(deoptBundle, allocValues); + AddOldDeoptBundle(oldDeoptBundles, callInstWithDeoptBundle); + + auto newCall = ChangeDeoptBundle(callInstWithDeoptBundle, newDeoptBundle); + UpdateVectorOfAllocs(allAllocs, callInstWithDeoptBundle, newCall); + UpdateOldDeoptBundles(oldDeoptBundles, callInstWithDeoptBundle, newCall); + + ReplaceOldCallWithNewOne(callInstWithDeoptBundle, newCall); +} + +void HeapAllocSink::ReturnDeopts(std::vector &oldDeoptBundles, + DominatorTree &dominatorTree) +{ + for (size_t i = 0; i < oldDeoptBundles.size(); ++i) { + bool callInstAfterOrBeforeIllegal = false; + bool deoptsToBeChanged = false; + std::set illegals; + + auto el = oldDeoptBundles[i]; + bool legalToReturnOldDeopt = true; + for (const auto &deoptEl : el.oldDeoptBundle) { + if (!isa(deoptEl)) { + continue; + } + auto currentCallInst = dyn_cast(deoptEl); + bool legalToMove = dominatorTree.dominates(currentCallInst, el.callInst); + if (!legalToMove) { + illegals.insert(currentCallInst); + legalToReturnOldDeopt = false; + continue; + } + callInstAfterOrBeforeIllegal = true; + } + if (callInstAfterOrBeforeIllegal && !legalToReturnOldDeopt) { + deoptsToBeChanged = true; + } + if (!legalToReturnOldDeopt && !deoptsToBeChanged) { + continue; + } + + CallBase *newCall; + if (deoptsToBeChanged) { + OperandBundleDef nonOldNewDeoptBundle = GetNewDeoptBundleWithoutAllocs(el.oldDeoptBundle, illegals); + newCall = ChangeDeoptBundle(el.callInst, nonOldNewDeoptBundle); + } else { + OperandBundleDef oldDeoptBundle("deopt", el.oldDeoptBundle); + newCall = ChangeDeoptBundle(el.callInst, oldDeoptBundle); + } + + UpdateOldDeoptBundles(oldDeoptBundles, el.callInst, newCall); + ReplaceOldCallWithNewOne(el.callInst, newCall); + } +} + +llvm::PreservedAnalyses HeapAllocSink::run(Function &func, llvm::FunctionAnalysisManager &analysisManager) +{ + std::vector oldDeoptBundles; + + LoopInfo &loopInfo = analysisManager.getResult(func); + + bool changed = false; + + std::vector allocs = GetCallsWithHeapKind(func, AllocFnKind::Alloc); + + auto callsWithAllocInDeoptBundle = GetCallsWithAllocInDeoptBundle(allocs); + RemoveAllocFromDeoptBundleForAllCalls(callsWithAllocInDeoptBundle, allocs, oldDeoptBundles); + + DominatorTree dominatorTree = DominatorTree(func); + + auto canBeOptimizedSet = GetAllocsAndBasicBlocksForOpt(func, allocs, loopInfo, dominatorTree); + + changed += static_cast(TryToOptimize(canBeOptimizedSet, dominatorTree)); + + ReturnDeopts(oldDeoptBundles, dominatorTree); + + return changed ? llvm::PreservedAnalyses::none() : llvm::PreservedAnalyses::all(); +} +} // namespace ark::llvmbackend::passes diff --git a/static_core/libllvmbackend/transforms/passes/heap_alloc_sink.h b/static_core/libllvmbackend/transforms/passes/heap_alloc_sink.h new file mode 100644 index 0000000000000000000000000000000000000000..3802c841a47ff8931593f9e61188f12c0b1d79c1 --- /dev/null +++ b/static_core/libllvmbackend/transforms/passes/heap_alloc_sink.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2023-2024 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 LIBLLVMBACKEND_TRANSFORMS_PASSES_HEAP_ALLOC_SINK_H +#define LIBLLVMBACKEND_TRANSFORMS_PASSES_HEAP_ALLOC_SINK_H + +#include "llvm/IR/PassManager.h" +#include "llvm/Passes/PassBuilder.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "llvm_ark_interface.h" + +namespace ark::llvmbackend { +struct LLVMCompilerOptions; +} // namespace ark::llvmbackend + +namespace ark::llvmbackend::passes { +class HeapAllocSink : public llvm::PassInfoMixin { +public: + explicit HeapAllocSink(LLVMArkInterface *arkInterface = nullptr); + + // NOLINTNEXTLINE(readability-identifier-naming) + llvm::PreservedAnalyses run(llvm::Function &func, llvm::FunctionAnalysisManager &analysisManager); + + static bool ShouldInsert([[maybe_unused]] const ark::llvmbackend::LLVMCompilerOptions *options) + { + return true; + } + + static HeapAllocSink Create(LLVMArkInterface *arkInterface, const ark::llvmbackend::LLVMCompilerOptions *options); + + class CallWithDeoptBundleWithAlocs { + public: + // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) + llvm::CallInst *callInst; + // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) + std::vector oldDeoptBundle; + + CallWithDeoptBundleWithAlocs(llvm::CallInst *call, std::vector oldDeopts) + : callInst(call), oldDeoptBundle(std::move(oldDeopts)) + { + } + }; + +private: + bool IsLegalToMove(llvm::CallInst *alloc, llvm::BasicBlock *basicBlock, llvm::DominatorTree &dominatorTree); + + void MoveAllocInsideBlock(llvm::CallInst *alloc, llvm::BasicBlock *blockForOpt); + + std::vector GetCallsWithHeapKind(llvm::Function &func, const llvm::AllocFnKind &allocFnKind); + + bool IsAllocKind(const llvm::CallInst *callInst, const llvm::AllocFnKind &allocFnKind); + + bool AttributeHasAllocKind(bool hasAllocKind, const llvm::AttributeList &attributes); + + bool AttributesContainAllocKind(bool hasAllocKind, const llvm::AttributeList &attributes, + const llvm::AllocFnKind &allocFnKind); + + llvm::BasicBlock *GetBasicBlockForOptForAlloc( + llvm::CallInst *alloc, llvm::LoopInfo &loopInfo, llvm::DominatorTree &dominatorTree, + const std::set &basicBlocksNotInLoop, + const std::set> &basicBlocksAndTheirLoopHeaders); + + std::set> GetAllocsAndBasicBlocksForOpt( + llvm::Function &func, const std::vector &allocs, llvm::LoopInfo &loopInfo, + llvm::DominatorTree &dominatorTree); + + void ClassifyBasicBlocks( + llvm::Function &func, std::set &basicBlocksNotInLoop, + std::set> &basicBlocksAndTheirLoopHeaders, + llvm::LoopInfo &loopInfo); + + void UpdateAvailableBlocksForOpt( + const llvm::Loop *loopOfAlloc, std::set &availableBlocksForOpt, + const std::set> &basicBlocksAndTheirLoopHeaders); + + llvm::BasicBlock *NeedOptimization(const std::set &availableBlocksForOpt, llvm::CallInst *alloc, + llvm::DominatorTree &dominatorTree); + + bool TryToOptimize(const std::set> &canBeOptimizedSet, + llvm::DominatorTree &dominatorTree); + + llvm::BasicBlock *FindPotentialBlockForOpt(llvm::CallInst *alloc, const llvm::DominatorTree &dominatorTree); + + llvm::BasicBlock *LowestCommonAncestor(llvm::BasicBlock *newBasicBlock, llvm::BasicBlock *lowestCommonAncestor, + const llvm::DominatorTree &dominatorTree); + + llvm::BasicBlock *GetBasicBlockForOptForPHINode(llvm::PHINode *phiNode, llvm::CallInst *alloc, + const llvm::DominatorTree &dominatorTree); + + std::vector FindBasicBlocksAccessingAllocInPHINode(llvm::PHINode *phiNode, + llvm::CallInst *alloc); + + bool BlockAccessAllocMemory(llvm::BasicBlock *block, llvm::CallInst *alloc); + + void RemoveHeapAllocFromDeoptBundle(llvm::CallInst *callInstWithDeoptBundle, + const std::set &allocValues, + std::vector &allocs, + std::vector &oldDeoptBundles); + + bool DeoptBundleContainsValue(const llvm::OperandBundleDef &deoptBundle, const llvm::Value *allocValue); + + std::map> GetCallsWithAllocInDeoptBundle( + const std::vector &allocs); + + void RemoveAllocFromDeoptBundleForAllCalls(std::map> &deoptBundlesToAllocMap, + std::vector &allocs, + std::vector &oldDeoptBundles); + + std::set GetUsersOfAlloc(llvm::Value *alloc); + + llvm::OperandBundleDef GetNewDeoptBundleWithoutAllocs(const std::vector &deoptBundle, + const std::set &allocValues); + + std::set GetAllocValuesFromIdxs(const std::vector &allocs, + const std::vector &allocIdxs); + void AddOldDeoptBundle(std::vector &oldDeoptBundles, + llvm::CallInst *oldInstWithDeoptBundle); + + llvm::CallBase *ChangeDeoptBundle(llvm::CallInst *callInstWithDeoptBundle, + const llvm::OperandBundleDef &newDeoptBundle); + + void UpdateVectorOfAllocs(std::vector &allAllocs, llvm::CallInst *oldCallNoLongerValid, + llvm::CallBase *newCallValid); + + void UpdateOldDeoptBundles(std::vector &oldDeoptBundles, + llvm::CallInst *oldCallNoLongerValid, llvm::CallBase *newCallValid); + + void ReplaceOldCallWithNewOne(llvm::CallInst *oldCallNoLongerValid, llvm::CallBase *newCall); + + llvm::OperandBundleDef GetOperandBundleDefFromUse(const llvm::OperandBundleUse &operandBundleUse); + + std::vector GetDeoptVec(const llvm::OperandBundleUse &deoptBundle); + + void ReturnDeopts(std::vector &oldDeoptBundles, llvm::DominatorTree &dominatorTree); + + bool LegalToChangeDeoptBundle(llvm::CallInst *callInst); + +private: + LLVMArkInterface *arkInterface_; + +public: + static constexpr llvm::StringRef ARG_NAME = "heap-alloc-sink"; +}; +} // namespace ark::llvmbackend::passes + +#endif // LIBLLVMBACKEND_TRANSFORMS_PASSES_HEAP_ALLOC_SINK_H diff --git a/static_core/libllvmbackend/transforms/passes/passes.yaml b/static_core/libllvmbackend/transforms/passes/passes.yaml index 5a1c5f68478dce4f3d7e5a2899106d69ef95fbb7..091cfda5bc5c3fda0b7d481f4900dc8ab7a4bd88 100644 --- a/static_core/libllvmbackend/transforms/passes/passes.yaml +++ b/static_core/libllvmbackend/transforms/passes/passes.yaml @@ -178,3 +178,9 @@ llvm_passes: Replace builtin for LenArray with size type: [function] setup: default + +- name: HeapAllocSink + description: > + Pass sinks heap allocation in place of use if possible + type: [function] + setup: default diff --git a/static_core/libllvmbackend/transforms/passes/prune_deopt.cpp b/static_core/libllvmbackend/transforms/passes/prune_deopt.cpp index 829782b6e893ebdf1906e73f90c63e968e2be0dc..80b69dd15cd77fc80d8935ab85587dfe48501ba2 100644 --- a/static_core/libllvmbackend/transforms/passes/prune_deopt.cpp +++ b/static_core/libllvmbackend/transforms/passes/prune_deopt.cpp @@ -91,7 +91,7 @@ CallInst *PruneDeopt::GetUpdatedCallInst(CallInst *call, const OperandBundleUse return updated; } -bool PruneDeopt::IsCaughtDeoptimization(ArrayRef inputs) const +bool PruneDeopt::IsCaughtDeoptimization(ArrayRef inputs) { constexpr auto CAUGHT_FLAG_IDX = 3; for (uint32_t i = 0; i < inputs.size(); ++i) { diff --git a/static_core/libllvmbackend/transforms/passes/prune_deopt.h b/static_core/libllvmbackend/transforms/passes/prune_deopt.h index 1692e6ba03db3a9bfe0be576815c2fc9c5b097a6..57a241ce695022844cb9244a0609da060dadb74b 100644 --- a/static_core/libllvmbackend/transforms/passes/prune_deopt.h +++ b/static_core/libllvmbackend/transforms/passes/prune_deopt.h @@ -38,12 +38,14 @@ public: // NOLINTNEXTLINE(readability-identifier-naming) llvm::PreservedAnalyses run(llvm::Function &function, llvm::FunctionAnalysisManager &analysisManager); + static bool IsCaughtDeoptimization(llvm::ArrayRef inputs); + private: llvm::CallInst *GetUpdatedCallInst(llvm::CallInst *call, const llvm::OperandBundleUse &bundle); using EncodedDeoptBundle = llvm::SmallVector; - bool IsCaughtDeoptimization(llvm::ArrayRef inputs) const; + // bool IsCaughtDeoptimization(llvm::ArrayRef inputs) const; bool IsNoReturn(llvm::ArrayRef inputs) const; diff --git a/static_core/libllvmbackend/transforms/pipeline.cfg b/static_core/libllvmbackend/transforms/pipeline.cfg index 7503bc6abc70283c4be687a34d524b99b38fdc66..17033c5991f5003907dc37d71a7e6698191ef91b 100644 --- a/static_core/libllvmbackend/transforms/pipeline.cfg +++ b/static_core/libllvmbackend/transforms/pipeline.cfg @@ -14,6 +14,7 @@ module( function( lower-expect, # Lower expect intrinsic + heap-alloc-sink, # Try to sink heap allocations # Optimistically try to unswitch early before simplifycfg makes a lot of Selects out of branches loop-mssa( licm, @@ -117,6 +118,7 @@ module( discard-inline-module, # Discard inline module function( + heap-alloc-sink, # Try to sink heap allocations loop-simplify, # Canonicalize natural loops lcssa, # Loop-Closed SSA Form Pass loop-rotate, # Rotate Loops diff --git a/static_core/plugins/ets/tests/checked/CMakeLists.txt b/static_core/plugins/ets/tests/checked/CMakeLists.txt index a899333637dabec08484b02a7253578e092bac44..fcc82756690422c21ca6733da41549ab35eaf79e 100644 --- a/static_core/plugins/ets/tests/checked/CMakeLists.txt +++ b/static_core/plugins/ets/tests/checked/CMakeLists.txt @@ -267,3 +267,8 @@ panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/escape_analysis_cast panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/load_array.sts) panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/optimize_negation.sts) panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/llvm_infer_flags.sts) +panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ets_llvmaot_heap_alloc_sink_deopt.sts) +panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ets_llvmaot_heap_alloc_sink.sts) +panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ets_llvmaot_heap_alloc_sink_two.sts) +panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ets_llvmaot_heap_alloc_sink_three.sts) +panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ets_llvmaot_heap_alloc_sink_except.sts) diff --git a/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink.sts b/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink.sts new file mode 100644 index 0000000000000000000000000000000000000000..6d6007dd8c09888a511d0dff601d8acdd22624ec --- /dev/null +++ b/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink.sts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023-2024 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. + */ + +//! CHECKER Check that heap-alloc-sink pass moved heap allocation before simplifycfg +//! RUN_LLVM options: "--llvm-dump-after" +//! READ_FILE "console.out" +//! LLVM_METHOD /define.*\"std.core.Object ETSGLOBAL::foo.*\d+\"/ +//! INST /%v[0-9]*.* = icmp .*42.*/ +//! INST_NEXT_NOT /%v[0-9]* = select i1.*/ +//! RUN entry: "ETSGLOBAL::main" + +function foo(rand: long): Object { + let obj: Object = new Object; + + let tmpObj: Object = new Object; + if (rand == 42) { + obj = tmpObj; + } + + return obj; +} + + +function main() { + let rand = 42; + let obj: Object = foo(rand); +} diff --git a/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink_deopt.sts b/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink_deopt.sts new file mode 100644 index 0000000000000000000000000000000000000000..e88b971d222309fa49ffa719f3c76d91be070cf4 --- /dev/null +++ b/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink_deopt.sts @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023-2024 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. + */ + +//! CHECKER Check that heap-alloc-sink pass moved heap allocation with use in deopt bundle +//! RUN_LLVM options: "--llvm-options=--print-after=heap-alloc-sink --compiler-regex='.*main'" +//! READ_FILE "console.out" +//! LLVM_METHOD /define.*\"i32 Test::main.*\d+\"/ +//! INST /%v[0-9]* = call (arkfastcc|x86_64_arkfastcallcc).*/ +//! INST_NEXT_NOT /%[0-9]* = call ptr @__builtin_load_init_class.*/ +//! RUN entry: "Test::main" + +class Consumer { + public static readonly x1: long = 0x41c64e6d; + public static readonly x2: long = 0xd431; + public static x3: long = 1 + public static localObj = new Object(); + public static pseudorand: long = Date.now() as long; + + public static consumeObj(obj: Object): void throws { + Consumer.pseudorand = (Consumer.pseudorand * Consumer.x1 + Consumer.x2); + if ((Consumer.pseudorand & Consumer.x3) >= 4201337) { + Consumer.x3 = (Consumer.x3 << 1) + (0xad as long); + Consumer.localObj = obj; + } + } +} + +class Test { + static holderSize: int; + + public allocObj(): void throws { + for (let i = 0; i < Test.holderSize; i++) { + Consumer.consumeObj(new Object()); + } + } + + public static main(): int { + Test.holderSize = 100; + let test = new Test(); + try { + test.allocObj(); + } catch (e) { + return 1; + } + + return 0; + } +} diff --git a/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink_except.sts b/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink_except.sts new file mode 100644 index 0000000000000000000000000000000000000000..4a15368572ed77490b2e88ef266e13b4d03daf7f --- /dev/null +++ b/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink_except.sts @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 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. + */ + +//! CHECKER Check that deoptimization is correctly handled after allocation sink AOT +//! RUN_PAOC options: "" +//! RUN entry: "ETSGLOBAL::main" + +//! CHECKER Check that deoptimization is correctly handled after allocation sink +//! RUN_LLVM options: "" +//! RUN entry: "ETSGLOBAL::main" + +function hiddenThrow() : void throws { + throw new Error(); +} + +function foo(a: int[]) : void { + for (let i: int = 0; i < a.length; ++i) { + a[i] = i; + } +} + +function main() : void { + let arr : int[] = new int[10]; + try { + hiddenThrow(); + } catch (e) { + console.println("exception"); + } + foo(arr); + console.println("success"); +} + diff --git a/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink_three.sts b/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink_three.sts new file mode 100644 index 0000000000000000000000000000000000000000..83d83fea3e6c2c636290e8c6fceeb6c2b3214fe1 --- /dev/null +++ b/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink_three.sts @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023-2024 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. + */ + +//! CHECKER Check that heap-alloc-sink pass left only select number of heap allocations +//! RUN_LLVM options: "--llvm-options=--print-after=heap-alloc-sink --compiler-regex='.*main'" +//! READ_FILE "console.out" +//! LLVM_METHOD /define.*\"i32 Test::main.*\d+\"/ +//! INST /%v[0-9]* = call (arkfastcc|x86_64_arkfastcallcc).*/ +//! INST_NEXT_NOT /%[0-9]* = call ptr @__builtin_load_init_class.*/ +//! RUN entry: "Test::main", options: "--compiler-enable-tlab-events" +//! TRUE EVENT_COUNT(/TlabAlloc,.*/) == 2 + +class Consumer { + public static localObj = new Object(); + + public static consumeObj(rand: int, obj: Object): void throws { + if (rand == 42 || rand == 420) { + Consumer.localObj = obj; + } + } +} + +class Test { + static holderSize: int; + + public allocObj(): void throws { + for (let i = 0; i < Test.holderSize; i++) { + Consumer.consumeObj(i, new Object()); + } + } + + public static main(): int { + Test.holderSize = 1000; + let test = new Test(); + try { + test.allocObj(); + } catch (e) { + return 1; + } + + return 0; + } +} diff --git a/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink_two.sts b/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink_two.sts new file mode 100644 index 0000000000000000000000000000000000000000..dc118bf655e8612f94664e444719cb7fd8ef951a --- /dev/null +++ b/static_core/plugins/ets/tests/checked/ets_llvmaot_heap_alloc_sink_two.sts @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023-2024 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. + */ + +//! CHECKER Check that heap-alloc-sink pass moved heap allocation +//! RUN_LLVM options: "--llvm-options=--print-after=heap-alloc-sink --compiler-regex='.*foo'" +//! READ_FILE "console.out" +//! LLVM_METHOD /define.*\"std.core.Object ETSGLOBAL::foo.*\d+\"/ +//! INST /%v[0-9]* = icmp ne i64 42, %a[0-9]*.*/ +//! INST_NEXT_NOT /%[a-z]*.[a-z]* = select i1.*/ +//! RUN entry: "ETSGLOBAL::main" + +function consume(obj: Object): Object { + let localObj = obj; + return localObj; +} + +function foo(rand: long): Object { + let diffObject = new Object(); + let obj = new Object(); + + if (rand == 42) { + let localObj = consume(obj); + return localObj; + } + + return diffObject; +} + +function main() { + let rand = 42; + let obj: Object = foo(rand); +} +