1// Copyright 2011 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "src/codegen/compilation-cache.h" 6 7#include "src/codegen/script-details.h" 8#include "src/common/globals.h" 9#include "src/heap/factory.h" 10#include "src/logging/counters.h" 11#include "src/logging/log.h" 12#include "src/objects/compilation-cache-table-inl.h" 13#include "src/objects/objects-inl.h" 14#include "src/objects/slots.h" 15#include "src/objects/visitors.h" 16#include "src/utils/ostreams.h" 17 18namespace v8 { 19namespace internal { 20 21// The number of generations for each sub cache. 22static const int kRegExpGenerations = 2; 23 24// Initial size of each compilation cache table allocated. 25static const int kInitialCacheSize = 64; 26 27CompilationCache::CompilationCache(Isolate* isolate) 28 : isolate_(isolate), 29 script_(isolate), 30 eval_global_(isolate), 31 eval_contextual_(isolate), 32 reg_exp_(isolate, kRegExpGenerations), 33 enabled_script_and_eval_(true) { 34 CompilationSubCache* subcaches[kSubCacheCount] = { 35 &script_, &eval_global_, &eval_contextual_, ®_exp_}; 36 for (int i = 0; i < kSubCacheCount; ++i) { 37 subcaches_[i] = subcaches[i]; 38 } 39} 40 41Handle<CompilationCacheTable> CompilationSubCache::GetTable(int generation) { 42 DCHECK_LT(generation, generations()); 43 Handle<CompilationCacheTable> result; 44 if (tables_[generation].IsUndefined(isolate())) { 45 result = CompilationCacheTable::New(isolate(), kInitialCacheSize); 46 tables_[generation] = *result; 47 } else { 48 CompilationCacheTable table = 49 CompilationCacheTable::cast(tables_[generation]); 50 result = Handle<CompilationCacheTable>(table, isolate()); 51 } 52 return result; 53} 54 55// static 56void CompilationSubCache::AgeByGeneration(CompilationSubCache* c) { 57 DCHECK_GT(c->generations(), 1); 58 59 // Age the generations implicitly killing off the oldest. 60 for (int i = c->generations() - 1; i > 0; i--) { 61 c->tables_[i] = c->tables_[i - 1]; 62 } 63 64 // Set the first generation as unborn. 65 c->tables_[0] = ReadOnlyRoots(c->isolate()).undefined_value(); 66} 67 68// static 69void CompilationSubCache::AgeCustom(CompilationSubCache* c) { 70 DCHECK_EQ(c->generations(), 1); 71 if (c->tables_[0].IsUndefined(c->isolate())) return; 72 CompilationCacheTable::cast(c->tables_[0]).Age(c->isolate()); 73} 74 75void CompilationCacheScript::Age() { 76 if (FLAG_isolate_script_cache_ageing) AgeCustom(this); 77} 78void CompilationCacheEval::Age() { AgeCustom(this); } 79void CompilationCacheRegExp::Age() { AgeByGeneration(this); } 80 81void CompilationSubCache::Iterate(RootVisitor* v) { 82 v->VisitRootPointers(Root::kCompilationCache, nullptr, 83 FullObjectSlot(&tables_[0]), 84 FullObjectSlot(&tables_[generations()])); 85} 86 87void CompilationSubCache::Clear() { 88 MemsetPointer(reinterpret_cast<Address*>(tables_), 89 ReadOnlyRoots(isolate()).undefined_value().ptr(), 90 generations()); 91} 92 93void CompilationSubCache::Remove(Handle<SharedFunctionInfo> function_info) { 94 // Probe the script generation tables. Make sure not to leak handles 95 // into the caller's handle scope. 96 { 97 HandleScope scope(isolate()); 98 for (int generation = 0; generation < generations(); generation++) { 99 Handle<CompilationCacheTable> table = GetTable(generation); 100 table->Remove(*function_info); 101 } 102 } 103} 104 105CompilationCacheScript::CompilationCacheScript(Isolate* isolate) 106 : CompilationSubCache(isolate, 1) {} 107 108namespace { 109 110// We only re-use a cached function for some script source code if the 111// script originates from the same place. This is to avoid issues 112// when reporting errors, etc. 113bool HasOrigin(Isolate* isolate, Handle<SharedFunctionInfo> function_info, 114 const ScriptDetails& script_details) { 115 Handle<Script> script = 116 Handle<Script>(Script::cast(function_info->script()), isolate); 117 // If the script name isn't set, the boilerplate script should have 118 // an undefined name to have the same origin. 119 Handle<Object> name; 120 if (!script_details.name_obj.ToHandle(&name)) { 121 return script->name().IsUndefined(isolate); 122 } 123 // Do the fast bailout checks first. 124 if (script_details.line_offset != script->line_offset()) return false; 125 if (script_details.column_offset != script->column_offset()) return false; 126 // Check that both names are strings. If not, no match. 127 if (!name->IsString() || !script->name().IsString()) return false; 128 // Are the origin_options same? 129 if (script_details.origin_options.Flags() != 130 script->origin_options().Flags()) { 131 return false; 132 } 133 // Compare the two name strings for equality. 134 if (!String::Equals(isolate, Handle<String>::cast(name), 135 Handle<String>(String::cast(script->name()), isolate))) { 136 return false; 137 } 138 139 // TODO(cbruni, chromium:1244145): Remove once migrated to the context 140 Handle<Object> maybe_host_defined_options; 141 if (!script_details.host_defined_options.ToHandle( 142 &maybe_host_defined_options)) { 143 maybe_host_defined_options = isolate->factory()->empty_fixed_array(); 144 } 145 Handle<FixedArray> host_defined_options = 146 Handle<FixedArray>::cast(maybe_host_defined_options); 147 Handle<FixedArray> script_options( 148 FixedArray::cast(script->host_defined_options()), isolate); 149 int length = host_defined_options->length(); 150 if (length != script_options->length()) return false; 151 152 for (int i = 0; i < length; i++) { 153 // host-defined options is a v8::PrimitiveArray. 154 DCHECK(host_defined_options->get(i).IsPrimitive()); 155 DCHECK(script_options->get(i).IsPrimitive()); 156 if (!host_defined_options->get(i).StrictEquals(script_options->get(i))) { 157 return false; 158 } 159 } 160 return true; 161} 162} // namespace 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. 168MaybeHandle<SharedFunctionInfo> CompilationCacheScript::Lookup( 169 Handle<String> source, const ScriptDetails& script_details, 170 LanguageMode language_mode) { 171 MaybeHandle<SharedFunctionInfo> result; 172 173 // Probe the script generation tables. Make sure not to leak handles 174 // into the caller's handle scope. 175 { 176 HandleScope scope(isolate()); 177 const int generation = 0; 178 DCHECK_EQ(generations(), 1); 179 Handle<CompilationCacheTable> table = GetTable(generation); 180 MaybeHandle<SharedFunctionInfo> probe = CompilationCacheTable::LookupScript( 181 table, source, language_mode, isolate()); 182 Handle<SharedFunctionInfo> function_info; 183 if (probe.ToHandle(&function_info)) { 184 // Break when we've found a suitable shared function info that 185 // matches the origin. 186 if (HasOrigin(isolate(), function_info, script_details)) { 187 result = scope.CloseAndEscape(function_info); 188 } 189 } 190 } 191 192 // Once outside the manacles of the handle scope, we need to recheck 193 // to see if we actually found a cached script. If so, we return a 194 // handle created in the caller's handle scope. 195 Handle<SharedFunctionInfo> function_info; 196 if (result.ToHandle(&function_info)) { 197 // Since HasOrigin can allocate, we need to protect the SharedFunctionInfo 198 // with handles during the call. 199 DCHECK(HasOrigin(isolate(), function_info, script_details)); 200 isolate()->counters()->compilation_cache_hits()->Increment(); 201 LOG(isolate(), CompilationCacheEvent("hit", "script", *function_info)); 202 } else { 203 isolate()->counters()->compilation_cache_misses()->Increment(); 204 } 205 return result; 206} 207 208void CompilationCacheScript::Put(Handle<String> source, 209 LanguageMode language_mode, 210 Handle<SharedFunctionInfo> function_info) { 211 HandleScope scope(isolate()); 212 Handle<CompilationCacheTable> table = GetFirstTable(); 213 SetFirstTable(CompilationCacheTable::PutScript(table, source, language_mode, 214 function_info, isolate())); 215} 216 217InfoCellPair CompilationCacheEval::Lookup(Handle<String> source, 218 Handle<SharedFunctionInfo> outer_info, 219 Handle<Context> native_context, 220 LanguageMode language_mode, 221 int position) { 222 HandleScope scope(isolate()); 223 // Make sure not to leak the table into the surrounding handle 224 // scope. Otherwise, we risk keeping old tables around even after 225 // having cleared the cache. 226 InfoCellPair result; 227 const int generation = 0; 228 DCHECK_EQ(generations(), 1); 229 Handle<CompilationCacheTable> table = GetTable(generation); 230 result = CompilationCacheTable::LookupEval( 231 table, source, outer_info, native_context, language_mode, position); 232 if (result.has_shared()) { 233 isolate()->counters()->compilation_cache_hits()->Increment(); 234 } else { 235 isolate()->counters()->compilation_cache_misses()->Increment(); 236 } 237 return result; 238} 239 240void CompilationCacheEval::Put(Handle<String> source, 241 Handle<SharedFunctionInfo> outer_info, 242 Handle<SharedFunctionInfo> function_info, 243 Handle<Context> native_context, 244 Handle<FeedbackCell> feedback_cell, 245 int position) { 246 HandleScope scope(isolate()); 247 Handle<CompilationCacheTable> table = GetFirstTable(); 248 table = 249 CompilationCacheTable::PutEval(table, source, outer_info, function_info, 250 native_context, feedback_cell, position); 251 SetFirstTable(table); 252} 253 254MaybeHandle<FixedArray> CompilationCacheRegExp::Lookup(Handle<String> source, 255 JSRegExp::Flags flags) { 256 HandleScope scope(isolate()); 257 // Make sure not to leak the table into the surrounding handle 258 // scope. Otherwise, we risk keeping old tables around even after 259 // having cleared the cache. 260 Handle<Object> result = isolate()->factory()->undefined_value(); 261 int generation; 262 for (generation = 0; generation < generations(); generation++) { 263 Handle<CompilationCacheTable> table = GetTable(generation); 264 result = table->LookupRegExp(source, flags); 265 if (result->IsFixedArray()) break; 266 } 267 if (result->IsFixedArray()) { 268 Handle<FixedArray> data = Handle<FixedArray>::cast(result); 269 if (generation != 0) { 270 Put(source, flags, data); 271 } 272 isolate()->counters()->compilation_cache_hits()->Increment(); 273 return scope.CloseAndEscape(data); 274 } else { 275 isolate()->counters()->compilation_cache_misses()->Increment(); 276 return MaybeHandle<FixedArray>(); 277 } 278} 279 280void CompilationCacheRegExp::Put(Handle<String> source, JSRegExp::Flags flags, 281 Handle<FixedArray> data) { 282 HandleScope scope(isolate()); 283 Handle<CompilationCacheTable> table = GetFirstTable(); 284 SetFirstTable( 285 CompilationCacheTable::PutRegExp(isolate(), table, source, flags, data)); 286} 287 288void CompilationCache::Remove(Handle<SharedFunctionInfo> function_info) { 289 if (!IsEnabledScriptAndEval()) return; 290 291 eval_global_.Remove(function_info); 292 eval_contextual_.Remove(function_info); 293 script_.Remove(function_info); 294} 295 296MaybeHandle<SharedFunctionInfo> CompilationCache::LookupScript( 297 Handle<String> source, const ScriptDetails& script_details, 298 LanguageMode language_mode) { 299 if (!IsEnabledScriptAndEval()) return MaybeHandle<SharedFunctionInfo>(); 300 return script_.Lookup(source, script_details, language_mode); 301} 302 303InfoCellPair CompilationCache::LookupEval(Handle<String> source, 304 Handle<SharedFunctionInfo> outer_info, 305 Handle<Context> context, 306 LanguageMode language_mode, 307 int position) { 308 InfoCellPair result; 309 if (!IsEnabledScriptAndEval()) return result; 310 311 const char* cache_type; 312 313 if (context->IsNativeContext()) { 314 result = eval_global_.Lookup(source, outer_info, context, language_mode, 315 position); 316 cache_type = "eval-global"; 317 318 } else { 319 DCHECK_NE(position, kNoSourcePosition); 320 Handle<Context> native_context(context->native_context(), isolate()); 321 result = eval_contextual_.Lookup(source, outer_info, native_context, 322 language_mode, position); 323 cache_type = "eval-contextual"; 324 } 325 326 if (result.has_shared()) { 327 LOG(isolate(), CompilationCacheEvent("hit", cache_type, result.shared())); 328 } 329 330 return result; 331} 332 333MaybeHandle<FixedArray> CompilationCache::LookupRegExp(Handle<String> source, 334 JSRegExp::Flags flags) { 335 return reg_exp_.Lookup(source, flags); 336} 337 338void CompilationCache::PutScript(Handle<String> source, 339 LanguageMode language_mode, 340 Handle<SharedFunctionInfo> function_info) { 341 if (!IsEnabledScriptAndEval()) return; 342 LOG(isolate(), CompilationCacheEvent("put", "script", *function_info)); 343 344 script_.Put(source, language_mode, function_info); 345} 346 347void CompilationCache::PutEval(Handle<String> source, 348 Handle<SharedFunctionInfo> outer_info, 349 Handle<Context> context, 350 Handle<SharedFunctionInfo> function_info, 351 Handle<FeedbackCell> feedback_cell, 352 int position) { 353 if (!IsEnabledScriptAndEval()) return; 354 355 const char* cache_type; 356 HandleScope scope(isolate()); 357 if (context->IsNativeContext()) { 358 eval_global_.Put(source, outer_info, function_info, context, feedback_cell, 359 position); 360 cache_type = "eval-global"; 361 } else { 362 DCHECK_NE(position, kNoSourcePosition); 363 Handle<Context> native_context(context->native_context(), isolate()); 364 eval_contextual_.Put(source, outer_info, function_info, native_context, 365 feedback_cell, position); 366 cache_type = "eval-contextual"; 367 } 368 LOG(isolate(), CompilationCacheEvent("put", cache_type, *function_info)); 369} 370 371void CompilationCache::PutRegExp(Handle<String> source, JSRegExp::Flags flags, 372 Handle<FixedArray> data) { 373 reg_exp_.Put(source, flags, data); 374} 375 376void CompilationCache::Clear() { 377 for (int i = 0; i < kSubCacheCount; i++) { 378 subcaches_[i]->Clear(); 379 } 380} 381 382void CompilationCache::Iterate(RootVisitor* v) { 383 for (int i = 0; i < kSubCacheCount; i++) { 384 subcaches_[i]->Iterate(v); 385 } 386} 387 388void CompilationCache::MarkCompactPrologue() { 389 for (int i = 0; i < kSubCacheCount; i++) { 390 subcaches_[i]->Age(); 391 } 392} 393 394void CompilationCache::EnableScriptAndEval() { 395 enabled_script_and_eval_ = true; 396} 397 398void CompilationCache::DisableScriptAndEval() { 399 enabled_script_and_eval_ = false; 400 Clear(); 401} 402 403} // namespace internal 404} // namespace v8 405