/*
    File: runtimeJit.cc

*/

#define DEBUG_LEVEL_FULL

// #include <llvm/Support/system_error.h>
#include <dlfcn.h>
#include <iomanip>
#include <string>
#include <llvm/Config/llvm-config.h>
#if LLVM_VERSION_MAJOR < 18
#include <llvm/ExecutionEngine/Orc/DebuggerSupportPlugin.h>
#else
#include <llvm/ExecutionEngine/Orc/Debugging/DebuggerSupportPlugin.h>
#endif
#include <llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h>
#include <clasp/core/foundation.h>
#include <clasp/core/object.h>
#include <clasp/core/cons.h>
#include <clasp/core/mpPackage.h>
#include <clasp/llvmo/code.h>
#include <clasp/gctools/snapshotSaveLoad.h>
#include <clasp/llvmo/jit.h>
#if LLVM_VERSION_MAJOR > 19
#include <llvm/ExecutionEngine/Orc/EHFrameRegistrationPlugin.h>
#endif
#if LLVM_VERSION_MAJOR > 20
#include <llvm/ExecutionEngine/Orc/SelfExecutorProcessControl.h>
#endif
//
// The include for Debug.h must be first so we can force NDEBUG undefined
// otherwise setCurrentDebugTypes will be an empty macro
#ifdef NDEBUG
#undef NDEBUG
#include "llvm/Support/Debug.h"
#define NDEBUG
#else
#include "llvm/Support/Debug.h"
#endif

#include <llvm/ADT/SmallString.h>
#include <llvm/ExecutionEngine/GenericValue.h>
#include <llvm/ExecutionEngine/SectionMemoryManager.h>
// #include <llvm/ExecutionEngine/Orc/MachOPlatform.h>
#ifdef NDEBUG
#undef NDEBUG
#define private public
#include <llvm/ExecutionEngine/Orc/ExecutionUtils.h>
#undef private
#define NDEBUG
#else
#define private public
#include <llvm/ExecutionEngine/Orc/ExecutionUtils.h>
#undef private
#endif
#include <llvm/IR/LLVMContext.h>
#include <llvm/CodeGen/LinkAllCodegenComponents.h>
#include <llvm/Bitcode/BitcodeWriter.h>
#include <llvm/Bitcode/BitcodeReader.h>
#include <llvm/IRReader/IRReader.h>
#include <llvm/IR/DiagnosticPrinter.h>
#include "llvm/MC/MCObjectFileInfo.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCDisassembler/MCDisassembler.h"
#include "llvm/MC/MCInstPrinter.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/Support/Error.h"
#include <llvm/Support/raw_ostream.h>
#include <llvm/Support/MemoryBuffer.h>
#include <llvm/Support/FileSystem.h>
#include <llvm/Support/CodeGen.h>
#include <llvm/Support/DynamicLibrary.h>
#include <llvm/LTO/legacy/ThinLTOCodeGenerator.h>
#include <llvm/Analysis/ModuleSummaryAnalysis.h>
#include <llvm/Analysis/ProfileSummaryInfo.h>
#if LLVM_VERSION_MAJOR < 17
#include <llvm/ADT/Triple.h>
#else
#include <llvm/TargetParser/Triple.h>
#endif
#include <llvm/IR/Module.h>
#include <llvm/IR/DerivedTypes.h>
#include <llvm/IR/Constants.h>
#include <llvm/IR/GlobalVariable.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/CallingConv.h>
#include <llvm/IR/AbstractCallSite.h>
#include <llvm/IR/BasicBlock.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/IntrinsicInst.h>
#include <llvm/IR/Mangler.h>
#if LLVM_VERSION_MAJOR < 20
#include <llvm/Transforms/Instrumentation.h>
#else
#include <llvm/Transforms/Utils/Instrumentation.h>
#endif
#include <llvm/Transforms/Instrumentation/ThreadSanitizer.h>
#include <llvm/Transforms/Utils/BasicBlockUtils.h>
#include <llvm/Transforms/IPO.h>
#include <llvm/Transforms/InstCombine/InstCombine.h>
#include <llvm/Transforms/Utils.h>
#include <llvm/ExecutionEngine/JITLink/EHFrameSupport.h>
#include <llvm/IR/InlineAsm.h>
#include <llvm/CodeGen/TargetPassConfig.h>
#include <llvm/Support/FormattedStream.h>
#include <llvm/MC/TargetRegistry.h>
#include <llvm/Support/MathExtras.h>
#include <llvm/Support/SourceMgr.h>
#include <llvm/Pass.h>
#include <llvm/Transforms/Utils/Cloning.h>
#include <llvm/IR/LegacyPassManager.h>
#include <llvm/ADT/SmallVector.h>
#include <llvm/IR/Verifier.h>
#include <llvm/IR/AssemblyAnnotationWriter.h> // will be llvm/IR
#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
#include <llvm/ExecutionEngine/Orc/ExecutorProcessControl.h>
#if LLVM_VERSION_MAJOR < 22
#include <llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h>
#else
#include <llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h>
#endif
#include <llvm/ExecutionEngine/Orc/TargetProcess/RegisterEHFrames.h>
#include <llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h>
#include <llvm-c/Disassembler.h>
// #include <llvm/IR/PrintModulePass.h> // will be llvm/IR  was llvm/Assembly

