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