はじめに
Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニインタープリター」「ミニコンパイラー」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成する小さなコンパイラーに取り組んでいます。
※以前の記事「Node.js でつくる WASM トランスパイラー - 04:比較演算子を実装する」のアップデート版になります。
今回実現したいこと
今回は次の比較演算子のサポートが目標です。それぞれ、WATでは次のように表記します。
- === ... eq
- !== ... ne
- < ... lt_s (符号あり)
-
... gt_s (符号あり)
- <= ... le_s (符号あり)
-
= ... ge_s (符号あり)
比較の変換例
このミニNode.jsのソースコードを対象とします。
putn(1 !== 2);
putn()は、前回用意した簡易デバッグ出力用の組み込み関数です。
これを読み込み、単純化したASTはこちらになります。
[ 'func_call', 'putn',
[ '!==', [ 'lit', 1 ], [ 'lit', 2 ] ]
]
この部分を次のWATに変換することが、今回の目的です。
i32.const 1
i32.const 2
i32.ne
call $putn
比較演算子の生成
早速コンパイラーのgenerate()関数を拡張します。
function generate(tree, indent, lctx) {
// ... 省略 ...
// --- compare operator ---
if (tree[0] === '===') {
return generateCompareOperator(tree, indent, 'eq', lctx);
}
if (tree[0] === '==') {
return generateCompareOperator(tree, indent, 'eq', lctx);
}
if (tree[0] === '!==') {
return generateCompareOperator(tree, indent, 'ne', lctx);
}
if (tree[0] === '!=') {
return generateCompareOperator(tree, indent, 'ne', lctx);
}
if (tree[0] === '>') {
return generateCompareOperator(tree, indent, 'gt_s', lctx);
}
if (tree[0] === '>=') {
return generateCompareOperator(tree, indent, 'ge_s', lctx);
}
if (tree[0] === '<') {
return generateCompareOperator(tree, indent, 'lt_s', lctx);
}
if (tree[0] === '<=') {
return generateCompareOperator(tree, indent, 'le_s', lctx);
}
// ... 省略 ...
}
// --- compare operator ---
function generateCompareOperator(tree, indent, operator, lctx) {
const leftBlock = generate(tree[1], indent, lctx);
const rightBlock = generate(tree[2], indent, lctx);
const op = 'i32.' + operator;
let block = '';
block = block + leftBlock + LF();
block = block + rightBlock + LF();
block = block + TABs(indent) + op + LF();
return block;
}
実際にコードを生成するのは、generateCompareOperator()関数で行なっています。
比較演算子の実行
対象ソース
こちらのコードをWASM生成対象とします。
putn(1 !== 2);
0;
WASMの生成
今回のステップまでのコンパイラーのソースコードを、mininode_wasm_04.js とします。次のようにgenerated.wat を生成します。
$ node mininode_wasm_04.js sample/neq.js
生成結果はこちら。
(module
(func $putn (import "imports" "imported_putn") (param i32))
(export "exported_main" (func $main))
(func $main (result i32)
i32.const 1
i32.const 2
i32.ne
call $putn
i32.const 0
return
)
)
これを WASM に変換、前回用意した run_wasm_putn.js を使って実行します。
$ wat2wasm generated.wat
$ node run_wasm_putn.js generated.wasm
Loading wasm file: generated.wasm
1
ret code=0
「1 !==2」は true なので、1 が標準出力に表示されました。
色々な比較の実行
もう一つ、色々な比較を使ったサンプルを変換、実行してみます。
let a = 1;
putn(a); // expect 1
let b = (a === 2);
putn(b); // expect 0
putn(a == 1); // expect 1
let c = (a !== 2);
putn(c); // expect 1
putn(a != 1); // expect 0
putn(999);
a = a * 10;
putn(a > 10) ; // expect 0
putn(a >= 10) ; // expect 1
putn(a < 10) ; // expect 0
putn(a <= 10) ; // expect 1
22;
実行結果はこちら。色々な比較演算子も想定通りの出力になりました。
Loading wasm file: generated.wasm
1
0
1
1
0
999
0
1
0
1
次回は
目標の1つであるFizzBuzzに必要な、条件分岐とループを実装する予定です。
ここまでのソース
GitHubにソースを上げておきます。
- GitHubのレポジトリ ... https://github.com/mganeko/mini_node_wasm
- mininode_wasm_04.js ... 今回のWASMコンパイラー
- sample/neq.js ... シンプルな比較演算子のサンプル
- sample/comp.js ... 色々な比較演算子のサンプル