#include <clasp/gctools/gcFunctions.h>
#include <clasp/core/fli.h>
#include <clasp/core/compiler.h>
#include <clasp/core/lispStream.h>
#include <clasp/core/evaluator.h>
#include <clasp/external/thread-pool/thread_pool.h>
#include <clasp/llvmo/jit.h>

namespace llvmo {

using namespace llvm;
using namespace llvm::jitlink;

std::atomic<size_t> global_object_file_number;
std::atomic<size_t> global_JITDylibCounter;

std::string literals_name = OS_LITERALS_NAME;

core::SimpleBaseString_sp createSimpleBaseStringForStage(const std::string& sname) {
  core::SimpleBaseString_sp name;
  if (snapshotSaveLoad::global_InSnapshotLoad) {
    name = core::SimpleBaseString_O::make<gctools::SnapshotLoadStage>(sname);
  } else {
    name = core::SimpleBaseString_O::make<gctools::RuntimeStage>(sname);
  }
  return name;
}

uint64_t getModuleSectionIndexForText(llvm::object::ObjectFile& objf) {
  llvm::ExitOnError ExitOnErr;
  for (llvm::object::SectionRef Sec : objf.sections()) {
    if (!Sec.isText() || Sec.isVirtual())
      continue;
    if (ExitOnErr(Sec.getName()).str() == TEXT_NAME) {
      return Sec.getIndex();
    }
  }
  return llvm::object::SectionedAddress::UndefSection;
}

class ClaspAllocator : public JITLinkMemoryManager {
public:
  ClaspAllocator() {}
  ClaspAllocator(ClaspAllocator&&) = delete;
  ~ClaspAllocator() {}

public:
  mp::Mutex MyMapMutex;
  std::map<void*, std::vector<orc::shared::WrapperFunctionCall>> MyMap;

public:
  static Expected<std::unique_ptr<ClaspAllocator>> Create() {
    Error Err = Error::success();
    std::unique_ptr<ClaspAllocator> Allocator(new ClaspAllocator());
    return std::move(Allocator);
  }

