//===--- LLJITWithThinLTOSummaries.cpp - Module summaries as LLJIT input --===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// In this example we will use a module summary index file produced for ThinLTO
// to (A) find the module that defines the main entry point and (B) find all
// extra modules that we need. We will do this in five steps:
//
// (1) Read the index file and parse the module summary index.
// (2) Find the path of the module that defines "main".
// (3) Parse the main module and create a matching LLJIT.
// (4) Add all modules to the LLJIT that are covered by the index.
// (5) Look up and run the JIT'd function.
//
// The index file name must be passed in as command line argument. Please find
// this test for instructions on creating the index file:
//
//       llvm/test/Examples/OrcV2Examples/lljit-with-thinlto-summaries.test
//
// If you use "build" as the build directory, you can run the test from the root
// of the monorepo like this:
//
// > build/bin/llvm-lit -a \
//       llvm/test/Examples/OrcV2Examples/lljit-with-thinlto-summaries.test
//
//===----------------------------------------------------------------------===//

#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Bitcode/BitcodeReader.h"
#include "llvm/ExecutionEngine/Orc/Core.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/ExecutionEngine/Orc/ThreadSafeModule.h"
#include "llvm/ExecutionEngine/Orc/TargetProcess/TargetExecutionUtils.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/ModuleSummaryIndex.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/raw_ostream.h"

#include <string>
#include <system_error>
#include <vector>

using namespace llvm;
using namespace llvm::orc;

// Path of the module summary index file.
cl::opt<std::string> IndexFile{cl::desc("<module summary index>"),
                               cl::Positional, cl::init("-")};

// Describe a fail state that is caused by the given ModuleSummaryIndex
// providing multiple definitions of the given global value name. It will dump
// name and GUID for the global value and list the paths of the modules covered
// by the index.
class DuplicateDefinitionInSummary
    : public ErrorInfo<DuplicateDefinitionInSummary> {
public:
  static char ID;

  DuplicateDefinitionInSummary(std::string GlobalValueName, ValueInfo VI)
      : GlobalValueName(std::move(GlobalValueName)) {
    ModulePaths.reserve(VI.getSummaryList().size());
    for (const auto &S : VI.getSummaryList())
      ModulePaths.push_back(S->modulePath().str());
    llvm::sort(ModulePaths);
  }

  void log(raw_ostream &OS) const override {
    OS << "Duplicate symbol for global value '" << GlobalValueName
       << "' (GUID: " << GlobalValue::getGUID(GlobalValueName) << ") in:\n";
    for (const std::string &Path : ModulePaths) {
      OS << "    " << Path << "\n";
    }
  }

  std::error_code convertToErrorCode() const override {
    return inconvertibleErrorCode();
  }

private:
  std::string GlobalValueName;
  std::vector<std::string> ModulePaths;
};

// Describe a fail state where the given global value name was not found in the
// given ModuleSummaryIndex. It will dump name and GUID for the global value and
// list the paths of the modules covered by the index.
class DefinitionNotFoundInSummary
    : public ErrorInfo<DefinitionNotFoundInSummary> {
public:
  static char ID;

  DefinitionNotFoundInSummary(std::string GlobalValueName,
                              ModuleSummaryIndex &Index)
      : GlobalValueName(std::move(GlobalValueName)) {
    ModulePaths.reserve(Index.modulePaths().size());
    for (const auto &Entry : Index.modulePaths())
      ModulePaths.push_back(Entry.first().str());
    llvm::sort(ModulePaths);
  }

  void log(raw_ostream &OS) const override {
    OS << "No symbol for global value '" << GlobalValueName
       << "' (GUID: " << GlobalValue::getGUID(GlobalValueName) << ") in:\n";
    for (const std::string &Path : ModulePaths) {
      OS << "    " << Path << "\n";
    }
  }

  std::error_code convertToErrorCode() const override {
    return llvm::inconvertibleErrorCode();
  }

private:
  std::string GlobalValueName;
  std::vector<std::string> ModulePaths;
};

char DuplicateDefinitionInSummary::ID = 0;
char DefinitionNotFoundInSummary::ID = 0;

