// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#include "rustllvm.h"
#include "llvm/Object/Archive.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/DiagnosticPrinter.h"

#include "llvm/IR/CallSite.h"

//===----------------------------------------------------------------------===
//
// This file defines alternate interfaces to core functions that are more
// readily callable by Rust's FFI.
//
//===----------------------------------------------------------------------===

using namespace llvm;
using namespace llvm::sys;
using namespace llvm::object;

static char *LastError;

extern "C" LLVMMemoryBufferRef
LLVMRustCreateMemoryBufferWithContentsOfFile(const char *Path) {
  ErrorOr<std::unique_ptr<MemoryBuffer>> buf_or = MemoryBuffer::getFile(Path,
                                                                        -1,
                                                                        false);
  if (!buf_or) {
      LLVMRustSetLastError(buf_or.getError().message().c_str());
      return nullptr;
  }
  return wrap(buf_or.get().release());
}

extern "C" char *LLVMRustGetLastError(void) {
  char *ret = LastError;
  LastError = NULL;
  return ret;
}

void LLVMRustSetLastError(const char *err) {
  free((void*) LastError);
  LastError = strdup(err);
}

extern "C" void
LLVMRustSetNormalizedTarget(LLVMModuleRef M, const char *triple) {
    unwrap(M)->setTargetTriple(Triple::normalize(triple));
}

extern "C" LLVMValueRef LLVMRustConstSmallInt(LLVMTypeRef IntTy, unsigned N,
                                              LLVMBool SignExtend) {
  return LLVMConstInt(IntTy, (unsigned long long)N, SignExtend);
}

extern "C" LLVMValueRef LLVMRustConstInt(LLVMTypeRef IntTy,
           unsigned N_hi,
           unsigned N_lo,
           LLVMBool SignExtend) {
  unsigned long long N = N_hi;
  N <<= 32;
  N |= N_lo;
  return LLVMConstInt(IntTy, N, SignExtend);
}

extern "C" void LLVMRustPrintPassTimings() {
  raw_fd_ostream OS (2, false); // stderr.
  TimerGroup::printAll(OS);
}

extern "C" LLVMValueRef LLVMGetNamedValue(LLVMModuleRef M,
                                          const char* Name) {
    return wrap(unwrap(M)->getNamedValue(Name));
}

extern "C" LLVMValueRef LLVMGetOrInsertFunction(LLVMModuleRef M,
                                                const char* Name,
                                                LLVMTypeRef FunctionTy) {
  return wrap(unwrap(M)->getOrInsertFunction(Name,
                                             unwrap<FunctionType>(FunctionTy)));
}

extern "C" LLVMValueRef LLVMGetOrInsertGlobal(LLVMModuleRef M,
                                              const char* Name,
                                              LLVMTypeRef Ty) {
  return wrap(unwrap(M)->getOrInsertGlobal(Name, unwrap(Ty)));
}

extern "C" LLVMTypeRef LLVMMetadataTypeInContext(LLVMContextRef C) {
  return wrap(Type::getMetadataTy(*unwrap(C)));
}

extern "C" void LLVMAddCallSiteAttribute(LLVMValueRef Instr, unsigned index, uint64_t Val) {
  CallSite Call = CallSite(unwrap<Instruction>(Instr));
  AttrBuilder B;
  B.addRawValue(Val);
  Call.setAttributes(
    Call.getAttributes().addAttributes(Call->getContext(), index,
                                       AttributeSet::get(Call->getContext(),
                                                         index, B)));
}


extern "C" void LLVMAddDereferenceableCallSiteAttr(LLVMValueRef Instr, unsigned idx, uint64_t b) {
  CallSite Call = CallSite(unwrap<Instruction>(Instr));
  AttrBuilder B;
  B.addDereferenceableAttr(b);
  Call.setAttributes(
    Call.getAttributes().addAttributes(Call->getContext(), idx,
                                       AttributeSet::get(Call->getContext(),
                                                         idx, B)));
}

