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
18 namespace v8 {
19 namespace internal {
20
21 // The number of generations for each sub cache.
22 static const int kRegExpGenerations = 2;
23
24 // Initial size of each compilation cache table allocated.
25 static const int kInitialCacheSize = 64;
26
CompilationCache(Isolate * isolate)27 CompilationCache::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
GetTable(int generation)41 Handle<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
AgeByGeneration(CompilationSubCache * c)56 void 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
AgeCustom(CompilationSubCache * c)69 void 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
Age()75 void CompilationCacheScript::Age() {
76 if (FLAG_isolate_script_cache_ageing) AgeCustom(this);
77 }
Age()78 void CompilationCacheEval::Age() { AgeCustom(this); }
Age()79 void CompilationCacheRegExp::Age() { AgeByGeneration(this); }
80
Iterate(RootVisitor * v)81 void CompilationSubCache::Iterate(RootVisitor* v) {
82 v->VisitRootPointers(Root::kCompilationCache, nullptr,
83 FullObjectSlot(&tables_[0]),
84 FullObjectSlot(&tables_[generations()]));
85 }
86
Clear()87 void CompilationSubCache::Clear() {
88 MemsetPointer(reinterpret_cast<Address*>(tables_),
89 ReadOnlyRoots(isolate()).undefined_value().ptr(),
90 generations());
91 }
92
Remove(Handle<SharedFunctionInfo> function_info)93 void 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
CompilationCacheScript(Isolate * isolate)105 CompilationCacheScript::CompilationCacheScript(Isolate* isolate)
106 : CompilationSubCache(isolate, 1) {}
107
108 namespace {
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.
HasOrigin(Isolate * isolate,Handle<SharedFunctionInfo> function_info,const ScriptDetails & script_details)113 bool 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.
Lookup(Handle<String> source,const ScriptDetails & script_details,LanguageMode language_mode)168 MaybeHandle<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
Put(Handle<String> source,LanguageMode language_mode,Handle<SharedFunctionInfo> function_info)208 void 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
Lookup(Handle<String> source,Handle<SharedFunctionInfo> outer_info,Handle<Context> native_context,LanguageMode language_mode,int position)217 InfoCellPair 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
Put(Handle<String> source,Handle<SharedFunctionInfo> outer_info,Handle<SharedFunctionInfo> function_info,Handle<Context> native_context,Handle<FeedbackCell> feedback_cell,int position)240 void 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
Lookup(Handle<String> source,JSRegExp::Flags flags)254 MaybeHandle<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
Put(Handle<String> source,JSRegExp::Flags flags,Handle<FixedArray> data)280 void 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
Remove(Handle<SharedFunctionInfo> function_info)288 void 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
LookupScript(Handle<String> source,const ScriptDetails & script_details,LanguageMode language_mode)296 MaybeHandle<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
LookupEval(Handle<String> source,Handle<SharedFunctionInfo> outer_info,Handle<Context> context,LanguageMode language_mode,int position)303 InfoCellPair 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
LookupRegExp(Handle<String> source,JSRegExp::Flags flags)333 MaybeHandle<FixedArray> CompilationCache::LookupRegExp(Handle<String> source,
334 JSRegExp::Flags flags) {
335 return reg_exp_.Lookup(source, flags);
336 }
337
PutScript(Handle<String> source,LanguageMode language_mode,Handle<SharedFunctionInfo> function_info)338 void 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
PutEval(Handle<String> source,Handle<SharedFunctionInfo> outer_info,Handle<Context> context,Handle<SharedFunctionInfo> function_info,Handle<FeedbackCell> feedback_cell,int position)347 void 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
PutRegExp(Handle<String> source,JSRegExp::Flags flags,Handle<FixedArray> data)371 void CompilationCache::PutRegExp(Handle<String> source, JSRegExp::Flags flags,
372 Handle<FixedArray> data) {
373 reg_exp_.Put(source, flags, data);
374 }
375
Clear()376 void CompilationCache::Clear() {
377 for (int i = 0; i < kSubCacheCount; i++) {
378 subcaches_[i]->Clear();
379 }
380 }
381
Iterate(RootVisitor * v)382 void CompilationCache::Iterate(RootVisitor* v) {
383 for (int i = 0; i < kSubCacheCount; i++) {
384 subcaches_[i]->Iterate(v);
385 }
386 }
387
MarkCompactPrologue()388 void CompilationCache::MarkCompactPrologue() {
389 for (int i = 0; i < kSubCacheCount; i++) {
390 subcaches_[i]->Age();
391 }
392 }
393
EnableScriptAndEval()394 void CompilationCache::EnableScriptAndEval() {
395 enabled_script_and_eval_ = true;
396 }
397
DisableScriptAndEval()398 void CompilationCache::DisableScriptAndEval() {
399 enabled_script_and_eval_ = false;
400 Clear();
401 }
402
403 } // namespace internal
404 } // namespace v8
405