diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 3339df8e4fe..8cbd5d971f0 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2745,6 +2745,7 @@ def write_commands(commands, filename): ("--pick-load-signs",), ("--precompute",), ("--precompute-propagate",), + ("--propagate-struct-gets",), ("--print",), ("--print-boundary",), ("--remove-unused-brs",), diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index c158d5cd31b..8e32db9a69f 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -98,6 +98,7 @@ set(passes_SOURCES PrintCallGraph.cpp PrintFeatures.cpp PrintFunctionMap.cpp + PropagateStructGets.cpp RemoveStart.cpp RoundTrip.cpp SetGlobals.cpp diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp new file mode 100644 index 00000000000..20dd905b6ca --- /dev/null +++ b/src/passes/PropagateStructGets.cpp @@ -0,0 +1,427 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Identify when a GC reference local is always used as "data". If all the +// local.gets and local.tees of a reference local are immediately consumed by +// a struct.get, to read data from the reference, then we can optimize: +// +// x = new Foo(42); +// bar(x.value, x.value); +// +// => +// +// // Store the value directly. +// x_value = (new Foo(42)).value; // do the struct.get earlier, here +// bar(x_value, x_value); +// +// More concretely, when we see all local operations look like this: +// +// (local.set $ref ..) # no constraint on local.set +// (struct.get (local.tee $ref ..)) # consumed by struct.get +// (struct.get (local.get $ref)) # consumed by struct.get +// +// Then we can reshape the code to look like this: +// +// (local.set $ref_field (struct.get (..)) # store data in local +// (local.tee $ref_field (struct.get (..)) # store data in local, get it +// (local.get $ref_field)) # get data from local +// +// In effect we "propagate" struct.gets to earlier positions. +// +// Note that this pattern can handle any input to local.set/local.tee. The only +// requirement is that the local.tees and local.gets are properly consumed (by +// struct.get). For example, we can handle this: +// +// (struct.get (local.tee $ref (local.get $param))) +// +// The tee is of a param, some unknown value, but that is fine: we can still +// optimize this to +// +// (local.tee $ref_field (struct.get (local.get $param))) +// +// When the uses of the local are of its data (immediate struct.get), then we +// can optimize, no matter what the reference is. +// +// This pass is basically a simple form of escape analysis, like Heap2Local, but +// which handles a far more restricted case (where the reference is immediately +// struct.get-ed). The benefit of this pass is that our restricted case can +// handle overlapping allocations, like this: +// +// x = new Foo(42); +// if (condition) x = new Foo(1337); +// bar(x.value, x.value); +// +// Heap2Local cannot optimize this due to the overlapping live ranges (it sees +// the x.value operations are not exclusive to any single allocation). But this +// pass can optimize into this: +// +// x_value = (new Foo(42)).value; +// if (condition) x_value = (new Foo(1337)).value; +// bar(x_value, x_value); +// +// The live ranges of the reference local no longer overlap (only of the data- +// storing local), so Heap2Local can handle both those allocations. +// + +#include "ir/bits.h" +#include "ir/eh-utils.h" +#include "ir/local-graph.h" +#include "ir/properties.h" +#include "pass.h" +#include "wasm-builder.h" +#include "wasm-traversal.h" +#include "wasm.h" + +namespace wasm { + +namespace { + +struct PropagateStructGets + : public WalkerPass> { + bool isFunctionParallel() override { return true; } + + std::unique_ptr create() override { + return std::make_unique(); + } + + // Track the number of gets for each local, so that we can see if all the + // local.gets are consumed by struct.gets. + std::unordered_map numGets; + + // The sets for each local index. + std::unordered_map> sets; + + // The parent struct.gets for each local index's local.gets (for us to + // optimize, each local.get must have such a parent). + std::unordered_map> getParents; + + // The parent struct.get for each tee (for us to optimize, each tee must have + // such a parent). + std::unordered_map teeParents; + + // All relevant struct.gets in the function. We gather these during the walk, + // and then determine which can actually be optimized (the final decision is + // stored in optimizedLocals, for each local, below). + std::vector structGets; + + // Checks if a local index is relevant for us: if it refers to a struct. + bool isStructLocal(Index index) { + auto type = getFunction()->getLocalType(index); + return type.isRef() && type.getHeapType().isStruct(); + } + + void visitLocalGet(LocalGet* curr) { + if (isStructLocal(curr->index)) { + numGets[curr->index]++; + } + } + + void visitLocalSet(LocalSet* curr) { + if (isStructLocal(curr->index)) { + sets[curr->index].push_back(getCurrentPointer()); + } + } + + void visitStructGet(StructGet* curr) { + // Avoid unreachable code. + if (curr->type == Type::unreachable) { + return; + } + + // See if the reference is one of the patterns we recognize. + // TODO fallthrough, but careful of br_if which can escape the value + if (auto* get = curr->ref->dynCast()) { + getParents[get->index].push_back(curr); + structGets.push_back(getCurrentPointer()); + } else if (auto* set = curr->ref->dynCast()) { + teeParents[set] = curr; + structGets.push_back(getCurrentPointer()); + } + // TODO: handle more things flowing into struct.get, like an If + } + + bool hasTry = false; + + void visitTry(Try* curr) { hasTry = true; } + + void visitFunction(Function* func) { + if (structGets.empty()) { + // Nothing was found to be optimizable in the walk. + return; + } + + pickLocalsToOptimize(func); + + if (!optimizedLocals.empty()) { + optimize(func); + } + } + + // For each optimized local, maps each used field index to the new local + // index that stores that field. Note that the inner map is sorted, both for + // deterministic ordering when we create new locals, and also because the + // number of used fields tends to be small (so hashing is overkill). + std::unordered_map> optimizedLocals; + + void pickLocalsToOptimize(Function* func) { + auto* module = getModule(); + Builder builder(*module); + LazyLocalGraph localGraph(func, module); + + for (auto& [localIndex, localSets] : sets) { + if (localSets.size() < 2) { + // We only care about the case of 2 or more sets for a local: otherwise, + // Heap2Local can handle things perfectly for a single set. + continue; + } + + auto getIt = getParents.find(localIndex); + if (getIt == getParents.end()) { + // No struct.get parents for this local's gets. + continue; + } + + // Check if each local.get is consumed by a struct.get, which is the case + // if the counts match. If so, then we understand all the gets, and this + // situation is the pattern we optimize: each local.get is consumed + // by a struct.get. + const auto& getStructGets = getIt->second; + // There must be a struct.get, if we added one for this local. + assert(!getStructGets.empty()); + if (getStructGets.size() != numGets[localIndex]) { + continue; + } + + // Gather all the field indexes read from this local, first from the + // local.gets, and later from the tees. + std::set usedFields; + for (auto* sget : getStructGets) { + usedFields.insert(sget->index); + } + + // Analyze the sets to the local. Things are especially simple if they all + // store a fresh unescaping allocation, that is, a struct.new which does + // not flow out through a tee. + bool ok = true; + bool allSetsAreUnescapingAllocs = true; + for (auto** setp : localSets) { + auto* set = (*setp)->cast(); + if (set->value->type == Type::unreachable) { + // Stop on dead code. + ok = false; + break; + } + if (set->isTee()) { + allSetsAreUnescapingAllocs = false; + auto teeIt = teeParents.find(set); + if (teeIt == teeParents.end()) { + // This tee does not feed into a struct.get, so the allocation(s) + // might escape. + ok = false; + break; + } + usedFields.insert(teeIt->second->index); + } else { + // Not a tee: a normal local.set. + if (!set->value->is()) { + allSetsAreUnescapingAllocs = false; + } + } + } + if (!ok) { + continue; + } + + // Each used field's value must not change between the local.set and + // local.get: either the field is immutable, or every set writes a fresh + // struct.new that only ever flows into this local's struct.gets. (Note + // that struct.*sets* - writes - are ruled out by the fact that we + // analyzed all local.get/tee operations, and all flowed into struct.get.) + // For example: + // + // fun foo(x) { + // var l = x; // local.set + // bar(); // x may be modified here, if foo's caller stashed it + // return l.value; + // } + // + // If we replace `l = x` with `l_value = x.value` on the first line, then + // we need it to remain the same until the return at the end. If x can be + // written to, that is a danger. Note how this cannot happen if each set + // writes a fresh struct.new: no reference arrived from outside, and we do + // not send it out (no local.get exists that is not consumed by a + // struct.get), so no outside writes can occur. If the sets are *not* all + // fresh, then only immutability can help us here. + auto& fields = + func->getLocalType(localIndex).getHeapType().getStruct().fields; + if (!allSetsAreUnescapingAllocs) { + for (auto fieldIndex : usedFields) { + if (fields[fieldIndex].mutable_ != Immutable) { + ok = false; + break; + } + } + if (!ok) { + continue; + } + } + + // If the original local is nullable, then the struct.gets that we add may + // end up reading it and trapping: + // + // x = getRef(); + // foo(); + // bar(x.value, x.value); + // + // This pass optimizes that to: + // + // x_value = getRef().value; + // foo(); + // bar(x_value, x_value); + // + // Note how the `.value` (the struct.get) happens *before* `foo()`, + // after the optimization, so we may have moved a trap around here. It is + // not enough to check trapsNeverHappen, since we are moving the trap to + // an earlier place, and `foo()` might have prevented us from reaching + // there. We can rule most of this problem out by seeing that only non- + // nullable values are written to the local. That leaves only the initial + // value of the local at the function entry, handled later. + for (auto** setp : localSets) { + auto* set = (*setp)->cast(); + if (set->value->type.isNullableRef()) { + ok = false; + break; + } + } + if (!ok) { + continue; + } + + // Finally, if the initial value of the local from the function entry is + // used, do not optimize. For a var, that means the default of zero, which + // will trap, but there is another issue for a param: the value is not + // known, so Heap2Local will not be able to optimize anything after us + // anyhow. + for (auto* sget : getStructGets) { + if (localGraph.getSets(sget->ref->cast()).contains(nullptr)) { + ok = false; + break; + } + } + if (!ok) { + continue; + } + + // Success! The pattern fits, prepare to optimize this local by adding a + // local to store the value of each used field. + for (auto fieldIndex : usedFields) { + optimizedLocals[localIndex][fieldIndex] = + builder.addVar(func, fields[fieldIndex].type); + } + } + } + + void optimize(Function* func) { + auto* module = getModule(); + Builder builder(*module); + + auto getFieldFromLocal = + [&](Index localIndex, Index fieldIndex, bool signed_) -> Expression* { + auto& field = func->getLocalType(localIndex) + .getHeapType() + .getStruct() + .fields[fieldIndex]; + // Read from the appropriate new local, handling sign extension as needed. + auto newLocal = optimizedLocals[localIndex][fieldIndex]; + Expression* ret = + builder.makeLocalGet(newLocal, func->getLocalType(newLocal)); + return Bits::makePackedFieldGet(ret, field, signed_, *module); + }; + + auto makeFieldSets = [&](LocalSet* set, + StructGet* teeParent = nullptr) -> Expression* { + auto localIndex = set->index; + auto localType = func->getLocalType(localIndex); + auto& fields = localType.getHeapType().getStruct().fields; + + // Store the reference, then store struct.gets of all used fields in the + // new locals. + std::vector list = { + builder.makeLocalSet(localIndex, set->value)}; + for (auto [fieldIndex, newLocal] : optimizedLocals[localIndex]) { + auto& field = fields[fieldIndex]; + // Note that the sign does not matter here: we just store the bits, and + // will handle the sign when reading back out (like in Heap2Local). + auto* sget = + builder.makeStructGet(fieldIndex, + builder.makeLocalGet(localIndex, localType), + MemoryOrder::Unordered, + field.type, + false); + list.push_back(builder.makeLocalSet(newLocal, sget)); + } + + // Finally, if this is a tee, then it was consumed by a struct.get, and we + // can read that field and return it. + if (teeParent) { + list.push_back( + getFieldFromLocal(localIndex, teeParent->index, teeParent->signed_)); + } + return builder.makeBlock(list); + }; + + // Rewrite recorded struct.gets in post-order. + for (auto** currp : structGets) { + auto* sget = (*currp)->cast(); + if (auto* get = sget->ref->dynCast()) { + if (!optimizedLocals.contains(get->index)) { + continue; + } + // struct.get of a local.get reads the field from a new local. + *currp = getFieldFromLocal(get->index, sget->index, sget->signed_); + } else if (auto* tee = sget->ref->dynCast()) { + if (!optimizedLocals.contains(tee->index)) { + continue; + } + // struct.get of a local.tee first writes the fields, then reads. + *currp = makeFieldSets(tee, sget); + } + } + + // Rewrite non-tee local.sets of optimized locals: local.sets now store the + // used fields. + for (auto& [x, _] : optimizedLocals) { + for (auto** setp : sets[x]) { + auto* set = (*setp)->cast(); + // Tees were already handled. + if (!set->isTee()) { + *setp = makeFieldSets(set); + } + } + } + + if (hasTry) { + EHUtils::handleBlockNestedPops(func, *getModule()); + } + } +}; + +} // anonymous namespace + +Pass* createPropagateStructGetsPass() { return new PropagateStructGets(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 55b20e05e7d..85b31f00e5f 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -423,6 +423,10 @@ void PassRegistry::registerPasses() { registerPass("propagate-globals-globally", "propagate global values to other globals (useful for tests)", createPropagateGlobalsGloballyPass); + registerPass("propagate-struct-gets", + "propagate struct.get operations to simplify locals and control " + "flow", + createPropagateStructGetsPass); registerPass("remove-non-js-ops", "removes operations incompatible with js", createRemoveNonJSOpsPass); @@ -721,6 +725,7 @@ void PassRunner::addDefaultFunctionOptimizationPasses() { // simplify-locals opens opportunities for optimizations addIfNoDWARFIssues("remove-unused-brs"); if (options.optimizeLevel > 1 && wasm->features.hasGC()) { + addIfNoDWARFIssues("propagate-struct-gets"); addIfNoDWARFIssues("heap2local"); } // if we are willing to work hard, also optimize copies before coalescing diff --git a/src/passes/passes.h b/src/passes/passes.h index 1324edf1af5..7c71b5cd6a9 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -138,6 +138,7 @@ Pass* createPrintCallGraphPass(); Pass* createPrintFeaturesPass(); Pass* createPrintFunctionMapPass(); Pass* createPropagateGlobalsGloballyPass(); +Pass* createPropagateStructGetsPass(); Pass* createRandomizeBranchHintsPass(); Pass* createRemoveNonJSOpsPass(); Pass* createRemoveRelaxedSIMDPass(); diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index ea45a3ce6fe..4575ee159ae 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -400,6 +400,10 @@ ;; CHECK-NEXT: --propagate-globals-globally propagate global values to other ;; CHECK-NEXT: globals (useful for tests) ;; CHECK-EMPTY: +;; CHECK-NEXT: --propagate-struct-gets propagate struct.get operations +;; CHECK-NEXT: to simplify locals and control +;; CHECK-NEXT: flow +;; CHECK-EMPTY: ;; CHECK-NEXT: --remove-empty-function-exports removes exports of empty ;; CHECK-NEXT: functions ;; CHECK-EMPTY: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 90c3b2990a3..82402256b1b 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -445,6 +445,10 @@ ;; CHECK-NEXT: --propagate-globals-globally propagate global values to other ;; CHECK-NEXT: globals (useful for tests) ;; CHECK-EMPTY: +;; CHECK-NEXT: --propagate-struct-gets propagate struct.get operations +;; CHECK-NEXT: to simplify locals and control +;; CHECK-NEXT: flow +;; CHECK-EMPTY: ;; CHECK-NEXT: --remove-empty-function-exports removes exports of empty ;; CHECK-NEXT: functions ;; CHECK-EMPTY: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index fdce36ac338..257ff5f5a70 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -364,6 +364,10 @@ ;; CHECK-NEXT: --propagate-globals-globally propagate global values to other ;; CHECK-NEXT: globals (useful for tests) ;; CHECK-EMPTY: +;; CHECK-NEXT: --propagate-struct-gets propagate struct.get operations +;; CHECK-NEXT: to simplify locals and control +;; CHECK-NEXT: flow +;; CHECK-EMPTY: ;; CHECK-NEXT: --remove-empty-function-exports removes exports of empty ;; CHECK-NEXT: functions ;; CHECK-EMPTY: diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast new file mode 100644 index 00000000000..f342b782555 --- /dev/null +++ b/test/lit/passes/propagate-struct-gets.wast @@ -0,0 +1,3336 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Test the pass by itself, and also followed up by Heap2Local, to verify that +;; we emit things Heap2Local can handle. + +;; RUN: foreach %s %t wasm-opt --propagate-struct-gets -all -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt --propagate-struct-gets --heap2local -all -S -o - | filecheck %s --check-prefix=H2L + +(module + ;; CHECK: (type $struct (struct (field (mut i32)))) + ;; H2L: (type $struct (struct (field (mut i32)))) + (type $struct (struct (field (mut i32)))) + + ;; CHECK: (type $struct-immutable (struct (field i32))) + + ;; CHECK: (type $struct-3fields-mixed (struct (field i32) (field (mut i32)) (field i64))) + ;; H2L: (type $struct-immutable (struct (field i32))) + + ;; H2L: (type $struct-3fields-mixed (struct (field i32) (field (mut i32)) (field i64))) + (type $struct-3fields-mixed (struct (field i32) (field (mut i32)) (field i64))) + + ;; CHECK: (type $struct-2fields (struct (field i32) (field i32))) + + ;; CHECK: (type $struct-packed (struct (field i8) (field i16))) + + ;; CHECK: (type $struct-nn-ref (struct (field (ref struct)))) + ;; H2L: (type $struct-nn-ref (struct (field (ref struct)))) + (type $struct-nn-ref (struct (field (ref struct)))) + + (type $struct-immutable (struct (field i32))) + + ;; H2L: (type $struct-2fields (struct (field i32) (field i32))) + (type $struct-2fields (struct (field i32) (field i32))) + + ;; H2L: (type $struct-packed (struct (field i8) (field i16))) + (type $struct-packed (struct (field i8) (field i16))) + + ;; CHECK: (type $super (sub (struct (field i32)))) + ;; H2L: (type $super (sub (struct (field i32)))) + (type $super (sub (struct (field i32)))) + + ;; CHECK: (type $sub (sub $super (struct (field i32)))) + (type $sub (sub $super (struct (field i32)))) + + ;; CHECK: (tag $tag (type $11) (param i32)) + ;; H2L: (tag $tag (type $9) (param i32)) + (tag $tag (param i32)) + + ;; CHECK: (func $optimize-struct-get-if (type $8) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; H2L: (func $optimize-struct-get-if (type $5) (param $x i32) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (i32.const 0) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (else + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + (func $optimize-struct-get-if (param $x i32) (result i32) + ;; Even when the field is mutable, we can optimize $ref to an i32 local + ;; holding field 0 because every set is a fresh struct.new and every get + ;; reads field 0. Heap2Local can then eliminate both allocations. + (local $ref (ref null $struct)) + (if + (local.get $x) + (then + (local.set $ref + (struct.new_default $struct) + ) + ) + (else + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $no-optimize-struct-get-uninitialized (type $9) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $no-optimize-struct-get-uninitialized (type $6) (param $x i32) (param $y i32) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $y) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (struct.new_default $struct) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (else + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (struct.new $struct + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (struct.get $struct 0 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $no-optimize-struct-get-uninitialized (param $x i32) (param $y i32) (result i32) + ;; Similar to $optimize-struct-get-if, but $ref is not initialized on all + ;; paths reaching the struct.get, so it can be null and trap. Do not optimize + ;; to an i32 local, which would not trap. + (local $ref (ref null $struct)) + (if + (local.get $x) + (then + (if + (local.get $y) + (then + (local.set $ref + (struct.new_default $struct) + ) + ) + (else + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + ) + ) + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $mixture (type $9) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (local $yes (ref null $struct)) + ;; CHECK-NEXT: (local $no (ref null $struct)) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $yes + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $yes) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $yes + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $yes) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $no + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $no + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $no) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $mixture (type $6) (param $x i32) (param $y i32) (result i32) + ;; H2L-NEXT: (local $yes (ref null $struct)) + ;; H2L-NEXT: (local $no (ref null $struct)) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (local $6 i32) + ;; H2L-NEXT: (local $7 i32) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (i32.const 0) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $7 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $6 + ;; H2L-NEXT: (local.get $7) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $6) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $no + ;; H2L-NEXT: (struct.new_default $struct) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $y) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (local.set $no + ;; H2L-NEXT: (struct.new $struct + ;; H2L-NEXT: (i32.const 1337) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: (struct.get $struct 0 + ;; H2L-NEXT: (local.get $no) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $mixture (param $x i32) (param $y i32) (result i32) + ;; $yes can be optimized, but not $no (which can read the default null). + (local $yes (ref null $struct)) + (local $no (ref null $struct)) + (local.set $yes + (struct.new_default $struct) + ) + (if + (local.get $x) + (then + (local.set $yes + (struct.new $struct + (i32.const 42) + ) + ) + (local.set $no + (struct.new_default $struct) + ) + (if + (local.get $y) + (then + (local.set $no + (struct.new $struct + (i32.const 1337) + ) + ) + ) + ) + ) + ) + (i32.add + (struct.get $struct 0 + (local.get $yes) + ) + (struct.get $struct 0 + (local.get $no) + ) + ) + ) + + ;; CHECK: (func $optimize-struct-get-tee-immutable (type $9) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (local $curr (ref $struct-immutable)) + ;; CHECK-NEXT: (local $ref (ref null $struct-immutable)) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (block $err + ;; CHECK-NEXT: (local.set $curr + ;; CHECK-NEXT: (struct.new $struct-immutable + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $err + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (local.get $curr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (struct.get $struct-immutable 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $curr + ;; CHECK-NEXT: (struct.new $struct-immutable + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $err + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (local.get $curr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (struct.get $struct-immutable 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; H2L: (func $optimize-struct-get-tee-immutable (type $6) (param $x i32) (param $y i32) (result i32) + ;; H2L-NEXT: (local $curr (ref $struct-immutable)) + ;; H2L-NEXT: (local $ref (ref null $struct-immutable)) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (local $6 i32) + ;; H2L-NEXT: (local $7 i32) + ;; H2L-NEXT: (local $8 i32) + ;; H2L-NEXT: (block $err + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $6 + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (local.get $6) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (br_if $err + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $8 + ;; H2L-NEXT: (local.get $y) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $7 + ;; H2L-NEXT: (local.get $8) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (br_if $err + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $7) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (return + ;; H2L-NEXT: (i32.const 0) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + (func $optimize-struct-get-tee-immutable (param $x i32) (param $y i32) (result i32) + ;; Two independent allocations in $curr are tee'd into a shared local $ref + ;; inside struct.get of an immutable field before branching to a shared + ;; block that reads the same field from $ref. Optimizing $ref to an i32 + ;; local disentangles the two allocations so Heap2Local can optimize both. + (local $curr (ref $struct-immutable)) + (local $ref (ref null $struct-immutable)) + (block $err + (local.set $curr + (struct.new $struct-immutable + (local.get $x) + ) + ) + (br_if $err + (struct.get $struct-immutable 0 + (local.tee $ref + (local.get $curr) + ) + ) + ) + (local.set $curr + (struct.new $struct-immutable + (local.get $y) + ) + ) + (br_if $err + (struct.get $struct-immutable 0 + (local.tee $ref + (local.get $curr) + ) + ) + ) + (return + (i32.const 0) + ) + ) + (struct.get $struct-immutable 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $optimize-struct-get-tee-immutable-nn-ref (type $12) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; CHECK-NEXT: (local $ref (ref null $struct-nn-ref)) + ;; CHECK-NEXT: (local $4 structref) + ;; CHECK-NEXT: (block $err + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (local.get $b1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (struct.get $struct-nn-ref 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $err + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (local.get $b2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (struct.get $struct-nn-ref 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $err + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $optimize-struct-get-tee-immutable-nn-ref (type $10) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; H2L-NEXT: (local $ref (ref null $struct-nn-ref)) + ;; H2L-NEXT: (local $4 structref) + ;; H2L-NEXT: (block $err + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result (ref struct)) + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (local.get $b1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (struct.get $struct-nn-ref 0 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.as_non_null + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (br_if $err + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result (ref struct)) + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (local.get $b2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (struct.get $struct-nn-ref 0 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.as_non_null + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (br_if $err + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (unreachable) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.as_non_null + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $optimize-struct-get-tee-immutable-nn-ref (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; As above, but the immutable field has a non-nullable reference type + ;; ((ref struct)). The optimized local uses a nullable type (since it is + ;; not dominated in the spec's sense by sets), and we add ref.as_non_null + ;; on the tees and get so validation succeeds across blocks. + (local $ref (ref null $struct-nn-ref)) + (block $err + (drop + (struct.get $struct-nn-ref 0 + (local.tee $ref + (local.get $b1) + ) + ) + ) + (br_if $err + (local.get $x) + ) + (drop + (struct.get $struct-nn-ref 0 + (local.tee $ref + (local.get $b2) + ) + ) + ) + (br_if $err + (local.get $x) + ) + (unreachable) + ) + (struct.get $struct-nn-ref 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $no-optimize-struct-get-tee-mutable (type $13) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (block $err + ;; CHECK-NEXT: (br_if $err + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (local.get $s1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $err + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (local.get $s2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $no-optimize-struct-get-tee-mutable (type $11) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L-NEXT: (block $err + ;; H2L-NEXT: (br_if $err + ;; H2L-NEXT: (struct.get $struct 0 + ;; H2L-NEXT: (local.tee $ref + ;; H2L-NEXT: (local.get $s1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (br_if $err + ;; H2L-NEXT: (struct.get $struct 0 + ;; H2L-NEXT: (local.tee $ref + ;; H2L-NEXT: (local.get $s2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (return + ;; H2L-NEXT: (i32.const 0) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (struct.get $struct 0 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $no-optimize-struct-get-tee-mutable (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; Do not optimize when tees write arbitrary struct references and the field + ;; is mutable: the references may be modified by something else with a + ;; reference. + (local $ref (ref null $struct)) + (block $err + (br_if $err + (struct.get $struct 0 + (local.tee $ref + (local.get $s1) + ) + ) + ) + (br_if $err + (struct.get $struct 0 + (local.tee $ref + (local.get $s2) + ) + ) + ) + (return + (i32.const 0) + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $optimize-struct-get-different-fields (type $8) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct-2fields)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new_default $struct-2fields) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct-2fields 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $struct-2fields 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct-2fields + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct-2fields 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $struct-2fields 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $optimize-struct-get-different-fields (type $5) (param $x i32) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct-2fields)) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (local $6 i32) + ;; H2L-NEXT: (local $7 i32) + ;; H2L-NEXT: (local $8 i32) + ;; H2L-NEXT: (local $9 i32) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (i32.const 0) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (i32.const 0) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (else + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $8 + ;; H2L-NEXT: (i32.const 10) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $9 + ;; H2L-NEXT: (i32.const 20) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $6 + ;; H2L-NEXT: (local.get $8) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $7 + ;; H2L-NEXT: (local.get $9) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $6) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $7) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $optimize-struct-get-different-fields (param $x i32) (result i32) + ;; Optimize when gets of $ref read different fields (field 0 and field 1). + (local $ref (ref null $struct-2fields)) + (if + (local.get $x) + (then + (local.set $ref + (struct.new_default $struct-2fields) + ) + ) + (else + (local.set $ref + (struct.new $struct-2fields + (i32.const 10) + (i32.const 20) + ) + ) + ) + ) + (i32.add + (struct.get $struct-2fields 0 + (local.get $ref) + ) + (struct.get $struct-2fields 1 + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $optimize-multiple-fields-tee-partial (type $14) (param $x i32) (param $y i32) (result i64) + ;; CHECK-NEXT: (local $curr (ref $struct-3fields-mixed)) + ;; CHECK-NEXT: (local $ref (ref null $struct-3fields-mixed)) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i64) + ;; CHECK-NEXT: (block $err + ;; CHECK-NEXT: (local.set $curr + ;; CHECK-NEXT: (struct.new $struct-3fields-mixed + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: (i64.const 1000) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $err + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (local.get $curr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (struct.get $struct-3fields-mixed 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (struct.get $struct-3fields-mixed 2 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $curr + ;; CHECK-NEXT: (struct.new $struct-3fields-mixed + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i32.const 200) + ;; CHECK-NEXT: (i64.const 2000) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $err + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (local.get $curr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (struct.get $struct-3fields-mixed 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (struct.get $struct-3fields-mixed 2 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.add + ;; CHECK-NEXT: (i64.extend_i32_s + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $optimize-multiple-fields-tee-partial (type $12) (param $x i32) (param $y i32) (result i64) + ;; H2L-NEXT: (local $curr (ref $struct-3fields-mixed)) + ;; H2L-NEXT: (local $ref (ref null $struct-3fields-mixed)) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i64) + ;; H2L-NEXT: (local $6 i32) + ;; H2L-NEXT: (local $7 i32) + ;; H2L-NEXT: (local $8 i64) + ;; H2L-NEXT: (local $9 i32) + ;; H2L-NEXT: (local $10 i32) + ;; H2L-NEXT: (local $11 i64) + ;; H2L-NEXT: (local $12 i32) + ;; H2L-NEXT: (local $13 i32) + ;; H2L-NEXT: (local $14 i64) + ;; H2L-NEXT: (local $15 i32) + ;; H2L-NEXT: (local $16 i32) + ;; H2L-NEXT: (local $17 i64) + ;; H2L-NEXT: (block $err + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $9 + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $10 + ;; H2L-NEXT: (i32.const 100) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $11 + ;; H2L-NEXT: (i64.const 1000) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $6 + ;; H2L-NEXT: (local.get $9) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $7 + ;; H2L-NEXT: (local.get $10) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $8 + ;; H2L-NEXT: (local.get $11) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (br_if $err + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $6) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (block (result i64) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $8) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $15 + ;; H2L-NEXT: (local.get $y) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $16 + ;; H2L-NEXT: (i32.const 200) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $17 + ;; H2L-NEXT: (i64.const 2000) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $12 + ;; H2L-NEXT: (local.get $15) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $13 + ;; H2L-NEXT: (local.get $16) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $14 + ;; H2L-NEXT: (local.get $17) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (br_if $err + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $12) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (block (result i64) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $14) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (return + ;; H2L-NEXT: (i64.const 0) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i64.add + ;; H2L-NEXT: (i64.extend_i32_s + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $optimize-multiple-fields-tee-partial (param $x i32) (param $y i32) (result i64) + ;; $struct-3fields-mixed has 3 fields: field 0 (immutable i32), field 1 + ;; (mutable i32), and field 2 (immutable i64). Only fields 0 and 2 are used + ;; here (field 0 at the tees and a get, field 2 at a get), so field 1's + ;; mutability does not prevent optimization, and no local is created for + ;; field 1. + (local $curr (ref $struct-3fields-mixed)) + (local $ref (ref null $struct-3fields-mixed)) + (block $err + (local.set $curr + (struct.new $struct-3fields-mixed + (local.get $x) + (i32.const 100) + (i64.const 1000) + ) + ) + (br_if $err + (struct.get $struct-3fields-mixed 0 + (local.tee $ref + (local.get $curr) + ) + ) + ) + (local.set $curr + (struct.new $struct-3fields-mixed + (local.get $y) + (i32.const 200) + (i64.const 2000) + ) + ) + (br_if $err + (struct.get $struct-3fields-mixed 0 + (local.tee $ref + (local.get $curr) + ) + ) + ) + (return + (i64.const 0) + ) + ) + (i64.add + (i64.extend_i32_s + (struct.get $struct-3fields-mixed 0 + (local.get $ref) + ) + ) + (struct.get $struct-3fields-mixed 2 + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $no-optimize-multiple-fields-tee-one-mutable (type $15) (param $s1 (ref $struct-3fields-mixed)) (param $s2 (ref $struct-3fields-mixed)) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct-3fields-mixed)) + ;; CHECK-NEXT: (block $err + ;; CHECK-NEXT: (br_if $err + ;; CHECK-NEXT: (struct.get $struct-3fields-mixed 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (local.get $s1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $err + ;; CHECK-NEXT: (struct.get $struct-3fields-mixed 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (local.get $s2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct-3fields-mixed 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $no-optimize-multiple-fields-tee-one-mutable (type $13) (param $s1 (ref $struct-3fields-mixed)) (param $s2 (ref $struct-3fields-mixed)) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct-3fields-mixed)) + ;; H2L-NEXT: (block $err + ;; H2L-NEXT: (br_if $err + ;; H2L-NEXT: (struct.get $struct-3fields-mixed 0 + ;; H2L-NEXT: (local.tee $ref + ;; H2L-NEXT: (local.get $s1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (br_if $err + ;; H2L-NEXT: (struct.get $struct-3fields-mixed 0 + ;; H2L-NEXT: (local.tee $ref + ;; H2L-NEXT: (local.get $s2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (return + ;; H2L-NEXT: (i32.const 0) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (struct.get $struct-3fields-mixed 1 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $no-optimize-multiple-fields-tee-one-mutable (param $s1 (ref $struct-3fields-mixed)) (param $s2 (ref $struct-3fields-mixed)) (result i32) + ;; As above, but now the mutable field 1 is also read, so we cannot + ;; optimize. + (local $ref (ref null $struct-3fields-mixed)) + (block $err + (br_if $err + (struct.get $struct-3fields-mixed 0 + (local.tee $ref + (local.get $s1) + ) + ) + ) + (br_if $err + (struct.get $struct-3fields-mixed 0 + (local.tee $ref + (local.get $s2) + ) + ) + ) + (return + (i32.const 0) + ) + ) + (struct.get $struct-3fields-mixed 1 + (local.get $ref) + ) + ) + + ;; CHECK: (func $optimize-packed-signed-and-unsigned (type $8) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct-packed)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct-packed + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: (i32.const -2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get_u $struct-packed 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get_u $struct-packed 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct-packed + ;; CHECK-NEXT: (i32.const 300) + ;; CHECK-NEXT: (i32.const 70000) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get_u $struct-packed 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get_u $struct-packed 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (i32.const 16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $optimize-packed-signed-and-unsigned (type $5) (param $x i32) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct-packed)) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (local $6 i32) + ;; H2L-NEXT: (local $7 i32) + ;; H2L-NEXT: (local $8 i32) + ;; H2L-NEXT: (local $9 i32) + ;; H2L-NEXT: (local $10 i32) + ;; H2L-NEXT: (local $11 i32) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $6 + ;; H2L-NEXT: (i32.const -1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $7 + ;; H2L-NEXT: (i32.const -2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (local.get $6) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (local.get $7) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.and + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: (i32.const 255) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.and + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: (i32.const 65535) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (else + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $10 + ;; H2L-NEXT: (i32.const 300) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $11 + ;; H2L-NEXT: (i32.const 70000) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $8 + ;; H2L-NEXT: (local.get $10) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $9 + ;; H2L-NEXT: (local.get $11) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.and + ;; H2L-NEXT: (local.get $8) + ;; H2L-NEXT: (i32.const 255) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.and + ;; H2L-NEXT: (local.get $9) + ;; H2L-NEXT: (i32.const 65535) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (i32.shr_s + ;; H2L-NEXT: (i32.shl + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: (i32.const 24) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.const 24) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.and + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: (i32.const 255) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.shr_s + ;; H2L-NEXT: (i32.shl + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: (i32.const 16) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.const 16) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $optimize-packed-signed-and-unsigned (param $x i32) (result i32) + ;; A packed field (field 0, i8) is read with both struct.get_s and + ;; struct.get_u, alongside a second packed field (field 1, i16). + (local $ref (ref null $struct-packed)) + (if + (local.get $x) + (then + (local.set $ref + (struct.new $struct-packed + (i32.const -1) + (i32.const -2) + ) + ) + ) + (else + (local.set $ref + (struct.new $struct-packed + (i32.const 300) + (i32.const 70000) + ) + ) + ) + ) + ;; The proper sign-extension (or not) will be used for each get. + (i32.add + (i32.add + (struct.get_s $struct-packed 0 + (local.get $ref) + ) + (struct.get_u $struct-packed 0 + (local.get $ref) + ) + ) + (struct.get_s $struct-packed 1 + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $no-single-set (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $no-single-set (type $3) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L-NEXT: (local $1 i32) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $1 + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $no-single-set (result i32) + ;; We leave a single local.set for Heap2Local (that situation has no + ;; overlapping live ranges etc.), and do not optimize. + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + (i32.add + (struct.get $struct 0 + (local.get $ref) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $unreachable (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $unreachable (type $3) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L-NEXT: (local $1 i32) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $1 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $1 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (local.get $1) + ;; H2L-NEXT: (block ;; (replaces unreachable StructGet we can't emit) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (unreachable) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (unreachable) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $unreachable (result i32) + ;; We ignore unreachable struct.gets code, but still optimize the rest. + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + (i32.add + (struct.get $struct 0 + (local.get $ref) + ) + (struct.get $struct 0 + (unreachable) + ) + ) + ) + + ;; CHECK: (func $unreachable-2 (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $unreachable-2 (type $3) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L-NEXT: (local $1 i32) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (block ;; (replaces unreachable StructNew we can't emit) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (unreachable) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (unreachable) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $1 + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (struct.get $struct 0 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (struct.get $struct 0 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $unreachable-2 (result i32) + ;; Now a struct.new and local.set are unreachable. We do not optimize here. + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (unreachable) + ) + ) + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + (i32.add + (struct.get $struct 0 + (local.get $ref) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $no-struct.get (type $16) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $no-struct.get (type $15) + ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L-NEXT: (local $1 i32) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $1 + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $no-struct.get + ;; No struct.gets for our local. We do not optimize (no struct.gets to + ;; propagate). + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + ) + + ;; CHECK: (func $extra-local.get (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $extra-local.get (type $3) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L-NEXT: (local $1 i32) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $1 + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $extra-local.get (result i32) + (local $ref (ref null $struct)) + ;; An extra local.get - which is not consumed by a struct.get - stops us. + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + (drop + (local.get $ref) ;; this stops us + ) + (i32.add + (struct.get $struct 0 + (local.get $ref) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $extra-local.tee (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $extra-local.tee (type $3) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L-NEXT: (local $1 i32) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (local $6 i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $1 + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $6 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (local.get $6) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $extra-local.tee (result i32) + (local $ref (ref null $struct)) + ;; An extra local.tee - which is not consumed by a struct.get - stops us. + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + (drop + (local.tee $ref ;; this stops us + (struct.new $struct + (i32.const 42) + ) + ) + ) + (i32.add + (struct.get $struct 0 + (local.get $ref) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $only-local.tee (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct-2fields)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct-2fields + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (struct.get $struct-2fields 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct-2fields 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct-2fields + ;; CHECK-NEXT: (i32.const 420) + ;; CHECK-NEXT: (i32.const 13370) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (struct.get $struct-2fields 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct-2fields 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $only-local.tee (type $3) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct-2fields)) + ;; H2L-NEXT: (local $1 i32) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (local $6 i32) + ;; H2L-NEXT: (local $7 i32) + ;; H2L-NEXT: (local $8 i32) + ;; H2L-NEXT: (local $9 i32) + ;; H2L-NEXT: (local $10 i32) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $6 + ;; H2L-NEXT: (i32.const 1337) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (local.get $6) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $1 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $9 + ;; H2L-NEXT: (i32.const 420) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $10 + ;; H2L-NEXT: (i32.const 13370) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $7 + ;; H2L-NEXT: (local.get $9) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $8 + ;; H2L-NEXT: (local.get $10) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $1 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $7) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $8) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $only-local.tee (result i32) + (local $ref (ref null $struct-2fields)) + ;; One field only used by a local.get, one by a local.tee. Both are used. + (local.set $ref + (struct.new $struct-2fields + (i32.const 42) + (i32.const 1337) + ) + ) + (i32.add + (struct.get $struct-2fields 0 + (local.tee $ref + (struct.new $struct-2fields + (i32.const 420) + (i32.const 13370) + ) + ) + ) + (struct.get $struct-2fields 1 + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $set-unknown-mut (type $17) (param $param (ref $struct)) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $set-unknown-mut (type $16) (param $param (ref $struct)) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (local.get $param) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $set-unknown-mut (param $param (ref $struct)) (result i32) + (local $ref (ref null $struct)) + ;; The first set is of an unknown, and the field is mutable, so we do not + ;; optimize. + (local.set $ref + (local.get $param) + ) + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + (i32.add + (struct.get $struct 0 + (local.get $ref) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $set-unknown-immutable (type $18) (param $param (ref $struct-immutable)) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct-immutable)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct-immutable 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct-immutable + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct-immutable 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $set-unknown-immutable (type $17) (param $param (ref $struct-immutable)) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct-immutable)) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (local.get $param) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (struct.get $struct-immutable 0 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $set-unknown-immutable (param $param (ref $struct-immutable)) (result i32) + (local $ref (ref null $struct-immutable)) + ;; As above, but now the field is immutable, so we optimize. + (local.set $ref + (local.get $param) + ) + (local.set $ref + (struct.new $struct-immutable + (i32.const 42) + ) + ) + (i32.add + (struct.get $struct-immutable 0 + (local.get $ref) + ) + (struct.get $struct-immutable 0 + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $set-unknown-mutable (type $19) (param $param (ref null $struct-immutable)) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct-immutable)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct-immutable + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (struct.get $struct-immutable 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct-immutable 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $set-unknown-mutable (type $18) (param $param (ref null $struct-immutable)) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct-immutable)) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (local.get $param) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $set-unknown-mutable (param $param (ref null $struct-immutable)) (result i32) + (local $ref (ref null $struct-immutable)) + ;; The first set is of a nullable value, so we might trap. We do not + ;; optimize. + (local.set $ref + (local.get $param) + ) + (local.set $ref + (struct.new $struct-immutable + (i32.const 42) + ) + ) + (i32.add + (struct.get $struct-immutable 0 + (local.get $ref) + ) + (struct.get $struct-immutable 0 + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $no-param-value (type $10) (param $ref (ref $struct)) (param $x i32) (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $no-param-value (type $8) (param $ref (ref $struct)) (param $x i32) (result i32) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (i32.const 1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (struct.new $struct + ;; H2L-NEXT: (i32.const 2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (struct.get $struct 0 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $no-param-value (param $ref (ref $struct)) (param $x i32) (result i32) + ;; Do not optimize here even though the param is non-nullable, as the param + ;; may be used. In such cases Heap2Local will not be able to optimize + ;; anyhow. + (if + (local.get $x) + (then + (local.set $ref + (struct.new $struct (i32.const 1)) + ) + (local.set $ref + (struct.new $struct (i32.const 2)) + ) + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $yes-overwritten-param-value (type $10) (param $ref (ref $struct)) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; H2L: (func $yes-overwritten-param-value (type $8) (param $ref (ref $struct)) (param $x i32) (result i32) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (local $6 i32) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (i32.const 1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $6 + ;; H2L-NEXT: (i32.const 2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (local.get $6) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + (func $yes-overwritten-param-value (param $ref (ref $struct)) (param $x i32) (result i32) + ;; As above, but the param is overwritten, and never used. We can optimize. + ;; That is, this checks that we can optimize a param and not just a var. + (local.set $ref + (struct.new $struct (i32.const 1)) + ) + (if + (local.get $x) + (then + (local.set $ref + (struct.new $struct (i32.const 2)) + ) + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $subtyping (type $4) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $super)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $super + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (struct.get $super 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $sub + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (struct.get $super 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $subtyping (type $3) (result i32) + ;; H2L-NEXT: (local $ref (ref null $super)) + ;; H2L-NEXT: (local $1 i32) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $1 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $1 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (local.get $1) + ;; H2L-NEXT: (local.get $1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $subtyping (result i32) + (local $ref (ref null $super)) + ;; Subtyping is not a problem: one set here is of super, and one of sub, + ;; but the local is super and only that can be read. + (local.set $ref + (struct.new $super + (i32.const 42) + ) + ) + (local.set $ref + (struct.new $sub + (i32.const 42) + ) + ) + (i32.add + (struct.get $super 0 + (local.get $ref) + ) + (struct.get $super 0 + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $try-pop (type $8) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; H2L: (func $try-pop (type $5) (param $x i32) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (local $6 i32) + ;; H2L-NEXT: (local $7 i32) + ;; H2L-NEXT: (local $8 i32) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (i32.const 0) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (try + ;; H2L-NEXT: (do + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $6 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (local.get $6) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (catch $tag + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (pop i32) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $8 + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $7 + ;; H2L-NEXT: (local.get $8) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $7) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (nop) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + (func $try-pop (param $x i32) (result i32) + ;; After we optimize here, we must fix up pops. + (local $ref (ref null $struct)) + (local.set $ref + (struct.new_default $struct) + ) + (try + (do + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + ) + (catch $tag + (local.set $ref + (struct.new $struct + (pop i32) + ) + ) + (nop) ;; add a second instruction so that there is a block here, + ;; which forces the fixup + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $nullref (type $4) (result i32) + ;; CHECK-NEXT: (local $ref nullref) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $nullref (type $3) (result i32) + ;; H2L-NEXT: (local $ref nullref) + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (block ;; (replaces unreachable StructGet we can't emit) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (unreachable) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $nullref (result i32) + ;; We should ignore struct.get operations on a nullref. We only optimize + ;; locals with struct types. + (local $ref nullref) + (local.set $ref + (ref.null none) + ) + (local.set $ref + (ref.null none) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $unoptimized-tee (type $21) (param $x i32) (result anyref) + ;; CHECK-NEXT: (local $ref (ref null $struct-immutable)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new_default $struct-immutable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct-immutable 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new $struct-immutable + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; H2L: (func $unoptimized-tee (type $20) (param $x i32) (result anyref) + ;; H2L-NEXT: (local $ref (ref null $struct-immutable)) + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (struct.new_default $struct-immutable) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (struct.get $struct-immutable 0 + ;; H2L-NEXT: (local.tee $ref + ;; H2L-NEXT: (struct.new $struct-immutable + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + (func $unoptimized-tee (param $x i32) (result anyref) + (local $ref (ref null $struct-immutable)) + ;; The local.get at the end escapes, so we cannot optimize here. We should + ;; do nothing with the local.set or local.tee. + (local.set $ref + (struct.new_default $struct-immutable) + ) + (if + (local.get $x) + (then + (drop + (struct.get $struct-immutable 0 + (local.tee $ref + (struct.new $struct-immutable + (i32.const 42) + ) + ) + ) + ) + ) + ) + (local.get $ref) + ) + + ;; CHECK: (func $struct.get-unknown-child (type $8) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (call $get-struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; H2L: (func $struct.get-unknown-child (type $5) (param $x i32) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (i32.const 0) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (struct.get $struct 0 + ;; H2L-NEXT: (call $get-struct) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + (func $struct.get-unknown-child (param $x i32) (result i32) + (local $ref (ref null $struct)) + ;; We can optimize here, and do not error on an extra struct.get with no + ;; relevant child. + (local.set $ref + (struct.new_default $struct) + ) + (if + (local.get $x) + (then + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + ) + ) + (drop + (struct.get $struct 0 + (call $get-struct) ;; this child is not a local.tee or a local.get + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $get-struct (type $22) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; H2L: (func $get-struct (type $21) (result (ref $struct)) + ;; H2L-NEXT: (struct.new_default $struct) + ;; H2L-NEXT: ) + (func $get-struct (result (ref $struct)) + (struct.new_default $struct) + ) +) + +;; Shared data is optimizable too. +(module + ;; CHECK: (type $struct-mut (shared (struct (field (mut i32))))) + + ;; CHECK: (type $struct (shared (struct (field i32)))) + ;; H2L: (type $struct-mut (shared (struct (field (mut i32))))) + + ;; H2L: (type $struct (shared (struct (field i32)))) + (type $struct (shared (struct (field i32)))) + + (type $struct-mut (shared (struct (field (mut i32))))) + + ;; CHECK: (func $struct.get-atomic (type $2) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct-mut)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new_default $struct-mut) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct-mut 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct-mut + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct-mut 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; H2L: (func $struct.get-atomic (type $2) (param $x i32) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct-mut)) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result (ref null (shared none))) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (i32.const 0) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null (shared none)) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null (shared none)) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result (ref null (shared none))) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null (shared none)) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null (shared none)) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + (func $struct.get-atomic (param $x i32) (result i32) + (local $ref (ref null $struct-mut)) + ;; That the data is shared and accessed atomically is not a problem: while + ;; the field is mutable, we see all the allocations. + (local.set $ref + (struct.new_default $struct-mut) + ) + (if + (local.get $x) + (then + (local.set $ref + (struct.new $struct-mut + (i32.const 42) + ) + ) + ) + ) + (struct.atomic.get acqrel $struct-mut 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $struct.get-atomic-param (type $3) (param $x i32) (param $p (ref $struct)) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; H2L: (func $struct.get-atomic-param (type $3) (param $x i32) (param $p (ref $struct)) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (block + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (local.get $p) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (struct.get $struct 0 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result (ref null (shared none))) + ;; H2L-NEXT: (local.set $5 + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (local.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null (shared none)) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $3 + ;; H2L-NEXT: (block (result i32) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.null (shared none)) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + (func $struct.get-atomic-param (param $x i32) (param $p (ref $struct)) (result i32) + (local $ref (ref null $struct)) + ;; Now we do not see one set, which writes a param. But the field is + ;; immutable, so we still optimize. + (local.set $ref + (local.get $p) + ) + (if + (local.get $x) + (then + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + ) + ) + (struct.atomic.get acqrel $struct 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $struct.get-atomic-param-mut (type $4) (param $x i32) (param $p (ref $struct-mut)) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct-mut)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct-mut + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.atomic.get acqrel $struct-mut 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $struct.get-atomic-param-mut (type $4) (param $x i32) (param $p (ref $struct-mut)) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct-mut)) + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (local.get $p) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (struct.new $struct-mut + ;; H2L-NEXT: (i32.const 42) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (struct.atomic.get acqrel $struct-mut 0 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $struct.get-atomic-param-mut (param $x i32) (param $p (ref $struct-mut)) (result i32) + (local $ref (ref null $struct-mut)) + ;; Now we do not see one set, and also the field is mutable. We do not + ;; optimize. + (local.set $ref + (local.get $p) + ) + (if + (local.get $x) + (then + (local.set $ref + (struct.new $struct-mut + (i32.const 42) + ) + ) + ) + ) + (struct.atomic.get acqrel $struct-mut 0 + (local.get $ref) + ) + ) +) +