diff --git a/src/ir/glbs.h b/src/ir/glbs.h new file mode 100644 index 00000000000..94c46529689 --- /dev/null +++ b/src/ir/glbs.h @@ -0,0 +1,75 @@ +/* + * 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) { + // We only compute GLBs of concrete types in our IR. + assert(type != Type::none); + + if (type != Type::unreachable) { + if (glb == Type::unreachable) { + // This is the first thing we see. + glb = type; + } else { + glb = Type::getGreatestLowerBound(glb, type); + // If the result is unreachable, when neither of the inputs was, then we + // have combined things from different hierarchies, which we do not + // allow here: We focus on computing GLBs for concrete places in our IR. + assert(glb != Type::unreachable); + } + } + } + + // 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 diff --git a/src/ir/lubs.h b/src/ir/lubs.h index afc5c067603..f61a5b0ad51 100644 --- a/src/ir/lubs.h +++ b/src/ir/lubs.h @@ -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; }; diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index da7f39df1be..bbc324479ac 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -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" @@ -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 childTypes; + std::unordered_map childTypes; // We only care about constraints on Expression* things. void noteSubtype(Type sub, Type super) {} @@ -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) { @@ -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); } } diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 54def3e3e4d..ecd351bc489 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -11,6 +11,7 @@ set(unittest_SOURCES cfg.cpp dfa_minimization.cpp disjoint_sets.cpp + glbs.cpp interpreter.cpp intervals.cpp json.cpp diff --git a/test/gtest/glbs.cpp b/test/gtest/glbs.cpp new file mode 100644 index 00000000000..87fecf506a5 --- /dev/null +++ b/test/gtest/glbs.cpp @@ -0,0 +1,46 @@ +/* + * 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); + + // Note unreachable, which changes nothing (we ignore unreachable inputs). + finder.note(Type::unreachable); + EXPECT_EQ(finder.getGLB(), refAny); +}