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 "optimizer/ir_builder/inst_builder.h"
17 #include "optimizer/ir_builder/ir_builder.h"
18 #include "optimizer/ir/inst.h"
19 #include "bytecode_instruction.h"
20 #include "bytecode_instruction-inl.h"
21
22 namespace ark::compiler {
23
24 template <RuntimeInterface::IntrinsicId ID, DataType::Type RET_TYPE, DataType::Type... PARAM_TYPES>
25 struct IntrinsicBuilder {
26 static constexpr size_t N = sizeof...(PARAM_TYPES);
27 template <typename... ARGS>
Buildark::compiler::IntrinsicBuilder28 static IntrinsicInst *Build(InstBuilder *ib, size_t pc, const ARGS &...inputs)
29 {
30 static_assert(sizeof...(inputs) == N + 1);
31 return ib->BuildInteropIntrinsic<N>(pc, ID, RET_TYPE, {PARAM_TYPES...}, {inputs...});
32 }
33 };
34
35 using IntrinsicCompilerConvertJSValueToLocal =
36 IntrinsicBuilder<RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_JS_VALUE_TO_LOCAL, DataType::POINTER,
37 DataType::REFERENCE>;
38 using IntrinsicCompilerResolveQualifiedJSCall =
39 IntrinsicBuilder<RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_RESOLVE_QUALIFIED_JS_CALL, DataType::POINTER,
40 DataType::POINTER, DataType::UINT32, DataType::UINT32, DataType::UINT32>;
41 using IntrinsicCompilerLoadResolvedJSCallFunction =
42 IntrinsicBuilder<RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_LOAD_RESOLVED_JS_FUNCTION, DataType::POINTER,
43 DataType::POINTER>;
44 using IntrinsicCompilerConvertLocalToJSValue =
45 IntrinsicBuilder<RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_JS_VALUE, DataType::REFERENCE,
46 DataType::POINTER>;
47 using IntrinsicCompilerCreateLocalScope =
48 IntrinsicBuilder<RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CREATE_LOCAL_SCOPE, DataType::VOID>;
49 using IntrinsicCompilerDestroyLocalScope =
50 IntrinsicBuilder<RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_DESTROY_LOCAL_SCOPE, DataType::VOID>;
51 using IntrinsicCompilerJSCallCheck = IntrinsicBuilder<RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_JS_CALL_CHECK,
52 DataType::POINTER, DataType::POINTER>;
53 using IntrinsicCompilerInitJSCallClassForCtx =
54 IntrinsicBuilder<RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_INIT_JS_CALL_CLASS_FOR_CTX, DataType::VOID,
55 DataType::REFERENCE>;
56
57 template <size_t N>
BuildInteropIntrinsic(size_t pc,RuntimeInterface::IntrinsicId id,DataType::Type retType,const std::array<DataType::Type,N> & types,const std::array<Inst *,N+1> & inputs)58 IntrinsicInst *InstBuilder::BuildInteropIntrinsic(size_t pc, RuntimeInterface::IntrinsicId id, DataType::Type retType,
59 const std::array<DataType::Type, N> &types,
60 const std::array<Inst *, N + 1> &inputs)
61 {
62 auto intrinsic = GetGraph()->CreateInstIntrinsic(retType, pc, id);
63 intrinsic->AllocateInputTypes(GetGraph()->GetAllocator(), N + 1);
64 for (size_t i = 0; i < N; ++i) {
65 intrinsic->AppendInput(inputs[i], types[i]);
66 }
67 intrinsic->AppendInput(inputs[N], DataType::NO_TYPE); // SaveState input
68 AddInstruction(intrinsic);
69 return intrinsic;
70 }
71
BuildInitJSCallClass(RuntimeInterface::MethodPtr method,size_t pc,SaveStateInst * saveState)72 Inst *InstBuilder::BuildInitJSCallClass(RuntimeInterface::MethodPtr method, size_t pc, SaveStateInst *saveState)
73 {
74 auto klass = GetRuntime()->GetClass(method);
75 auto cpOffsetField = GetRuntime()->GetInteropConstantPoolOffsetField(klass);
76 auto *cpOffsetForClass =
77 BuildLoadStaticInst(pc, DataType::UINT32, GetRuntime()->GetFieldId(cpOffsetField), saveState);
78 AddInstruction(cpOffsetForClass);
79 auto *loadAndInitJSCallClass = cpOffsetForClass->GetInput(0).GetInst();
80
81 // Preload JS strings forming qualified name. Rely on GVN
82 IntrinsicCompilerInitJSCallClassForCtx::Build(this, pc, loadAndInitJSCallClass, saveState);
83 return cpOffsetForClass;
84 }
85
BuildResolveInteropCallIntrinsic(RuntimeInterface::InteropCallKind callKind,size_t pc,RuntimeInterface::MethodPtr method,Inst * arg0,Inst * arg1,Inst * arg2,Inst * cpOffsetForClass,SaveStateInst * saveState)86 std::pair<Inst *, Inst *> InstBuilder::BuildResolveInteropCallIntrinsic(RuntimeInterface::InteropCallKind callKind,
87 size_t pc, RuntimeInterface::MethodPtr method,
88 Inst *arg0, Inst *arg1, Inst *arg2,
89 Inst *cpOffsetForClass,
90 SaveStateInst *saveState)
91 {
92 IntrinsicInst *jsThis = nullptr;
93 IntrinsicInst *jsFn = nullptr;
94 if (callKind == RuntimeInterface::InteropCallKind::CALL_BY_VALUE) {
95 ASSERT(arg2 == nullptr);
96 jsFn = IntrinsicCompilerConvertJSValueToLocal::Build(this, pc, arg0, saveState);
97 jsThis = IntrinsicCompilerConvertJSValueToLocal::Build(this, pc, arg1, saveState);
98 } else {
99 ASSERT(arg2 != nullptr);
100 auto jsVal = IntrinsicCompilerConvertJSValueToLocal::Build(this, pc, arg0, saveState);
101 // `arg1` and `arg2` are int constants, but may be phi instructions in IRBuilder
102 // We replace `ResolveQualifiedJSCall` by chain of `GetNamedProperty` intrinsics in Peepholes
103
104 jsThis =
105 IntrinsicCompilerResolveQualifiedJSCall::Build(this, pc, jsVal, arg1, arg2, cpOffsetForClass, saveState);
106 jsThis->SetMethod(method);
107 jsFn = IntrinsicCompilerLoadResolvedJSCallFunction::Build(this, pc, jsThis, saveState);
108 }
109 return {jsThis, jsFn};
110 }
111
CreateInteropCallIntrinsic(size_t pc,RuntimeInterface::InteropCallKind callKind)112 IntrinsicInst *InstBuilder::CreateInteropCallIntrinsic(size_t pc, RuntimeInterface::InteropCallKind callKind)
113 {
114 RuntimeInterface::IntrinsicId callId = RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_JS_CALL_FUNCTION;
115 if (callKind == RuntimeInterface::InteropCallKind::NEW_INSTANCE) {
116 callId = RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_JS_NEW_INSTANCE;
117 }
118 return GetGraph()->CreateInstIntrinsic(DataType::POINTER, pc, callId);
119 }
120
BuildReturnValueConvertInteropIntrinsic(RuntimeInterface::InteropCallKind callKind,size_t pc,RuntimeInterface::MethodPtr method,Inst * jsCall,SaveStateInst * saveState)121 void InstBuilder::BuildReturnValueConvertInteropIntrinsic(RuntimeInterface::InteropCallKind callKind, size_t pc,
122 RuntimeInterface::MethodPtr method, Inst *jsCall,
123 SaveStateInst *saveState)
124 {
125 if (callKind == RuntimeInterface::InteropCallKind::NEW_INSTANCE) {
126 auto ret = IntrinsicCompilerConvertLocalToJSValue::Build(this, pc, jsCall, saveState);
127 UpdateDefinitionAcc(ret);
128 } else {
129 auto retIntrinsicId = GetGraph()->GetRuntime()->GetInfoForInteropCallRetValueConversion(method);
130 if (retIntrinsicId.has_value()) {
131 Inst *ret = nullptr;
132 auto [id, retType] = retIntrinsicId.value();
133 if (id == RuntimeInterface::IntrinsicId::INTRINSIC_COMPILER_CONVERT_LOCAL_TO_REF_TYPE) {
134 auto loadClass = BuildLoadClass(GetRuntime()->GetMethodReturnTypeId(method), pc, saveState);
135 loadClass->ClearFlag(inst_flags::NO_HOIST);
136 AddInstruction(loadClass);
137 // LoadClass returns ref, create a new SaveState
138 saveState = CreateSaveState(Opcode::SaveState, pc);
139 AddInstruction(saveState);
140 ret = BuildInteropIntrinsic<2U>(pc, id, retType, {DataType::REFERENCE, DataType::POINTER},
141 {loadClass, jsCall, saveState});
142 } else {
143 ret = BuildInteropIntrinsic<1>(pc, id, retType, {DataType::POINTER}, {jsCall, saveState});
144 }
145 UpdateDefinitionAcc(ret);
146 }
147 }
148 }
149
BuildInteropCall(const BytecodeInstruction * bcInst,RuntimeInterface::InteropCallKind callKind,RuntimeInterface::MethodPtr method,bool isRange,bool accRead)150 void InstBuilder::BuildInteropCall(const BytecodeInstruction *bcInst, RuntimeInterface::InteropCallKind callKind,
151 RuntimeInterface::MethodPtr method, bool isRange, bool accRead)
152 {
153 auto pc = GetPc(bcInst->GetAddress());
154 auto saveState = CreateSaveState(Opcode::SaveState, pc);
155 AddInstruction(saveState);
156
157 // Create LOCAL scope
158 IntrinsicCompilerCreateLocalScope::Build(this, pc, saveState);
159 // Resolve call target
160 Inst *arg2 = nullptr;
161 uint32_t skipArgs = 2;
162 Inst *cpOffsetForClass = nullptr;
163 if (callKind != RuntimeInterface::InteropCallKind::CALL_BY_VALUE) {
164 cpOffsetForClass = BuildInitJSCallClass(method, pc, saveState);
165 arg2 = GetArgDefinition(bcInst, 2U, accRead, isRange);
166 skipArgs++;
167 }
168
169 auto [jsThis, jsFn] = BuildResolveInteropCallIntrinsic(
170 callKind, pc, method, GetArgDefinition(bcInst, 0, accRead, isRange),
171 GetArgDefinition(bcInst, 1, accRead, isRange), arg2, cpOffsetForClass, saveState);
172 // js call check
173 auto jsCallCheck = IntrinsicCompilerJSCallCheck::Build(this, pc, jsFn, saveState);
174
175 // js call
176 auto jsCall = CreateInteropCallIntrinsic(pc, callKind);
177 ArenaVector<std::pair<RuntimeInterface::IntrinsicId, DataType::Type>> intrinsicsIds(
178 GetGraph()->GetLocalAllocator()->Adapter());
179 GetGraph()->GetRuntime()->GetInfoForInteropCallArgsConversion(method, skipArgs, &intrinsicsIds);
180 if (callKind != RuntimeInterface::InteropCallKind::NEW_INSTANCE) {
181 jsCall->AllocateInputTypes(GetGraph()->GetAllocator(), intrinsicsIds.size() + 4U);
182 jsCall->AppendInput(jsThis, DataType::POINTER);
183 } else {
184 jsCall->AllocateInputTypes(GetGraph()->GetAllocator(), intrinsicsIds.size() + 3U);
185 }
186 jsCall->AppendInputs(
187 {{jsCallCheck, DataType::POINTER}, {GetGraph()->FindOrCreateConstant(intrinsicsIds.size()), DataType::UINT32}});
188
189 // Convert args
190 size_t argIdx = 0;
191 for (auto [intrinsicId, type] : intrinsicsIds) {
192 Inst *arg = nullptr;
193 if (type != DataType::NO_TYPE) {
194 arg = BuildInteropIntrinsic<1>(pc, intrinsicId, DataType::POINTER, {type},
195 {GetArgDefinition(bcInst, argIdx + skipArgs, accRead, isRange), saveState});
196 } else {
197 arg = BuildInteropIntrinsic<0>(pc, intrinsicId, DataType::POINTER, {}, {saveState});
198 }
199 jsCall->AppendInput(arg, DataType::POINTER);
200 argIdx++;
201 }
202
203 jsCall->AppendInput(saveState, DataType::NO_TYPE);
204 AddInstruction(jsCall);
205
206 // Convert ret value
207 BuildReturnValueConvertInteropIntrinsic(callKind, pc, method, jsCall, saveState);
208 // Create new a SaveState because instruction with ref value was built
209 saveState = CreateSaveState(Opcode::SaveState, pc);
210 AddInstruction(saveState);
211 // Destroy handle scope
212 IntrinsicCompilerDestroyLocalScope::Build(this, pc, saveState);
213 }
214
TryBuildInteropCall(const BytecodeInstruction * bcInst,bool isRange,bool accRead)215 bool InstBuilder::TryBuildInteropCall(const BytecodeInstruction *bcInst, bool isRange, bool accRead)
216 {
217 auto methodId = GetRuntime()->ResolveMethodIndex(GetMethod(), bcInst->GetId(0).AsIndex());
218 auto method = GetRuntime()->GetMethodById(GetMethod(), methodId);
219 if (g_options.IsCompilerEnableFastInterop()) {
220 auto interopCallKind = GetRuntime()->GetInteropCallKind(method);
221 if (interopCallKind != RuntimeInterface::InteropCallKind::UNKNOWN) {
222 auto arg1 = GetArgDefinition(bcInst, 1, accRead, isRange);
223 if (arg1->GetType() == DataType::REFERENCE) {
224 // arg1 is String - this should appear only in tests
225 return false;
226 }
227 BuildInteropCall(bcInst, interopCallKind, method, isRange, accRead);
228 return true;
229 }
230 }
231 return false;
232 }
233 } // namespace ark::compiler
234