Skip to content

Commit 4f25fee

Browse files
tlivelykripken
andauthored
[Custom Descriptors] Use placeholder describees in GTO (#7888)
GTO tries to optimize out unused descriptor types, but sometimes cannot because the descriptor type must remain a descriptor to be a valid supertype of a subtype that will remain a descriptor. To optimize the original described type to not have a descriptor while simultaneously keeping the descriptor a valid supertype, insert a new, trivial placeholder type for the descriptor to describe. Since this new type is not otherwise used in the module, it should be able to be optimized out after subsequent rounds of unsubtyping and other optimizations. --------- Co-authored-by: Alon Zakai <azakai@google.com>
1 parent beda737 commit 4f25fee

File tree

4 files changed

+112
-78
lines changed

4 files changed

+112
-78
lines changed

src/ir/type-updating.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,16 +127,15 @@ GlobalTypeRewriter::getSortedTypes(PredecessorGraph preds) {
127127

128128
GlobalTypeRewriter::TypeMap
129129
GlobalTypeRewriter::rebuildTypes(std::vector<HeapType> types) {
130-
Index i = 0;
131-
for (auto type : types) {
132-
typeIndices[type] = i++;
130+
for (Index i = 0; i < types.size(); ++i) {
131+
typeIndices[types[i]] = i;
133132
}
134133

135134
if (typeIndices.size() == 0) {
136135
return {};
137136
}
138137

139-
typeBuilder.grow(typeIndices.size());
138+
typeBuilder.grow(types.size());
140139

141140
// All the input types are distinct, so we need to make sure the output
142141
// types are distinct as well. Further, the new types may have more
@@ -146,14 +145,14 @@ GlobalTypeRewriter::rebuildTypes(std::vector<HeapType> types) {
146145
typeBuilder.createRecGroup(0, typeBuilder.size());
147146

148147
// Create the temporary heap types.
149-
i = 0;
150148
auto map = [&](HeapType type) -> HeapType {
151149
if (auto it = typeIndices.find(type); it != typeIndices.end()) {
152150
return typeBuilder[it->second];
153151
}
154152
return type;
155153
};
156-
for (auto [type, _] : typeIndices) {
154+
for (Index i = 0; i < types.size(); ++i) {
155+
auto type = types[i];
157156
typeBuilder[i].copy(type, map);
158157
switch (type.getKind()) {
159158
case HeapTypeKind::Func: {
@@ -191,7 +190,6 @@ GlobalTypeRewriter::rebuildTypes(std::vector<HeapType> types) {
191190
}
192191

193192
modifyTypeBuilderEntry(typeBuilder, i, type);
194-
++i;
195193
}
196194

197195
auto buildResults = typeBuilder.build();

src/passes/GlobalTypeOptimization.cpp

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,9 @@
3030
#include "ir/struct-utils.h"
3131
#include "ir/subtypes.h"
3232
#include "ir/type-updating.h"
33-
#include "ir/utils.h"
3433
#include "pass.h"
34+
#include "support/insert_ordered.h"
3535
#include "support/permutations.h"
36-
#include "wasm-builder.h"
3736
#include "wasm-type-ordering.h"
3837
#include "wasm-type.h"
3938
#include "wasm.h"
@@ -138,6 +137,12 @@ struct GlobalTypeOptimization : public Pass {
138137
// The types that no longer need a descriptor.
139138
std::unordered_set<HeapType> haveUnneededDescriptors;
140139

140+
// Descriptor types that are not needed by their described types but that
141+
// still need to be descriptors for their own subtypes and supertypes to be
142+
// valid. We will keep them descriptors by having them describe trivial new
143+
// placeholder types.
144+
InsertOrderedMap<HeapType, Index> descriptorsOfPlaceholders;
145+
141146
void run(Module* module) override {
142147
if (!module->features.hasGC()) {
143148
return;
@@ -423,45 +428,43 @@ struct GlobalTypeOptimization : public Pass {
423428
// ^
424429
// B -> B.desc
425430
//
426-
// Here the descriptors subtype, but *not* the describees. We cannot
427-
// remove A's descriptor without also removing $B's, so we need to propagate
428-
// that "must remain a descriptor" property among descriptors.
431+
// Say we want to optimize A to no longer have a descriptor. Then A.desc
432+
// will no longer describe A. But A.desc still needs to be a descriptor for
433+
// it to remain a valid supertype of B.desc. To allow the optimization of A
434+
// to proceed, we will introduce a placeholder type for A.desc to describe,
435+
// keeping it a descriptor type.
429436
if (!haveUnneededDescriptors.empty()) {
430437
StructUtils::TypeHierarchyPropagator<StructUtils::CombinableBool>
431438
descPropagator(subTypes);
432439

433440
// Populate the initial data: Any descriptor we did not see was unneeded,
434441
// is needed.
435442
StructUtils::TypeHierarchyPropagator<
436-
StructUtils::CombinableBool>::StructMap map;
443+
StructUtils::CombinableBool>::StructMap remainingDesciptors;
437444
for (auto type : subTypes.types) {
438445
if (auto desc = type.getDescriptorType()) {
439446
if (!haveUnneededDescriptors.count(type)) {
440447
// This descriptor type is needed.
441-
map[*desc].value = true;
448+
remainingDesciptors[*desc].value = true;
442449
}
443450
}
444451
}
445452

446453
// Propagate.
447-
descPropagator.propagateToSuperAndSubTypes(map);
448-
449-
// Remove optimization opportunities that the propagation ruled out.
450-
// TODO: We could do better here,
451-
//
452-
// A -> A.desc A A.desc <- A2
453-
// ^ => ^
454-
// B -> B.desc B -> B.desc
455-
//
456-
// Starting from the left, we can remove A's descriptor *but keep A.desc
457-
// as being a descriptor*, by making it describe a new type A2. That would
458-
// keep subtyping working for the descriptors, and later passes could
459-
// remove the unused A2.
460-
for (auto& [type, info] : map) {
461-
if (info.value) {
462-
auto described = type.getDescribedType();
463-
assert(described);
464-
haveUnneededDescriptors.erase(*described);
454+
descPropagator.propagateToSuperAndSubTypes(remainingDesciptors);
455+
456+
// Determine the set of descriptor types that will need placeholder
457+
// describees. Do not iterate directly on remainingDescriptors because it
458+
// is not deterministically ordered.
459+
for (auto type : subTypes.types) {
460+
if (auto it = remainingDesciptors.find(type);
461+
it != remainingDesciptors.end() && it->second.value) {
462+
auto desc = type.getDescribedType();
463+
assert(desc);
464+
if (haveUnneededDescriptors.count(*desc)) {
465+
descriptorsOfPlaceholders.insert(
466+
{type, descriptorsOfPlaceholders.size()});
467+
}
465468
}
466469
}
467470
}
@@ -484,10 +487,22 @@ struct GlobalTypeOptimization : public Pass {
484487
void updateTypes(Module& wasm) {
485488
class TypeRewriter : public GlobalTypeRewriter {
486489
GlobalTypeOptimization& parent;
490+
InsertOrderedMap<HeapType, Index>::iterator placeholderIt;
487491

488492
public:
489493
TypeRewriter(Module& wasm, GlobalTypeOptimization& parent)
490-
: GlobalTypeRewriter(wasm), parent(parent) {}
494+
: GlobalTypeRewriter(wasm), parent(parent),
495+
placeholderIt(parent.descriptorsOfPlaceholders.begin()) {}
496+
497+
std::vector<HeapType> getSortedTypes(PredecessorGraph preds) override {
498+
auto types = GlobalTypeRewriter::getSortedTypes(std::move(preds));
499+
// Prefix the types with placeholders to be overwritten with the
500+
// placeholder describees.
501+
HeapType placeholder = Struct{};
502+
types.insert(
503+
types.begin(), parent.descriptorsOfPlaceholders.size(), placeholder);
504+
return types;
505+
}
491506

492507
void modifyStruct(HeapType oldStructType, Struct& struct_) override {
493508
auto& newFields = struct_.fields;
@@ -549,17 +564,30 @@ struct GlobalTypeOptimization : public Pass {
549564
return;
550565
}
551566

552-
// Remove an unneeded descriptor.
553-
if (parent.haveUnneededDescriptors.count(oldType)) {
554-
typeBuilder.setDescriptor(i, std::nullopt);
567+
// Until we've created all the placeholders, create a placeholder
568+
// describee type for the next descriptor that needs one.
569+
if (placeholderIt != parent.descriptorsOfPlaceholders.end()) {
570+
typeBuilder[i].descriptor(getTempHeapType(placeholderIt->first));
571+
++placeholderIt;
572+
return;
555573
}
556574

557-
// Remove an unneeded describes.
575+
// Remove an unneeded describee or describe a placeholder type.
558576
if (auto described = oldType.getDescribedType()) {
559577
if (parent.haveUnneededDescriptors.count(*described)) {
560-
typeBuilder.setDescribed(i, std::nullopt);
578+
if (auto it = parent.descriptorsOfPlaceholders.find(oldType);
579+
it != parent.descriptorsOfPlaceholders.end()) {
580+
typeBuilder[i].describes(typeBuilder[it->second]);
581+
} else {
582+
typeBuilder[i].describes(std::nullopt);
583+
}
561584
}
562585
}
586+
587+
// Remove an unneeded descriptor.
588+
if (parent.haveUnneededDescriptors.count(oldType)) {
589+
typeBuilder.setDescriptor(i, std::nullopt);
590+
}
563591
}
564592
};
565593

test/lit/passes/gto-desc-tnh.wast

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -95,18 +95,21 @@
9595
;; B -> B.desc
9696
;;
9797
;; $B is written a null descriptor, so we cannot optimize it when traps are
98-
;; possible. This also prevents optimizations on $A: we cannot remove that
99-
;; descriptor either, or its subtype would break.
98+
;; possible. This means $A.desc must remain a descriptor even as we optimize $A,
99+
;; so we give $A.desc a placeholder describee. With TNH, we can optimize without
100+
;; the placeholder.
100101
;;
101102
;; This tests subtyping of descriptors *without* subtyping of describees.
102103
(module
103104
(rec
104105
;; CHECK: (rec
105-
;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct))))
106+
;; CHECK-NEXT: (type $0 (descriptor $A.desc (struct)))
107+
108+
;; CHECK: (type $A (sub (struct)))
106109
;; T_N_H: (rec
107110
;; T_N_H-NEXT: (type $A (sub (struct)))
108111
(type $A (sub (descriptor $A.desc (struct))))
109-
;; CHECK: (type $A.desc (sub (describes $A (struct))))
112+
;; CHECK: (type $A.desc (sub (describes $0 (struct))))
110113
;; T_N_H: (type $A.desc (sub (struct)))
111114
(type $A.desc (sub (describes $A (struct ))))
112115

@@ -118,9 +121,9 @@
118121
(type $B.desc (sub $A.desc (describes $B (struct))))
119122
)
120123

121-
;; CHECK: (type $4 (func))
124+
;; CHECK: (type $5 (func))
122125

123-
;; CHECK: (func $test (type $4)
126+
;; CHECK: (func $test (type $5)
124127
;; CHECK-NEXT: (drop
125128
;; CHECK-NEXT: (struct.new_default $B
126129
;; CHECK-NEXT: (ref.null none)
@@ -147,30 +150,34 @@
147150
;; null descriptor but a use. We cannot optimize even without traps.
148151
;; Subtyping of descriptors *without* subtyping of describees.
149152
;;
150-
;; $B's descriptor seems removable, but the subtyping of the descriptors
151-
;; prevents this.
153+
;; $A's descriptor can be removed, but $A.desc needs to be given a placeholder
154+
;; describee.
152155
(module
153156
(rec
154157
;; CHECK: (rec
155-
;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct))))
158+
;; CHECK-NEXT: (type $0 (descriptor $B.desc (struct)))
159+
160+
;; CHECK: (type $A (sub (descriptor $A.desc (struct))))
156161
;; T_N_H: (rec
157-
;; T_N_H-NEXT: (type $A (sub (descriptor $A.desc (struct))))
162+
;; T_N_H-NEXT: (type $0 (descriptor $B.desc (struct)))
163+
164+
;; T_N_H: (type $A (sub (descriptor $A.desc (struct))))
158165
(type $A (sub (descriptor $A.desc (struct))))
159166
;; CHECK: (type $A.desc (sub (describes $A (struct))))
160167
;; T_N_H: (type $A.desc (sub (describes $A (struct))))
161168
(type $A.desc (sub (describes $A (struct ))))
162169

163-
;; CHECK: (type $B (sub (descriptor $B.desc (struct))))
164-
;; T_N_H: (type $B (sub (descriptor $B.desc (struct))))
170+
;; CHECK: (type $B (sub (struct)))
171+
;; T_N_H: (type $B (sub (struct)))
165172
(type $B (sub (descriptor $B.desc (struct))))
166-
;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct))))
167-
;; T_N_H: (type $B.desc (sub $A.desc (describes $B (struct))))
173+
;; CHECK: (type $B.desc (sub $A.desc (describes $0 (struct))))
174+
;; T_N_H: (type $B.desc (sub $A.desc (describes $0 (struct))))
168175
(type $B.desc (sub $A.desc (describes $B (struct))))
169176
)
170177

171-
;; CHECK: (type $4 (func))
178+
;; CHECK: (type $5 (func))
172179

173-
;; CHECK: (func $test (type $4)
180+
;; CHECK: (func $test (type $5)
174181
;; CHECK-NEXT: (local $B (ref $B))
175182
;; CHECK-NEXT: (drop
176183
;; CHECK-NEXT: (ref.get_desc $A
@@ -180,9 +187,9 @@
180187
;; CHECK-NEXT: )
181188
;; CHECK-NEXT: )
182189
;; CHECK-NEXT: )
183-
;; T_N_H: (type $4 (func))
190+
;; T_N_H: (type $5 (func))
184191

185-
;; T_N_H: (func $test (type $4)
192+
;; T_N_H: (func $test (type $5)
186193
;; T_N_H-NEXT: (local $B (ref $B))
187194
;; T_N_H-NEXT: (drop
188195
;; T_N_H-NEXT: (ref.get_desc $A

0 commit comments

Comments
 (0)