ZEAM開発ログ v.0.4.14.1 Elixir / NIF 間で BigNum をやりとりする (NIF側)

ZACKYこと山崎進です。

Elixir/NIF間でBigNumをやりとりするの2回目は，NIF側について説明したいと思います。

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

# 書いたコード

```c
#include "erl_nif.h"

typedef struct vector {
  int size;
  unsigned long *value;
} VECTOR;

int enif_get_big_num(ErlNifEnv *env, ERL_NIF_TERM term, unsigned *is_negative, VECTOR **value)
{
  int tuple_arity;
  const ERL_NIF_TERM *tuple;
  if(__builtin_expect((enif_get_tuple(env, term, &tuple_arity, &tuple) == 0), 0)) {
    return 0;
  }
  if(__builtin_expect(tuple_arity != 2, 0)) {
    return 0;
  }
  if(__builtin_expect((enif_get_uint(env, tuple[0], is_negative) == 0), 0)) {
    return 0;
  }
  unsigned int length;
  if(__builtin_expect((enif_get_list_length(env, tuple[1], &length) == 0), 0)) {
    return 0;
  }
  if(__builtin_expect((length == 0), 0)) {
    return 0;
  }
  ERL_NIF_TERM head, tail;
  if(__builtin_expect((enif_get_list_cell(env, tuple[1], &head, &tail) == 0), 0)) {
    return 0;
  }
  unsigned long *v = (unsigned long *)enif_alloc(sizeof(unsigned long) * length);
  if(__builtin_expect((v == NULL), 0)) {
    return 0;
  }
  *value = (VECTOR *)enif_alloc(sizeof(VECTOR));
  if(__builtin_expect((*value == NULL), 0)) {
    enif_free(v);
    return 0;
  }
  for(int i = 0; i < length; i++) {
    if(__builtin_expect((enif_get_uint64(env, head, &v[i]) == 0), 0)) {
      enif_free(v);
      return 0;
    }
    if(__builtin_expect((enif_get_list_cell(env, tail, &head, &tail) == 0), 0)) {
      if(i + 1 < length) {
        enif_free(v);
        return 0;
      }
    }
  }
  (*value)->size = length;
  (*value)->value = v;
  return 1;
}


ERL_NIF_TERM enif_make_big_num(ErlNifEnv *env, const unsigned is_negative, const VECTOR *value)
{
	ERL_NIF_TERM term_is_negative = enif_make_uint(env, is_negative);
	ERL_NIF_TERM *term_array = enif_alloc(sizeof(ERL_NIF_TERM) * value->size);
	for(int i = 0; i < value->size; i++) {
		term_array[i] = enif_make_uint64(env, value->value[i]);
	}
	ERL_NIF_TERM term_list = enif_make_list_from_array(env, term_array, value->size);
	enif_free(term_array);
	return enif_make_tuple2(env, term_is_negative, term_list);
}
```

`enif_get_big_num` は型が厳密に合っていない場合などには 0 を返すようにしています。また型安全になるように最善の努力をしています。

分岐予測を最適化するためのコードが入っているので，読みにくいですね。
スッキリさせるとこんな感じです。

```c
#include "erl_nif.h"

typedef struct vector {
  int size;
  unsigned long *value;
} VECTOR;

int enif_get_big_num(ErlNifEnv *env, ERL_NIF_TERM term, unsigned *is_negative, VECTOR **value)
{
  int tuple_arity;
  const ERL_NIF_TERM *tuple;
  if(!enif_get_tuple(env, term, &tuple_arity, &tuple
    || tuple_arity != 2
    || !enif_get_uint(env, tuple[0], is_negative)) {
    return 0;
  }
  unsigned int length;
  if(!enif_get_list_length(env, tuple[1], &length)
     || length == 0) {
    return 0;
  }
  ERL_NIF_TERM head, tail;
  if(!enif_get_list_cell(env, tuple[1], &head, &tail)) {
    return 0;
  }
  unsigned long *v = (unsigned long *)enif_alloc(sizeof(unsigned long) * length);
  if(v == NULL) {
    return 0;
  }
  *value = (VECTOR *)enif_alloc(sizeof(VECTOR));
  if(*value == NULL) {
    enif_free(v);
    return 0;
  }
  for(int i = 0; i < length; i++) {
    if(!enif_get_uint64(env, head, &v[i])) {
      enif_free(v);
      return 0;
    }
    if(!enif_get_list_cell(env, tail, &head, &tail)) {
      if(i + 1 < length) {
        enif_free(v);
        return 0;
      }
    }
  }
  (*value)->size = length;
  (*value)->value = v;
  return 1;
}

ERL_NIF_TERM enif_make_big_num(ErlNifEnv *env, const unsigned is_negative, const VECTOR *value)
{
	ERL_NIF_TERM term_is_negative = enif_make_uint(env, is_negative);
	ERL_NIF_TERM *term_array = enif_alloc(sizeof(ERL_NIF_TERM) * value->size);
	for(int i = 0; i < value->size; i++) {
		term_array[i] = enif_make_uint64(env, value->value[i]);
	}
	ERL_NIF_TERM term_list = enif_make_list_from_array(env, term_array, value->size);
	enif_free(term_array);
	return enif_make_tuple2(env, term_is_negative, term_list);
}
```

元のコードで順に説明していきます。

```c
int enif_get_big_num(ErlNifEnv *env, ERL_NIF_TERM term, unsigned *is_negative, VECTOR **value)
```

NIFの関数と同じように名前付けと引数・戻り値の型を決めてみました。

呼び出す時にはこんな感じにします。

