• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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