  void allocate(const JITLinkDylib* JD, LinkGraph& G, OnAllocatedFunction OnAllocated) {

    typedef void* BoehmAllocHandle;

    struct MyOpaqueAlloc {
      MyOpaqueAlloc(BoehmAllocHandle StandardSegsMem) : StandardSegsMem(std::move(StandardSegsMem)) {}
      BoehmAllocHandle StandardSegsMem;
      //      std::vector<AllocationAction> DeallocActions;
    };

    class MyInFlightAlloc : public JITLinkMemoryManager::InFlightAlloc {
    public:
      ClaspAllocator* MyAllocator;

    public:
      MyInFlightAlloc(ClaspAllocator* MA, BasicLayout BL) : MyAllocator(MA), BL(std::move(BL)) {}

      void finalize(OnFinalizedFunction OnFinalized) override {
        // {1} Apply memory protections to A.DeallocSegsMem and FinalizedSegsMem.
        if (auto Err = applyProtections()) {
          OnFinalized(std::move(Err));
          return;
        }
        // (2) Scrape BL.getGraph().allocationActions() to run finalization actions
        //     and record dealloc actions in A->DeallocActions.
        // I don't know what this is so I'm doing my own thing.
        // (3) Release FinalizeSegsMem.
        // I don't know what this is so I'm ignoring it.
        // (4) Call OnFinalized(pointerToJITTargetAddress(A.release()));
        void* execMemory = NULL;
        size_t numSegments = 0;
        for (auto& KV : BL.segments()) {
          const auto& AG = KV.first;
          auto& Seg = KV.second;
          auto Prot = toSysMemoryProtectionFlags(AG.getMemProt());
          if (Prot & sys::Memory::MF_EXEC)
            execMemory = (void*)Seg.WorkingMem;
          ++numSegments;
        }
        if (auto DeallocActions = runFinalizeActions(BL.getGraph().allocActions())) {
          RAIILock lock(this->MyAllocator->MyMapMutex);
          this->MyAllocator->MyMap[execMemory] = std::move(*DeallocActions);
        } else {
          return OnFinalized(DeallocActions.takeError());
        }
        llvm::orc::ExecutorAddr ea((uintptr_t)execMemory);
        OnFinalized(llvm::jitlink::InProcessMemoryManager::FinalizedAlloc(ea));
      }

      virtual void abandon(OnAbandonedFunction OnAbandoned) {
        size_t PageSize = getpagesize();
        for (auto& KV : BL.segments()) {
          auto& Seg = KV.second;
          sys::MemoryBlock MB(Seg.WorkingMem, alignTo(Seg.ContentSize + Seg.ZeroFillSize, PageSize));
          auto code = sys::Memory::releaseMappedMemory(MB);
          if (code) {
            OnAbandoned(std::move(llvm::errorCodeToError(code)));
            return;
          }
        }
        OnAbandoned(std::move(llvm::Error::success()));
      }

    private:
      Error applyProtections() {
        JITMemoryReadExecute(BL);
        return Error::success();
      }

    private:
      BasicLayout BL;
    };

    //
    // New llvm-13.0.1 allocation code
    //
    BasicLayout BL(G);
    size_t segmentCount = 0;
    for ([[maybe_unused]] auto& KV : BL.segments()) {
      segmentCount++;
    }

    //
    // Find space in a CodeBlock and set Addr and WorkingMem of each Seg in BL.segments()
    //   if the current CodeBlock doesn't have room then allocate another one.
    //   Return the CodeBlock in codeBlock
    //
    if (segmentCount > 1) {
      CodeBlock_sp codeBlock;

      if (snapshotSaveLoad::global_InSnapshotLoad) {
        allocateInCodeBlock<gctools::SnapshotLoadStage>(BL, codeBlock);
      } else {
        allocateInCodeBlock<gctools::RuntimeStage>(BL, codeBlock);
      }

      //
      // Create the codeObject and add it to the global AllObjectFiles
      //
#if 0
      core::List_sp jitdylibs = _lisp->_Roots._JITDylibs.load();
      JITDylib_sp theJITDylib = unbound<JITDylib_O>();
      core::T_sp cur = jitdylibs;
      while (cur.consp()) {
        JITDylib_sp one = gc::As<JITDylib_sp>(CONS_CAR(cur));
        JITDylib* jitdylib = one->wrappedPtr();
        JITLinkDylib* jitlinkdylib = llvm::cast<JITLinkDylib>(jitdylib);
        if (jitlinkdylib == JD) theJITDylib = one;
        cur = CONS_CDR(cur);
      }
      if (theJITDylib.unboundp()) {
        printf("%s:%d:%s Could not identify the JITDylib_sp for JITLinkDylib* %p\n", __FILE__, __LINE__, __FUNCTION__, JD );
        abort();
      }
#endif
      ObjectFile_sp codeObject;
      codeObject = lookupObjectFile(G.getName());
      codeObject->_CodeBlock = codeBlock;
      [[maybe_unused]] core::SimpleBaseString_sp codeName = createSimpleBaseStringForStage(G.getName());
    } else {
      for (auto& KV : BL.segments()) {
        auto& Seg = KV.second;
        uint64_t ZeroFillStart = Seg.ContentSize;
        size_t SegmentSize = (uintptr_t)gctools::AlignUp(ZeroFillStart + Seg.ZeroFillSize, Seg.Alignment.value());
        void* base;
        base = aligned_alloc(Seg.Alignment.value(), SegmentSize);
        llvm::orc::ExecutorAddr eabase((uintptr_t)base);
        Seg.Addr = eabase;
        Seg.WorkingMem = jitTargetAddressToPointer<char*>((llvm::JITTargetAddress)base);
      }
    }

    if (auto Err = BL.apply()) {
      OnAllocated(std::move(Err));
      return;
    }

    OnAllocated(std::make_unique<MyInFlightAlloc>(this, BL));
  }

  //
  // deallocate only happens at shutdown - so I don't have a Mutex or much else to do
  //
  virtual void deallocate(std::vector<FinalizedAlloc> Allocs, OnDeallocatedFunction OnDeallocated) {
    // std::lock_guard<std::mutex> Lock(FinalizedAllocsMutex);
    Error DeallocErr = Error::success();
    RAIILock lock(this->MyMapMutex);
    for (auto& Alloc : Allocs) {
      // See if there are any dealloc actions to run.
      auto I = this->MyMap.find((void*)Alloc.release().getValue());
      if (I != this->MyMap.end()) {
        DeallocErr = joinErrors(std::move(DeallocErr), runDeallocActions(I->second));
        this->MyMap.erase(I);
      }
    }
    OnDeallocated(std::move(DeallocErr));
  };
};

}; // namespace llvmo

namespace llvmo {

class ClaspPlugin : public llvm::orc::ObjectLinkingLayer::Plugin {
  static constexpr auto personality_name = "DW.ref.__gxx_personality_v0";

  static bool isPersonalitySymbol(llvm::jitlink::Symbol* symbol) {
#if LLVM_VERSION_MAJOR < 20
    return symbol->getName() == personality_name;
#else
    return symbol->hasName() && (*symbol->getName()).compare(personality_name) == 0;
#endif
  }

