From 105df5ec038596015070949c9d2f99ac8ff8ac61 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 22 Sep 2015 22:10:59 -0700 Subject: [PATCH 1/2] Branch-like structured control flow. --- ml-proto/host/lexer.mll | 10 +- ml-proto/host/parser.mly | 68 ++++----- ml-proto/spec/ast.ml | 22 +-- ml-proto/spec/check.ml | 42 +++--- ml-proto/spec/eval.ml | 65 +++++---- ml-proto/test/br_switch.wase | 60 ++++++++ ml-proto/test/fac.wast | 65 ++++----- ml-proto/test/forward.wast | 14 +- ml-proto/test/memory.wast | 83 ++++++----- ml-proto/test/sugar-fac.wase | 73 ++++++++++ ml-proto/test/sugar-forward.wase | 25 ++++ ml-proto/test/sugar-memory.wase | 227 +++++++++++++++++++++++++++++++ 12 files changed, 551 insertions(+), 203 deletions(-) create mode 100644 ml-proto/test/br_switch.wase create mode 100644 ml-proto/test/sugar-fac.wase create mode 100644 ml-proto/test/sugar-forward.wase create mode 100644 ml-proto/test/sugar-memory.wase diff --git a/ml-proto/host/lexer.mll b/ml-proto/host/lexer.mll index 9749c76e64..da76d3555a 100644 --- a/ml-proto/host/lexer.mll +++ b/ml-proto/host/lexer.mll @@ -129,12 +129,10 @@ rule token = parse | "nop" { NOP } | "block" { BLOCK } - | "if" { IF } | "loop" { LOOP } - | "label" { LABEL } - | "break" { BREAK } - | "case" { CASE } - | "fallthrough" { FALLTHROUGH } + | "br" { BR } + | "br_if" { BRIF } + | "br_unless" { BRUNLESS } | "call" { CALL } | "call_import" { CALLIMPORT } | "call_indirect" { CALLINDIRECT } @@ -157,7 +155,7 @@ rule token = parse | (ixx as t)".store"(mem_size as sz)"/"(align as a) { STOREWRAP (wrapop t sz a) } - | (nxx as t)".switch" { SWITCH (value_type t) } + | (nxx as t)".br_switch" { BRSWITCH (value_type t) } | (nxx as t)".const" { CONST (value_type t) } | (ixx as t)".clz" { UNARY (intop t Int32Op.Clz Int64Op.Clz) } diff --git a/ml-proto/host/parser.mly b/ml-proto/host/parser.mly index 954cb46dd7..b807b48a36 100644 --- a/ml-proto/host/parser.mly +++ b/ml-proto/host/parser.mly @@ -95,7 +95,7 @@ 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 @@ -110,7 +110,7 @@ let anon_label c = {c with labels = VarMap.map ((+) 1) c.labels} %token VAR %token TYPE %token CONST -%token SWITCH +%token BRSWITCH %token UNARY %token BINARY %token COMPARE @@ -160,20 +160,23 @@ 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 */ - | 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) } + | 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) } | 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,40 +205,25 @@ 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 } -; -arm : - | LPAR CASE literal expr_block fallthrough RPAR - { let at = at() in let at3 = ati 3 in - fun c t -> - {value = literal at3 $3 t; expr = $4 c; fallthru = $5} @@ at } - | LPAR CASE literal RPAR /* Sugar */ - { let at = at() in let at3 = ati 3 in let at4 = ati 4 in - fun c t -> - {value = literal at3 $3 t; expr = Nop @@ at4; fallthru = true} @@ at } + +br_switch_arm : + | INT var { ($1, $2) } ; -arms : - | expr { fun c -> [], $1 c } - | arm arms { fun c -> let x, y = $2 c in $1 c :: x, y } +br_switch_arms : + | /* empty */ { [] } + | br_switch_arm br_switch_arms { $1 :: $2 } ; - /* Functions */ 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..491c212439 100644 --- a/ml-proto/spec/ast.ml +++ b/ml-proto/spec/ast.ml @@ -75,12 +75,13 @@ 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 *) - | Switch of value_type * expr * arm list * expr (* switch, latter expr is default *) + | 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 + (* 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 *) @@ -100,15 +101,6 @@ and expr' = | MemorySize (* return current size of linear memory *) | ResizeMemory of expr (* resize linear memory *) -and arm = arm' Source.phrase -and arm' = -{ - value : literal; - expr : expr; - fallthru : bool -} - - (* Functions and Modules *) type memory = memory' Source.phrase diff --git a/ml-proto/spec/check.ml b/ml-proto/spec/check.ml index 28afc891f0..c930b84297 100644 --- a/ml-proto/spec/check.ml +++ b/ml-proto/spec/check.ml @@ -118,32 +118,33 @@ 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 - | Switch (t, e1, arms, e2) -> - require (t.it = Int32Type || t.it = Int64Type) t.at "invalid switch type"; + | BrSwitch (t, ec, default, labels, eo) -> (* TODO: Check that cases are unique. *) - check_expr c (Some t.it) e1; - List.iter (check_arm c t.it et) arms; - check_expr c et e2 + 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 | Call (x, es) -> let {ins; out} = func c x in @@ -237,11 +238,6 @@ and check_expr_option c et eo at = and check_literal c et l = check_type (Some (type_value l.it)) et l.at -and check_arm c t et arm = - let {value = l; expr = e; fallthru} = arm.it in - check_literal c (Some t) l; - check_expr c (if fallthru then None else et) e - and check_load c et memop e1 at = check_has_memory c at; check_align memop.align at; diff --git a/ml-proto/spec/eval.ml b/ml-proto/spec/eval.ml index 121d5fa51a..f6a7b90af8 100644 --- a/ml-proto/spec/eval.ml +++ b/ml-proto/spec/eval.ml @@ -126,32 +126,47 @@ 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)) - | 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 - | `Seek | `Fallthru -> eval_expr c e2 - | `Done vs -> vs - ) + | 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)) | Call (x, es) -> let vs = List.map (fun vo -> some (eval_expr c vo) vo.at) es in @@ -259,16 +274,6 @@ and eval_expr_option c eo = | Some e -> eval_expr c e | None -> None -and eval_arm c vo stage arm = - let {value; expr = e; fallthru} = arm.it in - match stage, vo = value.it with - | `Seek, true | `Fallthru, _ -> - if fallthru - then (ignore (eval_expr c e); `Fallthru) - else `Done (eval_expr c e) - | `Seek, false | `Done _, _ -> - stage - and eval_func (m : instance) (f : func) (evs : value list) = let module Return = MakeLabel () in let args = List.map ref evs in 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..cd3dd36d1e --- /dev/null +++ b/ml-proto/test/sugar-fac.wase @@ -0,0 +1,73 @@ +;; (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)) + (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)) + ) + + ;; 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)) + ) + + (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..d2a35f9744 --- /dev/null +++ b/ml-proto/test/sugar-memory.wase @@ -0,0 +1,227 @@ +;; (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)) + (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)) + (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)) + (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)) + (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)) From 19e2c083ad95c7ee4a79df61880e93260182d7c2 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 29 Sep 2015 16:27:33 -0700 Subject: [PATCH 2/2] Syntax sugar for if, if-else, forever, break, and switch. --- ml-proto/host/lexer.mll | 6 ++++ ml-proto/host/parser.mly | 57 +++++++++++++++++++++++++++++++++ ml-proto/spec/ast.ml | 10 ++++++ ml-proto/spec/check.ml | 12 +++++++ ml-proto/spec/eval.ml | 17 ++++++++++ ml-proto/test/sugar-fac.wase | 24 +++++++------- ml-proto/test/sugar-memory.wase | 52 ++++++++++++++---------------- ml-proto/test/switch.wast | 26 +++++++-------- 8 files changed, 149 insertions(+), 55 deletions(-) diff --git a/ml-proto/host/lexer.mll b/ml-proto/host/lexer.mll index da76d3555a..0c15e03a87 100644 --- a/ml-proto/host/lexer.mll +++ b/ml-proto/host/lexer.mll @@ -133,6 +133,11 @@ rule token = parse | "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 } @@ -156,6 +161,7 @@ rule token = parse { 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) } | (ixx as t)".clz" { UNARY (intop t Int32Op.Clz Int64Op.Clz) } diff --git a/ml-proto/host/parser.mly b/ml-proto/host/parser.mly index b807b48a36..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 @@ -102,6 +108,7 @@ let anon_label c = {c with labels = VarMap.map ((+) 1) c.labels} %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 @@ -111,6 +118,7 @@ let anon_label c = {c with labels = VarMap.map ((+) 1) c.labels} %token TYPE %token CONST %token BRSWITCH +%token SWITCH %token UNARY %token BINARY %token COMPARE @@ -177,6 +185,33 @@ oper : 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 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 @@ -206,6 +241,28 @@ expr_list : | 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 } +; +arm : + | LPAR CASE literal expr_block fallthrough RPAR + { let at = at() in let at3 = ati 3 in + fun c t -> + {value = literal at3 $3 t; expr = $4 c; fallthru = $5} @@ at } + | LPAR CASE literal RPAR /* Sugar */ + { let at = at() in let at3 = ati 3 in let at4 = ati 4 in + fun c t -> + {value = literal at3 $3 t; expr = Nop @@ at4; fallthru = true} @@ at } +; +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) } diff --git a/ml-proto/spec/ast.ml b/ml-proto/spec/ast.ml index 491c212439..95c317b5df 100644 --- a/ml-proto/spec/ast.ml +++ b/ml-proto/spec/ast.ml @@ -81,6 +81,7 @@ and expr' = | 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 *) @@ -101,6 +102,15 @@ and expr' = | MemorySize (* return current size of linear memory *) | ResizeMemory of expr (* resize linear memory *) +and arm = arm' Source.phrase +and arm' = +{ + value : literal; + expr : expr; + fallthru : bool +} + + (* Functions and Modules *) type memory = memory' Source.phrase diff --git a/ml-proto/spec/check.ml b/ml-proto/spec/check.ml index c930b84297..3f31d8e13a 100644 --- a/ml-proto/spec/check.ml +++ b/ml-proto/spec/check.ml @@ -146,6 +146,13 @@ let rec check_expr c et e = 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. *) + check_expr c (Some t.it) e1; + List.iter (check_arm c t.it et) arms; + check_expr c et e2 + | Call (x, es) -> let {ins; out} = func c x in check_exprs c ins es; @@ -238,6 +245,11 @@ and check_expr_option c et eo at = and check_literal c et l = check_type (Some (type_value l.it)) et l.at +and check_arm c t et arm = + let {value = l; expr = e; fallthru} = arm.it in + check_literal c (Some t) l; + check_expr c (if fallthru then None else et) e + and check_load c et memop e1 at = check_has_memory c at; check_align memop.align at; diff --git a/ml-proto/spec/eval.ml b/ml-proto/spec/eval.ml index f6a7b90af8..719d429968 100644 --- a/ml-proto/spec/eval.ml +++ b/ml-proto/spec/eval.ml @@ -168,6 +168,13 @@ let rec eval_expr (c : config) (e : expr) = 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 + | `Seek | `Fallthru -> eval_expr c e2 + | `Done vs -> vs + ) + | Call (x, es) -> let vs = List.map (fun vo -> some (eval_expr c vo) vo.at) es in eval_func c.module_ (func c x) vs @@ -274,6 +281,16 @@ and eval_expr_option c eo = | Some e -> eval_expr c e | None -> None +and eval_arm c vo stage arm = + let {value; expr = e; fallthru} = arm.it in + match stage, vo = value.it with + | `Seek, true | `Fallthru, _ -> + if fallthru + then (ignore (eval_expr c e); `Fallthru) + else `Done (eval_expr c e) + | `Seek, false | `Done _, _ -> + stage + and eval_func (m : instance) (f : func) (evs : value list) = let module Return = MakeLabel () in let args = List.map ref evs in diff --git a/ml-proto/test/sugar-fac.wase b/ml-proto/test/sugar-fac.wase index cd3dd36d1e..b57d59c04c 100644 --- a/ml-proto/test/sugar-fac.wase +++ b/ml-proto/test/sugar-fac.wase @@ -25,15 +25,13 @@ (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))) - ) + (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))) ) ) ) @@ -46,18 +44,18 @@ (local $res i64) (set_local $i (get_local $n)) (set_local $res (i64.const 1)) - (label $done - (loop + (block + (forever (if (i64.eq (get_local $i) (i64.const 0)) - (break $done) + (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)) ) diff --git a/ml-proto/test/sugar-memory.wase b/ml-proto/test/sugar-memory.wase index d2a35f9744..a6d5b45aad 100644 --- a/ml-proto/test/sugar-memory.wase +++ b/ml-proto/test/sugar-memory.wase @@ -82,21 +82,19 @@ (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)) - (return (i32.const 0)) - ) - (set_local 0 (i32.sub (get_local 0) (i32.const 1))) + (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)) ) @@ -105,21 +103,19 @@ (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)) - (return (i32.const 0)) - ) - (set_local 0 (i32.sub (get_local 0) (i32.const 1))) + (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)) ) 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) ) )