summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/wasm/wasm-type.cpp1055
-rw-r--r--test/example/type-builder.cpp26
-rw-r--r--test/example/type-builder.txt37
-rw-r--r--test/lit/recursive-types.wast25
4 files changed, 768 insertions, 375 deletions
diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp
index 832ee6367..ae38e7f5c 100644
--- a/src/wasm/wasm-type.cpp
+++ b/src/wasm/wasm-type.cpp
@@ -33,6 +33,8 @@ namespace {
struct TypeInfo {
using type_t = Type;
+ // Used in assertions to ensure that temporary types don't leak into the
+ // global store.
bool isTemp = false;
enum Kind {
TupleKind,
@@ -69,8 +71,14 @@ struct TypeInfo {
struct HeapTypeInfo {
using type_t = HeapType;
+ // Used in assertions to ensure that temporary types don't leak into the
+ // global store.
bool isTemp = false;
- bool isSelfReferential = false;
+ // If `isFinalized`, then hashing and equality are performed on the finite
+ // shape of the type definition tree rooted at the HeapTypeInfo.
+ // Otherwise, the type definition tree is still being constructed via the
+ // TypeBuilder interface, so hashing and equality use pointer identity.
+ bool isFinalized = true;
enum Kind {
SignatureKind,
StructKind,
@@ -155,18 +163,57 @@ struct TypePrinter {
std::ostream& print(const Rtt& rtt);
private:
- template<typename T, typename F> std::ostream& printChild(T curr, F printer) {
- auto it = depths.find(curr.getID());
- if (it != depths.end()) {
- assert(it->second <= currDepth);
- size_t relativeDepth = currDepth - it->second;
- return os << "..." << relativeDepth;
- }
- depths[curr.getID()] = ++currDepth;
- printer();
- depths.erase(curr.getID());
- return os;
- }
+ template<typename T, typename F> std::ostream& printChild(T curr, F printer);
+};
+
+// Helper for hashing the shapes of TypeInfos and HeapTypeInfos. Keeps track of
+// previously seen HeapTypes to avoid traversing them more than once. Infos
+// referring to different type IDs but sharing a finite shape will compare and
+// hash the same.
+struct FiniteShapeHasher {
+ bool topLevelOnly;
+ size_t currDepth = 0;
+ size_t currStep = 0;
+ std::unordered_map<HeapType, size_t> seen;
+
+ FiniteShapeHasher(bool topLevelOnly = false) : topLevelOnly(topLevelOnly) {}
+
+ size_t hash(Type type);
+ size_t hash(HeapType heapType);
+ size_t hash(const TypeInfo& info);
+ size_t hash(const HeapTypeInfo& info);
+ size_t hash(const Tuple& tuple);
+ size_t hash(const Field& field);
+ size_t hash(const Signature& sig);
+ size_t hash(const Struct& struct_);
+ size_t hash(const Array& array);
+ size_t hash(const Rtt& rtt);
+};
+
+// Helper for comparing the shapes of TypeInfos and HeapTypeInfos for equality.
+// Like FiniteShapeHasher, keeps track of previously seen HeapTypes. Note that
+// this does not test for coinductive equality of the infinite expansion of the
+// type tree, but rather tests for equality of the finite shape of the graph. If
+// FiniteShapeEquator reports that two type shapes are equal, FiniteShapeHasher
+// should produce the same hash for them.
+struct FiniteShapeEquator {
+ bool topLevelOnly;
+ size_t currDepth = 0;
+ size_t currStep = 0;
+ std::unordered_map<HeapType, size_t> seenA, seenB;
+
+ FiniteShapeEquator(bool topLevelOnly = false) : topLevelOnly(topLevelOnly) {}
+
+ bool eq(Type a, Type b);
+ bool eq(HeapType a, HeapType b);
+ bool eq(const TypeInfo& a, const TypeInfo& b);
+ bool eq(const HeapTypeInfo& a, const HeapTypeInfo& b);
+ bool eq(const Tuple& a, const Tuple& b);
+ bool eq(const Field& a, const Field& b);
+ bool eq(const Signature& a, const Signature& b);
+ bool eq(const Struct& a, const Struct& b);
+ bool eq(const Array& a, const Array& b);
+ bool eq(const Rtt& a, const Rtt& b);
};
} // anonymous namespace
@@ -176,12 +223,31 @@ namespace std {
template<> class hash<wasm::TypeInfo> {
public:
- size_t operator()(const wasm::TypeInfo&) const;
+ size_t operator()(const wasm::TypeInfo& info) const {
+ return wasm::FiniteShapeHasher().hash(info);
+ }
};
template<> class hash<wasm::HeapTypeInfo> {
public:
- size_t operator()(const wasm::HeapTypeInfo&) const;
+ size_t operator()(const wasm::HeapTypeInfo& info) const {
+ return wasm::FiniteShapeHasher().hash(info);
+ }
+};
+
+template<typename T> class hash<reference_wrapper<const T>> {
+public:
+ size_t operator()(const reference_wrapper<const T>& ref) const {
+ return hash<T>{}(ref.get());
+ }
+};
+
+template<typename T> class equal_to<reference_wrapper<const T>> {
+public:
+ bool operator()(const reference_wrapper<const T>& a,
+ const reference_wrapper<const T>& b) const {
+ return equal_to<T>{}(a.get(), b.get());
+ }
};
} // namespace std
@@ -199,6 +265,10 @@ HeapTypeInfo* getHeapTypeInfo(HeapType ht) {
return (HeapTypeInfo*)ht.getID();
}
+HeapType asHeapType(std::unique_ptr<HeapTypeInfo>& info) {
+ return HeapType(uintptr_t(info.get()));
+}
+
Type markTemp(Type type) {
if (!type.isBasic()) {
getTypeInfo(type)->isTemp = true;
@@ -207,27 +277,13 @@ Type markTemp(Type type) {
}
bool isTemp(Type type) {
- // Avoid compiler warnings on this function not being used, as it is only used
- // in assertions, which might be off.
- bool (*func)(Type) = isTemp;
- WASM_UNUSED(func);
-
return !type.isBasic() && getTypeInfo(type)->isTemp;
}
bool isTemp(HeapType type) {
- // Avoid compiler warnings on this function not being used, as it is only used
- // in assertions, which might be off.
- bool (*func)(HeapType) = isTemp;
- WASM_UNUSED(func);
-
return !type.isBasic() && getHeapTypeInfo(type)->isTemp;
}
-bool isSelfReferential(HeapType type) {
- return !type.isBasic() && getHeapTypeInfo(type)->isSelfReferential;
-}
-
TypeInfo::TypeInfo(const TypeInfo& other) {
kind = other.kind;
switch (kind) {
@@ -260,24 +316,14 @@ TypeInfo::~TypeInfo() {
}
bool TypeInfo::operator==(const TypeInfo& other) const {
- if (kind != other.kind) {
- return false;
- }
- switch (kind) {
- case TupleKind:
- return tuple == other.tuple;
- case RefKind:
- return ref.heapType == other.ref.heapType &&
- ref.nullable == other.ref.nullable;
- case RttKind:
- return rtt == other.rtt;
- }
- WASM_UNREACHABLE("unexpected kind");
+ // TypeInfos with the same shape are considered equivalent. This is important
+ // during global canonicalization, when newly created canonically-shaped
+ // graphs are checked against the existing globally canonical graphs.
+ return FiniteShapeEquator().eq(*this, other);
}
HeapTypeInfo::HeapTypeInfo(const HeapTypeInfo& other) {
kind = other.kind;
- isSelfReferential = other.isSelfReferential;
switch (kind) {
case SignatureKind:
new (&signature) auto(other.signature);
@@ -316,18 +362,7 @@ HeapTypeInfo& HeapTypeInfo::operator=(const HeapTypeInfo& other) {
}
bool HeapTypeInfo::operator==(const HeapTypeInfo& other) const {
- if (kind != other.kind) {
- return false;
- }
- switch (kind) {
- case SignatureKind:
- return signature == other.signature;
- case StructKind:
- return struct_ == other.struct_;
- case ArrayKind:
- return array == other.array;
- }
- WASM_UNREACHABLE("unexpected kind");
+ return FiniteShapeEquator().eq(*this, other);
}
template<typename Info> struct Store {
@@ -337,7 +372,7 @@ template<typename Info> struct Store {
std::vector<std::unique_ptr<Info>> constructedTypes;
// Maps from constructed types to their canonical Type IDs.
- std::unordered_map<Info, uintptr_t> typeIDs;
+ std::unordered_map<std::reference_wrapper<const Info>, uintptr_t> typeIDs;
bool isGlobalStore();
@@ -349,7 +384,12 @@ struct TypeStore : Store<TypeInfo> {
Type canonicalize(TypeInfo info);
};
-using HeapTypeStore = Store<HeapTypeInfo>;
+struct HeapTypeStore : Store<HeapTypeInfo> {
+ HeapType canonicalize(const HeapTypeInfo& other) {
+ return Store<HeapTypeInfo>::canonicalize(other);
+ }
+ HeapType canonicalize(std::unique_ptr<HeapTypeInfo>&& info);
+};
TypeStore globalTypeStore;
HeapTypeStore globalHeapTypeStore;
@@ -384,13 +424,23 @@ TypeID Store<Info>::recordCanonical(std::unique_ptr<Info>&& info) {
template<typename Info>
typename Info::type_t Store<Info>::canonicalize(const Info& info) {
std::lock_guard<std::mutex> lock(mutex);
- auto indexIt = typeIDs.find(info);
+ auto indexIt = typeIDs.find(std::cref(info));
if (indexIt != typeIDs.end()) {
return typename Info::type_t(indexIt->second);
}
return typename Info::type_t(recordCanonical(std::make_unique<Info>(info)));
}
+HeapType HeapTypeStore::canonicalize(std::unique_ptr<HeapTypeInfo>&& info) {
+ std::lock_guard<std::mutex> lock(mutex);
+ auto indexIt = typeIDs.find(std::cref(*info));
+ if (indexIt != typeIDs.end()) {
+ return HeapType(indexIt->second);
+ }
+ info->isTemp = false;
+ return HeapType(recordCanonical(std::move(info)));
+}
+
Type TypeStore::canonicalize(TypeInfo info) {
if (info.isTuple()) {
if (info.tuple.types.size() == 0) {
@@ -1108,6 +1158,21 @@ bool SubTyper::isSubType(const Rtt& a, const Rtt& b) {
return a.heapType == b.heapType && a.hasDepth() && !b.hasDepth();
}
+template<typename T, typename F>
+std::ostream& TypePrinter::printChild(T curr, F printer) {
+ auto it = depths.find(curr.getID());
+ if (it != depths.end()) {
+ assert(it->second <= currDepth);
+ size_t relativeDepth = currDepth - it->second;
+ return os << "..." << relativeDepth;
+ }
+ depths[curr.getID()] = ++currDepth;
+ printer();
+ depths.erase(curr.getID());
+ --currDepth;
+ return os;
+}
+
std::ostream& TypePrinter::print(Type type) {
if (type.isBasic()) {
switch (type.getBasic()) {
@@ -1141,6 +1206,9 @@ std::ostream& TypePrinter::print(Type type) {
}
return printChild(type, [&]() {
+ if (isTemp(type)) {
+ os << "[T]";
+ }
if (type.isTuple()) {
print(type.getTuple());
} else if (type.isRef()) {
@@ -1177,6 +1245,9 @@ std::ostream& TypePrinter::print(HeapType heapType) {
}
return printChild(heapType, [&]() {
+ if (isTemp(heapType)) {
+ os << "[T]";
+ }
if (heapType.isSignature()) {
print(heapType.getSignature());
} else if (heapType.isStruct()) {
@@ -1274,6 +1345,221 @@ std::ostream& TypePrinter::print(const Rtt& rtt) {
return os << ')';
}
+size_t FiniteShapeHasher::hash(Type type) {
+ size_t digest = wasm::hash(type.isBasic());
+ if (type.isBasic()) {
+ rehash(digest, type.getID());
+ } else {
+ hash_combine(digest, hash(*getTypeInfo(type)));
+ }
+ return digest;
+}
+
+size_t FiniteShapeHasher::hash(HeapType heapType) {
+ size_t digest = wasm::hash(heapType.isBasic());
+ if (heapType.isBasic()) {
+ rehash(digest, heapType.getID());
+ return digest;
+ }
+ if (topLevelOnly && currDepth > 0) {
+ return digest;
+ }
+ auto it = seen.find(heapType);
+ rehash(digest, it != seen.end());
+ if (it != seen.end()) {
+ rehash(digest, it->second);
+ return digest;
+ }
+ seen[heapType] = ++currStep;
+ ++currDepth;
+ hash_combine(digest, hash(*getHeapTypeInfo(heapType)));
+ --currDepth;
+ return digest;
+}
+
+size_t FiniteShapeHasher::hash(const TypeInfo& info) {
+ size_t digest = wasm::hash(info.kind);
+ switch (info.kind) {
+ case TypeInfo::TupleKind:
+ hash_combine(digest, hash(info.tuple));
+ return digest;
+ case TypeInfo::RefKind:
+ rehash(digest, info.ref.nullable);
+ hash_combine(digest, hash(info.ref.heapType));
+ return digest;
+ case TypeInfo::RttKind:
+ hash_combine(digest, hash(info.rtt));
+ return digest;
+ }
+ WASM_UNREACHABLE("unexpected kind");
+}
+
+size_t FiniteShapeHasher::hash(const HeapTypeInfo& info) {
+ // If the HeapTypeInfo is not finalized, then it is mutable and its shape
+ // might change in the future. In that case, fall back to pointer identity to
+ // keep the hash consistent until all the TypeBuilder's types are finalized.
+ size_t digest = wasm::hash(info.isFinalized);
+ if (!info.isFinalized) {
+ rehash(digest, uintptr_t(&info));
+ return digest;
+ }
+ rehash(digest, info.kind);
+ switch (info.kind) {
+ case HeapTypeInfo::SignatureKind:
+ hash_combine(digest, hash(info.signature));
+ return digest;
+ case HeapTypeInfo::StructKind:
+ hash_combine(digest, hash(info.struct_));
+ return digest;
+ case HeapTypeInfo::ArrayKind:
+ hash_combine(digest, hash(info.array));
+ return digest;
+ }
+ WASM_UNREACHABLE("unexpected kind");
+}
+
+size_t FiniteShapeHasher::hash(const Tuple& tuple) {
+ size_t digest = wasm::hash(tuple.types.size());
+ for (auto type : tuple.types) {
+ hash_combine(digest, hash(type));
+ }
+ return digest;
+}
+
+size_t FiniteShapeHasher::hash(const Field& field) {
+ size_t digest = wasm::hash(field.packedType);
+ rehash(digest, field.mutable_);
+ hash_combine(digest, hash(field.type));
+ return digest;
+}
+
+size_t FiniteShapeHasher::hash(const Signature& sig) {
+ size_t digest = hash(sig.params);
+ hash_combine(digest, hash(sig.results));
+ return digest;
+}
+
+size_t FiniteShapeHasher::hash(const Struct& struct_) {
+ size_t digest = wasm::hash(struct_.fields.size());
+ for (const auto& field : struct_.fields) {
+ hash_combine(digest, hash(field));
+ }
+ return digest;
+}
+
+size_t FiniteShapeHasher::hash(const Array& array) {
+ return hash(array.element);
+}
+
+size_t FiniteShapeHasher::hash(const Rtt& rtt) {
+ size_t digest = wasm::hash(rtt.depth);
+ hash_combine(digest, hash(rtt.heapType));
+ return digest;
+}
+
+bool FiniteShapeEquator::eq(Type a, Type b) {
+ if (a.isBasic() != b.isBasic()) {
+ return false;
+ } else if (a.isBasic()) {
+ return a.getID() == b.getID();
+ } else {
+ return eq(*getTypeInfo(a), *getTypeInfo(b));
+ }
+}
+
+bool FiniteShapeEquator::eq(HeapType a, HeapType b) {
+ if (a.isBasic() != b.isBasic()) {
+ return false;
+ } else if (a.isBasic()) {
+ return a.getID() == b.getID();
+ }
+ if (topLevelOnly && currDepth > 0) {
+ return true;
+ }
+ auto itA = seenA.find(a);
+ auto itB = seenB.find(b);
+ if ((itA != seenA.end()) != (itB != seenB.end())) {
+ return false;
+ } else if (itA != seenA.end()) {
+ return itA->second == itB->second;
+ }
+ seenA[a] = seenB[b] = ++currStep;
+ ++currDepth;
+ bool ret = eq(*getHeapTypeInfo(a), *getHeapTypeInfo(b));
+ --currDepth;
+ return ret;
+}
+
+bool FiniteShapeEquator::eq(const TypeInfo& a, const TypeInfo& b) {
+ if (a.kind != b.kind) {
+ return false;
+ }
+ switch (a.kind) {
+ case TypeInfo::TupleKind:
+ return eq(a.tuple, b.tuple);
+ case TypeInfo::RefKind:
+ return a.ref.nullable == b.ref.nullable &&
+ eq(a.ref.heapType, b.ref.heapType);
+ case TypeInfo::RttKind:
+ return eq(a.rtt, b.rtt);
+ }
+ WASM_UNREACHABLE("unexpected kind");
+}
+
+bool FiniteShapeEquator::eq(const HeapTypeInfo& a, const HeapTypeInfo& b) {
+ if (a.isFinalized != b.isFinalized) {
+ return false;
+ } else if (!a.isFinalized) {
+ // See comment on corresponding FiniteShapeHasher method.
+ return &a == &b;
+ }
+ if (a.kind != b.kind) {
+ return false;
+ }
+ switch (a.kind) {
+ case HeapTypeInfo::SignatureKind:
+ return eq(a.signature, b.signature);
+ case HeapTypeInfo::StructKind:
+ return eq(a.struct_, b.struct_);
+ case HeapTypeInfo::ArrayKind:
+ return eq(a.array, b.array);
+ }
+ WASM_UNREACHABLE("unexpected kind");
+}
+
+bool FiniteShapeEquator::eq(const Tuple& a, const Tuple& b) {
+ return std::equal(a.types.begin(),
+ a.types.end(),
+ b.types.begin(),
+ b.types.end(),
+ [&](const Type& x, const Type& y) { return eq(x, y); });
+}
+
+bool FiniteShapeEquator::eq(const Field& a, const Field& b) {
+ return a.packedType == b.packedType && a.mutable_ == b.mutable_ &&
+ eq(a.type, b.type);
+}
+
+bool FiniteShapeEquator::eq(const Signature& a, const Signature& b) {
+ return eq(a.params, b.params) && eq(a.results, b.results);
+}
+
+bool FiniteShapeEquator::eq(const Struct& a, const Struct& b) {
+ return std::equal(a.fields.begin(),
+ a.fields.end(),
+ b.fields.begin(),
+ b.fields.end(),
+ [&](const Field& x, const Field& y) { return eq(x, y); });
+}
+
+bool FiniteShapeEquator::eq(const Array& a, const Array& b) {
+ return eq(a.element, b.element);
+}
+
+bool FiniteShapeEquator::eq(const Rtt& a, const Rtt& b) {
+ return a.depth == b.depth && eq(a.heapType, b.heapType);
+}
+
} // anonymous namespace
struct TypeBuilder::Impl {
@@ -1287,10 +1573,12 @@ struct TypeBuilder::Impl {
// to refer to it before it is initialized. Arbitrarily choose a default
// value.
info = std::make_unique<HeapTypeInfo>(Signature());
+ set(Signature());
}
void set(HeapTypeInfo&& hti) {
*info = std::move(hti);
info->isTemp = true;
+ info->isFinalized = false;
initialized = true;
}
HeapType get() { return HeapType(TypeID(info.get())); }
@@ -1351,13 +1639,304 @@ Type TypeBuilder::getTempRttType(size_t i, uint32_t depth) {
namespace {
-// Implements the algorithm to canonicalize the HeapTypes in a TypeBuilder,
-// replacing and deduplicating the temporary type and heaptypes backed by
-// storage owned by the TypeBuilder into normal types and heap types backed by
-// the global stores.
-struct Canonicalizer {
- TypeBuilder& builder;
+// A wrapper around a HeapType that provides equality and hashing based only on
+// its top-level shape, up to but not including its closest HeapType
+// descendants. This is the shape that determines the most fine-grained
+// initial partitions for Hopcroft's algorithm and also the shape that
+// determines the "alphabet" for transitioning to the child HeapTypes in the DFA
+// view of the type definition.
+struct ShallowHeapType {
+ HeapType heapType;
+
+ ShallowHeapType(HeapType heapType) : heapType(heapType) {}
+ bool operator==(const ShallowHeapType& other) const;
+};
+bool ShallowHeapType::operator==(const ShallowHeapType& other) const {
+ return FiniteShapeEquator(/*topLevelOnly=*/true)
+ .eq(this->heapType, other.heapType);
+}
+
+} // anonymous namespace
+} // namespace wasm
+
+namespace std {
+
+template<> class hash<wasm::ShallowHeapType> {
+public:
+ size_t operator()(const wasm::ShallowHeapType& type) const {
+ return wasm::FiniteShapeHasher(/*topLevelOnly=*/true).hash(type.heapType);
+ }
+};
+
+} // namespace std
+
+namespace wasm {
+namespace {
+
+// Uses Hopcroft's DFA minimization algorithm to construct a minimal type
+// definition graph from an input graph. See
+// https://en.wikipedia.org/wiki/DFA_minimization#Hopcroft's_algorithm.
+struct ShapeCanonicalizer {
+ // The new, minimal type definition graph.
+ std::vector<std::unique_ptr<HeapTypeInfo>> infos;
+
+ // Maps each input HeapType to the index of its partition in `partitions`,
+ // which is also the index of its canonicalized HeapTypeInfo in infos.
+ std::unordered_map<HeapType, size_t> partitionIndices;
+
+ ShapeCanonicalizer(const std::vector<HeapType>& input);
+
+private:
+ using TypeSet = std::unordered_set<HeapType>;
+
+ // The HeapTypes in the type definition graph to canonicalize.
+ const std::vector<HeapType>& input;
+
+ // The partitioning of the input HeapTypes used by Hopcroft's algorithm.
+ std::vector<TypeSet> partitions;
+
+ // Hopcroft's algorithm needs to be able to find the predecessors of a
+ // particular state via a given symbol in the alphabet. We use simple child
+ // indices as the alphabet.
+ size_t alphabetSize = 0;
+ std::unordered_map<HeapType, std::unordered_map<size_t, TypeSet>> preds;
+
+ void initializePredecessors();
+ void initializePartitions();
+ void translatePartitionsToTypes();
+
+ std::vector<HeapType*> getChildren(HeapType type);
+ const TypeSet& getPredsOf(HeapType type, size_t symbol);
+ TypeSet getIntersection(const TypeSet& a, const TypeSet& b);
+ TypeSet getDifference(const TypeSet& a, const TypeSet& b);
+};
+
+ShapeCanonicalizer::ShapeCanonicalizer(const std::vector<HeapType>& input)
+ : input(input) {
+ initializePredecessors();
+ initializePartitions();
+
+ // The Hopcroft's algorithm's list of partitions that may still be
+ // distinguishing partitions. Starts out containing all partitions.
+ std::set<size_t> distinguishers;
+ for (size_t i = 0; i < partitions.size(); ++i) {
+ distinguishers.insert(i);
+ }
+
+ // Hopcroft's algorithm
+ while (distinguishers.size()) {
+ // Choose a partition that might be able to distinguish between the members
+ // of some other partition.
+ auto distinguishingIndexIt = distinguishers.begin();
+ TypeSet distinguishing = partitions[*distinguishingIndexIt];
+ distinguishers.erase(distinguishingIndexIt);
+ // For each possibly distinguishing transition symbol...
+ for (size_t symbol = 0; symbol < alphabetSize; ++symbol) {
+ // Find all types that reach one of the current distinguishing types via
+ // `symbol`.
+ TypeSet currPreds;
+ for (auto type : distinguishing) {
+ const TypeSet& specificPreds = getPredsOf(type, symbol);
+ currPreds.insert(specificPreds.begin(), specificPreds.end());
+ }
+ // Find partitions that contain some elements that are predecessors of the
+ // current distinguishing partition and some elements that are not
+ // predecessors of the current distinguishing partition.
+ for (size_t distinguishedIndex = 0, end = partitions.size();
+ distinguishedIndex < end;
+ ++distinguishedIndex) {
+ TypeSet& distinguished = partitions[distinguishedIndex];
+ TypeSet intersection = getIntersection(distinguished, currPreds);
+ if (intersection.empty()) {
+ continue;
+ }
+ TypeSet difference = getDifference(distinguished, currPreds);
+ if (difference.empty()) {
+ continue;
+ }
+ // We can split the partition! Replace it with the intersection and add
+ // the difference as a new partition.
+ partitions[distinguishedIndex] = std::move(intersection);
+ size_t newPartitionIndex = partitions.size();
+ for (auto movedType : difference) {
+ partitionIndices[movedType] = newPartitionIndex;
+ }
+ partitions.emplace_back(std::move(difference));
+ // If the split partition was a potential distinguisher, both smaller
+ // partitions are as well. Otherwise, we only need to add the smaller of
+ // the two smaller partitions as a new potential distinguisher.
+ if (distinguishers.count(distinguishedIndex) ||
+ partitions[newPartitionIndex].size() <=
+ partitions[distinguishedIndex].size()) {
+ distinguishers.insert(newPartitionIndex);
+ } else {
+ distinguishers.insert(distinguishedIndex);
+ }
+ }
+ }
+ }
+
+ translatePartitionsToTypes();
+}
+
+void ShapeCanonicalizer::initializePredecessors() {
+ for (auto heapType : input) {
+ size_t childIndex = 0;
+ for (auto* child : getChildren(heapType)) {
+ alphabetSize = std::max(alphabetSize, childIndex + 1);
+ preds[*child][childIndex++].insert(heapType);
+ }
+ }
+}
+
+void ShapeCanonicalizer::initializePartitions() {
+ // Create the initial partitions based on the top-level shape of the input
+ // heap types. If two heap types are differentiable without recursing into
+ // their child heap types, then they are obviously not equivalent and can be
+ // placed in different partitions. Starting with this fine-grained partition
+ // lets us use simple child indices as our transition alphabet since we will
+ // never mix up equivalent indices from different kinds of types, for example
+ // considering a struct and a signature with the same children to be the same
+ // type.
+ std::unordered_map<ShallowHeapType, size_t> initialIndices;
+ for (auto type : input) {
+ ShallowHeapType shallow(type);
+ auto inserted = initialIndices.insert({shallow, partitions.size()});
+ if (inserted.second) {
+ // We have not seen a type with this shape before; create a new
+ // partition.
+ partitionIndices[type] = partitions.size();
+ partitions.emplace_back(TypeSet{type});
+ } else {
+ // Add to the partition we have already created for this type shape.
+ size_t index = inserted.first->second;
+ partitionIndices[type] = index;
+ partitions[index].insert(type);
+ }
+ }
+}
+
+void ShapeCanonicalizer::translatePartitionsToTypes() {
+ // Create a single new HeapTypeInfo for each partition. Initialize each new
+ // HeapTypeInfo as a copy of a representative HeapTypeInfo from its partition,
+ // then patch all the children of the new HeapTypeInfos to refer to other new
+ // HeapTypeInfos rather than the original HeapTypeInfos. This newly formed
+ // graph will have a shape coinductively equivalent to the original graph's
+ // shape, but each type definition will be minimal and distinct.
+ for (auto& partition : partitions) {
+ const auto& representative = *getHeapTypeInfo(*partition.begin());
+ infos.push_back(std::make_unique<HeapTypeInfo>(representative));
+ infos.back()->isTemp = true;
+ }
+ for (auto& info : infos) {
+ for (auto* child : getChildren(asHeapType(info))) {
+ auto partitionIt = partitionIndices.find(*child);
+ if (partitionIt == partitionIndices.end()) {
+ // This child has already been replaced.
+ continue;
+ }
+ *child = asHeapType(infos.at(partitionIt->second));
+ }
+ }
+}
+
+std::vector<HeapType*> ShapeCanonicalizer::getChildren(HeapType heapType) {
+ std::vector<HeapType*> children;
+
+ auto noteChild = [&](HeapType* child) {
+ if (!child->isBasic()) {
+ children.push_back(child);
+ }
+ };
+
+ // Scan through Types to find the next HeapType.
+ std::function<void(Type)> scanType = [&](Type type) {
+ if (type.isBasic()) {
+ return;
+ }
+ auto* info = getTypeInfo(type);
+ switch (info->kind) {
+ case TypeInfo::TupleKind:
+ for (Type t : info->tuple.types) {
+ scanType(t);
+ }
+ return;
+ case TypeInfo::RefKind:
+ return noteChild(&info->ref.heapType);
+ case TypeInfo::RttKind:
+ return noteChild(&info->rtt.heapType);
+ }
+ WASM_UNREACHABLE("unexpected kind");
+ };
+
+ assert(!heapType.isBasic() && "Cannot have basic defined HeapType");
+ auto* info = getHeapTypeInfo(heapType);
+ switch (info->kind) {
+ case HeapTypeInfo::SignatureKind:
+ scanType(info->signature.params);
+ scanType(info->signature.results);
+ return children;
+ case HeapTypeInfo::StructKind:
+ for (auto& field : info->struct_.fields) {
+ scanType(field.type);
+ }
+ return children;
+ case HeapTypeInfo::ArrayKind:
+ scanType(info->array.element.type);
+ return children;
+ }
+ WASM_UNREACHABLE("unexpected kind");
+}
+
+const std::unordered_set<HeapType>&
+ShapeCanonicalizer::getPredsOf(HeapType type, size_t symbol) {
+ static TypeSet empty;
+ auto predsIt = preds.find(type);
+ if (predsIt == preds.end()) {
+ return empty;
+ }
+ auto& predsOfType = predsIt->second;
+ auto specificPredsIt = predsOfType.find(symbol);
+ if (specificPredsIt == predsOfType.end()) {
+ return empty;
+ }
+ return specificPredsIt->second;
+}
+
+std::unordered_set<HeapType>
+ShapeCanonicalizer::getIntersection(const TypeSet& a, const TypeSet& b) {
+ TypeSet ret;
+ const TypeSet& smaller = a.size() < b.size() ? a : b;
+ const TypeSet& bigger = a.size() < b.size() ? b : a;
+ for (auto type : smaller) {
+ if (bigger.count(type)) {
+ ret.insert(type);
+ }
+ }
+ return ret;
+}
+
+std::unordered_set<HeapType>
+ShapeCanonicalizer::getDifference(const TypeSet& a, const TypeSet& b) {
+ TypeSet ret;
+ for (auto type : a) {
+ if (!b.count(type)) {
+ ret.insert(type);
+ }
+ }
+ return ret;
+}
+
+// Replaces temporary types and heap types in a type definition graph with their
+// globally canonical versions to prevent temporary types or heap type from
+// leaking into the global stores.
+struct GlobalCanonicalizer {
+
+ std::vector<HeapType> results;
+ GlobalCanonicalizer(std::vector<std::unique_ptr<HeapTypeInfo>>& infos);
+
+private:
struct Item {
enum Kind {
TypeKind,
@@ -1377,56 +1956,30 @@ struct Canonicalizer {
// The work list of Types and HeapTypes remaining to be scanned.
std::vector<Item> scanList;
- // Maps Type and HeapType IDs to the IDs of their child Types and HeapTypes
- // in the type graph. Only considers compound Types and HeapTypes.
- std::unordered_map<TypeID, std::unordered_set<TypeID>> children;
-
// Maps each temporary Type and HeapType to the locations where they will have
// to be replaced with canonical Types and HeapTypes.
std::unordered_map<Type, std::vector<Type*>> typeLocations;
std::unordered_map<HeapType, std::vector<HeapType*>> heapTypeLocations;
- // Maps Types and HeapTypes backed by the TypeBuilder's Stores to globally
- // canonical Types and HeapTypes.
- std::unordered_map<Type, Type> canonicalTypes;
- std::unordered_map<HeapType, HeapType> canonicalHeapTypes;
-
- // The fully canonicalized heap types.
- std::vector<HeapType> results;
-
- Canonicalizer(TypeBuilder& builder);
template<typename T1, typename T2> void noteChild(T1 parent, T2* child);
void scanHeapType(HeapType* ht);
void scanType(Type* child);
- void findSelfReferentialHeapTypes();
- std::vector<Item> getOrderedItems();
-
- // Replaces the pointee Type or HeapType of `type` with its globally canonical
- // equivalent, recording the substitution for future use in either
- // `canonicalTypes` or `canonicalHeapTypes`.
- template<typename T>
- void canonicalize(T* type, std::unordered_map<T, T>& canonicals);
};
-// Traverse the type graph rooted at the initialized HeapTypeInfos in reverse
-// postorder, replacing in place all Types and HeapTypes backed by the
-// TypeBuilder's Stores with equivalent globally canonicalized Types and
-// HeapTypes.
-Canonicalizer::Canonicalizer(TypeBuilder& builder) : builder(builder) {
- // Initialize `results` to hold all the temporary HeapTypes. Since we are
- // canonicalizing all Types and HeapTypes in place, this will end up holding
- // all the canonicalized HeapTypes instead. Also seed the scan list with these
- // HeapTypes.
- results.reserve(builder.impl->entries.size());
- for (auto& entry : builder.impl->entries) {
- assert(entry.initialized && "Cannot access uninitialized HeapType");
- results.push_back(entry.get());
+// Traverse the type graph rooted at the initialized HeapTypeInfos, replacing in
+// place all Types and HeapTypes backed by the TypeBuilder's Stores with
+// equivalent globally canonicalized Types and HeapTypes.
+GlobalCanonicalizer::GlobalCanonicalizer(
+ std::vector<std::unique_ptr<HeapTypeInfo>>& infos) {
+ // Seed the scan list with the HeapTypes to canonicalize.
+ results.reserve(infos.size());
+ for (auto& info : infos) {
+ results.push_back(asHeapType(info));
scanList.push_back(&results.back());
}
- // Traverse the type graph reachable from the heap types, calculating
- // reachability and collecting a list of types and heap types that need to be
- // canonicalized.
+ // Traverse the type graph reachable from the heap types, collecting a list of
+ // type and heap type use sites that need to be patched with canonical types.
while (scanList.size() != 0) {
auto item = scanList.back();
scanList.pop_back();
@@ -1440,42 +1993,47 @@ Canonicalizer::Canonicalizer(TypeBuilder& builder) : builder(builder) {
}
}
- findSelfReferentialHeapTypes();
-
- // Visit the use sites of Types and HeapTypes, replacing them with their
- // canonicalized versions in an order that guarantees no temporary types will
- // be leaked into the global stores.
- for (auto it : getOrderedItems()) {
- switch (it.kind) {
- case Item::TypeKind:
- canonicalize(it.type, canonicalTypes);
- break;
- case Item::HeapTypeKind:
- canonicalize(it.heapType, canonicalHeapTypes);
- break;
+ // Canonicalize HeapTypes at all their use sites. HeapTypes for which there
+ // was not already a globally canonical version are moved to the global store
+ // to become the canonical version. These new canonical HeapTypes still
+ // contain references to temporary Types owned by the TypeBuilder, so we must
+ // subsequently replace those references with references to canonical Types.
+ // Canonicalize non-tuple Types (which never directly refer to other Types)
+ // before tuple Types to avoid canonicalizing a tuple that still contains
+ // non-canonical Types.
+ for (auto& info : infos) {
+ HeapType original = asHeapType(info);
+ HeapType canonical = globalHeapTypeStore.canonicalize(std::move(info));
+ if (original != canonical) {
+ for (HeapType* use : heapTypeLocations.at(original)) {
+ *use = canonical;
+ }
}
}
-
- // Now that all other Types and HeapTypes have been canonicalized, move
- // self-referential HeapTypes to the global store so that they will be
- // considered canonical and outlive the TypeBuilder.
- std::lock_guard<std::mutex> lock(globalHeapTypeStore.mutex);
- for (auto& entry : builder.impl->entries) {
- if (isSelfReferential(entry.get())) {
- globalHeapTypeStore.recordCanonical(std::move(entry.info));
+ auto canonicalizeTypes = [&](bool tuples) {
+ for (auto& pair : typeLocations) {
+ Type original = pair.first;
+ std::vector<Type*>& uses = pair.second;
+ if (original.isTuple() == tuples) {
+ Type canonical = globalTypeStore.canonicalize(*getTypeInfo(original));
+ for (Type* use : uses) {
+ *use = canonical;
+ }
+ }
}
- }
+ };
+ canonicalizeTypes(false);
+ canonicalizeTypes(true);
}
template<typename T1, typename T2>
-void Canonicalizer::noteChild(T1 parent, T2* child) {
+void GlobalCanonicalizer::noteChild(T1 parent, T2* child) {
if (child->isCompound()) {
- children[parent.getID()].insert(child->getID());
scanList.push_back(child);
}
}
-void Canonicalizer::scanHeapType(HeapType* ht) {
+void GlobalCanonicalizer::scanHeapType(HeapType* ht) {
assert(ht->isCompound());
heapTypeLocations[*ht].push_back(ht);
if (scanned.count(ht->getID())) {
@@ -1500,7 +2058,7 @@ void Canonicalizer::scanHeapType(HeapType* ht) {
}
};
-void Canonicalizer::scanType(Type* type) {
+void GlobalCanonicalizer::scanType(Type* type) {
assert(type->isCompound());
typeLocations[*type].push_back(type);
if (scanned.count(type->getID())) {
@@ -1524,202 +2082,31 @@ void Canonicalizer::scanType(Type* type) {
}
}
-void Canonicalizer::findSelfReferentialHeapTypes() {
- // Use Tarjan's strongly connected components algorithm on the parent-child
- // graph to find self-referential types in O(|V|+|E|) time. Each HeapType in a
- // strongly connected component with multiple elements must be
- // self-referential because it is mutually recursive with all other HeapTypes
- // in that strongly connected component. HeapTypes in strongly connected
- // components of size one may also be self-referential, but it is trivial to
- // find these because they must be their own direct children. See
- // https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm.
-
- auto mark = [&](HeapType type) {
- auto* info = getHeapTypeInfo(type);
- info->isSelfReferential = true;
- info->isTemp = false;
- };
-
- // Get the HeapType children of a HeapType, skipping all intermediate Types.
- auto getChildren = [&](HeapType type) {
- std::unordered_set<HeapType> results;
- std::function<void(TypeID, bool)> visit = [&](TypeID id, bool isRoot) {
- if (isRoot || typeLocations.count(Type(id))) {
- auto it = children.find(id);
- if (it != children.end()) {
- for (TypeID child : it->second) {
- visit(child, false);
- }
- }
- } else {
- results.insert(HeapType(id));
- }
- };
- visit(type.getID(), true);
- return results;
- };
-
- // The Tarjan's algorithm stack. Similar to a DFS stack, but elements are only
- // popped off once they are committed to a strongly connected component.
- // HeapTypes stay on the stack after they are visited iff they have a back
- // edge to a HeapType earlier in the stack.
- std::vector<HeapType> stack;
- std::unordered_set<HeapType> stackElems;
- // Indices assigned to each HeapType in the order they are visited.
- std::unordered_map<HeapType, size_t> indices;
- // The smallest index of the HeapTypes reachable from each HeapType through
- // its subtree.
- std::unordered_map<HeapType, size_t> minReachable;
-
- std::function<void(HeapType)> visit = [&](HeapType curr) {
- size_t index = indices.size();
- indices[curr] = index;
- minReachable[curr] = index;
- stack.push_back(curr);
- stackElems.insert(curr);
-
- for (HeapType child : getChildren(curr)) {
- if (!indices.count(child)) {
- // Child has not been visited yet; visit it.
- visit(child);
- minReachable[curr] = std::min(minReachable[curr], minReachable[child]);
- } else if (stackElems.count(child)) {
- // Child was already visited but not committed to a strongly connected
- // component.
- minReachable[curr] = std::min(minReachable[curr], indices[child]);
- }
- // We cannot differentiate self-referential types with strongly connected
- // component size one from non-self-referential types just by looking at
- // the strongly connected components. If a type is directly
- // self-referential, mark it here so we don't miss it later.
- if (child == curr) {
- mark(curr);
- }
- }
-
- if (minReachable[curr] == indices[curr]) {
- // Curr doesn't have any back edges to an earlier element, so it is the
- // root of the strongly connected component including itself and anything
- // after it still on the stack. If this strongly connected component has
- // more than one element, they are all self referential HeapTypes.
- // Self-referential types with SCC size one were already accounted for.
- if (stack.back() != curr) {
- mark(curr);
- while (stack.back() != curr) {
- mark(stack.back());
- stackElems.erase(stack.back());
- stack.pop_back();
- }
- }
- stack.pop_back();
- stackElems.erase(curr);
- }
- };
+} // anonymous namespace
- for (auto& entry : builder.impl->entries) {
- visit(entry.get());
- }
-}
-
-std::vector<Canonicalizer::Item> Canonicalizer::getOrderedItems() {
- // In order to canonicalize Types and HeapTypes without leaking any temporary
- // types into the global type store, we need to canonicalize children before
- // parents. In the case of recursive types, this is not possible due to cycles
- // in the parent-child relation. In principle that can be overcome by
- // canonicalizing type structures rather than type contents, but for now we
- // instead cut corners and break the cycles by skipping canonicalization of
- // self-referential HeapTypes. This works because the path from any Type or
- // HeapType to itself in the parent-child relation must go through some
- // self-referential HeapType; it is not possible to have a cycle composed only
- // of Types, for example. In effect this means that we have a nominal type
- // system for self-referential HeapTypes and a structural type system for
- // everything else. Eventually we will have to implement something more
- // consistent, but this is good enough for getting prototype toolchains up and
- // running.
-
- // TODO: None of this is particularly optimized. Benchmark to see if this is a
- // significant bottleneck and investigate using better data structures and
- // algorithms.
-
- // Remove self-referential HeapTypes to cut cycles and collect the remaining
- // types to be sorted.
- auto childrenDAG = children;
- std::unordered_set<TypeID> toSort;
- for (auto& entry : builder.impl->entries) {
- HeapType curr = entry.get();
- if (isSelfReferential(curr)) {
- childrenDAG.erase(curr.getID());
- for (auto& kv : childrenDAG) {
- kv.second.erase(curr.getID());
- }
- } else {
- toSort.insert(curr.getID());
- }
- }
- for (auto& kv : childrenDAG) {
- toSort.insert(kv.first);
- for (auto& child : kv.second) {
- toSort.insert(child);
- }
+std::vector<HeapType> TypeBuilder::build() {
+ std::vector<HeapType> heapTypes;
+ for (auto& entry : impl->entries) {
+ assert(entry.initialized && "Cannot access uninitialized HeapType");
+ entry.info->isFinalized = true;
+ heapTypes.push_back(entry.get());
}
- // Topologically sort so that children come before their parents.
- std::vector<TypeID> sorted;
- std::unordered_set<TypeID> seen;
- std::function<void(TypeID)> visit = [&](TypeID id) {
- if (seen.count(id)) {
- return;
- }
- // Push children of the current type before pushing the current type.
- auto it = childrenDAG.find(id);
- if (it != childrenDAG.end()) {
- for (auto child : it->second) {
- visit(child);
- }
- }
- seen.insert(id);
- sorted.push_back(id);
- };
- for (TypeID i : toSort) {
- visit(i);
- }
+ // Canonicalize the shape of the type definition graph.
+ ShapeCanonicalizer minimized(heapTypes);
- // Expand the ordered types into ordered type use sites.
- std::vector<Item> items;
- for (TypeID id : sorted) {
- // IDs may be Types or HeapTypes, so just try both.
- if (typeLocations.count(Type(id))) {
- for (Type* loc : typeLocations[Type(id)]) {
- items.emplace_back(loc);
- }
- } else {
- for (HeapType* loc : heapTypeLocations[HeapType(id)]) {
- items.emplace_back(loc);
- }
- }
- }
- return items;
-}
+ // The shape of the definition graph is now canonicalized, but it is still
+ // comprised of temporary types and heap types. Get or create their globally
+ // canonical versions.
+ GlobalCanonicalizer globallyCanonical(minimized.infos);
-template<typename T>
-void Canonicalizer::canonicalize(T* type,
- std::unordered_map<T, T>& canonicals) {
- auto it = canonicals.find(*type);
- if (it != canonicals.end()) {
- *type = it->second;
- } else {
- // Get the globally canonicalized version of the type
- auto* info = MetaTypeInfo<T>::getInfo(*type);
- T canonical = MetaTypeInfo<T>::globalStore.canonicalize(*info);
- canonicals.insert({*type, canonical});
- *type = canonical;
+ // Map the original heap types to their minimized and globally canonical
+ // versions.
+ for (auto& type : heapTypes) {
+ type = globallyCanonical.results[minimized.partitionIndices[type]];
}
-}
-
-} // anonymous namespace
-std::vector<HeapType> TypeBuilder::build() {
- return Canonicalizer(*this).results;
+ return heapTypes;
}
} // namespace wasm
@@ -1748,40 +2135,6 @@ public:
}
};
-size_t hash<wasm::TypeInfo>::operator()(const wasm::TypeInfo& info) const {
- auto digest = wasm::hash(info.kind);
- switch (info.kind) {
- case wasm::TypeInfo::TupleKind:
- wasm::rehash(digest, info.tuple);
- return digest;
- case wasm::TypeInfo::RefKind:
- wasm::rehash(digest, info.ref.heapType);
- wasm::rehash(digest, info.ref.nullable);
- return digest;
- case wasm::TypeInfo::RttKind:
- wasm::rehash(digest, info.rtt);
- return digest;
- }
- WASM_UNREACHABLE("unexpected kind");
-}
-
-size_t hash<wasm::HeapTypeInfo>::
-operator()(const wasm::HeapTypeInfo& info) const {
- auto digest = wasm::hash(info.kind);
- switch (info.kind) {
- case wasm::HeapTypeInfo::SignatureKind:
- wasm::rehash(digest, info.signature);
- return digest;
- case wasm::HeapTypeInfo::StructKind:
- wasm::rehash(digest, info.struct_);
- return digest;
- case wasm::HeapTypeInfo::ArrayKind:
- wasm::rehash(digest, info.array);
- return digest;
- }
- WASM_UNREACHABLE("unexpected kind");
-}
-
size_t hash<wasm::Type>::operator()(const wasm::Type& type) const {
return wasm::hash(type.getID());
}
@@ -1800,8 +2153,6 @@ size_t hash<wasm::Field>::operator()(const wasm::Field& field) const {
auto digest = wasm::hash(field.type);
wasm::rehash(digest, field.packedType);
wasm::rehash(digest, field.mutable_);
- // Note that the name is not hashed here - it is pure metadata for printing
- // purposes only.
return digest;
}
diff --git a/test/example/type-builder.cpp b/test/example/type-builder.cpp
index 59222775b..0ac13f974 100644
--- a/test/example/type-builder.cpp
+++ b/test/example/type-builder.cpp
@@ -132,6 +132,7 @@ void test_recursive() {
std::cout << built[1] << "\n\n";
assert(built[0].getSignature().results.getHeapType() == built[1]);
assert(built[1].getSignature().results.getHeapType() == built[0]);
+ assert(built[0] == built[1]);
}
{
@@ -161,6 +162,10 @@ void test_recursive() {
assert(built[2].getSignature().results.getHeapType() == built[3]);
assert(built[3].getSignature().results.getHeapType() == built[4]);
assert(built[4].getSignature().results.getHeapType() == built[0]);
+ assert(built[0] == built[1]);
+ assert(built[1] == built[2]);
+ assert(built[2] == built[3]);
+ assert(built[3] == built[4]);
}
{
@@ -189,9 +194,9 @@ void test_recursive() {
std::cout << built[3] << "\n";
std::cout << built[4] << "\n";
std::cout << built[5] << "\n\n";
- assert(built[0] != built[1]); // TODO: canonicalize recursive types
+ assert(built[0] == built[1]);
assert(built[2] == built[3]);
- assert(built[4] != built[5]); // Contain "different" recursive types
+ assert(built[4] == built[5]);
assert(built[4].getSignature().results.getHeapType() == built[0]);
assert(built[5].getSignature().results.getHeapType() == built[1]);
assert(built[0].getSignature().results ==
@@ -199,6 +204,23 @@ void test_recursive() {
assert(built[1].getSignature().results ==
Type({Type(built[1], Nullable), Type(built[3], Nullable)}));
}
+
+ {
+ // Folded and unfolded
+ std::vector<HeapType> built;
+ {
+ TypeBuilder builder(2);
+ Type temp0 = builder.getTempRefType(0, Nullable);
+ builder.setHeapType(0, Signature(Type::none, temp0));
+ builder.setHeapType(1, Signature(Type::none, temp0));
+ built = builder.build();
+ }
+ std::cout << built[0] << "\n";
+ std::cout << built[1] << "\n\n";
+ assert(built[0].getSignature().results.getHeapType() == built[0]);
+ assert(built[1].getSignature().results.getHeapType() == built[0]);
+ assert(built[0] == built[1]);
+ }
}
int main() {
diff --git a/test/example/type-builder.txt b/test/example/type-builder.txt
index 6b42ade8c..a2985c2ed 100644
--- a/test/example/type-builder.txt
+++ b/test/example/type-builder.txt
@@ -1,17 +1,17 @@
;; Test TypeBuilder
Before setting heap types:
-(ref $sig) => (ref (func))
-(ref $struct) => (ref (func))
-(ref $array) => (ref (func))
-(ref null $array) => (ref null (func))
-(rtt 0 $array) => (rtt 0 (func))
+(ref $sig) => [T](ref [T](func))
+(ref $struct) => [T](ref [T](func))
+(ref $array) => [T](ref [T](func))
+(ref null $array) => [T](ref null [T](func))
+(rtt 0 $array) => [T](rtt 0 [T](func))
After setting heap types:
-(ref $sig) => (ref (func (param (ref (struct (field (ref null (array (mut externref))) (mut (rtt 0 (array (mut externref)))))))) (result (ref (array (mut externref))) i32)))
-(ref $struct) => (ref (struct (field (ref null (array (mut externref))) (mut (rtt 0 (array (mut externref)))))))
-(ref $array) => (ref (array (mut externref)))
-(ref null $array) => (ref null (array (mut externref)))
-(rtt 0 $array) => (rtt 0 (array (mut externref)))
+(ref $sig) => [T](ref [T](func (param [T](ref [T](struct (field [T](ref null [T](array (mut externref))) (mut [T](rtt 0 [T](array (mut externref)))))))) (result [T](ref [T](array (mut externref))) i32)))
+(ref $struct) => [T](ref [T](struct (field [T](ref null [T](array (mut externref))) (mut [T](rtt 0 [T](array (mut externref)))))))
+(ref $array) => [T](ref [T](array (mut externref)))
+(ref null $array) => [T](ref null [T](array (mut externref)))
+(rtt 0 $array) => [T](rtt 0 [T](array (mut externref)))
After building types:
(ref $sig) => (ref (func (param (ref (struct (field (ref null (array (mut externref))) (mut (rtt 0 (array (mut externref)))))))) (result (ref (array (mut externref))) i32)))
@@ -24,14 +24,14 @@ After building types:
;; Test recursive types
(func (result (ref null ...1)))
-(func (result (ref null (func (result (ref null ...3))))))
-(func (result (ref null (func (result (ref null ...3))))))
+(func (result (ref null ...1)))
+(func (result (ref null ...1)))
-(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9)))))))))))))))
-(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9)))))))))))))))
-(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9)))))))))))))))
-(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9)))))))))))))))
-(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9)))))))))))))))
+(func (result (ref null ...1)))
+(func (result (ref null ...1)))
+(func (result (ref null ...1)))
+(func (result (ref null ...1)))
+(func (result (ref null ...1)))
(func (result (ref null ...1) (ref null (func))))
(func (result (ref null ...1) (ref null (func))))
@@ -40,3 +40,6 @@ After building types:
(func (result (ref null (func (result ...1 (ref null (func)))))))
(func (result (ref null (func (result ...1 (ref null (func)))))))
+(func (result (ref null ...1)))
+(func (result (ref null ...1)))
+
diff --git a/test/lit/recursive-types.wast b/test/lit/recursive-types.wast
index 7f719a359..a5d301006 100644
--- a/test/lit/recursive-types.wast
+++ b/test/lit/recursive-types.wast
@@ -2,27 +2,44 @@
;; RUN: wasm-opt %s -all -S -o - | filecheck %s
-;; TODO: Fix the bug where structurally identical types are given the same
-;; generated name, making the wast invalid due to duplicate names.
-
;; CHECK: (module
;; CHECK-NEXT: (type $ref?|...0|_=>_ref?|...0| (func (param (ref null $ref?|...0|_=>_ref?|...0|)) (result (ref null $ref?|...0|_=>_ref?|...0|))))
-;; CHECK-NEXT: (type $ref?|...0|_=>_ref?|...0| (func (param (ref null $ref?|...0|_=>_ref?|...0|)) (result (ref null $ref?|...0|_=>_ref?|...0|))))
;; CHECK-NEXT: (func $foo (param $0 (ref null $ref?|...0|_=>_ref?|...0|)) (result (ref null $ref?|...0|_=>_ref?|...0|))
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (func $bar (param $0 (ref null $ref?|...0|_=>_ref?|...0|)) (result (ref null $ref?|...0|_=>_ref?|...0|))
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
+;; CHECK-NEXT: (func $baz (param $0 (ref null $ref?|...0|_=>_ref?|...0|)) (result (ref null $ref?|...0|_=>_ref?|...0|))
+;; CHECK-NEXT: (unreachable)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (func $qux (param $0 (ref null $ref?|...0|_=>_ref?|...0|)) (result (ref null $ref?|...0|_=>_ref?|...0|))
+;; CHECK-NEXT: (unreachable)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (func $quux (param $0 (ref null $ref?|...0|_=>_ref?|...0|)) (result (ref null $ref?|...0|_=>_ref?|...0|))
+;; CHECK-NEXT: (unreachable)
+;; CHECK-NEXT: )
;; CHECK-NEXT: )
(module
(type (func (param (ref null 0)) (result (ref null 0))))
(type (func (param (ref null 1)) (result (ref null 1))))
+ (type (func (param (ref null 0)) (result (ref null 1))))
+ (type (func (param (ref null 3)) (result (ref null 4))))
+ (type (func (param (ref null 4)) (result (ref null 3))))
(func $foo (type 0)
(unreachable)
)
(func $bar (type 1)
(unreachable)
)
+ (func $baz (type 2)
+ (unreachable)
+ )
+ (func $qux (type 3)
+ (unreachable)
+ )
+ (func $quux (type 4)
+ (unreachable)
+ )
)