2016-04-26 76 views
4

我正在編寫代碼來使用LLVM Go bindings從自定義VM字節碼生成LLVM字節碼;該代碼然後被JIT編譯並在進程中執行。JIT的LLVM代碼如何回調到Go函數?

自定義VM字節碼有幾個操作無法直接在LLVM中實現,因爲它們需要更改外部狀態;這些操作碼的功能實現爲Go功能。

有關於從Go生成LLVM字節碼入門的excellent guides,但沒有解決回調或導出函數的問題。是否有可能生成LLVM指令來回調Go功能?如果是這樣,怎麼樣?

我試過了@arrowd下面描述的方式,它看起來不起作用。源代碼,改編自費利克斯·安格爾的博客文章:

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\n", funcResult.Int(false)) 
} 

返回:

; 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 
+0

Haskell提供了一種將代碼導出爲C函數的方法,因此可以從任何東西(包括LLVM)中調用Haskell。 Go有這樣的功能嗎? – arrowd

+0

@arrowd我的理解是,CGo提供了這一點(而本機Go實現沒有,因爲它使用自己的調用約定)。這是朝着正確方向邁出的一步,但我仍然不確定如何從LLVM實際調用該功能。 –

回答

2

主要的問題是,你實際上並沒有使用JIT,而是使用瞭解釋器。 llvm.NewExecutionEngine將構造JIT編譯器(如果可用),否則將退回到解釋器。您可以使用llvm.NewMCJITCompiler。如果沒有額外的更改,這將失敗,出於同樣的原因,爲什麼NewExecutionEngine首先不會產生JIT。您需要將以下添加到您的「FUNC的main()」:

llvm.LinkInMCJIT()    
llvm.InitializeNativeTarget()  
llvm.InitializeNativeAsmPrinter() 

另外,規模較小,與您的代碼的問題是,(缺乏)空白處於「//出口」神奇重要。

+0

太棒了 - 謝謝! –

3

根據this answer您可以通過添加「出口」的評論出口都功能爲C的。如果您爲您的函數執行此操作,LLVM JIT應該能夠在運行時鏈接期間解析此符號。答案沒有提供調用導出函數的C代碼,但假設它只是goProgressCB(args);,那麼您可以創建一個CallInst並將其加入JIT中。

已更新。

看看this answer。看來最近的Go可以生成C頭文件給你回叫。通過這種方式,您可以使用clang -S -emit-llvmclang -march=cpp進行編譯,並獲得組成調用指令的LLVM API的文本IR或C++調用。

+0

謝謝!儘管如此,我仍然希望有人能夠提出一個簡單的端到端LLVM示例(部分原因是我確信我不會是最後一個人問這個問題)。 –

+0

順便說一下,用於go的LLVM綁定沒有'CallInst';有沒有相當於我應該尋找的東西? –

+0

所以,不幸的是,這似乎並不奏效 - 請參閱我的帖子更新。 –