Index: docs/ExceptionHandling.rst =================================================================== --- docs/ExceptionHandling.rst +++ docs/ExceptionHandling.rst @@ -775,3 +775,44 @@ The "inner" ``catchswitch`` consumes ``%1`` which is produced by the outer catchswitch. + +.. _wineh-constraints: + +Funclet transitions +----------------------- + +The EH tables for personalities that use funclets make implicit use of the +funclet nesting relationship to encode unwind destinations, and so are +constrained in the set of funclet transitions they can represent. The related +LLVM IR instructions accordingly have constraints that ensure encodability of +the EH edges in the flow graph. + +A ``catchswitch``, ``catchpad``, or ``cleanuppad`` is said to be "entered" +when it executes. It may subsequently be "exited" by any of the following +means: + +* A ``catchswitch`` is immediately exited when none of its constituent + ``catchpad``\ s are appropriate for the in-flight exception and it unwinds + to its unwind destination or the caller. +* A ``catchpad`` and its parent ``catchswitch`` are both exited when a + ``catchret`` from the ``catchpad`` is executed. +* A ``cleanuppad`` is exited when a ``cleanupret`` from it is executed. +* Any of these pads is exited when control unwinds to the function's caller + (by a ``call``, ``cleanupret``, or ``catchswitch``). +* Any of these pads is exited when an unwind edge (from an ``invoke``, + ``cleanupret``, or ``catchswitch``) unwinds to a destination pad that is not + a descendant of the given pad. + +Note that the ``ret`` instruction is *not* a valid way to exit a funclet pad; +it is undefined behavior to execute a ``ret`` when a pad has been entered but +not exited. + +A single unwind edge may exit any number of pads (with the restrictions that +the edge from a ``catchswitch`` must exit at least itself, and the edge from +a ``cleanupret`` must exit at least its ``cleanuppad``), and then must enter +exactly one pad, which must be distinct from all the exited pads. The parent +of the pad that an unwind edge enters must be the most-recently-entered +not-yet-exited pad (after exiting from any pads that the unwind edge exits), +or "none" if there is no such pad. This ensures that the stack of executing +funclets at run-time always corresponds to some path in the funclet pad tree +that the parent tokens encode. Index: docs/LangRef.rst =================================================================== --- docs/LangRef.rst +++ docs/LangRef.rst @@ -1579,6 +1579,8 @@ semantically equivalent to composing the caller's deoptimization continuation after the callee's deoptimization continuation. +.. _ob_funclet: + Funclet Operand Bundles ^^^^^^^^^^^^^^^^^^^^^^^ @@ -1588,6 +1590,18 @@ ``"funclet"`` operand bundle attached to a call site and it must have exactly one bundle operand. +If any funclet EH pads have been "entered" but not "exited" (per the +`description in the EH doc\ `_), +it is undefined behavior to execute a ``call`` or ``invoke`` which: + +* does not have a ``"funclet"`` bundle and is not a ``call`` to a nounwind + intrinsic, or +* has a ``"funclet"`` bundle whose operand is not the most-recently-entered + not-yet-exited funclet EH pad. + +Similarly, if no funclet EH pads have been entered-but-not-yet-exited, +executing a ``call`` or ``invoke`` with a ``"funclet"`` bundle is undefined behavior. + .. _moduleasm: Module-Level Inline Assembly @@ -5405,9 +5419,11 @@ this operand may be the token ``none``. The ``default`` argument is the label of another basic block beginning with a -"pad" instruction, one of ``cleanuppad`` or ``catchswitch``. +"pad" instruction, one of ``cleanuppad`` or ``catchswitch``. This unwind edge +must have a legal target with respect to the ``parent`` links, as described in +the `exception handling documentation\ `_. -The ``handlers`` are a list of successor blocks that each begin with a +The ``handlers`` are a nonempty list of successor blocks that each begin with a :ref:`catchpad ` instruction. Semantics: @@ -5481,20 +5497,12 @@ The meaning of the tokens produced and consumed by ``catchpad`` and other "pad" instructions is described in the -`Windows exception handling documentation `. +`Windows exception handling documentation\ `_. -Executing a ``catchpad`` instruction constitutes "entering" that pad. -The pad may then be "exited" in one of three ways: - -1) explicitly via a ``catchret`` that consumes it. Executing such a ``catchret`` - is undefined behavior if any descendant pads have been entered but not yet - exited. -2) implicitly via a call (which unwinds all the way to the current function's caller), - or via a ``catchswitch`` or a ``cleanupret`` that unwinds to caller. -3) implicitly via an unwind edge whose destination EH pad isn't a descendant of - the ``catchpad``. When the ``catchpad`` is exited in this manner, it is - undefined behavior if the destination EH pad has a parent which is not an - ancestor of the ``catchpad`` being exited. +When a ``catchpad`` has been "entered" but not yet "exited" (as +described in the `EH documentation\ `_), +it is undefined behavior to execute a :ref:`call ` or :ref:`invoke ` +that does not carry an appropriate :ref:`"funclet" bundle `. Example: """""""" @@ -5543,11 +5551,10 @@ code to, for example, destroy the active exception. Control then transfers to ``normal``. -The ``token`` argument must be a token produced by a dominating ``catchpad`` -instruction. The ``catchret`` destroys the physical frame established by -``catchpad``, so executing multiple returns on the same token without -re-executing the ``catchpad`` will result in undefined behavior. -See :ref:`catchpad ` for more details. +The ``token`` argument must be a token produced by a ``catchpad`` instruction. +If the specified ``catchpad`` is not the most-recently-entered not-yet-exited +funclet pad (as described in the `EH documentation\ `_), +the ``catchret``'s behavior is undefined. Example: """""""" @@ -5581,7 +5588,15 @@ The '``cleanupret``' instruction requires one argument, which indicates which ``cleanuppad`` it exits, and must be a :ref:`cleanuppad `. -It also has an optional successor, ``continue``. +If the specified ``cleanuppad`` is not the most-recently-entered not-yet-exited +funclet pad (as described in the `EH documentation\ `_), +the ``cleanupret``'s behavior is undefined. + +The '``cleanupret``' instruction also has an optional successor, ``continue``, +which must be the label of another basic block beginning with a +"pad" instruction, one of ``cleanuppad`` or ``catchswitch``. This unwind edge +must have a legal target with respect to the ``parent`` links, as described in +the `exception handling documentation\ `_. Semantics: """""""""" @@ -5591,13 +5606,6 @@ :ref:`cleanuppad ` it transferred control to has ended. It transfers control to ``continue`` or unwinds out of the function. -The unwind destination ``continue``, if present, must be an EH pad -whose parent is either ``none`` or an ancestor of the ``cleanuppad`` -being returned from. This constitutes an exceptional exit from all -ancestors of the completed ``cleanuppad``, up to but not including -the parent of ``continue``. -See :ref:`cleanuppad ` for more details. - Example: """""""" @@ -8644,18 +8652,10 @@ - A basic block that is not a cleanup block may not include a '``cleanuppad``' instruction. -Executing a ``cleanuppad`` instruction constitutes "entering" that pad. -The pad may then be "exited" in one of three ways: - -1) explicitly via a ``cleanupret`` that consumes it. Executing such a ``cleanupret`` - is undefined behavior if any descendant pads have been entered but not yet - exited. -2) implicitly via a call (which unwinds all the way to the current function's caller), - or via a ``catchswitch`` or a ``cleanupret`` that unwinds to caller. -3) implicitly via an unwind edge whose destination EH pad isn't a descendant of - the ``cleanuppad``. When the ``cleanuppad`` is exited in this manner, it is - undefined behavior if the destination EH pad has a parent which is not an - ancestor of the ``cleanuppad`` being exited. +When a ``cleanuppad`` has been "entered" but not yet "exited" (as +described in the `EH documentation\ `_), +it is undefined behavior to execute a :ref:`call ` or :ref:`invoke ` +that does not carry an appropriate :ref:`"funclet" bundle `. It is undefined behavior for the ``cleanuppad`` to exit via an unwind edge which does not transitively unwind to the same destination as a constituent Index: lib/IR/Verifier.cpp =================================================================== --- lib/IR/Verifier.cpp +++ lib/IR/Verifier.cpp @@ -2895,6 +2895,13 @@ visitInstruction(IVI); } +static Value *getParentPad(Value *EHPad) { + if (auto *FPI = dyn_cast(EHPad)) + return FPI->getParentPad(); + + return cast(EHPad)->getParentPad(); +} + void Verifier::visitEHPadPredecessors(Instruction &I) { assert(I.isEHPad()); @@ -2925,14 +2932,40 @@ return; } + // Verify that each pred has a legal terminator with a legal to/from EH + // pad relationship. + Instruction *ToPad = BB->getFirstNonPHI(); + Value *ToPadParent = getParentPad(ToPad); for (BasicBlock *PredBB : predecessors(BB)) { TerminatorInst *TI = PredBB->getTerminator(); + Value *FromPad; if (auto *II = dyn_cast(TI)) { Assert(II->getUnwindDest() == BB && II->getNormalDest() != BB, "EH pad must be jumped to via an unwind edge", &I, II); - } else if (!isa(TI) && !isa(TI)) { + if (auto Bundle = II->getOperandBundle(LLVMContext::OB_funclet)) + FromPad = Bundle->Inputs[0]; + else + FromPad = ConstantTokenNone::get(II->getContext()); + } else if (auto *CRI = dyn_cast(TI)) { + FromPad = CRI->getCleanupPad(); + Assert(FromPad != ToPadParent, "A cleanupret must exit its cleanup", CRI); + } else if (auto *CSI = dyn_cast(TI)) { + FromPad = CSI->getParentPad(); + } else { Assert(false, "EH pad must be jumped to via an unwind edge", &I, TI); } + + // The edge may exit from zero or more nested pads. + for (;; FromPad = getParentPad(FromPad)) { + Assert(FromPad != ToPad, + "EH pad cannot handle exceptions raised within it", FromPad, TI); + if (FromPad == ToPadParent) { + // This is a legal unwind edge. + break; + } + Assert(!isa(FromPad), + "A single unwind edge may only enter one EH pad", TI); + } } } Index: test/Bitcode/compatibility.ll =================================================================== --- test/Bitcode/compatibility.ll +++ test/Bitcode/compatibility.ll @@ -885,7 +885,8 @@ ; CHECK-NEXT: br label %body body: - invoke void @f.ccc() to label %continue unwind label %terminate.inner + invoke void @f.ccc() [ "funclet"(token %catch) ] + to label %continue unwind label %terminate.inner catchret from %catch to label %return ; CHECK: catchret from %catch to label %return Index: test/Feature/exception.ll =================================================================== --- test/Feature/exception.ll +++ test/Feature/exception.ll @@ -43,7 +43,7 @@ invoke void @_Z3quxv() optsize to label %exit unwind label %pad cleanup: - cleanupret from %cp unwind label %pad + cleanupret from %cp unwind to caller pad: %cp = cleanuppad within none [] br label %cleanup @@ -57,7 +57,7 @@ invoke void @_Z3quxv() optsize to label %exit unwind label %pad cleanup: - cleanupret from %0 unwind label %pad + cleanupret from %0 unwind to caller pad: %0 = cleanuppad within none [] br label %cleanup Index: test/Verifier/invalid-eh.ll =================================================================== --- test/Verifier/invalid-eh.ll +++ test/Verifier/invalid-eh.ll @@ -6,6 +6,10 @@ ; RUN: sed -e s/.T6:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK6 %s ; RUN: sed -e s/.T7:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK7 %s ; RUN: sed -e s/.T8:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK8 %s +; RUN: sed -e s/.T9:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK9 %s +; RUN: sed -e s/.T10:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK10 %s +; RUN: sed -e s/.T11:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK11 %s +; RUN: sed -e s/.T12:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK12 %s declare void @g() @@ -96,3 +100,72 @@ ;T8: %cs1 = catchswitch within none [ label %switch1 ] unwind to caller ;T8: ; CHECK8: CatchSwitchInst handlers must be catchpads ;T8: } + +;T9: define void @f() personality void ()* @g { +;T9: entry: +;T9: ret void +;T9: cleanup: +;T9: %cp = cleanuppad within none [] +;T9: invoke void @g() [ "funclet"(token %cp) ] +;T9: to label %exit unwind label %cleanup +;T9: ; CHECK9: EH pad cannot handle exceptions raised within it +;T9: ; CHECK9-NEXT: %cp = cleanuppad within none [] +;T9: ; CHECK9-NEXT: invoke void @g() [ "funclet"(token %cp) ] +;T9: exit: +;T9: ret void +;T9: } + +;T10: define void @f() personality void ()* @g { +;T10: entry: +;T10: ret void +;T10: cleanup1: +;T10: %cp1 = cleanuppad within none [] +;T10: unreachable +;T10: switch: +;T10: %cs = catchswitch within %cp1 [label %catch] unwind to caller +;T10: catch: +;T10: %catchp1 = catchpad within %cs [i32 1] +;T10: unreachable +;T10: cleanup2: +;T10: %cp2 = cleanuppad within %catchp1 [] +;T10: unreachable +;T10: cleanup3: +;T10: %cp3 = cleanuppad within %cp2 [] +;T10: cleanupret from %cp3 unwind label %switch +;T10: ; CHECK10: EH pad cannot handle exceptions raised within it +;T10: ; CHECK10-NEXT: %cs = catchswitch within %cp1 [label %catch] unwind to caller +;T10: ; CHECK10-NEXT: cleanupret from %cp3 unwind label %switch +;T10: } + +;T11: define void @f() personality void ()* @g { +;T11: entry: +;T11: ret void +;T11: cleanup1: +;T11: %cp1 = cleanuppad within none [] +;T11: unreachable +;T11: cleanup2: +;T11: %cp2 = cleanuppad within %cp1 [] +;T11: unreachable +;T11: switch: +;T11: %cs = catchswitch within none [label %catch] unwind label %cleanup2 +;T11: ; CHECK11: A single unwind edge may only enter one EH pad +;T11: ; CHECK11-NEXT: %cs = catchswitch within none [label %catch] unwind label %cleanup2 +;T11: catch: +;T11: catchpad within %cs [i32 1] +;T11: unreachable +;T11: } + +;T12: define void @f() personality void ()* @g { +;T12: entry: +;T12: ret void +;T12: cleanup: +;T12: %cp = cleanuppad within none [] +;T12: cleanupret from %cp unwind label %switch +;T12: ; CHECK12: A cleanupret must exit its cleanup +;T12: ; CHECK12-NEXT: cleanupret from %cp unwind label %switch +;T12: switch: +;T12: %cs = catchswitch within %cp [label %catch] unwind to caller +;T12: catch: +;T12: catchpad within %cs [i32 1] +;T12: unreachable +;T12: }