diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/binaryen-c.cpp | 3 | ||||
-rw-r--r-- | src/binaryen-c.h | 1 | ||||
-rw-r--r-- | src/ir/module-utils.cpp | 21 | ||||
-rw-r--r-- | src/ir/possible-contents.cpp | 13 | ||||
-rw-r--r-- | src/ir/subtypes.h | 4 | ||||
-rw-r--r-- | src/passes/ConstantFieldPropagation.cpp | 4 | ||||
-rw-r--r-- | src/passes/GUFA.cpp | 7 | ||||
-rw-r--r-- | src/passes/GlobalRefining.cpp | 4 | ||||
-rw-r--r-- | src/passes/GlobalStructInference.cpp | 4 | ||||
-rw-r--r-- | src/passes/GlobalTypeOptimization.cpp | 4 | ||||
-rw-r--r-- | src/passes/Print.cpp | 4 | ||||
-rw-r--r-- | src/passes/SignaturePruning.cpp | 4 | ||||
-rw-r--r-- | src/passes/SignatureRefining.cpp | 4 | ||||
-rw-r--r-- | src/passes/pass.cpp | 5 | ||||
-rw-r--r-- | src/tools/tool-options.h | 13 | ||||
-rw-r--r-- | src/tools/wasm-fuzz-types.cpp | 14 | ||||
-rw-r--r-- | src/tools/wasm-reduce.cpp | 5 | ||||
-rw-r--r-- | src/wasm-type.h | 3 | ||||
-rw-r--r-- | src/wasm/wasm-binary.cpp | 8 | ||||
-rw-r--r-- | src/wasm/wasm-type.cpp | 1073 |
20 files changed, 31 insertions, 1167 deletions
diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index e28b9fb77..e3d1505fa 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -408,9 +408,6 @@ BinaryenType BinaryenTypeFromHeapType(BinaryenHeapType heapType, // TypeSystem -BinaryenTypeSystem BinaryenTypeSystemEquirecursive() { - return static_cast<BinaryenTypeSystem>(TypeSystem::Equirecursive); -} BinaryenTypeSystem BinaryenTypeSystemNominal() { return static_cast<BinaryenTypeSystem>(TypeSystem::Nominal); } diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 8da248f07..c9331d49d 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -192,7 +192,6 @@ BINARYEN_API BinaryenType BinaryenTypeFromHeapType(BinaryenHeapType heapType, typedef uint32_t BinaryenTypeSystem; -BINARYEN_API BinaryenTypeSystem BinaryenTypeSystemEquirecursive(void); BINARYEN_API BinaryenTypeSystem BinaryenTypeSystemNominal(void); BINARYEN_API BinaryenTypeSystem BinaryenTypeSystemIsorecursive(void); BINARYEN_API BinaryenTypeSystem BinaryenGetTypeSystem(void); diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index c10a45b15..22f07a2e8 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -214,24 +214,6 @@ IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm) { TypeSystem system = getTypeSystem(); Counts counts = getHeapTypeCounts(wasm); - if (system == TypeSystem::Equirecursive) { - // Sort by frequency and then original insertion order. - std::vector<std::pair<HeapType, size_t>> sorted(counts.begin(), - counts.end()); - std::stable_sort(sorted.begin(), sorted.end(), [&](auto a, auto b) { - return a.second > b.second; - }); - - // Collect the results. - IndexedHeapTypes indexedTypes; - for (Index i = 0; i < sorted.size(); ++i) { - indexedTypes.types.push_back(sorted[i].first); - } - - setIndices(indexedTypes); - return indexedTypes; - } - // Types have to be arranged into topologically ordered recursion groups. // Under isorecrsive typing, the topological sort has to take all referenced // rec groups into account but under nominal typing it only has to take @@ -285,9 +267,6 @@ IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm) { info.preds.insert(super->getRecGroup()); } break; - case TypeSystem::Equirecursive: - WASM_UNREACHABLE( - "Equirecursive types should already have been handled"); } } diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 0284101ed..7ed6bfde6 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -488,12 +488,6 @@ struct InfoCollector } } } - if (type.isRef() && getTypeSystem() != TypeSystem::Nominal && - getTypeSystem() != TypeSystem::Isorecursive) { - // We need explicit supers in the SubTyping helper class. Without that, - // cannot handle refs, and consider them irrelevant. - return false; - } return true; } @@ -1582,11 +1576,8 @@ Flower::Flower(Module& wasm) : wasm(wasm) { std::cout << "struct phase\n"; #endif - if (getTypeSystem() == TypeSystem::Nominal || - getTypeSystem() == TypeSystem::Isorecursive) { - subTypes = std::make_unique<SubTypes>(wasm); - maxDepths = subTypes->getMaxDepths(); - } + subTypes = std::make_unique<SubTypes>(wasm); + maxDepths = subTypes->getMaxDepths(); #ifdef POSSIBLE_CONTENTS_DEBUG std::cout << "Link-targets phase\n"; diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h index 198a69b7a..bc14c1d4f 100644 --- a/src/ir/subtypes.h +++ b/src/ir/subtypes.h @@ -29,10 +29,6 @@ namespace wasm { // This only scans user types, and not basic types like HeapType::eq. struct SubTypes { SubTypes(const std::vector<HeapType>& types) : types(types) { - if (getTypeSystem() != TypeSystem::Nominal && - getTypeSystem() != TypeSystem::Isorecursive) { - Fatal() << "SubTypes requires explicit supers"; - } for (auto type : types) { note(type); } diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 5048b978b..c5b95b15a 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -187,10 +187,6 @@ struct ConstantFieldPropagation : public Pass { if (!module->features.hasGC()) { return; } - if (getTypeSystem() != TypeSystem::Nominal && - getTypeSystem() != TypeSystem::Isorecursive) { - Fatal() << "CFP requires nominal/isorecursive typing"; - } // Find and analyze all writes inside each function. PCVFunctionStructValuesMap functionNewInfos(*module), diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp index 9774c1bb7..4250c76e9 100644 --- a/src/passes/GUFA.cpp +++ b/src/passes/GUFA.cpp @@ -110,13 +110,6 @@ struct GUFAOptimizer return; } - if (type.isRef() && (getTypeSystem() != TypeSystem::Nominal && - getTypeSystem() != TypeSystem::Isorecursive)) { - // Without type info we can't analyze subtypes, so we cannot infer - // anything about refs. - return; - } - // Ok, this is an interesting location that we might optimize. See what the // oracle says is possible there. auto contents = getContents(curr); diff --git a/src/passes/GlobalRefining.cpp b/src/passes/GlobalRefining.cpp index f994d4082..0576e3285 100644 --- a/src/passes/GlobalRefining.cpp +++ b/src/passes/GlobalRefining.cpp @@ -38,10 +38,6 @@ struct GlobalRefining : public Pass { if (!module->features.hasGC()) { return; } - if (getTypeSystem() != TypeSystem::Nominal && - getTypeSystem() != TypeSystem::Isorecursive) { - Fatal() << "GlobalRefining requires nominal/isorecursive typing"; - } // First, find all the global.sets. diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index 8bb4c0ba9..938350960 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -71,10 +71,6 @@ struct GlobalStructInference : public Pass { if (!module->features.hasGC()) { return; } - if (getTypeSystem() != TypeSystem::Nominal && - getTypeSystem() != TypeSystem::Isorecursive) { - Fatal() << "GlobalStructInference requires nominal/isorecursive typing"; - } // First, find all the information we need. We need to know which struct // types are created in functions, because we will not be able to optimize diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index 95d8859e9..4f7ce7ac7 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -117,10 +117,6 @@ struct GlobalTypeOptimization : public Pass { if (!module->features.hasGC()) { return; } - if (getTypeSystem() != TypeSystem::Nominal && - getTypeSystem() != TypeSystem::Isorecursive) { - Fatal() << "GlobalTypeOptimization requires nominal/isorecursive typing"; - } // Find and analyze struct operations inside each function. StructUtils::FunctionStructValuesMap<FieldInfo> functionNewInfos(*module), diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index a92b0a16a..d9e54b054 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3034,9 +3034,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> { o << '('; printMajor(o, "func "); printName(curr->name, o); - if (currModule && currModule->features.hasGC() && - (getTypeSystem() == TypeSystem::Nominal || - getTypeSystem() == TypeSystem::Isorecursive)) { + if (currModule && currModule->features.hasGC()) { o << " (type "; printHeapType(o, curr->type, currModule) << ')'; } diff --git a/src/passes/SignaturePruning.cpp b/src/passes/SignaturePruning.cpp index e6a7aa44e..3244ee103 100644 --- a/src/passes/SignaturePruning.cpp +++ b/src/passes/SignaturePruning.cpp @@ -52,10 +52,6 @@ struct SignaturePruning : public Pass { if (!module->features.hasGC()) { return; } - if (getTypeSystem() != TypeSystem::Nominal && - getTypeSystem() != TypeSystem::Isorecursive) { - Fatal() << "SignaturePruning requires nominal/isorecursive typing"; - } if (!module->tables.empty()) { // When there are tables we must also take their types into account, which diff --git a/src/passes/SignatureRefining.cpp b/src/passes/SignatureRefining.cpp index 488e2c6cc..132079b76 100644 --- a/src/passes/SignatureRefining.cpp +++ b/src/passes/SignatureRefining.cpp @@ -54,10 +54,6 @@ struct SignatureRefining : public Pass { if (!module->features.hasGC()) { return; } - if (getTypeSystem() != TypeSystem::Nominal && - getTypeSystem() != TypeSystem::Isorecursive) { - Fatal() << "SignatureRefining requires nominal/hybrid typing"; - } if (!module->tables.empty()) { // When there are tables we must also take their types into account, which diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 9643b489e..9daf8e1d3 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -593,10 +593,7 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() { if (options.optimizeLevel >= 2) { addIfNoDWARFIssues("once-reduction"); } - if (wasm->features.hasGC() && - (getTypeSystem() == TypeSystem::Nominal || - getTypeSystem() == TypeSystem::Isorecursive) && - options.optimizeLevel >= 2) { + if (wasm->features.hasGC() && options.optimizeLevel >= 2) { addIfNoDWARFIssues("type-refining"); addIfNoDWARFIssues("signature-pruning"); addIfNoDWARFIssues("signature-refining"); diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index eb5212c84..0522b5538 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -148,15 +148,6 @@ struct ToolOptions : public Options { [](Options* o, const std::string& argument) { setTypeSystem(TypeSystem::Nominal); }) - .add("--structural", - "", - "Force all GC type definitions to be parsed as structural " - "(i.e. equirecursive). This is the default.", - ToolOptionsCategory, - Options::Arguments::Zero, - [](Options* o, const std::string& argument) { - setTypeSystem(TypeSystem::Equirecursive); - }) .add("--hybrid", "", "Force all GC type definitions to be parsed using the isorecursive " @@ -196,9 +187,7 @@ struct ToolOptions : public Options { void applyFeatures(Module& module) const { module.features.enable(enabledFeatures); module.features.disable(disabledFeatures); - // Non-default type systems only make sense with GC enabled. TODO: Error on - // non-GC equirecursive types as well once we make isorecursive the default - // if we don't remove equirecursive types entirely. + // Non-default type systems only make sense with GC enabled. if (!module.features.hasGC() && getTypeSystem() == TypeSystem::Nominal) { Fatal() << "Nominal typing is only allowed when GC is enabled"; } diff --git a/src/tools/wasm-fuzz-types.cpp b/src/tools/wasm-fuzz-types.cpp index bbf4967e3..0e40e7a15 100644 --- a/src/tools/wasm-fuzz-types.cpp +++ b/src/tools/wasm-fuzz-types.cpp @@ -517,25 +517,17 @@ int main(int argc, const char* argv[]) { Options::Arguments::Zero, [&](Options*, const std::string& arg) { verbose = true; }); - TypeSystem system = TypeSystem::Nominal; + TypeSystem system = TypeSystem::Isorecursive; options.add( "--nominal", "", - "Use the nominal type system (default)", + "Use the nominal type system", WasmFuzzTypesOption, Options::Arguments::Zero, [&](Options*, const std::string& arg) { system = TypeSystem::Nominal; }); - options.add("--structural", - "", - "Use the equirecursive type system", - WasmFuzzTypesOption, - Options::Arguments::Zero, - [&](Options*, const std::string& arg) { - system = TypeSystem::Equirecursive; - }); options.add("--hybrid", "", - "Use the isorecursive hybrid type system", + "Use the isorecursive hybrid type system (default)", WasmFuzzTypesOption, Options::Arguments::Zero, [&](Options*, const std::string& arg) { diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index eec1e0874..d92e0a93c 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -1293,9 +1293,10 @@ int main(int argc, const char* argv[]) { } if (getTypeSystem() == TypeSystem::Nominal) { extraFlags += " --nominal"; - } - if (getTypeSystem() == TypeSystem::Isorecursive) { + } else if (getTypeSystem() == TypeSystem::Isorecursive) { extraFlags += " --hybrid"; + } else { + WASM_UNREACHABLE("unexpected type system"); } if (test.size() == 0) { diff --git a/src/wasm-type.h b/src/wasm-type.h index fa365b0e5..140fcf893 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -41,9 +41,8 @@ namespace wasm { enum class TypeSystem { - Equirecursive, - Nominal, Isorecursive, + Nominal, }; // This should only ever be called before any Types or HeapTypes have been diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 3cceaef46..c2a7dfff3 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -232,12 +232,9 @@ void WasmBinaryWriter::writeTypes() { // MVP types are structural and do not use recursion groups. TypeSystem typeSystem = getTypeSystem(); if (!wasm->features.hasGC()) { - typeSystem = TypeSystem::Equirecursive; + typeSystem = TypeSystem::Isorecursive; } switch (typeSystem) { - case TypeSystem::Equirecursive: - numGroups = indexedTypes.types.size(); - break; case TypeSystem::Nominal: numGroups = 1; break; @@ -2191,9 +2188,6 @@ void WasmBinaryBuilder::readTypes() { auto form = getS32LEB(); if (form == BinaryConsts::EncodedType::Rec) { uint32_t groupSize = getU32LEB(); - if (getTypeSystem() == TypeSystem::Equirecursive) { - throwError("Recursion groups not allowed with equirecursive typing"); - } if (groupSize == 0u) { // TODO: Support groups of size zero by shrinking the builder. throwError("Recursion groups of size zero not supported"); diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index da40bb10e..284d6d7d6 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -151,9 +151,6 @@ struct HeapTypeInfo { // Helper for coinductively checking whether a pair of Types or HeapTypes are in // a subtype relation. struct SubTyper { - // Set of HeapTypes we are assuming are equivalent as long as we cannot prove - // otherwise. - std::unordered_set<std::pair<HeapType, HeapType>> seen; bool isSubType(Type a, Type b); bool isSubType(HeapType a, HeapType b); bool isSubType(const Tuple& a, const Tuple& b); @@ -164,36 +161,8 @@ struct SubTyper { }; // Helper for finding the equirecursive least upper bound of two types. -struct TypeBounder { - TypeBuilder builder; - // The indices in `builder` at which the LUB of each pair of HeapTypes are - // being constructed. - std::unordered_map<std::pair<HeapType, HeapType>, size_t> indices; - - bool hasLeastUpperBound(Type a, Type b); - Type getLeastUpperBound(Type a, Type b); - std::optional<HeapType> getLeastUpperBound(HeapType a, HeapType b); - -private: - // Return the LUB iff a LUB was found. The HeapType and Struct overloads are - // exceptional because they are infallible; HeapType::any is an upper bound of - // all HeapTypes and the empty struct is an upper bound of all struct types. - // Note that these methods can return temporary types, so they should never be - // used directly. - std::optional<Type> lub(Type a, Type b); - std::optional<HeapType> lub(HeapType a, HeapType b); - std::optional<Tuple> lub(const Tuple& a, const Tuple& b); - std::optional<Field> lub(const Field& a, const Field& b); - std::optional<Signature> lub(const Signature& a, const Signature& b); - Struct lub(const Struct& a, const Struct& b); - std::optional<Array> lub(const Array& a, const Array& b); -}; - // Helper for printing types. struct TypePrinter { - // Whether to print explicit supertypes. - bool printSupertypes; - // The stream we are printing to. std::ostream& os; @@ -204,8 +173,7 @@ struct TypePrinter { HeapTypeNameGenerator generator; TypePrinter(std::ostream& os, HeapTypeNameGenerator generator) - : printSupertypes(getTypeSystem() != TypeSystem::Equirecursive), os(os), - defaultGenerator(), generator(generator) {} + : os(os), defaultGenerator(), generator(generator) {} TypePrinter(std::ostream& os) : TypePrinter( os, [&](HeapType type) { return defaultGenerator->getNames(type); }) { @@ -213,7 +181,6 @@ struct TypePrinter { } void printHeapTypeName(HeapType type); - void printSupertypeOr(std::optional<HeapType> super, std::string other); std::ostream& print(Type type); std::ostream& print(HeapType type); @@ -227,54 +194,6 @@ struct TypePrinter { std::optional<HeapType> super = std::nullopt); }; -// Helper for hashing the shapes of TypeInfos and HeapTypeInfos. Keeps track of -// previously seen HeapTypes to avoid traversing them more than once. Infos -// referring to different type IDs but sharing a finite shape will compare and -// hash the same. -struct FiniteShapeHasher { - bool topLevelOnly; - size_t currDepth = 0; - size_t currStep = 0; - std::unordered_map<HeapType, size_t> seen; - - FiniteShapeHasher(bool topLevelOnly = false) : topLevelOnly(topLevelOnly) {} - - size_t hash(Type type); - size_t hash(HeapType heapType); - size_t hash(const TypeInfo& info); - size_t hash(const HeapTypeInfo& info); - size_t hash(const Tuple& tuple); - size_t hash(const Field& field); - size_t hash(const Signature& sig); - size_t hash(const Struct& struct_); - size_t hash(const Array& array); -}; - -// Helper for comparing the shapes of TypeInfos and HeapTypeInfos for equality. -// Like FiniteShapeHasher, keeps track of previously seen HeapTypes. Note that -// this does not test for coinductive equality of the infinite expansion of the -// type tree, but rather tests for equality of the finite shape of the graph. If -// FiniteShapeEquator reports that two type shapes are equal, FiniteShapeHasher -// should produce the same hash for them. -struct FiniteShapeEquator { - bool topLevelOnly; - size_t currDepth = 0; - size_t currStep = 0; - std::unordered_map<HeapType, size_t> seenA, seenB; - - FiniteShapeEquator(bool topLevelOnly = false) : topLevelOnly(topLevelOnly) {} - - bool eq(Type a, Type b); - bool eq(HeapType a, HeapType b); - bool eq(const TypeInfo& a, const TypeInfo& b); - bool eq(const HeapTypeInfo& a, const HeapTypeInfo& b); - bool eq(const Tuple& a, const Tuple& b); - bool eq(const Field& a, const Field& b); - bool eq(const Signature& a, const Signature& b); - bool eq(const Struct& a, const Struct& b); - bool eq(const Array& a, const Array& b); -}; - struct RecGroupHasher { // `group` may or may not be canonical, but any other recursion group it // reaches must be canonical. @@ -733,7 +652,7 @@ HeapTypeInfo& HeapTypeInfo::operator=(const HeapTypeInfo& other) { } bool HeapTypeInfo::operator==(const HeapTypeInfo& other) const { - return FiniteShapeEquator().eq(*this, other); + return this == &other; } template<typename Info> struct Store { @@ -1203,9 +1122,6 @@ std::vector<HeapType> Type::getHeapTypeChildren() { } bool Type::hasLeastUpperBound(Type a, Type b) { - if (getTypeSystem() == TypeSystem::Equirecursive) { - return TypeBounder().hasLeastUpperBound(a, b); - } return getLeastUpperBound(a, b) != Type::none; } @@ -1213,9 +1129,6 @@ Type Type::getLeastUpperBound(Type a, Type b) { if (a == b) { return a; } - if (getTypeSystem() == TypeSystem::Equirecursive) { - return TypeBounder().getLeastUpperBound(a, b); - } if (a == Type::unreachable) { return b; } @@ -1285,9 +1198,6 @@ HeapType::HeapType(Signature sig) { // signature to emulate structural behavior. new (this) HeapType(nominalSignatureCache.getType(sig)); return; - case TypeSystem::Equirecursive: - new (this) HeapType(globalHeapTypeStore.insert(sig)); - return; case TypeSystem::Isorecursive: new (this) HeapType( globalRecGroupStore.insert(std::make_unique<HeapTypeInfo>(sig))); @@ -1304,7 +1214,6 @@ HeapType::HeapType(const Struct& struct_) { #endif switch (getTypeSystem()) { case TypeSystem::Nominal: - case TypeSystem::Equirecursive: new (this) HeapType(globalHeapTypeStore.insert(struct_)); return; case TypeSystem::Isorecursive: @@ -1323,7 +1232,6 @@ HeapType::HeapType(Struct&& struct_) { #endif switch (getTypeSystem()) { case TypeSystem::Nominal: - case TypeSystem::Equirecursive: new (this) HeapType(globalHeapTypeStore.insert(std::move(struct_))); return; case TypeSystem::Isorecursive: @@ -1338,7 +1246,6 @@ HeapType::HeapType(Array array) { assert(!isTemp(array.element.type) && "Leaking temporary type!"); switch (getTypeSystem()) { case TypeSystem::Nominal: - case TypeSystem::Equirecursive: new (this) HeapType(globalHeapTypeStore.insert(array)); return; case TypeSystem::Isorecursive: @@ -1561,9 +1468,6 @@ std::optional<HeapType> HeapType::getLeastUpperBound(HeapType a, HeapType b) { if (b.isBottom()) { return a; } - if (getTypeSystem() == TypeSystem::Equirecursive) { - return TypeBounder().getLeastUpperBound(a, b); - } if (a.isBasic() || b.isBasic()) { return getBasicHeapTypeLUB(getBasicHeapSupertype(a), getBasicHeapSupertype(b)); @@ -1793,34 +1697,13 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { // bottom types. return a == b.getBottom(); } - if (typeSystem == TypeSystem::Nominal || - typeSystem == TypeSystem::Isorecursive) { - // Subtyping must be declared in a nominal system, not derived from - // structure, so we will not recurse. TODO: optimize this search with some - // form of caching. - HeapTypeInfo* curr = getHeapTypeInfo(a); - while ((curr = curr->supertype)) { - if (curr == getHeapTypeInfo(b)) { - return true; - } + // Subtyping must be declared rather than derived from structure, so we will + // not recurse. TODO: optimize this search with some form of caching. + HeapTypeInfo* curr = getHeapTypeInfo(a); + while ((curr = curr->supertype)) { + if (curr == getHeapTypeInfo(b)) { + return true; } - return false; - } - // As we recurse, we will coinductively assume that a == b unless proven - // otherwise. - if (!seen.insert({a, b}).second) { - // We weren't able to disprove that a == b since we last saw them, so the - // relation holds coinductively. - return true; - } - if (a.isSignature() && b.isSignature()) { - return isSubType(a.getSignature(), b.getSignature()); - } - if (a.isArray() && b.isArray()) { - return isSubType(a.getArray(), b.getArray()); - } - if (a.isStruct() && b.isStruct()) { - return isSubType(a.getStruct(), b.getStruct()); } return false; } @@ -1870,212 +1753,6 @@ bool SubTyper::isSubType(const Array& a, const Array& b) { return isSubType(a.element, b.element); } -bool TypeBounder::hasLeastUpperBound(Type a, Type b) { return bool(lub(a, b)); } - -Type TypeBounder::getLeastUpperBound(Type a, Type b) { - auto tempLUB = lub(a, b); - if (!tempLUB) { - return Type::none; - } - if (!isTemp(*tempLUB)) { - // The LUB is already canonical, so we're done. - return *tempLUB; - } - // `tempLUB` is a temporary type owned by `builder`. Since TypeBuilder::build - // returns HeapTypes rather than Types, create a new HeapType definition meant - // only to get `tempLUB` canonicalized in a known location. The use of an - // Array is arbitrary; it might as well have been a Struct. - builder.grow(1); - builder[builder.size() - 1] = Array(Field(*tempLUB, Mutable)); - std::vector<HeapType> built = *builder.build(); - return built.back().getArray().element.type; -} - -std::optional<HeapType> TypeBounder::getLeastUpperBound(HeapType a, - HeapType b) { - auto maybeLub = lub(a, b); - if (!maybeLub) { - return std::nullopt; - } - HeapType l = *maybeLub; - if (!isTemp(l)) { - // The LUB is already canonical, so we're done. - return l; - } - // Find the index corresponding to the LUB. - size_t index = 0; - while (l != builder[index]) { - ++index; - } - // Canonicalize and return the LUB. - return (*builder.build())[index]; -} - -std::optional<Type> TypeBounder::lub(Type a, Type b) { - if (a == b) { - return a; - } - if (a == Type::unreachable) { - return b; - } - if (b == Type::unreachable) { - return a; - } - if (a.isTuple() && b.isTuple()) { - auto tuple = lub(a.getTuple(), b.getTuple()); - if (!tuple) { - return {}; - } - return builder.getTempTupleType(*tuple); - } else if (a.isRef() && b.isRef()) { - if (auto heapType = lub(a.getHeapType(), b.getHeapType())) { - auto nullability = - (a.isNullable() || b.isNullable()) ? Nullable : NonNullable; - return builder.getTempRefType(*heapType, nullability); - } - } - return {}; -} - -std::optional<HeapType> TypeBounder::lub(HeapType a, HeapType b) { - if (a == b) { - return a; - } - if (a.getBottom() != b.getBottom()) { - return {}; - } - if (a.isBottom()) { - return b; - } - if (b.isBottom()) { - return a; - } - - if (a.isBasic() || b.isBasic()) { - return getBasicHeapTypeLUB(getBasicHeapSupertype(a), - getBasicHeapSupertype(b)); - } - - HeapTypeInfo* infoA = getHeapTypeInfo(a); - HeapTypeInfo* infoB = getHeapTypeInfo(b); - - if (infoA->kind != infoB->kind) { - return getBasicHeapTypeLUB(getBasicHeapSupertype(a), - getBasicHeapSupertype(b)); - } - - assert(getTypeSystem() == TypeSystem::Equirecursive); - - // Allocate a new slot to construct the LUB of this pair if we have not - // already seen it before. Canonicalize the pair to have the element with the - // smaller ID first since order does not matter. - auto pair = - a.getID() < b.getID() ? std::make_pair(a, b) : std::make_pair(b, a); - size_t index = builder.size(); - auto result = indices.insert({pair, index}); - if (!result.second) { - // We've seen this pair before; stop recursing and do not allocate. - return builder[result.first->second]; - } - - builder.grow(1); - switch (infoA->kind) { - case HeapTypeInfo::BasicKind: - WASM_UNREACHABLE("unexpected kind"); - case HeapTypeInfo::SignatureKind: { - if (auto sig = lub(infoA->signature, infoB->signature)) { - return builder[index] = *sig; - } else { - return builder[index] = HeapType::func; - } - } - case HeapTypeInfo::StructKind: { - return builder[index] = lub(infoA->struct_, infoB->struct_); - } - case HeapTypeInfo::ArrayKind: { - if (auto array = lub(infoA->array, infoB->array)) { - return builder[index] = *array; - } else { - return builder[index] = HeapType::data; - } - } - } - WASM_UNREACHABLE("unexpected kind"); -} - -std::optional<Tuple> TypeBounder::lub(const Tuple& a, const Tuple& b) { - if (a.types.size() != b.types.size()) { - return {}; - } - Tuple result; - result.types.resize(a.types.size()); - for (size_t i = 0; i < a.types.size(); ++i) { - if (auto type = lub(a.types[i], b.types[i])) { - result.types[i] = *type; - } else { - return {}; - } - } - return result; -} - -std::optional<Field> TypeBounder::lub(const Field& a, const Field& b) { - if (a == b) { - return a; - } - // Mutable fields are invariant, so they would have had to be the same. - if (a.mutable_ == Mutable || b.mutable_ == Mutable) { - return {}; - } - // Packed types must match. - if (a.isPacked() != b.isPacked() || - (a.isPacked() && a.packedType != b.packedType)) { - return {}; - } - // Either the packed types match or the types aren't packed. - Type type; - if (auto type = lub(a.type, b.type)) { - Field result = a; - result.type = *type; - return result; - } else { - return {}; - } -} - -std::optional<Signature> TypeBounder::lub(const Signature& a, - const Signature& b) { - // TODO: Implement proper signature subtyping, covariant in results and - // contravariant in params, once V8 implements it. - if (a != b) { - return {}; - } else { - return a; - } -} - -Struct TypeBounder::lub(const Struct& a, const Struct& b) { - Struct result; - size_t numFields = std::min(a.fields.size(), b.fields.size()); - for (size_t i = 0; i < numFields; ++i) { - if (auto field = lub(a.fields[i], b.fields[i])) { - result.fields.push_back(*field); - } else { - // Stop at the common prefix and ignore the rest. - break; - } - } - return result; -} - -std::optional<Array> TypeBounder::lub(const Array& a, const Array& b) { - if (auto elem = lub(a.element, b.element)) { - return Array{*elem}; - } else { - return {}; - } -} - void TypePrinter::printHeapTypeName(HeapType type) { if (type.isBasic()) { print(type); @@ -2084,15 +1761,6 @@ void TypePrinter::printHeapTypeName(HeapType type) { os << '$' << generator(type).name; } -void TypePrinter::printSupertypeOr(std::optional<HeapType> super, - std::string other) { - if (super) { - printHeapTypeName(*super); - } else { - os << other; - } -} - std::ostream& TypePrinter::print(Type type) { if (type.isBasic()) { switch (type.getBasic()) { @@ -2270,7 +1938,7 @@ std::ostream& TypePrinter::print(const Signature& sig, }; os << "(func"; - if (printSupertypes) { + if (super) { os << "_subtype"; } if (sig.params.getID() != Type::none) { @@ -2281,9 +1949,9 @@ std::ostream& TypePrinter::print(const Signature& sig, os << ' '; printPrefixed("result", sig.results); } - if (printSupertypes) { + if (super) { os << ' '; - printSupertypeOr(super, "func"); + printHeapTypeName(*super); } return os << ')'; } @@ -2291,7 +1959,7 @@ std::ostream& TypePrinter::print(const Signature& sig, std::ostream& TypePrinter::print(const Struct& struct_, std::optional<HeapType> super) { os << "(struct"; - if (printSupertypes) { + if (super) { os << "_subtype"; } if (struct_.fields.size()) { @@ -2304,9 +1972,9 @@ std::ostream& TypePrinter::print(const Struct& struct_, if (struct_.fields.size()) { os << ')'; } - if (printSupertypes) { + if (super) { os << ' '; - printSupertypeOr(super, "data"); + printHeapTypeName(*super); } return os << ')'; } @@ -2314,230 +1982,18 @@ std::ostream& TypePrinter::print(const Struct& struct_, std::ostream& TypePrinter::print(const Array& array, std::optional<HeapType> super) { os << "(array"; - if (printSupertypes) { + if (super) { os << "_subtype"; } os << ' '; print(array.element); - if (printSupertypes) { + if (super) { os << ' '; - printSupertypeOr(super, "data"); + printHeapTypeName(*super); } return os << ')'; } -size_t FiniteShapeHasher::hash(Type type) { - size_t digest = wasm::hash(type.isBasic()); - if (type.isBasic()) { - rehash(digest, type.getID()); - } else { - hash_combine(digest, hash(*getTypeInfo(type))); - } - return digest; -} - -size_t FiniteShapeHasher::hash(HeapType heapType) { - size_t digest = wasm::hash(heapType.isBasic()); - if (heapType.isBasic()) { - rehash(digest, heapType.getID()); - return digest; - } - if (topLevelOnly && currDepth > 0) { - return digest; - } - auto it = seen.find(heapType); - rehash(digest, it != seen.end()); - if (it != seen.end()) { - rehash(digest, it->second); - return digest; - } - seen[heapType] = ++currStep; - ++currDepth; - hash_combine(digest, hash(*getHeapTypeInfo(heapType))); - --currDepth; - return digest; -} - -size_t FiniteShapeHasher::hash(const TypeInfo& info) { - size_t digest = wasm::hash(info.kind); - switch (info.kind) { - case TypeInfo::TupleKind: - hash_combine(digest, hash(info.tuple)); - return digest; - case TypeInfo::RefKind: - rehash(digest, info.ref.nullable); - hash_combine(digest, hash(info.ref.heapType)); - return digest; - } - WASM_UNREACHABLE("unexpected kind"); -} - -size_t FiniteShapeHasher::hash(const HeapTypeInfo& info) { - if (getTypeSystem() == TypeSystem::Nominal || - getTypeSystem() == TypeSystem::Isorecursive) { - return wasm::hash(uintptr_t(&info)); - } - // If the HeapTypeInfo is not finalized, then it is mutable and its shape - // might change in the future. In that case, fall back to pointer identity to - // keep the hash consistent until all the TypeBuilder's types are finalized. - size_t digest = wasm::hash(info.isFinalized); - if (!info.isFinalized) { - rehash(digest, uintptr_t(&info)); - return digest; - } - rehash(digest, info.kind); - switch (info.kind) { - case HeapTypeInfo::BasicKind: - WASM_UNREACHABLE("Basic HeapTypeInfo should have been canonicalized"); - case HeapTypeInfo::SignatureKind: - hash_combine(digest, hash(info.signature)); - return digest; - case HeapTypeInfo::StructKind: - hash_combine(digest, hash(info.struct_)); - return digest; - case HeapTypeInfo::ArrayKind: - hash_combine(digest, hash(info.array)); - return digest; - } - WASM_UNREACHABLE("unexpected kind"); -} - -size_t FiniteShapeHasher::hash(const Tuple& tuple) { - size_t digest = wasm::hash(tuple.types.size()); - for (auto type : tuple.types) { - hash_combine(digest, hash(type)); - } - return digest; -} - -size_t FiniteShapeHasher::hash(const Field& field) { - size_t digest = wasm::hash(field.packedType); - rehash(digest, field.mutable_); - hash_combine(digest, hash(field.type)); - return digest; -} - -size_t FiniteShapeHasher::hash(const Signature& sig) { - size_t digest = hash(sig.params); - hash_combine(digest, hash(sig.results)); - return digest; -} - -size_t FiniteShapeHasher::hash(const Struct& struct_) { - size_t digest = wasm::hash(struct_.fields.size()); - for (const auto& field : struct_.fields) { - hash_combine(digest, hash(field)); - } - return digest; -} - -size_t FiniteShapeHasher::hash(const Array& array) { - return hash(array.element); -} - -bool FiniteShapeEquator::eq(Type a, Type b) { - if (a.isBasic() != b.isBasic()) { - return false; - } else if (a.isBasic()) { - return a.getID() == b.getID(); - } else { - return eq(*getTypeInfo(a), *getTypeInfo(b)); - } -} - -bool FiniteShapeEquator::eq(HeapType a, HeapType b) { - if (a.isBasic() != b.isBasic()) { - return false; - } else if (a.isBasic()) { - return a.getID() == b.getID(); - } - if (topLevelOnly && currDepth > 0) { - return true; - } - auto itA = seenA.find(a); - auto itB = seenB.find(b); - if ((itA != seenA.end()) != (itB != seenB.end())) { - return false; - } else if (itA != seenA.end()) { - return itA->second == itB->second; - } - seenA[a] = seenB[b] = ++currStep; - ++currDepth; - bool ret = eq(*getHeapTypeInfo(a), *getHeapTypeInfo(b)); - --currDepth; - return ret; -} - -bool FiniteShapeEquator::eq(const TypeInfo& a, const TypeInfo& b) { - if (a.kind != b.kind) { - return false; - } - switch (a.kind) { - case TypeInfo::TupleKind: - return eq(a.tuple, b.tuple); - case TypeInfo::RefKind: - return a.ref.nullable == b.ref.nullable && - eq(a.ref.heapType, b.ref.heapType); - } - WASM_UNREACHABLE("unexpected kind"); -} - -bool FiniteShapeEquator::eq(const HeapTypeInfo& a, const HeapTypeInfo& b) { - if (getTypeSystem() == TypeSystem::Nominal || - getTypeSystem() == TypeSystem::Isorecursive) { - return &a == &b; - } - if (a.isFinalized != b.isFinalized) { - return false; - } else if (!a.isFinalized) { - // See comment on corresponding FiniteShapeHasher method. - return &a == &b; - } - if (a.kind != b.kind) { - return false; - } - switch (a.kind) { - case HeapTypeInfo::BasicKind: - WASM_UNREACHABLE("Basic HeapTypeInfo should have been canonicalized"); - case HeapTypeInfo::SignatureKind: - return eq(a.signature, b.signature); - case HeapTypeInfo::StructKind: - return eq(a.struct_, b.struct_); - case HeapTypeInfo::ArrayKind: - return eq(a.array, b.array); - } - WASM_UNREACHABLE("unexpected kind"); -} - -bool FiniteShapeEquator::eq(const Tuple& a, const Tuple& b) { - return std::equal(a.types.begin(), - a.types.end(), - b.types.begin(), - b.types.end(), - [&](const Type& x, const Type& y) { return eq(x, y); }); -} - -bool FiniteShapeEquator::eq(const Field& a, const Field& b) { - return a.packedType == b.packedType && a.mutable_ == b.mutable_ && - eq(a.type, b.type); -} - -bool FiniteShapeEquator::eq(const Signature& a, const Signature& b) { - return eq(a.params, b.params) && eq(a.results, b.results); -} - -bool FiniteShapeEquator::eq(const Struct& a, const Struct& b) { - return std::equal(a.fields.begin(), - a.fields.end(), - b.fields.begin(), - b.fields.end(), - [&](const Field& x, const Field& y) { return eq(x, y); }); -} - -bool FiniteShapeEquator::eq(const Array& a, const Array& b) { - return eq(a.element, b.element); -} - size_t RecGroupHasher::operator()() const { size_t digest = wasm::hash(group.size()); for (auto type : group) { @@ -3125,480 +2581,6 @@ void CanonicalizationState::updateUses(ReplacementMap& replacements, } } -// A wrapper around a HeapType that provides equality and hashing based only on -// its top-level shape, up to but not including its closest HeapType -// descendants. This is the shape that determines the most fine-grained initial -// partitions for DFA minimization and also the shape that determines the -// "alphabet" for transitioning to the child HeapTypes in the DFA view of the -// type definition. -struct ShallowHeapType { - HeapType heapType; - - ShallowHeapType(HeapType heapType) : heapType(heapType) {} - bool operator==(const ShallowHeapType& other) const; -}; - -bool ShallowHeapType::operator==(const ShallowHeapType& other) const { - return FiniteShapeEquator(/*topLevelOnly=*/true) - .eq(this->heapType, other.heapType); -} - -} // anonymous namespace -} // namespace wasm - -namespace std { - -template<> class hash<wasm::ShallowHeapType> { -public: - size_t operator()(const wasm::ShallowHeapType& type) const { - return wasm::FiniteShapeHasher(/*topLevelOnly=*/true).hash(type.heapType); - } -}; - -} // namespace std - -namespace wasm { -namespace { - -// The Refined Partitions data structure used in Valmari-Lehtinen DFA -// minimization. The translation from terms used in the Valmari-Lehtinen paper -// to the more expanded terms used here is: -// -// Block => Set -// elems => elements -// loc => elementIndices -// sidx => setIndices -// first => beginnings -// end => endings -// mid => pivots -// -struct Partitions { - // The number of sets. - size_t sets = 0; - - // The partitioned elements. Elements in the same set are next to each other. - // Within each set, "marked" elements come first followed by "unmarked" - // elements. - std::vector<size_t> elements; - - // Maps elements to their indices in `elements`. - std::vector<size_t> elementIndices; - - // Maps elements to their sets, identified by an index. - std::vector<size_t> setIndices; - - // Maps sets to the indices of their first elements in `elements`. - std::vector<size_t> beginnings; - - // Maps sets to (one past) the indices of their ends in `elements`. - std::vector<size_t> endings; - - // Maps sets to the indices of their first unmarked elements in `elements`. - std::vector<size_t> pivots; - - Partitions() = default; - - // Allocate space up front so we never need to re-allocate. The actual - // contents of all the vectors will need to be externally initialized, - // though. - Partitions(size_t size) - : elements(size), elementIndices(size), setIndices(size), beginnings(size), - endings(size), pivots(size) {} - - struct Set { - using Iterator = std::vector<size_t>::iterator; - - Partitions& partitions; - size_t index; - - Set(Partitions& partitions, size_t index) - : partitions(partitions), index(index) {} - - Iterator begin() { - return partitions.elements.begin() + partitions.beginnings[index]; - } - Iterator end() { - return partitions.elements.begin() + partitions.endings[index]; - } - size_t size() { - return partitions.endings[index] - partitions.beginnings[index]; - } - - bool hasMarks() { - return partitions.pivots[index] != partitions.beginnings[index]; - } - - // Split the set between marked and unmarked elements if there are both - // marked and unmarked elements. Unmark all elements of this set regardless. - // Return the index of the new partition or 0 if there was no split. - size_t split(); - }; - - Set getSet(size_t index) { return {*this, index}; } - - // Returns the set containing an element, which can be iterated upon. The set - // may be invalidated by calls to `mark` or `Set::split`. - Set getSetForElem(size_t element) { return getSet(setIndices[element]); } - - void mark(size_t element) { - size_t index = elementIndices[element]; - size_t set = setIndices[element]; - size_t pivot = pivots[set]; - if (index >= pivot) { - // Move the pivot element into the location of the newly marked element. - elements[index] = elements[pivot]; - elementIndices[elements[index]] = index; - // Move the newly marked element into the pivot location. - elements[pivot] = element; - elementIndices[element] = pivot; - // Update the pivot index to mark the element. - ++pivots[set]; - } - } -}; - -size_t Partitions::Set::split() { - size_t begin = partitions.beginnings[index]; - size_t end = partitions.endings[index]; - size_t pivot = partitions.pivots[index]; - if (pivot == begin) { - // No elements marked, so there is nothing to do. - return 0; - } - if (pivot == end) { - // All elements were marked, so just unmark them. - partitions.pivots[index] = begin; - return 0; - } - // Create a new set covering the marked region. - size_t newIndex = partitions.sets++; - partitions.beginnings[newIndex] = begin; - partitions.pivots[newIndex] = begin; - partitions.endings[newIndex] = pivot; - for (size_t i = begin; i < pivot; ++i) { - partitions.setIndices[partitions.elements[i]] = newIndex; - } - // Update the old set. The end and pivot are already correct. - partitions.beginnings[index] = pivot; - return newIndex; -} - -// Uses Valmari and Lehtinen's partial DFA minimization algorithm to construct a -// minimal type definition graph from an input graph. See -// https://arxiv.org/pdf/0802.2826.pdf. -struct ShapeCanonicalizer { - // The results of shape canonicalization. - CanonicalizationState::ReplacementMap replacements; - - ShapeCanonicalizer(std::vector<HeapType>& roots); - -private: - // Maps state indices to their underlying HeapTypes and vice versa. - std::vector<HeapType> heapTypes; - std::unordered_map<HeapType, size_t> states; - - // A DFA transition into a state. - struct Transition { - size_t pred; - size_t label; - }; - - // The transitions arranged such that the transitions leading to state `q` are - // `transitions[transitionIndices[q] : transitionIndices[q+1]]`. - std::vector<Transition> transitions; - std::vector<size_t> transitionIndices; - - // The state partitions. - Partitions partitions; - - // The splitters, which are partitions of the input transitions. - Partitions splitters; - - void initialize(std::vector<HeapType>& roots); - void createReplacements(); - - // Return pointers to the non-basic HeapType children of `ht`, including - // BasicKind children. - std::vector<HeapType*> getChildren(HeapType ht); - -#if TRACE_CANONICALIZATION - void dumpPartitions() { - for (size_t set = 0; set < partitions.sets; ++set) { - std::cerr << "Partition " << set << '\n'; - std::cerr << "begin: " << partitions.beginnings[set] - << ", end: " << partitions.endings[set] - << ", pivot: " << partitions.pivots[set] << '\n'; - for (size_t index : partitions.getSet(set)) { - assert(partitions.setIndices[index] == set); - std::cerr << heapTypes[index] << '\n'; - } - std::cerr << '\n'; - } - } -#endif -}; - -ShapeCanonicalizer::ShapeCanonicalizer(std::vector<HeapType>& roots) { -#if TRACE_CANONICALIZATION - std::cerr << "Root HeapTypes:\n"; - for (auto root : roots) { - std::cerr << root << '\n'; - } - std::cerr << '\n'; -#endif - - initialize(roots); - -#if TRACE_CANONICALIZATION - std::cerr << "Initial partitions:\n"; - dumpPartitions(); -#endif - - // The list of splitter partitions that might be able to split states in some - // state partition. Starts out containing all splitter partitions. - std::vector<size_t> potentialSplitters; - potentialSplitters.reserve(splitters.sets); - for (size_t i = 0; i < splitters.sets; ++i) { - potentialSplitters.push_back(i); - } - - while (!potentialSplitters.empty()) { - size_t potentialSplitter = potentialSplitters.back(); - potentialSplitters.pop_back(); - - // The partitions that may be able to be split. - std::vector<size_t> markedPartitions; - - // Mark states that are predecessors via this splitter partition. - for (size_t transition : splitters.getSet(potentialSplitter)) { - size_t state = transitions[transition].pred; - auto partition = partitions.getSetForElem(state); - if (!partition.hasMarks()) { - markedPartitions.push_back(partition.index); - } - partitions.mark(state); - } - - // Try to split each partition with marked states. - for (size_t partition : markedPartitions) { - size_t newPartition = partitions.getSet(partition).split(); - if (!newPartition) { - // There was nothing to split. - continue; - } - - // We only want to keep using the smaller of the two split partitions. - if (partitions.getSet(newPartition).size() < - partitions.getSet(partition).size()) { - newPartition = partition; - } - - // The splitter partitions that may need to be split to match the new - // split of the state partitions. - std::vector<size_t> markedSplitters; - - // Mark transitions that lead to the newly split off states. - for (size_t state : partitions.getSet(newPartition)) { - for (size_t t = transitionIndices[state], - end = transitionIndices[state + 1]; - t < end; - ++t) { - auto splitter = splitters.getSetForElem(t); - if (!splitter.hasMarks()) { - markedSplitters.push_back(splitter.index); - } - splitters.mark(t); - } - } - - // Split the splitters and update `potentialSplitters`. - for (size_t splitter : markedSplitters) { - size_t newSplitter = splitters.getSet(splitter).split(); - if (newSplitter) { - potentialSplitters.push_back(newSplitter); - } - } - } - } - -#if TRACE_CANONICALIZATION - std::cerr << "Final partitions:\n"; - dumpPartitions(); -#endif - - createReplacements(); -} - -void ShapeCanonicalizer::initialize(std::vector<HeapType>& roots) { - struct Initializer : HeapTypeGraphWalker<Initializer> { - ShapeCanonicalizer& canonicalizer; - - // Maps shallow HeapType shapes to corresponding HeapType indices. - InsertOrderedMap<ShallowHeapType, std::vector<size_t>> initialPartitions; - - // Maps `dest` HeapType indices to their input transitions. - std::map<size_t, std::vector<Transition>> transitions; - size_t numTransitions = 0; - - Initializer(ShapeCanonicalizer& canonicalizer) - : canonicalizer(canonicalizer) {} - - size_t getIndex(HeapType type) { - // Allocate an index for the HeapType if it doesn't already have one. - auto inserted = - canonicalizer.states.insert({type, canonicalizer.states.size()}); - if (inserted.second) { - canonicalizer.heapTypes.push_back(type); - } - return inserted.first->second; - } - - void noteHeapType(HeapType type) { - size_t index = getIndex(type); - - // Allocate an initial partition for this HeapType's shallow shape if one - // does not already exist, then append the HeapType to the partition. - initialPartitions[ShallowHeapType(type)].push_back(index); - - // Traverse the non-basic children to collect graph edges, i.e. - // transitions in the DFA. - struct TransitionInitializer - : HeapTypeChildWalker<TransitionInitializer> { - Initializer& initializer; - size_t parent; - size_t label = 0; - TransitionInitializer(Initializer& initializer, size_t parent) - : initializer(initializer), parent(parent) {} - void noteChild(HeapType* childType) { - if (childType->isBasic()) { - return; - } - // Record the transition from parent to child. - size_t child = initializer.getIndex(*childType); - initializer.transitions[child].push_back({parent, label++}); - ++initializer.numTransitions; - } - }; - TransitionInitializer(*this, index).walkRoot(&type); - } - }; - - Initializer initializer(*this); - for (HeapType& root : roots) { - initializer.walkRoot(&root); - } - - // Now that we have initialized maps containing all the necessary data, use - // them to initialize the flattened vector-based data structures that we will - // use to efficiently run the minimization algorithm. - - // Initialize `partitions`. - partitions = Partitions(heapTypes.size()); - size_t elementIndex = 0; - for (auto pair : initializer.initialPartitions) { - size_t set = partitions.sets++; - partitions.beginnings[set] = elementIndex; - partitions.pivots[set] = elementIndex; - for (size_t elem : pair.second) { - partitions.elements[elementIndex] = elem; - partitions.elementIndices[elem] = elementIndex; - partitions.setIndices[elem] = set; - ++elementIndex; - } - partitions.endings[set] = elementIndex; - } - - // Initialize `transitions` and `transitionIndices`. - transitions.reserve(initializer.numTransitions); - transitionIndices.resize(heapTypes.size() + 1); - for (size_t dest = 0; dest < heapTypes.size(); ++dest) { - // Record the first index of transitions leading to `dest`. - transitionIndices[dest] = transitions.size(); - auto it = initializer.transitions.find(dest); - if (it != initializer.transitions.end()) { - transitions.insert( - transitions.end(), it->second.begin(), it->second.end()); - } - } - // Record one-past the end of the transitions leading to the final `dest`. - transitionIndices[heapTypes.size()] = transitions.size(); - - // Initialize `splitters`. The initial sets are partitioned by destination - // state partition and transition label. - splitters = Partitions(transitions.size()); - elementIndex = 0; - for (size_t statePartition = 0; statePartition < partitions.sets; - ++statePartition) { - // The in-transitions leading to states in the current partition, organized - // by transition label. - std::map<size_t, std::vector<size_t>> currTransitions; - for (size_t state : partitions.getSet(statePartition)) { - for (size_t transition = transitionIndices[state], - end = transitionIndices[state + 1]; - transition < end; - ++transition) { - currTransitions[transitions[transition].label].push_back(transition); - } - } - // Create a splitter partition for each in-transition label leading to the - // current state partition. - for (auto& pair : currTransitions) { - size_t set = splitters.sets++; - splitters.beginnings[set] = elementIndex; - splitters.pivots[set] = elementIndex; - for (size_t transition : pair.second) { - splitters.elements[elementIndex] = transition; - splitters.elementIndices[transition] = elementIndex; - splitters.setIndices[transition] = set; - ++elementIndex; - } - splitters.endings[set] = elementIndex; - } - } -} - -void ShapeCanonicalizer::createReplacements() { - // Create a single new HeapTypeInfo for each partition. Initialize each new - // HeapTypeInfo as a copy of a representative HeapTypeInfo from its partition, - // then patch all the children of the new HeapTypeInfos to refer to other new - // HeapTypeInfos rather than the original HeapTypeInfos. This newly formed - // graph will have a shape coinductively equivalent to the original graph's - // shape, but each type definition will be minimal and distinct. - // - // However, for partitions that already contain globally canonical types, find - // and use the corresponding HeapTypeInfo directly without copying. Since the - // partitions reachable from a globally canonical type will also contain a - // globally canonical type, no temporary types will end up being patched into - // the globally canonical types and we can skip patching the children of those - // types. - for (size_t p = 0; p < partitions.sets; ++p) { - auto partition = partitions.getSet(p); - auto it = std::find_if(partition.begin(), - partition.end(), - [this](size_t i) { return !isTemp(heapTypes[i]); }); - HeapType newType; - auto partitionIt = partition.begin(); - if (it != partition.end()) { - newType = heapTypes[*it]; - } else { - // We do not already know about a globally canonical type for this - // partition. Create a copy and a replacement that owns it. - HeapType oldType = heapTypes[*partitionIt++]; - const auto& representative = *getHeapTypeInfo(oldType); - auto info = std::make_unique<HeapTypeInfo>(representative); - info->isTemp = true; - newType = asHeapType(info); - replacements.insert({oldType, std::move(info)}); - } - // Create replacements for all the other types in the partition. - for (; partitionIt != partition.end(); ++partitionIt) { - if (partitionIt != it) { - replacements.insert({heapTypes[*partitionIt], newType}); - } - } - } -} - // Replaces temporary types and heap types in a type definition graph with their // globally canonical versions to prevent temporary types or heap type from // leaking into the global stores. @@ -3682,24 +2664,8 @@ void globallyCanonicalize(CanonicalizationState& state) { #endif } -void canonicalizeEquirecursive(CanonicalizationState& state) { - // Equirecursive types always have null supertypes and recursion groups. - for (auto& info : state.newInfos) { - info->recGroup = nullptr; - info->supertype = nullptr; - } - - // Canonicalize the shape of the type definition graph. - ShapeCanonicalizer minimized(state.results); - state.update(minimized.replacements); -} - std::optional<TypeBuilder::Error> validateSubtyping(const std::vector<HeapType>& types) { - if (getTypeSystem() == TypeSystem::Equirecursive) { - // Subtyping is not explicitly declared, so nothing to check. - return {}; - } for (size_t i = 0; i < types.size(); ++i) { HeapType type = types[i]; if (type.isBasic()) { @@ -3966,9 +2932,6 @@ TypeBuilder::BuildResult TypeBuilder::build() { #endif switch (typeSystem) { - case TypeSystem::Equirecursive: - canonicalizeEquirecursive(state); - break; case TypeSystem::Nominal: if (auto error = canonicalizeNominal(state)) { return {*error}; @@ -4091,7 +3054,7 @@ size_t hash<wasm::TypeInfo>::operator()(const wasm::TypeInfo& info) const { size_t hash<wasm::HeapTypeInfo>::operator()(const wasm::HeapTypeInfo& info) const { - return wasm::FiniteShapeHasher().hash(info); + return wasm::hash(uintptr_t(&info)); } } // namespace std |