diff options
author | Thomas Lively <tlively@google.com> | 2024-04-17 13:49:21 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-17 13:49:21 -0700 |
commit | 4a84a4b047021dfefd59d5d6dfd53e55c99f6d8d (patch) | |
tree | a52032a27348c7b39778f032d5809d024bfe1fff | |
parent | d8dc55ceafff2c3b92d7d3e6434d56d346f8913b (diff) | |
download | binaryen-4a84a4b047021dfefd59d5d6dfd53e55c99f6d8d.tar.gz binaryen-4a84a4b047021dfefd59d5d6dfd53e55c99f6d8d.tar.bz2 binaryen-4a84a4b047021dfefd59d5d6dfd53e55c99f6d8d.zip |
[Parser] Preserve try labels (#6505)
In the standard text format, try scopes can be targeted by both normal branches
and delegates, but in Binaryen IR we only allow them to be targeted by
delegates, so we have to translate branches to try scopes into branches to
wrapper blocks instead. These wrapper blocks must have different names than the
try expressions they wrap, so we actually need to track two label names for try
expressions: one for delegates and another for normal branches.
We previously tried to avoid this complexity by tracking only the branch label
and computing the delegate label from the branch label as necessary, but that
produced unnecessary wrapper blocks and ugly label names that did not appear in
the source.
To produce better IR and minimize the diff when switching to the new text
parser, bite the bullet and track the delegate and branch label names
separately. This eliminates unnecessary wrapper blocks and keeps try names the
same as in the wat source where possible.
-rw-r--r-- | src/wasm-ir-builder.h | 23 | ||||
-rw-r--r-- | src/wasm/wasm-ir-builder.cpp | 63 | ||||
-rw-r--r-- | test/lit/wat-kitchen-sink.wast | 246 |
3 files changed, 148 insertions, 184 deletions
diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 5803f0c61..addbd1a30 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -298,6 +298,10 @@ private: // The branch label name for this scope. Always fresh, never shadowed. Name label; + // For Try/Catch/CatchAll scopes, we need to separately track a label used + // for branches, since the normal label is only used for delegates. + Name branchLabel; + bool labelUsed = false; std::vector<Expression*> exprStack; @@ -308,6 +312,8 @@ private: ScopeCtx() : scope(NoScope{}) {} ScopeCtx(Scope scope) : scope(scope) {} ScopeCtx(Scope scope, Name label) : scope(scope), label(label) {} + ScopeCtx(Scope scope, Name label, Name branchLabel) + : scope(scope), label(label), branchLabel(branchLabel) {} static ScopeCtx makeFunc(Function* func) { return ScopeCtx(FuncScope{func}); @@ -325,11 +331,13 @@ private: static ScopeCtx makeTry(Try* tryy, Name originalLabel = {}) { return ScopeCtx(TryScope{tryy, originalLabel}); } - static ScopeCtx makeCatch(Try* tryy, Name originalLabel, Name label) { - return ScopeCtx(CatchScope{tryy, originalLabel}, label); + static ScopeCtx + makeCatch(Try* tryy, Name originalLabel, Name label, Name branchLabel) { + return ScopeCtx(CatchScope{tryy, originalLabel}, label, branchLabel); } - static ScopeCtx makeCatchAll(Try* tryy, Name originalLabel, Name label) { - return ScopeCtx(CatchAllScope{tryy, originalLabel}, label); + static ScopeCtx + makeCatchAll(Try* tryy, Name originalLabel, Name label, Name branchLabel) { + return ScopeCtx(CatchAllScope{tryy, originalLabel}, label, branchLabel); } static ScopeCtx makeTryTable(TryTable* trytable, Name originalLabel = {}) { return ScopeCtx(TryTableScope{trytable, originalLabel}); @@ -507,8 +515,11 @@ private: // `block`, but otherwise we will have to allocate a new block. Result<Expression*> finishScope(Block* block = nullptr); - [[nodiscard]] Result<Name> getLabelName(Index label); - [[nodiscard]] Result<Name> getDelegateLabelName(Index label); + [[nodiscard]] Result<Name> getLabelName(Index label, + bool forDelegate = false); + [[nodiscard]] Result<Name> getDelegateLabelName(Index label) { + return getLabelName(label, true); + } [[nodiscard]] Result<Index> addScratchLocal(Type); struct HoistedVal { diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 78ce07f8d..ccd2ee77a 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -235,6 +235,10 @@ void IRBuilder::dump() { std::cerr << " (label: " << scope.label << ")"; } + if (scope.branchLabel) { + std::cerr << " (branch label: " << scope.branchLabel << ")"; + } + if (scope.unreachable) { std::cerr << " (unreachable)"; } @@ -703,9 +707,6 @@ Result<> IRBuilder::visitLoopStart(Loop* loop) { Result<> IRBuilder::visitTryStart(Try* tryy, Name label) { applyDebugLoc(tryy); - // The delegate label will be regenerated if we need it. See - // `getDelegateLabelName` for details. - tryy->name = Name(); pushScope(ScopeCtx::makeTry(tryy, label)); return Ok{}; } @@ -829,6 +830,7 @@ Result<> IRBuilder::visitCatch(Name tag) { } auto originalLabel = scope.getOriginalLabel(); auto label = scope.label; + auto branchLabel = scope.branchLabel; auto expr = finishScope(); CHECK_ERR(expr); if (wasTry) { @@ -837,7 +839,7 @@ Result<> IRBuilder::visitCatch(Name tag) { tryy->catchBodies.push_back(*expr); } tryy->catchTags.push_back(tag); - pushScope(ScopeCtx::makeCatch(tryy, originalLabel, label)); + pushScope(ScopeCtx::makeCatch(tryy, originalLabel, label, branchLabel)); // Push a pop for the exception payload. auto params = wasm.getTag(tag)->sig.params; if (params != Type::none) { @@ -859,6 +861,7 @@ Result<> IRBuilder::visitCatchAll() { } auto originalLabel = scope.getOriginalLabel(); auto label = scope.label; + auto branchLabel = scope.branchLabel; auto expr = finishScope(); CHECK_ERR(expr); if (wasTry) { @@ -866,37 +869,10 @@ Result<> IRBuilder::visitCatchAll() { } else { tryy->catchBodies.push_back(*expr); } - pushScope(ScopeCtx::makeCatchAll(tryy, originalLabel, label)); + pushScope(ScopeCtx::makeCatchAll(tryy, originalLabel, label, branchLabel)); return Ok{}; } -Result<Name> IRBuilder::getDelegateLabelName(Index label) { - if (label >= scopeStack.size()) { - return Err{"invalid label: " + std::to_string(label)}; - } - auto& scope = scopeStack[scopeStack.size() - label - 1]; - auto* delegateTry = scope.getTry(); - if (!delegateTry) { - delegateTry = scope.getCatch(); - } - if (!delegateTry) { - delegateTry = scope.getCatchAll(); - } - if (!delegateTry) { - return Err{"expected try scope at label " + std::to_string(label)}; - } - // Only delegate and rethrow can reference the try name in Binaryen IR, so - // trys might need two labels: one for delegate/rethrow and one for all - // other control flow. These labels must be different to satisfy the - // Binaryen validator. To keep this complexity contained within the - // handling of trys and delegates, pretend there is just the single normal - // label and add a prefix to it to generate the delegate label. - auto delegateName = - Name(std::string("__delegate__") + getLabelName(label)->toString()); - delegateTry->name = delegateName; - return delegateName; -} - Result<> IRBuilder::visitDelegate(Index label) { auto& scope = getScope(); auto* tryy = scope.getTry(); @@ -946,8 +922,10 @@ Result<> IRBuilder::visitEnd() { // type of the scope expression. auto originalScopeType = scope.getResultType(); auto maybeWrapForLabel = [&](Expression* curr) -> Expression* { - if (scope.label) { - return builder.makeBlock(scope.label, + bool isTry = scope.getTry() || scope.getCatch() || scope.getCatchAll(); + auto& label = isTry ? scope.branchLabel : scope.label; + if (label) { + return builder.makeBlock(label, {curr}, scope.labelUsed ? originalScopeType : scope.getResultType()); @@ -981,11 +959,13 @@ Result<> IRBuilder::visitEnd() { push(maybeWrapForLabel(iff)); } else if (auto* tryy = scope.getTry()) { tryy->body = *expr; + tryy->name = scope.label; tryy->finalize(tryy->type); push(maybeWrapForLabel(tryy)); } else if (Try * tryy; (tryy = scope.getCatch()) || (tryy = scope.getCatchAll())) { tryy->catchBodies.push_back(*expr); + tryy->name = scope.label; tryy->finalize(tryy->type); push(maybeWrapForLabel(tryy)); } else if (auto* trytable = scope.getTryTable()) { @@ -1029,10 +1009,17 @@ Result<Index> IRBuilder::getLabelIndex(Name label, bool inDelegate) { return index; } -Result<Name> IRBuilder::getLabelName(Index label) { +Result<Name> IRBuilder::getLabelName(Index label, bool forDelegate) { auto scope = getScope(label); CHECK_ERR(scope); - auto& scopeLabel = (*scope)->label; + + // For normal branches to try blocks, we need to use the secondary label. + bool useTryBranchLabel = + !forDelegate && + ((*scope)->getTry() || (*scope)->getCatch() || (*scope)->getCatchAll()); + auto& scopeLabel = + useTryBranchLabel ? (*scope)->branchLabel : (*scope)->label; + if (!scopeLabel) { // The scope does not already have a name, so we need to create one. if ((*scope)->getBlock()) { @@ -1041,7 +1028,9 @@ Result<Name> IRBuilder::getLabelName(Index label) { scopeLabel = makeFresh("label"); } } - (*scope)->labelUsed = true; + if (!forDelegate) { + (*scope)->labelUsed = true; + } return scopeLabel; } diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast index c52e50d7e..3c4c56ba6 100644 --- a/test/lit/wat-kitchen-sink.wast +++ b/test/lit/wat-kitchen-sink.wast @@ -2025,15 +2025,13 @@ ) ;; CHECK: (func $try-delegate-nested-try-direct-index (type $void) - ;; CHECK-NEXT: (block $label - ;; CHECK-NEXT: (try $__delegate__label - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (delegate $__delegate__label) + ;; CHECK-NEXT: (try $label + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (delegate $label) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2048,15 +2046,13 @@ ) ;; CHECK: (func $try-delegate-nested-try-direct-name (type $void) - ;; CHECK-NEXT: (block $l - ;; CHECK-NEXT: (try $__delegate__l - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (delegate $__delegate__l) + ;; CHECK-NEXT: (try $l + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (delegate $l) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2071,15 +2067,13 @@ ) ;; CHECK: (func $try-delegate-nested-try-indirect-index (type $void) - ;; CHECK-NEXT: (block $label - ;; CHECK-NEXT: (try $__delegate__label - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (delegate $__delegate__label) + ;; CHECK-NEXT: (try $label + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (delegate $label) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2094,16 +2088,14 @@ ) ;; CHECK: (func $try-delegate-nested-try-indirect-name (type $void) - ;; CHECK-NEXT: (block $label - ;; CHECK-NEXT: (try $__delegate__label - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (block $l - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (delegate $__delegate__label) + ;; CHECK-NEXT: (try $label + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (block $l + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (delegate $label) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2119,18 +2111,14 @@ ) ;; CHECK: (func $try-delegate-nested-try-shadowing (type $void) - ;; CHECK-NEXT: (block $l - ;; CHECK-NEXT: (try $__delegate__l - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (block $l0 - ;; CHECK-NEXT: (block $l1 - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (delegate $__delegate__l) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try $l + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (block $l0 + ;; CHECK-NEXT: (try $l1 + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (delegate $l) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2146,24 +2134,18 @@ ) ;; CHECK: (func $try-delegate-nested-catch-shadowing (type $void) - ;; CHECK-NEXT: (block $l - ;; CHECK-NEXT: (try $__delegate__l - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (block $l0 - ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (try $l + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (try $l0 + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (try $l1 ;; CHECK-NEXT: (do ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $empty - ;; CHECK-NEXT: (block $l1 - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (delegate $__delegate__l) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (delegate $l) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2182,24 +2164,18 @@ ) ;; CHECK: (func $try-delegate-nested-catch_all-shadowing (type $void) - ;; CHECK-NEXT: (block $l - ;; CHECK-NEXT: (try $__delegate__l - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (block $l0 - ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (try $l + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (try $l0 + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (try $l1 ;; CHECK-NEXT: (do ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (block $l1 - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (delegate $__delegate__l) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (delegate $l) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2243,16 +2219,16 @@ ) ;; CHECK: (func $try-br-name (type $void) - ;; CHECK-NEXT: (block $l - ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (block $label + ;; CHECK-NEXT: (try $l ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (br $l) + ;; CHECK-NEXT: (br $label) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty - ;; CHECK-NEXT: (br $l) + ;; CHECK-NEXT: (br $label) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all - ;; CHECK-NEXT: (br $l) + ;; CHECK-NEXT: (br $label) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2324,14 +2300,12 @@ ) ;; CHECK: (func $rethrow (type $void) - ;; CHECK-NEXT: (block $label - ;; CHECK-NEXT: (try $__delegate__label - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $empty - ;; CHECK-NEXT: (rethrow $__delegate__label) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try $label + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (rethrow $label) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2343,14 +2317,12 @@ ) ;; CHECK: (func $rethrow-named (type $void) - ;; CHECK-NEXT: (block $l - ;; CHECK-NEXT: (try $__delegate__l - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $empty - ;; CHECK-NEXT: (rethrow $__delegate__l) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try $l + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (rethrow $l) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2362,19 +2334,17 @@ ) ;; CHECK: (func $rethrow-nested (type $void) - ;; CHECK-NEXT: (block $label - ;; CHECK-NEXT: (try $__delegate__label - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $empty - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $empty - ;; CHECK-NEXT: (rethrow $__delegate__label) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try $label + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (rethrow $label) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2393,19 +2363,17 @@ ) ;; CHECK: (func $rethrow-nested-named (type $void) - ;; CHECK-NEXT: (block $l - ;; CHECK-NEXT: (try $__delegate__l - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $empty - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $empty - ;; CHECK-NEXT: (rethrow $__delegate__l) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try $l + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (rethrow $l) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2424,16 +2392,14 @@ ) ;; CHECK: (func $rethrow-try-nested (type $void) - ;; CHECK-NEXT: (block $label - ;; CHECK-NEXT: (try $__delegate__label - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $empty - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (rethrow $__delegate__label) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try $label + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (rethrow $label) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2453,16 +2419,14 @@ ) ;; CHECK: (func $rethrow-try-nested-named (type $void) - ;; CHECK-NEXT: (block $l - ;; CHECK-NEXT: (try $__delegate__l - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $empty - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (rethrow $__delegate__l) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try $l + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (rethrow $l) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) |