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