extern "C" void LLVMAddFunctionAttribute(LLVMValueRef Fn, unsigned index, uint64_t Val) {
  Function *A = unwrap<Function>(Fn);
  AttrBuilder B;
  B.addRawValue(Val);
  A->addAttributes(index, AttributeSet::get(A->getContext(), index, B));
}

extern "C" void LLVMAddDereferenceableAttr(LLVMValueRef Fn, unsigned index, uint64_t bytes) {
  Function *A = unwrap<Function>(Fn);
  AttrBuilder B;
  B.addDereferenceableAttr(bytes);
  A->addAttributes(index, AttributeSet::get(A->getContext(), index, B));
}

extern "C" void LLVMAddFunctionAttrString(LLVMValueRef Fn, unsigned index, const char *Name) {
  Function *F = unwrap<Function>(Fn);
  AttrBuilder B;
  B.addAttribute(Name);
  F->addAttributes(index, AttributeSet::get(F->getContext(), index, B));
}

extern "C" void LLVMRemoveFunctionAttrString(LLVMValueRef fn, unsigned index, const char *Name) {
  Function *f = unwrap<Function>(fn);
  LLVMContext &C = f->getContext();
  AttrBuilder B;
  B.addAttribute(Name);
  AttributeSet to_remove = AttributeSet::get(C, index, B);

  AttributeSet attrs = f->getAttributes();
  f->setAttributes(attrs.removeAttributes(f->getContext(),
                                          index,
                                          to_remove));
}

extern "C" LLVMValueRef LLVMBuildAtomicLoad(LLVMBuilderRef B,
                                            LLVMValueRef source,
                                            const char* Name,
                                            AtomicOrdering order,
                                            unsigned alignment) {
    LoadInst* li = new LoadInst(unwrap(source),0);
    li->setVolatile(true);
    li->setAtomic(order);
    li->setAlignment(alignment);
    return wrap(unwrap(B)->Insert(li, Name));
}

extern "C" LLVMValueRef LLVMBuildAtomicStore(LLVMBuilderRef B,
                                             LLVMValueRef val,
                                             LLVMValueRef target,
                                             AtomicOrdering order,
                                             unsigned alignment) {
    StoreInst* si = new StoreInst(unwrap(val),unwrap(target));
    si->setVolatile(true);
    si->setAtomic(order);
    si->setAlignment(alignment);
    return wrap(unwrap(B)->Insert(si));
}

extern "C" LLVMValueRef LLVMBuildAtomicCmpXchg(LLVMBuilderRef B,
                                               LLVMValueRef target,
                                               LLVMValueRef old,
                                               LLVMValueRef source,
                                               AtomicOrdering order,
                                               AtomicOrdering failure_order) {
    return wrap(unwrap(B)->CreateAtomicCmpXchg(unwrap(target), unwrap(old),
                                               unwrap(source), order,
                                               failure_order
                                               ));
}
extern "C" LLVMValueRef LLVMBuildAtomicFence(LLVMBuilderRef B, AtomicOrdering order) {
    return wrap(unwrap(B)->CreateFence(order));
}

extern "C" void LLVMSetDebug(int Enabled) {
#ifndef NDEBUG
  DebugFlag = Enabled;
#endif
}

extern "C" LLVMValueRef LLVMInlineAsm(LLVMTypeRef Ty,
                                      char *AsmString,
                                      char *Constraints,
                                      LLVMBool HasSideEffects,
                                      LLVMBool IsAlignStack,
                                      unsigned Dialect) {
    return wrap(InlineAsm::get(unwrap<FunctionType>(Ty), AsmString,
                               Constraints, HasSideEffects,
                               IsAlignStack, (InlineAsm::AsmDialect) Dialect));
}

typedef DIBuilder* DIBuilderRef;

#if LLVM_VERSION_MINOR >= 6
typedef struct LLVMOpaqueMetadata *LLVMMetadataRef;

