From 3d6511f5ef36418c2fb53a49d82f30828344eb47 Mon Sep 17 00:00:00 2001 From: Joseph Tremoulet Date: Tue, 13 Oct 2015 20:18:27 +0000 Subject: [PATCH] [WinEH] Add CoreCLR EH table emission Summary: Emit the handler and clause locations immediately after the standard xdata. Clauses are emitted in the same order and format used to communiate them to the CLR Execution Engine. Add a lit test to verify correct table generation on a small but interesting example function. Reviewers: majnemer, andrew.w.kaylor, rnk Subscribers: pgavlin, AndyAyers, llvm-commits Differential Revision: http://reviews.llvm.org/D13451 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@250219 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/CodeGen/AsmPrinter/WinException.cpp | 280 +++++++++++++++++++++++- lib/CodeGen/AsmPrinter/WinException.h | 5 + test/CodeGen/WinEH/wineh-coreclr.ll | 239 ++++++++++++++++++++ 3 files changed, 521 insertions(+), 3 deletions(-) create mode 100644 test/CodeGen/WinEH/wineh-coreclr.ll diff --git a/lib/CodeGen/AsmPrinter/WinException.cpp b/lib/CodeGen/AsmPrinter/WinException.cpp index bd348f681c5..319320a09e0 100644 --- a/lib/CodeGen/AsmPrinter/WinException.cpp +++ b/lib/CodeGen/AsmPrinter/WinException.cpp @@ -139,6 +139,8 @@ void WinException::endFunction(const MachineFunction *MF) { emitExceptHandlerTable(MF); else if (Per == EHPersonality::MSVC_CXX) emitCXXFrameHandler3Table(MF); + else if (Per == EHPersonality::CoreCLR) + emitCLRExceptionTable(MF); else emitExceptionTable(); @@ -285,6 +287,20 @@ const MCExpr *WinException::getLabelPlusOne(const MCSymbol *Label) { Asm->OutContext); } +const MCExpr *WinException::getOffset(const MCSymbol *OffsetOf, + const MCSymbol *OffsetFrom) { + return MCBinaryExpr::createSub( + MCSymbolRefExpr::create(OffsetOf, Asm->OutContext), + MCSymbolRefExpr::create(OffsetFrom, Asm->OutContext), Asm->OutContext); +} + +const MCExpr *WinException::getOffsetPlusOne(const MCSymbol *OffsetOf, + const MCSymbol *OffsetFrom) { + return MCBinaryExpr::createAdd(getOffset(OffsetOf, OffsetFrom), + MCConstantExpr::create(1, Asm->OutContext), + Asm->OutContext); +} + int WinException::getFrameIndexOffset(int FrameIndex) { const TargetFrameLowering &TFI = *Asm->MF->getSubtarget().getFrameLowering(); unsigned UnusedReg; @@ -509,9 +525,7 @@ void WinException::emitCSpecificHandlerTable(const MachineFunction *MF) { Ctx.createTempSymbol("lsda_begin", /*AlwaysAddSuffix=*/true); MCSymbol *TableEnd = Ctx.createTempSymbol("lsda_end", /*AlwaysAddSuffix=*/true); - const MCExpr *LabelDiff = - MCBinaryExpr::createSub(MCSymbolRefExpr::create(TableEnd, Ctx), - MCSymbolRefExpr::create(TableBegin, Ctx), Ctx); + const MCExpr *LabelDiff = getOffset(TableEnd, TableBegin); const MCExpr *EntrySize = MCConstantExpr::create(16, Ctx); const MCExpr *EntryCount = MCBinaryExpr::createDiv(LabelDiff, EntrySize, Ctx); OS.EmitValue(EntryCount, 4); @@ -856,3 +870,263 @@ void WinException::emitExceptHandlerTable(const MachineFunction *MF) { OS.EmitValue(create32bitRef(ExceptOrFinally), 4); // Except/Finally } } + +static int getRank(WinEHFuncInfo &FuncInfo, int State) { + int Rank = 0; + while (State != -1) { + ++Rank; + State = FuncInfo.ClrEHUnwindMap[State].Parent; + } + return Rank; +} + +static int getAncestor(WinEHFuncInfo &FuncInfo, int Left, int Right) { + int LeftRank = getRank(FuncInfo, Left); + int RightRank = getRank(FuncInfo, Right); + + while (LeftRank < RightRank) { + Right = FuncInfo.ClrEHUnwindMap[Right].Parent; + --RightRank; + } + + while (RightRank < LeftRank) { + Left = FuncInfo.ClrEHUnwindMap[Left].Parent; + --LeftRank; + } + + while (Left != Right) { + Left = FuncInfo.ClrEHUnwindMap[Left].Parent; + Right = FuncInfo.ClrEHUnwindMap[Right].Parent; + } + + return Left; +} + +void WinException::emitCLRExceptionTable(const MachineFunction *MF) { + // CLR EH "states" are really just IDs that identify handlers/funclets; + // states, handlers, and funclets all have 1:1 mappings between them, and a + // handler/funclet's "state" is its index in the ClrEHUnwindMap. + MCStreamer &OS = *Asm->OutStreamer; + const Function *F = MF->getFunction(); + WinEHFuncInfo &FuncInfo = MMI->getWinEHFuncInfo(F); + MCSymbol *FuncBeginSym = Asm->getFunctionBegin(); + MCSymbol *FuncEndSym = Asm->getFunctionEnd(); + + // A ClrClause describes a protected region. + struct ClrClause { + const MCSymbol *StartLabel; // Start of protected region + const MCSymbol *EndLabel; // End of protected region + int State; // Index of handler protecting the protected region + int EnclosingState; // Index of funclet enclosing the protected region + }; + SmallVector Clauses; + + // Build a map from handler MBBs to their corresponding states (i.e. their + // indices in the ClrEHUnwindMap). + int NumStates = FuncInfo.ClrEHUnwindMap.size(); + assert(NumStates > 0 && "Don't need exception table!"); + DenseMap HandlerStates; + for (int State = 0; State < NumStates; ++State) { + MachineBasicBlock *HandlerBlock = + FuncInfo.ClrEHUnwindMap[State].Handler.get(); + HandlerStates[HandlerBlock] = State; + // Use this loop through all handlers to verify our assumption (used in + // the MinEnclosingState computation) that ancestors have lower state + // numbers than their descendants. + assert(FuncInfo.ClrEHUnwindMap[State].Parent < State && + "ill-formed state numbering"); + } + // Map the main function to the NullState. + HandlerStates[MF->begin()] = NullState; + + // Write out a sentinel indicating the end of the standard (Windows) xdata + // and the start of the additional (CLR) info. + OS.EmitIntValue(0xffffffff, 4); + // Write out the number of funclets + OS.EmitIntValue(NumStates, 4); + + // Walk the machine blocks/instrs, computing and emitting a few things: + // 1. Emit a list of the offsets to each handler entry, in lexical order. + // 2. Compute a map (EndSymbolMap) from each funclet to the symbol at its end. + // 3. Compute the list of ClrClauses, in the required order (inner before + // outer, earlier before later; the order by which a forward scan with + // early termination will find the innermost enclosing clause covering + // a given address). + // 4. A map (MinClauseMap) from each handler index to the index of the + // outermost funclet/function which contains a try clause targeting the + // key handler. This will be used to determine IsDuplicate-ness when + // emitting ClrClauses. The NullState value is used to indicate that the + // top-level function contains a try clause targeting the key handler. + // HandlerStack is a stack of (PendingStartLabel, PendingState) pairs for + // try regions we entered before entering the PendingState try but which + // we haven't yet exited. + SmallVector, 4> HandlerStack; + // EndSymbolMap and MinClauseMap are maps described above. + std::unique_ptr EndSymbolMap(new MCSymbol *[NumStates]); + SmallVector MinClauseMap((size_t)NumStates, NumStates); + + // Visit the root function and each funclet. + + for (MachineFunction::const_iterator FuncletStart = MF->begin(), + FuncletEnd = MF->begin(), + End = MF->end(); + FuncletStart != End; FuncletStart = FuncletEnd) { + int FuncletState = HandlerStates[FuncletStart]; + // Find the end of the funclet + MCSymbol *EndSymbol = FuncEndSym; + while (++FuncletEnd != End) { + if (FuncletEnd->isEHFuncletEntry()) { + EndSymbol = getMCSymbolForMBB(Asm, FuncletEnd); + break; + } + } + // Emit the function/funclet end and, if this is a funclet (and not the + // root function), record it in the EndSymbolMap. + OS.EmitValue(getOffset(EndSymbol, FuncBeginSym), 4); + if (FuncletState != NullState) { + // Record the end of the handler. + EndSymbolMap[FuncletState] = EndSymbol; + } + + // Walk the state changes in this function/funclet and compute its clauses. + // Funclets always start in the null state. + const MCSymbol *CurrentStartLabel = nullptr; + int CurrentState = NullState; + assert(HandlerStack.empty()); + for (const auto &StateChange : + InvokeStateChangeIterator::range(FuncInfo, FuncletStart, FuncletEnd)) { + // Close any try regions we're not still under + int AncestorState = + getAncestor(FuncInfo, CurrentState, StateChange.NewState); + while (CurrentState != AncestorState) { + assert(CurrentState != NullState && "Failed to find ancestor!"); + // Close the pending clause + Clauses.push_back({CurrentStartLabel, StateChange.PreviousEndLabel, + CurrentState, FuncletState}); + // Now the parent handler is current + CurrentState = FuncInfo.ClrEHUnwindMap[CurrentState].Parent; + // Pop the new start label from the handler stack if we've exited all + // descendants of the corresponding handler. + if (HandlerStack.back().second == CurrentState) + CurrentStartLabel = HandlerStack.pop_back_val().first; + } + + if (StateChange.NewState != CurrentState) { + // For each clause we're starting, update the MinClauseMap so we can + // know which is the topmost funclet containing a clause targeting + // it. + for (int EnteredState = StateChange.NewState; + EnteredState != CurrentState; + EnteredState = FuncInfo.ClrEHUnwindMap[EnteredState].Parent) { + int &MinEnclosingState = MinClauseMap[EnteredState]; + if (FuncletState < MinEnclosingState) + MinEnclosingState = FuncletState; + } + // Save the previous current start/label on the stack and update to + // the newly-current start/state. + HandlerStack.emplace_back(CurrentStartLabel, CurrentState); + CurrentStartLabel = StateChange.NewStartLabel; + CurrentState = StateChange.NewState; + } + } + assert(HandlerStack.empty()); + } + + // Now emit the clause info, starting with the number of clauses. + OS.EmitIntValue(Clauses.size(), 4); + for (ClrClause &Clause : Clauses) { + // Emit a CORINFO_EH_CLAUSE : + /* + struct CORINFO_EH_CLAUSE + { + CORINFO_EH_CLAUSE_FLAGS Flags; // actually a CorExceptionFlag + DWORD TryOffset; + DWORD TryLength; // actually TryEndOffset + DWORD HandlerOffset; + DWORD HandlerLength; // actually HandlerEndOffset + union + { + DWORD ClassToken; // use for catch clauses + DWORD FilterOffset; // use for filter clauses + }; + }; + + enum CORINFO_EH_CLAUSE_FLAGS + { + CORINFO_EH_CLAUSE_NONE = 0, + CORINFO_EH_CLAUSE_FILTER = 0x0001, // This clause is for a filter + CORINFO_EH_CLAUSE_FINALLY = 0x0002, // This clause is a finally clause + CORINFO_EH_CLAUSE_FAULT = 0x0004, // This clause is a fault clause + }; + typedef enum CorExceptionFlag + { + COR_ILEXCEPTION_CLAUSE_NONE, + COR_ILEXCEPTION_CLAUSE_FILTER = 0x0001, // This is a filter clause + COR_ILEXCEPTION_CLAUSE_FINALLY = 0x0002, // This is a finally clause + COR_ILEXCEPTION_CLAUSE_FAULT = 0x0004, // This is a fault clause + COR_ILEXCEPTION_CLAUSE_DUPLICATED = 0x0008, // duplicated clause. This + // clause was duplicated + // to a funclet which was + // pulled out of line + } CorExceptionFlag; + */ + // Add 1 to the start/end of the EH clause; the IP associated with a + // call when the runtime does its scan is the IP of the next instruction + // (the one to which control will return after the call), so we need + // to add 1 to the end of the clause to cover that offset. We also add + // 1 to the start of the clause to make sure that the ranges reported + // for all clauses are disjoint. Note that we'll need some additional + // logic when machine traps are supported, since in that case the IP + // that the runtime uses is the offset of the faulting instruction + // itself; if such an instruction immediately follows a call but the + // two belong to different clauses, we'll need to insert a nop between + // them so the runtime can distinguish the point to which the call will + // return from the point at which the fault occurs. + + const MCExpr *ClauseBegin = + getOffsetPlusOne(Clause.StartLabel, FuncBeginSym); + const MCExpr *ClauseEnd = getOffsetPlusOne(Clause.EndLabel, FuncBeginSym); + + ClrEHUnwindMapEntry &Entry = FuncInfo.ClrEHUnwindMap[Clause.State]; + MachineBasicBlock *HandlerBlock = Entry.Handler.get(); + MCSymbol *BeginSym = getMCSymbolForMBB(Asm, HandlerBlock); + const MCExpr *HandlerBegin = getOffset(BeginSym, FuncBeginSym); + MCSymbol *EndSym = EndSymbolMap[Clause.State]; + const MCExpr *HandlerEnd = getOffset(EndSym, FuncBeginSym); + + uint32_t Flags = 0; + switch (Entry.HandlerType) { + case ClrHandlerType::Catch: + // Leaving bits 0-2 clear indicates catch. + break; + case ClrHandlerType::Filter: + Flags |= 1; + break; + case ClrHandlerType::Finally: + Flags |= 2; + break; + case ClrHandlerType::Fault: + Flags |= 4; + break; + } + if (Clause.EnclosingState != MinClauseMap[Clause.State]) { + // This is a "duplicate" clause; the handler needs to be entered from a + // frame above the one holding the invoke. + assert(Clause.EnclosingState > MinClauseMap[Clause.State]); + Flags |= 8; + } + OS.EmitIntValue(Flags, 4); + + // Write the clause start/end + OS.EmitValue(ClauseBegin, 4); + OS.EmitValue(ClauseEnd, 4); + + // Write out the handler start/end + OS.EmitValue(HandlerBegin, 4); + OS.EmitValue(HandlerEnd, 4); + + // Write out the type token or filter offset + assert(Entry.HandlerType != ClrHandlerType::Filter && "NYI: filters"); + OS.EmitIntValue(Entry.TypeToken, 4); + } +} diff --git a/lib/CodeGen/AsmPrinter/WinException.h b/lib/CodeGen/AsmPrinter/WinException.h index 06a5fb43849..e553c3ff736 100644 --- a/lib/CodeGen/AsmPrinter/WinException.h +++ b/lib/CodeGen/AsmPrinter/WinException.h @@ -54,6 +54,8 @@ class LLVM_LIBRARY_VISIBILITY WinException : public EHStreamer { /// tables. void emitExceptHandlerTable(const MachineFunction *MF); + void emitCLRExceptionTable(const MachineFunction *MF); + void computeIP2StateTable( const MachineFunction *MF, WinEHFuncInfo &FuncInfo, SmallVectorImpl> &IPToStateTable); @@ -66,6 +68,9 @@ class LLVM_LIBRARY_VISIBILITY WinException : public EHStreamer { const MCExpr *create32bitRef(const MCSymbol *Value); const MCExpr *create32bitRef(const Value *V); const MCExpr *getLabelPlusOne(const MCSymbol *Label); + const MCExpr *getOffset(const MCSymbol *OffsetOf, const MCSymbol *OffsetFrom); + const MCExpr *getOffsetPlusOne(const MCSymbol *OffsetOf, + const MCSymbol *OffsetFrom); /// Gets the offset that we should use in a table for a stack object with the /// given index. For targets using CFI (Win64, etc), this is relative to the diff --git a/test/CodeGen/WinEH/wineh-coreclr.ll b/test/CodeGen/WinEH/wineh-coreclr.ll new file mode 100644 index 00000000000..56675fefae7 --- /dev/null +++ b/test/CodeGen/WinEH/wineh-coreclr.ll @@ -0,0 +1,239 @@ +; RUN: llc -mtriple=x86_64-pc-windows-coreclr < %s | FileCheck %s + +declare void @ProcessCLRException() +declare void @f(i32) + +; Simplified IR for pseudo-C# like the following: +; void test1() { +; try { +; f(1); +; try { +; f(2); +; } catch (type1) { +; f(3); +; } catch (type2) [ +; f(4); +; try { +; f(5); +; } fault { +; f(6); +; } +; } +; } finally { +; f(7); +; } +; f(8); +; } + +; CHECK-LABEL: test1: # @test1 +; CHECK-NEXT: [[L_begin:.*func_begin.*]]: +define void @test1() personality i8* bitcast (void ()* @ProcessCLRException to i8*) { +entry: +; CHECK: # %entry +; CHECK: .seh_endprologue +; CHECK: [[L_before_f1:.+]]: +; CHECK-NEXT: movl $1, %ecx +; CHECK-NEXT: callq f +; CHECK-NEXT: [[L_after_f1:.+]]: + invoke void @f(i32 1) + to label %inner_try unwind label %finally.pad +inner_try: +; CHECK: # %inner_try +; CHECK: [[L_before_f2:.+]]: +; CHECK-NEXT: movl $2, %ecx +; CHECK-NEXT: callq f +; CHECK-NEXT: [[L_after_f2:.+]]: + invoke void @f(i32 2) + to label %finally.clone unwind label %catch1.pad +catch1.pad: +; CHECK: .seh_proc [[L_catch1:[^ ]+]] + %catch1 = catchpad [i32 1] + to label %catch1.body unwind label %catch2.pad +catch1.body: +; CHECK: .seh_endprologue +; CHECK: [[L_before_f3:.+]]: +; CHECK-NEXT: movl $3, %ecx +; CHECK-NEXT: callq f +; CHECK-NEXT: [[L_after_f3:.+]]: + invoke void @f(i32 3) + to label %catch1.ret unwind label %catch.end +catch1.ret: + catchret %catch1 to label %finally.clone +catch2.pad: +; CHECK: .seh_proc [[L_catch2:[^ ]+]] + %catch2 = catchpad [i32 2] + to label %catch2.body unwind label %catch.end +catch2.body: +; CHECK: .seh_endprologue +; CHECK: [[L_before_f4:.+]]: +; CHECK-NEXT: movl $4, %ecx +; CHECK-NEXT: callq f +; CHECK-NEXT: [[L_after_f4:.+]]: + invoke void @f(i32 4) + to label %try_in_catch unwind label %catch.end +try_in_catch: +; CHECK: # %try_in_catch +; CHECK: [[L_before_f5:.+]]: +; CHECK-NEXT: movl $5, %ecx +; CHECK-NEXT: callq f +; CHECK-NEXT: [[L_after_f5:.+]]: + invoke void @f(i32 5) + to label %catch2.ret unwind label %fault.pad +fault.pad: +; CHECK: .seh_proc [[L_fault:[^ ]+]] + %fault = cleanuppad [i32 undef] +; CHECK: .seh_endprologue +; CHECK: [[L_before_f6:.+]]: +; CHECK-NEXT: movl $6, %ecx +; CHECK-NEXT: callq f +; CHECK-NEXT: [[L_after_f6:.+]]: + invoke void @f(i32 6) + to label %fault.ret unwind label %fault.end +fault.ret: + cleanupret %fault unwind label %catch.end +fault.end: + cleanupendpad %fault unwind label %catch.end +catch2.ret: + catchret %catch2 to label %finally.clone +catch.end: + catchendpad unwind label %finally.pad +finally.clone: + call void @f(i32 7) + br label %tail +finally.pad: +; CHECK: .seh_proc [[L_finally:[^ ]+]] + %finally = cleanuppad [] +; CHECK: .seh_endprologue +; CHECK: [[L_before_f7:.+]]: +; CHECK-NEXT: movl $7, %ecx +; CHECK-NEXT: callq f +; CHECK-NEXT: [[L_after_f7:.+]]: + invoke void @f(i32 7) + to label %finally.ret unwind label %finally.end +finally.ret: + cleanupret %finally unwind to caller +finally.end: + cleanupendpad %finally unwind to caller +tail: + call void @f(i32 8) + ret void +; CHECK: [[L_end:.*func_end.*]]: +} + +; Now check for EH table in xdata (following standard xdata) +; CHECK-LABEL: .section .xdata +; standard xdata comes here +; CHECK: .long 4{{$}} +; ^ number of funclets +; CHECK-NEXT: .long [[L_catch1]]-[[L_begin]] +; ^ offset from L_begin to start of 1st funclet +; CHECK-NEXT: .long [[L_catch2]]-[[L_begin]] +; ^ offset from L_begin to start of 2nd funclet +; CHECK-NEXT: .long [[L_fault]]-[[L_begin]] +; ^ offset from L_begin to start of 3rd funclet +; CHECK-NEXT: .long [[L_finally]]-[[L_begin]] +; ^ offset from L_begin to start of 4th funclet +; CHECK-NEXT: .long [[L_end]]-[[L_begin]] +; ^ offset from L_begin to end of last funclet +; CHECK-NEXT: .long 7 +; ^ number of EH clauses +; Clause 1: call f(2) is guarded by catch1 +; CHECK-NEXT: .long 0 +; ^ flags (0 => catch handler) +; CHECK-NEXT: .long ([[L_before_f2]]-[[L_begin]])+1 +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1 +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_catch1]]-[[L_begin]] +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_catch2]]-[[L_begin]] +; ^ offset of end of handler +; CHECK-NEXT: .long 1 +; ^ type token of catch (from catchpad) +; Clause 2: call f(2) is also guarded by catch2 +; CHECK-NEXT: .long 0 +; ^ flags (0 => catch handler) +; CHECK-NEXT: .long ([[L_before_f2]]-[[L_begin]])+1 +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1 +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_catch2]]-[[L_begin]] +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_fault]]-[[L_begin]] +; ^ offset of end of handler +; CHECK-NEXT: .long 2 +; ^ type token of catch (from catchpad) +; Clause 3: calls f(1) and f(2) are guarded by finally +; CHECK-NEXT: .long 2 +; ^ flags (2 => finally handler) +; CHECK-NEXT: .long ([[L_before_f1]]-[[L_begin]])+1 +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1 +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_finally]]-[[L_begin]] +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_end]]-[[L_begin]] +; ^ offset of end of handler +; CHECK-NEXT: .long 0 +; ^ type token slot (null for finally) +; Clause 4: call f(3) is guarded by finally +; This is a "duplicate" because the protected range (f(3)) +; is in funclet catch1 but the finally's immediate parent +; is the main function, not that funclet. +; CHECK-NEXT: .long 10 +; ^ flags (2 => finally handler | 8 => duplicate) +; CHECK-NEXT: .long ([[L_before_f3]]-[[L_begin]])+1 +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f3]]-[[L_begin]])+1 +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_finally]]-[[L_begin]] +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_end]]-[[L_begin]] +; ^ offset of end of handler +; CHECK-NEXT: .long 0 +; ^ type token slot (null for finally) +; Clause 5: call f(5) is guarded by fault +; CHECK-NEXT: .long 4 +; ^ flags (4 => fault handler) +; CHECK-NEXT: .long ([[L_before_f5]]-[[L_begin]])+1 +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f5]]-[[L_begin]])+1 +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_fault]]-[[L_begin]] +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_finally]]-[[L_begin]] +; ^ offset of end of handler +; CHECK-NEXT: .long 0 +; ^ type token slot (null for fault) +; Clause 6: calls f(4) and f(5) are guarded by finally +; This is a "duplicate" because the protected range (f(4)-f(5)) +; is in funclet catch2 but the finally's immediate parent +; is the main function, not that funclet. +; CHECK-NEXT: .long 10 +; ^ flags (2 => finally handler | 8 => duplicate) +; CHECK-NEXT: .long ([[L_before_f4]]-[[L_begin]])+1 +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f5]]-[[L_begin]])+1 +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_finally]]-[[L_begin]] +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_end]]-[[L_begin]] +; ^ offset of end of handler +; CHECK-NEXT: .long 0 +; ^ type token slot (null for finally) +; Clause 7: call f(6) is guarded by finally +; This is a "duplicate" because the protected range (f(3)) +; is in funclet catch1 but the finally's immediate parent +; is the main function, not that funclet. +; CHECK-NEXT: .long 10 +; ^ flags (2 => finally handler | 8 => duplicate) +; CHECK-NEXT: .long ([[L_before_f6]]-[[L_begin]])+1 +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f6]]-[[L_begin]])+1 +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_finally]]-[[L_begin]] +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_end]]-[[L_begin]] +; ^ offset of end of handler +; CHECK-NEXT: .long 0 +; ^ type token slot (null for finally) -- 2.34.1