/* * Copyright (C) 2014, 2015 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 "PolymorphicAccess.h" #if ENABLE(JIT) #include "BinarySwitch.h" #include "CCallHelpers.h" #include "CodeBlock.h" #include "GetterSetter.h" #include "Heap.h" #include "JITOperations.h" #include "JSCInlines.h" #include "LinkBuffer.h" #include "ScratchRegisterAllocator.h" #include "StructureStubClearingWatchpoint.h" #include "StructureStubInfo.h" #include #include namespace JSC { static const bool verbose = false; Watchpoint* AccessGenerationState::addWatchpoint(const ObjectPropertyCondition& condition) { return WatchpointsOnStructureStubInfo::ensureReferenceAndAddWatchpoint( watchpoints, jit->codeBlock(), stubInfo, condition); } void AccessGenerationState::restoreScratch() { allocator->restoreReusedRegistersByPopping(*jit, preservedReusedRegisterState); } void AccessGenerationState::succeed() { restoreScratch(); success.append(jit->jump()); } void AccessGenerationState::calculateLiveRegistersForCallAndExceptionHandling() { if (!m_calculatedRegistersForCallAndExceptionHandling) { m_calculatedRegistersForCallAndExceptionHandling = true; m_liveRegistersToPreserveAtExceptionHandlingCallSite = jit->codeBlock()->jitCode()->liveRegistersToPreserveAtExceptionHandlingCallSite(jit->codeBlock(), stubInfo->callSiteIndex); m_needsToRestoreRegistersIfException = m_liveRegistersToPreserveAtExceptionHandlingCallSite.numberOfSetRegisters() > 0; if (m_needsToRestoreRegistersIfException) RELEASE_ASSERT(JITCode::isOptimizingJIT(jit->codeBlock()->jitType())); m_liveRegistersForCall = RegisterSet(m_liveRegistersToPreserveAtExceptionHandlingCallSite, allocator->usedRegisters()); m_liveRegistersForCall.exclude(RegisterSet::registersToNotSaveForJSCall()); } } void AccessGenerationState::preserveLiveRegistersToStackForCall() { unsigned extraStackPadding = 0; unsigned numberOfStackBytesUsedForRegisterPreservation = ScratchRegisterAllocator::preserveRegistersToStackForCall(*jit, liveRegistersForCall(), extraStackPadding); if (m_numberOfStackBytesUsedForRegisterPreservation != std::numeric_limits::max()) RELEASE_ASSERT(numberOfStackBytesUsedForRegisterPreservation == m_numberOfStackBytesUsedForRegisterPreservation); m_numberOfStackBytesUsedForRegisterPreservation = numberOfStackBytesUsedForRegisterPreservation; } void AccessGenerationState::restoreLiveRegistersFromStackForCall(bool isGetter) { RegisterSet dontRestore; if (isGetter) { // This is the result value. We don't want to overwrite the result with what we stored to the stack. // We sometimes have to store it to the stack just in case we throw an exception and need the original value. dontRestore.set(valueRegs); } restoreLiveRegistersFromStackForCall(dontRestore); } void AccessGenerationState::restoreLiveRegistersFromStackForCallWithThrownException() { // Even if we're a getter, we don't want to ignore the result value like we normally do // because the getter threw, and therefore, didn't return a value that means anything. // Instead, we want to restore that register to what it was upon entering the getter // inline cache. The subtlety here is if the base and the result are the same register, // and the getter threw, we want OSR exit to see the original base value, not the result // of the getter call. RegisterSet dontRestore = liveRegistersForCall(); // As an optimization here, we only need to restore what is live for exception handling. // We can construct the dontRestore set to accomplish this goal by having it contain only // what is live for call but not live for exception handling. By ignoring things that are // only live at the call but not the exception handler, we will only restore things live // at the exception handler. dontRestore.exclude(liveRegistersToPreserveAtExceptionHandlingCallSite()); restoreLiveRegistersFromStackForCall(dontRestore); } void AccessGenerationState::restoreLiveRegistersFromStackForCall(const RegisterSet& dontRestore) { unsigned extraStackPadding = 0; ScratchRegisterAllocator::restoreRegistersFromStackForCall(*jit, liveRegistersForCall(), dontRestore, m_numberOfStackBytesUsedForRegisterPreservation, extraStackPadding); } CallSiteIndex AccessGenerationState::callSiteIndexForExceptionHandlingOrOriginal() { RELEASE_ASSERT(m_calculatedRegistersForCallAndExceptionHandling); if (!m_calculatedCallSiteIndex) { m_calculatedCallSiteIndex = true; if (m_needsToRestoreRegistersIfException) m_callSiteIndex = jit->codeBlock()->newExceptionHandlingCallSiteIndex(stubInfo->callSiteIndex); else m_callSiteIndex = originalCallSiteIndex(); } return m_callSiteIndex; } const HandlerInfo& AccessGenerationState::originalExceptionHandler() const { RELEASE_ASSERT(m_needsToRestoreRegistersIfException); HandlerInfo* exceptionHandler = jit->codeBlock()->handlerForIndex(stubInfo->callSiteIndex.bits()); RELEASE_ASSERT(exceptionHandler); return *exceptionHandler; } CallSiteIndex AccessGenerationState::originalCallSiteIndex() const { return stubInfo->callSiteIndex; } AccessCase::AccessCase() { } std::unique_ptr AccessCase::get( VM& vm, JSCell* owner, AccessType type, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, bool viaProxy, WatchpointSet* additionalSet, PropertySlot::GetValueFunc customGetter, JSObject* customSlotBase) { std::unique_ptr result(new AccessCase()); result->m_type = type; result->m_offset = offset; result->m_structure.set(vm, owner, structure); result->m_conditionSet = conditionSet; if (viaProxy || additionalSet || result->doesCalls() || customGetter || customSlotBase) { result->m_rareData = std::make_unique(); result->m_rareData->viaProxy = viaProxy; result->m_rareData->additionalSet = additionalSet; result->m_rareData->customAccessor.getter = customGetter; result->m_rareData->customSlotBase.setMayBeNull(vm, owner, customSlotBase); } return result; } std::unique_ptr AccessCase::replace( VM& vm, JSCell* owner, Structure* structure, PropertyOffset offset) { std::unique_ptr result(new AccessCase()); result->m_type = Replace; result->m_offset = offset; result->m_structure.set(vm, owner, structure); return result; } std::unique_ptr AccessCase::transition( VM& vm, JSCell* owner, Structure* oldStructure, Structure* newStructure, PropertyOffset offset, const ObjectPropertyConditionSet& conditionSet) { RELEASE_ASSERT(oldStructure == newStructure->previousID()); // Skip optimizing the case where we need a realloc, if we don't have // enough registers to make it happen. if (GPRInfo::numberOfRegisters < 6 && oldStructure->outOfLineCapacity() != newStructure->outOfLineCapacity() && oldStructure->outOfLineCapacity()) { return nullptr; } // Skip optimizing the case where we need realloc, and the structure has // indexing storage. // FIXME: We shouldn't skip this! Implement it! // https://bugs.webkit.org/show_bug.cgi?id=130914 if (oldStructure->couldHaveIndexingHeader()) return nullptr; std::unique_ptr result(new AccessCase()); result->m_type = Transition; result->m_offset = offset; result->m_structure.set(vm, owner, newStructure); result->m_conditionSet = conditionSet; return result; } std::unique_ptr AccessCase::setter( VM& vm, JSCell* owner, AccessType type, Structure* structure, PropertyOffset offset, const ObjectPropertyConditionSet& conditionSet, PutPropertySlot::PutValueFunc customSetter, JSObject* customSlotBase) { std::unique_ptr result(new AccessCase()); result->m_type = type; result->m_offset = offset; result->m_structure.set(vm, owner, structure); result->m_conditionSet = conditionSet; result->m_rareData = std::make_unique(); result->m_rareData->customAccessor.setter = customSetter; result->m_rareData->customSlotBase.setMayBeNull(vm, owner, customSlotBase); return result; } std::unique_ptr AccessCase::in( VM& vm, JSCell* owner, AccessType type, Structure* structure, const ObjectPropertyConditionSet& conditionSet) { std::unique_ptr result(new AccessCase()); result->m_type = type; result->m_structure.set(vm, owner, structure); result->m_conditionSet = conditionSet; return result; } std::unique_ptr AccessCase::getLength(VM&, JSCell*, AccessType type) { std::unique_ptr result(new AccessCase()); result->m_type = type; return result; } std::unique_ptr AccessCase::getIntrinsic( VM& vm, JSCell* owner, JSFunction* getter, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet) { std::unique_ptr result(new AccessCase()); result->m_type = IntrinsicGetter; result->m_structure.set(vm, owner, structure); result->m_conditionSet = conditionSet; result->m_offset = offset; result->m_rareData = std::make_unique(); result->m_rareData->intrinsicFunction.set(vm, owner, getter); return result; } AccessCase::~AccessCase() { } std::unique_ptr AccessCase::fromStructureStubInfo( VM& vm, JSCell* owner, StructureStubInfo& stubInfo) { switch (stubInfo.cacheType) { case CacheType::GetByIdSelf: return get( vm, owner, Load, stubInfo.u.byIdSelf.offset, stubInfo.u.byIdSelf.baseObjectStructure.get()); case CacheType::PutByIdReplace: return replace( vm, owner, stubInfo.u.byIdSelf.baseObjectStructure.get(), stubInfo.u.byIdSelf.offset); default: return nullptr; } } std::unique_ptr AccessCase::clone() const { std::unique_ptr result(new AccessCase()); result->m_type = m_type; result->m_offset = m_offset; result->m_structure = m_structure; result->m_conditionSet = m_conditionSet; if (RareData* rareData = m_rareData.get()) { result->m_rareData = std::make_unique(); result->m_rareData->viaProxy = rareData->viaProxy; result->m_rareData->additionalSet = rareData->additionalSet; // NOTE: We don't copy the callLinkInfo, since that's created during code generation. result->m_rareData->customAccessor.opaque = rareData->customAccessor.opaque; result->m_rareData->customSlotBase = rareData->customSlotBase; result->m_rareData->intrinsicFunction = rareData->intrinsicFunction; } return result; } bool AccessCase::guardedByStructureCheck() const { if (viaProxy()) return false; switch (m_type) { case ArrayLength: case StringLength: return false; default: return true; } } JSObject* AccessCase::alternateBase() const { if (customSlotBase()) return customSlotBase(); return conditionSet().slotBaseCondition().object(); } bool AccessCase::couldStillSucceed() const { return m_conditionSet.structuresEnsureValidityAssumingImpurePropertyWatchpoint(); } bool AccessCase::canReplace(const AccessCase& other) { // We could do a lot better here, but for now we just do something obvious. if (!guardedByStructureCheck() || !other.guardedByStructureCheck()) { // FIXME: Implement this! return false; } return structure() == other.structure(); } void AccessCase::dump(PrintStream& out) const { out.print(m_type, ":("); CommaPrinter comma; if (m_type == Transition) out.print(comma, "structure = ", pointerDump(structure()), " -> ", pointerDump(newStructure())); else if (m_structure) out.print(comma, "structure = ", pointerDump(m_structure.get())); if (isValidOffset(m_offset)) out.print(comma, "offset = ", m_offset); if (!m_conditionSet.isEmpty()) out.print(comma, "conditions = ", m_conditionSet); if (RareData* rareData = m_rareData.get()) { if (rareData->viaProxy) out.print(comma, "viaProxy = ", rareData->viaProxy); if (rareData->additionalSet) out.print(comma, "additionalSet = ", RawPointer(rareData->additionalSet.get())); if (rareData->callLinkInfo) out.print(comma, "callLinkInfo = ", RawPointer(rareData->callLinkInfo.get())); if (rareData->customAccessor.opaque) out.print(comma, "customAccessor = ", RawPointer(rareData->customAccessor.opaque)); if (rareData->customSlotBase) out.print(comma, "customSlotBase = ", RawPointer(rareData->customSlotBase.get())); } out.print(")"); } bool AccessCase::visitWeak(VM& vm) const { if (m_structure && !Heap::isMarked(m_structure.get())) return false; if (!m_conditionSet.areStillLive()) return false; if (m_rareData) { if (m_rareData->callLinkInfo) m_rareData->callLinkInfo->visitWeak(vm); if (m_rareData->customSlotBase && !Heap::isMarked(m_rareData->customSlotBase.get())) return false; if (m_rareData->intrinsicFunction && !Heap::isMarked(m_rareData->intrinsicFunction.get())) return false; } return true; } void AccessCase::generateWithGuard( AccessGenerationState& state, CCallHelpers::JumpList& fallThrough) { CCallHelpers& jit = *state.jit; switch (m_type) { case ArrayLength: { ASSERT(!viaProxy()); jit.load8(CCallHelpers::Address(state.baseGPR, JSCell::indexingTypeOffset()), state.scratchGPR); fallThrough.append( jit.branchTest32( CCallHelpers::Zero, state.scratchGPR, CCallHelpers::TrustedImm32(IsArray))); fallThrough.append( jit.branchTest32( CCallHelpers::Zero, state.scratchGPR, CCallHelpers::TrustedImm32(IndexingShapeMask))); break; } case StringLength: { ASSERT(!viaProxy()); fallThrough.append( jit.branch8( CCallHelpers::NotEqual, CCallHelpers::Address(state.baseGPR, JSCell::typeInfoTypeOffset()), CCallHelpers::TrustedImm32(StringType))); break; } default: { if (viaProxy()) { fallThrough.append( jit.branch8( CCallHelpers::NotEqual, CCallHelpers::Address(state.baseGPR, JSCell::typeInfoTypeOffset()), CCallHelpers::TrustedImm32(PureForwardingProxyType))); jit.loadPtr( CCallHelpers::Address(state.baseGPR, JSProxy::targetOffset()), state.scratchGPR); fallThrough.append( jit.branchStructure( CCallHelpers::NotEqual, CCallHelpers::Address(state.scratchGPR, JSCell::structureIDOffset()), structure())); } else { fallThrough.append( jit.branchStructure( CCallHelpers::NotEqual, CCallHelpers::Address(state.baseGPR, JSCell::structureIDOffset()), structure())); } break; } }; generate(state); } // EncodedJSValue in JSVALUE32_64 is a 64-bit integer. When being compiled in ARM EABI, it must be aligned on an even-numbered register (r0, r2 or [sp]). // To prevent the assembler from using wrong registers, let's occupy r1 or r3 with a dummy argument when necessary. #if (COMPILER_SUPPORTS(EABI) && CPU(ARM)) || CPU(MIPS) #define EABI_32BIT_DUMMY_ARG CCallHelpers::TrustedImm32(0), #else #define EABI_32BIT_DUMMY_ARG #endif void AccessCase::generate(AccessGenerationState& state) { if (verbose) dataLog("Generating code for: ", *this, "\n"); CCallHelpers& jit = *state.jit; VM& vm = *jit.vm(); CodeBlock* codeBlock = jit.codeBlock(); StructureStubInfo& stubInfo = *state.stubInfo; const Identifier& ident = *state.ident; JSValueRegs valueRegs = state.valueRegs; GPRReg baseGPR = state.baseGPR; GPRReg scratchGPR = state.scratchGPR; ASSERT(m_conditionSet.structuresEnsureValidityAssumingImpurePropertyWatchpoint()); if ((structure() && structure()->needImpurePropertyWatchpoint()) || m_conditionSet.needImpurePropertyWatchpoint()) vm.registerWatchpointForImpureProperty(ident, state.addWatchpoint()); if (additionalSet()) additionalSet()->add(state.addWatchpoint()); for (const ObjectPropertyCondition& condition : m_conditionSet) { Structure* structure = condition.object()->structure(); if (condition.isWatchableAssumingImpurePropertyWatchpoint()) { structure->addTransitionWatchpoint(state.addWatchpoint(condition)); continue; } if (!condition.structureEnsuresValidityAssumingImpurePropertyWatchpoint(structure)) { dataLog("This condition is no longer met: ", condition, "\n"); RELEASE_ASSERT_NOT_REACHED(); } // We will emit code that has a weak reference that isn't otherwise listed anywhere. state.weakReferences.append(WriteBarrier(vm, codeBlock, structure)); jit.move(CCallHelpers::TrustedImmPtr(condition.object()), scratchGPR); state.failAndRepatch.append( jit.branchStructure( CCallHelpers::NotEqual, CCallHelpers::Address(scratchGPR, JSCell::structureIDOffset()), structure)); } switch (m_type) { case InHit: case InMiss: jit.boxBooleanPayload(m_type == InHit, valueRegs.payloadGPR()); state.succeed(); return; case Miss: jit.moveTrustedValue(jsUndefined(), valueRegs); state.succeed(); return; case Load: case Getter: case Setter: case CustomValueGetter: case CustomAccessorGetter: case CustomValueSetter: case CustomAccessorSetter: { if (isValidOffset(m_offset)) { Structure* currStructure; if (m_conditionSet.isEmpty()) currStructure = structure(); else currStructure = m_conditionSet.slotBaseCondition().object()->structure(); currStructure->startWatchingPropertyForReplacements(vm, offset()); } GPRReg baseForGetGPR; if (viaProxy()) { baseForGetGPR = valueRegs.payloadGPR(); jit.loadPtr( CCallHelpers::Address(baseGPR, JSProxy::targetOffset()), baseForGetGPR); } else baseForGetGPR = baseGPR; GPRReg baseForAccessGPR; if (!m_conditionSet.isEmpty()) { jit.move( CCallHelpers::TrustedImmPtr(alternateBase()), scratchGPR); baseForAccessGPR = scratchGPR; } else baseForAccessGPR = baseForGetGPR; GPRReg loadedValueGPR = InvalidGPRReg; if (m_type != CustomValueGetter && m_type != CustomAccessorGetter && m_type != CustomValueSetter && m_type != CustomAccessorSetter) { if (m_type == Load) loadedValueGPR = valueRegs.payloadGPR(); else loadedValueGPR = scratchGPR; GPRReg storageGPR; if (isInlineOffset(m_offset)) storageGPR = baseForAccessGPR; else { jit.loadPtr( CCallHelpers::Address(baseForAccessGPR, JSObject::butterflyOffset()), loadedValueGPR); jit.removeSpaceBits(loadedValueGPR); storageGPR = loadedValueGPR; } #if USE(JSVALUE64) jit.load64( CCallHelpers::Address(storageGPR, offsetRelativeToBase(m_offset)), loadedValueGPR); #else if (m_type == Load) { jit.load32( CCallHelpers::Address(storageGPR, offsetRelativeToBase(m_offset) + TagOffset), valueRegs.tagGPR()); } jit.load32( CCallHelpers::Address(storageGPR, offsetRelativeToBase(m_offset) + PayloadOffset), loadedValueGPR); #endif } if (m_type == Load) { state.succeed(); return; } // Stuff for custom getters/setters. CCallHelpers::Call operationCall; CCallHelpers::Call lookupExceptionHandlerCall; // Stuff for JS getters/setters. CCallHelpers::DataLabelPtr addressOfLinkFunctionCheck; CCallHelpers::Call fastPathCall; CCallHelpers::Call slowPathCall; CCallHelpers::Jump success; CCallHelpers::Jump fail; // This also does the necessary calculations of whether or not we're an // exception handling call site. state.calculateLiveRegistersForCallAndExceptionHandling(); state.preserveLiveRegistersToStackForCall(); // Need to make sure that whenever this call is made in the future, we remember the // place that we made it from. jit.store32( CCallHelpers::TrustedImm32(state.callSiteIndexForExceptionHandlingOrOriginal().bits()), CCallHelpers::tagFor(static_cast(JSStack::ArgumentCount))); if (m_type == Getter || m_type == Setter) { // Create a JS call using a JS call inline cache. Assume that: // // - SP is aligned and represents the extent of the calling compiler's stack usage. // // - FP is set correctly (i.e. it points to the caller's call frame header). // // - SP - FP is an aligned difference. // // - Any byte between FP (exclusive) and SP (inclusive) could be live in the calling // code. // // Therefore, we temporarily grow the stack for the purpose of the call and then // shrink it after. RELEASE_ASSERT(!m_rareData->callLinkInfo); m_rareData->callLinkInfo = std::make_unique(); // FIXME: If we generated a polymorphic call stub that jumped back to the getter // stub, which then jumped back to the main code, then we'd have a reachability // situation that the GC doesn't know about. The GC would ensure that the polymorphic // call stub stayed alive, and it would ensure that the main code stayed alive, but // it wouldn't know that the getter stub was alive. Ideally JIT stub routines would // be GC objects, and then we'd be able to say that the polymorphic call stub has a // reference to the getter stub. // https://bugs.webkit.org/show_bug.cgi?id=148914 m_rareData->callLinkInfo->disallowStubs(); m_rareData->callLinkInfo->setUpCall( CallLinkInfo::Call, stubInfo.codeOrigin, loadedValueGPR); CCallHelpers::JumpList done; // There is a "this" argument. unsigned numberOfParameters = 1; // ... and a value argument if we're calling a setter. if (m_type == Setter) numberOfParameters++; // Get the accessor; if there ain't one then the result is jsUndefined(). if (m_type == Setter) { jit.loadPtr( CCallHelpers::Address(loadedValueGPR, GetterSetter::offsetOfSetter()), loadedValueGPR); } else { jit.loadPtr( CCallHelpers::Address(loadedValueGPR, GetterSetter::offsetOfGetter()), loadedValueGPR); } CCallHelpers::Jump returnUndefined = jit.branchTestPtr( CCallHelpers::Zero, loadedValueGPR); unsigned numberOfRegsForCall = JSStack::CallFrameHeaderSize + numberOfParameters; unsigned numberOfBytesForCall = numberOfRegsForCall * sizeof(Register) + sizeof(CallerFrameAndPC); unsigned alignedNumberOfBytesForCall = WTF::roundUpToMultipleOf(stackAlignmentBytes(), numberOfBytesForCall); jit.subPtr( CCallHelpers::TrustedImm32(alignedNumberOfBytesForCall), CCallHelpers::stackPointerRegister); CCallHelpers::Address calleeFrame = CCallHelpers::Address( CCallHelpers::stackPointerRegister, -static_cast(sizeof(CallerFrameAndPC))); jit.store32( CCallHelpers::TrustedImm32(numberOfParameters), calleeFrame.withOffset(JSStack::ArgumentCount * sizeof(Register) + PayloadOffset)); jit.storeCell( loadedValueGPR, calleeFrame.withOffset(JSStack::Callee * sizeof(Register))); jit.storeCell( baseForGetGPR, calleeFrame.withOffset(virtualRegisterForArgument(0).offset() * sizeof(Register))); if (m_type == Setter) { jit.storeValue( valueRegs, calleeFrame.withOffset( virtualRegisterForArgument(1).offset() * sizeof(Register))); } CCallHelpers::Jump slowCase = jit.branchPtrWithPatch( CCallHelpers::NotEqual, loadedValueGPR, addressOfLinkFunctionCheck, CCallHelpers::TrustedImmPtr(0)); fastPathCall = jit.nearCall(); if (m_type == Getter) jit.setupResults(valueRegs); done.append(jit.jump()); slowCase.link(&jit); jit.move(loadedValueGPR, GPRInfo::regT0); #if USE(JSVALUE32_64) // We *always* know that the getter/setter, if non-null, is a cell. jit.move(CCallHelpers::TrustedImm32(JSValue::CellTag), GPRInfo::regT1); #endif jit.move(CCallHelpers::TrustedImmPtr(m_rareData->callLinkInfo.get()), GPRInfo::regT2); slowPathCall = jit.nearCall(); if (m_type == Getter) jit.setupResults(valueRegs); done.append(jit.jump()); returnUndefined.link(&jit); if (m_type == Getter) jit.moveTrustedValue(jsUndefined(), valueRegs); done.link(&jit); jit.addPtr(CCallHelpers::TrustedImm32((jit.codeBlock()->stackPointerOffset() * sizeof(Register)) - state.preservedReusedRegisterState.numberOfBytesPreserved - state.numberOfStackBytesUsedForRegisterPreservation()), GPRInfo::callFrameRegister, CCallHelpers::stackPointerRegister); state.restoreLiveRegistersFromStackForCall(isGetter()); state.callbacks.append( [=, &vm] (LinkBuffer& linkBuffer) { m_rareData->callLinkInfo->setCallLocations( linkBuffer.locationOfNearCall(slowPathCall), linkBuffer.locationOf(addressOfLinkFunctionCheck), linkBuffer.locationOfNearCall(fastPathCall)); linkBuffer.link( slowPathCall, CodeLocationLabel(vm.getCTIStub(linkCallThunkGenerator).code())); }); } else { // Need to make room for the C call so any of our stack spillage isn't overwritten. // We also need to make room because we may be an inline cache in the FTL and not // have a JIT call frame. bool needsToMakeRoomOnStackForCCall = state.numberOfStackBytesUsedForRegisterPreservation() || codeBlock->jitType() == JITCode::FTLJIT; if (needsToMakeRoomOnStackForCCall) jit.makeSpaceOnStackForCCall(); // getter: EncodedJSValue (*GetValueFunc)(ExecState*, EncodedJSValue thisValue, PropertyName); // setter: void (*PutValueFunc)(ExecState*, EncodedJSValue thisObject, EncodedJSValue value); // Custom values are passed the slotBase (the property holder), custom accessors are passed the thisVaule (reciever). GPRReg baseForCustomValue = m_type == CustomValueGetter || m_type == CustomValueSetter ? baseForAccessGPR : baseForGetGPR; #if USE(JSVALUE64) if (m_type == CustomValueGetter || m_type == CustomAccessorGetter) { jit.setupArgumentsWithExecState( baseForCustomValue, CCallHelpers::TrustedImmPtr(ident.impl())); } else jit.setupArgumentsWithExecState(baseForCustomValue, valueRegs.gpr()); #else if (m_type == CustomValueGetter || m_type == CustomAccessorGetter) { jit.setupArgumentsWithExecState( EABI_32BIT_DUMMY_ARG baseForCustomValue, CCallHelpers::TrustedImm32(JSValue::CellTag), CCallHelpers::TrustedImmPtr(ident.impl())); } else { jit.setupArgumentsWithExecState( EABI_32BIT_DUMMY_ARG baseForCustomValue, CCallHelpers::TrustedImm32(JSValue::CellTag), valueRegs.payloadGPR(), valueRegs.tagGPR()); } #endif jit.storePtr(GPRInfo::callFrameRegister, &vm.topCallFrame); operationCall = jit.call(); if (m_type == CustomValueGetter || m_type == CustomAccessorGetter) jit.setupResults(valueRegs); if (needsToMakeRoomOnStackForCCall) jit.reclaimSpaceOnStackForCCall(); CCallHelpers::Jump noException = jit.emitExceptionCheck(CCallHelpers::InvertedExceptionCheck); bool didSetLookupExceptionHandler = false; state.restoreLiveRegistersFromStackForCallWithThrownException(); state.restoreScratch(); jit.copyCalleeSavesToVMCalleeSavesBuffer(); if (state.needsToRestoreRegistersIfException()) { // To the JIT that produces the original exception handling // call site, they will expect the OSR exit to be arrived // at from genericUnwind. Therefore we must model what genericUnwind // does here. I.e, set callFrameForCatch and copy callee saves. jit.storePtr(GPRInfo::callFrameRegister, vm.addressOfCallFrameForCatch()); CCallHelpers::Jump jumpToOSRExitExceptionHandler = jit.jump(); // We don't need to insert a new exception handler in the table // because we're doing a manual exception check here. i.e, we'll // never arrive here from genericUnwind(). HandlerInfo originalHandler = state.originalExceptionHandler(); state.callbacks.append( [=] (LinkBuffer& linkBuffer) { linkBuffer.link(jumpToOSRExitExceptionHandler, originalHandler.nativeCode); }); } else { jit.setupArguments(CCallHelpers::TrustedImmPtr(&vm), GPRInfo::callFrameRegister); lookupExceptionHandlerCall = jit.call(); didSetLookupExceptionHandler = true; jit.jumpToExceptionHandler(); } noException.link(&jit); state.restoreLiveRegistersFromStackForCall(isGetter()); state.callbacks.append( [=] (LinkBuffer& linkBuffer) { linkBuffer.link(operationCall, FunctionPtr(m_rareData->customAccessor.opaque)); if (didSetLookupExceptionHandler) linkBuffer.link(lookupExceptionHandlerCall, lookupExceptionHandler); }); } state.succeed(); return; } case Replace: { if (InferredType* type = structure()->inferredTypeFor(ident.impl())) { if (verbose) dataLog("Have type: ", type->descriptor(), "\n"); state.failAndRepatch.append( jit.branchIfNotType( valueRegs, scratchGPR, type->descriptor(), CCallHelpers::DoNotHaveTagRegisters)); } else if (verbose) dataLog("Don't have type.\n"); if (isInlineOffset(m_offset)) { jit.storeValue( valueRegs, CCallHelpers::Address( baseGPR, JSObject::offsetOfInlineStorage() + offsetInInlineStorage(m_offset) * sizeof(JSValue))); } else { jit.loadPtr(CCallHelpers::Address(baseGPR, JSObject::butterflyOffset()), scratchGPR); state.failAndIgnore.append(jit.branchIfNotToSpace(scratchGPR)); jit.storeValue( valueRegs, CCallHelpers::Address( scratchGPR, offsetInButterfly(m_offset) * sizeof(JSValue))); } state.succeed(); return; } case Transition: { // AccessCase::transition() should have returned null. RELEASE_ASSERT(GPRInfo::numberOfRegisters >= 6 || !structure()->outOfLineCapacity() || structure()->outOfLineCapacity() == newStructure()->outOfLineCapacity()); RELEASE_ASSERT(!structure()->couldHaveIndexingHeader()); if (InferredType* type = newStructure()->inferredTypeFor(ident.impl())) { if (verbose) dataLog("Have type: ", type->descriptor(), "\n"); state.failAndRepatch.append( jit.branchIfNotType( valueRegs, scratchGPR, type->descriptor(), CCallHelpers::DoNotHaveTagRegisters)); } else if (verbose) dataLog("Don't have type.\n"); CCallHelpers::JumpList slowPath; ScratchRegisterAllocator allocator(stubInfo.patch.usedRegisters); allocator.lock(baseGPR); #if USE(JSVALUE32_64) allocator.lock(static_cast(stubInfo.patch.baseTagGPR)); #endif allocator.lock(valueRegs); allocator.lock(scratchGPR); GPRReg scratchGPR2 = allocator.allocateScratchGPR(); GPRReg scratchGPR3; if (newStructure()->outOfLineCapacity() != structure()->outOfLineCapacity() && structure()->outOfLineCapacity()) scratchGPR3 = allocator.allocateScratchGPR(); else scratchGPR3 = InvalidGPRReg; ScratchRegisterAllocator::PreservedState preservedState = allocator.preserveReusedRegistersByPushing(jit, ScratchRegisterAllocator::ExtraStackSpace::SpaceForCCall); ASSERT(structure()->transitionWatchpointSetHasBeenInvalidated()); bool scratchGPRHasStorage = false; bool needsToMakeRoomOnStackForCCall = !preservedState.numberOfBytesPreserved && codeBlock->jitType() == JITCode::FTLJIT; if (newStructure()->outOfLineCapacity() != structure()->outOfLineCapacity()) { size_t newSize = newStructure()->outOfLineCapacity() * sizeof(JSValue); CopiedAllocator* copiedAllocator = &vm.heap.storageAllocator(); if (!structure()->outOfLineCapacity()) { jit.loadPtr(&copiedAllocator->m_currentRemaining, scratchGPR); slowPath.append( jit.branchSubPtr( CCallHelpers::Signed, CCallHelpers::TrustedImm32(newSize), scratchGPR)); jit.storePtr(scratchGPR, &copiedAllocator->m_currentRemaining); jit.negPtr(scratchGPR); jit.addPtr( CCallHelpers::AbsoluteAddress(&copiedAllocator->m_currentPayloadEnd), scratchGPR); jit.addPtr(CCallHelpers::TrustedImm32(sizeof(JSValue)), scratchGPR); } else { size_t oldSize = structure()->outOfLineCapacity() * sizeof(JSValue); ASSERT(newSize > oldSize); jit.loadPtr(CCallHelpers::Address(baseGPR, JSObject::butterflyOffset()), scratchGPR3); slowPath.append(jit.branchIfNotToSpace(scratchGPR3)); jit.loadPtr(&copiedAllocator->m_currentRemaining, scratchGPR); slowPath.append( jit.branchSubPtr( CCallHelpers::Signed, CCallHelpers::TrustedImm32(newSize), scratchGPR)); jit.storePtr(scratchGPR, &copiedAllocator->m_currentRemaining); jit.negPtr(scratchGPR); jit.addPtr( CCallHelpers::AbsoluteAddress(&copiedAllocator->m_currentPayloadEnd), scratchGPR); jit.addPtr(CCallHelpers::TrustedImm32(sizeof(JSValue)), scratchGPR); // We have scratchGPR = new storage, scratchGPR3 = old storage, // scratchGPR2 = available for (size_t offset = 0; offset < oldSize; offset += sizeof(void*)) { jit.loadPtr( CCallHelpers::Address( scratchGPR3, -static_cast( offset + sizeof(JSValue) + sizeof(void*))), scratchGPR2); jit.storePtr( scratchGPR2, CCallHelpers::Address( scratchGPR, -static_cast(offset + sizeof(JSValue) + sizeof(void*)))); } } jit.storePtr(scratchGPR, CCallHelpers::Address(baseGPR, JSObject::butterflyOffset())); scratchGPRHasStorage = true; } uint32_t structureBits = bitwise_cast(newStructure()->id()); jit.store32( CCallHelpers::TrustedImm32(structureBits), CCallHelpers::Address(baseGPR, JSCell::structureIDOffset())); if (isInlineOffset(m_offset)) { jit.storeValue( valueRegs, CCallHelpers::Address( baseGPR, JSObject::offsetOfInlineStorage() + offsetInInlineStorage(m_offset) * sizeof(JSValue))); } else { if (!scratchGPRHasStorage) { jit.loadPtr(CCallHelpers::Address(baseGPR, JSObject::butterflyOffset()), scratchGPR); state.failAndIgnore.append(jit.branchIfNotToSpace(scratchGPR)); } jit.storeValue( valueRegs, CCallHelpers::Address(scratchGPR, offsetInButterfly(m_offset) * sizeof(JSValue))); } ScratchBuffer* scratchBuffer = nullptr; if (newStructure()->outOfLineCapacity() != structure()->outOfLineCapacity()) scratchBuffer = vm.scratchBufferForSize(allocator.desiredScratchBufferSizeForCall()); if (newStructure()->outOfLineCapacity() != structure()->outOfLineCapacity()) { CCallHelpers::Call callFlushWriteBarrierBuffer; CCallHelpers::Jump ownerIsRememberedOrInEden = jit.jumpIfIsRememberedOrInEden(baseGPR); WriteBarrierBuffer& writeBarrierBuffer = jit.vm()->heap.writeBarrierBuffer(); jit.load32(writeBarrierBuffer.currentIndexAddress(), scratchGPR2); CCallHelpers::Jump needToFlush = jit.branch32( CCallHelpers::AboveOrEqual, scratchGPR2, CCallHelpers::TrustedImm32(writeBarrierBuffer.capacity())); jit.add32(CCallHelpers::TrustedImm32(1), scratchGPR2); jit.store32(scratchGPR2, writeBarrierBuffer.currentIndexAddress()); jit.move(CCallHelpers::TrustedImmPtr(writeBarrierBuffer.buffer()), scratchGPR); // We use an offset of -sizeof(void*) because we already added 1 to scratchGPR2. jit.storePtr( baseGPR, CCallHelpers::BaseIndex( scratchGPR, scratchGPR2, CCallHelpers::ScalePtr, static_cast(-sizeof(void*)))); CCallHelpers::Jump doneWithBarrier = jit.jump(); needToFlush.link(&jit); // FIXME: We should restoreReusedRegistersByPopping() before this. Then, we wouldn't need // padding in preserveReusedRegistersByPushing(). Or, maybe it would be even better if the // barrier slow path was just the normal slow path, below. // https://bugs.webkit.org/show_bug.cgi?id=149030 allocator.preserveUsedRegistersToScratchBufferForCall(jit, scratchBuffer, scratchGPR2); if (needsToMakeRoomOnStackForCCall) jit.makeSpaceOnStackForCCall(); jit.setupArgumentsWithExecState(baseGPR); callFlushWriteBarrierBuffer = jit.call(); if (needsToMakeRoomOnStackForCCall) jit.reclaimSpaceOnStackForCCall(); allocator.restoreUsedRegistersFromScratchBufferForCall( jit, scratchBuffer, scratchGPR2); doneWithBarrier.link(&jit); ownerIsRememberedOrInEden.link(&jit); state.callbacks.append( [=] (LinkBuffer& linkBuffer) { linkBuffer.link(callFlushWriteBarrierBuffer, operationFlushWriteBarrierBuffer); }); } allocator.restoreReusedRegistersByPopping(jit, preservedState); state.succeed(); if (newStructure()->outOfLineCapacity() != structure()->outOfLineCapacity()) { slowPath.link(&jit); allocator.restoreReusedRegistersByPopping(jit, preservedState); allocator.preserveUsedRegistersToScratchBufferForCall(jit, scratchBuffer, scratchGPR); if (needsToMakeRoomOnStackForCCall) jit.makeSpaceOnStackForCCall(); #if USE(JSVALUE64) jit.setupArgumentsWithExecState( baseGPR, CCallHelpers::TrustedImmPtr(newStructure()), CCallHelpers::TrustedImm32(m_offset), valueRegs.gpr()); #else jit.setupArgumentsWithExecState( baseGPR, CCallHelpers::TrustedImmPtr(newStructure()), CCallHelpers::TrustedImm32(m_offset), valueRegs.payloadGPR(), valueRegs.tagGPR()); #endif CCallHelpers::Call operationCall = jit.call(); if (needsToMakeRoomOnStackForCCall) jit.reclaimSpaceOnStackForCCall(); allocator.restoreUsedRegistersFromScratchBufferForCall(jit, scratchBuffer, scratchGPR); state.succeed(); state.callbacks.append( [=] (LinkBuffer& linkBuffer) { linkBuffer.link(operationCall, operationReallocateStorageAndFinishPut); }); } return; } case ArrayLength: { jit.loadPtr(CCallHelpers::Address(baseGPR, JSObject::butterflyOffset()), scratchGPR); jit.removeSpaceBits(scratchGPR); jit.load32(CCallHelpers::Address(scratchGPR, ArrayStorage::lengthOffset()), scratchGPR); state.failAndIgnore.append( jit.branch32(CCallHelpers::LessThan, scratchGPR, CCallHelpers::TrustedImm32(0))); jit.boxInt32(scratchGPR, valueRegs, CCallHelpers::DoNotHaveTagRegisters); state.succeed(); return; } case StringLength: { jit.load32(CCallHelpers::Address(baseGPR, JSString::offsetOfLength()), valueRegs.payloadGPR()); jit.boxInt32(valueRegs.payloadGPR(), valueRegs, CCallHelpers::DoNotHaveTagRegisters); state.succeed(); return; } case IntrinsicGetter: { RELEASE_ASSERT(isValidOffset(offset())); // We need to ensure the getter value does not move from under us. Note that GetterSetters // are immutable so we just need to watch the property not any value inside it. Structure* currStructure; if (m_conditionSet.isEmpty()) currStructure = structure(); else currStructure = m_conditionSet.slotBaseCondition().object()->structure(); currStructure->startWatchingPropertyForReplacements(vm, offset()); emitIntrinsicGetter(state); return; } } RELEASE_ASSERT_NOT_REACHED(); } PolymorphicAccess::PolymorphicAccess() { } PolymorphicAccess::~PolymorphicAccess() { } MacroAssemblerCodePtr PolymorphicAccess::regenerateWithCases( VM& vm, CodeBlock* codeBlock, StructureStubInfo& stubInfo, const Identifier& ident, Vector> originalCasesToAdd) { // This method will add the originalCasesToAdd to the list one at a time while preserving the // invariants: // - If a newly added case canReplace() any existing case, then the existing case is removed before // the new case is added. Removal doesn't change order of the list. Any number of existing cases // can be removed via the canReplace() rule. // - Cases in the list always appear in ascending order of time of addition. Therefore, if you // cascade through the cases in reverse order, you will get the most recent cases first. // - If this method fails (returns null, doesn't add the cases), then both the previous case list // and the previous stub are kept intact and the new cases are destroyed. It's OK to attempt to // add more things after failure. // First, verify that we can generate code for all of the new cases while eliminating any of the // new cases that replace each other. Vector> casesToAdd; for (unsigned i = 0; i < originalCasesToAdd.size(); ++i) { std::unique_ptr myCase = WTFMove(originalCasesToAdd[i]); // Add it only if it is not replaced by the subsequent cases in the list. bool found = false; for (unsigned j = i + 1; j < originalCasesToAdd.size(); ++j) { if (originalCasesToAdd[j]->canReplace(*myCase)) { found = true; break; } } if (found) continue; casesToAdd.append(WTFMove(myCase)); } if (verbose) dataLog("casesToAdd: ", listDump(casesToAdd), "\n"); // If there aren't any cases to add, then fail on the grounds that there's no point to generating a // new stub that will be identical to the old one. Returning null should tell the caller to just // keep doing what they were doing before. if (casesToAdd.isEmpty()) return MacroAssemblerCodePtr(); // Now construct the list of cases as they should appear if we are successful. This means putting // all of the previous cases in this list in order but excluding those that can be replaced, and // then adding the new cases. ListType newCases; for (auto& oldCase : m_list) { // Ignore old cases that cannot possibly succeed anymore. if (!oldCase->couldStillSucceed()) continue; // Figure out if this is replaced by any new cases. bool found = false; for (auto& caseToAdd : casesToAdd) { if (caseToAdd->canReplace(*oldCase)) { found = true; break; } } if (found) continue; newCases.append(oldCase->clone()); } for (auto& caseToAdd : casesToAdd) newCases.append(WTFMove(caseToAdd)); if (verbose) dataLog("newCases: ", listDump(newCases), "\n"); if (newCases.size() > Options::maxAccessVariantListSize()) { if (verbose) dataLog("Too many cases.\n"); return MacroAssemblerCodePtr(); } MacroAssemblerCodePtr result = regenerate(vm, codeBlock, stubInfo, ident, newCases); if (!result) return MacroAssemblerCodePtr(); m_list = WTFMove(newCases); return result; } MacroAssemblerCodePtr PolymorphicAccess::regenerateWithCase( VM& vm, CodeBlock* codeBlock, StructureStubInfo& stubInfo, const Identifier& ident, std::unique_ptr newAccess) { Vector> newAccesses; newAccesses.append(WTFMove(newAccess)); return regenerateWithCases(vm, codeBlock, stubInfo, ident, WTFMove(newAccesses)); } bool PolymorphicAccess::visitWeak(VM& vm) const { for (unsigned i = 0; i < size(); ++i) { if (!at(i).visitWeak(vm)) return false; } if (Vector>* weakReferences = m_weakReferences.get()) { for (WriteBarrier& weakReference : *weakReferences) { if (!Heap::isMarked(weakReference.get())) return false; } } return true; } void PolymorphicAccess::dump(PrintStream& out) const { out.print(RawPointer(this), ":["); CommaPrinter comma; for (auto& entry : m_list) out.print(comma, *entry); out.print("]"); } MacroAssemblerCodePtr PolymorphicAccess::regenerate( VM& vm, CodeBlock* codeBlock, StructureStubInfo& stubInfo, const Identifier& ident, PolymorphicAccess::ListType& cases) { if (verbose) dataLog("Generating code for cases: ", listDump(cases), "\n"); AccessGenerationState state; state.access = this; state.stubInfo = &stubInfo; state.ident = &ident; state.baseGPR = static_cast(stubInfo.patch.baseGPR); state.valueRegs = JSValueRegs( #if USE(JSVALUE32_64) static_cast(stubInfo.patch.valueTagGPR), #endif static_cast(stubInfo.patch.valueGPR)); ScratchRegisterAllocator allocator(stubInfo.patch.usedRegisters); state.allocator = &allocator; allocator.lock(state.baseGPR); allocator.lock(state.valueRegs); #if USE(JSVALUE32_64) allocator.lock(static_cast(stubInfo.patch.baseTagGPR)); #endif state.scratchGPR = allocator.allocateScratchGPR(); CCallHelpers jit(&vm, codeBlock); state.jit = &jit; state.preservedReusedRegisterState = allocator.preserveReusedRegistersByPushing(jit, ScratchRegisterAllocator::ExtraStackSpace::NoExtraSpace); bool allGuardedByStructureCheck = true; bool hasJSGetterSetterCall = false; for (auto& entry : cases) { allGuardedByStructureCheck &= entry->guardedByStructureCheck(); if (entry->type() == AccessCase::Getter || entry->type() == AccessCase::Setter) hasJSGetterSetterCall = true; } if (cases.isEmpty()) { // This is super unlikely, but we make it legal anyway. state.failAndRepatch.append(jit.jump()); } else if (!allGuardedByStructureCheck || cases.size() == 1) { // If there are any proxies in the list, we cannot just use a binary switch over the structure. // We need to resort to a cascade. A cascade also happens to be optimal if we only have just // one case. CCallHelpers::JumpList fallThrough; // Cascade through the list, preferring newer entries. for (unsigned i = cases.size(); i--;) { fallThrough.link(&jit); cases[i]->generateWithGuard(state, fallThrough); } state.failAndRepatch.append(fallThrough); } else { jit.load32( CCallHelpers::Address(state.baseGPR, JSCell::structureIDOffset()), state.scratchGPR); Vector caseValues(cases.size()); for (unsigned i = 0; i < cases.size(); ++i) caseValues[i] = bitwise_cast(cases[i]->structure()->id()); BinarySwitch binarySwitch(state.scratchGPR, caseValues, BinarySwitch::Int32); while (binarySwitch.advance(jit)) cases[binarySwitch.caseIndex()]->generate(state); state.failAndRepatch.append(binarySwitch.fallThrough()); } if (!state.failAndIgnore.empty()) { state.failAndIgnore.link(&jit); // Make sure that the inline cache optimization code knows that we are taking slow path because // of something that isn't patchable. The slow path will decrement "countdown" and will only // patch things if the countdown reaches zero. We increment the slow path count here to ensure // that the slow path does not try to patch. jit.load8(&stubInfo.countdown, state.scratchGPR); jit.add32(CCallHelpers::TrustedImm32(1), state.scratchGPR); jit.store8(state.scratchGPR, &stubInfo.countdown); } CCallHelpers::JumpList failure; if (allocator.didReuseRegisters()) { state.failAndRepatch.link(&jit); state.restoreScratch(); } else failure = state.failAndRepatch; failure.append(jit.jump()); CodeBlock* codeBlockThatOwnsExceptionHandlers = nullptr; CallSiteIndex callSiteIndexForExceptionHandling; if (state.needsToRestoreRegistersIfException() && hasJSGetterSetterCall) { // Emit the exception handler. // Note that this code is only reachable when doing genericUnwind from a pure JS getter/setter . // Note also that this is not reachable from custom getter/setter. Custom getter/setters will have // their own exception handling logic that doesn't go through genericUnwind. MacroAssembler::Label makeshiftCatchHandler = jit.label(); int stackPointerOffset = codeBlock->stackPointerOffset() * sizeof(EncodedJSValue); stackPointerOffset -= state.preservedReusedRegisterState.numberOfBytesPreserved; stackPointerOffset -= state.numberOfStackBytesUsedForRegisterPreservation(); jit.loadPtr(vm.addressOfCallFrameForCatch(), GPRInfo::callFrameRegister); jit.addPtr(CCallHelpers::TrustedImm32(stackPointerOffset), GPRInfo::callFrameRegister, CCallHelpers::stackPointerRegister); state.restoreLiveRegistersFromStackForCallWithThrownException(); state.restoreScratch(); CCallHelpers::Jump jumpToOSRExitExceptionHandler = jit.jump(); HandlerInfo oldHandler = state.originalExceptionHandler(); CallSiteIndex newExceptionHandlingCallSite = state.callSiteIndexForExceptionHandling(); state.callbacks.append( [=] (LinkBuffer& linkBuffer) { linkBuffer.link(jumpToOSRExitExceptionHandler, oldHandler.nativeCode); HandlerInfo handlerToRegister = oldHandler; handlerToRegister.nativeCode = linkBuffer.locationOf(makeshiftCatchHandler); handlerToRegister.start = newExceptionHandlingCallSite.bits(); handlerToRegister.end = newExceptionHandlingCallSite.bits() + 1; codeBlock->appendExceptionHandler(handlerToRegister); }); // We set these to indicate to the stub to remove itself from the CodeBlock's // exception handler table when it is deallocated. codeBlockThatOwnsExceptionHandlers = codeBlock; ASSERT(JITCode::isOptimizingJIT(codeBlockThatOwnsExceptionHandlers->jitType())); callSiteIndexForExceptionHandling = state.callSiteIndexForExceptionHandling(); } LinkBuffer linkBuffer(vm, jit, codeBlock, JITCompilationCanFail); if (linkBuffer.didFailToAllocate()) { if (verbose) dataLog("Did fail to allocate.\n"); return MacroAssemblerCodePtr(); } CodeLocationLabel successLabel = stubInfo.callReturnLocation.labelAtOffset(stubInfo.patch.deltaCallToDone); linkBuffer.link(state.success, successLabel); linkBuffer.link( failure, stubInfo.callReturnLocation.labelAtOffset(stubInfo.patch.deltaCallToSlowCase)); for (auto callback : state.callbacks) callback(linkBuffer); if (verbose) dataLog(*codeBlock, " ", stubInfo.codeOrigin, ": Generating polymorphic access stub for ", listDump(cases), "\n"); MacroAssemblerCodeRef code = FINALIZE_CODE_FOR( codeBlock, linkBuffer, ("%s", toCString("Access stub for ", *codeBlock, " ", stubInfo.codeOrigin, " with return point ", successLabel, ": ", listDump(cases)).data())); bool doesCalls = false; for (auto& entry : cases) doesCalls |= entry->doesCalls(); m_stubRoutine = createJITStubRoutine(code, vm, codeBlock, doesCalls, nullptr, codeBlockThatOwnsExceptionHandlers, callSiteIndexForExceptionHandling); m_watchpoints = WTFMove(state.watchpoints); if (!state.weakReferences.isEmpty()) m_weakReferences = std::make_unique>>(WTFMove(state.weakReferences)); if (verbose) dataLog("Returning: ", code.code(), "\n"); return code.code(); } void PolymorphicAccess::aboutToDie() { m_stubRoutine->aboutToDie(); } } // namespace JSC namespace WTF { using namespace JSC; void printInternal(PrintStream& out, AccessCase::AccessType type) { switch (type) { case AccessCase::Load: out.print("Load"); return; case AccessCase::Transition: out.print("Transition"); return; case AccessCase::Replace: out.print("Replace"); return; case AccessCase::Miss: out.print("Miss"); return; case AccessCase::Getter: out.print("Getter"); return; case AccessCase::Setter: out.print("Setter"); return; case AccessCase::CustomValueGetter: out.print("CustomValueGetter"); return; case AccessCase::CustomAccessorGetter: out.print("CustomAccessorGetter"); return; case AccessCase::CustomValueSetter: out.print("CustomValueSetter"); return; case AccessCase::CustomAccessorSetter: out.print("CustomAccessorSetter"); return; case AccessCase::IntrinsicGetter: out.print("IntrinsicGetter"); return; case AccessCase::InHit: out.print("InHit"); return; case AccessCase::InMiss: out.print("InMiss"); return; case AccessCase::ArrayLength: out.print("ArrayLength"); return; case AccessCase::StringLength: out.print("StringLength"); return; } RELEASE_ASSERT_NOT_REACHED(); } } // namespace WTF #endif // ENABLE(JIT)