  static std::string getSymbolName(llvm::jitlink::Symbol* symbol) {
#if LLVM_VERSION_MAJOR < 20
    return symbol->hasName() ? symbol->getName().str() : "";
#else
    return symbol->hasName() ? std::string(*symbol->getName()) : std::string();
#endif
  }

  void modifyPassConfig(llvm::orc::MaterializationResponsibility& MR, llvm::jitlink::LinkGraph& G,
                        llvm::jitlink::PassConfiguration& Config) {
    auto PersonalitySymbol = MR.getTargetJITDylib().getExecutionSession().intern(personality_name);
    if (!MR.getSymbols().count(PersonalitySymbol))
      Config.PrePrunePasses.insert(Config.PrePrunePasses.begin(), [this](jitlink::LinkGraph& G) -> Error {
        for (auto ssym : G.defined_symbols()) {
          if (isPersonalitySymbol(ssym)) {
            ssym->setLinkage(Linkage::Strong);
            ssym->setScope(Scope::Local);
            break;
          }
        }
        return Error::success();
      });
    Config.PrePrunePasses.push_back([this](jitlink::LinkGraph& G) -> Error {
      size_t count = 0;
      for (auto& Sec : G.sections()) {
        if (Sec.getName() == EH_FRAME_NAME)
          for (auto* S : Sec.symbols()) {
            S->setLive(true);
            count++;
          }
      }
      for (auto ssym : G.defined_symbols())
        if (ssym->hasName()) {
          std::string sname = getSymbolName(ssym);
          bool keptAlive = false;
#ifdef _TARGET_OS_LINUX
          keptAlive = true;
#endif
          if (sname.find(literals_name) != std::string::npos) {
            keptAlive = true;
#if 0
                                          // I'd like to do this on linux because jit symbols need to be exposed
                                          // but it slows down startup enormously
                                        } else if ( sname.find("^^") != std::string::npos ) {
                                          // Keep alive mangled symbols that we care about
//                                          keptAlive = true;
#endif
          }
          if (keptAlive)
            ssym->setLive(true);
        }
      return Error::success();
    });
    Config.PrePrunePasses.push_back([this](jitlink::LinkGraph& G) -> Error {
      keepAliveStackmap(G);
      // printLinkGraph(G, "PrePrune:");
      return Error::success();
    });
    Config.PostFixupPasses.push_back([this](jitlink::LinkGraph& G) -> Error {
      parseLinkGraph(G);
      // printLinkGraph(G, "PostFixup:");
      return Error::success();
    });
  }

  void notifyLoaded(llvm::orc::MaterializationResponsibility& MR) {}

  llvm::Error notifyFailed(llvm::orc::MaterializationResponsibility& MR) { return Error::success(); }

#if LLVM_VERSION_MAJOR < 16
  llvm::Error notifyRemovingResources(ResourceKey K) { return Error::success(); }

  void notifyTransferringResources(ResourceKey DstKey, ResourceKey SrcKey) {}
#else
  Error notifyRemovingResources(JITDylib& JD, ResourceKey K) { return Error::success(); };

  void notifyTransferringResources(JITDylib& JD, ResourceKey DstKey, ResourceKey SrcKey) {}
#endif

  void keepAliveStackmap(llvm::jitlink::LinkGraph& G) {
    for (auto& S : G.sections()) {
      if (S.getName().str() == STACKMAPS_NAME) {
        for (auto& sym : S.symbols())
          sym->setLive(true);
      }
    }
  }

