/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef vm_StencilCache_h #define vm_StencilCache_h #include "mozilla/Atomics.h" // mozilla::Atomic #include "mozilla/HashFunctions.h" // mozilla::HashGeneric #include "mozilla/RefPtr.h" // mozilla::RefPtr #include "js/HashTable.h" // js::HashTable #include "threading/ExclusiveData.h" // js::ExclusiveData #include "vm/JSScript.h" // js::ScriptSource #include "vm/SharedStencil.h" // js::SourceExtent struct JS_PUBLIC_API JSContext; // vm/JSContext.h namespace js { namespace frontend { struct CompilationStencil; // frontend/CompilationStencil.h struct ExtensibleCompilationStencil; // frontend/CompilationStencil.h } /* namespace frontend */ // Note, the key is a RefPtr, but neither the PointerHasher nor // DefaultHasher seems to handle these correctly. struct SourceCachePolicy { using Lookup = const ScriptSource*; static HashNumber hash(const Lookup& l) { return mozilla::HashGeneric(l); } static bool match(const Lookup& entry, const Lookup& l) { return entry == l; } }; // Immutable information to identify a unique function, as well as the // compilation context which will result in a unique Stencil once compiled. struct StencilContext { // This pointer is used to isolate a single source. Note the uniqueness of the // ReadOnlyCompileOptions is implied by the fact that we allocate a new // ScriptSource for each CompilationInput, which are initialized with a set of // CompileOptions. RefPtr source; SourceExtent::FunctionKey funKey; StencilContext(RefPtr& source, SourceExtent extent) : source(source), funKey(extent.toFunctionKey()) {} }; struct StencilCachePolicy { using Lookup = StencilContext; static HashNumber hash(const Lookup& l) { const ScriptSource* raw = l.source; return mozilla::HashGeneric(raw, l.funKey); } static bool match(const Lookup& entry, const Lookup& l) { return entry.source == l.source && entry.funKey == l.funKey; } }; // Cache stencils which are parsed from the same source, and with identical // compilation options. // // This cache does not check the principals, as the source should not be // aliased across different principals. // // The content provided by this cache is computed by delazification tasks. The // delazification task needs contextual information to generate Stencils, which // are then registered in this cache. // // To reclaim memory from this cache, the producers should be shutdown and the // cache should be cleared. As the delazification process needs contextual // information to generate the Stencils, it is impossible to resume without // either keeping memory or recomputing from the beginning. // // Therefore, this cache cannot be cleared without disabling it at the same // time. Disabling this cache ends all threads which attempts to add new // stencils. // // The cache can be in multiple states: // // - Cache disabled: // // The cache can be disabled when running in a steady state for some time, // or after reaching a shrinking GC, which reclaims the memory from this // cache. // // This state is expected for the steady state of web pages, once the // pages are loaded for a while and that we do not expect a huge load of // delazification. // // - Cache enabled, Source is not cached: // // This is a rare case which can happen either when testing the // StencilCache, or after some dynamic load of JavaScript code in a page // which already reached as steady state. In which case, we want to prevent // locking for any functions which are not part of the newly loaded source. // // This might also happen for eval-ed or inline JavaScript, which is // parsed on the main thread, and also delazified on the main thread. // // - Cache enabled, Source is cached: // // This case is expected to be frequent for any new document. A new // document will register many sources for parsing off-thread, and // triggering off-thread delazification. // // All newly parse sources will be registered here until a steady state is // reached, or a shrinking GC is called. class StencilCache { using SourceSet = js::HashSet, SourceCachePolicy, SystemAllocPolicy>; using StencilMap = js::HashMap, StencilCachePolicy, SystemAllocPolicy>; struct CacheData { // Sources which are recorded in this cache. SourceSet watched; // Stencils of functions which are recorded in this cache. StencilMap functions; }; // Map a function to its CompilationStencil. ExclusiveData cache; // This flag is mostly read, and changes rarely. We use this Atomic to avoid // locking a Mutex when the cache is disabled. // // The cache can be disabled when running in a steady state for some time, or // after reaching a shrinking GC, which reclaims the memory from this cache // and indirectly ends the threads which are producing stencils for this // cache. mozilla::Atomic enabled; public: StencilCache(); // An access key is returned when checking whether the cache is enabled. It // should be used in an if statement and provided to all follow-up functions // if it is true-ish. using AccessKey = ExclusiveData::NullableGuard; // Not all stencils should be cached, we use the ScriptSource pointer to // identify whether we should look further or not in the cache. AccessKey isSourceCached(ScriptSource* src); // Register a source in the cache in order to cache any stencil associated // with this source in the future. To stop caching, the function // clearAndDisable can be used. // // Note: This function should be called once per source. Which is usualy after // creating it. [[nodiscard]] bool startCaching(RefPtr&& src); // Checks if the cache contains a specific stencil and returns a pointer to // it if it does. Otherwise, returns nullptr. frontend::CompilationStencil* lookup(AccessKey& guard, const StencilContext& key); // Adds a newly compiled stencil to the cache. The cache should not contain // any entry for this function before calling this function. [[nodiscard]] bool putNew(AccessKey& guard, const StencilContext& key, frontend::CompilationStencil* value); // Prevent any further stencil from being cached, and clear and reclaim the // memory of all stencil held by the cache. // // WARNING: This function should not be called within a scope checking for // isSourceCached, as this would cause a dead-lock. void clearAndDisable(); }; } /* namespace js */ #endif /* vm_StencilCache_h */