ZACKYこと山崎進です。
fukuoka.ex もくもく会明けのプログラミングフィーバーは続きます。今回は NIF を Elixir の中で記述する micro Elixir の仕様について,プロトタイピングしながら検討しました。
当初構想
最初はこんなのがいいかなと思っていました。
たとえば
def_nif_module Foo do
def_nif add, do: asm add a, b
def_nif mul, do: asm mul a, b
end
とか書かれていたら,
defmodule Foo do
require Asm
import Asm
def_nif add, do: asm add a, b
def_nif mul, do: asm mul a, b
generate_nif
end
とかに変換し,add
と mul
の情報を集めて generate_nif
で NIFモジュールのネイティブコードを生成する,というようなことを考えていました。
当初構想の問題点
def_nif_module
のマクロ定義をいつどこで読むのよ? という問題があり,あえなくボツに。
第2案
既存の文法との整合性も加味して,こんな感じの文法を考えました。
defmodule Foo do
require Asm
import Asm
nif_module do
def_nif add, do: asm add a, b
def_nif mul, do: asm mul a, b
end
end
nif_module
のブロックの最後で NIFモジュールのネイティブコードを生成するという案です。
第2案プロトタイプ
そこで実験してみました。
(「ZEAM開発ログ v.0.4.11 マクロを使って micro Elixir のフロントエンドを作ってみる (黎明編)」との差分)
def get_env(atom) do
atom
|> Atom.to_string
|> System.get_env
end
def put_env(atom, value) do
atom
|> Atom.to_string
|> System.put_env(value)
end
@doc """
def_nif defines a NIF that includes micro Elixir code.
"""
defmacro def_nif func, do_clause do
put_env(__ENV__.module, get_name_arity(func))
quote do
def unquote(func), unquote(do_clause)
def unquote(when_and_int64(func, __ENV__.module)), unquote(do_clause)
def unquote(when_and_uint64(func, __ENV__.module)), unquote(do_clause)
def unquote(when_and_float(func, __ENV__.module)), unquote(do_clause)
end
end
@doc """
"""
defmacro nif_module do_clause do
ret = quote do: unquote(do_clause)
IO.puts get_env(__ENV__.module)
ret
end
もし狙い通りに行けば,下記のコードを実行すると,add/2
を表示してくれるはずです。
defmodule Foo do
nif_module do
def_nif add(a, b), do: asm add a, b
end
end
しかし,何も表示してくれませんでした。
調べてみるとどうやら,IO.puts get_env(__ENV__.module)
は nif_module
が定義されている文の位置で実行が終わってしまい,def_nif add(a, b), do: asm add a, b
よりも前に実行されているようなのです。
第3案
generate_nif
を nif_module
の最後に書くことにしました。
defmodule Foo do
require Asm
import Asm
nif_module do
def_nif add, do: asm add a, b
def_nif mul, do: asm mul a, b
generate_nif
end
end
第3案プロトタイプ
実験してみました。第2案からの差分です。
defmacro nif_module do_clause do
quote do: unquote(do_clause)
end
defmacro generate_nif do
IO.puts get_env(__ENV__.module)
end
もし狙い通りにいけば下記のコードでadd/1
と表示してくれるはずです。
defmodule Foo do
nif_module do
def_nif add(a, b), do: asm add a, b
generate_nif
end
end
果たして!
$ iex -S mix
Erlang/OTP 21 [erts-10.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [sharing-preserving]
Compiling 1 file (.ex)
Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> require Asm
Asm
iex(2)> import Asm
Asm
iex(3)> defmodule Foo do
...(3)> nif_module do
...(3)> def_nif add(a,b), do: asm add a,b
...(3)> generate_nif
...(3)> end
...(3)> end
add/2
{:module, Foo,
<<70, 79, 82, 49, 0, 0, 6, 0, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 144, 0,
0, 0, 17, 10, 69, 108, 105, 120, 105, 114, 46, 70, 111, 111, 8, 95, 95, 105,
110, 102, 111, 95, 95, 7, 99, 111, ...>>, [do: :ok]}
iex(4)>
うまくいきました!
まとめ
そういうわけで,インラインアセンブラの文法はこんな感じにします。
defmodule Foo do
require Asm
import Asm
nif_module do
def_nif add, do: asm add a, b
def_nif mul, do: asm mul a, b
generate_nif
end
end
次回は「ZEAM開発ログ v.0.4.14 Elixir / NIF 間で BigNum をやりとりする (Elixir 側)」をお送りします。お楽しみに!