| 1 | /* |
| 2 | * Copyright (C) 2012, 2016 Apple Inc. All Rights Reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| 14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "CodeCache.h" |
| 28 | |
| 29 | #include "IndirectEvalExecutable.h" |
| 30 | #include <wtf/text/StringConcatenateNumbers.h> |
| 31 | |
| 32 | namespace JSC { |
| 33 | |
| 34 | const Seconds CodeCacheMap::workingSetTime = 10_s; |
| 35 | |
| 36 | void CodeCacheMap::pruneSlowCase() |
| 37 | { |
| 38 | m_minCapacity = std::max(m_size - m_sizeAtLastPrune, static_cast<int64_t>(0)); |
| 39 | m_sizeAtLastPrune = m_size; |
| 40 | m_timeAtLastPrune = MonotonicTime::now(); |
| 41 | |
| 42 | if (m_capacity < m_minCapacity) |
| 43 | m_capacity = m_minCapacity; |
| 44 | |
| 45 | while (m_size > m_capacity || !canPruneQuickly()) { |
| 46 | MapType::iterator it = m_map.begin(); |
| 47 | |
| 48 | writeCodeBlock(*it->value.cell->vm(), it->key, it->value); |
| 49 | |
| 50 | m_size -= it->key.length(); |
| 51 | m_map.remove(it); |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | template <class UnlinkedCodeBlockType, class ExecutableType> |
| 56 | UnlinkedCodeBlockType* CodeCache::getUnlinkedGlobalCodeBlock(VM& vm, ExecutableType* executable, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, EvalContextType evalContextType) |
| 57 | { |
| 58 | DerivedContextType derivedContextType = executable->derivedContextType(); |
| 59 | bool isArrowFunctionContext = executable->isArrowFunctionContext(); |
| 60 | SourceCodeKey key( |
| 61 | source, String(), CacheTypes<UnlinkedCodeBlockType>::codeType, strictMode, scriptMode, |
| 62 | derivedContextType, evalContextType, isArrowFunctionContext, codeGenerationMode, |
| 63 | WTF::nullopt); |
| 64 | UnlinkedCodeBlockType* unlinkedCodeBlock = m_sourceCode.findCacheAndUpdateAge<UnlinkedCodeBlockType>(vm, key); |
| 65 | if (unlinkedCodeBlock && Options::useCodeCache()) { |
| 66 | unsigned lineCount = unlinkedCodeBlock->lineCount(); |
| 67 | unsigned startColumn = unlinkedCodeBlock->startColumn() + source.startColumn().oneBasedInt(); |
| 68 | bool endColumnIsOnStartLine = !lineCount; |
| 69 | unsigned endColumn = unlinkedCodeBlock->endColumn() + (endColumnIsOnStartLine ? startColumn : 1); |
| 70 | executable->recordParse(unlinkedCodeBlock->codeFeatures(), unlinkedCodeBlock->hasCapturedVariables(), source.firstLine().oneBasedInt() + lineCount, endColumn); |
| 71 | if (!unlinkedCodeBlock->sourceURLDirective().isNull()) |
| 72 | source.provider()->setSourceURLDirective(unlinkedCodeBlock->sourceURLDirective()); |
| 73 | if (!unlinkedCodeBlock->sourceMappingURLDirective().isNull()) |
| 74 | source.provider()->setSourceMappingURLDirective(unlinkedCodeBlock->sourceMappingURLDirective()); |
| 75 | return unlinkedCodeBlock; |
| 76 | } |
| 77 | |
| 78 | VariableEnvironment variablesUnderTDZ; |
| 79 | unlinkedCodeBlock = generateUnlinkedCodeBlock<UnlinkedCodeBlockType, ExecutableType>(vm, executable, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType, &variablesUnderTDZ); |
| 80 | |
| 81 | if (unlinkedCodeBlock && Options::useCodeCache()) { |
| 82 | m_sourceCode.addCache(key, SourceCodeValue(vm, unlinkedCodeBlock, m_sourceCode.age())); |
| 83 | |
| 84 | key.source().provider().cacheBytecode([&] { |
| 85 | return encodeCodeBlock(vm, key, unlinkedCodeBlock); |
| 86 | }); |
| 87 | } |
| 88 | |
| 89 | return unlinkedCodeBlock; |
| 90 | } |
| 91 | |
| 92 | UnlinkedProgramCodeBlock* CodeCache::getUnlinkedProgramCodeBlock(VM& vm, ProgramExecutable* executable, const SourceCode& source, JSParserStrictMode strictMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error) |
| 93 | { |
| 94 | return getUnlinkedGlobalCodeBlock<UnlinkedProgramCodeBlock>(vm, executable, source, strictMode, JSParserScriptMode::Classic, codeGenerationMode, error, EvalContextType::None); |
| 95 | } |
| 96 | |
| 97 | UnlinkedEvalCodeBlock* CodeCache::getUnlinkedEvalCodeBlock(VM& vm, IndirectEvalExecutable* executable, const SourceCode& source, JSParserStrictMode strictMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, EvalContextType evalContextType) |
| 98 | { |
| 99 | return getUnlinkedGlobalCodeBlock<UnlinkedEvalCodeBlock>(vm, executable, source, strictMode, JSParserScriptMode::Classic, codeGenerationMode, error, evalContextType); |
| 100 | } |
| 101 | |
| 102 | UnlinkedModuleProgramCodeBlock* CodeCache::getUnlinkedModuleProgramCodeBlock(VM& vm, ModuleProgramExecutable* executable, const SourceCode& source, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error) |
| 103 | { |
| 104 | return getUnlinkedGlobalCodeBlock<UnlinkedModuleProgramCodeBlock>(vm, executable, source, JSParserStrictMode::Strict, JSParserScriptMode::Module, codeGenerationMode, error, EvalContextType::None); |
| 105 | } |
| 106 | |
| 107 | UnlinkedFunctionExecutable* CodeCache::getUnlinkedGlobalFunctionExecutable(VM& vm, const Identifier& name, const SourceCode& source, OptionSet<CodeGenerationMode> codeGenerationMode, Optional<int> functionConstructorParametersEndPosition, ParserError& error) |
| 108 | { |
| 109 | bool isArrowFunctionContext = false; |
| 110 | SourceCodeKey key( |
| 111 | source, name.string(), SourceCodeType::FunctionType, |
| 112 | JSParserStrictMode::NotStrict, |
| 113 | JSParserScriptMode::Classic, |
| 114 | DerivedContextType::None, |
| 115 | EvalContextType::None, |
| 116 | isArrowFunctionContext, |
| 117 | codeGenerationMode, |
| 118 | functionConstructorParametersEndPosition); |
| 119 | UnlinkedFunctionExecutable* executable = m_sourceCode.findCacheAndUpdateAge<UnlinkedFunctionExecutable>(vm, key); |
| 120 | if (executable && Options::useCodeCache()) { |
| 121 | if (!executable->sourceURLDirective().isNull()) |
| 122 | source.provider()->setSourceURLDirective(executable->sourceURLDirective()); |
| 123 | if (!executable->sourceMappingURLDirective().isNull()) |
| 124 | source.provider()->setSourceMappingURLDirective(executable->sourceMappingURLDirective()); |
| 125 | return executable; |
| 126 | } |
| 127 | |
| 128 | JSTextPosition positionBeforeLastNewline; |
| 129 | std::unique_ptr<ProgramNode> program = parseFunctionForFunctionConstructor(vm, source, error, &positionBeforeLastNewline, functionConstructorParametersEndPosition); |
| 130 | if (!program) { |
| 131 | RELEASE_ASSERT(error.isValid()); |
| 132 | return nullptr; |
| 133 | } |
| 134 | |
| 135 | // This function assumes an input string that would result in a single function declaration. |
| 136 | StatementNode* funcDecl = program->singleStatement(); |
| 137 | if (UNLIKELY(!funcDecl)) { |
| 138 | JSToken token; |
| 139 | error = ParserError(ParserError::SyntaxError, ParserError::SyntaxErrorIrrecoverable, token, "Parser error" , -1); |
| 140 | return nullptr; |
| 141 | } |
| 142 | ASSERT(funcDecl->isFuncDeclNode()); |
| 143 | |
| 144 | FunctionMetadataNode* metadata = static_cast<FuncDeclNode*>(funcDecl)->metadata(); |
| 145 | ASSERT(metadata); |
| 146 | if (!metadata) |
| 147 | return nullptr; |
| 148 | |
| 149 | metadata->overrideName(name); |
| 150 | metadata->setEndPosition(positionBeforeLastNewline); |
| 151 | // The Function constructor only has access to global variables, so no variables will be under TDZ unless they're |
| 152 | // in the global lexical environment, which we always TDZ check accesses from. |
| 153 | ConstructAbility constructAbility = constructAbilityForParseMode(metadata->parseMode()); |
| 154 | UnlinkedFunctionExecutable* functionExecutable = UnlinkedFunctionExecutable::create(&vm, source, metadata, UnlinkedNormalFunction, constructAbility, JSParserScriptMode::Classic, WTF::nullopt, DerivedContextType::None); |
| 155 | |
| 156 | if (!source.provider()->sourceURLDirective().isNull()) |
| 157 | functionExecutable->setSourceURLDirective(source.provider()->sourceURLDirective()); |
| 158 | if (!source.provider()->sourceMappingURLDirective().isNull()) |
| 159 | functionExecutable->setSourceMappingURLDirective(source.provider()->sourceMappingURLDirective()); |
| 160 | |
| 161 | if (Options::useCodeCache()) |
| 162 | m_sourceCode.addCache(key, SourceCodeValue(vm, functionExecutable, m_sourceCode.age())); |
| 163 | return functionExecutable; |
| 164 | } |
| 165 | |
| 166 | void CodeCache::updateCache(const UnlinkedFunctionExecutable* executable, const SourceCode& parentSource, CodeSpecializationKind kind, const UnlinkedFunctionCodeBlock* codeBlock) |
| 167 | { |
| 168 | parentSource.provider()->updateCache(executable, parentSource, kind, codeBlock); |
| 169 | } |
| 170 | |
| 171 | void CodeCache::write(VM& vm) |
| 172 | { |
| 173 | for (auto& it : m_sourceCode) |
| 174 | writeCodeBlock(vm, it.key, it.value); |
| 175 | } |
| 176 | |
| 177 | void generateUnlinkedCodeBlockForFunctions(VM& vm, UnlinkedCodeBlock* unlinkedCodeBlock, const SourceCode& parentSource, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error) |
| 178 | { |
| 179 | auto generate = [&](UnlinkedFunctionExecutable* unlinkedExecutable, CodeSpecializationKind constructorKind) { |
| 180 | if (constructorKind == CodeForConstruct && SourceParseModeSet(SourceParseMode::AsyncArrowFunctionMode, SourceParseMode::AsyncMethodMode, SourceParseMode::AsyncFunctionMode).contains(unlinkedExecutable->parseMode())) |
| 181 | return; |
| 182 | |
| 183 | SourceCode source = unlinkedExecutable->linkedSourceCode(parentSource); |
| 184 | UnlinkedFunctionCodeBlock* unlinkedFunctionCodeBlock = unlinkedExecutable->unlinkedCodeBlockFor(vm, source, constructorKind, codeGenerationMode, error, unlinkedExecutable->parseMode()); |
| 185 | if (unlinkedFunctionCodeBlock) |
| 186 | generateUnlinkedCodeBlockForFunctions(vm, unlinkedFunctionCodeBlock, source, codeGenerationMode, error); |
| 187 | }; |
| 188 | |
| 189 | // FIXME: We should also generate CodeBlocks for CodeForConstruct |
| 190 | // https://bugs.webkit.org/show_bug.cgi?id=193823 |
| 191 | for (unsigned i = 0; i < unlinkedCodeBlock->numberOfFunctionDecls(); i++) |
| 192 | generate(unlinkedCodeBlock->functionDecl(i), CodeForCall); |
| 193 | for (unsigned i = 0; i < unlinkedCodeBlock->numberOfFunctionExprs(); i++) |
| 194 | generate(unlinkedCodeBlock->functionExpr(i), CodeForCall); |
| 195 | } |
| 196 | |
| 197 | void writeCodeBlock(VM& vm, const SourceCodeKey& key, const SourceCodeValue& value) |
| 198 | { |
| 199 | UnlinkedCodeBlock* codeBlock = jsDynamicCast<UnlinkedCodeBlock*>(vm, value.cell.get()); |
| 200 | if (!codeBlock) |
| 201 | return; |
| 202 | |
| 203 | key.source().provider().commitCachedBytecode(); |
| 204 | } |
| 205 | |
| 206 | static SourceCodeKey sourceCodeKeyForSerializedBytecode(VM&, const SourceCode& sourceCode, SourceCodeType codeType, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode) |
| 207 | { |
| 208 | return SourceCodeKey( |
| 209 | sourceCode, String(), codeType, strictMode, scriptMode, |
| 210 | DerivedContextType::None, EvalContextType::None, false, codeGenerationMode, |
| 211 | WTF::nullopt); |
| 212 | } |
| 213 | |
| 214 | SourceCodeKey sourceCodeKeyForSerializedProgram(VM& vm, const SourceCode& sourceCode) |
| 215 | { |
| 216 | JSParserStrictMode strictMode = JSParserStrictMode::NotStrict; |
| 217 | JSParserScriptMode scriptMode = JSParserScriptMode::Classic; |
| 218 | return sourceCodeKeyForSerializedBytecode(vm, sourceCode, SourceCodeType::ProgramType, strictMode, scriptMode, { }); |
| 219 | } |
| 220 | |
| 221 | SourceCodeKey sourceCodeKeyForSerializedModule(VM& vm, const SourceCode& sourceCode) |
| 222 | { |
| 223 | JSParserStrictMode strictMode = JSParserStrictMode::Strict; |
| 224 | JSParserScriptMode scriptMode = JSParserScriptMode::Module; |
| 225 | return sourceCodeKeyForSerializedBytecode(vm, sourceCode, SourceCodeType::ModuleType, strictMode, scriptMode, { }); |
| 226 | } |
| 227 | |
| 228 | Ref<CachedBytecode> serializeBytecode(VM& vm, UnlinkedCodeBlock* codeBlock, const SourceCode& source, SourceCodeType codeType, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode) |
| 229 | { |
| 230 | return encodeCodeBlock(vm, |
| 231 | sourceCodeKeyForSerializedBytecode(vm, source, codeType, strictMode, scriptMode, codeGenerationMode), codeBlock); |
| 232 | } |
| 233 | |
| 234 | } |
| 235 | |