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