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