LoginSignup
2
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-09-30

ZACKYこと山崎進です。

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

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

書いたコード

#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 を返すようにしています。また型安全になるように最善の努力をしています。

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

#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);
}

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

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

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

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

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引数を読み取った結果が入っている
  }
  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 を返します。

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

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

  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を使って一つずつ取り出すしかないですね。あとで対策を考えよう。

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

Elixirでいうところの hdtl の取り出しです。headhdtailtlが入ります。

  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です。

  *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;
      }
    }
  }

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

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

結果として返却する構造体にデータを格納して 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 に比べると enif_make_big_num はごくシンプルです。ただし,valuevalue->valueNULL だったり is_negativevalue->size が不正な値だったり,term_array で得た値が NULL だったり,といった場合のチェックはサボっています。要改善。

    ERL_NIF_TERM term_is_negative = enif_make_uint(env, is_negative);

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);

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

    return enif_make_tuple2(env, term_is_negative, term_list);

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

次回は「ZEAM開発ログ v.0.4.14.2 Elixir / NIF 間で BigNum をやりとりする (Elixir での使用例)」をお送りします。お楽しみに!

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