I'm writing code to generate LLVM bytecode from a custom VM bytecode using the LLVM Go bindings; the code is then JITed and executed in-process.
The custom VM bytecode has several operations that can't be implemented directly in LLVM, because they require changing external state; the functionality for these opcodes is implemented as Go functions.
There are excellent guides on getting started with generating LLVM bytecode from Go, but none address the issue of callbacks or exported functions. Is it possible to generate LLVM instructions to call back into the Go functions? If so, how?
I've tried the way described below by @arrowd, and it doesn't appear to work. Source code, adapted from Felix Angell's blog post:
package main
import (
"C"
"fmt"
"llvm.org/llvm/final/bindings/go/llvm"
)
// export AddInts
func AddInts(arg1, arg2 int) int {
return arg1 + arg2;
}
func main() {
// setup our builder and module
builder := llvm.NewBuilder()
mod := llvm.NewModule("my_module")
// create our function prologue
main := llvm.FunctionType(llvm.Int32Type(), []llvm.Type{}, false)
llvm.AddFunction(mod, "main", main)
block := llvm.AddBasicBlock(mod.NamedFunction("main"), "entry")
builder.SetInsertPoint(block, block.FirstInstruction())
// int a = 32
a := builder.CreateAlloca(llvm.Int32Type(), "a")
builder.CreateStore(llvm.ConstInt(llvm.Int32Type(), 32, false), a)
// int b = 16
b := builder.CreateAlloca(llvm.Int32Type(), "b")
builder.CreateStore(llvm.ConstInt(llvm.Int32Type(), 16, false), b)
// return a + b
bVal := builder.CreateLoad(b, "b_val")
aVal := builder.CreateLoad(a, "a_val")
addIntsType := llvm.FunctionType(llvm.Int32Type(), []llvm.Type{llvm.Int32Type(), llvm.Int32Type()}, false)
addInts := llvm.AddFunction(mod, "AddInts", addIntsType)
call := builder.CreateCall(addInts, []llvm.Value{aVal, bVal}, "AddInts")
builder.CreateRet(call)
// verify it's all good
if ok := llvm.VerifyModule(mod, llvm.ReturnStatusAction); ok != nil {
fmt.Println(ok.Error())
}
mod.Dump()
// create our exe engine
engine, err := llvm.NewExecutionEngine(mod)
if err != nil {
fmt.Println(err.Error())
}
// run the function!
funcResult := engine.RunFunction(mod.NamedFunction("main"), []llvm.GenericValue{})
fmt.Printf("%d
", funcResult.Int(false))
}
Returns:
; ModuleID = 'my_module'
define i32 @main() {
entry:
%a = alloca i32
store i32 32, i32* %a
%b = alloca i32
store i32 16, i32* %b
%b_val = load i32* %b
%a_val = load i32* %a
%AddInts = call i32 @AddInts(i32 %a_val, i32 %b_val)
ret i32 %AddInts
}
declare i32 @AddInts(i32, i32)
LLVM ERROR: Tried to execute an unknown external function: AddInts
exit status 1
The main problem is that you're not actually using the JIT here, you're using the interpreter. llvm.NewExecutionEngine
will construct a JIT compiler if it is available, and fall back to the interpreter otherwise.
You should use llvm.NewMCJITCompiler
. This will fail without additional changes, for the same reason why NewExecutionEngine did not yield a JIT in the first place. You need to add the following to your "func main()":
llvm.LinkInMCJIT()
llvm.InitializeNativeTarget()
llvm.InitializeNativeAsmPrinter()
Another, smaller, issue with your code is that (lack of) whitespace is important in the "//export" magic.
According to this answer you can export Go functions as C ones by adding "export" comment. If you do this for your function, the LLVM JIT should be able to resolve this symbol during runtime linking. The answer didn't provide the C code which calls that exported function, but assuming it would be just goProgressCB(args);
then you can create a CallInst
to this function and JIT it.
Updated.
Take a look at this answer. It seems that recent Go can generate C headers for you to call Go back. With this you can compile them with clang -S -emit-llvm
or clang -march=cpp
and obtain textual IR or C++ calls to the LLVM API that would make up call instructions.