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(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 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 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 110 void CompilationSubCache::Iterate(ObjectVisitor* v) { 111 v->VisitPointers(&tables_[0], &tables_[generations_]); 112 } 113 114 115 void CompilationSubCache::Clear() { 116 MemsetPointer(tables_, isolate()->heap()->undefined_value(), generations_); 117 } 118 119 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 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. 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. 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 469 void CompilationCache::Clear() { 470 for (int i = 0; i < kSubCacheCount; i++) { 471 subcaches_[i]->Clear(); 472 } 473 } 474 475 476 void CompilationCache::Iterate(ObjectVisitor* v) { 477 for (int i = 0; i < kSubCacheCount; i++) { 478 subcaches_[i]->Iterate(v); 479 } 480 } 481 482 483 void CompilationCache::IterateFunctions(ObjectVisitor* v) { 484 for (int i = 0; i < kSubCacheCount; i++) { 485 subcaches_[i]->IterateFunctions(v); 486 } 487 } 488 489 490 void CompilationCache::MarkCompactPrologue() { 491 for (int i = 0; i < kSubCacheCount; i++) { 492 subcaches_[i]->Age(); 493 } 494 } 495 496 497 void CompilationCache::Enable() { 498 enabled_ = true; 499 } 500 501 502 void CompilationCache::Disable() { 503 enabled_ = false; 504 Clear(); 505 } 506 507 508 } } // namespace v8::internal 509