diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index 0124f4715d2..6316dc7e57e 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -24,7 +24,6 @@ #include "ir/eh-utils.h" #include "ir/localize.h" -#include "ir/module-utils.h" #include "ir/names.h" #include "ir/ordering.h" #include "ir/struct-utils.h" @@ -42,10 +41,8 @@ namespace { // Information about usage of a field. struct FieldInfo { - // This represents a normal write for normal fields. For a descriptor, we only - // note "dangerous" writes, specifically ones which might trap (when the - // descriptor in a struct.new is nullable), which is a special situation we - // must avoid. + // This represents a normal write for normal fields. (Unused descriptors are + // optimized in Unsubtyping instead.) bool hasWrite = false; bool hasRead = false; @@ -87,9 +84,8 @@ struct FieldInfoScanner HeapType type, Index index, FieldInfo& info) { - if (index == StructUtils::DescriptorIndex && expr->type.isNonNullable()) { - // A non-dangerous write to a descriptor, which as mentioned above, we do - // not track. + if (index == StructUtils::DescriptorIndex) { + // We do not optimize descriptors. Ignore them. return; } info.noteWrite(); @@ -105,6 +101,9 @@ struct FieldInfoScanner } void noteRead(HeapType type, Index index, FieldInfo& info) { + if (index == StructUtils::DescriptorIndex) { + return; + } info.noteRead(); } @@ -133,15 +132,6 @@ struct GlobalTypeOptimization : public Pass { static const Index RemovedField = Index(-1); std::unordered_map> indexesAfterRemovals; - // The types that no longer need a descriptor. - std::unordered_set haveUnneededDescriptors; - - // Descriptor types that are not needed by their described types but that - // still need to be descriptors for their own subtypes and supertypes to be - // valid. We will keep them descriptors by having them describe trivial new - // placeholder types. - std::unordered_set descriptorsOfPlaceholders; - void run(Module* module) override { if (!module->features.hasGC()) { return; @@ -151,8 +141,6 @@ struct GlobalTypeOptimization : public Pass { Fatal() << "GTO requires --closed-world"; } - auto trapsNeverHappen = getPassOptions().trapsNeverHappen; - // Find and analyze struct operations inside each function. StructUtils::FunctionStructValuesMap functionNewInfos(*module), functionSetGetInfos(*module); @@ -163,23 +151,6 @@ struct GlobalTypeOptimization : public Pass { // Combine the data from the functions. functionSetGetInfos.combineInto(combinedSetGetInfos); - // Custom descriptor handling: We need to look at struct.news, which - // normally we ignore (nothing in a struct.new can cause fields to remain - // mutable, or force the field to stay around. We cannot ignore them with CD - // because struct.news can now trap, and removing the descriptor could - // change things, so we must be careful. (Without traps, though, this is - // unnecessary.) - if (module->features.hasCustomDescriptors() && !trapsNeverHappen) { - for (auto& [func, infos] : functionNewInfos) { - for (auto& [type, info] : infos) { - if (info.desc.hasWrite) { - // Copy the descriptor write to the info we will propagate below. - combinedSetGetInfos[type].desc.noteWrite(); - } - } - } - } - // Propagate information to super and subtypes on set/get infos: // // * For removing unread fields, we can only remove a field if it is never @@ -408,85 +379,18 @@ struct GlobalTypeOptimization : public Pass { indexesAfterRemovals[type] = indexesAfterRemoval; } } - - // Process the descriptor. - if (auto desc = type.getDescriptorType()) { - // To remove a descriptor, it must not be read via supertypes and it - // must not have to remain in supertypes because e.g. they are public. - // It must also have no write (see above, we note only dangerous writes - // which might trap), as if it could trap, we'd have no easy way to - // remove it in a global scope. - // TODO: We could check and handle the global scope specifically, but - // the trapsNeverHappen flag avoids this problem entirely anyhow. - // - // This does not handle descriptor subtyping, see below. - auto super = type.getDeclaredSuperType(); - bool superNeedsDescriptor = super && super->getDescriptorType() && - !haveUnneededDescriptors.count(*super); - bool descriptorIsUsed = - dataFromSupers.desc.hasRead || - (dataFromSupers.desc.hasWrite && !trapsNeverHappen); - if (!superNeedsDescriptor && !descriptorIsUsed) { - haveUnneededDescriptors.insert(type); - } - } - } - - // Handle descriptor subtyping: - // - // A -> A.desc - // ^ - // B -> B.desc - // - // Say we want to optimize A to no longer have a descriptor. Then A.desc - // will no longer describe A. But A.desc still needs to be a descriptor for - // it to remain a valid supertype of B.desc. To allow the optimization of A - // to proceed, we will introduce a placeholder type for A.desc to describe, - // keeping it a descriptor type. - if (!haveUnneededDescriptors.empty()) { - StructUtils::TypeHierarchyPropagator - descPropagator(subTypes); - - // Populate the initial data: Any descriptor we did not see was unneeded, - // is needed. - StructUtils::TypeHierarchyPropagator< - StructUtils::CombinableBool>::StructMap remainingDesciptors; - for (auto type : subTypes.types) { - if (auto desc = type.getDescriptorType()) { - if (!haveUnneededDescriptors.count(type)) { - // This descriptor type is needed. - remainingDesciptors[*desc].value = true; - } - } - } - - // Propagate. - descPropagator.propagateToSuperAndSubTypes(remainingDesciptors); - - // Determine the set of descriptor types that will need placeholder - // describees. - for (auto [type, kept] : remainingDesciptors) { - if (kept.value) { - auto desc = type.getDescribedType(); - assert(desc); - if (haveUnneededDescriptors.count(*desc)) { - descriptorsOfPlaceholders.insert(type); - } - } - } } // If we found things that can be removed, remove them from instructions. // (Note that we must do this first, while we still have the old heap types // that we can identify, and only after this should we update all the types // throughout the module.) - if (!indexesAfterRemovals.empty() || !haveUnneededDescriptors.empty()) { + if (!indexesAfterRemovals.empty()) { updateInstructions(*module); } // Update the types in the entire module. - if (!indexesAfterRemovals.empty() || !canBecomeImmutable.empty() || - !haveUnneededDescriptors.empty()) { + if (!indexesAfterRemovals.empty() || !canBecomeImmutable.empty()) { updateTypes(*module); } } @@ -494,41 +398,10 @@ struct GlobalTypeOptimization : public Pass { void updateTypes(Module& wasm) { class TypeRewriter : public GlobalTypeRewriter { GlobalTypeOptimization& parent; - // Differentiate the first occurrence of a descriptor of a placeholder - // type, which is actually saving space for the placeholder itself, and - // the next occurrence, which is real. - bool sawPlaceholder = false; public: TypeRewriter(Module& wasm, GlobalTypeOptimization& parent) : GlobalTypeRewriter(wasm), parent(parent) {} - - std::vector getSortedTypes(PredecessorGraph preds) override { - auto types = GlobalTypeRewriter::getSortedTypes(std::move(preds)); - // Intersperse placeholder types throughout the sorted types. Each - // descriptor type that needs a placeholder describee will be duplicated - // in the list of sorted types. The first appearance will be modified to - // become the placeholder describee and the subsequent appearance will - // be modified to describe the preceding placeholder. We represent the - // placeholder slots here using their intended descriptor types for two - // reasons: first, it lets us easily recognize the placeholder slots - // in modifyTypeBuilderEntry. Second, it ensures that the type-to-index - // mapping created in GlobalTypeRewriter::rebuiltTypes does not end up - // mapping the placeholder types to any index, since their mappings are - // immediately overwritten by the following "real" occurrences of the - // descriptor types. - std::vector typesWithPlaceholders; - for (auto type : types) { - if (parent.descriptorsOfPlaceholders.count(type)) { - // Insert the type an extra time to make space for the placeholder. - typesWithPlaceholders.push_back(type); - } - typesWithPlaceholders.push_back(type); - } - - return typesWithPlaceholders; - } - void modifyStruct(HeapType oldStructType, Struct& struct_) override { auto& newFields = struct_.fields; @@ -581,45 +454,6 @@ struct GlobalTypeOptimization : public Pass { } } } - - void modifyTypeBuilderEntry(TypeBuilder& typeBuilder, - Index i, - HeapType oldType) override { - if (!oldType.isStruct()) { - return; - } - - // Remove an unneeded descriptor. - if (parent.haveUnneededDescriptors.count(oldType)) { - typeBuilder[i].descriptor(std::nullopt); - } - - // Remove an unneeded describee or describe a placeholder type. - if (auto described = oldType.getDescribedType()) { - if (parent.haveUnneededDescriptors.count(*described)) { - if (parent.descriptorsOfPlaceholders.count(oldType)) { - if (!sawPlaceholder) { - // This is the placeholder describee. Set its descriptor to be - // the succeeding real descriptor type. - typeBuilder[i] = Struct{}; - typeBuilder[i].setShared(described->getShared()); - typeBuilder[i].descriptor(typeBuilder[i + 1]); - typeBuilder[i].describes(std::nullopt); - typeBuilder[i].subTypeOf(std::nullopt); - sawPlaceholder = true; - } else { - // This is the real placedholder-describing descriptor type. - // Have it describe the preceding placeholder describee. - typeBuilder[i].describes(typeBuilder[i - 1]); - sawPlaceholder = false; - } - } else { - // This type no longer needs its describee. - typeBuilder[i].describes(std::nullopt); - } - } - } - } }; TypeRewriter(wasm, *this).update(); @@ -650,20 +484,18 @@ struct GlobalTypeOptimization : public Pass { if (curr->type == Type::unreachable) { return; } + if (curr->isWithDefault()) { + // No indices to remove. + return; + } auto type = curr->type.getHeapType(); - auto removeDesc = parent.haveUnneededDescriptors.count(type); - - // There may be no indexes to remove, if we are only removing the - // descriptor. - std::vector* indexesAfterRemoval = nullptr; - // There are also no indexes to remove if we only write default values. - if (!curr->isWithDefault()) { - auto iter = parent.indexesAfterRemovals.find(type); - if (iter != parent.indexesAfterRemovals.end()) { - indexesAfterRemoval = &iter->second; - } + + auto iter = parent.indexesAfterRemovals.find(type); + if (iter == parent.indexesAfterRemovals.end()) { + return; } + std::vector& indexesAfterRemoval = iter->second; // Ensure any children with non-trivial effects are replaced with // local.gets, so that we can remove/reorder to our hearts' content. @@ -678,45 +510,32 @@ struct GlobalTypeOptimization : public Pass { needEHFixups = true; } - if (indexesAfterRemoval) { - // Remove and reorder operands. - auto& operands = curr->operands; - assert(indexesAfterRemoval->size() == operands.size()); - - Index removed = 0; - std::vector old(operands.begin(), operands.end()); - for (Index i = 0; i < operands.size(); ++i) { - auto newIndex = (*indexesAfterRemoval)[i]; - if (newIndex != RemovedField) { - assert(newIndex < operands.size()); - operands[newIndex] = old[i]; - } else { - ++removed; - if (!func && - EffectAnalyzer(getPassOptions(), *getModule(), old[i]).trap) { - removedTrappingInits.push_back(old[i]); - } - } - } - if (removed) { - operands.resize(operands.size() - removed); + // Remove and reorder operands. + auto& operands = curr->operands; + assert(indexesAfterRemoval.size() == operands.size()); + + Index removed = 0; + std::vector old(operands.begin(), operands.end()); + for (Index i = 0; i < operands.size(); ++i) { + auto newIndex = indexesAfterRemoval[i]; + if (newIndex != RemovedField) { + assert(newIndex < operands.size()); + operands[newIndex] = old[i]; } else { - // If we didn't remove anything then we must have reordered (or else - // we have done pointless work). - assert(*indexesAfterRemoval != - makeIdentity(indexesAfterRemoval->size())); + ++removed; + if (!func && + EffectAnalyzer(getPassOptions(), *getModule(), old[i]).trap) { + removedTrappingInits.push_back(old[i]); + } } } - - if (removeDesc) { - // We already handled the case of a possible trap here, so we can - // remove the descriptor, but must be careful of nested effects (our - // descriptor may be ok to remove, but a nested struct.new may not). - if (!func && - EffectAnalyzer(getPassOptions(), *getModule(), curr->desc).trap) { - removedTrappingInits.push_back(curr->desc); - } - curr->desc = nullptr; + if (removed) { + operands.resize(operands.size() - removed); + } else { + // If we didn't remove anything then we must have reordered (or else + // we have done pointless work). + assert(indexesAfterRemoval != + makeIdentity(indexesAfterRemoval.size())); } } @@ -761,18 +580,6 @@ struct GlobalTypeOptimization : public Pass { curr->index = newIndex; } - void visitRefCast(RefCast* curr) { - // Unreachable ref.cast_desc instructions would not have been counted as - // reading the descriptor field, so their descriptor operands may no - // longer be descriptors. This is invalid, so replace such casts - // entirely. - if (curr->type == Type::unreachable && curr->desc && - curr->desc->type != Type::unreachable) { - assert(curr->ref->type == Type::unreachable); - replaceCurrent(curr->ref); - } - } - void visitFunction(Function* curr) { if (needEHFixups) { EHUtils::handleBlockNestedPops(curr, *getModule()); diff --git a/test/lit/passes/gto-desc-tnh.wast b/test/lit/passes/gto-desc-tnh.wast deleted file mode 100644 index 2a6c10be046..00000000000 --- a/test/lit/passes/gto-desc-tnh.wast +++ /dev/null @@ -1,265 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. - -;; RUN: foreach %s %t wasm-opt -all --closed-world --gto --preserve-type-order -S -o - | filecheck %s -;; RUN: foreach %s %t wasm-opt -tnh -all --closed-world --gto --preserve-type-order -S -o - | filecheck %s --check-prefix=T_N_H - -;; The descriptor passed in is nullable, so it might trap. We only remove it -;; when traps never happen. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (descriptor $B (struct))) - ;; T_N_H: (rec - ;; T_N_H-NEXT: (type $A (struct)) - (type $A (descriptor $B (struct))) - ;; CHECK: (type $B (describes $A (struct))) - ;; T_N_H: (type $B (struct)) - (type $B (describes $A (struct))) - ) - - ;; CHECK: (type $2 (func (param (ref null (exact $B))))) - - ;; CHECK: (func $test (type $2) (param $nullable (ref null (exact $B))) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $A - ;; CHECK-NEXT: (local.get $nullable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; T_N_H: (type $2 (func (param (ref null (exact $B))))) - - ;; T_N_H: (func $test (type $2) (param $nullable (ref null (exact $B))) - ;; T_N_H-NEXT: (local $A (ref $A)) - ;; T_N_H-NEXT: (local $B (ref $B)) - ;; T_N_H-NEXT: (drop - ;; T_N_H-NEXT: (struct.new_default $A) - ;; T_N_H-NEXT: ) - ;; T_N_H-NEXT: ) - (func $test (param $nullable (ref null (exact $B))) - (local $A (ref $A)) - (local $B (ref $B)) - (drop - (struct.new $A - (local.get $nullable) - ) - ) - ) -) - -;; As above, with the struct.new in the global scope. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (descriptor $B (struct))) - ;; T_N_H: (rec - ;; T_N_H-NEXT: (type $A (struct)) - (type $A (descriptor $B (struct))) - ;; CHECK: (type $B (describes $A (struct))) - ;; T_N_H: (type $B (struct)) - (type $B (describes $A (struct))) - ) - - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (global $g anyref (struct.new_default $A - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: )) - ;; T_N_H: (type $2 (func)) - - ;; T_N_H: (global $g anyref (struct.new_default $A)) - (global $g anyref (struct.new $A - (ref.null $B) - )) - - ;; CHECK: (func $test (type $2) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: ) - ;; T_N_H: (func $test (type $2) - ;; T_N_H-NEXT: (local $A (ref $A)) - ;; T_N_H-NEXT: (local $B (ref $B)) - ;; T_N_H-NEXT: ) - (func $test - (local $A (ref $A)) - (local $B (ref $B)) - ) -) - -;; $A and $B have descriptors, and the descriptors also subtype: -;; -;; A -> A.desc -;; ^ -;; B -> B.desc -;; -;; $B is written a null descriptor, so we cannot optimize it when traps are -;; possible. This means $A.desc must remain a descriptor even as we optimize $A, -;; so we give $A.desc a placeholder describee. With TNH, we can optimize without -;; the placeholder. -;; -;; This tests subtyping of descriptors *without* subtyping of describees. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (sub (struct))) - ;; T_N_H: (rec - ;; T_N_H-NEXT: (type $A (sub (struct))) - (type $A (sub (descriptor $A.desc (struct)))) - ;; CHECK: (type $1 (sub (descriptor $A.desc (struct)))) - - ;; CHECK: (type $A.desc (sub (describes $1 (struct)))) - ;; T_N_H: (type $A.desc (sub (struct))) - (type $A.desc (sub (describes $A (struct )))) - - ;; CHECK: (type $B (sub (descriptor $B.desc (struct)))) - ;; T_N_H: (type $B (sub (struct))) - (type $B (sub (descriptor $B.desc (struct)))) - ;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct)))) - ;; T_N_H: (type $B.desc (sub $A.desc (struct))) - (type $B.desc (sub $A.desc (describes $B (struct)))) - ) - - ;; CHECK: (type $5 (func)) - - ;; CHECK: (func $test (type $5) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $B - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; T_N_H: (type $4 (func)) - - ;; T_N_H: (func $test (type $4) - ;; T_N_H-NEXT: (drop - ;; T_N_H-NEXT: (struct.new_default $B) - ;; T_N_H-NEXT: ) - ;; T_N_H-NEXT: ) - (func $test - (drop - (struct.new_default $B - (ref.null none) - ) - ) - ) -) - -;; A similar situation, but now the thing that stops optimizing $B is not a -;; null descriptor but a use. We cannot optimize even without traps. -;; Subtyping of descriptors *without* subtyping of describees. -;; -;; $A's descriptor can be removed, but $A.desc needs to be given a placeholder -;; describee. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) - ;; T_N_H: (rec - ;; T_N_H-NEXT: (type $A (sub (descriptor $A.desc (struct)))) - (type $A (sub (descriptor $A.desc (struct)))) - ;; CHECK: (type $A.desc (sub (describes $A (struct)))) - ;; T_N_H: (type $A.desc (sub (describes $A (struct)))) - (type $A.desc (sub (describes $A (struct )))) - - ;; CHECK: (type $B (sub (struct))) - ;; T_N_H: (type $B (sub (struct))) - (type $B (sub (descriptor $B.desc (struct)))) - ;; CHECK: (type $3 (sub (descriptor $B.desc (struct)))) - - ;; CHECK: (type $B.desc (sub $A.desc (describes $3 (struct)))) - ;; T_N_H: (type $3 (sub (descriptor $B.desc (struct)))) - - ;; T_N_H: (type $B.desc (sub $A.desc (describes $3 (struct)))) - (type $B.desc (sub $A.desc (describes $B (struct)))) - ) - - ;; CHECK: (type $5 (func)) - - ;; CHECK: (func $test (type $5) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.get_desc $A - ;; CHECK-NEXT: (struct.new_default $A - ;; CHECK-NEXT: (struct.new_default $A.desc) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; T_N_H: (type $5 (func)) - - ;; T_N_H: (func $test (type $5) - ;; T_N_H-NEXT: (local $B (ref $B)) - ;; T_N_H-NEXT: (drop - ;; T_N_H-NEXT: (ref.get_desc $A - ;; T_N_H-NEXT: (struct.new_default $A - ;; T_N_H-NEXT: (struct.new_default $A.desc) - ;; T_N_H-NEXT: ) - ;; T_N_H-NEXT: ) - ;; T_N_H-NEXT: ) - ;; T_N_H-NEXT: ) - (func $test - (local $B (ref $B)) ;; keep $B alive - (drop - (ref.get_desc $A - (struct.new $A - (struct.new $A.desc) - ) - ) - ) - ) -) - -;; A chain of descriptors, where we can remove the outer one, but must be -;; careful not to remove nested children. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct)) - ;; T_N_H: (rec - ;; T_N_H-NEXT: (type $A (struct)) - (type $A (descriptor $B (struct))) - ;; CHECK: (type $B (descriptor $C (struct))) - ;; T_N_H: (type $B (struct)) - (type $B (describes $A (descriptor $C (struct)))) - ;; CHECK: (type $C (describes $B (struct))) - ;; T_N_H: (type $C (struct)) - (type $C (describes $B (struct))) - ) - - ;; CHECK: (type $3 (func)) - - ;; CHECK: (global $g anyref (struct.new_default $A)) - ;; T_N_H: (type $3 (func)) - - ;; T_N_H: (global $g anyref (struct.new_default $A)) - (global $g anyref - (struct.new $A ;; The outer struct.new $A is ok to remove, - (struct.new $B ;; but the inner struct.new $B is not, due - (ref.null $C) ;; to its null descriptor. We keep the inner - ) ;; one around as a new global later (but not - ) ;; if traps cannot happen). - ) - - ;; CHECK: (global $gto-removed-0 (ref (exact $B)) (struct.new_default $B - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: )) - - ;; CHECK: (func $test (type $3) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (local $C (ref $C)) - ;; CHECK-NEXT: ) - ;; T_N_H: (func $test (type $3) - ;; T_N_H-NEXT: (local $A (ref $A)) - ;; T_N_H-NEXT: (local $B (ref $B)) - ;; T_N_H-NEXT: (local $C (ref $C)) - ;; T_N_H-NEXT: ) - (func $test - (local $A (ref $A)) - (local $B (ref $B)) - (local $C (ref $C)) - ) -) - diff --git a/test/lit/passes/gto-desc.wast b/test/lit/passes/gto-desc.wast index 77b051db95b..1f6fe34b08a 100644 --- a/test/lit/passes/gto-desc.wast +++ b/test/lit/passes/gto-desc.wast @@ -17,8 +17,6 @@ ;; CHECK: (type $4 (func (param (ref $struct) (ref $used-pair)))) - ;; CHECK: (type $5 (func (param (ref $struct)))) - ;; CHECK: (global $nullable-desc (ref null (exact $desc)) (struct.new_default $desc)) (global $nullable-desc (ref null (exact $desc)) (struct.new $desc)) @@ -129,280 +127,10 @@ ) ) ) - - ;; CHECK: (func $use-desc (type $5) (param $struct (ref $struct)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.get_desc $struct - ;; CHECK-NEXT: (local.get $struct) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $use-desc (param $struct (ref $struct)) - ;; Use the descriptor, so this test is not trivial. - (drop - (ref.get_desc $struct - (local.get $struct) - ) - ) - ) -) - -;; The descriptor here is not needed. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct)) - (type $A (descriptor $B (struct))) - ;; CHECK: (type $B (struct)) - (type $B (describes $A (struct))) - ) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (func $test (type $2) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $B (ref $B)) - ) -) - -;; As above, but even creating the type does not force us to keep the -;; descriptor, if it is never used. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct)) - (type $A (descriptor $B (struct))) - ;; CHECK: (type $B (struct)) - (type $B (describes $A (struct))) - ) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (func $test (type $2) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $B (ref $B)) - (drop - (struct.new $A - (struct.new $B) - ) - ) - ) -) - -;; As above, with the creation in the global scope. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct)) - (type $A (descriptor $B (struct))) - ;; CHECK: (type $B (struct)) - (type $B (describes $A (struct))) - ) - - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (global $g anyref (struct.new_default $A)) - (global $g anyref (struct.new $A - (struct.new $B) - )) - - ;; CHECK: (func $test (type $2) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $B (ref $B)) - ) -) - -;; Both descriptors in this chain are unneeded. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct)) - (type $A (descriptor $B (struct))) - ;; CHECK: (type $B (struct)) - (type $B (describes $A (descriptor $C (struct)))) - ;; CHECK: (type $C (struct)) - (type $C (describes $B (struct))) - ) - - ;; CHECK: (type $3 (func)) - - ;; CHECK: (func $test (type $3) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (local $C (ref $C)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $B (ref $B)) - (local $C (ref $C)) - (drop - (struct.new $A - (struct.new $B - (struct.new $C) - ) - ) - ) - ) -) - -;; ref.get_desc keeps the descriptor. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (descriptor $B (struct))) - (type $A (descriptor $B (struct))) - ;; CHECK: (type $B (describes $A (struct))) - (type $B (describes $A (struct))) - ) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (func $test (type $2) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (local.set $A - ;; CHECK-NEXT: (struct.new_default $A - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.get_desc $A - ;; CHECK-NEXT: (local.get $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $B (ref $B)) - (local.set $A - (struct.new $A - (struct.new $B) - ) - ) - (drop - (ref.get_desc $A - (local.get $A) - ) - ) - ) -) - -;; ref.cast_desc keeps the descriptor. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (descriptor $B (struct))) - (type $A (descriptor $B (struct))) - ;; CHECK: (type $B (describes $A (struct))) - (type $B (describes $A (struct))) - ) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (func $test (type $2) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (local.set $A - ;; CHECK-NEXT: (struct.new_default $A - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast_desc (ref (exact $A)) - ;; CHECK-NEXT: (local.get $A) - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $B (ref $B)) - (local.set $A - (struct.new $A - (struct.new $B) - ) - ) - (drop - (ref.cast_desc (ref null (exact $A)) - (local.get $A) - (struct.new $B) - ) - ) - ) -) - -;; br_on_cast_desc keeps the descriptor. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (descriptor $B (struct))) - (type $A (descriptor $B (struct))) - ;; CHECK: (type $B (describes $A (struct))) - (type $B (describes $A (struct))) - ) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (func $test (type $2) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (local.set $A - ;; CHECK-NEXT: (struct.new_default $A - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $l (result (ref null $A)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast_desc $l (ref $A) (ref (exact $A)) - ;; CHECK-NEXT: (local.get $A) - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $B (ref $B)) - (local.set $A - (struct.new $A - (struct.new $B) - ) - ) - (drop - (block $l (result (ref null $A)) - (br_on_cast_desc $l anyref (ref null $A) - (local.get $A) - (struct.new $B) - ) - (unreachable) - ) - ) - ) ) -;; br_on_cast_desc_fail keeps the descriptor. +;; The descriptor here is not needed, but we now optimize descriptors in +;; Unsubtyping, so we do nothing here. (module (rec ;; CHECK: (rec @@ -417,1013 +145,11 @@ ;; CHECK: (func $test (type $2) ;; CHECK-NEXT: (local $A (ref $A)) ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (local.set $A - ;; CHECK-NEXT: (struct.new_default $A - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $l (result (ref null $A)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast_desc_fail $l (ref $A) (ref (exact $A)) - ;; CHECK-NEXT: (local.get $A) - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (local $A (ref $A)) (local $B (ref $B)) - (local.set $A - (struct.new $A - (struct.new $B) - ) - ) - (drop - (block $l (result (ref null $A)) - (br_on_cast_desc_fail $l anyref (ref null $A) - (local.get $A) - (struct.new $B) - ) - (unreachable) - ) - ) - ) -) - -;; The descriptor can be removed, but its effects must be preserved. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct)) - (type $A (descriptor $B (struct))) - ;; CHECK: (type $B (struct)) - (type $B (describes $A (struct))) - ) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (func $test (type $2) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref (exact $B))) - ;; CHECK-NEXT: (local $2 (ref (exact $B))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref (exact $A))) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.tee $B - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $B (ref (exact $B))) - (drop - (struct.new $A - (local.tee $B - (struct.new $B) - ) - ) - ) - ) -) - -;; Test removing indexes /immutability as well as removing a descriptor. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct (field $c i32))) - (type $A (descriptor $B (struct (field $a (mut i32)) (field $b (mut i32)) (field $c (mut i32))))) - ;; CHECK: (type $B (struct)) - (type $B (describes $A (struct (field (mut i32))))) - ) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (func $test (type $2) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref (exact $B))) - ;; CHECK-NEXT: (local $2 (ref (exact $B))) - ;; CHECK-NEXT: (local.set $A - ;; CHECK-NEXT: (block (result (ref (exact $A))) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.tee $B - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (i32.const 30) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (block (result (ref $A)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $A $c - ;; CHECK-NEXT: (local.get $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (block (result (ref (exact $B))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 9999) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $B (ref (exact $B))) - (local.set $A - (struct.new $A - (i32.const 10) - (i32.const 20) - (i32.const 30) - (local.tee $B - (struct.new $B - (i32.const 1337) - ) - ) - ) - ) - ;; Write only the middle field (we can remove it as write-only). - (struct.set $A $b - (local.get $A) - (i32.const 42) - ) - ;; Read only the last field. We can make it immutable. - (drop - (struct.get $A $c - (local.get $A) - ) - ) - ;; Also mutate the field in the descriptor, which should not bother us. - (struct.set $B 0 - (local.get $B) - (i32.const 9999) - ) ) ) -;; As above, but with struct.new_default. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct (field i32))) - (type $A (descriptor $B (struct (field (mut i32)) (field (mut i32)) (field (mut i32))))) - ;; CHECK: (type $B (struct)) - (type $B (describes $A (struct (field (mut i32))))) - ) - - ;; CHECK: (type $2 (func)) - - ;; CHECK: (func $test (type $2) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref (exact $B))) - ;; CHECK-NEXT: (local $2 (ref (exact $B))) - ;; CHECK-NEXT: (local.set $A - ;; CHECK-NEXT: (block (result (ref (exact $A))) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.tee $B - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (block (result (ref $A)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (local.get $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (block (result (ref (exact $B))) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 9999) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $B (ref (exact $B))) - (local.set $A - (struct.new_default $A - (local.tee $B - (struct.new $B - (i32.const 1337) - ) - ) - ) - ) - (struct.set $A 1 - (local.get $A) - (i32.const 42) - ) - (drop - (struct.get $A 2 - (local.get $A) - ) - ) - (struct.set $B 0 - (local.get $B) - (i32.const 9999) - ) - ) -) - -;; Multiple types with descriptors, only one of whom can be removed. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct)) - (type $A (descriptor $B (struct))) - ;; CHECK: (type $B (struct)) - (type $B (describes $A (struct))) - - ;; CHECK: (type $C (descriptor $D (struct))) - (type $C (descriptor $D (struct))) - ;; CHECK: (type $D (describes $C (struct))) - (type $D (describes $C (struct))) - ) - - ;; CHECK: (type $4 (func)) - - ;; CHECK: (func $test (type $4) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $C (ref $C)) - ;; CHECK-NEXT: (local.set $A - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $C - ;; CHECK-NEXT: (struct.new_default $C - ;; CHECK-NEXT: (struct.new_default $D) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.get_desc $C - ;; CHECK-NEXT: (local.get $C) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $C (ref $C)) - (local.set $A - (struct.new $A - (struct.new $B) - ) - ) - (local.set $C - (struct.new $C - (struct.new $D) - ) - ) - ;; Use the descriptor in $C but not $A. - (drop - (ref.get_desc $C - (local.get $C) - ) - ) - ) -) - -;; Subtyping. All these descriptors can be optimized away. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (sub (struct))) - (type $A (sub (descriptor $A.desc (struct)))) - ;; CHECK: (type $A.desc (sub (struct))) - (type $A.desc (sub (describes $A (struct)))) - - ;; CHECK: (type $B (sub $A (struct))) - (type $B (sub $A (descriptor $B.desc (struct)))) - ;; CHECK: (type $B.desc (sub $A.desc (struct))) - (type $B.desc (sub $A.desc (describes $B (struct)))) - - ;; CHECK: (type $C (sub $B (struct))) - (type $C (sub $B (descriptor $C.desc (struct)))) - ;; CHECK: (type $C.desc (sub $B.desc (struct))) - (type $C.desc (sub $B.desc (describes $C (struct)))) - ) - - ;; CHECK: (type $6 (func)) - - ;; CHECK: (func $test (type $6) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $A.desc (ref $A.desc)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (local $B.desc (ref $B.desc)) - ;; CHECK-NEXT: (local $C (ref $C)) - ;; CHECK-NEXT: (local $C.desc (ref $C.desc)) - ;; CHECK-NEXT: (local.set $A - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $B - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $C - ;; CHECK-NEXT: (struct.new_default $C) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $A.desc (ref $A.desc)) - (local $B (ref $B)) - (local $B.desc (ref $B.desc)) - (local $C (ref $C)) - (local $C.desc (ref $C.desc)) - (local.set $A - (struct.new $A - (struct.new $A.desc) - ) - ) - (local.set $B - (struct.new $B - (struct.new $B.desc) - ) - ) - (local.set $C - (struct.new $C - (struct.new $C.desc) - ) - ) - ) -) - -;; As above, but add a use of $A's descriptor. We cannot remove a descriptor -;; without removing it from supertypes, so we cannot optimize anything. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) - (type $A (sub (descriptor $A.desc (struct)))) - ;; CHECK: (type $A.desc (sub (describes $A (struct)))) - (type $A.desc (sub (describes $A (struct)))) - - ;; CHECK: (type $B (sub $A (descriptor $B.desc (struct)))) - (type $B (sub $A (descriptor $B.desc (struct)))) - ;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct)))) - (type $B.desc (sub $A.desc (describes $B (struct)))) - - ;; CHECK: (type $C (sub $B (descriptor $C.desc (struct)))) - (type $C (sub $B (descriptor $C.desc (struct)))) - ;; CHECK: (type $C.desc (sub $B.desc (describes $C (struct)))) - (type $C.desc (sub $B.desc (describes $C (struct)))) - ) - - ;; CHECK: (type $6 (func)) - - ;; CHECK: (func $test (type $6) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $A.desc (ref $A.desc)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (local $B.desc (ref $B.desc)) - ;; CHECK-NEXT: (local $C (ref $C)) - ;; CHECK-NEXT: (local $C.desc (ref $C.desc)) - ;; CHECK-NEXT: (local.set $A - ;; CHECK-NEXT: (struct.new_default $A - ;; CHECK-NEXT: (struct.new_default $A.desc) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $B - ;; CHECK-NEXT: (struct.new_default $B - ;; CHECK-NEXT: (struct.new_default $B.desc) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $C - ;; CHECK-NEXT: (struct.new_default $C - ;; CHECK-NEXT: (struct.new_default $C.desc) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.get_desc $A - ;; CHECK-NEXT: (local.get $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $A.desc (ref $A.desc)) - (local $B (ref $B)) - (local $B.desc (ref $B.desc)) - (local $C (ref $C)) - (local $C.desc (ref $C.desc)) - (local.set $A - (struct.new $A - (struct.new $A.desc) - ) - ) - (local.set $B - (struct.new $B - (struct.new $B.desc) - ) - ) - (local.set $C - (struct.new $C - (struct.new $C.desc) - ) - ) - ;; This is new. - (drop - (ref.get_desc $A - (local.get $A) - ) - ) - ) -) - -;; As above, but use $B's. Now we can optimize $A's descriptor, but we need to -;; give it a placeholder type to describe. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (sub (struct))) - (type $A (sub (descriptor $A.desc (struct)))) - ;; CHECK: (type $1 (sub (descriptor $A.desc (struct)))) - - ;; CHECK: (type $A.desc (sub (describes $1 (struct)))) - (type $A.desc (sub (describes $A (struct)))) - - ;; CHECK: (type $B (sub $A (descriptor $B.desc (struct)))) - (type $B (sub $A (descriptor $B.desc (struct)))) - ;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct)))) - (type $B.desc (sub $A.desc (describes $B (struct)))) - - ;; CHECK: (type $C (sub $B (descriptor $C.desc (struct)))) - (type $C (sub $B (descriptor $C.desc (struct)))) - ;; CHECK: (type $C.desc (sub $B.desc (describes $C (struct)))) - (type $C.desc (sub $B.desc (describes $C (struct)))) - ) - - ;; CHECK: (type $7 (func)) - - ;; CHECK: (func $test (type $7) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $A.desc (ref $A.desc)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (local $B.desc (ref $B.desc)) - ;; CHECK-NEXT: (local $C (ref $C)) - ;; CHECK-NEXT: (local $C.desc (ref $C.desc)) - ;; CHECK-NEXT: (local.set $A - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $B - ;; CHECK-NEXT: (struct.new_default $B - ;; CHECK-NEXT: (struct.new_default $B.desc) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $C - ;; CHECK-NEXT: (struct.new_default $C - ;; CHECK-NEXT: (struct.new_default $C.desc) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.get_desc $B - ;; CHECK-NEXT: (local.get $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $A.desc (ref $A.desc)) - (local $B (ref $B)) - (local $B.desc (ref $B.desc)) - (local $C (ref $C)) - (local $C.desc (ref $C.desc)) - (local.set $A - (struct.new $A - (struct.new $A.desc) - ) - ) - (local.set $B - (struct.new $B - (struct.new $B.desc) - ) - ) - (local.set $C - (struct.new $C - (struct.new $C.desc) - ) - ) - ;; This changed. - (drop - (ref.get_desc $B - (local.get $B) - ) - ) - ) -) - -;; As above, with $C. Now we optimize $A and $B with placeholders. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (sub (struct))) - (type $A (sub (descriptor $A.desc (struct)))) - ;; CHECK: (type $1 (sub (descriptor $A.desc (struct)))) - - ;; CHECK: (type $A.desc (sub (describes $1 (struct)))) - (type $A.desc (sub (describes $A (struct)))) - - ;; CHECK: (type $B (sub $A (struct))) - (type $B (sub $A (descriptor $B.desc (struct)))) - ;; CHECK: (type $4 (sub (descriptor $B.desc (struct)))) - - ;; CHECK: (type $B.desc (sub $A.desc (describes $4 (struct)))) - (type $B.desc (sub $A.desc (describes $B (struct)))) - - ;; CHECK: (type $C (sub $B (descriptor $C.desc (struct)))) - (type $C (sub $B (descriptor $C.desc (struct)))) - ;; CHECK: (type $C.desc (sub $B.desc (describes $C (struct)))) - (type $C.desc (sub $B.desc (describes $C (struct)))) - ) - - ;; CHECK: (type $8 (func)) - - ;; CHECK: (func $test (type $8) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $A.desc (ref $A.desc)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (local $B.desc (ref $B.desc)) - ;; CHECK-NEXT: (local $C (ref $C)) - ;; CHECK-NEXT: (local $C.desc (ref $C.desc)) - ;; CHECK-NEXT: (local.set $A - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $B - ;; CHECK-NEXT: (struct.new_default $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $C - ;; CHECK-NEXT: (struct.new_default $C - ;; CHECK-NEXT: (struct.new_default $C.desc) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.get_desc $C - ;; CHECK-NEXT: (local.get $C) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $A.desc (ref $A.desc)) - (local $B (ref $B)) - (local $B.desc (ref $B.desc)) - (local $C (ref $C)) - (local $C.desc (ref $C.desc)) - (local.set $A - (struct.new $A - (struct.new $A.desc) - ) - ) - (local.set $B - (struct.new $B - (struct.new $B.desc) - ) - ) - (local.set $C - (struct.new $C - (struct.new $C.desc) - ) - ) - ;; This changed. - (drop - (ref.get_desc $C - (local.get $C) - ) - ) - ) -) - -;; Here we cannot optimize with placeholders because the public supertype needs -;; to keep its descriptor and its subtypes therefore need to keep their related -;; descriptors. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $public (sub (descriptor $public.desc (struct)))) - (type $public (sub (descriptor $public.desc (struct)))) - ;; CHECK: (type $public.desc (sub (describes $public (struct)))) - (type $public.desc (sub (describes $public (struct)))) - ) - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $unused (sub $public (descriptor $unused.desc (struct)))) - (type $unused (sub $public (descriptor $unused.desc (struct)))) - ;; CHECK: (type $unused.desc (sub $public.desc (describes $unused (struct)))) - (type $unused.desc (sub $public.desc (describes $unused (struct)))) - - ;; CHECK: (type $used (sub $unused (descriptor $used.desc (struct)))) - (type $used (sub $unused (descriptor $used.desc (struct)))) - ;; CHECK: (type $used.desc (sub $unused.desc (describes $used (struct)))) - (type $used.desc (sub $unused.desc (describes $used (struct)))) - ) - - ;; CHECK: (type $6 (func (param (ref $used)) (result (ref $used.desc)))) - - ;; CHECK: (global $public (ref null $public) (ref.null none)) - (global $public (export "public") (ref null $public) (ref.null none)) - - ;; CHECK: (export "public" (global $public)) - - ;; CHECK: (func $use (type $6) (param $used (ref $used)) (result (ref $used.desc)) - ;; CHECK-NEXT: (ref.get_desc $used - ;; CHECK-NEXT: (local.get $used) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $use (param $used (ref $used)) (result (ref $used.desc)) - (ref.get_desc $used - (local.get $used) - ) - ) -) - -;; Now the public supertype does not have a descriptor, so we can still -;; optimize. -(module - (rec - ;; CHECK: (type $public (sub (struct))) - (type $public (sub (struct))) - ) - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $unused (sub $public (struct))) - (type $unused (sub $public (descriptor $unused.desc (struct)))) - ;; CHECK: (type $2 (sub (descriptor $unused.desc (struct)))) - - ;; CHECK: (type $unused.desc (sub (describes $2 (struct)))) - (type $unused.desc (sub (describes $unused (struct)))) - - ;; CHECK: (type $used (sub $unused (descriptor $used.desc (struct)))) - (type $used (sub $unused (descriptor $used.desc (struct)))) - ;; CHECK: (type $used.desc (sub $unused.desc (describes $used (struct)))) - (type $used.desc (sub $unused.desc (describes $used (struct)))) - ) - - ;; CHECK: (type $6 (func (param (ref $used)) (result (ref $used.desc)))) - - ;; CHECK: (global $public (ref null $public) (ref.null none)) - (global $public (export "public") (ref null $public) (ref.null none)) - - ;; CHECK: (export "public" (global $public)) - - ;; CHECK: (func $use (type $6) (param $used (ref $used)) (result (ref $used.desc)) - ;; CHECK-NEXT: (ref.get_desc $used - ;; CHECK-NEXT: (local.get $used) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $use (param $used (ref $used)) (result (ref $used.desc)) - (ref.get_desc $used - (local.get $used) - ) - ) -) - -;; Now we can optimize $A with a placeholder, but the placeholder must be -;; shared. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (sub (shared (struct)))) - (type $A (sub (shared (descriptor $A.desc (struct))))) - ;; CHECK: (type $1 (sub (shared (descriptor $A.desc (struct))))) - - ;; CHECK: (type $A.desc (sub (shared (describes $1 (struct))))) - (type $A.desc (sub (shared (describes $A (struct))))) - - ;; CHECK: (type $B (sub $A (shared (descriptor $B.desc (struct))))) - (type $B (sub $A (shared (descriptor $B.desc (struct))))) - ;; CHECK: (type $B.desc (sub $A.desc (shared (describes $B (struct))))) - (type $B.desc (sub $A.desc (shared (describes $B (struct))))) - ) - - ;; CHECK: (type $5 (func)) - - ;; CHECK: (func $test (type $5) - ;; CHECK-NEXT: (local $A (ref $A)) - ;; CHECK-NEXT: (local $A.desc (ref $A.desc)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (local $B.desc (ref $B.desc)) - ;; CHECK-NEXT: (local.set $A - ;; CHECK-NEXT: (struct.new_default $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $B - ;; CHECK-NEXT: (struct.new_default $B - ;; CHECK-NEXT: (struct.new_default $B.desc) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.get_desc $B - ;; CHECK-NEXT: (local.get $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $A (ref $A)) - (local $A.desc (ref $A.desc)) - (local $B (ref $B)) - (local $B.desc (ref $B.desc)) - (local.set $A - (struct.new $A - (struct.new $A.desc) - ) - ) - (local.set $B - (struct.new $B - (struct.new $B.desc) - ) - ) - (drop - (ref.get_desc $B - (local.get $B) - ) - ) - ) -) - -;; Here we would optimize $unused with a placeholder, except that we don't -;; include it in the output at all because it is unused. We should not get -;; confused and try to emit a placeholder for it anyway. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $public (sub (descriptor $public.desc (struct)))) - (type $public (sub (descriptor $public.desc (struct)))) - ;; CHECK: (type $public.desc (sub (describes $public (struct)))) - (type $public.desc (sub (describes $public (struct)))) - ) - (rec - (type $unused (descriptor $unused.desc (struct))) - (type $unused.desc (sub $public.desc (describes $unused (struct)))) - ;; CHECK: (type $private (struct)) - (type $private (struct)) - ) - - ;; CHECK: (global $public (ref null $public) (ref.null none)) - (global $public (export "public") (ref null $public) (ref.null none)) - ;; CHECK: (global $private (ref null $private) (ref.null none)) - (global $private (ref null $private) (ref.null none)) -) - -;; Regression test for a bug where we accidentally replaced empty struct types -;; with placeholders. -;; CHECK: (export "public" (global $public)) -(module - ;; CHECK: (type $public (struct)) - (type $public (struct)) - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $super (sub (struct))) - (type $super (sub (descriptor $super.desc (struct)))) - ;; CHECK: (type $2 (sub (descriptor $super.desc (struct)))) - - ;; CHECK: (type $super.desc (sub (describes $2 (struct)))) - (type $super.desc (sub (describes $super (struct)))) - ;; CHECK: (type $sub (sub $super (descriptor $sub.desc (struct)))) - (type $sub (sub $super (descriptor $sub.desc (struct)))) - ;; CHECK: (type $sub.desc (sub $super.desc (describes $sub (struct)))) - (type $sub.desc (sub $super.desc (describes $sub (struct)))) - ) - - ;; CHECK: (type $6 (func (param (ref $sub)))) - - ;; CHECK: (global $public (ref null $public) (ref.null none)) - (global $public (export "public") (ref null $public) (ref.null none)) - - ;; CHECK: (export "public" (global $public)) - - ;; CHECK: (func $test (type $6) (param $sub (ref $sub)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.get_desc $sub - ;; CHECK-NEXT: (local.get $sub) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $public) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test (param $sub (ref $sub)) - ;; Use $sub's descriptor to keep it a descriptor. This forces $super.desc to - ;; describe a placeholder. - (drop - (ref.get_desc $sub - (local.get $sub) - ) - ) - ;; We should not accidentally replace this trivial struct with the - ;; placeholder, which would cause a validation failure because we do not - ;; supply a descriptor in this allocation. - (drop - (struct.new_default $public) - ) - ) -) - -;; Regression test for a bug where a placeholder was not set up to be described -;; by its intended descriptor if that intended descriptor also happened to have -;; its own unneeded descriptor. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $public (descriptor $public.desc (struct))) - (type $public (descriptor $public.desc (struct))) - ;; CHECK: (type $public.desc (sub (describes $public (struct)))) - (type $public.desc (sub (describes $public (struct)))) - ) - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $private (struct)) - (type $private (descriptor $private.desc (struct))) - ;; CHECK: (type $3 (sub (descriptor $private.desc (struct)))) - - ;; CHECK: (type $private.desc (sub $public.desc (describes $3 (struct)))) - (type $private.desc (sub $public.desc (describes $private (descriptor $private.meta (struct))))) - ;; CHECK: (type $private.meta (struct)) - (type $private.meta (describes $private.desc (struct))) - ) - - ;; Force $private.desc to remain a descriptor. - ;; CHECK: (global $public (ref null $public) (ref.null none)) - (global $public (export "public") (ref null $public) (ref.null none)) - - ;; CHECK: (global $private (ref null $private) (ref.null none)) - (global $private (ref null $private) (ref.null none)) -) - -;; Sibling types $A and $B. The supertype that connects them should not stop us -;; from optimizing $B here, even though $A cannot be optimized. -;; CHECK: (export "public" (global $public)) -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $super (sub (struct))) - (type $super (sub (struct))) - - ;; CHECK: (type $A (sub $super (descriptor $A.desc (struct)))) - (type $A (sub $super (descriptor $A.desc (struct)))) - ;; CHECK: (type $A.desc (describes $A (struct))) - (type $A.desc (describes $A (struct))) - - ;; CHECK: (type $B (sub $super (struct))) - (type $B (sub $super (descriptor $B.desc (struct)))) - ;; CHECK: (type $B.desc (struct)) - (type $B.desc (describes $B (struct))) - ) - - ;; CHECK: (type $5 (func (param (ref $A)))) - - ;; CHECK: (func $test (type $5) (param $A (ref $A)) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.get_desc $A - ;; CHECK-NEXT: (local.get $A) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test (param $A (ref $A)) - (local $B (ref $B)) - (drop - (ref.get_desc $A - (local.get $A) - ) - ) - ) -) - -;; As above, but now with a cast of the supertype. We can still optimize $B but -;; not $A. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $super (sub (struct))) - (type $super (sub (struct))) - - ;; CHECK: (type $A (sub $super (descriptor $A.desc (struct)))) - (type $A (sub $super (descriptor $A.desc (struct)))) - ;; CHECK: (type $A.desc (describes $A (struct))) - (type $A.desc (describes $A (struct))) - - ;; CHECK: (type $B (sub $super (struct))) - (type $B (sub $super (descriptor $B.desc (struct)))) - ;; CHECK: (type $B.desc (struct)) - (type $B.desc (describes $B (struct))) - ) - - ;; CHECK: (type $5 (func (param (ref $super) (ref (exact $A.desc))) (result anyref))) - - ;; CHECK: (func $test (type $5) (param $ref (ref $super)) (param $desc (ref (exact $A.desc))) (result anyref) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (ref.cast_desc (ref (exact $A)) - ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: (local.get $desc) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test (param $ref (ref $super)) (param $desc (ref (exact $A.desc))) (result anyref) - (local $B (ref $B)) - (ref.cast_desc (ref (exact $A)) - (local.get $ref) - (local.get $desc) - ) - ) -) - -;; As above, but now with a cast of a basic type. We can still optimize $B but -;; not $A. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) - (type $A (sub (descriptor $A.desc (struct)))) - ;; CHECK: (type $A.desc (sub (describes $A (struct)))) - (type $A.desc (sub (describes $A (struct)))) - - ;; CHECK: (type $B (sub (struct))) - (type $B (sub (descriptor $B.desc (struct)))) - ;; CHECK: (type $B.desc (sub (struct))) - (type $B.desc (sub (describes $B (struct)))) - ) - - ;; CHECK: (type $4 (func (param anyref (ref (exact $A.desc))) (result (ref (exact $A))))) - - ;; CHECK: (func $cast_anyref (type $4) (param $ref anyref) (param $desc (ref (exact $A.desc))) (result (ref (exact $A))) - ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (ref.cast_desc (ref (exact $A)) - ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: (local.get $desc) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $cast_anyref (param $ref anyref) (param $desc (ref (exact $A.desc))) (result (ref (exact $A))) - (local $B (ref $B)) - ;; The cast input is anyref. That means any struct could be sent in, but we - ;; only need to prevent optimization of $A, who is the cast output. $B can - ;; still be optimized. - (ref.cast_desc (ref (exact $A)) - (local.get $ref) - (local.get $desc) - ) - ) -) - -;; We can optimize away the descriptor since its only use is unreachable, but -;; we must update that use to remain valid despite being unreachable. -(module - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $struct (sub (struct))) - (type $struct (sub (descriptor $desc (struct)))) - ;; CHECK: (type $desc (sub (struct))) - (type $desc (sub (describes $struct (struct)))) - ) - ;; CHECK: (type $2 (func)) - - ;; CHECK: (func $test (type $2) - ;; CHECK-NEXT: (local $struct (ref $struct)) - ;; CHECK-NEXT: (local $desc (ref $desc)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (local $struct (ref $struct)) - (local $desc (ref $desc)) - (drop - (ref.cast_desc (ref $struct) - (unreachable) - (struct.new $desc) - ) - ) - ) -) diff --git a/test/lit/passes/unsubtyping-desc.wast b/test/lit/passes/unsubtyping-desc.wast index bf0af50c0f2..6d43fad0a06 100644 --- a/test/lit/passes/unsubtyping-desc.wast +++ b/test/lit/passes/unsubtyping-desc.wast @@ -1208,6 +1208,41 @@ ;; CHECK: (global $unsubtyping-removed-0 (ref (exact $desc)) (struct.new_default $desc ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: )) +(module + ;; We could optimize away the descriptor since its only use is unreachable, + ;; but instead we do nothing and depend on other passes clean up the + ;; unreachable code first. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct)))) + (type $struct (sub (descriptor $desc (struct)))) + ;; CHECK: (type $desc (sub (describes $struct (struct)))) + (type $desc (sub (describes $struct (struct)))) + ) + ;; CHECK: (type $2 (func)) + + ;; CHECK: (func $test (type $2) + ;; CHECK-NEXT: (local $struct (ref $struct)) + ;; CHECK-NEXT: (local $desc (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc (ref (exact $struct)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (local $struct (ref $struct)) + (local $desc (ref $desc)) + (drop + (ref.cast_desc (ref $struct) + (unreachable) + (struct.new $desc) + ) + ) + ) +) + (module ;; This will be invalid soon, but in the meantime we should not be confused when ;; the types described by two related descriptors are unrelated.