Skip to content

Commit 8e5f757

Browse files
authored
SubtypingDiscoverer: Handle values flowing out of br_if (#8101)
Consider (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. We were missing the value flowing out.
1 parent 6fb2b91 commit 8e5f757

File tree

3 files changed

+83
-0
lines changed

3 files changed

+83
-0
lines changed

src/ir/subtype-exprs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ struct SubtypingDiscoverer : public OverriddenVisitor<SubType> {
133133
void visitBreak(Break* curr) {
134134
if (curr->value) {
135135
self()->noteSubtype(curr->value, self()->findBreakTarget(curr->name));
136+
if (curr->condition) {
137+
self()->noteSubtype(curr->value, curr);
138+
}
136139
}
137140
}
138141
void visitSwitch(Switch* curr) {

test/gtest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ set(unittest_SOURCES
2222
public-type-validator.cpp
2323
scc.cpp
2424
stringify.cpp
25+
subtype-exprs.cpp
2526
suffix_tree.cpp
2627
topological-sort.cpp
2728
type-builder.cpp

test/gtest/subtype-exprs.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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/subtype-exprs.h"
18+
#include "wasm-traversal.h"
19+
#include "wasm.h"
20+
#include "gtest/gtest.h"
21+
22+
using namespace wasm;
23+
24+
static Type anyref = Type(HeapType::any, Nullable);
25+
26+
// A BrIf must require of its value to both match the block it targets, and
27+
// also the BrIf itself, as the value flows out.
28+
TEST(SubtypeExprsTest, BrIf) {
29+
Module wasm;
30+
Builder builder(wasm);
31+
// A br_if in a block, whose value is a null.
32+
auto* null = builder.makeRefNull(HeapType::any);
33+
auto* c = builder.makeConst(Literal(int32_t(42)));
34+
auto* brIf = builder.makeBreak("block", null, c);
35+
auto* block = builder.makeBlock("block", {brIf}, anyref);
36+
auto* drop = builder.makeDrop(block);
37+
auto func = builder.makeFunction(
38+
"func", {}, Signature(Type::none, Type::none), {}, drop);
39+
40+
// Implement a SubtypingDiscoverer.
41+
struct Finder
42+
: public ControlFlowWalker<Finder, SubtypingDiscoverer<Finder>> {
43+
std::set<Expression*> seen;
44+
45+
void noteSubtype(Type sub, Type super) {}
46+
void noteSubtype(HeapType sub, HeapType super) {}
47+
void noteSubtype(Type sub, Expression* super) {}
48+
void noteSubtype(Expression* sub, Type super) {}
49+
void noteSubtype(Expression* sub, Expression* super) {
50+
if (sub->is<RefNull>()) {
51+
// The null will be called with two constraints: the block (the null is
52+
// sent there) and also the break (the null flows out through it).
53+
seen.insert(super);
54+
}
55+
}
56+
void noteNonFlowSubtype(Expression* sub, Type super) {}
57+
void noteCast(HeapType src, Type dst) {}
58+
void noteCast(Expression* src, Type dst) {}
59+
void noteCast(Expression* src, Expression* dst) {}
60+
} finder;
61+
62+
finder.walkFunctionInModule(func.get(), &wasm);
63+
64+
// We should have seen both things.
65+
ASSERT_EQ(finder.seen.size(), 2);
66+
EXPECT_EQ(finder.seen.count(brIf), 1);
67+
EXPECT_EQ(finder.seen.count(block), 1);
68+
69+
// Remove the condition from the br. Now it is unreachable and no value
70+
// flows out.
71+
brIf->condition = nullptr;
72+
brIf->type = Type::unreachable;
73+
74+
// We should now only see the sent value - nothing flows out to the br_if.
75+
finder.seen.clear();
76+
finder.walkFunctionInModule(func.get(), &wasm);
77+
ASSERT_EQ(finder.seen.size(), 1);
78+
EXPECT_EQ(finder.seen.count(block), 1);
79+
}

0 commit comments

Comments
 (0)