1/*
2 * Copyright (C) 2012-2019 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#pragma once
27
28#include "BytecodeGenerator.h"
29#include "CachedTypes.h"
30#include "ExecutableInfo.h"
31#include "JSCInlines.h"
32#include "Parser.h"
33#include "ParserModes.h"
34#include "SourceCodeKey.h"
35#include "Strong.h"
36#include "StrongInlines.h"
37#include "UnlinkedCodeBlock.h"
38#include "UnlinkedEvalCodeBlock.h"
39#include "UnlinkedFunctionCodeBlock.h"
40#include "UnlinkedModuleProgramCodeBlock.h"
41#include "UnlinkedProgramCodeBlock.h"
42
43namespace JSC {
44
45class EvalExecutable;
46class IndirectEvalExecutable;
47class Identifier;
48class DirectEvalExecutable;
49class ModuleProgramExecutable;
50class ParserError;
51class ProgramExecutable;
52class SourceCode;
53class UnlinkedCodeBlock;
54class UnlinkedEvalCodeBlock;
55class UnlinkedFunctionExecutable;
56class UnlinkedModuleProgramCodeBlock;
57class UnlinkedProgramCodeBlock;
58class VM;
59class VariableEnvironment;
60
61namespace CodeCacheInternal {
62static const bool verbose = false;
63} // namespace CodeCacheInternal
64
65struct SourceCodeValue {
66 SourceCodeValue()
67 {
68 }
69
70 SourceCodeValue(VM& vm, JSCell* cell, int64_t age)
71 : cell(vm, cell)
72 , age(age)
73 {
74 }
75
76 Strong<JSCell> cell;
77 int64_t age;
78};
79
80class CodeCacheMap {
81public:
82 typedef HashMap<SourceCodeKey, SourceCodeValue, SourceCodeKey::Hash, SourceCodeKey::HashTraits> MapType;
83 typedef MapType::iterator iterator;
84 typedef MapType::AddResult AddResult;
85
86 CodeCacheMap()
87 : m_size(0)
88 , m_sizeAtLastPrune(0)
89 , m_timeAtLastPrune(MonotonicTime::now())
90 , m_minCapacity(0)
91 , m_capacity(0)
92 , m_age(0)
93 {
94 }
95
96 iterator begin() { return m_map.begin(); }
97 iterator end() { return m_map.end(); }
98
99 template<typename UnlinkedCodeBlockType>
100 UnlinkedCodeBlockType* findCacheAndUpdateAge(VM& vm, const SourceCodeKey& key)
101 {
102 prune();
103
104 iterator findResult = m_map.find(key);
105 if (findResult == m_map.end())
106 return fetchFromDisk<UnlinkedCodeBlockType>(vm, key);
107
108 int64_t age = m_age - findResult->value.age;
109 if (age > m_capacity) {
110 // A requested object is older than the cache's capacity. We can
111 // infer that requested objects are subject to high eviction probability,
112 // so we grow the cache to improve our hit rate.
113 m_capacity += recencyBias * oldObjectSamplingMultiplier * key.length();
114 } else if (age < m_capacity / 2) {
115 // A requested object is much younger than the cache's capacity. We can
116 // infer that requested objects are subject to low eviction probability,
117 // so we shrink the cache to save memory.
118 m_capacity -= recencyBias * key.length();
119 if (m_capacity < m_minCapacity)
120 m_capacity = m_minCapacity;
121 }
122
123 findResult->value.age = m_age;
124 m_age += key.length();
125
126 return jsCast<UnlinkedCodeBlockType*>(findResult->value.cell.get());
127 }
128
129 AddResult addCache(const SourceCodeKey& key, const SourceCodeValue& value)
130 {
131 prune();
132
133 AddResult addResult = m_map.add(key, value);
134 ASSERT(addResult.isNewEntry);
135
136 m_size += key.length();
137 m_age += key.length();
138 return addResult;
139 }
140
141 void remove(iterator it)
142 {
143 m_size -= it->key.length();
144 m_map.remove(it);
145 }
146
147 void clear()
148 {
149 m_size = 0;
150 m_age = 0;
151 m_map.clear();
152 }
153
154 int64_t age() { return m_age; }
155
156private:
157 template<typename UnlinkedCodeBlockType>
158 UnlinkedCodeBlockType* fetchFromDiskImpl(VM& vm, const SourceCodeKey& key)
159 {
160 RefPtr<CachedBytecode> cachedBytecode = key.source().provider().cachedBytecode();
161 if (!cachedBytecode || !cachedBytecode->size())
162 return nullptr;
163 return decodeCodeBlock<UnlinkedCodeBlockType>(vm, key, *cachedBytecode);
164 }
165
166 template<typename UnlinkedCodeBlockType>
167 std::enable_if_t<std::is_base_of<UnlinkedCodeBlock, UnlinkedCodeBlockType>::value && !std::is_same<UnlinkedCodeBlockType, UnlinkedEvalCodeBlock>::value, UnlinkedCodeBlockType*>
168 fetchFromDisk(VM& vm, const SourceCodeKey& key)
169 {
170 UnlinkedCodeBlockType* codeBlock = fetchFromDiskImpl<UnlinkedCodeBlockType>(vm, key);
171 if (UNLIKELY(Options::forceDiskCache()))
172 RELEASE_ASSERT(codeBlock);
173 return codeBlock;
174 }
175
176 template<typename T>
177 std::enable_if_t<!std::is_base_of<UnlinkedCodeBlock, T>::value || std::is_same<T, UnlinkedEvalCodeBlock>::value, T*>
178 fetchFromDisk(VM&, const SourceCodeKey&) { return nullptr; }
179
180 // This constant factor biases cache capacity toward allowing a minimum
181 // working set to enter the cache before it starts evicting.
182 static const Seconds workingSetTime;
183 static const int64_t workingSetMaxBytes = 16000000;
184 static const size_t workingSetMaxEntries = 2000;
185
186 // This constant factor biases cache capacity toward recent activity. We
187 // want to adapt to changing workloads.
188 static const int64_t recencyBias = 4;
189
190 // This constant factor treats a sampled event for one old object as if it
191 // happened for many old objects. Most old objects are evicted before we can
192 // sample them, so we need to extrapolate from the ones we do sample.
193 static const int64_t oldObjectSamplingMultiplier = 32;
194
195 size_t numberOfEntries() const { return static_cast<size_t>(m_map.size()); }
196 bool canPruneQuickly() const { return numberOfEntries() < workingSetMaxEntries; }
197
198 void pruneSlowCase();
199 void prune()
200 {
201 if (m_size <= m_capacity && canPruneQuickly())
202 return;
203
204 if (MonotonicTime::now() - m_timeAtLastPrune < workingSetTime
205 && m_size - m_sizeAtLastPrune < workingSetMaxBytes
206 && canPruneQuickly())
207 return;
208
209 pruneSlowCase();
210 }
211
212 MapType m_map;
213 int64_t m_size;
214 int64_t m_sizeAtLastPrune;
215 MonotonicTime m_timeAtLastPrune;
216 int64_t m_minCapacity;
217 int64_t m_capacity;
218 int64_t m_age;
219};
220
221// Caches top-level code such as <script>, window.eval(), new Function, and JSEvaluateScript().
222class CodeCache {
223 WTF_MAKE_FAST_ALLOCATED;
224public:
225 UnlinkedProgramCodeBlock* getUnlinkedProgramCodeBlock(VM&, ProgramExecutable*, const SourceCode&, JSParserStrictMode, OptionSet<CodeGenerationMode>, ParserError&);
226 UnlinkedEvalCodeBlock* getUnlinkedEvalCodeBlock(VM&, IndirectEvalExecutable*, const SourceCode&, JSParserStrictMode, OptionSet<CodeGenerationMode>, ParserError&, EvalContextType);
227 UnlinkedModuleProgramCodeBlock* getUnlinkedModuleProgramCodeBlock(VM&, ModuleProgramExecutable*, const SourceCode&, OptionSet<CodeGenerationMode>, ParserError&);
228 UnlinkedFunctionExecutable* getUnlinkedGlobalFunctionExecutable(VM&, const Identifier&, const SourceCode&, OptionSet<CodeGenerationMode>, Optional<int> functionConstructorParametersEndPosition, ParserError&);
229
230 void updateCache(const UnlinkedFunctionExecutable*, const SourceCode&, CodeSpecializationKind, const UnlinkedFunctionCodeBlock*);
231
232 void clear() { m_sourceCode.clear(); }
233 JS_EXPORT_PRIVATE void write(VM&);
234
235private:
236 template <class UnlinkedCodeBlockType, class ExecutableType>
237 UnlinkedCodeBlockType* getUnlinkedGlobalCodeBlock(VM&, ExecutableType*, const SourceCode&, JSParserStrictMode, JSParserScriptMode, OptionSet<CodeGenerationMode>, ParserError&, EvalContextType);
238
239 CodeCacheMap m_sourceCode;
240};
241
242template <typename T> struct CacheTypes { };
243
244template <> struct CacheTypes<UnlinkedProgramCodeBlock> {
245 typedef JSC::ProgramNode RootNode;
246 static const SourceCodeType codeType = SourceCodeType::ProgramType;
247 static const SourceParseMode parseMode = SourceParseMode::ProgramMode;
248};
249
250template <> struct CacheTypes<UnlinkedEvalCodeBlock> {
251 typedef JSC::EvalNode RootNode;
252 static const SourceCodeType codeType = SourceCodeType::EvalType;
253 static const SourceParseMode parseMode = SourceParseMode::ProgramMode;
254};
255
256template <> struct CacheTypes<UnlinkedModuleProgramCodeBlock> {
257 typedef JSC::ModuleProgramNode RootNode;
258 static const SourceCodeType codeType = SourceCodeType::ModuleType;
259 static const SourceParseMode parseMode = SourceParseMode::ModuleEvaluateMode;
260};
261
262template <class UnlinkedCodeBlockType, class ExecutableType = ScriptExecutable>
263UnlinkedCodeBlockType* generateUnlinkedCodeBlockImpl(VM& vm, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, EvalContextType evalContextType, DerivedContextType derivedContextType, bool isArrowFunctionContext, const VariableEnvironment* variablesUnderTDZ, ExecutableType* executable = nullptr)
264{
265 typedef typename CacheTypes<UnlinkedCodeBlockType>::RootNode RootNode;
266 std::unique_ptr<RootNode> rootNode = parse<RootNode>(
267 &vm, source, Identifier(), JSParserBuiltinMode::NotBuiltin, strictMode, scriptMode, CacheTypes<UnlinkedCodeBlockType>::parseMode, SuperBinding::NotNeeded, error, nullptr, ConstructorKind::None, derivedContextType, evalContextType);
268 if (!rootNode)
269 return nullptr;
270
271 unsigned lineCount = rootNode->lastLine() - rootNode->firstLine();
272 unsigned startColumn = rootNode->startColumn() + 1;
273 bool endColumnIsOnStartLine = !lineCount;
274 unsigned unlinkedEndColumn = rootNode->endColumn();
275 unsigned endColumn = unlinkedEndColumn + (endColumnIsOnStartLine ? startColumn : 1);
276 unsigned arrowContextFeature = isArrowFunctionContext ? ArrowFunctionContextFeature : 0;
277 if (executable)
278 executable->recordParse(rootNode->features() | arrowContextFeature, rootNode->hasCapturedVariables(), rootNode->lastLine(), endColumn);
279
280 bool usesEval = rootNode->features() & EvalFeature;
281 bool isStrictMode = rootNode->features() & StrictModeFeature;
282 ExecutableInfo executableInfo(usesEval, isStrictMode, false, false, ConstructorKind::None, scriptMode, SuperBinding::NotNeeded, CacheTypes<UnlinkedCodeBlockType>::parseMode, derivedContextType, isArrowFunctionContext, false, evalContextType);
283
284 UnlinkedCodeBlockType* unlinkedCodeBlock = UnlinkedCodeBlockType::create(&vm, executableInfo, codeGenerationMode);
285 unlinkedCodeBlock->recordParse(rootNode->features(), rootNode->hasCapturedVariables(), lineCount, unlinkedEndColumn);
286 if (!source.provider()->sourceURLDirective().isNull())
287 unlinkedCodeBlock->setSourceURLDirective(source.provider()->sourceURLDirective());
288 if (!source.provider()->sourceMappingURLDirective().isNull())
289 unlinkedCodeBlock->setSourceMappingURLDirective(source.provider()->sourceMappingURLDirective());
290
291 error = BytecodeGenerator::generate(vm, rootNode.get(), source, unlinkedCodeBlock, codeGenerationMode, variablesUnderTDZ);
292
293 if (error.isValid())
294 return nullptr;
295
296 return unlinkedCodeBlock;
297}
298
299template <class UnlinkedCodeBlockType, class ExecutableType>
300UnlinkedCodeBlockType* generateUnlinkedCodeBlock(VM& vm, ExecutableType* executable, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, EvalContextType evalContextType, const VariableEnvironment* variablesUnderTDZ)
301{
302 return generateUnlinkedCodeBlockImpl<UnlinkedCodeBlockType, ExecutableType>(vm, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType, executable->derivedContextType(), executable->isArrowFunctionContext(), variablesUnderTDZ, executable);
303}
304
305void generateUnlinkedCodeBlockForFunctions(VM&, UnlinkedCodeBlock*, const SourceCode&, OptionSet<CodeGenerationMode>, ParserError&);
306
307template <class UnlinkedCodeBlockType>
308std::enable_if_t<!std::is_same<UnlinkedCodeBlockType, UnlinkedEvalCodeBlock>::value, UnlinkedCodeBlockType*>
309recursivelyGenerateUnlinkedCodeBlock(VM& vm, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, EvalContextType evalContextType, const VariableEnvironment* variablesUnderTDZ)
310{
311 bool isArrowFunctionContext = false;
312 UnlinkedCodeBlockType* unlinkedCodeBlock = generateUnlinkedCodeBlockImpl<UnlinkedCodeBlockType>(vm, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType, DerivedContextType::None, isArrowFunctionContext, variablesUnderTDZ);
313 if (!unlinkedCodeBlock)
314 return nullptr;
315
316 generateUnlinkedCodeBlockForFunctions(vm, unlinkedCodeBlock, source, codeGenerationMode, error);
317 return unlinkedCodeBlock;
318}
319
320void writeCodeBlock(VM&, const SourceCodeKey&, const SourceCodeValue&);
321RefPtr<CachedBytecode> serializeBytecode(VM&, UnlinkedCodeBlock*, const SourceCode&, SourceCodeType, JSParserStrictMode, JSParserScriptMode, int fd, BytecodeCacheError&, OptionSet<CodeGenerationMode>);
322SourceCodeKey sourceCodeKeyForSerializedProgram(VM&, const SourceCode&);
323SourceCodeKey sourceCodeKeyForSerializedModule(VM&, const SourceCode&);
324
325} // namespace JSC
326