5
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.

WebAssemblyAdvent Calendar 2019

Day 20

mwasm(webassemblyテキストフォーマット用プリプロセッサ) の製作

Last updated at Posted at 2019-12-19

mwasm(wasmテキストフォーマット用プリプロセッサ) の製作

はじめに

mwasmはWebAssesembly Text Format (S式)用のプリプロセッサである。
独自仕様+JavaScriptの構文を採用し、条件付きコンパイル、実行時定数値の演算、マクロ、JavaScriptによるプリプロセス時のプログラミング(メタプログラミング)を実現する。

WebAssesembly は言語バインディングが進んでおり、テキスト・フォーマットでプログラミングする必要性はほぼない。このプリプロセッサはかつてアセンブリ言語に親しんだ私個人の趣味レベルの産物である。自身の成果物には有効活用しようとしているが、一般的にみて実用性は乏しいだろう。

しかし誰かの何かに役立つかもしれないと思いQiitaに書いておくことにした。

動機

wasmのテキスト・フォーマット(wat)はlisp風のS式で記述でき(ステートメント部分はフラット形式でも記述可)、アセンブリ言語というよりは高級言語的な佇まいである。

;; 2 + (4 * 5)
(i32.add 
 (i32.const 2)
 (i32.mul 
   (i32.const 4)
   (i32.const 5)  
 )
)

wat2wasmを使用すればwatでソースコードを書き、wasmバイナリを作ることも可能である。しかしwatは人間がコーディングするために作られたものではないからちと機能が足りない。そこでwatに、

  • 定数ラベルや定数式
  • メモリ配置定義
  • マクロ
  • 条件によるソースコード有効化・無効化
  • ソースインクルード

があれば、私が作ろうとしているモノくらいならwatですべて書くことができるようになるかなと考えた。
また実装そのものもnode.js + peg.jsで行えば比較的簡単にできると踏んで実装してみることにした。

2018年から2019年にかけて「オレオレ言語コンパイラ」を作っていた。「オレオレ言語」は実装上の課題があったり、仕様詳細について考えすぎたり、パーサの実装に使っていたpratt parserからpeg.jsの移行で思い悩んだり、ほかにやりたいことができたりして、動きだしたところで実装を止めてしまっている。プリプロセッサはそのオレオレ言語を実装する上での課題の一つである。私のスキルでは一度にすべてのことを解決するには難しいので、まずは単体でプリプロセッサを作ってノウハウを得ることも動機となっている。なのでいずれ「オレオレ言語つくり」は再開するつもりではある。というかこの記事を書いていたらその意欲が湧いてきた。。

プリプロセッサの設計について

私はJSを好んで使っており、その知識の上で作ったためこのような設計になってしまったが、S式で拡張するほうが文法が洗練され、使い勝手がいいものものになると思う。

何ができるのか

定数名(定数ラベル)の定義・使用

watでまず困るのは定数(constant)の取り扱いである。残念ながらwatでは定数名(定数ラベル)を定義して使用できないので、以下のコードのように定数をそのまま埋め込むことになる。

(module
  (memory $memory 1 )
  (export "memory" (memory $memory))
  (export "test" (func $test))
    (func $test (result i32)
      (if (result i32)
        (; offset bar(offset 4)の値とbit 1のandをとる;)
        (i32.and 
          (i32.load (i32.const 4)) (; offset bar ;)
          (i32.const 0x1) (; bit flag foo ;)
        )
        (then
          (; 立っていたら bar(offset 4)の値を返す ;)
          (i32.load (i32.const 4)) (; offset bar ;)
        )
        (else 
          (i32.const 0) (; エラーの意味 ;)
        )
      )
    )
)

上記のコードでオフセットが4でなく8に変わった場合、定数値の4をすべて探しそれがオフセットか文脈で判断し、変更しなければならない。これが非常に面倒である。また可読性を高めるためにコメントを余計に追加する必要もあるだろう。

同じコードをmwasmを使って書き直してみる。

{@
 // JS コードブロック
 // 定数値をコンテキスト・オブジェクトに定義する
 $.BAR = 4;
 $.FLAG_FOO = 1;
 $.ERROR = 0;
}
(module
  (memory $memory 1 )
  (export "memory" (memory $memory))
  (export "test" (func $test))
    (func $test (result i32)
      (if (result i32)
        (; offset bar(offset 4)の値とbit 1のandをとる;)
        (i32.and 
          (i32.load (i32.const %BAR;)) 
          (i32.const %FLAG_FOO;)
        )
        (then
          (; 立っていたら bar(offset 4)の値を返す ;)
          (i32.load (i32.const %BAR;))
        )
        (else 
          (i32.const %ERROR;) 
        )
      )
    )
)

定数名が利用できるとオフセットBARの値が変わっても1行目のコードブロック中の$.BARの値を変えるだけで変更できる。またコードの可読性が増す。

{@ }はJSコードブロックである。組み込みオブジェクトとして$を使用し、$のプロパティとして定数(プリプロセス時は変数)を定義することができる。さらにJSコードブロック中はJSのステートメントを書き、プリプロセス時に処理を行うことができる。

また%(定数名のみまたは式);で定義した定数をコード中に埋め込むことができる。定数同士の演算をして埋め込むことも可能である。

{@
 // JS コードブロック
 // 定数値をコンテキスト・オブジェクトに定義する
 $.R = 2;
 $.Math = Math; // Mathをプロパティにセットする
}
(module
  (memory $memory 1 )
  (export "memory" (memory $memory))
  (export "test" (func $test))
    (func $test (result f32)
      (f32.const %R * Math.sin(Math.PI/4);)
    )
)

プリプロセスの結果:

(module
  (memory $memory 1 )
  (export "memory" (memory $memory))
  (export "test" (func $test))
    (func $test (result f32)
      (f32.const 1.414213562373095) 
    )
)

JSコードブロックによるプリプロセス時のプログラミング(メタプログラミング)

このプリプロセッサの特徴的な部分である。プリプロセス時にJSのステートメントを実行する。JSコードブロックはステートメントを{}内に書く。{}は内部的にはfunctionオブジェクトで、戻り値を返すことでコードブロックを戻り値に置き換える。

以下のコードは線形メモリの先頭から32ビット整数値を順に16個addするコードである。

(module
  (memory $memory 1 )
  (export "memory" (memory $memory))
  (export "test" (func $test))
  (func $test (result i32)
    i32.const 0
    i32.load
    {
      let offset = 4;
      // JSによるWASMソースコード生成
      let instructions = '';
      for(let i = 0;i < 16; ++ i ){
        instructions += `
    i32.const ${offset}
    i32.load 
    i32.add`;
        offset += 4;
      }
      return instructions;
    }
  )
)

