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