blob: 96c9573899380a2709fdfe42058be57fa1422c07 [file] [log] [blame]
/*
* Copyright (C) 2009-2022 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 "CodeBlock.h"
#include "Debugger.h"
#include "EvalCodeBlock.h"
#include "FunctionCodeBlock.h"
#include "FunctionExecutableInlines.h"
#include "GlobalExecutable.h"
#include "IsoCellSetInlines.h"
#include "JIT.h"
#include "JSCellInlines.h"
#include "JSGlobalObjectInlines.h"
#include "JSObjectInlines.h"
#include "JSTemplateObjectDescriptor.h"
#include "LLIntEntrypoint.h"
#include "ModuleProgramCodeBlock.h"
#include "ParserError.h"
#include "ProgramCodeBlock.h"
#include "VMInlines.h"
namespace JSC {
const ClassInfo ScriptExecutable::s_info = { "ScriptExecutable"_s, &ExecutableBase::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ScriptExecutable) };
ScriptExecutable::ScriptExecutable(Structure* structure, VM& vm, const SourceCode& source, LexicalScopeFeatures lexicalScopeFeatures, DerivedContextType derivedContextType, bool isInArrowFunctionContext, bool isInsideOrdinaryFunction, EvalContextType evalContextType, Intrinsic intrinsic)
: ExecutableBase(vm, structure)
, m_source(source)
, m_intrinsic(intrinsic)
, m_features(NoFeatures)
, m_lexicalScopeFeatures(lexicalScopeFeatures)
, m_hasCapturedVariables(false)
, m_neverInline(false)
, m_neverOptimize(false)
, m_neverFTLOptimize(false)
, m_isArrowFunctionContext(isInArrowFunctionContext)
, m_canUseOSRExitFuzzing(true)
, m_codeForGeneratorBodyWasGenerated(false)
, m_isInsideOrdinaryFunction(isInsideOrdinaryFunction)
, m_derivedContextType(static_cast<unsigned>(derivedContextType))
, m_evalContextType(static_cast<unsigned>(evalContextType))
{
}
void ScriptExecutable::destroy(JSCell* cell)
{
static_cast<ScriptExecutable*>(cell)->ScriptExecutable::~ScriptExecutable();
}
void ScriptExecutable::clearCode(IsoCellSet& clearableCodeSet)
{
m_jitCodeForCall = nullptr;
m_jitCodeForConstruct = nullptr;
m_jitCodeForCallWithArityCheck = MacroAssemblerCodePtr<JSEntryPtrTag>();
m_jitCodeForConstructWithArityCheck = MacroAssemblerCodePtr<JSEntryPtrTag>();
switch (type()) {
case FunctionExecutableType: {
FunctionExecutable* executable = static_cast<FunctionExecutable*>(this);
executable->m_codeBlockForCall.clear();
executable->m_codeBlockForConstruct.clear();
break;
}
case EvalExecutableType: {
EvalExecutable* executable = static_cast<EvalExecutable*>(this);
executable->m_codeBlock.clear();
executable->m_unlinkedCodeBlock.clear();
break;
}
case ProgramExecutableType: {
ProgramExecutable* executable = static_cast<ProgramExecutable*>(this);
executable->m_codeBlock.clear();
executable->m_unlinkedCodeBlock.clear();
break;
}
case ModuleProgramExecutableType: {
ModuleProgramExecutable* executable = static_cast<ModuleProgramExecutable*>(this);
executable->m_codeBlock.clear();
executable->m_unlinkedCodeBlock.clear();
executable->m_moduleEnvironmentSymbolTable.clear();
break;
}
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
ASSERT(&Heap::ScriptExecutableSpaceAndSets::clearableCodeSetFor(*subspace()) == &clearableCodeSet);
clearableCodeSet.remove(this);
}
void ScriptExecutable::installCode(CodeBlock* codeBlock)
{
installCode(codeBlock->vm(), codeBlock, codeBlock->codeType(), codeBlock->specializationKind());
}
void ScriptExecutable::installCode(VM& vm, CodeBlock* genericCodeBlock, CodeType codeType, CodeSpecializationKind kind)
{
if (genericCodeBlock)
CODEBLOCK_LOG_EVENT(genericCodeBlock, "installCode", ());
CodeBlock* oldCodeBlock = nullptr;
switch (codeType) {
case GlobalCode: {
ProgramExecutable* executable = jsCast<ProgramExecutable*>(this);
ProgramCodeBlock* codeBlock = static_cast<ProgramCodeBlock*>(genericCodeBlock);
ASSERT(kind == CodeForCall);
oldCodeBlock = executable->replaceCodeBlockWith(vm, codeBlock);
break;
}
case ModuleCode: {
ModuleProgramExecutable* executable = jsCast<ModuleProgramExecutable*>(this);
ModuleProgramCodeBlock* codeBlock = static_cast<ModuleProgramCodeBlock*>(genericCodeBlock);
ASSERT(kind == CodeForCall);
oldCodeBlock = executable->replaceCodeBlockWith(vm, codeBlock);
break;
}
case EvalCode: {
EvalExecutable* executable = jsCast<EvalExecutable*>(this);
EvalCodeBlock* codeBlock = static_cast<EvalCodeBlock*>(genericCodeBlock);
ASSERT(kind == CodeForCall);
oldCodeBlock = executable->replaceCodeBlockWith(vm, codeBlock);
break;
}
case FunctionCode: {
FunctionExecutable* executable = jsCast<FunctionExecutable*>(this);
FunctionCodeBlock* codeBlock = static_cast<FunctionCodeBlock*>(genericCodeBlock);
oldCodeBlock = executable->replaceCodeBlockWith(vm, kind, codeBlock);
break;
}
}
switch (kind) {
case CodeForCall:
m_jitCodeForCall = genericCodeBlock ? genericCodeBlock->jitCode() : nullptr;
m_jitCodeForCallWithArityCheck = nullptr;
break;
case CodeForConstruct:
m_jitCodeForConstruct = genericCodeBlock ? genericCodeBlock->jitCode() : nullptr;
m_jitCodeForConstructWithArityCheck = nullptr;
break;
}
auto& clearableCodeSet = Heap::ScriptExecutableSpaceAndSets::clearableCodeSetFor(*subspace());
if (hasClearableCode())
clearableCodeSet.add(this);
else
clearableCodeSet.remove(this);
if (genericCodeBlock) {
RELEASE_ASSERT(genericCodeBlock->ownerExecutable() == this);
RELEASE_ASSERT(JITCode::isExecutableScript(genericCodeBlock->jitType()));
genericCodeBlock->m_isJettisoned = false;
dataLogLnIf(Options::verboseOSR(), "Installing ", *genericCodeBlock);
if (UNLIKELY(vm.m_perBytecodeProfiler))
vm.m_perBytecodeProfiler->ensureBytecodesFor(genericCodeBlock);
Debugger* debugger = genericCodeBlock->globalObject()->debugger();
if (UNLIKELY(debugger))
debugger->registerCodeBlock(genericCodeBlock);
}
if (oldCodeBlock)
oldCodeBlock->unlinkIncomingCalls();
vm.writeBarrier(this);
}
bool ScriptExecutable::hasClearableCode() const
{
if (m_jitCodeForCall
|| m_jitCodeForConstruct
|| m_jitCodeForCallWithArityCheck
|| m_jitCodeForConstructWithArityCheck)
return true;
if (structure()->classInfoForCells() == FunctionExecutable::info()) {
auto* executable = static_cast<const FunctionExecutable*>(this);
if (executable->eitherCodeBlock())
return true;
} else if (structure()->classInfoForCells() == EvalExecutable::info()) {
auto* executable = static_cast<const EvalExecutable*>(this);
if (executable->m_codeBlock || executable->m_unlinkedCodeBlock)
return true;
} else if (structure()->classInfoForCells() == ProgramExecutable::info()) {
auto* executable = static_cast<const ProgramExecutable*>(this);
if (executable->m_codeBlock || executable->m_unlinkedCodeBlock)
return true;
} else if (structure()->classInfoForCells() == ModuleProgramExecutable::info()) {
auto* executable = static_cast<const ModuleProgramExecutable*>(this);
if (executable->m_codeBlock
|| executable->m_unlinkedCodeBlock
|| executable->m_moduleEnvironmentSymbolTable)
return true;
}
return false;
}
CodeBlock* ScriptExecutable::newCodeBlockFor(CodeSpecializationKind kind, JSFunction* function, JSScope* scope)
{
VM& vm = scope->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
ASSERT(vm.heap.isDeferred());
ASSERT(endColumn() != UINT_MAX);
JSGlobalObject* globalObject = scope->globalObject();
if (classInfo() == EvalExecutable::info()) {
EvalExecutable* executable = jsCast<EvalExecutable*>(this);
RELEASE_ASSERT(kind == CodeForCall);
RELEASE_ASSERT(!executable->m_codeBlock);
RELEASE_ASSERT(!function);
RELEASE_AND_RETURN(throwScope, EvalCodeBlock::create(vm, executable, executable->unlinkedCodeBlock(), scope));
}
if (classInfo() == ProgramExecutable::info()) {
ProgramExecutable* executable = jsCast<ProgramExecutable*>(this);
RELEASE_ASSERT(kind == CodeForCall);
RELEASE_ASSERT(!executable->m_codeBlock);
RELEASE_ASSERT(!function);
RELEASE_AND_RETURN(throwScope, ProgramCodeBlock::create(vm, executable, executable->unlinkedCodeBlock(), scope));
}
if (classInfo() == ModuleProgramExecutable::info()) {
ModuleProgramExecutable* executable = jsCast<ModuleProgramExecutable*>(this);
RELEASE_ASSERT(kind == CodeForCall);
RELEASE_ASSERT(!executable->m_codeBlock);
RELEASE_ASSERT(!function);
RELEASE_AND_RETURN(throwScope, ModuleProgramCodeBlock::create(vm, executable, executable->unlinkedCodeBlock(), scope));
}
RELEASE_ASSERT(classInfo() == FunctionExecutable::info());
RELEASE_ASSERT(function);
FunctionExecutable* executable = jsCast<FunctionExecutable*>(this);
RELEASE_ASSERT(!executable->codeBlockFor(kind));
ParserError error;
OptionSet<CodeGenerationMode> codeGenerationMode = globalObject->defaultCodeGenerationMode();
// We continue using the same CodeGenerationMode for Generators because live generator objects can
// keep the state which is only valid with the CodeBlock compiled with the same CodeGenerationMode.
if (isGeneratorOrAsyncFunctionBodyParseMode(executable->parseMode())) {
if (!m_codeForGeneratorBodyWasGenerated) {
m_codeGenerationModeForGeneratorBody = codeGenerationMode;
m_codeForGeneratorBodyWasGenerated = true;
} else
codeGenerationMode = m_codeGenerationModeForGeneratorBody;
}
UnlinkedFunctionCodeBlock* unlinkedCodeBlock =
executable->m_unlinkedExecutable->unlinkedCodeBlockFor(
vm, executable->source(), kind, codeGenerationMode, error,
executable->parseMode());
recordParse(
executable->m_unlinkedExecutable->features(),
executable->m_unlinkedExecutable->lexicalScopeFeatures(),
executable->m_unlinkedExecutable->hasCapturedVariables(),
lastLine(), endColumn());
if (!unlinkedCodeBlock) {
throwException(globalObject, throwScope, error.toErrorObject(globalObject, executable->source()));
return nullptr;
}
RELEASE_AND_RETURN(throwScope, FunctionCodeBlock::create(vm, executable, unlinkedCodeBlock, scope));
}
CodeBlock* ScriptExecutable::newReplacementCodeBlockFor(
CodeSpecializationKind kind)
{
VM& vm = this->vm();
if (classInfo() == EvalExecutable::info()) {
RELEASE_ASSERT(kind == CodeForCall);
EvalExecutable* executable = jsCast<EvalExecutable*>(this);
EvalCodeBlock* baseline = static_cast<EvalCodeBlock*>(
executable->codeBlock()->baselineVersion());
EvalCodeBlock* result = EvalCodeBlock::create(vm,
CodeBlock::CopyParsedBlock, *baseline);
result->setAlternative(vm, baseline);
return result;
}
if (classInfo() == ProgramExecutable::info()) {
RELEASE_ASSERT(kind == CodeForCall);
ProgramExecutable* executable = jsCast<ProgramExecutable*>(this);
ProgramCodeBlock* baseline = static_cast<ProgramCodeBlock*>(
executable->codeBlock()->baselineVersion());
ProgramCodeBlock* result = ProgramCodeBlock::create(vm,
CodeBlock::CopyParsedBlock, *baseline);
result->setAlternative(vm, baseline);
return result;
}
if (classInfo() == ModuleProgramExecutable::info()) {
RELEASE_ASSERT(kind == CodeForCall);
ModuleProgramExecutable* executable = jsCast<ModuleProgramExecutable*>(this);
ModuleProgramCodeBlock* baseline = static_cast<ModuleProgramCodeBlock*>(
executable->codeBlock()->baselineVersion());
ModuleProgramCodeBlock* result = ModuleProgramCodeBlock::create(vm,
CodeBlock::CopyParsedBlock, *baseline);
result->setAlternative(vm, baseline);
return result;
}
RELEASE_ASSERT(classInfo() == FunctionExecutable::info());
FunctionExecutable* executable = jsCast<FunctionExecutable*>(this);
FunctionCodeBlock* baseline = static_cast<FunctionCodeBlock*>(
executable->codeBlockFor(kind)->baselineVersion());
FunctionCodeBlock* result = FunctionCodeBlock::create(vm,
CodeBlock::CopyParsedBlock, *baseline);
result->setAlternative(vm, baseline);
return result;
}
static void setupLLInt(CodeBlock* codeBlock)
{
LLInt::setEntrypoint(codeBlock);
}
static void setupJIT(VM& vm, CodeBlock* codeBlock)
{
#if ENABLE(JIT)
CompilationResult result = JIT::compile(vm, codeBlock, JITCompilationMustSucceed);
RELEASE_ASSERT(result == CompilationSuccessful);
#else
UNUSED_PARAM(vm);
UNUSED_PARAM(codeBlock);
UNREACHABLE_FOR_PLATFORM();
#endif
}
void ScriptExecutable::prepareForExecutionImpl(VM& vm, JSFunction* function, JSScope* scope, CodeSpecializationKind kind, CodeBlock*& resultCodeBlock)
{
auto throwScope = DECLARE_THROW_SCOPE(vm);
DeferGCForAWhile deferGC(vm);
if (UNLIKELY(vm.getAndClearFailNextNewCodeBlock())) {
JSGlobalObject* globalObject = scope->globalObject();
throwException(globalObject, throwScope, createError(globalObject, "Forced Failure"_s));
return;
}
CodeBlock* codeBlock = newCodeBlockFor(kind, function, scope);
RETURN_IF_EXCEPTION(throwScope, void());
ASSERT(codeBlock);
resultCodeBlock = codeBlock;
if (Options::validateBytecode())
codeBlock->validate();
bool installedUnlinkedBaselineCode = false;
#if ENABLE(JIT)
if (RefPtr<BaselineJITCode> baselineRef = codeBlock->unlinkedCodeBlock()->m_unlinkedBaselineCode) {
codeBlock->setupWithUnlinkedBaselineCode(baselineRef.releaseNonNull());
installedUnlinkedBaselineCode = true;
}
#endif
if (!installedUnlinkedBaselineCode) {
if (Options::useLLInt())
setupLLInt(codeBlock);
else
setupJIT(vm, codeBlock);
}
installCode(vm, codeBlock, codeBlock->codeType(), codeBlock->specializationKind());
}
ScriptExecutable* ScriptExecutable::topLevelExecutable()
{
switch (type()) {
case FunctionExecutableType:
return jsCast<FunctionExecutable*>(this)->topLevelExecutable();
default:
return this;
}
}
JSArray* ScriptExecutable::createTemplateObject(JSGlobalObject* globalObject, JSTemplateObjectDescriptor* descriptor)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
TemplateObjectMap& templateObjectMap = ensureTemplateObjectMap(vm);
TemplateObjectMap::AddResult result;
{
Locker locker { cellLock() };
result = templateObjectMap.add(descriptor->endOffset(), WriteBarrier<JSArray>());
}
if (JSArray* array = result.iterator->value.get())
return array;
JSArray* templateObject = descriptor->createTemplateObject(globalObject);
RETURN_IF_EXCEPTION(scope, nullptr);
result.iterator->value.set(vm, this, templateObject);
return templateObject;
}
auto ScriptExecutable::ensureTemplateObjectMapImpl(std::unique_ptr<TemplateObjectMap>& dest) -> TemplateObjectMap&
{
if (dest)
return *dest;
auto result = makeUnique<TemplateObjectMap>();
WTF::storeStoreFence();
dest = WTFMove(result);
return *dest;
}
auto ScriptExecutable::ensureTemplateObjectMap(VM& vm) -> TemplateObjectMap&
{
switch (type()) {
case FunctionExecutableType:
return static_cast<FunctionExecutable*>(this)->ensureTemplateObjectMap(vm);
case EvalExecutableType:
return static_cast<EvalExecutable*>(this)->ensureTemplateObjectMap(vm);
case ProgramExecutableType:
return static_cast<ProgramExecutable*>(this)->ensureTemplateObjectMap(vm);
case ModuleProgramExecutableType:
default:
ASSERT(type() == ModuleProgramExecutableType);
return static_cast<ModuleProgramExecutable*>(this)->ensureTemplateObjectMap(vm);
}
}
CodeBlockHash ScriptExecutable::hashFor(CodeSpecializationKind kind) const
{
return CodeBlockHash(source(), kind);
}
std::optional<int> ScriptExecutable::overrideLineNumber(VM&) const
{
if (inherits<FunctionExecutable>())
return jsCast<const FunctionExecutable*>(this)->overrideLineNumber();
return std::nullopt;
}
unsigned ScriptExecutable::typeProfilingStartOffset(VM& vm) const
{
if (inherits<FunctionExecutable>())
return jsCast<const FunctionExecutable*>(this)->typeProfilingStartOffset(vm);
if (inherits<EvalExecutable>())
return UINT_MAX;
return 0;
}
unsigned ScriptExecutable::typeProfilingEndOffset(VM& vm) const
{
if (inherits<FunctionExecutable>())
return jsCast<const FunctionExecutable*>(this)->typeProfilingEndOffset(vm);
if (inherits<EvalExecutable>())
return UINT_MAX;
return source().length() - 1;
}
void ScriptExecutable::recordParse(CodeFeatures features, LexicalScopeFeatures lexicalScopeFeatures, bool hasCapturedVariables, int lastLine, unsigned endColumn)
{
switch (type()) {
case FunctionExecutableType:
// Since UnlinkedFunctionExecutable holds the information to calculate lastLine and endColumn, we do not need to remember them in ScriptExecutable's fields.
jsCast<FunctionExecutable*>(this)->recordParse(features, lexicalScopeFeatures, hasCapturedVariables);
return;
default:
jsCast<GlobalExecutable*>(this)->recordParse(features, lexicalScopeFeatures, hasCapturedVariables, lastLine, endColumn);
return;
}
}
int ScriptExecutable::lastLine() const
{
switch (type()) {
case FunctionExecutableType:
return jsCast<const FunctionExecutable*>(this)->lastLine();
default:
return jsCast<const GlobalExecutable*>(this)->lastLine();
}
return 0;
}
unsigned ScriptExecutable::endColumn() const
{
switch (type()) {
case FunctionExecutableType:
return jsCast<const FunctionExecutable*>(this)->endColumn();
default:
return jsCast<const GlobalExecutable*>(this)->endColumn();
}
return 0;
}
template<typename Visitor>
void ScriptExecutable::runConstraint(const ConcurrentJSLocker& locker, Visitor& visitor, CodeBlock* codeBlock)
{
ASSERT(codeBlock);
codeBlock->propagateTransitions(locker, visitor);
codeBlock->determineLiveness(locker, visitor);
}
template void ScriptExecutable::runConstraint(const ConcurrentJSLocker&, AbstractSlotVisitor&, CodeBlock*);
template void ScriptExecutable::runConstraint(const ConcurrentJSLocker&, SlotVisitor&, CodeBlock*);
template<typename Visitor>
void ScriptExecutable::visitCodeBlockEdge(Visitor& visitor, CodeBlock* codeBlock)
{
ASSERT(codeBlock);
ConcurrentJSLocker locker(codeBlock->m_lock);
if (codeBlock->shouldVisitStrongly(locker, visitor))
visitor.appendUnbarriered(codeBlock);
if (JITCode::isOptimizingJIT(codeBlock->jitType())) {
// If we jettison ourselves we'll install our alternative, so make sure that it
// survives GC even if we don't.
visitor.append(codeBlock->m_alternative);
}
// NOTE: There are two sides to this constraint, with different requirements for correctness.
// Because everything is ultimately protected with weak references and jettisoning, it's
// always "OK" to claim that something is dead prematurely and it's "OK" to keep things alive.
// But both choices could lead to bad perf - either recomp cycles or leaks.
//
// Determining CodeBlock liveness: This part is the most consequential. We want to keep the
// output constraint active so long as we think that we may yet prove that the CodeBlock is
// live but we haven't done it yet.
//
// Marking Structures if profitable: It's important that we do a pass of this. Logically, this
// seems like it is a constraint of CodeBlock. But we have always first run this as a result
// of the edge being marked even before we determine the liveness of the CodeBlock. This
// allows a CodeBlock to mark itself by first proving that all of the Structures it weakly
// depends on could be strongly marked. (This part is also called propagateTransitions.)
//
// As a weird caveat, we only fixpoint the constraints so long as the CodeBlock is not live.
// This means that we may overlook structure marking opportunities created by other marking
// that happens after the CodeBlock is marked. This was an accidental policy decision from a
// long time ago, but it is probably OK, since it's only worthwhile to keep fixpointing the
// structure marking if we still have unmarked structures after the first round. We almost
// never will because we will mark-if-profitable based on the owning global object being
// already marked. We mark it just in case that hadn't happened yet. And if the CodeBlock is
// not yet marked because it weakly depends on a structure that we did not yet mark, then we
// will keep fixpointing until the end.
visitor.appendUnbarriered(codeBlock->globalObject());
runConstraint(locker, visitor, codeBlock);
}
template void ScriptExecutable::visitCodeBlockEdge(AbstractSlotVisitor&, CodeBlock*);
template void ScriptExecutable::visitCodeBlockEdge(SlotVisitor&, CodeBlock*);
} // namespace JSC