1
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 3 years have passed since last update.

Node.js でつくる WASM トランスパイラー - 05:条件分岐とループを実装する

Last updated at Posted at 2019-04-29

はじめに

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

※この記事のアップデート版はこちら ... Node.js でつくる WASM コンパイラー - 05:条件分岐とループを実装する

これまでの取り組み

今回実現したいこと(1) if

今回の目標の1つ目は条件分岐(if 〜 else)のサポートです。

ifの例

対象となるミニNode.jsのコードはこちら。

if1.js
if (1) {
  putn(111);
}

生成したいWASTはこちらです。

(if
  (i32.const 1)
  (then
    (call $putn
      (i32.const 111)
    )
  )
)

if 〜 else の例

もう1つ、elseがある場合はこちら。

if0.js
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 の実行

複数の条件分岐を使ったサンプルを用意します。

if.js
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のサンプルも用意しました。

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

1
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
1
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?