// This file is a part of Julia. License is MIT: https://julialang.org/license

#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Constants.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/Value.h>

#include <llvm/ExecutionEngine/SectionMemoryManager.h>
#if defined(USE_ORCJIT)
#include "llvm/ExecutionEngine/Orc/CompileUtils.h"
#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h"
#include "llvm/ExecutionEngine/Orc/LambdaResolver.h"
#include "llvm/ExecutionEngine/Orc/LazyEmittingLayer.h"
#if JL_LLVM_VERSION >= 50000
#  include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h"
#else
#  include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h"
#endif
#include "llvm/ExecutionEngine/ObjectMemoryBuffer.h"
#elif defined(USE_MCJIT)
#include <llvm/ExecutionEngine/MCJIT.h>
#include <llvm/ADT/DenseMapInfo.h>
#include <llvm/Object/ObjectFile.h>
#else
#include <llvm/ExecutionEngine/JIT.h>
#include <llvm/ExecutionEngine/JITMemoryManager.h>
#include <llvm/ExecutionEngine/Interpreter.h>
#include <llvm/ExecutionEngine/ExecutionEngine.h>
#include <llvm/ExecutionEngine/JITEventListener.h>
#endif

#if JL_LLVM_VERSION >= 30700
#include "llvm/IR/LegacyPassManager.h"
extern legacy::PassManager *jl_globalPM;
#else
#include <llvm/PassManager.h>
extern PassManager *jl_globalPM;
#endif

#if JL_LLVM_VERSION >= 30500
#include <llvm/Target/TargetMachine.h>
#endif
#include "fix_llvm_assert.h"

extern "C" {
    extern int globalUnique;
}
extern TargetMachine *jl_TargetMachine;
extern Module *shadow_output;
extern bool imaging_mode;
#if defined(_OS_WINDOWS_) && defined(_CPU_X86_64_)
extern Function *juliapersonality_func;
#endif


#ifdef JULIA_ENABLE_THREADING
extern size_t jltls_states_func_idx;
#endif

typedef struct {Value *gv; int32_t index;} jl_value_llvm; // uses 1-based indexing

#if JL_LLVM_VERSION >= 30700
void addOptimizationPasses(legacy::PassManager *PM);
#else
void addOptimizationPasses(PassManager *PM);
#endif
void* jl_emit_and_add_to_shadow(GlobalVariable *gv, void *gvarinit = NULL);
GlobalVariable *jl_emit_sysimg_slot(Module *m, Type *typ, const char *name,
                                           uintptr_t init, size_t &idx);
void* jl_get_global(GlobalVariable *gv);
GlobalVariable *jl_get_global_for(const char *cname, void *addr, Module *M);
void jl_add_to_shadow(Module *m);
void jl_init_function(Function *f);
bool jl_can_finalize_function(Function *F);
void jl_finalize_function(Function *F);
void jl_finalize_module(Module *m, bool shadow);

// Connect Modules via prototypes, each owned by module `M`
static inline GlobalVariable *global_proto(GlobalVariable *G, Module *M = NULL)
{
    // Copy the GlobalVariable, but without the initializer, so it becomes a declaration
    GlobalVariable *proto = new GlobalVariable(G->getType()->getElementType(),
            G->isConstant(), GlobalVariable::ExternalLinkage,
            NULL, G->getName(),  G->getThreadLocalMode());
    proto->copyAttributesFrom(G);
#if JL_LLVM_VERSION >= 30500
    // DLLImport only needs to be set for the shadow module
    // it just gets annoying in the JIT
    proto->setDLLStorageClass(GlobalValue::DefaultStorageClass);
#endif
    if (M)
        M->getGlobalList().push_back(proto);
    return proto;
}

static inline Function *function_proto(Function *F, Module *M = NULL)
{
    // Copy the declaration characteristics of the Function (not the body)
    Function *NewF = Function::Create(F->getFunctionType(),
                                      Function::ExternalLinkage,
                                      F->getName(), M);

    // Declarations are not allowed to have personality routines, but
    // copyAttributesFrom sets them anyway. Temporarily unset the personality
    // routine from `F`, since copying it and then resetting is more expensive
    // as well as introducing an extra use from this unowned function, which
    // can cause crashes in the LLVMContext's global destructor.
#if JL_LLVM_VERSION >= 30700
    llvm::Constant *OldPersonalityFn = nullptr;
    if (F->hasPersonalityFn()) {
        OldPersonalityFn = F->getPersonalityFn();
        F->setPersonalityFn(nullptr);
    }
#endif

     // FunctionType does not include any attributes. Copy them over manually
     // as codegen may make decisions based on the presence of certain attributes
     NewF->copyAttributesFrom(F);

#if JL_LLVM_VERSION >= 30700
    if (OldPersonalityFn)
        F->setPersonalityFn(OldPersonalityFn);
#endif

#if JL_LLVM_VERSION >= 30500
    // DLLImport only needs to be set for the shadow module
    // it just gets annoying in the JIT
    NewF->setDLLStorageClass(GlobalValue::DefaultStorageClass);
#endif

    return NewF;
}

