Flutter Engine
The Flutter Engine
write_barrier_elimination_test.cc
Go to the documentation of this file.
1// Copyright (c) 2020, 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 "platform/assert.h"
8#include "vm/unit_test.h"
9
10namespace dart {
11
12DEBUG_ONLY(DECLARE_FLAG(bool, trace_write_barrier_elimination);)
13
14ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_JoinSuccessors) {
16 SetFlagScope<bool> sfs(&FLAG_trace_write_barrier_elimination, true));
17
18 // This is a regression test for a bug where we were using
19 // JoinEntry::SuccessorCount() to determine the number of outgoing blocks
20 // from the join block. JoinEntry::SuccessorCount() is in fact always 0;
21 // JoinEntry::last_instruction()->SuccessorCount() should be used instead.
22 // clang-format off
23 const char* kScript = R"(
24 class C {
25 int? value;
26 C? next;
27 C? prev;
28 }
29
30 @pragma("vm:never-inline")
31 fn() {}
32
33 foo(int x) {
34 C? prev = C();
35 C? next;
36 while (x --> 0) {
37 next = C();
38 next!.prev = prev;
39 prev?.next = next;
40 prev = next;
41 fn();
42 }
43 return next;
44 }
45
46 main() { foo(10); }
47 )";
48 // clang-format on
49
50 const auto& root_library = Library::Handle(LoadTestScript(kScript));
51
52 Invoke(root_library, "main");
53
54 const auto& function = Function::Handle(GetFunction(root_library, "foo"));
56 FlowGraph* flow_graph = pipeline.RunPasses({});
57
58 auto entry = flow_graph->graph_entry()->normal_entry();
59 EXPECT(entry != nullptr);
60
61 StoreFieldInstr* store1 = nullptr;
62 StoreFieldInstr* store2 = nullptr;
63
64 ILMatcher cursor(flow_graph, entry);
66 kMoveGlob,
67 kMatchAndMoveGoto,
68 kMoveGlob,
69 kMatchAndMoveBranchTrue,
70 kMoveGlob,
71 {kMatchAndMoveStoreField, &store1},
73 {kMatchAndMoveStoreField, &store2},
74 }));
75
76 EXPECT(store1->ShouldEmitStoreBarrier() == false);
77 EXPECT(store2->ShouldEmitStoreBarrier() == true);
78}
79
80ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_AtLeastOnce) {
82 SetFlagScope<bool> sfs(&FLAG_trace_write_barrier_elimination, true));
83 // Ensure that we process every block at least once during the analysis
84 // phase so that the out-sets will be initialized. If we don't process
85 // each block at least once, the store "c.next = n" will be marked
86 // NoWriteBarrier.
87 // clang-format off
88 const char* kScript = R"(
89 class C {
90 late C next;
91 }
92
93 @pragma("vm:never-inline")
94 fn() {}
95
96 foo(int x) {
97 C c = C();
98 C n = C();
99 if (x > 5) {
100 fn();
101 }
102 c.next = n;
103 return c;
104 }
105
106 main() { foo(0); foo(10); }
107 )";
108 // clang-format on
109 const auto& root_library = Library::Handle(LoadTestScript(kScript));
110
111 Invoke(root_library, "main");
112
113 const auto& function = Function::Handle(GetFunction(root_library, "foo"));
114 TestPipeline pipeline(function, CompilerPass::kJIT);
115 FlowGraph* flow_graph = pipeline.RunPasses({});
116
117 auto entry = flow_graph->graph_entry()->normal_entry();
118 EXPECT(entry != nullptr);
119
120 StoreFieldInstr* store = nullptr;
121
122 ILMatcher cursor(flow_graph, entry);
123 RELEASE_ASSERT(cursor.TryMatch({
124 kMoveGlob,
125 kMatchAndMoveBranchFalse,
126 kMoveGlob,
127 kMatchAndMoveGoto,
128 kMoveGlob,
129 {kMatchAndMoveStoreField, &store},
130 }));
131
132 EXPECT(store->ShouldEmitStoreBarrier() == true);
133}
134
135static void TestWBEForArrays(int length) {
137 SetFlagScope<bool> sfs(&FLAG_trace_write_barrier_elimination, true));
138
139 // Test that array allocations are considered usable after a
140 // may-trigger-GC instruction (in this case CheckStackOverflow) iff they
141 // are small.
142 // clang-format off
143 CStringUniquePtr kScript(OS::SCreate(nullptr, R"(
144 class C {
145 late C next;
146 }
147
148 @pragma("vm:never-inline")
149 fn() {}
150
151 foo(int x) {
152 C c = C();
153 C n = C();
154 List<C?> array = List<C?>.filled(%d, null);
155 array[0] = c;
156 while (x --> 0) {
157 c.next = n;
158 n = c;
159 c = C();
160 }
161 array[0] = c;
162 return array;
163 }
164
165 main() { foo(10); }
166 )", length));
167 // clang-format on
168
169 // Generate a length dependent test library uri.
170 char lib_uri[256];
171 snprintf(lib_uri, sizeof(lib_uri), "%s%d", RESOLVED_USER_TEST_URI, length);
172
173 const auto& root_library = Library::Handle(
174 LoadTestScript(kScript.get(), /*resolver=*/nullptr, lib_uri));
175
176 Invoke(root_library, "main");
177
178 const auto& function = Function::Handle(GetFunction(root_library, "foo"));
179 TestPipeline pipeline(function, CompilerPass::kJIT);
180 FlowGraph* flow_graph = pipeline.RunPasses({});
181
182 auto entry = flow_graph->graph_entry()->normal_entry();
183 EXPECT(entry != nullptr);
184
185 StoreFieldInstr* store_into_c = nullptr;
186 StoreIndexedInstr* store_into_array_before_loop = nullptr;
187 StoreIndexedInstr* store_into_array_after_loop = nullptr;
188
189 ILMatcher cursor(flow_graph, entry);
190 RELEASE_ASSERT(cursor.TryMatch({
191 kMoveGlob,
192 {kMatchAndMoveStoreIndexed, &store_into_array_before_loop},
193 kMoveGlob,
194 kMatchAndMoveGoto,
195 kMoveGlob,
197 kMoveGlob,
198 {kMatchAndMoveStoreField, &store_into_c},
199 kMoveGlob,
200 kMatchAndMoveGoto,
201 kMoveGlob,
203 kMoveGlob,
204 {kMatchAndMoveStoreIndexed, &store_into_array_after_loop},
205 }));
206
207 EXPECT(store_into_c->ShouldEmitStoreBarrier() == false);
208 EXPECT(store_into_array_before_loop->ShouldEmitStoreBarrier() == false);
209 EXPECT(store_into_array_after_loop->ShouldEmitStoreBarrier() ==
210 (length > Array::kMaxLengthForWriteBarrierElimination));
211}
212
213ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_Arrays) {
215 TestWBEForArrays(Array::kMaxLengthForWriteBarrierElimination);
216 TestWBEForArrays(Array::kMaxLengthForWriteBarrierElimination + 1);
217}
218
219ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_Regress43786) {
221 SetFlagScope<bool> sfs(&FLAG_trace_write_barrier_elimination, true));
222 const char* kScript = R"(
223 foo() {
224 final root = List<dynamic>.filled(128, null);
225 List<dynamic> last = root;
226 for (int i = 0; i < 10 * 1024; ++i) {
227 final nc = List<dynamic>.filled(128, null);
228 last[0] = nc;
229 last = nc;
230 }
231 }
232
233 main() { foo(); }
234 )";
235
236 const auto& root_library = Library::Handle(LoadTestScript(kScript));
237
238 Invoke(root_library, "main");
239
240 const auto& function = Function::Handle(GetFunction(root_library, "foo"));
241 TestPipeline pipeline(function, CompilerPass::kJIT);
242 FlowGraph* flow_graph = pipeline.RunPasses({});
243
244 auto entry = flow_graph->graph_entry()->normal_entry();
245 EXPECT(entry != nullptr);
246
247 StoreIndexedInstr* store_into_phi = nullptr;
248
249 ILMatcher cursor(flow_graph, entry);
251 {
252 kMatchAndMoveCreateArray,
253 kMatchAndMoveGoto,
254 kMatchAndMoveBranchTrue,
255 kMatchAndMoveCreateArray,
256 {kMatchAndMoveStoreIndexed, &store_into_phi},
257 },
258 kMoveGlob));
259
260 EXPECT(store_into_phi->array()->definition()->IsPhi());
261 EXPECT(store_into_phi->ShouldEmitStoreBarrier());
262}
263
264ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_LoadLateField) {
266 SetFlagScope<bool> sfs(&FLAG_trace_write_barrier_elimination, true));
267 const char* kScript = R"(
268 class A {
269 late var x = new B();
270 }
271
272 class B {
273 }
274
275 class C {
276 C(this.a, this.b);
277 A a;
278 B b;
279 }
280
281 foo(A a) => C(a, a.x);
282
283 main() { foo(A()); }
284 )";
285
286 const auto& root_library = Library::Handle(LoadTestScript(kScript));
287
288 Invoke(root_library, "main");
289
290 const auto& function = Function::Handle(GetFunction(root_library, "foo"));
291 TestPipeline pipeline(function, CompilerPass::kJIT);
292 FlowGraph* flow_graph = pipeline.RunPasses({});
293
294 auto entry = flow_graph->graph_entry()->normal_entry();
295 EXPECT(entry != nullptr);
296
297 StoreFieldInstr* store1 = nullptr;
298 StoreFieldInstr* store2 = nullptr;
299
300 ILMatcher cursor(flow_graph, entry);
302 {
303 kMatchAndMoveAllocateObject,
304 kMatchAndMoveLoadField,
305 {kMatchAndMoveStoreField, &store1},
306 {kMatchAndMoveStoreField, &store2},
307 },
308 kMoveGlob));
309
310 EXPECT(!store1->ShouldEmitStoreBarrier());
311 EXPECT(!store2->ShouldEmitStoreBarrier());
312}
313
314ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_LoadLateStaticField) {
316 SetFlagScope<bool> sfs(&FLAG_trace_write_barrier_elimination, true));
317 const char* kScript = R"(
318 class A {
319 }
320
321 class B {
322 }
323
324 class C {
325 C(this.a, this.b);
326 A a;
327 B b;
328 }
329
330 late var x = new B();
331
332 foo(A a) => C(a, x);
333
334 main() { foo(A()); }
335 )";
336
337 const auto& root_library = Library::Handle(LoadTestScript(kScript));
338
339 Invoke(root_library, "main");
340
341 const auto& function = Function::Handle(GetFunction(root_library, "foo"));
342 TestPipeline pipeline(function, CompilerPass::kJIT);
343 FlowGraph* flow_graph = pipeline.RunPasses({});
344
345 auto entry = flow_graph->graph_entry()->normal_entry();
346 EXPECT(entry != nullptr);
347
348 StoreFieldInstr* store1 = nullptr;
349 StoreFieldInstr* store2 = nullptr;
350
351 ILMatcher cursor(flow_graph, entry);
353 {
354 kMatchAndMoveAllocateObject,
355 kMatchAndMoveLoadStaticField,
356 {kMatchAndMoveStoreField, &store1},
357 {kMatchAndMoveStoreField, &store2},
358 },
359 kMoveGlob));
360
361 EXPECT(!store1->ShouldEmitStoreBarrier());
362 EXPECT(!store2->ShouldEmitStoreBarrier());
363}
364
365} // namespace dart
SI void store(P *ptr, const T &val)
#define EXPECT(type, expectedAlignment, expectedSize)
#define RELEASE_ASSERT(cond)
Definition: assert.h:327
GraphEntryInstr * graph_entry() const
Definition: flow_graph.h:268
FunctionEntryInstr * normal_entry() const
Definition: il.h:2001
bool TryMatch(std::initializer_list< MatchCode > match_codes, MatchOpCode insert_before=kInvalidMatchOpCode)
static Object & Handle()
Definition: object.h:407
FlowGraph * RunPasses(std::initializer_list< CompilerPass::Id > passes)
Dart_NativeFunction function
Definition: fuchsia.cc:51
size_t length
SI void store2(uint16_t *ptr, U16 r, U16 g)
Definition: dart_vm.cc:33
LibraryPtr LoadTestScript(const char *script, Dart_NativeEntryResolver resolver, const char *lib_uri)
@ kMatchAndMoveBranchFalse
@ kMatchAndMoveBranchTrue
ObjectPtr Invoke(const Library &lib, const char *name)
FunctionPtr GetFunction(const Library &lib, const char *name)
static void TestWBEForArrays(int length)
ISOLATE_UNIT_TEST_CASE(StackAllocatedDestruction)
DECLARE_FLAG(bool, show_invisible_frames)
#define DEBUG_ONLY(code)
Definition: globals.h:141
#define ISOLATE_UNIT_TEST_CASE(name)
Definition: unit_test.h:64
#define RESOLVED_USER_TEST_URI
Definition: unit_test.h:317