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, ®_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