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#include "src/v8.h" 6 7#if V8_TARGET_ARCH_ARM64 8 9#include "src/arm64/simulator-arm64.h" 10#include "src/codegen.h" 11#include "src/macro-assembler.h" 12 13namespace v8 { 14namespace internal { 15 16#define __ ACCESS_MASM(masm) 17 18#if defined(USE_SIMULATOR) 19byte* fast_exp_arm64_machine_code = NULL; 20double fast_exp_simulator(double x) { 21 Simulator * simulator = Simulator::current(Isolate::Current()); 22 Simulator::CallArgument args[] = { 23 Simulator::CallArgument(x), 24 Simulator::CallArgument::End() 25 }; 26 return simulator->CallDouble(fast_exp_arm64_machine_code, args); 27} 28#endif 29 30 31UnaryMathFunction CreateExpFunction() { 32 if (!FLAG_fast_math) return &std::exp; 33 34 // Use the Math.exp implemetation in MathExpGenerator::EmitMathExp() to create 35 // an AAPCS64-compliant exp() function. This will be faster than the C 36 // library's exp() function, but probably less accurate. 37 size_t actual_size; 38 byte* buffer = 39 static_cast<byte*>(base::OS::Allocate(1 * KB, &actual_size, true)); 40 if (buffer == NULL) return &std::exp; 41 42 ExternalReference::InitializeMathExpData(); 43 MacroAssembler masm(NULL, buffer, static_cast<int>(actual_size)); 44 masm.SetStackPointer(csp); 45 46 // The argument will be in d0 on entry. 47 DoubleRegister input = d0; 48 // Use other caller-saved registers for all other values. 49 DoubleRegister result = d1; 50 DoubleRegister double_temp1 = d2; 51 DoubleRegister double_temp2 = d3; 52 Register temp1 = x10; 53 Register temp2 = x11; 54 Register temp3 = x12; 55 56 MathExpGenerator::EmitMathExp(&masm, input, result, 57 double_temp1, double_temp2, 58 temp1, temp2, temp3); 59 // Move the result to the return register. 60 masm.Fmov(d0, result); 61 masm.Ret(); 62 63 CodeDesc desc; 64 masm.GetCode(&desc); 65 DCHECK(!RelocInfo::RequiresRelocation(desc)); 66 67 CpuFeatures::FlushICache(buffer, actual_size); 68 base::OS::ProtectCode(buffer, actual_size); 69 70#if !defined(USE_SIMULATOR) 71 return FUNCTION_CAST<UnaryMathFunction>(buffer); 72#else 73 fast_exp_arm64_machine_code = buffer; 74 return &fast_exp_simulator; 75#endif 76} 77 78 79UnaryMathFunction CreateSqrtFunction() { 80 return &std::sqrt; 81} 82 83 84// ------------------------------------------------------------------------- 85// Platform-specific RuntimeCallHelper functions. 86 87void StubRuntimeCallHelper::BeforeCall(MacroAssembler* masm) const { 88 masm->EnterFrame(StackFrame::INTERNAL); 89 DCHECK(!masm->has_frame()); 90 masm->set_has_frame(true); 91} 92 93 94void StubRuntimeCallHelper::AfterCall(MacroAssembler* masm) const { 95 masm->LeaveFrame(StackFrame::INTERNAL); 96 DCHECK(masm->has_frame()); 97 masm->set_has_frame(false); 98} 99 100 101// ------------------------------------------------------------------------- 102// Code generators 103 104void ElementsTransitionGenerator::GenerateMapChangeElementsTransition( 105 MacroAssembler* masm, 106 Register receiver, 107 Register key, 108 Register value, 109 Register target_map, 110 AllocationSiteMode mode, 111 Label* allocation_memento_found) { 112 ASM_LOCATION( 113 "ElementsTransitionGenerator::GenerateMapChangeElementsTransition"); 114 DCHECK(!AreAliased(receiver, key, value, target_map)); 115 116 if (mode == TRACK_ALLOCATION_SITE) { 117 DCHECK(allocation_memento_found != NULL); 118 __ JumpIfJSArrayHasAllocationMemento(receiver, x10, x11, 119 allocation_memento_found); 120 } 121 122 // Set transitioned map. 123 __ Str(target_map, FieldMemOperand(receiver, HeapObject::kMapOffset)); 124 __ RecordWriteField(receiver, 125 HeapObject::kMapOffset, 126 target_map, 127 x10, 128 kLRHasNotBeenSaved, 129 kDontSaveFPRegs, 130 EMIT_REMEMBERED_SET, 131 OMIT_SMI_CHECK); 132} 133 134 135void ElementsTransitionGenerator::GenerateSmiToDouble( 136 MacroAssembler* masm, 137 Register receiver, 138 Register key, 139 Register value, 140 Register target_map, 141 AllocationSiteMode mode, 142 Label* fail) { 143 ASM_LOCATION("ElementsTransitionGenerator::GenerateSmiToDouble"); 144 Label gc_required, only_change_map; 145 Register elements = x4; 146 Register length = x5; 147 Register array_size = x6; 148 Register array = x7; 149 150 Register scratch = x6; 151 152 // Verify input registers don't conflict with locals. 153 DCHECK(!AreAliased(receiver, key, value, target_map, 154 elements, length, array_size, array)); 155 156 if (mode == TRACK_ALLOCATION_SITE) { 157 __ JumpIfJSArrayHasAllocationMemento(receiver, x10, x11, fail); 158 } 159 160 // Check for empty arrays, which only require a map transition and no changes 161 // to the backing store. 162 __ Ldr(elements, FieldMemOperand(receiver, JSObject::kElementsOffset)); 163 __ JumpIfRoot(elements, Heap::kEmptyFixedArrayRootIndex, &only_change_map); 164 165 __ Push(lr); 166 __ Ldrsw(length, UntagSmiFieldMemOperand(elements, 167 FixedArray::kLengthOffset)); 168 169 // Allocate new FixedDoubleArray. 170 __ Lsl(array_size, length, kDoubleSizeLog2); 171 __ Add(array_size, array_size, FixedDoubleArray::kHeaderSize); 172 __ Allocate(array_size, array, x10, x11, &gc_required, DOUBLE_ALIGNMENT); 173 // Register array is non-tagged heap object. 174 175 // Set the destination FixedDoubleArray's length and map. 176 Register map_root = array_size; 177 __ LoadRoot(map_root, Heap::kFixedDoubleArrayMapRootIndex); 178 __ SmiTag(x11, length); 179 __ Str(x11, MemOperand(array, FixedDoubleArray::kLengthOffset)); 180 __ Str(map_root, MemOperand(array, HeapObject::kMapOffset)); 181 182 __ Str(target_map, FieldMemOperand(receiver, HeapObject::kMapOffset)); 183 __ RecordWriteField(receiver, HeapObject::kMapOffset, target_map, scratch, 184 kLRHasBeenSaved, kDontSaveFPRegs, OMIT_REMEMBERED_SET, 185 OMIT_SMI_CHECK); 186 187 // Replace receiver's backing store with newly created FixedDoubleArray. 188 __ Add(x10, array, kHeapObjectTag); 189 __ Str(x10, FieldMemOperand(receiver, JSObject::kElementsOffset)); 190 __ RecordWriteField(receiver, JSObject::kElementsOffset, x10, 191 scratch, kLRHasBeenSaved, kDontSaveFPRegs, 192 EMIT_REMEMBERED_SET, OMIT_SMI_CHECK); 193 194 // Prepare for conversion loop. 195 Register src_elements = x10; 196 Register dst_elements = x11; 197 Register dst_end = x12; 198 __ Add(src_elements, elements, FixedArray::kHeaderSize - kHeapObjectTag); 199 __ Add(dst_elements, array, FixedDoubleArray::kHeaderSize); 200 __ Add(dst_end, dst_elements, Operand(length, LSL, kDoubleSizeLog2)); 201 202 FPRegister nan_d = d1; 203 __ Fmov(nan_d, rawbits_to_double(kHoleNanInt64)); 204 205 Label entry, done; 206 __ B(&entry); 207 208 __ Bind(&only_change_map); 209 __ Str(target_map, FieldMemOperand(receiver, HeapObject::kMapOffset)); 210 __ RecordWriteField(receiver, HeapObject::kMapOffset, target_map, scratch, 211 kLRHasNotBeenSaved, kDontSaveFPRegs, OMIT_REMEMBERED_SET, 212 OMIT_SMI_CHECK); 213 __ B(&done); 214 215 // Call into runtime if GC is required. 216 __ Bind(&gc_required); 217 __ Pop(lr); 218 __ B(fail); 219 220 // Iterate over the array, copying and coverting smis to doubles. If an 221 // element is non-smi, write a hole to the destination. 222 { 223 Label loop; 224 __ Bind(&loop); 225 __ Ldr(x13, MemOperand(src_elements, kPointerSize, PostIndex)); 226 __ SmiUntagToDouble(d0, x13, kSpeculativeUntag); 227 __ Tst(x13, kSmiTagMask); 228 __ Fcsel(d0, d0, nan_d, eq); 229 __ Str(d0, MemOperand(dst_elements, kDoubleSize, PostIndex)); 230 231 __ Bind(&entry); 232 __ Cmp(dst_elements, dst_end); 233 __ B(lt, &loop); 234 } 235 236 __ Pop(lr); 237 __ Bind(&done); 238} 239 240 241void ElementsTransitionGenerator::GenerateDoubleToObject( 242 MacroAssembler* masm, 243 Register receiver, 244 Register key, 245 Register value, 246 Register target_map, 247 AllocationSiteMode mode, 248 Label* fail) { 249 ASM_LOCATION("ElementsTransitionGenerator::GenerateDoubleToObject"); 250 Register elements = x4; 251 Register array_size = x6; 252 Register array = x7; 253 Register length = x5; 254 255 // Verify input registers don't conflict with locals. 256 DCHECK(!AreAliased(receiver, key, value, target_map, 257 elements, array_size, array, length)); 258 259 if (mode == TRACK_ALLOCATION_SITE) { 260 __ JumpIfJSArrayHasAllocationMemento(receiver, x10, x11, fail); 261 } 262 263 // Check for empty arrays, which only require a map transition and no changes 264 // to the backing store. 265 Label only_change_map; 266 267 __ Ldr(elements, FieldMemOperand(receiver, JSObject::kElementsOffset)); 268 __ JumpIfRoot(elements, Heap::kEmptyFixedArrayRootIndex, &only_change_map); 269 270 __ Push(lr); 271 // TODO(all): These registers may not need to be pushed. Examine 272 // RecordWriteStub and check whether it's needed. 273 __ Push(target_map, receiver, key, value); 274 __ Ldrsw(length, UntagSmiFieldMemOperand(elements, 275 FixedArray::kLengthOffset)); 276 // Allocate new FixedArray. 277 Label gc_required; 278 __ Mov(array_size, FixedDoubleArray::kHeaderSize); 279 __ Add(array_size, array_size, Operand(length, LSL, kPointerSizeLog2)); 280 __ Allocate(array_size, array, x10, x11, &gc_required, NO_ALLOCATION_FLAGS); 281 282 // Set destination FixedDoubleArray's length and map. 283 Register map_root = array_size; 284 __ LoadRoot(map_root, Heap::kFixedArrayMapRootIndex); 285 __ SmiTag(x11, length); 286 __ Str(x11, MemOperand(array, FixedDoubleArray::kLengthOffset)); 287 __ Str(map_root, MemOperand(array, HeapObject::kMapOffset)); 288 289 // Prepare for conversion loop. 290 Register src_elements = x10; 291 Register dst_elements = x11; 292 Register dst_end = x12; 293 __ Add(src_elements, elements, 294 FixedDoubleArray::kHeaderSize - kHeapObjectTag); 295 __ Add(dst_elements, array, FixedArray::kHeaderSize); 296 __ Add(array, array, kHeapObjectTag); 297 __ Add(dst_end, dst_elements, Operand(length, LSL, kPointerSizeLog2)); 298 299 Register the_hole = x14; 300 Register heap_num_map = x15; 301 __ LoadRoot(the_hole, Heap::kTheHoleValueRootIndex); 302 __ LoadRoot(heap_num_map, Heap::kHeapNumberMapRootIndex); 303 304 Label entry; 305 __ B(&entry); 306 307 // Call into runtime if GC is required. 308 __ Bind(&gc_required); 309 __ Pop(value, key, receiver, target_map); 310 __ Pop(lr); 311 __ B(fail); 312 313 { 314 Label loop, convert_hole; 315 __ Bind(&loop); 316 __ Ldr(x13, MemOperand(src_elements, kPointerSize, PostIndex)); 317 __ Cmp(x13, kHoleNanInt64); 318 __ B(eq, &convert_hole); 319 320 // Non-hole double, copy value into a heap number. 321 Register heap_num = length; 322 Register scratch = array_size; 323 Register scratch2 = elements; 324 __ AllocateHeapNumber(heap_num, &gc_required, scratch, scratch2, 325 x13, heap_num_map); 326 __ Mov(x13, dst_elements); 327 __ Str(heap_num, MemOperand(dst_elements, kPointerSize, PostIndex)); 328 __ RecordWrite(array, x13, heap_num, kLRHasBeenSaved, kDontSaveFPRegs, 329 EMIT_REMEMBERED_SET, OMIT_SMI_CHECK); 330 331 __ B(&entry); 332 333 // Replace the-hole NaN with the-hole pointer. 334 __ Bind(&convert_hole); 335 __ Str(the_hole, MemOperand(dst_elements, kPointerSize, PostIndex)); 336 337 __ Bind(&entry); 338 __ Cmp(dst_elements, dst_end); 339 __ B(lt, &loop); 340 } 341 342 __ Pop(value, key, receiver, target_map); 343 // Replace receiver's backing store with newly created and filled FixedArray. 344 __ Str(array, FieldMemOperand(receiver, JSObject::kElementsOffset)); 345 __ RecordWriteField(receiver, JSObject::kElementsOffset, array, x13, 346 kLRHasBeenSaved, kDontSaveFPRegs, EMIT_REMEMBERED_SET, 347 OMIT_SMI_CHECK); 348 __ Pop(lr); 349 350 __ Bind(&only_change_map); 351 __ Str(target_map, FieldMemOperand(receiver, HeapObject::kMapOffset)); 352 __ RecordWriteField(receiver, HeapObject::kMapOffset, target_map, x13, 353 kLRHasNotBeenSaved, kDontSaveFPRegs, OMIT_REMEMBERED_SET, 354 OMIT_SMI_CHECK); 355} 356 357 358CodeAgingHelper::CodeAgingHelper() { 359 DCHECK(young_sequence_.length() == kNoCodeAgeSequenceLength); 360 // The sequence of instructions that is patched out for aging code is the 361 // following boilerplate stack-building prologue that is found both in 362 // FUNCTION and OPTIMIZED_FUNCTION code: 363 PatchingAssembler patcher(young_sequence_.start(), 364 young_sequence_.length() / kInstructionSize); 365 // The young sequence is the frame setup code for FUNCTION code types. It is 366 // generated by FullCodeGenerator::Generate. 367 MacroAssembler::EmitFrameSetupForCodeAgePatching(&patcher); 368 369#ifdef DEBUG 370 const int length = kCodeAgeStubEntryOffset / kInstructionSize; 371 DCHECK(old_sequence_.length() >= kCodeAgeStubEntryOffset); 372 PatchingAssembler patcher_old(old_sequence_.start(), length); 373 MacroAssembler::EmitCodeAgeSequence(&patcher_old, NULL); 374#endif 375} 376 377 378#ifdef DEBUG 379bool CodeAgingHelper::IsOld(byte* candidate) const { 380 return memcmp(candidate, old_sequence_.start(), kCodeAgeStubEntryOffset) == 0; 381} 382#endif 383 384 385bool Code::IsYoungSequence(Isolate* isolate, byte* sequence) { 386 return MacroAssembler::IsYoungSequence(isolate, sequence); 387} 388 389 390void Code::GetCodeAgeAndParity(Isolate* isolate, byte* sequence, Age* age, 391 MarkingParity* parity) { 392 if (IsYoungSequence(isolate, sequence)) { 393 *age = kNoAgeCodeAge; 394 *parity = NO_MARKING_PARITY; 395 } else { 396 byte* target = sequence + kCodeAgeStubEntryOffset; 397 Code* stub = GetCodeFromTargetAddress(Memory::Address_at(target)); 398 GetCodeAgeAndParity(stub, age, parity); 399 } 400} 401 402 403void Code::PatchPlatformCodeAge(Isolate* isolate, 404 byte* sequence, 405 Code::Age age, 406 MarkingParity parity) { 407 PatchingAssembler patcher(sequence, 408 kNoCodeAgeSequenceLength / kInstructionSize); 409 if (age == kNoAgeCodeAge) { 410 MacroAssembler::EmitFrameSetupForCodeAgePatching(&patcher); 411 } else { 412 Code * stub = GetCodeAgeStub(isolate, age, parity); 413 MacroAssembler::EmitCodeAgeSequence(&patcher, stub); 414 } 415} 416 417 418void StringCharLoadGenerator::Generate(MacroAssembler* masm, 419 Register string, 420 Register index, 421 Register result, 422 Label* call_runtime) { 423 DCHECK(string.Is64Bits() && index.Is32Bits() && result.Is64Bits()); 424 // Fetch the instance type of the receiver into result register. 425 __ Ldr(result, FieldMemOperand(string, HeapObject::kMapOffset)); 426 __ Ldrb(result, FieldMemOperand(result, Map::kInstanceTypeOffset)); 427 428 // We need special handling for indirect strings. 429 Label check_sequential; 430 __ TestAndBranchIfAllClear(result, kIsIndirectStringMask, &check_sequential); 431 432 // Dispatch on the indirect string shape: slice or cons. 433 Label cons_string; 434 __ TestAndBranchIfAllClear(result, kSlicedNotConsMask, &cons_string); 435 436 // Handle slices. 437 Label indirect_string_loaded; 438 __ Ldr(result.W(), 439 UntagSmiFieldMemOperand(string, SlicedString::kOffsetOffset)); 440 __ Ldr(string, FieldMemOperand(string, SlicedString::kParentOffset)); 441 __ Add(index, index, result.W()); 442 __ B(&indirect_string_loaded); 443 444 // Handle cons strings. 445 // Check whether the right hand side is the empty string (i.e. if 446 // this is really a flat string in a cons string). If that is not 447 // the case we would rather go to the runtime system now to flatten 448 // the string. 449 __ Bind(&cons_string); 450 __ Ldr(result, FieldMemOperand(string, ConsString::kSecondOffset)); 451 __ JumpIfNotRoot(result, Heap::kempty_stringRootIndex, call_runtime); 452 // Get the first of the two strings and load its instance type. 453 __ Ldr(string, FieldMemOperand(string, ConsString::kFirstOffset)); 454 455 __ Bind(&indirect_string_loaded); 456 __ Ldr(result, FieldMemOperand(string, HeapObject::kMapOffset)); 457 __ Ldrb(result, FieldMemOperand(result, Map::kInstanceTypeOffset)); 458 459 // Distinguish sequential and external strings. Only these two string 460 // representations can reach here (slices and flat cons strings have been 461 // reduced to the underlying sequential or external string). 462 Label external_string, check_encoding; 463 __ Bind(&check_sequential); 464 STATIC_ASSERT(kSeqStringTag == 0); 465 __ TestAndBranchIfAnySet(result, kStringRepresentationMask, &external_string); 466 467 // Prepare sequential strings 468 STATIC_ASSERT(SeqTwoByteString::kHeaderSize == SeqOneByteString::kHeaderSize); 469 __ Add(string, string, SeqTwoByteString::kHeaderSize - kHeapObjectTag); 470 __ B(&check_encoding); 471 472 // Handle external strings. 473 __ Bind(&external_string); 474 if (FLAG_debug_code) { 475 // Assert that we do not have a cons or slice (indirect strings) here. 476 // Sequential strings have already been ruled out. 477 __ Tst(result, kIsIndirectStringMask); 478 __ Assert(eq, kExternalStringExpectedButNotFound); 479 } 480 // Rule out short external strings. 481 STATIC_ASSERT(kShortExternalStringTag != 0); 482 // TestAndBranchIfAnySet can emit Tbnz. Do not use it because call_runtime 483 // can be bound far away in deferred code. 484 __ Tst(result, kShortExternalStringMask); 485 __ B(ne, call_runtime); 486 __ Ldr(string, FieldMemOperand(string, ExternalString::kResourceDataOffset)); 487 488 Label one_byte, done; 489 __ Bind(&check_encoding); 490 STATIC_ASSERT(kTwoByteStringTag == 0); 491 __ TestAndBranchIfAnySet(result, kStringEncodingMask, &one_byte); 492 // Two-byte string. 493 __ Ldrh(result, MemOperand(string, index, SXTW, 1)); 494 __ B(&done); 495 __ Bind(&one_byte); 496 // One-byte string. 497 __ Ldrb(result, MemOperand(string, index, SXTW)); 498 __ Bind(&done); 499} 500 501 502static MemOperand ExpConstant(Register base, int index) { 503 return MemOperand(base, index * kDoubleSize); 504} 505 506 507void MathExpGenerator::EmitMathExp(MacroAssembler* masm, 508 DoubleRegister input, 509 DoubleRegister result, 510 DoubleRegister double_temp1, 511 DoubleRegister double_temp2, 512 Register temp1, 513 Register temp2, 514 Register temp3) { 515 // TODO(jbramley): There are several instances where fnmsub could be used 516 // instead of fmul and fsub. Doing this changes the result, but since this is 517 // an estimation anyway, does it matter? 518 519 DCHECK(!AreAliased(input, result, 520 double_temp1, double_temp2, 521 temp1, temp2, temp3)); 522 DCHECK(ExternalReference::math_exp_constants(0).address() != NULL); 523 DCHECK(!masm->serializer_enabled()); // External references not serializable. 524 525 Label done; 526 DoubleRegister double_temp3 = result; 527 Register constants = temp3; 528 529 // The algorithm used relies on some magic constants which are initialized in 530 // ExternalReference::InitializeMathExpData(). 531 532 // Load the address of the start of the array. 533 __ Mov(constants, ExternalReference::math_exp_constants(0)); 534 535 // We have to do a four-way split here: 536 // - If input <= about -708.4, the output always rounds to zero. 537 // - If input >= about 709.8, the output always rounds to +infinity. 538 // - If the input is NaN, the output is NaN. 539 // - Otherwise, the result needs to be calculated. 540 Label result_is_finite_non_zero; 541 // Assert that we can load offset 0 (the small input threshold) and offset 1 542 // (the large input threshold) with a single ldp. 543 DCHECK(kDRegSize == (ExpConstant(constants, 1).offset() - 544 ExpConstant(constants, 0).offset())); 545 __ Ldp(double_temp1, double_temp2, ExpConstant(constants, 0)); 546 547 __ Fcmp(input, double_temp1); 548 __ Fccmp(input, double_temp2, NoFlag, hi); 549 // At this point, the condition flags can be in one of five states: 550 // NZCV 551 // 1000 -708.4 < input < 709.8 result = exp(input) 552 // 0110 input == 709.8 result = +infinity 553 // 0010 input > 709.8 result = +infinity 554 // 0011 input is NaN result = input 555 // 0000 input <= -708.4 result = +0.0 556 557 // Continue the common case first. 'mi' tests N == 1. 558 __ B(&result_is_finite_non_zero, mi); 559 560 // TODO(jbramley): Consider adding a +infinity register for ARM64. 561 __ Ldr(double_temp2, ExpConstant(constants, 2)); // Synthesize +infinity. 562 563 // Select between +0.0 and +infinity. 'lo' tests C == 0. 564 __ Fcsel(result, fp_zero, double_temp2, lo); 565 // Select between {+0.0 or +infinity} and input. 'vc' tests V == 0. 566 __ Fcsel(result, result, input, vc); 567 __ B(&done); 568 569 // The rest is magic, as described in InitializeMathExpData(). 570 __ Bind(&result_is_finite_non_zero); 571 572 // Assert that we can load offset 3 and offset 4 with a single ldp. 573 DCHECK(kDRegSize == (ExpConstant(constants, 4).offset() - 574 ExpConstant(constants, 3).offset())); 575 __ Ldp(double_temp1, double_temp3, ExpConstant(constants, 3)); 576 __ Fmadd(double_temp1, double_temp1, input, double_temp3); 577 __ Fmov(temp2.W(), double_temp1.S()); 578 __ Fsub(double_temp1, double_temp1, double_temp3); 579 580 // Assert that we can load offset 5 and offset 6 with a single ldp. 581 DCHECK(kDRegSize == (ExpConstant(constants, 6).offset() - 582 ExpConstant(constants, 5).offset())); 583 __ Ldp(double_temp2, double_temp3, ExpConstant(constants, 5)); 584 // TODO(jbramley): Consider using Fnmsub here. 585 __ Fmul(double_temp1, double_temp1, double_temp2); 586 __ Fsub(double_temp1, double_temp1, input); 587 588 __ Fmul(double_temp2, double_temp1, double_temp1); 589 __ Fsub(double_temp3, double_temp3, double_temp1); 590 __ Fmul(double_temp3, double_temp3, double_temp2); 591 592 __ Mov(temp1.W(), Operand(temp2.W(), LSR, 11)); 593 594 __ Ldr(double_temp2, ExpConstant(constants, 7)); 595 // TODO(jbramley): Consider using Fnmsub here. 596 __ Fmul(double_temp3, double_temp3, double_temp2); 597 __ Fsub(double_temp3, double_temp3, double_temp1); 598 599 // The 8th constant is 1.0, so use an immediate move rather than a load. 600 // We can't generate a runtime assertion here as we would need to call Abort 601 // in the runtime and we don't have an Isolate when we generate this code. 602 __ Fmov(double_temp2, 1.0); 603 __ Fadd(double_temp3, double_temp3, double_temp2); 604 605 __ And(temp2, temp2, 0x7ff); 606 __ Add(temp1, temp1, 0x3ff); 607 608 // Do the final table lookup. 609 __ Mov(temp3, ExternalReference::math_exp_log_table()); 610 611 __ Add(temp3, temp3, Operand(temp2, LSL, kDRegSizeLog2)); 612 __ Ldp(temp2.W(), temp3.W(), MemOperand(temp3)); 613 __ Orr(temp1.W(), temp3.W(), Operand(temp1.W(), LSL, 20)); 614 __ Bfi(temp2, temp1, 32, 32); 615 __ Fmov(double_temp1, temp2); 616 617 __ Fmul(result, double_temp3, double_temp1); 618 619 __ Bind(&done); 620} 621 622#undef __ 623 624} } // namespace v8::internal 625 626#endif // V8_TARGET_ARCH_ARM64 627