事の始まり
LLVMには、フロントエンド開発の事始めとしてKaleidoscopeという独自言語を開発するチュートリアルがある。 このチュートリアルはC++で実装していく流れなのだが、グローバル変数だらけでLLVMどの関数/変数が何に依存していて何の機能に重要なのかがいまいち汲み取るのがしんどかった。 そこで、グローバル変数を取り除いていきモダンなコードで書けるようにしたいと思いたち、どうせならRustで書いていくかと始めたのが事のはじまりであった。 このチュートリアルの1、2章は字句解析器と構文解析器の実装なので特に問題は起こらなかった。
このブログは3章に突入したときの話である。 C++APIになるべく近い形でLLVMのAPIを叩きたかったので、llvm-sysクレートを使うことに決めた。
llvm-sysクレート
ここで少しllvm-sysクレートの紹介をする。
Rustの*-sys
クレートの命名の慣習をご存知の方はすぐに予想ができるであろうが、このクレートはllvmのC/C++APIをRust側から呼べるように単純にラップしたものだ。
crates.ioでは既に使われていないAppveyorのバッジが残っており、build failingになってるのが気にはなるが、
開発は継続されてなされており最新のLLVM9にも対応しているようなので、信頼できそうだ。
本題
さて話を戻そう。dependenciesにllvm-sysを追加してcargo build
をしたところ、以下のようなリンクエラーが起きた(一部抜粋)。
= note: /usr/lib/gcc/x86_64-pc-linux-gnu/9.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: /home/ben1jake/wrk/develop/kaleidoscope/target/debug/deps/kaleidoscope-7e8763b44e7ac4a4.3otu9pud9f3w0saw.rcgu.o: in function `kaleidoscope::ir::LLVMContext::new': /home/ben1jake/wrk/develop/kaleidoscope/src/ir.rs:14: undefined reference to `LLVMContextCreate'
どうやらLLVMContextCreate
が見つからないようだ。
しかし、このチュートリアルをC++で書いたときはちゃんとリンクができており、
LLVMのライブラリをリンクするときに必要な情報はllvm-config
コマンドで取得していた。
この情報はllvm-sysクレートが取得してくれているはずである。
Rustのプロジェクトではリンクなどのrustcに渡される情報はbuild.rsによって生成でき、この情報はtarget/debug/build
などの下に出力される。
今回はllvm-sysの結果を見たかったので、target/debug/build/llvm-sys-*/output
を見にいった(以下一部抜粋)。
cargo:rustc-link-lib=static=targetwrappers cargo:rustc-link-search=native=/home/ben1jake/wrk/develop/kaleidoscope/target/debug/build/llvm-sys-ada015182d93d582/out cargo:config_path=llvm-config cargo:libdir=/usr/lib64/llvm/9/lib64 cargo:rustc-link-search=native=/usr/lib64/llvm/9/lib64 cargo:rustc-link-lib=dylib=stdc++
rustcにリンクすべきライブラリを指定するのはcargo:rustc-link-lib=
であるが、
ここではtargetwrappers
とstdc++
だけが指定されており、LLVMのライブラリが指定されていない
(targetwrappers
はllvm-sysが生成するライブラリであり、これだけでは足りない)。
少なくともcargo:libdir=
は正しく出力されているようなのでllvm-config
は使われているようだ。
どうしようもないので、llvm-configのbuild.rsを覗きに行ったら原因が発覚した。
上記のURLにあるとおり、llvm-sysではllvm-config
にオプション--link-static
を渡しており、
LLVMを静的リンクするようになっていたようだ。そこで自分の環境を見てみるとLLVMの動的ライブラリしかなかったのである!
しかたがないので、LLVMを自前ビルドし、PATHを通してから再度ビルドを行ったら無事にリンクも成功した。
おそらく、今回の例のように静的ライブラリが存在しないというのは稀なケースだと思われるが、 パッケージマネージャーでLLVMをインストールする際に静的ライブラリが含まれないような環境の人には、是非ともLLVMを自前ビルドしてもらいたい。 余談ではあるが、LLVMを自前ビルドする際に並列ビルドをしたらリンク時にメモリをすべて喰われてリンクに失敗したので、並列ビルドのご利用は計画的に。