ZACKYこと山崎進です。
Tsuyoshi Yamaguchi @Tsuyoshi84 さんが書いた「OKを使ってElixirの :ok, :error タプルをエレガントに処理」に刺激を受けて,さっそく型多相・型安全なNIFコードにOKを使ってみました。
さっそくコードを紹介
mix.exs
defmodule NifLlvm.MixProject do
use Mix.Project
def project do
[
app: :nif_llvm,
version: "0.1.0",
elixir: "~> 1.6",
compilers: [:nif_llvm] ++ Mix.compilers,
start_permanent: Mix.env() == :prod,
deps: deps()
]
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
[
{:asm, "~> 0.0.4"},
{:ok, "~> 2.0"}
]
end
end
defmodule Mix.Tasks.Compile.NifLlvm do
def run(_) do
if match? {:win32, _}, :os.type do
# libpostal does not support Windows unfortunately.
IO.warn("Windows is not supported.")
exit(1)
else
File.mkdir_p("priv")
{result, _error_code} = System.cmd("make", ["priv/libnifllvm.so"], stderr_to_stdout: true)
IO.binwrite result
end
:ok
end
end
defp deps
に {:ok, "~> 2.0"}
を追加しています。
lib/nif_llvm.ex
defmodule NifLlvm do
require OK
require Asm
import Asm
@on_load :load_nifs
def load_nifs do
:erlang.load_nif('./priv/libnifllvm', 0)
end
@moduledoc """
Documentation for NifLlvm.
"""
def main do
IO.puts asm_1(1, 2)
IO.puts asm_1(1.0, 2)
IO.puts asm_1(1, 2.0)
IO.puts asm_1(1.0, 2.0)
IO.puts asm_1(Asm.max_int, 0)
IO.puts asm_1(Asm.min_int, 0)
try do
IO.puts asm_1(Asm.max_int, 1)
rescue
error in [ArithmeticError] -> IO.puts "it needs BigNum!: #{Exception.message(error)}"
end
try do
IO.puts asm_1(Asm.max_int + 1, 1)
rescue
error in [ArithmeticError] -> IO.puts "it needs BigNum!: #{Exception.message(error)}"
end
end
def asm_1(a, b) do
OK.try do
result <- case {a, b} do
{a, b} when is_int64(a) and is_int64(b) -> asm_1_nif_ii(a, b)
{a, b} when is_int64(a) and is_float(b) -> asm_1_nif_if(a, b)
{a, b} when is_float(a) and is_int64(b) -> asm_1_nif_fi(a, b)
{a, b} when is_float(a) and is_float(b) -> asm_1_nif_ff(a, b)
_ -> {:error, :arithmetic_error}
end
after
result
rescue
:arithmetic_error -> raise ArithmeticError, message: "bad argument in arithmetic expression"
end
end
def asm_1_nif_ii(a, b) when is_int64(a) and is_int64(b), do: raise "NIF asm_1_nif_ii/2 not implemented"
def asm_1_nif_if(a, b) when is_int64(a) and is_float(b), do: raise "NIF asm_1_nif_if/2 not implemented"
def asm_1_nif_fi(a, b) when is_float(a) and is_int64(b), do: raise "NIF asm_1_nif_fi/2 not implemented"
def asm_1_nif_ff(a, b) when is_float(a) and is_float(b), do: raise "NIF asm_1_nif_ff/2 not implemented"
end
かなりスッキリ書けましたね。
native/lib.c
#include <limits.h>
#include "erl_nif.h"
#include "loader.c"
static ERL_NIF_TERM arithmetic_error;
static ERL_NIF_TERM ok_atom;
static ERL_NIF_TERM error_atom;
static ERL_NIF_TERM arithmetic_error_atom;
static void init_nif_llvm(ErlNifEnv *env)
{
arithmetic_error = enif_raise_exception(env, enif_make_atom(env, "ArithmeticError"));
ok_atom = enif_make_atom(env, "ok");
error_atom = enif_make_atom(env, "error");
arithmetic_error_atom = enif_make_atom(env, "arithmetic_error");
}
static
ERL_NIF_TERM asm_1_nif_ii(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
long a, b;
if(__builtin_expect((enif_get_int64(env, argv[0], &a) == 0), 0)) {
goto error;
}
if(__builtin_expect((enif_get_int64(env, argv[1], &b) == 0), 0)) {
goto error;
}
long result;
if(__builtin_expect(__builtin_saddl_overflow(a, b, &result), 0)) {
goto error2;
}
return enif_make_tuple2(env, ok_atom, enif_make_int64(env, result));
error:
return arithmetic_error;
error2:
return enif_make_tuple2(env, error_atom, arithmetic_error_atom);
}
static
ERL_NIF_TERM asm_1_nif_if(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
long a;
double b;
if(__builtin_expect((enif_get_int64(env, argv[0], &a) == 0), 0)) {
goto error;
}
if(__builtin_expect((enif_get_double(env, argv[1], &b) == 0), 0)) {
goto error;
}
double result = ((double)a) + b;
return enif_make_tuple2(env, ok_atom, enif_make_double(env, result));
error:
return arithmetic_error;
}
static
ERL_NIF_TERM asm_1_nif_fi(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
double a;
long b;
if(__builtin_expect((enif_get_double(env, argv[0], &a) == 0), 0)) {
goto error;
}
if(__builtin_expect((enif_get_int64(env, argv[1], &b) == 0), 0)) {
goto error;
}
double result = a + ((double) b);
return enif_make_tuple2(env, ok_atom, enif_make_double(env, result));
error:
return arithmetic_error;
}
static
ERL_NIF_TERM asm_1_nif_ff(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
double a, b;
if(__builtin_expect((enif_get_double(env, argv[0], &a) == 0), 0)) {
goto error;
}
if(__builtin_expect((enif_get_double(env, argv[1], &b) == 0), 0)) {
goto error;
}
double result = a + b;
return enif_make_tuple2(env, ok_atom, enif_make_double(env, result));
error:
return arithmetic_error;
}
static
ErlNifFunc nif_funcs[] =
{
// {erl_function_name, erl_function_arity, c_function}
{"asm_1_nif_ii", 2, asm_1_nif_ii},
{"asm_1_nif_if", 2, asm_1_nif_if},
{"asm_1_nif_fi", 2, asm_1_nif_fi},
{"asm_1_nif_ff", 2, asm_1_nif_ff}
};
ERL_NIF_INIT(Elixir.NifLlvm, nif_funcs, &load, &reload, &upgrade, &unload)
NIFコードの変更点は,:ok
, :error
を第1引数に,計算結果もしくは :arithmetic_error
を第2引数にした2引数のタプルを返すようにした点です。
次回は「ZEAM開発ログ v.0.4.7 BigNum をどのようにNIFで扱うか考える」です。お楽しみに!
お知らせ:Elixirもくもく会(リモート参加OK、入門トラック有)を9月28日に開催します
「fukuoka.ex#14:Elixir/Phoenixもくもく会~入門もあるよ」を2018年9月28日金曜日に開催します
前回は,ゲリラ的に募った「Zoomによるリモート参加」を,今回から正式に受け付けるようになりましたので,福岡以外の首都圏や地方からでも参加できます(申し込みいただいたら、追ってZoom URLをconnpassメールでお送りします)
また,これまではElixir/Phoenix経験者を対象とした,もくもく会オンリーでしたが,今回から,入門者トラックも併設し,fukuoka.exアドバイザーズ/キャストに質問できるようにアップグレードしました
私,山崎も参加します! この記事の延長線上のものを作ろうと思っています。
お申込みはコチラから
https://fukuokaex.connpass.com/event/100659/