summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/wasm/wasm-type.cpp603
-rw-r--r--test/lit/lub-bug-3843.wast31
2 files changed, 423 insertions, 211 deletions
diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp
index 87b562937..52af20847 100644
--- a/src/wasm/wasm-type.cpp
+++ b/src/wasm/wasm-type.cpp
@@ -571,10 +571,10 @@ typename Info::type_t Store<Info>::canonicalize(std::unique_ptr<Info>&& info) {
std::lock_guard<std::mutex> lock(mutex);
auto indexIt = typeIDs.find(std::cref(*info));
if (indexIt != typeIDs.end()) {
- return HeapType(indexIt->second);
+ return typename Info::type_t(indexIt->second);
}
info->isTemp = false;
- return HeapType(recordCanonical(std::move(info)));
+ return typename Info::type_t(recordCanonical(std::move(info)));
}
template<typename Info>
@@ -1551,6 +1551,9 @@ std::ostream& TypePrinter::print(Type type) {
if (isTemp(type)) {
os << "[T]";
}
+#if TRACE_CANONICALIZATION
+ os << "[" << ((type.getID() >> 4) % 1000) << "]";
+#endif
if (type.isTuple()) {
print(type.getTuple());
} else if (type.isRef()) {
@@ -1590,6 +1593,9 @@ std::ostream& TypePrinter::print(HeapType heapType) {
if (isTemp(heapType)) {
os << "[T]";
}
+#if TRACE_CANONICALIZATION
+ os << "[" << ((heapType.getID() >> 4) % 1000) << "]";
+#endif
if (getHeapTypeInfo(heapType)->kind == HeapTypeInfo::BasicKind) {
os << '*';
print(getHeapTypeInfo(heapType)->basic);
@@ -2007,6 +2013,217 @@ Type TypeBuilder::getTempRttType(Rtt rtt) {
namespace {
+// Generic utility for traversing type graphs. The inserted roots must live as
+// long as the Walker because they are referenced by address. This base class
+// only has logic for traversing type graphs; figuring out when to stop
+// traversing the graph and doing useful work during the traversal is left to
+// subclasses.
+template<typename Self> struct TypeGraphWalkerBase {
+ void walkRoot(Type* type);
+ void walkRoot(HeapType* ht);
+
+ // Override these in subclasses to do useful work.
+ void preVisitType(Type* type) {}
+ void preVisitHeapType(HeapType* ht) {}
+ void postVisitType(Type* type) {}
+ void postVisitHeapType(HeapType* ht) {}
+
+ // This base walker does not know when to stop scanning, so at least one of
+ // these needs to be overridden with a method that calls the base scanning
+ // method only if some end condition isn't met.
+ void scanHeapType(HeapType* ht);
+ void scanType(Type* type);
+
+private:
+ struct Task {
+ enum Kind {
+ PreType,
+ PreHeapType,
+ ScanType,
+ ScanHeapType,
+ PostType,
+ PostHeapType,
+ } kind;
+ union {
+ Type* type;
+ HeapType* heapType;
+ };
+ static Task preVisit(Type* type) { return Task(type, PreType); }
+ static Task preVisit(HeapType* ht) { return Task(ht, PreHeapType); }
+ static Task scan(Type* type) { return Task(type, ScanType); }
+ static Task scan(HeapType* ht) { return Task(ht, ScanHeapType); }
+ static Task postVisit(Type* type) { return Task(type, PostType); }
+ static Task postVisit(HeapType* ht) { return Task(ht, PostHeapType); }
+
+ private:
+ Task(Type* type, Kind kind) : kind(kind), type(type) {}
+ Task(HeapType* ht, Kind kind) : kind(kind), heapType(ht) {}
+ };
+
+ void doWalk();
+
+ std::vector<Task> taskList;
+ void push(Type* type);
+ void push(HeapType* type);
+
+ Self& self() { return *static_cast<Self*>(this); }
+};
+
+template<typename Self> void TypeGraphWalkerBase<Self>::walkRoot(Type* type) {
+ assert(taskList.empty());
+ taskList.push_back(Task::scan(type));
+ doWalk();
+}
+
+template<typename Self> void TypeGraphWalkerBase<Self>::walkRoot(HeapType* ht) {
+ assert(taskList.empty());
+ taskList.push_back(Task::scan(ht));
+ doWalk();
+}
+
+template<typename Self> void TypeGraphWalkerBase<Self>::doWalk() {
+ while (!taskList.empty()) {
+ auto curr = taskList.back();
+ taskList.pop_back();
+ switch (curr.kind) {
+ case Task::PreType:
+ self().preVisitType(curr.type);
+ break;
+ case Task::PreHeapType:
+ self().preVisitHeapType(curr.heapType);
+ break;
+ case Task::ScanType:
+ taskList.push_back(Task::postVisit(curr.type));
+ self().scanType(curr.type);
+ taskList.push_back(Task::preVisit(curr.type));
+ break;
+ case Task::ScanHeapType:
+ taskList.push_back(Task::postVisit(curr.heapType));
+ self().scanHeapType(curr.heapType);
+ taskList.push_back(Task::preVisit(curr.heapType));
+ break;
+ case Task::PostType:
+ self().postVisitType(curr.type);
+ break;
+ case Task::PostHeapType:
+ self().postVisitHeapType(curr.heapType);
+ break;
+ }
+ }
+}
+
+template<typename Self>
+void TypeGraphWalkerBase<Self>::scanHeapType(HeapType* ht) {
+ if (ht->isBasic()) {
+ return;
+ }
+ auto* info = getHeapTypeInfo(*ht);
+ switch (info->kind) {
+ case HeapTypeInfo::BasicKind:
+ break;
+ case HeapTypeInfo::SignatureKind:
+ taskList.push_back(Task::scan(&info->signature.results));
+ taskList.push_back(Task::scan(&info->signature.params));
+ break;
+ case HeapTypeInfo::StructKind: {
+ auto& fields = info->struct_.fields;
+ for (auto field = fields.rbegin(); field != fields.rend(); ++field) {
+ taskList.push_back(Task::scan(&field->type));
+ }
+ break;
+ }
+ case HeapTypeInfo::ArrayKind:
+ taskList.push_back(Task::scan(&info->array.element.type));
+ break;
+ }
+}
+
+template<typename Self> void TypeGraphWalkerBase<Self>::scanType(Type* type) {
+ if (type->isBasic()) {
+ return;
+ }
+ auto* info = getTypeInfo(*type);
+ switch (info->kind) {
+ case TypeInfo::TupleKind: {
+ auto& types = info->tuple.types;
+ for (auto it = types.rbegin(); it != types.rend(); ++it) {
+ taskList.push_back(Task::scan(&*it));
+ }
+ break;
+ }
+ case TypeInfo::RefKind: {
+ taskList.push_back(Task::scan(&info->ref.heapType));
+ break;
+ }
+ case TypeInfo::RttKind:
+ taskList.push_back(Task::scan(&info->rtt.heapType));
+ break;
+ }
+}
+
+// A type graph walker base class that still does no useful work, but at least
+// knows to scan each HeapType only once.
+template<typename Self> struct HeapTypeGraphWalker : TypeGraphWalkerBase<Self> {
+ // Override this.
+ void noteHeapType(HeapType ht) {}
+
+ void scanHeapType(HeapType* ht) {
+ if (scanned.insert(*ht).second) {
+ static_cast<Self*>(this)->noteHeapType(*ht);
+ TypeGraphWalkerBase<Self>::scanHeapType(ht);
+ }
+ }
+
+private:
+ std::unordered_set<HeapType> scanned;
+};
+
+// A type graph walker base class that still does no useful work, but at least
+// knows to scan each HeapType and Type only once.
+template<typename Self> struct TypeGraphWalker : TypeGraphWalkerBase<Self> {
+ // Override these.
+ void noteType(Type type) {}
+ void noteHeapType(HeapType ht) {}
+
+ void scanType(Type* type) {
+ if (scannedTypes.insert(*type).second) {
+ static_cast<Self*>(this)->noteType(*type);
+ TypeGraphWalkerBase<Self>::scanType(type);
+ }
+ }
+ void scanHeapType(HeapType* ht) {
+ if (scannedHeapTypes.insert(*ht).second) {
+ static_cast<Self*>(this)->noteHeapType(*ht);
+ TypeGraphWalkerBase<Self>::scanHeapType(ht);
+ }
+ }
+
+private:
+ std::unordered_set<HeapType> scannedHeapTypes;
+ std::unordered_set<Type> scannedTypes;
+};
+
+// A type graph walker that notes parent-child relationships between HeapTypes.
+// What to do with each noted relationship is left to subclasses.
+template<typename Self> struct HeapTypePathWalker : HeapTypeGraphWalker<Self> {
+ // Override this.
+ void noteChild(HeapType parent, HeapType* child) {}
+
+ void preVisitHeapType(HeapType* ht) {
+ if (!path.empty()) {
+ static_cast<Self*>(this)->noteChild(path.back(), ht);
+ }
+ path.push_back(*ht);
+ }
+ void postVisitHeapType(HeapType* ht) {
+ assert(!path.empty() && path.back() == *ht);
+ path.pop_back();
+ }
+
+private:
+ std::vector<HeapType> path;
+};
+
// 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
@@ -2046,21 +2263,26 @@ namespace {
// 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.
+ // The minimized HeapTypes, possibly including both new temporary HeapTypes as
+ // well as globally canonical HeapTypes that were reachable from the input
+ // roots.
+ std::vector<HeapType> results;
+
+ // The new, temporary, minimal HeapTypeInfos. Contains empty unique_ptrs at
+ // indices corresponding to globally canonical HeapTypes.
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.
+ // Maps each input root HeapType to the index of its partition in
+ // `partitions`, which is also the index of its minimized version in
+ // `minimized`, and if that minimized version is not globally canonical, also
+ // the index of the minimized HeapTypeInfo in `infos`.
std::unordered_map<HeapType, size_t> partitionIndices;
- ShapeCanonicalizer(const std::vector<HeapType>& input);
+ ShapeCanonicalizer(std::vector<HeapType>& roots);
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;
@@ -2070,18 +2292,13 @@ private:
size_t alphabetSize = 0;
std::unordered_map<HeapType, std::unordered_map<size_t, TypeSet>> preds;
- void initializePredecessors();
+ void initializePredecessors(std::vector<HeapType>& roots);
void initializePartitions();
void translatePartitionsToTypes();
- // Returns pointers to the HeapType's immediate descendant compound HeapTypes.
- // For determining partitions and state transitions, BasicKind HeapTypes are
- // treated identically to basic HeapTypes and are not included in the results
- // of `getChildren`. For translating the partitions back into types, though,
- // it is important that BasicKind children are included so they can be updated
- // to refer to their corresponding shape-canonicalized HeapTypeInfo in the
- // results. TODO: Consolidate all type scanning in one utility.
- std::vector<HeapType*> getChildren(HeapType type, bool includeBasic = false);
+ // Return the non-basic HeapType children of `ht` (including BasicKind
+ // children).
+ std::vector<HeapType*> getChildren(HeapType ht);
const TypeSet& getPredsOf(HeapType type, size_t symbol);
TypeSet getIntersection(const TypeSet& a, const TypeSet& b);
TypeSet getDifference(const TypeSet& a, const TypeSet& b);
@@ -2098,9 +2315,16 @@ private:
#endif
};
-ShapeCanonicalizer::ShapeCanonicalizer(const std::vector<HeapType>& input)
- : input(input) {
- initializePredecessors();
+ShapeCanonicalizer::ShapeCanonicalizer(std::vector<HeapType>& roots) {
+#if TRACE_CANONICALIZATION
+ std::cerr << "Root HeapTypes:\n";
+ for (auto root : roots) {
+ std::cerr << root << '\n';
+ }
+ std::cerr << '\n';
+#endif
+
+ initializePredecessors(roots);
initializePartitions();
#if TRACE_CANONICALIZATION
@@ -2120,7 +2344,8 @@ ShapeCanonicalizer::ShapeCanonicalizer(const std::vector<HeapType>& input)
// Choose a partition that might be able to distinguish between the members
// of some other partition.
auto distinguishingIndexIt = distinguishers.begin();
- TypeSet distinguishing = partitions[*distinguishingIndexIt];
+ size_t distinguishingIndex = *distinguishingIndexIt;
+ TypeSet distinguishing = partitions[distinguishingIndex];
distinguishers.erase(distinguishingIndexIt);
// For each possibly distinguishing transition symbol...
for (size_t symbol = 0; symbol < alphabetSize; ++symbol) {
@@ -2146,6 +2371,13 @@ ShapeCanonicalizer::ShapeCanonicalizer(const std::vector<HeapType>& input)
if (difference.empty()) {
continue;
}
+
+#if TRACE_CANONICALIZATION
+ std::cerr << "Partition " << distinguishingIndex
+ << " distinguishes partition " << distinguishedIndex
+ << " via child " << symbol << "\n";
+#endif
+
// We can split the partition! Replace it with the intersection and add
// the difference as a new partition.
partitions[distinguishedIndex] = std::move(intersection);
@@ -2164,11 +2396,15 @@ ShapeCanonicalizer::ShapeCanonicalizer(const std::vector<HeapType>& input)
} else {
distinguishers.insert(distinguishedIndex);
}
+
+#if TRACE_CANONICALIZATION
+ dumpPartitions();
+#endif
}
}
}
-#if TRACE_PARTITIONS
+#if TRACE_CANONICALIZATION
std::cerr << "Final partitions:\n";
dumpPartitions();
#endif
@@ -2176,27 +2412,46 @@ ShapeCanonicalizer::ShapeCanonicalizer(const std::vector<HeapType>& input)
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::initializePredecessors(std::vector<HeapType>& roots) {
+ struct Walker : HeapTypeGraphWalker<Walker> {
+ ShapeCanonicalizer& canonicalizer;
+ Walker(ShapeCanonicalizer& canonicalizer) : canonicalizer(canonicalizer) {}
+ void noteHeapType(HeapType ht) {
+ if (ht.isBasic()) {
+ return;
+ }
+ // Ensure each compound HeapType gets an entry even if it has no
+ // predecessors.
+ canonicalizer.preds.insert({ht, {}});
+ size_t index = 0;
+ for (HeapType* child : canonicalizer.getChildren(ht)) {
+ // Skip children that represent basic HeapTypes.
+ if (getHeapTypeInfo(*child)->kind == HeapTypeInfo::BasicKind) {
+ continue;
+ }
+ canonicalizer.preds[*child][index++].insert(ht);
+ }
+ canonicalizer.alphabetSize = std::max(canonicalizer.alphabetSize, index);
}
+ };
+ Walker walker(*this);
+ for (HeapType& root : roots) {
+ walker.walkRoot(&root);
}
}
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
+ // Create the initial partitions based on the top-level shape of the 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) {
+ for (auto& pair : preds) {
+ HeapType type = pair.first;
ShallowHeapType shallow(type);
auto inserted = initialIndices.insert({shallow, partitions.size()});
if (inserted.second) {
@@ -2220,85 +2475,80 @@ void ShapeCanonicalizer::translatePartitionsToTypes() {
// 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.
+ //
+ // However, for partitions that already contain globally canonical types, find
+ // and use the corresponding HeapTypeInfo directly without copying. Since the
+ // partitions reachable from a globally canonical type will also contain a
+ // globally canonical type, no temporary types will end up being patched into
+ // the globally canonical types and we can skip patching the children of those
+ // types.
for (auto& partition : partitions) {
- const auto& representative = *getHeapTypeInfo(*partition.begin());
- infos.push_back(std::make_unique<HeapTypeInfo>(representative));
- infos.back()->isTemp = true;
+ auto it = std::find_if(partition.begin(), partition.end(), [](HeapType ht) {
+ return !isTemp(ht);
+ });
+ if (it == partition.end()) {
+ // We do not already know about a globally canonical type for this
+ // partition. Create a copy.
+ const auto& representative = *getHeapTypeInfo(*partition.begin());
+ infos.push_back(std::make_unique<HeapTypeInfo>(representative));
+ infos.back()->isTemp = true;
+ results.push_back(asHeapType(infos.back()));
+ } else {
+ // We already have a globally canonical type for this partition.
+ results.push_back(*it);
+ infos.push_back({});
+ }
}
for (auto& info : infos) {
- for (auto* child : getChildren(asHeapType(info), true)) {
+ if (!info) {
+ // No need to replace the children of globally canonical HeapTypes.
+ continue;
+ }
+ 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));
+ *child = results.at(partitionIt->second);
}
}
-}
-
-std::vector<HeapType*> ShapeCanonicalizer::getChildren(HeapType heapType,
- bool includeBasic) {
- std::vector<HeapType*> children;
- auto noteChild = [&](HeapType* child) {
- HeapType type = *child;
- if (!includeBasic) {
- type = asCanonical(type);
- }
- if (!type.isBasic()) {
- children.push_back(child);
- }
- };
+#if TRACE_CANONICALIZATION
+ std::cerr << "Minimization results:\n";
+ for (HeapType ht : results) {
+ std::cerr << ht << '\n';
+ }
+ std::cerr << '\n';
+#endif
+}
- // Scan through Types to find the next HeapType.
- std::function<void(Type)> scanType = [&](Type type) {
- if (type.isBasic()) {
- return;
+std::vector<HeapType*> ShapeCanonicalizer::getChildren(HeapType ht) {
+ struct Walker : HeapTypePathWalker<Walker> {
+ std::vector<HeapType*> children;
+ bool topLevel = true;
+ void noteChild(HeapType, HeapType* child) {
+ if (!child->isBasic()) {
+ children.push_back(child);
+ }
}
- 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);
+ void scanHeapType(HeapType* ht) {
+ if (topLevel) {
+ HeapTypePathWalker<Walker>::scanHeapType(ht);
+ topLevel = false;
+ }
}
- WASM_UNREACHABLE("unexpected kind");
};
-
- assert(!heapType.isBasic() && "Cannot have basic defined HeapType");
- auto* info = getHeapTypeInfo(heapType);
- switch (info->kind) {
- case HeapTypeInfo::BasicKind:
- return children;
- 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");
+ Walker walker;
+ walker.walkRoot(&ht);
+ return walker.children;
}
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;
- }
+ assert(predsIt != preds.end());
auto& predsOfType = predsIt->second;
auto specificPredsIt = predsOfType.find(symbol);
if (specificPredsIt == predsOfType.end()) {
@@ -2334,51 +2584,43 @@ ShapeCanonicalizer::getDifference(const TypeSet& a, const TypeSet& b) {
// 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>
+globallyCanonicalize(std::vector<std::unique_ptr<HeapTypeInfo>>& infos) {
+
+ // Map each temporary Type and HeapType to the locations where they will
+ // have to be replaced with canonical Types and HeapTypes.
+ struct Locations : TypeGraphWalker<Locations> {
+ std::unordered_map<Type, std::unordered_set<Type*>> types;
+ std::unordered_map<HeapType, std::unordered_set<HeapType*>> heapTypes;
+
+ void preVisitType(Type* type) {
+ if (!type->isBasic()) {
+ types[*type].insert(type);
+ }
+ }
+ void preVisitHeapType(HeapType* ht) {
+ if (!ht->isBasic()) {
+ heapTypes[*ht].insert(ht);
+ }
+ }
+ } locations;
std::vector<HeapType> results;
- GlobalCanonicalizer(std::vector<std::unique_ptr<HeapTypeInfo>>& infos);
-
-private:
- struct Item {
- enum Kind {
- TypeKind,
- HeapTypeKind,
- } kind;
- union {
- Type* type;
- HeapType* heapType;
- };
- Item(Type* type) : kind(TypeKind), type(type) {}
- Item(HeapType* heapType) : kind(HeapTypeKind), heapType(heapType) {}
- };
-
- // IDs of scanned Types and HeapTypes, used to prevent repeated scanning.
- std::unordered_set<TypeID> scanned;
-
- // The work list of Types and HeapTypes remaining to be scanned.
- std::vector<Item> scanList;
-
- // 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;
-
- template<typename T1, typename T2> void noteChild(T1 parent, T2* child);
- void scanHeapType(HeapType* ht);
- void scanType(Type* child);
-};
-
-// 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) {
+ if (!info) {
+ // TODO: That we have to deal with null info pointers here is a sign of a
+ // very leaky abstraction. Hack around it by for now to keep the diff for
+ // this change easier to reason about, but fix this in a followup to make
+ // the code itself easier to reason about.
+
+ // Produce an arbitrary HeapType that will not be used.
+ results.push_back(HeapType(0));
+ continue;
+ }
+
results.push_back(asHeapType(info));
- scanList.push_back(&results.back());
+ locations.walkRoot(&results.back());
}
#if TRACE_CANONICALIZATION
@@ -2389,21 +2631,6 @@ GlobalCanonicalizer::GlobalCanonicalizer(
std::cerr << '\n';
#endif
- // 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();
- switch (item.kind) {
- case Item::TypeKind:
- scanType(item.type);
- break;
- case Item::HeapTypeKind:
- scanHeapType(item.heapType);
- 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
@@ -2414,6 +2641,9 @@ GlobalCanonicalizer::GlobalCanonicalizer(
// non-canonical Types.
std::unordered_map<HeapType, HeapType> canonicalHeapTypes;
for (auto& info : infos) {
+ if (!info) {
+ continue;
+ }
HeapType original = asHeapType(info);
HeapType canonical = globalHeapTypeStore.canonicalize(std::move(info));
if (original != canonical) {
@@ -2423,14 +2653,15 @@ GlobalCanonicalizer::GlobalCanonicalizer(
for (auto& pair : canonicalHeapTypes) {
HeapType original = pair.first;
HeapType canonical = pair.second;
- for (HeapType* use : heapTypeLocations.at(original)) {
+ for (HeapType* use : locations.heapTypes.at(original)) {
*use = canonical;
}
}
+
auto canonicalizeTypes = [&](bool tuples) {
- for (auto& pair : typeLocations) {
+ for (auto& pair : locations.types) {
Type original = pair.first;
- std::vector<Type*>& uses = pair.second;
+ auto& uses = pair.second;
if (original.isTuple() == tuples) {
Type canonical = globalTypeStore.canonicalize(*getTypeInfo(original));
for (Type* use : uses) {
@@ -2449,64 +2680,8 @@ GlobalCanonicalizer::GlobalCanonicalizer(
}
std::cerr << '\n';
#endif
-}
-template<typename T1, typename T2>
-void GlobalCanonicalizer::noteChild(T1 parent, T2* child) {
- if (child->isCompound()) {
- scanList.push_back(child);
- }
-}
-
-void GlobalCanonicalizer::scanHeapType(HeapType* ht) {
- assert(ht->isCompound());
- heapTypeLocations[*ht].push_back(ht);
- if (scanned.count(ht->getID())) {
- return;
- }
- scanned.insert(ht->getID());
-
- auto* info = getHeapTypeInfo(*ht);
- switch (info->kind) {
- case HeapTypeInfo::BasicKind:
- break;
- case HeapTypeInfo::SignatureKind:
- noteChild(*ht, &info->signature.params);
- noteChild(*ht, &info->signature.results);
- break;
- case HeapTypeInfo::StructKind:
- for (auto& field : info->struct_.fields) {
- noteChild(*ht, &field.type);
- }
- break;
- case HeapTypeInfo::ArrayKind:
- noteChild(*ht, &info->array.element.type);
- break;
- }
-};
-
-void GlobalCanonicalizer::scanType(Type* type) {
- assert(type->isCompound());
- typeLocations[*type].push_back(type);
- if (scanned.count(type->getID())) {
- return;
- }
- scanned.insert(type->getID());
-
- auto* info = getTypeInfo(*type);
- switch (info->kind) {
- case TypeInfo::TupleKind:
- for (auto& child : info->tuple.types) {
- noteChild(*type, &child);
- }
- break;
- case TypeInfo::RefKind:
- noteChild(*type, &info->ref.heapType);
- break;
- case TypeInfo::RttKind:
- noteChild(*type, &info->rtt.heapType);
- break;
- }
+ return results;
}
} // anonymous namespace
@@ -2525,12 +2700,18 @@ std::vector<HeapType> TypeBuilder::build() {
// 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);
+ std::vector<HeapType> canonical = globallyCanonicalize(minimized.infos);
// Map the original heap types to their minimized and globally canonical
// versions.
for (auto& type : heapTypes) {
- type = globallyCanonical.results[minimized.partitionIndices[type]];
+ size_t index = minimized.partitionIndices.at(type);
+ // TODO: This is messy. Clean it up.
+ if (minimized.infos.at(index)) {
+ type = canonical.at(index);
+ } else {
+ type = minimized.results.at(index);
+ }
}
return heapTypes;
diff --git a/test/lit/lub-bug-3843.wast b/test/lit/lub-bug-3843.wast
new file mode 100644
index 000000000..849134d7a
--- /dev/null
+++ b/test/lit/lub-bug-3843.wast
@@ -0,0 +1,31 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s -all --precompute -S -o - | filecheck %s
+
+;; Regression test for a bug (#3843) in which the LUB calculation done during
+;; the refinalization of the select incorrectly produced a new type rather than
+;; returning (ref null $A).
+
+(module
+ (type $A (struct (field (ref null $C))))
+ (type $B (struct (field (ref null $D))))
+ (type $C (struct (field (mut (ref $A)))))
+ (type $D (struct (field (mut (ref $A))) (field (mut (ref $A)))))
+
+ ;; CHECK: (func $foo (param $a (ref null $A)) (result (ref null $A))
+ ;; CHECK-NEXT: (select (result (ref null $A))
+ ;; CHECK-NEXT: (local.get $a)
+ ;; CHECK-NEXT: (ref.null $B)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $foo (param $a (ref null $A)) (result (ref null $A))
+ ;; the select should have type $A
+ (select (result (ref null $A))
+ ;; one arm has type $A
+ (local.get $a)
+ ;; one arm has type $B (a subtype of $A)
+ (ref.null $B)
+ (i32.const 0)
+ )
+ )
+)