/* * Copyright (C) 2012-2019 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 "CodeCache.h" #include "BytecodeGenerator.h" #include "IndirectEvalExecutable.h" namespace JSC { void CodeCacheMap::pruneSlowCase() { m_minCapacity = std::max(m_size - m_sizeAtLastPrune, static_cast(0)); m_sizeAtLastPrune = m_size; m_timeAtLastPrune = MonotonicTime::now(); if (m_capacity < m_minCapacity) m_capacity = m_minCapacity; while (m_size > m_capacity || !canPruneQuickly()) { MapType::iterator it = m_map.begin(); writeCodeBlock(it->value.cell->vm(), it->key, it->value); m_size -= it->key.length(); m_map.remove(it); } } static void generateUnlinkedCodeBlockForFunctions(VM& vm, UnlinkedCodeBlock* unlinkedCodeBlock, const SourceCode& parentSource, OptionSet codeGenerationMode, ParserError& error) { auto generate = [&](UnlinkedFunctionExecutable* unlinkedExecutable, CodeSpecializationKind constructorKind) { if (constructorKind == CodeForConstruct && SourceParseModeSet(SourceParseMode::AsyncArrowFunctionMode, SourceParseMode::AsyncMethodMode, SourceParseMode::AsyncFunctionMode).contains(unlinkedExecutable->parseMode())) return; SourceCode source = unlinkedExecutable->linkedSourceCode(parentSource); UnlinkedFunctionCodeBlock* unlinkedFunctionCodeBlock = unlinkedExecutable->unlinkedCodeBlockFor(vm, source, constructorKind, codeGenerationMode, error, unlinkedExecutable->parseMode()); if (unlinkedFunctionCodeBlock) generateUnlinkedCodeBlockForFunctions(vm, unlinkedFunctionCodeBlock, source, codeGenerationMode, error); }; // FIXME: We should also generate CodeBlocks for CodeForConstruct // https://bugs.webkit.org/show_bug.cgi?id=193823 for (unsigned i = 0; i < unlinkedCodeBlock->numberOfFunctionDecls(); i++) generate(unlinkedCodeBlock->functionDecl(i), CodeForCall); for (unsigned i = 0; i < unlinkedCodeBlock->numberOfFunctionExprs(); i++) generate(unlinkedCodeBlock->functionExpr(i), CodeForCall); } template UnlinkedCodeBlockType* generateUnlinkedCodeBlockImpl(VM& vm, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet codeGenerationMode, ParserError& error, EvalContextType evalContextType, DerivedContextType derivedContextType, bool isArrowFunctionContext, const TDZEnvironment* variablesUnderTDZ = nullptr, const VariableEnvironment* parentScopePrivateNames = nullptr, ExecutableType* executable = nullptr) { typedef typename CacheTypes::RootNode RootNode; bool isInsideOrdinaryFunction = executable && executable->isInsideOrdinaryFunction(); std::unique_ptr rootNode = parse( vm, source, Identifier(), JSParserBuiltinMode::NotBuiltin, strictMode, scriptMode, CacheTypes::parseMode, SuperBinding::NotNeeded, error, nullptr, ConstructorKind::None, derivedContextType, evalContextType, nullptr, parentScopePrivateNames, nullptr, isInsideOrdinaryFunction); if (!rootNode) return nullptr; unsigned lineCount = rootNode->lastLine() - rootNode->firstLine(); unsigned startColumn = rootNode->startColumn() + 1; bool endColumnIsOnStartLine = !lineCount; unsigned unlinkedEndColumn = rootNode->endColumn(); unsigned endColumn = unlinkedEndColumn + (endColumnIsOnStartLine ? startColumn : 1); unsigned arrowContextFeature = isArrowFunctionContext ? ArrowFunctionContextFeature : 0; if (executable) executable->recordParse(rootNode->features() | arrowContextFeature, rootNode->hasCapturedVariables(), rootNode->lastLine(), endColumn); bool usesEval = rootNode->features() & EvalFeature; ECMAMode ecmaMode = rootNode->features() & StrictModeFeature ? ECMAMode::strict() : ECMAMode::sloppy(); NeedsClassFieldInitializer needsClassFieldInitializer = NeedsClassFieldInitializer::No; if constexpr (std::is_same_v) needsClassFieldInitializer = executable->needsClassFieldInitializer(); ExecutableInfo executableInfo(usesEval, false, false, ConstructorKind::None, scriptMode, SuperBinding::NotNeeded, CacheTypes::parseMode, derivedContextType, needsClassFieldInitializer, isArrowFunctionContext, false, evalContextType); UnlinkedCodeBlockType* unlinkedCodeBlock = UnlinkedCodeBlockType::create(vm, executableInfo, codeGenerationMode); unlinkedCodeBlock->recordParse(rootNode->features(), rootNode->hasCapturedVariables(), lineCount, unlinkedEndColumn); if (!source.provider()->sourceURLDirective().isNull()) unlinkedCodeBlock->setSourceURLDirective(source.provider()->sourceURLDirective()); if (!source.provider()->sourceMappingURLDirective().isNull()) unlinkedCodeBlock->setSourceMappingURLDirective(source.provider()->sourceMappingURLDirective()); RefPtr parentVariablesUnderTDZ; if (variablesUnderTDZ) parentVariablesUnderTDZ = TDZEnvironmentLink::create(vm.m_compactVariableMap->get(*variablesUnderTDZ), nullptr); error = BytecodeGenerator::generate(vm, rootNode.get(), source, unlinkedCodeBlock, codeGenerationMode, parentVariablesUnderTDZ, ecmaMode); if (error.isValid()) return nullptr; return unlinkedCodeBlock; } template UnlinkedCodeBlockType* generateUnlinkedCodeBlock(VM& vm, ExecutableType* executable, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet codeGenerationMode, ParserError& error, EvalContextType evalContextType, const TDZEnvironment* variablesUnderTDZ = nullptr, const VariableEnvironment* parentScopePrivateNames = nullptr) { return generateUnlinkedCodeBlockImpl(vm, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType, executable->derivedContextType(), executable->isArrowFunctionContext(), variablesUnderTDZ, parentScopePrivateNames, executable); } UnlinkedEvalCodeBlock* generateUnlinkedCodeBlockForDirectEval(VM& vm, DirectEvalExecutable* executable, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet codeGenerationMode, ParserError& error, EvalContextType evalContextType, const TDZEnvironment* variablesUnderTDZ, const VariableEnvironment* parentScopePrivateNames) { return generateUnlinkedCodeBlock(vm, executable, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType, variablesUnderTDZ, parentScopePrivateNames); } template std::enable_if_t::value, UnlinkedCodeBlockType*> recursivelyGenerateUnlinkedCodeBlock(VM& vm, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet codeGenerationMode, ParserError& error, EvalContextType evalContextType) { bool isArrowFunctionContext = false; UnlinkedCodeBlockType* unlinkedCodeBlock = generateUnlinkedCodeBlockImpl(vm, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType, DerivedContextType::None, isArrowFunctionContext); if (!unlinkedCodeBlock) return nullptr; generateUnlinkedCodeBlockForFunctions(vm, unlinkedCodeBlock, source, codeGenerationMode, error); return unlinkedCodeBlock; } UnlinkedProgramCodeBlock* recursivelyGenerateUnlinkedCodeBlockForProgram(VM& vm, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet codeGenerationMode, ParserError& error, EvalContextType evalContextType) { return recursivelyGenerateUnlinkedCodeBlock(vm, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType); } UnlinkedModuleProgramCodeBlock* recursivelyGenerateUnlinkedCodeBlockForModuleProgram(VM& vm, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet codeGenerationMode, ParserError& error, EvalContextType evalContextType) { return recursivelyGenerateUnlinkedCodeBlock(vm, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType); } template UnlinkedCodeBlockType* CodeCache::getUnlinkedGlobalCodeBlock(VM& vm, ExecutableType* executable, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet codeGenerationMode, ParserError& error, EvalContextType evalContextType) { DerivedContextType derivedContextType = executable->derivedContextType(); bool isArrowFunctionContext = executable->isArrowFunctionContext(); SourceCodeKey key( source, String(), CacheTypes::codeType, strictMode, scriptMode, derivedContextType, evalContextType, isArrowFunctionContext, codeGenerationMode, WTF::nullopt); UnlinkedCodeBlockType* unlinkedCodeBlock = m_sourceCode.findCacheAndUpdateAge(vm, key); if (unlinkedCodeBlock && Options::useCodeCache()) { unsigned lineCount = unlinkedCodeBlock->lineCount(); unsigned startColumn = unlinkedCodeBlock->startColumn() + source.startColumn().oneBasedInt(); bool endColumnIsOnStartLine = !lineCount; unsigned endColumn = unlinkedCodeBlock->endColumn() + (endColumnIsOnStartLine ? startColumn : 1); executable->recordParse(unlinkedCodeBlock->codeFeatures(), unlinkedCodeBlock->hasCapturedVariables(), source.firstLine().oneBasedInt() + lineCount, endColumn); if (unlinkedCodeBlock->sourceURLDirective()) source.provider()->setSourceURLDirective(unlinkedCodeBlock->sourceURLDirective()); if (unlinkedCodeBlock->sourceMappingURLDirective()) source.provider()->setSourceMappingURLDirective(unlinkedCodeBlock->sourceMappingURLDirective()); return unlinkedCodeBlock; } unlinkedCodeBlock = generateUnlinkedCodeBlock(vm, executable, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType); if (unlinkedCodeBlock && Options::useCodeCache()) { m_sourceCode.addCache(key, SourceCodeValue(vm, unlinkedCodeBlock, m_sourceCode.age())); key.source().provider().cacheBytecode([&] { return encodeCodeBlock(vm, key, unlinkedCodeBlock); }); } return unlinkedCodeBlock; } UnlinkedProgramCodeBlock* CodeCache::getUnlinkedProgramCodeBlock(VM& vm, ProgramExecutable* executable, const SourceCode& source, JSParserStrictMode strictMode, OptionSet codeGenerationMode, ParserError& error) { return getUnlinkedGlobalCodeBlock(vm, executable, source, strictMode, JSParserScriptMode::Classic, codeGenerationMode, error, EvalContextType::None); } UnlinkedEvalCodeBlock* CodeCache::getUnlinkedEvalCodeBlock(VM& vm, IndirectEvalExecutable* executable, const SourceCode& source, JSParserStrictMode strictMode, OptionSet codeGenerationMode, ParserError& error, EvalContextType evalContextType) { return getUnlinkedGlobalCodeBlock(vm, executable, source, strictMode, JSParserScriptMode::Classic, codeGenerationMode, error, evalContextType); } UnlinkedModuleProgramCodeBlock* CodeCache::getUnlinkedModuleProgramCodeBlock(VM& vm, ModuleProgramExecutable* executable, const SourceCode& source, OptionSet codeGenerationMode, ParserError& error) { return getUnlinkedGlobalCodeBlock(vm, executable, source, JSParserStrictMode::Strict, JSParserScriptMode::Module, codeGenerationMode, error, EvalContextType::None); } UnlinkedFunctionExecutable* CodeCache::getUnlinkedGlobalFunctionExecutable(VM& vm, const Identifier& name, const SourceCode& source, OptionSet codeGenerationMode, Optional functionConstructorParametersEndPosition, ParserError& error) { bool isArrowFunctionContext = false; SourceCodeKey key( source, name.string(), SourceCodeType::FunctionType, JSParserStrictMode::NotStrict, JSParserScriptMode::Classic, DerivedContextType::None, EvalContextType::None, isArrowFunctionContext, codeGenerationMode, functionConstructorParametersEndPosition); UnlinkedFunctionExecutable* executable = m_sourceCode.findCacheAndUpdateAge(vm, key); if (executable && Options::useCodeCache()) { if (!executable->sourceURLDirective().isNull()) source.provider()->setSourceURLDirective(executable->sourceURLDirective()); if (!executable->sourceMappingURLDirective().isNull()) source.provider()->setSourceMappingURLDirective(executable->sourceMappingURLDirective()); return executable; } JSTextPosition positionBeforeLastNewline; std::unique_ptr program = parseFunctionForFunctionConstructor(vm, source, error, &positionBeforeLastNewline, functionConstructorParametersEndPosition); if (!program) { RELEASE_ASSERT(error.isValid()); return nullptr; } // This function assumes an input string that would result in a single function declaration. StatementNode* funcDecl = program->singleStatement(); if (UNLIKELY(!funcDecl)) { JSToken token; error = ParserError(ParserError::SyntaxError, ParserError::SyntaxErrorIrrecoverable, token, "Parser error", -1); return nullptr; } ASSERT(funcDecl->isFuncDeclNode()); FunctionMetadataNode* metadata = static_cast(funcDecl)->metadata(); ASSERT(metadata); if (!metadata) return nullptr; metadata->overrideName(name); metadata->setEndPosition(positionBeforeLastNewline); // The Function constructor only has access to global variables, so no variables will be under TDZ unless they're // in the global lexical environment, which we always TDZ check accesses from. ConstructAbility constructAbility = constructAbilityForParseMode(metadata->parseMode()); UnlinkedFunctionExecutable* functionExecutable = UnlinkedFunctionExecutable::create(vm, source, metadata, UnlinkedNormalFunction, constructAbility, JSParserScriptMode::Classic, nullptr, DerivedContextType::None, NeedsClassFieldInitializer::No); if (!source.provider()->sourceURLDirective().isNull()) functionExecutable->setSourceURLDirective(source.provider()->sourceURLDirective()); if (!source.provider()->sourceMappingURLDirective().isNull()) functionExecutable->setSourceMappingURLDirective(source.provider()->sourceMappingURLDirective()); if (Options::useCodeCache()) m_sourceCode.addCache(key, SourceCodeValue(vm, functionExecutable, m_sourceCode.age())); return functionExecutable; } void CodeCache::updateCache(const UnlinkedFunctionExecutable* executable, const SourceCode& parentSource, CodeSpecializationKind kind, const UnlinkedFunctionCodeBlock* codeBlock) { parentSource.provider()->updateCache(executable, parentSource, kind, codeBlock); } void CodeCache::write(VM& vm) { for (auto& it : m_sourceCode) writeCodeBlock(vm, it.key, it.value); } void writeCodeBlock(VM& vm, const SourceCodeKey& key, const SourceCodeValue& value) { UnlinkedCodeBlock* codeBlock = jsDynamicCast(vm, value.cell.get()); if (!codeBlock) return; key.source().provider().commitCachedBytecode(); } static SourceCodeKey sourceCodeKeyForSerializedBytecode(VM&, const SourceCode& sourceCode, SourceCodeType codeType, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet codeGenerationMode) { return SourceCodeKey( sourceCode, String(), codeType, strictMode, scriptMode, DerivedContextType::None, EvalContextType::None, false, codeGenerationMode, WTF::nullopt); } SourceCodeKey sourceCodeKeyForSerializedProgram(VM& vm, const SourceCode& sourceCode) { JSParserStrictMode strictMode = JSParserStrictMode::NotStrict; JSParserScriptMode scriptMode = JSParserScriptMode::Classic; return sourceCodeKeyForSerializedBytecode(vm, sourceCode, SourceCodeType::ProgramType, strictMode, scriptMode, { }); } SourceCodeKey sourceCodeKeyForSerializedModule(VM& vm, const SourceCode& sourceCode) { JSParserStrictMode strictMode = JSParserStrictMode::Strict; JSParserScriptMode scriptMode = JSParserScriptMode::Module; return sourceCodeKeyForSerializedBytecode(vm, sourceCode, SourceCodeType::ModuleType, strictMode, scriptMode, { }); } RefPtr serializeBytecode(VM& vm, UnlinkedCodeBlock* codeBlock, const SourceCode& source, SourceCodeType codeType, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, FileSystem::PlatformFileHandle fd, BytecodeCacheError& error, OptionSet codeGenerationMode) { return encodeCodeBlock(vm, sourceCodeKeyForSerializedBytecode(vm, source, codeType, strictMode, scriptMode, codeGenerationMode), codeBlock, fd, error); } }