Skip to content

Commit 762dd9c

Browse files
authored
Handle exactness in MinimizeRecGroups (#7555)
Treat rec groups differing only in the exactness of a reference as different, but only when custom descriptors is enabled. When custom descriptors is not enabled, exactness will be erased before the binary is written, so if two minimized rec groups differed only in exactness, they would in fact be written as the same rec group. This would change the behavior of casts meant to differentiate between types in that rec group, so it would be incorrect.
1 parent 51727f6 commit 762dd9c

File tree

7 files changed

+122
-22
lines changed

7 files changed

+122
-22
lines changed

scripts/test/fuzzing.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@
127127
'type-merging-exact.wast',
128128
'type-refining-exact.wast',
129129
'type-refining-gufa-exact.wast',
130+
'mimimize-rec-groups-exact.wast',
131+
'mimimize-rec-groups-ignore-exact.wast',
130132
'public-exact.wast',
131133
# TODO: fuzzer support for custom descriptors
132134
'custom-descriptors.wast',

src/passes/MinimizeRecGroups.cpp

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -208,14 +208,14 @@ struct GroupClassInfo {
208208
static std::vector<std::vector<Index>> initSubtypeGraph(RecGroupInfo& info);
209209
GroupClassInfo(RecGroupInfo& info);
210210

211-
void advance() {
211+
void advance(FeatureSet features) {
212212
++orders;
213213
if (orders == orders.end()) {
214-
advanceBrand();
214+
advanceBrand(features);
215215
}
216216
}
217217

218-
void advanceBrand() {
218+
void advanceBrand(FeatureSet features) {
219219
if (brand) {
220220
++*brand;
221221
} else {
@@ -231,8 +231,8 @@ struct GroupClassInfo {
231231
}
232232
}
233233
// Make sure the brand is not the same as the real type.
234-
if (singletonType &&
235-
RecGroupShape({**brand}) == RecGroupShape({*singletonType})) {
234+
if (singletonType && RecGroupShape({**brand}, features) ==
235+
RecGroupShape({*singletonType}, features)) {
236236
++*brand;
237237
}
238238
// Start back at the initial permutation with the new brand.
@@ -370,9 +370,13 @@ struct MinimizeRecGroups : Pass {
370370
// whose shapes we need to check for uniqueness to avoid deep recursions.
371371
std::vector<Index> shapesToUpdate;
372372

373+
// The comparison of rec group shapes depends on the features.
374+
FeatureSet features;
375+
373376
void run(Module* module) override {
377+
features = module->features;
374378
// There are no recursion groups to minimize if GC is not enabled.
375-
if (!module->features.hasGC()) {
379+
if (!features.hasGC()) {
376380
return;
377381
}
378382

@@ -402,7 +406,7 @@ struct MinimizeRecGroups : Pass {
402406
for (auto group : publicGroups) {
403407
publicGroupTypes.emplace_back(group.begin(), group.end());
404408
[[maybe_unused]] auto [_, inserted] = groupShapeIndices.insert(
405-
{RecGroupShape(publicGroupTypes.back()), PublicGroupIndex});
409+
{RecGroupShape(publicGroupTypes.back(), features), PublicGroupIndex});
406410
assert(inserted);
407411
}
408412

@@ -452,8 +456,8 @@ struct MinimizeRecGroups : Pass {
452456
}
453457

454458
void updateShape(Index group) {
455-
auto [it, inserted] =
456-
groupShapeIndices.insert({RecGroupShape(groups[group].group), group});
459+
auto [it, inserted] = groupShapeIndices.insert(
460+
{RecGroupShape(groups[group].group, features), group});
457461
if (inserted) {
458462
// This shape was unique. We're done.
459463
return;
@@ -509,7 +513,7 @@ struct MinimizeRecGroups : Pass {
509513
// We have everything we need to generate the next permutation of this
510514
// group.
511515
auto& classInfo = *groups[groupRep].classInfo;
512-
classInfo.advance();
516+
classInfo.advance(features);
513517
classInfo.permute(groupInfo);
514518
shapesToUpdate.push_back(group);
515519
return;
@@ -538,7 +542,7 @@ struct MinimizeRecGroups : Pass {
538542

539543
// Move to the next permutation after advancing the type brand to skip
540544
// further repeated shapes.
541-
classInfo.advanceBrand();
545+
classInfo.advanceBrand(features);
542546
classInfo.permute(groupInfo);
543547

544548
shapesToUpdate.push_back(group);
@@ -556,7 +560,7 @@ struct MinimizeRecGroups : Pass {
556560
// conflict.
557561
if (groups[groupRep].classInfo && groups[otherRep].classInfo) {
558562
auto& classInfo = *groups[groupRep].classInfo;
559-
classInfo.advance();
563+
classInfo.advance(features);
560564
classInfo.permute(groupInfo);
561565
shapesToUpdate.push_back(group);
562566
return;
@@ -578,7 +582,7 @@ struct MinimizeRecGroups : Pass {
578582
// same shape. Advance `group` to the next permutation.
579583
otherInfo.classInfo = std::nullopt;
580584
otherInfo.permutation = groupInfo.permutation;
581-
classInfo.advance();
585+
classInfo.advance(features);
582586
classInfo.permute(groupInfo);
583587

584588
shapesToUpdate.push_back(group);
@@ -600,7 +604,7 @@ struct MinimizeRecGroups : Pass {
600604
// permutation.
601605
groupInfo.classInfo = std::nullopt;
602606
groupInfo.permutation = otherInfo.permutation;
603-
classInfo.advance();
607+
classInfo.advance(features);
604608
classInfo.permute(groupInfo);
605609

606610
shapesToUpdate.push_back(group);
@@ -754,9 +758,10 @@ struct MinimizeRecGroups : Pass {
754758
// shapes to lists of automorphically equivalent root types.
755759
std::map<ComparableRecGroupShape, std::vector<HeapType>> typeClasses;
756760
for (const auto& order : dfsOrders) {
757-
ComparableRecGroupShape shape(order, [this](HeapType a, HeapType b) {
758-
return this->typeIndices.at(a) < this->typeIndices.at(b);
759-
});
761+
ComparableRecGroupShape shape(
762+
order, features, [this](HeapType a, HeapType b) {
763+
return this->typeIndices.at(a) < this->typeIndices.at(b);
764+
});
760765
typeClasses[shape].push_back(order[0]);
761766
}
762767

src/tools/wasm-fuzz-types.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ void Fuzzer::checkRecGroupShapes() {
542542
};
543543

544544
for (size_t i = 0; i < groups.size(); ++i) {
545-
ComparableRecGroupShape shape(groups[i], less);
545+
ComparableRecGroupShape shape(groups[i], FeatureSet::All, less);
546546
// A rec group should compare equal to itself.
547547
if (shape != shape) {
548548
Fatal() << "Rec group shape " << i << " not equal to itself";
@@ -556,7 +556,7 @@ void Fuzzer::checkRecGroupShapes() {
556556

557557
// Check how it compares to other groups.
558558
for (size_t j = i + 1; j < groups.size(); ++j) {
559-
ComparableRecGroupShape other(groups[j], less);
559+
ComparableRecGroupShape other(groups[j], FeatureSet::All, less);
560560
bool isLess = shape < other;
561561
bool isEq = shape == other;
562562
bool isGreater = shape > other;
@@ -598,7 +598,7 @@ void Fuzzer::checkRecGroupShapes() {
598598

599599
if (j + 1 < groups.size()) {
600600
// Check transitivity.
601-
RecGroupShape third(groups[j + 1]);
601+
RecGroupShape third(groups[j + 1], FeatureSet::All);
602602
if ((isLess && other <= third && shape >= third) ||
603603
(isEq && other == third && shape != third) ||
604604
(isGreater && other >= third && shape <= third)) {

src/wasm-type-shape.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <functional>
2121
#include <vector>
2222

23+
#include "wasm-features.h"
2324
#include "wasm-type.h"
2425

2526
namespace wasm {
@@ -35,7 +36,13 @@ namespace wasm {
3536
struct RecGroupShape {
3637
const std::vector<HeapType>& types;
3738

38-
RecGroupShape(const std::vector<HeapType>& types) : types(types) {}
39+
// Depending on the feature set, some types may be generalized when they are
40+
// written out. Take the features into account to ensure our comparisons
41+
// account for the rec groups that will ultimately be written.
42+
const FeatureSet features;
43+
44+
RecGroupShape(const std::vector<HeapType>& types, const FeatureSet features)
45+
: types(types), features(features) {}
3946

4047
bool operator==(const RecGroupShape& other) const;
4148
bool operator!=(const RecGroupShape& other) const {
@@ -51,8 +58,9 @@ struct ComparableRecGroupShape : RecGroupShape {
5158
std::function<bool(HeapType, HeapType)> less;
5259

5360
ComparableRecGroupShape(const std::vector<HeapType>& types,
61+
FeatureSet features,
5462
std::function<bool(HeapType, HeapType)> less)
55-
: RecGroupShape(types), less(less) {}
63+
: RecGroupShape(types, features), less(less) {}
5664

5765
bool operator<(const RecGroupShape& other) const;
5866
bool operator>(const RecGroupShape& other) const;

src/wasm/wasm-type-shape.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ namespace {
2525
enum Comparison { EQ, LT, GT };
2626

2727
template<typename CompareTypes> struct RecGroupComparator {
28+
FeatureSet features;
2829
std::unordered_map<HeapType, Index> indicesA;
2930
std::unordered_map<HeapType, Index> indicesB;
3031
CompareTypes compareTypes;
3132

3233
RecGroupComparator(CompareTypes compareTypes) : compareTypes(compareTypes) {}
3334

3435
Comparison compare(const RecGroupShape& a, const RecGroupShape& b) {
36+
assert(a.features == b.features);
37+
features = a.features;
3538
if (a.types.size() != b.types.size()) {
3639
return a.types.size() < b.types.size() ? LT : GT;
3740
}
@@ -147,6 +150,11 @@ template<typename CompareTypes> struct RecGroupComparator {
147150
return compare(a.getTuple(), b.getTuple());
148151
}
149152
assert(a.isRef() && b.isRef());
153+
// Only consider exactness if custom descriptors are enabled. Otherwise, it
154+
// will be erased when the types are written, so we ignore it here, too.
155+
if (features.hasCustomDescriptors() && a.isExact() != b.isExact()) {
156+
return a.isExact() < b.isExact() ? LT : GT;
157+
}
150158
if (a.isNullable() != b.isNullable()) {
151159
return a.isNullable() < b.isNullable() ? LT : GT;
152160
}
@@ -201,9 +209,11 @@ template<typename CompareTypes>
201209
RecGroupComparator(CompareTypes) -> RecGroupComparator<CompareTypes>;
202210

203211
struct RecGroupHasher {
212+
FeatureSet features;
204213
std::unordered_map<HeapType, Index> typeIndices;
205214

206215
size_t hash(const RecGroupShape& shape) {
216+
features = shape.features;
207217
for (auto type : shape.types) {
208218
typeIndices.insert({type, typeIndices.size()});
209219
}
@@ -285,6 +295,9 @@ struct RecGroupHasher {
285295
return digest;
286296
}
287297
assert(type.isRef());
298+
if (features.hasCustomDescriptors()) {
299+
wasm::rehash(digest, type.isExact());
300+
}
288301
wasm::rehash(digest, type.isNullable());
289302
hash_combine(digest, hash(type.getHeapType()));
290303
return digest;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
;; RUN: wasm-opt %s -all --minimize-rec-groups -S -o - | filecheck %s
3+
4+
(module
5+
;; CHECK: (type $foo (struct))
6+
(type $foo (struct))
7+
;; CHECK: (type $exact (struct (field (ref (exact $foo)))))
8+
(type $exact (struct (field (ref (exact $foo)))))
9+
;; CHECK: (type $inexact (struct (field (ref $foo))))
10+
(type $inexact (struct (field (ref $foo))))
11+
12+
;; If we didn't differentiate between exact and inexact types, there would be
13+
;; an assertion failure on adding these public types to the set of public
14+
;; shapes.
15+
;; CHECK: (import "" "exact" (global $exact (ref null $exact)))
16+
(import "" "exact" (global $exact (ref null $exact)))
17+
;; CHECK: (import "" "inexact" (global $inexact (ref null $inexact)))
18+
(import "" "inexact" (global $inexact (ref null $inexact)))
19+
)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
3+
;; Check that we take exactness into account correctly depending on the
4+
;; features. It differentiates shapes only when custom descriptors is enabled.
5+
6+
;; RUN: wasm-opt %s -all --minimize-rec-groups -S -o - | filecheck %s
7+
;; RUN: wasm-opt %s -all --disable-custom-descriptors --minimize-rec-groups -S -o - | filecheck %s --check-prefix=NO_CD
8+
9+
(module
10+
(rec
11+
(type $foo (struct))
12+
13+
;; This SCC has only one distinct permutation.
14+
;; CHECK: (rec
15+
;; CHECK-NEXT: (type $b-inexact (struct (field (ref $a-inexact))))
16+
17+
;; CHECK: (type $a-inexact (struct (field (ref $b-inexact))))
18+
;; NO_CD: (rec
19+
;; NO_CD-NEXT: (type $b-inexact (struct (field (ref $a-inexact))))
20+
21+
;; NO_CD: (type $a-inexact (struct (field (ref $b-inexact))))
22+
(type $a-inexact (struct (field (ref $b-inexact))))
23+
(type $b-inexact (struct (field (ref $a-inexact))))
24+
25+
;; This SCC is only different because of exactness. It needs a brand only if
26+
;; custom descriptors is disabled.
27+
;; CHECK: (rec
28+
;; CHECK-NEXT: (type $b-exact (struct (field (ref (exact $a-exact)))))
29+
30+
;; CHECK: (type $a-exact (struct (field (ref (exact $b-exact)))))
31+
;; NO_CD: (rec
32+
;; NO_CD-NEXT: (type $2 (struct))
33+
34+
;; NO_CD: (type $b-exact (struct (field (ref (exact $a-exact)))))
35+
36+
;; NO_CD: (type $a-exact (struct (field (ref (exact $b-exact)))))
37+
(type $a-exact (struct (field (ref (exact $b-exact)))))
38+
(type $b-exact (struct (field (ref (exact $a-exact)))))
39+
)
40+
41+
;; CHECK: (global $a-inexact (ref null $a-inexact) (ref.null none))
42+
;; NO_CD: (global $a-inexact (ref null $a-inexact) (ref.null none))
43+
(global $a-inexact (ref null $a-inexact) (ref.null none))
44+
;; CHECK: (global $b-inexact (ref null $b-inexact) (ref.null none))
45+
;; NO_CD: (global $b-inexact (ref null $b-inexact) (ref.null none))
46+
(global $b-inexact (ref null $b-inexact) (ref.null none))
47+
;; CHECK: (global $a-exact (ref null $a-exact) (ref.null none))
48+
;; NO_CD: (global $a-exact (ref null $a-exact) (ref.null none))
49+
(global $a-exact (ref null $a-exact) (ref.null none))
50+
;; CHECK: (global $b-exact (ref null $b-exact) (ref.null none))
51+
;; NO_CD: (global $b-exact (ref null $b-exact) (ref.null none))
52+
(global $b-exact (ref null $b-exact) (ref.null none))
53+
)

0 commit comments

Comments
 (0)