//===--------------- OrcCAPITest.cpp - Unit tests Orc C API ---------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "OrcTestCommon.h"
#include "llvm/ExecutionEngine/Orc/CompileUtils.h"
#include "llvm-c/Core.h"
#include "llvm-c/OrcBindings.h"
#include "llvm-c/Target.h"
#include "llvm-c/TargetMachine.h"
#include "gtest/gtest.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

namespace llvm {

DEFINE_SIMPLE_CONVERSION_FUNCTIONS(TargetMachine, LLVMTargetMachineRef)

class OrcCAPIExecutionTest : public testing::Test, public OrcExecutionTest {
protected:
  std::unique_ptr<Module> createTestModule(const Triple &TT) {
    ModuleBuilder MB(Context, TT.str(), "");
    Function *TestFunc = MB.createFunctionDecl<int()>("testFunc");
    Function *Main = MB.createFunctionDecl<int(int, char*[])>("main");

    Main->getBasicBlockList().push_back(BasicBlock::Create(Context));
    IRBuilder<> B(&Main->back());
    Value* Result = B.CreateCall(TestFunc);
    B.CreateRet(Result);

    return MB.takeModule();
  }

  std::unique_ptr<MemoryBuffer> createTestObject() {
    orc::SimpleCompiler IRCompiler(*TM);
    auto M = createTestModule(TM->getTargetTriple());
    M->setDataLayout(TM->createDataLayout());
    return IRCompiler(*M);
  }

  typedef int (*MainFnTy)();

  static int myTestFuncImpl() {
    return 42;
  }

  static char *testFuncName;

  static uint64_t myResolver(const char *Name, void *Ctx) {
    if (!strncmp(Name, testFuncName, 8))
      return (uint64_t)&myTestFuncImpl;
    return 0;
  }

  struct CompileContext {
    CompileContext() : Compiled(false) { }

    OrcCAPIExecutionTest* APIExecTest;
    std::unique_ptr<Module> M;
    LLVMOrcModuleHandle H;
    bool Compiled;
  };

