@11
[r25+8] <- r24
r26: i64 <- 0
ret r26
}
```
===
# 変数割り当て
--------------
* SSAの1変数 = WASMの1ローカル変数
* スタックの効率利用を完全に無視
* 計算はLV→スタック→LVに書き戻し
* どうせスタックもLVもレジスタ扱いにしてレジスタ割り当てされるでしょ
* (測ってないけど)多分速度は変わらない
===
# CFG
-----
* コンパイラが一旦gotoを使うコントロールフローグラフを作る
* WASMにはgotoがない
* どうやったら生成出来るか?
* そもそも生成出来るの?
===
# blockと前方ジャンプ
------------
* `block` + `break` で前方ジャンプ
* 閉じ括弧の位置にジャンプ
* `block` の位置は自由
===
(block
...
(br 0)--+
... |
)<------+
===
(block
...
...
...
(br 0)--+
... |
)<------+
===
loopと前方ジャンプ
loop
+ break
で後方ジャンプ
loop
からの break
はいわゆる continue
- 開き括弧の位置にジャンプ
- 閉じ括弧の位置は自由
===
(loop<----+
... |
(br 0)--+
...
)
===
(loop<----+
... |
(br 0)--+
...
...
...
)
===
ジャンプのクロス
- 単一gotoは割り当て出来る
- 複数のgotoが入り組んだら?
===
絶対出来る
- チューリング完全なら必ず書ける
- whileとswitchでステートマシン作ればいい
- 効率的とは限らない
- 効率的なコードを吐きたい
===
ステートマシンはつらい
#include <stdio.h>
int main() {
int sum = 0;
for (int i = 1; i <= 100; i++)
sum += i;
printf("1+...+100=%d\n", sum);
return 0;
}
===
function _main() {
var __stackBase__ = STACKTOP;
STACKTOP += 12;
var __label__ = -1;
while(1)
switch(__label__) {
case -1:
...
__label__ = 0;
break;
case 0:
...
if ($4) {
__label__ = 1;
break;
} else {
__label__ = 2;
break;
}
case 1:
...
__label__ = 3;
break;
case 3:
...
__label__ = 0;
break;
case 2:
...
return 0;
}
}
===
ステートマシンはつらい
- どうにかする必要がある
- なんか気に食わなかった
- そもそもステートマシンを使わずに生成したい
- 複数のgotoが入り組んだパターンを自分で考えたの紹介します。
- CFGと基本ブロックは知ってるかな?
===
前前
[ ]--+
| |
[ ]--+-+
| | |
[ ]<-+ |
| |
[ ]<---+
===
前前
(block
(block
...
(br 0)-+
... |
(br 1)-+-+
)<-------+ |
)<-----------+
===
後後
[ ]<-+
| |
[ ]<-+-+
| | |
[ ]--+ |
| |
[ ]----+
===
後後
(loop<-----+
(loop<---+-+
... | |
(br 1)-+ |
... |
(br 0)---+
)
)
===
後前
[ ]<-+
| |
[ ]--+-+
| | |
[ ]--+ |
| |
[ ]<---+
===
後前
(block
(loop<---+
... |
(br 1)-+-+
... | |
(br 0)-+ |
) |
)<-----------+
===
前後
[ ]--+
| |
[ ]<-+-+
| | |
[ ]<-+ |
| |
[ ]----+
===
前後
- 素直には出来ない…?
- 部分的にステートマーシン作る?
===
部分的ステートマーシン
[1]--+
| |
[2]<-+-+
| | |
[3]<-+ |
| |
[4]----+
===
部分的ステートマーシン
[1] label = 0
|
+->[br]-+ if label == 0
| | | then goto 3
| | | else goto 2
| | |
| [2] |
| | |
| [3]<-+ label = 1
| |
+--[4]
===
コード生成
===
フォーマット
- バイナリフォーマットとテキストフォーマットがある
- バイナリ: wasm
- テキスト: wast
- 人間可読+機械可読=S式
- 低級にもちょっと高級にも書ける
- 一旦アセンブラ噛まさないと動かない
===
アセンブラ
- オンメモリで生成するためにアセンブラ自作
- アセンブラ自体ブラウザで動かすのでRust製
- まだ動かしてない
===
let mut mb = ModuleBuilder::new();
let f = mb.new_function(FunctionBuilder::new(funtype!((i32, i32) -> i32))
.code(|cb, args| {
cb.constant(2)
.get_local(args[0])
.i32_add()
})
.build());
mb.export("addTwo", f);
let module = mb.build();
===
スタック領域
- Cでいう
struct foo x;
みたいにエフェメラルな多ワード領域が欲しい
- WebAssemblyのローカルストレージはLVだけ
- 可能性
- 諦めてメモリに確保(場合によっては最適化で消えるかも)
- ワード毎に分割してLVに保存(大変だけど速そう)
===
ランタイム
===
言語のランタイム
- 主にはGC
- その他データのメモリ表現
- スタック領域
- FFI
- コンパイラなのでシンボルテーブルはなし
===
メモリ
- mallocとかはない
- ページ単位のアロケーションだけ
- 自前でGCを実装することになる
===
GC
- コールスタックを遡れない
- メモリonlyな走査なら可能
- ポインタを都度メモリ領域(arena)にコピーすれば良い。
- arenaもルートになる
- コールスタックと連動するのでスタックで管理出来る。
- CF Matzにっき(2013-07-31)
- ただしコンパイラなので関数全部をsave/restoreで囲んだりはしない。
===
アロケータ
- どうにか書いてブラウザでリンクする必要がある
- ブラウザにもランタイムライブラリの時代…
- WASMはライブラリ間でメモリ共有出来る
- とりあえずRustで書く方針
- WASMのページアロケートとかどうすればいいんだろう
- まだ色々未定
===
メモリ表現
- 出来れば楽して64bit統一したかった
- WASM32しかないのでポインタが32bit…
- 仕方ないので64bitアラインメントでパディングする
- 空いた32bitの使い道は未定
- 静的型付だし型タグが要らない
- 代数的データ型のタグ?
===
高階関数
- WASMに関数ポインタがない
- テーブルに関数を登録してインデックス参照
- C++のvtableのための機能
- 型も動的チェック
- ちょっと遅そう
- 気合でインライン化を頑張ろう
===
FFI
- JSの関数を呼びたい
- 細かいところどうなってるんだろう
- 型付…
- ノープラン
===
雑にまとめ
- WASMはちょっと高級なので最適化コンパイラは困るよ
- コード生成は努力で解決
- GCは割とつらいよ
- JS連携や将来のスレッドとかはみんなで考えよう