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 38namespace v8 { 39namespace internal { 40 41CPURegList TurboAssembler::DefaultTmpList() { return CPURegList(ip0, ip1); } 42 43CPURegList TurboAssembler::DefaultFPTmpList() { 44 return CPURegList(fp_scratch1, fp_scratch2); 45} 46 47namespace { 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 52constexpr int kStackSavedSavedFPSizeInBits = kQRegSizeInBits; 53#else 54constexpr int kStackSavedSavedFPSizeInBits = kDRegSizeInBits; 55#endif // V8_ENABLE_WEBASSEMBLY 56 57} // namespace 58 59void 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 79void 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 99int 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 115int 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 135int 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 155void 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 263void 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 339void 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 407void TurboAssembler::Mov(const Register& rd, Smi smi) { 408 return Mov(rd, Operand(smi)); 409} 410 411void 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 433void 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 510void 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 546void 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 567void 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 579void 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 602unsigned 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. 620bool 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. 627bool TurboAssembler::IsImmMovn(uint64_t imm, unsigned reg_size) { 628 return IsImmMovz(~imm, reg_size); 629} 630 631void 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 659void 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 693bool 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 715Operand 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 765void 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 811void 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 860void 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 890void 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 923bool 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 946void 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 980void 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 1010void 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 1027void 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 1043void 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 1059void 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 1075void 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 1093void 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 1116void 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 1130void 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 1148void 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 1176void 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 1214void 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 1252void 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 1259void 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 1266void 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 1300void 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 1328void 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 1341void 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 1350void 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 1358void 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 1429void TurboAssembler::SlotAddress(Register dst, int slot_offset) { 1430 Add(dst, sp, slot_offset << kSystemPointerSizeLog2); 1431} 1432 1433void TurboAssembler::SlotAddress(Register dst, Register slot_offset) { 1434 Add(dst, sp, Operand(slot_offset, LSL, kSystemPointerSizeLog2)); 1435} 1436 1437void 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 1461void 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 1471void 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 1479void 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 1487void TurboAssembler::Move(Register dst, Smi src) { Mov(dst, src); } 1488void TurboAssembler::Move(Register dst, MemOperand src) { Ldr(dst, src); } 1489void TurboAssembler::Move(Register dst, Register src) { 1490 if (dst == src) return; 1491 Mov(dst, src); 1492} 1493 1494void 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 1510void 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 1520void 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 1538void 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 1546void 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 1554void 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 1566void 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 1581void 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 1594void 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 1607void 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 1619void 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 1646void 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 1660void 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 1670void 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 1689void 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 1699void MacroAssembler::JumpToOffHeapInstructionStream(Address entry) { 1700 Ldr(kOffHeapTrampolineRegister, Operand(entry, RelocInfo::OFF_HEAP_TARGET)); 1701 Br(kOffHeapTrampolineRegister); 1702} 1703 1704void 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 1718int 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 1734void TurboAssembler::CallCFunction(ExternalReference function, 1735 int num_of_reg_args) { 1736 CallCFunction(function, num_of_reg_args, 0); 1737} 1738 1739void 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 1749static const int kRegisterPassedArguments = 8; 1750static const int kFPRegisterPassedArguments = 8; 1751 1752void 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 1817void 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 1827void TurboAssembler::LoadRootRelative(Register destination, int32_t offset) { 1828 Ldr(destination, MemOperand(kRootRegister, offset)); 1829} 1830 1831void 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 1840void 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 1848void 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. 1872int64_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 1897void 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 1903void 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 1928void TurboAssembler::Jump(const ExternalReference& reference) { 1929 UseScratchRegisterScope temps(this); 1930 Register scratch = temps.AcquireX(); 1931 Mov(scratch, reference); 1932 Jump(scratch); 1933} 1934 1935void TurboAssembler::Call(Register target) { 1936 BlockPoolsScope scope(this); 1937 Blr(target); 1938} 1939 1940void 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 1951void 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 1975void TurboAssembler::Call(ExternalReference target) { 1976 UseScratchRegisterScope temps(this); 1977 Register temp = temps.AcquireX(); 1978 Mov(temp, target); 1979 Call(temp); 1980} 1981 1982void 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 2005void TurboAssembler::LoadEntryFromBuiltin(Builtin builtin, 2006 Register destination) { 2007 Ldr(destination, EntryFromBuiltinAsOperand(builtin)); 2008} 2009 2010MemOperand TurboAssembler::EntryFromBuiltinAsOperand(Builtin builtin) { 2011 ASM_CODE_COMMENT(this); 2012 DCHECK(root_array_available()); 2013 return MemOperand(kRootRegister, 2014 IsolateData::BuiltinEntrySlotOffset(builtin)); 2015} 2016 2017void TurboAssembler::CallBuiltinByIndex(Register builtin_index) { 2018 ASM_CODE_COMMENT(this); 2019 LoadEntryFromBuiltinIndex(builtin_index); 2020 Call(builtin_index); 2021} 2022 2023void 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 2039void 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 2064void 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 2119void TurboAssembler::CallCodeObject(Register code_object) { 2120 ASM_CODE_COMMENT(this); 2121 LoadCodeObjectEntry(code_object, code_object); 2122 Call(code_object); 2123} 2124 2125void 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 2138void 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 2150void 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 2162void 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 2170void 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 2184void 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 2193void TurboAssembler::CallCodeTObject(Register code) { 2194 if (V8_EXTERNAL_CODE_SPACE_BOOL) { 2195 CallCodeDataContainerObject(code); 2196 } else { 2197 CallCodeObject(code); 2198 } 2199} 2200 2201void 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 2209void 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 2243void 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 2251bool TurboAssembler::IsNearCallOffset(int64_t offset) { 2252 return is_int26(offset); 2253} 2254 2255void 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 2266void 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 2281void 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 2300void 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 2407void 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 2431void 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 2488Operand MacroAssembler::ReceiverOperand(Register arg_count) { 2489 return Operand(0); 2490} 2491 2492void 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 2521void 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 2541void 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 2566void 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 2619void 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 2627void 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 2686void 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 2694void MacroAssembler::ExitFramePreserveFPRegs() { 2695 ASM_CODE_COMMENT(this); 2696 DCHECK_EQ(kCallerSavedV.Count() % 2, 0); 2697 PushCPURegList(kCallerSavedV); 2698} 2699 2700void 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 2716void 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. 2778void 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 2811void MacroAssembler::LoadGlobalProxy(Register dst) { 2812 ASM_CODE_COMMENT(this); 2813 LoadNativeContextSlot(dst, Context::GLOBAL_PROXY_INDEX); 2814} 2815 2816void 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 2825void 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 2841void 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. 2850void 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 2857void 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. 2863void 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. 2871void 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 2883void 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 2891void 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 2900void MacroAssembler::JumpIfRoot(const Register& obj, RootIndex index, 2901 Label* if_equal) { 2902 CompareRoot(obj, index); 2903 B(eq, if_equal); 2904} 2905 2906void MacroAssembler::JumpIfNotRoot(const Register& obj, RootIndex index, 2907 Label* if_not_equal) { 2908 CompareRoot(obj, index); 2909 B(ne, if_not_equal); 2910} 2911 2912void 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 2929void 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 2938void 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 2947void 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 2956void TurboAssembler::SmiUntagField(Register dst, const MemOperand& src) { 2957 SmiUntag(dst, src); 2958} 2959 2960void 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 2969void 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 2981void 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 2992void 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 2999void TurboAssembler::DecompressTaggedPointer(const Register& destination, 3000 const Register& source) { 3001 ASM_CODE_COMMENT(this); 3002 Add(destination, kPtrComprCageBaseRegister, Operand(source, UXTW)); 3003} 3004 3005void 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 3012void 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 3026void 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 3036void 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 3046void 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 3061void 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 3101void 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 3111void 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 3121void 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 3128void 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 3138void 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 3169void 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 3179void 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 3189void 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 3205void 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 3223void 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 3250void 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. 3288void 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 3346void TurboAssembler::Assert(Condition cond, AbortReason reason) { 3347 if (FLAG_debug_code) { 3348 Check(cond, reason); 3349 } 3350} 3351 3352void TurboAssembler::AssertUnreachable(AbortReason reason) { 3353 if (FLAG_debug_code) Abort(reason); 3354} 3355 3356void 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 3364void TurboAssembler::Trap() { Brk(0); } 3365void TurboAssembler::DebugBreak() { Debug("DebugBreak", 0, BREAK); } 3366 3367void 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 3410void 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. 3420void 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 3570void 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 3600void 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 3676UseScratchRegisterScope::~UseScratchRegisterScope() { 3677 available_->set_bits(old_available_); 3678 availablefp_->set_bits(old_availablefp_); 3679} 3680 3681Register UseScratchRegisterScope::AcquireSameSizeAs(const Register& reg) { 3682 int code = AcquireNextAvailable(available_).code(); 3683 return Register::Create(code, reg.SizeInBits()); 3684} 3685 3686VRegister UseScratchRegisterScope::AcquireSameSizeAs(const VRegister& reg) { 3687 int code = AcquireNextAvailable(availablefp_).code(); 3688 return VRegister::Create(code, reg.SizeInBits()); 3689} 3690 3691CPURegister 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 3699void TurboAssembler::ComputeCodeStartAddress(const Register& rd) { 3700 // We can use adr to load a pc relative location. 3701 adr(rd, -pc_offset()); 3702} 3703 3704void 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 3723void 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 3735void 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 3745void 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 3756void 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