load(libdir + "wasm.js");

var caught = false;
try {
    wasmBinaryToText(new Int8Array(1));
} catch (e) {
    caught = true;
}
assertEq(caught, true);

assertErrorMessage(() => wasmBinaryToText(wasmTextToBinary(`(module (func (result i32) (f32.const 13.37)))`)), WebAssembly.CompileError, /type mismatch/);

function runTest(code) {
    var expected = wasmTextToBinary(code);
    var s = wasmBinaryToText(expected);
    print("TEXT: " + s);
    var roundtrip = wasmTextToBinary(s);
    assertDeepEq(expected, roundtrip);
}

// Smoke test
runTest(`
(module
  (func (param i32) (result f64)
     (local $l f32)
     (block
        (set_local $l (f32.const 0.0))
        (loop $exit $cont
           (br_if $exit (get_local 0))
           (br 2)
        )
        (drop (if f64 (i32.const 1)
           (f64.min (f64.neg (f64.const 1)) (f64.const 0))
           (f64.add (f64.const 0.5) (f64.load offset=0 (i32.const 0)) )
        ))
     )
     (i32.store16 (i32.const 8) (i32.const 128))

     (return (f64.const 0))
  )
  (export "test" 0)
  (memory 1 10)
)`);

// Constants, stores and loads
runTest(`
(module (func
  (local i32) (local f32) (local f64)
  (drop (i32.const 0))
  (drop (i32.const 100002))
  (drop (f32.const 0.0))
  (drop (f32.const 1.5))
  (drop (f64.const 0.0))
  (drop (f64.const -10.25))
  (i32.store (i32.const 0) (i32.load (i32.const 0)))
  (i32.store8 (i32.const 1) (i32.load8_s (i32.const 2)))
  (i32.store8 (i32.const 3) (i32.load8_u (i32.const 4)))
  (i32.store16 (i32.const 2) (i32.load16_s (i32.const 0)))
  (i32.store16 (i32.const 1) (i32.load16_u (i32.const 0)))
  (f32.store (i32.const 5) (f32.load (i32.const 6)))
  (f64.store (i32.const 5) (f64.load (i32.const 6)))
  (set_local 0 (get_local 0))
  (set_local 2 (get_local 2))
)(memory 100))`);

// Branching
runTest(`
(module
(func
  (block (block (block (nop))))
  (block (loop ))
  (if (i32.const 0) (block $label (nop)))
  (if (i32.const 1) (nop) (loop $exit $cont (block )))
  (block $l (br $l))
  (block $m (block (block (br $m))))
  (block $k (br_if 0 (i32.const 0)) (return))
  (block $n (block (block (br_if 2 (i32.const 1)) (nop))))
  (block $1 (block $2 (block $3 (br_table $2 $3 $1 (i32.const 1)) )) (nop))
  (loop $exit $cont (br_if $cont (i32.const 0)) (nop))
  (return)
)
(func (result f32) (return (f32.const -0.5)))
(memory 0)
)`);

