【2017年8月】WebAssemblyに近未来フロントエンドの「夢」を見る。

  • 5
    いいね
  • 0
    コメント

半日ほど暇があったので、前々から気になっていたWebAssemblyを試してみることにした。
お試し機種は、日頃、ブラウジングマシンとして余生を送っている古めのMacbook air(RAM 4GB)。

ハマりどころにいくつかハマりつつ、ちょろっと動かしたところで時間切れとなったが、ツールのインストール・アンインストール合間にWebAssemblyの近況をいろいろと調べたところ、けっこうわくわくするところもあったので、導入記と思ったところを備忘録しておく。

※WebAssemblyの基本については、以下が分かりやすく解説してくれている。
【2017年4月版】WebAssemblyとは?〜実際にC言語をブラウザで動かす〜
ただ、WebAssemblyについて、「現時点でコンパイルがめちゃめちゃめんどくさい」と書かれているが、そこまでめんどくさくはないと思う(私がはまったところは概ねインストールまわりのみ)。


[追記] 長くなった上に深夜に書いていたので、「夢」を書く前に寝てしまったので、追記。要するに、情報量の多いiOSのような実行系(のサブシステムなど)がWebAssemblyで動作するようになることで、良い意味での選択肢が増えてくれることが、フロントエンド開発の「夢」。スマートフォンの文脈で言うと、アップルのAppStoreで配布したアプリのコードを活用して、Chrome App StoreやFirefox App Store経由でAndroid向けにも配布できる、的な。WebAssemblyのコードのネイティブコードにそこそこ迫る実行速度を活かし、Alexaをはじめとする音声端末やconnectedcarのナビゲーションシステムなど、UXがAI+音声となる端末に適用される、といった展開もありうるだろう(こちらはQtのような実行系がブラウザに持ち込まれることになるのかな)。モジラ財団が主導してきただけに、それどんなfirefox osと言われちゃうかもだけれど...
Mozillaが「Firefox OS」の開発を断念、スマホ・スマートテレビともにシェア得られず
...Firefox OS、2017年初まで、メジャー化の夢を見ていたのか...

WebAssemblyお試し用の環境整備

WebAssemblyの「大元」を開発したmozilla財団は、開発者サイトmdnにおいて、WebAssemblyに関する情報を提供してくれている。WebAssembly Conceptsには、以下の図が掲げられている:

EmscriptenなるツールがC/C++のソースコードをWebAssemblyの実行モジュール(wasm)に変換してくれるというわけだ。

① Emscriptenのコンパイル環境整備

Emscriptenはコードの変換の過程で、clang, llvm, cmakeを使う。
Macの場合、これらの導入は容易だ(開発者なら既に導入済みだろう)。X Codeの最近の版が入っていれば、clangとllvmも新しいものであるはずだ。私のマシンでのclangのバージョンは以下の通りだった。

$ clang -v
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin16.7.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

cmakeがなかったら、brew install cmakeなどでインストール。Emscriptenもbrewで入るのだが、Emscripten本家サイトに手順が示されているポータブル環境emsdk_portableでの導入をお勧めしておく(windowsやlinuxでも共通に導入できる)。

(付記) はまりどころのひとつが、Emscriptenのツールはpython2.7に依存していること。Macの場合、はじめからpython2.7が入っているが、あとでpython3がディフォルトとなっていた場合などは、Emscriptenツールの途中でエラーが出る(渡しの場合、pyenv経由で何も考えずにAnacondaを入れたところ、localもglobalもpython3固定となっていた...)

② Wasmの実行環境整備

実行環境といっても、web上での実行なので、ブラウザがあれぱ良いのだが、現時点ではmozilla財団のFirefoxを用意しておくのが良さそうだ(なぜか私の場合、chromeではうまく動かないWebAssemblyアプリがいくつかあった)。
また、web経由での配布という建前上、webサーバを動かしておいた方が良い(必須かどうかは分からない)。私の場合、手っ取り早く動かすという意味でh2oを入れた。

