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

# はじめに

ZACKYこと山崎進です。

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

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


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

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

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

ドキュメントはこちら。

[Clang Language Extensions &mdash; Clang 4 documentation](http://releases.llvm.org/4.0.0/tools/clang/docs/LanguageExtensions.html)

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

```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のコードを分岐予測の観点で最適化」](https://qiita.com/zacky1972/items/4d72b95be62b446ae4be)を参照ください。

# 実行結果

```elixir
  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
```

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

```bash
$ 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だとこんな感じにコーディングするようです。

```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レポジトリはこちら](https://github.com/zeam-vm/nif_llvm)


バックエンドでどんなコードを生成したらいいかの見通しは立ったので，次回は[「ZEAM開発ログ v.0.4.4 INT64判定をマクロで簡単に判定する」](https://qiita.com/zacky1972/items/878489ce88f380f0ae60)で，フロントエンドについて検討を始めます。お楽しみに！

:stars::stars::stars:<b>　お知らせ：Elixirもくもく会（リモート参加OK、入門トラック有）を9月28日に開催します　</b>:stars::stars::stars:

[<b>「fukuoka.ex#14：Elixir／Phoenixもくもく会～入門もあるよ」</b>](https://fukuokaex.connpass.com/event/100659/)を2018年9月28日金曜日に開催します

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

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

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

お申込みはコチラから
https://fukuokaex.connpass.com/event/100659/
[![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/3f0fbb56-e5e8-2c4d-7c62-1b0b311d6d02.png)](https://fukuokaex.connpass.com/event/100659/)