From 68bd62c5ef4847595ba89ac9ea0bc9b2ebee1333 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 12 Jun 2026 15:19:53 -0700 Subject: [PATCH 1/3] Preserve unreachable struct.set of non-nullable locals Binaryen usually elides all instructions between unreachable instructions and the ends of their surrounding control flow structures. It also elides unnamed blocks. Meanwhile, WebAssembly validation rules require that gets of non-nullable locals are preceded by sets of the same locals in the same or outer blocks. The combination of these circumstances could previously cause Binaryen to emit invalid modules where local.sets after unreachables in unnamed blocks were elided but also required for validation by later local.gets after the unnamed blocks. Fix the problem by specifically finding and emitting local.sets of non-nullable locals after unreachable instructions, emitting unreachable LocalSets as local.set instead of local.tee, and eliding more unnamed blocks. Update the fuzzer to no longer run DCE unconditionally at the end of the pipeline, which was the previous workaround for this problem. Fixes #5599. --- scripts/fuzz_opt.py | 21 -- src/passes/Poppify.cpp | 7 + src/passes/Print.cpp | 5 +- src/wasm-stack.h | 91 +++++-- src/wasm/wasm-stack.cpp | 22 +- test/lit/basic/polymorphic_stack.wast | 36 +-- test/lit/basic/stack_switching_switch.wast | 10 +- test/lit/basic/unreachable-local-set.wast | 229 ++++++++++++++++++ ...syncify_pass-arg=asyncify-addlist@foo.wast | 2 +- ...o_pass-arg=asyncify-propagate-addlist.wast | 2 +- ...asyncify_pass-arg=asyncify-eh-asserts.wast | 4 +- .../passes/asyncify_pass-arg=asyncify-eh.wast | 2 +- test/lit/passes/code-pushing_tnh.wast | 2 +- test/lit/passes/dae-gc.wast | 2 +- test/lit/passes/dae2.wast | 2 +- test/lit/passes/gufa-cast-all.wast | 2 +- test/lit/passes/gufa-eh-null-type.wast | 2 +- test/lit/passes/gufa-refs.wast | 2 +- test/lit/passes/heap-store-optimization.wast | 2 +- test/lit/passes/heap2local-desc.wast | 4 +- test/lit/passes/heap2local.wast | 2 +- .../lit/passes/inlining_enable-tail-call.wast | 2 +- .../lit/passes/inlining_optimize-level=3.wast | 2 +- .../passes/memory-packing_all-features.wast | 2 +- test/lit/passes/optimize-instructions-gc.wast | 6 +- .../optimize-instructions-ignore-traps.wast | 2 +- .../lit/passes/optimize-instructions-mvp.wast | 4 +- test/lit/passes/precompute-gc-immutable.wast | 2 +- test/lit/passes/precompute-gc.wast | 4 +- .../remove-unused-brs_enable-multivalue.wast | 8 +- test/lit/passes/simplify-locals-gc.wast | 2 +- test/lit/passes/tuple-optimization.wast | 10 +- test/lit/passes/unsubtyping.wast | 2 +- ...d-brs_generate-stack-ir_print-stack-ir.txt | 4 +- test/passes/simplify-locals_all-features.txt | 2 +- ...ll-features_disable-exception-handling.txt | 2 +- 36 files changed, 401 insertions(+), 104 deletions(-) create mode 100644 test/lit/basic/unreachable-local-set.wast diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index fe908e9d5d9..55dce6dc47c 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -229,18 +229,6 @@ def randomize_fuzz_settings(): else: LEGALIZE = False - # if GC is enabled then run --dce at the very end, to ensure that our - # binaries validate in other VMs, due to how non-nullable local validation - # and unreachable code interact. see - # https://github.com/WebAssembly/binaryen/pull/5665 - # https://github.com/WebAssembly/binaryen/issues/5599 - if '--disable-gc' not in FEATURE_OPTS: - GEN_ARGS += ['--dce'] - - # Add --dce not only when generating the original wasm but to the - # optimizations we use to create any other wasm file. - FUZZ_OPTS += ['--dce'] - if CLOSED_WORLD: GEN_ARGS += [CLOSED_WORLD_FLAG] # Enclose the world much of the time when fuzzing closed-world, so that @@ -450,13 +438,6 @@ def pick_initial_contents(): # --fuzz-exec reports a stack limit using this notation STACK_LIMIT = '[trap stack limit]' -# V8 reports this error in rare cases due to limitations in our handling of non- -# nullable locals in unreachable code, see -# https://github.com/WebAssembly/binaryen/pull/5665 -# https://github.com/WebAssembly/binaryen/issues/5599 -# and also see the --dce workaround below that also links to those issues. -V8_UNINITIALIZED_NONDEF_LOCAL = 'uninitialized non-defaultable local' - # JS exceptions are logged as exception thrown: REASON EXCEPTION_PREFIX = 'exception thrown: ' @@ -655,8 +636,6 @@ def filter_known_issues(output): # strings in this list for known issues (to which more need to be # added as necessary). HOST_LIMIT_PREFIX, - # see comment above on this constant - V8_UNINITIALIZED_NONDEF_LOCAL, # V8 does not accept nullable stringviews # (https://github.com/WebAssembly/binaryen/pull/6574) 'expected (ref stringview_wtf16), got nullref', diff --git a/src/passes/Poppify.cpp b/src/passes/Poppify.cpp index c12c7203c0d..b6452b51cde 100644 --- a/src/passes/Poppify.cpp +++ b/src/passes/Poppify.cpp @@ -132,6 +132,7 @@ struct Poppifier : BinaryenIRWriter { void emitScopeEnd(Expression* curr); void emitFunctionEnd(); void emitUnreachable(); + void emitUnreachableLocalSet(Index i); void emitDebugLocation(Expression* curr) {} // Tuple lowering methods @@ -317,6 +318,12 @@ void Poppifier::emitUnreachable() { instrs.push_back(builder.makeUnreachable()); } +void Poppifier::emitUnreachableLocalSet(Index i) { + auto& instrs = scopeStack.back().instrs; + instrs.push_back(builder.makeUnreachable()); + instrs.push_back(builder.makeLocalSet(i, builder.makePop(Type::unreachable))); +} + void Poppifier::emitTupleExtract(TupleExtract* curr) { auto& instrs = scopeStack.back().instrs; auto types = curr->tuple->type; diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 83e7baa580d..2180749eac0 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -498,7 +498,10 @@ struct PrintExpressionContents printLocal(curr->index, currFunction, o); } void visitLocalSet(LocalSet* curr) { - if (curr->isTee()) { + // Print unreachable tees as sets. This makes the output valid WebAssembly + // in more cases because it avoids pushing a concrete type (which may not + // be the type required by the next instruction) onto a polymorphic stack. + if (curr->isTee() && curr->type != Type::unreachable) { printMedium(o, "local.tee "); } else { printMedium(o, "local.set "); diff --git a/src/wasm-stack.h b/src/wasm-stack.h index 6a6649d0aa4..62f844113d5 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -18,6 +18,7 @@ #define wasm_stack_h #include "ir/branch-utils.h" +#include "ir/find_all.h" #include "ir/module-utils.h" #include "ir/properties.h" #include "pass.h" @@ -123,6 +124,7 @@ class BinaryInstWriter : public OverriddenVisitor { // emit an end at the end of a function void emitFunctionEnd(); void emitUnreachable(); + void emitUnreachableLocalSet(Index index); void mapLocalsAndEmitHeader(); MappedLocals mappedLocals; @@ -243,11 +245,16 @@ class BinaryenIRWriter : public Visitor> { void emitScopeEnd(Expression* curr) { static_cast(this)->emitScopeEnd(curr); } - void emitFunctionEnd() { static_cast(this)->emitFunctionEnd(); } - void emitUnreachable() { static_cast(this)->emitUnreachable(); } + SubType& self() { return *static_cast(this); } + void emitFunctionEnd() { self().emitFunctionEnd(); } + void emitUnreachable() { self().emitUnreachable(); } + void emitUnreachableLocalSet(Index index) { + self().emitUnreachableLocalSet(index); + } void emitDebugLocation(Expression* curr) { static_cast(this)->emitDebugLocation(curr); } + void emitUnreachableLocalSets(Expression* curr); void visitPossibleBlockContents(Expression* curr); }; @@ -258,6 +265,23 @@ template void BinaryenIRWriter::write() { emitFunctionEnd(); } +// Normally we do not emit any instructions between a stack-polymorphic (i.e. +// unreachable) instruction and the end of the current control flow structure. +// However, local.sets of non-nullable locals in these unreachable regions can +// still be necessary to make later local.gets valid. We can emit just these +// sets without emitting their values; they will pull whatever type they need +// out of the unreachable. This works no matter what type the current control +// flow structure returns; it will also pull whatever values it needs out of the +// unreachable, and the sets will not push any values to get in the way. +template +void BinaryenIRWriter::emitUnreachableLocalSets(Expression* curr) { + for (auto* set : FindAll(curr).list) { + if (func->getLocalType(set->index).isNonNullable()) { + emitUnreachableLocalSet(set->index); + } + } +} + // Emits a node in a position that can contain a list of contents, like an if // arm. This will emit the node, but if it is a block with no name, just emit // its contents. This is ok to do because a list of contents is ok in the wasm @@ -279,10 +303,12 @@ void BinaryenIRWriter::visitPossibleBlockContents(Expression* curr) { } for (auto* child : block->list) { visit(child); - // Since this child was unreachable, either this child or one of its - // descendants was a source of unreachability that was actually - // emitted. Subsequent children won't be reachable, so skip them. if (child->type == Type::unreachable) { + // Since this child was unreachable, either this child or one of its + // descendants was a source of unreachability that was actually + // emitted. Subsequent children won't be reachable, so skip them. We also + // don't have to worry about local.sets here because they will not be + // visible past the end of the surrounding control flow structure anyway. break; } } @@ -294,19 +320,28 @@ void BinaryenIRWriter::visit(Expression* curr) { // unreachable instructions that just inherit unreachability from their // children, since the latter won't be reached. This (together with logic in // the control flow visitors) also ensures that the final instruction in each - // unreachable block is a source of unreachability, which means we don't need - // to emit an extra `unreachable` before the end of the block to prevent type - // errors. - bool hasUnreachableChild = false; + // unreachable block is a source of unreachability (possibly followed by a + // sequence of local.sets), which means we don't need to emit an extra + // `unreachable` before the end of the block to prevent type errors. + bool sawUnreachable = false; for (auto* child : ValueChildIterator(curr)) { + if (sawUnreachable) { + emitUnreachableLocalSets(curr); + continue; + } visit(child); if (child->type == Type::unreachable) { - hasUnreachableChild = true; - break; + // Skip the following children, except for necessary local.sets they + // contain. + sawUnreachable = true; } } - if (hasUnreachableChild) { - // `curr` is not reachable, so don't emit it. + if (sawUnreachable) { + // `curr` is not reachable, so don't emit it unless it is a + // potentially-necessary local.set itself. + if (auto* set = curr->dynCast()) { + emitUnreachableLocalSet(set->index); + } return; } emitDebugLocation(curr); @@ -323,13 +358,17 @@ template void BinaryenIRWriter::visitBlock(Block* curr) { auto visitChildren = [this](Block* curr, Index from) { auto& list = curr->list; - while (from < list.size()) { - auto* child = list[from]; + bool sawUnreachable = false; + for (Index i = from; i < list.size(); ++i) { + auto* child = list[i]; + if (sawUnreachable) { + emitUnreachableLocalSets(child); + continue; + } visit(child); if (child->type == Type::unreachable) { - break; + sawUnreachable = true; } - ++from; } }; @@ -354,6 +393,10 @@ void BinaryenIRWriter::visitBlock(Block* curr) { } auto afterChildren = [this](Block* curr) { + if (!curr->name) { + // Not emitting a block, so continue on in the parent. + return; + } emitScopeEnd(curr); if (curr->type == Type::unreachable) { // Since this block is unreachable, no instructions will be emitted after @@ -376,13 +419,17 @@ void BinaryenIRWriter::visitBlock(Block* curr) { Block* child; while (!curr->list.empty() && (child = curr->list[0]->dynCast())) { parents.push_back(curr); - emit(curr); + if (curr->name) { + emit(curr); + } curr = child; emitDebugLocation(curr); } // Emit the current block, which does not have a block as a child in the // first position. - emit(curr); + if (curr->name) { + emit(curr); + } visitChildren(curr, 0); afterChildren(curr); bool childUnreachable = curr->type == Type::unreachable; @@ -391,6 +438,9 @@ void BinaryenIRWriter::visitBlock(Block* curr) { auto* parent = parents.back(); parents.pop_back(); if (!childUnreachable) { + // No need to emit unreachable sets here because we will skip everything + // up to the end of the next named block, which would hide their effects + // anyway. visitChildren(parent, 1); } afterChildren(parent); @@ -504,6 +554,9 @@ class BinaryenIRToBinaryWriter writer.emitFunctionEnd(); } void emitUnreachable() { writer.emitUnreachable(); } + void emitUnreachableLocalSet(Index index) { + writer.emitUnreachableLocalSet(index); + } void emitDebugLocation(Expression* curr) { if (sourceMap) { parent.writeSourceMapLocation(curr, func); diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 13f52570b22..1b235ae9301 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -329,8 +329,11 @@ void BinaryInstWriter::visitLocalSet(LocalSet* curr) { o << static_cast(BinaryConsts::LocalSet) << U32LEB(mappedLocals[std::make_pair(curr->index, i)]); } - if (!curr->isTee()) { - // This is not a tee, so just finish setting the values. + if (!curr->isTee() || curr->type == Type::unreachable) { + // This is not a tee or it is unreachable, so just finish setting the + // values. We emit unreachable sets as sets rather than tees to avoid + // pushing concrete types (which may not be correct for the next + // instruction) onto polymorphic stacks. o << static_cast(BinaryConsts::LocalSet) << U32LEB(mappedLocals[std::make_pair(curr->index, 0)]); } else if (auto it = extractedGets.find(curr); it != extractedGets.end()) { @@ -3196,6 +3199,14 @@ void BinaryInstWriter::emitUnreachable() { o << static_cast(BinaryConsts::Unreachable); } +void BinaryInstWriter::emitUnreachableLocalSet(Index index) { + LocalSet set; + set.index = index; + set.type = Type::none; + set.value = nullptr; + visitLocalSet(&set); +} + void BinaryInstWriter::mapLocalsAndEmitHeader() { assert(func && "BinaryInstWriter: function is not set"); // Map params @@ -3583,6 +3594,13 @@ class StackIRGenerator : public BinaryenIRWriter { void emitUnreachable() { stackIR.push_back(makeStackInst(Builder(module).makeUnreachable())); } + void emitUnreachableLocalSet(Index i) { + Builder builder(module); + auto unreachable = builder.makeUnreachable(); + auto set = builder.makeLocalSet(i, unreachable); + emit(unreachable); + emit(set); + } void emitDebugLocation(Expression* curr) {} StackIR& getStackIR() { return stackIR; } diff --git a/test/lit/basic/polymorphic_stack.wast b/test/lit/basic/polymorphic_stack.wast index 2ccaa51e7db..b5249e05877 100644 --- a/test/lit/basic/polymorphic_stack.wast +++ b/test/lit/basic/polymorphic_stack.wast @@ -121,13 +121,13 @@ ;; CHECK-TEXT-NEXT: (local $y f32) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (i64.eqz - ;; CHECK-TEXT-NEXT: (local.tee $x + ;; CHECK-TEXT-NEXT: (local.set $x ;; CHECK-TEXT-NEXT: (unreachable) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (local.tee $y + ;; CHECK-TEXT-NEXT: (local.set $y ;; CHECK-TEXT-NEXT: (i64.eqz ;; CHECK-TEXT-NEXT: (unreachable) ;; CHECK-TEXT-NEXT: ) @@ -136,7 +136,9 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $tee (type $3) (param $x i32) ;; CHECK-BIN-NEXT: (local $y f32) - ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: (local.set $x + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) (func $tee (param $x i32) (local $y f32) @@ -161,7 +163,7 @@ ;; CHECK-TEXT-NEXT: (if ;; CHECK-TEXT-NEXT: (i32.const 259) ;; CHECK-TEXT-NEXT: (then - ;; CHECK-TEXT-NEXT: (local.tee $0 + ;; CHECK-TEXT-NEXT: (local.set $0 ;; CHECK-TEXT-NEXT: (unreachable) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) @@ -172,7 +174,9 @@ ;; CHECK-BIN-NEXT: (if ;; CHECK-BIN-NEXT: (i32.const 259) ;; CHECK-BIN-NEXT: (then - ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: (local.set $0 + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -225,11 +229,8 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $untaken-break-should-have-value (type $0) (result i32) - ;; CHECK-BIN-NEXT: (block - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (i32.const 0) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (i32.const 0) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) @@ -401,7 +402,9 @@ ;; CHECK-BIN-NODEBUG: (func $2 (type $3) (param $0 i32) ;; CHECK-BIN-NODEBUG-NEXT: (local $1 f32) -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $3 (type $2) @@ -409,7 +412,9 @@ ;; CHECK-BIN-NODEBUG-NEXT: (if ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 259) ;; CHECK-BIN-NODEBUG-NEXT: (then -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -419,11 +424,8 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $5 (type $0) (result i32) -;; CHECK-BIN-NODEBUG-NEXT: (block -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/stack_switching_switch.wast b/test/lit/basic/stack_switching_switch.wast index 152ef0aedec..2799525f145 100644 --- a/test/lit/basic/stack_switching_switch.wast +++ b/test/lit/basic/stack_switching_switch.wast @@ -127,7 +127,7 @@ ;; CHECK-TEXT: (func $unreachable (type $2) (param $k (ref null $ct)) (result i32) ;; CHECK-TEXT-NEXT: (return - ;; CHECK-TEXT-NEXT: (local.tee $k + ;; CHECK-TEXT-NEXT: (local.set $k ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable StackSwitch we can't emit) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (i32.const 42) @@ -144,7 +144,9 @@ ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (i32.const 42) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: (local.set $k + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) (func $unreachable (param $k (ref null $ct)) (result i32) (switch $ct $t @@ -215,5 +217,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 42) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/unreachable-local-set.wast b/test/lit/basic/unreachable-local-set.wast new file mode 100644 index 00000000000..d0658a4afc2 --- /dev/null +++ b/test/lit/basic/unreachable-local-set.wast @@ -0,0 +1,229 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s -all --generate-stack-ir --print-stack-ir | filecheck %s +;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s --check-prefix=RTRIP + +;; We normally skip emitting instructions after unreachables, but we must keep +;; local.sets of non-nullable locals in case there are later local.gets that +;; would be invalid without them. + +(module + ;; CHECK: (func $end-of-block (type $0) (param $ref (ref struct)) (result (ref struct)) + ;; CHECK-NEXT: (local $local (ref struct)) + ;; CHECK-NEXT: nop + ;; CHECK-NEXT: unreachable + ;; CHECK-NEXT: unreachable + ;; CHECK-NEXT: local.set $local + ;; CHECK-NEXT: drop + ;; CHECK-NEXT: local.get $local + ;; CHECK-NEXT: ) + ;; RTRIP: (func $end-of-block (type $0) (param $ref (ref struct)) (result (ref struct)) + ;; RTRIP-NEXT: (local $local (ref struct)) + ;; RTRIP-NEXT: (nop) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (local.set $local + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (local.get $local) + ;; RTRIP-NEXT: ) + (func $end-of-block (param $ref (ref struct)) (result (ref struct)) + (local $local (ref struct)) + (drop + (block (result i32) + (nop) + (local.set $local + (unreachable) + ) + ) + ) + (local.get $local) + ) + + ;; CHECK: (func $middle-of-block (type $0) (param $ref (ref struct)) (result (ref struct)) + ;; CHECK-NEXT: (local $local (ref struct)) + ;; CHECK-NEXT: nop + ;; CHECK-NEXT: unreachable + ;; CHECK-NEXT: unreachable + ;; CHECK-NEXT: local.set $local + ;; CHECK-NEXT: drop + ;; CHECK-NEXT: local.get $local + ;; CHECK-NEXT: ) + ;; RTRIP: (func $middle-of-block (type $0) (param $ref (ref struct)) (result (ref struct)) + ;; RTRIP-NEXT: (local $local (ref struct)) + ;; RTRIP-NEXT: (nop) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (local.set $local + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (local.get $local) + ;; RTRIP-NEXT: ) + (func $middle-of-block (param $ref (ref struct)) (result (ref struct)) + (local $local (ref struct)) + (drop + (block (result i32) + (nop) + (local.set $local + (unreachable) + ) + (i32.const 0) + ) + ) + (local.get $local) + ) + + ;; CHECK: (func $after-unreachable (type $0) (param $ref (ref struct)) (result (ref struct)) + ;; CHECK-NEXT: (local $local (ref struct)) + ;; CHECK-NEXT: unreachable + ;; CHECK-NEXT: unreachable + ;; CHECK-NEXT: local.set $local + ;; CHECK-NEXT: drop + ;; CHECK-NEXT: local.get $local + ;; CHECK-NEXT: ) + ;; RTRIP: (func $after-unreachable (type $0) (param $ref (ref struct)) (result (ref struct)) + ;; RTRIP-NEXT: (local $local (ref struct)) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (local.set $local + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (local.get $local) + ;; RTRIP-NEXT: ) + (func $after-unreachable (param $ref (ref struct)) (result (ref struct)) + (local $local (ref struct)) + (drop + (block (result i32) + (unreachable) + (local.set $local + (local.get $ref) + ) + (i32.const 0) + ) + ) + (local.get $local) + ) + + ;; CHECK: (func $nested-exprs (type $0) (param $ref (ref struct)) (result (ref struct)) + ;; CHECK-NEXT: (local $local (ref struct)) + ;; CHECK-NEXT: unreachable + ;; CHECK-NEXT: unreachable + ;; CHECK-NEXT: local.set $local + ;; CHECK-NEXT: drop + ;; CHECK-NEXT: local.get $local + ;; CHECK-NEXT: ) + ;; RTRIP: (func $nested-exprs (type $0) (param $ref (ref struct)) (result (ref struct)) + ;; RTRIP-NEXT: (local $local (ref struct)) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (local.set $local + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (local.get $local) + ;; RTRIP-NEXT: ) + (func $nested-exprs (param $ref (ref struct)) (result (ref struct)) + (local $local (ref struct)) + (drop + (block (result (ref struct)) + (ref.eq + (unreachable) + (ref.as_non_null + (local.tee $local + (local.get $ref) + ) + ) + ) + ) + ) + (local.get $local) + ) + + ;; CHECK: (func $renumbered-locals (type $0) (param $ref (ref struct)) (result (ref struct)) + ;; CHECK-NEXT: (local $tuple (tuple i32 i32)) + ;; CHECK-NEXT: (local $local (ref struct)) + ;; CHECK-NEXT: nop + ;; CHECK-NEXT: unreachable + ;; CHECK-NEXT: unreachable + ;; CHECK-NEXT: local.set $local + ;; CHECK-NEXT: drop + ;; CHECK-NEXT: local.get $local + ;; CHECK-NEXT: ) + ;; RTRIP: (func $renumbered-locals (type $0) (param $ref (ref struct)) (result (ref struct)) + ;; RTRIP-NEXT: (local $tuple i32) + ;; RTRIP-NEXT: (local $2 i32) + ;; RTRIP-NEXT: (local $local (ref struct)) + ;; RTRIP-NEXT: (nop) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (local.set $local + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (local.get $local) + ;; RTRIP-NEXT: ) + (func $renumbered-locals (param $ref (ref struct)) (result (ref struct)) + ;; When the tuple is lowered, it causes subsequent locals to be mapped to + ;; new indices. We should respect that remapping when writing unreachable + ;; local.sets. + (local $tuple (tuple i32 i32)) + (local $local (ref struct)) + (drop + (block (result i32) + (nop) + (local.set $local + (unreachable) + ) + ) + ) + (local.get $local) + ) + + ;; CHECK: (func $unnamed-nested-block (type $1) (result (ref struct)) + ;; CHECK-NEXT: (local $local (ref struct)) + ;; CHECK-NEXT: block $block (result (ref struct)) + ;; CHECK-NEXT: ref.null none + ;; CHECK-NEXT: br_on_non_null $block + ;; CHECK-NEXT: ref.null none + ;; CHECK-NEXT: ref.as_non_null + ;; CHECK-NEXT: local.set $local + ;; CHECK-NEXT: local.get $local + ;; CHECK-NEXT: drop + ;; CHECK-NEXT: unreachable + ;; CHECK-NEXT: end + ;; CHECK-NEXT: ) + ;; RTRIP: (func $unnamed-nested-block (type $1) (result (ref struct)) + ;; RTRIP-NEXT: (local $local (ref struct)) + ;; RTRIP-NEXT: (block $block (result (ref struct)) + ;; RTRIP-NEXT: (br_on_non_null $block + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (local.set $local + ;; RTRIP-NEXT: (ref.as_non_null + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (local.get $local) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $unnamed-nested-block (result (ref struct)) + (local $local (ref struct)) + (block $block (result (ref struct)) + ;; This unnamed intermediate block must be elided for the later local.get to + ;; be valid. + (block + ;; Use the label to ensure the outer block is emitted. + (br_on_non_null $block + (ref.null none) + ) + (local.set $local + (ref.as_non_null (ref.null none)) + ) + ) + (drop + (local.get $local) + ) + (unreachable) + ) + ) +) diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo.wast b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo.wast index 2803d243d06..0860f5eeae3 100644 --- a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo.wast +++ b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo.wast @@ -43,7 +43,7 @@ ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (block $__asyncify_unwind ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-propagate-addlist.wast b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-propagate-addlist.wast index 37075e800a4..7c2186dd494 100644 --- a/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-propagate-addlist.wast +++ b/test/lit/passes/asyncify_pass-arg=asyncify-addlist@foo_pass-arg=asyncify-propagate-addlist.wast @@ -42,7 +42,7 @@ ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (block $__asyncify_unwind ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-eh-asserts.wast b/test/lit/passes/asyncify_pass-arg=asyncify-eh-asserts.wast index 2a9a2b244ba..59c31c447ca 100644 --- a/test/lit/passes/asyncify_pass-arg=asyncify-eh-asserts.wast +++ b/test/lit/passes/asyncify_pass-arg=asyncify-eh-asserts.wast @@ -59,7 +59,7 @@ ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $12 + ;; CHECK-NEXT: (local.set $12 ;; CHECK-NEXT: (block $__asyncify_unwind ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block @@ -338,7 +338,7 @@ ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $18 + ;; CHECK-NEXT: (local.set $18 ;; CHECK-NEXT: (block $__asyncify_unwind ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-eh.wast b/test/lit/passes/asyncify_pass-arg=asyncify-eh.wast index e3d932bb500..c07df1c9988 100644 --- a/test/lit/passes/asyncify_pass-arg=asyncify-eh.wast +++ b/test/lit/passes/asyncify_pass-arg=asyncify-eh.wast @@ -45,7 +45,7 @@ ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (block $__asyncify_unwind ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block diff --git a/test/lit/passes/code-pushing_tnh.wast b/test/lit/passes/code-pushing_tnh.wast index 74791aca91c..aa8373056d9 100644 --- a/test/lit/passes/code-pushing_tnh.wast +++ b/test/lit/passes/code-pushing_tnh.wast @@ -53,7 +53,7 @@ ;; CHECK: (func $unreachable-value ;; CHECK-NEXT: (local $x i32) - ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast index 6d014d0f8b4..bdccf601edf 100644 --- a/test/lit/passes/dae-gc.wast +++ b/test/lit/passes/dae-gc.wast @@ -24,7 +24,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/dae2.wast b/test/lit/passes/dae2.wast index b6ed701f7f1..594ad0054de 100644 --- a/test/lit/passes/dae2.wast +++ b/test/lit/passes/dae2.wast @@ -3677,7 +3677,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $a + ;; CHECK-NEXT: (local.set $a ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $g diff --git a/test/lit/passes/gufa-cast-all.wast b/test/lit/passes/gufa-cast-all.wast index a1675982f08..acbe4d9c562 100644 --- a/test/lit/passes/gufa-cast-all.wast +++ b/test/lit/passes/gufa-cast-all.wast @@ -127,7 +127,7 @@ ;; CHECK: (func $unreachable (type $none_=>_none) ;; CHECK-NEXT: (local $a (ref $A)) - ;; CHECK-NEXT: (local.tee $a + ;; CHECK-NEXT: (local.set $a ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop diff --git a/test/lit/passes/gufa-eh-null-type.wast b/test/lit/passes/gufa-eh-null-type.wast index dd05f8388bc..4cccd1bac43 100644 --- a/test/lit/passes/gufa-eh-null-type.wast +++ b/test/lit/passes/gufa-eh-null-type.wast @@ -34,7 +34,7 @@ ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block0 (result (ref noexn)) - ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br_on_non_null $block0 diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index 14f362f5e3d..173d086e918 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -313,7 +313,7 @@ ;; CHECK-NEXT: (local $x anyref) ;; CHECK-NEXT: (local $y anyref) ;; CHECK-NEXT: (local $z anyref) - ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $get-nothing) diff --git a/test/lit/passes/heap-store-optimization.wast b/test/lit/passes/heap-store-optimization.wast index 2a773a3882f..c62078f7cba 100644 --- a/test/lit/passes/heap-store-optimization.wast +++ b/test/lit/passes/heap-store-optimization.wast @@ -774,7 +774,7 @@ ;; CHECK: (func $unreachable (type $1) ;; CHECK-NEXT: (local $ref (ref null $struct)) - ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) diff --git a/test/lit/passes/heap2local-desc.wast b/test/lit/passes/heap2local-desc.wast index b6467beb544..ecd9189e12f 100644 --- a/test/lit/passes/heap2local-desc.wast +++ b/test/lit/passes/heap2local-desc.wast @@ -1079,7 +1079,7 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $v + ;; CHECK-NEXT: (local.set $v ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block @@ -1305,7 +1305,7 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $func + ;; CHECK-NEXT: (local.set $func ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; (replaces unreachable RefGetDesc we can't emit) diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index 1f72d5f947c..ec1e06a4a1d 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -4402,7 +4402,7 @@ ;; CHECK: (func $array.cast.struct.set (type $1) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (local $struct (ref struct)) - ;; CHECK-NEXT: (local.tee $struct + ;; CHECK-NEXT: (local.set $struct ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) diff --git a/test/lit/passes/inlining_enable-tail-call.wast b/test/lit/passes/inlining_enable-tail-call.wast index dae243dcf5b..52687ee0fd8 100644 --- a/test/lit/passes/inlining_enable-tail-call.wast +++ b/test/lit/passes/inlining_enable-tail-call.wast @@ -1175,7 +1175,7 @@ ;; CHECK-NEXT: (block $__return_call ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (block ;; CHECK-NEXT: (br $__inlined_func$second-2$1) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/inlining_optimize-level=3.wast b/test/lit/passes/inlining_optimize-level=3.wast index 654786d5e0d..dc35fb10b23 100644 --- a/test/lit/passes/inlining_optimize-level=3.wast +++ b/test/lit/passes/inlining_optimize-level=3.wast @@ -455,7 +455,7 @@ ;; CHECK-NEXT: (block $__inlined_func$A$3 ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (block $__inlined_func$C$2 (result f32) - ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (block $__inlined_func$D$1 ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/memory-packing_all-features.wast b/test/lit/passes/memory-packing_all-features.wast index 82e4009ffd9..01650a592bc 100644 --- a/test/lit/passes/memory-packing_all-features.wast +++ b/test/lit/passes/memory-packing_all-features.wast @@ -2497,7 +2497,7 @@ ;; CHECK: (func $0 (type $0) ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 0e1676ef063..c231f76d059 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -2834,7 +2834,7 @@ ;; CHECK-NEXT: (local $temp (ref null $struct)) ;; CHECK-NEXT: (block ;; (replaces unreachable StructSet we can't emit) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $temp + ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2863,7 +2863,7 @@ ;; CHECK-NEXT: (local $temp (ref none)) ;; CHECK-NEXT: (block ;; (replaces unreachable ArraySet we can't emit) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $temp + ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2978,7 +2978,7 @@ ;; CHECK: (func $non-null-bottom-ref (type $45) (result (ref func)) ;; CHECK-NEXT: (local $0 funcref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (loop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-ignore-traps.wast b/test/lit/passes/optimize-instructions-ignore-traps.wast index 2417e58d48a..8e89f71caef 100644 --- a/test/lit/passes/optimize-instructions-ignore-traps.wast +++ b/test/lit/passes/optimize-instructions-ignore-traps.wast @@ -213,7 +213,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (i32.or ;; CHECK-NEXT: (i32.eqz diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index eb5f36382b7..dd2d0daa4d5 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -10981,7 +10981,7 @@ ;; CHECK: (func $tee-with-unreachable-value (result f64) ;; CHECK-NEXT: (local $var$0 i32) ;; CHECK-NEXT: (block $label$1 (result f64) - ;; CHECK-NEXT: (local.tee $var$0 + ;; CHECK-NEXT: (local.set $var$0 ;; CHECK-NEXT: (br_if $label$1 ;; CHECK-NEXT: (f64.const 1) ;; CHECK-NEXT: (unreachable) @@ -18704,7 +18704,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $temp + ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/precompute-gc-immutable.wast b/test/lit/passes/precompute-gc-immutable.wast index 7520c8f1266..ed8f33bf58f 100644 --- a/test/lit/passes/precompute-gc-immutable.wast +++ b/test/lit/passes/precompute-gc-immutable.wast @@ -175,7 +175,7 @@ ;; CHECK: (func $unreachable (type $2) ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) - ;; CHECK-NEXT: (local.tee $ref-imm + ;; CHECK-NEXT: (local.set $ref-imm ;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) diff --git a/test/lit/passes/precompute-gc.wast b/test/lit/passes/precompute-gc.wast index 6f5b1af41b3..bd4fc50c5cb 100644 --- a/test/lit/passes/precompute-gc.wast +++ b/test/lit/passes/precompute-gc.wast @@ -1066,7 +1066,7 @@ ;; CHECK: (func $get-nonnullable-in-unreachable (type $10) (result anyref) ;; CHECK-NEXT: (local $x (ref any)) - ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if @@ -1182,7 +1182,7 @@ ;; CHECK: (func $get-nonnullable-in-unreachable-tuple (type $20) (result anyref i32) ;; CHECK-NEXT: (local $x (tuple (ref any) i32)) - ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if diff --git a/test/lit/passes/remove-unused-brs_enable-multivalue.wast b/test/lit/passes/remove-unused-brs_enable-multivalue.wast index efe701e6fd4..f706cd7558f 100644 --- a/test/lit/passes/remove-unused-brs_enable-multivalue.wast +++ b/test/lit/passes/remove-unused-brs_enable-multivalue.wast @@ -3795,7 +3795,7 @@ ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $p) ;; CHECK-NEXT: (then @@ -3858,7 +3858,7 @@ ;; CHECK-NEXT: (block $label$4 (result i64) ;; CHECK-NEXT: (block $label$5 ;; CHECK-NEXT: (block $label$6 - ;; CHECK-NEXT: (local.tee $var$1 + ;; CHECK-NEXT: (local.set $var$1 ;; CHECK-NEXT: (if (result f64) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (then @@ -4055,7 +4055,7 @@ ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (block $label$2 - ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (loop $label$5 ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (drop @@ -5193,7 +5193,7 @@ ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/simplify-locals-gc.wast b/test/lit/passes/simplify-locals-gc.wast index 85ec16d99ca..ba7007a361d 100644 --- a/test/lit/passes/simplify-locals-gc.wast +++ b/test/lit/passes/simplify-locals-gc.wast @@ -73,7 +73,7 @@ ;; CHECK: (func $unreachable-struct.get (type $6) (param $x (ref $struct)) (param $y (ref $struct-immutable)) (result i32) ;; CHECK-NEXT: (local $temp i32) - ;; CHECK-NEXT: (local.tee $temp + ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 6ee5cc829b5..fce17ba0ba4 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -576,7 +576,7 @@ ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.tee $tuple + ;; CHECK-NEXT: (local.set $tuple ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -586,14 +586,14 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.tee $tuple + ;; CHECK-NEXT: (local.set $tuple ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $tuple - ;; CHECK-NEXT: (local.tee $nontuple + ;; CHECK-NEXT: (local.set $tuple + ;; CHECK-NEXT: (local.set $nontuple ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1069,7 +1069,7 @@ ;; CHECK-NEXT: (local $tuple (tuple i32 i64)) ;; CHECK-NEXT: (local $non-tuple i32) ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.tee $non-tuple + ;; CHECK-NEXT: (local.set $non-tuple ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/unsubtyping.wast b/test/lit/passes/unsubtyping.wast index af406571ff2..bbbeb8e7252 100644 --- a/test/lit/passes/unsubtyping.wast +++ b/test/lit/passes/unsubtyping.wast @@ -2030,7 +2030,7 @@ ;; CHECK: (func $test (type $3) (param $C (ref $C)) (result (ref $A) (ref $A)) ;; CHECK-NEXT: (local $Bs (tuple (ref $B) (ref $B))) ;; CHECK-NEXT: (block $l - ;; CHECK-NEXT: (local.tee $Bs + ;; CHECK-NEXT: (local.set $Bs ;; CHECK-NEXT: (block ;; CHECK-NEXT: (tuple.drop 2 ;; CHECK-NEXT: (tuple.make 2 diff --git a/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt b/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt index 8d4847a976b..c31240709fb 100644 --- a/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt +++ b/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt @@ -4,7 +4,7 @@ (block $label$1 (block $label$2 (loop $label$3 - (local.tee $var$0 + (local.set $var$0 (block $label$4 (unreachable) ) @@ -24,6 +24,8 @@ unreachable end unreachable + unreachable + local.set $var$0 end unreachable end diff --git a/test/passes/simplify-locals_all-features.txt b/test/passes/simplify-locals_all-features.txt index 9daee90a264..72870ef17df 100644 --- a/test/passes/simplify-locals_all-features.txt +++ b/test/passes/simplify-locals_all-features.txt @@ -915,7 +915,7 @@ ) (func $drop-tee-unreachable (type $FUNCSIG$v) (local $x i32) - (local.tee $x + (local.set $x (unreachable) ) (drop diff --git a/test/passes/simplify-locals_all-features_disable-exception-handling.txt b/test/passes/simplify-locals_all-features_disable-exception-handling.txt index 92356286763..0b3b4fe96aa 100644 --- a/test/passes/simplify-locals_all-features_disable-exception-handling.txt +++ b/test/passes/simplify-locals_all-features_disable-exception-handling.txt @@ -909,7 +909,7 @@ ) (func $drop-tee-unreachable (type $FUNCSIG$v) (local $x i32) - (local.tee $x + (local.set $x (unreachable) ) (drop From eb74cb0c7aecf776524a772640423b757422e23f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 16 Jun 2026 14:57:46 -0700 Subject: [PATCH 2/3] fix curr => child --- src/wasm-stack.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-stack.h b/src/wasm-stack.h index 62f844113d5..a236d8ca691 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -326,7 +326,7 @@ void BinaryenIRWriter::visit(Expression* curr) { bool sawUnreachable = false; for (auto* child : ValueChildIterator(curr)) { if (sawUnreachable) { - emitUnreachableLocalSets(curr); + emitUnreachableLocalSets(child); continue; } visit(child); From 06f78473ca3bc3c498be1c744064da639aba43a9 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 16 Jun 2026 16:42:41 -0700 Subject: [PATCH 3/3] check non-nullability in another place --- src/wasm-stack.h | 3 ++- test/lit/basic/polymorphic_stack.wast | 16 ++++------------ test/lit/basic/stack_switching_switch.wast | 8 ++------ ...used-brs_generate-stack-ir_print-stack-ir.txt | 2 -- 4 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/wasm-stack.h b/src/wasm-stack.h index a236d8ca691..93f484d9709 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -339,7 +339,8 @@ void BinaryenIRWriter::visit(Expression* curr) { if (sawUnreachable) { // `curr` is not reachable, so don't emit it unless it is a // potentially-necessary local.set itself. - if (auto* set = curr->dynCast()) { + if (auto* set = curr->dynCast(); + set && func->getLocalType(set->index).isNonNullable()) { emitUnreachableLocalSet(set->index); } return; diff --git a/test/lit/basic/polymorphic_stack.wast b/test/lit/basic/polymorphic_stack.wast index b5249e05877..72a3bceb454 100644 --- a/test/lit/basic/polymorphic_stack.wast +++ b/test/lit/basic/polymorphic_stack.wast @@ -136,9 +136,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $tee (type $3) (param $x i32) ;; CHECK-BIN-NEXT: (local $y f32) - ;; CHECK-BIN-NEXT: (local.set $x - ;; CHECK-BIN-NEXT: (unreachable) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $tee (param $x i32) (local $y f32) @@ -174,9 +172,7 @@ ;; CHECK-BIN-NEXT: (if ;; CHECK-BIN-NEXT: (i32.const 259) ;; CHECK-BIN-NEXT: (then - ;; CHECK-BIN-NEXT: (local.set $0 - ;; CHECK-BIN-NEXT: (unreachable) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -402,9 +398,7 @@ ;; CHECK-BIN-NODEBUG: (func $2 (type $3) (param $0 i32) ;; CHECK-BIN-NODEBUG-NEXT: (local $1 f32) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $3 (type $2) @@ -412,9 +406,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (if ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 259) ;; CHECK-BIN-NODEBUG-NEXT: (then -;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/stack_switching_switch.wast b/test/lit/basic/stack_switching_switch.wast index 2799525f145..29ac99d9651 100644 --- a/test/lit/basic/stack_switching_switch.wast +++ b/test/lit/basic/stack_switching_switch.wast @@ -144,9 +144,7 @@ ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (i32.const 42) ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (local.set $k - ;; CHECK-BIN-NEXT: (unreachable) - ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) ;; CHECK-BIN-NEXT: ) (func $unreachable (param $k (ref null $ct)) (result i32) (switch $ct $t @@ -217,7 +215,5 @@ ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 42) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (local.set $0 -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) -;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt b/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt index c31240709fb..c07a6cea041 100644 --- a/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt +++ b/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt @@ -24,8 +24,6 @@ unreachable end unreachable - unreachable - local.set $var$0 end unreachable end