From 5216f0e55155b3e2a77d7afb7704c69bc1fa7cd9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Dec 2025 09:52:45 -0800 Subject: [PATCH 1/5] work --- src/ir/glbs.h | 68 +++++++++++++++++++++++++++++++++++ src/ir/lubs.h | 2 +- src/tools/fuzzing/fuzzing.cpp | 16 ++++----- test/gtest/CMakeLists.txt | 1 + test/gtest/glbs.cpp | 44 +++++++++++++++++++++++ 5 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 src/ir/glbs.h create mode 100644 test/gtest/glbs.cpp diff --git a/src/ir/glbs.h b/src/ir/glbs.h new file mode 100644 index 00000000000..2b2528ce0c3 --- /dev/null +++ b/src/ir/glbs.h @@ -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 GLBFinder, 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 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 5e2714df41d..a389c10adff 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..2bfdfead21e --- /dev/null +++ b/test/gtest/glbs.cpp @@ -0,0 +1,44 @@ +/* + * 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; + +// A BrIf must require of its value to both match the block it targets, and +// also the BrIf itself, as the value flows out. +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); +} From 92d3584c024a222aa943a65ed4ed9b93b3af1e19 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Dec 2025 10:06:12 -0800 Subject: [PATCH 2/5] typo --- src/ir/glbs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/glbs.h b/src/ir/glbs.h index 2b2528ce0c3..d15cd5099f8 100644 --- a/src/ir/glbs.h +++ b/src/ir/glbs.h @@ -23,7 +23,7 @@ namespace wasm { // -// Similar to GLBFinder, but for GLBs. +// Similar to LUBFinder, but for GLBs. // struct GLBFinder { GLBFinder() {} From 486c0ab701701cd08ce62e9ce8b4dc303d9b3de0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Dec 2025 10:07:14 -0800 Subject: [PATCH 3/5] typo --- test/gtest/glbs.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/gtest/glbs.cpp b/test/gtest/glbs.cpp index 2bfdfead21e..1ceb8b55062 100644 --- a/test/gtest/glbs.cpp +++ b/test/gtest/glbs.cpp @@ -21,8 +21,6 @@ using namespace wasm; -// A BrIf must require of its value to both match the block it targets, and -// also the BrIf itself, as the value flows out. TEST(GLBsTest, Basics) { GLBFinder finder; From 6e29fce1ebd86e1d630f0e7b245a0a62026fd629 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 11 Dec 2025 16:16:53 -0800 Subject: [PATCH 4/5] corner cases --- src/ir/glbs.h | 7 +++++++ test/gtest/glbs.cpp | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/ir/glbs.h b/src/ir/glbs.h index d15cd5099f8..94c46529689 100644 --- a/src/ir/glbs.h +++ b/src/ir/glbs.h @@ -32,12 +32,19 @@ struct GLBFinder { // 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); } } } diff --git a/test/gtest/glbs.cpp b/test/gtest/glbs.cpp index 1ceb8b55062..3c05cbb212d 100644 --- a/test/gtest/glbs.cpp +++ b/test/gtest/glbs.cpp @@ -39,4 +39,9 @@ TEST(GLBsTest, Basics) { 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); } + From cde024286357df80fcc7ac2601f7af485596fee6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 11 Dec 2025 16:33:14 -0800 Subject: [PATCH 5/5] format --- test/gtest/glbs.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/gtest/glbs.cpp b/test/gtest/glbs.cpp index 3c05cbb212d..87fecf506a5 100644 --- a/test/gtest/glbs.cpp +++ b/test/gtest/glbs.cpp @@ -44,4 +44,3 @@ TEST(GLBsTest, Basics) { finder.note(Type::unreachable); EXPECT_EQ(finder.getGLB(), refAny); } -