summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/bytecode/PolymorphicAccess.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2016-05-24 08:28:08 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2016-05-24 08:28:08 +0000
commita4e969f4965059196ca948db781e52f7cfebf19e (patch)
tree6ca352808c8fdc52006a0f33f6ae3c593b23867d /Source/JavaScriptCore/bytecode/PolymorphicAccess.cpp
parent41386e9cb918eed93b3f13648cbef387e371e451 (diff)
downloadWebKitGtk-tarball-a4e969f4965059196ca948db781e52f7cfebf19e.tar.gz
webkitgtk-2.12.3webkitgtk-2.12.3
Diffstat (limited to 'Source/JavaScriptCore/bytecode/PolymorphicAccess.cpp')
-rw-r--r--Source/JavaScriptCore/bytecode/PolymorphicAccess.cpp1469
1 files changed, 1469 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/bytecode/PolymorphicAccess.cpp b/Source/JavaScriptCore/bytecode/PolymorphicAccess.cpp
new file mode 100644
index 000000000..3a59f8db4
--- /dev/null
+++ b/Source/JavaScriptCore/bytecode/PolymorphicAccess.cpp
@@ -0,0 +1,1469 @@
+/*
+ * 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 <wtf/CommaPrinter.h>
+#include <wtf/ListDump.h>
+
+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<unsigned>::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> 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<AccessCase> 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<RareData>();
+ 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> AccessCase::replace(
+ VM& vm, JSCell* owner, Structure* structure, PropertyOffset offset)
+{
+ std::unique_ptr<AccessCase> result(new AccessCase());
+
+ result->m_type = Replace;
+ result->m_offset = offset;
+ result->m_structure.set(vm, owner, structure);
+
+ return result;
+}
+
+std::unique_ptr<AccessCase> 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<AccessCase> 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> AccessCase::setter(
+ VM& vm, JSCell* owner, AccessType type, Structure* structure, PropertyOffset offset,
+ const ObjectPropertyConditionSet& conditionSet, PutPropertySlot::PutValueFunc customSetter,
+ JSObject* customSlotBase)
+{
+ std::unique_ptr<AccessCase> 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<RareData>();
+ result->m_rareData->customAccessor.setter = customSetter;
+ result->m_rareData->customSlotBase.setMayBeNull(vm, owner, customSlotBase);
+
+ return result;
+}
+
+std::unique_ptr<AccessCase> AccessCase::in(
+ VM& vm, JSCell* owner, AccessType type, Structure* structure,
+ const ObjectPropertyConditionSet& conditionSet)
+{
+ std::unique_ptr<AccessCase> result(new AccessCase());
+
+ result->m_type = type;
+ result->m_structure.set(vm, owner, structure);
+ result->m_conditionSet = conditionSet;
+
+ return result;
+}
+
+std::unique_ptr<AccessCase> AccessCase::getLength(VM&, JSCell*, AccessType type)
+{
+ std::unique_ptr<AccessCase> result(new AccessCase());
+
+ result->m_type = type;
+
+ return result;
+}
+
+std::unique_ptr<AccessCase> AccessCase::getIntrinsic(
+ VM& vm, JSCell* owner, JSFunction* getter, PropertyOffset offset,
+ Structure* structure, const ObjectPropertyConditionSet& conditionSet)
+{
+ std::unique_ptr<AccessCase> 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<RareData>();
+ result->m_rareData->intrinsicFunction.set(vm, owner, getter);
+
+ return result;
+}
+
+AccessCase::~AccessCase()
+{
+}
+
+std::unique_ptr<AccessCase> 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> AccessCase::clone() const
+{
+ std::unique_ptr<AccessCase> 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<RareData>();
+ 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<JSCell>(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<VirtualRegister>(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<CallLinkInfo>();
+
+ // 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<ptrdiff_t>(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<GPRReg>(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<ptrdiff_t>(
+ offset + sizeof(JSValue) + sizeof(void*))),
+ scratchGPR2);
+ jit.storePtr(
+ scratchGPR2,
+ CCallHelpers::Address(
+ scratchGPR,
+ -static_cast<ptrdiff_t>(offset + sizeof(JSValue) + sizeof(void*))));
+ }
+ }
+
+ jit.storePtr(scratchGPR, CCallHelpers::Address(baseGPR, JSObject::butterflyOffset()));
+ scratchGPRHasStorage = true;
+ }
+
+ uint32_t structureBits = bitwise_cast<uint32_t>(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<int32_t>(-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<std::unique_ptr<AccessCase>> 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<std::unique_ptr<AccessCase>> casesToAdd;
+ for (unsigned i = 0; i < originalCasesToAdd.size(); ++i) {
+ std::unique_ptr<AccessCase> 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<AccessCase> newAccess)
+{
+ Vector<std::unique_ptr<AccessCase>> 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<WriteBarrier<JSCell>>* weakReferences = m_weakReferences.get()) {
+ for (WriteBarrier<JSCell>& 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<GPRReg>(stubInfo.patch.baseGPR);
+ state.valueRegs = JSValueRegs(
+#if USE(JSVALUE32_64)
+ static_cast<GPRReg>(stubInfo.patch.valueTagGPR),
+#endif
+ static_cast<GPRReg>(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<GPRReg>(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<int64_t> caseValues(cases.size());
+ for (unsigned i = 0; i < cases.size(); ++i)
+ caseValues[i] = bitwise_cast<int32_t>(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<Vector<WriteBarrier<JSCell>>>(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)
+
+