• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_, &reg_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