ZEAM開発ログ v.0.4.3 型多相かつ型安全なNIFでオーバーフローを検出する


はじめに

ZACKYこと山崎進です。

整数の加減乗算のオーバーフローを検出する方法がわかりましたので,報告します。

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

整数のオーバーフローを検出する方法

__builtin_*_overflow という関数を使います。

符号付き64ビット整数だったら,__builtin_saddl_overflow になります。

ドキュメントはこちら。

Clang Language Extensions — Clang 4 documentation

C言語のコードはこんな感じです。

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_int64(env, result);
error:
    return arithmetic_error;
error2:
    return error_atom;
}

__built_in_expectを使うと分岐予測を考慮して最適化してくれます。詳しくは「ZEAM開発ログ v.0.4.2 型多相かつ型安全なNIFのコードを分岐予測の観点で最適化」を参照ください。

実行結果

  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(@max_int, 0)
    IO.puts asm_1(@min_int, 0)
    try do
      IO.puts asm_1(@max_int, 1)
    rescue
      error in [ArithmeticError] -> IO.puts "it needs BigNum!: #{Exception.message(error)}"
    end
    try do
      IO.puts asm_1(@max_int + 1, 1)
    rescue
      error in [ArithmeticError] -> IO.puts "it needs BigNum!: #{Exception.message(error)}"
    end
  end

実行してみると,オーバーフローで例外が発生します。

$ mix run -e "NifLlvm.main"
make: `priv/libnifllvm.so' is up to date.
3
3.0
3.0
3.0
9223372036854775807
-9223372036854775808
it needs BigNum!: bad argument in arithmetic expression
it needs BigNum!: bad argument in arithmetic expression

アセンブリコードを見ると意図通りオーバーフローのジャンプ命令が生成されています。

    ##DEBUG_VALUE: asm_1_nif_ii:a <- %rsi
    .loc    3 26 22 is_stmt 0       ## native/lib.c:26:22
    addq    -24(%rbp), %rsi
Ltmp27:
    .loc    3 26 5                  ## native/lib.c:26:5
    jo  LBB4_5

LLVMだとこんな感じにコーディングするようです。

  %19 = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %17, i64 %18), !dbg !181
  %20 = extractvalue { i64, i1 } %19, 1, !dbg !181
  call void @llvm.dbg.value(metadata i64 %22, metadata !160, metadata !DIExpression()), !dbg !182
  br i1 %20, label %26, label %21, !dbg !183, !prof !171

ソースコード全体のGitHubレポジトリはこちら

バックエンドでどんなコードを生成したらいいかの見通しは立ったので,次回は「ZEAM開発ログ v.0.4.4 INT64判定をマクロで簡単に判定する」で,フロントエンドについて検討を始めます。お楽しみに!

 お知らせ: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/