/* * Copyright (C) 2011 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. */ #include "config.h" #include "DFGSpeculativeJIT.h" #if ENABLE(DFG_JIT) #include "Arguments.h" #include "DFGCallArrayAllocatorSlowPathGenerator.h" #include "DFGSlowPathGenerator.h" #include "LinkBuffer.h" namespace JSC { namespace DFG { SpeculativeJIT::SpeculativeJIT(JITCompiler& jit) : m_compileOkay(true) , m_jit(jit) , m_compileIndex(0) , m_indexInBlock(0) , m_generationInfo(m_jit.codeBlock()->m_numCalleeRegisters) , m_blockHeads(jit.graph().m_blocks.size()) , m_arguments(jit.codeBlock()->numParameters()) , m_variables(jit.graph().m_localVars) , m_lastSetOperand(std::numeric_limits::max()) , m_state(m_jit.graph()) , m_stream(&jit.codeBlock()->variableEventStream()) , m_minifiedGraph(&jit.codeBlock()->minifiedDFG()) , m_isCheckingArgumentTypes(false) { } SpeculativeJIT::~SpeculativeJIT() { WTF::deleteAllValues(m_slowPathGenerators); } void SpeculativeJIT::emitAllocateJSArray(Structure* structure, GPRReg resultGPR, GPRReg storageGPR, unsigned numElements) { ASSERT(hasUndecided(structure->indexingType()) || hasInt32(structure->indexingType()) || hasDouble(structure->indexingType()) || hasContiguous(structure->indexingType())); GPRTemporary scratch(this); GPRReg scratchGPR = scratch.gpr(); unsigned vectorLength = std::max(BASE_VECTOR_LEN, numElements); JITCompiler::JumpList slowCases; slowCases.append( emitAllocateBasicStorage(TrustedImm32(vectorLength * sizeof(JSValue) + sizeof(IndexingHeader)), storageGPR)); m_jit.subPtr(TrustedImm32(vectorLength * sizeof(JSValue)), storageGPR); emitAllocateBasicJSObject( TrustedImmPtr(structure), resultGPR, scratchGPR, storageGPR, sizeof(JSArray), slowCases); // I'm assuming that two 32-bit stores are better than a 64-bit store. // I have no idea if that's true. And it probably doesn't matter anyway. m_jit.store32(TrustedImm32(numElements), MacroAssembler::Address(storageGPR, Butterfly::offsetOfPublicLength())); m_jit.store32(TrustedImm32(vectorLength), MacroAssembler::Address(storageGPR, Butterfly::offsetOfVectorLength())); if (hasDouble(structure->indexingType()) && numElements < vectorLength) { #if USE(JSVALUE64) m_jit.move(TrustedImm64(bitwise_cast(QNaN)), scratchGPR); for (unsigned i = numElements; i < vectorLength; ++i) m_jit.store64(scratchGPR, MacroAssembler::Address(storageGPR, sizeof(double) * i)); #else EncodedValueDescriptor value; value.asInt64 = JSValue::encode(JSValue(JSValue::EncodeAsDouble, QNaN)); for (unsigned i = numElements; i < vectorLength; ++i) { m_jit.store32(TrustedImm32(value.asBits.tag), MacroAssembler::Address(storageGPR, sizeof(double) * i + OBJECT_OFFSETOF(JSValue, u.asBits.tag))); m_jit.store32(TrustedImm32(value.asBits.payload), MacroAssembler::Address(storageGPR, sizeof(double) * i + OBJECT_OFFSETOF(JSValue, u.asBits.payload))); } #endif } // I want a slow path that also loads out the storage pointer, and that's // what this custom CallArrayAllocatorSlowPathGenerator gives me. It's a lot // of work for a very small piece of functionality. :-/ addSlowPathGenerator(adoptPtr( new CallArrayAllocatorSlowPathGenerator( slowCases, this, operationNewArrayWithSize, resultGPR, storageGPR, structure, numElements))); } void SpeculativeJIT::speculationCheck(ExitKind kind, JSValueSource jsValueSource, NodeIndex nodeIndex, MacroAssembler::Jump jumpToFail) { if (!m_compileOkay) return; ASSERT(at(m_compileIndex).canExit() || m_isCheckingArgumentTypes); m_jit.codeBlock()->appendOSRExit(OSRExit(kind, jsValueSource, m_jit.graph().methodOfGettingAValueProfileFor(nodeIndex), jumpToFail, this, m_stream->size())); } void SpeculativeJIT::speculationCheck(ExitKind kind, JSValueSource jsValueSource, Edge nodeUse, MacroAssembler::Jump jumpToFail) { ASSERT(at(m_compileIndex).canExit() || m_isCheckingArgumentTypes); speculationCheck(kind, jsValueSource, nodeUse.index(), jumpToFail); } void SpeculativeJIT::speculationCheck(ExitKind kind, JSValueSource jsValueSource, NodeIndex nodeIndex, const MacroAssembler::JumpList& jumpsToFail) { ASSERT(at(m_compileIndex).canExit() || m_isCheckingArgumentTypes); Vector jumpVector = jumpsToFail.jumps(); for (unsigned i = 0; i < jumpVector.size(); ++i) speculationCheck(kind, jsValueSource, nodeIndex, jumpVector[i]); } void SpeculativeJIT::speculationCheck(ExitKind kind, JSValueSource jsValueSource, Edge nodeUse, const MacroAssembler::JumpList& jumpsToFail) { ASSERT(at(m_compileIndex).canExit() || m_isCheckingArgumentTypes); speculationCheck(kind, jsValueSource, nodeUse.index(), jumpsToFail); } void SpeculativeJIT::speculationCheck(ExitKind kind, JSValueSource jsValueSource, NodeIndex nodeIndex, MacroAssembler::Jump jumpToFail, const SpeculationRecovery& recovery) { if (!m_compileOkay) return; ASSERT(at(m_compileIndex).canExit() || m_isCheckingArgumentTypes); m_jit.codeBlock()->appendSpeculationRecovery(recovery); m_jit.codeBlock()->appendOSRExit(OSRExit(kind, jsValueSource, m_jit.graph().methodOfGettingAValueProfileFor(nodeIndex), jumpToFail, this, m_stream->size(), m_jit.codeBlock()->numberOfSpeculationRecoveries())); } void SpeculativeJIT::speculationCheck(ExitKind kind, JSValueSource jsValueSource, Edge nodeUse, MacroAssembler::Jump jumpToFail, const SpeculationRecovery& recovery) { ASSERT(at(m_compileIndex).canExit() || m_isCheckingArgumentTypes); speculationCheck(kind, jsValueSource, nodeUse.index(), jumpToFail, recovery); } void SpeculativeJIT::speculationCheck(ExitKind kind, JSValueSource jsValueSource, NodeIndex nodeIndex, MacroAssembler::Jump jumpToFail, const SpeculationRecovery& recovery, SpeculationDirection direction) { speculationCheck(kind, jsValueSource, nodeIndex, jumpToFail, recovery); if (direction == ForwardSpeculation) convertLastOSRExitToForward(); } JumpReplacementWatchpoint* SpeculativeJIT::speculationWatchpoint(ExitKind kind, JSValueSource jsValueSource, NodeIndex nodeIndex) { if (!m_compileOkay) return 0; ASSERT(at(m_compileIndex).canExit() || m_isCheckingArgumentTypes); OSRExit& exit = m_jit.codeBlock()->osrExit( m_jit.codeBlock()->appendOSRExit( OSRExit(kind, jsValueSource, m_jit.graph().methodOfGettingAValueProfileFor(nodeIndex), JITCompiler::Jump(), this, m_stream->size()))); exit.m_watchpointIndex = m_jit.codeBlock()->appendWatchpoint( JumpReplacementWatchpoint(m_jit.watchpointLabel())); return &m_jit.codeBlock()->watchpoint(exit.m_watchpointIndex); } JumpReplacementWatchpoint* SpeculativeJIT::speculationWatchpoint(ExitKind kind) { return speculationWatchpoint(kind, JSValueSource(), NoNode); } void SpeculativeJIT::convertLastOSRExitToForward(const ValueRecovery& valueRecovery) { if (!valueRecovery) { // Check that either the current node is a SetLocal, or the preceding node was a // SetLocal with the same code origin. if (at(m_compileIndex).op() != SetLocal) { Node* setLocal = &at(m_jit.graph().m_blocks[m_block]->at(m_indexInBlock - 1)); ASSERT_UNUSED(setLocal, setLocal->op() == SetLocal); ASSERT_UNUSED(setLocal, setLocal->codeOrigin == at(m_compileIndex).codeOrigin); } // Find the next node. unsigned indexInBlock = m_indexInBlock + 1; Node* node = 0; for (;;) { if (indexInBlock == m_jit.graph().m_blocks[m_block]->size()) { // This is an inline return. Give up and do a backwards speculation. This is safe // because an inline return has its own bytecode index and it's always safe to // reexecute that bytecode. ASSERT(node->op() == Jump); return; } node = &at(m_jit.graph().m_blocks[m_block]->at(indexInBlock)); if (node->codeOrigin != at(m_compileIndex).codeOrigin) break; indexInBlock++; } ASSERT(node->codeOrigin != at(m_compileIndex).codeOrigin); OSRExit& exit = m_jit.codeBlock()->lastOSRExit(); exit.m_codeOrigin = node->codeOrigin; return; } unsigned setLocalIndexInBlock = m_indexInBlock + 1; Node* setLocal = &at(m_jit.graph().m_blocks[m_block]->at(setLocalIndexInBlock)); bool hadInt32ToDouble = false; if (setLocal->op() == Int32ToDouble) { setLocal = &at(m_jit.graph().m_blocks[m_block]->at(++setLocalIndexInBlock)); hadInt32ToDouble = true; } if (setLocal->op() == Flush || setLocal->op() == Phantom) setLocal = &at(m_jit.graph().m_blocks[m_block]->at(++setLocalIndexInBlock)); if (hadInt32ToDouble) ASSERT(at(setLocal->child1()).child1() == m_compileIndex); else ASSERT(setLocal->child1() == m_compileIndex); ASSERT(setLocal->op() == SetLocal); ASSERT(setLocal->codeOrigin == at(m_compileIndex).codeOrigin); Node* nextNode = &at(m_jit.graph().m_blocks[m_block]->at(setLocalIndexInBlock + 1)); if (nextNode->op() == Jump && nextNode->codeOrigin == at(m_compileIndex).codeOrigin) { // We're at an inlined return. Use a backward speculation instead. return; } ASSERT(nextNode->codeOrigin != at(m_compileIndex).codeOrigin); OSRExit& exit = m_jit.codeBlock()->lastOSRExit(); exit.m_codeOrigin = nextNode->codeOrigin; exit.m_lastSetOperand = setLocal->local(); exit.m_valueRecoveryOverride = adoptRef( new ValueRecoveryOverride(setLocal->local(), valueRecovery)); } JumpReplacementWatchpoint* SpeculativeJIT::forwardSpeculationWatchpoint(ExitKind kind) { JumpReplacementWatchpoint* result = speculationWatchpoint(kind); convertLastOSRExitToForward(); return result; } JumpReplacementWatchpoint* SpeculativeJIT::speculationWatchpoint(ExitKind kind, SpeculationDirection direction) { JumpReplacementWatchpoint* result = speculationWatchpoint(kind); if (direction == ForwardSpeculation) convertLastOSRExitToForward(); return result; } void SpeculativeJIT::forwardSpeculationCheck(ExitKind kind, JSValueSource jsValueSource, NodeIndex nodeIndex, MacroAssembler::Jump jumpToFail, const ValueRecovery& valueRecovery) { ASSERT(at(m_compileIndex).canExit() || m_isCheckingArgumentTypes); speculationCheck(kind, jsValueSource, nodeIndex, jumpToFail); convertLastOSRExitToForward(valueRecovery); } void SpeculativeJIT::forwardSpeculationCheck(ExitKind kind, JSValueSource jsValueSource, NodeIndex nodeIndex, const MacroAssembler::JumpList& jumpsToFail, const ValueRecovery& valueRecovery) { ASSERT(at(m_compileIndex).canExit() || m_isCheckingArgumentTypes); Vector jumpVector = jumpsToFail.jumps(); for (unsigned i = 0; i < jumpVector.size(); ++i) forwardSpeculationCheck(kind, jsValueSource, nodeIndex, jumpVector[i], valueRecovery); } void SpeculativeJIT::speculationCheck(ExitKind kind, JSValueSource jsValueSource, NodeIndex nodeIndex, MacroAssembler::Jump jumpToFail, SpeculationDirection direction) { if (direction == ForwardSpeculation) forwardSpeculationCheck(kind, jsValueSource, nodeIndex, jumpToFail); else speculationCheck(kind, jsValueSource, nodeIndex, jumpToFail); } void SpeculativeJIT::terminateSpeculativeExecution(ExitKind kind, JSValueRegs jsValueRegs, NodeIndex nodeIndex) { ASSERT(at(m_compileIndex).canExit() || m_isCheckingArgumentTypes); #if DFG_ENABLE(DEBUG_VERBOSE) dataLogF("SpeculativeJIT was terminated.\n"); #endif if (!m_compileOkay) return; speculationCheck(kind, jsValueRegs, nodeIndex, m_jit.jump()); m_compileOkay = false; } void SpeculativeJIT::terminateSpeculativeExecution(ExitKind kind, JSValueRegs jsValueRegs, Edge nodeUse) { ASSERT(at(m_compileIndex).canExit() || m_isCheckingArgumentTypes); terminateSpeculativeExecution(kind, jsValueRegs, nodeUse.index()); } void SpeculativeJIT::terminateSpeculativeExecution(ExitKind kind, JSValueRegs jsValueRegs, NodeIndex nodeIndex, SpeculationDirection direction) { ASSERT(at(m_compileIndex).canExit() || m_isCheckingArgumentTypes); #if DFG_ENABLE(DEBUG_VERBOSE) dataLogF("SpeculativeJIT was terminated.\n"); #endif if (!m_compileOkay) return; speculationCheck(kind, jsValueRegs, nodeIndex, m_jit.jump(), direction); m_compileOkay = false; } void SpeculativeJIT::addSlowPathGenerator(PassOwnPtr slowPathGenerator) { m_slowPathGenerators.append(slowPathGenerator.leakPtr()); } void SpeculativeJIT::runSlowPathGenerators() { #if DFG_ENABLE(DEBUG_VERBOSE) dataLogF("Running %lu slow path generators.\n", m_slowPathGenerators.size()); #endif for (unsigned i = 0; i < m_slowPathGenerators.size(); ++i) m_slowPathGenerators[i]->generate(this); } // On Windows we need to wrap fmod; on other platforms we can call it directly. // On ARMv7 we assert that all function pointers have to low bit set (point to thumb code). #if CALLING_CONVENTION_IS_STDCALL || CPU(ARM_THUMB2) static double DFG_OPERATION fmodAsDFGOperation(double x, double y) { return fmod(x, y); } #else #define fmodAsDFGOperation fmod #endif void SpeculativeJIT::clearGenerationInfo() { for (unsigned i = 0; i < m_generationInfo.size(); ++i) m_generationInfo[i] = GenerationInfo(); m_gprs = RegisterBank(); m_fprs = RegisterBank(); } const TypedArrayDescriptor* SpeculativeJIT::typedArrayDescriptor(ArrayMode arrayMode) { switch (arrayMode.type()) { case Array::Int8Array: return &m_jit.globalData()->int8ArrayDescriptor(); case Array::Int16Array: return &m_jit.globalData()->int16ArrayDescriptor(); case Array::Int32Array: return &m_jit.globalData()->int32ArrayDescriptor(); case Array::Uint8Array: return &m_jit.globalData()->uint8ArrayDescriptor(); case Array::Uint8ClampedArray: return &m_jit.globalData()->uint8ClampedArrayDescriptor(); case Array::Uint16Array: return &m_jit.globalData()->uint16ArrayDescriptor(); case Array::Uint32Array: return &m_jit.globalData()->uint32ArrayDescriptor(); case Array::Float32Array: return &m_jit.globalData()->float32ArrayDescriptor(); case Array::Float64Array: return &m_jit.globalData()->float64ArrayDescriptor(); default: return 0; } } JITCompiler::Jump SpeculativeJIT::jumpSlowForUnwantedArrayMode(GPRReg tempGPR, ArrayMode arrayMode, IndexingType shape, bool invert) { switch (arrayMode.arrayClass()) { case Array::OriginalArray: { CRASH(); JITCompiler::Jump result; // I already know that VC++ takes unkindly to the expression "return Jump()", so I'm doing it this way in anticipation of someone eventually using VC++ to compile the DFG. return result; } case Array::Array: m_jit.and32(TrustedImm32(IsArray | IndexingShapeMask), tempGPR); return m_jit.branch32( invert ? MacroAssembler::Equal : MacroAssembler::NotEqual, tempGPR, TrustedImm32(IsArray | shape)); default: m_jit.and32(TrustedImm32(IndexingShapeMask), tempGPR); return m_jit.branch32(invert ? MacroAssembler::Equal : MacroAssembler::NotEqual, tempGPR, TrustedImm32(shape)); } } JITCompiler::JumpList SpeculativeJIT::jumpSlowForUnwantedArrayMode(GPRReg tempGPR, ArrayMode arrayMode, bool invert) { JITCompiler::JumpList result; switch (arrayMode.type()) { case Array::Int32: return jumpSlowForUnwantedArrayMode(tempGPR, arrayMode, Int32Shape, invert); case Array::Double: return jumpSlowForUnwantedArrayMode(tempGPR, arrayMode, DoubleShape, invert); case Array::Contiguous: return jumpSlowForUnwantedArrayMode(tempGPR, arrayMode, ContiguousShape, invert); case Array::ArrayStorage: case Array::SlowPutArrayStorage: { ASSERT(!arrayMode.isJSArrayWithOriginalStructure()); if (arrayMode.isJSArray()) { if (arrayMode.isSlowPut()) { if (invert) { JITCompiler::Jump slow = m_jit.branchTest32( MacroAssembler::Zero, tempGPR, MacroAssembler::TrustedImm32(IsArray)); m_jit.and32(TrustedImm32(IndexingShapeMask), tempGPR); m_jit.sub32(TrustedImm32(ArrayStorageShape), tempGPR); result.append( m_jit.branch32( MacroAssembler::BelowOrEqual, tempGPR, TrustedImm32(SlowPutArrayStorageShape - ArrayStorageShape))); slow.link(&m_jit); } result.append( m_jit.branchTest32( MacroAssembler::Zero, tempGPR, MacroAssembler::TrustedImm32(IsArray))); m_jit.and32(TrustedImm32(IndexingShapeMask), tempGPR); m_jit.sub32(TrustedImm32(ArrayStorageShape), tempGPR); result.append( m_jit.branch32( MacroAssembler::Above, tempGPR, TrustedImm32(SlowPutArrayStorageShape - ArrayStorageShape))); break; } m_jit.and32(TrustedImm32(IsArray | IndexingShapeMask), tempGPR); result.append( m_jit.branch32(invert ? MacroAssembler::Equal : MacroAssembler::NotEqual, tempGPR, TrustedImm32(ArrayStorageShape))); break; } m_jit.and32(TrustedImm32(IndexingShapeMask), tempGPR); if (arrayMode.isSlowPut()) { m_jit.sub32(TrustedImm32(ArrayStorageShape), tempGPR); result.append( m_jit.branch32( invert ? MacroAssembler::BelowOrEqual : MacroAssembler::Above, tempGPR, TrustedImm32(SlowPutArrayStorageShape - ArrayStorageShape))); break; } result.append( m_jit.branch32(invert ? MacroAssembler::Equal : MacroAssembler::NotEqual, tempGPR, TrustedImm32(ArrayStorageShape))); break; } default: CRASH(); break; } return result; } void SpeculativeJIT::checkArray(Node& node) { ASSERT(node.arrayMode().isSpecific()); ASSERT(!node.arrayMode().doesConversion()); SpeculateCellOperand base(this, node.child1()); GPRReg baseReg = base.gpr(); const TypedArrayDescriptor* result = typedArrayDescriptor(node.arrayMode()); if (node.arrayMode().alreadyChecked(m_jit.graph(), node, m_state.forNode(node.child1()))) { noResult(m_compileIndex); return; } const ClassInfo* expectedClassInfo = 0; switch (node.arrayMode().type()) { case Array::String: expectedClassInfo = &JSString::s_info; break; case Array::Int32: case Array::Double: case Array::Contiguous: case Array::ArrayStorage: case Array::SlowPutArrayStorage: { GPRTemporary temp(this); GPRReg tempGPR = temp.gpr(); m_jit.loadPtr( MacroAssembler::Address(baseReg, JSCell::structureOffset()), tempGPR); m_jit.load8(MacroAssembler::Address(tempGPR, Structure::indexingTypeOffset()), tempGPR); speculationCheck( BadIndexingType, JSValueSource::unboxedCell(baseReg), NoNode, jumpSlowForUnwantedArrayMode(tempGPR, node.arrayMode())); noResult(m_compileIndex); return; } case Array::Arguments: expectedClassInfo = &Arguments::s_info; break; case Array::Int8Array: case Array::Int16Array: case Array::Int32Array: case Array::Uint8Array: case Array::Uint8ClampedArray: case Array::Uint16Array: case Array::Uint32Array: case Array::Float32Array: case Array::Float64Array: expectedClassInfo = result->m_classInfo; break; default: ASSERT_NOT_REACHED(); break; } GPRTemporary temp(this); m_jit.loadPtr( MacroAssembler::Address(baseReg, JSCell::structureOffset()), temp.gpr()); speculationCheck( Uncountable, JSValueRegs(), NoNode, m_jit.branchPtr( MacroAssembler::NotEqual, MacroAssembler::Address(temp.gpr(), Structure::classInfoOffset()), MacroAssembler::TrustedImmPtr(expectedClassInfo))); noResult(m_compileIndex); } void SpeculativeJIT::arrayify(Node& node, GPRReg baseReg, GPRReg propertyReg) { ASSERT(node.arrayMode().doesConversion()); GPRTemporary temp(this); GPRTemporary structure; GPRReg tempGPR = temp.gpr(); GPRReg structureGPR = InvalidGPRReg; if (node.op() != ArrayifyToStructure) { GPRTemporary realStructure(this); structure.adopt(realStructure); structureGPR = structure.gpr(); } // We can skip all that comes next if we already have array storage. MacroAssembler::JumpList done; if (node.op() == ArrayifyToStructure) { done.append(m_jit.branchWeakPtr( JITCompiler::Equal, JITCompiler::Address(baseReg, JSCell::structureOffset()), node.structure())); } else { m_jit.loadPtr( MacroAssembler::Address(baseReg, JSCell::structureOffset()), structureGPR); m_jit.load8( MacroAssembler::Address(structureGPR, Structure::indexingTypeOffset()), tempGPR); done = jumpSlowForUnwantedArrayMode(tempGPR, node.arrayMode(), true); // Next check that the object does not intercept indexed accesses. If it does, // then this mode won't work. speculationCheck( BadIndexingType, JSValueSource::unboxedCell(baseReg), NoNode, m_jit.branchTest8( MacroAssembler::NonZero, MacroAssembler::Address(structureGPR, Structure::typeInfoFlagsOffset()), MacroAssembler::TrustedImm32(InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero))); } // If we're allegedly creating contiguous storage and the index is bogus, then // just don't. if (propertyReg != InvalidGPRReg) { switch (node.arrayMode().type()) { case Array::Int32: case Array::Double: case Array::Contiguous: speculationCheck( Uncountable, JSValueRegs(), NoNode, m_jit.branch32( MacroAssembler::AboveOrEqual, propertyReg, TrustedImm32(MIN_SPARSE_ARRAY_INDEX))); break; default: break; } } // Now call out to create the array storage. silentSpillAllRegisters(tempGPR); switch (node.arrayMode().type()) { case Array::Int32: callOperation(operationEnsureInt32, tempGPR, baseReg); break; case Array::Double: callOperation(operationEnsureDouble, tempGPR, baseReg); break; case Array::Contiguous: callOperation(operationEnsureContiguous, tempGPR, baseReg); break; case Array::ArrayStorage: case Array::SlowPutArrayStorage: callOperation(operationEnsureArrayStorage, tempGPR, baseReg); break; default: CRASH(); break; } silentFillAllRegisters(tempGPR); if (node.op() == ArrayifyToStructure) { speculationCheck( BadIndexingType, JSValueSource::unboxedCell(baseReg), NoNode, m_jit.branchWeakPtr( JITCompiler::NotEqual, JITCompiler::Address(baseReg, JSCell::structureOffset()), node.structure())); } else { // Alas, we need to reload the structure because silent spilling does not save // temporaries. Nor would it be useful for it to do so. Either way we're talking // about a load. m_jit.loadPtr( MacroAssembler::Address(baseReg, JSCell::structureOffset()), structureGPR); // Finally, check that we have the kind of array storage that we wanted to get. // Note that this is a backwards speculation check, which will result in the // bytecode operation corresponding to this arrayification being reexecuted. // That's fine, since arrayification is not user-visible. m_jit.load8( MacroAssembler::Address(structureGPR, Structure::indexingTypeOffset()), structureGPR); speculationCheck( BadIndexingType, JSValueSource::unboxedCell(baseReg), NoNode, jumpSlowForUnwantedArrayMode(structureGPR, node.arrayMode())); } done.link(&m_jit); noResult(m_compileIndex); } void SpeculativeJIT::arrayify(Node& node) { ASSERT(node.arrayMode().isSpecific()); SpeculateCellOperand base(this, node.child1()); if (!node.child2()) { arrayify(node, base.gpr(), InvalidGPRReg); return; } SpeculateIntegerOperand property(this, node.child2()); arrayify(node, base.gpr(), property.gpr()); } GPRReg SpeculativeJIT::fillStorage(NodeIndex nodeIndex) { Node& node = m_jit.graph()[nodeIndex]; VirtualRegister virtualRegister = node.virtualRegister(); GenerationInfo& info = m_generationInfo[virtualRegister]; switch (info.registerFormat()) { case DataFormatNone: { if (info.spillFormat() == DataFormatStorage) { GPRReg gpr = allocate(); m_gprs.retain(gpr, virtualRegister, SpillOrderSpilled); m_jit.loadPtr(JITCompiler::addressFor(virtualRegister), gpr); info.fillStorage(*m_stream, gpr); return gpr; } // Must be a cell; fill it as a cell and then return the pointer. return fillSpeculateCell(nodeIndex, BackwardSpeculation); } case DataFormatStorage: { GPRReg gpr = info.gpr(); m_gprs.lock(gpr); return gpr; } default: return fillSpeculateCell(nodeIndex, BackwardSpeculation); } } void SpeculativeJIT::useChildren(Node& node) { if (node.flags() & NodeHasVarArgs) { for (unsigned childIdx = node.firstChild(); childIdx < node.firstChild() + node.numChildren(); childIdx++) { if (!!m_jit.graph().m_varArgChildren[childIdx]) use(m_jit.graph().m_varArgChildren[childIdx]); } } else { Edge child1 = node.child1(); if (!child1) { ASSERT(!node.child2() && !node.child3()); return; } use(child1); Edge child2 = node.child2(); if (!child2) { ASSERT(!node.child3()); return; } use(child2); Edge child3 = node.child3(); if (!child3) return; use(child3); } } bool SpeculativeJIT::isStrictInt32(NodeIndex nodeIndex) { if (isInt32Constant(nodeIndex)) return true; Node& node = m_jit.graph()[nodeIndex]; GenerationInfo& info = m_generationInfo[node.virtualRegister()]; return info.registerFormat() == DataFormatInteger; } bool SpeculativeJIT::isKnownInteger(NodeIndex nodeIndex) { if (isInt32Constant(nodeIndex)) return true; Node& node = m_jit.graph()[nodeIndex]; if (node.hasInt32Result()) return true; GenerationInfo& info = m_generationInfo[node.virtualRegister()]; return info.isJSInteger(); } bool SpeculativeJIT::isKnownNumeric(NodeIndex nodeIndex) { if (isInt32Constant(nodeIndex) || isNumberConstant(nodeIndex)) return true; Node& node = m_jit.graph()[nodeIndex]; if (node.hasNumberResult()) return true; GenerationInfo& info = m_generationInfo[node.virtualRegister()]; return info.isJSInteger() || info.isJSDouble(); } bool SpeculativeJIT::isKnownCell(NodeIndex nodeIndex) { return m_generationInfo[m_jit.graph()[nodeIndex].virtualRegister()].isJSCell(); } bool SpeculativeJIT::isKnownNotCell(NodeIndex nodeIndex) { Node& node = m_jit.graph()[nodeIndex]; VirtualRegister virtualRegister = node.virtualRegister(); GenerationInfo& info = m_generationInfo[virtualRegister]; if (node.hasConstant() && !valueOfJSConstant(nodeIndex).isCell()) return true; return !(info.isJSCell() || info.isUnknownJS()); } bool SpeculativeJIT::isKnownNotInteger(NodeIndex nodeIndex) { Node& node = m_jit.graph()[nodeIndex]; VirtualRegister virtualRegister = node.virtualRegister(); GenerationInfo& info = m_generationInfo[virtualRegister]; return info.isJSDouble() || info.isJSCell() || info.isJSBoolean() || (node.hasConstant() && !valueOfJSConstant(nodeIndex).isInt32()); } bool SpeculativeJIT::isKnownNotNumber(NodeIndex nodeIndex) { Node& node = m_jit.graph()[nodeIndex]; VirtualRegister virtualRegister = node.virtualRegister(); GenerationInfo& info = m_generationInfo[virtualRegister]; return (!info.isJSDouble() && !info.isJSInteger() && !info.isUnknownJS()) || (node.hasConstant() && !isNumberConstant(nodeIndex)); } void SpeculativeJIT::writeBarrier(MacroAssembler& jit, GPRReg owner, GPRReg scratch1, GPRReg scratch2, WriteBarrierUseKind useKind) { UNUSED_PARAM(jit); UNUSED_PARAM(owner); UNUSED_PARAM(scratch1); UNUSED_PARAM(scratch2); UNUSED_PARAM(useKind); ASSERT(owner != scratch1); ASSERT(owner != scratch2); ASSERT(scratch1 != scratch2); #if ENABLE(WRITE_BARRIER_PROFILING) JITCompiler::emitCount(jit, WriteBarrierCounters::jitCounterFor(useKind)); #endif markCellCard(jit, owner, scratch1, scratch2); } void SpeculativeJIT::markCellCard(MacroAssembler& jit, GPRReg owner, GPRReg scratch1, GPRReg scratch2) { UNUSED_PARAM(jit); UNUSED_PARAM(owner); UNUSED_PARAM(scratch1); UNUSED_PARAM(scratch2); #if ENABLE(GGC) jit.move(owner, scratch1); jit.andPtr(TrustedImm32(static_cast(MarkedBlock::blockMask)), scratch1); jit.move(owner, scratch2); // consume additional 8 bits as we're using an approximate filter jit.rshift32(TrustedImm32(MarkedBlock::atomShift + 8), scratch2); jit.andPtr(TrustedImm32(MarkedBlock::atomMask >> 8), scratch2); MacroAssembler::Jump filter = jit.branchTest8(MacroAssembler::Zero, MacroAssembler::BaseIndex(scratch1, scratch2, MacroAssembler::TimesOne, MarkedBlock::offsetOfMarks())); jit.move(owner, scratch2); jit.rshift32(TrustedImm32(MarkedBlock::cardShift), scratch2); jit.andPtr(TrustedImm32(MarkedBlock::cardMask), scratch2); jit.store8(TrustedImm32(1), MacroAssembler::BaseIndex(scratch1, scratch2, MacroAssembler::TimesOne, MarkedBlock::offsetOfCards())); filter.link(&jit); #endif } void SpeculativeJIT::writeBarrier(GPRReg ownerGPR, GPRReg valueGPR, Edge valueUse, WriteBarrierUseKind useKind, GPRReg scratch1, GPRReg scratch2) { UNUSED_PARAM(ownerGPR); UNUSED_PARAM(valueGPR); UNUSED_PARAM(scratch1); UNUSED_PARAM(scratch2); UNUSED_PARAM(useKind); if (isKnownNotCell(valueUse.index())) return; #if ENABLE(WRITE_BARRIER_PROFILING) JITCompiler::emitCount(m_jit, WriteBarrierCounters::jitCounterFor(useKind)); #endif #if ENABLE(GGC) GPRTemporary temp1; GPRTemporary temp2; if (scratch1 == InvalidGPRReg) { GPRTemporary scratchGPR(this); temp1.adopt(scratchGPR); scratch1 = temp1.gpr(); } if (scratch2 == InvalidGPRReg) { GPRTemporary scratchGPR(this); temp2.adopt(scratchGPR); scratch2 = temp2.gpr(); } JITCompiler::Jump rhsNotCell; bool hadCellCheck = false; if (!isKnownCell(valueUse.index()) && !isCellSpeculation(m_jit.getSpeculation(valueUse.index()))) { hadCellCheck = true; rhsNotCell = m_jit.branchIfNotCell(valueGPR); } markCellCard(m_jit, ownerGPR, scratch1, scratch2); if (hadCellCheck) rhsNotCell.link(&m_jit); #endif } void SpeculativeJIT::writeBarrier(GPRReg ownerGPR, JSCell* value, WriteBarrierUseKind useKind, GPRReg scratch1, GPRReg scratch2) { UNUSED_PARAM(ownerGPR); UNUSED_PARAM(value); UNUSED_PARAM(scratch1); UNUSED_PARAM(scratch2); UNUSED_PARAM(useKind); if (Heap::isMarked(value)) return; #if ENABLE(WRITE_BARRIER_PROFILING) JITCompiler::emitCount(m_jit, WriteBarrierCounters::jitCounterFor(useKind)); #endif #if ENABLE(GGC) GPRTemporary temp1; GPRTemporary temp2; if (scratch1 == InvalidGPRReg) { GPRTemporary scratchGPR(this); temp1.adopt(scratchGPR); scratch1 = temp1.gpr(); } if (scratch2 == InvalidGPRReg) { GPRTemporary scratchGPR(this); temp2.adopt(scratchGPR); scratch2 = temp2.gpr(); } markCellCard(m_jit, ownerGPR, scratch1, scratch2); #endif } void SpeculativeJIT::writeBarrier(JSCell* owner, GPRReg valueGPR, Edge valueUse, WriteBarrierUseKind useKind, GPRReg scratch) { UNUSED_PARAM(owner); UNUSED_PARAM(valueGPR); UNUSED_PARAM(scratch); UNUSED_PARAM(useKind); if (isKnownNotCell(valueUse.index())) return; #if ENABLE(WRITE_BARRIER_PROFILING) JITCompiler::emitCount(m_jit, WriteBarrierCounters::jitCounterFor(useKind)); #endif #if ENABLE(GGC) JITCompiler::Jump rhsNotCell; bool hadCellCheck = false; if (!isKnownCell(valueUse.index()) && !isCellSpeculation(m_jit.getSpeculation(valueUse.index()))) { hadCellCheck = true; rhsNotCell = m_jit.branchIfNotCell(valueGPR); } GPRTemporary temp; if (scratch == InvalidGPRReg) { GPRTemporary scratchGPR(this); temp.adopt(scratchGPR); scratch = temp.gpr(); } uint8_t* cardAddress = Heap::addressOfCardFor(owner); m_jit.move(JITCompiler::TrustedImmPtr(cardAddress), scratch); m_jit.store8(JITCompiler::TrustedImm32(1), JITCompiler::Address(scratch)); if (hadCellCheck) rhsNotCell.link(&m_jit); #endif } bool SpeculativeJIT::nonSpeculativeCompare(Node& node, MacroAssembler::RelationalCondition cond, S_DFGOperation_EJJ helperFunction) { unsigned branchIndexInBlock = detectPeepHoleBranch(); if (branchIndexInBlock != UINT_MAX) { NodeIndex branchNodeIndex = m_jit.graph().m_blocks[m_block]->at(branchIndexInBlock); ASSERT(node.adjustedRefCount() == 1); nonSpeculativePeepholeBranch(node, branchNodeIndex, cond, helperFunction); m_indexInBlock = branchIndexInBlock; m_compileIndex = branchNodeIndex; return true; } nonSpeculativeNonPeepholeCompare(node, cond, helperFunction); return false; } bool SpeculativeJIT::nonSpeculativeStrictEq(Node& node, bool invert) { unsigned branchIndexInBlock = detectPeepHoleBranch(); if (branchIndexInBlock != UINT_MAX) { NodeIndex branchNodeIndex = m_jit.graph().m_blocks[m_block]->at(branchIndexInBlock); ASSERT(node.adjustedRefCount() == 1); nonSpeculativePeepholeStrictEq(node, branchNodeIndex, invert); m_indexInBlock = branchIndexInBlock; m_compileIndex = branchNodeIndex; return true; } nonSpeculativeNonPeepholeStrictEq(node, invert); return false; } #ifndef NDEBUG static const char* dataFormatString(DataFormat format) { // These values correspond to the DataFormat enum. const char* strings[] = { "[ ]", "[ i]", "[ d]", "[ c]", "Err!", "Err!", "Err!", "Err!", "[J ]", "[Ji]", "[Jd]", "[Jc]", "Err!", "Err!", "Err!", "Err!", }; return strings[format]; } void SpeculativeJIT::dump(const char* label) { if (label) dataLogF("<%s>\n", label); dataLogF(" gprs:\n"); m_gprs.dump(); dataLogF(" fprs:\n"); m_fprs.dump(); dataLogF(" VirtualRegisters:\n"); for (unsigned i = 0; i < m_generationInfo.size(); ++i) { GenerationInfo& info = m_generationInfo[i]; if (info.alive()) dataLogF(" % 3d:%s%s", i, dataFormatString(info.registerFormat()), dataFormatString(info.spillFormat())); else dataLogF(" % 3d:[__][__]", i); if (info.registerFormat() == DataFormatDouble) dataLogF(":fpr%d\n", info.fpr()); else if (info.registerFormat() != DataFormatNone #if USE(JSVALUE32_64) && !(info.registerFormat() & DataFormatJS) #endif ) { ASSERT(info.gpr() != InvalidGPRReg); dataLogF(":%s\n", GPRInfo::debugName(info.gpr())); } else dataLogF("\n"); } if (label) dataLogF("\n", label); } #endif #if DFG_ENABLE(CONSISTENCY_CHECK) void SpeculativeJIT::checkConsistency() { bool failed = false; for (gpr_iterator iter = m_gprs.begin(); iter != m_gprs.end(); ++iter) { if (iter.isLocked()) { dataLogF("DFG_CONSISTENCY_CHECK failed: gpr %s is locked.\n", iter.debugName()); failed = true; } } for (fpr_iterator iter = m_fprs.begin(); iter != m_fprs.end(); ++iter) { if (iter.isLocked()) { dataLogF("DFG_CONSISTENCY_CHECK failed: fpr %s is locked.\n", iter.debugName()); failed = true; } } for (unsigned i = 0; i < m_generationInfo.size(); ++i) { VirtualRegister virtualRegister = (VirtualRegister)i; GenerationInfo& info = m_generationInfo[virtualRegister]; if (!info.alive()) continue; switch (info.registerFormat()) { case DataFormatNone: break; case DataFormatJS: case DataFormatJSInteger: case DataFormatJSDouble: case DataFormatJSCell: case DataFormatJSBoolean: #if USE(JSVALUE32_64) break; #endif case DataFormatInteger: case DataFormatCell: case DataFormatBoolean: case DataFormatStorage: { GPRReg gpr = info.gpr(); ASSERT(gpr != InvalidGPRReg); if (m_gprs.name(gpr) != virtualRegister) { dataLogF("DFG_CONSISTENCY_CHECK failed: name mismatch for virtual register %d (gpr %s).\n", virtualRegister, GPRInfo::debugName(gpr)); failed = true; } break; } case DataFormatDouble: { FPRReg fpr = info.fpr(); ASSERT(fpr != InvalidFPRReg); if (m_fprs.name(fpr) != virtualRegister) { dataLogF("DFG_CONSISTENCY_CHECK failed: name mismatch for virtual register %d (fpr %s).\n", virtualRegister, FPRInfo::debugName(fpr)); failed = true; } break; } } } for (gpr_iterator iter = m_gprs.begin(); iter != m_gprs.end(); ++iter) { VirtualRegister virtualRegister = iter.name(); if (virtualRegister == InvalidVirtualRegister) continue; GenerationInfo& info = m_generationInfo[virtualRegister]; #if USE(JSVALUE64) if (iter.regID() != info.gpr()) { dataLogF("DFG_CONSISTENCY_CHECK failed: name mismatch for gpr %s (virtual register %d).\n", iter.debugName(), virtualRegister); failed = true; } #else if (!(info.registerFormat() & DataFormatJS)) { if (iter.regID() != info.gpr()) { dataLogF("DFG_CONSISTENCY_CHECK failed: name mismatch for gpr %s (virtual register %d).\n", iter.debugName(), virtualRegister); failed = true; } } else { if (iter.regID() != info.tagGPR() && iter.regID() != info.payloadGPR()) { dataLogF("DFG_CONSISTENCY_CHECK failed: name mismatch for gpr %s (virtual register %d).\n", iter.debugName(), virtualRegister); failed = true; } } #endif } for (fpr_iterator iter = m_fprs.begin(); iter != m_fprs.end(); ++iter) { VirtualRegister virtualRegister = iter.name(); if (virtualRegister == InvalidVirtualRegister) continue; GenerationInfo& info = m_generationInfo[virtualRegister]; if (iter.regID() != info.fpr()) { dataLogF("DFG_CONSISTENCY_CHECK failed: name mismatch for fpr %s (virtual register %d).\n", iter.debugName(), virtualRegister); failed = true; } } if (failed) { dump(); CRASH(); } } #endif GPRTemporary::GPRTemporary() : m_jit(0) , m_gpr(InvalidGPRReg) { } GPRTemporary::GPRTemporary(SpeculativeJIT* jit) : m_jit(jit) , m_gpr(InvalidGPRReg) { m_gpr = m_jit->allocate(); } GPRTemporary::GPRTemporary(SpeculativeJIT* jit, GPRReg specific) : m_jit(jit) , m_gpr(InvalidGPRReg) { m_gpr = m_jit->allocate(specific); } GPRTemporary::GPRTemporary(SpeculativeJIT* jit, SpeculateIntegerOperand& op1) : m_jit(jit) , m_gpr(InvalidGPRReg) { if (m_jit->canReuse(op1.index())) m_gpr = m_jit->reuse(op1.gpr()); else m_gpr = m_jit->allocate(); } GPRTemporary::GPRTemporary(SpeculativeJIT* jit, SpeculateIntegerOperand& op1, SpeculateIntegerOperand& op2) : m_jit(jit) , m_gpr(InvalidGPRReg) { if (m_jit->canReuse(op1.index())) m_gpr = m_jit->reuse(op1.gpr()); else if (m_jit->canReuse(op2.index())) m_gpr = m_jit->reuse(op2.gpr()); else m_gpr = m_jit->allocate(); } GPRTemporary::GPRTemporary(SpeculativeJIT* jit, SpeculateStrictInt32Operand& op1) : m_jit(jit) , m_gpr(InvalidGPRReg) { if (m_jit->canReuse(op1.index())) m_gpr = m_jit->reuse(op1.gpr()); else m_gpr = m_jit->allocate(); } GPRTemporary::GPRTemporary(SpeculativeJIT* jit, IntegerOperand& op1) : m_jit(jit) , m_gpr(InvalidGPRReg) { if (m_jit->canReuse(op1.index())) m_gpr = m_jit->reuse(op1.gpr()); else m_gpr = m_jit->allocate(); } GPRTemporary::GPRTemporary(SpeculativeJIT* jit, IntegerOperand& op1, IntegerOperand& op2) : m_jit(jit) , m_gpr(InvalidGPRReg) { if (m_jit->canReuse(op1.index())) m_gpr = m_jit->reuse(op1.gpr()); else if (m_jit->canReuse(op2.index())) m_gpr = m_jit->reuse(op2.gpr()); else m_gpr = m_jit->allocate(); } GPRTemporary::GPRTemporary(SpeculativeJIT* jit, SpeculateCellOperand& op1) : m_jit(jit) , m_gpr(InvalidGPRReg) { if (m_jit->canReuse(op1.index())) m_gpr = m_jit->reuse(op1.gpr()); else m_gpr = m_jit->allocate(); } GPRTemporary::GPRTemporary(SpeculativeJIT* jit, SpeculateBooleanOperand& op1) : m_jit(jit) , m_gpr(InvalidGPRReg) { if (m_jit->canReuse(op1.index())) m_gpr = m_jit->reuse(op1.gpr()); else m_gpr = m_jit->allocate(); } #if USE(JSVALUE64) GPRTemporary::GPRTemporary(SpeculativeJIT* jit, JSValueOperand& op1) : m_jit(jit) , m_gpr(InvalidGPRReg) { if (m_jit->canReuse(op1.index())) m_gpr = m_jit->reuse(op1.gpr()); else m_gpr = m_jit->allocate(); } #else GPRTemporary::GPRTemporary(SpeculativeJIT* jit, JSValueOperand& op1, bool tag) : m_jit(jit) , m_gpr(InvalidGPRReg) { if (!op1.isDouble() && m_jit->canReuse(op1.index())) m_gpr = m_jit->reuse(tag ? op1.tagGPR() : op1.payloadGPR()); else m_gpr = m_jit->allocate(); } #endif GPRTemporary::GPRTemporary(SpeculativeJIT* jit, StorageOperand& op1) : m_jit(jit) , m_gpr(InvalidGPRReg) { if (m_jit->canReuse(op1.index())) m_gpr = m_jit->reuse(op1.gpr()); else m_gpr = m_jit->allocate(); } void GPRTemporary::adopt(GPRTemporary& other) { ASSERT(!m_jit); ASSERT(m_gpr == InvalidGPRReg); ASSERT(other.m_jit); ASSERT(other.m_gpr != InvalidGPRReg); m_jit = other.m_jit; m_gpr = other.m_gpr; other.m_jit = 0; other.m_gpr = InvalidGPRReg; } FPRTemporary::FPRTemporary(SpeculativeJIT* jit) : m_jit(jit) , m_fpr(InvalidFPRReg) { m_fpr = m_jit->fprAllocate(); } FPRTemporary::FPRTemporary(SpeculativeJIT* jit, DoubleOperand& op1) : m_jit(jit) , m_fpr(InvalidFPRReg) { if (m_jit->canReuse(op1.index())) m_fpr = m_jit->reuse(op1.fpr()); else m_fpr = m_jit->fprAllocate(); } FPRTemporary::FPRTemporary(SpeculativeJIT* jit, DoubleOperand& op1, DoubleOperand& op2) : m_jit(jit) , m_fpr(InvalidFPRReg) { if (m_jit->canReuse(op1.index())) m_fpr = m_jit->reuse(op1.fpr()); else if (m_jit->canReuse(op2.index())) m_fpr = m_jit->reuse(op2.fpr()); else m_fpr = m_jit->fprAllocate(); } FPRTemporary::FPRTemporary(SpeculativeJIT* jit, SpeculateDoubleOperand& op1) : m_jit(jit) , m_fpr(InvalidFPRReg) { if (m_jit->canReuse(op1.index())) m_fpr = m_jit->reuse(op1.fpr()); else m_fpr = m_jit->fprAllocate(); } FPRTemporary::FPRTemporary(SpeculativeJIT* jit, SpeculateDoubleOperand& op1, SpeculateDoubleOperand& op2) : m_jit(jit) , m_fpr(InvalidFPRReg) { if (m_jit->canReuse(op1.index())) m_fpr = m_jit->reuse(op1.fpr()); else if (m_jit->canReuse(op2.index())) m_fpr = m_jit->reuse(op2.fpr()); else m_fpr = m_jit->fprAllocate(); } #if USE(JSVALUE32_64) FPRTemporary::FPRTemporary(SpeculativeJIT* jit, JSValueOperand& op1) : m_jit(jit) , m_fpr(InvalidFPRReg) { if (op1.isDouble() && m_jit->canReuse(op1.index())) m_fpr = m_jit->reuse(op1.fpr()); else m_fpr = m_jit->fprAllocate(); } #endif void SpeculativeJIT::compilePeepHoleDoubleBranch(Node& node, NodeIndex branchNodeIndex, JITCompiler::DoubleCondition condition) { Node& branchNode = at(branchNodeIndex); BlockIndex taken = branchNode.takenBlockIndex(); BlockIndex notTaken = branchNode.notTakenBlockIndex(); SpeculateDoubleOperand op1(this, node.child1()); SpeculateDoubleOperand op2(this, node.child2()); branchDouble(condition, op1.fpr(), op2.fpr(), taken); jump(notTaken); } void SpeculativeJIT::compilePeepHoleObjectEquality(Node& node, NodeIndex branchNodeIndex) { Node& branchNode = at(branchNodeIndex); BlockIndex taken = branchNode.takenBlockIndex(); BlockIndex notTaken = branchNode.notTakenBlockIndex(); MacroAssembler::RelationalCondition condition = MacroAssembler::Equal; if (taken == nextBlock()) { condition = MacroAssembler::NotEqual; BlockIndex tmp = taken; taken = notTaken; notTaken = tmp; } SpeculateCellOperand op1(this, node.child1()); SpeculateCellOperand op2(this, node.child2()); GPRReg op1GPR = op1.gpr(); GPRReg op2GPR = op2.gpr(); if (m_jit.graph().globalObjectFor(node.codeOrigin)->masqueradesAsUndefinedWatchpoint()->isStillValid()) { m_jit.graph().globalObjectFor(node.codeOrigin)->masqueradesAsUndefinedWatchpoint()->add(speculationWatchpoint()); speculationCheck(BadType, JSValueSource::unboxedCell(op1GPR), node.child1().index(), m_jit.branchPtr( MacroAssembler::Equal, MacroAssembler::Address(op1GPR, JSCell::structureOffset()), MacroAssembler::TrustedImmPtr(m_jit.globalData()->stringStructure.get()))); speculationCheck(BadType, JSValueSource::unboxedCell(op2GPR), node.child2().index(), m_jit.branchPtr( MacroAssembler::Equal, MacroAssembler::Address(op2GPR, JSCell::structureOffset()), MacroAssembler::TrustedImmPtr(m_jit.globalData()->stringStructure.get()))); } else { GPRTemporary structure(this); GPRReg structureGPR = structure.gpr(); m_jit.loadPtr(MacroAssembler::Address(op1GPR, JSCell::structureOffset()), structureGPR); speculationCheck(BadType, JSValueSource::unboxedCell(op1GPR), node.child1().index(), m_jit.branchPtr( MacroAssembler::Equal, structureGPR, MacroAssembler::TrustedImmPtr(m_jit.globalData()->stringStructure.get()))); speculationCheck(BadType, JSValueSource::unboxedCell(op1GPR), node.child1().index(), m_jit.branchTest8( MacroAssembler::NonZero, MacroAssembler::Address(structureGPR, Structure::typeInfoFlagsOffset()), MacroAssembler::TrustedImm32(MasqueradesAsUndefined))); m_jit.loadPtr(MacroAssembler::Address(op2GPR, JSCell::structureOffset()), structureGPR); speculationCheck(BadType, JSValueSource::unboxedCell(op2GPR), node.child2().index(), m_jit.branchPtr( MacroAssembler::Equal, structureGPR, MacroAssembler::TrustedImmPtr(m_jit.globalData()->stringStructure.get()))); speculationCheck(BadType, JSValueSource::unboxedCell(op2GPR), node.child2().index(), m_jit.branchTest8( MacroAssembler::NonZero, MacroAssembler::Address(structureGPR, Structure::typeInfoFlagsOffset()), MacroAssembler::TrustedImm32(MasqueradesAsUndefined))); } branchPtr(condition, op1GPR, op2GPR, taken); jump(notTaken); } void SpeculativeJIT::compilePeepHoleIntegerBranch(Node& node, NodeIndex branchNodeIndex, JITCompiler::RelationalCondition condition) { Node& branchNode = at(branchNodeIndex); BlockIndex taken = branchNode.takenBlockIndex(); BlockIndex notTaken = branchNode.notTakenBlockIndex(); // The branch instruction will branch to the taken block. // If taken is next, switch taken with notTaken & invert the branch condition so we can fall through. if (taken == nextBlock()) { condition = JITCompiler::invert(condition); BlockIndex tmp = taken; taken = notTaken; notTaken = tmp; } if (isInt32Constant(node.child1().index())) { int32_t imm = valueOfInt32Constant(node.child1().index()); SpeculateIntegerOperand op2(this, node.child2()); branch32(condition, JITCompiler::Imm32(imm), op2.gpr(), taken); } else if (isInt32Constant(node.child2().index())) { SpeculateIntegerOperand op1(this, node.child1()); int32_t imm = valueOfInt32Constant(node.child2().index()); branch32(condition, op1.gpr(), JITCompiler::Imm32(imm), taken); } else { SpeculateIntegerOperand op1(this, node.child1()); SpeculateIntegerOperand op2(this, node.child2()); branch32(condition, op1.gpr(), op2.gpr(), taken); } jump(notTaken); } // Returns true if the compare is fused with a subsequent branch. bool SpeculativeJIT::compilePeepHoleBranch(Node& node, MacroAssembler::RelationalCondition condition, MacroAssembler::DoubleCondition doubleCondition, S_DFGOperation_EJJ operation) { // Fused compare & branch. unsigned branchIndexInBlock = detectPeepHoleBranch(); if (branchIndexInBlock != UINT_MAX) { NodeIndex branchNodeIndex = m_jit.graph().m_blocks[m_block]->at(branchIndexInBlock); // detectPeepHoleBranch currently only permits the branch to be the very next node, // so can be no intervening nodes to also reference the compare. ASSERT(node.adjustedRefCount() == 1); if (Node::shouldSpeculateInteger(at(node.child1()), at(node.child2()))) compilePeepHoleIntegerBranch(node, branchNodeIndex, condition); else if (Node::shouldSpeculateNumber(at(node.child1()), at(node.child2()))) compilePeepHoleDoubleBranch(node, branchNodeIndex, doubleCondition); else if (node.op() == CompareEq) { if (at(node.child1()).shouldSpeculateString() || at(node.child2()).shouldSpeculateString()) { nonSpeculativePeepholeBranch(node, branchNodeIndex, condition, operation); return true; } if (at(node.child1()).shouldSpeculateNonStringCell() && at(node.child2()).shouldSpeculateNonStringCellOrOther()) compilePeepHoleObjectToObjectOrOtherEquality(node.child1(), node.child2(), branchNodeIndex); else if (at(node.child1()).shouldSpeculateNonStringCellOrOther() && at(node.child2()).shouldSpeculateNonStringCell()) compilePeepHoleObjectToObjectOrOtherEquality(node.child2(), node.child1(), branchNodeIndex); else if (at(node.child1()).shouldSpeculateNonStringCell() && at(node.child2()).shouldSpeculateNonStringCell()) compilePeepHoleObjectEquality(node, branchNodeIndex); else { nonSpeculativePeepholeBranch(node, branchNodeIndex, condition, operation); return true; } } else { nonSpeculativePeepholeBranch(node, branchNodeIndex, condition, operation); return true; } use(node.child1()); use(node.child2()); m_indexInBlock = branchIndexInBlock; m_compileIndex = branchNodeIndex; return true; } return false; } void SpeculativeJIT::noticeOSRBirth(NodeIndex nodeIndex, Node& node) { if (!node.hasVirtualRegister()) return; VirtualRegister virtualRegister = node.virtualRegister(); GenerationInfo& info = m_generationInfo[virtualRegister]; info.noticeOSRBirth(*m_stream, nodeIndex, virtualRegister); } void SpeculativeJIT::compileMovHint(Node& node) { ASSERT(node.op() == SetLocal); m_lastSetOperand = node.local(); Node& child = at(node.child1()); noticeOSRBirth(node.child1().index(), child); if (child.op() == UInt32ToNumber) noticeOSRBirth(child.child1().index(), at(child.child1())); m_stream->appendAndLog(VariableEvent::movHint(node.child1().index(), node.local())); } void SpeculativeJIT::compile(BasicBlock& block) { ASSERT(m_compileOkay); if (!block.isReachable) return; if (!block.cfaHasVisited) { // Don't generate code for basic blocks that are unreachable according to CFA. // But to be sure that nobody has generated a jump to this block, drop in a // breakpoint here. #if !ASSERT_DISABLED m_jit.breakpoint(); #endif return; } m_blockHeads[m_block] = m_jit.label(); #if DFG_ENABLE(JIT_BREAK_ON_EVERY_BLOCK) m_jit.breakpoint(); #endif #if DFG_ENABLE(DEBUG_VERBOSE) dataLogF("Setting up state for block #%u: ", m_block); #endif m_stream->appendAndLog(VariableEvent::reset()); m_jit.jitAssertHasValidCallFrame(); ASSERT(m_arguments.size() == block.variablesAtHead.numberOfArguments()); for (size_t i = 0; i < m_arguments.size(); ++i) { ValueSource valueSource = ValueSource(ValueInJSStack); m_arguments[i] = valueSource; m_stream->appendAndLog(VariableEvent::setLocal(argumentToOperand(i), valueSource.dataFormat())); } m_state.reset(); m_state.beginBasicBlock(&block); ASSERT(m_variables.size() == block.variablesAtHead.numberOfLocals()); for (size_t i = 0; i < m_variables.size(); ++i) { NodeIndex nodeIndex = block.variablesAtHead.local(i); ValueSource valueSource; if (nodeIndex == NoNode) valueSource = ValueSource(SourceIsDead); else if (at(nodeIndex).variableAccessData()->isArgumentsAlias()) valueSource = ValueSource(ArgumentsSource); else if (at(nodeIndex).variableAccessData()->isCaptured()) valueSource = ValueSource(ValueInJSStack); else if (!at(nodeIndex).refCount()) valueSource = ValueSource(SourceIsDead); else if (at(nodeIndex).variableAccessData()->shouldUseDoubleFormat()) valueSource = ValueSource(DoubleInJSStack); else valueSource = ValueSource::forSpeculation(at(nodeIndex).variableAccessData()->argumentAwarePrediction()); m_variables[i] = valueSource; m_stream->appendAndLog(VariableEvent::setLocal(i, valueSource.dataFormat())); } m_lastSetOperand = std::numeric_limits::max(); m_codeOriginForOSR = CodeOrigin(); if (DFG_ENABLE_EDGE_CODE_VERIFICATION) { JITCompiler::Jump verificationSucceeded = m_jit.branch32(JITCompiler::Equal, GPRInfo::regT0, TrustedImm32(m_block)); m_jit.breakpoint(); verificationSucceeded.link(&m_jit); } #if DFG_ENABLE(DEBUG_VERBOSE) dataLogF("\n"); #endif for (m_indexInBlock = 0; m_indexInBlock < block.size(); ++m_indexInBlock) { m_compileIndex = block[m_indexInBlock]; m_jit.setForNode(m_compileIndex); Node& node = at(m_compileIndex); m_codeOriginForOSR = node.codeOrigin; if (!node.shouldGenerate()) { #if DFG_ENABLE(DEBUG_VERBOSE) dataLogF("SpeculativeJIT skipping Node @%d (bc#%u) at JIT offset 0x%x ", (int)m_compileIndex, node.codeOrigin.bytecodeIndex, m_jit.debugOffset()); #endif switch (node.op()) { case JSConstant: m_minifiedGraph->append(MinifiedNode::fromNode(m_compileIndex, node)); break; case WeakJSConstant: m_jit.addWeakReference(node.weakConstant()); m_minifiedGraph->append(MinifiedNode::fromNode(m_compileIndex, node)); break; case SetLocal: compileMovHint(node); break; case InlineStart: { InlineCallFrame* inlineCallFrame = node.codeOrigin.inlineCallFrame; int argumentCountIncludingThis = inlineCallFrame->arguments.size(); unsigned argumentPositionStart = node.argumentPositionStart(); CodeBlock* codeBlock = baselineCodeBlockForInlineCallFrame(inlineCallFrame); for (int i = 0; i < argumentCountIncludingThis; ++i) { ValueRecovery recovery; if (codeBlock->isCaptured(argumentToOperand(i))) recovery = ValueRecovery::alreadyInJSStack(); else { ArgumentPosition& argumentPosition = m_jit.graph().m_argumentPositions[argumentPositionStart + i]; ValueSource valueSource; if (argumentPosition.shouldUseDoubleFormat()) valueSource = ValueSource(DoubleInJSStack); else if (isInt32Speculation(argumentPosition.prediction())) valueSource = ValueSource(Int32InJSStack); else if (isCellSpeculation(argumentPosition.prediction())) valueSource = ValueSource(CellInJSStack); else if (isBooleanSpeculation(argumentPosition.prediction())) valueSource = ValueSource(BooleanInJSStack); else valueSource = ValueSource(ValueInJSStack); recovery = computeValueRecoveryFor(valueSource); } // The recovery should refer either to something that has already been // stored into the stack at the right place, or to a constant, // since the Arguments code isn't smart enough to handle anything else. // The exception is the this argument, which we don't really need to be // able to recover. #if DFG_ENABLE(DEBUG_VERBOSE) dataLogF("\nRecovery for argument %d: ", i); recovery.dump(WTF::dataFile()); #endif inlineCallFrame->arguments[i] = recovery; } break; } default: if (belongsInMinifiedGraph(node.op())) m_minifiedGraph->append(MinifiedNode::fromNode(m_compileIndex, node)); break; } } else { #if DFG_ENABLE(DEBUG_VERBOSE) dataLogF("SpeculativeJIT generating Node @%d (bc#%u) at JIT offset 0x%x ", (int)m_compileIndex, node.codeOrigin.bytecodeIndex, m_jit.debugOffset()); #endif #if DFG_ENABLE(JIT_BREAK_ON_EVERY_NODE) m_jit.breakpoint(); #endif #if DFG_ENABLE(XOR_DEBUG_AID) m_jit.xorPtr(JITCompiler::TrustedImm32(m_compileIndex), GPRInfo::regT0); m_jit.xorPtr(JITCompiler::TrustedImm32(m_compileIndex), GPRInfo::regT0); #endif checkConsistency(); compile(node); if (!m_compileOkay) { m_compileOkay = true; clearGenerationInfo(); return; } if (belongsInMinifiedGraph(node.op())) { m_minifiedGraph->append(MinifiedNode::fromNode(m_compileIndex, node)); noticeOSRBirth(m_compileIndex, node); } #if DFG_ENABLE(DEBUG_VERBOSE) if (node.hasResult()) { GenerationInfo& info = m_generationInfo[node.virtualRegister()]; dataLogF("-> %s, vr#%d", dataFormatToString(info.registerFormat()), (int)node.virtualRegister()); if (info.registerFormat() != DataFormatNone) { if (info.registerFormat() == DataFormatDouble) dataLogF(", %s", FPRInfo::debugName(info.fpr())); #if USE(JSVALUE32_64) else if (info.registerFormat() & DataFormatJS) dataLogF(", %s %s", GPRInfo::debugName(info.tagGPR()), GPRInfo::debugName(info.payloadGPR())); #endif else dataLogF(", %s", GPRInfo::debugName(info.gpr())); } dataLogF(" "); } else dataLogF(" "); #endif } #if DFG_ENABLE(DEBUG_VERBOSE) dataLogF("\n"); #endif // Make sure that the abstract state is rematerialized for the next node. m_state.execute(m_indexInBlock); if (node.shouldGenerate()) checkConsistency(); } // Perform the most basic verification that children have been used correctly. #if !ASSERT_DISABLED for (unsigned index = 0; index < m_generationInfo.size(); ++index) { GenerationInfo& info = m_generationInfo[index]; ASSERT(!info.alive()); } #endif } // If we are making type predictions about our arguments then // we need to check that they are correct on function entry. void SpeculativeJIT::checkArgumentTypes() { ASSERT(!m_compileIndex); m_isCheckingArgumentTypes = true; m_codeOriginForOSR = CodeOrigin(0); for (size_t i = 0; i < m_arguments.size(); ++i) m_arguments[i] = ValueSource(ValueInJSStack); for (size_t i = 0; i < m_variables.size(); ++i) m_variables[i] = ValueSource(ValueInJSStack); for (int i = 0; i < m_jit.codeBlock()->numParameters(); ++i) { NodeIndex nodeIndex = m_jit.graph().m_arguments[i]; Node& node = at(nodeIndex); ASSERT(node.op() == SetArgument); if (!node.shouldGenerate()) { // The argument is dead. We don't do any checks for such arguments. continue; } VariableAccessData* variableAccessData = node.variableAccessData(); VirtualRegister virtualRegister = variableAccessData->local(); SpeculatedType predictedType = variableAccessData->prediction(); JSValueSource valueSource = JSValueSource(JITCompiler::addressFor(virtualRegister)); #if USE(JSVALUE64) if (isInt32Speculation(predictedType)) speculationCheck(BadType, valueSource, nodeIndex, m_jit.branch64(MacroAssembler::Below, JITCompiler::addressFor(virtualRegister), GPRInfo::tagTypeNumberRegister)); else if (isBooleanSpeculation(predictedType)) { GPRTemporary temp(this); m_jit.load64(JITCompiler::addressFor(virtualRegister), temp.gpr()); m_jit.xor64(TrustedImm32(static_cast(ValueFalse)), temp.gpr()); speculationCheck(BadType, valueSource, nodeIndex, m_jit.branchTest64(MacroAssembler::NonZero, temp.gpr(), TrustedImm32(static_cast(~1)))); } else if (isCellSpeculation(predictedType)) speculationCheck(BadType, valueSource, nodeIndex, m_jit.branchTest64(MacroAssembler::NonZero, JITCompiler::addressFor(virtualRegister), GPRInfo::tagMaskRegister)); #else if (isInt32Speculation(predictedType)) speculationCheck(BadType, valueSource, nodeIndex, m_jit.branch32(MacroAssembler::NotEqual, JITCompiler::tagFor(virtualRegister), TrustedImm32(JSValue::Int32Tag))); else if (isBooleanSpeculation(predictedType)) speculationCheck(BadType, valueSource, nodeIndex, m_jit.branch32(MacroAssembler::NotEqual, JITCompiler::tagFor(virtualRegister), TrustedImm32(JSValue::BooleanTag))); else if (isCellSpeculation(predictedType)) speculationCheck(BadType, valueSource, nodeIndex, m_jit.branch32(MacroAssembler::NotEqual, JITCompiler::tagFor(virtualRegister), TrustedImm32(JSValue::CellTag))); #endif } m_isCheckingArgumentTypes = false; } bool SpeculativeJIT::compile() { checkArgumentTypes(); if (DFG_ENABLE_EDGE_CODE_VERIFICATION) m_jit.move(TrustedImm32(0), GPRInfo::regT0); ASSERT(!m_compileIndex); for (m_block = 0; m_block < m_jit.graph().m_blocks.size(); ++m_block) { m_jit.setForBlock(m_block); BasicBlock* block = m_jit.graph().m_blocks[m_block].get(); if (block) compile(*block); } linkBranches(); return true; } void SpeculativeJIT::createOSREntries() { for (BlockIndex blockIndex = 0; blockIndex < m_jit.graph().m_blocks.size(); ++blockIndex) { BasicBlock* block = m_jit.graph().m_blocks[blockIndex].get(); if (!block) continue; if (!block->isOSRTarget) continue; // Currently we only need to create OSR entry trampolines when using edge code // verification. But in the future, we'll need this for other things as well (like // when we have global reg alloc). // If we don't need OSR entry trampolin if (!DFG_ENABLE_EDGE_CODE_VERIFICATION) { m_osrEntryHeads.append(m_blockHeads[blockIndex]); continue; } m_osrEntryHeads.append(m_jit.label()); m_jit.move(TrustedImm32(blockIndex), GPRInfo::regT0); m_jit.jump().linkTo(m_blockHeads[blockIndex], &m_jit); } } void SpeculativeJIT::linkOSREntries(LinkBuffer& linkBuffer) { unsigned osrEntryIndex = 0; for (BlockIndex blockIndex = 0; blockIndex < m_jit.graph().m_blocks.size(); ++blockIndex) { BasicBlock* block = m_jit.graph().m_blocks[blockIndex].get(); if (!block) continue; if (!block->isOSRTarget) continue; m_jit.noticeOSREntry(*block, m_osrEntryHeads[osrEntryIndex++], linkBuffer); } ASSERT(osrEntryIndex == m_osrEntryHeads.size()); } ValueRecovery SpeculativeJIT::computeValueRecoveryFor(const ValueSource& valueSource) { if (valueSource.isInJSStack()) return valueSource.valueRecovery(); ASSERT(valueSource.kind() == HaveNode); if (isConstant(valueSource.nodeIndex())) return ValueRecovery::constant(valueOfJSConstant(valueSource.nodeIndex())); return ValueRecovery(); } void SpeculativeJIT::compileDoublePutByVal(Node& node, SpeculateCellOperand& base, SpeculateStrictInt32Operand& property) { Edge child3 = m_jit.graph().varArgChild(node, 2); Edge child4 = m_jit.graph().varArgChild(node, 3); ArrayMode arrayMode = node.arrayMode(); GPRReg baseReg = base.gpr(); GPRReg propertyReg = property.gpr(); SpeculateDoubleOperand value(this, child3); FPRReg valueReg = value.fpr(); if (!isRealNumberSpeculation(m_state.forNode(child3).m_type)) { // FIXME: We need a way of profiling these, and we need to hoist them into // SpeculateDoubleOperand. speculationCheck( BadType, JSValueRegs(), NoNode, m_jit.branchDouble(MacroAssembler::DoubleNotEqualOrUnordered, valueReg, valueReg)); } if (!m_compileOkay) return; StorageOperand storage(this, child4); GPRReg storageReg = storage.gpr(); if (node.op() == PutByValAlias) { // Store the value to the array. GPRReg propertyReg = property.gpr(); FPRReg valueReg = value.fpr(); m_jit.storeDouble(valueReg, MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesEight)); noResult(m_compileIndex); return; } GPRTemporary temporary; GPRReg temporaryReg = temporaryRegisterForPutByVal(temporary, node); MacroAssembler::JumpList slowCases; if (arrayMode.isInBounds()) { speculationCheck( Uncountable, JSValueRegs(), NoNode, m_jit.branch32(MacroAssembler::AboveOrEqual, propertyReg, MacroAssembler::Address(storageReg, Butterfly::offsetOfPublicLength()))); } else { MacroAssembler::Jump inBounds = m_jit.branch32(MacroAssembler::Below, propertyReg, MacroAssembler::Address(storageReg, Butterfly::offsetOfPublicLength())); slowCases.append(m_jit.branch32(MacroAssembler::AboveOrEqual, propertyReg, MacroAssembler::Address(storageReg, Butterfly::offsetOfVectorLength()))); if (!arrayMode.isOutOfBounds()) speculationCheck(Uncountable, JSValueRegs(), NoNode, slowCases); m_jit.add32(TrustedImm32(1), propertyReg, temporaryReg); m_jit.store32(temporaryReg, MacroAssembler::Address(storageReg, Butterfly::offsetOfPublicLength())); inBounds.link(&m_jit); } m_jit.storeDouble(valueReg, MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesEight)); base.use(); property.use(); value.use(); storage.use(); if (arrayMode.isOutOfBounds()) { addSlowPathGenerator( slowPathCall( slowCases, this, m_jit.codeBlock()->isStrictMode() ? operationPutDoubleByValBeyondArrayBoundsStrict : operationPutDoubleByValBeyondArrayBoundsNonStrict, NoResult, baseReg, propertyReg, valueReg)); } noResult(m_compileIndex, UseChildrenCalledExplicitly); } void SpeculativeJIT::compileGetCharCodeAt(Node& node) { SpeculateCellOperand string(this, node.child1()); SpeculateStrictInt32Operand index(this, node.child2()); StorageOperand storage(this, node.child3()); GPRReg stringReg = string.gpr(); GPRReg indexReg = index.gpr(); GPRReg storageReg = storage.gpr(); ASSERT(speculationChecked(m_state.forNode(node.child1()).m_type, SpecString)); // unsigned comparison so we can filter out negative indices and indices that are too large speculationCheck(Uncountable, JSValueRegs(), NoNode, m_jit.branch32(MacroAssembler::AboveOrEqual, indexReg, MacroAssembler::Address(stringReg, JSString::offsetOfLength()))); GPRTemporary scratch(this); GPRReg scratchReg = scratch.gpr(); m_jit.loadPtr(MacroAssembler::Address(stringReg, JSString::offsetOfValue()), scratchReg); // Load the character into scratchReg JITCompiler::Jump is16Bit = m_jit.branchTest32(MacroAssembler::Zero, MacroAssembler::Address(scratchReg, StringImpl::flagsOffset()), TrustedImm32(StringImpl::flagIs8Bit())); m_jit.load8(MacroAssembler::BaseIndex(storageReg, indexReg, MacroAssembler::TimesOne, 0), scratchReg); JITCompiler::Jump cont8Bit = m_jit.jump(); is16Bit.link(&m_jit); m_jit.load16(MacroAssembler::BaseIndex(storageReg, indexReg, MacroAssembler::TimesTwo, 0), scratchReg); cont8Bit.link(&m_jit); integerResult(scratchReg, m_compileIndex); } void SpeculativeJIT::compileGetByValOnString(Node& node) { SpeculateCellOperand base(this, node.child1()); SpeculateStrictInt32Operand property(this, node.child2()); StorageOperand storage(this, node.child3()); GPRReg baseReg = base.gpr(); GPRReg propertyReg = property.gpr(); GPRReg storageReg = storage.gpr(); ASSERT(ArrayMode(Array::String).alreadyChecked(m_jit.graph(), node, m_state.forNode(node.child1()))); // unsigned comparison so we can filter out negative indices and indices that are too large speculationCheck(Uncountable, JSValueRegs(), NoNode, m_jit.branch32(MacroAssembler::AboveOrEqual, propertyReg, MacroAssembler::Address(baseReg, JSString::offsetOfLength()))); GPRTemporary scratch(this); GPRReg scratchReg = scratch.gpr(); m_jit.loadPtr(MacroAssembler::Address(baseReg, JSString::offsetOfValue()), scratchReg); // Load the character into scratchReg JITCompiler::Jump is16Bit = m_jit.branchTest32(MacroAssembler::Zero, MacroAssembler::Address(scratchReg, StringImpl::flagsOffset()), TrustedImm32(StringImpl::flagIs8Bit())); m_jit.load8(MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesOne, 0), scratchReg); JITCompiler::Jump cont8Bit = m_jit.jump(); is16Bit.link(&m_jit); m_jit.load16(MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesTwo, 0), scratchReg); // We only support ascii characters speculationCheck(Uncountable, JSValueRegs(), NoNode, m_jit.branch32(MacroAssembler::AboveOrEqual, scratchReg, TrustedImm32(0x100))); // 8 bit string values don't need the isASCII check. cont8Bit.link(&m_jit); GPRTemporary smallStrings(this); GPRReg smallStringsReg = smallStrings.gpr(); m_jit.move(MacroAssembler::TrustedImmPtr(m_jit.globalData()->smallStrings.singleCharacterStrings()), smallStringsReg); m_jit.loadPtr(MacroAssembler::BaseIndex(smallStringsReg, scratchReg, MacroAssembler::ScalePtr, 0), scratchReg); speculationCheck(Uncountable, JSValueRegs(), NoNode, m_jit.branchTest32(MacroAssembler::Zero, scratchReg)); cellResult(scratchReg, m_compileIndex); } GeneratedOperandType SpeculativeJIT::checkGeneratedTypeForToInt32(NodeIndex nodeIndex) { #if DFG_ENABLE(DEBUG_VERBOSE) dataLogF("checkGeneratedTypeForToInt32@%d ", nodeIndex); #endif Node& node = at(nodeIndex); VirtualRegister virtualRegister = node.virtualRegister(); GenerationInfo& info = m_generationInfo[virtualRegister]; if (info.registerFormat() == DataFormatNone) { if (node.hasConstant()) { if (isInt32Constant(nodeIndex)) return GeneratedOperandInteger; if (isNumberConstant(nodeIndex)) return GeneratedOperandDouble; terminateSpeculativeExecution(Uncountable, JSValueRegs(), NoNode); return GeneratedOperandTypeUnknown; } if (info.spillFormat() == DataFormatDouble) return GeneratedOperandDouble; } switch (info.registerFormat()) { case DataFormatBoolean: // This type never occurs. case DataFormatStorage: ASSERT_NOT_REACHED(); case DataFormatCell: terminateSpeculativeExecution(Uncountable, JSValueRegs(), NoNode); return GeneratedOperandTypeUnknown; case DataFormatNone: case DataFormatJSCell: case DataFormatJS: case DataFormatJSBoolean: return GeneratedOperandJSValue; case DataFormatJSInteger: case DataFormatInteger: return GeneratedOperandInteger; case DataFormatJSDouble: case DataFormatDouble: return GeneratedOperandDouble; default: ASSERT_NOT_REACHED(); return GeneratedOperandTypeUnknown; } } void SpeculativeJIT::compileValueToInt32(Node& node) { if (at(node.child1()).shouldSpeculateInteger()) { SpeculateIntegerOperand op1(this, node.child1()); GPRTemporary result(this, op1); m_jit.move(op1.gpr(), result.gpr()); integerResult(result.gpr(), m_compileIndex, op1.format()); return; } if (at(node.child1()).shouldSpeculateNumber()) { switch (checkGeneratedTypeForToInt32(node.child1().index())) { case GeneratedOperandInteger: { SpeculateIntegerOperand op1(this, node.child1()); GPRTemporary result(this, op1); m_jit.move(op1.gpr(), result.gpr()); integerResult(result.gpr(), m_compileIndex, op1.format()); return; } case GeneratedOperandDouble: { GPRTemporary result(this); DoubleOperand op1(this, node.child1()); FPRReg fpr = op1.fpr(); GPRReg gpr = result.gpr(); JITCompiler::Jump notTruncatedToInteger = m_jit.branchTruncateDoubleToInt32(fpr, gpr, JITCompiler::BranchIfTruncateFailed); addSlowPathGenerator(slowPathCall(notTruncatedToInteger, this, toInt32, gpr, fpr)); integerResult(gpr, m_compileIndex); return; } case GeneratedOperandJSValue: { GPRTemporary result(this); #if USE(JSVALUE64) JSValueOperand op1(this, node.child1()); GPRReg gpr = op1.gpr(); GPRReg resultGpr = result.gpr(); FPRTemporary tempFpr(this); FPRReg fpr = tempFpr.fpr(); JITCompiler::Jump isInteger = m_jit.branch64(MacroAssembler::AboveOrEqual, gpr, GPRInfo::tagTypeNumberRegister); if (!isNumberSpeculation(m_state.forNode(node.child1()).m_type)) speculationCheck(BadType, JSValueRegs(gpr), node.child1().index(), m_jit.branchTest64(MacroAssembler::Zero, gpr, GPRInfo::tagTypeNumberRegister)); // First, if we get here we have a double encoded as a JSValue m_jit.move(gpr, resultGpr); unboxDouble(resultGpr, fpr); silentSpillAllRegisters(resultGpr); callOperation(toInt32, resultGpr, fpr); silentFillAllRegisters(resultGpr); JITCompiler::Jump converted = m_jit.jump(); isInteger.link(&m_jit); m_jit.zeroExtend32ToPtr(gpr, resultGpr); converted.link(&m_jit); #else Node& childNode = at(node.child1().index()); VirtualRegister virtualRegister = childNode.virtualRegister(); GenerationInfo& info = m_generationInfo[virtualRegister]; JSValueOperand op1(this, node.child1()); GPRReg payloadGPR = op1.payloadGPR(); GPRReg resultGpr = result.gpr(); if (info.registerFormat() == DataFormatJSInteger) m_jit.move(payloadGPR, resultGpr); else { GPRReg tagGPR = op1.tagGPR(); FPRTemporary tempFpr(this); FPRReg fpr = tempFpr.fpr(); FPRTemporary scratch(this); JITCompiler::Jump isInteger = m_jit.branch32(MacroAssembler::Equal, tagGPR, TrustedImm32(JSValue::Int32Tag)); if (!isNumberSpeculation(m_state.forNode(node.child1()).m_type)) speculationCheck(BadType, JSValueRegs(tagGPR, payloadGPR), node.child1().index(), m_jit.branch32(MacroAssembler::AboveOrEqual, tagGPR, TrustedImm32(JSValue::LowestTag))); unboxDouble(tagGPR, payloadGPR, fpr, scratch.fpr()); silentSpillAllRegisters(resultGpr); callOperation(toInt32, resultGpr, fpr); silentFillAllRegisters(resultGpr); JITCompiler::Jump converted = m_jit.jump(); isInteger.link(&m_jit); m_jit.move(payloadGPR, resultGpr); converted.link(&m_jit); } #endif integerResult(resultGpr, m_compileIndex); return; } case GeneratedOperandTypeUnknown: ASSERT_NOT_REACHED(); break; } } if (at(node.child1()).shouldSpeculateBoolean()) { SpeculateBooleanOperand op1(this, node.child1()); GPRTemporary result(this, op1); m_jit.move(op1.gpr(), result.gpr()); m_jit.and32(JITCompiler::TrustedImm32(1), result.gpr()); integerResult(result.gpr(), m_compileIndex); return; } // Do it the safe way. nonSpeculativeValueToInt32(node); return; } void SpeculativeJIT::compileUInt32ToNumber(Node& node) { if (!nodeCanSpeculateInteger(node.arithNodeFlags())) { // We know that this sometimes produces doubles. So produce a double every // time. This at least allows subsequent code to not have weird conditionals. IntegerOperand op1(this, node.child1()); FPRTemporary result(this); GPRReg inputGPR = op1.gpr(); FPRReg outputFPR = result.fpr(); m_jit.convertInt32ToDouble(inputGPR, outputFPR); JITCompiler::Jump positive = m_jit.branch32(MacroAssembler::GreaterThanOrEqual, inputGPR, TrustedImm32(0)); m_jit.addDouble(JITCompiler::AbsoluteAddress(&AssemblyHelpers::twoToThe32), outputFPR); positive.link(&m_jit); doubleResult(outputFPR, m_compileIndex); return; } IntegerOperand op1(this, node.child1()); GPRTemporary result(this); // For the benefit of OSR exit, force these to be in different registers. In reality the OSR exit compiler could find cases where you have uint32(%r1) followed by int32(%r1) and then use different registers, but that seems like too much effort. m_jit.move(op1.gpr(), result.gpr()); // Test the operand is positive. This is a very special speculation check - we actually // use roll-forward speculation here, where if this fails, we jump to the baseline // instruction that follows us, rather than the one we're executing right now. We have // to do this because by this point, the original values necessary to compile whatever // operation the UInt32ToNumber originated from might be dead. forwardSpeculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branch32(MacroAssembler::LessThan, result.gpr(), TrustedImm32(0)), ValueRecovery::uint32InGPR(result.gpr())); integerResult(result.gpr(), m_compileIndex, op1.format()); } void SpeculativeJIT::compileDoubleAsInt32(Node& node) { SpeculateDoubleOperand op1(this, node.child1()); FPRTemporary scratch(this); GPRTemporary result(this); FPRReg valueFPR = op1.fpr(); FPRReg scratchFPR = scratch.fpr(); GPRReg resultGPR = result.gpr(); JITCompiler::JumpList failureCases; m_jit.branchConvertDoubleToInt32(valueFPR, resultGPR, failureCases, scratchFPR); forwardSpeculationCheck(Overflow, JSValueRegs(), NoNode, failureCases, ValueRecovery::inFPR(valueFPR)); integerResult(resultGPR, m_compileIndex); } void SpeculativeJIT::compileInt32ToDouble(Node& node) { #if USE(JSVALUE64) // On JSVALUE64 we have a way of loading double constants in a more direct manner // than a int->double conversion. On 32_64, unfortunately, we currently don't have // any such mechanism - though we could have it, if we just provisioned some memory // in CodeBlock for the double form of integer constants. if (isInt32Constant(node.child1().index())) { FPRTemporary result(this); GPRTemporary temp(this); m_jit.move(MacroAssembler::Imm64(reinterpretDoubleToInt64(valueOfNumberConstant(node.child1().index()))), temp.gpr()); m_jit.move64ToDouble(temp.gpr(), result.fpr()); doubleResult(result.fpr(), m_compileIndex); return; } #endif if (isInt32Speculation(m_state.forNode(node.child1()).m_type)) { SpeculateIntegerOperand op1(this, node.child1()); FPRTemporary result(this); m_jit.convertInt32ToDouble(op1.gpr(), result.fpr()); doubleResult(result.fpr(), m_compileIndex); return; } JSValueOperand op1(this, node.child1()); FPRTemporary result(this); #if USE(JSVALUE64) GPRTemporary temp(this); GPRReg op1GPR = op1.gpr(); GPRReg tempGPR = temp.gpr(); FPRReg resultFPR = result.fpr(); JITCompiler::Jump isInteger = m_jit.branch64( MacroAssembler::AboveOrEqual, op1GPR, GPRInfo::tagTypeNumberRegister); if (!isNumberSpeculation(m_state.forNode(node.child1()).m_type)) { speculationCheck( BadType, JSValueRegs(op1GPR), node.child1(), m_jit.branchTest64(MacroAssembler::Zero, op1GPR, GPRInfo::tagTypeNumberRegister)); } m_jit.move(op1GPR, tempGPR); unboxDouble(tempGPR, resultFPR); JITCompiler::Jump done = m_jit.jump(); isInteger.link(&m_jit); m_jit.convertInt32ToDouble(op1GPR, resultFPR); done.link(&m_jit); #else FPRTemporary temp(this); GPRReg op1TagGPR = op1.tagGPR(); GPRReg op1PayloadGPR = op1.payloadGPR(); FPRReg tempFPR = temp.fpr(); FPRReg resultFPR = result.fpr(); JITCompiler::Jump isInteger = m_jit.branch32( MacroAssembler::Equal, op1TagGPR, TrustedImm32(JSValue::Int32Tag)); if (!isNumberSpeculation(m_state.forNode(node.child1()).m_type)) { speculationCheck( BadType, JSValueRegs(op1TagGPR, op1PayloadGPR), node.child1(), m_jit.branch32(MacroAssembler::AboveOrEqual, op1TagGPR, TrustedImm32(JSValue::LowestTag))); } unboxDouble(op1TagGPR, op1PayloadGPR, resultFPR, tempFPR); JITCompiler::Jump done = m_jit.jump(); isInteger.link(&m_jit); m_jit.convertInt32ToDouble(op1PayloadGPR, resultFPR); done.link(&m_jit); #endif doubleResult(resultFPR, m_compileIndex); } static double clampDoubleToByte(double d) { d += 0.5; if (!(d > 0)) d = 0; else if (d > 255) d = 255; return d; } static void compileClampIntegerToByte(JITCompiler& jit, GPRReg result) { MacroAssembler::Jump inBounds = jit.branch32(MacroAssembler::BelowOrEqual, result, JITCompiler::TrustedImm32(0xff)); MacroAssembler::Jump tooBig = jit.branch32(MacroAssembler::GreaterThan, result, JITCompiler::TrustedImm32(0xff)); jit.xorPtr(result, result); MacroAssembler::Jump clamped = jit.jump(); tooBig.link(&jit); jit.move(JITCompiler::TrustedImm32(255), result); clamped.link(&jit); inBounds.link(&jit); } static void compileClampDoubleToByte(JITCompiler& jit, GPRReg result, FPRReg source, FPRReg scratch) { // Unordered compare so we pick up NaN static const double zero = 0; static const double byteMax = 255; static const double half = 0.5; jit.loadDouble(&zero, scratch); MacroAssembler::Jump tooSmall = jit.branchDouble(MacroAssembler::DoubleLessThanOrEqualOrUnordered, source, scratch); jit.loadDouble(&byteMax, scratch); MacroAssembler::Jump tooBig = jit.branchDouble(MacroAssembler::DoubleGreaterThan, source, scratch); jit.loadDouble(&half, scratch); // FIXME: This should probably just use a floating point round! // https://bugs.webkit.org/show_bug.cgi?id=72054 jit.addDouble(source, scratch); jit.truncateDoubleToInt32(scratch, result); MacroAssembler::Jump truncatedInt = jit.jump(); tooSmall.link(&jit); jit.xorPtr(result, result); MacroAssembler::Jump zeroed = jit.jump(); tooBig.link(&jit); jit.move(JITCompiler::TrustedImm32(255), result); truncatedInt.link(&jit); zeroed.link(&jit); } void SpeculativeJIT::compileGetByValOnIntTypedArray(const TypedArrayDescriptor& descriptor, Node& node, size_t elementSize, TypedArraySignedness signedness) { SpeculateCellOperand base(this, node.child1()); SpeculateStrictInt32Operand property(this, node.child2()); StorageOperand storage(this, node.child3()); GPRReg baseReg = base.gpr(); GPRReg propertyReg = property.gpr(); GPRReg storageReg = storage.gpr(); GPRTemporary result(this); GPRReg resultReg = result.gpr(); ASSERT(node.arrayMode().alreadyChecked(m_jit.graph(), node, m_state.forNode(node.child1()))); speculationCheck( Uncountable, JSValueRegs(), NoNode, m_jit.branch32( MacroAssembler::AboveOrEqual, propertyReg, MacroAssembler::Address(baseReg, descriptor.m_lengthOffset))); switch (elementSize) { case 1: if (signedness == SignedTypedArray) m_jit.load8Signed(MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesOne), resultReg); else m_jit.load8(MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesOne), resultReg); break; case 2: if (signedness == SignedTypedArray) m_jit.load16Signed(MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesTwo), resultReg); else m_jit.load16(MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesTwo), resultReg); break; case 4: m_jit.load32(MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesFour), resultReg); break; default: CRASH(); } if (elementSize < 4 || signedness == SignedTypedArray) { integerResult(resultReg, m_compileIndex); return; } ASSERT(elementSize == 4 && signedness == UnsignedTypedArray); if (node.shouldSpeculateInteger()) { forwardSpeculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branch32(MacroAssembler::LessThan, resultReg, TrustedImm32(0)), ValueRecovery::uint32InGPR(resultReg)); integerResult(resultReg, m_compileIndex); return; } FPRTemporary fresult(this); m_jit.convertInt32ToDouble(resultReg, fresult.fpr()); JITCompiler::Jump positive = m_jit.branch32(MacroAssembler::GreaterThanOrEqual, resultReg, TrustedImm32(0)); m_jit.addDouble(JITCompiler::AbsoluteAddress(&AssemblyHelpers::twoToThe32), fresult.fpr()); positive.link(&m_jit); doubleResult(fresult.fpr(), m_compileIndex); } void SpeculativeJIT::compilePutByValForIntTypedArray(const TypedArrayDescriptor& descriptor, GPRReg base, GPRReg property, Node& node, size_t elementSize, TypedArraySignedness signedness, TypedArrayRounding rounding) { StorageOperand storage(this, m_jit.graph().varArgChild(node, 3)); GPRReg storageReg = storage.gpr(); Edge valueUse = m_jit.graph().varArgChild(node, 2); GPRTemporary value; GPRReg valueGPR; if (at(valueUse).isConstant()) { JSValue jsValue = valueOfJSConstant(valueUse.index()); if (!jsValue.isNumber()) { terminateSpeculativeExecution(Uncountable, JSValueRegs(), NoNode); noResult(m_compileIndex); return; } double d = jsValue.asNumber(); if (rounding == ClampRounding) { ASSERT(elementSize == 1); d = clampDoubleToByte(d); } GPRTemporary scratch(this); GPRReg scratchReg = scratch.gpr(); m_jit.move(Imm32(toInt32(d)), scratchReg); value.adopt(scratch); valueGPR = scratchReg; } else if (at(valueUse).shouldSpeculateInteger()) { SpeculateIntegerOperand valueOp(this, valueUse); GPRTemporary scratch(this); GPRReg scratchReg = scratch.gpr(); m_jit.move(valueOp.gpr(), scratchReg); if (rounding == ClampRounding) { ASSERT(elementSize == 1); compileClampIntegerToByte(m_jit, scratchReg); } value.adopt(scratch); valueGPR = scratchReg; } else if (rounding == ClampRounding) { ASSERT(elementSize == 1); SpeculateDoubleOperand valueOp(this, valueUse); GPRTemporary result(this); FPRTemporary floatScratch(this); FPRReg fpr = valueOp.fpr(); GPRReg gpr = result.gpr(); compileClampDoubleToByte(m_jit, gpr, fpr, floatScratch.fpr()); value.adopt(result); valueGPR = gpr; } else { SpeculateDoubleOperand valueOp(this, valueUse); GPRTemporary result(this); FPRReg fpr = valueOp.fpr(); GPRReg gpr = result.gpr(); MacroAssembler::Jump notNaN = m_jit.branchDouble(MacroAssembler::DoubleEqual, fpr, fpr); m_jit.xorPtr(gpr, gpr); MacroAssembler::Jump fixed = m_jit.jump(); notNaN.link(&m_jit); MacroAssembler::Jump failed; if (signedness == SignedTypedArray) failed = m_jit.branchTruncateDoubleToInt32(fpr, gpr, MacroAssembler::BranchIfTruncateFailed); else failed = m_jit.branchTruncateDoubleToUint32(fpr, gpr, MacroAssembler::BranchIfTruncateFailed); addSlowPathGenerator(slowPathCall(failed, this, toInt32, gpr, fpr)); fixed.link(&m_jit); value.adopt(result); valueGPR = gpr; } ASSERT_UNUSED(valueGPR, valueGPR != property); ASSERT(valueGPR != base); ASSERT(valueGPR != storageReg); MacroAssembler::Jump outOfBounds; if (node.op() == PutByVal) outOfBounds = m_jit.branch32(MacroAssembler::AboveOrEqual, property, MacroAssembler::Address(base, descriptor.m_lengthOffset)); switch (elementSize) { case 1: m_jit.store8(value.gpr(), MacroAssembler::BaseIndex(storageReg, property, MacroAssembler::TimesOne)); break; case 2: m_jit.store16(value.gpr(), MacroAssembler::BaseIndex(storageReg, property, MacroAssembler::TimesTwo)); break; case 4: m_jit.store32(value.gpr(), MacroAssembler::BaseIndex(storageReg, property, MacroAssembler::TimesFour)); break; default: CRASH(); } if (node.op() == PutByVal) outOfBounds.link(&m_jit); noResult(m_compileIndex); } void SpeculativeJIT::compileGetByValOnFloatTypedArray(const TypedArrayDescriptor& descriptor, Node& node, size_t elementSize) { SpeculateCellOperand base(this, node.child1()); SpeculateStrictInt32Operand property(this, node.child2()); StorageOperand storage(this, node.child3()); GPRReg baseReg = base.gpr(); GPRReg propertyReg = property.gpr(); GPRReg storageReg = storage.gpr(); ASSERT(node.arrayMode().alreadyChecked(m_jit.graph(), node, m_state.forNode(node.child1()))); FPRTemporary result(this); FPRReg resultReg = result.fpr(); speculationCheck( Uncountable, JSValueRegs(), NoNode, m_jit.branch32( MacroAssembler::AboveOrEqual, propertyReg, MacroAssembler::Address(baseReg, descriptor.m_lengthOffset))); switch (elementSize) { case 4: m_jit.loadFloat(MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesFour), resultReg); m_jit.convertFloatToDouble(resultReg, resultReg); break; case 8: { m_jit.loadDouble(MacroAssembler::BaseIndex(storageReg, propertyReg, MacroAssembler::TimesEight), resultReg); MacroAssembler::Jump notNaN = m_jit.branchDouble(MacroAssembler::DoubleEqual, resultReg, resultReg); static const double NaN = QNaN; m_jit.loadDouble(&NaN, resultReg); notNaN.link(&m_jit); break; } default: ASSERT_NOT_REACHED(); } doubleResult(resultReg, m_compileIndex); } void SpeculativeJIT::compilePutByValForFloatTypedArray(const TypedArrayDescriptor& descriptor, GPRReg base, GPRReg property, Node& node, size_t elementSize) { StorageOperand storage(this, m_jit.graph().varArgChild(node, 3)); GPRReg storageReg = storage.gpr(); Edge baseUse = m_jit.graph().varArgChild(node, 0); Edge valueUse = m_jit.graph().varArgChild(node, 2); SpeculateDoubleOperand valueOp(this, valueUse); ASSERT_UNUSED(baseUse, node.arrayMode().alreadyChecked(m_jit.graph(), m_jit.graph()[m_compileIndex], m_state.forNode(baseUse))); GPRTemporary result(this); MacroAssembler::Jump outOfBounds; if (node.op() == PutByVal) outOfBounds = m_jit.branch32(MacroAssembler::AboveOrEqual, property, MacroAssembler::Address(base, descriptor.m_lengthOffset)); switch (elementSize) { case 4: { FPRTemporary scratch(this); m_jit.moveDouble(valueOp.fpr(), scratch.fpr()); m_jit.convertDoubleToFloat(valueOp.fpr(), scratch.fpr()); m_jit.storeFloat(scratch.fpr(), MacroAssembler::BaseIndex(storageReg, property, MacroAssembler::TimesFour)); break; } case 8: m_jit.storeDouble(valueOp.fpr(), MacroAssembler::BaseIndex(storageReg, property, MacroAssembler::TimesEight)); break; default: ASSERT_NOT_REACHED(); } if (node.op() == PutByVal) outOfBounds.link(&m_jit); noResult(m_compileIndex); } void SpeculativeJIT::compileInstanceOfForObject(Node&, GPRReg valueReg, GPRReg prototypeReg, GPRReg scratchReg) { // Check that prototype is an object. m_jit.loadPtr(MacroAssembler::Address(prototypeReg, JSCell::structureOffset()), scratchReg); speculationCheck(BadType, JSValueRegs(), NoNode, m_jit.branchIfNotObject(scratchReg)); // Initialize scratchReg with the value being checked. m_jit.move(valueReg, scratchReg); // Walk up the prototype chain of the value (in scratchReg), comparing to prototypeReg. MacroAssembler::Label loop(&m_jit); m_jit.loadPtr(MacroAssembler::Address(scratchReg, JSCell::structureOffset()), scratchReg); #if USE(JSVALUE64) m_jit.load64(MacroAssembler::Address(scratchReg, Structure::prototypeOffset()), scratchReg); MacroAssembler::Jump isInstance = m_jit.branch64(MacroAssembler::Equal, scratchReg, prototypeReg); m_jit.branchTest64(MacroAssembler::Zero, scratchReg, GPRInfo::tagMaskRegister).linkTo(loop, &m_jit); #else m_jit.load32(MacroAssembler::Address(scratchReg, Structure::prototypeOffset() + OBJECT_OFFSETOF(JSValue, u.asBits.payload)), scratchReg); MacroAssembler::Jump isInstance = m_jit.branchPtr(MacroAssembler::Equal, scratchReg, prototypeReg); m_jit.branchTest32(MacroAssembler::NonZero, scratchReg).linkTo(loop, &m_jit); #endif // No match - result is false. #if USE(JSVALUE64) m_jit.move(MacroAssembler::TrustedImm64(JSValue::encode(jsBoolean(false))), scratchReg); #else m_jit.move(MacroAssembler::TrustedImm32(0), scratchReg); #endif MacroAssembler::Jump putResult = m_jit.jump(); isInstance.link(&m_jit); #if USE(JSVALUE64) m_jit.move(MacroAssembler::TrustedImm64(JSValue::encode(jsBoolean(true))), scratchReg); #else m_jit.move(MacroAssembler::TrustedImm32(1), scratchReg); #endif putResult.link(&m_jit); } void SpeculativeJIT::compileInstanceOf(Node& node) { if ((!!(at(node.child1()).prediction() & ~SpecCell) && !!(m_state.forNode(node.child1()).m_type & ~SpecCell)) || at(node.child1()).adjustedRefCount() == 1) { // It might not be a cell. Speculate less aggressively. // Or: it might only be used once (i.e. by us), so we get zero benefit // from speculating any more aggressively than we absolutely need to. JSValueOperand value(this, node.child1()); SpeculateCellOperand prototype(this, node.child2()); GPRTemporary scratch(this); GPRReg prototypeReg = prototype.gpr(); GPRReg scratchReg = scratch.gpr(); #if USE(JSVALUE64) GPRReg valueReg = value.gpr(); MacroAssembler::Jump isCell = m_jit.branchTest64(MacroAssembler::Zero, valueReg, GPRInfo::tagMaskRegister); m_jit.move(MacroAssembler::TrustedImm64(JSValue::encode(jsBoolean(false))), scratchReg); #else GPRReg valueTagReg = value.tagGPR(); GPRReg valueReg = value.payloadGPR(); MacroAssembler::Jump isCell = m_jit.branch32(MacroAssembler::Equal, valueTagReg, TrustedImm32(JSValue::CellTag)); m_jit.move(MacroAssembler::TrustedImm32(0), scratchReg); #endif MacroAssembler::Jump done = m_jit.jump(); isCell.link(&m_jit); compileInstanceOfForObject(node, valueReg, prototypeReg, scratchReg); done.link(&m_jit); #if USE(JSVALUE64) jsValueResult(scratchReg, m_compileIndex, DataFormatJSBoolean); #else booleanResult(scratchReg, m_compileIndex); #endif return; } SpeculateCellOperand value(this, node.child1()); SpeculateCellOperand prototype(this, node.child2()); GPRTemporary scratch(this); GPRReg valueReg = value.gpr(); GPRReg prototypeReg = prototype.gpr(); GPRReg scratchReg = scratch.gpr(); compileInstanceOfForObject(node, valueReg, prototypeReg, scratchReg); #if USE(JSVALUE64) jsValueResult(scratchReg, m_compileIndex, DataFormatJSBoolean); #else booleanResult(scratchReg, m_compileIndex); #endif } void SpeculativeJIT::compileSoftModulo(Node& node) { // In the fast path, the dividend value could be the final result // (in case of |dividend| < |divisor|), so we speculate it as strict int32. SpeculateStrictInt32Operand op1(this, node.child1()); #if CPU(X86) || CPU(X86_64) if (isInt32Constant(node.child2().index())) { int32_t divisor = valueOfInt32Constant(node.child2().index()); if (divisor) { GPRReg op1Gpr = op1.gpr(); GPRTemporary eax(this, X86Registers::eax); GPRTemporary edx(this, X86Registers::edx); GPRTemporary scratch(this); GPRReg scratchGPR = scratch.gpr(); GPRReg op1SaveGPR; if (op1Gpr == X86Registers::eax || op1Gpr == X86Registers::edx) { op1SaveGPR = allocate(); ASSERT(op1Gpr != op1SaveGPR); m_jit.move(op1Gpr, op1SaveGPR); } else op1SaveGPR = op1Gpr; ASSERT(op1SaveGPR != X86Registers::eax); ASSERT(op1SaveGPR != X86Registers::edx); m_jit.move(op1Gpr, eax.gpr()); m_jit.move(TrustedImm32(divisor), scratchGPR); if (divisor == -1) speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branch32(JITCompiler::Equal, eax.gpr(), TrustedImm32(-2147483647-1))); m_jit.assembler().cdq(); m_jit.assembler().idivl_r(scratchGPR); // Check that we're not about to create negative zero. // FIXME: if the node use doesn't care about neg zero, we can do this more easily. JITCompiler::Jump numeratorPositive = m_jit.branch32(JITCompiler::GreaterThanOrEqual, op1SaveGPR, TrustedImm32(0)); speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branchTest32(JITCompiler::Zero, edx.gpr())); numeratorPositive.link(&m_jit); if (op1SaveGPR != op1Gpr) unlock(op1SaveGPR); integerResult(edx.gpr(), m_compileIndex); return; } } #endif SpeculateIntegerOperand op2(this, node.child2()); #if CPU(X86) || CPU(X86_64) GPRTemporary eax(this, X86Registers::eax); GPRTemporary edx(this, X86Registers::edx); GPRReg op1GPR = op1.gpr(); GPRReg op2GPR = op2.gpr(); GPRReg op2TempGPR; GPRReg temp; GPRReg op1SaveGPR; if (op2GPR == X86Registers::eax || op2GPR == X86Registers::edx) { op2TempGPR = allocate(); temp = op2TempGPR; } else { op2TempGPR = InvalidGPRReg; if (op1GPR == X86Registers::eax) temp = X86Registers::edx; else temp = X86Registers::eax; } if (op1GPR == X86Registers::eax || op1GPR == X86Registers::edx) { op1SaveGPR = allocate(); ASSERT(op1GPR != op1SaveGPR); m_jit.move(op1GPR, op1SaveGPR); } else op1SaveGPR = op1GPR; ASSERT(temp != op1GPR); ASSERT(temp != op2GPR); ASSERT(op1SaveGPR != X86Registers::eax); ASSERT(op1SaveGPR != X86Registers::edx); m_jit.add32(JITCompiler::TrustedImm32(1), op2GPR, temp); JITCompiler::Jump safeDenominator = m_jit.branch32(JITCompiler::Above, temp, JITCompiler::TrustedImm32(1)); JITCompiler::Jump done; // FIXME: if the node is not used as number then we can do this more easily. speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branchTest32(JITCompiler::Zero, op2GPR)); speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branch32(JITCompiler::Equal, op1GPR, TrustedImm32(-2147483647-1))); safeDenominator.link(&m_jit); if (op2TempGPR != InvalidGPRReg) { m_jit.move(op2GPR, op2TempGPR); op2GPR = op2TempGPR; } m_jit.move(op1GPR, eax.gpr()); m_jit.assembler().cdq(); m_jit.assembler().idivl_r(op2GPR); if (op2TempGPR != InvalidGPRReg) unlock(op2TempGPR); // Check that we're not about to create negative zero. // FIXME: if the node use doesn't care about neg zero, we can do this more easily. JITCompiler::Jump numeratorPositive = m_jit.branch32(JITCompiler::GreaterThanOrEqual, op1SaveGPR, TrustedImm32(0)); speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branchTest32(JITCompiler::Zero, edx.gpr())); numeratorPositive.link(&m_jit); if (op1SaveGPR != op1GPR) unlock(op1SaveGPR); integerResult(edx.gpr(), m_compileIndex); #else // CPU(X86) || CPU(X86_64) --> so not X86 // Do this the *safest* way possible: call out to a C function that will do the modulo, // and then attempt to convert back. GPRReg op1GPR = op1.gpr(); GPRReg op2GPR = op2.gpr(); FPRResult result(this); flushRegisters(); callOperation(operationFModOnInts, result.fpr(), op1GPR, op2GPR); FPRTemporary scratch(this); GPRTemporary intResult(this); JITCompiler::JumpList failureCases; m_jit.branchConvertDoubleToInt32(result.fpr(), intResult.gpr(), failureCases, scratch.fpr()); speculationCheck(Overflow, JSValueRegs(), NoNode, failureCases); integerResult(intResult.gpr(), m_compileIndex); #endif // CPU(X86) || CPU(X86_64) } void SpeculativeJIT::compileAdd(Node& node) { if (m_jit.graph().addShouldSpeculateInteger(node)) { if (isNumberConstant(node.child1().index())) { int32_t imm1 = valueOfNumberConstantAsInt32(node.child1().index()); SpeculateIntegerOperand op2(this, node.child2()); GPRTemporary result(this); if (nodeCanTruncateInteger(node.arithNodeFlags())) { m_jit.move(op2.gpr(), result.gpr()); m_jit.add32(Imm32(imm1), result.gpr()); } else speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branchAdd32(MacroAssembler::Overflow, op2.gpr(), Imm32(imm1), result.gpr())); integerResult(result.gpr(), m_compileIndex); return; } if (isNumberConstant(node.child2().index())) { SpeculateIntegerOperand op1(this, node.child1()); int32_t imm2 = valueOfNumberConstantAsInt32(node.child2().index()); GPRTemporary result(this); if (nodeCanTruncateInteger(node.arithNodeFlags())) { m_jit.move(op1.gpr(), result.gpr()); m_jit.add32(Imm32(imm2), result.gpr()); } else speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branchAdd32(MacroAssembler::Overflow, op1.gpr(), Imm32(imm2), result.gpr())); integerResult(result.gpr(), m_compileIndex); return; } SpeculateIntegerOperand op1(this, node.child1()); SpeculateIntegerOperand op2(this, node.child2()); GPRTemporary result(this, op1, op2); GPRReg gpr1 = op1.gpr(); GPRReg gpr2 = op2.gpr(); GPRReg gprResult = result.gpr(); if (nodeCanTruncateInteger(node.arithNodeFlags())) { if (gpr1 == gprResult) m_jit.add32(gpr2, gprResult); else { m_jit.move(gpr2, gprResult); m_jit.add32(gpr1, gprResult); } } else { MacroAssembler::Jump check = m_jit.branchAdd32(MacroAssembler::Overflow, gpr1, gpr2, gprResult); if (gpr1 == gprResult) speculationCheck(Overflow, JSValueRegs(), NoNode, check, SpeculationRecovery(SpeculativeAdd, gprResult, gpr2)); else if (gpr2 == gprResult) speculationCheck(Overflow, JSValueRegs(), NoNode, check, SpeculationRecovery(SpeculativeAdd, gprResult, gpr1)); else speculationCheck(Overflow, JSValueRegs(), NoNode, check); } integerResult(gprResult, m_compileIndex); return; } if (Node::shouldSpeculateNumberExpectingDefined(at(node.child1()), at(node.child2()))) { SpeculateDoubleOperand op1(this, node.child1()); SpeculateDoubleOperand op2(this, node.child2()); FPRTemporary result(this, op1, op2); FPRReg reg1 = op1.fpr(); FPRReg reg2 = op2.fpr(); m_jit.addDouble(reg1, reg2, result.fpr()); doubleResult(result.fpr(), m_compileIndex); return; } if (node.op() == ValueAdd) { compileValueAdd(node); return; } // We don't handle this yet. :-( terminateSpeculativeExecution(Uncountable, JSValueRegs(), NoNode); } void SpeculativeJIT::compileArithSub(Node& node) { if (m_jit.graph().addShouldSpeculateInteger(node)) { if (isNumberConstant(node.child2().index())) { SpeculateIntegerOperand op1(this, node.child1()); int32_t imm2 = valueOfNumberConstantAsInt32(node.child2().index()); GPRTemporary result(this); if (nodeCanTruncateInteger(node.arithNodeFlags())) { m_jit.move(op1.gpr(), result.gpr()); m_jit.sub32(Imm32(imm2), result.gpr()); } else { #if ENABLE(JIT_CONSTANT_BLINDING) GPRTemporary scratch(this); speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branchSub32(MacroAssembler::Overflow, op1.gpr(), Imm32(imm2), result.gpr(), scratch.gpr())); #else speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branchSub32(MacroAssembler::Overflow, op1.gpr(), Imm32(imm2), result.gpr())); #endif } integerResult(result.gpr(), m_compileIndex); return; } if (isNumberConstant(node.child1().index())) { int32_t imm1 = valueOfNumberConstantAsInt32(node.child1().index()); SpeculateIntegerOperand op2(this, node.child2()); GPRTemporary result(this); m_jit.move(Imm32(imm1), result.gpr()); if (nodeCanTruncateInteger(node.arithNodeFlags())) m_jit.sub32(op2.gpr(), result.gpr()); else speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branchSub32(MacroAssembler::Overflow, op2.gpr(), result.gpr())); integerResult(result.gpr(), m_compileIndex); return; } SpeculateIntegerOperand op1(this, node.child1()); SpeculateIntegerOperand op2(this, node.child2()); GPRTemporary result(this); if (nodeCanTruncateInteger(node.arithNodeFlags())) { m_jit.move(op1.gpr(), result.gpr()); m_jit.sub32(op2.gpr(), result.gpr()); } else speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branchSub32(MacroAssembler::Overflow, op1.gpr(), op2.gpr(), result.gpr())); integerResult(result.gpr(), m_compileIndex); return; } SpeculateDoubleOperand op1(this, node.child1()); SpeculateDoubleOperand op2(this, node.child2()); FPRTemporary result(this, op1); FPRReg reg1 = op1.fpr(); FPRReg reg2 = op2.fpr(); m_jit.subDouble(reg1, reg2, result.fpr()); doubleResult(result.fpr(), m_compileIndex); } void SpeculativeJIT::compileArithNegate(Node& node) { if (m_jit.graph().negateShouldSpeculateInteger(node)) { SpeculateIntegerOperand op1(this, node.child1()); GPRTemporary result(this); m_jit.move(op1.gpr(), result.gpr()); if (nodeCanTruncateInteger(node.arithNodeFlags())) m_jit.neg32(result.gpr()); else { speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branchNeg32(MacroAssembler::Overflow, result.gpr())); if (!nodeCanIgnoreNegativeZero(node.arithNodeFlags())) speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branchTest32(MacroAssembler::Zero, result.gpr())); } integerResult(result.gpr(), m_compileIndex); return; } SpeculateDoubleOperand op1(this, node.child1()); FPRTemporary result(this); m_jit.negateDouble(op1.fpr(), result.fpr()); doubleResult(result.fpr(), m_compileIndex); } void SpeculativeJIT::compileArithMul(Node& node) { if (m_jit.graph().mulShouldSpeculateInteger(node)) { SpeculateIntegerOperand op1(this, node.child1()); SpeculateIntegerOperand op2(this, node.child2()); GPRTemporary result(this); GPRReg reg1 = op1.gpr(); GPRReg reg2 = op2.gpr(); // We can perform truncated multiplications if we get to this point, because if the // fixup phase could not prove that it would be safe, it would have turned us into // a double multiplication. if (nodeCanTruncateInteger(node.arithNodeFlags())) { m_jit.move(reg1, result.gpr()); m_jit.mul32(reg2, result.gpr()); } else { speculationCheck( Overflow, JSValueRegs(), NoNode, m_jit.branchMul32(MacroAssembler::Overflow, reg1, reg2, result.gpr())); } // Check for negative zero, if the users of this node care about such things. if (!nodeCanIgnoreNegativeZero(node.arithNodeFlags())) { MacroAssembler::Jump resultNonZero = m_jit.branchTest32(MacroAssembler::NonZero, result.gpr()); speculationCheck(NegativeZero, JSValueRegs(), NoNode, m_jit.branch32(MacroAssembler::LessThan, reg1, TrustedImm32(0))); speculationCheck(NegativeZero, JSValueRegs(), NoNode, m_jit.branch32(MacroAssembler::LessThan, reg2, TrustedImm32(0))); resultNonZero.link(&m_jit); } integerResult(result.gpr(), m_compileIndex); return; } SpeculateDoubleOperand op1(this, node.child1()); SpeculateDoubleOperand op2(this, node.child2()); FPRTemporary result(this, op1, op2); FPRReg reg1 = op1.fpr(); FPRReg reg2 = op2.fpr(); m_jit.mulDouble(reg1, reg2, result.fpr()); doubleResult(result.fpr(), m_compileIndex); } #if CPU(X86) || CPU(X86_64) void SpeculativeJIT::compileIntegerArithDivForX86(Node& node) { SpeculateIntegerOperand op1(this, node.child1()); SpeculateIntegerOperand op2(this, node.child2()); GPRTemporary eax(this, X86Registers::eax); GPRTemporary edx(this, X86Registers::edx); GPRReg op1GPR = op1.gpr(); GPRReg op2GPR = op2.gpr(); GPRReg op2TempGPR; GPRReg temp; if (op2GPR == X86Registers::eax || op2GPR == X86Registers::edx) { op2TempGPR = allocate(); temp = op2TempGPR; } else { op2TempGPR = InvalidGPRReg; if (op1GPR == X86Registers::eax) temp = X86Registers::edx; else temp = X86Registers::eax; } ASSERT(temp != op1GPR); ASSERT(temp != op2GPR); m_jit.add32(JITCompiler::TrustedImm32(1), op2GPR, temp); JITCompiler::Jump safeDenominator = m_jit.branch32(JITCompiler::Above, temp, JITCompiler::TrustedImm32(1)); JITCompiler::Jump done; if (nodeUsedAsNumber(node.arithNodeFlags())) { speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branchTest32(JITCompiler::Zero, op2GPR)); speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branch32(JITCompiler::Equal, op1GPR, TrustedImm32(-2147483647-1))); } else { JITCompiler::Jump zero = m_jit.branchTest32(JITCompiler::Zero, op2GPR); JITCompiler::Jump isNeg2ToThe31 = m_jit.branch32(JITCompiler::Equal, op1GPR, TrustedImm32(-2147483647-1)); zero.link(&m_jit); m_jit.move(TrustedImm32(0), eax.gpr()); isNeg2ToThe31.link(&m_jit); done = m_jit.jump(); } safeDenominator.link(&m_jit); // If the user cares about negative zero, then speculate that we're not about // to produce negative zero. if (!nodeCanIgnoreNegativeZero(node.arithNodeFlags())) { MacroAssembler::Jump numeratorNonZero = m_jit.branchTest32(MacroAssembler::NonZero, op1GPR); speculationCheck(NegativeZero, JSValueRegs(), NoNode, m_jit.branch32(MacroAssembler::LessThan, op2GPR, TrustedImm32(0))); numeratorNonZero.link(&m_jit); } if (op2TempGPR != InvalidGPRReg) { m_jit.move(op2GPR, op2TempGPR); op2GPR = op2TempGPR; } m_jit.move(op1GPR, eax.gpr()); m_jit.assembler().cdq(); m_jit.assembler().idivl_r(op2GPR); if (op2TempGPR != InvalidGPRReg) unlock(op2TempGPR); // Check that there was no remainder. If there had been, then we'd be obligated to // produce a double result instead. if (nodeUsedAsNumber(node.arithNodeFlags())) speculationCheck(Overflow, JSValueRegs(), NoNode, m_jit.branchTest32(JITCompiler::NonZero, edx.gpr())); else done.link(&m_jit); integerResult(eax.gpr(), m_compileIndex); } #endif // CPU(X86) || CPU(X86_64) void SpeculativeJIT::compileArithMod(Node& node) { if (Node::shouldSpeculateIntegerForArithmetic(at(node.child1()), at(node.child2())) && node.canSpeculateInteger()) { compileSoftModulo(node); return; } SpeculateDoubleOperand op1(this, node.child1()); SpeculateDoubleOperand op2(this, node.child2()); FPRReg op1FPR = op1.fpr(); FPRReg op2FPR = op2.fpr(); flushRegisters(); FPRResult result(this); callOperation(fmodAsDFGOperation, result.fpr(), op1FPR, op2FPR); doubleResult(result.fpr(), m_compileIndex); } // Returns true if the compare is fused with a subsequent branch. bool SpeculativeJIT::compare(Node& node, MacroAssembler::RelationalCondition condition, MacroAssembler::DoubleCondition doubleCondition, S_DFGOperation_EJJ operation) { if (compilePeepHoleBranch(node, condition, doubleCondition, operation)) return true; if (Node::shouldSpeculateInteger(at(node.child1()), at(node.child2()))) { compileIntegerCompare(node, condition); return false; } if (Node::shouldSpeculateNumber(at(node.child1()), at(node.child2()))) { compileDoubleCompare(node, doubleCondition); return false; } if (node.op() == CompareEq) { if (at(node.child1()).shouldSpeculateString() || at(node.child2()).shouldSpeculateString()) { nonSpeculativeNonPeepholeCompare(node, condition, operation); return false; } if (at(node.child1()).shouldSpeculateNonStringCell() && at(node.child2()).shouldSpeculateNonStringCellOrOther()) { compileObjectToObjectOrOtherEquality(node.child1(), node.child2()); return false; } if (at(node.child1()).shouldSpeculateNonStringCellOrOther() && at(node.child2()).shouldSpeculateNonStringCell()) { compileObjectToObjectOrOtherEquality(node.child2(), node.child1()); return false; } if (at(node.child1()).shouldSpeculateNonStringCell() && at(node.child2()).shouldSpeculateNonStringCell()) { compileObjectEquality(node); return false; } } nonSpeculativeNonPeepholeCompare(node, condition, operation); return false; } bool SpeculativeJIT::compileStrictEqForConstant(Node& node, Edge value, JSValue constant) { JSValueOperand op1(this, value); unsigned branchIndexInBlock = detectPeepHoleBranch(); if (branchIndexInBlock != UINT_MAX) { NodeIndex branchNodeIndex = m_jit.graph().m_blocks[m_block]->at(branchIndexInBlock); Node& branchNode = at(branchNodeIndex); BlockIndex taken = branchNode.takenBlockIndex(); BlockIndex notTaken = branchNode.notTakenBlockIndex(); MacroAssembler::RelationalCondition condition = MacroAssembler::Equal; // The branch instruction will branch to the taken block. // If taken is next, switch taken with notTaken & invert the branch condition so we can fall through. if (taken == nextBlock()) { condition = MacroAssembler::NotEqual; BlockIndex tmp = taken; taken = notTaken; notTaken = tmp; } #if USE(JSVALUE64) branch64(condition, op1.gpr(), MacroAssembler::TrustedImm64(JSValue::encode(constant)), taken); #else GPRReg payloadGPR = op1.payloadGPR(); GPRReg tagGPR = op1.tagGPR(); if (condition == MacroAssembler::Equal) { // Drop down if not equal, go elsewhere if equal. MacroAssembler::Jump notEqual = m_jit.branch32(MacroAssembler::NotEqual, tagGPR, MacroAssembler::Imm32(constant.tag())); branch32(MacroAssembler::Equal, payloadGPR, MacroAssembler::Imm32(constant.payload()), taken); notEqual.link(&m_jit); } else { // Drop down if equal, go elsehwere if not equal. branch32(MacroAssembler::NotEqual, tagGPR, MacroAssembler::Imm32(constant.tag()), taken); branch32(MacroAssembler::NotEqual, payloadGPR, MacroAssembler::Imm32(constant.payload()), taken); } #endif jump(notTaken); use(node.child1()); use(node.child2()); m_indexInBlock = branchIndexInBlock; m_compileIndex = branchNodeIndex; return true; } GPRTemporary result(this); #if USE(JSVALUE64) GPRReg op1GPR = op1.gpr(); GPRReg resultGPR = result.gpr(); m_jit.move(MacroAssembler::TrustedImm64(ValueFalse), resultGPR); MacroAssembler::Jump notEqual = m_jit.branch64(MacroAssembler::NotEqual, op1GPR, MacroAssembler::TrustedImm64(JSValue::encode(constant))); m_jit.or32(MacroAssembler::TrustedImm32(1), resultGPR); notEqual.link(&m_jit); jsValueResult(resultGPR, m_compileIndex, DataFormatJSBoolean); #else GPRReg op1PayloadGPR = op1.payloadGPR(); GPRReg op1TagGPR = op1.tagGPR(); GPRReg resultGPR = result.gpr(); m_jit.move(TrustedImm32(0), resultGPR); MacroAssembler::JumpList notEqual; notEqual.append(m_jit.branch32(MacroAssembler::NotEqual, op1TagGPR, MacroAssembler::Imm32(constant.tag()))); notEqual.append(m_jit.branch32(MacroAssembler::NotEqual, op1PayloadGPR, MacroAssembler::Imm32(constant.payload()))); m_jit.move(TrustedImm32(1), resultGPR); notEqual.link(&m_jit); booleanResult(resultGPR, m_compileIndex); #endif return false; } bool SpeculativeJIT::compileStrictEq(Node& node) { // 1) If either operand is a constant and that constant is not a double, integer, // or string, then do a JSValue comparison. if (isJSConstant(node.child1().index())) { JSValue value = valueOfJSConstant(node.child1().index()); if (!value.isNumber() && !value.isString()) return compileStrictEqForConstant(node, node.child2(), value); } if (isJSConstant(node.child2().index())) { JSValue value = valueOfJSConstant(node.child2().index()); if (!value.isNumber() && !value.isString()) return compileStrictEqForConstant(node, node.child1(), value); } // 2) If the operands are predicted integer, do an integer comparison. if (Node::shouldSpeculateInteger(at(node.child1()), at(node.child2()))) { unsigned branchIndexInBlock = detectPeepHoleBranch(); if (branchIndexInBlock != UINT_MAX) { NodeIndex branchNodeIndex = m_jit.graph().m_blocks[m_block]->at(branchIndexInBlock); compilePeepHoleIntegerBranch(node, branchNodeIndex, MacroAssembler::Equal); use(node.child1()); use(node.child2()); m_indexInBlock = branchIndexInBlock; m_compileIndex = branchNodeIndex; return true; } compileIntegerCompare(node, MacroAssembler::Equal); return false; } // 3) If the operands are predicted double, do a double comparison. if (Node::shouldSpeculateNumber(at(node.child1()), at(node.child2()))) { unsigned branchIndexInBlock = detectPeepHoleBranch(); if (branchIndexInBlock != UINT_MAX) { NodeIndex branchNodeIndex = m_jit.graph().m_blocks[m_block]->at(branchIndexInBlock); compilePeepHoleDoubleBranch(node, branchNodeIndex, MacroAssembler::DoubleEqual); use(node.child1()); use(node.child2()); m_indexInBlock = branchIndexInBlock; m_compileIndex = branchNodeIndex; return true; } compileDoubleCompare(node, MacroAssembler::DoubleEqual); return false; } if (at(node.child1()).shouldSpeculateString() || at(node.child2()).shouldSpeculateString()) return nonSpeculativeStrictEq(node); if (at(node.child1()).shouldSpeculateNonStringCell() && at(node.child2()).shouldSpeculateNonStringCell()) { unsigned branchIndexInBlock = detectPeepHoleBranch(); if (branchIndexInBlock != UINT_MAX) { NodeIndex branchNodeIndex = m_jit.graph().m_blocks[m_block]->at(branchIndexInBlock); compilePeepHoleObjectEquality(node, branchNodeIndex); use(node.child1()); use(node.child2()); m_indexInBlock = branchIndexInBlock; m_compileIndex = branchNodeIndex; return true; } compileObjectEquality(node); return false; } // 5) Fall back to non-speculative strict equality. return nonSpeculativeStrictEq(node); } void SpeculativeJIT::compileGetIndexedPropertyStorage(Node& node) { SpeculateCellOperand base(this, node.child1()); GPRReg baseReg = base.gpr(); GPRTemporary storage(this); GPRReg storageReg = storage.gpr(); const TypedArrayDescriptor* descriptor = typedArrayDescriptor(node.arrayMode()); switch (node.arrayMode().type()) { case Array::String: m_jit.loadPtr(MacroAssembler::Address(baseReg, JSString::offsetOfValue()), storageReg); // Speculate that we're not accessing a rope speculationCheck(Uncountable, JSValueRegs(), NoNode, m_jit.branchTest32(MacroAssembler::Zero, storageReg)); m_jit.loadPtr(MacroAssembler::Address(storageReg, StringImpl::dataOffset()), storageReg); break; default: ASSERT(descriptor); m_jit.loadPtr(MacroAssembler::Address(baseReg, descriptor->m_storageOffset), storageReg); break; } storageResult(storageReg, m_compileIndex); } void SpeculativeJIT::compileGetByValOnArguments(Node& node) { SpeculateCellOperand base(this, node.child1()); SpeculateStrictInt32Operand property(this, node.child2()); GPRTemporary result(this); #if USE(JSVALUE32_64) GPRTemporary resultTag(this); #endif GPRTemporary scratch(this); GPRReg baseReg = base.gpr(); GPRReg propertyReg = property.gpr(); GPRReg resultReg = result.gpr(); #if USE(JSVALUE32_64) GPRReg resultTagReg = resultTag.gpr(); #endif GPRReg scratchReg = scratch.gpr(); if (!m_compileOkay) return; ASSERT(ArrayMode(Array::Arguments).alreadyChecked(m_jit.graph(), node, m_state.forNode(node.child1()))); // Two really lame checks. speculationCheck( Uncountable, JSValueSource(), NoNode, m_jit.branchPtr( MacroAssembler::AboveOrEqual, propertyReg, MacroAssembler::Address(baseReg, OBJECT_OFFSETOF(Arguments, m_numArguments)))); speculationCheck( Uncountable, JSValueSource(), NoNode, m_jit.branchTestPtr( MacroAssembler::NonZero, MacroAssembler::Address( baseReg, OBJECT_OFFSETOF(Arguments, m_slowArguments)))); m_jit.move(propertyReg, resultReg); m_jit.neg32(resultReg); m_jit.signExtend32ToPtr(resultReg, resultReg); m_jit.loadPtr( MacroAssembler::Address(baseReg, OBJECT_OFFSETOF(Arguments, m_registers)), scratchReg); #if USE(JSVALUE32_64) m_jit.load32( MacroAssembler::BaseIndex( scratchReg, resultReg, MacroAssembler::TimesEight, CallFrame::thisArgumentOffset() * sizeof(Register) - sizeof(Register) + OBJECT_OFFSETOF(JSValue, u.asBits.tag)), resultTagReg); m_jit.load32( MacroAssembler::BaseIndex( scratchReg, resultReg, MacroAssembler::TimesEight, CallFrame::thisArgumentOffset() * sizeof(Register) - sizeof(Register) + OBJECT_OFFSETOF(JSValue, u.asBits.payload)), resultReg); jsValueResult(resultTagReg, resultReg, m_compileIndex); #else m_jit.load64( MacroAssembler::BaseIndex( scratchReg, resultReg, MacroAssembler::TimesEight, CallFrame::thisArgumentOffset() * sizeof(Register) - sizeof(Register)), resultReg); jsValueResult(resultReg, m_compileIndex); #endif } void SpeculativeJIT::compileGetArgumentsLength(Node& node) { SpeculateCellOperand base(this, node.child1()); GPRTemporary result(this, base); GPRReg baseReg = base.gpr(); GPRReg resultReg = result.gpr(); if (!m_compileOkay) return; ASSERT(ArrayMode(Array::Arguments).alreadyChecked(m_jit.graph(), node, m_state.forNode(node.child1()))); speculationCheck( Uncountable, JSValueSource(), NoNode, m_jit.branchTest8( MacroAssembler::NonZero, MacroAssembler::Address(baseReg, OBJECT_OFFSETOF(Arguments, m_overrodeLength)))); m_jit.load32( MacroAssembler::Address(baseReg, OBJECT_OFFSETOF(Arguments, m_numArguments)), resultReg); integerResult(resultReg, m_compileIndex); } void SpeculativeJIT::compileGetArrayLength(Node& node) { const TypedArrayDescriptor* descriptor = typedArrayDescriptor(node.arrayMode()); switch (node.arrayMode().type()) { case Array::Int32: case Array::Double: case Array::Contiguous: { StorageOperand storage(this, node.child2()); GPRTemporary result(this, storage); GPRReg storageReg = storage.gpr(); GPRReg resultReg = result.gpr(); m_jit.load32(MacroAssembler::Address(storageReg, Butterfly::offsetOfPublicLength()), resultReg); integerResult(resultReg, m_compileIndex); break; } case Array::ArrayStorage: case Array::SlowPutArrayStorage: { StorageOperand storage(this, node.child2()); GPRTemporary result(this, storage); GPRReg storageReg = storage.gpr(); GPRReg resultReg = result.gpr(); m_jit.load32(MacroAssembler::Address(storageReg, Butterfly::offsetOfPublicLength()), resultReg); speculationCheck(Uncountable, JSValueRegs(), NoNode, m_jit.branch32(MacroAssembler::LessThan, resultReg, MacroAssembler::TrustedImm32(0))); integerResult(resultReg, m_compileIndex); break; } case Array::String: { SpeculateCellOperand base(this, node.child1()); GPRTemporary result(this, base); GPRReg baseGPR = base.gpr(); GPRReg resultGPR = result.gpr(); m_jit.load32(MacroAssembler::Address(baseGPR, JSString::offsetOfLength()), resultGPR); integerResult(resultGPR, m_compileIndex); break; } case Array::Arguments: { compileGetArgumentsLength(node); break; } default: SpeculateCellOperand base(this, node.child1()); GPRTemporary result(this, base); GPRReg baseGPR = base.gpr(); GPRReg resultGPR = result.gpr(); ASSERT(descriptor); m_jit.load32(MacroAssembler::Address(baseGPR, descriptor->m_lengthOffset), resultGPR); integerResult(resultGPR, m_compileIndex); break; } } void SpeculativeJIT::compileNewFunctionNoCheck(Node& node) { GPRResult result(this); GPRReg resultGPR = result.gpr(); flushRegisters(); callOperation( operationNewFunction, resultGPR, m_jit.codeBlock()->functionDecl(node.functionDeclIndex())); cellResult(resultGPR, m_compileIndex); } void SpeculativeJIT::compileNewFunctionExpression(Node& node) { GPRResult result(this); GPRReg resultGPR = result.gpr(); flushRegisters(); callOperation( operationNewFunctionExpression, resultGPR, m_jit.codeBlock()->functionExpr(node.functionExprIndex())); cellResult(resultGPR, m_compileIndex); } bool SpeculativeJIT::compileRegExpExec(Node& node) { unsigned branchIndexInBlock = detectPeepHoleBranch(); if (branchIndexInBlock == UINT_MAX) return false; NodeIndex branchNodeIndex = m_jit.graph().m_blocks[m_block]->at(branchIndexInBlock); ASSERT(node.adjustedRefCount() == 1); Node& branchNode = at(branchNodeIndex); BlockIndex taken = branchNode.takenBlockIndex(); BlockIndex notTaken = branchNode.notTakenBlockIndex(); bool invert = false; if (taken == nextBlock()) { invert = true; BlockIndex tmp = taken; taken = notTaken; notTaken = tmp; } SpeculateCellOperand base(this, node.child1()); SpeculateCellOperand argument(this, node.child2()); GPRReg baseGPR = base.gpr(); GPRReg argumentGPR = argument.gpr(); flushRegisters(); GPRResult result(this); callOperation(operationRegExpTest, result.gpr(), baseGPR, argumentGPR); branchTest32(invert ? JITCompiler::Zero : JITCompiler::NonZero, result.gpr(), taken); jump(notTaken); use(node.child1()); use(node.child2()); m_indexInBlock = branchIndexInBlock; m_compileIndex = branchNodeIndex; return true; } void SpeculativeJIT::compileAllocatePropertyStorage(Node& node) { if (hasIndexingHeader(node.structureTransitionData().previousStructure->indexingType())) { SpeculateCellOperand base(this, node.child1()); GPRReg baseGPR = base.gpr(); flushRegisters(); GPRResult result(this); callOperation(operationReallocateButterflyToHavePropertyStorageWithInitialCapacity, result.gpr(), baseGPR); storageResult(result.gpr(), m_compileIndex); return; } SpeculateCellOperand base(this, node.child1()); GPRTemporary scratch(this); GPRReg baseGPR = base.gpr(); GPRReg scratchGPR = scratch.gpr(); ASSERT(!node.structureTransitionData().previousStructure->outOfLineCapacity()); ASSERT(initialOutOfLineCapacity == node.structureTransitionData().newStructure->outOfLineCapacity()); JITCompiler::Jump slowPath = emitAllocateBasicStorage( TrustedImm32(initialOutOfLineCapacity * sizeof(JSValue)), scratchGPR); m_jit.addPtr(JITCompiler::TrustedImm32(sizeof(JSValue)), scratchGPR); addSlowPathGenerator( slowPathCall(slowPath, this, operationAllocatePropertyStorageWithInitialCapacity, scratchGPR)); m_jit.storePtr(scratchGPR, JITCompiler::Address(baseGPR, JSObject::butterflyOffset())); storageResult(scratchGPR, m_compileIndex); } void SpeculativeJIT::compileReallocatePropertyStorage(Node& node) { size_t oldSize = node.structureTransitionData().previousStructure->outOfLineCapacity() * sizeof(JSValue); size_t newSize = oldSize * outOfLineGrowthFactor; ASSERT(newSize == node.structureTransitionData().newStructure->outOfLineCapacity() * sizeof(JSValue)); if (hasIndexingHeader(node.structureTransitionData().previousStructure->indexingType())) { SpeculateCellOperand base(this, node.child1()); GPRReg baseGPR = base.gpr(); flushRegisters(); GPRResult result(this); callOperation(operationReallocateButterflyToGrowPropertyStorage, result.gpr(), baseGPR, newSize / sizeof(JSValue)); storageResult(result.gpr(), m_compileIndex); return; } SpeculateCellOperand base(this, node.child1()); StorageOperand oldStorage(this, node.child2()); GPRTemporary scratch1(this); GPRTemporary scratch2(this); GPRReg baseGPR = base.gpr(); GPRReg oldStorageGPR = oldStorage.gpr(); GPRReg scratchGPR1 = scratch1.gpr(); GPRReg scratchGPR2 = scratch2.gpr(); JITCompiler::Jump slowPath = emitAllocateBasicStorage(TrustedImm32(newSize), scratchGPR2); m_jit.addPtr(JITCompiler::TrustedImm32(sizeof(JSValue)), scratchGPR2); addSlowPathGenerator( slowPathCall(slowPath, this, operationAllocatePropertyStorage, scratchGPR2, newSize / sizeof(JSValue))); // We have scratchGPR2 = new storage, scratchGPR1 = scratch for (ptrdiff_t offset = 0; offset < static_cast(oldSize); offset += sizeof(void*)) { m_jit.loadPtr(JITCompiler::Address(oldStorageGPR, -(offset + sizeof(JSValue) + sizeof(void*))), scratchGPR1); m_jit.storePtr(scratchGPR1, JITCompiler::Address(scratchGPR2, -(offset + sizeof(JSValue) + sizeof(void*)))); } m_jit.storePtr(scratchGPR2, JITCompiler::Address(baseGPR, JSObject::butterflyOffset())); storageResult(scratchGPR2, m_compileIndex); } GPRReg SpeculativeJIT::temporaryRegisterForPutByVal(GPRTemporary& temporary, ArrayMode arrayMode) { if (!putByValWillNeedExtraRegister(arrayMode)) return InvalidGPRReg; GPRTemporary realTemporary(this); temporary.adopt(realTemporary); return temporary.gpr(); } } } // namespace JSC::DFG #endif