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 #include "ecmascript/compiler/assembler/aarch64/macro_assembler_aarch64.h"
17 #include "ecmascript/js_function.h"
18
19 namespace panda::ecmascript::kungfu {
20 using namespace panda::ecmascript;
21 constexpr uint32_t k4BitSize = 4;
22 constexpr uint32_t k16BitSize = 16;
23 constexpr uint32_t k32BitSize = 32;
24 constexpr uint32_t k48BitSize = 48;
25 constexpr uint32_t k64BitSize = 64;
26
27 const std::set<uint64_t> ValidBitmaskImmSet = {
28 #include "valid_bitmask_imm.txt"
29 };
30 constexpr uint32_t kMaxBitTableSize = 5;
31 constexpr std::array<uint64_t, kMaxBitTableSize> kBitmaskImmMultTable = {
32 0x0000000100000001UL, 0x0001000100010001UL, 0x0101010101010101UL, 0x1111111111111111UL, 0x5555555555555555UL,
33 };
34
Move(const StackSlotOperand & dstStackSlot,Immediate value)35 void MacroAssemblerAArch64::Move(const StackSlotOperand &dstStackSlot, Immediate value)
36 {
37 aarch64::Register baseReg = (dstStackSlot.IsFrameBase()) ? aarch64::Register(aarch64::FP) :
38 aarch64::Register(aarch64::SP);
39 aarch64::MemoryOperand dstOpnd(baseReg, static_cast<int64_t>(dstStackSlot.GetOffset()));
40 assembler.Mov(LOCAL_SCOPE_REGISTER, aarch64::Immediate(value.GetValue()));
41 PickLoadStoreInsn(LOCAL_SCOPE_REGISTER, dstOpnd, false);
42 }
43
Move(const StackSlotOperand & dstStackSlot,const StackSlotOperand & srcStackSlot)44 void MacroAssemblerAArch64::Move(const StackSlotOperand &dstStackSlot,
45 const StackSlotOperand &srcStackSlot)
46 {
47 aarch64::Register dstBaseReg = (dstStackSlot.IsFrameBase()) ? aarch64::Register(aarch64::FP) :
48 aarch64::Register(aarch64::SP);
49 aarch64::MemoryOperand dstOpnd(dstBaseReg, static_cast<int64_t>(dstStackSlot.GetOffset()));
50 aarch64::Register srcBaseReg = (srcStackSlot.IsFrameBase()) ? aarch64::Register(aarch64::FP) :
51 aarch64::Register(aarch64::SP);
52 aarch64::MemoryOperand srcOpnd(srcBaseReg, static_cast<int64_t>(srcStackSlot.GetOffset()));
53 PickLoadStoreInsn(LOCAL_SCOPE_REGISTER, srcOpnd);
54 PickLoadStoreInsn(LOCAL_SCOPE_REGISTER, dstOpnd, false);
55 }
56
Cmp(const StackSlotOperand & stackSlot,Immediate value)57 void MacroAssemblerAArch64::Cmp(const StackSlotOperand &stackSlot, Immediate value)
58 {
59 aarch64::Register baseReg = (stackSlot.IsFrameBase()) ? aarch64::Register(aarch64::FP) :
60 aarch64::Register(aarch64::SP);
61 aarch64::MemoryOperand opnd(baseReg, static_cast<int64_t>(stackSlot.GetOffset()));
62 aarch64::Operand immOpnd = aarch64::Immediate(value.GetValue());
63 PickLoadStoreInsn(LOCAL_SCOPE_REGISTER, opnd);
64 assembler.Cmp(LOCAL_SCOPE_REGISTER, immOpnd);
65 }
66
Bind(JumpLabel & label)67 void MacroAssemblerAArch64::Bind(JumpLabel &label)
68 {
69 assembler.Bind(&label);
70 }
71
Jz(JumpLabel & label)72 void MacroAssemblerAArch64::Jz(JumpLabel &label)
73 {
74 assembler.B(aarch64::EQ, &label);
75 }
76
Jnz(JumpLabel & label)77 void MacroAssemblerAArch64::Jnz(JumpLabel &label)
78 {
79 assembler.B(aarch64::NE, &label);
80 }
81
Jump(JumpLabel & label)82 void MacroAssemblerAArch64::Jump(JumpLabel &label)
83 {
84 assembler.B(&label);
85 }
86
CallBuiltin(Address funcAddress,const std::vector<MacroParameter> & parameters)87 void MacroAssemblerAArch64::CallBuiltin(Address funcAddress,
88 const std::vector<MacroParameter> ¶meters)
89 {
90 for (size_t i = 0; i < parameters.size(); ++i) {
91 auto param = parameters[i];
92 if (i == PARAM_REGISTER_COUNT) {
93 std::cout << "not support aarch64 baseline stack parameter " << std::endl;
94 std::abort();
95 break;
96 }
97 MovParameterIntoParamReg(param, registerParamVec[i]);
98 }
99 size_t startPc = assembler.GetCurPos();
100 assembler.Mov(LOCAL_SCOPE_REGISTER, aarch64::Immediate(funcAddress));
101 assembler.Blr(LOCAL_SCOPE_REGISTER);
102 size_t endPc = assembler.GetCurPos() - INSTRUCT_SIZE;
103 assembler.RecordRelocInfo(startPc, endPc, funcAddress);
104 }
105
SaveReturnRegister(const StackSlotOperand & dstStackSlot)106 void MacroAssemblerAArch64::SaveReturnRegister(const StackSlotOperand &dstStackSlot)
107 {
108 aarch64::Register baseReg = (dstStackSlot.IsFrameBase()) ? aarch64::Register(aarch64::FP) :
109 aarch64::Register(aarch64::SP);
110 aarch64::MemoryOperand dstOpnd(baseReg, static_cast<int64_t>(dstStackSlot.GetOffset()));
111 PickLoadStoreInsn(RETURN_REGISTER, dstOpnd, false);
112 }
113
MovParameterIntoParamReg(MacroParameter param,aarch64::Register paramReg)114 void MacroAssemblerAArch64::MovParameterIntoParamReg(MacroParameter param, aarch64::Register paramReg)
115 {
116 if (std::holds_alternative<BaselineSpecialParameter>(param)) {
117 auto specialParam = std::get<BaselineSpecialParameter>(param);
118 switch (specialParam) {
119 case BaselineSpecialParameter::GLUE: {
120 assembler.Mov(paramReg, GLUE_REGISTER);
121 break;
122 }
123 case BaselineSpecialParameter::PROFILE_TYPE_INFO: {
124 assembler.Ldur(LOCAL_SCOPE_REGISTER,
125 aarch64::MemoryOperand(aarch64::Register(aarch64::X29),
126 static_cast<int64_t>(FUNCTION_OFFSET_FROM_SP)));
127 assembler.Ldr(LOCAL_SCOPE_REGISTER,
128 aarch64::MemoryOperand(LOCAL_SCOPE_REGISTER, JSFunction::RAW_PROFILE_TYPE_INFO_OFFSET));
129 assembler.Ldr(paramReg,
130 aarch64::MemoryOperand(LOCAL_SCOPE_REGISTER, ProfileTypeInfoCell::VALUE_OFFSET));
131 break;
132 }
133 case BaselineSpecialParameter::SP: {
134 assembler.Mov(paramReg, aarch64::Register(aarch64::X29));
135 break;
136 }
137 case BaselineSpecialParameter::HOTNESS_COUNTER: {
138 assembler.Ldur(LOCAL_SCOPE_REGISTER, aarch64::MemoryOperand(aarch64::Register(aarch64::X29),
139 static_cast<int64_t>(FUNCTION_OFFSET_FROM_SP)));
140 assembler.Ldr(LOCAL_SCOPE_REGISTER,
141 aarch64::MemoryOperand(LOCAL_SCOPE_REGISTER, JSFunctionBase::METHOD_OFFSET));
142 assembler.Ldr(paramReg,
143 aarch64::MemoryOperand(LOCAL_SCOPE_REGISTER, Method::LITERAL_INFO_OFFSET));
144 break;
145 }
146 default: {
147 std::cout << "not supported other BaselineSpecialParameter currently" << std::endl;
148 std::abort();
149 }
150 }
151 return;
152 }
153 if (std::holds_alternative<int8_t>(param)) {
154 int16_t num = std::get<int8_t>(param);
155 assembler.Mov(paramReg, aarch64::Immediate(static_cast<int64_t>(num)));
156 return;
157 }
158 if (std::holds_alternative<int16_t>(param)) {
159 int16_t num = std::get<int16_t>(param);
160 assembler.Mov(paramReg, aarch64::Immediate(static_cast<int64_t>(num)));
161 return;
162 }
163 if (std::holds_alternative<int32_t>(param)) {
164 int32_t num = std::get<int32_t>(param);
165 assembler.Mov(paramReg, aarch64::Immediate(static_cast<int64_t>(num)));
166 return;
167 }
168 if (std::holds_alternative<int64_t>(param)) {
169 int64_t num = std::get<int64_t>(param);
170 CopyImm(paramReg, num, k64BitSize);
171 return;
172 }
173 if (std::holds_alternative<StackSlotOperand>(param)) {
174 StackSlotOperand stackSlotOpnd = std::get<StackSlotOperand>(param);
175 aarch64::Register dstBaseReg = (stackSlotOpnd.IsFrameBase()) ? aarch64::Register(aarch64::FP) :
176 aarch64::Register(aarch64::SP);
177 aarch64::MemoryOperand paramOpnd(dstBaseReg, static_cast<int64_t>(stackSlotOpnd.GetOffset()));
178 PickLoadStoreInsn(paramReg, paramOpnd);
179 return;
180 }
181 std::cout << "not supported other type of aarch64 baseline parameters currently" << std::endl;
182 std::abort();
183 }
184
PickLoadStoreInsn(aarch64::Register reg,aarch64::MemoryOperand memOpnd,bool isLoad)185 void MacroAssemblerAArch64::PickLoadStoreInsn(aarch64::Register reg, aarch64::MemoryOperand memOpnd, bool isLoad)
186 {
187 int64_t maxNineBitsSignedValue = 255;
188 int64_t minNineBitsSignedValue = -256;
189 int64_t value = memOpnd.GetImmediate().Value();
190 if (value < minNineBitsSignedValue) {
191 std::cout << "not support aarch64 offset in memory operand is less than -256!" << std::endl;
192 std::abort();
193 }
194 if (value > maxNineBitsSignedValue && isLoad) {
195 if (value % 8 != 0) { // 8: offset in memory operand must be a multiple of 8 for ldr insn
196 std::cout << "not support offset in memory operand is not a multiple of 8 for ldr insn!" << std::endl;
197 std::abort();
198 }
199 assembler.Ldr(reg, memOpnd);
200 }
201 if (value > maxNineBitsSignedValue && !isLoad) {
202 if (value % 8 != 0) { // 8: offset in memory operand must be a multiple of 8 for str insn
203 std::cout << "not support offset in memory operand is not a multiple of 8 for str insn!" << std::endl;
204 std::abort();
205 }
206 assembler.Str(reg, memOpnd);
207 }
208 if (value <= maxNineBitsSignedValue && isLoad) {
209 assembler.Ldur(reg, memOpnd);
210 }
211 if (value <= maxNineBitsSignedValue && !isLoad) {
212 assembler.Stur(reg, memOpnd);
213 }
214 }
215
IsMoveWidableImmediate(uint64_t val,uint32_t bitLen)216 bool MacroAssemblerAArch64::IsMoveWidableImmediate(uint64_t val, uint32_t bitLen)
217 {
218 if (bitLen == k64BitSize) {
219 /* 0xHHHH000000000000 or 0x0000HHHH00000000, return true */
220 if (((val & ((static_cast<uint64_t>(0xffff)) << k48BitSize)) == val) ||
221 ((val & ((static_cast<uint64_t>(0xffff)) << k32BitSize)) == val)) {
222 return true;
223 }
224 } else {
225 /* get lower 32 bits */
226 val &= static_cast<uint64_t>(0xffffffff);
227 }
228 /* 0x00000000HHHH0000 or 0x000000000000HHHH, return true */
229 return ((val & ((static_cast<uint64_t>(0xffff)) << k16BitSize)) == val ||
230 (val & ((static_cast<uint64_t>(0xffff)) << 0)) == val);
231 }
232
IsBitmaskImmediate(uint64_t val,uint32_t bitLen)233 bool MacroAssemblerAArch64::IsBitmaskImmediate(uint64_t val, uint32_t bitLen)
234 {
235 if (static_cast<int64_t>(val) == -1 || val == 0) {
236 std::cout << "IsBitmaskImmediate() don't accept 0 or -1!" << std::endl;
237 std::abort();
238 }
239 if ((bitLen == k32BitSize) && (static_cast<int32_t>(val) == -1)) {
240 return false;
241 }
242 uint64_t val2 = val;
243 if (bitLen == k32BitSize) {
244 val2 = (val2 << k32BitSize) | (val2 & ((1ULL << k32BitSize) - 1));
245 }
246 bool expectedOutcome = (ValidBitmaskImmSet.find(val2) != ValidBitmaskImmSet.end());
247
248 if ((val & 0x1) != 0) {
249 /*
250 * we want to work with
251 * 0000000000000000000000000000000000000000000001100000000000000000
252 * instead of
253 * 1111111111111111111111111111111111111111111110011111111111111111
254 */
255 val = ~val;
256 }
257
258 if (bitLen == k32BitSize) {
259 val = (val << k32BitSize) | (val & ((1ULL << k32BitSize) - 1));
260 }
261
262 /* get the least significant bit set and add it to 'val' */
263 uint64_t tmpVal = val + (val & static_cast<uint64_t>(UINT64_MAX - val + 1));
264
265 /* now check if tmp is a power of 2 or tmpVal==0. */
266 if (tmpVal < 1 || tmpVal > UINT64_MAX) {
267 std::cout << "tmpVal value overflow!" << std::endl;
268 std::abort();
269 }
270 tmpVal = tmpVal & (tmpVal - 1);
271 if (tmpVal == 0) {
272 if (!expectedOutcome) {
273 return false;
274 }
275 /* power of two or zero ; return true */
276 return true;
277 }
278
279 int32_t p0 = __builtin_ctzll(val);
280 int32_t p1 = __builtin_ctzll(tmpVal);
281 int64_t diff = p1 - p0;
282
283 /* check if diff is a power of two; return false if not. */
284 if (static_cast<uint64_t>(diff) < 1 || static_cast<uint64_t>(diff) > UINT64_MAX) {
285 std::cout << "diff value overflow!" << std::endl;
286 std::abort();
287 }
288 if ((static_cast<uint64_t>(diff) & (static_cast<uint64_t>(diff) - 1)) != 0) {
289 return false;
290 }
291
292 uint32_t logDiff = static_cast<uint32_t>(__builtin_ctzll(static_cast<uint64_t>(diff)));
293 uint64_t pattern = val & ((1ULL << static_cast<uint64_t>(diff)) - 1);
294 return val == pattern * kBitmaskImmMultTable[kMaxBitTableSize - logDiff];
295 }
296
IsSingleInstructionMovable(uint64_t val,uint32_t size)297 bool MacroAssemblerAArch64::IsSingleInstructionMovable(uint64_t val, uint32_t size)
298 {
299 return (IsMoveWidableImmediate(val, size) ||
300 IsMoveWidableImmediate(~val, size) || IsBitmaskImmediate(val, size));
301 }
302
BetterUseMOVZ(uint64_t val)303 bool MacroAssemblerAArch64::BetterUseMOVZ(uint64_t val)
304 {
305 int32_t n16zerosChunks = 0;
306 int32_t n16onesChunks = 0;
307 uint64_t sa = 0;
308 /* a 64 bits number is split 4 chunks, each chunk has 16 bits. check each chunk whether is all 1 or is all 0 */
309 for (uint64_t i = 0; i < k4BitSize; ++i, sa += k16BitSize) {
310 uint64_t chunkVal = (val >> (static_cast<uint64_t>(sa))) & 0x0000FFFFUL;
311 if (chunkVal == 0) {
312 ++n16zerosChunks;
313 } else if (chunkVal == 0xFFFFUL) {
314 ++n16onesChunks;
315 }
316 }
317 return (n16zerosChunks >= n16onesChunks);
318 }
319
CopyImm(aarch64::Register destReg,int64_t imm,uint32_t size)320 void MacroAssemblerAArch64::CopyImm(aarch64::Register destReg, int64_t imm, uint32_t size)
321 {
322 uint64_t srcVal = static_cast<uint64_t>(imm);
323
324 if (IsSingleInstructionMovable(srcVal, size)) {
325 assembler.Mov(destReg, aarch64::Immediate(imm));
326 return;
327 }
328
329 if (size != k32BitSize && size != k64BitSize) {
330 std::cout << "only support 32 and 64 bits size!" << std::endl;
331 std::abort();
332 }
333
334 if (size == k32BitSize) {
335 /* check lower 16 bits and higher 16 bits respectively */
336 if ((srcVal & 0x0000FFFFULL) == 0 || (srcVal & 0x0000FFFFULL) == 0xFFFFULL) {
337 std::cout << "unexpected val!" << std::endl;
338 std::abort();
339 }
340 if (((srcVal >> k16BitSize) & 0x0000FFFFULL) == 0 || ((srcVal >> k16BitSize) & 0x0000FFFFULL) == 0xFFFFULL) {
341 std::cout << "unexpected val" << std::endl;
342 std::abort();
343 }
344 /* create an imm opereand which represents lower 16 bits of the immediate */
345 int64_t srcLower = static_cast<int64_t>(srcVal & 0x0000FFFFULL);
346 assembler.Mov(destReg, aarch64::Immediate(srcLower));
347 /* create an imm opereand which represents upper 16 bits of the immediate */
348 int64_t srcUpper = static_cast<int64_t>((srcVal >> k16BitSize) & 0x0000FFFFULL);
349 assembler.Movk(destReg, srcUpper, k16BitSize);
350 } else {
351 CopyImmSize64(destReg, srcVal);
352 }
353 }
354
CopyImmSize64(aarch64::Register destReg,uint64_t srcVal)355 void MacroAssemblerAArch64::CopyImmSize64(aarch64::Register destReg, uint64_t srcVal)
356 {
357 /*
358 * partition it into 4 16-bit chunks
359 * if more 0's than 0xFFFF's, use movz as the initial instruction.
360 * otherwise, movn.
361 */
362 bool useMovz = BetterUseMOVZ(srcVal);
363 bool useMovk = false;
364 /* get lower 32 bits of the immediate */
365 uint64_t chunkLval = srcVal & 0xFFFFFFFFULL;
366 /* get upper 32 bits of the immediate */
367 uint64_t chunkHval = (srcVal >> k32BitSize) & 0xFFFFFFFFULL;
368 int32_t maxLoopTime = 4;
369
370 if (chunkLval == chunkHval) {
371 /* compute lower 32 bits, and then copy to higher 32 bits, so only 2 chunks need be processed */
372 maxLoopTime = 2;
373 }
374
375 uint64_t sa = 0;
376 for (int64_t i = 0; i < maxLoopTime; ++i, sa += k16BitSize) {
377 /* create an imm opereand which represents the i-th 16-bit chunk of the immediate */
378 uint64_t chunkVal = (srcVal >> (static_cast<uint64_t>(sa))) & 0x0000FFFFULL;
379 if (useMovz ? (chunkVal == 0) : (chunkVal == 0x0000FFFFULL)) {
380 continue;
381 }
382 int64_t src16 = static_cast<int64_t>(chunkVal);
383 if (!useMovk) {
384 /* use movz or movn */
385 if (!useMovz) {
386 src16 = ~(static_cast<uint64_t>(src16)) & ((1ULL << k16BitSize) - 1UL);
387 assembler.Movn(destReg, src16, sa);
388 } else {
389 assembler.Movz(destReg, src16, sa);
390 }
391 useMovk = true;
392 } else {
393 assembler.Movk(destReg, src16, sa);
394 }
395 }
396
397 if (maxLoopTime == 2) { /* as described above, only 2 chunks need be processed */
398 /* copy lower 32 bits to higher 32 bits */
399 uint32_t immr = -k32BitSize % k64BitSize; // immr = -shift % size
400 uint32_t imms = k32BitSize - 1; // imms = width - 1
401 assembler.Bfm(destReg, destReg, immr, imms);
402 }
403 }
404
405 } // namespace panda::ecmascript::kungfu
406