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