| 1 | // Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file | 
|---|
| 2 | // for details. All rights reserved. Use of this source code is governed by a | 
|---|
| 3 | // BSD-style license that can be found in the LICENSE file. | 
|---|
| 4 |  | 
|---|
| 5 | #include "vm/compiler/backend/inliner.h" | 
|---|
| 6 |  | 
|---|
| 7 | #include "vm/compiler/backend/il.h" | 
|---|
| 8 | #include "vm/compiler/backend/il_printer.h" | 
|---|
| 9 | #include "vm/compiler/backend/il_test_helper.h" | 
|---|
| 10 | #include "vm/compiler/compiler_pass.h" | 
|---|
| 11 | #include "vm/object.h" | 
|---|
| 12 | #include "vm/unit_test.h" | 
|---|
| 13 |  | 
|---|
| 14 | namespace dart { | 
|---|
| 15 |  | 
|---|
| 16 | // Test that the redefinition for an inlined polymorphic function used with | 
|---|
| 17 | // multiple receiver cids does not have a concrete type. | 
|---|
| 18 | ISOLATE_UNIT_TEST_CASE(Inliner_PolyInliningRedefinition) { | 
|---|
| 19 | const char* kScript = R"( | 
|---|
| 20 |     abstract class A { | 
|---|
| 21 |       String toInline() { return "A"; } | 
|---|
| 22 |     } | 
|---|
| 23 |  | 
|---|
| 24 |     class B extends A {} | 
|---|
| 25 |     class C extends A { | 
|---|
| 26 |       @override | 
|---|
| 27 |       String toInline() { return "C";} | 
|---|
| 28 |     } | 
|---|
| 29 |     class D extends A {} | 
|---|
| 30 |  | 
|---|
| 31 |     testInlining(A arg) { | 
|---|
| 32 |       arg.toInline(); | 
|---|
| 33 |     } | 
|---|
| 34 |  | 
|---|
| 35 |     main() { | 
|---|
| 36 |       for (var i = 0; i < 10; i++) { | 
|---|
| 37 |         testInlining(B()); | 
|---|
| 38 |         testInlining(C()); | 
|---|
| 39 |         testInlining(D()); | 
|---|
| 40 |       } | 
|---|
| 41 |     } | 
|---|
| 42 |   )"; | 
|---|
| 43 |  | 
|---|
| 44 | const auto& root_library = Library::Handle(LoadTestScript(kScript)); | 
|---|
| 45 | const auto& function = | 
|---|
| 46 | Function::Handle(GetFunction(root_library, "testInlining")); | 
|---|
| 47 |  | 
|---|
| 48 | Invoke(root_library, "main"); | 
|---|
| 49 |  | 
|---|
| 50 | TestPipeline pipeline(function, CompilerPass::kJIT); | 
|---|
| 51 | FlowGraph* flow_graph = pipeline.RunPasses({ | 
|---|
| 52 | CompilerPass::kComputeSSA, | 
|---|
| 53 | CompilerPass::kApplyICData, | 
|---|
| 54 | CompilerPass::kTryOptimizePatterns, | 
|---|
| 55 | CompilerPass::kSetOuterInliningId, | 
|---|
| 56 | CompilerPass::kTypePropagation, | 
|---|
| 57 | CompilerPass::kApplyClassIds, | 
|---|
| 58 | CompilerPass::kInlining, | 
|---|
| 59 | }); | 
|---|
| 60 |  | 
|---|
| 61 | auto entry = flow_graph->graph_entry()->normal_entry(); | 
|---|
| 62 | EXPECT(entry != nullptr); | 
|---|
| 63 |  | 
|---|
| 64 | EXPECT(entry->initial_definitions()->length() == 1); | 
|---|
| 65 | EXPECT(entry->initial_definitions()->At(0)->IsParameter()); | 
|---|
| 66 | ParameterInstr* param = entry->initial_definitions()->At(0)->AsParameter(); | 
|---|
| 67 |  | 
|---|
| 68 | // First we find the start of the prelude for the inlined instruction, | 
|---|
| 69 | // and also keep a reference to the LoadClassId instruction for later. | 
|---|
| 70 | LoadClassIdInstr* lcid = nullptr; | 
|---|
| 71 | BranchInstr* prelude = nullptr; | 
|---|
| 72 |  | 
|---|
| 73 | ILMatcher cursor(flow_graph, entry); | 
|---|
| 74 | RELEASE_ASSERT(cursor.TryMatch( | 
|---|
| 75 | { | 
|---|
| 76 | {kMatchLoadClassId, &lcid}, | 
|---|
| 77 | {kMatchBranch, &prelude}, | 
|---|
| 78 | }, | 
|---|
| 79 | /*insert_before=*/kMoveGlob)); | 
|---|
| 80 |  | 
|---|
| 81 | const Class& cls = Class::Handle( | 
|---|
| 82 | root_library.LookupLocalClass(String::Handle(Symbols::New(thread, "B")))); | 
|---|
| 83 |  | 
|---|
| 84 | Definition* cid_B = flow_graph->GetConstant(Smi::Handle(Smi::New(cls.id()))); | 
|---|
| 85 | Instruction* current = prelude; | 
|---|
| 86 |  | 
|---|
| 87 | // We walk false branches until we either reach a branch instruction that uses | 
|---|
| 88 | // B's cid for comparison to the value returned from the LCID instruction | 
|---|
| 89 | // above, or a default case if there was no branch instruction for B's cid. | 
|---|
| 90 | while (true) { | 
|---|
| 91 | EXPECT(current->IsBranch()); | 
|---|
| 92 | const ComparisonInstr* check = current->AsBranch()->comparison(); | 
|---|
| 93 | EXPECT(check->left()->definition() == lcid); | 
|---|
| 94 | if (check->right()->definition() == cid_B) break; | 
|---|
| 95 | current = current->SuccessorAt(1); | 
|---|
| 96 | // By following false paths, we should be walking a series of blocks that | 
|---|
| 97 | // looks like: | 
|---|
| 98 | // B#[target]:# | 
|---|
| 99 | //   Branch if <check on class ID> | 
|---|
| 100 | // If we end up not finding a branch, then we're in a default case | 
|---|
| 101 | // that contains a class check. | 
|---|
| 102 | current = current->next(); | 
|---|
| 103 | if (!current->IsBranch()) { | 
|---|
| 104 | break; | 
|---|
| 105 | } | 
|---|
| 106 | } | 
|---|
| 107 | // If we found a branch that checks against the class ID, we follow the true | 
|---|
| 108 | // branch to a block that contains only a goto to the desired join block. | 
|---|
| 109 | if (current->IsBranch()) { | 
|---|
| 110 | current = current->SuccessorAt(0); | 
|---|
| 111 | } else { | 
|---|
| 112 | // We're in the default case, which will check the class ID to make sure | 
|---|
| 113 | // it's the one expected for the fallthrough. That check will be followed | 
|---|
| 114 | // by a goto to the desired join block. | 
|---|
| 115 | EXPECT(current->IsRedefinition()); | 
|---|
| 116 | const auto redef = current->AsRedefinition(); | 
|---|
| 117 | EXPECT(redef->value()->definition() == lcid); | 
|---|
| 118 | current = current->next(); | 
|---|
| 119 | EXPECT(current->IsCheckClassId()); | 
|---|
| 120 | EXPECT(current->AsCheckClassId()->value()->definition() == redef); | 
|---|
| 121 | } | 
|---|
| 122 | current = current->next(); | 
|---|
| 123 | EXPECT(current->IsGoto()); | 
|---|
| 124 | current = current->AsGoto()->successor(); | 
|---|
| 125 | // Now we should be at a block that starts like: | 
|---|
| 126 | // BY[join]:# pred(...) | 
|---|
| 127 | //    vW <- Redefinition(vV) | 
|---|
| 128 | // | 
|---|
| 129 | // where vV is a reference to the function parameter (the receiver of | 
|---|
| 130 | // the inlined function). | 
|---|
| 131 | current = current->next(); | 
|---|
| 132 | EXPECT(current->IsRedefinition()); | 
|---|
| 133 | EXPECT(current->AsRedefinition()->value()->definition() == param); | 
|---|
| 134 | EXPECT(current->AsRedefinition()->Type()->ToCid() == kDynamicCid); | 
|---|
| 135 | } | 
|---|
| 136 |  | 
|---|
| 137 | ISOLATE_UNIT_TEST_CASE(Inliner_TypedData_Regress7551) { | 
|---|
| 138 | const char* kScript = R"( | 
|---|
| 139 |     import 'dart:typed_data'; | 
|---|
| 140 |  | 
|---|
| 141 |     setValue(Int32List list, int value) { | 
|---|
| 142 |       list[0] = value; | 
|---|
| 143 |     } | 
|---|
| 144 |  | 
|---|
| 145 |     main() { | 
|---|
| 146 |       final list = Int32List(10); | 
|---|
| 147 |       setValue(list, 0x1122334455); | 
|---|
| 148 |     } | 
|---|
| 149 |   )"; | 
|---|
| 150 |  | 
|---|
| 151 | const auto& root_library = Library::Handle(LoadTestScript(kScript)); | 
|---|
| 152 | const auto& function = | 
|---|
| 153 | Function::Handle(GetFunction(root_library, "setValue")); | 
|---|
| 154 |  | 
|---|
| 155 | Invoke(root_library, "main"); | 
|---|
| 156 |  | 
|---|
| 157 | TestPipeline pipeline(function, CompilerPass::kJIT); | 
|---|
| 158 | FlowGraph* flow_graph = pipeline.RunPasses({ | 
|---|
| 159 | CompilerPass::kComputeSSA, | 
|---|
| 160 | CompilerPass::kApplyICData, | 
|---|
| 161 | CompilerPass::kTryOptimizePatterns, | 
|---|
| 162 | CompilerPass::kSetOuterInliningId, | 
|---|
| 163 | CompilerPass::kTypePropagation, | 
|---|
| 164 | CompilerPass::kApplyClassIds, | 
|---|
| 165 | CompilerPass::kInlining, | 
|---|
| 166 | }); | 
|---|
| 167 |  | 
|---|
| 168 | auto entry = flow_graph->graph_entry()->normal_entry(); | 
|---|
| 169 |  | 
|---|
| 170 | EXPECT(entry->initial_definitions()->length() == 2); | 
|---|
| 171 | EXPECT(entry->initial_definitions()->At(0)->IsParameter()); | 
|---|
| 172 | EXPECT(entry->initial_definitions()->At(1)->IsParameter()); | 
|---|
| 173 | ParameterInstr* list_param = | 
|---|
| 174 | entry->initial_definitions()->At(0)->AsParameter(); | 
|---|
| 175 | ParameterInstr* value_param = | 
|---|
| 176 | entry->initial_definitions()->At(1)->AsParameter(); | 
|---|
| 177 |  | 
|---|
| 178 | ILMatcher cursor(flow_graph, entry); | 
|---|
| 179 |  | 
|---|
| 180 | CheckArrayBoundInstr* bounds_check_instr = nullptr; | 
|---|
| 181 | UnboxInt32Instr* unbox_instr = nullptr; | 
|---|
| 182 | StoreIndexedInstr* store_instr = nullptr; | 
|---|
| 183 |  | 
|---|
| 184 | RELEASE_ASSERT(cursor.TryMatch({ | 
|---|
| 185 | {kMoveGlob}, | 
|---|
| 186 | {kMatchAndMoveCheckArrayBound, &bounds_check_instr}, | 
|---|
| 187 | {kMatchAndMoveUnboxInt32, &unbox_instr}, | 
|---|
| 188 | {kMatchAndMoveStoreIndexed, &store_instr}, | 
|---|
| 189 | })); | 
|---|
| 190 |  | 
|---|
| 191 | RELEASE_ASSERT(unbox_instr->InputAt(0)->definition() == value_param); | 
|---|
| 192 | RELEASE_ASSERT(store_instr->InputAt(0)->definition() == list_param); | 
|---|
| 193 | RELEASE_ASSERT(store_instr->InputAt(2)->definition() == unbox_instr); | 
|---|
| 194 | RELEASE_ASSERT(unbox_instr->is_truncating()); | 
|---|
| 195 | } | 
|---|
| 196 |  | 
|---|
| 197 | #if defined(DART_PRECOMPILER) | 
|---|
| 198 |  | 
|---|
| 199 | // Verifies that all calls are inlined in List.generate call | 
|---|
| 200 | // with a simple closure. | 
|---|
| 201 | ISOLATE_UNIT_TEST_CASE(Inliner_List_generate) { | 
|---|
| 202 | const char* kScript = R"( | 
|---|
| 203 |     foo(n) => List<int>.generate(n, (int x) => x, growable: false); | 
|---|
| 204 |     main() { | 
|---|
| 205 |       foo(100); | 
|---|
| 206 |     } | 
|---|
| 207 |   )"; | 
|---|
| 208 |  | 
|---|
| 209 | const auto& root_library = Library::Handle(LoadTestScript(kScript)); | 
|---|
| 210 | const auto& function = Function::Handle(GetFunction(root_library, "foo")); | 
|---|
| 211 |  | 
|---|
| 212 | TestPipeline pipeline(function, CompilerPass::kAOT); | 
|---|
| 213 | FlowGraph* flow_graph = pipeline.RunPasses({}); | 
|---|
| 214 |  | 
|---|
| 215 | auto entry = flow_graph->graph_entry()->normal_entry(); | 
|---|
| 216 | ILMatcher cursor(flow_graph, entry, /*trace=*/true, | 
|---|
| 217 | ParallelMovesHandling::kSkip); | 
|---|
| 218 |  | 
|---|
| 219 | if (function.is_declared_in_bytecode()) { | 
|---|
| 220 | RELEASE_ASSERT(cursor.TryMatch({ | 
|---|
| 221 | kMoveGlob, | 
|---|
| 222 | kMatchAndMoveCreateArray, | 
|---|
| 223 | kWordSize == 8 ? kMatchAndMoveUnboxInt64 : kNop, | 
|---|
| 224 | kMatchAndMoveGoto, | 
|---|
| 225 |  | 
|---|
| 226 | // Loop header | 
|---|
| 227 | kMatchAndMoveJoinEntry, | 
|---|
| 228 | kMatchAndMoveCheckStackOverflow, | 
|---|
| 229 | kMatchAndMoveUnboxInt64, | 
|---|
| 230 | kMatchAndMoveBranchTrue, | 
|---|
| 231 |  | 
|---|
| 232 | // Loop body | 
|---|
| 233 | kMatchAndMoveTargetEntry, | 
|---|
| 234 | kMatchAndMoveGenericCheckBound, | 
|---|
| 235 | kMatchAndMoveStoreIndexed, | 
|---|
| 236 | kMatchAndMoveCheckedSmiOp, | 
|---|
| 237 | kMatchAndMoveGoto, | 
|---|
| 238 |  | 
|---|
| 239 | // Loop header once again | 
|---|
| 240 | kMatchAndMoveJoinEntry, | 
|---|
| 241 | kMatchAndMoveCheckStackOverflow, | 
|---|
| 242 | kMatchAndMoveUnboxInt64, | 
|---|
| 243 | kMatchAndMoveBranchFalse, | 
|---|
| 244 |  | 
|---|
| 245 | // After loop | 
|---|
| 246 | kMatchAndMoveTargetEntry, | 
|---|
| 247 | kMatchReturn, | 
|---|
| 248 | })); | 
|---|
| 249 | } else { | 
|---|
| 250 | Instruction* unbox1 = nullptr; | 
|---|
| 251 | Instruction* unbox2 = nullptr; | 
|---|
| 252 |  | 
|---|
| 253 | RELEASE_ASSERT(cursor.TryMatch({ | 
|---|
| 254 | kMoveGlob, | 
|---|
| 255 | kMatchAndMoveCreateArray, | 
|---|
| 256 | kMatchAndMoveUnboxInt64, | 
|---|
| 257 | {kMoveAny, &unbox1}, | 
|---|
| 258 | {kMoveAny, &unbox2}, | 
|---|
| 259 | kMatchAndMoveGoto, | 
|---|
| 260 |  | 
|---|
| 261 | // Loop header | 
|---|
| 262 | kMatchAndMoveJoinEntry, | 
|---|
| 263 | kMatchAndMoveCheckStackOverflow, | 
|---|
| 264 | kMatchAndMoveBranchTrue, | 
|---|
| 265 |  | 
|---|
| 266 | // Loop body | 
|---|
| 267 | kMatchAndMoveTargetEntry, | 
|---|
| 268 | kWordSize == 4 ? kMatchAndMoveBoxInt64 : kNop, | 
|---|
| 269 | kMatchAndMoveBoxInt64, | 
|---|
| 270 | kMatchAndMoveStoreIndexed, | 
|---|
| 271 | kMatchAndMoveBinaryInt64Op, | 
|---|
| 272 | kMatchAndMoveGoto, | 
|---|
| 273 |  | 
|---|
| 274 | // Loop header once again | 
|---|
| 275 | kMatchAndMoveJoinEntry, | 
|---|
| 276 | kMatchAndMoveCheckStackOverflow, | 
|---|
| 277 | kMatchAndMoveBranchFalse, | 
|---|
| 278 |  | 
|---|
| 279 | // After loop | 
|---|
| 280 | kMatchAndMoveTargetEntry, | 
|---|
| 281 | kMatchReturn, | 
|---|
| 282 | })); | 
|---|
| 283 |  | 
|---|
| 284 | EXPECT(unbox1->IsUnboxedConstant() || unbox1->IsUnboxInt64()); | 
|---|
| 285 | EXPECT(unbox2->IsUnboxedConstant() || unbox2->IsUnboxInt64()); | 
|---|
| 286 | } | 
|---|
| 287 | } | 
|---|
| 288 |  | 
|---|
| 289 | #endif  // defined(DART_PRECOMPILER) | 
|---|
| 290 |  | 
|---|
| 291 | }  // namespace dart | 
|---|
| 292 |  | 
|---|