brew install h2o

h2oの場合、簡単なconfファイル一つで、実行環境を整えることができる。適当なディレクトリに以下のようなconfを用意すると、直下のXXXディレクトリ以下にhtml/css/jsと合わせてwasmを配置できる。

h2o.conf
listen: 8080
hosts:
  "localhost:8080":
    paths:
      /:
        file.dir: XXX

お試しでの起動はこのディレクトリで以下のようなコマンドで良い。

$ /usr/local/bin/h2o -c h2o.conf &

WebAssemblyお試し

Nimの場合

以下、私の趣味でCなどにコンパイルされる言語Nimを使うこととした。
ありがたいことに、snowlt23氏が、NimとCでもWebAssemblyやってみたというレポートを残してくださっているので、お試し自体ははまるところがなかった。
WebAssemblyをお試しをしたい方は、snowlt23氏のfib-wasmをcloneして眺めることからはじめると良い(Nimに加え、CとRustでの実行コードもあり)。
https://github.com/snowlt23/fib-wasm

ただ、Nimになじみがない場合、ビルドツールnakeの存在を知らないと思うので補充しておきたい。

Nimでは、Makefile的なタスクを以下のようにNimコードで記述できる。

github.com/snowlt23/fib-wasm/blob/master/nakefile.nim
()
task "build-c", "build wasm of C":
  discard execShellCmd "emcc -s WASM=1 -O3 -o public/fib-c.html fibonacci.c"

task "build-rust", "build wasm of Rust":
  discard execShellCmd "rustc -O --target=wasm32-unknown-emscripten -o public/fib-rust.html fibonacci.rs"

task "build-nim", "build wasm of Nim":
  discard execShellCmd "nim c -d:release -d:emscripten -o:public/fib-nim.html fibonacci.nim"
() # discardは返り値を用いない、との宣言

execShellCmdの引数である以下は、それぞれC, Rust, そしてNimのwasmへのコンパイルを行うコマンドだ。(-oオプションにhtmlを指定することで、wasmを実行するためhtml/jsも生成される)。

emcc -s WASM=1 -O3 -o public/fib-c.html fibonacci.c
rustc -O --target=wasm32-unknown-emscripten -o public/fib-rust.html fibonacci.rs
nim c -d:release -d:emscripten -o:public/fib-nim.html fibonacci.nim

nimの場合、snowlt23氏が別途用意してくれたcfgファイルを用意することで、通常のコンパイルオプションと並んで-d:emscriptenオプションを指定するだけで、Emscriptenがwasmを作ってくれる。snowlt23氏のソースコードをnim分のみビルドすると以下のような結果となる。/publicフォルダ以下を公開対象とすれば良いことが分かる。

$ exa -T
.
├── LICENSE
├── README.md
├── fibonacci
├── fibonacci.c
├── fibonacci.html
├── fibonacci.nim
├── fibonacci.rs
├── index.html
├── nakefile.nim
├── nim.cfg
├── nimcache
│  ├── fibonacci.c
│  ├── fibonacci.json
│  ├── fibonacci.o
│  ├── stdlib_algorithm.c
│  ├── stdlib_algorithm.o
│  ├── stdlib_math.c
│  ├── stdlib_math.o
│  ├── stdlib_parseutils.c
│  ├── stdlib_parseutils.o
│  ├── stdlib_strutils.c
│  ├── stdlib_strutils.o
│  ├── stdlib_system.c
│  ├── stdlib_system.o
│  ├── stdlib_times.c
│  └── stdlib_times.o
├── public
│  ├── fib-nim.html
│  ├── fib-nim.js
│  └── fib-nim.wasm
└── server.nim

# exaは、Rust製の素敵なls代替ツール。
# cf.http://qiita.com/tbpgr/items/9b05894c06fa7ec27ff0