namespace llvm {
DEFINE_ISA_CONVERSION_FUNCTIONS(Metadata, LLVMMetadataRef)

inline Metadata **unwrap(LLVMMetadataRef *Vals) {
  return reinterpret_cast<Metadata**>(Vals);
}
}
#else
typedef LLVMValueRef LLVMMetadataRef;
#endif

template<typename DIT>
DIT unwrapDI(LLVMMetadataRef ref) {
    return DIT(ref ? unwrap<MDNode>(ref) : NULL);
}

extern "C" const uint32_t LLVMRustDebugMetadataVersion = DEBUG_METADATA_VERSION;

extern "C" void LLVMRustAddModuleFlag(LLVMModuleRef M,
                                      const char *name,
                                      uint32_t value) {
    unwrap(M)->addModuleFlag(Module::Warning, name, value);
}

extern "C" DIBuilderRef LLVMDIBuilderCreate(LLVMModuleRef M) {
    return new DIBuilder(*unwrap(M));
}

extern "C" void LLVMDIBuilderDispose(DIBuilderRef Builder) {
    delete Builder;
}

extern "C" void LLVMDIBuilderFinalize(DIBuilderRef Builder) {
    Builder->finalize();
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateCompileUnit(
    DIBuilderRef Builder,
    unsigned Lang,
    const char* File,
    const char* Dir,
    const char* Producer,
    bool isOptimized,
    const char* Flags,
    unsigned RuntimeVer,
    const char* SplitName) {
    return wrap(Builder->createCompileUnit(Lang,
                                           File,
                                           Dir,
                                           Producer,
                                           isOptimized,
                                           Flags,
                                           RuntimeVer,
                                           SplitName));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateFile(
    DIBuilderRef Builder,
    const char* Filename,
    const char* Directory) {
    return wrap(Builder->createFile(Filename, Directory));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateSubroutineType(
    DIBuilderRef Builder,
    LLVMMetadataRef File,
    LLVMMetadataRef ParameterTypes) {
    return wrap(Builder->createSubroutineType(
        unwrapDI<DIFile>(File),
#if LLVM_VERSION_MINOR >= 6
        unwrapDI<DITypeArray>(ParameterTypes)));
#else
        unwrapDI<DIArray>(ParameterTypes)));
#endif
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateFunction(
    DIBuilderRef Builder,
    LLVMMetadataRef Scope,
    const char* Name,
    const char* LinkageName,
    LLVMMetadataRef File,
    unsigned LineNo,
    LLVMMetadataRef Ty,
    bool isLocalToUnit,
    bool isDefinition,
    unsigned ScopeLine,
    unsigned Flags,
    bool isOptimized,
    LLVMValueRef Fn,
    LLVMMetadataRef TParam,
    LLVMMetadataRef Decl) {
    return wrap(Builder->createFunction(
        unwrapDI<DIScope>(Scope), Name, LinkageName,
        unwrapDI<DIFile>(File), LineNo,
        unwrapDI<DICompositeType>(Ty), isLocalToUnit, isDefinition, ScopeLine,
        Flags, isOptimized,
        unwrap<Function>(Fn),
        unwrapDI<MDNode*>(TParam),
        unwrapDI<MDNode*>(Decl)));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateBasicType(
    DIBuilderRef Builder,
    const char* Name,
    uint64_t SizeInBits,
    uint64_t AlignInBits,
    unsigned Encoding) {
    return wrap(Builder->createBasicType(
        Name, SizeInBits,
        AlignInBits, Encoding));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreatePointerType(
    DIBuilderRef Builder,
    LLVMMetadataRef PointeeTy,
    uint64_t SizeInBits,
    uint64_t AlignInBits,
    const char* Name) {
    return wrap(Builder->createPointerType(
        unwrapDI<DIType>(PointeeTy), SizeInBits, AlignInBits, Name));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateStructType(
    DIBuilderRef Builder,
    LLVMMetadataRef Scope,
    const char* Name,
    LLVMMetadataRef File,
    unsigned LineNumber,
    uint64_t SizeInBits,
    uint64_t AlignInBits,
    unsigned Flags,
    LLVMMetadataRef DerivedFrom,
    LLVMMetadataRef Elements,
    unsigned RunTimeLang,
    LLVMMetadataRef VTableHolder,
    const char *UniqueId) {
    return wrap(Builder->createStructType(
        unwrapDI<DIDescriptor>(Scope),
        Name,
        unwrapDI<DIFile>(File),
        LineNumber,
        SizeInBits,
        AlignInBits,
        Flags,
        unwrapDI<DIType>(DerivedFrom),
        unwrapDI<DIArray>(Elements),
        RunTimeLang,
        unwrapDI<DIType>(VTableHolder),
        UniqueId
        ));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateMemberType(
    DIBuilderRef Builder,
    LLVMMetadataRef Scope,
    const char* Name,
    LLVMMetadataRef File,
    unsigned LineNo,
    uint64_t SizeInBits,
    uint64_t AlignInBits,
    uint64_t OffsetInBits,
    unsigned Flags,
    LLVMMetadataRef Ty) {
    return wrap(Builder->createMemberType(
        unwrapDI<DIDescriptor>(Scope), Name,
        unwrapDI<DIFile>(File), LineNo,
        SizeInBits, AlignInBits, OffsetInBits, Flags,
        unwrapDI<DIType>(Ty)));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateLexicalBlock(
    DIBuilderRef Builder,
    LLVMMetadataRef Scope,
    LLVMMetadataRef File,
    unsigned Line,
    unsigned Col) {
    return wrap(Builder->createLexicalBlock(
        unwrapDI<DIDescriptor>(Scope),
        unwrapDI<DIFile>(File), Line, Col
#if LLVM_VERSION_MINOR == 5
        , 0
#endif
        ));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateStaticVariable(
    DIBuilderRef Builder,
    LLVMMetadataRef Context,
    const char* Name,
    const char* LinkageName,
    LLVMMetadataRef File,
    unsigned LineNo,
    LLVMMetadataRef Ty,
    bool isLocalToUnit,
    LLVMValueRef Val,
    LLVMMetadataRef Decl = NULL) {
#if LLVM_VERSION_MINOR == 6
    return wrap(Builder->createGlobalVariable(unwrapDI<DIDescriptor>(Context),
#else
    return wrap(Builder->createStaticVariable(unwrapDI<DIDescriptor>(Context),
#endif
        Name,
        LinkageName,
        unwrapDI<DIFile>(File),
        LineNo,
        unwrapDI<DIType>(Ty),
        isLocalToUnit,
        cast<Constant>(unwrap(Val)),
        unwrapDI<MDNode*>(Decl)));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateVariable(
    DIBuilderRef Builder,
    unsigned Tag,
    LLVMMetadataRef Scope,
    const char* Name,
    LLVMMetadataRef File,
    unsigned LineNo,
    LLVMMetadataRef Ty,
    bool AlwaysPreserve,
    unsigned Flags,
    int64_t* AddrOps,
    unsigned AddrOpsCount,
    unsigned ArgNo) {
#if LLVM_VERSION_MINOR < 6
    if (AddrOpsCount > 0) {
        SmallVector<llvm::Value *, 16> addr_ops;
        llvm::Type *Int64Ty = Type::getInt64Ty(unwrap<MDNode>(Scope)->getContext());
        for (unsigned i = 0; i < AddrOpsCount; ++i)
            addr_ops.push_back(ConstantInt::get(Int64Ty, AddrOps[i]));

        return wrap(Builder->createComplexVariable(
            Tag,
            unwrapDI<DIDescriptor>(Scope),
            Name,
            unwrapDI<DIFile>(File),
            LineNo,
            unwrapDI<DIType>(Ty),
            addr_ops,
            ArgNo
        ));
    }
#endif
    return wrap(Builder->createLocalVariable(Tag,
        unwrapDI<DIDescriptor>(Scope), Name,
        unwrapDI<DIFile>(File),
        LineNo,
        unwrapDI<DIType>(Ty), AlwaysPreserve, Flags, ArgNo));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateArrayType(
    DIBuilderRef Builder,
    uint64_t Size,
    uint64_t AlignInBits,
    LLVMMetadataRef Ty,
    LLVMMetadataRef Subscripts) {
    return wrap(Builder->createArrayType(Size, AlignInBits,
        unwrapDI<DIType>(Ty),
        unwrapDI<DIArray>(Subscripts)));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateVectorType(
    DIBuilderRef Builder,
    uint64_t Size,
    uint64_t AlignInBits,
    LLVMMetadataRef Ty,
    LLVMMetadataRef Subscripts) {
    return wrap(Builder->createVectorType(Size, AlignInBits,
        unwrapDI<DIType>(Ty),
        unwrapDI<DIArray>(Subscripts)));
}

extern "C" LLVMMetadataRef LLVMDIBuilderGetOrCreateSubrange(
    DIBuilderRef Builder,
    int64_t Lo,
    int64_t Count) {
    return wrap(Builder->getOrCreateSubrange(Lo, Count));
}

extern "C" LLVMMetadataRef LLVMDIBuilderGetOrCreateArray(
    DIBuilderRef Builder,
    LLVMMetadataRef* Ptr,
    unsigned Count) {
    return wrap(Builder->getOrCreateArray(
#if LLVM_VERSION_MINOR >= 6
        ArrayRef<Metadata*>(unwrap(Ptr), Count)));
#else
        ArrayRef<Value*>(reinterpret_cast<Value**>(Ptr), Count)));
#endif
}

extern "C" LLVMValueRef LLVMDIBuilderInsertDeclareAtEnd(
    DIBuilderRef Builder,
    LLVMValueRef Val,
    LLVMMetadataRef VarInfo,
    int64_t* AddrOps,
    unsigned AddrOpsCount,
    LLVMBasicBlockRef InsertAtEnd) {
#if LLVM_VERSION_MINOR >= 6
    DIExpression Expr;
    if (AddrOpsCount == 0) {
      Expr = Builder->createExpression();
    } else {
      llvm::ArrayRef<int64_t> addr_ops(AddrOps, AddrOpsCount);
      Expr = Builder->createExpression(addr_ops);
    }
#endif
    return wrap(Builder->insertDeclare(
        unwrap(Val),
        unwrapDI<DIVariable>(VarInfo),
#if LLVM_VERSION_MINOR >= 6
        Expr,
#endif
        unwrap(InsertAtEnd)));
}

extern "C" LLVMValueRef LLVMDIBuilderInsertDeclareBefore(
    DIBuilderRef Builder,
    LLVMValueRef Val,
    LLVMMetadataRef VarInfo,
    int64_t* AddrOps,
    unsigned AddrOpsCount,
    LLVMValueRef InsertBefore) {
#if LLVM_VERSION_MINOR >= 6
    DIExpression Expr;
    if (AddrOpsCount == 0) {
      Expr = Builder->createExpression();
    } else {
      llvm::ArrayRef<int64_t> addr_ops(AddrOps, AddrOpsCount);
      Expr = Builder->createExpression(addr_ops);
    }
#endif
    return wrap(Builder->insertDeclare(
        unwrap(Val),
        unwrapDI<DIVariable>(VarInfo),
#if LLVM_VERSION_MINOR >= 6
        Expr,
#endif
        unwrap<Instruction>(InsertBefore)));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateEnumerator(
    DIBuilderRef Builder,
    const char* Name,
    uint64_t Val)
{
    return wrap(Builder->createEnumerator(Name, Val));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateEnumerationType(
    DIBuilderRef Builder,
    LLVMMetadataRef Scope,
    const char* Name,
    LLVMMetadataRef File,
    unsigned LineNumber,
    uint64_t SizeInBits,
    uint64_t AlignInBits,
    LLVMMetadataRef Elements,
    LLVMMetadataRef ClassType)
{
    return wrap(Builder->createEnumerationType(
        unwrapDI<DIDescriptor>(Scope),
        Name,
        unwrapDI<DIFile>(File),
        LineNumber,
        SizeInBits,
        AlignInBits,
        unwrapDI<DIArray>(Elements),
        unwrapDI<DIType>(ClassType)));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateUnionType(
    DIBuilderRef Builder,
    LLVMMetadataRef Scope,
    const char* Name,
    LLVMMetadataRef File,
    unsigned LineNumber,
    uint64_t SizeInBits,
    uint64_t AlignInBits,
    unsigned Flags,
    LLVMMetadataRef Elements,
    unsigned RunTimeLang,
    const char* UniqueId)
{
    return wrap(Builder->createUnionType(
        unwrapDI<DIDescriptor>(Scope),
        Name,
        unwrapDI<DIFile>(File),
        LineNumber,
        SizeInBits,
        AlignInBits,
        Flags,
        unwrapDI<DIArray>(Elements),
        RunTimeLang,
        UniqueId
        ));
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateTemplateTypeParameter(
    DIBuilderRef Builder,
    LLVMMetadataRef Scope,
    const char* Name,
    LLVMMetadataRef Ty,
    LLVMMetadataRef File,
    unsigned LineNo,
    unsigned ColumnNo)
{
    return wrap(Builder->createTemplateTypeParameter(
      unwrapDI<DIDescriptor>(Scope),
      Name,
      unwrapDI<DIType>(Ty),
      unwrapDI<MDNode*>(File),
      LineNo,
      ColumnNo));
}

extern "C" int64_t LLVMDIBuilderCreateOpDeref()
{
    return dwarf::DW_OP_deref;
}

extern "C" int64_t LLVMDIBuilderCreateOpPlus()
{
    return dwarf::DW_OP_plus;
}

extern "C" LLVMMetadataRef LLVMDIBuilderCreateNameSpace(
    DIBuilderRef Builder,
    LLVMMetadataRef Scope,
    const char* Name,
    LLVMMetadataRef File,
    unsigned LineNo)
{
    return wrap(Builder->createNameSpace(
        unwrapDI<DIDescriptor>(Scope),
        Name,
        unwrapDI<DIFile>(File),
        LineNo));
}

extern "C" void LLVMDICompositeTypeSetTypeArray(
    DIBuilderRef Builder,
    LLVMMetadataRef CompositeType,
    LLVMMetadataRef TypeArray)
{
#if LLVM_VERSION_MINOR >= 6
    DICompositeType tmp = unwrapDI<DICompositeType>(CompositeType);
    Builder->replaceArrays(tmp, unwrapDI<DIArray>(TypeArray));
#else
    unwrapDI<DICompositeType>(CompositeType).setTypeArray(unwrapDI<DIArray>(TypeArray));
#endif
}

extern "C" LLVMValueRef LLVMDIBuilderCreateDebugLocation(
  LLVMContextRef Context,
  unsigned Line,
  unsigned Column,
  LLVMMetadataRef Scope,
  LLVMMetadataRef InlinedAt) {

    LLVMContext& context = *unwrap(Context);

    DebugLoc debug_loc = DebugLoc::get(Line,
                                       Column,
                                       unwrapDI<MDNode*>(Scope),
                                       unwrapDI<MDNode*>(InlinedAt));

#if LLVM_VERSION_MINOR >= 6
    return wrap(MetadataAsValue::get(context, debug_loc.getAsMDNode(context)));
#else
    return wrap(debug_loc.getAsMDNode(context));
#endif
}

extern "C" void LLVMWriteTypeToString(LLVMTypeRef Type, RustStringRef str) {
    raw_rust_string_ostream os(str);
    unwrap<llvm::Type>(Type)->print(os);
}

extern "C" void LLVMWriteValueToString(LLVMValueRef Value, RustStringRef str) {
    raw_rust_string_ostream os(str);
    os << "(";
    unwrap<llvm::Value>(Value)->getType()->print(os);
    os << ":";
    unwrap<llvm::Value>(Value)->print(os);
    os << ")";
}

extern "C" bool
LLVMRustLinkInExternalBitcode(LLVMModuleRef dst, char *bc, size_t len) {
    Module *Dst = unwrap(dst);
#if LLVM_VERSION_MINOR == 5
    MemoryBuffer* buf = MemoryBuffer::getMemBufferCopy(StringRef(bc, len));
    ErrorOr<Module *> Src = llvm::getLazyBitcodeModule(buf, Dst->getContext());
#else
    std::unique_ptr<MemoryBuffer> buf = MemoryBuffer::getMemBufferCopy(StringRef(bc, len));
    ErrorOr<Module *> Src = llvm::getLazyBitcodeModule(std::move(buf), Dst->getContext());
#endif
    if (!Src) {
        LLVMRustSetLastError(Src.getError().message().c_str());
#if LLVM_VERSION_MINOR == 5
        delete buf;
#endif
        return false;
    }

    std::string Err;

#if LLVM_VERSION_MINOR >= 6
    raw_string_ostream Stream(Err);
    DiagnosticPrinterRawOStream DP(Stream);
    if (Linker::LinkModules(Dst, *Src, [&](const DiagnosticInfo &DI) { DI.print(DP); })) {
#else
    if (Linker::LinkModules(Dst, *Src, Linker::DestroySource, &Err)) {
#endif
        LLVMRustSetLastError(Err.c_str());
        return false;
    }
    return true;
}

extern "C" void*
LLVMRustOpenArchive(char *path) {
    ErrorOr<std::unique_ptr<MemoryBuffer>> buf_or = MemoryBuffer::getFile(path,
                                                                          -1,
                                                                          false);
    if (!buf_or) {
        LLVMRustSetLastError(buf_or.getError().message().c_str());
        return nullptr;
    }

#if LLVM_VERSION_MINOR >= 6
    ErrorOr<std::unique_ptr<Archive>> archive_or =
        Archive::create(buf_or.get()->getMemBufferRef());

    if (!archive_or) {
        LLVMRustSetLastError(archive_or.getError().message().c_str());
        return nullptr;
    }

    OwningBinary<Archive> *ret = new OwningBinary<Archive>(
            std::move(archive_or.get()), std::move(buf_or.get()));
#else
    std::error_code err;
    Archive *ret = new Archive(std::move(buf_or.get()), err);
    if (err) {
        LLVMRustSetLastError(err.message().c_str());
        return nullptr;
    }
#endif

    return ret;
}

extern "C" const char*
#if LLVM_VERSION_MINOR >= 6
LLVMRustArchiveReadSection(OwningBinary<Archive> *ob, char *name, size_t *size) {

    Archive *ar = ob->getBinary();
#else
LLVMRustArchiveReadSection(Archive *ar, char *name, size_t *size) {
#endif

    Archive::child_iterator child = ar->child_begin(),
                              end = ar->child_end();
    for (; child != end; ++child) {
        ErrorOr<StringRef> name_or_err = child->getName();
        if (name_or_err.getError()) continue;
        StringRef sect_name = name_or_err.get();
        if (sect_name.trim(" ") == name) {
            StringRef buf = child->getBuffer();
            *size = buf.size();
            return buf.data();
        }
    }
    return NULL;
}

extern "C" void
#if LLVM_VERSION_MINOR >= 6
LLVMRustDestroyArchive(OwningBinary<Archive> *ar) {
#else
LLVMRustDestroyArchive(Archive *ar) {
#endif
    delete ar;
}

extern "C" void
LLVMRustSetDLLExportStorageClass(LLVMValueRef Value) {
    GlobalValue *V = unwrap<GlobalValue>(Value);
    V->setDLLStorageClass(GlobalValue::DLLExportStorageClass);
}

extern "C" int
LLVMVersionMinor() {
    return LLVM_VERSION_MINOR;
}

extern "C" int
LLVMVersionMajor() {
    return LLVM_VERSION_MAJOR;
}

// Note that the two following functions look quite similar to the
// LLVMGetSectionName function. Sadly, it appears that this function only
// returns a char* pointer, which isn't guaranteed to be null-terminated. The
// function provided by LLVM doesn't return the length, so we've created our own
// function which returns the length as well as the data pointer.
//
// For an example of this not returning a null terminated string, see
// lib/Object/COFFObjectFile.cpp in the getSectionName function. One of the
// branches explicitly creates a StringRef without a null terminator, and then
// that's returned.

inline section_iterator *unwrap(LLVMSectionIteratorRef SI) {
    return reinterpret_cast<section_iterator*>(SI);
}

extern "C" int
LLVMRustGetSectionName(LLVMSectionIteratorRef SI, const char **ptr) {
    StringRef ret;
    if (std::error_code ec = (*unwrap(SI))->getName(ret))
      report_fatal_error(ec.message());
    *ptr = ret.data();
    return ret.size();
}

// LLVMArrayType function does not support 64-bit ElementCount
extern "C" LLVMTypeRef
LLVMRustArrayType(LLVMTypeRef ElementType, uint64_t ElementCount) {
    return wrap(ArrayType::get(unwrap(ElementType), ElementCount));
}

DEFINE_SIMPLE_CONVERSION_FUNCTIONS(Twine, LLVMTwineRef)
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(DebugLoc, LLVMDebugLocRef)

extern "C" void
LLVMWriteTwineToString(LLVMTwineRef T, RustStringRef str) {
    raw_rust_string_ostream os(str);
    unwrap(T)->print(os);
}

extern "C" void
LLVMUnpackOptimizationDiagnostic(
    LLVMDiagnosticInfoRef di,
    const char **pass_name_out,
    LLVMValueRef *function_out,
    LLVMDebugLocRef *debugloc_out,
    LLVMTwineRef *message_out)
{
    // Undefined to call this not on an optimization diagnostic!
    llvm::DiagnosticInfoOptimizationBase *opt
        = static_cast<llvm::DiagnosticInfoOptimizationBase*>(unwrap(di));

    *pass_name_out = opt->getPassName();
    *function_out = wrap(&opt->getFunction());
    *debugloc_out = wrap(&opt->getDebugLoc());
    *message_out = wrap(&opt->getMsg());
}

extern "C" void
LLVMUnpackInlineAsmDiagnostic(
    LLVMDiagnosticInfoRef di,
    unsigned *cookie_out,
    LLVMTwineRef *message_out,
    LLVMValueRef *instruction_out)
{
    // Undefined to call this not on an inline assembly diagnostic!
    llvm::DiagnosticInfoInlineAsm *ia
        = static_cast<llvm::DiagnosticInfoInlineAsm*>(unwrap(di));

    *cookie_out = ia->getLocCookie();
    *message_out = wrap(&ia->getMsgStr());
    *instruction_out = wrap(ia->getInstruction());
}

extern "C" void LLVMWriteDiagnosticInfoToString(LLVMDiagnosticInfoRef di, RustStringRef str) {
    raw_rust_string_ostream os(str);
    DiagnosticPrinterRawOStream dp(os);
    unwrap(di)->print(dp);
}

extern "C" int LLVMGetDiagInfoKind(LLVMDiagnosticInfoRef di) {
    return unwrap(di)->getKind();
}

extern "C" void LLVMWriteDebugLocToString(
    LLVMContextRef C,
    LLVMDebugLocRef dl,
    RustStringRef str)
{
    raw_rust_string_ostream os(str);
    unwrap(dl)->print(*unwrap(C), os);
}

DEFINE_SIMPLE_CONVERSION_FUNCTIONS(SMDiagnostic, LLVMSMDiagnosticRef)

extern "C" void LLVMSetInlineAsmDiagnosticHandler(
    LLVMContextRef C,
    LLVMContext::InlineAsmDiagHandlerTy H,
    void *CX)
{
    unwrap(C)->setInlineAsmDiagnosticHandler(H, CX);
}

extern "C" void LLVMWriteSMDiagnosticToString(LLVMSMDiagnosticRef d, RustStringRef str) {
    raw_rust_string_ostream os(str);
    unwrap(d)->print("", os);
}
