/* * Copyright (C) 2012, 2013 Apple Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "BytecodeGenerator.h" #include "CachedTypes.h" #include "ExecutableInfo.h" #include "JSCInlines.h" #include "Parser.h" #include "ParserModes.h" #include "SourceCodeKey.h" #include "Strong.h" #include "StrongInlines.h" #include "UnlinkedCodeBlock.h" #include "UnlinkedEvalCodeBlock.h" #include "UnlinkedFunctionCodeBlock.h" #include "UnlinkedModuleProgramCodeBlock.h" #include "UnlinkedProgramCodeBlock.h" #include #include #include namespace JSC { class EvalExecutable; class IndirectEvalExecutable; class Identifier; class DirectEvalExecutable; class ModuleProgramExecutable; class ParserError; class ProgramExecutable; class SourceCode; class UnlinkedCodeBlock; class UnlinkedEvalCodeBlock; class UnlinkedFunctionExecutable; class UnlinkedModuleProgramCodeBlock; class UnlinkedProgramCodeBlock; class VM; class VariableEnvironment; namespace CodeCacheInternal { static const bool verbose = false; } // namespace CodeCacheInternal #define VERBOSE_LOG(...) do { \ if (CodeCacheInternal::verbose) \ dataLogLn("(JSC::CodeCache) ", __VA_ARGS__); \ } while (false) struct SourceCodeValue { SourceCodeValue() { } SourceCodeValue(VM& vm, JSCell* cell, int64_t age, bool written = false) : cell(vm, cell) , age(age) , written(written) { } Strong cell; int64_t age; bool written; }; class CodeCacheMap { public: typedef HashMap MapType; typedef MapType::iterator iterator; typedef MapType::AddResult AddResult; CodeCacheMap() : m_size(0) , m_sizeAtLastPrune(0) , m_timeAtLastPrune(MonotonicTime::now()) , m_minCapacity(0) , m_capacity(0) , m_age(0) { } iterator begin() { return m_map.begin(); } iterator end() { return m_map.end(); } template UnlinkedCodeBlockType* fetchFromDiskImpl(VM& vm, const SourceCodeKey& key) { { const auto* cachedBytecode = key.source().provider().cachedBytecode(); if (cachedBytecode && cachedBytecode->size()) { VERBOSE_LOG("Found cached CodeBlock in the SourceProvider"); UnlinkedCodeBlockType* unlinkedCodeBlock = decodeCodeBlock(vm, key, cachedBytecode->data(), cachedBytecode->size()); if (unlinkedCodeBlock) return unlinkedCodeBlock; } } #if OS(DARWIN) const char* cachePath = Options::diskCachePath(); if (!cachePath) return nullptr; unsigned hash = key.hash(); char filename[512]; int count = snprintf(filename, 512, "%s/%u.cache", cachePath, hash); if (count < 0 || count > 512) return nullptr; int fd = open(filename, O_RDONLY); if (fd == -1) return nullptr; int rc = flock(fd, LOCK_SH | LOCK_NB); if (rc) { close(fd); return nullptr; } struct stat sb; int res = fstat(fd, &sb); size_t size = static_cast(sb.st_size); if (res || !size) { close(fd); return nullptr; } void* buffer = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); UnlinkedCodeBlockType* unlinkedCodeBlock = decodeCodeBlock(vm, key, buffer, size); munmap(buffer, size); if (!unlinkedCodeBlock) return nullptr; VERBOSE_LOG("Found cached CodeBlock on disk"); addCache(key, SourceCodeValue(vm, unlinkedCodeBlock, m_age, true)); return unlinkedCodeBlock; #else UNUSED_PARAM(vm); UNUSED_PARAM(key); return nullptr; #endif } template std::enable_if_t::value && !std::is_same::value, UnlinkedCodeBlockType*> fetchFromDisk(VM& vm, const SourceCodeKey& key) { UnlinkedCodeBlockType* codeBlock = fetchFromDiskImpl(vm, key); if (UNLIKELY(Options::forceDiskCache())) RELEASE_ASSERT(codeBlock); return codeBlock; } template std::enable_if_t::value || std::is_same::value, T*> fetchFromDisk(VM&, const SourceCodeKey&) { return nullptr; } template UnlinkedCodeBlockType* findCacheAndUpdateAge(VM& vm, const SourceCodeKey& key) { prune(); VERBOSE_LOG("Trying to find cached CodeBlock for ", key.source().provider().url().string()); iterator findResult = m_map.find(key); if (findResult == m_map.end()) return fetchFromDisk(vm, key); int64_t age = m_age - findResult->value.age; if (age > m_capacity) { // A requested object is older than the cache's capacity. We can // infer that requested objects are subject to high eviction probability, // so we grow the cache to improve our hit rate. m_capacity += recencyBias * oldObjectSamplingMultiplier * key.length(); } else if (age < m_capacity / 2) { // A requested object is much younger than the cache's capacity. We can // infer that requested objects are subject to low eviction probability, // so we shrink the cache to save memory. m_capacity -= recencyBias * key.length(); if (m_capacity < m_minCapacity) m_capacity = m_minCapacity; } findResult->value.age = m_age; m_age += key.length(); VERBOSE_LOG("Found cached CodeBlock in memory"); return jsCast(findResult->value.cell.get()); } AddResult addCache(const SourceCodeKey& key, const SourceCodeValue& value) { prune(); AddResult addResult = m_map.add(key, value); ASSERT(addResult.isNewEntry); m_size += key.length(); m_age += key.length(); return addResult; } void remove(iterator it) { m_size -= it->key.length(); m_map.remove(it); } void clear() { m_size = 0; m_age = 0; m_map.clear(); } int64_t age() { return m_age; } private: // This constant factor biases cache capacity toward allowing a minimum // working set to enter the cache before it starts evicting. static const Seconds workingSetTime; static const int64_t workingSetMaxBytes = 16000000; static const size_t workingSetMaxEntries = 2000; // This constant factor biases cache capacity toward recent activity. We // want to adapt to changing workloads. static const int64_t recencyBias = 4; // This constant factor treats a sampled event for one old object as if it // happened for many old objects. Most old objects are evicted before we can // sample them, so we need to extrapolate from the ones we do sample. static const int64_t oldObjectSamplingMultiplier = 32; size_t numberOfEntries() const { return static_cast(m_map.size()); } bool canPruneQuickly() const { return numberOfEntries() < workingSetMaxEntries; } void pruneSlowCase(); void prune() { if (m_size <= m_capacity && canPruneQuickly()) return; if (MonotonicTime::now() - m_timeAtLastPrune < workingSetTime && m_size - m_sizeAtLastPrune < workingSetMaxBytes && canPruneQuickly()) return; pruneSlowCase(); } MapType m_map; int64_t m_size; int64_t m_sizeAtLastPrune; MonotonicTime m_timeAtLastPrune; int64_t m_minCapacity; int64_t m_capacity; int64_t m_age; }; // Caches top-level code such as