summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2020-09-09 09:56:50 -0700
committerGitHub <noreply@github.com>2020-09-09 09:56:50 -0700
commit41ea543093ed734c73e5202c58c899be447b3223 (patch)
tree9a31f000c84cfe7e0dcbf327798369ed5447968d
parent916ce6f1a9f7c85102a8c69f593b301c8df5d19d (diff)
downloadbinaryen-41ea543093ed734c73e5202c58c899be447b3223.tar.gz
binaryen-41ea543093ed734c73e5202c58c899be447b3223.tar.bz2
binaryen-41ea543093ed734c73e5202c58c899be447b3223.zip
Interpreter: Don't change NaN bits when multiplying by 1 (#3096)
Similar to #2958, but for multiplication. I thought this was limited only to division (it doesn't happen for addition, for example), but the fuzzer found that it does indeed happen for multiplication as well. Overall these are kind of workarounds for the interpreter doing normal f32/f64 multiplications using the host CPU, so we pick up any oddness of its NaN behavior. Using soft float might be safer (but much slower).
-rw-r--r--src/wasm/literal.cpp43
-rw-r--r--test/passes/fuzz-exec_O.txt40
-rw-r--r--test/passes/fuzz-exec_O.wast29
-rw-r--r--test/spec/old_float_exprs.wast6
4 files changed, 75 insertions, 43 deletions
diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp
index ec8642865..ffb98abe4 100644
--- a/src/wasm/literal.cpp
+++ b/src/wasm/literal.cpp
@@ -863,10 +863,35 @@ Literal Literal::mul(const Literal& other) const {
return Literal(uint32_t(i32) * uint32_t(other.i32));
case Type::i64:
return Literal(uint64_t(i64) * uint64_t(other.i64));
- case Type::f32:
- return Literal(getf32() * other.getf32());
- case Type::f64:
- return Literal(getf64() * other.getf64());
+ case Type::f32: {
+ // Special-case multiplication by 1. nan * 1 can change nan bits per the
+ // wasm spec, but it is ok to just return that original nan, and we
+ // do that here so that we are consistent with the optimization of
+ // removing the * 1 and leaving just the nan. That is, if we just
+ // do a normal multiply and the CPU decides to change the bits, we'd
+ // give a different result on optimized code, which would look like
+ // it was a bad optimization. So out of all the valid results to
+ // return here, return the simplest one that is consistent with
+ // our optimization for the case of 1.
+ float lhs = getf32(), rhs = other.getf32();
+ if (rhs == 1) {
+ return Literal(lhs);
+ }
+ if (lhs == 1) {
+ return Literal(rhs);
+ }
+ return Literal(lhs * rhs);
+ }
+ case Type::f64: {
+ double lhs = getf64(), rhs = other.getf64();
+ if (rhs == 1) {
+ return Literal(lhs);
+ }
+ if (lhs == 1) {
+ return Literal(rhs);
+ }
+ return Literal(lhs * rhs);
+ }
case Type::v128:
case Type::funcref:
case Type::externref:
@@ -903,15 +928,7 @@ Literal Literal::div(const Literal& other) const {
case FP_INFINITE: // fallthrough
case FP_NORMAL: // fallthrough
case FP_SUBNORMAL:
- // Special-case division by 1. nan / 1 can change nan bits per the
- // wasm spec, but it is ok to just return that original nan, and we
- // do that here so that we are consistent with the optimization of
- // removing the / 1 and leaving just the nan. That is, if we just
- // do a normal divide and the CPU decides to change the bits, we'd
- // give a different result on optimized code, which would look like
- // it was a bad optimization. So out of all the valid results to
- // return here, return the simplest one that is consistent with
- // optimization.
+ // Special-case division by 1, similar to multiply from earlier.
if (rhs == 1) {
return Literal(lhs);
}
diff --git a/test/passes/fuzz-exec_O.txt b/test/passes/fuzz-exec_O.txt
index 9c8280586..ef8e165bb 100644
--- a/test/passes/fuzz-exec_O.txt
+++ b/test/passes/fuzz-exec_O.txt
@@ -30,22 +30,30 @@
[trap final > memory: 18446744073709551615 > 65514]
[fuzz-exec] comparing func_0
[fuzz-exec] comparing func_1
-[fuzz-exec] calling func_113
-[LoggingExternalInterface logging -nan:0x23017a]
-[fuzz-exec] note result: func_113 => 113
+[fuzz-exec] calling div
+[fuzz-exec] note result: div => -nan:0x23017a
+[fuzz-exec] calling mul1
+[fuzz-exec] note result: mul1 => -nan:0x34546d
+[fuzz-exec] calling mul2
+[fuzz-exec] note result: mul2 => -nan:0x34546d
(module
- (type $f32_=>_none (func (param f32)))
- (type $none_=>_i64 (func (result i64)))
- (import "fuzzing-support" "log-f32" (func $fimport$0 (param f32)))
- (export "func_113" (func $0))
- (func $0 (; has Stack IR ;) (result i64)
- (call $fimport$0
- (f32.const -nan:0x23017a)
- )
- (i64.const 113)
+ (type $none_=>_f32 (func (result f32)))
+ (export "div" (func $0))
+ (export "mul1" (func $1))
+ (export "mul2" (func $1))
+ (func $0 (; has Stack IR ;) (result f32)
+ (f32.const -nan:0x23017a)
+ )
+ (func $1 (; has Stack IR ;) (result f32)
+ (f32.const -nan:0x34546d)
)
)
-[fuzz-exec] calling func_113
-[LoggingExternalInterface logging -nan:0x23017a]
-[fuzz-exec] note result: func_113 => 113
-[fuzz-exec] comparing func_113
+[fuzz-exec] calling div
+[fuzz-exec] note result: div => -nan:0x23017a
+[fuzz-exec] calling mul1
+[fuzz-exec] note result: mul1 => -nan:0x34546d
+[fuzz-exec] calling mul2
+[fuzz-exec] note result: mul2 => -nan:0x34546d
+[fuzz-exec] comparing div
+[fuzz-exec] comparing mul1
+[fuzz-exec] comparing mul2
diff --git a/test/passes/fuzz-exec_O.wast b/test/passes/fuzz-exec_O.wast
index 44bbd7101..5c739c548 100644
--- a/test/passes/fuzz-exec_O.wast
+++ b/test/passes/fuzz-exec_O.wast
@@ -21,18 +21,23 @@
)
)
(module
- (type $f32_=>_none (func (param f32)))
- (type $none_=>_i64 (func (result i64)))
- (import "fuzzing-support" "log-f32" (func $fimport$0 (param f32)))
- (export "func_113" (func $0))
- (func $0 (result i64)
- (call $fimport$0
- (f32.div
- (f32.const -nan:0x23017a) ;; div by 1 can be removed, leaving this nan
- (f32.const 1) ;; as it is. wasm semantics allow nan bits to
- ) ;; change, but the interpreter should not do so,
- ) ;; so that it does not fail on that opt.
- (i64.const 113)
+ (func "div" (result f32)
+ (f32.div ;; div by 1 can be removed, leaving this nan
+ (f32.const -nan:0x23017a) ;; as it is. wasm semantics allow nan bits to
+ (f32.const 1) ;; change, but the interpreter should not do so,
+ ) ;; so that it does not fail on that opt.
+ )
+ (func "mul1" (result f32)
+ (f32.mul
+ (f32.const -nan:0x34546d)
+ (f32.const 1)
+ )
+ )
+ (func "mul2" (result f32)
+ (f32.mul
+ (f32.const 1)
+ (f32.const -nan:0x34546d)
+ )
)
)
diff --git a/test/spec/old_float_exprs.wast b/test/spec/old_float_exprs.wast
index 44515a32e..ca031114f 100644
--- a/test/spec/old_float_exprs.wast
+++ b/test/spec/old_float_exprs.wast
@@ -103,8 +103,10 @@
(f64.mul (local.get $x) (f64.const 1.0)))
)
-(assert_return (invoke "f32.no_fold_mul_one" (f32.const nan:0x200000)) (f32.const nan:0x600000))
-(assert_return (invoke "f64.no_fold_mul_one" (f64.const nan:0x4000000000000)) (f64.const nan:0xc000000000000))
+;; XXX BINARYEN: disable this test, as we have testing for the more strict property
+;; of not changing the bits at all in our interpreter
+;; (assert_return (invoke "f32.no_fold_mul_one" (f32.const nan:0x200000)) (f32.const nan:0x600000))
+;; (assert_return (invoke "f64.no_fold_mul_one" (f64.const nan:0x4000000000000)) (f64.const nan:0xc000000000000))
;; Test that 0.0/x is not folded to 0.0.