From 20e051ab67c1c12b8ebabf6b531f110638a46cf2 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 5 Sep 2025 22:58:48 -0700 Subject: [PATCH 01/21] Propate to exact types in struct-utils.h Key collected struct information on both heap type and exactness, allowing queries for exact types to return more precise results than queries on the corresponding inexact types. Use this to fix a bug in CFP where it failed to take into account exactness and would unnecessarily and incorrectly emit a select between two values of different types where a single exact type was expected. Also update GTO to propagate to exact types even though it does not take advantage of them. This is necessary because the FieldScanner now collects information for exact and inexact types separately and they need to be combined. --- src/ir/struct-utils.h | 115 +++++++++++++++--------- src/passes/ConstantFieldPropagation.cpp | 19 ++-- src/passes/GlobalTypeOptimization.cpp | 10 +-- test/lit/passes/cfp-reftest-desc.wast | 79 ++++++++++++++++ test/lit/passes/cfp-reftest.wast | 54 +++++++++++ 5 files changed, 223 insertions(+), 54 deletions(-) diff --git a/src/ir/struct-utils.h b/src/ir/struct-utils.h index c2c92f32a42..35c4e7ef613 100644 --- a/src/ir/struct-utils.h +++ b/src/ir/struct-utils.h @@ -19,6 +19,7 @@ #include "ir/properties.h" #include "ir/subtypes.h" +#include "wasm-type.h" #include "wasm.h" namespace wasm { @@ -88,19 +89,24 @@ template struct StructValues : public std::vector { // Also provides a combineInto() helper that combines one map into another. This // depends on the underlying T defining a combine() method. template -struct StructValuesMap : public std::unordered_map> { +struct StructValuesMap + : public std::unordered_map, StructValues> { // When we access an item, if it does not already exist, create it with a // vector of the right length for that type. - StructValues& operator[](HeapType type) { - assert(type.isStruct()); + StructValues& operator[](std::pair type) { + assert(type.first.isStruct()); auto inserted = this->insert({type, {}}); auto& values = inserted.first->second; if (inserted.second) { - values.resize(type.getStruct().fields.size()); + values.resize(type.first.getStruct().fields.size()); } return values; } + StructValues& operator[](HeapType type) { + return (*this)[{type, Inexact}]; + } + void combineInto(StructValuesMap& combinedInfos) const { for (auto& [type, info] : *this) { for (Index i = 0; i < info.size(); i++) { @@ -113,7 +119,8 @@ struct StructValuesMap : public std::unordered_map> { void dump(std::ostream& o) { o << "dump " << this << '\n'; for (auto& [type, vec] : (*this)) { - o << "dump " << type << " " << &vec << ' '; + o << "dump " << type.first << (type.second == Exact ? " exact " : " ") + << &vec << ' '; for (auto x : vec) { x.dump(o); o << " "; @@ -203,7 +210,8 @@ struct StructScanner // Note writes to all the fields of the struct. auto heapType = type.getHeapType(); auto& fields = heapType.getStruct().fields; - auto& infos = functionNewInfos[this->getFunction()][heapType]; + auto ht = std::make_pair(heapType, Exact); + auto& infos = functionNewInfos[this->getFunction()][ht]; for (Index i = 0; i < fields.size(); i++) { if (curr->isWithDefault()) { self().noteDefault(fields[i].type, heapType, i, infos[i]); @@ -224,11 +232,12 @@ struct StructScanner } // Note a write to this field of the struct. - noteExpressionOrCopy(curr->value, - type.getHeapType(), - curr->index, - functionSetGetInfos[this->getFunction()] - [type.getHeapType()][curr->index]); + auto ht = std::make_pair(type.getHeapType(), type.getExactness()); + noteExpressionOrCopy( + curr->value, + type.getHeapType(), + curr->index, + functionSetGetInfos[this->getFunction()][ht][curr->index]); } void visitStructGet(StructGet* curr) { @@ -237,11 +246,11 @@ struct StructScanner return; } - auto heapType = type.getHeapType(); + auto ht = std::make_pair(type.getHeapType(), type.getExactness()); auto index = curr->index; - self().noteRead(heapType, + self().noteRead(type.getHeapType(), index, - functionSetGetInfos[this->getFunction()][heapType][index]); + functionSetGetInfos[this->getFunction()][ht][index]); } void visitStructRMW(StructRMW* curr) { @@ -251,9 +260,9 @@ struct StructScanner } auto heapType = type.getHeapType(); + auto ht = std::make_pair(heapType, type.getExactness()); auto index = curr->index; - auto& info = - functionSetGetInfos[this->getFunction()][type.getHeapType()][index]; + auto& info = functionSetGetInfos[this->getFunction()][ht][index]; if (curr->op == RMWXchg) { // An xchg is really like a read and write combined. @@ -274,9 +283,9 @@ struct StructScanner } auto heapType = type.getHeapType(); + auto ht = std::make_pair(heapType, type.getExactness()); auto index = curr->index; - auto& info = - functionSetGetInfos[this->getFunction()][type.getHeapType()][curr->index]; + auto& info = functionSetGetInfos[this->getFunction()][ht][index]; // A cmpxchg is like a read and conditional write. self().noteRead(heapType, index, info); @@ -310,11 +319,12 @@ struct StructScanner return; } auto heapType = type.getHeapType(); + auto ht = std::make_pair(heapType, type.getExactness()); if (heapType.isStruct()) { // Any subtype of the reference here may be read from. self().noteRead(heapType, DescriptorIndex, - functionSetGetInfos[this->getFunction()][heapType].desc); + functionSetGetInfos[this->getFunction()][ht].desc); return; } } @@ -372,13 +382,19 @@ template class TypeHierarchyPropagator { // Propagate given a StructValuesMap, which means we need to take into // account fields. void propagateToSuperTypes(StructValuesMap& infos) { - propagate(infos, false, true); + propagate(infos, false, true, true); } void propagateToSubTypes(StructValuesMap& infos) { - propagate(infos, true, false); + propagate(infos, true, false, false); + } + void propagateToSubTypesWithExact(StructValuesMap& infos) { + propagate(infos, true, false, true); } void propagateToSuperAndSubTypes(StructValuesMap& infos) { - propagate(infos, true, true); + propagate(infos, true, true, false); + } + void propagateToSuperAndSubTypesWithExact(StructValuesMap& infos) { + propagate(infos, true, true, true); } // Propagate on a simpler map of structs and infos (that is, not using @@ -398,46 +414,63 @@ template class TypeHierarchyPropagator { private: void propagate(StructValuesMap& combinedInfos, bool toSubTypes, - bool toSuperTypes) { - UniqueDeferredQueue work; - for (auto& [type, _] : combinedInfos) { - work.push(type); + bool toSuperTypes, + bool includeExact) { + UniqueDeferredQueue> work; + for (auto& [ht, _] : combinedInfos) { + work.push(ht); } while (!work.empty()) { - auto type = work.pop(); - auto& infos = combinedInfos[type]; + auto [type, exactness] = work.pop(); + auto& infos = combinedInfos[{type, exactness}]; if (toSuperTypes) { - // Propagate shared fields to the supertype. - if (auto superType = type.getDeclaredSuperType()) { - auto& superInfos = combinedInfos[*superType]; - auto& superFields = superType->getStruct().fields; - for (Index i = 0; i < superFields.size(); i++) { + // Propagate shared fields to the supertype, which may be the inexact + // version of the same type. + std::optional> super; + if (exactness == Exact) { + super = {type, Inexact}; + } else if (auto superType = type.getDeclaredSuperType()) { + super = {*superType, Inexact}; + } + if (super) { + auto& superInfos = combinedInfos[*super]; + const auto& superFields = &super->first.getStruct().fields; + for (Index i = 0; i < superFields->size(); i++) { if (superInfos[i].combine(infos[i])) { - work.push(*superType); + work.push(*super); } } // Propagate the descriptor to the super, if the super has one. - if (superType->getDescriptorType() && + if (super->first.getDescriptorType() && superInfos.desc.combine(infos.desc)) { - work.push(*superType); + work.push(*super); } } } if (toSubTypes) { - // Propagate shared fields to the subtypes. + // Propagate shared fields to the subtypes, which may just be the exact + // version of the same type. auto numFields = type.getStruct().fields.size(); - for (auto subType : subTypes.getImmediateSubTypes(type)) { - auto& subInfos = combinedInfos[subType]; + std::vector> subs; + if (includeExact && exactness == Inexact) { + subs = {{type, Exact}}; + } else { + for (auto subType : subTypes.getImmediateSubTypes(type)) { + subs.emplace_back(subType, Inexact); + } + } + for (auto sub : subs) { + auto& subInfos = combinedInfos[sub]; for (Index i = 0; i < numFields; i++) { if (subInfos[i].combine(infos[i])) { - work.push(subType); + work.push(sub); } } // Propagate the descriptor. if (subInfos.desc.combine(infos.desc)) { - work.push(subType); + work.push(sub); } } } diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 9838bf636ff..8d0244f9450 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -53,7 +53,6 @@ #include "ir/bits.h" #include "ir/gc-type-utils.h" -#include "ir/module-utils.h" #include "ir/possible-constant.h" #include "ir/struct-utils.h" #include "ir/utils.h" @@ -114,8 +113,10 @@ struct FunctionOptimizer : public WalkerPass> { return heapType; } - PossibleConstantValues getInfo(HeapType type, Index index) { - if (auto it = propagatedInfos.find(type); it != propagatedInfos.end()) { + PossibleConstantValues + getInfo(HeapType type, Exactness exactness, Index index) { + if (auto it = propagatedInfos.find({type, exactness}); + it != propagatedInfos.end()) { // There is information on this type, fetch it. return it->second[index]; } @@ -177,7 +178,8 @@ struct FunctionOptimizer : public WalkerPass> { // Find the info for this field, and see if we can optimize. First, see if // there is any information for this heap type at all. If there isn't, it is // as if nothing was ever noted for that field. - PossibleConstantValues info = getInfo(heapType, index); + PossibleConstantValues info = + getInfo(heapType, ref->type.getExactness(), index); if (!info.hasNoted()) { // This field is never written at all. That means that we do not even // construct any data of this type, and so it is a logic error to reach @@ -282,7 +284,7 @@ struct FunctionOptimizer : public WalkerPass> { return; } - auto iter = rawNewInfos.find(type); + auto iter = rawNewInfos.find({type, Exact}); if (iter == rawNewInfos.end()) { // This type has no struct.news, so we can ignore it: it is abstract. return; @@ -446,7 +448,8 @@ struct PCVScanner void noteCopy(HeapType type, Index index, PossibleConstantValues& info) { // Note copies, as they must be considered later. See the comment on the // propagation of values below. - functionCopyInfos[getFunction()][type][index] = true; + // TODO: Take into account exactness here. + functionCopyInfos[getFunction()][{type, Inexact}][index] = true; } void noteRead(HeapType type, Index index, PossibleConstantValues& info) { @@ -558,7 +561,7 @@ struct ConstantFieldPropagation : public Pass { // a copy of A means it could be a copy of B or C). StructUtils::TypeHierarchyPropagator boolPropagator(subTypes); - boolPropagator.propagateToSubTypes(combinedCopyInfos); + boolPropagator.propagateToSubTypesWithExact(combinedCopyInfos); for (auto& [type, copied] : combinedCopyInfos) { for (Index i = 0; i < copied.size(); i++) { if (copied[i]) { @@ -570,7 +573,7 @@ struct ConstantFieldPropagation : public Pass { StructUtils::TypeHierarchyPropagator propagator( subTypes); propagator.propagateToSuperTypes(combinedNewInfos); - propagator.propagateToSuperAndSubTypes(combinedSetInfos); + propagator.propagateToSuperAndSubTypesWithExact(combinedSetInfos); // Combine both sources of information to the final information that gets // care about. diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index 3c6e411799c..d3d0a3a8883 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -30,7 +30,6 @@ #include "ir/struct-utils.h" #include "ir/subtypes.h" #include "ir/type-updating.h" -#include "ir/utils.h" #include "pass.h" #include "support/permutations.h" #include "wasm-builder.h" @@ -205,9 +204,9 @@ struct GlobalTypeOptimization : public Pass { SubTypes subTypes(*module); StructUtils::TypeHierarchyPropagator propagator(subTypes); auto dataFromSubsAndSupersMap = combinedSetGetInfos; - propagator.propagateToSuperAndSubTypes(dataFromSubsAndSupersMap); + propagator.propagateToSuperAndSubTypesWithExact(dataFromSubsAndSupersMap); auto dataFromSupersMap = std::move(combinedSetGetInfos); - propagator.propagateToSubTypes(dataFromSupersMap); + propagator.propagateToSubTypesWithExact(dataFromSupersMap); // Find the public types, which we must not modify. auto publicTypes = ModuleUtils::getPublicHeapTypes(*module); @@ -224,8 +223,9 @@ struct GlobalTypeOptimization : public Pass { continue; } auto& fields = type.getStruct().fields; - auto& dataFromSubsAndSupers = dataFromSubsAndSupersMap[type]; - auto& dataFromSupers = dataFromSupersMap[type]; + auto ht = std::make_pair(type, Exact); + auto& dataFromSubsAndSupers = dataFromSubsAndSupersMap[ht]; + auto& dataFromSupers = dataFromSupersMap[ht]; // Process immutability. for (Index i = 0; i < fields.size(); i++) { diff --git a/test/lit/passes/cfp-reftest-desc.wast b/test/lit/passes/cfp-reftest-desc.wast index 11c649260a4..7064276f2b4 100644 --- a/test/lit/passes/cfp-reftest-desc.wast +++ b/test/lit/passes/cfp-reftest-desc.wast @@ -106,3 +106,82 @@ ) ) +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (descriptor $super.desc (struct)))) + (type $super (sub (descriptor $super.desc (struct)))) + ;; CHECK: (type $super.desc (sub (describes $super (struct)))) + (type $super.desc (sub (describes $super (struct)))) + + ;; CHECK: (type $func (func (param i32) (result i32))) + (type $func (func (param i32) (result i32))) + + ;; CHECK: (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub (sub $super (descriptor $sub.desc (struct)))) + ;; CHECK: (type $sub.desc (sub $super.desc (describes $sub (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) + ) + + ;; CHECK: (type $5 (func (result (ref (exact $super.desc))))) + + ;; CHECK: (global $A (ref (exact $super.desc)) (struct.new_default $super.desc)) + (global $A (ref (exact $super.desc)) (struct.new $super.desc)) + + ;; CHECK: (global $B (ref (exact $sub.desc)) (struct.new_default $sub.desc)) + (global $B (ref (exact $sub.desc)) (struct.new $sub.desc)) + + ;; CHECK: (func $test (type $5) (result (ref (exact $super.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $super + ;; CHECK-NEXT: (global.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $sub + ;; CHECK-NEXT: (global.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result (ref (exact $super.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result (ref (exact $super.desc))) + (drop + (struct.new_default $super + (global.get $A) + ) + ) + (drop + (struct.new_default $sub + (global.get $B) + ) + ) + ;; We read from an exact $super here, so the type of the ref.get_desc is + ;; exact as well. If we ignore that in the optimization, we might think that + ;; the two struct.news before us are two possible values, one from $super and + ;; one from $sub, and if we emitted a ref.test between those values, we'd get + ;; a non-exact value that does not validate. + ;; + ;; Instead, we should look only at $super itself, and optimize to $A. + (ref.get_desc $super + (block (result (ref null (exact $super))) + (ref.null $super) + ) + ) + ) + + ;; CHECK: (func $func (type $func) (param $0 i32) (result i32) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + (func $func (type $func) (param $0 i32) (result i32) + (i32.const 42) + ) +) diff --git a/test/lit/passes/cfp-reftest.wast b/test/lit/passes/cfp-reftest.wast index 3f903e8b451..f46b211cd5e 100644 --- a/test/lit/passes/cfp-reftest.wast +++ b/test/lit/passes/cfp-reftest.wast @@ -1456,3 +1456,57 @@ ) ) ) + +(module + ;; CHECK: (type $struct (sub (struct (field i32)))) + (type $struct (sub (struct i32))) + ;; CHECK: (type $1 (func)) + + ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64)))) + (type $substruct (sub $struct (struct i32 f64))) + + ;; CHECK: (type $3 (func (param (ref null (exact $struct))) (result i32))) + + ;; CHECK: (func $create (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $substruct + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create + ;; Used below. + (drop + (struct.new $struct + (i32.const 10) + ) + ) + (drop + (struct.new $substruct + (i32.const 20) + (f64.const 3.14159) + ) + ) + ) + ;; CHECK: (func $get (type $3) (param $struct (ref null (exact $struct))) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + (func $get (param $struct (ref null (exact $struct))) (result i32) + ;; The type here is exact, so we do not even need to do a select: only the + ;; super's value is possible, 10. + (struct.get $struct 0 + (local.get $struct) + ) + ) +) From be904ae09b912a31805495b44801bc2418a3fe87 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 8 Sep 2025 14:28:35 -0700 Subject: [PATCH 02/21] [NFC] Add tests for missing exact CFP optimizations CFP takes advantage of exact type information, but it currently does so only for immutable fields. It is also unnecessarily conservative about how it propagates type information so that sets to a type inhibit optimizations of its sibling types, even though those sets cannot possibly affect the siblings. Add tests for these cases to demonstrate the benefit of follow-on PRs that will fix these issues. --- test/lit/passes/cfp.wast | 365 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 365 insertions(+) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 324178d331d..16e80c5c8f6 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2499,6 +2499,371 @@ ) ) +(module + ;; Same as above but now the fields are mutable. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (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 i32))) + + ;; CHECK: (func $test (type $2) (param $0 i32) + ;; CHECK-NEXT: (local $A (ref $A)) + ;; CHECK-NEXT: (local $B (ref $B)) + ;; CHECK-NEXT: (local $A-exact (ref (exact $A))) + ;; CHECK-NEXT: (local $B-exact (ref (exact $B))) + ;; CHECK-NEXT: (local.set $A + ;; CHECK-NEXT: (local.tee $A-exact + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $B + ;; CHECK-NEXT: (local.tee $B-exact + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $0 i32) + (local $A (ref $A)) + (local $B (ref $B)) + (local $A-exact (ref (exact $A))) + (local $B-exact (ref (exact $B))) + (local.set $A + (local.tee $A-exact + (struct.new $A + (i32.const 10) + ) + ) + ) + (local.set $B + (local.tee $B-exact + (struct.new $B + (i32.const 20) + ) + ) + ) + ;; We can optimize an inexact $B, but not $A. + (drop + (struct.get $A 0 + (local.get $A) + ) + ) + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + ;; We should be able to optimize both exact references TODO. + (drop + (struct.get $A 0 + (local.get $A-exact) + ) + ) + (drop + (struct.get $B 0 + (local.get $B-exact) + ) + ) + ) +) + +(module + ;; Same as above but now we add no-op sets. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (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 i32))) + + ;; CHECK: (func $test (type $2) (param $0 i32) + ;; CHECK-NEXT: (local $A (ref $A)) + ;; CHECK-NEXT: (local $B (ref $B)) + ;; CHECK-NEXT: (local $A-exact (ref (exact $A))) + ;; CHECK-NEXT: (local $B-exact (ref (exact $B))) + ;; CHECK-NEXT: (local.set $A + ;; CHECK-NEXT: (local.tee $A-exact + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $B + ;; CHECK-NEXT: (local.tee $B-exact + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $0 i32) + (local $A (ref $A)) + (local $B (ref $B)) + (local $A-exact (ref (exact $A))) + (local $B-exact (ref (exact $B))) + (local.set $A + (local.tee $A-exact + (struct.new $A + (i32.const 10) + ) + ) + ) + (local.set $B + (local.tee $B-exact + (struct.new $B + (i32.const 20) + ) + ) + ) + ;; No-op exact sets should not inhibit optimization. + (struct.set $A 0 + (local.get $A-exact) + (i32.const 10) + ) + (struct.set $B 0 + (local.get $B-exact) + (i32.const 20) + ) + ;; We should be able to optimize an inexact $B, but not $A TODO. + (drop + (struct.get $A 0 + (local.get $A) + ) + ) + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + ;; We should be able to optimize both exact references TODO. + (drop + (struct.get $A 0 + (local.get $A-exact) + ) + ) + (drop + (struct.get $B 0 + (local.get $B-exact) + ) + ) + ) +) + +(module + ;; Sets to a subtype should not affect exact gets of a supertype or sibling. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (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 $C (sub $A (struct (field (mut i32))))) + (type $C (sub $A (struct (field (mut i32))))) + ) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (type $4 (func (param (ref $B)))) + + ;; CHECK: (type $5 (func (param (ref $A) (ref $B) (ref $C)))) + + ;; CHECK: (type $6 (func (param (ref (exact $A)) (ref (exact $B)) (ref (exact $C))))) + + ;; CHECK: (func $news (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: (drop + ;; CHECK-NEXT: (struct.new $C + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $news + (drop + (struct.new $A + (i32.const 10) + ) + ) + (drop + (struct.new $B + (i32.const 20) + ) + ) + (drop + (struct.new $C + (i32.const 30) + ) + ) + ) + + ;; CHECK: (func $set-B (type $4) (param $B (ref $B)) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 666) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-B (param $B (ref $B)) + ;; Inhibits optimizations on B and inexact A only. + (struct.set $B 0 + (local.get $B) + (i32.const 666) + ) + ) + + ;; CHECK: (func $inexact-gets (type $5) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $inexact-gets (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + (drop + (struct.get $A 0 + (local.get $A) + ) + ) + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + ;; This should be optimizable TODO. + (drop + (struct.get $C 0 + (local.get $C) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $6) (param $A-exact (ref (exact $A))) (param $B-exact (ref (exact $B))) (param $C-exact (ref (exact $C))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $C 0 + ;; CHECK-NEXT: (local.get $C-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets (param $A-exact (ref (exact $A))) + (param $B-exact (ref (exact $B))) + (param $C-exact (ref (exact $C))) + (drop + ;; This should be optimizable TODO. + (struct.get $A 0 + (local.get $A-exact) + ) + ) + ;; Not optimizable. + (drop + (struct.get $B 0 + (local.get $B-exact) + ) + ) + ;; This should be optimizable TODO. + (drop + (struct.get $C 0 + (local.get $C-exact) + ) + ) + ) +) + ;; A type with two subtypes. A copy on the parent can affect either child. (module (rec From 65eff0c1cefc0e63b2cc1d6ec5d99b8f23cc27d0 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 8 Sep 2025 14:47:42 -0700 Subject: [PATCH 03/21] udpate test --- test/lit/passes/cfp.wast | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 16e80c5c8f6..4ccdda92a2b 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2546,8 +2546,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -2591,7 +2596,7 @@ (local.get $B) ) ) - ;; We should be able to optimize both exact references TODO. + ;; We should be able to optimize both exact references. (drop (struct.get $A 0 (local.get $A-exact) From a310e7fd8035de25e2b82799335b4914f2df4da4 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 8 Sep 2025 21:30:31 -0700 Subject: [PATCH 04/21] fix --- src/ir/struct-utils.h | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/ir/struct-utils.h b/src/ir/struct-utils.h index 35c4e7ef613..7f3e7f91a6f 100644 --- a/src/ir/struct-utils.h +++ b/src/ir/struct-utils.h @@ -449,17 +449,16 @@ template class TypeHierarchyPropagator { } } - if (toSubTypes) { + if (toSubTypes && exactness == Inexact) { // Propagate shared fields to the subtypes, which may just be the exact // version of the same type. auto numFields = type.getStruct().fields.size(); std::vector> subs; - if (includeExact && exactness == Inexact) { - subs = {{type, Exact}}; - } else { - for (auto subType : subTypes.getImmediateSubTypes(type)) { - subs.emplace_back(subType, Inexact); - } + if (includeExact) { + subs.emplace_back(type, Exact); + } + for (auto subType : subTypes.getImmediateSubTypes(type)) { + subs.emplace_back(subType, Inexact); } for (auto sub : subs) { auto& subInfos = combinedInfos[sub]; From c567e374e82c616273126701ab55f5659260a78b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 8 Sep 2025 21:34:20 -0700 Subject: [PATCH 05/21] comment --- src/ir/struct-utils.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ir/struct-utils.h b/src/ir/struct-utils.h index 7f3e7f91a6f..77ed41fdbea 100644 --- a/src/ir/struct-utils.h +++ b/src/ir/struct-utils.h @@ -84,7 +84,9 @@ template struct StructValues : public std::vector { T desc; }; -// Maps heap types to a StructValues for that heap type. +// Maps heap types to a StructValues for that heap type. Includes exactness in +// the key to allow differentiating between values for exact and inexact +// references to each type. // // Also provides a combineInto() helper that combines one map into another. This // depends on the underlying T defining a combine() method. From fd3ec9caf7d5239d453db836a0634aece01ae7f0 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 8 Sep 2025 21:23:49 -0700 Subject: [PATCH 06/21] Improve CFP by removing rawNewInfos For the purposes of CFP, there's nothing fundamentally different between a set on an exact reference and a value set by allocation. CFP's use of the allocation values without considering all exact sets was therefore an unnecessary complication that restricted CFP's optimizing power. Expand optimizeUsingRefTest to optimize mutable fields, including those that have been set, by using the full available information instead of just the allocation values. Handle copies more judiciously by propagating once to find copied values and then propagate again while taking those copied values into account. This scheme can be extended in the future to precisely handle copies between different fields and types as well. Also optimize siblings better by propagating first down and then up rather than propagating in both directions at once. This avoid unnecessarily propagating set values to siblings. --- src/passes/ConstantFieldPropagation.cpp | 123 ++++++++--------- test/lit/passes/cfp-reftest.wast | 169 +++++++++++++++++++++++- test/lit/passes/cfp.wast | 142 +++++++++++++------- 3 files changed, 321 insertions(+), 113 deletions(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index e1c120685bf..9043a31475a 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -91,15 +91,15 @@ struct FunctionOptimizer : public WalkerPass> { // subtyping and new infos (information about struct.news). std::unique_ptr create() override { return std::make_unique( - propagatedInfos, subTypes, rawNewInfos, refTest); + propagatedInfos, refTestInfos, subTypes, refTest); } FunctionOptimizer(const PCVStructValuesMap& propagatedInfos, + const PCVStructValuesMap& refTestInfos, const SubTypes& subTypes, - const PCVStructValuesMap& rawNewInfos, bool refTest) - : propagatedInfos(propagatedInfos), subTypes(subTypes), - rawNewInfos(rawNewInfos), refTest(refTest) {} + : propagatedInfos(propagatedInfos), refTestInfos(refTestInfos), + subTypes(subTypes), refTest(refTest) {} template std::optional getRelevantHeapType(T* ref) { auto type = ref->type; @@ -210,7 +210,7 @@ struct FunctionOptimizer : public WalkerPass> { // on simply applying a constant. However, we can try to use a ref.test, if // that is allowed. if (!info.isConstant()) { - if (refTest) { + if (refTest && !ref->type.isExact()) { optimizeUsingRefTest(curr, ref, index); } return; @@ -233,22 +233,6 @@ struct FunctionOptimizer : public WalkerPass> { auto refType = ref->type; auto refHeapType = refType.getHeapType(); - // We only handle immutable fields in this function, as we will be looking - // at |rawNewInfos|. That is, we are trying to see when a type and its - // subtypes have different values (so that we can differentiate between them - // using a ref.test), and those differences are lost in |propagatedInfos|, - // which has propagated to relevant types so that we can do a single check - // to see what value could be there. So we need to use something more - // precise, |rawNewInfos|, which tracks the values written to struct.news, - // where we know the type exactly (unlike with a struct.set). But for that - // reason the field must be immutable, so that it is valid to only look at - // the struct.news. (A more complex flow analysis could do better here, but - // would be far beyond the scope of this pass.) - if (index != StructUtils::DescriptorIndex && - GCTypeUtils::getField(refType, index)->mutable_ == Mutable) { - return; - } - // We seek two possible constant values. For each we track the constant and // the types that have that constant. For example, if we have types A, B, C // and A and B have 42 in their field, and C has 1337, then we'd have this: @@ -283,13 +267,17 @@ struct FunctionOptimizer : public WalkerPass> { return; } - auto iter = rawNewInfos.find({type, Exact}); - if (iter == rawNewInfos.end()) { - // This type has no struct.news, so we can ignore it: it is abstract. + auto iter = refTestInfos.find({type, Exact}); + if (iter == refTestInfos.end()) { + // This type has no allocations, so we can ignore it: it is abstract. return; } auto value = iter->second[index]; + if (!value.hasNoted()) { + // Also abstract and ignorable. + return; + } if (!value.isConstant()) { // The value here is not constant, so give up entirely. fail = true; @@ -409,8 +397,8 @@ struct FunctionOptimizer : public WalkerPass> { private: const PCVStructValuesMap& propagatedInfos; + const PCVStructValuesMap& refTestInfos; const SubTypes& subTypes; - const PCVStructValuesMap& rawNewInfos; const bool refTest; bool changed = false; @@ -492,20 +480,13 @@ struct ConstantFieldPropagation : public Pass { scanner.runOnModuleCode(runner, module); // Combine the data from the functions. - PCVStructValuesMap combinedNewInfos, combinedSetInfos; - functionNewInfos.combineInto(combinedNewInfos); + PCVStructValuesMap combinedSetInfos; + functionNewInfos.combineInto(combinedSetInfos); functionSetInfos.combineInto(combinedSetInfos); BoolStructValuesMap combinedCopyInfos; functionCopyInfos.combineInto(combinedCopyInfos); - // Prepare data we will need later. - SubTypes subTypes(*module); - - // Copy the unpropagated data before we propagate. We use this in precise - // lookups. - auto rawNewInfos = combinedNewInfos; - - // Handle subtyping. |combinedInfo| so far contains data that represents + // Handle subtyping. |combinedSetInfos| so far contains data that represents // each struct.new and struct.set's operation on the struct type used in // that instruction. That is, if we do a struct.set to type T, the value was // noted for type T. But our actual goal is to answer questions about @@ -532,10 +513,11 @@ struct ConstantFieldPropagation : public Pass { // efficient, we therefore propagate information about the possible values // in each field to both subtypes and supertypes. // - // struct.new on the other hand knows exactly what type is being written to, - // and so given a get of $A and a new of $B, the new is relevant for the get - // iff $A is a subtype of $B, so we only need to propagate in one direction - // there, to supertypes. + // Values written in struct.news are equivalent to values written to exact + // references. In both cases, the propagation to subtypes will not do + // anything because an exact reference has no non-trivial subtypes. This + // works out because a set of a field of an exact reference (or an + // allocation) cannot ever affect the value read out of a subtype's field. // // An exception to the above are copies. If a field is copied then even // struct.new information cannot be assumed to be precise: @@ -549,36 +531,57 @@ struct ConstantFieldPropagation : public Pass { // foo(A->f0); // These can contain 20, // foo(C->f0); // if the copy read from B. // - // To handle that, copied fields are treated like struct.set ones (by - // copying the struct.new data to struct.set). Note that we must propagate - // copying to subtypes first, as in the example above the struct.new values - // of subtypes must be taken into account (that is, A or a subtype is being - // copied, so we want to do the same thing for B and C as well as A, since - // a copy of A means it could be a copy of B or C). - StructUtils::TypeHierarchyPropagator - boolPropagator(subTypes); - boolPropagator.propagateToSubTypesWithExact(combinedCopyInfos); + // The handling of copies is explained below. + SubTypes subTypes(*module); + StructUtils::TypeHierarchyPropagator propagator( + subTypes); + + // Compute the values without accounting for copies. + PCVStructValuesMap noCopySetInfos = combinedSetInfos; + propagator.propagateToSubTypesWithExact(noCopySetInfos); + propagator.propagateToSuperTypes(noCopySetInfos); + + // Now account for copies. A copy takes a value from any subtype + // of the copy source to any subtype of the copy destination. Since we last + // propagated to supertypes, we know the propagated values increase + // monotonically as you go up the type hierarchy. The propagated value in a + // field therefore overapproximates the values in the corresponding field in + // all the subtypes. So for each copy, we can use the propagated value as + // the copied value. Then we will propagate set values again, this time + // including the copied values. We only need to repeat the propagation once; + // if the second propagation discovers greater values in the copied fields, + // it can only be because those greater values were propagated from a + // supertype. In that case, the greater value has also been propagated to + // all subtypes, so repeating the process will not further change anything. + // + // TODO: Track separate sources and destinations of copies rather than + // special-casing copies to self. This would let propagation discover + // greater copied values from unrelated types or even different field + // indices, so we would have to repeatedly propagate taking into account the + // latest discovered copied values until reaching a fixed point. for (auto& [type, copied] : combinedCopyInfos) { - for (Index i = 0; i < copied.size(); i++) { + for (Index i = 0; i < copied.size(); ++i) { if (copied[i]) { - combinedSetInfos[type][i].combine(combinedNewInfos[type][i]); + combinedSetInfos[type][i].combine(noCopySetInfos[type][i]); } } } - StructUtils::TypeHierarchyPropagator propagator( - subTypes); - propagator.propagateToSuperTypes(combinedNewInfos); - propagator.propagateToSuperAndSubTypesWithExact(combinedSetInfos); - - // Combine both sources of information to the final information that gets - // care about. - PCVStructValuesMap combinedInfos = std::move(combinedNewInfos); - combinedSetInfos.combineInto(combinedInfos); + // Propagate the values again, now including values readable by copies. + // RefTest optimization manually checks the values in every subtype to + // make sure they match, so there's no need to propagate values up for that. + // Snapshot the info before propagating up for use in RefTest + // optimization. + PCVStructValuesMap refTestInfos; + propagator.propagateToSubTypesWithExact(combinedSetInfos); + if (refTest) { + refTestInfos = combinedSetInfos; + } + propagator.propagateToSuperTypes(combinedSetInfos); // Optimize. // TODO: Skip this if we cannot optimize anything - FunctionOptimizer(combinedInfos, subTypes, rawNewInfos, refTest) + FunctionOptimizer(combinedSetInfos, refTestInfos, subTypes, refTest) .run(runner, module); } }; diff --git a/test/lit/passes/cfp-reftest.wast b/test/lit/passes/cfp-reftest.wast index f46b211cd5e..4c618c5eeea 100644 --- a/test/lit/passes/cfp-reftest.wast +++ b/test/lit/passes/cfp-reftest.wast @@ -279,18 +279,18 @@ ) ) -;; Almost optimizable, but the field is mutable, so we can't. +;; The field is mutable, but we can still optimize. (module ;; CHECK: (type $struct (sub (struct (field (mut i32))))) (type $struct (sub (struct (mut i32)))) - ;; CHECK: (type $1 (func)) - ;; CHECK: (type $substruct (sub $struct (struct (field (mut i32)) (field f64)))) (type $substruct (sub $struct (struct (mut i32) f64))) + ;; CHECK: (type $2 (func)) + ;; CHECK: (type $3 (func (param (ref null $struct)) (result i32))) - ;; CHECK: (func $create (type $1) + ;; CHECK: (func $create (type $2) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 10) @@ -317,6 +317,167 @@ ) ) ;; CHECK: (func $get (type $3) (param $struct (ref null $struct)) (result i32) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (ref.test (ref $substruct) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $struct (ref null $struct)) (result i32) + ;; We cannot optimize here. + (struct.get $struct 0 + (local.get $struct) + ) + ) +) + +;; No-op sets do not inhibit optimization. +(module + ;; CHECK: (type $struct (sub (struct (field (mut i32))))) + (type $struct (sub (struct (mut i32)))) + ;; CHECK: (type $substruct (sub $struct (struct (field (mut i32)) (field f64)))) + (type $substruct (sub $struct (struct (mut i32) f64))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (func (param (ref null (exact $struct)) (ref null $substruct)))) + + ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32))) + + ;; CHECK: (func $create (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $substruct + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create + (drop + (struct.new $struct + (i32.const 10) + ) + ) + (drop + (struct.new $substruct + (i32.const 20) + (f64.const 3.14159) + ) + ) + ) + + ;; CHECK: (func $sets (type $3) (param $struct-exact (ref null (exact $struct))) (param $substruct (ref null $substruct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct-exact) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $substruct 0 + ;; CHECK-NEXT: (local.get $substruct) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $sets (param $struct-exact (ref null (exact $struct))) (param $substruct (ref null $substruct)) + (struct.set $struct 0 + (local.get $struct-exact) + (i32.const 10) + ) + (struct.set $substruct 0 + (local.get $substruct) + (i32.const 20) + ) + ) + + ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (ref.test (ref $substruct) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $struct (ref null $struct)) (result i32) + ;; We cannot optimize here. + (struct.get $struct 0 + (local.get $struct) + ) + ) +) + +;; Same as above, except now the set to $struct is inexact so we cannot +;; optimize. +(module + ;; CHECK: (type $struct (sub (struct (field (mut i32))))) + (type $struct (sub (struct (mut i32)))) + ;; CHECK: (type $substruct (sub $struct (struct (field (mut i32)) (field f64)))) + (type $substruct (sub $struct (struct (mut i32) f64))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (func (param (ref null $struct) (ref null $substruct)))) + + ;; CHECK: (type $4 (func (param (ref null $struct)) (result i32))) + + ;; CHECK: (func $create (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $substruct + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create + (drop + (struct.new $struct + (i32.const 10) + ) + ) + (drop + (struct.new $substruct + (i32.const 20) + (f64.const 3.14159) + ) + ) + ) + + ;; CHECK: (func $sets (type $3) (param $struct (ref null $struct)) (param $substruct (ref null $substruct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $substruct 0 + ;; CHECK-NEXT: (local.get $substruct) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $sets (param $struct (ref null $struct)) (param $substruct (ref null $substruct)) + (struct.set $struct 0 + (local.get $struct) + (i32.const 10) + ) + (struct.set $substruct 0 + (local.get $substruct) + (i32.const 20) + ) + ) + + ;; CHECK: (func $get (type $4) (param $struct (ref null $struct)) (result i32) ;; CHECK-NEXT: (struct.get $struct 0 ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 4ccdda92a2b..4478ced671f 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2331,19 +2331,25 @@ ;; CHECK-NEXT: (local $B (ref $B)) ;; CHECK-NEXT: (struct.set $A 0 ;; CHECK-NEXT: (select (result (ref null $A)) - ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (block (result (ref null $A)) - ;; CHECK-NEXT: (local.tee $B - ;; CHECK-NEXT: (struct.new $B - ;; CHECK-NEXT: (i32.const 20) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $B + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (select (result (ref null $A)) + ;; CHECK-NEXT: (block (result (ref null $A)) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2360,22 +2366,29 @@ ;; that we track the copied value even though the copy is on $A but it ;; affects $B. (struct.set $A 0 - ;; This select is used to keep the type that reaches the struct.set $A, - ;; and not $B, so it looks like a perfect copy of $A->$A. + ;; Use selects to make sure the types reaching the set and get are not more + ;; precise than (ref null $A). This will look like a perfect copy of + ;; $A->$A. (select (result (ref null $A)) - (ref.null none) (block (result (ref null $A)) - (local.tee $B - (struct.new $B - (i32.const 20) - ) + (ref.null none) + ) + (local.tee $B + (struct.new $B + (i32.const 20) ) ) (i32.const 0) ) (struct.get $A 0 - (struct.new $A - (i32.const 10) + (select (result (ref null $A)) + (block (result (ref null $A)) + (ref.null none) + ) + (struct.new $A + (i32.const 10) + ) + (i32.const 0) ) ) ) @@ -2655,18 +2668,33 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $B 0 - ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $B 0 - ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2698,7 +2726,7 @@ (local.get $B-exact) (i32.const 20) ) - ;; We should be able to optimize an inexact $B, but not $A TODO. + ;; We should be able to optimize an inexact $B, but not $A. (drop (struct.get $A 0 (local.get $A) @@ -2804,8 +2832,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $C 0 - ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 30) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2820,7 +2853,7 @@ (local.get $B) ) ) - ;; This should be optimizable TODO. + ;; This should be optimizable. (drop (struct.get $C 0 (local.get $C) @@ -2830,8 +2863,13 @@ ;; CHECK: (func $exact-gets (type $6) (param $A-exact (ref (exact $A))) (param $B-exact (ref (exact $B))) (param $C-exact (ref (exact $C))) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -2840,8 +2878,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $C 0 - ;; CHECK-NEXT: (local.get $C-exact) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 30) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2849,7 +2892,7 @@ (param $B-exact (ref (exact $B))) (param $C-exact (ref (exact $C))) (drop - ;; This should be optimizable TODO. + ;; This should be optimizable. (struct.get $A 0 (local.get $A-exact) ) @@ -2860,7 +2903,7 @@ (local.get $B-exact) ) ) - ;; This should be optimizable TODO. + ;; This should be optimizable. (drop (struct.get $C 0 (local.get $C-exact) @@ -3030,8 +3073,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $B2 0 - ;; CHECK-NEXT: (local.get $B2) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3073,8 +3121,7 @@ (local.get $B1) ) ) - ;; The copy can't refer to a $B2, so we can optimize here. TODO (but GUFA - ;; can do this) + ;; The copy can't refer to a $B2, so we can optimize here. (drop (struct.get $B2 0 (local.get $B2) @@ -3132,8 +3179,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $B1 0 - ;; CHECK-NEXT: (local.get $B1) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -3179,8 +3231,7 @@ (local.get $A) ) ) - ;; The copy can't refer to a $B1, so we can optimize here. TODO (but GUFA - ;; can do this) + ;; The copy can't refer to a $B1, so we can optimize here. (drop (struct.get $B1 0 (local.get $B1) @@ -3287,16 +3338,9 @@ ;; CHECK: (func $get-B (type $9) (param $B (ref null $B)) (result (ref null $Y)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $B) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (global.get $global) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $get-B (param $B (ref null $B)) (result (ref null $Y)) ;; This should not be optimized to a global.get: no $B is created, and we From 7d0445e3351d7e40dcec0584df2c51c8a087e5e3 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 9 Sep 2025 08:24:45 -0700 Subject: [PATCH 07/21] Comment about convenience subscripting. Co-authored-by: Alon Zakai --- src/ir/struct-utils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ir/struct-utils.h b/src/ir/struct-utils.h index 77ed41fdbea..b8e50fc1f0f 100644 --- a/src/ir/struct-utils.h +++ b/src/ir/struct-utils.h @@ -105,6 +105,7 @@ struct StructValuesMap return values; } + // Convenience operator for inexact queries. StructValues& operator[](HeapType type) { return (*this)[{type, Inexact}]; } From 89751b1ca691f0dd3ee35c1bfc9e056a2e426088 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 9 Sep 2025 09:34:28 -0700 Subject: [PATCH 08/21] lambda --- src/ir/struct-utils.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ir/struct-utils.h b/src/ir/struct-utils.h index b8e50fc1f0f..4b328f97ddb 100644 --- a/src/ir/struct-utils.h +++ b/src/ir/struct-utils.h @@ -456,14 +456,7 @@ template class TypeHierarchyPropagator { // Propagate shared fields to the subtypes, which may just be the exact // version of the same type. auto numFields = type.getStruct().fields.size(); - std::vector> subs; - if (includeExact) { - subs.emplace_back(type, Exact); - } - for (auto subType : subTypes.getImmediateSubTypes(type)) { - subs.emplace_back(subType, Inexact); - } - for (auto sub : subs) { + auto handleSubtype = [&](std::pair sub) { auto& subInfos = combinedInfos[sub]; for (Index i = 0; i < numFields; i++) { if (subInfos[i].combine(infos[i])) { @@ -474,6 +467,12 @@ template class TypeHierarchyPropagator { if (subInfos.desc.combine(infos.desc)) { work.push(sub); } + }; + if (includeExact) { + handleSubtype({type, Exact}); + } + for (auto subType : subTypes.getImmediateSubTypes(type)) { + handleSubtype({subType, Inexact}); } } } From 49dcfc7c244e4a415286e6879705f12a8cdd9de6 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 9 Sep 2025 09:37:25 -0700 Subject: [PATCH 09/21] comment on exact propagation --- src/ir/struct-utils.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ir/struct-utils.h b/src/ir/struct-utils.h index 4b328f97ddb..d89041fac24 100644 --- a/src/ir/struct-utils.h +++ b/src/ir/struct-utils.h @@ -415,6 +415,8 @@ template class TypeHierarchyPropagator { } private: + // `includeExact` is whether to propagate to exact subtypes only because there + // are no exact supertypes. void propagate(StructValuesMap& combinedInfos, bool toSubTypes, bool toSuperTypes, From 640307378d1fa7e8802033776472ebe60ffc8014 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 10 Sep 2025 15:06:53 -0700 Subject: [PATCH 10/21] update comment --- src/ir/struct-utils.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir/struct-utils.h b/src/ir/struct-utils.h index d89041fac24..347d9700a52 100644 --- a/src/ir/struct-utils.h +++ b/src/ir/struct-utils.h @@ -415,8 +415,8 @@ template class TypeHierarchyPropagator { } private: - // `includeExact` is whether to propagate to exact subtypes only because there - // are no exact supertypes. + // N.B. `includeExact` is only whether to propagate to exact subtypes because + // there are no exact supertypes. void propagate(StructValuesMap& combinedInfos, bool toSubTypes, bool toSuperTypes, From 1d61606f65990346011dc468b5fbf116caac99ae Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 12 Sep 2025 14:39:41 -0700 Subject: [PATCH 11/21] remove complexity of avoiding exactness --- src/ir/struct-utils.h | 26 +++++-------------------- src/passes/ConstantFieldPropagation.cpp | 4 ++-- src/passes/GlobalTypeOptimization.cpp | 6 ++++-- src/passes/TypeRefining.cpp | 21 ++++++++++++-------- 4 files changed, 24 insertions(+), 33 deletions(-) diff --git a/src/ir/struct-utils.h b/src/ir/struct-utils.h index 347d9700a52..414a8767d35 100644 --- a/src/ir/struct-utils.h +++ b/src/ir/struct-utils.h @@ -105,11 +105,6 @@ struct StructValuesMap return values; } - // Convenience operator for inexact queries. - StructValues& operator[](HeapType type) { - return (*this)[{type, Inexact}]; - } - void combineInto(StructValuesMap& combinedInfos) const { for (auto& [type, info] : *this) { for (Index i = 0; i < info.size(); i++) { @@ -385,19 +380,13 @@ template class TypeHierarchyPropagator { // Propagate given a StructValuesMap, which means we need to take into // account fields. void propagateToSuperTypes(StructValuesMap& infos) { - propagate(infos, false, true, true); + propagate(infos, false, true); } void propagateToSubTypes(StructValuesMap& infos) { - propagate(infos, true, false, false); - } - void propagateToSubTypesWithExact(StructValuesMap& infos) { - propagate(infos, true, false, true); + propagate(infos, true, false); } void propagateToSuperAndSubTypes(StructValuesMap& infos) { - propagate(infos, true, true, false); - } - void propagateToSuperAndSubTypesWithExact(StructValuesMap& infos) { - propagate(infos, true, true, true); + propagate(infos, true, true); } // Propagate on a simpler map of structs and infos (that is, not using @@ -415,12 +404,9 @@ template class TypeHierarchyPropagator { } private: - // N.B. `includeExact` is only whether to propagate to exact subtypes because - // there are no exact supertypes. void propagate(StructValuesMap& combinedInfos, bool toSubTypes, - bool toSuperTypes, - bool includeExact) { + bool toSuperTypes) { UniqueDeferredQueue> work; for (auto& [ht, _] : combinedInfos) { work.push(ht); @@ -470,9 +456,7 @@ template class TypeHierarchyPropagator { work.push(sub); } }; - if (includeExact) { - handleSubtype({type, Exact}); - } + handleSubtype({type, Exact}); for (auto subType : subTypes.getImmediateSubTypes(type)) { handleSubtype({subType, Inexact}); } diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index e1c120685bf..d0d7f2938bd 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -557,7 +557,7 @@ struct ConstantFieldPropagation : public Pass { // a copy of A means it could be a copy of B or C). StructUtils::TypeHierarchyPropagator boolPropagator(subTypes); - boolPropagator.propagateToSubTypesWithExact(combinedCopyInfos); + boolPropagator.propagateToSubTypes(combinedCopyInfos); for (auto& [type, copied] : combinedCopyInfos) { for (Index i = 0; i < copied.size(); i++) { if (copied[i]) { @@ -569,7 +569,7 @@ struct ConstantFieldPropagation : public Pass { StructUtils::TypeHierarchyPropagator propagator( subTypes); propagator.propagateToSuperTypes(combinedNewInfos); - propagator.propagateToSuperAndSubTypesWithExact(combinedSetInfos); + propagator.propagateToSuperAndSubTypes(combinedSetInfos); // Combine both sources of information to the final information that gets // care about. diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index bf0dc74fdfd..546c8c0a280 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -210,9 +210,9 @@ struct GlobalTypeOptimization : public Pass { SubTypes subTypes(*module); StructUtils::TypeHierarchyPropagator propagator(subTypes); auto dataFromSubsAndSupersMap = combinedSetGetInfos; - propagator.propagateToSuperAndSubTypesWithExact(dataFromSubsAndSupersMap); + propagator.propagateToSuperAndSubTypes(dataFromSubsAndSupersMap); auto dataFromSupersMap = std::move(combinedSetGetInfos); - propagator.propagateToSubTypesWithExact(dataFromSupersMap); + propagator.propagateToSubTypes(dataFromSupersMap); // Find the public types, which we must not modify. auto publicTypes = ModuleUtils::getPublicHeapTypes(*module); @@ -229,6 +229,8 @@ struct GlobalTypeOptimization : public Pass { continue; } auto& fields = type.getStruct().fields; + // Use the exact entry because information from the inexact entry will + // have been propagated down into it but not vice versa. auto ht = std::make_pair(type, Exact); auto& dataFromSubsAndSupers = dataFromSubsAndSupersMap[ht]; auto& dataFromSupers = dataFromSupersMap[ht]; diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index 0afeff28404..2db26b77a3d 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -193,7 +193,8 @@ struct TypeRefining : public Pass { for (auto type : allTypes) { if (type.isStruct()) { auto& fields = type.getStruct().fields; - auto& infos = finalInfos[type]; + // Update the inexact entry because that's what we will query later. + auto& infos = finalInfos[{type, Inexact}]; for (Index i = 0; i < fields.size(); i++) { auto gufaType = oracle.getContents(DataLocation{type, i}).getType(); // Do not introduce new exact fields that might requires invalid @@ -223,7 +224,7 @@ struct TypeRefining : public Pass { } auto type = structNew->type.getHeapType(); - auto& infos = finalInfos[type]; + auto& infos = finalInfos[{type, Inexact}]; auto& fields = type.getStruct().fields; for (Index i = 0; i < fields.size(); i++) { // We are in a situation like this: @@ -287,7 +288,9 @@ struct TypeRefining : public Pass { auto& fields = type.getStruct().fields; for (Index i = 0; i < fields.size(); i++) { auto oldType = fields[i].type; - auto& info = finalInfos[type][i]; + // Use inexact because exact info will have been propagated up to + // inexact entries but not necessarily vice versa. + auto& info = finalInfos[{type, Inexact}][i]; if (!info.noted()) { info = LUBFinder(oldType); } @@ -301,11 +304,11 @@ struct TypeRefining : public Pass { // public, unchanged since we cannot optimize it Type newSuperType; if (!publicTypesSet.count(*super)) { - newSuperType = finalInfos[*super][i].getLUB(); + newSuperType = finalInfos[{*super, Inexact}][i].getLUB(); } else { newSuperType = superFields[i].type; } - auto& info = finalInfos[type][i]; + auto& info = finalInfos[{type, Inexact}][i]; auto newType = info.getLUB(); if (!Type::isSubType(newType, newSuperType)) { // To ensure we are a subtype of the super's field, simply copy that @@ -340,7 +343,7 @@ struct TypeRefining : public Pass { // After all those decisions, see if we found anything to optimize. for (Index i = 0; i < fields.size(); i++) { auto oldType = fields[i].type; - auto& lub = finalInfos[type][i]; + auto& lub = finalInfos[{type, Inexact}][i]; auto newType = lub.getLUB(); if (newType != oldType) { canOptimize = true; @@ -384,7 +387,8 @@ struct TypeRefining : public Pass { Type newFieldType; if (!curr->ref->type.isNull()) { auto oldType = curr->ref->type.getHeapType(); - newFieldType = parent.finalInfos[oldType][curr->index].getLUB(); + newFieldType = + parent.finalInfos[{oldType, Inexact}][curr->index].getLUB(); } if (curr->ref->type.isNull() || newFieldType == Type::unreachable || @@ -449,7 +453,8 @@ struct TypeRefining : public Pass { if (!oldType.isRef()) { continue; } - auto newType = parent.finalInfos[oldStructType][i].getLUB(); + auto newType = + parent.finalInfos[{oldStructType, Inexact}][i].getLUB(); newFields[i].type = getTempType(newType); } } From 7974701edfabd7fb2ceef550f47681137e7e0c3d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 9 Sep 2025 13:56:24 -0700 Subject: [PATCH 12/21] [NFC] Note copies between any fields in struct-utils.h In preparation for CFP updates that will handle the more general copies. --- src/ir/struct-utils.h | 45 +++++++++++-------------- src/passes/ConstantFieldPropagation.cpp | 20 ++++++++--- src/passes/GlobalTypeOptimization.cpp | 2 +- src/passes/TypeRefining.cpp | 13 +++++-- 4 files changed, 45 insertions(+), 35 deletions(-) diff --git a/src/ir/struct-utils.h b/src/ir/struct-utils.h index 414a8767d35..925f81503d9 100644 --- a/src/ir/struct-utils.h +++ b/src/ir/struct-utils.h @@ -167,12 +167,10 @@ struct FunctionStructValuesMap // // void noteRMW(Expression* expr, HeapType type, Index index, T& info); // -// * Note a copied value (read from this field and written to the same, possibly -// in another object). Note that we require that the two types (the one read -// from, and written to) are identical; allowing subtyping is possible, but -// would add complexity amid diminishing returns. +// * Note a copied value (read from a struct field and written to another struct +// field). // -// void noteCopy(HeapType type, Index index, T& info); +// void noteCopy(StructGet* src, Type dstType, Index index, T& info); // // * Note a read. // @@ -214,7 +212,7 @@ struct StructScanner if (curr->isWithDefault()) { self().noteDefault(fields[i].type, heapType, i, infos[i]); } else { - noteExpressionOrCopy(curr->operands[i], heapType, i, infos[i]); + noteExpressionOrCopy(curr->operands[i], type, i, infos[i]); } } @@ -233,7 +231,7 @@ struct StructScanner auto ht = std::make_pair(type.getHeapType(), type.getExactness()); noteExpressionOrCopy( curr->value, - type.getHeapType(), + type, curr->index, functionSetGetInfos[this->getFunction()][ht][curr->index]); } @@ -265,7 +263,7 @@ struct StructScanner if (curr->op == RMWXchg) { // An xchg is really like a read and write combined. self().noteRead(heapType, index, info); - noteExpressionOrCopy(curr->value, heapType, index, info); + noteExpressionOrCopy(curr->value, type, index, info); return; } @@ -287,7 +285,7 @@ struct StructScanner // A cmpxchg is like a read and conditional write. self().noteRead(heapType, index, info); - noteExpressionOrCopy(curr->replacement, heapType, index, info); + noteExpressionOrCopy(curr->replacement, type, index, info); } void visitRefCast(RefCast* curr) { @@ -327,27 +325,22 @@ struct StructScanner } } - void - noteExpressionOrCopy(Expression* expr, HeapType type, Index index, T& info) { - // Look at the value falling through, if it has the exact same type - // (otherwise, we'd need to consider both the type actually written and the - // type of the fallthrough, somehow). - auto* fallthrough = Properties::getFallthrough( - expr, - this->getPassOptions(), - *this->getModule(), - static_cast(this)->getFallthroughBehavior()); + void noteExpressionOrCopy(Expression* expr, Type type, Index index, T& info) { + auto* fallthrough = + Properties::getFallthrough(expr, + this->getPassOptions(), + *this->getModule(), + self().getFallthroughBehavior()); + // TODO: Consider lifting this restriction on the use of fallthrough values. if (fallthrough->type == expr->type) { expr = fallthrough; } - if (auto* get = expr->dynCast()) { - if (get->index == index && get->ref->type != Type::unreachable && - get->ref->type.getHeapType() == type) { - static_cast(this)->noteCopy(type, index, info); - return; - } + if (auto* get = expr->dynCast(); + get && get->ref->type.isStruct()) { + self().noteCopy(get, type, index, info); + return; } - static_cast(this)->noteExpression(expr, type, index, info); + self().noteExpression(expr, type.getHeapType(), index, info); } Properties::FallthroughBehavior getFallthroughBehavior() { diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 8a145745e95..9ef383f02fe 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -432,11 +432,21 @@ struct PCVScanner info.note(Literal::makeZero(fieldType)); } - void noteCopy(HeapType type, Index index, PossibleConstantValues& info) { - // Note copies, as they must be considered later. See the comment on the - // propagation of values below. - // TODO: Take into account exactness here. - functionCopyInfos[getFunction()][{type, Inexact}][index] = true; + void noteCopy(StructGet* get, + Type type, + Index index, + PossibleConstantValues& info) { + // We currently only treat copies from a field to itself specially. See the + // comments on value propagation below. + // TODO: generalize this. + if (get->ref->type.getHeapType() == type.getHeapType() && + get->index == index) { + // TODO: Use exactness from `type`. + auto ht = std::make_pair(type.getHeapType(), Inexact); + functionCopyInfos[getFunction()][ht][index] = true; + } else { + info.noteUnknown(); + } } void noteRead(HeapType type, Index index, PossibleConstantValues& info) { diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index 546c8c0a280..114b5baaec5 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -101,7 +101,7 @@ struct FieldInfoScanner info.noteWrite(); } - void noteCopy(HeapType type, Index index, FieldInfo& info) { + void noteCopy(StructGet* get, Type type, Index index, FieldInfo& info) { info.noteWrite(); } diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index 2db26b77a3d..fd1d3bfd97f 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -86,9 +86,16 @@ struct FieldInfoScanner info.note(fieldType); } - void noteCopy(HeapType type, Index index, FieldInfo& info) { - // Copies do not add any type requirements at all: the type will always be - // read and written to a place with the same type. + void noteCopy(StructGet* get, Type type, Index index, FieldInfo& info) { + // Copies with identical sources and destinations do not add any type + // requirements. + auto srcType = get->ref->type.getHeapType(); + auto dstType = type.getHeapType(); + if (srcType == dstType && get->index == index) { + return; + } + // Otherwise we must note the written type. + noteExpression(get, dstType, index, info); } void noteRead(HeapType type, Index index, FieldInfo& info) { From 76aec7da7a61dda364e0aebb4ce06d234a9bc55e Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 9 Sep 2025 15:42:41 -0700 Subject: [PATCH 13/21] Handle arbitrary copies in CFP We previously special-cased copies from a field to itself in CFP. Generalize the analysis to handle copies from any field in any type to any field in any type. Along with this change, make the entire analysis more precise by explicit analyzing the written values, analyzing the readable values that result from the written values, then propagating readable values back to the writable values via copies and iterating until a fixed point. Use custom propagation logic after applying the copies the first time to minimize the amount of work done when propagating. --- src/ir/possible-constant.h | 59 +- src/passes/ConstantFieldPropagation.cpp | 277 +- test/lit/passes/cfp-copies.wast | 8064 +++++++++++++++++++++++ test/lit/passes/cfp-reftest-copies.wast | 744 +++ test/lit/passes/cfp.wast | 149 - 5 files changed, 9028 insertions(+), 265 deletions(-) create mode 100644 test/lit/passes/cfp-copies.wast create mode 100644 test/lit/passes/cfp-reftest-copies.wast diff --git a/src/ir/possible-constant.h b/src/ir/possible-constant.h index 79a9973b530..2c2214e5a55 100644 --- a/src/ir/possible-constant.h +++ b/src/ir/possible-constant.h @@ -20,6 +20,7 @@ #include #include "ir/properties.h" +#include "support/utilities.h" #include "wasm-builder.h" #include "wasm.h" @@ -85,6 +86,42 @@ struct PossibleConstantValues { // identify a constant value here. void noteUnknown() { value = Many(); } + void packForField(const Field& field, bool isSigned = false) { + if (field.type != Type::i32 || !field.isPacked()) { + return; + } + if (!isConstant()) { + // Nothing to pack. + return; + } + if (isConstantGlobal()) { + // Cannot track global and bit masking simultaneously, so give up. + noteUnknown(); + return; + } + assert(isConstantLiteral()); + auto val = getConstantLiteral(); + assert(val.type == Type::i32); + switch (field.packedType) { + case Field::i8: + if (isSigned) { + value = val.extendS8(); + } else { + value = val.and_(Literal(uint32_t(0xff))); + } + break; + case Field::i16: + if (isSigned) { + value = val.extendS16(); + } else { + value = val.and_(Literal(uint32_t(0xffff))); + } + break; + case Field::not_packed: + WASM_UNREACHABLE("unexpected packed type"); + } + } + // Combine the information in a given PossibleConstantValues to this one. This // is the same as if we have called note*() on us with all the history of // calls to that other object. @@ -109,28 +146,6 @@ struct PossibleConstantValues { return true; } - // Nulls compare equal, and we could consider any of the input nulls as the - // combination of the two (as any of them would be valid to place in the - // location we are working to optimize). In order to have simple symmetric - // behavior here, which does not depend on the order of the inputs, use the - // LUB. - if (isNull() && other.isNull()) { - auto type = getConstantLiteral().type.getHeapType(); - auto otherType = other.getConstantLiteral().type.getHeapType(); - auto lub = HeapType::getLeastUpperBound(type, otherType); - if (!lub) { - // TODO: Remove this workaround once we have bottom types to assign to - // null literals. - value = Many(); - return true; - } - if (*lub != type) { - value = Literal::makeNull(*lub); - return true; - } - return false; - } - return false; } diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 9ef383f02fe..b9deb063a4e 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -51,13 +51,17 @@ // wasm GC programs we need to check for type escaping. // +#include + #include "ir/bits.h" #include "ir/gc-type-utils.h" #include "ir/possible-constant.h" #include "ir/struct-utils.h" #include "ir/utils.h" #include "pass.h" +#include "support/hash.h" #include "support/small_vector.h" +#include "support/unique_deferring_queue.h" #include "wasm-builder.h" #include "wasm-traversal.h" #include "wasm.h" @@ -66,6 +70,40 @@ namespace wasm { namespace { +struct CopyInfo { + HeapType type; + Exactness exact; + Index index; + bool isSigned; + + bool operator==(const CopyInfo& other) const { + return type == other.type && exact == other.exact && index == other.index && + isSigned == other.isSigned; + } +}; + +} // anonymous namespace + +} // namespace wasm + +namespace std { + +template<> struct hash { + size_t operator()(const wasm::CopyInfo& copy) const { + auto digest = wasm::hash(copy.type); + wasm::rehash(digest, copy.exact); + wasm::rehash(digest, copy.index); + wasm::rehash(digest, copy.isSigned); + return digest; + } +}; + +} // namespace std + +namespace wasm { + +namespace { + using PCVStructValuesMap = StructUtils::StructValuesMap; using PCVFunctionStructValuesMap = StructUtils::FunctionStructValuesMap; @@ -75,6 +113,25 @@ using BoolStructValuesMap = using BoolFunctionStructValuesMap = StructUtils::FunctionStructValuesMap; +using StructFieldPairs = + std::unordered_set>; + +// TODO: Deduplicate with Lattice infrastructure. +template struct CombinableSet : std::unordered_set { + bool combine(const CombinableSet& other) { + auto originalSize = this->size(); + this->insert(other.begin(), other.end()); + return this->size() != originalSize; + } +}; + +// For each field, the set of fields it is copied to. +using CopiesStructValuesMap = + StructUtils::StructValuesMap>; + +using CopiesFunctionStructValuesMap = + StructUtils::FunctionStructValuesMap>; + // Optimize struct gets based on what we've learned about writes. // // TODO Aside from writes, we could use information like whether any struct of @@ -413,7 +470,7 @@ struct PCVScanner PCVScanner(PCVFunctionStructValuesMap& functionNewInfos, PCVFunctionStructValuesMap& functionSetInfos, - BoolFunctionStructValuesMap& functionCopyInfos) + CopiesFunctionStructValuesMap& functionCopyInfos) : StructUtils::StructScanner( functionNewInfos, functionSetInfos), functionCopyInfos(functionCopyInfos) {} @@ -436,17 +493,11 @@ struct PCVScanner Type type, Index index, PossibleConstantValues& info) { - // We currently only treat copies from a field to itself specially. See the - // comments on value propagation below. - // TODO: generalize this. - if (get->ref->type.getHeapType() == type.getHeapType() && - get->index == index) { - // TODO: Use exactness from `type`. - auto ht = std::make_pair(type.getHeapType(), Inexact); - functionCopyInfos[getFunction()][ht][index] = true; - } else { - info.noteUnknown(); - } + auto srcType = get->ref->type.getHeapType(); + auto srcExact = get->ref->type.getExactness(); + auto srcIndex = get->index; + functionCopyInfos[getFunction()][{srcType, srcExact}][srcIndex].insert( + {type.getHeapType(), type.getExactness(), index, get->signed_}); } void noteRead(HeapType type, Index index, PossibleConstantValues& info) { @@ -463,7 +514,7 @@ struct PCVScanner info.noteUnknown(); } - BoolFunctionStructValuesMap& functionCopyInfos; + CopiesFunctionStructValuesMap& functionCopyInfos; }; struct ConstantFieldPropagation : public Pass { @@ -483,7 +534,10 @@ struct ConstantFieldPropagation : public Pass { // Find and analyze all writes inside each function. PCVFunctionStructValuesMap functionNewInfos(*module), functionSetInfos(*module); - BoolFunctionStructValuesMap functionCopyInfos(*module); + CopiesFunctionStructValuesMap functionCopyInfos(*module); + for (auto& func : module->functions) { + functionCopyInfos[func.get()]; + } PCVScanner scanner(functionNewInfos, functionSetInfos, functionCopyInfos); auto* runner = getPassRunner(); scanner.run(runner, module); @@ -493,106 +547,141 @@ struct ConstantFieldPropagation : public Pass { PCVStructValuesMap combinedSetInfos; functionNewInfos.combineInto(combinedSetInfos); functionSetInfos.combineInto(combinedSetInfos); - BoolStructValuesMap combinedCopyInfos; + CopiesStructValuesMap combinedCopyInfos; functionCopyInfos.combineInto(combinedCopyInfos); - // Handle subtyping. |combinedSetInfos| so far contains data that represents - // each struct.new and struct.set's operation on the struct type used in - // that instruction. That is, if we do a struct.set to type T, the value was - // noted for type T. But our actual goal is to answer questions about - // struct.gets. Specifically, when later we see: - // - // (struct.get $A x (REF-1)) - // - // Then we want to be aware of all the relevant struct.sets, that is, the - // sets that can write data that this get reads. Given a set + // Perform an analysis to compute the readable values for each triple of + // heap type, exactness, and field index. The readable values are + // determined by the written values and copies. // - // (struct.set $B x (REF-2) (..value..)) + // Whenever we have a write like this: // - // then + // (struct.set $super x (... ref ...) (... value ...)) // - // 1. If $B is a subtype of $A, it is relevant: the get might read from a - // struct of type $B (i.e., REF-1 and REF-2 might be identical, and both - // be a struct of type $B). - // 2. If $B is a supertype of $A that still has the field x then it may - // also be relevant: since $A is a subtype of $B, the set may write to a - // struct of type $A (and again, REF-1 and REF-2 may be identical). + // The dynamic type of the struct we are writing to may be any subtype of + // the type of the ref. For example, if the ref has type (ref $super), + // then the write may go to an object of type (ref $super) or (ref $sub). + // In contrast, if the ref has an exact type, then we know the write + // cannot go to an object of type (ref $sub), which is not a subtype of + // (ref (exact $super)). The set of values that may have been written to a + // field is therefore the join of all the values we observe being written + // to that field in all supertypes of the written reference. The written + // values are propagated down to subtypes. // - // Thus, if either $A <: $B or $B <: $A then we must consider the get and - // set to be relevant to each other. To make our later lookups for gets - // efficient, we therefore propagate information about the possible values - // in each field to both subtypes and supertypes. + // Similarly, whenever we have a read like this: // - // Values written in struct.news are equivalent to values written to exact - // references. In both cases, the propagation to subtypes will not do - // anything because an exact reference has no non-trivial subtypes. This - // works out because a set of a field of an exact reference (or an - // allocation) cannot ever affect the value read out of a subtype's field. + // (struct.get $super x (... ref ...)) // - // An exception to the above are copies. If a field is copied then even - // struct.new information cannot be assumed to be precise: + // The dynamic type of the struct we are reading from may be any subtype + // of the type of the ref. The set of values that we might read from a + // field is therefore the join of all the values that may have been + // written to that field in all subtypes of the read reference. The read + // values are propagated up to supertypes. // - // // A :> B :> C - // .. - // new B(20); - // .. - // A1->f0 = A2->f0; // Either of these might refer to an A, B, or C. - // .. - // foo(A->f0); // These can contain 20, - // foo(C->f0); // if the copy read from B. - // - // The handling of copies is explained below. + // Copies are interesting because they invert the normal dependence of + // readable values on written values. A copy is a write of a read, so the + // writable values depends on the readable values. Because of this cyclic + // dependency, we must iteratively update our knowledge of the written and + // readable values until we reach a fixed point. SubTypes subTypes(*module); StructUtils::TypeHierarchyPropagator propagator( subTypes); - // Compute the values without accounting for copies. - PCVStructValuesMap noCopySetInfos = combinedSetInfos; - propagator.propagateToSubTypes(noCopySetInfos); - propagator.propagateToSuperTypes(noCopySetInfos); - - // Now account for copies. A copy takes a value from any subtype - // of the copy source to any subtype of the copy destination. Since we last - // propagated to supertypes, we know the propagated values increase - // monotonically as you go up the type hierarchy. The propagated value in a - // field therefore overapproximates the values in the corresponding field in - // all the subtypes. So for each copy, we can use the propagated value as - // the copied value. Then we will propagate set values again, this time - // including the copied values. We only need to repeat the propagation once; - // if the second propagation discovers greater values in the copied fields, - // it can only be because those greater values were propagated from a - // supertype. In that case, the greater value has also been propagated to - // all subtypes, so repeating the process will not further change anything. + PCVStructValuesMap written = std::move(combinedSetInfos); + propagator.propagateToSubTypes(written); + PCVStructValuesMap readable = written; + propagator.propagateToSuperTypes(readable); + + // Now apply copies and propagate the new information until we have a + // fixed point. We could just join the copied values into `written`, + // propagate all of `written` down again, and recompute `readable`, + // but that would do more work than necessary since most fields are not + // going to be involved in copies. We will handle the propagation manually + // instead. // - // TODO: Track separate sources and destinations of copies rather than - // special-casing copies to self. This would let propagation discover - // greater copied values from unrelated types or even different field - // indices, so we would have to repeatedly propagate taking into account the - // latest discovered copied values until reaching a fixed point. - for (auto& [type, copied] : combinedCopyInfos) { - for (Index i = 0; i < copied.size(); ++i) { - if (copied[i]) { - combinedSetInfos[type][i].combine(noCopySetInfos[type][i]); + // Since the analysis records untruncated values for packed fields, we must + // be careful to truncate and sign extend copy source values as necessary. + // We generally don't truncate values based on their destination because + // that would regress propagation of globals when they are not copied. + // TODO: Track truncations in the analysis itself to propagate them through + // copies, even of globals. + UniqueDeferredQueue work; + auto applyCopiesTo = [&](auto& dsts, const Field& src, const auto& val) { + for (auto& dst : dsts) { + auto packed = val; + packed.packForField(src, dst.isSigned); + if (written[{dst.type, dst.exact}][dst.index].combine(packed)) { + work.push(dst); + } + } + }; + auto applyCopiesFrom = + [&](HeapType src, Exactness exact, Index index, const auto& val) { + if (auto it = combinedCopyInfos.find({src, exact}); + it != combinedCopyInfos.end()) { + const auto& srcField = src.getStruct().fields[index]; + applyCopiesTo(it->second[index], srcField, val); } + }; + // For each copy, take the readable values at its source and join them to + // the written values at its destination. Record the written values that + // change so we can propagate the new information afterward. + for (auto& [srcType, fields] : combinedCopyInfos) { + for (Index srcField = 0; srcField < fields.size(); ++srcField) { + const auto& field = srcType.first.getStruct().fields[srcType.second]; + applyCopiesTo(fields[srcField], field, readable[srcType][srcField]); } } - - // Propagate the values again, now including values readable by copies. - // RefTest optimization manually checks the values in every subtype to - // make sure they match, so there's no need to propagate values up for that. - // Snapshot the info before propagating up for use in RefTest - // optimization. - PCVStructValuesMap refTestInfos; - propagator.propagateToSubTypes(combinedSetInfos); - if (refTest) { - refTestInfos = combinedSetInfos; + while (work.size()) { + // Propagate down from dst in both written and readable, then + // propagate up from dst in readable only. Whenever we make a change in + // readable, see if there are copies to apply. If there are copies and + // they make changes, then we have more propagation work to do later. + auto dst = work.pop(); + assert(dst.index != StructUtils::DescriptorIndex); + auto val = written[{dst.type, dst.exact}][dst.index]; + val.packForField(dst.type.getStruct().fields[dst.index]); + // Make the copied value readable. + if (readable[{dst.type, dst.exact}][dst.index].combine(val)) { + applyCopiesFrom(dst.type, dst.exact, dst.index, val); + } + if (dst.exact == Inexact) { + // Propagate down to subtypes. + written[{dst.type, Exact}][dst.index].combine(val); + subTypes.iterSubTypes(dst.type, [&](HeapType sub, Index depth) { + written[{sub, Inexact}][dst.index].combine(val); + written[{sub, Exact}][dst.index].combine(val); + if (readable[{sub, Inexact}][dst.index].combine(val)) { + applyCopiesFrom(sub, Inexact, dst.index, val); + } + if (readable[{sub, Exact}][dst.index].combine(val)) { + applyCopiesFrom(sub, Exact, dst.index, val); + } + }); + } else { + // The copy destination is exact, so there are no subtypes to + // propagate to, but we do need to propagate up to the inexact type. + if (readable[{dst.type, Inexact}][dst.index].combine(val)) { + applyCopiesFrom(dst.type, Inexact, dst.index, val); + } + } + // Propagate up to the supertypes. + for (auto super = dst.type.getDeclaredSuperType(); super; + super = super->getDeclaredSuperType()) { + auto& readableSuperFields = readable[{*super, Inexact}]; + if (dst.index >= readableSuperFields.size()) { + break; + } + if (readableSuperFields[dst.index].combine(val)) { + applyCopiesFrom(*super, Inexact, dst.index, val); + } + } } - propagator.propagateToSuperTypes(combinedSetInfos); // Optimize. - // TODO: Skip this if we cannot optimize anything - FunctionOptimizer(combinedSetInfos, refTestInfos, subTypes, refTest) - .run(runner, module); + // TODO: Skip this if we cannot optimize anything. + FunctionOptimizer(readable, written, subTypes, refTest).run(runner, module); + return; } }; diff --git a/test/lit/passes/cfp-copies.wast b/test/lit/passes/cfp-copies.wast new file mode 100644 index 00000000000..c2281adb139 --- /dev/null +++ b/test/lit/passes/cfp-copies.wast @@ -0,0 +1,8064 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --closed-world --cfp -all -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (local $A (ref $A)) + ;; CHECK-NEXT: (local.set $A + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $B (ref $B)) (result i32) + ;; Copy the default value from $A to $B. Note that we do not detect that $B + ;; is never allocated in this pass. + (local $A (ref $A)) + (local.set $A + (struct.new_default $A) + ) + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; This should be optimized. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (local $A (ref $A)) + ;; CHECK-NEXT: (local.set $A + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $B (ref $B)) (result i32) + ;; Same, but copy a non-default value. + (local $A (ref $A)) + (local.set $A + (struct.new $A + (i32.const 10) + ) + ) + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; This should be optimized. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (local $A (ref $A)) + ;; CHECK-NEXT: (local.set $A + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $B (ref $B)) (result i32) + ;; Now the copy has to look through fallthroughs on both the source and + ;; destination. + (local $A (ref $A)) + (local.set $A + (struct.new $A + (i32.const 10) + ) + ) + (struct.set $B 0 + (block (result (ref null $B)) + (local.get $B) + ) + (block (result i32) + (struct.get $A 0 + (local.get $A) + ) + ) + ) + ;; This should be optimized. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (local $A (ref $A)) + ;; CHECK-NEXT: (local.set $A + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $B + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $B (ref $B)) (result i32) + ;; Now copy from $A to $B, but also allocate a $B with a matching value. We + ;; should still optimize. + (local $A (ref $A)) + (local.set $A + (struct.new $A + (i32.const 10) + ) + ) + ;; This is new. + (local.set $B + (struct.new $B + (i32.const 10) + ) + ) + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; This should be optimized. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (local $A (ref $A)) + ;; CHECK-NEXT: (local.set $A + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $B (ref $B)) (result i32) + ;; Now instead of allocating $B with the same value, we set $B with the same + ;; value. We can still optimize. + (local $A (ref $A)) + (local.set $A + (struct.new $A + (i32.const 10) + ) + ) + ;; This is a set now. + (struct.set $B 0 + (local.get $B) + (i32.const 10) + ) + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; This should be optimized. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (local $A (ref $A)) + ;; CHECK-NEXT: (local.set $A + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $B + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $B (ref $B)) (result i32) + ;; Same, but now allocate $B with a conflicting value. We cannot optimize. + (local $A (ref $A)) + (local.set $A + (struct.new $A + (i32.const 10) + ) + ) + (local.set $B + (struct.new $B + ;; Does not match. + (i32.const 20) + ) + ) + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; This cannot be optimized. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (local $A (ref $A)) + ;; CHECK-NEXT: (local.set $A + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $B (ref $B)) (result i32) + ;; Same, but now the conflicting value comes from a set rather than an + ;; allocation. + (local $A (ref $A)) + (local.set $A + (struct.new $A + (i32.const 10) + ) + ) + ;; This is a set now. + (struct.set $B 0 + (local.get $B) + (i32.const 20) + ) + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; This cannot be optimized. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; Now the copied value comes from a set. + (struct.set $A 0 + (local.get $A) + (i32.const 10) + ) + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; This should be optimized. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (local.set $A + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; The copied value comes from both a set and an allocation, but they match. + (local.set $A + (struct.new $A + (i32.const 10) + ) + ) + (struct.set $A 0 + (local.get $A) + (i32.const 10) + ) + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; This should be optimized. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (local.set $A + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; Now the source values don't match, so we cannot optimize. + (local.set $A + (struct.new $A + (i32.const 10) + ) + ) + (struct.set $A 0 + (local.get $A) + (i32.const 20) + ) + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; This should be optimized. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $struct (struct (field (mut i32))))) + (type $sub (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $other (struct (field (mut i32)))) + (type $other (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $other) (ref $struct)))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $other + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $other (ref $other)) (param $struct (ref $struct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $struct (ref $struct)) + ;; Copy from $other to $struct. + (struct.set $struct 0 + (local.get $struct) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub (ref null $sub)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub (ref null (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub (ref null (exact $sub))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $struct (struct (field (mut i32))))) + (type $sub (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $other (struct (field (mut i32)))) + (type $other (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $other) (ref (exact $struct))))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $other + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $other (ref $other)) (param $exact-struct (ref (exact $struct))) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $exact-struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $exact-struct (ref (exact $struct))) + ;; Same as above, but now we copy to exact $struct. + (struct.set $struct 0 + (local.get $exact-struct) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub (ref null $sub)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub (ref null (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub (ref null (exact $sub))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub1 (sub $struct (struct (field (mut i32))))) + (type $sub1 (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $sub2 (sub $struct (struct (field (mut i32))))) + (type $sub2 (sub $struct (struct (field (mut i32))))) + + ;; CHECK: (type $other (struct (field (mut i32)))) + (type $other (struct (field (mut i32)))) + ) + + ;; CHECK: (type $5 (func)) + + ;; CHECK: (type $6 (func (param (ref $other) (ref $struct)))) + + ;; CHECK: (func $init (type $5) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $sub1 + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + ;; Same as above, but now with a different value in $sub1. + (drop + (struct.new $other + (i32.const 10) + ) + ) + (drop + (struct.new $sub1 + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $copy (type $6) (param $other (ref $other)) (param $struct (ref $struct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $struct (ref $struct)) + ;; Copy from $other to $struct. + (struct.set $struct 0 + (local.get $struct) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $gets (type $5) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub1 0 + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $5) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub1 (ref null (exact $sub1))) + ;; CHECK-NEXT: (local $sub2 (ref null (exact $sub2))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub1 0 + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub1 (ref null (exact $sub1))) + (local $sub2 (ref null (exact $sub2))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub1 (sub $struct (struct (field (mut i32))))) + (type $sub1 (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $sub2 (sub $struct (struct (field (mut i32))))) + (type $sub2 (sub $struct (struct (field (mut i32))))) + + ;; CHECK: (type $other (struct (field (mut i32)))) + (type $other (struct (field (mut i32)))) + ) + + ;; CHECK: (type $5 (func)) + + ;; CHECK: (type $6 (func (param (ref $other) (ref (exact $struct))))) + + ;; CHECK: (func $init (type $5) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $sub1 + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + ;; Same as above. + (drop + (struct.new $other + (i32.const 10) + ) + ) + (drop + (struct.new $sub1 + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $copy (type $6) (param $other (ref $other)) (param $exact-struct (ref (exact $struct))) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $exact-struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $exact-struct (ref (exact $struct))) + ;; Now the copy is to exact $struct. + (struct.set $struct 0 + (local.get $exact-struct) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $gets (type $5) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $5) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub1 (ref null (exact $sub1))) + ;; CHECK-NEXT: (local $sub2 (ref null (exact $sub2))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub1 (ref null (exact $sub1))) + (local $sub2 (ref null (exact $sub2))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub1 (sub $struct (struct (field (mut i32))))) + (type $sub1 (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $sub2 (sub $struct (struct (field (mut i32))))) + (type $sub2 (sub $struct (struct (field (mut i32))))) + + ;; CHECK: (type $other (struct (field (mut i32)))) + (type $other (struct (field (mut i32)))) + ) + + ;; CHECK: (type $5 (func)) + + ;; CHECK: (type $6 (func (param (ref $sub1)))) + + ;; CHECK: (type $7 (func (param (ref $other) (ref $struct)))) + + ;; CHECK: (func $init (type $6) (param $sub1 (ref $sub1)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $sub1 0 + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $sub1 (ref $sub1)) + ;; Now the value is set to an inexact reference to $sub1. This won't make a + ;; difference. + (drop + (struct.new $other + (i32.const 10) + ) + ) + (struct.set $sub1 0 + (local.get $sub1) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $7) (param $other (ref $other)) (param $struct (ref $struct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $struct (ref $struct)) + ;; Copy from $other to $struct. + (struct.set $struct 0 + (local.get $struct) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $gets (type $5) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub1 0 + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $5) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub1 (ref null (exact $sub1))) + ;; CHECK-NEXT: (local $sub2 (ref null (exact $sub2))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub1 0 + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub1 (ref null (exact $sub1))) + (local $sub2 (ref null (exact $sub2))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub1 (sub $struct (struct (field (mut i32))))) + (type $sub1 (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $sub2 (sub $struct (struct (field (mut i32))))) + (type $sub2 (sub $struct (struct (field (mut i32))))) + + ;; CHECK: (type $other (struct (field (mut i32)))) + (type $other (struct (field (mut i32)))) + ) + + ;; CHECK: (type $5 (func)) + + ;; CHECK: (type $6 (func (param (ref $sub1)))) + + ;; CHECK: (type $7 (func (param (ref $other) (ref (exact $struct))))) + + ;; CHECK: (func $init (type $6) (param $sub1 (ref $sub1)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $sub1 0 + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $sub1 (ref $sub1)) + ;; Same as above. + (drop + (struct.new $other + (i32.const 10) + ) + ) + (struct.set $sub1 0 + (local.get $sub1) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $7) (param $other (ref $other)) (param $exact-struct (ref (exact $struct))) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $exact-struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $exact-struct (ref (exact $struct))) + ;; Now the copy is to exact $struct. + (struct.set $struct 0 + (local.get $exact-struct) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $gets (type $5) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $5) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub1 (ref null (exact $sub1))) + ;; CHECK-NEXT: (local $sub2 (ref null (exact $sub2))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub1 (ref null (exact $sub1))) + (local $sub2 (ref null (exact $sub2))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $struct (struct (field (mut i32))))) + (type $sub (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $other (struct (field (mut i32)))) + (type $other (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $other) (ref $struct)))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $super + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + ;; Now the different value is in $super. Since it's set in an exact $super, + ;; it won't interfere with the copy. + (drop + (struct.new $other + (i32.const 10) + ) + ) + (drop + (struct.new $super + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $other (ref $other)) (param $struct (ref $struct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $struct (ref $struct)) + ;; Copy from $other to $struct. + (struct.set $struct 0 + (local.get $struct) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub (ref null $sub)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub (ref null (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub (ref null (exact $sub))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $struct (struct (field (mut i32))))) + (type $sub (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $other (struct (field (mut i32)))) + (type $other (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $other) (ref (exact $struct))))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $super + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + ;; Same as above. + (drop + (struct.new $other + (i32.const 10) + ) + ) + (drop + (struct.new $super + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $other (ref $other)) (param $exact-struct (ref (exact $struct))) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $exact-struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $exact-struct (ref (exact $struct))) + ;; Copy from $other to exact $struct. + (struct.set $struct 0 + (local.get $exact-struct) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub (ref null $sub)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub (ref null (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub (ref null (exact $sub))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $struct (struct (field (mut i32))))) + (type $sub (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $other (struct (field (mut i32)))) + (type $other (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $super)))) + + ;; CHECK: (type $6 (func (param (ref $other) (ref $struct)))) + + ;; CHECK: (func $init (type $5) (param $super (ref $super)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $super (ref $super)) + ;; Now the different value is in an inexact $super. + (drop + (struct.new $other + (i32.const 10) + ) + ) + (struct.set $super 0 + (local.get $super) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $6) (param $other (ref $other)) (param $struct (ref $struct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $struct (ref $struct)) + ;; Copy from $other to $struct. + (struct.set $struct 0 + (local.get $struct) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub 0 + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub (ref null $sub)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub (ref null (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub 0 + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub (ref null (exact $sub))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $struct (struct (field (mut i32))))) + (type $sub (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $other (struct (field (mut i32)))) + (type $other (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $super)))) + + ;; CHECK: (type $6 (func (param (ref $other) (ref (exact $struct))))) + + ;; CHECK: (func $init (type $5) (param $super (ref $super)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $super (ref $super)) + ;; Same as above. + (drop + (struct.new $other + (i32.const 10) + ) + ) + (struct.set $super 0 + (local.get $super) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $6) (param $other (ref $other)) (param $exact-struct (ref (exact $struct))) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $exact-struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $exact-struct (ref (exact $struct))) + ;; Copy from $other to exact $struct. + (struct.set $struct 0 + (local.get $exact-struct) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub (ref null $sub)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub (ref null (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub (ref null (exact $sub))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $A (sub $super (struct (field (mut i32))))) + (type $A (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $B (sub $super (struct (field (mut i32))))) + (type $B (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $other (struct (field (mut i32)))) + (type $other (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $super)))) + + ;; CHECK: (type $6 (func (param (ref $other) (ref $A)))) + + ;; CHECK: (func $init (type $5) (param $super (ref $super)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $super (ref $super)) + (drop + (struct.new $other + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $6) (param $other (ref $other)) (param $A (ref $A)) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $A (ref $A)) + ;; Copy from $other to $A. The fact that the field is missing in $super + ;; should not cause problems. + (struct.set $A 0 + (local.get $A) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $B (ref null $B)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $A (ref null $A)) + (local $B (ref null $B)) + (drop + (struct.get $A 0 + (local.get $A) + ) + ) + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $A (ref null (exact $A))) + ;; CHECK-NEXT: (local $B (ref null (exact $B))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $A (ref null (exact $A))) + (local $B (ref null (exact $B))) + (drop + (struct.get $A 0 + (local.get $A) + ) + ) + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $A (sub $super (struct (field (mut i32))))) + (type $A (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $B (sub $super (struct (field (mut i32))))) + (type $B (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $other (struct (field (mut i32)))) + (type $other (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $super)))) + + ;; CHECK: (type $6 (func (param (ref $other) (ref (exact $A))))) + + ;; CHECK: (func $init (type $5) (param $super (ref $super)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $super (ref $super)) + (drop + (struct.new $other + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $6) (param $other (ref $other)) (param $exact-A (ref (exact $A))) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $exact-A) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $exact-A (ref (exact $A))) + ;; Same as above, but now the copy is to exact $A. + (struct.set $A 0 + (local.get $exact-A) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $B (ref null $B)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $A (ref null $A)) + (local $B (ref null $B)) + (drop + (struct.get $A 0 + (local.get $A) + ) + ) + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $A (ref null (exact $A))) + ;; CHECK-NEXT: (local $B (ref null (exact $B))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $A (ref null (exact $A))) + (local $B (ref null (exact $B))) + (drop + (struct.get $A 0 + (local.get $A) + ) + ) + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub1 (sub $struct (struct (field (mut i32))))) + (type $sub1 (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $sub2 (sub $struct (struct (field (mut i32))))) + (type $sub2 (sub $struct (struct (field (mut i32))))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $struct)))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $sub1 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + ;; Same as above. + (drop + (struct.new $sub1 + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $struct (ref $struct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $struct (ref $struct)) + ;; Copy from $struct to itself. This propagates the write from $sub1 to + ;; $sub2. + (struct.set $struct 0 + (local.get $struct) + (struct.get $struct 0 + (local.get $struct) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub1 (ref null (exact $sub1))) + ;; CHECK-NEXT: (local $sub2 (ref null (exact $sub2))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub1 (ref null (exact $sub1))) + (local $sub2 (ref null (exact $sub2))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub1 (sub $struct (struct (field (mut i32))))) + (type $sub1 (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $sub2 (sub $struct (struct (field (mut i32))))) + (type $sub2 (sub $struct (struct (field (mut i32))))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $struct) (ref (exact $struct))))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $sub1 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + ;; Same as above. + (drop + (struct.new $sub1 + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $struct (ref $struct)) (param $exact-struct (ref (exact $struct))) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $exact-struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $struct (ref $struct)) (param $exact-struct (ref (exact $struct))) + ;; Now copy from $struct to exact $struct. This does not propagate to $sub2. + (struct.set $struct 0 + (local.get $exact-struct) + (struct.get $struct 0 + (local.get $struct) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub1 (ref null (exact $sub1))) + ;; CHECK-NEXT: (local $sub2 (ref null (exact $sub2))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub1 (ref null (exact $sub1))) + (local $sub2 (ref null (exact $sub2))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub1 (sub $struct (struct (field (mut i32))))) + (type $sub1 (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $sub2 (sub $struct (struct (field (mut i32))))) + (type $sub2 (sub $struct (struct (field (mut i32))))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $struct) (ref (exact $struct))))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $sub1 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + ;; Same as above. + (drop + (struct.new $sub1 + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $struct (ref $struct)) (param $exact-struct (ref (exact $struct))) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $exact-struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $struct (ref $struct)) (param $exact-struct (ref (exact $struct))) + ;; Now copy from exact $struct to $struct. This does nothing, since exact + ;; $struct is not written to originally. + (struct.set $struct 0 + (local.get $struct) + (struct.get $struct 0 + (local.get $exact-struct) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub1 (ref null (exact $sub1))) + ;; CHECK-NEXT: (local $sub2 (ref null (exact $sub2))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub1 (ref null (exact $sub1))) + (local $sub2 (ref null (exact $sub2))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub1 (sub $struct (struct (field (mut i32))))) + (type $sub1 (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $sub2 (sub $struct (struct (field (mut i32))))) + (type $sub2 (sub $struct (struct (field (mut i32))))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref (exact $struct))))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $sub1 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + ;; Same as above. + (drop + (struct.new $sub1 + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $exact-struct (ref (exact $struct))) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $exact-struct) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $exact-struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $exact-struct (ref (exact $struct))) + ;; Now copy from exact $struct to exact $struct. + (struct.set $struct 0 + (local.get $exact-struct) + (struct.get $struct 0 + (local.get $exact-struct) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $sub1 (ref null (exact $sub1))) + ;; CHECK-NEXT: (local $sub2 (ref null (exact $sub2))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $sub1 (ref null (exact $sub1))) + (local $sub2 (ref null (exact $sub2))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $struct2 (sub $super (struct (field (mut i32))))) + (type $struct2 (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $struct (struct (field (mut i32))))) + (type $sub (sub $struct (struct (field (mut i32))))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $struct) (ref $super)))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $sub + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $sub + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $struct (ref $struct)) (param $super (ref $super)) + ;; CHECK-NEXT: (struct.set $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $struct (ref $struct)) (param $super (ref $super)) + ;; Copy from $struct to $super. + (struct.set $super 0 + (local.get $super) + (struct.get $struct 0 + (local.get $struct) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $struct2 (ref null $struct2)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $struct2 (ref null $struct2)) + (local $sub (ref null $sub)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $struct2 0 + (local.get $struct2) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $struct2 (ref null (exact $struct2))) + ;; CHECK-NEXT: (local $sub (ref null (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $struct2 (ref null (exact $struct2))) + (local $sub (ref null (exact $sub))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $struct2 0 + (local.get $struct2) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $struct2 (sub $super (struct (field (mut i32))))) + (type $struct2 (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $struct (struct (field (mut i32))))) + (type $sub (sub $struct (struct (field (mut i32))))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $struct) (ref (exact $super))))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $sub + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $sub + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $struct (ref $struct)) (param $exact-super (ref (exact $super))) + ;; CHECK-NEXT: (struct.set $super 0 + ;; CHECK-NEXT: (local.get $exact-super) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $struct (ref $struct)) (param $exact-super (ref (exact $super))) + ;; Copy from $struct to exact $super. + (struct.set $super 0 + (local.get $exact-super) + (struct.get $struct 0 + (local.get $struct) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $struct2 (ref null $struct2)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $struct2 (ref null $struct2)) + (local $sub (ref null $sub)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $struct2 0 + (local.get $struct2) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $struct2 (ref null (exact $struct2))) + ;; CHECK-NEXT: (local $sub (ref null (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $struct2 (ref null (exact $struct2))) + (local $sub (ref null (exact $sub))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $struct2 0 + (local.get $struct2) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $struct2 (sub $super (struct (field (mut i32))))) + (type $struct2 (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $struct (struct (field (mut i32))))) + (type $sub (sub $struct (struct (field (mut i32))))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref (exact $struct)) (ref $super)))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $sub + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $sub + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $exact-struct (ref (exact $struct))) (param $super (ref $super)) + ;; CHECK-NEXT: (struct.set $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $exact-struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $exact-struct (ref (exact $struct))) (param $super (ref $super)) + ;; Copy from exact $struct to $super. + (struct.set $super 0 + (local.get $super) + (struct.get $struct 0 + (local.get $exact-struct) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $struct2 (ref null $struct2)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $struct2 (ref null $struct2)) + (local $sub (ref null $sub)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $struct2 0 + (local.get $struct2) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $struct2 (ref null (exact $struct2))) + ;; CHECK-NEXT: (local $sub (ref null (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $struct2 (ref null (exact $struct2))) + (local $sub (ref null (exact $sub))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $struct2 0 + (local.get $struct2) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $struct2 (sub $super (struct (field (mut i32))))) + (type $struct2 (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $struct (struct (field (mut i32))))) + (type $sub (sub $struct (struct (field (mut i32))))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref (exact $struct)) (ref (exact $super))))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $sub + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $sub + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $exact-struct (ref (exact $struct))) (param $exact-super (ref (exact $super))) + ;; CHECK-NEXT: (struct.set $super 0 + ;; CHECK-NEXT: (local.get $exact-super) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $exact-struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $exact-struct (ref (exact $struct))) (param $exact-super (ref (exact $super))) + ;; Copy from exact $struct to exact $super. + (struct.set $super 0 + (local.get $exact-super) + (struct.get $struct 0 + (local.get $exact-struct) + ) + ) + ) + + ;; CHECK: (func $gets (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $struct2 (ref null $struct2)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $struct2 (ref null $struct2)) + (local $sub (ref null $sub)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $struct2 0 + (local.get $struct2) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $4) + ;; CHECK-NEXT: (local $super (ref null (exact $super))) + ;; CHECK-NEXT: (local $struct (ref null (exact $struct))) + ;; CHECK-NEXT: (local $struct2 (ref null (exact $struct2))) + ;; CHECK-NEXT: (local $sub (ref null (exact $sub))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $super (ref null (exact $super))) + (local $struct (ref null (exact $struct))) + (local $struct2 (ref null (exact $struct2))) + (local $sub (ref null (exact $sub))) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $struct2 0 + (local.get $struct2) + ) + ) + (drop + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (sub (struct (field (mut i32))))) + (type $struct (sub (struct (field (mut i32))))) + ;; CHECK: (type $sub1 (sub $struct (struct (field (mut i32))))) + (type $sub1 (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $sub2 (sub $struct (struct (field (mut i32))))) + (type $sub2 (sub $struct (struct (field (mut i32))))) + ) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (type $4 (func (param (ref $struct) (ref $sub1)))) + + ;; CHECK: (func $init (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $struct + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $4) (param $struct (ref $struct)) (param $sub1 (ref $sub1)) + ;; CHECK-NEXT: (struct.set $sub1 0 + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $struct (ref $struct)) (param $sub1 (ref $sub1)) + ;; Copy from $struct to $sub1. + (struct.set $sub1 0 + (local.get $sub1) + (struct.get $struct 0 + (local.get $struct) + ) + ) + ) + + ;; CHECK: (func $gets (type $3) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $3) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null (exact $sub1))) + ;; CHECK-NEXT: (local $sub2 (ref null (exact $sub2))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $struct (ref null $struct)) + (local $sub1 (ref null (exact $sub1))) + (local $sub2 (ref null (exact $sub2))) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (sub (struct (field (mut i32))))) + (type $struct (sub (struct (field (mut i32))))) + ;; CHECK: (type $sub1 (sub $struct (struct (field (mut i32))))) + (type $sub1 (sub $struct (struct (field (mut i32))))) + ;; CHECK: (type $sub2 (sub $struct (struct (field (mut i32))))) + (type $sub2 (sub $struct (struct (field (mut i32))))) + ) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (type $4 (func (param (ref $struct) (ref (exact $sub1))))) + + ;; CHECK: (func $init (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $struct + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $4) (param $struct (ref $struct)) (param $exact-sub1 (ref (exact $sub1))) + ;; CHECK-NEXT: (struct.set $sub1 0 + ;; CHECK-NEXT: (local.get $exact-sub1) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $struct (ref $struct)) (param $exact-sub1 (ref (exact $sub1))) + ;; Copy from $struct to exact $sub1. + (struct.set $sub1 0 + (local.get $exact-sub1) + (struct.get $struct 0 + (local.get $struct) + ) + ) + ) + + ;; CHECK: (func $gets (type $3) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) + + ;; CHECK: (func $exact-gets (type $3) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null (exact $sub1))) + ;; CHECK-NEXT: (local $sub2 (ref null (exact $sub2))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $exact-gets + (local $struct (ref null $struct)) + (local $sub1 (ref null (exact $sub1))) + (local $sub2 (ref null (exact $sub2))) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32)) (field (mut i32)) (field (mut i32))))) + (type $super (sub (struct (field (mut i32) (mut i32) (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32)) (field (mut i32)) (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32) (mut i32) (mut i32))))) + ;; CHECK: (type $sub1 (sub $struct (struct (field (mut i32)) (field (mut i32)) (field (mut i32))))) + (type $sub1 (sub $struct (struct (field (mut i32) (mut i32) (mut i32))))) + ;; CHECK: (type $sub2 (sub $struct (struct (field (mut i32)) (field (mut i32)) (field (mut i32))))) + (type $sub2 (sub $struct (struct (field (mut i32) (mut i32) (mut i32))))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $struct) (ref $struct)))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $sub1 + ;; CHECK-NEXT: (i32.const 666) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $sub1 + (i32.const 666) + (i32.const 10) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $src (ref $struct)) (param $dst (ref $struct)) + ;; CHECK-NEXT: (struct.set $struct 2 + ;; CHECK-NEXT: (local.get $dst) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $src) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $src (ref $struct)) (param $dst (ref $struct)) + ;; Copy from index 1 to index 2 in the same type. + (struct.set $struct 2 + (local.get $dst) + (struct.get $struct 1 + (local.get $src) + ) + ) + ) + + ;; CHECK: (func $get-0 (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 666) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 666) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 666) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-0 + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $super 0 + (local.get $super) + ) + ) + (drop + (struct.get $struct 0 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 0 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 0 + (local.get $sub2) + ) + ) + ) + + ;; CHECK: (func $get-1 (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-1 + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $super 1 + (local.get $super) + ) + ) + (drop + (struct.get $struct 1 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 1 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 1 + (local.get $sub2) + ) + ) + ) + + ;; CHECK: (func $get-2 (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-2 + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $super 1 + (local.get $super) + ) + ) + (drop + (struct.get $struct 1 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 1 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 1 + (local.get $sub2) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (type $4 (func (param (ref $C)) (result i32))) + + ;; CHECK: (func $init (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $A + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $3) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $B (ref null $B)) + ;; CHECK-NEXT: (local $C (ref null $C)) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy + (local $A (ref null $A)) + (local $B (ref null $B)) + (local $C (ref null $C)) + ;; Copy $A to $B. + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy $B to $C. + (struct.set $C 0 + (local.get $C) + (struct.get $B 0 + (local.get $B) + ) + ) + ) + + ;; CHECK: (func $get (type $4) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + (func $get (param $C (ref $C)) (result i32) + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (type $4 (func (param (ref $C)) (result i32))) + + ;; CHECK: (func $init (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $A + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $3) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $B (ref null $B)) + ;; CHECK-NEXT: (local $exact-B (ref null (exact $B))) + ;; CHECK-NEXT: (local $C (ref null $C)) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $exact-B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy + (local $A (ref null $A)) + (local $B (ref null $B)) + (local $exact-B (ref null (exact $B))) + (local $C (ref null $C)) + ;; Copy $A to $B. + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy exact $B to $C. + (struct.set $C 0 + (local.get $C) + (struct.get $B 0 + (local.get $exact-B) + ) + ) + ) + + ;; CHECK: (func $get (type $4) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + (func $get (param $C (ref $C)) (result i32) + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (type $4 (func (param (ref $C)) (result i32))) + + ;; CHECK: (func $init (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $A + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $3) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $B (ref null $B)) + ;; CHECK-NEXT: (local $exact-B (ref null (exact $B))) + ;; CHECK-NEXT: (local $C (ref null $C)) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $exact-B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy + (local $A (ref null $A)) + (local $B (ref null $B)) + (local $exact-B (ref null (exact $B))) + (local $C (ref null $C)) + ;; Copy $A to exact $B. + (struct.set $B 0 + (local.get $exact-B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy $B to $C. + (struct.set $C 0 + (local.get $C) + (struct.get $B 0 + (local.get $B) + ) + ) + ) + + ;; CHECK: (func $get (type $4) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + (func $get (param $C (ref $C)) (result i32) + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (type $4 (func (param (ref $C)) (result i32))) + + ;; CHECK: (func $init (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $A + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $3) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $exact-B (ref null (exact $B))) + ;; CHECK-NEXT: (local $C (ref null $C)) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $exact-B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $exact-B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy + (local $A (ref null $A)) + (local $exact-B (ref null (exact $B))) + (local $C (ref null $C)) + ;; Copy $A to exact $B. + (struct.set $B 0 + (local.get $exact-B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy $exact B to $C. + (struct.set $C 0 + (local.get $C) + (struct.get $B 0 + (local.get $exact-B) + ) + ) + ) + + ;; CHECK: (func $get (type $4) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + (func $get (param $C (ref $C)) (result i32) + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $super (struct (field (mut i32))))) + (type $sub (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $C)) (result i32))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $A + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $4) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $C (ref null $C)) + ;; CHECK-NEXT: (struct.set $sub 0 + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy + (local $A (ref null $A)) + (local $sub (ref null $sub)) + (local $super (ref null $super)) + (local $C (ref null $C)) + ;; Copy $A to $sub. + (struct.set $sub 0 + (local.get $sub) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy $super to $C. + (struct.set $C 0 + (local.get $C) + (struct.get $super 0 + (local.get $super) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + (func $get (param $C (ref $C)) (result i32) + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $super (struct (field (mut i32))))) + (type $sub (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $C)) (result i32))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $A + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $4) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (local $exact-super (ref null (exact $super))) + ;; CHECK-NEXT: (local $C (ref null $C)) + ;; CHECK-NEXT: (struct.set $sub 0 + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $exact-super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy + (local $A (ref null $A)) + (local $sub (ref null $sub)) + (local $exact-super (ref null (exact $super))) + (local $C (ref null $C)) + ;; Copy $A to $sub. + (struct.set $sub 0 + (local.get $sub) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy exact $super to $C. This does not propagate the value. + (struct.set $C 0 + (local.get $C) + (struct.get $super 0 + (local.get $exact-super) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $get (param $C (ref $C)) (result i32) + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $super (struct (field (mut i32))))) + (type $sub (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $C)) (result i32))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $A + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $4) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $exact-sub (ref null (exact $sub))) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $C (ref null $C)) + ;; CHECK-NEXT: (struct.set $sub 0 + ;; CHECK-NEXT: (local.get $exact-sub) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy + (local $A (ref null $A)) + (local $exact-sub (ref null (exact $sub))) + (local $super (ref null $super)) + (local $C (ref null $C)) + ;; Copy $A to exact $sub. + (struct.set $sub 0 + (local.get $exact-sub) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy $super to $C. + (struct.set $C 0 + (local.get $C) + (struct.get $super 0 + (local.get $super) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + (func $get (param $C (ref $C)) (result i32) + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $super (struct (field (mut i32))))) + (type $sub (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $C)) (result i32))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $A + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $4) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $exact-sub (ref null (exact $sub))) + ;; CHECK-NEXT: (local $exact-super (ref null (exact $super))) + ;; CHECK-NEXT: (local $C (ref null $C)) + ;; CHECK-NEXT: (struct.set $sub 0 + ;; CHECK-NEXT: (local.get $exact-sub) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $exact-super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy + (local $A (ref null $A)) + (local $exact-sub (ref null (exact $sub))) + (local $exact-super (ref null (exact $super))) + (local $C (ref null $C)) + ;; Copy $A to exact $sub. + (struct.set $sub 0 + (local.get $exact-sub) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy exact $super to $C. This does not propagate the value. + (struct.set $C 0 + (local.get $C) + (struct.get $super 0 + (local.get $exact-super) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $get (param $C (ref $C)) (result i32) + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $super (struct (field (mut i32))))) + (type $sub (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $C)) (result i32))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $A + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $4) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $C (ref null $C)) + ;; CHECK-NEXT: (struct.set $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy + (local $A (ref null $A)) + (local $sub (ref null $sub)) + (local $super (ref null $super)) + (local $C (ref null $C)) + ;; Copy $A to $super. + (struct.set $super 0 + (local.get $super) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy $sub to $C. + (struct.set $C 0 + (local.get $C) + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + (func $get (param $C (ref $C)) (result i32) + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $super (struct (field (mut i32))))) + (type $sub (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $C)) (result i32))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $A + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $4) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $exact-sub (ref null (exact $sub))) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $C (ref null $C)) + ;; CHECK-NEXT: (struct.set $super 0 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $exact-sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy + (local $A (ref null $A)) + (local $exact-sub (ref null (exact $sub))) + (local $super (ref null $super)) + (local $C (ref null $C)) + ;; Copy $A to $super. + (struct.set $super 0 + (local.get $super) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy exact $sub to $C. + (struct.set $C 0 + (local.get $C) + (struct.get $sub 0 + (local.get $exact-sub) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + (func $get (param $C (ref $C)) (result i32) + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $super (struct (field (mut i32))))) + (type $sub (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $C)) (result i32))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $A + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $4) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (local $exact-super (ref null (exact $super))) + ;; CHECK-NEXT: (local $C (ref null $C)) + ;; CHECK-NEXT: (struct.set $super 0 + ;; CHECK-NEXT: (local.get $exact-super) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy + (local $A (ref null $A)) + (local $sub (ref null $sub)) + (local $exact-super (ref null (exact $super))) + (local $C (ref null $C)) + ;; Copy $A to exact $super. + (struct.set $super 0 + (local.get $exact-super) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy $sub to $C. This does not propagate the value. + (struct.set $C 0 + (local.get $C) + (struct.get $sub 0 + (local.get $sub) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $get (param $C (ref $C)) (result i32) + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $super (sub (struct (field (mut i32))))) + (type $super (sub (struct (field (mut i32))))) + ;; CHECK: (type $sub (sub $super (struct (field (mut i32))))) + (type $sub (sub $super (struct (field (mut i32))))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $C)) (result i32))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + (drop + (struct.new $A + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $4) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (local $exact-sub (ref null (exact $sub))) + ;; CHECK-NEXT: (local $exact-super (ref null (exact $super))) + ;; CHECK-NEXT: (local $C (ref null $C)) + ;; CHECK-NEXT: (struct.set $super 0 + ;; CHECK-NEXT: (local.get $exact-super) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $exact-sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy + (local $A (ref null $A)) + (local $exact-sub (ref null (exact $sub))) + (local $exact-super (ref null (exact $super))) + (local $C (ref null $C)) + ;; Copy $A to exact $super. + (struct.set $super 0 + (local.get $exact-super) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy exact $sub to $C. This does not propagate the value. + (struct.set $C 0 + (local.get $C) + (struct.get $sub 0 + (local.get $exact-sub) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $get (param $C (ref $C)) (result i32) + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i8)))) + (type $B (struct (field (mut i8)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x000001FF) + ) + ;; Copy i32 to i8. + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Read truncated i8. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i16)))) + (type $B (struct (field (mut i16)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 131071) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 131071) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x0001FFFF) + ) + ;; Copy i32 to i16. + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Read truncated i16. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x000001FF) + ) + ;; Copy i8 to i32. + (struct.set $B 0 + (local.get $B) + (struct.get_u $A 0 + (local.get $A) + ) + ) + ;; Read truncated i32. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x000001FF) + ) + ;; Copy i8 to i32 with sign extension. + (struct.set $B 0 + (local.get $B) + (struct.get_s $A 0 + (local.get $A) + ) + ) + ;; Read truncated then sign extended i32. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ;; CHECK: (type $B (struct (field (mut i16)))) + (type $B (struct (field (mut i16)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x000001FF) + ) + ;; Copy i8 to i16. + (struct.set $B 0 + (local.get $B) + (struct.get_u $A 0 + (local.get $A) + ) + ) + ;; Read truncated i16. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ;; CHECK: (type $B (struct (field (mut i16)))) + (type $B (struct (field (mut i16)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x000001FF) + ) + ;; Copy i8 to i16 with sign extension. + (struct.set $B 0 + (local.get $B) + (struct.get_s $A 0 + (local.get $A) + ) + ) + ;; Read truncated then sign extended i16. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i16)))) + (type $A (struct (field (mut i16)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 131071) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 131071) + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x0001FFFF) + ) + ;; Copy i16 to i32. + (struct.set $B 0 + (local.get $B) + (struct.get_u $A 0 + (local.get $A) + ) + ) + ;; Read truncated i32. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i16)))) + (type $A (struct (field (mut i16)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 131071) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (i32.const 131071) + ;; CHECK-NEXT: (i32.const 16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x0001FFFF) + ) + ;; Copy i16 to i32 with sign extension. + (struct.set $B 0 + (local.get $B) + (struct.get_s $A 0 + (local.get $A) + ) + ) + ;; Read truncated then sign extended i32. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i16)))) + (type $A (struct (field (mut i16)))) + ;; CHECK: (type $B (struct (field (mut i8)))) + (type $B (struct (field (mut i8)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 131071) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 131071) + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x0001FFFF) + ) + ;; Copy i16 to i8. + (struct.set $B 0 + (local.get $B) + (struct.get_u $A 0 + (local.get $A) + ) + ) + ;; Read truncated i8. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i16)))) + (type $A (struct (field (mut i16)))) + ;; CHECK: (type $B (struct (field (mut i8)))) + (type $B (struct (field (mut i8)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 131071) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (i32.const 131071) + ;; CHECK-NEXT: (i32.const 16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x0001FFFF) + ) + ;; Copy i16 to i8 with sign extension. This does not make a difference. + (struct.set $B 0 + (local.get $B) + (struct.get_s $A 0 + (local.get $A) + ) + ) + ;; Read truncated i8. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ) + + ;; CHECK: (type $1 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $test (type $1) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (struct.get_u $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get_u $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0xFFFFFFFF) + ) + ;; Copy the i8 to itself. + (struct.set $A 0 + (local.get $A) + (struct.get_u $A 0 + (local.get $A) + ) + ) + ;; Read truncated i8. + ;; TODO: We do not optimize this because the truncated copy value conflicts + ;; with the untruncated value found by the initial analysis. Fix this by + ;; tracking truncations and sign extensions as part of the analysis. + (struct.get $A 0 + (local.get $A) + ) + ) +) + +(module + (rec + ;; CHECK: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ) + + ;; CHECK: (type $1 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $test (type $1) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0xFFFFFFFF) + ) + ;; Copy the i8 to itself with sign extension. + (struct.set $A 0 + (local.get $A) + (struct.get_s $A 0 + (local.get $A) + ) + ) + ;; Read truncated i8. Now we can optimize because the sign-extended value + ;; happens to match the original value. + (struct.get $A 0 + (local.get $A) + ) + ) +) + +(module + (rec + ;; CHECK: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ) + + ;; CHECK: (type $1 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $test (type $1) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (struct.get_s $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get_u $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x000001FF) + ) + ;; Copy the i8 to itself with sign extension. + (struct.set $A 0 + (local.get $A) + (struct.get_s $A 0 + (local.get $A) + ) + ) + ;; Now the value does not match and we don't optimize, even though we could + ;; in principal. + (struct.get $A 0 + (local.get $A) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i8)))) + (type $B (struct (field (mut i8)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (global $g i32 (i32.const 0)) + (global $g i32 (i32.const 0)) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get_u $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + (struct.set $A 0 + (local.get $A) + (global.get $g) + ) + ;; Copy the i32 with global to the i8. + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Read the i8. We do not optimize this because we cannot model the + ;; truncation of the global. TODO. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (global $g i32 (i32.const 0)) + (global $g i32 (i32.const 0)) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + (struct.set $A 0 + (local.get $A) + (global.get $g) + ) + ;; Copy the i8 with global to the i32. + (struct.set $B 0 + (local.get $B) + (struct.get_u $A 0 + (local.get $A) + ) + ) + ;; Read the i32. We still cannot optimize. TODO. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (global $g i32 (i32.const 0)) + (global $g i32 (i32.const 0)) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + (struct.set $A 0 + (local.get $A) + (global.get $g) + ) + ;; Copy the i8 with global to the i32 with sign extension. + (struct.set $B 0 + (local.get $B) + (struct.get_s $A 0 + (local.get $A) + ) + ) + ;; Read the i32. We still cannot optimize. TODO. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ) + + ;; CHECK: (type $1 (func (param (ref $A)) (result i32))) + + ;; CHECK: (global $g i32 (i32.const 0)) + (global $g i32 (i32.const 0)) + + ;; CHECK: (func $test (type $1) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (struct.get_u $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get_u $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (result i32) + (struct.set $A 0 + (local.get $A) + (global.get $g) + ) + ;; Copy the i8 holding the global to itself. + (struct.set $A 0 + (local.get $A) + (struct.get_u $A 0 + (local.get $A) + ) + ) + ;; Read the i8. We cannot model the truncated global. TODO. + (struct.get_u $A 0 + (local.get $A) + ) + ) +) + + +(module + (rec + ;; CHECK: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ) + + ;; CHECK: (type $1 (func (param (ref $A)) (result i32))) + + ;; CHECK: (global $g i32 (i32.const 0)) + (global $g i32 (i32.const 0)) + + ;; CHECK: (func $test (type $1) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (struct.get_s $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get_u $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (result i32) + (struct.set $A 0 + (local.get $A) + (global.get $g) + ) + ;; Copy the i8 holding the global to itself with sign extension. + (struct.set $A 0 + (local.get $A) + (struct.get_s $A 0 + (local.get $A) + ) + ) + ;; Read the i8. We cannot model the truncated global. TODO. + (struct.get_u $A 0 + (local.get $A) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; Copy the uninitialized i8 to i32. + (struct.set $B 0 + (local.get $B) + (struct.get_u $A 0 + (local.get $A) + ) + ) + ;; Read the i32. It should still be uninitialized and optimized out. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; Copy the uninitialized i8 to i32 with sign extension. + (struct.set $B 0 + (local.get $B) + (struct.get_s $A 0 + (local.get $A) + ) + ) + ;; Read the i32. It should still be uninitialized and optimized out. + (struct.get $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i8)))) + (type $B (struct (field (mut i8)))) + ) + + ;; CHECK: (type $2 (func (param (ref $A) (ref $B)) (result i32))) + + ;; CHECK: (func $test (type $2) (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (result i32) + ;; Copy the uninitialized i32 to i8. + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Read the i8. It should still be uninitialized and optimized out. + (struct.get_u $B 0 + (local.get $B) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i8)))) + (type $B (struct (field (mut i8)))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $3 (func (param (ref $A) (ref $B) (ref $C)) (result i32))) + + ;; CHECK: (func $test (type $3) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x000001FF) + ) + ;; Copy i32 to i8. + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy the i8 to another i32. + (struct.set $C 0 + (local.get $C) + (struct.get_u $B 0 + (local.get $B) + ) + ) + ;; Read truncated i32. + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i8)))) + (type $B (struct (field (mut i8)))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $3 (func (param (ref $A) (ref $B) (ref $C)) (result i32))) + + ;; CHECK: (func $test (type $3) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x000001FF) + ) + ;; Copy i32 to i8. + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy the i8 to another i32 with sign extension. + (struct.set $C 0 + (local.get $C) + (struct.get_s $B 0 + (local.get $B) + ) + ) + ;; Read truncated i32. + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ;; CHECK: (type $C (struct (field (mut i8)))) + (type $C (struct (field (mut i8)))) + ) + + ;; CHECK: (type $3 (func (param (ref $A) (ref $B) (ref $C)) (result i32))) + + ;; CHECK: (func $test (type $3) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x000001FF) + ) + ;; Copy i8 to i32. + (struct.set $B 0 + (local.get $B) + (struct.get_u $A 0 + (local.get $A) + ) + ) + ;; Copy the i32 to another i8. + (struct.set $C 0 + (local.get $C) + (struct.get $B 0 + (local.get $B) + ) + ) + ;; Read the i8. + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i8)))) + (type $A (struct (field (mut i8)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ;; CHECK: (type $C (struct (field (mut i8)))) + (type $C (struct (field (mut i8)))) + ) + + ;; CHECK: (type $3 (func (param (ref $A) (ref $B) (ref $C)) (result i32))) + + ;; CHECK: (func $test (type $3) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (i32.const 511) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) (result i32) + (struct.set $A 0 + (local.get $A) + (i32.const 0x000001FF) + ) + ;; Copy i8 to i32 with sign extension. + (struct.set $B 0 + (local.get $B) + (struct.get_s $A 0 + (local.get $A) + ) + ) + ;; Copy the i32 to another i8. + (struct.set $C 0 + (local.get $C) + (struct.get $B 0 + (local.get $B) + ) + ) + ;; Read the i8. + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $3 (func (param (ref $A) (ref $B) (ref $C)) (result i32))) + + ;; CHECK: (func $test (type $3) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) (result i32) + ;; A and B separately copy matching values into C + (struct.set $A 0 + (local.get $A) + (i32.const 10) + ) + (struct.set $B 0 + (local.get $B) + (i32.const 10) + ) + ;; Copy A to C. + (struct.set $C 0 + (local.get $C) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy B to C. + (struct.set $C 0 + (local.get $C) + (struct.get $B 0 + (local.get $B) + ) + ) + ;; Read C. + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $3 (func (param (ref $A) (ref $B) (ref $C)) (result i32))) + + ;; CHECK: (func $test (type $3) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) (result i32) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) (result i32) + ;; Now A and B separately copy different values into C + (struct.set $A 0 + (local.get $A) + (i32.const 10) + ) + (struct.set $B 0 + (local.get $B) + (i32.const 20) + ) + ;; Copy A to C. + (struct.set $C 0 + (local.get $C) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy B to C. + (struct.set $C 0 + (local.get $C) + (struct.get $B 0 + (local.get $B) + ) + ) + ;; Read C. We cannot optimize because there are multiple values. + (struct.get $C 0 + (local.get $C) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $3 (func (param (ref $A) (ref $B) (ref $C)))) + + ;; CHECK: (func $test (type $3) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; Copy A to both B and C. + (struct.set $A 0 + (local.get $A) + (i32.const 10) + ) + ;; Copy A to B. + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy A to C. + (struct.set $C 0 + (local.get $C) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Read B. We can optimize. + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + ;; Read C. We can optimize this, too. + (drop + (struct.get $C 0 + (local.get $C) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $3 (func (param (ref $A) (ref $B) (ref $C)))) + + ;; CHECK: (func $copy (type $3) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; Create a loop copying A -> B -> C -> A. + ;; Copy A to B. + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy B to C. + (struct.set $C 0 + (local.get $C) + (struct.get $B 0 + (local.get $B) + ) + ) + ;; Copy C to A. + (struct.set $A 0 + (local.get $A) + (struct.get $C 0 + (local.get $C) + ) + ) + ) + + ;; CHECK: (func $get (type $3) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; Since no value ever entered the loop, these all get optimized out. + (drop + (struct.get $A 0 + (local.get $A) + ) + ) + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + (drop + (struct.get $C 0 + (local.get $C) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $3 (func (param (ref $A) (ref $B) (ref $C)))) + + ;; CHECK: (type $4 (func (param (ref $A)))) + + ;; CHECK: (func $init (type $4) (param $A (ref $A)) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $A (ref $A)) + ;; Now we inject a value into the loop. + (struct.set $A 0 + (local.get $A) + (i32.const 10) + ) + ) + + ;; CHECK: (func $copy (type $3) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; Create a loop copying A -> B -> C -> A. + ;; Copy A to B. + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy B to C. + (struct.set $C 0 + (local.get $C) + (struct.get $B 0 + (local.get $B) + ) + ) + ;; Copy C to A. + (struct.set $A 0 + (local.get $A) + (struct.get $C 0 + (local.get $C) + ) + ) + ) + + ;; CHECK: (func $get (type $3) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; These all get optimized to the one injected value. + (drop + (struct.get $A 0 + (local.get $A) + ) + ) + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + (drop + (struct.get $C 0 + (local.get $C) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + ;; CHECK: (type $B (struct (field (mut i32)))) + (type $B (struct (field (mut i32)))) + ;; CHECK: (type $C (struct (field (mut i32)))) + (type $C (struct (field (mut i32)))) + ) + + ;; CHECK: (type $3 (func (param (ref $A) (ref $B) (ref $C)))) + + ;; CHECK: (type $4 (func (param (ref $A) (ref $C)))) + + ;; CHECK: (func $init (type $4) (param $A (ref $A)) (param $C (ref $C)) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $A (ref $A)) (param $C (ref $C)) + ;; Now we inject two different values into the loop in different places. We + ;; won't be able to optimize anything. + (struct.set $A 0 + (local.get $A) + (i32.const 10) + ) + (struct.set $C 0 + (local.get $C) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $3) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (struct.get $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; Create a loop copying A -> B -> C -> A. + ;; Copy A to B. + (struct.set $B 0 + (local.get $B) + (struct.get $A 0 + (local.get $A) + ) + ) + ;; Copy B to C. + (struct.set $C 0 + (local.get $C) + (struct.get $B 0 + (local.get $B) + ) + ) + ;; Copy C to A. + (struct.set $A 0 + (local.get $A) + (struct.get $C 0 + (local.get $C) + ) + ) + ) + + ;; CHECK: (func $get (type $3) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $C 0 + ;; CHECK-NEXT: (local.get $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) + ;; These cannot be optimized. + (drop + (struct.get $A 0 + (local.get $A) + ) + ) + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + (drop + (struct.get $C 0 + (local.get $C) + ) + ) + ) +) diff --git a/test/lit/passes/cfp-reftest-copies.wast b/test/lit/passes/cfp-reftest-copies.wast new file mode 100644 index 00000000000..6422431617c --- /dev/null +++ b/test/lit/passes/cfp-reftest-copies.wast @@ -0,0 +1,744 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --closed-world --cfp-reftest -all -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (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 $other (sub (struct (field (mut i32))))) + (type $other (sub (struct (field (mut i32))))) + ) + + ;; CHECK: (type $3 (func (param (ref $other) (ref $A)))) + + ;; CHECK: (type $4 (func (param (ref $other) (ref (exact $A))))) + + ;; CHECK: (type $5 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $3) (param $other (ref $other)) (param $A (ref $A)) + ;; CHECK-NEXT: (struct.set $other 0 + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $other (ref $other)) (param $A (ref $A)) + (struct.set $other 0 + (local.get $other) + (i32.const 10) + ) + ;; Set inexact A. + (struct.set $A 0 + (local.get $A) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $4) (param $other (ref $other)) (param $A-exact (ref (exact $A))) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $A-exact (ref (exact $A))) + ;; Copy to exact A. + (struct.set $A 0 + (local.get $A-exact) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $A (ref $A)) (result i32) + ;; We won't be able to optimize because the set of inexact A could go to + ;; anything. + (struct.get $A 0 + (local.get $A) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (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 $other (sub (struct (field (mut i32))))) + (type $other (sub (struct (field (mut i32))))) + ) + + ;; CHECK: (type $3 (func (param (ref $other) (ref $A)))) + + ;; CHECK: (type $4 (func (param (ref $other) (ref $B)))) + + ;; CHECK: (type $5 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $3) (param $other (ref $other)) (param $A (ref $A)) + ;; CHECK-NEXT: (struct.set $other 0 + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $other (ref $other)) (param $A (ref $A)) + (struct.set $other 0 + (local.get $other) + (i32.const 10) + ) + ;; Set inexact A. + (struct.set $A 0 + (local.get $A) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $4) (param $other (ref $other)) (param $B (ref $B)) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $B (ref $B)) + ;; Copy to inexact B. + (struct.set $B 0 + (local.get $B) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $A (ref $A)) (result i32) + ;; We won't be able to optimize because the set of inexact A could go to + ;; anything. + (struct.get $A 0 + (local.get $A) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (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 $other (sub (struct (field (mut i32))))) + (type $other (sub (struct (field (mut i32))))) + ) + + ;; CHECK: (type $3 (func (param (ref $other) (ref $A)))) + + ;; CHECK: (type $4 (func (param (ref $other) (ref (exact $B))))) + + ;; CHECK: (type $5 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $3) (param $other (ref $other)) (param $A (ref $A)) + ;; CHECK-NEXT: (struct.set $other 0 + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $other (ref $other)) (param $A (ref $A)) + (struct.set $other 0 + (local.get $other) + (i32.const 10) + ) + ;; Set inexact A. + (struct.set $A 0 + (local.get $A) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $4) (param $other (ref $other)) (param $B-exact (ref (exact $B))) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $B-exact (ref (exact $B))) + ;; Copy to exact B. + (struct.set $B 0 + (local.get $B-exact) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $A (ref $A)) (result i32) + ;; We won't be able to optimize because the set of inexact A could go to + ;; anything. + (struct.get $A 0 + (local.get $A) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (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 $other (sub (struct (field (mut i32))))) + (type $other (sub (struct (field (mut i32))))) + ) + + ;; CHECK: (type $3 (func (param (ref $other) (ref (exact $A))))) + + ;; CHECK: (type $4 (func (param (ref $other) (ref $A)))) + + ;; CHECK: (type $5 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $3) (param $other (ref $other)) (param $A-exact (ref (exact $A))) + ;; CHECK-NEXT: (struct.set $other 0 + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $other (ref $other)) (param $A-exact (ref (exact $A))) + (struct.set $other 0 + (local.get $other) + (i32.const 10) + ) + ;; Set exact A. + (struct.set $A 0 + (local.get $A-exact) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $4) (param $other (ref $other)) (param $A (ref $A)) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $A (ref $A)) + ;; Copy to inexact A. + (struct.set $A 0 + (local.get $A) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $A (ref $A)) (result i32) + ;; We won't be able to optimize because the copy to inexact A could go to + ;; anything. + (struct.get $A 0 + (local.get $A) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (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 $other (sub (struct (field (mut i32))))) + (type $other (sub (struct (field (mut i32))))) + ) + + ;; CHECK: (type $3 (func (param (ref $other) (ref (exact $A))))) + + ;; CHECK: (type $4 (func (param (ref $other) (ref $B)))) + + ;; CHECK: (type $5 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $3) (param $other (ref $other)) (param $A-exact (ref (exact $A))) + ;; CHECK-NEXT: (struct.set $other 0 + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $other (ref $other)) (param $A-exact (ref (exact $A))) + (struct.set $other 0 + (local.get $other) + (i32.const 10) + ) + ;; Set exact A. + (struct.set $A 0 + (local.get $A-exact) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $4) (param $other (ref $other)) (param $B (ref $B)) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $B (ref $B)) + ;; Copy to inexact B. + (struct.set $B 0 + (local.get $B) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (ref.test (ref $B) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $A (ref $A)) (result i32) + ;; We can optimize! Only B will have the copied value. + (struct.get $A 0 + (local.get $A) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (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 $other (sub (struct (field (mut i32))))) + (type $other (sub (struct (field (mut i32))))) + ) + + ;; CHECK: (type $3 (func (param (ref $other) (ref (exact $A))))) + + ;; CHECK: (type $4 (func (param (ref $other) (ref (exact $B))))) + + ;; CHECK: (type $5 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $3) (param $other (ref $other)) (param $A-exact (ref (exact $A))) + ;; CHECK-NEXT: (struct.set $other 0 + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $other (ref $other)) (param $A-exact (ref (exact $A))) + (struct.set $other 0 + (local.get $other) + (i32.const 10) + ) + ;; Set exact A. + (struct.set $A 0 + (local.get $A-exact) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $4) (param $other (ref $other)) (param $B-exact (ref (exact $B))) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $B-exact (ref (exact $B))) + ;; Copy to exact B. + (struct.set $B 0 + (local.get $B-exact) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (ref.test (ref $B) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $A (ref $A)) (result i32) + ;; We can optimize! Only B will have the copied value. + (struct.get $A 0 + (local.get $A) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (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 $other (sub (struct (field (mut i32))))) + (type $other (sub (struct (field (mut i32))))) + ) + + ;; CHECK: (type $3 (func (param (ref $other) (ref (exact $B))))) + + ;; CHECK: (type $4 (func (param (ref $other) (ref (exact $A))))) + + ;; CHECK: (type $5 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $3) (param $other (ref $other)) (param $B-exact (ref (exact $B))) + ;; CHECK-NEXT: (struct.set $other 0 + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $other (ref $other)) (param $B-exact (ref (exact $B))) + (struct.set $other 0 + (local.get $other) + (i32.const 10) + ) + ;; Set exact B. + (struct.set $B 0 + (local.get $B-exact) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $4) (param $other (ref $other)) (param $A-exact (ref (exact $A))) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $A-exact (ref (exact $A))) + ;; Copy to exact A. + (struct.set $A 0 + (local.get $A-exact) + (struct.get $other 0 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (ref.test (ref $B) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $A (ref $A)) (result i32) + ;; Switch the copy and set destinations from the previous case. We can still + ;; optimize! + (struct.get $A 0 + (local.get $A) + ) + ) +) + + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (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 $other (sub (struct (field (mut i32)) (field (mut i32))))) + (type $other (sub (struct (field (mut i32)) (field (mut i32))))) + ) + + ;; CHECK: (type $3 (func (param (ref $other) (ref (exact $B))))) + + ;; CHECK: (type $4 (func (param (ref $other) (ref (exact $A))))) + + ;; CHECK: (type $5 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $3) (param $other (ref $other)) (param $B-exact (ref (exact $B))) + ;; CHECK-NEXT: (struct.set $other 0 + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $other (ref $other)) (param $B-exact (ref (exact $B))) + (struct.set $other 0 + (local.get $other) + (i32.const 10) + ) + ;; Set exact B. + (struct.set $B 0 + (local.get $B-exact) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $4) (param $other (ref $other)) (param $A-exact (ref (exact $A))) + ;; CHECK-NEXT: (struct.set $other 1 + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $A-exact (ref (exact $A))) + ;; Copy to another field and from there to exact A. + (struct.set $other 1 + (local.get $other) + (struct.get $other 0 + (local.get $other) + ) + ) + ;; Copy to exact A. + (struct.set $A 0 + (local.get $A-exact) + (struct.get $other 1 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (ref.test (ref $B) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $A (ref $A)) (result i32) + ;; We can still optimize after doing two copies. + (struct.get $A 0 + (local.get $A) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (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 $other (sub (struct (field (mut i32)) (field (mut i32))))) + (type $other (sub (struct (field (mut i32)) (field (mut i32))))) + ) + + ;; CHECK: (type $3 (func (param (ref $other) (ref (exact $B))))) + + ;; CHECK: (type $4 (func (param (ref $other) (ref $A)))) + + ;; CHECK: (type $5 (func (param (ref $A)) (result i32))) + + ;; CHECK: (func $init (type $3) (param $other (ref $other)) (param $B-exact (ref (exact $B))) + ;; CHECK-NEXT: (struct.set $other 0 + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init (param $other (ref $other)) (param $B-exact (ref (exact $B))) + (struct.set $other 0 + (local.get $other) + (i32.const 10) + ) + ;; Set exact B. + (struct.set $B 0 + (local.get $B-exact) + (i32.const 20) + ) + ) + + ;; CHECK: (func $copy (type $4) (param $other (ref $other)) (param $A (ref $A)) + ;; CHECK-NEXT: (struct.set $other 1 + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $other (ref $other)) (param $A (ref $A)) + ;; Copy to another field and from there to inexact A. + (struct.set $other 1 + (local.get $other) + (struct.get $other 0 + (local.get $other) + ) + ) + ;; Copy to A. + (struct.set $A 0 + (local.get $A) + (struct.get $other 1 + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $get (type $5) (param $A (ref $A)) (result i32) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $A (ref $A)) (result i32) + ;; We cannot optimize because the copy to inexact A could write to anything. + (struct.get $A 0 + (local.get $A) + ) + ) +) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 4478ced671f..de34fb7f4c2 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -1695,155 +1695,6 @@ ) ) -;; Copies of a field to itself can be ignored. As a result, we can optimize both -;; of the gets here. -(module - ;; CHECK: (type $struct (struct (field (mut i32)))) - (type $struct (struct (mut i32))) - - ;; CHECK: (type $1 (func (param (ref null $struct) (ref null $struct)))) - - ;; CHECK: (func $test (type $1) (param $struct (ref null $struct)) (param $other (ref null $struct)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $struct) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (struct.set $struct 0 - ;; CHECK-NEXT: (local.get $struct) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $struct) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $other) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test (param $struct (ref null $struct)) (param $other (ref null $struct)) - (drop - (struct.new_default $struct) - ) - ;; This copy does not actually introduce any new possible values, and so it - ;; remains true that the only possible value is the default. - (struct.set $struct 0 - (local.get $struct) - (struct.get $struct 0 - (local.get $struct) - ) - ) - (drop - (struct.get $struct 0 - (local.get $other) - ) - ) - ) -) - -;; Test of a near-copy, of a similar looking field (same index, and same field -;; type) but in a different struct. -(module - ;; CHECK: (type $struct (struct (field (mut f32)) (field (mut i32)))) - (type $struct (struct (mut f32) (mut i32))) - ;; CHECK: (type $other (struct (field (mut f64)) (field (mut i32)))) - (type $other (struct (mut f64) (mut i32))) - - ;; CHECK: (type $2 (func (param (ref null $struct) (ref null $other)))) - - ;; CHECK: (func $test (type $2) (param $struct (ref null $struct)) (param $other (ref null $other)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $struct) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (struct.set $struct 1 - ;; CHECK-NEXT: (local.get $struct) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $other) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $struct 1 - ;; CHECK-NEXT: (local.get $struct) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test (param $struct (ref null $struct)) (param $other (ref null $other)) - (drop - (struct.new_default $struct) - ) - ;; As this is not a copy, we cannot optimize struct.1's get lower down. - (struct.set $struct 1 - (local.get $struct) - (struct.get $other 1 - (local.get $other) - ) - ) - (drop - (struct.get $struct 1 - (local.get $struct) - ) - ) - ) -) - -;; Test of a near-copy, of a different index. -(module - ;; CHECK: (type $struct (struct (field (mut i32)) (field (mut i32)))) - (type $struct (struct (mut i32) (mut i32))) - - ;; CHECK: (type $1 (func (param (ref null $struct)))) - - ;; CHECK: (func $test (type $1) (param $struct (ref null $struct)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_default $struct) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (struct.set $struct 0 - ;; CHECK-NEXT: (local.get $struct) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (local.get $struct) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $struct 0 - ;; CHECK-NEXT: (local.get $struct) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test (param $struct (ref null $struct)) - (drop - (struct.new_default $struct) - ) - ;; As this is not a copy, we cannot optimize struct.0's get lower down. - (struct.set $struct 0 - (local.get $struct) - (struct.get $struct 1 - (local.get $struct) - ) - ) - (drop - (struct.get $struct 0 - (local.get $struct) - ) - ) - ) -) - (module ;; CHECK: (type $struct (struct (field i32))) (type $struct (struct i32)) From 2eeb2f6a87105a132baf24d4d999d4d7f594ed55 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 18 Sep 2025 16:14:14 -0700 Subject: [PATCH 14/21] packForField comment --- src/ir/possible-constant.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ir/possible-constant.h b/src/ir/possible-constant.h index 2c2214e5a55..a75dd76299a 100644 --- a/src/ir/possible-constant.h +++ b/src/ir/possible-constant.h @@ -86,6 +86,9 @@ struct PossibleConstantValues { // identify a constant value here. void noteUnknown() { value = Many(); } + // Modify the possible constant to account for being written to or read from a + // possibly-packed field. When used to model a read, `isSigned` controls + // whether the value will be sign-extended or not. void packForField(const Field& field, bool isSigned = false) { if (field.type != Type::i32 || !field.isPacked()) { return; From d021ea809412c4bdc1febe30c21cc43fc6de5d93 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 18 Sep 2025 16:19:19 -0700 Subject: [PATCH 15/21] CopyInfo comment --- src/passes/ConstantFieldPropagation.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index b9deb063a4e..4ed22dc5e06 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -70,6 +70,9 @@ namespace wasm { namespace { +// The destination reference type and field index of a copy, as well as whether +// the copy read is signed (in the case of packed fields). This will be used to +// propagate copied values from their sources to destinations in the analysis. struct CopyInfo { HeapType type; Exactness exact; From d7a99c115f243f77feebe61eabb0f47d6f0cb5b7 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 18 Sep 2025 16:21:16 -0700 Subject: [PATCH 16/21] noteCopy dst params --- src/passes/ConstantFieldPropagation.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 4ed22dc5e06..67888bae053 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -493,14 +493,14 @@ struct PCVScanner } void noteCopy(StructGet* get, - Type type, - Index index, - PossibleConstantValues& info) { + Type dstType, + Index dstIndex, + PossibleConstantValues& dstInfo) { auto srcType = get->ref->type.getHeapType(); auto srcExact = get->ref->type.getExactness(); auto srcIndex = get->index; functionCopyInfos[getFunction()][{srcType, srcExact}][srcIndex].insert( - {type.getHeapType(), type.getExactness(), index, get->signed_}); + {dstType.getHeapType(), dstType.getExactness(), dstIndex, get->signed_}); } void noteRead(HeapType type, Index index, PossibleConstantValues& info) { From 50f328fd5423afe53fd045876a0c0c10ca920f28 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 18 Sep 2025 16:21:56 -0700 Subject: [PATCH 17/21] remove redundant function map initialization --- src/passes/ConstantFieldPropagation.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 67888bae053..a11bb536d5f 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -538,9 +538,6 @@ struct ConstantFieldPropagation : public Pass { PCVFunctionStructValuesMap functionNewInfos(*module), functionSetInfos(*module); CopiesFunctionStructValuesMap functionCopyInfos(*module); - for (auto& func : module->functions) { - functionCopyInfos[func.get()]; - } PCVScanner scanner(functionNewInfos, functionSetInfos, functionCopyInfos); auto* runner = getPassRunner(); scanner.run(runner, module); From 7c12a7d5df4f9b17e20c94cd5fb355e4da57a53e Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 18 Sep 2025 16:29:43 -0700 Subject: [PATCH 18/21] test comment --- test/lit/passes/cfp-copies.wast | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/lit/passes/cfp-copies.wast b/test/lit/passes/cfp-copies.wast index c2281adb139..869aa59323f 100644 --- a/test/lit/passes/cfp-copies.wast +++ b/test/lit/passes/cfp-copies.wast @@ -723,6 +723,8 @@ (local $super (ref null $super)) (local $struct (ref null $struct)) (local $sub (ref null $sub)) + ;; We never wrote to $super, so the only value we can read from it is what + ;; was written to $struct (which also may have been a $sub at runtime). (drop (struct.get $super 0 (local.get $super) From 60952dc1339779975181a10ee43fbda3b10a9787 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 18 Sep 2025 16:32:42 -0700 Subject: [PATCH 19/21] fix asan error --- src/passes/ConstantFieldPropagation.cpp | 7 ++++--- test/lit/passes/cfp-copies.wast | 6 +++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index a11bb536d5f..b0f75931959 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -626,10 +626,11 @@ struct ConstantFieldPropagation : public Pass { // For each copy, take the readable values at its source and join them to // the written values at its destination. Record the written values that // change so we can propagate the new information afterward. - for (auto& [srcType, fields] : combinedCopyInfos) { + for (auto& [src, fields] : combinedCopyInfos) { + auto [srcType, srcExact] = src; for (Index srcField = 0; srcField < fields.size(); ++srcField) { - const auto& field = srcType.first.getStruct().fields[srcType.second]; - applyCopiesTo(fields[srcField], field, readable[srcType][srcField]); + const auto& field = srcType.getStruct().fields[srcField]; + applyCopiesTo(fields[srcField], field, readable[src][srcField]); } } while (work.size()) { diff --git a/test/lit/passes/cfp-copies.wast b/test/lit/passes/cfp-copies.wast index 869aa59323f..576c84b0ceb 100644 --- a/test/lit/passes/cfp-copies.wast +++ b/test/lit/passes/cfp-copies.wast @@ -992,7 +992,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $init - ;; Same as above, but now with a different value in $sub1. + ;; Same as above, but now with a different value in the subtype $sub1. We + ;; also add an additional subtype, $sub2, which can still be optimized. (drop (struct.new $other (i32.const 10) @@ -1064,6 +1065,9 @@ (local $struct (ref null $struct)) (local $sub1 (ref null $sub1)) (local $sub2 (ref null $sub2)) + ;; The copy might have written to $sub1 or $sub2, but only $sub2 does not + ;; already have another conflicting value. We can optimize $sub2 but not + ;; $sub1. (drop (struct.get $super 0 (local.get $super) From 9b24942d3a336e59b98f2d9d1ff77a7007c272e7 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 19 Sep 2025 17:42:10 -0700 Subject: [PATCH 20/21] fix and add field copy tests --- test/lit/passes/cfp-copies.wast | 251 +++++++++++++++++++++++++++++++- 1 file changed, 246 insertions(+), 5 deletions(-) diff --git a/test/lit/passes/cfp-copies.wast b/test/lit/passes/cfp-copies.wast index 576c84b0ceb..c0ca947823b 100644 --- a/test/lit/passes/cfp-copies.wast +++ b/test/lit/passes/cfp-copies.wast @@ -4622,7 +4622,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $copy (param $src (ref $struct)) (param $dst (ref $struct)) - ;; Copy from index 1 to index 2 in the same type. + ;; Copy from index 1 to index 2 in the same type. The copied value will + ;; conflict with the initial value and inhibit optimization. (struct.set $struct 2 (local.get $dst) (struct.get $struct 1 @@ -4773,6 +4774,126 @@ ) ) + ;; CHECK: (func $get-2 (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super 2 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 2 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub1 2 + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-2 + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $super 2 + (local.get $super) + ) + ) + (drop + (struct.get $struct 2 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 2 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 2 + (local.get $sub2) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32)) (field (mut i32)) (field (mut i32))))) + (type $super (sub (struct (field (mut i32) (mut i32) (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32)) (field (mut i32)) (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32) (mut i32) (mut i32))))) + ;; CHECK: (type $sub1 (sub $struct (struct (field (mut i32)) (field (mut i32)) (field (mut i32))))) + (type $sub1 (sub $struct (struct (field (mut i32) (mut i32) (mut i32))))) + ;; CHECK: (type $sub2 (sub $struct (struct (field (mut i32)) (field (mut i32)) (field (mut i32))))) + (type $sub2 (sub $struct (struct (field (mut i32) (mut i32) (mut i32))))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $struct) (ref $struct)))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $sub1 + ;; CHECK-NEXT: (i32.const 666) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + ;; Same as above, except now field 2 is initialized with the same value as + ;; field 1, so the copy will not do anything. + (drop + (struct.new $sub1 + (i32.const 666) + (i32.const 10) + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $src (ref $struct)) (param $dst (ref $struct)) + ;; CHECK-NEXT: (struct.set $struct 2 + ;; CHECK-NEXT: (local.get $dst) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $src) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $src (ref $struct)) (param $dst (ref $struct)) + ;; Copy from index 1 to index 2 in the same type. + (struct.set $struct 2 + (local.get $dst) + (struct.get $struct 1 + (local.get $src) + ) + ) + ) + ;; CHECK: (func $get-2 (type $4) ;; CHECK-NEXT: (local $super (ref null $super)) ;; CHECK-NEXT: (local $struct (ref null $struct)) @@ -4809,6 +4930,126 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-2 + (local $super (ref null $super)) + (local $struct (ref null $struct)) + (local $sub1 (ref null $sub1)) + (local $sub2 (ref null $sub2)) + (drop + (struct.get $super 2 + (local.get $super) + ) + ) + (drop + (struct.get $struct 2 + (local.get $struct) + ) + ) + (drop + (struct.get $sub1 2 + (local.get $sub1) + ) + ) + (drop + (struct.get $sub2 2 + (local.get $sub2) + ) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct (field (mut i32)) (field (mut i32)) (field (mut i32))))) + (type $super (sub (struct (field (mut i32) (mut i32) (mut i32))))) + ;; CHECK: (type $struct (sub $super (struct (field (mut i32)) (field (mut i32)) (field (mut i32))))) + (type $struct (sub $super (struct (field (mut i32) (mut i32) (mut i32))))) + ;; CHECK: (type $sub1 (sub $struct (struct (field (mut i32)) (field (mut i32)) (field (mut i32))))) + (type $sub1 (sub $struct (struct (field (mut i32) (mut i32) (mut i32))))) + ;; CHECK: (type $sub2 (sub $struct (struct (field (mut i32)) (field (mut i32)) (field (mut i32))))) + (type $sub2 (sub $struct (struct (field (mut i32) (mut i32) (mut i32))))) + ) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (type $5 (func (param (ref $sub1) (ref $sub1)))) + + ;; CHECK: (func $init (type $4) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $sub1 + ;; CHECK-NEXT: (i32.const 666) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $init + ;; We're back to initializing field 2 with a different value. + (drop + (struct.new $sub1 + (i32.const 666) + (i32.const 10) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $copy (type $5) (param $src (ref $sub1)) (param $dst (ref $sub1)) + ;; CHECK-NEXT: (struct.set $sub1 2 + ;; CHECK-NEXT: (local.get $dst) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $src) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $copy (param $src (ref $sub1)) (param $dst (ref $sub1)) + ;; Copy from index 1 to index 2, but now on $sub1, so we will be able to + ;; optimize $sub2. + (struct.set $sub1 2 + (local.get $dst) + (struct.get $sub1 1 + (local.get $src) + ) + ) + ) + + ;; CHECK: (func $get-2 (type $4) + ;; CHECK-NEXT: (local $super (ref null $super)) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (local $sub1 (ref null $sub1)) + ;; CHECK-NEXT: (local $sub2 (ref null $sub2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super 2 + ;; CHECK-NEXT: (local.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 2 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub1 2 + ;; CHECK-NEXT: (local.get $sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $sub2) @@ -4823,22 +5064,22 @@ (local $sub1 (ref null $sub1)) (local $sub2 (ref null $sub2)) (drop - (struct.get $super 1 + (struct.get $super 2 (local.get $super) ) ) (drop - (struct.get $struct 1 + (struct.get $struct 2 (local.get $struct) ) ) (drop - (struct.get $sub1 1 + (struct.get $sub1 2 (local.get $sub1) ) ) (drop - (struct.get $sub2 1 + (struct.get $sub2 2 (local.get $sub2) ) ) From 019a8104a94ca6e22a283bef0d047c4795d08713 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 23 Sep 2025 07:34:19 -0700 Subject: [PATCH 21/21] principal principle --- test/lit/passes/cfp-copies.wast | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lit/passes/cfp-copies.wast b/test/lit/passes/cfp-copies.wast index c0ca947823b..a5af3e26c82 100644 --- a/test/lit/passes/cfp-copies.wast +++ b/test/lit/passes/cfp-copies.wast @@ -6957,7 +6957,7 @@ ) ) ;; Now the value does not match and we don't optimize, even though we could - ;; in principal. + ;; in principle. (struct.get $A 0 (local.get $A) )