PolymorphicAccess.cpp [plain text]
#include "config.h"
#include "PolymorphicAccess.h"
#if ENABLE(JIT)
#include "BinarySwitch.h"
#include "CCallHelpers.h"
#include "CodeBlock.h"
#include "FullCodeOrigin.h"
#include "Heap.h"
#include "JITOperations.h"
#include "JSCInlines.h"
#include "LinkBuffer.h"
#include "StructureStubClearingWatchpoint.h"
#include "StructureStubInfo.h"
#include <wtf/CommaPrinter.h>
#include <wtf/ListDump.h>
namespace JSC {
static const bool verbose = false;
void AccessGenerationResult::dump(PrintStream& out) const
{
out.print(m_kind);
if (m_code)
out.print(":", m_code);
}
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());
}
const RegisterSet& AccessGenerationState::liveRegistersForCall()
{
if (!m_calculatedRegistersForCallAndExceptionHandling)
calculateLiveRegistersForCallAndExceptionHandling();
return m_liveRegistersForCall;
}
const RegisterSet& AccessGenerationState::liveRegistersToPreserveAtExceptionHandlingCallSite()
{
if (!m_calculatedRegistersForCallAndExceptionHandling)
calculateLiveRegistersForCallAndExceptionHandling();
return m_liveRegistersToPreserveAtExceptionHandlingCallSite;
}
static RegisterSet calleeSaveRegisters()
{
RegisterSet result = RegisterSet::registersToNotSaveForJSCall();
result.filter(RegisterSet::registersToNotSaveForCCall());
return result;
}
const RegisterSet& 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(calleeSaveRegisters());
}
return m_liveRegistersForCall;
}
auto AccessGenerationState::preserveLiveRegistersToStackForCall(const RegisterSet& extra) -> SpillState
{
RegisterSet liveRegisters = liveRegistersForCall();
liveRegisters.merge(extra);
unsigned extraStackPadding = 0;
unsigned numberOfStackBytesUsedForRegisterPreservation = ScratchRegisterAllocator::preserveRegistersToStackForCall(*jit, liveRegisters, extraStackPadding);
return SpillState {
WTFMove(liveRegisters),
numberOfStackBytesUsedForRegisterPreservation
};
}
void AccessGenerationState::restoreLiveRegistersFromStackForCallWithThrownException(const SpillState& spillState)
{
RegisterSet dontRestore = spillState.spilledRegisters;
dontRestore.exclude(liveRegistersToPreserveAtExceptionHandlingCallSite());
restoreLiveRegistersFromStackForCall(spillState, dontRestore);
}
void AccessGenerationState::restoreLiveRegistersFromStackForCall(const SpillState& spillState, const RegisterSet& dontRestore)
{
unsigned extraStackPadding = 0;
ScratchRegisterAllocator::restoreRegistersFromStackForCall(*jit, spillState.spilledRegisters, dontRestore, spillState.numberOfStackBytesUsedForRegisterPreservation, extraStackPadding);
}
CallSiteIndex AccessGenerationState::callSiteIndexForExceptionHandlingOrOriginal()
{
if (!m_calculatedRegistersForCallAndExceptionHandling)
calculateLiveRegistersForCallAndExceptionHandling();
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()
{
if (!m_calculatedRegistersForCallAndExceptionHandling)
calculateLiveRegistersForCallAndExceptionHandling();
RELEASE_ASSERT(m_needsToRestoreRegistersIfException);
HandlerInfo* exceptionHandler = jit->codeBlock()->handlerForIndex(stubInfo->callSiteIndex.bits());
RELEASE_ASSERT(exceptionHandler);
return *exceptionHandler;
}
CallSiteIndex AccessGenerationState::originalCallSiteIndex() const { return stubInfo->callSiteIndex; }
void AccessGenerationState::emitExplicitExceptionHandler()
{
restoreScratch();
jit->copyCalleeSavesToVMEntryFrameCalleeSavesBuffer(m_vm);
if (needsToRestoreRegistersIfException()) {
jit->storePtr(GPRInfo::callFrameRegister, m_vm.addressOfCallFrameForCatch());
CCallHelpers::Jump jumpToOSRExitExceptionHandler = jit->jump();
HandlerInfo originalHandler = originalExceptionHandler();
jit->addLinkTask(
[=] (LinkBuffer& linkBuffer) {
linkBuffer.link(jumpToOSRExitExceptionHandler, originalHandler.nativeCode);
});
} else {
jit->setupArguments(CCallHelpers::TrustedImmPtr(&m_vm), GPRInfo::callFrameRegister);
CCallHelpers::Call lookupExceptionHandlerCall = jit->call();
jit->addLinkTask(
[=] (LinkBuffer& linkBuffer) {
linkBuffer.link(lookupExceptionHandlerCall, lookupExceptionHandler);
});
jit->jumpToExceptionHandler(m_vm);
}
}
PolymorphicAccess::PolymorphicAccess() { }
PolymorphicAccess::~PolymorphicAccess() { }
AccessGenerationResult PolymorphicAccess::addCases(
const GCSafeConcurrentJSLocker& locker, VM& vm, CodeBlock* codeBlock, StructureStubInfo& stubInfo,
const Identifier& ident, Vector<std::unique_ptr<AccessCase>, 2> originalCasesToAdd)
{
SuperSamplerScope superSamplerScope(false);
Vector<std::unique_ptr<AccessCase>> casesToAdd;
for (unsigned i = 0; i < originalCasesToAdd.size(); ++i) {
std::unique_ptr<AccessCase> myCase = WTFMove(originalCasesToAdd[i]);
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 (casesToAdd.isEmpty())
return AccessGenerationResult::MadeNoChanges;
for (auto& caseToAdd : casesToAdd) {
commit(locker, vm, m_watchpoints, codeBlock, stubInfo, ident, *caseToAdd);
m_list.append(WTFMove(caseToAdd));
}
if (verbose)
dataLog("After addCases: m_list: ", listDump(m_list), "\n");
return AccessGenerationResult::Buffered;
}
AccessGenerationResult PolymorphicAccess::addCase(
const GCSafeConcurrentJSLocker& locker, VM& vm, CodeBlock* codeBlock, StructureStubInfo& stubInfo,
const Identifier& ident, std::unique_ptr<AccessCase> newAccess)
{
Vector<std::unique_ptr<AccessCase>, 2> newAccesses;
newAccesses.append(WTFMove(newAccess));
return addCases(locker, 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;
}
bool PolymorphicAccess::propagateTransitions(SlotVisitor& visitor) const
{
bool result = true;
for (unsigned i = 0; i < size(); ++i)
result &= at(i).propagateTransitions(visitor);
return result;
}
void PolymorphicAccess::dump(PrintStream& out) const
{
out.print(RawPointer(this), ":[");
CommaPrinter comma;
for (auto& entry : m_list)
out.print(comma, *entry);
out.print("]");
}
void PolymorphicAccess::commit(
const GCSafeConcurrentJSLocker&, VM& vm, std::unique_ptr<WatchpointsOnStructureStubInfo>& watchpoints, CodeBlock* codeBlock,
StructureStubInfo& stubInfo, const Identifier& ident, AccessCase& accessCase)
{
for (WatchpointSet* set : accessCase.commit(vm, ident)) {
Watchpoint* watchpoint =
WatchpointsOnStructureStubInfo::ensureReferenceAndAddWatchpoint(
watchpoints, codeBlock, &stubInfo, ObjectPropertyCondition());
set->add(watchpoint);
}
}
AccessGenerationResult PolymorphicAccess::regenerate(
const GCSafeConcurrentJSLocker& locker, VM& vm, CodeBlock* codeBlock, StructureStubInfo& stubInfo, const Identifier& ident)
{
SuperSamplerScope superSamplerScope(false);
if (verbose)
dataLog("Regenerate with m_list: ", listDump(m_list), "\n");
AccessGenerationState state(vm);
state.access = this;
state.stubInfo = &stubInfo;
state.ident = &ident;
state.baseGPR = static_cast<GPRReg>(stubInfo.patch.baseGPR);
state.thisGPR = static_cast<GPRReg>(stubInfo.patch.thisGPR);
state.valueRegs = stubInfo.valueRegs();
ScratchRegisterAllocator allocator(stubInfo.patch.usedRegisters);
state.allocator = &allocator;
allocator.lock(state.baseGPR);
if (state.thisGPR != InvalidGPRReg)
allocator.lock(state.thisGPR);
allocator.lock(state.valueRegs);
#if USE(JSVALUE32_64)
allocator.lock(static_cast<GPRReg>(stubInfo.patch.baseTagGPR));
#endif
state.scratchGPR = allocator.allocateScratchGPR();
CCallHelpers jit(codeBlock);
state.jit = &jit;
state.preservedReusedRegisterState =
allocator.preserveReusedRegistersByPushing(jit, ScratchRegisterAllocator::ExtraStackSpace::NoExtraSpace);
ListType cases;
unsigned srcIndex = 0;
unsigned dstIndex = 0;
while (srcIndex < m_list.size()) {
std::unique_ptr<AccessCase> someCase = WTFMove(m_list[srcIndex++]);
bool isGenerated = someCase->state() == AccessCase::Generated;
[&] () {
if (!someCase->couldStillSucceed())
return;
for (unsigned j = srcIndex; j < m_list.size(); ++j) {
if (m_list[j]->canReplace(*someCase))
return;
}
if (isGenerated)
cases.append(someCase->clone());
else
cases.append(WTFMove(someCase));
}();
if (isGenerated)
m_list[dstIndex++] = WTFMove(someCase);
}
m_list.resize(dstIndex);
if (verbose)
dataLog("Optimized cases: ", listDump(cases), "\n");
bool allGuardedByStructureCheck = true;
bool hasJSGetterSetterCall = false;
for (auto& newCase : cases) {
commit(locker, vm, state.watchpoints, codeBlock, stubInfo, ident, *newCase);
allGuardedByStructureCheck &= newCase->guardedByStructureCheck();
if (newCase->type() == AccessCase::Getter || newCase->type() == AccessCase::Setter)
hasJSGetterSetterCall = true;
}
if (cases.isEmpty()) {
state.failAndRepatch.append(jit.jump());
} else if (!allGuardedByStructureCheck || cases.size() == 1) {
CCallHelpers::JumpList fallThrough;
for (unsigned i = cases.size(); i--;) {
fallThrough.link(&jit);
fallThrough.clear();
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);
#if CPU(X86) || CPU(X86_64)
jit.move(CCallHelpers::TrustedImmPtr(&stubInfo.countdown), state.scratchGPR);
jit.add8(CCallHelpers::TrustedImm32(1), CCallHelpers::Address(state.scratchGPR));
#else
jit.load8(&stubInfo.countdown, state.scratchGPR);
jit.add32(CCallHelpers::TrustedImm32(1), state.scratchGPR);
jit.store8(state.scratchGPR, &stubInfo.countdown);
#endif
}
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) {
MacroAssembler::Label makeshiftCatchHandler = jit.label();
int stackPointerOffset = codeBlock->stackPointerOffset() * sizeof(EncodedJSValue);
AccessGenerationState::SpillState spillStateForJSGetterSetter = state.spillStateForJSGetterSetter();
ASSERT(!spillStateForJSGetterSetter.isEmpty());
stackPointerOffset -= state.preservedReusedRegisterState.numberOfBytesPreserved;
stackPointerOffset -= spillStateForJSGetterSetter.numberOfStackBytesUsedForRegisterPreservation;
jit.loadPtr(vm.addressOfCallFrameForCatch(), GPRInfo::callFrameRegister);
jit.addPtr(CCallHelpers::TrustedImm32(stackPointerOffset), GPRInfo::callFrameRegister, CCallHelpers::stackPointerRegister);
state.restoreLiveRegistersFromStackForCallWithThrownException(spillStateForJSGetterSetter);
state.restoreScratch();
CCallHelpers::Jump jumpToOSRExitExceptionHandler = jit.jump();
HandlerInfo oldHandler = state.originalExceptionHandler();
CallSiteIndex newExceptionHandlingCallSite = state.callSiteIndexForExceptionHandling();
jit.addLinkTask(
[=] (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);
});
codeBlockThatOwnsExceptionHandlers = codeBlock;
ASSERT(JITCode::isOptimizingJIT(codeBlockThatOwnsExceptionHandlers->jitType()));
callSiteIndexForExceptionHandling = state.callSiteIndexForExceptionHandling();
}
LinkBuffer linkBuffer(jit, codeBlock, JITCompilationCanFail);
if (linkBuffer.didFailToAllocate()) {
if (verbose)
dataLog("Did fail to allocate.\n");
return AccessGenerationResult::GaveUp;
}
CodeLocationLabel successLabel = stubInfo.doneLocation();
linkBuffer.link(state.success, successLabel);
linkBuffer.link(failure, stubInfo.slowPathStartLocation());
if (verbose)
dataLog(FullCodeOrigin(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;
Vector<JSCell*> cellsToMark;
for (auto& entry : cases)
doesCalls |= entry->doesCalls(&cellsToMark);
m_stubRoutine = createJITStubRoutine(code, vm, codeBlock, doesCalls, cellsToMark, 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");
m_list = WTFMove(cases);
AccessGenerationResult::Kind resultKind;
if (m_list.size() >= Options::maxAccessVariantListSize())
resultKind = AccessGenerationResult::GeneratedFinalCode;
else
resultKind = AccessGenerationResult::GeneratedNewCode;
return AccessGenerationResult(resultKind, code.code());
}
void PolymorphicAccess::aboutToDie()
{
if (m_stubRoutine)
m_stubRoutine->aboutToDie();
}
}
namespace WTF {
using namespace JSC;
void printInternal(PrintStream& out, AccessGenerationResult::Kind kind)
{
switch (kind) {
case AccessGenerationResult::MadeNoChanges:
out.print("MadeNoChanges");
return;
case AccessGenerationResult::GaveUp:
out.print("GaveUp");
return;
case AccessGenerationResult::Buffered:
out.print("Buffered");
return;
case AccessGenerationResult::GeneratedNewCode:
out.print("GeneratedNewCode");
return;
case AccessGenerationResult::GeneratedFinalCode:
out.print("GeneratedFinalCode");
return;
}
RELEASE_ASSERT_NOT_REACHED();
}
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::GetGetter:
out.print("GetGetter");
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;
case AccessCase::DirectArgumentsLength:
out.print("DirectArgumentsLength");
return;
case AccessCase::ScopedArgumentsLength:
out.print("ScopedArgumentsLength");
return;
case AccessCase::ModuleNamespaceLoad:
out.print("ModuleNamespaceLoad");
return;
}
RELEASE_ASSERT_NOT_REACHED();
}
void printInternal(PrintStream& out, AccessCase::State state)
{
switch (state) {
case AccessCase::Primordial:
out.print("Primordial");
return;
case AccessCase::Committed:
out.print("Committed");
return;
case AccessCase::Generated:
out.print("Generated");
return;
}
RELEASE_ASSERT_NOT_REACHED();
}
}
#endif // ENABLE(JIT)