Skip to content

Commit 606d337

Browse files
kripkenradekdoulik
authored andcommitted
Fuzzer: Add SIMD support to DeNaN (WebAssembly#6318)
1 parent 30b420e commit 606d337

File tree

2 files changed

+221
-22
lines changed

2 files changed

+221
-22
lines changed

src/passes/DeNaN.cpp

Lines changed: 90 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct DeNaN : public WalkerPass<
3535
// Adds calls.
3636
bool addsEffects() override { return true; }
3737

38-
Name deNan32, deNan64;
38+
Name deNan32, deNan64, deNan128;
3939

4040
void visitExpression(Expression* expr) {
4141
// If the expression returns a floating-point value, ensure it is not a
@@ -67,6 +67,13 @@ struct DeNaN : public WalkerPass<
6767
} else {
6868
replacement = builder.makeCall(deNan64, {expr}, Type::f64);
6969
}
70+
} else if (expr->type == Type::v128) {
71+
if (c && hasNaNLane(c)) {
72+
uint8_t zero[16] = {};
73+
replacement = builder.makeConst(Literal(zero));
74+
} else {
75+
replacement = builder.makeCall(deNan128, {expr}, Type::v128);
76+
}
7077
}
7178
if (replacement) {
7279
// We can't do this outside of a function, like in a global initializer,
@@ -98,6 +105,11 @@ struct DeNaN : public WalkerPass<
98105
i,
99106
builder.makeCall(
100107
deNan64, {builder.makeLocalGet(i, Type::f64)}, Type::f64)));
108+
} else if (func->getLocalType(i) == Type::v128) {
109+
fixes.push_back(builder.makeLocalSet(
110+
i,
111+
builder.makeCall(
112+
deNan128, {builder.makeLocalGet(i, Type::v128)}, Type::v128)));
101113
}
102114
}
103115
if (!fixes.empty()) {
@@ -115,34 +127,90 @@ struct DeNaN : public WalkerPass<
115127
// Pick names for the helper functions.
116128
deNan32 = Names::getValidFunctionName(*module, "deNan32");
117129
deNan64 = Names::getValidFunctionName(*module, "deNan64");
130+
deNan128 = Names::getValidFunctionName(*module, "deNan128");
118131

119132
ControlFlowWalker<DeNaN, UnifiedExpressionVisitor<DeNaN>>::doWalkModule(
120133
module);
121134

122135
// Add helper functions after the walk, so they are not instrumented.
136+
addFunc(module, deNan32, Type::f32, Literal(float(0)), EqFloat32);
137+
addFunc(module, deNan64, Type::f64, Literal(double(0)), EqFloat64);
138+
139+
if (module->features.hasSIMD()) {
140+
uint8_t zero128[16] = {};
141+
addFunc(module, deNan128, Type::v128, Literal(zero128));
142+
}
143+
}
144+
145+
// Add a de-NaN-ing helper function.
146+
void addFunc(Module* module,
147+
Name name,
148+
Type type,
149+
Literal literal,
150+
std::optional<BinaryOp> op = {}) {
123151
Builder builder(*module);
124-
auto add = [&](Name name, Type type, Literal literal, BinaryOp op) {
125-
auto func = Builder::makeFunction(name, Signature(type, type), {});
126-
// Compare the value to itself to check if it is a NaN, and return 0 if
127-
// so:
152+
auto func = Builder::makeFunction(name, Signature(type, type), {});
153+
// Compare the value to itself to check if it is a NaN, and return 0 if
154+
// so:
155+
//
156+
// (if (result f*)
157+
// (f*.eq
158+
// (local.get $0)
159+
// (local.get $0)
160+
// )
161+
// (local.get $0)
162+
// (f*.const 0)
163+
// )
164+
Expression* condition;
165+
if (type != Type::v128) {
166+
// Generate a simple condition.
167+
assert(op);
168+
condition = builder.makeBinary(
169+
*op, builder.makeLocalGet(0, type), builder.makeLocalGet(0, type));
170+
} else {
171+
assert(!op);
172+
// v128 is trickier as the 128 bits may contain f32s or f64s, and we
173+
// need to check for nans both ways in principle. However, the f32 NaN
174+
// pattern is a superset of f64, since it checks less bits (8 bit
175+
// exponent vs 11), and it is checked in more places (4 32-bit values vs
176+
// 2 64-bit ones), so we can just check that. That is, this reduces to 4
177+
// checks of f32s, but is otherwise the same as a check of a single f32.
128178
//
129-
// (if (result f*)
130-
// (f*.eq
131-
// (local.get $0)
132-
// (local.get $0)
133-
// )
134-
// (local.get $0)
135-
// (f*.const 0)
136-
// )
137-
func->body = builder.makeIf(
138-
builder.makeBinary(
139-
op, builder.makeLocalGet(0, type), builder.makeLocalGet(0, type)),
140-
builder.makeLocalGet(0, type),
141-
builder.makeConst(literal));
142-
module->addFunction(std::move(func));
143-
};
144-
add(deNan32, Type::f32, Literal(float(0)), EqFloat32);
145-
add(deNan64, Type::f64, Literal(double(0)), EqFloat64);
179+
// However there is additional complexity, which is that if we do
180+
// EqVecF32x4 then we get all-1s for each case where we compare equal.
181+
// That itself is a NaN pattern, which means that running this pass
182+
// twice would interfere with itself. To avoid that we'd need a way to
183+
// detect our previous instrumentation and not instrument it, but that
184+
// is tricky (we can't depend on function names etc. while fuzzing).
185+
// Instead, extract the lanes and use f32 checks.
186+
auto getLane = [&](Index index) {
187+
return builder.makeSIMDExtract(
188+
ExtractLaneVecF32x4, builder.makeLocalGet(0, type), index);
189+
};
190+
auto getLaneCheck = [&](Index index) {
191+
return builder.makeBinary(EqFloat32, getLane(index), getLane(index));
192+
};
193+
auto* firstTwo =
194+
builder.makeBinary(AndInt32, getLaneCheck(0), getLaneCheck(1));
195+
auto* lastTwo =
196+
builder.makeBinary(AndInt32, getLaneCheck(2), getLaneCheck(3));
197+
condition = builder.makeBinary(AndInt32, firstTwo, lastTwo);
198+
}
199+
func->body = builder.makeIf(
200+
condition, builder.makeLocalGet(0, type), builder.makeConst(literal));
201+
module->addFunction(std::move(func));
202+
};
203+
204+
// Check if a contant v128 may contain f32 or f64 NaNs.
205+
bool hasNaNLane(Const* c) {
206+
assert(c->type == Type::v128);
207+
auto value = c->value;
208+
209+
// Compute if all f32s are equal to themselves.
210+
auto test32 = value.eqF32x4(value);
211+
test32 = test32.allTrueI32x4();
212+
213+
return !test32.getInteger();
146214
}
147215
};
148216

