// Copyright 2025 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/builtins/builtins-effects-analyzer.h"

#include <fstream>

#include "src/execution/isolate.h"

namespace v8::internal {

// static
BuiltinsEffectsAnalyzer* BuiltinsEffectsAnalyzer::Setup(Isolate* isolate) {
  BuiltinsEffectsAnalyzer* analyzer = new BuiltinsEffectsAnalyzer();
  isolate->set_builtins_effects_analyzer(analyzer);
  return analyzer;
}

// static
void BuiltinsEffectsAnalyzer::TearDown(Isolate* isolate) {
  BuiltinsEffectsAnalyzer* analyzer = isolate->builtins_effects_analyzer();
  DCHECK(analyzer->is_finalized());
  isolate->set_builtins_effects_analyzer(nullptr);
  delete analyzer;
}

void BuiltinsEffectsAnalyzer::RegisterInitialEffects(
    Builtin builtin, BuiltinAllocateEffect effects,
    std::unordered_set<Builtin> callees) {
  base::MutexGuard guard(&mutex_);

  size_t builtin_id = static_cast<size_t>(builtin);
  builtins_effects_[builtin_id] = {effects, callees};
}

void FinalizeBuiltin(
    Builtin builtin,
    std::array<BuiltinEffects, Builtins::kBuiltinCount>& builtins_effects) {
  BuiltinEffects& effects = builtins_effects[static_cast<size_t>(builtin)];

  if (effects.is_finalized) return;

  if (effects.can_allocate == BuiltinAllocateEffect::kUninitialized) {
    // This builtin wasn't compiled with Turbofan; we assume that it can
    // allocate.
    // TODO(dmercadier): for CPP builtins, we could have a mechanism similar as
    // Runtime functions: annotate them with can_allocate/cant_allocate and
    // enforce this with a DisallowGarbageCollection scope.
    effects.can_allocate = BuiltinAllocateEffect::kYes;
    effects.is_finalized = true;
    return;
  }

  if (effects.can_allocate != BuiltinAllocateEffect::kMaybeWithBuiltinCall) {
    effects.is_finalized = true;
    return;
  }

  for (Builtin callee : effects.callees) {
    BuiltinEffects& callee_effects =
        builtins_effects[static_cast<size_t>(callee)];
    if (!callee_effects.is_finalized) {
      // Note that we rely on builtins not recursviely calling each others. So
      // far, this always hold. If it ever stops holding, then we'll have
      // unbounded recursion here, and we can then think about fixing it.
      FinalizeBuiltin(callee, builtins_effects);
    }
    if (callee_effects.can_allocate == BuiltinAllocateEffect::kYes) {
      effects.can_allocate = BuiltinAllocateEffect::kYes;
      effects.is_finalized = true;
      return;
    }
  }

  // None of the callees can allocate.
  effects.can_allocate = BuiltinAllocateEffect::kNo;
  effects.is_finalized = true;
}

void BuiltinsEffectsAnalyzer::Finalize() {
  CHECK(!is_finalized_);

  for (size_t i = 0; i < builtins_effects_.size(); i++) {
    FinalizeBuiltin(static_cast<Builtin>(i), builtins_effects_);
    CHECK(builtins_effects_[i].is_finalized);
  }

  is_finalized_ = true;
}

// This file is automatically generated by `tools/dev/gen-builtins-effects.py`.
// Do not edit manually.
void BuiltinsEffectsAnalyzer::Write(const char* file) {
  CHECK(is_finalized_);
  CHECK_NOT_NULL(file);

  std::ofstream out(file, std::ios::out);

  out << "// Copyright 2025 the V8 project authors. All rights reserved.\n"
      << "// Use of this source code is governed by a BSD-style license "
         "that can be\n"
      << "// found in the LICENSE file.\n"
      << "\n"
      << "// This file is automatically generated by mksnapshot (more "
         "precisely, by\n"
      << "// BuiltinsEffectsState::write in builtins.cc). Do not edit "
         "manually.\n"
      << "\n"
      << "#include \"src/builtins/builtins.h\"\n"
      << "\n"
      << "namespace v8::internal {\n"
      << "\n"
      << "bool BuiltinCanAllocate(Builtin builtin) {\n"
      << "  switch (builtin) {\n";

  for (size_t id = 0; id < builtins_effects_.size(); id++) {
    BuiltinEffects effects = builtins_effects_[id];
    CHECK(effects.is_finalized);
    out << "    case Builtin::k" << Builtins::name(static_cast<Builtin>(id))
        << ":\n";
    switch (effects.can_allocate) {
      case BuiltinAllocateEffect::kNo:
        out << "      return false;\n";
        break;
      case BuiltinAllocateEffect::kYes:
        out << "      return true;\n";
        break;
      case BuiltinAllocateEffect::kMaybeWithBuiltinCall:
      case BuiltinAllocateEffect::kUninitialized:
        UNREACHABLE();
    }
  }

  out << "    case Builtin::kNoBuiltinId:\n"
      << "      UNREACHABLE();\n"
      << "  }\n"
      << "  UNREACHABLE();\n"
      << "}\n"
      << "\n"
      << "}  // namespace v8::internal\n";

  out.close();
}

}  // namespace v8::internal