// i32, f32 and f64 operations
runTest(`
(module
  (func $iadd (param $x i32) (param $y i32) (result i32) (i32.add (get_local $x) (get_local $y)))
  (func $isub (param $x i32) (param $y i32) (result i32) (i32.sub (get_local $x) (get_local $y)))
  (func $imul (param $x i32) (param $y i32) (result i32) (i32.mul (get_local $x) (get_local $y)))
  (func $idiv_s (param $x i32) (param $y i32) (result i32) (i32.div_s (get_local $x) (get_local $y)))
  (func $idiv_u (param $x i32) (param $y i32) (result i32) (i32.div_u (get_local $x) (get_local $y)))
  (func $irem_s (param $x i32) (param $y i32) (result i32) (i32.rem_s (get_local $x) (get_local $y)))
  (func $irem_u (param $x i32) (param $y i32) (result i32) (i32.rem_u (get_local $x) (get_local $y)))
  (func $iand (param $x i32) (param $y i32) (result i32) (i32.and (get_local $x) (get_local $y)))
  (func $ior (param $x i32) (param $y i32) (result i32) (i32.or (get_local $x) (get_local $y)))
  (func $ixor (param $x i32) (param $y i32) (result i32) (i32.xor (get_local $x) (get_local $y)))
  (func $ishl (param $x i32) (param $y i32) (result i32) (i32.shl (get_local $x) (get_local $y)))
  (func $ishr_s (param $x i32) (param $y i32) (result i32) (i32.shr_s (get_local $x) (get_local $y)))
  (func $ishr_u (param $x i32) (param $y i32) (result i32) (i32.shr_u (get_local $x) (get_local $y)))
  (func $iclz (param $x i32) (result i32) (i32.clz (get_local $x)))
  (func $ictz (param $x i32) (result i32) (i32.ctz (get_local $x)))
  (func $ipopcnt (param $x i32) (result i32) (i32.popcnt (get_local $x)))
  (func $ieq (param $x i32) (param $y i32) (result i32) (i32.eq (get_local $x) (get_local $y)))
  (func $ine (param $x i32) (param $y i32) (result i32) (i32.ne (get_local $x) (get_local $y)))
  (func $ilt_s (param $x i32) (param $y i32) (result i32) (i32.lt_s (get_local $x) (get_local $y)))
  (func $ilt_u (param $x i32) (param $y i32) (result i32) (i32.lt_u (get_local $x) (get_local $y)))
  (func $ile_s (param $x i32) (param $y i32) (result i32) (i32.le_s (get_local $x) (get_local $y)))
  (func $ile_u (param $x i32) (param $y i32) (result i32) (i32.le_u (get_local $x) (get_local $y)))
  (func $igt_s (param $x i32) (param $y i32) (result i32) (i32.gt_s (get_local $x) (get_local $y)))
  (func $igt_u (param $x i32) (param $y i32) (result i32) (i32.gt_u (get_local $x) (get_local $y)))
  (func $ige_s (param $x i32) (param $y i32) (result i32) (i32.ge_s (get_local $x) (get_local $y)))
  (func $ige_u (param $x i32) (param $y i32) (result i32) (i32.ge_u (get_local $x) (get_local $y)))

  (func $fadd (param $x f32) (param $y f32) (result f32) (f32.add (get_local $x) (get_local $y)))
  (func $fsub (param $x f32) (param $y f32) (result f32) (f32.sub (get_local $x) (get_local $y)))
  (func $fmul (param $x f32) (param $y f32) (result f32) (f32.mul (get_local $x) (get_local $y)))
  (func $fdiv (param $x f32) (param $y f32) (result f32) (f32.div (get_local $x) (get_local $y)))
  (func $fsqrt (param $x f32) (result f32) (f32.sqrt (get_local $x)))
  (func $fmin (param $x f32) (param $y f32) (result f32) (f32.min (get_local $x) (get_local $y)))
  (func $fmax (param $x f32) (param $y f32) (result f32) (f32.max (get_local $x) (get_local $y)))
  (func $fceil (param $x f32) (result f32) (f32.ceil (get_local $x)))
  (func $ffloor (param $x f32) (result f32) (f32.floor (get_local $x)))
  (func $fabs (param $x f32) (result f32) (f32.abs (get_local $x)))
  (func $fneg (param $x f32) (result f32) (f32.neg (get_local $x)))

  (func $dadd (param $x f64) (param $y f64) (result f64) (f64.add (get_local $x) (get_local $y)))
  (func $dsub (param $x f64) (param $y f64) (result f64) (f64.sub (get_local $x) (get_local $y)))
  (func $dmul (param $x f64) (param $y f64) (result f64) (f64.mul (get_local $x) (get_local $y)))
  (func $ddiv (param $x f64) (param $y f64) (result f64) (f64.div (get_local $x) (get_local $y)))
  (func $dceil (param $x f64) (result f64) (f64.ceil (get_local $x)))
  (func $dfloor (param $x f64) (result f64) (f64.floor (get_local $x)))
  (func $dabs (param $x f64) (result f64) (f64.abs (get_local $x)))
  (func $dneg (param $x f64) (result f64) (f64.neg (get_local $x)))
(memory 0))`);