  void parseLinkGraph(llvm::jitlink::LinkGraph& G) {
    uintptr_t textStart = ~0;
    uintptr_t textEnd = 0;
    ObjectFile_sp currentCode = lookupObjectFile(G.getName());
    for (auto& S : G.sections()) {
      std::string sectionName = S.getName().str();
      auto Prot = toSysMemoryProtectionFlags(S.getMemProt());
      if ((sectionName.find(BSS_NAME) != string::npos) || (sectionName.find(DATA_NAME) != string::npos)) {
      } else if (Prot & llvm::sys::Memory::MF_EXEC) {
        // Text section
        llvm::jitlink::SectionRange range(S);
        if ((uintptr_t)range.getStart().getValue() < textStart)
          textStart = (uintptr_t)range.getStart().getValue();
        uintptr_t tend = (uintptr_t)range.getStart().getValue() + range.getSize();
        if (textEnd < tend)
          textEnd = tend;
        currentCode->_TextSectionStart = (void*)range.getStart().getValue();
        currentCode->_TextSectionEnd = (void*)((char*)range.getStart().getValue() + range.getSize());
        for (auto& sym : S.symbols()) {
          if (sym->isCallable() && sym->hasName()) {
            std::string name = getSymbolName(sym);
            void* address = (void*)sym->getAddress().getValue();
            size_t size = sym->getSize();
            core::core__jit_register_symbol(name, size, (void*)address);
          }
        }
      } else if (sectionName.find(EH_FRAME_NAME) != string::npos) {
        llvm::jitlink::SectionRange range(S);
      } else if (sectionName.find(STACKMAPS_NAME) != string::npos) {
        llvm::jitlink::SectionRange range(S);
        currentCode->_StackmapStart = (void*)range.getStart().getValue();
        currentCode->_StackmapSize = (size_t)range.getSize();
      }
    }
    // Keep track of the executable region
    if (textStart) {
      //      printf("%s:%d:%s  textStart %p - textStop %p\n", __FILE__, __LINE__, __FUNCTION__, (void*)textStart, (void*)textEnd );
      currentCode->_TextSectionStart = (void*)textStart;
      currentCode->_TextSectionEnd = (void*)textEnd;
    } else {
      printf("%s:%d:%s No executable region was found for the Code_O object for graph %s\n", __FILE__, __LINE__, __FUNCTION__,
             G.getName().c_str());
      for (auto* Sym : G.external_symbols())
        if (Sym->hasName())
          fmt::print("       Symbol: {}\n", getSymbolName(Sym));
    }
    //
    bool found_literals = false;
    for (auto ssym : G.defined_symbols())
      if (isPersonalitySymbol(ssym)) {
        ssym->setLinkage(Linkage::Strong);
        ssym->setScope(Scope::Local);
        break;
      }
    for (auto ssym : G.defined_symbols()) {
      if (ssym->hasName()) {
        std::string sname = getSymbolName(ssym);
        size_t pos;
        pos = sname.find(literals_name);
        if (pos != std::string::npos) {
          found_literals = true;
          currentCode->setLiteralVectorStart((void*)ssym->getAddress().getValue());
          size_t origSymbolSize = (size_t)ssym->getSize();
          size_t symbolSize = origSymbolSize;
          if (symbolSize == 1) {
            // A symbol of size 1 is really zero
            symbolSize = 0;
#ifdef _TARGET_OS_DARWIN
          } else if (symbolSize == 8) {
            // On DARWIN assume symbolSize of 8 is really 0
            // because DARWIN doesn't save symbol size
            // This may be a cludge - I'm not sure (Chris Schafmeister, 2023)
#if 0
            printf("%s:%d:%s Assuming symbolSize is 0 on DARWIN for currentCode->_LiteralVectorStart = %p\n",
                   __FILE__, __LINE__, __FUNCTION__, (void*)currentCode->_LiteralVectorStart );
#endif
            symbolSize = 0;
#endif

          } else if ((symbolSize & 7) != 0) {
            printf("%s:%d:%s The symbol %s is %lu bytes in size but it must be a multiple of 8 bytes!!!\n", __FILE__, __LINE__,
                   __FUNCTION__, sname.c_str(), symbolSize);
            abort();
          }
          currentCode->_LiteralVectorSizeBytes = symbolSize;
        }
      }
    }
    if (!found_literals) {
      printf("%s:%d Did NOT FIND %s\n", __FILE__, __LINE__, literals_name.c_str());
      abort();
    }
    //
  }

  void printLinkGraph(llvm::jitlink::LinkGraph& G, llvm::StringRef Title) {
    constexpr llvm::JITTargetAddress LineWidth = 16;
    stringstream ss;
    ss << "--- " << Title.str() << "---\n";
    for (auto& S : G.sections()) {
      ss << "  section: " << S.getName().str() << "\n";
      for (auto* B : S.blocks()) {
        ss << "    block@";
        ss << formatv("{0:x16}", B->getAddress()).str();
        ss << ":\n";
        if (B->isZeroFill())
          continue;
        llvm::JITTargetAddress InitAddr = B->getAddress().getValue() & ~(LineWidth - 1);
        llvm::JITTargetAddress StartAddr = B->getAddress().getValue();
        llvm::JITTargetAddress EndAddr = B->getAddress().getValue() + B->getSize();
        auto* Data = reinterpret_cast<const uint8_t*>(B->getContent().data());
        for (llvm::JITTargetAddress CurAddr = InitAddr; CurAddr != EndAddr; ++CurAddr) {
          if (CurAddr % LineWidth == 0)
            ss << "    " << formatv("{0:x16}", CurAddr).str() << ": ";
          if (CurAddr < StartAddr)
            ss << "   ";
          else
            ss << formatv("{0:x-2}", Data[CurAddr - StartAddr]).str() << " ";
          if (CurAddr % LineWidth == LineWidth - 1)
            ss << "\n";
        }
        if (EndAddr % LineWidth != 0)
          ss << "\n";
        ss << "\n";
      }
    }
    printf("%s\n", ss.str().c_str());
  }
};

}; // namespace llvmo

