• 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/codegen/script-details.h"
8 #include "src/common/globals.h"
9 #include "src/heap/factory.h"
10 #include "src/logging/counters.h"
11 #include "src/logging/log.h"
12 #include "src/objects/compilation-cache-table-inl.h"
13 #include "src/objects/objects-inl.h"
14 #include "src/objects/slots.h"
15 #include "src/objects/visitors.h"
16 #include "src/utils/ostreams.h"
17 
18 namespace v8 {
19 namespace internal {
20 
21 // The number of generations for each sub cache.
22 static const int kRegExpGenerations = 2;
23 
24 // Initial size of each compilation cache table allocated.
25 static const int kInitialCacheSize = 64;
26 
CompilationCache(Isolate * isolate)27 CompilationCache::CompilationCache(Isolate* isolate)
28     : isolate_(isolate),
29       script_(isolate),
30       eval_global_(isolate),
31       eval_contextual_(isolate),
32       reg_exp_(isolate, kRegExpGenerations),
33       enabled_script_and_eval_(true) {
34   CompilationSubCache* subcaches[kSubCacheCount] = {
35       &script_, &eval_global_, &eval_contextual_, &reg_exp_};
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(c->isolate());
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); }
80 
Iterate(RootVisitor * v)81 void CompilationSubCache::Iterate(RootVisitor* v) {
82   v->VisitRootPointers(Root::kCompilationCache, nullptr,
83                        FullObjectSlot(&tables_[0]),
84                        FullObjectSlot(&tables_[generations()]));
85 }
86 
Clear()87 void CompilationSubCache::Clear() {
88   MemsetPointer(reinterpret_cast<Address*>(tables_),
89                 ReadOnlyRoots(isolate()).undefined_value().ptr(),
90                 generations());
91 }
92 
Remove(Handle<SharedFunctionInfo> function_info)93 void CompilationSubCache::Remove(Handle<SharedFunctionInfo> function_info) {
94   // Probe the script generation tables. Make sure not to leak handles
95   // into the caller's handle scope.
96   {
97     HandleScope scope(isolate());
98     for (int generation = 0; generation < generations(); generation++) {
99       Handle<CompilationCacheTable> table = GetTable(generation);
100       table->Remove(*function_info);
101     }
102   }
103 }
104 
CompilationCacheScript(Isolate * isolate)105 CompilationCacheScript::CompilationCacheScript(Isolate* isolate)
106     : CompilationSubCache(isolate, 1) {}
107 
108 namespace {
109 
110 // We only re-use a cached function for some script source code if the
111 // script originates from the same place. This is to avoid issues
112 // when reporting errors, etc.
HasOrigin(Isolate * isolate,Handle<SharedFunctionInfo> function_info,const ScriptDetails & script_details)113 bool HasOrigin(Isolate* isolate, Handle<SharedFunctionInfo> function_info,
114                const ScriptDetails& script_details) {
115   Handle<Script> script =
116       Handle<Script>(Script::cast(function_info->script()), isolate);
117   // If the script name isn't set, the boilerplate script should have
118   // an undefined name to have the same origin.
119   Handle<Object> name;
120   if (!script_details.name_obj.ToHandle(&name)) {
121     return script->name().IsUndefined(isolate);
122   }
123   // Do the fast bailout checks first.
124   if (script_details.line_offset != script->line_offset()) return false;
125   if (script_details.column_offset != script->column_offset()) return false;
126   // Check that both names are strings. If not, no match.
127   if (!name->IsString() || !script->name().IsString()) return false;
128   // Are the origin_options same?
129   if (script_details.origin_options.Flags() !=
130       script->origin_options().Flags()) {
131     return false;
132   }
133   // Compare the two name strings for equality.
134   if (!String::Equals(isolate, Handle<String>::cast(name),
135                       Handle<String>(String::cast(script->name()), isolate))) {
136     return false;
137   }
138 
139   // TODO(cbruni, chromium:1244145): Remove once migrated to the context
140   Handle<Object> maybe_host_defined_options;
141   if (!script_details.host_defined_options.ToHandle(
142           &maybe_host_defined_options)) {
143     maybe_host_defined_options = isolate->factory()->empty_fixed_array();
144   }
145   Handle<FixedArray> host_defined_options =
146       Handle<FixedArray>::cast(maybe_host_defined_options);
147   Handle<FixedArray> script_options(
148       FixedArray::cast(script->host_defined_options()), isolate);
149   int length = host_defined_options->length();
150   if (length != script_options->length()) return false;
151 
152   for (int i = 0; i < length; i++) {
153     // host-defined options is a v8::PrimitiveArray.
154     DCHECK(host_defined_options->get(i).IsPrimitive());
155     DCHECK(script_options->get(i).IsPrimitive());
156     if (!host_defined_options->get(i).StrictEquals(script_options->get(i))) {
157       return false;
158     }
159   }
160   return true;
161 }
162 }  // namespace
163 
164 // TODO(245): Need to allow identical code from different contexts to
165 // be cached in the same script generation. Currently the first use
166 // will be cached, but subsequent code from different source / line
167 // won't.
Lookup(Handle<String> source,const ScriptDetails & script_details,LanguageMode language_mode)168 MaybeHandle<SharedFunctionInfo> CompilationCacheScript::Lookup(
169     Handle<String> source, const ScriptDetails& script_details,
170     LanguageMode language_mode) {
171   MaybeHandle<SharedFunctionInfo> result;
172 
173   // Probe the script generation tables. Make sure not to leak handles
174   // into the caller's handle scope.
175   {
176     HandleScope scope(isolate());
177     const int generation = 0;
178     DCHECK_EQ(generations(), 1);
179     Handle<CompilationCacheTable> table = GetTable(generation);
180     MaybeHandle<SharedFunctionInfo> probe = CompilationCacheTable::LookupScript(
181         table, source, language_mode, isolate());
182     Handle<SharedFunctionInfo> function_info;
183     if (probe.ToHandle(&function_info)) {
184       // Break when we've found a suitable shared function info that
185       // matches the origin.
186       if (HasOrigin(isolate(), function_info, script_details)) {
187         result = scope.CloseAndEscape(function_info);
188       }
189     }
190   }
191 
192   // Once outside the manacles of the handle scope, we need to recheck
193   // to see if we actually found a cached script. If so, we return a
194   // handle created in the caller's handle scope.
195   Handle<SharedFunctionInfo> function_info;
196   if (result.ToHandle(&function_info)) {
197     // Since HasOrigin can allocate, we need to protect the SharedFunctionInfo
198     // with handles during the call.
199     DCHECK(HasOrigin(isolate(), function_info, script_details));
200     isolate()->counters()->compilation_cache_hits()->Increment();
201     LOG(isolate(), CompilationCacheEvent("hit", "script", *function_info));
202   } else {
203     isolate()->counters()->compilation_cache_misses()->Increment();
204   }
205   return result;
206 }
207 
Put(Handle<String> source,LanguageMode language_mode,Handle<SharedFunctionInfo> function_info)208 void CompilationCacheScript::Put(Handle<String> source,
209                                  LanguageMode language_mode,
210                                  Handle<SharedFunctionInfo> function_info) {
211   HandleScope scope(isolate());
212   Handle<CompilationCacheTable> table = GetFirstTable();
213   SetFirstTable(CompilationCacheTable::PutScript(table, source, language_mode,
214                                                  function_info, isolate()));
215 }
216 
Lookup(Handle<String> source,Handle<SharedFunctionInfo> outer_info,Handle<Context> native_context,LanguageMode language_mode,int position)217 InfoCellPair CompilationCacheEval::Lookup(Handle<String> source,
218                                           Handle<SharedFunctionInfo> outer_info,
219                                           Handle<Context> native_context,
220                                           LanguageMode language_mode,
221                                           int position) {
222   HandleScope scope(isolate());
223   // Make sure not to leak the table into the surrounding handle
224   // scope. Otherwise, we risk keeping old tables around even after
225   // having cleared the cache.
226   InfoCellPair result;
227   const int generation = 0;
228   DCHECK_EQ(generations(), 1);
229   Handle<CompilationCacheTable> table = GetTable(generation);
230   result = CompilationCacheTable::LookupEval(
231       table, source, outer_info, native_context, language_mode, position);
232   if (result.has_shared()) {
233     isolate()->counters()->compilation_cache_hits()->Increment();
234   } else {
235     isolate()->counters()->compilation_cache_misses()->Increment();
236   }
237   return result;
238 }
239 
Put(Handle<String> source,Handle<SharedFunctionInfo> outer_info,Handle<SharedFunctionInfo> function_info,Handle<Context> native_context,Handle<FeedbackCell> feedback_cell,int position)240 void CompilationCacheEval::Put(Handle<String> source,
241                                Handle<SharedFunctionInfo> outer_info,
242                                Handle<SharedFunctionInfo> function_info,
243                                Handle<Context> native_context,
244                                Handle<FeedbackCell> feedback_cell,
245                                int position) {
246   HandleScope scope(isolate());
247   Handle<CompilationCacheTable> table = GetFirstTable();
248   table =
249       CompilationCacheTable::PutEval(table, source, outer_info, function_info,
250                                      native_context, feedback_cell, position);
251   SetFirstTable(table);
252 }
253 
Lookup(Handle<String> source,JSRegExp::Flags flags)254 MaybeHandle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source,
255                                                        JSRegExp::Flags flags) {
256   HandleScope scope(isolate());
257   // Make sure not to leak the table into the surrounding handle
258   // scope. Otherwise, we risk keeping old tables around even after
259   // having cleared the cache.
260   Handle<Object> result = isolate()->factory()->undefined_value();
261   int generation;
262   for (generation = 0; generation < generations(); generation++) {
263     Handle<CompilationCacheTable> table = GetTable(generation);
264     result = table->LookupRegExp(source, flags);
265     if (result->IsFixedArray()) break;
266   }
267   if (result->IsFixedArray()) {
268     Handle<FixedArray> data = Handle<FixedArray>::cast(result);
269     if (generation != 0) {
270       Put(source, flags, data);
271     }
272     isolate()->counters()->compilation_cache_hits()->Increment();
273     return scope.CloseAndEscape(data);
274   } else {
275     isolate()->counters()->compilation_cache_misses()->Increment();
276     return MaybeHandle<FixedArray>();
277   }
278 }
279 
Put(Handle<String> source,JSRegExp::Flags flags,Handle<FixedArray> data)280 void CompilationCacheRegExp::Put(Handle<String> source, JSRegExp::Flags flags,
281                                  Handle<FixedArray> data) {
282   HandleScope scope(isolate());
283   Handle<CompilationCacheTable> table = GetFirstTable();
284   SetFirstTable(
285       CompilationCacheTable::PutRegExp(isolate(), table, source, flags, data));
286 }
287 
Remove(Handle<SharedFunctionInfo> function_info)288 void CompilationCache::Remove(Handle<SharedFunctionInfo> function_info) {
289   if (!IsEnabledScriptAndEval()) return;
290 
291   eval_global_.Remove(function_info);
292   eval_contextual_.Remove(function_info);
293   script_.Remove(function_info);
294 }
295 
LookupScript(Handle<String> source,const ScriptDetails & script_details,LanguageMode language_mode)296 MaybeHandle<SharedFunctionInfo> CompilationCache::LookupScript(
297     Handle<String> source, const ScriptDetails& script_details,
298     LanguageMode language_mode) {
299   if (!IsEnabledScriptAndEval()) return MaybeHandle<SharedFunctionInfo>();
300   return script_.Lookup(source, script_details, language_mode);
301 }
302 
LookupEval(Handle<String> source,Handle<SharedFunctionInfo> outer_info,Handle<Context> context,LanguageMode language_mode,int position)303 InfoCellPair CompilationCache::LookupEval(Handle<String> source,
304                                           Handle<SharedFunctionInfo> outer_info,
305                                           Handle<Context> context,
306                                           LanguageMode language_mode,
307                                           int position) {
308   InfoCellPair result;
309   if (!IsEnabledScriptAndEval()) return result;
310 
311   const char* cache_type;
312 
313   if (context->IsNativeContext()) {
314     result = eval_global_.Lookup(source, outer_info, context, language_mode,
315                                  position);
316     cache_type = "eval-global";
317 
318   } else {
319     DCHECK_NE(position, kNoSourcePosition);
320     Handle<Context> native_context(context->native_context(), isolate());
321     result = eval_contextual_.Lookup(source, outer_info, native_context,
322                                      language_mode, position);
323     cache_type = "eval-contextual";
324   }
325 
326   if (result.has_shared()) {
327     LOG(isolate(), CompilationCacheEvent("hit", cache_type, result.shared()));
328   }
329 
330   return result;
331 }
332 
LookupRegExp(Handle<String> source,JSRegExp::Flags flags)333 MaybeHandle<FixedArray> CompilationCache::LookupRegExp(Handle<String> source,
334                                                        JSRegExp::Flags flags) {
335   return reg_exp_.Lookup(source, flags);
336 }
337 
PutScript(Handle<String> source,LanguageMode language_mode,Handle<SharedFunctionInfo> function_info)338 void CompilationCache::PutScript(Handle<String> source,
339                                  LanguageMode language_mode,
340                                  Handle<SharedFunctionInfo> function_info) {
341   if (!IsEnabledScriptAndEval()) return;
342   LOG(isolate(), CompilationCacheEvent("put", "script", *function_info));
343 
344   script_.Put(source, language_mode, function_info);
345 }
346 
PutEval(Handle<String> source,Handle<SharedFunctionInfo> outer_info,Handle<Context> context,Handle<SharedFunctionInfo> function_info,Handle<FeedbackCell> feedback_cell,int position)347 void CompilationCache::PutEval(Handle<String> source,
348                                Handle<SharedFunctionInfo> outer_info,
349                                Handle<Context> context,
350                                Handle<SharedFunctionInfo> function_info,
351                                Handle<FeedbackCell> feedback_cell,
352                                int position) {
353   if (!IsEnabledScriptAndEval()) return;
354 
355   const char* cache_type;
356   HandleScope scope(isolate());
357   if (context->IsNativeContext()) {
358     eval_global_.Put(source, outer_info, function_info, context, feedback_cell,
359                      position);
360     cache_type = "eval-global";
361   } else {
362     DCHECK_NE(position, kNoSourcePosition);
363     Handle<Context> native_context(context->native_context(), isolate());
364     eval_contextual_.Put(source, outer_info, function_info, native_context,
365                          feedback_cell, position);
366     cache_type = "eval-contextual";
367   }
368   LOG(isolate(), CompilationCacheEvent("put", cache_type, *function_info));
369 }
370 
PutRegExp(Handle<String> source,JSRegExp::Flags flags,Handle<FixedArray> data)371 void CompilationCache::PutRegExp(Handle<String> source, JSRegExp::Flags flags,
372                                  Handle<FixedArray> data) {
373   reg_exp_.Put(source, flags, data);
374 }
375 
Clear()376 void CompilationCache::Clear() {
377   for (int i = 0; i < kSubCacheCount; i++) {
378     subcaches_[i]->Clear();
379   }
380 }
381 
Iterate(RootVisitor * v)382 void CompilationCache::Iterate(RootVisitor* v) {
383   for (int i = 0; i < kSubCacheCount; i++) {
384     subcaches_[i]->Iterate(v);
385   }
386 }
387 
MarkCompactPrologue()388 void CompilationCache::MarkCompactPrologue() {
389   for (int i = 0; i < kSubCacheCount; i++) {
390     subcaches_[i]->Age();
391   }
392 }
393 
EnableScriptAndEval()394 void CompilationCache::EnableScriptAndEval() {
395   enabled_script_and_eval_ = true;
396 }
397 
DisableScriptAndEval()398 void CompilationCache::DisableScriptAndEval() {
399   enabled_script_and_eval_ = false;
400   Clear();
401 }
402 
403 }  // namespace internal
404 }  // namespace v8
405