// conversions
runTest(`
(module
  (func $itrunc_s_f32 (param $x f32) (result i32) (i32.trunc_s/f32 (get_local $x)))
  (func $itrunc_u_f32 (param $x f32) (result i32) (i32.trunc_u/f32 (get_local $x)))
  (func $itrunc_s_f64 (param $x f64) (result i32) (i32.trunc_s/f64 (get_local $x)))
  (func $itrunc_u_f64 (param $x f64) (result i32) (i32.trunc_u/f64 (get_local $x)))
  (func $fconvert_s_i32 (param $x i32) (result f32) (f32.convert_s/i32 (get_local $x)))
  (func $dconvert_s_i32 (param $x i32) (result f64) (f64.convert_s/i32 (get_local $x)))
  (func $fconvert_u_i32 (param $x i32) (result f32) (f32.convert_u/i32 (get_local $x)))
  (func $dconvert_u_i32 (param $x i32) (result f64) (f64.convert_u/i32 (get_local $x)))
  (func $dpromote_f32 (param $x f32) (result f64) (f64.promote/f32 (get_local $x)))
  (func $fdemote_f64 (param $x f64) (result f32) (f32.demote/f64 (get_local $x)))
(memory 0))`);

// function calls
runTest(`
(module
  (type $type1 (func (param i32) (result i32)))
  (import $import1 "mod" "test" (param f32) (result f32))
  (table anyfunc (elem $func1 $func2))
  (func $func1 (param i32) (param f32) (nop))
  (func $func2 (param i32) (result i32) (get_local 0))
  (func $test
    (call $func1
      (call_indirect $type1 (i32.const 2) (i32.const 1))
      (call $import1 (f32.const 1.0))
    )
  )
  (export "test" $test)
  (memory 1)
)`);

// default memory export from binaryen
runTest(`(module (func (nop)) (memory 0 65535))`);

// stack-machine code that isn't directly representable as an AST
runTest(`
(module
   (func (result i32)
     (local $x i32)
     i32.const 100
     set_local $x
     i32.const 200
     set_local $x
     i32.const 400
     set_local $x
     i32.const 2
     i32.const 16
     nop
     set_local $x
     i32.const 3
     i32.const 17
     set_local $x
     i32.const 18
     set_local $x
     i32.lt_s
     if i32
       i32.const 101
       set_local $x
       i32.const 8
       i32.const 102
       set_local $x
     else
       i32.const 103
       set_local $x
       i32.const 900
       i32.const 104
       set_local $x
       i32.const 105
       set_local $x
     end
     i32.const 107
     set_local $x
     get_local $x
     i32.add
     i32.const 106
     set_local $x
   )
   (export "" 0)
)`);

// more stack-machine code that isn't directly representable as an AST
runTest(`
(module
   (func $return_void)

   (func (result i32)
     (local $x i32)
     i32.const 0
     block
       i32.const 1
       set_local $x
     end
     i32.const 2
     set_local $x
     i32.const 3
     loop
       i32.const 4
       set_local $x
     end
     i32.const 5
     set_local $x
     i32.add
     call $return_void
   )
   (export "" 0)
)`);

runTest(`
  (module
   (func $func
    block $block
     i32.const 0
     if
      i32.const 0
      if
      end
     else
     end
    end
   )
  (export "" 0)
)`);

// Branch table.
runTest(`(module
    (func (export "run") (param $p i32) (local $n i32)
        i32.const 0
        set_local $n
        loop $outer
            block $c block $b block $a
                loop $inner
                    get_local $p
                    br_table $b $a $c $inner $outer
                end $inner
            end $a
                get_local $n
                i32.const 1
                i32.add
                set_local $n
            end $b
                block
                    get_local $n
                    i32.const 2
                    i32.add
                    set_local $n
                end
            end $c
        end $outer
    )
)`);
