Skip to content

Commit 41ea543

Browse files
authored
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).
1 parent 916ce6f commit 41ea543

File tree

4 files changed

+75
-43
lines changed

4 files changed

+75
-43
lines changed

src/wasm/literal.cpp

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -863,10 +863,35 @@ Literal Literal::mul(const Literal& other) const {
863863
return Literal(uint32_t(i32) * uint32_t(other.i32));
864864
case Type::i64:
865865
return Literal(uint64_t(i64) * uint64_t(other.i64));
866-
case Type::f32:
867-
return Literal(getf32() * other.getf32());
868-
case Type::f64:
869-
return Literal(getf64() * other.getf64());
866+
case Type::f32: {
867+
// Special-case multiplication by 1. nan * 1 can change nan bits per the
868+
// wasm spec, but it is ok to just return that original nan, and we
869+
// do that here so that we are consistent with the optimization of
870+
// removing the * 1 and leaving just the nan. That is, if we just
871+
// do a normal multiply and the CPU decides to change the bits, we'd
872+
// give a different result on optimized code, which would look like
873+
// it was a bad optimization. So out of all the valid results to
874+
// return here, return the simplest one that is consistent with
875+
// our optimization for the case of 1.
876+
float lhs = getf32(), rhs = other.getf32();
877+
if (rhs == 1) {
878+
return Literal(lhs);
879+
}
880+
if (lhs == 1) {
881+
return Literal(rhs);
882+
}
883+
return Literal(lhs * rhs);
884+
}
885+
case Type::f64: {
886+
double lhs = getf64(), rhs = other.getf64();
887+
if (rhs == 1) {
888+
return Literal(lhs);
889+
}
890+
if (lhs == 1) {
891+
return Literal(rhs);
892+
}
893+
return Literal(lhs * rhs);
894+
}
870895
case Type::v128:
871896
case Type::funcref:
872897
case Type::externref:
@@ -903,15 +928,7 @@ Literal Literal::div(const Literal& other) const {
903928
case FP_INFINITE: // fallthrough
904929
case FP_NORMAL: // fallthrough
905930
case FP_SUBNORMAL:
906-
// Special-case division by 1. nan / 1 can change nan bits per the
907-
// wasm spec, but it is ok to just return that original nan, and we
908-
// do that here so that we are consistent with the optimization of
909-
// removing the / 1 and leaving just the nan. That is, if we just
910-
// do a normal divide and the CPU decides to change the bits, we'd
911-
// give a different result on optimized code, which would look like
912-
// it was a bad optimization. So out of all the valid results to
913-
// return here, return the simplest one that is consistent with
914-
// optimization.
931+
// Special-case division by 1, similar to multiply from earlier.
915932
if (rhs == 1) {
916933
return Literal(lhs);
917934
}

test/passes/fuzz-exec_O.txt

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,30 @@
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
33+
[fuzz-exec] calling div
34+
[fuzz-exec] note result: div => -nan:0x23017a
35+
[fuzz-exec] calling mul1
36+
[fuzz-exec] note result: mul1 => -nan:0x34546d
37+
[fuzz-exec] calling mul2
38+
[fuzz-exec] note result: mul2 => -nan:0x34546d
3639
(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)
40+
(type $none_=>_f32 (func (result f32)))
41+
(export "div" (func $0))
42+
(export "mul1" (func $1))
43+
(export "mul2" (func $1))
44+
(func $0 (; has Stack IR ;) (result f32)
45+
(f32.const -nan:0x23017a)
46+
)
47+
(func $1 (; has Stack IR ;) (result f32)
48+
(f32.const -nan:0x34546d)
4649
)
4750
)
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
51+
[fuzz-exec] calling div
52+
[fuzz-exec] note result: div => -nan:0x23017a
53+
[fuzz-exec] calling mul1
54+
[fuzz-exec] note result: mul1 => -nan:0x34546d
55+
[fuzz-exec] calling mul2
56+
[fuzz-exec] note result: mul2 => -nan:0x34546d
57+
[fuzz-exec] comparing div
58+
[fuzz-exec] comparing mul1
59+
[fuzz-exec] comparing mul2

test/passes/fuzz-exec_O.wast

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,23 @@
2121
)
2222
)
2323
(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)
24+
(func "div" (result f32)
25+
(f32.div ;; div by 1 can be removed, leaving this nan
26+
(f32.const -nan:0x23017a) ;; as it is. wasm semantics allow nan bits to
27+
(f32.const 1) ;; change, but the interpreter should not do so,
28+
) ;; so that it does not fail on that opt.
29+
)
30+
(func "mul1" (result f32)
31+
(f32.mul
32+
(f32.const -nan:0x34546d)
33+
(f32.const 1)
34+
)
35+
)
36+
(func "mul2" (result f32)
37+
(f32.mul
38+
(f32.const 1)
39+
(f32.const -nan:0x34546d)
40+
)
3641
)
3742
)
3843

test/spec/old_float_exprs.wast

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,10 @@
103103
(f64.mul (local.get $x) (f64.const 1.0)))
104104
)
105105

106-
(assert_return (invoke "f32.no_fold_mul_one" (f32.const nan:0x200000)) (f32.const nan:0x600000))
107-
(assert_return (invoke "f64.no_fold_mul_one" (f64.const nan:0x4000000000000)) (f64.const nan:0xc000000000000))
106+
;; XXX BINARYEN: disable this test, as we have testing for the more strict property
107+
;; of not changing the bits at all in our interpreter
108+
;; (assert_return (invoke "f32.no_fold_mul_one" (f32.const nan:0x200000)) (f32.const nan:0x600000))
109+
;; (assert_return (invoke "f64.no_fold_mul_one" (f64.const nan:0x4000000000000)) (f64.const nan:0xc000000000000))
108110

109111
;; Test that 0.0/x is not folded to 0.0.
110112

0 commit comments

Comments
 (0)