`--translate-to-exnref` / `--emit-exnref` produce invalid IR when a delegate target's unreachable `try` body ends in a none-typed instruction
Maintainer antworten meist innerhalb von 1 Tag
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 3/5
- Geschätzter Aufwand
- 1-2 Tage
- Anfängerfreundlichkeit
- 76/100
Rechercherichtung
Read TranslateToExnref::processDelegateTarget in src/passes/TranslateEH.cpp and the existing cases in test/lit/passes/translate-to-exnref.wast. Reproduce the invalid output with the supplied WAT and --translate-to-exnref, then add a regression case covering an unreachable delegate target body ending in a none-typed instruction. Done when the pass produces valid output and both RUN lines in the lit test pass.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
wasm-opt validates the module after running passes, and a pass should not turn a valid module into an invalid one. On the input below, --translate-to-exnref (also reached through --emit-exnref) does: the validator rejects the pass's output with "block with value must not have last element that is none" and wasm-opt aborts with Fatal: error after opts.
Versions: the version_133 release binary (binaryen-version_133-x86_64-linux.tar.gz) and main at 6e5257bd (2026-10-21).
The input is a try that is the target of a delegate, whose body is unreachable and ends in a none-typed instruction ((unreachable) (nop)). In TranslateToExnref::processDelegateTarget (src/passes/TranslateEH.cpp), no br is appended for an unreachable body, and Builder::blockifyWithName then reuses the unnamed body block as the (result exnref) trampoline block. That block still ends in (nop), which is invalid once the block has a value. This code has not changed since the pass was added in #6210.
Reproduction steps
poc.wat:
(module
(func (export "f")
(try $outer
(do
(try
(do)
(delegate $outer))
(unreachable)
(nop))
(catch_all))))
wasm-opt poc.wat -all -o before.wasm
# expected: exit 0
# version_133: exit 0 (the input is valid)
wasm-opt poc.wat --enable-exception-handling --translate-to-exnref -o out.wasm
# expected: exit 0, a valid out.wasm
# version_133: exit 1
# [wasm-validator error in function 0] none == none: block with value must not have last element that is none, on
# (block $outer0 (result exnref)
# (try_table (catch_all_ref $outer0)
# )
# (unreachable)
# (nop)
# )
# Fatal: error after opts
wasm-opt poc.wat -all --emit-exnref -o out.wasm
# expected: exit 0
# version_133: exit 1, same validator error
It does not trigger when:
wasm-opt before.wasm -all --translate-to-exnref -o out.wasm # binary input: the reader drops the dead (nop)
# version_133: exit 0
wasm-opt poc.wat -all -O1 --translate-to-exnref -o out.wasm # -O1 removes the dead code first
# version_133: exit 0
and it does not trigger when the body ends in (unreachable) itself (remove the (nop)).
Proposed Patch
The patch below wraps such a body in a new block instead of reusing it, and adds a lit test. With it, the input above and the full test/lit/passes/translate-to-exnref.wast (both RUN lines) pass.
Applies to main (6e5257bd) with git am: save the block below as fix.patch and run git am fix.patch.
fix.patch
From c9897d52b449323b25a8ad7bd791f1da42ad6781 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alperen=20Kele=C5=9F?= <[email protected]>
Date: Wed, 21 Oct 2026 17:38:20 -0400
Subject: [PATCH] [EH] Fix invalid trampoline block for unreachable delegate
target bodies
When a try that is the target of a delegate has an unreachable body,
TranslateToExnref::processDelegateTarget adds no br to the outer block,
and Builder::blockifyWithName reuses the unnamed body block as the
(result exnref) trampoline block. If that block ends with a none-typed
expression, e.g. `(unreachable) (nop)`, it becomes a block with a value
whose last element is none, and validation after the pass fails with
"block with value must not have last element that is none".
Wrap such a body in a new block instead of reusing it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
src/passes/TranslateEH.cpp | 12 ++++++-
test/lit/passes/translate-to-exnref.wast | 45 ++++++++++++++++++++++++
2 files changed, 56 insertions(+), 1 deletion(-)
diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp
index 23d21e6..94d6060 100644
--- a/src/passes/TranslateEH.cpp
+++ b/src/passes/TranslateEH.cpp
@@ -302,7 +302,17 @@ struct TranslateToExnref : public WalkerPass<PostWalker<TranslateToExnref>> {
auto* brToOuter = curr->body->type == Type::unreachable
? nullptr
: builder.makeBreak(outerBlock->name);
- innerBody = builder.blockifyWithName(curr->body,
+ // An unreachable body can be a block that ends with a none-typed
+ // expression, which cannot end a block with a value, so wrap it instead
+ // of reusing it as the trampoline block.
+ auto* bodyBlock = curr->body->dynCast<Block>();
+ Expression* body = curr->body;
+ if (!brToOuter && bodyBlock && !bodyBlock->name.is() &&
+ !bodyBlock->list.empty() &&
+ bodyBlock->list.back()->type != Type::unreachable) {
+ body = builder.makeBlock(curr->body);
+ }
+ innerBody = builder.blockifyWithName(body,
delegateTrampoline,
brToOuter,
Type(HeapType::exn, Nullable));
diff --git a/test/lit/passes/translate-to-exnref.wast b/test/lit/passes/translate-to-exnref.wast
index b416850..11b0ecb 100644
--- a/test/lit/passes/translate-to-exnref.wast
+++ b/test/lit/passes/translate-to-exnref.wast
@@ -1827,6 +1827,51 @@
)
)
+ ;; CHECK: (func $delegate-target-outer-try-unreachable-ends-with-none (type $1)
+ ;; CHECK-NEXT: (try_table
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $l00 (result exnref)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (try_table (catch_all_ref $l00)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; STACKIR-OPT: (func $delegate-target-outer-try-unreachable-ends-with-none (type $1)
+ ;; STACKIR-OPT-NEXT: try_table
+ ;; STACKIR-OPT-NEXT: block $l00 (result exnref)
+ ;; STACKIR-OPT-NEXT: try_table (catch_all_ref $l00)
+ ;; STACKIR-OPT-NEXT: call $foo
+ ;; STACKIR-OPT-NEXT: end
+ ;; STACKIR-OPT-NEXT: return
+ ;; STACKIR-OPT-NEXT: end
+ ;; STACKIR-OPT-NEXT: throw_ref
+ ;; STACKIR-OPT-NEXT: end
+ ;; STACKIR-OPT-NEXT: unreachable
+ ;; STACKIR-OPT-NEXT: )
+ (func $delegate-target-outer-try-unreachable-ends-with-none
+ ;; Same as above, but the unreachable body ends with a none-typed
+ ;; instruction. The body block should not be reused as the exnref-typed
+ ;; trampoline block, which would be invalid.
+ (try $l0
+ (do
+ (try
+ (do
+ (call $foo)
+ )
+ (delegate $l0)
+ )
+ (return)
+ (nop)
+ )
+ )
+ )
+
;; CHECK: (func $delegate-target-caller-none (type $1)
;; CHECK-NEXT: (throw_ref
;; CHECK-NEXT: (block $__binaryen_delegate_caller_target0 (result exnref)
--
2.56.0
Notes
- Related, but different bugs in the same pass: #6274, #6518, #6519.
- Found by an automated differential tester (Ephyra); the cause analysis was drafted with AI assistance.
- Vorherrschende Sprache
- WebAssembly
- Sterne
- 8.7k
- Forks
- 897
- Ø Merge
- 2 T. 2 Std.
- Gemergte PRs (30 T.)
- 101
Entwicklungsumgebung
- Kein Dockerfile und keine Docker-Compose-Datei
- Keine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus WebAssembly/binaryen
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 88/100
WebAssembly/binaryen#9135 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 Ein halber Tag Anfängerfreundlichkeit 76/100
WebAssembly/binaryen#9018 · 3 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 62/100
WebAssembly/binaryen#9257 · 2 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 45/100
WebAssembly/binaryen#9249 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Memory64Lowering: table.get/table.set keep i64 index on lowered table64, output fails validationOffen
Schwierigkeit 3/5 Ein halber Tag Anfängerfreundlichkeit 66/100
WebAssembly/binaryen#9245 · 2 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
Alle Issues in WebAssembly/binaryen
Ähnliche Issues
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
rescript-lang/rescript#8996 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 83/100
Maintainer antworten meist innerhalb von 1 Tag
-
bug P2
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 74/100
Maintainer antworten meist innerhalb von 1 Tag
-
duplicate
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
numba/numba#10939 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
Quantinuum/tket#2255 ·
Maintainer antworten meist innerhalb von 1 Tag