diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 692d0c0746e..671505bd0a7 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2766,6 +2766,7 @@ def write_commands(commands, filename): ("--flatten", "--rereloop",), ("--roundtrip",), ("--rse",), + ("--scalarize-struct-locals",), ("--signature-pruning",), ("--signature-refining",), ("--simplify-globals",), diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index bcaffb8ac1b..db0009d0c7c 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -134,6 +134,7 @@ set(passes_SOURCES TypeMerging.cpp TypeSSA.cpp SafeHeap.cpp + ScalarizeStructLocals.cpp SeparateDataSegments.cpp SimplifyGlobals.cpp SimplifyLocals.cpp diff --git a/src/passes/ScalarizeStructLocals.cpp b/src/passes/ScalarizeStructLocals.cpp new file mode 100644 index 00000000000..658de011a2c --- /dev/null +++ b/src/passes/ScalarizeStructLocals.cpp @@ -0,0 +1,410 @@ +/* + * 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. + */ + +// +// Simple multi-allocation escape analysis. +// +// This pass identifies a simple case of refs not escaping, like Heap2Local, but +// allowing more than a single allocation (struct.new), which is better, and +// which handles a far simpler-to-identify situation, which is worse. To see the +// motivation, consider overlapping allocations like this: +// +// x = new Foo(42); +// if (condition) x = new Foo(1337); +// else x.value = -1; +// 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; +// else x_value = -1; +// bar(x_value, x_value); +// +// Note how we now operate on x_value, a new local with data. Basically, each +// struct.get/struct.set is turned into an operation on that new local. This is +// safe despite the multiple allocations because the local x is always used +// immediately, as data: rather than store a reference, we can just focus on the +// data. Nothing observes x as a reference (no ref.eq), and it does not escape, +// so this is valid. Heap2Local will then see 'untangled' allocations, each one +// immediately consumed, and remove them all. +// +// Note that Heap2Local could safely optimize in this example, even before our +// changes: while the `x.value` operations are not exclusive to allocations, the +// live ranges of the allocations join exactly there - they do not overlap in a +// dangerous way. Here is an actually dangerous example: +// +// (local.set $a +// (struct.new $struct +// (..value..) +// ) +// ) +// (struct.get $ref 0 +// (select +// (local.get $a) +// (local.tee $a +// (struct.new_default $struct) +// ) +// (..condition..) +// ) +// ) +// +// The struct.get at the end may read ..value.. from set at the top, or it may +// read the default value from the tee at the bottom. But here we cannot +// optimize while using a single local `a_value` because of the live range +// overlap: both allocations are alive as the select executes, and it picks +// between them, so we must store both of them somewhere in the interim. +// Detecting such overlaps is not trivial as it involves locals as well as the +// wasm value stack, which is why this pass is useful, as it handles common and +// obviously-optimizable cases. +// + +#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 ScalarizeStructLocals + : 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.get/sets. + std::unordered_map numGets; + + // The sets for each local index. We store this in an ordered set so that + // later iteration is deterministic. + std::map> sets; + + // The parent struct.get/sets 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 the relevant struct.gets/sets. 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 structOperations; + + // 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) { handleStructOperation(curr); } + void visitStructSet(StructSet* curr) { handleStructOperation(curr); } + + template void handleStructOperation(T* 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->template dynCast()) { + getParents[get->index].push_back(curr); + structOperations.push_back(getCurrentPointer()); + } else if (auto* tee = curr->ref->template dynCast()) { + // TODO: Handle a tee for struct.set. This is a little complex to handle + // below, and not really needed, since + // + // (struct.set $A k + // (struct.new $A ..) + // (value) + // ) + // + // can be folded into a single struct.new (that is, we are writing all the + // fields, then writing an additional one, and other passes make us do + // them all at once anyhow). + if (!curr->template is()) { + teeParents[tee] = curr; + structOperations.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 (structOperations.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/set parents for this local's gets. + continue; + } + + // Check if each local.get is consumed by a struct.get/set, 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/set. + const auto& getStructOperations = getIt->second; + // There must be a struct.get/set, if we added one for this local. + assert(!getStructOperations.empty()); + if (getStructOperations.size() != numGets[localIndex]) { + continue; + } + + auto getIndex = [](Expression* curr) { + if (auto* get = curr->dynCast()) { + return get->index; + } else if (auto* set = curr->dynCast()) { + return set->index; + } else { + WASM_UNREACHABLE("bad op"); + } + }; + + // Gather all the field indexes read from this local, first from the + // local.gets, and later from the tees. + std::set usedFields; + for (auto* op : getStructOperations) { + usedFields.insert(getIndex(op)); + } + + // Analyze the sets to the local: a struct.new must be written in each. + bool ok = true; + for (auto** setp : localSets) { + auto* set = (*setp)->cast(); + if (set->value->type == Type::unreachable) { + // Stop on dead code. + ok = false; + break; + } + if (!set->value->is()) { + // Stop on writing an unknown reference that is not a struct.new. + ok = false; + break; + } + if (set->isTee()) { + auto teeIt = teeParents.find(set); + if (teeIt == teeParents.end()) { + // This tee does not feed into a struct.get/set, so the + // allocation(s) might escape. + ok = false; + break; + } + usedFields.insert(getIndex(teeIt->second)); + } + } + 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. TODO tnh? + for (auto* op : getStructOperations) { + Expression* ref; + if (auto* get = op->dynCast()) { + ref = get->ref; + } else if (auto* set = op->dynCast()) { + ref = set->ref; + } else { + WASM_UNREACHABLE("bad op"); + } + if (localGraph.getSets(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. + auto& fields = + func->getLocalType(localIndex).getHeapType().getStruct().fields; + for (auto fieldIndex : usedFields) { + optimizedLocals[localIndex][fieldIndex] = + builder.addVar(func, fields[fieldIndex].type); + } + } + } + + void optimize(Function* func) { + struct Optimizer : PostWalker { + ScalarizeStructLocals& parent; + + Optimizer(ScalarizeStructLocals& parent) : parent(parent) {} + + void visitLocalSet(LocalSet* curr) { + if (!curr->isTee() && parent.optimizedLocals.contains(curr->index)) { + // local.set now stores the used fields (tee is handled below). + replaceCurrent(makeFieldSets(curr)); + } + } + + void visitStructGet(StructGet* curr) { + if (auto* get = curr->ref->dynCast()) { + if (parent.optimizedLocals.contains(get->index)) { + // struct.get of a local.get reads the field from a new local. + replaceCurrent( + getFieldFromLocal(get->index, curr->index, curr->signed_)); + } + } else if (auto* tee = curr->ref->dynCast()) { + if (parent.optimizedLocals.contains(tee->index)) { + // struct.get of a local.tee first writes the fields, then reads. + replaceCurrent(makeFieldSets(tee, curr)); + } + } + } + + void visitStructSet(StructSet* curr) { + if (auto* get = curr->ref->dynCast()) { + if (parent.optimizedLocals.contains(get->index)) { + // struct.set of a local.get writes the field into a new local. + replaceCurrent( + setFieldToLocal(get->index, curr->index, curr->value)); + } + } + } + + Expression* + getFieldFromLocal(Index localIndex, Index fieldIndex, bool signed_) { + auto& field = getFunction() + ->getLocalType(localIndex) + .getHeapType() + .getStruct() + .fields[fieldIndex]; + // Read from the appropriate new local, handling sign extension as + // needed. + auto newLocal = parent.optimizedLocals[localIndex][fieldIndex]; + Builder builder(*getModule()); + Expression* ret = + builder.makeLocalGet(newLocal, getFunction()->getLocalType(newLocal)); + return Bits::makePackedFieldGet(ret, field, signed_, *getModule()); + } + + Expression* + setFieldToLocal(Index localIndex, Index fieldIndex, Expression* value) { + // Read from the appropriate new local, handling sign extension as + // needed. + auto newLocal = parent.optimizedLocals[localIndex][fieldIndex]; + Builder builder(*getModule()); + return builder.makeLocalSet(newLocal, value); + } + + Expression* makeFieldSets(LocalSet* set, StructGet* teeParent = nullptr) { + auto localIndex = set->index; + auto localType = getFunction()->getLocalType(localIndex); + auto& fields = localType.getHeapType().getStruct().fields; + + // Store the reference, then store struct.gets of all used fields in the + // new locals. + Builder builder(*getModule()); + std::vector list = { + builder.makeLocalSet(localIndex, set->value)}; + for (auto [fieldIndex, newLocal] : parent.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); + } + } optimizer(*this); + optimizer.setModule(getModule()); + optimizer.walkFunction(func); + + if (hasTry) { + EHUtils::handleBlockNestedPops(func, *getModule()); + } + } +}; + +} // anonymous namespace + +Pass* createScalarizeStructLocalsPass() { return new ScalarizeStructLocals(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index e8fcd32ba1d..37bdc30b7de 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -489,6 +489,10 @@ void PassRegistry::registerPasses() { registerPass("safe-heap", "instrument loads and stores to check for invalid behavior", createSafeHeapPass); + registerPass( + "scalarize-struct-locals", + "replace non-escaping struct locals with locals for their fields", + createScalarizeStructLocalsPass); registerPass("set-globals", "sets specified globals to specified values", createSetGlobalsPass); @@ -725,6 +729,7 @@ void PassRunner::addDefaultFunctionOptimizationPasses() { // simplify-locals opens opportunities for optimizations addIfNoDWARFIssues("remove-unused-brs"); if (options.optimizeLevel > 1 && wasm->features.hasGC()) { + addIfNoDWARFIssues("scalarize-struct-locals"); 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 d7eac10bd60..d8d3c72ba7a 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -163,6 +163,7 @@ Pass* createReReloopPass(); Pass* createRedundantSetEliminationPass(); Pass* createRoundTripPass(); Pass* createSafeHeapPass(); +Pass* createScalarizeStructLocalsPass(); Pass* createSetGlobalsPass(); Pass* createSeparateDataSegmentsPass(); Pass* createSignaturePruningPass(); diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 15c0452507d..6cfbb776756 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -461,6 +461,10 @@ ;; CHECK-NEXT: --safe-heap instrument loads and stores to ;; CHECK-NEXT: check for invalid behavior ;; CHECK-EMPTY: +;; CHECK-NEXT: --scalarize-struct-locals replace non-escaping struct +;; CHECK-NEXT: locals with locals for their +;; CHECK-NEXT: fields +;; CHECK-EMPTY: ;; CHECK-NEXT: --separate-data-segments write data segments to a file ;; CHECK-NEXT: and strip them from the module ;; CHECK-EMPTY: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index bc304c4f679..6f180fb0945 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -506,6 +506,10 @@ ;; CHECK-NEXT: --safe-heap instrument loads and stores to ;; CHECK-NEXT: check for invalid behavior ;; CHECK-EMPTY: +;; CHECK-NEXT: --scalarize-struct-locals replace non-escaping struct +;; CHECK-NEXT: locals with locals for their +;; CHECK-NEXT: fields +;; CHECK-EMPTY: ;; CHECK-NEXT: --separate-data-segments write data segments to a file ;; CHECK-NEXT: and strip them from the module ;; CHECK-EMPTY: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index fec9a3487a7..c0f002dbaef 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -425,6 +425,10 @@ ;; CHECK-NEXT: --safe-heap instrument loads and stores to ;; CHECK-NEXT: check for invalid behavior ;; CHECK-EMPTY: +;; CHECK-NEXT: --scalarize-struct-locals replace non-escaping struct +;; CHECK-NEXT: locals with locals for their +;; CHECK-NEXT: fields +;; CHECK-EMPTY: ;; CHECK-NEXT: --separate-data-segments write data segments to a file ;; CHECK-NEXT: and strip them from the module ;; CHECK-EMPTY: diff --git a/test/lit/passes/scalarize-struct-locals.wast b/test/lit/passes/scalarize-struct-locals.wast new file mode 100644 index 00000000000..f382b05228a --- /dev/null +++ b/test/lit/passes/scalarize-struct-locals.wast @@ -0,0 +1,2739 @@ +;; 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 --scalarize-struct-locals -all -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt --scalarize-struct-locals --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)))) + + (type $struct-3fields-mixed (struct (field i32) (field (mut i32)) (field i64))) + + (type $struct-nn-ref (struct (field (ref struct)))) + + ;; CHECK: (type $struct-immutable (struct (field i32))) + ;; H2L: (type $struct-immutable (struct (field i32))) + (type $struct-immutable (struct (field i32))) + + ;; CHECK: (type $struct-2fields (struct (field i32) (field i32))) + ;; H2L: (type $struct-2fields (struct (field i32) (field i32))) + (type $struct-2fields (struct (field i32) (field i32))) + + ;; CHECK: (type $struct-packed (struct (field i8) (field i16))) + ;; 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 $7) (param i32)) + ;; H2L: (tag $tag (type $4) (param i32)) + (tag $tag (param i32)) + + ;; CHECK: (func $optimize-struct-get-if (type $5) (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 $3) (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 $8) (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 $5) (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 $8) (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 $5) (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-different-fields (type $5) (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 $3) (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-packed-signed-and-unsigned (type $5) (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 $3) (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 $3) (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 $2) (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 $3) (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 $2) (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 $3) (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 $2) (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 $10) + ;; 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 $9) + ;; 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 $3) (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 $2) (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 $3) (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 $2) (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 $3) (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 $2) (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 $11) (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 $10) (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 $12) (param $param (ref $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-immutable (type $11) (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.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-immutable (param $param (ref $struct-immutable)) (result i32) + (local $ref (ref null $struct-immutable)) + ;; As above, but now the field is immutable. We still do not optimize, as + ;; the value is unknown. + (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 $13) (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 $12) (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 $9) (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 $7) (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 $9) (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 $7) (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 $3) (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 $2) (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 $5) (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: (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: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (pop i32) + ;; 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: (local.get $2) + ;; CHECK-NEXT: ) + ;; H2L: (func $try-pop (type $3) (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: (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: (try + ;; H2L-NEXT: (do + ;; 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: (catch $tag + ;; H2L-NEXT: (block + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $7 + ;; H2L-NEXT: (pop i32) + ;; 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 $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: ) + ;; H2L-NEXT: (nop) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + (func $try-pop (param $x i32) (result i32) + ;; After we optimize here, a pop is inside an unnamed block (which is ok). + (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 $3) (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 $2) (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 $7) (param $x i32) + ;; CHECK-NEXT: (local $no (ref null $struct-immutable)) + ;; CHECK-NEXT: (local $yes (ref null $struct-immutable)) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local.set $no + ;; 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 $no + ;; 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: (drop + ;; CHECK-NEXT: (local.get $no) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $yes + ;; CHECK-NEXT: (struct.new_default $struct-immutable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $struct-immutable 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: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $yes + ;; CHECK-NEXT: (struct.new $struct-immutable + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (struct.get $struct-immutable 0 + ;; CHECK-NEXT: (local.get $yes) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $unoptimized-tee (type $4) (param $x i32) + ;; H2L-NEXT: (local $no (ref null $struct-immutable)) + ;; H2L-NEXT: (local $yes (ref null $struct-immutable)) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (local $4 i32) + ;; H2L-NEXT: (local $5 i32) + ;; H2L-NEXT: (local $6 i32) + ;; H2L-NEXT: (local.set $no + ;; 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 $no + ;; 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: (drop + ;; H2L-NEXT: (local.get $no) + ;; H2L-NEXT: ) + ;; 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 $3 + ;; 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: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result i32) + ;; 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 $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: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (local.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $unoptimized-tee (param $x i32) + (local $no (ref null $struct-immutable)) + (local $yes (ref null $struct-immutable)) + ;; The local.get at the end might escape so we don't optimize $no. We should + ;; do nothing with the local.set or local.tee for that local. We can, + ;; however, optimize $yes. + (local.set $no + (struct.new_default $struct-immutable) + ) + (if + (local.get $x) + (then + (drop + (struct.get $struct-immutable 0 + (local.tee $no + (struct.new $struct-immutable + (i32.const 42) + ) + ) + ) + ) + ) + ) + (drop + (local.get $no) + ) + + (local.set $yes + (struct.new_default $struct-immutable) + ) + (if + (local.get $x) + (then + (drop + (struct.get $struct-immutable 0 + (local.tee $yes + (struct.new $struct-immutable + (i32.const 42) + ) + ) + ) + ) + ) + ) + (drop + (struct.get $struct-immutable 0 + (local.get $yes) + ) + ) + ) + + ;; CHECK: (func $struct.get-unknown-child (type $5) (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 $3) (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 $15) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; H2L: (func $get-struct (type $14) (result (ref $struct)) + ;; H2L-NEXT: (struct.new_default $struct) + ;; H2L-NEXT: ) + (func $get-struct (result (ref $struct)) + (struct.new_default $struct) + ) + + ;; CHECK: (func $optimize-struct-set (type $5) (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: (else + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; H2L: (func $optimize-struct-set (type $3) (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: (else + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (i32.const 1337) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + (func $optimize-struct-set (param $x i32) (result i32) + (local $ref (ref null $struct)) + ;; A mixture of struct.gets and sets. A set is simply a write to the local. + (local.set $ref + (struct.new_default $struct) + ) + (if + (local.get $x) + (then + (local.set $ref + (struct.new $struct + (i32.const 42) + ) + ) + ) + (else + (struct.set $struct 0 + (local.get $ref) + (i32.const 1337) + ) + ) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) +) + +;; 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 (shared (struct (field i32)))) + (type $struct (shared (struct (field i32)))) + + ;; H2L: (type $struct-mut (shared (struct (field (mut 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 $1) (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 $no-allocations (type $3) (param $param (ref $struct)) (result i32) + ;; CHECK-NEXT: (local $ref (ref $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $no-allocations (type $3) (param $param (ref $struct)) (result i32) + ;; H2L-NEXT: (local $ref (ref $struct)) + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (local.get $param) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (local.get $param) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (struct.get $struct 0 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $no-allocations (param $param (ref $struct)) (result i32) + ;; We do not optimize when there are no allocations, as then Heap2Local has + ;; no optimization to do afterwards. Even two local.sets is not enough. + (local $ref (ref $struct)) + (local.set $ref + (local.get $param) + ) + (local.set $ref + (local.get $param) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) +) +