1 // Copyright 2013 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #if V8_TARGET_ARCH_ARM64
6
7 #include "src/base/bits.h"
8 #include "src/base/division-by-constant.h"
9 #include "src/codegen/assembler.h"
10 #include "src/codegen/callable.h"
11 #include "src/codegen/code-factory.h"
12 #include "src/codegen/external-reference-table.h"
13 #include "src/codegen/interface-descriptors-inl.h"
14 #include "src/codegen/macro-assembler-inl.h"
15 #include "src/codegen/register-configuration.h"
16 #include "src/codegen/reloc-info.h"
17 #include "src/debug/debug.h"
18 #include "src/deoptimizer/deoptimizer.h"
19 #include "src/execution/frame-constants.h"
20 #include "src/execution/frames-inl.h"
21 #include "src/heap/memory-chunk.h"
22 #include "src/init/bootstrapper.h"
23 #include "src/logging/counters.h"
24 #include "src/runtime/runtime.h"
25 #include "src/snapshot/snapshot.h"
26
27 #if V8_ENABLE_WEBASSEMBLY
28 #include "src/wasm/wasm-code-manager.h"
29 #endif // V8_ENABLE_WEBASSEMBLY
30
31 // Satisfy cpplint check, but don't include platform-specific header. It is
32 // included recursively via macro-assembler.h.
33 #if 0
34 #include "src/base/platform/wrappers.h"
35 #include "src/codegen/arm64/macro-assembler-arm64.h"
36 #endif
37
38 namespace v8 {
39 namespace internal {
40
DefaultTmpList()41 CPURegList TurboAssembler::DefaultTmpList() { return CPURegList(ip0, ip1); }
42
DefaultFPTmpList()43 CPURegList TurboAssembler::DefaultFPTmpList() {
44 return CPURegList(fp_scratch1, fp_scratch2);
45 }
46
47 namespace {
48
49 // For WebAssembly we care about the full floating point register. If we are not
50 // running Wasm, we can get away with saving half of those registers.
51 #if V8_ENABLE_WEBASSEMBLY
52 constexpr int kStackSavedSavedFPSizeInBits = kQRegSizeInBits;
53 #else
54 constexpr int kStackSavedSavedFPSizeInBits = kDRegSizeInBits;
55 #endif // V8_ENABLE_WEBASSEMBLY
56
57 } // namespace
58
PushCPURegList(CPURegList registers)59 void TurboAssembler::PushCPURegList(CPURegList registers) {
60 // If LR was stored here, we would need to sign it if
61 // V8_ENABLE_CONTROL_FLOW_INTEGRITY is on.
62 DCHECK(!registers.IncludesAliasOf(lr));
63
64 int size = registers.RegisterSizeInBytes();
65 DCHECK_EQ(0, (size * registers.Count()) % 16);
66
67 // Push up to four registers at a time.
68 while (!registers.IsEmpty()) {
69 int count_before = registers.Count();
70 const CPURegister& src0 = registers.PopHighestIndex();
71 const CPURegister& src1 = registers.PopHighestIndex();
72 const CPURegister& src2 = registers.PopHighestIndex();
73 const CPURegister& src3 = registers.PopHighestIndex();
74 int count = count_before - registers.Count();
75 PushHelper(count, size, src0, src1, src2, src3);
76 }
77 }
78
PopCPURegList(CPURegList registers)79 void TurboAssembler::PopCPURegList(CPURegList registers) {
80 int size = registers.RegisterSizeInBytes();
81 DCHECK_EQ(0, (size * registers.Count()) % 16);
82
83 // If LR was loaded here, we would need to authenticate it if
84 // V8_ENABLE_CONTROL_FLOW_INTEGRITY is on.
85 DCHECK(!registers.IncludesAliasOf(lr));
86
87 // Pop up to four registers at a time.
88 while (!registers.IsEmpty()) {
89 int count_before = registers.Count();
90 const CPURegister& dst0 = registers.PopLowestIndex();
91 const CPURegister& dst1 = registers.PopLowestIndex();
92 const CPURegister& dst2 = registers.PopLowestIndex();
93 const CPURegister& dst3 = registers.PopLowestIndex();
94 int count = count_before - registers.Count();
95 PopHelper(count, size, dst0, dst1, dst2, dst3);
96 }
97 }
98
RequiredStackSizeForCallerSaved(SaveFPRegsMode fp_mode,Register exclusion) const99 int TurboAssembler::RequiredStackSizeForCallerSaved(SaveFPRegsMode fp_mode,
100 Register exclusion) const {
101 auto list = kCallerSaved;
102 list.Remove(exclusion);
103 list.Align();
104
105 int bytes = list.TotalSizeInBytes();
106
107 if (fp_mode == SaveFPRegsMode::kSave) {
108 auto fp_list = CPURegList::GetCallerSavedV(kStackSavedSavedFPSizeInBits);
109 DCHECK_EQ(fp_list.Count() % 2, 0);
110 bytes += fp_list.TotalSizeInBytes();
111 }
112 return bytes;
113 }
114
PushCallerSaved(SaveFPRegsMode fp_mode,Register exclusion)115 int TurboAssembler::PushCallerSaved(SaveFPRegsMode fp_mode,
116 Register exclusion) {
117 ASM_CODE_COMMENT(this);
118 auto list = kCallerSaved;
119 list.Remove(exclusion);
120 list.Align();
121
122 PushCPURegList(list);
123
124 int bytes = list.TotalSizeInBytes();
125
126 if (fp_mode == SaveFPRegsMode::kSave) {
127 auto fp_list = CPURegList::GetCallerSavedV(kStackSavedSavedFPSizeInBits);
128 DCHECK_EQ(fp_list.Count() % 2, 0);
129 PushCPURegList(fp_list);
130 bytes += fp_list.TotalSizeInBytes();
131 }
132 return bytes;
133 }
134
PopCallerSaved(SaveFPRegsMode fp_mode,Register exclusion)135 int TurboAssembler::PopCallerSaved(SaveFPRegsMode fp_mode, Register exclusion) {
136 ASM_CODE_COMMENT(this);
137 int bytes = 0;
138 if (fp_mode == SaveFPRegsMode::kSave) {
139 auto fp_list = CPURegList::GetCallerSavedV(kStackSavedSavedFPSizeInBits);
140 DCHECK_EQ(fp_list.Count() % 2, 0);
141 PopCPURegList(fp_list);
142 bytes += fp_list.TotalSizeInBytes();
143 }
144
145 auto list = kCallerSaved;
146 list.Remove(exclusion);
147 list.Align();
148
149 PopCPURegList(list);
150 bytes += list.TotalSizeInBytes();
151
152 return bytes;
153 }
154
LogicalMacro(const Register & rd,const Register & rn,const Operand & operand,LogicalOp op)155 void TurboAssembler::LogicalMacro(const Register& rd, const Register& rn,
156 const Operand& operand, LogicalOp op) {
157 ASM_CODE_COMMENT(this);
158 UseScratchRegisterScope temps(this);
159
160 if (operand.NeedsRelocation(this)) {
161 Register temp = temps.AcquireX();
162 Ldr(temp, operand.immediate());
163 Logical(rd, rn, temp, op);
164
165 } else if (operand.IsImmediate()) {
166 int64_t immediate = operand.ImmediateValue();
167 unsigned reg_size = rd.SizeInBits();
168
169 // If the operation is NOT, invert the operation and immediate.
170 if ((op & NOT) == NOT) {
171 op = static_cast<LogicalOp>(op & ~NOT);
172 immediate = ~immediate;
173 }
174
175 // Ignore the top 32 bits of an immediate if we're moving to a W register.
176 if (rd.Is32Bits()) {
177 // Check that the top 32 bits are consistent.
178 DCHECK(((immediate >> kWRegSizeInBits) == 0) ||
179 ((immediate >> kWRegSizeInBits) == -1));
180 immediate &= kWRegMask;
181 }
182
183 DCHECK(rd.Is64Bits() || is_uint32(immediate));
184
185 // Special cases for all set or all clear immediates.
186 if (immediate == 0) {
187 switch (op) {
188 case AND:
189 Mov(rd, 0);
190 return;
191 case ORR: // Fall through.
192 case EOR:
193 Mov(rd, rn);
194 return;
195 case ANDS: // Fall through.
196 case BICS:
197 break;
198 default:
199 UNREACHABLE();
200 }
201 } else if ((rd.Is64Bits() && (immediate == -1L)) ||
202 (rd.Is32Bits() && (immediate == 0xFFFFFFFFL))) {
203 switch (op) {
204 case AND:
205 Mov(rd, rn);
206 return;
207 case ORR:
208 Mov(rd, immediate);
209 return;
210 case EOR:
211 Mvn(rd, rn);
212 return;
213 case ANDS: // Fall through.
214 case BICS:
215 break;
216 default:
217 UNREACHABLE();
218 }
219 }
220
221 unsigned n, imm_s, imm_r;
222 if (IsImmLogical(immediate, reg_size, &n, &imm_s, &imm_r)) {
223 // Immediate can be encoded in the instruction.
224 LogicalImmediate(rd, rn, n, imm_s, imm_r, op);
225 } else {
226 // Immediate can't be encoded: synthesize using move immediate.
227 Register temp = temps.AcquireSameSizeAs(rn);
228
229 // If the left-hand input is the stack pointer, we can't pre-shift the
230 // immediate, as the encoding won't allow the subsequent post shift.
231 PreShiftImmMode mode = rn == sp ? kNoShift : kAnyShift;
232 Operand imm_operand = MoveImmediateForShiftedOp(temp, immediate, mode);
233
234 if (rd.IsSP()) {
235 // If rd is the stack pointer we cannot use it as the destination
236 // register so we use the temp register as an intermediate again.
237 Logical(temp, rn, imm_operand, op);
238 Mov(sp, temp);
239 } else {
240 Logical(rd, rn, imm_operand, op);
241 }
242 }
243
244 } else if (operand.IsExtendedRegister()) {
245 DCHECK(operand.reg().SizeInBits() <= rd.SizeInBits());
246 // Add/sub extended supports shift <= 4. We want to support exactly the
247 // same modes here.
248 DCHECK_LE(operand.shift_amount(), 4);
249 DCHECK(operand.reg().Is64Bits() ||
250 ((operand.extend() != UXTX) && (operand.extend() != SXTX)));
251 Register temp = temps.AcquireSameSizeAs(rn);
252 EmitExtendShift(temp, operand.reg(), operand.extend(),
253 operand.shift_amount());
254 Logical(rd, rn, temp, op);
255
256 } else {
257 // The operand can be encoded in the instruction.
258 DCHECK(operand.IsShiftedRegister());
259 Logical(rd, rn, operand, op);
260 }
261 }
262
Mov(const Register & rd,uint64_t imm)263 void TurboAssembler::Mov(const Register& rd, uint64_t imm) {
264 DCHECK(allow_macro_instructions());
265 DCHECK(is_uint32(imm) || is_int32(imm) || rd.Is64Bits());
266 DCHECK(!rd.IsZero());
267
268 // TODO(all) extend to support more immediates.
269 //
270 // Immediates on Aarch64 can be produced using an initial value, and zero to
271 // three move keep operations.
272 //
273 // Initial values can be generated with:
274 // 1. 64-bit move zero (movz).
275 // 2. 32-bit move inverted (movn).
276 // 3. 64-bit move inverted.
277 // 4. 32-bit orr immediate.
278 // 5. 64-bit orr immediate.
279 // Move-keep may then be used to modify each of the 16-bit half-words.
280 //
281 // The code below supports all five initial value generators, and
282 // applying move-keep operations to move-zero and move-inverted initial
283 // values.
284
285 // Try to move the immediate in one instruction, and if that fails, switch to
286 // using multiple instructions.
287 if (!TryOneInstrMoveImmediate(rd, imm)) {
288 unsigned reg_size = rd.SizeInBits();
289
290 // Generic immediate case. Imm will be represented by
291 // [imm3, imm2, imm1, imm0], where each imm is 16 bits.
292 // A move-zero or move-inverted is generated for the first non-zero or
293 // non-0xFFFF immX, and a move-keep for subsequent non-zero immX.
294
295 uint64_t ignored_halfword = 0;
296 bool invert_move = false;
297 // If the number of 0xFFFF halfwords is greater than the number of 0x0000
298 // halfwords, it's more efficient to use move-inverted.
299 if (CountSetHalfWords(imm, reg_size) > CountSetHalfWords(~imm, reg_size)) {
300 ignored_halfword = 0xFFFFL;
301 invert_move = true;
302 }
303
304 // Mov instructions can't move immediate values into the stack pointer, so
305 // set up a temporary register, if needed.
306 UseScratchRegisterScope temps(this);
307 Register temp = rd.IsSP() ? temps.AcquireSameSizeAs(rd) : rd;
308
309 // Iterate through the halfwords. Use movn/movz for the first non-ignored
310 // halfword, and movk for subsequent halfwords.
311 DCHECK_EQ(reg_size % 16, 0);
312 bool first_mov_done = false;
313 for (int i = 0; i < (rd.SizeInBits() / 16); i++) {
314 uint64_t imm16 = (imm >> (16 * i)) & 0xFFFFL;
315 if (imm16 != ignored_halfword) {
316 if (!first_mov_done) {
317 if (invert_move) {
318 movn(temp, (~imm16) & 0xFFFFL, 16 * i);
319 } else {
320 movz(temp, imm16, 16 * i);
321 }
322 first_mov_done = true;
323 } else {
324 // Construct a wider constant.
325 movk(temp, imm16, 16 * i);
326 }
327 }
328 }
329 DCHECK(first_mov_done);
330
331 // Move the temporary if the original destination register was the stack
332 // pointer.
333 if (rd.IsSP()) {
334 mov(rd, temp);
335 }
336 }
337 }
338
Mov(const Register & rd,const Operand & operand,DiscardMoveMode discard_mode)339 void TurboAssembler::Mov(const Register& rd, const Operand& operand,
340 DiscardMoveMode discard_mode) {
341 DCHECK(allow_macro_instructions());
342 DCHECK(!rd.IsZero());
343
344 // Provide a swap register for instructions that need to write into the
345 // system stack pointer (and can't do this inherently).
346 UseScratchRegisterScope temps(this);
347 Register dst = (rd.IsSP()) ? temps.AcquireSameSizeAs(rd) : rd;
348
349 if (operand.NeedsRelocation(this)) {
350 // TODO(jgruber,v8:8887): Also consider a root-relative load when generating
351 // non-isolate-independent code. In many cases it might be cheaper than
352 // embedding the relocatable value.
353 if (root_array_available_ && options().isolate_independent_code) {
354 if (operand.ImmediateRMode() == RelocInfo::EXTERNAL_REFERENCE) {
355 Address addr = static_cast<Address>(operand.ImmediateValue());
356 ExternalReference reference = bit_cast<ExternalReference>(addr);
357 IndirectLoadExternalReference(rd, reference);
358 return;
359 } else if (RelocInfo::IsEmbeddedObjectMode(operand.ImmediateRMode())) {
360 Handle<HeapObject> x(
361 reinterpret_cast<Address*>(operand.ImmediateValue()));
362 // TODO(v8:9706): Fix-it! This load will always uncompress the value
363 // even when we are loading a compressed embedded object.
364 IndirectLoadConstant(rd.X(), x);
365 return;
366 }
367 }
368 Ldr(dst, operand);
369 } else if (operand.IsImmediate()) {
370 // Call the macro assembler for generic immediates.
371 Mov(dst, operand.ImmediateValue());
372 } else if (operand.IsShiftedRegister() && (operand.shift_amount() != 0)) {
373 // Emit a shift instruction if moving a shifted register. This operation
374 // could also be achieved using an orr instruction (like orn used by Mvn),
375 // but using a shift instruction makes the disassembly clearer.
376 EmitShift(dst, operand.reg(), operand.shift(), operand.shift_amount());
377 } else if (operand.IsExtendedRegister()) {
378 // Emit an extend instruction if moving an extended register. This handles
379 // extend with post-shift operations, too.
380 EmitExtendShift(dst, operand.reg(), operand.extend(),
381 operand.shift_amount());
382 } else {
383 // Otherwise, emit a register move only if the registers are distinct, or
384 // if they are not X registers.
385 //
386 // Note that mov(w0, w0) is not a no-op because it clears the top word of
387 // x0. A flag is provided (kDiscardForSameWReg) if a move between the same W
388 // registers is not required to clear the top word of the X register. In
389 // this case, the instruction is discarded.
390 //
391 // If sp is an operand, add #0 is emitted, otherwise, orr #0.
392 if (rd != operand.reg() ||
393 (rd.Is32Bits() && (discard_mode == kDontDiscardForSameWReg))) {
394 Assembler::mov(rd, operand.reg());
395 }
396 // This case can handle writes into the system stack pointer directly.
397 dst = rd;
398 }
399
400 // Copy the result to the system stack pointer.
401 if (dst != rd) {
402 DCHECK(rd.IsSP());
403 Assembler::mov(rd, dst);
404 }
405 }
406
Mov(const Register & rd,Smi smi)407 void TurboAssembler::Mov(const Register& rd, Smi smi) {
408 return Mov(rd, Operand(smi));
409 }
410
Movi16bitHelper(const VRegister & vd,uint64_t imm)411 void TurboAssembler::Movi16bitHelper(const VRegister& vd, uint64_t imm) {
412 DCHECK(is_uint16(imm));
413 int byte1 = (imm & 0xFF);
414 int byte2 = ((imm >> 8) & 0xFF);
415 if (byte1 == byte2) {
416 movi(vd.Is64Bits() ? vd.V8B() : vd.V16B(), byte1);
417 } else if (byte1 == 0) {
418 movi(vd, byte2, LSL, 8);
419 } else if (byte2 == 0) {
420 movi(vd, byte1);
421 } else if (byte1 == 0xFF) {
422 mvni(vd, ~byte2 & 0xFF, LSL, 8);
423 } else if (byte2 == 0xFF) {
424 mvni(vd, ~byte1 & 0xFF);
425 } else {
426 UseScratchRegisterScope temps(this);
427 Register temp = temps.AcquireW();
428 movz(temp, imm);
429 dup(vd, temp);
430 }
431 }
432
Movi32bitHelper(const VRegister & vd,uint64_t imm)433 void TurboAssembler::Movi32bitHelper(const VRegister& vd, uint64_t imm) {
434 DCHECK(is_uint32(imm));
435
436 uint8_t bytes[sizeof(imm)];
437 memcpy(bytes, &imm, sizeof(imm));
438
439 // All bytes are either 0x00 or 0xFF.
440 {
441 bool all0orff = true;
442 for (int i = 0; i < 4; ++i) {
443 if ((bytes[i] != 0) && (bytes[i] != 0xFF)) {
444 all0orff = false;
445 break;
446 }
447 }
448
449 if (all0orff == true) {
450 movi(vd.Is64Bits() ? vd.V1D() : vd.V2D(), ((imm << 32) | imm));
451 return;
452 }
453 }
454
455 // Of the 4 bytes, only one byte is non-zero.
456 for (int i = 0; i < 4; i++) {
457 if ((imm & (0xFF << (i * 8))) == imm) {
458 movi(vd, bytes[i], LSL, i * 8);
459 return;
460 }
461 }
462
463 // Of the 4 bytes, only one byte is not 0xFF.
464 for (int i = 0; i < 4; i++) {
465 uint32_t mask = ~(0xFF << (i * 8));
466 if ((imm & mask) == mask) {
467 mvni(vd, ~bytes[i] & 0xFF, LSL, i * 8);
468 return;
469 }
470 }
471
472 // Immediate is of the form 0x00MMFFFF.
473 if ((imm & 0xFF00FFFF) == 0x0000FFFF) {
474 movi(vd, bytes[2], MSL, 16);
475 return;
476 }
477
478 // Immediate is of the form 0x0000MMFF.
479 if ((imm & 0xFFFF00FF) == 0x000000FF) {
480 movi(vd, bytes[1], MSL, 8);
481 return;
482 }
483
484 // Immediate is of the form 0xFFMM0000.
485 if ((imm & 0xFF00FFFF) == 0xFF000000) {
486 mvni(vd, ~bytes[2] & 0xFF, MSL, 16);
487 return;
488 }
489 // Immediate is of the form 0xFFFFMM00.
490 if ((imm & 0xFFFF00FF) == 0xFFFF0000) {
491 mvni(vd, ~bytes[1] & 0xFF, MSL, 8);
492 return;
493 }
494
495 // Top and bottom 16-bits are equal.
496 if (((imm >> 16) & 0xFFFF) == (imm & 0xFFFF)) {
497 Movi16bitHelper(vd.Is64Bits() ? vd.V4H() : vd.V8H(), imm & 0xFFFF);
498 return;
499 }
500
501 // Default case.
502 {
503 UseScratchRegisterScope temps(this);
504 Register temp = temps.AcquireW();
505 Mov(temp, imm);
506 dup(vd, temp);
507 }
508 }
509
Movi64bitHelper(const VRegister & vd,uint64_t imm)510 void TurboAssembler::Movi64bitHelper(const VRegister& vd, uint64_t imm) {
511 // All bytes are either 0x00 or 0xFF.
512 {
513 bool all0orff = true;
514 for (int i = 0; i < 8; ++i) {
515 int byteval = (imm >> (i * 8)) & 0xFF;
516 if (byteval != 0 && byteval != 0xFF) {
517 all0orff = false;
518 break;
519 }
520 }
521 if (all0orff == true) {
522 movi(vd, imm);
523 return;
524 }
525 }
526
527 // Top and bottom 32-bits are equal.
528 if (((imm >> 32) & 0xFFFFFFFF) == (imm & 0xFFFFFFFF)) {
529 Movi32bitHelper(vd.Is64Bits() ? vd.V2S() : vd.V4S(), imm & 0xFFFFFFFF);
530 return;
531 }
532
533 // Default case.
534 {
535 UseScratchRegisterScope temps(this);
536 Register temp = temps.AcquireX();
537 Mov(temp, imm);
538 if (vd.Is1D()) {
539 mov(vd.D(), 0, temp);
540 } else {
541 dup(vd.V2D(), temp);
542 }
543 }
544 }
545
Movi(const VRegister & vd,uint64_t imm,Shift shift,int shift_amount)546 void TurboAssembler::Movi(const VRegister& vd, uint64_t imm, Shift shift,
547 int shift_amount) {
548 DCHECK(allow_macro_instructions());
549 if (shift_amount != 0 || shift != LSL) {
550 movi(vd, imm, shift, shift_amount);
551 } else if (vd.Is8B() || vd.Is16B()) {
552 // 8-bit immediate.
553 DCHECK(is_uint8(imm));
554 movi(vd, imm);
555 } else if (vd.Is4H() || vd.Is8H()) {
556 // 16-bit immediate.
557 Movi16bitHelper(vd, imm);
558 } else if (vd.Is2S() || vd.Is4S()) {
559 // 32-bit immediate.
560 Movi32bitHelper(vd, imm);
561 } else {
562 // 64-bit immediate.
563 Movi64bitHelper(vd, imm);
564 }
565 }
566
Movi(const VRegister & vd,uint64_t hi,uint64_t lo)567 void TurboAssembler::Movi(const VRegister& vd, uint64_t hi, uint64_t lo) {
568 // TODO(v8:11033): Move 128-bit values in a more efficient way.
569 DCHECK(vd.Is128Bits());
570 Movi(vd.V2D(), lo);
571 if (lo != hi) {
572 UseScratchRegisterScope temps(this);
573 Register temp = temps.AcquireX();
574 Mov(temp, hi);
575 Ins(vd.V2D(), 1, temp);
576 }
577 }
578
Mvn(const Register & rd,const Operand & operand)579 void TurboAssembler::Mvn(const Register& rd, const Operand& operand) {
580 DCHECK(allow_macro_instructions());
581
582 if (operand.NeedsRelocation(this)) {
583 Ldr(rd, operand.immediate());
584 mvn(rd, rd);
585
586 } else if (operand.IsImmediate()) {
587 // Call the macro assembler for generic immediates.
588 Mov(rd, ~operand.ImmediateValue());
589
590 } else if (operand.IsExtendedRegister()) {
591 // Emit two instructions for the extend case. This differs from Mov, as
592 // the extend and invert can't be achieved in one instruction.
593 EmitExtendShift(rd, operand.reg(), operand.extend(),
594 operand.shift_amount());
595 mvn(rd, rd);
596
597 } else {
598 mvn(rd, operand);
599 }
600 }
601
CountSetHalfWords(uint64_t imm,unsigned reg_size)602 unsigned TurboAssembler::CountSetHalfWords(uint64_t imm, unsigned reg_size) {
603 DCHECK_EQ(reg_size % 16, 0);
604
605 #define HALFWORD(idx) (((imm >> ((idx)*16)) & 0xFFFF) ? 1u : 0u)
606 switch (reg_size / 16) {
607 case 1:
608 return HALFWORD(0);
609 case 2:
610 return HALFWORD(0) + HALFWORD(1);
611 case 4:
612 return HALFWORD(0) + HALFWORD(1) + HALFWORD(2) + HALFWORD(3);
613 }
614 #undef HALFWORD
615 UNREACHABLE();
616 }
617
618 // The movz instruction can generate immediates containing an arbitrary 16-bit
619 // half-word, with remaining bits clear, eg. 0x00001234, 0x0000123400000000.
IsImmMovz(uint64_t imm,unsigned reg_size)620 bool TurboAssembler::IsImmMovz(uint64_t imm, unsigned reg_size) {
621 DCHECK((reg_size == kXRegSizeInBits) || (reg_size == kWRegSizeInBits));
622 return CountSetHalfWords(imm, reg_size) <= 1;
623 }
624
625 // The movn instruction can generate immediates containing an arbitrary 16-bit
626 // half-word, with remaining bits set, eg. 0xFFFF1234, 0xFFFF1234FFFFFFFF.
IsImmMovn(uint64_t imm,unsigned reg_size)627 bool TurboAssembler::IsImmMovn(uint64_t imm, unsigned reg_size) {
628 return IsImmMovz(~imm, reg_size);
629 }
630
ConditionalCompareMacro(const Register & rn,const Operand & operand,StatusFlags nzcv,Condition cond,ConditionalCompareOp op)631 void TurboAssembler::ConditionalCompareMacro(const Register& rn,
632 const Operand& operand,
633 StatusFlags nzcv, Condition cond,
634 ConditionalCompareOp op) {
635 DCHECK((cond != al) && (cond != nv));
636 if (operand.NeedsRelocation(this)) {
637 UseScratchRegisterScope temps(this);
638 Register temp = temps.AcquireX();
639 Ldr(temp, operand.immediate());
640 ConditionalCompareMacro(rn, temp, nzcv, cond, op);
641
642 } else if ((operand.IsShiftedRegister() && (operand.shift_amount() == 0)) ||
643 (operand.IsImmediate() &&
644 IsImmConditionalCompare(operand.ImmediateValue()))) {
645 // The immediate can be encoded in the instruction, or the operand is an
646 // unshifted register: call the assembler.
647 ConditionalCompare(rn, operand, nzcv, cond, op);
648
649 } else {
650 // The operand isn't directly supported by the instruction: perform the
651 // operation on a temporary register.
652 UseScratchRegisterScope temps(this);
653 Register temp = temps.AcquireSameSizeAs(rn);
654 Mov(temp, operand);
655 ConditionalCompare(rn, temp, nzcv, cond, op);
656 }
657 }
658
Csel(const Register & rd,const Register & rn,const Operand & operand,Condition cond)659 void TurboAssembler::Csel(const Register& rd, const Register& rn,
660 const Operand& operand, Condition cond) {
661 DCHECK(allow_macro_instructions());
662 DCHECK(!rd.IsZero());
663 DCHECK((cond != al) && (cond != nv));
664 if (operand.IsImmediate()) {
665 // Immediate argument. Handle special cases of 0, 1 and -1 using zero
666 // register.
667 int64_t imm = operand.ImmediateValue();
668 Register zr = AppropriateZeroRegFor(rn);
669 if (imm == 0) {
670 csel(rd, rn, zr, cond);
671 } else if (imm == 1) {
672 csinc(rd, rn, zr, cond);
673 } else if (imm == -1) {
674 csinv(rd, rn, zr, cond);
675 } else {
676 UseScratchRegisterScope temps(this);
677 Register temp = temps.AcquireSameSizeAs(rn);
678 Mov(temp, imm);
679 csel(rd, rn, temp, cond);
680 }
681 } else if (operand.IsShiftedRegister() && (operand.shift_amount() == 0)) {
682 // Unshifted register argument.
683 csel(rd, rn, operand.reg(), cond);
684 } else {
685 // All other arguments.
686 UseScratchRegisterScope temps(this);
687 Register temp = temps.AcquireSameSizeAs(rn);
688 Mov(temp, operand);
689 csel(rd, rn, temp, cond);
690 }
691 }
692
TryOneInstrMoveImmediate(const Register & dst,int64_t imm)693 bool TurboAssembler::TryOneInstrMoveImmediate(const Register& dst,
694 int64_t imm) {
695 unsigned n, imm_s, imm_r;
696 int reg_size = dst.SizeInBits();
697 if (IsImmMovz(imm, reg_size) && !dst.IsSP()) {
698 // Immediate can be represented in a move zero instruction. Movz can't write
699 // to the stack pointer.
700 movz(dst, imm);
701 return true;
702 } else if (IsImmMovn(imm, reg_size) && !dst.IsSP()) {
703 // Immediate can be represented in a move not instruction. Movn can't write
704 // to the stack pointer.
705 movn(dst, dst.Is64Bits() ? ~imm : (~imm & kWRegMask));
706 return true;
707 } else if (IsImmLogical(imm, reg_size, &n, &imm_s, &imm_r)) {
708 // Immediate can be represented in a logical orr instruction.
709 LogicalImmediate(dst, AppropriateZeroRegFor(dst), n, imm_s, imm_r, ORR);
710 return true;
711 }
712 return false;
713 }
714
MoveImmediateForShiftedOp(const Register & dst,int64_t imm,PreShiftImmMode mode)715 Operand TurboAssembler::MoveImmediateForShiftedOp(const Register& dst,
716 int64_t imm,
717 PreShiftImmMode mode) {
718 int reg_size = dst.SizeInBits();
719 // Encode the immediate in a single move instruction, if possible.
720 if (TryOneInstrMoveImmediate(dst, imm)) {
721 // The move was successful; nothing to do here.
722 } else {
723 // Pre-shift the immediate to the least-significant bits of the register.
724 int shift_low;
725 if (reg_size == 64) {
726 shift_low = base::bits::CountTrailingZeros(imm);
727 } else {
728 DCHECK_EQ(reg_size, 32);
729 shift_low = base::bits::CountTrailingZeros(static_cast<uint32_t>(imm));
730 }
731
732 if (mode == kLimitShiftForSP) {
733 // When applied to the stack pointer, the subsequent arithmetic operation
734 // can use the extend form to shift left by a maximum of four bits. Right
735 // shifts are not allowed, so we filter them out later before the new
736 // immediate is tested.
737 shift_low = std::min(shift_low, 4);
738 }
739 int64_t imm_low = imm >> shift_low;
740
741 // Pre-shift the immediate to the most-significant bits of the register. We
742 // insert set bits in the least-significant bits, as this creates a
743 // different immediate that may be encodable using movn or orr-immediate.
744 // If this new immediate is encodable, the set bits will be eliminated by
745 // the post shift on the following instruction.
746 int shift_high = CountLeadingZeros(imm, reg_size);
747 int64_t imm_high = (imm << shift_high) | ((INT64_C(1) << shift_high) - 1);
748
749 if ((mode != kNoShift) && TryOneInstrMoveImmediate(dst, imm_low)) {
750 // The new immediate has been moved into the destination's low bits:
751 // return a new leftward-shifting operand.
752 return Operand(dst, LSL, shift_low);
753 } else if ((mode == kAnyShift) && TryOneInstrMoveImmediate(dst, imm_high)) {
754 // The new immediate has been moved into the destination's high bits:
755 // return a new rightward-shifting operand.
756 return Operand(dst, LSR, shift_high);
757 } else {
758 // Use the generic move operation to set up the immediate.
759 Mov(dst, imm);
760 }
761 }
762 return Operand(dst);
763 }
764
AddSubMacro(const Register & rd,const Register & rn,const Operand & operand,FlagsUpdate S,AddSubOp op)765 void TurboAssembler::AddSubMacro(const Register& rd, const Register& rn,
766 const Operand& operand, FlagsUpdate S,
767 AddSubOp op) {
768 if (operand.IsZero() && rd == rn && rd.Is64Bits() && rn.Is64Bits() &&
769 !operand.NeedsRelocation(this) && (S == LeaveFlags)) {
770 // The instruction would be a nop. Avoid generating useless code.
771 return;
772 }
773
774 if (operand.NeedsRelocation(this)) {
775 UseScratchRegisterScope temps(this);
776 Register temp = temps.AcquireX();
777 Ldr(temp, operand.immediate());
778 AddSubMacro(rd, rn, temp, S, op);
779 } else if ((operand.IsImmediate() &&
780 !IsImmAddSub(operand.ImmediateValue())) ||
781 (rn.IsZero() && !operand.IsShiftedRegister()) ||
782 (operand.IsShiftedRegister() && (operand.shift() == ROR))) {
783 UseScratchRegisterScope temps(this);
784 Register temp = temps.AcquireSameSizeAs(rn);
785 if (operand.IsImmediate()) {
786 PreShiftImmMode mode = kAnyShift;
787
788 // If the destination or source register is the stack pointer, we can
789 // only pre-shift the immediate right by values supported in the add/sub
790 // extend encoding.
791 if (rd == sp) {
792 // If the destination is SP and flags will be set, we can't pre-shift
793 // the immediate at all.
794 mode = (S == SetFlags) ? kNoShift : kLimitShiftForSP;
795 } else if (rn == sp) {
796 mode = kLimitShiftForSP;
797 }
798
799 Operand imm_operand =
800 MoveImmediateForShiftedOp(temp, operand.ImmediateValue(), mode);
801 AddSub(rd, rn, imm_operand, S, op);
802 } else {
803 Mov(temp, operand);
804 AddSub(rd, rn, temp, S, op);
805 }
806 } else {
807 AddSub(rd, rn, operand, S, op);
808 }
809 }
810
AddSubWithCarryMacro(const Register & rd,const Register & rn,const Operand & operand,FlagsUpdate S,AddSubWithCarryOp op)811 void TurboAssembler::AddSubWithCarryMacro(const Register& rd,
812 const Register& rn,
813 const Operand& operand, FlagsUpdate S,
814 AddSubWithCarryOp op) {
815 DCHECK(rd.SizeInBits() == rn.SizeInBits());
816 UseScratchRegisterScope temps(this);
817
818 if (operand.NeedsRelocation(this)) {
819 Register temp = temps.AcquireX();
820 Ldr(temp, operand.immediate());
821 AddSubWithCarryMacro(rd, rn, temp, S, op);
822
823 } else if (operand.IsImmediate() ||
824 (operand.IsShiftedRegister() && (operand.shift() == ROR))) {
825 // Add/sub with carry (immediate or ROR shifted register.)
826 Register temp = temps.AcquireSameSizeAs(rn);
827 Mov(temp, operand);
828 AddSubWithCarry(rd, rn, temp, S, op);
829
830 } else if (operand.IsShiftedRegister() && (operand.shift_amount() != 0)) {
831 // Add/sub with carry (shifted register).
832 DCHECK(operand.reg().SizeInBits() == rd.SizeInBits());
833 DCHECK(operand.shift() != ROR);
834 DCHECK(is_uintn(operand.shift_amount(), rd.SizeInBits() == kXRegSizeInBits
835 ? kXRegSizeInBitsLog2
836 : kWRegSizeInBitsLog2));
837 Register temp = temps.AcquireSameSizeAs(rn);
838 EmitShift(temp, operand.reg(), operand.shift(), operand.shift_amount());
839 AddSubWithCarry(rd, rn, temp, S, op);
840
841 } else if (operand.IsExtendedRegister()) {
842 // Add/sub with carry (extended register).
843 DCHECK(operand.reg().SizeInBits() <= rd.SizeInBits());
844 // Add/sub extended supports a shift <= 4. We want to support exactly the
845 // same modes.
846 DCHECK_LE(operand.shift_amount(), 4);
847 DCHECK(operand.reg().Is64Bits() ||
848 ((operand.extend() != UXTX) && (operand.extend() != SXTX)));
849 Register temp = temps.AcquireSameSizeAs(rn);
850 EmitExtendShift(temp, operand.reg(), operand.extend(),
851 operand.shift_amount());
852 AddSubWithCarry(rd, rn, temp, S, op);
853
854 } else {
855 // The addressing mode is directly supported by the instruction.
856 AddSubWithCarry(rd, rn, operand, S, op);
857 }
858 }
859
LoadStoreMacro(const CPURegister & rt,const MemOperand & addr,LoadStoreOp op)860 void TurboAssembler::LoadStoreMacro(const CPURegister& rt,
861 const MemOperand& addr, LoadStoreOp op) {
862 int64_t offset = addr.offset();
863 unsigned size = CalcLSDataSize(op);
864
865 // Check if an immediate offset fits in the immediate field of the
866 // appropriate instruction. If not, emit two instructions to perform
867 // the operation.
868 if (addr.IsImmediateOffset() && !IsImmLSScaled(offset, size) &&
869 !IsImmLSUnscaled(offset)) {
870 // Immediate offset that can't be encoded using unsigned or unscaled
871 // addressing modes.
872 UseScratchRegisterScope temps(this);
873 Register temp = temps.AcquireSameSizeAs(addr.base());
874 Mov(temp, addr.offset());
875 LoadStore(rt, MemOperand(addr.base(), temp), op);
876 } else if (addr.IsPostIndex() && !IsImmLSUnscaled(offset)) {
877 // Post-index beyond unscaled addressing range.
878 LoadStore(rt, MemOperand(addr.base()), op);
879 add(addr.base(), addr.base(), offset);
880 } else if (addr.IsPreIndex() && !IsImmLSUnscaled(offset)) {
881 // Pre-index beyond unscaled addressing range.
882 add(addr.base(), addr.base(), offset);
883 LoadStore(rt, MemOperand(addr.base()), op);
884 } else {
885 // Encodable in one load/store instruction.
886 LoadStore(rt, addr, op);
887 }
888 }
889
LoadStorePairMacro(const CPURegister & rt,const CPURegister & rt2,const MemOperand & addr,LoadStorePairOp op)890 void TurboAssembler::LoadStorePairMacro(const CPURegister& rt,
891 const CPURegister& rt2,
892 const MemOperand& addr,
893 LoadStorePairOp op) {
894 // TODO(all): Should we support register offset for load-store-pair?
895 DCHECK(!addr.IsRegisterOffset());
896
897 int64_t offset = addr.offset();
898 unsigned size = CalcLSPairDataSize(op);
899
900 // Check if the offset fits in the immediate field of the appropriate
901 // instruction. If not, emit two instructions to perform the operation.
902 if (IsImmLSPair(offset, size)) {
903 // Encodable in one load/store pair instruction.
904 LoadStorePair(rt, rt2, addr, op);
905 } else {
906 Register base = addr.base();
907 if (addr.IsImmediateOffset()) {
908 UseScratchRegisterScope temps(this);
909 Register temp = temps.AcquireSameSizeAs(base);
910 Add(temp, base, offset);
911 LoadStorePair(rt, rt2, MemOperand(temp), op);
912 } else if (addr.IsPostIndex()) {
913 LoadStorePair(rt, rt2, MemOperand(base), op);
914 Add(base, base, offset);
915 } else {
916 DCHECK(addr.IsPreIndex());
917 Add(base, base, offset);
918 LoadStorePair(rt, rt2, MemOperand(base), op);
919 }
920 }
921 }
922
NeedExtraInstructionsOrRegisterBranch(Label * label,ImmBranchType b_type)923 bool TurboAssembler::NeedExtraInstructionsOrRegisterBranch(
924 Label* label, ImmBranchType b_type) {
925 bool need_longer_range = false;
926 // There are two situations in which we care about the offset being out of
927 // range:
928 // - The label is bound but too far away.
929 // - The label is not bound but linked, and the previous branch
930 // instruction in the chain is too far away.
931 if (label->is_bound() || label->is_linked()) {
932 need_longer_range =
933 !Instruction::IsValidImmPCOffset(b_type, label->pos() - pc_offset());
934 }
935 if (!need_longer_range && !label->is_bound()) {
936 int max_reachable_pc = pc_offset() + Instruction::ImmBranchRange(b_type);
937 unresolved_branches_.insert(std::pair<int, FarBranchInfo>(
938 max_reachable_pc, FarBranchInfo(pc_offset(), label)));
939 // Also maintain the next pool check.
940 next_veneer_pool_check_ = std::min(
941 next_veneer_pool_check_, max_reachable_pc - kVeneerDistanceCheckMargin);
942 }
943 return need_longer_range;
944 }
945
Adr(const Register & rd,Label * label,AdrHint hint)946 void TurboAssembler::Adr(const Register& rd, Label* label, AdrHint hint) {
947 DCHECK(allow_macro_instructions());
948 DCHECK(!rd.IsZero());
949
950 if (hint == kAdrNear) {
951 adr(rd, label);
952 return;
953 }
954
955 DCHECK_EQ(hint, kAdrFar);
956 if (label->is_bound()) {
957 int label_offset = label->pos() - pc_offset();
958 if (Instruction::IsValidPCRelOffset(label_offset)) {
959 adr(rd, label);
960 } else {
961 DCHECK_LE(label_offset, 0);
962 int min_adr_offset = -(1 << (Instruction::ImmPCRelRangeBitwidth - 1));
963 adr(rd, min_adr_offset);
964 Add(rd, rd, label_offset - min_adr_offset);
965 }
966 } else {
967 UseScratchRegisterScope temps(this);
968 Register scratch = temps.AcquireX();
969
970 InstructionAccurateScope scope(this,
971 PatchingAssembler::kAdrFarPatchableNInstrs);
972 adr(rd, label);
973 for (int i = 0; i < PatchingAssembler::kAdrFarPatchableNNops; ++i) {
974 nop(ADR_FAR_NOP);
975 }
976 movz(scratch, 0);
977 }
978 }
979
B(Label * label,BranchType type,Register reg,int bit)980 void TurboAssembler::B(Label* label, BranchType type, Register reg, int bit) {
981 DCHECK((reg == NoReg || type >= kBranchTypeFirstUsingReg) &&
982 (bit == -1 || type >= kBranchTypeFirstUsingBit));
983 if (kBranchTypeFirstCondition <= type && type <= kBranchTypeLastCondition) {
984 B(static_cast<Condition>(type), label);
985 } else {
986 switch (type) {
987 case always:
988 B(label);
989 break;
990 case never:
991 break;
992 case reg_zero:
993 Cbz(reg, label);
994 break;
995 case reg_not_zero:
996 Cbnz(reg, label);
997 break;
998 case reg_bit_clear:
999 Tbz(reg, bit, label);
1000 break;
1001 case reg_bit_set:
1002 Tbnz(reg, bit, label);
1003 break;
1004 default:
1005 UNREACHABLE();
1006 }
1007 }
1008 }
1009
B(Label * label,Condition cond)1010 void TurboAssembler::B(Label* label, Condition cond) {
1011 DCHECK(allow_macro_instructions());
1012 DCHECK((cond != al) && (cond != nv));
1013
1014 Label done;
1015 bool need_extra_instructions =
1016 NeedExtraInstructionsOrRegisterBranch(label, CondBranchType);
1017
1018 if (need_extra_instructions) {
1019 b(&done, NegateCondition(cond));
1020 B(label);
1021 } else {
1022 b(label, cond);
1023 }
1024 bind(&done);
1025 }
1026
Tbnz(const Register & rt,unsigned bit_pos,Label * label)1027 void TurboAssembler::Tbnz(const Register& rt, unsigned bit_pos, Label* label) {
1028 DCHECK(allow_macro_instructions());
1029
1030 Label done;
1031 bool need_extra_instructions =
1032 NeedExtraInstructionsOrRegisterBranch(label, TestBranchType);
1033
1034 if (need_extra_instructions) {
1035 tbz(rt, bit_pos, &done);
1036 B(label);
1037 } else {
1038 tbnz(rt, bit_pos, label);
1039 }
1040 bind(&done);
1041 }
1042
Tbz(const Register & rt,unsigned bit_pos,Label * label)1043 void TurboAssembler::Tbz(const Register& rt, unsigned bit_pos, Label* label) {
1044 DCHECK(allow_macro_instructions());
1045
1046 Label done;
1047 bool need_extra_instructions =
1048 NeedExtraInstructionsOrRegisterBranch(label, TestBranchType);
1049
1050 if (need_extra_instructions) {
1051 tbnz(rt, bit_pos, &done);
1052 B(label);
1053 } else {
1054 tbz(rt, bit_pos, label);
1055 }
1056 bind(&done);
1057 }
1058
Cbnz(const Register & rt,Label * label)1059 void TurboAssembler::Cbnz(const Register& rt, Label* label) {
1060 DCHECK(allow_macro_instructions());
1061
1062 Label done;
1063 bool need_extra_instructions =
1064 NeedExtraInstructionsOrRegisterBranch(label, CompareBranchType);
1065
1066 if (need_extra_instructions) {
1067 cbz(rt, &done);
1068 B(label);
1069 } else {
1070 cbnz(rt, label);
1071 }
1072 bind(&done);
1073 }
1074
Cbz(const Register & rt,Label * label)1075 void TurboAssembler::Cbz(const Register& rt, Label* label) {
1076 DCHECK(allow_macro_instructions());
1077
1078 Label done;
1079 bool need_extra_instructions =
1080 NeedExtraInstructionsOrRegisterBranch(label, CompareBranchType);
1081
1082 if (need_extra_instructions) {
1083 cbnz(rt, &done);
1084 B(label);
1085 } else {
1086 cbz(rt, label);
1087 }
1088 bind(&done);
1089 }
1090
1091 // Pseudo-instructions.
1092
Abs(const Register & rd,const Register & rm,Label * is_not_representable,Label * is_representable)1093 void TurboAssembler::Abs(const Register& rd, const Register& rm,
1094 Label* is_not_representable, Label* is_representable) {
1095 DCHECK(allow_macro_instructions());
1096 DCHECK(AreSameSizeAndType(rd, rm));
1097
1098 Cmp(rm, 1);
1099 Cneg(rd, rm, lt);
1100
1101 // If the comparison sets the v flag, the input was the smallest value
1102 // representable by rm, and the mathematical result of abs(rm) is not
1103 // representable using two's complement.
1104 if ((is_not_representable != nullptr) && (is_representable != nullptr)) {
1105 B(is_not_representable, vs);
1106 B(is_representable);
1107 } else if (is_not_representable != nullptr) {
1108 B(is_not_representable, vs);
1109 } else if (is_representable != nullptr) {
1110 B(is_representable, vc);
1111 }
1112 }
1113
1114 // Abstracted stack operations.
1115
Push(const CPURegister & src0,const CPURegister & src1,const CPURegister & src2,const CPURegister & src3,const CPURegister & src4,const CPURegister & src5,const CPURegister & src6,const CPURegister & src7)1116 void TurboAssembler::Push(const CPURegister& src0, const CPURegister& src1,
1117 const CPURegister& src2, const CPURegister& src3,
1118 const CPURegister& src4, const CPURegister& src5,
1119 const CPURegister& src6, const CPURegister& src7) {
1120 DCHECK(AreSameSizeAndType(src0, src1, src2, src3, src4, src5, src6, src7));
1121
1122 int count = 5 + src5.is_valid() + src6.is_valid() + src6.is_valid();
1123 int size = src0.SizeInBytes();
1124 DCHECK_EQ(0, (size * count) % 16);
1125
1126 PushHelper(4, size, src0, src1, src2, src3);
1127 PushHelper(count - 4, size, src4, src5, src6, src7);
1128 }
1129
Pop(const CPURegister & dst0,const CPURegister & dst1,const CPURegister & dst2,const CPURegister & dst3,const CPURegister & dst4,const CPURegister & dst5,const CPURegister & dst6,const CPURegister & dst7)1130 void TurboAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1,
1131 const CPURegister& dst2, const CPURegister& dst3,
1132 const CPURegister& dst4, const CPURegister& dst5,
1133 const CPURegister& dst6, const CPURegister& dst7) {
1134 // It is not valid to pop into the same register more than once in one
1135 // instruction, not even into the zero register.
1136 DCHECK(!AreAliased(dst0, dst1, dst2, dst3, dst4, dst5, dst6, dst7));
1137 DCHECK(AreSameSizeAndType(dst0, dst1, dst2, dst3, dst4, dst5, dst6, dst7));
1138 DCHECK(dst0.is_valid());
1139
1140 int count = 5 + dst5.is_valid() + dst6.is_valid() + dst7.is_valid();
1141 int size = dst0.SizeInBytes();
1142 DCHECK_EQ(0, (size * count) % 16);
1143
1144 PopHelper(4, size, dst0, dst1, dst2, dst3);
1145 PopHelper(count - 4, size, dst4, dst5, dst6, dst7);
1146 }
1147
PushMultipleTimes(CPURegister src,Register count)1148 void MacroAssembler::PushMultipleTimes(CPURegister src, Register count) {
1149 UseScratchRegisterScope temps(this);
1150 Register temp = temps.AcquireSameSizeAs(count);
1151
1152 Label loop, leftover2, leftover1, done;
1153
1154 Subs(temp, count, 4);
1155 B(mi, &leftover2);
1156
1157 // Push groups of four first.
1158 Bind(&loop);
1159 Subs(temp, temp, 4);
1160 PushHelper(4, src.SizeInBytes(), src, src, src, src);
1161 B(pl, &loop);
1162
1163 // Push groups of two.
1164 Bind(&leftover2);
1165 Tbz(count, 1, &leftover1);
1166 PushHelper(2, src.SizeInBytes(), src, src, NoReg, NoReg);
1167
1168 // Push the last one (if required).
1169 Bind(&leftover1);
1170 Tbz(count, 0, &done);
1171 PushHelper(1, src.SizeInBytes(), src, NoReg, NoReg, NoReg);
1172
1173 Bind(&done);
1174 }
1175
PushHelper(int count,int size,const CPURegister & src0,const CPURegister & src1,const CPURegister & src2,const CPURegister & src3)1176 void TurboAssembler::PushHelper(int count, int size, const CPURegister& src0,
1177 const CPURegister& src1,
1178 const CPURegister& src2,
1179 const CPURegister& src3) {
1180 // Ensure that we don't unintentially modify scratch or debug registers.
1181 InstructionAccurateScope scope(this);
1182
1183 DCHECK(AreSameSizeAndType(src0, src1, src2, src3));
1184 DCHECK(size == src0.SizeInBytes());
1185
1186 // When pushing multiple registers, the store order is chosen such that
1187 // Push(a, b) is equivalent to Push(a) followed by Push(b).
1188 switch (count) {
1189 case 1:
1190 DCHECK(src1.IsNone() && src2.IsNone() && src3.IsNone());
1191 str(src0, MemOperand(sp, -1 * size, PreIndex));
1192 break;
1193 case 2:
1194 DCHECK(src2.IsNone() && src3.IsNone());
1195 stp(src1, src0, MemOperand(sp, -2 * size, PreIndex));
1196 break;
1197 case 3:
1198 DCHECK(src3.IsNone());
1199 stp(src2, src1, MemOperand(sp, -3 * size, PreIndex));
1200 str(src0, MemOperand(sp, 2 * size));
1201 break;
1202 case 4:
1203 // Skip over 4 * size, then fill in the gap. This allows four W registers
1204 // to be pushed using sp, whilst maintaining 16-byte alignment for sp
1205 // at all times.
1206 stp(src3, src2, MemOperand(sp, -4 * size, PreIndex));
1207 stp(src1, src0, MemOperand(sp, 2 * size));
1208 break;
1209 default:
1210 UNREACHABLE();
1211 }
1212 }
1213
PopHelper(int count,int size,const CPURegister & dst0,const CPURegister & dst1,const CPURegister & dst2,const CPURegister & dst3)1214 void TurboAssembler::PopHelper(int count, int size, const CPURegister& dst0,
1215 const CPURegister& dst1, const CPURegister& dst2,
1216 const CPURegister& dst3) {
1217 // Ensure that we don't unintentially modify scratch or debug registers.
1218 InstructionAccurateScope scope(this);
1219
1220 DCHECK(AreSameSizeAndType(dst0, dst1, dst2, dst3));
1221 DCHECK(size == dst0.SizeInBytes());
1222
1223 // When popping multiple registers, the load order is chosen such that
1224 // Pop(a, b) is equivalent to Pop(a) followed by Pop(b).
1225 switch (count) {
1226 case 1:
1227 DCHECK(dst1.IsNone() && dst2.IsNone() && dst3.IsNone());
1228 ldr(dst0, MemOperand(sp, 1 * size, PostIndex));
1229 break;
1230 case 2:
1231 DCHECK(dst2.IsNone() && dst3.IsNone());
1232 ldp(dst0, dst1, MemOperand(sp, 2 * size, PostIndex));
1233 break;
1234 case 3:
1235 DCHECK(dst3.IsNone());
1236 ldr(dst2, MemOperand(sp, 2 * size));
1237 ldp(dst0, dst1, MemOperand(sp, 3 * size, PostIndex));
1238 break;
1239 case 4:
1240 // Load the higher addresses first, then load the lower addresses and
1241 // skip the whole block in the second instruction. This allows four W
1242 // registers to be popped using sp, whilst maintaining 16-byte alignment
1243 // for sp at all times.
1244 ldp(dst2, dst3, MemOperand(sp, 2 * size));
1245 ldp(dst0, dst1, MemOperand(sp, 4 * size, PostIndex));
1246 break;
1247 default:
1248 UNREACHABLE();
1249 }
1250 }
1251
PokePair(const CPURegister & src1,const CPURegister & src2,int offset)1252 void TurboAssembler::PokePair(const CPURegister& src1, const CPURegister& src2,
1253 int offset) {
1254 DCHECK(AreSameSizeAndType(src1, src2));
1255 DCHECK((offset >= 0) && ((offset % src1.SizeInBytes()) == 0));
1256 Stp(src1, src2, MemOperand(sp, offset));
1257 }
1258
PeekPair(const CPURegister & dst1,const CPURegister & dst2,int offset)1259 void MacroAssembler::PeekPair(const CPURegister& dst1, const CPURegister& dst2,
1260 int offset) {
1261 DCHECK(AreSameSizeAndType(dst1, dst2));
1262 DCHECK((offset >= 0) && ((offset % dst1.SizeInBytes()) == 0));
1263 Ldp(dst1, dst2, MemOperand(sp, offset));
1264 }
1265
PushCalleeSavedRegisters()1266 void MacroAssembler::PushCalleeSavedRegisters() {
1267 ASM_CODE_COMMENT(this);
1268 // Ensure that the macro-assembler doesn't use any scratch registers.
1269 InstructionAccurateScope scope(this);
1270
1271 MemOperand tos(sp, -2 * static_cast<int>(kXRegSize), PreIndex);
1272
1273 stp(d14, d15, tos);
1274 stp(d12, d13, tos);
1275 stp(d10, d11, tos);
1276 stp(d8, d9, tos);
1277
1278 stp(x27, x28, tos);
1279 stp(x25, x26, tos);
1280 stp(x23, x24, tos);
1281 stp(x21, x22, tos);
1282 stp(x19, x20, tos);
1283
1284 STATIC_ASSERT(
1285 EntryFrameConstants::kCalleeSavedRegisterBytesPushedBeforeFpLrPair ==
1286 18 * kSystemPointerSize);
1287
1288 #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
1289 // Use the stack pointer's value immediately before pushing the LR as the
1290 // context for signing it. This is what the StackFrameIterator expects.
1291 pacibsp();
1292 #endif
1293
1294 stp(x29, x30, tos); // fp, lr
1295
1296 STATIC_ASSERT(
1297 EntryFrameConstants::kCalleeSavedRegisterBytesPushedAfterFpLrPair == 0);
1298 }
1299
PopCalleeSavedRegisters()1300 void MacroAssembler::PopCalleeSavedRegisters() {
1301 ASM_CODE_COMMENT(this);
1302 // Ensure that the macro-assembler doesn't use any scratch registers.
1303 InstructionAccurateScope scope(this);
1304
1305 MemOperand tos(sp, 2 * kXRegSize, PostIndex);
1306
1307 ldp(x29, x30, tos); // fp, lr
1308
1309 #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
1310 // The context (stack pointer value) for authenticating
1311 // the LR here must
1312 // match the one used for signing it (see `PushCalleeSavedRegisters`).
1313 autibsp();
1314 #endif
1315
1316 ldp(x19, x20, tos);
1317 ldp(x21, x22, tos);
1318 ldp(x23, x24, tos);
1319 ldp(x25, x26, tos);
1320 ldp(x27, x28, tos);
1321
1322 ldp(d8, d9, tos);
1323 ldp(d10, d11, tos);
1324 ldp(d12, d13, tos);
1325 ldp(d14, d15, tos);
1326 }
1327
AssertSpAligned()1328 void TurboAssembler::AssertSpAligned() {
1329 if (!FLAG_debug_code) return;
1330 ASM_CODE_COMMENT(this);
1331 HardAbortScope hard_abort(this); // Avoid calls to Abort.
1332 // Arm64 requires the stack pointer to be 16-byte aligned prior to address
1333 // calculation.
1334 UseScratchRegisterScope scope(this);
1335 Register temp = scope.AcquireX();
1336 Mov(temp, sp);
1337 Tst(temp, 15);
1338 Check(eq, AbortReason::kUnexpectedStackPointer);
1339 }
1340
CopySlots(int dst,Register src,Register slot_count)1341 void TurboAssembler::CopySlots(int dst, Register src, Register slot_count) {
1342 DCHECK(!src.IsZero());
1343 UseScratchRegisterScope scope(this);
1344 Register dst_reg = scope.AcquireX();
1345 SlotAddress(dst_reg, dst);
1346 SlotAddress(src, src);
1347 CopyDoubleWords(dst_reg, src, slot_count);
1348 }
1349
CopySlots(Register dst,Register src,Register slot_count)1350 void TurboAssembler::CopySlots(Register dst, Register src,
1351 Register slot_count) {
1352 DCHECK(!dst.IsZero() && !src.IsZero());
1353 SlotAddress(dst, dst);
1354 SlotAddress(src, src);
1355 CopyDoubleWords(dst, src, slot_count);
1356 }
1357
CopyDoubleWords(Register dst,Register src,Register count,CopyDoubleWordsMode mode)1358 void TurboAssembler::CopyDoubleWords(Register dst, Register src, Register count,
1359 CopyDoubleWordsMode mode) {
1360 ASM_CODE_COMMENT(this);
1361 DCHECK(!AreAliased(dst, src, count));
1362
1363 if (FLAG_debug_code) {
1364 Register pointer1 = dst;
1365 Register pointer2 = src;
1366 if (mode == kSrcLessThanDst) {
1367 pointer1 = src;
1368 pointer2 = dst;
1369 }
1370 // Copy requires pointer1 < pointer2 || (pointer1 - pointer2) >= count.
1371 Label pointer1_below_pointer2;
1372 Subs(pointer1, pointer1, pointer2);
1373 B(lt, &pointer1_below_pointer2);
1374 Cmp(pointer1, count);
1375 Check(ge, AbortReason::kOffsetOutOfRange);
1376 Bind(&pointer1_below_pointer2);
1377 Add(pointer1, pointer1, pointer2);
1378 }
1379 static_assert(kSystemPointerSize == kDRegSize,
1380 "pointers must be the same size as doubles");
1381
1382 if (mode == kDstLessThanSrcAndReverse) {
1383 Add(src, src, Operand(count, LSL, kSystemPointerSizeLog2));
1384 Sub(src, src, kSystemPointerSize);
1385 }
1386
1387 int src_direction = (mode == kDstLessThanSrc) ? 1 : -1;
1388 int dst_direction = (mode == kSrcLessThanDst) ? -1 : 1;
1389
1390 UseScratchRegisterScope scope(this);
1391 VRegister temp0 = scope.AcquireD();
1392 VRegister temp1 = scope.AcquireD();
1393
1394 Label pairs, loop, done;
1395
1396 Tbz(count, 0, &pairs);
1397 Ldr(temp0, MemOperand(src, src_direction * kSystemPointerSize, PostIndex));
1398 Sub(count, count, 1);
1399 Str(temp0, MemOperand(dst, dst_direction * kSystemPointerSize, PostIndex));
1400
1401 Bind(&pairs);
1402 if (mode == kSrcLessThanDst) {
1403 // Adjust pointers for post-index ldp/stp with negative offset:
1404 Sub(dst, dst, kSystemPointerSize);
1405 Sub(src, src, kSystemPointerSize);
1406 } else if (mode == kDstLessThanSrcAndReverse) {
1407 Sub(src, src, kSystemPointerSize);
1408 }
1409 Bind(&loop);
1410 Cbz(count, &done);
1411 Ldp(temp0, temp1,
1412 MemOperand(src, 2 * src_direction * kSystemPointerSize, PostIndex));
1413 Sub(count, count, 2);
1414 if (mode == kDstLessThanSrcAndReverse) {
1415 Stp(temp1, temp0,
1416 MemOperand(dst, 2 * dst_direction * kSystemPointerSize, PostIndex));
1417 } else {
1418 Stp(temp0, temp1,
1419 MemOperand(dst, 2 * dst_direction * kSystemPointerSize, PostIndex));
1420 }
1421 B(&loop);
1422
1423 // TODO(all): large copies may benefit from using temporary Q registers
1424 // to copy four double words per iteration.
1425
1426 Bind(&done);
1427 }
1428
SlotAddress(Register dst,int slot_offset)1429 void TurboAssembler::SlotAddress(Register dst, int slot_offset) {
1430 Add(dst, sp, slot_offset << kSystemPointerSizeLog2);
1431 }
1432
SlotAddress(Register dst,Register slot_offset)1433 void TurboAssembler::SlotAddress(Register dst, Register slot_offset) {
1434 Add(dst, sp, Operand(slot_offset, LSL, kSystemPointerSizeLog2));
1435 }
1436
AssertFPCRState(Register fpcr)1437 void TurboAssembler::AssertFPCRState(Register fpcr) {
1438 if (!FLAG_debug_code) return;
1439 ASM_CODE_COMMENT(this);
1440 Label unexpected_mode, done;
1441 UseScratchRegisterScope temps(this);
1442 if (fpcr.IsNone()) {
1443 fpcr = temps.AcquireX();
1444 Mrs(fpcr, FPCR);
1445 }
1446
1447 // Settings left to their default values:
1448 // - Assert that flush-to-zero is not set.
1449 Tbnz(fpcr, FZ_offset, &unexpected_mode);
1450 // - Assert that the rounding mode is nearest-with-ties-to-even.
1451 STATIC_ASSERT(FPTieEven == 0);
1452 Tst(fpcr, RMode_mask);
1453 B(eq, &done);
1454
1455 Bind(&unexpected_mode);
1456 Abort(AbortReason::kUnexpectedFPCRMode);
1457
1458 Bind(&done);
1459 }
1460
CanonicalizeNaN(const VRegister & dst,const VRegister & src)1461 void TurboAssembler::CanonicalizeNaN(const VRegister& dst,
1462 const VRegister& src) {
1463 AssertFPCRState();
1464
1465 // Subtracting 0.0 preserves all inputs except for signalling NaNs, which
1466 // become quiet NaNs. We use fsub rather than fadd because fsub preserves -0.0
1467 // inputs: -0.0 + 0.0 = 0.0, but -0.0 - 0.0 = -0.0.
1468 Fsub(dst, src, fp_zero);
1469 }
1470
LoadRoot(Register destination,RootIndex index)1471 void TurboAssembler::LoadRoot(Register destination, RootIndex index) {
1472 ASM_CODE_COMMENT(this);
1473 // TODO(jbramley): Most root values are constants, and can be synthesized
1474 // without a load. Refer to the ARM back end for details.
1475 Ldr(destination,
1476 MemOperand(kRootRegister, RootRegisterOffsetForRootIndex(index)));
1477 }
1478
PushRoot(RootIndex index)1479 void TurboAssembler::PushRoot(RootIndex index) {
1480 ASM_CODE_COMMENT(this);
1481 UseScratchRegisterScope temps(this);
1482 Register tmp = temps.AcquireX();
1483 LoadRoot(tmp, index);
1484 Push(tmp);
1485 }
1486
Move(Register dst,Smi src)1487 void TurboAssembler::Move(Register dst, Smi src) { Mov(dst, src); }
Move(Register dst,MemOperand src)1488 void TurboAssembler::Move(Register dst, MemOperand src) { Ldr(dst, src); }
Move(Register dst,Register src)1489 void TurboAssembler::Move(Register dst, Register src) {
1490 if (dst == src) return;
1491 Mov(dst, src);
1492 }
1493
MovePair(Register dst0,Register src0,Register dst1,Register src1)1494 void TurboAssembler::MovePair(Register dst0, Register src0, Register dst1,
1495 Register src1) {
1496 DCHECK_NE(dst0, dst1);
1497 if (dst0 != src1) {
1498 Mov(dst0, src0);
1499 Mov(dst1, src1);
1500 } else if (dst1 != src0) {
1501 // Swap the order of the moves to resolve the overlap.
1502 Mov(dst1, src1);
1503 Mov(dst0, src0);
1504 } else {
1505 // Worse case scenario, this is a swap.
1506 Swap(dst0, src0);
1507 }
1508 }
1509
Swap(Register lhs,Register rhs)1510 void TurboAssembler::Swap(Register lhs, Register rhs) {
1511 DCHECK(lhs.IsSameSizeAndType(rhs));
1512 DCHECK_NE(lhs, rhs);
1513 UseScratchRegisterScope temps(this);
1514 Register temp = temps.AcquireX();
1515 Mov(temp, rhs);
1516 Mov(rhs, lhs);
1517 Mov(lhs, temp);
1518 }
1519
Swap(VRegister lhs,VRegister rhs)1520 void TurboAssembler::Swap(VRegister lhs, VRegister rhs) {
1521 DCHECK(lhs.IsSameSizeAndType(rhs));
1522 DCHECK_NE(lhs, rhs);
1523 UseScratchRegisterScope temps(this);
1524 VRegister temp = VRegister::no_reg();
1525 if (lhs.IsS()) {
1526 temp = temps.AcquireS();
1527 } else if (lhs.IsD()) {
1528 temp = temps.AcquireD();
1529 } else {
1530 DCHECK(lhs.IsQ());
1531 temp = temps.AcquireQ();
1532 }
1533 Mov(temp, rhs);
1534 Mov(rhs, lhs);
1535 Mov(lhs, temp);
1536 }
1537
AssertSmi(Register object,AbortReason reason)1538 void TurboAssembler::AssertSmi(Register object, AbortReason reason) {
1539 if (!FLAG_debug_code) return;
1540 ASM_CODE_COMMENT(this);
1541 STATIC_ASSERT(kSmiTag == 0);
1542 Tst(object, kSmiTagMask);
1543 Check(eq, reason);
1544 }
1545
AssertNotSmi(Register object,AbortReason reason)1546 void MacroAssembler::AssertNotSmi(Register object, AbortReason reason) {
1547 if (!FLAG_debug_code) return;
1548 ASM_CODE_COMMENT(this);
1549 STATIC_ASSERT(kSmiTag == 0);
1550 Tst(object, kSmiTagMask);
1551 Check(ne, reason);
1552 }
1553
AssertCodeT(Register object)1554 void MacroAssembler::AssertCodeT(Register object) {
1555 if (!FLAG_debug_code) return;
1556 ASM_CODE_COMMENT(this);
1557 AssertNotSmi(object, AbortReason::kOperandIsNotACodeT);
1558
1559 UseScratchRegisterScope temps(this);
1560 Register temp = temps.AcquireX();
1561
1562 CompareObjectType(object, temp, temp, CODET_TYPE);
1563 Check(eq, AbortReason::kOperandIsNotACodeT);
1564 }
1565
AssertConstructor(Register object)1566 void MacroAssembler::AssertConstructor(Register object) {
1567 if (!FLAG_debug_code) return;
1568 ASM_CODE_COMMENT(this);
1569 AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotAConstructor);
1570
1571 UseScratchRegisterScope temps(this);
1572 Register temp = temps.AcquireX();
1573
1574 LoadMap(temp, object);
1575 Ldrb(temp, FieldMemOperand(temp, Map::kBitFieldOffset));
1576 Tst(temp, Operand(Map::Bits1::IsConstructorBit::kMask));
1577
1578 Check(ne, AbortReason::kOperandIsNotAConstructor);
1579 }
1580
AssertFunction(Register object)1581 void MacroAssembler::AssertFunction(Register object) {
1582 if (!FLAG_debug_code) return;
1583 ASM_CODE_COMMENT(this);
1584 AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotAFunction);
1585
1586 UseScratchRegisterScope temps(this);
1587 Register temp = temps.AcquireX();
1588 LoadMap(temp, object);
1589 CompareInstanceTypeRange(temp, temp, FIRST_JS_FUNCTION_TYPE,
1590 LAST_JS_FUNCTION_TYPE);
1591 Check(ls, AbortReason::kOperandIsNotAFunction);
1592 }
1593
AssertCallableFunction(Register object)1594 void MacroAssembler::AssertCallableFunction(Register object) {
1595 if (!FLAG_debug_code) return;
1596 ASM_CODE_COMMENT(this);
1597 AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotAFunction);
1598
1599 UseScratchRegisterScope temps(this);
1600 Register temp = temps.AcquireX();
1601 LoadMap(temp, object);
1602 CompareInstanceTypeRange(temp, temp, FIRST_CALLABLE_JS_FUNCTION_TYPE,
1603 LAST_CALLABLE_JS_FUNCTION_TYPE);
1604 Check(ls, AbortReason::kOperandIsNotACallableFunction);
1605 }
1606
AssertBoundFunction(Register object)1607 void MacroAssembler::AssertBoundFunction(Register object) {
1608 if (!FLAG_debug_code) return;
1609 ASM_CODE_COMMENT(this);
1610 AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotABoundFunction);
1611
1612 UseScratchRegisterScope temps(this);
1613 Register temp = temps.AcquireX();
1614
1615 CompareObjectType(object, temp, temp, JS_BOUND_FUNCTION_TYPE);
1616 Check(eq, AbortReason::kOperandIsNotABoundFunction);
1617 }
1618
AssertGeneratorObject(Register object)1619 void MacroAssembler::AssertGeneratorObject(Register object) {
1620 if (!FLAG_debug_code) return;
1621 ASM_CODE_COMMENT(this);
1622 AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotAGeneratorObject);
1623
1624 // Load map
1625 UseScratchRegisterScope temps(this);
1626 Register temp = temps.AcquireX();
1627 LoadMap(temp, object);
1628
1629 Label do_check;
1630 // Load instance type and check if JSGeneratorObject
1631 CompareInstanceType(temp, temp, JS_GENERATOR_OBJECT_TYPE);
1632 B(eq, &do_check);
1633
1634 // Check if JSAsyncFunctionObject
1635 Cmp(temp, JS_ASYNC_FUNCTION_OBJECT_TYPE);
1636 B(eq, &do_check);
1637
1638 // Check if JSAsyncGeneratorObject
1639 Cmp(temp, JS_ASYNC_GENERATOR_OBJECT_TYPE);
1640
1641 bind(&do_check);
1642 // Restore generator object to register and perform assertion
1643 Check(eq, AbortReason::kOperandIsNotAGeneratorObject);
1644 }
1645
AssertUndefinedOrAllocationSite(Register object)1646 void MacroAssembler::AssertUndefinedOrAllocationSite(Register object) {
1647 if (!FLAG_debug_code) return;
1648 ASM_CODE_COMMENT(this);
1649 UseScratchRegisterScope temps(this);
1650 Register scratch = temps.AcquireX();
1651 Label done_checking;
1652 AssertNotSmi(object);
1653 JumpIfRoot(object, RootIndex::kUndefinedValue, &done_checking);
1654 LoadMap(scratch, object);
1655 CompareInstanceType(scratch, scratch, ALLOCATION_SITE_TYPE);
1656 Assert(eq, AbortReason::kExpectedUndefinedOrCell);
1657 Bind(&done_checking);
1658 }
1659
AssertPositiveOrZero(Register value)1660 void TurboAssembler::AssertPositiveOrZero(Register value) {
1661 if (!FLAG_debug_code) return;
1662 ASM_CODE_COMMENT(this);
1663 Label done;
1664 int sign_bit = value.Is64Bits() ? kXSignBit : kWSignBit;
1665 Tbz(value, sign_bit, &done);
1666 Abort(AbortReason::kUnexpectedNegativeValue);
1667 Bind(&done);
1668 }
1669
CallRuntime(const Runtime::Function * f,int num_arguments,SaveFPRegsMode save_doubles)1670 void MacroAssembler::CallRuntime(const Runtime::Function* f, int num_arguments,
1671 SaveFPRegsMode save_doubles) {
1672 ASM_CODE_COMMENT(this);
1673 // All arguments must be on the stack before this function is called.
1674 // x0 holds the return value after the call.
1675
1676 // Check that the number of arguments matches what the function expects.
1677 // If f->nargs is -1, the function can accept a variable number of arguments.
1678 CHECK(f->nargs < 0 || f->nargs == num_arguments);
1679
1680 // Place the necessary arguments.
1681 Mov(x0, num_arguments);
1682 Mov(x1, ExternalReference::Create(f));
1683
1684 Handle<CodeT> code =
1685 CodeFactory::CEntry(isolate(), f->result_size, save_doubles);
1686 Call(code, RelocInfo::CODE_TARGET);
1687 }
1688
JumpToExternalReference(const ExternalReference & builtin,bool builtin_exit_frame)1689 void MacroAssembler::JumpToExternalReference(const ExternalReference& builtin,
1690 bool builtin_exit_frame) {
1691 ASM_CODE_COMMENT(this);
1692 Mov(x1, builtin);
1693 Handle<CodeT> code =
1694 CodeFactory::CEntry(isolate(), 1, SaveFPRegsMode::kIgnore,
1695 ArgvMode::kStack, builtin_exit_frame);
1696 Jump(code, RelocInfo::CODE_TARGET);
1697 }
1698
JumpToOffHeapInstructionStream(Address entry)1699 void MacroAssembler::JumpToOffHeapInstructionStream(Address entry) {
1700 Ldr(kOffHeapTrampolineRegister, Operand(entry, RelocInfo::OFF_HEAP_TARGET));
1701 Br(kOffHeapTrampolineRegister);
1702 }
1703
TailCallRuntime(Runtime::FunctionId fid)1704 void MacroAssembler::TailCallRuntime(Runtime::FunctionId fid) {
1705 ASM_CODE_COMMENT(this);
1706 const Runtime::Function* function = Runtime::FunctionForId(fid);
1707 DCHECK_EQ(1, function->result_size);
1708 if (function->nargs >= 0) {
1709 // TODO(1236192): Most runtime routines don't need the number of
1710 // arguments passed in because it is constant. At some point we
1711 // should remove this need and make the runtime routine entry code
1712 // smarter.
1713 Mov(x0, function->nargs);
1714 }
1715 JumpToExternalReference(ExternalReference::Create(fid));
1716 }
1717
ActivationFrameAlignment()1718 int TurboAssembler::ActivationFrameAlignment() {
1719 #if V8_HOST_ARCH_ARM64
1720 // Running on the real platform. Use the alignment as mandated by the local
1721 // environment.
1722 // Note: This will break if we ever start generating snapshots on one ARM
1723 // platform for another ARM platform with a different alignment.
1724 return base::OS::ActivationFrameAlignment();
1725 #else // V8_HOST_ARCH_ARM64
1726 // If we are using the simulator then we should always align to the expected
1727 // alignment. As the simulator is used to generate snapshots we do not know
1728 // if the target platform will need alignment, so this is controlled from a
1729 // flag.
1730 return FLAG_sim_stack_alignment;
1731 #endif // V8_HOST_ARCH_ARM64
1732 }
1733
CallCFunction(ExternalReference function,int num_of_reg_args)1734 void TurboAssembler::CallCFunction(ExternalReference function,
1735 int num_of_reg_args) {
1736 CallCFunction(function, num_of_reg_args, 0);
1737 }
1738
CallCFunction(ExternalReference function,int num_of_reg_args,int num_of_double_args)1739 void TurboAssembler::CallCFunction(ExternalReference function,
1740 int num_of_reg_args,
1741 int num_of_double_args) {
1742 ASM_CODE_COMMENT(this);
1743 UseScratchRegisterScope temps(this);
1744 Register temp = temps.AcquireX();
1745 Mov(temp, function);
1746 CallCFunction(temp, num_of_reg_args, num_of_double_args);
1747 }
1748
1749 static const int kRegisterPassedArguments = 8;
1750 static const int kFPRegisterPassedArguments = 8;
1751
CallCFunction(Register function,int num_of_reg_args,int num_of_double_args)1752 void TurboAssembler::CallCFunction(Register function, int num_of_reg_args,
1753 int num_of_double_args) {
1754 ASM_CODE_COMMENT(this);
1755 DCHECK_LE(num_of_reg_args + num_of_double_args, kMaxCParameters);
1756 DCHECK(has_frame());
1757
1758 // Save the frame pointer and PC so that the stack layout remains iterable,
1759 // even without an ExitFrame which normally exists between JS and C frames.
1760 Register pc_scratch = x4;
1761 Register addr_scratch = x5;
1762 Push(pc_scratch, addr_scratch);
1763
1764 Label get_pc;
1765 Bind(&get_pc);
1766 Adr(pc_scratch, &get_pc);
1767
1768 // See x64 code for reasoning about how to address the isolate data fields.
1769 if (root_array_available()) {
1770 Str(pc_scratch,
1771 MemOperand(kRootRegister, IsolateData::fast_c_call_caller_pc_offset()));
1772 Str(fp,
1773 MemOperand(kRootRegister, IsolateData::fast_c_call_caller_fp_offset()));
1774 } else {
1775 DCHECK_NOT_NULL(isolate());
1776 Mov(addr_scratch,
1777 ExternalReference::fast_c_call_caller_pc_address(isolate()));
1778 Str(pc_scratch, MemOperand(addr_scratch));
1779 Mov(addr_scratch,
1780 ExternalReference::fast_c_call_caller_fp_address(isolate()));
1781 Str(fp, MemOperand(addr_scratch));
1782 }
1783
1784 Pop(addr_scratch, pc_scratch);
1785
1786 // Call directly. The function called cannot cause a GC, or allow preemption,
1787 // so the return address in the link register stays correct.
1788 Call(function);
1789
1790 // We don't unset the PC; the FP is the source of truth.
1791 if (root_array_available()) {
1792 Str(xzr,
1793 MemOperand(kRootRegister, IsolateData::fast_c_call_caller_fp_offset()));
1794 } else {
1795 DCHECK_NOT_NULL(isolate());
1796 Push(addr_scratch, xzr);
1797 Mov(addr_scratch,
1798 ExternalReference::fast_c_call_caller_fp_address(isolate()));
1799 Str(xzr, MemOperand(addr_scratch));
1800 Pop(xzr, addr_scratch);
1801 }
1802
1803 if (num_of_reg_args > kRegisterPassedArguments) {
1804 // Drop the register passed arguments.
1805 int claim_slots = RoundUp(num_of_reg_args - kRegisterPassedArguments, 2);
1806 Drop(claim_slots);
1807 }
1808
1809 if (num_of_double_args > kFPRegisterPassedArguments) {
1810 // Drop the register passed arguments.
1811 int claim_slots =
1812 RoundUp(num_of_double_args - kFPRegisterPassedArguments, 2);
1813 Drop(claim_slots);
1814 }
1815 }
1816
LoadFromConstantsTable(Register destination,int constant_index)1817 void TurboAssembler::LoadFromConstantsTable(Register destination,
1818 int constant_index) {
1819 ASM_CODE_COMMENT(this);
1820 DCHECK(RootsTable::IsImmortalImmovable(RootIndex::kBuiltinsConstantsTable));
1821 LoadRoot(destination, RootIndex::kBuiltinsConstantsTable);
1822 LoadTaggedPointerField(
1823 destination, FieldMemOperand(destination, FixedArray::OffsetOfElementAt(
1824 constant_index)));
1825 }
1826
LoadRootRelative(Register destination,int32_t offset)1827 void TurboAssembler::LoadRootRelative(Register destination, int32_t offset) {
1828 Ldr(destination, MemOperand(kRootRegister, offset));
1829 }
1830
LoadRootRegisterOffset(Register destination,intptr_t offset)1831 void TurboAssembler::LoadRootRegisterOffset(Register destination,
1832 intptr_t offset) {
1833 if (offset == 0) {
1834 Mov(destination, kRootRegister);
1835 } else {
1836 Add(destination, kRootRegister, offset);
1837 }
1838 }
1839
Jump(Register target,Condition cond)1840 void TurboAssembler::Jump(Register target, Condition cond) {
1841 if (cond == nv) return;
1842 Label done;
1843 if (cond != al) B(NegateCondition(cond), &done);
1844 Br(target);
1845 Bind(&done);
1846 }
1847
JumpHelper(int64_t offset,RelocInfo::Mode rmode,Condition cond)1848 void TurboAssembler::JumpHelper(int64_t offset, RelocInfo::Mode rmode,
1849 Condition cond) {
1850 if (cond == nv) return;
1851 Label done;
1852 if (cond != al) B(NegateCondition(cond), &done);
1853 if (CanUseNearCallOrJump(rmode)) {
1854 DCHECK(IsNearCallOffset(offset));
1855 near_jump(static_cast<int>(offset), rmode);
1856 } else {
1857 UseScratchRegisterScope temps(this);
1858 Register temp = temps.AcquireX();
1859 uint64_t imm = reinterpret_cast<uint64_t>(pc_) + offset * kInstrSize;
1860 Mov(temp, Immediate(imm, rmode));
1861 Br(temp);
1862 }
1863 Bind(&done);
1864 }
1865
1866 // The calculated offset is either:
1867 // * the 'target' input unmodified if this is a Wasm call, or
1868 // * the offset of the target from the code range start, if this is a call to
1869 // un-embedded builtin, or
1870 // * the offset of the target from the current PC, in instructions, for any
1871 // other type of call.
CalculateTargetOffset(Address target,RelocInfo::Mode rmode,byte * pc)1872 int64_t TurboAssembler::CalculateTargetOffset(Address target,
1873 RelocInfo::Mode rmode, byte* pc) {
1874 int64_t offset = static_cast<int64_t>(target);
1875 if (rmode == RelocInfo::WASM_CALL || rmode == RelocInfo::WASM_STUB_CALL) {
1876 // The target of WebAssembly calls is still an index instead of an actual
1877 // address at this point, and needs to be encoded as-is.
1878 return offset;
1879 }
1880 if (RelocInfo::IsRuntimeEntry(rmode)) {
1881 // The runtime entry targets are used for generating short builtin calls
1882 // from JIT-compiled code (it's not used during snapshot creation).
1883 // The value is encoded as an offset from the code range (see
1884 // Assembler::runtime_entry_at()).
1885 // Note, that builtin-to-builitin calls use different OFF_HEAP_TARGET mode
1886 // and therefore are encoded differently.
1887 DCHECK_NE(options().code_range_base, 0);
1888 offset -= static_cast<int64_t>(options().code_range_base);
1889 } else {
1890 offset -= reinterpret_cast<int64_t>(pc);
1891 }
1892 DCHECK_EQ(offset % kInstrSize, 0);
1893 offset = offset / static_cast<int>(kInstrSize);
1894 return offset;
1895 }
1896
Jump(Address target,RelocInfo::Mode rmode,Condition cond)1897 void TurboAssembler::Jump(Address target, RelocInfo::Mode rmode,
1898 Condition cond) {
1899 int64_t offset = CalculateTargetOffset(target, rmode, pc_);
1900 JumpHelper(offset, rmode, cond);
1901 }
1902
Jump(Handle<CodeT> code,RelocInfo::Mode rmode,Condition cond)1903 void TurboAssembler::Jump(Handle<CodeT> code, RelocInfo::Mode rmode,
1904 Condition cond) {
1905 DCHECK(RelocInfo::IsCodeTarget(rmode));
1906 DCHECK_IMPLIES(options().isolate_independent_code,
1907 Builtins::IsIsolateIndependentBuiltin(FromCodeT(*code)));
1908
1909 if (options().inline_offheap_trampolines) {
1910 Builtin builtin = Builtin::kNoBuiltinId;
1911 if (isolate()->builtins()->IsBuiltinHandle(code, &builtin)) {
1912 // Inline the trampoline.
1913 CHECK_EQ(cond, Condition::al); // Implement if necessary.
1914 TailCallBuiltin(builtin);
1915 return;
1916 }
1917 }
1918
1919 if (CanUseNearCallOrJump(rmode)) {
1920 EmbeddedObjectIndex index = AddEmbeddedObject(code);
1921 DCHECK(is_int32(index));
1922 JumpHelper(static_cast<int64_t>(index), rmode, cond);
1923 } else {
1924 Jump(code.address(), rmode, cond);
1925 }
1926 }
1927
Jump(const ExternalReference & reference)1928 void TurboAssembler::Jump(const ExternalReference& reference) {
1929 UseScratchRegisterScope temps(this);
1930 Register scratch = temps.AcquireX();
1931 Mov(scratch, reference);
1932 Jump(scratch);
1933 }
1934
Call(Register target)1935 void TurboAssembler::Call(Register target) {
1936 BlockPoolsScope scope(this);
1937 Blr(target);
1938 }
1939
Call(Address target,RelocInfo::Mode rmode)1940 void TurboAssembler::Call(Address target, RelocInfo::Mode rmode) {
1941 BlockPoolsScope scope(this);
1942 if (CanUseNearCallOrJump(rmode)) {
1943 int64_t offset = CalculateTargetOffset(target, rmode, pc_);
1944 DCHECK(IsNearCallOffset(offset));
1945 near_call(static_cast<int>(offset), rmode);
1946 } else {
1947 IndirectCall(target, rmode);
1948 }
1949 }
1950
Call(Handle<CodeT> code,RelocInfo::Mode rmode)1951 void TurboAssembler::Call(Handle<CodeT> code, RelocInfo::Mode rmode) {
1952 DCHECK_IMPLIES(options().isolate_independent_code,
1953 Builtins::IsIsolateIndependentBuiltin(FromCodeT(*code)));
1954 BlockPoolsScope scope(this);
1955
1956 if (options().inline_offheap_trampolines) {
1957 Builtin builtin = Builtin::kNoBuiltinId;
1958 if (isolate()->builtins()->IsBuiltinHandle(code, &builtin)) {
1959 // Inline the trampoline.
1960 CallBuiltin(builtin);
1961 return;
1962 }
1963 }
1964
1965 DCHECK(FromCodeT(*code).IsExecutable());
1966 if (CanUseNearCallOrJump(rmode)) {
1967 EmbeddedObjectIndex index = AddEmbeddedObject(code);
1968 DCHECK(is_int32(index));
1969 near_call(static_cast<int32_t>(index), rmode);
1970 } else {
1971 IndirectCall(code.address(), rmode);
1972 }
1973 }
1974
Call(ExternalReference target)1975 void TurboAssembler::Call(ExternalReference target) {
1976 UseScratchRegisterScope temps(this);
1977 Register temp = temps.AcquireX();
1978 Mov(temp, target);
1979 Call(temp);
1980 }
1981
LoadEntryFromBuiltinIndex(Register builtin_index)1982 void TurboAssembler::LoadEntryFromBuiltinIndex(Register builtin_index) {
1983 ASM_CODE_COMMENT(this);
1984 // The builtin_index register contains the builtin index as a Smi.
1985 // Untagging is folded into the indexing operand below.
1986 if (SmiValuesAre32Bits()) {
1987 Asr(builtin_index, builtin_index, kSmiShift - kSystemPointerSizeLog2);
1988 Add(builtin_index, builtin_index,
1989 IsolateData::builtin_entry_table_offset());
1990 Ldr(builtin_index, MemOperand(kRootRegister, builtin_index));
1991 } else {
1992 DCHECK(SmiValuesAre31Bits());
1993 if (COMPRESS_POINTERS_BOOL) {
1994 Add(builtin_index, kRootRegister,
1995 Operand(builtin_index.W(), SXTW, kSystemPointerSizeLog2 - kSmiShift));
1996 } else {
1997 Add(builtin_index, kRootRegister,
1998 Operand(builtin_index, LSL, kSystemPointerSizeLog2 - kSmiShift));
1999 }
2000 Ldr(builtin_index,
2001 MemOperand(builtin_index, IsolateData::builtin_entry_table_offset()));
2002 }
2003 }
2004
LoadEntryFromBuiltin(Builtin builtin,Register destination)2005 void TurboAssembler::LoadEntryFromBuiltin(Builtin builtin,
2006 Register destination) {
2007 Ldr(destination, EntryFromBuiltinAsOperand(builtin));
2008 }
2009
EntryFromBuiltinAsOperand(Builtin builtin)2010 MemOperand TurboAssembler::EntryFromBuiltinAsOperand(Builtin builtin) {
2011 ASM_CODE_COMMENT(this);
2012 DCHECK(root_array_available());
2013 return MemOperand(kRootRegister,
2014 IsolateData::BuiltinEntrySlotOffset(builtin));
2015 }
2016
CallBuiltinByIndex(Register builtin_index)2017 void TurboAssembler::CallBuiltinByIndex(Register builtin_index) {
2018 ASM_CODE_COMMENT(this);
2019 LoadEntryFromBuiltinIndex(builtin_index);
2020 Call(builtin_index);
2021 }
2022
CallBuiltin(Builtin builtin)2023 void TurboAssembler::CallBuiltin(Builtin builtin) {
2024 ASM_CODE_COMMENT(this);
2025 DCHECK(Builtins::IsBuiltinId(builtin));
2026 RecordCommentForOffHeapTrampoline(builtin);
2027 CHECK_NE(builtin, Builtin::kNoBuiltinId);
2028 if (options().short_builtin_calls) {
2029 Call(BuiltinEntry(builtin), RelocInfo::RUNTIME_ENTRY);
2030
2031 } else {
2032 UseScratchRegisterScope temps(this);
2033 Register scratch = temps.AcquireX();
2034 Ldr(scratch, Operand(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET));
2035 Call(scratch);
2036 }
2037 }
2038
TailCallBuiltin(Builtin builtin)2039 void TurboAssembler::TailCallBuiltin(Builtin builtin) {
2040 ASM_CODE_COMMENT(this);
2041 DCHECK(Builtins::IsBuiltinId(builtin));
2042 RecordCommentForOffHeapTrampoline(builtin);
2043 CHECK_NE(builtin, Builtin::kNoBuiltinId);
2044 if (options().short_builtin_calls) {
2045 Jump(BuiltinEntry(builtin), RelocInfo::RUNTIME_ENTRY);
2046
2047 } else {
2048 // The control flow integrity (CFI) feature allows us to "sign" code entry
2049 // points as a target for calls, jumps or both. Arm64 has special
2050 // instructions for this purpose, so-called "landing pads" (see
2051 // TurboAssembler::CallTarget(), TurboAssembler::JumpTarget() and
2052 // TurboAssembler::JumpOrCallTarget()). Currently, we generate "Call"
2053 // landing pads for CPP builtins. In order to allow tail calling to those
2054 // builtins we have to use a workaround.
2055 // x17 is used to allow using "Call" (i.e. `bti c`) rather than "Jump"
2056 // (i.e. `bti j`) landing pads for the tail-called code.
2057 Register temp = x17;
2058
2059 Ldr(temp, Operand(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET));
2060 Jump(temp);
2061 }
2062 }
2063
LoadCodeObjectEntry(Register destination,Register code_object)2064 void TurboAssembler::LoadCodeObjectEntry(Register destination,
2065 Register code_object) {
2066 ASM_CODE_COMMENT(this);
2067 if (V8_EXTERNAL_CODE_SPACE_BOOL) {
2068 LoadCodeDataContainerEntry(destination, code_object);
2069 return;
2070 }
2071
2072 // Code objects are called differently depending on whether we are generating
2073 // builtin code (which will later be embedded into the binary) or compiling
2074 // user JS code at runtime.
2075 // * Builtin code runs in --jitless mode and thus must not call into on-heap
2076 // Code targets. Instead, we dispatch through the builtins entry table.
2077 // * Codegen at runtime does not have this restriction and we can use the
2078 // shorter, branchless instruction sequence. The assumption here is that
2079 // targets are usually generated code and not builtin Code objects.
2080
2081 if (options().isolate_independent_code) {
2082 DCHECK(root_array_available());
2083 Label if_code_is_off_heap, out;
2084
2085 UseScratchRegisterScope temps(this);
2086 Register scratch = temps.AcquireX();
2087
2088 DCHECK(!AreAliased(destination, scratch));
2089 DCHECK(!AreAliased(code_object, scratch));
2090
2091 // Check whether the Code object is an off-heap trampoline. If so, call its
2092 // (off-heap) entry point directly without going through the (on-heap)
2093 // trampoline. Otherwise, just call the Code object as always.
2094
2095 Ldr(scratch.W(), FieldMemOperand(code_object, Code::kFlagsOffset));
2096 TestAndBranchIfAnySet(scratch.W(), Code::IsOffHeapTrampoline::kMask,
2097 &if_code_is_off_heap);
2098
2099 // Not an off-heap trampoline object, the entry point is at
2100 // Code::raw_instruction_start().
2101 Add(destination, code_object, Code::kHeaderSize - kHeapObjectTag);
2102 B(&out);
2103
2104 // An off-heap trampoline, the entry point is loaded from the builtin entry
2105 // table.
2106 bind(&if_code_is_off_heap);
2107 Ldrsw(scratch, FieldMemOperand(code_object, Code::kBuiltinIndexOffset));
2108 Add(destination, kRootRegister,
2109 Operand(scratch, LSL, kSystemPointerSizeLog2));
2110 Ldr(destination,
2111 MemOperand(destination, IsolateData::builtin_entry_table_offset()));
2112
2113 bind(&out);
2114 } else {
2115 Add(destination, code_object, Code::kHeaderSize - kHeapObjectTag);
2116 }
2117 }
2118
CallCodeObject(Register code_object)2119 void TurboAssembler::CallCodeObject(Register code_object) {
2120 ASM_CODE_COMMENT(this);
2121 LoadCodeObjectEntry(code_object, code_object);
2122 Call(code_object);
2123 }
2124
JumpCodeObject(Register code_object,JumpMode jump_mode)2125 void TurboAssembler::JumpCodeObject(Register code_object, JumpMode jump_mode) {
2126 ASM_CODE_COMMENT(this);
2127 DCHECK_EQ(JumpMode::kJump, jump_mode);
2128 LoadCodeObjectEntry(code_object, code_object);
2129
2130 UseScratchRegisterScope temps(this);
2131 if (code_object != x17) {
2132 temps.Exclude(x17);
2133 Mov(x17, code_object);
2134 }
2135 Jump(x17);
2136 }
2137
LoadCodeDataContainerEntry(Register destination,Register code_data_container_object)2138 void TurboAssembler::LoadCodeDataContainerEntry(
2139 Register destination, Register code_data_container_object) {
2140 ASM_CODE_COMMENT(this);
2141 CHECK(V8_EXTERNAL_CODE_SPACE_BOOL);
2142
2143 LoadExternalPointerField(
2144 destination,
2145 FieldMemOperand(code_data_container_object,
2146 CodeDataContainer::kCodeEntryPointOffset),
2147 kCodeEntryPointTag);
2148 }
2149
LoadCodeDataContainerCodeNonBuiltin(Register destination,Register code_data_container_object)2150 void TurboAssembler::LoadCodeDataContainerCodeNonBuiltin(
2151 Register destination, Register code_data_container_object) {
2152 ASM_CODE_COMMENT(this);
2153 CHECK(V8_EXTERNAL_CODE_SPACE_BOOL);
2154 // Given the fields layout we can read the Code reference as a full word.
2155 STATIC_ASSERT(!V8_EXTERNAL_CODE_SPACE_BOOL ||
2156 (CodeDataContainer::kCodeCageBaseUpper32BitsOffset ==
2157 CodeDataContainer::kCodeOffset + kTaggedSize));
2158 Ldr(destination, FieldMemOperand(code_data_container_object,
2159 CodeDataContainer::kCodeOffset));
2160 }
2161
CallCodeDataContainerObject(Register code_data_container_object)2162 void TurboAssembler::CallCodeDataContainerObject(
2163 Register code_data_container_object) {
2164 ASM_CODE_COMMENT(this);
2165 LoadCodeDataContainerEntry(code_data_container_object,
2166 code_data_container_object);
2167 Call(code_data_container_object);
2168 }
2169
JumpCodeDataContainerObject(Register code_data_container_object,JumpMode jump_mode)2170 void TurboAssembler::JumpCodeDataContainerObject(
2171 Register code_data_container_object, JumpMode jump_mode) {
2172 ASM_CODE_COMMENT(this);
2173 DCHECK_EQ(JumpMode::kJump, jump_mode);
2174 LoadCodeDataContainerEntry(code_data_container_object,
2175 code_data_container_object);
2176 UseScratchRegisterScope temps(this);
2177 if (code_data_container_object != x17) {
2178 temps.Exclude(x17);
2179 Mov(x17, code_data_container_object);
2180 }
2181 Jump(x17);
2182 }
2183
LoadCodeTEntry(Register destination,Register code)2184 void TurboAssembler::LoadCodeTEntry(Register destination, Register code) {
2185 ASM_CODE_COMMENT(this);
2186 if (V8_EXTERNAL_CODE_SPACE_BOOL) {
2187 LoadCodeDataContainerEntry(destination, code);
2188 } else {
2189 Add(destination, code, Operand(Code::kHeaderSize - kHeapObjectTag));
2190 }
2191 }
2192
CallCodeTObject(Register code)2193 void TurboAssembler::CallCodeTObject(Register code) {
2194 if (V8_EXTERNAL_CODE_SPACE_BOOL) {
2195 CallCodeDataContainerObject(code);
2196 } else {
2197 CallCodeObject(code);
2198 }
2199 }
2200
JumpCodeTObject(Register code,JumpMode jump_mode)2201 void TurboAssembler::JumpCodeTObject(Register code, JumpMode jump_mode) {
2202 if (V8_EXTERNAL_CODE_SPACE_BOOL) {
2203 JumpCodeDataContainerObject(code, jump_mode);
2204 } else {
2205 JumpCodeObject(code, jump_mode);
2206 }
2207 }
2208
StoreReturnAddressAndCall(Register target)2209 void TurboAssembler::StoreReturnAddressAndCall(Register target) {
2210 ASM_CODE_COMMENT(this);
2211 // This generates the final instruction sequence for calls to C functions
2212 // once an exit frame has been constructed.
2213 //
2214 // Note that this assumes the caller code (i.e. the Code object currently
2215 // being generated) is immovable or that the callee function cannot trigger
2216 // GC, since the callee function will return to it.
2217
2218 UseScratchRegisterScope temps(this);
2219 temps.Exclude(x16, x17);
2220
2221 Label return_location;
2222 Adr(x17, &return_location);
2223 #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
2224 Add(x16, sp, kSystemPointerSize);
2225 Pacib1716();
2226 #endif
2227 Poke(x17, 0);
2228
2229 if (FLAG_debug_code) {
2230 ASM_CODE_COMMENT_STRING(this, "Verify fp[kSPOffset]-8");
2231 // Verify that the slot below fp[kSPOffset]-8 points to the signed return
2232 // location.
2233 Ldr(x16, MemOperand(fp, ExitFrameConstants::kSPOffset));
2234 Ldr(x16, MemOperand(x16, -static_cast<int64_t>(kXRegSize)));
2235 Cmp(x16, x17);
2236 Check(eq, AbortReason::kReturnAddressNotFoundInFrame);
2237 }
2238
2239 Blr(target);
2240 Bind(&return_location);
2241 }
2242
IndirectCall(Address target,RelocInfo::Mode rmode)2243 void TurboAssembler::IndirectCall(Address target, RelocInfo::Mode rmode) {
2244 ASM_CODE_COMMENT(this);
2245 UseScratchRegisterScope temps(this);
2246 Register temp = temps.AcquireX();
2247 Mov(temp, Immediate(target, rmode));
2248 Blr(temp);
2249 }
2250
IsNearCallOffset(int64_t offset)2251 bool TurboAssembler::IsNearCallOffset(int64_t offset) {
2252 return is_int26(offset);
2253 }
2254
CallForDeoptimization(Builtin target,int deopt_id,Label * exit,DeoptimizeKind kind,Label * ret,Label * jump_deoptimization_entry_label)2255 void TurboAssembler::CallForDeoptimization(
2256 Builtin target, int deopt_id, Label* exit, DeoptimizeKind kind, Label* ret,
2257 Label* jump_deoptimization_entry_label) {
2258 ASM_CODE_COMMENT(this);
2259 BlockPoolsScope scope(this);
2260 bl(jump_deoptimization_entry_label);
2261 DCHECK_EQ(SizeOfCodeGeneratedSince(exit),
2262 (kind == DeoptimizeKind::kLazy) ? Deoptimizer::kLazyDeoptExitSize
2263 : Deoptimizer::kEagerDeoptExitSize);
2264 }
2265
LoadStackLimit(Register destination,StackLimitKind kind)2266 void MacroAssembler::LoadStackLimit(Register destination, StackLimitKind kind) {
2267 ASM_CODE_COMMENT(this);
2268 DCHECK(root_array_available());
2269 Isolate* isolate = this->isolate();
2270 ExternalReference limit =
2271 kind == StackLimitKind::kRealStackLimit
2272 ? ExternalReference::address_of_real_jslimit(isolate)
2273 : ExternalReference::address_of_jslimit(isolate);
2274 DCHECK(TurboAssembler::IsAddressableThroughRootRegister(isolate, limit));
2275
2276 intptr_t offset =
2277 TurboAssembler::RootRegisterOffsetForExternalReference(isolate, limit);
2278 Ldr(destination, MemOperand(kRootRegister, offset));
2279 }
2280
StackOverflowCheck(Register num_args,Label * stack_overflow)2281 void MacroAssembler::StackOverflowCheck(Register num_args,
2282 Label* stack_overflow) {
2283 ASM_CODE_COMMENT(this);
2284 UseScratchRegisterScope temps(this);
2285 Register scratch = temps.AcquireX();
2286
2287 // Check the stack for overflow.
2288 // We are not trying to catch interruptions (e.g. debug break and
2289 // preemption) here, so the "real stack limit" is checked.
2290
2291 LoadStackLimit(scratch, StackLimitKind::kRealStackLimit);
2292 // Make scratch the space we have left. The stack might already be overflowed
2293 // here which will cause scratch to become negative.
2294 Sub(scratch, sp, scratch);
2295 // Check if the arguments will overflow the stack.
2296 Cmp(scratch, Operand(num_args, LSL, kSystemPointerSizeLog2));
2297 B(le, stack_overflow);
2298 }
2299
InvokePrologue(Register formal_parameter_count,Register actual_argument_count,Label * done,InvokeType type)2300 void MacroAssembler::InvokePrologue(Register formal_parameter_count,
2301 Register actual_argument_count, Label* done,
2302 InvokeType type) {
2303 ASM_CODE_COMMENT(this);
2304 // x0: actual arguments count.
2305 // x1: function (passed through to callee).
2306 // x2: expected arguments count.
2307 // x3: new target
2308 Label regular_invoke;
2309 DCHECK_EQ(actual_argument_count, x0);
2310 DCHECK_EQ(formal_parameter_count, x2);
2311
2312 // If the formal parameter count is equal to the adaptor sentinel, no need
2313 // to push undefined value as arguments.
2314 if (kDontAdaptArgumentsSentinel != 0) {
2315 Cmp(formal_parameter_count, Operand(kDontAdaptArgumentsSentinel));
2316 B(eq, ®ular_invoke);
2317 }
2318
2319 // If overapplication or if the actual argument count is equal to the
2320 // formal parameter count, no need to push extra undefined values.
2321 Register extra_argument_count = x2;
2322 Subs(extra_argument_count, formal_parameter_count, actual_argument_count);
2323 B(le, ®ular_invoke);
2324
2325 // The stack pointer in arm64 needs to be 16-byte aligned. We might need to
2326 // (1) add an extra padding or (2) remove (re-use) the extra padding already
2327 // in the stack. Let {slots_to_copy} be the number of slots (arguments) to
2328 // move up in the stack and let {slots_to_claim} be the number of extra stack
2329 // slots to claim.
2330 Label even_extra_count, skip_move;
2331 Register slots_to_copy = x4;
2332 Register slots_to_claim = x5;
2333
2334 Mov(slots_to_copy, actual_argument_count);
2335 Mov(slots_to_claim, extra_argument_count);
2336 Tbz(extra_argument_count, 0, &even_extra_count);
2337
2338 // Calculate {slots_to_claim} when {extra_argument_count} is odd.
2339 // If {actual_argument_count} is even, we need one extra padding slot
2340 // {slots_to_claim = extra_argument_count + 1}.
2341 // If {actual_argument_count} is odd, we know that the
2342 // original arguments will have a padding slot that we can reuse
2343 // {slots_to_claim = extra_argument_count - 1}.
2344 {
2345 Register scratch = x11;
2346 Add(slots_to_claim, extra_argument_count, 1);
2347 And(scratch, actual_argument_count, 1);
2348 Sub(slots_to_claim, slots_to_claim, Operand(scratch, LSL, 1));
2349 }
2350
2351 Bind(&even_extra_count);
2352 Cbz(slots_to_claim, &skip_move);
2353
2354 Label stack_overflow;
2355 StackOverflowCheck(slots_to_claim, &stack_overflow);
2356 Claim(slots_to_claim);
2357
2358 // Move the arguments already in the stack including the receiver.
2359 {
2360 Register src = x6;
2361 Register dst = x7;
2362 SlotAddress(src, slots_to_claim);
2363 SlotAddress(dst, 0);
2364 CopyDoubleWords(dst, src, slots_to_copy);
2365 }
2366
2367 Bind(&skip_move);
2368 Register pointer_next_value = x5;
2369
2370 // Copy extra arguments as undefined values.
2371 {
2372 Label loop;
2373 Register undefined_value = x6;
2374 Register count = x7;
2375 LoadRoot(undefined_value, RootIndex::kUndefinedValue);
2376 SlotAddress(pointer_next_value, actual_argument_count);
2377 Mov(count, extra_argument_count);
2378 Bind(&loop);
2379 Str(undefined_value,
2380 MemOperand(pointer_next_value, kSystemPointerSize, PostIndex));
2381 Subs(count, count, 1);
2382 Cbnz(count, &loop);
2383 }
2384
2385 // Set padding if needed.
2386 {
2387 Label skip;
2388 Register total_args_slots = x4;
2389 Add(total_args_slots, actual_argument_count, extra_argument_count);
2390 Tbz(total_args_slots, 0, &skip);
2391 Str(padreg, MemOperand(pointer_next_value));
2392 Bind(&skip);
2393 }
2394 B(®ular_invoke);
2395
2396 bind(&stack_overflow);
2397 {
2398 FrameScope frame(
2399 this, has_frame() ? StackFrame::NO_FRAME_TYPE : StackFrame::INTERNAL);
2400 CallRuntime(Runtime::kThrowStackOverflow);
2401 Unreachable();
2402 }
2403
2404 Bind(®ular_invoke);
2405 }
2406
CallDebugOnFunctionCall(Register fun,Register new_target,Register expected_parameter_count,Register actual_parameter_count)2407 void MacroAssembler::CallDebugOnFunctionCall(Register fun, Register new_target,
2408 Register expected_parameter_count,
2409 Register actual_parameter_count) {
2410 ASM_CODE_COMMENT(this);
2411 // Load receiver to pass it later to DebugOnFunctionCall hook.
2412 Peek(x4, ReceiverOperand(actual_parameter_count));
2413 FrameScope frame(
2414 this, has_frame() ? StackFrame::NO_FRAME_TYPE : StackFrame::INTERNAL);
2415
2416 if (!new_target.is_valid()) new_target = padreg;
2417
2418 // Save values on stack.
2419 SmiTag(expected_parameter_count);
2420 SmiTag(actual_parameter_count);
2421 Push(expected_parameter_count, actual_parameter_count, new_target, fun);
2422 Push(fun, x4);
2423 CallRuntime(Runtime::kDebugOnFunctionCall);
2424
2425 // Restore values from stack.
2426 Pop(fun, new_target, actual_parameter_count, expected_parameter_count);
2427 SmiUntag(actual_parameter_count);
2428 SmiUntag(expected_parameter_count);
2429 }
2430
InvokeFunctionCode(Register function,Register new_target,Register expected_parameter_count,Register actual_parameter_count,InvokeType type)2431 void MacroAssembler::InvokeFunctionCode(Register function, Register new_target,
2432 Register expected_parameter_count,
2433 Register actual_parameter_count,
2434 InvokeType type) {
2435 ASM_CODE_COMMENT(this);
2436 // You can't call a function without a valid frame.
2437 DCHECK_IMPLIES(type == InvokeType::kCall, has_frame());
2438 DCHECK_EQ(function, x1);
2439 DCHECK_IMPLIES(new_target.is_valid(), new_target == x3);
2440
2441 // On function call, call into the debugger if necessary.
2442 Label debug_hook, continue_after_hook;
2443 {
2444 Mov(x4, ExternalReference::debug_hook_on_function_call_address(isolate()));
2445 Ldrsb(x4, MemOperand(x4));
2446 Cbnz(x4, &debug_hook);
2447 }
2448 bind(&continue_after_hook);
2449
2450 // Clear the new.target register if not given.
2451 if (!new_target.is_valid()) {
2452 LoadRoot(x3, RootIndex::kUndefinedValue);
2453 }
2454
2455 Label done;
2456 InvokePrologue(expected_parameter_count, actual_parameter_count, &done, type);
2457
2458 // If actual != expected, InvokePrologue will have handled the call through
2459 // the argument adaptor mechanism.
2460 // The called function expects the call kind in x5.
2461 // We call indirectly through the code field in the function to
2462 // allow recompilation to take effect without changing any of the
2463 // call sites.
2464 Register code = kJavaScriptCallCodeStartRegister;
2465 LoadTaggedPointerField(code,
2466 FieldMemOperand(function, JSFunction::kCodeOffset));
2467 switch (type) {
2468 case InvokeType::kCall:
2469 CallCodeTObject(code);
2470 break;
2471 case InvokeType::kJump:
2472 JumpCodeTObject(code);
2473 break;
2474 }
2475 B(&done);
2476
2477 // Deferred debug hook.
2478 bind(&debug_hook);
2479 CallDebugOnFunctionCall(function, new_target, expected_parameter_count,
2480 actual_parameter_count);
2481 B(&continue_after_hook);
2482
2483 // Continue here if InvokePrologue does handle the invocation due to
2484 // mismatched parameter counts.
2485 Bind(&done);
2486 }
2487
ReceiverOperand(Register arg_count)2488 Operand MacroAssembler::ReceiverOperand(Register arg_count) {
2489 return Operand(0);
2490 }
2491
InvokeFunctionWithNewTarget(Register function,Register new_target,Register actual_parameter_count,InvokeType type)2492 void MacroAssembler::InvokeFunctionWithNewTarget(
2493 Register function, Register new_target, Register actual_parameter_count,
2494 InvokeType type) {
2495 ASM_CODE_COMMENT(this);
2496 // You can't call a function without a valid frame.
2497 DCHECK(type == InvokeType::kJump || has_frame());
2498
2499 // Contract with called JS functions requires that function is passed in x1.
2500 // (See FullCodeGenerator::Generate().)
2501 DCHECK_EQ(function, x1);
2502
2503 Register expected_parameter_count = x2;
2504
2505 LoadTaggedPointerField(cp,
2506 FieldMemOperand(function, JSFunction::kContextOffset));
2507 // The number of arguments is stored as an int32_t, and -1 is a marker
2508 // (kDontAdaptArgumentsSentinel), so we need sign
2509 // extension to correctly handle it.
2510 LoadTaggedPointerField(
2511 expected_parameter_count,
2512 FieldMemOperand(function, JSFunction::kSharedFunctionInfoOffset));
2513 Ldrh(expected_parameter_count,
2514 FieldMemOperand(expected_parameter_count,
2515 SharedFunctionInfo::kFormalParameterCountOffset));
2516
2517 InvokeFunctionCode(function, new_target, expected_parameter_count,
2518 actual_parameter_count, type);
2519 }
2520
InvokeFunction(Register function,Register expected_parameter_count,Register actual_parameter_count,InvokeType type)2521 void MacroAssembler::InvokeFunction(Register function,
2522 Register expected_parameter_count,
2523 Register actual_parameter_count,
2524 InvokeType type) {
2525 ASM_CODE_COMMENT(this);
2526 // You can't call a function without a valid frame.
2527 DCHECK(type == InvokeType::kJump || has_frame());
2528
2529 // Contract with called JS functions requires that function is passed in x1.
2530 // (See FullCodeGenerator::Generate().)
2531 DCHECK_EQ(function, x1);
2532
2533 // Set up the context.
2534 LoadTaggedPointerField(cp,
2535 FieldMemOperand(function, JSFunction::kContextOffset));
2536
2537 InvokeFunctionCode(function, no_reg, expected_parameter_count,
2538 actual_parameter_count, type);
2539 }
2540
TryConvertDoubleToInt64(Register result,DoubleRegister double_input,Label * done)2541 void TurboAssembler::TryConvertDoubleToInt64(Register result,
2542 DoubleRegister double_input,
2543 Label* done) {
2544 ASM_CODE_COMMENT(this);
2545 // Try to convert with an FPU convert instruction. It's trivial to compute
2546 // the modulo operation on an integer register so we convert to a 64-bit
2547 // integer.
2548 //
2549 // Fcvtzs will saturate to INT64_MIN (0x800...00) or INT64_MAX (0x7FF...FF)
2550 // when the double is out of range. NaNs and infinities will be converted to 0
2551 // (as ECMA-262 requires).
2552 Fcvtzs(result.X(), double_input);
2553
2554 // The values INT64_MIN (0x800...00) or INT64_MAX (0x7FF...FF) are not
2555 // representable using a double, so if the result is one of those then we know
2556 // that saturation occurred, and we need to manually handle the conversion.
2557 //
2558 // It is easy to detect INT64_MIN and INT64_MAX because adding or subtracting
2559 // 1 will cause signed overflow.
2560 Cmp(result.X(), 1);
2561 Ccmp(result.X(), -1, VFlag, vc);
2562
2563 B(vc, done);
2564 }
2565
TruncateDoubleToI(Isolate * isolate,Zone * zone,Register result,DoubleRegister double_input,StubCallMode stub_mode,LinkRegisterStatus lr_status)2566 void TurboAssembler::TruncateDoubleToI(Isolate* isolate, Zone* zone,
2567 Register result,
2568 DoubleRegister double_input,
2569 StubCallMode stub_mode,
2570 LinkRegisterStatus lr_status) {
2571 ASM_CODE_COMMENT(this);
2572 if (CpuFeatures::IsSupported(JSCVT)) {
2573 Fjcvtzs(result.W(), double_input);
2574 return;
2575 }
2576
2577 Label done;
2578
2579 // Try to convert the double to an int64. If successful, the bottom 32 bits
2580 // contain our truncated int32 result.
2581 TryConvertDoubleToInt64(result, double_input, &done);
2582
2583 // If we fell through then inline version didn't succeed - call stub instead.
2584 if (lr_status == kLRHasNotBeenSaved) {
2585 Push<TurboAssembler::kSignLR>(lr, double_input);
2586 } else {
2587 Push<TurboAssembler::kDontStoreLR>(xzr, double_input);
2588 }
2589
2590 // DoubleToI preserves any registers it needs to clobber.
2591 #if V8_ENABLE_WEBASSEMBLY
2592 if (stub_mode == StubCallMode::kCallWasmRuntimeStub) {
2593 Call(wasm::WasmCode::kDoubleToI, RelocInfo::WASM_STUB_CALL);
2594 #else
2595 // For balance.
2596 if (false) {
2597 #endif // V8_ENABLE_WEBASSEMBLY
2598 } else if (options().inline_offheap_trampolines) {
2599 CallBuiltin(Builtin::kDoubleToI);
2600 } else {
2601 Call(BUILTIN_CODE(isolate, DoubleToI), RelocInfo::CODE_TARGET);
2602 }
2603 Ldr(result, MemOperand(sp, 0));
2604
2605 DCHECK_EQ(xzr.SizeInBytes(), double_input.SizeInBytes());
2606
2607 if (lr_status == kLRHasNotBeenSaved) {
2608 // Pop into xzr here to drop the double input on the stack:
2609 Pop<TurboAssembler::kAuthLR>(xzr, lr);
2610 } else {
2611 Drop(2);
2612 }
2613
2614 Bind(&done);
2615 // Keep our invariant that the upper 32 bits are zero.
2616 Uxtw(result.W(), result.W());
2617 }
2618
2619 void TurboAssembler::Prologue() {
2620 ASM_CODE_COMMENT(this);
2621 Push<TurboAssembler::kSignLR>(lr, fp);
2622 mov(fp, sp);
2623 STATIC_ASSERT(kExtraSlotClaimedByPrologue == 1);
2624 Push(cp, kJSFunctionRegister, kJavaScriptCallArgCountRegister, padreg);
2625 }
2626
2627 void TurboAssembler::EnterFrame(StackFrame::Type type) {
2628 UseScratchRegisterScope temps(this);
2629
2630 if (type == StackFrame::INTERNAL
2631 #if V8_ENABLE_WEBASSEMBLY
2632 || type == StackFrame::WASM_DEBUG_BREAK
2633 #endif // V8_ENABLE_WEBASSEMBLY
2634 ) {
2635 Register type_reg = temps.AcquireX();
2636 Mov(type_reg, StackFrame::TypeToMarker(type));
2637 Push<TurboAssembler::kSignLR>(lr, fp, type_reg, padreg);
2638 const int kFrameSize =
2639 TypedFrameConstants::kFixedFrameSizeFromFp + kSystemPointerSize;
2640 Add(fp, sp, kFrameSize);
2641 // sp[3] : lr
2642 // sp[2] : fp
2643 // sp[1] : type
2644 // sp[0] : for alignment
2645 #if V8_ENABLE_WEBASSEMBLY
2646 } else if (type == StackFrame::WASM ||
2647 type == StackFrame::WASM_COMPILE_LAZY ||
2648 type == StackFrame::WASM_EXIT) {
2649 Register type_reg = temps.AcquireX();
2650 Mov(type_reg, StackFrame::TypeToMarker(type));
2651 Push<TurboAssembler::kSignLR>(lr, fp);
2652 Mov(fp, sp);
2653 Push(type_reg, kWasmInstanceRegister);
2654 // sp[3] : lr
2655 // sp[2] : fp
2656 // sp[1] : type
2657 // sp[0] : wasm instance
2658 #endif // V8_ENABLE_WEBASSEMBLY
2659 } else if (type == StackFrame::CONSTRUCT) {
2660 Register type_reg = temps.AcquireX();
2661 Mov(type_reg, StackFrame::TypeToMarker(type));
2662
2663 // Users of this frame type push a context pointer after the type field,
2664 // so do it here to keep the stack pointer aligned.
2665 Push<TurboAssembler::kSignLR>(lr, fp, type_reg, cp);
2666
2667 // The context pointer isn't part of the fixed frame, so add an extra slot
2668 // to account for it.
2669 Add(fp, sp,
2670 TypedFrameConstants::kFixedFrameSizeFromFp + kSystemPointerSize);
2671 // sp[3] : lr
2672 // sp[2] : fp
2673 // sp[1] : type
2674 // sp[0] : cp
2675 } else {
2676 DCHECK(StackFrame::IsJavaScript(type));
2677 // Just push a minimal "machine frame", saving the frame pointer and return
2678 // address, without any markers.
2679 Push<TurboAssembler::kSignLR>(lr, fp);
2680 Mov(fp, sp);
2681 // sp[1] : lr
2682 // sp[0] : fp
2683 }
2684 }
2685
2686 void TurboAssembler::LeaveFrame(StackFrame::Type type) {
2687 ASM_CODE_COMMENT(this);
2688 // Drop the execution stack down to the frame pointer and restore
2689 // the caller frame pointer and return address.
2690 Mov(sp, fp);
2691 Pop<TurboAssembler::kAuthLR>(fp, lr);
2692 }
2693
2694 void MacroAssembler::ExitFramePreserveFPRegs() {
2695 ASM_CODE_COMMENT(this);
2696 DCHECK_EQ(kCallerSavedV.Count() % 2, 0);
2697 PushCPURegList(kCallerSavedV);
2698 }
2699
2700 void MacroAssembler::ExitFrameRestoreFPRegs() {
2701 // Read the registers from the stack without popping them. The stack pointer
2702 // will be reset as part of the unwinding process.
2703 ASM_CODE_COMMENT(this);
2704 CPURegList saved_fp_regs = kCallerSavedV;
2705 DCHECK_EQ(saved_fp_regs.Count() % 2, 0);
2706
2707 int offset = ExitFrameConstants::kLastExitFrameField;
2708 while (!saved_fp_regs.IsEmpty()) {
2709 const CPURegister& dst0 = saved_fp_regs.PopHighestIndex();
2710 const CPURegister& dst1 = saved_fp_regs.PopHighestIndex();
2711 offset -= 2 * kDRegSize;
2712 Ldp(dst1, dst0, MemOperand(fp, offset));
2713 }
2714 }
2715
2716 void MacroAssembler::EnterExitFrame(bool save_doubles, const Register& scratch,
2717 int extra_space,
2718 StackFrame::Type frame_type) {
2719 ASM_CODE_COMMENT(this);
2720 DCHECK(frame_type == StackFrame::EXIT ||
2721 frame_type == StackFrame::BUILTIN_EXIT);
2722
2723 // Set up the new stack frame.
2724 Push<TurboAssembler::kSignLR>(lr, fp);
2725 Mov(fp, sp);
2726 Mov(scratch, StackFrame::TypeToMarker(frame_type));
2727 Push(scratch, xzr);
2728 // fp[8]: CallerPC (lr)
2729 // fp -> fp[0]: CallerFP (old fp)
2730 // fp[-8]: STUB marker
2731 // sp -> fp[-16]: Space reserved for SPOffset.
2732 STATIC_ASSERT((2 * kSystemPointerSize) ==
2733 ExitFrameConstants::kCallerSPOffset);
2734 STATIC_ASSERT((1 * kSystemPointerSize) ==
2735 ExitFrameConstants::kCallerPCOffset);
2736 STATIC_ASSERT((0 * kSystemPointerSize) ==
2737 ExitFrameConstants::kCallerFPOffset);
2738 STATIC_ASSERT((-2 * kSystemPointerSize) == ExitFrameConstants::kSPOffset);
2739
2740 // Save the frame pointer and context pointer in the top frame.
2741 Mov(scratch,
2742 ExternalReference::Create(IsolateAddressId::kCEntryFPAddress, isolate()));
2743 Str(fp, MemOperand(scratch));
2744 Mov(scratch,
2745 ExternalReference::Create(IsolateAddressId::kContextAddress, isolate()));
2746 Str(cp, MemOperand(scratch));
2747
2748 STATIC_ASSERT((-2 * kSystemPointerSize) ==
2749 ExitFrameConstants::kLastExitFrameField);
2750 if (save_doubles) {
2751 ExitFramePreserveFPRegs();
2752 }
2753
2754 // Round the number of space we need to claim to a multiple of two.
2755 int slots_to_claim = RoundUp(extra_space + 1, 2);
2756
2757 // Reserve space for the return address and for user requested memory.
2758 // We do this before aligning to make sure that we end up correctly
2759 // aligned with the minimum of wasted space.
2760 Claim(slots_to_claim, kXRegSize);
2761 // fp[8]: CallerPC (lr)
2762 // fp -> fp[0]: CallerFP (old fp)
2763 // fp[-8]: STUB marker
2764 // fp[-16]: Space reserved for SPOffset.
2765 // fp[-16 - fp_size]: Saved doubles (if save_doubles is true).
2766 // sp[8]: Extra space reserved for caller (if extra_space != 0).
2767 // sp -> sp[0]: Space reserved for the return address.
2768
2769 // ExitFrame::GetStateForFramePointer expects to find the return address at
2770 // the memory address immediately below the pointer stored in SPOffset.
2771 // It is not safe to derive much else from SPOffset, because the size of the
2772 // padding can vary.
2773 Add(scratch, sp, kXRegSize);
2774 Str(scratch, MemOperand(fp, ExitFrameConstants::kSPOffset));
2775 }
2776
2777 // Leave the current exit frame.
2778 void MacroAssembler::LeaveExitFrame(bool restore_doubles,
2779 const Register& scratch,
2780 const Register& scratch2) {
2781 ASM_CODE_COMMENT(this);
2782 if (restore_doubles) {
2783 ExitFrameRestoreFPRegs();
2784 }
2785
2786 // Restore the context pointer from the top frame.
2787 Mov(scratch,
2788 ExternalReference::Create(IsolateAddressId::kContextAddress, isolate()));
2789 Ldr(cp, MemOperand(scratch));
2790
2791 if (FLAG_debug_code) {
2792 // Also emit debug code to clear the cp in the top frame.
2793 Mov(scratch2, Operand(Context::kInvalidContext));
2794 Mov(scratch, ExternalReference::Create(IsolateAddressId::kContextAddress,
2795 isolate()));
2796 Str(scratch2, MemOperand(scratch));
2797 }
2798 // Clear the frame pointer from the top frame.
2799 Mov(scratch,
2800 ExternalReference::Create(IsolateAddressId::kCEntryFPAddress, isolate()));
2801 Str(xzr, MemOperand(scratch));
2802
2803 // Pop the exit frame.
2804 // fp[8]: CallerPC (lr)
2805 // fp -> fp[0]: CallerFP (old fp)
2806 // fp[...]: The rest of the frame.
2807 Mov(sp, fp);
2808 Pop<TurboAssembler::kAuthLR>(fp, lr);
2809 }
2810
2811 void MacroAssembler::LoadGlobalProxy(Register dst) {
2812 ASM_CODE_COMMENT(this);
2813 LoadNativeContextSlot(dst, Context::GLOBAL_PROXY_INDEX);
2814 }
2815
2816 void MacroAssembler::LoadWeakValue(Register out, Register in,
2817 Label* target_if_cleared) {
2818 ASM_CODE_COMMENT(this);
2819 CompareAndBranch(in.W(), Operand(kClearedWeakHeapObjectLower32), eq,
2820 target_if_cleared);
2821
2822 and_(out, in, Operand(~kWeakHeapObjectMask));
2823 }
2824
2825 void MacroAssembler::EmitIncrementCounter(StatsCounter* counter, int value,
2826 Register scratch1,
2827 Register scratch2) {
2828 ASM_CODE_COMMENT(this);
2829 DCHECK_NE(value, 0);
2830 if (FLAG_native_code_counters && counter->Enabled()) {
2831 // This operation has to be exactly 32-bit wide in case the external
2832 // reference table redirects the counter to a uint32_t dummy_stats_counter_
2833 // field.
2834 Mov(scratch2, ExternalReference::Create(counter));
2835 Ldr(scratch1.W(), MemOperand(scratch2));
2836 Add(scratch1.W(), scratch1.W(), value);
2837 Str(scratch1.W(), MemOperand(scratch2));
2838 }
2839 }
2840
2841 void MacroAssembler::JumpIfObjectType(Register object, Register map,
2842 Register type_reg, InstanceType type,
2843 Label* if_cond_pass, Condition cond) {
2844 ASM_CODE_COMMENT(this);
2845 CompareObjectType(object, map, type_reg, type);
2846 B(cond, if_cond_pass);
2847 }
2848
2849 // Sets condition flags based on comparison, and returns type in type_reg.
2850 void MacroAssembler::CompareObjectType(Register object, Register map,
2851 Register type_reg, InstanceType type) {
2852 ASM_CODE_COMMENT(this);
2853 LoadMap(map, object);
2854 CompareInstanceType(map, type_reg, type);
2855 }
2856
2857 void TurboAssembler::LoadMap(Register dst, Register object) {
2858 ASM_CODE_COMMENT(this);
2859 LoadTaggedPointerField(dst, FieldMemOperand(object, HeapObject::kMapOffset));
2860 }
2861
2862 // Sets condition flags based on comparison, and returns type in type_reg.
2863 void MacroAssembler::CompareInstanceType(Register map, Register type_reg,
2864 InstanceType type) {
2865 ASM_CODE_COMMENT(this);
2866 Ldrh(type_reg, FieldMemOperand(map, Map::kInstanceTypeOffset));
2867 Cmp(type_reg, type);
2868 }
2869
2870 // Sets condition flags based on comparison, and returns type in type_reg.
2871 void MacroAssembler::CompareInstanceTypeRange(Register map, Register type_reg,
2872 InstanceType lower_limit,
2873 InstanceType higher_limit) {
2874 ASM_CODE_COMMENT(this);
2875 DCHECK_LT(lower_limit, higher_limit);
2876 UseScratchRegisterScope temps(this);
2877 Register scratch = temps.AcquireX();
2878 Ldrh(type_reg, FieldMemOperand(map, Map::kInstanceTypeOffset));
2879 Sub(scratch, type_reg, Operand(lower_limit));
2880 Cmp(scratch, Operand(higher_limit - lower_limit));
2881 }
2882
2883 void MacroAssembler::LoadElementsKindFromMap(Register result, Register map) {
2884 ASM_CODE_COMMENT(this);
2885 // Load the map's "bit field 2".
2886 Ldrb(result, FieldMemOperand(map, Map::kBitField2Offset));
2887 // Retrieve elements_kind from bit field 2.
2888 DecodeField<Map::Bits2::ElementsKindBits>(result);
2889 }
2890
2891 void MacroAssembler::CompareRoot(const Register& obj, RootIndex index) {
2892 ASM_CODE_COMMENT(this);
2893 UseScratchRegisterScope temps(this);
2894 Register temp = temps.AcquireX();
2895 DCHECK(!AreAliased(obj, temp));
2896 LoadRoot(temp, index);
2897 CmpTagged(obj, temp);
2898 }
2899
2900 void MacroAssembler::JumpIfRoot(const Register& obj, RootIndex index,
2901 Label* if_equal) {
2902 CompareRoot(obj, index);
2903 B(eq, if_equal);
2904 }
2905
2906 void MacroAssembler::JumpIfNotRoot(const Register& obj, RootIndex index,
2907 Label* if_not_equal) {
2908 CompareRoot(obj, index);
2909 B(ne, if_not_equal);
2910 }
2911
2912 void MacroAssembler::JumpIfIsInRange(const Register& value,
2913 unsigned lower_limit,
2914 unsigned higher_limit,
2915 Label* on_in_range) {
2916 ASM_CODE_COMMENT(this);
2917 if (lower_limit != 0) {
2918 UseScratchRegisterScope temps(this);
2919 Register scratch = temps.AcquireW();
2920 Sub(scratch, value, Operand(lower_limit));
2921 CompareAndBranch(scratch, Operand(higher_limit - lower_limit), ls,
2922 on_in_range);
2923 } else {
2924 CompareAndBranch(value, Operand(higher_limit - lower_limit), ls,
2925 on_in_range);
2926 }
2927 }
2928
2929 void TurboAssembler::LoadTaggedPointerField(const Register& destination,
2930 const MemOperand& field_operand) {
2931 if (COMPRESS_POINTERS_BOOL) {
2932 DecompressTaggedPointer(destination, field_operand);
2933 } else {
2934 Ldr(destination, field_operand);
2935 }
2936 }
2937
2938 void TurboAssembler::LoadAnyTaggedField(const Register& destination,
2939 const MemOperand& field_operand) {
2940 if (COMPRESS_POINTERS_BOOL) {
2941 DecompressAnyTagged(destination, field_operand);
2942 } else {
2943 Ldr(destination, field_operand);
2944 }
2945 }
2946
2947 void TurboAssembler::LoadTaggedSignedField(const Register& destination,
2948 const MemOperand& field_operand) {
2949 if (COMPRESS_POINTERS_BOOL) {
2950 DecompressTaggedSigned(destination, field_operand);
2951 } else {
2952 Ldr(destination, field_operand);
2953 }
2954 }
2955
2956 void TurboAssembler::SmiUntagField(Register dst, const MemOperand& src) {
2957 SmiUntag(dst, src);
2958 }
2959
2960 void TurboAssembler::StoreTaggedField(const Register& value,
2961 const MemOperand& dst_field_operand) {
2962 if (COMPRESS_POINTERS_BOOL) {
2963 Str(value.W(), dst_field_operand);
2964 } else {
2965 Str(value, dst_field_operand);
2966 }
2967 }
2968
2969 void TurboAssembler::AtomicStoreTaggedField(const Register& value,
2970 const Register& dst_base,
2971 const Register& dst_index,
2972 const Register& temp) {
2973 Add(temp, dst_base, dst_index);
2974 if (COMPRESS_POINTERS_BOOL) {
2975 Stlr(value.W(), temp);
2976 } else {
2977 Stlr(value, temp);
2978 }
2979 }
2980
2981 void TurboAssembler::DecompressTaggedSigned(const Register& destination,
2982 const MemOperand& field_operand) {
2983 ASM_CODE_COMMENT(this);
2984 Ldr(destination.W(), field_operand);
2985 if (FLAG_debug_code) {
2986 // Corrupt the top 32 bits. Made up of 16 fixed bits and 16 pc offset bits.
2987 Add(destination, destination,
2988 ((kDebugZapValue << 16) | (pc_offset() & 0xffff)) << 32);
2989 }
2990 }
2991
2992 void TurboAssembler::DecompressTaggedPointer(const Register& destination,
2993 const MemOperand& field_operand) {
2994 ASM_CODE_COMMENT(this);
2995 Ldr(destination.W(), field_operand);
2996 Add(destination, kPtrComprCageBaseRegister, destination);
2997 }
2998
2999 void TurboAssembler::DecompressTaggedPointer(const Register& destination,
3000 const Register& source) {
3001 ASM_CODE_COMMENT(this);
3002 Add(destination, kPtrComprCageBaseRegister, Operand(source, UXTW));
3003 }
3004
3005 void TurboAssembler::DecompressAnyTagged(const Register& destination,
3006 const MemOperand& field_operand) {
3007 ASM_CODE_COMMENT(this);
3008 Ldr(destination.W(), field_operand);
3009 Add(destination, kPtrComprCageBaseRegister, destination);
3010 }
3011
3012 void TurboAssembler::AtomicDecompressTaggedSigned(const Register& destination,
3013 const Register& base,
3014 const Register& index,
3015 const Register& temp) {
3016 ASM_CODE_COMMENT(this);
3017 Add(temp, base, index);
3018 Ldar(destination.W(), temp);
3019 if (FLAG_debug_code) {
3020 // Corrupt the top 32 bits. Made up of 16 fixed bits and 16 pc offset bits.
3021 Add(destination, destination,
3022 ((kDebugZapValue << 16) | (pc_offset() & 0xffff)) << 32);
3023 }
3024 }
3025
3026 void TurboAssembler::AtomicDecompressTaggedPointer(const Register& destination,
3027 const Register& base,
3028 const Register& index,
3029 const Register& temp) {
3030 ASM_CODE_COMMENT(this);
3031 Add(temp, base, index);
3032 Ldar(destination.W(), temp);
3033 Add(destination, kPtrComprCageBaseRegister, destination);
3034 }
3035
3036 void TurboAssembler::AtomicDecompressAnyTagged(const Register& destination,
3037 const Register& base,
3038 const Register& index,
3039 const Register& temp) {
3040 ASM_CODE_COMMENT(this);
3041 Add(temp, base, index);
3042 Ldar(destination.W(), temp);
3043 Add(destination, kPtrComprCageBaseRegister, destination);
3044 }
3045
3046 void TurboAssembler::CheckPageFlag(const Register& object, int mask,
3047 Condition cc, Label* condition_met) {
3048 ASM_CODE_COMMENT(this);
3049 UseScratchRegisterScope temps(this);
3050 Register scratch = temps.AcquireX();
3051 And(scratch, object, ~kPageAlignmentMask);
3052 Ldr(scratch, MemOperand(scratch, BasicMemoryChunk::kFlagsOffset));
3053 if (cc == eq) {
3054 TestAndBranchIfAnySet(scratch, mask, condition_met);
3055 } else {
3056 DCHECK_EQ(cc, ne);
3057 TestAndBranchIfAllClear(scratch, mask, condition_met);
3058 }
3059 }
3060
3061 void MacroAssembler::RecordWriteField(Register object, int offset,
3062 Register value,
3063 LinkRegisterStatus lr_status,
3064 SaveFPRegsMode save_fp,
3065 RememberedSetAction remembered_set_action,
3066 SmiCheck smi_check) {
3067 ASM_CODE_COMMENT(this);
3068 DCHECK(!AreAliased(object, value));
3069 // First, check if a write barrier is even needed. The tests below
3070 // catch stores of Smis.
3071 Label done;
3072
3073 // Skip the barrier if writing a smi.
3074 if (smi_check == SmiCheck::kInline) {
3075 JumpIfSmi(value, &done);
3076 }
3077
3078 // Although the object register is tagged, the offset is relative to the start
3079 // of the object, so offset must be a multiple of kTaggedSize.
3080 DCHECK(IsAligned(offset, kTaggedSize));
3081
3082 if (FLAG_debug_code) {
3083 ASM_CODE_COMMENT_STRING(this, "Verify slot_address");
3084 Label ok;
3085 UseScratchRegisterScope temps(this);
3086 Register scratch = temps.AcquireX();
3087 DCHECK(!AreAliased(object, value, scratch));
3088 Add(scratch, object, offset - kHeapObjectTag);
3089 Tst(scratch, kTaggedSize - 1);
3090 B(eq, &ok);
3091 Abort(AbortReason::kUnalignedCellInWriteBarrier);
3092 Bind(&ok);
3093 }
3094
3095 RecordWrite(object, Operand(offset - kHeapObjectTag), value, lr_status,
3096 save_fp, remembered_set_action, SmiCheck::kOmit);
3097
3098 Bind(&done);
3099 }
3100
3101 void TurboAssembler::EncodeSandboxedPointer(const Register& value) {
3102 ASM_CODE_COMMENT(this);
3103 #ifdef V8_SANDBOXED_POINTERS
3104 Sub(value, value, kPtrComprCageBaseRegister);
3105 Mov(value, Operand(value, LSL, kSandboxedPointerShift));
3106 #else
3107 UNREACHABLE();
3108 #endif
3109 }
3110
3111 void TurboAssembler::DecodeSandboxedPointer(const Register& value) {
3112 ASM_CODE_COMMENT(this);
3113 #ifdef V8_SANDBOXED_POINTERS
3114 Add(value, kPtrComprCageBaseRegister,
3115 Operand(value, LSR, kSandboxedPointerShift));
3116 #else
3117 UNREACHABLE();
3118 #endif
3119 }
3120
3121 void TurboAssembler::LoadSandboxedPointerField(
3122 const Register& destination, const MemOperand& field_operand) {
3123 ASM_CODE_COMMENT(this);
3124 Ldr(destination, field_operand);
3125 DecodeSandboxedPointer(destination);
3126 }
3127
3128 void TurboAssembler::StoreSandboxedPointerField(
3129 const Register& value, const MemOperand& dst_field_operand) {
3130 ASM_CODE_COMMENT(this);
3131 UseScratchRegisterScope temps(this);
3132 Register scratch = temps.AcquireX();
3133 Mov(scratch, value);
3134 EncodeSandboxedPointer(scratch);
3135 Str(scratch, dst_field_operand);
3136 }
3137
3138 void TurboAssembler::LoadExternalPointerField(Register destination,
3139 MemOperand field_operand,
3140 ExternalPointerTag tag,
3141 Register isolate_root) {
3142 DCHECK(!AreAliased(destination, isolate_root));
3143 ASM_CODE_COMMENT(this);
3144 #ifdef V8_SANDBOXED_EXTERNAL_POINTERS
3145 DCHECK_NE(kExternalPointerNullTag, tag);
3146 UseScratchRegisterScope temps(this);
3147 Register external_table = temps.AcquireX();
3148 if (isolate_root == no_reg) {
3149 DCHECK(root_array_available_);
3150 isolate_root = kRootRegister;
3151 }
3152 Ldr(external_table,
3153 MemOperand(isolate_root,
3154 IsolateData::external_pointer_table_offset() +
3155 Internals::kExternalPointerTableBufferOffset));
3156 Ldr(destination.W(), field_operand);
3157 // MemOperand doesn't support LSR currently (only LSL), so here we do the
3158 // offset computation separately first.
3159 STATIC_ASSERT(kExternalPointerIndexShift > kSystemPointerSizeLog2);
3160 int shift_amount = kExternalPointerIndexShift - kSystemPointerSizeLog2;
3161 Mov(destination, Operand(destination, LSR, shift_amount));
3162 Ldr(destination, MemOperand(external_table, destination));
3163 And(destination, destination, Immediate(~tag));
3164 #else
3165 Ldr(destination, field_operand);
3166 #endif // V8_SANDBOXED_EXTERNAL_POINTERS
3167 }
3168
3169 void TurboAssembler::MaybeSaveRegisters(RegList registers) {
3170 if (registers.is_empty()) return;
3171 ASM_CODE_COMMENT(this);
3172 CPURegList regs(kXRegSizeInBits, registers);
3173 // If we were saving LR, we might need to sign it.
3174 DCHECK(!regs.IncludesAliasOf(lr));
3175 regs.Align();
3176 PushCPURegList(regs);
3177 }
3178
3179 void TurboAssembler::MaybeRestoreRegisters(RegList registers) {
3180 if (registers.is_empty()) return;
3181 ASM_CODE_COMMENT(this);
3182 CPURegList regs(kXRegSizeInBits, registers);
3183 // If we were saving LR, we might need to sign it.
3184 DCHECK(!regs.IncludesAliasOf(lr));
3185 regs.Align();
3186 PopCPURegList(regs);
3187 }
3188
3189 void TurboAssembler::CallEphemeronKeyBarrier(Register object, Operand offset,
3190 SaveFPRegsMode fp_mode) {
3191 ASM_CODE_COMMENT(this);
3192 RegList registers = WriteBarrierDescriptor::ComputeSavedRegisters(object);
3193 MaybeSaveRegisters(registers);
3194
3195 MoveObjectAndSlot(WriteBarrierDescriptor::ObjectRegister(),
3196 WriteBarrierDescriptor::SlotAddressRegister(), object,
3197 offset);
3198
3199 Call(isolate()->builtins()->code_handle(
3200 Builtins::GetEphemeronKeyBarrierStub(fp_mode)),
3201 RelocInfo::CODE_TARGET);
3202 MaybeRestoreRegisters(registers);
3203 }
3204
3205 void TurboAssembler::CallRecordWriteStubSaveRegisters(
3206 Register object, Operand offset, RememberedSetAction remembered_set_action,
3207 SaveFPRegsMode fp_mode, StubCallMode mode) {
3208 ASM_CODE_COMMENT(this);
3209 RegList registers = WriteBarrierDescriptor::ComputeSavedRegisters(object);
3210 MaybeSaveRegisters(registers);
3211
3212 Register object_parameter = WriteBarrierDescriptor::ObjectRegister();
3213 Register slot_address_parameter =
3214 WriteBarrierDescriptor::SlotAddressRegister();
3215 MoveObjectAndSlot(object_parameter, slot_address_parameter, object, offset);
3216
3217 CallRecordWriteStub(object_parameter, slot_address_parameter,
3218 remembered_set_action, fp_mode, mode);
3219
3220 MaybeRestoreRegisters(registers);
3221 }
3222
3223 void TurboAssembler::CallRecordWriteStub(
3224 Register object, Register slot_address,
3225 RememberedSetAction remembered_set_action, SaveFPRegsMode fp_mode,
3226 StubCallMode mode) {
3227 ASM_CODE_COMMENT(this);
3228 DCHECK_EQ(WriteBarrierDescriptor::ObjectRegister(), object);
3229 DCHECK_EQ(WriteBarrierDescriptor::SlotAddressRegister(), slot_address);
3230 #if V8_ENABLE_WEBASSEMBLY
3231 if (mode == StubCallMode::kCallWasmRuntimeStub) {
3232 auto wasm_target =
3233 wasm::WasmCode::GetRecordWriteStub(remembered_set_action, fp_mode);
3234 Call(wasm_target, RelocInfo::WASM_STUB_CALL);
3235 #else
3236 if (false) {
3237 #endif
3238 } else {
3239 Builtin builtin =
3240 Builtins::GetRecordWriteStub(remembered_set_action, fp_mode);
3241 if (options().inline_offheap_trampolines) {
3242 CallBuiltin(builtin);
3243 } else {
3244 Handle<CodeT> code_target = isolate()->builtins()->code_handle(builtin);
3245 Call(code_target, RelocInfo::CODE_TARGET);
3246 }
3247 }
3248 }
3249
3250 void TurboAssembler::MoveObjectAndSlot(Register dst_object, Register dst_slot,
3251 Register object, Operand offset) {
3252 ASM_CODE_COMMENT(this);
3253 DCHECK_NE(dst_object, dst_slot);
3254 // If `offset` is a register, it cannot overlap with `object`.
3255 DCHECK_IMPLIES(!offset.IsImmediate(), offset.reg() != object);
3256
3257 // If the slot register does not overlap with the object register, we can
3258 // overwrite it.
3259 if (dst_slot != object) {
3260 Add(dst_slot, object, offset);
3261 Mov(dst_object, object);
3262 return;
3263 }
3264
3265 DCHECK_EQ(dst_slot, object);
3266
3267 // If the destination object register does not overlap with the offset
3268 // register, we can overwrite it.
3269 if (offset.IsImmediate() || (offset.reg() != dst_object)) {
3270 Mov(dst_object, dst_slot);
3271 Add(dst_slot, dst_slot, offset);
3272 return;
3273 }
3274
3275 DCHECK_EQ(dst_object, offset.reg());
3276
3277 // We only have `dst_slot` and `dst_object` left as distinct registers so we
3278 // have to swap them. We write this as a add+sub sequence to avoid using a
3279 // scratch register.
3280 Add(dst_slot, dst_slot, dst_object);
3281 Sub(dst_object, dst_slot, dst_object);
3282 }
3283
3284 // If lr_status is kLRHasBeenSaved, lr will be clobbered.
3285 //
3286 // The register 'object' contains a heap object pointer. The heap object tag is
3287 // shifted away.
3288 void MacroAssembler::RecordWrite(Register object, Operand offset,
3289 Register value, LinkRegisterStatus lr_status,
3290 SaveFPRegsMode fp_mode,
3291 RememberedSetAction remembered_set_action,
3292 SmiCheck smi_check) {
3293 ASM_CODE_COMMENT(this);
3294 ASM_LOCATION_IN_ASSEMBLER("MacroAssembler::RecordWrite");
3295 DCHECK(!AreAliased(object, value));
3296
3297 if (FLAG_debug_code) {
3298 ASM_CODE_COMMENT_STRING(this, "Verify slot_address");
3299 UseScratchRegisterScope temps(this);
3300 Register temp = temps.AcquireX();
3301 DCHECK(!AreAliased(object, value, temp));
3302 Add(temp, object, offset);
3303 LoadTaggedPointerField(temp, MemOperand(temp));
3304 Cmp(temp, value);
3305 Check(eq, AbortReason::kWrongAddressOrValuePassedToRecordWrite);
3306 }
3307
3308 if ((remembered_set_action == RememberedSetAction::kOmit &&
3309 !FLAG_incremental_marking) ||
3310 FLAG_disable_write_barriers) {
3311 return;
3312 }
3313
3314 // First, check if a write barrier is even needed. The tests below
3315 // catch stores of smis and stores into the young generation.
3316 Label done;
3317
3318 if (smi_check == SmiCheck::kInline) {
3319 DCHECK_EQ(0, kSmiTag);
3320 JumpIfSmi(value, &done);
3321 }
3322 CheckPageFlag(value, MemoryChunk::kPointersToHereAreInterestingMask, ne,
3323 &done);
3324
3325 CheckPageFlag(object, MemoryChunk::kPointersFromHereAreInterestingMask, ne,
3326 &done);
3327
3328 // Record the actual write.
3329 if (lr_status == kLRHasNotBeenSaved) {
3330 Push<TurboAssembler::kSignLR>(padreg, lr);
3331 }
3332 Register slot_address = WriteBarrierDescriptor::SlotAddressRegister();
3333 DCHECK(!AreAliased(object, slot_address, value));
3334 // TODO(cbruni): Turn offset into int.
3335 DCHECK(offset.IsImmediate());
3336 Add(slot_address, object, offset);
3337 CallRecordWriteStub(object, slot_address, remembered_set_action, fp_mode);
3338 if (lr_status == kLRHasNotBeenSaved) {
3339 Pop<TurboAssembler::kAuthLR>(lr, padreg);
3340 }
3341 if (FLAG_debug_code) Mov(slot_address, Operand(kZapValue));
3342
3343 Bind(&done);
3344 }
3345
3346 void TurboAssembler::Assert(Condition cond, AbortReason reason) {
3347 if (FLAG_debug_code) {
3348 Check(cond, reason);
3349 }
3350 }
3351
3352 void TurboAssembler::AssertUnreachable(AbortReason reason) {
3353 if (FLAG_debug_code) Abort(reason);
3354 }
3355
3356 void TurboAssembler::Check(Condition cond, AbortReason reason) {
3357 Label ok;
3358 B(cond, &ok);
3359 Abort(reason);
3360 // Will not return here.
3361 Bind(&ok);
3362 }
3363
3364 void TurboAssembler::Trap() { Brk(0); }
3365 void TurboAssembler::DebugBreak() { Debug("DebugBreak", 0, BREAK); }
3366
3367 void TurboAssembler::Abort(AbortReason reason) {
3368 ASM_CODE_COMMENT(this);
3369 if (FLAG_code_comments) {
3370 RecordComment("Abort message: ");
3371 RecordComment(GetAbortReason(reason));
3372 }
3373
3374 // Avoid emitting call to builtin if requested.
3375 if (trap_on_abort()) {
3376 Brk(0);
3377 return;
3378 }
3379
3380 // We need some scratch registers for the MacroAssembler, so make sure we have
3381 // some. This is safe here because Abort never returns.
3382 uint64_t old_tmp_list = TmpList()->bits();
3383 TmpList()->Combine(MacroAssembler::DefaultTmpList());
3384
3385 if (should_abort_hard()) {
3386 // We don't care if we constructed a frame. Just pretend we did.
3387 FrameScope assume_frame(this, StackFrame::NO_FRAME_TYPE);
3388 Mov(w0, static_cast<int>(reason));
3389 Call(ExternalReference::abort_with_reason());
3390 return;
3391 }
3392
3393 // Avoid infinite recursion; Push contains some assertions that use Abort.
3394 HardAbortScope hard_aborts(this);
3395
3396 Mov(x1, Smi::FromInt(static_cast<int>(reason)));
3397
3398 if (!has_frame_) {
3399 // We don't actually want to generate a pile of code for this, so just
3400 // claim there is a stack frame, without generating one.
3401 FrameScope scope(this, StackFrame::NO_FRAME_TYPE);
3402 Call(BUILTIN_CODE(isolate(), Abort), RelocInfo::CODE_TARGET);
3403 } else {
3404 Call(BUILTIN_CODE(isolate(), Abort), RelocInfo::CODE_TARGET);
3405 }
3406
3407 TmpList()->set_bits(old_tmp_list);
3408 }
3409
3410 void MacroAssembler::LoadNativeContextSlot(Register dst, int index) {
3411 LoadMap(dst, cp);
3412 LoadTaggedPointerField(
3413 dst, FieldMemOperand(
3414 dst, Map::kConstructorOrBackPointerOrNativeContextOffset));
3415 LoadTaggedPointerField(dst, MemOperand(dst, Context::SlotOffset(index)));
3416 }
3417
3418 // This is the main Printf implementation. All other Printf variants call
3419 // PrintfNoPreserve after setting up one or more PreserveRegisterScopes.
3420 void TurboAssembler::PrintfNoPreserve(const char* format,
3421 const CPURegister& arg0,
3422 const CPURegister& arg1,
3423 const CPURegister& arg2,
3424 const CPURegister& arg3) {
3425 ASM_CODE_COMMENT(this);
3426 // We cannot handle a caller-saved stack pointer. It doesn't make much sense
3427 // in most cases anyway, so this restriction shouldn't be too serious.
3428 DCHECK(!kCallerSaved.IncludesAliasOf(sp));
3429
3430 // The provided arguments, and their proper procedure-call standard registers.
3431 CPURegister args[kPrintfMaxArgCount] = {arg0, arg1, arg2, arg3};
3432 CPURegister pcs[kPrintfMaxArgCount] = {NoReg, NoReg, NoReg, NoReg};
3433
3434 int arg_count = kPrintfMaxArgCount;
3435
3436 // The PCS varargs registers for printf. Note that x0 is used for the printf
3437 // format string.
3438 static const CPURegList kPCSVarargs =
3439 CPURegList(CPURegister::kRegister, kXRegSizeInBits, 1, arg_count);
3440 static const CPURegList kPCSVarargsFP =
3441 CPURegList(CPURegister::kVRegister, kDRegSizeInBits, 0, arg_count - 1);
3442
3443 // We can use caller-saved registers as scratch values, except for the
3444 // arguments and the PCS registers where they might need to go.
3445 CPURegList tmp_list = kCallerSaved;
3446 tmp_list.Remove(x0); // Used to pass the format string.
3447 tmp_list.Remove(kPCSVarargs);
3448 tmp_list.Remove(arg0, arg1, arg2, arg3);
3449
3450 CPURegList fp_tmp_list = kCallerSavedV;
3451 fp_tmp_list.Remove(kPCSVarargsFP);
3452 fp_tmp_list.Remove(arg0, arg1, arg2, arg3);
3453
3454 // Override the TurboAssembler's scratch register list. The lists will be
3455 // reset automatically at the end of the UseScratchRegisterScope.
3456 UseScratchRegisterScope temps(this);
3457 TmpList()->set_bits(tmp_list.bits());
3458 FPTmpList()->set_bits(fp_tmp_list.bits());
3459
3460 // Copies of the printf vararg registers that we can pop from.
3461 CPURegList pcs_varargs = kPCSVarargs;
3462 #ifndef V8_OS_WIN
3463 CPURegList pcs_varargs_fp = kPCSVarargsFP;
3464 #endif
3465
3466 // Place the arguments. There are lots of clever tricks and optimizations we
3467 // could use here, but Printf is a debug tool so instead we just try to keep
3468 // it simple: Move each input that isn't already in the right place to a
3469 // scratch register, then move everything back.
3470 for (unsigned i = 0; i < kPrintfMaxArgCount; i++) {
3471 // Work out the proper PCS register for this argument.
3472 if (args[i].IsRegister()) {
3473 pcs[i] = pcs_varargs.PopLowestIndex().X();
3474 // We might only need a W register here. We need to know the size of the
3475 // argument so we can properly encode it for the simulator call.
3476 if (args[i].Is32Bits()) pcs[i] = pcs[i].W();
3477 } else if (args[i].IsVRegister()) {
3478 // In C, floats are always cast to doubles for varargs calls.
3479 #ifdef V8_OS_WIN
3480 // In case of variadic functions SIMD and Floating-point registers
3481 // aren't used. The general x0-x7 should be used instead.
3482 // https://docs.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions
3483 pcs[i] = pcs_varargs.PopLowestIndex().X();
3484 #else
3485 pcs[i] = pcs_varargs_fp.PopLowestIndex().D();
3486 #endif
3487 } else {
3488 DCHECK(args[i].IsNone());
3489 arg_count = i;
3490 break;
3491 }
3492
3493 // If the argument is already in the right place, leave it where it is.
3494 if (args[i].Aliases(pcs[i])) continue;
3495
3496 // Otherwise, if the argument is in a PCS argument register, allocate an
3497 // appropriate scratch register and then move it out of the way.
3498 if (kPCSVarargs.IncludesAliasOf(args[i]) ||
3499 kPCSVarargsFP.IncludesAliasOf(args[i])) {
3500 if (args[i].IsRegister()) {
3501 Register old_arg = args[i].Reg();
3502 Register new_arg = temps.AcquireSameSizeAs(old_arg);
3503 Mov(new_arg, old_arg);
3504 args[i] = new_arg;
3505 } else {
3506 VRegister old_arg = args[i].VReg();
3507 VRegister new_arg = temps.AcquireSameSizeAs(old_arg);
3508 Fmov(new_arg, old_arg);
3509 args[i] = new_arg;
3510 }
3511 }
3512 }
3513
3514 // Do a second pass to move values into their final positions and perform any
3515 // conversions that may be required.
3516 for (int i = 0; i < arg_count; i++) {
3517 #ifdef V8_OS_WIN
3518 if (args[i].IsVRegister()) {
3519 if (pcs[i].SizeInBytes() != args[i].SizeInBytes()) {
3520 // If the argument is half- or single-precision
3521 // converts to double-precision before that is
3522 // moved into the one of X scratch register.
3523 VRegister temp0 = temps.AcquireD();
3524 Fcvt(temp0.VReg(), args[i].VReg());
3525 Fmov(pcs[i].Reg(), temp0);
3526 } else {
3527 Fmov(pcs[i].Reg(), args[i].VReg());
3528 }
3529 } else {
3530 Mov(pcs[i].Reg(), args[i].Reg(), kDiscardForSameWReg);
3531 }
3532 #else
3533 DCHECK(pcs[i].type() == args[i].type());
3534 if (pcs[i].IsRegister()) {
3535 Mov(pcs[i].Reg(), args[i].Reg(), kDiscardForSameWReg);
3536 } else {
3537 DCHECK(pcs[i].IsVRegister());
3538 if (pcs[i].SizeInBytes() == args[i].SizeInBytes()) {
3539 Fmov(pcs[i].VReg(), args[i].VReg());
3540 } else {
3541 Fcvt(pcs[i].VReg(), args[i].VReg());
3542 }
3543 }
3544 #endif
3545 }
3546
3547 // Load the format string into x0, as per the procedure-call standard.
3548 //
3549 // To make the code as portable as possible, the format string is encoded
3550 // directly in the instruction stream. It might be cleaner to encode it in a
3551 // literal pool, but since Printf is usually used for debugging, it is
3552 // beneficial for it to be minimally dependent on other features.
3553 Label format_address;
3554 Adr(x0, &format_address);
3555
3556 // Emit the format string directly in the instruction stream.
3557 {
3558 BlockPoolsScope scope(this);
3559 Label after_data;
3560 B(&after_data);
3561 Bind(&format_address);
3562 EmitStringData(format);
3563 Unreachable();
3564 Bind(&after_data);
3565 }
3566
3567 CallPrintf(arg_count, pcs);
3568 }
3569
3570 void TurboAssembler::CallPrintf(int arg_count, const CPURegister* args) {
3571 ASM_CODE_COMMENT(this);
3572 // A call to printf needs special handling for the simulator, since the system
3573 // printf function will use a different instruction set and the procedure-call
3574 // standard will not be compatible.
3575 if (options().enable_simulator_code) {
3576 InstructionAccurateScope scope(this, kPrintfLength / kInstrSize);
3577 hlt(kImmExceptionIsPrintf);
3578 dc32(arg_count); // kPrintfArgCountOffset
3579
3580 // Determine the argument pattern.
3581 uint32_t arg_pattern_list = 0;
3582 for (int i = 0; i < arg_count; i++) {
3583 uint32_t arg_pattern;
3584 if (args[i].IsRegister()) {
3585 arg_pattern = args[i].Is32Bits() ? kPrintfArgW : kPrintfArgX;
3586 } else {
3587 DCHECK(args[i].Is64Bits());
3588 arg_pattern = kPrintfArgD;
3589 }
3590 DCHECK(arg_pattern < (1 << kPrintfArgPatternBits));
3591 arg_pattern_list |= (arg_pattern << (kPrintfArgPatternBits * i));
3592 }
3593 dc32(arg_pattern_list); // kPrintfArgPatternListOffset
3594 return;
3595 }
3596
3597 Call(ExternalReference::printf_function());
3598 }
3599
3600 void TurboAssembler::Printf(const char* format, CPURegister arg0,
3601 CPURegister arg1, CPURegister arg2,
3602 CPURegister arg3) {
3603 ASM_CODE_COMMENT(this);
3604 // Printf is expected to preserve all registers, so make sure that none are
3605 // available as scratch registers until we've preserved them.
3606 uint64_t old_tmp_list = TmpList()->bits();
3607 uint64_t old_fp_tmp_list = FPTmpList()->bits();
3608 TmpList()->set_bits(0);
3609 FPTmpList()->set_bits(0);
3610
3611 CPURegList saved_registers = kCallerSaved;
3612 saved_registers.Align();
3613
3614 // Preserve all caller-saved registers as well as NZCV.
3615 // PushCPURegList asserts that the size of each list is a multiple of 16
3616 // bytes.
3617 PushCPURegList(saved_registers);
3618 PushCPURegList(kCallerSavedV);
3619
3620 // We can use caller-saved registers as scratch values (except for argN).
3621 CPURegList tmp_list = saved_registers;
3622 CPURegList fp_tmp_list = kCallerSavedV;
3623 tmp_list.Remove(arg0, arg1, arg2, arg3);
3624 fp_tmp_list.Remove(arg0, arg1, arg2, arg3);
3625 TmpList()->set_bits(tmp_list.bits());
3626 FPTmpList()->set_bits(fp_tmp_list.bits());
3627
3628 {
3629 UseScratchRegisterScope temps(this);
3630 // If any of the arguments are the current stack pointer, allocate a new
3631 // register for them, and adjust the value to compensate for pushing the
3632 // caller-saved registers.
3633 bool arg0_sp = arg0.is_valid() && sp.Aliases(arg0);
3634 bool arg1_sp = arg1.is_valid() && sp.Aliases(arg1);
3635 bool arg2_sp = arg2.is_valid() && sp.Aliases(arg2);
3636 bool arg3_sp = arg3.is_valid() && sp.Aliases(arg3);
3637 if (arg0_sp || arg1_sp || arg2_sp || arg3_sp) {
3638 // Allocate a register to hold the original stack pointer value, to pass
3639 // to PrintfNoPreserve as an argument.
3640 Register arg_sp = temps.AcquireX();
3641 Add(arg_sp, sp,
3642 saved_registers.TotalSizeInBytes() +
3643 kCallerSavedV.TotalSizeInBytes());
3644 if (arg0_sp) arg0 = Register::Create(arg_sp.code(), arg0.SizeInBits());
3645 if (arg1_sp) arg1 = Register::Create(arg_sp.code(), arg1.SizeInBits());
3646 if (arg2_sp) arg2 = Register::Create(arg_sp.code(), arg2.SizeInBits());
3647 if (arg3_sp) arg3 = Register::Create(arg_sp.code(), arg3.SizeInBits());
3648 }
3649
3650 // Preserve NZCV.
3651 {
3652 UseScratchRegisterScope temps(this);
3653 Register tmp = temps.AcquireX();
3654 Mrs(tmp, NZCV);
3655 Push(tmp, xzr);
3656 }
3657
3658 PrintfNoPreserve(format, arg0, arg1, arg2, arg3);
3659
3660 // Restore NZCV.
3661 {
3662 UseScratchRegisterScope temps(this);
3663 Register tmp = temps.AcquireX();
3664 Pop(xzr, tmp);
3665 Msr(NZCV, tmp);
3666 }
3667 }
3668
3669 PopCPURegList(kCallerSavedV);
3670 PopCPURegList(saved_registers);
3671
3672 TmpList()->set_bits(old_tmp_list);
3673 FPTmpList()->set_bits(old_fp_tmp_list);
3674 }
3675
3676 UseScratchRegisterScope::~UseScratchRegisterScope() {
3677 available_->set_bits(old_available_);
3678 availablefp_->set_bits(old_availablefp_);
3679 }
3680
3681 Register UseScratchRegisterScope::AcquireSameSizeAs(const Register& reg) {
3682 int code = AcquireNextAvailable(available_).code();
3683 return Register::Create(code, reg.SizeInBits());
3684 }
3685
3686 VRegister UseScratchRegisterScope::AcquireSameSizeAs(const VRegister& reg) {
3687 int code = AcquireNextAvailable(availablefp_).code();
3688 return VRegister::Create(code, reg.SizeInBits());
3689 }
3690
3691 CPURegister UseScratchRegisterScope::AcquireNextAvailable(
3692 CPURegList* available) {
3693 CHECK(!available->IsEmpty());
3694 CPURegister result = available->PopLowestIndex();
3695 DCHECK(!AreAliased(result, xzr, sp));
3696 return result;
3697 }
3698
3699 void TurboAssembler::ComputeCodeStartAddress(const Register& rd) {
3700 // We can use adr to load a pc relative location.
3701 adr(rd, -pc_offset());
3702 }
3703
3704 void TurboAssembler::RestoreFPAndLR() {
3705 static_assert(StandardFrameConstants::kCallerFPOffset + kSystemPointerSize ==
3706 StandardFrameConstants::kCallerPCOffset,
3707 "Offsets must be consecutive for ldp!");
3708 #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
3709 // Make sure we can use x16 and x17.
3710 UseScratchRegisterScope temps(this);
3711 temps.Exclude(x16, x17);
3712 // We can load the return address directly into x17.
3713 Add(x16, fp, StandardFrameConstants::kCallerSPOffset);
3714 Ldp(fp, x17, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
3715 Autib1716();
3716 Mov(lr, x17);
3717 #else
3718 Ldp(fp, lr, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
3719 #endif
3720 }
3721
3722 #if V8_ENABLE_WEBASSEMBLY
3723 void TurboAssembler::StoreReturnAddressInWasmExitFrame(Label* return_location) {
3724 UseScratchRegisterScope temps(this);
3725 temps.Exclude(x16, x17);
3726 Adr(x17, return_location);
3727 #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
3728 Add(x16, fp, WasmExitFrameConstants::kCallingPCOffset + kSystemPointerSize);
3729 Pacib1716();
3730 #endif
3731 Str(x17, MemOperand(fp, WasmExitFrameConstants::kCallingPCOffset));
3732 }
3733 #endif // V8_ENABLE_WEBASSEMBLY
3734
3735 void TurboAssembler::PopcntHelper(Register dst, Register src) {
3736 UseScratchRegisterScope temps(this);
3737 VRegister scratch = temps.AcquireV(kFormat8B);
3738 VRegister tmp = src.Is32Bits() ? scratch.S() : scratch.D();
3739 Fmov(tmp, src);
3740 Cnt(scratch, scratch);
3741 Addv(scratch.B(), scratch);
3742 Fmov(dst, tmp);
3743 }
3744
3745 void TurboAssembler::I64x2BitMask(Register dst, VRegister src) {
3746 ASM_CODE_COMMENT(this);
3747 UseScratchRegisterScope scope(this);
3748 VRegister tmp1 = scope.AcquireV(kFormat2D);
3749 Register tmp2 = scope.AcquireX();
3750 Ushr(tmp1.V2D(), src.V2D(), 63);
3751 Mov(dst.X(), tmp1.D(), 0);
3752 Mov(tmp2.X(), tmp1.D(), 1);
3753 Add(dst.W(), dst.W(), Operand(tmp2.W(), LSL, 1));
3754 }
3755
3756 void TurboAssembler::I64x2AllTrue(Register dst, VRegister src) {
3757 ASM_CODE_COMMENT(this);
3758 UseScratchRegisterScope scope(this);
3759 VRegister tmp = scope.AcquireV(kFormat2D);
3760 Cmeq(tmp.V2D(), src.V2D(), 0);
3761 Addp(tmp.D(), tmp);
3762 Fcmp(tmp.D(), tmp.D());
3763 Cset(dst, eq);
3764 }
3765
3766 } // namespace internal
3767 } // namespace v8
3768
3769 #endif // V8_TARGET_ARCH_ARM64
3770