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