Skip to content

Commit 79b76ca

Browse files
authored
[NFC] Use principal types in ChildTyper (#7816)
Refactor ChildTyper to report type constraints using a single callback that takes a sequence of VarTypes from principal-type.h rather than using several callbacks for the various kinds of constraints. The previous approach using several callbacks did not scale well to the various kinds of polymorphism introduced in new Wasm proposals, such as polymorphism over sharedness in `ref.eq` or polymorphism over exactness in `ref.get_desc`. NFC despite better modeling these contraints because ChildTyper is so far only used from TypeSSA, which only uses it to look for exact types, and from IRBuilder, which only uses it to control the construction of unreachable IR. Follow-on changes will use ChildTyper in new ways that require this improved precision.
1 parent 819a39f commit 79b76ca

5 files changed

Lines changed: 95 additions & 165 deletions

File tree

src/ir/child-typer.h

Lines changed: 70 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,28 @@
1616
#ifndef wasm_ir_child_typer_h
1717
#define wasm_ir_child_typer_h
1818

19+
#include "ir/principal-type.h"
1920
#include "wasm-traversal.h"
2021
#include "wasm.h"
2122

2223
namespace wasm {
2324

24-
// CRTP visitor for determining constraints on the types of expression children.
25-
// For each child of the visited expression, calls a callback with a pointer to
26-
// the child and information on how the child is constrained. The possible
27-
// callbacks are:
25+
// CRTP visitor for determining constaints on the types of expression children.
26+
// For each child of the visited expression, calls a callback with the VarTypes
27+
// giving the constraint on the child:
2828
//
29-
// noteSubtype(Expression** childp, Type type) - The child must be a subtype
30-
// of `type`, which may be a tuple type. For children that must not produce
31-
// values, this may be `Type::none`. This accounts for most type constraints.
29+
// note(Expression** childp, SmallVector<VarType, 1> type)
3230
//
33-
// noteAnyType(Expression** childp) - The child may have any non-tuple type.
34-
// Used for the children of polymorphic instructions like `drop` and `select`.
35-
//
36-
// noteAnyReferenceType(Expression** childp) - The child may have any
37-
// reference type. Used for the children of polymorphic reference instructions
38-
// like `ref.is_null`.
39-
//
40-
// noteAnyTupleType(Expression** childp, size_t arity) - The child may have
41-
// any tuple type with the given arity. Used for the children of polymorphic
42-
// tuple instructions like `tuple.drop` and `tuple.extract`.
31+
// Multiple VarTypes are provided if and only if the child must be a tuple. In
32+
// that case, each vartype gives the constraint on a single element of the
33+
// tuple.
4334
//
4435
// Subclasses must additionally implement a callback for getting the type of a
45-
// branch target. This callback will only be used when the label type is not
36+
// branch target. This callback will only be used when a the label type is not
4637
// passed directly as an argument to the branch visitor method (see below).
4738
//
4839
// Type getLabelType(Name label)
4940
//
50-
// Children with type `unreachable` satisfy all constraints.
51-
//
5241
// Constraints are determined using information that would be present in the
5342
// binary, e.g. type annotation immediates. Many of the visitor methods take
5443
// optional additional parameter for passing this information directly, and if
@@ -70,33 +59,49 @@ namespace wasm {
7059
// information to generate constraints for all the children. Some users may wish
7160
// to ignore this situation and others may want to assert that it never occurs.
7261
template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
62+
using Constraints = SmallVector<VarType, 1>;
7363
Module& wasm;
7464
Function* func;
7565

7666
ChildTyper(Module& wasm, Function* func) : wasm(wasm), func(func) {}
7767

7868
Subtype& self() { return *static_cast<Subtype*>(this); }
7969

80-
void note(Expression** childp, Type type) {
81-
self().noteSubtype(childp, type);
70+
void note(Expression** childp, VarType type) {
71+
self().note(childp, Constraints{type});
8272
}
8373

84-
void notePointer(Expression** ptrp, Name mem) {
85-
note(ptrp, wasm.getMemory(mem)->addressType);
74+
void note(Expression** childp, Type type) {
75+
if (type.isTuple()) {
76+
Constraints tuple;
77+
for (auto t : type.getTuple()) {
78+
tuple.push_back(t);
79+
}
80+
self().note(childp, tuple);
81+
} else {
82+
note(childp, VarType{type});
83+
}
8684
}
8785

88-
void noteTableIndex(Expression** indexp, Name table) {
89-
note(indexp, wasm.getTable(table)->addressType);
86+
// Disambiguate betwween Type and VarType.
87+
void note(Expression** childp, Type::BasicType type) {
88+
note(childp, VarType{Type(type)});
9089
}
9190

92-
void noteAny(Expression** childp) { self().noteAnyType(childp); }
91+
void noteAnyTuple(Expression** childp, size_t n) {
92+
Constraints tuple;
93+
for (; n != 0; --n) {
94+
tuple.push_back(VarType{Index(n - 1)});
95+
}
96+
self().note(childp, tuple);
97+
}
9398

94-
void noteAnyReference(Expression** childp) {
95-
self().noteAnyReferenceType(childp);
99+
void notePointer(Expression** ptrp, Name mem) {
100+
note(ptrp, wasm.getMemory(mem)->addressType);
96101
}
97102

98-
void noteAnyTuple(Expression** childp, size_t arity) {
99-
self().noteAnyTupleType(childp, arity);
103+
void noteTableIndex(Expression** indexp, Name table) {
104+
note(indexp, wasm.getTable(table)->addressType);
100105
}
101106

102107
Type getLabelType(Name label) { return self().getLabelType(label); }
@@ -115,6 +120,7 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
115120
}
116121

117122
void visitIf(If* curr) {
123+
// TODO: if the condition is unreachable, use a type variable for the arms?
118124
note(&curr->condition, Type::i32);
119125
note(&curr->ifTrue, curr->type);
120126
if (curr->ifFalse) {
@@ -200,8 +206,6 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
200206

201207
void visitAtomicCmpxchg(AtomicCmpxchg* curr,
202208
std::optional<Type> type = std::nullopt) {
203-
assert(!type || *type == Type::i32 || *type == Type::i64);
204-
notePointer(&curr->ptr, curr->memory);
205209
if (!type) {
206210
if (curr->expected->type == Type::i64 ||
207211
curr->replacement->type == Type::i64) {
@@ -210,6 +214,8 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
210214
type = Type::i32;
211215
}
212216
}
217+
assert(*type == Type::i32 || *type == Type::i64);
218+
notePointer(&curr->ptr, curr->memory);
213219
note(&curr->expected, *type);
214220
note(&curr->replacement, *type);
215221
}
@@ -701,8 +707,10 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
701707
note(&curr->ifTrue, *type);
702708
note(&curr->ifFalse, *type);
703709
} else {
704-
noteAny(&curr->ifTrue);
705-
noteAny(&curr->ifFalse);
710+
// Polymorphic over types.
711+
// TODO: Model the constraint that this must be a numeric or vector type.
712+
note(&curr->ifTrue, VarType{0u});
713+
note(&curr->ifFalse, VarType{0u});
706714
}
707715
note(&curr->condition, Type::i32);
708716
}
@@ -711,11 +719,9 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
711719
if (!arity) {
712720
arity = curr->value->type.size();
713721
}
714-
if (*arity > 1) {
715-
noteAnyTuple(&curr->value, *arity);
716-
} else {
717-
noteAny(&curr->value);
718-
}
722+
assert(*arity > 0);
723+
// Might not actually be a tuple, but works either way.
724+
noteAnyTuple(&curr->value, *arity);
719725
}
720726

721727
void visitReturn(Return* curr) {
@@ -738,14 +744,19 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
738744

739745
void visitRefNull(RefNull* curr) {}
740746

741-
void visitRefIsNull(RefIsNull* curr) { noteAnyReference(&curr->value); }
747+
void visitRefIsNull(RefIsNull* curr) {
748+
// Polymorphic over heap types.
749+
note(&curr->value, VarRef{Nullable, VarHeapType{0u}});
750+
}
742751

743752
void visitRefFunc(RefFunc* curr) {}
744753

745754
void visitRefEq(RefEq* curr) {
746-
Type eqref(HeapType::eq, Nullable);
747-
note(&curr->left, eqref);
748-
note(&curr->right, eqref);
755+
// Polymorphic over sharedness.
756+
VarRef maybeShareEqref{Nullable,
757+
VarAbsHeapType{VarSharedness{0u}, HeapType::eq}};
758+
note(&curr->left, maybeShareEqref);
759+
note(&curr->right, maybeShareEqref);
749760
}
750761

751762
void visitTableGet(TableGet* curr) {
@@ -815,8 +826,8 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
815826
}
816827

817828
void visitTupleMake(TupleMake* curr) {
818-
for (auto& expr : curr->operands) {
819-
noteAny(&expr);
829+
for (Index i = 0; i < curr->operands.size(); ++i) {
830+
note(&curr->operands[i], VarType{Index(curr->operands.size() - i - 1)});
820831
}
821832
}
822833

@@ -848,10 +859,7 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
848859
ht = curr->target->type.getHeapType().getSignature();
849860
}
850861
auto params = ht->getSignature().params;
851-
assert(curr->operands.size() == params.size());
852-
for (size_t i = 0; i < params.size(); ++i) {
853-
note(&curr->operands[i], params[i]);
854-
}
862+
handleCall(curr, params);
855863
note(&curr->target, Type(*ht, Nullable));
856864
}
857865

@@ -886,7 +894,9 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
886894
}
887895
ht = curr->ref->type.getHeapType();
888896
}
889-
note(&curr->ref, Type(*ht, Nullable));
897+
// Polymorphic over exactness.
898+
assert(!ht->isBasic());
899+
note(&curr->ref, VarRef{Nullable, VarDefHeapType{VarExactness{0u}, *ht}});
890900
}
891901

892902
void visitBrOn(BrOn* curr, std::optional<Type> target = std::nullopt) {
@@ -896,7 +906,8 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
896906
// br_on(_non)_null is polymorphic over reference types and does not
897907
// take a type immediate.
898908
assert(!target);
899-
noteAnyReference(&curr->ref);
909+
// Polymorphic over heap types.
910+
note(&curr->ref, VarRef{Nullable, VarHeapType{0u}});
900911
return;
901912
case BrOnCast:
902913
case BrOnCastFail:
@@ -931,8 +942,7 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
931942
note(&curr->operands[i], fields[i].type);
932943
}
933944
}
934-
auto desc = curr->type.getHeapType().getDescriptorType();
935-
if (desc) {
945+
if (auto desc = curr->type.getHeapType().getDescriptorType()) {
936946
note(&curr->desc, Type(*desc, NonNullable, Exact));
937947
}
938948
}
@@ -992,6 +1002,7 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
9921002
assert(curr->index < fields.size());
9931003
note(&curr->ref, Type(*ht, Nullable));
9941004
auto type = fields[curr->index].type;
1005+
// TODO: (shared eq) as appropriate.
9951006
note(&curr->expected, type.isRef() ? Type(HeapType::eq, Nullable) : type);
9961007
note(&curr->replacement, type);
9971008
}
@@ -1154,17 +1165,19 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
11541165
}
11551166
ht = curr->ref->type.getHeapType();
11561167
}
1157-
auto type = ht->getArray().element.type;
1168+
Type type = ht->getArray().element.type;
11581169
note(&curr->ref, Type(*ht, Nullable));
11591170
note(&curr->index, Type::i32);
1171+
// TODO: (shared eq) as appropriate.
11601172
note(&curr->expected, type.isRef() ? Type(HeapType::eq, Nullable) : type);
11611173
note(&curr->replacement, type);
11621174
}
11631175

