Skip to content

Commit de315aa

Browse files
authored
Add a ReorderTypes pass (#7879)
The binary writer already takes care to order rec groups to minimize the size taken to encode type indices throughout the module, but it cannot order types within a rec group because that would change their identity. MinimizeRecGroups can be used to split the type section into small recursion groups that can be ordered by the binary writer, but that pass does not optimize the order of types within a rec group. Furthermore, if the minimal early rec groups are large, all the extra types taking up valuable index space can cause a large code size overhead. To get the best possible outcome, add a ReorderTypes pass that creates a single large rec group, giving the types the most possible flexibility to be moved around, and orders the types within it. As we do in ReorderGlobals, try multiple tolological sorts with different factors for adjusting weights based on successor types, then choose the best ordering. Refactoring so that ReorderGlobals and ReorderTypes can share more code is left as future work. To avoid reimplementing all of the GlobalTypeRewriter utility, which already rewrites all the private types into a single rec group, factor out just the part that sorts the types and make it overrideable in subclasses.
1 parent 09838cb commit de315aa

14 files changed

Lines changed: 1343 additions & 23 deletions

src/ir/type-updating.cpp

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
#include "ir/names.h"
2222
#include "ir/utils.h"
2323
#include "support/topological_sort.h"
24-
#include "wasm-type-ordering.h"
2524
#include "wasm-type.h"
2625
#include "wasm.h"
2726

@@ -31,10 +30,11 @@ GlobalTypeRewriter::GlobalTypeRewriter(Module& wasm) : wasm(wasm) {}
3130

3231
void GlobalTypeRewriter::update(
3332
const std::vector<HeapType>& additionalPrivateTypes) {
34-
mapTypes(rebuildTypes(additionalPrivateTypes));
33+
mapTypes(rebuildTypes(
34+
getSortedTypes(getPrivatePredecessors(additionalPrivateTypes))));
3535
}
3636

37-
GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes(
37+
GlobalTypeRewriter::PredecessorGraph GlobalTypeRewriter::getPrivatePredecessors(
3838
const std::vector<HeapType>& additionalPrivateTypes) {
3939
// Find the heap types that are not publicly observable. Even in a closed
4040
// world scenario, don't modify public types because we assume that they may
@@ -66,36 +66,41 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes(
6666

6767
// For each type, note all the predecessors it must have, i.e., that must
6868
// appear before it. That includes supertypes and described types.
69-
std::vector<std::pair<HeapType, SmallVector<HeapType, 1>>> privatePreds;
70-
privatePreds.reserve(typeInfo.size());
69+
std::vector<std::pair<HeapType, SmallVector<HeapType, 1>>> preds;
70+
preds.reserve(typeInfo.size());
7171
for (auto& [type, info] : typeInfo) {
7272
if (isPublicGivenInfo(type, info)) {
7373
continue;
7474
}
75-
privatePreds.push_back({type, {}});
75+
preds.push_back({type, {}});
7676

7777
// Check for a (private) supertype.
7878
if (auto super = getDeclaredSuperType(type); super && !isPublic(*super)) {
79-
privatePreds.back().second.push_back(*super);
79+
preds.back().second.push_back(*super);
8080
}
8181

8282
// Check for a (private) described type.
8383
if (auto desc = type.getDescribedType()) {
8484
// It is not possible for a a described type to be public while its
8585
// descriptor is private, or vice versa.
8686
assert(!isPublic(*desc));
87-
privatePreds.back().second.push_back(*desc);
87+
preds.back().second.push_back(*desc);
8888
}
8989
}
9090

91+
return preds;
92+
}
93+
94+
std::vector<HeapType>
95+
GlobalTypeRewriter::getSortedTypes(PredecessorGraph preds) {
9196
std::vector<HeapType> sorted;
9297
if (wasm.typeIndices.empty()) {
93-
sorted = TopologicalSort::sortOf(privatePreds.begin(), privatePreds.end());
98+
sorted = TopologicalSort::sortOf(preds.begin(), preds.end());
9499
} else {
95100
sorted = TopologicalSort::minSortOf(
96-
privatePreds.begin(), privatePreds.end(), [&](Index a, Index b) {
97-
auto typeA = privatePreds[a].first;
98-
auto typeB = privatePreds[b].first;
101+
preds.begin(), preds.end(), [&](Index a, Index b) {
102+
auto typeA = preds[a].first;
103+
auto typeB = preds[b].first;
99104
// Preserve type order.
100105
auto itA = wasm.typeIndices.find(typeA);
101106
auto itB = wasm.typeIndices.find(typeB);
@@ -117,8 +122,13 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes(
117122
});
118123
}
119124
std::reverse(sorted.begin(), sorted.end());
125+
return sorted;
126+
}
127+
128+
GlobalTypeRewriter::TypeMap
129+
GlobalTypeRewriter::rebuildTypes(std::vector<HeapType> types) {
120130
Index i = 0;
121-
for (auto type : sorted) {
131+
for (auto type : types) {
122132
typeIndices[type] = i++;
123133
}
124134

@@ -128,11 +138,11 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes(
128138

129139
typeBuilder.grow(typeIndices.size());
130140

131-
// All the input types are distinct, so we need to make sure the output types
132-
// are distinct as well. Further, the new types may have more recursions than
133-
// the original types, so the old recursion groups may not be sufficient any
134-
// more. Both of these problems are solved by putting all the new types into a
135-
// single large recursion group.
141+
// All the input types are distinct, so we need to make sure the output
142+
// types are distinct as well. Further, the new types may have more
143+
// recursions than the original types, so the old recursion groups may not
144+
// be sufficient any more. Both of these problems are solved by putting all
145+
// the new types into a single large recursion group.
136146
typeBuilder.createRecGroup(0, typeBuilder.size());
137147

138148
// Create the temporary heap types.

src/ir/type-updating.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
#define wasm_ir_type_updating_h
1919

2020
#include "ir/branch-utils.h"
21-
#include "ir/module-utils.h"
2221
#include "support/insert_ordered.h"
2322
#include "wasm-traversal.h"
2423

@@ -344,6 +343,9 @@ struct TypeUpdater
344343
// made while doing so.
345344
class GlobalTypeRewriter {
346345
public:
346+
using PredecessorGraph =
347+
std::vector<std::pair<HeapType, SmallVector<HeapType, 1>>>;
348+
347349
Module& wasm;
348350

349351
GlobalTypeRewriter(Module& wasm);
@@ -375,6 +377,12 @@ class GlobalTypeRewriter {
375377
// mapping for recorded type indices.
376378
void mapTypeNamesAndIndices(const TypeMap& oldToNewTypes);
377379

380+
// Given the predecessor graph of the types to be rebuilt, return a list of
381+
// the types in the order in which they will be rebuilt. Users can override
382+
// this to inject placeholders for extra types or use custom logic to sort the
383+
// types.
384+
virtual std::vector<HeapType> getSortedTypes(PredecessorGraph preds);
385+
378386
// Subclasses can implement these methods to modify the new set of types that
379387
// we map to. By default, we simply copy over the types, and these functions
380388
// are the hooks to apply changes through. The methods receive as input the
@@ -441,13 +449,16 @@ class GlobalTypeRewriter {
441449
}
442450

443451
protected:
452+
// Return the graph matching each private type to its private predecessors.
453+
PredecessorGraph getPrivatePredecessors(
454+
const std::vector<HeapType>& additionalPrivateTypes = {});
455+
444456
// Builds new types after updating their contents using the hooks below and
445457
// returns a map from the old types to the modified types. Used internally in
446458
// update().
447459
//
448460
// See above regarding private types.
449-
TypeMap
450-
rebuildTypes(const std::vector<HeapType>& additionalPrivateTypes = {});
461+
TypeMap rebuildTypes(std::vector<HeapType> types);
451462

452463
private:
453464
TypeBuilder typeBuilder;
@@ -472,7 +483,8 @@ class TypeMapper : public GlobalTypeRewriter {
472483
void map(const std::vector<HeapType>& additionalPrivateTypes = {}) {
473484
// Update the internals of types (struct fields, signatures, etc.) to
474485
// refer to the merged types.
475-
auto newMapping = rebuildTypes(additionalPrivateTypes);
486+
auto newMapping = rebuildTypes(
487+
getSortedTypes(getPrivatePredecessors(additionalPrivateTypes)));
476488

477489
// Compose the user-provided mapping from old types to other old types with
478490
// the new mapping from old types to new types. `newMapping` will become

src/passes/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ set(passes_SOURCES
114114
ReorderFunctions.cpp
115115
ReorderGlobals.cpp
116116
ReorderLocals.cpp
117+
ReorderTypes.cpp
117118
ReReloop.cpp
118119
TrapMode.cpp
119120
TypeGeneralizing.cpp

src/passes/RemoveUnusedTypes.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
// optimize.
2121
//
2222

23-
#include "ir/module-utils.h"
2423
#include "ir/type-updating.h"
2524
#include "pass.h"
2625

src/passes/ReorderTypes.cpp

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright 2025 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//
18+
// Reorder private types within a single large recursion group to minimize the
19+
// cumulative size of type indices throughout the module.
20+
//
21+
22+
#include "ir/module-utils.h"
23+
#include "ir/type-updating.h"
24+
#include "pass.h"
25+
#include "support/insert_ordered.h"
26+
#include "support/topological_sort.h"
27+
#include "wasm-type.h"
28+
#include "wasm.h"
29+
namespace wasm {
30+
31+
namespace {
32+
33+
struct ReorderingTypeRewriter : GlobalTypeRewriter {
34+
using InfoMap = InsertOrderedMap<HeapType, ModuleUtils::HeapTypeInfo>;
35+
36+
InfoMap& typeInfo;
37+
38+
// Use a simpler cost calculation so the effects can be seen with smaller test
39+
// cases.
40+
bool forTesting;
41+
42+
// Try sorting with several exponential factors applied to the weight
43+
// contribution from successors, then pick the best result.
44+
static constexpr float minFactor = 0.0;
45+
static constexpr float maxFactor = 1.0;
46+
static constexpr Index numFactors = 21;
47+
48+
ReorderingTypeRewriter(Module& wasm, InfoMap& typeInfo, bool forTesting)
49+
: GlobalTypeRewriter(wasm), typeInfo(typeInfo), forTesting(forTesting) {}
50+
51+
std::vector<HeapType> getSortedTypes(PredecessorGraph preds) override {
52+
auto numTypes = preds.size();
53+
std::unordered_map<HeapType, Index> indices;
54+
for (auto& [type, _] : preds) {
55+
indices[type] = indices.size();
56+
}
57+
// We will use raw type indices in the various sorts. Extract an index-only
58+
// successor graph and a map from type index to use count.
59+
TopologicalSort::Graph succs(numTypes);
60+
std::vector<Index> counts;
61+
counts.reserve(numTypes);
62+
for (auto& [type, currPreds] : preds) {
63+
auto it = typeInfo.find(type);
64+
assert(it != typeInfo.end());
65+
counts.push_back(it->second.useCount);
66+
for (auto pred : currPreds) {
67+
succs[indices.at(pred)].push_back(indices.at(type));
68+
}
69+
}
70+
71+
// A successors-first order used to propagate weights from successors to
72+
// predecessors.
73+
auto succsFirst = TopologicalSort::sort(succs);
74+
std::reverse(succsFirst.begin(), succsFirst.end());
75+
76+
// Try each factor in turn, keeping the best results.
77+
std::vector<Index> bestSort;
78+
Index bestCost = 0;
79+
for (Index factorIndex = 0; factorIndex < numFactors; ++factorIndex) {
80+
float factor = getFactor(factorIndex);
81+
82+
// Accumulate weights. Start with the use counts for each type, then add
83+
// the adjusted weight for each successor to the weights of its
84+
// predecessors.
85+
std::vector<float> weights(numTypes);
86+
for (Index i = 0; i < numTypes; ++i) {
87+
weights[i] = counts[i];
88+
}
89+
for (Index pred : succsFirst) {
90+
for (Index succ : succs[pred]) {
91+
weights[pred] += weights[succ] * factor;
92+
}
93+
}
94+
95+
auto sort = TopologicalSort::minSort(
96+
succs, [&](Index a, Index b) { return weights[a] > weights[b]; });
97+
auto cost = getCost(sort, counts);
98+
if (factorIndex == 0 || cost < bestCost) {
99+
bestSort = std::move(sort);
100+
bestCost = cost;
101+
}
102+
}
103+
104+
// Translate the best sort from indices back to types.
105+
std::vector<HeapType> result;
106+
result.reserve(numTypes);
107+
for (Index i = 0; i < numTypes; ++i) {
108+
result.push_back(preds[bestSort[i]].first);
109+
}
110+
return result;
111+
}
112+
113+
float getFactor(Index i) {
114+
return minFactor + (maxFactor - minFactor) * i / (numFactors - 1);
115+
}
116+
117+
Index getCost(const std::vector<Index>& order,
118+
const std::vector<Index> counts) {
119+
// Model the number of usable bits in an LEB byte, but make it much smaller
120+
// for testing.
121+
Index bitsPerByte = forTesting ? 1 : 7;
122+
Index indicesPerByte = 1u << bitsPerByte;
123+
Index cost = 0;
124+
Index numBytes = 1;
125+
Index maxIndex = indicesPerByte;
126+
for (Index i = 0; i < order.size(); ++i) {
127+
if (i == maxIndex) {
128+
++numBytes;
129+
maxIndex *= indicesPerByte;
130+
}
131+
cost += numBytes * counts[order[i]];
132+
}
133+
return cost;
134+
}
135+
};
136+
137+
struct ReorderTypes : Pass {
138+
bool forTesting = false;
139+
140+
ReorderTypes(bool forTesting = false) : forTesting(forTesting) {}
141+
142+
void run(Module* module) override {
143+
if (!module->features.hasGC()) {
144+
// This pass only does anything with GC types.
145+
return;
146+
}
147+
148+
// See note in RemoveUnusedTypes.
149+
if (!getPassOptions().closedWorld) {
150+
Fatal() << "ReorderTypes requires --closed-world";
151+
}
152+
153+
// Collect the use counts for each type.
154+
auto typeInfo = ModuleUtils::collectHeapTypeInfo(
155+
*module, ModuleUtils::TypeInclusion::BinaryTypes);
156+
157+
ReorderingTypeRewriter(*module, typeInfo, forTesting).update();
158+
}
159+
};
160+
161+
} // anonymous namespace
162+
163+
Pass* createReorderTypesPass() { return new ReorderTypes(); }
164+
Pass* createReorderTypesForTestingPass() { return new ReorderTypes(true); }
165+
166+
} // namespace wasm

src/passes/Unsubtyping.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <unordered_set>
2424
#endif
2525

26+
#include "ir/module-utils.h"
2627
#include "ir/subtype-exprs.h"
2728
#include "ir/type-updating.h"
2829
#include "ir/utils.h"

src/passes/pass.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,9 @@ void PassRegistry::registerPasses() {
451451
registerPass("reorder-locals",
452452
"sorts locals by access frequency",
453453
createReorderLocalsPass);
454+
registerPass("reorder-types",
455+
"sorts private types by access frequency",
456+
createReorderTypesPass);
454457
registerPass("rereloop",
455458
"re-optimize control flow using the relooper algorithm",
456459
createReReloopPass);
@@ -608,6 +611,10 @@ void PassRegistry::registerPasses() {
608611
registerTestPass("reorder-globals-always",
609612
"sorts globals by access frequency (even if there are few)",
610613
createReorderGlobalsAlwaysPass);
614+
registerTestPass(
615+
"reorder-types-for-testing",
616+
"sorts types by access frequency with an exaggerated cost function",
617+
createReorderTypesForTestingPass);
611618
}
612619

613620
void PassRunner::addIfNoDWARFIssues(std::string passName) {

src/passes/passes.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ Pass* createReorderFunctionsPass();
147147
Pass* createReorderGlobalsPass();
148148
Pass* createReorderGlobalsAlwaysPass();
149149
Pass* createReorderLocalsPass();
150+
Pass* createReorderTypesPass();
151+
Pass* createReorderTypesForTestingPass();
150152
Pass* createReReloopPass();
151153
Pass* createRedundantSetEliminationPass();
152154
Pass* createRoundTripPass();

0 commit comments

Comments
 (0)