「Pelemay」EnumコードのElixirネイティブコンパイラをWindowsで試す

[fukuoka.ex](https://fukuokaex.connpass.com/)のpiacereです
ご覧いただいて、ありがとうございます :bow:

fukuoka.ex発足から半年後にスタートし、今月、満を持してリリースされた、EnumをSIMD命令として生成する（将来的にはGPUをドライブするコードも生成予定）Elixirネイティブコードコンパイラ[「Pelemay」](https://github.com/zeam-vm/pelemay)をWindowsで試します

1年弱くらい前、この「Enumのみをネイティブコードコンパイルする」「Enum以外は、通常通り、ErlangVMを呼び出す」という棲み分けのコンセプトを @zacky1972 さんとサンドイッチ屋で話し、半年前にテキサスで開催されたLonestar ElixirConf 2019でデビュー＆開発スケジュールを公開し、先月のElixirConf US 2019で遂にお披露目となりました

[<b>Lonestar ElixirConf 2019</b> - Hastega: Challenge for GPGPU on Elixir](https://www.youtube.com/watch?v=lypqlGlK1So)
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/6f6e5c1d-b882-4feb-a986-7a21dd41d629.png)](https://www.youtube.com/watch?v=lypqlGlK1So)

[<b>ElixirConf US 2019</b> - Return of Wabi-Sabi: Hastega Will Bring More and More Computational Power to Elixir](https://www.youtube.com/watch?v=uCkPyfFhPxI)
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/87519228-1e56-2433-4060-08a4ca57a5b3.png)](https://www.youtube.com/watch?v=uCkPyfFhPxI)

まだ数値の四則計算しかできないですが、先週まで、[ElixirConf JP 2019](https://fukuokaex.fun/)の開催で一杯だった私もようやく落ち着いたので、Pelemayをキャッチアップして、データサイエンスの処理の大半を占める「String.replace」が利用可能なエディションに改善するプルリクとか出したいと思います（Issueは下記の通り、既に出してます）
https://github.com/zeam-vm/pelemay/issues/49

# 本コラムの検証環境、事前構築のコマンド

本コラムは、以下環境で検証しています（Windowsで実施していますが、Linuxやmacでも動作する想定です）

- Windows 10
- Elixir 1.9.1　　　　 ※<b>[最新版のインストール手順はコチラ](https://qiita.com/piacere_ex/items/6714e1440e3f25fb46a1#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99elixir%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)</b>

# Pelemayを使うPJを作成

実際にPelemayを使うPJを作る過程で説明します

[こちらのチュートリアル](https://qiita.com/piacere_ex/items/6714e1440e3f25fb46a1)を参考に、予めElixirはインストールしておいてください

なお、Pelemayは、Elixir 1.9以上しかサポートしないため、1.8以下の方は、Elixirのアップグレードをお忘れなく

適当なフォルダ配下で、Elixir PJを作成します

```
mix new pelemay_sample
cd pelemay_sample
```

mix.exsのdepsに、Pelemayを追加します

[PelemayのGithubに記載ある通り](https://github.com/zeam-vm/pelemay)、バージョン指定では無く、masterから拾います

```elixir:mix.exs
defmodule PelemaySample.MixProject do
…
  defp deps do
    [
      { :pelemay, git: "https://github.com/zeam-vm/pelemay.git", branch: "master" },
    ]
…
```

ライブラリ取得します

```
mix deps.get
```

まず、ライブラリ配下のPelemayのテストを実行します

```
cd deps/pelemay
mix test
...................

Finished in 0.2 seconds
8 doctests, 11 tests, 0 failures

Randomized with seed 142000
```

計19件のテストが通りました…コード内の「## Examples」の数よりも、doctestが少ない気がする…

ひとまず置いといて（笑）、先に進みます

# Pelemayサンプルコードを実行する

[PelemayのGithub](https://github.com/zeam-vm/pelemay)に書いてあるサンプルコードを、作ったPJのモジュールに書いてみます

```elixir:lib/pelemay_sample.ex
defmodule PelemaySample do
  require Pelemay
  import Pelemay

  defpelemay do
    def map_square (list) do
      list
      |> Enum.map(& &1 * &1)
    end
  end
end
```

コンパイルしてみましょう

```
cd ../..
mix compile
==> pelemay
Compiling 8 files (.ex)
Generated pelemay app
==> pelemay_sample
Compiling 1 file (.ex)
clang -Ofast -g -ansi -pedantic -femit-all-decls -Ic:/Tools/erl9.0/erts-9.0/include -I/usr/local/include -I/usr/include -L/usr/local/lib -L/usr/lib -std=c11 -Wno-unused-function -shared -o c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.so c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c
In file included from c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c:3:
In file included from c:/Tools/erl9.0/erts-9.0/include\erl_nif.h:31:
c:/Tools/erl9.0/erts-9.0/include/erl_drv_nif.h:105:18: warning: extension used [-Wlanguage-extension-token]
typedef unsigned __int64 ErlNapiUInt64;
                 ^
c:/Tools/erl9.0/erts-9.0/include/erl_drv_nif.h:106:16: warning: extension used [-Wlanguage-extension-token]
typedef signed __int64 ErlNapiSInt64;
               ^
c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c:106:50: warning: incompatible pointer types passing 'long *' to parameter of type 'ErlNifSInt64 *'
      (aka 'long long *') [-Wincompatible-pointer-types]
          tmp_r[c] = enif_get_int64(env, tmp[c], &t[i++]);
                                                 ^~~~~~~
c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c:147:54: warning: incompatible pointer types passing 'long *' to parameter of type 'ErlNifSInt64 *'
      (aka 'long long *') [-Wincompatible-pointer-types]
      tmp_r[count] = enif_get_int64(env, tmp[count], &t[i + count]);
                                                     ^~~~~~~~~~~~~
c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c:274:55: warning: incompatible pointer types passing 'long *' to parameter of type 'ErlNifSInt64 *'
      (aka 'long long *') [-Wincompatible-pointer-types]
      if (__builtin_expect((enif_get_int64(env, head, &tmp) == fail), false)) {
                                                      ^~~~
5 warnings generated.

== Compilation error in file lib/pelemay_sample.ex ==
** (ArgumentError) argument error
    (stdlib) :io.put_chars(:standard_io, :unicode, [<<32, 32, 32, 131, 137, 131, 67, 131, 117, 131, 137, 131, 138, 32, 99, 58, 47, 112, 105, 97, 99, 101, 114, 101, 47, 99, 111, 100, 101, 47, 112, 101, 108, 101, 109, 97, 121, 95, 115, 97, 109, 112, 108, 101, 47, 95, 98, 117, 105, ...>>, 10])
    lib/pelemay.ex:53: Pelemay.pelemaystub/2
    expanding macro: Pelemay.defpelemay/1
    lib/pelemay_sample.ex:5: PelemaySample (module)
```

おや？なんか盛大にエラーが出てますね…

どうやら、IO.putsのネイティブコード（:io.put_chars）のUnicode周りで問題ありの模様

ん？これはWindowsのターミナルのせいか？

```
chcp 65001
```

改めてコンパイルします

```
mix compile
Compiling 1 file (.ex)
clang -Ofast -g -ansi -pedantic -femit-all-decls -Ic:/Tools/erl9.0/erts-9.0/include -I/usr/local/include -I/usr/include -L/usr/local/lib -L/usr/lib -std=c11 -Wno-unused-function -shared -o c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.so c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c
In file included from c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c:3:
In file included from c:/Tools/erl9.0/erts-9.0/include\erl_nif.h:31:
c:/Tools/erl9.0/erts-9.0/include/erl_drv_nif.h:105:18: warning: extension used [-Wlanguage-extension-token]
typedef unsigned __int64 ErlNapiUInt64;
                 ^
c:/Tools/erl9.0/erts-9.0/include/erl_drv_nif.h:106:16: warning: extension used [-Wlanguage-extension-token]
typedef signed __int64 ErlNapiSInt64;
               ^
c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c:106:50: warning: incompatible pointer types passing 'long *' to parameter of type 'ErlNifSInt64 *'
      (aka 'long long *') [-Wincompatible-pointer-types]
          tmp_r[c] = enif_get_int64(env, tmp[c], &t[i++]);
                                                 ^~~~~~~
c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c:147:54: warning: incompatible pointer types passing 'long *' to parameter of type 'ErlNifSInt64 *'
      (aka 'long long *') [-Wincompatible-pointer-types]
      tmp_r[count] = enif_get_int64(env, tmp[count], &t[i + count]);
                                                     ^~~~~~~~~~~~~
c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c:274:55: warning: incompatible pointer types passing 'long *' to parameter of type 'ErlNifSInt64 *'
      (aka 'long long *') [-Wincompatible-pointer-types]
      if (__builtin_expect((enif_get_int64(env, head, &tmp) == fail), false)) {
                                                      ^~~~
5 warnings generated.

== Compilation error in file lib/pelemay_sample.ex ==
** (ArgumentError) argument error
    (stdlib) :io.put_chars(:standard_io, :unicode, [<<32, 32, 32, 131, 137, 131, 67, 131, 117, 131, 137, 131, 138, 32, 99, 58, 47, 112, 105, 97, 99, 101, 114, 101, 47, 99, 111, 100, 101, 47, 112, 101, 108, 101, 109, 97, 121, 95, 115, 97, 109, 112, 108, 101, 47, 95, 98, 117, 105, ...>>, 10])
    lib/pelemay.ex:53: Pelemay.pelemaystub/2
    expanding macro: Pelemay.defpelemay/1
    lib/pelemay_sample.ex:5: PelemaySample (module)
```

ううむ、同じ箇所のエラーは解消しない…

ソースコードの5行目を見ると、defpelemayマクロで、エラーメッセージを見る限り、その中のpelemaystubでIO.putsしててコケているようなので、元のソースコードを見てみます

```elixir:deps/pelemay/lib/pelemay.ex
defmodule Pelemay do
…
  defp pelemaystub(ret, module) do
    Generator.generate(module)
    ret
  end
…
```

```elixir:deps/pelemay/lib/pelemay/generator.ex
defmodule Pelemay.Generator do
…
  def generate(module) do
    Application.app_dir(:pelemay, "priv")
    |> File.mkdir()

    Interface.generate(module)
    Native.generate(module)
    Builder.generate(module)
  end
…
```

エラーメッセージから追えるのは、ここまでなので、grepでIO.putsを追うと、Builderモジュールが該当するようなので見てみます

```elixir:
defmodule Pelemay.Generator.Builder do
…
  def generate(module) do
    …
    options =
      cflags ++ ["-shared"] ++ ldflags ++ ["-o", Generator.libso(module), Generator.libc(module)]

    IO.puts(Enum.join([@cc] ++ options, " "))

    {result, 0} = System.cmd(@cc, options)

    IO.puts(result)
  end
…
```

この2つのいずれか、もしくは両方と思われるので、コメントアウトしてみます

```elixir:
defmodule Pelemay.Generator.Builder do
…
  def generate(module) do
    …
    options =
      cflags ++ ["-shared"] ++ ldflags ++ ["-o", Generator.libso(module), Generator.libc(module)]

#    IO.puts(Enum.join([@cc] ++ options, " "))

    {result, 0} = System.cmd(@cc, options)

#    IO.puts(result)
  end
…
```

depsを再コンパイルします

```
mix deps.compile
```

本体をコンパイルします

```
mix compile
Compiling 1 file (.ex)
In file included from c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c:3:
In file included from c:/Tools/erl9.0/erts-9.0/include\erl_nif.h:31:
c:/Tools/erl9.0/erts-9.0/include/erl_drv_nif.h:105:18: warning: extension used [-Wlanguage-extension-token]
typedef unsigned __int64 ErlNapiUInt64;
                 ^
c:/Tools/erl9.0/erts-9.0/include/erl_drv_nif.h:106:16: warning: extension used [-Wlanguage-extension-token]
typedef signed __int64 ErlNapiSInt64;
               ^
c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c:106:50: warning: incompatible pointer types passing 'long *' to parameter of type 'ErlNifSInt64 *'
      (aka 'long long *') [-Wincompatible-pointer-types]
          tmp_r[c] = enif_get_int64(env, tmp[c], &t[i++]);
                                                 ^~~~~~~
c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c:147:54: warning: incompatible pointer types passing 'long *' to parameter of type 'ErlNifSInt64 *'
      (aka 'long long *') [-Wincompatible-pointer-types]
      tmp_r[count] = enif_get_int64(env, tmp[count], &t[i + count]);
                                                     ^~~~~~~~~~~~~
c:/piacere/code/pelemay_sample/_build/dev/lib/pelemay/priv/libnifelixirpelemaysample.c:274:55: warning: incompatible pointer types passing 'long *' to parameter of type 'ErlNifSInt64 *'
      (aka 'long long *') [-Wincompatible-pointer-types]
      if (__builtin_expect((enif_get_int64(env, head, &tmp) == fail), false)) {
                                                      ^~~~
5 warnings generated.
Generated pelemay_sample app
```

ひとまずコンパイルは通ったので、実行しています

```
iex -S mix
iex> PelemaySample.map_square [ 1, 2, 3 ]
** (UndefinedFunctionError) function PelemayNifElixirPelemaySample.map_mult/1 is undefined (module PelemayNifElixirPelemaySample is not available)
    (pelemay_sample) PelemayNifElixirPelemaySample.map_mult([1, 2, 3])
iex(1)>
23:18:07.913 [warn]  The on_load function for module Elixir.PelemayNifElixirPelemaySample returned:
{:error, {:load_failed, [70, 97, 105, 108, 101, 100, ...]}}
```

うーむ、今度は、PelemayNifElixirPelemaySample.map_multが存在しない…

findすると、BEAM自体は存在しているので、関数が未定義なんでしょうね…

ここから先は、ネイティブコード生成周り含めた深追いする必要もありそうなので、今回は一旦ここまでにしておきましょう

# 2019/9/27続報

いったん暫定で動きました

``_build/dev/lib/pelemay/priv/``配下にある、``libnifelixirpelemaysample.so``というファイルを、``libnifelixirpelemaysample.dll``にリネームするだけです

リネーム後に動かすと、こうなります

```
iex -S mix
iex> PelemaySample.map_square [ 1, 2, 3 ]
[1, 4, 9]
```

Pelemayの、Windowsビルド時のLLVMオプション指定を修正ですかねー

# 終わり

<strike>ひとまず、WindowsでのPelemay初体験は、ランタイムエラーで一時断念 … という結果でした</strike>

<strike>macでは、特に滞り無く、IO.putsでのエラーも出ず、実行できているようなので、きっとWindows固有の問題でしょう</strike>

<strike>@zacky1972 さんや、 @hisaway さんと、お話しながら、解決を進めたいと思います</strike>

（2019/9/27続報）
ひとまず、WindowsでのPelemay初体験は、暫定的ではありますが、うまくいきました

下記Bug reportを挙げました
https://github.com/zeam-vm/pelemay/issues/52

あと、コラム中で挙げたdoctest不具合についても、下記Bug reportが作られていました
https://github.com/zeam-vm/pelemay/issues/50
https://github.com/zeam-vm/pelemay/issues/51

いったんは、Pelemayを手元のWindowsで動かすことができたので、今度は、データサイエンスの処理の大半を占める「String.replace」を利用可能にしていきたいと思います

# p.s.「いいね」よろしくお願いします

ページ左上の ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/4d515047-cc48-382e-c2b1-3ad0cc50dbbf.png) や ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/a4e3da58-70a3-4197-95a2-6a6906650d01.png) のクリックを、どうぞよろしくお願いします:bow:
ここの数字が増えると、書き手としては「ウケている」という感覚が得られ、連載を更に進化させていくモチベーションになりますので、もっとElixirネタを見たいというあなた、私達と一緒に盛り上げてください！:tada: