ZACKYこと山崎進です。
Elixir の整数は上限・下限が無制限なのが特徴ですが,NIFのAPIでは整数値は64ビット整数(INT64もしくはUINT64)までしか対応していません。64ビット整数の範囲を超える整数値をNIFで扱うにはどうしたらいいかを考えたいと思います。
こんなコードを実行してみた
static
ERL_NIF_TERM asm_1_nif_bi(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
if(enif_is_number(env, argv[0])) {
printf("is a number\n");
} else {
printf("is not a number\n");
}
if(enif_is_map(env, argv[0])) {
printf("is a map (struct)\n");
} else {
printf("is not a map (struct)\n");
}
if(enif_is_empty_list(env, argv[0])) {
printf("is an empty list\n");
} else {
printf("is not an empty list\n");
}
if(enif_is_list(env, argv[0])) {
printf("is a list\n");
} else {
printf("is not a list\n");
}
if(enif_is_tuple(env, argv[0])) {
printf("is a tuple\n");
} else {
printf("is not a tuple\n");
}
if(enif_is_ref(env, argv[0])) {
printf("is a ref\n");
} else {
printf("is not a ref\n");
}
if(enif_is_atom(env, argv[0])) {
printf("is an atom\n");
} else {
printf("is not an atom\n");
}
if(enif_is_binary(env, argv[0])) {
printf("is a binary\n");
} else {
printf("is not a binary\n");
}
long l;
unsigned long u;
if(enif_get_int64(env, argv[0], &l)) {
printf("is INT64\n");
} else if(enif_get_uint64(env, argv[0], &u)) {
printf("is UINT64\n");
} else {
printf("is a bignum\n");
}
return enif_make_tuple2(env, error_atom, arithmetic_error_atom);
}
try do
IO.puts asm_1(Asm.max_uint + 1, 1)
rescue
error in [ArithmeticError] -> IO.puts "it needs BigNum!: #{Exception.message(error)}"
end
実行結果
is a number
is not a map (struct)
is not an empty list
is not a list
is not a tuple
is not a ref
is not an atom
is not a binary
is a bignum
it needs BigNum!: bad argument in arithmetic expression
実行結果 (is a number) から,enif_is_number
では true が返ってくることがわかります。
それ以外の基本型にはいずれも該当しません。しかし,enif_get_int64
と enif_get_uint64
にはいずれも取得に失敗しています。
このことから,NIFで標準に用意されているAPIでは64ビット整数の範囲外の整数値は取得できないことがわかりました。
ちなみに ERL_NIF_TERM
はただの整数値,ErlNifEnv
はメンバーが定義されていない構造体ポインタとして定義されていますので,正攻法では中身をパースすることもできません。これは NIF 側から不用意に Erlang VM 内部をいじらせないためにこのようになっていると考えられます。
では打ち手はないのか?
打ち手はあります! 整数値どうか,64ビット整数値の範囲内かどうかは Elixir 側で判別できるので,該当する場合には,論理演算・シフト演算を使って64ビット整数のリストに分解してから NIF に渡せばいいです。この方法は,また機会を改めて試したいと思います。
おまけ: max_uint, min_uint, is_uint64, is_bignum を追加しました
「ZEAM開発ログ v.0.4.4 INT64判定をマクロで簡単に判定する」で紹介した判定マクロに機能を追加しました。 https://github.com/zeam-vm/asm
-
max_int
: 符号付き64ビット整数の最大値 -
min_int
: 符号付き64ビット整数の最小値 -
max_uint
: 符号なし64ビット整数の最大値 -
min_uint
: 符号なし64ビット整数の最小値 -
is_int64
: INT64判定 -
is_uint64
: UINT64判定 -
is_bignum
: 整数値だが64ビット整数に収まらない場合にtrue
mix.exs
に次のように記述してください。
defp deps do
[
{:asm, "~> 0.0.7"}
]
この結果,型多相で型安全なNIFは次のようになりました。
lib/nif_llvm.ex
の一部
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_uint64(a) and is_uint64(b) -> asm_1_nif_uu(a, b)
{a, b} when is_integer(a) and is_integer(b) ->
IO.puts "need BigNum"
{:error, :arithmetic_error}
{a, b} when is_int64(a) and is_float(b) -> asm_1_nif_if(a, b)
{a, b} when is_uint64(a) and is_float(b) -> asm_1_nif_uf(a, b)
{a, b} when is_integer(a) and is_float(b) ->
IO.puts "need BigNum"
{:error, :arithmetic_error}
{a, b} when is_float(a) and is_int64(b) -> asm_1_nif_fi(a, b)
{a, b} when is_float(a) and is_uint64(b) -> asm_1_nif_fu(a, b)
{a, b} when is_float(a) and is_integer(b) ->
IO.puts "need BigNum"
{:error, :arithmetic_error}
{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_uu(a, b) when is_uint64(a) and is_uint64(b), do: raise "NIF asm_1_nif_uu/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_uf(a, b) when is_uint64(a) and is_float(b), do: raise "NIF asm_1_nif_uf/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_fu(a, b) when is_float(a) and is_uint64(b), do: raise "NIF asm_1_nif_fu/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"
次回は「ZEAM開発ログ v.0.4.8 INT64判定をGPUベンチマークに組込む」 をお送りします。お楽しみに!
お知らせ: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/