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 #include "serialize.h" 32 33 namespace v8 { 34 namespace internal { 35 36 37 // The number of sub caches covering the different types to cache. 38 static const int kSubCacheCount = 4; 39 40 // The number of generations for each sub cache. 41 // The number of ScriptGenerations is carefully chosen based on histograms. 42 // See issue 458: http://code.google.com/p/v8/issues/detail?id=458 43 static const int kScriptGenerations = 5; 44 static const int kEvalGlobalGenerations = 2; 45 static const int kEvalContextualGenerations = 2; 46 static const int kRegExpGenerations = 2; 47 48 // Initial size of each compilation cache table allocated. 49 static const int kInitialCacheSize = 64; 50 51 // The compilation cache consists of several generational sub-caches which uses 52 // this class as a base class. A sub-cache contains a compilation cache tables 53 // for each generation of the sub-cache. Since the same source code string has 54 // different compiled code for scripts and evals, we use separate sub-caches 55 // for different compilation modes, to avoid retrieving the wrong result. 56 class CompilationSubCache { 57 public: 58 explicit CompilationSubCache(int generations): generations_(generations) { 59 tables_ = NewArray<Object*>(generations); 60 } 61 62 ~CompilationSubCache() { DeleteArray(tables_); } 63 64 // Get the compilation cache tables for a specific generation. 65 Handle<CompilationCacheTable> GetTable(int generation); 66 67 // Age the sub-cache by evicting the oldest generation and creating a new 68 // young generation. 69 void Age(); 70 71 // GC support. 72 void Iterate(ObjectVisitor* v); 73 74 // Clear this sub-cache evicting all its content. 75 void Clear(); 76 77 // Number of generations in this sub-cache. 78 inline int generations() { return generations_; } 79 80 private: 81 int generations_; // Number of generations. 82 Object** tables_; // Compilation cache tables - one for each generation. 83 84 DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationSubCache); 85 }; 86 87 88 // Sub-cache for scripts. 89 class CompilationCacheScript : public CompilationSubCache { 90 public: 91 explicit CompilationCacheScript(int generations) 92 : CompilationSubCache(generations) { } 93 94 Handle<JSFunction> Lookup(Handle<String> source, 95 Handle<Object> name, 96 int line_offset, 97 int column_offset); 98 void Put(Handle<String> source, Handle<JSFunction> boilerplate); 99 100 private: 101 bool HasOrigin(Handle<JSFunction> boilerplate, 102 Handle<Object> name, 103 int line_offset, 104 int column_offset); 105 106 DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheScript); 107 }; 108 109 110 // Sub-cache for eval scripts. 111 class CompilationCacheEval: public CompilationSubCache { 112 public: 113 explicit CompilationCacheEval(int generations) 114 : CompilationSubCache(generations) { } 115 116 Handle<JSFunction> Lookup(Handle<String> source, Handle<Context> context); 117 118 void Put(Handle<String> source, 119 Handle<Context> context, 120 Handle<JSFunction> boilerplate); 121 122 DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheEval); 123 }; 124 125 126 // Sub-cache for regular expressions. 127 class CompilationCacheRegExp: public CompilationSubCache { 128 public: 129 explicit CompilationCacheRegExp(int generations) 130 : CompilationSubCache(generations) { } 131 132 Handle<FixedArray> Lookup(Handle<String> source, JSRegExp::Flags flags); 133 134 void Put(Handle<String> source, 135 JSRegExp::Flags flags, 136 Handle<FixedArray> data); 137 138 DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheRegExp); 139 }; 140 141 142 // Statically allocate all the sub-caches. 143 static CompilationCacheScript script(kScriptGenerations); 144 static CompilationCacheEval eval_global(kEvalGlobalGenerations); 145 static CompilationCacheEval eval_contextual(kEvalContextualGenerations); 146 static CompilationCacheRegExp reg_exp(kRegExpGenerations); 147 static CompilationSubCache* subcaches[kSubCacheCount] = 148 {&script, &eval_global, &eval_contextual, ®_exp}; 149 150 151 // Current enable state of the compilation cache. 152 static bool enabled = true; 153 static inline bool IsEnabled() { 154 return FLAG_compilation_cache && enabled; 155 } 156 157 158 static Handle<CompilationCacheTable> AllocateTable(int size) { 159 CALL_HEAP_FUNCTION(CompilationCacheTable::Allocate(size), 160 CompilationCacheTable); 161 } 162 163 164 Handle<CompilationCacheTable> CompilationSubCache::GetTable(int generation) { 165 ASSERT(generation < generations_); 166 Handle<CompilationCacheTable> result; 167 if (tables_[generation]->IsUndefined()) { 168 result = AllocateTable(kInitialCacheSize); 169 tables_[generation] = *result; 170 } else { 171 CompilationCacheTable* table = 172 CompilationCacheTable::cast(tables_[generation]); 173 result = Handle<CompilationCacheTable>(table); 174 } 175 return result; 176 } 177 178 179 void CompilationSubCache::Age() { 180 // Age the generations implicitly killing off the oldest. 181 for (int i = generations_ - 1; i > 0; i--) { 182 tables_[i] = tables_[i - 1]; 183 } 184 185 // Set the first generation as unborn. 186 tables_[0] = Heap::undefined_value(); 187 } 188 189 190 void CompilationSubCache::Iterate(ObjectVisitor* v) { 191 v->VisitPointers(&tables_[0], &tables_[generations_]); 192 } 193 194 195 void CompilationSubCache::Clear() { 196 for (int i = 0; i < generations_; i++) { 197 tables_[i] = Heap::undefined_value(); 198 } 199 } 200 201 202 // We only re-use a cached function for some script source code if the 203 // script originates from the same place. This is to avoid issues 204 // when reporting errors, etc. 205 bool CompilationCacheScript::HasOrigin(Handle<JSFunction> boilerplate, 206 Handle<Object> name, 207 int line_offset, 208 int column_offset) { 209 Handle<Script> script = 210 Handle<Script>(Script::cast(boilerplate->shared()->script())); 211 // If the script name isn't set, the boilerplate script should have 212 // an undefined name to have the same origin. 213 if (name.is_null()) { 214 return script->name()->IsUndefined(); 215 } 216 // Do the fast bailout checks first. 217 if (line_offset != script->line_offset()->value()) return false; 218 if (column_offset != script->column_offset()->value()) return false; 219 // Check that both names are strings. If not, no match. 220 if (!name->IsString() || !script->name()->IsString()) return false; 221 // Compare the two name strings for equality. 222 return String::cast(*name)->Equals(String::cast(script->name())); 223 } 224 225 226 // TODO(245): Need to allow identical code from different contexts to 227 // be cached in the same script generation. Currently the first use 228 // will be cached, but subsequent code from different source / line 229 // won't. 230 Handle<JSFunction> CompilationCacheScript::Lookup(Handle<String> source, 231 Handle<Object> name, 232 int line_offset, 233 int column_offset) { 234 Object* result = NULL; 235 int generation; 236 237 // Probe the script generation tables. Make sure not to leak handles 238 // into the caller's handle scope. 239 { HandleScope scope; 240 for (generation = 0; generation < generations(); generation++) { 241 Handle<CompilationCacheTable> table = GetTable(generation); 242 Handle<Object> probe(table->Lookup(*source)); 243 if (probe->IsJSFunction()) { 244 Handle<JSFunction> boilerplate = Handle<JSFunction>::cast(probe); 245 // Break when we've found a suitable boilerplate function that 246 // matches the origin. 247 if (HasOrigin(boilerplate, name, line_offset, column_offset)) { 248 result = *boilerplate; 249 break; 250 } 251 } 252 } 253 } 254 255 static void* script_histogram = StatsTable::CreateHistogram( 256 "V8.ScriptCache", 257 0, 258 kScriptGenerations, 259 kScriptGenerations + 1); 260 261 if (script_histogram != NULL) { 262 // The level NUMBER_OF_SCRIPT_GENERATIONS is equivalent to a cache miss. 263 StatsTable::AddHistogramSample(script_histogram, generation); 264 } 265 266 // Once outside the manacles of the handle scope, we need to recheck 267 // to see if we actually found a cached script. If so, we return a 268 // handle created in the caller's handle scope. 269 if (result != NULL) { 270 Handle<JSFunction> boilerplate(JSFunction::cast(result)); 271 ASSERT(HasOrigin(boilerplate, name, line_offset, column_offset)); 272 // If the script was found in a later generation, we promote it to 273 // the first generation to let it survive longer in the cache. 274 if (generation != 0) Put(source, boilerplate); 275 Counters::compilation_cache_hits.Increment(); 276 return boilerplate; 277 } else { 278 Counters::compilation_cache_misses.Increment(); 279 return Handle<JSFunction>::null(); 280 } 281 } 282 283 284 void CompilationCacheScript::Put(Handle<String> source, 285 Handle<JSFunction> boilerplate) { 286 HandleScope scope; 287 ASSERT(boilerplate->IsBoilerplate()); 288 Handle<CompilationCacheTable> table = GetTable(0); 289 CALL_HEAP_FUNCTION_VOID(table->Put(*source, *boilerplate)); 290 } 291 292 293 Handle<JSFunction> CompilationCacheEval::Lookup(Handle<String> source, 294 Handle<Context> context) { 295 // Make sure not to leak the table into the surrounding handle 296 // scope. Otherwise, we risk keeping old tables around even after 297 // having cleared the cache. 298 Object* result = NULL; 299 int generation; 300 { HandleScope scope; 301 for (generation = 0; generation < generations(); generation++) { 302 Handle<CompilationCacheTable> table = GetTable(generation); 303 result = table->LookupEval(*source, *context); 304 if (result->IsJSFunction()) { 305 break; 306 } 307 } 308 } 309 if (result->IsJSFunction()) { 310 Handle<JSFunction> boilerplate(JSFunction::cast(result)); 311 if (generation != 0) { 312 Put(source, context, boilerplate); 313 } 314 Counters::compilation_cache_hits.Increment(); 315 return boilerplate; 316 } else { 317 Counters::compilation_cache_misses.Increment(); 318 return Handle<JSFunction>::null(); 319 } 320 } 321 322 323 void CompilationCacheEval::Put(Handle<String> source, 324 Handle<Context> context, 325 Handle<JSFunction> boilerplate) { 326 HandleScope scope; 327 ASSERT(boilerplate->IsBoilerplate()); 328 Handle<CompilationCacheTable> table = GetTable(0); 329 CALL_HEAP_FUNCTION_VOID(table->PutEval(*source, *context, *boilerplate)); 330 } 331 332 333 Handle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source, 334 JSRegExp::Flags flags) { 335 // Make sure not to leak the table into the surrounding handle 336 // scope. Otherwise, we risk keeping old tables around even after 337 // having cleared the cache. 338 Object* result = NULL; 339 int generation; 340 { HandleScope scope; 341 for (generation = 0; generation < generations(); generation++) { 342 Handle<CompilationCacheTable> table = GetTable(generation); 343 result = table->LookupRegExp(*source, flags); 344 if (result->IsFixedArray()) { 345 break; 346 } 347 } 348 } 349 if (result->IsFixedArray()) { 350 Handle<FixedArray> data(FixedArray::cast(result)); 351 if (generation != 0) { 352 Put(source, flags, data); 353 } 354 Counters::compilation_cache_hits.Increment(); 355 return data; 356 } else { 357 Counters::compilation_cache_misses.Increment(); 358 return Handle<FixedArray>::null(); 359 } 360 } 361 362 363 void CompilationCacheRegExp::Put(Handle<String> source, 364 JSRegExp::Flags flags, 365 Handle<FixedArray> data) { 366 HandleScope scope; 367 Handle<CompilationCacheTable> table = GetTable(0); 368 CALL_HEAP_FUNCTION_VOID(table->PutRegExp(*source, flags, *data)); 369 } 370 371 372 Handle<JSFunction> CompilationCache::LookupScript(Handle<String> source, 373 Handle<Object> name, 374 int line_offset, 375 int column_offset) { 376 if (!IsEnabled()) { 377 return Handle<JSFunction>::null(); 378 } 379 380 return script.Lookup(source, name, line_offset, column_offset); 381 } 382 383 384 Handle<JSFunction> CompilationCache::LookupEval(Handle<String> source, 385 Handle<Context> context, 386 bool is_global) { 387 if (!IsEnabled()) { 388 return Handle<JSFunction>::null(); 389 } 390 391 Handle<JSFunction> result; 392 if (is_global) { 393 result = eval_global.Lookup(source, context); 394 } else { 395 result = eval_contextual.Lookup(source, context); 396 } 397 return result; 398 } 399 400 401 Handle<FixedArray> CompilationCache::LookupRegExp(Handle<String> source, 402 JSRegExp::Flags flags) { 403 if (!IsEnabled()) { 404 return Handle<FixedArray>::null(); 405 } 406 407 return reg_exp.Lookup(source, flags); 408 } 409 410 411 void CompilationCache::PutScript(Handle<String> source, 412 Handle<JSFunction> boilerplate) { 413 if (!IsEnabled()) { 414 return; 415 } 416 417 ASSERT(boilerplate->IsBoilerplate()); 418 script.Put(source, boilerplate); 419 } 420 421 422 void CompilationCache::PutEval(Handle<String> source, 423 Handle<Context> context, 424 bool is_global, 425 Handle<JSFunction> boilerplate) { 426 if (!IsEnabled()) { 427 return; 428 } 429 430 HandleScope scope; 431 ASSERT(boilerplate->IsBoilerplate()); 432 if (is_global) { 433 eval_global.Put(source, context, boilerplate); 434 } else { 435 eval_contextual.Put(source, context, boilerplate); 436 } 437 } 438 439 440 441 void CompilationCache::PutRegExp(Handle<String> source, 442 JSRegExp::Flags flags, 443 Handle<FixedArray> data) { 444 if (!IsEnabled()) { 445 return; 446 } 447 448 reg_exp.Put(source, flags, data); 449 } 450 451 452 void CompilationCache::Clear() { 453 for (int i = 0; i < kSubCacheCount; i++) { 454 subcaches[i]->Clear(); 455 } 456 } 457 458 459 void CompilationCache::Iterate(ObjectVisitor* v) { 460 for (int i = 0; i < kSubCacheCount; i++) { 461 subcaches[i]->Iterate(v); 462 } 463 } 464 465 466 void CompilationCache::MarkCompactPrologue() { 467 for (int i = 0; i < kSubCacheCount; i++) { 468 subcaches[i]->Age(); 469 } 470 } 471 472 473 void CompilationCache::Enable() { 474 enabled = true; 475 } 476 477 478 void CompilationCache::Disable() { 479 enabled = false; 480 Clear(); 481 } 482 483 484 } } // namespace v8::internal 485