namespace llvmo {

ClaspJIT_O::ClaspJIT_O() {
  llvm::ExitOnError ExitOnErr;
  auto JTMB = ExitOnErr(JITTargetMachineBuilder::detectHost());
  TargetOptions to;
  to.FunctionSections = false;
  JTMB.setOptions(to);
  JTMB.setCodeModel(CodeModel::Small);
  JTMB.setRelocationModel(Reloc::Model::PIC_);
  auto TPC = ExitOnErr(orc::SelfExecutorProcessControl::Create(std::make_shared<orc::SymbolStringPool>()));
  auto J = ExitOnErr(
      LLJITBuilder()
          .setExecutionSession(std::make_unique<ExecutionSession>(std::move(TPC)))
          .setNumCompileThreads(0) // <<<<<<< In May 2021 a path will open to use multicores for LLJIT.
          .setJITTargetMachineBuilder(std::move(JTMB))
#if LLVM_VERSION_MAJOR < 22
          .setObjectLinkingLayerCreator([this, &ExitOnErr](ExecutionSession& ES, const Triple& TT) {
            auto ObjLinkingLayer = std::make_unique<ObjectLinkingLayer>(ES, std::make_unique<ClaspAllocator>());
            ObjLinkingLayer->addPlugin(
                std::make_unique<EHFrameRegistrationPlugin>(ES, std::make_unique<jitlink::InProcessEHFrameRegistrar>()));
            ObjLinkingLayer->addPlugin(std::make_unique<ClaspPlugin>());
            // GDB registrar isn't working at the moment
            if (!getenv("CLASP_NO_GDB_JIT")) {
              ObjLinkingLayer->addPlugin(
                  std::make_unique<orc::DebugObjectManagerPlugin>(ES, ExitOnErr(orc::createJITLoaderGDBRegistrar(ES))));
              if (TT.isOSBinFormatMachO()) {
                ObjLinkingLayer->addPlugin(std::make_unique<GDBJITDebugInfoRegistrationPlugin>(
                    llvm::orc::ExecutorAddr::fromPtr(&llvm_orc_registerJITLoaderGDBWrapper)));
              }
            }
            ObjLinkingLayer->setReturnObjectBuffer(ClaspReturnObjectBuffer); // <<< Capture the ObjectBuffer after JITting code
            return ObjLinkingLayer;
          })
#else
          .setObjectLinkingLayerCreator([this, &ExitOnErr](ExecutionSession& ES) {
            auto ObjLinkingLayer = std::make_unique<ObjectLinkingLayer>(ES, std::make_unique<ClaspAllocator>());
            ObjLinkingLayer->addPlugin(ExitOnErr(orc::EHFrameRegistrationPlugin::Create(ES)));
            ObjLinkingLayer->addPlugin(std::make_unique<ClaspPlugin>());
            if (!getenv("CLASP_NO_GDB_JIT")) {
              Error TargetSymErr = Error::success();
              auto Plugin = std::make_unique<ELFDebugObjectPlugin>(ES, true, true, TargetSymErr);
              ExitOnErr(std::move(TargetSymErr));
              ObjLinkingLayer->addPlugin(std::move(Plugin));
              if (ES.getTargetTriple().isOSBinFormatMachO()) {
                ObjLinkingLayer->addPlugin(std::make_unique<GDBJITDebugInfoRegistrationPlugin>(
                    llvm::orc::ExecutorAddr::fromPtr(&llvm_orc_registerJITLoaderGDBAllocAction)));
              }
            }
            ObjLinkingLayer->setReturnObjectBuffer(ClaspReturnObjectBuffer); // <<< Capture the ObjectBuffer after JITting code
            return ObjLinkingLayer;
          })
#endif
          .create());
  this->_LLJIT = std::move(J);

  this->_LLJIT->getMainJITDylib().addGenerator(
      llvm::cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess(this->_LLJIT->getDataLayout().getGlobalPrefix())));
}

// Allocate a Lisp object for the _MainJITDylib for this JIT.
// The actual underlying LLVM dylib has already been built by our constructor -
// here we just wrap it up in a JITDylib_O object and add it to _JITDylibs.
void ClaspJIT_O::installMainJITDylib() {
  JITDylib& under = this->_LLJIT->getMainJITDylib();
  auto sname = core::SimpleBaseString_O::make(under.getName());
  _MainJITDylib = gc::GC<JITDylib_O>::allocate(sname, &under);
  auto pair = core::Cons_O::create(_MainJITDylib, nil<core::T_O>());
  _lisp->_Roots._JITDylibs.store(pair);
}

// Modifying an existing _MainJITDylib smart pointer to use our main jit dylib.
// This is used in snapshot load.
void ClaspJIT_O::adjustMainJITDylib(JITDylib_sp mainJITDylib) {
  JITDylib& dylib = this->_LLJIT->getMainJITDylib();
  auto sname = core::SimpleBaseString_O::make(dylib.getName());
  new (&*mainJITDylib) JITDylib_O(sname, &dylib);
  this->_MainJITDylib = mainJITDylib;
}

