1 /*
2 * Copyright (c) 2025 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 "compiler_logger.h"
17 #include "optimizer/analysis/alias_analysis.h"
18 #include "optimizer/analysis/bounds_analysis.h"
19 #include "optimizer/ir/analysis.h"
20 #include "optimizer/optimizations/native_call_optimization.h"
21
22 namespace ark::compiler {
23
InvalidateAnalyses()24 void NativeCallOptimization::InvalidateAnalyses()
25 {
26 GetGraph()->InvalidateAnalysis<BoundsAnalysis>();
27 GetGraph()->InvalidateAnalysis<AliasAnalysis>();
28 }
29
RunImpl()30 bool NativeCallOptimization::RunImpl()
31 {
32 if (!GetGraph()->CanOptimizeNativeMethods()) {
33 COMPILER_LOG(DEBUG, NATIVE_CALL_OPT)
34 << "Graph " << GetGraph()->GetRuntime()->GetMethodFullName(GetGraph()->GetMethod(), true)
35 << " cannot optimize native methods, skip";
36 return false;
37 }
38
39 VisitGraph();
40 return IsApplied();
41 }
42
VisitCallStatic(GraphVisitor * v,Inst * inst)43 void NativeCallOptimization::VisitCallStatic(GraphVisitor *v, Inst *inst)
44 {
45 CallInst *callInst = inst->CastToCallStatic();
46 auto *that = static_cast<NativeCallOptimization *>(v);
47 auto *runtime = that->GetGraph()->GetRuntime();
48
49 if (!callInst->GetIsNative()) {
50 COMPILER_LOG(DEBUG, NATIVE_CALL_OPT) << "CallStatic with id=" << callInst->GetId() << " is not native, skip";
51 // NOTE: add event here!
52 return;
53 }
54
55 // NOTE: workaround, need to enable back after fixing stack walker & gc roots issue
56 if (runtime->IsNecessarySwitchThreadState(callInst->GetCallMethod())) {
57 COMPILER_LOG(DEBUG, NATIVE_CALL_OPT)
58 << "CallStatic with id=" << callInst->GetId() << " needs to switch exec state, skip (workaround)";
59 return;
60 }
61
62 if (runtime->IsMethodInModuleScope(callInst->GetCallMethod())) {
63 COMPILER_LOG(DEBUG, NATIVE_CALL_OPT)
64 << "CallStatic with id=" << callInst->GetId() << " is in module scope, skip (workaround)";
65 return;
66 }
67
68 if (runtime->CanNativeMethodUseObjects(callInst->GetCallMethod())) {
69 ASSERT(callInst->GetCanNativeException());
70 OptimizeNativeCallWithObjects(v, callInst);
71 } else {
72 ASSERT(!callInst->GetCanNativeException());
73 OptimizePrimitiveNativeCall(v, callInst);
74 }
75 }
76
CreateNativeApiIntrinsic(DataType::Type type,uint32_t pc,RuntimeInterface::IntrinsicId id,const MethodDataMixin * methodData)77 IntrinsicInst *NativeCallOptimization::CreateNativeApiIntrinsic(DataType::Type type, uint32_t pc,
78 RuntimeInterface::IntrinsicId id,
79 const MethodDataMixin *methodData)
80 {
81 IntrinsicInst *intrinsic = GetGraph()->CreateInstIntrinsic(type, pc, id)->CastToIntrinsic();
82 intrinsic->SetCallMethodId(methodData->GetCallMethodId());
83 intrinsic->SetCallMethod(methodData->GetCallMethod());
84 return intrinsic;
85 }
86
OptimizePrimitiveNativeCall(GraphVisitor * v,CallInst * callInst)87 void NativeCallOptimization::OptimizePrimitiveNativeCall(GraphVisitor *v, CallInst *callInst)
88 {
89 auto *that = static_cast<NativeCallOptimization *>(v);
90 auto *graph = that->GetGraph();
91
92 auto pc = callInst->GetPc();
93 auto *methodData = static_cast<MethodDataMixin *>(callInst);
94 auto *saveState = callInst->GetSaveState();
95 ASSERT(saveState != nullptr);
96
97 IntrinsicInst *getNativeMethod = that->CreateNativeApiIntrinsic(
98 DataType::POINTER, pc, RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_GET_NATIVE_METHOD, methodData);
99 getNativeMethod->SetInputs(graph->GetAllocator(), {{saveState, saveState->GetType()}});
100 if (graph->IsJitOrOsrMode()) {
101 getNativeMethod->ClearFlag(inst_flags::Flags::RUNTIME_CALL);
102 }
103
104 IntrinsicInst *getNativePointer = that->CreateNativeApiIntrinsic(
105 DataType::POINTER, pc, RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_GET_METHOD_NATIVE_POINTER, methodData);
106 getNativePointer->SetInputs(graph->GetAllocator(), {{getNativeMethod, getNativeMethod->GetType()}});
107
108 callInst->InsertBefore(getNativeMethod);
109 callInst->InsertBefore(getNativePointer);
110
111 CallInst *callNative = callInst->Clone(graph)->CastToCallStatic();
112 callNative->SetOpcode(Opcode::CallNative);
113 callNative->GetInputTypes()->clear();
114 callNative->AppendInput(getNativePointer, getNativePointer->GetType());
115 for (auto input : callInst->GetInputs()) {
116 auto *inputInst = input.GetInst();
117 callNative->AppendInput(inputInst, inputInst->GetType());
118 }
119
120 callInst->ReplaceUsers(callNative);
121 callInst->RemoveInputs();
122 callInst->GetBasicBlock()->ReplaceInst(callInst, callNative);
123
124 callNative->ClearFlag(inst_flags::Flags::CAN_THROW);
125 callNative->ClearFlag(inst_flags::Flags::HEAP_INV);
126 callNative->ClearFlag(inst_flags::Flags::REQUIRE_STATE);
127 callNative->ClearFlag(inst_flags::Flags::RUNTIME_CALL);
128
129 // remove save state from managed call
130 callNative->RemoveInput(callNative->GetInputsCount() - 1U);
131
132 that->SetIsApplied();
133 COMPILER_LOG(DEBUG, NATIVE_CALL_OPT) << "CallStatic with id=" << callInst->GetId()
134 << " is native and was replaced with CallNative with id="
135 << callNative->GetId();
136 // NOTE: add event here!
137 }
138
139 // CC-OFFNXT(huge_method[C++]) solid logic
OptimizeNativeCallWithObjects(GraphVisitor * v,CallInst * callInst)140 void NativeCallOptimization::OptimizeNativeCallWithObjects(GraphVisitor *v, CallInst *callInst)
141 {
142 auto *that = static_cast<NativeCallOptimization *>(v);
143 auto *graph = that->GetGraph();
144 auto *runtime = graph->GetRuntime();
145
146 auto pc = callInst->GetPc();
147 auto *methodData = static_cast<MethodDataMixin *>(callInst);
148 auto *externalSaveState = callInst->GetSaveState();
149 ASSERT(externalSaveState != nullptr);
150
151 auto *internalSaveState = CopySaveState(graph, externalSaveState);
152 internalSaveState->RemoveNumericInputs();
153 for (size_t i = 0U; i < internalSaveState->GetInputsCount(); ++i) {
154 internalSaveState->SetVirtualRegister(i, VirtualRegister(VirtualRegister::BRIDGE, VRegType::VREG));
155 }
156 internalSaveState->SetCallerInst(callInst);
157 internalSaveState->SetInliningDepth(externalSaveState->GetInliningDepth() + 1U);
158 internalSaveState->SetPc(INVALID_PC);
159
160 Inst *thisInput = nullptr;
161 if (!runtime->IsMethodStatic(callInst->GetCallMethod())) {
162 auto *nullcheckInst = callInst->GetObjectInst();
163 if (nullcheckInst->GetOpcode() == Opcode::NullCheck) {
164 thisInput = nullcheckInst;
165 }
166 }
167
168 IntrinsicInst *getNativeMethod = that->CreateNativeApiIntrinsic(
169 DataType::POINTER, pc, RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_GET_NATIVE_METHOD, methodData);
170 getNativeMethod->SetInputs(graph->GetAllocator(), {{externalSaveState, externalSaveState->GetType()}});
171 if (graph->IsJitOrOsrMode()) {
172 getNativeMethod->ClearFlag(inst_flags::Flags::RUNTIME_CALL);
173 }
174
175 auto deoptComp = graph->CreateInstCompare(DataType::BOOL, pc, getNativeMethod, graph->FindOrCreateConstant(0),
176 DataType::POINTER, CC_EQ);
177 auto deoptimizeIf =
178 graph->CreateInstDeoptimizeIf(pc, deoptComp, externalSaveState, DeoptimizeType::NOT_SUPPORTED_NATIVE);
179
180 Inst *getManagedClass = nullptr;
181 if (runtime->IsMethodStatic(callInst->GetCallMethod())) {
182 getManagedClass = that->CreateNativeApiIntrinsic(
183 DataType::REFERENCE, INVALID_PC,
184 RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_GET_NATIVE_METHOD_MANAGED_CLASS, methodData);
185 getManagedClass->CastToIntrinsic()->SetInputs(graph->GetAllocator(),
186 {{getNativeMethod, getNativeMethod->GetType()}});
187 }
188
189 IntrinsicInst *getNativePointer = that->CreateNativeApiIntrinsic(
190 DataType::POINTER, INVALID_PC, RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_GET_METHOD_NATIVE_POINTER,
191 methodData);
192 getNativePointer->SetInputs(graph->GetAllocator(), {{getNativeMethod, getNativeMethod->GetType()}});
193
194 IntrinsicInst *getNativeApiEnv = that->CreateNativeApiIntrinsic(
195 DataType::POINTER, INVALID_PC, RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_GET_NATIVE_API_ENV,
196 methodData);
197
198 IntrinsicInst *beginNativeMethod = that->CreateNativeApiIntrinsic(
199 DataType::VOID, INVALID_PC, RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_BEGIN_NATIVE_METHOD, methodData);
200 beginNativeMethod->SetInputs(graph->GetAllocator(), {{internalSaveState, internalSaveState->GetType()}});
201
202 CallInst *callNative = callInst->Clone(graph)->CastToCallStatic();
203 callNative->SetOpcode(Opcode::CallNative);
204 callNative->GetInputTypes()->clear();
205 callInst->ReplaceUsers(callNative);
206
207 IntrinsicInst *endNativeMethod = nullptr;
208 if (!DataType::IsReference(callNative->GetType())) {
209 endNativeMethod = that->CreateNativeApiIntrinsic(
210 DataType::VOID, INVALID_PC, RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_END_NATIVE_METHOD_PRIM,
211 methodData);
212 endNativeMethod->SetInputs(graph->GetAllocator(), {{internalSaveState, internalSaveState->GetType()}});
213 } else {
214 callNative->SetType(DataType::POINTER);
215 endNativeMethod = that->CreateNativeApiIntrinsic(
216 DataType::REFERENCE, INVALID_PC, RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_END_NATIVE_METHOD_OBJ,
217 methodData);
218 callNative->ReplaceUsers(endNativeMethod);
219 endNativeMethod->SetInputs(graph->GetAllocator(), {{callNative, callNative->GetType()},
220 {internalSaveState, internalSaveState->GetType()}});
221 }
222
223 auto *checkException = that->CreateNativeApiIntrinsic(
224 DataType::VOID, pc, RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CHECK_NATIVE_EXCEPTION, methodData);
225 checkException->SetInputs(graph->GetAllocator(), {{externalSaveState, externalSaveState->GetType()}});
226
227 auto *returnInlined = graph->CreateInstReturnInlined(DataType::VOID, pc, externalSaveState);
228
229 callInst->InsertBefore(getNativeMethod);
230 callInst->InsertBefore(deoptComp);
231 callInst->InsertBefore(deoptimizeIf);
232 InstAppender appender(callInst->GetBasicBlock(), callInst);
233 if (getManagedClass != nullptr) {
234 appender.Append({internalSaveState, getManagedClass, getNativePointer, getNativeApiEnv, beginNativeMethod});
235 } else {
236 appender.Append({internalSaveState, getNativePointer, getNativeApiEnv, beginNativeMethod});
237 }
238 for (size_t i = 0U; i < callInst->GetInputsCount(); ++i) {
239 Inst *input = callInst->GetInput(i).GetInst();
240 if (DataType::IsReference(input->GetType())) {
241 auto *wrapObjectNative = graph->CreateInstWrapObjectNative(DataType::POINTER, INVALID_PC, input);
242 callInst->SetInput(i, wrapObjectNative);
243 appender.Append(wrapObjectNative);
244 }
245 }
246 if (getManagedClass != nullptr) {
247 auto *wrapObjectNative = graph->CreateInstWrapObjectNative(DataType::POINTER, INVALID_PC, getManagedClass);
248 appender.Append(wrapObjectNative);
249 getManagedClass = wrapObjectNative;
250 }
251 appender.Append({callNative, endNativeMethod, returnInlined, checkException});
252
253 callNative->AppendInput(getNativePointer, getNativePointer->GetType());
254 callNative->AppendInput(getNativeApiEnv, getNativeApiEnv->GetType());
255 if (getManagedClass != nullptr) {
256 callNative->AppendInput(getManagedClass, getManagedClass->GetType());
257 }
258 for (auto input : callInst->GetInputs()) {
259 auto *inputInst = input.GetInst();
260 callNative->AppendInput(inputInst, inputInst->GetType());
261 }
262 callNative->SetSaveState(internalSaveState);
263
264 callInst->RemoveInputs();
265 if (thisInput != nullptr) {
266 callInst->AppendInput(thisInput);
267 }
268 callInst->AppendInput(externalSaveState);
269
270 callInst->SetInlined(true);
271 callInst->SetFlag(inst_flags::NO_DST);
272 callInst->SetFlag(inst_flags::NO_DCE);
273
274 static_cast<NativeCallOptimization *>(v)->SetIsApplied();
275 COMPILER_LOG(DEBUG, NATIVE_CALL_OPT) << "CallStatic with id=" << callInst->GetId()
276 << " is native and was replaced with CallNative with id="
277 << callNative->GetId();
278 // NOTE: add event here!
279 }
280
281 } // namespace ark::compiler
282