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