Index: lib/CodeGen/AsmPrinter/WinException.h =================================================================== --- lib/CodeGen/AsmPrinter/WinException.h +++ lib/CodeGen/AsmPrinter/WinException.h @@ -50,6 +50,8 @@ /// tables. void emitExceptHandlerTable(const MachineFunction *MF); + void emitCLRExceptionTable(const MachineFunction *MF); + void computeIP2StateTable( const MachineFunction *MF, WinEHFuncInfo &FuncInfo, SmallVectorImpl> &IPToStateTable); @@ -62,6 +64,8 @@ const MCExpr *create32bitRef(const MCSymbol *Value); const MCExpr *create32bitRef(const Value *V); const MCExpr *getLabelPlusOne(MCSymbol *Label); + const MCExpr *getOffset(MCSymbol *OffsetOf, MCSymbol *OffsetFrom); + const MCExpr *getOffset(const MCExpr *OffsetOf, MCSymbol *OffsetFrom); public: //===--------------------------------------------------------------------===// Index: lib/CodeGen/AsmPrinter/WinException.cpp =================================================================== --- lib/CodeGen/AsmPrinter/WinException.cpp +++ lib/CodeGen/AsmPrinter/WinException.cpp @@ -142,6 +142,8 @@ emitExceptHandlerTable(MF); else if (Per == EHPersonality::MSVC_CXX) emitCXXFrameHandler3Table(MF); + else if (Per == EHPersonality::CoreCLR) + emitCLRExceptionTable(MF); else emitExceptionTable(); @@ -924,3 +926,312 @@ } } } + +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; +} + +const MCExpr *WinException::getOffset(MCSymbol *OffsetOf, MCSymbol *OffsetFrom) { + return MCBinaryExpr::createSub(create32bitRef(OffsetOf), + create32bitRef(OffsetFrom), Asm->OutContext); +} + +const MCExpr *WinException::getOffset(const MCExpr *OffsetOf, + MCSymbol *OffsetFrom) { + return MCBinaryExpr::createSub(OffsetOf, create32bitRef(OffsetFrom), + Asm->OutContext); +} + +void WinException::emitCLRExceptionTable(const MachineFunction *MF) { + const int NullState = -1; + MCStreamer &OS = *Asm->OutStreamer; + const Function *F = MF->getFunction(); + WinEHFuncInfo &FuncInfo = MMI->getWinEHFuncInfo(F); + MCSymbol *FuncBeginSym = Asm->getFunctionBegin(); + MCSymbol *FuncEndSym = Asm->getFunctionEnd(); + + struct ClrClause { + MCSymbol *StartLabel; // Start of protected regino + MCSymbol *EndLabel; // End of protected region + int State; // State of handler protecting the protected region + int EnclosingState; // State of funclet enclosing the protected region + }; + SmallVector Clauses; + + // Build a map from handler MBBs to their corresponding states + 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; + } + + // Write out a sentinel indicating the end of the xdata proper. + 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 state 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 state to the state 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. + + // PendingStartLabel is the EH label preceding the most recently visited + // invoke. + MCSymbol *PendingStartLabel = nullptr; + // PendingState is the state of the handler of the most recently visited + // invoke. + int PendingState = NullState; + // 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; + // PendingEndLabel is the EH label following the most recently visited + // invoke. + MCSymbol *PendingEndLabel = nullptr; + // EnclosingState is the state corresponding to the function/funclet + // that the current MBB belongs to. + int EnclosingState = NullState; + // VisitingInvoke indicates whether the current instruction is between the + // start and end EH labels for an invoke. + bool VisitingInvoke = false; + // EndSymbolMap and MinClauseMap are maps described above. + std::unique_ptr EndSymbolMap(new MCSymbol *[NumStates]); + SmallVector MinClauseMap((size_t)NumStates, NumStates); + + // Helper to emit any pending clauses up to the specified ancestor state. + auto RewindPendingTo = [&](int AncestorState) { + while (PendingState != AncestorState) { + // Close the pending clause + Clauses.push_back( + {PendingStartLabel, PendingEndLabel, PendingState, EnclosingState}); + // Now the parent handler is pending + PendingState = FuncInfo.ClrEHUnwindMap[PendingState].Parent; + // Pop the new start label from the handler stack if we've exited all + // descendants of the corresponding handler. + if (!HandlerStack.empty() && HandlerStack.back().second == PendingState) + PendingStartLabel = HandlerStack.pop_back_val().first; + } + }; + + for (const auto &MBB : *MF) { + if (MBB.isEHFuncletEntry()) { + // First, close any pending clauses from the last funclet, so that we + // don't try to emit clauses that span funclets. + RewindPendingTo(NullState); + // Emit the offset to the start of this funclet. + MCSymbol *HandlerSymbol = getMCSymbolForMBBOrGV(Asm, &MBB); + OS.EmitValue(getOffset(HandlerSymbol, FuncBeginSym), 4); + if (EnclosingState != NullState) { + // Mark the start of this handler as the end of the prior handler. + EndSymbolMap[EnclosingState] = HandlerSymbol; + } + // Track the new EnclosingState. + EnclosingState = HandlerStates[&MBB]; + } + + for (const auto &MI : MBB) { + MCSymbol *NewStartLabel; + MCSymbol *NewEndLabel; + int NewState; + + if (MI.isEHLabel()) { + MCSymbol *Label = MI.getOperand(0).getMCSymbol(); + if (Label == PendingEndLabel) { + VisitingInvoke = false; + continue; + } + auto StateAndEnd = FuncInfo.InvokeToStateMap.find(Label); + if (StateAndEnd == FuncInfo.InvokeToStateMap.end()) + continue; + // This is the label immediately before an invoke + NewStartLabel = Label; + std::tie(NewState, NewEndLabel) = StateAndEnd->second; + // Ignore this label if the state recorded on it is the null state. + // This happens for invokes that unwind to a chain of endpads that + // terminates with an "unwind to caller" endpad. + if (NewState == NullState) + continue; + VisitingInvoke = true; + } else if (!VisitingInvoke && MI.isCall() && + !callToNoUnwindFunction(&MI)) { + // We don't have a labels handy for this call, but that's ok because + // we don't need them. + NewStartLabel = nullptr; + NewEndLabel = nullptr; + // This is a call which may unwind to the caller, so it is + // implicitly in the toplevel NullState state. + NewState = NullState; + } else { + // Not an interesting instruction for identifying try regions + continue; + } + + // Close any try regions we're not still under + int AncestorState = getAncestor(FuncInfo, PendingState, NewState); + RewindPendingTo(AncestorState); + + if (NewState != PendingState) { + // 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 = NewState; EnteredState != PendingState; + EnteredState = FuncInfo.ClrEHUnwindMap[EnteredState].Parent) { + int &MinEnclosingState = MinClauseMap[EnteredState]; + if (EnclosingState < MinEnclosingState) + MinEnclosingState = EnclosingState; + } + // Save the previous pending start/label on the stack and update to + // the newly-pending start/state. + HandlerStack.emplace_back(PendingStartLabel, PendingState); + PendingStartLabel = NewStartLabel; + PendingState = NewState; + } + + // Extend the pending clause to the new end label. + PendingEndLabel = NewEndLabel; + } + } + + // Also record and emit the offset to the end of the last funclet. + EndSymbolMap[EnclosingState] = FuncEndSym; + OS.EmitValue(getOffset(FuncEndSym, FuncBeginSym), 4); + + // Close any trailing regions + RewindPendingTo(NullState); + 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 : + /* + 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 clause is for a filter + COR_ILEXCEPTION_CLAUSE_FINALLY = 0x0002, // This clause is a finally clause + COR_ILEXCEPTION_CLAUSE_FAULT = 0x0004, // This clause is a fault + COR_ILEXCEPTION_CLAUSE_DUPLICATED = 0x0008, // duplicated clause. This + // clause was duplicated + // to a funclet which was + // pulled out of line + } CorExceptionFlag; + + struct CORINFO_EH_CLAUSE + { + CORINFO_EH_CLAUSE_FLAGS Flags; // actually a CorExceptionFlag + DWORD TryOffset; + DWORD TryLength; // actually pass TryEndOffset + DWORD HandlerOffset; + DWORD HandlerLength; // actually pass HandlerEndOffset + union + { + DWORD ClassToken; // use for catch clauses + DWORD FilterOffset; // use for filter clauses + }; + }; + */ + // 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 = + getOffset(getLabelPlusOne(Clause.StartLabel), FuncBeginSym); + const MCExpr *ClauseEnd = + getOffset(getLabelPlusOne(Clause.EndLabel), FuncBeginSym); + + ClrEHUnwindMapEntry &Entry = FuncInfo.ClrEHUnwindMap[Clause.State]; + MachineBasicBlock *HandlerBlock = Entry.Handler.get(); + MCSymbol *BeginSym = getMCSymbolForMBBOrGV(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); + } +} Index: test/CodeGen/WinEH/wineh-coreclr.ll =================================================================== --- /dev/null +++ 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]]@IMGREL-[[L_begin]]@IMGREL +; ^ offset from L_begin to start of 1st funclet +; CHECK-NEXT: .long [[L_catch2]]@IMGREL-[[L_begin]]@IMGREL +; ^ offset from L_begin to start of 2nd funclet +; CHECK-NEXT: .long [[L_fault]]@IMGREL-[[L_begin]]@IMGREL +; ^ offset from L_begin to start of 3rd funclet +; CHECK-NEXT: .long [[L_finally]]@IMGREL-[[L_begin]]@IMGREL +; ^ offset from L_begin to start of 4th funclet +; CHECK-NEXT: .long [[L_end]]@IMGREL-[[L_begin]]@IMGREL +; ^ 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]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f2]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_catch1]]@IMGREL-[[L_begin]]@IMGREL +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_catch2]]@IMGREL-[[L_begin]]@IMGREL +; ^ 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]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f2]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_catch2]]@IMGREL-[[L_begin]]@IMGREL +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_fault]]@IMGREL-[[L_begin]]@IMGREL +; ^ 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]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f2]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_finally]]@IMGREL-[[L_begin]]@IMGREL +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_end]]@IMGREL-[[L_begin]]@IMGREL +; ^ 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]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f3]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_finally]]@IMGREL-[[L_begin]]@IMGREL +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_end]]@IMGREL-[[L_begin]]@IMGREL +; ^ 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]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f5]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_fault]]@IMGREL-[[L_begin]]@IMGREL +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_finally]]@IMGREL-[[L_begin]]@IMGREL +; ^ 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]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f5]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_finally]]@IMGREL-[[L_begin]]@IMGREL +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_end]]@IMGREL-[[L_begin]]@IMGREL +; ^ 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]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of start of clause +; CHECK-NEXT: .long ([[L_after_f6]]@IMGREL+1)-[[L_begin]]@IMGREL +; ^ offset of end of clause +; CHECK-NEXT: .long [[L_finally]]@IMGREL-[[L_begin]]@IMGREL +; ^ offset of start of handler +; CHECK-NEXT: .long [[L_end]]@IMGREL-[[L_begin]]@IMGREL +; ^ offset of end of handler +; CHECK-NEXT: .long 0 +; ^ type token slot (null for finally)