summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/gen-s-parser.py5
-rw-r--r--src/gen-s-parser.inc42
-rw-r--r--src/ir/ReFinalize.cpp2
-rw-r--r--src/ir/branch-utils.h2
-rw-r--r--src/ir/gc-type-utils.h20
-rw-r--r--src/passes/Print.cpp15
-rw-r--r--src/passes/RemoveUnusedBrs.cpp13
-rw-r--r--src/wasm-binary.h5
-rw-r--r--src/wasm-interpreter.h78
-rw-r--r--src/wasm.h10
-rw-r--r--src/wasm/wasm-binary.cpp20
-rw-r--r--src/wasm/wasm-s-parser.cpp2
-rw-r--r--src/wasm/wasm-stack.cpp15
-rw-r--r--src/wasm/wasm-validator.cpp4
-rw-r--r--src/wasm/wasm.cpp49
-rw-r--r--test/heap-types.wast44
-rw-r--r--test/heap-types.wast.from-wast55
-rw-r--r--test/heap-types.wast.fromBinary55
-rw-r--r--test/heap-types.wast.fromBinary.noDebugInfo55
-rw-r--r--test/lit/passes/remove-unused-brs-gc.wast54
-rw-r--r--test/passes/Oz_fuzz-exec_all-features.txt130
-rw-r--r--test/passes/Oz_fuzz-exec_all-features.wast115
-rw-r--r--test/passes/remove-unused-brs_all-features.txt16
-rw-r--r--test/passes/remove-unused-brs_all-features.wast9
24 files changed, 746 insertions, 69 deletions
diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py
index 731d50fd7..f7ff7a1fb 100755
--- a/scripts/gen-s-parser.py
+++ b/scripts/gen-s-parser.py
@@ -539,10 +539,15 @@ instructions = [
("ref.test", "makeRefTest(s)"),
("ref.cast", "makeRefCast(s)"),
("br_on_null", "makeBrOn(s, BrOnNull)"),
+ ("br_on_non_null", "makeBrOn(s, BrOnNonNull)"),
("br_on_cast", "makeBrOn(s, BrOnCast)"),
+ ("br_on_cast_fail", "makeBrOn(s, BrOnCastFail)"),
("br_on_func", "makeBrOn(s, BrOnFunc)"),
+ ("br_on_non_func", "makeBrOn(s, BrOnNonFunc)"),
("br_on_data", "makeBrOn(s, BrOnData)"),
+ ("br_on_non_data", "makeBrOn(s, BrOnNonData)"),
("br_on_i31", "makeBrOn(s, BrOnI31)"),
+ ("br_on_non_i31", "makeBrOn(s, BrOnNonI31)"),
("rtt.canon", "makeRttCanon(s)"),
("rtt.sub", "makeRttSub(s)"),
("struct.new_with_rtt", "makeStructNew(s, false)"),
diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc
index 7aba3faf1..2eca900cc 100644
--- a/src/gen-s-parser.inc
+++ b/src/gen-s-parser.inc
@@ -76,9 +76,17 @@ switch (op[0]) {
goto parse_error;
case 'o': {
switch (op[6]) {
- case 'c':
- if (strcmp(op, "br_on_cast") == 0) { return makeBrOn(s, BrOnCast); }
- goto parse_error;
+ case 'c': {
+ switch (op[10]) {
+ case '\0':
+ if (strcmp(op, "br_on_cast") == 0) { return makeBrOn(s, BrOnCast); }
+ goto parse_error;
+ case '_':
+ if (strcmp(op, "br_on_cast_fail") == 0) { return makeBrOn(s, BrOnCastFail); }
+ goto parse_error;
+ default: goto parse_error;
+ }
+ }
case 'd':
if (strcmp(op, "br_on_data") == 0) { return makeBrOn(s, BrOnData); }
goto parse_error;
@@ -88,9 +96,31 @@ switch (op[0]) {
case 'i':
if (strcmp(op, "br_on_i31") == 0) { return makeBrOn(s, BrOnI31); }
goto parse_error;
- case 'n':
- if (strcmp(op, "br_on_null") == 0) { return makeBrOn(s, BrOnNull); }
- goto parse_error;
+ case 'n': {
+ switch (op[7]) {
+ case 'o': {
+ switch (op[10]) {
+ case 'd':
+ if (strcmp(op, "br_on_non_data") == 0) { return makeBrOn(s, BrOnNonData); }
+ goto parse_error;
+ case 'f':
+ if (strcmp(op, "br_on_non_func") == 0) { return makeBrOn(s, BrOnNonFunc); }
+ goto parse_error;
+ case 'i':
+ if (strcmp(op, "br_on_non_i31") == 0) { return makeBrOn(s, BrOnNonI31); }
+ goto parse_error;
+ case 'n':
+ if (strcmp(op, "br_on_non_null") == 0) { return makeBrOn(s, BrOnNonNull); }
+ goto parse_error;
+ default: goto parse_error;
+ }
+ }
+ case 'u':
+ if (strcmp(op, "br_on_null") == 0) { return makeBrOn(s, BrOnNull); }
+ goto parse_error;
+ default: goto parse_error;
+ }
+ }
default: goto parse_error;
}
}
diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp
index 1dcb55f61..b024c94be 100644
--- a/src/ir/ReFinalize.cpp
+++ b/src/ir/ReFinalize.cpp
@@ -153,7 +153,7 @@ void ReFinalize::visitBrOn(BrOn* curr) {
if (curr->type == Type::unreachable) {
replaceUntaken(curr->ref, nullptr);
} else {
- updateBreakValueType(curr->name, curr->getCastType());
+ updateBreakValueType(curr->name, curr->getSentType());
}
}
void ReFinalize::visitRttCanon(RttCanon* curr) { curr->finalize(); }
diff --git a/src/ir/branch-utils.h b/src/ir/branch-utils.h
index 662f70725..87f7813e7 100644
--- a/src/ir/branch-utils.h
+++ b/src/ir/branch-utils.h
@@ -81,7 +81,7 @@ void operateOnScopeNameUsesAndSentTypes(Expression* expr, T func) {
} else if (auto* sw = expr->dynCast<Switch>()) {
func(name, sw->value ? sw->value->type : Type::none);
} else if (auto* br = expr->dynCast<BrOn>()) {
- func(name, br->getCastType());
+ func(name, br->getSentType());
} else {
assert(expr->is<Try>() || expr->is<Rethrow>()); // delegate or rethrow
}
diff --git a/src/ir/gc-type-utils.h b/src/ir/gc-type-utils.h
index d59193193..70db7b178 100644
--- a/src/ir/gc-type-utils.h
+++ b/src/ir/gc-type-utils.h
@@ -46,19 +46,33 @@ inline EvaluationResult evaluateKindCheck(Expression* curr) {
Kind expected;
Expression* child;
+ // Some operations flip the condition.
+ bool flip = false;
+
if (auto* br = curr->dynCast<BrOn>()) {
switch (br->op) {
// We don't check nullability here.
case BrOnNull:
+ case BrOnNonNull:
// Casts can only be known at runtime using RTTs.
case BrOnCast:
+ case BrOnCastFail:
return Unknown;
+ case BrOnNonFunc:
+ flip = true;
+ [[fallthrough]];
case BrOnFunc:
expected = Func;
break;
+ case BrOnNonData:
+ flip = true;
+ [[fallthrough]];
case BrOnData:
expected = Data;
break;
+ case BrOnNonI31:
+ flip = true;
+ [[fallthrough]];
case BrOnI31:
expected = I31;
break;
@@ -120,7 +134,11 @@ inline EvaluationResult evaluateKindCheck(Expression* curr) {
return Unknown;
}
- return actual == expected ? Success : Failure;
+ auto success = actual == expected;
+ if (flip) {
+ success = !success;
+ }
+ return success ? Success : Failure;
}
} // namespace GCTypeUtils
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index df408fdf3..47db029ab 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -1888,18 +1888,33 @@ struct PrintExpressionContents
case BrOnNull:
printMedium(o, "br_on_null ");
break;
+ case BrOnNonNull:
+ printMedium(o, "br_on_non_null ");
+ break;
case BrOnCast:
printMedium(o, "br_on_cast ");
break;
+ case BrOnCastFail:
+ printMedium(o, "br_on_cast_fail ");
+ break;
case BrOnFunc:
printMedium(o, "br_on_func ");
break;
+ case BrOnNonFunc:
+ printMedium(o, "br_on_non_func ");
+ break;
case BrOnData:
printMedium(o, "br_on_data ");
break;
+ case BrOnNonData:
+ printMedium(o, "br_on_non_data ");
+ break;
case BrOnI31:
printMedium(o, "br_on_i31 ");
break;
+ case BrOnNonI31:
+ printMedium(o, "br_on_non_i31 ");
+ break;
default:
WASM_UNREACHABLE("invalid ref.is_*");
}
diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp
index 88a87ba1f..5631c9d5f 100644
--- a/src/passes/RemoveUnusedBrs.cpp
+++ b/src/passes/RemoveUnusedBrs.cpp
@@ -389,10 +389,9 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
// First, check for a possible null which would prevent all other
// optimizations.
- // (Note: if the spec had BrOnNonNull, instead of BrOnNull, then we could
- // replace a br_on_func whose input is (ref null func) with br_on_non_null,
- // as only the null check would be needed. But as things are, we cannot do
- // such a thing.)
+ // TODO: Look into using BrOnNonNull here, to replace a br_on_func whose
+ // input is (ref null func) with br_on_non_null (as only the null check
+ // would be needed).
auto refType = curr->ref->type;
if (refType.isNullable()) {
return;
@@ -405,6 +404,12 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
anotherCycle = true;
return;
}
+ if (curr->op == BrOnNonNull) {
+ // This cannot be null, so the br is always taken.
+ replaceCurrent(Builder(*getModule()).makeBreak(curr->name, curr->ref));
+ anotherCycle = true;
+ return;
+ }
// Check if the type is the kind we are checking for.
auto result = GCTypeUtils::evaluateKindCheck(curr);
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index 00c45ef60..408aafc63 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -1019,6 +1019,7 @@ enum ASTNodes {
RefFunc = 0xd2,
RefAsNonNull = 0xd3,
BrOnNull = 0xd4,
+ BrOnNonNull = 0xd6,
// exception handling opcodes
@@ -1060,6 +1061,7 @@ enum ASTNodes {
RefTest = 0x40,
RefCast = 0x41,
BrOnCast = 0x42,
+ BrOnCastFail = 0x43,
RefIsFunc = 0x50,
RefIsData = 0x51,
RefIsI31 = 0x52,
@@ -1069,6 +1071,9 @@ enum ASTNodes {
BrOnFunc = 0x60,
BrOnData = 0x61,
BrOnI31 = 0x62,
+ BrOnNonFunc = 0x63,
+ BrOnNonData = 0x64,
+ BrOnNonI31 = 0x65,
};
enum MemoryAccess {
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index 86d868ac8..94230b747 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -1500,17 +1500,25 @@ public:
}
Flow visitBrOn(BrOn* curr) {
NOTE_ENTER("BrOn");
- // BrOnCast uses the casting infrastructure, so handle it first.
- if (curr->op == BrOnCast) {
+ // BrOnCast* uses the casting infrastructure, so handle it first.
+ if (curr->op == BrOnCast || curr->op == BrOnCastFail) {
auto cast = doCast(curr);
if (cast.outcome == cast.Break) {
return cast.breaking;
}
if (cast.outcome == cast.Null || cast.outcome == cast.Failure) {
- return cast.originalRef;
+ if (curr->op == BrOnCast) {
+ return cast.originalRef;
+ } else {
+ return Flow(curr->name, cast.originalRef);
+ }
}
assert(cast.outcome == cast.Success);
- return Flow(curr->name, cast.castRef);
+ if (curr->op == BrOnCast) {
+ return Flow(curr->name, cast.castRef);
+ } else {
+ return cast.castRef;
+ }
}
// The others do a simpler check for the type.
Flow flow = visit(curr->ref);
@@ -1528,30 +1536,54 @@ public:
// If the branch is not taken, we return the non-null value.
return {value};
}
+ if (curr->op == BrOnNonNull) {
+ // Unlike the others, BrOnNonNull does not return a value if it does not
+ // take the branch.
+ if (value.isNull()) {
+ return Flow();
+ }
+ // If the branch is taken, we send the non-null value.
+ return Flow(curr->name, value);
+ }
+ // See if the input is the right kind (ignoring the flipping behavior of
+ // BrOn*).
+ bool isRightKind;
if (value.isNull()) {
- return {value};
+ // A null is never the right kind.
+ isRightKind = false;
+ } else {
+ switch (curr->op) {
+ case BrOnNonFunc:
+ case BrOnFunc:
+ isRightKind = value.type.isFunction();
+ break;
+ case BrOnNonData:
+ case BrOnData:
+ isRightKind = value.isData();
+ break;
+ case BrOnNonI31:
+ case BrOnI31:
+ isRightKind = value.type.getHeapType() == HeapType::i31;
+ break;
+ default:
+ WASM_UNREACHABLE("invalid br_on_*");
+ }
}
+ // The Non* operations require us to flip the normal behavior.
switch (curr->op) {
- case BrOnFunc:
- if (!value.type.isFunction()) {
- return {value};
- }
- break;
- case BrOnData:
- if (!value.isData()) {
- return {value};
- }
+ case BrOnNonFunc:
+ case BrOnNonData:
+ case BrOnNonI31:
+ isRightKind = !isRightKind;
break;
- case BrOnI31:
- if (value.type.getHeapType() != HeapType::i31) {
- return {value};
- }
- break;
- default:
- WASM_UNREACHABLE("invalid br_on_*");
+ default: {
+ }
+ }
+ if (isRightKind) {
+ // Take the branch.
+ return Flow(curr->name, value);
}
- // No problems: take the branch.
- return Flow(curr->name, value);
+ return {value};
}
Flow visitRttCanon(RttCanon* curr) { return Literal(curr->type); }
Flow visitRttSub(RttSub* curr) {
diff --git a/src/wasm.h b/src/wasm.h
index baea1fabe..08f039451 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -545,10 +545,15 @@ enum RefAsOp {
enum BrOnOp {
BrOnNull,
+ BrOnNonNull,
BrOnCast,
+ BrOnCastFail,
BrOnFunc,
+ BrOnNonFunc,
BrOnData,
+ BrOnNonData,
BrOnI31,
+ BrOnNonI31,
};
//
@@ -1374,7 +1379,7 @@ public:
Name name;
Expression* ref;
- // BrOnCast has an rtt that is used in the cast.
+ // BrOnCast* has an rtt that is used in the cast.
Expression* rtt;
// TODO: BrOnNull also has an optional extra value in the spec, which we do
@@ -1385,7 +1390,8 @@ public:
void finalize();
- Type getCastType();
+ // Returns the type sent on the branch, if it is taken.
+ Type getSentType();
};
class RttCanon : public SpecificExpression<Expression::RttCanonId> {
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index 12aa5f214..35bc468b1 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -3377,6 +3377,9 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
case BinaryConsts::BrOnNull:
maybeVisitBrOn(curr, code);
break;
+ case BinaryConsts::BrOnNonNull:
+ maybeVisitBrOn(curr, code);
+ break;
case BinaryConsts::Try:
visitTryOrTryInBlock(curr);
break;
@@ -6333,24 +6336,39 @@ bool WasmBinaryBuilder::maybeVisitBrOn(Expression*& out, uint32_t code) {
case BinaryConsts::BrOnNull:
op = BrOnNull;
break;
+ case BinaryConsts::BrOnNonNull:
+ op = BrOnNonNull;
+ break;
case BinaryConsts::BrOnCast:
op = BrOnCast;
break;
+ case BinaryConsts::BrOnCastFail:
+ op = BrOnCastFail;
+ break;
case BinaryConsts::BrOnFunc:
op = BrOnFunc;
break;
+ case BinaryConsts::BrOnNonFunc:
+ op = BrOnNonFunc;
+ break;
case BinaryConsts::BrOnData:
op = BrOnData;
break;
+ case BinaryConsts::BrOnNonData:
+ op = BrOnNonData;
+ break;
case BinaryConsts::BrOnI31:
op = BrOnI31;
break;
+ case BinaryConsts::BrOnNonI31:
+ op = BrOnNonI31;
+ break;
default:
return false;
}
auto name = getBreakTarget(getU32LEB()).name;
Expression* rtt = nullptr;
- if (op == BrOnCast) {
+ if (op == BrOnCast || op == BrOnCastFail) {
rtt = popNonVoidExpression();
}
auto* ref = popNonVoidExpression();
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index 25db9a993..9c2d028a4 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -2573,7 +2573,7 @@ Expression* SExpressionWasmBuilder::makeBrOn(Element& s, BrOnOp op) {
auto name = getLabel(*s[1]);
auto* ref = parseExpression(*s[2]);
Expression* rtt = nullptr;
- if (op == BrOnCast) {
+ if (op == BrOnCast || op == BrOnCastFail) {
rtt = parseExpression(*s[3]);
}
return ValidatingBuilder(wasm, s.line, s.col)
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index 12c999c0d..70a593bec 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -1954,18 +1954,33 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) {
case BrOnNull:
o << int8_t(BinaryConsts::BrOnNull);
break;
+ case BrOnNonNull:
+ o << int8_t(BinaryConsts::BrOnNonNull);
+ break;
case BrOnCast:
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnCast);
break;
+ case BrOnCastFail:
+ o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnCastFail);
+ break;
case BrOnFunc:
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnFunc);
break;
+ case BrOnNonFunc:
+ o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnNonFunc);
+ break;
case BrOnData:
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnData);
break;
+ case BrOnNonData:
+ o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnNonData);
+ break;
case BrOnI31:
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnI31);
break;
+ case BrOnNonI31:
+ o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnNonI31);
+ break;
default:
WASM_UNREACHABLE("invalid br_on_*");
}
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index 10b60e0a7..bbc92f09c 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -2245,14 +2245,14 @@ void FunctionValidator::visitBrOn(BrOn* curr) {
shouldBeTrue(
curr->ref->type.isRef(), curr, "br_on_cast ref must have ref type");
}
- if (curr->op == BrOnCast) {
+ if (curr->op == BrOnCast || curr->op == BrOnCastFail) {
// Note that an unreachable rtt is not supported: the text and binary
// formats do not provide the type, so if it's unreachable we should not
// even create a br_on_cast in such a case, as we'd have no idea what it
// casts to.
shouldBeTrue(
curr->rtt->type.isRtt(), curr, "br_on_cast rtt must have rtt type");
- noteBreak(curr->name, curr->getCastType(), curr);
+ noteBreak(curr->name, curr->getSentType(), curr);
} else {
shouldBeTrue(curr->rtt == nullptr, curr, "non-cast BrOn must not have rtt");
}
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index f0d130d6b..b41f34ddf 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -916,22 +916,52 @@ void BrOn::finalize() {
if (ref->type == Type::unreachable ||
(rtt && rtt->type == Type::unreachable)) {
type = Type::unreachable;
- } else {
- if (op == BrOnNull) {
- // If BrOnNull does not branch, it flows out the existing value as
- // non-null.
+ return;
+ }
+ switch (op) {
+ case BrOnNull:
+ // If we do not branch, we flow out the existing value as non-null.
type = Type(ref->type.getHeapType(), NonNullable);
- } else {
+ break;
+ case BrOnNonNull:
+ // If we do not branch, we flow out nothing (the spec could also have had
+ // us flow out the null, but it does not).
+ type = Type::none;
+ break;
+ case BrOnCast:
+ case BrOnFunc:
+ case BrOnData:
+ case BrOnI31:
+ // If we do not branch, we return the input in this case.
type = ref->type;
- }
+ break;
+ case BrOnCastFail:
+ // If we do not branch, the cast worked, and we have something of the cast
+ // type.
+ type = Type(rtt->type.getHeapType(), NonNullable);
+ break;
+ case BrOnNonFunc:
+ type = Type(HeapType::func, NonNullable);
+ break;
+ case BrOnNonData:
+ type = Type(HeapType::data, NonNullable);
+ break;
+ case BrOnNonI31:
+ type = Type(HeapType::i31, NonNullable);
+ break;
+ default:
+ WASM_UNREACHABLE("invalid br_on_*");
}
}
-Type BrOn::getCastType() {
+Type BrOn::getSentType() {
switch (op) {
case BrOnNull:
// BrOnNull does not send a value on the branch.
return Type::none;
+ case BrOnNonNull:
+ // BrOnNonNull sends the non-nullable type on the branch.
+ return Type(ref->type.getHeapType(), NonNullable);
case BrOnCast:
return Type(rtt->type.getHeapType(), NonNullable);
case BrOnFunc:
@@ -940,6 +970,11 @@ Type BrOn::getCastType() {
return Type::dataref;
case BrOnI31:
return Type::i31ref;
+ case BrOnCastFail:
+ case BrOnNonFunc:
+ case BrOnNonData:
+ case BrOnNonI31:
+ return ref->type;
default:
WASM_UNREACHABLE("invalid br_on_*");
}
diff --git a/test/heap-types.wast b/test/heap-types.wast
index 35284bef6..0445c97d5 100644
--- a/test/heap-types.wast
+++ b/test/heap-types.wast
@@ -194,6 +194,7 @@
(func $rtt-param-without-depth (param $rtt (rtt $parent)))
(func $rtt-operations
(local $temp.A (ref null $struct.A))
+ (local $temp.B (ref null $struct.B))
(drop
(ref.test (ref.null $struct.A) (rtt.canon $struct.B))
)
@@ -213,6 +214,16 @@
(unreachable)
)
)
+ (drop
+ (block $out2 (result (ref null $struct.A))
+ ;; set the value to a local with type $struct.A, showing that the value
+ ;; flowing out has the right type
+ (local.set $temp.B
+ (br_on_cast_fail $out2 (ref.null $struct.A) (rtt.canon $struct.B))
+ )
+ (ref.null $struct.A)
+ )
+ )
)
(func $ref.is_X (param $x anyref)
(if (ref.is_func (local.get $x)) (unreachable))
@@ -228,6 +239,9 @@
(func $br_on_X (param $x anyref)
(local $y anyref)
(local $z (ref null any))
+ (local $temp-func (ref null func))
+ (local $temp-data (ref null data))
+ (local $temp-i31 (ref null i31))
(block $null
(local.set $z
(br_on_null $null (local.get $x))
@@ -257,6 +271,36 @@
(ref.null i31)
)
)
+ (drop
+ (block $non-null (result (ref any))
+ (br_on_non_null $non-null (local.get $x))
+ (unreachable)
+ )
+ )
+ (drop
+ (block $non-func (result anyref)
+ (local.set $temp-func
+ (br_on_non_func $non-func (local.get $x))
+ )
+ (ref.null any)
+ )
+ )
+ (drop
+ (block $non-data (result anyref)
+ (local.set $temp-data
+ (br_on_non_data $non-data (local.get $x))
+ )
+ (ref.null any)
+ )
+ )
+ (drop
+ (block $non-i31 (result anyref)
+ (local.set $temp-i31
+ (br_on_non_i31 $non-i31 (local.get $x))
+ )
+ (ref.null any)
+ )
+ )
)
(func $unreachables-1
(drop
diff --git a/test/heap-types.wast.from-wast b/test/heap-types.wast.from-wast
index e9dfc9a25..dfb8b622a 100644
--- a/test/heap-types.wast.from-wast
+++ b/test/heap-types.wast.from-wast
@@ -1,8 +1,8 @@
(module
(type $struct.A (struct (field i32) (field f32) (field $named f64)))
+ (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A)))))
(type $vector (array (mut f64)))
(type $none_=>_none (func))
- (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A)))))
(type $grandchild (struct (field i32) (field i64)))
(type $struct.C (struct (field $named-mut (mut f32))))
(type $matrix (array (mut (ref null $vector))))
@@ -195,6 +195,7 @@
)
(func $rtt-operations
(local $temp.A (ref null $struct.A))
+ (local $temp.B (ref null $struct.B))
(drop
(ref.test
(ref.null $struct.A)
@@ -224,6 +225,17 @@
(unreachable)
)
)
+ (drop
+ (block $out2 (result (ref null $struct.A))
+ (local.set $temp.B
+ (br_on_cast_fail $out2
+ (ref.null $struct.A)
+ (rtt.canon $struct.B)
+ )
+ )
+ (ref.null $struct.A)
+ )
+ )
)
(func $ref.is_X (param $x anyref)
(if
@@ -270,6 +282,9 @@
(func $br_on_X (param $x anyref)
(local $y anyref)
(local $z anyref)
+ (local $temp-func funcref)
+ (local $temp-data (ref null data))
+ (local $temp-i31 (ref null i31))
(block $null
(local.set $z
(br_on_null $null
@@ -307,6 +322,44 @@
(ref.null i31)
)
)
+ (drop
+ (block $non-null (result (ref any))
+ (br_on_non_null $non-null
+ (local.get $x)
+ )
+ (unreachable)
+ )
+ )
+ (drop
+ (block $non-func (result anyref)
+ (local.set $temp-func
+ (br_on_non_func $non-func
+ (local.get $x)
+ )
+ )
+ (ref.null any)
+ )
+ )
+ (drop
+ (block $non-data (result anyref)
+ (local.set $temp-data
+ (br_on_non_data $non-data
+ (local.get $x)
+ )
+ )
+ (ref.null any)
+ )
+ )
+ (drop
+ (block $non-i31 (result anyref)
+ (local.set $temp-i31
+ (br_on_non_i31 $non-i31
+ (local.get $x)
+ )
+ )
+ (ref.null any)
+ )
+ )
)
(func $unreachables-1
(drop
diff --git a/test/heap-types.wast.fromBinary b/test/heap-types.wast.fromBinary
index dbe080996..314b3c29e 100644
--- a/test/heap-types.wast.fromBinary
+++ b/test/heap-types.wast.fromBinary
@@ -1,8 +1,8 @@
(module
(type $struct.A (struct (field i32) (field f32) (field $named f64)))
+ (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A)))))
(type $vector (array (mut f64)))
(type $none_=>_none (func))
- (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A)))))
(type $grandchild (struct (field i32) (field i64)))
(type $matrix (array (mut (ref null $vector))))
(type $struct.C (struct (field $named-mut (mut f32))))
@@ -195,6 +195,7 @@
)
(func $rtt-operations
(local $temp.A (ref null $struct.A))
+ (local $temp.B (ref null $struct.B))
(drop
(ref.test
(ref.null $struct.A)
@@ -223,6 +224,17 @@
)
)
)
+ (drop
+ (block $label$3 (result (ref null $struct.A))
+ (local.set $temp.B
+ (br_on_cast_fail $label$3
+ (ref.null $struct.A)
+ (rtt.canon $struct.B)
+ )
+ )
+ (ref.null $struct.A)
+ )
+ )
)
(func $ref.is_X (param $x anyref)
(if
@@ -269,6 +281,9 @@
(func $br_on_X (param $x anyref)
(local $y anyref)
(local $z anyref)
+ (local $temp-func funcref)
+ (local $temp-data (ref null data))
+ (local $temp-i31 (ref null i31))
(block $label$1
(local.set $z
(br_on_null $label$1
@@ -306,6 +321,44 @@
(ref.null i31)
)
)
+ (drop
+ (block $label$5 (result (ref any))
+ (br_on_non_null $label$5
+ (local.get $x)
+ )
+ (unreachable)
+ )
+ )
+ (drop
+ (block $label$6 (result anyref)
+ (local.set $temp-func
+ (br_on_non_func $label$6
+ (local.get $x)
+ )
+ )
+ (ref.null any)
+ )
+ )
+ (drop
+ (block $label$7 (result anyref)
+ (local.set $temp-data
+ (br_on_non_data $label$7
+ (local.get $x)
+ )
+ )
+ (ref.null any)
+ )
+ )
+ (drop
+ (block $label$8 (result anyref)
+ (local.set $temp-i31
+ (br_on_non_i31 $label$8
+ (local.get $x)
+ )
+ )
+ (ref.null any)
+ )
+ )
)
(func $unreachables-1
(unreachable)
diff --git a/test/heap-types.wast.fromBinary.noDebugInfo b/test/heap-types.wast.fromBinary.noDebugInfo
index 2fb57aacb..bb6ff9165 100644
--- a/test/heap-types.wast.fromBinary.noDebugInfo
+++ b/test/heap-types.wast.fromBinary.noDebugInfo
@@ -1,8 +1,8 @@
(module
(type ${i32_f32_f64} (struct (field i32) (field f32) (field f64)))
+ (type ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|} (struct (field i8) (field (mut i16)) (field (ref ${i32_f32_f64})) (field (mut (ref ${i32_f32_f64})))))
(type $[mut:f64] (array (mut f64)))
(type $none_=>_none (func))
- (type ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|} (struct (field i8) (field (mut i16)) (field (ref ${i32_f32_f64})) (field (mut (ref ${i32_f32_f64})))))
(type ${i32_i64} (struct (field i32) (field i64)))
(type $[mut:ref?|[mut:f64]|] (array (mut (ref null $[mut:f64]))))
(type ${mut:f32} (struct (field (mut f32))))
@@ -195,6 +195,7 @@
)
(func $4
(local $0 (ref null ${i32_f32_f64}))
+ (local $1 (ref null ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}))
(drop
(ref.test
(ref.null ${i32_f32_f64})
@@ -223,6 +224,17 @@
)
)
)
+ (drop
+ (block $label$3 (result (ref null ${i32_f32_f64}))
+ (local.set $1
+ (br_on_cast_fail $label$3
+ (ref.null ${i32_f32_f64})
+ (rtt.canon ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|})
+ )
+ )
+ (ref.null ${i32_f32_f64})
+ )
+ )
)
(func $5 (param $0 anyref)
(if
@@ -269,6 +281,9 @@
(func $7 (param $0 anyref)
(local $1 anyref)
(local $2 anyref)
+ (local $3 funcref)
+ (local $4 (ref null data))
+ (local $5 (ref null i31))
(block $label$1
(local.set $2
(br_on_null $label$1
@@ -306,6 +321,44 @@
(ref.null i31)
)
)
+ (drop
+ (block $label$5 (result (ref any))
+ (br_on_non_null $label$5
+ (local.get $0)
+ )
+ (unreachable)
+ )
+ )
+ (drop
+ (block $label$6 (result anyref)
+ (local.set $3
+ (br_on_non_func $label$6
+ (local.get $0)
+ )
+ )
+ (ref.null any)
+ )
+ )
+ (drop
+ (block $label$7 (result anyref)
+ (local.set $4
+ (br_on_non_data $label$7
+ (local.get $0)
+ )
+ )
+ (ref.null any)
+ )
+ )
+ (drop
+ (block $label$8 (result anyref)
+ (local.set $5
+ (br_on_non_i31 $label$8
+ (local.get $0)
+ )
+ )
+ (ref.null any)
+ )
+ )
)
(func $8
(unreachable)
diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast
new file mode 100644
index 000000000..7fe579c5a
--- /dev/null
+++ b/test/lit/passes/remove-unused-brs-gc.wast
@@ -0,0 +1,54 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --remove-unused-brs -all -S -o - \
+;; RUN: | filecheck %s
+
+(module
+ ;; CHECK: (func $br_on_non_data-1
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $any (result anyref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (br $any
+ ;; CHECK-NEXT: (ref.func $br_on_non_data-1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null any)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $br_on_non_data-1
+ (drop
+ (block $any (result anyref)
+ (drop
+ ;; A function is not data, and so we should branch.
+ (br_on_non_data $any
+ (ref.func $br_on_non_data-1)
+ )
+ )
+ (ref.null any)
+ )
+ )
+ )
+ ;; CHECK: (func $br_on_non_data-2 (param $data dataref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $any (result anyref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $data)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null any)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $br_on_non_data-2 (param $data (ref data))
+ (drop
+ (block $any (result anyref)
+ (drop
+ ;; Data is provided here, and so we will not branch.
+ (br_on_non_data $any
+ (local.get $data)
+ )
+ )
+ (ref.null any)
+ )
+ )
+ )
+)
diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt
index 359311e41..2ce8cfa00 100644
--- a/test/passes/Oz_fuzz-exec_all-features.txt
+++ b/test/passes/Oz_fuzz-exec_all-features.txt
@@ -21,10 +21,23 @@
[fuzz-exec] calling br_on_cast
[LoggingExternalInterface logging 3]
[trap unreachable]
+[fuzz-exec] calling br_on_failed_cast-1
+[LoggingExternalInterface logging 1]
+[fuzz-exec] calling br_on_failed_cast-2
+[LoggingExternalInterface logging 1]
+[LoggingExternalInterface logging 999]
[fuzz-exec] calling cast-null-anyref-to-gc
[LoggingExternalInterface logging 0]
[fuzz-exec] calling br_on_data
[LoggingExternalInterface logging 1]
+[fuzz-exec] calling br_on_non_data-null
+[fuzz-exec] calling br_on_non_data-data
+[LoggingExternalInterface logging 1]
+[fuzz-exec] calling br_on_non_data-other
+[fuzz-exec] calling br-on_non_null
+[fuzz-exec] calling br-on_non_null-2
+[LoggingExternalInterface logging 1]
+[trap unreachable]
[fuzz-exec] calling rtt-and-cast-on-func
[LoggingExternalInterface logging 0]
[LoggingExternalInterface logging 1]
@@ -45,10 +58,10 @@
[LoggingExternalInterface logging 0]
[LoggingExternalInterface logging 10]
(module
- (type $struct (struct (field (mut i32))))
(type $void_func (func))
- (type $bytes (array (mut i8)))
+ (type $struct (struct (field (mut i32))))
(type $extendedstruct (struct (field (mut i32)) (field f64)))
+ (type $bytes (array (mut i8)))
(type $int_func (func (result i32)))
(type $i32_=>_none (func (param i32)))
(type $anyref_=>_none (func (param anyref)))
@@ -59,13 +72,20 @@
(export "arrays" (func $1))
(export "rtts" (func $2))
(export "br_on_cast" (func $3))
- (export "cast-null-anyref-to-gc" (func $4))
- (export "br_on_data" (func $6))
- (export "rtt-and-cast-on-func" (func $8))
+ (export "br_on_failed_cast-1" (func $4))
+ (export "br_on_failed_cast-2" (func $5))
+ (export "cast-null-anyref-to-gc" (func $6))
+ (export "br_on_data" (func $8))
+ (export "br_on_non_data-null" (func $9))
+ (export "br_on_non_data-data" (func $10))
+ (export "br_on_non_data-other" (func $9))
+ (export "br-on_non_null" (func $9))
+ (export "br-on_non_null-2" (func $13))
+ (export "rtt-and-cast-on-func" (func $15))
(export "array-alloc-failure" (func $9))
- (export "init-array-packed" (func $10))
- (export "cast-func-to-struct" (func $12))
- (export "array-copy" (func $13))
+ (export "init-array-packed" (func $17))
+ (export "cast-func-to-struct" (func $19))
+ (export "array-copy" (func $20))
(func $0 (; has Stack IR ;)
(local $0 i32)
(call $log
@@ -221,11 +241,61 @@
(unreachable)
)
(func $4 (; has Stack IR ;)
+ (local $0 anyref)
+ (local.set $0
+ (struct.new_default_with_rtt $struct
+ (rtt.canon $struct)
+ )
+ )
+ (drop
+ (block $any (result anyref)
+ (call $log
+ (i32.const 1)
+ )
+ (drop
+ (br_on_cast_fail $any
+ (local.get $0)
+ (rtt.canon $extendedstruct)
+ )
+ )
+ (call $log
+ (i32.const 999)
+ )
+ (ref.null any)
+ )
+ )
+ )
+ (func $5 (; has Stack IR ;)
+ (local $0 anyref)
+ (local.set $0
+ (struct.new_default_with_rtt $extendedstruct
+ (rtt.canon $extendedstruct)
+ )
+ )
+ (drop
+ (block $any (result anyref)
+ (call $log
+ (i32.const 1)
+ )
+ (drop
+ (br_on_cast_fail $any
+ (local.get $0)
+ (rtt.canon $extendedstruct)
+ )
+ )
+ (call $log
+ (i32.const 999)
+ )
+ (ref.null any)
+ )
+ )
+ )
+ (func $6 (; has Stack IR ;)
(call $log
(i32.const 0)
)
)
- (func $6 (; has Stack IR ;) (param $0 anyref)
+ (func $8 (; has Stack IR ;) (param $0 anyref)
(drop
(block $data (result dataref)
(drop
@@ -242,12 +312,30 @@
)
)
)
+ (func $9 (; has Stack IR ;)
+ (nop)
+ )
+ (func $10 (; has Stack IR ;)
+ (call $log
+ (i32.const 1)
+ )
+ )
+ (func $13 (; has Stack IR ;)
+ (drop
+ (block
+ (call $log
+ (i32.const 1)
+ )
+ (unreachable)
+ )
+ )
+ )
(func $a-void-func (; has Stack IR ;)
(call $log
(i32.const 1337)
)
)
- (func $8 (; has Stack IR ;)
+ (func $15 (; has Stack IR ;)
(call $log
(i32.const 0)
)
@@ -273,10 +361,7 @@
(i32.const 4)
)
)
- (func $9 (; has Stack IR ;)
- (nop)
- )
- (func $10 (; has Stack IR ;) (result i32)
+ (func $17 (; has Stack IR ;) (result i32)
(array.get_u $bytes
(array.new_with_rtt $bytes
(i32.const -43)
@@ -289,7 +374,7 @@
(func $call-target (; has Stack IR ;) (param $0 eqref)
(nop)
)
- (func $12 (; has Stack IR ;)
+ (func $19 (; has Stack IR ;)
(drop
(ref.cast
(ref.func $call-target)
@@ -297,7 +382,7 @@
)
)
)
- (func $13 (; has Stack IR ;)
+ (func $20 (; has Stack IR ;)
(local $0 (ref null $bytes))
(local $1 (ref null $bytes))
(array.set $bytes
@@ -378,10 +463,23 @@
[fuzz-exec] calling br_on_cast
[LoggingExternalInterface logging 3]
[trap unreachable]
+[fuzz-exec] calling br_on_failed_cast-1
+[LoggingExternalInterface logging 1]
+[fuzz-exec] calling br_on_failed_cast-2
+[LoggingExternalInterface logging 1]
+[LoggingExternalInterface logging 999]
[fuzz-exec] calling cast-null-anyref-to-gc
[LoggingExternalInterface logging 0]
[fuzz-exec] calling br_on_data
[LoggingExternalInterface logging 1]
+[fuzz-exec] calling br_on_non_data-null
+[fuzz-exec] calling br_on_non_data-data
+[LoggingExternalInterface logging 1]
+[fuzz-exec] calling br_on_non_data-other
+[fuzz-exec] calling br-on_non_null
+[fuzz-exec] calling br-on_non_null-2
+[LoggingExternalInterface logging 1]
+[trap unreachable]
[fuzz-exec] calling rtt-and-cast-on-func
[LoggingExternalInterface logging 0]
[LoggingExternalInterface logging 1]
diff --git a/test/passes/Oz_fuzz-exec_all-features.wast b/test/passes/Oz_fuzz-exec_all-features.wast
index 11a9f798d..93935d5c4 100644
--- a/test/passes/Oz_fuzz-exec_all-features.wast
+++ b/test/passes/Oz_fuzz-exec_all-features.wast
@@ -180,6 +180,50 @@
)
)
)
+ (func "br_on_failed_cast-1"
+ (local $any anyref)
+ ;; create a simple $struct, store it in an anyref
+ (local.set $any
+ (struct.new_default_with_rtt $struct (rtt.canon $struct))
+ )
+ (drop
+ (block $any (result (ref null any))
+ (call $log (i32.const 1))
+ (drop
+ ;; try to cast our simple $struct to an extended, which will fail, and
+ ;; so we will branch, skipping the next logging.
+ (br_on_cast_fail $any
+ (local.get $any)
+ (rtt.canon $extendedstruct)
+ )
+ )
+ (call $log (i32.const 999)) ;; we should skip this
+ (ref.null any)
+ )
+ )
+ )
+ (func "br_on_failed_cast-2"
+ (local $any anyref)
+ ;; create an $extendedstruct, store it in an anyref
+ (local.set $any
+ (struct.new_default_with_rtt $extendedstruct (rtt.canon $extendedstruct))
+ )
+ (drop
+ (block $any (result (ref null any))
+ (call $log (i32.const 1))
+ (drop
+ ;; try to cast our simple $struct to an extended, which will succeed, and
+ ;; so we will continue to the next logging.
+ (br_on_cast_fail $any
+ (local.get $any)
+ (rtt.canon $extendedstruct)
+ )
+ )
+ (call $log (i32.const 999))
+ (ref.null any)
+ )
+ )
+ )
(func "cast-null-anyref-to-gc"
;; a null anyref is a literal which is not even of GC data, as it's not an
;; array or a struct, so our casting code should not assume it is. it is ok
@@ -208,6 +252,77 @@
)
)
)
+ (func "br_on_non_data-null"
+ (local $x anyref)
+ (drop
+ (block $any (result anyref)
+ (drop
+ (br_on_non_data $any (local.get $x))
+ )
+ ;; $x is a null, and so it is not data, and the branch will be taken, and no
+ ;; logging will occur.
+ (call $log (i32.const 1))
+ (ref.null any)
+ )
+ )
+ )
+ (func "br_on_non_data-data"
+ (local $x anyref)
+ ;; set x to valid data
+ (local.set $x
+ (struct.new_default_with_rtt $struct
+ (rtt.canon $struct)
+ )
+ )
+ (drop
+ (block $any (result anyref)
+ (drop
+ (br_on_non_data $any (local.get $x))
+ )
+ ;; $x refers to valid data, and so we will not branch, and will log.
+ (call $log (i32.const 1))
+ (ref.null any)
+ )
+ )
+ )
+ (func "br_on_non_data-other"
+ (local $x anyref)
+ ;; set x to something that is not null, but also not data
+ (local.set $x
+ (ref.func $a-void-func)
+ )
+ (drop
+ (block $any (result anyref)
+ (drop
+ (br_on_non_data $any (local.get $x))
+ )
+ ;; $x refers to a function, so we will branch, and not log
+ (call $log (i32.const 1))
+ (ref.null any)
+ )
+ )
+ )
+ (func "br-on_non_null"
+ (drop
+ (block $non-null (result (ref any))
+ (br_on_non_null $non-null (ref.func $a-void-func))
+ ;; $x refers to a function, which is not null, so we will branch, and not
+ ;; log
+ (call $log (i32.const 1))
+ (unreachable)
+ )
+ )
+ )
+ (func "br-on_non_null-2"
+ (drop
+ (block $non-null (result (ref any))
+ (br_on_non_null $non-null (ref.null any))
+ ;; $x is null, and so we will not branch, and log and then trap
+ (call $log (i32.const 1))
+ (unreachable)
+ )
+ )
+ )
(func $a-void-func
(call $log (i32.const 1337))
)
diff --git a/test/passes/remove-unused-brs_all-features.txt b/test/passes/remove-unused-brs_all-features.txt
index 6f68797cf..abfcc3e76 100644
--- a/test/passes/remove-unused-brs_all-features.txt
+++ b/test/passes/remove-unused-brs_all-features.txt
@@ -1,8 +1,8 @@
(module
(type $vector (array (mut i32)))
(type $struct (struct (field (ref null $vector))))
- (type $i32_=>_none (func (param i32)))
(type $ref|func|_=>_none (func (param (ref func))))
+ (type $i32_=>_none (func (param i32)))
(type $none_=>_ref?|$struct| (func (result (ref null $struct))))
(type $none_=>_f64 (func (result f64)))
(type $none_=>_i32 (func (result i32)))
@@ -123,6 +123,20 @@
)
)
)
+ (call $log
+ (i32.const 8)
+ )
+ (drop
+ (block $non-null (result (ref $ref|func|_=>_none))
+ (br $non-null
+ (ref.func $br_on-to-br)
+ )
+ (call $log
+ (i32.const 9)
+ )
+ (ref.func $br_on-to-br)
+ )
+ )
)
(func $br_on-to-flow
(drop
diff --git a/test/passes/remove-unused-brs_all-features.wast b/test/passes/remove-unused-brs_all-features.wast
index 4ee676589..9614f7f5c 100644
--- a/test/passes/remove-unused-brs_all-features.wast
+++ b/test/passes/remove-unused-brs_all-features.wast
@@ -107,6 +107,15 @@
(i31.new (i32.const 1337))
)
)
+ (call $log (i32.const 8))
+ (drop
+ (block $non-null (result (ref func))
+ ;; a non-null reference is not null, and the br is always taken
+ (br_on_non_null $non-null (ref.func $br_on-to-br))
+ (call $log (i32.const 9))
+ (ref.func $br_on-to-br)
+ )
+ )
)
;; a br_on of the obviously incorrect kind can just flow out the value as the