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