0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Node.js でつくる WASM トランスパイラー - 04:比較演算子を実装する

Last updated at Posted at 2019-04-15

はじめに

Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニコンパイラー」「ミニインタープリター」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成するトランスパイラーに取り組んでいます。

※この記事のアップデート版はこちら ... Node.js でつくる WASM コンパイラー - 04:比較演算子を実装する

今回実現したいこと

今回は次の比較演算子のサポートが目標です。それぞれ、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生成対象とします。

sample/neq.js
putn(1 !== 2);

0;

WASMの生成

今回のステップまでのトランスパイラーのソースコードを、mininode_wasm_04.js とします。次のようにgenerated.wast を生成します。

$ node mininode_wasm_04.js sample/neq.js

生成結果はこちら。

generated.wast
(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 が標準出力に表示されました。

色々な比較の実行

もう一つ、色々な比較を使ったサンプルを変換、実行してみます。

comp.js
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にソースを上げておきます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?