diff --git a/ml-proto/host/lexer.mll b/ml-proto/host/lexer.mll index 9749c76e64..0c15e03a87 100644 --- a/ml-proto/host/lexer.mll +++ b/ml-proto/host/lexer.mll @@ -129,12 +129,15 @@ rule token = parse | "nop" { NOP } | "block" { BLOCK } - | "if" { IF } | "loop" { LOOP } - | "label" { LABEL } - | "break" { BREAK } + | "br" { BR } + | "br_if" { BRIF } + | "br_unless" { BRUNLESS } + | "if" { IF } + | "forever" { FOREVER } | "case" { CASE } | "fallthrough" { FALLTHROUGH } + | "break" { BREAK } | "call" { CALL } | "call_import" { CALLIMPORT } | "call_indirect" { CALLINDIRECT } @@ -157,6 +160,7 @@ rule token = parse | (ixx as t)".store"(mem_size as sz)"/"(align as a) { STOREWRAP (wrapop t sz a) } + | (nxx as t)".br_switch" { BRSWITCH (value_type t) } | (nxx as t)".switch" { SWITCH (value_type t) } | (nxx as t)".const" { CONST (value_type t) } diff --git a/ml-proto/host/parser.mly b/ml-proto/host/parser.mly index 954cb46dd7..e716ad62c7 100644 --- a/ml-proto/host/parser.mly +++ b/ml-proto/host/parser.mly @@ -86,6 +86,12 @@ let bind_label c x = Error.error x.at ("duplicate label " ^ x.it); {c with labels = VarMap.add x.it 0 (VarMap.map ((+) 1) c.labels)} +let sugar_label c = + (label c ("(syntax sugar)" @@ at())) @@ at() + +let bind_sugar_label c = + {c with labels = VarMap.add "(syntax sugar)" 0 (VarMap.map ((+) 1) c.labels)} + let anon space n = space.count <- space.count + n let anon_func c = anon c.funcs 1 @@ -95,13 +101,14 @@ let anon_label c = {c with labels = VarMap.map ((+) 1) c.labels} %} %token INT FLOAT TEXT VAR TYPE LPAR RPAR -%token NOP BLOCK IF LOOP LABEL BREAK SWITCH CASE FALLTHROUGH +%token NOP BLOCK LOOP BR BRIF BRUNLESS BRSWITCH %token CALL CALLIMPORT CALLINDIRECT RETURN %token GETLOCAL SETLOCAL LOAD STORE %token CONST UNARY BINARY COMPARE CONVERT %token FUNC PARAM RESULT LOCAL MODULE MEMORY SEGMENT IMPORT EXPORT TABLE %token PAGESIZE MEMORYSIZE RESIZEMEMORY %token ASSERTINVALID ASSERTRETURN ASSERTRETURNNAN ASSERTTRAP INVOKE +%token IF FOREVER SWITCH CASE FALLTHROUGH BREAK %token EOF %token INT @@ -110,6 +117,7 @@ let anon_label c = {c with labels = VarMap.map ((+) 1) c.labels} %token VAR %token TYPE %token CONST +%token BRSWITCH %token SWITCH %token UNARY %token BINARY @@ -160,20 +168,50 @@ expr : ; oper : | NOP { fun c -> Nop } - | BLOCK expr expr_list { fun c -> Block ($2 c :: $3 c) } - | IF expr expr expr { fun c -> If ($2 c, $3 c, $4 c) } - | IF expr expr /* Sugar */ - { let at1 = ati 1 in fun c -> If ($2 c, $3 c, Nop @@ at1) } - | LOOP expr_block { fun c -> Loop ($2 c) } - | LABEL expr_block { fun c -> Label ($2 (anon_label c)) } - | LABEL bind_var expr_block /* Sugar */ - { fun c -> Label ($3 (bind_label c $2)) } - | BREAK var expr_opt { fun c -> Break ($2 c label, $3 c) } - | BREAK { let at = at() in fun c -> Break (0 @@ at, None) } /* Sugar */ + | BLOCK expr_list bind_var { fun c -> Block ($2 (bind_label c $3)) } + /* Sugar: block with no label */ + | BLOCK expr_list { fun c -> Block ($2 (anon_label c)) } + | LOOP bind_var expr_list { fun c -> Loop ($3 (bind_label c $2)) } + /* Sugar: loop with additional label at end */ + | LOOP bind_var expr_list bind_var + { fun c -> let at = at() in + Loop ((fun c -> [Block ($3 (bind_label c $4)) @@ at]) + (bind_label c $2)) } + | BR var expr_opt { fun c -> Br ($2 c label, $3 c) } + | BRIF var expr expr_opt { fun c -> BrIf ($2 c label, $3 c, $4 c) } + | BRUNLESS var expr expr_opt { fun c -> BrUnless ($2 c label, $3 c, $4 c) } + | BRSWITCH expr var br_switch_arms expr_opt + { let at = at() in + fun c -> BrSwitch ($1 @@ at, $2 c, $3 c label, + List.map (fun (x,y) -> ((literal at x $1).it, y c label)) $4, + $5 c) } + /* Sugar: if statements */ + | IF expr expr + { fun c -> let at = at() in + let c' = (anon_label c) in + Block [BrUnless (0 @@ at, $2 c', None) @@ at; $3 c'] } + /* Sugar: if-else statements */ + | IF expr expr expr + { fun c -> let at = at() in + let c' = (anon_label c) in + Block [Block [BrUnless (0 @@ at, $2 c', None) @@ at; + Br (1 @@ at, Some ($3 (anon_label c'))) @@ at] @@ at; + $4 c'] } + /* Sugar: forever-loop statements */ + | FOREVER expr_list + { fun c -> let at = at() in + let c' = (bind_sugar_label c) in + let c'' = (anon_label c') in + Block [Loop (List.append ($2 c'') [Br (0 @@ at, None) @@ at]) @@ at] + } + /* Sugar: switch statements */ | SWITCH expr arms { let at1 = ati 1 in - fun c -> let x, y = $3 c in - Switch ($1 @@ at1, $2 c, List.map (fun a -> a $1) x, y) } + fun c -> let c' = (bind_sugar_label c) in + let x, y = $3 c' in + Block [Switch ($1 @@ at1, $2 c', List.map (fun a -> a $1) x, y) @@ at1] } + /* Sugar: break statements */ + | BREAK expr_opt { fun c -> Br (sugar_label c, $2 c) } | CALL var expr_list { fun c -> Call ($2 c func, $3 c) } | CALLIMPORT var expr_list { fun c -> CallImport ($2 c import, $3 c) } | CALLINDIRECT var expr expr_list @@ -202,12 +240,12 @@ expr_list : | /* empty */ { fun c -> [] } | expr expr_list { fun c -> $1 c :: $2 c } ; + expr_block : | expr { $1 } | expr expr expr_list /* Sugar */ { let at = at() in fun c -> Block ($1 c :: $2 c :: $3 c) @@ at } ; - fallthrough : | /* empty */ { false } | FALLTHROUGH { true } @@ -225,8 +263,14 @@ arm : arms : | expr { fun c -> [], $1 c } | arm arms { fun c -> let x, y = $2 c in $1 c :: x, y } -; +br_switch_arm : + | INT var { ($1, $2) } +; +br_switch_arms : + | /* empty */ { [] } + | br_switch_arm br_switch_arms { $1 :: $2 } +; /* Functions */ @@ -234,8 +278,9 @@ func_fields : | /* empty */ /* Sugar */ { let at = at() in fun c -> {params = []; result = None; locals = []; body = Nop @@ at} } - | expr_block - { fun c -> {params = []; result = None; locals = []; body = $1 c} } + | expr_list + { let at = at() in + fun c -> {params = []; result = None; locals = []; body = Block ($1 c) @@ at} } | LPAR PARAM value_type_list RPAR func_fields { fun c -> anon_locals c $3; let f = $5 c in {f with params = $3 @ f.params} } diff --git a/ml-proto/spec/ast.ml b/ml-proto/spec/ast.ml index 1b3ee7a922..95c317b5df 100644 --- a/ml-proto/spec/ast.ml +++ b/ml-proto/spec/ast.ml @@ -75,12 +75,14 @@ type literal = value Source.phrase type expr = expr' Source.phrase and expr' = | Nop (* do nothing *) - | Block of expr list (* execute in sequence *) - | If of expr * expr * expr (* conditional *) - | Loop of expr (* infinite loop *) - | Label of expr (* labelled expression *) - | Break of var * expr option (* break to n-th surrounding label *) + | Block of expr list (* execute in sequence, label at end *) + | Loop of expr list (* execute in sequence, label at beginning *) + | Br of var * expr option (* branch to label *) + | BrIf of var * expr * expr option (* branch to label if expr is true *) + | BrUnless of var * expr * expr option (* branch to label if expr is false *) + | BrSwitch of value_type * expr * var * (value * var) list * expr option | Switch of value_type * expr * arm list * expr (* switch, latter expr is default *) + (* branch to label selected by expr *) | Call of var * expr list (* call function *) | CallImport of var * expr list (* call imported function *) | CallIndirect of var * expr * expr list (* call function through table *) diff --git a/ml-proto/spec/check.ml b/ml-proto/spec/check.ml index 28afc891f0..3f31d8e13a 100644 --- a/ml-proto/spec/check.ml +++ b/ml-proto/spec/check.ml @@ -118,26 +118,34 @@ let rec check_expr c et e = check_type None et e.at | Block es -> - require (es <> []) e.at "invalid block"; + let c' = {c with labels = et :: c.labels} in let es', eN = Lib.List.split_last es in - List.iter (check_expr c None) es'; - check_expr c et eN + List.iter (check_expr c' None) es'; + check_expr c' et eN; - | If (e1, e2, e3) -> - check_expr c (Some Int32Type) e1; - check_expr c et e2; - check_expr c et e3 + | Loop es -> + let c' = {c with labels = et :: c.labels} in + let es', eN = Lib.List.split_last es in + List.iter (check_expr c' None) es'; + check_expr c' et eN; - | Loop e1 -> - check_expr c None e1 + | Br (x, eo) -> + check_expr_option c (label c x) eo e.at - | Label e1 -> - let c' = {c with labels = et :: c.labels} in - check_expr c' et e1 + | BrIf (x, ec, eo) -> + check_expr c (Some Int32Type) ec; + check_expr_option c (label c x) eo e.at - | Break (x, eo) -> + | BrUnless (x, ec, eo) -> + check_expr c (Some Int32Type) ec; check_expr_option c (label c x) eo e.at + | BrSwitch (t, ec, default, labels, eo) -> + (* TODO: Check that cases are unique. *) + require (t.it = Int32Type || t.it = Int64Type) t.at "invalid br_switch type"; + check_expr c (Some t.it) ec; + check_expr_option c (label c default) eo e.at + | Switch (t, e1, arms, e2) -> require (t.it = Int32Type || t.it = Int64Type) t.at "invalid switch type"; (* TODO: Check that cases are unique. *) diff --git a/ml-proto/spec/eval.ml b/ml-proto/spec/eval.ml index 121d5fa51a..719d429968 100644 --- a/ml-proto/spec/eval.ml +++ b/ml-proto/spec/eval.ml @@ -126,26 +126,48 @@ let rec eval_expr (c : config) (e : expr) = None | Block es -> - let es', eN = Lib.List.split_last es in - List.iter (fun eI -> ignore (eval_expr c eI)) es'; - eval_expr c eN - - | If (e1, e2, e3) -> - let i = int32 (eval_expr c e1) e1.at in - eval_expr c (if i <> Int32.zero then e2 else e3) - - | Loop e1 -> - ignore (eval_expr c e1); - eval_expr c e + let module L = MakeLabel () in + let c' = {c with labels = L.label :: c.labels} in + (try + (let es', eN = Lib.List.split_last es in + List.iter (fun eI -> ignore (eval_expr c' eI)) es'; + eval_expr c' eN) + with L.Label vo -> vo) - | Label e1 -> + | Loop es -> let module L = MakeLabel () in let c' = {c with labels = L.label :: c.labels} in - (try eval_expr c' e1 with L.Label vo -> vo) + (try + (let es', eN = Lib.List.split_last es in + List.iter (fun eI -> ignore (eval_expr c' eI)) es'; + eval_expr c' eN) + with L.Label _ -> eval_expr c e) - | Break (x, eo) -> + | Br (x, eo) -> raise (label c x (eval_expr_option c eo)) + | BrIf (x, ec, eo) -> + let i = int32 (eval_expr c ec) ec.at in + if i <> Int32.zero then + raise (label c x (eval_expr_option c eo)) + else + None + + | BrUnless (x, ec, eo) -> + let i = int32 (eval_expr c ec) ec.at in + if i = Int32.zero then + raise (label c x (eval_expr_option c eo)) + else + None + + | BrSwitch (_t, ec, default, labels, eo) -> + let e = some (eval_expr c ec) ec.at in + raise (label c + (try + let i, l = List.find (fun (i, l) -> i = e) labels in l + with Not_found -> default) + (eval_expr_option c eo)) + | Switch (_t, e1, arms, e2) -> let vo = some (eval_expr c e1) e1.at in (match List.fold_left (eval_arm c vo) `Seek arms with diff --git a/ml-proto/test/br_switch.wase b/ml-proto/test/br_switch.wase new file mode 100644 index 0000000000..2d53248991 --- /dev/null +++ b/ml-proto/test/br_switch.wase @@ -0,0 +1,60 @@ +(module + ;; Statement br_switch + (func $stmt (param $i i32) (result i32) + (local $j i32) + (set_local $j (i32.const 100)) + (block (block (block (block (block (block (block (block (block + + (i32.br_switch (get_local $i) + $default 0 $case0 1 $case1 2 $case2 3 $case3 4 $case4 5 $case5 6 $case6) + $case0) (return (get_local $i)) + $case1) + $case2) + $case3) (set_local $j (i32.sub (i32.const 0) (get_local $i))) (br $end) + $case4) (br $end) + $case5) (set_local $j (i32.const 101)) (br $end) + $case6) (set_local $j (i32.const 101)) + $default) (set_local $j (i32.const 102)) + $end) + (return (get_local $j)) + ) + + ;; Expression br_switch + (func $expr (param $i i64) (result i64) + (local $j i64) + (set_local $j (i64.const 100)) + (return + (block (block (block (block (block (block (block + (i64.br_switch (get_local $i) + $default 0 $case0 1 $case1 2 $case2 3 $case3 6 $case6) + $case0) (return (get_local $i)) + $case1) + $case2) + $case3) (br $exit (i64.sub (i64.const 0) (get_local $i))) + $case6) (set_local $j (i64.const 101)) + $default) (get_local $j) + $exit) + ) + ) + + (export "stmt" $stmt) + (export "expr" $expr) +) + +(assert_eq (invoke "stmt" (i32.const 0)) (i32.const 0)) +(assert_eq (invoke "stmt" (i32.const 1)) (i32.const -1)) +(assert_eq (invoke "stmt" (i32.const 2)) (i32.const -2)) +(assert_eq (invoke "stmt" (i32.const 3)) (i32.const -3)) +(assert_eq (invoke "stmt" (i32.const 4)) (i32.const 100)) +(assert_eq (invoke "stmt" (i32.const 5)) (i32.const 101)) +(assert_eq (invoke "stmt" (i32.const 6)) (i32.const 102)) +(assert_eq (invoke "stmt" (i32.const 7)) (i32.const 102)) +(assert_eq (invoke "stmt" (i32.const -10)) (i32.const 102)) + +(assert_eq (invoke "expr" (i64.const 0)) (i64.const 0)) +(assert_eq (invoke "expr" (i64.const 1)) (i64.const -1)) +(assert_eq (invoke "expr" (i64.const 2)) (i64.const -2)) +(assert_eq (invoke "expr" (i64.const 3)) (i64.const -3)) +(assert_eq (invoke "expr" (i64.const 6)) (i64.const 101)) +(assert_eq (invoke "expr" (i64.const 7)) (i64.const 100)) +(assert_eq (invoke "expr" (i64.const -10)) (i64.const 100)) diff --git a/ml-proto/test/fac.wast b/ml-proto/test/fac.wast index cca812f94a..05110f4434 100644 --- a/ml-proto/test/fac.wast +++ b/ml-proto/test/fac.wast @@ -3,62 +3,51 @@ (module ;; Recursive factorial (func (param i64) (result i64) - (if (i64.eq (get_local 0) (i64.const 0)) - (i64.const 1) + (block + (br_unless $else (i64.eq (get_local 0) (i64.const 0))) + (return (i64.const 1)) + $else) (i64.mul (get_local 0) (call 0 (i64.sub (get_local 0) (i64.const 1)))) - ) ) ;; Recursive factorial named (func $fac-rec (param $n i64) (result i64) - (if (i64.eq (get_local $n) (i64.const 0)) - (i64.const 1) + (block + (br_unless $else (i64.eq (get_local $n) (i64.const 0))) + (return (i64.const 1)) + $else) (i64.mul (get_local $n) - (call $fac-rec (i64.sub (get_local $n) (i64.const 1))) - ) - ) + (call $fac-rec (i64.sub (get_local $n) (i64.const 1)))) ) ;; Iterative factorial (func (param i64) (result i64) (local i64 i64) - (set_local 1 (get_local 0)) - (set_local 2 (i64.const 1)) - (label - (loop - (if - (i64.eq (get_local 1) (i64.const 0)) - (break 0) - (block - (set_local 2 (i64.mul (get_local 1) (get_local 2))) - (set_local 1 (i64.sub (get_local 1) (i64.const 1))) - ) - ) - ) - ) - (return (get_local 2)) + (set_local 1 (get_local 0)) + (set_local 2 (i64.const 1)) + (loop $loop + (br_if $done (i64.eq (get_local 1) (i64.const 0))) + (set_local 2 (i64.mul (get_local 1) (get_local 2))) + (set_local 1 (i64.sub (get_local 1) (i64.const 1))) + (br $loop) + $done) + (return (get_local 2)) ) ;; Iterative factorial named (func $fac-iter (param $n i64) (result i64) (local $i i64) (local $res i64) - (set_local $i (get_local $n)) - (set_local $res (i64.const 1)) - (label $done - (loop - (if - (i64.eq (get_local $i) (i64.const 0)) - (break $done) - (block - (set_local $res (i64.mul (get_local $i) (get_local $res))) - (set_local $i (i64.sub (get_local $i) (i64.const 1))) - ) - ) - ) - ) - (return (get_local $res)) + (set_local $i (get_local $n)) + (set_local $res (i64.const 1)) + (loop $loop + (br_if $done (i64.eq (get_local $i) (i64.const 0))) + (set_local $res (i64.mul (get_local $i) (get_local $res))) + (set_local $i (i64.sub (get_local $i) (i64.const 1))) + (br $loop) + $done) + (return (get_local $res)) ) (export "fac-rec" 0) diff --git a/ml-proto/test/forward.wast b/ml-proto/test/forward.wast index 9c49228344..e215eaf80e 100644 --- a/ml-proto/test/forward.wast +++ b/ml-proto/test/forward.wast @@ -5,17 +5,19 @@ (export "odd" $odd) (func $even (param $n i32) (result i32) - (if (i32.eq (get_local $n) (i32.const 0)) - (i32.const 1) + (block + (br_unless $endif (i32.eq (get_local $n) (i32.const 0))) + (return (i32.const 1)) + $endif) (call $odd (i32.sub (get_local $n) (i32.const 1))) - ) ) (func $odd (param $n i32) (result i32) - (if (i32.eq (get_local $n) (i32.const 0)) - (i32.const 0) + (block + (br_unless $endif (i32.eq (get_local $n) (i32.const 0))) + (return (i32.const 0)) + $endif) (call $even (i32.sub (get_local $n) (i32.const 1))) - ) ) ) diff --git a/ml-proto/test/memory.wast b/ml-proto/test/memory.wast index f27a8b82cb..65daeb0342 100644 --- a/ml-proto/test/memory.wast +++ b/ml-proto/test/memory.wast @@ -81,62 +81,55 @@ ;; Aligned read/write (func $aligned (result i32) (local i32 i32 i32) - (set_local 0 (i32.const 10)) - (label - (loop - (if - (i32.eq (get_local 0) (i32.const 0)) - (break) - ) - (set_local 2 (i32.mul (get_local 0) (i32.const 4))) - (i32.store (get_local 2) (get_local 0)) - (set_local 1 (i32.load (get_local 2))) - (if - (i32.ne (get_local 0) (get_local 1)) + (set_local 0 (i32.const 10)) + (loop $loop + (block + (br_unless $break (i32.eq (get_local 0) (i32.const 0))) + (set_local 2 (i32.mul (get_local 0) (i32.const 4))) + (i32.store (get_local 2) (get_local 0)) + (set_local 1 (i32.load (get_local 2))) + (br_unless $endif (i32.ne (get_local 0) (get_local 1))) (return (i32.const 0)) - ) - (set_local 0 (i32.sub (get_local 0) (i32.const 1))) - ) - ) - (return (i32.const 1)) + $endif) + (set_local 0 (i32.sub (get_local 0) (i32.const 1))) + (br $loop) + $break) + (return (i32.const 1)) ) ;; Unaligned read/write (func $unaligned (result i32) (local i32 f64 f64) - (set_local 0 (i32.const 10)) - (label - (loop - (if - (i32.eq (get_local 0) (i32.const 0)) - (break) - ) - (set_local 2 (f64.convert_s/i32 (get_local 0))) - (f64.store/1 (get_local 0) (get_local 2)) - (set_local 1 (f64.load/1 (get_local 0))) - (if - (f64.ne (get_local 2) (get_local 1)) + (set_local 0 (i32.const 10)) + (loop $loop + (block + (br_unless $break (i32.eq (get_local 0) (i32.const 0))) + (set_local 2 (f64.convert_s/i32 (get_local 0))) + (f64.store/1 (get_local 0) (get_local 2)) + (set_local 1 (f64.load/1 (get_local 0))) + (br_unless $endif (f64.ne (get_local 2) (get_local 1))) (return (i32.const 0)) - ) - (set_local 0 (i32.sub (get_local 0) (i32.const 1))) - ) - ) - (return (i32.const 1)) + $endif) + (set_local 0 (i32.sub (get_local 0) (i32.const 1))) + (br $loop) + $break) + (return (i32.const 1)) ) ;; Memory cast (func $cast (result f64) - (i64.store (i32.const 8) (i64.const -12345)) - (if - (f64.eq - (f64.load (i32.const 8)) - (f64.reinterpret/i64 (i64.const -12345)) - ) - (return (f64.const 0)) - ) - (i64.store/1 (i32.const 9) (i64.const 0)) - (i32.store16/1 (i32.const 15) (i32.const 16453)) - (return (f64.load/1 (i32.const 9))) + (block + (i64.store (i32.const 8) (i64.const -12345)) + (br_unless $endif + (f64.eq + (f64.load (i32.const 8)) + (f64.reinterpret/i64 (i64.const -12345)) + )) + (return (f64.const 0)) + $endif) + (i64.store/1 (i32.const 9) (i64.const 0)) + (i32.store16/1 (i32.const 15) (i32.const 16453)) + (return (f64.load/1 (i32.const 9))) ) ;; Sign and zero extending memory loads diff --git a/ml-proto/test/sugar-fac.wase b/ml-proto/test/sugar-fac.wase new file mode 100644 index 0000000000..b57d59c04c --- /dev/null +++ b/ml-proto/test/sugar-fac.wase @@ -0,0 +1,71 @@ +;; (c) 2015 Andreas Rossberg + +(module + ;; Recursive factorial + (func (param i64) (result i64) + (if (i64.eq (get_local 0) (i64.const 0)) + (i64.const 1) + (i64.mul (get_local 0) (call 0 (i64.sub (get_local 0) (i64.const 1)))) + ) + ) + + ;; Recursive factorial named + (func $fac-rec (param $n i64) (result i64) + (if (i64.eq (get_local $n) (i64.const 0)) + (i64.const 1) + (i64.mul + (get_local $n) + (call $fac-rec (i64.sub (get_local $n) (i64.const 1))) + ) + ) + ) + + ;; Iterative factorial + (func (param i64) (result i64) + (local i64 i64) + (set_local 1 (get_local 0)) + (set_local 2 (i64.const 1)) + (forever + (if + (i64.eq (get_local 1) (i64.const 0)) + (break) + (block + (set_local 2 (i64.mul (get_local 1) (get_local 2))) + (set_local 1 (i64.sub (get_local 1) (i64.const 1))) + ) + ) + ) + (return (get_local 2)) + ) + + ;; Iterative factorial named + (func $fac-iter (param $n i64) (result i64) + (local $i i64) + (local $res i64) + (set_local $i (get_local $n)) + (set_local $res (i64.const 1)) + (block + (forever + (if + (i64.eq (get_local $i) (i64.const 0)) + (br $done) + (block + (set_local $res (i64.mul (get_local $i) (get_local $res))) + (set_local $i (i64.sub (get_local $i) (i64.const 1))) + ) + ) + ) + $done) + (return (get_local $res)) + ) + + (export "fac-rec" 0) + (export "fac-iter" 2) + (export "fac-rec-named" $fac-rec) + (export "fac-iter-named" $fac-iter) +) + +(assert_eq (invoke "fac-rec" (i64.const 25)) (i64.const 7034535277573963776)) +(assert_eq (invoke "fac-iter" (i64.const 25)) (i64.const 7034535277573963776)) +(assert_eq (invoke "fac-rec-named" (i64.const 25)) (i64.const 7034535277573963776)) +(assert_eq (invoke "fac-iter-named" (i64.const 25)) (i64.const 7034535277573963776)) diff --git a/ml-proto/test/sugar-forward.wase b/ml-proto/test/sugar-forward.wase new file mode 100644 index 0000000000..811884ff9b --- /dev/null +++ b/ml-proto/test/sugar-forward.wase @@ -0,0 +1,25 @@ +;; (c) 2015 Andreas Rossberg + +(module + (export "even" $even) + (export "odd" $odd) + + (func $even (param $n i32) (result i32) + (if (i32.eq (get_local $n) (i32.const 0)) + (i32.const 1) + (call $odd (i32.sub (get_local $n) (i32.const 1))) + ) + ) + + (func $odd (param $n i32) (result i32) + (if (i32.eq (get_local $n) (i32.const 0)) + (i32.const 0) + (call $even (i32.sub (get_local $n) (i32.const 1))) + ) + ) +) + +(assert_eq (invoke "even" (i32.const 13)) (i32.const 0)) +(assert_eq (invoke "even" (i32.const 20)) (i32.const 1)) +(assert_eq (invoke "odd" (i32.const 13)) (i32.const 1)) +(assert_eq (invoke "odd" (i32.const 20)) (i32.const 0)) diff --git a/ml-proto/test/sugar-memory.wase b/ml-proto/test/sugar-memory.wase new file mode 100644 index 0000000000..a6d5b45aad --- /dev/null +++ b/ml-proto/test/sugar-memory.wase @@ -0,0 +1,223 @@ +;; (c) 2015 Andreas Rossberg + +;; Test memory section structure +(module (memory 0 0)) +(module (memory 0 1)) +(module (memory 4096 16777216)) +(module (memory 0 0 (segment 0 ""))) +(module (memory 1 1 (segment 0 "a"))) +(module (memory 100 1000 (segment 0 "a") (segment 99 "b"))) +(module (memory 100 1000 (segment 0 "a") (segment 1 "b") (segment 2 "c"))) + +(assert_invalid + (module (memory 1 0)) + "initial memory size must be less than maximum" +) +(assert_invalid + (module (memory 0 0 (segment 0 "a"))) + "data segment does not fit memory" +) +(assert_invalid + (module (memory 100 1000 (segment 0 "a") (segment 500 "b"))) + "data segment does not fit memory" +) +(assert_invalid + (module (memory 100 1000 (segment 0 "abc") (segment 0 "def"))) + "data segment not disjoint and ordered" +) +(assert_invalid + (module (memory 100 1000 (segment 3 "ab") (segment 0 "de"))) + "data segment not disjoint and ordered" +) +(assert_invalid + (module (memory 100 1000 (segment 0 "a") (segment 2 "b") (segment 1 "c"))) + "data segment not disjoint and ordered" +) + +;; Test alignment annotation rules +(module (func (i32.load8_u/2 (i32.const 0)))) +(module (func (i32.load16_u/4 (i32.const 0)))) +(module (func (i32.load/8 (i32.const 0)))) +(module (func (f32.load/8 (i32.const 0)))) + +(assert_invalid + (module (func (i64.load/0 (i32.const 0)))) + "non-power-of-two alignment" +) +(assert_invalid + (module (func (i64.load/3 (i32.const 0)))) + "non-power-of-two alignment" +) +(assert_invalid + (module (func (i64.load/5 (i32.const 0)))) + "non-power-of-two alignment" +) +(assert_invalid + (module (func (i64.load/6 (i32.const 0)))) + "non-power-of-two alignment" +) +(assert_invalid + (module (func (i64.load/7 (i32.const 0)))) + "non-power-of-two alignment" +) + +(module + (memory 1024 (segment 0 "ABC\a7D") (segment 20 "WASM")) + + ;; Data section + (func $data (result i32) + (i32.and + (i32.and + (i32.eq (i32.load8_u (i32.const 0)) (i32.const 65)) + (i32.eq (i32.load8_u (i32.const 3)) (i32.const 167)) + ) + (i32.and + (i32.eq (i32.load8_u (i32.const 20)) (i32.const 87)) + (i32.eq (i32.load8_u (i32.const 23)) (i32.const 77)) + ) + ) + ) + + ;; Aligned read/write + (func $aligned (result i32) + (local i32 i32 i32) + (set_local 0 (i32.const 10)) + (forever + (if + (i32.eq (get_local 0) (i32.const 0)) + (break) + ) + (set_local 2 (i32.mul (get_local 0) (i32.const 4))) + (i32.store (get_local 2) (get_local 0)) + (set_local 1 (i32.load (get_local 2))) + (if + (i32.ne (get_local 0) (get_local 1)) + (return (i32.const 0)) + ) + (set_local 0 (i32.sub (get_local 0) (i32.const 1))) + ) + (return (i32.const 1)) + ) + + ;; Unaligned read/write + (func $unaligned (result i32) + (local i32 f64 f64) + (set_local 0 (i32.const 10)) + (forever + (if + (i32.eq (get_local 0) (i32.const 0)) + (break) + ) + (set_local 2 (f64.convert_s/i32 (get_local 0))) + (f64.store/1 (get_local 0) (get_local 2)) + (set_local 1 (f64.load/1 (get_local 0))) + (if + (f64.ne (get_local 2) (get_local 1)) + (return (i32.const 0)) + ) + (set_local 0 (i32.sub (get_local 0) (i32.const 1))) + ) + (return (i32.const 1)) + ) + + ;; Memory cast + (func $cast (result f64) + (i64.store (i32.const 8) (i64.const -12345)) + (if + (f64.eq + (f64.load (i32.const 8)) + (f64.reinterpret/i64 (i64.const -12345)) + ) + (return (f64.const 0)) + ) + (i64.store/1 (i32.const 9) (i64.const 0)) + (i32.store16/1 (i32.const 15) (i32.const 16453)) + (return (f64.load/1 (i32.const 9))) + ) + + ;; Sign and zero extending memory loads + (func $i32_load8_s (param $i i32) (result i32) + (i32.store8 (i32.const 8) (get_local $i)) + (return (i32.load8_s (i32.const 8))) + ) + (func $i32_load8_u (param $i i32) (result i32) + (i32.store8 (i32.const 8) (get_local $i)) + (return (i32.load8_u (i32.const 8))) + ) + (func $i32_load16_s (param $i i32) (result i32) + (i32.store16 (i32.const 8) (get_local $i)) + (return (i32.load16_s (i32.const 8))) + ) + (func $i32_load16_u (param $i i32) (result i32) + (i32.store16 (i32.const 8) (get_local $i)) + (return (i32.load16_u (i32.const 8))) + ) + (func $i64_load8_s (param $i i64) (result i64) + (i64.store8 (i32.const 8) (get_local $i)) + (return (i64.load8_s (i32.const 8))) + ) + (func $i64_load8_u (param $i i64) (result i64) + (i64.store8 (i32.const 8) (get_local $i)) + (return (i64.load8_u (i32.const 8))) + ) + (func $i64_load16_s (param $i i64) (result i64) + (i64.store16 (i32.const 8) (get_local $i)) + (return (i64.load16_s (i32.const 8))) + ) + (func $i64_load16_u (param $i i64) (result i64) + (i64.store16 (i32.const 8) (get_local $i)) + (return (i64.load16_u (i32.const 8))) + ) + (func $i64_load32_s (param $i i64) (result i64) + (i64.store32 (i32.const 8) (get_local $i)) + (return (i64.load32_s (i32.const 8))) + ) + (func $i64_load32_u (param $i i64) (result i64) + (i64.store32 (i32.const 8) (get_local $i)) + (return (i64.load32_u (i32.const 8))) + ) + + (export "data" $data) + (export "aligned" $aligned) + (export "unaligned" $unaligned) + (export "cast" $cast) + (export "i32_load8_s" $i32_load8_s) + (export "i32_load8_u" $i32_load8_u) + (export "i32_load16_s" $i32_load16_s) + (export "i32_load16_u" $i32_load16_u) + (export "i64_load8_s" $i64_load8_s) + (export "i64_load8_u" $i64_load8_u) + (export "i64_load16_s" $i64_load16_s) + (export "i64_load16_u" $i64_load16_u) + (export "i64_load32_s" $i64_load32_s) + (export "i64_load32_u" $i64_load32_u) +) + +(assert_eq (invoke "data") (i32.const 1)) +(assert_eq (invoke "aligned") (i32.const 1)) +(assert_eq (invoke "unaligned") (i32.const 1)) +(assert_eq (invoke "cast") (f64.const 42.0)) + +(assert_eq (invoke "i32_load8_s" (i32.const -1)) (i32.const -1)) +(assert_eq (invoke "i32_load8_u" (i32.const -1)) (i32.const 255)) +(assert_eq (invoke "i32_load16_s" (i32.const -1)) (i32.const -1)) +(assert_eq (invoke "i32_load16_u" (i32.const -1)) (i32.const 65535)) + +(assert_eq (invoke "i32_load8_s" (i32.const 100)) (i32.const 100)) +(assert_eq (invoke "i32_load8_u" (i32.const 200)) (i32.const 200)) +(assert_eq (invoke "i32_load16_s" (i32.const 20000)) (i32.const 20000)) +(assert_eq (invoke "i32_load16_u" (i32.const 40000)) (i32.const 40000)) + +(assert_eq (invoke "i64_load8_s" (i64.const -1)) (i64.const -1)) +(assert_eq (invoke "i64_load8_u" (i64.const -1)) (i64.const 255)) +(assert_eq (invoke "i64_load16_s" (i64.const -1)) (i64.const -1)) +(assert_eq (invoke "i64_load16_u" (i64.const -1)) (i64.const 65535)) +(assert_eq (invoke "i64_load32_s" (i64.const -1)) (i64.const -1)) +(assert_eq (invoke "i64_load32_u" (i64.const -1)) (i64.const 4294967295)) + +(assert_eq (invoke "i64_load8_s" (i64.const 100)) (i64.const 100)) +(assert_eq (invoke "i64_load8_u" (i64.const 200)) (i64.const 200)) +(assert_eq (invoke "i64_load16_s" (i64.const 20000)) (i64.const 20000)) +(assert_eq (invoke "i64_load16_u" (i64.const 40000)) (i64.const 40000)) +(assert_eq (invoke "i64_load32_s" (i64.const 20000)) (i64.const 20000)) +(assert_eq (invoke "i64_load32_u" (i64.const 40000)) (i64.const 40000)) diff --git a/ml-proto/test/switch.wast b/ml-proto/test/switch.wast index fd62bd2f64..22b86b7092 100644 --- a/ml-proto/test/switch.wast +++ b/ml-proto/test/switch.wast @@ -5,17 +5,15 @@ (func $stmt (param $i i32) (result i32) (local $j i32) (set_local $j (i32.const 100)) - (label - (i32.switch (get_local $i) - (case 0 (return (get_local $i))) - (case 1 (nop) fallthrough) - (case 2) ;; implicit fallthrough - (case 3 (set_local $j (i32.sub (i32.const 0) (get_local $i))) (break)) - (case 4 (break)) - (case 5 (set_local $j (i32.const 101))) - (case 6 (set_local $j (i32.const 101)) fallthrough) - (;default;) (set_local $j (i32.const 102)) - ) + (i32.switch (get_local $i) + (case 0 (return (get_local $i))) + (case 1 (nop) fallthrough) + (case 2) ;; implicit fallthrough + (case 3 (set_local $j (i32.sub (i32.const 0) (get_local $i))) (break)) + (case 4 (break)) + (case 5 (set_local $j (i32.const 101))) + (case 6 (set_local $j (i32.const 101)) fallthrough) + (;default;) (set_local $j (i32.const 102)) ) (return (get_local $j)) ) @@ -25,16 +23,16 @@ (local $j i64) (set_local $j (i64.const 100)) (return - (label $l + (block (i64.switch (get_local $i) (case 0 (return (get_local $i))) (case 1 (nop) fallthrough) (case 2) ;; implicit fallthrough - (case 3 (break $l (i64.sub (i64.const 0) (get_local $i)))) + (case 3 (br $l (i64.sub (i64.const 0) (get_local $i)))) (case 6 (set_local $j (i64.const 101)) fallthrough) (;default;) (get_local $j) ) - ) + $l) ) )