diff options
Diffstat (limited to 'Source/JavaScriptCore/jit/JITOperations.cpp')
-rw-r--r-- | Source/JavaScriptCore/jit/JITOperations.cpp | 2237 |
1 files changed, 2237 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/jit/JITOperations.cpp b/Source/JavaScriptCore/jit/JITOperations.cpp new file mode 100644 index 000000000..868eed755 --- /dev/null +++ b/Source/JavaScriptCore/jit/JITOperations.cpp @@ -0,0 +1,2237 @@ +/* + * Copyright (C) 2013-2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "JITOperations.h" + +#if ENABLE(JIT) + +#include "ArrayConstructor.h" +#include "CommonSlowPaths.h" +#include "DFGCompilationMode.h" +#include "DFGDriver.h" +#include "DFGOSREntry.h" +#include "DFGThunks.h" +#include "DFGWorklist.h" +#include "Debugger.h" +#include "DirectArguments.h" +#include "Error.h" +#include "ErrorHandlingScope.h" +#include "ExceptionFuzz.h" +#include "GetterSetter.h" +#include "HostCallReturnValue.h" +#include "JIT.h" +#include "JITExceptions.h" +#include "JITToDFGDeferredCompilationCallback.h" +#include "JSCInlines.h" +#include "JSGeneratorFunction.h" +#include "JSGlobalObjectFunctions.h" +#include "JSLexicalEnvironment.h" +#include "JSPropertyNameEnumerator.h" +#include "JSStackInlines.h" +#include "JSWithScope.h" +#include "LegacyProfiler.h" +#include "ObjectConstructor.h" +#include "PropertyName.h" +#include "Repatch.h" +#include "ScopedArguments.h" +#include "TestRunnerUtils.h" +#include "TypeProfilerLog.h" +#include "VMInlines.h" +#include <wtf/InlineASM.h> + +namespace JSC { + +extern "C" { + +#if COMPILER(MSVC) +void * _ReturnAddress(void); +#pragma intrinsic(_ReturnAddress) + +#define OUR_RETURN_ADDRESS _ReturnAddress() +#else +#define OUR_RETURN_ADDRESS __builtin_return_address(0) +#endif + +#if ENABLE(OPCODE_SAMPLING) +#define CTI_SAMPLER vm->interpreter->sampler() +#else +#define CTI_SAMPLER 0 +#endif + + +void JIT_OPERATION operationThrowStackOverflowError(ExecState* exec, CodeBlock* codeBlock) +{ + // We pass in our own code block, because the callframe hasn't been populated. + VM* vm = codeBlock->vm(); + + VMEntryFrame* vmEntryFrame = vm->topVMEntryFrame; + CallFrame* callerFrame = exec->callerFrame(vmEntryFrame); + if (!callerFrame) + callerFrame = exec; + + NativeCallFrameTracerWithRestore tracer(vm, vmEntryFrame, callerFrame); + throwStackOverflowError(callerFrame); +} + +#if ENABLE(WEBASSEMBLY) +void JIT_OPERATION operationThrowDivideError(ExecState* exec) +{ + VM* vm = &exec->vm(); + VMEntryFrame* vmEntryFrame = vm->topVMEntryFrame; + CallFrame* callerFrame = exec->callerFrame(vmEntryFrame); + + NativeCallFrameTracerWithRestore tracer(vm, vmEntryFrame, callerFrame); + ErrorHandlingScope errorScope(*vm); + vm->throwException(callerFrame, createError(callerFrame, ASCIILiteral("Division by zero or division overflow."))); +} + +void JIT_OPERATION operationThrowOutOfBoundsAccessError(ExecState* exec) +{ + VM* vm = &exec->vm(); + VMEntryFrame* vmEntryFrame = vm->topVMEntryFrame; + CallFrame* callerFrame = exec->callerFrame(vmEntryFrame); + + NativeCallFrameTracerWithRestore tracer(vm, vmEntryFrame, callerFrame); + ErrorHandlingScope errorScope(*vm); + vm->throwException(callerFrame, createError(callerFrame, ASCIILiteral("Out-of-bounds access."))); +} +#endif + +int32_t JIT_OPERATION operationCallArityCheck(ExecState* exec) +{ + VM* vm = &exec->vm(); + JSStack& stack = vm->interpreter->stack(); + + int32_t missingArgCount = CommonSlowPaths::arityCheckFor(exec, &stack, CodeForCall); + if (missingArgCount < 0) { + VMEntryFrame* vmEntryFrame = vm->topVMEntryFrame; + CallFrame* callerFrame = exec->callerFrame(vmEntryFrame); + NativeCallFrameTracerWithRestore tracer(vm, vmEntryFrame, callerFrame); + throwStackOverflowError(callerFrame); + } + + return missingArgCount; +} + +int32_t JIT_OPERATION operationConstructArityCheck(ExecState* exec) +{ + VM* vm = &exec->vm(); + JSStack& stack = vm->interpreter->stack(); + + int32_t missingArgCount = CommonSlowPaths::arityCheckFor(exec, &stack, CodeForConstruct); + if (missingArgCount < 0) { + VMEntryFrame* vmEntryFrame = vm->topVMEntryFrame; + CallFrame* callerFrame = exec->callerFrame(vmEntryFrame); + NativeCallFrameTracerWithRestore tracer(vm, vmEntryFrame, callerFrame); + throwStackOverflowError(callerFrame); + } + + return missingArgCount; +} + +EncodedJSValue JIT_OPERATION operationGetById(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue base, UniquedStringImpl* uid) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + stubInfo->tookSlowPath = true; + + JSValue baseValue = JSValue::decode(base); + PropertySlot slot(baseValue, PropertySlot::InternalMethodType::Get); + Identifier ident = Identifier::fromUid(vm, uid); + return JSValue::encode(baseValue.get(exec, ident, slot)); +} + +EncodedJSValue JIT_OPERATION operationGetByIdGeneric(ExecState* exec, EncodedJSValue base, UniquedStringImpl* uid) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + JSValue baseValue = JSValue::decode(base); + PropertySlot slot(baseValue, PropertySlot::InternalMethodType::Get); + Identifier ident = Identifier::fromUid(vm, uid); + return JSValue::encode(baseValue.get(exec, ident, slot)); +} + +EncodedJSValue JIT_OPERATION operationGetByIdOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue base, UniquedStringImpl* uid) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + Identifier ident = Identifier::fromUid(vm, uid); + + JSValue baseValue = JSValue::decode(base); + PropertySlot slot(baseValue, PropertySlot::InternalMethodType::Get); + + bool hasResult = baseValue.getPropertySlot(exec, ident, slot); + if (stubInfo->considerCaching()) + repatchGetByID(exec, baseValue, ident, slot, *stubInfo); + + return JSValue::encode(hasResult? slot.getValue(exec, ident) : jsUndefined()); +} + +EncodedJSValue JIT_OPERATION operationInOptimize(ExecState* exec, StructureStubInfo* stubInfo, JSCell* base, UniquedStringImpl* key) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + if (!base->isObject()) { + vm->throwException(exec, createInvalidInParameterError(exec, base)); + return JSValue::encode(jsUndefined()); + } + + AccessType accessType = static_cast<AccessType>(stubInfo->accessType); + + Identifier ident = Identifier::fromUid(vm, key); + PropertySlot slot(base, PropertySlot::InternalMethodType::HasProperty); + bool result = asObject(base)->getPropertySlot(exec, ident, slot); + + RELEASE_ASSERT(accessType == stubInfo->accessType); + + if (stubInfo->considerCaching()) + repatchIn(exec, base, ident, result, slot, *stubInfo); + + return JSValue::encode(jsBoolean(result)); +} + +EncodedJSValue JIT_OPERATION operationIn(ExecState* exec, StructureStubInfo* stubInfo, JSCell* base, UniquedStringImpl* key) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + stubInfo->tookSlowPath = true; + + if (!base->isObject()) { + vm->throwException(exec, createInvalidInParameterError(exec, base)); + return JSValue::encode(jsUndefined()); + } + + Identifier ident = Identifier::fromUid(vm, key); + return JSValue::encode(jsBoolean(asObject(base)->hasProperty(exec, ident))); +} + +EncodedJSValue JIT_OPERATION operationGenericIn(ExecState* exec, JSCell* base, EncodedJSValue key) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + return JSValue::encode(jsBoolean(CommonSlowPaths::opIn(exec, JSValue::decode(key), base))); +} + +void JIT_OPERATION operationPutByIdStrict(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + stubInfo->tookSlowPath = true; + + Identifier ident = Identifier::fromUid(vm, uid); + PutPropertySlot slot(JSValue::decode(encodedBase), true, exec->codeBlock()->putByIdContext()); + JSValue::decode(encodedBase).putInline(exec, ident, JSValue::decode(encodedValue), slot); +} + +void JIT_OPERATION operationPutByIdNonStrict(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + stubInfo->tookSlowPath = true; + + Identifier ident = Identifier::fromUid(vm, uid); + PutPropertySlot slot(JSValue::decode(encodedBase), false, exec->codeBlock()->putByIdContext()); + JSValue::decode(encodedBase).putInline(exec, ident, JSValue::decode(encodedValue), slot); +} + +void JIT_OPERATION operationPutByIdDirectStrict(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + stubInfo->tookSlowPath = true; + + Identifier ident = Identifier::fromUid(vm, uid); + PutPropertySlot slot(JSValue::decode(encodedBase), true, exec->codeBlock()->putByIdContext()); + asObject(JSValue::decode(encodedBase))->putDirect(exec->vm(), ident, JSValue::decode(encodedValue), slot); +} + +void JIT_OPERATION operationPutByIdDirectNonStrict(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + stubInfo->tookSlowPath = true; + + Identifier ident = Identifier::fromUid(vm, uid); + PutPropertySlot slot(JSValue::decode(encodedBase), false, exec->codeBlock()->putByIdContext()); + asObject(JSValue::decode(encodedBase))->putDirect(exec->vm(), ident, JSValue::decode(encodedValue), slot); +} + +void JIT_OPERATION operationPutByIdStrictOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + Identifier ident = Identifier::fromUid(vm, uid); + AccessType accessType = static_cast<AccessType>(stubInfo->accessType); + + JSValue value = JSValue::decode(encodedValue); + JSValue baseValue = JSValue::decode(encodedBase); + PutPropertySlot slot(baseValue, true, exec->codeBlock()->putByIdContext()); + + Structure* structure = baseValue.isCell() ? baseValue.asCell()->structure(*vm) : nullptr; + baseValue.putInline(exec, ident, value, slot); + + if (accessType != static_cast<AccessType>(stubInfo->accessType)) + return; + + if (stubInfo->considerCaching()) + repatchPutByID(exec, baseValue, structure, ident, slot, *stubInfo, NotDirect); +} + +void JIT_OPERATION operationPutByIdNonStrictOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + Identifier ident = Identifier::fromUid(vm, uid); + AccessType accessType = static_cast<AccessType>(stubInfo->accessType); + + JSValue value = JSValue::decode(encodedValue); + JSValue baseValue = JSValue::decode(encodedBase); + PutPropertySlot slot(baseValue, false, exec->codeBlock()->putByIdContext()); + + Structure* structure = baseValue.isCell() ? baseValue.asCell()->structure(*vm) : nullptr; + baseValue.putInline(exec, ident, value, slot); + + if (accessType != static_cast<AccessType>(stubInfo->accessType)) + return; + + if (stubInfo->considerCaching()) + repatchPutByID(exec, baseValue, structure, ident, slot, *stubInfo, NotDirect); +} + +void JIT_OPERATION operationPutByIdDirectStrictOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + Identifier ident = Identifier::fromUid(vm, uid); + AccessType accessType = static_cast<AccessType>(stubInfo->accessType); + + JSValue value = JSValue::decode(encodedValue); + JSObject* baseObject = asObject(JSValue::decode(encodedBase)); + PutPropertySlot slot(baseObject, true, exec->codeBlock()->putByIdContext()); + + Structure* structure = baseObject->structure(*vm); + baseObject->putDirect(exec->vm(), ident, value, slot); + + if (accessType != static_cast<AccessType>(stubInfo->accessType)) + return; + + if (stubInfo->considerCaching()) + repatchPutByID(exec, baseObject, structure, ident, slot, *stubInfo, Direct); +} + +void JIT_OPERATION operationPutByIdDirectNonStrictOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + Identifier ident = Identifier::fromUid(vm, uid); + AccessType accessType = static_cast<AccessType>(stubInfo->accessType); + + JSValue value = JSValue::decode(encodedValue); + JSObject* baseObject = asObject(JSValue::decode(encodedBase)); + PutPropertySlot slot(baseObject, false, exec->codeBlock()->putByIdContext()); + + Structure* structure = baseObject->structure(*vm); + baseObject->putDirect(exec->vm(), ident, value, slot); + + if (accessType != static_cast<AccessType>(stubInfo->accessType)) + return; + + if (stubInfo->considerCaching()) + repatchPutByID(exec, baseObject, structure, ident, slot, *stubInfo, Direct); +} + +void JIT_OPERATION operationReallocateStorageAndFinishPut(ExecState* exec, JSObject* base, Structure* structure, PropertyOffset offset, EncodedJSValue value) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + ASSERT(structure->outOfLineCapacity() > base->structure(vm)->outOfLineCapacity()); + ASSERT(!vm.heap.storageAllocator().fastPathShouldSucceed(structure->outOfLineCapacity() * sizeof(JSValue))); + base->setStructureAndReallocateStorageIfNecessary(vm, structure); + base->putDirect(vm, offset, JSValue::decode(value)); +} + +ALWAYS_INLINE static bool isStringOrSymbol(JSValue value) +{ + return value.isString() || value.isSymbol(); +} + +static void putByVal(CallFrame* callFrame, JSValue baseValue, JSValue subscript, JSValue value, ByValInfo* byValInfo) +{ + VM& vm = callFrame->vm(); + if (LIKELY(subscript.isUInt32())) { + byValInfo->tookSlowPath = true; + uint32_t i = subscript.asUInt32(); + if (baseValue.isObject()) { + JSObject* object = asObject(baseValue); + if (object->canSetIndexQuickly(i)) + object->setIndexQuickly(callFrame->vm(), i, value); + else { + // FIXME: This will make us think that in-bounds typed array accesses are actually + // out-of-bounds. + // https://bugs.webkit.org/show_bug.cgi?id=149886 + byValInfo->arrayProfile->setOutOfBounds(); + object->methodTable(vm)->putByIndex(object, callFrame, i, value, callFrame->codeBlock()->isStrictMode()); + } + } else + baseValue.putByIndex(callFrame, i, value, callFrame->codeBlock()->isStrictMode()); + return; + } + + auto property = subscript.toPropertyKey(callFrame); + // Don't put to an object if toString threw an exception. + if (callFrame->vm().exception()) + return; + + if (byValInfo->stubInfo && (!isStringOrSymbol(subscript) || byValInfo->cachedId != property)) + byValInfo->tookSlowPath = true; + + PutPropertySlot slot(baseValue, callFrame->codeBlock()->isStrictMode()); + baseValue.putInline(callFrame, property, value, slot); +} + +static void directPutByVal(CallFrame* callFrame, JSObject* baseObject, JSValue subscript, JSValue value, ByValInfo* byValInfo) +{ + bool isStrictMode = callFrame->codeBlock()->isStrictMode(); + if (LIKELY(subscript.isUInt32())) { + // Despite its name, JSValue::isUInt32 will return true only for positive boxed int32_t; all those values are valid array indices. + byValInfo->tookSlowPath = true; + uint32_t index = subscript.asUInt32(); + ASSERT(isIndex(index)); + if (baseObject->canSetIndexQuicklyForPutDirect(index)) { + baseObject->setIndexQuickly(callFrame->vm(), index, value); + return; + } + + // FIXME: This will make us think that in-bounds typed array accesses are actually + // out-of-bounds. + // https://bugs.webkit.org/show_bug.cgi?id=149886 + byValInfo->arrayProfile->setOutOfBounds(); + baseObject->putDirectIndex(callFrame, index, value, 0, isStrictMode ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow); + return; + } + + if (subscript.isDouble()) { + double subscriptAsDouble = subscript.asDouble(); + uint32_t subscriptAsUInt32 = static_cast<uint32_t>(subscriptAsDouble); + if (subscriptAsDouble == subscriptAsUInt32 && isIndex(subscriptAsUInt32)) { + byValInfo->tookSlowPath = true; + baseObject->putDirectIndex(callFrame, subscriptAsUInt32, value, 0, isStrictMode ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow); + return; + } + } + + // Don't put to an object if toString threw an exception. + auto property = subscript.toPropertyKey(callFrame); + if (callFrame->vm().exception()) + return; + + if (Optional<uint32_t> index = parseIndex(property)) { + byValInfo->tookSlowPath = true; + baseObject->putDirectIndex(callFrame, index.value(), value, 0, isStrictMode ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow); + return; + } + + if (byValInfo->stubInfo && (!isStringOrSymbol(subscript) || byValInfo->cachedId != property)) + byValInfo->tookSlowPath = true; + + PutPropertySlot slot(baseObject, isStrictMode); + baseObject->putDirect(callFrame->vm(), property, value, slot); +} + +enum class OptimizationResult { + NotOptimized, + SeenOnce, + Optimized, + GiveUp, +}; + +static OptimizationResult tryPutByValOptimize(ExecState* exec, JSValue baseValue, JSValue subscript, ByValInfo* byValInfo, ReturnAddressPtr returnAddress) +{ + // See if it's worth optimizing at all. + OptimizationResult optimizationResult = OptimizationResult::NotOptimized; + + VM& vm = exec->vm(); + + if (baseValue.isObject() && subscript.isInt32()) { + JSObject* object = asObject(baseValue); + + ASSERT(exec->bytecodeOffset()); + ASSERT(!byValInfo->stubRoutine); + + Structure* structure = object->structure(vm); + if (hasOptimizableIndexing(structure)) { + // Attempt to optimize. + JITArrayMode arrayMode = jitArrayModeForStructure(structure); + if (jitArrayModePermitsPut(arrayMode) && arrayMode != byValInfo->arrayMode) { + CodeBlock* codeBlock = exec->codeBlock(); + ConcurrentJITLocker locker(codeBlock->m_lock); + byValInfo->arrayProfile->computeUpdatedPrediction(locker, codeBlock, structure); + + JIT::compilePutByVal(&vm, exec->codeBlock(), byValInfo, returnAddress, arrayMode); + optimizationResult = OptimizationResult::Optimized; + } + } + + // If we failed to patch and we have some object that intercepts indexed get, then don't even wait until 10 times. + if (optimizationResult != OptimizationResult::Optimized && object->structure(vm)->typeInfo().interceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero()) + optimizationResult = OptimizationResult::GiveUp; + } + + if (baseValue.isObject() && isStringOrSymbol(subscript)) { + const Identifier propertyName = subscript.toPropertyKey(exec); + if (!subscript.isString() || !parseIndex(propertyName)) { + ASSERT(exec->bytecodeOffset()); + ASSERT(!byValInfo->stubRoutine); + if (byValInfo->seen) { + if (byValInfo->cachedId == propertyName) { + JIT::compilePutByValWithCachedId(&vm, exec->codeBlock(), byValInfo, returnAddress, NotDirect, propertyName); + optimizationResult = OptimizationResult::Optimized; + } else { + // Seem like a generic property access site. + optimizationResult = OptimizationResult::GiveUp; + } + } else { + byValInfo->seen = true; + byValInfo->cachedId = propertyName; + optimizationResult = OptimizationResult::SeenOnce; + } + } + } + + if (optimizationResult != OptimizationResult::Optimized && optimizationResult != OptimizationResult::SeenOnce) { + // If we take slow path more than 10 times without patching then make sure we + // never make that mistake again. For cases where we see non-index-intercepting + // objects, this gives 10 iterations worth of opportunity for us to observe + // that the put_by_val may be polymorphic. We count up slowPathCount even if + // the result is GiveUp. + if (++byValInfo->slowPathCount >= 10) + optimizationResult = OptimizationResult::GiveUp; + } + + return optimizationResult; +} + +void JIT_OPERATION operationPutByValOptimize(ExecState* exec, EncodedJSValue encodedBaseValue, EncodedJSValue encodedSubscript, EncodedJSValue encodedValue, ByValInfo* byValInfo) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + JSValue baseValue = JSValue::decode(encodedBaseValue); + JSValue subscript = JSValue::decode(encodedSubscript); + JSValue value = JSValue::decode(encodedValue); + if (tryPutByValOptimize(exec, baseValue, subscript, byValInfo, ReturnAddressPtr(OUR_RETURN_ADDRESS)) == OptimizationResult::GiveUp) { + // Don't ever try to optimize. + byValInfo->tookSlowPath = true; + ctiPatchCallByReturnAddress(ReturnAddressPtr(OUR_RETURN_ADDRESS), FunctionPtr(operationPutByValGeneric)); + } + putByVal(exec, baseValue, subscript, value, byValInfo); +} + +static OptimizationResult tryDirectPutByValOptimize(ExecState* exec, JSObject* object, JSValue subscript, ByValInfo* byValInfo, ReturnAddressPtr returnAddress) +{ + // See if it's worth optimizing at all. + OptimizationResult optimizationResult = OptimizationResult::NotOptimized; + + VM& vm = exec->vm(); + + if (subscript.isInt32()) { + ASSERT(exec->bytecodeOffset()); + ASSERT(!byValInfo->stubRoutine); + + Structure* structure = object->structure(vm); + if (hasOptimizableIndexing(structure)) { + // Attempt to optimize. + JITArrayMode arrayMode = jitArrayModeForStructure(structure); + if (jitArrayModePermitsPut(arrayMode) && arrayMode != byValInfo->arrayMode) { + CodeBlock* codeBlock = exec->codeBlock(); + ConcurrentJITLocker locker(codeBlock->m_lock); + byValInfo->arrayProfile->computeUpdatedPrediction(locker, codeBlock, structure); + + JIT::compileDirectPutByVal(&vm, exec->codeBlock(), byValInfo, returnAddress, arrayMode); + optimizationResult = OptimizationResult::Optimized; + } + } + + // If we failed to patch and we have some object that intercepts indexed get, then don't even wait until 10 times. + if (optimizationResult != OptimizationResult::Optimized && object->structure(vm)->typeInfo().interceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero()) + optimizationResult = OptimizationResult::GiveUp; + } else if (isStringOrSymbol(subscript)) { + const Identifier propertyName = subscript.toPropertyKey(exec); + Optional<uint32_t> index = parseIndex(propertyName); + + if (!subscript.isString() || !index) { + ASSERT(exec->bytecodeOffset()); + ASSERT(!byValInfo->stubRoutine); + if (byValInfo->seen) { + if (byValInfo->cachedId == propertyName) { + JIT::compilePutByValWithCachedId(&vm, exec->codeBlock(), byValInfo, returnAddress, Direct, propertyName); + optimizationResult = OptimizationResult::Optimized; + } else { + // Seem like a generic property access site. + optimizationResult = OptimizationResult::GiveUp; + } + } else { + byValInfo->seen = true; + byValInfo->cachedId = propertyName; + optimizationResult = OptimizationResult::SeenOnce; + } + } + } + + if (optimizationResult != OptimizationResult::Optimized && optimizationResult != OptimizationResult::SeenOnce) { + // If we take slow path more than 10 times without patching then make sure we + // never make that mistake again. For cases where we see non-index-intercepting + // objects, this gives 10 iterations worth of opportunity for us to observe + // that the get_by_val may be polymorphic. We count up slowPathCount even if + // the result is GiveUp. + if (++byValInfo->slowPathCount >= 10) + optimizationResult = OptimizationResult::GiveUp; + } + + return optimizationResult; +} + +void JIT_OPERATION operationDirectPutByValOptimize(ExecState* exec, EncodedJSValue encodedBaseValue, EncodedJSValue encodedSubscript, EncodedJSValue encodedValue, ByValInfo* byValInfo) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + JSValue baseValue = JSValue::decode(encodedBaseValue); + JSValue subscript = JSValue::decode(encodedSubscript); + JSValue value = JSValue::decode(encodedValue); + RELEASE_ASSERT(baseValue.isObject()); + JSObject* object = asObject(baseValue); + if (tryDirectPutByValOptimize(exec, object, subscript, byValInfo, ReturnAddressPtr(OUR_RETURN_ADDRESS)) == OptimizationResult::GiveUp) { + // Don't ever try to optimize. + byValInfo->tookSlowPath = true; + ctiPatchCallByReturnAddress(ReturnAddressPtr(OUR_RETURN_ADDRESS), FunctionPtr(operationDirectPutByValGeneric)); + } + + directPutByVal(exec, object, subscript, value, byValInfo); +} + +void JIT_OPERATION operationPutByValGeneric(ExecState* exec, EncodedJSValue encodedBaseValue, EncodedJSValue encodedSubscript, EncodedJSValue encodedValue, ByValInfo* byValInfo) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + JSValue baseValue = JSValue::decode(encodedBaseValue); + JSValue subscript = JSValue::decode(encodedSubscript); + JSValue value = JSValue::decode(encodedValue); + + putByVal(exec, baseValue, subscript, value, byValInfo); +} + + +void JIT_OPERATION operationDirectPutByValGeneric(ExecState* exec, EncodedJSValue encodedBaseValue, EncodedJSValue encodedSubscript, EncodedJSValue encodedValue, ByValInfo* byValInfo) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + JSValue baseValue = JSValue::decode(encodedBaseValue); + JSValue subscript = JSValue::decode(encodedSubscript); + JSValue value = JSValue::decode(encodedValue); + RELEASE_ASSERT(baseValue.isObject()); + directPutByVal(exec, asObject(baseValue), subscript, value, byValInfo); +} + +EncodedJSValue JIT_OPERATION operationCallEval(ExecState* exec, ExecState* execCallee) +{ + UNUSED_PARAM(exec); + + execCallee->setCodeBlock(0); + + if (!isHostFunction(execCallee->calleeAsValue(), globalFuncEval)) + return JSValue::encode(JSValue()); + + VM* vm = &execCallee->vm(); + JSValue result = eval(execCallee); + if (vm->exception()) + return EncodedJSValue(); + + return JSValue::encode(result); +} + +static SlowPathReturnType handleHostCall(ExecState* execCallee, JSValue callee, CallLinkInfo* callLinkInfo) +{ + ExecState* exec = execCallee->callerFrame(); + VM* vm = &exec->vm(); + + execCallee->setCodeBlock(0); + + if (callLinkInfo->specializationKind() == CodeForCall) { + CallData callData; + CallType callType = getCallData(callee, callData); + + ASSERT(callType != CallTypeJS); + + if (callType == CallTypeHost) { + NativeCallFrameTracer tracer(vm, execCallee); + execCallee->setCallee(asObject(callee)); + vm->hostCallReturnValue = JSValue::decode(callData.native.function(execCallee)); + if (vm->exception()) { + return encodeResult( + vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).code().executableAddress(), + reinterpret_cast<void*>(KeepTheFrame)); + } + + return encodeResult( + bitwise_cast<void*>(getHostCallReturnValue), + reinterpret_cast<void*>(callLinkInfo->callMode() == CallMode::Tail ? ReuseTheFrame : KeepTheFrame)); + } + + ASSERT(callType == CallTypeNone); + exec->vm().throwException(exec, createNotAFunctionError(exec, callee)); + return encodeResult( + vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).code().executableAddress(), + reinterpret_cast<void*>(KeepTheFrame)); + } + + ASSERT(callLinkInfo->specializationKind() == CodeForConstruct); + + ConstructData constructData; + ConstructType constructType = getConstructData(callee, constructData); + + ASSERT(constructType != ConstructTypeJS); + + if (constructType == ConstructTypeHost) { + NativeCallFrameTracer tracer(vm, execCallee); + execCallee->setCallee(asObject(callee)); + vm->hostCallReturnValue = JSValue::decode(constructData.native.function(execCallee)); + if (vm->exception()) { + return encodeResult( + vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).code().executableAddress(), + reinterpret_cast<void*>(KeepTheFrame)); + } + + return encodeResult(bitwise_cast<void*>(getHostCallReturnValue), reinterpret_cast<void*>(KeepTheFrame)); + } + + ASSERT(constructType == ConstructTypeNone); + exec->vm().throwException(exec, createNotAConstructorError(exec, callee)); + return encodeResult( + vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).code().executableAddress(), + reinterpret_cast<void*>(KeepTheFrame)); +} + +SlowPathReturnType JIT_OPERATION operationLinkCall(ExecState* execCallee, CallLinkInfo* callLinkInfo) +{ + ExecState* exec = execCallee->callerFrame(); + VM* vm = &exec->vm(); + CodeSpecializationKind kind = callLinkInfo->specializationKind(); + NativeCallFrameTracer tracer(vm, exec); + + JSValue calleeAsValue = execCallee->calleeAsValue(); + JSCell* calleeAsFunctionCell = getJSFunction(calleeAsValue); + if (!calleeAsFunctionCell) { + // FIXME: We should cache these kinds of calls. They can be common and currently they are + // expensive. + // https://bugs.webkit.org/show_bug.cgi?id=144458 + return handleHostCall(execCallee, calleeAsValue, callLinkInfo); + } + + JSFunction* callee = jsCast<JSFunction*>(calleeAsFunctionCell); + JSScope* scope = callee->scopeUnchecked(); + ExecutableBase* executable = callee->executable(); + + MacroAssemblerCodePtr codePtr; + CodeBlock* codeBlock = 0; + if (executable->isHostFunction()) { + codePtr = executable->entrypointFor(kind, MustCheckArity); +#if ENABLE(WEBASSEMBLY) + } else if (executable->isWebAssemblyExecutable()) { + WebAssemblyExecutable* webAssemblyExecutable = static_cast<WebAssemblyExecutable*>(executable); + webAssemblyExecutable->prepareForExecution(execCallee); + codeBlock = webAssemblyExecutable->codeBlockForCall(); + ASSERT(codeBlock); + ArityCheckMode arity; + if (execCallee->argumentCountIncludingThis() < static_cast<size_t>(codeBlock->numParameters())) + arity = MustCheckArity; + else + arity = ArityCheckNotRequired; + codePtr = webAssemblyExecutable->entrypointFor(kind, arity); +#endif + } else { + FunctionExecutable* functionExecutable = static_cast<FunctionExecutable*>(executable); + + if (!isCall(kind) && functionExecutable->constructAbility() == ConstructAbility::CannotConstruct) { + exec->vm().throwException(exec, createNotAConstructorError(exec, callee)); + return encodeResult( + vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).code().executableAddress(), + reinterpret_cast<void*>(KeepTheFrame)); + } + + JSObject* error = functionExecutable->prepareForExecution(execCallee, callee, scope, kind); + if (error) { + exec->vm().throwException(exec, error); + return encodeResult( + vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).code().executableAddress(), + reinterpret_cast<void*>(KeepTheFrame)); + } + codeBlock = functionExecutable->codeBlockFor(kind); + ArityCheckMode arity; + if (execCallee->argumentCountIncludingThis() < static_cast<size_t>(codeBlock->numParameters()) || callLinkInfo->isVarargs()) + arity = MustCheckArity; + else + arity = ArityCheckNotRequired; + codePtr = functionExecutable->entrypointFor(kind, arity); + } + if (!callLinkInfo->seenOnce()) + callLinkInfo->setSeen(); + else + linkFor(execCallee, *callLinkInfo, codeBlock, callee, codePtr); + + return encodeResult(codePtr.executableAddress(), reinterpret_cast<void*>(callLinkInfo->callMode() == CallMode::Tail ? ReuseTheFrame : KeepTheFrame)); +} + +inline SlowPathReturnType virtualForWithFunction( + ExecState* execCallee, CallLinkInfo* callLinkInfo, JSCell*& calleeAsFunctionCell) +{ + ExecState* exec = execCallee->callerFrame(); + VM* vm = &exec->vm(); + CodeSpecializationKind kind = callLinkInfo->specializationKind(); + NativeCallFrameTracer tracer(vm, exec); + + JSValue calleeAsValue = execCallee->calleeAsValue(); + calleeAsFunctionCell = getJSFunction(calleeAsValue); + if (UNLIKELY(!calleeAsFunctionCell)) + return handleHostCall(execCallee, calleeAsValue, callLinkInfo); + + JSFunction* function = jsCast<JSFunction*>(calleeAsFunctionCell); + JSScope* scope = function->scopeUnchecked(); + ExecutableBase* executable = function->executable(); + if (UNLIKELY(!executable->hasJITCodeFor(kind))) { + bool isWebAssemblyExecutable = false; +#if ENABLE(WEBASSEMBLY) + isWebAssemblyExecutable = executable->isWebAssemblyExecutable(); +#endif + if (!isWebAssemblyExecutable) { + FunctionExecutable* functionExecutable = static_cast<FunctionExecutable*>(executable); + + if (!isCall(kind) && functionExecutable->constructAbility() == ConstructAbility::CannotConstruct) { + exec->vm().throwException(exec, createNotAConstructorError(exec, function)); + return encodeResult( + vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).code().executableAddress(), + reinterpret_cast<void*>(KeepTheFrame)); + } + + JSObject* error = functionExecutable->prepareForExecution(execCallee, function, scope, kind); + if (error) { + exec->vm().throwException(exec, error); + return encodeResult( + vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).code().executableAddress(), + reinterpret_cast<void*>(KeepTheFrame)); + } + } else { +#if ENABLE(WEBASSEMBLY) + if (!isCall(kind)) { + exec->vm().throwException(exec, createNotAConstructorError(exec, function)); + return encodeResult( + vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).code().executableAddress(), + reinterpret_cast<void*>(KeepTheFrame)); + } + + WebAssemblyExecutable* webAssemblyExecutable = static_cast<WebAssemblyExecutable*>(executable); + webAssemblyExecutable->prepareForExecution(execCallee); +#endif + } + } + return encodeResult(executable->entrypointFor( + kind, MustCheckArity).executableAddress(), + reinterpret_cast<void*>(callLinkInfo->callMode() == CallMode::Tail ? ReuseTheFrame : KeepTheFrame)); +} + +SlowPathReturnType JIT_OPERATION operationLinkPolymorphicCall(ExecState* execCallee, CallLinkInfo* callLinkInfo) +{ + ASSERT(callLinkInfo->specializationKind() == CodeForCall); + JSCell* calleeAsFunctionCell; + SlowPathReturnType result = virtualForWithFunction(execCallee, callLinkInfo, calleeAsFunctionCell); + + linkPolymorphicCall(execCallee, *callLinkInfo, CallVariant(calleeAsFunctionCell)); + + return result; +} + +SlowPathReturnType JIT_OPERATION operationVirtualCall(ExecState* execCallee, CallLinkInfo* callLinkInfo) +{ + JSCell* calleeAsFunctionCellIgnored; + return virtualForWithFunction(execCallee, callLinkInfo, calleeAsFunctionCellIgnored); +} + +size_t JIT_OPERATION operationCompareLess(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + return jsLess<true>(exec, JSValue::decode(encodedOp1), JSValue::decode(encodedOp2)); +} + +size_t JIT_OPERATION operationCompareLessEq(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + return jsLessEq<true>(exec, JSValue::decode(encodedOp1), JSValue::decode(encodedOp2)); +} + +size_t JIT_OPERATION operationCompareGreater(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + return jsLess<false>(exec, JSValue::decode(encodedOp2), JSValue::decode(encodedOp1)); +} + +size_t JIT_OPERATION operationCompareGreaterEq(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + return jsLessEq<false>(exec, JSValue::decode(encodedOp2), JSValue::decode(encodedOp1)); +} + +size_t JIT_OPERATION operationConvertJSValueToBoolean(ExecState* exec, EncodedJSValue encodedOp) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + return JSValue::decode(encodedOp).toBoolean(exec); +} + +size_t JIT_OPERATION operationCompareEq(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + return JSValue::equalSlowCaseInline(exec, JSValue::decode(encodedOp1), JSValue::decode(encodedOp2)); +} + +#if USE(JSVALUE64) +EncodedJSValue JIT_OPERATION operationCompareStringEq(ExecState* exec, JSCell* left, JSCell* right) +#else +size_t JIT_OPERATION operationCompareStringEq(ExecState* exec, JSCell* left, JSCell* right) +#endif +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + bool result = WTF::equal(*asString(left)->value(exec).impl(), *asString(right)->value(exec).impl()); +#if USE(JSVALUE64) + return JSValue::encode(jsBoolean(result)); +#else + return result; +#endif +} + +size_t JIT_OPERATION operationHasProperty(ExecState* exec, JSObject* base, JSString* property) +{ + int result = base->hasProperty(exec, property->toIdentifier(exec)); + return result; +} + + +EncodedJSValue JIT_OPERATION operationNewArrayWithProfile(ExecState* exec, ArrayAllocationProfile* profile, const JSValue* values, int size) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + return JSValue::encode(constructArrayNegativeIndexed(exec, profile, values, size)); +} + +EncodedJSValue JIT_OPERATION operationNewArrayBufferWithProfile(ExecState* exec, ArrayAllocationProfile* profile, const JSValue* values, int size) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + return JSValue::encode(constructArray(exec, profile, values, size)); +} + +EncodedJSValue JIT_OPERATION operationNewArrayWithSizeAndProfile(ExecState* exec, ArrayAllocationProfile* profile, EncodedJSValue size) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + JSValue sizeValue = JSValue::decode(size); + return JSValue::encode(constructArrayWithSizeQuirk(exec, profile, exec->lexicalGlobalObject(), sizeValue)); +} + +} + +template<typename FunctionType> +static EncodedJSValue operationNewFunctionCommon(ExecState* exec, JSScope* scope, JSCell* functionExecutable, bool isInvalidated) +{ + ASSERT(functionExecutable->inherits(FunctionExecutable::info())); + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + if (isInvalidated) + return JSValue::encode(FunctionType::createWithInvalidatedReallocationWatchpoint(vm, static_cast<FunctionExecutable*>(functionExecutable), scope)); + return JSValue::encode(FunctionType::create(vm, static_cast<FunctionExecutable*>(functionExecutable), scope)); +} + +extern "C" { + +EncodedJSValue JIT_OPERATION operationNewFunction(ExecState* exec, JSScope* scope, JSCell* functionExecutable) +{ + return operationNewFunctionCommon<JSFunction>(exec, scope, functionExecutable, false); +} + +EncodedJSValue JIT_OPERATION operationNewFunctionWithInvalidatedReallocationWatchpoint(ExecState* exec, JSScope* scope, JSCell* functionExecutable) +{ + return operationNewFunctionCommon<JSFunction>(exec, scope, functionExecutable, true); +} + +EncodedJSValue JIT_OPERATION operationNewGeneratorFunction(ExecState* exec, JSScope* scope, JSCell* functionExecutable) +{ + return operationNewFunctionCommon<JSGeneratorFunction>(exec, scope, functionExecutable, false); +} + +EncodedJSValue JIT_OPERATION operationNewGeneratorFunctionWithInvalidatedReallocationWatchpoint(ExecState* exec, JSScope* scope, JSCell* functionExecutable) +{ + return operationNewFunctionCommon<JSGeneratorFunction>(exec, scope, functionExecutable, true); +} + +JSCell* JIT_OPERATION operationNewObject(ExecState* exec, Structure* structure) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + return constructEmptyObject(exec, structure); +} + +EncodedJSValue JIT_OPERATION operationNewRegexp(ExecState* exec, void* regexpPtr) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + RegExp* regexp = static_cast<RegExp*>(regexpPtr); + if (!regexp->isValid()) { + vm.throwException(exec, createSyntaxError(exec, ASCIILiteral("Invalid flags supplied to RegExp constructor."))); + return JSValue::encode(jsUndefined()); + } + + return JSValue::encode(RegExpObject::create(vm, exec->lexicalGlobalObject()->regExpStructure(), regexp)); +} + +// The only reason for returning an UnusedPtr (instead of void) is so that we can reuse the +// existing DFG slow path generator machinery when creating the slow path for CheckWatchdogTimer +// in the DFG. If a DFG slow path generator that supports a void return type is added in the +// future, we can switch to using that then. +UnusedPtr JIT_OPERATION operationHandleWatchdogTimer(ExecState* exec) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + if (UNLIKELY(vm.shouldTriggerTermination(exec))) + vm.throwException(exec, createTerminatedExecutionException(&vm)); + + return nullptr; +} + +void JIT_OPERATION operationThrowStaticError(ExecState* exec, EncodedJSValue encodedValue, int32_t referenceErrorFlag) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSValue errorMessageValue = JSValue::decode(encodedValue); + RELEASE_ASSERT(errorMessageValue.isString()); + String errorMessage = asString(errorMessageValue)->value(exec); + if (referenceErrorFlag) + vm.throwException(exec, createReferenceError(exec, errorMessage)); + else + vm.throwException(exec, createTypeError(exec, errorMessage)); +} + +void JIT_OPERATION operationDebug(ExecState* exec, int32_t debugHookID) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + vm.interpreter->debug(exec, static_cast<DebugHookID>(debugHookID)); +} + +#if ENABLE(DFG_JIT) +static void updateAllPredictionsAndOptimizeAfterWarmUp(CodeBlock* codeBlock) +{ + codeBlock->updateAllPredictions(); + codeBlock->optimizeAfterWarmUp(); +} + +SlowPathReturnType JIT_OPERATION operationOptimize(ExecState* exec, int32_t bytecodeIndex) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + // Defer GC for a while so that it doesn't run between when we enter into this + // slow path and when we figure out the state of our code block. This prevents + // a number of awkward reentrancy scenarios, including: + // + // - The optimized version of our code block being jettisoned by GC right after + // we concluded that we wanted to use it, but have not planted it into the JS + // stack yet. + // + // - An optimized version of our code block being installed just as we decided + // that it wasn't ready yet. + // + // Note that jettisoning won't happen if we already initiated OSR, because in + // that case we would have already planted the optimized code block into the JS + // stack. + DeferGCForAWhile deferGC(vm.heap); + + CodeBlock* codeBlock = exec->codeBlock(); + if (codeBlock->jitType() != JITCode::BaselineJIT) { + dataLog("Unexpected code block in Baseline->DFG tier-up: ", *codeBlock, "\n"); + RELEASE_ASSERT_NOT_REACHED(); + } + + if (bytecodeIndex) { + // If we're attempting to OSR from a loop, assume that this should be + // separately optimized. + codeBlock->m_shouldAlwaysBeInlined = false; + } + + if (Options::verboseOSR()) { + dataLog( + *codeBlock, ": Entered optimize with bytecodeIndex = ", bytecodeIndex, + ", executeCounter = ", codeBlock->jitExecuteCounter(), + ", optimizationDelayCounter = ", codeBlock->reoptimizationRetryCounter(), + ", exitCounter = "); + if (codeBlock->hasOptimizedReplacement()) + dataLog(codeBlock->replacement()->osrExitCounter()); + else + dataLog("N/A"); + dataLog("\n"); + } + + if (!codeBlock->checkIfOptimizationThresholdReached()) { + codeBlock->updateAllPredictions(); + if (Options::verboseOSR()) + dataLog("Choosing not to optimize ", *codeBlock, " yet, because the threshold hasn't been reached.\n"); + return encodeResult(0, 0); + } + + if (vm.enabledProfiler()) { + updateAllPredictionsAndOptimizeAfterWarmUp(codeBlock); + return encodeResult(0, 0); + } + + Debugger* debugger = codeBlock->globalObject()->debugger(); + if (debugger && (debugger->isStepping() || codeBlock->baselineAlternative()->hasDebuggerRequests())) { + updateAllPredictionsAndOptimizeAfterWarmUp(codeBlock); + return encodeResult(0, 0); + } + + if (codeBlock->m_shouldAlwaysBeInlined) { + updateAllPredictionsAndOptimizeAfterWarmUp(codeBlock); + if (Options::verboseOSR()) + dataLog("Choosing not to optimize ", *codeBlock, " yet, because m_shouldAlwaysBeInlined == true.\n"); + return encodeResult(0, 0); + } + + // We cannot be in the process of asynchronous compilation and also have an optimized + // replacement. + DFG::Worklist* worklist = DFG::existingGlobalDFGWorklistOrNull(); + ASSERT( + !worklist + || !(worklist->compilationState(DFG::CompilationKey(codeBlock, DFG::DFGMode)) != DFG::Worklist::NotKnown + && codeBlock->hasOptimizedReplacement())); + + DFG::Worklist::State worklistState; + if (worklist) { + // The call to DFG::Worklist::completeAllReadyPlansForVM() will complete all ready + // (i.e. compiled) code blocks. But if it completes ours, we also need to know + // what the result was so that we don't plow ahead and attempt OSR or immediate + // reoptimization. This will have already also set the appropriate JIT execution + // count threshold depending on what happened, so if the compilation was anything + // but successful we just want to return early. See the case for worklistState == + // DFG::Worklist::Compiled, below. + + // Note that we could have alternatively just called Worklist::compilationState() + // here, and if it returned Compiled, we could have then called + // completeAndScheduleOSR() below. But that would have meant that it could take + // longer for code blocks to be completed: they would only complete when *their* + // execution count trigger fired; but that could take a while since the firing is + // racy. It could also mean that code blocks that never run again after being + // compiled would sit on the worklist until next GC. That's fine, but it's + // probably a waste of memory. Our goal here is to complete code blocks as soon as + // possible in order to minimize the chances of us executing baseline code after + // optimized code is already available. + worklistState = worklist->completeAllReadyPlansForVM( + vm, DFG::CompilationKey(codeBlock, DFG::DFGMode)); + } else + worklistState = DFG::Worklist::NotKnown; + + if (worklistState == DFG::Worklist::Compiling) { + // We cannot be in the process of asynchronous compilation and also have an optimized + // replacement. + RELEASE_ASSERT(!codeBlock->hasOptimizedReplacement()); + codeBlock->setOptimizationThresholdBasedOnCompilationResult(CompilationDeferred); + return encodeResult(0, 0); + } + + if (worklistState == DFG::Worklist::Compiled) { + // If we don't have an optimized replacement but we did just get compiled, then + // the compilation failed or was invalidated, in which case the execution count + // thresholds have already been set appropriately by + // CodeBlock::setOptimizationThresholdBasedOnCompilationResult() and we have + // nothing left to do. + if (!codeBlock->hasOptimizedReplacement()) { + codeBlock->updateAllPredictions(); + if (Options::verboseOSR()) + dataLog("Code block ", *codeBlock, " was compiled but it doesn't have an optimized replacement.\n"); + return encodeResult(0, 0); + } + } else if (codeBlock->hasOptimizedReplacement()) { + if (Options::verboseOSR()) + dataLog("Considering OSR ", *codeBlock, " -> ", *codeBlock->replacement(), ".\n"); + // If we have an optimized replacement, then it must be the case that we entered + // cti_optimize from a loop. That's because if there's an optimized replacement, + // then all calls to this function will be relinked to the replacement and so + // the prologue OSR will never fire. + + // This is an interesting threshold check. Consider that a function OSR exits + // in the middle of a loop, while having a relatively low exit count. The exit + // will reset the execution counter to some target threshold, meaning that this + // code won't be reached until that loop heats up for >=1000 executions. But then + // we do a second check here, to see if we should either reoptimize, or just + // attempt OSR entry. Hence it might even be correct for + // shouldReoptimizeFromLoopNow() to always return true. But we make it do some + // additional checking anyway, to reduce the amount of recompilation thrashing. + if (codeBlock->replacement()->shouldReoptimizeFromLoopNow()) { + if (Options::verboseOSR()) { + dataLog( + "Triggering reoptimization of ", *codeBlock, + "(", *codeBlock->replacement(), ") (in loop).\n"); + } + codeBlock->replacement()->jettison(Profiler::JettisonDueToBaselineLoopReoptimizationTrigger, CountReoptimization); + return encodeResult(0, 0); + } + } else { + if (!codeBlock->shouldOptimizeNow()) { + if (Options::verboseOSR()) { + dataLog( + "Delaying optimization for ", *codeBlock, + " because of insufficient profiling.\n"); + } + return encodeResult(0, 0); + } + + if (Options::verboseOSR()) + dataLog("Triggering optimized compilation of ", *codeBlock, "\n"); + + unsigned numVarsWithValues; + if (bytecodeIndex) + numVarsWithValues = codeBlock->m_numVars; + else + numVarsWithValues = 0; + Operands<JSValue> mustHandleValues(codeBlock->numParameters(), numVarsWithValues); + int localsUsedForCalleeSaves = static_cast<int>(CodeBlock::llintBaselineCalleeSaveSpaceAsVirtualRegisters()); + for (size_t i = 0; i < mustHandleValues.size(); ++i) { + int operand = mustHandleValues.operandForIndex(i); + if (operandIsLocal(operand) && VirtualRegister(operand).toLocal() < localsUsedForCalleeSaves) + continue; + mustHandleValues[i] = exec->uncheckedR(operand).jsValue(); + } + + CodeBlock* replacementCodeBlock = codeBlock->newReplacement(); + CompilationResult result = DFG::compile( + vm, replacementCodeBlock, nullptr, DFG::DFGMode, bytecodeIndex, + mustHandleValues, JITToDFGDeferredCompilationCallback::create()); + + if (result != CompilationSuccessful) + return encodeResult(0, 0); + } + + CodeBlock* optimizedCodeBlock = codeBlock->replacement(); + ASSERT(JITCode::isOptimizingJIT(optimizedCodeBlock->jitType())); + + if (void* dataBuffer = DFG::prepareOSREntry(exec, optimizedCodeBlock, bytecodeIndex)) { + if (Options::verboseOSR()) { + dataLog( + "Performing OSR ", *codeBlock, " -> ", *optimizedCodeBlock, ".\n"); + } + + codeBlock->optimizeSoon(); + return encodeResult(vm.getCTIStub(DFG::osrEntryThunkGenerator).code().executableAddress(), dataBuffer); + } + + if (Options::verboseOSR()) { + dataLog( + "Optimizing ", *codeBlock, " -> ", *codeBlock->replacement(), + " succeeded, OSR failed, after a delay of ", + codeBlock->optimizationDelayCounter(), ".\n"); + } + + // Count the OSR failure as a speculation failure. If this happens a lot, then + // reoptimize. + optimizedCodeBlock->countOSRExit(); + + // We are a lot more conservative about triggering reoptimization after OSR failure than + // before it. If we enter the optimize_from_loop trigger with a bucket full of fail + // already, then we really would like to reoptimize immediately. But this case covers + // something else: there weren't many (or any) speculation failures before, but we just + // failed to enter the speculative code because some variable had the wrong value or + // because the OSR code decided for any spurious reason that it did not want to OSR + // right now. So, we only trigger reoptimization only upon the more conservative (non-loop) + // reoptimization trigger. + if (optimizedCodeBlock->shouldReoptimizeNow()) { + if (Options::verboseOSR()) { + dataLog( + "Triggering reoptimization of ", *codeBlock, " -> ", + *codeBlock->replacement(), " (after OSR fail).\n"); + } + optimizedCodeBlock->jettison(Profiler::JettisonDueToBaselineLoopReoptimizationTriggerOnOSREntryFail, CountReoptimization); + return encodeResult(0, 0); + } + + // OSR failed this time, but it might succeed next time! Let the code run a bit + // longer and then try again. + codeBlock->optimizeAfterWarmUp(); + + return encodeResult(0, 0); +} +#endif + +void JIT_OPERATION operationPutByIndex(ExecState* exec, EncodedJSValue encodedArrayValue, int32_t index, EncodedJSValue encodedValue) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + JSValue arrayValue = JSValue::decode(encodedArrayValue); + ASSERT(isJSArray(arrayValue)); + asArray(arrayValue)->putDirectIndex(exec, index, JSValue::decode(encodedValue)); +} + +enum class AccessorType { + Getter, + Setter +}; + +static void putAccessorByVal(ExecState* exec, JSObject* base, JSValue subscript, int32_t attribute, JSObject* accessor, AccessorType accessorType) +{ + auto propertyKey = subscript.toPropertyKey(exec); + if (exec->hadException()) + return; + + if (accessorType == AccessorType::Getter) + base->putGetter(exec, propertyKey, accessor, attribute); + else + base->putSetter(exec, propertyKey, accessor, attribute); +} + +void JIT_OPERATION operationPutGetterById(ExecState* exec, JSCell* object, UniquedStringImpl* uid, int32_t options, JSCell* getter) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + ASSERT(object && object->isObject()); + JSObject* baseObj = object->getObject(); + + ASSERT(getter->isObject()); + baseObj->putGetter(exec, uid, getter, options); +} + +void JIT_OPERATION operationPutSetterById(ExecState* exec, JSCell* object, UniquedStringImpl* uid, int32_t options, JSCell* setter) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + ASSERT(object && object->isObject()); + JSObject* baseObj = object->getObject(); + + ASSERT(setter->isObject()); + baseObj->putSetter(exec, uid, setter, options); +} + +void JIT_OPERATION operationPutGetterByVal(ExecState* exec, JSCell* base, EncodedJSValue encodedSubscript, int32_t attribute, JSCell* getter) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + putAccessorByVal(exec, asObject(base), JSValue::decode(encodedSubscript), attribute, asObject(getter), AccessorType::Getter); +} + +void JIT_OPERATION operationPutSetterByVal(ExecState* exec, JSCell* base, EncodedJSValue encodedSubscript, int32_t attribute, JSCell* setter) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + putAccessorByVal(exec, asObject(base), JSValue::decode(encodedSubscript), attribute, asObject(setter), AccessorType::Setter); +} + +#if USE(JSVALUE64) +void JIT_OPERATION operationPutGetterSetter(ExecState* exec, JSCell* object, UniquedStringImpl* uid, int32_t attribute, EncodedJSValue encodedGetterValue, EncodedJSValue encodedSetterValue) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + ASSERT(object && object->isObject()); + JSObject* baseObj = asObject(object); + + GetterSetter* accessor = GetterSetter::create(vm, exec->lexicalGlobalObject()); + + JSValue getter = JSValue::decode(encodedGetterValue); + JSValue setter = JSValue::decode(encodedSetterValue); + ASSERT(getter.isObject() || getter.isUndefined()); + ASSERT(setter.isObject() || setter.isUndefined()); + ASSERT(getter.isObject() || setter.isObject()); + + if (!getter.isUndefined()) + accessor->setGetter(vm, exec->lexicalGlobalObject(), asObject(getter)); + if (!setter.isUndefined()) + accessor->setSetter(vm, exec->lexicalGlobalObject(), asObject(setter)); + baseObj->putDirectAccessor(exec, uid, accessor, attribute); +} + +#else +void JIT_OPERATION operationPutGetterSetter(ExecState* exec, JSCell* object, UniquedStringImpl* uid, int32_t attribute, JSCell* getter, JSCell* setter) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + ASSERT(object && object->isObject()); + JSObject* baseObj = asObject(object); + + GetterSetter* accessor = GetterSetter::create(vm, exec->lexicalGlobalObject()); + + ASSERT(!getter || getter->isObject()); + ASSERT(!setter || setter->isObject()); + ASSERT(getter || setter); + + if (getter) + accessor->setGetter(vm, exec->lexicalGlobalObject(), getter->getObject()); + if (setter) + accessor->setSetter(vm, exec->lexicalGlobalObject(), setter->getObject()); + baseObj->putDirectAccessor(exec, uid, accessor, attribute); +} +#endif + +void JIT_OPERATION operationPopScope(ExecState* exec, int32_t scopeReg) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + JSScope* scope = exec->uncheckedR(scopeReg).Register::scope(); + exec->uncheckedR(scopeReg) = scope->next(); +} + +void JIT_OPERATION operationProfileDidCall(ExecState* exec, EncodedJSValue encodedValue) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + if (LegacyProfiler* profiler = vm.enabledProfiler()) + profiler->didExecute(exec, JSValue::decode(encodedValue)); +} + +void JIT_OPERATION operationProfileWillCall(ExecState* exec, EncodedJSValue encodedValue) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + if (LegacyProfiler* profiler = vm.enabledProfiler()) + profiler->willExecute(exec, JSValue::decode(encodedValue)); +} + +int32_t JIT_OPERATION operationInstanceOfCustom(ExecState* exec, EncodedJSValue encodedValue, JSObject* constructor, EncodedJSValue encodedHasInstance) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + JSValue value = JSValue::decode(encodedValue); + JSValue hasInstanceValue = JSValue::decode(encodedHasInstance); + + ASSERT(hasInstanceValue != exec->lexicalGlobalObject()->functionProtoHasInstanceSymbolFunction() || !constructor->structure()->typeInfo().implementsDefaultHasInstance()); + + if (constructor->hasInstance(exec, value, hasInstanceValue)) + return 1; + return 0; +} + +} + +static bool canAccessArgumentIndexQuickly(JSObject& object, uint32_t index) +{ + switch (object.structure()->typeInfo().type()) { + case DirectArgumentsType: { + DirectArguments* directArguments = jsCast<DirectArguments*>(&object); + if (directArguments->canAccessArgumentIndexQuicklyInDFG(index)) + return true; + break; + } + case ScopedArgumentsType: { + ScopedArguments* scopedArguments = jsCast<ScopedArguments*>(&object); + if (scopedArguments->canAccessArgumentIndexQuicklyInDFG(index)) + return true; + break; + } + default: + break; + } + return false; +} + +static JSValue getByVal(ExecState* exec, JSValue baseValue, JSValue subscript, ByValInfo* byValInfo, ReturnAddressPtr returnAddress) +{ + if (LIKELY(baseValue.isCell() && subscript.isString())) { + VM& vm = exec->vm(); + Structure& structure = *baseValue.asCell()->structure(vm); + if (JSCell::canUseFastGetOwnProperty(structure)) { + if (RefPtr<AtomicStringImpl> existingAtomicString = asString(subscript)->toExistingAtomicString(exec)) { + if (JSValue result = baseValue.asCell()->fastGetOwnProperty(vm, structure, existingAtomicString.get())) { + ASSERT(exec->bytecodeOffset()); + if (byValInfo->stubInfo && byValInfo->cachedId.impl() != existingAtomicString) + byValInfo->tookSlowPath = true; + return result; + } + } + } + } + + if (subscript.isUInt32()) { + ASSERT(exec->bytecodeOffset()); + byValInfo->tookSlowPath = true; + + uint32_t i = subscript.asUInt32(); + if (isJSString(baseValue)) { + if (asString(baseValue)->canGetIndex(i)) { + ctiPatchCallByReturnAddress(returnAddress, FunctionPtr(operationGetByValString)); + return asString(baseValue)->getIndex(exec, i); + } + byValInfo->arrayProfile->setOutOfBounds(); + } else if (baseValue.isObject()) { + JSObject* object = asObject(baseValue); + if (object->canGetIndexQuickly(i)) + return object->getIndexQuickly(i); + + if (!canAccessArgumentIndexQuickly(*object, i)) { + // FIXME: This will make us think that in-bounds typed array accesses are actually + // out-of-bounds. + // https://bugs.webkit.org/show_bug.cgi?id=149886 + byValInfo->arrayProfile->setOutOfBounds(); + } + } + + return baseValue.get(exec, i); + } + + baseValue.requireObjectCoercible(exec); + if (exec->hadException()) + return jsUndefined(); + auto property = subscript.toPropertyKey(exec); + if (exec->hadException()) + return jsUndefined(); + + ASSERT(exec->bytecodeOffset()); + if (byValInfo->stubInfo && (!isStringOrSymbol(subscript) || byValInfo->cachedId != property)) + byValInfo->tookSlowPath = true; + + return baseValue.get(exec, property); +} + +static OptimizationResult tryGetByValOptimize(ExecState* exec, JSValue baseValue, JSValue subscript, ByValInfo* byValInfo, ReturnAddressPtr returnAddress) +{ + // See if it's worth optimizing this at all. + OptimizationResult optimizationResult = OptimizationResult::NotOptimized; + + VM& vm = exec->vm(); + + if (baseValue.isObject() && subscript.isInt32()) { + JSObject* object = asObject(baseValue); + + ASSERT(exec->bytecodeOffset()); + ASSERT(!byValInfo->stubRoutine); + + if (hasOptimizableIndexing(object->structure(vm))) { + // Attempt to optimize. + Structure* structure = object->structure(vm); + JITArrayMode arrayMode = jitArrayModeForStructure(structure); + if (arrayMode != byValInfo->arrayMode) { + // If we reached this case, we got an interesting array mode we did not expect when we compiled. + // Let's update the profile to do better next time. + CodeBlock* codeBlock = exec->codeBlock(); + ConcurrentJITLocker locker(codeBlock->m_lock); + byValInfo->arrayProfile->computeUpdatedPrediction(locker, codeBlock, structure); + + JIT::compileGetByVal(&vm, exec->codeBlock(), byValInfo, returnAddress, arrayMode); + optimizationResult = OptimizationResult::Optimized; + } + } + + // If we failed to patch and we have some object that intercepts indexed get, then don't even wait until 10 times. + if (optimizationResult != OptimizationResult::Optimized && object->structure(vm)->typeInfo().interceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero()) + optimizationResult = OptimizationResult::GiveUp; + } + + if (baseValue.isObject() && isStringOrSymbol(subscript)) { + const Identifier propertyName = subscript.toPropertyKey(exec); + if (!subscript.isString() || !parseIndex(propertyName)) { + ASSERT(exec->bytecodeOffset()); + ASSERT(!byValInfo->stubRoutine); + if (byValInfo->seen) { + if (byValInfo->cachedId == propertyName) { + JIT::compileGetByValWithCachedId(&vm, exec->codeBlock(), byValInfo, returnAddress, propertyName); + optimizationResult = OptimizationResult::Optimized; + } else { + // Seem like a generic property access site. + optimizationResult = OptimizationResult::GiveUp; + } + } else { + byValInfo->seen = true; + byValInfo->cachedId = propertyName; + optimizationResult = OptimizationResult::SeenOnce; + } + + } + } + + if (optimizationResult != OptimizationResult::Optimized && optimizationResult != OptimizationResult::SeenOnce) { + // If we take slow path more than 10 times without patching then make sure we + // never make that mistake again. For cases where we see non-index-intercepting + // objects, this gives 10 iterations worth of opportunity for us to observe + // that the get_by_val may be polymorphic. We count up slowPathCount even if + // the result is GiveUp. + if (++byValInfo->slowPathCount >= 10) + optimizationResult = OptimizationResult::GiveUp; + } + + return optimizationResult; +} + +extern "C" { + +EncodedJSValue JIT_OPERATION operationGetByValGeneric(ExecState* exec, EncodedJSValue encodedBase, EncodedJSValue encodedSubscript, ByValInfo* byValInfo) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSValue baseValue = JSValue::decode(encodedBase); + JSValue subscript = JSValue::decode(encodedSubscript); + + JSValue result = getByVal(exec, baseValue, subscript, byValInfo, ReturnAddressPtr(OUR_RETURN_ADDRESS)); + return JSValue::encode(result); +} + +EncodedJSValue JIT_OPERATION operationGetByValOptimize(ExecState* exec, EncodedJSValue encodedBase, EncodedJSValue encodedSubscript, ByValInfo* byValInfo) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + JSValue baseValue = JSValue::decode(encodedBase); + JSValue subscript = JSValue::decode(encodedSubscript); + ReturnAddressPtr returnAddress = ReturnAddressPtr(OUR_RETURN_ADDRESS); + if (tryGetByValOptimize(exec, baseValue, subscript, byValInfo, returnAddress) == OptimizationResult::GiveUp) { + // Don't ever try to optimize. + byValInfo->tookSlowPath = true; + ctiPatchCallByReturnAddress(returnAddress, FunctionPtr(operationGetByValGeneric)); + } + + return JSValue::encode(getByVal(exec, baseValue, subscript, byValInfo, returnAddress)); +} + +EncodedJSValue JIT_OPERATION operationHasIndexedPropertyDefault(ExecState* exec, EncodedJSValue encodedBase, EncodedJSValue encodedSubscript, ByValInfo* byValInfo) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSValue baseValue = JSValue::decode(encodedBase); + JSValue subscript = JSValue::decode(encodedSubscript); + + ASSERT(baseValue.isObject()); + ASSERT(subscript.isUInt32()); + + JSObject* object = asObject(baseValue); + bool didOptimize = false; + + ASSERT(exec->bytecodeOffset()); + ASSERT(!byValInfo->stubRoutine); + + if (hasOptimizableIndexing(object->structure(vm))) { + // Attempt to optimize. + JITArrayMode arrayMode = jitArrayModeForStructure(object->structure(vm)); + if (arrayMode != byValInfo->arrayMode) { + JIT::compileHasIndexedProperty(&vm, exec->codeBlock(), byValInfo, ReturnAddressPtr(OUR_RETURN_ADDRESS), arrayMode); + didOptimize = true; + } + } + + if (!didOptimize) { + // If we take slow path more than 10 times without patching then make sure we + // never make that mistake again. Or, if we failed to patch and we have some object + // that intercepts indexed get, then don't even wait until 10 times. For cases + // where we see non-index-intercepting objects, this gives 10 iterations worth of + // opportunity for us to observe that the get_by_val may be polymorphic. + if (++byValInfo->slowPathCount >= 10 + || object->structure(vm)->typeInfo().interceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero()) { + // Don't ever try to optimize. + ctiPatchCallByReturnAddress(ReturnAddressPtr(OUR_RETURN_ADDRESS), FunctionPtr(operationHasIndexedPropertyGeneric)); + } + } + + uint32_t index = subscript.asUInt32(); + if (object->canGetIndexQuickly(index)) + return JSValue::encode(JSValue(JSValue::JSTrue)); + + if (!canAccessArgumentIndexQuickly(*object, index)) { + // FIXME: This will make us think that in-bounds typed array accesses are actually + // out-of-bounds. + // https://bugs.webkit.org/show_bug.cgi?id=149886 + byValInfo->arrayProfile->setOutOfBounds(); + } + return JSValue::encode(jsBoolean(object->hasProperty(exec, index))); +} + +EncodedJSValue JIT_OPERATION operationHasIndexedPropertyGeneric(ExecState* exec, EncodedJSValue encodedBase, EncodedJSValue encodedSubscript, ByValInfo* byValInfo) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSValue baseValue = JSValue::decode(encodedBase); + JSValue subscript = JSValue::decode(encodedSubscript); + + ASSERT(baseValue.isObject()); + ASSERT(subscript.isUInt32()); + + JSObject* object = asObject(baseValue); + uint32_t index = subscript.asUInt32(); + if (object->canGetIndexQuickly(index)) + return JSValue::encode(JSValue(JSValue::JSTrue)); + + if (!canAccessArgumentIndexQuickly(*object, index)) { + // FIXME: This will make us think that in-bounds typed array accesses are actually + // out-of-bounds. + // https://bugs.webkit.org/show_bug.cgi?id=149886 + byValInfo->arrayProfile->setOutOfBounds(); + } + return JSValue::encode(jsBoolean(object->hasProperty(exec, subscript.asUInt32()))); +} + +EncodedJSValue JIT_OPERATION operationGetByValString(ExecState* exec, EncodedJSValue encodedBase, EncodedJSValue encodedSubscript, ByValInfo* byValInfo) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSValue baseValue = JSValue::decode(encodedBase); + JSValue subscript = JSValue::decode(encodedSubscript); + + JSValue result; + if (LIKELY(subscript.isUInt32())) { + uint32_t i = subscript.asUInt32(); + if (isJSString(baseValue) && asString(baseValue)->canGetIndex(i)) + result = asString(baseValue)->getIndex(exec, i); + else { + result = baseValue.get(exec, i); + if (!isJSString(baseValue)) { + ASSERT(exec->bytecodeOffset()); + ctiPatchCallByReturnAddress(ReturnAddressPtr(OUR_RETURN_ADDRESS), FunctionPtr(byValInfo->stubRoutine ? operationGetByValGeneric : operationGetByValOptimize)); + } + } + } else { + baseValue.requireObjectCoercible(exec); + if (exec->hadException()) + return JSValue::encode(jsUndefined()); + auto property = subscript.toPropertyKey(exec); + if (exec->hadException()) + return JSValue::encode(jsUndefined()); + result = baseValue.get(exec, property); + } + + return JSValue::encode(result); +} + +EncodedJSValue JIT_OPERATION operationDeleteById(ExecState* exec, EncodedJSValue encodedBase, const Identifier* identifier) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + JSObject* baseObj = JSValue::decode(encodedBase).toObject(exec); + bool couldDelete = baseObj->methodTable(vm)->deleteProperty(baseObj, exec, *identifier); + JSValue result = jsBoolean(couldDelete); + if (!couldDelete && exec->codeBlock()->isStrictMode()) + vm.throwException(exec, createTypeError(exec, ASCIILiteral("Unable to delete property."))); + return JSValue::encode(result); +} + +EncodedJSValue JIT_OPERATION operationInstanceOf(ExecState* exec, EncodedJSValue encodedValue, EncodedJSValue encodedProto) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSValue value = JSValue::decode(encodedValue); + JSValue proto = JSValue::decode(encodedProto); + + ASSERT(!value.isObject() || !proto.isObject()); + + bool result = JSObject::defaultHasInstance(exec, value, proto); + return JSValue::encode(jsBoolean(result)); +} + +int32_t JIT_OPERATION operationSizeFrameForVarargs(ExecState* exec, EncodedJSValue encodedArguments, int32_t numUsedStackSlots, int32_t firstVarArgOffset) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSStack* stack = &exec->interpreter()->stack(); + JSValue arguments = JSValue::decode(encodedArguments); + return sizeFrameForVarargs(exec, stack, arguments, numUsedStackSlots, firstVarArgOffset); +} + +CallFrame* JIT_OPERATION operationSetupVarargsFrame(ExecState* exec, CallFrame* newCallFrame, EncodedJSValue encodedArguments, int32_t firstVarArgOffset, int32_t length) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSValue arguments = JSValue::decode(encodedArguments); + setupVarargsFrame(exec, newCallFrame, arguments, firstVarArgOffset, length); + return newCallFrame; +} + +EncodedJSValue JIT_OPERATION operationToObject(ExecState* exec, EncodedJSValue value) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + return JSValue::encode(JSValue::decode(value).toObject(exec)); +} + +char* JIT_OPERATION operationSwitchCharWithUnknownKeyType(ExecState* exec, EncodedJSValue encodedKey, size_t tableIndex) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSValue key = JSValue::decode(encodedKey); + CodeBlock* codeBlock = exec->codeBlock(); + + SimpleJumpTable& jumpTable = codeBlock->switchJumpTable(tableIndex); + void* result = jumpTable.ctiDefault.executableAddress(); + + if (key.isString()) { + StringImpl* value = asString(key)->value(exec).impl(); + if (value->length() == 1) + result = jumpTable.ctiForValue((*value)[0]).executableAddress(); + } + + return reinterpret_cast<char*>(result); +} + +char* JIT_OPERATION operationSwitchImmWithUnknownKeyType(ExecState* exec, EncodedJSValue encodedKey, size_t tableIndex) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSValue key = JSValue::decode(encodedKey); + CodeBlock* codeBlock = exec->codeBlock(); + + SimpleJumpTable& jumpTable = codeBlock->switchJumpTable(tableIndex); + void* result; + if (key.isInt32()) + result = jumpTable.ctiForValue(key.asInt32()).executableAddress(); + else if (key.isDouble() && key.asDouble() == static_cast<int32_t>(key.asDouble())) + result = jumpTable.ctiForValue(static_cast<int32_t>(key.asDouble())).executableAddress(); + else + result = jumpTable.ctiDefault.executableAddress(); + return reinterpret_cast<char*>(result); +} + +char* JIT_OPERATION operationSwitchStringWithUnknownKeyType(ExecState* exec, EncodedJSValue encodedKey, size_t tableIndex) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSValue key = JSValue::decode(encodedKey); + CodeBlock* codeBlock = exec->codeBlock(); + + void* result; + StringJumpTable& jumpTable = codeBlock->stringSwitchJumpTable(tableIndex); + + if (key.isString()) { + StringImpl* value = asString(key)->value(exec).impl(); + result = jumpTable.ctiForValue(value).executableAddress(); + } else + result = jumpTable.ctiDefault.executableAddress(); + + return reinterpret_cast<char*>(result); +} + +EncodedJSValue JIT_OPERATION operationGetFromScope(ExecState* exec, Instruction* bytecodePC) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + CodeBlock* codeBlock = exec->codeBlock(); + Instruction* pc = bytecodePC; + + const Identifier& ident = codeBlock->identifier(pc[3].u.operand); + JSObject* scope = jsCast<JSObject*>(exec->uncheckedR(pc[2].u.operand).jsValue()); + GetPutInfo getPutInfo(pc[4].u.operand); + + // ModuleVar is always converted to ClosureVar for get_from_scope. + ASSERT(getPutInfo.resolveType() != ModuleVar); + + PropertySlot slot(scope, PropertySlot::InternalMethodType::Get); + if (!scope->getPropertySlot(exec, ident, slot)) { + if (getPutInfo.resolveMode() == ThrowIfNotFound) + vm.throwException(exec, createUndefinedVariableError(exec, ident)); + return JSValue::encode(jsUndefined()); + } + + JSValue result = JSValue(); + if (jsDynamicCast<JSGlobalLexicalEnvironment*>(scope)) { + // When we can't statically prove we need a TDZ check, we must perform the check on the slow path. + result = slot.getValue(exec, ident); + if (result == jsTDZValue()) { + exec->vm().throwException(exec, createTDZError(exec)); + return JSValue::encode(jsUndefined()); + } + } + + CommonSlowPaths::tryCacheGetFromScopeGlobal(exec, vm, pc, scope, slot, ident); + + if (!result) + result = slot.getValue(exec, ident); + return JSValue::encode(result); +} + +void JIT_OPERATION operationPutToScope(ExecState* exec, Instruction* bytecodePC) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + Instruction* pc = bytecodePC; + + CodeBlock* codeBlock = exec->codeBlock(); + const Identifier& ident = codeBlock->identifier(pc[2].u.operand); + JSObject* scope = jsCast<JSObject*>(exec->uncheckedR(pc[1].u.operand).jsValue()); + JSValue value = exec->r(pc[3].u.operand).jsValue(); + GetPutInfo getPutInfo = GetPutInfo(pc[4].u.operand); + + // ModuleVar does not keep the scope register value alive in DFG. + ASSERT(getPutInfo.resolveType() != ModuleVar); + + if (getPutInfo.resolveType() == LocalClosureVar) { + JSLexicalEnvironment* environment = jsCast<JSLexicalEnvironment*>(scope); + environment->variableAt(ScopeOffset(pc[6].u.operand)).set(vm, environment, value); + if (WatchpointSet* set = pc[5].u.watchpointSet) + set->touch("Executed op_put_scope<LocalClosureVar>"); + return; + } + + bool hasProperty = scope->hasProperty(exec, ident); + if (hasProperty + && jsDynamicCast<JSGlobalLexicalEnvironment*>(scope) + && getPutInfo.initializationMode() != Initialization) { + // When we can't statically prove we need a TDZ check, we must perform the check on the slow path. + PropertySlot slot(scope, PropertySlot::InternalMethodType::Get); + JSGlobalLexicalEnvironment::getOwnPropertySlot(scope, exec, ident, slot); + if (slot.getValue(exec, ident) == jsTDZValue()) { + exec->vm().throwException(exec, createTDZError(exec)); + return; + } + } + + if (getPutInfo.resolveMode() == ThrowIfNotFound && !hasProperty) { + exec->vm().throwException(exec, createUndefinedVariableError(exec, ident)); + return; + } + + PutPropertySlot slot(scope, codeBlock->isStrictMode(), PutPropertySlot::UnknownContext, getPutInfo.initializationMode() == Initialization); + scope->methodTable()->put(scope, exec, ident, value, slot); + + if (exec->vm().exception()) + return; + + CommonSlowPaths::tryCachePutToScopeGlobal(exec, codeBlock, pc, scope, getPutInfo, slot, ident); +} + +void JIT_OPERATION operationThrow(ExecState* exec, EncodedJSValue encodedExceptionValue) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + JSValue exceptionValue = JSValue::decode(encodedExceptionValue); + vm->throwException(exec, exceptionValue); + + // Results stored out-of-band in vm.targetMachinePCForThrow & vm.callFrameForCatch + genericUnwind(vm, exec); +} + +void JIT_OPERATION operationFlushWriteBarrierBuffer(ExecState* exec, JSCell* cell) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + vm->heap.flushWriteBarrierBuffer(cell); +} + +void JIT_OPERATION operationOSRWriteBarrier(ExecState* exec, JSCell* cell) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + vm->heap.writeBarrier(cell); +} + +// NB: We don't include the value as part of the barrier because the write barrier elision +// phase in the DFG only tracks whether the object being stored to has been barriered. It +// would be much more complicated to try to model the value being stored as well. +void JIT_OPERATION operationUnconditionalWriteBarrier(ExecState* exec, JSCell* cell) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + vm->heap.writeBarrier(cell); +} + +void JIT_OPERATION operationInitGlobalConst(ExecState* exec, Instruction* pc) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + + JSValue value = exec->r(pc[2].u.operand).jsValue(); + pc[1].u.variablePointer->set(*vm, exec->codeBlock()->globalObject(), value); +} + +void JIT_OPERATION lookupExceptionHandler(VM* vm, ExecState* exec) +{ + NativeCallFrameTracer tracer(vm, exec); + genericUnwind(vm, exec); + ASSERT(vm->targetMachinePCForThrow); +} + +void JIT_OPERATION lookupExceptionHandlerFromCallerFrame(VM* vm, ExecState* exec) +{ + NativeCallFrameTracer tracer(vm, exec); + genericUnwind(vm, exec, UnwindFromCallerFrame); + ASSERT(vm->targetMachinePCForThrow); +} + +void JIT_OPERATION operationVMHandleException(ExecState* exec) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); + genericUnwind(vm, exec); +} + +// This function "should" just take the ExecState*, but doing so would make it more difficult +// to call from exception check sites. So, unlike all of our other functions, we allow +// ourselves to play some gnarly ABI tricks just to simplify the calling convention. This is +// particularly safe here since this is never called on the critical path - it's only for +// testing. +void JIT_OPERATION operationExceptionFuzz(ExecState* exec) +{ + VM* vm = &exec->vm(); + NativeCallFrameTracer tracer(vm, exec); +#if COMPILER(GCC_OR_CLANG) + void* returnPC = __builtin_return_address(0); + doExceptionFuzzing(exec, "JITOperations", returnPC); +#endif // COMPILER(GCC_OR_CLANG) +} + +EncodedJSValue JIT_OPERATION operationHasGenericProperty(ExecState* exec, EncodedJSValue encodedBaseValue, JSCell* propertyName) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSValue baseValue = JSValue::decode(encodedBaseValue); + if (baseValue.isUndefinedOrNull()) + return JSValue::encode(jsBoolean(false)); + + JSObject* base = baseValue.toObject(exec); + return JSValue::encode(jsBoolean(base->hasProperty(exec, asString(propertyName)->toIdentifier(exec)))); +} + +EncodedJSValue JIT_OPERATION operationHasIndexedProperty(ExecState* exec, JSCell* baseCell, int32_t subscript) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSObject* object = baseCell->toObject(exec, exec->lexicalGlobalObject()); + return JSValue::encode(jsBoolean(object->hasProperty(exec, subscript))); +} + +JSCell* JIT_OPERATION operationGetPropertyEnumerator(ExecState* exec, JSCell* cell) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + + JSObject* base = cell->toObject(exec, exec->lexicalGlobalObject()); + + return propertyNameEnumerator(exec, base); +} + +EncodedJSValue JIT_OPERATION operationNextEnumeratorPname(ExecState* exec, JSCell* enumeratorCell, int32_t index) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + JSPropertyNameEnumerator* enumerator = jsCast<JSPropertyNameEnumerator*>(enumeratorCell); + JSString* propertyName = enumerator->propertyNameAtIndex(index); + return JSValue::encode(propertyName ? propertyName : jsNull()); +} + +JSCell* JIT_OPERATION operationToIndexString(ExecState* exec, int32_t index) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + return jsString(exec, Identifier::from(exec, index).string()); +} + +void JIT_OPERATION operationProcessTypeProfilerLog(ExecState* exec) +{ + exec->vm().typeProfilerLog()->processLogEntries(ASCIILiteral("Log Full, called from inside baseline JIT")); +} + +int32_t JIT_OPERATION operationCheckIfExceptionIsUncatchableAndNotifyProfiler(ExecState* exec) +{ + VM& vm = exec->vm(); + NativeCallFrameTracer tracer(&vm, exec); + RELEASE_ASSERT(!!vm.exception()); + + if (LegacyProfiler* profiler = vm.enabledProfiler()) + profiler->exceptionUnwind(exec); + + if (isTerminatedExecutionException(vm.exception())) { + genericUnwind(&vm, exec); + return 1; + } else + return 0; +} + +} // extern "C" + +// Note: getHostCallReturnValueWithExecState() needs to be placed before the +// definition of getHostCallReturnValue() below because the Windows build +// requires it. +extern "C" EncodedJSValue HOST_CALL_RETURN_VALUE_OPTION getHostCallReturnValueWithExecState(ExecState* exec) +{ + if (!exec) + return JSValue::encode(JSValue()); + return JSValue::encode(exec->vm().hostCallReturnValue); +} + +#if COMPILER(GCC_OR_CLANG) && CPU(X86_64) +asm ( +".globl " SYMBOL_STRING(getHostCallReturnValue) "\n" +HIDE_SYMBOL(getHostCallReturnValue) "\n" +SYMBOL_STRING(getHostCallReturnValue) ":" "\n" + "lea -8(%rsp), %rdi\n" + "jmp " LOCAL_REFERENCE(getHostCallReturnValueWithExecState) "\n" +); + +#elif COMPILER(GCC_OR_CLANG) && CPU(X86) +asm ( +".text" "\n" \ +".globl " SYMBOL_STRING(getHostCallReturnValue) "\n" +HIDE_SYMBOL(getHostCallReturnValue) "\n" +SYMBOL_STRING(getHostCallReturnValue) ":" "\n" + "push %ebp\n" + "mov %esp, %eax\n" + "leal -4(%esp), %esp\n" + "push %eax\n" + "call " LOCAL_REFERENCE(getHostCallReturnValueWithExecState) "\n" + "leal 8(%esp), %esp\n" + "pop %ebp\n" + "ret\n" +); + +#elif COMPILER(GCC_OR_CLANG) && CPU(ARM_THUMB2) +asm ( +".text" "\n" +".align 2" "\n" +".globl " SYMBOL_STRING(getHostCallReturnValue) "\n" +HIDE_SYMBOL(getHostCallReturnValue) "\n" +".thumb" "\n" +".thumb_func " THUMB_FUNC_PARAM(getHostCallReturnValue) "\n" +SYMBOL_STRING(getHostCallReturnValue) ":" "\n" + "sub r0, sp, #8" "\n" + "b " LOCAL_REFERENCE(getHostCallReturnValueWithExecState) "\n" +); + +#elif COMPILER(GCC_OR_CLANG) && CPU(ARM_TRADITIONAL) +asm ( +".text" "\n" +".globl " SYMBOL_STRING(getHostCallReturnValue) "\n" +HIDE_SYMBOL(getHostCallReturnValue) "\n" +INLINE_ARM_FUNCTION(getHostCallReturnValue) +SYMBOL_STRING(getHostCallReturnValue) ":" "\n" + "sub r0, sp, #8" "\n" + "b " LOCAL_REFERENCE(getHostCallReturnValueWithExecState) "\n" +); + +#elif CPU(ARM64) +asm ( +".text" "\n" +".align 2" "\n" +".globl " SYMBOL_STRING(getHostCallReturnValue) "\n" +HIDE_SYMBOL(getHostCallReturnValue) "\n" +SYMBOL_STRING(getHostCallReturnValue) ":" "\n" + "sub x0, sp, #16" "\n" + "b " LOCAL_REFERENCE(getHostCallReturnValueWithExecState) "\n" +); + +#elif COMPILER(GCC_OR_CLANG) && CPU(MIPS) + +#if WTF_MIPS_PIC +#define LOAD_FUNCTION_TO_T9(function) \ + ".set noreorder" "\n" \ + ".cpload $25" "\n" \ + ".set reorder" "\n" \ + "la $t9, " LOCAL_REFERENCE(function) "\n" +#else +#define LOAD_FUNCTION_TO_T9(function) "" "\n" +#endif + +asm ( +".text" "\n" +".globl " SYMBOL_STRING(getHostCallReturnValue) "\n" +HIDE_SYMBOL(getHostCallReturnValue) "\n" +SYMBOL_STRING(getHostCallReturnValue) ":" "\n" + LOAD_FUNCTION_TO_T9(getHostCallReturnValueWithExecState) + "addi $a0, $sp, -8" "\n" + "b " LOCAL_REFERENCE(getHostCallReturnValueWithExecState) "\n" +); + +#elif COMPILER(GCC_OR_CLANG) && CPU(SH4) + +#define SH4_SCRATCH_REGISTER "r11" + +asm ( +".text" "\n" +".globl " SYMBOL_STRING(getHostCallReturnValue) "\n" +HIDE_SYMBOL(getHostCallReturnValue) "\n" +SYMBOL_STRING(getHostCallReturnValue) ":" "\n" + "mov r15, r4" "\n" + "add -8, r4" "\n" + "mov.l 2f, " SH4_SCRATCH_REGISTER "\n" + "braf " SH4_SCRATCH_REGISTER "\n" + "nop" "\n" + "1: .balign 4" "\n" + "2: .long " LOCAL_REFERENCE(getHostCallReturnValueWithExecState) "-1b\n" +); + +#elif COMPILER(MSVC) && CPU(X86) +extern "C" { + __declspec(naked) EncodedJSValue HOST_CALL_RETURN_VALUE_OPTION getHostCallReturnValue() + { + __asm lea eax, [esp - 4] + __asm mov [esp + 4], eax; + __asm jmp getHostCallReturnValueWithExecState + } +} +#endif + +} // namespace JSC + +#endif // ENABLE(JIT) |