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" |
6 | #include "vm/compiler/backend/il_printer.h" |
7 | #include "vm/compiler/backend/il_test_helper.h" |
8 | #include "vm/unit_test.h" |
9 | |
10 | namespace dart { |
11 | |
12 | DEBUG_ONLY(DECLARE_FLAG(bool, trace_write_barrier_elimination);) |
13 | |
14 | ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_JoinSuccessors) { |
15 | DEBUG_ONLY(FLAG_trace_write_barrier_elimination = true); |
16 | const char* nullable_tag = TestCase::NullableTag(); |
17 | const char* null_assert_tag = TestCase::NullAssertTag(); |
18 | |
19 | // This is a regression test for a bug where we were using |
20 | // JoinEntry::SuccessorCount() to determine the number of outgoing blocks |
21 | // from the join block. JoinEntry::SuccessorCount() is in fact always 0; |
22 | // JoinEntry::last_instruction()->SuccessorCount() should be used instead. |
23 | // clang-format off |
24 | auto kScript = Utils::CStringUniquePtr( |
25 | OS::SCreate(nullptr, R"( |
26 | class C { |
27 | int%s value; |
28 | C%s next; |
29 | C%s prev; |
30 | } |
31 | |
32 | @pragma("vm:never-inline") |
33 | fn() {} |
34 | |
35 | foo(int x) { |
36 | C%s prev = C(); |
37 | C%s next; |
38 | while (x --> 0) { |
39 | next = C(); |
40 | next%s.prev = prev; |
41 | prev?.next = next; |
42 | prev = next; |
43 | fn(); |
44 | } |
45 | return next; |
46 | } |
47 | |
48 | main() { foo(10); } |
49 | )" , |
50 | nullable_tag, nullable_tag, nullable_tag, nullable_tag, |
51 | nullable_tag, null_assert_tag), std::free); |
52 | // clang-format on |
53 | |
54 | const auto& root_library = Library::Handle(LoadTestScript(kScript.get())); |
55 | |
56 | Invoke(root_library, "main" ); |
57 | |
58 | const auto& function = Function::Handle(GetFunction(root_library, "foo" )); |
59 | TestPipeline pipeline(function, CompilerPass::kJIT); |
60 | FlowGraph* flow_graph = pipeline.RunPasses({}); |
61 | |
62 | auto entry = flow_graph->graph_entry()->normal_entry(); |
63 | EXPECT(entry != nullptr); |
64 | |
65 | StoreInstanceFieldInstr* store1 = nullptr; |
66 | StoreInstanceFieldInstr* store2 = nullptr; |
67 | |
68 | ILMatcher cursor(flow_graph, entry); |
69 | RELEASE_ASSERT(cursor.TryMatch({ |
70 | kMoveGlob, |
71 | kMatchAndMoveGoto, |
72 | kMoveGlob, |
73 | kMatchAndMoveBranchTrue, |
74 | kMoveGlob, |
75 | {kMatchAndMoveStoreInstanceField, &store1}, |
76 | kMoveGlob, |
77 | {kMatchAndMoveStoreInstanceField, &store2}, |
78 | })); |
79 | |
80 | EXPECT(store1->ShouldEmitStoreBarrier() == false); |
81 | EXPECT(store2->ShouldEmitStoreBarrier() == true); |
82 | } |
83 | |
84 | ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_AtLeastOnce) { |
85 | DEBUG_ONLY(FLAG_trace_write_barrier_elimination = true); |
86 | // Ensure that we process every block at least once during the analysis |
87 | // phase so that the out-sets will be initialized. If we don't process |
88 | // each block at least once, the store "c.next = n" will be marked |
89 | // NoWriteBarrier. |
90 | // clang-format off |
91 | auto kScript = Utils::CStringUniquePtr(OS::SCreate(nullptr, |
92 | R"( |
93 | class C { |
94 | %s C next; |
95 | } |
96 | |
97 | @pragma("vm:never-inline") |
98 | fn() {} |
99 | |
100 | foo(int x) { |
101 | C c = C(); |
102 | C n = C(); |
103 | if (x > 5) { |
104 | fn(); |
105 | } |
106 | c.next = n; |
107 | return c; |
108 | } |
109 | |
110 | main() { foo(0); foo(10); } |
111 | )" , TestCase::LateTag()), std::free); |
112 | // clang-format on |
113 | const auto& root_library = Library::Handle(LoadTestScript(kScript.get())); |
114 | |
115 | Invoke(root_library, "main" ); |
116 | |
117 | const auto& function = Function::Handle(GetFunction(root_library, "foo" )); |
118 | TestPipeline pipeline(function, CompilerPass::kJIT); |
119 | FlowGraph* flow_graph = pipeline.RunPasses({}); |
120 | |
121 | auto entry = flow_graph->graph_entry()->normal_entry(); |
122 | EXPECT(entry != nullptr); |
123 | |
124 | StoreInstanceFieldInstr* store = nullptr; |
125 | |
126 | ILMatcher cursor(flow_graph, entry); |
127 | RELEASE_ASSERT(cursor.TryMatch({ |
128 | kMoveGlob, |
129 | kMatchAndMoveBranchFalse, |
130 | kMoveGlob, |
131 | kMatchAndMoveGoto, |
132 | kMoveGlob, |
133 | {kMatchAndMoveStoreInstanceField, &store}, |
134 | })); |
135 | |
136 | EXPECT(store->ShouldEmitStoreBarrier() == true); |
137 | } |
138 | |
139 | ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_Arrays) { |
140 | DEBUG_ONLY(FLAG_trace_write_barrier_elimination = true); |
141 | const char* nullable_tag = TestCase::NullableTag(); |
142 | |
143 | // Test that array allocations are not considered usable after a |
144 | // may-trigger-GC instruction (in this case CheckStackOverflow), unlike |
145 | // normal allocations, which are only interruped by a Dart call. |
146 | // clang-format off |
147 | auto kScript = |
148 | Utils::CStringUniquePtr(OS::SCreate(nullptr, R"( |
149 | class C { |
150 | %s C next; |
151 | } |
152 | |
153 | @pragma("vm:never-inline") |
154 | fn() {} |
155 | |
156 | foo(int x) { |
157 | C c = C(); |
158 | C n = C(); |
159 | List<C%s> array = List<C%s>.filled(1, null); |
160 | while (x --> 0) { |
161 | c.next = n; |
162 | n = c; |
163 | c = C(); |
164 | } |
165 | array[0] = c; |
166 | return c; |
167 | } |
168 | |
169 | main() { foo(10); } |
170 | )" , TestCase::LateTag(), nullable_tag, nullable_tag), std::free); |
171 | // clang-format on |
172 | |
173 | const auto& root_library = Library::Handle(LoadTestScript(kScript.get())); |
174 | |
175 | Invoke(root_library, "main" ); |
176 | |
177 | const auto& function = Function::Handle(GetFunction(root_library, "foo" )); |
178 | TestPipeline pipeline(function, CompilerPass::kJIT); |
179 | FlowGraph* flow_graph = pipeline.RunPasses({}); |
180 | |
181 | auto entry = flow_graph->graph_entry()->normal_entry(); |
182 | EXPECT(entry != nullptr); |
183 | |
184 | StoreInstanceFieldInstr* store_into_c = nullptr; |
185 | StoreIndexedInstr* store_into_array = nullptr; |
186 | |
187 | ILMatcher cursor(flow_graph, entry); |
188 | RELEASE_ASSERT(cursor.TryMatch({ |
189 | kMoveGlob, |
190 | kMatchAndMoveGoto, |
191 | kMoveGlob, |
192 | kMatchAndMoveBranchTrue, |
193 | kMoveGlob, |
194 | {kMatchAndMoveStoreInstanceField, &store_into_c}, |
195 | kMoveGlob, |
196 | kMatchAndMoveGoto, |
197 | kMoveGlob, |
198 | kMatchAndMoveBranchFalse, |
199 | kMoveGlob, |
200 | {kMatchAndMoveStoreIndexed, &store_into_array}, |
201 | })); |
202 | |
203 | EXPECT(store_into_c->ShouldEmitStoreBarrier() == false); |
204 | EXPECT(store_into_array->ShouldEmitStoreBarrier() == true); |
205 | } |
206 | |
207 | } // namespace dart |
208 | |