これをプロセスした結果は以下となる

(module
  (memory $memory 1 )
  (export "memory" (memory $memory))
  (export "test" (func $test))
  (func $test (result i32)
    i32.const 0
    i32.load
    i32.const 4
    i32.load 
    i32.add
    i32.const 8
    i32.load 
    i32.add
    i32.const 12
    i32.load 
    i32.add
    i32.const 16
    i32.load 
    i32.add
    i32.const 20
    i32.load 
    i32.add
    i32.const 24
    i32.load 
    i32.add
    i32.const 28
    i32.load 
    i32.add
    i32.const 32
    i32.load 
    i32.add
    i32.const 36
    i32.load 
    i32.add
    i32.const 40
    i32.load 
    i32.add
    i32.const 44
    i32.load 
    i32.add
    i32.const 48
    i32.load 
    i32.add
    i32.const 52
    i32.load 
    i32.add
    i32.const 56
    i32.load 
    i32.add
    i32.const 60
    i32.load 
    i32.add
    i32.const 64
    i32.load 
    i32.add
  )
)

通常はloop/block命令を使って書くべきところだが、この例のようにコードブロックによってループ・アンロールを行い、プリプロセス時にソースコードを生成することができる。

コードブロックはもう一つ{@ JSステートメント }という構文もあり、これはJSステートメントを実行するのみで戻り値は空白と同等となり埋め込まれない。

{@
 // JS コードブロック

 // $には関数などもセットできる
 $.circleArea = r =>{
   return r * r * Math.PI;
 }
 
 // $には配列などもセットできる
 $.array = [0,1,2,3,4,5];

 // thisにはrequireメソッドあり
 // ファイルシステムを使ってみる
 const fs = this.require('fs');
 // ソースファイルを読み込んで$にセットしてみる
 $.data = fs.readFileSync("./test8_inc.wat","utf-8");

}

(module
  (memory $memory 1 )
  (export "memory" (memory $memory))
  (export "test" (func $test))
  (func $test (result f32)
    (; 式の結果を定数として埋め込む ;)
    f32.const %circleArea(1) + array[4];
    (; fsによって読み込んだソースコードを展開する;)
    %data; 
  )
)

プリプロセスの結果:

(module
  (memory $memory 1 )
  (export "memory" (memory $memory))
  (export "test" (func $test))
  (func $test (result f32)
    f32.const 7.141592653589793
    f32.const 3
    f32.add
  )
)

コードブロック内は$オブジェクトによって他のコードブロックで定義したプロパティや、JSが持つビルトインオブジェクトなどにアクセスできる。またコードブロック内のthisは、プリプロセスに関する情報(メタ情報)を管理するContextオブジェクトのインタンスとなっている。thisを使うことでメタ情報にアクセスでき、より複雑な処理も(いちおう)できるようにしている。

さらに以下のような埋め込み書式がある。

{$ JS式}

JS式を実行し、結果をその位置に埋めこむ。

f32.const {$ Math.cos(Math.PI / 4)}

結果:

f32.const 0.7071067811865476

{$.プロパティ式}

プロパティ式を実行し、結果をその位置に埋め込む。

{@ 
$X = 1;
}

i32.const {$.X + 1}

結果:

i32.const 2

メモリ配置定義(メモリマップ)

WebAssemblyはデータ領域として線形メモリが利用できる。線形メモリは64kbyteのページ単位で取得でき、load/store命令を使ってデータを読み書きすることができる。

取得したメモリはオフセット0からはi32のデータXXを置く、そしてオフセット4にはi32のYYを置いて...みたいな感じでメモリにおけるデータの配置を考える。ここで問題になるのはメモリ配置のメンテナンスである。

watはオフセットを定数値として埋め込むことしかできないので、可読性やメンテナンス性が下がるし、メモリのXXの値をi32からi64に変更すると、後続のYY以降のオフセットをすべて4バイト分ずらしてやらなければならず、さらにずらした分だけソースコードに埋め込まれたオフセットをすべて修正しなくてはならない。配置するデータが多くなるとこのメンテナンスがとてつもなく面倒になる。

mwasmではその面倒さをなくすためにメモリマップを定義できるようにした。

{@map offset 0
  i32 XX;
  i32 YY; 
  i32 ZZ;
  i32 AA;
}

上の定義は線形メモリのオフセット0を開始位置とし、i32の大きさでXX,YY,ZZ,AAの4つのメモリラベルを定義している。このメモリラベルは&(メモリラベル);という構文でi32.const メモリラベルのオフセット値としてソースコード中に展開できる。

(module
  (memory $memory 1 )
  {@map
    i32 XX;
    i32 YY;
    i32 ZZ;
    i32 AA,BB;
  }
  (export "memory" (memory $memory))
  (export "memoryAdd" (func $memoryAdd))
  (func $memoryAdd
    (i32.store 
      (&AA;)
      (i32.add
        (i32.load (&XX;))
        (i32.load (&YY;))
      )
    )
  )
)

このコードをプリプロセスした結果は以下となる。プリプロセッサがオフセットを型の大きさとメモリマップの並び順から計算し、ソースコードに展開する。

(module
  (memory $memory 1 )
  (export "memory" (memory $memory))
  (export "memoryAdd" (func $memoryAdd))
  (func $memoryAdd
    (i32.store 
      (i32.const 12 (; AA ;))
      (i32.add
        (i32.load (i32.const 0 (; XX ;)))
        (i32.load (i32.const 4 (; YY ;)))
      )
    )
  )
)

上記のコードでZZの型をi64に変更した場合、メモリラベルのオフセットも再計算される。

(module
  (memory $memory 1 )
  {@map
    i32 XX;
    i32 YY;
    ;; ZZをi64値に変更する
    i64 ZZ; 
    i32 AA,BB;
  }
  (export "memory" (memory $memory))
  (export "memoryAdd" (func $memoryAdd))
  (func $memoryAdd
    (i32.store 
      (&AA;)
      (i32.add
        (i32.load (&XX;))
        (i32.load (&YY;))
      )
    )
  )
)

プリプロセスの結果は以下の通り、AAのオフセットが12->16に変更されている。

(module
  (memory $memory 1 )
  
  (export "memory" (memory $memory))
  (export "memoryAdd" (func $memoryAdd))
  (func $memoryAdd
    (i32.store 
      (i32.const 16 (; AAが12->16に変更されている ;))
      (i32.add
        (i32.load (i32.const 0 (; XX ;)))
        (i32.load (i32.const 4 (; YY ;)))
      )
    )
  )
)

メモリラベルの初期値設定

メモリラベルは初期値が設定可能である。

(module
  (memory $memory 1 )
  {@map
    i32 XX = 10;
    i32 YY[10] = [0,1,2,3,4,5,6,7,8,9];
  }
  (export "memory" (memory $memory))
  (export "memoryAdd" (func $memoryAdd))
  (func $memoryAdd (param $yy_index i32) (result i32)
    (i32.add
      (i32.load (&XX;))
      (i32.load 
        (i32.add
          (&YY;)
          (i32.shl
            (local.get $yy_index)
            (@YY;) ;; @(メモリラベル);でメモリラベルの型サイズのlog2を埋め込む
          )
        )
      )
    )
  )
)

プリプロセスの結果、初期値はdataセクションのバイト列に変換される。

(module
  (memory $memory 1 )
  (data (i32.const 0) "\0a\00\00\00")(data (i32.const 4) "\00\00\00\00\01\00\00\00\02\00\00\00\03\00\00\00\04\00\00\00\05\00\00\00\06\00\00\00\07\00\00\00\08\00\00\00\09\00\00\00")
  (export "memory" (memory $memory))
  (export "memoryAdd" (func $memoryAdd))
  (func $memoryAdd (param $yy_index i32) (result i32)
    (i32.add
      (i32.load (i32.const 0 (; XX ;)))
      (i32.load 
        (i32.add
          (i32.const 4 (; YY ;))
          (i32.shl
            (local.get $yy_index)
            (i32.const 2 (; YY ;)) ;; @(メモリラベル);でメモリラベルの型サイズのlog2を埋め込む
          )
        )
      )
    )
  )
)

初期値はJSコードブロックも使用でき、配列の初期値をプロシージャルに求めることも可能である。

(module
  (memory $memory 1 )
  {@map
    f32 sin_table[256] = {
      // sin関数のテーブル化
      let a = [];
      for(i = 0;i < 256;++i){
        a.push(Math.sin(Math.PI * 2 / i));
      }
      return a;
    };
  }
  (export "memory" (memory $memory))
  (export "sin_table" (func $sin_table))
  (func $sin_table (param $a i32 ) (result f32)
    (f32.load 
      (i32.add
        (&sin_table;)
        (i32.shl
          (local.get $a)
          (@sin_table;)
        )
      )
    )
  )
)

プリプロセスの結果は以下となる。

module
  (memory $memory 1 )
  (data (i32.const 0) "\00\00\c0\ff\32\31\8d\a5\32\31\0d\25\d7\b3\5d\3f\00\00\80\3f\71\78\73\3f\d7\b3\5d\3f\1c\26\48\3f\f3\04\35\3f\bb\8d\24\3f\18\79\16\3f\70\67\0a\3f\00\00\00\3f\32\f0\ed\3e\02\26\de\3e\c9\3f\d0\3e\15\ef\c3\3e\ab\f4\b8\3e\44\1d\af\3e\02\3f\a6\3e\7a\37\9e\3e\26\ea\96\3e\40\3f\90\3e\cd\22\8a\3e\ee\83\84\3e\90\a8\7e\3e\2a\0f\75\3e\91\26\6c\3e\87\dc\63\3e\36\21\5c\3e\cd\e6\54\3e\33\21\4e\3e\c2\c5\47\3e\14\cb\41\3e\d5\28\3c\3e\9e\d7\36\3e\d4\d0\31\3e\8d\0e\2d\3e\7c\8b\28\3e\db\42\24\3e\5b\30\20\3e\18\50\1c\3e\89\9e\18\3e\79\18\15\3e\fb\ba\11\3e\65\83\0e\3e\45\6f\0b\3e\60\7c\08\3e\a8\a8\05\3e\3b\f2\02\3e\5b\57\00\3e\db\ac\fb\3d\ef\db\f6\3d\34\39\f2\3d\1f\c2\ed\3d\54\74\e9\3d\a2\4d\e5\3d\fd\4b\e1\3d\81\6d\dd\3d\66\b0\d9\3d\05\13\d6\3d\d1\93\d2\3d\56\31\cf\3d\3a\ea\cb\3d\36\bd\c8\3d\17\a9\c5\3d\be\ac\c2\3d\1c\c7\bf\3d\32\f7\bc\3d\0f\3c\ba\3d\d0\94\b7\3d\a1\00\b5\3d\b6\7e\b2\3d\50\0e\b0\3d\bb\ae\ad\3d\4a\5f\ab\3d\5c\1f\a9\3d\57\ee\a6\3d\a8\cb\a4\3d\c5\b6\a2\3d\2a\af\a0\3d\59\b4\9e\3d\db\c5\9c\3d\3e\e3\9a\3d\17\0c\99\3d\fe\3f\97\3d\91\7e\95\3d\72\c7\93\3d\47\1a\92\3d\bb\76\90\3d\7b\dc\8e\3d\3b\4b\8d\3d\ae\c2\8b\3d\8d\42\8a\3d\94\ca\88\3d\80\5a\87\3d\13\f2\85\3d\11\91\84\3d\3f\37\83\3d\66\e4\81\3d\51\98\80\3d\97\a5\7e\3d\4a\27\7c\3d\5c\b5\79\3d\71\4f\77\3d\33\f5\74\3d\4d\a6\72\3d\6d\62\70\3d\46\29\6e\3d\8b\fa\6b\3d\f5\d5\69\3d\3d\bb\67\3d\20\aa\65\3d\5d\a2\63\3d\b4\a3\61\3d\ea\ad\5f\3d\c3\c0\5d\3d\07\dc\5b\3d\7f\ff\59\3d\f6\2a\58\3d\3a\5e\56\3d\19\99\54\3d\63\db\52\3d\ea\24\51\3d\81\75\4f\3d\fc\cc\4d\3d\33\2b\4c\3d\fc\8f\4a\3d\30\fb\48\3d\a8\6c\47\3d\40\e4\45\3d\d4\61\44\3d\41\e5\42\3d\66\6e\41\3d\21\fd\3f\3d\53\91\3e\3d\de\2a\3d\3d\a2\c9\3b\3d\84\6d\3a\3d\67\16\39\3d\2f\c4\37\3d\c2\76\36\3d\07\2e\35\3d\e3\e9\33\3d\3e\aa\32\3d\01\6f\31\3d\15\38\30\3d\62\05\2f\3d\d4\d6\2d\3d\54\ac\2c\3d\ce\85\2b\3d\2e\63\2a\3d\60\44\29\3d\51\29\28\3d\ef\11\27\3d\26\fe\25\3d\e6\ed\24\3d\1d\e1\23\3d\ba\d7\22\3d\ad\d1\21\3d\e7\ce\20\3d\56\cf\1f\3d\ec\d2\1e\3d\9b\d9\1d\3d\54\e3\1c\3d\08\f0\1b\3d\aa\ff\1a\3d\2d\12\1a\3d\82\27\19\3d\9e\3f\18\3d\75\5a\17\3d\f8\77\16\3d\1e\98\15\3d\da\ba\14\3d\20\e0\13\3d\e6\07\13\3d\20\32\12\3d\c4\5e\11\3d\c8\8d\10\3d\22\bf\0f\3d\c6\f2\0e\3d\ad\28\0e\3d\cc\60\0d\3d\19\9b\0c\3d\8c\d7\0b\3d\1d\16\0b\3d\c1\56\0a\3d\71\99\09\3d\25\de\08\3d\d3\24\08\3d\75\6d\07\3d\01\b8\06\3d\72\04\06\3d\be\52\05\3d\e0\a2\04\3d\cf\f4\03\3d\84\48\03\3d\f9\9d\02\3d\26\f5\01\3d\06\4e\01\3d\91\a8\00\3d\c2\04\00\3d\23\c5\fe\3c\f4\83\fd\3c\eb\45\fc\3c\fc\0a\fb\3c\1b\d3\f9\3c\3d\9e\f8\3c\57\6c\f7\3c\5e\3d\f6\3c\48\11\f5\3c\09\e8\f3\3c\98\c1\f2\3c\ea\9d\f1\3c\f6\7c\f0\3c\b2\5e\ef\3c\13\43\ee\3c\12\2a\ed\3c\a5\13\ec\3c\c2\ff\ea\3c\61\ee\e9\3c\79\df\e8\3c\02\d3\e7\3c\f3\c8\e6\3c\43\c1\e5\3c\ec\bb\e4\3c\e4\b8\e3\3c\24\b8\e2\3c\a5\b9\e1\3c\5e\bd\e0\3c\49\c3\df\3c\5e\cb\de\3c\96\d5\dd\3c\ea\e1\dc\3c\52\f0\db\3c\c9\00\db\3c\48\13\da\3c\c7\27\d9\3c\41\3e\d8\3c\ae\56\d7\3c\0a\71\d6\3c\4d\8d\d5\3c\73\ab\d4\3c\73\cb\d3\3c\4a\ed\d2\3c\f1\10\d2\3c\62\36\d1\3c\98\5d\d0\3c\8d\86\cf\3c\3d\b1\ce\3c\a1\dd\cd\3c\b5\0b\cd\3c\73\3b\cc\3c\d7\6c\cb\3c\db\9f\ca\3c\7a\d4\c9\3c")
  (export "memory" (memory $memory))
  (export "sin_table" (func $sin_table))
  (func $sin_table (param $a i32 ) (result f32)
    (f32.load 
      (i32.add
        (i32.const 0 (; sin_table ;))
        (i32.shl
          (local.get $a)
          (i32.const 2 (; sin_table ;))
        )
      )
    )
  )
)

