From 1d0a1226a92d449aec98022aa45e8fe3c3977eaf Mon Sep 17 00:00:00 2001 From: ArkadySkv Date: Mon, 7 Sep 2026 15:20:19 +0400 Subject: [PATCH] fix(Heap2Local): Track RMW results to handle nested flows (#8850) Add RMW result tracking infrastructure to handle cases where an inner cmpxchg result flows into the value operand of an outer xchg. - Add RMWResultInfo struct and RMWResultInfoMap - Extend EscapeAnalyzer to follow RMW result flows - Modify visitStructRMW and visitStructCmpxchg to detect nested flows - Store reference-typed RMW results in scratch locals for downstream use Fixes #8850. --- src/passes/Heap2Local.cpp | 125 ++++++-- test/lit/passes/heap2local-rmw.wast | 424 +++++++++++++++------------- 2 files changed, 341 insertions(+), 208 deletions(-) diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index 7f8381e2a6c..69c6eb88153 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -163,6 +163,7 @@ #include "wasm-builder.h" #include "wasm-type.h" #include "wasm.h" +#include namespace wasm { @@ -192,6 +193,15 @@ enum class ParentChildInteraction : int8_t { None, }; +// Tracks the scratch local used to hold the result of an RMW operation that +// returns a reference type, so that we can follow data flow through it. +struct RMWResultInfo { + Expression* originalRMW; // the RMW being replaced + LocalSet* resultSet; // local.set that writes the result + LocalGet* resultGet; // local.get that reads the result +}; +using RMWResultInfoMap = std::unordered_map; + // Core analysis that provides an escapes() method to check if an allocation // escapes in a way that prevents optimizing it away as described above. It also // stashes information about the relevant expressions as it goes, which helps @@ -215,13 +225,16 @@ struct EscapeAnalyzer { const PassOptions& passOptions; Module& wasm; + RMWResultInfoMap& rmwResultInfo; + EscapeAnalyzer(const LazyLocalGraph& localGraph, Parents& parents, const BranchUtils::BranchTargets& branchTargets, const PassOptions& passOptions, - Module& wasm) + Module& wasm, + RMWResultInfoMap& rmwResultInfo) : localGraph(localGraph), parents(parents), branchTargets(branchTargets), - passOptions(passOptions), wasm(wasm) {} + passOptions(passOptions), wasm(wasm), rmwResultInfo(rmwResultInfo) {} // We must track all the local.sets that write the allocation, to verify // exclusivity. @@ -286,6 +299,13 @@ struct EscapeAnalyzer { // value. Note the set, and we will check the gets at the end once we // know all of our sets. sets.insert(set); + // If this set is writing an RMW result, then the flow continues + // through the corresponding get. + for (auto& [rmw, info] : rmwResultInfo) { + if (info.resultSet == set) { + flows.push({info.resultGet, parents.getParent(info.resultGet)}); + } + } for (auto* get : localGraph.getSetInfluences(set)) { flows.push({get, parents.getParent(get)}); } @@ -609,12 +629,16 @@ struct Struct2Local : PostWalker { // is only something to store if it is non-nullable, and we store it that way. Type descType; + RMWResultInfoMap& rmwResultInfo; + Struct2Local(StructNew* allocation, EscapeAnalyzer& analyzer, Function* func, - Module& wasm) + Module& wasm, + RMWResultInfoMap& rmwResultInfo) : allocation(allocation), analyzer(analyzer), func(func), wasm(wasm), - builder(wasm), fields(allocation->type.getHeapType().getStruct().fields) { + builder(wasm), fields(allocation->type.getHeapType().getStruct().fields), + rmwResultInfo(rmwResultInfo) { // Allocate locals to store the allocation's fields and descriptor in. for (auto field : fields) { @@ -1061,6 +1085,24 @@ struct Struct2Local : PostWalker { assert(type == field.type); assert(!field.isPacked()); + bool replacementIsRMWResult = + curr->value->is() && + std::any_of(rmwResultInfo.begin(), rmwResultInfo.end(), + [&](auto& pair) { return pair.second.resultGet == curr->value; }); + + // If the replacement is an RMW result, we cannot replace the whole RMW + // with a local.get; we must keep the RMW operation and just localize + // the ref operand. + if (replacementIsRMWResult) { + auto refType = curr->ref->type; + auto scratch = builder.addVar(func, refType); + auto* set = builder.makeLocalSet(scratch, curr->ref); + auto* get = builder.makeLocalGet(scratch, refType); + curr->ref = get; + replaceCurrent(builder.makeSequence(set, curr)); + return; + } + // We need a scratch local to hold the old, unmodified field value while we // update the original local with the modified value. We also need another // scratch local to hold the evaluated modification value while we set the @@ -1073,7 +1115,7 @@ struct Struct2Local : PostWalker { auto* block = builder.makeSequence(builder.makeDrop(curr->ref), - builder.makeLocalSet(valScratch, curr->value)); + builder.makeLocalSet(valScratch, curr->value)); // Stash the old value to return. block->list.push_back( @@ -1113,6 +1155,19 @@ struct Struct2Local : PostWalker { // Unstash the old value. block->list.push_back(builder.makeLocalGet(oldScratch, type)); block->type = type; + + // If the RMW result is a reference type, store it in a scratch local + // so that downstream uses can see the flow. + if (curr->type.isRef() && !curr->type.isNull()) { + auto resultType = curr->type; + auto scratch = builder.addVar(func, resultType); + auto* setScratch = builder.makeLocalSet(scratch, block); + auto* getScratch = builder.makeLocalGet(scratch, resultType); + rmwResultInfo[curr] = {curr, setScratch, getScratch}; + replaceCurrent(builder.makeBlock({setScratch, getScratch})); + return; + } + replaceCurrent(block); } @@ -1122,6 +1177,28 @@ struct Struct2Local : PostWalker { return; } + // Check if expected or replacement are RMW results + bool expectedIsRMWResult = + curr->expected->is() && + std::any_of(rmwResultInfo.begin(), rmwResultInfo.end(), + [&](auto& pair) { return pair.second.resultGet == curr->expected; }); + bool replacementIsRMWResult = + curr->replacement->is() && + std::any_of(rmwResultInfo.begin(), rmwResultInfo.end(), + [&](auto& pair) { return pair.second.resultGet == curr->replacement; }); + + // If either is an RMW result, we cannot fully optimize away the cmpxchg; + // we just localize the ref operand. + if (expectedIsRMWResult || replacementIsRMWResult) { + auto refType = curr->ref->type; + auto scratch = builder.addVar(func, refType); + auto* set = builder.makeLocalSet(scratch, curr->ref); + auto* get = builder.makeLocalGet(scratch, refType); + curr->ref = get; + replaceCurrent(builder.makeSequence(set, curr)); + return; + } + // The allocation might flow into `ref` or `expected`, but not // `replacement`, because then it would be considered to have escaped. if (analyzer.getInteraction(curr->ref) == ParentChildInteraction::Flows) { @@ -1145,9 +1222,9 @@ struct Struct2Local : PostWalker { auto* block = builder.makeBlock( {builder.makeDrop(curr->ref), - builder.makeLocalSet(expectedScratch, curr->expected), - builder.makeLocalSet(replacementScratch, curr->replacement), - builder.makeLocalSet(oldScratch, builder.makeLocalGet(local, type))}); + builder.makeLocalSet(expectedScratch, curr->expected), + builder.makeLocalSet(replacementScratch, curr->replacement), + builder.makeLocalSet(oldScratch, builder.makeLocalGet(local, type))}); // Create the check for whether we should do the exchange. auto* lhs = builder.makeLocalGet(local, type); @@ -1164,14 +1241,27 @@ struct Struct2Local : PostWalker { block->list.push_back(builder.makeIf( pred, builder.makeLocalSet(local, - builder.makeLocalGet(replacementScratch, type)))); + builder.makeLocalGet(replacementScratch, type)))); // Unstash the old value. block->list.push_back(builder.makeLocalGet(oldScratch, type)); block->type = type; + + // If the cmpxchg result is a reference type, store it in a scratch local + if (curr->type.isRef() && !curr->type.isNull()) { + auto resultType = curr->type; + auto scratch = builder.addVar(func, resultType); + auto* setScratch = builder.makeLocalSet(scratch, block); + auto* getScratch = builder.makeLocalGet(scratch, resultType); + rmwResultInfo[curr] = {curr, setScratch, getScratch}; + replaceCurrent(builder.makeBlock({setScratch, getScratch})); + return; + } + replaceCurrent(block); return; } + if (analyzer.getInteraction(curr->expected) != ParentChildInteraction::Flows) { // Since the allocation does not flow from `ref`, it must not flow through @@ -1191,11 +1281,10 @@ struct Struct2Local : PostWalker { auto* structGet = builder.makeStructGet( curr->index, getRefScratch, curr->order, curr->type); auto* block = builder.makeBlock({setRefScratch, - builder.makeDrop(curr->expected), - builder.makeDrop(curr->replacement), - structGet}); + builder.makeDrop(curr->expected), + builder.makeDrop(curr->replacement), + structGet}); replaceCurrent(block); - return; } void visitArrayCmpxchg(ArrayCmpxchg* curr) { @@ -1553,6 +1642,8 @@ struct Heap2Local { std::unique_ptr parents; std::unique_ptr branchTargets; + RMWResultInfoMap rmwResultInfo; + Heap2Local(Function* func, Module& wasm, const PassOptions& passOptions) : func(func), wasm(wasm), passOptions(passOptions), localGraph(std::make_unique(func, &wasm)), @@ -1623,13 +1714,13 @@ struct Heap2Local { continue; } EscapeAnalyzer analyzer( - *localGraph, *parents, *branchTargets, passOptions, wasm); + *localGraph, *parents, *branchTargets, passOptions, wasm, rmwResultInfo); if (!analyzer.escapes(allocation)) { // Convert the allocation and all its uses into a struct. Then convert // the struct into locals. auto* structNew = Array2Struct(allocation, analyzer, func, wasm).structNew; - Struct2Local(structNew, analyzer, func, wasm); + Struct2Local(structNew, analyzer, func, wasm, rmwResultInfo); optimized = true; resetAnalysisData(); } @@ -1644,9 +1735,9 @@ struct Heap2Local { // Check for escaping, noting relevant information as we go. If this does // not escape, optimize it into locals. EscapeAnalyzer analyzer( - *localGraph, *parents, *branchTargets, passOptions, wasm); + *localGraph, *parents, *branchTargets, passOptions, wasm, rmwResultInfo); if (!analyzer.escapes(allocation)) { - Struct2Local(allocation, analyzer, func, wasm); + Struct2Local(allocation, analyzer, func, wasm, rmwResultInfo); optimized = true; resetAnalysisData(); } diff --git a/test/lit/passes/heap2local-rmw.wast b/test/lit/passes/heap2local-rmw.wast index 07fc1eadac1..a09ed6e6263 100644 --- a/test/lit/passes/heap2local-rmw.wast +++ b/test/lit/passes/heap2local-rmw.wast @@ -561,24 +561,30 @@ ;; CHECK-NEXT: (local $1 (ref null $struct)) ;; CHECK-NEXT: (local $2 (ref null $struct)) ;; CHECK-NEXT: (local $3 (ref null $struct)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local $4 (ref null $struct)) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (local.get $3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) (func $rmw-xchg-ref (param (ref null $struct)) (result (ref null $struct)) (struct.atomic.rmw.xchg $struct 0 @@ -592,35 +598,41 @@ ;; CHECK-NEXT: (local $3 (ref null $struct)) ;; CHECK-NEXT: (local $4 eqref) ;; CHECK-NEXT: (local $5 (ref null $struct)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (local $6 (ref null $struct)) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: (local.get $4) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (local.get $6) ;; CHECK-NEXT: ) (func $rmw-cmpxchg-ref (param (ref null $struct) (ref null $struct)) (result (ref null $struct)) (struct.atomic.rmw.cmpxchg $struct 0 @@ -1166,6 +1178,7 @@ ;; CHECK-NEXT: (local $2 eqref) ;; CHECK-NEXT: (local $3 eqref) ;; CHECK-NEXT: (local $4 eqref) + ;; CHECK-NEXT: (local $5 eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) @@ -1173,44 +1186,49 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result eqref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (block (result eqref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) @@ -1258,49 +1276,55 @@ ;; CHECK-NEXT: (local $2 (ref $inner)) ;; CHECK-NEXT: (local $3 eqref) ;; CHECK-NEXT: (local $4 (ref $inner)) + ;; CHECK-NEXT: (local $5 (ref $inner)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $inner)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (struct.new_default $inner) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (block (result (ref $inner)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (struct.new_default $inner) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1326,49 +1350,55 @@ ;; CHECK-NEXT: (local $2 (ref $shared-inner)) ;; CHECK-NEXT: (local $3 (ref null (shared eq))) ;; CHECK-NEXT: (local $4 (ref $shared-inner)) + ;; CHECK-NEXT: (local $5 (ref $shared-inner)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $shared-inner)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null (shared none))) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (struct.new_default $shared-inner) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.null (shared none)) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null (shared none))) - ;; CHECK-NEXT: (ref.null (shared none)) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null (shared none))) - ;; CHECK-NEXT: (ref.null (shared none)) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (block (result (ref $shared-inner)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (block (result (ref null (shared none))) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (struct.new_default $shared-inner) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: (block (result (ref null (shared none))) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: (block (result (ref null (shared none))) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1401,37 +1431,43 @@ ;; CHECK-NEXT: (local $2 (ref null $struct)) ;; CHECK-NEXT: (local $3 eqref) ;; CHECK-NEXT: (local $4 (ref null $struct)) + ;; CHECK-NEXT: (local $5 (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $struct)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: (local.get $3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1655,47 +1691,53 @@ ;; CHECK-NEXT: (local $3 (ref null (shared eq))) ;; CHECK-NEXT: (local $4 (ref null $struct)) ;; CHECK-NEXT: (local $5 (ref null $struct)) + ;; CHECK-NEXT: (local $6 (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $struct)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null (shared none))) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (ref.null (shared none)) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.null (shared none)) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null (shared none))) - ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (ref.null (shared none)) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (ref.null (shared none)) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (struct.new_default $struct) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (block (result (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (block (result (ref null (shared none))) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: (block (result (ref null (shared none))) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.is_null