Skip to content

Commit f321a13

Browse files
authored
[PrincipalType] Finish and fix join and compose edge cases (#9301)
Default-initialize PrincipalType::unreachable so the Signature constructor does not leave it uninitialized, and grow AssignmentClasses sets up to max(i, *other) before looking up classes.getRoot(*other). Implement the remaining join cases for VarNullability, VarExactness, VarSharedness, VarHeapType (including mixed abstract/defined heap types and defined heap types with basic LUBs), and VarRef/Type, as well as erasing leading unreachable elements at the bottom of results in PrincipalType::compose.
1 parent 7d27113 commit f321a13

3 files changed

Lines changed: 164 additions & 55 deletions

File tree

‎src/ir/principal-type.cpp‎

Lines changed: 100 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -103,35 +103,47 @@ VarRef asVarRef(Type type) {
103103
// `true` iff the least upper bound exists.
104104

105105
bool join(VarNullability& value, const VarNullability& other) {
106-
assert(!std::get_if<Index>(&value) && !std::get_if<Index>(&other) &&
107-
"joining variables not yet supported");
108-
if (value == other) {
106+
if (value == other || other == VarNullability{NonNullable}) {
109107
return true;
110108
}
111-
value = Nullable;
112-
return true;
109+
if (value == VarNullability{NonNullable}) {
110+
value = other;
111+
return true;
112+
}
113+
if (value == VarNullability{Nullable} || other == VarNullability{Nullable}) {
114+
value = Nullable;
115+
return true;
116+
}
117+
WASM_UNREACHABLE("joining distinct nullability variables not supported");
113118
}
114119

115120
bool join(VarExactness& value, const VarExactness& other) {
116-
assert(!std::get_if<Index>(&value) && !std::get_if<Index>(&other) &&
117-
"joining variables not yet supported");
118-
if (value == other) {
121+
if (value == other || other == VarExactness{Exact}) {
119122
return true;
120123
}
121-
value = Inexact;
122-
return true;
124+
if (value == VarExactness{Exact}) {
125+
value = other;
126+
return true;
127+
}
128+
if (value == VarExactness{Inexact} || other == VarExactness{Inexact}) {
129+
value = Inexact;
130+
return true;
131+
}
132+
WASM_UNREACHABLE("joining distinct exactness variables not supported");
123133
}
124134

125135
bool join(VarSharedness& value, const VarSharedness& other) {
126-
assert(!std::get_if<Index>(&value) && !std::get_if<Index>(&other) &&
127-
"joining variables not yet supported");
128136
if (value == other || std::get_if<BottomShare>(&other)) {
129137
return true;
130138
}
131139
if (std::get_if<BottomShare>(&value)) {
132140
value = other;
133141
return true;
134142
}
143+
if (std::get_if<Index>(&value) || std::get_if<Index>(&other)) {
144+
WASM_UNREACHABLE(
145+
"joining non-bottom sharedness with variable not supported");
146+
}
135147
return false;
136148
}
137149

@@ -147,32 +159,60 @@ bool join(VarAbsHeapType& value, const VarAbsHeapType& other) {
147159
return join(value.share, other.share) && join(value.ht, other.ht);
148160
}
149161

150-
bool join(VarDefHeapType& value, const VarDefHeapType& other) {
151-
if (value.ht != other.ht) {
152-
VarExactness inexact = Inexact;
153-
return join(value.ht, other.ht) && join(value.exact, inexact) &&
154-
join(inexact, other.exact);
162+
bool joinAbsAndDef(VarHeapType& value,
163+
const VarAbsHeapType& abs,
164+
const VarDefHeapType& def) {
165+
VarSharedness share = abs.share;
166+
if (!join(share, VarSharedness{def.ht.getShared()})) {
167+
return false;
155168
}
156-
return join(value.exact, other.exact);
157-
}
158-
159-
bool join(VarAbsHeapType& value, const VarDefHeapType& other) {
160-
WASM_UNREACHABLE("TODO");
169+
// Since `def.ht.getShared()` is a concrete Shareability, the joined `share`
170+
// is guaranteed to equal `def.ht.getShared()`.
171+
auto lub =
172+
HeapType::getLeastUpperBound(abs.ht.getBasic(def.ht.getShared()), def.ht);
173+
if (!lub) {
174+
return false;
175+
}
176+
if (lub->isBasic()) {
177+
value = VarAbsHeapType{share, lub->getBasic(Unshared)};
178+
} else {
179+
assert(*lub == def.ht);
180+
value = def;
181+
}
182+
return true;
161183
}
162184

163-
bool join(VarDefHeapType& value, const VarAbsHeapType& other) {
164-
WASM_UNREACHABLE("TODO");
185+
bool joinDefAndDef(VarHeapType& value,
186+
const VarDefHeapType& a,
187+
const VarDefHeapType& b) {
188+
auto lub = HeapType::getLeastUpperBound(a.ht, b.ht);
189+
if (!lub) {
190+
return false;
191+
}
192+
if (lub->isBasic()) {
193+
value = VarAbsHeapType{lub->getShared(), lub->getBasic(Unshared)};
194+
return true;
195+
}
196+
VarExactness exact = a.ht == b.ht ? a.exact : VarExactness{Inexact};
197+
if (!join(exact, b.exact)) {
198+
return false;
199+
}
200+
value = VarDefHeapType{exact, *lub};
201+
return true;
165202
}
166203

167204
bool join(VarHeapType& value, const VarHeapType& other) {
168-
assert(!std::get_if<Index>(&value) && !std::get_if<Index>(&other));
169205
if (value == other || std::get_if<BottomHeapType>(&other)) {
170206
return true;
171207
}
172208
if (std::get_if<BottomHeapType>(&value)) {
173209
value = other;
174210
return true;
175211
}
212+
if (std::get_if<Index>(&value) || std::get_if<Index>(&other)) {
213+
WASM_UNREACHABLE(
214+
"joining non-bottom heap type with variable not supported");
215+
}
176216
auto* abs = std::get_if<VarAbsHeapType>(&value);
177217
auto* otherAbs = std::get_if<VarAbsHeapType>(&other);
178218
auto* def = std::get_if<VarDefHeapType>(&value);
@@ -181,13 +221,13 @@ bool join(VarHeapType& value, const VarHeapType& other) {
181221
return join(*abs, *otherAbs);
182222
}
183223
if (abs && otherDef) {
184-
return join(*abs, *otherDef);
224+
return joinAbsAndDef(value, *abs, *otherDef);
185225
}
186226
if (def && otherAbs) {
187-
return join(*def, *otherAbs);
227+
return joinAbsAndDef(value, *otherAbs, *def);
188228
}
189229
if (def && otherDef) {
190-
return join(*def, *otherDef);
230+
return joinDefAndDef(value, *def, *otherDef);
191231
}
192232
WASM_UNREACHABLE("unexpected variant");
193233
}
@@ -201,11 +241,20 @@ bool join(Type& value, const Type& other) {
201241
return value != Type::none;
202242
}
203243

204-
bool join(VarRef& value, const Type& other) { WASM_UNREACHABLE("TODO"); }
244+
bool join(VarRef& value, const Type& other) {
245+
if (!other.isRef()) {
246+
return false;
247+
}
248+
return join(value, asVarRef(other));
249+
}
205250

206251
bool join(VarType& value, const VarType& other) {
207252
assert(!std::get_if<Index>(&value) && !std::get_if<Index>(&other));
208-
if (value == other) {
253+
if (value == other || other == VarType{Type(Type::unreachable)}) {
254+
return true;
255+
}
256+
if (value == VarType{Type(Type::unreachable)}) {
257+
value = other;
209258
return true;
210259
}
211260
auto* ref = std::get_if<VarRef>(&value);
@@ -227,11 +276,8 @@ bool join(VarType& value, const VarType& other) {
227276
return true;
228277
}
229278
if (type && otherRef) {
230-
if (!type->isRef()) {
231-
return false;
232-
}
233-
auto r = asVarRef(*type);
234-
if (!join(r, *otherRef)) {
279+
VarRef r = *otherRef;
280+
if (!join(r, *type)) {
235281
return false;
236282
}
237283
value = canonicalizeRef(r);
@@ -449,10 +495,11 @@ template<typename T> struct AssignmentClasses {
449495
// Setting a variable equal to itself is a no-op.
450496
return true;
451497
}
452-
// Make sure we have a class for i.
453-
classes.reserve(i + 1);
454-
values.reserve(i + 1);
455-
while (values.size() <= i) {
498+
// Make sure we have classes for i and *other.
499+
size_t maxIndex = other ? std::max(i, *other) : i;
500+
classes.reserve(maxIndex + 1);
501+
values.reserve(maxIndex + 1);
502+
while (values.size() <= maxIndex) {
456503
[[maybe_unused]] size_t set = classes.addSet();
457504
assert(set == values.size());
458505
values.push_back(std::nullopt);
@@ -1031,19 +1078,20 @@ bool PrincipalType::compose(const PrincipalType& next) {
10311078

10321079
apply(assignments, *this);
10331080
// If a type variable was instantiated with bottom type (i.e. unreachable) due
1034-
// to popping from an unreachable stack, the result type may end with some
1035-
// number of unreachables. This is nonsensical, since `unreachable` is not a
1036-
// concrete type. We could have alternatively left the variables
1037-
// uninstantiated, but it would have no corresponding introduction on the left
1038-
// hand side of the type. Even better is to canonicalize to empty results.
1039-
// Since the new principal type is unreachable, this has the same semantics as
1040-
// having an unconstrained result variable.
1041-
if (!results.empty()) {
1042-
auto& last = results[results.size() - 1];
1043-
if (auto* t = std::get_if<Type>(&last); t && *t == Type::unreachable) {
1044-
results.clear();
1045-
}
1046-
}
1081+
// to popping from an unreachable stack, `results` may start with some number
1082+
// of `unreachable`s at the bottom of the stack. This is nonsensical, since
1083+
// `unreachable` is not a concrete type. We could have alternatively left the
1084+
// variables uninstantiated, but they would have no corresponding introduction
1085+
// on the left hand side of the type. Even better is to remove the leading
1086+
// `unreachable`s at the bottom of `results`. Since the new principal type is
1087+
// unreachable (`*`), unconstrained elements at the bottom of the stack are
1088+
// absorbed into `*`, while any concrete types above them remain at the top of
1089+
// the stack.
1090+
auto firstNonBottom =
1091+
std::find_if(results.begin(), results.end(), [](const VarType& t) {
1092+
return t != VarType{Type(Type::unreachable)};
1093+
});
1094+
results.erase(results.begin(), firstNonBottom);
10471095

10481096
canonicalizeVars(*this);
10491097
return true;

‎src/ir/principal-type.h‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ using VarType = std::variant<Type, VarRef, Index>;
104104
struct PrincipalType {
105105
std::vector<VarType> rparams;
106106
std::vector<VarType> results;
107-
bool unreachable;
107+
bool unreachable = false;
108+
109+
PrincipalType() = default;
108110

109111
PrincipalType(std::initializer_list<VarType> params,
110112
std::initializer_list<VarType> results,

‎test/gtest/principal-type.cpp‎

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ TEST(PrincipalTypeTest, MatchTypeVariables) {
183183
// Match bottom.
184184
// []*->[] + [t0]->[t0] = []*->[]
185185
COMPOSE(({}, {}, true), ({t0}, {t0}), ({}, {}, true));
186+
// []*->[i32] + [t1 t0]->[t1 t0] = []*->[i32]
187+
COMPOSE(({}, {i32}, true), ({t1, t0}, {t1, t0}), ({}, {i32}, true));
186188
// [t0]->[t0] + []*->[] = [t0]*->[]
187189
COMPOSE(({t0}, {t0}), ({}, {}, true), ({t0}, {}, true));
188190

@@ -469,11 +471,12 @@ TEST(PrincipalTypeTest, UnifyVariables) {
469471
VarType t2 = {2u};
470472
VarType refN0T0 = VarRef{{0u}, {0u}};
471473

472-
TypeBuilder builder(2);
474+
TypeBuilder builder(3);
473475
builder[0] = Struct{};
474476
builder[0].setOpen();
475477
builder[1] = Struct();
476478
builder[1].subTypeOf(builder[0]);
479+
builder[2] = Struct{};
477480
auto built = builder.build();
478481
HeapType foo = (*built)[0];
479482
VarType refFoo = Type(foo, NonNullable);
@@ -482,6 +485,13 @@ TEST(PrincipalTypeTest, UnifyVariables) {
482485
HeapType bar = (*built)[1];
483486
VarType refBar = Type(bar, NonNullable);
484487
VarType refExactBar = Type(bar, NonNullable, Exact);
488+
HeapType baz = (*built)[2];
489+
VarType refBaz = Type(baz, NonNullable);
490+
Type refStruct = Type(HeapType::struct_, NonNullable);
491+
Type refNone = Type(HeapType::none, NonNullable);
492+
VarType refBot = VarRef{NonNullable, BottomHeapType{}};
493+
VarType refBotNone =
494+
VarRef{NonNullable, VarAbsHeapType{BottomShare{}, HeapType::none}};
485495

486496
// Unify multiple variables.
487497
// []->[t2 t2 t1 t1 t0] + [t2' t1' t1' t0' t0']->[t0' t1' t2']
@@ -502,7 +512,55 @@ TEST(PrincipalTypeTest, UnifyVariables) {
502512
// = []->[anyref]
503513
COMPOSE(
504514
({}, {eqref, refAny}), ({refN0T0, refN0T0}, {refN0T0}), ({}, {anyref}));
505-
// TODO: Join abstract and defined heap types.
515+
// []->[(ref (bot-share eq)) (ref any)] + [t0 t0]->[t0] = []->[(ref any)]
516+
COMPOSE(({}, {refBotEq, refAny}), ({t0, t0}, {t0}), ({}, {refAny}));
517+
// []->[(ref any) (ref (bot-share eq))] + [t0 t0]->[t0] = []->[(ref any)]
518+
COMPOSE(({}, {refAny, refBotEq}), ({t0, t0}, {t0}), ({}, {refAny}));
519+
// []*->[(ref (bot-share eq))] + [t0 t0]->[t0] = []*->[(ref (bot-share eq))]
520+
COMPOSE(({}, {refBotEq}, true), ({t0, t0}, {t0}), ({}, {refBotEq}, true));
521+
// [(ref n0 t0)]->[(ref bot) (ref n0 t0)] + [t0 t0]->[t0]
522+
// = [(ref n0 t0)]->[(ref n0 t0)]
523+
COMPOSE(
524+
({refN0T0}, {refBot, refN0T0}), ({t0, t0}, {t0}), ({refN0T0}, {refN0T0}));
525+
526+
// Join abstract and defined heap types.
527+
// []->[eqref (ref foo)] + [t0 t0]->[t0] = []->[eqref]
528+
COMPOSE(({}, {eqref, refFoo}), ({t0, t0}, {t0}), ({}, {eqref}));
529+
// []->[(ref foo) eqref] + [t0 t0]->[t0] = []->[eqref]
530+
COMPOSE(({}, {refFoo, eqref}), ({t0, t0}, {t0}), ({}, {eqref}));
531+
// []->[eqref (ref foo)] + [(ref n0 t0) (ref n0 t0)]->[(ref n0 t0)]
532+
// = []->[eqref]
533+
COMPOSE(
534+
({}, {eqref, refFoo}), ({refN0T0, refN0T0}, {refN0T0}), ({}, {eqref}));
535+
// []->[(ref foo) eqref] + [(ref n0 t0) (ref n0 t0)]->[(ref n0 t0)]
536+
// = []->[eqref]
537+
COMPOSE(
538+
({}, {refFoo, eqref}), ({refN0T0, refN0T0}, {refN0T0}), ({}, {eqref}));
539+
// []->[(ref (bot-share eq)) (ref foo)]
540+
// + [(ref n0 t0) (ref n0 t0)]->[(ref n0 t0)] = []->[(ref eq)]
541+
COMPOSE(
542+
({}, {refBotEq, refFoo}), ({refN0T0, refN0T0}, {refN0T0}), ({}, {refEq}));
543+
// []->[(ref foo) (ref (bot-share eq))]
544+
// + [(ref n0 t0) (ref n0 t0)]->[(ref n0 t0)] = []->[(ref eq)]
545+
COMPOSE(
546+
({}, {refFoo, refBotEq}), ({refN0T0, refN0T0}, {refN0T0}), ({}, {refEq}));
547+
// []->[(ref none) (ref (exact foo))]
548+
// + [(ref n0 t0) (ref n0 t0)]->[(ref n0 t0)] = []->[(ref (exact foo))]
549+
COMPOSE(({}, {refNone, refExactFoo}),
550+
({refN0T0, refN0T0}, {refN0T0}),
551+
({}, {refExactFoo}));
552+
// []->[(ref (bot-share none)) (ref (exact foo))]
553+
// + [(ref n0 t0) (ref n0 t0)]->[(ref n0 t0)] = []->[(ref (exact foo))]
554+
COMPOSE(({}, {refBotNone, refExactFoo}),
555+
({refN0T0, refN0T0}, {refN0T0}),
556+
({}, {refExactFoo}));
557+
// []->[(ref (shared any)) (ref foo)]
558+
// + [(ref n0 t0) (ref n0 t0)]->[(ref n0 t0)] = X
559+
NO_COMPOSE(({}, {refSharedAny, refFoo}), ({refN0T0, refN0T0}, {refN0T0}));
560+
// []->[(ref foo) (ref baz)] + [(ref n0 t0) (ref n0 t0)]->[(ref n0 t0)]
561+
// = []->[(ref struct)]
562+
COMPOSE(
563+
({}, {refFoo, refBaz}), ({refN0T0, refN0T0}, {refN0T0}), ({}, {refStruct}));
506564

507565
// Join sharedness assignments.
508566
// []->[(ref any) (ref any)]
@@ -618,6 +676,7 @@ static void testSignatureRoundTrip(const Signature& sig) {
618676
SCOPED_TRACE("Testing signature: " + sig.toString());
619677

620678
PrincipalType pt(sig);
679+
EXPECT_FALSE(pt.unreachable);
621680
std::optional<Signature> maybeSig = pt.getSignature();
622681
ASSERT_TRUE(maybeSig.has_value());
623682
EXPECT_EQ(*maybeSig, sig);

0 commit comments

Comments
 (0)