Skip to content

Commit 1a2d26f

Browse files
authored
Replace the old topological sort everywhere (WebAssembly#6902)
To avoid having two separate topological sort utilities in the code base, replace remaining uses of the old DFS-based, CRTP topological sort with the newer Kahn's algorithm implementation. This would be NFC, except that the new topological sort produces a different order than the old topological sort, so the output of some passes is reordered.
1 parent b0c955d commit 1a2d26f

14 files changed

+245
-353
lines changed

src/ir/subtypes.h

+12-22
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#define wasm_ir_subtypes_h
1919

2020
#include "ir/module-utils.h"
21-
#include "support/old_topological_sort.h"
21+
#include "support/topological_sort.h"
2222
#include "wasm.h"
2323

2424
namespace wasm {
@@ -79,29 +79,19 @@ struct SubTypes {
7979
}
8080

8181
// A topological sort that visits subtypes first.
82-
auto getSubTypesFirstSort() const {
83-
struct SubTypesFirstSort : OldTopologicalSort<HeapType, SubTypesFirstSort> {
84-
const SubTypes& parent;
85-
86-
SubTypesFirstSort(const SubTypes& parent) : parent(parent) {
87-
for (auto type : parent.types) {
88-
// The roots are types with no supertype.
89-
if (!type.getDeclaredSuperType()) {
90-
push(type);
91-
}
92-
}
93-
}
94-
95-
void pushPredecessors(HeapType type) {
96-
// Things we need to process before each type are its subtypes. Once we
97-
// know their depth, we can easily compute our own.
98-
for (auto pred : parent.getImmediateSubTypes(type)) {
99-
push(pred);
82+
std::vector<HeapType> getSubTypesFirstSort() const {
83+
std::vector<std::pair<HeapType, std::vector<HeapType>>> graph;
84+
graph.reserve(types.size());
85+
for (auto type : types) {
86+
if (auto it = typeSubTypes.find(type); it != typeSubTypes.end()) {
87+
graph.emplace_back(*it);
88+
} else {
89+
graph.emplace_back(type, std::vector<HeapType>{});
10090
}
10191
}
102-
};
103-
104-
return SubTypesFirstSort(*this);
92+
auto sorted = TopologicalSort::sortOf(graph.begin(), graph.end());
93+
std::reverse(sorted.begin(), sorted.end());
94+
return sorted;
10595
}
10696

10797
// Computes the depth of children for each type. This is 0 if the type has no

src/ir/type-updating.cpp

+2-48
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,6 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes(
4040
// world scenario, don't modify public types because we assume that they may
4141
// be reflected on or used for linking. Figure out where each private type
4242
// will be located in the builder.
43-
//
44-
// There are two code paths here: a new one that is used when there are type
45-
// indices to preserve and an old one that is used otherwise. The old code
46-
// path is kept around to avoid unnecessary changes to test outputs while we
47-
// incrementally add --preserve-type-order to tests that could benefit from
48-
// it. Once we are done adding --preserve-type-order to tests, we should
49-
// remove the old code path here since the new code path is strictly better.
50-
if (wasm.typeIndices.size()) {
51-
// New code path, currently used only with --preserve-type-order.
5243
auto typeInfo = ModuleUtils::collectHeapTypeInfo(
5344
wasm,
5445
ModuleUtils::TypeInclusion::UsedIRTypes,
@@ -57,8 +48,7 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes(
5748
std::unordered_set<HeapType> additionalSet(additionalPrivateTypes.begin(),
5849
additionalPrivateTypes.end());
5950

60-
std::vector<std::pair<HeapType, SmallVector<HeapType, 1>>>
61-
privateSupertypes;
51+
std::vector<std::pair<HeapType, SmallVector<HeapType, 1>>> privateSupertypes;
6252
privateSupertypes.reserve(typeInfo.size());
6353
for (auto& [type, info] : typeInfo) {
6454
if (info.visibility != ModuleUtils::Visibility::Private &&
@@ -117,42 +107,6 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes(
117107
for (auto type : sorted) {
118108
typeIndices[type] = i++;
119109
}
120-
} else {
121-
// Old code path.
122-
123-
auto privateTypes = ModuleUtils::getPrivateHeapTypes(wasm);
124-
125-
if (!additionalPrivateTypes.empty()) {
126-
// Only add additional private types that are not already in the list.
127-
std::unordered_set<HeapType> privateTypesSet(privateTypes.begin(),
128-
privateTypes.end());
129-
130-
for (auto t : additionalPrivateTypes) {
131-
if (!privateTypesSet.count(t)) {
132-
privateTypes.push_back(t);
133-
privateTypesSet.insert(t);
134-
}
135-
}
136-
}
137-
138-
// Topological sort to have supertypes first, but we have to account for the
139-
// fact that we may be replacing the supertypes to get the order correct.
140-
struct SupertypesFirst
141-
: HeapTypeOrdering::SupertypesFirstBase<SupertypesFirst> {
142-
GlobalTypeRewriter& parent;
143-
144-
SupertypesFirst(GlobalTypeRewriter& parent) : parent(parent) {}
145-
std::optional<HeapType> getDeclaredSuperType(HeapType type) {
146-
return parent.getDeclaredSuperType(type);
147-
}
148-
};
149-
150-
SupertypesFirst sortedTypes(*this);
151-
Index i = 0;
152-
for (auto type : sortedTypes.sort(privateTypes)) {
153-
typeIndices[type] = i++;
154-
}
155-
}
156110

157111
if (typeIndices.size() == 0) {
158112
return {};
@@ -168,7 +122,7 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes(
168122
typeBuilder.createRecGroup(0, typeBuilder.size());
169123

170124
// Create the temporary heap types.
171-
Index i = 0;
125+
i = 0;
172126
auto map = [&](HeapType type) -> HeapType {
173127
if (auto it = typeIndices.find(type); it != typeIndices.end()) {
174128
return typeBuilder[it->second];

src/passes/GlobalTypeOptimization.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ struct GlobalTypeOptimization : public Pass {
171171
// fields in a supertype is a constraint on what subtypes can do. That is,
172172
// we decide for each supertype what the optimal order is, and consider that
173173
// fixed, and then subtypes can decide how to sort fields that they append.
174-
HeapTypeOrdering::SupertypesFirst sorted;
175-
for (auto type : sorted.sort(propagator.subTypes.types)) {
174+
for (auto type :
175+
HeapTypeOrdering::supertypesFirst(propagator.subTypes.types)) {
176176
if (!type.isStruct()) {
177177
continue;
178178
}

src/passes/TypeMerging.cpp

+14-19
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,17 @@ struct TypeMerging : public Pass {
142142
return type;
143143
}
144144

145+
std::vector<HeapType>
146+
mergeableSupertypesFirst(const std::vector<HeapType>& types) {
147+
return HeapTypeOrdering::supertypesFirst(
148+
types, [&](HeapType type) -> std::optional<HeapType> {
149+
if (auto super = type.getDeclaredSuperType()) {
150+
return getMerged(*super);
151+
}
152+
return std::nullopt;
153+
});
154+
}
155+
145156
void run(Module* module_) override;
146157

147158
// We will do two different kinds of merging: First, we will merge types into
@@ -163,20 +174,6 @@ struct TypeMerging : public Pass {
163174
void applyMerges();
164175
};
165176

166-
struct MergeableSupertypesFirst
167-
: HeapTypeOrdering::SupertypesFirstBase<MergeableSupertypesFirst> {
168-
TypeMerging& merging;
169-
170-
MergeableSupertypesFirst(TypeMerging& merging) : merging(merging) {}
171-
172-
std::optional<HeapType> getDeclaredSuperType(HeapType type) {
173-
if (auto super = type.getDeclaredSuperType()) {
174-
return merging.getMerged(*super);
175-
}
176-
return std::nullopt;
177-
}
178-
};
179-
180177
// Hash and equality-compare HeapTypes based on their top-level structure (i.e.
181178
// "shape"), ignoring nontrivial heap type children that will not be
182179
// differentiated between until we run the DFA partition refinement.
@@ -305,8 +302,7 @@ bool TypeMerging::merge(MergeKind kind) {
305302

306303
// For each type, either create a new partition or add to its supertype's
307304
// partition.
308-
MergeableSupertypesFirst sortedTypes(*this);
309-
for (auto type : sortedTypes.sort(mergeable)) {
305+
for (auto type : mergeableSupertypesFirst(mergeable)) {
310306
// We need partitions for any public children of this type since those
311307
// children will participate in the DFA we're creating.
312308
for (auto child : getPublicChildren(type)) {
@@ -414,7 +410,7 @@ bool TypeMerging::merge(MergeKind kind) {
414410
std::vector<HeapType> newMergeable;
415411
bool merged = false;
416412
for (const auto& partition : refinedPartitions) {
417-
auto target = *MergeableSupertypesFirst(*this).sort(partition).begin();
413+
auto target = mergeableSupertypesFirst(partition).front();
418414
newMergeable.push_back(target);
419415
for (auto type : partition) {
420416
if (type != target) {
@@ -452,8 +448,7 @@ TypeMerging::splitSupertypePartition(const std::vector<HeapType>& types) {
452448
std::unordered_set<HeapType> includedTypes(types.begin(), types.end());
453449
std::vector<std::vector<HeapType>> partitions;
454450
std::unordered_map<HeapType, Index> partitionIndices;
455-
MergeableSupertypesFirst sortedTypes(*this);
456-
for (auto type : sortedTypes.sort(types)) {
451+
for (auto type : mergeableSupertypesFirst(types)) {
457452
auto super = type.getDeclaredSuperType();
458453
if (super && includedTypes.count(*super)) {
459454
// We must already have a partition for the supertype we can add to.

src/tools/wasm-ctor-eval.cpp

+9-27
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@
3636
#include "support/colors.h"
3737
#include "support/file.h"
3838
#include "support/insert_ordered.h"
39-
#include "support/old_topological_sort.h"
4039
#include "support/small_set.h"
4140
#include "support/string.h"
41+
#include "support/topological_sort.h"
4242
#include "tool-options.h"
4343
#include "wasm-builder.h"
4444
#include "wasm-interpreter.h"
@@ -609,9 +609,9 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
609609
Builder builder(*wasm);
610610

611611
// First, find what constraints we have on the ordering of the globals. We
612-
// will build up a map of each global to the globals it must be after.
613-
using MustBeAfter = InsertOrderedMap<Name, InsertOrderedSet<Name>>;
614-
MustBeAfter mustBeAfter;
612+
// will build up a map of each global to the globals it must be before.
613+
using MustBeBefore = InsertOrderedMap<Name, InsertOrderedSet<Name>>;
614+
MustBeBefore mustBeBefore;
615615

616616
for (auto& global : wasm->globals) {
617617
if (!global->init) {
@@ -672,31 +672,12 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
672672

673673
// Any global.gets that cannot be fixed up are constraints.
674674
for (auto* get : scanner.unfixableGets) {
675-
mustBeAfter[global->name].insert(get->name);
675+
mustBeBefore[global->name];
676+
mustBeBefore[get->name].insert(global->name);
676677
}
677678
}
678679

679-
if (!mustBeAfter.empty()) {
680-
// We found constraints that require reordering, so do so.
681-
struct MustBeAfterSort : OldTopologicalSort<Name, MustBeAfterSort> {
682-
MustBeAfter& mustBeAfter;
683-
684-
MustBeAfterSort(MustBeAfter& mustBeAfter) : mustBeAfter(mustBeAfter) {
685-
for (auto& [global, _] : mustBeAfter) {
686-
push(global);
687-
}
688-
}
689-
690-
void pushPredecessors(Name global) {
691-
auto iter = mustBeAfter.find(global);
692-
if (iter != mustBeAfter.end()) {
693-
for (auto other : iter->second) {
694-
push(other);
695-
}
696-
}
697-
}
698-
};
699-
680+
if (!mustBeBefore.empty()) {
700681
auto oldGlobals = std::move(wasm->globals);
701682
// After clearing the globals vector, clear the map as well.
702683
wasm->updateMaps();
@@ -706,7 +687,8 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
706687
globalIndexes[oldGlobals[i]->name] = i;
707688
}
708689
// Add the globals that had an important ordering, in the right order.
709-
for (auto global : MustBeAfterSort(mustBeAfter)) {
690+
for (auto global :
691+
TopologicalSort::sortOf(mustBeBefore.begin(), mustBeBefore.end())) {
710692
wasm->addGlobal(std::move(oldGlobals[globalIndexes[global]]));
711693
}
712694
// Add all other globals after them.

src/wasm-type-ordering.h

+16-40
Original file line numberDiff line numberDiff line change
@@ -20,58 +20,34 @@
2020
#include <unordered_set>
2121

2222
#include "support/insert_ordered.h"
23-
#include "support/old_topological_sort.h"
23+
#include "support/topological_sort.h"
2424
#include "wasm-type.h"
2525

2626
namespace wasm::HeapTypeOrdering {
2727

28-
// Given a collection of types, iterate through it such that each type in the
29-
// collection is visited only after its immediate supertype in the collection is
30-
// visited.
31-
template<typename SupertypeProvider>
32-
struct SupertypesFirstBase
33-
: OldTopologicalSort<HeapType, SupertypesFirstBase<SupertypeProvider>> {
34-
// For each type in the input collection, whether it is a supertype. Used to
35-
// track membership in the input collection.
36-
InsertOrderedMap<HeapType, bool> typeSet;
28+
// Given a collection of types, return a sequence of the types such that each
29+
// type in the sequence comes only after its immediate supertype in the
30+
// collection is visited.
31+
template<typename T>
32+
std::vector<HeapType> supertypesFirst(
33+
const T& types,
34+
std::function<std::optional<HeapType>(HeapType)> getSuper =
35+
[](HeapType type) { return type.getDeclaredSuperType(); }) {
3736

38-
SupertypeProvider& self() { return *static_cast<SupertypeProvider*>(this); }
39-
40-
template<typename T> SupertypeProvider& sort(const T& types) {
37+
InsertOrderedMap<HeapType, std::vector<HeapType>> subtypes;
4138
for (auto type : types) {
42-
typeSet[type] = false;
39+
subtypes.insert({type, {}});
4340
}
4441
// Find the supertypes that are in the collection.
45-
for (auto [type, _] : typeSet) {
46-
if (auto super = self().getDeclaredSuperType(type)) {
47-
if (auto it = typeSet.find(*super); it != typeSet.end()) {
48-
it->second = true;
49-
}
50-
}
51-
}
52-
// Types that are not supertypes of others are the roots.
53-
for (auto [type, isSuper] : typeSet) {
54-
if (!isSuper) {
55-
this->push(type);
42+
for (auto [type, _] : subtypes) {
43+
if (auto super = getSuper(type)) {
44+
if (auto it = subtypes.find(*super); it != subtypes.end()) {
45+
it->second.push_back(type);
5646
}
5747
}
58-
return self();
5948
}
60-
61-
void pushPredecessors(HeapType type) {
62-
// Do not visit types that weren't in the input collection.
63-
if (auto super = self().getDeclaredSuperType(type);
64-
super && typeSet.count(*super)) {
65-
this->push(*super);
66-
}
67-
}
68-
};
69-
70-
struct SupertypesFirst : SupertypesFirstBase<SupertypesFirst> {
71-
std::optional<HeapType> getDeclaredSuperType(HeapType type) {
72-
return type.getDeclaredSuperType();
49+
return TopologicalSort::sortOf(subtypes.begin(), subtypes.end());
7350
}
74-
};
7551

7652
} // namespace wasm::HeapTypeOrdering
7753

test/ctor-eval/gc-2.wast.out

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
(module
22
(type $struct (sub (struct (field i32))))
33
(type $1 (func (result i32)))
4-
(global $ctor-eval$global_4 (ref $struct) (struct.new $struct
5-
(i32.const 9999)
6-
))
7-
(global $global3 (ref $struct) (global.get $ctor-eval$global_4))
8-
(global $global2 (mut (ref null $struct)) (global.get $ctor-eval$global_4))
94
(global $ctor-eval$global (ref $struct) (struct.new $struct
105
(i32.const 1337)
116
))
7+
(global $ctor-eval$global_4 (ref $struct) (struct.new $struct
8+
(i32.const 9999)
9+
))
1210
(global $global1 (ref any) (global.get $ctor-eval$global))
11+
(global $global2 (mut (ref null $struct)) (global.get $ctor-eval$global_4))
12+
(global $global3 (ref $struct) (global.get $ctor-eval$global_4))
1313
(export "keepalive" (func $keepalive))
1414
(func $keepalive (type $1) (result i32)
1515
(select

0 commit comments

Comments
 (0)