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