Skip to content

[interpreter] Unify assert_result* assertions #1104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 9, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions interpreter/README.md
Original file line number Diff line number Diff line change
@@ -324,16 +324,22 @@ action:
( get <name>? <string> ) ;; get global export
assertion:
( assert_return <action> <expr>* ) ;; assert action has expected results
( assert_return_canonical_nan <action> ) ;; assert action results in NaN in a canonical form
( assert_return_arithmetic_nan <action> ) ;; assert action results in NaN with 1 in MSB of fraction field
( assert_return <action> <result>* ) ;; assert action has expected results
( assert_trap <action> <failure> ) ;; assert action traps with given failure string
( assert_exhaustion <action> <failure> ) ;; assert action exhausts system resources
( assert_malformed <module> <failure> ) ;; assert module cannot be decoded with given failure string
( assert_invalid <module> <failure> ) ;; assert module is invalid with given failure string
( assert_unlinkable <module> <failure> ) ;; assert module fails to link
( assert_trap <module> <failure> ) ;; assert module traps on instantiation
result:
( <val_type>.const <numpat> )
numpat:
<value> ;; literal result
nan:canonical ;; NaN in canonical form
nan:arithmetic ;; NaN with 1 in MSB of payload
meta:
( script <name>? <script> ) ;; name a subscript
( input <name>? <string> ) ;; read script or module from file
2 changes: 2 additions & 0 deletions interpreter/exec/float.ml
Original file line number Diff line number Diff line change
@@ -30,6 +30,8 @@ sig
type bits
val pos_nan : t
val neg_nan : t
val is_inf : t -> bool
val is_nan : t -> bool
val of_float : float -> t
val to_float : t -> float
val of_string : string -> t
120 changes: 62 additions & 58 deletions interpreter/script/js.ml
Original file line number Diff line number Diff line change
@@ -122,27 +122,21 @@ function assert_exhaustion(action) {

function assert_return(action, expected) {
let actual = action();
if (!Object.is(actual, expected)) {
throw new Error("Wasm return value " + expected + " expected, got " + actual);
};
}

function assert_return_canonical_nan(action) {
let actual = action();
// Note that JS can't reliably distinguish different NaN values,
// so there's no good way to test that it's a canonical NaN.
if (!Number.isNaN(actual)) {
throw new Error("Wasm return value NaN expected, got " + actual);
};
}

function assert_return_arithmetic_nan(action) {
// Note that JS can't reliably distinguish different NaN values,
// so there's no good way to test for specific bitpatterns here.
let actual = action();
if (!Number.isNaN(actual)) {
throw new Error("Wasm return value NaN expected, got " + actual);
};
switch (expected) {
case "nan:canonical":
case "nan:arithmetic":
case "nan:any":
// Note that JS can't reliably distinguish different NaN values,
// so there's no good way to test that it's a canonical NaN.
if (!Number.isNaN(actual)) {
throw new Error("Wasm return value NaN expected, got " + actual);
};
return;
default:
if (!Object.is(actual, expected)) {
throw new Error("Wasm return value " + expected + " expected, got " + actual);
};
}
}
|}

@@ -220,36 +214,38 @@ let get t at =
let run ts at =
[], []

