5
1

More than 5 years have passed since last update.

ZEAM開発ログ v.0.4.6 OKを使ってNIFのエラー処理をエレガントに書いてみる

Last updated at Posted at 2018-09-22

ZACKYこと山崎進です。

Tsuyoshi Yamaguchi @Tsuyoshi84 さんが書いた「OKを使ってElixirの :ok, :error タプルをエレガントに処理」に刺激を受けて,さっそく型多相・型安全なNIFコードにOKを使ってみました。

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

さっそくコードを紹介

全体はこちら(GitHub)

mix.exs

 defmodule NifLlvm.MixProject do
  use Mix.Project

  def project do
    [
      app: :nif_llvm,
      version: "0.1.0",
      elixir: "~> 1.6",
      compilers: [:nif_llvm] ++ Mix.compilers,
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      {:asm, "~> 0.0.4"},
      {:ok, "~> 2.0"}
    ]
  end
end

defmodule Mix.Tasks.Compile.NifLlvm do
  def run(_) do
    if match? {:win32, _}, :os.type do
      # libpostal does not support Windows unfortunately.
      IO.warn("Windows is not supported.")
      exit(1)
    else
      File.mkdir_p("priv")
      {result, _error_code} = System.cmd("make", ["priv/libnifllvm.so"], stderr_to_stdout: true)
      IO.binwrite result
    end
    :ok
  end
end

defp deps{:ok, "~> 2.0"}を追加しています。

lib/nif_llvm.ex

defmodule NifLlvm do
  require OK
  require Asm
  import Asm

  @on_load :load_nifs

  def load_nifs do
    :erlang.load_nif('./priv/libnifllvm', 0)
  end

  @moduledoc """
  Documentation for NifLlvm.
  """

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

  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_int64(a) and is_float(b) -> asm_1_nif_if(a, b)
        {a, b} when is_float(a) and is_int64(b) -> asm_1_nif_fi(a, b)
        {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_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_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_ff(a, b) when is_float(a) and is_float(b), do: raise "NIF asm_1_nif_ff/2 not implemented"

end

かなりスッキリ書けましたね。

native/lib.c

#include <limits.h>
#include "erl_nif.h"
#include "loader.c"

static ERL_NIF_TERM arithmetic_error;
static ERL_NIF_TERM ok_atom;
static ERL_NIF_TERM error_atom;
static ERL_NIF_TERM arithmetic_error_atom;

static void init_nif_llvm(ErlNifEnv *env)
{
  arithmetic_error = enif_raise_exception(env, enif_make_atom(env, "ArithmeticError"));
  ok_atom = enif_make_atom(env, "ok");
  error_atom = enif_make_atom(env, "error");
  arithmetic_error_atom = enif_make_atom(env, "arithmetic_error");
}

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_tuple2(env, ok_atom, enif_make_int64(env, result));
error:
  return arithmetic_error;
error2:
  return enif_make_tuple2(env, error_atom, arithmetic_error_atom);
}

static
ERL_NIF_TERM asm_1_nif_if(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
  long a;
  double b;
  if(__builtin_expect((enif_get_int64(env, argv[0], &a) == 0), 0)) {
    goto error;
  }
  if(__builtin_expect((enif_get_double(env, argv[1], &b) == 0), 0)) {
    goto error;
  }
  double result = ((double)a) + b;
  return enif_make_tuple2(env, ok_atom, enif_make_double(env, result));
error:
  return arithmetic_error;
}

static
ERL_NIF_TERM asm_1_nif_fi(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
  double a;
  long b;
  if(__builtin_expect((enif_get_double(env, argv[0], &a) == 0), 0)) {
    goto error;
  }
  if(__builtin_expect((enif_get_int64(env, argv[1], &b) == 0), 0)) {
    goto error;
  }
  double result = a + ((double) b);
  return enif_make_tuple2(env, ok_atom, enif_make_double(env, result));
error:
  return arithmetic_error;
}

static
ERL_NIF_TERM asm_1_nif_ff(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
  double a, b;
  if(__builtin_expect((enif_get_double(env, argv[0], &a) == 0), 0)) {
    goto error;
  }
  if(__builtin_expect((enif_get_double(env, argv[1], &b) == 0), 0)) {
    goto error;
  }
  double result = a + b;
  return enif_make_tuple2(env, ok_atom, enif_make_double(env, result));
error:
  return arithmetic_error;
}

static
ErlNifFunc nif_funcs[] =
{
  // {erl_function_name, erl_function_arity, c_function}
  {"asm_1_nif_ii", 2, asm_1_nif_ii},
  {"asm_1_nif_if", 2, asm_1_nif_if},
  {"asm_1_nif_fi", 2, asm_1_nif_fi},
  {"asm_1_nif_ff", 2, asm_1_nif_ff}
};

ERL_NIF_INIT(Elixir.NifLlvm, nif_funcs, &load, &reload, &upgrade, &unload)

NIFコードの変更点は,:ok, :error を第1引数に,計算結果もしくは :arithmetic_error を第2引数にした2引数のタプルを返すようにした点です。

次回は「ZEAM開発ログ v.0.4.7 BigNum をどのようにNIFで扱うか考える」です。お楽しみに!

: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

5
1
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
5
1