| 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 | |