From 89c8c0060e628a743e42b54a62a4980f35fe4f00 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 29 May 2026 18:07:46 -0700 Subject: [PATCH] Fix open world public types In an open world, we have to assume that the environment may cast any public type down to any of its subtypes and expect that to work before and after optimization. We therefore must make public visibility propagate to subtypes. But we can be more precise than this, because some types are public not because they can cross the module boundary, but rather because they are reachable in the definition of some other type that can cross the module boundary. Subtypes of such public types do not need to be public, since their values will never cross the module boundary. In addition, we do not need to make public the subtypes of types that are exposed only as exact references. Allow Unsubtyping to operate in open-world mode and use it in tests that the visibility propagation works correctly. Fixes #8718. --- src/ir/module-utils.cpp | 272 +++-- src/ir/module-utils.h | 3 +- src/passes/Unsubtyping.cpp | 3 - src/wasm/wasm-validator.cpp | 2 +- test/lit/passes/gufa-tnh.wast | 138 ++- test/lit/passes/gufa-vs-cfp.wast | 2 +- test/lit/passes/minimize-rec-groups.wast | 2 +- .../signature-refining-configureAll.wast | 65 +- test/lit/passes/unsubtyping-open-world.wast | 959 ++++++++++++++++++ 9 files changed, 1279 insertions(+), 167 deletions(-) create mode 100644 test/lit/passes/unsubtyping-open-world.wast 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)) +)