• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2008 the V8 project authors. All rights reserved.
2 // Redistribution and use in source and binary forms, with or without
3 // modification, are permitted provided that the following conditions are
4 // met:
5 //
6 //     * Redistributions of source code must retain the above copyright
7 //       notice, this list of conditions and the following disclaimer.
8 //     * Redistributions in binary form must reproduce the above
9 //       copyright notice, this list of conditions and the following
10 //       disclaimer in the documentation and/or other materials provided
11 //       with the distribution.
12 //     * Neither the name of Google Inc. nor the names of its
13 //       contributors may be used to endorse or promote products derived
14 //       from this software without specific prior written permission.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 
28 #include "v8.h"
29 
30 #include "compilation-cache.h"
31 
32 namespace v8 {
33 namespace internal {
34 
35 
36 // The number of sub caches covering the different types to cache.
37 static const int kSubCacheCount = 4;
38 
39 // The number of generations for each sub cache.
40 #if defined(ANDROID)
41 static const int kScriptGenerations = 1;
42 static const int kEvalGlobalGenerations = 1;
43 static const int kEvalContextualGenerations = 1;
44 static const int kRegExpGenerations = 1;
45 #else
46 static const int kScriptGenerations = 5;
47 static const int kEvalGlobalGenerations = 2;
48 static const int kEvalContextualGenerations = 2;
49 static const int kRegExpGenerations = 2;
50 #endif
51 
52 // Initial of each compilation cache table allocated.
53 static const int kInitialCacheSize = 64;
54 
55 // The compilation cache consists of several generational sub-caches which uses
56 // this class as a base class. A sub-cache contains a compilation cache tables
57 // for each generation of the sub-cache. As the same source code string has
58 // different compiled code for scripts and evals. Internally, we use separate
59 // sub-caches to avoid getting the wrong kind of result when looking up.
60 class CompilationSubCache {
61  public:
CompilationSubCache(int generations)62   explicit CompilationSubCache(int generations): generations_(generations) {
63     tables_ = NewArray<Object*>(generations);
64   }
65 
~CompilationSubCache()66   ~CompilationSubCache() { DeleteArray(tables_); }
67 
68   // Get the compilation cache tables for a specific generation.
69   Handle<CompilationCacheTable> GetTable(int generation);
70 
71   // Age the sub-cache by evicting the oldest generation and creating a new
72   // young generation.
73   void Age();
74 
75   // GC support.
76   void Iterate(ObjectVisitor* v);
77 
78   // Clear this sub-cache evicting all its content.
79   void Clear();
80 
81   // Number of generations in this sub-cache.
generations()82   inline int generations() { return generations_; }
83 
84  private:
85   int generations_;  // Number of generations.
86   Object** tables_;  // Compilation cache tables - one for each generation.
87 
88   DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationSubCache);
89 };
90 
91 
92 // Sub-cache for scripts.
93 class CompilationCacheScript : public CompilationSubCache {
94  public:
CompilationCacheScript(int generations)95   explicit CompilationCacheScript(int generations)
96       : CompilationSubCache(generations) { }
97 
98   Handle<JSFunction> Lookup(Handle<String> source,
99                             Handle<Object> name,
100                             int line_offset,
101                             int column_offset);
102   void Put(Handle<String> source, Handle<JSFunction> boilerplate);
103 
104  private:
105   bool HasOrigin(Handle<JSFunction> boilerplate,
106                  Handle<Object> name,
107                  int line_offset,
108                  int column_offset);
109 
110   DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheScript);
111 };
112 
113 
114 // Sub-cache for eval scripts.
115 class CompilationCacheEval: public CompilationSubCache {
116  public:
CompilationCacheEval(int generations)117   explicit CompilationCacheEval(int generations)
118       : CompilationSubCache(generations) { }
119 
120   Handle<JSFunction> Lookup(Handle<String> source, Handle<Context> context);
121 
122   void Put(Handle<String> source,
123            Handle<Context> context,
124            Handle<JSFunction> boilerplate);
125 
126   DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheEval);
127 };
128 
129 
130 // Sub-cache for regular expressions.
131 class CompilationCacheRegExp: public CompilationSubCache {
132  public:
CompilationCacheRegExp(int generations)133   explicit CompilationCacheRegExp(int generations)
134       : CompilationSubCache(generations) { }
135 
136   Handle<FixedArray> Lookup(Handle<String> source, JSRegExp::Flags flags);
137 
138   void Put(Handle<String> source,
139            JSRegExp::Flags flags,
140            Handle<FixedArray> data);
141 
142   DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheRegExp);
143 };
144 
145 
146 // Statically allocate all the sub-caches.
147 static CompilationCacheScript script(kScriptGenerations);
148 static CompilationCacheEval eval_global(kEvalGlobalGenerations);
149 static CompilationCacheEval eval_contextual(kEvalContextualGenerations);
150 static CompilationCacheRegExp reg_exp(kRegExpGenerations);
151 static CompilationSubCache* subcaches[kSubCacheCount] =
152     {&script, &eval_global, &eval_contextual, &reg_exp};
153 
154 
155 // Current enable state of the compilation cache.
156 static bool enabled = true;
IsEnabled()157 static inline bool IsEnabled() {
158   return FLAG_compilation_cache && enabled;
159 }
160 
161 
AllocateTable(int size)162 static Handle<CompilationCacheTable> AllocateTable(int size) {
163   CALL_HEAP_FUNCTION(CompilationCacheTable::Allocate(size),
164                      CompilationCacheTable);
165 }
166 
167 
GetTable(int generation)168 Handle<CompilationCacheTable> CompilationSubCache::GetTable(int generation) {
169   ASSERT(generation < generations_);
170   Handle<CompilationCacheTable> result;
171   if (tables_[generation]->IsUndefined()) {
172     result = AllocateTable(kInitialCacheSize);
173     tables_[generation] = *result;
174   } else {
175     CompilationCacheTable* table =
176         CompilationCacheTable::cast(tables_[generation]);
177     result = Handle<CompilationCacheTable>(table);
178   }
179   return result;
180 }
181 
182 
Age()183 void CompilationSubCache::Age() {
184   // Age the generations implicitly killing off the oldest.
185   for (int i = generations_ - 1; i > 0; i--) {
186     tables_[i] = tables_[i - 1];
187   }
188 
189   // Set the first generation as unborn.
190   tables_[0] = Heap::undefined_value();
191 }
192 
193 
Iterate(ObjectVisitor * v)194 void CompilationSubCache::Iterate(ObjectVisitor* v) {
195   v->VisitPointers(&tables_[0], &tables_[generations_]);
196 }
197 
198 
Clear()199 void CompilationSubCache::Clear() {
200   for (int i = 0; i < generations_; i++) {
201     tables_[i] = Heap::undefined_value();
202   }
203 }
204 
205 
206 // We only re-use a cached function for some script source code if the
207 // script originates from the same place. This is to avoid issues
208 // when reporting errors, etc.
HasOrigin(Handle<JSFunction> boilerplate,Handle<Object> name,int line_offset,int column_offset)209 bool CompilationCacheScript::HasOrigin(Handle<JSFunction> boilerplate,
210                                        Handle<Object> name,
211                                        int line_offset,
212                                        int column_offset) {
213   Handle<Script> script =
214       Handle<Script>(Script::cast(boilerplate->shared()->script()));
215   // If the script name isn't set, the boilerplate script should have
216   // an undefined name to have the same origin.
217   if (name.is_null()) {
218     return script->name()->IsUndefined();
219   }
220   // Do the fast bailout checks first.
221   if (line_offset != script->line_offset()->value()) return false;
222   if (column_offset != script->column_offset()->value()) return false;
223   // Check that both names are strings. If not, no match.
224   if (!name->IsString() || !script->name()->IsString()) return false;
225   // Compare the two name strings for equality.
226   return String::cast(*name)->Equals(String::cast(script->name()));
227 }
228 
229 
230 // TODO(245): Need to allow identical code from different contexts to
231 // be cached in the same script generation. Currently the first use
232 // will be cached, but subsequent code from different source / line
233 // won't.
Lookup(Handle<String> source,Handle<Object> name,int line_offset,int column_offset)234 Handle<JSFunction> CompilationCacheScript::Lookup(Handle<String> source,
235                                                   Handle<Object> name,
236                                                   int line_offset,
237                                                   int column_offset) {
238   Object* result = NULL;
239   int generation;
240 
241   // Probe the script generation tables. Make sure not to leak handles
242   // into the caller's handle scope.
243   { HandleScope scope;
244     for (generation = 0; generation < generations(); generation++) {
245       Handle<CompilationCacheTable> table = GetTable(generation);
246       Handle<Object> probe(table->Lookup(*source));
247       if (probe->IsJSFunction()) {
248         Handle<JSFunction> boilerplate = Handle<JSFunction>::cast(probe);
249         // Break when we've found a suitable boilerplate function that
250         // matches the origin.
251         if (HasOrigin(boilerplate, name, line_offset, column_offset)) {
252           result = *boilerplate;
253           break;
254         }
255       }
256     }
257   }
258 
259   static void* script_histogram = StatsTable::CreateHistogram(
260       "V8.ScriptCache",
261       0,
262       kScriptGenerations,
263       kScriptGenerations + 1);
264 
265   if (script_histogram != NULL) {
266     // The level NUMBER_OF_SCRIPT_GENERATIONS is equivalent to a cache miss.
267     StatsTable::AddHistogramSample(script_histogram, generation);
268   }
269 
270   // Once outside the manacles of the handle scope, we need to recheck
271   // to see if we actually found a cached script. If so, we return a
272   // handle created in the caller's handle scope.
273   if (result != NULL) {
274     Handle<JSFunction> boilerplate(JSFunction::cast(result));
275     ASSERT(HasOrigin(boilerplate, name, line_offset, column_offset));
276     // If the script was found in a later generation, we promote it to
277     // the first generation to let it survive longer in the cache.
278     if (generation != 0) Put(source, boilerplate);
279     Counters::compilation_cache_hits.Increment();
280     return boilerplate;
281   } else {
282     Counters::compilation_cache_misses.Increment();
283     return Handle<JSFunction>::null();
284   }
285 }
286 
287 
Put(Handle<String> source,Handle<JSFunction> boilerplate)288 void CompilationCacheScript::Put(Handle<String> source,
289                                  Handle<JSFunction> boilerplate) {
290   HandleScope scope;
291   ASSERT(boilerplate->IsBoilerplate());
292   Handle<CompilationCacheTable> table = GetTable(0);
293   CALL_HEAP_FUNCTION_VOID(table->Put(*source, *boilerplate));
294 }
295 
296 
Lookup(Handle<String> source,Handle<Context> context)297 Handle<JSFunction> CompilationCacheEval::Lookup(Handle<String> source,
298                                                 Handle<Context> context) {
299   // Make sure not to leak the table into the surrounding handle
300   // scope. Otherwise, we risk keeping old tables around even after
301   // having cleared the cache.
302   Object* result = NULL;
303   int generation;
304   { HandleScope scope;
305     for (generation = 0; generation < generations(); generation++) {
306       Handle<CompilationCacheTable> table = GetTable(generation);
307       result = table->LookupEval(*source, *context);
308       if (result->IsJSFunction()) {
309         break;
310       }
311     }
312   }
313   if (result->IsJSFunction()) {
314     Handle<JSFunction> boilerplate(JSFunction::cast(result));
315     if (generation != 0) {
316       Put(source, context, boilerplate);
317     }
318     Counters::compilation_cache_hits.Increment();
319     return boilerplate;
320   } else {
321     Counters::compilation_cache_misses.Increment();
322     return Handle<JSFunction>::null();
323   }
324 }
325 
326 
Put(Handle<String> source,Handle<Context> context,Handle<JSFunction> boilerplate)327 void CompilationCacheEval::Put(Handle<String> source,
328                                Handle<Context> context,
329                                Handle<JSFunction> boilerplate) {
330   HandleScope scope;
331   ASSERT(boilerplate->IsBoilerplate());
332   Handle<CompilationCacheTable> table = GetTable(0);
333   CALL_HEAP_FUNCTION_VOID(table->PutEval(*source, *context, *boilerplate));
334 }
335 
336 
Lookup(Handle<String> source,JSRegExp::Flags flags)337 Handle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source,
338                                                   JSRegExp::Flags flags) {
339   // Make sure not to leak the table into the surrounding handle
340   // scope. Otherwise, we risk keeping old tables around even after
341   // having cleared the cache.
342   Object* result = NULL;
343   int generation;
344   { HandleScope scope;
345     for (generation = 0; generation < generations(); generation++) {
346       Handle<CompilationCacheTable> table = GetTable(generation);
347       result = table->LookupRegExp(*source, flags);
348       if (result->IsFixedArray()) {
349         break;
350       }
351     }
352   }
353   if (result->IsFixedArray()) {
354     Handle<FixedArray> data(FixedArray::cast(result));
355     if (generation != 0) {
356       Put(source, flags, data);
357     }
358     Counters::compilation_cache_hits.Increment();
359     return data;
360   } else {
361     Counters::compilation_cache_misses.Increment();
362     return Handle<FixedArray>::null();
363   }
364 }
365 
366 
Put(Handle<String> source,JSRegExp::Flags flags,Handle<FixedArray> data)367 void CompilationCacheRegExp::Put(Handle<String> source,
368                                  JSRegExp::Flags flags,
369                                  Handle<FixedArray> data) {
370   HandleScope scope;
371   Handle<CompilationCacheTable> table = GetTable(0);
372   CALL_HEAP_FUNCTION_VOID(table->PutRegExp(*source, flags, *data));
373 }
374 
375 
LookupScript(Handle<String> source,Handle<Object> name,int line_offset,int column_offset)376 Handle<JSFunction> CompilationCache::LookupScript(Handle<String> source,
377                                                   Handle<Object> name,
378                                                   int line_offset,
379                                                   int column_offset) {
380   if (!IsEnabled()) {
381     return Handle<JSFunction>::null();
382   }
383 
384   return script.Lookup(source, name, line_offset, column_offset);
385 }
386 
387 
LookupEval(Handle<String> source,Handle<Context> context,bool is_global)388 Handle<JSFunction> CompilationCache::LookupEval(Handle<String> source,
389                                                 Handle<Context> context,
390                                                 bool is_global) {
391   if (!IsEnabled()) {
392     return Handle<JSFunction>::null();
393   }
394 
395   Handle<JSFunction> result;
396   if (is_global) {
397     result = eval_global.Lookup(source, context);
398   } else {
399     result = eval_contextual.Lookup(source, context);
400   }
401   return result;
402 }
403 
404 
LookupRegExp(Handle<String> source,JSRegExp::Flags flags)405 Handle<FixedArray> CompilationCache::LookupRegExp(Handle<String> source,
406                                                   JSRegExp::Flags flags) {
407   if (!IsEnabled()) {
408     return Handle<FixedArray>::null();
409   }
410 
411   return reg_exp.Lookup(source, flags);
412 }
413 
414 
PutScript(Handle<String> source,Handle<JSFunction> boilerplate)415 void CompilationCache::PutScript(Handle<String> source,
416                                  Handle<JSFunction> boilerplate) {
417   if (!IsEnabled()) {
418     return;
419   }
420 
421   ASSERT(boilerplate->IsBoilerplate());
422   script.Put(source, boilerplate);
423 }
424 
425 
PutEval(Handle<String> source,Handle<Context> context,bool is_global,Handle<JSFunction> boilerplate)426 void CompilationCache::PutEval(Handle<String> source,
427                                Handle<Context> context,
428                                bool is_global,
429                                Handle<JSFunction> boilerplate) {
430   if (!IsEnabled()) {
431     return;
432   }
433 
434   HandleScope scope;
435   ASSERT(boilerplate->IsBoilerplate());
436   if (is_global) {
437     eval_global.Put(source, context, boilerplate);
438   } else {
439     eval_contextual.Put(source, context, boilerplate);
440   }
441 }
442 
443 
444 
PutRegExp(Handle<String> source,JSRegExp::Flags flags,Handle<FixedArray> data)445 void CompilationCache::PutRegExp(Handle<String> source,
446                                  JSRegExp::Flags flags,
447                                  Handle<FixedArray> data) {
448   if (!IsEnabled()) {
449     return;
450   }
451 
452   reg_exp.Put(source, flags, data);
453 }
454 
455 
Clear()456 void CompilationCache::Clear() {
457   for (int i = 0; i < kSubCacheCount; i++) {
458     subcaches[i]->Clear();
459   }
460 }
461 
462 
Iterate(ObjectVisitor * v)463 void CompilationCache::Iterate(ObjectVisitor* v) {
464   for (int i = 0; i < kSubCacheCount; i++) {
465     subcaches[i]->Iterate(v);
466   }
467 }
468 
469 
MarkCompactPrologue()470 void CompilationCache::MarkCompactPrologue() {
471   for (int i = 0; i < kSubCacheCount; i++) {
472     subcaches[i]->Age();
473   }
474 }
475 
476 
Enable()477 void CompilationCache::Enable() {
478   enabled = true;
479 }
480 
481 
Disable()482 void CompilationCache::Disable() {
483   enabled = false;
484   Clear();
485 }
486 
487 
488 } }  // namespace v8::internal
489