mwasmのプリプロセッサを作っている

公開:2019-05-26 15:06
更新:2019-05-26 15:06
カテゴリ:wasm,プリプロセッサ

wasmのテキスト・モードではメモリのオフセットアドレスにラベルを付けることができない。なので可読性が著しく下がる。

(module
  (memory $memory 1 )
  (export "memory" (memory $memory))
  (func $test 
    (i32.load (i32.const 1024 ));; <-- メモリオフセットは定数でしか表せない。
    .
    .

  )
)

そのためラベル付けして、それをwat2wasmに渡す前にオフセット・アドレスに置換できるようなプリプロセッサを作っている。

(module
  (memory $memory 1 )
  (export "memory" (memory $memory))
  ;; メモリラベルの定義
  {@map
    i32 output;
    i32 input;
  }
  (func $test 
    (i32.load (&input;)) ;; 
    .
    .
  )
)

これをプリプロセスすると以下のコードに置換される

(module
  (memory $memory 1 )
  (export "memory" (memory $memory))
  (func $test 
    (i32.load (i32.const 4)) ;; <-- メモリオフセットに置換
    .
    .
  )
)

ついでに条件付きインクルードやソース・インクルードをサポートするようにしようと思い作っているところ。

https://github.com/sfpgmr/mwasm

このプリプロセッサの特徴はプリプロセスをJSで書けるというところ。

{@
  $.X = 1;
  $.Y = 2;
}
(module
  (func $test2 (result i32)
    i32.const @X;; comment
    i32.const {$ $.X + $.Y }
    i32.add
    {
      // JSによるWASMソースコード生成
      let instructions = '';
      for(let i = 0;i < 4; ++ i ){
        ++$.X; 
        instructions += `
  i32.const ${$.X + $.Y}
  i32.add`;
      }
      return instructions;
    }
  )
)

mwasmのプリプロセッサを今作っているが、ようやくメモリ・マップの定義ができるようになって、ぼちぼちpsgエミュレータのコードを書いているところ。

PSGエミュレータ・コードは以下のCソースを参考にしている。というかそれをまんまwatに書き直そうとしている。

digital-sound-antiques/emu2149: A YM2149 (aka PSG) emulator written in C.

以下のコードは書きかけのPSGエミュレータ・コードである。

