ce99ec92创建于 2025年9月9日历史提交
// Copyright 2011 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/codegen/compilation-cache.h"

#include "src/common/globals.h"
#include "src/heap/factory.h"
#include "src/logging/counters.h"
#include "src/logging/log.h"
#include "src/objects/compilation-cache-table-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/objects.h"
#include "src/objects/slots.h"
#include "src/objects/visitors.h"
#include "src/utils/ostreams.h"

namespace v8 {
namespace internal {

// Initial size of each compilation cache table allocated.
static const int kInitialCacheSize = 64;

CompilationCache::CompilationCache(Isolate* isolate)
    : isolate_(isolate),
      script_(isolate),
      eval_global_(isolate),
      eval_contextual_(isolate),
      reg_exp_(isolate),
      enabled_script_and_eval_(true) {}

Handle<CompilationCacheTable> CompilationCacheEvalOrScript::GetTable() {
  if (IsUndefined(table_, isolate())) {
    return CompilationCacheTable::New(isolate(), kInitialCacheSize);
  }
  return handle(Cast<CompilationCacheTable>(table_), isolate());
}

DirectHandle<CompilationCacheTable> CompilationCacheRegExp::GetTable(
    int generation) {
  DCHECK_LT(generation, kGenerations);
  DirectHandle<CompilationCacheTable> result;
  if (IsUndefined(tables_[generation], isolate())) {
    result = CompilationCacheTable::New(isolate(), kInitialCacheSize);
    tables_[generation] = *result;
  } else {
    Tagged<CompilationCacheTable> table =
        Cast<CompilationCacheTable>(tables_[generation]);
    result = DirectHandle<CompilationCacheTable>(table, isolate());
  }
  return result;
}

void CompilationCacheRegExp::Age() {
  static_assert(kGenerations > 1);

  // Age the generations implicitly killing off the oldest.
  for (int i = kGenerations - 1; i > 0; i--) {
    tables_[i] = tables_[i - 1];
  }

  // Set the first generation as unborn.
  tables_[0] = ReadOnlyRoots(isolate()).undefined_value();
}

void CompilationCacheScript::Age() {
  DisallowGarbageCollection no_gc;
  if (IsUndefined(table_, isolate())) return;
  Tagged<CompilationCacheTable> table = Cast<CompilationCacheTable>(table_);

  for (InternalIndex entry : table->IterateEntries()) {
    Tagged<Object> key;
    if (!table->ToKey(isolate(), entry, &key)) continue;
    DCHECK(IsWeakFixedArray(key));

    Tagged<Object> value = table->PrimaryValueAt(entry);
    if (!IsUndefined(value, isolate())) {
      Tagged<SharedFunctionInfo> info = Cast<SharedFunctionInfo>(value);
      // Clear entries after Bytecode was flushed from SFI.
      if (!info->HasBytecodeArray()) {
        table->SetPrimaryValueAt(entry,
                                 ReadOnlyRoots(isolate()).undefined_value(),
                                 SKIP_WRITE_BARRIER);
      }
    }
  }
}

void CompilationCacheEval::Age() {
  DisallowGarbageCollection no_gc;
  if (IsUndefined(table_, isolate())) return;
  Tagged<CompilationCacheTable> table = Cast<CompilationCacheTable>(table_);

  for (InternalIndex entry : table->IterateEntries()) {
    Tagged<Object> key;
    if (!table->ToKey(isolate(), entry, &key)) continue;

    DCHECK(IsFixedArray(key));
    // The ageing mechanism for eval caches.
    Tagged<SharedFunctionInfo> info =
        Cast<SharedFunctionInfo>(table->PrimaryValueAt(entry));
    // Clear entries after Bytecode was flushed from SFI.
    if (!info->HasBytecodeArray()) {
      table->RemoveEntry(entry);
    }
  }
}

void CompilationCacheEvalOrScript::Iterate(RootVisitor* v) {
  v->VisitRootPointer(Root::kCompilationCache, nullptr,
                      FullObjectSlot(&table_));
}

void CompilationCacheRegExp::Iterate(RootVisitor* v) {
  v->VisitRootPointers(Root::kCompilationCache, nullptr,
                       FullObjectSlot(&tables_[0]),
                       FullObjectSlot(&tables_[kGenerations]));
}

void CompilationCacheEvalOrScript::Clear() {
  table_ = ReadOnlyRoots(isolate()).undefined_value();
}

void CompilationCacheRegExp::Clear() {
  MemsetPointer(FullObjectSlot(tables_),
                ReadOnlyRoots(isolate()).undefined_value(), kGenerations);
}

void CompilationCacheEvalOrScript::Remove(
    DirectHandle<SharedFunctionInfo> function_info) {
  if (IsUndefined(table_, isolate())) return;
  Cast<CompilationCacheTable>(table_)->Remove(*function_info);
}

CompilationCacheScript::LookupResult CompilationCacheScript::Lookup(
    Handle<String> source, const ScriptDetails& script_details) {
  DirectHandle<CompilationCacheTable> table = GetTable();
  LookupResult result = CompilationCacheTable::LookupScript(
      table, source, script_details, isolate());

  DirectHandle<Script> script;
  if (result.script().ToHandle(&script)) {
    DirectHandle<SharedFunctionInfo> sfi;
    if (result.toplevel_sfi().ToHandle(&sfi)) {
      isolate()->counters()->compilation_cache_hits()->Increment();
      LOG(isolate(), CompilationCacheEvent("hit", "script", *sfi));
    } else {
      isolate()->counters()->compilation_cache_partial_hits()->Increment();
    }
  } else {
    isolate()->counters()->compilation_cache_misses()->Increment();
  }
  return result;
}

void CompilationCacheScript::Put(
    Handle<String> source, DirectHandle<SharedFunctionInfo> function_info) {
  Handle<CompilationCacheTable> table = GetTable();
  table_ = *CompilationCacheTable::PutScript(table, source, kNullMaybeHandle,
                                             function_info, isolate());
}

void CompilationCacheEval::UpdateEval(
    DirectHandle<String> source, DirectHandle<SharedFunctionInfo> outer_info,
    DirectHandle<JSFunction> js_function, LanguageMode language_mode,
    int position) {
  DirectHandle<CompilationCacheTable> table = GetTable();
  CompilationCacheTable::UpdateEval(table, source, outer_info, js_function,
                                    language_mode, position);
}

InfoCellPair CompilationCacheEval::Lookup(
    DirectHandle<String> source, DirectHandle<SharedFunctionInfo> outer_info,
    DirectHandle<NativeContext> native_context, LanguageMode language_mode,
    int position) {
  InfoCellPair result;
  DirectHandle<CompilationCacheTable> table = GetTable();
  result = CompilationCacheTable::LookupEval(
      table, source, outer_info, native_context, language_mode, position);
  if (result.has_shared()) {
    isolate()->counters()->compilation_cache_hits()->Increment();
  } else {
    isolate()->counters()->compilation_cache_misses()->Increment();
  }
  return result;
}

void CompilationCacheEval::Put(DirectHandle<String> source,
                               DirectHandle<SharedFunctionInfo> outer_info,
                               DirectHandle<JSFunction> js_function,
                               int position) {
  DirectHandle<CompilationCacheTable> table = GetTable();
  table_ = *CompilationCacheTable::PutEval(table, source, outer_info,
                                           js_function, position);
}

MaybeDirectHandle<RegExpData> CompilationCacheRegExp::Lookup(
    DirectHandle<String> source, JSRegExp::Flags flags) {
  HandleScope scope(isolate());
  // Make sure not to leak the table into the surrounding handle
  // scope. Otherwise, we risk keeping old tables around even after
  // having cleared the cache.
  DirectHandle<Object> result = isolate()->factory()->undefined_value();
  int generation;
  for (generation = 0; generation < kGenerations; generation++) {
    DirectHandle<CompilationCacheTable> table = GetTable(generation);
    result = table->LookupRegExp(source, flags);
    if (IsRegExpDataWrapper(*result)) break;
  }
  if (IsRegExpDataWrapper(*result)) {
    Handle<RegExpData> data(Cast<RegExpDataWrapper>(result)->data(isolate()),
                            isolate());
    if (generation != 0) {
      Put(source, flags, data);
    }
    isolate()->counters()->compilation_cache_hits()->Increment();
    return scope.CloseAndEscape(data);
  } else {
    isolate()->counters()->compilation_cache_misses()->Increment();
    return MaybeDirectHandle<RegExpData>();
  }
}

void CompilationCacheRegExp::Put(DirectHandle<String> source,
                                 JSRegExp::Flags flags,
                                 DirectHandle<RegExpData> data) {
  HandleScope scope(isolate());
  DirectHandle<CompilationCacheTable> table = GetTable(0);
  tables_[0] =
      *CompilationCacheTable::PutRegExp(isolate(), table, source, flags, data);
}

void CompilationCache::Remove(DirectHandle<SharedFunctionInfo> function_info) {
  if (!IsEnabledScriptAndEval()) return;

  eval_global_.Remove(function_info);
  eval_contextual_.Remove(function_info);
  script_.Remove(function_info);
}

CompilationCacheScript::LookupResult CompilationCache::LookupScript(
    Handle<String> source, const ScriptDetails& script_details,
    LanguageMode language_mode) {
  if (!IsEnabledScript(language_mode)) return {};
  return script_.Lookup(source, script_details);
}

void CompilationCache::UpdateEval(DirectHandle<String> source,
                                  DirectHandle<SharedFunctionInfo> outer_info,
                                  DirectHandle<JSFunction> js_function,
                                  LanguageMode language_mode, int position) {
  if (IsNativeContext(js_function->context())) {
    eval_global_.UpdateEval(source, outer_info, js_function, language_mode,
                            position);
  } else {
    DCHECK_NE(position, kNoSourcePosition);
    eval_contextual_.UpdateEval(source, outer_info, js_function, language_mode,
                                position);
  }
}

InfoCellPair CompilationCache::LookupEval(
    DirectHandle<String> source, DirectHandle<SharedFunctionInfo> outer_info,
    DirectHandle<Context> context, LanguageMode language_mode, int position) {
  InfoCellPair result;
  if (!IsEnabledScriptAndEval()) return result;

  const char* cache_type;

  DirectHandle<NativeContext> maybe_native_context;
  if (TryCast<NativeContext>(context, &maybe_native_context)) {
    result = eval_global_.Lookup(source, outer_info, maybe_native_context,
                                 language_mode, position);
    cache_type = "eval-global";

  } else {
    DCHECK_NE(position, kNoSourcePosition);
    DirectHandle<NativeContext> native_context(context->native_context(),
                                               isolate());
    result = eval_contextual_.Lookup(source, outer_info, native_context,
                                     language_mode, position);
    cache_type = "eval-contextual";
  }

  if (result.has_shared()) {
    LOG(isolate(), CompilationCacheEvent("hit", cache_type, result.shared()));
  }

  return result;
}

MaybeDirectHandle<RegExpData> CompilationCache::LookupRegExp(
    DirectHandle<String> source, JSRegExp::Flags flags) {
  return reg_exp_.Lookup(source, flags);
}

void CompilationCache::PutScript(
    Handle<String> source, LanguageMode language_mode,
    DirectHandle<SharedFunctionInfo> function_info) {
  if (!IsEnabledScript(language_mode)) return;
  LOG(isolate(), CompilationCacheEvent("put", "script", *function_info));

  script_.Put(source, function_info);
}

void CompilationCache::PutEval(DirectHandle<String> source,
                               DirectHandle<SharedFunctionInfo> outer_info,
                               DirectHandle<JSFunction> js_function,
                               int position) {
  if (!IsEnabledScriptAndEval()) return;

  const char* cache_type;
  if (IsNativeContext(js_function->context())) {
    eval_global_.Put(source, outer_info, js_function, position);
    cache_type = "eval-global";
  } else {
    DCHECK_NE(position, kNoSourcePosition);
    eval_contextual_.Put(source, outer_info, js_function, position);
    cache_type = "eval-contextual";
  }
  LOG(isolate(),
      CompilationCacheEvent("put", cache_type, js_function->shared()));
}

void CompilationCache::PutRegExp(DirectHandle<String> source,
                                 JSRegExp::Flags flags,
                                 DirectHandle<RegExpData> data) {
  reg_exp_.Put(source, flags, data);
}

void CompilationCache::Clear() {
  script_.Clear();
  eval_global_.Clear();
  eval_contextual_.Clear();
  reg_exp_.Clear();
}

void CompilationCache::Iterate(RootVisitor* v) {
  script_.Iterate(v);
  eval_global_.Iterate(v);
  eval_contextual_.Iterate(v);
  reg_exp_.Iterate(v);
}

void CompilationCache::MarkCompactPrologue() {
  // Drop SFI entries with flushed bytecode.
  script_.Age();
  eval_global_.Age();
  eval_contextual_.Age();

  // Drop entries in oldest generation.
  reg_exp_.Age();
}

void CompilationCache::EnableScriptAndEval() {
  enabled_script_and_eval_ = true;
}

void CompilationCache::DisableScriptAndEval() {
  enabled_script_and_eval_ = false;
  Clear();
}

}  // namespace internal
}  // namespace v8