diff --git a/BUILD.gn b/BUILD.gn index 5cdca5a58174f27e42444bb20f62447271b64dc5..c136904130b1338f0952e01d82c7f0c0f1de5bb2 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -125,6 +125,10 @@ config("ark_config") { outputs = [ "$target_gen_dir/plugins/create_pipeline_includes.h" ] contents = "" } + generated_file("optimizations_after_unroll") { + outputs = [ "$target_gen_dir/plugins/optimizations_after_unroll.h" ] + contents = "" + } generated_file("clear_profile") { outputs = [ "$target_gen_dir/plugins/clear_profile.h" ] diff --git a/compiler/CMakeLists.txt b/compiler/CMakeLists.txt index 3b461821c51c8adc5775827bee238b39c576ece5..2553d64d07de48827d332f23f0908e4a8c9a30c5 100644 --- a/compiler/CMakeLists.txt +++ b/compiler/CMakeLists.txt @@ -689,3 +689,4 @@ add_subdirectory(aot/aot_builder aot_builder) declare_plugin_file("create_pipeline.h") declare_plugin_file("create_pipeline_includes.h") +declare_plugin_file("optimizations_after_unroll.h") diff --git a/compiler/compiler.yaml b/compiler/compiler.yaml index cad0538b2557b3fdd5d3033ebe9e43bd717f3245..1d9735560d225826f83e8428e04950d6da9f32f6 100644 --- a/compiler/compiler.yaml +++ b/compiler/compiler.yaml @@ -156,6 +156,7 @@ options: - bridges-ss - pm - pea + - merge-handle-scopes description: Set log compiler components tags: [debug] delimiter: "," @@ -706,6 +707,18 @@ options: description: Enable fast ArkTS->JS interop tags: [perf] +- name: compiler-merge-handle-scopes + type: bool + default: true + description: Enable merging local scopes in ArkTS->EcmaScript interop + tags: [perf] + +- name: compiler-local-scope-object-limit + type: uint32_t + default: 256 + description: Object limit for merged local scope in ArkTS->EcmaScript interop + tags: [perf] + events: - name: branch-elimination fields: diff --git a/compiler/compiler_logger.h b/compiler/compiler_logger.h index abb7d7f528efc0ddb6bc847285b475d4ba4c457a..2d2ca83a92ecc0f0c34c4d824f3791a1b1f17c23 100644 --- a/compiler/compiler_logger.h +++ b/compiler/compiler_logger.h @@ -66,6 +66,10 @@ private: // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define COMPILER_LOG(level, comp) \ CompilerLogger::IsComponentEnabled(CompilerLoggerComponents::comp) && LOG(level, COMPILER) << "[" #comp "] " +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define COMPILER_LOG_IF(cond, level, comp) \ + CompilerLogger::IsComponentEnabled(CompilerLoggerComponents::comp) && LOG_IF(cond, level, COMPILER) \ + << "[" #comp "] " } // namespace panda::compiler #endif // COMPILER_COMPILER_LOGGER_H_ diff --git a/compiler/optimizer/ir/ir_constructor.h b/compiler/optimizer/ir/ir_constructor.h index 3c50896ae6c35d6c3fc4e533aa38447272caf1c6..d9d66c5eed3f05541184ea3c6a0734384e3c4a0d 100644 --- a/compiler/optimizer/ir/ir_constructor.h +++ b/compiler/optimizer/ir/ir_constructor.h @@ -266,10 +266,21 @@ public: template IrConstructor &InputsAutoType(Args... inputs) { - ASSERT(CurrentInst()->IsCall() || CurrentInst()->GetOpcode() == Opcode::MultiArray); - auto *call_inst = static_cast(CurrentInst()); - call_inst->AllocateInputTypes(graph_->GetAllocator(), sizeof...(inputs)); - ((call_inst->AddInputType(GetInst(inputs).GetType())), ...); + InputTypesMixin *types; + switch (CurrentInst()->GetOpcode()) { + case Opcode::Intrinsic: + types = static_cast(CurrentInst()->CastToIntrinsic()); + break; + case Opcode::MultiArray: + types = static_cast(CurrentInst()->CastToMultiArray()); + break; + default: + ASSERT(CurrentInst()->IsCall()); + types = static_cast(static_cast(CurrentInst())); + break; + } + types->AllocateInputTypes(graph_->GetAllocator(), sizeof...(inputs)); + ((types->AddInputType(GetInst(inputs).GetType())), ...); inst_inputs_map_[CurrentInstIndex()].reserve(sizeof...(inputs)); ((inst_inputs_map_[CurrentInstIndex()].push_back(inputs)), ...); return *this; @@ -916,6 +927,21 @@ public: return *this; } + // Useful for parametrized tests + IrConstructor &CleanupInputs() + { + ASSERT(CurrentInst()->IsSaveState()); + auto &inputs = inst_inputs_map_[CurrentInstIndex()]; + auto &vregs = save_state_inst_vregs_map_[CurrentInstIndex()]; + for (int i = inputs.size() - 1; i >= 0; i--) { + if (inst_map_.count(inputs[i]) == 0) { + inputs.erase(inputs.begin() + i); + vregs.erase(vregs.begin() + i); + } + } + return *this; + } + IrConstructor &CatchTypeIds(std::vector &&ids) { auto inst = CurrentInst(); @@ -1347,6 +1373,7 @@ public: Inst &GetInst(unsigned index) { + ASSERT_DO(inst_map_.find(index) != inst_map_.end(), std::cerr << "Inst with Id " << index << " isn't found\n"); return *inst_map_.at(index); } diff --git a/compiler/optimizer/pipeline.cpp b/compiler/optimizer/pipeline.cpp index 6e8e2372d1513934c314070b48fb76bd1f0a2f26..78e76e0dddd9207bbcf94b7dc4a0b4f76a53f626 100644 --- a/compiler/optimizer/pipeline.cpp +++ b/compiler/optimizer/pipeline.cpp @@ -204,6 +204,7 @@ bool Pipeline::RunOptimizations() graph->RunPass(); graph->RunPass(); graph->RunPass(OPTIONS.GetCompilerLoopUnrollInstLimit(), OPTIONS.GetCompilerLoopUnrollFactor()); +#include /* to be removed once generic loop unrolling is implemented */ ASSERT(graph->IsUnrollComplete()); diff --git a/plugins/ets/compiler/CMakeLists.txt b/plugins/ets/compiler/CMakeLists.txt index 4dcc98d9b877b7e91374c558d3d40ff02cc52d2f..eb4a3bc8a589f11d874187856febcd067c4f7e36 100644 --- a/plugins/ets/compiler/CMakeLists.txt +++ b/plugins/ets/compiler/CMakeLists.txt @@ -33,7 +33,11 @@ endif() if (PANDA_ETS_INTEROP_JS) list(APPEND COMPILER_SOURCES ${PANDA_ETS_PLUGIN_SOURCE}/compiler/optimizer/ir_builder/js_interop/js_interop_inst_builder.cpp + ${PANDA_ETS_PLUGIN_SOURCE}/compiler/optimizer/interop_js/merge_handle_scopes.cpp ) + + add_merge_plugin(PLUGIN_NAME "create_pipeline_includes.h" INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/plugin_files/plugin_create_pipeline_includes.h") + add_merge_plugin(PLUGIN_NAME "optimizations_after_unroll.h" INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/plugin_files/optimizations_after_unroll.h") endif() panda_target_sources(arkcompiler PRIVATE ${COMPILER_SOURCES}) diff --git a/plugins/ets/compiler/optimizer/interop_js/merge_handle_scopes.cpp b/plugins/ets/compiler/optimizer/interop_js/merge_handle_scopes.cpp new file mode 100644 index 0000000000000000000000000000000000000000..21c41fb344a174b66db0b10c6385fe06637477f6 --- /dev/null +++ b/plugins/ets/compiler/optimizer/interop_js/merge_handle_scopes.cpp @@ -0,0 +1,1107 @@ +/** + * Copyright (c) 2021-2022 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 "merge_handle_scopes.h" +#include "optimizer/ir/runtime_interface.h" +#include "optimizer/analysis/countable_loop_parser.h" +#include "optimizer/analysis/loop_analyzer.h" + +namespace panda::compiler { + +static bool IsForbiddenInst(Inst *inst) +{ + return inst->IsCall() && !static_cast(inst)->IsInlined(); +} + +static bool IsScopeStart(Inst *inst) +{ + return inst->IsIntrinsic() && inst->CastToIntrinsic()->GetIntrinsicId() == + RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CREATE_LOCAL_SCOPE; +} + +static bool IsScopeEnd(Inst *inst) +{ + return inst->IsIntrinsic() && inst->CastToIntrinsic()->GetIntrinsicId() == + RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_DESTROY_LOCAL_SCOPE; +} + +static bool IsWrapIntrinsicId(RuntimeInterface::IntrinsicId id) +{ + switch (id) { + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_VOID_TO_LOCAL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_U1_TO_LOCAL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_U8_TO_LOCAL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_I8_TO_LOCAL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_U16_TO_LOCAL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_I16_TO_LOCAL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_U32_TO_LOCAL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_I32_TO_LOCAL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_U64_TO_LOCAL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_I64_TO_LOCAL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_F32_TO_LOCAL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_F64_TO_LOCAL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_REF_TYPE_TO_LOCAL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_JS_VALUE_TO_LOCAL: + return true; + default: + return false; + } +} + +static bool IsUnwrapIntrinsicId(RuntimeInterface::IntrinsicId id) +{ + switch (id) { + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_U1: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_U8: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_I8: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_U16: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_I16: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_U32: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_I32: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_U64: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_I64: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_F32: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_F64: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_JS_VALUE: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_STRING: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_REF_TYPE: + return true; + default: + return false; + } +} + +static RuntimeInterface::IntrinsicId GetUnwrapIntrinsicId(RuntimeInterface::IntrinsicId id) +{ + switch (id) { + case RuntimeInterface::IntrinsicId::INTRINSIC_JS_RUNTIME_GET_VALUE_DOUBLE: + return RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_F64; + case RuntimeInterface::IntrinsicId::INTRINSIC_JS_RUNTIME_GET_VALUE_BOOLEAN: + return RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_U1; + case RuntimeInterface::IntrinsicId::INTRINSIC_JS_RUNTIME_GET_VALUE_STRING: + return RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_STRING; + default: + return RuntimeInterface::IntrinsicId::INVALID; + } +} + +static bool IsConvertIntrinsic(Inst *inst) +{ + if (!inst->IsIntrinsic()) { + return false; + } + auto id = inst->CastToIntrinsic()->GetIntrinsicId(); + return IsWrapIntrinsicId(id) || IsUnwrapIntrinsicId(id); +} + +static bool IsInteropIntrinsic(Inst *inst) +{ + if (!inst->IsIntrinsic()) { + return false; + } + auto id = inst->CastToIntrinsic()->GetIntrinsicId(); + if (IsWrapIntrinsicId(id) || IsUnwrapIntrinsicId(id)) { + return true; + } + switch (id) { + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_GET_JS_NAMED_PROPERTY: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_RESOLVE_QUALIFIED_JS_CALL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_JS_CALL_CHECK: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_JS_CALL_FUNCTION: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_JS_NEW_INSTANCE: + return true; + default: + return false; + } +} + +static bool CanCreateNewScopeObject(Inst *inst) +{ + if (!inst->IsIntrinsic()) { + return false; + } + auto id = inst->CastToIntrinsic()->GetIntrinsicId(); + if (IsWrapIntrinsicId(id)) { + return true; + } + switch (inst->CastToIntrinsic()->GetIntrinsicId()) { + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_GET_JS_NAMED_PROPERTY: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_RESOLVE_QUALIFIED_JS_CALL: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_JS_CALL_FUNCTION: + case RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_JS_NEW_INSTANCE: + return true; + default: + return false; + } +} + +MergeHandleScopes::BlockInfo &MergeHandleScopes::GetInfo(BasicBlock *block) +{ + return block_info_[block->GetId()]; +} + +void MergeHandleScopes::MergeScopesInsideBlock(BasicBlock *block) +{ + [[maybe_unused]] Inst *last_start = nullptr; // Start of the current scope or nullptr if no scope is open + Inst *last_end = nullptr; // End of the last closed scope + size_t last_object_count = + 0; // Number of object creations in the last closed scope (valid value if we are in the next scope now) + size_t current_object_count = 0; // Number of object creations in the current scope or 0 + for (auto *inst : block->InstsSafe()) { + if (IsScopeStart(inst)) { + ASSERT(last_start == nullptr); + ASSERT(current_object_count == 0); + has_scopes_ = true; + last_start = inst; + } else if (IsScopeEnd(inst)) { + ASSERT(last_start != nullptr); + if (last_end != nullptr && last_object_count + current_object_count <= scope_object_limit_) { + ASSERT(last_end->IsPrecedingInSameBlock(last_start)); + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << "Remove scope end " << *last_end << "\nand scope start " + << *last_start << "\nfrom BB " << block->GetId(); + block->RemoveInst(last_end); + block->RemoveInst(last_start); + last_object_count += current_object_count; + is_applied_ = true; + } else { + if (last_end != nullptr) { + object_limit_hit_ = true; + } + last_object_count = current_object_count; + } + current_object_count = 0; + + last_end = inst; + last_start = nullptr; + } else if (IsForbiddenInst(inst)) { + ASSERT(last_start == nullptr); + last_end = nullptr; + last_object_count = 0; + current_object_count = 0; + } else if (CanCreateNewScopeObject(inst)) { + ASSERT(last_start != nullptr); + ++current_object_count; + } + } +} + +void MergeHandleScopes::MarkOuterLoopsForbidden(Loop *loop) +{ + if (loop == nullptr) { + return; + } + while (!loop->IsRoot() && forbidden_loops_.insert(loop).second) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Mark loop (id " << loop->GetId() << ") as forbidden to move into scope"; + loop = loop->GetOuterLoop(); + } +} + +void MergeHandleScopes::MarkForbiddenLoops() +{ + ArenaUnorderedSet interop_loops(GetGraph()->GetLocalAllocator()->Adapter()); + for (auto *block : GetGraph()->GetBlocksRPO()) { + auto loop = block->GetLoop(); + ASSERT(loop != nullptr); + if (forbidden_loops_.count(loop) > 0) { + continue; + } + if (loop->IsIrreducible()) { + MarkOuterLoopsForbidden(loop); + continue; + } + for (auto inst : block->Insts()) { + if (IsForbiddenInst(inst)) { + MarkOuterLoopsForbidden(loop); + break; + } + if (IsScopeStart(inst) && !loop->IsRoot()) { + interop_loops.insert(loop); + } + } + } + for (auto *loop : interop_loops) { + if (forbidden_loops_.count(loop) > 0) { + continue; + } + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << "INTEROP LOOP?? id = " << loop->GetId(); + if (auto loop_info = CountableLoopParser(*loop).Parse()) { + auto opt_iterations = CountableLoopParser::GetLoopIterations(*loop_info); + if (opt_iterations && *opt_iterations <= MAX_LOOP_ITERATIONS) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Found small countable loop, id = " << loop->GetId() << ", iterations = " << *opt_iterations; + loop = loop->GetOuterLoop(); + } + } + MarkOuterLoopsForbidden(loop); + } +} + +bool MergeHandleScopes::IsForbiddenLoopEntry(BasicBlock *block) +{ + return block->GetLoop()->IsIrreducible() || (block->IsLoopHeader() && forbidden_loops_.count(block->GetLoop()) > 0); +} + +template +static auto GetInstsIter(BasicBlock *block) +{ + if constexpr (REVERSE) { + return block->InstsReverse(); + } else { + return block->Insts(); + } +} + +// Implementation of disjoint set union with path compression +int32_t MergeHandleScopes::GetParentComponent(int32_t component) +{ + auto &parent = components_[component].parent; + if (parent != component) { + parent = GetParentComponent(parent); + } + ASSERT(parent != DFS_NOT_VISITED); + return parent; +} + +void MergeHandleScopes::MergeComponents(int32_t first, int32_t second) +{ + first = GetParentComponent(first); + second = GetParentComponent(second); + components_[second].object_count += components_[first].object_count; + components_[second].parent = first; +} + +template +void MergeHandleScopes::BlockBoundaryDfs(BasicBlock *block) +{ + auto index = static_cast(IS_END); + if (GetInfo(block).block_component[index] != DFS_NOT_VISITED) { + return; + } + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << "Added " << (IS_END ? "end" : "start ") << " of block " + << block->GetId() << " to connected component " << current_component_; + GetInfo(block).block_component[index] = current_component_; + auto other_end_component = GetInfo(block).block_component[1 - index]; + if (other_end_component != current_component_) { + current_component_blocks_.push_back(block); + } + size_t scope_switches = 0; + for (auto *inst : GetInstsIter(block)) { + if (IsScopeStart(inst) || IsScopeEnd(inst)) { + ++scope_switches; + // If both ends of the block belong to the same component, count instructions only from + // the first visited end + } else if (other_end_component != current_component_ && CanCreateNewScopeObject(inst)) { + if (scope_switches <= 1) { + ++objects_in_scope_after_merge_; + if (scope_switches == 0) { + ++components_[current_component_].object_count; + } + } + } + if (IsForbiddenInst(inst)) { + if (scope_switches == 1) { + can_merge_ = false; + } else if (scope_switches == 0) { + COMPILER_LOG_IF(!components_[current_component_].is_forbidden, DEBUG, MERGE_HANDLE_SCOPES) + << "Connected component " << current_component_ + << " cannot be moved into scope because it contains forbidden inst " << *inst; + components_[current_component_].is_forbidden = true; + break; + } + } + } + if (scope_switches == 0) { + if (block->IsStartBlock() || block->IsEndBlock()) { + COMPILER_LOG_IF(!components_[current_component_].is_forbidden, DEBUG, MERGE_HANDLE_SCOPES) + << "Connected component " << current_component_ << " cannot be moved into scope because it contains " + << (block->IsStartBlock() ? "start" : "end") << " block " << block->GetId(); + components_[current_component_].is_forbidden = true; + } + BlockBoundaryDfs(block); + } else if (scope_switches == 1) { + // Other end of the block was already moved into scope + ASSERT(other_end_component != DFS_NOT_VISITED && other_end_component != current_component_); + other_end_component = GetParentComponent(other_end_component); + if (components_[other_end_component].is_forbidden) { + can_merge_ = false; + } + objects_in_scope_after_merge_ += TryInclude(components_[other_end_component], current_component_); + } + + auto &neighbours = IS_END ? block->GetSuccsBlocks() : block->GetPredsBlocks(); + for (auto *neighbour : neighbours) { + if (!IS_END && IsForbiddenLoopEntry(neighbour)) { + COMPILER_LOG_IF(!components_[current_component_].is_forbidden, DEBUG, MERGE_HANDLE_SCOPES) + << "Connected component " << current_component_ + << " cannot be moved into scope because it contains entry to forbidden loop " + << neighbour->GetLoop()->GetId() << " via BB " << neighbour->GetId(); + components_[current_component_].is_forbidden = true; + } + BlockBoundaryDfs(neighbour); + } +} + +static void MoveBlockStartIntoScope(BasicBlock *block) +{ + for (auto *inst : block->Insts()) { + ASSERT(!IsScopeEnd(inst)); + if (IsScopeStart(inst)) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Remove first scope start " << *inst << "\nfrom BB " << block->GetId(); + block->RemoveInst(inst); + return; + } + } +} + +static void MoveBlockEndIntoScope(BasicBlock *block) +{ + for (auto *inst : block->InstsReverse()) { + ASSERT(!IsScopeStart(inst)); + if (IsScopeEnd(inst)) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Remove last scope end " << *inst << "\nfrom BB " << block->GetId(); + block->RemoveInst(inst); + return; + } + } +} + +void MergeHandleScopes::MergeCurrentComponentWithNeighbours() +{ + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Merging connected component " << current_component_ << " with its neighbours"; + for (auto *block : current_component_blocks_) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << "Iterate block " << block->GetId(); + if (GetInfo(block).block_component[0] == current_component_) { + MoveBlockStartIntoScope(block); + } + if (GetInfo(block).block_component[1] == current_component_) { + MoveBlockEndIntoScope(block); + } + } + is_applied_ = true; +} + +template +void MergeHandleScopes::FindComponentAndTryMerge(BasicBlock *block) +{ + auto index = static_cast(IS_END); + if (GetInfo(block).block_component[index] != DFS_NOT_VISITED) { + return; + } + components_.push_back({current_component_, 0, 0, false}); + current_component_blocks_.clear(); + objects_in_scope_after_merge_ = 0; + can_merge_ = true; + BlockBoundaryDfs(block); + if (components_[current_component_].is_forbidden) { + can_merge_ = false; + } + if (can_merge_) { + if (objects_in_scope_after_merge_ > scope_object_limit_) { + object_limit_hit_ = true; + } else { + MergeCurrentComponentWithNeighbours(); + } + } + ++current_component_; +} + +bool MergeHandleScopes::MergeScopesInBlock(BasicBlock *block, bool include_start, bool include_end) +{ + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Merge scopes in BB " << block->GetId() << ", include_start: " << include_start + << ", include_end: " << include_end; + Inst *last_start = nullptr; + Inst *last_end = nullptr; + for (auto *inst : block->InstsSafe()) { + if (IsScopeStart(inst) && (last_end != nullptr || include_start)) { + if (include_start) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Remove first scope start " << *inst << "\nfrom BB " << block->GetId(); + include_start = false; + } else { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << "Remove scope end " << *last_end << "\nand scope start " + << *inst << "\nfrom BB " << block->GetId(); + block->RemoveInst(last_end); + last_end = nullptr; + } + block->RemoveInst(inst); + is_applied_ = true; + } else if (IsScopeStart(inst)) { + ASSERT(last_start == nullptr); // No nested scopes + ASSERT(!include_start); + last_start = inst; + } else if (IsScopeEnd(inst)) { + ASSERT(last_end == nullptr); + last_start = nullptr; + last_end = inst; + } else if (IsForbiddenInst(inst)) { + ASSERT(last_start == nullptr); + last_end = nullptr; + } + } + if (include_end && last_end != nullptr) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Remove last scope end " << *last_end << "\nfrom BB " << block->GetId(); + block->RemoveInst(last_end); + is_applied_ = true; + include_end = false; + } + if (include_start || !include_end) { + ASSERT(!include_start); + return true; + } + ASSERT(!include_start && include_end && last_start == nullptr && last_end == nullptr); + // No start/end/forbidden insts, need to start a new scope + for (auto *ss : block->InstsSafeReverse()) { + ASSERT(!IsForbiddenInst(ss)); + if (ss->GetOpcode() == Opcode::SaveState) { + auto scope_start = GetGraph()->CreateInstIntrinsic( + DataType::VOID, ss->GetPc(), RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CREATE_LOCAL_SCOPE); + scope_start->AllocateInputTypes(GetGraph()->GetAllocator(), 1); + scope_start->AppendInput(ss); + scope_start->AddInputType(DataType::NO_TYPE); + ss->InsertAfter(scope_start); + is_applied_ = true; + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Created new scope start " << *scope_start << " in BB " << block->GetId(); + return true; + } + } + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << "Failed to create new scope start in BB " << block->GetId(); + return false; +} + +void MergeHandleScopes::MergeInterScopeRegions() +{ + for (auto *block : GetGraph()->GetBlocksRPO()) { + FindComponentAndTryMerge(block); + FindComponentAndTryMerge(block); + } +} + +// Numbering is similar to pre-order, but we stop in blocks with scope starts +void MergeHandleScopes::DfsNumbering(BasicBlock *block) +{ + if (GetInfo(block).dfs_num_in != DFS_NOT_VISITED) { + return; + } + GetInfo(block).dfs_num_in = dfs_num_++; + for (auto inst : block->Insts()) { + ASSERT(!IsScopeEnd(inst)); + if (IsScopeStart(inst)) { + GetInfo(block).dfs_num_out = dfs_num_; + return; + } + } + for (auto *succ : block->GetSuccsBlocks()) { + DfsNumbering(succ); + } + GetInfo(block).dfs_num_out = dfs_num_; +} + +// Calculate minimal and maximal `dfs_num_in` for blocks that can be reached by walking some edges forward +// and, after that, maybe one edge backward +// Visit order will be the same as in DfsNumbering +// We walk the graph again because during the first DFS some numbers for predecessors may be invalid yet +void MergeHandleScopes::CalculateReachabilityRec(BasicBlock *block) +{ + if (block->SetMarker(visited_)) { + return; + } + auto &info = GetInfo(block); + info.subgraph_min_num = info.subgraph_max_num = info.dfs_num_in; + + bool is_scope_start = false; + for (auto inst : block->Insts()) { + ASSERT(!IsScopeEnd(inst)); + if (IsForbiddenInst(inst)) { + info.subgraph_min_num = DFS_NOT_VISITED; + } + if (IsScopeStart(inst)) { + is_scope_start = true; + break; + } + } + + if (!is_scope_start) { + for (auto *succ : block->GetSuccsBlocks()) { + CalculateReachabilityRec(succ); + info.subgraph_min_num = std::min(info.subgraph_min_num, GetInfo(succ).subgraph_min_num); + info.subgraph_max_num = std::max(info.subgraph_max_num, GetInfo(succ).subgraph_max_num); + if (IsForbiddenLoopEntry(succ)) { + info.subgraph_min_num = DFS_NOT_VISITED; + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "BB " << block->GetId() << " cannot be moved into scope because succ " << succ->GetId() + << " is entry to forbidden loop " << succ->GetLoop()->GetId(); + break; + } + } + if (info.dfs_num_in <= info.subgraph_min_num && info.subgraph_max_num < info.dfs_num_out) { + block->SetMarker(can_hoist_to_); + } + } + for (auto *pred : block->GetPredsBlocks()) { + if (pred->IsMarked(start_dfs_)) { + info.subgraph_min_num = DFS_NOT_VISITED; + break; + } + info.subgraph_min_num = std::min(info.subgraph_min_num, GetInfo(pred).dfs_num_in); + info.subgraph_max_num = std::max(info.subgraph_max_num, GetInfo(pred).dfs_num_in); + } +} + +template +void MergeHandleScopes::DoDfs() +{ + // We launch DFS in a graph where vertices correspond to block starts not contained in scopes, and + // vertices for bb1 and bb2 are connected by a (directed) edge if bb1 and bb2 are connected in CFG and + // there are no scopes in bb1 + + for (auto *block : GetGraph()->GetBlocksRPO()) { + // We mark block with `start_dfs_` marker if its **end** is contained in a new inter-scope region + // (i. e. block is the start block or last scope switch in block is scope end) + // And since our graph contains **starts** of blocks, we launch DFS from succs, not from the block itself + if (block->IsMarked(start_dfs_)) { + for (auto *succ : block->GetSuccsBlocks()) { + (this->*DFS)(succ); + } + } + } +} + +void MergeHandleScopes::RemoveReachableScopeStarts(BasicBlock *block, BasicBlock *new_start_block) +{ + ASSERT(!block->IsEndBlock()); + if (block->SetMarker(visited_)) { + return; + } + block->ResetMarker(can_hoist_to_); + ASSERT(new_start_block->IsDominate(block)); + if (block != new_start_block) { + ASSERT(!IsForbiddenLoopEntry(block)); + for (auto *inst : block->Insts()) { + ASSERT(!IsForbiddenInst(inst)); + ASSERT(!IsScopeEnd(inst)); + if (IsScopeStart(inst)) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Removed scope start " << *inst << "from BB " << block->GetId() << ", new scope start in " + << new_start_block->GetId(); + block->RemoveInst(inst); + return; + } + } + } + for (auto *succ : block->GetSuccsBlocks()) { + RemoveReachableScopeStarts(succ, new_start_block); + } +} + +void MergeHandleScopes::HoistScopeStarts() +{ + auto start_dfs_holder = MarkerHolder(GetGraph()); + start_dfs_ = start_dfs_holder.GetMarker(); + auto can_hoist_to_holder = MarkerHolder(GetGraph()); + can_hoist_to_ = can_hoist_to_holder.GetMarker(); + for (auto *block : GetGraph()->GetBlocksRPO()) { + bool end_in_scope = false; + for (auto inst : block->InstsReverse()) { + if (IsScopeEnd(inst)) { + block->SetMarker(start_dfs_); + break; + } + if (IsScopeStart(inst)) { + end_in_scope = true; + break; + } + } + if (block->IsStartBlock() && !end_in_scope) { + block->SetMarker(start_dfs_); + } + } + DoDfs<&MergeHandleScopes::DfsNumbering>(); + { + auto visited_holder = MarkerHolder(GetGraph()); + visited_ = visited_holder.GetMarker(); + DoDfs<&MergeHandleScopes::CalculateReachabilityRec>(); + } + + for (auto *block : GetGraph()->GetBlocksRPO()) { + if (block->IsMarked(can_hoist_to_) && MergeScopesInBlock(block, false, true)) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Hoisted scope start to BB " << block->GetId() << ", removing scope starts reachable from it"; + auto visited_holder = MarkerHolder(GetGraph()); + visited_ = visited_holder.GetMarker(); + RemoveReachableScopeStarts(block, block); + } + } +} + +void MergeHandleScopes::InvalidateScopesInSubgraph(BasicBlock *block) +{ + ASSERT(!block->IsEndBlock()); + if (block->SetMarker(scope_start_invalidated_)) { + return; + } + for (auto *inst : block->Insts()) { + ASSERT(!IsScopeStart(inst)); + if (IsScopeEnd(inst)) { + return; + } + if (IsInteropIntrinsic(inst)) { + scope_for_inst_[inst] = nullptr; + } + } + for (auto *succ : block->GetSuccsBlocks()) { + InvalidateScopesInSubgraph(succ); + } +} + +void MergeHandleScopes::CheckGraphRec(BasicBlock *block, Inst *scope_start) +{ + if (block->SetMarker(visited_)) { + if (GetInfo(block).last_scope_start != scope_start) { + // It's impossible to have scope start in one path to block and to have no scope in another path + ASSERT(GetInfo(block).last_scope_start != nullptr); + ASSERT(scope_start != nullptr); + // Different scope starts for different execution paths are possible + // TODO(aefremov): find insts with always equal scopes somehow + InvalidateScopesInSubgraph(block); + } + return; + } + GetInfo(block).last_scope_start = scope_start; + for (auto *inst : block->Insts()) { + if (IsScopeEnd(inst)) { + ASSERT(scope_start != nullptr); + scope_start = nullptr; + } else if (IsScopeStart(inst)) { + ASSERT(scope_start == nullptr); + scope_start = inst; + } else if (IsForbiddenInst(inst)) { + ASSERT(scope_start == nullptr); + } else if (IsInteropIntrinsic(inst)) { + ASSERT(scope_start != nullptr); + scope_for_inst_[inst] = scope_start; + } + } + if (block->IsEndBlock()) { + ASSERT(scope_start == nullptr); + } + for (auto *succ : block->GetSuccsBlocks()) { + CheckGraphRec(succ, scope_start); + } +} + +void MergeHandleScopes::CheckGraphAndFindScopes() +{ + auto visited_holder = MarkerHolder(GetGraph()); + visited_ = visited_holder.GetMarker(); + auto invalidated_holder = MarkerHolder(GetGraph()); + scope_start_invalidated_ = invalidated_holder.GetMarker(); + CheckGraphRec(GetGraph()->GetStartBlock(), nullptr); +} + +void MergeHandleScopes::MarkRequireRegMap(Inst *inst) +{ + SaveStateInst *save_state = nullptr; + if (inst->IsSaveState()) { + save_state = static_cast(inst); + } else if (inst->RequireState()) { + save_state = inst->GetSaveState(); + } + while (save_state != nullptr && save_state->SetMarker(require_reg_map_) && save_state->GetCallerInst() != nullptr) { + save_state = save_state->GetCallerInst()->GetSaveState(); + } +} + +void MergeHandleScopes::TryRemoveUnwrapAndWrap(Inst *inst, Inst *input) +{ + if (scope_for_inst_.at(inst) != scope_for_inst_.at(input)) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Scopes don't match, skip: wrap " << *inst << "\nwith unwrap input " << *input; + return; + } + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << "Remove wrap " << *inst << "\nwith unwrap input " << *input; + auto *new_input = input->GetInput(0).GetInst(); + // We don't extend scopes out of basic blocks in OSR mode + ASSERT(!GetGraph()->IsOsrMode() || inst->GetBasicBlock() == new_input->GetBasicBlock()); + ASSERT(new_input->GetType() == DataType::POINTER); + ASSERT(inst->GetType() == DataType::POINTER); + inst->ReplaceUsers(new_input); + inst->GetBasicBlock()->RemoveInst(inst); + // If `input` (unwrap from local to JSValue or ets primitve) has SaveState users which require regmap, + // we will not delete the unwrap intrinsic + // TODO(aefremov): support unwrap (local => JSValue/primitive) during deoptimization + is_applied_ = true; +} + +void MergeHandleScopes::TryRemoveUnwrapToJSValue(Inst *inst) +{ + auto common_id = RuntimeInterface::IntrinsicId::INVALID; + DataType::Type user_type; + for (auto &user : inst->GetUsers()) { + auto *user_inst = user.GetInst(); + if (user_inst->IsSaveState()) { + // see the previous comment + if (user_inst->IsMarked(require_reg_map_)) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "User " << *user_inst << "\nof inst " << *inst << " requires regmap, skip"; + return; + } + continue; + } + if (!user_inst->IsIntrinsic()) { + return; + } + if (HasOsrEntryBetween(inst, user_inst)) { + return; + } + auto current_id = user_inst->CastToIntrinsic()->GetIntrinsicId(); + if (common_id == RuntimeInterface::IntrinsicId::INVALID) { + user_type = user_inst->GetType(); + common_id = current_id; + } else if (current_id != common_id) { + return; + } + } + auto new_id = GetUnwrapIntrinsicId(common_id); + if (new_id == RuntimeInterface::IntrinsicId::INVALID) { + // includes the case when common_id is INVALID + return; + } + inst->CastToIntrinsic()->SetIntrinsicId(new_id); + inst->SetOpcode(Opcode::Intrinsic); // reset flags to default for intrinsic inst + AdjustFlags(new_id, inst); + inst->SetType(user_type); + for (auto user_it = inst->GetUsers().begin(); user_it != inst->GetUsers().end();) { + auto user_inst = user_it->GetInst(); + if (user_inst->IsIntrinsic() && user_inst->CastToIntrinsic()->GetIntrinsicId() == common_id) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Replace cast from JSValue " << *user_inst << "\nwith direct unwrap " << *inst; + user_inst->ReplaceUsers(inst); + user_inst->GetBasicBlock()->RemoveInst(user_inst); + user_it = inst->GetUsers().begin(); + } else { + ++user_it; + } + } + is_applied_ = true; +} + +void MergeHandleScopes::TryRemoveIntrinsic(Inst *inst) +{ + if (!inst->IsIntrinsic()) { + return; + } + auto *input = inst->GetInput(0).GetInst(); + auto intrinsic_id = inst->CastToIntrinsic()->GetIntrinsicId(); + if (input->IsIntrinsic() && IsWrapIntrinsicId(intrinsic_id) && + IsUnwrapIntrinsicId(input->CastToIntrinsic()->GetIntrinsicId())) { + TryRemoveUnwrapAndWrap(inst, input); + } else if (intrinsic_id == RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_JS_VALUE) { + TryRemoveUnwrapToJSValue(inst); + } +} + +void MergeHandleScopes::EliminateCastPairs() +{ + auto require_reg_map_holder = MarkerHolder(GetGraph()); + require_reg_map_ = require_reg_map_holder.GetMarker(); + auto &blocks_rpo = GetGraph()->GetBlocksRPO(); + for (auto it = blocks_rpo.rbegin(); it != blocks_rpo.rend(); ++it) { + auto *block = *it; + for (auto inst : block->InstsSafeReverse()) { + if (inst->RequireRegMap()) { + MarkRequireRegMap(inst); + } + TryRemoveIntrinsic(inst); + } + } +} + +void MergeHandleScopes::DomTreeDfs(BasicBlock *block) +{ + // bb1->IsDominate(bb2) iff bb1.dom_tree_in <= bb2.dom_tree_in < bb1.dom_tree_out + GetInfo(block).dom_tree_in = dom_tree_num_++; + for (auto *dom : block->GetDominatedBlocks()) { + DomTreeDfs(dom); + } + GetInfo(block).dom_tree_out = dom_tree_num_; +} + +void MergeHandleScopes::MarkPartiallyAnticipated(BasicBlock *block, BasicBlock *stop_block) +{ + if (block->SetMarker(inst_anticipated_)) { + return; + } + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Mark block " << block->GetId() << " where current inst is partially anticipated"; + GetInfo(block).subgraph_min_num = DFS_NOT_VISITED; + if (block == stop_block) { + return; + } + ASSERT(!block->IsStartBlock()); + for (auto *pred : block->GetPredsBlocks()) { + MarkPartiallyAnticipated(pred, stop_block); + } +} + +void MergeHandleScopes::CalculateDownSafe(BasicBlock *block) +{ + auto &info = GetInfo(block); + if (info.subgraph_min_num != DFS_NOT_VISITED) { + return; + } + if (!block->IsMarked(inst_anticipated_)) { + info.max_chain = 0; + info.subgraph_min_num = DFS_INVALIDATED; + return; + } + ASSERT(info.dom_tree_in >= 0); + info.subgraph_min_num = info.subgraph_max_num = info.dom_tree_in; + int32_t succ_max_chain = 0; + for (auto *succ : block->GetSuccsBlocks()) { + CalculateDownSafe(succ); + succ_max_chain = std::max(succ_max_chain, GetInfo(succ).max_chain); + if (!block->IsMarked(elimination_candidate_)) { + info.subgraph_min_num = std::min(info.subgraph_min_num, GetInfo(succ).subgraph_min_num); + info.subgraph_max_num = std::max(info.subgraph_max_num, GetInfo(succ).subgraph_max_num); + } + } + info.max_chain += succ_max_chain; +} + +// If *new_inst is SaveState, we move the first dominated inst we want to replace after insert_after and set *new_inst +// to it Otherwise just replace dominated inst with *new_inst +void MergeHandleScopes::HoistAndEliminateRec(BasicBlock *block, const BlockInfo &start_info, Inst **new_inst, + Inst *insert_after) +{ + if (block->ResetMarker(elimination_candidate_)) { + for (auto *inst : block->InstsSafe()) { + if (inst->IsMarked(elimination_candidate_)) { + ASSERT((*new_inst)->IsDominate(inst)); + if ((*new_inst)->GetOpcode() == Opcode::SaveState) { + [[maybe_unused]] auto old_next_inst = inst->GetNext(); + block->EraseInst(inst); + insert_after->InsertAfter(inst); + inst->SetSaveState(*new_inst); + *new_inst = inst; + if (inst->IsReferenceOrAny()) { + // SSB is needed for conversion from local to JSValue or other ref type + ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), inst, old_next_inst, nullptr, block); + } + is_applied_ = true; + } else if (inst != *new_inst) { + ASSERT(inst->GetOpcode() == (*new_inst)->GetOpcode()); + inst->ReplaceUsers(*new_inst); + if (inst->IsReferenceOrAny()) { + ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), *new_inst, inst); + } + is_applied_ = true; + } + } + } + } + for (auto *succ : block->GetSuccsBlocks()) { + if (!succ->ResetMarker(inst_anticipated_)) { + continue; + } + // Fast IsDominate check + if (start_info.dom_tree_in <= GetInfo(succ).dom_tree_in && + GetInfo(succ).dom_tree_in < start_info.dom_tree_out) { + HoistAndEliminateRec(succ, start_info, new_inst, insert_after); + } else { + blocks_to_process_.push_back(succ); + } + } +} + +bool MergeHandleScopes::CanHoistTo(BasicBlock *block) +{ + ASSERT(!block->IsMarked(elimination_candidate_)); + auto &info = GetInfo(block); + bool dominates_subgraph = info.dom_tree_in <= info.subgraph_min_num && info.subgraph_max_num < info.dom_tree_out; + if (!dominates_subgraph) { + return false; + } + auto hoist_value = info.max_chain - (block->GetLoop()->GetDepth() + 1); + if (hoist_value <= 0) { + return false; + } + for (auto *dom : block->GetDominatedBlocks()) { + auto dom_hoist_value = GetInfo(dom).max_chain - (dom->GetLoop()->GetDepth() + 1); + if (dom_hoist_value < hoist_value) { + // TODO(aefremov): avoid hoisting to the head of an inner loop + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << " Block " << block->GetId() << " is candidate for hoisting"; + return true; + } + } + return false; +} + +void MergeHandleScopes::HoistAndEliminate(BasicBlock *start_block, Inst *boundary_inst) +{ + ASSERT(boundary_inst->GetBasicBlock() == start_block); + blocks_to_process_.clear(); + blocks_to_process_.push_back(start_block); + ASSERT(start_block->ResetMarker(inst_anticipated_)); + while (!blocks_to_process_.empty()) { + auto *block = blocks_to_process_.back(); + blocks_to_process_.pop_back(); + ASSERT(!block->IsMarked(inst_anticipated_)); + auto &info = GetInfo(block); + ASSERT(info.subgraph_min_num != DFS_NOT_VISITED); + if (info.subgraph_min_num == DFS_INVALIDATED) { + continue; + } + Inst *new_inst = nullptr; + Inst *insert_after = nullptr; + if (block->IsMarked(elimination_candidate_)) { + for (auto inst : block->Insts()) { + if (inst->IsMarked(elimination_candidate_)) { + new_inst = inst; + break; + } + } + ASSERT(new_inst != nullptr); + } else if (CanHoistTo(block)) { + for (auto inst : block->InstsReverse()) { + if (inst == boundary_inst) { + auto prev = inst->GetPrev(); + if (prev != nullptr && prev->GetOpcode() == Opcode::SaveState && !inst->IsMovableObject()) { + new_inst = prev; + insert_after = inst; + } + break; + } + if (inst->GetOpcode() == Opcode::SaveState) { + new_inst = inst; + insert_after = inst; + break; + } + } + if (new_inst == nullptr) { + info.subgraph_min_num = DFS_INVALIDATED; + continue; + } + } + if (new_inst != nullptr) { + HoistAndEliminateRec(block, GetInfo(block), &new_inst, insert_after); + } + info.subgraph_min_num = DFS_INVALIDATED; + } +} + +void MergeHandleScopes::DoRedundancyElimination(Inst *input, Inst *scope_start, InstVector &insts) +{ + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << "Process group of intrinsics with identical inputs and scope start: " << *scope_start + << "\ninput: " << *input; + auto *boundary_inst = input->IsDominate(scope_start) ? scope_start : input; + auto *boundary = boundary_inst->GetBasicBlock(); + ASSERT(input->IsDominate(boundary_inst) && scope_start->IsDominate(boundary_inst)); + auto inst_anticipated_holder = MarkerHolder(GetGraph()); + inst_anticipated_ = inst_anticipated_holder.GetMarker(); + auto elimination_candidate_holder = MarkerHolder(GetGraph()); + elimination_candidate_ = elimination_candidate_holder.GetMarker(); + // Marking blocks where inst is partially anticipated is needed only to reduce number of processed blocks + for (auto *inst : insts) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << " Inst in this group: " << *inst; + auto *block = inst->GetBasicBlock(); + GetInfo(block).max_chain += block->GetLoop()->GetDepth() + 1; + inst->SetMarker(elimination_candidate_); + block->SetMarker(elimination_candidate_); + MarkPartiallyAnticipated(block, boundary); + } + CalculateDownSafe(boundary); + HoistAndEliminate(boundary, boundary_inst); +} + +void MergeHandleScopes::RedundancyElimination() +{ + DomTreeDfs(GetGraph()->GetStartBlock()); + auto processed_holder = MarkerHolder(GetGraph()); + auto processed = processed_holder.GetMarker(); + ArenaMap current_insts(GetGraph()->GetLocalAllocator()->Adapter()); + for (auto *block : GetGraph()->GetBlocksRPO()) { + for (auto *inst : block->InstsSafe()) { + if (inst->IsMarked(processed) || !IsConvertIntrinsic(inst)) { + continue; + } + auto id = inst->CastToIntrinsic()->GetIntrinsicId(); + current_insts.clear(); + auto *input = inst->GetInput(0).GetInst(); + const auto &adapter = GetGraph()->GetLocalAllocator()->Adapter(); + for (auto &user : input->GetUsers()) { + auto *sibling = user.GetInst(); + if (!sibling->IsIntrinsic() || sibling->CastToIntrinsic()->GetIntrinsicId() != id) { + continue; + } + sibling->SetMarker(processed); + auto scope_start = scope_for_inst_[sibling]; + if (scope_start == nullptr) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << "Scope for inst is not unique, skip: " << *inst; + continue; + } + auto it = current_insts.try_emplace(scope_start, adapter).first; + it->second.push_back(sibling); + } + for (auto &[scope, insts] : current_insts) { + DoRedundancyElimination(input, scope, insts); + } + } + } +} + +bool MergeHandleScopes::RunImpl() +{ + Logger::InitializeStdLogging(Logger::Level::DEBUG, Logger::ComponentMaskFromString("compiler")); + CompilerLogger::EnableComponent(CompilerLoggerComponents::MERGE_HANDLE_SCOPES); + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << " Phase I: merge scopes inside basic blocks"; + for (auto *block : GetGraph()->GetVectorBlocks()) { + if (block != nullptr) { + MergeScopesInsideBlock(block); + } + } + if (!has_scopes_) { + return false; + } + if (!GetGraph()->IsOsrMode()) { + GetGraph()->RunPass(); + GetGraph()->RunPass(); + MarkForbiddenLoops(); + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) + << " Phase II: remove inter-scope regions to merge neighbouring scopes"; + MergeInterScopeRegions(); + if (!object_limit_hit_) { + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << " Phase III: hoist scope starts"; + HoistScopeStarts(); + } + } + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << " Phase IV: Check graph and find scope starts for interop intrinsics"; + // NB: we assume that each scope is residing inside one block before the pass, and that there are no nested scopes + CheckGraphAndFindScopes(); + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << " Phase V: Peepholes for interop intrinsics"; + EliminateCastPairs(); + COMPILER_LOG(DEBUG, MERGE_HANDLE_SCOPES) << " Phase VI: Redundancy elimination for wrap intrinsics"; + RedundancyElimination(); + return IsApplied(); +} + +} // namespace panda::compiler diff --git a/plugins/ets/compiler/optimizer/interop_js/merge_handle_scopes.h b/plugins/ets/compiler/optimizer/interop_js/merge_handle_scopes.h new file mode 100644 index 0000000000000000000000000000000000000000..d4bb867dbcf177fe4391df0d1471553cc986cd52 --- /dev/null +++ b/plugins/ets/compiler/optimizer/interop_js/merge_handle_scopes.h @@ -0,0 +1,173 @@ +/** + * Copyright (c) 2021-2022 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 PLUGINS_ETS_COMPILER_OPTIMIZER_INTEROP_JS_MERGE_HANDLE_SCOPES_H_ +#define PLUGINS_ETS_COMPILER_OPTIMIZER_INTEROP_JS_MERGE_HANDLE_SCOPES_H_ + +#include "optimizer/ir/graph.h" +#include "optimizer/pass.h" + +#include "compiler_logger.h" +#include "optimizer/ir/analysis.h" +#include "optimizer/ir/graph_visitor.h" + +namespace panda::compiler { +// NOLINTNEXTLINE(fuchsia-multiple-inheritance) +class MergeHandleScopes : public Optimization, public GraphVisitor { + using Optimization::Optimization; + +public: + explicit MergeHandleScopes(Graph *graph) + : Optimization(graph), + scope_object_limit_(OPTIONS.GetCompilerLocalScopeObjectLimit()), + forbidden_loops_(GetGraph()->GetLocalAllocator()->Adapter()), + block_info_(GetGraph()->GetVectorBlocks().size(), GetGraph()->GetLocalAllocator()->Adapter()), + components_(GetGraph()->GetLocalAllocator()->Adapter()), + current_component_blocks_(GetGraph()->GetLocalAllocator()->Adapter()), + scope_for_inst_(GetGraph()->GetLocalAllocator()->Adapter()), + blocks_to_process_(GetGraph()->GetLocalAllocator()->Adapter()) + { + } + + NO_MOVE_SEMANTIC(MergeHandleScopes); + NO_COPY_SEMANTIC(MergeHandleScopes); + ~MergeHandleScopes() override = default; + + bool RunImpl() override; + + bool IsEnable() const override + { + return OPTIONS.IsCompilerEnableFastInterop() && OPTIONS.IsCompilerMergeHandleScopes(); + } + + const char *GetPassName() const override + { + return "MergeHandleScopes"; + } + + bool IsApplied() const + { + return is_applied_; + } + + const ArenaVector &GetBlocksToVisit() const override + { + return GetGraph()->GetBlocksRPO(); + } + +#include "optimizer/ir/visitor.inc" + +private: + struct BlockInfo { + int32_t dfs_num_in {DFS_NOT_VISITED}; + int32_t dfs_num_out {DFS_NOT_VISITED}; + int32_t dom_tree_in {DFS_NOT_VISITED}; + int32_t dom_tree_out {DFS_NOT_VISITED}; + int32_t subgraph_min_num {DFS_NOT_VISITED}; + int32_t subgraph_max_num {DFS_NOT_VISITED}; + std::array block_component {DFS_NOT_VISITED, DFS_NOT_VISITED}; + int32_t max_chain {}; + Inst *last_scope_start {}; + }; + + struct Component { + int32_t parent {-1}; + size_t object_count {}; + size_t last_used {}; + bool is_forbidden {}; + }; + + // Mechanism is similar to markers + size_t TryInclude(Component &comp, size_t used_number) + { + if (comp.last_used == used_number + 1) { + return 0; + } + comp.last_used = used_number + 1; + return comp.object_count; + } + + BlockInfo &GetInfo(BasicBlock *block); + void MergeScopesInsideBlock(BasicBlock *block); + void MarkOuterLoopsForbidden(Loop *loop); + void MarkForbiddenLoops(); + bool IsForbiddenLoopEntry(BasicBlock *block); + int32_t GetParentComponent(int32_t component); + void MergeComponents(int32_t first, int32_t second); + template + void BlockBoundaryDfs(BasicBlock *block); + void MergeCurrentComponentWithNeighbours(); + template + void FindComponentAndTryMerge(BasicBlock *block); + bool MergeScopesInBlock(BasicBlock *block, bool include_start, bool include_end); + void MergeInterScopeRegions(); + void DfsNumbering(BasicBlock *block); + void CalculateReachabilityRec(BasicBlock *block); + template + void DoDfs(); + void RemoveReachableScopeStarts(BasicBlock *block, BasicBlock *new_start_block); + void HoistScopeStarts(); + + void InvalidateScopesInSubgraph(BasicBlock *block); + void CheckGraphRec(BasicBlock *block, Inst *scope_start); + void CheckGraphAndFindScopes(); + + void MarkRequireRegMap(Inst *inst); + void TryRemoveUnwrapAndWrap(Inst *inst, Inst *input); + void TryRemoveUnwrapToJSValue(Inst *inst); + void TryRemoveIntrinsic(Inst *inst); + void EliminateCastPairs(); + + void DomTreeDfs(BasicBlock *block); + void MarkPartiallyAnticipated(BasicBlock *block, BasicBlock *stop_block); + void CalculateDownSafe(BasicBlock *block); + void HoistAndEliminateRec(BasicBlock *block, const BlockInfo &start_info, Inst **new_inst, Inst *insert_after); + bool CanHoistTo(BasicBlock *block); + void HoistAndEliminate(BasicBlock *start_block, Inst *boundary_inst); + void DoRedundancyElimination(Inst *input, Inst *scope_start, InstVector &insts); + void RedundancyElimination(); + +private: + static constexpr uint64_t MAX_LOOP_ITERATIONS = 10; + static constexpr int32_t DFS_NOT_VISITED = -1; + static constexpr int32_t DFS_INVALIDATED = -2; + + uint32_t scope_object_limit_; + Marker start_dfs_ {}; + Marker can_hoist_to_ {}; + Marker visited_ {}; + Marker inst_anticipated_ {}; + Marker scope_start_invalidated_ {}; + Marker elimination_candidate_ {}; + Marker require_reg_map_ {}; + bool has_scopes_ {false}; + bool is_applied_ {false}; + int32_t dfs_num_ {}; + int32_t dom_tree_num_ {}; + int32_t current_component_ {}; + size_t objects_in_scope_after_merge_ {}; + bool can_merge_ {}; + bool object_limit_hit_ {false}; + ArenaUnorderedSet forbidden_loops_; + ArenaVector block_info_; + ArenaVector components_; + ArenaVector current_component_blocks_; + ArenaUnorderedMap scope_for_inst_; + ArenaVector blocks_to_process_; + SaveStateBridgesBuilder ssb_; +}; +} // namespace panda::compiler + +#endif // PLUGINS_ETS_COMPILER_OPTIMIZER_INTEROP_JS_MERGE_HANDLE_SCOPES_H_ diff --git a/plugins/ets/compiler/optimizer/ir_builder/js_interop/js_interop_inst_builder.cpp b/plugins/ets/compiler/optimizer/ir_builder/js_interop/js_interop_inst_builder.cpp index abfd33a52b485b931a00cb80eb711c7ed8350bed..c439d350b2b2b330834c4a9278d7bd00d520d758 100644 --- a/plugins/ets/compiler/optimizer/ir_builder/js_interop/js_interop_inst_builder.cpp +++ b/plugins/ets/compiler/optimizer/ir_builder/js_interop/js_interop_inst_builder.cpp @@ -27,7 +27,7 @@ struct IntrinsicBuilder { template static IntrinsicInst *Build(InstBuilder *ib, size_t pc, const ARGS &...inputs) { - static_assert(sizeof...(inputs) == N); + static_assert(sizeof...(inputs) == N + 1); return ib->BuildInteropIntrinsic(pc, ID, RET_TYPE, {PARAM_TYPES...}, {inputs...}); } }; @@ -54,7 +54,7 @@ using IntrinsicCompilerJSCallCheck = IntrinsicBuilder IntrinsicInst *InstBuilder::BuildInteropIntrinsic(size_t pc, RuntimeInterface::IntrinsicId id, DataType::Type ret_type, const std::array &types, - const std::array &inputs) + const std::array &inputs) { auto save_state = CreateSaveState(Opcode::SaveState, pc); AddInstruction(save_state); @@ -64,7 +64,7 @@ IntrinsicInst *InstBuilder::BuildInteropIntrinsic(size_t pc, RuntimeInterface::I intrinsic->AppendInput(inputs[i]); intrinsic->AddInputType(types[i]); } - intrinsic->AppendInput(save_state); + intrinsic->AppendInput(inputs[N]); // SaveState input intrinsic->AddInputType(DataType::NO_TYPE); AddInstruction(intrinsic); return intrinsic; @@ -72,18 +72,19 @@ IntrinsicInst *InstBuilder::BuildInteropIntrinsic(size_t pc, RuntimeInterface::I std::pair InstBuilder::BuildResolveInteropCallIntrinsic(RuntimeInterface::InteropCallKind call_kind, size_t pc, RuntimeInterface::MethodPtr method, - Inst *arg0, Inst *arg1) + Inst *arg0, Inst *arg1, + SaveStateInst *save_state) { IntrinsicInst *js_this = nullptr; IntrinsicInst *js_fn = nullptr; if (call_kind == RuntimeInterface::InteropCallKind::CALL_BY_VALUE) { - js_fn = IntrinsicCompilerConvertJSValueToLocal::Build(this, pc, arg0); - js_this = IntrinsicCompilerConvertJSValueToLocal::Build(this, pc, arg1); + js_fn = IntrinsicCompilerConvertJSValueToLocal::Build(this, pc, arg0, save_state); + js_this = IntrinsicCompilerConvertJSValueToLocal::Build(this, pc, arg1, save_state); } else { - auto js_val = IntrinsicCompilerConvertJSValueToLocal::Build(this, pc, arg0); + auto js_val = IntrinsicCompilerConvertJSValueToLocal::Build(this, pc, arg0, save_state); auto str_id = arg1->CastToLoadString()->GetTypeId(); - js_this = IntrinsicCompilerResolveQualifiedJSCall::Build(this, pc, js_val, arg1); + js_this = IntrinsicCompilerResolveQualifiedJSCall::Build(this, pc, js_val, arg1, save_state); Inst *prop_name = nullptr; if (!GetGraph()->IsAotMode()) { @@ -97,16 +98,17 @@ std::pair InstBuilder::BuildResolveInteropCallIntrinsic(RuntimeI } AddInstruction(prop_name); - js_fn = IntrinsicCompilerGetJSNamedProperty::Build(this, pc, js_this, prop_name); + js_fn = IntrinsicCompilerGetJSNamedProperty::Build(this, pc, js_this, prop_name, save_state); } return {js_this, js_fn}; } void InstBuilder::BuildReturnValueConvertInteropIntrinsic(RuntimeInterface::InteropCallKind call_kind, size_t pc, - RuntimeInterface::MethodPtr method, Inst *js_call) + RuntimeInterface::MethodPtr method, Inst *js_call, + SaveStateInst *save_state) { if (call_kind == RuntimeInterface::InteropCallKind::NEW_INSTANCE) { - auto ret = IntrinsicCompilerConvertLocalToJSValue::Build(this, pc, js_call); + auto ret = IntrinsicCompilerConvertLocalToJSValue::Build(this, pc, js_call, save_state); UpdateDefinitionAcc(ret); } else { auto ret_intrinsic_id = GetGraph()->GetRuntime()->GetInfoForInteropCallRetValueConversion(method); @@ -114,14 +116,15 @@ void InstBuilder::BuildReturnValueConvertInteropIntrinsic(RuntimeInterface::Inte Inst *ret = nullptr; auto [id, ret_type] = ret_intrinsic_id.value(); if (id == RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_REF_TYPE) { - auto save_state = CreateSaveState(Opcode::SaveState, pc); - AddInstruction(save_state); auto load_class = BuildLoadClass(GetRuntime()->GetMethodReturnTypeId(method), pc, save_state); AddInstruction(load_class); + // LoadClass returns ref, create a new SaveState + save_state = CreateSaveState(Opcode::SaveState, pc); + AddInstruction(save_state); ret = BuildInteropIntrinsic<2>(pc, id, ret_type, {DataType::REFERENCE, DataType::POINTER}, - {load_class, js_call}); + {load_class, js_call, save_state}); } else { - ret = BuildInteropIntrinsic<1>(pc, id, ret_type, {DataType::POINTER}, {js_call}); + ret = BuildInteropIntrinsic<1>(pc, id, ret_type, {DataType::POINTER}, {js_call, save_state}); } UpdateDefinitionAcc(ret); } @@ -132,15 +135,17 @@ void InstBuilder::BuildInteropCall(const BytecodeInstruction *bc_inst, RuntimeIn RuntimeInterface::MethodPtr method, bool is_range, bool acc_read) { auto pc = GetPc(bc_inst->GetAddress()); + auto save_state = CreateSaveState(Opcode::SaveState, pc); + AddInstruction(save_state); // Create LOCAL scope - IntrinsicCompilerCreateLocalScope::Build(this, pc); + IntrinsicCompilerCreateLocalScope::Build(this, pc, save_state); // Resolve call target auto [js_this, js_fn] = BuildResolveInteropCallIntrinsic(call_kind, pc, method, GetArgDefinition(bc_inst, 0, acc_read, is_range), - GetArgDefinition(bc_inst, 1, acc_read, is_range)); + GetArgDefinition(bc_inst, 1, acc_read, is_range), save_state); // js call check - auto js_call_check = IntrinsicCompilerJSCallCheck::Build(this, pc, js_fn); + auto js_call_check = IntrinsicCompilerJSCallCheck::Build(this, pc, js_fn, save_state); // js call RuntimeInterface::IntrinsicId call_id = RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_JS_CALL_FUNCTION; @@ -173,25 +178,26 @@ void InstBuilder::BuildInteropCall(const BytecodeInstruction *bc_inst, RuntimeIn Inst *arg = nullptr; if (type != DataType::NO_TYPE) { arg = BuildInteropIntrinsic<1>(pc, intrinsic_id, DataType::POINTER, {type}, - {GetArgDefinition(bc_inst, arg_idx + 2, acc_read, is_range)}); + {GetArgDefinition(bc_inst, arg_idx + 2, acc_read, is_range), save_state}); } else { - arg = BuildInteropIntrinsic<0>(pc, intrinsic_id, DataType::POINTER, {}, {}); + arg = BuildInteropIntrinsic<0>(pc, intrinsic_id, DataType::POINTER, {}, {save_state}); } js_call->AppendInput(arg); js_call->AddInputType(DataType::POINTER); arg_idx++; } - auto save_state = CreateSaveState(Opcode::SaveState, pc); - AddInstruction(save_state); js_call->AppendInput(save_state); js_call->AddInputType(DataType::NO_TYPE); AddInstruction(js_call); // Convert ret value - BuildReturnValueConvertInteropIntrinsic(call_kind, pc, method, js_call); + BuildReturnValueConvertInteropIntrinsic(call_kind, pc, method, js_call, save_state); + // Create new a SaveState because instruction with ref value was built + save_state = CreateSaveState(Opcode::SaveState, pc); + AddInstruction(save_state); // Destroy handle scope - IntrinsicCompilerDestroyLocalScope::Build(this, pc); + IntrinsicCompilerDestroyLocalScope::Build(this, pc, save_state); } bool InstBuilder::TryBuildInteropCall(const BytecodeInstruction *bc_inst, bool is_range, bool acc_read) diff --git a/plugins/ets/compiler/optimizer/ir_builder/js_interop/js_interop_inst_builder.h b/plugins/ets/compiler/optimizer/ir_builder/js_interop/js_interop_inst_builder.h index 5f211e01d6fa22b6710c9a28a10eb3720226d9c5..2a7e82564af09361929a34d1ca065cc61bfe591a 100644 --- a/plugins/ets/compiler/optimizer/ir_builder/js_interop/js_interop_inst_builder.h +++ b/plugins/ets/compiler/optimizer/ir_builder/js_interop/js_interop_inst_builder.h @@ -18,11 +18,14 @@ friend struct IntrinsicBuilder; template IntrinsicInst *BuildInteropIntrinsic(size_t pc, RuntimeInterface::IntrinsicId id, DataType::Type ret_type, - const std::array &types, const std::array &inputs); + const std::array &types, + const std::array &inputs); std::pair BuildResolveInteropCallIntrinsic(RuntimeInterface::InteropCallKind call_kind, size_t pc, - RuntimeInterface::MethodPtr method, Inst *arg0, Inst *arg1); + RuntimeInterface::MethodPtr method, Inst *arg0, Inst *arg1, + SaveStateInst *save_state); void BuildReturnValueConvertInteropIntrinsic(RuntimeInterface::InteropCallKind call_kind, size_t pc, - RuntimeInterface::MethodPtr method, Inst *js_call); + RuntimeInterface::MethodPtr method, Inst *js_call, + SaveStateInst *save_state); void BuildInteropCall(const BytecodeInstruction *bc_inst, RuntimeInterface::InteropCallKind call_kind, RuntimeInterface::MethodPtr method, bool is_range, bool acc_read); bool TryBuildInteropCall(const BytecodeInstruction *bc_inst, bool is_range, bool acc_read); diff --git a/plugins/ets/compiler/plugin_files/plugin_create_pipeline_includes.h b/plugins/ets/compiler/plugin_files/plugin_create_pipeline_includes.h new file mode 100644 index 0000000000000000000000000000000000000000..9f2bc298c7cb3f6b9dfa8c7882175e8d9003fa83 --- /dev/null +++ b/plugins/ets/compiler/plugin_files/plugin_create_pipeline_includes.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2022 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 "plugins/ets/compiler/optimizer/interop_js/merge_handle_scopes.h" diff --git a/plugins/ets/runtime/interop_js/intrinsics/std_js_jsruntime.yaml b/plugins/ets/runtime/interop_js/intrinsics/std_js_jsruntime.yaml index 0ea011027f477908b183e93ca5a76058833f1b7e..c1fa28d5116e4f6d783744ebf609de5743ad272c 100644 --- a/plugins/ets/runtime/interop_js/intrinsics/std_js_jsruntime.yaml +++ b/plugins/ets/runtime/interop_js/intrinsics/std_js_jsruntime.yaml @@ -399,7 +399,7 @@ intrinsics: ret: ptr args: [] impl: panda::ets::interop::js::intrinsics::CompilerConvertVoidToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertU1ToLocal space: ets @@ -408,7 +408,7 @@ intrinsics: ret: ptr args: [u1] impl: panda::ets::interop::js::intrinsics::CompilerConvertU1ToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertU8ToLocal space: ets @@ -417,7 +417,7 @@ intrinsics: ret: ptr args: [u8] impl: panda::ets::interop::js::intrinsics::CompilerConvertU8ToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertI8ToLocal space: ets @@ -426,7 +426,7 @@ intrinsics: ret: ptr args: [i8] impl: panda::ets::interop::js::intrinsics::CompilerConvertI8ToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertU16ToLocal space: ets @@ -435,7 +435,7 @@ intrinsics: ret: ptr args: [u16] impl: panda::ets::interop::js::intrinsics::CompilerConvertU16ToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertI16ToLocal space: ets @@ -444,7 +444,7 @@ intrinsics: ret: ptr args: [i16] impl: panda::ets::interop::js::intrinsics::CompilerConvertI16ToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertU32ToLocal space: ets @@ -453,7 +453,7 @@ intrinsics: ret: ptr args: [u32] impl: panda::ets::interop::js::intrinsics::CompilerConvertU32ToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertI32ToLocal space: ets @@ -462,7 +462,7 @@ intrinsics: ret: ptr args: [i32] impl: panda::ets::interop::js::intrinsics::CompilerConvertI32ToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertU64ToLocal space: ets @@ -471,7 +471,7 @@ intrinsics: ret: ptr args: [u64] impl: panda::ets::interop::js::intrinsics::CompilerConvertU64ToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertI64ToLocal space: ets @@ -480,7 +480,7 @@ intrinsics: ret: ptr args: [i64] impl: panda::ets::interop::js::intrinsics::CompilerConvertI64ToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertF32ToLocal space: ets @@ -489,7 +489,7 @@ intrinsics: ret: ptr args: [f32] impl: panda::ets::interop::js::intrinsics::CompilerConvertF32ToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertF64ToLocal space: ets @@ -498,7 +498,7 @@ intrinsics: ret: ptr args: [f64] impl: panda::ets::interop::js::intrinsics::CompilerConvertF64ToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertRefTypeToLocal space: ets @@ -507,7 +507,7 @@ intrinsics: ret: ptr args: [std.core.Object] impl: panda::ets::interop::js::intrinsics::CompilerConvertRefTypeToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertJSValueToLocal space: ets @@ -516,7 +516,7 @@ intrinsics: ret: ptr args: [std.interop.js.JSValue] impl: panda::ets::interop::js::intrinsics::CompilerConvertJSValueToLocalIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] ######################################################## # Internal intrinsics, convert napi_value to ets value # @@ -627,7 +627,7 @@ intrinsics: ret: std.interop.js.JSValue args: [ptr] impl: panda::ets::interop::js::intrinsics::CompilerConvertLocalToJSValueIntrinsic - clear_flags: [ ] + clear_flags: [ no_dce ] - name: CompilerConvertLocalToString space: ets diff --git a/plugins/ets/tests/interop_js/tests/CMakeLists.txt b/plugins/ets/tests/interop_js/tests/CMakeLists.txt index 19db6415f08da2406a71cad84261e11a7ff965c4..2e6e3aaa3eaee323890444e941bf47c6c9ffba36 100644 --- a/plugins/ets/tests/interop_js/tests/CMakeLists.txt +++ b/plugins/ets/tests/interop_js/tests/CMakeLists.txt @@ -157,3 +157,4 @@ add_subdirectory(generic_types) add_subdirectory(proxies) add_subdirectory(checked) add_subdirectory(perf) +add_subdirectory(compiler) diff --git a/plugins/ets/tests/interop_js/tests/checked/CMakeLists.txt b/plugins/ets/tests/interop_js/tests/checked/CMakeLists.txt index 62cf7f824ec0c11b990c446acd3bb36a6a7f68ca..0f37d18da2306808013feb3b082767e29cf2583f 100644 --- a/plugins/ets/tests/interop_js/tests/checked/CMakeLists.txt +++ b/plugins/ets/tests/interop_js/tests/checked/CMakeLists.txt @@ -15,6 +15,7 @@ panda_ets_interop_js_checked_test(FILE ${CMAKE_CURRENT_LIST_DIR}/example/strings panda_ets_interop_js_checked_test(FILE ${CMAKE_CURRENT_LIST_DIR}/js_call/js_call.ets) panda_ets_interop_js_checked_test(FILE ${CMAKE_CURRENT_LIST_DIR}/obj_call/obj_call.ets) panda_ets_interop_js_checked_test(FILE ${CMAKE_CURRENT_LIST_DIR}/../class_operations/class_operations_frontend.ets) +panda_ets_interop_js_checked_test(FILE ${CMAKE_CURRENT_LIST_DIR}/merge_handle_scopes/merge_handle_scopes.ets) set(INTEROP_TESTS_GENERATED_DIR "${PANDA_BINARY_ROOT}/plugins/ets/tests/ets_interop_js/generated") panda_ets_interop_js_checked_test(FILE ${INTEROP_TESTS_GENERATED_DIR}/number_subtypes_frontend.ets) diff --git a/plugins/ets/tests/interop_js/tests/checked/convert/convert.ets.erb b/plugins/ets/tests/interop_js/tests/checked/convert/convert.ets.erb index fd252da0b1afffe5b152fdc27284fcfba0d2fc18..1d07c5445a973632e17e798ce64f2c676907d05b 100644 --- a/plugins/ets/tests/interop_js/tests/checked/convert/convert.ets.erb +++ b/plugins/ets/tests/interop_js/tests/checked/convert/convert.ets.erb @@ -74,21 +74,28 @@ function test_deep_equal(): int { } % @type_infos.each do |type_info| -% unless type_info.skip_cast +% if type_info.unwrap //! CHECKER call_js_get_<%= type_info.name %> compare INT and JIT //! RUN entry: "test_js_get_<%= type_info.name %>", options: "--compiler-enable-jit=true --compiler-hotness-threshold=1 --no-async-jit true --compiler-regex=ETSGLOBAL::call_js_get_<%= type_info.name %>" //! EVENT /Compilation,ETSGLOBAL::call_js_get_<%= type_info.name %>,.*,COMPILED/ //! METHOD "call_js_get_<%= type_info.name %>" //! PASS_AFTER "IrBuilder" -//! INST_NOT /CallStatic.*jscall::invoke/ +//! INST_NOT /CallStatic.*jscall::invoke/ +//! INST "Intrinsic.CompilerJSCallFunction" +//! INST_NEXT "Intrinsic.CompilerConvertLocalToJSValue" % if ["String","boolean"].include?(type_info.name) //! INST_NEXT "Intrinsic.JSRuntimeGetValue<%= type_info.name.capitalize %>" % else //! INST_NEXT /CallStatic.*JSRuntime::getValue<%= type_info.name.capitalize %>/ +//! PASS_AFTER "Inline" +//! INST "Intrinsic.JSRuntimeGetValueDouble" % end +//! PASS_AFTER "MergeHandleScopes" +//! INST_NOT /Intrinsic.JSRuntimeGetValue.*/ +//! INST "Intrinsic.CompilerConvertLocalTo<%= type_info.unwrap.capitalize %>" -function call_js_get_<%= type_info.name %>(): <%= type_info.etsType %> { - return js_get_<%= type_info.name %>() as <%= type_info.etsType %>; +function call_js_get_<%= type_info.name %>(): <%= type_info.type %> { + return js_get_<%= type_info.name %>() as <%= type_info.type %>; } function test_js_get_<%= type_info.name %>(): int { @@ -99,16 +106,18 @@ function test_js_get_<%= type_info.name %>(): int { } return 0; } -% end +% end # if type_info.unwrap //! CHECKER call_js_get_jsvalue_<%= type_info.name %> compare INT and JIT //! RUN entry: "test_js_get_jsvalue_<%= type_info.name %>", options: "--compiler-enable-jit=true --compiler-hotness-threshold=1 --no-async-jit true --compiler-regex=ETSGLOBAL::call_js_get_jsvalue_<%= type_info.name %>" //! EVENT /Compilation,ETSGLOBAL::call_js_get_jsvalue_<%= type_info.name %>,.*,COMPILED/ //! METHOD "call_js_get_jsvalue_<%= type_info.name %>" //! PASS_AFTER "IrBuilder" -//! INST_NOT /CallStatic.*jscall::invoke/ -//! INST "Call" -//! INST "Intrinsic" +//! INST_NOT /CallStatic.*jscall::invoke/ +//! INST "Intrinsic.CompilerJSCallFunction" +//! INST_NEXT "Intrinsic.CompilerConvertLocalToJSValue" +//! PASS_AFTER "Codegen" +//! INST_NEXT "Intrinsic.CompilerConvertLocalToJSValue" # cannot be removed function call_js_get_jsvalue_<%= type_info.name %>(): JSValue { return js_get_<%= type_info.name %>(); @@ -128,20 +137,21 @@ function test_js_get_jsvalue_<%= type_info.name %>(): int { //! EVENT /Compilation,ETSGLOBAL::call_js_set_<%= type_info.name %>,.*,COMPILED/ //! METHOD "call_js_set_<%= type_info.name %>" //! PASS_AFTER "IrBuilder" -//! INST /0\..*Parameter/ -//! INST /1\..*Parameter/ -//! # 5 arguments, the 3rd and 4th are parameters of call_js_set_<%= type_info.name %> -//! INST_NOT /CallStatic.*jscall::invoke.* v.* v.* v0.* v1.* v.* ->/ +//! INST_NOT /CallStatic.*jscall::invoke/ +//! INST "Intrinsic.CompilerJSCallCheck" +//! INST_NEXT "Intrinsic.CompilerConvertI32ToLocal" +//! INST_NEXT "Intrinsic.CompilerConvert<%= type_info.wrap %>ToLocal" +//! INST_NEXT "Intrinsic.CompilerJSCallFunction" -function call_js_set_<%= type_info.name %>(index: int, value: <%= type_info.etsType %>): int { +function call_js_set_<%= type_info.name %>(index: int, value: <%= type_info.type %>): int { return js_set(index, value) as int; } function test_js_set_<%= type_info.name %>(): int { - if (call_js_set_<%= type_info.name %>(0, <%= type_info.value %> as <%= type_info.etsType %>) != 0) { + if (call_js_set_<%= type_info.name %>(0, <%= type_info.value %> as <%= type_info.type %>) != 0) { return -1; } - if (call_js_set_<%= type_info.name %>(1, <%= type_info.value %> as <%= type_info.etsType %>) != 1) { + if (call_js_set_<%= type_info.name %>(1, <%= type_info.value %> as <%= type_info.type %>) != 1) { return -2; } return js_check_set() as int; diff --git a/plugins/ets/tests/interop_js/tests/checked/convert/convert.rb b/plugins/ets/tests/interop_js/tests/checked/convert/convert.rb index 4d3c24e87f9894b08e1b63fcd1448794b5f9a566..3cf86bcfa64060ebef0a1cd93da1ce70c9b75f5b 100644 --- a/plugins/ets/tests/interop_js/tests/checked/convert/convert.rb +++ b/plugins/ets/tests/interop_js/tests/checked/convert/convert.rb @@ -12,23 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. -TypeInfo = Struct.new(:name, :value, :type, :skip_cast) do - def etsType - type || name +TypeInfo = Struct.new(:name, :value, :type, :unwrap, :wrap, keyword_init: true) do + def initialize(*) + super + self.wrap ||= 'RefType' + self.type ||= name end end @type_infos = [ - TypeInfo.new('String', '"abc"'), - TypeInfo.new('boolean', 'true'), - TypeInfo.new('byte', '127'), - TypeInfo.new('short', '32767'), - TypeInfo.new('int', '2147483647'), - TypeInfo.new('long', '9223372036854775'), - TypeInfo.new('char', '65535'), - TypeInfo.new('int_array', '[1, 2]', 'int[]', true), - TypeInfo.new('String_array', '["ab", "cd"]', 'String[]', true), - TypeInfo.new('const_object', 'const_obj', 'EtsClass', true), - TypeInfo.new('object', 'create_obj()', 'EtsClass | null', true), - TypeInfo.new('null_object', 'create_null_obj()', 'EtsClass | null', true), -] \ No newline at end of file + TypeInfo.new(name: 'String', value: '"abc"', unwrap: 'String'), + TypeInfo.new(name: 'boolean', value: 'true', unwrap: 'U1', wrap: 'U1'), + TypeInfo.new(name: 'byte', value: '127', unwrap: 'F64', wrap: 'I8'), + TypeInfo.new(name: 'short', value: '32767', unwrap: 'F64', wrap: 'I16'), + TypeInfo.new(name: 'int', value: '2147483647', unwrap: 'F64', wrap: 'I32'), # TODO(aefremov): replace get_value_double -> f64toi32 with get_value_int32 + TypeInfo.new(name: 'long', value: '9223372036854775', unwrap: 'F64', wrap: 'I64'), + TypeInfo.new(name: 'char', value: '65535', unwrap: 'F64', wrap: 'U16'), + TypeInfo.new(name: 'int_array', value: '[1, 2]', type: 'int[]'), + TypeInfo.new(name: 'String_array', value: '["ab", "cd"]', type: 'String[]'), + TypeInfo.new(name: 'const_object', value: 'const_obj', type: 'EtsClass'), + TypeInfo.new(name: 'object', value: 'create_obj()', type: 'EtsClass | null'), + TypeInfo.new(name: 'null_object', value: 'create_null_obj()', type: 'EtsClass | null'), +] diff --git a/plugins/ets/tests/interop_js/tests/checked/js_call/js_call.ets b/plugins/ets/tests/interop_js/tests/checked/js_call/js_call.ets index a454e35ef2d7858eafbf9b1b66ba5877df5dd1f3..9f83274a262f8d8cc4e28eeeeac440ea2d108772 100644 --- a/plugins/ets/tests/interop_js/tests/checked/js_call/js_call.ets +++ b/plugins/ets/tests/interop_js/tests/checked/js_call/js_call.ets @@ -47,6 +47,18 @@ import { cons, car, cdr, sum, TreeNode, DynObject, Empty, make_swappable, Static //! INST /CallStatic.*std.interop.js.JSRuntime::getValueInt/ //! INST "Intrinsic.JSRuntimeGetValueDouble" //! INST "Intrinsic.JSRuntimeGetValueString" +//! PASS_BEFORE "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 3 +//! INST_COUNT "Intrinsic.CompilerConvertLocalToJSValue", 3 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 3 +//! PASS_AFTER "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 1 +//! INST_NOT "Intrinsic.CompilerConvertLocalToJSValue" +//! INST "Intrinsic.CompilerConvertLocalToF64" +//! INST_NEXT /i32.*Cast f64/ +//! INST_NEXT "Intrinsic.CompilerConvertLocalToF64" +//! INST_NEXT "Intrinsic.CompilerConvertLocalToString" +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 1 //! CHECKER Several js function calls AOT (slow) //! RUN_PAOC options: "--compiler-regex=ETSGLOBAL::call_sequence --compiler-enable-fast-interop=false" @@ -80,6 +92,9 @@ import { cons, car, cdr, sum, TreeNode, DynObject, Empty, make_swappable, Static //! INST /CallStatic.*std.interop.js.JSRuntime::getValueInt/ //! INST "Intrinsic.JSRuntimeGetValueDouble" //! INST "Intrinsic.JSRuntimeGetValueString" +//! PASS_AFTER "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 2 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 2 //! RUN entry: "call_sequence" function call_sequence(): int { @@ -124,6 +139,22 @@ function call_sequence(): int { //! INST_COUNT "Intrinsic.CompilerConvertLocalToJSValue", 6 //! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 6 //! INST /CallStatic.*JSRuntime::getValueInt/ +//! PASS_BEFORE "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 6 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 6 +//! INST_COUNT "Intrinsic.CompilerConvertJSValueToLocal", 6 +//! PASS_AFTER "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 1 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 1 +//! PASS_AFTER "DeoptimizeElimination" +//! PASS_AFTER_NEXT "Cleanup" +//! INST_COUNT "Intrinsic.CompilerConvertJSValueToLocal", 1 +//! INST_NOT "Intrinsic.CompilerConvertLocalToJSValue" +//! INST_COUNT "Intrinsic.CompilerConvertI32ToLocal", 3 +//! # Intrinsic.CompilerConvertRefTypeToLocal is user of Intrinsic.JSRuntimeGetPropertyJSValue +//! # TODO(aefremov): find a way to remove +//! INST_COUNT "Intrinsic.CompilerConvertRefTypeToLocal", 1 +//! INST_COUNT "Intrinsic.CompilerConvertLocalToF64", 1 //! CHECKER Chain of js function calls AOT (slow) //! RUN_PAOC options: "--compiler-regex=ETSGLOBAL::call_chain --compiler-enable-fast-interop=false" @@ -150,6 +181,9 @@ function call_sequence(): int { //! INST_COUNT "Intrinsic.CompilerConvertLocalToJSValue", 6 //! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 6 //! INST /CallStatic.*JSRuntime::getValueInt/ +//! PASS_AFTER "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 1 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 1 //! RUN entry: "call_chain" function call_chain(): int { @@ -190,6 +224,22 @@ function call_chain(): int { //! INST_COUNT "Intrinsic.CompilerConvertLocalToJSValue", 4 //! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 4 //! INST_COUNT /CallStatic.*JSRuntime::getValueInt/, 1 +//! PASS_BEFORE "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 10 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 10 +//! INST_COUNT "Intrinsic.CompilerConvertJSValueToLocal", 10 +//! INST_COUNT "Intrinsic.CompilerConvertI32ToLocal", 9 +//! PASS_AFTER "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 3 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 3 +//! PASS_AFTER "DeoptimizeElimination" +//! PASS_AFTER_NEXT "Cleanup" +//! INST_COUNT "Intrinsic.CompilerConvertJSValueToLocal", 3 +//! INST_COUNT "Intrinsic.CompilerConvertLocalToJSValue", 2 +//! INST_COUNT "Intrinsic.CompilerConvertI32ToLocal", 7 +//! # We convert Phi inputs to JSValue and then convert Phi back to value in local scope, TODO(aefremov): remove +//! INST_COUNT "Intrinsic.CompilerConvertRefTypeToLocal", 2 +//! INST_COUNT "Intrinsic.CompilerConvertLocalToF64", 1 //! CHECKER Chain of js new calls AOT (slow) //! RUN_PAOC options: "--compiler-regex=ETSGLOBAL::new_chain --compiler-enable-fast-interop=false" @@ -219,6 +269,9 @@ function call_chain(): int { //! INST_COUNT "Intrinsic.CompilerConvertLocalToJSValue", 4 //! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 4 //! INST_COUNT /CallStatic.*JSRuntime::getValueInt/, 1 +//! PASS_AFTER "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 3 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 3 //! RUN entry: "new_chain" function new_chain(): int { @@ -346,6 +399,12 @@ function load_store_chain(): int { //! INST_COUNT "Intrinsic.CompilerConvertLocalToJSValue", 6 //! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 6 //! INST_COUNT /CallStatic.*JSRuntime::getValueInt/, 4 +//! PASS_BEFORE "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 6 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 6 +//! PASS_AFTER "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 2 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 2 //! CHECKER Define object method in js and call it from ets, AOT (slow) //! RUN_PAOC options: "--compiler-regex=ETSGLOBAL::method_call --compiler-enable-fast-interop=false" @@ -382,6 +441,8 @@ function method_call(): int { pair.second = i; make_swappable(pair); pair.swap(); + // TODO (aefremov): we do not extend handle scopes downwards, + // so we get 2 scopes instead of 1 here because of return if (pair.first as int != i || pair.second as int != -i) { return 1; } diff --git a/plugins/ets/tests/interop_js/tests/checked/merge_handle_scopes/merge_handle_scopes.ets b/plugins/ets/tests/interop_js/tests/checked/merge_handle_scopes/merge_handle_scopes.ets new file mode 100644 index 0000000000000000000000000000000000000000..5e74f2c823ca8e79a1584e71cd1794300c44f293 --- /dev/null +++ b/plugins/ets/tests/interop_js/tests/checked/merge_handle_scopes/merge_handle_scopes.ets @@ -0,0 +1,246 @@ +/** + * Copyright (c) 2021-2023 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 { cons, car, cdr, undefined } + from "/plugins/ets/tests/interop_js/tests/checked/js_call/js_call.js" + +//! CHECKER Tree of js function calls JIT (fast) +//! RUN force_jit: true, entry: "test_call_tree", options: "--compiler-regex=ETSGLOBAL::call_tree" +//! METHOD "call_tree" +//! PASS_AFTER "IrBuilder" +//! PASS_BEFORE "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 4 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 4 +//! INST_COUNT "Intrinsic.CompilerConvertI32ToLocal", 4 +//! INST_COUNT "Intrinsic.CompilerConvertJSValueToLocal", 4 +//! INST_COUNT "Intrinsic.CompilerConvertLocalToJSValue", 4 +//! INST_COUNT "Intrinsic.CompilerConvertRefTypeToLocal", 3 +//! PASS_AFTER "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 1 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 3 +//! PASS_AFTER "DeoptimizeElimination" +//! PASS_AFTER_NEXT "Cleanup" +//! INST_COUNT "Intrinsic.CompilerConvertI32ToLocal", 3 # remove dominated wrap of x +//! INST_COUNT "Intrinsic.CompilerConvertJSValueToLocal", 1 # only conversion of LoadStatic $dynmodule... left +//! INST_COUNT "Intrinsic.CompilerConvertLocalToJSValue", 3 # cannot remove because we return JSValue +//! INST_NOT "Intrinsic.CompilerConvertRefTypeToLocal" + +function call_tree(x: int): JSValue { + let list = cons(x, -1); + if (x < 10) { + list = cons(1, list); + } else { + if (x < 20) { + list = cons(x, list); + } else { + list = cdr(list); + } + } + return list; +} + +function test_call_tree(): int { + if (car(call_tree(5)) as int != 1) { + return 1; + } + if (car(call_tree(15)) as int != 15) { + return 2; + } + if (call_tree(25) as int != -1) { + return 3; + } + return 0; +} + +//! CHECKER JS function calls in tree leaves, create common scope start JIT (fast) +//! RUN force_jit: true, entry: "test_call_hoist_scope_start", options: "--compiler-regex=ETSGLOBAL::call_hoist_scope_start" +//! METHOD "call_hoist_scope_start" +//! PASS_AFTER "IrBuilder" +//! PASS_BEFORE "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 3 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 3 +//! PASS_AFTER "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 1 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 3 + +function call_hoist_scope_start(x: int): JSValue { + let list = undefined; + if (x < 10) { + list = cons(1, undefined); + } else { + if (x < 20) { + list = cons(x, undefined); + } else { + list = cons(x * 2, undefined); + } + } + return list; +} + +function test_call_hoist_scope_start(): int { + if (car(call_hoist_scope_start(5)) as int != 1) { + return 1; + } + if (car(call_hoist_scope_start(15)) as int != 15) { + return 2; + } + if (car(call_hoist_scope_start(25)) as int != 50) { + return 3; + } + return 0; +} + +//! CHECKER ETS loop (not countable) between JS function calls (fast) +//! RUN force_jit: true, entry: "loop_into_scope", options: "--compiler-regex=ETSGLOBAL::loop_into_scope" +//! METHOD "loop_into_scope" +//! PASS_AFTER "IrBuilder" +//! PASS_BEFORE "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 3 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 3 +//! INST_COUNT "Intrinsic.CompilerConvertJSValueToLocal", 3 +//! INST_COUNT "Intrinsic.CompilerConvertLocalToJSValue", 3 +//! INST_COUNT "Intrinsic.CompilerConvertRefTypeToLocal", 3 +//! PASS_AFTER "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 1 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 1 +//! PASS_AFTER "DeoptimizeElimination" +//! PASS_AFTER_NEXT "Cleanup" +//! INST_COUNT "Intrinsic.CompilerConvertJSValueToLocal", 2 +//! INST_COUNT "Intrinsic.CompilerConvertRefTypeToLocal", 1 # conversion of JSRuntimeGetPropertyJSValue to local, TODO(aefremov): remove +//! INST_NOT "Intrinsic.CompilerConvertLocalToJSValue" +//! INST_COUNT "Intrinsic.CompilerConvertLocalToF64", 1 + +function loop_into_scope(): int { + let list = cons(1, undefined); + let x = 0; + for (let i = 1; i < 10; i *= 2) { + x += i; + } + list = cons(x, list); + if (car(list) as int != 15) { + return 1; + } + return 0; +} + +//! CHECKER Countable loop with JS calls (fast) +//! RUN force_jit: true, entry: "small_loop_with_scopes", options: "--compiler-regex=ETSGLOBAL::small_loop_with_scopes" +//! METHOD "small_loop_with_scopes" +//! PASS_AFTER "IrBuilder" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 4 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 4 +//! PASS_BEFORE "MergeHandleScopes" +//! # Additional scopes were created in LoopUnroll +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 8 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 8 +//! PASS_AFTER "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 3 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 3 + +function small_loop_with_scopes(): int { + let list = cons(1, undefined); + for (let i = 1; i <= 5; i++) { + list = cons(i, list); + } + for (let i = 5; i >= 1; i--) { + if (car(list) as int != i) { + return 1; + } + list = cdr(list); + } + return 0; +} + +//! CHECKER ETS if chain between JS function calls (fast) +//! RUN force_jit: true, entry: "test_if_chain_into_scope", options: "--compiler-regex=ETSGLOBAL::if_chain_into_scope" +//! METHOD "if_chain_into_scope" +//! PASS_AFTER "IrBuilder" +//! PASS_BEFORE "MergeHandleScopes" +//! INST_COUNT "IfImm", 2 +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 2 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 2 +//! PASS_AFTER "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 1 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 1 + +function if_chain_into_scope(x: int): JSValue { + let list = cons(1, undefined); + if (x < 0) { + x = -x; + } + if (x > 10) { + x = 10; + } + return cons(x, list); +} + +function test_if_chain_into_scope(): int { + let x = car(if_chain_into_scope(-2)) as int; + if (x != 2) { + return 1; + } + x = car(if_chain_into_scope(15)) as int; + if (x != 10) { + return 2; + } + return 0; +} + +//! CHECKER ETS if chain with JS function calls (fast) +//! RUN force_jit: true, entry: "test_if_chain_with_scopes", options: "--compiler-regex=ETSGLOBAL::if_chain_with_scopes" +//! METHOD "if_chain_with_scopes" +//! PASS_AFTER "IrBuilder" +//! PASS_BEFORE "MergeHandleScopes" +//! INST_COUNT "IfImm", 2 +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 5 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 5 +//! PASS_AFTER "MergeHandleScopes" +//! INST_COUNT "Intrinsic.CompilerCreateLocalScope", 1 +//! INST_COUNT "Intrinsic.CompilerDestroyLocalScope", 1 + +function if_chain_with_scopes(x: int): JSValue { + let list = cons(1, undefined); + if (x < 0) { + list = cons(x, list); + } + if (x < -10) { + list = cons(-10, list); + } else { + list = cons(x, list); + } + return cons(x, list); +} + +function compare_lists(list: JSValue, expected: int[]): boolean { + for (let i: int in expected) { + if (car(list) as int != i) { + return false; + } + list = cdr(list); + } + return true; +} + +function test_if_chain_with_scopes(): int { + let list = if_chain_with_scopes(2); + if (!compare_lists(list, [2, 2, 1])) { + return 1; + } + list = if_chain_with_scopes(-12); + if (!compare_lists(list, [-12, -10, -12, 1])) { + return 2; + } + return 0; +} \ No newline at end of file diff --git a/plugins/ets/tests/interop_js/tests/compiler/CMakeLists.txt b/plugins/ets/tests/interop_js/tests/compiler/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..8d957e24369577e0c225d8859ffffdcbb8d8d0f2 --- /dev/null +++ b/plugins/ets/tests/interop_js/tests/compiler/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright (c) 2021-2023 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. + +if(NOT PANDA_MINIMAL_VIXL AND PANDA_COMPILER_ENABLE) + panda_add_gtest( + CONTAINS_MAIN + NAME interop_compiler_unit_tests + SOURCES + ${PANDA_ROOT}/compiler/tests/unit_test.cpp + merge_handle_scopes_test.cpp + LIBRARIES + arkcompiler arkbase arkruntime + SANITIZERS + ${PANDA_SANITIZERS_LIST} + DEPS_TARGETS + arkstdlib + ) +endif() + +add_dependencies(ets_interop_tests interop_compiler_unit_tests_gtests) \ No newline at end of file diff --git a/plugins/ets/tests/interop_js/tests/compiler/merge_handle_scopes_test.cpp b/plugins/ets/tests/interop_js/tests/compiler/merge_handle_scopes_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c8a2017d43cffcee3e33b6fed37d397ebfec34cf --- /dev/null +++ b/plugins/ets/tests/interop_js/tests/compiler/merge_handle_scopes_test.cpp @@ -0,0 +1,760 @@ +/** + * Copyright (c) 2021-2022 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 "tests/unit_test.h" +#include "optimizer/ir/graph_cloner.h" +#include "optimizer/optimizations/deoptimize_elimination.h" +#include "optimizer/optimizations/cleanup.h" +#include "plugins/ets/compiler/optimizer/interop_js/merge_handle_scopes.h" + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define INTRINSIC(ID, INTRINSIC_ID) \ + INST(ID, Opcode::Intrinsic).IntrinsicId(RuntimeInterface::IntrinsicId::INTRINSIC_##INTRINSIC_ID) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define PARAM_TEST(CLASS_NAME, TEST_NAME, ...) \ + class TEST_NAME : public CLASS_NAME {}; \ + INSTANTIATE_TEST_SUITE_P(CLASS_NAME, TEST_NAME, __VA_ARGS__); \ + TEST_P(TEST_NAME, test) + +namespace panda::compiler { + +enum BlockPos : uint8_t { PRED = 1U << 0U, LEFT = 1U << 1U, RIGHT = 1U << 2U, NEXT = 1U << 3U, NONE = 0 }; + +BlockPos operator|(BlockPos lhs, BlockPos rhs) +{ + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +// NOLINTNEXTLINE(fuchsia-multiple-inheritance) +class MergeHandleScopesTest : public GraphTest, public testing::WithParamInterface { +public: + bool BlockEnabled(BlockPos block_pos) + { + return (GetParam() & block_pos) != 0; + } +}; + +std::string GetTestName(const testing::TestParamInfo &info) +{ + static std::array block_pos_names {"Pred", "Left", "Right", "Next"}; + auto param = info.param; + if (param == BlockPos::NONE) { + return "None"; + } + std::stringstream res; + for (size_t i = 0; i < block_pos_names.size(); ++i) { + if ((param & (1U << i)) != 0) { + res << block_pos_names[i]; + } + } + return res.str(); +} + +// NOLINTBEGIN(readability-magic-numbers) + +// Check that two adjacent scopes are merged and +TEST_F(MergeHandleScopesTest, SingleBlock) +{ + GRAPH(GetGraph()) + { + PARAMETER(0, 0).s32(); + PARAMETER(1, 1).ptr(); // Pass this and function values as parameters to simplify the test + PARAMETER(2, 2).ptr(); + CONSTANT(3, 2).s64(); + + BASIC_BLOCK(2, -1) + { + INST(4, Opcode::SaveState).NoVregs(); + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + INTRINSIC(6, COMPILER_CONVERT_I32_TO_LOCAL).ptr().InputsAutoType(0, 4); + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 6, 6, 4); + INTRINSIC(8, COMPILER_CONVERT_LOCAL_TO_JS_VALUE).ref().InputsAutoType(7, 4); + INST(9, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(10, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(9); + + INST(11, Opcode::Mul).s32().Inputs(0, 0); + + INST(12, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(13, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(12); + INTRINSIC(14, COMPILER_CONVERT_I32_TO_LOCAL).ptr().InputsAutoType(11, 12); + INTRINSIC(15, COMPILER_CONVERT_JS_VALUE_TO_LOCAL).ptr().InputsAutoType(8, 12); + INTRINSIC(16, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 14, 15, 12); + INTRINSIC(17, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(12); + + INST(18, Opcode::ReturnVoid).v0id(); + } + } + + Graph *graph = CreateEmptyGraph(); + GRAPH(graph) + { + PARAMETER(0, 0).s32(); + PARAMETER(1, 1).ptr(); // Pass this and function values as parameters to simplify the test + PARAMETER(2, 2).ptr(); + CONSTANT(3, 2).s64(); + + BASIC_BLOCK(2, -1) + { + INST(4, Opcode::SaveState).NoVregs(); + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + INTRINSIC(6, COMPILER_CONVERT_I32_TO_LOCAL).ptr().InputsAutoType(0, 4); + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 6, 6, 4); + + INST(11, Opcode::Mul).s32().Inputs(0, 0); + + INST(12, Opcode::SaveState).NoVregs(); + INTRINSIC(14, COMPILER_CONVERT_I32_TO_LOCAL).ptr().InputsAutoType(11, 12); + INTRINSIC(16, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 14, 7, 12); + INTRINSIC(17, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(12); + + INST(18, Opcode::ReturnVoid).v0id(); + } + } + + ASSERT_TRUE(GetGraph()->RunPass()); + GetGraph()->Dump(&std::cerr); + // DeoptimizeElimination removes SaveState user of ConvertLocalToJSValue + ASSERT_TRUE(GetGraph()->RunPass()); + GetGraph()->Dump(&std::cerr); + ASSERT_TRUE(GetGraph()->RunPass()); + GetGraph()->Dump(&std::cerr); + GraphChecker(GetGraph()).Check(); + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph)); +} + +TEST_F(MergeHandleScopesTest, SingleBlockNotApplied) +{ + GRAPH(GetGraph()) + { + PARAMETER(0, 0).s32(); + PARAMETER(1, 1).ptr(); // Pass this and function values as parameters to simplify the test + PARAMETER(2, 2).ptr(); + CONSTANT(3, 2).s64(); + + BASIC_BLOCK(2, -1) + { + INST(4, Opcode::SaveState).NoVregs(); + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + INTRINSIC(6, COMPILER_CONVERT_I32_TO_LOCAL).ptr().InputsAutoType(0, 4); + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 6, 6, 4); + INTRINSIC(8, COMPILER_CONVERT_LOCAL_TO_JS_VALUE).ref().InputsAutoType(7, 4); + INST(9, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(10, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(9); + + INST(19, Opcode::SaveState).NoVregs(); + INST(20, Opcode::CallStatic).v0id().InputsAutoType(19); + + INST(12, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(13, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(12); + INTRINSIC(14, COMPILER_CONVERT_I32_TO_LOCAL).ptr().InputsAutoType(0, 12); + INTRINSIC(15, COMPILER_CONVERT_JS_VALUE_TO_LOCAL).ptr().InputsAutoType(8, 12); + INTRINSIC(16, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 14, 15, 12); + INTRINSIC(17, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(12); + + INST(18, Opcode::ReturnVoid).v0id(); + } + } + + auto initial = GraphCloner(GetGraph(), GetGraph()->GetAllocator(), GetGraph()->GetLocalAllocator()).CloneGraph(); + ASSERT_FALSE(GetGraph()->RunPass()); + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), initial)); +} + +PARAM_TEST(MergeHandleScopesTest, ChainOfBlocks, + ::testing::Values(BlockPos::PRED, BlockPos::LEFT, BlockPos::NEXT, BlockPos::NONE), GetTestName) +{ + GRAPH(GetGraph()) + { + PARAMETER(0, 0).s32(); + PARAMETER(1, 1).ptr(); // Pass this and function values as parameters to simplify the test + PARAMETER(2, 2).ptr(); + CONSTANT(3, 2).s64(); + + BASIC_BLOCK(2, 3) + { + INST(18, Opcode::Add).s32().Inputs(0, 0); + } + BASIC_BLOCK(3, 4) + { + INST(4, Opcode::SaveState).NoVregs(); + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + INTRINSIC(6, COMPILER_CONVERT_I32_TO_LOCAL).ptr().InputsAutoType(0, 4); + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 6, 6, 4); + INTRINSIC(8, COMPILER_CONVERT_LOCAL_TO_JS_VALUE).ref().InputsAutoType(7, 4); + INST(9, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(10, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(9); + + if (BlockEnabled(BlockPos::PRED)) { + INST(23, Opcode::SaveState).NoVregs(); + INST(24, Opcode::CallStatic).v0id().InputsAutoType(23); + } + INST(19, Opcode::Add).s32().Inputs(18, 0); + } + BASIC_BLOCK(4, 5) + { + if (BlockEnabled(BlockPos::LEFT)) { + INST(23, Opcode::SaveState).NoVregs(); + INST(24, Opcode::CallStatic).v0id().InputsAutoType(23); + } + INST(20, Opcode::Add).s32().Inputs(19, 0); + } + BASIC_BLOCK(5, -1) + { + INST(21, Opcode::Add).s32().Inputs(20, 0); + if (BlockEnabled(BlockPos::NEXT)) { + INST(23, Opcode::SaveState).NoVregs(); + INST(24, Opcode::CallStatic).v0id().InputsAutoType(23); + } + + INST(12, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(13, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(12); + INTRINSIC(14, COMPILER_CONVERT_I32_TO_LOCAL).ptr().InputsAutoType(21, 12); + INTRINSIC(15, COMPILER_CONVERT_JS_VALUE_TO_LOCAL).ptr().InputsAutoType(8, 12); + INTRINSIC(16, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 14, 15, 12); + INTRINSIC(17, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(12); + + INST(22, Opcode::ReturnVoid).v0id(); + } + } + GraphChecker(GetGraph()).Check(); + if (GetParam() == BlockPos::NONE) { + Graph *graph = CreateEmptyGraph(); + GRAPH(graph) + { + PARAMETER(0, 0).s32(); + PARAMETER(1, 1).ptr(); + PARAMETER(2, 2).ptr(); + CONSTANT(3, 2).s64(); + + BASIC_BLOCK(2, -1) + { + INST(18, Opcode::Add).s32().Inputs(0, 0); + INST(4, Opcode::SaveState).NoVregs(); + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + INTRINSIC(6, COMPILER_CONVERT_I32_TO_LOCAL).ptr().InputsAutoType(0, 4); + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 6, 6, 4); + + INST(19, Opcode::Add).s32().Inputs(18, 0); + INST(20, Opcode::Add).s32().Inputs(19, 0); + INST(21, Opcode::Add).s32().Inputs(20, 0); + + INST(12, Opcode::SaveState).NoVregs(); + INTRINSIC(14, COMPILER_CONVERT_I32_TO_LOCAL).ptr().InputsAutoType(21, 12); + INTRINSIC(16, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 14, 7, 12); + INTRINSIC(17, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(12); + + INST(22, Opcode::ReturnVoid).v0id(); + } + } + ASSERT_TRUE(GetGraph()->RunPass()); + // DeoptimizeElimination removes SaveState user of ConvertLocalToJSValue + ASSERT_TRUE(GetGraph()->RunPass()); + ASSERT_TRUE(GetGraph()->RunPass()); + GraphChecker(GetGraph()).Check(); + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph)); + } else { + ASSERT(INS(24).GetOpcode() == Opcode::CallStatic); + auto initial = + GraphCloner(GetGraph(), GetGraph()->GetAllocator(), GetGraph()->GetLocalAllocator()).CloneGraph(); + ASSERT_FALSE(GetGraph()->RunPass()); + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), initial)); + } +} + +// Param specifies for which blocks there is a local scope +PARAM_TEST(MergeHandleScopesTest, TriangleOfBlocks, + ::testing::Values(BlockPos::PRED | BlockPos::LEFT | BlockPos::NEXT, BlockPos::PRED | BlockPos::LEFT, + BlockPos::PRED | BlockPos::NEXT, BlockPos::LEFT | BlockPos::NEXT), + GetTestName) +{ + GRAPH(GetGraph()) + { + PARAMETER(0, 0).s32(); + PARAMETER(1, 1).ptr(); // Pass this and function values as parameters to simplify the test + PARAMETER(2, 2).ptr(); + CONSTANT(3, 1).s64(); + CONSTANT(23, 0).s64(); + + BASIC_BLOCK(2, 3, 4) + { + INST(4, Opcode::SaveState).NoVregs(); + if (BlockEnabled(BlockPos::PRED)) { + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 23, 4); + INTRINSIC(8, COMPILER_CONVERT_LOCAL_TO_JS_VALUE).ref().InputsAutoType(7, 4); + INST(9, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(10, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(9); + } else { + INTRINSIC(8, JS_RUNTIME_GET_UNDEFINED).ref().InputsAutoType(4); + } + INST(11, Opcode::IfImm).CC(compiler::CC_EQ).Imm(0).Inputs(0); + } + BASIC_BLOCK(3, 4) + { + if (BlockEnabled(BlockPos::LEFT)) { + INST(24, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(25, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(24); + INTRINSIC(27, COMPILER_CONVERT_JS_VALUE_TO_LOCAL).ptr().InputsAutoType(8, 24); + INTRINSIC(28, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 27, 24); + INTRINSIC(29, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(24); + } + } + BASIC_BLOCK(4, -1) + { + INST(30, Opcode::Phi).s64().Inputs(3, 23); + if (BlockEnabled(BlockPos::NEXT)) { + INST(12, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(13, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(12); + INTRINSIC(15, COMPILER_CONVERT_JS_VALUE_TO_LOCAL).ptr().InputsAutoType(8, 12); + INTRINSIC(16, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 15, 12); + INTRINSIC(17, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(12); + } + + INST(22, Opcode::Return).s64().Inputs(30); + } + } + GraphChecker(GetGraph()).Check(); + if (BlockEnabled(BlockPos::PRED) && BlockEnabled(BlockPos::NEXT)) { + Graph *graph = CreateEmptyGraph(); + GRAPH(graph) + { + PARAMETER(0, 0).s32(); + PARAMETER(1, 1).ptr(); // Pass this and function values as parameters to simplify the test + PARAMETER(2, 2).ptr(); + CONSTANT(3, 1).s64(); + CONSTANT(23, 0).s64(); + + BASIC_BLOCK(2, 3, 4) + { + INST(4, Opcode::SaveState).NoVregs(); + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 23, 4); + INST(11, Opcode::IfImm).CC(compiler::CC_EQ).Imm(0).Inputs(0); + } + BASIC_BLOCK(3, 4) + { + if (BlockEnabled(BlockPos::LEFT)) { + INST(24, Opcode::SaveState).NoVregs(); + INTRINSIC(28, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 7, 24); + } + } + BASIC_BLOCK(4, -1) + { + INST(30, Opcode::Phi).s64().Inputs(3, 23); + INST(12, Opcode::SaveState).NoVregs(); + INTRINSIC(16, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 7, 12); + INTRINSIC(17, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(12); + + INST(22, Opcode::Return).s64().Inputs(30); + } + } + ASSERT_TRUE(GetGraph()->RunPass()); + ASSERT_TRUE(GetGraph()->RunPass()); + ASSERT_TRUE(GetGraph()->RunPass()); + GraphChecker(GetGraph()).Check(); + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph)); + } else { + auto initial = + GraphCloner(GetGraph(), GetGraph()->GetAllocator(), GetGraph()->GetLocalAllocator()).CloneGraph(); + ASSERT_FALSE(GetGraph()->RunPass()); + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), initial)); + } +} + +// Param specifies for which blocks there is a local scope +PARAM_TEST(MergeHandleScopesTest, DiamondOfBlocks, + ::testing::Values(BlockPos::PRED | BlockPos::RIGHT | BlockPos::NEXT, BlockPos::PRED | BlockPos::RIGHT, + BlockPos::PRED | BlockPos::NEXT, BlockPos::RIGHT | BlockPos::NEXT), + GetTestName) +{ + GRAPH(GetGraph()) + { + PARAMETER(0, 0).s32(); + PARAMETER(1, 1).ptr(); // Pass this and function values as parameters to simplify the test + PARAMETER(2, 2).ptr(); + PARAMETER(32, 3).ptr(); + CONSTANT(23, 0).s64(); + CONSTANT(3, 1).s64(); + CONSTANT(42, 2).s64(); + + BASIC_BLOCK(2, 3, 4) + { + INST(4, Opcode::SaveState).NoVregs(); + if (BlockEnabled(BlockPos::PRED)) { + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 23, 4); + INTRINSIC(8, COMPILER_CONVERT_LOCAL_TO_JS_VALUE).ref().InputsAutoType(7, 4); + INST(9, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(10, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(9); + } else { + INTRINSIC(8, JS_RUNTIME_GET_UNDEFINED).ref().InputsAutoType(4); + } + INST(11, Opcode::IfImm).CC(compiler::CC_EQ).Imm(0).Inputs(0); + } + BASIC_BLOCK(3, 5) + { + INST(24, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(25, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(24); + INTRINSIC(27, COMPILER_CONVERT_JS_VALUE_TO_LOCAL).ptr().InputsAutoType(8, 24); + INTRINSIC(28, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 32, 3, 27, 24); + INTRINSIC(29, COMPILER_CONVERT_LOCAL_TO_JS_VALUE).ref().InputsAutoType(28, 24); + INST(30, Opcode::SaveState).Inputs(8, 29).SrcVregs({0, 1}); + INTRINSIC(31, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(30); + } + BASIC_BLOCK(4, 5) + { + INST(33, Opcode::SaveState).Inputs(8).SrcVregs({0}); + if (BlockEnabled(BlockPos::RIGHT)) { + INTRINSIC(34, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(33); + INTRINSIC(35, COMPILER_CONVERT_JS_VALUE_TO_LOCAL).ptr().InputsAutoType(8, 33); + INTRINSIC(36, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 35, 33); + INTRINSIC(37, COMPILER_CONVERT_LOCAL_TO_JS_VALUE).ref().InputsAutoType(36, 33); + INST(38, Opcode::SaveState).Inputs(8, 37).SrcVregs({0, 1}); + INTRINSIC(39, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(38); + } else { + INTRINSIC(37, JS_RUNTIME_GET_UNDEFINED).ref().InputsAutoType(33); + } + } + BASIC_BLOCK(5, -1) + { + INST(40, Opcode::Phi).ref().Inputs(29, 37); + if (BlockEnabled(BlockPos::NEXT)) { + INST(12, Opcode::SaveState).Inputs(8, 40).SrcVregs({0, 2}); + INTRINSIC(13, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(12); + INTRINSIC(14, COMPILER_CONVERT_JS_VALUE_TO_LOCAL).ptr().InputsAutoType(8, 12); + INTRINSIC(15, COMPILER_CONVERT_JS_VALUE_TO_LOCAL).ptr().InputsAutoType(40, 12); + INTRINSIC(16, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 42, 14, 15, 12); + INTRINSIC(17, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(12); + INST(41, Opcode::ReturnVoid).v0id(); + } else { + INST(41, Opcode::Return).ref().Inputs(40); + } + } + } + Graph *graph = CreateEmptyGraph(); + int js_val {}; + GRAPH(graph) + { + PARAMETER(0, 0).s32(); + PARAMETER(1, 1).ptr(); // Pass this and function values as parameters to simplify the test + PARAMETER(2, 2).ptr(); + PARAMETER(32, 3).ptr(); + CONSTANT(23, 0).s64(); + CONSTANT(3, 1).s64(); + CONSTANT(42, 2).s64(); + + BASIC_BLOCK(2, 3, 4) + { + INST(4, Opcode::SaveState).NoVregs(); + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + if (BlockEnabled(BlockPos::PRED)) { + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 23, 4); + js_val = 7; + } else { + INTRINSIC(8, JS_RUNTIME_GET_UNDEFINED).ref().InputsAutoType(4); + } + INST(11, Opcode::IfImm).CC(compiler::CC_EQ).Imm(0).Inputs(0); + } + BASIC_BLOCK(3, 5) + { + INST(24, Opcode::SaveState).Inputs(8).SrcVregs({0}).CleanupInputs(); + if (!BlockEnabled(BlockPos::PRED)) { + INTRINSIC(27, COMPILER_CONVERT_JS_VALUE_TO_LOCAL).ptr().InputsAutoType(8, 24); + js_val = 27; + } + INTRINSIC(28, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 32, 3, js_val, 24); + INTRINSIC(29, COMPILER_CONVERT_LOCAL_TO_JS_VALUE).ref().InputsAutoType(28, 24); + if (!BlockEnabled(BlockPos::NEXT)) { + INST(30, Opcode::SaveState).Inputs(8, 29).SrcVregs({0, 1}).CleanupInputs(); + INTRINSIC(31, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(30); + } + } + BASIC_BLOCK(4, 5) + { + INST(33, Opcode::SaveState).Inputs(8).SrcVregs({0}).CleanupInputs(); + if (BlockEnabled(BlockPos::RIGHT)) { + if (!BlockEnabled(BlockPos::PRED)) { + INTRINSIC(35, COMPILER_CONVERT_JS_VALUE_TO_LOCAL).ptr().InputsAutoType(8, 33); + js_val = 35; + } + INTRINSIC(36, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, js_val, 33); + INTRINSIC(37, COMPILER_CONVERT_LOCAL_TO_JS_VALUE).ref().InputsAutoType(36, 33); + if (!BlockEnabled(BlockPos::NEXT)) { + INST(38, Opcode::SaveState).Inputs(8, 37).SrcVregs({0, 1}).CleanupInputs(); + INTRINSIC(39, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(38); + } + } else { + INTRINSIC(37, JS_RUNTIME_GET_UNDEFINED).ref().InputsAutoType(33); + } + } + BASIC_BLOCK(5, -1) + { + INST(40, Opcode::Phi).ref().Inputs(29, 37); + if (BlockEnabled(BlockPos::NEXT)) { + INST(12, Opcode::SaveState).Inputs(8, 40).SrcVregs({0, 2}).CleanupInputs(); + if (!BlockEnabled(BlockPos::PRED)) { + INTRINSIC(14, COMPILER_CONVERT_JS_VALUE_TO_LOCAL).ptr().InputsAutoType(8, 12); + js_val = 14; + } + // TODO(aefremov): unwrap of Phi inputs and wrap of Phi may be removed + // in a way similar to PhiTypeResolving pass + INTRINSIC(15, COMPILER_CONVERT_JS_VALUE_TO_LOCAL).ptr().InputsAutoType(40, 12); + INTRINSIC(16, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 42, js_val, 15, 12); + INTRINSIC(17, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(12); + INST(41, Opcode::ReturnVoid).v0id(); + } else { + INST(41, Opcode::Return).ref().Inputs(40); + } + } + } + ASSERT_TRUE(GetGraph()->RunPass()); + ASSERT_TRUE(GetGraph()->RunPass()); + ASSERT_TRUE(GetGraph()->RunPass()); + // Remove unused constants for some parameter values + graph->RunPass(); + GraphChecker(GetGraph()).Check(); + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph)); +} + +// Replace a wrap in dominated block with a wrap in dominator +// In some cases hoist the wrap to the dominator +// Param specifies for which blocks there is a wrap of int value v0 to js value +PARAM_TEST(MergeHandleScopesTest, DiamondOfBlocksRepeatedWraps, + ::testing::Values(BlockPos::PRED | BlockPos::LEFT | BlockPos::RIGHT | BlockPos::NEXT, + BlockPos::LEFT | BlockPos::RIGHT, BlockPos::LEFT | BlockPos::RIGHT | BlockPos::NEXT, + BlockPos::LEFT | BlockPos::NEXT, BlockPos::PRED | BlockPos::NEXT, BlockPos::NEXT), + GetTestName) +{ + int js_val {}; + GRAPH(GetGraph()) + { + PARAMETER(0, 0).s32(); + PARAMETER(1, 1).ptr(); // Pass this and function values as parameters to simplify the test + PARAMETER(2, 2).ptr(); + PARAMETER(32, 3).ptr(); + CONSTANT(3, 1).s64(); + + BASIC_BLOCK(2, 3, 4) + { + INST(4, Opcode::SaveState).NoVregs(); + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + if (BlockEnabled(BlockPos::PRED)) { + INTRINSIC(8, COMPILER_CONVERT_I32_TO_LOCAL).ref().InputsAutoType(0, 4); + js_val = 8; + } else { + INTRINSIC(9, JS_RUNTIME_GET_UNDEFINED).ref().InputsAutoType(4); + js_val = 9; + } + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, js_val, 4); + INTRINSIC(10, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(4); + INST(11, Opcode::IfImm).CC(compiler::CC_EQ).Imm(0).Inputs(0); + } + BASIC_BLOCK(3, 5) + { + INST(24, Opcode::SaveState).NoVregs(); + INTRINSIC(25, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(24); + if (BlockEnabled(BlockPos::LEFT)) { + INTRINSIC(27, COMPILER_CONVERT_I32_TO_LOCAL).ref().InputsAutoType(0, 24); + } else { + INTRINSIC(27, JS_RUNTIME_GET_UNDEFINED).ref().InputsAutoType(24); + } + INTRINSIC(28, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 32, 3, 27, 24); + INTRINSIC(29, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(24); + } + BASIC_BLOCK(4, 5) + { + INST(33, Opcode::SaveState).NoVregs(); + INTRINSIC(34, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(33); + if (BlockEnabled(BlockPos::RIGHT)) { + INTRINSIC(35, COMPILER_CONVERT_I32_TO_LOCAL).ref().InputsAutoType(0, 33); + } else { + INTRINSIC(35, JS_RUNTIME_GET_UNDEFINED).ref().InputsAutoType(33); + } + INTRINSIC(36, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 35, 33); + INTRINSIC(37, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(33); + } + BASIC_BLOCK(5, -1) + { + INST(12, Opcode::SaveState).NoVregs(); + INTRINSIC(13, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(12); + if (BlockEnabled(BlockPos::NEXT)) { + INTRINSIC(14, COMPILER_CONVERT_I32_TO_LOCAL).ref().InputsAutoType(0, 12); + } else { + INTRINSIC(14, JS_RUNTIME_GET_UNDEFINED).ref().InputsAutoType(12); + } + INTRINSIC(15, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, 14, 12); + INTRINSIC(16, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(12); + INST(17, Opcode::ReturnVoid).v0id(); + } + } + + Graph *graph = CreateEmptyGraph(); + bool wrap_in_pred = GetParam() != (BlockPos::LEFT | BlockPos::RIGHT) && GetParam() != BlockPos::NEXT; + GRAPH(graph) + { + PARAMETER(0, 0).s32(); + PARAMETER(1, 1).ptr(); // Pass this and function values as parameters to simplify the test + PARAMETER(2, 2).ptr(); + PARAMETER(32, 3).ptr(); + CONSTANT(3, 1).s64(); + + BASIC_BLOCK(2, 3, 4) + { + INST(4, Opcode::SaveState).NoVregs(); + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + if (wrap_in_pred) { + INTRINSIC(8, COMPILER_CONVERT_I32_TO_LOCAL).ref().InputsAutoType(0, 4); + js_val = 8; + } + if (!BlockEnabled(BlockPos::PRED)) { + INTRINSIC(9, JS_RUNTIME_GET_UNDEFINED).ref().InputsAutoType(4); + js_val = 9; + } + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, js_val, 4); + INST(11, Opcode::IfImm).CC(compiler::CC_EQ).Imm(0).Inputs(0); + } + BASIC_BLOCK(3, 5) + { + INST(24, Opcode::SaveState).Inputs(8).SrcVregs({VirtualRegister::BRIDGE}).CleanupInputs(); + if (!BlockEnabled(BlockPos::LEFT)) { + INTRINSIC(27, JS_RUNTIME_GET_UNDEFINED).ref().InputsAutoType(24); + js_val = 27; + } else if (wrap_in_pred) { + js_val = 8; + } else { + INTRINSIC(27, COMPILER_CONVERT_I32_TO_LOCAL).ref().InputsAutoType(0, 24); + js_val = 27; + } + INTRINSIC(28, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 32, 3, js_val, 24); + } + BASIC_BLOCK(4, 5) + { + INST(33, Opcode::SaveState).Inputs(8).SrcVregs({VirtualRegister::BRIDGE}).CleanupInputs(); + if (!BlockEnabled(BlockPos::RIGHT)) { + INTRINSIC(35, JS_RUNTIME_GET_UNDEFINED).ref().InputsAutoType(33); + js_val = 35; + } else if (wrap_in_pred) { + js_val = 8; + } else { + INTRINSIC(35, COMPILER_CONVERT_I32_TO_LOCAL).ref().InputsAutoType(0, 33); + js_val = 35; + } + INTRINSIC(36, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, js_val, 33); + } + BASIC_BLOCK(5, -1) + { + if (BlockEnabled(BlockPos::NEXT) && wrap_in_pred) { + INST(12, Opcode::SaveState).Inputs(8).SrcVregs({VirtualRegister::BRIDGE}); + js_val = 8; + } else { + INST(12, Opcode::SaveState).NoVregs(); + if (BlockEnabled(BlockPos::NEXT)) { + INTRINSIC(14, COMPILER_CONVERT_I32_TO_LOCAL).ref().InputsAutoType(0, 12); + } else { + INTRINSIC(14, JS_RUNTIME_GET_UNDEFINED).ref().InputsAutoType(12); + } + js_val = 14; + } + INTRINSIC(15, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(1, 2, 3, js_val, 12); + INTRINSIC(16, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(12); + INST(17, Opcode::ReturnVoid).v0id(); + } + } + ASSERT_TRUE(GetGraph()->RunPass()); + GetGraph()->RunPass(); + + GraphChecker(GetGraph()).Check(); + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph)); +} + +TEST_F(MergeHandleScopesTest, WrapUnwrapReturnValue) +{ + GRAPH(GetGraph()) + { + PARAMETER(0, 0).ptr(); + PARAMETER(1, 1).ptr(); // Pass this and function values as parameters to simplify the test + CONSTANT(3, 0).s64(); + + BASIC_BLOCK(2, -1) + { + INST(4, Opcode::SaveState).NoVregs(); + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(0, 1, 3, 4); + INTRINSIC(8, COMPILER_CONVERT_LOCAL_TO_JS_VALUE).ref().InputsAutoType(7, 4); + INST(9, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(10, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(9); + + INTRINSIC(11, JS_RUNTIME_GET_VALUE_DOUBLE).f64().InputsAutoType(8, 9); + INST(18, Opcode::Return).f64().Inputs(11); + } + } + + Graph *graph = CreateEmptyGraph(); + GRAPH(graph) + { + PARAMETER(0, 0).ptr(); // Pass this and function values as parameters to simplify the test + PARAMETER(1, 1).ptr(); + CONSTANT(3, 0).s64(); + + BASIC_BLOCK(2, -1) + { + INST(4, Opcode::SaveState).NoVregs(); + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(0, 1, 3, 4); + INTRINSIC(8, COMPILER_CONVERT_LOCAL_TO_F64).f64().InputsAutoType(7, 4); + INST(9, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(10, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(9); + + INST(18, Opcode::Return).f64().Inputs(8); + } + } + + ASSERT_TRUE(GetGraph()->RunPass()); + GraphChecker(GetGraph()).Check(); + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph)); +} + +// ConvertLocalToJSValue has another user (return), so we do not merge it with GetValueDouble +TEST_F(MergeHandleScopesTest, WrapUnwrapReturnValueNotApplied) +{ + GRAPH(GetGraph()) + { + PARAMETER(0, 0).ptr(); // Pass this and function values as parameters to simplify the test + PARAMETER(1, 1).ptr(); + CONSTANT(3, 0).s64(); + + BASIC_BLOCK(2, -1) + { + INST(4, Opcode::SaveState).NoVregs(); + INTRINSIC(5, COMPILER_CREATE_LOCAL_SCOPE).v0id().InputsAutoType(4); + INTRINSIC(7, COMPILER_JS_CALL_FUNCTION).ptr().InputsAutoType(0, 1, 3, 4); + INTRINSIC(8, COMPILER_CONVERT_LOCAL_TO_JS_VALUE).ref().InputsAutoType(7, 4); + INST(9, Opcode::SaveState).Inputs(8).SrcVregs({0}); + INTRINSIC(10, COMPILER_DESTROY_LOCAL_SCOPE).v0id().InputsAutoType(9); + + INTRINSIC(11, JS_RUNTIME_GET_VALUE_DOUBLE).f64().InputsAutoType(8, 9); + INST(12, Opcode::CallStatic).v0id().InputsAutoType(11, 9); + INST(18, Opcode::Return).ref().Inputs(8); + } + } + + auto initial = GraphCloner(GetGraph(), GetGraph()->GetAllocator(), GetGraph()->GetLocalAllocator()).CloneGraph(); + ASSERT_FALSE(GetGraph()->RunPass()); + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), initial)); +} + +// NOLINTEND(readability-magic-numbers) + +} // namespace panda::compiler