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