Julia で WebAssembly

この記事は、[Julia Advent Calendar 2016](http://qiita.com/advent-calendar/2016/julialang)の17日目です。
Julia のコードを WebAssembly にしてみました。

# TL;TR
Julia コードから無理矢理 WebAssemblyを作れたことは作れたけど実用レベルにはほど遠かった。
# はじめに
## 動機
WebAssembly といえば LLVM[^1]、Julia といえば LLVM、ならば Julia コードを WebAssembly 化できるのではないか、という単純な発想です。
## WebAssemblyとは
Webブラウザのためのバイナリコードフォーマットです。
詳しくは、[公式サイト](http://webassembly.org/)を参照してください。とても変化が速いので、公式に勝る情報源はありません。
Qiita にも [WebAssemblyタグ](http://qiita.com/tags/WebAssembly) のついた記事がいくつかあります。
ただし、すでに内容が古くなっている記述も多いため、気をつけてください。
## Julia とは
手軽さと高速な動作を両立した動的プログラミング言語です。
詳しくは、[Julia Advent Calendar 2016](http://qiita.com/advent-calendar/2016/julialang) の1日目の記事でもある@bicycle1885さんの[記事](http://bicycle1885.hatenablog.com/entry/2016/12/01/210618)などを参照してください。
# 準備
## ブラウザ
WebAssembly を動かすためには、以下のブラウザのいずれかが必要です。

+ [Chrome Canary](https://www.google.co.jp/chrome/browser/canary.html)
chrome://flags/#enable-webassembly を有効にして再起動
+ [Firefox Nightly](https://www.mozilla.org/ja/firefox/channel/desktop/)
about:config で about:config#javascript.options.wasm を`true`に設定

通常版のブラウザでも対応していますが、対応している WebAssembly のバージョンが古く、最新のツールで作成した WebAssembly を動かせないことがあります。なので、Canary/Nightlyをお勧めします（Edgeの状況は未確認）。

## ツール
WebAssembly を作るためには、以下の3つのツール群が必要です。

+ [LLVM](http://releases.llvm.org/download.html#3.9.0)
+ [Binaryen](https://github.com/WebAssembly/binaryen) 
+ [WABT](https://github.com/WebAssembly/wabt)

それぞれ、最新のソースをダウンロードしてビルドしてください。
LLVMでは`LLVM_EXPERIMENTAL_TARGETS_TO_BUILD`に`WebAssembly `を設定する必要があります。

ビルドに必要な設定および手順は、以下の記事が参考になります。
[WebAssemblyを使ってみる(C/C++をWebAssemblyに変換してChromeで実行)](http://qiita.com/Hiroki_M/items/89975a9e8205ced3603f)
[WindowsでWebAssemblyの環境を整える](http://qiita.com/Hiroki_M/items/1d2bb08b515582d39f16)
なお、`sexpr-wasm-prototype`は`WABT`に置き換わりました。

## 動作確認環境
OS: Windows 10
Julia: 0.5.0
LLVM: 3.9.0
ブラウザ: Chrome Canary 57.0.2954.1

# Julia コードから作成した WebAssembly をブラウザで実行する

## WebAssembly の作成

WebAssembly を作成するには、以下のような手順を踏みます。

1. Julia (.jl) から LLVM IR (.ll) に変換する
2. LLVM IR (.ll) からアセンブラ (.s) に変換する
3. アセンブラ (.s) から WebAssemblyテキスト形式 (.wast) に変換する
4. WebAssemblyテキスト形式 (.wast) から WebAssemblyバイナリ形式 (.wasm) に変換する

今回 WebAssembly 化する Julia コードは以下のような極めて単純なものです。

```julia:sample.jl
function add(a, b)
    return a + b
end

code_llvm(add, (Int32, Int32))
```
肝になるのは`code_llvm()`関数です。これは、引数に与えられた関数の LLVM IR 出力を得る関数です。通常はデバッグやチューニングを目的として用います。

実は Julia コマンドには LLVM bitcode ファイル（LLVM IR のバイナリフォーマット）を出力するオプション`--output-bc`があります。しかし、このオプションを使って bitcode ファイルを得るためには、Julia のランタイム全体を含んだひとつのファイルとして出力しなければならないのです。

Julia のランタイムには、高速化を目的として外部のC言語ライブラリを呼び出している箇所が含まれます。また WebAssembly は、LLVM IR の全てに対応しているわけではありません。代表的なところでは末尾呼び出し命令が未サポートです。

したがって、WebAssembly化可能な Julia コードとは、すべてが pure Julia で実装されていて、かつサポートされている範囲の LLVM IR にコンパイルされなければなりません。

そのような条件を満たす関数を実装し、かつそこだけの LLVM IR を得るために、`code_llvm()`関数を使います。

さて、ではこの`add()`関数の LLVM IR を出力してみましょう。

```
$ julia sample.jl > sample.ll
```

すると、このような出力が得られました。

```llvm:sample.ll
; Function Attrs: uwtable
define i32 @julia_add_71360(i32, i32) #0 {
top:
  %2 = add i32 %1, %0
  ret i32 %2
}
```

関数名がマングリングされているのがわかりますね。
残念ですがこれは仕方ありません。以降、`add()`関数ではなく`julia_add_71360()`関数と呼ぶことにしましょう。[^2]

次に、これをアセンブラに変換します。LLVM に含まれる`llc`コマンドを使います。

```
$ llc -march=wasm32 -filetype=asm -o sample.s sample.ll
```

出力された`sample.s` を見てみましょう。

```llvm:sample.s
	.text
	.file	"sample.ll"
	.globl	julia_add_71360
	.type	julia_add_71360,@function
julia_add_71360:                        # @julia_add_71360
	.param  	i32, i32
	.result 	i32
# BB#0:                                 # %top
	i32.add 	$push0=, $1, $0
                                        # fallthrough-return: $pop0
	.endfunc
.Lfunc_end0:
	.size	julia_add_71360, .Lfunc_end0-julia_add_71360
```

ふむふむ。では次。いよいよ WebAssembly の世界に足を踏み入れていきます。
Binaryen に含まれる`s2wasm`コマンドを使い、まずは WebAssemblyのテキスト形式ファイルを作成します。

```
$ s2wasm -o sample.wast sample.s
```

```wast:sample.wast
(module
  (table 0 anyfunc)
  (memory $0 1)
  (export "memory" (memory $0))
  (export "julia_add_71360" (func $julia_add_71360))
  (func $julia_add_71360 (param $0 i32) (param $1 i32) (result i32)
    (i32.add
      (get_local $1)
      (get_local $0)
    )
  )
)
```

ご覧の通り、S式です。これなら手で書くのも難しくないですね😏

さて、では最後。本命のバイナリ形式ファイルを作ります。
WABT に含まれる`wast2wasm`コマンドを使いましょう。

```
$ wast2wasm -o sample.wasm sample.wast
```

これでやっと WebAssembly のバイナリファイルを手に入れました。

## ブラウザで実行する

さて、できあがった`.wasm`を実行するためには、JavaScript のコードを書かなければなりません。というのも、現在の WebAssembly は JavaScript の代替として動くものではなく、ES6 のモジュール機構に統合されるものだからです。

また、`.wasm`ファイルは、タグか何かで簡単に読み込めるものではなく、バイト配列としてソースコードに埋め込んだり、xhr/fetch で外部から取得したりする必要があります。そして、それをさらにコンパイルしてインスタンス化しないとモジュールとして使えないのです。面倒くさい！　しかし、将来はもっと簡単に実行できるようになるそうなので、しばらく我慢しましょう。

ということで、モジュールを読み込んで実行するためのスクリプト、挙動を確認するためのロジックを含んだ HTML ファイルを作成します。

```html:sample.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes" />
  </head>
  <body>
    <div>
      <input id="a" type="number" value="1242">+<input id="b" type="number" value="372">=<span id="c"></span>
    </div>
    <button type="button" onclick="calc()">Add!</button>   
  <script>
    function calc() {
      const a = Number(document.getElementById('a').value);
      const b = Number(document.getElementById('b').value);
      fetch('sample.wasm')
      .then(response => response.arrayBuffer())
      .then(bytes => WebAssembly.compile(bytes))
      .then(m     => new WebAssembly.Instance(m))
      .then(instance => document.getElementById('c').innerText = instance.exports.julia_add_71360(a, b));
    }
  </script>
  </body>
</html>
```

大事なところはここです。

```javascript
      fetch('sample.wasm') // 1. 外部からファイルを取得
      .then(response => response.arrayBuffer()) // 2. バイト列の取り出し 
      .then(bytes => WebAssembly.compile(bytes)) // 3. コンパイル
      .then(m     => new WebAssembly.Instance(m)) // 4. インスタンス化
      .then(instance => document.getElementById('c').innerText = instance.exports.julia_add_71360(a, b)); // 5. 呼び出し
```

これでやっと WebAssembly を実行する準備が整いました。
`.wasm`と HTML をどこかの Web サーバー上に配置するか、こんな感じのスクリプトを書いて Web サーバーをローカルで起動し、挙動を確認してみましょう。

```javascript:server.js
const cnct = require('connect');
const static = require('serve-static')
cnct().use(static('./')).listen(3000);
```

```
$ node server.js
```

WebAssembly機能を有効にした Chrome Canary でページにアクセスすると、このように表示されます。
![before.PNG](https://qiita-image-store.s3.amazonaws.com/0/19821/b2a1bd9c-ba1a-df22-cc91-f0013fa412d7.png)

Add!ボタンを押下すると……
![after.PNG](https://qiita-image-store.s3.amazonaws.com/0/19821/e1b5017d-623b-7473-0bbf-ffea87ea152d.png)
計算結果が表示されました！

※ [こちら](https://yasami.github.io/CodeSamples/JuliaWebAssembly/)に上のサンプルを置きました。Chrome Canary などでアクセスしてください。

# もっと高度なスクリプトを WebAssembly 化したい！
と思って試行錯誤してみたのですが、いくつかの壁にぶつかってほとんど前に進めませんでした。

+ `code_llvm()`関数で出力した LLVM IR は、Julia で定義した型のサイズが解決されていない。
+ LLVM組み込み関数の呼び出しを変換できない。
+ etc...

これらは Julia 本体のソースに手を入れたり、外部ツールで LLVM IR を書き換えてあげたりすればある程度の所までは解決するのではないかと思います。

また、WebAssembly には関数を export するだけでなく、import する機能やメモリを共有する機能もありますが、今回はそこまで調査できませんでした。

Julia コードの WebAssembly 化、ないしは何らかの形でブラウザ上で動かすことに興味を持っている人はいるようなので（https://github.com/JuliaLang/julia/issues/9430 や https://github.com/JuliaLang/julia/issues/2418 など）、その人たちのアクションに期待しつつ、私も引き続き挑戦をしていきたいです。

[^1]: MVP(Minimum Viable Product)時点の WebAssembly ツールチェインは LLVM 基盤に依存していますが、仕様としての WebAssembly は LLVM から独立しています。
[^2]: マングリングを解除するツールを作ることは容易でしょうが、今回はそのままにすることにしました。