メモリラベルの添え字(配列)

メモリラベルに添え字[]を定義した場合は、配列風にアクセスすることが可能。

(module
(memory 0)
{@map
  (; i32 10個分のメモリエリアを定義 ;)
  i32 label[10];
}
(export "test" (func $test))
(func $test (result i32)
  (i32.add
    (i32.load (&label[4];))
    (i32.load (&label[2];))
  )
)
)

プリプロセスの結果:

(module
(memory 0)
(export "test" (func $test))
(func $test (result i32)
  (i32.add
    (i32.load (i32.const 16 (; label[4] ;)))
    (i32.load (i32.const 8 (; label[2] ;)))
  )
)
)

添え字にはコンテキスト変数に定義したものも使用できる。

{@
$.INDEX = 4;
}
(module
(memory 0)
{@map
  i32 label[10];
}
(export "test" (func $test))
(func $test (result i32)
  (i32.add
    (i32.load (&label[INDEX++];))
    (i32.load (&label[INDEX];))
  )
)
)

プリプロセスの結果

(module
(memory 0)
(export "test" (func $test))
(func $test (result i32)
  (i32.add
    (i32.load (i32.const 16 (; label[INDEX++] つまり label[4] ;)))
    (i32.load (i32.const 20 (; label[INDEX] つまり label[5];)))
  )
)
)

構造体

メモリラベルのグループ化のために構造体が利用できる。

(module
  (memory 0)
  ;; 構造体の定義
  {@struct A 
    i32 a;
    i32 b;
  }
  ;; メモリーマップ
  {@map offset 0;
    A a;;;型名のように定義できる。
  }

  (export "test" (func $test))
  (func $test (result i32)
    (i32.add 
      (i32.load (&a.a;)) ;;メンバはドット演算子で指定
      (i32.load (&a.b;))
    )
  )
)

プリプロセスの結果:

(module
  (memory 0)
  (export "test" (func $test))
  (func $test (result i32)
    (i32.add 
      (i32.load (i32.const 0))
      (i32.load (i32.const 4))
    )
  )
)

構造体中では構造体を使用でき、階層的な構造も表現することができる。

{@struct A 
  i32 a;
}

{@struct B 
  i32 b;
  A a;
}

{@struct C 
  i32 c;
  B b;
}


(module
(memory 0)
{@map 
  C c[10];
}
(export "test" (func $test))
(func $test (result i32)
  (i32.add 
    (i32.load (&c[0].b.a.a;))
    (&c[0].c;)
  )
)
)

構造体のオフセット・サイズの取得

構造体は構造体の最初のメンバを0とした時のオフセット・サイズを&構造体名;#構造体名;で取得できる。

{@struct A 
  i32 a;
  i32 b;
}

(module
(memory 0)
{@map
}
(export "test" (func $test))
(func $test (result i32)
    (&A;)
)
)

{@macro_def} / {@end_macro_def}

いわゆる引数付きマクロを定義するもの。
コードブロックさえあればマクロはいらんかな...と思ってたけどコード書いてみるとやっぱり必要だったので追加した。

マクロ定義は以下の構文となっている。

@macro_def マクロ名(マクロ引数,マクロ引数 ... )
 (マクロ・ボディ)
@end_macro_def

定義例

{@macro_def c(a)}
(i32.const a)
{@end_macro_def}

定義したマクロは{@@マクロ名 パラメータ,パラメータ... }で使用できる。

{@@c 32}
{@@c 64}

プリプロセスの結果:

(i32.const 32)
(i32.const 64)

マクロ定義の利用例:

(; マクロ定義 ;)
{@macro_def t(a)}
(i32.const a)
{@end_macro_def}

{@macro_def offset (a,b)}
(a.add 
  (a.const 10)
  {@@t b}
)
{@end_macro_def}

