diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index b17d6c772b9..6423de5d59b 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -18,9 +18,12 @@ #include "ir/intrinsics.h" #include "ir/manipulation.h" #include "ir/metadata.h" -#include "ir/properties.h" +#include "ir/subtypes.h" +#include "pass.h" #include "support/insert_ordered.h" #include "support/topological_sort.h" +#include "support/utilities.h" +#include "wasm-builder.h" namespace wasm::ModuleUtils { @@ -604,55 +607,24 @@ collectHeapTypeInfo(Module& wasm, namespace { -void classifyTypeVisibility(Module& wasm, - InsertOrderedMap& types, - WorldMode worldMode) { - for (auto type : getPublicHeapTypes(wasm, worldMode)) { - if (auto it = types.find(type); it != types.end()) { - it->second.visibility = Visibility::Public; - } - } - for (auto& [type, info] : types) { - if (info.visibility != Visibility::Public) { - info.visibility = Visibility::Private; - } - } -} - -// Collects all heap types transitively reachable from a root set of types. -// Options are provided to customize the traversal: -// - `includeSupertypes`: if true, declared supertypes are also traversed. -// - `includeRecGroups`: if true, all types in the same recursion group -// are also traversed. +// Collects all defined heap types transitively reachable from a root set of +// types. std::vector -getTransitivelyReachable(const std::vector& roots, - bool includeSupertypes, - bool includeRecGroups) { +getTransitivelyReachable(const std::vector& roots) { std::vector result; std::vector worklist; - std::unordered_set seen; std::unordered_set seenRecGroups; auto note = [&](HeapType type) { if (type.isBasic()) { - if (seen.insert(type).second) { - result.push_back(type); - } return; } - if (includeRecGroups) { - auto group = type.getRecGroup(); - if (seenRecGroups.insert(group).second) { - for (auto member : group) { - result.push_back(member); - worklist.push_back(member); - } - } - } else { - if (seen.insert(type).second) { - result.push_back(type); - worklist.push_back(type); + auto group = type.getRecGroup(); + if (seenRecGroups.insert(group).second) { + for (auto member : group) { + result.push_back(member); + worklist.push_back(member); } } }; @@ -664,12 +636,7 @@ getTransitivelyReachable(const std::vector& roots, while (!worklist.empty()) { auto curr = worklist.back(); worklist.pop_back(); - std::optional super = - includeSupertypes ? std::nullopt : curr.getDeclaredSuperType(); for (auto t : curr.getReferencedHeapTypes()) { - if (super && t == *super) { - continue; - } note(t); } } @@ -677,6 +644,150 @@ getTransitivelyReachable(const std::vector& roots, return result; } +// Computes the visibility of all types in the module. +// +// ## Closed World Mode +// Every type reachable from imports/exports and all of their rec group siblings +// are marked public. This preserves the structural type identity of the imports +// and exports. +// +// ## Open World Mode +// In an open world, the outside environment may cast a publicized type down +// to any of its subtypes. Thus, subtypes of exposed types must also remain +// public to preserve their structural identities. +void classifyTypeVisibility(Module& wasm, + InsertOrderedMap& types, + WorldMode worldMode) { + if (worldMode == WorldMode::Closed) { + // In closed world mode, the public types are simply the exposed types and + // all types reachable from their definitions. + for (auto type : getPublicHeapTypes(wasm, WorldMode::Closed)) { + if (auto it = types.find(type); it != types.end()) { + it->second.visibility = Visibility::Public; + } + } + for (auto& [_, info] : types) { + if (info.visibility != Visibility::Public) { + info.visibility = Visibility::Private; + } + } + return; + } + + // Open world public types have different levels of exposure that change + // whether their related types must be public or not. Types may be public only + // because they are part of the structural identity of another public type, + // but not be otherwise exposed across the module boundary, or they may be + // exposed only via exact references, or they may be exposed fully. + enum Exposure { NotExposed, ExposedExactly, Exposed }; + + std::unordered_map visited; + std::vector worklist; + + // Insert or upgrade a type's exposure in the `visited` map. If a type's + // exposure is upgraded, we re-push it to the worklist to update the + // propagation to related types. + auto visit = [&](HeapType type, Exposure state) { + auto [it, inserted] = visited.insert({type, state}); + if (inserted || state > it->second) { + it->second = state; + worklist.push_back(type); + } + }; + + // Build the subtype hierarchy. + std::vector heapTypes; + heapTypes.reserve(types.size()); + for (auto& [type, _] : types) { + heapTypes.push_back(type); + } + SubTypes subTypes(heapTypes); + + // Initialize with directly exposed types. + for (auto& [type, exact] : getExposedPublicHeapTypes(wasm)) { + visit(type, exact == Exact ? Exposure::ExposedExactly : Exposure::Exposed); + } + + while (!worklist.empty()) { + auto curr = worklist.back(); + worklist.pop_back(); + + auto state = visited.at(curr); + + // Propagate exposed status to subtypes. + if (state == Exposure::Exposed) { + if (curr.isBasic()) { + for (auto& [definedType, _] : types) { + if (HeapType::isSubType(definedType, curr)) { + visit(definedType, Exposure::Exposed); + } + } + } else { + for (auto sub : subTypes.getImmediateSubTypes(curr)) { + visit(sub, Exposure::Exposed); + } + } + } + + if (curr.isBasic()) { + continue; + } + + // Rec group members must also be public, but do not necessarily cross the + // module boundary. + for (auto member : curr.getRecGroup()) { + visit(member, Exposure::NotExposed); + } + + // Types reachable from this public type (e.g. params, results, fields) must + // be public. If the current type is not exposed, the other reachable types + // are not necessarily exposed either. If the current type is exposed + // (whether exactly or not), the reachable types are exposed with exactness + // depending on the reference type. + for (auto child : curr.getTypeChildren()) { + if (child.isRef()) { + auto exposure = state == NotExposed ? NotExposed + : child.isExact() ? ExposedExactly + : Exposed; + visit(child.getHeapType(), exposure); + } + } + + // Public continuation types require their function types to be public, but + // a continuation reference does not make any function reference available. + if (curr.isContinuation()) { + visit(curr.getContinuation().type, NotExposed); + } + + // Descriptor types are like type children, except that they are exposed + // exactly iff the current type is exposed exactly. + if (auto desc = curr.getDescriptorType()) { + visit(*desc, state); + } + + // Supertypes need to be public, but only to keep structural identity the + // same. Other types related to the supertypes are not necessarily exposed. + if (auto super = curr.getDeclaredSuperType()) { + visit(*super, Exposure::NotExposed); + } + + // Similarly, described types also need to be kept public, but they are not + // necessarily exposed just because their descriptor is exposed. + if (auto described = curr.getDescribedType()) { + visit(*described, Exposure::NotExposed); + } + } + + // Mark visibility for all defined types + for (auto& [type, typeInfo] : types) { + if (visited.contains(type)) { + typeInfo.visibility = Visibility::Public; + } else { + typeInfo.visibility = Visibility::Private; + } + } +} + void setIndices(IndexedHeapTypes& indexedTypes) { for (Index i = 0; i < indexedTypes.types.size(); i++) { indexedTypes.indices[indexedTypes.types[i]] = i; @@ -695,60 +806,59 @@ std::vector collectHeapTypes(Module& wasm) { return types; } -std::vector getExposedPublicHeapTypes(Module& wasm) { - // Look at the types of imports and exports to get an initial set of public - // types. - std::vector publicTypes; - std::unordered_set seenTypes; +std::vector> +getExposedPublicHeapTypes(Module& wasm) { + InsertOrderedMap seenTypes; - auto notePublic = [&](HeapType type) { - if (seenTypes.insert(type).second) { - publicTypes.push_back(type); + auto notePublic = [&](HeapType type, Exactness exact) { + auto [it, inserted] = seenTypes.insert({type, exact}); + if (!inserted) { + if (it->second == Exact && exact == Inexact) { + it->second = Inexact; + } } }; - ModuleUtils::iterImportedTags(wasm, [&](Tag* tag) { notePublic(tag->type); }); + ModuleUtils::iterImportedTags( + wasm, [&](Tag* tag) { notePublic(tag->type, Inexact); }); ModuleUtils::iterImportedTables(wasm, [&](Table* table) { assert(table->type.isRef()); - notePublic(table->type.getHeapType()); + notePublic(table->type.getHeapType(), table->type.getExactness()); }); ModuleUtils::iterImportedGlobals(wasm, [&](Global* global) { if (global->type.isRef()) { - notePublic(global->type.getHeapType()); + notePublic(global->type.getHeapType(), global->type.getExactness()); } }); ModuleUtils::iterImportedFunctions(wasm, [&](Function* func) { - // We can ignore call.without.effects, which is implemented as an import but - // functionally is a call within the module. if (!Intrinsics(wasm).isCallWithoutEffects(func)) { - notePublic(func->type.getHeapType()); + notePublic(func->type.getHeapType(), Inexact); } }); for (auto& ex : wasm.exports) { switch (ex->kind) { case ExternalKind::Function: { auto* func = wasm.getFunction(*ex->getInternalName()); - notePublic(func->type.getHeapType()); + notePublic(func->type.getHeapType(), Inexact); continue; } case ExternalKind::Table: { auto* table = wasm.getTable(*ex->getInternalName()); assert(table->type.isRef()); - notePublic(table->type.getHeapType()); + notePublic(table->type.getHeapType(), table->type.getExactness()); continue; } case ExternalKind::Memory: - // Never a reference type. continue; case ExternalKind::Global: { auto* global = wasm.getGlobal(*ex->getInternalName()); if (global->type.isRef()) { - notePublic(global->type.getHeapType()); + notePublic(global->type.getHeapType(), global->type.getExactness()); } continue; } case ExternalKind::Tag: - notePublic(wasm.getTag(*ex->getInternalName())->type); + notePublic(wasm.getTag(*ex->getInternalName())->type, Inexact); continue; case ExternalKind::Invalid: break; @@ -756,22 +866,36 @@ std::vector getExposedPublicHeapTypes(Module& wasm) { WASM_UNREACHABLE("unexpected export kind"); } - // Ignorable public types are public. for (auto type : getIgnorablePublicTypes()) { - notePublic(type); + notePublic(type, Inexact); } - return publicTypes; + return std::vector>(seenTypes.begin(), + seenTypes.end()); } std::vector getPublicHeapTypes(Module& wasm, WorldMode worldMode) { - auto directlyExposed = getExposedPublicHeapTypes(wasm); - auto transitivelyExposed = getTransitivelyReachable( - directlyExposed, /*includeSupertypes=*/true, /*includeRecGroups=*/true); + if (worldMode == WorldMode::Closed) { + // Find all the types reachable from the directly exposed types. There's no + // need to traverse the entire module to find all the subtypes, etc. + auto exposedPairs = getExposedPublicHeapTypes(wasm); + std::vector directlyExposed; + directlyExposed.reserve(exposedPairs.size()); + for (auto& [type, _] : exposedPairs) { + directlyExposed.push_back(type); + } + return getTransitivelyReachable(directlyExposed); + } + + // In open-world mode we need to find all the types so we can include + // subtypes. + auto typeInfo = collectHeapTypeInfo(wasm, + worldMode, + TypeInclusion::AllTypes, + VisibilityHandling::FindVisibility); std::vector publicTypes; - publicTypes.reserve(transitivelyExposed.size()); - for (auto type : transitivelyExposed) { - if (!type.isBasic()) { + for (auto& [type, info] : typeInfo) { + if (info.visibility == Visibility::Public) { publicTypes.push_back(type); } } @@ -781,7 +905,7 @@ std::vector getPublicHeapTypes(Module& wasm, WorldMode worldMode) { std::vector getPrivateHeapTypes(Module& wasm, WorldMode worldMode) { auto info = collectHeapTypeInfo(wasm, worldMode, - TypeInclusion::UsedIRTypes, + TypeInclusion::AllTypes, VisibilityHandling::FindVisibility); std::vector types; types.reserve(info.size()); diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index 860672beef8..dde0a71e3ba 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -483,7 +483,8 @@ std::vector collectHeapTypes(Module& wasm); // Get the types directly made public by imported or exported module items. For // example, the types of imported or exported globals or functions, but not // other types reachable from those types. Includes abstract heap types. -std::vector getExposedPublicHeapTypes(Module& wasm); +std::vector> +getExposedPublicHeapTypes(Module& wasm); // Collect all the defined heap types visible on the module boundary that cannot // be changed, e.g. the defined types from getExposedPublicHeapTypes and those diff --git a/src/passes/Unsubtyping.cpp b/src/passes/Unsubtyping.cpp index 866e32ba58a..74b0850da0e 100644 --- a/src/passes/Unsubtyping.cpp +++ b/src/passes/Unsubtyping.cpp @@ -567,9 +567,6 @@ struct Unsubtyping : Pass, Noter { if (!wasm->features.hasGC()) { return; } - if (getPassOptions().worldMode == WorldMode::Open) { - Fatal() << "Unsubtyping requires --closed-world"; - } // Initialize the subtype relation based on what is immediately required to // keep the code and public types valid. diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 8a5a63ca0af..a28faacc3c0 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -250,7 +250,7 @@ void validateExactReferences(Module& module, ValidationInfo& info) { return; } - for (auto type : ModuleUtils::getExposedPublicHeapTypes(module)) { + for (auto& [type, _] : ModuleUtils::getExposedPublicHeapTypes(module)) { for (auto child : type.getTypeChildren()) { if (child.isExact()) { std::string typeName; diff --git a/test/lit/passes/gufa-tnh.wast b/test/lit/passes/gufa-tnh.wast index a0dd9846191..b4601d47661 100644 --- a/test/lit/passes/gufa-tnh.wast +++ b/test/lit/passes/gufa-tnh.wast @@ -169,8 +169,6 @@ ;; CHECK: (type $4 (func (param anyref))) - ;; CHECK: (export "out" (func $caller)) - ;; CHECK: (func $maker (type $2) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $A @@ -213,7 +211,8 @@ ) ) - ;; CHECK: (func $caller (type $4) (param $any anyref) + ;; CHECK: (@binaryen.js.called) + ;; CHECK-NEXT: (func $caller (type $4) (param $any anyref) ;; CHECK-NEXT: (local $x (ref null $A)) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (local.tee $x @@ -241,7 +240,8 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $caller (export "out") (param $any anyref) + (@binaryen.js.called) + (func $caller (param $any anyref) (local $x (ref null $A)) ;; The called function casts to $B. This lets us infer the value of the ;; fallthrough ref.cast, which will turn into $B. Furthermore, that then @@ -296,8 +296,6 @@ ;; CHECK: (type $3 (func)) - ;; CHECK: (export "out" (func $caller)) - ;; CHECK: (func $maker (type $3) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $A @@ -340,7 +338,8 @@ ) ) - ;; CHECK: (func $caller (type $2) (param $a (ref null $A)) + ;; CHECK: (@binaryen.js.called) + ;; CHECK-NEXT: (func $caller (type $2) (param $a (ref null $A)) ;; CHECK-NEXT: (local $x (ref null $A)) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (local.tee $x @@ -351,7 +350,8 @@ ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $caller (export "out") (param $a (ref null $A)) + (@binaryen.js.called) + (func $caller (param $a (ref null $A)) (local $x (ref null $A)) ;; The change compared to before is that we only have a local.tee here, and ;; no ref.cast. We can still infer the type of the tee's value, and @@ -370,6 +370,96 @@ ) ) +;; The same module as above, but now $caller is exported instead of marked +;; @binaryen.js.called. This makes $A public, and since we are in open-world +;; mode, that in turn makes its subtype $B public, so we cannot infer anything +;; about either type. +(module + ;; CHECK: (type $A (sub (struct (field (mut i32))))) + (type $A (sub (struct (field (mut i32))))) + + ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) + (type $B (sub $A (struct (field (mut i32))))) + + ;; CHECK: (type $2 (func (param (ref null $A)))) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (export "out" (func $caller)) + + ;; CHECK: (func $maker (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $maker + ;; A always contains 10, and B always contains 20, except that they are both + ;; public, so they can come in from the outside with any field value. + (drop + (struct.new $A + (i32.const 10) + ) + ) + (drop + (struct.new $B + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $called (type $2) (param $x (ref null $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $called (param $x (ref null $A)) + ;; Cast the input to a $B, which will help the caller. + (drop + (ref.cast (ref $B) + (local.get $x) + ) + ) + ) + + ;; CHECK: (func $caller (type $2) (param $a (ref null $A)) + ;; CHECK-NEXT: (local $x (ref null $A)) + ;; CHECK-NEXT: (call $called + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller (export "out") (param $a (ref null $A)) + (local $x (ref null $A)) + ;; Despite inferring that the argument must be a $B, we still cannot + ;; optimize. + (call $called + (local.tee $x + (local.get $a) + ) + ) + (drop + (struct.get $A 0 + (local.get $x) + ) + ) + ) +) + ;; As above, but add a local.tee etc. in the called function. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) @@ -1146,12 +1236,6 @@ ;; CHECK: (type $5 (func)) - ;; CHECK: (export "caller-C" (func $caller-C)) - - ;; CHECK: (export "caller-B" (func $caller-B)) - - ;; CHECK: (export "caller-A" (func $caller-A)) - ;; CHECK: (func $called (type $4) (param $x (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) @@ -1204,7 +1288,8 @@ ) ) - ;; CHECK: (func $caller-C (type $3) (param $any anyref) + ;; CHECK: (@binaryen.js.called) + ;; CHECK-NEXT: (func $caller-C (type $3) (param $any anyref) ;; CHECK-NEXT: (local $temp-C (ref $C)) ;; CHECK-NEXT: (local $temp-any anyref) ;; CHECK-NEXT: (call $called @@ -1234,7 +1319,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $caller-C (export "caller-C") (param $any anyref) + (@binaryen.js.called) + (func $caller-C (param $any anyref) (local $temp-C (ref $C)) (local $temp-any anyref) (call $called @@ -1276,7 +1362,8 @@ ) ) - ;; CHECK: (func $caller-B (type $3) (param $any anyref) + ;; CHECK: (@binaryen.js.called) + ;; CHECK-NEXT: (func $caller-B (type $3) (param $any anyref) ;; CHECK-NEXT: (local $temp (ref $A)) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (local.tee $temp @@ -1291,7 +1378,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $caller-B (export "caller-B") (param $any anyref) + (@binaryen.js.called) + (func $caller-B (param $any anyref) (local $temp (ref $A)) (call $called (local.tee $temp @@ -1307,7 +1395,8 @@ ) ) - ;; CHECK: (func $caller-A (type $3) (param $any anyref) + ;; CHECK: (@binaryen.js.called) + ;; CHECK-NEXT: (func $caller-A (type $3) (param $any anyref) ;; CHECK-NEXT: (local $temp (ref $A)) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (local.tee $temp @@ -1322,7 +1411,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $caller-A (export "caller-A") (param $any anyref) + (@binaryen.js.called) + (func $caller-A (param $any anyref) (local $temp (ref $A)) (call $called (local.tee $temp @@ -1731,8 +1821,6 @@ ;; CHECK: (elem $e func) (elem $e funcref) - ;; CHECK: (export "out" (func $caller)) - ;; CHECK: (func $called (type $3) (param $struct.get (ref null $A)) (param $struct.set (ref null $A)) (param $array.get (ref null $B)) (param $array.set (ref null $B)) (param $array.len (ref null $B)) (param $array.copy.src (ref null $B)) (param $array.copy.dest (ref null $B)) (param $array.fill (ref null $B)) (param $array.init_data (ref null $B)) (param $array.init_elem (ref null $C)) (param $ref.test (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) @@ -1859,7 +1947,8 @@ ) ) - ;; CHECK: (func $caller (type $4) (param $any anyref) + ;; CHECK: (@binaryen.js.called) + ;; CHECK-NEXT: (func $caller (type $4) (param $any anyref) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) @@ -1896,7 +1985,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $caller (export "out") (param $any anyref) + (@binaryen.js.called) + (func $caller (param $any anyref) ;; All these casts will be refined to non-nullable, aside from the last ;; param which is but a ref.test. (call $called diff --git a/test/lit/passes/gufa-vs-cfp.wast b/test/lit/passes/gufa-vs-cfp.wast index bfeeac5fa22..bbd834e62d6 100644 --- a/test/lit/passes/gufa-vs-cfp.wast +++ b/test/lit/passes/gufa-vs-cfp.wast @@ -1,6 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt --remove-unused-names --gufa -all -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt --remove-unused-names --gufa --closed-world -all -S -o - | filecheck %s ;; (remove-unused-names is added to test fallthrough values without a block ;; name getting in the way) diff --git a/test/lit/passes/minimize-rec-groups.wast b/test/lit/passes/minimize-rec-groups.wast index 963f655f20d..f0805c79e26 100644 --- a/test/lit/passes/minimize-rec-groups.wast +++ b/test/lit/passes/minimize-rec-groups.wast @@ -1,5 +1,5 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt -all --minimize-rec-groups -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --closed-world --minimize-rec-groups -S -o - | filecheck %s ;; A module with no heap types at all should be ok. (module diff --git a/test/lit/passes/signature-refining-configureAll.wast b/test/lit/passes/signature-refining-configureAll.wast index 2fd1b1d3eae..362f433c29c 100644 --- a/test/lit/passes/signature-refining-configureAll.wast +++ b/test/lit/passes/signature-refining-configureAll.wast @@ -1,10 +1,10 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; RUN: foreach %s %t wasm-opt --signature-refining --closed-world -all -S -o - | filecheck %s -;; RUN: foreach %s %t wasm-opt --signature-refining -all -S -o - | filecheck %s --check-prefix OPEN_WORLD -;; Test that configureAll is respected: referred functions are not refined. -;; This is so even in closed world. +;; Test that configureAll is respected: referred functions are not refined. In +;; open-world, no function types would be refined because configureAll makes +;; `func`, and therefore all function types, public. (module ;; CHECK: (rec @@ -19,30 +19,15 @@ ;; CHECK: (type $4 (func)) ;; CHECK: (type $externs (array (mut externref))) - ;; OPEN_WORLD: (rec - ;; OPEN_WORLD-NEXT: (type $func-2 (func (param (ref (exact $struct))) (result (ref (exact $struct))))) - - ;; OPEN_WORLD: (type $func-1 (func (param anyref) (result (ref (exact $struct))))) - - ;; OPEN_WORLD: (type $2 (func (result i32))) - - ;; OPEN_WORLD: (type $struct (struct)) - - ;; OPEN_WORLD: (type $4 (func)) - - ;; OPEN_WORLD: (type $externs (array (mut externref))) (type $externs (array (mut externref))) ;; CHECK: (type $funcs (array (mut funcref))) - ;; OPEN_WORLD: (type $funcs (array (mut funcref))) (type $funcs (array (mut funcref))) ;; CHECK: (type $bytes (array (mut i8))) - ;; OPEN_WORLD: (type $bytes (array (mut i8))) (type $bytes (array (mut i8))) ;; CHECK: (type $configureAll (func (param (ref null $externs) (ref null $funcs) (ref null $bytes) externref))) - ;; OPEN_WORLD: (type $configureAll (func (param (ref null $externs) (ref null $funcs) (ref null $bytes) externref))) (type $configureAll (func (param (ref null $externs)) (param (ref null $funcs)) (param (ref null $bytes)) (param externref))) (type $struct (struct)) @@ -62,21 +47,16 @@ ) ;; CHECK: (import "wasm:js-prototypes" "configureAll" (func $configureAll (type $configureAll) (param (ref null $externs) (ref null $funcs) (ref null $bytes) externref))) - ;; OPEN_WORLD: (import "wasm:js-prototypes" "configureAll" (func $configureAll (type $configureAll) (param (ref null $externs) (ref null $funcs) (ref null $bytes) externref))) (import "wasm:js-prototypes" "configureAll" (func $configureAll (type $configureAll))) ;; CHECK: (data $bytes "12345678") ;; CHECK: (elem $externs externref (item (ref.null noextern))) - ;; OPEN_WORLD: (data $bytes "12345678") - - ;; OPEN_WORLD: (elem $externs externref (item (ref.null noextern))) (elem $externs externref (ref.null extern) ) ;; CHECK: (elem $funcs func $foo $bar) - ;; OPEN_WORLD: (elem $funcs func $foo $bar) (elem $funcs funcref (ref.func $foo) (ref.func $bar) @@ -85,7 +65,6 @@ (data $bytes "12345678") ;; CHECK: (start $start) - ;; OPEN_WORLD: (start $start) (start $start) ;; CHECK: (func $start (type $4) @@ -105,23 +84,6 @@ ;; CHECK-NEXT: (ref.null noextern) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPEN_WORLD: (func $start (type $4) - ;; OPEN_WORLD-NEXT: (call $configureAll - ;; OPEN_WORLD-NEXT: (array.new_elem $externs $externs - ;; OPEN_WORLD-NEXT: (i32.const 0) - ;; OPEN_WORLD-NEXT: (i32.const 1) - ;; OPEN_WORLD-NEXT: ) - ;; OPEN_WORLD-NEXT: (array.new_elem $funcs $funcs - ;; OPEN_WORLD-NEXT: (i32.const 0) - ;; OPEN_WORLD-NEXT: (i32.const 2) - ;; OPEN_WORLD-NEXT: ) - ;; OPEN_WORLD-NEXT: (array.new_data $bytes $bytes - ;; OPEN_WORLD-NEXT: (i32.const 0) - ;; OPEN_WORLD-NEXT: (i32.const 8) - ;; OPEN_WORLD-NEXT: ) - ;; OPEN_WORLD-NEXT: (ref.null noextern) - ;; OPEN_WORLD-NEXT: ) - ;; OPEN_WORLD-NEXT: ) (func $start (call $configureAll (array.new_elem $externs $externs @@ -146,18 +108,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPEN_WORLD: (func $calls (type $4) - ;; OPEN_WORLD-NEXT: (drop - ;; OPEN_WORLD-NEXT: (call $bar - ;; OPEN_WORLD-NEXT: (struct.new_default $struct) - ;; OPEN_WORLD-NEXT: ) - ;; OPEN_WORLD-NEXT: ) - ;; OPEN_WORLD-NEXT: (drop - ;; OPEN_WORLD-NEXT: (call $unconfigured - ;; OPEN_WORLD-NEXT: (struct.new_default $struct) - ;; OPEN_WORLD-NEXT: ) - ;; OPEN_WORLD-NEXT: ) - ;; OPEN_WORLD-NEXT: ) (func $calls (drop (call $bar @@ -174,9 +124,6 @@ ;; CHECK: (func $foo (type $2) (result i32) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) - ;; OPEN_WORLD: (func $foo (type $2) (result i32) - ;; OPEN_WORLD-NEXT: (i32.const 42) - ;; OPEN_WORLD-NEXT: ) (func $foo (result i32) ;; Nothing to do here anyhow, but do not error. (i32.const 42) @@ -185,9 +132,6 @@ ;; CHECK: (func $bar (type $func-1) (param $x anyref) (result (ref (exact $struct))) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) - ;; OPEN_WORLD: (func $bar (type $func-1) (param $x anyref) (result (ref (exact $struct))) - ;; OPEN_WORLD-NEXT: (struct.new_default $struct) - ;; OPEN_WORLD-NEXT: ) (func $bar (type $func-1) (param $x anyref) (result anyref) ;; The params will not be refined due to configureAll, but the result will. (struct.new $struct) @@ -196,9 +140,6 @@ ;; CHECK: (func $unconfigured (type $func-2) (param $x (ref (exact $struct))) (result (ref (exact $struct))) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) - ;; OPEN_WORLD: (func $unconfigured (type $func-2) (param $x (ref (exact $struct))) (result (ref (exact $struct))) - ;; OPEN_WORLD-NEXT: (struct.new_default $struct) - ;; OPEN_WORLD-NEXT: ) (func $unconfigured (type $func-2) (param $x anyref) (result anyref) ;; This is not referred to by configureAll, and can be refined in both ;; params and result. diff --git a/test/lit/passes/unsubtyping-open-world.wast b/test/lit/passes/unsubtyping-open-world.wast new file mode 100644 index 00000000000..5359a0f80fc --- /dev/null +++ b/test/lit/passes/unsubtyping-open-world.wast @@ -0,0 +1,959 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt -all --preserve-type-order --unsubtyping -all -S -o - | filecheck %s + +;; No public types. We can still optimize. +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (global $use-super (ref null $super) (ref.null none)) + (global $use-super (ref null $super) (ref.null none)) + ;; CHECK: (global $use-sub (ref null $sub) (ref.null none)) + (global $use-sub (ref null $sub) (ref.null none)) +) + +;; func is public, so we cannot optimize any functions. We can still optimize +;; structs. +(module + ;; CHECK: (type $super-func (sub (func))) + (type $super-func (sub (func))) + ;; CHECK: (type $sub-func (sub $super-func (func))) + (type $sub-func (sub $super-func (func))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super-struct (sub (struct))) + (type $super-struct (sub (struct))) + ;; CHECK: (type $sub-struct (sub (struct))) + (type $sub-struct (sub $super-struct (struct))) + + ;; CHECK: (global $g-func funcref (ref.null nofunc)) + (global $g-func funcref (ref.null nofunc)) + ;; CHECK: (global $use-super-func (ref null $super-func) (ref.null nofunc)) + (global $use-super-func (ref null $super-func) (ref.null nofunc)) + ;; CHECK: (global $use-sub-func (ref null $sub-func) (ref.null nofunc)) + (global $use-sub-func (ref null $sub-func) (ref.null nofunc)) + ;; CHECK: (global $use-super-struct (ref null $super-struct) (ref.null none)) + (global $use-super-struct (ref null $super-struct) (ref.null none)) + ;; CHECK: (global $use-sub-struct (ref null $sub-struct) (ref.null none)) + (global $use-sub-struct (ref null $sub-struct) (ref.null none)) + ;; CHECK: (export "g-func" (global $g-func)) + (export "g-func" (global $g-func)) +) + +;; any is public, so we cannot optimize any structs or arrays. We can still +;; optimize functions. +(module + ;; CHECK: (type $super-struct (sub (struct))) + (type $super-struct (sub (struct))) + ;; CHECK: (type $sub-struct (sub $super-struct (struct))) + (type $sub-struct (sub $super-struct (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super-func (sub (func))) + (type $super-func (sub (func))) + ;; CHECK: (type $sub-func (sub (func))) + (type $sub-func (sub $super-func (func))) + + ;; CHECK: (global $g-any anyref (ref.null none)) + (global $g-any anyref (ref.null none)) + ;; CHECK: (global $use-super-struct (ref null $super-struct) (ref.null none)) + (global $use-super-struct (ref null $super-struct) (ref.null none)) + ;; CHECK: (global $use-sub-struct (ref null $sub-struct) (ref.null none)) + (global $use-sub-struct (ref null $sub-struct) (ref.null none)) + ;; CHECK: (global $use-super-func (ref null $super-func) (ref.null nofunc)) + (global $use-super-func (ref null $super-func) (ref.null nofunc)) + ;; CHECK: (global $use-sub-func (ref null $sub-func) (ref.null nofunc)) + (global $use-sub-func (ref null $sub-func) (ref.null nofunc)) + ;; CHECK: (export "g-any" (global $g-any)) + (export "g-any" (global $g-any)) +) + +;; i31 is public. We can still optimize structs. +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super-struct (sub (struct))) + (type $super-struct (sub (struct))) + ;; CHECK: (type $sub-struct (sub (struct))) + (type $sub-struct (sub $super-struct (struct))) + + ;; CHECK: (global $g-i31 i31ref (ref.null none)) + (global $g-i31 i31ref (ref.null none)) + ;; CHECK: (global $use-super-struct (ref null $super-struct) (ref.null none)) + (global $use-super-struct (ref null $super-struct) (ref.null none)) + ;; CHECK: (global $use-sub-struct (ref null $sub-struct) (ref.null none)) + (global $use-sub-struct (ref null $sub-struct) (ref.null none)) + ;; CHECK: (export "g-i31" (global $g-i31)) + (export "g-i31" (global $g-i31)) +) + +;; eq is public. We cannot optimize structs. +(module + ;; CHECK: (type $super-struct (sub (struct))) + (type $super-struct (sub (struct))) + ;; CHECK: (type $sub-struct (sub $super-struct (struct))) + (type $sub-struct (sub $super-struct (struct))) + + ;; CHECK: (global $g-eq eqref (ref.null none)) + (global $g-eq eqref (ref.null none)) + ;; CHECK: (global $use-super-struct (ref null $super-struct) (ref.null none)) + (global $use-super-struct (ref null $super-struct) (ref.null none)) + ;; CHECK: (global $use-sub-struct (ref null $sub-struct) (ref.null none)) + (global $use-sub-struct (ref null $sub-struct) (ref.null none)) + ;; CHECK: (export "g-eq" (global $g-eq)) + (export "g-eq" (global $g-eq)) +) + +;; We cannot optimize subtypes of a publicly exposed defined type. We can still +;; optimize unrelated types. +(module + ;; CHECK: (type $public-parent (sub (struct))) + (type $public-parent (sub (struct))) + ;; CHECK: (type $public-child (sub $public-parent (struct))) + (type $public-child (sub $public-parent (struct))) + ;; CHECK: (type $public-grandchild (sub $public-child (struct))) + (type $public-grandchild (sub $public-child (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $unrelated-super (sub (struct (field i32)))) + (type $unrelated-super (sub (struct (field i32)))) + ;; CHECK: (type $unrelated-sub (sub (struct (field i32) (field f32)))) + (type $unrelated-sub (sub $unrelated-super (struct i32 f32))) + + ;; CHECK: (global $g-public (ref null $public-parent) (ref.null none)) + (global $g-public (ref null $public-parent) (ref.null none)) + ;; CHECK: (global $use-public-child (ref null $public-child) (ref.null none)) + (global $use-public-child (ref null $public-child) (ref.null none)) + ;; CHECK: (global $use-public-grandchild (ref null $public-grandchild) (ref.null none)) + (global $use-public-grandchild (ref null $public-grandchild) (ref.null none)) + ;; CHECK: (global $use-unrelated-super (ref null $unrelated-super) (ref.null none)) + (global $use-unrelated-super (ref null $unrelated-super) (ref.null none)) + ;; CHECK: (global $use-unrelated-sub (ref null $unrelated-sub) (ref.null none)) + (global $use-unrelated-sub (ref null $unrelated-sub) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; When a type is exposed exactly, we can still optimize its subtypes. +(module + ;; CHECK: (type $public-parent (sub (struct))) + (type $public-parent (sub (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $public-child (sub (struct))) + (type $public-child (sub $public-parent (struct))) + ;; CHECK: (type $public-grandchild (sub (struct))) + (type $public-grandchild (sub $public-child (struct))) + + ;; CHECK: (global $g-public (ref (exact $public-parent)) (struct.new_default $public-parent)) + (global $g-public (ref (exact $public-parent)) (struct.new_default $public-parent)) + ;; CHECK: (global $use-public-child (ref null $public-child) (ref.null none)) + (global $use-public-child (ref null $public-child) (ref.null none)) + ;; CHECK: (global $use-public-grandchild (ref null $public-grandchild) (ref.null none)) + (global $use-public-grandchild (ref null $public-grandchild) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; We cannot optimize a type referenced from a publicly exposed type, nor a +;; subtype of that type. +(module + ;; CHECK: (type $referenced-super (sub (struct))) + (type $referenced-super (sub (struct))) + ;; CHECK: (type $referenced (sub $referenced-super (struct))) + (type $referenced (sub $referenced-super (struct))) + ;; CHECK: (type $referenced-child (sub $referenced (struct))) + (type $referenced-child (sub $referenced (struct))) + ;; CHECK: (type $public (sub (struct (field (ref $referenced))))) + (type $public (sub (struct (field (ref $referenced))))) + + ;; CHECK: (global $g-public (ref null $public) (ref.null none)) + (global $g-public (ref null $public) (ref.null none)) + ;; CHECK: (global $use-referenced-super (ref null $referenced-super) (ref.null none)) + (global $use-referenced-super (ref null $referenced-super) (ref.null none)) + ;; CHECK: (global $use-referenced (ref null $referenced) (ref.null none)) + (global $use-referenced (ref null $referenced) (ref.null none)) + ;; CHECK: (global $use-referenced-child (ref null $referenced-child) (ref.null none)) + (global $use-referenced-child (ref null $referenced-child) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; Same, but with the public type exposed exactly. We still cannot optimize +;; subtypes of the reachable type. +(module + ;; CHECK: (type $referenced-super (sub (struct))) + (type $referenced-super (sub (struct))) + ;; CHECK: (type $referenced (sub $referenced-super (struct))) + (type $referenced (sub $referenced-super (struct))) + ;; CHECK: (type $referenced-child (sub $referenced (struct))) + (type $referenced-child (sub $referenced (struct))) + ;; CHECK: (type $public (sub (struct (field (ref null $referenced))))) + (type $public (sub (struct (field (ref null $referenced))))) + + ;; CHECK: (global $g-public (ref (exact $public)) (struct.new_default $public)) + (global $g-public (ref (exact $public)) (struct.new_default $public)) + ;; CHECK: (global $use-referenced-super (ref null $referenced-super) (ref.null none)) + (global $use-referenced-super (ref null $referenced-super) (ref.null none)) + ;; CHECK: (global $use-referenced (ref null $referenced) (ref.null none)) + (global $use-referenced (ref null $referenced) (ref.null none)) + ;; CHECK: (global $use-referenced-child (ref null $referenced-child) (ref.null none)) + (global $use-referenced-child (ref null $referenced-child) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; When the referenced type is referenced exactly, we still cannot optimize it, +;; but we can optimize its subtypes. +(module + ;; CHECK: (type $referenced-super (sub (struct))) + (type $referenced-super (sub (struct))) + ;; CHECK: (type $referenced (sub $referenced-super (struct))) + (type $referenced (sub $referenced-super (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $referenced-child (sub (struct))) + (type $referenced-child (sub $referenced (struct))) + ;; CHECK: (type $3 (struct)) + + ;; CHECK: (type $public (sub (struct (field (ref (exact $referenced)))))) + (type $public (sub (struct (field (ref (exact $referenced)))))) + + ;; CHECK: (global $g-public (ref null $public) (ref.null none)) + (global $g-public (ref null $public) (ref.null none)) + ;; CHECK: (global $use-referenced-super (ref null $referenced-super) (ref.null none)) + (global $use-referenced-super (ref null $referenced-super) (ref.null none)) + ;; CHECK: (global $use-referenced (ref null $referenced) (ref.null none)) + (global $use-referenced (ref null $referenced) (ref.null none)) + ;; CHECK: (global $use-referenced-child (ref null $referenced-child) (ref.null none)) + (global $use-referenced-child (ref null $referenced-child) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; When func is exposed indirectly because it is referenced by a publicly +;; exposed type, we still cannot optimize any function types. +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super-struct (sub (struct))) + (type $super-struct (sub (struct))) + ;; CHECK: (type $sub-struct (sub (struct))) + (type $sub-struct (sub $super-struct (struct))) + ;; CHECK: (type $super-func (sub (func))) + (type $super-func (sub (func))) + ;; CHECK: (type $sub-func (sub $super-func (func))) + (type $sub-func (sub $super-func (func))) + ;; CHECK: (type $public (sub (struct (field funcref)))) + (type $public (sub (struct (field funcref)))) + + ;; CHECK: (global $g-public (ref null $public) (ref.null none)) + (global $g-public (ref null $public) (ref.null none)) + ;; CHECK: (global $use-super-struct (ref null $super-struct) (ref.null none)) + (global $use-super-struct (ref null $super-struct) (ref.null none)) + ;; CHECK: (global $use-sub-struct (ref null $sub-struct) (ref.null none)) + (global $use-sub-struct (ref null $sub-struct) (ref.null none)) + ;; CHECK: (global $use-super-func (ref null $super-func) (ref.null nofunc)) + (global $use-super-func (ref null $super-func) (ref.null nofunc)) + ;; CHECK: (global $use-sub-func (ref null $sub-func) (ref.null nofunc)) + (global $use-sub-func (ref null $sub-func) (ref.null nofunc)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; We cannot optimize the supertype of a publicly exposed type, but we can +;; optimize other subtypes of the supertype. +(module + ;; CHECK: (type $super-super (sub (struct))) + (type $super-super (sub (struct))) + ;; CHECK: (type $super (sub $super-super (struct))) + (type $super (sub $super-super (struct))) + ;; CHECK: (type $public (sub $super (struct))) + (type $public (sub $super (struct))) + ;; CHECK: (type $sibling-private (sub (struct (field i32)))) + (type $sibling-private (sub $super (struct (field i32)))) + + ;; CHECK: (global $g-public (ref null $public) (ref.null none)) + (global $g-public (ref null $public) (ref.null none)) + ;; CHECK: (global $use-super-super (ref null $super-super) (ref.null none)) + (global $use-super-super (ref null $super-super) (ref.null none)) + ;; CHECK: (global $use-super (ref null $super) (ref.null none)) + (global $use-super (ref null $super) (ref.null none)) + ;; CHECK: (global $use-sibling-private (ref null $sibling-private) (ref.null none)) + (global $use-sibling-private (ref null $sibling-private) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; We cannot optimize the descriptor of a publicly exposed type, nor subtypes of +;; that descriptor. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $public-super (sub (descriptor $desc-super) (struct))) + (type $public-super (sub (descriptor $desc-super) (struct))) + ;; CHECK: (type $desc-super (sub (describes $public-super) (struct))) + (type $desc-super (sub (describes $public-super) (struct))) + ) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $public (sub $public-super (descriptor $desc) (struct))) + (type $public (sub $public-super (descriptor $desc) (struct))) + ;; CHECK: (type $desc (sub $desc-super (describes $public) (struct))) + (type $desc (sub $desc-super (describes $public) (struct))) + ) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $public-child (sub $public (descriptor $desc-child) (struct))) + (type $public-child (sub $public (descriptor $desc-child) (struct))) + ;; CHECK: (type $desc-child (sub $desc (describes $public-child) (struct))) + (type $desc-child (sub $desc (describes $public-child) (struct))) + ) + + ;; CHECK: (global $g-public (ref null $public) (ref.null none)) + (global $g-public (ref null $public) (ref.null none)) + ;; CHECK: (global $use-public-super (ref null $public-super) (ref.null none)) + (global $use-public-super (ref null $public-super) (ref.null none)) + ;; CHECK: (global $use-desc-super (ref null $desc-super) (ref.null none)) + (global $use-desc-super (ref null $desc-super) (ref.null none)) + ;; CHECK: (global $use-public-child (ref null $public-child) (ref.null none)) + (global $use-public-child (ref null $public-child) (ref.null none)) + ;; CHECK: (global $use-desc (ref null $desc) (ref.null none)) + (global $use-desc (ref null $desc) (ref.null none)) + ;; CHECK: (global $use-desc-child (ref null $desc-child) (ref.null none)) + (global $use-desc-child (ref null $desc-child) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; When the publicly exposed type is exact, we cannot optimize its descriptor, +;; but we can optimize subtypes of that descriptor. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $public-super (sub (descriptor $desc-super) (struct))) + (type $public-super (sub (descriptor $desc-super) (struct))) + ;; CHECK: (type $desc-super (sub (describes $public-super) (struct))) + (type $desc-super (sub (describes $public-super) (struct))) + ) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $public (sub $public-super (descriptor $desc) (struct))) + (type $public (sub $public-super (descriptor $desc) (struct))) + ;; CHECK: (type $desc (sub $desc-super (describes $public) (struct))) + (type $desc (sub $desc-super (describes $public) (struct))) + ) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $public-child (sub (struct))) + (type $public-child (sub $public (descriptor $desc-child) (struct))) + ;; CHECK: (type $desc-child (sub (struct))) + (type $desc-child (sub $desc (describes $public-child) (struct))) + ) + + ;; CHECK: (global $g-public (ref (exact $public)) (struct.new_default_desc $public + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: )) + (global $g-public (ref (exact $public)) (struct.new_default_desc $public (struct.new_default $desc))) + ;; CHECK: (global $use-public-super (ref null $public-super) (ref.null none)) + (global $use-public-super (ref null $public-super) (ref.null none)) + ;; CHECK: (global $use-desc-super (ref null $desc-super) (ref.null none)) + (global $use-desc-super (ref null $desc-super) (ref.null none)) + ;; CHECK: (global $use-public-child (ref null $public-child) (ref.null none)) + (global $use-public-child (ref null $public-child) (ref.null none)) + ;; CHECK: (global $use-desc-child (ref null $desc-child) (ref.null none)) + (global $use-desc-child (ref null $desc-child) (ref.null none)) + ;; CHECK: (global $use-desc (ref null $desc) (ref.null none)) + (global $use-desc (ref null $desc) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; We cannot optimize the described type of a publicly exposed type. We could +;; optimize subtypes of that described type, except that they are necessarily in +;; the same rec group as exposed subtypes of the publicly exposed type. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $described-super (sub (descriptor $public-desc-super) (struct))) + (type $described-super (sub (descriptor $public-desc-super) (struct))) + ;; CHECK: (type $public-desc-super (sub (describes $described-super) (struct))) + (type $public-desc-super (sub (describes $described-super) (struct))) + ) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $described (sub $described-super (descriptor $public-desc) (struct))) + (type $described (sub $described-super (descriptor $public-desc) (struct))) + ;; CHECK: (type $public-desc (sub $public-desc-super (describes $described) (struct))) + (type $public-desc (sub $public-desc-super (describes $described) (struct))) + ) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $described-child (sub $described (descriptor $desc-child) (struct))) + (type $described-child (sub $described (descriptor $desc-child) (struct))) + ;; CHECK: (type $desc-child (sub $public-desc (describes $described-child) (struct))) + (type $desc-child (sub $public-desc (describes $described-child) (struct))) + ) + + ;; CHECK: (global $g-public (ref null $public-desc) (ref.null none)) + (global $g-public (ref null $public-desc) (ref.null none)) + ;; CHECK: (global $use-described-super (ref null $described-super) (ref.null none)) + (global $use-described-super (ref null $described-super) (ref.null none)) + ;; CHECK: (global $use-public-desc-super (ref null $public-desc-super) (ref.null none)) + (global $use-public-desc-super (ref null $public-desc-super) (ref.null none)) + ;; CHECK: (global $use-described-child (ref null $described-child) (ref.null none)) + (global $use-described-child (ref null $described-child) (ref.null none)) + ;; CHECK: (global $use-desc-child (ref null $desc-child) (ref.null none)) + (global $use-desc-child (ref null $desc-child) (ref.null none)) + ;; CHECK: (global $use-described (ref null $described) (ref.null none)) + (global $use-described (ref null $described) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; We can still optimize subtypes of the described type when the publicly +;; exposed descriptor is exact. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $described-super (sub (descriptor $public-desc-super) (struct))) + (type $described-super (sub (descriptor $public-desc-super) (struct))) + ;; CHECK: (type $public-desc-super (sub (describes $described-super) (struct))) + (type $public-desc-super (sub (describes $described-super) (struct))) + ) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $described (sub $described-super (descriptor $public-desc) (struct))) + (type $described (sub $described-super (descriptor $public-desc) (struct))) + ;; CHECK: (type $public-desc (sub $public-desc-super (describes $described) (struct))) + (type $public-desc (sub $public-desc-super (describes $described) (struct))) + ) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $described-child (sub (struct))) + (type $described-child (sub $described (descriptor $desc-child) (struct))) + ;; CHECK: (type $desc-child (sub (struct))) + (type $desc-child (sub $public-desc (describes $described-child) (struct))) + ) + + ;; CHECK: (global $g-public (ref (exact $public-desc)) (struct.new_default $public-desc)) + (global $g-public (ref (exact $public-desc)) (struct.new_default $public-desc)) + ;; CHECK: (global $use-described-super (ref null $described-super) (ref.null none)) + (global $use-described-super (ref null $described-super) (ref.null none)) + ;; CHECK: (global $use-public-desc-super (ref null $public-desc-super) (ref.null none)) + (global $use-public-desc-super (ref null $public-desc-super) (ref.null none)) + ;; CHECK: (global $use-described-child (ref null $described-child) (ref.null none)) + (global $use-described-child (ref null $described-child) (ref.null none)) + ;; CHECK: (global $use-desc-child (ref null $desc-child) (ref.null none)) + (global $use-desc-child (ref null $desc-child) (ref.null none)) + ;; CHECK: (global $use-described (ref null $described) (ref.null none)) + (global $use-described (ref null $described) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; We cannot optimize rec groups siblings of a public type, but we can optimize +;; subtypes of those siblings. +(module + ;; CHECK: (type $sibling-super (sub (struct))) + (type $sibling-super (sub (struct))) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $public (sub (struct))) + (type $public (sub (struct))) + ;; CHECK: (type $sibling (sub $sibling-super (struct (field i32)))) + (type $sibling (sub $sibling-super (struct (field i32)))) + ) + ;; CHECK: (type $sibling-child (sub (struct (field i32) (field f32)))) + (type $sibling-child (sub $sibling (struct i32 f32))) + + ;; CHECK: (global $g-public (ref null $public) (ref.null none)) + (global $g-public (ref null $public) (ref.null none)) + ;; CHECK: (global $use-sibling-super (ref null $sibling-super) (ref.null none)) + (global $use-sibling-super (ref null $sibling-super) (ref.null none)) + ;; CHECK: (global $use-sibling (ref null $sibling) (ref.null none)) + (global $use-sibling (ref null $sibling) (ref.null none)) + ;; CHECK: (global $use-sibling-child (ref null $sibling-child) (ref.null none)) + (global $use-sibling-child (ref null $sibling-child) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; Continuation function type is not exposed. We can optimize its subtypes and +;; the subtypes of other types it references. +(module + ;; CHECK: (type $other-super (sub (struct))) + (type $other-super (sub (struct))) + ;; CHECK: (type $other (sub $other-super (struct))) + (type $other (sub $other-super (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $other-child (sub (struct))) + (type $other-child (sub $other (struct))) + + ;; CHECK: (type $func-sub-child (sub (func (result (ref null $other))))) + + ;; CHECK: (type $func-super (sub (func (result (ref null $other-super))))) + (type $func-super (sub (func (result (ref null $other-super))))) + ;; CHECK: (type $func-sub (sub $func-super (func (result (ref null $other))))) + (type $func-sub (sub $func-super (func (result (ref null $other))))) + (type $func-sub-child (sub $func-sub (func (result (ref null $other))))) + ;; CHECK: (type $cont (sub (cont $func-sub))) + (type $cont (sub (cont $func-sub))) + + ;; CHECK: (global $g-cont (ref null $cont) (ref.null nocont)) + (global $g-cont (ref null $cont) (ref.null nocont)) + ;; CHECK: (global $use-func-super (ref null $func-super) (ref.null nofunc)) + (global $use-func-super (ref null $func-super) (ref.null nofunc)) + ;; CHECK: (global $use-func-sub (ref null $func-sub) (ref.null nofunc)) + (global $use-func-sub (ref null $func-sub) (ref.null nofunc)) + ;; CHECK: (global $use-func-sub-child (ref null $func-sub-child) (ref.null nofunc)) + (global $use-func-sub-child (ref null $func-sub-child) (ref.null nofunc)) + ;; CHECK: (global $use-other-super (ref null $other-super) (ref.null none)) + (global $use-other-super (ref null $other-super) (ref.null none)) + ;; CHECK: (global $use-other (ref null $other) (ref.null none)) + (global $use-other (ref null $other) (ref.null none)) + ;; CHECK: (global $use-other-child (ref null $other-child) (ref.null none)) + (global $use-other-child (ref null $other-child) (ref.null none)) + ;; CHECK: (export "g-cont" (global $g-cont)) + (export "g-cont" (global $g-cont)) +) + +;; structref is public. We can optimize arrays, functions, and continuations. +(module + ;; CHECK: (type $struct-super (sub (struct))) + (type $struct-super (sub (struct))) + ;; CHECK: (type $struct-sub (sub $struct-super (struct))) + (type $struct-sub (sub $struct-super (struct))) + ;; CHECK: (type $struct-sub-child (sub $struct-sub (struct))) + (type $struct-sub-child (sub $struct-sub (struct))) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $array-super (sub (array i32))) + (type $array-super (sub (array i32))) + ;; CHECK: (type $array-sub (sub (array i32))) + (type $array-sub (sub $array-super (array i32))) + + ;; CHECK: (type $func-super (sub (func))) + (type $func-super (sub (func))) + ;; CHECK: (type $func-sub (sub (func))) + (type $func-sub (sub $func-super (func))) + + ;; CHECK: (type $cont-super (sub (cont $func-super))) + (type $cont-super (sub (cont $func-super))) + ;; CHECK: (type $cont-sub (sub (cont $func-super))) + (type $cont-sub (sub $cont-super (cont $func-super))) + + ;; CHECK: (global $g (mut structref) (ref.null none)) + (global $g (mut structref) (ref.null none)) + ;; CHECK: (global $use-struct-super (ref null $struct-super) (ref.null none)) + (global $use-struct-super (ref null $struct-super) (ref.null none)) + ;; CHECK: (global $use-struct-sub (ref null $struct-sub) (ref.null none)) + (global $use-struct-sub (ref null $struct-sub) (ref.null none)) + ;; CHECK: (global $use-struct-sub-child (ref null $struct-sub-child) (ref.null none)) + (global $use-struct-sub-child (ref null $struct-sub-child) (ref.null none)) + ;; CHECK: (global $use-array-super (ref null $array-super) (ref.null none)) + (global $use-array-super (ref null $array-super) (ref.null none)) + ;; CHECK: (global $use-array-sub (ref null $array-sub) (ref.null none)) + (global $use-array-sub (ref null $array-sub) (ref.null none)) + ;; CHECK: (global $use-func-super (ref null $func-super) (ref.null nofunc)) + (global $use-func-super (ref null $func-super) (ref.null nofunc)) + ;; CHECK: (global $use-func-sub (ref null $func-sub) (ref.null nofunc)) + (global $use-func-sub (ref null $func-sub) (ref.null nofunc)) + ;; CHECK: (global $use-cont-super (ref null $cont-super) (ref.null nocont)) + (global $use-cont-super (ref null $cont-super) (ref.null nocont)) + ;; CHECK: (global $use-cont-sub (ref null $cont-sub) (ref.null nocont)) + (global $use-cont-sub (ref null $cont-sub) (ref.null nocont)) + ;; CHECK: (export "g" (global $g)) + (export "g" (global $g)) +) + +;; A is exposed and has an exact reference to B, which has an inexact reference +;; to C. Subtypes of B can be optimized, but not subtypes of A or C. +(module + ;; CHECK: (type $C-super (sub (struct))) + (type $C-super (sub (struct))) + ;; CHECK: (type $C (sub $C-super (struct))) + (type $C (sub $C-super (struct))) + ;; CHECK: (type $C-child (sub $C (struct))) + (type $C-child (sub $C (struct))) + + (type $B-super (sub (struct))) + ;; CHECK: (type $B (sub $C-super (struct (field (ref $C))))) + (type $B (sub $B-super (struct (field (ref $C))))) + ;; CHECK: (type $B-child (sub (struct (field (ref $C))))) + (type $B-child (sub $B (struct (field (ref $C))))) + + (type $A-super (sub (struct))) + ;; CHECK: (type $A (sub $C-super (struct (field (ref (exact $B)))))) + (type $A (sub $A-super (struct (field (ref (exact $B)))))) + ;; CHECK: (type $A-child (sub $A (struct (field (ref (exact $B)))))) + (type $A-child (sub $A (struct (field (ref (exact $B)))))) + + ;; CHECK: (global $g-public (ref null $A) (ref.null none)) + (global $g-public (ref null $A) (ref.null none)) + ;; CHECK: (global $use-A-super (ref null $C-super) (ref.null none)) + (global $use-A-super (ref null $A-super) (ref.null none)) + ;; CHECK: (global $use-A-child (ref null $A-child) (ref.null none)) + (global $use-A-child (ref null $A-child) (ref.null none)) + ;; CHECK: (global $use-B-super (ref null $C-super) (ref.null none)) + (global $use-B-super (ref null $B-super) (ref.null none)) + ;; CHECK: (global $use-B-child (ref null $B-child) (ref.null none)) + (global $use-B-child (ref null $B-child) (ref.null none)) + ;; CHECK: (global $use-C-super (ref null $C-super) (ref.null none)) + (global $use-C-super (ref null $C-super) (ref.null none)) + ;; CHECK: (global $use-C-child (ref null $C-child) (ref.null none)) + (global $use-C-child (ref null $C-child) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; Array type exposed inexactly. We cannot optimize its subtypes. +(module + ;; CHECK: (type $array-super (sub (array (mut i32)))) + (type $array-super (sub (array (mut i32)))) + ;; CHECK: (type $array-sub (sub $array-super (array (mut i32)))) + (type $array-sub (sub $array-super (array (mut i32)))) + ;; CHECK: (type $array-sub-child (sub $array-sub (array (mut i32)))) + (type $array-sub-child (sub $array-sub (array (mut i32)))) + + ;; CHECK: (global $g (ref null $array-sub) (ref.null none)) + (global $g (ref null $array-sub) (ref.null none)) + ;; CHECK: (global $use-super (ref null $array-super) (ref.null none)) + (global $use-super (ref null $array-super) (ref.null none)) + ;; CHECK: (global $use-child (ref null $array-sub-child) (ref.null none)) + (global $use-child (ref null $array-sub-child) (ref.null none)) + ;; CHECK: (export "g" (global $g)) + (export "g" (global $g)) +) + +;; Array type exposed exactly. We can optimize its subtypes. +(module + ;; CHECK: (type $array-super (sub (array (mut i32)))) + (type $array-super (sub (array (mut i32)))) + ;; CHECK: (type $array-sub (sub $array-super (array (mut i32)))) + (type $array-sub (sub $array-super (array (mut i32)))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $array-sub-child (sub (array (mut i32)))) + (type $array-sub-child (sub $array-sub (array (mut i32)))) + + ;; CHECK: (type $3 (struct)) + + ;; CHECK: (global $g (ref (exact $array-sub)) (array.new_default $array-sub + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: )) + (global $g (ref (exact $array-sub)) (array.new_default $array-sub (i32.const 1))) + ;; CHECK: (global $use-super (ref null $array-super) (ref.null none)) + (global $use-super (ref null $array-super) (ref.null none)) + ;; CHECK: (global $use-child (ref null $array-sub-child) (ref.null none)) + (global $use-child (ref null $array-sub-child) (ref.null none)) + ;; CHECK: (export "g" (global $g)) + (export "g" (global $g)) +) + +;; Array element type is inexact. We cannot optimize its subtypes. +(module + ;; CHECK: (type $element-super (sub (struct))) + (type $element-super (sub (struct))) + ;; CHECK: (type $element (sub $element-super (struct))) + (type $element (sub $element-super (struct))) + ;; CHECK: (type $element-child (sub $element (struct))) + (type $element-child (sub $element (struct))) + ;; CHECK: (type $array (sub (array (ref $element)))) + (type $array (sub (array (ref $element)))) + + ;; CHECK: (global $g (ref null $array) (ref.null none)) + (global $g (ref null $array) (ref.null none)) + ;; CHECK: (global $use-element-super (ref null $element-super) (ref.null none)) + (global $use-element-super (ref null $element-super) (ref.null none)) + ;; CHECK: (global $use-element (ref null $element) (ref.null none)) + (global $use-element (ref null $element) (ref.null none)) + ;; CHECK: (global $use-element-child (ref null $element-child) (ref.null none)) + (global $use-element-child (ref null $element-child) (ref.null none)) + ;; CHECK: (export "g" (global $g)) + (export "g" (global $g)) +) + +;; Array element type is exact. We can optimize its subtypes. +(module + ;; CHECK: (type $element-super (sub (struct))) + (type $element-super (sub (struct))) + ;; CHECK: (type $element (sub $element-super (struct))) + (type $element (sub $element-super (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $element-child (sub (struct))) + (type $element-child (sub $element (struct))) + ;; CHECK: (type $3 (struct)) + + ;; CHECK: (type $array (sub (array (ref (exact $element))))) + (type $array (sub (array (ref (exact $element))))) + + ;; CHECK: (global $g (ref null $array) (ref.null none)) + (global $g (ref null $array) (ref.null none)) + ;; CHECK: (global $use-element-super (ref null $element-super) (ref.null none)) + (global $use-element-super (ref null $element-super) (ref.null none)) + ;; CHECK: (global $use-element (ref null $element) (ref.null none)) + (global $use-element (ref null $element) (ref.null none)) + ;; CHECK: (global $use-element-child (ref null $element-child) (ref.null none)) + (global $use-element-child (ref null $element-child) (ref.null none)) + ;; CHECK: (export "g" (global $g)) + (export "g" (global $g)) +) + +;; Table type exposed inexactly. We cannot optimize its subtypes. +(module + ;; CHECK: (type $T-super (sub (struct))) + (type $T-super (sub (struct))) + ;; CHECK: (type $T (sub $T-super (struct))) + (type $T (sub $T-super (struct))) + ;; CHECK: (type $T-child (sub $T (struct))) + (type $T-child (sub $T (struct))) + + (import "env" "table" (table 1 (ref null $T))) + ;; CHECK: (import "env" "table" (table $timport$0 1 (ref null $T))) + + ;; CHECK: (global $use-super (ref null $T-super) (ref.null none)) + (global $use-super (ref null $T-super) (ref.null none)) + ;; CHECK: (global $use-child (ref null $T-child) (ref.null none)) + (global $use-child (ref null $T-child) (ref.null none)) +) + +;; Table type exposed exactly. We can optimize its subtypes. +(module + ;; CHECK: (type $T-super (sub (struct))) + (type $T-super (sub (struct))) + ;; CHECK: (type $T (sub $T-super (struct))) + (type $T (sub $T-super (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $T-child (sub (struct))) + (type $T-child (sub $T (struct))) + + (import "env" "table" (table 1 (ref (exact $T)))) + ;; CHECK: (type $3 (struct)) + + ;; CHECK: (import "env" "table" (table $timport$0 1 (ref (exact $T)))) + + ;; CHECK: (global $use-super (ref null $T-super) (ref.null none)) + (global $use-super (ref null $T-super) (ref.null none)) + ;; CHECK: (global $use-child (ref null $T-child) (ref.null none)) + (global $use-child (ref null $T-child) (ref.null none)) +) + +;; Tag type and its children are exposed inexactly. We cannot optimize their +;; subtypes. +(module + ;; CHECK: (type $param-super (sub (struct))) + (type $param-super (sub (struct))) + ;; CHECK: (type $param (sub $param-super (struct))) + (type $param (sub $param-super (struct))) + ;; CHECK: (type $param-child (sub $param (struct))) + (type $param-child (sub $param (struct))) + ;; CHECK: (type $sig (sub (func (param (ref $param))))) + (type $sig (sub (func (param (ref $param))))) + + ;; CHECK: (global $use-param-super (ref null $param-super) (ref.null none)) + + ;; CHECK: (global $use-param-child (ref null $param-child) (ref.null none)) + + ;; CHECK: (tag $t (type $sig) (param (ref $param))) + (tag $t (type $sig)) + (global $use-param-super (ref null $param-super) (ref.null none)) + (global $use-param-child (ref null $param-child) (ref.null none)) + ;; CHECK: (export "t" (tag $t)) + (export "t" (tag $t)) +) + +;; Function signature exact vs inexact params and results. +(module + ;; CHECK: (type $param-exact-super (sub (struct (field i32)))) + (type $param-exact-super (sub (struct (field i32)))) + ;; CHECK: (type $param-exact (sub $param-exact-super (struct (field i32) (field i32)))) + (type $param-exact (sub $param-exact-super (struct (field i32 i32)))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $param-exact-child (sub (struct (field i32) (field i32) (field f32)))) + (type $param-exact-child (sub $param-exact (struct (field i32 i32 f32)))) + + ;; CHECK: (type $result-exact-child (sub (struct (field f32) (field f32) (field i32)))) + + ;; CHECK: (type $param-inexact-super (sub (struct (field i64)))) + (type $param-inexact-super (sub (struct (field i64)))) + ;; CHECK: (type $param-inexact (sub $param-inexact-super (struct (field i64) (field i64)))) + (type $param-inexact (sub $param-inexact-super (struct (field i64 i64)))) + ;; CHECK: (type $param-inexact-child (sub $param-inexact (struct (field i64) (field i64) (field f64)))) + (type $param-inexact-child (sub $param-inexact (struct (field i64 i64 f64)))) + + ;; CHECK: (type $result-exact-super (sub (struct (field f32)))) + (type $result-exact-super (sub (struct (field f32)))) + ;; CHECK: (type $result-exact (sub $result-exact-super (struct (field f32) (field f32)))) + (type $result-exact (sub $result-exact-super (struct (field f32 f32)))) + (type $result-exact-child (sub $result-exact (struct (field f32 f32 i32)))) + + ;; CHECK: (type $result-inexact-super (sub (struct (field f64)))) + (type $result-inexact-super (sub (struct (field f64)))) + ;; CHECK: (type $result-inexact (sub $result-inexact-super (struct (field f64) (field f64)))) + (type $result-inexact (sub $result-inexact-super (struct (field f64 f64)))) + ;; CHECK: (type $result-inexact-child (sub $result-inexact (struct (field f64) (field f64) (field i64)))) + (type $result-inexact-child (sub $result-inexact (struct (field f64 f64 i64)))) + + ;; CHECK: (type $sig (sub (func (param (ref (exact $param-exact)) (ref $param-inexact)) (result (ref (exact $result-exact)) (ref $result-inexact))))) + (type $sig (sub (func (param (ref (exact $param-exact)) (ref $param-inexact)) (result (ref (exact $result-exact)) (ref $result-inexact))))) + + + ;; CHECK: (global $use-param-exact-super (ref null $param-exact-super) (ref.null none)) + (global $use-param-exact-super (ref null $param-exact-super) (ref.null none)) + ;; CHECK: (global $use-param-exact-child (ref null $param-exact-child) (ref.null none)) + (global $use-param-exact-child (ref null $param-exact-child) (ref.null none)) + ;; CHECK: (global $use-param-inexact-super (ref null $param-inexact-super) (ref.null none)) + (global $use-param-inexact-super (ref null $param-inexact-super) (ref.null none)) + ;; CHECK: (global $use-param-inexact-child (ref null $param-inexact-child) (ref.null none)) + (global $use-param-inexact-child (ref null $param-inexact-child) (ref.null none)) + ;; CHECK: (global $use-result-exact-super (ref null $result-exact-super) (ref.null none)) + (global $use-result-exact-super (ref null $result-exact-super) (ref.null none)) + ;; CHECK: (global $use-result-exact-child (ref null $result-exact-child) (ref.null none)) + (global $use-result-exact-child (ref null $result-exact-child) (ref.null none)) + ;; CHECK: (global $use-result-inexact-super (ref null $result-inexact-super) (ref.null none)) + (global $use-result-inexact-super (ref null $result-inexact-super) (ref.null none)) + ;; CHECK: (global $use-result-inexact-child (ref null $result-inexact-child) (ref.null none)) + (global $use-result-inexact-child (ref null $result-inexact-child) (ref.null none)) + ;; CHECK: (export "f" (func $f)) + (export "f" (func $f)) + ;; CHECK: (func $f (type $sig) (param $0 (ref (exact $param-exact))) (param $1 (ref $param-inexact)) (result (ref (exact $result-exact)) (ref $result-inexact)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $f (type $sig) (param (ref (exact $param-exact)) (ref $param-inexact)) (result (ref (exact $result-exact)) (ref $result-inexact)) + (unreachable) + ) +) + +;; contref is public. We can optimize structs, arrays, and functions. +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct-super (sub (struct))) + (type $struct-super (sub (struct))) + ;; CHECK: (type $struct-sub (sub (struct))) + (type $struct-sub (sub $struct-super (struct))) + + ;; CHECK: (type $array-super (sub (array i32))) + (type $array-super (sub (array i32))) + ;; CHECK: (type $array-sub (sub (array i32))) + (type $array-sub (sub $array-super (array i32))) + + ;; CHECK: (type $func-sub (sub (func))) + + ;; CHECK: (type $func-super (sub (func))) + (type $func-super (sub (func))) + (type $func-sub (sub $func-super (func))) + + ;; CHECK: (type $cont-super (sub (cont $func-super))) + (type $cont-super (sub (cont $func-super))) + ;; CHECK: (type $cont-sub (sub $cont-super (cont $func-super))) + (type $cont-sub (sub $cont-super (cont $func-super))) + ;; CHECK: (type $cont-sub-child (sub $cont-sub (cont $func-super))) + (type $cont-sub-child (sub $cont-sub (cont $func-super))) + + ;; CHECK: (global $g (mut contref) (ref.null nocont)) + (global $g (mut contref) (ref.null nocont)) + ;; CHECK: (global $use-struct-super (ref null $struct-super) (ref.null none)) + (global $use-struct-super (ref null $struct-super) (ref.null none)) + ;; CHECK: (global $use-struct-sub (ref null $struct-sub) (ref.null none)) + (global $use-struct-sub (ref null $struct-sub) (ref.null none)) + ;; CHECK: (global $use-array-super (ref null $array-super) (ref.null none)) + (global $use-array-super (ref null $array-super) (ref.null none)) + ;; CHECK: (global $use-array-sub (ref null $array-sub) (ref.null none)) + (global $use-array-sub (ref null $array-sub) (ref.null none)) + ;; CHECK: (global $use-func-super (ref null $func-super) (ref.null nofunc)) + (global $use-func-super (ref null $func-super) (ref.null nofunc)) + ;; CHECK: (global $use-func-sub (ref null $func-sub) (ref.null nofunc)) + (global $use-func-sub (ref null $func-sub) (ref.null nofunc)) + ;; CHECK: (global $use-cont-super (ref null $cont-super) (ref.null nocont)) + (global $use-cont-super (ref null $cont-super) (ref.null nocont)) + ;; CHECK: (global $use-cont-sub (ref null $cont-sub) (ref.null nocont)) + (global $use-cont-sub (ref null $cont-sub) (ref.null nocont)) + ;; CHECK: (global $use-cont-sub-child (ref null $cont-sub-child) (ref.null nocont)) + (global $use-cont-sub-child (ref null $cont-sub-child) (ref.null nocont)) + ;; CHECK: (export "g" (global $g)) + (export "g" (global $g)) +) + +;; A is exposed exactly and has an inexact reference to B, which has an exact +;; reference to C. Subtypes of A and C but not B can be optimized. +(module + ;; CHECK: (type $C-super (sub (struct))) + (type $C-super (sub (struct))) + ;; CHECK: (type $C (sub $C-super (struct))) + (type $C (sub $C-super (struct))) + ;; CHECK: (type $B (sub $C-super (struct (field (ref null (exact $C)))))) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $C-child (sub (struct))) + (type $C-child (sub $C (struct))) + + (type $B-super (sub (struct))) + (type $B (sub $B-super (struct (field (ref null (exact $C)))))) + ;; CHECK: (type $A-child (sub (struct (field (ref null $B))))) + + ;; CHECK: (type $B-child (sub $B (struct (field (ref null (exact $C)))))) + (type $B-child (sub $B (struct (field (ref null (exact $C)))))) + + (type $A-super (sub (struct))) + ;; CHECK: (type $A (sub $C-super (struct (field (ref null $B))))) + (type $A (sub $A-super (struct (field (ref null $B))))) + (type $A-child (sub $A (struct (field (ref null $B))))) + + ;; CHECK: (global $g-public (ref (exact $A)) (struct.new_default $A)) + (global $g-public (ref (exact $A)) (struct.new_default $A)) + ;; CHECK: (global $use-A-super (ref null $C-super) (ref.null none)) + (global $use-A-super (ref null $A-super) (ref.null none)) + ;; CHECK: (global $use-A-child (ref null $A-child) (ref.null none)) + (global $use-A-child (ref null $A-child) (ref.null none)) + ;; CHECK: (global $use-B-super (ref null $C-super) (ref.null none)) + (global $use-B-super (ref null $B-super) (ref.null none)) + ;; CHECK: (global $use-B-child (ref null $B-child) (ref.null none)) + (global $use-B-child (ref null $B-child) (ref.null none)) + ;; CHECK: (global $use-C-super (ref null $C-super) (ref.null none)) + (global $use-C-super (ref null $C-super) (ref.null none)) + ;; CHECK: (global $use-C-child (ref null $C-child) (ref.null none)) + (global $use-C-child (ref null $C-child) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +) + +;; A is exposed, making A-super and B public. The inexact reference from A-super +;; to B does not prevent B-child from being optimized, nor does it prevent the +;; subtypes of C (referenced inexactly from B) from being optimized. +(module + ;; CHECK: (type $C-super (sub (struct))) + (type $C-super (sub (struct))) + ;; CHECK: (type $C (sub $C-super (struct))) + (type $C (sub $C-super (struct))) + ;; CHECK: (rec + ;; CHECK-NEXT: (type $C-child (sub (struct))) + (type $C-child (sub $C (struct))) + + ;; CHECK: (type $B-child (sub (struct (field (ref null $C))))) + + ;; CHECK: (type $B-super (sub (struct (field (ref null $C))))) + (type $B-super (sub (struct (field (ref null $C))))) + ;; CHECK: (type $B (sub $B-super (struct (field (ref null $C))))) + (type $B (sub $B-super (struct (field (ref null $C))))) + (type $B-child (sub $B (struct (field (ref null $C))))) + + ;; CHECK: (type $A-super (sub (struct (field (ref null $B))))) + (type $A-super (sub (struct (field (ref null $B))))) + ;; CHECK: (type $A (sub $A-super (struct (field nullref)))) + (type $A (sub $A-super (struct (field (ref null none))))) + + ;; CHECK: (global $g-public (ref null $A) (ref.null none)) + (global $g-public (ref null $A) (ref.null none)) + ;; CHECK: (global $use-A-super (ref null $A-super) (ref.null none)) + (global $use-A-super (ref null $A-super) (ref.null none)) + ;; CHECK: (global $use-B-super (ref null $B-super) (ref.null none)) + (global $use-B-super (ref null $B-super) (ref.null none)) + ;; CHECK: (global $use-B (ref null $B) (ref.null none)) + (global $use-B (ref null $B) (ref.null none)) + ;; CHECK: (global $use-B-child (ref null $B-child) (ref.null none)) + (global $use-B-child (ref null $B-child) (ref.null none)) + ;; CHECK: (global $use-C-super (ref null $C-super) (ref.null none)) + (global $use-C-super (ref null $C-super) (ref.null none)) + ;; CHECK: (global $use-C (ref null $C) (ref.null none)) + (global $use-C (ref null $C) (ref.null none)) + ;; CHECK: (global $use-C-child (ref null $C-child) (ref.null none)) + (global $use-C-child (ref null $C-child) (ref.null none)) + ;; CHECK: (export "g-public" (global $g-public)) + (export "g-public" (global $g-public)) +)