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