AssemblyScript に入門する
WebAssembly っぽいものはおもしろいなーと思いつつも、wasm コンパイルできる言語で有名なもの(C,C++,Rust,Go等)で、慣れている言語がなかったので、TypeScript のサブセットから wasm にコンパイルできるらしい AssemblyScript に入門してみる。
ちなみに今の段階の自分の WebAssembly への理解は、「任意の言語からコンパイルできかつブラウザ動くバイナリフォーマットで、C や C++ 等の資産が活かせる上に実行速度が速かったり速くなかったりするらしい」という感じ。
Emscripten というのを使うと C や C++ から WebAssembly にコンパイルすることができるらしいというのも知っている。
Rust からも WebAssembly にコンパイルできるらしくてなんか流行ってるという印象がある。
他のことは知らない。
https://github.com/WebAssembly/binaryen を使って TS のサブセットを WebAssembly にコンパイルするコンパイラらしい。Binaryen は名前しか知らないけどなんだろうな
Binaryen は C++ で記述された WebAssembly 用のコンパイラおよびツールチェインインフラストラクチャライブラリらしい
WebAssembly へのコンパイルを Easy, Fast, Effective にするのが目的
何から WebAssembly へのコンパイラなんだ?
https://github.com/WebAssembly/binaryen#tools いろんなツールがあるっぽい
- wasm-opt
- WebAssembly を読み込んで Binaryen IR を実行
- wasm-as
- S式のテキストフォーマット WebAssembly をバイナリフォーマットにアセンブルする
- wasm-dis
- バイナリフォーマットの WebAssembly をテキストフォーマットに逆アセンブルする
- wasm2js
- WebAssembly から JavaScript へのコンパイラ
- wasm-reduce
- なんかよくわからん wasm ファイルがある場合、それと同じような挙動をしつつデバッグしやすい wasm ファイルをゲットできるんかな、わからん
- wasm-shell
- WebAssembly コードをロードして解釈できるシェル
- wasm-ctor-eval
- C++グローバルコンストラクタを事前に実行できるらしい
- よくわからないけど Emscripten から使われている
- binaryen.js
- WASM モジュールを作成および最適化するための Binaryen メソッドを公開するスタンドアローンの JavaScript ライブラリ
雰囲気はなんとなくわかった。本当にコンパイラーツールチェインという感じ
ところで、WebAssembly と WASM と wasm は厳密に使い分ける必要がある用語なんだろうか
うーん、なんか具体的に Binaryen が AssemblyScript からどんな感じで使われているのかまだピンとこない。
TypeScript のサブセットで型チェックを行うってことは、どっかで TypeScript Compiler API とか叩いているだろうしそのへんのやりとりがどうなってるのかわからん
https://github.com/AssemblyScript/assemblyscript/tree/master/src にアーキテクチャの図みたいのがあるけどよくわからん
Portable compiler sources that compile to both JavaScript using tsc and WebAssembly using asc.
なるほど tsc を使って TS(のサブセット)を JS にして、asc を使って WebAssembly にするコンパイラ
結構ガチの TS 用 tokenizer と parser があるからパースは tsc でやってるわけじゃないのか
The AssemblyScript compiler utilizes Binaryen's C-API directly. Even though it currently imports binaryen.js, none of the JS APIs it provides are used.
え、あそうなんだ。Binaryen の C API を直接使っていて binaryen.js の API を使っているわけではない
binaryen を使うための glue code
binaryen にはいろんな C API が生えているんですね
どこから TypeScript Compiler が使われてるのかわからない
https://raw.githubusercontent.com/AssemblyScript/assemblyscript/master/media/architecture.svg の通り、Compiler というのは Module というものを吐くらしい
Module はどこにいくんだ
あれ、なんか ./cli/asc.js
で ts-node
を使っているな。しかも transpileOnly
で。全然ちゃんと読んでいないがもしかして ts-node でトランスパイルしているのか?
どうやらこれは、JS 版 AssemblyScript も wasm 版 AssemblyScript も存在しない場合は入力のコードをそのまま ts-node で実行するという話みたい
まあよくわからないので手元で console.log
仕込んで流れを追うかー
意図的に型エラーを発生させてみた
$ ./bin/asc ./_tmptmptmp.fib.ts -b fib.wasm -O3
ERROR TS2322: Type '~lib/string/String' is not assignable to type 'i32'.
return "foo";
~~~~~
in _tmptmptmp.fib.ts(12,10)
FAILURE 1 compile error(s)
ここからエラーをおうと
https://github.com/AssemblyScript/assemblyscript/blob/86dc8df740aa94d2c44b0811dc373fe819dd97bf/cli/asc.js#L1244-L1287 でこのエラーが出力されていることがわかり、さらに https://github.com/AssemblyScript/assemblyscript/blob/86dc8df740aa94d2c44b0811dc373fe819dd97bf/cli/asc.js#L1247 でエラーを取得していそう
let diagnosticPtr = assemblyscript.nextDiagnostic(program);
はなんだろうね
assemblyscript
は https://github.com/AssemblyScript/assemblyscript/blob/86dc8df740aa94d2c44b0811dc373fe819dd97bf/cli/asc.js#L136-L155 で取得されている。wasm 版 AssemblyScript と JS 版 AssemblyScript を切り替えることができるっぽい
https://github.com/AssemblyScript/assemblyscript/blob/86dc8df740aa94d2c44b0811dc373fe819dd97bf/src/index.ts#L211-L215 nextDiagnostic
の定義。つまりどっかしらで program.diagnostics
がつまれている
emitDiagnostic
https://github.com/AssemblyScript/assemblyscript/blob/86dc8df740aa94d2c44b0811dc373fe819dd97bf/src/diagnostics.ts#L281-L319 という関数があって、そいつがいろんなところから呼ばれている
emitDiagnostic
で意図的に throw new Error()
してスタックトレースを見るとこう(もっと前からこれをやればよかった
▌ Error:
▌ at Compiler.emitDiagnostic (/assemblyscript/src/diagnostics.ts:317:11)
▌ at Compiler.error (/assemblyscript/src/diagnostics.ts:399:10)
▌ at Compiler.convertExpression (/assemblyscript/src/compiler.ts:3579:12)
▌ at Compiler.compileExpression (/assemblyscript/src/compiler.ts:3506:21)
▌ at Compiler.compileReturnStatement (/assemblyscript/src/compiler.ts:2796:19)
▌ at Compiler.compileStatement (/assemblyscript/src/compiler.ts:2185:21)
▌ at Compiler.compileStatements (/assemblyscript/src/compiler.ts:2249:23)
▌ at Compiler.compileFunctionBody (/assemblyscript/src/compiler.ts:1585:20)
▌ at Compiler.compileFunction (/assemblyscript/src/compiler.ts:1515:17)
▌ at Compiler.compileElement (/assemblyscript/src/compiler.ts:994:38)
さらに compileElement
でやるとこう
▌ Error:
▌ at Compiler.compileElement (/assemblyscript/src/compiler.ts:1027:11)
▌ at Compiler.compileExports (/assemblyscript/src/compiler.ts:1037:62)
▌ at Compiler.compile (/assemblyscript/src/compiler.ts:482:14)
▌ at Object.compile (/assemblyscript/src/index.ts:276:32)
▌ at /assemblyscript/cli/asc.js:833:31
▌ at measure (/assemblyscript/cli/asc.js:1331:3)
▌ at Object.main (/assemblyscript/cli/asc.js:831:24)
▌ at /assemblyscript/bin/asc:19:47
まあなんか普通に compile でエラーを emit しているっぽい。tsc はそこにいるのか...
そしてスタックトレースが ts のまま見れて便利だし、明示的にビルドせずにそのまま動いているっぽい?よくわかんないけど開発体験が良い