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