1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "instruction_simplifier.h"
18
19 #include <initializer_list>
20 #include <tuple>
21
22 #include "gtest/gtest.h"
23
24 #include "class_root-inl.h"
25 #include "nodes.h"
26 #include "optimizing/data_type.h"
27 #include "optimizing_unit_test.h"
28
29 namespace art HIDDEN {
30
31 namespace mirror {
32 class ClassExt;
33 class Throwable;
34 } // namespace mirror
35
36 static constexpr bool kDebugSimplifierTests = false;
37
38 template<typename SuperClass>
39 class InstructionSimplifierTestBase : public SuperClass, public OptimizingUnitTestHelper {
40 public:
InstructionSimplifierTestBase()41 InstructionSimplifierTestBase() {
42 this->use_boot_image_ = true; // Make the Runtime creation cheaper.
43 }
44
SetUp()45 void SetUp() override {
46 SuperClass::SetUp();
47 gLogVerbosity.compiler = true;
48 }
49
TearDown()50 void TearDown() override {
51 SuperClass::TearDown();
52 gLogVerbosity.compiler = false;
53 }
54
PerformSimplification(const AdjacencyListGraph & blks)55 void PerformSimplification(const AdjacencyListGraph& blks) {
56 if (kDebugSimplifierTests) {
57 LOG(INFO) << "Pre simplification " << blks;
58 }
59 graph_->ClearDominanceInformation();
60 graph_->BuildDominatorTree();
61 InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
62 simp.Run();
63 if (kDebugSimplifierTests) {
64 LOG(INFO) << "Post simplify " << blks;
65 }
66 }
67 };
68
69 class InstructionSimplifierTest : public InstructionSimplifierTestBase<CommonCompilerTest> {};
70
71 // Various configs we can use for testing. Currently used in PartialComparison tests.
72 enum class InstanceOfKind {
73 kSelf,
74 kUnrelatedLoaded,
75 kUnrelatedUnloaded,
76 kSupertype,
77 };
78
operator <<(std::ostream & os,const InstanceOfKind & comp)79 std::ostream& operator<<(std::ostream& os, const InstanceOfKind& comp) {
80 switch (comp) {
81 case InstanceOfKind::kSupertype:
82 return os << "kSupertype";
83 case InstanceOfKind::kSelf:
84 return os << "kSelf";
85 case InstanceOfKind::kUnrelatedLoaded:
86 return os << "kUnrelatedLoaded";
87 case InstanceOfKind::kUnrelatedUnloaded:
88 return os << "kUnrelatedUnloaded";
89 }
90 }
91
92 class InstanceOfInstructionSimplifierTestGroup
93 : public InstructionSimplifierTestBase<CommonCompilerTestWithParam<InstanceOfKind>> {
94 public:
GetConstantResult() const95 bool GetConstantResult() const {
96 switch (GetParam()) {
97 case InstanceOfKind::kSupertype:
98 case InstanceOfKind::kSelf:
99 return true;
100 case InstanceOfKind::kUnrelatedLoaded:
101 case InstanceOfKind::kUnrelatedUnloaded:
102 return false;
103 }
104 }
105
GetLoadClasses(VariableSizedHandleScope * vshs)106 std::pair<HLoadClass*, HLoadClass*> GetLoadClasses(VariableSizedHandleScope* vshs) {
107 InstanceOfKind kind = GetParam();
108 ScopedObjectAccess soa(Thread::Current());
109 // New inst always needs to have a valid rti since we dcheck that.
110 HLoadClass* new_inst = MakeClassLoad(
111 /* ti= */ std::nullopt, vshs->NewHandle<mirror::Class>(GetClassRoot<mirror::ClassExt>()));
112 new_inst->SetValidLoadedClassRTI();
113 if (kind == InstanceOfKind::kSelf) {
114 return {new_inst, new_inst};
115 }
116 if (kind == InstanceOfKind::kUnrelatedUnloaded) {
117 HLoadClass* target_class = MakeClassLoad();
118 EXPECT_FALSE(target_class->GetLoadedClassRTI().IsValid());
119 return {new_inst, target_class};
120 }
121 // Force both classes to be a real classes.
122 // For simplicity we use class-roots as the types. The new-inst will always
123 // be a ClassExt, unrelated-loaded will always be Throwable and super will
124 // always be Object
125 HLoadClass* target_class = MakeClassLoad(
126 /* ti= */ std::nullopt,
127 vshs->NewHandle<mirror::Class>(kind == InstanceOfKind::kSupertype ?
128 GetClassRoot<mirror::Object>() :
129 GetClassRoot<mirror::Throwable>()));
130 target_class->SetValidLoadedClassRTI();
131 EXPECT_TRUE(target_class->GetLoadedClassRTI().IsValid());
132 return {new_inst, target_class};
133 }
134 };
135
136 // // ENTRY
137 // obj = new Obj();
138 // // Make sure this graph isn't broken
139 // if (obj instanceof <other>) {
140 // // LEFT
141 // } else {
142 // // RIGHT
143 // }
144 // EXIT
145 // return obj.field
TEST_P(InstanceOfInstructionSimplifierTestGroup,ExactClassInstanceOfOther)146 TEST_P(InstanceOfInstructionSimplifierTestGroup, ExactClassInstanceOfOther) {
147 ScopedObjectAccess soa(Thread::Current());
148 VariableSizedHandleScope vshs(soa.Self());
149 InitGraph(/*handles=*/&vshs);
150
151 AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
152 "exit",
153 {{"entry", "left"},
154 {"entry", "right"},
155 {"left", "breturn"},
156 {"right", "breturn"},
157 {"breturn", "exit"}}));
158 #define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
159 GET_BLOCK(entry);
160 GET_BLOCK(exit);
161 GET_BLOCK(breturn);
162 GET_BLOCK(left);
163 GET_BLOCK(right);
164 #undef GET_BLOCK
165 EnsurePredecessorOrder(breturn, {left, right});
166 HInstruction* test_res = graph_->GetIntConstant(GetConstantResult() ? 1 : 0);
167
168 auto [new_inst_klass, target_klass] = GetLoadClasses(&vshs);
169 HInstruction* new_inst = MakeNewInstance(new_inst_klass);
170 new_inst->SetReferenceTypeInfo(
171 ReferenceTypeInfo::Create(new_inst_klass->GetClass(), /*is_exact=*/true));
172 HInstanceOf* instance_of = new (GetAllocator()) HInstanceOf(new_inst,
173 target_klass,
174 TypeCheckKind::kClassHierarchyCheck,
175 target_klass->GetClass(),
176 0u,
177 GetAllocator(),
178 nullptr,
179 nullptr);
180 if (target_klass->GetLoadedClassRTI().IsValid()) {
181 instance_of->SetValidTargetClassRTI();
182 }
183 HInstruction* if_inst = new (GetAllocator()) HIf(instance_of);
184 entry->AddInstruction(new_inst_klass);
185 if (new_inst_klass != target_klass) {
186 entry->AddInstruction(target_klass);
187 }
188 entry->AddInstruction(new_inst);
189 entry->AddInstruction(instance_of);
190 entry->AddInstruction(if_inst);
191 ManuallyBuildEnvFor(new_inst_klass, {});
192 if (new_inst_klass != target_klass) {
193 target_klass->CopyEnvironmentFrom(new_inst_klass->GetEnvironment());
194 }
195 new_inst->CopyEnvironmentFrom(new_inst_klass->GetEnvironment());
196
197 HInstruction* goto_left = new (GetAllocator()) HGoto();
198 left->AddInstruction(goto_left);
199
200 HInstruction* goto_right = new (GetAllocator()) HGoto();
201 right->AddInstruction(goto_right);
202
203 HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
204 HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
205 breturn->AddInstruction(read_bottom);
206 breturn->AddInstruction(return_exit);
207
208 SetupExit(exit);
209
210 PerformSimplification(blks);
211
212 if (!GetConstantResult() || GetParam() == InstanceOfKind::kSelf) {
213 EXPECT_INS_RETAINED(target_klass);
214 } else {
215 EXPECT_INS_REMOVED(target_klass);
216 }
217 EXPECT_INS_REMOVED(instance_of);
218 EXPECT_INS_EQ(if_inst->InputAt(0), test_res);
219 }
220
221 // // ENTRY
222 // obj = new Obj();
223 // (<other>)obj;
224 // // Make sure this graph isn't broken
225 // EXIT
226 // return obj
TEST_P(InstanceOfInstructionSimplifierTestGroup,ExactClassCheckCastOther)227 TEST_P(InstanceOfInstructionSimplifierTestGroup, ExactClassCheckCastOther) {
228 ScopedObjectAccess soa(Thread::Current());
229 VariableSizedHandleScope vshs(soa.Self());
230 InitGraph(/*handles=*/&vshs);
231
232 AdjacencyListGraph blks(SetupFromAdjacencyList("entry", "exit", {{"entry", "exit"}}));
233 #define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
234 GET_BLOCK(entry);
235 GET_BLOCK(exit);
236 #undef GET_BLOCK
237
238 auto [new_inst_klass, target_klass] = GetLoadClasses(&vshs);
239 HInstruction* new_inst = MakeNewInstance(new_inst_klass);
240 new_inst->SetReferenceTypeInfo(
241 ReferenceTypeInfo::Create(new_inst_klass->GetClass(), /*is_exact=*/true));
242 HCheckCast* check_cast = new (GetAllocator()) HCheckCast(new_inst,
243 target_klass,
244 TypeCheckKind::kClassHierarchyCheck,
245 target_klass->GetClass(),
246 0u,
247 GetAllocator(),
248 nullptr,
249 nullptr);
250 if (target_klass->GetLoadedClassRTI().IsValid()) {
251 check_cast->SetValidTargetClassRTI();
252 }
253 HInstruction* entry_return = new (GetAllocator()) HReturn(new_inst);
254 entry->AddInstruction(new_inst_klass);
255 if (new_inst_klass != target_klass) {
256 entry->AddInstruction(target_klass);
257 }
258 entry->AddInstruction(new_inst);
259 entry->AddInstruction(check_cast);
260 entry->AddInstruction(entry_return);
261 ManuallyBuildEnvFor(new_inst_klass, {});
262 if (new_inst_klass != target_klass) {
263 target_klass->CopyEnvironmentFrom(new_inst_klass->GetEnvironment());
264 }
265 new_inst->CopyEnvironmentFrom(new_inst_klass->GetEnvironment());
266
267 SetupExit(exit);
268
269 PerformSimplification(blks);
270
271 if (!GetConstantResult() || GetParam() == InstanceOfKind::kSelf) {
272 EXPECT_INS_RETAINED(target_klass);
273 } else {
274 EXPECT_INS_REMOVED(target_klass);
275 }
276 if (GetConstantResult()) {
277 EXPECT_INS_REMOVED(check_cast);
278 } else {
279 EXPECT_INS_RETAINED(check_cast);
280 }
281 }
282
283 INSTANTIATE_TEST_SUITE_P(InstructionSimplifierTest,
284 InstanceOfInstructionSimplifierTestGroup,
285 testing::Values(InstanceOfKind::kSelf,
286 InstanceOfKind::kUnrelatedLoaded,
287 InstanceOfKind::kUnrelatedUnloaded,
288 InstanceOfKind::kSupertype));
289
290 } // namespace art
291