1 // Copyright 2011 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/codegen/compilation-cache.h"
6
7 #include "src/common/globals.h"
8 #include "src/heap/factory.h"
9 #include "src/logging/counters.h"
10 #include "src/logging/log.h"
11 #include "src/objects/compilation-cache-table-inl.h"
12 #include "src/objects/objects-inl.h"
13 #include "src/objects/slots.h"
14 #include "src/objects/visitors.h"
15 #include "src/utils/ostreams.h"
16
17 namespace v8 {
18 namespace internal {
19
20 // The number of generations for each sub cache.
21 static const int kRegExpGenerations = 2;
22
23 // Initial size of each compilation cache table allocated.
24 static const int kInitialCacheSize = 64;
25
CompilationCache(Isolate * isolate)26 CompilationCache::CompilationCache(Isolate* isolate)
27 : isolate_(isolate),
28 script_(isolate),
29 eval_global_(isolate),
30 eval_contextual_(isolate),
31 reg_exp_(isolate, kRegExpGenerations),
32 code_(isolate),
33 enabled_script_and_eval_(true) {
34 CompilationSubCache* subcaches[kSubCacheCount] = {
35 &script_, &eval_global_, &eval_contextual_, ®_exp_, &code_};
36 for (int i = 0; i < kSubCacheCount; ++i) {
37 subcaches_[i] = subcaches[i];
38 }
39 }
40
GetTable(int generation)41 Handle<CompilationCacheTable> CompilationSubCache::GetTable(int generation) {
42 DCHECK_LT(generation, generations());
43 Handle<CompilationCacheTable> result;
44 if (tables_[generation].IsUndefined(isolate())) {
45 result = CompilationCacheTable::New(isolate(), kInitialCacheSize);
46 tables_[generation] = *result;
47 } else {
48 CompilationCacheTable table =
49 CompilationCacheTable::cast(tables_[generation]);
50 result = Handle<CompilationCacheTable>(table, isolate());
51 }
52 return result;
53 }
54
55 // static
AgeByGeneration(CompilationSubCache * c)56 void CompilationSubCache::AgeByGeneration(CompilationSubCache* c) {
57 DCHECK_GT(c->generations(), 1);
58
59 // Age the generations implicitly killing off the oldest.
60 for (int i = c->generations() - 1; i > 0; i--) {
61 c->tables_[i] = c->tables_[i - 1];
62 }
63
64 // Set the first generation as unborn.
65 c->tables_[0] = ReadOnlyRoots(c->isolate()).undefined_value();
66 }
67
68 // static
AgeCustom(CompilationSubCache * c)69 void CompilationSubCache::AgeCustom(CompilationSubCache* c) {
70 DCHECK_EQ(c->generations(), 1);
71 if (c->tables_[0].IsUndefined(c->isolate())) return;
72 CompilationCacheTable::cast(c->tables_[0]).Age();
73 }
74
Age()75 void CompilationCacheScript::Age() {
76 if (FLAG_isolate_script_cache_ageing) AgeCustom(this);
77 }
Age()78 void CompilationCacheEval::Age() { AgeCustom(this); }
Age()79 void CompilationCacheRegExp::Age() { AgeByGeneration(this); }
Age()80 void CompilationCacheCode::Age() {
81 if (FLAG_turbo_nci_cache_ageing) {
82 if (FLAG_trace_turbo_nci) CompilationCacheCode::TraceAgeing();
83 AgeByGeneration(this);
84 }
85 }
86
Iterate(RootVisitor * v)87 void CompilationSubCache::Iterate(RootVisitor* v) {
88 v->VisitRootPointers(Root::kCompilationCache, nullptr,
89 FullObjectSlot(&tables_[0]),
90 FullObjectSlot(&tables_[generations()]));
91 }
92
Clear()93 void CompilationSubCache::Clear() {
94 MemsetPointer(reinterpret_cast<Address*>(tables_),
95 ReadOnlyRoots(isolate()).undefined_value().ptr(),
96 generations());
97 }
98
Remove(Handle<SharedFunctionInfo> function_info)99 void CompilationSubCache::Remove(Handle<SharedFunctionInfo> function_info) {
100 // Probe the script generation tables. Make sure not to leak handles
101 // into the caller's handle scope.
102 {
103 HandleScope scope(isolate());
104 for (int generation = 0; generation < generations(); generation++) {
105 Handle<CompilationCacheTable> table = GetTable(generation);
106 table->Remove(*function_info);
107 }
108 }
109 }
110
CompilationCacheScript(Isolate * isolate)111 CompilationCacheScript::CompilationCacheScript(Isolate* isolate)
112 : CompilationSubCache(isolate, 1) {}
113
114 // We only re-use a cached function for some script source code if the
115 // script originates from the same place. This is to avoid issues
116 // when reporting errors, etc.
HasOrigin(Handle<SharedFunctionInfo> function_info,MaybeHandle<Object> maybe_name,int line_offset,int column_offset,ScriptOriginOptions resource_options)117 bool CompilationCacheScript::HasOrigin(Handle<SharedFunctionInfo> function_info,
118 MaybeHandle<Object> maybe_name,
119 int line_offset, int column_offset,
120 ScriptOriginOptions resource_options) {
121 Handle<Script> script =
122 Handle<Script>(Script::cast(function_info->script()), isolate());
123 // If the script name isn't set, the boilerplate script should have
124 // an undefined name to have the same origin.
125 Handle<Object> name;
126 if (!maybe_name.ToHandle(&name)) {
127 return script->name().IsUndefined(isolate());
128 }
129 // Do the fast bailout checks first.
130 if (line_offset != script->line_offset()) return false;
131 if (column_offset != script->column_offset()) return false;
132 // Check that both names are strings. If not, no match.
133 if (!name->IsString() || !script->name().IsString()) return false;
134 // Are the origin_options same?
135 if (resource_options.Flags() != script->origin_options().Flags())
136 return false;
137 // Compare the two name strings for equality.
138 return String::Equals(
139 isolate(), Handle<String>::cast(name),
140 Handle<String>(String::cast(script->name()), isolate()));
141 }
142
143 // TODO(245): Need to allow identical code from different contexts to
144 // be cached in the same script generation. Currently the first use
145 // will be cached, but subsequent code from different source / line
146 // won't.
Lookup(Handle<String> source,MaybeHandle<Object> name,int line_offset,int column_offset,ScriptOriginOptions resource_options,Handle<Context> native_context,LanguageMode language_mode)147 MaybeHandle<SharedFunctionInfo> CompilationCacheScript::Lookup(
148 Handle<String> source, MaybeHandle<Object> name, int line_offset,
149 int column_offset, ScriptOriginOptions resource_options,
150 Handle<Context> native_context, LanguageMode language_mode) {
151 MaybeHandle<SharedFunctionInfo> result;
152
153 // Probe the script generation tables. Make sure not to leak handles
154 // into the caller's handle scope.
155 {
156 HandleScope scope(isolate());
157 const int generation = 0;
158 DCHECK_EQ(generations(), 1);
159 Handle<CompilationCacheTable> table = GetTable(generation);
160 MaybeHandle<SharedFunctionInfo> probe = CompilationCacheTable::LookupScript(
161 table, source, native_context, language_mode);
162 Handle<SharedFunctionInfo> function_info;
163 if (probe.ToHandle(&function_info)) {
164 // Break when we've found a suitable shared function info that
165 // matches the origin.
166 if (HasOrigin(function_info, name, line_offset, column_offset,
167 resource_options)) {
168 result = scope.CloseAndEscape(function_info);
169 }
170 }
171 }
172
173 // Once outside the manacles of the handle scope, we need to recheck
174 // to see if we actually found a cached script. If so, we return a
175 // handle created in the caller's handle scope.
176 Handle<SharedFunctionInfo> function_info;
177 if (result.ToHandle(&function_info)) {
178 #ifdef DEBUG
179 // Since HasOrigin can allocate, we need to protect the SharedFunctionInfo
180 // with handles during the call.
181 DCHECK(HasOrigin(function_info, name, line_offset, column_offset,
182 resource_options));
183 #endif
184 isolate()->counters()->compilation_cache_hits()->Increment();
185 LOG(isolate(), CompilationCacheEvent("hit", "script", *function_info));
186 } else {
187 isolate()->counters()->compilation_cache_misses()->Increment();
188 }
189 return result;
190 }
191
Put(Handle<String> source,Handle<Context> native_context,LanguageMode language_mode,Handle<SharedFunctionInfo> function_info)192 void CompilationCacheScript::Put(Handle<String> source,
193 Handle<Context> native_context,
194 LanguageMode language_mode,
195 Handle<SharedFunctionInfo> function_info) {
196 HandleScope scope(isolate());
197 Handle<CompilationCacheTable> table = GetFirstTable();
198 SetFirstTable(CompilationCacheTable::PutScript(table, source, native_context,
199 language_mode, function_info));
200 }
201
Lookup(Handle<String> source,Handle<SharedFunctionInfo> outer_info,Handle<Context> native_context,LanguageMode language_mode,int position)202 InfoCellPair CompilationCacheEval::Lookup(Handle<String> source,
203 Handle<SharedFunctionInfo> outer_info,
204 Handle<Context> native_context,
205 LanguageMode language_mode,
206 int position) {
207 HandleScope scope(isolate());
208 // Make sure not to leak the table into the surrounding handle
209 // scope. Otherwise, we risk keeping old tables around even after
210 // having cleared the cache.
211 InfoCellPair result;
212 const int generation = 0;
213 DCHECK_EQ(generations(), 1);
214 Handle<CompilationCacheTable> table = GetTable(generation);
215 result = CompilationCacheTable::LookupEval(
216 table, source, outer_info, native_context, language_mode, position);
217 if (result.has_shared()) {
218 isolate()->counters()->compilation_cache_hits()->Increment();
219 } else {
220 isolate()->counters()->compilation_cache_misses()->Increment();
221 }
222 return result;
223 }
224
Put(Handle<String> source,Handle<SharedFunctionInfo> outer_info,Handle<SharedFunctionInfo> function_info,Handle<Context> native_context,Handle<FeedbackCell> feedback_cell,int position)225 void CompilationCacheEval::Put(Handle<String> source,
226 Handle<SharedFunctionInfo> outer_info,
227 Handle<SharedFunctionInfo> function_info,
228 Handle<Context> native_context,
229 Handle<FeedbackCell> feedback_cell,
230 int position) {
231 HandleScope scope(isolate());
232 Handle<CompilationCacheTable> table = GetFirstTable();
233 table =
234 CompilationCacheTable::PutEval(table, source, outer_info, function_info,
235 native_context, feedback_cell, position);
236 SetFirstTable(table);
237 }
238
Lookup(Handle<String> source,JSRegExp::Flags flags)239 MaybeHandle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source,
240 JSRegExp::Flags flags) {
241 HandleScope scope(isolate());
242 // Make sure not to leak the table into the surrounding handle
243 // scope. Otherwise, we risk keeping old tables around even after
244 // having cleared the cache.
245 Handle<Object> result = isolate()->factory()->undefined_value();
246 int generation;
247 for (generation = 0; generation < generations(); generation++) {
248 Handle<CompilationCacheTable> table = GetTable(generation);
249 result = table->LookupRegExp(source, flags);
250 if (result->IsFixedArray()) break;
251 }
252 if (result->IsFixedArray()) {
253 Handle<FixedArray> data = Handle<FixedArray>::cast(result);
254 if (generation != 0) {
255 Put(source, flags, data);
256 }
257 isolate()->counters()->compilation_cache_hits()->Increment();
258 return scope.CloseAndEscape(data);
259 } else {
260 isolate()->counters()->compilation_cache_misses()->Increment();
261 return MaybeHandle<FixedArray>();
262 }
263 }
264
Put(Handle<String> source,JSRegExp::Flags flags,Handle<FixedArray> data)265 void CompilationCacheRegExp::Put(Handle<String> source, JSRegExp::Flags flags,
266 Handle<FixedArray> data) {
267 HandleScope scope(isolate());
268 Handle<CompilationCacheTable> table = GetFirstTable();
269 SetFirstTable(
270 CompilationCacheTable::PutRegExp(isolate(), table, source, flags, data));
271 }
272
Lookup(Handle<SharedFunctionInfo> key)273 MaybeHandle<Code> CompilationCacheCode::Lookup(Handle<SharedFunctionInfo> key) {
274 // Make sure not to leak the table into the surrounding handle
275 // scope. Otherwise, we risk keeping old tables around even after
276 // having cleared the cache.
277 HandleScope scope(isolate());
278 MaybeHandle<Code> maybe_value;
279 int generation = 0;
280 for (; generation < generations(); generation++) {
281 Handle<CompilationCacheTable> table = GetTable(generation);
282 maybe_value = table->LookupCode(key);
283 if (!maybe_value.is_null()) break;
284 }
285
286 if (maybe_value.is_null()) {
287 isolate()->counters()->compilation_cache_misses()->Increment();
288 return MaybeHandle<Code>();
289 }
290
291 Handle<Code> value = maybe_value.ToHandleChecked();
292 if (generation != 0) Put(key, value); // Add to the first generation.
293 isolate()->counters()->compilation_cache_hits()->Increment();
294 return scope.CloseAndEscape(value);
295 }
296
Put(Handle<SharedFunctionInfo> key,Handle<Code> value)297 void CompilationCacheCode::Put(Handle<SharedFunctionInfo> key,
298 Handle<Code> value) {
299 HandleScope scope(isolate());
300 Handle<CompilationCacheTable> table = GetFirstTable();
301 SetFirstTable(CompilationCacheTable::PutCode(isolate(), table, key, value));
302 }
303
TraceAgeing()304 void CompilationCacheCode::TraceAgeing() {
305 DCHECK(FLAG_trace_turbo_nci);
306 StdoutStream os;
307 os << "NCI cache ageing: Removing oldest generation" << std::endl;
308 }
309
TraceInsertion(Handle<SharedFunctionInfo> key,Handle<Code> value)310 void CompilationCacheCode::TraceInsertion(Handle<SharedFunctionInfo> key,
311 Handle<Code> value) {
312 DCHECK(FLAG_trace_turbo_nci);
313 StdoutStream os;
314 os << "NCI cache insertion: " << Brief(*key) << ", " << Brief(*value)
315 << std::endl;
316 }
317
TraceHit(Handle<SharedFunctionInfo> key,Handle<Code> value)318 void CompilationCacheCode::TraceHit(Handle<SharedFunctionInfo> key,
319 Handle<Code> value) {
320 DCHECK(FLAG_trace_turbo_nci);
321 StdoutStream os;
322 os << "NCI cache hit: " << Brief(*key) << ", " << Brief(*value) << std::endl;
323 }
324
Remove(Handle<SharedFunctionInfo> function_info)325 void CompilationCache::Remove(Handle<SharedFunctionInfo> function_info) {
326 if (!IsEnabledScriptAndEval()) return;
327
328 eval_global_.Remove(function_info);
329 eval_contextual_.Remove(function_info);
330 script_.Remove(function_info);
331 }
332
LookupScript(Handle<String> source,MaybeHandle<Object> name,int line_offset,int column_offset,ScriptOriginOptions resource_options,Handle<Context> native_context,LanguageMode language_mode)333 MaybeHandle<SharedFunctionInfo> CompilationCache::LookupScript(
334 Handle<String> source, MaybeHandle<Object> name, int line_offset,
335 int column_offset, ScriptOriginOptions resource_options,
336 Handle<Context> native_context, LanguageMode language_mode) {
337 if (!IsEnabledScriptAndEval()) return MaybeHandle<SharedFunctionInfo>();
338
339 return script_.Lookup(source, name, line_offset, column_offset,
340 resource_options, native_context, language_mode);
341 }
342
LookupEval(Handle<String> source,Handle<SharedFunctionInfo> outer_info,Handle<Context> context,LanguageMode language_mode,int position)343 InfoCellPair CompilationCache::LookupEval(Handle<String> source,
344 Handle<SharedFunctionInfo> outer_info,
345 Handle<Context> context,
346 LanguageMode language_mode,
347 int position) {
348 InfoCellPair result;
349 if (!IsEnabledScriptAndEval()) return result;
350
351 const char* cache_type;
352
353 if (context->IsNativeContext()) {
354 result = eval_global_.Lookup(source, outer_info, context, language_mode,
355 position);
356 cache_type = "eval-global";
357
358 } else {
359 DCHECK_NE(position, kNoSourcePosition);
360 Handle<Context> native_context(context->native_context(), isolate());
361 result = eval_contextual_.Lookup(source, outer_info, native_context,
362 language_mode, position);
363 cache_type = "eval-contextual";
364 }
365
366 if (result.has_shared()) {
367 LOG(isolate(), CompilationCacheEvent("hit", cache_type, result.shared()));
368 }
369
370 return result;
371 }
372
LookupRegExp(Handle<String> source,JSRegExp::Flags flags)373 MaybeHandle<FixedArray> CompilationCache::LookupRegExp(Handle<String> source,
374 JSRegExp::Flags flags) {
375 return reg_exp_.Lookup(source, flags);
376 }
377
LookupCode(Handle<SharedFunctionInfo> sfi)378 MaybeHandle<Code> CompilationCache::LookupCode(Handle<SharedFunctionInfo> sfi) {
379 return code_.Lookup(sfi);
380 }
381
PutScript(Handle<String> source,Handle<Context> native_context,LanguageMode language_mode,Handle<SharedFunctionInfo> function_info)382 void CompilationCache::PutScript(Handle<String> source,
383 Handle<Context> native_context,
384 LanguageMode language_mode,
385 Handle<SharedFunctionInfo> function_info) {
386 if (!IsEnabledScriptAndEval()) return;
387 LOG(isolate(), CompilationCacheEvent("put", "script", *function_info));
388
389 script_.Put(source, native_context, language_mode, function_info);
390 }
391
PutEval(Handle<String> source,Handle<SharedFunctionInfo> outer_info,Handle<Context> context,Handle<SharedFunctionInfo> function_info,Handle<FeedbackCell> feedback_cell,int position)392 void CompilationCache::PutEval(Handle<String> source,
393 Handle<SharedFunctionInfo> outer_info,
394 Handle<Context> context,
395 Handle<SharedFunctionInfo> function_info,
396 Handle<FeedbackCell> feedback_cell,
397 int position) {
398 if (!IsEnabledScriptAndEval()) return;
399
400 const char* cache_type;
401 HandleScope scope(isolate());
402 if (context->IsNativeContext()) {
403 eval_global_.Put(source, outer_info, function_info, context, feedback_cell,
404 position);
405 cache_type = "eval-global";
406 } else {
407 DCHECK_NE(position, kNoSourcePosition);
408 Handle<Context> native_context(context->native_context(), isolate());
409 eval_contextual_.Put(source, outer_info, function_info, native_context,
410 feedback_cell, position);
411 cache_type = "eval-contextual";
412 }
413 LOG(isolate(), CompilationCacheEvent("put", cache_type, *function_info));
414 }
415
PutRegExp(Handle<String> source,JSRegExp::Flags flags,Handle<FixedArray> data)416 void CompilationCache::PutRegExp(Handle<String> source, JSRegExp::Flags flags,
417 Handle<FixedArray> data) {
418 reg_exp_.Put(source, flags, data);
419 }
420
PutCode(Handle<SharedFunctionInfo> shared,Handle<Code> code)421 void CompilationCache::PutCode(Handle<SharedFunctionInfo> shared,
422 Handle<Code> code) {
423 code_.Put(shared, code);
424 }
425
Clear()426 void CompilationCache::Clear() {
427 for (int i = 0; i < kSubCacheCount; i++) {
428 subcaches_[i]->Clear();
429 }
430 }
431
Iterate(RootVisitor * v)432 void CompilationCache::Iterate(RootVisitor* v) {
433 for (int i = 0; i < kSubCacheCount; i++) {
434 subcaches_[i]->Iterate(v);
435 }
436 }
437
MarkCompactPrologue()438 void CompilationCache::MarkCompactPrologue() {
439 for (int i = 0; i < kSubCacheCount; i++) {
440 subcaches_[i]->Age();
441 }
442 }
443
EnableScriptAndEval()444 void CompilationCache::EnableScriptAndEval() {
445 enabled_script_and_eval_ = true;
446 }
447
DisableScriptAndEval()448 void CompilationCache::DisableScriptAndEval() {
449 enabled_script_and_eval_ = false;
450 Clear();
451 }
452
453 } // namespace internal
454 } // namespace v8
455