ZEAM開発ログ v.0.4.15 Rustler から LLVM 7.0 を動かしてみた

ZACKYこと山崎進です。

いよいよコード生成に取組みます。今回は Rustler から LLVM 7.0 を動かしてみました。

[「ZEAM開発ログ 目次」はこちら](https://qiita.com/zacky1972/items/70593ab2b70d192813df)

# 準備〜Mac編

まず[「macOS / Homebrew での exenv / erlenv を用いた Elixir / Erlang のソースコードインストール」](https://qiita.com/zacky1972/items/27676894a03fb881e160)にしたがって Erlang と Elixir をインストールします。Erlang は OTP21以降，Elixirは1.7以降をインストールします。

次に rustup をインストールします。[こちらのrustupホームページ](https://rustup.rs)に書かれているスクリプトを実行します。

それからLLVM 7.0をインストールする必要があります。Homebrew は最近 LLVM 7.0 が含まれるようになりました。

```bash
$ brew install llvm
$ brew link llvm --force
```

次のようにして `libedit` をインストールする必要があります。

```bash
$ brew install libedit
```

# 準備〜Ubuntu 16.04編

まず[「Linux (Ubuntu 16.04) での exenv / erlenv を用いた Elixir / Erlang のソースコードインストール」](https://qiita.com/zacky1972/items/338baab1ccde12dcfab2)にしたがって Erlang と Elixir をインストールします。Erlang は OTP21以降，Elixirは1.7以降をインストールします。

次に rustup をインストールします。[こちらのrustupホームページ](https://rustup.rs)に書かれているスクリプトを実行します。

それからLLVM 7.0をインストールする必要があります。Ubuntuの標準パッケージはバージョンが古いので，[llvm.org の指示](https://apt.llvm.org)にしたがってインストールします。

まず，`sudo vi /etc/apt/sources.list` として，末尾に次の記述を足します。

```
# LLVM 7
deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-7 main
deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-7 main
```

次のコマンドを実行して認証します。

```bash
$ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
```

途中で止まったら，`sudo` のパスワードを入力します。

次のコマンドを実行してインストールします。

```bash
$ sudo apt-get update
$ sudo apt-get install clang-7 lldb-7 lld-7
```

途中，「検証なしにこれらのパッケージをインストールしますか？ [y/N]」と表示されるので，`y` を押します。

そして，`libedit` をインストールします。

```bash
$ sudo apt-get install libedit-dev
```

# サンプルコードの実行

```bash
$ git clone https://github.com/zeam-vm/nif_llvm_2.git
$ cd nif_llvm_2
$ mix deps.get
$ mix run -e "NifLlvm2.generate_code"
```

次のように表示されれば無事実行できました。

```
; ModuleID = 'my_module'
source_filename = "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, i32* %b
  %a_val = load i32, i32* %a
  %ab_val = add i32 %a_val, %b_val
  ret i32 %ab_val
}
32 + 16 = 48
```

# サンプルコードについて

次の記事を参考にしました。

* [Elixirから簡単にRustを呼び出せるRustler #1 準備編](https://qiita.com/twinbee/items/aabc11d0d667800fc0bb)
* [RustでLLVMしてみたメモ](https://qiita.com/JunSuzukiJapan/items/88c5fec58dddb0522cca)
* [Go言語で利用するLLVM入門](https://postd.cc/an-introduction-to-llvm-in-go/)

まず[「Elixirから簡単にRustを呼び出せるRustler #1 準備編」](https://qiita.com/twinbee/items/aabc11d0d667800fc0bb)に沿って Rustler をセットアップします。

変更点は次の通りです。

1. `mix rustler.new` のところで Elixir モジュール名を `NifLlvm2`，Rustユニット名を `llvm` としました。
2. lib/example.ex は lib/nif_llvm_2.ex に，native/example/src/lib.rs は native/llvm/src/lib.rs にそれぞれ置き換わっています。
3. mix.exs を下記のようにしています。

mix.exs

```elixir
defmodule NifLlvm2.MixProject do
  use Mix.Project

  def project do
    [
      app: :nif_llvm_2,
      version: "0.1.0",
      elixir: "~> 1.7",
      compilers: [:rustler] ++ Mix.compilers,
      rustler_crates: rustler_crates(),
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  defp rustler_crates() do
    [llvm: [
      path: "native/llvm",
      mode: :release,
    ]]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
    	{:rustler, "~> 0.18.0"}
    ]
  end
end
```

ここまでで一旦コンパイルできることを確かめます。

次に native/llvm/Cargo.toml を次のようにします。

native/llvm/Cargo.toml

```toml
[package]
name = "llvm"
version = "0.1.0"
authors = []

[lib]
name = "llvm"
path = "src/lib.rs"
crate-type = ["dylib"]

[dependencies]
rustler = "0.18.0"
rustler_codegen = "0.18.0"
lazy_static = "1.0"
llvm-sys = "70"
```

末尾の `llvm-sys = "70"` が追加した項目です。番号は LLVM のバージョン番号と対応しています。詳しくは[llvm-sys のドキュメント](https://crates.io/crates/llvm-sys)を参照してください。

そして native/llvm/.cargo/config を作成して次のように入れます。(これがわからなくてハマった。。。)

native/llvm/.cargo/config

```toml
[build]
rustflags = ["-lffi", "-ledit"]
```

さらに native/llvm/src/lib.src を次のようにします。

native/llvm/src/lib.src

```rust
#[macro_use] extern crate rustler;
// #[macro_use] extern crate rustler_codegen;
#[macro_use] extern crate lazy_static;

extern crate llvm_sys;

use rustler::{Env, Term, NifResult, Encoder};
use llvm_sys::core::*;
use llvm_sys::target;
use llvm_sys::analysis::{LLVMVerifyModule, LLVMVerifierFailureAction};
use llvm_sys::execution_engine::*;
use std::ffi::CString;
use std::os::raw::{c_char};

mod atoms {
    rustler_atoms! {
        atom ok;
        //atom error;
        //atom __true__ = "true";
        //atom __false__ = "false";
    }
}

rustler_export_nifs! {
    "Elixir.NifLlvm2",
    [("generate_code", 0, generate_code)],
    None
}

/*

int main() {
  int a = 32;
  int b = 16;
  return a + b;
}

define i32 @main() #0 {
  %1 = alloca i32, align 4
  %a = alloca i32, align 4
  %b = alloca i32, align 4
  store i32 0, i32* %1
  store i32 32, i32* %a, align 4
  store i32 16, i32* %b, align 4
  %2 = load i32, i32* %a, align 4
  %3 = load i32, i32* %b, align 4
  %4 = add nsw i32 %2, %3
  ret i32 %4
}

*/

fn initialize_llvm() {
    unsafe {
        if target::LLVM_InitializeNativeTarget() != 0 {
            panic!("Could not initialize target")
        }
        if target::LLVM_InitializeNativeAsmPrinter() != 0 {
            panic!("Could not initialize ASM Printer")
        }
    }
}

fn generate_code<'a>(env: Env<'a>, _args: &[Term<'a>]) -> NifResult<Term<'a>> {
    let llvm_error = 1;
    let val1 = 32;
    let val2 = 16;

    initialize_llvm();


    // setup our builder and module
    let builder = unsafe { LLVMCreateBuilder() };
    let mod_name = CString::new("my_module").unwrap();
    let module = unsafe { LLVMModuleCreateWithName(mod_name.as_ptr()) };

    // create our function prologue
    let function_type = unsafe {
        let mut param_types = [];
        LLVMFunctionType(LLVMInt32Type(), param_types.as_mut_ptr(), param_types.len() as u32, 0)
    };
    let function_name = CString::new("main").unwrap();
    let function = unsafe { LLVMAddFunction(module, function_name.as_ptr(), function_type)};
    let entry_name = CString::new("entry").unwrap();
    let entry_block = unsafe { LLVMAppendBasicBlock(function, entry_name.as_ptr())};
    unsafe { LLVMPositionBuilderAtEnd(builder, entry_block); }

    // int a = 32
    let a_name = CString::new("a").unwrap();
    let a = unsafe { LLVMBuildAlloca(builder, LLVMInt32Type(), a_name.as_ptr())};
    unsafe { LLVMBuildStore(builder, LLVMConstInt(LLVMInt32Type(), val1, 0), a); }

    // int b = 16
    let b_name = CString::new("b").unwrap();
    let b = unsafe { LLVMBuildAlloca(builder, LLVMInt32Type(), b_name.as_ptr())};
    unsafe { LLVMBuildStore(builder, LLVMConstInt(LLVMInt32Type(), val2, 0), b); }

    // return a + b
    let b_val_name = CString::new("b_val").unwrap();
    let b_val = unsafe { LLVMBuildLoad(builder, b, b_val_name.as_ptr()) };
    let a_val_name = CString::new("a_val").unwrap();
    let a_val = unsafe { LLVMBuildLoad(builder, a, a_val_name.as_ptr()) };
    let ab_val_name = CString::new("ab_val").unwrap();
    unsafe {
        let res = LLVMBuildAdd(builder, a_val, b_val, ab_val_name.as_ptr());
        LLVMBuildRet(builder, res);
    }

    // verify it's all good
    let mut error: *mut c_char = 0 as *mut c_char;
    let ok = unsafe {
        let buf: *mut *mut c_char = &mut error;
        LLVMVerifyModule(module, LLVMVerifierFailureAction::LLVMReturnStatusAction, buf)
    };
    if ok == llvm_error {
        let err_msg = unsafe { CString::from_raw(error).into_string().unwrap() };
        panic!("cannot verify module '{:?}.\nError: {}", mod_name, err_msg);
    }

    // Clean up the builder now that we are finished using it.
    unsafe { LLVMDisposeBuilder(builder) }

    // Dump the LLVM IR to stdout so we can see what we've created
    unsafe { LLVMDumpModule(module) }

    // create our exe engine
    let mut engine: LLVMExecutionEngineRef = 0 as LLVMExecutionEngineRef;
    let ok = unsafe {
        error = 0 as *mut c_char;
        let buf: *mut *mut c_char = &mut error;
        let engine_ref: *mut LLVMExecutionEngineRef = &mut engine;
        LLVMLinkInInterpreter();
        LLVMCreateInterpreterForModule(engine_ref, module, buf)
    };

    if ok == llvm_error {
        let err_msg = unsafe { CString::from_raw(error).into_string().unwrap() };
        println!("Execution error: {}", err_msg);
    } else {
        // run the function!
        let func_name = CString::new("main").unwrap();
        let named_function = unsafe { LLVMGetNamedFunction(module, func_name.as_ptr()) };
        let mut params = [];
        let func_result = unsafe { LLVMRunFunction(engine, named_function, params.len() as u32, params.as_mut_ptr()) };
        let result = unsafe { LLVMGenericValueToInt(func_result, 0) };
        println!("{} + {} = {}", val1, val2, result);
    }

    // Clean up the module after we're done with it.
    unsafe { LLVMDisposeModule(module) }

    Ok(atoms::ok().encode(env))
}
```

最後に lib/nif_llvm_2.ex を次のようにします。

lib/nif_llvm_2.ex

```elixir
defmodule NifLlvm2 do
  use Rustler, otp_app: :nif_llvm_2, crate: :llvm

  @moduledoc """
  Documentation for NifLlvm2.
  """


  @doc """
    ## Examples

    iex> NifLlvm2.generate_code()
    :ok
  """
  def generate_code(), do: exit(:nif_not_loaded)
end
```

# 解説

このコードは，次のコード相当を LLVM で生成して JIT 実行するというものです。

```c
int main() {
  int a = 32;
  int b = 16;
  return a + b;
}
```

```llvm
define i32 @main() #0 {
  %1 = alloca i32, align 4
  %a = alloca i32, align 4
  %b = alloca i32, align 4
  store i32 0, i32* %1
  store i32 32, i32* %a, align 4
  store i32 16, i32* %b, align 4
  %2 = load i32, i32* %a, align 4
  %3 = load i32, i32* %b, align 4
  %4 = add nsw i32 %2, %3
  ret i32 %4
}
```

詳しい解説は，後で追記します。[「Go言語で利用するLLVM入門」](https://postd.cc/an-introduction-to-llvm-in-go/)に解説がされていますので，急ぐ人はそちらを参照してください。