From 66f0b3a17118f6225496582d5adffbe64ce90af5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 14:00:43 -0700 Subject: [PATCH 01/74] start --- src/passes/CMakeLists.txt | 1 + src/passes/PropagateStructGets.cpp | 285 +++++++++ src/passes/pass.cpp | 5 + src/passes/passes.h | 1 + test/lit/passes/propagate-struct-gets.wast | 704 +++++++++++++++++++++ 5 files changed, 996 insertions(+) create mode 100644 src/passes/PropagateStructGets.cpp create mode 100644 test/lit/passes/propagate-struct-gets.wast 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..2cd918e31da --- /dev/null +++ b/src/passes/PropagateStructGets.cpp @@ -0,0 +1,285 @@ +/* + * 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. + */ + +// +// Propagate struct.get operations backwards into local sets/tees and into if +// arms that allocate structs: +// +// 1. When a struct-typed local is written multiple times and every read of it +// (including via local.tee) is immediately consumed by an identical +// struct.get of the same field, replace the struct local with a local of +// the field's type and perform the struct.get at each local.set/local.tee. +// +// 2. When a struct.get consumes an if whose arm(s) allocate a struct via +// struct.new, sink the struct.get into the if arms. +// +// This disentangles shared struct locals and control-flow merges so that +// Heap2Local can optimize the underlying allocations independently. +// + +#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; } + + // New locals created here are always defaultable (nullable when references). + bool requiresNonNullableLocalFixups() override { return false; } + + std::unique_ptr create() override { + return std::make_unique(); + } + + std::unordered_map numGets; + std::unordered_map> sets; + std::unordered_map> getParents; + std::unordered_map teeParents; + std::vector structGets; + + bool isStructVar(Index index) { + auto* func = getFunction(); + return func->isVar(index) && func->getLocalType(index).isStruct(); + } + + void visitLocalGet(LocalGet* curr) { + if (isStructVar(curr->index)) { + numGets[curr->index]++; + } + } + + void visitLocalSet(LocalSet* curr) { + if (isStructVar(curr->index)) { + sets[curr->index].push_back(curr); + } + } + + void visitStructGet(StructGet* curr) { + if (curr->type == Type::unreachable || + curr->order != MemoryOrder::Unordered) { + return; + } + if (auto* get = curr->ref->dynCast()) { + if (isStructVar(get->index)) { + getParents[get->index].push_back(curr); + structGets.push_back(getCurrentPointer()); + } + } else if (auto* set = curr->ref->dynCast()) { + if (isStructVar(set->index)) { + teeParents[set] = curr; + structGets.push_back(getCurrentPointer()); + } + } else if (auto* iff = curr->ref->dynCast()) { + if (iff->ifFalse && iff->type != Type::unreachable && + iff->ifTrue->type != Type::unreachable && + iff->ifFalse->type != Type::unreachable) { + auto* module = getModule(); + const auto& passOptions = getPassOptions(); + if (Properties::getFallthrough(iff->ifTrue, passOptions, *module) + ->is() || + Properties::getFallthrough(iff->ifFalse, passOptions, *module) + ->is()) { + structGets.push_back(getCurrentPointer()); + } + } + } + } + + void doWalkFunction(Function* func) { + if (!getModule()->features.hasGC()) { + return; + } + Super::doWalkFunction(func); + if (structGets.empty()) { + return; + } + + auto* module = getModule(); + const auto& passOptions = getPassOptions(); + Builder builder(*module); + std::unique_ptr localGraph; + std::unordered_map projectedLocals; + + for (auto& [x, localSets] : sets) { + if (localSets.size() < 2) { + continue; + } + auto getIt = getParents.find(x); + if (getIt == getParents.end()) { + continue; + } + const auto& sgets = getIt->second; + if (sgets.empty() || sgets.size() != numGets[x]) { + continue; + } + + Index fieldIndex = sgets[0]->index; + bool isSigned = sgets[0]->signed_; + Type fieldType = sgets[0]->type; + Type localType = fieldType.isRef() ? fieldType.with(Nullable) : fieldType; + if (!localType.isDefaultable()) { + continue; + } + + bool ok = true; + for (auto* sget : sgets) { + if (sget->index != fieldIndex || sget->signed_ != isSigned || + sget->type != fieldType) { + ok = false; + break; + } + } + if (!ok) { + continue; + } + + bool allSetsAreFreshAllocs = true; + for (auto* set : localSets) { + if (set->value->type == Type::unreachable) { + ok = false; + break; + } + if (set->isTee()) { + allSetsAreFreshAllocs = false; + auto teeIt = teeParents.find(set); + if (teeIt == teeParents.end()) { + ok = false; + break; + } + auto* pget = teeIt->second; + if (pget->index != fieldIndex || pget->signed_ != isSigned || + pget->type != fieldType) { + ok = false; + break; + } + } else { + if (!set->value->is()) { + allSetsAreFreshAllocs = false; + } + if (!set->value->type.isNonNullable() && + !passOptions.trapsNeverHappen) { + ok = false; + break; + } + } + } + if (!ok) { + continue; + } + + // The field 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. + auto& field = + func->getLocalType(x).getHeapType().getStruct().fields[fieldIndex]; + if (field.mutable_ != Immutable && !allSetsAreFreshAllocs) { + continue; + } + + // If the original local is nullable and the replacement local.get will + // not trap on null via ref.as_non_null, ensure no get can observe the + // uninitialized default null value. + if (func->getLocalType(x).isNullable() && !fieldType.isNonNullable() && + !passOptions.trapsNeverHappen) { + if (!localGraph) { + localGraph = std::make_unique(func, module); + } + for (auto* sget : sgets) { + if (localGraph->getSets(sget->ref->cast()) + .contains(nullptr)) { + ok = false; + break; + } + } + if (!ok) { + continue; + } + } + + projectedLocals[x] = builder.addVar(func, localType); + } + + // Rewrite recorded struct.gets in post-order. + for (auto** currp : structGets) { + auto* sget = (*currp)->cast(); + if (auto* get = sget->ref->dynCast()) { + auto it = projectedLocals.find(get->index); + if (it == projectedLocals.end()) { + continue; + } + Index newLocal = it->second; + Expression* rep = + builder.makeLocalGet(newLocal, func->getLocalType(newLocal)); + if (sget->type.isNonNullable()) { + rep = builder.makeRefAs(RefAsNonNull, rep); + } + *currp = rep; + } else if (auto* set = sget->ref->dynCast()) { + auto it = projectedLocals.find(set->index); + if (it == projectedLocals.end()) { + continue; + } + Index newLocal = it->second; + sget->ref = set->value; + sget->finalize(); + set->index = newLocal; + set->value = sget; + set->makeTee(func->getLocalType(newLocal)); + Expression* rep = set; + if (sget->type.isNonNullable()) { + rep = builder.makeRefAs(RefAsNonNull, rep); + } + *currp = rep; + } else if (auto* iff = sget->ref->dynCast()) { + iff->ifTrue = builder.makeStructGet( + sget->index, iff->ifTrue, sget->order, sget->type, sget->signed_); + iff->ifFalse = builder.makeStructGet( + sget->index, iff->ifFalse, sget->order, sget->type, sget->signed_); + iff->finalize(); + *currp = iff; + } + } + + // Rewrite non-tee local.sets of projected locals. + for (auto [x, newLocal] : projectedLocals) { + auto* sget = getParents[x][0]; + for (auto* set : sets[x]) { + if (!set->isTee()) { + set->value = builder.makeStructGet(sget->index, + set->value, + MemoryOrder::Unordered, + sget->type, + sget->signed_); + set->index = newLocal; + set->finalize(); + } + } + } + } +}; + +} // 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/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast new file mode 100644 index 00000000000..fcecbf703c0 --- /dev/null +++ b/test/lit/passes/propagate-struct-gets.wast @@ -0,0 +1,704 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s --propagate-struct-gets -all -S -o - | filecheck %s +;; RUN: wasm-opt %s --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-nn-ref (struct (field (ref struct)))) + ;; H2L: (type $struct-nn-ref (struct (field (ref struct)))) + (type $struct-nn-ref (struct (field (ref struct)))) + + ;; 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))) + + ;; H2L: (type $struct-immutable (struct (field i32))) + (type $struct-immutable (struct (field i32))) + + ;; CHECK: (func $project-struct-get-if (type $4) (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 $2 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; H2L: (func $project-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: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; 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.get $3) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (else + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (block (result i32) + ;; 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.get $4) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: ) + (func $project-struct-get-if (param $x i32) (result i32) + ;; Even when the field is mutable, we can project $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 $sink-struct-get-if (type $4) (param $x i32) (result i32) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $sink-struct-get-if (type $3) (param $x i32) (result i32) + ;; H2L-NEXT: (local $1 i32) + ;; H2L-NEXT: (local $2 i32) + ;; H2L-NEXT: (local $3 i32) + ;; H2L-NEXT: (if (result i32) + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $1 + ;; H2L-NEXT: (i32.const 0) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (ref.null none) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (else + ;; 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.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $sink-struct-get-if (param $x i32) (result i32) + ;; A struct.get directly consuming an if that allocates structs in its arms + ;; should be sunk into the arms so Heap2Local can eliminate both + ;; allocations. + (struct.get $struct 0 + (if (result (ref $struct)) + (local.get $x) + (then + (struct.new_default $struct) + ) + (else + (struct.new $struct + (i32.const 42) + ) + ) + ) + ) + ) + + ;; CHECK: (func $no-sink-struct-get-if-no-alloc (type $6) (param $x i32) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (if (result (ref $struct)) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $s1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.get $s2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $no-sink-struct-get-if-no-alloc (type $6) (param $x i32) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; H2L-NEXT: (struct.get $struct 0 + ;; H2L-NEXT: (if (result (ref $struct)) + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (local.get $s1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (else + ;; H2L-NEXT: (local.get $s2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $no-sink-struct-get-if-no-alloc (param $x i32) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; Do not sink struct.get into an if when neither arm allocates a struct.new. + (struct.get $struct 0 + (if (result (ref $struct)) + (local.get $x) + (then + (local.get $s1) + ) + (else + (local.get $s2) + ) + ) + ) + ) + + ;; CHECK: (func $no-project-struct-get-uninitialized (type $5) (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-project-struct-get-uninitialized (type $4) (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-project-struct-get-uninitialized (param $x i32) (param $y i32) (result i32) + ;; Similar to $project-struct-get-if, but $ref is not initialized on all + ;; paths reaching the struct.get, so it can be null and trap. Do not project + ;; 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 $project-struct-get-tee-immutable (type $5) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (local $cur (ref $struct-immutable)) + ;; CHECK-NEXT: (local $ref (ref null $struct-immutable)) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (block $err + ;; CHECK-NEXT: (local.set $cur + ;; CHECK-NEXT: (struct.new $struct-immutable + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $err + ;; CHECK-NEXT: (local.tee $4 + ;; CHECK-NEXT: (struct.get $struct-immutable 0 + ;; CHECK-NEXT: (local.get $cur) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $cur + ;; CHECK-NEXT: (struct.new $struct-immutable + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $err + ;; CHECK-NEXT: (local.tee $4 + ;; CHECK-NEXT: (struct.get $struct-immutable 0 + ;; CHECK-NEXT: (local.get $cur) + ;; CHECK-NEXT: ) + ;; 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 $project-struct-get-tee-immutable (type $4) (param $x i32) (param $y i32) (result i32) + ;; H2L-NEXT: (local $cur (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: (local.tee $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: (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: (local.tee $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: ) + ;; H2L-NEXT: (return + ;; H2L-NEXT: (i32.const 0) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) + ;; H2L-NEXT: ) + (func $project-struct-get-tee-immutable (param $x i32) (param $y i32) (result i32) + ;; Two independent allocations in $cur 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. Projecting $ref to an i32 + ;; local disentangles the two allocations so Heap2Local can optimize both. + (local $cur (ref $struct-immutable)) + (local $ref (ref null $struct-immutable)) + (block $err + (local.set $cur + (struct.new $struct-immutable + (local.get $x) + ) + ) + (br_if $err + (struct.get $struct-immutable 0 + (local.tee $ref + (local.get $cur) + ) + ) + ) + (local.set $cur + (struct.new $struct-immutable + (local.get $y) + ) + ) + (br_if $err + (struct.get $struct-immutable 0 + (local.tee $ref + (local.get $cur) + ) + ) + ) + (return + (i32.const 0) + ) + ) + (struct.get $struct-immutable 0 + (local.get $ref) + ) + ) + + ;; CHECK: (func $project-struct-get-tee-immutable-nn-ref (type $7) (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: (ref.as_non_null + ;; CHECK-NEXT: (local.tee $4 + ;; CHECK-NEXT: (struct.get $struct-nn-ref 0 + ;; CHECK-NEXT: (local.get $b1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $err + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.tee $4 + ;; CHECK-NEXT: (struct.get $struct-nn-ref 0 + ;; CHECK-NEXT: (local.get $b2) + ;; CHECK-NEXT: ) + ;; 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 $project-struct-get-tee-immutable-nn-ref (type $7) (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: (ref.as_non_null + ;; H2L-NEXT: (local.tee $4 + ;; H2L-NEXT: (struct.get $struct-nn-ref 0 + ;; H2L-NEXT: (local.get $b1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (br_if $err + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (drop + ;; H2L-NEXT: (ref.as_non_null + ;; H2L-NEXT: (local.tee $4 + ;; H2L-NEXT: (struct.get $struct-nn-ref 0 + ;; H2L-NEXT: (local.get $b2) + ;; H2L-NEXT: ) + ;; 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 $project-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 projected local uses a nullable type with + ;; 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-project-struct-get-tee-mutable (type $8) (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-project-struct-get-tee-mutable (type $8) (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-project-struct-get-tee-mutable (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; Do not project when tees write arbitrary struct references and the field + ;; is mutable. + (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 $no-project-struct-get-different-fields (type $4) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct-2fields)) + ;; 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: ) + ;; 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: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (struct.get $struct-2fields 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct-2fields 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; H2L: (func $no-project-struct-get-different-fields (type $3) (param $x i32) (result i32) + ;; H2L-NEXT: (local $ref (ref null $struct-2fields)) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (struct.new_default $struct-2fields) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (else + ;; H2L-NEXT: (local.set $ref + ;; H2L-NEXT: (struct.new $struct-2fields + ;; H2L-NEXT: (i32.const 10) + ;; H2L-NEXT: (i32.const 20) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (struct.get $struct-2fields 0 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (struct.get $struct-2fields 1 + ;; H2L-NEXT: (local.get $ref) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $no-project-struct-get-different-fields (param $x i32) (result i32) + ;; Do not project when gets of $ref read different fields (field 0 vs + ;; 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) + ) + ) + ) +) From e69921aa34c45154d80d5640a716445f84d84443 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 14:36:00 -0700 Subject: [PATCH 02/74] form --- src/passes/PropagateStructGets.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 2cd918e31da..882a059fde5 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -41,7 +41,8 @@ namespace wasm { namespace { -struct PropagateStructGets : public WalkerPass> { +struct PropagateStructGets + : public WalkerPass> { bool isFunctionParallel() override { return true; } // New locals created here are always defaultable (nullable when references). From 324b32ec6c840b11cc0610c3d874344077a6c601 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 14:43:25 -0700 Subject: [PATCH 03/74] commentses --- src/passes/PropagateStructGets.cpp | 47 +++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 882a059fde5..67f9c28c7cb 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -22,9 +22,42 @@ // (including via local.tee) is immediately consumed by an identical // struct.get of the same field, replace the struct local with a local of // the field's type and perform the struct.get at each local.set/local.tee. +// For example: +// +// x = new Foo(42); +// bar(x.value, x.value); +// +// => +// +// // Store the value directly. +// x_value = (new Foo(42)).value; +// bar(x_value, x_value); +// +// We do this when we detect a simple situation where all the uses are just +// gets like that. In this case, we know Heap2Local can later optimize +// the allocation away. // // 2. When a struct.get consumes an if whose arm(s) allocate a struct via -// struct.new, sink the struct.get into the if arms. +// struct.new, sink the struct.get into the if arms. For example: +// +// (struct.get $x 3 +// (if +// (..condition..) +// (struct.new ..) +// (struct.new ..))) +// +// => +// +// (struct.get $x 3 +// (if +// (..condition..) +// (struct.new ..) +// (struct.new ..))) +// +// This could be done as a peephole optimization in OptimizeInstructions, +// but it generally *increases* code size. Here we only do it when we +// detect the proper situation as in point 1, where Heap2Local's followup +// optimizations make it worthwhile. // // This disentangles shared struct locals and control-flow merges so that // Heap2Local can optimize the underlying allocations independently. @@ -52,12 +85,24 @@ struct PropagateStructGets return std::make_unique(); } + // Track the number of gets for each local, so that we can see if all gets are + // accounted for and understood. std::unordered_map numGets; + + // The sets for each get. std::unordered_map> sets; + + // The parent struct.get for each local. std::unordered_map> getParents; + + // The parent struct.get for each tee. std::unordered_map teeParents; + + // All struct.gets XXX std::vector structGets; + // Check if a local index is a reference to a struct. Those are the locals we + // try to optimize. bool isStructVar(Index index) { auto* func = getFunction(); return func->isVar(index) && func->getLocalType(index).isStruct(); From 8c19fdea2d3d3a0bb86ae57d247ab2e1c0ad14fb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 14:47:43 -0700 Subject: [PATCH 04/74] commentses --- src/passes/PropagateStructGets.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 67f9c28c7cb..0aa8379b781 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -98,7 +98,8 @@ struct PropagateStructGets // The parent struct.get for each tee. std::unordered_map teeParents; - // All struct.gets XXX + // All relevant struct.gets in the function, which means the struct.gets that + // operate on references that fit the patterns we can optimize. std::vector structGets; // Check if a local index is a reference to a struct. Those are the locals we @@ -121,10 +122,13 @@ struct PropagateStructGets } void visitStructGet(StructGet* curr) { + // Avoid unreachable code, and leave atomic operations for now TODO if (curr->type == Type::unreachable || curr->order != MemoryOrder::Unordered) { return; } + + // See if the reference is one of the patterns we recognize. if (auto* get = curr->ref->dynCast()) { if (isStructVar(get->index)) { getParents[get->index].push_back(curr); @@ -151,12 +155,9 @@ struct PropagateStructGets } } - void doWalkFunction(Function* func) { - if (!getModule()->features.hasGC()) { - return; - } - Super::doWalkFunction(func); + void visitFunction(Function* func) { if (structGets.empty()) { + // Nothing was found to be optimizable in the walk. return; } From d39f84edb85ab67d15c23f067592ef1500f43c44 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 14:50:29 -0700 Subject: [PATCH 05/74] work --- src/passes/PropagateStructGets.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 0aa8379b781..903ddd6b0d5 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -164,19 +164,19 @@ struct PropagateStructGets auto* module = getModule(); const auto& passOptions = getPassOptions(); Builder builder(*module); - std::unique_ptr localGraph; + LazyLocalGraph localGraph(func, module); std::unordered_map projectedLocals; - for (auto& [x, localSets] : sets) { + for (auto& [localIndex, localSets] : sets) { if (localSets.size() < 2) { continue; } - auto getIt = getParents.find(x); + auto getIt = getParents.find(localIndex); if (getIt == getParents.end()) { continue; } const auto& sgets = getIt->second; - if (sgets.empty() || sgets.size() != numGets[x]) { + if (sgets.empty() || sgets.size() != numGets[localIndex]) { continue; } @@ -238,7 +238,7 @@ struct PropagateStructGets // either the field is immutable, or every set writes a fresh struct.new // that only ever flows into this local's struct.gets. auto& field = - func->getLocalType(x).getHeapType().getStruct().fields[fieldIndex]; + func->getLocalType(localIndex).getHeapType().getStruct().fields[fieldIndex]; if (field.mutable_ != Immutable && !allSetsAreFreshAllocs) { continue; } @@ -246,13 +246,10 @@ struct PropagateStructGets // If the original local is nullable and the replacement local.get will // not trap on null via ref.as_non_null, ensure no get can observe the // uninitialized default null value. - if (func->getLocalType(x).isNullable() && !fieldType.isNonNullable() && + if (func->getLocalType(localIndex).isNullable() && !fieldType.isNonNullable() && !passOptions.trapsNeverHappen) { - if (!localGraph) { - localGraph = std::make_unique(func, module); - } for (auto* sget : sgets) { - if (localGraph->getSets(sget->ref->cast()) + if (localGraph.getSets(sget->ref->cast()) .contains(nullptr)) { ok = false; break; @@ -263,7 +260,7 @@ struct PropagateStructGets } } - projectedLocals[x] = builder.addVar(func, localType); + projectedLocals[localIndex] = builder.addVar(func, localType); } // Rewrite recorded struct.gets in post-order. From 36fed8fc02e5f651dfacb6ca1059f34e842f6edc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 14:51:22 -0700 Subject: [PATCH 06/74] work --- src/passes/PropagateStructGets.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 903ddd6b0d5..3a4f6453be4 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -89,7 +89,7 @@ struct PropagateStructGets // accounted for and understood. std::unordered_map numGets; - // The sets for each get. + // The sets for each local index. std::unordered_map> sets; // The parent struct.get for each local. @@ -169,7 +169,7 @@ struct PropagateStructGets for (auto& [localIndex, localSets] : sets) { if (localSets.size() < 2) { - continue; +// continue; } auto getIt = getParents.find(localIndex); if (getIt == getParents.end()) { From 549a1f00ae19161bf5a4da0496bf4eb5d9fe1b33 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 14:51:36 -0700 Subject: [PATCH 07/74] work --- src/passes/PropagateStructGets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 3a4f6453be4..024855327e1 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -169,7 +169,7 @@ struct PropagateStructGets for (auto& [localIndex, localSets] : sets) { if (localSets.size() < 2) { -// continue; + continue; } auto getIt = getParents.find(localIndex); if (getIt == getParents.end()) { From 541428194ceb382bcb97f89c54ff3fe41601c42a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 15:04:19 -0700 Subject: [PATCH 08/74] work --- src/passes/PropagateStructGets.cpp | 29 ++++++++--- test/lit/passes/propagate-struct-gets.wast | 60 ++++++++++++++++++++++ 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 024855327e1..b2be062b911 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -15,10 +15,10 @@ */ // -// Propagate struct.get operations backwards into local sets/tees and into if +// Propagate struct.get operations backwards into local sets/tees and into If // arms that allocate structs: // -// 1. When a struct-typed local is written multiple times and every read of it +// 1. When a struct-typed local is written and every read of it // (including via local.tee) is immediately consumed by an identical // struct.get of the same field, replace the struct local with a local of // the field's type and perform the struct.get at each local.set/local.tee. @@ -35,7 +35,27 @@ // // We do this when we detect a simple situation where all the uses are just // gets like that. In this case, we know Heap2Local can later optimize -// the allocation away. +// the allocation away. That is, we are detecting a simple case where +// nothing escapes and things are easy to optimize, and we handle it +// directly. This is simpler than making Heap2Local able to reason about +// groups of allocations and their possibly overlapping livenesses, in +// situations like this: +// +// x = new Foo(42); +// if (condition) x = new Foo(1337); +// bar(x.value, x.value); +// +// The allocations here have overlapping live ranges, and Heap2Local +// cannot handle them (it sees the x.value operations are not exclusive to +// any single allocation). After we simplify things in this pass, the +// situation becomes trivial for Heap2Local, as the allocations do not +// overlap: +// +// x_value = (new Foo(42)).value; +// if (condition) x_value = (new Foo(1337)).value; +// bar(x_value, x_value); +// +// TODO verify in tests // // 2. When a struct.get consumes an if whose arm(s) allocate a struct via // struct.new, sink the struct.get into the if arms. For example: @@ -168,9 +188,6 @@ struct PropagateStructGets std::unordered_map projectedLocals; for (auto& [localIndex, localSets] : sets) { - if (localSets.size() < 2) { - continue; - } auto getIt = getParents.find(localIndex); if (getIt == getParents.end()) { continue; diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index fcecbf703c0..89d4fb82d52 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -701,4 +701,64 @@ ) ) ) + + ;; CHECK: (func $project-single-set (type $9) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; 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 $project-single-set (type $9) (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.set $1 + ;; H2L-NEXT: (block (result i32) + ;; 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.get $2) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (i32.add + ;; H2L-NEXT: (local.get $1) + ;; H2L-NEXT: (local.get $1) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + (func $project-single-set (result i32) + ;; Even with a single local.set, propagating the struct.get to the set + ;; deduplicates multiple gets of the same field. + (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) + ) + ) + ) ) From 273f8e28c9de6fb16e810add4bd03bf83c19bd80 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 15:37:51 -0700 Subject: [PATCH 09/74] work --- src/passes/PropagateStructGets.cpp | 7 +++ test/lit/passes/propagate-struct-gets.wast | 57 ++++++++++++---------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index b2be062b911..d11601cc56e 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -188,6 +188,13 @@ struct PropagateStructGets std::unordered_map projectedLocals; 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 (and zero is + // trivially trapping). + continue; + } + auto getIt = getParents.find(localIndex); if (getIt == getParents.end()) { continue; diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 89d4fb82d52..027a11d482c 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -704,48 +704,53 @@ ;; CHECK: (func $project-single-set (type $9) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) - ;; CHECK-NEXT: (local $1 i32) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (struct.get $struct 0 - ;; CHECK-NEXT: (struct.new $struct - ;; CHECK-NEXT: (i32.const 42) - ;; 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: (local.get $1) - ;; CHECK-NEXT: (local.get $1) + ;; 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 $project-single-set (type $9) (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.set $1 - ;; H2L-NEXT: (block (result i32) - ;; 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: (drop + ;; H2L-NEXT: (block (result nullref) + ;; H2L-NEXT: (local.set $2 + ;; H2L-NEXT: (i32.const 42) ;; H2L-NEXT: ) - ;; H2L-NEXT: (local.get $2) + ;; 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: (local.get $1) - ;; H2L-NEXT: (local.get $1) + ;; 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 $project-single-set (result i32) - ;; Even with a single local.set, propagating the struct.get to the set - ;; deduplicates multiple gets of the same field. + ;; We leave a single local.set for Heap2Local (that situation has no + ;; overlapping live ranges etc.). (local $ref (ref null $struct)) (local.set $ref (struct.new $struct From 3d9e22fae9ee97c3651206609abe4b4c15f71138 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 15:41:41 -0700 Subject: [PATCH 10/74] work --- src/passes/PropagateStructGets.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index d11601cc56e..6df402176c0 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -149,6 +149,7 @@ struct PropagateStructGets } // See if the reference is one of the patterns we recognize. + // TODO fallthrough if (auto* get = curr->ref->dynCast()) { if (isStructVar(get->index)) { getParents[get->index].push_back(curr); @@ -197,10 +198,17 @@ struct PropagateStructGets auto getIt = getParents.find(localIndex); if (getIt == getParents.end()) { + // No struct.get parents for this local. continue; } + + // Check if the number of struct.gets is equal to the total number of + // gets for the local. 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& sgets = getIt->second; - if (sgets.empty() || sgets.size() != numGets[localIndex]) { + assert(!sgets.empty()); // if we added the local, the vec is nonempty + if (sgets.size() != numGets[localIndex]) { continue; } From 18790e7ad48a8936be57aea32b7d2f76a3b8d095 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 15:51:41 -0700 Subject: [PATCH 11/74] work --- src/passes/PropagateStructGets.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 6df402176c0..24f56d7a039 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -212,6 +212,9 @@ struct PropagateStructGets continue; } + // Veirfy all the struct.gets operate on the same field in the struct, and + // in the same way. + // TODO: handle multiple field indexes, and mixtures of signed/unsigned Index fieldIndex = sgets[0]->index; bool isSigned = sgets[0]->signed_; Type fieldType = sgets[0]->type; @@ -232,19 +235,26 @@ struct PropagateStructGets continue; } + // Analyze the sets to the local. Things are especially simple if they all + // store a fresh allocation, that is, a struct.new. bool allSetsAreFreshAllocs = true; for (auto* set : localSets) { if (set->value->type == Type::unreachable) { + // Stop on dead code. ok = false; break; } if (set->isTee()) { - allSetsAreFreshAllocs = false; + allSetsAreFreshAllocs = false; // XXX 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; } + // Check if the parent struct.get is compatible with all the ones + // before, that we checked for the parents of local.gets. auto* pget = teeIt->second; if (pget->index != fieldIndex || pget->signed_ != isSigned || pget->type != fieldType) { @@ -252,9 +262,14 @@ struct PropagateStructGets break; } } else { + // Not a tee: a normal local.set. + if (!set->value->is()) { allSetsAreFreshAllocs = false; } + + // If we might trap (setting a nullable value, and traps are possible) + // then we cannot optimize, as we would be removing a possible trap. if (!set->value->type.isNonNullable() && !passOptions.trapsNeverHappen) { ok = false; From bbed88f1c70dbcd13fbc65dc44d87d9fc9d60bf5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 15:53:05 -0700 Subject: [PATCH 12/74] work --- src/passes/PropagateStructGets.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 24f56d7a039..c249bf48e51 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -236,8 +236,9 @@ struct PropagateStructGets } // Analyze the sets to the local. Things are especially simple if they all - // store a fresh allocation, that is, a struct.new. - bool allSetsAreFreshAllocs = true; + // store a fresh unescaping allocation, that is, a struct.new which does + // not flow out through a tee. + bool allSetsAreUnescapingAllocs = true; for (auto* set : localSets) { if (set->value->type == Type::unreachable) { // Stop on dead code. @@ -245,7 +246,7 @@ struct PropagateStructGets break; } if (set->isTee()) { - allSetsAreFreshAllocs = false; // XXX + allSetsAreUnescapingAllocs = false; auto teeIt = teeParents.find(set); if (teeIt == teeParents.end()) { // This tee does not feed into a struct.get, so the allocation(s) @@ -265,7 +266,7 @@ struct PropagateStructGets // Not a tee: a normal local.set. if (!set->value->is()) { - allSetsAreFreshAllocs = false; + allSetsAreUnescapingAllocs = false; } // If we might trap (setting a nullable value, and traps are possible) @@ -286,7 +287,7 @@ struct PropagateStructGets // that only ever flows into this local's struct.gets. auto& field = func->getLocalType(localIndex).getHeapType().getStruct().fields[fieldIndex]; - if (field.mutable_ != Immutable && !allSetsAreFreshAllocs) { + if (field.mutable_ != Immutable && !allSetsAreUnescapingAllocs) { continue; } From 88d62e15447db0eeec9b25f5fb606f92d21660d8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 15:53:13 -0700 Subject: [PATCH 13/74] work --- src/passes/PropagateStructGets.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index c249bf48e51..5c411b8cb85 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -285,8 +285,10 @@ struct PropagateStructGets // The field 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. - auto& field = - func->getLocalType(localIndex).getHeapType().getStruct().fields[fieldIndex]; + auto& field = func->getLocalType(localIndex) + .getHeapType() + .getStruct() + .fields[fieldIndex]; if (field.mutable_ != Immutable && !allSetsAreUnescapingAllocs) { continue; } @@ -294,8 +296,8 @@ struct PropagateStructGets // If the original local is nullable and the replacement local.get will // not trap on null via ref.as_non_null, ensure no get can observe the // uninitialized default null value. - if (func->getLocalType(localIndex).isNullable() && !fieldType.isNonNullable() && - !passOptions.trapsNeverHappen) { + if (func->getLocalType(localIndex).isNullable() && + !fieldType.isNonNullable() && !passOptions.trapsNeverHappen) { for (auto* sget : sgets) { if (localGraph.getSets(sget->ref->cast()) .contains(nullptr)) { From 63b9bea91a4257d3996b73f91f9e4ad5d41c4f20 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 15:54:56 -0700 Subject: [PATCH 14/74] work --- src/passes/PropagateStructGets.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 5c411b8cb85..87182c23147 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -82,6 +82,7 @@ // This disentangles shared struct locals and control-flow merges so that // Heap2Local can optimize the underlying allocations independently. // +// TODO: test file for tnh/non-tnh #include "ir/local-graph.h" #include "ir/properties.h" From 03bd309c152261953396e5b2ac745a784bd71b20 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 16:23:47 -0700 Subject: [PATCH 15/74] work --- src/passes/PropagateStructGets.cpp | 48 +++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 87182c23147..9ea87247533 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -184,7 +184,6 @@ struct PropagateStructGets } auto* module = getModule(); - const auto& passOptions = getPassOptions(); Builder builder(*module); LazyLocalGraph localGraph(func, module); std::unordered_map projectedLocals; @@ -269,14 +268,6 @@ struct PropagateStructGets if (!set->value->is()) { allSetsAreUnescapingAllocs = false; } - - // If we might trap (setting a nullable value, and traps are possible) - // then we cannot optimize, as we would be removing a possible trap. - if (!set->value->type.isNonNullable() && - !passOptions.trapsNeverHappen) { - ok = false; - break; - } } } if (!ok) { @@ -294,11 +285,39 @@ struct PropagateStructGets continue; } - // If the original local is nullable and the replacement local.get will - // not trap on null via ref.as_non_null, ensure no get can observe the - // uninitialized default null value. - if (func->getLocalType(localIndex).isNullable() && - !fieldType.isNonNullable() && !passOptions.trapsNeverHappen) { + // 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); + // + // => + // + // 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 here, since we are moving the + // trap earlier, after `foo()` which might have prevented us from reaching + // the trap. We can rule this problem out by seeing that only non-nullable + // values are written to the local (it is fine for the local itself to be + // nullable, see below). + for (auto* set : localSets) { + if (set->value->type.isNullable()) { + ok = false; + break; + } + } + if (!ok) { + continue; + } + + // Finally, if the local is nullable, see if the null default value can + // actually be read. + if (func->getLocalType(localIndex).isNullable()) { for (auto* sget : sgets) { if (localGraph.getSets(sget->ref->cast()) .contains(nullptr)) { @@ -311,6 +330,7 @@ struct PropagateStructGets } } + // Success! The pattern fits, prepare to optimize this local. projectedLocals[localIndex] = builder.addVar(func, localType); } From 110938c7b381c7bc00b7563fd1d6c9993bc8fb1b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 16:24:06 -0700 Subject: [PATCH 16/74] work --- src/passes/PropagateStructGets.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 9ea87247533..31e0c4da56e 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -82,7 +82,6 @@ // This disentangles shared struct locals and control-flow merges so that // Heap2Local can optimize the underlying allocations independently. // -// TODO: test file for tnh/non-tnh #include "ir/local-graph.h" #include "ir/properties.h" From b5a8718610abd7c7eb17674469fc1160dbc31866 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 16:27:45 -0700 Subject: [PATCH 17/74] work --- src/passes/PropagateStructGets.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 31e0c4da56e..ca75e8f7a89 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -197,7 +197,7 @@ struct PropagateStructGets auto getIt = getParents.find(localIndex); if (getIt == getParents.end()) { - // No struct.get parents for this local. + // No struct.get parents for this local's gets. continue; } @@ -205,25 +205,25 @@ struct PropagateStructGets // gets for the local. 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& sgets = getIt->second; - assert(!sgets.empty()); // if we added the local, the vec is nonempty - if (sgets.size() != numGets[localIndex]) { + const auto& getStructGets = getIt->second; + assert(!getStructGets.empty()); // if we added the local, the vec is nonempty + if (getStructGets.size() != numGets[localIndex]) { continue; } // Veirfy all the struct.gets operate on the same field in the struct, and // in the same way. // TODO: handle multiple field indexes, and mixtures of signed/unsigned - Index fieldIndex = sgets[0]->index; - bool isSigned = sgets[0]->signed_; - Type fieldType = sgets[0]->type; + Index fieldIndex = getStructGets[0]->index; + bool isSigned = getStructGets[0]->signed_; + Type fieldType = getStructGets[0]->type; Type localType = fieldType.isRef() ? fieldType.with(Nullable) : fieldType; if (!localType.isDefaultable()) { continue; } bool ok = true; - for (auto* sget : sgets) { + for (auto* sget : getStructGets) { if (sget->index != fieldIndex || sget->signed_ != isSigned || sget->type != fieldType) { ok = false; @@ -317,7 +317,7 @@ struct PropagateStructGets // Finally, if the local is nullable, see if the null default value can // actually be read. if (func->getLocalType(localIndex).isNullable()) { - for (auto* sget : sgets) { + for (auto* sget : getStructGets) { if (localGraph.getSets(sget->ref->cast()) .contains(nullptr)) { ok = false; From 6da0c5434c54630dc298d8a527ef29e3daf2708b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 16:37:43 -0700 Subject: [PATCH 18/74] work --- src/passes/PropagateStructGets.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index ca75e8f7a89..6d9322ac805 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -217,10 +217,6 @@ struct PropagateStructGets Index fieldIndex = getStructGets[0]->index; bool isSigned = getStructGets[0]->signed_; Type fieldType = getStructGets[0]->type; - Type localType = fieldType.isRef() ? fieldType.with(Nullable) : fieldType; - if (!localType.isDefaultable()) { - continue; - } bool ok = true; for (auto* sget : getStructGets) { @@ -329,8 +325,14 @@ struct PropagateStructGets } } - // Success! The pattern fits, prepare to optimize this local. - projectedLocals[localIndex] = builder.addVar(func, localType); + // Success! The pattern fits, prepare to optimize this local by adding a + // local to store the value in its field. The local is normally of the + // type of the field, but nullable since the . + Type newLocalType = fieldType.isRef() ? fieldType.with(Nullable) : fieldType; + if (!newLocalType.isDefaultable()) { + continue; + } + projectedLocals[localIndex] = builder.addVar(func, newLocalType); } // Rewrite recorded struct.gets in post-order. From 15be564035c24a762416a9125d1bdea5bea096ed Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 16:47:35 -0700 Subject: [PATCH 19/74] work --- src/passes/PropagateStructGets.cpp | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 6d9322ac805..5c06667fee5 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -98,9 +98,6 @@ struct PropagateStructGets : public WalkerPass> { bool isFunctionParallel() override { return true; } - // New locals created here are always defaultable (nullable when references). - bool requiresNonNullableLocalFixups() override { return false; } - std::unique_ptr create() override { return std::make_unique(); } @@ -326,13 +323,8 @@ struct PropagateStructGets } // Success! The pattern fits, prepare to optimize this local by adding a - // local to store the value in its field. The local is normally of the - // type of the field, but nullable since the . - Type newLocalType = fieldType.isRef() ? fieldType.with(Nullable) : fieldType; - if (!newLocalType.isDefaultable()) { - continue; - } - projectedLocals[localIndex] = builder.addVar(func, newLocalType); + // local to store the value in its field. + projectedLocals[localIndex] = builder.addVar(func, fieldType); } // Rewrite recorded struct.gets in post-order. @@ -346,9 +338,6 @@ struct PropagateStructGets Index newLocal = it->second; Expression* rep = builder.makeLocalGet(newLocal, func->getLocalType(newLocal)); - if (sget->type.isNonNullable()) { - rep = builder.makeRefAs(RefAsNonNull, rep); - } *currp = rep; } else if (auto* set = sget->ref->dynCast()) { auto it = projectedLocals.find(set->index); @@ -362,9 +351,6 @@ struct PropagateStructGets set->value = sget; set->makeTee(func->getLocalType(newLocal)); Expression* rep = set; - if (sget->type.isNonNullable()) { - rep = builder.makeRefAs(RefAsNonNull, rep); - } *currp = rep; } else if (auto* iff = sget->ref->dynCast()) { iff->ifTrue = builder.makeStructGet( From 8b0309ecb1c04cf96970ef91fd99c41b702196ec Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 16:48:26 -0700 Subject: [PATCH 20/74] work --- src/passes/PropagateStructGets.cpp | 6 ++---- test/lit/passes/propagate-struct-gets.wast | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 5c06667fee5..55958dd1ddb 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -336,9 +336,8 @@ struct PropagateStructGets continue; } Index newLocal = it->second; - Expression* rep = + *currp = builder.makeLocalGet(newLocal, func->getLocalType(newLocal)); - *currp = rep; } else if (auto* set = sget->ref->dynCast()) { auto it = projectedLocals.find(set->index); if (it == projectedLocals.end()) { @@ -350,8 +349,7 @@ struct PropagateStructGets set->index = newLocal; set->value = sget; set->makeTee(func->getLocalType(newLocal)); - Expression* rep = set; - *currp = rep; + *currp = set; } else if (auto* iff = sget->ref->dynCast()) { iff->ifTrue = builder.makeStructGet( sget->index, iff->ifTrue, sget->order, sget->type, sget->signed_); diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 027a11d482c..11e6ebed626 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -750,7 +750,7 @@ ;; H2L-NEXT: ) (func $project-single-set (result i32) ;; We leave a single local.set for Heap2Local (that situation has no - ;; overlapping live ranges etc.). + ;; overlapping live ranges etc.), and do not optimize. (local $ref (ref null $struct)) (local.set $ref (struct.new $struct From ff69080f63fd4914e3a7a165b40f8350426e3077 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 16:55:37 -0700 Subject: [PATCH 21/74] work --- src/passes/PropagateStructGets.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 55958dd1ddb..7eea92a11c0 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -335,22 +335,32 @@ struct PropagateStructGets if (it == projectedLocals.end()) { continue; } + + // Replace (struct.get (local.get)) with a local.get of the field, which + // is now in a local directly. Index newLocal = it->second; *currp = builder.makeLocalGet(newLocal, func->getLocalType(newLocal)); - } else if (auto* set = sget->ref->dynCast()) { - auto it = projectedLocals.find(set->index); + } else if (auto* tee = sget->ref->dynCast()) { + auto it = projectedLocals.find(tee->index); if (it == projectedLocals.end()) { continue; } + + // Replace (struct.get (local.tee (REF))) with + // (local.tee (struct.get (REF))). Before we stashed the ref in a local, + // and now we stash the field. Index newLocal = it->second; - sget->ref = set->value; + sget->ref = tee->value; sget->finalize(); - set->index = newLocal; - set->value = sget; - set->makeTee(func->getLocalType(newLocal)); - *currp = set; + tee->index = newLocal; + tee->value = sget; + tee->makeTee(func->getLocalType(newLocal)); + *currp = tee; } else if (auto* iff = sget->ref->dynCast()) { + // Replace (struct.get (if .. (A) (B))) with + // (if .. (struct.get (A)) (struct.get (B))). This avoids an overlapping + // live range for the allocations in the If arms. iff->ifTrue = builder.makeStructGet( sget->index, iff->ifTrue, sget->order, sget->type, sget->signed_); iff->ifFalse = builder.makeStructGet( From 0c2a6d2652c7d90544b135974fe2972ea1d2f7fb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 16:56:55 -0700 Subject: [PATCH 22/74] work --- src/passes/PropagateStructGets.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 7eea92a11c0..d769fcb37f0 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -370,8 +370,11 @@ struct PropagateStructGets } } - // Rewrite non-tee local.sets of projected locals. + // Rewrite non-tee local.sets of projected locals: all local.sets now store + // the field. for (auto [x, newLocal] : projectedLocals) { + // All the struct.gets are the same, so it is enough to look at the first + // and copy it. auto* sget = getParents[x][0]; for (auto* set : sets[x]) { if (!set->isTee()) { From 84bbb61b81b6503d1391b45bc171b597ed29c23e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 17:00:58 -0700 Subject: [PATCH 23/74] fuzz --- scripts/fuzz_opt.py | 1 + 1 file changed, 1 insertion(+) 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",), From 86c90f3656ee46e2078ce4ed342bdf651dae4f6d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 17:02:51 -0700 Subject: [PATCH 24/74] help --- test/lit/help/wasm-metadce.test | 4 ++++ test/lit/help/wasm-opt.test | 4 ++++ test/lit/help/wasm2js.test | 4 ++++ 3 files changed, 12 insertions(+) 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: From 934598d6a98890cc27f70d3e12e8b6a4118d6d7b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 08:46:02 -0700 Subject: [PATCH 25/74] work --- src/passes/PropagateStructGets.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index d769fcb37f0..fcbf11795c5 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -80,7 +80,11 @@ // optimizations make it worthwhile. // // This disentangles shared struct locals and control-flow merges so that -// Heap2Local can optimize the underlying allocations independently. +// Heap2Local can optimize the underlying allocations independently. Another way +// to look at it is that it handles a simple case of escape analysis, the easy +// situation where the heap allocation is just used as data - we immediately +// struct.get data from it, and never use it otherwise, not in ref.eq or +// anything else. // #include "ir/local-graph.h" @@ -158,6 +162,7 @@ struct PropagateStructGets structGets.push_back(getCurrentPointer()); } } else if (auto* iff = curr->ref->dynCast()) { + // TODO Handle arbitrary nesting here, including leading to gets/tees. if (iff->ifFalse && iff->type != Type::unreachable && iff->ifTrue->type != Type::unreachable && iff->ifFalse->type != Type::unreachable) { @@ -198,12 +203,13 @@ struct PropagateStructGets continue; } - // Check if the number of struct.gets is equal to the total number of - // gets for the local. If so, then we understand all the gets, and this + // 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; - assert(!getStructGets.empty()); // if we added the local, the vec is nonempty + // There must be a struct.get, if we added one for this local. + assert(!getStructGets.empty()); if (getStructGets.size() != numGets[localIndex]) { continue; } From 931dd17ba73243076f2ad42f51612a5bc96a2ae7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 09:33:47 -0700 Subject: [PATCH 26/74] work --- src/passes/PropagateStructGets.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index fcbf11795c5..4941aeee7e6 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -15,8 +15,11 @@ */ // -// Propagate struct.get operations backwards into local sets/tees and into If -// arms that allocate structs: +// Identify when a GC reference local is always read from using struct.get, as +// opposed to operations on the reference itself (like passing it out, or doing +// ref.eq). When all local.gets are immediately consumed by struct.gets, then +// the GC allocation is basically used as data, and we can store that data +// directly, simplifying things. More concretely: // // 1. When a struct-typed local is written and every read of it // (including via local.tee) is immediately consumed by an identical From 7748a47cd849e2ea6397affd3fdebf6a07745687 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 09:35:45 -0700 Subject: [PATCH 27/74] work --- src/passes/PropagateStructGets.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 4941aeee7e6..82b4e087b6e 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -22,10 +22,9 @@ // directly, simplifying things. More concretely: // // 1. When a struct-typed local is written and every read of it -// (including via local.tee) is immediately consumed by an identical -// struct.get of the same field, replace the struct local with a local of -// the field's type and perform the struct.get at each local.set/local.tee. -// For example: +// (including via local.tee) is immediately consumed by a struct.get, +// replace the struct local with a local of the field's type and perform +// the struct.get at each local.set/local.tee. For example: // // x = new Foo(42); // bar(x.value, x.value); @@ -33,7 +32,7 @@ // => // // // Store the value directly. -// x_value = (new Foo(42)).value; +// x_value = (new Foo(42)).value; // do the struct.get earlier, here // bar(x_value, x_value); // // We do this when we detect a simple situation where all the uses are just @@ -58,7 +57,7 @@ // if (condition) x_value = (new Foo(1337)).value; // bar(x_value, x_value); // -// TODO verify in tests +// TODO verify this exact thing in a test // // 2. When a struct.get consumes an if whose arm(s) allocate a struct via // struct.new, sink the struct.get into the if arms. For example: @@ -85,9 +84,7 @@ // This disentangles shared struct locals and control-flow merges so that // Heap2Local can optimize the underlying allocations independently. Another way // to look at it is that it handles a simple case of escape analysis, the easy -// situation where the heap allocation is just used as data - we immediately -// struct.get data from it, and never use it otherwise, not in ref.eq or -// anything else. +// situation where the heap allocation is just used as data. // #include "ir/local-graph.h" From 39bb1c93d67079eb24f5fbb42bd9995da90ca8e0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 10:07:36 -0700 Subject: [PATCH 28/74] work --- src/passes/PropagateStructGets.cpp | 14 --- test/lit/passes/propagate-struct-gets.wast | 122 +-------------------- 2 files changed, 6 insertions(+), 130 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 82b4e087b6e..526ffc6ef21 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -161,20 +161,6 @@ struct PropagateStructGets teeParents[set] = curr; structGets.push_back(getCurrentPointer()); } - } else if (auto* iff = curr->ref->dynCast()) { - // TODO Handle arbitrary nesting here, including leading to gets/tees. - if (iff->ifFalse && iff->type != Type::unreachable && - iff->ifTrue->type != Type::unreachable && - iff->ifFalse->type != Type::unreachable) { - auto* module = getModule(); - const auto& passOptions = getPassOptions(); - if (Properties::getFallthrough(iff->ifTrue, passOptions, *module) - ->is() || - Properties::getFallthrough(iff->ifFalse, passOptions, *module) - ->is()) { - structGets.push_back(getCurrentPointer()); - } - } } } diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 11e6ebed626..e00b1fb6a83 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -114,116 +114,6 @@ ) ) - ;; CHECK: (func $sink-struct-get-if (type $4) (param $x i32) (result i32) - ;; CHECK-NEXT: (if (result i32) - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (struct.get $struct 0 - ;; CHECK-NEXT: (struct.new_default $struct) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (struct.get $struct 0 - ;; CHECK-NEXT: (struct.new $struct - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; H2L: (func $sink-struct-get-if (type $3) (param $x i32) (result i32) - ;; H2L-NEXT: (local $1 i32) - ;; H2L-NEXT: (local $2 i32) - ;; H2L-NEXT: (local $3 i32) - ;; H2L-NEXT: (if (result i32) - ;; H2L-NEXT: (local.get $x) - ;; H2L-NEXT: (then - ;; H2L-NEXT: (drop - ;; H2L-NEXT: (block (result nullref) - ;; H2L-NEXT: (local.set $1 - ;; H2L-NEXT: (i32.const 0) - ;; H2L-NEXT: ) - ;; H2L-NEXT: (ref.null none) - ;; H2L-NEXT: ) - ;; H2L-NEXT: ) - ;; H2L-NEXT: (local.get $1) - ;; H2L-NEXT: ) - ;; H2L-NEXT: (else - ;; 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.get $2) - ;; H2L-NEXT: ) - ;; H2L-NEXT: ) - ;; H2L-NEXT: ) - (func $sink-struct-get-if (param $x i32) (result i32) - ;; A struct.get directly consuming an if that allocates structs in its arms - ;; should be sunk into the arms so Heap2Local can eliminate both - ;; allocations. - (struct.get $struct 0 - (if (result (ref $struct)) - (local.get $x) - (then - (struct.new_default $struct) - ) - (else - (struct.new $struct - (i32.const 42) - ) - ) - ) - ) - ) - - ;; CHECK: (func $no-sink-struct-get-if-no-alloc (type $6) (param $x i32) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) - ;; CHECK-NEXT: (struct.get $struct 0 - ;; CHECK-NEXT: (if (result (ref $struct)) - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (local.get $s1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (local.get $s2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; H2L: (func $no-sink-struct-get-if-no-alloc (type $6) (param $x i32) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) - ;; H2L-NEXT: (struct.get $struct 0 - ;; H2L-NEXT: (if (result (ref $struct)) - ;; H2L-NEXT: (local.get $x) - ;; H2L-NEXT: (then - ;; H2L-NEXT: (local.get $s1) - ;; H2L-NEXT: ) - ;; H2L-NEXT: (else - ;; H2L-NEXT: (local.get $s2) - ;; H2L-NEXT: ) - ;; H2L-NEXT: ) - ;; H2L-NEXT: ) - ;; H2L-NEXT: ) - (func $no-sink-struct-get-if-no-alloc (param $x i32) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) - ;; Do not sink struct.get into an if when neither arm allocates a struct.new. - (struct.get $struct 0 - (if (result (ref $struct)) - (local.get $x) - (then - (local.get $s1) - ) - (else - (local.get $s2) - ) - ) - ) - ) - ;; CHECK: (func $no-project-struct-get-uninitialized (type $5) (param $x i32) (param $y i32) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (if @@ -439,7 +329,7 @@ ) ) - ;; CHECK: (func $project-struct-get-tee-immutable-nn-ref (type $7) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; CHECK: (func $project-struct-get-tee-immutable-nn-ref (type $6) (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 @@ -473,7 +363,7 @@ ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $project-struct-get-tee-immutable-nn-ref (type $7) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; H2L: (func $project-struct-get-tee-immutable-nn-ref (type $6) (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 @@ -540,7 +430,7 @@ ) ) - ;; CHECK: (func $no-project-struct-get-tee-mutable (type $8) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; CHECK: (func $no-project-struct-get-tee-mutable (type $7) (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 @@ -565,7 +455,7 @@ ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-project-struct-get-tee-mutable (type $8) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; H2L: (func $no-project-struct-get-tee-mutable (type $7) (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 @@ -702,7 +592,7 @@ ) ) - ;; CHECK: (func $project-single-set (type $9) (result i32) + ;; CHECK: (func $project-single-set (type $8) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $struct @@ -718,7 +608,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $project-single-set (type $9) (result i32) + ;; H2L: (func $project-single-set (type $8) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) From 7c504b33c2ff3f418f48444f2381a807baad44e3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 10:09:56 -0700 Subject: [PATCH 29/74] work --- src/passes/PropagateStructGets.cpp | 80 ++++++++++-------------------- 1 file changed, 27 insertions(+), 53 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 526ffc6ef21..8070dc11670 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -21,65 +21,38 @@ // the GC allocation is basically used as data, and we can store that data // directly, simplifying things. More concretely: // -// 1. When a struct-typed local is written and every read of it -// (including via local.tee) is immediately consumed by a struct.get, -// replace the struct local with a local of the field's type and perform -// the struct.get at each local.set/local.tee. For example: +// x = new Foo(42); +// bar(x.value, x.value); // -// 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); // -// // Store the value directly. -// x_value = (new Foo(42)).value; // do the struct.get earlier, here -// bar(x_value, x_value); +// We do this when we detect a simple situation where all the uses are just +// gets like that. In this case, we know Heap2Local can later optimize +// the allocation away. That is, we are detecting a simple case where +// nothing escapes and things are easy to optimize, and we handle it +// directly. This is simpler than making Heap2Local able to reason about +// groups of allocations and their possibly overlapping livenesses, in +// situations like this: // -// We do this when we detect a simple situation where all the uses are just -// gets like that. In this case, we know Heap2Local can later optimize -// the allocation away. That is, we are detecting a simple case where -// nothing escapes and things are easy to optimize, and we handle it -// directly. This is simpler than making Heap2Local able to reason about -// groups of allocations and their possibly overlapping livenesses, in -// situations like this: +// x = new Foo(42); +// if (condition) x = new Foo(1337); +// bar(x.value, x.value); // -// x = new Foo(42); -// if (condition) x = new Foo(1337); -// bar(x.value, x.value); +// The allocations here have overlapping live ranges, and Heap2Local +// cannot handle them (it sees the x.value operations are not exclusive to +// any single allocation). After we simplify things in this pass, the +// situation becomes trivial for Heap2Local, as the allocations do not +// overlap: // -// The allocations here have overlapping live ranges, and Heap2Local -// cannot handle them (it sees the x.value operations are not exclusive to -// any single allocation). After we simplify things in this pass, the -// situation becomes trivial for Heap2Local, as the allocations do not -// overlap: +// x_value = (new Foo(42)).value; +// if (condition) x_value = (new Foo(1337)).value; +// bar(x_value, x_value); // -// x_value = (new Foo(42)).value; -// if (condition) x_value = (new Foo(1337)).value; -// bar(x_value, x_value); -// -// TODO verify this exact thing in a test -// -// 2. When a struct.get consumes an if whose arm(s) allocate a struct via -// struct.new, sink the struct.get into the if arms. For example: -// -// (struct.get $x 3 -// (if -// (..condition..) -// (struct.new ..) -// (struct.new ..))) -// -// => -// -// (struct.get $x 3 -// (if -// (..condition..) -// (struct.new ..) -// (struct.new ..))) -// -// This could be done as a peephole optimization in OptimizeInstructions, -// but it generally *increases* code size. Here we only do it when we -// detect the proper situation as in point 1, where Heap2Local's followup -// optimizations make it worthwhile. +// TODO verify this exact thing in a test // // This disentangles shared struct locals and control-flow merges so that // Heap2Local can optimize the underlying allocations independently. Another way @@ -150,7 +123,7 @@ struct PropagateStructGets } // See if the reference is one of the patterns we recognize. - // TODO fallthrough + // TODO fallthrough, but careful of br_if which can escape the value if (auto* get = curr->ref->dynCast()) { if (isStructVar(get->index)) { getParents[get->index].push_back(curr); @@ -162,6 +135,7 @@ struct PropagateStructGets structGets.push_back(getCurrentPointer()); } } + // TODO: handle more things flowing into struct.get, like an If } void visitFunction(Function* func) { From 53a7f3599ecbe7e3310648b8cd844854313a7ace Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 10:11:17 -0700 Subject: [PATCH 30/74] work --- src/passes/PropagateStructGets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 8070dc11670..a93196532e7 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -86,7 +86,7 @@ struct PropagateStructGets // The sets for each local index. std::unordered_map> sets; - // The parent struct.get for each local. + // The parent struct.gets for each local index. std::unordered_map> getParents; // The parent struct.get for each tee. From cda0c348c1fb323508ef0ff222678f9ef74eb08a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 10:12:08 -0700 Subject: [PATCH 31/74] work --- test/lit/passes/propagate-struct-gets.wast | 54 +++++++++++----------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index e00b1fb6a83..dff93e26e61 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -21,7 +21,7 @@ ;; H2L: (type $struct-immutable (struct (field i32))) (type $struct-immutable (struct (field i32))) - ;; CHECK: (func $project-struct-get-if (type $4) (param $x i32) (result i32) + ;; CHECK: (func $optimize-struct-get-if (type $4) (param $x i32) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (if @@ -45,7 +45,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; H2L: (func $project-struct-get-if (type $3) (param $x i32) (result i32) + ;; 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) @@ -89,8 +89,8 @@ ;; H2L-NEXT: ) ;; H2L-NEXT: (local.get $2) ;; H2L-NEXT: ) - (func $project-struct-get-if (param $x i32) (result i32) - ;; Even when the field is mutable, we can project $ref to an i32 local + (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)) @@ -114,7 +114,7 @@ ) ) - ;; CHECK: (func $no-project-struct-get-uninitialized (type $5) (param $x i32) (param $y i32) (result i32) + ;; CHECK: (func $no-optimize-struct-get-uninitialized (type $5) (param $x i32) (param $y i32) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $x) @@ -140,7 +140,7 @@ ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-project-struct-get-uninitialized (type $4) (param $x i32) (param $y i32) (result i32) + ;; H2L: (func $no-optimize-struct-get-uninitialized (type $4) (param $x i32) (param $y i32) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (if ;; H2L-NEXT: (local.get $x) @@ -166,9 +166,9 @@ ;; H2L-NEXT: (local.get $ref) ;; H2L-NEXT: ) ;; H2L-NEXT: ) - (func $no-project-struct-get-uninitialized (param $x i32) (param $y i32) (result i32) - ;; Similar to $project-struct-get-if, but $ref is not initialized on all - ;; paths reaching the struct.get, so it can be null and trap. Do not project + (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 @@ -196,7 +196,7 @@ ) ) - ;; CHECK: (func $project-struct-get-tee-immutable (type $5) (param $x i32) (param $y i32) (result i32) + ;; CHECK: (func $optimize-struct-get-tee-immutable (type $5) (param $x i32) (param $y i32) (result i32) ;; CHECK-NEXT: (local $cur (ref $struct-immutable)) ;; CHECK-NEXT: (local $ref (ref null $struct-immutable)) ;; CHECK-NEXT: (local $4 i32) @@ -231,7 +231,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) - ;; H2L: (func $project-struct-get-tee-immutable (type $4) (param $x i32) (param $y i32) (result i32) + ;; H2L: (func $optimize-struct-get-tee-immutable (type $4) (param $x i32) (param $y i32) (result i32) ;; H2L-NEXT: (local $cur (ref $struct-immutable)) ;; H2L-NEXT: (local $ref (ref null $struct-immutable)) ;; H2L-NEXT: (local $4 i32) @@ -288,7 +288,7 @@ ;; H2L-NEXT: ) ;; H2L-NEXT: (local.get $4) ;; H2L-NEXT: ) - (func $project-struct-get-tee-immutable (param $x i32) (param $y i32) (result i32) + (func $optimize-struct-get-tee-immutable (param $x i32) (param $y i32) (result i32) ;; Two independent allocations in $cur 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. Projecting $ref to an i32 @@ -329,7 +329,7 @@ ) ) - ;; CHECK: (func $project-struct-get-tee-immutable-nn-ref (type $6) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; CHECK: (func $optimize-struct-get-tee-immutable-nn-ref (type $6) (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 @@ -363,7 +363,7 @@ ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $project-struct-get-tee-immutable-nn-ref (type $6) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; H2L: (func $optimize-struct-get-tee-immutable-nn-ref (type $6) (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 @@ -397,9 +397,9 @@ ;; H2L-NEXT: (local.get $4) ;; H2L-NEXT: ) ;; H2L-NEXT: ) - (func $project-struct-get-tee-immutable-nn-ref (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + (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 projected local uses a nullable type with + ;; ((ref struct)). The optimizeed local uses a nullable type with ;; ref.as_non_null on the tees and get so validation succeeds across blocks. (local $ref (ref null $struct-nn-ref)) (block $err @@ -430,7 +430,7 @@ ) ) - ;; CHECK: (func $no-project-struct-get-tee-mutable (type $7) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; CHECK: (func $no-optimize-struct-get-tee-mutable (type $7) (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 @@ -455,7 +455,7 @@ ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-project-struct-get-tee-mutable (type $7) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; H2L: (func $no-optimize-struct-get-tee-mutable (type $7) (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 @@ -480,8 +480,8 @@ ;; H2L-NEXT: (local.get $ref) ;; H2L-NEXT: ) ;; H2L-NEXT: ) - (func $no-project-struct-get-tee-mutable (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) - ;; Do not project when tees write arbitrary struct references and the field + (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. (local $ref (ref null $struct)) (block $err @@ -508,7 +508,7 @@ ) ) - ;; CHECK: (func $no-project-struct-get-different-fields (type $4) (param $x i32) (result i32) + ;; CHECK: (func $no-optimize-struct-get-different-fields (type $4) (param $x i32) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct-2fields)) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $x) @@ -535,7 +535,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-project-struct-get-different-fields (type $3) (param $x i32) (result i32) + ;; H2L: (func $no-optimize-struct-get-different-fields (type $3) (param $x i32) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct-2fields)) ;; H2L-NEXT: (if ;; H2L-NEXT: (local.get $x) @@ -562,8 +562,8 @@ ;; H2L-NEXT: ) ;; H2L-NEXT: ) ;; H2L-NEXT: ) - (func $no-project-struct-get-different-fields (param $x i32) (result i32) - ;; Do not project when gets of $ref read different fields (field 0 vs + (func $no-optimize-struct-get-different-fields (param $x i32) (result i32) + ;; Do not optimize when gets of $ref read different fields (field 0 vs ;; field 1). (local $ref (ref null $struct-2fields)) (if @@ -592,7 +592,7 @@ ) ) - ;; CHECK: (func $project-single-set (type $8) (result i32) + ;; CHECK: (func $optimize-single-set (type $8) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $struct @@ -608,7 +608,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $project-single-set (type $8) (result i32) + ;; H2L: (func $optimize-single-set (type $8) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) @@ -638,7 +638,7 @@ ;; H2L-NEXT: ) ;; H2L-NEXT: ) ;; H2L-NEXT: ) - (func $project-single-set (result i32) + (func $optimize-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)) From 34452b7c5ba0e52d2ad15c95b6c60100340682b1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 10:17:06 -0700 Subject: [PATCH 32/74] work --- test/lit/passes/propagate-struct-gets.wast | 158 +++++++++++++++++++-- 1 file changed, 150 insertions(+), 8 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index dff93e26e61..2e9c41aa696 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -21,7 +21,7 @@ ;; H2L: (type $struct-immutable (struct (field i32))) (type $struct-immutable (struct (field i32))) - ;; CHECK: (func $optimize-struct-get-if (type $4) (param $x i32) (result 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 @@ -45,7 +45,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-struct-get-if (type $3) (param $x i32) (result i32) + ;; H2L: (func $optimize-struct-get-if (type $4) (param $x i32) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $2 i32) ;; H2L-NEXT: (local $3 i32) @@ -114,7 +114,7 @@ ) ) - ;; CHECK: (func $no-optimize-struct-get-uninitialized (type $5) (param $x i32) (param $y i32) (result i32) + ;; CHECK: (func $no-optimize-struct-get-uninitialized (type $4) (param $x i32) (param $y i32) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $x) @@ -140,7 +140,7 @@ ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-optimize-struct-get-uninitialized (type $4) (param $x i32) (param $y i32) (result i32) + ;; H2L: (func $no-optimize-struct-get-uninitialized (type $3) (param $x i32) (param $y i32) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (if ;; H2L-NEXT: (local.get $x) @@ -196,7 +196,149 @@ ) ) - ;; CHECK: (func $optimize-struct-get-tee-immutable (type $5) (param $x i32) (param $y i32) (result i32) + ;; CHECK: (func $mixture (type $4) (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: (local.set $4 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; 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 $3) (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: (local.set $4 + ;; H2L-NEXT: (block (result i32) + ;; 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.get $5) + ;; H2L-NEXT: ) + ;; H2L-NEXT: ) + ;; H2L-NEXT: (if + ;; H2L-NEXT: (local.get $x) + ;; H2L-NEXT: (then + ;; H2L-NEXT: (local.set $4 + ;; H2L-NEXT: (block (result i32) + ;; 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.get $6) + ;; 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 $4) (param $x i32) (param $y i32) (result i32) ;; CHECK-NEXT: (local $cur (ref $struct-immutable)) ;; CHECK-NEXT: (local $ref (ref null $struct-immutable)) ;; CHECK-NEXT: (local $4 i32) @@ -231,7 +373,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-struct-get-tee-immutable (type $4) (param $x i32) (param $y i32) (result i32) + ;; H2L: (func $optimize-struct-get-tee-immutable (type $3) (param $x i32) (param $y i32) (result i32) ;; H2L-NEXT: (local $cur (ref $struct-immutable)) ;; H2L-NEXT: (local $ref (ref null $struct-immutable)) ;; H2L-NEXT: (local $4 i32) @@ -508,7 +650,7 @@ ) ) - ;; CHECK: (func $no-optimize-struct-get-different-fields (type $4) (param $x i32) (result i32) + ;; CHECK: (func $no-optimize-struct-get-different-fields (type $5) (param $x i32) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct-2fields)) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $x) @@ -535,7 +677,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-optimize-struct-get-different-fields (type $3) (param $x i32) (result i32) + ;; H2L: (func $no-optimize-struct-get-different-fields (type $4) (param $x i32) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct-2fields)) ;; H2L-NEXT: (if ;; H2L-NEXT: (local.get $x) From 0e455523e62914f56a798136c64fa495791fc615 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 10:17:48 -0700 Subject: [PATCH 33/74] work --- src/passes/PropagateStructGets.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index a93196532e7..0e468eb85fa 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -147,7 +147,7 @@ struct PropagateStructGets auto* module = getModule(); Builder builder(*module); LazyLocalGraph localGraph(func, module); - std::unordered_map projectedLocals; + std::unordered_map optimizedLocals; for (auto& [localIndex, localSets] : sets) { if (localSets.size() < 2) { @@ -290,15 +290,15 @@ struct PropagateStructGets // Success! The pattern fits, prepare to optimize this local by adding a // local to store the value in its field. - projectedLocals[localIndex] = builder.addVar(func, fieldType); + optimizedLocals[localIndex] = builder.addVar(func, fieldType); } // Rewrite recorded struct.gets in post-order. for (auto** currp : structGets) { auto* sget = (*currp)->cast(); if (auto* get = sget->ref->dynCast()) { - auto it = projectedLocals.find(get->index); - if (it == projectedLocals.end()) { + auto it = optimizedLocals.find(get->index); + if (it == optimizedLocals.end()) { continue; } @@ -308,8 +308,8 @@ struct PropagateStructGets *currp = builder.makeLocalGet(newLocal, func->getLocalType(newLocal)); } else if (auto* tee = sget->ref->dynCast()) { - auto it = projectedLocals.find(tee->index); - if (it == projectedLocals.end()) { + auto it = optimizedLocals.find(tee->index); + if (it == optimizedLocals.end()) { continue; } @@ -336,9 +336,9 @@ struct PropagateStructGets } } - // Rewrite non-tee local.sets of projected locals: all local.sets now store + // Rewrite non-tee local.sets of optimized locals: all local.sets now store // the field. - for (auto [x, newLocal] : projectedLocals) { + for (auto [x, newLocal] : optimizedLocals) { // All the struct.gets are the same, so it is enough to look at the first // and copy it. auto* sget = getParents[x][0]; From 23ea839e055d32fff13b30cb2275d1e98e1a7b49 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 10:25:53 -0700 Subject: [PATCH 34/74] work --- src/passes/PropagateStructGets.cpp | 33 ++++++++++++++---------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 0e468eb85fa..abc85b5ad8f 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -17,8 +17,8 @@ // // Identify when a GC reference local is always read from using struct.get, as // opposed to operations on the reference itself (like passing it out, or doing -// ref.eq). When all local.gets are immediately consumed by struct.gets, then -// the GC allocation is basically used as data, and we can store that data +// ref.eq). When all local.gets/tees are immediately consumed by struct.gets +// then the GC allocation is basically used as data, and we can store that data // directly, simplifying things. More concretely: // // x = new Foo(42); @@ -30,30 +30,26 @@ // x_value = (new Foo(42)).value; // do the struct.get earlier, here // bar(x_value, x_value); // -// We do this when we detect a simple situation where all the uses are just -// gets like that. In this case, we know Heap2Local can later optimize -// the allocation away. That is, we are detecting a simple case where -// nothing escapes and things are easy to optimize, and we handle it -// directly. This is simpler than making Heap2Local able to reason about -// groups of allocations and their possibly overlapping livenesses, in -// situations like this: +// We do this when we detect a simple situation where all the uses are just +// gets like that. In this case, we know Heap2Local can later optimize the +// allocation away. That is, we are detecting a simple case where nothing +// escapes and things are easy to optimize, and we handle it directly. This is +// simpler than making Heap2Local able to reason about groups of allocations and +// their possibly overlapping livenesses, in situations like this: // // x = new Foo(42); // if (condition) x = new Foo(1337); // bar(x.value, x.value); // -// The allocations here have overlapping live ranges, and Heap2Local -// cannot handle them (it sees the x.value operations are not exclusive to -// any single allocation). After we simplify things in this pass, the -// situation becomes trivial for Heap2Local, as the allocations do not -// overlap: +// The allocations here have overlapping live ranges, and Heap2Local cannot +// handle them (it sees the x.value operations are not exclusive to any single +// allocation). After we simplify things in this pass, the situation becomes +// trivial for Heap2Local, as the allocations do not overlap: // // x_value = (new Foo(42)).value; // if (condition) x_value = (new Foo(1337)).value; // bar(x_value, x_value); // -// TODO verify this exact thing in a test -// // This disentangles shared struct locals and control-flow merges so that // Heap2Local can optimize the underlying allocations independently. Another way // to look at it is that it handles a simple case of escape analysis, the easy @@ -92,8 +88,9 @@ struct PropagateStructGets // The parent struct.get for each tee. std::unordered_map teeParents; - // All relevant struct.gets in the function, which means the struct.gets that - // operate on references that fit the patterns we can optimize. + // 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; // Check if a local index is a reference to a struct. Those are the locals we From a58f6f652f598f0b77be083686f7226d42f16cf7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 10:57:33 -0700 Subject: [PATCH 35/74] work --- src/passes/PropagateStructGets.cpp | 169 ++-- test/lit/passes/propagate-struct-gets.wast | 920 ++++++++++++++++++--- 2 files changed, 902 insertions(+), 187 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index abc85b5ad8f..77c7155adbf 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -56,6 +56,7 @@ // situation where the heap allocation is just used as data. // +#include "ir/bits.h" #include "ir/local-graph.h" #include "ir/properties.h" #include "pass.h" @@ -80,7 +81,7 @@ struct PropagateStructGets std::unordered_map numGets; // The sets for each local index. - std::unordered_map> sets; + std::unordered_map> sets; // The parent struct.gets for each local index. std::unordered_map> getParents; @@ -108,7 +109,7 @@ struct PropagateStructGets void visitLocalSet(LocalSet* curr) { if (isStructVar(curr->index)) { - sets[curr->index].push_back(curr); + sets[curr->index].push_back(getCurrentPointer()); } } @@ -144,7 +145,10 @@ struct PropagateStructGets auto* module = getModule(); Builder builder(*module); LazyLocalGraph localGraph(func, module); - std::unordered_map optimizedLocals; + + // For each optimized local, maps each used field index to the new local + // index that stores that field. + std::unordered_map> optimizedLocals; for (auto& [localIndex, localSets] : sets) { if (localSets.size() < 2) { @@ -171,30 +175,19 @@ struct PropagateStructGets continue; } - // Veirfy all the struct.gets operate on the same field in the struct, and - // in the same way. - // TODO: handle multiple field indexes, and mixtures of signed/unsigned - Index fieldIndex = getStructGets[0]->index; - bool isSigned = getStructGets[0]->signed_; - Type fieldType = getStructGets[0]->type; - - bool ok = true; + // Gather all the field indexes read from this local. + std::set usedFields; for (auto* sget : getStructGets) { - if (sget->index != fieldIndex || sget->signed_ != isSigned || - sget->type != fieldType) { - ok = false; - break; - } - } - if (!ok) { - continue; + 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* set : localSets) { + for (auto** setp : localSets) { + auto* set = (*setp)->cast(); if (set->value->type == Type::unreachable) { // Stop on dead code. ok = false; @@ -209,17 +202,9 @@ struct PropagateStructGets ok = false; break; } - // Check if the parent struct.get is compatible with all the ones - // before, that we checked for the parents of local.gets. - auto* pget = teeIt->second; - if (pget->index != fieldIndex || pget->signed_ != isSigned || - pget->type != fieldType) { - ok = false; - break; - } + usedFields.insert(teeIt->second->index); } else { // Not a tee: a normal local.set. - if (!set->value->is()) { allSetsAreUnescapingAllocs = false; } @@ -229,15 +214,23 @@ struct PropagateStructGets continue; } - // The field 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. - auto& field = func->getLocalType(localIndex) - .getHeapType() - .getStruct() - .fields[fieldIndex]; - if (field.mutable_ != Immutable && !allSetsAreUnescapingAllocs) { - 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 are ruled out by the fact that we analyzed all + // local.get/tee operations, and all flowed into struct.get.) + 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 @@ -260,7 +253,8 @@ struct PropagateStructGets // the trap. We can rule this problem out by seeing that only non-nullable // values are written to the local (it is fine for the local itself to be // nullable, see below). - for (auto* set : localSets) { + for (auto** setp : localSets) { + auto* set = (*setp)->cast(); if (set->value->type.isNullable()) { ok = false; break; @@ -286,68 +280,79 @@ struct PropagateStructGets } // Success! The pattern fits, prepare to optimize this local by adding a - // local to store the value in its field. - optimizedLocals[localIndex] = builder.addVar(func, fieldType); + // local to store the value of each used field. + for (auto fieldIndex : usedFields) { + optimizedLocals[localIndex][fieldIndex] = + builder.addVar(func, fields[fieldIndex].type); + } } + auto getFieldFromLocal = + [&](Index localIndex, Index fieldIndex, bool signed_) -> Expression* { + auto& field = func->getLocalType(localIndex) + .getHeapType() + .getStruct() + .fields[fieldIndex]; + 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; + std::vector list = { + builder.makeLocalSet(localIndex, set->value)}; + for (auto [fieldIndex, newLocal] : optimizedLocals[localIndex]) { + auto& field = fields[fieldIndex]; + auto* sget = + builder.makeStructGet(fieldIndex, + builder.makeLocalGet(localIndex, localType), + MemoryOrder::Unordered, + field.type, + false); + list.push_back(builder.makeLocalSet(newLocal, sget)); + } + 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()) { - auto it = optimizedLocals.find(get->index); - if (it == optimizedLocals.end()) { + if (!optimizedLocals.contains(get->index)) { continue; } // Replace (struct.get (local.get)) with a local.get of the field, which // is now in a local directly. - Index newLocal = it->second; - *currp = - builder.makeLocalGet(newLocal, func->getLocalType(newLocal)); + *currp = getFieldFromLocal(get->index, sget->index, sget->signed_); } else if (auto* tee = sget->ref->dynCast()) { - auto it = optimizedLocals.find(tee->index); - if (it == optimizedLocals.end()) { + if (!optimizedLocals.contains(tee->index)) { continue; } - // Replace (struct.get (local.tee (REF))) with - // (local.tee (struct.get (REF))). Before we stashed the ref in a local, - // and now we stash the field. - Index newLocal = it->second; - sget->ref = tee->value; - sget->finalize(); - tee->index = newLocal; - tee->value = sget; - tee->makeTee(func->getLocalType(newLocal)); - *currp = tee; - } else if (auto* iff = sget->ref->dynCast()) { - // Replace (struct.get (if .. (A) (B))) with - // (if .. (struct.get (A)) (struct.get (B))). This avoids an overlapping - // live range for the allocations in the If arms. - iff->ifTrue = builder.makeStructGet( - sget->index, iff->ifTrue, sget->order, sget->type, sget->signed_); - iff->ifFalse = builder.makeStructGet( - sget->index, iff->ifFalse, sget->order, sget->type, sget->signed_); - iff->finalize(); - *currp = iff; + // Replace (struct.get (local.tee (REF))) with a block that sets the + // local, copies all used fields into their locals, and returns the + // tee'd field. + *currp = makeFieldSets(tee, sget); } } - // Rewrite non-tee local.sets of optimized locals: all local.sets now store - // the field. - for (auto [x, newLocal] : optimizedLocals) { - // All the struct.gets are the same, so it is enough to look at the first - // and copy it. - auto* sget = getParents[x][0]; - for (auto* set : sets[x]) { + // Rewrite non-tee local.sets of optimized locals: all local.sets now also + // store the used fields. + for (auto& [x, _] : optimizedLocals) { + for (auto** setp : sets[x]) { + auto* set = (*setp)->cast(); if (!set->isTee()) { - set->value = builder.makeStructGet(sget->index, - set->value, - MemoryOrder::Unordered, - sget->type, - sget->signed_); - set->index = newLocal; - set->finalize(); + *setp = makeFieldSets(set); } } } diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 2e9c41aa696..f0d5bf91cf3 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -8,44 +8,63 @@ ;; H2L: (type $struct (struct (field (mut i32)))) (type $struct (struct (field (mut i32)))) + ;; CHECK: (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-immutable (struct (field i32))) ;; CHECK: (type $struct-nn-ref (struct (field (ref struct)))) + ;; H2L: (type $struct-3fields-mixed (struct (field i32) (field (mut i32)) (field i64))) + ;; H2L: (type $struct-nn-ref (struct (field (ref struct)))) (type $struct-nn-ref (struct (field (ref struct)))) - ;; CHECK: (type $struct-2fields (struct (field i32) (field i32))) + ;; H2L: (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-immutable (struct (field i32))) + (type $struct-3fields-mixed (struct (field i32) (field (mut i32)) (field i64))) + + ;; H2L: (type $struct-packed (struct (field i8) (field i16))) + (type $struct-packed (struct (field i8) (field i16))) + (type $struct-immutable (struct (field i32))) - ;; CHECK: (func $optimize-struct-get-if (type $5) (param $x i32) (result i32) + ;; CHECK: (func $optimize-struct-get-if (type $6) (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: (struct.new_default $struct) + ;; 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: (struct.new $struct - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) + ;; 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 $4) (param $x i32) (result i32) + ;; 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) @@ -54,33 +73,39 @@ ;; 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: (block (result nullref) - ;; H2L-NEXT: (local.set $3 - ;; H2L-NEXT: (i32.const 0) - ;; H2L-NEXT: ) - ;; H2L-NEXT: (ref.null none) - ;; H2L-NEXT: ) + ;; 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: (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: (ref.null none) ;; H2L-NEXT: ) ;; H2L-NEXT: (local.get $4) ;; H2L-NEXT: ) @@ -114,7 +139,7 @@ ) ) - ;; CHECK: (func $no-optimize-struct-get-uninitialized (type $4) (param $x i32) (param $y i32) (result i32) + ;; CHECK: (func $no-optimize-struct-get-uninitialized (type $7) (param $x i32) (param $y i32) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $x) @@ -140,7 +165,7 @@ ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-optimize-struct-get-uninitialized (type $3) (param $x i32) (param $y i32) (result i32) + ;; H2L: (func $no-optimize-struct-get-uninitialized (type $4) (param $x i32) (param $y i32) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (if ;; H2L-NEXT: (local.get $x) @@ -196,24 +221,34 @@ ) ) - ;; CHECK: (func $mixture (type $4) (param $x i32) (param $y i32) (result i32) + ;; CHECK: (func $mixture (type $7) (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: (local.set $4 - ;; CHECK-NEXT: (struct.get $struct 0 + ;; 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: (local.set $4 - ;; CHECK-NEXT: (struct.get $struct 0 + ;; 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) @@ -237,43 +272,53 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $mixture (type $3) (param $x i32) (param $y i32) (result i32) + ;; H2L: (func $mixture (type $4) (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: (local.set $4 - ;; H2L-NEXT: (block (result i32) - ;; H2L-NEXT: (drop - ;; H2L-NEXT: (block (result nullref) - ;; H2L-NEXT: (local.set $5 - ;; H2L-NEXT: (i32.const 0) - ;; H2L-NEXT: ) + ;; 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: (local.get $5) ;; H2L-NEXT: ) ;; H2L-NEXT: ) ;; H2L-NEXT: (if ;; H2L-NEXT: (local.get $x) ;; H2L-NEXT: (then - ;; H2L-NEXT: (local.set $4 - ;; H2L-NEXT: (block (result i32) - ;; 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: (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: (local.get $6) ;; H2L-NEXT: ) ;; H2L-NEXT: ) ;; H2L-NEXT: (local.set $no @@ -338,7 +383,7 @@ ) ) - ;; CHECK: (func $optimize-struct-get-tee-immutable (type $4) (param $x i32) (param $y i32) (result i32) + ;; CHECK: (func $optimize-struct-get-tee-immutable (type $7) (param $x i32) (param $y i32) (result i32) ;; CHECK-NEXT: (local $cur (ref $struct-immutable)) ;; CHECK-NEXT: (local $ref (ref null $struct-immutable)) ;; CHECK-NEXT: (local $4 i32) @@ -349,10 +394,16 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br_if $err - ;; CHECK-NEXT: (local.tee $4 - ;; CHECK-NEXT: (struct.get $struct-immutable 0 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (local.get $cur) ;; 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 $cur @@ -361,10 +412,16 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br_if $err - ;; CHECK-NEXT: (local.tee $4 - ;; CHECK-NEXT: (struct.get $struct-immutable 0 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (local.get $cur) ;; 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 @@ -373,7 +430,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-struct-get-tee-immutable (type $3) (param $x i32) (param $y i32) (result i32) + ;; H2L: (func $optimize-struct-get-tee-immutable (type $4) (param $x i32) (param $y i32) (result i32) ;; H2L-NEXT: (local $cur (ref $struct-immutable)) ;; H2L-NEXT: (local $ref (ref null $struct-immutable)) ;; H2L-NEXT: (local $4 i32) @@ -394,13 +451,19 @@ ;; H2L-NEXT: ) ;; H2L-NEXT: ) ;; H2L-NEXT: (br_if $err - ;; H2L-NEXT: (local.tee $4 - ;; H2L-NEXT: (block (result i32) - ;; H2L-NEXT: (drop - ;; H2L-NEXT: (ref.null none) + ;; 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: (local.get $5) ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) ;; H2L-NEXT: ) ;; H2L-NEXT: ) ;; H2L-NEXT: (drop @@ -415,13 +478,19 @@ ;; H2L-NEXT: ) ;; H2L-NEXT: ) ;; H2L-NEXT: (br_if $err - ;; H2L-NEXT: (local.tee $4 - ;; H2L-NEXT: (block (result i32) - ;; H2L-NEXT: (drop - ;; H2L-NEXT: (ref.null none) + ;; 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: (local.get $7) ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $4) ;; H2L-NEXT: ) ;; H2L-NEXT: ) ;; H2L-NEXT: (return @@ -471,29 +540,41 @@ ) ) - ;; CHECK: (func $optimize-struct-get-tee-immutable-nn-ref (type $6) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; CHECK: (func $optimize-struct-get-tee-immutable-nn-ref (type $8) (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: (ref.as_non_null - ;; CHECK-NEXT: (local.tee $4 + ;; 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 $b1) + ;; 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: (ref.as_non_null - ;; CHECK-NEXT: (local.tee $4 + ;; 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 $b2) + ;; 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 @@ -510,24 +591,36 @@ ;; H2L-NEXT: (local $4 structref) ;; H2L-NEXT: (block $err ;; H2L-NEXT: (drop - ;; H2L-NEXT: (ref.as_non_null - ;; H2L-NEXT: (local.tee $4 + ;; 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 $b1) + ;; 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: (ref.as_non_null - ;; H2L-NEXT: (local.tee $4 + ;; 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 $b2) + ;; 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 @@ -572,7 +665,7 @@ ) ) - ;; CHECK: (func $no-optimize-struct-get-tee-mutable (type $7) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; CHECK: (func $no-optimize-struct-get-tee-mutable (type $9) (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 @@ -650,14 +743,26 @@ ) ) - ;; CHECK: (func $no-optimize-struct-get-different-fields (type $5) (param $x i32) (result i32) + ;; CHECK: (func $optimize-struct-get-different-fields (type $6) (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 @@ -666,47 +771,107 @@ ;; 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: (struct.get $struct-2fields 0 - ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (struct.get $struct-2fields 1 - ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-optimize-struct-get-different-fields (type $4) (param $x i32) (result i32) + ;; 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: (local.set $ref - ;; H2L-NEXT: (struct.new_default $struct-2fields) + ;; 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: (local.set $ref - ;; H2L-NEXT: (struct.new $struct-2fields - ;; H2L-NEXT: (i32.const 10) - ;; H2L-NEXT: (i32.const 20) + ;; 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: (struct.get $struct-2fields 0 - ;; H2L-NEXT: (local.get $ref) - ;; H2L-NEXT: ) - ;; H2L-NEXT: (struct.get $struct-2fields 1 - ;; H2L-NEXT: (local.get $ref) - ;; H2L-NEXT: ) + ;; H2L-NEXT: (local.get $2) + ;; H2L-NEXT: (local.get $3) ;; H2L-NEXT: ) ;; H2L-NEXT: ) - (func $no-optimize-struct-get-different-fields (param $x i32) (result i32) - ;; Do not optimize when gets of $ref read different fields (field 0 vs - ;; field 1). + (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) @@ -734,7 +899,552 @@ ) ) - ;; CHECK: (func $optimize-single-set (type $8) (result i32) + ;; CHECK: (func $optimize-multiple-fields-tee-partial (type $10) (param $x i32) (param $y i32) (result i64) + ;; CHECK-NEXT: (local $cur (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 $cur + ;; 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 $cur) + ;; 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 $cur + ;; 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 $cur) + ;; 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 $9) (param $x i32) (param $y i32) (result i64) + ;; H2L-NEXT: (local $cur (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 $cur (ref $struct-3fields-mixed)) + (local $ref (ref null $struct-3fields-mixed)) + (block $err + (local.set $cur + (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 $cur) + ) + ) + ) + (local.set $cur + (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 $cur) + ) + ) + ) + (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 $11) (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 $10) (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 $6) (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) + ) + ) + ) + ) + (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 $optimize-single-set (type $12) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $struct @@ -750,7 +1460,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-single-set (type $8) (result i32) + ;; H2L: (func $optimize-single-set (type $12) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) From e1649a03aea0c84dec40f52e33fcd8fbc1244d3e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 11:20:10 -0700 Subject: [PATCH 36/74] work --- src/passes/PropagateStructGets.cpp | 47 +++++++++++++++++------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 77c7155adbf..7282942876f 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -15,11 +15,9 @@ */ // -// Identify when a GC reference local is always read from using struct.get, as -// opposed to operations on the reference itself (like passing it out, or doing -// ref.eq). When all local.gets/tees are immediately consumed by struct.gets -// then the GC allocation is basically used as data, and we can store that data -// directly, simplifying things. More concretely: +// 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); @@ -30,30 +28,37 @@ // x_value = (new Foo(42)).value; // do the struct.get earlier, here // bar(x_value, x_value); // -// We do this when we detect a simple situation where all the uses are just -// gets like that. In this case, we know Heap2Local can later optimize the -// allocation away. That is, we are detecting a simple case where nothing -// escapes and things are easy to optimize, and we handle it directly. This is -// simpler than making Heap2Local able to reason about groups of allocations and -// their possibly overlapping livenesses, in situations like this: +// 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 +// +// This 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); // -// The allocations here have overlapping live ranges, and Heap2Local cannot -// handle them (it sees the x.value operations are not exclusive to any single -// allocation). After we simplify things in this pass, the situation becomes -// trivial for Heap2Local, as the allocations do not overlap: +// 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); // -// This disentangles shared struct locals and control-flow merges so that -// Heap2Local can optimize the underlying allocations independently. Another way -// to look at it is that it handles a simple case of escape analysis, the easy -// situation where the heap allocation is just used as data. +// 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" @@ -77,13 +82,13 @@ struct PropagateStructGets } // Track the number of gets for each local, so that we can see if all gets are - // accounted for and understood. + // 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. + // The parent struct.gets for each local index's gets. std::unordered_map> getParents; // The parent struct.get for each tee. From bd016853224fa449f1aebca7c459d318bba27dde Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 11:20:16 -0700 Subject: [PATCH 37/74] work --- src/passes/PropagateStructGets.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 7282942876f..bb5fe8eb9a4 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -102,6 +102,7 @@ struct PropagateStructGets // Check if a local index is a reference to a struct. Those are the locals we // try to optimize. bool isStructVar(Index index) { + return true; auto* func = getFunction(); return func->isVar(index) && func->getLocalType(index).isStruct(); } From 6117c658b80a526a3f562b78de630c2f5c396526 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 11:21:18 -0700 Subject: [PATCH 38/74] work --- src/passes/PropagateStructGets.cpp | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index bb5fe8eb9a4..b40e94fa345 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -99,24 +99,12 @@ struct PropagateStructGets // stored in optimizedLocals, for each local, below). std::vector structGets; - // Check if a local index is a reference to a struct. Those are the locals we - // try to optimize. - bool isStructVar(Index index) { - return true; - auto* func = getFunction(); - return func->isVar(index) && func->getLocalType(index).isStruct(); - } - void visitLocalGet(LocalGet* curr) { - if (isStructVar(curr->index)) { - numGets[curr->index]++; - } + numGets[curr->index]++; } void visitLocalSet(LocalSet* curr) { - if (isStructVar(curr->index)) { - sets[curr->index].push_back(getCurrentPointer()); - } + sets[curr->index].push_back(getCurrentPointer()); } void visitStructGet(StructGet* curr) { @@ -129,15 +117,11 @@ struct PropagateStructGets // 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()) { - if (isStructVar(get->index)) { - getParents[get->index].push_back(curr); - structGets.push_back(getCurrentPointer()); - } + getParents[get->index].push_back(curr); + structGets.push_back(getCurrentPointer()); } else if (auto* set = curr->ref->dynCast()) { - if (isStructVar(set->index)) { - teeParents[set] = curr; - structGets.push_back(getCurrentPointer()); - } + teeParents[set] = curr; + structGets.push_back(getCurrentPointer()); } // TODO: handle more things flowing into struct.get, like an If } From 6b93c8b1d860b877963f2a0015e80a3772f64a3a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 11:30:01 -0700 Subject: [PATCH 39/74] work --- src/passes/PropagateStructGets.cpp | 31 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index b40e94fa345..2acce92a609 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -137,14 +137,15 @@ struct PropagateStructGets LazyLocalGraph localGraph(func, module); // For each optimized local, maps each used field index to the new local - // index that stores that field. + // 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; 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 (and zero is - // trivially trapping). + // Heap2Local can handle things perfectly for a single set. continue; } @@ -165,7 +166,8 @@ struct PropagateStructGets continue; } - // Gather all the field indexes read from this local. + // 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); @@ -207,8 +209,8 @@ struct PropagateStructGets // 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 are ruled out by the fact that we analyzed all - // local.get/tee operations, and all flowed into struct.get.) + // that struct.*sets* - writes - are ruled out by the fact that we + // analyzed all local.get/tee operations, and all flowed into struct.get.) auto& fields = func->getLocalType(localIndex).getHeapType().getStruct().fields; if (!allSetsAreUnescapingAllocs) { @@ -230,19 +232,19 @@ struct PropagateStructGets // 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()`, + // 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 here, since we are moving the - // trap earlier, after `foo()` which might have prevented us from reaching - // the trap. We can rule this problem out by seeing that only non-nullable - // values are written to the local (it is fine for the local itself to be - // nullable, see below). + // 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 (it is fine for the local + // itself to be nullable, see below). for (auto** setp : localSets) { auto* set = (*setp)->cast(); if (set->value->type.isNullable()) { @@ -255,7 +257,8 @@ struct PropagateStructGets } // Finally, if the local is nullable, see if the null default value can - // actually be read. + // actually be read (which would mean we trap; we could handle this but + // realistically it should be rare TODO). if (func->getLocalType(localIndex).isNullable()) { for (auto* sget : getStructGets) { if (localGraph.getSets(sget->ref->cast()) From cde69f1dc9ad06cf2100c2b12eca4e2e1b7cdd5f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 11:36:15 -0700 Subject: [PATCH 40/74] work --- src/passes/PropagateStructGets.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 2acce92a609..ae12a52c4cd 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -286,6 +286,7 @@ struct PropagateStructGets .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)); @@ -297,10 +298,15 @@ struct PropagateStructGets 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), @@ -309,6 +315,9 @@ struct PropagateStructGets 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_)); @@ -316,25 +325,20 @@ struct PropagateStructGets return builder.makeBlock(list); }; - // Rewrite recorded struct.gets in post-order. + // 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; } - - // Replace (struct.get (local.get)) with a local.get of the field, which - // is now in a local directly. + // 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; } - - // Replace (struct.get (local.tee (REF))) with a block that sets the - // local, copies all used fields into their locals, and returns the - // tee'd field. + // struct.get of a local.tee first writes the fields, then reads. *currp = makeFieldSets(tee, sget); } } From 9b6d8810a10fa80ccfcca6d8f980daa4a60c7603 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 11:37:08 -0700 Subject: [PATCH 41/74] work --- src/passes/PropagateStructGets.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index ae12a52c4cd..fc69f9047a6 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -325,7 +325,7 @@ struct PropagateStructGets return builder.makeBlock(list); }; - // Rewrite recorded struct.gets in post-order: + // Rewrite recorded struct.gets in post-order. for (auto** currp : structGets) { auto* sget = (*currp)->cast(); if (auto* get = sget->ref->dynCast()) { @@ -343,11 +343,12 @@ struct PropagateStructGets } } - // Rewrite non-tee local.sets of optimized locals: all local.sets now also - // store the used fields. + // 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); } From 71fb51c2562eea573ea67b4b0b09e24e52be3a8d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 12:40:18 -0700 Subject: [PATCH 42/74] work --- src/passes/PropagateStructGets.cpp | 2 ++ test/lit/passes/propagate-struct-gets.wast | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index fc69f9047a6..b787fbd0bf7 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -40,6 +40,8 @@ // (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. +// // This 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 diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index f0d5bf91cf3..a7bb0b01037 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -1,5 +1,8 @@ ;; 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: wasm-opt %s --propagate-struct-gets -all -S -o - | filecheck %s ;; RUN: wasm-opt %s --propagate-struct-gets --heap2local -all -S -o - | filecheck %s --check-prefix=H2L @@ -9,6 +12,8 @@ (type $struct (struct (field (mut i32)))) ;; CHECK: (type $struct-3fields-mixed (struct (field i32) (field (mut i32)) (field i64))) + ;; 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))) @@ -17,22 +22,18 @@ ;; CHECK: (type $struct-immutable (struct (field i32))) ;; CHECK: (type $struct-nn-ref (struct (field (ref struct)))) - ;; H2L: (type $struct-3fields-mixed (struct (field i32) (field (mut i32)) (field i64))) - ;; H2L: (type $struct-nn-ref (struct (field (ref struct)))) (type $struct-nn-ref (struct (field (ref struct)))) ;; H2L: (type $struct-immutable (struct (field i32))) + (type $struct-immutable (struct (field i32))) ;; H2L: (type $struct-2fields (struct (field i32) (field i32))) (type $struct-2fields (struct (field i32) (field i32))) - (type $struct-3fields-mixed (struct (field i32) (field (mut i32)) (field i64))) - ;; H2L: (type $struct-packed (struct (field i8) (field i16))) (type $struct-packed (struct (field i8) (field i16))) - (type $struct-immutable (struct (field i32))) ;; CHECK: (func $optimize-struct-get-if (type $6) (param $x i32) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) From 621cb263654b8e07ceaf30f9a80d27d84c7d67e6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 12:43:31 -0700 Subject: [PATCH 43/74] work --- test/lit/passes/propagate-struct-gets.wast | 46 +++++++++++----------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index a7bb0b01037..70a2fd50fc3 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -385,11 +385,11 @@ ) ;; CHECK: (func $optimize-struct-get-tee-immutable (type $7) (param $x i32) (param $y i32) (result i32) - ;; CHECK-NEXT: (local $cur (ref $struct-immutable)) + ;; 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 $cur + ;; CHECK-NEXT: (local.set $curr ;; CHECK-NEXT: (struct.new $struct-immutable ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -397,7 +397,7 @@ ;; CHECK-NEXT: (br_if $err ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (local.set $ref - ;; CHECK-NEXT: (local.get $cur) + ;; CHECK-NEXT: (local.get $curr) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (struct.get $struct-immutable 0 @@ -407,7 +407,7 @@ ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $cur + ;; CHECK-NEXT: (local.set $curr ;; CHECK-NEXT: (struct.new $struct-immutable ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) @@ -415,7 +415,7 @@ ;; CHECK-NEXT: (br_if $err ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (local.set $ref - ;; CHECK-NEXT: (local.get $cur) + ;; CHECK-NEXT: (local.get $curr) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (struct.get $struct-immutable 0 @@ -432,7 +432,7 @@ ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; H2L: (func $optimize-struct-get-tee-immutable (type $4) (param $x i32) (param $y i32) (result i32) - ;; H2L-NEXT: (local $cur (ref $struct-immutable)) + ;; 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) @@ -501,14 +501,14 @@ ;; 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 $cur are tee'd into a shared local $ref + ;; 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. Projecting $ref to an i32 ;; local disentangles the two allocations so Heap2Local can optimize both. - (local $cur (ref $struct-immutable)) + (local $curr (ref $struct-immutable)) (local $ref (ref null $struct-immutable)) (block $err - (local.set $cur + (local.set $curr (struct.new $struct-immutable (local.get $x) ) @@ -516,11 +516,11 @@ (br_if $err (struct.get $struct-immutable 0 (local.tee $ref - (local.get $cur) + (local.get $curr) ) ) ) - (local.set $cur + (local.set $curr (struct.new $struct-immutable (local.get $y) ) @@ -528,7 +528,7 @@ (br_if $err (struct.get $struct-immutable 0 (local.tee $ref - (local.get $cur) + (local.get $curr) ) ) ) @@ -901,12 +901,12 @@ ) ;; CHECK: (func $optimize-multiple-fields-tee-partial (type $10) (param $x i32) (param $y i32) (result i64) - ;; CHECK-NEXT: (local $cur (ref $struct-3fields-mixed)) + ;; 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 $cur + ;; CHECK-NEXT: (local.set $curr ;; CHECK-NEXT: (struct.new $struct-3fields-mixed ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (i32.const 100) @@ -916,7 +916,7 @@ ;; CHECK-NEXT: (br_if $err ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (local.set $ref - ;; CHECK-NEXT: (local.get $cur) + ;; CHECK-NEXT: (local.get $curr) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (struct.get $struct-3fields-mixed 0 @@ -931,7 +931,7 @@ ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $cur + ;; CHECK-NEXT: (local.set $curr ;; CHECK-NEXT: (struct.new $struct-3fields-mixed ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: (i32.const 200) @@ -941,7 +941,7 @@ ;; CHECK-NEXT: (br_if $err ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (local.set $ref - ;; CHECK-NEXT: (local.get $cur) + ;; CHECK-NEXT: (local.get $curr) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (struct.get $struct-3fields-mixed 0 @@ -968,7 +968,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; H2L: (func $optimize-multiple-fields-tee-partial (type $9) (param $x i32) (param $y i32) (result i64) - ;; H2L-NEXT: (local $cur (ref $struct-3fields-mixed)) + ;; 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) @@ -1096,10 +1096,10 @@ ;; 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 $cur (ref $struct-3fields-mixed)) + (local $curr (ref $struct-3fields-mixed)) (local $ref (ref null $struct-3fields-mixed)) (block $err - (local.set $cur + (local.set $curr (struct.new $struct-3fields-mixed (local.get $x) (i32.const 100) @@ -1109,11 +1109,11 @@ (br_if $err (struct.get $struct-3fields-mixed 0 (local.tee $ref - (local.get $cur) + (local.get $curr) ) ) ) - (local.set $cur + (local.set $curr (struct.new $struct-3fields-mixed (local.get $y) (i32.const 200) @@ -1123,7 +1123,7 @@ (br_if $err (struct.get $struct-3fields-mixed 0 (local.tee $ref - (local.get $cur) + (local.get $curr) ) ) ) From 029ab8df9e4d7a0dfb4106b972c66df9f9fd730b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 12:47:21 -0700 Subject: [PATCH 44/74] work --- test/lit/passes/propagate-struct-gets.wast | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 70a2fd50fc3..51dd91f30ba 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -503,7 +503,7 @@ (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. Projecting $ref to an i32 + ;; 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)) @@ -635,8 +635,9 @@ ;; 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 optimizeed local uses a nullable type with - ;; ref.as_non_null on the tees and get so validation succeeds across blocks. + ;; ((ref struct)). The optimizeed 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 From 20ecd6bcc2c841b3208552325b2e24ec90569170 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 12:54:07 -0700 Subject: [PATCH 45/74] work --- test/lit/passes/propagate-struct-gets.wast | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 51dd91f30ba..5b8e8af8db5 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -719,7 +719,8 @@ ;; 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. + ;; is mutable: the references may be modified by something else with a + ;; reference. (local $ref (ref null $struct)) (block $err (br_if $err @@ -1431,6 +1432,7 @@ ) ) ) + ;; The proper sign-extension (or not) will be used for each get. (i32.add (i32.add (struct.get_s $struct-packed 0 From 742e9de649ac0f3a2c9be8577393c65dee5201ac Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 13:09:17 -0700 Subject: [PATCH 46/74] work --- test/lit/passes/propagate-struct-gets.wast | 141 +++++++++++++++++++-- 1 file changed, 130 insertions(+), 11 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 5b8e8af8db5..985ae261f23 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -541,7 +541,7 @@ ) ) - ;; CHECK: (func $optimize-struct-get-tee-immutable-nn-ref (type $8) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; CHECK: (func $optimize-struct-get-tee-immutable-nn-ref (type $9) (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 @@ -587,7 +587,7 @@ ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-struct-get-tee-immutable-nn-ref (type $6) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; H2L: (func $optimize-struct-get-tee-immutable-nn-ref (type $7) (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 @@ -667,7 +667,7 @@ ) ) - ;; CHECK: (func $no-optimize-struct-get-tee-mutable (type $9) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; CHECK: (func $no-optimize-struct-get-tee-mutable (type $10) (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 @@ -692,7 +692,7 @@ ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-optimize-struct-get-tee-mutable (type $7) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; H2L: (func $no-optimize-struct-get-tee-mutable (type $8) (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 @@ -902,7 +902,7 @@ ) ) - ;; CHECK: (func $optimize-multiple-fields-tee-partial (type $10) (param $x i32) (param $y i32) (result i64) + ;; CHECK: (func $optimize-multiple-fields-tee-partial (type $11) (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) @@ -969,7 +969,7 @@ ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-multiple-fields-tee-partial (type $9) (param $x i32) (param $y i32) (result i64) + ;; H2L: (func $optimize-multiple-fields-tee-partial (type $10) (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) @@ -1145,7 +1145,7 @@ ) ) - ;; CHECK: (func $no-optimize-multiple-fields-tee-one-mutable (type $11) (param $s1 (ref $struct-3fields-mixed)) (param $s2 (ref $struct-3fields-mixed)) (result i32) + ;; CHECK: (func $no-optimize-multiple-fields-tee-one-mutable (type $12) (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 @@ -1170,7 +1170,7 @@ ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-optimize-multiple-fields-tee-one-mutable (type $10) (param $s1 (ref $struct-3fields-mixed)) (param $s2 (ref $struct-3fields-mixed)) (result i32) + ;; H2L: (func $no-optimize-multiple-fields-tee-one-mutable (type $11) (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 @@ -1448,7 +1448,7 @@ ) ) - ;; CHECK: (func $optimize-single-set (type $12) (result i32) + ;; CHECK: (func $no-single-set (type $8) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $struct @@ -1464,7 +1464,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-single-set (type $12) (result i32) + ;; H2L: (func $no-single-set (type $6) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) @@ -1494,7 +1494,7 @@ ;; H2L-NEXT: ) ;; H2L-NEXT: ) ;; H2L-NEXT: ) - (func $optimize-single-set (result i32) + (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)) @@ -1512,4 +1512,123 @@ ) ) ) + + ;; CHECK: (func $no-unreachable (type $8) (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 $no-unreachable (type $6) (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 $no-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) + ) + ) + ) ) From 135ae8cbe7bd160147a38f7323dd0314d029f72e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 13:09:52 -0700 Subject: [PATCH 47/74] work --- test/lit/passes/propagate-struct-gets.wast | 25 +++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 985ae261f23..587508996d2 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -1609,7 +1609,7 @@ ;; H2L-NEXT: ) ;; H2L-NEXT: ) ;; H2L-NEXT: ) - (func $no-unreachable (result i32) + (func $unreachable (result i32) ;; We ignore unreachable struct.gets code, but still optimize the rest. (local $ref (ref null $struct)) (local.set $ref @@ -1631,4 +1631,27 @@ ) ) ) + + (func $unreachable-2 (result i32) + ;; Now a struct.new and local.set are unreachable. + (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) + ) + ) + ) ) From e4c366dfda9e835a6fb656971f19f37e7790aedd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 13:10:36 -0700 Subject: [PATCH 48/74] work --- test/lit/passes/propagate-struct-gets.wast | 66 ++++++++++++++++++++-- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 587508996d2..6e0508ff45c 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -1464,7 +1464,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-single-set (type $6) (result i32) + ;; H2L: (func $no-single-set (type $5) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) @@ -1513,7 +1513,7 @@ ) ) - ;; CHECK: (func $no-unreachable (type $8) (result i32) + ;; CHECK: (func $unreachable (type $8) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (block @@ -1550,7 +1550,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-unreachable (type $6) (result i32) + ;; H2L: (func $unreachable (type $5) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) @@ -1632,12 +1632,68 @@ ) ) + ;; CHECK: (func $unreachable-2 (type $8) (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 $5) (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. + ;; 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 + (unreachable) ) ) (local.set $ref From 608541c184ba8a1dee48004b4aec033968395106 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 13:12:18 -0700 Subject: [PATCH 49/74] work --- test/lit/passes/propagate-struct-gets.wast | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 6e0508ff45c..1d7ced49930 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -1710,4 +1710,62 @@ ) ) ) + + ;; CHECK: (func $no-struct.get (type $13) + ;; 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 $13) + ;; 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) + ) + ) + ) ) From 71d5688c39d9739e7413d07bde33fea7afa6bf2b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 13:13:58 -0700 Subject: [PATCH 50/74] work --- test/lit/passes/propagate-struct-gets.wast | 132 ++++++++++++++++++--- 1 file changed, 114 insertions(+), 18 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 1d7ced49930..947a7412d97 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -35,7 +35,7 @@ (type $struct-packed (struct (field i8) (field i16))) - ;; CHECK: (func $optimize-struct-get-if (type $6) (param $x i32) (result i32) + ;; CHECK: (func $optimize-struct-get-if (type $7) (param $x i32) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (if @@ -65,7 +65,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-struct-get-if (type $3) (param $x i32) (result i32) + ;; H2L: (func $optimize-struct-get-if (type $4) (param $x i32) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $2 i32) ;; H2L-NEXT: (local $3 i32) @@ -140,7 +140,7 @@ ) ) - ;; CHECK: (func $no-optimize-struct-get-uninitialized (type $7) (param $x i32) (param $y i32) (result i32) + ;; 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) @@ -166,7 +166,7 @@ ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-optimize-struct-get-uninitialized (type $4) (param $x i32) (param $y i32) (result i32) + ;; 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) @@ -222,7 +222,7 @@ ) ) - ;; CHECK: (func $mixture (type $7) (param $x i32) (param $y i32) (result i32) + ;; 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) @@ -273,7 +273,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $mixture (type $4) (param $x i32) (param $y i32) (result i32) + ;; 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) @@ -384,7 +384,7 @@ ) ) - ;; CHECK: (func $optimize-struct-get-tee-immutable (type $7) (param $x i32) (param $y i32) (result i32) + ;; CHECK: (func $optimize-struct-get-tee-immutable (type $8) (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) @@ -431,7 +431,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-struct-get-tee-immutable (type $4) (param $x i32) (param $y i32) (result i32) + ;; H2L: (func $optimize-struct-get-tee-immutable (type $5) (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) @@ -746,7 +746,7 @@ ) ) - ;; CHECK: (func $optimize-struct-get-different-fields (type $6) (param $x i32) (result i32) + ;; CHECK: (func $optimize-struct-get-different-fields (type $7) (param $x i32) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct-2fields)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) @@ -791,7 +791,7 @@ ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-struct-get-different-fields (type $3) (param $x i32) (result i32) + ;; H2L: (func $optimize-struct-get-different-fields (type $4) (param $x i32) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct-2fields)) ;; H2L-NEXT: (local $2 i32) ;; H2L-NEXT: (local $3 i32) @@ -1223,7 +1223,7 @@ ) ) - ;; CHECK: (func $optimize-packed-signed-and-unsigned (type $6) (param $x i32) (result i32) + ;; CHECK: (func $optimize-packed-signed-and-unsigned (type $7) (param $x i32) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct-packed)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) @@ -1289,7 +1289,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-packed-signed-and-unsigned (type $3) (param $x i32) (result i32) + ;; H2L: (func $optimize-packed-signed-and-unsigned (type $4) (param $x i32) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct-packed)) ;; H2L-NEXT: (local $2 i32) ;; H2L-NEXT: (local $3 i32) @@ -1448,7 +1448,7 @@ ) ) - ;; CHECK: (func $no-single-set (type $8) (result i32) + ;; CHECK: (func $no-single-set (type $6) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $struct @@ -1464,7 +1464,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-single-set (type $5) (result i32) + ;; 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) @@ -1513,7 +1513,7 @@ ) ) - ;; CHECK: (func $unreachable (type $8) (result i32) + ;; CHECK: (func $unreachable (type $6) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (block @@ -1550,7 +1550,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $unreachable (type $5) (result i32) + ;; H2L: (func $unreachable (type $3) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) @@ -1632,7 +1632,7 @@ ) ) - ;; CHECK: (func $unreachable-2 (type $8) (result i32) + ;; CHECK: (func $unreachable-2 (type $6) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit) @@ -1656,7 +1656,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $unreachable-2 (type $5) (result i32) + ;; 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) @@ -1768,4 +1768,100 @@ ) ) ) + + ;; CHECK: (func $extra-local.get (type $6) (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) + ) + (i32.add + (struct.get $struct 0 + (local.get $ref) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + ) ) From 9ec884724bb3cf686711b9a2d72aa55f3e785f3e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 13:16:01 -0700 Subject: [PATCH 51/74] work --- test/lit/passes/propagate-struct-gets.wast | 116 ++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 947a7412d97..b9ee1f8a204 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -1853,7 +1853,121 @@ ) ) (drop - (local.get $ref) + (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 $6) (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 From 59008538d80471aa58ed1afbf6d6486755c8cd64 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 13:21:37 -0700 Subject: [PATCH 52/74] work --- test/lit/passes/propagate-struct-gets.wast | 180 +++++++++++++++++++-- 1 file changed, 168 insertions(+), 12 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index b9ee1f8a204..51c1c347530 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -587,7 +587,7 @@ ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-struct-get-tee-immutable-nn-ref (type $7) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; H2L: (func $optimize-struct-get-tee-immutable-nn-ref (type $8) (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 @@ -692,7 +692,7 @@ ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-optimize-struct-get-tee-mutable (type $8) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; H2L: (func $no-optimize-struct-get-tee-mutable (type $9) (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 @@ -1448,7 +1448,7 @@ ) ) - ;; CHECK: (func $no-single-set (type $6) (result i32) + ;; CHECK: (func $no-single-set (type $5) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $struct @@ -1464,7 +1464,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-single-set (type $3) (result i32) + ;; 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) @@ -1513,7 +1513,7 @@ ) ) - ;; CHECK: (func $unreachable (type $6) (result i32) + ;; CHECK: (func $unreachable (type $5) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (block @@ -1550,7 +1550,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $unreachable (type $3) (result i32) + ;; H2L: (func $unreachable (type $2) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) @@ -1632,7 +1632,7 @@ ) ) - ;; CHECK: (func $unreachable-2 (type $6) (result i32) + ;; CHECK: (func $unreachable-2 (type $5) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit) @@ -1656,7 +1656,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $unreachable-2 (type $3) (result i32) + ;; 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) @@ -1769,7 +1769,7 @@ ) ) - ;; CHECK: (func $extra-local.get (type $6) (result i32) + ;; CHECK: (func $extra-local.get (type $5) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $struct @@ -1793,7 +1793,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $extra-local.get (type $3) (result i32) + ;; 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) @@ -1865,7 +1865,7 @@ ) ) - ;; CHECK: (func $extra-local.tee (type $6) (result i32) + ;; CHECK: (func $extra-local.tee (type $5) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $struct @@ -1893,7 +1893,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $extra-local.tee (type $3) (result i32) + ;; 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) @@ -1978,4 +1978,160 @@ ) ) ) + + ;; CHECK: (func $only-local.tee (type $5) (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) + ) + ) + ) ) From e536c39c4032d9e43ed129db803ef365481199f2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 13:24:42 -0700 Subject: [PATCH 53/74] work --- test/lit/passes/propagate-struct-gets.wast | 74 ++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 51c1c347530..8686ddaf3e5 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -2134,4 +2134,78 @@ ) ) ) + + ;; CHECK: (func $set-unknown-mut (type $14) (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 $14) (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) + ) + ) + ) ) From f0b6e55ad5beadf16814818823f411a5a8b6b61c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 13:25:58 -0700 Subject: [PATCH 54/74] work --- test/lit/passes/propagate-struct-gets.wast | 111 +++++++++++++++++++-- 1 file changed, 102 insertions(+), 9 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 8686ddaf3e5..eb88d6b80b9 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -17,15 +17,16 @@ ;; CHECK: (type $struct-2fields (struct (field i32) (field i32))) - ;; CHECK: (type $struct-packed (struct (field i8) (field i16))) - ;; CHECK: (type $struct-immutable (struct (field i32))) + ;; CHECK: (type $struct-packed (struct (field i8) (field i16))) + ;; CHECK: (type $struct-nn-ref (struct (field (ref struct)))) + ;; H2L: (type $struct-immutable (struct (field i32))) + ;; H2L: (type $struct-nn-ref (struct (field (ref struct)))) (type $struct-nn-ref (struct (field (ref struct)))) - ;; H2L: (type $struct-immutable (struct (field i32))) (type $struct-immutable (struct (field i32))) ;; H2L: (type $struct-2fields (struct (field i32) (field i32))) @@ -65,7 +66,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-struct-get-if (type $4) (param $x i32) (result i32) + ;; 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) @@ -166,7 +167,7 @@ ;; 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: (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) @@ -273,7 +274,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $mixture (type $5) (param $x i32) (param $y i32) (result i32) + ;; 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) @@ -431,7 +432,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-struct-get-tee-immutable (type $5) (param $x i32) (param $y i32) (result i32) + ;; 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) @@ -791,7 +792,7 @@ ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-struct-get-different-fields (type $4) (param $x i32) (result i32) + ;; 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) @@ -1289,7 +1290,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-packed-signed-and-unsigned (type $4) (param $x i32) (result i32) + ;; 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) @@ -2208,4 +2209,96 @@ ) ) ) + + ;; CHECK: (func $set-unknown-immutable (type $15) (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 $15) (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) + ) + ) + ) ) From 5ea568456a025fc518400f8003f149b00d462a39 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 13:28:02 -0700 Subject: [PATCH 55/74] work --- test/lit/passes/propagate-struct-gets.wast | 76 ++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index eb88d6b80b9..68616a207e5 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -2301,4 +2301,80 @@ ) ) ) + + ;; CHECK: (func $set-unknown-mutable (type $16) (param $param (ref null $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-mutable (type $16) (param $param (ref null $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-mutable (param $param (ref null $struct)) (result i32) + (local $ref (ref null $struct)) + ;; 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 + (i32.const 42) + ) + ) + (i32.add + (struct.get $struct 0 + (local.get $ref) + ) + (struct.get $struct 0 + (local.get $ref) + ) + ) + ) ) + +;; TODO: subtyping stopps From 8810a6d6066577460773a35122f83db9bce0ee0e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 13:28:54 -0700 Subject: [PATCH 56/74] work --- test/lit/passes/propagate-struct-gets.wast | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 68616a207e5..f286cfa129b 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -11,14 +11,14 @@ ;; 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-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-immutable (struct (field i32))) - ;; CHECK: (type $struct-packed (struct (field i8) (field i16))) ;; CHECK: (type $struct-nn-ref (struct (field (ref struct)))) @@ -1465,7 +1465,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-single-set (type $2) (result i32) + ;; 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) @@ -1551,7 +1551,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $unreachable (type $2) (result i32) + ;; H2L: (func $unreachable (type $3) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) @@ -1657,7 +1657,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $unreachable-2 (type $2) (result i32) + ;; 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) @@ -1794,7 +1794,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $extra-local.get (type $2) (result i32) + ;; 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) @@ -1894,7 +1894,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $extra-local.tee (type $2) (result i32) + ;; 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) @@ -2025,7 +2025,7 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $only-local.tee (type $2) (result i32) + ;; 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) @@ -2302,27 +2302,27 @@ ) ) - ;; CHECK: (func $set-unknown-mutable (type $16) (param $param (ref null $struct)) (result i32) - ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK: (func $set-unknown-mutable (type $16) (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 + ;; CHECK-NEXT: (struct.new $struct-immutable ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.get $struct-immutable 0 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.get $struct-immutable 0 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $set-unknown-mutable (type $16) (param $param (ref null $struct)) (result i32) - ;; H2L-NEXT: (local $ref (ref null $struct)) + ;; H2L: (func $set-unknown-mutable (type $16) (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 @@ -2354,23 +2354,23 @@ ;; H2L-NEXT: ) ;; H2L-NEXT: ) ;; H2L-NEXT: ) - (func $set-unknown-mutable (param $param (ref null $struct)) (result i32) - (local $ref (ref null $struct)) + (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 + (struct.new $struct-immutable (i32.const 42) ) ) (i32.add - (struct.get $struct 0 + (struct.get $struct-immutable 0 (local.get $ref) ) - (struct.get $struct 0 + (struct.get $struct-immutable 0 (local.get $ref) ) ) From cb1bb8b472db1aafc31f981e7457afb92e510e03 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 13:29:44 -0700 Subject: [PATCH 57/74] work --- src/passes/PropagateStructGets.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index b787fbd0bf7..7e52197872e 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -101,9 +101,7 @@ struct PropagateStructGets // stored in optimizedLocals, for each local, below). std::vector structGets; - void visitLocalGet(LocalGet* curr) { - numGets[curr->index]++; - } + void visitLocalGet(LocalGet* curr) { numGets[curr->index]++; } void visitLocalSet(LocalSet* curr) { sets[curr->index].push_back(getCurrentPointer()); From 6b5941056e6549384c8447dab78e203ccd03ff14 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 16:01:29 -0700 Subject: [PATCH 58/74] fix the case of a param's unknown value, we should not optimize there --- src/passes/PropagateStructGets.cpp | 30 +-- test/lit/passes/propagate-struct-gets.wast | 203 +++++++++++++++++++-- 2 files changed, 202 insertions(+), 31 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 7e52197872e..d98c5b77ae0 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -243,8 +243,8 @@ struct PropagateStructGets // 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 (it is fine for the local - // itself to be nullable, see below). + // 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.isNullable()) { @@ -256,21 +256,21 @@ struct PropagateStructGets continue; } - // Finally, if the local is nullable, see if the null default value can - // actually be read (which would mean we trap; we could handle this but - // realistically it should be rare TODO). - if (func->getLocalType(localIndex).isNullable()) { - for (auto* sget : getStructGets) { - if (localGraph.getSets(sget->ref->cast()) - .contains(nullptr)) { - 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. diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index f286cfa129b..a629ad4bcdb 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -542,7 +542,7 @@ ) ) - ;; CHECK: (func $optimize-struct-get-tee-immutable-nn-ref (type $9) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; CHECK: (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)) ;; CHECK-NEXT: (local $ref (ref null $struct-nn-ref)) ;; CHECK-NEXT: (local $4 structref) ;; CHECK-NEXT: (block $err @@ -588,7 +588,7 @@ ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-struct-get-tee-immutable-nn-ref (type $8) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; H2L: (func $optimize-struct-get-tee-immutable-nn-ref (type $9) (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 @@ -668,7 +668,7 @@ ) ) - ;; CHECK: (func $no-optimize-struct-get-tee-mutable (type $10) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; CHECK: (func $no-optimize-struct-get-tee-mutable (type $11) (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 @@ -693,7 +693,7 @@ ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-optimize-struct-get-tee-mutable (type $9) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; H2L: (func $no-optimize-struct-get-tee-mutable (type $10) (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 @@ -903,7 +903,7 @@ ) ) - ;; CHECK: (func $optimize-multiple-fields-tee-partial (type $11) (param $x i32) (param $y i32) (result i64) + ;; CHECK: (func $optimize-multiple-fields-tee-partial (type $12) (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) @@ -970,7 +970,7 @@ ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-multiple-fields-tee-partial (type $10) (param $x i32) (param $y i32) (result i64) + ;; H2L: (func $optimize-multiple-fields-tee-partial (type $11) (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) @@ -1146,7 +1146,7 @@ ) ) - ;; CHECK: (func $no-optimize-multiple-fields-tee-one-mutable (type $12) (param $s1 (ref $struct-3fields-mixed)) (param $s2 (ref $struct-3fields-mixed)) (result i32) + ;; CHECK: (func $no-optimize-multiple-fields-tee-one-mutable (type $13) (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 @@ -1171,7 +1171,7 @@ ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-optimize-multiple-fields-tee-one-mutable (type $11) (param $s1 (ref $struct-3fields-mixed)) (param $s2 (ref $struct-3fields-mixed)) (result i32) + ;; H2L: (func $no-optimize-multiple-fields-tee-one-mutable (type $12) (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 @@ -1712,7 +1712,7 @@ ) ) - ;; CHECK: (func $no-struct.get (type $13) + ;; CHECK: (func $no-struct.get (type $14) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $struct @@ -1725,7 +1725,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-struct.get (type $13) + ;; H2L: (func $no-struct.get (type $14) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) @@ -2136,7 +2136,7 @@ ) ) - ;; CHECK: (func $set-unknown-mut (type $14) (param $param (ref $struct)) (result i32) + ;; CHECK: (func $set-unknown-mut (type $15) (param $param (ref $struct)) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (local.get $param) @@ -2155,7 +2155,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $set-unknown-mut (type $14) (param $param (ref $struct)) (result i32) + ;; H2L: (func $set-unknown-mut (type $15) (param $param (ref $struct)) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $2 i32) ;; H2L-NEXT: (local $3 i32) @@ -2210,7 +2210,7 @@ ) ) - ;; CHECK: (func $set-unknown-immutable (type $15) (param $param (ref $struct-immutable)) (result i32) + ;; CHECK: (func $set-unknown-immutable (type $16) (param $param (ref $struct-immutable)) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct-immutable)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (block @@ -2240,7 +2240,7 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $set-unknown-immutable (type $15) (param $param (ref $struct-immutable)) (result i32) + ;; H2L: (func $set-unknown-immutable (type $16) (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) @@ -2302,7 +2302,7 @@ ) ) - ;; CHECK: (func $set-unknown-mutable (type $16) (param $param (ref null $struct-immutable)) (result i32) + ;; CHECK: (func $set-unknown-mutable (type $17) (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) @@ -2321,7 +2321,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $set-unknown-mutable (type $16) (param $param (ref null $struct-immutable)) (result i32) + ;; H2L: (func $set-unknown-mutable (type $17) (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) @@ -2375,6 +2375,177 @@ ) ) ) + + ;; 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 $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 $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 $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) + ) + ) ) ;; TODO: subtyping stopps From c09bd7879fd43f572192f77d404c8c75b83dd465 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 Oct 2026 16:05:10 -0700 Subject: [PATCH 59/74] add subtyping test --- test/lit/passes/propagate-struct-gets.wast | 149 ++++++++++++++++++--- 1 file changed, 132 insertions(+), 17 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index a629ad4bcdb..6ea0d91f6a9 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -35,8 +35,14 @@ ;; 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: (func $optimize-struct-get-if (type $7) (param $x i32) (result i32) + ;; CHECK: (type $sub (sub $super (struct (field i32)))) + (type $sub (sub $super (struct (field 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 @@ -141,7 +147,7 @@ ) ) - ;; CHECK: (func $no-optimize-struct-get-uninitialized (type $8) (param $x i32) (param $y i32) (result i32) + ;; 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) @@ -223,7 +229,7 @@ ) ) - ;; CHECK: (func $mixture (type $8) (param $x i32) (param $y i32) (result i32) + ;; 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) @@ -385,7 +391,7 @@ ) ) - ;; CHECK: (func $optimize-struct-get-tee-immutable (type $8) (param $x i32) (param $y i32) (result i32) + ;; 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) @@ -542,7 +548,7 @@ ) ) - ;; CHECK: (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)) + ;; CHECK: (func $optimize-struct-get-tee-immutable-nn-ref (type $11) (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 @@ -668,7 +674,7 @@ ) ) - ;; CHECK: (func $no-optimize-struct-get-tee-mutable (type $11) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; CHECK: (func $no-optimize-struct-get-tee-mutable (type $12) (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 @@ -747,7 +753,7 @@ ) ) - ;; CHECK: (func $optimize-struct-get-different-fields (type $7) (param $x i32) (result i32) + ;; 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) @@ -903,7 +909,7 @@ ) ) - ;; CHECK: (func $optimize-multiple-fields-tee-partial (type $12) (param $x i32) (param $y i32) (result i64) + ;; CHECK: (func $optimize-multiple-fields-tee-partial (type $13) (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) @@ -1146,7 +1152,7 @@ ) ) - ;; CHECK: (func $no-optimize-multiple-fields-tee-one-mutable (type $13) (param $s1 (ref $struct-3fields-mixed)) (param $s2 (ref $struct-3fields-mixed)) (result i32) + ;; CHECK: (func $no-optimize-multiple-fields-tee-one-mutable (type $14) (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 @@ -1224,7 +1230,7 @@ ) ) - ;; CHECK: (func $optimize-packed-signed-and-unsigned (type $7) (param $x i32) (result i32) + ;; 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) @@ -1712,7 +1718,7 @@ ) ) - ;; CHECK: (func $no-struct.get (type $14) + ;; CHECK: (func $no-struct.get (type $15) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $struct @@ -2136,7 +2142,7 @@ ) ) - ;; CHECK: (func $set-unknown-mut (type $15) (param $param (ref $struct)) (result i32) + ;; CHECK: (func $set-unknown-mut (type $16) (param $param (ref $struct)) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (local.get $param) @@ -2210,7 +2216,7 @@ ) ) - ;; CHECK: (func $set-unknown-immutable (type $16) (param $param (ref $struct-immutable)) (result i32) + ;; CHECK: (func $set-unknown-immutable (type $17) (param $param (ref $struct-immutable)) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct-immutable)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (block @@ -2302,7 +2308,7 @@ ) ) - ;; CHECK: (func $set-unknown-mutable (type $17) (param $param (ref null $struct-immutable)) (result i32) + ;; CHECK: (func $set-unknown-mutable (type $18) (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) @@ -2376,7 +2382,7 @@ ) ) - ;; CHECK: (func $no-param-value (type $9) (param $ref (ref $struct)) (param $x i32) (result i32) + ;; 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 @@ -2444,7 +2450,7 @@ ) ) - ;; CHECK: (func $yes-overwritten-param-value (type $9) (param $ref (ref $struct)) (param $x i32) (result i32) + ;; 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 @@ -2546,6 +2552,115 @@ (local.get $ref) ) ) + + ;; CHECK: (func $subtyping (type $5) (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) + ) + ) + ) ) -;; TODO: subtyping stopps From 41593058d7217dd7a560204cee237403a6228d15 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 19 Oct 2026 12:47:49 -0700 Subject: [PATCH 60/74] fix pops --- src/passes/PropagateStructGets.cpp | 14 ++ test/lit/passes/propagate-struct-gets.wast | 196 +++++++++++++++++++-- 2 files changed, 194 insertions(+), 16 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index d98c5b77ae0..744d144370e 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -64,6 +64,7 @@ // #include "ir/bits.h" +#include "ir/eh-utils.h" #include "ir/local-graph.h" #include "ir/properties.h" #include "pass.h" @@ -126,6 +127,10 @@ struct PropagateStructGets // 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. @@ -280,6 +285,11 @@ struct PropagateStructGets } } + if (optimizedLocals.empty()) { + // Nothing to optimize. + return; + } + auto getFieldFromLocal = [&](Index localIndex, Index fieldIndex, bool signed_) -> Expression* { auto& field = func->getLocalType(localIndex) @@ -354,6 +364,10 @@ struct PropagateStructGets } } } + + if (hasTry) { + EHUtils::handleBlockNestedPops(func, *getModule()); + } } }; diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 6ea0d91f6a9..3d0558b70a4 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -42,6 +42,10 @@ ;; 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) @@ -548,7 +552,7 @@ ) ) - ;; CHECK: (func $optimize-struct-get-tee-immutable-nn-ref (type $11) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; 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 @@ -594,7 +598,7 @@ ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-struct-get-tee-immutable-nn-ref (type $9) (param $x i32) (param $b1 (ref $struct-nn-ref)) (param $b2 (ref $struct-nn-ref)) (result (ref struct)) + ;; 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 @@ -674,7 +678,7 @@ ) ) - ;; CHECK: (func $no-optimize-struct-get-tee-mutable (type $12) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; 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 @@ -699,7 +703,7 @@ ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-optimize-struct-get-tee-mutable (type $10) (param $s1 (ref $struct)) (param $s2 (ref $struct)) (result i32) + ;; 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 @@ -909,7 +913,7 @@ ) ) - ;; CHECK: (func $optimize-multiple-fields-tee-partial (type $13) (param $x i32) (param $y i32) (result i64) + ;; 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) @@ -976,7 +980,7 @@ ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $optimize-multiple-fields-tee-partial (type $11) (param $x i32) (param $y i32) (result i64) + ;; 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) @@ -1152,7 +1156,7 @@ ) ) - ;; CHECK: (func $no-optimize-multiple-fields-tee-one-mutable (type $14) (param $s1 (ref $struct-3fields-mixed)) (param $s2 (ref $struct-3fields-mixed)) (result i32) + ;; 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 @@ -1177,7 +1181,7 @@ ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-optimize-multiple-fields-tee-one-mutable (type $12) (param $s1 (ref $struct-3fields-mixed)) (param $s2 (ref $struct-3fields-mixed)) (result i32) + ;; 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 @@ -1718,7 +1722,7 @@ ) ) - ;; CHECK: (func $no-struct.get (type $15) + ;; CHECK: (func $no-struct.get (type $16) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $struct @@ -1731,7 +1735,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-struct.get (type $14) + ;; H2L: (func $no-struct.get (type $15) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) @@ -2142,7 +2146,7 @@ ) ) - ;; CHECK: (func $set-unknown-mut (type $16) (param $param (ref $struct)) (result i32) + ;; 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) @@ -2161,7 +2165,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $set-unknown-mut (type $15) (param $param (ref $struct)) (result i32) + ;; 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) @@ -2216,7 +2220,7 @@ ) ) - ;; CHECK: (func $set-unknown-immutable (type $17) (param $param (ref $struct-immutable)) (result i32) + ;; 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 @@ -2246,7 +2250,7 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $set-unknown-immutable (type $16) (param $param (ref $struct-immutable)) (result i32) + ;; 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) @@ -2308,7 +2312,7 @@ ) ) - ;; CHECK: (func $set-unknown-mutable (type $18) (param $param (ref null $struct-immutable)) (result i32) + ;; 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) @@ -2327,7 +2331,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $set-unknown-mutable (type $17) (param $param (ref null $struct-immutable)) (result i32) + ;; 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) @@ -2662,5 +2666,165 @@ ) ) ) + + ;; 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) + ) + ) ) From 077d48acd54884c5b0048fce0be18974f7e6044a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 19 Oct 2026 12:47:57 -0700 Subject: [PATCH 61/74] format --- src/passes/PropagateStructGets.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 744d144370e..aeeac0f7488 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -267,8 +267,7 @@ struct PropagateStructGets // 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)) { + if (localGraph.getSets(sget->ref->cast()).contains(nullptr)) { ok = false; break; } From ce3e52a9034e2fc074ad735d896242b901482cba Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 19 Oct 2026 13:12:04 -0700 Subject: [PATCH 62/74] fix nullref --- src/passes/PropagateStructGets.cpp | 16 ++++- test/lit/passes/propagate-struct-gets.wast | 74 +++++++++++++++++----- 2 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index aeeac0f7488..84917f12709 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -102,10 +102,22 @@ struct PropagateStructGets // stored in optimizedLocals, for each local, below). std::vector structGets; - void visitLocalGet(LocalGet* curr) { numGets[curr->index]++; } + // 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) { - sets[curr->index].push_back(getCurrentPointer()); + if (isStructLocal(curr->index)) { + sets[curr->index].push_back(getCurrentPointer()); + } } void visitStructGet(StructGet* curr) { diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 3d0558b70a4..dee59f1aa7c 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -1459,7 +1459,7 @@ ) ) - ;; CHECK: (func $no-single-set (type $5) (result i32) + ;; 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 @@ -1475,7 +1475,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-single-set (type $3) (result i32) + ;; 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) @@ -1524,7 +1524,7 @@ ) ) - ;; CHECK: (func $unreachable (type $5) (result i32) + ;; CHECK: (func $unreachable (type $4) (result i32) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (block @@ -1561,7 +1561,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $unreachable (type $3) (result i32) + ;; H2L: (func $unreachable (type $2) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) @@ -1643,7 +1643,7 @@ ) ) - ;; CHECK: (func $unreachable-2 (type $5) (result i32) + ;; 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) @@ -1667,7 +1667,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $unreachable-2 (type $3) (result i32) + ;; 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) @@ -1780,7 +1780,7 @@ ) ) - ;; CHECK: (func $extra-local.get (type $5) (result i32) + ;; 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 @@ -1804,7 +1804,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $extra-local.get (type $3) (result i32) + ;; 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) @@ -1876,7 +1876,7 @@ ) ) - ;; CHECK: (func $extra-local.tee (type $5) (result i32) + ;; 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 @@ -1904,7 +1904,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $extra-local.tee (type $3) (result i32) + ;; 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) @@ -1990,7 +1990,7 @@ ) ) - ;; CHECK: (func $only-local.tee (type $5) (result i32) + ;; 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) @@ -2035,7 +2035,7 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $only-local.tee (type $3) (result i32) + ;; 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) @@ -2557,7 +2557,7 @@ ) ) - ;; CHECK: (func $subtyping (type $5) (result i32) + ;; CHECK: (func $subtyping (type $4) (result i32) ;; CHECK-NEXT: (local $ref (ref null $super)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (block @@ -2589,7 +2589,7 @@ ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $subtyping (type $3) (result i32) + ;; H2L: (func $subtyping (type $2) (result i32) ;; H2L-NEXT: (local $ref (ref null $super)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) @@ -2826,5 +2826,49 @@ (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 $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) + ) + ) +) From 055481c1ea456cf1e0aa962fe5373abe7edc4e26 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Oct 2026 12:00:57 -0700 Subject: [PATCH 63/74] Update src/passes/PropagateStructGets.cpp Co-authored-by: Thomas Lively --- src/passes/PropagateStructGets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 84917f12709..19af8bb3aed 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -84,7 +84,7 @@ struct PropagateStructGets return std::make_unique(); } - // Track the number of gets for each local, so that we can see if all gets are + // 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; From 77a7b8114f205e8bb583c38af473251efa4c20cf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Oct 2026 12:01:19 -0700 Subject: [PATCH 64/74] Update src/passes/PropagateStructGets.cpp Co-authored-by: Thomas Lively --- src/passes/PropagateStructGets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 19af8bb3aed..8d5e4b9eae3 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -91,7 +91,7 @@ struct PropagateStructGets // The sets for each local index. std::unordered_map> sets; - // The parent struct.gets for each local index's gets. + // The parent struct.gets for each local index's local.gets. std::unordered_map> getParents; // The parent struct.get for each tee. From 06fe0cf3bb4fa0fad89d84965df401866e9a8adb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Oct 2026 12:02:59 -0700 Subject: [PATCH 65/74] mention what happens when tee/get lack a struct.get parent --- src/passes/PropagateStructGets.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 8d5e4b9eae3..6baff5834bb 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -91,10 +91,12 @@ struct PropagateStructGets // The sets for each local index. std::unordered_map> sets; - // The parent struct.gets for each local index's local.gets. + // 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. + // 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, From 32f864a26ac63bd0e082000b0d03ca5e5ca547e5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Oct 2026 12:03:06 -0700 Subject: [PATCH 66/74] format --- src/passes/PropagateStructGets.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index 6baff5834bb..bb57c8609b1 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -84,8 +84,8 @@ struct PropagateStructGets 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. + // 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. From e9d4cba70cb079df795b9d92af42c1941f88cd39 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Oct 2026 13:04:00 -0700 Subject: [PATCH 67/74] add test for local.tee unpotimization --- test/lit/passes/propagate-struct-gets.wast | 86 +++++++++++++++++++--- 1 file changed, 76 insertions(+), 10 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index dee59f1aa7c..20d3d8fcb05 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -14,6 +14,8 @@ ;; 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))) @@ -22,8 +24,6 @@ ;; CHECK: (type $struct-packed (struct (field i8) (field i16))) ;; CHECK: (type $struct-nn-ref (struct (field (ref struct)))) - ;; H2L: (type $struct-immutable (struct (field i32))) - ;; H2L: (type $struct-nn-ref (struct (field (ref struct)))) (type $struct-nn-ref (struct (field (ref struct)))) @@ -1475,7 +1475,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $no-single-set (type $2) (result i32) + ;; 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) @@ -1561,7 +1561,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $unreachable (type $2) (result i32) + ;; H2L: (func $unreachable (type $3) (result i32) ;; H2L-NEXT: (local $ref (ref null $struct)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) @@ -1667,7 +1667,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $unreachable-2 (type $2) (result i32) + ;; 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) @@ -1804,7 +1804,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $extra-local.get (type $2) (result i32) + ;; 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) @@ -1904,7 +1904,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $extra-local.tee (type $2) (result i32) + ;; 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) @@ -2035,7 +2035,7 @@ ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $only-local.tee (type $2) (result i32) + ;; 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) @@ -2589,7 +2589,7 @@ ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $subtyping (type $2) (result i32) + ;; H2L: (func $subtyping (type $3) (result i32) ;; H2L-NEXT: (local $ref (ref null $super)) ;; H2L-NEXT: (local $1 i32) ;; H2L-NEXT: (local $2 i32) @@ -2842,7 +2842,7 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; H2L: (func $nullref (type $2) (result i32) + ;; H2L: (func $nullref (type $3) (result i32) ;; H2L-NEXT: (local $ref nullref) ;; H2L-NEXT: (local.set $ref ;; H2L-NEXT: (ref.null none) @@ -2871,4 +2871,70 @@ (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) + ) ) From 8c1e6154b07b2cf454efaf68fc2423ba220c85f6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Oct 2026 13:21:04 -0700 Subject: [PATCH 68/74] add a testcase for a struct.get child not being a tee or a get --- test/lit/passes/propagate-struct-gets.wast | 127 +++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 20d3d8fcb05..b704f931058 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -2937,4 +2937,131 @@ ) (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) + ) ) From 055cd52ad000520a5d7dcc90fe93dc0a1115ef85 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Oct 2026 13:50:41 -0700 Subject: [PATCH 69/74] add comment on immutability --- src/passes/PropagateStructGets.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index bb57c8609b1..d03ff43ee3d 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -230,6 +230,21 @@ struct PropagateStructGets // 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) { From a8061e05608ac74ebff84b4b56b2424ec048da93 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Oct 2026 13:58:20 -0700 Subject: [PATCH 70/74] typo --- src/passes/PropagateStructGets.cpp | 16 +++++++++++++++- test/lit/passes/propagate-struct-gets.wast | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index d03ff43ee3d..ed611b247f9 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -42,7 +42,21 @@ // // In effect we "propagate" struct.gets to earlier positions. // -// This is basically a simple form of escape analysis, like Heap2Local, but +// 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: diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index b704f931058..d66e7e23ea7 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -646,7 +646,7 @@ ;; 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 optimizeed local uses a nullable type (since it is + ;; ((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)) From d3b728eea3b6a450cd46e72440a7abdfb40ea0fa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Oct 2026 14:08:29 -0700 Subject: [PATCH 71/74] split up visitFunction --- src/passes/PropagateStructGets.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index ed611b247f9..a95dd83675f 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -165,16 +165,24 @@ struct PropagateStructGets 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 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; - for (auto& [localIndex, localSets] : sets) { if (localSets.size() < 2) { // We only care about the case of 2 or more sets for a local: otherwise, @@ -326,11 +334,11 @@ struct PropagateStructGets builder.addVar(func, fields[fieldIndex].type); } } + } - if (optimizedLocals.empty()) { - // Nothing to optimize. - return; - } + void optimize(Function* func) { + auto* module = getModule(); + Builder builder(*module); auto getFieldFromLocal = [&](Index localIndex, Index fieldIndex, bool signed_) -> Expression* { From bb0a6e5486b1e5468c11f4f1e82cafdae476cd8d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Oct 2026 14:19:05 -0700 Subject: [PATCH 72/74] add atomic test 1 --- src/passes/PropagateStructGets.cpp | 5 +- test/lit/passes/propagate-struct-gets.wast | 116 ++++++++++++++++++++- 2 files changed, 116 insertions(+), 5 deletions(-) diff --git a/src/passes/PropagateStructGets.cpp b/src/passes/PropagateStructGets.cpp index a95dd83675f..aea24a73d9f 100644 --- a/src/passes/PropagateStructGets.cpp +++ b/src/passes/PropagateStructGets.cpp @@ -137,9 +137,8 @@ struct PropagateStructGets } void visitStructGet(StructGet* curr) { - // Avoid unreachable code, and leave atomic operations for now TODO - if (curr->type == Type::unreachable || - curr->order != MemoryOrder::Unordered) { + // Avoid unreachable code. + if (curr->type == Type::unreachable) { return; } diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index d66e7e23ea7..4cdbc4b291e 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -3,8 +3,8 @@ ;; Test the pass by itself, and also followed up by Heap2Local, to verify that ;; we emit things Heap2Local can handle. -;; RUN: wasm-opt %s --propagate-struct-gets -all -S -o - | filecheck %s -;; RUN: wasm-opt %s --propagate-struct-gets --heap2local -all -S -o - | filecheck %s --check-prefix=H2L +;; 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)))) @@ -3065,3 +3065,115 @@ (struct.new_default $struct) ) ) + +;; Shared data is optimizable too. +(module + (type $struct (shared (struct (field i32)))) + + ;; CHECK: (type $struct-mut (shared (struct (field (mut i32))))) + ;; H2L: (type $struct-mut (shared (struct (field (mut i32))))) + (type $struct-mut (shared (struct (field (mut i32))))) + + ;; CHECK: (func $struct.get-unknown-child (type $1) (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-unknown-child (type $0) (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-unknown-child (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) + ) + ) +) + From c0e67253c7d67dc2a17d540d97c85ff4d7adf122 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Oct 2026 14:20:25 -0700 Subject: [PATCH 73/74] add atomic test 2 --- test/lit/passes/propagate-struct-gets.wast | 101 ++++++++++++++++++++- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index 4cdbc4b291e..be766552d36 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -3068,13 +3068,15 @@ ;; Shared data is optimizable too. (module + ;; CHECK: (type $struct (shared (struct (field i32)))) + ;; H2L: (type $struct (shared (struct (field i32)))) (type $struct (shared (struct (field i32)))) ;; CHECK: (type $struct-mut (shared (struct (field (mut i32))))) ;; H2L: (type $struct-mut (shared (struct (field (mut i32))))) (type $struct-mut (shared (struct (field (mut i32))))) - ;; CHECK: (func $struct.get-unknown-child (type $1) (param $x i32) (result 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 @@ -3104,7 +3106,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; H2L: (func $struct.get-unknown-child (type $0) (param $x i32) (result i32) + ;; 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) @@ -3154,7 +3156,7 @@ ;; H2L-NEXT: ) ;; H2L-NEXT: (local.get $2) ;; H2L-NEXT: ) - (func $struct.get-unknown-child (param $x i32) (result i32) + (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. @@ -3175,5 +3177,98 @@ (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) + ) + ) ) From 0980b2920852d59da00539a154080a5f49dabea0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 20 Oct 2026 14:21:08 -0700 Subject: [PATCH 74/74] add atomic test 3 --- test/lit/passes/propagate-struct-gets.wast | 68 +++++++++++++++++++++- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/test/lit/passes/propagate-struct-gets.wast b/test/lit/passes/propagate-struct-gets.wast index be766552d36..f342b782555 100644 --- a/test/lit/passes/propagate-struct-gets.wast +++ b/test/lit/passes/propagate-struct-gets.wast @@ -3068,12 +3068,14 @@ ;; 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)))) - ;; CHECK: (type $struct-mut (shared (struct (field (mut 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) @@ -3106,7 +3108,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; H2L: (func $struct.get-atomic (type $1) (param $x i32) (result i32) + ;; 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) @@ -3270,5 +3272,65 @@ (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) + ) + ) )