# -*- Mode: Cython -*-
# distutils: language = c++

# Use LLVM's JIT engine to load up some bitcode and execute it.

# lotsa clues from here:
# http://stackoverflow.com/questions/1838304/call-the-llvm-jit-from-c-program

from libcpp.string cimport string

# --------------------------------------------------------------------------------
# minimal declarations to get to the LLVM API.
# --------------------------------------------------------------------------------

cdef extern from "llvm/LLVMContext.h" namespace "llvm":
    cdef cppclass LLVMContext:
        LLVMContext()

cdef extern from "llvm/Support/TargetSelect.h" namespace "llvm":
    cdef bint InitializeNativeTarget()

cdef extern from "llvm/Module.h" namespace "llvm":
    cdef cppclass Module:
        pass

cdef extern from "llvm/Support/MemoryBuffer.h" namespace "llvm":
    cdef cppclass MemoryBuffer:
        char * getBufferStart()
        size_t getBufferSize()
    # note naming hack to get to the static method (also note that we lose the
    #   namespace in the override).
    cdef MemoryBuffer * MemoryBuffer_getMemBuffer "llvm::MemoryBuffer::getMemBuffer" (string)

cdef extern from "llvm/Bitcode/ReaderWriter.h" namespace "llvm":
    cdef Module * ParseBitcodeFile (MemoryBuffer * Buffer, LLVMContext Context, string * ErrMsg)

cdef extern from "llvm/Function.h" namespace "llvm":
    cdef cppclass Function:
        pass

cdef extern from "llvm/ExecutionEngine/JIT.h" namespace "llvm":
    cdef cppclass ExecutionEngine:
        Function * FindFunctionNamed (char *FnName)
        void * getPointerToFunction (Function *)
    # static method
    cdef ExecutionEngine * ExecutionEngine_create "llvm::ExecutionEngine::create" (Module *)

# --------------------------------------------------------------------------------

# global initialization
cdef bint init_nt = InitializeNativeTarget()
cdef LLVMContext * context = new LLVMContext()

class LLVMError (Exception):
    pass

# 1) put this into a file, 'fact.c'
#
# int factorial (int x)
# {
#   if (X == 0) {
#     return 1;
#   } else {
#     return x * factorial (x-1);
#   }
# }
#
# 2) clang -emit-llvm -O3 -S ~/fact.c
# This will compile to llvm assembly, optimized.  Note that LLVM can actually turn it into a loop!
#
# 3) llvm-as fact.s
# This will compile the assembly to bitcode
#
# 4) run 'tryme' with the path to the resultant bitcode file (fact.s.bc).

ctypedef int iifun (int)

def tryme (path):
    cdef MemoryBuffer * mb
    cdef Module * module
    cdef ExecutionEngine * engine
    cdef Function * function
    cdef string * error = new string()
    cdef string * bitcode
    cdef iifun * myfun
    cdef bytes bc
    bc = open (path, 'rb').read()
    bitcode = new string (<char *>bc, <size_t>len(bc))
    mb = MemoryBuffer_getMemBuffer (bitcode[0])
    print 'mb.size = ', mb.getBufferSize()
    module = ParseBitcodeFile (mb, context[0], error)
    if not module:
        raise LLVMError ("ParseBitcodeFile", error.c_str())
    engine = ExecutionEngine_create (module)
    if not engine:
        raise LLVMError ("ExecutionEngine::create")
    function = engine.FindFunctionNamed ("factorial")
    if function:
        myfun = <iifun*> engine.getPointerToFunction (function)
        print myfun(5)