  static LLVMOrcTargetAddress myCompileCallback(LLVMOrcJITStackRef JITStack,
                                                void *Ctx) {
    CompileContext *CCtx = static_cast<CompileContext*>(Ctx);
    auto *ET = CCtx->APIExecTest;
    CCtx->M = ET->createTestModule(ET->TM->getTargetTriple());
    LLVMOrcAddEagerlyCompiledIR(JITStack, &CCtx->H, wrap(CCtx->M.release()),
                                myResolver, nullptr);
    CCtx->Compiled = true;
    LLVMOrcTargetAddress MainAddr;
    LLVMOrcGetSymbolAddress(JITStack, &MainAddr, "main");
    LLVMOrcSetIndirectStubPointer(JITStack, "foo", MainAddr);
    return MainAddr;
  }
};

char *OrcCAPIExecutionTest::testFuncName = nullptr;

TEST_F(OrcCAPIExecutionTest, TestEagerIRCompilation) {
  if (!SupportsJIT)
    return;

  LLVMOrcJITStackRef JIT =
    LLVMOrcCreateInstance(wrap(TM.get()));

  std::unique_ptr<Module> M = createTestModule(TM->getTargetTriple());

  LLVMOrcGetMangledSymbol(JIT, &testFuncName, "testFunc");

  LLVMOrcModuleHandle H;
  LLVMOrcAddEagerlyCompiledIR(JIT, &H, wrap(M.release()), myResolver, nullptr);

  // get symbol address searching the entire stack
  {
    LLVMOrcTargetAddress MainAddr;
    LLVMOrcGetSymbolAddress(JIT, &MainAddr, "main");
    MainFnTy MainFn = (MainFnTy)MainAddr;
    int Result = MainFn();
    EXPECT_EQ(Result, 42)
      << "Eagerly JIT'd code did not return expected result";
  }

  // and then just searching a single handle
  {
    LLVMOrcTargetAddress MainAddr;
    LLVMOrcGetSymbolAddressIn(JIT, &MainAddr, H, "main");
    MainFnTy MainFn = (MainFnTy)MainAddr;
    int Result = MainFn();
    EXPECT_EQ(Result, 42)
      << "Eagerly JIT'd code did not return expected result";
  }

  LLVMOrcRemoveModule(JIT, H);

  LLVMOrcDisposeMangledSymbol(testFuncName);
  LLVMOrcDisposeInstance(JIT);
}

TEST_F(OrcCAPIExecutionTest, TestLazyIRCompilation) {
  if (!SupportsIndirection)
    return;

  LLVMOrcJITStackRef JIT =
    LLVMOrcCreateInstance(wrap(TM.get()));

  std::unique_ptr<Module> M = createTestModule(TM->getTargetTriple());

  LLVMOrcGetMangledSymbol(JIT, &testFuncName, "testFunc");

  LLVMOrcModuleHandle H;
  LLVMOrcAddLazilyCompiledIR(JIT, &H, wrap(M.release()), myResolver, nullptr);
  LLVMOrcTargetAddress MainAddr;
  LLVMOrcGetSymbolAddress(JIT, &MainAddr, "main");
  MainFnTy MainFn = (MainFnTy)MainAddr;
  int Result = MainFn();
  EXPECT_EQ(Result, 42)
    << "Lazily JIT'd code did not return expected result";

  LLVMOrcRemoveModule(JIT, H);

  LLVMOrcDisposeMangledSymbol(testFuncName);
  LLVMOrcDisposeInstance(JIT);
}

TEST_F(OrcCAPIExecutionTest, TestAddObjectFile) {
  if (!SupportsJIT)
    return;

  auto ObjBuffer = createTestObject();

  LLVMOrcJITStackRef JIT =
    LLVMOrcCreateInstance(wrap(TM.get()));
  LLVMOrcGetMangledSymbol(JIT, &testFuncName, "testFunc");

  LLVMOrcModuleHandle H;
  LLVMOrcAddObjectFile(JIT, &H, wrap(ObjBuffer.release()), myResolver, nullptr);
  LLVMOrcTargetAddress MainAddr;
  LLVMOrcGetSymbolAddress(JIT, &MainAddr, "main");
  MainFnTy MainFn = (MainFnTy)MainAddr;
  int Result = MainFn();
  EXPECT_EQ(Result, 42)
    << "Lazily JIT'd code did not return expected result";

  LLVMOrcRemoveModule(JIT, H);

  LLVMOrcDisposeMangledSymbol(testFuncName);
  LLVMOrcDisposeInstance(JIT);
}

TEST_F(OrcCAPIExecutionTest, TestDirectCallbacksAPI) {
  if (!SupportsIndirection)
    return;

  LLVMOrcJITStackRef JIT =
    LLVMOrcCreateInstance(wrap(TM.get()));

  LLVMOrcGetMangledSymbol(JIT, &testFuncName, "testFunc");

  CompileContext C;
  C.APIExecTest = this;
  LLVMOrcTargetAddress CCAddr;
  LLVMOrcCreateLazyCompileCallback(JIT, &CCAddr, myCompileCallback, &C);
  LLVMOrcCreateIndirectStub(JIT, "foo", CCAddr);
  LLVMOrcTargetAddress MainAddr;
  LLVMOrcGetSymbolAddress(JIT, &MainAddr, "foo");
  MainFnTy FooFn = (MainFnTy)MainAddr;
  int Result = FooFn();
  EXPECT_TRUE(C.Compiled)
    << "Function wasn't lazily compiled";
  EXPECT_EQ(Result, 42)
    << "Direct-callback JIT'd code did not return expected result";

  C.Compiled = false;
  FooFn();
  EXPECT_FALSE(C.Compiled)
    << "Direct-callback JIT'd code was JIT'd twice";

  LLVMOrcRemoveModule(JIT, C.H);

  LLVMOrcDisposeMangledSymbol(testFuncName);
  LLVMOrcDisposeInstance(JIT);
}

} // namespace llvm
