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