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
16namespace v8 {
17namespace internal {
18
19// The number of generations for each sub cache.
20static const int kRegExpGenerations = 2;
21
22// Initial size of each compilation cache table allocated.
23static const int kInitialCacheSize = 64;
24
25CompilationCache::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_, &reg_exp_};
34 for (int i = 0; i < kSubCacheCount; ++i) {
35 subcaches_[i] = subcaches[i];
36 }
37}
38
39Handle<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
53void 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
71void CompilationSubCache::Iterate(RootVisitor* v) {
72 v->VisitRootPointers(Root::kCompilationCache, nullptr,
73 FullObjectSlot(&tables_[0]),
74 FullObjectSlot(&tables_[generations_]));
75}
76
77void CompilationSubCache::Clear() {
78 MemsetPointer(reinterpret_cast<Address*>(tables_),
79 ReadOnlyRoots(isolate()).undefined_value()->ptr(),
80 generations_);
81}
82
83void 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
94CompilationCacheScript::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.
100bool 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.
130MaybeHandle<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
174void 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
184InfoCellPair 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
207void 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
221MaybeHandle<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
248void 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
257void 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
265MaybeHandle<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
275InfoCellPair 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
305MaybeHandle<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
312void 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
322void 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
346void 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
354void CompilationCache::Clear() {
355 for (int i = 0; i < kSubCacheCount; i++) {
356 subcaches_[i]->Clear();
357 }
358}
359
360void CompilationCache::Iterate(RootVisitor* v) {
361 for (int i = 0; i < kSubCacheCount; i++) {
362 subcaches_[i]->Iterate(v);
363 }
364}
365
366void CompilationCache::MarkCompactPrologue() {
367 for (int i = 0; i < kSubCacheCount; i++) {
368 subcaches_[i]->Age();
369 }
370}
371
372void CompilationCache::Enable() {
373 enabled_ = true;
374}
375
376void CompilationCache::Disable() {
377 enabled_ = false;
378 Clear();
379}
380
381} // namespace internal
382} // namespace v8
383