Skip to content

Commit 1c75f7d

Browse files
authored
Interpreter: Don't change NaN bits when dividing by 1 (#2958)
It's valid to change NaN bits in that case per the wasm spec, but if we do so then fuzz testcases will fail on the optimization of nan:foo / 1 => nan:foo That is, it is ok to leave the bits as they are, and if we do that then we are consistent with the simple and valid optimization of removing a divide by 1. Found by the fuzzer - looks like on x64 on some float32 NaNs, the bits will actually change (see the testcase). I've seen this on two machines consistently, so it's normal apparently. Disable an old wasm spectest that has been updated in upstream anyhow, but the new test here is even more strict and verifies the interpreter literally changes no bits.
1 parent 78e1e5f commit 1c75f7d

File tree

4 files changed

+54
-2
lines changed

4 files changed

+54
-2
lines changed

src/wasm/literal.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,18 @@ Literal Literal::div(const Literal& other) const {
868868
case FP_INFINITE: // fallthrough
869869
case FP_NORMAL: // fallthrough
870870
case FP_SUBNORMAL:
871+
// Special-case division by 1. nan / 1 can change nan bits per the
872+
// wasm spec, but it is ok to just return that original nan, and we
873+
// do that here so that we are consistent with the optimization of
874+
// removing the / 1 and leaving just the nan. That is, if we just
875+
// do a normal divide and the CPU decides to change the bits, we'd
876+
// give a different result on optimized code, which would look like
877+
// it was a bad optimization. So out of all the valid results to
878+
// return here, return the simplest one that is consistent with
879+
// optimization.
880+
if (rhs == 1) {
881+
return Literal(lhs);
882+
}
871883
return Literal(lhs / rhs);
872884
default:
873885
WASM_UNREACHABLE("invalid fp classification");
@@ -896,6 +908,10 @@ Literal Literal::div(const Literal& other) const {
896908
case FP_INFINITE: // fallthrough
897909
case FP_NORMAL: // fallthrough
898910
case FP_SUBNORMAL:
911+
// See above comment on f32.
912+
if (rhs == 1) {
913+
return Literal(lhs);
914+
}
899915
return Literal(lhs / rhs);
900916
default:
901917
WASM_UNREACHABLE("invalid fp classification");

test/passes/fuzz-exec_O.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,22 @@
3030
[trap final > memory: 18446744073709551615 > 65514]
3131
[fuzz-exec] comparing func_0
3232
[fuzz-exec] comparing func_1
33+
[fuzz-exec] calling func_113
34+
[LoggingExternalInterface logging -nan:0x23017a]
35+
[fuzz-exec] note result: func_113 => 113
36+
(module
37+
(type $f32_=>_none (func (param f32)))
38+
(type $none_=>_i64 (func (result i64)))
39+
(import "fuzzing-support" "log-f32" (func $fimport$0 (param f32)))
40+
(export "func_113" (func $0))
41+
(func $0 (; has Stack IR ;) (result i64)
42+
(call $fimport$0
43+
(f32.const -nan:0x23017a)
44+
)
45+
(i64.const 113)
46+
)
47+
)
48+
[fuzz-exec] calling func_113
49+
[LoggingExternalInterface logging -nan:0x23017a]
50+
[fuzz-exec] note result: func_113 => 113
51+
[fuzz-exec] comparing func_113

test/passes/fuzz-exec_O.wast

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,19 @@
2020
)
2121
)
2222
)
23+
(module
24+
(type $f32_=>_none (func (param f32)))
25+
(type $none_=>_i64 (func (result i64)))
26+
(import "fuzzing-support" "log-f32" (func $fimport$0 (param f32)))
27+
(export "func_113" (func $0))
28+
(func $0 (result i64)
29+
(call $fimport$0
30+
(f32.div
31+
(f32.const -nan:0x23017a) ;; div by 1 can be removed, leaving this nan
32+
(f32.const 1) ;; as it is. wasm semantics allow nan bits to
33+
) ;; change, but the interpreter should not do so,
34+
) ;; so that it does not fail on that opt.
35+
(i64.const 113)
36+
)
37+
)
2338

test/spec/old_float_exprs.wast

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,10 @@
133133
(f64.div (local.get $x) (f64.const 1.0)))
134134
)
135135

136-
(assert_return (invoke "f32.no_fold_div_one" (f32.const nan:0x200000)) (f32.const nan:0x600000))
137-
(assert_return (invoke "f64.no_fold_div_one" (f64.const nan:0x4000000000000)) (f64.const nan:0xc000000000000))
136+
;; XXX BINARYEN: disable this test, as we have testing for the more strict property
137+
;; of not changing the bits at all in our interpreter
138+
;; (assert_return (invoke "f32.no_fold_div_one" (f32.const nan:0x200000)) (f32.const nan:arithmetic))
139+
;; (assert_return (invoke "f64.no_fold_div_one" (f64.const nan:0x4000000000000)) (f64.const nan:arithmetic))
138140

139141
;; Test that x/-1.0 is not folded to -x.
140142

0 commit comments

Comments
 (0)