はじめに
Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニコンパイラー」「ミニインタープリター」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成するトランスパイラーに取り組んでいます。
※この記事のアップデート版はこちら ... Node.js でつくる WASM コンパイラー - 05:条件分岐とループを実装する
- 前回の記事 ... 04:比較演算子を実装する
これまでの取り組み
今回実現したいこと(1) if
今回の目標の1つ目は条件分岐(if 〜 else)のサポートです。
ifの例
対象となるミニNode.jsのコードはこちら。
if (1) {
putn(111);
}
生成したいWASTはこちらです。
(if
(i32.const 1)
(then
(call $putn
(i32.const 111)
)
)
)
if 〜 else の例
もう1つ、elseがある場合はこちら。
if (0) {
putn(111);
}
else {
putn(0);
}
同様に、このようにWASTを生成していきます。
(if
(i32.const 0)
(then
(call $putn
(i32.const 111)
)
)
(else
(call $putn
(i32.const 0)
)
)
)
if 〜 else の生成
いつものようにトランスパイラーのgenerate()関数を拡張します。実際にif〜elseに対応する部分は、genereateIf()関数で生成しています。
function generate(tree, indent, lctx) {
// ... 省略 ...
// --- if ---
if (tree[0] === 'if') {
const block = genereateIf(tree, indent, lctx);
return block;
}
// ... 省略 ...
}
// --- if ---
// tree = ['if', condition, then, else]
function genereateIf(tree, indent, lctx) {
const conditionBlock = generate(tree[1], indent + 1, lctx);
const positiveBlock = generate(tree[2], indent + 2, lctx);
let block = TABs(indent) + '(if' + LF();
block = block + conditionBlock + LF();
// -- then --
block = block + TABs(indent + 1) + '(then' + LF();
block = block + positiveBlock + LF();
block = block + TABs(indent + 1) + ')' + LF();
// -- else --
if (tree[3]) {
const negativeBlock = generate(tree[3], indent + 2, lctx);
block = block + TABs(indent + 1) + '(else' + LF();
block = block + negativeBlock + LF();
block = block + TABs(indent + 1) + ')' + LF();
}
block = block + TABs(indent) + ')';
return block;
}
if 〜 else の実行
複数の条件分岐を使ったサンプルを用意します。
let a = 1;
if (a === 1) {
putn(111);
}
if (a !== 1) {
putn(222);
}
else {
putn(333);
}
putn(999);
33;
これをWASMに変換、実行した結果がこちらです。
$ node mininode_wasm_05.js sample/if.js
$ wasm-as generated.wast
$ node run_wasm_putn.js generated.wasm
Loading wasm file: generated.wasm
111
333
999
ret code=33
想定通りの出力になりました。
今回実現したいこと(2) while ループ
今回の目標の2つ目は while ループのサポートです。
whileの例
let a = 0;
while (a < 10) {
putn(a);
a = a + 1;
}
生成したいWASTはこちら。
(local $a i32)
(set_local $a
(i32.const 0)
)
(loop ;; --begin of while loop--
(if
(i32.lt_s
(get_local $a)
(i32.const 10)
)
(then
(call $putn
(get_local $a)
)
(set_local $a
(i32.add
(get_local $a)
(i32.const 1)
)
)
(br 1) ;; --jump to head of while loop--
) ;; end of then
) ;; end of if
) ;; --end of while loop--
色々な実現の仕方があると思いますが、今回は while ループの中身を if で記述し、br 命令でループの先頭に戻るようにしました。
while の生成
再度generate()関数を拡張します。実際にwhileに対応する部分は、genereateWhile()関数で生成しています。
function generate(tree, indent, lctx) {
// ... 省略 ...
// --- while ---
if (tree[0] === 'while') {
const block = genereateWhile(tree, indent, lctx);
return block;
}
// ... 省略 ...
}
// --- while ---
// tree = ['while', condition, then, else]
function genereateWhile(tree, indent, lctx) {
const conditionBlock = generate(tree[1], indent + 2, lctx);
const innerBlock = generate(tree[2], indent + 3, lctx);
let block = TABs(indent) + '(loop ;; --begin of while loop--' + LF();
block = block + TABs(indent + 1) + '(if' + LF();
block = block + conditionBlock + LF();
block = block + TABs(indent + 2) + '(then' + LF();
block = block + innerBlock;
block = block + TABs(indent + 3) + '(br 1) ;; --jump to head of while loop--' + LF();
block = block + TABs(indent + 2) + ') ;; end of then' + LF();
block = block + TABs(indent + 1) + ') ;; end of if' + LF();
block = block + TABs(indent) + ') ;; --end of while loop--';
return block;
}
while の実行
whileのサンプルも用意しました。
let a = 0;
while (a < 10) {
putn(a);
a = a + 1;
}
0;
これをWASMに変換、実行した結果がこちらです。
$ node mininode_wasm_05.js sample/while.js
$ wasm-as generated.wast
$ node run_wasm_putn.js generated.wasm
Loading wasm file: generated.wasm
0
1
2
3
4
5
6
7
8
9
ret code=0
無事ループが実行されました。
次回は
いよいよ目標の1つであるFizzBuzzにチャレンジする予定です。
ここまでのソース
GitHubにソースを上げておきます。
- GitHubのレポジトリ ... https://github.com/mganeko/node_wasm
- mininode_wasm_05.js ... 今回のWASMトランスパイラー
- sample/if.js ... if〜elseのサンプル
- sample/while.js ... whileのサンプル