2 * Copyright (C) 2012, 2013 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
28 #include "BytecodeGenerator.h"
29 #include "CachedTypes.h"
30 #include "ExecutableInfo.h"
31 #include "JSCInlines.h"
33 #include "ParserModes.h"
34 #include "SourceCodeKey.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"
43 #include <wtf/Forward.h>
44 #include <wtf/text/WTFString.h>
49 class IndirectEvalExecutable;
51 class DirectEvalExecutable;
52 class ModuleProgramExecutable;
54 class ProgramExecutable;
56 class UnlinkedCodeBlock;
57 class UnlinkedEvalCodeBlock;
58 class UnlinkedFunctionExecutable;
59 class UnlinkedModuleProgramCodeBlock;
60 class UnlinkedProgramCodeBlock;
62 class VariableEnvironment;
64 namespace CodeCacheInternal {
65 static const bool verbose = false;
66 } // namespace CodeCacheInternal
68 #define VERBOSE_LOG(...) do { \
69 if (CodeCacheInternal::verbose) \
70 dataLogLn("(JSC::CodeCache) ", __VA_ARGS__); \
73 struct SourceCodeValue {
78 SourceCodeValue(VM& vm, JSCell* cell, int64_t age, bool written = false)
92 typedef HashMap<SourceCodeKey, SourceCodeValue, SourceCodeKey::Hash, SourceCodeKey::HashTraits> MapType;
93 typedef MapType::iterator iterator;
94 typedef MapType::AddResult AddResult;
98 , m_sizeAtLastPrune(0)
99 , m_timeAtLastPrune(MonotonicTime::now())
106 iterator begin() { return m_map.begin(); }
107 iterator end() { return m_map.end(); }
109 template<typename UnlinkedCodeBlockType>
110 UnlinkedCodeBlockType* fetchFromDiskImpl(VM& vm, const SourceCodeKey& key)
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;
123 const char* cachePath = Options::diskCachePath();
127 unsigned hash = key.hash();
129 int count = snprintf(filename, 512, "%s/%u.cache", cachePath, hash);
130 if (count < 0 || count > 512)
133 int fd = open(filename, O_RDONLY);
137 int rc = flock(fd, LOCK_SH | LOCK_NB);
144 int res = fstat(fd, &sb);
145 size_t size = static_cast<size_t>(sb.st_size);
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);
155 if (!unlinkedCodeBlock)
158 VERBOSE_LOG("Found cached CodeBlock on disk");
159 addCache(key, SourceCodeValue(vm, unlinkedCodeBlock, m_age, true));
160 return unlinkedCodeBlock;
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)
172 UnlinkedCodeBlockType* codeBlock = fetchFromDiskImpl<UnlinkedCodeBlockType>(vm, key);
173 if (UNLIKELY(Options::forceDiskCache()))
174 RELEASE_ASSERT(codeBlock);
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; }
182 template<typename UnlinkedCodeBlockType>
183 UnlinkedCodeBlockType* findCacheAndUpdateAge(VM& vm, const SourceCodeKey& key)
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);
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;
207 findResult->value.age = m_age;
208 m_age += key.length();
210 VERBOSE_LOG("Found cached CodeBlock in memory");
211 return jsCast<UnlinkedCodeBlockType*>(findResult->value.cell.get());
214 AddResult addCache(const SourceCodeKey& key, const SourceCodeValue& value)
218 AddResult addResult = m_map.add(key, value);
219 ASSERT(addResult.isNewEntry);
221 m_size += key.length();
222 m_age += key.length();
226 void remove(iterator it)
228 m_size -= it->key.length();
239 int64_t age() { return m_age; }
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;
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;
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;
257 size_t numberOfEntries() const { return static_cast<size_t>(m_map.size()); }
258 bool canPruneQuickly() const { return numberOfEntries() < workingSetMaxEntries; }
260 void pruneSlowCase();
263 if (m_size <= m_capacity && canPruneQuickly())
266 if (MonotonicTime::now() - m_timeAtLastPrune < workingSetTime
267 && m_size - m_sizeAtLastPrune < workingSetMaxBytes
268 && canPruneQuickly())
276 int64_t m_sizeAtLastPrune;
277 MonotonicTime m_timeAtLastPrune;
278 int64_t m_minCapacity;
283 // Caches top-level code such as <script>, window.eval(), new Function, and JSEvaluateScript().
285 WTF_MAKE_FAST_ALLOCATED;
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&);
292 void clear() { m_sourceCode.clear(); }
293 JS_EXPORT_PRIVATE void write(VM&);
296 template <class UnlinkedCodeBlockType, class ExecutableType>
297 UnlinkedCodeBlockType* getUnlinkedGlobalCodeBlock(VM&, ExecutableType*, const SourceCode&, JSParserStrictMode, JSParserScriptMode, DebuggerMode, ParserError&, EvalContextType);
299 CodeCacheMap m_sourceCode;
302 template <typename T> struct CacheTypes { };
304 template <> struct CacheTypes<UnlinkedProgramCodeBlock> {
305 typedef JSC::ProgramNode RootNode;
306 static const SourceCodeType codeType = SourceCodeType::ProgramType;
307 static const SourceParseMode parseMode = SourceParseMode::ProgramMode;
310 template <> struct CacheTypes<UnlinkedEvalCodeBlock> {
311 typedef JSC::EvalNode RootNode;
312 static const SourceCodeType codeType = SourceCodeType::EvalType;
313 static const SourceParseMode parseMode = SourceParseMode::ProgramMode;
316 template <> struct CacheTypes<UnlinkedModuleProgramCodeBlock> {
317 typedef JSC::ModuleProgramNode RootNode;
318 static const SourceCodeType codeType = SourceCodeType::ModuleType;
319 static const SourceParseMode parseMode = SourceParseMode::ModuleEvaluateMode;
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)
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);
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;
338 executable->recordParse(rootNode->features() | arrowContextFeature, rootNode->hasCapturedVariables(), rootNode->lastLine(), endColumn);
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);
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());
351 error = BytecodeGenerator::generate(vm, rootNode.get(), source, unlinkedCodeBlock, debuggerMode, variablesUnderTDZ);
356 return unlinkedCodeBlock;
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)
362 return generateUnlinkedCodeBlockImpl<UnlinkedCodeBlockType, ExecutableType>(vm, source, strictMode, scriptMode, debuggerMode, error, evalContextType, executable->derivedContextType(), executable->isArrowFunctionContext(), variablesUnderTDZ, executable);
365 void generateUnlinkedCodeBlockForFunctions(VM&, UnlinkedCodeBlock*, const SourceCode&, DebuggerMode, ParserError&);
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)
371 bool isArrowFunctionContext = false;
372 UnlinkedCodeBlockType* unlinkedCodeBlock = generateUnlinkedCodeBlockImpl<UnlinkedCodeBlockType>(vm, source, strictMode, scriptMode, debuggerMode, error, evalContextType, DerivedContextType::None, isArrowFunctionContext, variablesUnderTDZ);
373 if (!unlinkedCodeBlock)
376 generateUnlinkedCodeBlockForFunctions(vm, unlinkedCodeBlock, source, debuggerMode, error);
377 return unlinkedCodeBlock;
380 void writeCodeBlock(VM&, const SourceCodeKey&, const SourceCodeValue&);
381 CachedBytecode serializeBytecode(VM&, UnlinkedCodeBlock*, const SourceCode&, SourceCodeType, JSParserStrictMode, JSParserScriptMode, DebuggerMode);