| /* |
| * Copyright (C) 2013-2018 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 "ArithProfile.h" |
| #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 "EvalCodeBlock.h" |
| #include "ExceptionFuzz.h" |
| #include "FTLOSREntry.h" |
| #include "FrameTracers.h" |
| #include "FunctionCodeBlock.h" |
| #include "GetterSetter.h" |
| #include "HostCallReturnValue.h" |
| #include "ICStats.h" |
| #include "Interpreter.h" |
| #include "JIT.h" |
| #include "JITExceptions.h" |
| #include "JITToDFGDeferredCompilationCallback.h" |
| #include "JSAsyncFunction.h" |
| #include "JSAsyncGeneratorFunction.h" |
| #include "JSCInlines.h" |
| #include "JSCPtrTag.h" |
| #include "JSGeneratorFunction.h" |
| #include "JSGlobalObjectFunctions.h" |
| #include "JSLexicalEnvironment.h" |
| #include "JSWithScope.h" |
| #include "ModuleProgramCodeBlock.h" |
| #include "ObjectConstructor.h" |
| #include "PolymorphicAccess.h" |
| #include "ProgramCodeBlock.h" |
| #include "PropertyName.h" |
| #include "RegExpObject.h" |
| #include "Repatch.h" |
| #include "ScopedArguments.h" |
| #include "ShadowChicken.h" |
| #include "StructureStubInfo.h" |
| #include "SuperSampler.h" |
| #include "TestRunnerUtils.h" |
| #include "ThunkGenerators.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(); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| |
| EntryFrame* entryFrame = vm->topEntryFrame; |
| CallFrame* callerFrame = exec->callerFrame(entryFrame); |
| if (!callerFrame) { |
| callerFrame = exec; |
| entryFrame = vm->topEntryFrame; |
| } |
| |
| NativeCallFrameTracerWithRestore tracer(vm, entryFrame, callerFrame); |
| throwStackOverflowError(callerFrame, scope); |
| } |
| |
| #if ENABLE(WEBASSEMBLY) |
| void JIT_OPERATION operationThrowDivideError(ExecState* exec) |
| { |
| VM* vm = &exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| |
| EntryFrame* entryFrame = vm->topEntryFrame; |
| CallFrame* callerFrame = exec->callerFrame(entryFrame); |
| |
| NativeCallFrameTracerWithRestore tracer(vm, entryFrame, callerFrame); |
| ErrorHandlingScope errorScope(*vm); |
| throwException(callerFrame, scope, createError(callerFrame, ASCIILiteral("Division by zero or division overflow."))); |
| } |
| |
| void JIT_OPERATION operationThrowOutOfBoundsAccessError(ExecState* exec) |
| { |
| VM* vm = &exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| |
| EntryFrame* entryFrame = vm->topEntryFrame; |
| CallFrame* callerFrame = exec->callerFrame(entryFrame); |
| |
| NativeCallFrameTracerWithRestore tracer(vm, entryFrame, callerFrame); |
| ErrorHandlingScope errorScope(*vm); |
| throwException(callerFrame, scope, createError(callerFrame, ASCIILiteral("Out-of-bounds access."))); |
| } |
| #endif |
| |
| int32_t JIT_OPERATION operationCallArityCheck(ExecState* exec) |
| { |
| VM* vm = &exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| |
| int32_t missingArgCount = CommonSlowPaths::arityCheckFor(exec, *vm, CodeForCall); |
| if (missingArgCount < 0) { |
| EntryFrame* entryFrame = vm->topEntryFrame; |
| CallFrame* callerFrame = exec->callerFrame(entryFrame); |
| NativeCallFrameTracerWithRestore tracer(vm, entryFrame, callerFrame); |
| throwStackOverflowError(callerFrame, scope); |
| } |
| |
| return missingArgCount; |
| } |
| |
| int32_t JIT_OPERATION operationConstructArityCheck(ExecState* exec) |
| { |
| VM* vm = &exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| |
| int32_t missingArgCount = CommonSlowPaths::arityCheckFor(exec, *vm, CodeForConstruct); |
| if (missingArgCount < 0) { |
| EntryFrame* entryFrame = vm->topEntryFrame; |
| CallFrame* callerFrame = exec->callerFrame(entryFrame); |
| NativeCallFrameTracerWithRestore tracer(vm, entryFrame, callerFrame); |
| throwStackOverflowError(callerFrame, scope); |
| } |
| |
| return missingArgCount; |
| } |
| |
| EncodedJSValue JIT_OPERATION operationTryGetById(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue base, UniquedStringImpl* uid) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| Identifier ident = Identifier::fromUid(vm, uid); |
| stubInfo->tookSlowPath = true; |
| |
| JSValue baseValue = JSValue::decode(base); |
| PropertySlot slot(baseValue, PropertySlot::InternalMethodType::VMInquiry); |
| baseValue.getPropertySlot(exec, ident, slot); |
| |
| return JSValue::encode(slot.getPureResult()); |
| } |
| |
| |
| EncodedJSValue JIT_OPERATION operationTryGetByIdGeneric(ExecState* exec, 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::VMInquiry); |
| baseValue.getPropertySlot(exec, ident, slot); |
| |
| return JSValue::encode(slot.getPureResult()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationTryGetByIdOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue base, UniquedStringImpl* uid) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| Identifier ident = Identifier::fromUid(vm, uid); |
| |
| JSValue baseValue = JSValue::decode(base); |
| PropertySlot slot(baseValue, PropertySlot::InternalMethodType::VMInquiry); |
| |
| baseValue.getPropertySlot(exec, ident, slot); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| |
| if (stubInfo->considerCaching(exec->codeBlock(), baseValue.structureOrNull()) && !slot.isTaintedByOpaqueObject() && (slot.isCacheableValue() || slot.isCacheableGetter() || slot.isUnset())) |
| repatchGetByID(exec, baseValue, ident, slot, *stubInfo, GetByIDKind::Try); |
| |
| return JSValue::encode(slot.getPureResult()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationGetByIdDirect(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue base, UniquedStringImpl* uid) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| Identifier ident = Identifier::fromUid(&vm, uid); |
| stubInfo->tookSlowPath = true; |
| |
| JSValue baseValue = JSValue::decode(base); |
| PropertySlot slot(baseValue, PropertySlot::InternalMethodType::GetOwnProperty); |
| |
| bool found = baseValue.getOwnPropertySlot(exec, ident, slot); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| |
| scope.release(); |
| return JSValue::encode(found ? slot.getValue(exec, ident) : jsUndefined()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationGetByIdDirectGeneric(ExecState* exec, EncodedJSValue base, UniquedStringImpl* uid) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| Identifier ident = Identifier::fromUid(&vm, uid); |
| |
| JSValue baseValue = JSValue::decode(base); |
| PropertySlot slot(baseValue, PropertySlot::InternalMethodType::GetOwnProperty); |
| |
| bool found = baseValue.getOwnPropertySlot(exec, ident, slot); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| |
| scope.release(); |
| return JSValue::encode(found ? slot.getValue(exec, ident) : jsUndefined()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationGetByIdDirectOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue base, UniquedStringImpl* uid) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| Identifier ident = Identifier::fromUid(&vm, uid); |
| |
| JSValue baseValue = JSValue::decode(base); |
| PropertySlot slot(baseValue, PropertySlot::InternalMethodType::GetOwnProperty); |
| |
| bool found = baseValue.getOwnPropertySlot(exec, ident, slot); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| |
| if (stubInfo->considerCaching(exec->codeBlock(), baseValue.structureOrNull())) |
| repatchGetByID(exec, baseValue, ident, slot, *stubInfo, GetByIDKind::Direct); |
| |
| scope.release(); |
| return JSValue::encode(found ? slot.getValue(exec, ident) : jsUndefined()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationGetById(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue base, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| 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); |
| |
| LOG_IC((ICEvent::OperationGetById, baseValue.classInfoOrNull(*vm), ident)); |
| return JSValue::encode(baseValue.get(exec, ident, slot)); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationGetByIdGeneric(ExecState* exec, EncodedJSValue base, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| 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); |
| LOG_IC((ICEvent::OperationGetByIdGeneric, baseValue.classInfoOrNull(*vm), ident)); |
| return JSValue::encode(baseValue.get(exec, ident, slot)); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationGetByIdOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue base, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| Identifier ident = Identifier::fromUid(vm, uid); |
| |
| JSValue baseValue = JSValue::decode(base); |
| LOG_IC((ICEvent::OperationGetByIdOptimize, baseValue.classInfoOrNull(*vm), ident)); |
| |
| return JSValue::encode(baseValue.getPropertySlot(exec, ident, [&] (bool found, PropertySlot& slot) -> JSValue { |
| if (stubInfo->considerCaching(exec->codeBlock(), baseValue.structureOrNull())) |
| repatchGetByID(exec, baseValue, ident, slot, *stubInfo, GetByIDKind::Normal); |
| return found ? slot.getValue(exec, ident) : jsUndefined(); |
| })); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationGetByIdWithThis(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue base, EncodedJSValue thisEncoded, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| Identifier ident = Identifier::fromUid(vm, uid); |
| |
| stubInfo->tookSlowPath = true; |
| |
| JSValue baseValue = JSValue::decode(base); |
| JSValue thisValue = JSValue::decode(thisEncoded); |
| PropertySlot slot(thisValue, PropertySlot::InternalMethodType::Get); |
| |
| return JSValue::encode(baseValue.get(exec, ident, slot)); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationGetByIdWithThisGeneric(ExecState* exec, EncodedJSValue base, EncodedJSValue thisEncoded, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| Identifier ident = Identifier::fromUid(vm, uid); |
| |
| JSValue baseValue = JSValue::decode(base); |
| JSValue thisValue = JSValue::decode(thisEncoded); |
| PropertySlot slot(thisValue, PropertySlot::InternalMethodType::Get); |
| |
| return JSValue::encode(baseValue.get(exec, ident, slot)); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationGetByIdWithThisOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue base, EncodedJSValue thisEncoded, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| Identifier ident = Identifier::fromUid(vm, uid); |
| |
| JSValue baseValue = JSValue::decode(base); |
| JSValue thisValue = JSValue::decode(thisEncoded); |
| LOG_IC((ICEvent::OperationGetByIdWithThisOptimize, baseValue.classInfoOrNull(*vm), ident)); |
| |
| PropertySlot slot(thisValue, PropertySlot::InternalMethodType::Get); |
| return JSValue::encode(baseValue.getPropertySlot(exec, ident, slot, [&] (bool found, PropertySlot& slot) -> JSValue { |
| if (stubInfo->considerCaching(exec->codeBlock(), baseValue.structureOrNull())) |
| repatchGetByID(exec, baseValue, ident, slot, *stubInfo, GetByIDKind::WithThis); |
| return found ? slot.getValue(exec, ident) : jsUndefined(); |
| })); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationInOptimize(ExecState* exec, StructureStubInfo* stubInfo, JSCell* base, UniquedStringImpl* key) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| |
| if (!base->isObject()) { |
| throwException(exec, scope, createInvalidInParameterError(exec, base)); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| AccessType accessType = static_cast<AccessType>(stubInfo->accessType); |
| |
| Identifier ident = Identifier::fromUid(vm, key); |
| LOG_IC((ICEvent::OperationInOptimize, base->classInfo(*vm), ident)); |
| PropertySlot slot(base, PropertySlot::InternalMethodType::HasProperty); |
| bool result = asObject(base)->getPropertySlot(exec, ident, slot); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| |
| RELEASE_ASSERT(accessType == stubInfo->accessType); |
| |
| if (stubInfo->considerCaching(exec->codeBlock(), asObject(base)->structure())) |
| repatchIn(exec, base, ident, result, slot, *stubInfo); |
| |
| return JSValue::encode(jsBoolean(result)); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationIn(ExecState* exec, StructureStubInfo* stubInfo, JSCell* base, UniquedStringImpl* key) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| |
| stubInfo->tookSlowPath = true; |
| |
| if (!base->isObject()) { |
| throwException(exec, scope, createInvalidInParameterError(exec, base)); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| Identifier ident = Identifier::fromUid(vm, key); |
| LOG_IC((ICEvent::OperationIn, base->classInfo(*vm), ident)); |
| scope.release(); |
| return JSValue::encode(jsBoolean(asObject(base)->hasProperty(exec, ident))); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationGenericIn(ExecState* exec, JSCell* base, EncodedJSValue key) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| return JSValue::encode(jsBoolean(CommonSlowPaths::opIn(exec, base, JSValue::decode(key)))); |
| } |
| |
| void JIT_OPERATION operationPutByIdStrict(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| stubInfo->tookSlowPath = true; |
| |
| JSValue baseValue = JSValue::decode(encodedBase); |
| Identifier ident = Identifier::fromUid(vm, uid); |
| LOG_IC((ICEvent::OperationPutByIdStrict, baseValue.classInfoOrNull(*vm), ident)); |
| |
| PutPropertySlot slot(baseValue, true, exec->codeBlock()->putByIdContext()); |
| baseValue.putInline(exec, ident, JSValue::decode(encodedValue), slot); |
| } |
| |
| void JIT_OPERATION operationPutByIdNonStrict(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| stubInfo->tookSlowPath = true; |
| |
| JSValue baseValue = JSValue::decode(encodedBase); |
| Identifier ident = Identifier::fromUid(vm, uid); |
| LOG_IC((ICEvent::OperationPutByIdNonStrict, baseValue.classInfoOrNull(*vm), ident)); |
| PutPropertySlot slot(baseValue, false, exec->codeBlock()->putByIdContext()); |
| baseValue.putInline(exec, ident, JSValue::decode(encodedValue), slot); |
| } |
| |
| void JIT_OPERATION operationPutByIdDirectStrict(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| stubInfo->tookSlowPath = true; |
| |
| JSValue baseValue = JSValue::decode(encodedBase); |
| Identifier ident = Identifier::fromUid(vm, uid); |
| LOG_IC((ICEvent::OperationPutByIdDirectStrict, baseValue.classInfoOrNull(*vm), ident)); |
| PutPropertySlot slot(baseValue, true, exec->codeBlock()->putByIdContext()); |
| asObject(baseValue)->putDirect(*vm, ident, JSValue::decode(encodedValue), slot); |
| } |
| |
| void JIT_OPERATION operationPutByIdDirectNonStrict(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| stubInfo->tookSlowPath = true; |
| |
| JSValue baseValue = JSValue::decode(encodedBase); |
| Identifier ident = Identifier::fromUid(vm, uid); |
| LOG_IC((ICEvent::OperationPutByIdDirectNonStrict, baseValue.classInfoOrNull(*vm), ident)); |
| PutPropertySlot slot(baseValue, false, exec->codeBlock()->putByIdContext()); |
| asObject(baseValue)->putDirect(*vm, ident, JSValue::decode(encodedValue), slot); |
| } |
| |
| void JIT_OPERATION operationPutByIdStrictOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| |
| Identifier ident = Identifier::fromUid(vm, uid); |
| AccessType accessType = static_cast<AccessType>(stubInfo->accessType); |
| |
| JSValue value = JSValue::decode(encodedValue); |
| JSValue baseValue = JSValue::decode(encodedBase); |
| LOG_IC((ICEvent::OperationPutByIdStrictOptimize, baseValue.classInfoOrNull(*vm), ident)); |
| CodeBlock* codeBlock = exec->codeBlock(); |
| PutPropertySlot slot(baseValue, true, codeBlock->putByIdContext()); |
| |
| Structure* structure = baseValue.isCell() ? baseValue.asCell()->structure(*vm) : nullptr; |
| baseValue.putInline(exec, ident, value, slot); |
| RETURN_IF_EXCEPTION(scope, void()); |
| |
| if (accessType != static_cast<AccessType>(stubInfo->accessType)) |
| return; |
| |
| if (stubInfo->considerCaching(codeBlock, structure)) |
| repatchPutByID(exec, baseValue, structure, ident, slot, *stubInfo, NotDirect); |
| } |
| |
| void JIT_OPERATION operationPutByIdNonStrictOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| |
| Identifier ident = Identifier::fromUid(vm, uid); |
| AccessType accessType = static_cast<AccessType>(stubInfo->accessType); |
| |
| JSValue value = JSValue::decode(encodedValue); |
| JSValue baseValue = JSValue::decode(encodedBase); |
| LOG_IC((ICEvent::OperationPutByIdNonStrictOptimize, baseValue.classInfoOrNull(*vm), ident)); |
| CodeBlock* codeBlock = exec->codeBlock(); |
| PutPropertySlot slot(baseValue, false, codeBlock->putByIdContext()); |
| |
| Structure* structure = baseValue.isCell() ? baseValue.asCell()->structure(*vm) : nullptr; |
| baseValue.putInline(exec, ident, value, slot); |
| RETURN_IF_EXCEPTION(scope, void()); |
| |
| if (accessType != static_cast<AccessType>(stubInfo->accessType)) |
| return; |
| |
| if (stubInfo->considerCaching(codeBlock, structure)) |
| repatchPutByID(exec, baseValue, structure, ident, slot, *stubInfo, NotDirect); |
| } |
| |
| void JIT_OPERATION operationPutByIdDirectStrictOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| 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)); |
| LOG_IC((ICEvent::OperationPutByIdDirectStrictOptimize, baseObject->classInfo(*vm), ident)); |
| CodeBlock* codeBlock = exec->codeBlock(); |
| PutPropertySlot slot(baseObject, true, codeBlock->putByIdContext()); |
| |
| Structure* structure = baseObject->structure(*vm); |
| baseObject->putDirect(*vm, ident, value, slot); |
| |
| if (accessType != static_cast<AccessType>(stubInfo->accessType)) |
| return; |
| |
| if (stubInfo->considerCaching(codeBlock, structure)) |
| repatchPutByID(exec, baseObject, structure, ident, slot, *stubInfo, Direct); |
| } |
| |
| void JIT_OPERATION operationPutByIdDirectNonStrictOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| |
| 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)); |
| LOG_IC((ICEvent::OperationPutByIdDirectNonStrictOptimize, baseObject->classInfo(*vm), ident)); |
| CodeBlock* codeBlock = exec->codeBlock(); |
| PutPropertySlot slot(baseObject, false, codeBlock->putByIdContext()); |
| |
| Structure* structure = baseObject->structure(*vm); |
| baseObject->putDirect(*vm, ident, value, slot); |
| |
| if (accessType != static_cast<AccessType>(stubInfo->accessType)) |
| return; |
| |
| if (stubInfo->considerCaching(codeBlock, structure)) |
| repatchPutByID(exec, baseObject, structure, ident, slot, *stubInfo, Direct); |
| } |
| |
| 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(); |
| auto scope = DECLARE_THROW_SCOPE(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(vm, i, 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(); |
| scope.release(); |
| object->methodTable(vm)->putByIndex(object, callFrame, i, value, callFrame->codeBlock()->isStrictMode()); |
| return; |
| } |
| |
| scope.release(); |
| 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. |
| RETURN_IF_EXCEPTION(scope, void()); |
| |
| if (byValInfo->stubInfo && (!isStringOrSymbol(subscript) || byValInfo->cachedId != property)) |
| byValInfo->tookSlowPath = true; |
| |
| scope.release(); |
| 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) |
| { |
| VM& vm = callFrame->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| 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)); |
| |
| switch (baseObject->indexingType()) { |
| case ALL_INT32_INDEXING_TYPES: |
| case ALL_DOUBLE_INDEXING_TYPES: |
| case ALL_CONTIGUOUS_INDEXING_TYPES: |
| case ALL_ARRAY_STORAGE_INDEXING_TYPES: |
| if (index < baseObject->butterfly()->vectorLength()) |
| break; |
| FALLTHROUGH; |
| default: |
| byValInfo->arrayProfile->setOutOfBounds(); |
| break; |
| } |
| |
| scope.release(); |
| 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; |
| scope.release(); |
| 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); |
| RETURN_IF_EXCEPTION(scope, void()); |
| |
| if (std::optional<uint32_t> index = parseIndex(property)) { |
| byValInfo->tookSlowPath = true; |
| scope.release(); |
| 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(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(); |
| ConcurrentJSLocker locker(codeBlock->m_lock); |
| byValInfo->arrayProfile->computeUpdatedPrediction(locker, codeBlock, structure); |
| |
| JIT::compilePutByVal(&vm, 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.isSymbol() || !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 { |
| CodeBlock* codeBlock = exec->codeBlock(); |
| ConcurrentJSLocker locker(codeBlock->m_lock); |
| byValInfo->seen = true; |
| byValInfo->cachedId = propertyName; |
| if (subscript.isSymbol()) |
| byValInfo->cachedSymbol.set(vm, codeBlock, asSymbol(subscript)); |
| 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), 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 (jitArrayModePermitsPutDirect(arrayMode) && arrayMode != byValInfo->arrayMode) { |
| CodeBlock* codeBlock = exec->codeBlock(); |
| ConcurrentJSLocker locker(codeBlock->m_lock); |
| byValInfo->arrayProfile->computeUpdatedPrediction(locker, codeBlock, structure); |
| |
| JIT::compileDirectPutByVal(&vm, 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); |
| if (subscript.isSymbol() || !parseIndex(propertyName)) { |
| 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 { |
| CodeBlock* codeBlock = exec->codeBlock(); |
| ConcurrentJSLocker locker(codeBlock->m_lock); |
| byValInfo->seen = true; |
| byValInfo->cachedId = propertyName; |
| if (subscript.isSymbol()) |
| byValInfo->cachedSymbol.set(vm, codeBlock, asSymbol(subscript)); |
| 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), 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) |
| { |
| VM* vm = &exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| |
| execCallee->setCodeBlock(0); |
| |
| if (!isHostFunction(execCallee->guaranteedJSValueCallee(), globalFuncEval)) |
| return JSValue::encode(JSValue()); |
| |
| JSValue result = eval(execCallee); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| |
| return JSValue::encode(result); |
| } |
| |
| static SlowPathReturnType handleHostCall(ExecState* execCallee, JSValue callee, CallLinkInfo* callLinkInfo) |
| { |
| ExecState* exec = execCallee->callerFrame(); |
| VM* vm = &exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| |
| execCallee->setCodeBlock(0); |
| |
| if (callLinkInfo->specializationKind() == CodeForCall) { |
| CallData callData; |
| CallType callType = getCallData(callee, callData); |
| |
| ASSERT(callType != CallType::JS); |
| |
| if (callType == CallType::Host) { |
| NativeCallFrameTracer tracer(vm, execCallee); |
| execCallee->setCallee(asObject(callee)); |
| vm->hostCallReturnValue = JSValue::decode(callData.native.function(execCallee)); |
| if (UNLIKELY(scope.exception())) { |
| return encodeResult( |
| vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).retaggedCode<JSEntryPtrTag>().executableAddress(), |
| reinterpret_cast<void*>(KeepTheFrame)); |
| } |
| |
| return encodeResult( |
| tagCFunctionPtr<void*, JSEntryPtrTag>(getHostCallReturnValue), |
| reinterpret_cast<void*>(callLinkInfo->callMode() == CallMode::Tail ? ReuseTheFrame : KeepTheFrame)); |
| } |
| |
| ASSERT(callType == CallType::None); |
| throwException(exec, scope, createNotAFunctionError(exec, callee)); |
| return encodeResult( |
| vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).retaggedCode<JSEntryPtrTag>().executableAddress(), |
| reinterpret_cast<void*>(KeepTheFrame)); |
| } |
| |
| ASSERT(callLinkInfo->specializationKind() == CodeForConstruct); |
| |
| ConstructData constructData; |
| ConstructType constructType = getConstructData(callee, constructData); |
| |
| ASSERT(constructType != ConstructType::JS); |
| |
| if (constructType == ConstructType::Host) { |
| NativeCallFrameTracer tracer(vm, execCallee); |
| execCallee->setCallee(asObject(callee)); |
| vm->hostCallReturnValue = JSValue::decode(constructData.native.function(execCallee)); |
| if (UNLIKELY(scope.exception())) { |
| return encodeResult( |
| vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).retaggedCode<JSEntryPtrTag>().executableAddress(), |
| reinterpret_cast<void*>(KeepTheFrame)); |
| } |
| |
| return encodeResult(tagCFunctionPtr<void*, JSEntryPtrTag>(getHostCallReturnValue), reinterpret_cast<void*>(KeepTheFrame)); |
| } |
| |
| ASSERT(constructType == ConstructType::None); |
| throwException(exec, scope, createNotAConstructorError(exec, callee)); |
| return encodeResult( |
| vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).retaggedCode<JSEntryPtrTag>().executableAddress(), |
| reinterpret_cast<void*>(KeepTheFrame)); |
| } |
| |
| SlowPathReturnType JIT_OPERATION operationLinkCall(ExecState* execCallee, CallLinkInfo* callLinkInfo) |
| { |
| ExecState* exec = execCallee->callerFrame(); |
| VM* vm = &exec->vm(); |
| auto throwScope = DECLARE_THROW_SCOPE(*vm); |
| |
| CodeSpecializationKind kind = callLinkInfo->specializationKind(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| RELEASE_ASSERT(!callLinkInfo->isDirect()); |
| |
| JSValue calleeAsValue = execCallee->guaranteedJSValueCallee(); |
| JSCell* calleeAsFunctionCell = getJSFunction(calleeAsValue); |
| if (!calleeAsFunctionCell) { |
| if (auto* internalFunction = jsDynamicCast<InternalFunction*>(*vm, calleeAsValue)) { |
| MacroAssemblerCodePtr<JSEntryPtrTag> codePtr = vm->getCTIInternalFunctionTrampolineFor(kind); |
| RELEASE_ASSERT(!!codePtr); |
| |
| if (!callLinkInfo->seenOnce()) |
| callLinkInfo->setSeen(); |
| else |
| linkFor(execCallee, *callLinkInfo, nullptr, internalFunction, codePtr); |
| |
| void* linkedTarget = codePtr.executableAddress(); |
| return encodeResult(linkedTarget, reinterpret_cast<void*>(callLinkInfo->callMode() == CallMode::Tail ? ReuseTheFrame : KeepTheFrame)); |
| } |
| throwScope.release(); |
| return handleHostCall(execCallee, calleeAsValue, callLinkInfo); |
| } |
| |
| JSFunction* callee = jsCast<JSFunction*>(calleeAsFunctionCell); |
| JSScope* scope = callee->scopeUnchecked(); |
| ExecutableBase* executable = callee->executable(); |
| |
| MacroAssemblerCodePtr<JSEntryPtrTag> codePtr; |
| CodeBlock* codeBlock = nullptr; |
| if (executable->isHostFunction()) |
| codePtr = executable->entrypointFor(kind, MustCheckArity); |
| else { |
| FunctionExecutable* functionExecutable = static_cast<FunctionExecutable*>(executable); |
| |
| auto handleThrowException = [&] () { |
| void* throwTarget = vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).retaggedCode<JSEntryPtrTag>().executableAddress(); |
| return encodeResult(throwTarget, reinterpret_cast<void*>(KeepTheFrame)); |
| }; |
| |
| if (!isCall(kind) && functionExecutable->constructAbility() == ConstructAbility::CannotConstruct) { |
| throwException(exec, throwScope, createNotAConstructorError(exec, callee)); |
| return handleThrowException(); |
| } |
| |
| CodeBlock** codeBlockSlot = execCallee->addressOfCodeBlock(); |
| JSObject* error = functionExecutable->prepareForExecution<FunctionExecutable>(*vm, callee, scope, kind, *codeBlockSlot); |
| EXCEPTION_ASSERT(throwScope.exception() == reinterpret_cast<Exception*>(error)); |
| if (error) |
| return handleThrowException(); |
| codeBlock = *codeBlockSlot; |
| 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)); |
| } |
| |
| void JIT_OPERATION operationLinkDirectCall(ExecState* exec, CallLinkInfo* callLinkInfo, JSFunction* callee) |
| { |
| VM* vm = &exec->vm(); |
| auto throwScope = DECLARE_THROW_SCOPE(*vm); |
| |
| CodeSpecializationKind kind = callLinkInfo->specializationKind(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| RELEASE_ASSERT(callLinkInfo->isDirect()); |
| |
| // This would happen if the executable died during GC but the CodeBlock did not die. That should |
| // not happen because the CodeBlock should have a weak reference to any executable it uses for |
| // this purpose. |
| RELEASE_ASSERT(callLinkInfo->executable()); |
| |
| // Having a CodeBlock indicates that this is linked. We shouldn't be taking this path if it's |
| // linked. |
| RELEASE_ASSERT(!callLinkInfo->codeBlock()); |
| |
| // We just don't support this yet. |
| RELEASE_ASSERT(!callLinkInfo->isVarargs()); |
| |
| ExecutableBase* executable = callLinkInfo->executable(); |
| RELEASE_ASSERT(callee->executable() == callLinkInfo->executable()); |
| |
| JSScope* scope = callee->scopeUnchecked(); |
| |
| MacroAssemblerCodePtr<JSEntryPtrTag> codePtr; |
| CodeBlock* codeBlock = nullptr; |
| if (executable->isHostFunction()) |
| codePtr = executable->entrypointFor(kind, MustCheckArity); |
| else { |
| FunctionExecutable* functionExecutable = static_cast<FunctionExecutable*>(executable); |
| |
| RELEASE_ASSERT(isCall(kind) || functionExecutable->constructAbility() != ConstructAbility::CannotConstruct); |
| |
| JSObject* error = functionExecutable->prepareForExecution<FunctionExecutable>(*vm, callee, scope, kind, codeBlock); |
| EXCEPTION_ASSERT_UNUSED(throwScope, throwScope.exception() == reinterpret_cast<Exception*>(error)); |
| if (error) |
| return; |
| unsigned argumentStackSlots = callLinkInfo->maxNumArguments(); |
| if (argumentStackSlots < static_cast<size_t>(codeBlock->numParameters())) |
| codePtr = functionExecutable->entrypointFor(kind, MustCheckArity); |
| else |
| codePtr = functionExecutable->entrypointFor(kind, ArityCheckNotRequired); |
| } |
| |
| linkDirectFor(exec, *callLinkInfo, codeBlock, codePtr); |
| } |
| |
| inline SlowPathReturnType virtualForWithFunction( |
| ExecState* execCallee, CallLinkInfo* callLinkInfo, JSCell*& calleeAsFunctionCell) |
| { |
| ExecState* exec = execCallee->callerFrame(); |
| VM* vm = &exec->vm(); |
| auto throwScope = DECLARE_THROW_SCOPE(*vm); |
| |
| CodeSpecializationKind kind = callLinkInfo->specializationKind(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| JSValue calleeAsValue = execCallee->guaranteedJSValueCallee(); |
| calleeAsFunctionCell = getJSFunction(calleeAsValue); |
| if (UNLIKELY(!calleeAsFunctionCell)) { |
| if (jsDynamicCast<InternalFunction*>(*vm, calleeAsValue)) { |
| MacroAssemblerCodePtr<JSEntryPtrTag> codePtr = vm->getCTIInternalFunctionTrampolineFor(kind); |
| ASSERT(!!codePtr); |
| return encodeResult(codePtr.executableAddress(), reinterpret_cast<void*>(callLinkInfo->callMode() == CallMode::Tail ? ReuseTheFrame : KeepTheFrame)); |
| } |
| throwScope.release(); |
| return handleHostCall(execCallee, calleeAsValue, callLinkInfo); |
| } |
| |
| JSFunction* function = jsCast<JSFunction*>(calleeAsFunctionCell); |
| JSScope* scope = function->scopeUnchecked(); |
| ExecutableBase* executable = function->executable(); |
| if (UNLIKELY(!executable->hasJITCodeFor(kind))) { |
| FunctionExecutable* functionExecutable = static_cast<FunctionExecutable*>(executable); |
| |
| if (!isCall(kind) && functionExecutable->constructAbility() == ConstructAbility::CannotConstruct) { |
| throwException(exec, throwScope, createNotAConstructorError(exec, function)); |
| return encodeResult( |
| vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).retaggedCode<JSEntryPtrTag>().executableAddress(), |
| reinterpret_cast<void*>(KeepTheFrame)); |
| } |
| |
| CodeBlock** codeBlockSlot = execCallee->addressOfCodeBlock(); |
| JSObject* error = functionExecutable->prepareForExecution<FunctionExecutable>(*vm, function, scope, kind, *codeBlockSlot); |
| EXCEPTION_ASSERT(throwScope.exception() == reinterpret_cast<Exception*>(error)); |
| if (error) { |
| return encodeResult( |
| vm->getCTIStub(throwExceptionFromCallSlowPathGenerator).retaggedCode<JSEntryPtrTag>().executableAddress(), |
| reinterpret_cast<void*>(KeepTheFrame)); |
| } |
| } |
| 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 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 = asString(left)->equal(exec, asString(right)); |
| #if USE(JSVALUE64) |
| return JSValue::encode(jsBoolean(result)); |
| #else |
| return result; |
| #endif |
| } |
| |
| size_t JIT_OPERATION operationCompareStrictEq(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| JSValue src1 = JSValue::decode(encodedOp1); |
| JSValue src2 = JSValue::decode(encodedOp2); |
| |
| return JSValue::strictEqual(exec, src1, src2); |
| } |
| |
| 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 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) |
| { |
| VM& vm = exec->vm(); |
| ASSERT(functionExecutable->inherits<FunctionExecutable>(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); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationNewAsyncFunction(ExecState* exec, JSScope* scope, JSCell* functionExecutable) |
| { |
| return operationNewFunctionCommon<JSAsyncFunction>(exec, scope, functionExecutable, false); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationNewAsyncFunctionWithInvalidatedReallocationWatchpoint(ExecState* exec, JSScope* scope, JSCell* functionExecutable) |
| { |
| return operationNewFunctionCommon<JSAsyncFunction>(exec, scope, functionExecutable, true); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationNewAsyncGeneratorFunction(ExecState* exec, JSScope* scope, JSCell* functionExecutable) |
| { |
| return operationNewFunctionCommon<JSAsyncGeneratorFunction>(exec, scope, functionExecutable, false); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationNewAsyncGeneratorFunctionWithInvalidatedReallocationWatchpoint(ExecState* exec, JSScope* scope, JSCell* functionExecutable) |
| { |
| return operationNewFunctionCommon<JSAsyncGeneratorFunction>(exec, scope, functionExecutable, true); |
| } |
| |
| void JIT_OPERATION operationSetFunctionName(ExecState* exec, JSCell* funcCell, EncodedJSValue encodedName) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| JSFunction* func = jsCast<JSFunction*>(funcCell); |
| JSValue name = JSValue::decode(encodedName); |
| func->setFunctionName(exec, name); |
| } |
| |
| JSCell* JIT_OPERATION operationNewObject(ExecState* exec, Structure* structure) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| return constructEmptyObject(exec, structure); |
| } |
| |
| JSCell* JIT_OPERATION operationNewRegexp(ExecState* exec, JSCell* regexpPtr) |
| { |
| SuperSamplerScope superSamplerScope(false); |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| |
| RegExp* regexp = static_cast<RegExp*>(regexpPtr); |
| ASSERT(regexp->isValid()); |
| return 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 CheckTraps |
| // 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 operationHandleTraps(ExecState* exec) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| ASSERT(vm.needTrapHandling()); |
| vm.handleTraps(exec); |
| return nullptr; |
| } |
| |
| void JIT_OPERATION operationDebug(ExecState* exec, int32_t debugHookType) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| |
| vm.interpreter->debug(exec, static_cast<DebugHookType>(debugHookType)); |
| } |
| |
| #if ENABLE(DFG_JIT) |
| static void updateAllPredictionsAndOptimizeAfterWarmUp(CodeBlock* codeBlock) |
| { |
| codeBlock->updateAllPredictions(); |
| codeBlock->optimizeAfterWarmUp(); |
| } |
| |
| SlowPathReturnType JIT_OPERATION operationOptimize(ExecState* exec, uint32_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 (UNLIKELY(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 (UNLIKELY(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_LOG_EVENT(codeBlock, "delayOptimizeToDFG", ("counter = ", codeBlock->jitExecuteCounter())); |
| codeBlock->updateAllPredictions(); |
| if (UNLIKELY(Options::verboseOSR())) |
| dataLog("Choosing not to optimize ", *codeBlock, " yet, because the threshold hasn't been reached.\n"); |
| return encodeResult(0, 0); |
| } |
| |
| Debugger* debugger = codeBlock->globalObject()->debugger(); |
| if (UNLIKELY(debugger && (debugger->isStepping() || codeBlock->baselineAlternative()->hasDebuggerRequests()))) { |
| CODEBLOCK_LOG_EVENT(codeBlock, "delayOptimizeToDFG", ("debugger is stepping or has requests")); |
| updateAllPredictionsAndOptimizeAfterWarmUp(codeBlock); |
| return encodeResult(0, 0); |
| } |
| |
| if (codeBlock->m_shouldAlwaysBeInlined) { |
| CODEBLOCK_LOG_EVENT(codeBlock, "delayOptimizeToDFG", ("should always be inlined")); |
| updateAllPredictionsAndOptimizeAfterWarmUp(codeBlock); |
| if (UNLIKELY(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) { |
| CODEBLOCK_LOG_EVENT(codeBlock, "delayOptimizeToDFG", ("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_LOG_EVENT(codeBlock, "delayOptimizeToDFG", ("compiled and failed")); |
| codeBlock->updateAllPredictions(); |
| if (UNLIKELY(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 (UNLIKELY(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()) { |
| CODEBLOCK_LOG_EVENT(codeBlock, "delayOptimizeToDFG", ("should reoptimize from loop now")); |
| if (UNLIKELY(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()) { |
| CODEBLOCK_LOG_EVENT(codeBlock, "delayOptimizeToDFG", ("insufficient profiling")); |
| if (UNLIKELY(Options::verboseOSR())) { |
| dataLog( |
| "Delaying optimization for ", *codeBlock, |
| " because of insufficient profiling.\n"); |
| } |
| return encodeResult(0, 0); |
| } |
| |
| if (UNLIKELY(Options::verboseOSR())) |
| dataLog("Triggering optimized compilation of ", *codeBlock, "\n"); |
| |
| unsigned numVarsWithValues; |
| if (bytecodeIndex) |
| numVarsWithValues = codeBlock->m_numCalleeLocals; |
| 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) { |
| CODEBLOCK_LOG_EVENT(codeBlock, "delayOptimizeToDFG", ("compilation failed")); |
| return encodeResult(0, 0); |
| } |
| } |
| |
| CodeBlock* optimizedCodeBlock = codeBlock->replacement(); |
| ASSERT(JITCode::isOptimizingJIT(optimizedCodeBlock->jitType())); |
| |
| if (void* dataBuffer = DFG::prepareOSREntry(exec, optimizedCodeBlock, bytecodeIndex)) { |
| CODEBLOCK_LOG_EVENT(optimizedCodeBlock, "osrEntry", ("at bc#", bytecodeIndex)); |
| if (UNLIKELY(Options::verboseOSR())) { |
| dataLog( |
| "Performing OSR ", *codeBlock, " -> ", *optimizedCodeBlock, ".\n"); |
| } |
| |
| codeBlock->optimizeSoon(); |
| codeBlock->unlinkedCodeBlock()->setDidOptimize(TrueTriState); |
| void* targetPC = vm.getCTIStub(DFG::osrEntryThunkGenerator).code().executableAddress(); |
| targetPC = retagCodePtr(targetPC, JITThunkPtrTag, bitwise_cast<PtrTag>(exec)); |
| return encodeResult(targetPC, dataBuffer); |
| } |
| |
| if (UNLIKELY(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()) { |
| CODEBLOCK_LOG_EVENT(codeBlock, "delayOptimizeToDFG", ("should reoptimize now")); |
| if (UNLIKELY(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(); |
| |
| CODEBLOCK_LOG_EVENT(codeBlock, "delayOptimizeToDFG", ("OSR failed")); |
| return encodeResult(0, 0); |
| } |
| |
| char* JIT_OPERATION operationTryOSREnterAtCatch(ExecState* exec, uint32_t bytecodeIndex) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| |
| CodeBlock* optimizedReplacement = exec->codeBlock()->replacement(); |
| switch (optimizedReplacement->jitType()) { |
| case JITCode::DFGJIT: |
| case JITCode::FTLJIT: { |
| MacroAssemblerCodePtr<ExceptionHandlerPtrTag> entry = DFG::prepareCatchOSREntry(exec, optimizedReplacement, bytecodeIndex); |
| return entry.executableAddress<char*>(); |
| } |
| default: |
| break; |
| } |
| return nullptr; |
| } |
| |
| char* JIT_OPERATION operationTryOSREnterAtCatchAndValueProfile(ExecState* exec, uint32_t bytecodeIndex) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| |
| CodeBlock* codeBlock = exec->codeBlock(); |
| CodeBlock* optimizedReplacement = codeBlock->replacement(); |
| |
| switch (optimizedReplacement->jitType()) { |
| case JITCode::DFGJIT: |
| case JITCode::FTLJIT: { |
| MacroAssemblerCodePtr<ExceptionHandlerPtrTag> entry = DFG::prepareCatchOSREntry(exec, optimizedReplacement, bytecodeIndex); |
| return entry.executableAddress<char*>(); |
| } |
| default: |
| break; |
| } |
| |
| codeBlock->ensureCatchLivenessIsComputedForBytecodeOffset(bytecodeIndex); |
| ValueProfileAndOperandBuffer* buffer = static_cast<ValueProfileAndOperandBuffer*>(codeBlock->instructions()[bytecodeIndex + 3].u.pointer); |
| buffer->forEach([&] (ValueProfileAndOperand& profile) { |
| profile.m_profile.m_buckets[0] = JSValue::encode(exec->uncheckedR(profile.m_operand).jsValue()); |
| }); |
| |
| return nullptr; |
| } |
| |
| #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) |
| { |
| VM& vm = exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| auto propertyKey = subscript.toPropertyKey(exec); |
| RETURN_IF_EXCEPTION(scope, void()); |
| |
| scope.release(); |
| 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(); |
| } |
| |
| 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 JSValue getByVal(ExecState* exec, JSValue baseValue, JSValue subscript, ByValInfo* byValInfo, ReturnAddressPtr returnAddress) |
| { |
| VM& vm = exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (LIKELY(baseValue.isCell() && subscript.isString())) { |
| 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, operationGetByValString); |
| scope.release(); |
| 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); |
| |
| bool skipMarkingOutOfBounds = false; |
| |
| if (object->indexingType() == ArrayWithContiguous && i < object->butterfly()->publicLength()) { |
| // FIXME: expand this to ArrayStorage, Int32, and maybe Double: |
| // https://bugs.webkit.org/show_bug.cgi?id=182940 |
| auto* globalObject = object->globalObject(); |
| skipMarkingOutOfBounds = globalObject->isOriginalArrayStructure(object->structure()) && globalObject->arrayPrototypeChainIsSane(); |
| } |
| |
| if (!skipMarkingOutOfBounds && !CommonSlowPaths::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(); |
| } |
| } |
| |
| scope.release(); |
| return baseValue.get(exec, i); |
| } |
| |
| baseValue.requireObjectCoercible(exec); |
| RETURN_IF_EXCEPTION(scope, JSValue()); |
| auto property = subscript.toPropertyKey(exec); |
| RETURN_IF_EXCEPTION(scope, JSValue()); |
| |
| ASSERT(exec->bytecodeOffset()); |
| if (byValInfo->stubInfo && (!isStringOrSymbol(subscript) || byValInfo->cachedId != property)) |
| byValInfo->tookSlowPath = true; |
| |
| scope.release(); |
| 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(); |
| ConcurrentJSLocker locker(codeBlock->m_lock); |
| byValInfo->arrayProfile->computeUpdatedPrediction(locker, codeBlock, structure); |
| |
| JIT::compileGetByVal(&vm, 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.isSymbol() || !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 { |
| CodeBlock* codeBlock = exec->codeBlock(); |
| ConcurrentJSLocker locker(codeBlock->m_lock); |
| byValInfo->seen = true; |
| byValInfo->cachedId = propertyName; |
| if (subscript.isSymbol()) |
| byValInfo->cachedSymbol.set(vm, codeBlock, asSymbol(subscript)); |
| 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, 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), operationHasIndexedPropertyGeneric); |
| } |
| } |
| |
| uint32_t index = subscript.asUInt32(); |
| if (object->canGetIndexQuickly(index)) |
| return JSValue::encode(JSValue(JSValue::JSTrue)); |
| |
| if (!CommonSlowPaths::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->hasPropertyGeneric(exec, index, PropertySlot::InternalMethodType::GetOwnProperty))); |
| } |
| |
| 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 (!CommonSlowPaths::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->hasPropertyGeneric(exec, subscript.asUInt32(), PropertySlot::InternalMethodType::GetOwnProperty))); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationGetByValString(ExecState* exec, EncodedJSValue encodedBase, EncodedJSValue encodedSubscript, ByValInfo* byValInfo) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| 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)) { |
| scope.release(); |
| return JSValue::encode(asString(baseValue)->getIndex(exec, i)); |
| } |
| result = baseValue.get(exec, i); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| if (!isJSString(baseValue)) { |
| ASSERT(exec->bytecodeOffset()); |
| auto getByValFunction = byValInfo->stubRoutine ? operationGetByValGeneric : operationGetByValOptimize; |
| ctiPatchCallByReturnAddress(ReturnAddressPtr(OUR_RETURN_ADDRESS), getByValFunction); |
| } |
| } else { |
| baseValue.requireObjectCoercible(exec); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| auto property = subscript.toPropertyKey(exec); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| scope.release(); |
| result = baseValue.get(exec, property); |
| } |
| |
| return JSValue::encode(result); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationDeleteByIdJSResult(ExecState* exec, EncodedJSValue base, UniquedStringImpl* uid) |
| { |
| return JSValue::encode(jsBoolean(operationDeleteById(exec, base, uid))); |
| } |
| |
| size_t JIT_OPERATION operationDeleteById(ExecState* exec, EncodedJSValue encodedBase, UniquedStringImpl* uid) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSObject* baseObj = JSValue::decode(encodedBase).toObject(exec); |
| RETURN_IF_EXCEPTION(scope, false); |
| if (!baseObj) |
| return false; |
| bool couldDelete = baseObj->methodTable(vm)->deleteProperty(baseObj, exec, Identifier::fromUid(&vm, uid)); |
| RETURN_IF_EXCEPTION(scope, false); |
| if (!couldDelete && exec->codeBlock()->isStrictMode()) |
| throwTypeError(exec, scope, ASCIILiteral(UnableToDeletePropertyError)); |
| return couldDelete; |
| } |
| |
| EncodedJSValue JIT_OPERATION operationDeleteByValJSResult(ExecState* exec, EncodedJSValue base, EncodedJSValue key) |
| { |
| return JSValue::encode(jsBoolean(operationDeleteByVal(exec, base, key))); |
| } |
| |
| size_t JIT_OPERATION operationDeleteByVal(ExecState* exec, EncodedJSValue encodedBase, EncodedJSValue encodedKey) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSObject* baseObj = JSValue::decode(encodedBase).toObject(exec); |
| RETURN_IF_EXCEPTION(scope, false); |
| JSValue key = JSValue::decode(encodedKey); |
| if (!baseObj) |
| return false; |
| |
| bool couldDelete; |
| uint32_t index; |
| if (key.getUInt32(index)) |
| couldDelete = baseObj->methodTable(vm)->deletePropertyByIndex(baseObj, exec, index); |
| else { |
| Identifier property = key.toPropertyKey(exec); |
| RETURN_IF_EXCEPTION(scope, false); |
| couldDelete = baseObj->methodTable(vm)->deleteProperty(baseObj, exec, property); |
| } |
| RETURN_IF_EXCEPTION(scope, false); |
| if (!couldDelete && exec->codeBlock()->isStrictMode()) |
| throwTypeError(exec, scope, ASCIILiteral(UnableToDeletePropertyError)); |
| return couldDelete; |
| } |
| |
| JSCell* JIT_OPERATION operationPushWithScope(ExecState* exec, JSCell* currentScopeCell, EncodedJSValue objectValue) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSObject* object = JSValue::decode(objectValue).toObject(exec); |
| RETURN_IF_EXCEPTION(scope, nullptr); |
| |
| JSScope* currentScope = jsCast<JSScope*>(currentScopeCell); |
| |
| return JSWithScope::create(vm, exec->lexicalGlobalObject(), currentScope, object); |
| } |
| |
| JSCell* JIT_OPERATION operationPushWithScopeObject(ExecState* exec, JSCell* currentScopeCell, JSObject* object) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| JSScope* currentScope = jsCast<JSScope*>(currentScopeCell); |
| return JSWithScope::create(vm, exec->lexicalGlobalObject(), currentScope, object); |
| } |
| |
| 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); |
| |
| bool result = JSObject::defaultHasInstance(exec, value, proto); |
| return JSValue::encode(jsBoolean(result)); |
| } |
| |
| int32_t JIT_OPERATION operationSizeFrameForForwardArguments(ExecState* exec, EncodedJSValue, int32_t numUsedStackSlots, int32_t) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| return sizeFrameForForwardArguments(exec, vm, numUsedStackSlots); |
| } |
| |
| int32_t JIT_OPERATION operationSizeFrameForVarargs(ExecState* exec, EncodedJSValue encodedArguments, int32_t numUsedStackSlots, int32_t firstVarArgOffset) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| JSValue arguments = JSValue::decode(encodedArguments); |
| return sizeFrameForVarargs(exec, vm, arguments, numUsedStackSlots, firstVarArgOffset); |
| } |
| |
| CallFrame* JIT_OPERATION operationSetupForwardArgumentsFrame(ExecState* exec, CallFrame* newCallFrame, EncodedJSValue, int32_t, int32_t length) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| setupForwardArgumentsFrame(exec, newCallFrame, length); |
| return newCallFrame; |
| } |
| |
| 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; |
| } |
| |
| 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(); |
| } |
| |
| assertIsTaggedWith(result, JSSwitchPtrTag); |
| 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(); |
| assertIsTaggedWith(result, JSSwitchPtrTag); |
| 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(); |
| |
| assertIsTaggedWith(result, JSSwitchPtrTag); |
| return reinterpret_cast<char*>(result); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationGetFromScope(ExecState* exec, Instruction* bytecodePC) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| auto throwScope = DECLARE_THROW_SCOPE(vm); |
| |
| 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); |
| |
| throwScope.release(); |
| return JSValue::encode(scope->getPropertySlot(exec, ident, [&] (bool found, PropertySlot& slot) -> JSValue { |
| if (!found) { |
| if (getPutInfo.resolveMode() == ThrowIfNotFound) |
| throwException(exec, throwScope, createUndefinedVariableError(exec, ident)); |
| return jsUndefined(); |
| } |
| |
| JSValue result = JSValue(); |
| if (scope->isGlobalLexicalEnvironment()) { |
| // 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()) { |
| throwException(exec, throwScope, createTDZError(exec)); |
| return jsUndefined(); |
| } |
| } |
| |
| CommonSlowPaths::tryCacheGetFromScopeGlobal(exec, vm, pc, scope, slot, ident); |
| |
| if (!result) |
| return slot.getValue(exec, ident); |
| return result; |
| })); |
| } |
| |
| void JIT_OPERATION operationPutToScope(ExecState* exec, Instruction* bytecodePC) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| auto throwScope = DECLARE_THROW_SCOPE(vm); |
| |
| 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(vm, "Executed op_put_scope<LocalClosureVar>"); |
| return; |
| } |
| |
| bool hasProperty = scope->hasProperty(exec, ident); |
| EXCEPTION_ASSERT(!throwScope.exception() || !hasProperty); |
| if (hasProperty |
| && scope->isGlobalLexicalEnvironment() |
| && !isInitialization(getPutInfo.initializationMode())) { |
| // 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()) { |
| throwException(exec, throwScope, createTDZError(exec)); |
| return; |
| } |
| } |
| |
| if (getPutInfo.resolveMode() == ThrowIfNotFound && !hasProperty) { |
| throwException(exec, throwScope, createUndefinedVariableError(exec, ident)); |
| return; |
| } |
| |
| PutPropertySlot slot(scope, codeBlock->isStrictMode(), PutPropertySlot::UnknownContext, isInitialization(getPutInfo.initializationMode())); |
| scope->methodTable(vm)->put(scope, exec, ident, value, slot); |
| |
| RETURN_IF_EXCEPTION(throwScope, void()); |
| |
| 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); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| |
| JSValue exceptionValue = JSValue::decode(encodedExceptionValue); |
| throwException(exec, scope, exceptionValue); |
| |
| // Results stored out-of-band in vm.targetMachinePCForThrow & vm.callFrameForCatch |
| genericUnwind(vm, exec); |
| } |
| |
| char* JIT_OPERATION operationReallocateButterflyToHavePropertyStorageWithInitialCapacity(ExecState* exec, JSObject* object) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| |
| ASSERT(!object->structure()->outOfLineCapacity()); |
| Butterfly* result = object->allocateMoreOutOfLineStorage(vm, 0, initialOutOfLineCapacity); |
| object->nukeStructureAndSetButterfly(vm, object->structureID(), result); |
| return reinterpret_cast<char*>(result); |
| } |
| |
| char* JIT_OPERATION operationReallocateButterflyToGrowPropertyStorage(ExecState* exec, JSObject* object, size_t newSize) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| |
| Butterfly* result = object->allocateMoreOutOfLineStorage(vm, object->structure()->outOfLineCapacity(), newSize); |
| object->nukeStructureAndSetButterfly(vm, object->structureID(), result); |
| return reinterpret_cast<char*>(result); |
| } |
| |
| void JIT_OPERATION operationOSRWriteBarrier(ExecState* exec, JSCell* cell) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| vm->heap.writeBarrier(cell); |
| } |
| |
| void JIT_OPERATION operationWriteBarrierSlowPath(ExecState* exec, JSCell* cell) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| vm->heap.writeBarrierSlowPath(cell); |
| } |
| |
| 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) |
| { |
| vm->topCallFrame = exec->callerFrame(); |
| 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); |
| auto scope = DECLARE_THROW_SCOPE(*vm); |
| UNUSED_PARAM(scope); |
| #if COMPILER(GCC_OR_CLANG) |
| void* returnPC = __builtin_return_address(0); |
| doExceptionFuzzing(exec, scope, "JITOperations", returnPC); |
| #endif // COMPILER(GCC_OR_CLANG) |
| } |
| |
| ALWAYS_INLINE static EncodedJSValue unprofiledAdd(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| JSValue op1 = JSValue::decode(encodedOp1); |
| JSValue op2 = JSValue::decode(encodedOp2); |
| |
| return JSValue::encode(jsAdd(exec, op1, op2)); |
| } |
| |
| ALWAYS_INLINE static EncodedJSValue profiledAdd(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, ArithProfile& arithProfile) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| JSValue op1 = JSValue::decode(encodedOp1); |
| JSValue op2 = JSValue::decode(encodedOp2); |
| |
| arithProfile.observeLHSAndRHS(op1, op2); |
| JSValue result = jsAdd(exec, op1, op2); |
| arithProfile.observeResult(result); |
| |
| return JSValue::encode(result); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueAdd(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) |
| { |
| return unprofiledAdd(exec, encodedOp1, encodedOp2); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueAddProfiled(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, ArithProfile* arithProfile) |
| { |
| ASSERT(arithProfile); |
| return profiledAdd(exec, encodedOp1, encodedOp2, *arithProfile); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueAddProfiledOptimize(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, JITAddIC* addIC) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| JSValue op1 = JSValue::decode(encodedOp1); |
| JSValue op2 = JSValue::decode(encodedOp2); |
| |
| ArithProfile* arithProfile = addIC->arithProfile(); |
| ASSERT(arithProfile); |
| arithProfile->observeLHSAndRHS(op1, op2); |
| auto nonOptimizeVariant = operationValueAddProfiledNoOptimize; |
| addIC->generateOutOfLine(exec->codeBlock(), nonOptimizeVariant); |
| |
| #if ENABLE(MATH_IC_STATS) |
| exec->codeBlock()->dumpMathICStats(); |
| #endif |
| |
| JSValue result = jsAdd(exec, op1, op2); |
| arithProfile->observeResult(result); |
| |
| return JSValue::encode(result); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueAddProfiledNoOptimize(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, JITAddIC* addIC) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| ArithProfile* arithProfile = addIC->arithProfile(); |
| ASSERT(arithProfile); |
| return profiledAdd(exec, encodedOp1, encodedOp2, *arithProfile); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueAddOptimize(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, JITAddIC* addIC) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| JSValue op1 = JSValue::decode(encodedOp1); |
| JSValue op2 = JSValue::decode(encodedOp2); |
| |
| auto nonOptimizeVariant = operationValueAddNoOptimize; |
| if (ArithProfile* arithProfile = addIC->arithProfile()) |
| arithProfile->observeLHSAndRHS(op1, op2); |
| addIC->generateOutOfLine(exec->codeBlock(), nonOptimizeVariant); |
| |
| #if ENABLE(MATH_IC_STATS) |
| exec->codeBlock()->dumpMathICStats(); |
| #endif |
| |
| return JSValue::encode(jsAdd(exec, op1, op2)); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueAddNoOptimize(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, JITAddIC*) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| JSValue op1 = JSValue::decode(encodedOp1); |
| JSValue op2 = JSValue::decode(encodedOp2); |
| |
| JSValue result = jsAdd(exec, op1, op2); |
| |
| return JSValue::encode(result); |
| } |
| |
| ALWAYS_INLINE static EncodedJSValue unprofiledMul(VM& vm, ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| JSValue op1 = JSValue::decode(encodedOp1); |
| JSValue op2 = JSValue::decode(encodedOp2); |
| |
| double a = op1.toNumber(exec); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| scope.release(); |
| double b = op2.toNumber(exec); |
| return JSValue::encode(jsNumber(a * b)); |
| } |
| |
| ALWAYS_INLINE static EncodedJSValue profiledMul(VM& vm, ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, ArithProfile& arithProfile, bool shouldObserveLHSAndRHSTypes = true) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| JSValue op1 = JSValue::decode(encodedOp1); |
| JSValue op2 = JSValue::decode(encodedOp2); |
| |
| if (shouldObserveLHSAndRHSTypes) |
| arithProfile.observeLHSAndRHS(op1, op2); |
| |
| double a = op1.toNumber(exec); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| double b = op2.toNumber(exec); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| |
| JSValue result = jsNumber(a * b); |
| arithProfile.observeResult(result); |
| return JSValue::encode(result); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueMul(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| return unprofiledMul(*vm, exec, encodedOp1, encodedOp2); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueMulNoOptimize(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, JITMulIC*) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| return unprofiledMul(*vm, exec, encodedOp1, encodedOp2); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueMulOptimize(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, JITMulIC* mulIC) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| auto nonOptimizeVariant = operationValueMulNoOptimize; |
| if (ArithProfile* arithProfile = mulIC->arithProfile()) |
| arithProfile->observeLHSAndRHS(JSValue::decode(encodedOp1), JSValue::decode(encodedOp2)); |
| mulIC->generateOutOfLine(exec->codeBlock(), nonOptimizeVariant); |
| |
| #if ENABLE(MATH_IC_STATS) |
| exec->codeBlock()->dumpMathICStats(); |
| #endif |
| |
| return unprofiledMul(*vm, exec, encodedOp1, encodedOp2); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueMulProfiled(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, ArithProfile* arithProfile) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| ASSERT(arithProfile); |
| return profiledMul(*vm, exec, encodedOp1, encodedOp2, *arithProfile); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueMulProfiledOptimize(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, JITMulIC* mulIC) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| ArithProfile* arithProfile = mulIC->arithProfile(); |
| ASSERT(arithProfile); |
| arithProfile->observeLHSAndRHS(JSValue::decode(encodedOp1), JSValue::decode(encodedOp2)); |
| auto nonOptimizeVariant = operationValueMulProfiledNoOptimize; |
| mulIC->generateOutOfLine(exec->codeBlock(), nonOptimizeVariant); |
| |
| #if ENABLE(MATH_IC_STATS) |
| exec->codeBlock()->dumpMathICStats(); |
| #endif |
| |
| return profiledMul(*vm, exec, encodedOp1, encodedOp2, *arithProfile, false); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueMulProfiledNoOptimize(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, JITMulIC* mulIC) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| ArithProfile* arithProfile = mulIC->arithProfile(); |
| ASSERT(arithProfile); |
| return profiledMul(*vm, exec, encodedOp1, encodedOp2, *arithProfile); |
| } |
| |
| ALWAYS_INLINE static EncodedJSValue unprofiledNegate(ExecState* exec, EncodedJSValue encodedOperand) |
| { |
| VM& vm = exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| NativeCallFrameTracer tracer(&vm, exec); |
| |
| JSValue operand = JSValue::decode(encodedOperand); |
| double number = operand.toNumber(exec); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| return JSValue::encode(jsNumber(-number)); |
| } |
| |
| ALWAYS_INLINE static EncodedJSValue profiledNegate(ExecState* exec, EncodedJSValue encodedOperand, ArithProfile& arithProfile) |
| { |
| VM& vm = exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| NativeCallFrameTracer tracer(&vm, exec); |
| |
| JSValue operand = JSValue::decode(encodedOperand); |
| arithProfile.observeLHS(operand); |
| double number = operand.toNumber(exec); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| |
| JSValue result = jsNumber(-number); |
| arithProfile.observeResult(result); |
| return JSValue::encode(result); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationArithNegate(ExecState* exec, EncodedJSValue operand) |
| { |
| return unprofiledNegate(exec, operand); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationArithNegateProfiled(ExecState* exec, EncodedJSValue operand, ArithProfile* arithProfile) |
| { |
| ASSERT(arithProfile); |
| return profiledNegate(exec, operand, *arithProfile); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationArithNegateProfiledOptimize(ExecState* exec, EncodedJSValue encodedOperand, JITNegIC* negIC) |
| { |
| VM& vm = exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| NativeCallFrameTracer tracer(&vm, exec); |
| |
| JSValue operand = JSValue::decode(encodedOperand); |
| |
| ArithProfile* arithProfile = negIC->arithProfile(); |
| ASSERT(arithProfile); |
| arithProfile->observeLHS(operand); |
| negIC->generateOutOfLine(exec->codeBlock(), operationArithNegateProfiled); |
| |
| #if ENABLE(MATH_IC_STATS) |
| exec->codeBlock()->dumpMathICStats(); |
| #endif |
| |
| double number = operand.toNumber(exec); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| JSValue result = jsNumber(-number); |
| arithProfile->observeResult(result); |
| return JSValue::encode(result); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationArithNegateOptimize(ExecState* exec, EncodedJSValue encodedOperand, JITNegIC* negIC) |
| { |
| VM& vm = exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| NativeCallFrameTracer tracer(&vm, exec); |
| |
| JSValue operand = JSValue::decode(encodedOperand); |
| |
| if (ArithProfile* arithProfile = negIC->arithProfile()) |
| arithProfile->observeLHS(operand); |
| negIC->generateOutOfLine(exec->codeBlock(), operationArithNegate); |
| |
| #if ENABLE(MATH_IC_STATS) |
| exec->codeBlock()->dumpMathICStats(); |
| #endif |
| |
| double number = operand.toNumber(exec); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| return JSValue::encode(jsNumber(-number)); |
| } |
| |
| ALWAYS_INLINE static EncodedJSValue unprofiledSub(VM& vm, ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| JSValue op1 = JSValue::decode(encodedOp1); |
| JSValue op2 = JSValue::decode(encodedOp2); |
| |
| double a = op1.toNumber(exec); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| scope.release(); |
| double b = op2.toNumber(exec); |
| return JSValue::encode(jsNumber(a - b)); |
| } |
| |
| ALWAYS_INLINE static EncodedJSValue profiledSub(VM& vm, ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, ArithProfile& arithProfile, bool shouldObserveLHSAndRHSTypes = true) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| JSValue op1 = JSValue::decode(encodedOp1); |
| JSValue op2 = JSValue::decode(encodedOp2); |
| |
| if (shouldObserveLHSAndRHSTypes) |
| arithProfile.observeLHSAndRHS(op1, op2); |
| |
| double a = op1.toNumber(exec); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| double b = op2.toNumber(exec); |
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); |
| |
| JSValue result = jsNumber(a - b); |
| arithProfile.observeResult(result); |
| return JSValue::encode(result); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueSub(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| return unprofiledSub(*vm, exec, encodedOp1, encodedOp2); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueSubProfiled(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, ArithProfile* arithProfile) |
| { |
| ASSERT(arithProfile); |
| |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| return profiledSub(*vm, exec, encodedOp1, encodedOp2, *arithProfile); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueSubOptimize(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, JITSubIC* subIC) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| auto nonOptimizeVariant = operationValueSubNoOptimize; |
| if (ArithProfile* arithProfile = subIC->arithProfile()) |
| arithProfile->observeLHSAndRHS(JSValue::decode(encodedOp1), JSValue::decode(encodedOp2)); |
| subIC->generateOutOfLine(exec->codeBlock(), nonOptimizeVariant); |
| |
| #if ENABLE(MATH_IC_STATS) |
| exec->codeBlock()->dumpMathICStats(); |
| #endif |
| |
| return unprofiledSub(*vm, exec, encodedOp1, encodedOp2); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueSubNoOptimize(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, JITSubIC*) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| return unprofiledSub(*vm, exec, encodedOp1, encodedOp2); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueSubProfiledOptimize(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, JITSubIC* subIC) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| ArithProfile* arithProfile = subIC->arithProfile(); |
| ASSERT(arithProfile); |
| arithProfile->observeLHSAndRHS(JSValue::decode(encodedOp1), JSValue::decode(encodedOp2)); |
| auto nonOptimizeVariant = operationValueSubProfiledNoOptimize; |
| subIC->generateOutOfLine(exec->codeBlock(), nonOptimizeVariant); |
| |
| #if ENABLE(MATH_IC_STATS) |
| exec->codeBlock()->dumpMathICStats(); |
| #endif |
| |
| return profiledSub(*vm, exec, encodedOp1, encodedOp2, *arithProfile, false); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationValueSubProfiledNoOptimize(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2, JITSubIC* subIC) |
| { |
| VM* vm = &exec->vm(); |
| NativeCallFrameTracer tracer(vm, exec); |
| |
| ArithProfile* arithProfile = subIC->arithProfile(); |
| ASSERT(arithProfile); |
| return profiledSub(*vm, exec, encodedOp1, encodedOp2, *arithProfile); |
| } |
| |
| void JIT_OPERATION operationProcessTypeProfilerLog(ExecState* exec) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| vm.typeProfilerLog()->processLogEntries(ASCIILiteral("Log Full, called from inside baseline JIT")); |
| } |
| |
| void JIT_OPERATION operationProcessShadowChickenLog(ExecState* exec) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| vm.shadowChicken().update(vm, exec); |
| } |
| |
| int32_t JIT_OPERATION operationCheckIfExceptionIsUncatchableAndNotifyProfiler(ExecState* exec) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| RELEASE_ASSERT(!!scope.exception()); |
| |
| if (isTerminatedExecutionException(vm, scope.exception())) { |
| genericUnwind(&vm, exec); |
| return 1; |
| } |
| 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(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) |