zick pages

WebAssemblyのテキスト表現を書いてみた

数年前からWebAssembly というのが流行っているらしい。 WebAssemblyにはテキスト表現とバイナリ表現というものがあり、 通常はCやRustからWebAssemblyのバイナリ表現にコンパイルし、 ブラウザ上で実行するもののようだが [要出典] 、 人間がWebAssemblyのテキスト表現を書いて、 それをバイナリ表現に変換(アセンブル?)することもできる。

そのWebAssemblyのテキスト表現がS式だということで、 数年前に一部の人達が大喜びしていた。 例によって私はすっかり流行に乗りそこねてしまい放置していたのだが、 気が向いたので少し触ってみることにした。

このページ に色々説明が載っているが、 この説明に則り、2つの整数を受け取りその平均を返す関数を書くと次のようになる。

(func $avg1 (param $x i32) (param $y i32) (result i32)
  local.get $x
  local.get $y
  i32.add
  i32.const 2
  i32.div_s)

1行目を見るとおっと思うが、2行目以降はJavaバイトコードと大差のない 特に面白みのないスタックマシンのコードだ。 十分手書きすることができるが、積極的に手書きしたいとは思わない。 しかし、悲しむ必要はない。もっとLISPっぽい書き方もできる。

(func $avg2 (param $x i32) (param $y i32) (result i32)
  (i32.div_s
   (i32.add (local.get $x) (local.get $y))
   (i32.const 2)))

少々ごちゃごちゃしたものが付いているとは言え、 かなりLISPに近い気分で書くことができる。読むのもはるかに容易だ。 この差はプログラムが複雑になるほど開いてくる。

(func $fact1 (param $n i32) (result i32)
  (local $ret i32)
  local.get $n
  i32.eqz
  (if
   (then
    i32.const 1
    local.set $ret)
   (else
    local.get $n
    i32.const 1
    i32.sub
    call $fact1
    local.get $n
    i32.mul
    local.set $ret))
  local.get $ret)

条件分岐にジャンプではなくifを使うというのが興味深いというのは置いておいて、 これは読みにくくて仕方ない。 書くのも常にスタックを意識しながら気合を入れて書かないといけない。 これをLISP風の書き方にしたら一気に楽になる。

(func $fact2 (param $n i32) (result i32)
  (local $ret i32)
  (if (i32.eqz (local.get $n))
      (local.set $ret (i32.const 1))
      (local.set
       $ret
       (i32.mul (call $fact2 (i32.sub (local.get $n) (i32.const 1)))
                (local.get $n))))
  (local.get $ret))

余計な$retという変数が出てくることに目をつぶれば普通に読み書きできる。 なぜMDNのドキュメントがわざわざ読み書きしにくい方式で書いてあるのかが謎だ。 ちなみに、なぜ余計な変数を使っているかというと、 ifに入る前と出るときにスタックの深さが 変わってはいけないという制約があるためだ。 本来はthenとelseでスタックの変化量が等しければ問題ないはずだが。

2022-02-11