私は、Nimでの階乗計算をコマンドラインとwebとで試してみることにした。コード:

fact1.nim
import times
import strutils

proc fact*(n: int): int =
  if n == 0 : 1
  else : n * fact(n - 1)

proc loop(N = 5) =
  echo N, " times loop."
  for i in 1..N:
    let result : string = $fact(i)
    echo "fact $# = $#" % [$i, result.insertSep(sep=',',3)] 

loop()
let starttime = times.epochTime()
loop(12)
let endtime = times.epochTime()
echo "elapsed: $# sec" % [$(endtime - starttime)]

といってもsnowlt23氏のコードを一部変えただけなのだが、nimのpythonテイストな特徴をちょっと強調してみた。

pythonになじみのある人は関数(プロシージャ)にdefでなくprocというキーワードが使われている以外は違和感が少ないのではないかと思いう。

※Nimでは、if/else文の中身が1行の場合、1行にまとめることが多いようなのでそれにならった。もちろん、pythonテイストに以下のよう書くこともできる。

proc fact*(n: int): int = 
  if n == 0 :
    1
  else :
    n * fact(n - 1) #  注、末尾再帰(プロシージャに型を明記)

proc loop(N=5)は、pythonと同様のディフォルト引数だ。Nimは静的言語だが、型推論によりint型(32bit)であることが推論されている。int型 が32bitであることはloopの引数を大きくしていく(例えば、N=20)と確認できる。

実行結果(コマンドライン):

$ nim c -d:release  -r fibonacci.nim
Hint: used config file '/usr/local/Cellar/nim/0.17.0/nim/config/nim.cfg' [Conf]
Hint: used config file '/Users/yoshihiro/OneDrive/reditime/h2odir/dir/fib-wasm/nim.cfg' [Conf]
Hint: system [Processing]
Hint: fibonacci [Processing]
Hint: times [Processing]
Hint: strutils [Processing]
Hint: parseutils [Processing]
Hint: math [Processing]
Hint: algorithm [Processing]
Hint:  [Link]
Hint: operation successful (16266 lines compiled; 0.382 sec total; 17.938MiB peakmem; Release Build) [SuccessX]
Hint: /Users/yoshihiro/OneDrive/reditime/h2odir/dir/fib-wasm/fibonacci  [Exec]
5 times loop.
fact 1 = 1
fact 2 = 2
fact 3 = 6
fact 4 = 24
fact 5 = 120
12 times loop.
fact 1 = 1
fact 2 = 2
fact 3 = 6
fact 4 = 24
fact 5 = 120
fact 6 = 720
fact 7 = 5,040
fact 8 = 40,320
fact 9 = 362,880
fact 10 = 3,628,800
fact 11 = 39,916,800
fact 12 = 479,001,600
elapsed: 2.598762512207031e-05 sec

nimの場合、この程度に簡単なプログラムではコンパイル、リンクから実行まで1秒かからない("nim c -d:release -r"は、「nimをCコンパイラで、リリース版としてコンパイルし、実行する」の意味。)。

ブラウザ向けのビルドについては初回コンパイルと2回目以降のコンパイルで大きく速度が異なる(初回は、nimのメソッドが依存するライブラリもコンパイルされるためと思われる(違ったらご指摘ください))。

wasmの(2回目以降の)ビルドの例:

$ nim c -d:release -d:emscripten -o:public/fib-nim.html fibonacci.nim
Hint: used config file '/usr/local/Cellar/nim/0.17.0/nim/config/nim.cfg' [Conf]
Hint: used config file '/Users/yoshihiro/OneDrive/reditime/h2odir/dir/fib-wasm/nim.cfg' [Conf]
Hint: system [Processing]
Hint: fibonacci [Processing]
Hint: times [Processing]
Hint: strutils [Processing]
Hint: parseutils [Processing]
Hint: math [Processing]
Hint: algorithm [Processing]
Hint:  [Link]
warning: unresolved symbol: _longjmp
warning: unresolved symbol: _setjmp
Hint: operation successful (16266 lines compiled; 2.234 sec total; 17.938MiB peakmem; Release Build) [SuccessX]

