blob: 8dd9ec16b23d436e628068a2a236a027cd618c9d [file] [log] [blame]
Steve Blocka7e24c12009-10-30 11:49:00 +00001// 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
32namespace v8 {
33namespace internal {
34
35
36// The number of sub caches covering the different types to cache.
37static const int kSubCacheCount = 4;
38
39// The number of generations for each sub cache.
40#if defined(ANDROID)
41static const int kScriptGenerations = 1;
42static const int kEvalGlobalGenerations = 1;
43static const int kEvalContextualGenerations = 1;
44static const int kRegExpGenerations = 1;
45#else
46static const int kScriptGenerations = 5;
47static const int kEvalGlobalGenerations = 2;
48static const int kEvalContextualGenerations = 2;
49static const int kRegExpGenerations = 2;
50#endif
51
52// Initial of each compilation cache table allocated.
53static 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.
60class CompilationSubCache {
61 public:
62 explicit CompilationSubCache(int generations): generations_(generations) {
63 tables_ = NewArray<Object*>(generations);
64 }
65
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.
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.
93class CompilationCacheScript : public CompilationSubCache {
94 public:
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.
115class CompilationCacheEval: public CompilationSubCache {
116 public:
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.
131class CompilationCacheRegExp: public CompilationSubCache {
132 public:
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.
147static CompilationCacheScript script(kScriptGenerations);
148static CompilationCacheEval eval_global(kEvalGlobalGenerations);
149static CompilationCacheEval eval_contextual(kEvalContextualGenerations);
150static CompilationCacheRegExp reg_exp(kRegExpGenerations);
151static CompilationSubCache* subcaches[kSubCacheCount] =
152 {&script, &eval_global, &eval_contextual, &reg_exp};
153
154
155// Current enable state of the compilation cache.
156static bool enabled = true;
157static inline bool IsEnabled() {
158 return FLAG_compilation_cache && enabled;
159}
160
161
162static Handle<CompilationCacheTable> AllocateTable(int size) {
163 CALL_HEAP_FUNCTION(CompilationCacheTable::Allocate(size),
164 CompilationCacheTable);
165}
166
167
168Handle<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
183void 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
194void CompilationSubCache::Iterate(ObjectVisitor* v) {
195 v->VisitPointers(&tables_[0], &tables_[generations_]);
196}
197
198
199void 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.
209bool 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.
234Handle<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
288void 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
297Handle<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
327void 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
337Handle<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
367void 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
376Handle<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
388Handle<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
405Handle<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
415void 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
426void 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
445void 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
456void CompilationCache::Clear() {
457 for (int i = 0; i < kSubCacheCount; i++) {
458 subcaches[i]->Clear();
459 }
460}
461
462
463void CompilationCache::Iterate(ObjectVisitor* v) {
464 for (int i = 0; i < kSubCacheCount; i++) {
465 subcaches[i]->Iterate(v);
466 }
467}
468
469
470void CompilationCache::MarkCompactPrologue() {
471 for (int i = 0; i < kSubCacheCount; i++) {
472 subcaches[i]->Age();
473 }
474}
475
476
477void CompilationCache::Enable() {
478 enabled = true;
479}
480
481
482void CompilationCache::Disable() {
483 enabled = false;
484 Clear();
485}
486
487
488} } // namespace v8::internal