LoginSignup
8
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-10-12

ZACKYこと山崎進です。

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

「ZEAM開発ログ 目次」はこちら

準備〜Mac編

まず「macOS / Homebrew での exenv / erlenv を用いた Elixir / Erlang のソースコードインストール」にしたがって Erlang と Elixir をインストールします。Erlang は OTP21以降,Elixirは1.7以降をインストールします。

次に rustup をインストールします。こちらのrustupホームページに書かれているスクリプトを実行します。

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

$ brew install llvm
$ brew link llvm --force

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

$ brew install libedit

準備〜Ubuntu 16.04編

まず「Linux (Ubuntu 16.04) での exenv / erlenv を用いた Elixir / Erlang のソースコードインストール」にしたがって Erlang と Elixir をインストールします。Erlang は OTP21以降,Elixirは1.7以降をインストールします。

次に rustup をインストールします。こちらのrustupホームページに書かれているスクリプトを実行します。

それからLLVM 7.0をインストールする必要があります。Ubuntuの標準パッケージはバージョンが古いので,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

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

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

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

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

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

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

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

$ sudo apt-get install libedit-dev

サンプルコードの実行

$ 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 準備編」に沿って 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

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

[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 のドキュメントを参照してください。

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

native/llvm/.cargo/config

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

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

native/llvm/src/lib.src

#[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

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 実行するというものです。

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
}

詳しい解説は,後で追記します。「Go言語で利用するLLVM入門」に解説がされていますので,急ぐ人はそちらを参照してください。

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4