Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions src/ir/glbs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2025 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef wasm_ir_glbs_h
#define wasm_ir_glbs_h

#include "ir/module-utils.h"
#include "wasm.h"

namespace wasm {

//
// Similar to LUBFinder, but for GLBs.
//
struct GLBFinder {
GLBFinder() {}

GLBFinder(Type initialType) { note(initialType); }

// Note another type to take into account in the GLB.
void note(Type type) {
if (type != Type::unreachable) {
if (glb == Type::unreachable) {
// This is the first thing we see.
glb = type;
} else {
glb = Type::getGreatestLowerBound(glb, type);
}
}
}

// Returns whether we noted any (reachable) value.
bool noted() { return glb != Type::unreachable; }

// Returns the GLB.
Type getGLB() { return glb; }

// Combines the information in another GLBFinder into this one, and returns
// whether we changed anything.
bool combine(const GLBFinder& other) {
// Check if the GLB was changed.
auto old = glb;
note(other.glb);
return old != glb;
}

private:
// The greatest lower bound. As we go this always contains the latest value
// based on everything we've seen so far.
Type glb = Type::unreachable;
};

} // namespace wasm

#endif // wasm_ir_glbs_h
2 changes: 1 addition & 1 deletion src/ir/lubs.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ struct LUBFinder {

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

Expand Down
16 changes: 7 additions & 9 deletions src/tools/fuzzing/fuzzing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

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

// We only care about constraints on Expression* things.
void noteSubtype(Type sub, Type super) {}
Expand All @@ -1904,9 +1905,8 @@ void TranslateToFuzzReader::mutate(Function* func) {
// The expression must be a supertype of a fixed type. Nothing to do.
}
void noteSubtype(Expression* sub, Type super) {
if (super.isRef() && sub->type != super) {
// This is a nontrivial opportunity to replace sub with a given type.
childTypes[sub] = super;
if (super.isRef()) {
childTypes[sub].note(super);
}
}
void noteSubtype(Expression* sub, Expression* super) {
Expand Down Expand Up @@ -1962,14 +1962,12 @@ void TranslateToFuzzReader::mutate(Function* func) {
// Find the type to replace with.
auto type = curr->type;
if (type.isRef()) {
auto iter = finder.childTypes.find(curr);
if (iter != finder.childTypes.end()) {
type = iter->second;
auto& glb = finder.childTypes[curr];
if (glb.noted()) {
type = glb.getGLB();
// We can only be given a less-refined type (certainly we can replace
// curr with its own type).
assert(Type::isSubType(curr->type, type));
// We only store an interesting non-trivial type.
assert(type != curr->type);
}
}

Expand Down
1 change: 1 addition & 0 deletions test/gtest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ set(unittest_SOURCES
cfg.cpp
dfa_minimization.cpp
disjoint_sets.cpp
glbs.cpp
interpreter.cpp
intervals.cpp
json.cpp
Expand Down
42 changes: 42 additions & 0 deletions test/gtest/glbs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2025 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "ir/glbs.h"
#include "wasm-type.h"
#include "wasm.h"
#include "gtest/gtest.h"

using namespace wasm;

TEST(GLBsTest, Basics) {
GLBFinder finder;

// Nothing noted yet.
EXPECT_FALSE(finder.noted());
EXPECT_EQ(finder.getGLB(), Type::unreachable);

// Note a type.
Type anyref = Type(HeapType::any, Nullable);
finder.note(anyref);
EXPECT_TRUE(finder.noted());
EXPECT_EQ(finder.getGLB(), anyref);

// Note another, leaving the more-refined GLB.
Type refAny = Type(HeapType::any, NonNullable);
finder.note(refAny);
EXPECT_TRUE(finder.noted());
EXPECT_EQ(finder.getGLB(), refAny);
}
Loading