1 /*
2 * Copyright (c) 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 #ifndef COMPILER_OPTIMIZER_CODEGEN_CODEGEN_INL_H
17 #define COMPILER_OPTIMIZER_CODEGEN_CODEGEN_INL_H
18
19 namespace ark::compiler {
20
21 /// 'live_inputs' shows that inst's source registers should be added the the mask
22 template <bool LIVE_INPUTS>
GetLiveRegisters(Inst * inst)23 std::pair<RegMask, VRegMask> Codegen::GetLiveRegisters(Inst *inst)
24 {
25 RegMask liveRegs;
26 VRegMask liveFpRegs;
27 if (!g_options.IsCompilerSaveOnlyLiveRegisters() || inst == nullptr) {
28 liveRegs.set();
29 liveFpRegs.set();
30 return {liveRegs, liveFpRegs};
31 }
32 // Run LiveRegisters pass only if it is actually required
33 if (!GetGraph()->IsAnalysisValid<LiveRegisters>()) {
34 GetGraph()->RunPass<LiveRegisters>();
35 }
36
37 // Add registers from intervals that are live at inst's definition
38 auto &lr = GetGraph()->GetAnalysis<LiveRegisters>();
39 lr.VisitIntervalsWithLiveRegisters<LIVE_INPUTS>(inst, [&liveRegs, &liveFpRegs, this](const auto &li) {
40 auto reg = ConvertRegister(li->GetReg(), li->GetType());
41 GetEncoder()->SetRegister(&liveRegs, &liveFpRegs, reg);
42 });
43
44 // Add live temp registers
45 liveRegs |= GetEncoder()->GetLiveTmpRegMask();
46 liveFpRegs |= GetEncoder()->GetLiveTmpFpRegMask();
47
48 return {liveRegs, liveFpRegs};
49 }
50
51 template <typename T, typename... Args>
CreateSlowPath(Inst * inst,Args &&...args)52 T *Codegen::CreateSlowPath(Inst *inst, Args &&...args)
53 {
54 static_assert(std::is_base_of_v<SlowPathBase, T>);
55 auto label = GetEncoder()->CreateLabel();
56 auto slowPath = GetLocalAllocator()->New<T>(label, inst, std::forward<Args>(args)...);
57 slowPaths_.push_back(slowPath);
58 return slowPath;
59 }
60
61 /**
62 * Insert tracing code to the generated code. See `Trace` method in the `runtime/entrypoints.cpp`.
63 * NOTE(compiler): we should rework parameters assigning algorithm, that is duplicated here.
64 * @param params parameters to be passed to the TRACE entrypoint, first parameter must be TraceId value.
65 */
66 template <typename... Args>
InsertTrace(Args &&...params)67 void Codegen::InsertTrace(Args &&...params)
68 {
69 SCOPED_DISASM_STR(this, "Trace");
70 [[maybe_unused]] constexpr size_t MAX_PARAM_NUM = 8;
71 static_assert(sizeof...(Args) <= MAX_PARAM_NUM);
72 auto regfile = GetRegfile();
73 auto saveRegs = regfile->GetCallerSavedRegMask();
74 saveRegs.set(GetTarget().GetReturnRegId());
75 auto saveVregs = regfile->GetCallerSavedVRegMask();
76 saveVregs.set(GetTarget().GetReturnFpRegId());
77
78 SaveCallerRegisters(saveRegs, saveVregs, false);
79 FillCallParams(std::forward<Args>(params)...);
80 EmitCallRuntimeCode(nullptr, EntrypointId::TRACE);
81 LoadCallerRegisters(saveRegs, saveVregs, false);
82 }
83
84 template <bool IS_FASTPATH, typename... Args>
CallEntrypoint(Inst * inst,EntrypointId id,Reg dstReg,RegMask preservedRegs,Args &&...params)85 void Codegen::CallEntrypoint(Inst *inst, EntrypointId id, Reg dstReg, RegMask preservedRegs, Args &&...params)
86 {
87 ASSERT(inst != nullptr);
88 CHECK_EQ(sizeof...(Args), GetRuntime()->GetEntrypointArgsNum(id));
89 if (GetArch() == Arch::AARCH32) {
90 // There is a problem with 64-bit parameters:
91 // params number passed from entrypoints_gen.S.erb will be inconsistent with Aarch32 ABI.
92 // Thus, runtime bridges will have wrong params number (\paramsnum macro argument).
93 ASSERT(EnsureParamsFitIn32Bit({params...}));
94 ASSERT(!dstReg.IsValid() || dstReg.GetSize() <= WORD_SIZE);
95 }
96
97 SCOPED_DISASM_STR(this, std::string("CallEntrypoint: ") + GetRuntime()->GetEntrypointName(id));
98 RegMask liveRegs {preservedRegs | GetLiveRegisters(inst).first};
99 RegMask paramsMask;
100 if (inst->HasImplicitRuntimeCall() && !GetRuntime()->IsEntrypointNoreturn(id)) {
101 SaveRegistersForImplicitRuntime(inst, ¶msMask, &liveRegs);
102 }
103
104 ASSERT(IS_FASTPATH == GetRuntime()->IsEntrypointFastPath(id));
105 bool retRegAlive {liveRegs.Test(GetTarget().GetReturnRegId())};
106 // parameter regs: their initial values must be stored by the caller
107 // Other caller regs stored in bridges
108 FillOnlyParameters(&liveRegs, sizeof...(Args), IS_FASTPATH);
109 // When value stored in target return register outlives current call, it must be stored too
110 if (retRegAlive && dstReg.IsValid()) {
111 Reg retReg = GetTarget().GetReturnReg(dstReg.GetType());
112 if (dstReg.GetId() != retReg.GetId()) {
113 GetEncoder()->SetRegister(&liveRegs, nullptr, retReg, true);
114 }
115 }
116
117 SaveCallerRegisters(liveRegs, VRegMask(), true);
118
119 if (sizeof...(Args) != 0) {
120 FillCallParams(std::forward<Args>(params)...);
121 }
122
123 // Call Code
124 if (!EmitCallRuntimeCode(inst, id)) {
125 return;
126 }
127 if (dstReg.IsValid()) {
128 ASSERT(dstReg.IsScalar());
129 GetEncoder()->SetRegister(&liveRegs, nullptr, dstReg, false);
130 Reg retReg = GetTarget().GetReturnReg(dstReg.GetType());
131 // We must:
132 // sign extended INT8 and INT16 to INT32
133 // zero extended UINT8 and UINT16 to UINT32
134 if (dstReg.GetSize() < WORD_SIZE) {
135 bool isSigned = DataType::IsTypeSigned(inst->GetType());
136 GetEncoder()->EncodeCast(dstReg.As(INT32_TYPE), isSigned, retReg, isSigned);
137 } else {
138 GetEncoder()->EncodeMov(dstReg, retReg);
139 }
140 }
141 CallEntrypointFinalize(liveRegs, paramsMask, inst);
142 }
143
144 // The function is used for calling runtime functions through special bridges.
145 // !NOTE Don't use the function for calling runtime without bridges(it save only parameters on stack)
146 template <typename... Args>
CallRuntime(Inst * inst,EntrypointId id,Reg dstReg,RegMask preservedRegs,Args &&...params)147 void Codegen::CallRuntime(Inst *inst, EntrypointId id, Reg dstReg, RegMask preservedRegs, Args &&...params)
148 {
149 CallEntrypoint<false>(inst, id, dstReg, preservedRegs, std::forward<Args>(params)...);
150 }
151
152 template <typename... Args>
CallFastPath(Inst * inst,EntrypointId id,Reg dstReg,RegMask preservedRegs,Args &&...params)153 void Codegen::CallFastPath(Inst *inst, EntrypointId id, Reg dstReg, RegMask preservedRegs, Args &&...params)
154 {
155 CallEntrypoint<true>(inst, id, dstReg, preservedRegs, std::forward<Args>(params)...);
156 }
157
158 template <typename... Args>
CallRuntimeWithMethod(Inst * inst,void * method,EntrypointId eid,Reg dstReg,Args &&...params)159 void Codegen::CallRuntimeWithMethod(Inst *inst, void *method, EntrypointId eid, Reg dstReg, Args &&...params)
160 {
161 if (GetGraph()->IsAotMode()) {
162 ScopedTmpReg methodReg(GetEncoder());
163 LoadMethod(methodReg);
164 CallRuntime(inst, eid, dstReg, RegMask::GetZeroMask(), methodReg, std::forward<Args>(params)...);
165 } else {
166 if (Is64BitsArch(GetArch())) {
167 CallRuntime(inst, eid, dstReg, RegMask::GetZeroMask(), TypedImm(reinterpret_cast<uint64_t>(method)),
168 std::forward<Args>(params)...);
169 } else {
170 // uintptr_t causes problems on host cross-jit compilation
171 CallRuntime(inst, eid, dstReg, RegMask::GetZeroMask(), TypedImm(down_cast<uint32_t>(method)),
172 std::forward<Args>(params)...);
173 }
174 }
175 }
176
177 template <typename... Args>
CallBarrier(RegMask liveRegs,VRegMask liveVregs,std::variant<EntrypointId,Reg> entrypoint,Args &&...params)178 void Codegen::CallBarrier(RegMask liveRegs, VRegMask liveVregs, std::variant<EntrypointId, Reg> entrypoint,
179 Args &&...params)
180 {
181 bool isFastpath = GetGraph()->GetMode().IsFastPath();
182 if (isFastpath) {
183 // irtoc fastpath needs to save all caller registers in case of call native function
184 liveRegs = GetCallerRegsMask(GetArch(), false);
185 liveVregs = GetCallerRegsMask(GetArch(), true);
186 }
187 SaveCallerRegisters(liveRegs, liveVregs, !isFastpath);
188 FillCallParams(std::forward<Args>(params)...);
189 EmitCallRuntimeCode(nullptr, entrypoint);
190 LoadCallerRegisters(liveRegs, liveVregs, !isFastpath);
191 }
192
193 template <typename T>
CreateUnaryCheck(Inst * inst,RuntimeInterface::EntrypointId id,DeoptimizeType type,Condition cc)194 void Codegen::CreateUnaryCheck(Inst *inst, RuntimeInterface::EntrypointId id, DeoptimizeType type, Condition cc)
195 {
196 [[maybe_unused]] auto ss = inst->GetSaveState();
197 ASSERT(ss != nullptr && (ss->GetOpcode() == Opcode::SaveState || ss->GetOpcode() == Opcode::SaveStateDeoptimize));
198
199 LabelHolder::LabelId slowPath;
200 if (inst->CanDeoptimize()) {
201 slowPath = CreateSlowPath<SlowPathDeoptimize>(inst, type)->GetLabel();
202 } else {
203 slowPath = CreateSlowPath<T>(inst, id)->GetLabel();
204 }
205 auto srcType = inst->GetInputType(0);
206 auto src = ConvertRegister(inst->GetSrcReg(0), srcType);
207 GetEncoder()->EncodeJump(slowPath, src, cc);
208 }
209
210 // The function alignment up the value from alignment_reg using tmp_reg.
211
GetStackOffset(Location location)212 inline ssize_t Codegen::GetStackOffset(Location location)
213 {
214 if (location.GetKind() == LocationType::STACK_ARGUMENT) {
215 return location.GetValue() * GetFrameLayout().GetSlotSize();
216 }
217
218 if (location.GetKind() == LocationType::STACK_PARAMETER) {
219 return GetFrameLayout().GetFrameSize<CFrameLayout::OffsetUnit::BYTES>() +
220 (location.GetValue() * GetFrameLayout().GetSlotSize());
221 }
222
223 ASSERT(location.GetKind() == LocationType::STACK);
224 return GetFrameLayout().GetSpillOffsetFromSpInBytes(location.GetValue());
225 }
226
GetMemRefForSlot(Location location)227 inline MemRef Codegen::GetMemRefForSlot(Location location)
228 {
229 ASSERT(location.IsAnyStack());
230 return MemRef(SpReg(), GetStackOffset(location));
231 }
232
SpReg()233 inline Reg Codegen::SpReg() const
234 {
235 return GetTarget().GetStackReg();
236 }
237
FpReg()238 inline Reg Codegen::FpReg() const
239 {
240 return GetTarget().GetFrameReg();
241 }
242
GetDisasm()243 inline const Disassembly *Codegen::GetDisasm() const
244 {
245 return &disasm_;
246 }
247
GetDisasm()248 inline Disassembly *Codegen::GetDisasm()
249 {
250 return &disasm_;
251 }
252
AddLiveOut(const BasicBlock * bb,const Register reg)253 inline void Codegen::AddLiveOut(const BasicBlock *bb, const Register reg)
254 {
255 liveOuts_[bb].Set(reg);
256 }
257
GetLiveOut(const BasicBlock * bb)258 inline RegMask Codegen::GetLiveOut(const BasicBlock *bb) const
259 {
260 auto it = liveOuts_.find(bb);
261 return it != liveOuts_.end() ? it->second : RegMask();
262 }
263
ThreadReg()264 inline Reg Codegen::ThreadReg() const
265 {
266 return Reg(GetThreadReg(GetArch()), GetTarget().GetPtrRegType());
267 }
268
OffsetFitReferenceTypeSize(uint64_t offset)269 inline bool Codegen::OffsetFitReferenceTypeSize(uint64_t offset) const
270 {
271 // -1 because some arch uses signed offset
272 // NOLINTNEXTLINE(hicpp-signed-bitwise)
273 uint64_t maxOffset = 1ULL << (DataType::GetTypeSize(DataType::REFERENCE, GetArch()) - 1);
274 return offset < maxOffset;
275 }
276
GetUsedRegs()277 inline RegMask Codegen::GetUsedRegs() const
278 {
279 return usedRegs_;
280 }
GetUsedVRegs()281 inline RegMask Codegen::GetUsedVRegs() const
282 {
283 return usedVregs_;
284 }
285
GetVtableShift()286 inline uint32_t Codegen::GetVtableShift()
287 {
288 // The size of the VTable element is equal to the size of pointers for the architecture
289 // (not the size of pointer to objects)
290 constexpr uint32_t SHIFT_64_BITS = 3;
291 constexpr uint32_t SHIFT_32_BITS = 2;
292 return Is64BitsArch(GetGraph()->GetArch()) ? SHIFT_64_BITS : SHIFT_32_BITS;
293 }
294
295 template <typename Arg, typename... Args>
AddParamRegsInLiveMasksHandleArgs(ParameterInfo * paramInfo,RegMask * liveRegs,VRegMask * liveVregs,Arg param,Args &&...params)296 ALWAYS_INLINE void Codegen::AddParamRegsInLiveMasksHandleArgs(ParameterInfo *paramInfo, RegMask *liveRegs,
297 VRegMask *liveVregs, Arg param, Args &&...params)
298 {
299 auto currDst = paramInfo->GetNativeParam(param.GetType());
300 if (std::holds_alternative<Reg>(currDst)) {
301 auto reg = std::get<Reg>(currDst);
302 if (reg.IsScalar()) {
303 liveRegs->set(reg.GetId());
304 } else {
305 liveVregs->set(reg.GetId());
306 }
307 } else {
308 GetEncoder()->SetFalseResult();
309 UNREACHABLE();
310 }
311 if constexpr (sizeof...(Args) != 0) {
312 AddParamRegsInLiveMasksHandleArgs(paramInfo, liveRegs, liveVregs, std::forward<Args>(params)...);
313 }
314 }
315
316 template <typename... Args>
AddParamRegsInLiveMasks(RegMask * liveRegs,VRegMask * liveVregs,Args &&...params)317 void Codegen::AddParamRegsInLiveMasks(RegMask *liveRegs, VRegMask *liveVregs, Args &&...params)
318 {
319 auto callconv = GetCallingConvention();
320 auto paramInfo = callconv->GetParameterInfo(0);
321 AddParamRegsInLiveMasksHandleArgs(paramInfo, liveRegs, liveVregs, std::forward<Args>(params)...);
322 }
323
324 template <typename... Args>
CreateStubCall(Inst * inst,RuntimeInterface::IntrinsicId intrinsicId,Reg dst,Args &&...params)325 void Codegen::CreateStubCall(Inst *inst, RuntimeInterface::IntrinsicId intrinsicId, Reg dst, Args &&...params)
326 {
327 VRegMask liveVregs;
328 RegMask liveRegs;
329 auto enc = GetEncoder();
330
331 AddParamRegsInLiveMasks(&liveRegs, &liveVregs, params...);
332
333 if (dst.IsValid()) {
334 ASSERT(dst.IsScalar());
335 enc->SetRegister(&liveRegs, &liveVregs, dst, false);
336 Reg retVal = GetTarget().GetReturnReg(dst.GetType());
337 if (dst.GetId() != retVal.GetId()) {
338 enc->SetRegister(&liveRegs, &liveVregs, retVal, true);
339 }
340 }
341
342 SaveCallerRegisters(liveRegs, liveVregs, true);
343
344 FillCallParams(std::forward<Args>(params)...);
345 CallIntrinsic(inst, intrinsicId);
346
347 if (inst->GetSaveState() != nullptr) {
348 CreateStackMap(inst);
349 }
350
351 if (dst.IsValid()) {
352 Reg retVal = GetTarget().GetReturnReg(dst.GetType());
353 enc->EncodeMov(dst, retVal);
354 }
355
356 LoadCallerRegisters(liveRegs, liveVregs, true);
357 }
358
359 template <typename T>
EncodeImms(const T & imms,bool skipFirstLocation)360 void Codegen::EncodeImms(const T &imms, bool skipFirstLocation)
361 {
362 auto paramInfo = GetCallingConvention()->GetParameterInfo(0);
363 auto immType = DataType::INT32;
364 if (skipFirstLocation) {
365 paramInfo->GetNextLocation(immType);
366 }
367 for (auto imm : imms) {
368 auto location = paramInfo->GetNextLocation(immType);
369 ASSERT(location.IsFixedRegister());
370 auto dstReg = ConvertRegister(location.GetValue(), immType);
371 GetEncoder()->EncodeMov(dstReg, Imm(imm));
372 }
373 }
374
375 template <typename... Args>
376 void FillPostWrbCallParams(MemRef mem, Args &&...params);
377
378 template <size_t IMM_ARRAY_SIZE>
379 class Codegen::FillCallParamsHelper {
380 public:
381 using ImmsIter = typename std::array<std::pair<Reg, Imm>, IMM_ARRAY_SIZE>::iterator;
382
FillCallParamsHelper(Codegen * cg,ParameterInfo * paramInfo,SpillFillInst * regMoves,ArenaVector<Reg> * spMoves,ImmsIter immsIter)383 FillCallParamsHelper(Codegen *cg, ParameterInfo *paramInfo, SpillFillInst *regMoves, ArenaVector<Reg> *spMoves,
384 ImmsIter immsIter)
385 : cg_(cg), paramInfo_(paramInfo), regMoves_(regMoves), spMoves_(spMoves), immsIter_(immsIter)
386 {
387 }
388
389 template <typename Arg, typename... Args>
FillCallParamsHandleOperands(Arg && arg,Args &&...params)390 ALWAYS_INLINE void FillCallParamsHandleOperands(Arg &&arg, Args &&...params)
391 {
392 Location dst;
393 auto type = arg.GetType().ToDataType();
394 dst = paramInfo_->GetNextLocation(type);
395 if (dst.IsStackArgument()) {
396 cg_->GetEncoder()->SetFalseResult();
397 UNREACHABLE(); // Move to BoundaryFrame
398 }
399
400 static_assert(std::is_same_v<std::decay_t<Arg>, TypedImm> || std::is_convertible_v<Arg, Reg>);
401 if constexpr (std::is_same_v<std::decay_t<Arg>, TypedImm>) {
402 auto reg = cg_->ConvertRegister(dst.GetValue(), type);
403 *immsIter_ = {reg, arg.GetImm()};
404 immsIter_++;
405 } else {
406 Reg reg(std::forward<Arg>(arg));
407 if (reg == cg_->SpReg()) {
408 // SP should be handled separately, since on the ARM64 target it has ID out of range
409 spMoves_->emplace_back(cg_->ConvertRegister(dst.GetValue(), type));
410 } else {
411 regMoves_->AddSpillFill(Location::MakeRegister(reg.GetId(), type), dst, type);
412 }
413 }
414 if constexpr (sizeof...(Args) != 0) {
415 FillCallParamsHandleOperands(std::forward<Args>(params)...);
416 }
417 }
418
419 private:
420 Codegen *cg_ {};
421 ParameterInfo *paramInfo_ {};
422 SpillFillInst *regMoves_ {};
423 ArenaVector<Reg> *spMoves_ {};
424 ImmsIter immsIter_ {};
425 };
426
427 template <typename T, typename... Args>
CountParameters()428 constexpr std::pair<size_t, size_t> CountParameters()
429 {
430 static_assert(std::is_same_v<std::decay_t<T>, TypedImm> != std::is_convertible_v<T, Reg>);
431 if constexpr (sizeof...(Args) != 0) {
432 constexpr auto IMM_REG_COUNT = CountParameters<Args...>();
433
434 if constexpr (std::is_same_v<std::decay_t<T>, TypedImm>) {
435 return {IMM_REG_COUNT.first + 1, IMM_REG_COUNT.second};
436 } else if constexpr (std::is_convertible_v<T, Reg>) {
437 return {IMM_REG_COUNT.first, IMM_REG_COUNT.second + 1};
438 }
439 }
440 return {std::is_same_v<std::decay_t<T>, TypedImm>, std::is_convertible_v<T, Reg>};
441 }
442
443 template <typename... Args>
FillCallParams(Args &&...params)444 void Codegen::FillCallParams(Args &&...params)
445 {
446 SCOPED_DISASM_STR(this, "FillCallParams");
447 if constexpr (sizeof...(Args) != 0) {
448 constexpr size_t IMMEDIATES_COUNT = CountParameters<Args...>().first;
449 constexpr size_t REGS_COUNT = CountParameters<Args...>().second;
450 // Native call - do not add reserve parameters
451 auto paramInfo = GetCallingConvention()->GetParameterInfo(0);
452 std::array<std::pair<Reg, Imm>, IMMEDIATES_COUNT> immediates {};
453 ArenaVector<Reg> spMoves(GetLocalAllocator()->Adapter());
454 auto regMoves = GetGraph()->CreateInstSpillFill();
455 spMoves.reserve(REGS_COUNT);
456 regMoves->GetSpillFills().reserve(REGS_COUNT);
457
458 FillCallParamsHelper<IMMEDIATES_COUNT> h {this, paramInfo, regMoves, &spMoves, immediates.begin()};
459 h.FillCallParamsHandleOperands(std::forward<Args>(params)...);
460
461 // Resolve registers move order and encode
462 spillFillsResolver_.ResolveIfRequired(regMoves);
463 SpillFillEncoder(this, regMoves).EncodeSpillFill();
464
465 // Encode immediates moves
466 for (auto &immValues : immediates) {
467 GetEncoder()->EncodeMov(immValues.first, immValues.second);
468 }
469
470 // Encode moves from SP reg
471 for (auto dst : spMoves) {
472 GetEncoder()->EncodeMov(dst, SpReg());
473 }
474 }
475 }
476
477 template <typename... Args>
FillPostWrbCallParams(MemRef mem,Args &&...params)478 void Codegen::FillPostWrbCallParams(MemRef mem, Args &&...params)
479 {
480 auto base {mem.GetBase().As(TypeInfo::FromDataType(DataType::REFERENCE, GetArch()))};
481 if (mem.HasIndex()) {
482 ASSERT(mem.GetScale() == 0 && !mem.HasDisp());
483 FillCallParams(base, mem.GetIndex(), std::forward<Args>(params)...);
484 } else {
485 FillCallParams(base, TypedImm(mem.GetDisp()), std::forward<Args>(params)...);
486 }
487 }
488
489 } // namespace ark::compiler
490
491 #endif // COMPILER_OPTIMIZER_CODEGEN_CODEGEN_H
492