{@macro_def macro_fp(b)}
(f32.add
  b
  (f32.const 0.5)
)
{@end_macro_def}

{@map
  i32 test_offset;
}

(module
(memory 0)
(export "test" (func $test))
(func $test (result i32)
  (local $a i32)
  {@@offset i32,0x0001}
)
(func $testfp (result f32)
  {@@macro_fp
    (f32.const 1)
  }
)
)

プリプロセス後の結果:

(module
(memory 0)
(export "test" (func $test))
(func $test (result i32)
  (local $a i32)
  (i32.add 
   (i32.const +10)
   (i32.const 0x0001)
  )
)
(func $testfp (result f32)
 (f32.add
  (f32.const +1)
  (f32.const +0.5)
 )
)
)

{@include JS式}

JS式の結果得られるpathにあるファイルをインクルードする。

{@include './test_inc.mwat'}

{@if JS式} ソース1 [{@else} ソース2] {@endif}

JS式が真の場合ソース1を埋め込み、そうでない場合はソース2を埋め込む。
@else ソース2は省略可能である。

{@if $.X < 1}

...ソース1

{@else}

...ソース2

{@endif}

実装について

  • 実装はnode.jsを使用した。
  • パッケージのバンドルにはrollupを使用している。
  • 構文パーサはpeg.jsを使用している。

プリプロセスを実行部分はスクラッチで書いている。プリプロセス後のソースコードはwabtに引き渡し、wasmバイナリにすることも可能になっている。

このツールの状態

作りたいプロダクトのツールとして使っていて、wasmモジュールはほぼすべてこのプリプロセッサを使って作っている。が、ツールとしての完成度はまだ低い。このドキュメントを書いている途中でもバグや仕様の不足を見つけて修正・実装しているくらいなので。テストを網羅的に実施し、バグや実装の不足部分をもっと叩き出さないといけないだろう。

あとはエラーメッセージだよなあ。。ウーム。。

レポジトリ URL

https://github.com/sfpgmr/mwasm

mwasmのインストール方法

  1. git cloneする。
  2. クローンしたディレクトリにて npm installを実行。
  3. さらにnpm install -gする。

サンプルの実行

npm run test-allでテストが走る。

コマンドライン

bin/mwasm コマンドで実行する。

mwasm ソーステキスト名 [-o wasmバイナリ出力ファイル名]

-oを指定しない場合はコンソールにプリプロセス後のソースコードを表示する。

実装例

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; mwasm サンプルコード
;; PSG エミュレータ
;;
;; 以下のコード参考にをWebAssembly化してみた
;; https://github.com/digital-sound-antiques/emu2149
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
{@
  //;; 定数定義 ;;
  $.GETA_BITS = 24;
  $.EMU2149_VOL_YM2149 = 0;
  $.EMU2149_VOL_AY_3_8910 = 1;
  $.EMU2149_VOL_DEFAULT  =  $.EMU2149_VOL_AY_3_8910;
  $.SHIFT_BITS = 1 << $.GETA_BITS;
  $.SHIFT_BITS_MASK = (1 << $.GETA_BITS) - 1;
  $.REG_MAX = 16;
}