// Lookup the a function in the ModuleSummaryIndex and return the path of the
// module that defines it. Paths in the ModuleSummaryIndex are relative to the
// build directory of the covered modules.
Expected<StringRef> getMainModulePath(StringRef FunctionName,
                                      ModuleSummaryIndex &Index) {
  // Summaries use unmangled names.
  GlobalValue::GUID G = GlobalValue::getGUID(FunctionName);
  ValueInfo VI = Index.getValueInfo(G);

  // We need a unique definition, otherwise don't try further.
  if (!VI || VI.getSummaryList().empty())
    return make_error<DefinitionNotFoundInSummary>(FunctionName.str(), Index);
  if (VI.getSummaryList().size() > 1)
    return make_error<DuplicateDefinitionInSummary>(FunctionName.str(), VI);

  GlobalValueSummary *S = VI.getSummaryList().front()->getBaseObject();
  if (!isa<FunctionSummary>(S))
    return createStringError(inconvertibleErrorCode(),
                             "Entry point is not a function: " + FunctionName);

  // Return a reference. ModuleSummaryIndex owns the module paths.
  return S->modulePath();
}

// Parse the bitcode module from the given path into a ThreadSafeModule.
Expected<ThreadSafeModule> loadModule(StringRef Path,
                                      orc::ThreadSafeContext TSCtx) {
  outs() << "About to load module: " << Path << "\n";

  Expected<std::unique_ptr<MemoryBuffer>> BitcodeBuffer =
      errorOrToExpected(MemoryBuffer::getFile(Path));
  if (!BitcodeBuffer)
    return BitcodeBuffer.takeError();

  MemoryBufferRef BitcodeBufferRef = (**BitcodeBuffer).getMemBufferRef();
  Expected<std::unique_ptr<Module>> M =
      parseBitcodeFile(BitcodeBufferRef, *TSCtx.getContext());
  if (!M)
    return M.takeError();

  return ThreadSafeModule(std::move(*M), std::move(TSCtx));
}

int main(int Argc, char *Argv[]) {
  InitLLVM X(Argc, Argv);

  InitializeNativeTarget();
  InitializeNativeTargetAsmPrinter();

  cl::ParseCommandLineOptions(Argc, Argv, "LLJITWithThinLTOSummaries");

  ExitOnError ExitOnErr;
  ExitOnErr.setBanner(std::string(Argv[0]) + ": ");

  // (1) Read the index file and parse the module summary index.
  std::unique_ptr<MemoryBuffer> SummaryBuffer =
      ExitOnErr(errorOrToExpected(MemoryBuffer::getFile(IndexFile)));

  std::unique_ptr<ModuleSummaryIndex> SummaryIndex =
      ExitOnErr(getModuleSummaryIndex(SummaryBuffer->getMemBufferRef()));

  // (2) Find the path of the module that defines "main".
  std::string MainFunctionName = "main";
  StringRef MainModulePath =
      ExitOnErr(getMainModulePath(MainFunctionName, *SummaryIndex));

  // (3) Parse the main module and create a matching LLJIT.
  ThreadSafeContext TSCtx(std::make_unique<LLVMContext>());
  ThreadSafeModule MainModule = ExitOnErr(loadModule(MainModulePath, TSCtx));

  auto Builder = LLJITBuilder();

  MainModule.withModuleDo([&](Module &M) {
    if (M.getTargetTriple().empty()) {
      Builder.setJITTargetMachineBuilder(
          ExitOnErr(JITTargetMachineBuilder::detectHost()));
    } else {
      Builder.setJITTargetMachineBuilder(
          JITTargetMachineBuilder(Triple(M.getTargetTriple())));
    }
    if (!M.getDataLayout().getStringRepresentation().empty())
      Builder.setDataLayout(M.getDataLayout());
  });

  auto J = ExitOnErr(Builder.create());

  // (4) Add all modules to the LLJIT that are covered by the index.
  JITDylib &JD = J->getMainJITDylib();

  for (const auto &Entry : SummaryIndex->modulePaths()) {
    StringRef Path = Entry.first();
    ThreadSafeModule M = (Path == MainModulePath)
                             ? std::move(MainModule)
                             : ExitOnErr(loadModule(Path, TSCtx));
    ExitOnErr(J->addIRModule(JD, std::move(M)));
  }

  // (5) Look up and run the JIT'd function.
  auto MainSym = ExitOnErr(J->lookup(MainFunctionName));

  using MainFnPtr = int (*)(int, char *[]);
  MainFnPtr MainFunction =
      jitTargetAddressToFunction<MainFnPtr>(MainSym.getAddress());

  int Result = runAsMain(MainFunction, {}, MainModulePath);
  outs() << "'" << MainFunctionName << "' finished with exit code: " << Result
         << "\n";

  return 0;
}
