//===-- GCMetadata.cpp - Garbage collector metadata -----------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements the GCFunctionInfo class and GCModuleInfo pass.
//
//===----------------------------------------------------------------------===//

#include "llvm/CodeGen/GCMetadata.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Module.h"
#include "llvm/InitializePasses.h"
#include "llvm/Pass.h"
#include <cassert>
#include <memory>
#include <string>

using namespace llvm;

bool GCStrategyMap::invalidate(Module &M, const PreservedAnalyses &PA,
                               ModuleAnalysisManager::Invalidator &) {
  for (const auto &F : M) {
    if (F.isDeclaration() || !F.hasGC())
      continue;
    if (!contains(F.getGC()))
      return true;
  }
  return false;
}

AnalysisKey CollectorMetadataAnalysis::Key;

CollectorMetadataAnalysis::Result
CollectorMetadataAnalysis::run(Module &M, ModuleAnalysisManager &MAM) {
  Result StrategyMap;
  for (auto &F : M) {
    if (F.isDeclaration() || !F.hasGC())
      continue;
    StringRef GCName = F.getGC();
    auto [It, Inserted] = StrategyMap.try_emplace(GCName);
    if (Inserted) {
      It->second = getGCStrategy(GCName);
      It->second->Name = GCName;
    }
  }
  return StrategyMap;
}

AnalysisKey GCFunctionAnalysis::Key;

GCFunctionAnalysis::Result
GCFunctionAnalysis::run(Function &F, FunctionAnalysisManager &FAM) {
  assert(!F.isDeclaration() && "Can only get GCFunctionInfo for a definition!");
  assert(F.hasGC() && "Function doesn't have GC!");

  auto &MAMProxy = FAM.getResult<ModuleAnalysisManagerFunctionProxy>(F);
  assert(
      MAMProxy.cachedResultExists<CollectorMetadataAnalysis>(*F.getParent()) &&
      "This pass need module analysis `collector-metadata`!");
  auto &Map =
      *MAMProxy.getCachedResult<CollectorMetadataAnalysis>(*F.getParent());
  GCStrategy &S = *Map.try_emplace(F.getGC()).first->second;
  GCFunctionInfo Info(F, S);
  return Info;
}

INITIALIZE_PASS(GCModuleInfo, "collector-metadata",
                "Create Garbage Collector Module Metadata", false, true)

// -----------------------------------------------------------------------------

GCFunctionInfo::GCFunctionInfo(const Function &F, GCStrategy &S)
    : F(F), S(S), FrameSize(~0LL) {}

GCFunctionInfo::~GCFunctionInfo() = default;

bool GCFunctionInfo::invalidate(Function &F, const PreservedAnalyses &PA,
                                FunctionAnalysisManager::Invalidator &) {
  auto PAC = PA.getChecker<GCFunctionAnalysis>();
  return !PAC.preserved() && !PAC.preservedSet<AllAnalysesOn<Function>>();
}

// -----------------------------------------------------------------------------

char GCModuleInfo::ID = 0;

GCModuleInfo::GCModuleInfo() : ImmutablePass(ID) {
  initializeGCModuleInfoPass(*PassRegistry::getPassRegistry());
}

GCFunctionInfo &GCModuleInfo::getFunctionInfo(const Function &F) {
  assert(!F.isDeclaration() && "Can only get GCFunctionInfo for a definition!");
  assert(F.hasGC());

  finfo_map_type::iterator I = FInfoMap.find(&F);
  if (I != FInfoMap.end())
    return *I->second;

  GCStrategy *S = getGCStrategy(F.getGC());
  Functions.push_back(std::make_unique<GCFunctionInfo>(F, *S));
  GCFunctionInfo *GFI = Functions.back().get();
  FInfoMap[&F] = GFI;
  return *GFI;
}

void GCModuleInfo::clear() {
  Functions.clear();
  FInfoMap.clear();
  GCStrategyList.clear();
}

// -----------------------------------------------------------------------------

GCStrategy *GCModuleInfo::getGCStrategy(const StringRef Name) {
  // TODO: Arguably, just doing a linear search would be faster for small N
  auto NMI = GCStrategyMap.find(Name);
  if (NMI != GCStrategyMap.end())
    return NMI->getValue();

  std::unique_ptr<GCStrategy> S = llvm::getGCStrategy(Name);
  S->Name = std::string(Name);
  GCStrategyMap[Name] = S.get();
  GCStrategyList.push_back(std::move(S));
  return GCStrategyList.back().get();
}
