1 | // Copyright (c) 2012, 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/il.h" |
6 | |
7 | #include <vector> |
8 | |
9 | #include "platform/utils.h" |
10 | #include "vm/compiler/backend/il_test_helper.h" |
11 | #include "vm/unit_test.h" |
12 | |
13 | namespace dart { |
14 | |
15 | ISOLATE_UNIT_TEST_CASE(InstructionTests) { |
16 | TargetEntryInstr* target_instr = |
17 | new TargetEntryInstr(1, kInvalidTryIndex, DeoptId::kNone); |
18 | EXPECT(target_instr->IsBlockEntry()); |
19 | EXPECT(!target_instr->IsDefinition()); |
20 | SpecialParameterInstr* context = new SpecialParameterInstr( |
21 | SpecialParameterInstr::kContext, DeoptId::kNone, target_instr); |
22 | EXPECT(context->IsDefinition()); |
23 | EXPECT(!context->IsBlockEntry()); |
24 | EXPECT(context->GetBlock() == target_instr); |
25 | } |
26 | |
27 | ISOLATE_UNIT_TEST_CASE(OptimizationTests) { |
28 | JoinEntryInstr* join = |
29 | new JoinEntryInstr(1, kInvalidTryIndex, DeoptId::kNone); |
30 | |
31 | Definition* def1 = new PhiInstr(join, 0); |
32 | Definition* def2 = new PhiInstr(join, 0); |
33 | Value* use1a = new Value(def1); |
34 | Value* use1b = new Value(def1); |
35 | EXPECT(use1a->Equals(use1b)); |
36 | Value* use2 = new Value(def2); |
37 | EXPECT(!use2->Equals(use1a)); |
38 | |
39 | ConstantInstr* c1 = new ConstantInstr(Bool::True()); |
40 | ConstantInstr* c2 = new ConstantInstr(Bool::True()); |
41 | EXPECT(c1->Equals(c2)); |
42 | ConstantInstr* c3 = new ConstantInstr(Object::ZoneHandle()); |
43 | ConstantInstr* c4 = new ConstantInstr(Object::ZoneHandle()); |
44 | EXPECT(c3->Equals(c4)); |
45 | EXPECT(!c3->Equals(c1)); |
46 | } |
47 | |
48 | ISOLATE_UNIT_TEST_CASE(IRTest_EliminateWriteBarrier) { |
49 | const char* nullable_tag = TestCase::NullableTag(); |
50 | // clang-format off |
51 | auto kScript = Utils::CStringUniquePtr(OS::SCreate(nullptr, R"( |
52 | class Container<T> { |
53 | operator []=(var index, var value) { |
54 | return data[index] = value; |
55 | } |
56 | |
57 | List<T%s> data = List<T%s>.filled(10, null); |
58 | } |
59 | |
60 | Container<int> x = Container<int>(); |
61 | |
62 | foo() { |
63 | for (int i = 0; i < 10; ++i) { |
64 | x[i] = i; |
65 | } |
66 | } |
67 | )" , nullable_tag, nullable_tag), std::free); |
68 | // clang-format on |
69 | |
70 | const auto& root_library = Library::Handle(LoadTestScript(kScript.get())); |
71 | const auto& function = Function::Handle(GetFunction(root_library, "foo" )); |
72 | |
73 | Invoke(root_library, "foo" ); |
74 | |
75 | TestPipeline pipeline(function, CompilerPass::kJIT); |
76 | FlowGraph* flow_graph = pipeline.RunPasses({}); |
77 | |
78 | auto entry = flow_graph->graph_entry()->normal_entry(); |
79 | EXPECT(entry != nullptr); |
80 | |
81 | StoreIndexedInstr* store_indexed = nullptr; |
82 | |
83 | ILMatcher cursor(flow_graph, entry, true); |
84 | RELEASE_ASSERT(cursor.TryMatch({ |
85 | kMoveGlob, |
86 | kMatchAndMoveBranchTrue, |
87 | kMoveGlob, |
88 | {kMatchStoreIndexed, &store_indexed}, |
89 | })); |
90 | |
91 | EXPECT(!store_indexed->value()->NeedsWriteBarrier()); |
92 | } |
93 | |
94 | static void ExpectStores(FlowGraph* flow_graph, |
95 | const std::vector<const char*>& expected_stores) { |
96 | size_t next_expected_store = 0; |
97 | for (BlockIterator block_it = flow_graph->reverse_postorder_iterator(); |
98 | !block_it.Done(); block_it.Advance()) { |
99 | for (ForwardInstructionIterator it(block_it.Current()); !it.Done(); |
100 | it.Advance()) { |
101 | if (auto store = it.Current()->AsStoreInstanceField()) { |
102 | EXPECT_LT(next_expected_store, expected_stores.size()); |
103 | EXPECT_STREQ(expected_stores[next_expected_store], |
104 | store->slot().Name()); |
105 | next_expected_store++; |
106 | } |
107 | } |
108 | } |
109 | } |
110 | |
111 | static void RunInitializingStoresTest( |
112 | const Library& root_library, |
113 | const char* function_name, |
114 | CompilerPass::PipelineMode mode, |
115 | const std::vector<const char*>& expected_stores) { |
116 | const auto& function = |
117 | Function::Handle(GetFunction(root_library, function_name)); |
118 | TestPipeline pipeline(function, mode); |
119 | FlowGraph* flow_graph = pipeline.RunPasses({ |
120 | CompilerPass::kComputeSSA, |
121 | CompilerPass::kTypePropagation, |
122 | CompilerPass::kApplyICData, |
123 | CompilerPass::kInlining, |
124 | CompilerPass::kTypePropagation, |
125 | CompilerPass::kSelectRepresentations, |
126 | CompilerPass::kCanonicalize, |
127 | CompilerPass::kConstantPropagation, |
128 | }); |
129 | ASSERT(flow_graph != nullptr); |
130 | ExpectStores(flow_graph, expected_stores); |
131 | } |
132 | |
133 | ISOLATE_UNIT_TEST_CASE(IRTest_InitializingStores) { |
134 | // clang-format off |
135 | auto kScript = Utils::CStringUniquePtr(OS::SCreate(nullptr, R"( |
136 | class Bar { |
137 | var f; |
138 | var g; |
139 | |
140 | Bar({this.f, this.g}); |
141 | } |
142 | Bar f1() => Bar(f: 10); |
143 | Bar f2() => Bar(g: 10); |
144 | f3() { |
145 | return () { }; |
146 | } |
147 | f4<T>({T%s value}) { |
148 | return () { return value; }; |
149 | } |
150 | main() { |
151 | f1(); |
152 | f2(); |
153 | f3(); |
154 | f4(); |
155 | } |
156 | )" , |
157 | TestCase::NullableTag()), std::free); |
158 | // clang-format on |
159 | |
160 | const auto& root_library = Library::Handle(LoadTestScript(kScript.get())); |
161 | Invoke(root_library, "main" ); |
162 | |
163 | RunInitializingStoresTest(root_library, "f1" , CompilerPass::kJIT, |
164 | /*expected_stores=*/{"f" }); |
165 | RunInitializingStoresTest(root_library, "f2" , CompilerPass::kJIT, |
166 | /*expected_stores=*/{"g" }); |
167 | RunInitializingStoresTest(root_library, "f3" , CompilerPass::kJIT, |
168 | /*expected_stores=*/ |
169 | {"Closure.function" }); |
170 | |
171 | // Note that in JIT mode we lower context allocation in a way that hinders |
172 | // removal of initializing moves so there would be some redundant stores of |
173 | // null left in the graph. In AOT mode we don't apply this optimization |
174 | // which enables us to remove more stores. |
175 | std::vector<const char*> expected_stores_jit; |
176 | std::vector<const char*> expected_stores_aot; |
177 | if (root_library.is_declared_in_bytecode()) { |
178 | // Bytecode flow graph builder doesn't provide readable |
179 | // variable names for captured variables. Also, bytecode may omit |
180 | // stores of context parent in certain cases. |
181 | expected_stores_jit.insert( |
182 | expected_stores_jit.end(), |
183 | {":context_var0" , "Context.parent" , ":context_var0" , |
184 | "Closure.function_type_arguments" , "Closure.function" , |
185 | "Closure.context" }); |
186 | expected_stores_aot.insert( |
187 | expected_stores_aot.end(), |
188 | {":context_var0" , "Closure.function_type_arguments" , "Closure.function" , |
189 | "Closure.context" }); |
190 | } else { |
191 | // These expectations are for AST-based flow graph builder. |
192 | expected_stores_jit.insert(expected_stores_jit.end(), |
193 | {"value" , "Context.parent" , "Context.parent" , |
194 | "value" , "Closure.function_type_arguments" , |
195 | "Closure.function" , "Closure.context" }); |
196 | expected_stores_aot.insert(expected_stores_aot.end(), |
197 | {"value" , "Closure.function_type_arguments" , |
198 | "Closure.function" , "Closure.context" }); |
199 | } |
200 | |
201 | RunInitializingStoresTest(root_library, "f4" , CompilerPass::kJIT, |
202 | expected_stores_jit); |
203 | RunInitializingStoresTest(root_library, "f4" , CompilerPass::kAOT, |
204 | expected_stores_aot); |
205 | } |
206 | |
207 | } // namespace dart |
208 | |