[[nodiscard]] bool ClaspJIT_O::do_lookup(JITDylib_sp dylibsp, const std::string& Name, void*& ptr) {
  JITDylib& dylib = *dylibsp->wrappedPtr();
  std::string mangledName = Name;
  auto symbol = this->_LLJIT->lookup(dylib, mangledName);
  if (!symbol) {
    // printf("%s:%d could not find external linkage symbol named: %s\n", __FILE__, __LINE__, mangledName.c_str() );
    // dylib.dump(llvm::errs());
    return false;
  }
  //  printf("%s:%d:%s !!symbol -> %d  symbol->getAddress() -> %p\n", __FILE__, __LINE__, __FUNCTION__, !!symbol,
  //  (void*)symbol->getAddress());
  ptr = (void*)symbol->getValue();
  return true;
}

void* ClaspJIT_O::lookup_literals(JITDylib_sp dylibsp, size_t id) {
  void* literals = NULL;
  int namelen = snprintf(NULL, 0, "__clasp_literals_%zu", id);
  char literals_name[namelen + 1]; // +1 for null terminator
  sprintf(literals_name, "__clasp_literals_%zu", id);
  // ok to discard because literals is NULL iff lookup failed
  (void)do_lookup(dylibsp, literals_name, literals);
  return literals;
}

void* ClaspJIT_O::lookup_fvector(JITDylib_sp dylibsp, size_t id) {
  void* fvector = NULL;
  int namelen = snprintf(NULL, 0, "function-vector-%zu", id);
  char fvector_name[namelen + 1];
  sprintf(fvector_name, "function-vector-%zu", id);
  (void)do_lookup(dylibsp, fvector_name, fvector);
  return fvector;
}

[[nodiscard]] bool ClaspJIT_O::force_materialize(JITDylib_sp dylibsp, size_t id) {
  return lookup_literals(dylibsp, id) != NULL && lookup_fvector(dylibsp, id) != NULL;
}

CL_DEFMETHOD core::Pointer_sp ClaspJIT_O::lookup(JITDylib_sp dylibsp, const std::string& Name) {
  void* ptr;
  bool found = this->do_lookup(dylibsp, Name, ptr);
  if (!found) {
    SIMPLE_ERROR("Could not find pointer for name |{}|", Name);
  }
  return core::Pointer_O::create(ptr);
}

CL_DEFMETHOD core::T_sp ClaspJIT_O::lookup_all_dylibs(const std::string& name) {
  core::T_sp jcur = _lisp->_Roots._JITDylibs.load();
  void* ptr;
  while (jcur.consp()) {
    JITDylib_sp jitdylib = gc::As<JITDylib_sp>(CONS_CAR(jcur));
    bool found = this->do_lookup(jitdylib, name, ptr);
    if (found) {
      clasp_ffi::ForeignData_sp sp_sym = clasp_ffi::ForeignData_O::create(ptr);
      sp_sym->set_kind(kw::_sym_clasp_foreign_data_kind_symbol_pointer);
      return sp_sym;
    }
    jcur = CONS_CDR(jcur);
  }
  return nil<core::T_O>();
}

ObjectFile_sp prepareObjectFileForMaterialization(JITDylib_sp dylib, const std::string& uniqueObjectFileName, size_t objectId) {
  core::SimpleBaseString_sp sbs = core::SimpleBaseString_O::make(uniqueObjectFileName);
  ObjectFile_sp codeObject = gc::GC<ObjectFile_O>::allocate<gctools::RuntimeStage>(sbs, unbound<CodeBlock_O>(), dylib, objectId);
  registerObjectFile<gctools::RuntimeStage>(codeObject);
  return codeObject;
};

CL_DEFMETHOD ObjectFile_sp ClaspJIT_O::addIRModule(JITDylib_sp dylib, Module_sp module, ThreadSafeContext_sp context,
                                                   size_t startupID) {
  //  printf("%s:%d:%s module = %p\n", __FILE__, __LINE__, __FUNCTION__, module.raw_() );
  std::unique_ptr<llvm::Module> umodule(module->wrappedPtr());
  llvm::ExitOnError ExitOnErr;
  std::string prefix;
  std::string futureName = createIRModuleObjectFileName(startupID, prefix);
  module->wrappedPtr()->setModuleIdentifier(prefix);
  ObjectFile_sp codeObject = prepareObjectFileForMaterialization(dylib, futureName, startupID);
  ExitOnErr(
      this->_LLJIT->addIRModule(*dylib->wrappedPtr(), llvm::orc::ThreadSafeModule(std::move(umodule), *context->wrappedPtr())));
  return codeObject;
}

