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