| /* |
| * Copyright (C) 2011-2020 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #pragma once |
| |
| #if ENABLE(JIT) |
| |
| #include "CodeBlock.h" |
| #include "EntryFrame.h" |
| #include "FPRInfo.h" |
| #include "GPRInfo.h" |
| #include "Heap.h" |
| #include "InlineCallFrame.h" |
| #include "JITAllocator.h" |
| #include "JITCode.h" |
| #include "JSCellInlines.h" |
| #include "MacroAssembler.h" |
| #include "MarkedSpace.h" |
| #include "RegisterAtOffsetList.h" |
| #include "RegisterSet.h" |
| #include "StackAlignment.h" |
| #include "TagRegistersMode.h" |
| #include "TypeofType.h" |
| #include "VM.h" |
| #include <wtf/Optional.h> |
| |
| namespace JSC { |
| |
| typedef void (*V_DebugOperation_EPP)(CallFrame*, void*, void*); |
| |
| class AssemblyHelpers : public MacroAssembler { |
| public: |
| AssemblyHelpers(CodeBlock* codeBlock) |
| : m_codeBlock(codeBlock) |
| , m_baselineCodeBlock(codeBlock ? codeBlock->baselineAlternative() : nullptr) |
| { |
| if (m_codeBlock) { |
| ASSERT(m_baselineCodeBlock); |
| ASSERT(!m_baselineCodeBlock->alternative()); |
| ASSERT(m_baselineCodeBlock->jitType() == JITType::None || JITCode::isBaselineCode(m_baselineCodeBlock->jitType())); |
| } |
| } |
| |
| CodeBlock* codeBlock() { return m_codeBlock; } |
| VM& vm() { return m_codeBlock->vm(); } |
| AssemblerType_T& assembler() { return m_assembler; } |
| |
| void prepareCallOperation(VM& vm) |
| { |
| UNUSED_PARAM(vm); |
| #if !USE(BUILTIN_FRAME_ADDRESS) || ASSERT_ENABLED |
| storePtr(GPRInfo::callFrameRegister, &vm.topCallFrame); |
| #endif |
| } |
| |
| void checkStackPointerAlignment() |
| { |
| // This check is both unneeded and harder to write correctly for ARM64 |
| #if !defined(NDEBUG) && !CPU(ARM64) |
| Jump stackPointerAligned = branchTestPtr(Zero, stackPointerRegister, TrustedImm32(0xf)); |
| abortWithReason(AHStackPointerMisaligned); |
| stackPointerAligned.link(this); |
| #endif |
| } |
| |
| #if USE(JSVALUE64) |
| void store64FromReg(Reg src, Address dst) |
| { |
| if (src.isFPR()) |
| storeDouble(src.fpr(), dst); |
| else |
| store64(src.gpr(), dst); |
| } |
| #endif |
| |
| void store32FromReg(Reg src, Address dst) |
| { |
| if (src.isFPR()) |
| storeFloat(src.fpr(), dst); |
| else |
| store32(src.gpr(), dst); |
| } |
| |
| void storeReg(Reg src, Address dst) |
| { |
| #if USE(JSVALUE64) |
| store64FromReg(src, dst); |
| #else |
| store32FromReg(src, dst); |
| #endif |
| } |
| |
| #if USE(JSVALUE64) |
| void load64ToReg(Address src, Reg dst) |
| { |
| if (dst.isFPR()) |
| loadDouble(src, dst.fpr()); |
| else |
| load64(src, dst.gpr()); |
| } |
| #endif |
| |
| void load32ToReg(Address src, Reg dst) |
| { |
| if (dst.isFPR()) |
| loadFloat(src, dst.fpr()); |
| else |
| load32(src, dst.gpr()); |
| } |
| |
| void loadReg(Address src, Reg dst) |
| { |
| #if USE(JSVALUE64) |
| load64ToReg(src, dst); |
| #else |
| load32ToReg(src, dst); |
| #endif |
| } |
| |
| template<typename T> |
| void storeCell(T cell, Address address) |
| { |
| #if USE(JSVALUE64) |
| store64(cell, address); |
| #else |
| store32(cell, address.withOffset(PayloadOffset)); |
| store32(TrustedImm32(JSValue::CellTag), address.withOffset(TagOffset)); |
| #endif |
| } |
| |
| void loadCell(Address address, GPRReg gpr) |
| { |
| #if USE(JSVALUE64) |
| load64(address, gpr); |
| #else |
| load32(address.withOffset(PayloadOffset), gpr); |
| #endif |
| } |
| |
| void storeValue(JSValueRegs regs, Address address) |
| { |
| #if USE(JSVALUE64) |
| store64(regs.gpr(), address); |
| #else |
| store32(regs.payloadGPR(), address.withOffset(PayloadOffset)); |
| store32(regs.tagGPR(), address.withOffset(TagOffset)); |
| #endif |
| } |
| |
| void storeValue(JSValueRegs regs, BaseIndex address) |
| { |
| #if USE(JSVALUE64) |
| store64(regs.gpr(), address); |
| #else |
| store32(regs.payloadGPR(), address.withOffset(PayloadOffset)); |
| store32(regs.tagGPR(), address.withOffset(TagOffset)); |
| #endif |
| } |
| |
| void storeValue(JSValueRegs regs, void* address) |
| { |
| #if USE(JSVALUE64) |
| store64(regs.gpr(), address); |
| #else |
| store32(regs.payloadGPR(), bitwise_cast<void*>(bitwise_cast<uintptr_t>(address) + PayloadOffset)); |
| store32(regs.tagGPR(), bitwise_cast<void*>(bitwise_cast<uintptr_t>(address) + TagOffset)); |
| #endif |
| } |
| |
| void loadValue(Address address, JSValueRegs regs) |
| { |
| #if USE(JSVALUE64) |
| load64(address, regs.gpr()); |
| #else |
| if (address.base == regs.payloadGPR()) { |
| load32(address.withOffset(TagOffset), regs.tagGPR()); |
| load32(address.withOffset(PayloadOffset), regs.payloadGPR()); |
| } else { |
| load32(address.withOffset(PayloadOffset), regs.payloadGPR()); |
| load32(address.withOffset(TagOffset), regs.tagGPR()); |
| } |
| #endif |
| } |
| |
| void loadValue(BaseIndex address, JSValueRegs regs) |
| { |
| #if USE(JSVALUE64) |
| load64(address, regs.gpr()); |
| #else |
| if (address.base == regs.payloadGPR() || address.index == regs.payloadGPR()) { |
| // We actually could handle the case where the registers are aliased to both |
| // tag and payload, but we don't for now. |
| RELEASE_ASSERT(address.base != regs.tagGPR()); |
| RELEASE_ASSERT(address.index != regs.tagGPR()); |
| |
| load32(address.withOffset(TagOffset), regs.tagGPR()); |
| load32(address.withOffset(PayloadOffset), regs.payloadGPR()); |
| } else { |
| load32(address.withOffset(PayloadOffset), regs.payloadGPR()); |
| load32(address.withOffset(TagOffset), regs.tagGPR()); |
| } |
| #endif |
| } |
| |
| void loadValue(void* address, JSValueRegs regs) |
| { |
| #if USE(JSVALUE64) |
| load64(address, regs.gpr()); |
| #else |
| move(TrustedImmPtr(address), regs.payloadGPR()); |
| loadValue(Address(regs.payloadGPR()), regs); |
| #endif |
| } |
| |
| // Note that this clobbers offset. |
| void loadProperty(GPRReg object, GPRReg offset, JSValueRegs result); |
| |
| void moveValueRegs(JSValueRegs srcRegs, JSValueRegs destRegs) |
| { |
| #if USE(JSVALUE32_64) |
| if (destRegs.tagGPR() == srcRegs.payloadGPR()) { |
| if (destRegs.payloadGPR() == srcRegs.tagGPR()) { |
| swap(srcRegs.payloadGPR(), srcRegs.tagGPR()); |
| return; |
| } |
| move(srcRegs.payloadGPR(), destRegs.payloadGPR()); |
| move(srcRegs.tagGPR(), destRegs.tagGPR()); |
| return; |
| } |
| move(srcRegs.tagGPR(), destRegs.tagGPR()); |
| move(srcRegs.payloadGPR(), destRegs.payloadGPR()); |
| #else |
| move(srcRegs.gpr(), destRegs.gpr()); |
| #endif |
| } |
| |
| void moveValue(JSValue value, JSValueRegs regs) |
| { |
| #if USE(JSVALUE64) |
| move(Imm64(JSValue::encode(value)), regs.gpr()); |
| #else |
| move(Imm32(value.tag()), regs.tagGPR()); |
| move(Imm32(value.payload()), regs.payloadGPR()); |
| #endif |
| } |
| |
| void moveTrustedValue(JSValue value, JSValueRegs regs) |
| { |
| #if USE(JSVALUE64) |
| move(TrustedImm64(JSValue::encode(value)), regs.gpr()); |
| #else |
| move(TrustedImm32(value.tag()), regs.tagGPR()); |
| move(TrustedImm32(value.payload()), regs.payloadGPR()); |
| #endif |
| } |
| |
| void storeTrustedValue(JSValue value, Address address) |
| { |
| #if USE(JSVALUE64) |
| store64(TrustedImm64(JSValue::encode(value)), address); |
| #else |
| store32(TrustedImm32(value.tag()), address.withOffset(TagOffset)); |
| store32(TrustedImm32(value.payload()), address.withOffset(PayloadOffset)); |
| #endif |
| } |
| |
| void storeTrustedValue(JSValue value, BaseIndex address) |
| { |
| #if USE(JSVALUE64) |
| store64(TrustedImm64(JSValue::encode(value)), address); |
| #else |
| store32(TrustedImm32(value.tag()), address.withOffset(TagOffset)); |
| store32(TrustedImm32(value.payload()), address.withOffset(PayloadOffset)); |
| #endif |
| } |
| |
| Address addressFor(const RegisterAtOffset& entry) |
| { |
| return Address(GPRInfo::callFrameRegister, entry.offset()); |
| } |
| |
| void emitSave(const RegisterAtOffsetList& list) |
| { |
| for (const RegisterAtOffset& entry : list) { |
| if (entry.reg().isGPR()) |
| storePtr(entry.reg().gpr(), addressFor(entry)); |
| else |
| storeDouble(entry.reg().fpr(), addressFor(entry)); |
| } |
| } |
| |
| void emitRestore(const RegisterAtOffsetList& list) |
| { |
| for (const RegisterAtOffset& entry : list) { |
| if (entry.reg().isGPR()) |
| loadPtr(addressFor(entry), entry.reg().gpr()); |
| else |
| loadDouble(addressFor(entry), entry.reg().fpr()); |
| } |
| } |
| |
| void emitSaveCalleeSavesFor(CodeBlock* codeBlock) |
| { |
| ASSERT(codeBlock); |
| |
| const RegisterAtOffsetList* calleeSaves = codeBlock->calleeSaveRegisters(); |
| RegisterSet dontSaveRegisters = RegisterSet(RegisterSet::stackRegisters(), RegisterSet::allFPRs()); |
| unsigned registerCount = calleeSaves->size(); |
| |
| for (unsigned i = 0; i < registerCount; i++) { |
| RegisterAtOffset entry = calleeSaves->at(i); |
| if (dontSaveRegisters.get(entry.reg())) |
| continue; |
| storePtr(entry.reg().gpr(), Address(framePointerRegister, entry.offset())); |
| } |
| } |
| |
| enum RestoreTagRegisterMode { UseExistingTagRegisterContents, CopyBaselineCalleeSavedRegistersFromBaseFrame }; |
| |
| void emitSaveOrCopyCalleeSavesFor(CodeBlock* codeBlock, VirtualRegister offsetVirtualRegister, RestoreTagRegisterMode tagRegisterMode, GPRReg temp) |
| { |
| ASSERT(codeBlock); |
| |
| const RegisterAtOffsetList* calleeSaves = codeBlock->calleeSaveRegisters(); |
| RegisterSet dontSaveRegisters = RegisterSet(RegisterSet::stackRegisters(), RegisterSet::allFPRs()); |
| unsigned registerCount = calleeSaves->size(); |
| |
| #if USE(JSVALUE64) |
| RegisterSet baselineCalleeSaves = RegisterSet::llintBaselineCalleeSaveRegisters(); |
| #endif |
| |
| for (unsigned i = 0; i < registerCount; i++) { |
| RegisterAtOffset entry = calleeSaves->at(i); |
| if (dontSaveRegisters.get(entry.reg())) |
| continue; |
| |
| GPRReg registerToWrite; |
| |
| #if USE(JSVALUE32_64) |
| UNUSED_PARAM(tagRegisterMode); |
| UNUSED_PARAM(temp); |
| #else |
| if (tagRegisterMode == CopyBaselineCalleeSavedRegistersFromBaseFrame && baselineCalleeSaves.get(entry.reg())) { |
| registerToWrite = temp; |
| loadPtr(AssemblyHelpers::Address(GPRInfo::callFrameRegister, entry.offset()), registerToWrite); |
| } else |
| #endif |
| registerToWrite = entry.reg().gpr(); |
| |
| storePtr(registerToWrite, Address(framePointerRegister, offsetVirtualRegister.offsetInBytes() + entry.offset())); |
| } |
| } |
| |
| void emitRestoreCalleeSavesFor(CodeBlock* codeBlock) |
| { |
| ASSERT(codeBlock); |
| |
| const RegisterAtOffsetList* calleeSaves = codeBlock->calleeSaveRegisters(); |
| RegisterSet dontRestoreRegisters = RegisterSet(RegisterSet::stackRegisters(), RegisterSet::allFPRs()); |
| unsigned registerCount = calleeSaves->size(); |
| |
| for (unsigned i = 0; i < registerCount; i++) { |
| RegisterAtOffset entry = calleeSaves->at(i); |
| if (dontRestoreRegisters.get(entry.reg())) |
| continue; |
| loadPtr(Address(framePointerRegister, entry.offset()), entry.reg().gpr()); |
| } |
| } |
| |
| void emitSaveCalleeSaves() |
| { |
| emitSaveCalleeSavesFor(codeBlock()); |
| } |
| |
| void emitSaveThenMaterializeTagRegisters() |
| { |
| #if USE(JSVALUE64) |
| #if CPU(ARM64) |
| pushPair(GPRInfo::numberTagRegister, GPRInfo::notCellMaskRegister); |
| #else |
| push(GPRInfo::numberTagRegister); |
| push(GPRInfo::notCellMaskRegister); |
| #endif |
| emitMaterializeTagCheckRegisters(); |
| #endif |
| } |
| |
| void emitRestoreCalleeSaves() |
| { |
| emitRestoreCalleeSavesFor(codeBlock()); |
| } |
| |
| void emitRestoreSavedTagRegisters() |
| { |
| #if USE(JSVALUE64) |
| #if CPU(ARM64) |
| popPair(GPRInfo::numberTagRegister, GPRInfo::notCellMaskRegister); |
| #else |
| pop(GPRInfo::notCellMaskRegister); |
| pop(GPRInfo::numberTagRegister); |
| #endif |
| #endif |
| } |
| |
| // If you use this, be aware that vmGPR will get trashed. |
| void copyCalleeSavesToVMEntryFrameCalleeSavesBuffer(GPRReg vmGPR) |
| { |
| #if NUMBER_OF_CALLEE_SAVES_REGISTERS > 0 |
| loadPtr(Address(vmGPR, VM::topEntryFrameOffset()), vmGPR); |
| copyCalleeSavesToEntryFrameCalleeSavesBufferImpl(vmGPR); |
| #else |
| UNUSED_PARAM(vmGPR); |
| #endif |
| } |
| |
| void copyCalleeSavesToEntryFrameCalleeSavesBuffer(EntryFrame*& topEntryFrame) |
| { |
| #if NUMBER_OF_CALLEE_SAVES_REGISTERS > 0 |
| const TempRegisterSet& usedRegisters = { RegisterSet::stubUnavailableRegisters() }; |
| GPRReg temp1 = usedRegisters.getFreeGPR(0); |
| loadPtr(&topEntryFrame, temp1); |
| copyCalleeSavesToEntryFrameCalleeSavesBufferImpl(temp1); |
| #else |
| UNUSED_PARAM(topEntryFrame); |
| #endif |
| } |
| |
| void copyCalleeSavesToEntryFrameCalleeSavesBuffer(GPRReg topEntryFrame) |
| { |
| #if NUMBER_OF_CALLEE_SAVES_REGISTERS > 0 |
| copyCalleeSavesToEntryFrameCalleeSavesBufferImpl(topEntryFrame); |
| #else |
| UNUSED_PARAM(topEntryFrame); |
| #endif |
| } |
| |
| void restoreCalleeSavesFromEntryFrameCalleeSavesBuffer(EntryFrame*&); |
| |
| void copyCalleeSavesFromFrameOrRegisterToEntryFrameCalleeSavesBuffer(EntryFrame*& topEntryFrame, const TempRegisterSet& usedRegisters = { RegisterSet::stubUnavailableRegisters() }) |
| { |
| #if NUMBER_OF_CALLEE_SAVES_REGISTERS > 0 |
| GPRReg temp1 = usedRegisters.getFreeGPR(0); |
| GPRReg temp2 = usedRegisters.getFreeGPR(1); |
| FPRReg fpTemp = usedRegisters.getFreeFPR(); |
| ASSERT(temp2 != InvalidGPRReg); |
| |
| ASSERT(codeBlock()); |
| |
| // Copy saved calleeSaves on stack or unsaved calleeSaves in register to vm calleeSave buffer |
| loadPtr(&topEntryFrame, temp1); |
| addPtr(TrustedImm32(EntryFrame::calleeSaveRegistersBufferOffset()), temp1); |
| |
| RegisterAtOffsetList* allCalleeSaves = RegisterSet::vmCalleeSaveRegisterOffsets(); |
| const RegisterAtOffsetList* currentCalleeSaves = codeBlock()->calleeSaveRegisters(); |
| RegisterSet dontCopyRegisters = RegisterSet::stackRegisters(); |
| unsigned registerCount = allCalleeSaves->size(); |
| |
| for (unsigned i = 0; i < registerCount; i++) { |
| RegisterAtOffset entry = allCalleeSaves->at(i); |
| if (dontCopyRegisters.get(entry.reg())) |
| continue; |
| RegisterAtOffset* currentFrameEntry = currentCalleeSaves->find(entry.reg()); |
| |
| if (entry.reg().isGPR()) { |
| GPRReg regToStore; |
| if (currentFrameEntry) { |
| // Load calleeSave from stack into temp register |
| regToStore = temp2; |
| loadPtr(Address(framePointerRegister, currentFrameEntry->offset()), regToStore); |
| } else |
| // Just store callee save directly |
| regToStore = entry.reg().gpr(); |
| |
| storePtr(regToStore, Address(temp1, entry.offset())); |
| } else { |
| FPRReg fpRegToStore; |
| if (currentFrameEntry) { |
| // Load calleeSave from stack into temp register |
| fpRegToStore = fpTemp; |
| loadDouble(Address(framePointerRegister, currentFrameEntry->offset()), fpRegToStore); |
| } else |
| // Just store callee save directly |
| fpRegToStore = entry.reg().fpr(); |
| |
| storeDouble(fpRegToStore, Address(temp1, entry.offset())); |
| } |
| } |
| #else |
| UNUSED_PARAM(topEntryFrame); |
| UNUSED_PARAM(usedRegisters); |
| #endif |
| } |
| |
| void emitMaterializeTagCheckRegisters() |
| { |
| #if USE(JSVALUE64) |
| move(MacroAssembler::TrustedImm64(JSValue::NumberTag), GPRInfo::numberTagRegister); |
| or64(MacroAssembler::TrustedImm32(JSValue::OtherTag), GPRInfo::numberTagRegister, GPRInfo::notCellMaskRegister); |
| #endif |
| } |
| |
| #if CPU(X86_64) |
| static constexpr size_t prologueStackPointerDelta() |
| { |
| // Prologue only saves the framePointerRegister |
| return sizeof(void*); |
| } |
| |
| void emitFunctionPrologue() |
| { |
| push(framePointerRegister); |
| move(stackPointerRegister, framePointerRegister); |
| } |
| |
| void emitFunctionEpilogueWithEmptyFrame() |
| { |
| pop(framePointerRegister); |
| } |
| |
| void emitFunctionEpilogue() |
| { |
| move(framePointerRegister, stackPointerRegister); |
| pop(framePointerRegister); |
| } |
| |
| void preserveReturnAddressAfterCall(GPRReg reg) |
| { |
| pop(reg); |
| } |
| |
| void restoreReturnAddressBeforeReturn(GPRReg reg) |
| { |
| push(reg); |
| } |
| |
| void restoreReturnAddressBeforeReturn(Address address) |
| { |
| push(address); |
| } |
| |
| // dest = base + index << shift. |
| void shiftAndAdd(RegisterID base, RegisterID index, uint8_t shift, RegisterID dest, Optional<RegisterID> optionalScratch = { }) |
| { |
| ASSERT(shift < 32); |
| if (shift <= 3) { |
| x86Lea64(BaseIndex(base, index, static_cast<Scale>(shift)), dest); |
| return; |
| } |
| |
| RegisterID scratch = dest; |
| bool needToPreserveIndexRegister = false; |
| if (base == dest) { |
| scratch = optionalScratch ? optionalScratch.value() : scratchRegister(); |
| if (base == scratch) { |
| scratch = index; |
| needToPreserveIndexRegister = true; |
| } else if (index == scratch) |
| needToPreserveIndexRegister = true; |
| if (needToPreserveIndexRegister) |
| push(index); |
| } |
| |
| move(index, scratch); |
| lshift64(TrustedImm32(shift), scratch); |
| m_assembler.leaq_mr(0, base, scratch, 0, dest); |
| |
| if (needToPreserveIndexRegister) |
| pop(index); |
| } |
| |
| #endif // CPU(X86_64) |
| |
| #if CPU(ARM_THUMB2) || CPU(ARM64) |
| static constexpr size_t prologueStackPointerDelta() |
| { |
| // Prologue saves the framePointerRegister and linkRegister |
| return 2 * sizeof(void*); |
| } |
| |
| void emitFunctionPrologue() |
| { |
| tagReturnAddress(); |
| pushPair(framePointerRegister, linkRegister); |
| move(stackPointerRegister, framePointerRegister); |
| } |
| |
| void emitFunctionEpilogueWithEmptyFrame() |
| { |
| popPair(framePointerRegister, linkRegister); |
| } |
| |
| void emitFunctionEpilogue() |
| { |
| move(framePointerRegister, stackPointerRegister); |
| emitFunctionEpilogueWithEmptyFrame(); |
| } |
| |
| ALWAYS_INLINE void preserveReturnAddressAfterCall(RegisterID reg) |
| { |
| move(linkRegister, reg); |
| } |
| |
| ALWAYS_INLINE void restoreReturnAddressBeforeReturn(RegisterID reg) |
| { |
| move(reg, linkRegister); |
| } |
| |
| ALWAYS_INLINE void restoreReturnAddressBeforeReturn(Address address) |
| { |
| loadPtr(address, linkRegister); |
| } |
| |
| #if CPU(ARM64) |
| // dest = base + index << shift. |
| void shiftAndAdd(RegisterID base, RegisterID index, uint8_t shift, RegisterID dest, Optional<RegisterID> = { }) |
| { |
| ASSERT(shift < 32); |
| ASSERT(base != index); |
| getEffectiveAddress(BaseIndex(base, index, static_cast<Scale>(shift)), dest); |
| } |
| #endif // CPU(ARM64) |
| #endif |
| |
| #if CPU(MIPS) |
| static constexpr size_t prologueStackPointerDelta() |
| { |
| // Prologue saves the framePointerRegister and returnAddressRegister |
| return 2 * sizeof(void*); |
| } |
| |
| void emitFunctionPrologue() |
| { |
| pushPair(framePointerRegister, returnAddressRegister); |
| move(stackPointerRegister, framePointerRegister); |
| } |
| |
| void emitFunctionEpilogueWithEmptyFrame() |
| { |
| popPair(framePointerRegister, returnAddressRegister); |
| } |
| |
| void emitFunctionEpilogue() |
| { |
| move(framePointerRegister, stackPointerRegister); |
| emitFunctionEpilogueWithEmptyFrame(); |
| } |
| |
| ALWAYS_INLINE void preserveReturnAddressAfterCall(RegisterID reg) |
| { |
| move(returnAddressRegister, reg); |
| } |
| |
| ALWAYS_INLINE void restoreReturnAddressBeforeReturn(RegisterID reg) |
| { |
| move(reg, returnAddressRegister); |
| } |
| |
| ALWAYS_INLINE void restoreReturnAddressBeforeReturn(Address address) |
| { |
| loadPtr(address, returnAddressRegister); |
| } |
| #endif |
| |
| void emitGetFromCallFrameHeaderPtr(VirtualRegister entry, GPRReg to, GPRReg from = GPRInfo::callFrameRegister) |
| { |
| ASSERT(entry.isHeader()); |
| loadPtr(Address(from, entry.offset() * sizeof(Register)), to); |
| } |
| void emitGetFromCallFrameHeader32(VirtualRegister entry, GPRReg to, GPRReg from = GPRInfo::callFrameRegister) |
| { |
| ASSERT(entry.isHeader()); |
| load32(Address(from, entry.offset() * sizeof(Register)), to); |
| } |
| #if USE(JSVALUE64) |
| void emitGetFromCallFrameHeader64(VirtualRegister entry, GPRReg to, GPRReg from = GPRInfo::callFrameRegister) |
| { |
| ASSERT(entry.isHeader()); |
| load64(Address(from, entry.offset() * sizeof(Register)), to); |
| } |
| #endif // USE(JSVALUE64) |
| void emitPutToCallFrameHeader(GPRReg from, VirtualRegister entry) |
| { |
| ASSERT(entry.isHeader()); |
| storePtr(from, Address(GPRInfo::callFrameRegister, entry.offset() * sizeof(Register))); |
| } |
| |
| void emitPutToCallFrameHeader(void* value, VirtualRegister entry) |
| { |
| ASSERT(entry.isHeader()); |
| storePtr(TrustedImmPtr(value), Address(GPRInfo::callFrameRegister, entry.offset() * sizeof(Register))); |
| } |
| |
| void emitGetCallerFrameFromCallFrameHeaderPtr(RegisterID to) |
| { |
| loadPtr(Address(GPRInfo::callFrameRegister, CallFrame::callerFrameOffset()), to); |
| } |
| void emitPutCallerFrameToCallFrameHeader(RegisterID from) |
| { |
| storePtr(from, Address(GPRInfo::callFrameRegister, CallFrame::callerFrameOffset())); |
| } |
| |
| void emitPutReturnPCToCallFrameHeader(RegisterID from) |
| { |
| storePtr(from, Address(GPRInfo::callFrameRegister, CallFrame::returnPCOffset())); |
| } |
| void emitPutReturnPCToCallFrameHeader(TrustedImmPtr from) |
| { |
| storePtr(from, Address(GPRInfo::callFrameRegister, CallFrame::returnPCOffset())); |
| } |
| |
| // emitPutToCallFrameHeaderBeforePrologue() and related are used to access callee frame header |
| // fields before the code from emitFunctionPrologue() has executed. |
| // First, the access is via the stack pointer. Second, the address calculation must also take |
| // into account that the stack pointer may not have been adjusted down for the return PC and/or |
| // caller's frame pointer. On some platforms, the callee is responsible for pushing the |
| // "link register" containing the return address in the function prologue. |
| #if USE(JSVALUE64) |
| void emitPutToCallFrameHeaderBeforePrologue(GPRReg from, VirtualRegister entry) |
| { |
| storePtr(from, Address(stackPointerRegister, entry.offset() * static_cast<ptrdiff_t>(sizeof(Register)) - prologueStackPointerDelta())); |
| } |
| #else |
| void emitPutPayloadToCallFrameHeaderBeforePrologue(GPRReg from, VirtualRegister entry) |
| { |
| storePtr(from, Address(stackPointerRegister, entry.offset() * static_cast<ptrdiff_t>(sizeof(Register)) - prologueStackPointerDelta() + OBJECT_OFFSETOF(EncodedValueDescriptor, asBits.payload))); |
| } |
| |
| void emitPutTagToCallFrameHeaderBeforePrologue(TrustedImm32 tag, VirtualRegister entry) |
| { |
| storePtr(tag, Address(stackPointerRegister, entry.offset() * static_cast<ptrdiff_t>(sizeof(Register)) - prologueStackPointerDelta() + OBJECT_OFFSETOF(EncodedValueDescriptor, asBits.tag))); |
| } |
| #endif |
| |
| JumpList branchIfNotEqual(JSValueRegs regs, JSValue value) |
| { |
| #if USE(JSVALUE64) |
| return branch64(NotEqual, regs.gpr(), TrustedImm64(JSValue::encode(value))); |
| #else |
| JumpList result; |
| result.append(branch32(NotEqual, regs.tagGPR(), TrustedImm32(value.tag()))); |
| if (value.isEmpty() || value.isUndefinedOrNull()) |
| return result; // These don't have anything interesting in the payload. |
| result.append(branch32(NotEqual, regs.payloadGPR(), TrustedImm32(value.payload()))); |
| return result; |
| #endif |
| } |
| |
| Jump branchIfEqual(JSValueRegs regs, JSValue value) |
| { |
| #if USE(JSVALUE64) |
| return branch64(Equal, regs.gpr(), TrustedImm64(JSValue::encode(value))); |
| #else |
| Jump notEqual; |
| // These don't have anything interesting in the payload. |
| if (!value.isEmpty() && !value.isUndefinedOrNull()) |
| notEqual = branch32(NotEqual, regs.payloadGPR(), TrustedImm32(value.payload())); |
| Jump result = branch32(Equal, regs.tagGPR(), TrustedImm32(value.tag())); |
| if (notEqual.isSet()) |
| notEqual.link(this); |
| return result; |
| #endif |
| } |
| |
| Jump branchIfNotCell(GPRReg reg, TagRegistersMode mode = HaveTagRegisters) |
| { |
| #if USE(JSVALUE64) |
| if (mode == HaveTagRegisters) |
| return branchTest64(NonZero, reg, GPRInfo::notCellMaskRegister); |
| return branchTest64(NonZero, reg, TrustedImm64(JSValue::NotCellMask)); |
| #else |
| UNUSED_PARAM(mode); |
| return branch32(MacroAssembler::NotEqual, reg, TrustedImm32(JSValue::CellTag)); |
| #endif |
| } |
| |
| Jump branchIfNotCell(JSValueRegs regs, TagRegistersMode mode = HaveTagRegisters) |
| { |
| #if USE(JSVALUE64) |
| return branchIfNotCell(regs.gpr(), mode); |
| #else |
| return branchIfNotCell(regs.tagGPR(), mode); |
| #endif |
| } |
| |
| Jump branchIfCell(GPRReg reg, TagRegistersMode mode = HaveTagRegisters) |
| { |
| #if USE(JSVALUE64) |
| if (mode == HaveTagRegisters) |
| return branchTest64(Zero, reg, GPRInfo::notCellMaskRegister); |
| return branchTest64(Zero, reg, TrustedImm64(JSValue::NotCellMask)); |
| #else |
| UNUSED_PARAM(mode); |
| return branch32(MacroAssembler::Equal, reg, TrustedImm32(JSValue::CellTag)); |
| #endif |
| } |
| Jump branchIfCell(JSValueRegs regs, TagRegistersMode mode = HaveTagRegisters) |
| { |
| #if USE(JSVALUE64) |
| return branchIfCell(regs.gpr(), mode); |
| #else |
| return branchIfCell(regs.tagGPR(), mode); |
| #endif |
| } |
| |
| Jump branchIfOther(JSValueRegs regs, GPRReg tempGPR) |
| { |
| #if USE(JSVALUE64) |
| move(regs.gpr(), tempGPR); |
| and64(TrustedImm32(~JSValue::UndefinedTag), tempGPR); |
| return branch64(Equal, tempGPR, TrustedImm64(JSValue::ValueNull)); |
| #else |
| or32(TrustedImm32(1), regs.tagGPR(), tempGPR); |
| return branch32(Equal, tempGPR, TrustedImm32(JSValue::NullTag)); |
| #endif |
| } |
| |
| Jump branchIfNotOther(JSValueRegs regs, GPRReg tempGPR) |
| { |
| #if USE(JSVALUE64) |
| move(regs.gpr(), tempGPR); |
| and64(TrustedImm32(~JSValue::UndefinedTag), tempGPR); |
| return branch64(NotEqual, tempGPR, TrustedImm64(JSValue::ValueNull)); |
| #else |
| or32(TrustedImm32(1), regs.tagGPR(), tempGPR); |
| return branch32(NotEqual, tempGPR, TrustedImm32(JSValue::NullTag)); |
| #endif |
| } |
| |
| Jump branchIfInt32(GPRReg gpr, TagRegistersMode mode = HaveTagRegisters) |
| { |
| #if USE(JSVALUE64) |
| if (mode == HaveTagRegisters) |
| return branch64(AboveOrEqual, gpr, GPRInfo::numberTagRegister); |
| return branch64(AboveOrEqual, gpr, TrustedImm64(JSValue::NumberTag)); |
| #else |
| UNUSED_PARAM(mode); |
| return branch32(Equal, gpr, TrustedImm32(JSValue::Int32Tag)); |
| #endif |
| } |
| |
| Jump branchIfInt32(JSValueRegs regs, TagRegistersMode mode = HaveTagRegisters) |
| { |
| #if USE(JSVALUE64) |
| return branchIfInt32(regs.gpr(), mode); |
| #else |
| return branchIfInt32(regs.tagGPR(), mode); |
| #endif |
| } |
| |
| Jump branchIfNotInt32(GPRReg gpr, TagRegistersMode mode = HaveTagRegisters) |
| { |
| #if USE(JSVALUE64) |
| if (mode == HaveTagRegisters) |
| return branch64(Below, gpr, GPRInfo::numberTagRegister); |
| return branch64(Below, gpr, TrustedImm64(JSValue::NumberTag)); |
| #else |
| UNUSED_PARAM(mode); |
| return branch32(NotEqual, gpr, TrustedImm32(JSValue::Int32Tag)); |
| #endif |
| } |
| |
| Jump branchIfNotInt32(JSValueRegs regs, TagRegistersMode mode = HaveTagRegisters) |
| { |
| #if USE(JSVALUE64) |
| return branchIfNotInt32(regs.gpr(), mode); |
| #else |
| return branchIfNotInt32(regs.tagGPR(), mode); |
| #endif |
| } |
| |
| // Note that the tempGPR is not used in 64-bit mode. |
| Jump branchIfNumber(JSValueRegs regs, GPRReg tempGPR, TagRegistersMode mode = HaveTagRegisters) |
| { |
| #if USE(JSVALUE64) |
| UNUSED_PARAM(tempGPR); |
| return branchIfNumber(regs.gpr(), mode); |
| #else |
| UNUSED_PARAM(mode); |
| ASSERT(tempGPR != InvalidGPRReg); |
| add32(TrustedImm32(1), regs.tagGPR(), tempGPR); |
| return branch32(Below, tempGPR, TrustedImm32(JSValue::LowestTag + 1)); |
| #endif |
| } |
| |
| #if USE(JSVALUE64) |
| Jump branchIfNumber(GPRReg gpr, TagRegistersMode mode = HaveTagRegisters) |
| { |
| if (mode == HaveTagRegisters) |
| return branchTest64(NonZero, gpr, GPRInfo::numberTagRegister); |
| return branchTest64(NonZero, gpr, TrustedImm64(JSValue::NumberTag)); |
| } |
| #endif |
| |
| // Note that the tempGPR is not used in 64-bit mode. |
| Jump branchIfNotNumber(JSValueRegs regs, GPRReg tempGPR, TagRegistersMode mode = HaveTagRegisters) |
| { |
| #if USE(JSVALUE64) |
| UNUSED_PARAM(tempGPR); |
| return branchIfNotNumber(regs.gpr(), mode); |
| #else |
| UNUSED_PARAM(mode); |
| add32(TrustedImm32(1), regs.tagGPR(), tempGPR); |
| return branch32(AboveOrEqual, tempGPR, TrustedImm32(JSValue::LowestTag + 1)); |
| #endif |
| } |
| |
| #if USE(JSVALUE64) |
| Jump branchIfNotNumber(GPRReg gpr, TagRegistersMode mode = HaveTagRegisters) |
| { |
| if (mode == HaveTagRegisters) |
| return branchTest64(Zero, gpr, GPRInfo::numberTagRegister); |
| return branchTest64(Zero, gpr, TrustedImm64(JSValue::NumberTag)); |
| } |
| #endif |
| |
| Jump branchIfNotDoubleKnownNotInt32(JSValueRegs regs, TagRegistersMode mode = HaveTagRegisters) |
| { |
| #if USE(JSVALUE64) |
| if (mode == HaveTagRegisters) |
| return branchTest64(Zero, regs.gpr(), GPRInfo::numberTagRegister); |
| return branchTest64(Zero, regs.gpr(), TrustedImm64(JSValue::NumberTag)); |
| #else |
| UNUSED_PARAM(mode); |
| return branch32(AboveOrEqual, regs.tagGPR(), TrustedImm32(JSValue::LowestTag)); |
| #endif |
| } |
| |
| // Note that the tempGPR is not used in 32-bit mode. |
| Jump branchIfBoolean(GPRReg gpr, GPRReg tempGPR) |
| { |
| #if USE(JSVALUE64) |
| ASSERT(tempGPR != InvalidGPRReg); |
| move(gpr, tempGPR); |
| xor64(TrustedImm32(JSValue::ValueFalse), tempGPR); |
| return branchTest64(Zero, tempGPR, TrustedImm32(static_cast<int32_t>(~1))); |
| #else |
| UNUSED_PARAM(tempGPR); |
| return branch32(Equal, gpr, TrustedImm32(JSValue::BooleanTag)); |
| #endif |
| } |
| |
| // Note that the tempGPR is not used in 32-bit mode. |
| Jump branchIfBoolean(JSValueRegs regs, GPRReg tempGPR) |
| { |
| #if USE(JSVALUE64) |
| return branchIfBoolean(regs.gpr(), tempGPR); |
| #else |
| return branchIfBoolean(regs.tagGPR(), tempGPR); |
| #endif |
| } |
| |
| // Note that the tempGPR is not used in 32-bit mode. |
| Jump branchIfNotBoolean(GPRReg gpr, GPRReg tempGPR) |
| { |
| #if USE(JSVALUE64) |
| ASSERT(tempGPR != InvalidGPRReg); |
| move(gpr, tempGPR); |
| xor64(TrustedImm32(JSValue::ValueFalse), tempGPR); |
| return branchTest64(NonZero, tempGPR, TrustedImm32(static_cast<int32_t>(~1))); |
| #else |
| UNUSED_PARAM(tempGPR); |
| return branch32(NotEqual, gpr, TrustedImm32(JSValue::BooleanTag)); |
| #endif |
| } |
| |
| // Note that the tempGPR is not used in 32-bit mode. |
| Jump branchIfNotBoolean(JSValueRegs regs, GPRReg tempGPR) |
| { |
| #if USE(JSVALUE64) |
| return branchIfNotBoolean(regs.gpr(), tempGPR); |
| #else |
| return branchIfNotBoolean(regs.tagGPR(), tempGPR); |
| #endif |
| } |
| |
| #if USE(BIGINT32) |
| Jump branchIfBigInt32(GPRReg gpr, GPRReg tempGPR, TagRegistersMode mode = HaveTagRegisters) |
| { |
| ASSERT(tempGPR != InvalidGPRReg); |
| if (mode == HaveTagRegisters && gpr != tempGPR) { |
| static_assert(JSValue::BigInt32Mask == JSValue::NumberTag + JSValue::BigInt32Tag); |
| add64(TrustedImm32(JSValue::BigInt32Tag), GPRInfo::numberTagRegister, tempGPR); |
| and64(gpr, tempGPR); |
| return branch64(Equal, tempGPR, TrustedImm32(JSValue::BigInt32Tag)); |
| } |
| move(gpr, tempGPR); |
| and64(TrustedImm64(JSValue::BigInt32Mask), tempGPR); |
| return branch64(Equal, tempGPR, TrustedImm32(JSValue::BigInt32Tag)); |
| } |
| Jump branchIfNotBigInt32(GPRReg gpr, GPRReg tempGPR, TagRegistersMode mode = HaveTagRegisters) |
| { |
| ASSERT(tempGPR != InvalidGPRReg); |
| if (mode == HaveTagRegisters && gpr != tempGPR) { |
| static_assert(JSValue::BigInt32Mask == JSValue::NumberTag + JSValue::BigInt32Tag); |
| add64(TrustedImm32(JSValue::BigInt32Tag), GPRInfo::numberTagRegister, tempGPR); |
| and64(gpr, tempGPR); |
| return branch64(NotEqual, tempGPR, TrustedImm32(JSValue::BigInt32Tag)); |
| } |
| move(gpr, tempGPR); |
| and64(TrustedImm64(JSValue::BigInt32Mask), tempGPR); |
| return branch64(NotEqual, tempGPR, TrustedImm32(JSValue::BigInt32Tag)); |
| } |
| Jump branchIfBigInt32(JSValueRegs regs, GPRReg tempGPR, TagRegistersMode mode = HaveTagRegisters) |
| { |
| return branchIfBigInt32(regs.gpr(), tempGPR, mode); |
| } |
| Jump branchIfNotBigInt32(JSValueRegs regs, GPRReg tempGPR, TagRegistersMode mode = HaveTagRegisters) |
| { |
| return branchIfNotBigInt32(regs.gpr(), tempGPR, mode); |
| } |
| #endif // USE(BIGINT32) |
| |
| // FIXME: rename these to make it clear that they require their input to be a cell. |
| Jump branchIfObject(GPRReg cellGPR) |
| { |
| return branch8( |
| AboveOrEqual, Address(cellGPR, JSCell::typeInfoTypeOffset()), TrustedImm32(ObjectType)); |
| } |
| |
| Jump branchIfNotObject(GPRReg cellGPR) |
| { |
| return branch8( |
| Below, Address(cellGPR, JSCell::typeInfoTypeOffset()), TrustedImm32(ObjectType)); |
| } |
| |
| // Note that first and last are inclusive. |
| Jump branchIfType(GPRReg cellGPR, JSTypeRange range) |
| { |
| if (range.last == range.first) |
| return branch8(Equal, Address(cellGPR, JSCell::typeInfoTypeOffset()), TrustedImm32(range.first)); |
| |
| ASSERT(range.last > range.first); |
| GPRReg scratch = scratchRegister(); |
| load8(Address(cellGPR, JSCell::typeInfoTypeOffset()), scratch); |
| sub32(TrustedImm32(range.first), scratch); |
| return branch32(BelowOrEqual, scratch, TrustedImm32(range.last - range.first)); |
| } |
| |
| Jump branchIfType(GPRReg cellGPR, JSType type) |
| { |
| return branchIfType(cellGPR, JSTypeRange { type, type }); |
| } |
| |
| Jump branchIfNotType(GPRReg cellGPR, JSTypeRange range) |
| { |
| if (range.last == range.first) |
| return branch8(NotEqual, Address(cellGPR, JSCell::typeInfoTypeOffset()), TrustedImm32(range.first)); |
| |
| ASSERT(range.last > range.first); |
| GPRReg scratch = scratchRegister(); |
| load8(Address(cellGPR, JSCell::typeInfoTypeOffset()), scratch); |
| sub32(TrustedImm32(range.first), scratch); |
| return branch32(Above, scratch, TrustedImm32(range.last - range.first)); |
| } |
| |
| Jump branchIfNotType(GPRReg cellGPR, JSType type) |
| { |
| return branchIfNotType(cellGPR, JSTypeRange { type, type }); |
| } |
| |
| // FIXME: rename these to make it clear that they require their input to be a cell. |
| Jump branchIfString(GPRReg cellGPR) { return branchIfType(cellGPR, StringType); } |
| Jump branchIfNotString(GPRReg cellGPR) { return branchIfNotType(cellGPR, StringType); } |
| Jump branchIfSymbol(GPRReg cellGPR) { return branchIfType(cellGPR, SymbolType); } |
| Jump branchIfNotSymbol(GPRReg cellGPR) { return branchIfNotType(cellGPR, SymbolType); } |
| Jump branchIfHeapBigInt(GPRReg cellGPR) { return branchIfType(cellGPR, HeapBigIntType); } |
| Jump branchIfNotHeapBigInt(GPRReg cellGPR) { return branchIfNotType(cellGPR, HeapBigIntType); } |
| Jump branchIfFunction(GPRReg cellGPR) { return branchIfType(cellGPR, JSFunctionType); } |
| Jump branchIfNotFunction(GPRReg cellGPR) { return branchIfNotType(cellGPR, JSFunctionType); } |
| |
| void isEmpty(GPRReg gpr, GPRReg dst) |
| { |
| #if USE(JSVALUE64) |
| test64(Zero, gpr, gpr, dst); |
| #else |
| compare32(Equal, gpr, TrustedImm32(JSValue::EmptyValueTag), dst); |
| #endif |
| } |
| |
| void isNotEmpty(GPRReg gpr, GPRReg dst) |
| { |
| #if USE(JSVALUE64) |
| test64(NonZero, gpr, gpr, dst); |
| #else |
| compare32(NotEqual, gpr, TrustedImm32(JSValue::EmptyValueTag), dst); |
| #endif |
| } |
| |
| Jump branchIfEmpty(GPRReg gpr) |
| { |
| #if USE(JSVALUE64) |
| return branchTest64(Zero, gpr); |
| #else |
| return branch32(Equal, gpr, TrustedImm32(JSValue::EmptyValueTag)); |
| #endif |
| } |
| |
| Jump branchIfEmpty(JSValueRegs regs) |
| { |
| #if USE(JSVALUE64) |
| return branchIfEmpty(regs.gpr()); |
| #else |
| return branchIfEmpty(regs.tagGPR()); |
| #endif |
| } |
| |
| Jump branchIfNotEmpty(GPRReg gpr) |
| { |
| #if USE(JSVALUE64) |
| return branchTest64(NonZero, gpr); |
| #else |
| return branch32(NotEqual, gpr, TrustedImm32(JSValue::EmptyValueTag)); |
| #endif |
| } |
| |
| Jump branchIfNotEmpty(JSValueRegs regs) |
| { |
| #if USE(JSVALUE64) |
| return branchIfNotEmpty(regs.gpr()); |
| #else |
| return branchIfNotEmpty(regs.tagGPR()); |
| #endif |
| } |
| |
| // Note that this function does not respect MasqueradesAsUndefined. |
| Jump branchIfUndefined(GPRReg gpr) |
| { |
| #if USE(JSVALUE64) |
| return branch64(Equal, gpr, TrustedImm64(JSValue::encode(jsUndefined()))); |
| #else |
| return branch32(Equal, gpr, TrustedImm32(JSValue::UndefinedTag)); |
| #endif |
| } |
| |
| // Note that this function does not respect MasqueradesAsUndefined. |
| Jump branchIfUndefined(JSValueRegs regs) |
| { |
| #if USE(JSVALUE64) |
| return branchIfUndefined(regs.gpr()); |
| #else |
| return branchIfUndefined(regs.tagGPR()); |
| #endif |
| } |
| |
| // Note that this function does not respect MasqueradesAsUndefined. |
| Jump branchIfNotUndefined(GPRReg gpr) |
| { |
| #if USE(JSVALUE64) |
| return branch64(NotEqual, gpr, TrustedImm64(JSValue::encode(jsUndefined()))); |
| #else |
| return branch32(NotEqual, gpr, TrustedImm32(JSValue::UndefinedTag)); |
| #endif |
| } |
| |
| // Note that this function does not respect MasqueradesAsUndefined. |
| Jump branchIfNotUndefined(JSValueRegs regs) |
| { |
| #if USE(JSVALUE64) |
| return branchIfNotUndefined(regs.gpr()); |
| #else |
| return branchIfNotUndefined(regs.tagGPR()); |
| #endif |
| } |
| |
| Jump branchIfNull(GPRReg gpr) |
| { |
| #if USE(JSVALUE64) |
| return branch64(Equal, gpr, TrustedImm64(JSValue::encode(jsNull()))); |
| #else |
| return branch32(Equal, gpr, TrustedImm32(JSValue::NullTag)); |
| #endif |
| } |
| |
| Jump branchIfNull(JSValueRegs regs) |
| { |
| #if USE(JSVALUE64) |
| return branchIfNull(regs.gpr()); |
| #else |
| return branchIfNull(regs.tagGPR()); |
| #endif |
| } |
| |
| Jump branchIfNotNull(GPRReg gpr) |
| { |
| #if USE(JSVALUE64) |
| return branch64(NotEqual, gpr, TrustedImm64(JSValue::encode(jsNull()))); |
| #else |
| return branch32(NotEqual, gpr, TrustedImm32(JSValue::NullTag)); |
| #endif |
| } |
| |
| Jump branchIfNotNull(JSValueRegs regs) |
| { |
| #if USE(JSVALUE64) |
| return branchIfNotNull(regs.gpr()); |
| #else |
| return branchIfNotNull(regs.tagGPR()); |
| #endif |
| } |
| |
| template<typename T> |
| Jump branchStructure(RelationalCondition condition, T leftHandSide, Structure* structure) |
| { |
| #if USE(JSVALUE64) |
| return branch32(condition, leftHandSide, TrustedImm32(structure->id())); |
| #else |
| return branchPtr(condition, leftHandSide, TrustedImmPtr(structure)); |
| #endif |
| } |
| |
| Jump branchIfFastTypedArray(GPRReg baseGPR); |
| Jump branchIfNotFastTypedArray(GPRReg baseGPR); |
| |
| Jump branchIfNaN(FPRReg fpr) |
| { |
| return branchDouble(DoubleNotEqualOrUnordered, fpr, fpr); |
| } |
| |
| Jump branchIfNotNaN(FPRReg fpr) |
| { |
| return branchDouble(DoubleEqualAndOrdered, fpr, fpr); |
| } |
| |
| Jump branchIfRopeStringImpl(GPRReg stringImplGPR) |
| { |
| return branchTestPtr(NonZero, stringImplGPR, TrustedImm32(JSString::isRopeInPointer)); |
| } |
| |
| Jump branchIfNotRopeStringImpl(GPRReg stringImplGPR) |
| { |
| return branchTestPtr(Zero, stringImplGPR, TrustedImm32(JSString::isRopeInPointer)); |
| } |
| |
| static Address addressForByteOffset(ptrdiff_t byteOffset) |
| { |
| return Address(GPRInfo::callFrameRegister, byteOffset); |
| } |
| static Address addressFor(VirtualRegister virtualRegister, GPRReg baseReg) |
| { |
| ASSERT(virtualRegister.isValid()); |
| return Address(baseReg, virtualRegister.offset() * sizeof(Register)); |
| } |
| static Address addressFor(VirtualRegister virtualRegister) |
| { |
| // NB. It's tempting on some architectures to sometimes use an offset from the stack |
| // register because for some offsets that will encode to a smaller instruction. But we |
| // cannot do this. We use this in places where the stack pointer has been moved to some |
| // unpredictable location. |
| ASSERT(virtualRegister.isValid()); |
| return Address(GPRInfo::callFrameRegister, virtualRegister.offset() * sizeof(Register)); |
| } |
| static Address addressFor(Operand operand) |
| { |
| ASSERT(!operand.isTmp()); |
| return addressFor(operand.virtualRegister()); |
| } |
| |
| static Address tagFor(VirtualRegister virtualRegister, GPRReg baseGPR) |
| { |
| ASSERT(virtualRegister.isValid()); |
| return Address(baseGPR, virtualRegister.offset() * sizeof(Register) + TagOffset); |
| } |
| static Address tagFor(VirtualRegister virtualRegister) |
| { |
| ASSERT(virtualRegister.isValid()); |
| return Address(GPRInfo::callFrameRegister, virtualRegister.offset() * sizeof(Register) + TagOffset); |
| } |
| static Address tagFor(Operand operand) |
| { |
| ASSERT(!operand.isTmp()); |
| return tagFor(operand.virtualRegister()); |
| } |
| |
| static Address payloadFor(VirtualRegister virtualRegister, GPRReg baseGPR) |
| { |
| ASSERT(virtualRegister.isValid()); |
| return Address(baseGPR, virtualRegister.offset() * sizeof(Register) + PayloadOffset); |
| } |
| static Address payloadFor(VirtualRegister virtualRegister) |
| { |
| ASSERT(virtualRegister.isValid()); |
| return Address(GPRInfo::callFrameRegister, virtualRegister.offset() * sizeof(Register) + PayloadOffset); |
| } |
| static Address payloadFor(Operand operand) |
| { |
| ASSERT(!operand.isTmp()); |
| return payloadFor(operand.virtualRegister()); |
| } |
| |
| // Access to our fixed callee CallFrame. |
| static Address calleeFrameSlot(VirtualRegister slot) |
| { |
| ASSERT(slot.offset() >= CallerFrameAndPC::sizeInRegisters); |
| return Address(stackPointerRegister, sizeof(Register) * (slot - CallerFrameAndPC::sizeInRegisters).offset()); |
| } |
| |
| // Access to our fixed callee CallFrame. |
| static Address calleeArgumentSlot(int argument) |
| { |
| return calleeFrameSlot(virtualRegisterForArgumentIncludingThis(argument)); |
| } |
| |
| static Address calleeFrameTagSlot(VirtualRegister slot) |
| { |
| return calleeFrameSlot(slot).withOffset(TagOffset); |
| } |
| |
| static Address calleeFramePayloadSlot(VirtualRegister slot) |
| { |
| return calleeFrameSlot(slot).withOffset(PayloadOffset); |
| } |
| |
| static Address calleeArgumentTagSlot(int argument) |
| { |
| return calleeArgumentSlot(argument).withOffset(TagOffset); |
| } |
| |
| static Address calleeArgumentPayloadSlot(int argument) |
| { |
| return calleeArgumentSlot(argument).withOffset(PayloadOffset); |
| } |
| |
| static Address calleeFrameCallerFrame() |
| { |
| return calleeFrameSlot(VirtualRegister(0)).withOffset(CallFrame::callerFrameOffset()); |
| } |
| |
| static GPRReg selectScratchGPR(RegisterSet preserved) |
| { |
| GPRReg registers[] = { |
| GPRInfo::regT0, |
| GPRInfo::regT1, |
| GPRInfo::regT2, |
| GPRInfo::regT3, |
| GPRInfo::regT4, |
| GPRInfo::regT5, |
| }; |
| |
| for (GPRReg reg : registers) { |
| if (!preserved.contains(reg)) |
| return reg; |
| } |
| RELEASE_ASSERT_NOT_REACHED(); |
| return InvalidGPRReg; |
| } |
| |
| template<typename... Regs> |
| static GPRReg selectScratchGPR(Regs... args) |
| { |
| RegisterSet set; |
| constructRegisterSet(set, args...); |
| return selectScratchGPR(set); |
| } |
| |
| static void constructRegisterSet(RegisterSet&) |
| { |
| } |
| |
| template<typename... Regs> |
| static void constructRegisterSet(RegisterSet& set, JSValueRegs regs, Regs... args) |
| { |
| if (regs.tagGPR() != InvalidGPRReg) |
| set.set(regs.tagGPR()); |
| if (regs.payloadGPR() != InvalidGPRReg) |
| set.set(regs.payloadGPR()); |
| constructRegisterSet(set, args...); |
| } |
| |
| template<typename... Regs> |
| static void constructRegisterSet(RegisterSet& set, GPRReg reg, Regs... args) |
| { |
| if (reg != InvalidGPRReg) |
| set.set(reg); |
| constructRegisterSet(set, args...); |
| } |
| |
| // Add a debug call. This call has no effect on JIT code execution state. |
| void debugCall(VM&, V_DebugOperation_EPP function, void* argument); |
| |
| // These methods JIT generate dynamic, debug-only checks - akin to ASSERTs. |
| #if ASSERT_ENABLED |
| void jitAssertIsInt32(GPRReg); |
| void jitAssertIsJSInt32(GPRReg); |
| void jitAssertIsJSNumber(GPRReg); |
| void jitAssertIsJSDouble(GPRReg); |
| void jitAssertIsCell(GPRReg); |
| void jitAssertHasValidCallFrame(); |
| void jitAssertIsNull(GPRReg); |
| void jitAssertTagsInPlace(); |
| void jitAssertArgumentCountSane(); |
| #else |
| void jitAssertIsInt32(GPRReg) { } |
| void jitAssertIsJSInt32(GPRReg) { } |
| void jitAssertIsJSNumber(GPRReg) { } |
| void jitAssertIsJSDouble(GPRReg) { } |
| void jitAssertIsCell(GPRReg) { } |
| void jitAssertHasValidCallFrame() { } |
| void jitAssertIsNull(GPRReg) { } |
| void jitAssertTagsInPlace() { } |
| void jitAssertArgumentCountSane() { } |
| #endif |
| |
| void jitReleaseAssertNoException(VM&); |
| |
| void incrementSuperSamplerCount(); |
| void decrementSuperSamplerCount(); |
| |
| void purifyNaN(FPRReg); |
| |
| // These methods convert between doubles, and doubles boxed and JSValues. |
| #if USE(JSVALUE64) |
| GPRReg boxDouble(FPRReg fpr, GPRReg gpr, TagRegistersMode mode = HaveTagRegisters) |
| { |
| moveDoubleTo64(fpr, gpr); |
| if (mode == DoNotHaveTagRegisters) |
| sub64(TrustedImm64(JSValue::NumberTag), gpr); |
| else { |
| sub64(GPRInfo::numberTagRegister, gpr); |
| jitAssertIsJSDouble(gpr); |
| } |
| return gpr; |
| } |
| FPRReg unboxDoubleWithoutAssertions(GPRReg gpr, GPRReg resultGPR, FPRReg fpr) |
| { |
| add64(GPRInfo::numberTagRegister, gpr, resultGPR); |
| move64ToDouble(resultGPR, fpr); |
| return fpr; |
| } |
| FPRReg unboxDouble(GPRReg gpr, GPRReg resultGPR, FPRReg fpr) |
| { |
| jitAssertIsJSDouble(gpr); |
| return unboxDoubleWithoutAssertions(gpr, resultGPR, fpr); |
| } |
| |
| void boxDouble(FPRReg fpr, JSValueRegs regs, TagRegistersMode mode = HaveTagRegisters) |
| { |
| boxDouble(fpr, regs.gpr(), mode); |
| } |
| |
| void unboxDoubleNonDestructive(JSValueRegs regs, FPRReg destFPR, GPRReg resultGPR, FPRReg) |
| { |
| unboxDouble(regs.payloadGPR(), resultGPR, destFPR); |
| } |
| |
| // Here are possible arrangements of source, target, scratch: |
| // - source, target, scratch can all be separate registers. |
| // - source and target can be the same but scratch is separate. |
| // - target and scratch can be the same but source is separate. |
| void boxInt52(GPRReg source, GPRReg target, GPRReg scratch, FPRReg fpScratch) |
| { |
| // Is it an int32? |
| signExtend32ToPtr(source, scratch); |
| Jump isInt32 = branch64(Equal, source, scratch); |
| |
| // Nope, it's not, but regT0 contains the int64 value. |
| convertInt64ToDouble(source, fpScratch); |
| boxDouble(fpScratch, target); |
| Jump done = jump(); |
| |
| isInt32.link(this); |
| zeroExtend32ToWord(source, target); |
| or64(GPRInfo::numberTagRegister, target); |
| |
| done.link(this); |
| } |
| #endif // USE(JSVALUE64) |
| |
| #if USE(BIGINT32) |
| void unboxBigInt32(GPRReg src, GPRReg dest) |
| { |
| #if CPU(ARM64) |
| urshift64(src, trustedImm32ForShift(Imm32(16)), dest); |
| #else |
| move(src, dest); |
| urshift64(trustedImm32ForShift(Imm32(16)), dest); |
| #endif |
| } |
| |
| void boxBigInt32(GPRReg gpr) |
| { |
| lshift64(trustedImm32ForShift(Imm32(16)), gpr); |
| or64(TrustedImm32(JSValue::BigInt32Tag), gpr); |
| } |
| #endif |
| |
| #if USE(JSVALUE32_64) |
| void boxDouble(FPRReg fpr, GPRReg tagGPR, GPRReg payloadGPR) |
| { |
| moveDoubleToInts(fpr, payloadGPR, tagGPR); |
| } |
| void unboxDouble(GPRReg tagGPR, GPRReg payloadGPR, FPRReg fpr, FPRReg scratchFPR) |
| { |
| moveIntsToDouble(payloadGPR, tagGPR, fpr, scratchFPR); |
| } |
| |
| void boxDouble(FPRReg fpr, JSValueRegs regs) |
| { |
| boxDouble(fpr, regs.tagGPR(), regs.payloadGPR()); |
| } |
| void unboxDouble(JSValueRegs regs, FPRReg fpr, FPRReg scratchFPR) |
| { |
| unboxDouble(regs.tagGPR(), regs.payloadGPR(), fpr, scratchFPR); |
| } |
| |
| void unboxDoubleNonDestructive(const JSValueRegs regs, FPRReg destFPR, GPRReg, FPRReg scratchFPR) |
| { |
| unboxDouble(regs, destFPR, scratchFPR); |
| } |
| #endif |
| |
| void boxBooleanPayload(GPRReg boolGPR, GPRReg payloadGPR) |
| { |
| #if USE(JSVALUE64) |
| add32(TrustedImm32(JSValue::ValueFalse), boolGPR, payloadGPR); |
| #else |
| move(boolGPR, payloadGPR); |
| #endif |
| } |
| |
| void boxBooleanPayload(bool value, GPRReg payloadGPR) |
| { |
| #if USE(JSVALUE64) |
| move(TrustedImm32(JSValue::ValueFalse + value), payloadGPR); |
| #else |
| move(TrustedImm32(value), payloadGPR); |
| #endif |
| } |
| |
| void boxBoolean(GPRReg boolGPR, JSValueRegs boxedRegs) |
| { |
| boxBooleanPayload(boolGPR, boxedRegs.payloadGPR()); |
| #if USE(JSVALUE32_64) |
| move(TrustedImm32(JSValue::BooleanTag), boxedRegs.tagGPR()); |
| #endif |
| } |
| |
| void boxBoolean(bool value, JSValueRegs boxedRegs) |
| { |
| boxBooleanPayload(value, boxedRegs.payloadGPR()); |
| #if USE(JSVALUE32_64) |
| move(TrustedImm32(JSValue::BooleanTag), boxedRegs.tagGPR()); |
| #endif |
| } |
| |
| void boxInt32(GPRReg intGPR, JSValueRegs boxedRegs, TagRegistersMode mode = HaveTagRegisters) |
| { |
| #if USE(JSVALUE64) |
| if (mode == DoNotHaveTagRegisters) { |
| move(intGPR, boxedRegs.gpr()); |
| or64(TrustedImm64(JSValue::NumberTag), boxedRegs.gpr()); |
| } else |
| or64(GPRInfo::numberTagRegister, intGPR, boxedRegs.gpr()); |
| #else |
| UNUSED_PARAM(mode); |
| move(intGPR, boxedRegs.payloadGPR()); |
| move(TrustedImm32(JSValue::Int32Tag), boxedRegs.tagGPR()); |
| #endif |
| } |
| |
| void boxCell(GPRReg cellGPR, JSValueRegs boxedRegs) |
| { |
| #if USE(JSVALUE64) |
| move(cellGPR, boxedRegs.gpr()); |
| #else |
| move(cellGPR, boxedRegs.payloadGPR()); |
| move(TrustedImm32(JSValue::CellTag), boxedRegs.tagGPR()); |
| #endif |
| } |
| |
| void callExceptionFuzz(VM&); |
| |
| enum ExceptionCheckKind { NormalExceptionCheck, InvertedExceptionCheck }; |
| enum ExceptionJumpWidth { NormalJumpWidth, FarJumpWidth }; |
| JS_EXPORT_PRIVATE Jump emitExceptionCheck( |
| VM&, ExceptionCheckKind = NormalExceptionCheck, ExceptionJumpWidth = NormalJumpWidth); |
| JS_EXPORT_PRIVATE Jump emitNonPatchableExceptionCheck(VM&); |
| Jump emitJumpIfException(VM&); |
| |
| #if ENABLE(SAMPLING_COUNTERS) |
| static void emitCount(MacroAssembler& jit, AbstractSamplingCounter& counter, int32_t increment = 1) |
| { |
| jit.add64(TrustedImm32(increment), AbsoluteAddress(counter.addressOfCounter())); |
| } |
| void emitCount(AbstractSamplingCounter& counter, int32_t increment = 1) |
| { |
| add64(TrustedImm32(increment), AbsoluteAddress(counter.addressOfCounter())); |
| } |
| #endif |
| |
| #if ENABLE(SAMPLING_FLAGS) |
| void setSamplingFlag(int32_t); |
| void clearSamplingFlag(int32_t flag); |
| #endif |
| |
| JSGlobalObject* globalObjectFor(CodeOrigin codeOrigin) |
| { |
| return codeBlock()->globalObjectFor(codeOrigin); |
| } |
| |
| ExecutableBase* executableFor(const CodeOrigin& codeOrigin); |
| |
| CodeBlock* baselineCodeBlockFor(const CodeOrigin& codeOrigin) |
| { |
| return baselineCodeBlockForOriginAndBaselineCodeBlock(codeOrigin, baselineCodeBlock()); |
| } |
| |
| CodeBlock* baselineCodeBlockFor(InlineCallFrame* inlineCallFrame) |
| { |
| if (!inlineCallFrame) |
| return baselineCodeBlock(); |
| return baselineCodeBlockForInlineCallFrame(inlineCallFrame); |
| } |
| |
| CodeBlock* baselineCodeBlock() |
| { |
| return m_baselineCodeBlock; |
| } |
| |
| static VirtualRegister argumentsStart(InlineCallFrame* inlineCallFrame) |
| { |
| if (!inlineCallFrame) |
| return VirtualRegister(CallFrame::argumentOffset(0)); |
| if (inlineCallFrame->argumentsWithFixup.size() <= 1) |
| return virtualRegisterForLocal(0); |
| ValueRecovery recovery = inlineCallFrame->argumentsWithFixup[1]; |
| RELEASE_ASSERT(recovery.technique() == DisplacedInJSStack); |
| return recovery.virtualRegister(); |
| } |
| |
| static VirtualRegister argumentsStart(const CodeOrigin& codeOrigin) |
| { |
| return argumentsStart(codeOrigin.inlineCallFrame()); |
| } |
| |
| static VirtualRegister argumentCount(InlineCallFrame* inlineCallFrame) |
| { |
| ASSERT(!inlineCallFrame || inlineCallFrame->isVarargs()); |
| if (!inlineCallFrame) |
| return CallFrameSlot::argumentCountIncludingThis; |
| return inlineCallFrame->argumentCountRegister; |
| } |
| |
| static VirtualRegister argumentCount(const CodeOrigin& codeOrigin) |
| { |
| return argumentCount(codeOrigin.inlineCallFrame()); |
| } |
| |
| void emitLoadStructure(VM&, RegisterID source, RegisterID dest, RegisterID scratch); |
| void emitLoadPrototype(VM&, GPRReg objectGPR, JSValueRegs resultRegs, GPRReg scratchGPR, JumpList& slowPath); |
| |
| void emitStoreStructureWithTypeInfo(TrustedImmPtr structure, RegisterID dest, RegisterID) |
| { |
| emitStoreStructureWithTypeInfo(*this, structure, dest); |
| } |
| |
| void emitStoreStructureWithTypeInfo(RegisterID structure, RegisterID dest, RegisterID scratch) |
| { |
| #if USE(JSVALUE64) |
| load64(MacroAssembler::Address(structure, Structure::structureIDOffset()), scratch); |
| store64(scratch, MacroAssembler::Address(dest, JSCell::structureIDOffset())); |
| #else |
| // Store all the info flags using a single 32-bit wide load and store. |
| load32(MacroAssembler::Address(structure, Structure::indexingModeIncludingHistoryOffset()), scratch); |
| store32(scratch, MacroAssembler::Address(dest, JSCell::indexingTypeAndMiscOffset())); |
| |
| // Store the StructureID |
| storePtr(structure, MacroAssembler::Address(dest, JSCell::structureIDOffset())); |
| #endif |
| } |
| |
| static void emitStoreStructureWithTypeInfo(AssemblyHelpers& jit, TrustedImmPtr structure, RegisterID dest); |
| |
| Jump barrierBranchWithoutFence(GPRReg cell) |
| { |
| return branch8(Above, Address(cell, JSCell::cellStateOffset()), TrustedImm32(blackThreshold)); |
| } |
| |
| Jump barrierBranchWithoutFence(JSCell* cell) |
| { |
| uint8_t* address = reinterpret_cast<uint8_t*>(cell) + JSCell::cellStateOffset(); |
| return branch8(Above, AbsoluteAddress(address), TrustedImm32(blackThreshold)); |
| } |
| |
| Jump barrierBranch(VM& vm, GPRReg cell, GPRReg scratchGPR) |
| { |
| load8(Address(cell, JSCell::cellStateOffset()), scratchGPR); |
| return branch32(Above, scratchGPR, AbsoluteAddress(vm.heap.addressOfBarrierThreshold())); |
| } |
| |
| Jump barrierBranch(VM& vm, JSCell* cell, GPRReg scratchGPR) |
| { |
| uint8_t* address = reinterpret_cast<uint8_t*>(cell) + JSCell::cellStateOffset(); |
| load8(address, scratchGPR); |
| return branch32(Above, scratchGPR, AbsoluteAddress(vm.heap.addressOfBarrierThreshold())); |
| } |
| |
| void barrierStoreLoadFence(VM& vm) |
| { |
| Jump ok = jumpIfMutatorFenceNotNeeded(vm); |
| memoryFence(); |
| ok.link(this); |
| } |
| |
| void mutatorFence(VM& vm) |
| { |
| if (isX86()) |
| return; |
| Jump ok = jumpIfMutatorFenceNotNeeded(vm); |
| storeFence(); |
| ok.link(this); |
| } |
| |
| void cageWithoutUntagging(Gigacage::Kind kind, GPRReg storage) |
| { |
| #if GIGACAGE_ENABLED |
| if (!Gigacage::isEnabled(kind)) |
| return; |
| |
| #if CPU(ARM64E) |
| RegisterID tempReg = InvalidGPRReg; |
| if (kind == Gigacage::Primitive) { |
| tempReg = getCachedMemoryTempRegisterIDAndInvalidate(); |
| move(storage, tempReg); |
| // Flip the registers since bitFieldInsert only inserts into the low bits. |
| std::swap(storage, tempReg); |
| } |
| #endif |
| andPtr(TrustedImmPtr(Gigacage::mask(kind)), storage); |
| addPtr(TrustedImmPtr(Gigacage::basePtr(kind)), storage); |
| #if CPU(ARM64E) |
| if (kind == Gigacage::Primitive) |
| bitFieldInsert64(storage, 0, 64 - numberOfPACBits, tempReg); |
| #endif |
| |
| #else |
| UNUSED_PARAM(kind); |
| UNUSED_PARAM(storage); |
| #endif |
| } |
| |
| // length may be the same register as scratch. |
| void cageConditionally(Gigacage::Kind kind, GPRReg storage, GPRReg length, GPRReg scratch) |
| { |
| #if GIGACAGE_ENABLED |
| if (Gigacage::isEnabled(kind)) { |
| if (kind != Gigacage::Primitive || Gigacage::disablingPrimitiveGigacageIsForbidden()) |
| cageWithoutUntagging(kind, storage); |
| else { |
| #if CPU(ARM64E) |
| if (length == scratch) |
| scratch = getCachedMemoryTempRegisterIDAndInvalidate(); |
| #endif |
| JumpList done; |
| done.append(branchTest8(NonZero, AbsoluteAddress(&Gigacage::disablePrimitiveGigacageRequested))); |
| |
| loadPtr(Gigacage::addressOfBasePtr(kind), scratch); |
| done.append(branchTest64(Zero, scratch)); |
| #if CPU(ARM64E) |
| GPRReg tempReg = getCachedDataTempRegisterIDAndInvalidate(); |
| move(storage, tempReg); |
| ASSERT(LogicalImmediate::create64(Gigacage::mask(kind)).isValid()); |
| andPtr(TrustedImmPtr(Gigacage::mask(kind)), tempReg); |
| addPtr(scratch, tempReg); |
| bitFieldInsert64(tempReg, 0, 64 - numberOfPACBits, storage); |
| #else |
| andPtr(TrustedImmPtr(Gigacage::mask(kind)), storage); |
| addPtr(scratch, storage); |
| #endif // CPU(ARM64E) |
| done.link(this); |
| } |
| } |
| #endif |
| |
| #if CPU(ARM64E) |
| if (kind == Gigacage::Primitive) |
| untagArrayPtr(length, storage); |
| #endif |
| UNUSED_PARAM(kind); |
| UNUSED_PARAM(storage); |
| UNUSED_PARAM(length); |
| UNUSED_PARAM(scratch); |
| } |
| |
| void emitComputeButterflyIndexingMask(GPRReg vectorLengthGPR, GPRReg scratchGPR, GPRReg resultGPR) |
| { |
| ASSERT(scratchGPR != resultGPR); |
| Jump done; |
| // If vectorLength == 0 then clz will return 32 on both ARM and x86. On 64-bit systems, we can then do a 64-bit right shift on a 32-bit -1 to get a 0 mask for zero vectorLength. On 32-bit ARM, shift masks with 0xff, which means it will still create a 0 mask. |
| countLeadingZeros32(vectorLengthGPR, scratchGPR); |
| move(TrustedImm32(-1), resultGPR); |
| urshiftPtr(scratchGPR, resultGPR); |
| if (done.isSet()) |
| done.link(this); |
| } |
| |
| // If for whatever reason the butterfly is going to change vector length this function does NOT |
| // update the indexing mask. |
| void nukeStructureAndStoreButterfly(VM& vm, GPRReg butterfly, GPRReg object) |
| { |
| if (isX86()) { |
| or32(TrustedImm32(bitwise_cast<int32_t>(nukedStructureIDBit())), Address(object, JSCell::structureIDOffset())); |
| storePtr(butterfly, Address(object, JSObject::butterflyOffset())); |
| return; |
| } |
| |
| Jump ok = jumpIfMutatorFenceNotNeeded(vm); |
| or32(TrustedImm32(bitwise_cast<int32_t>(nukedStructureIDBit())), Address(object, JSCell::structureIDOffset())); |
| storeFence(); |
| storePtr(butterfly, Address(object, JSObject::butterflyOffset())); |
| storeFence(); |
| Jump done = jump(); |
| ok.link(this); |
| storePtr(butterfly, Address(object, JSObject::butterflyOffset())); |
| done.link(this); |
| } |
| |
| Jump jumpIfMutatorFenceNotNeeded(VM& vm) |
| { |
| return branchTest8(Zero, AbsoluteAddress(vm.heap.addressOfMutatorShouldBeFenced())); |
| } |
| |
| void sanitizeStackInline(VM&, GPRReg scratch); |
| |
| // Emits the branch structure for typeof. The code emitted by this doesn't fall through. The |
| // functor is called at those points where we have pinpointed a type. One way to use this is to |
| // have the functor emit the code to put the type string into an appropriate register and then |
| // jump out. A secondary functor is used for the call trap and masquerades-as-undefined slow |
| // case. It is passed the unlinked jump to the slow case. |
| template<typename Functor, typename SlowPathFunctor> |
| void emitTypeOf( |
| JSValueRegs regs, GPRReg tempGPR, const Functor& functor, |
| const SlowPathFunctor& slowPathFunctor) |
| { |
| // Implements the following branching structure: |
| // |
| // if (is cell) { |
| // if (is object) { |
| // if (is function) { |
| // return function; |
| // } else if (doesn't have call trap and doesn't masquerade as undefined) { |
| // return object |
| // } else { |
| // return slowPath(); |
| // } |
| // } else if (is string) { |
| // return string |
| // } else if (is heapbigint) { |
| // return bigint |
| // } else { |
| // return symbol |
| // } |
| // } else if (is number) { |
| // return number |
| // } else if (is null) { |
| // return object |
| // } else if (is boolean) { |
| // return boolean |
| // } else if (is bigint32) { |
| // return bigint |
| // } else { |
| // return undefined |
| // } |
| // |
| // FIXME: typeof Symbol should be more frequently seen than BigInt. |
| // We should change the order of type detection based on this frequency. |
| // https://bugs.webkit.org/show_bug.cgi?id=192650 |
| |
| Jump notCell = branchIfNotCell(regs); |
| |
| GPRReg cellGPR = regs.payloadGPR(); |
| Jump notObject = branchIfNotObject(cellGPR); |
| |
| Jump notFunction = branchIfNotFunction(cellGPR); |
| functor(TypeofType::Function, false); |
| |
| notFunction.link(this); |
| slowPathFunctor( |
| branchTest8( |
| NonZero, |
| Address(cellGPR, JSCell::typeInfoFlagsOffset()), |
| TrustedImm32(MasqueradesAsUndefined | OverridesGetCallData))); |
| functor(TypeofType::Object, false); |
| |
| notObject.link(this); |
| |
| Jump notString = branchIfNotString(cellGPR); |
| functor(TypeofType::String, false); |
| |
| notString.link(this); |
| |
| Jump notHeapBigInt = branchIfNotHeapBigInt(cellGPR); |
| functor(TypeofType::BigInt, false); |
| |
| notHeapBigInt.link(this); |
| functor(TypeofType::Symbol, false); |
| |
| notCell.link(this); |
| |
| Jump notNumber = branchIfNotNumber(regs, tempGPR); |
| functor(TypeofType::Number, false); |
| notNumber.link(this); |
| |
| JumpList notNull = branchIfNotEqual(regs, jsNull()); |
| functor(TypeofType::Object, false); |
| notNull.link(this); |
| |
| Jump notBoolean = branchIfNotBoolean(regs, tempGPR); |
| functor(TypeofType::Boolean, false); |
| notBoolean.link(this); |
| |
| #if USE(BIGINT32) |
| Jump notBigInt32 = branchIfNotBigInt32(regs, tempGPR); |
| functor(TypeofType::BigInt, false); |
| notBigInt32.link(this); |
| #endif |
| |
| functor(TypeofType::Undefined, true); |
| } |
| |
| void emitDumbVirtualCall(VM&, JSGlobalObject*, CallLinkInfo*); |
| |
| void makeSpaceOnStackForCCall(); |
| void reclaimSpaceOnStackForCCall(); |
| |
| #if USE(JSVALUE64) |
| void emitRandomThunk(JSGlobalObject*, GPRReg scratch0, GPRReg scratch1, GPRReg scratch2, FPRReg result); |
| void emitRandomThunk(VM&, GPRReg scratch0, GPRReg scratch1, GPRReg scratch2, GPRReg scratch3, FPRReg result); |
| #endif |
| |
| // Call this if you know that the value held in allocatorGPR is non-null. This DOES NOT mean |
| // that allocator is non-null; allocator can be null as a signal that we don't know what the |
| // value of allocatorGPR is. Additionally, if the allocator is not null, then there is no need |
| // to populate allocatorGPR - this code will ignore the contents of allocatorGPR. |
| void emitAllocateWithNonNullAllocator(GPRReg resultGPR, const JITAllocator&, GPRReg allocatorGPR, GPRReg scratchGPR, JumpList& slowPath, Optional<GPRReg> scratchGPR2 = { }, Optional<GPRReg> scratchGPR3 = { }); |
| |
| void emitAllocate(GPRReg resultGPR, const JITAllocator& allocator, GPRReg allocatorGPR, GPRReg scratchGPR, JumpList& slowPath); |
| |
| template<typename StructureType> |
| void emitAllocateJSCell(GPRReg resultGPR, const JITAllocator& allocator, GPRReg allocatorGPR, StructureType structure, GPRReg scratchGPR, JumpList& slowPath) |
| { |
| emitAllocate(resultGPR, allocator, allocatorGPR, scratchGPR, slowPath); |
| emitStoreStructureWithTypeInfo(structure, resultGPR, scratchGPR); |
| } |
| |
| template<typename StructureType, typename StorageType> |
| void emitAllocateJSObject(GPRReg resultGPR, const JITAllocator& allocator, GPRReg allocatorGPR, StructureType structure, StorageType storage, GPRReg scratchGPR, JumpList& slowPath) |
| { |
| emitAllocateJSCell(resultGPR, allocator, allocatorGPR, structure, scratchGPR, slowPath); |
| storePtr(storage, Address(resultGPR, JSObject::butterflyOffset())); |
| } |
| |
| template<typename ClassType, typename StructureType, typename StorageType> |
| void emitAllocateJSObjectWithKnownSize( |
| VM& vm, GPRReg resultGPR, StructureType structure, StorageType storage, GPRReg scratchGPR1, |
| GPRReg scratchGPR2, JumpList& slowPath, size_t size) |
| { |
| Allocator allocator = allocatorForNonVirtualConcurrently<ClassType>(vm, size, AllocatorForMode::AllocatorIfExists); |
| emitAllocateJSObject(resultGPR, JITAllocator::constant(allocator), scratchGPR1, structure, storage, scratchGPR2, slowPath); |
| } |
| |
| template<typename ClassType, typename StructureType, typename StorageType> |
| void emitAllocateJSObject(VM& vm, GPRReg resultGPR, StructureType structure, StorageType storage, GPRReg scratchGPR1, GPRReg scratchGPR2, JumpList& slowPath) |
| { |
| emitAllocateJSObjectWithKnownSize<ClassType>(vm, resultGPR, structure, storage, scratchGPR1, scratchGPR2, slowPath, ClassType::allocationSize(0)); |
| } |
| |
| // allocationSize can be aliased with any of the other input GPRs. If it's not aliased then it |
| // won't be clobbered. |
| void emitAllocateVariableSized(GPRReg resultGPR, CompleteSubspace& subspace, GPRReg allocationSize, GPRReg scratchGPR1, GPRReg scratchGPR2, JumpList& slowPath); |
| |
| template<typename ClassType, typename StructureType> |
| void emitAllocateVariableSizedCell(VM& vm, GPRReg resultGPR, StructureType structure, GPRReg allocationSize, GPRReg scratchGPR1, GPRReg scratchGPR2, JumpList& slowPath) |
| { |
| CompleteSubspace* subspace = subspaceForConcurrently<ClassType>(vm); |
| RELEASE_ASSERT_WITH_MESSAGE(subspace, "CompleteSubspace is always allocated"); |
| emitAllocateVariableSized(resultGPR, *subspace, allocationSize, scratchGPR1, scratchGPR2, slowPath); |
| emitStoreStructureWithTypeInfo(structure, resultGPR, scratchGPR2); |
| } |
| |
| template<typename ClassType, typename StructureType> |
| void emitAllocateVariableSizedJSObject(VM& vm, GPRReg resultGPR, StructureType structure, GPRReg allocationSize, GPRReg scratchGPR1, GPRReg scratchGPR2, JumpList& slowPath) |
| { |
| emitAllocateVariableSizedCell<ClassType>(vm, resultGPR, structure, allocationSize, scratchGPR1, scratchGPR2, slowPath); |
| storePtr(TrustedImmPtr(nullptr), Address(resultGPR, JSObject::butterflyOffset())); |
| } |
| |
| JumpList branchIfValue(VM&, JSValueRegs, GPRReg scratch, GPRReg scratchIfShouldCheckMasqueradesAsUndefined, FPRReg, FPRReg, bool shouldCheckMasqueradesAsUndefined, JSGlobalObject*, bool negateResult); |
| JumpList branchIfTruthy(VM& vm, JSValueRegs value, GPRReg scratch, GPRReg scratchIfShouldCheckMasqueradesAsUndefined, FPRReg scratchFPR0, FPRReg scratchFPR1, bool shouldCheckMasqueradesAsUndefined, JSGlobalObject* globalObject) |
| { |
| return branchIfValue(vm, value, scratch, scratchIfShouldCheckMasqueradesAsUndefined, scratchFPR0, scratchFPR1, shouldCheckMasqueradesAsUndefined, globalObject, false); |
| } |
| JumpList branchIfFalsey(VM& vm, JSValueRegs value, GPRReg scratch, GPRReg scratchIfShouldCheckMasqueradesAsUndefined, FPRReg scratchFPR0, FPRReg scratchFPR1, bool shouldCheckMasqueradesAsUndefined, JSGlobalObject* globalObject) |
| { |
| return branchIfValue(vm, value, scratch, scratchIfShouldCheckMasqueradesAsUndefined, scratchFPR0, scratchFPR1, shouldCheckMasqueradesAsUndefined, globalObject, true); |
| } |
| void emitConvertValueToBoolean(VM&, JSValueRegs, GPRReg result, GPRReg scratchIfShouldCheckMasqueradesAsUndefined, FPRReg, FPRReg, bool shouldCheckMasqueradesAsUndefined, JSGlobalObject*, bool negateResult = false); |
| |
| void emitInitializeInlineStorage(GPRReg baseGPR, unsigned inlineCapacity) |
| { |
| for (unsigned i = 0; i < inlineCapacity; ++i) |
| storeTrustedValue(JSValue(), Address(baseGPR, JSObject::offsetOfInlineStorage() + i * sizeof(EncodedJSValue))); |
| } |
| |
| void emitInitializeInlineStorage(GPRReg baseGPR, GPRReg inlineCapacity) |
| { |
| Jump empty = branchTest32(Zero, inlineCapacity); |
| Label loop = label(); |
| sub32(TrustedImm32(1), inlineCapacity); |
| storeTrustedValue(JSValue(), BaseIndex(baseGPR, inlineCapacity, TimesEight, JSObject::offsetOfInlineStorage())); |
| branchTest32(NonZero, inlineCapacity).linkTo(loop, this); |
| empty.link(this); |
| } |
| |
| void emitInitializeOutOfLineStorage(GPRReg butterflyGPR, unsigned outOfLineCapacity) |
| { |
| for (unsigned i = 0; i < outOfLineCapacity; ++i) |
| storeTrustedValue(JSValue(), Address(butterflyGPR, -sizeof(IndexingHeader) - (i + 1) * sizeof(EncodedJSValue))); |
| } |
| |
| #if USE(JSVALUE64) |
| void wangsInt64Hash(GPRReg inputAndResult, GPRReg scratch); |
| #endif |
| |
| #if ENABLE(WEBASSEMBLY) |
| void loadWasmContextInstance(GPRReg dst); |
| void storeWasmContextInstance(GPRReg src); |
| static bool loadWasmContextInstanceNeedsMacroScratchRegister(); |
| static bool storeWasmContextInstanceNeedsMacroScratchRegister(); |
| #endif |
| |
| protected: |
| void copyCalleeSavesToEntryFrameCalleeSavesBufferImpl(GPRReg calleeSavesBuffer); |
| |
| CodeBlock* m_codeBlock; |
| CodeBlock* m_baselineCodeBlock; |
| }; |
| |
| } // namespace JSC |
| |
| #endif // ENABLE(JIT) |