内部的にemscriptenのコンパイラ(emcc)が呼び出されている関係で、2回目以降のコンパイルでも2秒以上(最大5秒程度)の時間がかかっている。...といっても、メモリ不足気味の環境で、iTunesでBGMを流しながらの記録なので、体感ちょっと遅かったな、というくらい。

wasmの実行記録(実行環境はchrome):
※firefoxより実行の早かったchromeでの実行結果のスクリーンショットを貼っておく

スクリーンショット 2017-08-13 00.05.08.png

上のコードのloop(12)に対し、コマンドライン上での実行時間約0.025[ms]に対し、chromedの実行時間約7[ms]。100倍以上の速度差ということになるが、コマンドラインの表示とDOMのパース等を経てのブラウザ上の表示とではおそらく公平な比較ではないのだろう(数値3桁ごとにカンマを入れるといった文字列処理もさりげなく入っている)。以下の(より公平な条件であろう)ベンチマークのレポートでは、ネイティブコードでの実行に対しwasmの(数値演算の)実行速度は2倍以内であると報告されている。
https://forum.nim-lang.org/t/2991

WebAssemblyを使いこなす戦略

さて、WebAssemblyを開発実務で使うことはどのような条件下で可能になるのだろうか。 まっさきに気になるところは、WebAssembly上でどのようなライブラリが利用できるのか、というところだ。

Emscriptenの対応(C/C++)

以下は、C言語視点でライブラリ周りを調査してくださったもの。

Emscriptenが対応しているシステムコールについて調べた :

システムコールやlibcが無ければ既存のC資産は全く使い物になりません。なければ作るしかないということで、Emscriptenでは軽量libc実装のmuslを採用しており、これを静的リンクしてwasmファイルを生成しています。また、システムコールについてはJavaScriptで実装して提供しています

Cとして書くのであれば、けっこうなんとかなりそうだ、ということ。ただ、実行環境はブラウザであり、DOM操作等をどうするか、等、自らが人柱で取り組むのはけっこうつらそう。C++も同様で、実務で使うには、WebAssemblyに特化した商用のしっかりした環境が必要と思われる。

RustでWebAssembly 

Rust言語について、ここでは述べない。高めの学習曲線さえクリアできれば良い言語なのだと思う。mozilla財団がrustを推していることもあり、ライブラリのWebAssembly 対応は期待できるのでは。WebAssemblyの今後の「伸びしろ」に期待してrust入門をWebAssemblyで行うといったこともありなのかも。

以下は、RustとJavaScriptの相互呼び出し例(けっこう大変そう):
【Rust×WebAssembly】Rust JavaScript間でデータをやり取りする

UnityのWebAssembly対応

WebAssemblyによるブラウザ上での高速処理が求められる一典型が、ゲーム。メジャーなゲーム開発プラットフォームであるUnityはWebAssembly 対応を進めている模様。C#な人は、Unityが提供するツールで、iOS、Androidと同様のコードがweb上でも動作る近未来を期待できるだろう。
以下に紹介されているように既に動作するゲームも公開されている(私の古いmacでもfirefox上で動作)。
http://d.hatena.ne.jp/nakamura001/20170421/1492751387

このタンクゲームにあるように、WebAssemblyにmainから入って、最後までつっきるというのが、ゲームを典型に当初のWebAssemblyの典型ユースケースとなるものと考える(このことに気がついて、nim-JSの相互呼び出しのコードを書いてみるのはやめにした(...大変そうだから^^;))

WebAssemblyとゲームとの関係は以下に分かりやすく解説されている。

2016/05/11時点でWebAssembly関連の情報を整理してみた :

