はじめに
Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニコンパイラー」「ミニインタープリター」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成するトランスパイラーに取り組んでいます。
※この記事のアップデート版はこちら ... Node.js でつくる WASM コンパイラー - 04:比較演算子を実装する
- 前回の記事 ... 03:ローカル変数を実装する
今回実現したいこと
今回は次の比較演算子のサポートが目標です。それぞれ、WASTでは次のように表記します。
- === ... eq
- !== ... ne
- < ... lt_s (符号あり)
-
... gt_s (符号あり)
- <= ... le_s (符号あり)
-
= ... ge_s (符号あり)
比較の変換例
このミニNode.jsのソースコードを対象とします。
putn(1 !== 2);
putn()は、前回用意した簡易デバッグ出力用の組み込み関数です。
これを読み込み、単純化したASTはこちらになります。
[ 'func_call', 'putn',
[ '!==', [ 'lit', 1 ], [ 'lit', 2 ] ]
]
この部分を次のWASTに変換することが、今回の目的です。
(call $putn
(i32.ne
(i32.const 1)
(i32.const 2)
)
)
比較演算子の生成
早速トランスパイラーの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+1, lctx);
const rightBlock = generate(tree[2], indent+1, lctx);
let block = TABs(indent) + '(i32.' + operator + LF();
block = block + leftBlock + LF();
block = block + rightBlock + LF();
block = block + TABs(indent) + ')';
return block;
}
実際にコードを生成するのは、generateCompareOperator()関数で行なっています。
比較演算子の実行
対象ソース
こちらのコードをWASM生成対象とします。
putn(1 !== 2);
0;
WASMの生成
今回のステップまでのトランスパイラーのソースコードを、mininode_wasm_04.js とします。次のようにgenerated.wast を生成します。
$ 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)
(call $putn
(i32.ne
(i32.const 1)
(i32.const 2)
)
)
(i32.const 0)
)
)
これを WASM に変換、前回用意した run_wasm_putn.js を使って実行します。
$ wasm-as generated.wast
$ 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/node_wasm
- mininode_wasm_04.js ... 今回のWASMトランスパイラー
- sample/neq.js ... シンプルな比較演算子のサンプル
- sample/comp.js ... 色々な比較演算子のサンプル