1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef vm_Caches_h 8 #define vm_Caches_h 9 10 #include <iterator> 11 #include <new> 12 13 #include "frontend/SourceNotes.h" // SrcNote 14 #include "gc/Tracer.h" 15 #include "js/RootingAPI.h" 16 #include "js/TypeDecls.h" 17 #include "js/UniquePtr.h" 18 #include "util/Memory.h" 19 #include "vm/ArrayObject.h" 20 #include "vm/JSAtom.h" 21 #include "vm/JSObject.h" 22 #include "vm/JSScript.h" 23 #include "vm/NativeObject.h" 24 25 namespace js { 26 27 /* 28 * GetSrcNote cache to avoid O(n^2) growth in finding a source note for a 29 * given pc in a script. We use the script->code pointer to tag the cache, 30 * instead of the script address itself, so that source notes are always found 31 * by offset from the bytecode with which they were generated. 32 */ 33 struct GSNCache { 34 typedef HashMap<jsbytecode*, const SrcNote*, PointerHasher<jsbytecode*>, 35 SystemAllocPolicy> 36 Map; 37 38 jsbytecode* code; 39 Map map; 40 GSNCacheGSNCache41 GSNCache() : code(nullptr) {} 42 43 void purge(); 44 }; 45 46 struct EvalCacheEntry { 47 JSLinearString* str; 48 JSScript* script; 49 JSScript* callerScript; 50 jsbytecode* pc; 51 52 // We sweep this cache before a nursery collection to remove entries with 53 // string keys in the nursery. 54 // 55 // The entire cache is purged on a major GC, so we don't need to sweep it 56 // then. needsSweepEvalCacheEntry57 bool needsSweep() { return !str->isTenured(); } 58 }; 59 60 struct EvalCacheLookup { EvalCacheLookupEvalCacheLookup61 explicit EvalCacheLookup(JSContext* cx) : str(cx), callerScript(cx) {} 62 RootedLinearString str; 63 RootedScript callerScript; 64 MOZ_INIT_OUTSIDE_CTOR jsbytecode* pc; 65 }; 66 67 struct EvalCacheHashPolicy { 68 using Lookup = EvalCacheLookup; 69 70 static HashNumber hash(const Lookup& l); 71 static bool match(const EvalCacheEntry& entry, const EvalCacheLookup& l); 72 }; 73 74 typedef GCHashSet<EvalCacheEntry, EvalCacheHashPolicy, SystemAllocPolicy> 75 EvalCache; 76 77 /* 78 * Cache for speeding up repetitive creation of objects in the VM. 79 * When an object is created which matches the criteria in the 'key' section 80 * below, an entry is filled with the resulting object. 81 */ 82 class NewObjectCache { 83 static constexpr unsigned MAX_OBJ_SIZE = sizeof(JSObject_Slots16); 84 staticAsserts()85 static void staticAsserts() { 86 static_assert(gc::AllocKind::OBJECT_LAST == 87 gc::AllocKind::OBJECT16_BACKGROUND); 88 } 89 90 struct Entry { 91 /* Class of the constructed object. */ 92 const JSClass* clasp; 93 94 /* 95 * Key with one of two possible values: 96 * 97 * - Global for the object. The object must have a standard class and will 98 * have this global's builtin prototype for this class as proto. 99 * 100 * - Prototype for the object (non-null). Cannot be a global object because 101 * that would be ambiguous (see previous case). 102 */ 103 JSObject* key; 104 105 /* Allocation kind for the constructed object. */ 106 gc::AllocKind kind; 107 108 /* Number of bytes to copy from the template object. */ 109 uint32_t nbytes; 110 111 /* 112 * Template object to copy from, with the initial values of fields, 113 * fixed slots (undefined) and private data (nullptr). 114 */ 115 char templateObject[MAX_OBJ_SIZE]; 116 }; 117 118 using EntryArray = Entry[41]; // TODO: reconsider size; 119 EntryArray entries; 120 121 public: 122 using EntryIndex = int; 123 NewObjectCache()124 NewObjectCache() 125 : entries{} // zeroes out the array 126 {} 127 purge()128 void purge() { 129 new (&entries) EntryArray{}; // zeroes out the array 130 } 131 132 /* Remove any cached items keyed on moved objects. */ 133 void clearNurseryObjects(JSRuntime* rt); 134 135 /* 136 * Get the entry index for the given lookup, return whether there was a hit 137 * on an existing entry. 138 */ 139 inline bool lookupProto(const JSClass* clasp, JSObject* proto, 140 gc::AllocKind kind, EntryIndex* pentry); 141 inline bool lookupGlobal(const JSClass* clasp, js::GlobalObject* global, 142 gc::AllocKind kind, EntryIndex* pentry); 143 144 /* 145 * Return a new object from a cache hit produced by a lookup method, or 146 * nullptr if returning the object could possibly trigger GC (does not 147 * indicate failure). 148 */ 149 inline NativeObject* newObjectFromHit(JSContext* cx, EntryIndex entry, 150 js::gc::InitialHeap heap, 151 gc::AllocSite* site = nullptr); 152 153 /* Fill an entry after a cache miss. */ 154 void fillProto(EntryIndex entry, const JSClass* clasp, js::TaggedProto proto, 155 gc::AllocKind kind, NativeObject* obj); 156 157 inline void fillGlobal(EntryIndex entry, const JSClass* clasp, 158 js::GlobalObject* global, gc::AllocKind kind, 159 NativeObject* obj); 160 161 /* Invalidate any entries which might produce an object with shape. */ 162 void invalidateEntriesForShape(Shape* shape); 163 164 private: makeIndex(const JSClass * clasp,gc::Cell * key,gc::AllocKind kind)165 EntryIndex makeIndex(const JSClass* clasp, gc::Cell* key, 166 gc::AllocKind kind) { 167 uintptr_t hash = (uintptr_t(clasp) ^ uintptr_t(key)) + size_t(kind); 168 return hash % std::size(entries); 169 } 170 lookup(const JSClass * clasp,JSObject * key,gc::AllocKind kind,EntryIndex * pentry)171 bool lookup(const JSClass* clasp, JSObject* key, gc::AllocKind kind, 172 EntryIndex* pentry) { 173 *pentry = makeIndex(clasp, key, kind); 174 Entry* entry = &entries[*pentry]; 175 176 // N.B. Lookups with the same clasp/key but different kinds map to 177 // different entries. 178 return entry->clasp == clasp && entry->key == key; 179 } 180 fill(EntryIndex entry_,const JSClass * clasp,JSObject * key,gc::AllocKind kind,NativeObject * obj)181 void fill(EntryIndex entry_, const JSClass* clasp, JSObject* key, 182 gc::AllocKind kind, NativeObject* obj) { 183 MOZ_ASSERT(unsigned(entry_) < std::size(entries)); 184 MOZ_ASSERT(entry_ == makeIndex(clasp, key, kind)); 185 Entry* entry = &entries[entry_]; 186 187 MOZ_ASSERT(!obj->hasDynamicSlots()); 188 MOZ_ASSERT(obj->hasEmptyElements() || obj->is<ArrayObject>()); 189 190 entry->clasp = clasp; 191 entry->key = key; 192 entry->kind = kind; 193 194 entry->nbytes = gc::Arena::thingSize(kind); 195 js_memcpy(&entry->templateObject, obj, entry->nbytes); 196 } 197 copyCachedToObject(NativeObject * dst,NativeObject * src,gc::AllocKind kind)198 static void copyCachedToObject(NativeObject* dst, NativeObject* src, 199 gc::AllocKind kind) { 200 js_memcpy(dst, src, gc::Arena::thingSize(kind)); 201 202 // Initialize with barriers 203 dst->initShape(src->shape()); 204 } 205 }; 206 207 // Cache for AtomizeString, mapping JSLinearString* to the corresponding 208 // JSAtom*. Also used by nursery GC to de-duplicate strings to atoms. 209 // Purged on minor and major GC. 210 class StringToAtomCache { 211 using Map = HashMap<JSLinearString*, JSAtom*, PointerHasher<JSLinearString*>, 212 SystemAllocPolicy>; 213 Map map_; 214 215 public: 216 // Don't use the cache for short strings. Hashing them is less expensive. 217 static constexpr size_t MinStringLength = 30; 218 lookup(JSLinearString * s)219 JSAtom* lookup(JSLinearString* s) { 220 MOZ_ASSERT(!s->isAtom()); 221 if (!s->inStringToAtomCache()) { 222 MOZ_ASSERT(!map_.lookup(s)); 223 return nullptr; 224 } 225 226 MOZ_ASSERT(s->length() >= MinStringLength); 227 228 auto p = map_.lookup(s); 229 JSAtom* atom = p ? p->value() : nullptr; 230 MOZ_ASSERT_IF(atom, EqualStrings(s, atom)); 231 return atom; 232 } 233 maybePut(JSLinearString * s,JSAtom * atom)234 void maybePut(JSLinearString* s, JSAtom* atom) { 235 MOZ_ASSERT(!s->isAtom()); 236 if (s->length() < MinStringLength) { 237 return; 238 } 239 if (!map_.putNew(s, atom)) { 240 return; 241 } 242 s->setInStringToAtomCache(); 243 } 244 purge()245 void purge() { map_.clearAndCompact(); } 246 }; 247 248 class RuntimeCaches { 249 public: 250 js::GSNCache gsnCache; 251 js::NewObjectCache newObjectCache; 252 js::UncompressedSourceCache uncompressedSourceCache; 253 js::EvalCache evalCache; 254 js::StringToAtomCache stringToAtomCache; 255 purgeForMinorGC(JSRuntime * rt)256 void purgeForMinorGC(JSRuntime* rt) { 257 newObjectCache.clearNurseryObjects(rt); 258 evalCache.sweep(); 259 } 260 purgeForCompaction()261 void purgeForCompaction() { 262 newObjectCache.purge(); 263 evalCache.clear(); 264 stringToAtomCache.purge(); 265 } 266 purge()267 void purge() { 268 purgeForCompaction(); 269 gsnCache.purge(); 270 uncompressedSourceCache.purge(); 271 } 272 }; 273 274 } // namespace js 275 276 #endif /* vm_Caches_h */ 277