test/lit/passes/denan-simd.wast

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
3+
;; RUN: foreach %s %t wasm-opt --denan -all -S -o - | filecheck %s
4+
5+
(module
6+
;; CHECK: (type $0 (func (param v128) (result v128)))
7+
8+
;; CHECK: (type $1 (func (param f32) (result f32)))
9+
10+
;; CHECK: (type $2 (func (param f64) (result f64)))
11+
12+
;; CHECK: (func $foo128 (type $0) (param $x v128) (result v128)
13+
;; CHECK-NEXT: (local.set $x
14+
;; CHECK-NEXT: (call $deNan128
15+
;; CHECK-NEXT: (local.get $x)
16+
;; CHECK-NEXT: )
17+
;; CHECK-NEXT: )
18+
;; CHECK-NEXT: (drop
19+
;; CHECK-NEXT: (call $deNan128
20+
;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000002 0x00000003 0x00000004)
21+
;; CHECK-NEXT: )
22+
;; CHECK-NEXT: )
23+
;; CHECK-NEXT: (drop
24+
;; CHECK-NEXT: (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
25+
;; CHECK-NEXT: )
26+
;; CHECK-NEXT: (drop
27+
;; CHECK-NEXT: (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
28+
;; CHECK-NEXT: )
29+
;; CHECK-NEXT: (call $deNan128
30+
;; CHECK-NEXT: (call $foo128
31+
;; CHECK-NEXT: (local.get $x)
32+
;; CHECK-NEXT: )
33+
;; CHECK-NEXT: )
34+
;; CHECK-NEXT: )
35+
(func $foo128 (param $x v128) (result v128)
36+
;; The incoming param will be de-naned.
37+
38+
;; This is not a NaN. (We do still emit a call for it atm, FIXME)
39+
(drop
40+
(v128.const i32x4 0x00000001 0x00000002 0x00000003 0x00000004)
41+
)
42+
;; This is an f64 NaN and also an f32. It will become 0's.
43+
(drop
44+
(v128.const i32x4 0xffffffff 0x00000002 0x00000003 0x00000004)
45+
)
46+
;; This is an f32 NaN and not an f64. It will also become 0's.
47+
(drop
48+
(v128.const i32x4 0x00000001 0xffffffff 0x00000003 0x00000004)
49+
)
50+
51+
(call $foo128 (local.get $x))
52+
)
53+
)
54+
;; CHECK: (func $deNan32 (type $1) (param $0 f32) (result f32)
55+
;; CHECK-NEXT: (if (result f32)
56+
;; CHECK-NEXT: (f32.eq
57+
;; CHECK-NEXT: (local.get $0)
58+
;; CHECK-NEXT: (local.get $0)
59+
;; CHECK-NEXT: )
60+
;; CHECK-NEXT: (then
61+
;; CHECK-NEXT: (local.get $0)
62+
;; CHECK-NEXT: )
63+
;; CHECK-NEXT: (else
64+
;; CHECK-NEXT: (f32.const 0)
65+
;; CHECK-NEXT: )
66+
;; CHECK-NEXT: )
67+
;; CHECK-NEXT: )
68+
69+
;; CHECK: (func $deNan64 (type $2) (param $0 f64) (result f64)
70+
;; CHECK-NEXT: (if (result f64)
71+
;; CHECK-NEXT: (f64.eq
72+
;; CHECK-NEXT: (local.get $0)
73+
;; CHECK-NEXT: (local.get $0)
74+
;; CHECK-NEXT: )
75+
;; CHECK-NEXT: (then
76+
;; CHECK-NEXT: (local.get $0)
77+
;; CHECK-NEXT: )
78+
;; CHECK-NEXT: (else
79+
;; CHECK-NEXT: (f64.const 0)
80+
;; CHECK-NEXT: )
81+
;; CHECK-NEXT: )
82+
;; CHECK-NEXT: )
83+
84+
;; CHECK: (func $deNan128 (type $0) (param $0 v128) (result v128)
85+
;; CHECK-NEXT: (if (result v128)
86+
;; CHECK-NEXT: (i32.and
87+
;; CHECK-NEXT: (i32.and
88+
;; CHECK-NEXT: (f32.eq
89+
;; CHECK-NEXT: (f32x4.extract_lane 0
90+
;; CHECK-NEXT: (local.get $0)
91+
;; CHECK-NEXT: )
92+
;; CHECK-NEXT: (f32x4.extract_lane 0
93+
;; CHECK-NEXT: (local.get $0)
94+
;; CHECK-NEXT: )
95+
;; CHECK-NEXT: )
96+
;; CHECK-NEXT: (f32.eq
97+
;; CHECK-NEXT: (f32x4.extract_lane 1
98+
;; CHECK-NEXT: (local.get $0)
99+
;; CHECK-NEXT: )
100+
;; CHECK-NEXT: (f32x4.extract_lane 1
101+
;; CHECK-NEXT: (local.get $0)
102+
;; CHECK-NEXT: )
103+
;; CHECK-NEXT: )
104+
;; CHECK-NEXT: )
105+
;; CHECK-NEXT: (i32.and
106+
;; CHECK-NEXT: (f32.eq
107+
;; CHECK-NEXT: (f32x4.extract_lane 2
108+
;; CHECK-NEXT: (local.get $0)
109+
;; CHECK-NEXT: )
110+
;; CHECK-NEXT: (f32x4.extract_lane 2
111+
;; CHECK-NEXT: (local.get $0)
112+
;; CHECK-NEXT: )
113+
;; CHECK-NEXT: )
114+
;; CHECK-NEXT: (f32.eq
115+
;; CHECK-NEXT: (f32x4.extract_lane 3
116+
;; CHECK-NEXT: (local.get $0)
117+
;; CHECK-NEXT: )
118+
;; CHECK-NEXT: (f32x4.extract_lane 3
119+
;; CHECK-NEXT: (local.get $0)
120+
;; CHECK-NEXT: )
121+
;; CHECK-NEXT: )
122+
;; CHECK-NEXT: )
123+
;; CHECK-NEXT: )
124+
;; CHECK-NEXT: (then
125+
;; CHECK-NEXT: (local.get $0)
126+
;; CHECK-NEXT: )
127+
;; CHECK-NEXT: (else
128+
;; CHECK-NEXT: (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
129+
;; CHECK-NEXT: )
130+
;; CHECK-NEXT: )
131+
;; CHECK-NEXT: )

0 commit comments

Comments
 (0)