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