llvm-sysクレートを使うときにリンクに失敗した

事の始まり

LLVMには、フロントエンド開発の事始めとしてKaleidoscopeという独自言語を開発するチュートリアルがある。 このチュートリアルC++で実装していく流れなのだが、グローバル変数だらけでLLVMどの関数/変数が何に依存していて何の機能に重要なのかがいまいち汲み取るのがしんどかった。 そこで、グローバル変数を取り除いていきモダンなコードで書けるようにしたいと思いたち、どうせならRustで書いていくかと始めたのが事のはじまりであった。 このチュートリアルの1、2章は字句解析器と構文解析器の実装なので特に問題は起こらなかった。

このブログは3章に突入したときの話である。 C++APIになるべく近い形でLLVMAPIを叩きたかったので、llvm-sysクレートを使うことに決めた。

llvm-sysクレート

ここで少しllvm-sysクレートの紹介をする。 Rustの*-sysクレートの命名の慣習をご存知の方はすぐに予想ができるであろうが、このクレートはllvmC/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=であるが、 ここではtargetwrappersstdc++だけが指定されており、LLVMのライブラリが指定されていない (targetwrappersllvm-sysが生成するライブラリであり、これだけでは足りない)。

少なくともcargo:libdir=は正しく出力されているようなのでllvm-configは使われているようだ。 どうしようもないので、llvm-configのbuild.rsを覗きに行ったら原因が発覚した。

gitlab.com

上記のURLにあるとおり、llvm-sysではllvm-configにオプション--link-staticを渡しており、 LLVMを静的リンクするようになっていたようだ。そこで自分の環境を見てみるとLLVMの動的ライブラリしかなかったのである! しかたがないので、LLVMを自前ビルドし、PATHを通してから再度ビルドを行ったら無事にリンクも成功した。

おそらく、今回の例のように静的ライブラリが存在しないというのは稀なケースだと思われるが、 パッケージマネージャーでLLVMをインストールする際に静的ライブラリが含まれないような環境の人には、是非ともLLVMを自前ビルドしてもらいたい。 余談ではあるが、LLVMを自前ビルドする際に並列ビルドをしたらリンク時にメモリをすべて喰われてリンクに失敗したので、並列ビルドのご利用は計画的に。