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
34 namespace v8 {
35 namespace 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
41 static const int kScriptGenerations = 5;
42 static const int kEvalGlobalGenerations = 2;
43 static const int kEvalContextualGenerations = 2;
44 static const int kRegExpGenerations = 2;
45
46 // Initial size of each compilation cache table allocated.
47 static const int kInitialCacheSize = 64;
48
49
CompilationCache(Isolate * isolate)50 CompilationCache::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_, ®_exp_};
59 for (int i = 0; i < kSubCacheCount; ++i) {
60 subcaches_[i] = subcaches[i];
61 }
62 }
63
64
~CompilationCache()65 CompilationCache::~CompilationCache() {}
66
67
AllocateTable(Isolate * isolate,int size)68 static Handle<CompilationCacheTable> AllocateTable(Isolate* isolate, int size) {
69 CALL_HEAP_FUNCTION(isolate,
70 CompilationCacheTable::Allocate(size),
71 CompilationCacheTable);
72 }
73
74
GetTable(int generation)75 Handle<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
Age()89 void 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
IterateFunctions(ObjectVisitor * v)100 void 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
Iterate(ObjectVisitor * v)110 void CompilationSubCache::Iterate(ObjectVisitor* v) {
111 v->VisitPointers(&tables_[0], &tables_[generations_]);
112 }
113
114
Clear()115 void CompilationSubCache::Clear() {
116 MemsetPointer(tables_, isolate()->heap()->undefined_value(), generations_);
117 }
118
119
Remove(Handle<SharedFunctionInfo> function_info)120 void 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
CompilationCacheScript(Isolate * isolate,int generations)132 CompilationCacheScript::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.
HasOrigin(Handle<SharedFunctionInfo> function_info,Handle<Object> name,int line_offset,int column_offset)142 bool 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.
Lookup(Handle<String> source,Handle<Object> name,int line_offset,int column_offset)168 Handle<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
TryTablePut(Handle<String> source,Handle<SharedFunctionInfo> function_info)227 MaybeObject* 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
TablePut(Handle<String> source,Handle<SharedFunctionInfo> function_info)235 Handle<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
Put(Handle<String> source,Handle<SharedFunctionInfo> function_info)244 void CompilationCacheScript::Put(Handle<String> source,
245 Handle<SharedFunctionInfo> function_info) {
246 HandleScope scope(isolate());
247 SetFirstTable(TablePut(source, function_info));
248 }
249
250
Lookup(Handle<String> source,Handle<Context> context,LanguageMode language_mode,int scope_position)251 Handle<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
TryTablePut(Handle<String> source,Handle<Context> context,Handle<SharedFunctionInfo> function_info,int scope_position)286 MaybeObject* 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
TablePut(Handle<String> source,Handle<Context> context,Handle<SharedFunctionInfo> function_info,int scope_position)296 Handle<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
Put(Handle<String> source,Handle<Context> context,Handle<SharedFunctionInfo> function_info,int scope_position)308 void 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
Lookup(Handle<String> source,JSRegExp::Flags flags)317 Handle<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
TryTablePut(Handle<String> source,JSRegExp::Flags flags,Handle<FixedArray> data)347 MaybeObject* 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
TablePut(Handle<String> source,JSRegExp::Flags flags,Handle<FixedArray> data)356 Handle<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
Put(Handle<String> source,JSRegExp::Flags flags,Handle<FixedArray> data)366 void 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
Remove(Handle<SharedFunctionInfo> function_info)374 void 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
LookupScript(Handle<String> source,Handle<Object> name,int line_offset,int column_offset)383 Handle<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
LookupEval(Handle<String> source,Handle<Context> context,bool is_global,LanguageMode language_mode,int scope_position)395 Handle<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
LookupRegExp(Handle<String> source,JSRegExp::Flags flags)418 Handle<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
PutScript(Handle<String> source,Handle<SharedFunctionInfo> function_info)428 void 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
PutEval(Handle<String> source,Handle<Context> context,bool is_global,Handle<SharedFunctionInfo> function_info,int scope_position)438 void 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
PutRegExp(Handle<String> source,JSRegExp::Flags flags,Handle<FixedArray> data)458 void 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
Clear()469 void CompilationCache::Clear() {
470 for (int i = 0; i < kSubCacheCount; i++) {
471 subcaches_[i]->Clear();
472 }
473 }
474
475
Iterate(ObjectVisitor * v)476 void CompilationCache::Iterate(ObjectVisitor* v) {
477 for (int i = 0; i < kSubCacheCount; i++) {
478 subcaches_[i]->Iterate(v);
479 }
480 }
481
482
IterateFunctions(ObjectVisitor * v)483 void CompilationCache::IterateFunctions(ObjectVisitor* v) {
484 for (int i = 0; i < kSubCacheCount; i++) {
485 subcaches_[i]->IterateFunctions(v);
486 }
487 }
488
489
MarkCompactPrologue()490 void CompilationCache::MarkCompactPrologue() {
491 for (int i = 0; i < kSubCacheCount; i++) {
492 subcaches_[i]->Age();
493 }
494 }
495
496
Enable()497 void CompilationCache::Enable() {
498 enabled_ = true;
499 }
500
501
Disable()502 void CompilationCache::Disable() {
503 enabled_ = false;
504 Clear();
505 }
506
507
508 } } // namespace v8::internal
509