static inline GlobalVariable *prepare_global(GlobalVariable *G, Module *M)
{
    if (G->getParent() == M)
        return G;
    GlobalValue *local = M->getNamedValue(G->getName());
    if (!local) {
        local = global_proto(G, M);
    }
    return cast<GlobalVariable>(local);
}

#if JL_LLVM_VERSION >= 30500
void add_named_global(GlobalObject *gv, void *addr, bool dllimport);
template<typename T>
static inline void add_named_global(GlobalObject *gv, T *addr, bool dllimport = true)
#else
void add_named_global(GlobalValue *gv, void *addr, bool dllimport);
template<typename T>
static inline void add_named_global(GlobalValue *gv, T *addr, bool dllimport = true)
#endif
{
    // cast through integer to avoid c++ pedantic warning about casting between
    // data and code pointers
    add_named_global(gv, (void*)(uintptr_t)addr, dllimport);
}

void jl_init_jit(Type *T_pjlvalue_);
#ifdef USE_ORCJIT
#if JL_LLVM_VERSION >= 40000
typedef JITSymbol JL_JITSymbol;
// The type that is similar to SymbolInfo on LLVM 4.0 is actually
// `JITEvaluatedSymbol`. However, we only use this type when a JITSymbol
// is expected.
typedef JITSymbol JL_SymbolInfo;
#else
typedef orc::JITSymbol JL_JITSymbol;
typedef RuntimeDyld::SymbolInfo JL_SymbolInfo;
#endif
#if JL_LLVM_VERSION >= 50000
using orc::RTDyldObjectLinkingLayerBase;
using orc::RTDyldObjectLinkingLayer;
#else
using RTDyldObjectLinkingLayerBase = orc::ObjectLinkingLayerBase;
template <typename NotifyLoadedFtor>
using RTDyldObjectLinkingLayer = orc::ObjectLinkingLayer<NotifyLoadedFtor>;
#endif

class JuliaOJIT {
    // Custom object emission notification handler for the JuliaOJIT
    // TODO: hook up RegisterJITEventListener, instead of hard-coding the GDB and JuliaListener targets
    class DebugObjectRegistrar {
    public:
        DebugObjectRegistrar(JuliaOJIT &JIT);
        template <typename ObjSetT, typename LoadResult>
        void operator()(RTDyldObjectLinkingLayerBase::ObjSetHandleT H, const ObjSetT &Objects,
                        const LoadResult &LOS);
    private:
        void NotifyGDB(object::OwningBinary<object::ObjectFile> &DebugObj);
        std::vector<object::OwningBinary<object::ObjectFile>> SavedObjects;
        std::unique_ptr<JITEventListener> JuliaListener;
        JuliaOJIT &JIT;
    };

public:
    typedef RTDyldObjectLinkingLayer<DebugObjectRegistrar> ObjLayerT;
    typedef orc::IRCompileLayer<ObjLayerT> CompileLayerT;
    typedef CompileLayerT::ModuleSetHandleT ModuleHandleT;
    typedef StringMap<void*> SymbolTableT;
    typedef object::OwningBinary<object::ObjectFile> OwningObj;

    JuliaOJIT(TargetMachine &TM);

    void addGlobalMapping(StringRef Name, uint64_t Addr);
    void addGlobalMapping(const GlobalValue *GV, void *Addr);
    void *getPointerToGlobalIfAvailable(StringRef S);
    void *getPointerToGlobalIfAvailable(const GlobalValue *GV);
    void addModule(std::unique_ptr<Module> M);
    void removeModule(ModuleHandleT H);
    JL_JITSymbol findSymbol(const std::string &Name, bool ExportedSymbolsOnly);
    JL_JITSymbol findUnmangledSymbol(const std::string Name);
    uint64_t getGlobalValueAddress(const std::string &Name);
    uint64_t getFunctionAddress(const std::string &Name);
    Function *FindFunctionNamed(const std::string &Name);
    void RegisterJITEventListener(JITEventListener *L);
    const DataLayout& getDataLayout() const;
    const Triple& getTargetTriple() const;
private:
    std::string getMangledName(const std::string &Name);
    std::string getMangledName(const GlobalValue *GV);

    TargetMachine &TM;
    const DataLayout DL;
    // Should be big enough that in the common case, The
    // object fits in its entirety
    SmallVector<char, 4096> ObjBufferSV;
    raw_svector_ostream ObjStream;
    legacy::PassManager PM;
    MCContext *Ctx;
    RTDyldMemoryManager *MemMgr;
    ObjLayerT ObjectLayer;
    CompileLayerT CompileLayer;
    SymbolTableT GlobalSymbolTable;
    SymbolTableT LocalSymbolTable;
};
extern JuliaOJIT *jl_ExecutionEngine;
#else
extern ExecutionEngine *jl_ExecutionEngine;
#endif
#if JL_LLVM_VERSION >= 30900
JL_DLLEXPORT extern LLVMContext jl_LLVMContext;
#else
JL_DLLEXPORT extern LLVMContext &jl_LLVMContext;
#endif

Pass *createLowerPTLSPass(bool imaging_mode);
Pass *createLowerGCFramePass();
// Whether the Function is an llvm or julia intrinsic.
static inline bool isIntrinsicFunction(Function *F)
{
    return F->isIntrinsic() || F->getName().startswith("julia.");
}
