LoginSignup
5
2

More than 5 years have passed since last update.

ZEAM開発ログ v.0.4.13 マクロを使って micro Elixir のフロントエンドを作ってみる (野望編)

Last updated at Posted at 2018-09-29

ZACKYこと山崎進です。

fukuoka.ex もくもく会明けのプログラミングフィーバーは続きます。今回は NIF を Elixir の中で記述する micro Elixir の仕様について,プロトタイピングしながら検討しました。

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

当初構想

最初はこんなのがいいかなと思っていました。

たとえば

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

とかに変換し,addmul の情報を集めて 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_nifnif_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 側)」をお送りします。お楽しみに!

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