3
0

More than 5 years have passed since last update.

ZEAM開発ログ v.0.4.7 BigNum をどのようにNIFで扱うか考える

Last updated at Posted at 2018-09-25

ZACKYこと山崎進です。

Elixir の整数は上限・下限が無制限なのが特徴ですが,NIFのAPIでは整数値は64ビット整数(INT64もしくはUINT64)までしか対応していません。64ビット整数の範囲を超える整数値をNIFで扱うにはどうしたらいいかを考えたいと思います。

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

こんなコードを実行してみた

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_int64enif_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"

コード全体はこちら(GitHub)

次回は「ZEAM開発ログ v.0.4.8 INT64判定をGPUベンチマークに組込む」 をお送りします。お楽しみに!

:stars::stars::stars: お知らせ:Elixirもくもく会(リモート参加OK、入門トラック有)を9月28日に開催します :stars::stars::stars:

「fukuoka.ex#14:Elixir/Phoenixもくもく会~入門もあるよ」を2018年9月28日金曜日に開催します

前回は,ゲリラ的に募った「Zoomによるリモート参加」を,今回から正式に受け付けるようになりましたので,福岡以外の首都圏や地方からでも参加できます(申し込みいただいたら、追ってZoom URLをconnpassメールでお送りします)

また,これまではElixir/Phoenix経験者を対象とした,もくもく会オンリーでしたが,今回から,入門者トラックも併設し,fukuoka.exアドバイザーズ/キャストに質問できるようにアップグレードしました

私,山崎も参加します! この記事の延長線上のものを作ろうと思っています。

お申込みはコチラから
https://fukuokaex.connpass.com/event/100659/
image.png

3
0
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
3
0