wasmの直近の目標はゲームプラットフォームをブラウザ上に構築することにあると見ている。マーケットとして巨大なゲーム市場をブラウザに持ってこれれば各ベンダの利益に繋がる。オープンプラットフォームを標榜するMozillaが、ともするとエンドユーザーにとっては難読化だと捉えることが可能で本来の思想と反しそうなWASMに熱心なのも、その辺りの経済的な事情があるだろう。(とはいえ、AppStoreでネイティブアプリを売って儲けてるAppleは別だから、WASMやWebGLのようなAPIで歩調が乱れるのが恒例になっているのだが)

確かに、アップル製webkitが、WebAssembly対応に遅れるということはいかにもありそう。

Objective-CでWebAssembly

 なんか微妙な気分もするところもあるけれど、アップルがWebAssembly対応に一番消極的という読みの下では、iOSのアプリをWebAssembly対応させるという戦略が成り立つことが分かる。この場合、アップルが、自社の儲けのためにiPhone等のWebAssembly対応を後回しにしようと関係ない。
 
 そもそもEmscripten/WebAssemblyが依って立つ、clang/LLVMはアップルの主体的関与の下、普及していったもの。iOSのObjective-Cコードだって動作するはず。
 
 ...前々から気になっていたWebAssemblyを今更に試したことの最大の成果は、この流れが現実のものとなりつつあることを知ったこと。
 
Extending Emscripten to Support Objective-C — running iOS Apps on the Web : 

As part of our effort to run iOS applications on web browsers, we have extended Emscripten to compile and run Objective-C....Like obj4, CoreFoundation also has environment dependencies, such as input/output and directory structures. We made modifications so that these would work on Emscripten.
CoreFoundation is open sourced by Apple and Foundation is open sourced by Apportable, so we were able to build on top of their work.

 clang/LLVMはそもそもが Objective-C対応。CベースのCoreFoundationライブラリのEmscripten対応を皮切りに、iOSの実行環境をブラウザ上に移植していくことは、(先人のApportable等の努力の上に)十分に成り立ちうる。
 
 そのためのPoC実装は既にあった。ブラウザ上でお試してしてみて欲しい:
 http://demo.tombo.io/
※私のMac上ではFirefoxでのみ動作

前々から、Objective-C資産のクロスプラットフォーム化には興味を持っていて、いくつかの実装をみていたのだけれど、その最新・最善のものが(カルフォルニアに渡った)日本人チームによって作られつつあるとは知らなかった:
http://tombo.io/

私的結論

LLVM上でもっとも伸びている言語といえば、間違いなくアップルのSwift。既にオープンソース化されており、アップル自身が対応しなくとも、いずれ有志がWebAssembly対応するのではと思われる。とはいえ、並の工数感では下回りのC/Objective-CのCoreFoundation/FoundationライブラリのWebAssembly対応が終わって、はじめてまともなSwift対応ができるところ。このあたりは、いわゆるクロスコンパイルの開発の世界と同様。

#別途、シングルバイナリを得意とするGoogle GoのLLVM版が出て来る可能性もある(その他、scala native、kotlin nativeなども)。

WebAssembly市場の大きさはいまだ見えない中、C/C++/Objective-c、Rust/Swift/C#/Go、
どの言語が来るかは読めないところだろう。

 そうした中、pythonを日々書いている私としては
 C/C++/Objective-CそしてJavaScriptにコンパイルできるNimと
 python(2)からのコンパイル(grumpy)を受け入れるGoか面白いな、と思う。
 これからの分野だけに行けるとなったら突っ走ったもの勝ち(人柱的な意味で)かな、と。
 分野としては、ゲーム以外にIoT系のレポートアプリなどがあったらいいなと。
 
 Rust / Swiftに実用的な環境が整えば、そちらが良いのかも、だけれど、
 こちら、個人的な夢見備忘録なので、私的な結論で終えておこうかな、と。
 
 Enjoy yourself!