1 /*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "object_type_check_elimination.h"
17 #include "optimizer/analysis/alias_analysis.h"
18 #include "optimizer/analysis/bounds_analysis.h"
19 #include "optimizer/analysis/dominators_tree.h"
20 #include "optimizer/analysis/loop_analyzer.h"
21 #include "optimizer/analysis/object_type_propagation.h"
22
23 namespace ark::compiler {
RunImpl()24 bool ObjectTypeCheckElimination::RunImpl()
25 {
26 GetGraph()->RunPass<LoopAnalyzer>();
27 GetGraph()->RunPass<ObjectTypePropagation>();
28 GetGraph()->RunPass<DominatorsTree>();
29 VisitGraph();
30 ReplaceCheckMustThrowByUnconditionalDeoptimize();
31 return IsApplied();
32 }
33
VisitIsInstance(GraphVisitor * visitor,Inst * inst)34 void ObjectTypeCheckElimination::VisitIsInstance(GraphVisitor *visitor, Inst *inst)
35 {
36 if (TryEliminateIsInstance(inst)) {
37 static_cast<ObjectTypeCheckElimination *>(visitor)->SetApplied();
38 }
39 }
40
VisitCheckCast(GraphVisitor * visitor,Inst * inst)41 void ObjectTypeCheckElimination::VisitCheckCast(GraphVisitor *visitor, Inst *inst)
42 {
43 auto result = TryEliminateCheckCast(inst);
44 if (result != CheckCastEliminateType::INVALID) {
45 auto opt = static_cast<ObjectTypeCheckElimination *>(visitor);
46 opt->SetApplied();
47 if (result == CheckCastEliminateType::MUST_THROW) {
48 opt->PushNewCheckMustThrow(inst);
49 }
50 }
51 }
52
ReplaceCheckMustThrowByUnconditionalDeoptimize()53 void ObjectTypeCheckElimination::ReplaceCheckMustThrowByUnconditionalDeoptimize()
54 {
55 for (auto &inst : checksMustThrow_) {
56 auto block = inst->GetBasicBlock();
57 if (block != nullptr) {
58 COMPILER_LOG(DEBUG, CHECKS_ELIM)
59 << "Replace check with id = " << inst->GetId() << " by uncondition deoptimize";
60 block->ReplaceInstByDeoptimize(inst);
61 SetApplied();
62 }
63 }
64 }
65
66 /**
67 * This function try to replace IsInstance with a constant.
68 * If input of IsInstance is Nullptr then it replaced by zero constant.
69 */
TryEliminateIsInstance(Inst * inst)70 bool ObjectTypeCheckElimination::TryEliminateIsInstance(Inst *inst)
71 {
72 ASSERT(inst->GetOpcode() == Opcode::IsInstance);
73 if (!inst->HasUsers()) {
74 return false;
75 }
76 auto block = inst->GetBasicBlock();
77 auto graph = block->GetGraph();
78 auto ref = inst->GetDataFlowInput(0);
79 // Null isn't instance of any class.
80 if (ref->GetOpcode() == Opcode::NullPtr) {
81 auto newCnst = graph->FindOrCreateConstant(0);
82 inst->ReplaceUsers(newCnst);
83 return true;
84 }
85 auto isInstance = inst->CastToIsInstance();
86 if (!graph->IsBytecodeOptimizer() && IsMember(ref, isInstance->GetTypeId(), isInstance)) {
87 if (BoundsAnalysis::IsInstNotNull(ref, block)) {
88 auto newCnst = graph->FindOrCreateConstant(true);
89 inst->ReplaceUsers(newCnst);
90 return true;
91 }
92 }
93 auto tgtKlass = graph->GetRuntime()->GetClass(isInstance->GetMethod(), isInstance->GetTypeId());
94 // If we can't resolve klass in runtime we must throw exception, so we check NullPtr after
95 // But we can't change the IsInstance to Deoptimize, because we can resolve after compilation
96 if (tgtKlass == nullptr) {
97 return false;
98 }
99 auto refInfo = ref->GetObjectTypeInfo();
100 if (refInfo) {
101 auto refKlass = refInfo.GetClass();
102 bool result = graph->GetRuntime()->IsAssignableFrom(tgtKlass, refKlass);
103 // If ref can be null, IsInstance cannot be changed to true
104 if (result) {
105 if (graph->IsBytecodeOptimizer()) {
106 // Cannot run BoundsAnalysis
107 return false;
108 }
109 if (!BoundsAnalysis::IsInstNotNull(ref, block)) {
110 return false;
111 }
112 }
113 // If class of ref can be subclass of ref_klass, IsInstance cannot be changed to false
114 if (!result && !refInfo.IsExact()) {
115 return false;
116 }
117 auto newCnst = graph->FindOrCreateConstant(result);
118 inst->ReplaceUsers(newCnst);
119 return true;
120 }
121 return false;
122 }
123
TryEliminateCheckCast(Inst * inst)124 ObjectTypeCheckElimination::CheckCastEliminateType ObjectTypeCheckElimination::TryEliminateCheckCast(Inst *inst)
125 {
126 ASSERT(inst->GetOpcode() == Opcode::CheckCast);
127 auto block = inst->GetBasicBlock();
128 auto graph = block->GetGraph();
129 auto ref = inst->GetDataFlowInput(0);
130 // Null can be cast to every type.
131 if (ref->GetOpcode() == Opcode::NullPtr) {
132 inst->RemoveInputs();
133 block->ReplaceInst(inst, block->GetGraph()->CreateInstNOP());
134 return CheckCastEliminateType::REDUNDANT;
135 }
136 auto checkCast = inst->CastToCheckCast();
137 auto tgtKlass = graph->GetRuntime()->GetClass(checkCast->GetMethod(), checkCast->GetTypeId());
138 auto refInfo = ref->GetObjectTypeInfo();
139 // If we can't resolve klass in runtime we must throw exception, so we check NullPtr after
140 // But we can't change the CheckCast to Deoptimize, because we can resolve after compilation
141 if (tgtKlass != nullptr && refInfo) {
142 auto refKlass = refInfo.GetClass();
143 bool result = graph->GetRuntime()->IsAssignableFrom(tgtKlass, refKlass);
144 if (result) {
145 inst->RemoveInputs();
146 block->ReplaceInst(inst, block->GetGraph()->CreateInstNOP());
147 return CheckCastEliminateType::REDUNDANT;
148 }
149 if (BoundsAnalysis::IsInstNotNull(ref, block) && refInfo.IsExact()) {
150 return CheckCastEliminateType::MUST_THROW;
151 }
152 }
153 if (IsMember(ref, checkCast->GetTypeId(), inst)) {
154 inst->RemoveInputs();
155 block->ReplaceInst(inst, block->GetGraph()->CreateInstNOP());
156 return CheckCastEliminateType::REDUNDANT;
157 }
158 return CheckCastEliminateType::INVALID;
159 }
160
161 // returns true if data flow input of inst is always member of class type_id when ref_user is executed
IsMember(Inst * inst,uint32_t typeId,Inst * refUser)162 bool ObjectTypeCheckElimination::IsMember(Inst *inst, uint32_t typeId, Inst *refUser)
163 {
164 for (auto &user : inst->GetUsers()) {
165 auto userInst = user.GetInst();
166 if (userInst == refUser || !userInst->IsDominate(refUser)) {
167 continue;
168 }
169 bool success = false;
170 switch (userInst->GetOpcode()) {
171 case Opcode::CheckCast:
172 success = (userInst->CastToCheckCast()->GetTypeId() == typeId);
173 break;
174 case Opcode::IsInstance:
175 success = IsSuccessfulIsInstance(userInst->CastToIsInstance(), typeId, refUser);
176 break;
177 case Opcode::NullCheck:
178 success = IsMember(userInst, typeId, refUser);
179 break;
180 default:
181 break;
182 }
183 if (success) {
184 return true;
185 }
186 }
187 return false;
188 }
189
190 // returns true if is_instance has given type_id and evaluates to true at ref_user
IsSuccessfulIsInstance(IsInstanceInst * isInstance,uint32_t typeId,Inst * refUser)191 bool ObjectTypeCheckElimination::IsSuccessfulIsInstance(IsInstanceInst *isInstance, uint32_t typeId, Inst *refUser)
192 {
193 ASSERT(isInstance->GetDataFlowInput(0) == refUser->GetDataFlowInput(0));
194 if (isInstance->GetTypeId() != typeId) {
195 return false;
196 }
197 for (auto &user : isInstance->GetUsers()) {
198 auto userInst = user.GetInst();
199 if (userInst->GetOpcode() != Opcode::IfImm) {
200 continue;
201 }
202 auto trueBlock = userInst->CastToIfImm()->GetEdgeIfInputTrue();
203 if (trueBlock->GetPredsBlocks().size() == 1 && trueBlock->IsDominate(refUser->GetBasicBlock())) {
204 return true;
205 }
206 }
207 return false;
208 }
209
InvalidateAnalyses()210 void ObjectTypeCheckElimination::InvalidateAnalyses()
211 {
212 GetGraph()->InvalidateAnalysis<LoopAnalyzer>();
213 GetGraph()->InvalidateAnalysis<BoundsAnalysis>();
214 GetGraph()->InvalidateAnalysis<AliasAnalysis>();
215 }
216 } // namespace ark::compiler
217