1 // Copyright 2020 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/objects/compilation-cache-table.h"
6
7 #include "src/common/assert-scope.h"
8 #include "src/objects/compilation-cache-table-inl.h"
9
10 namespace v8 {
11 namespace internal {
12
13 namespace {
14
15 const int kLiteralEntryLength = 2;
16 const int kLiteralInitialLength = 2;
17 const int kLiteralContextOffset = 0;
18 const int kLiteralLiteralsOffset = 1;
19
20 // The initial placeholder insertion of the eval cache survives this many GCs.
21 const int kHashGenerations = 10;
22
SearchLiteralsMapEntry(CompilationCacheTable cache,int cache_entry,Context native_context)23 int SearchLiteralsMapEntry(CompilationCacheTable cache, int cache_entry,
24 Context native_context) {
25 DisallowGarbageCollection no_gc;
26 DCHECK(native_context.IsNativeContext());
27 Object obj = cache.get(cache_entry);
28
29 // Check that there's no confusion between FixedArray and WeakFixedArray (the
30 // object used to be a FixedArray here).
31 DCHECK(!obj.IsFixedArray());
32 if (obj.IsWeakFixedArray()) {
33 WeakFixedArray literals_map = WeakFixedArray::cast(obj);
34 int length = literals_map.length();
35 for (int i = 0; i < length; i += kLiteralEntryLength) {
36 DCHECK(literals_map.Get(i + kLiteralContextOffset)->IsWeakOrCleared());
37 if (literals_map.Get(i + kLiteralContextOffset) ==
38 HeapObjectReference::Weak(native_context)) {
39 return i;
40 }
41 }
42 }
43 return -1;
44 }
45
AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache,int cache_entry,Handle<Context> native_context,Handle<FeedbackCell> feedback_cell)46 void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache, int cache_entry,
47 Handle<Context> native_context,
48 Handle<FeedbackCell> feedback_cell) {
49 Isolate* isolate = native_context->GetIsolate();
50 DCHECK(native_context->IsNativeContext());
51 STATIC_ASSERT(kLiteralEntryLength == 2);
52 Handle<WeakFixedArray> new_literals_map;
53 int entry;
54
55 Object obj = cache->get(cache_entry);
56
57 // Check that there's no confusion between FixedArray and WeakFixedArray (the
58 // object used to be a FixedArray here).
59 DCHECK(!obj.IsFixedArray());
60 if (!obj.IsWeakFixedArray() || WeakFixedArray::cast(obj).length() == 0) {
61 new_literals_map = isolate->factory()->NewWeakFixedArray(
62 kLiteralInitialLength, AllocationType::kOld);
63 entry = 0;
64 } else {
65 Handle<WeakFixedArray> old_literals_map(WeakFixedArray::cast(obj), isolate);
66 entry = SearchLiteralsMapEntry(*cache, cache_entry, *native_context);
67 if (entry >= 0) {
68 // Just set the code of the entry.
69 old_literals_map->Set(entry + kLiteralLiteralsOffset,
70 HeapObjectReference::Weak(*feedback_cell));
71 return;
72 }
73
74 // Can we reuse an entry?
75 DCHECK_LT(entry, 0);
76 int length = old_literals_map->length();
77 for (int i = 0; i < length; i += kLiteralEntryLength) {
78 if (old_literals_map->Get(i + kLiteralContextOffset)->IsCleared()) {
79 new_literals_map = old_literals_map;
80 entry = i;
81 break;
82 }
83 }
84
85 if (entry < 0) {
86 // Copy old optimized code map and append one new entry.
87 new_literals_map = isolate->factory()->CopyWeakFixedArrayAndGrow(
88 old_literals_map, kLiteralEntryLength);
89 entry = old_literals_map->length();
90 }
91 }
92
93 new_literals_map->Set(entry + kLiteralContextOffset,
94 HeapObjectReference::Weak(*native_context));
95 new_literals_map->Set(entry + kLiteralLiteralsOffset,
96 HeapObjectReference::Weak(*feedback_cell));
97
98 #ifdef DEBUG
99 for (int i = 0; i < new_literals_map->length(); i += kLiteralEntryLength) {
100 MaybeObject object = new_literals_map->Get(i + kLiteralContextOffset);
101 DCHECK(object->IsCleared() ||
102 object->GetHeapObjectAssumeWeak().IsNativeContext());
103 object = new_literals_map->Get(i + kLiteralLiteralsOffset);
104 DCHECK(object->IsCleared() ||
105 object->GetHeapObjectAssumeWeak().IsFeedbackCell());
106 }
107 #endif
108
109 Object old_literals_map = cache->get(cache_entry);
110 if (old_literals_map != *new_literals_map) {
111 cache->set(cache_entry, *new_literals_map);
112 }
113 }
114
SearchLiteralsMap(CompilationCacheTable cache,int cache_entry,Context native_context)115 FeedbackCell SearchLiteralsMap(CompilationCacheTable cache, int cache_entry,
116 Context native_context) {
117 FeedbackCell result;
118 int entry = SearchLiteralsMapEntry(cache, cache_entry, native_context);
119 if (entry >= 0) {
120 WeakFixedArray literals_map = WeakFixedArray::cast(cache.get(cache_entry));
121 DCHECK_LE(entry + kLiteralEntryLength, literals_map.length());
122 MaybeObject object = literals_map.Get(entry + kLiteralLiteralsOffset);
123
124 if (!object->IsCleared()) {
125 result = FeedbackCell::cast(object->GetHeapObjectAssumeWeak());
126 }
127 }
128 DCHECK(result.is_null() || result.IsFeedbackCell());
129 return result;
130 }
131
132 // StringSharedKeys are used as keys in the eval cache.
133 class StringSharedKey : public HashTableKey {
134 public:
135 // This tuple unambiguously identifies calls to eval() or
136 // CreateDynamicFunction() (such as through the Function() constructor).
137 // * source is the string passed into eval(). For dynamic functions, this is
138 // the effective source for the function, some of which is implicitly
139 // generated.
140 // * shared is the shared function info for the function containing the call
141 // to eval(). for dynamic functions, shared is the native context closure.
142 // * When positive, position is the position in the source where eval is
143 // called. When negative, position is the negation of the position in the
144 // dynamic function's effective source where the ')' ends the parameters.
StringSharedKey(Handle<String> source,Handle<SharedFunctionInfo> shared,LanguageMode language_mode,int position)145 StringSharedKey(Handle<String> source, Handle<SharedFunctionInfo> shared,
146 LanguageMode language_mode, int position)
147 : HashTableKey(CompilationCacheShape::StringSharedHash(
148 *source, *shared, language_mode, position)),
149 source_(source),
150 shared_(shared),
151 language_mode_(language_mode),
152 position_(position) {}
153
154 // This tuple unambiguously identifies script compilation.
StringSharedKey(Handle<String> source,LanguageMode language_mode)155 StringSharedKey(Handle<String> source, LanguageMode language_mode)
156 : HashTableKey(
157 CompilationCacheShape::StringSharedHash(*source, language_mode)),
158 source_(source),
159 language_mode_(language_mode),
160 position_(kNoSourcePosition) {}
161
IsMatch(Object other)162 bool IsMatch(Object other) override {
163 DisallowGarbageCollection no_gc;
164 if (!other.IsFixedArray()) {
165 DCHECK(other.IsNumber());
166 uint32_t other_hash = static_cast<uint32_t>(other.Number());
167 return Hash() == other_hash;
168 }
169 FixedArray other_array = FixedArray::cast(other);
170 DCHECK(other_array.get(0).IsSharedFunctionInfo() ||
171 other_array.get(0) == Smi::zero());
172 Handle<SharedFunctionInfo> shared;
173 if (shared_.ToHandle(&shared)) {
174 if (*shared != other_array.get(0)) return false;
175 } else {
176 if (Smi::zero() != other_array.get(0)) return false;
177 }
178 int language_unchecked = Smi::ToInt(other_array.get(2));
179 DCHECK(is_valid_language_mode(language_unchecked));
180 LanguageMode language_mode = static_cast<LanguageMode>(language_unchecked);
181 if (language_mode != language_mode_) return false;
182 int position = Smi::ToInt(other_array.get(3));
183 if (position != position_) return false;
184 String source = String::cast(other_array.get(1));
185 return source.Equals(*source_);
186 }
187
AsHandle(Isolate * isolate)188 Handle<Object> AsHandle(Isolate* isolate) {
189 Handle<FixedArray> array = isolate->factory()->NewFixedArray(4);
190 Handle<SharedFunctionInfo> shared;
191 if (shared_.ToHandle(&shared)) {
192 array->set(0, *shared);
193 } else {
194 array->set(0, Smi::zero());
195 }
196 array->set(1, *source_);
197 array->set(2, Smi::FromEnum(language_mode_));
198 array->set(3, Smi::FromInt(position_));
199 array->set_map(ReadOnlyRoots(isolate).fixed_cow_array_map());
200 return array;
201 }
202
203 private:
204 Handle<String> source_;
205 MaybeHandle<SharedFunctionInfo> shared_;
206 LanguageMode language_mode_;
207 int position_;
208 };
209
210 // RegExpKey carries the source and flags of a regular expression as key.
211 class RegExpKey : public HashTableKey {
212 public:
RegExpKey(Handle<String> string,JSRegExp::Flags flags)213 RegExpKey(Handle<String> string, JSRegExp::Flags flags)
214 : HashTableKey(
215 CompilationCacheShape::RegExpHash(*string, Smi::FromInt(flags))),
216 string_(string),
217 flags_(Smi::FromInt(flags)) {}
218
219 // Rather than storing the key in the hash table, a pointer to the
220 // stored value is stored where the key should be. IsMatch then
221 // compares the search key to the found object, rather than comparing
222 // a key to a key.
IsMatch(Object obj)223 bool IsMatch(Object obj) override {
224 FixedArray val = FixedArray::cast(obj);
225 return string_->Equals(String::cast(val.get(JSRegExp::kSourceIndex))) &&
226 (flags_ == val.get(JSRegExp::kFlagsIndex));
227 }
228
229 Handle<String> string_;
230 Smi flags_;
231 };
232
233 // CodeKey carries the SharedFunctionInfo key associated with a Code
234 // object value.
235 class CodeKey : public HashTableKey {
236 public:
CodeKey(Handle<SharedFunctionInfo> key)237 explicit CodeKey(Handle<SharedFunctionInfo> key)
238 : HashTableKey(key->Hash()), key_(key) {}
239
IsMatch(Object string)240 bool IsMatch(Object string) override { return *key_ == string; }
241
242 Handle<SharedFunctionInfo> key_;
243 };
244
245 } // namespace
246
LookupScript(Handle<CompilationCacheTable> table,Handle<String> src,LanguageMode language_mode,Isolate * isolate)247 MaybeHandle<SharedFunctionInfo> CompilationCacheTable::LookupScript(
248 Handle<CompilationCacheTable> table, Handle<String> src,
249 LanguageMode language_mode, Isolate* isolate) {
250 src = String::Flatten(isolate, src);
251 StringSharedKey key(src, language_mode);
252 InternalIndex entry = table->FindEntry(isolate, &key);
253 if (entry.is_not_found()) return MaybeHandle<SharedFunctionInfo>();
254 int index = EntryToIndex(entry);
255 if (!table->get(index).IsFixedArray()) {
256 return MaybeHandle<SharedFunctionInfo>();
257 }
258 Object obj = table->get(index + 1);
259 if (obj.IsSharedFunctionInfo()) {
260 return handle(SharedFunctionInfo::cast(obj), isolate);
261 }
262 return MaybeHandle<SharedFunctionInfo>();
263 }
264
LookupEval(Handle<CompilationCacheTable> table,Handle<String> src,Handle<SharedFunctionInfo> outer_info,Handle<Context> native_context,LanguageMode language_mode,int position)265 InfoCellPair CompilationCacheTable::LookupEval(
266 Handle<CompilationCacheTable> table, Handle<String> src,
267 Handle<SharedFunctionInfo> outer_info, Handle<Context> native_context,
268 LanguageMode language_mode, int position) {
269 InfoCellPair empty_result;
270 Isolate* isolate = native_context->GetIsolate();
271 src = String::Flatten(isolate, src);
272
273 StringSharedKey key(src, outer_info, language_mode, position);
274 InternalIndex entry = table->FindEntry(isolate, &key);
275 if (entry.is_not_found()) return empty_result;
276
277 int index = EntryToIndex(entry);
278 if (!table->get(index).IsFixedArray()) return empty_result;
279 Object obj = table->get(index + 1);
280 if (!obj.IsSharedFunctionInfo()) return empty_result;
281
282 STATIC_ASSERT(CompilationCacheShape::kEntrySize == 3);
283 FeedbackCell feedback_cell =
284 SearchLiteralsMap(*table, index + 2, *native_context);
285 return InfoCellPair(isolate, SharedFunctionInfo::cast(obj), feedback_cell);
286 }
287
LookupRegExp(Handle<String> src,JSRegExp::Flags flags)288 Handle<Object> CompilationCacheTable::LookupRegExp(Handle<String> src,
289 JSRegExp::Flags flags) {
290 Isolate* isolate = GetIsolate();
291 DisallowGarbageCollection no_gc;
292 RegExpKey key(src, flags);
293 InternalIndex entry = FindEntry(isolate, &key);
294 if (entry.is_not_found()) return isolate->factory()->undefined_value();
295 return Handle<Object>(get(EntryToIndex(entry) + 1), isolate);
296 }
297
PutScript(Handle<CompilationCacheTable> cache,Handle<String> src,LanguageMode language_mode,Handle<SharedFunctionInfo> value,Isolate * isolate)298 Handle<CompilationCacheTable> CompilationCacheTable::PutScript(
299 Handle<CompilationCacheTable> cache, Handle<String> src,
300 LanguageMode language_mode, Handle<SharedFunctionInfo> value,
301 Isolate* isolate) {
302 src = String::Flatten(isolate, src);
303 StringSharedKey key(src, language_mode);
304 Handle<Object> k = key.AsHandle(isolate);
305 cache = EnsureCapacity(isolate, cache);
306 InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
307 cache->set(EntryToIndex(entry), *k);
308 cache->set(EntryToIndex(entry) + 1, *value);
309 cache->ElementAdded();
310 return cache;
311 }
312
PutEval(Handle<CompilationCacheTable> cache,Handle<String> src,Handle<SharedFunctionInfo> outer_info,Handle<SharedFunctionInfo> value,Handle<Context> native_context,Handle<FeedbackCell> feedback_cell,int position)313 Handle<CompilationCacheTable> CompilationCacheTable::PutEval(
314 Handle<CompilationCacheTable> cache, Handle<String> src,
315 Handle<SharedFunctionInfo> outer_info, Handle<SharedFunctionInfo> value,
316 Handle<Context> native_context, Handle<FeedbackCell> feedback_cell,
317 int position) {
318 Isolate* isolate = native_context->GetIsolate();
319 src = String::Flatten(isolate, src);
320 StringSharedKey key(src, outer_info, value->language_mode(), position);
321
322 // This block handles 'real' insertions, i.e. the initial dummy insert
323 // (below) has already happened earlier.
324 {
325 Handle<Object> k = key.AsHandle(isolate);
326 InternalIndex entry = cache->FindEntry(isolate, &key);
327 if (entry.is_found()) {
328 cache->set(EntryToIndex(entry), *k);
329 cache->set(EntryToIndex(entry) + 1, *value);
330 // AddToFeedbackCellsMap may allocate a new sub-array to live in the
331 // entry, but it won't change the cache array. Therefore EntryToIndex
332 // and entry remains correct.
333 STATIC_ASSERT(CompilationCacheShape::kEntrySize == 3);
334 AddToFeedbackCellsMap(cache, EntryToIndex(entry) + 2, native_context,
335 feedback_cell);
336 // Add hash again even on cache hit to avoid unnecessary cache delay in
337 // case of hash collisions.
338 }
339 }
340
341 // Create a dummy entry to mark that this key has already been inserted once.
342 cache = EnsureCapacity(isolate, cache);
343 InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
344 Handle<Object> k =
345 isolate->factory()->NewNumber(static_cast<double>(key.Hash()));
346 cache->set(EntryToIndex(entry), *k);
347 cache->set(EntryToIndex(entry) + 1, Smi::FromInt(kHashGenerations));
348 cache->ElementAdded();
349 return cache;
350 }
351
PutRegExp(Isolate * isolate,Handle<CompilationCacheTable> cache,Handle<String> src,JSRegExp::Flags flags,Handle<FixedArray> value)352 Handle<CompilationCacheTable> CompilationCacheTable::PutRegExp(
353 Isolate* isolate, Handle<CompilationCacheTable> cache, Handle<String> src,
354 JSRegExp::Flags flags, Handle<FixedArray> value) {
355 RegExpKey key(src, flags);
356 cache = EnsureCapacity(isolate, cache);
357 InternalIndex entry = cache->FindInsertionEntry(isolate, key.Hash());
358 // We store the value in the key slot, and compare the search key
359 // to the stored value with a custom IsMatch function during lookups.
360 cache->set(EntryToIndex(entry), *value);
361 cache->set(EntryToIndex(entry) + 1, *value);
362 cache->ElementAdded();
363 return cache;
364 }
365
Age(Isolate * isolate)366 void CompilationCacheTable::Age(Isolate* isolate) {
367 DisallowGarbageCollection no_gc;
368 for (InternalIndex entry : IterateEntries()) {
369 const int entry_index = EntryToIndex(entry);
370 const int value_index = entry_index + 1;
371
372 Object key = get(entry_index);
373 if (key.IsNumber()) {
374 // The ageing mechanism for the initial dummy entry in the eval cache.
375 // The 'key' is the hash represented as a Number. The 'value' is a smi
376 // counting down from kHashGenerations. On reaching zero, the entry is
377 // cleared.
378 // Note: The following static assert only establishes an explicit
379 // connection between initialization- and use-sites of the smi value
380 // field.
381 STATIC_ASSERT(kHashGenerations);
382 const int new_count = Smi::ToInt(get(value_index)) - 1;
383 if (new_count == 0) {
384 RemoveEntry(entry_index);
385 } else {
386 DCHECK_GT(new_count, 0);
387 NoWriteBarrierSet(*this, value_index, Smi::FromInt(new_count));
388 }
389 } else if (key.IsFixedArray()) {
390 // The ageing mechanism for script and eval caches.
391 SharedFunctionInfo info = SharedFunctionInfo::cast(get(value_index));
392 if (info.HasBytecodeArray() && info.GetBytecodeArray(isolate).IsOld()) {
393 RemoveEntry(entry_index);
394 }
395 }
396 }
397 }
398
Remove(Object value)399 void CompilationCacheTable::Remove(Object value) {
400 DisallowGarbageCollection no_gc;
401 for (InternalIndex entry : IterateEntries()) {
402 int entry_index = EntryToIndex(entry);
403 int value_index = entry_index + 1;
404 if (get(value_index) == value) {
405 RemoveEntry(entry_index);
406 }
407 }
408 }
409
RemoveEntry(int entry_index)410 void CompilationCacheTable::RemoveEntry(int entry_index) {
411 Object the_hole_value = GetReadOnlyRoots().the_hole_value();
412 for (int i = 0; i < kEntrySize; i++) {
413 NoWriteBarrierSet(*this, entry_index + i, the_hole_value);
414 }
415 ElementRemoved();
416 }
417
418 } // namespace internal
419 } // namespace v8
420