はじめに
Xcodeの次のバージョンではC++との連携が便利になるらしいので、今のうちにライブラリ等を使わずにLLVM-Cを直に使ってLLVMの感じを調べた。
下準備
-
Homebrew等でLLVMの最新版(執筆時点では16.0.6)をインストール。
-
XcodeでCommand Line Toolのプロジェクトを作って、LLVMのヘッダとライブラリをサーチパスに追加。
TARGETS
-Build Settings
-Basic
のHeader Search Paths
とLibrary Search Paths
。
パスの確認はllvm-config
でできる。
$ llvm-config --includedir
/opt/homebrew/Cellar/llvm/16.0.6/include
$ llvm-config --libdir
/opt/homebrew/Cellar/llvm/16.0.6/lib
-
TARGETS
-Build Phases
のLink Binary With Libraries
にlibLLVM-C.dylib
を追加。
このままだとlibLLVM-C.dylib
を見つけてくれないので、TARGETS
-Signing & Capabilities
タブのHardened Runtime
の中のDisable Library Validation
にチェックを入れる。 -
Swiftから関数が使えるようにするための、
Bridging-Header.h
を作る。(File - New - File...からHeader Fileを選ぶ)
TARGETS
-Build Settings
-Basic
のObjective-C Bridging Header
にBridging-Header.h
を追加。
中身は今回使用する関数が含まれているものだけ。
#ifndef Bridging_Header_h
#define Bridging_Header_h
#include <llvm-c/Core.h>
#include <llvm-c/ExecutionEngine.h>
#endif /* Bridging_Header_h */
実装
参考にしたサイトとほとんど何にも変わらない。
できるだけ記述をコンパクトにまとめて、LLVMContexRefを使用するようにしただけ。
func llvm() {
let ctx = LLVMContextCreate()
defer { LLVMContextDispose(ctx) }
let mod = LLVMModuleCreateWithNameInContext("Hello", ctx)
defer { LLVMDisposeModule(mod) }
let i32 = LLVMInt32TypeInContext(ctx)
// sum: int(*)(int, int)
var spt = [i32, i32]
let sft = LLVMFunctionType(i32, &spt, UInt32(spt.count), LLVMBool(0))
let sum = LLVMAddFunction(mod, "sum", sft)
let sbb = LLVMAppendBasicBlockInContext(ctx, sum, "entry")
let sb = LLVMCreateBuilderInContext(ctx)
defer { LLVMDisposeBuilder(sb) }
LLVMPositionBuilderAtEnd(sb, sbb)
LLVMBuildRet(sb, LLVMBuildAdd(sb, LLVMGetParam(sum, 0), LLVMGetParam(sum, 1), "add_tmp"))
// main: int(*)(void)
let mft = LLVMFunctionType(i32, nil, 0, LLVMBool(0))
let wrap = LLVMAddFunction(mod, "__wrapper_main", mft)
let mbb = LLVMAppendBasicBlockInContext(ctx, wrap, "entry")
let mb = LLVMCreateBuilderInContext(ctx)
defer { LLVMDisposeBuilder(mb) }
LLVMPositionBuilderAtEnd(mb, mbb)
let x = 10, y = 25
var args = [LLVMConstInt(i32, UInt64(x), LLVMBool(0)),
LLVMConstInt(i32, UInt64(y), LLVMBool(0)), ]
LLVMBuildRet(mb, LLVMBuildCall2(mb, sft, sum, &args, UInt32(args.count), "call_tmp"))
// execution
LLVMLinkInMCJIT()
LLVMInitializeNativeTarget()
LLVMInitializeNativeAsmPrinter()
var ee: LLVMExecutionEngineRef? = nil
var err: UnsafeMutablePointer<CChar>? = nil
defer { err?.deallocate() }
LLVMLinkInInterpreter()
guard LLVMCreateExecutionEngineForModule(&ee, mod, &err) == 0 else {
print(String(cString: err!))
LLVMDisposeMessage(err)
return
}
let z = LLVMRunFunction(ee, wrap, 0, nil)
print("\(x) + \(y) = \(LLVMGenericValueToInt(z, LLVMBool(0)))")
LLVMDumpModule(mod)
}
llvm()
実行結果
特に面白いことは何もしていないけど、感じはわかった。
10 + 25 = 35
; ModuleID = 'Hello'
source_filename = "Hello"
target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128"
define i32 @sum(i32 %0, i32 %1) {
entry:
%add_tmp = add i32 %0, %1
ret i32 %add_tmp
}
define i32 @__wrapper_main() {
entry:
%call_tmp = call i32 @sum(i32 10, i32 25)
ret i32 %call_tmp
}
おわりに
Xcodeのバージョンが15になったらC++で書き直してみたい。
また、ORCというJITもあるみたいなので、LLVM-Cでどうにか使えないか調べてみたい。
参考にしたサイト