// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/compiler/backend/inliner.h" #include "vm/compiler/backend/il.h" #include "vm/compiler/backend/il_printer.h" #include "vm/compiler/backend/il_test_helper.h" #include "vm/compiler/compiler_pass.h" #include "vm/object.h" #include "vm/unit_test.h" namespace dart { // Test that the redefinition for an inlined polymorphic function used with // multiple receiver cids does not have a concrete type. ISOLATE_UNIT_TEST_CASE(Inliner_PolyInliningRedefinition) { const char* kScript = R"( abstract class A { String toInline() { return "A"; } } class B extends A {} class C extends A { @override String toInline() { return "C";} } class D extends A {} testInlining(A arg) { arg.toInline(); } main() { for (var i = 0; i < 10; i++) { testInlining(B()); testInlining(C()); testInlining(D()); } } )"; const auto& root_library = Library::Handle(LoadTestScript(kScript)); const auto& function = Function::Handle(GetFunction(root_library, "testInlining")); Invoke(root_library, "main"); TestPipeline pipeline(function, CompilerPass::kJIT); FlowGraph* flow_graph = pipeline.RunPasses({ CompilerPass::kComputeSSA, CompilerPass::kApplyICData, CompilerPass::kTryOptimizePatterns, CompilerPass::kSetOuterInliningId, CompilerPass::kTypePropagation, CompilerPass::kApplyClassIds, CompilerPass::kInlining, }); auto entry = flow_graph->graph_entry()->normal_entry(); EXPECT(entry != nullptr); EXPECT(entry->initial_definitions()->length() == 1); EXPECT(entry->initial_definitions()->At(0)->IsParameter()); ParameterInstr* param = entry->initial_definitions()->At(0)->AsParameter(); // First we find the start of the prelude for the inlined instruction, // and also keep a reference to the LoadClassId instruction for later. LoadClassIdInstr* lcid = nullptr; BranchInstr* prelude = nullptr; ILMatcher cursor(flow_graph, entry); RELEASE_ASSERT(cursor.TryMatch( { {kMatchLoadClassId, &lcid}, {kMatchBranch, &prelude}, }, /*insert_before=*/kMoveGlob)); const Class& cls = Class::Handle( root_library.LookupLocalClass(String::Handle(Symbols::New(thread, "B")))); Definition* cid_B = flow_graph->GetConstant(Smi::Handle(Smi::New(cls.id()))); Instruction* current = prelude; // We walk false branches until we either reach a branch instruction that uses // B's cid for comparison to the value returned from the LCID instruction // above, or a default case if there was no branch instruction for B's cid. while (true) { EXPECT(current->IsBranch()); const ComparisonInstr* check = current->AsBranch()->comparison(); EXPECT(check->left()->definition() == lcid); if (check->right()->definition() == cid_B) break; current = current->SuccessorAt(1); // By following false paths, we should be walking a series of blocks that // looks like: // B#[target]:# // Branch if // If we end up not finding a branch, then we're in a default case // that contains a class check. current = current->next(); if (!current->IsBranch()) { break; } } // If we found a branch that checks against the class ID, we follow the true // branch to a block that contains only a goto to the desired join block. if (current->IsBranch()) { current = current->SuccessorAt(0); } else { // We're in the default case, which will check the class ID to make sure // it's the one expected for the fallthrough. That check will be followed // by a goto to the desired join block. EXPECT(current->IsRedefinition()); const auto redef = current->AsRedefinition(); EXPECT(redef->value()->definition() == lcid); current = current->next(); EXPECT(current->IsCheckClassId()); EXPECT(current->AsCheckClassId()->value()->definition() == redef); } current = current->next(); EXPECT(current->IsGoto()); current = current->AsGoto()->successor(); // Now we should be at a block that starts like: // BY[join]:# pred(...) // vW <- Redefinition(vV) // // where vV is a reference to the function parameter (the receiver of // the inlined function). current = current->next(); EXPECT(current->IsRedefinition()); EXPECT(current->AsRedefinition()->value()->definition() == param); EXPECT(current->AsRedefinition()->Type()->ToCid() == kDynamicCid); } ISOLATE_UNIT_TEST_CASE(Inliner_TypedData_Regress7551) { const char* kScript = R"( import 'dart:typed_data'; setValue(Int32List list, int value) { list[0] = value; } main() { final list = Int32List(10); setValue(list, 0x1122334455); } )"; const auto& root_library = Library::Handle(LoadTestScript(kScript)); const auto& function = Function::Handle(GetFunction(root_library, "setValue")); Invoke(root_library, "main"); TestPipeline pipeline(function, CompilerPass::kJIT); FlowGraph* flow_graph = pipeline.RunPasses({ CompilerPass::kComputeSSA, CompilerPass::kApplyICData, CompilerPass::kTryOptimizePatterns, CompilerPass::kSetOuterInliningId, CompilerPass::kTypePropagation, CompilerPass::kApplyClassIds, CompilerPass::kInlining, }); auto entry = flow_graph->graph_entry()->normal_entry(); EXPECT(entry->initial_definitions()->length() == 2); EXPECT(entry->initial_definitions()->At(0)->IsParameter()); EXPECT(entry->initial_definitions()->At(1)->IsParameter()); ParameterInstr* list_param = entry->initial_definitions()->At(0)->AsParameter(); ParameterInstr* value_param = entry->initial_definitions()->At(1)->AsParameter(); ILMatcher cursor(flow_graph, entry); CheckArrayBoundInstr* bounds_check_instr = nullptr; UnboxInt32Instr* unbox_instr = nullptr; StoreIndexedInstr* store_instr = nullptr; RELEASE_ASSERT(cursor.TryMatch({ {kMoveGlob}, {kMatchAndMoveCheckArrayBound, &bounds_check_instr}, {kMatchAndMoveUnboxInt32, &unbox_instr}, {kMatchAndMoveStoreIndexed, &store_instr}, })); RELEASE_ASSERT(unbox_instr->InputAt(0)->definition() == value_param); RELEASE_ASSERT(store_instr->InputAt(0)->definition() == list_param); RELEASE_ASSERT(store_instr->InputAt(2)->definition() == unbox_instr); RELEASE_ASSERT(unbox_instr->is_truncating()); } #if defined(DART_PRECOMPILER) // Verifies that all calls are inlined in List.generate call // with a simple closure. ISOLATE_UNIT_TEST_CASE(Inliner_List_generate) { const char* kScript = R"( foo(n) => List.generate(n, (int x) => x, growable: false); main() { foo(100); } )"; const auto& root_library = Library::Handle(LoadTestScript(kScript)); const auto& function = Function::Handle(GetFunction(root_library, "foo")); TestPipeline pipeline(function, CompilerPass::kAOT); FlowGraph* flow_graph = pipeline.RunPasses({}); auto entry = flow_graph->graph_entry()->normal_entry(); ILMatcher cursor(flow_graph, entry, /*trace=*/true, ParallelMovesHandling::kSkip); RELEASE_ASSERT(cursor.TryMatch({ kMoveGlob, kMatchAndMoveCreateArray, kMatchAndMoveUnboxInt64, #if defined(TARGET_ARCH_IS_32_BIT) // TODO(rmacnak): Implement missing ops to allow 32-bit architectures in // UnboxInt64Instr::Canonicalize. kMatchAndMoveUnboxInt64, #endif kMatchAndMoveGoto, // Loop header kMatchAndMoveJoinEntry, kMatchAndMoveCheckStackOverflow, kMatchAndMoveBranchTrue, // Loop body kMatchAndMoveTargetEntry, kWordSize == 4 ? kMatchAndMoveBoxInt64 : kNop, kMatchAndMoveBoxInt64, kMatchAndMoveStoreIndexed, kMatchAndMoveBinaryInt64Op, kMatchAndMoveGoto, // Loop header once again kMatchAndMoveJoinEntry, kMatchAndMoveCheckStackOverflow, kMatchAndMoveBranchFalse, // After loop kMatchAndMoveTargetEntry, kMatchReturn, })); } #endif // defined(DART_PRECOMPILER) } // namespace dart