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
32namespace JSC {
33
34const Seconds CodeCacheMap::workingSetTime = 10_s;
35
36void 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
55template <class UnlinkedCodeBlockType, class ExecutableType>
56UnlinkedCodeBlockType* CodeCache::getUnlinkedGlobalCodeBlock(VM& vm, ExecutableType* executable, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, DebuggerMode debuggerMode, 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, debuggerMode,
63 vm.typeProfiler() ? TypeProfilerEnabled::Yes : TypeProfilerEnabled::No,
64 vm.controlFlowProfiler() ? ControlFlowProfilerEnabled::Yes : ControlFlowProfilerEnabled::No,
65 WTF::nullopt);
66 UnlinkedCodeBlockType* unlinkedCodeBlock = m_sourceCode.findCacheAndUpdateAge<UnlinkedCodeBlockType>(vm, key);
67 if (unlinkedCodeBlock && Options::useCodeCache()) {
68 unsigned lineCount = unlinkedCodeBlock->lineCount();
69 unsigned startColumn = unlinkedCodeBlock->startColumn() + source.startColumn().oneBasedInt();
70 bool endColumnIsOnStartLine = !lineCount;
71 unsigned endColumn = unlinkedCodeBlock->endColumn() + (endColumnIsOnStartLine ? startColumn : 1);
72 executable->recordParse(unlinkedCodeBlock->codeFeatures(), unlinkedCodeBlock->hasCapturedVariables(), source.firstLine().oneBasedInt() + lineCount, endColumn);
73 if (!unlinkedCodeBlock->sourceURLDirective().isNull())
74 source.provider()->setSourceURLDirective(unlinkedCodeBlock->sourceURLDirective());
75 if (!unlinkedCodeBlock->sourceMappingURLDirective().isNull())
76 source.provider()->setSourceMappingURLDirective(unlinkedCodeBlock->sourceMappingURLDirective());
77 return unlinkedCodeBlock;
78 }
79
80 VariableEnvironment variablesUnderTDZ;
81 unlinkedCodeBlock = generateUnlinkedCodeBlock<UnlinkedCodeBlockType, ExecutableType>(vm, executable, source, strictMode, scriptMode, debuggerMode, error, evalContextType, &variablesUnderTDZ);
82
83 if (unlinkedCodeBlock && Options::useCodeCache())
84 m_sourceCode.addCache(key, SourceCodeValue(vm, unlinkedCodeBlock, m_sourceCode.age()));
85
86 return unlinkedCodeBlock;
87}
88
89UnlinkedProgramCodeBlock* CodeCache::getUnlinkedProgramCodeBlock(VM& vm, ProgramExecutable* executable, const SourceCode& source, JSParserStrictMode strictMode, DebuggerMode debuggerMode, ParserError& error)
90{
91 return getUnlinkedGlobalCodeBlock<UnlinkedProgramCodeBlock>(vm, executable, source, strictMode, JSParserScriptMode::Classic, debuggerMode, error, EvalContextType::None);
92}
93
94UnlinkedEvalCodeBlock* CodeCache::getUnlinkedEvalCodeBlock(VM& vm, IndirectEvalExecutable* executable, const SourceCode& source, JSParserStrictMode strictMode, DebuggerMode debuggerMode, ParserError& error, EvalContextType evalContextType)
95{
96 return getUnlinkedGlobalCodeBlock<UnlinkedEvalCodeBlock>(vm, executable, source, strictMode, JSParserScriptMode::Classic, debuggerMode, error, evalContextType);
97}
98
99UnlinkedModuleProgramCodeBlock* CodeCache::getUnlinkedModuleProgramCodeBlock(VM& vm, ModuleProgramExecutable* executable, const SourceCode& source, DebuggerMode debuggerMode, ParserError& error)
100{
101 return getUnlinkedGlobalCodeBlock<UnlinkedModuleProgramCodeBlock>(vm, executable, source, JSParserStrictMode::Strict, JSParserScriptMode::Module, debuggerMode, error, EvalContextType::None);
102}
103
104UnlinkedFunctionExecutable* CodeCache::getUnlinkedGlobalFunctionExecutable(VM& vm, const Identifier& name, const SourceCode& source, DebuggerMode debuggerMode, Optional<int> functionConstructorParametersEndPosition, ParserError& error)
105{
106 bool isArrowFunctionContext = false;
107 SourceCodeKey key(
108 source, name.string(), SourceCodeType::FunctionType,
109 JSParserStrictMode::NotStrict,
110 JSParserScriptMode::Classic,
111 DerivedContextType::None,
112 EvalContextType::None,
113 isArrowFunctionContext,
114 debuggerMode,
115 vm.typeProfiler() ? TypeProfilerEnabled::Yes : TypeProfilerEnabled::No,
116 vm.controlFlowProfiler() ? ControlFlowProfilerEnabled::Yes : ControlFlowProfilerEnabled::No,
117 functionConstructorParametersEndPosition);
118 UnlinkedFunctionExecutable* executable = m_sourceCode.findCacheAndUpdateAge<UnlinkedFunctionExecutable>(vm, key);
119 if (executable && Options::useCodeCache()) {
120 if (!executable->sourceURLDirective().isNull())
121 source.provider()->setSourceURLDirective(executable->sourceURLDirective());
122 if (!executable->sourceMappingURLDirective().isNull())
123 source.provider()->setSourceMappingURLDirective(executable->sourceMappingURLDirective());
124 return executable;
125 }
126
127 JSTextPosition positionBeforeLastNewline;
128 std::unique_ptr<ProgramNode> program = parseFunctionForFunctionConstructor(vm, source, error, &positionBeforeLastNewline, functionConstructorParametersEndPosition);
129 if (!program) {
130 RELEASE_ASSERT(error.isValid());
131 return nullptr;
132 }
133
134 // This function assumes an input string that would result in a single function declaration.
135 StatementNode* funcDecl = program->singleStatement();
136 if (UNLIKELY(!funcDecl)) {
137 JSToken token;
138 error = ParserError(ParserError::SyntaxError, ParserError::SyntaxErrorIrrecoverable, token, "Parser error", -1);
139 return nullptr;
140 }
141 ASSERT(funcDecl->isFuncDeclNode());
142
143 FunctionMetadataNode* metadata = static_cast<FuncDeclNode*>(funcDecl)->metadata();
144 ASSERT(metadata);
145 if (!metadata)
146 return nullptr;
147
148 metadata->overrideName(name);
149 metadata->setEndPosition(positionBeforeLastNewline);
150 // The Function constructor only has access to global variables, so no variables will be under TDZ unless they're
151 // in the global lexical environment, which we always TDZ check accesses from.
152 ConstructAbility constructAbility = constructAbilityForParseMode(metadata->parseMode());
153 UnlinkedFunctionExecutable* functionExecutable = UnlinkedFunctionExecutable::create(&vm, source, metadata, UnlinkedNormalFunction, constructAbility, JSParserScriptMode::Classic, WTF::nullopt, DerivedContextType::None);
154
155 if (!source.provider()->sourceURLDirective().isNull())
156 functionExecutable->setSourceURLDirective(source.provider()->sourceURLDirective());
157 if (!source.provider()->sourceMappingURLDirective().isNull())
158 functionExecutable->setSourceMappingURLDirective(source.provider()->sourceMappingURLDirective());
159
160 if (Options::useCodeCache())
161 m_sourceCode.addCache(key, SourceCodeValue(vm, functionExecutable, m_sourceCode.age()));
162 return functionExecutable;
163}
164
165void CodeCache::write(VM& vm)
166{
167 for (auto& it : m_sourceCode) {
168 if (it.value.written)
169 continue;
170 it.value.written = true;
171 writeCodeBlock(vm, it.key, it.value);
172 }
173}
174
175void generateUnlinkedCodeBlockForFunctions(VM& vm, UnlinkedCodeBlock* unlinkedCodeBlock, const SourceCode& parentSource, DebuggerMode debuggerMode, ParserError& error)
176{
177 auto generate = [&](UnlinkedFunctionExecutable* unlinkedExecutable, CodeSpecializationKind constructorKind) {
178 if (constructorKind == CodeForConstruct && SourceParseModeSet(SourceParseMode::AsyncArrowFunctionMode, SourceParseMode::AsyncMethodMode, SourceParseMode::AsyncFunctionMode).contains(unlinkedExecutable->parseMode()))
179 return;
180
181 SourceCode source = unlinkedExecutable->linkedSourceCode(parentSource);
182 UnlinkedFunctionCodeBlock* unlinkedFunctionCodeBlock = unlinkedExecutable->unlinkedCodeBlockFor(vm, source, constructorKind, debuggerMode, error, unlinkedExecutable->parseMode());
183 if (unlinkedFunctionCodeBlock)
184 generateUnlinkedCodeBlockForFunctions(vm, unlinkedFunctionCodeBlock, source, debuggerMode, error);
185 };
186
187 // FIXME: We should also generate CodeBlocks for CodeForConstruct
188 // https://bugs.webkit.org/show_bug.cgi?id=193823
189 for (unsigned i = 0; i < unlinkedCodeBlock->numberOfFunctionDecls(); i++)
190 generate(unlinkedCodeBlock->functionDecl(i), CodeForCall);
191 for (unsigned i = 0; i < unlinkedCodeBlock->numberOfFunctionExprs(); i++)
192 generate(unlinkedCodeBlock->functionExpr(i), CodeForCall);
193}
194
195void writeCodeBlock(VM& vm, const SourceCodeKey& key, const SourceCodeValue& value)
196{
197 UnlinkedCodeBlock* codeBlock = jsDynamicCast<UnlinkedCodeBlock*>(vm, value.cell.get());
198 if (!codeBlock)
199 return;
200
201 key.source().provider().cacheBytecode([&] {
202 std::pair<MallocPtr<uint8_t>, size_t> result = encodeCodeBlock(vm, key, codeBlock);
203 return CachedBytecode { WTFMove(result.first), result.second };
204 });
205}
206
207static SourceCodeKey sourceCodeKeyForSerializedBytecode(VM& vm, const SourceCode& sourceCode, SourceCodeType codeType, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, DebuggerMode debuggerMode)
208{
209 return SourceCodeKey(
210 sourceCode, String(), codeType, strictMode, scriptMode,
211 DerivedContextType::None, EvalContextType::None, false, debuggerMode,
212 vm.typeProfiler() ? TypeProfilerEnabled::Yes : TypeProfilerEnabled::No,
213 vm.controlFlowProfiler() ? ControlFlowProfilerEnabled::Yes : ControlFlowProfilerEnabled::No,
214 WTF::nullopt);
215}
216
217SourceCodeKey sourceCodeKeyForSerializedProgram(VM& vm, const SourceCode& sourceCode)
218{
219 JSParserStrictMode strictMode = JSParserStrictMode::NotStrict;
220 JSParserScriptMode scriptMode = JSParserScriptMode::Classic;
221 DebuggerMode debuggerMode = DebuggerOff;
222 return sourceCodeKeyForSerializedBytecode(vm, sourceCode, SourceCodeType::ProgramType, strictMode, scriptMode, debuggerMode);
223}
224
225SourceCodeKey sourceCodeKeyForSerializedModule(VM& vm, const SourceCode& sourceCode)
226{
227 JSParserStrictMode strictMode = JSParserStrictMode::Strict;
228 JSParserScriptMode scriptMode = JSParserScriptMode::Module;
229 DebuggerMode debuggerMode = DebuggerOff;
230 return sourceCodeKeyForSerializedBytecode(vm, sourceCode, SourceCodeType::ModuleType, strictMode, scriptMode, debuggerMode);
231}
232
233CachedBytecode serializeBytecode(VM& vm, UnlinkedCodeBlock* codeBlock, const SourceCode& source, SourceCodeType codeType, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, DebuggerMode debuggerMode)
234{
235 std::pair<MallocPtr<uint8_t>, size_t> result = encodeCodeBlock(vm,
236 sourceCodeKeyForSerializedBytecode(vm, source, codeType, strictMode, scriptMode, debuggerMode), codeBlock);
237 return CachedBytecode { WTFMove(result.first), result.second };
238}
239
240}
241