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
10namespace dart {
11
12DEBUG_ONLY(DECLARE_FLAG(bool, trace_write_barrier_elimination);)
13
14ISOLATE_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
84ISOLATE_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
139ISOLATE_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