ObjectFile_sp ClaspJIT_O::addObjectFile(JITDylib_sp dylib, std::unique_ptr<llvm::MemoryBuffer> objectFile, bool print,
                                        size_t startupId) {
  // Create an llvm::MemoryBuffer for the ObjectFile bytes
  //  printf("%s:%d:%s \n", __FILE__, __LINE__, __FUNCTION__ );
  if (print)
    core::clasp_write_string(fmt::format("{}:{} Adding object file at {}  {} bytes\n", __FILE__, __LINE__,
                                         (void*)objectFile->getBufferStart(), objectFile->getBufferSize()));
  // Force the object file to be linked using MaterializationUnit::doMaterialize(...)
  if (print)
    core::clasp_write_string(fmt::format("{}:{} Materializing\n", __FILE__, __LINE__));
  //  llvmo::ObjectFile_sp of = llvmo::ObjectFile_O::createForObjectFile(objectFile->getBufferIdentifier().str(), dylib );
  llvm::ExitOnError ExitOnErr;
  ObjectFile_sp codeObject = prepareObjectFileForMaterialization(dylib, objectFile->getBufferIdentifier().str(), startupId);
  ExitOnErr(this->_LLJIT->addObjectFile(*dylib->wrappedPtr(), std::move(objectFile)));
  return codeObject;
}

CL_DEFMETHOD JITDylib_sp ClaspJIT_O::getMainJITDylib() {
  if (this->_MainJITDylib.raw_() && gctools::untag_general<core::T_O*>(this->_MainJITDylib.raw_())) {
    return this->_MainJITDylib;
  }
  SIMPLE_ERROR("The main-jit-dylib of the JIT is not setup properly: raw -> {}\n", (void*)this->_MainJITDylib.raw_());
}

CL_DEFMETHOD JITDylib_sp ClaspJIT_O::createAndRegisterJITDylib(const std::string& name) {
#if LLVM_VERSION_MAJOR >= 22
  llvm::ExitOnError ExitOnErr;
#endif
  stringstream sname;
  sname << name << "-" << global_JITDylibCounter;
  //  printf("%s:%d:%s  name -> %s\n", __FILE__, __LINE__, __FUNCTION__, sname.str().c_str());
#if LLVM_VERSION_MAJOR < 22
  auto dy = this->_LLJIT->createJITDylib(sname.str());
  JITDylib& dylib(*dy);
#else
  JITDylib& dylib = ExitOnErr(_LLJIT->createJITDylib(sname.str()));
#endif
  dylib.addGenerator(
      llvm::cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess(this->_LLJIT->getDataLayout().getGlobalPrefix())));
  core::SimpleBaseString_sp ssname = core::SimpleBaseString_O::make(sname.str());
  JITDylib_sp dylib_sp = gc::GC<JITDylib_O>::allocate(ssname, &dylib);
  dylib_sp->_Id = ++global_JITDylibCounter;
  core::Cons_sp cell = core::Cons_O::create(dylib_sp, nil<core::T_O>());
  core::T_sp expected;
  core::T_sp current;
  // Use CAS to push the new JITDylib into the list of JITDylibs.
  do {
    current = _lisp->_Roots._JITDylibs.load();
    expected = current;
    cell->rplacd(current);
    _lisp->_Roots._JITDylibs.compare_exchange_strong(expected, gc::As_unsafe<core::T_sp>(cell));
  } while (expected != current);
  return dylib_sp;
}

void ClaspReturnObjectBuffer(std::unique_ptr<llvm::MemoryBuffer> buffer) {
  // Grab the buffer and put it in the current ObjectFile

  ObjectFile_sp code = lookupObjectFile(buffer->getBufferIdentifier().str());
  code->_MemoryBuffer = std::move(buffer);
  llvm::ExitOnError ExitOnErr;
  auto ofp = ExitOnErr(code->getObjectFile());
  llvm::object::ObjectFile& of = *ofp;
#if defined(_TARGET_OS_LINUX)
  uint64_t secId = getModuleSectionIndexForText(of);
  code->_TextSectionId = secId;
#elif defined(_TARGET_OS_DARWIN)
  code->_TextSectionId = 0;
#else
  printf("%s:%d:%s Add support to set _TextSectionID for this os\n", __FILE__, __LINE__, __FUNCTION__);
#endif
}

void ClaspJIT_O::registerJITDylibAfterLoad(JITDylib_sp jitDylib) {
  if (jitDylib->_Id >= global_JITDylibCounter.load()) {
    global_JITDylibCounter.store(jitDylib->_Id + 1);
  }
}

#if 0
/void handleObjectEmitted(VModuleKey K, std::unique_ptr<MemoryBuffer> O) {
//  printf("%s:%d:%s Received emitted object buffer obj@%p\n", __FILE__,__LINE__, __FUNCTION__, (void*)O->getBufferStart());
}
#endif

}; // namespace llvmo
