Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/ir/subtype-exprs.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,14 @@ struct SubtypingDiscoverer : public OverriddenVisitor<SubType> {
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())) {
Comment on lines +239 to +242

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my understanding, the only way that isShared doesn't match between the two arguments is if one argument is unreachable, is that right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's correct for valid IR (which all users of this utility should be assuming we have).

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);
}
Expand Down
2 changes: 2 additions & 0 deletions src/passes/Unsubtyping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ template<typename Self> struct Noter {
Self& self() { return *static_cast<Self*>(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()) {
Expand Down Expand Up @@ -701,6 +702,7 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
}

void noteNonFlowSubtype(Expression* sub, Type super) {
assert(Type::isSubType(sub->type, super));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these added? They don't look specific to this pass?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, if these make sense, why not put them in the general utility so they apply to all passes?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're added because there's no way to add an unsubtyping regression test otherwise. Unsubtyping takes the early return just below this.

Adding the assertions to the general utility makes sense, though. I'll do that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, the assertions cannot be simply moved to the shared utility because the shared utility is doing CRTP calls out to subtypes for these functions. We could have the CRTP utility call a chokepoint function that does the assertion and then calls the subtype, but that would be a much larger change and doesn't seem worth it to me.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, fair enough.

// 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
Expand Down
46 changes: 46 additions & 0 deletions test/gtest/subtype-exprs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Finder, SubtypingDiscoverer<Finder>> {
std::map<Expression*, Type> 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]));
}
34 changes: 34 additions & 0 deletions test/lit/passes/unsubtyping.wast
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
)
)
)
Loading