{@
  $.GETA_BITS = 24;
  $.EMU2149_VOL_YM2149 = 0;
  $.EMU2149_VOL_AY_3_8910 = 1;
  $.EMU2149_VOL_DEFAULT  =  $.EMU2149_VOL_AY_3_8910;
}
{@struct CHANNEL
  i32 vol[3];
}
{@struct PSG
i32 regmsk[16] = [
    0xff, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0x1f, 0x3f,
    0x1f, 0x1f, 0x1f, 0xff, 0xff, 0x0f, 0xff, 0xff
];
    (;; Volume Table ;;)
    i32 voltbl_[64] = [ 0 , 0b0000_0001, 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[0x20];
    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;
    CHANNEL ch_c;

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

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


}


(module
  (memory $memory 1 )
  (export "memory" (memory $memory))
{@map
  i32 OutputBuffer[1024];
  PSG psg;
}
  (export "test" (func $test))
  (func $test (result i32)
    (i32.load (&psg.ch_c.vol[1] + X*4;))
    i32.const 1
    i32.add 
  )

  (func $internal_refresh
    (if
      ;; condition
      (i32.eqz (i32.load (&psg.quality;)))
      (then
        (i32.store
          (&psg.base_incr;)
          (i32.wrap_i64 
            (i64.div_u 
              (i64.mul 
                (i64.extend_i32_u (i32.load (&psg.clk;)))
                (i64.const {$ 1 << $.GETA_BITS})
              )
              (i64.shl
                (i64.extend_i32_u (i32.load (&psg.rate;)))
                (i32.const 4)
              )
            )
          )
        )
      )
      (else 
        (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))
      )
    )
  )

  (func $set_rate (param $r i32) 
    (i32.store (&psg.rate;) (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;) (i32.const 0))
    (i32.store 
      (&psg.rate;)
      (select
        (local.get $r)
        (i32.const 44100)
        (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) (i32.const 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.tee $c (i32.const 3))

    (block $exit
      (loop $loop
        (br_if $exit (i32.eqz))
        (local.tee $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.tee $c (i32.const 16))
    (local.set $work (&psg.reg;))
    (block $exit_reg
      (loop $loop_reg
        (br_if $exit_reg (i32.eqz ))
        (local.tee $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.mask;)) )
        (br $loop)
      )
    )

    (i32.store (&psg.adr;) (i32.const 0))
    (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 0))

    (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 (result i32)
;;
 ;; )
)

これを今作っているプリプロセッサで処理するとこんなwatに変換される。

(module (memory $memory 1 ) (export "memory" (memory $memory)) (data (i32.const 4096) "\ff\00\00\00\0f\00\00\00\ff\00\00\00\0f\00\00\00\ff\00\00\00\0f\00\00\00\1f\00\00\00\3f\00\00\00\1f\00\00\00\1f\00\00\00\1f\00\00\00\ff\00\00\00\ff\00\00\00\0f\00\00\00\ff\00\00\00\ff\00\00\00")(data (i32.const 4160) "\00\00\00\00\00\00\00\00\01\00\00\00\02\00\00\00\02\00\00\00\03\00\00\00\03\00\00\00\04\00\00\00\05\00\00\00\06\00\00\00\07\00\00\00\09\00\00\00\1f\00\00\00\21\00\00\00\23\00\00\00\12\00\00\00\16\00\00\00\2e\00\00\00\33\00\00\00\25\00\00\00\41\00\00\00\35\00\00\00\53\00\00\00\60\00\00\00\6e\00\00\00\7e\00\00\00\93\00\00\00\97\00\00\00\f4\01\00\00\16\02\00\00\53\02\00\00\53\02\00\00\00\00\00\00\00\00\00\00\01\00\00\00\01\00\00\00\02\00\00\00\02\00\00\00\03\00\00\00\03\00\00\00\05\00\00\00\05\00\00\00\07\00\00\00\07\00\00\00\1f\00\00\00\1f\00\00\00\23\00\00\00\23\00\00\00\16\00\00\00\16\00\00\00\33\00\00\00\33\00\00\00\41\00\00\00\41\00\00\00\53\00\00\00\53\00\00\00\6e\00\00\00\6e\00\00\00\93\00\00\00\93\00\00\00\f4\01\00\00\f4\01\00\00\53\02\00\00\53\02\00\00") (export "test" (func $test)) (func $test (result i32) (i32.load (i32.const 632)) i32.const 1 i32.add ) (func $internal_refresh (if  (i32.eqz (i32.load (i32.const 4564))) (then (i32.store (i32.const 4560) (i32.wrap_i64 (i64.div_u (i64.mul (i64.extend_i32_u (i32.load (i32.const 4552))) (i64.const 16777216) ) (i64.shl (i64.extend_i32_u (i32.load (i32.const 4556))) (i32.const 4) ) ) ) ) ) (else (i32.store (i32.const 4560) (i32.const 16777216)) (i32.store (i32.const 4704) (i32.div_u (i32.const -2147483648) (i32.load (i32.const 4556)) ) ) (i32.store (i32.const 4712) (i32.div_u (i32.const -2147483648) (i32.shr_u (i32.load (i32.const 4552)) (i32.const 4)) ) ) (i32.store (i32.const 4708) (i32.const 0)) ) ) ) (func $set_rate (param $r i32) (i32.store (i32.const 4556) (local.get $r)) (call $internal_refresh) ) (func $set_quality (param $q i32) (i32.store (i32.const 4564) (local.get $q)) (call $internal_refresh) ) (func $init (param $c i32) (param $r i32) (call $set_volume_mode (i32.const 1)) (i32.store (i32.const 4552) (i32.const 0)) (i32.store (i32.const 4556) (select (local.get $r) (i32.const 44100) (i32.eqz (local.get $r)) ) ) (call $set_quality (i32.const 0)) ) (func $set_volume_mode (param $type i32) (i32.store (i32.const 4544) (i32.add (i32.const 4160) (i32.shl (local.get $type) (i32.const 5)))) ) (func $set_mask (param $mask i32) (result i32) (i32.load (i32.const 4640)) (i32.store (i32.const 4640) (local.get $mask)) ) (func $toggle_mask (param $mask i32) (result i32) (i32.load (i32.const 4640)) (i32.store (i32.const 4640) (i32.xor (i32.load (i32.const 4640)) (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 (i32.const 4568)) (local.set $freq (i32.const 4592)) (local.set $edge (i32.const 4604)) (local.set $volume (i32.const 4580)) (local.set $ch_out (i32.const 4732)) (i32.store (i32.const 4644) (i32.const 0)) (local.tee $c (i32.const 3)) (block $exit (loop $loop (br_if $exit (i32.eqz)) (local.tee $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) (i32.const 4))) (local.set $freq (i32.add (local.get $freq) (i32.const 4))) (local.set $edge (i32.add (local.get $edge) (i32.const 4))) (local.set $volume (i32.add (local.get $volume) (i32.const 4))) (local.set $ch_out (i32.add (local.get $ch_out) (i32.const 4))) (br $loop) ) ) (i32.store (i32.const 4640) (i32.const 0))  (local.tee $c (i32.const 16)) (local.set $work (i32.const 4416)) (block $exit_reg (loop $loop_reg (br_if $exit_reg (i32.eqz )) (local.tee $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) (i32.const 4640)) ) (br $loop) ) ) (i32.store (i32.const 4728) (i32.const 0)) (i32.store (i32.const 4696) (i32.const 0x40)) (i32.store (i32.const 4700) (i32.const 0)) (i32.store (i32.const 4648) (i32.const 0)) (i32.store (i32.const 4652) (i32.const 0)) (i32.store (i32.const 4684) (i32.const 0)) (i32.store (i32.const 4688) (i32.const 0)) (i32.store (i32.const 4676) (i32.const 0)) (i32.store (i32.const 4548) (i32.const 0)) ) (func $read_io (result i32) (i32.load (i32.add (i32.const 4416) (i32.shl (i32.load (i32.const 4728) (i32.const 2))) ) ) )   )