(module
  (export "setQuality" (func $set_quality))
  (export "setRate" (func $set_rate))
  (export "init" (func $init))
  (export "setVolumeMode" (func $set_volume_mode))
  (export "setMask" (func $set_mask))
  (export "toggleMask" (func $toggle_mask))
  (export "readIo" (func $read_io))
  (export "readReg" (func $read_reg))
  (export "writeIo" (func $write_io))
  (export "updateOutput" (func $update_output))
  (export "mixOutput" (func $mix_output))
  (export "calc" (func $calc))
  (export "reset" (func $reset))
  (export "writeReg" (func $write_reg))
  (export "memory" (memory $memory))
  (memory $memory 1 )
  ;; 構造体 定義;;
  {@struct PSG
  i32 regmsk[REG_MAX] = [
      0xff, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0x1f, 0x3f,
      0x1f, 0x1f, 0x1f, 0xff, 0xff, 0x0f, 0xff, 0xff
  ];
      (;; Volume Table ;;)
      i32 voltbl_[64] = [ 0 , 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09,
    0x0B, 0x0D, 0x0F, 0x12, 0x16, 0x1A, 0x1F, 0x25, 0x2D, 0x35, 0x3F, 0x4C,
    0x5A, 0x6A, 0x7F, 0x97, 0xB4, 0xD6, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x01,
    0x02, 0x02, 0x03, 0x03, 0x05, 0x05, 0x07, 0x07, 0x0B, 0x0B, 0x0F, 0x0F,
    0x16, 0x16, 0x1F, 0x1F, 0x2D, 0x2D, 0x3F, 0x3F, 0x5A, 0x5A, 0x7F, 0x7F,
    0xB4, 0xB4, 0xFF, 0xFF];
      i32 reg[REG_MAX];
      i32 voltbl;
      i32 out;

      i32 clk, rate, base_incr, quality;

      i32 count[3];
      i32 volume[3];
      i32 freq[3];
      i32 edge[3];
      i32 tmask[3];
      i32 nmask[3];
      i32 mask;

      i32 base_count;

      i32 env_volume;
      i32 env_ptr;
      i32 env_face;

      i32 env_continue;
      i32 env_attack;
      i32 env_alternate;
      i32 env_hold;
      i32 env_pause;
      i32 env_reset;

      i32 env_freq;
      i32 env_count;

      i32 noise_seed;
      i32 noise_count;
      i32 noise_freq;

      (;; rate converter ;;)
      i32 realstep;
      i32 psgtime;
      i32 psgstep;

      (;; I/O Ctrl ;;)
      i32 adr;

      (;; output of channels ;;)
      i32 ch_out[3];


  }

  ;; リニアメモリ配置定義
  {@map offset 0x1000;
    PSG psg;
    f32 OutputBuffer[128];
  }

  (func $internal_refresh
     (i32.store (i32.const 0 ) (i32.load (&PSG.clk;)))
     (if
      ;; condition
      (i32.load (&psg.quality;))
      (then 
        (i32.store (&psg.base_incr;) (i32.const {$ 1 << $.GETA_BITS}))
        (i32.store 
          (&psg.realstep;)
          (i32.div_u 
            (i32.const {$ 1 << 31})
            (i32.load (&psg.rate;))
          ) 
        )
        (i32.store
          (&psg.psgstep;)
          (i32.div_u
            (i32.const {$ 1 << 31})
            (i32.shr_u (i32.load (&psg.clk;)) (i32.const 4))
          )
        )
        (i32.store (&psg.psgtime;) (i32.const 0))
      )
      (else
        (i32.store
          (&psg.base_incr;)
          (i32.trunc_f64_u 
            (f64.div 
              (f64.mul 
                (f64.convert_i32_u (i32.load (&psg.clk;)))
                (f64.const {$ 1 << $.GETA_BITS})
              )
              (f64.mul
                (f64.convert_i32_u (i32.load (&psg.rate;)))
                (f64.const 16)
              )
            )
          )
        )
      )

    )
  )

  (func $set_rate (param $r i32) 
    (i32.store (&psg.rate;) 
     (select (local.get $r) (i32.const 44100) (local.get $r))
    )
    (call $internal_refresh)
  )

  
  (func $set_quality (param $q i32)
    (i32.store 
      (&psg.quality;)
      (local.get $q)
    )
    (call $internal_refresh)
  )

  (func $init (param $c i32) (param $r i32)
    (call $set_volume_mode (i32.const {$.EMU2149_VOL_DEFAULT}))
    (i32.store (&psg.clk;) (local.get $c))
    (i32.store 
      (&psg.rate;)
      (select
        (i32.const 44100)
        (local.get $r)
        (i32.eqz (local.get $r))
      )
    )
    (call $set_quality (i32.const 0))
  )
  
  (func $set_volume_mode (param $type i32)
    (i32.store 
      (&psg.voltbl;) 
      (i32.add 
        (&psg.voltbl_;) 
        (i32.shl (local.get $type) (@psg.voltbl + 5;))
      )
    )
  )

  (func $set_mask (param $mask i32) (result i32)
    (i32.load (&psg.mask;))
    (i32.store (&psg.mask;) (local.get $mask))
  )

  (func $toggle_mask (param $mask i32) (result i32)
    (i32.load (&psg.mask;))
    (i32.store (&psg.mask;)
      (i32.xor (i32.load (&psg.mask;)) (i32.const 0xffff_ffff))
    )
  )

  (func $reset
    (local $c i32)
    (local $work i32)
    (local $size i32)
    (local $count i32)
    (local $freq i32)
    (local $edge i32)
    (local $volume i32)
    (local $ch_out i32)

    (local.set $count (&psg.count;))
    (local.set $freq (&psg.freq;))
    (local.set $edge (&psg.edge;))
    (local.set $volume (&psg.volume;))
    (local.set $ch_out (&psg.ch_out;))

    (i32.store (&psg.base_count;) (i32.const 0))
    (local.set $c (i32.const 3))

    (block $exit
      (loop $loop
        (br_if $exit (i32.eqz (local.get $c)))
        (local.set $c (i32.sub (local.get $c) (i32.const 1)))
        (i32.store (local.get $count) (i32.const 0x1000))
        (i32.store (local.get $freq) (i32.const 0))
        (i32.store (local.get $edge) (i32.const 0))
        (i32.store (local.get $volume) (i32.const 0))
        (i32.store (local.get $ch_out) (i32.const 0))
        (local.set $count (i32.add (local.get $count) (#psg.count;)))
        (local.set $freq (i32.add (local.get $freq) (#psg.freq;)))
        (local.set $edge (i32.add (local.get $edge) (#psg.edge;)))
        (local.set $volume (i32.add (local.get $volume) (#psg.volume;)))
        (local.set $ch_out (i32.add (local.get $ch_out) (#psg.ch_out;)))
        (br $loop)
      )
    )
    
    (i32.store (&psg.mask;) (i32.const 0))

    ;; レジスタの初期化
    (local.set $c (i32.const 16))
    (local.set $work (&psg.reg;))
    (block $exit_reg
      (loop $loop_reg
        (br_if $exit_reg (i32.eqz(local.get $c) ))
        (local.set $c (i32.sub (local.get $c) (i32.const 1)))
        (i32.store (local.get $work) (i32.const 0))
        (local.set $work (i32.add (local.get $work) (#psg.reg;)) )
        (br $loop_reg)
      )
    )

    (i32.store (&psg.adr;) (i32.const 0))
    (i32.store (&psg.noise_seed;) (i32.const 0xffff))
    (i32.store (&psg.noise_count;) (i32.const 0x40))
    (i32.store (&psg.noise_freq;) (i32.const 0))

    (i32.store (&psg.env_volume;) (i32.const 0))
    (i32.store (&psg.env_ptr;) (i32.const 0))
    (i32.store (&psg.env_freq;) (i32.const 0))
    (i32.store (&psg.env_count;) (i32.const 0))
    (i32.store (&psg.env_pause;) (i32.const 1))

    (i32.store (&psg.out;) (i32.const 0))

  )

  (func $read_io (result i32)
    (i32.load
      (i32.add 
        (&psg.reg;)
        (i32.shl (i32.load (&psg.adr;)) (i32.const {$ Math.log2($.psg.adr[$attributes].size) | 0 }))
      ) 
    )
  )
  (func $read_reg (param $reg i32) (result i32)
    (i32.load
      (i32.add 
        (&psg.reg;)
        (i32.shl (local.get $reg) (@psg.reg;))      
      )
    )
  )
  
  (func $write_io (param $adr i32) (param $val i32)
    (if 
      (i32.and (local.get $adr) (i32.const 1))
      (then
        (call $write_reg (i32.load (&psg.adr;)) (local.get $val))
      )
      (else
        (i32.store (&psg.adr;) (i32.and (local.get $val) (i32.const 0x1f))) 
      )
    )
  )

  (func $update_output 
    (local $incr i32)
    (local $noise i32)
    (local $i i32)
    (local $offset i32)

    (i32.store (&psg.base_count;) 
      (i32.add 
        (i32.load (&psg.base_count;))
        (i32.load (&psg.base_incr;))
      )
    )
    (local.set $incr (i32.shr_u (i32.load (&psg.base_count;)) (i32.const {$.GETA_BITS})))
    (i32.store 
      (&psg.base_count;)
      (i32.and 
        (i32.load(&psg.base_count;))
        (i32.const {$.SHIFT_BITS_MASK})
      )
    )

    ;; Envelope
    (i32.store 
      (&psg.env_count;)
      (i32.add 
        (i32.load (&psg.env_count;)) 
        (local.get $incr)
      )
    )
    
    (block $exit_envelope
      (loop $loop_envelope
        (br_if $exit_envelope
          (i32.or 
            (i32.lt_u (i32.load (&psg.env_count;)) (i32.const 0x10000))
            (i32.eqz (i32.load (&psg.env_freq;) ))
          )
        )
        (if (i32.eqz (i32.load (&psg.env_pause;)))
          (then
            (if (i32.load (&psg.env_face;))
              (then
                (i32.store 
                  (&psg.env_ptr;)
                  (i32.and 
                    (i32.add
                      (i32.load (&psg.env_ptr;))
                      (i32.const 1)
                    )
                    (i32.const 0x3f)
                  )
                )
              )
              (else
                (i32.store 
                  (&psg.env_ptr;)
                  (i32.and 
                    (i32.add
                      (i32.load (&psg.env_ptr;))
                      (i32.const 0x3f)
                    )
                    (i32.const 0x3f)
                  )
                )
              
              )
            )
          )
        )

        (if
          (i32.and (i32.load (&psg.env_ptr;)) (i32.const 0x20))
          (then
            (if 
              (i32.load (&psg.env_continue;))
              (then
                (if
                  (i32.xor 
                    (i32.load (&psg.env_alternate;))
                    (i32.load (&psg.env_hold;))
                  )
                  (then
                    (i32.store (&psg.env_face;)
                      (i32.xor
                        (i32.load (&psg.env_face;))
                        (i32.const 1)
                      )
                    )
                  )
                )
                (if
                  (i32.load (&psg.env_hold;))
                  (then
                    (i32.store (&psg.env_pause;) (i32.const 1))
                  )
                )
                (i32.store 
                  (&psg.env_ptr;)
                  (select
                    (i32.const 0)
                    (i32.const 0x1f)
                    (i32.load (&psg.env_face;))
                  )
                )
              )
              (else
                (i32.store (&psg.env_pause;) (i32.const 1))
                (i32.store (&psg.env_ptr;) (i32.const 0))
              )
            )
          )
        )
        (i32.store
          (&psg.env_count;)
          (i32.sub
            (i32.load (&psg.env_count;)) 
            (i32.load (&psg.env_freq;)) 
          ) 
        ) 
        (br $loop_envelope)
      )
    )

    ;; Noise
    (i32.store 
      (&psg.noise_count;)
      (i32.add
        (i32.load (&psg.noise_count;))
        (local.get $incr)
      )
    )
    (if
      (i32.and (i32.load (&psg.noise_count;)) (i32.const 0x40))
      (then
        (if
          (i32.and 
            (i32.load (&psg.noise_seed;))
            (i32.const 1)
          )
          (then
            (i32.store
              (&psg.noise_seed;)
              (i32.xor 
                (i32.load (&psg.noise_seed;))
                (i32.const 0x24000)
              )
            )
          )
        )
        (i32.store 
            (&psg.noise_seed;)
            (i32.shr_u 
              (i32.load (&psg.noise_seed;))
              (i32.const 1)
            )
        )
        (i32.store
          (&psg.noise_count;)
          (i32.sub 
            (i32.load (&psg.noise_count;))
            (select 
              (i32.load (&psg.noise_freq;))
              (i32.const 2)
              (i32.load (&psg.noise_freq;))
            )
          )
        )
      )
    )
    
    (local.set $noise
      (i32.and 
        (i32.load (&psg.noise_seed;))
        (i32.const 1)
      )
    )

    ;; Tone
    (local.set $i (i32.const 3))
    (block $tone_exit
      (loop $tone_loop
        (br_if $tone_exit (i32.eqz (local.get $i)))
        (local.set $i
          (i32.sub (local.get $i) (i32.const 1))
        )

        (local.set $offset
            (i32.shl
              (local.get $i)
              (i32.const 2)
            )
        )
       (i32.store 
          (i32.add 
            (&psg.count;)
            (local.get $offset)
          )
          (i32.add
            (i32.load
              (i32.add 
                (&psg.count;)
                (local.get $offset)
              ) 
            )
            (local.get $incr)
          )
        )
        (if
          (i32.and 
            (i32.load
              (i32.add 
                (&psg.count;)
                (local.get $offset)
              )
            )
            (i32.const 0x1000)
          )
          (then
            (if
              (i32.gt_u
                (i32.load
                  (i32.add 
                    (&psg.freq;)
                    (local.get $offset)
                  )
                )
                (i32.const 1)
              )
              (then
                (i32.store
                  (i32.add 
                    (&psg.edge;)
                    (local.get $offset)
                  )
                  (i32.xor
                    (i32.load
                      (i32.add 
                        (&psg.edge;)
                        (local.get $offset)
                      )
                    )
                    (i32.const 0x1 )
                  )
                )
                (i32.store
                  (i32.add 
                    (&psg.count;)
                    (local.get $offset)
                  )
                  (i32.sub
                    (i32.load
                      (i32.add 
                        (&psg.count;)
                        (local.get $offset)
                      )
                    )
                    (i32.load
                      (i32.add 
                        (&psg.freq;)
                        (local.get $offset)
                      )
                    )
                  )
                )
              )
              (else
                (i32.store
                  (i32.add 
                    (&psg.edge;)
                    (local.get $offset)
                  )
                  (i32.const 1)

                )
              )
            )
          )
        )


        
       
        (if
          (i32.and
            (select (i32.const 1) (i32.const 0) 
              (i32.or
                (i32.load (i32.add (&psg.tmask;) (local.get $offset)))
                (i32.load (i32.add (&psg.edge;) (local.get $offset)))
              )
            )
            (select (i32.const 1) (i32.const 0)
              (i32.or
                (i32.load (i32.add (&psg.nmask;) (local.get $offset)))
                (local.get $noise)
              )
            )
          )
          (then
            (if
              (i32.eqz 
                (i32.and
                  (i32.load
                    (i32.add 
                      (&psg.volume;)
                      (local.get $offset)
                    )
                  )
                  (i32.const 32)
                )
              )
              (then
                (i32.store
                  (i32.add
                    (&psg.ch_out;)
                    (local.get $offset)
                  )
                  (i32.add
                    (i32.load 
                      (i32.add
                        (&psg.ch_out;)
                        (local.get $offset)
                      )
                    )
                    (i32.shl
                      (i32.load
                        (i32.add
                          (i32.load (&psg.voltbl;))
                          (i32.shl
                            (i32.and 
                              (i32.load
                                (i32.add
                                  (&psg.volume;)
                                  (local.get $offset)
                                )
                              )
                              (i32.const 31)
                            )
                            (@psg.voltbl;)
                          )
                        )
                      )
                      (i32.const 4)
                    )
                  )
                )  
              )
              (else
                (i32.store
                  (i32.add
                    (&psg.ch_out;)
                    (local.get $offset)
                  )
                  (i32.add
                    (i32.load
                      (i32.add
                        (&psg.ch_out;)
                        (local.get $offset)
                      )
                    )
                    (i32.shl
                      (i32.load
                        (i32.add 
                          (i32.load (&psg.voltbl;))
                          (i32.shl 
                            (i32.load (&psg.env_ptr;))
                            (@psg.voltbl_;)
                          )
                        )
                      )
                      (i32.const 4)
                    )
                  )
                )
              )
            )
          )

        )

        (i32.store 
          (i32.add
            (&psg.ch_out;)
            (local.get $offset)
          )
          (i32.shr_u
            (i32.load
              (i32.add
                (&psg.ch_out;)
                (local.get $offset)
              )
            )
            (i32.const 1)
          )
        )
        (br $tone_loop)
      )
    )
  )

  (func $mix_output (result i32)
    (i32.store
      (&psg.out;)
      (i32.add
        (i32.load (&psg.ch_out;))
        (i32.add
          (i32.load (&psg.ch_out[1];))
          (i32.load (&psg.ch_out[2];))
        )
      )
    )
    (i32.load (&psg.out;))
  )

  (func $calc (result i32)
    (if (i32.eqz (i32.load (&psg.quality;)))
      (then
        call $update_output
        call $mix_output
        return
      )
    )
    (block $rate_loop_exit
      (loop $rate_loop
        (br_if $rate_loop_exit 
          (i32.le_u (i32.load(&psg.realstep;)) (i32.load(&psg.psgtime;)))
        )
        (i32.store
          (&psg.psgtime;)
          (i32.add
            (i32.load(&psg.psgtime;))
            (i32.load(&psg.psgstep;))
          )
        )
        call $update_output
        (br $rate_loop)
      )
    )
    (i32.store
      (&psg.psgtime;)
      (i32.sub
        (i32.load(&psg.psgtime;))
        (i32.load(&psg.realstep;))
      )
    )

    call $mix_output
  )



  (func $write_reg (param $reg i32) (param $val i32) (local $c i32) (local $w i32)
    (if (i32.gt_u (local.get $reg) (i32.const 15))
      (then
        return
      )
    )
    (local.set $val
      (i32.and
        (local.get $val)
        (i32.load 
          (i32.add 
            (&psg.regmsk;)
            (i32.shl 
              (local.get $reg)
              (@psg.regmsk;)
            )
          )
        )
      ) 
    )

    (i32.store
      (i32.add 
        (&psg.reg;)
        (i32.shl 
          (local.get $reg)
          (@psg.reg;)
        )
      )
      (local.get $val)
    )
    
    (block $default
      (br_if $default (i32.gt_u (local.get $reg) (i32.const 13))) 
      (block $reg0_5
        (block $reg6
          (block $reg7
            (block $reg8_10
              (block $reg11_12
                (block $reg13
                  (br_table 
                    $reg0_5 $reg0_5 $reg0_5 $reg0_5 $reg0_5 $reg0_5 $reg6 $reg7 
                    $reg8_10 $reg8_10 $reg8_10 $reg11_12 $reg11_12 $reg13
                    (local.get $reg)
                  )
                )
                ;; reg 13
                (i32.store
                  (&psg.env_continue;) 
                  (i32.and
                    (i32.shr_u (local.get $val) (i32.const 3))
                    (i32.const 1)
                  )
                )
                (i32.store
                  (&psg.env_attack;) 
                  (i32.and
                    (i32.shr_u (local.get $val) (i32.const 2))
                    (i32.const 1)
                  )
                )
                (i32.store
                  (&psg.env_alternate;) 
                  (i32.and
                    (i32.shr_u (local.get $val) (i32.const 1))
                    (i32.const 1)
                  )
                )
                (i32.store
                  (&psg.env_hold;) 
                  (i32.and
                    (local.get $val)
                    (i32.const 1)
                  )
                )
                (i32.store
                  (&psg.env_face;) 
                  (i32.load (&psg.env_attack;)) 
                )
                (i32.store
                  (&psg.env_pause;) 
                  (i32.const 0) 
                )
                (i32.store
                  (&psg.env_count;) 
                  (i32.sub 
                    (i32.const 0x10000)
                    (i32.load (&psg.env_freq;))
                  )
                )
                (i32.store
                  (&psg.env_ptr;)
                  (select
                    (i32.const 0)
                    (i32.const 0x1f)
                    (i32.load (&psg.env_face;))
                  )
                )
                return
              )
              ;; reg11-12
              (i32.store
                (&psg.env_freq;)
                (i32.add 
                  (i32.shl
                    (i32.load (&psg.reg[12];))
                    (i32.const 8)
                  )
                  (i32.load(&psg.reg[11];))
                )
              )
              return
            )
            ;; reg 8-10
            (i32.store
              (i32.add 
                (&psg.volume;)
                (i32.shl 
                  (i32.sub (local.get $reg) (i32.const 8)) 
                  (@psg.volume;)
                )
              )
              (i32.shl
                (local.get $val)
                (i32.const 1)
              )
            )
            return
          )
          ;; reg 7
          ;;(local.set $val (i32.xor (i32.const 0xff) (local.get $val)))
          (i32.store (&psg.tmask[0];) (i32.and (local.get $val) (i32.const 1)))
          (i32.store (&psg.tmask[1];) (i32.and (local.get $val) (i32.const 2)))
          (i32.store (&psg.tmask[2];) (i32.and (local.get $val) (i32.const 4)))

          (i32.store (&psg.nmask[0];) (i32.and (local.get $val) (i32.const 8)))
          (i32.store (&psg.nmask[1];) (i32.and (local.get $val) (i32.const 16)))
          (i32.store (&psg.nmask[2];) (i32.and (local.get $val) (i32.const 32)))

          return
        )
        ;; reg 6
        (i32.store 
          (&psg.noise_freq;)
          (i32.shl
            (i32.and
              (local.get $val)
              (i32.const 31)
            )
            (i32.const 1)
          )
        )
        return
      ) 
      ;; reg 0-5
      (local.set $c 
        (i32.shr_u
          (local.get $reg)
          (i32.const 1)
        )
      )

      (i32.store
        (i32.add
          (&psg.freq;)
          (i32.shl (local.get $c) (@psg.freq;))
        )
        (i32.add
          (i32.shl 
            (i32.and 
              (i32.load
                (i32.add 
                  (&psg.reg;)
                  (i32.shl
                    (i32.add
                      (i32.shl
                        (local.get $c)
                        (i32.const 1)
                      )
                      (i32.const 1)
                    )
                    (@psg.reg;)
                  )
                )
              )
              (i32.const 15)
            )
            (i32.const 8)
          )
          (i32.load
            (i32.add
              (&psg.reg;)
              (i32.shl
                (i32.shl 
                  (local.get $c)
                  (i32.const 1)
                )
                (@psg.reg;)
              )
            )
          )
        )
      )
      return
    )
  )
)

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