let assert_return lits ts at =
let test lit =
let t', reinterpret = reinterpret_of (Values.type_of lit.it) in
[ reinterpret @@ at;
Const lit @@ at;
reinterpret @@ at;
Compare (eq_of t') @@ at;
Test (Values.I32 I32Op.Eqz) @@ at;
BrIf (0l @@ at) @@ at ]
in [], List.flatten (List.rev_map test lits)

let assert_return_nan_bitpattern nan_bitmask_of ts at =
let test t =
let t', reinterpret = reinterpret_of t in
[ reinterpret @@ at;
Const (nan_bitmask_of t' @@ at) @@ at;
Binary (and_of t') @@ at;
Const (canonical_nan_of t' @@ at) @@ at;
Compare (eq_of t') @@ at;
Test (Values.I32 I32Op.Eqz) @@ at;
BrIf (0l @@ at) @@ at ]
in [], List.flatten (List.rev_map test ts)

let assert_return_canonical_nan =
(* The result may only differ from the canonical NaN in its sign bit *)
assert_return_nan_bitpattern abs_mask_of

let assert_return_arithmetic_nan =
(* The result can be any NaN that's one everywhere the canonical NaN is one *)
assert_return_nan_bitpattern canonical_nan_of
let assert_return ress ts at =
let test res =
match res.it with
| LitResult lit ->
let t', reinterpret = reinterpret_of (Values.type_of lit.it) in
[ reinterpret @@ at;
Const lit @@ at;
reinterpret @@ at;
Compare (eq_of t') @@ at;
Test (Values.I32 I32Op.Eqz) @@ at;
BrIf (0l @@ at) @@ at ]
| NanResult nanop ->
let nan =
match nanop.it with
| Values.I32 _ | Values.I64 _ -> assert false
| Values.F32 n | Values.F64 n -> n
in
let nan_bitmask_of =
match nan with
| CanonicalNan -> abs_mask_of (* must only differ from the canonical NaN in its sign bit *)
| ArithmeticNan -> canonical_nan_of (* can be any NaN that's one everywhere the canonical NaN is one *)
in
let t = Values.type_of nanop.it in
let t', reinterpret = reinterpret_of t in
[ reinterpret @@ at;
Const (nan_bitmask_of t' @@ at) @@ at;
Binary (and_of t') @@ at;
Const (canonical_nan_of t' @@ at) @@ at;
Compare (eq_of t') @@ at;
Test (Values.I32 I32Op.Eqz) @@ at;
BrIf (0l @@ at) @@ at ]
in [], List.flatten (List.rev_map test ress)

let wrap module_name item_name wrap_action wrap_assertion at =
let itypes, idesc, action = wrap_action at in
@@ -321,6 +317,18 @@ let of_literal lit =
| Values.F32 z -> of_float (F32.to_float z)
| Values.F64 z -> of_float (F64.to_float z)

let of_nan = function
| CanonicalNan -> "nan:canonical"
| ArithmeticNan -> "nan:arithmetic"

let of_result res =
match res.it with
| LitResult lit -> of_literal lit
| NanResult nanop ->
match nanop.it with
| Values.I32 _ | Values.I64 _ -> assert false
| Values.F32 n | Values.F64 n -> of_nan n

let rec of_definition def =
match def.it with
| Textual m -> of_bytes (Encode.encode m)
@@ -377,13 +385,9 @@ let of_assertion mods ass =
"assert_unlinkable(" ^ of_definition def ^ ");"
| AssertUninstantiable (def, _) ->
"assert_uninstantiable(" ^ of_definition def ^ ");"
| AssertReturn (act, lits) ->
of_assertion' mods act "assert_return" (List.map of_literal lits)
(Some (assert_return lits))
| AssertReturnCanonicalNaN act ->
of_assertion' mods act "assert_return_canonical_nan" [] (Some assert_return_canonical_nan)
| AssertReturnArithmeticNaN act ->
of_assertion' mods act "assert_return_arithmetic_nan" [] (Some assert_return_arithmetic_nan)
| AssertReturn (act, ress) ->
of_assertion' mods act "assert_return" (List.map of_result ress)
(Some (assert_return ress))
| AssertTrap (act, _) ->
of_assertion' mods act "assert_trap" [] None
| AssertExhaustion (act, _) ->
91 changes: 56 additions & 35 deletions interpreter/script/run.ml
Original file line number Diff line number Diff line change
@@ -238,12 +238,39 @@ let print_module x_opt m =
List.iter (print_export m) m.it.Ast.exports;
flush_all ()

let print_result vs =
let print_values vs =
let ts = List.map Values.type_of vs in
Printf.printf "%s : %s\n"
(Values.string_of_values vs) (Types.string_of_value_types ts);
flush_all ()

let string_of_nan = function
| CanonicalNan -> "nan:canonical"
| ArithmeticNan -> "nan:arithmetic"

let type_of_result r =
match r with
| LitResult v -> Values.type_of v.it
| NanResult n -> Values.type_of n.it

let string_of_result r =
match r with
| LitResult v -> Values.string_of_value v.it
| NanResult nanop ->
match nanop.it with
| Values.I32 _ | Values.I64 _ -> assert false
| Values.F32 n | Values.F64 n -> string_of_nan n

let string_of_results = function
| [r] -> string_of_result r
| rs -> "[" ^ String.concat " " (List.map string_of_result rs) ^ "]"

let print_results rs =
let ts = List.map type_of_result rs in
Printf.printf "%s : %s\n"
(string_of_results rs) (Types.string_of_value_types ts);
flush_all ()


(* Configuration *)

@@ -281,7 +308,7 @@ let lookup_registry module_name item_name _t =

(* Running *)

let rec run_definition def =
let rec run_definition def : Ast.module_ =
match def.it with
| Textual m -> m
| Encoded (name, bs) ->
@@ -292,7 +319,7 @@ let rec run_definition def =
let def' = Parse.string_to_module s in
run_definition def'

let run_action act =
let run_action act : Values.value list =
match act.it with
| Invoke (x_opt, name, vs) ->
trace ("Invoking function \"" ^ Ast.string_of_name name ^ "\"...");
@@ -313,10 +340,28 @@ let run_action act =
| None -> Assert.error act.at "undefined export"
)

let assert_result at correct got print_expect expect =
if not correct then begin
print_string "Result: "; print_result got;
print_string "Expect: "; print_expect expect;
let assert_result at got expect =
let open Values in
if
List.length got <> List.length expect ||
List.exists2 (fun v r ->
match r with
| LitResult v' -> v <> v'.it
| NanResult nanop ->
match nanop.it, v with
| F32 CanonicalNan, F32 z -> z <> F32.pos_nan && z <> F32.neg_nan
| F64 CanonicalNan, F64 z -> z <> F64.pos_nan && z <> F64.neg_nan
| F32 ArithmeticNan, F32 z ->
let pos_nan = F32.to_bits F32.pos_nan in
Int32.logand (F32.to_bits z) pos_nan <> pos_nan
| F64 ArithmeticNan, F64 z ->
let pos_nan = F64.to_bits F64.pos_nan in
Int64.logand (F64.to_bits z) pos_nan <> pos_nan
| _, _ -> false
) got expect
then begin
print_string "Result: "; print_values got;
print_string "Expect: "; print_results expect;
Assert.error at "wrong return values"
end

@@ -377,35 +422,11 @@ let run_assertion ass =
| _ -> Assert.error ass.at "expected instantiation error"
)

| AssertReturn (act, vs) ->
trace ("Asserting return...");
let got_vs = run_action act in
let expect_vs = List.map (fun v -> v.it) vs in
assert_result ass.at (got_vs = expect_vs) got_vs print_result expect_vs

| AssertReturnCanonicalNaN act ->
trace ("Asserting return...");
let got_vs = run_action act in
let is_canonical_nan =
match got_vs with
| [Values.F32 got_f32] -> got_f32 = F32.pos_nan || got_f32 = F32.neg_nan
| [Values.F64 got_f64] -> got_f64 = F64.pos_nan || got_f64 = F64.neg_nan
| _ -> false
in assert_result ass.at is_canonical_nan got_vs print_endline "nan"

| AssertReturnArithmeticNaN act ->
| AssertReturn (act, rs) ->
trace ("Asserting return...");
let got_vs = run_action act in
let is_arithmetic_nan =
match got_vs with
| [Values.F32 got_f32] ->
let pos_nan = F32.to_bits F32.pos_nan in
Int32.logand (F32.to_bits got_f32) pos_nan = pos_nan
| [Values.F64 got_f64] ->
let pos_nan = F64.to_bits F64.pos_nan in
Int64.logand (F64.to_bits got_f64) pos_nan = pos_nan
| _ -> false
in assert_result ass.at is_arithmetic_nan got_vs print_endline "nan"
let expect_rs = List.map (fun r -> r.it) rs in
assert_result ass.at got_vs expect_rs

| AssertTrap (act, re) ->
trace ("Asserting trap...");
@@ -457,7 +478,7 @@ let rec run_command cmd =
quote := cmd :: !quote;
if not !Flags.dry then begin
let vs = run_action act in
if vs <> [] then print_result vs
if vs <> [] then print_values vs
end

| Assertion ass ->
13 changes: 10 additions & 3 deletions interpreter/script/script.ml
Original file line number Diff line number Diff line change
@@ -11,15 +11,22 @@ and action' =
| Invoke of var option * Ast.name * Ast.literal list
| Get of var option * Ast.name

type nanop = nanop' Source.phrase
and nanop' = (unit, unit, nan, nan) Values.op
and nan = CanonicalNan | ArithmeticNan

type result = result' Source.phrase
and result' =
| LitResult of Ast.literal
| NanResult of nanop

type assertion = assertion' Source.phrase
and assertion' =
| AssertMalformed of definition * string
| AssertInvalid of definition * string
| AssertUnlinkable of definition * string
| AssertUninstantiable of definition * string
| AssertReturn of action * Ast.literal list
| AssertReturnCanonicalNaN of action
| AssertReturnArithmeticNaN of action
| AssertReturn of action * result list
| AssertTrap of action * string
| AssertExhaustion of action * string

21 changes: 15 additions & 6 deletions interpreter/text/arrange.ml
Original file line number Diff line number Diff line change
@@ -417,6 +417,19 @@ let action act =
| Get (x_opt, name) ->
Node ("get" ^ access x_opt name, [])

let nan = function
| CanonicalNan -> "nan:canonical"
| ArithmeticNan -> "nan:arithmetic"

let result res =
match res.it with
| LitResult lit -> literal lit
| NanResult nanop ->
match nanop.it with
| Values.I32 _ | Values.I64 _ -> assert false
| Values.F32 n -> Node ("f32.const " ^ nan n, [])
| Values.F64 n -> Node ("f64.const " ^ nan n, [])

let assertion mode ass =
match ass.it with
| AssertMalformed (def, re) ->
@@ -427,12 +440,8 @@ let assertion mode ass =
Node ("assert_unlinkable", [definition mode None def; Atom (string re)])
| AssertUninstantiable (def, re) ->
Node ("assert_trap", [definition mode None def; Atom (string re)])
| AssertReturn (act, lits) ->
Node ("assert_return", action act :: List.map literal lits)
| AssertReturnCanonicalNaN act ->
Node ("assert_return_canonical_nan", [action act])
| AssertReturnArithmeticNaN act ->
Node ("assert_return_arithmetic_nan", [action act])
| AssertReturn (act, results) ->
Node ("assert_return", action act :: List.map result results)
| AssertTrap (act, re) ->
Node ("assert_trap", [action act; Atom (string re)])
| AssertExhaustion (act, re) ->
4 changes: 2 additions & 2 deletions interpreter/text/lexer.mll
Original file line number Diff line number Diff line change
@@ -344,10 +344,10 @@ rule token = parse
| "assert_invalid" { ASSERT_INVALID }
| "assert_unlinkable" { ASSERT_UNLINKABLE }
| "assert_return" { ASSERT_RETURN }
| "assert_return_canonical_nan" { ASSERT_RETURN_CANONICAL_NAN }
| "assert_return_arithmetic_nan" { ASSERT_RETURN_ARITHMETIC_NAN }
| "assert_trap" { ASSERT_TRAP }
| "assert_exhaustion" { ASSERT_EXHAUSTION }
| "nan:canonical" { NAN Script.CanonicalNan }
| "nan:arithmetic" { NAN Script.ArithmeticNan }
| "input" { INPUT }
| "output" { OUTPUT }

25 changes: 21 additions & 4 deletions interpreter/text/parser.mly
Original file line number Diff line number Diff line change
@@ -39,6 +39,14 @@ let ati i =
let literal f s =
try f s with Failure _ -> error s.at "constant out of range"

let nanop f nan =
let open Source in
let open Values in
match snd (f ("0" @@ no_region)) with
| F32 _ -> F32 nan.it @@ nan.at
| F64 _ -> F64 nan.it @@ nan.at
| I32 _ | I64 _ -> error nan.at "NaN pattern with non-float type"

let nat s at =
try
let n = int_of_string s in
@@ -157,7 +165,8 @@ let inline_type_explicit (c : context) x ft at =
%token MODULE BIN QUOTE
%token SCRIPT REGISTER INVOKE GET
%token ASSERT_MALFORMED ASSERT_INVALID ASSERT_SOFT_INVALID ASSERT_UNLINKABLE
%token ASSERT_RETURN ASSERT_RETURN_CANONICAL_NAN ASSERT_RETURN_ARITHMETIC_NAN ASSERT_TRAP ASSERT_EXHAUSTION
%token ASSERT_RETURN ASSERT_TRAP ASSERT_EXHAUSTION
%token NAN
%token INPUT OUTPUT
%token EOF

@@ -178,6 +187,8 @@ let inline_type_explicit (c : context) x ft at =
%token<string> OFFSET_EQ_NAT
%token<string> ALIGN_EQ_NAT

%token<Script.nan> NAN

%nonassoc LOW
%nonassoc VAR

@@ -795,9 +806,7 @@ assertion :
{ AssertUnlinkable (snd $3, $4) @@ at () }
| LPAR ASSERT_TRAP script_module STRING RPAR
{ AssertUninstantiable (snd $3, $4) @@ at () }
| LPAR ASSERT_RETURN action const_list RPAR { AssertReturn ($3, $4) @@ at () }
| LPAR ASSERT_RETURN_CANONICAL_NAN action RPAR { AssertReturnCanonicalNaN $3 @@ at () }
| LPAR ASSERT_RETURN_ARITHMETIC_NAN action RPAR { AssertReturnArithmeticNaN $3 @@ at () }
| LPAR ASSERT_RETURN action result_list RPAR { AssertReturn ($3, $4) @@ at () }
| LPAR ASSERT_TRAP action STRING RPAR { AssertTrap ($3, $4) @@ at () }
| LPAR ASSERT_EXHAUSTION action STRING RPAR { AssertExhaustion ($3, $4) @@ at () }
@@ -825,6 +834,14 @@ const_list :
| /* empty */ { [] }
| const const_list { $1 :: $2 }
result :
| const { LitResult $1 @@ at () }
| LPAR CONST NAN RPAR { NanResult (nanop $2 ($3 @@ ati 3)) @@ at () }
result_list :
| /* empty */ { [] }
| result result_list { $1 :: $2 }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the result list be inside parens? Right now, when there are multiple return values specified, it seems like we would write assert_return (invoke ...) result1 result2 result3) but should it instead be assert_return (invoke ...) (result1 result2 result3))? (Asking, not claiming it should be...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's already the case in the current format. It is symmetric to the argument list, which is not being parenthesised either:

(assert_return (invoke "f" arg1 arg2 arg3) res1 res2 res3)

In fact, parens there would require inserting another keyword as head.

script :
| cmd_list EOF { $1 }
| inline_module1 EOF { [Module (None, $1) @@ at ()] } /* Sugar */
16 changes: 8 additions & 8 deletions test/core/conversions.wast
Original file line number Diff line number Diff line change
@@ -354,10 +354,10 @@
(assert_return (invoke "f64.promote_f32" (f32.const 0x1.8f867ep+125)) (f64.const 6.6382536710104395e+37))
(assert_return (invoke "f64.promote_f32" (f32.const inf)) (f64.const inf))
(assert_return (invoke "f64.promote_f32" (f32.const -inf)) (f64.const -inf))
(assert_return_canonical_nan (invoke "f64.promote_f32" (f32.const nan)))
(assert_return_arithmetic_nan (invoke "f64.promote_f32" (f32.const nan:0x200000)))
(assert_return_canonical_nan (invoke "f64.promote_f32" (f32.const -nan)))
(assert_return_arithmetic_nan (invoke "f64.promote_f32" (f32.const -nan:0x200000)))
(assert_return (invoke "f64.promote_f32" (f32.const nan)) (f64.const nan:canonical))
(assert_return (invoke "f64.promote_f32" (f32.const nan:0x200000)) (f64.const nan:arithmetic))
(assert_return (invoke "f64.promote_f32" (f32.const -nan)) (f64.const nan:canonical))
(assert_return (invoke "f64.promote_f32" (f32.const -nan:0x200000)) (f64.const nan:arithmetic))

(assert_return (invoke "f32.demote_f64" (f64.const 0.0)) (f32.const 0.0))
(assert_return (invoke "f32.demote_f64" (f64.const -0.0)) (f32.const -0.0))
@@ -401,10 +401,10 @@
(assert_return (invoke "f32.demote_f64" (f64.const 0x1.cb98354d521ffp-127)) (f32.const 0x1.cb9834p-127))
(assert_return (invoke "f32.demote_f64" (f64.const -0x1.6972b30cfb562p+1)) (f32.const -0x1.6972b4p+1))
(assert_return (invoke "f32.demote_f64" (f64.const -0x1.bedbe4819d4c4p+112)) (f32.const -0x1.bedbe4p+112))
(assert_return_canonical_nan (invoke "f32.demote_f64" (f64.const nan)))
(assert_return_arithmetic_nan (invoke "f32.demote_f64" (f64.const nan:0x4000000000000)))
(assert_return_canonical_nan (invoke "f32.demote_f64" (f64.const -nan)))
(assert_return_arithmetic_nan (invoke "f32.demote_f64" (f64.const -nan:0x4000000000000)))
(assert_return (invoke "f32.demote_f64" (f64.const nan)) (f32.const nan:canonical))
(assert_return (invoke "f32.demote_f64" (f64.const nan:0x4000000000000)) (f32.const nan:arithmetic))
(assert_return (invoke "f32.demote_f64" (f64.const -nan)) (f32.const nan:canonical))
(assert_return (invoke "f32.demote_f64" (f64.const -nan:0x4000000000000)) (f32.const nan:arithmetic))
(assert_return (invoke "f32.demote_f64" (f64.const 0x1p-1022)) (f32.const 0.0))
(assert_return (invoke "f32.demote_f64" (f64.const -0x1p-1022)) (f32.const -0.0))
(assert_return (invoke "f32.demote_f64" (f64.const 0x1.0p-150)) (f32.const 0.0))
1,822 changes: 911 additions & 911 deletions test/core/f32.wast

Large diffs are not rendered by default.

1,822 changes: 911 additions & 911 deletions test/core/f64.wast

Large diffs are not rendered by default.

126 changes: 63 additions & 63 deletions test/core/float_exprs.wast

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion test/core/float_misc.wast
Original file line number Diff line number Diff line change
@@ -569,7 +569,7 @@
(assert_return (invoke "f64.sqrt" (f64.const 0x1.c201b94757145p-492)) (f64.const 0x1.5369ee6bf2967p-246))

;; Computations that round differently when computed via f32.
(assert_return_canonical_nan (invoke "f64.sqrt" (f64.const -0x1.360e8d0032adp-963)))
(assert_return (invoke "f64.sqrt" (f64.const -0x1.360e8d0032adp-963)) (f64.const nan:canonical))
(assert_return (invoke "f64.sqrt" (f64.const 0x1.d9a6f5eef0503p+103)) (f64.const 0x1.ec73f56c166f6p+51))
(assert_return (invoke "f64.sqrt" (f64.const 0x1.aa051a5c4ec27p-760)) (f64.const 0x1.4a3e771ff5149p-380))
(assert_return (invoke "f64.sqrt" (f64.const 0x1.e5522a741babep-276)) (f64.const 0x1.607ae2b6feb7dp-138))