diff --git a/compiler/optimizer/ir/graph_checker.cpp b/compiler/optimizer/ir/graph_checker.cpp index fe3f340aa63e968715b7a61710cbef8c442efe66..9a21182fe3b47887c2ad273f5959f3a44d4c7cf9 100644 --- a/compiler/optimizer/ir/graph_checker.cpp +++ b/compiler/optimizer/ir/graph_checker.cpp @@ -361,6 +361,18 @@ void GraphChecker::CheckDataFlow(BasicBlock *block) void GraphChecker::CheckCallReturnInlined() { + [[maybe_unused]] bool throw_exit = false; + if (GetGraph()->HasEndBlock()) { + for (auto block : GetGraph()->GetEndBlock()->GetPredsBlocks()) { + if (block->IsTryEnd()) { + continue; + } + if (block->IsEndWithThrowOrDeoptimize()) { + throw_exit = true; + break; + } + } + } ArenaStack inlined_calles(GetLocalAllocator()->Adapter()); for (auto block : GetGraph()->GetBlocksRPO()) { for (auto inst : block->Insts()) { @@ -373,22 +385,12 @@ void GraphChecker::CheckCallReturnInlined() continue; } ASSERT_EXT(!inlined_calles.empty()); + ASSERT(inlined_calles.top()->GetSaveState() == inst->GetSaveState() || throw_exit || + GetGraph()->IsRegAllocApplied()); inlined_calles.pop(); } } } - [[maybe_unused]] bool throw_exit = false; - if (GetGraph()->HasEndBlock()) { - for (auto block : GetGraph()->GetEndBlock()->GetPredsBlocks()) { - if (block->IsTryEnd()) { - continue; - } - if (block->IsEndWithThrowOrDeoptimize()) { - throw_exit = true; - break; - } - } - } ASSERT_EXT(inlined_calles.empty() || throw_exit); #ifndef NDEBUG // avoid check after ir_builder in inline pass @@ -416,6 +418,7 @@ void GraphChecker::CheckSaveStateCaller(SaveStateInst *savestate) } ASSERT_EXT(caller->GetBasicBlock() != nullptr); ASSERT_EXT(caller->GetBasicBlock()->GetGraph() == block->GetGraph()); + ASSERT(caller->IsInlined()); auto dom_block = block; bool skip = true; for (auto inst : dom_block->InstsSafeReverse()) { diff --git a/compiler/optimizer/ir/ir_constructor.h b/compiler/optimizer/ir/ir_constructor.h index 135c532f5cae021222c7468ad76a6ed292b41a55..c0ad8ba3e0e23514de23757dec5f00e436f33d40 100644 --- a/compiler/optimizer/ir/ir_constructor.h +++ b/compiler/optimizer/ir/ir_constructor.h @@ -390,6 +390,17 @@ public: return *this; } + IrConstructor &Caller(unsigned index) + { + auto inst = CurrentInst(); + ASSERT(inst->GetOpcode() == Opcode::SaveState); + auto call_inst = &GetInst(index); + ASSERT(call_inst->GetOpcode() == Opcode::CallStatic || call_inst->GetOpcode() == Opcode::CallVirtual || + call_inst->GetOpcode() == Opcode::CallDynamic); + inst->CastToSaveState()->SetCallerInst(static_cast(call_inst)); + return *this; + } + IrConstructor &Scale(uint64_t scale) { auto inst = CurrentInst(); diff --git a/compiler/optimizer/optimizations/checks_elimination.cpp b/compiler/optimizer/optimizations/checks_elimination.cpp index 6ee9cd224febc65cdd597bca484ed47cbba9560e..91feb8948ab6e2e301caa24de86eac6b2ea285bf 100644 --- a/compiler/optimizer/optimizations/checks_elimination.cpp +++ b/compiler/optimizer/optimizations/checks_elimination.cpp @@ -187,14 +187,23 @@ bool ChecksElimination::CanRemoveDynCallCheck(Inst *inst) { bool has_inlined_user = false; for (auto &user : inst->GetUsers()) { - if (user.GetInst()->IsCall()) { - if (user.GetInst()->CastToCallDynamic()->IsInlined()) { + auto user_inst = user.GetInst(); + if (user_inst->IsCall()) { + if (user_inst->CastToCallDynamic()->IsInlined()) { has_inlined_user = true; } else { return false; } + } else if (user_inst->GetOpcode() == Opcode::Compare) { + for (auto &compare_user : user_inst->GetUsers()) { + if (compare_user.GetInst()->GetOpcode() == Opcode::DeoptimizeIf && + compare_user.GetInst()->CastToDeoptimizeIf()->GetDeoptimizeType() == DeoptimizeType::INLINE_DYN) { + // Call.Inlined was removed by Cleanup, but guard remained + has_inlined_user = true; + } + } } - ASSERT(!user.GetInst()->IsCheck()); + ASSERT(!user_inst->IsCheck()); } return has_inlined_user; } diff --git a/compiler/optimizer/optimizations/cleanup.cpp b/compiler/optimizer/optimizations/cleanup.cpp index 1acfd1c7244c7e6fdcf2dc6cac339da08cfab54b..0fc5613e8954b5af11c381e8d7e7f511de969804 100644 --- a/compiler/optimizer/optimizations/cleanup.cpp +++ b/compiler/optimizer/optimizations/cleanup.cpp @@ -127,8 +127,10 @@ bool Cleanup::RunOnce(ArenaSet *empty_blocks, ArenaSet(dead_mrk, new_empty_blocks); } else { - modified |= Dce(dead_mrk, new_empty_blocks); + modified |= Dce(dead_mrk, new_empty_blocks); } dead_.clear(); @@ -243,8 +245,35 @@ bool Cleanup::ProcessBB(BasicBlock *bb, Marker dead_mrk, ArenaSet return true; } +void Cleanup::MarkInlinedCaller(Marker live_mrk, Inst *save_state) +{ + if (!save_state->IsSaveState()) { + return; + } + auto caller = static_cast(save_state)->GetCallerInst(); + // if caller->IsInlined() is false, graph is being inlined and caller is not in the graph + if (caller != nullptr && !caller->IsMarked(live_mrk) && caller->IsInlined()) { + MarkLiveRec(live_mrk, caller); + } +} + +bool Cleanup::IsRemovableCall(Inst *inst) +{ + if (inst->IsCall() && static_cast(inst)->IsInlined()) { + for (auto &ss_user : inst->GetSaveState()->GetUsers()) { + if (ss_user.GetInst()->GetOpcode() == Opcode::ReturnInlined && + ss_user.GetInst()->GetFlag(inst_flags::MEM_BARRIER)) { + return false; + } + } + return true; + } + return false; +} + // Mark instructions that have the NOT_REMOVABLE property // and recursively mark all their inputs +template void Cleanup::MarkLiveRec(Marker live_mrk, Inst *inst) { // No recursion for one-input case, otherwise got stackoverflow on TSAN job @@ -254,15 +283,22 @@ void Cleanup::MarkLiveRec(Marker live_mrk, Inst *inst) if (marked) { break; } + if constexpr (!LIGHT_MODE) { + MarkInlinedCaller(live_mrk, inst); + } inst = inst->GetInput(0).GetInst(); } if (!marked && !inst->SetMarker(live_mrk)) { + if constexpr (!LIGHT_MODE) { + MarkInlinedCaller(live_mrk, inst); + } for (auto input : inst->GetInputs()) { - MarkLiveRec(live_mrk, input.GetInst()); + MarkLiveRec(live_mrk, input.GetInst()); } } } +template bool Cleanup::Dce(Marker dead_mrk, ArenaSet *new_empty_blocks) { bool modified = false; @@ -272,8 +308,17 @@ bool Cleanup::Dce(Marker dead_mrk, ArenaSet *new_empty_blocks) // Mark live instructions for (auto bb : GetGraph()->GetBlocksRPO()) { for (auto inst : bb->AllInsts()) { - if (inst->IsNotRemovable() && !inst->IsMarked(dead_mrk)) { - MarkLiveRec(live_mrk, inst); + if constexpr (LIGHT_MODE) { + if (inst->IsNotRemovable() && !inst->IsMarked(dead_mrk)) { + MarkLiveRec(live_mrk, inst); + } + } else { + if (inst->GetOpcode() == Opcode::ReturnInlined) { + // SaveState input of ReturnInlined will be marked as live through CallInlined if needed + inst->SetMarker(live_mrk); + } else if (inst->IsNotRemovable() && !inst->IsMarked(dead_mrk) && !IsRemovableCall(inst)) { + MarkLiveRec(live_mrk, inst); + } } } } @@ -281,7 +326,21 @@ bool Cleanup::Dce(Marker dead_mrk, ArenaSet *new_empty_blocks) for (auto bb : GetGraph()->GetBlocksRPO()) { for (auto inst : bb->AllInstsSafe()) { if (inst->IsMarked(live_mrk)) { - continue; + if (LIGHT_MODE || + !(inst->GetOpcode() == Opcode::ReturnInlined && inst->GetSaveState()->GetBasicBlock() == nullptr)) { + continue; + } + } + if (!LIGHT_MODE && inst->IsCall() && static_cast(inst)->IsInlined()) { + auto caller_ss = inst->GetSaveState(); + for (auto &user : caller_ss->GetUsers()) { + auto user_inst = user.GetInst(); + // we mark all ReturnInlined as live initially to reduce number of IsDominate checks + // IsDominate check is needed because several pairs of Call/Return inlined can share SaveState + if (user_inst->GetOpcode() == Opcode::ReturnInlined && inst->IsDominate(user_inst)) { + user_inst->ResetMarker(live_mrk); + } + } } bool is_phi = inst->IsPhi(); bb->RemoveInst(inst); diff --git a/compiler/optimizer/optimizations/cleanup.h b/compiler/optimizer/optimizations/cleanup.h index c89cd8d5085f551b293db62d8622c6ad5ee0ba84..4fc6366309d4e274c4b58a78f7b86d65d6000dc1 100644 --- a/compiler/optimizer/optimizations/cleanup.h +++ b/compiler/optimizer/optimizations/cleanup.h @@ -61,7 +61,11 @@ private: bool ProcessBB(BasicBlock *bb, Marker dead_mrk, ArenaSet *new_empty_blocks); bool CheckSpecialTriangle(BasicBlock *bb); + void MarkInlinedCaller(Marker live_mrk, Inst *save_state); + bool IsRemovableCall(Inst *inst); + template void MarkLiveRec(Marker live_mrk, Inst *inst); + template bool Dce(Marker dead_mrk, ArenaSet *new_empty_blocks); void SetLiveRec(Inst *inst, Marker mrk, Marker live_mrk); diff --git a/compiler/optimizer/optimizations/escape.cpp b/compiler/optimizer/optimizations/escape.cpp index 0c0f726f492c04ec93a78af01b79c3fa4a7d9a45..61c5b98882d350b525de2ab9c69e0015a97bb46b 100644 --- a/compiler/optimizer/optimizations/escape.cpp +++ b/compiler/optimizer/optimizations/escape.cpp @@ -731,8 +731,20 @@ std::pair EscapeAnalysis::CreatePhi(BasicBlock *target_block, void EscapeAnalysis::MaterializeInBlock(StateOwner inst, BasicBlock *block) { - if (GetState(block)->GetStateId(inst) != MATERIALIZED_ID) { - RegisterMaterialization(block, std::get(inst)); + auto block_state = GetState(block); + inst = block_state->GetState(inst)->GetInst(); + if (block_state->GetStateId(inst) == EscapeAnalysis::MATERIALIZED_ID) { + return; + } + RegisterMaterialization(block, std::get(inst)); + auto inst_state = block_state->GetState(inst); + block_state->Materialize(inst); + for (auto &t : inst_state->GetFields()) { + auto &field_inst = t.second; + if (block_state->GetStateId(field_inst) == MATERIALIZED_ID) { + continue; + } + MaterializeInBlock(field_inst, block); } } @@ -741,13 +753,11 @@ void EscapeAnalysis::Materialize(StateOwner inst, BasicBlock *block) if (GetState(block)->GetStateId(inst) == EscapeAnalysis::MATERIALIZED_ID) { return; } - inst = GetState(block)->GetState(inst)->GetInst(); if (block->IsEmpty()) { MaterializeInBlock(inst, block); } else { Materialize(inst, block->GetLastInst()); } - GetState(block)->Materialize(inst); } void EscapeAnalysis::Materialize(StateOwner inst, Inst *before) @@ -1745,9 +1755,7 @@ void ScalarReplacement::AddLiveInputs(Inst *inst, ArenaUnorderedSet &liv { for (auto &input : inst->GetInputs()) { auto input_inst = inst->GetDataFlowInput(input.GetInst()); - if (!DataType::IsReference(input_inst->GetType()) || input_inst->IsNullPtr() || input_inst->IsStore() || - input_inst->IsClassInst() || input_inst->GetOpcode() == Opcode::GetInstanceClass || - input_inst->GetOpcode() == Opcode::ClassImmediate) { + if (!DataType::IsReference(input_inst->GetType()) || input_inst->IsStore() || !input_inst->IsMovableObject()) { continue; } live_ins.insert(input_inst); diff --git a/compiler/optimizer/optimizations/inlining.cpp b/compiler/optimizer/optimizations/inlining.cpp index 84b695bc427b2ada85d58652fc7abcd5cb8d5310..760b22fee735d322fe3261f8b624631c8a610711 100644 --- a/compiler/optimizer/optimizations/inlining.cpp +++ b/compiler/optimizer/optimizations/inlining.cpp @@ -123,7 +123,8 @@ bool Inlining::RunImpl() void Inlining::RunOptimizations() const { - if (GetGraph()->RunPass() && GetGraph()->RunPass()) { + if (GetGraph()->GetParentGraph() == nullptr && GetGraph()->RunPass() && + GetGraph()->RunPass()) { GetGraph()->RunPass(); } } @@ -1080,8 +1081,8 @@ InlinedGraph Inlining::BuildGraph(InlineContext *ctx, CallInst *call_inst, CallI auto object_type_applied = graph_inl->RunPass(); if (peephole_applied || object_type_applied) { graph_inl->RunPass(); + graph_inl->RunPass(); } - graph_inl->RunPass(); // Don't inline if we reach the limit of instructions and method is big enough. auto inlined_insts_count = CalculateInstructionsCount(graph_inl); diff --git a/compiler/optimizer/optimizations/loop_unroll.cpp b/compiler/optimizer/optimizations/loop_unroll.cpp index 6fa04e701dc50fc536393c31594ab2da54a94932..18301cf62c8854c73969b55242fd186cc59bf5a2 100644 --- a/compiler/optimizer/optimizations/loop_unroll.cpp +++ b/compiler/optimizer/optimizations/loop_unroll.cpp @@ -184,7 +184,7 @@ LoopUnroll::UnrollParams LoopUnroll::GetUnrollParams(Loop *loop) if ((block->IsLoopHeader() && inst->IsPhi()) || inst->GetOpcode() == Opcode::SafePoint) { not_cloneable_count++; } - has_call |= inst->IsCall(); + has_call |= inst->IsCall() && !static_cast(inst)->IsInlined(); } } diff --git a/compiler/tests/cleanup_test.cpp b/compiler/tests/cleanup_test.cpp index a55e9043ae486c207306ca418d192168cca5573e..72b5595724d7d5046abd019c504fc55611621e45 100644 --- a/compiler/tests/cleanup_test.cpp +++ b/compiler/tests/cleanup_test.cpp @@ -1741,6 +1741,200 @@ TEST_F(CleanupTest, TwoBlocksAndPhi) } ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph)); } + +TEST_F(CleanupTest, RemoveCallReturnInlined) +{ + GRAPH(GetGraph()) + { + PARAMETER(0, 0).ref(); + PARAMETER(1, 1).s32(); + + BASIC_BLOCK(2, -1) + { + INST(2, Opcode::SaveState).Inputs(0).SrcVregs({0}); + INST(5, Opcode::CallStatic).u64().Inlined().InputsAutoType(0, 1, 2); + INST(6, Opcode::SaveState).Inputs(0).SrcVregs({0}).Caller(5); + INST(7, Opcode::Add).s32().Inputs(1, 1); + INST(8, Opcode::ReturnInlined).Inputs(2); + + INST(9, Opcode::Return).s32().Inputs(7); + } + } + + ASSERT_TRUE(GetGraph()->RunPass(false)); + + auto graph = CreateEmptyGraph(); + GRAPH(graph) + { + PARAMETER(1, 1).s32(); + + BASIC_BLOCK(2, -1) + { + INST(7, Opcode::Add).s32().Inputs(1, 1); + INST(9, Opcode::Return).s32().Inputs(7); + } + } + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph)); +} + +TEST_F(CleanupTest, RemoveNestedCallInlined) +{ + GRAPH(GetGraph()) + { + PARAMETER(0, 0).ref(); + + BASIC_BLOCK(2, -1) + { + INST(2, Opcode::SaveState).Inputs(0).SrcVregs({0}); + INST(5, Opcode::CallStatic).u64().Inlined().InputsAutoType(0, 2); + INST(6, Opcode::SaveState).Inputs(0).SrcVregs({0}).Caller(5); + INST(7, Opcode::NullCheck).ref().Inputs(0, 6); + + INST(10, Opcode::CallStatic).u64().Inlined().InputsAutoType(7, 6); + INST(11, Opcode::SaveState).Inputs(0).SrcVregs({0}).Caller(10); + INST(12, Opcode::LoadObject).u64().Inputs(7); + + INST(13, Opcode::SaveState).Inputs(0).SrcVregs({0}).Caller(10); + INST(14, Opcode::CallStatic).v0id().Inlined().InputsAutoType(12, 13); + + INST(15, Opcode::ReturnInlined).Inputs(13); + INST(16, Opcode::ReturnInlined).Inputs(6); + INST(8, Opcode::ReturnInlined).Inputs(2); + INST(9, Opcode::Return).u64().Inputs(12); + } + } + + ASSERT_TRUE(GetGraph()->RunPass(false)); + + auto graph = CreateEmptyGraph(); + GRAPH(graph) + { + PARAMETER(0, 0).ref(); + + BASIC_BLOCK(2, -1) + { + INST(2, Opcode::SaveState).Inputs(0).SrcVregs({0}); + INST(5, Opcode::CallStatic).u64().Inlined().InputsAutoType(0, 2); + + INST(6, Opcode::SaveState).Inputs(0).SrcVregs({0}).Caller(5); + INST(7, Opcode::NullCheck).ref().Inputs(0, 6); + INST(12, Opcode::LoadObject).u64().Inputs(7); + + INST(8, Opcode::ReturnInlined).Inputs(2); + INST(9, Opcode::Return).u64().Inputs(12); + } + } + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph)); +} + +TEST_F(CleanupTest, InlinedCallsWithCommonSaveState) +{ + GRAPH(GetGraph()) + { + PARAMETER(0, 0).ref(); + PARAMETER(1, 1).u64(); + + BASIC_BLOCK(2, 3, 4) + { + INST(2, Opcode::SaveState).Inputs(0).SrcVregs({0}); + INST(3, Opcode::IfImm).Inputs(1).Imm(0).CC(CC_NE); + } + + BASIC_BLOCK(3, -1) + { + INST(13, Opcode::CallStatic).ref().Inlined().InputsAutoType(0, 2); + INST(14, Opcode::ReturnInlined).Inputs(2); + INST(15, Opcode::Return).u64().Inputs(1); + } + + BASIC_BLOCK(4, -1) + { + INST(5, Opcode::CallStatic).ref().Inlined().InputsAutoType(0, 2); + INST(6, Opcode::SaveState).Inputs(0).SrcVregs({0}).Caller(5); + INST(7, Opcode::NullCheck).ref().Inputs(0, 6); + INST(12, Opcode::LoadObject).u64().Inputs(7); + INST(8, Opcode::ReturnInlined).Inputs(2); + INST(9, Opcode::Return).u64().Inputs(12); + } + } + + ASSERT_TRUE(GetGraph()->RunPass(false)); + + auto graph = CreateEmptyGraph(); + GRAPH(graph) + { + PARAMETER(0, 0).ref(); + PARAMETER(1, 1).u64(); + + BASIC_BLOCK(2, 3, 4) + { + INST(2, Opcode::SaveState).Inputs(0).SrcVregs({0}); + INST(3, Opcode::IfImm).Inputs(1).Imm(0).CC(CC_NE); + } + + BASIC_BLOCK(3, -1) + { + INST(15, Opcode::Return).u64().Inputs(1); + } + + BASIC_BLOCK(4, -1) + { + INST(5, Opcode::CallStatic).ref().Inlined().InputsAutoType(0, 2); + INST(6, Opcode::SaveState).Inputs(0).SrcVregs({0}).Caller(5); + INST(7, Opcode::NullCheck).ref().Inputs(0, 6); + INST(12, Opcode::LoadObject).u64().Inputs(7); + INST(8, Opcode::ReturnInlined).Inputs(2); + INST(9, Opcode::Return).u64().Inputs(12); + } + } + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), graph)); +} + +TEST_F(CleanupTest, DontRemoveCallInlined) +{ + GRAPH(GetGraph()) + { + PARAMETER(0, 0).ref(); + + BASIC_BLOCK(2, -1) + { + INST(2, Opcode::SaveState).Inputs(0).SrcVregs({0}); + INST(5, Opcode::CallStatic).u64().Inlined().InputsAutoType(0, 2); + INST(6, Opcode::SaveState).Inputs(0).SrcVregs({0}).Caller(5); + INST(7, Opcode::NullCheck).ref().Inputs(0, 6); + INST(8, Opcode::ReturnInlined).Inputs(2); + + INST(9, Opcode::Return).ref().Inputs(7); + } + } + + auto clone = GraphCloner(GetGraph(), GetGraph()->GetAllocator(), GetGraph()->GetLocalAllocator()).CloneGraph(); + ASSERT_FALSE(GetGraph()->RunPass(false)); + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), clone)); +} + +TEST_F(CleanupTest, DontRemoveCallInlinedWithBarrier) +{ + GRAPH(GetGraph()) + { + PARAMETER(0, 0).ref(); + PARAMETER(1, 1).ref(); + + BASIC_BLOCK(2, -1) + { + INST(2, Opcode::SaveState).Inputs(0).SrcVregs({0}); + INST(5, Opcode::CallStatic).u64().Inlined().InputsAutoType(0, 2); + INST(6, Opcode::StoreObject).ref().Inputs(0, 1); + INST(8, Opcode::ReturnInlined).Inputs(2).SetFlag(compiler::inst_flags::MEM_BARRIER); + + INST(9, Opcode::Return).ref().Inputs(0); + } + } + + auto clone = GraphCloner(GetGraph(), GetGraph()->GetAllocator(), GetGraph()->GetLocalAllocator()).CloneGraph(); + ASSERT_FALSE(GetGraph()->RunPass(false)); + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), clone)); +} // NOLINTEND(readability-magic-numbers) } // namespace panda::compiler diff --git a/compiler/tests/deoptimize_elimination_test.cpp b/compiler/tests/deoptimize_elimination_test.cpp index 7541d185e2ddf7a8aea0a8ff02ba4afab0a0fcff..4dab424faf6504dcff17ec08d9e98deeaf4dd0fb 100644 --- a/compiler/tests/deoptimize_elimination_test.cpp +++ b/compiler/tests/deoptimize_elimination_test.cpp @@ -221,7 +221,7 @@ TEST_F(DeoptimizeEliminationTest, ChaGuardOneBlockCallInlinedTest) INST(2, Opcode::DeoptimizeIf).Inputs(1, 0); INST(20, Opcode::SaveState).NoVregs(); INST(6, Opcode::CallStatic).v0id().Inlined().InputsAutoType(20); - INST(7, Opcode::ReturnInlined).Inputs(0); + INST(7, Opcode::ReturnInlined).Inputs(20); INST(3, Opcode::SaveStateDeoptimize).Inputs(10).SrcVregs({10}); INST(4, Opcode::IsMustDeoptimize).b(); @@ -242,7 +242,7 @@ TEST_F(DeoptimizeEliminationTest, ChaGuardOneBlockCallInlinedTest) INST(2, Opcode::DeoptimizeIf).Inputs(1, 0); INST(20, Opcode::SaveState).NoVregs(); INST(6, Opcode::CallStatic).v0id().Inlined().InputsAutoType(20); - INST(7, Opcode::ReturnInlined).Inputs(0); + INST(7, Opcode::ReturnInlined).Inputs(20); INST(3, Opcode::NOP); INST(4, Opcode::NOP); @@ -332,7 +332,7 @@ TEST_F(DeoptimizeEliminationTest, ChaGuardIfSeveralGuardsTest) { INST(21, Opcode::SaveState).NoVregs(); INST(5, Opcode::CallStatic).v0id().Inlined().InputsAutoType(21); - INST(16, Opcode::ReturnInlined).Inputs(0); + INST(16, Opcode::ReturnInlined).Inputs(21); } BASIC_BLOCK(5, 1) { @@ -365,7 +365,7 @@ TEST_F(DeoptimizeEliminationTest, ChaGuardIfSeveralGuardsTest) { INST(21, Opcode::SaveState).NoVregs(); INST(5, Opcode::CallStatic).v0id().Inlined().InputsAutoType(21); - INST(16, Opcode::ReturnInlined).Inputs(0); + INST(16, Opcode::ReturnInlined).Inputs(21); } BASIC_BLOCK(5, 1) { diff --git a/compiler/tests/escape_analysis_test.cpp b/compiler/tests/escape_analysis_test.cpp index 41b016f7828cb7fafc09d88da2f753ab1aabbe06..33c3df0ba13b5b0f702816f852db4034e61fd3cc 100644 --- a/compiler/tests/escape_analysis_test.cpp +++ b/compiler/tests/escape_analysis_test.cpp @@ -1371,9 +1371,9 @@ TEST_F(EscapeAnalysisTest, RemoveVirtualObjectFromSaveStates) { INST(6, Opcode::SaveState).Inputs(3).SrcVregs({0}); INST(7, Opcode::CallStatic).v0id().InputsAutoType(3, 6).Inlined(); - INST(8, Opcode::SaveState).Inputs(3).SrcVregs({0}); + INST(8, Opcode::SaveState).Inputs(3).SrcVregs({0}).Caller(7); INST(9, Opcode::CallStatic).v0id().InputsAutoType(3, 8).Inlined(); - INST(10, Opcode::SaveState).Inputs(3).SrcVregs({0}); + INST(10, Opcode::SaveState).Inputs(3).SrcVregs({0}).Caller(9); INST(11, Opcode::CallStatic).v0id().InputsAutoType(3, 10); INST(12, Opcode::ReturnInlined).Inputs(8); INST(13, Opcode::ReturnInlined).Inputs(6); diff --git a/compiler/tests/lse_test.cpp b/compiler/tests/lse_test.cpp index 51d0aac7579029170c764a8eb199b37627d6d86c..d63338e1d0e6a8e738840cb06bf370454ef255f7 100644 --- a/compiler/tests/lse_test.cpp +++ b/compiler/tests/lse_test.cpp @@ -1986,15 +1986,13 @@ TEST_F(LSETest, OverInlinedVirtualCall) } BASIC_BLOCK(3, 5) { - INST(20, Opcode::SaveState).NoVregs(); - INST(6, Opcode::CallVirtual).v0id().InputsAutoType(1, 2, 20).Inlined(); + INST(6, Opcode::CallVirtual).v0id().InputsAutoType(1, 2, 4).Inlined(); INST(7, Opcode::LoadObject).i32().Inputs(1).TypeId(122); INST(8, Opcode::ReturnInlined).v0id().Inputs(4); } BASIC_BLOCK(4, 5) { - INST(21, Opcode::SaveState).NoVregs(); - INST(9, Opcode::CallVirtual).v0id().InputsAutoType(1, 2, 21).Inlined(); + INST(9, Opcode::CallVirtual).v0id().InputsAutoType(1, 2, 4).Inlined(); INST(10, Opcode::LoadObject).i32().Inputs(1).TypeId(122); INST(11, Opcode::ReturnInlined).v0id().Inputs(4); } @@ -2018,14 +2016,12 @@ TEST_F(LSETest, OverInlinedVirtualCall) } BASIC_BLOCK(3, 5) { - INST(20, Opcode::SaveState).NoVregs(); - INST(6, Opcode::CallVirtual).v0id().InputsAutoType(1, 2, 20).Inlined(); + INST(6, Opcode::CallVirtual).v0id().InputsAutoType(1, 2, 4).Inlined(); INST(8, Opcode::ReturnInlined).v0id().Inputs(4); } BASIC_BLOCK(4, 5) { - INST(21, Opcode::SaveState).NoVregs(); - INST(9, Opcode::CallVirtual).v0id().InputsAutoType(1, 2, 21).Inlined(); + INST(9, Opcode::CallVirtual).v0id().InputsAutoType(1, 2, 4).Inlined(); INST(11, Opcode::ReturnInlined).v0id().Inputs(4); } BASIC_BLOCK(5, -1) diff --git a/plugins/ets/tests/checked/CMakeLists.txt b/plugins/ets/tests/checked/CMakeLists.txt index b2f64dcc566e605a8dfd3bb26b7750820fb38c60..8b0308f69e0f4e89c64f9b900c8164889f78780d 100644 --- a/plugins/ets/tests/checked/CMakeLists.txt +++ b/plugins/ets/tests/checked/CMakeLists.txt @@ -129,4 +129,5 @@ if (PANDA_TARGET_AMD64 OR NOT PANDA_ARM64_TESTS_WITH_SANITIZER) panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ets_floor.ets) panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ets_trunc.ets) panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ets_ceil.ets) + panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/cleanup_call_inlined.ets) endif() diff --git a/plugins/ets/tests/checked/cleanup_call_inlined.ets b/plugins/ets/tests/checked/cleanup_call_inlined.ets new file mode 100644 index 0000000000000000000000000000000000000000..166338be5de8493b99f29ba0a7491164955215fb --- /dev/null +++ b/plugins/ets/tests/checked/cleanup_call_inlined.ets @@ -0,0 +1,76 @@ +/* + * Copyright (c) 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. + */ + +//! CHECKER Remove redundant Call.Inlined and ReturnInlined +//! RUN force_jit: true, options: "--compiler-regex='ETSGLOBAL::test_.*'", entry: "ETSGLOBAL::main" +//! METHOD "ETSGLOBAL::test_after_checks_elim" +//! PASS_AFTER "Inline" +//! INST_COUNT /Call.*Inlined/, 1 +//! INST_COUNT /ReturnInlined/, 1 +//! PASS_AFTER "ChecksElimination" +//! PASS_AFTER_NEXT "Cleanup" # skip cleanup in light mode +//! PASS_AFTER_NEXT "Cleanup" +//! INST_NOT /Inlined/ +//! METHOD "ETSGLOBAL::test_after_escape" +//! PASS_AFTER "Inline" +//! INST_COUNT /Call.*Inlined/, 9 +//! INST_COUNT /ReturnInlined/, 9 +//! PASS_AFTER "EscapeAnalysis" +//! PASS_AFTER_NEXT "Cleanup" +//! INST_NOT /Inlined/ + +function sum2(a: int[]): int { + return a[0] + a[1]; +} + +function test_after_checks_elim(): int { + let a = new int[2]; + a[0] = 2; + a[1] = 3; + return sum2(a); +} + +final class Counter { + public add(value: int) { + // NullCheck here can be removed only after ScalarReplacement and ChecksElimination, + // after that all inlined call instructions can also be removed + this.total += value; + } + + total: int = 0; +} + +function add(c: WeakRef, value: int) { + c.deref().add(value); +} + +function test_after_escape(): int { + let counter = new Counter(); + let ref = new WeakRef(counter); + add(ref, 4); + add(ref, 5); + return ref.deref().total; +} + + +function main(): int { + if (test_after_checks_elim() != 5) { + return 1; + } + if (test_after_escape() != 9) { + return 2; + } + return 0; +} diff --git a/runtime/compiler.cpp b/runtime/compiler.cpp index a7910f33074a10979e23bc26d49f8d2928622337..d47bdde37dcd2cff2f2e7f46b04e78f15176ac60 100644 --- a/runtime/compiler.cpp +++ b/runtime/compiler.cpp @@ -320,7 +320,11 @@ bool PandaRuntimeInterface::IsMemoryBarrierRequired(MethodPtr method) const return false; } for (auto &field : MethodCast(method)->GetClass()->GetFields()) { - if (field.IsFinal()) { + // We insert memory barrier after call to constructor to ensure writes + // to final fields will be visible after constructor finishes + // Static fields are initialized in runtime entrypoints like InitializeClass, + // so barrier is not needed here if they are final + if (field.IsFinal() && !field.IsStatic()) { return true; } }