Skip to content

Commit 56f8f4f

Browse files
authored
Fuzzer mutation: Use GLB to find the type to replace with (#8100)
Mutation will find the necessary types of children, which can be more general than the current child, and replace them. It did not handle children with multiple constraints, however, like this: (br_if $target (value) (condition) ) The value here must be a subtype of the thing the `br_if` flows into, and also of the block it targets - the value is sent twice, effectively, so it has two subtyping constraints. Add a GLBFinder utility for this, parallel to LUBFinder.
1 parent c443688 commit 56f8f4f

File tree

5 files changed

+130
-10
lines changed

5 files changed

+130
-10
lines changed

src/ir/glbs.h

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2025 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef wasm_ir_glbs_h
18+
#define wasm_ir_glbs_h
19+
20+
#include "ir/module-utils.h"
21+
#include "wasm.h"
22+
23+
namespace wasm {
24+
25+
//
26+
// Similar to LUBFinder, but for GLBs.
27+
//
28+
struct GLBFinder {
29+
GLBFinder() {}
30+
31+
GLBFinder(Type initialType) { note(initialType); }
32+
33+
// Note another type to take into account in the GLB.
34+
void note(Type type) {
35+
// We only compute GLBs of concrete types in our IR.
36+
assert(type != Type::none);
37+
38+
if (type != Type::unreachable) {
39+
if (glb == Type::unreachable) {
40+
// This is the first thing we see.
41+
glb = type;
42+
} else {
43+
glb = Type::getGreatestLowerBound(glb, type);
44+
// If the result is unreachable, when neither of the inputs was, then we
45+
// have combined things from different hierarchies, which we do not
46+
// allow here: We focus on computing GLBs for concrete places in our IR.
47+
assert(glb != Type::unreachable);
48+
}
49+
}
50+
}
51+
52+
// Returns whether we noted any (reachable) value.
53+
bool noted() { return glb != Type::unreachable; }
54+
55+
// Returns the GLB.
56+
Type getGLB() { return glb; }
57+
58+
// Combines the information in another GLBFinder into this one, and returns
59+
// whether we changed anything.
60+
bool combine(const GLBFinder& other) {
61+
// Check if the GLB was changed.
62+
auto old = glb;
63+
note(other.glb);
64+
return old != glb;
65+
}
66+
67+
private:
68+
// The greatest lower bound. As we go this always contains the latest value
69+
// based on everything we've seen so far.
70+
Type glb = Type::unreachable;
71+
};
72+
73+
} // namespace wasm
74+
75+
#endif // wasm_ir_glbs_h

src/ir/lubs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ struct LUBFinder {
5252

5353
private:
5454
// The least upper bound. As we go this always contains the latest value based
55-
// on everything we've seen so far, except for nulls.
55+
// on everything we've seen so far.
5656
Type lub = Type::unreachable;
5757
};
5858

src/tools/fuzzing/fuzzing.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "tools/fuzzing.h"
1818
#include "ir/gc-type-utils.h"
19+
#include "ir/glbs.h"
1920
#include "ir/iteration.h"
2021
#include "ir/local-structural-dominance.h"
2122
#include "ir/module-utils.h"
@@ -1894,7 +1895,7 @@ void TranslateToFuzzReader::mutate(Function* func) {
18941895
// Maps children we can replace to the types we can replace them with. We
18951896
// only store nontrivial ones (i.e., where the type is not just the child's
18961897
// type).
1897-
std::unordered_map<Expression*, Type> childTypes;
1898+
std::unordered_map<Expression*, GLBFinder> childTypes;
18981899

18991900
// We only care about constraints on Expression* things.
19001901
void noteSubtype(Type sub, Type super) {}
@@ -1904,9 +1905,8 @@ void TranslateToFuzzReader::mutate(Function* func) {
19041905
// The expression must be a supertype of a fixed type. Nothing to do.
19051906
}
19061907
void noteSubtype(Expression* sub, Type super) {
1907-
if (super.isRef() && sub->type != super) {
1908-
// This is a nontrivial opportunity to replace sub with a given type.
1909-
childTypes[sub] = super;
1908+
if (super.isRef()) {
1909+
childTypes[sub].note(super);
19101910
}
19111911
}
19121912
void noteSubtype(Expression* sub, Expression* super) {
@@ -1962,14 +1962,12 @@ void TranslateToFuzzReader::mutate(Function* func) {
19621962
// Find the type to replace with.
19631963
auto type = curr->type;
19641964
if (type.isRef()) {
1965-
auto iter = finder.childTypes.find(curr);
1966-
if (iter != finder.childTypes.end()) {
1967-
type = iter->second;
1965+
auto& glb = finder.childTypes[curr];
1966+
if (glb.noted()) {
1967+
type = glb.getGLB();
19681968
// We can only be given a less-refined type (certainly we can replace
19691969
// curr with its own type).
19701970
assert(Type::isSubType(curr->type, type));
1971-
// We only store an interesting non-trivial type.
1972-
assert(type != curr->type);
19731971
}
19741972
}
19751973

test/gtest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ set(unittest_SOURCES
1111
cfg.cpp
1212
dfa_minimization.cpp
1313
disjoint_sets.cpp
14+
glbs.cpp
1415
interpreter.cpp
1516
intervals.cpp
1617
json.cpp

test/gtest/glbs.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2025 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "ir/glbs.h"
18+
#include "wasm-type.h"
19+
#include "wasm.h"
20+
#include "gtest/gtest.h"
21+
22+
using namespace wasm;
23+
24+
TEST(GLBsTest, Basics) {
25+
GLBFinder finder;
26+
27+
// Nothing noted yet.
28+
EXPECT_FALSE(finder.noted());
29+
EXPECT_EQ(finder.getGLB(), Type::unreachable);
30+
31+
// Note a type.
32+
Type anyref = Type(HeapType::any, Nullable);
33+
finder.note(anyref);
34+
EXPECT_TRUE(finder.noted());
35+
EXPECT_EQ(finder.getGLB(), anyref);
36+
37+
// Note another, leaving the more-refined GLB.
38+
Type refAny = Type(HeapType::any, NonNullable);
39+
finder.note(refAny);
40+
EXPECT_TRUE(finder.noted());
41+
EXPECT_EQ(finder.getGLB(), refAny);
42+
43+
// Note unreachable, which changes nothing (we ignore unreachable inputs).
44+
finder.note(Type::unreachable);
45+
EXPECT_EQ(finder.getGLB(), refAny);
46+
}

0 commit comments

Comments
 (0)