Previously we assumed `rethrow`'s argument was always 0, but it turned
out `rethrow` follows the same rule with `br` or `delegate`:
https://github.com/WebAssembly/exception-handling/pull/137
https://github.com/WebAssembly/exception-handling/issues/146#issuecomment-
777349038
Currently `rethrow`s generated by our backend always rethrow the
exception caught by the innermost enclosing catch, so this adds a
function to compute that and replaces `rethrow`'s argument with its
computed result.
This also renames `EHPadStack` in `InstPrinter` to `TryStack`, because
in CFGStackify we use `EHPadStack` to mean the range between
`catch`~`end`, while in `InstPrinter` we used it to mean the range
between `try`~`catch`, so choosing different names would look clearer.
Doesn't contain any functional changes in `InstPrinter`.
Reviewed By: dschuff
Differential Revision: https://reviews.llvm.org/D96595
case WebAssembly::TRY:
case WebAssembly::TRY_S:
ControlFlowStack.push_back(std::make_pair(ControlFlowCounter, false));
- EHPadStack.push_back(ControlFlowCounter++);
+ TryStack.push_back(ControlFlowCounter++);
EHInstStack.push_back(TRY);
return;
} else if (EHInstStack.back() == CATCH_ALL) {
printAnnotation(OS, "catch/catch_all cannot occur after catch_all");
} else if (EHInstStack.back() == TRY) {
- if (EHPadStack.empty()) {
+ if (TryStack.empty()) {
printAnnotation(OS, "try-catch mismatch!");
} else {
- printAnnotation(OS,
- "catch" + utostr(EHPadStack.pop_back_val()) + ':');
+ printAnnotation(OS, "catch" + utostr(TryStack.pop_back_val()) + ':');
}
EHInstStack.pop_back();
if (Opc == WebAssembly::CATCH || Opc == WebAssembly::CATCH_S) {
case WebAssembly::RETHROW_S:
// 'rethrow' rethrows to the nearest enclosing catch scope, if any. If
// there's no enclosing catch scope, it throws up to the caller.
- if (EHPadStack.empty()) {
+ if (TryStack.empty()) {
printAnnotation(OS, "to caller");
} else {
- printAnnotation(OS, "down to catch" + utostr(EHPadStack.back()));
+ printAnnotation(OS, "down to catch" + utostr(TryStack.back()));
}
return;
case WebAssembly::DELEGATE:
case WebAssembly::DELEGATE_S:
- if (ControlFlowStack.empty() || EHPadStack.empty() ||
- EHInstStack.empty()) {
+ if (ControlFlowStack.empty() || TryStack.empty() || EHInstStack.empty()) {
printAnnotation(OS, "try-delegate mismatch!");
} else {
// 'delegate' is
// 1. A marker for the end of block label
// 2. A destination for throwing instructions
// 3. An instruction that itself rethrows to another 'catch'
- assert(ControlFlowStack.back().first == EHPadStack.back());
+ assert(ControlFlowStack.back().first == TryStack.back());
std::string Label = "label/catch" +
utostr(ControlFlowStack.pop_back_val().first) +
": ";
- EHPadStack.pop_back();
+ TryStack.pop_back();
EHInstStack.pop_back();
uint64_t Depth = MI->getOperand(0).getImm();
if (Depth >= ControlFlowStack.size()) {
class WebAssemblyInstPrinter final : public MCInstPrinter {
uint64_t ControlFlowCounter = 0;
SmallVector<std::pair<uint64_t, bool>, 4> ControlFlowStack;
- SmallVector<uint64_t, 4> EHPadStack;
+ SmallVector<uint64_t, 4> TryStack;
enum EHInstKind { TRY, CATCH, CATCH_ALL };
SmallVector<EHInstKind, 4> EHInstStack;
const MachineBasicBlock *MBB);
unsigned getDelegateDepth(const SmallVectorImpl<EndMarkerInfo> &Stack,
const MachineBasicBlock *MBB);
+ unsigned
+ getRethrowDepth(const SmallVectorImpl<EndMarkerInfo> &Stack,
+ const SmallVectorImpl<const MachineBasicBlock *> &EHPadStack);
void rewriteDepthImmediates(MachineFunction &MF);
void fixEndsAtEndOfFunction(MachineFunction &MF);
void cleanupFunctionData(MachineFunction &MF);
return Depth;
}
+unsigned WebAssemblyCFGStackify::getRethrowDepth(
+ const SmallVectorImpl<EndMarkerInfo> &Stack,
+ const SmallVectorImpl<const MachineBasicBlock *> &EHPadStack) {
+ unsigned Depth = 0;
+ // In our current implementation, rethrows always rethrow the exception caught
+ // by the innermost enclosing catch. This means while traversing Stack in the
+ // reverse direction, when we encounter END_TRY, we should check if the
+ // END_TRY corresponds to the current innermost EH pad. For example:
+ // try
+ // ...
+ // catch ;; (a)
+ // try
+ // rethrow 1 ;; (b)
+ // catch ;; (c)
+ // rethrow 0 ;; (d)
+ // end ;; (e)
+ // end ;; (f)
+ //
+ // When we are at 'rethrow' (d), while reversely traversing Stack the first
+ // 'end' we encounter is the 'end' (e), which corresponds to the 'catch' (c).
+ // And 'rethrow' (d) rethrows the exception caught by 'catch' (c), so we stop
+ // there and the depth should be 0. But when we are at 'rethrow' (b), it
+ // rethrows the exception caught by 'catch' (a), so when traversing Stack
+ // reversely, we should skip the 'end' (e) and choose 'end' (f), which
+ // corresponds to 'catch' (a).
+ for (auto X : reverse(Stack)) {
+ const MachineInstr *End = X.second;
+ if (End->getOpcode() == WebAssembly::END_TRY) {
+ auto *EHPad = TryToEHPad[EndToBegin[End]];
+ if (EHPadStack.back() == EHPad)
+ break;
+ }
+ ++Depth;
+ }
+ assert(Depth < Stack.size() && "Rethrow destination should be in scope");
+ return Depth;
+}
+
void WebAssemblyCFGStackify::rewriteDepthImmediates(MachineFunction &MF) {
// Now rewrite references to basic blocks to be depth immediates.
SmallVector<EndMarkerInfo, 8> Stack;
+ SmallVector<const MachineBasicBlock *, 8> EHPadStack;
for (auto &MBB : reverse(MF)) {
for (auto I = MBB.rbegin(), E = MBB.rend(); I != E; ++I) {
MachineInstr &MI = *I;
Stack.push_back(std::make_pair(&MBB, &MI));
break;
- case WebAssembly::END_TRY:
+ case WebAssembly::END_TRY: {
// We handle DELEGATE in the default level, because DELEGATE has
// immediate operands to rewrite.
Stack.push_back(std::make_pair(&MBB, &MI));
+ auto *EHPad = TryToEHPad[EndToBegin[&MI]];
+ EHPadStack.push_back(EHPad);
break;
+ }
case WebAssembly::END_LOOP:
Stack.push_back(std::make_pair(EndToBegin[&MI]->getParent(), &MI));
break;
+ case WebAssembly::CATCH:
+ case WebAssembly::CATCH_ALL:
+ EHPadStack.pop_back();
+ break;
+
+ case WebAssembly::RETHROW:
+ MI.getOperand(0).setImm(getRethrowDepth(Stack, EHPadStack));
+ break;
+
default:
if (MI.isTerminator()) {
// Rewrite MBB operands to be depth immediates.
"throw \t$tag", "throw \t$tag", 0x08>;
defm RETHROW : NRI<(outs), (ins i32imm:$depth), [], "rethrow \t$depth", 0x09>;
} // isTerminator = 1, hasCtrlDep = 1, isBarrier = 1
-// For C++ support, we only rethrow the latest exception, thus always setting
-// the depth to 0.
+// The depth argument will be computed in CFGStackify. We set it to 0 here for
+// now.
def : Pat<(int_wasm_rethrow), (RETHROW 0)>;
// Region within which an exception is caught: try / end_try
; CHECK: rethrow 0 # down to catch[[C0:[0-9]+]]
; CHECK: end_try
; CHECK: end_block # label[[L2]]:
-; CHECK: rethrow 0 # down to catch[[C0]]
+; CHECK: rethrow 1 # down to catch[[C0]]
; CHECK: catch_all # catch[[C0]]:
; CHECK: call __cxa_end_catch
; CHECK: rethrow 0 # to caller
; CHECK: br 2 # 2: down to label[[L1]]
; CHECK: end_try
; CHECK: end_block # label[[L0]]:
-; CHECK: rethrow 0 # to caller
+; CHECK: rethrow 1 # to caller
; CHECK: end_block # label[[L1]]:
; CHECK: call __cxa_end_catch
; CHECK: end_try
define void @unreachable_ehpad_test() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
ret void
}
+ define void @rethrow_arg_test() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
+ ret void
+ }
...
---
body: |
bb.0:
; TRY should be before EH_LABEL wrappers of throwing calls
+ ; CHECK: bb.0
; CHECK: TRY
; CHECK-NEXT: EH_LABEL
; CHECK-NEXT: CALL @foo
; predecessors: %bb.0
successors: %bb.2
; CATCH_ALL should be after an EH_LABEL at the beginning of an EH pad
+ ; CHECK: bb.1
; CHECK: EH_LABEL
; CHECK-NEXT: CATCH_ALL
EH_LABEL <mcsymbol .Ltmp2>
- CATCHRET %bb.2, %bb.0, implicit-def dead $arguments
+ CATCHRET %bb.2, %bb.1, implicit-def dead $arguments
bb.2:
; predecessors: %bb.0, %bb.1
RETURN implicit-def dead $arguments
...
+
---
# Unreachable EH pads should be removed by LateEHPrepare.
# CHECK-LABEL: name: unreachable_ehpad_test
bb.1 (landing-pad):
successors: %bb.2
EH_LABEL <mcsymbol .Ltmp2>
- CATCHRET %bb.2, %bb.0, implicit-def dead $arguments
+ CATCHRET %bb.2, %bb.1, implicit-def dead $arguments
; CHECK: bb.2
bb.2:
; predecessors: %bb.0, %bb.1
RETURN implicit-def dead $arguments
...
+
+---
+# CHECK-LABEL: name: rethrow_arg_test
+name: rethrow_arg_test
+liveins:
+ - { reg: '$arguments' }
+body: |
+ bb.0:
+ successors: %bb.1, %bb.4
+ ; CHECK: bb.0
+ ; CHECK: TRY
+ EH_LABEL <mcsymbol .Ltmp0>
+ CALL @foo, implicit-def dead $arguments, implicit $sp32, implicit $sp64
+ EH_LABEL <mcsymbol .Ltmp1>
+ BR %bb.4, implicit-def dead $arguments
+
+ bb.1 (landing-pad):
+ ; predecessors: %bb.0
+ successors: %bb.2
+ ; CHECK: bb.1
+ ; CHECK: CATCH
+ ; CHECK: TRY
+ ; This RETHROW rethrows the exception caught by this BB's CATCH, but after
+ ; CFGStackify a TRY is placed between the CATCH and this RETHROW, so after
+ ; CFGStackify its immediate argument should become not 0, but 1.
+ ; CHECK: RETHROW 1
+ EH_LABEL <mcsymbol .Ltmp2>
+ %0:i32 = CATCH &__cpp_exception, implicit-def dead $arguments
+ RETHROW 0, implicit-def dead $arguments
+
+ bb.2 (landing-pad):
+ ; predecessors: %bb.1
+ successors: %bb.3
+ ; CHECK: bb.2
+ ; CHECK: CATCH
+ ; CHECK: RETHROW 0
+ EH_LABEL <mcsymbol .Ltmp5>
+ %1:i32 = CATCH &__cpp_exception, implicit-def dead $arguments
+ RETHROW 0, implicit-def dead $arguments
+ CATCHRET %bb.3, %bb.2, implicit-def dead $arguments
+
+ bb.3:
+ ; predecessors: %bb.2
+ successors: %bb.4
+ CATCHRET %bb.4, %bb.1, implicit-def dead $arguments
+
+ bb.4:
+ ; predecessors: %bb.0, %bb.3
+ ; CHECK: bb.4
+ ; CHECK: END_TRY
+ ; CHECK: END_TRY
+ RETURN implicit-def dead $arguments
+