diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index a9168061476..60e4628a7c1 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -235,11 +235,14 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitRefEq(RefEq* curr) { // Match the shareability of the current content (if it exists). // TODO: This could also allow both sides to flip. - HeapType eq = HeapType::eq; - if (curr->type != Type::unreachable) { - eq = eq.getBasic(curr->left->type.getHeapType().getShared()); + auto share = Unshared; + if ((curr->left->type.isRef() && + curr->left->type.getHeapType().isShared()) || + (curr->right->type.isRef() && + curr->right->type.getHeapType().isShared())) { + share = Shared; } - auto type = Type(eq, Nullable); + auto type = Type(HeapTypes::eq.getBasic(share), Nullable); self()->noteNonFlowSubtype(curr->left, type); self()->noteNonFlowSubtype(curr->right, type); } diff --git a/src/passes/Unsubtyping.cpp b/src/passes/Unsubtyping.cpp index c4bd9885e48..ea481bcd6c1 100644 --- a/src/passes/Unsubtyping.cpp +++ b/src/passes/Unsubtyping.cpp @@ -456,6 +456,7 @@ template struct Noter { Self& self() { return *static_cast(this); } void noteSubtype(HeapType sub, HeapType super) { + assert(HeapType::isSubType(sub, super)); // Bottom types are uninteresting, but other basic heap types can be // interesting because of their interactions with casts. if (sub == super || sub.isBottom()) { @@ -701,6 +702,7 @@ struct Unsubtyping : Pass, Noter { } void noteNonFlowSubtype(Expression* sub, Type super) { + assert(Type::isSubType(sub->type, super)); // This expression's type must be a subtype of |super|, but the value // does not flow anywhere - this is a static constraint. As the value // does not flow, it cannot reach anywhere else, which means we need diff --git a/test/gtest/subtype-exprs.cpp b/test/gtest/subtype-exprs.cpp index 0f2db3a4942..e3e87ec2d6a 100644 --- a/test/gtest/subtype-exprs.cpp +++ b/test/gtest/subtype-exprs.cpp @@ -77,3 +77,49 @@ TEST(SubtypeExprsTest, BrIf) { ASSERT_EQ(finder.seen.size(), 1); EXPECT_EQ(finder.seen.count(block), 1); } + +// A RefEq with an unreachable operand and a shared reference operand must still +// constrain its operands to shared eqref rather than unshared eqref. +TEST(SubtypeExprsTest, RefEqSharedUnreachable) { + Module wasm; + Builder builder(wasm); + Type sharedEqref = Type(HeapTypes::eq.getBasic(Shared), Nullable); + auto* sharedNull = builder.makeRefNull(HeapTypes::none.getBasic(Shared)); + auto* unreachable = builder.makeUnreachable(); + auto* refEq = builder.makeRefEq(unreachable, sharedNull); + ASSERT_EQ(refEq->type, Type::unreachable); + auto* drop = builder.makeDrop(refEq); + auto func = builder.makeFunction( + "func", {}, Signature(Type::none, Type::none), {}, drop); + + struct Finder + : public ControlFlowWalker> { + std::map noted; + + void noteSubtype(Type sub, Type super) {} + void noteSubtype(HeapType sub, HeapType super) {} + void noteSubtype(Type sub, Expression* super) {} + void noteSubtype(Expression* sub, Type super) {} + void noteSubtype(Expression* sub, Expression* super) {} + void noteNonFlowSubtype(Expression* sub, Type super) { noted[sub] = super; } + void noteCast(HeapType src, Type dst) {} + void noteCast(Expression* src, Type dst) {} + void noteCast(Expression* src, Expression* dst) {} + } finder; + + finder.walkFunctionInModule(func.get(), &wasm); + EXPECT_EQ(finder.noted[sharedNull], sharedEqref); + EXPECT_TRUE(Type::isSubType(sharedNull->type, finder.noted[sharedNull])); + + // Swap the operands so the shared reference is on the left and unreachable is + // on the right. + refEq->left = sharedNull; + refEq->right = unreachable; + refEq->finalize(); + ASSERT_EQ(refEq->type, Type::unreachable); + + finder.noted.clear(); + finder.walkFunctionInModule(func.get(), &wasm); + EXPECT_EQ(finder.noted[sharedNull], sharedEqref); + EXPECT_TRUE(Type::isSubType(sharedNull->type, finder.noted[sharedNull])); +} diff --git a/test/lit/passes/unsubtyping.wast b/test/lit/passes/unsubtyping.wast index bbbeb8e7252..311e0791c9b 100644 --- a/test/lit/passes/unsubtyping.wast +++ b/test/lit/passes/unsubtyping.wast @@ -2059,3 +2059,37 @@ ) ) ) + +;; Regression test for unreachable ref.eq with shared reference operands. +(module + ;; CHECK: (type $0 (func (param (ref null (shared eq))))) + + ;; CHECK: (func $ref-eq-shared-unreachable (type $0) (param $eq (ref null (shared eq))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (local.get $eq) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (local.get $eq) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-eq-shared-unreachable (param $eq (ref null (shared eq))) + (drop + (ref.eq + (unreachable) + (local.get $eq) + ) + ) + (drop + (ref.eq + (local.get $eq) + (unreachable) + ) + ) + ) +)