Skip to content

Commit e3a3c12

Browse files
authored
[interpreter] Unify assert_result* assertions (#1104)
1 parent 3e5fdc6 commit e3a3c12

File tree

13 files changed

+2071
-2005
lines changed

13 files changed

+2071
-2005
lines changed

interpreter/README.md

+9-3
Original file line numberDiff line numberDiff line change
@@ -324,16 +324,22 @@ action:
324324
( get <name>? <string> ) ;; get global export
325325
326326
assertion:
327-
( assert_return <action> <expr>* ) ;; assert action has expected results
328-
( assert_return_canonical_nan <action> ) ;; assert action results in NaN in a canonical form
329-
( assert_return_arithmetic_nan <action> ) ;; assert action results in NaN with 1 in MSB of fraction field
327+
( assert_return <action> <result>* ) ;; assert action has expected results
330328
( assert_trap <action> <failure> ) ;; assert action traps with given failure string
331329
( assert_exhaustion <action> <failure> ) ;; assert action exhausts system resources
332330
( assert_malformed <module> <failure> ) ;; assert module cannot be decoded with given failure string
333331
( assert_invalid <module> <failure> ) ;; assert module is invalid with given failure string
334332
( assert_unlinkable <module> <failure> ) ;; assert module fails to link
335333
( assert_trap <module> <failure> ) ;; assert module traps on instantiation
336334
335+
result:
336+
( <val_type>.const <numpat> )
337+
338+
numpat:
339+
<value> ;; literal result
340+
nan:canonical ;; NaN in canonical form
341+
nan:arithmetic ;; NaN with 1 in MSB of payload
342+
337343
meta:
338344
( script <name>? <script> ) ;; name a subscript
339345
( input <name>? <string> ) ;; read script or module from file

interpreter/exec/float.ml

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ sig
3030
type bits
3131
val pos_nan : t
3232
val neg_nan : t
33+
val is_inf : t -> bool
34+
val is_nan : t -> bool
3335
val of_float : float -> t
3436
val to_float : t -> float
3537
val of_string : string -> t

interpreter/script/js.ml

+62-58
Original file line numberDiff line numberDiff line change
@@ -122,27 +122,21 @@ function assert_exhaustion(action) {
122122

123123
function assert_return(action, expected) {
124124
let actual = action();
125-
if (!Object.is(actual, expected)) {
126-
throw new Error("Wasm return value " + expected + " expected, got " + actual);
127-
};
128-
}
129-
130-
function assert_return_canonical_nan(action) {
131-
let actual = action();
132-
// Note that JS can't reliably distinguish different NaN values,
133-
// so there's no good way to test that it's a canonical NaN.
134-
if (!Number.isNaN(actual)) {
135-
throw new Error("Wasm return value NaN expected, got " + actual);
136-
};
137-
}
138-
139-
function assert_return_arithmetic_nan(action) {
140-
// Note that JS can't reliably distinguish different NaN values,
141-
// so there's no good way to test for specific bitpatterns here.
142-
let actual = action();
143-
if (!Number.isNaN(actual)) {
144-
throw new Error("Wasm return value NaN expected, got " + actual);
145-
};
125+
switch (expected) {
126+
case "nan:canonical":
127+
case "nan:arithmetic":
128+
case "nan:any":
129+
// Note that JS can't reliably distinguish different NaN values,
130+
// so there's no good way to test that it's a canonical NaN.
131+
if (!Number.isNaN(actual)) {
132+
throw new Error("Wasm return value NaN expected, got " + actual);
133+
};
134+
return;
135+
default:
136+
if (!Object.is(actual, expected)) {
137+
throw new Error("Wasm return value " + expected + " expected, got " + actual);
138+
};
139+
}
146140
}
147141
|}
148142

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

223-
let assert_return lits ts at =
224-
let test lit =
225-
let t', reinterpret = reinterpret_of (Values.type_of lit.it) in
226-
[ reinterpret @@ at;
227-
Const lit @@ at;
228-
reinterpret @@ at;
229-
Compare (eq_of t') @@ at;
230-
Test (Values.I32 I32Op.Eqz) @@ at;
231-
BrIf (0l @@ at) @@ at ]
232-
in [], List.flatten (List.rev_map test lits)
233-
234-
let assert_return_nan_bitpattern nan_bitmask_of ts at =
235-
let test t =
236-
let t', reinterpret = reinterpret_of t in
237-
[ reinterpret @@ at;
238-
Const (nan_bitmask_of t' @@ at) @@ at;
239-
Binary (and_of t') @@ at;
240-
Const (canonical_nan_of t' @@ at) @@ at;
241-
Compare (eq_of t') @@ at;
242-
Test (Values.I32 I32Op.Eqz) @@ at;
243-
BrIf (0l @@ at) @@ at ]
244-
in [], List.flatten (List.rev_map test ts)
245-
246-
let assert_return_canonical_nan =
247-
(* The result may only differ from the canonical NaN in its sign bit *)
248-
assert_return_nan_bitpattern abs_mask_of
249-
250-
let assert_return_arithmetic_nan =
251-
(* The result can be any NaN that's one everywhere the canonical NaN is one *)
252-
assert_return_nan_bitpattern canonical_nan_of
217+
let assert_return ress ts at =
218+
let test res =
219+
match res.it with
220+
| LitResult lit ->
221+
let t', reinterpret = reinterpret_of (Values.type_of lit.it) in
222+
[ reinterpret @@ at;
223+
Const lit @@ at;
224+
reinterpret @@ at;
225+
Compare (eq_of t') @@ at;
226+
Test (Values.I32 I32Op.Eqz) @@ at;
227+
BrIf (0l @@ at) @@ at ]
228+
| NanResult nanop ->
229+
let nan =
230+
match nanop.it with
231+
| Values.I32 _ | Values.I64 _ -> assert false
232+
| Values.F32 n | Values.F64 n -> n
233+
in
234+
let nan_bitmask_of =
235+
match nan with
236+
| CanonicalNan -> abs_mask_of (* must only differ from the canonical NaN in its sign bit *)
237+
| ArithmeticNan -> canonical_nan_of (* can be any NaN that's one everywhere the canonical NaN is one *)
238+
in
239+
let t = Values.type_of nanop.it in
240+
let t', reinterpret = reinterpret_of t in
241+
[ reinterpret @@ at;
242+
Const (nan_bitmask_of t' @@ at) @@ at;
243+
Binary (and_of t') @@ at;
244+
Const (canonical_nan_of t' @@ at) @@ at;
245+
Compare (eq_of t') @@ at;
246+
Test (Values.I32 I32Op.Eqz) @@ at;
247+
BrIf (0l @@ at) @@ at ]
248+
in [], List.flatten (List.rev_map test ress)
253249

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

320+
let of_nan = function
321+
| CanonicalNan -> "nan:canonical"
322+
| ArithmeticNan -> "nan:arithmetic"
323+
324+
let of_result res =
325+
match res.it with
326+
| LitResult lit -> of_literal lit
327+
| NanResult nanop ->
328+
match nanop.it with
329+
| Values.I32 _ | Values.I64 _ -> assert false
330+
| Values.F32 n | Values.F64 n -> of_nan n
331+
324332
let rec of_definition def =
325333
match def.it with
326334
| Textual m -> of_bytes (Encode.encode m)
@@ -377,13 +385,9 @@ let of_assertion mods ass =
377385
"assert_unlinkable(" ^ of_definition def ^ ");"
378386
| AssertUninstantiable (def, _) ->
379387
"assert_uninstantiable(" ^ of_definition def ^ ");"
380-
| AssertReturn (act, lits) ->
381-
of_assertion' mods act "assert_return" (List.map of_literal lits)
382-
(Some (assert_return lits))
383-
| AssertReturnCanonicalNaN act ->
384-
of_assertion' mods act "assert_return_canonical_nan" [] (Some assert_return_canonical_nan)
385-
| AssertReturnArithmeticNaN act ->
386-
of_assertion' mods act "assert_return_arithmetic_nan" [] (Some assert_return_arithmetic_nan)
388+
| AssertReturn (act, ress) ->
389+
of_assertion' mods act "assert_return" (List.map of_result ress)
390+
(Some (assert_return ress))
387391
| AssertTrap (act, _) ->
388392
of_assertion' mods act "assert_trap" [] None
389393
| AssertExhaustion (act, _) ->

interpreter/script/run.ml

+56-35
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,39 @@ let print_module x_opt m =
238238
List.iter (print_export m) m.it.Ast.exports;
239239
flush_all ()
240240

241-
let print_result vs =
241+
let print_values vs =
242242
let ts = List.map Values.type_of vs in
243243
Printf.printf "%s : %s\n"
244244
(Values.string_of_values vs) (Types.string_of_value_types ts);
245245
flush_all ()
246246

247+
let string_of_nan = function
248+
| CanonicalNan -> "nan:canonical"
249+
| ArithmeticNan -> "nan:arithmetic"
250+
251+
let type_of_result r =
252+
match r with
253+
| LitResult v -> Values.type_of v.it
254+
| NanResult n -> Values.type_of n.it
255+
256+
let string_of_result r =
257+
match r with
258+
| LitResult v -> Values.string_of_value v.it
259+
| NanResult nanop ->
260+
match nanop.it with
261+
| Values.I32 _ | Values.I64 _ -> assert false
262+
| Values.F32 n | Values.F64 n -> string_of_nan n
263+
264+
let string_of_results = function
265+
| [r] -> string_of_result r
266+
| rs -> "[" ^ String.concat " " (List.map string_of_result rs) ^ "]"
267+
268+
let print_results rs =
269+
let ts = List.map type_of_result rs in
270+
Printf.printf "%s : %s\n"
271+
(string_of_results rs) (Types.string_of_value_types ts);
272+
flush_all ()
273+
247274

248275
(* Configuration *)
249276

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

282309
(* Running *)
283310

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

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

316-
let assert_result at correct got print_expect expect =
317-
if not correct then begin
318-
print_string "Result: "; print_result got;
319-
print_string "Expect: "; print_expect expect;
343+
let assert_result at got expect =
344+
let open Values in
345+
if
346+
List.length got <> List.length expect ||
347+
List.exists2 (fun v r ->
348+
match r with
349+
| LitResult v' -> v <> v'.it
350+
| NanResult nanop ->
351+
match nanop.it, v with
352+
| F32 CanonicalNan, F32 z -> z <> F32.pos_nan && z <> F32.neg_nan
353+
| F64 CanonicalNan, F64 z -> z <> F64.pos_nan && z <> F64.neg_nan
354+
| F32 ArithmeticNan, F32 z ->
355+
let pos_nan = F32.to_bits F32.pos_nan in
356+
Int32.logand (F32.to_bits z) pos_nan <> pos_nan
357+
| F64 ArithmeticNan, F64 z ->
358+
let pos_nan = F64.to_bits F64.pos_nan in
359+
Int64.logand (F64.to_bits z) pos_nan <> pos_nan
360+
| _, _ -> false
361+
) got expect
362+
then begin
363+
print_string "Result: "; print_values got;
364+
print_string "Expect: "; print_results expect;
320365
Assert.error at "wrong return values"
321366
end
322367

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

380-
| AssertReturn (act, vs) ->
381-
trace ("Asserting return...");
382-
let got_vs = run_action act in
383-
let expect_vs = List.map (fun v -> v.it) vs in
384-
assert_result ass.at (got_vs = expect_vs) got_vs print_result expect_vs
385-
386-
| AssertReturnCanonicalNaN act ->
387-
trace ("Asserting return...");
388-
let got_vs = run_action act in
389-
let is_canonical_nan =
390-
match got_vs with
391-
| [Values.F32 got_f32] -> got_f32 = F32.pos_nan || got_f32 = F32.neg_nan
392-
| [Values.F64 got_f64] -> got_f64 = F64.pos_nan || got_f64 = F64.neg_nan
393-
| _ -> false
394-
in assert_result ass.at is_canonical_nan got_vs print_endline "nan"
395-
396-
| AssertReturnArithmeticNaN act ->
425+
| AssertReturn (act, rs) ->
397426
trace ("Asserting return...");
398427
let got_vs = run_action act in
399-
let is_arithmetic_nan =
400-
match got_vs with
401-
| [Values.F32 got_f32] ->
402-
let pos_nan = F32.to_bits F32.pos_nan in
403-
Int32.logand (F32.to_bits got_f32) pos_nan = pos_nan
404-
| [Values.F64 got_f64] ->
405-
let pos_nan = F64.to_bits F64.pos_nan in
406-
Int64.logand (F64.to_bits got_f64) pos_nan = pos_nan
407-
| _ -> false
408-
in assert_result ass.at is_arithmetic_nan got_vs print_endline "nan"
428+
let expect_rs = List.map (fun r -> r.it) rs in
429+
assert_result ass.at got_vs expect_rs
409430

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

463484
| Assertion ass ->

interpreter/script/script.ml

+10-3
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,22 @@ and action' =
1111
| Invoke of var option * Ast.name * Ast.literal list
1212
| Get of var option * Ast.name
1313

14+
type nanop = nanop' Source.phrase
15+
and nanop' = (unit, unit, nan, nan) Values.op
16+
and nan = CanonicalNan | ArithmeticNan
17+
18+
type result = result' Source.phrase
19+
and result' =
20+
| LitResult of Ast.literal
21+
| NanResult of nanop
22+
1423
type assertion = assertion' Source.phrase
1524
and assertion' =
1625
| AssertMalformed of definition * string
1726
| AssertInvalid of definition * string
1827
| AssertUnlinkable of definition * string
1928
| AssertUninstantiable of definition * string
20-
| AssertReturn of action * Ast.literal list
21-
| AssertReturnCanonicalNaN of action
22-
| AssertReturnArithmeticNaN of action
29+
| AssertReturn of action * result list
2330
| AssertTrap of action * string
2431
| AssertExhaustion of action * string
2532

interpreter/text/arrange.ml

+15-6
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,19 @@ let action act =
417417
| Get (x_opt, name) ->
418418
Node ("get" ^ access x_opt name, [])
419419

420+
let nan = function
421+
| CanonicalNan -> "nan:canonical"
422+
| ArithmeticNan -> "nan:arithmetic"
423+
424+
let result res =
425+
match res.it with
426+
| LitResult lit -> literal lit
427+
| NanResult nanop ->
428+
match nanop.it with
429+
| Values.I32 _ | Values.I64 _ -> assert false
430+
| Values.F32 n -> Node ("f32.const " ^ nan n, [])
431+
| Values.F64 n -> Node ("f64.const " ^ nan n, [])
432+
420433
let assertion mode ass =
421434
match ass.it with
422435
| AssertMalformed (def, re) ->
@@ -427,12 +440,8 @@ let assertion mode ass =
427440
Node ("assert_unlinkable", [definition mode None def; Atom (string re)])
428441
| AssertUninstantiable (def, re) ->
429442
Node ("assert_trap", [definition mode None def; Atom (string re)])
430-
| AssertReturn (act, lits) ->
431-
Node ("assert_return", action act :: List.map literal lits)
432-
| AssertReturnCanonicalNaN act ->
433-
Node ("assert_return_canonical_nan", [action act])
434-
| AssertReturnArithmeticNaN act ->
435-
Node ("assert_return_arithmetic_nan", [action act])
443+
| AssertReturn (act, results) ->
444+
Node ("assert_return", action act :: List.map result results)
436445
| AssertTrap (act, re) ->
437446
Node ("assert_trap", [action act; Atom (string re)])
438447
| AssertExhaustion (act, re) ->

interpreter/text/lexer.mll

+2-2
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,10 @@ rule token = parse
344344
| "assert_invalid" { ASSERT_INVALID }
345345
| "assert_unlinkable" { ASSERT_UNLINKABLE }
346346
| "assert_return" { ASSERT_RETURN }
347-
| "assert_return_canonical_nan" { ASSERT_RETURN_CANONICAL_NAN }
348-
| "assert_return_arithmetic_nan" { ASSERT_RETURN_ARITHMETIC_NAN }
349347
| "assert_trap" { ASSERT_TRAP }
350348
| "assert_exhaustion" { ASSERT_EXHAUSTION }
349+
| "nan:canonical" { NAN Script.CanonicalNan }
350+
| "nan:arithmetic" { NAN Script.ArithmeticNan }
351351
| "input" { INPUT }
352352
| "output" { OUTPUT }
353353

0 commit comments

Comments
 (0)