11641176
void visitRefAs(RefAs* curr) {
11651177
switch (curr->op) {
11661178
case RefAsNonNull:
1167-
noteAnyReference(&curr->value);
1179+
// Polymorphic over heap types.
1180+
note(&curr->value, VarRef{Nullable, VarHeapType{0u}});
11681181
return;
11691182
case AnyConvertExtern:
11701183
note(&curr->value, Type(HeapType::ext, Nullable));
@@ -1200,6 +1213,7 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
12001213
void visitStringConst(StringConst* curr) {}
12011214

12021215
void visitStringMeasure(StringMeasure* curr) {
1216+
// TODO: extern instead of string? For other ops as well.
12031217
note(&curr->ref, Type(HeapType::string, Nullable));
12041218
}
12051219

src/ir/principal-type.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,11 @@ std::optional<Signature> PrincipalType::getSignature() const {
10691069
return Signature(sigParams, sigResults);
10701070
}
10711071

1072+
bool PrincipalType::matches(Type type, VarType constraint) {
1073+
VarAssignments assignments;
1074+
return match(assignments, VarType{type}, constraint);
1075+
}
1076+
10721077
std::ostream& operator<<(std::ostream& o, const PrincipalType& type) {
10731078
print(o, type);
10741079
return o;

src/ir/principal-type.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ struct PrincipalType {
130130
// variables.
131131
std::optional<Signature> getSignature() const;
132132

133+
static bool matches(Type type, VarType constraint);
134+
133135
bool operator==(const PrincipalType& other) const {
134136
return rparams == other.rparams && results == other.results &&
135137
unreachable == other.unreachable;

src/passes/TypeSSA.cpp

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,12 @@
4848
//
4949

5050
#include "ir/child-typer.h"
51-
#include "ir/find_all.h"
5251
#include "ir/module-utils.h"
5352
#include "ir/names.h"
5453
#include "ir/possible-constant.h"
5554
#include "ir/utils.h"
5655
#include "pass.h"
5756
#include "support/hash.h"
58-
#include "wasm-builder.h"
5957
#include "wasm.h"
6058

6159
namespace wasm {
@@ -221,21 +219,19 @@ struct Analyzer
221219
: ChildTyper(*parent.getModule(), parent.getFunction()),
222220
parent(parent) {}
223221

224-
void noteSubtype(Expression**, Type type) {
225-
for (Type t : type) {
226-
if (t.isExact()) {
227-
parent.disallowedTypes.insert(t.getHeapType());
222+
void note(Expression**, Constraints type) {
223+
// Check closed type constraints for exactness. Other kinds of type
224+
// constaints do not concern us.
225+
// TODO: Handle tuples?
226+
for (auto varType : type) {
227+
if (auto* t = std::get_if<Type>(&varType)) {
228+
if (t->isExact()) {
229+
parent.disallowedTypes.insert(t->getHeapType());
230+
}
228231
}
229232
}
230233
}
231234

232-
// Other constraints do not matter to us.
233-
void noteAnyType(Expression**) {}
234-
void noteAnyReferenceType(Expression**) {}
235-
void noteAnyTupleType(Expression**, size_t) {}
236-
void noteAnyI8ArrayReferenceType(Expression**) {}
237-
void noteAnyI16ArrayReferenceType(Expression**) {}
238-
239235
Type getLabelType(Name label) { WASM_UNREACHABLE("unexpected branch"); }
240236

241237
// We don't mind if we cannot compute a constraint due to unreachability.

0 commit comments

Comments
 (0)