```c
static
ERL_NIF_TERM func(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
  unsinged int a_neg;
  VECTOR *a;
  if(enif_get_big_num(env, argv[0], &a_neg, &a) {
    // a_neg, a に第1引数を読み取った結果が入っている
  }
```

```c
  int tuple_arity;
  const ERL_NIF_TERM *tuple;
  if(__builtin_expect((enif_get_tuple(env, term, &tuple_arity, &tuple) == 0), 0)) {
    return 0;
  }
  if(__builtin_expect(tuple_arity != 2, 0)) {
    return 0;
  }
```

`enif_get_tuple` はタプルを読み込みます。`tuple_arity`にはタプルの引数の数，`tuple`にはERL_NIF_TERMの配列として読み込まれたタプルが入ります。タプル型でなかったり，`tuple_arity`が2でなかった場合には 0 を返します。


```c
  if(__builtin_expect((enif_get_uint(env, tuple[0], is_negative) == 0), 0)) {
    return 0;
  }
```

タプルの第1引数である`is_negative`を読み込みます。真偽値ではなく整数値にしたので，扱いがシンプルになります。

```c
  unsigned int length;
  if(__builtin_expect((enif_get_list_length(env, tuple[1], &length) == 0), 0)) {
    return 0;
  }
  if(__builtin_expect((length == 0), 0)) {
    return 0;
  }
```

タプルの第2引数であるリストの長さを読み込んで`length`に入れます。リストではなかったり`length`が0のときには0を返します。

このAPIをみると，リストの長さはUINT32の範囲までですね。これを超える場合は，後述する`enif_get_list_cell`を使って一つずつ取り出すしかないですね。あとで対策を考えよう。

```c
  ERL_NIF_TERM head, tail;
  if(__builtin_expect((enif_get_list_cell(env, tuple[1], &head, &tail) == 0), 0)) {
    return 0;
  }
```

Elixirでいうところの `hd` と `tl` の取り出しです。`head` に `hd`，`tail`に`tl`が入ります。 

```c
  unsigned long *v = (unsigned long *)enif_alloc(sizeof(unsigned long) * length);
  if(__builtin_expect((v == NULL), 0)) {
    return 0;
  }
```

配列のメモリを確保します。`enif_alloc`は NIF で用意されているメモリ確保の関数です。ErlangVMのメモリアロケーターを利用するので，`malloc`より高速とのことです。メモリの解放は `enif_free`です。

```c
  *value = (VECTOR *)enif_alloc(sizeof(VECTOR));
  if(__builtin_expect((*value == NULL), 0)) {
    enif_free(v);
    return 0;
  }
```

構造体のメモリを確保します。失敗した時にはすでに確保している配列のメモリも解放します。

```c
  for(int i = 0; i < length; i++) {
    if(__builtin_expect((enif_get_uint64(env, head, &v[i]) == 0), 0)) {
      enif_free(v);
      return 0;
    }
    if(__builtin_expect((enif_get_list_cell(env, tail, &head, &tail) == 0), 0)) {
      if(i + 1 < length) {
        enif_free(v);
        return 0;
      }
    }
  }
```

リストから配列への読み込みです。`tail`を次々読み込むのはリストの読み込みの定番です。終了処理がちょっとトリッキーですが，i == length - 1 のときに `enif_get_list_cell` を実行すると 0 が返ってきてしまうためです。


```c
  (*value)->size = length;
  (*value)->value = v;
  return 1;
}
```

結果として返却する構造体にデータを格納して 1 を返します。


```c
ERL_NIF_TERM enif_make_big_num(ErlNifEnv *env, const unsigned is_negative, const VECTOR *value)
{
	ERL_NIF_TERM term_is_negative = enif_make_uint(env, is_negative);
	ERL_NIF_TERM *term_array = enif_alloc(sizeof(ERL_NIF_TERM) * value->size);
	for(int i = 0; i < value->size; i++) {
		term_array[i] = enif_make_uint64(env, value->value[i]);
	}
	ERL_NIF_TERM term_list = enif_make_list_from_array(env, term_array, value->size);
	enif_free(term_array);
	return enif_make_tuple2(env, term_is_negative, term_list);
}
```

`enif_get_big_num` に比べると `enif_make_big_num` はごくシンプルです。ただし，`value` や `value->value` が `NULL` だったり `is_negative` や `value->size` が不正な値だったり，`term_array` で得た値が `NULL` だったり，といった場合のチェックはサボっています。要改善。

```c
	ERL_NIF_TERM term_is_negative = enif_make_uint(env, is_negative);
```

`is_negative` を変換しています。

```c
	ERL_NIF_TERM *term_array = enif_alloc(sizeof(ERL_NIF_TERM) * value->size);
	for(int i = 0; i < value->size; i++) {
		term_array[i] = enif_make_uint64(env, value->value[i]);
	}
	ERL_NIF_TERM term_list = enif_make_list_from_array(env, term_array, value->size);
	enif_free(term_array);
```

`unsined long` の配列をリストに変換しています。最初，`enif_make_list_from_array` の `term_array` と書いているところに直に `unsigned long` の配列を突っ込んで，セグメンテーションフォールトを発生させてしまいました。ただしくは `ERL_NIF_TERM` の配列を与えてやる必要があります。


```c
	return enif_make_tuple2(env, term_is_negative, term_list);
```

まとめてタプルにして返します。

次回は[「ZEAM開発ログ v.0.4.14.2 Elixir / NIF 間で BigNum をやりとりする (Elixir での使用例)」](https://qiita.com/zacky1972/items/d96bf0fc907456459760)をお送りします。お楽しみに！