From 9b8433595de9e77354098651363b61b572f322de Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 24 Apr 2025 18:52:04 -0700 Subject: [PATCH 1/5] Use GLB in PossibleContents::intersect Use the standard utility rather than reimplementing type intersection. The new code is simpler, shorter, and properly supports exactness, avoiding an assertion failure in the added test case. The other functional change is that when one of the intersected heap types is bottom and the type GLB is a non-nullable reference to bottom, the result of the intersection is `None` where it was previously a `Cone`. --- src/ir/possible-contents.cpp | 35 ++++------------------- test/lit/passes/gufa-cast-all-exact.wast | 36 ++++++++++++++++++++++++ test/lit/passes/gufa-refs.wast | 9 ++++-- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 625136506f0..58298383f89 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -182,29 +182,19 @@ void PossibleContents::intersect(const PossibleContents& other) { auto heapType = type.getHeapType(); auto otherHeapType = otherType.getHeapType(); - // If both inputs are nullable then the intersection is nullable as well. - auto nullability = - type.isNullable() && otherType.isNullable() ? Nullable : NonNullable; + // Intersect the types. + auto newType = Type::getGreatestLowerBound(type, otherType); auto setNoneOrNull = [&]() { - if (nullability == Nullable) { + if (newType.isNullable()) { value = Literal::makeNull(heapType); } else { value = None(); } }; - // If the heap types are not compatible then they are in separate hierarchies - // and there is no intersection, aside from possibly a null of the bottom - // type. - auto isSubType = HeapType::isSubType(heapType, otherHeapType); - auto otherIsSubType = HeapType::isSubType(otherHeapType, heapType); - if (!isSubType && !otherIsSubType) { - if (heapType.getBottom() == otherHeapType.getBottom()) { - setNoneOrNull(); - } else { - value = None(); - } + if (newType == Type::unreachable || newType.isNull()) { + setNoneOrNull(); return; } @@ -212,17 +202,6 @@ void PossibleContents::intersect(const PossibleContents& other) { auto depthFromRoot = heapType.getDepth(); auto otherDepthFromRoot = otherHeapType.getDepth(); - // To compute the new cone, find the new heap type for it, and to compute its - // depth, consider the adjustments to the existing depths that stem from the - // choice of new heap type. - HeapType newHeapType; - - if (depthFromRoot < otherDepthFromRoot) { - newHeapType = otherHeapType; - } else { - newHeapType = heapType; - } - // Note the global's information, if we started as a global. In that case, the // code below will refine our type but we can remain a global, which we will // accomplish by restoring our global status at the end. @@ -231,8 +210,6 @@ void PossibleContents::intersect(const PossibleContents& other) { globalName = getGlobal(); } - auto newType = Type(newHeapType, nullability); - // By assumption |other| has full depth. Consider the other cone in |this|. if (hasFullCone()) { // Both are full cones, so the result is as well. @@ -252,7 +229,7 @@ void PossibleContents::intersect(const PossibleContents& other) { // E.g. if |this| is a cone of depth 10, and |otherHeapType| is an immediate // subtype of |this|, then the new cone must be of depth 9. auto newDepth = getCone().depth; - if (newHeapType == otherHeapType) { + if (newType.getHeapType() == otherHeapType) { assert(depthFromRoot <= otherDepthFromRoot); auto reduction = otherDepthFromRoot - depthFromRoot; if (reduction > newDepth) { diff --git a/test/lit/passes/gufa-cast-all-exact.wast b/test/lit/passes/gufa-cast-all-exact.wast index 5d181d3bb21..1b40f9ee266 100644 --- a/test/lit/passes/gufa-cast-all-exact.wast +++ b/test/lit/passes/gufa-cast-all-exact.wast @@ -138,3 +138,39 @@ ) ) ) + +(module + ;; CHECK: (type $foo (sub (struct (field i32)))) + ;; NO_CD: (type $foo (sub (struct (field i32)))) + (type $foo (sub (struct (field i32)))) + + ;; CHECK: (import "" "" (global $null-exact (ref null (exact $foo)))) + ;; NO_CD: (import "" "" (global $null-exact (ref null (exact $foo)))) + (import "" "" (global $null-exact (ref null (exact $foo)))) + + ;; CHECK: (func $as-non-null (type $1) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (global.get $null-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; NO_CD: (func $as-non-null (type $1) (result i32) + ;; NO_CD-NEXT: (drop + ;; NO_CD-NEXT: (ref.as_non_null + ;; NO_CD-NEXT: (global.get $null-exact) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: ) + ;; NO_CD-NEXT: (unreachable) + ;; NO_CD-NEXT: ) + (func $as-non-null (result i32) + (struct.get $foo 0 + ;; Regression test for an assertion failure when the intersection here + ;; dropped exactness as well as nullness. + (ref.as_non_null + (global.get $null-exact) + ) + ) + ) +) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index 6d48d651a5f..2178f142383 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -6069,8 +6069,13 @@ ;; CHECK: (func $test-set-bottom (type $2) ;; CHECK-NEXT: (block ;; (replaces unreachable ArraySet we can't emit) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref - ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop From 4b9bd7d98a0e42e4aaa5d35664c4f1688e15374b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 24 Apr 2025 21:39:15 -0700 Subject: [PATCH 2/5] Avoid errors in Type::with(HeapType) Previously doing e.g. `type.with(HeapType::none)` would cause an assertion failure if `type` was exact because `.with()` would only replace the heap type and exact references to basic heap types are disallowed. Rather than checking for and avoiding this error in all the callers, simply drop exactness when `.with()` is called with a basic heap type. This is reasonable behavior because the only alternative is never correct. Add a test that hits an assertion failure without this fix. AbstractTypeRefining replaces a defined type with `none` and the type updating utility does not check whether the new heap type is basic before doing the replacement. --- scripts/test/fuzzing.py | 1 + src/wasm-type.h | 4 +++- .../passes/abstract-type-refining-exact.wast | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 test/lit/passes/abstract-type-refining-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 8ddc4982563..96121d4649e 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -114,6 +114,7 @@ 'exact-references-lowering.wast', 'exact-casts.wast', 'exact-casts-trivial.wast', + 'abstract-type-refining-exact.wast', 'optimize-instructions-exact.wast', 'optimize-instructions-all-casts.wast', 'optimize-instructions-all-casts-exact.wast', diff --git a/src/wasm-type.h b/src/wasm-type.h index 4ecbf6ab379..9965e7ee849 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -419,7 +419,9 @@ class Type { // Return a new reference type with some part updated to the specified value. Type with(HeapType heapType) const { - return Type(heapType, getNullability(), getExactness()); + return Type(heapType, + getNullability(), + heapType.isBasic() ? Inexact : getExactness()); } Type with(Nullability nullability) const { return Type(getHeapType(), nullability, getExactness()); diff --git a/test/lit/passes/abstract-type-refining-exact.wast b/test/lit/passes/abstract-type-refining-exact.wast new file mode 100644 index 00000000000..dec05e0f328 --- /dev/null +++ b/test/lit/passes/abstract-type-refining-exact.wast @@ -0,0 +1,18 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --closed-world --abstract-type-refining -S -o - | filecheck %s + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $foo (struct)) + (type $foo (struct)) + + ;; CHECK: (func $test (type $1) + ;; CHECK-NEXT: (local $0 (ref none)) + ;; CHECK-NEXT: ) + (func $test + ;; $foo will be replaced with none` and the exactness should be dropped + ;; without errors. + (local (ref (exact $foo))) + ) +) From 5740469ab8d44055ba99673d9d528a351d5492db Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 25 Apr 2025 14:18:08 -0700 Subject: [PATCH 3/5] Disallow exact references in public types When custom descriptors are disabled, validate that public types do not contain exact references. If they did, we would drop the exactness and change the identity of the public type during binary writing, which would be incorrect. This still allows internal usage of exact types without custom descriptors enabled, and it is up to the individual passes to ensure that the eventual erasing of exactness does not cause any problems. --- src/wasm/wasm-validator.cpp | 28 +++++++++++++++++++++++++++ test/lit/validation/public-exact.wast | 23 ++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 test/lit/validation/public-exact.wast diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 62a970bbb12..6abcc1df0a3 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3970,6 +3970,33 @@ static void validateBinaryenIR(Module& wasm, ValidationInfo& info) { // Main validator class +static void validateTypes(Module& module, ValidationInfo& info) { + // Check that public types do not contain any exact references if custom + // descriptors is not enabled. If they did, we would erase the exactness + // during binary writing and change the public type identities. + if (module.features.hasCustomDescriptors()) { + return; + } + for (auto type : ModuleUtils::getPublicHeapTypes(module)) { + for (auto child : type.getTypeChildren()) { + if (child.isExact()) { + std::string typeName; + if (auto it = module.typeNames.find(type); + it != module.typeNames.end()) { + typeName = '$' + it->second.name.toString(); + } else { + typeName = type.toString(); + } + info.fail("Exact reference in public type not allowed without custom " + "descriptors [--enable-custom-descriptors]", + typeName, + nullptr); + break; + } + } + } +} + static void validateImports(Module& module, ValidationInfo& info) { ModuleUtils::iterImportedFunctions(module, [&](Function* curr) { if (curr->getResults().isTuple()) { @@ -4439,6 +4466,7 @@ bool WasmValidator::validate(Module& module, Flags flags) { // Validate globally. if (info.validateGlobally) { + validateTypes(module, info); validateImports(module, info); validateExports(module, info); validateGlobals(module, info); diff --git a/test/lit/validation/public-exact.wast b/test/lit/validation/public-exact.wast new file mode 100644 index 00000000000..d663935c8d7 --- /dev/null +++ b/test/lit/validation/public-exact.wast @@ -0,0 +1,23 @@ +;; Test that exact references in public types are disallowed without custom descriptors + +;; RUN: not wasm-opt %s -all --disable-custom-descriptors 2>&1 | filecheck %s +;; RUN: wasm-opt %s -all -S -o - | filecheck %s --check-prefix=NOERR + +;; CHECK: [wasm-validator error in module] Exact reference in public type not allowed without custom descriptors [--enable-custom-descriptors], on +;; CHECK-NEXT: $struct +;; CHECK-NEXT: [wasm-validator error in module] Exact reference in public type not allowed without custom descriptors [--enable-custom-descriptors], on +;; CHECK-NEXT: $array +;; CHECK-NEXT: [wasm-validator error in module] Exact reference in public type not allowed without custom descriptors [--enable-custom-descriptors], on +;; CHECK-NEXT: $func + +;; NOERR: (module + +(module + (type $struct (struct (field (ref null (exact $struct))))) + (type $array (array (field (ref (exact $struct))))) + (type $func (func (param (ref null (exact $struct))) (result (ref (exact $array))))) + + (import "" "struct" (global $struct (ref $struct))) + (import "" "array" (global $array (ref $array))) + (import "" "func" (global $func (ref $func))) +) \ No newline at end of file From caa72c737b7c910a776dd78e63ff13bccd084b6a Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 25 Apr 2025 18:27:21 -0700 Subject: [PATCH 4/5] Handle exactness in MinimizeRecGroups 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. --- scripts/test/fuzzing.py | 2 + src/passes/MinimizeRecGroups.cpp | 39 ++++++++------ src/tools/wasm-fuzz-types.cpp | 6 +-- src/wasm-type-shape.h | 12 ++++- src/wasm/wasm-type-shape.cpp | 13 +++++ .../lit/passes/minimize-rec-groups-exact.wast | 19 +++++++ .../minimize-rec-groups-ignore-exact.wast | 53 +++++++++++++++++++ 7 files changed, 122 insertions(+), 22 deletions(-) create mode 100644 test/lit/passes/minimize-rec-groups-exact.wast create mode 100644 test/lit/passes/minimize-rec-groups-ignore-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 96121d4649e..3f1a54884db 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -127,6 +127,8 @@ 'type-merging-exact.wast', 'type-refining-exact.wast', 'type-refining-gufa-exact.wast', + 'mimimize-rec-groups-exact.wast', + 'mimimize-rec-groups-ignore-exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending diff --git a/src/passes/MinimizeRecGroups.cpp b/src/passes/MinimizeRecGroups.cpp index 0faf297f545..4d727bd907f 100644 --- a/src/passes/MinimizeRecGroups.cpp +++ b/src/passes/MinimizeRecGroups.cpp @@ -208,14 +208,14 @@ struct GroupClassInfo { static std::vector> initSubtypeGraph(RecGroupInfo& info); GroupClassInfo(RecGroupInfo& info); - void advance() { + void advance(FeatureSet features) { ++orders; if (orders == orders.end()) { - advanceBrand(); + advanceBrand(features); } } - void advanceBrand() { + void advanceBrand(FeatureSet features) { if (brand) { ++*brand; } else { @@ -231,8 +231,8 @@ struct GroupClassInfo { } } // Make sure the brand is not the same as the real type. - if (singletonType && - RecGroupShape({**brand}) == RecGroupShape({*singletonType})) { + if (singletonType && RecGroupShape({**brand}, features) == + RecGroupShape({*singletonType}, features)) { ++*brand; } // Start back at the initial permutation with the new brand. @@ -370,9 +370,13 @@ struct MinimizeRecGroups : Pass { // whose shapes we need to check for uniqueness to avoid deep recursions. std::vector shapesToUpdate; + // The comparison of rec group shapes depends on the features. + FeatureSet features; + void run(Module* module) override { + features = module->features; // There are no recursion groups to minimize if GC is not enabled. - if (!module->features.hasGC()) { + if (!features.hasGC()) { return; } @@ -402,7 +406,7 @@ struct MinimizeRecGroups : Pass { for (auto group : publicGroups) { publicGroupTypes.emplace_back(group.begin(), group.end()); [[maybe_unused]] auto [_, inserted] = groupShapeIndices.insert( - {RecGroupShape(publicGroupTypes.back()), PublicGroupIndex}); + {RecGroupShape(publicGroupTypes.back(), features), PublicGroupIndex}); assert(inserted); } @@ -452,8 +456,8 @@ struct MinimizeRecGroups : Pass { } void updateShape(Index group) { - auto [it, inserted] = - groupShapeIndices.insert({RecGroupShape(groups[group].group), group}); + auto [it, inserted] = groupShapeIndices.insert( + {RecGroupShape(groups[group].group, features), group}); if (inserted) { // This shape was unique. We're done. return; @@ -509,7 +513,7 @@ struct MinimizeRecGroups : Pass { // We have everything we need to generate the next permutation of this // group. auto& classInfo = *groups[groupRep].classInfo; - classInfo.advance(); + classInfo.advance(features); classInfo.permute(groupInfo); shapesToUpdate.push_back(group); return; @@ -538,7 +542,7 @@ struct MinimizeRecGroups : Pass { // Move to the next permutation after advancing the type brand to skip // further repeated shapes. - classInfo.advanceBrand(); + classInfo.advanceBrand(features); classInfo.permute(groupInfo); shapesToUpdate.push_back(group); @@ -556,7 +560,7 @@ struct MinimizeRecGroups : Pass { // conflict. if (groups[groupRep].classInfo && groups[otherRep].classInfo) { auto& classInfo = *groups[groupRep].classInfo; - classInfo.advance(); + classInfo.advance(features); classInfo.permute(groupInfo); shapesToUpdate.push_back(group); return; @@ -578,7 +582,7 @@ struct MinimizeRecGroups : Pass { // same shape. Advance `group` to the next permutation. otherInfo.classInfo = std::nullopt; otherInfo.permutation = groupInfo.permutation; - classInfo.advance(); + classInfo.advance(features); classInfo.permute(groupInfo); shapesToUpdate.push_back(group); @@ -600,7 +604,7 @@ struct MinimizeRecGroups : Pass { // permutation. groupInfo.classInfo = std::nullopt; groupInfo.permutation = otherInfo.permutation; - classInfo.advance(); + classInfo.advance(features); classInfo.permute(groupInfo); shapesToUpdate.push_back(group); @@ -754,9 +758,10 @@ struct MinimizeRecGroups : Pass { // shapes to lists of automorphically equivalent root types. std::map> typeClasses; for (const auto& order : dfsOrders) { - ComparableRecGroupShape shape(order, [this](HeapType a, HeapType b) { - return this->typeIndices.at(a) < this->typeIndices.at(b); - }); + ComparableRecGroupShape shape( + order, features, [this](HeapType a, HeapType b) { + return this->typeIndices.at(a) < this->typeIndices.at(b); + }); typeClasses[shape].push_back(order[0]); } diff --git a/src/tools/wasm-fuzz-types.cpp b/src/tools/wasm-fuzz-types.cpp index 7ba341e09df..0f5e8bff2a6 100644 --- a/src/tools/wasm-fuzz-types.cpp +++ b/src/tools/wasm-fuzz-types.cpp @@ -542,7 +542,7 @@ void Fuzzer::checkRecGroupShapes() { }; for (size_t i = 0; i < groups.size(); ++i) { - ComparableRecGroupShape shape(groups[i], less); + ComparableRecGroupShape shape(groups[i], FeatureSet::All, less); // A rec group should compare equal to itself. if (shape != shape) { Fatal() << "Rec group shape " << i << " not equal to itself"; @@ -556,7 +556,7 @@ void Fuzzer::checkRecGroupShapes() { // Check how it compares to other groups. for (size_t j = i + 1; j < groups.size(); ++j) { - ComparableRecGroupShape other(groups[j], less); + ComparableRecGroupShape other(groups[j], FeatureSet::All, less); bool isLess = shape < other; bool isEq = shape == other; bool isGreater = shape > other; @@ -598,7 +598,7 @@ void Fuzzer::checkRecGroupShapes() { if (j + 1 < groups.size()) { // Check transitivity. - RecGroupShape third(groups[j + 1]); + RecGroupShape third(groups[j + 1], FeatureSet::All); if ((isLess && other <= third && shape >= third) || (isEq && other == third && shape != third) || (isGreater && other >= third && shape <= third)) { diff --git a/src/wasm-type-shape.h b/src/wasm-type-shape.h index 5eb4250f0df..e72f28dd530 100644 --- a/src/wasm-type-shape.h +++ b/src/wasm-type-shape.h @@ -20,6 +20,7 @@ #include #include +#include "wasm-features.h" #include "wasm-type.h" namespace wasm { @@ -35,7 +36,13 @@ namespace wasm { struct RecGroupShape { const std::vector& types; - RecGroupShape(const std::vector& types) : types(types) {} + // Depending on the feature set, some types may be generalized when they are + // written out. Take the features into account to ensure our comparisons + // account for the rec groups that will ultimately be written. + const FeatureSet features; + + RecGroupShape(const std::vector& types, const FeatureSet features) + : types(types), features(features) {} bool operator==(const RecGroupShape& other) const; bool operator!=(const RecGroupShape& other) const { @@ -51,8 +58,9 @@ struct ComparableRecGroupShape : RecGroupShape { std::function less; ComparableRecGroupShape(const std::vector& types, + FeatureSet features, std::function less) - : RecGroupShape(types), less(less) {} + : RecGroupShape(types, features), less(less) {} bool operator<(const RecGroupShape& other) const; bool operator>(const RecGroupShape& other) const; diff --git a/src/wasm/wasm-type-shape.cpp b/src/wasm/wasm-type-shape.cpp index 734c5e4b903..522ee45bb3e 100644 --- a/src/wasm/wasm-type-shape.cpp +++ b/src/wasm/wasm-type-shape.cpp @@ -25,6 +25,7 @@ namespace { enum Comparison { EQ, LT, GT }; template struct RecGroupComparator { + FeatureSet features; std::unordered_map indicesA; std::unordered_map indicesB; CompareTypes compareTypes; @@ -32,6 +33,8 @@ template struct RecGroupComparator { RecGroupComparator(CompareTypes compareTypes) : compareTypes(compareTypes) {} Comparison compare(const RecGroupShape& a, const RecGroupShape& b) { + assert(a.features == b.features); + features = a.features; if (a.types.size() != b.types.size()) { return a.types.size() < b.types.size() ? LT : GT; } @@ -147,6 +150,11 @@ template struct RecGroupComparator { return compare(a.getTuple(), b.getTuple()); } assert(a.isRef() && b.isRef()); + // Only consider exactness if custom descriptors are enabled. Otherwise, it + // will be erased when the types are written, so we ignore it here, too. + if (features.hasCustomDescriptors() && a.isExact() != b.isExact()) { + return a.isExact() < b.isExact() ? LT : GT; + } if (a.isNullable() != b.isNullable()) { return a.isNullable() < b.isNullable() ? LT : GT; } @@ -201,9 +209,11 @@ template RecGroupComparator(CompareTypes) -> RecGroupComparator; struct RecGroupHasher { + FeatureSet features; std::unordered_map typeIndices; size_t hash(const RecGroupShape& shape) { + features = shape.features; for (auto type : shape.types) { typeIndices.insert({type, typeIndices.size()}); } @@ -285,6 +295,9 @@ struct RecGroupHasher { return digest; } assert(type.isRef()); + if (features.hasCustomDescriptors()) { + wasm::rehash(digest, type.isExact()); + } wasm::rehash(digest, type.isNullable()); hash_combine(digest, hash(type.getHeapType())); return digest; diff --git a/test/lit/passes/minimize-rec-groups-exact.wast b/test/lit/passes/minimize-rec-groups-exact.wast new file mode 100644 index 00000000000..e1a2a8f85cc --- /dev/null +++ b/test/lit/passes/minimize-rec-groups-exact.wast @@ -0,0 +1,19 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-opt %s -all --minimize-rec-groups -S -o - | filecheck %s + +(module + ;; CHECK: (type $foo (struct)) + (type $foo (struct)) + ;; CHECK: (type $exact (struct (field (ref (exact $foo))))) + (type $exact (struct (field (ref (exact $foo))))) + ;; CHECK: (type $inexact (struct (field (ref $foo)))) + (type $inexact (struct (field (ref $foo)))) + + ;; If we didn't differentiate between exact and inexact types, there would be + ;; an assertion failure on adding these public types to the set of public + ;; shapes. + ;; CHECK: (import "" "exact" (global $exact (ref null $exact))) + (import "" "exact" (global $exact (ref null $exact))) + ;; CHECK: (import "" "inexact" (global $inexact (ref null $inexact))) + (import "" "inexact" (global $inexact (ref null $inexact))) +) diff --git a/test/lit/passes/minimize-rec-groups-ignore-exact.wast b/test/lit/passes/minimize-rec-groups-ignore-exact.wast new file mode 100644 index 00000000000..1a1fa8a7c82 --- /dev/null +++ b/test/lit/passes/minimize-rec-groups-ignore-exact.wast @@ -0,0 +1,53 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Check that we take exactness into account correctly depending on the +;; features. It differentiates shapes only when custom descriptors is enabled. + +;; RUN: wasm-opt %s -all --minimize-rec-groups -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --disable-custom-descriptors --minimize-rec-groups -S -o - | filecheck %s --check-prefix=NO_CD + +(module + (rec + (type $foo (struct)) + + ;; This SCC has only one distinct permutation. + ;; CHECK: (rec + ;; CHECK-NEXT: (type $b-inexact (struct (field (ref $a-inexact)))) + + ;; CHECK: (type $a-inexact (struct (field (ref $b-inexact)))) + ;; NO_CD: (rec + ;; NO_CD-NEXT: (type $b-inexact (struct (field (ref $a-inexact)))) + + ;; NO_CD: (type $a-inexact (struct (field (ref $b-inexact)))) + (type $a-inexact (struct (field (ref $b-inexact)))) + (type $b-inexact (struct (field (ref $a-inexact)))) + + ;; This SCC is only different because of exactness. It needs a brand only if + ;; custom descriptors is disabled. + ;; CHECK: (rec + ;; CHECK-NEXT: (type $b-exact (struct (field (ref (exact $a-exact))))) + + ;; CHECK: (type $a-exact (struct (field (ref (exact $b-exact))))) + ;; NO_CD: (rec + ;; NO_CD-NEXT: (type $2 (struct)) + + ;; NO_CD: (type $b-exact (struct (field (ref (exact $a-exact))))) + + ;; NO_CD: (type $a-exact (struct (field (ref (exact $b-exact))))) + (type $a-exact (struct (field (ref (exact $b-exact))))) + (type $b-exact (struct (field (ref (exact $a-exact))))) + ) + + ;; CHECK: (global $a-inexact (ref null $a-inexact) (ref.null none)) + ;; NO_CD: (global $a-inexact (ref null $a-inexact) (ref.null none)) + (global $a-inexact (ref null $a-inexact) (ref.null none)) + ;; CHECK: (global $b-inexact (ref null $b-inexact) (ref.null none)) + ;; NO_CD: (global $b-inexact (ref null $b-inexact) (ref.null none)) + (global $b-inexact (ref null $b-inexact) (ref.null none)) + ;; CHECK: (global $a-exact (ref null $a-exact) (ref.null none)) + ;; NO_CD: (global $a-exact (ref null $a-exact) (ref.null none)) + (global $a-exact (ref null $a-exact) (ref.null none)) + ;; CHECK: (global $b-exact (ref null $b-exact) (ref.null none)) + ;; NO_CD: (global $b-exact (ref null $b-exact) (ref.null none)) + (global $b-exact (ref null $b-exact) (ref.null none)) +) From 64fc4d149f41e278a17553cf21b659939cbefa32 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 29 Apr 2025 13:46:16 -0700 Subject: [PATCH 5/5] vertical space --- src/wasm/wasm-validator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 6abcc1df0a3..03446d51efe 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3977,6 +3977,7 @@ static void validateTypes(Module& module, ValidationInfo& info) { if (module.features.hasCustomDescriptors()) { return; } + for (auto type : ModuleUtils::getPublicHeapTypes(module)) { for (auto child : type.getTypeChildren()) { if (child.isExact()) {