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