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
14namespace dart {
15
16// Test that the redefinition for an inlined polymorphic function used with
17// multiple receiver cids does not have a concrete type.
18ISOLATE_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
137ISOLATE_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.
201ISOLATE_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