blob: 8c291b62a5183ae117f234dd28c599791e37a324 [file] [log] [blame]
/*
* Copyright (C) 2011-2023 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 "Repatch.h"
#include "BinarySwitch.h"
#include "CCallHelpers.h"
#include "CacheableIdentifierInlines.h"
#include "CallFrameShuffler.h"
#include "DFGOperations.h"
#include "DFGSpeculativeJIT.h"
#include "DOMJITGetterSetter.h"
#include "DirectArguments.h"
#include "ECMAMode.h"
#include "ExecutableBaseInlines.h"
#include "FTLThunks.h"
#include "FullCodeOrigin.h"
#include "FunctionCodeBlock.h"
#include "GCAwareJITStubRoutine.h"
#include "GetterSetter.h"
#include "GetterSetterAccessCase.h"
#include "ICStats.h"
#include "InlineAccess.h"
#include "InlineCacheCompiler.h"
#include "InstanceOfAccessCase.h"
#include "IntrinsicGetterAccessCase.h"
#include "JIT.h"
#include "JITInlines.h"
#include "JITThunks.h"
#include "JSCInlines.h"
#include "JSModuleNamespaceObject.h"
#include "JSWebAssembly.h"
#include "JSWebAssemblyInstance.h"
#include "JSWebAssemblyModule.h"
#include "LLIntData.h"
#include "LinkBuffer.h"
#include "MaxFrameExtentForSlowPathCall.h"
#include "ModuleNamespaceAccessCase.h"
#include "ScopedArguments.h"
#include "ScratchRegisterAllocator.h"
#include "StackAlignment.h"
#include "StructureRareDataInlines.h"
#include "StructureStubClearingWatchpoint.h"
#include "StructureStubInfo.h"
#include "SuperSampler.h"
#include "ThunkGenerators.h"
#include "WebAssemblyFunction.h"
#include <wtf/CommaPrinter.h>
#include <wtf/ListDump.h>
#include <wtf/StringPrintStream.h>
namespace JSC {
static void linkSlowFor(VM& vm, CallLinkInfo& callLinkInfo)
{
if (callLinkInfo.type() == CallLinkInfo::Type::Optimizing)
callLinkInfo.setVirtualCall(vm);
}
void linkMonomorphicCall(VM& vm, JSCell* owner, CallLinkInfo& callLinkInfo, CodeBlock* calleeCodeBlock, JSObject* callee, CodePtr<JSEntryPtrTag> codePtr)
{
ASSERT(!callLinkInfo.stub());
CodeBlock* callerCodeBlock = jsDynamicCast<CodeBlock*>(owner); // WebAssembly -> JS stubs don't have a valid CodeBlock.
ASSERT(owner);
if (Options::forceICFailure()) [[unlikely]]
return;
ASSERT(!callLinkInfo.isLinked());
callLinkInfo.setMonomorphicCallee(vm, owner, callee, calleeCodeBlock, codePtr);
callLinkInfo.setLastSeenCallee(vm, owner, callee);
if (shouldDumpDisassemblyFor(callerCodeBlock))
dataLog("Linking call in ", FullCodeOrigin(callerCodeBlock, callLinkInfo.codeOrigin()), " to ", pointerDump(calleeCodeBlock), ", entrypoint at ", codePtr, "\n");
if (calleeCodeBlock)
calleeCodeBlock->linkIncomingCall(owner, &callLinkInfo);
if (callLinkInfo.specializationKind() == CodeSpecializationKind::CodeForCall)
return;
linkSlowFor(vm, callLinkInfo);
}
CodePtr<JSEntryPtrTag> jsToWasmICCodePtr(CodeSpecializationKind kind, JSObject* callee)
{
#if ENABLE(WEBASSEMBLY)
if (!callee)
return nullptr;
if (kind != CodeSpecializationKind::CodeForCall)
return nullptr;
if (auto* wasmFunction = jsDynamicCast<WebAssemblyFunction*>(callee))
return wasmFunction->jsCallICEntrypoint();
#else
UNUSED_PARAM(kind);
UNUSED_PARAM(callee);
#endif
return nullptr;
}
void linkPolymorphicCall(VM& vm, JSCell* owner, CallFrame* callFrame, CallLinkInfo& callLinkInfo, CallVariant newVariant)
{
// During execution of linkPolymorphicCall, we strongly assume that we never do GC.
// GC jettisons CodeBlocks, changes CallLinkInfo etc. and breaks assumption done before and after this call.
DeferGCForAWhile deferGCForAWhile(vm);
if (!newVariant) {
callLinkInfo.setVirtualCall(vm);
return;
}
CodeBlock* callerCodeBlock = jsDynamicCast<CodeBlock*>(owner); // WebAssembly -> JS stubs don't have a valid CodeBlock.
ASSERT(owner);
#if ENABLE(WEBASSEMBLY)
bool isWebAssembly = owner->inherits<JSWebAssemblyModule>();
#else
bool isWebAssembly = false;
#endif
bool isTailCall = callLinkInfo.isTailCall();
bool isClosureCall = false;
CallVariantList list;
if (PolymorphicCallStubRoutine* stub = callLinkInfo.stub()) {
list = stub->variants();
isClosureCall = stub->isClosureCall();
} else if (JSObject* oldCallee = callLinkInfo.callee())
list = CallVariantList { CallVariant(oldCallee) };
list = variantListWithVariant(list, newVariant);
// If there are any closure calls then it makes sense to treat all of them as closure calls.
// This makes switching on callee cheaper. It also produces profiling that's easier on the DFG;
// the DFG doesn't really want to deal with a combination of closure and non-closure callees.
if (!isClosureCall) {
for (CallVariant variant : list) {
if (variant.isClosureCall()) {
list = despecifiedVariantList(list);
isClosureCall = true;
break;
}
}
}
if (isClosureCall)
callLinkInfo.setHasSeenClosure();
// If we are over the limit, just use a normal virtual call.
unsigned maxPolymorphicCallVariantListSize;
if (isWebAssembly)
maxPolymorphicCallVariantListSize = Options::maxPolymorphicCallVariantListSizeForWasmToJS();
else if (callerCodeBlock->jitType() == JITCode::topTierJIT())
maxPolymorphicCallVariantListSize = Options::maxPolymorphicCallVariantListSizeForTopTier();
else
maxPolymorphicCallVariantListSize = Options::maxPolymorphicCallVariantListSize();
// We use list.size() instead of callSlots.size() because we respect CallVariant size for now.
if (list.size() > maxPolymorphicCallVariantListSize) {
callLinkInfo.setVirtualCall(vm);
return;
}
Vector<CallSlot, 16> callSlots;
// Figure out what our cases are.
for (CallVariant variant : list) {
CodeBlock* codeBlock = nullptr;
if (variant.executable() && !variant.executable()->isHostFunction()) {
ExecutableBase* executable = variant.executable();
codeBlock = jsCast<FunctionExecutable*>(executable)->codeBlockForCall();
// If we cannot handle a callee, because we don't have a CodeBlock,
// assume that it's better for this whole thing to be a virtual call.
if (!codeBlock) {
callLinkInfo.setVirtualCall(vm);
return;
}
}
JSCell* caseValue = nullptr;
if (isClosureCall) {
caseValue = variant.executable();
// FIXME: We could add a fast path for InternalFunction with closure call.
// https://bugs.webkit.org/show_bug.cgi?id=179311
if (!caseValue)
continue;
} else {
if (auto* function = variant.function())
caseValue = function;
else
caseValue = variant.internalFunction();
}
CallSlot slot;
CodePtr<JSEntryPtrTag> codePtr;
if (variant.executable()) {
ASSERT(variant.executable()->hasJITCodeForCall());
codePtr = jsToWasmICCodePtr(callLinkInfo.specializationKind(), variant.function());
if (!codePtr) {
ArityCheckMode arityCheck = ArityCheckMode::ArityCheckNotRequired;
if (codeBlock) {
ASSERT(!variant.executable()->isHostFunction());
if ((callFrame->argumentCountIncludingThis() < static_cast<size_t>(codeBlock->numParameters()) || callLinkInfo.isVarargs()))
arityCheck = ArityCheckMode::MustCheckArity;
}
codePtr = variant.executable()->generatedJITCodeForCall()->addressForCall(arityCheck);
slot.m_arityCheckMode = arityCheck;
}
} else {
ASSERT(variant.internalFunction());
codePtr = vm.getCTIInternalFunctionTrampolineFor(CodeSpecializationKind::CodeForCall);
}
slot.m_index = callSlots.size();
slot.m_target = codePtr;
slot.m_codeBlock = codeBlock;
slot.m_calleeOrExecutable = caseValue;
callSlots.append(WTFMove(slot));
}
bool notUsingCounting = isWebAssembly || callerCodeBlock->jitType() == JITCode::topTierJIT();
if (callSlots.isEmpty())
notUsingCounting = true;
CallFrame* callerFrame = nullptr;
if (!isTailCall)
callerFrame = callFrame->callerFrame();
MacroAssemblerCodeRef<JITStubRoutinePtrTag> code;
#if ENABLE(JIT)
if (Options::useJIT()) {
CommonJITThunkID jitThunk = CommonJITThunkID::PolymorphicThunkForClosure;
if (notUsingCounting)
jitThunk = isClosureCall ? CommonJITThunkID::PolymorphicTopTierThunkForClosure : CommonJITThunkID::PolymorphicTopTierThunk;
else
jitThunk = isClosureCall ? CommonJITThunkID::PolymorphicThunkForClosure : CommonJITThunkID::PolymorphicThunk;
code = vm.getCTIStub(jitThunk).retagged<JITStubRoutinePtrTag>();
}
#endif
if (!code) {
if (isClosureCall)
code = LLInt::getCodeRef<JITStubRoutinePtrTag>(llint_polymorphic_closure_call_trampoline);
else
code = LLInt::getCodeRef<JITStubRoutinePtrTag>(llint_polymorphic_normal_call_trampoline);
}
auto stubRoutine = PolymorphicCallStubRoutine::create(WTFMove(code), vm, owner, callerFrame, callLinkInfo, callSlots, notUsingCounting, isClosureCall);
// If there had been a previous stub routine, that one will die as soon as the GC runs and sees
// that it's no longer on stack.
callLinkInfo.setStub(WTFMove(stubRoutine));
}
#if ENABLE(JIT)
static ECMAMode ecmaModeFor(PutByKind putByKind)
{
switch (putByKind) {
case PutByKind::ByIdSloppy:
case PutByKind::ByValSloppy:
case PutByKind::ByIdDirectSloppy:
case PutByKind::ByValDirectSloppy:
return ECMAMode::sloppy();
case PutByKind::ByIdStrict:
case PutByKind::ByValStrict:
case PutByKind::ByIdDirectStrict:
case PutByKind::ByValDirectStrict:
case PutByKind::DefinePrivateNameById:
case PutByKind::DefinePrivateNameByVal:
case PutByKind::SetPrivateNameById:
case PutByKind::SetPrivateNameByVal:
return ECMAMode::strict();
}
RELEASE_ASSERT_NOT_REACHED();
}
void ftlThunkAwareRepatchCall(CodeBlock* codeBlock, CodeLocationCall<JSInternalPtrTag> call, CodePtr<CFunctionPtrTag> newCalleeFunction)
{
#if ENABLE(FTL_JIT)
if (codeBlock->jitType() == JITType::FTLJIT) {
VM& vm = codeBlock->vm();
FTL::Thunks& thunks = *vm.ftlThunks;
CodePtr<JITThunkPtrTag> slowPathThunk = MacroAssembler::readCallTarget<JITThunkPtrTag>(call);
FTL::SlowPathCallKey key = thunks.keyForSlowPathCallThunk(slowPathThunk);
key = key.withCallTarget(newCalleeFunction);
MacroAssembler::repatchCall(call, thunks.getSlowPathCallThunk(vm, key).code());
return;
}
#else // ENABLE(FTL_JIT)
UNUSED_PARAM(codeBlock);
#endif // ENABLE(FTL_JIT)
MacroAssembler::repatchCall(call, newCalleeFunction.retagged<OperationPtrTag>());
}
static void repatchSlowPathCall(CodeBlock* codeBlock, StructureStubInfo& stubInfo, CodePtr<CFunctionPtrTag> newCalleeFunction)
{
if (stubInfo.useDataIC) {
stubInfo.m_slowOperation = newCalleeFunction.retagged<OperationPtrTag>();
return;
}
ftlThunkAwareRepatchCall(codeBlock, stubInfo.m_slowPathCallLocation, newCalleeFunction);
}
enum InlineCacheAction {
GiveUpOnCache,
RetryCacheLater,
AttemptToCache,
PromoteToMegamorphic,
};
static InlineCacheAction actionForCell(VM& vm, JSCell* cell)
{
Structure* structure = cell->structure();
TypeInfo typeInfo = structure->typeInfo();
if (typeInfo.prohibitsPropertyCaching())
return GiveUpOnCache;
if (structure->isUncacheableDictionary()) {
if (structure->hasBeenFlattenedBefore())
return GiveUpOnCache;
// Flattening could have changed the offset, so return early for another try.
asObject(cell)->flattenDictionaryObject(vm);
return RetryCacheLater;
}
if (!structure->propertyAccessesAreCacheable())
return GiveUpOnCache;
return AttemptToCache;
}
static bool forceICFailure(JSGlobalObject*)
{
return Options::forceICFailure();
}
ALWAYS_INLINE static void fireWatchpointsAndClearStubIfNeeded(VM& vm, StructureStubInfo& stubInfo, CodeBlock* codeBlock, AccessGenerationResult& result)
{
if (result.shouldResetStubAndFireWatchpoints()) {
result.fireWatchpoints(vm);
{
GCSafeConcurrentJSLocker locker(codeBlock->m_lock, vm);
stubInfo.reset(locker, codeBlock);
}
}
}
inline CodePtr<CFunctionPtrTag> appropriateGetByOptimizeFunction(GetByKind kind)
{
switch (kind) {
case GetByKind::ById:
return operationGetByIdOptimize;
case GetByKind::ByIdWithThis:
return operationGetByIdWithThisOptimize;
case GetByKind::TryById:
return operationTryGetByIdOptimize;
case GetByKind::ByIdDirect:
return operationGetByIdDirectOptimize;
case GetByKind::ByVal:
return operationGetByValOptimize;
case GetByKind::ByValWithThis:
return operationGetByValWithThisOptimize;
case GetByKind::PrivateName:
return operationGetPrivateNameOptimize;
case GetByKind::PrivateNameById:
return operationGetPrivateNameByIdOptimize;
}
RELEASE_ASSERT_NOT_REACHED();
}
inline CodePtr<CFunctionPtrTag> appropriateGetByGaveUpFunction(GetByKind kind)
{
switch (kind) {
case GetByKind::ById:
return operationGetByIdGaveUp;
case GetByKind::ByIdWithThis:
return operationGetByIdWithThisGaveUp;
case GetByKind::TryById:
return operationTryGetByIdGaveUp;
case GetByKind::ByIdDirect:
return operationGetByIdDirectGaveUp;
case GetByKind::ByVal:
return operationGetByValGaveUp;
case GetByKind::ByValWithThis:
return operationGetByValWithThisGaveUp;
case GetByKind::PrivateName:
return operationGetPrivateNameGaveUp;
case GetByKind::PrivateNameById:
return operationGetPrivateNameByIdGaveUp;
}
RELEASE_ASSERT_NOT_REACHED();
}
static InlineCacheAction tryCacheGetBy(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue baseValue, CacheableIdentifier propertyName, const PropertySlot& slot, StructureStubInfo& stubInfo, GetByKind kind)
{
VM& vm = globalObject->vm();
AccessGenerationResult result;
{
GCSafeConcurrentJSLocker locker(codeBlock->m_lock, globalObject->vm());
if (forceICFailure(globalObject))
return GiveUpOnCache;
// FIXME: Cache property access for immediates.
if (!baseValue.isCell())
return GiveUpOnCache;
JSCell* baseCell = baseValue.asCell();
const bool isPrivate = kind == GetByKind::PrivateName || kind == GetByKind::PrivateNameById;
RefPtr<AccessCase> newCase;
if (propertyName == vm.propertyNames->length) {
auto lengthPropertyName = CacheableIdentifier::createFromImmortalIdentifier(vm.propertyNames->length.impl());
if (isJSArray(baseCell)) {
if (stubInfo.cacheType() == CacheType::Unset
&& slot.slotBase() == baseCell
&& InlineAccess::isCacheableArrayLength(stubInfo, jsCast<JSArray*>(baseCell))) {
bool generatedCodeInline = InlineAccess::generateArrayLength(stubInfo, jsCast<JSArray*>(baseCell));
if (generatedCodeInline) {
repatchSlowPathCall(codeBlock, stubInfo, appropriateGetByOptimizeFunction(kind));
stubInfo.initArrayLength(locker);
return RetryCacheLater;
}
}
newCase = AccessCase::create(vm, codeBlock, AccessCase::ArrayLength, lengthPropertyName);
} else if (isJSString(baseCell)) {
if (stubInfo.cacheType() == CacheType::Unset
&& InlineAccess::isCacheableStringLength(stubInfo)) {
bool generatedCodeInline = InlineAccess::generateStringLength(stubInfo);
if (generatedCodeInline) {
repatchSlowPathCall(codeBlock, stubInfo, appropriateGetByOptimizeFunction(kind));
stubInfo.initStringLength(locker);
return RetryCacheLater;
}
}
newCase = AccessCase::create(vm, codeBlock, AccessCase::StringLength, lengthPropertyName);
} else if (DirectArguments* arguments = jsDynamicCast<DirectArguments*>(baseCell)) {
// If there were overrides, then we can handle this as a normal property load! Guarding
// this with such a check enables us to add an IC case for that load if needed.
if (!arguments->overrodeThings())
newCase = AccessCase::create(vm, codeBlock, AccessCase::DirectArgumentsLength, lengthPropertyName);
} else if (ScopedArguments* arguments = jsDynamicCast<ScopedArguments*>(baseCell)) {
// Ditto.
if (!arguments->overrodeThings())
newCase = AccessCase::create(vm, codeBlock, AccessCase::ScopedArgumentsLength, lengthPropertyName);
}
}
if (!propertyName.isSymbol() && baseCell->inherits<JSModuleNamespaceObject>() && !slot.isUnset()) {
if (auto moduleNamespaceSlot = slot.moduleNamespaceSlot())
newCase = ModuleNamespaceAccessCase::create(vm, codeBlock, propertyName, jsCast<JSModuleNamespaceObject*>(baseCell), moduleNamespaceSlot->environment, ScopeOffset(moduleNamespaceSlot->scopeOffset));
}
if (!propertyName.isPrivateName() && baseCell->inherits<ProxyObject>()) {
switch (kind) {
case GetByKind::ById:
case GetByKind::ByIdWithThis: {
propertyName.ensureIsCell(vm);
newCase = AccessCase::create(vm, codeBlock, AccessCase::ProxyObjectLoad, propertyName);
break;
}
case GetByKind::ByVal:
case GetByKind::ByValWithThis: {
newCase = AccessCase::create(vm, codeBlock, AccessCase::IndexedProxyObjectLoad, nullptr);
break;
}
case GetByKind::PrivateName:
case GetByKind::PrivateNameById:
RELEASE_ASSERT_NOT_REACHED();
default:
break;
}
}
if (!newCase) {
if (!slot.isCacheable() && !slot.isUnset())
return GiveUpOnCache;
ObjectPropertyConditionSet conditionSet;
Structure* structure = baseCell->structure();
bool loadTargetFromProxy = false;
if (baseCell->type() == GlobalProxyType) {
if (isPrivate)
return GiveUpOnCache;
baseValue = jsCast<JSGlobalProxy*>(baseCell)->target();
baseCell = baseValue.asCell();
structure = baseCell->structure();
loadTargetFromProxy = true;
}
InlineCacheAction action = actionForCell(vm, baseCell);
if (action != AttemptToCache)
return action;
// Optimize self access.
if (stubInfo.cacheType() == CacheType::Unset
&& slot.isCacheableValue()
&& slot.slotBase() == baseValue
&& !slot.watchpointSet()
&& !structure->needImpurePropertyWatchpoint()
&& !loadTargetFromProxy) {
bool generatedCodeInline = InlineAccess::generateSelfPropertyAccess(stubInfo, structure, slot.cachedOffset());
if (generatedCodeInline) {
LOG_IC((vm, ICEvent::GetBySelfPatch, structure->classInfoForCells(), Identifier::fromUid(vm, propertyName.uid()), slot.slotBase() == baseValue));
structure->startWatchingPropertyForReplacements(vm, slot.cachedOffset());
repatchSlowPathCall(codeBlock, stubInfo, appropriateGetByOptimizeFunction(kind));
stubInfo.initGetByIdSelf(locker, codeBlock, structure, slot.cachedOffset());
return RetryCacheLater;
}
}
RefPtr<PolyProtoAccessChain> prototypeAccessChain;
PropertyOffset offset = slot.isUnset() ? invalidOffset : slot.cachedOffset();
if (slot.isCustom() && slot.slotBase() == baseValue) {
// To cache self customs, we must disallow dictionaries because we
// need to be informed if the custom goes away since we cache the
// constant function pointer.
if (!prepareChainForCaching(globalObject, slot.slotBase(), propertyName.uid(), slot.slotBase()))
return GiveUpOnCache;
}
if (slot.isUnset() || slot.slotBase() != baseValue) {
if (structure->typeInfo().prohibitsPropertyCaching())
return GiveUpOnCache;
if (structure->isDictionary()) {
if (structure->hasBeenFlattenedBefore())
return GiveUpOnCache;
structure->flattenDictionaryStructure(vm, jsCast<JSObject*>(baseCell));
return RetryCacheLater; // We may have changed property offsets.
}
if (slot.isUnset() && structure->typeInfo().getOwnPropertySlotIsImpureForPropertyAbsence())
return GiveUpOnCache;
// If a kind is GetByKind::ByIdDirect or GetByKind::PrivateName, we do not need to investigate prototype chains further.
// Cacheability just depends on the head structure.
if (kind != GetByKind::ByIdDirect && !isPrivate) {
auto cacheStatus = prepareChainForCaching(globalObject, baseCell, propertyName.uid(), slot);
if (!cacheStatus)
return GiveUpOnCache;
if (cacheStatus->flattenedDictionary) {
// Property offsets may have changed due to flattening. We'll cache later.
return RetryCacheLater;
}
if (cacheStatus->usesPolyProto) {
prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, baseCell, propertyName, slot);
if (!prototypeAccessChain)
return GiveUpOnCache;
ASSERT(slot.isCacheableCustom() || prototypeAccessChain->slotBaseStructure(vm, structure)->get(vm, propertyName.uid()) == offset);
} else {
// We use ObjectPropertyConditionSet instead for faster accesses.
prototypeAccessChain = nullptr;
// FIXME: Maybe this `if` should be inside generateConditionsForPropertyBlah.
// https://bugs.webkit.org/show_bug.cgi?id=185215
if (slot.isUnset()) {
conditionSet = generateConditionsForPropertyMiss(
vm, codeBlock, globalObject, structure, propertyName.uid());
} else if (!slot.isCacheableCustom()) {
conditionSet = generateConditionsForPrototypePropertyHit(
vm, codeBlock, globalObject, structure, slot.slotBase(),
propertyName.uid());
RELEASE_ASSERT(!conditionSet.isValid() || conditionSet.slotBaseCondition().offset() == offset);
} else {
conditionSet = generateConditionsForPrototypePropertyHitCustom(
vm, codeBlock, globalObject, structure, slot.slotBase(),
propertyName.uid(), slot.attributes());
}
if (!conditionSet.isValid())
return GiveUpOnCache;
}
}
}
JSFunction* getter = nullptr;
if (slot.isCacheableGetter())
getter = jsDynamicCast<JSFunction*>(slot.getterSetter()->getter());
std::optional<DOMAttributeAnnotation> domAttribute;
if (slot.isCacheableCustom() && slot.domAttribute())
domAttribute = slot.domAttribute();
if (kind == GetByKind::TryById) {
AccessCase::AccessType type;
if (slot.isCacheableValue())
type = AccessCase::Load;
else if (slot.isUnset())
type = AccessCase::Miss;
else if (slot.isCacheableGetter())
type = AccessCase::GetGetter;
else
RELEASE_ASSERT_NOT_REACHED();
newCase = ProxyableAccessCase::create(vm, codeBlock, type, propertyName, offset, structure, conditionSet, loadTargetFromProxy, slot.watchpointSet(), WTFMove(prototypeAccessChain));
} else if (!loadTargetFromProxy && getter && InlineCacheCompiler::canEmitIntrinsicGetter(stubInfo, getter, structure))
newCase = IntrinsicGetterAccessCase::create(vm, codeBlock, propertyName, slot.cachedOffset(), structure, conditionSet, getter, WTFMove(prototypeAccessChain));
else {
if (isPrivate) {
RELEASE_ASSERT(!slot.isUnset());
RELEASE_ASSERT(conditionSet.isEmpty());
constexpr bool isGlobalProxy = false;
if (!slot.isCacheable())
return GiveUpOnCache;
newCase = ProxyableAccessCase::create(vm, codeBlock, AccessCase::Load, propertyName, offset, structure,
conditionSet, isGlobalProxy, slot.watchpointSet(), WTFMove(prototypeAccessChain));
} else if (slot.isCacheableValue() || slot.isUnset()) {
newCase = ProxyableAccessCase::create(vm, codeBlock, slot.isUnset() ? AccessCase::Miss : AccessCase::Load,
propertyName, offset, structure, conditionSet, loadTargetFromProxy, slot.watchpointSet(), WTFMove(prototypeAccessChain));
} else {
AccessCase::AccessType type;
if (slot.isCacheableGetter())
type = AccessCase::Getter;
else if (slot.attributes() & PropertyAttribute::CustomAccessor)
type = AccessCase::CustomAccessorGetter;
else
type = AccessCase::CustomValueGetter;
if ((kind == GetByKind::ByIdWithThis || kind == GetByKind::ByValWithThis) && type == AccessCase::CustomAccessorGetter && domAttribute)
return GiveUpOnCache;
CodePtr<CustomAccessorPtrTag> customAccessor;
if (slot.isCacheableCustom())
customAccessor = slot.customGetter();
newCase = GetterSetterAccessCase::create(
vm, codeBlock, type, propertyName, offset, structure, conditionSet, loadTargetFromProxy,
slot.watchpointSet(), customAccessor,
slot.isCacheableCustom() && slot.slotBase() != baseValue ? slot.slotBase() : nullptr,
domAttribute, WTFMove(prototypeAccessChain));
}
}
}
LOG_IC((vm, ICEvent::GetByAddAccessCase, baseValue.classInfoOrNull(), Identifier::fromUid(vm, propertyName.uid()), slot.slotBase() == baseValue));
result = stubInfo.addAccessCase(locker, globalObject, codeBlock, ECMAMode::strict(), propertyName, WTFMove(newCase));
if (result.generatedSomeCode())
LOG_IC((vm, ICEvent::GetByReplaceWithJump, baseValue.classInfoOrNull(), Identifier::fromUid(vm, propertyName.uid()), slot.slotBase() == baseValue));
}
fireWatchpointsAndClearStubIfNeeded(vm, stubInfo, codeBlock, result);
if (result.generatedMegamorphicCode())
return PromoteToMegamorphic;
return result.shouldGiveUpNow() ? GiveUpOnCache : RetryCacheLater;
}
void repatchGetBy(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue baseValue, CacheableIdentifier propertyName, const PropertySlot& slot, StructureStubInfo& stubInfo, GetByKind kind)
{
SuperSamplerScope superSamplerScope(false);
switch (tryCacheGetBy(globalObject, codeBlock, baseValue, propertyName, slot, stubInfo, kind)) {
case PromoteToMegamorphic: {
switch (kind) {
case GetByKind::ById:
repatchSlowPathCall(codeBlock, stubInfo, operationGetByIdMegamorphic);
break;
case GetByKind::ByIdWithThis:
repatchSlowPathCall(codeBlock, stubInfo, operationGetByIdWithThisMegamorphic);
break;
case GetByKind::ByVal:
repatchSlowPathCall(codeBlock, stubInfo, operationGetByValMegamorphic);
break;
case GetByKind::ByValWithThis:
repatchSlowPathCall(codeBlock, stubInfo, operationGetByValWithThisMegamorphic);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
case GiveUpOnCache:
repatchSlowPathCall(codeBlock, stubInfo, appropriateGetByGaveUpFunction(kind));
break;
case RetryCacheLater:
case AttemptToCache:
break;
}
}
// Mainly used to transition from megamorphic case to generic case.
void repatchGetBySlowPathCall(CodeBlock* codeBlock, StructureStubInfo& stubInfo, GetByKind kind)
{
resetGetBy(codeBlock, stubInfo, kind);
repatchSlowPathCall(codeBlock, stubInfo, appropriateGetByGaveUpFunction(kind));
}
static InlineCacheAction tryCacheArrayGetByVal(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue baseValue, JSValue index, StructureStubInfo& stubInfo)
{
if (!baseValue.isCell())
return GiveUpOnCache;
if (!index.isInt32())
return RetryCacheLater;
VM& vm = globalObject->vm();
AccessGenerationResult result;
{
GCSafeConcurrentJSLocker locker(codeBlock->m_lock, globalObject->vm());
JSCell* base = baseValue.asCell();
RefPtr<AccessCase> newCase;
AccessCase::AccessType accessType = AccessCase::IndexedInt32Load;
if (base->type() == DirectArgumentsType)
accessType = AccessCase::IndexedDirectArgumentsLoad;
else if (base->type() == ScopedArgumentsType)
accessType = AccessCase::IndexedScopedArgumentsLoad;
else if (base->type() == StringType)
accessType = AccessCase::IndexedStringLoad;
else if (base->type() == ProxyObjectType)
accessType = AccessCase::IndexedProxyObjectLoad;
else if (isTypedView(base->type())) {
auto* typedArray = jsCast<JSArrayBufferView*>(base);
#if USE(JSVALUE32_64)
if (typedArray->isResizableOrGrowableShared())
return GiveUpOnCache;
#endif
switch (typedArray->type()) {
case Int8ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayInt8Load : AccessCase::IndexedTypedArrayInt8Load;
break;
case Uint8ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayUint8Load : AccessCase::IndexedTypedArrayUint8Load;
break;
case Uint8ClampedArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayUint8ClampedLoad : AccessCase::IndexedTypedArrayUint8ClampedLoad;
break;
case Int16ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayInt16Load : AccessCase::IndexedTypedArrayInt16Load;
break;
case Uint16ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayUint16Load : AccessCase::IndexedTypedArrayUint16Load;
break;
case Int32ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayInt32Load : AccessCase::IndexedTypedArrayInt32Load;
break;
case Uint32ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayUint32Load : AccessCase::IndexedTypedArrayUint32Load;
break;
case Float16ArrayType:
if (!CCallHelpers::supportsFloat16())
return GiveUpOnCache;
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayFloat16Load : AccessCase::IndexedTypedArrayFloat16Load;
break;
case Float32ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayFloat32Load : AccessCase::IndexedTypedArrayFloat32Load;
break;
case Float64ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayFloat64Load : AccessCase::IndexedTypedArrayFloat64Load;
break;
// FIXME: Optimize BigInt64Array / BigUint64Array in IC
// https://bugs.webkit.org/show_bug.cgi?id=221183
case BigInt64ArrayType:
case BigUint64ArrayType:
return GiveUpOnCache;
default:
RELEASE_ASSERT_NOT_REACHED();
}
} else {
IndexingType indexingShape = base->indexingType() & IndexingShapeMask;
switch (indexingShape) {
case Int32Shape:
accessType = AccessCase::IndexedInt32Load;
break;
case DoubleShape:
ASSERT(Options::allowDoubleShape());
accessType = AccessCase::IndexedDoubleLoad;
break;
case ContiguousShape:
accessType = AccessCase::IndexedContiguousLoad;
break;
case ArrayStorageShape:
accessType = AccessCase::IndexedArrayStorageLoad;
break;
case NoIndexingShape: {
if (!base->isObject())
return GiveUpOnCache;
if (base->structure()->mayInterceptIndexedAccesses() || base->structure()->typeInfo().interceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero())
return GiveUpOnCache;
// FIXME: prepareChainForCaching is conservative. We should have another function which only cares about information related to this IC.
auto cacheStatus = prepareChainForCaching(globalObject, base, nullptr, nullptr);
if (!cacheStatus)
return GiveUpOnCache;
if (cacheStatus->usesPolyProto)
return GiveUpOnCache;
Structure* headStructure = base->structure();
ObjectPropertyConditionSet conditionSet = generateConditionsForIndexedMiss(vm, codeBlock, globalObject, headStructure);
if (!conditionSet.isValid())
return GiveUpOnCache;
newCase = AccessCase::create(vm, codeBlock, AccessCase::IndexedNoIndexingMiss, nullptr, invalidOffset, headStructure, conditionSet);
break;
}
default:
return GiveUpOnCache;
}
}
if (!newCase)
newCase = AccessCase::create(vm, codeBlock, accessType, nullptr);
result = stubInfo.addAccessCase(locker, globalObject, codeBlock, ECMAMode::strict(), nullptr, newCase.releaseNonNull());
if (result.generatedSomeCode())
LOG_IC((vm, ICEvent::GetByReplaceWithJump, baseValue.classInfoOrNull(), Identifier()));
}
fireWatchpointsAndClearStubIfNeeded(vm, stubInfo, codeBlock, result);
if (result.generatedMegamorphicCode())
return PromoteToMegamorphic;
return result.shouldGiveUpNow() ? GiveUpOnCache : RetryCacheLater;
}
void repatchArrayGetByVal(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue base, JSValue index, StructureStubInfo& stubInfo, GetByKind kind)
{
switch (tryCacheArrayGetByVal(globalObject, codeBlock, base, index, stubInfo)) {
case PromoteToMegamorphic: {
switch (kind) {
case GetByKind::ById:
repatchSlowPathCall(codeBlock, stubInfo, operationGetByIdMegamorphic);
break;
case GetByKind::ByIdWithThis:
repatchSlowPathCall(codeBlock, stubInfo, operationGetByIdWithThisMegamorphic);
break;
case GetByKind::ByVal:
repatchSlowPathCall(codeBlock, stubInfo, operationGetByValMegamorphic);
break;
case GetByKind::ByValWithThis:
repatchSlowPathCall(codeBlock, stubInfo, operationGetByValWithThisMegamorphic);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
case GiveUpOnCache:
repatchSlowPathCall(codeBlock, stubInfo, appropriateGetByGaveUpFunction(kind));
break;
case RetryCacheLater:
case AttemptToCache:
break;
}
}
static CodePtr<CFunctionPtrTag> appropriatePutByGaveUpFunction(PutByKind putByKind)
{
switch (putByKind) {
case PutByKind::ByIdStrict:
return operationPutByIdStrictGaveUp;
case PutByKind::ByIdSloppy:
return operationPutByIdSloppyGaveUp;
case PutByKind::ByIdDirectStrict:
return operationPutByIdDirectStrictGaveUp;
case PutByKind::ByIdDirectSloppy:
return operationPutByIdDirectSloppyGaveUp;
case PutByKind::DefinePrivateNameById:
return operationPutByIdDefinePrivateFieldStrictGaveUp;
case PutByKind::SetPrivateNameById:
return operationPutByIdSetPrivateFieldStrictGaveUp;
case PutByKind::ByValStrict:
return operationPutByValStrictGaveUp;
case PutByKind::ByValSloppy:
return operationPutByValSloppyGaveUp;
case PutByKind::ByValDirectStrict:
return operationDirectPutByValStrictGaveUp;
case PutByKind::ByValDirectSloppy:
return operationDirectPutByValSloppyGaveUp;
case PutByKind::DefinePrivateNameByVal:
return operationPutByValDefinePrivateFieldGaveUp;
case PutByKind::SetPrivateNameByVal:
return operationPutByValSetPrivateFieldGaveUp;
}
// Make win port compiler happy
RELEASE_ASSERT_NOT_REACHED();
return nullptr;
}
// Mainly used to transition from megamorphic case to generic case.
void repatchPutBySlowPathCall(CodeBlock* codeBlock, StructureStubInfo& stubInfo, PutByKind kind)
{
resetPutBy(codeBlock, stubInfo, kind);
repatchSlowPathCall(codeBlock, stubInfo, appropriatePutByGaveUpFunction(kind));
}
static CodePtr<CFunctionPtrTag> appropriatePutByOptimizeFunction(PutByKind putByKind)
{
switch (putByKind) {
case PutByKind::ByIdStrict:
return operationPutByIdStrictOptimize;
case PutByKind::ByIdSloppy:
return operationPutByIdSloppyOptimize;
case PutByKind::ByIdDirectStrict:
return operationPutByIdDirectStrictOptimize;
case PutByKind::ByIdDirectSloppy:
return operationPutByIdDirectSloppyOptimize;
case PutByKind::DefinePrivateNameById:
return operationPutByIdDefinePrivateFieldStrictOptimize;
case PutByKind::SetPrivateNameById:
return operationPutByIdSetPrivateFieldStrictOptimize;
case PutByKind::ByValStrict:
return operationPutByValStrictOptimize;
case PutByKind::ByValSloppy:
return operationPutByValSloppyOptimize;
case PutByKind::ByValDirectStrict:
return operationDirectPutByValStrictOptimize;
case PutByKind::ByValDirectSloppy:
return operationDirectPutByValSloppyOptimize;
case PutByKind::DefinePrivateNameByVal:
return operationPutByValDefinePrivateFieldOptimize;
case PutByKind::SetPrivateNameByVal:
return operationPutByValSetPrivateFieldOptimize;
}
// Make win port compiler happy
RELEASE_ASSERT_NOT_REACHED();
return nullptr;
}
static InlineCacheAction tryCachePutBy(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue baseValue, Structure* oldStructure, CacheableIdentifier propertyName, const PutPropertySlot& slot, StructureStubInfo& stubInfo, PutByKind putByKind)
{
VM& vm = globalObject->vm();
AccessGenerationResult result;
Identifier ident = Identifier::fromUid(vm, propertyName.uid());
{
GCSafeConcurrentJSLocker locker(codeBlock->m_lock, globalObject->vm());
if (forceICFailure(globalObject))
return GiveUpOnCache;
if (!baseValue.isCell())
return GiveUpOnCache;
JSCell* baseCell = baseValue.asCell();
bool isProxyObject = baseCell->type() == ProxyObjectType;
if (!isProxyObject) {
if (!slot.isCacheablePut() && !slot.isCacheableCustom() && !slot.isCacheableSetter())
return GiveUpOnCache;
// FIXME: We should try to do something smarter here...
if (isCopyOnWrite(oldStructure->indexingMode()))
return GiveUpOnCache;
// We can't end up storing to a CoW on the prototype since it shouldn't own properties.
ASSERT(!isCopyOnWrite(slot.base()->indexingMode()));
if (!oldStructure->propertyAccessesAreCacheable())
return GiveUpOnCache;
}
bool isGlobalProxy = false;
if (baseCell->type() == GlobalProxyType) {
baseCell = jsCast<JSGlobalProxy*>(baseCell)->target();
baseValue = baseCell;
isGlobalProxy = true;
// We currently only cache Replace and JS/Custom Setters on JSGlobalProxy. We don't
// cache transitions because global objects will never share the same structure
// in our current implementation.
bool isCacheableProxy = (slot.isCacheablePut() && slot.type() == PutPropertySlot::ExistingProperty)
|| slot.isCacheableSetter()
|| slot.isCacheableCustom();
if (!isCacheableProxy)
return GiveUpOnCache;
}
if (isGlobalProxy) {
switch (putByKind) {
case PutByKind::DefinePrivateNameById:
case PutByKind::DefinePrivateNameByVal:
case PutByKind::SetPrivateNameById:
case PutByKind::SetPrivateNameByVal:
return GiveUpOnCache;
case PutByKind::ByIdStrict:
case PutByKind::ByIdSloppy:
case PutByKind::ByValStrict:
case PutByKind::ByValSloppy:
case PutByKind::ByIdDirectStrict:
case PutByKind::ByIdDirectSloppy:
case PutByKind::ByValDirectStrict:
case PutByKind::ByValDirectSloppy:
break;
}
}
RefPtr<AccessCase> newCase;
if (slot.base() == baseValue && slot.isCacheablePut()) {
if (slot.type() == PutPropertySlot::ExistingProperty) {
// This assert helps catch bugs if we accidentally forget to disable caching
// when we transition then store to an existing property. This is common among
// paths that reify lazy properties. If we reify a lazy property and forget
// to disable caching, we may come down this path. The Replace IC does not
// know how to model these types of structure transitions (or any structure
// transition for that matter).
RELEASE_ASSERT(baseValue.asCell()->structure() == oldStructure);
oldStructure->didCachePropertyReplacement(vm, slot.cachedOffset());
if (stubInfo.cacheType() == CacheType::Unset
&& InlineAccess::canGenerateSelfPropertyReplace(stubInfo, slot.cachedOffset())
&& !oldStructure->needImpurePropertyWatchpoint()
&& !isGlobalProxy) {
bool generatedCodeInline = InlineAccess::generateSelfPropertyReplace(stubInfo, oldStructure, slot.cachedOffset());
if (generatedCodeInline) {
LOG_IC((vm, ICEvent::PutBySelfPatch, oldStructure->classInfoForCells(), ident, slot.base() == baseValue));
repatchSlowPathCall(codeBlock, stubInfo, appropriatePutByOptimizeFunction(putByKind));
stubInfo.initPutByIdReplace(locker, codeBlock, oldStructure, slot.cachedOffset());
return RetryCacheLater;
}
}
newCase = AccessCase::createReplace(vm, codeBlock, propertyName, slot.cachedOffset(), oldStructure, isGlobalProxy);
} else {
ASSERT(!isGlobalProxy);
ASSERT(slot.type() == PutPropertySlot::NewProperty);
if (!oldStructure->isObject())
return GiveUpOnCache;
// Right now, we disable IC for put onto prototype for NewProperty case.
if (oldStructure->mayBePrototype())
return GiveUpOnCache;
// If the old structure is dictionary, it means that this is one-on-one between an object and a structure.
// If this is NewProperty operation, generating IC for this does not offer any benefit because this transition never happens again.
if (oldStructure->isDictionary())
return RetryCacheLater;
PropertyOffset offset;
Structure* newStructure = Structure::addPropertyTransitionToExistingStructureConcurrently(oldStructure, ident.impl(), static_cast<unsigned>(PropertyAttribute::None), offset);
if (!newStructure || !newStructure->propertyAccessesAreCacheable())
return GiveUpOnCache;
// If JSObject::put is overridden by UserObject, UserObject::put performs side-effect on JSObject::put, and it neglects to mark the PutPropertySlot as non-cachaeble,
// then arbitrary structure transitions can happen during the put operation, and this generates wrong transition information here as if oldStructure -> newStructure.
// In reality, the transition is oldStructure -> something unknown structures -> baseValue's structure.
// To guard against the embedder's potentially incorrect UserObject::put implementation, we should check for this condition and if found, and give up on caching the put.
ASSERT(baseValue.asCell()->structure() == newStructure);
if (baseValue.asCell()->structure() != newStructure)
return GiveUpOnCache;
ASSERT(newStructure->previousID() == oldStructure);
ASSERT(!newStructure->isDictionary());
ASSERT(newStructure->isObject());
RefPtr<PolyProtoAccessChain> prototypeAccessChain;
ObjectPropertyConditionSet conditionSet;
switch (putByKind) {
case PutByKind::ByIdStrict:
case PutByKind::ByIdSloppy:
case PutByKind::ByValStrict:
case PutByKind::ByValSloppy: {
auto cacheStatus = prepareChainForCaching(globalObject, baseCell, propertyName.uid(), nullptr);
if (!cacheStatus)
return GiveUpOnCache;
if (cacheStatus->usesPolyProto) {
prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, baseCell, propertyName, nullptr);
if (!prototypeAccessChain)
return GiveUpOnCache;
} else {
prototypeAccessChain = nullptr;
conditionSet = generateConditionsForPropertySetterMiss(
vm, codeBlock, globalObject, newStructure, ident.impl());
if (!conditionSet.isValid())
return GiveUpOnCache;
}
break;
}
case PutByKind::DefinePrivateNameById:
case PutByKind::DefinePrivateNameByVal:
ASSERT(ident.isPrivateName());
break;
case PutByKind::ByIdDirectStrict:
case PutByKind::ByIdDirectSloppy:
case PutByKind::ByValDirectStrict:
case PutByKind::ByValDirectSloppy:
case PutByKind::SetPrivateNameById:
case PutByKind::SetPrivateNameByVal:
break;
}
newCase = AccessCase::createTransition(vm, codeBlock, propertyName, offset, oldStructure, newStructure, conditionSet, WTFMove(prototypeAccessChain), stubInfo);
}
} else if (slot.isCacheableCustom() || slot.isCacheableSetter()) {
if (slot.isCacheableCustom()) {
ObjectPropertyConditionSet conditionSet;
RefPtr<PolyProtoAccessChain> prototypeAccessChain;
// We need to do this even if we're a self custom, since we must disallow dictionaries
// because we need to be informed if the custom goes away since we cache the constant
// function pointer.
auto cacheStatus = prepareChainForCaching(globalObject, baseCell, propertyName.uid(), slot.base());
if (!cacheStatus)
return GiveUpOnCache;
if (slot.base() != baseValue) {
if (cacheStatus->usesPolyProto) {
prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, baseCell, propertyName, slot.base());
if (!prototypeAccessChain)
return GiveUpOnCache;
} else {
prototypeAccessChain = nullptr;
conditionSet = generateConditionsForPrototypePropertyHitCustom(
vm, codeBlock, globalObject, oldStructure, slot.base(), ident.impl(), static_cast<unsigned>(PropertyAttribute::None));
if (!conditionSet.isValid())
return GiveUpOnCache;
}
}
newCase = GetterSetterAccessCase::create(
vm, codeBlock, slot.isCustomAccessor() ? AccessCase::CustomAccessorSetter : AccessCase::CustomValueSetter, oldStructure, propertyName,
invalidOffset, conditionSet, WTFMove(prototypeAccessChain), isGlobalProxy, slot.customSetter(), slot.base() != baseValue ? slot.base() : nullptr);
} else {
ASSERT(slot.isCacheableSetter());
ObjectPropertyConditionSet conditionSet;
RefPtr<PolyProtoAccessChain> prototypeAccessChain;
PropertyOffset offset = slot.cachedOffset();
if (slot.base() != baseValue) {
auto cacheStatus = prepareChainForCaching(globalObject, baseCell, propertyName.uid(), slot.base());
if (!cacheStatus)
return GiveUpOnCache;
if (cacheStatus->flattenedDictionary)
return RetryCacheLater;
if (cacheStatus->usesPolyProto) {
prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, baseCell, propertyName, slot.base());
if (!prototypeAccessChain)
return GiveUpOnCache;
unsigned attributes;
offset = prototypeAccessChain->slotBaseStructure(vm, baseCell->structure())->get(vm, ident.impl(), attributes);
if (!isValidOffset(offset) || !(attributes & PropertyAttribute::Accessor))
return RetryCacheLater;
} else {
prototypeAccessChain = nullptr;
conditionSet = generateConditionsForPrototypePropertyHit(
vm, codeBlock, globalObject, oldStructure, slot.base(), ident.impl());
if (!conditionSet.isValid())
return GiveUpOnCache;
if (!(conditionSet.slotBaseCondition().attributes() & PropertyAttribute::Accessor))
return GiveUpOnCache;
offset = conditionSet.slotBaseCondition().offset();
}
}
newCase = GetterSetterAccessCase::create(
vm, codeBlock, AccessCase::Setter, oldStructure, propertyName, offset, conditionSet, WTFMove(prototypeAccessChain), isGlobalProxy);
}
} else if (!propertyName.isPrivateName() && isProxyObject) {
switch (putByKind) {
case PutByKind::ByIdStrict:
case PutByKind::ByIdSloppy: {
propertyName.ensureIsCell(vm);
newCase = AccessCase::create(vm, codeBlock, AccessCase::ProxyObjectStore, propertyName);
break;
}
case PutByKind::ByValStrict:
case PutByKind::ByValSloppy: {
newCase = AccessCase::create(vm, codeBlock, AccessCase::IndexedProxyObjectStore, nullptr);
break;
}
case PutByKind::DefinePrivateNameById:
case PutByKind::DefinePrivateNameByVal:
case PutByKind::SetPrivateNameById:
case PutByKind::SetPrivateNameByVal:
RELEASE_ASSERT_NOT_REACHED();
case PutByKind::ByIdDirectStrict:
case PutByKind::ByIdDirectSloppy:
case PutByKind::ByValDirectStrict:
case PutByKind::ByValDirectSloppy:
return GiveUpOnCache;
}
}
LOG_IC((vm, ICEvent::PutByAddAccessCase, oldStructure->classInfoForCells(), ident, slot.base() == baseValue));
result = stubInfo.addAccessCase(locker, globalObject, codeBlock, slot.isStrictMode() ? ECMAMode::strict() : ECMAMode::sloppy(), propertyName, WTFMove(newCase));
if (result.generatedSomeCode())
LOG_IC((vm, ICEvent::PutByReplaceWithJump, oldStructure->classInfoForCells(), ident, slot.base() == baseValue));
}
fireWatchpointsAndClearStubIfNeeded(vm, stubInfo, codeBlock, result);
if (result.generatedMegamorphicCode())
return PromoteToMegamorphic;
return result.shouldGiveUpNow() ? GiveUpOnCache : RetryCacheLater;
}
void repatchPutBy(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue baseValue, Structure* oldStructure, CacheableIdentifier propertyName, const PutPropertySlot& slot, StructureStubInfo& stubInfo, PutByKind putByKind)
{
SuperSamplerScope superSamplerScope(false);
switch (tryCachePutBy(globalObject, codeBlock, baseValue, oldStructure, propertyName, slot, stubInfo, putByKind)) {
case PromoteToMegamorphic: {
switch (putByKind) {
case PutByKind::ByIdStrict:
repatchSlowPathCall(codeBlock, stubInfo, operationPutByIdStrictMegamorphic);
break;
case PutByKind::ByIdSloppy:
repatchSlowPathCall(codeBlock, stubInfo, operationPutByIdSloppyMegamorphic);
break;
case PutByKind::ByValStrict:
repatchSlowPathCall(codeBlock, stubInfo, operationPutByValStrictMegamorphic);
break;
case PutByKind::ByValSloppy:
repatchSlowPathCall(codeBlock, stubInfo, operationPutByValSloppyMegamorphic);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
case GiveUpOnCache:
repatchSlowPathCall(codeBlock, stubInfo, appropriatePutByGaveUpFunction(putByKind));
break;
case RetryCacheLater:
case AttemptToCache:
break;
}
}
static InlineCacheAction tryCacheArrayPutByVal(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue baseValue, JSValue index, StructureStubInfo& stubInfo, PutByKind putByKind)
{
if (!baseValue.isCell())
return GiveUpOnCache;
if (!index.isInt32())
return RetryCacheLater;
VM& vm = globalObject->vm();
AccessGenerationResult result;
{
GCSafeConcurrentJSLocker locker(codeBlock->m_lock, globalObject->vm());
JSCell* base = baseValue.asCell();
RefPtr<AccessCase> newCase;
AccessCase::AccessType accessType = AccessCase::IndexedInt32Store;
if (base->type() == ProxyObjectType) {
switch (putByKind) {
case PutByKind::ByValStrict:
case PutByKind::ByValSloppy:
accessType = AccessCase::IndexedProxyObjectStore;
break;
default:
return RetryCacheLater;
}
} else if (isTypedView(base->type())) {
auto* typedArray = jsCast<JSArrayBufferView*>(base);
#if USE(JSVALUE32_64)
if (typedArray->isResizableOrGrowableShared())
return GiveUpOnCache;
#endif
switch (typedArray->type()) {
case Int8ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayInt8Store : AccessCase::IndexedTypedArrayInt8Store;
break;
case Uint8ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayUint8Store : AccessCase::IndexedTypedArrayUint8Store;
break;
case Uint8ClampedArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayUint8ClampedStore : AccessCase::IndexedTypedArrayUint8ClampedStore;
break;
case Int16ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayInt16Store : AccessCase::IndexedTypedArrayInt16Store;
break;
case Uint16ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayUint16Store : AccessCase::IndexedTypedArrayUint16Store;
break;
case Int32ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayInt32Store : AccessCase::IndexedTypedArrayInt32Store;
break;
case Uint32ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayUint32Store : AccessCase::IndexedTypedArrayUint32Store;
break;
case Float16ArrayType:
if (!CCallHelpers::supportsFloat16())
return GiveUpOnCache;
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayFloat16Store : AccessCase::IndexedTypedArrayFloat16Store;
break;
case Float32ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayFloat32Store : AccessCase::IndexedTypedArrayFloat32Store;
break;
case Float64ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayFloat64Store : AccessCase::IndexedTypedArrayFloat64Store;
break;
// FIXME: Optimize BigInt64Array / BigUint64Array in IC
// https://bugs.webkit.org/show_bug.cgi?id=221183
case BigInt64ArrayType:
case BigUint64ArrayType:
return GiveUpOnCache;
default:
RELEASE_ASSERT_NOT_REACHED();
}
} else {
IndexingType indexingShape = base->indexingType() & IndexingShapeMask;
switch (indexingShape) {
case Int32Shape:
accessType = AccessCase::IndexedInt32Store;
break;
case DoubleShape:
ASSERT(Options::allowDoubleShape());
accessType = AccessCase::IndexedDoubleStore;
break;
case ContiguousShape:
accessType = AccessCase::IndexedContiguousStore;
break;
case ArrayStorageShape:
accessType = AccessCase::IndexedArrayStorageStore;
break;
default:
return GiveUpOnCache;
}
}
if (!newCase)
newCase = AccessCase::create(vm, codeBlock, accessType, nullptr);
result = stubInfo.addAccessCase(locker, globalObject, codeBlock, ecmaModeFor(putByKind), nullptr, WTFMove(newCase));
if (result.generatedSomeCode())
LOG_IC((vm, ICEvent::PutByReplaceWithJump, baseValue.classInfoOrNull(), Identifier()));
}
fireWatchpointsAndClearStubIfNeeded(vm, stubInfo, codeBlock, result);
if (result.generatedMegamorphicCode())
return PromoteToMegamorphic;
return result.shouldGiveUpNow() ? GiveUpOnCache : RetryCacheLater;
}
void repatchArrayPutByVal(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue base, JSValue index, StructureStubInfo& stubInfo, PutByKind putByKind)
{
switch (tryCacheArrayPutByVal(globalObject, codeBlock, base, index, stubInfo, putByKind)) {
case PromoteToMegamorphic: {
switch (putByKind) {
case PutByKind::ByIdStrict:
repatchSlowPathCall(codeBlock, stubInfo, operationPutByIdStrictMegamorphic);
break;
case PutByKind::ByIdSloppy:
repatchSlowPathCall(codeBlock, stubInfo, operationPutByIdSloppyMegamorphic);
break;
case PutByKind::ByValStrict:
repatchSlowPathCall(codeBlock, stubInfo, operationPutByValStrictMegamorphic);
break;
case PutByKind::ByValSloppy:
repatchSlowPathCall(codeBlock, stubInfo, operationPutByValSloppyMegamorphic);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
case GiveUpOnCache:
repatchSlowPathCall(codeBlock, stubInfo, appropriatePutByGaveUpFunction(putByKind));
break;
case RetryCacheLater:
case AttemptToCache:
break;
}
}
static InlineCacheAction tryCacheDeleteBy(JSGlobalObject* globalObject, CodeBlock* codeBlock, DeletePropertySlot& slot, JSValue baseValue, Structure* oldStructure, CacheableIdentifier propertyName, StructureStubInfo& stubInfo, DelByKind, ECMAMode ecmaMode)
{
VM& vm = globalObject->vm();
AccessGenerationResult result;
{
GCSafeConcurrentJSLocker locker(codeBlock->m_lock, globalObject->vm());
if (forceICFailure(globalObject))
return GiveUpOnCache;
ASSERT(oldStructure);
if (!baseValue.isObject() || !oldStructure->propertyAccessesAreCacheable() || oldStructure->isProxy())
return GiveUpOnCache;
if (!slot.isCacheableDelete())
return GiveUpOnCache;
if (baseValue.asCell()->structure()->isDictionary()) {
if (baseValue.asCell()->structure()->hasBeenFlattenedBefore())
return GiveUpOnCache;
jsCast<JSObject*>(baseValue)->flattenDictionaryObject(vm);
return RetryCacheLater;
}
if (oldStructure->isDictionary())
return RetryCacheLater;
RefPtr<AccessCase> newCase;
if (slot.isDeleteHit()) {
PropertyOffset newOffset = invalidOffset;
Structure* newStructure = Structure::removePropertyTransitionFromExistingStructureConcurrently(oldStructure, propertyName.uid(), newOffset);
if (!newStructure)
return RetryCacheLater;
if (!newStructure->propertyAccessesAreCacheable() || newStructure->isDictionary())
return GiveUpOnCache;
// Right now, we disable IC for put onto prototype.
if (oldStructure->mayBePrototype())
return GiveUpOnCache;
ASSERT(newOffset == slot.cachedOffset());
ASSERT(newStructure->previousID() == oldStructure);
ASSERT(newStructure->transitionKind() == TransitionKind::PropertyDeletion);
ASSERT(newStructure->isObject());
ASSERT(isValidOffset(newOffset));
newCase = AccessCase::createDelete(vm, codeBlock, propertyName, newOffset, oldStructure, newStructure);
} else if (slot.isNonconfigurable()) {
if (ecmaMode.isStrict())
return GiveUpOnCache;
// Right now, we disable IC for put onto prototype.
if (oldStructure->mayBePrototype())
return GiveUpOnCache;
newCase = AccessCase::create(vm, codeBlock, AccessCase::DeleteNonConfigurable, propertyName, invalidOffset, oldStructure, { }, nullptr);
} else
newCase = AccessCase::create(vm, codeBlock, AccessCase::DeleteMiss, propertyName, invalidOffset, oldStructure, { }, nullptr);
result = stubInfo.addAccessCase(locker, globalObject, codeBlock, ecmaMode, propertyName, WTFMove(newCase));
if (result.generatedSomeCode())
LOG_IC((vm, ICEvent::DelByReplaceWithJump, oldStructure->classInfoForCells(), Identifier::fromUid(vm, propertyName.uid())));
}
fireWatchpointsAndClearStubIfNeeded(vm, stubInfo, codeBlock, result);
return result.shouldGiveUpNow() ? GiveUpOnCache : RetryCacheLater;
}
void repatchDeleteBy(JSGlobalObject* globalObject, CodeBlock* codeBlock, DeletePropertySlot& slot, JSValue baseValue, Structure* oldStructure, CacheableIdentifier propertyName, StructureStubInfo& stubInfo, DelByKind kind, ECMAMode ecmaMode)
{
SuperSamplerScope superSamplerScope(false);
if (tryCacheDeleteBy(globalObject, codeBlock, slot, baseValue, oldStructure, propertyName, stubInfo, kind, ecmaMode) == GiveUpOnCache) {
LOG_IC((globalObject->vm(), ICEvent::DelByReplaceWithGeneric, baseValue.classInfoOrNull(), Identifier::fromUid(globalObject->vm(), propertyName.uid())));
switch (kind) {
case DelByKind::ByIdStrict:
repatchSlowPathCall(codeBlock, stubInfo, operationDeleteByIdStrictGaveUp);
break;
case DelByKind::ByIdSloppy:
repatchSlowPathCall(codeBlock, stubInfo, operationDeleteByIdSloppyGaveUp);
break;
case DelByKind::ByValStrict:
repatchSlowPathCall(codeBlock, stubInfo, operationDeleteByValStrictGaveUp);
break;
case DelByKind::ByValSloppy:
repatchSlowPathCall(codeBlock, stubInfo, operationDeleteByValSloppyGaveUp);
break;
}
}
}
inline CodePtr<CFunctionPtrTag> appropriateInByOptimizeFunction(InByKind kind)
{
switch (kind) {
case InByKind::ById:
return operationInByIdOptimize;
case InByKind::ByVal:
return operationInByValOptimize;
case InByKind::PrivateName:
return operationHasPrivateNameOptimize;
}
RELEASE_ASSERT_NOT_REACHED();
}
inline CodePtr<CFunctionPtrTag> appropriateInByGaveUpFunction(InByKind kind)
{
switch (kind) {
case InByKind::ById:
return operationInByIdGaveUp;
case InByKind::ByVal:
return operationInByValGaveUp;
case InByKind::PrivateName:
return operationHasPrivateNameGaveUp;
}
RELEASE_ASSERT_NOT_REACHED();
}
// Mainly used to transition from megamorphic case to generic case.
void repatchInBySlowPathCall(CodeBlock* codeBlock, StructureStubInfo& stubInfo, InByKind kind)
{
resetInBy(codeBlock, stubInfo, kind);
repatchSlowPathCall(codeBlock, stubInfo, appropriateInByGaveUpFunction(kind));
}
static InlineCacheAction tryCacheInBy(
JSGlobalObject* globalObject, CodeBlock* codeBlock, JSObject* base, CacheableIdentifier propertyName,
bool wasFound, const PropertySlot& slot, StructureStubInfo& stubInfo, InByKind kind)
{
VM& vm = globalObject->vm();
AccessGenerationResult result;
Identifier ident = Identifier::fromUid(vm, propertyName.uid());
{
GCSafeConcurrentJSLocker locker(codeBlock->m_lock, vm);
if (forceICFailure(globalObject))
return GiveUpOnCache;
RefPtr<AccessCase> newCase;
Structure* structure = base->structure();
RefPtr<PolyProtoAccessChain> prototypeAccessChain;
ObjectPropertyConditionSet conditionSet;
if ((kind == InByKind::ById || kind == InByKind::ByVal) && !propertyName.isPrivateName() && base->inherits<ProxyObject>()) {
switch (kind) {
case InByKind::ById: {
propertyName.ensureIsCell(vm);
newCase = AccessCase::create(vm, codeBlock, AccessCase::ProxyObjectIn, propertyName);
break;
}
case InByKind::ByVal: {
newCase = AccessCase::create(vm, codeBlock, AccessCase::IndexedProxyObjectIn, nullptr);
break;
}
default:
RELEASE_ASSERT_NOT_REACHED();
}
} else if (wasFound) {
if (!structure->propertyAccessesAreCacheable())
return GiveUpOnCache;
if (!slot.isCacheable())
return GiveUpOnCache;
InlineCacheAction action = actionForCell(vm, base);
if (action != AttemptToCache)
return action;
// Optimize self access.
if (stubInfo.cacheType() == CacheType::Unset
&& slot.isCacheableValue()
&& slot.slotBase() == base
&& !slot.watchpointSet()
&& !structure->needImpurePropertyWatchpoint()) {
bool generatedCodeInline = InlineAccess::generateSelfInAccess(stubInfo, structure);
if (generatedCodeInline) {
LOG_IC((vm, ICEvent::InBySelfPatch, structure->classInfoForCells(), ident, slot.slotBase() == base));
structure->startWatchingPropertyForReplacements(vm, slot.cachedOffset());
repatchSlowPathCall(codeBlock, stubInfo, operationInByIdOptimize);
stubInfo.initInByIdSelf(locker, codeBlock, structure, slot.cachedOffset());
return RetryCacheLater;
}
}
if (slot.slotBase() != base) {
auto cacheStatus = prepareChainForCaching(globalObject, base, propertyName.uid(), slot);
if (!cacheStatus)
return GiveUpOnCache;
if (cacheStatus->flattenedDictionary)
return RetryCacheLater;
if (cacheStatus->usesPolyProto) {
prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, base, propertyName, slot);
if (!prototypeAccessChain)
return GiveUpOnCache;
ASSERT(slot.isCacheableCustom() || prototypeAccessChain->slotBaseStructure(vm, structure)->get(vm, propertyName.uid()) == slot.cachedOffset());
} else {
prototypeAccessChain = nullptr;
conditionSet = generateConditionsForPrototypePropertyHit(
vm, codeBlock, globalObject, structure, slot.slotBase(), ident.impl());
if (!conditionSet.isValid())
return GiveUpOnCache;
ASSERT(slot.isCacheableCustom() || conditionSet.slotBaseCondition().offset() == slot.cachedOffset());
}
}
} else {
if (!structure->propertyAccessesAreCacheable() || !structure->propertyAccessesAreCacheableForAbsence())
return GiveUpOnCache;
auto cacheStatus = prepareChainForCaching(globalObject, base, propertyName.uid(), nullptr);
if (!cacheStatus)
return GiveUpOnCache;
if (cacheStatus->usesPolyProto) {
prototypeAccessChain = PolyProtoAccessChain::tryCreate(globalObject, base, propertyName, slot);
if (!prototypeAccessChain)
return GiveUpOnCache;
} else {
prototypeAccessChain = nullptr;
conditionSet = generateConditionsForPropertyMiss(
vm, codeBlock, globalObject, structure, ident.impl());
if (!conditionSet.isValid())
return GiveUpOnCache;
}
}
LOG_IC((vm, ICEvent::InAddAccessCase, structure->classInfoForCells(), ident, slot.slotBase() == base));
if (!newCase)
newCase = AccessCase::create(vm, codeBlock, wasFound ? AccessCase::InHit : AccessCase::InMiss, propertyName, wasFound ? slot.cachedOffset() : invalidOffset, structure, conditionSet, WTFMove(prototypeAccessChain));
result = stubInfo.addAccessCase(locker, globalObject, codeBlock, ECMAMode::strict(), propertyName, WTFMove(newCase));
if (result.generatedSomeCode())
LOG_IC((vm, ICEvent::InReplaceWithJump, structure->classInfoForCells(), ident, slot.slotBase() == base));
}
fireWatchpointsAndClearStubIfNeeded(vm, stubInfo, codeBlock, result);
if (result.generatedMegamorphicCode())
return PromoteToMegamorphic;
return result.shouldGiveUpNow() ? GiveUpOnCache : RetryCacheLater;
}
void repatchInBy(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSObject* baseObject, CacheableIdentifier propertyName, bool wasFound, const PropertySlot& slot, StructureStubInfo& stubInfo, InByKind kind)
{
SuperSamplerScope superSamplerScope(false);
switch (tryCacheInBy(globalObject, codeBlock, baseObject, propertyName, wasFound, slot, stubInfo, kind)) {
case PromoteToMegamorphic: {
switch (kind) {
case InByKind::ById:
repatchSlowPathCall(codeBlock, stubInfo, operationInByIdMegamorphic);
break;
case InByKind::ByVal:
repatchSlowPathCall(codeBlock, stubInfo, operationInByValMegamorphic);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
case GiveUpOnCache:
LOG_IC((globalObject->vm(), ICEvent::InReplaceWithGeneric, baseObject->classInfo(), Identifier::fromUid(globalObject->vm(), propertyName.uid())));
repatchSlowPathCall(codeBlock, stubInfo, appropriateInByGaveUpFunction(kind));
break;
case RetryCacheLater:
case AttemptToCache:
break;
}
}
static InlineCacheAction tryCacheHasPrivateBrand(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSObject* base, CacheableIdentifier brandID, bool wasFound, StructureStubInfo& stubInfo)
{
VM& vm = globalObject->vm();
AccessGenerationResult result;
Identifier ident = Identifier::fromUid(vm, brandID.uid());
{
GCSafeConcurrentJSLocker locker(codeBlock->m_lock, vm);
if (forceICFailure(globalObject))
return GiveUpOnCache;
Structure* structure = base->structure();
InlineCacheAction action = actionForCell(vm, base);
if (action != AttemptToCache)
return action;
bool isBaseProperty = true;
LOG_IC((vm, ICEvent::InAddAccessCase, structure->classInfoForCells(), ident, isBaseProperty));
Ref<AccessCase> newCase = AccessCase::create(vm, codeBlock, wasFound ? AccessCase::InHit : AccessCase::InMiss, brandID, invalidOffset, structure, { }, { });
result = stubInfo.addAccessCase(locker, globalObject, codeBlock, ECMAMode::strict(), brandID, WTFMove(newCase));
if (result.generatedSomeCode())
LOG_IC((vm, ICEvent::InReplaceWithJump, structure->classInfoForCells(), ident, isBaseProperty));
}
fireWatchpointsAndClearStubIfNeeded(vm, stubInfo, codeBlock, result);
return result.shouldGiveUpNow() ? GiveUpOnCache : RetryCacheLater;
}
void repatchHasPrivateBrand(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSObject* baseObject, CacheableIdentifier brandID, bool wasFound, StructureStubInfo& stubInfo)
{
SuperSamplerScope superSamplerScope(false);
if (tryCacheHasPrivateBrand(globalObject, codeBlock, baseObject, brandID, wasFound, stubInfo) == GiveUpOnCache)
repatchSlowPathCall(codeBlock, stubInfo, operationHasPrivateBrandGaveUp);
}
static InlineCacheAction tryCacheCheckPrivateBrand(
JSGlobalObject* globalObject, CodeBlock* codeBlock, JSObject* base, CacheableIdentifier brandID,
StructureStubInfo& stubInfo)
{
VM& vm = globalObject->vm();
AccessGenerationResult result;
Identifier ident = Identifier::fromUid(vm, brandID.uid());
{
GCSafeConcurrentJSLocker locker(codeBlock->m_lock, vm);
if (forceICFailure(globalObject))
return GiveUpOnCache;
Structure* structure = base->structure();
InlineCacheAction action = actionForCell(vm, base);
if (action != AttemptToCache)
return action;
bool isBaseProperty = true;
LOG_IC((vm, ICEvent::CheckPrivateBrandAddAccessCase, structure->classInfoForCells(), ident, isBaseProperty));
Ref<AccessCase> newCase = AccessCase::createCheckPrivateBrand(vm, codeBlock, brandID, structure);
result = stubInfo.addAccessCase(locker, globalObject, codeBlock, ECMAMode::strict(), brandID, WTFMove(newCase));
if (result.generatedSomeCode())
LOG_IC((vm, ICEvent::CheckPrivateBrandReplaceWithJump, structure->classInfoForCells(), ident, isBaseProperty));
}
fireWatchpointsAndClearStubIfNeeded(vm, stubInfo, codeBlock, result);
return result.shouldGiveUpNow() ? GiveUpOnCache : RetryCacheLater;
}
void repatchCheckPrivateBrand(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSObject* baseObject, CacheableIdentifier brandID, StructureStubInfo& stubInfo)
{
SuperSamplerScope superSamplerScope(false);
if (tryCacheCheckPrivateBrand(globalObject, codeBlock, baseObject, brandID, stubInfo) == GiveUpOnCache)
repatchSlowPathCall(codeBlock, stubInfo, operationCheckPrivateBrandGaveUp);
}
static InlineCacheAction tryCacheSetPrivateBrand(
JSGlobalObject* globalObject, CodeBlock* codeBlock, JSObject* base, Structure* oldStructure, CacheableIdentifier brandID,
StructureStubInfo& stubInfo)
{
VM& vm = globalObject->vm();
AccessGenerationResult result;
Identifier ident = Identifier::fromUid(vm, brandID.uid());
{
GCSafeConcurrentJSLocker locker(codeBlock->m_lock, vm);
if (forceICFailure(globalObject))
return GiveUpOnCache;
ASSERT(oldStructure);
if (oldStructure->isDictionary())
return RetryCacheLater;
InlineCacheAction action = actionForCell(vm, base);
if (action != AttemptToCache)
return action;
Structure* newStructure = Structure::setBrandTransitionFromExistingStructureConcurrently(oldStructure, brandID.uid());
if (!newStructure)
return RetryCacheLater;
if (newStructure->isDictionary())
return GiveUpOnCache;
ASSERT(newStructure->previousID() == oldStructure);
ASSERT(newStructure->transitionKind() == TransitionKind::SetBrand);
ASSERT(newStructure->isObject());
bool isBaseProperty = true;
LOG_IC((vm, ICEvent::SetPrivateBrandAddAccessCase, oldStructure->classInfoForCells(), ident, isBaseProperty));
Ref<AccessCase> newCase = AccessCase::createSetPrivateBrand(vm, codeBlock, brandID, oldStructure, newStructure);
result = stubInfo.addAccessCase(locker, globalObject, codeBlock, ECMAMode::strict(), brandID, WTFMove(newCase));
if (result.generatedSomeCode())
LOG_IC((vm, ICEvent::SetPrivateBrandReplaceWithJump, oldStructure->classInfoForCells(), ident, isBaseProperty));
}
fireWatchpointsAndClearStubIfNeeded(vm, stubInfo, codeBlock, result);
return result.shouldGiveUpNow() ? GiveUpOnCache : RetryCacheLater;
}
void repatchSetPrivateBrand(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSObject* baseObject, Structure* oldStructure, CacheableIdentifier brandID, StructureStubInfo& stubInfo)
{
SuperSamplerScope superSamplerScope(false);
if (tryCacheSetPrivateBrand(globalObject, codeBlock, baseObject, oldStructure, brandID, stubInfo) == GiveUpOnCache)
repatchSlowPathCall(codeBlock, stubInfo, operationSetPrivateBrandGaveUp);
}
static InlineCacheAction tryCacheInstanceOf(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue valueValue, JSValue prototypeValue, StructureStubInfo& stubInfo, bool wasFound)
{
VM& vm = globalObject->vm();
AccessGenerationResult result;
RELEASE_ASSERT(valueValue.isCell()); // shouldConsiderCaching rejects non-cells.
if (forceICFailure(globalObject))
return GiveUpOnCache;
{
GCSafeConcurrentJSLocker locker(codeBlock->m_lock, vm);
JSCell* value = valueValue.asCell();
Structure* structure = value->structure();
RefPtr<AccessCase> newCase;
JSObject* prototype = jsDynamicCast<JSObject*>(prototypeValue);
if (prototype) {
if (!jsDynamicCast<JSObject*>(value)) {
newCase = InstanceOfAccessCase::create(
vm, codeBlock, AccessCase::InstanceOfMiss, structure, ObjectPropertyConditionSet(),
prototype);
} else if (structure->prototypeQueriesAreCacheable()) {
// FIXME: Teach this to do poly proto.
// https://bugs.webkit.org/show_bug.cgi?id=185663
prepareChainForCaching(globalObject, value, nullptr, wasFound ? prototype : nullptr);
ObjectPropertyConditionSet conditionSet = generateConditionsForInstanceOf(
vm, codeBlock, globalObject, structure, prototype, wasFound);
if (conditionSet.isValid()) {
newCase = InstanceOfAccessCase::create(
vm, codeBlock,
wasFound ? AccessCase::InstanceOfHit : AccessCase::InstanceOfMiss,
structure, conditionSet, prototype);
}
}
}
if (!newCase)
newCase = AccessCase::create(vm, codeBlock, AccessCase::InstanceOfMegamorphic, nullptr);
LOG_IC((vm, ICEvent::InstanceOfAddAccessCase, structure->classInfoForCells(), Identifier()));
result = stubInfo.addAccessCase(locker, globalObject, codeBlock, ECMAMode::strict(), nullptr, WTFMove(newCase));
if (result.generatedSomeCode())
LOG_IC((vm, ICEvent::InstanceOfReplaceWithJump, structure->classInfoForCells(), Identifier()));
}
fireWatchpointsAndClearStubIfNeeded(vm, stubInfo, codeBlock, result);
if (result.generatedMegamorphicCode())
return GiveUpOnCache; // In this case, we give up since we do not cache the results in the megamorphic table.
return result.shouldGiveUpNow() ? GiveUpOnCache : RetryCacheLater;
}
static InlineCacheAction tryCacheArrayInByVal(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue baseValue, JSValue index, StructureStubInfo& stubInfo)
{
ASSERT(baseValue.isCell());
if (!index.isInt32())
return RetryCacheLater;
VM& vm = globalObject->vm();
AccessGenerationResult result;
{
GCSafeConcurrentJSLocker locker(codeBlock->m_lock, globalObject->vm());
JSCell* base = baseValue.asCell();
RefPtr<AccessCase> newCase;
AccessCase::AccessType accessType = AccessCase::IndexedInt32InHit;
if (base->type() == DirectArgumentsType)
accessType = AccessCase::IndexedDirectArgumentsInHit;
else if (base->type() == ScopedArgumentsType)
accessType = AccessCase::IndexedScopedArgumentsInHit;
else if (base->type() == StringType)
accessType = AccessCase::IndexedStringInHit;
else if (base->type() == ProxyObjectType)
accessType = AccessCase::IndexedProxyObjectIn;
else if (isTypedView(base->type())) {
auto* typedArray = jsCast<JSArrayBufferView*>(base);
#if USE(JSVALUE32_64)
if (typedArray->isResizableOrGrowableShared())
return GiveUpOnCache;
#endif
switch (typedArray->type()) {
case Int8ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayInt8In : AccessCase::IndexedTypedArrayInt8In;
break;
case Uint8ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayUint8In : AccessCase::IndexedTypedArrayUint8In;
break;
case Uint8ClampedArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayUint8ClampedIn : AccessCase::IndexedTypedArrayUint8ClampedIn;
break;
case Int16ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayInt16In : AccessCase::IndexedTypedArrayInt16In;
break;
case Uint16ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayUint16In : AccessCase::IndexedTypedArrayUint16In;
break;
case Int32ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayInt32In : AccessCase::IndexedTypedArrayInt32In;
break;
case Uint32ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayUint32In : AccessCase::IndexedTypedArrayUint32In;
break;
case Float16ArrayType:
if (!CCallHelpers::supportsFloat16())
return GiveUpOnCache;
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayFloat16In : AccessCase::IndexedTypedArrayFloat16In;
break;
case Float32ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayFloat32In : AccessCase::IndexedTypedArrayFloat32In;
break;
case Float64ArrayType:
accessType = typedArray->isResizableOrGrowableShared() ? AccessCase::IndexedResizableTypedArrayFloat64In : AccessCase::IndexedTypedArrayFloat64In;
break;
// FIXME: Optimize BigInt64Array / BigUint64Array in IC
// https://bugs.webkit.org/show_bug.cgi?id=221183
case BigInt64ArrayType:
case BigUint64ArrayType:
return GiveUpOnCache;
default:
RELEASE_ASSERT_NOT_REACHED();
}
} else {
IndexingType indexingShape = base->indexingType() & IndexingShapeMask;
switch (indexingShape) {
case Int32Shape:
accessType = AccessCase::IndexedInt32InHit;
break;
case DoubleShape:
ASSERT(Options::allowDoubleShape());
accessType = AccessCase::IndexedDoubleInHit;
break;
case ContiguousShape:
accessType = AccessCase::IndexedContiguousInHit;
break;
case ArrayStorageShape:
accessType = AccessCase::IndexedArrayStorageInHit;
break;
case NoIndexingShape: {
if (!base->isObject())
return GiveUpOnCache;
if (base->structure()->mayInterceptIndexedAccesses() || base->structure()->typeInfo().interceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero())
return GiveUpOnCache;
// FIXME: prepareChainForCaching is conservative. We should have another function which only cares about information related to this IC.
auto cacheStatus = prepareChainForCaching(globalObject, base, nullptr, nullptr);
if (!cacheStatus)
return GiveUpOnCache;
if (cacheStatus->usesPolyProto)
return GiveUpOnCache;
Structure* headStructure = base->structure();
ObjectPropertyConditionSet conditionSet = generateConditionsForIndexedMiss(vm, codeBlock, globalObject, headStructure);
if (!conditionSet.isValid())
return GiveUpOnCache;
newCase = AccessCase::create(vm, codeBlock, AccessCase::IndexedNoIndexingInMiss, nullptr, invalidOffset, headStructure, conditionSet);
break;
}
default:
return GiveUpOnCache;
}
}
if (!newCase)
newCase = AccessCase::create(vm, codeBlock, accessType, nullptr);
result = stubInfo.addAccessCase(locker, globalObject, codeBlock, ECMAMode::strict(), nullptr, newCase.releaseNonNull());
}
fireWatchpointsAndClearStubIfNeeded(vm, stubInfo, codeBlock, result);
if (result.generatedMegamorphicCode())
return PromoteToMegamorphic;
return result.shouldGiveUpNow() ? GiveUpOnCache : RetryCacheLater;
}
void repatchArrayInByVal(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue base, JSValue index, StructureStubInfo& stubInfo, InByKind kind)
{
switch (tryCacheArrayInByVal(globalObject, codeBlock, base, index, stubInfo)) {
case PromoteToMegamorphic: {
switch (kind) {
case InByKind::ById:
repatchSlowPathCall(codeBlock, stubInfo, operationInByIdMegamorphic);
break;
case InByKind::ByVal:
repatchSlowPathCall(codeBlock, stubInfo, operationInByValMegamorphic);
break;
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
}
case GiveUpOnCache:
repatchSlowPathCall(codeBlock, stubInfo, appropriateInByGaveUpFunction(kind));
break;
case RetryCacheLater:
case AttemptToCache:
break;
}
}
void repatchInstanceOf(
JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue valueValue, JSValue prototypeValue, StructureStubInfo& stubInfo,
bool wasFound)
{
SuperSamplerScope superSamplerScope(false);
if (tryCacheInstanceOf(globalObject, codeBlock, valueValue, prototypeValue, stubInfo, wasFound) == GiveUpOnCache)
repatchSlowPathCall(codeBlock, stubInfo, operationInstanceOfGaveUp);
}
void linkDirectCall(DirectCallLinkInfo& callLinkInfo, CodeBlock* calleeCodeBlock, CodePtr<JSEntryPtrTag> codePtr)
{
// DirectCall is only used from DFG / FTL.
callLinkInfo.setCallTarget(jsCast<FunctionCodeBlock*>(calleeCodeBlock), CodeLocationLabel<JSEntryPtrTag>(codePtr));
if (calleeCodeBlock)
calleeCodeBlock->linkIncomingCall(callLinkInfo.owner(), &callLinkInfo);
}
void resetGetBy(CodeBlock* codeBlock, StructureStubInfo& stubInfo, GetByKind kind)
{
repatchSlowPathCall(codeBlock, stubInfo, appropriateGetByOptimizeFunction(kind));
stubInfo.resetStubAsJumpInAccess(codeBlock);
}
void resetPutBy(CodeBlock* codeBlock, StructureStubInfo& stubInfo, PutByKind kind)
{
CodePtr<CFunctionPtrTag> optimizedFunction;
switch (kind) {
case PutByKind::ByIdStrict:
optimizedFunction = operationPutByIdStrictOptimize;
break;
case PutByKind::ByIdSloppy:
optimizedFunction = operationPutByIdSloppyOptimize;
break;
case PutByKind::ByIdDirectStrict:
optimizedFunction = operationPutByIdDirectStrictOptimize;
break;
case PutByKind::ByIdDirectSloppy:
optimizedFunction = operationPutByIdDirectSloppyOptimize;
break;
case PutByKind::DefinePrivateNameById:
optimizedFunction = operationPutByIdDefinePrivateFieldStrictOptimize;
break;
case PutByKind::SetPrivateNameById:
optimizedFunction = operationPutByIdSetPrivateFieldStrictOptimize;
break;
case PutByKind::ByValStrict:
optimizedFunction = operationPutByValStrictOptimize;
break;
case PutByKind::ByValSloppy:
optimizedFunction = operationPutByValSloppyOptimize;
break;
case PutByKind::ByValDirectStrict:
optimizedFunction = operationDirectPutByValStrictOptimize;
break;
case PutByKind::ByValDirectSloppy:
optimizedFunction = operationDirectPutByValSloppyOptimize;
break;
case PutByKind::DefinePrivateNameByVal:
optimizedFunction = operationPutByValDefinePrivateFieldOptimize;
break;
case PutByKind::SetPrivateNameByVal:
optimizedFunction = operationPutByValSetPrivateFieldOptimize;
break;
}
repatchSlowPathCall(codeBlock, stubInfo, optimizedFunction);
stubInfo.resetStubAsJumpInAccess(codeBlock);
}
void resetDelBy(CodeBlock* codeBlock, StructureStubInfo& stubInfo, DelByKind kind)
{
switch (kind) {
case DelByKind::ByIdStrict:
repatchSlowPathCall(codeBlock, stubInfo, operationDeleteByIdStrictOptimize);
break;
case DelByKind::ByIdSloppy:
repatchSlowPathCall(codeBlock, stubInfo, operationDeleteByIdSloppyOptimize);
break;
case DelByKind::ByValStrict:
repatchSlowPathCall(codeBlock, stubInfo, operationDeleteByValStrictOptimize);
break;
case DelByKind::ByValSloppy:
repatchSlowPathCall(codeBlock, stubInfo, operationDeleteByValSloppyOptimize);
break;
}
stubInfo.resetStubAsJumpInAccess(codeBlock);
}
void resetInBy(CodeBlock* codeBlock, StructureStubInfo& stubInfo, InByKind kind)
{
repatchSlowPathCall(codeBlock, stubInfo, appropriateInByOptimizeFunction(kind));
stubInfo.resetStubAsJumpInAccess(codeBlock);
}
void resetHasPrivateBrand(CodeBlock* codeBlock, StructureStubInfo& stubInfo)
{
repatchSlowPathCall(codeBlock, stubInfo, operationHasPrivateBrandOptimize);
stubInfo.resetStubAsJumpInAccess(codeBlock);
}
void resetInstanceOf(CodeBlock* codeBlock, StructureStubInfo& stubInfo)
{
repatchSlowPathCall(codeBlock, stubInfo, operationInstanceOfOptimize);
stubInfo.resetStubAsJumpInAccess(codeBlock);
}
void resetCheckPrivateBrand(CodeBlock* codeBlock, StructureStubInfo& stubInfo)
{
repatchSlowPathCall(codeBlock, stubInfo, operationCheckPrivateBrandOptimize);
stubInfo.resetStubAsJumpInAccess(codeBlock);
}
void resetSetPrivateBrand(CodeBlock* codeBlock, StructureStubInfo& stubInfo)
{
repatchSlowPathCall(codeBlock, stubInfo, operationSetPrivateBrandOptimize);
stubInfo.resetStubAsJumpInAccess(codeBlock);
}
#endif
} // namespace JSC