diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp --- a/clang/lib/CodeGen/CGException.cpp +++ b/clang/lib/CodeGen/CGException.cpp @@ -1635,7 +1635,8 @@ } void CodeGenFunction::EmitSEHTryStmt(const SEHTryStmt &S) { - EnterSEHTryStmt(S); + bool ContainsRetStmt = false; + EnterSEHTryStmt(S, ContainsRetStmt); { JumpDest TryExit = getJumpDestInCurrentScope("__try.__leave"); @@ -1664,7 +1665,7 @@ else delete TryExit.getBlock(); } - ExitSEHTryStmt(S); + ExitSEHTryStmt(S, ContainsRetStmt); } // Recursively walk through blocks in a _try @@ -1699,8 +1700,9 @@ namespace { struct PerformSEHFinally final : EHScopeStack::Cleanup { llvm::Function *OutlinedFinally; - PerformSEHFinally(llvm::Function *OutlinedFinally) - : OutlinedFinally(OutlinedFinally) {} + bool RetFromFinally; + PerformSEHFinally(llvm::Function *OutlinedFinally, bool RetFromFinally) + : OutlinedFinally(OutlinedFinally), RetFromFinally(RetFromFinally) {} void Emit(CodeGenFunction &CGF, Flags F) override { ASTContext &Context = CGF.getContext(); @@ -1744,6 +1746,21 @@ auto Callee = CGCallee::forDirect(OutlinedFinally); CGF.EmitCall(FnInfo, Callee, ReturnValueSlot(), Args); + + if (F.isForEHCleanup() && RetFromFinally) { + llvm::BasicBlock *AbnormalCont = CGF.createBasicBlock("if.then"); + llvm::BasicBlock *NormalCont = CGF.createBasicBlock("if.end"); + llvm::Value *ShouldRetLoad = + CGF.Builder.CreateLoad(CGF.SEHRetNowStack.back()); + llvm::Value *ShouldRet = CGF.Builder.CreateIsNotNull(ShouldRetLoad); + + CGF.Builder.CreateCondBr(ShouldRet, AbnormalCont, NormalCont); + CGF.EmitBlock(AbnormalCont); + CGF.EmitSEHLocalUnwind(); + CGF.Builder.CreateUnreachable(); + + CGF.EmitBlock(NormalCont); + } } }; } // end anonymous namespace @@ -1755,12 +1772,13 @@ const VarDecl *ParentThis; llvm::SmallSetVector Captures; Address SEHCodeSlot = Address::invalid(); + bool ContainsRetStmt = false; CaptureFinder(CodeGenFunction &ParentCGF, const VarDecl *ParentThis) : ParentCGF(ParentCGF), ParentThis(ParentThis) {} // Return true if we need to do any capturing work. bool foundCaptures() { - return !Captures.empty() || SEHCodeSlot.isValid(); + return !Captures.empty() || SEHCodeSlot.isValid() || ContainsRetStmt; } void Visit(const Stmt *S) { @@ -1802,6 +1820,25 @@ break; } } + + void VisitReturnStmt(const ReturnStmt *) { ContainsRetStmt = true; } +}; +} // end anonymous namespace + +namespace { +/// Find all local variable captures in the statement. +struct ReturnStmtFinder : ConstStmtVisitor { + bool ContainsRetStmt = false; + + void Visit(const Stmt *S) { + // See if this is a capture, then recurse. + ConstStmtVisitor::Visit(S); + for (const Stmt *Child : S->children()) + if (Child) + Visit(Child); + } + + void VisitReturnStmt(const ReturnStmt *) { ContainsRetStmt = true; } }; } // end anonymous namespace @@ -1850,7 +1887,8 @@ bool IsFilter) { // Find all captures in the Stmt. CaptureFinder Finder(ParentCGF, ParentCGF.CXXABIThisDecl); - Finder.Visit(OutlinedStmt); + if (OutlinedStmt) + Finder.Visit(OutlinedStmt); // We can exit early on x86_64 when there are no captures. We just have to // save the exception code in filters so that __exception_code() works. @@ -1988,6 +2026,16 @@ if (IsFilter) EmitSEHExceptionCodeSave(ParentCGF, ParentFP, EntryFP); + + if (Finder.ContainsRetStmt) { + SEHRetNowParent = recoverAddrOfEscapedLocal( + ParentCGF, ParentCGF.SEHRetNowStack.back(), ParentFP); + Address ParentSEHRetVal = + ParentCGF.ParentCGF ? ParentCGF.SEHReturnValue : ParentCGF.ReturnValue; + if (ParentSEHRetVal.isValid()) + SEHReturnValue = + recoverAddrOfEscapedLocal(ParentCGF, ParentSEHRetVal, ParentFP); + } } /// Arrange a function prototype that can be called by Windows exception @@ -2147,19 +2195,93 @@ void CodeGenFunction::pushSEHCleanup(CleanupKind Kind, llvm::Function *FinallyFunc) { - EHStack.pushCleanup(Kind, FinallyFunc); + EHStack.pushCleanup(Kind, FinallyFunc, false); } -void CodeGenFunction::EnterSEHTryStmt(const SEHTryStmt &S) { +void CodeGenFunction::EnterSEHTryStmt(const SEHTryStmt &S, + bool &ContainsRetStmt) { CodeGenFunction HelperCGF(CGM, /*suppressNewContext=*/true); HelperCGF.ParentCGF = this; if (const SEHFinallyStmt *Finally = S.getFinallyHandler()) { + ReturnStmtFinder Finder; + Finder.Visit(Finally); + ContainsRetStmt = Finder.ContainsRetStmt; + if (ContainsRetStmt) { + // Suppose we have something like: + // __try { + // f1(); + // } __finally { + // f2(); + // if (z) + // return; + // f3(); + // } + // + // We want to generate code something like this, where "StopUnwinding()" + // refers to the operation of aborting the unwind, and jumping back + // to normal code. + // + // int immediate_return = 0; + // __try { + // f1(); + // } __finally { + // f2(); + // if (z) { + // immediate_return = 1; + // goto end_of_finally; + // } + // f3(); + // end_of_finally: + // if (_abnormal_termination()) + // StopUnwinding(); + // } + // if (immediate_return) { + // return; + // } + // + // To handle the non-unwind case, we need to synthesize the + // "immediate_return" variable, and use it to change control flow + // after the finally block. + // + // To make "StopUnwinding()" work, we use _local_unwind. This function + // tells the SEH unwinder to recompute the unwind action: instead of + // using the __except handler that was already computed, stop unwinding + // when the unwinder reaches the current function. (The mechanism used + // here is unofficially called a "collided unwind".) + // + // We represent the destination of _local_unwind with a fake CatchPad: + // when the backend sees a filter named "__IsLocalUnwind", it arranges + // the unwind tables so that _local_unwind stops at that CatchPad, but + // other unwinding ignores it. + // + // Note that this construct could itself be inside an __try or __finally + // block. + // + // If it's inside the __try of a __try/__finally, the outer __finally + // executes before the function returns. + // + // If it's inside a __finally, we need to jump out of that __finally + // in a similar way. + + // Initialize the variable controlling the exception filter. + SEHRetNowStack.push_back( + CreateTempAlloca(CGM.Int8Ty, CharUnits::fromQuantity(1), "retnow")); + Builder.CreateStore(Builder.getInt8(0), SEHRetNowStack.back()); + + // Create the exception filter. + EHCatchScope *CatchScope = EHStack.pushCatch(1); + llvm::Function *FilterFunc = GenerateSEHIsLocalUnwindFunction(); + llvm::Constant *OpaqueFunc = + llvm::ConstantExpr::getBitCast(FilterFunc, Int8PtrTy); + CatchScope->setHandler(0, OpaqueFunc, createBasicBlock("__except.ret")); + } // Outline the finally block. llvm::Function *FinallyFunc = HelperCGF.GenerateSEHFinallyFunction(*this, *Finally); // Push a cleanup for __finally blocks. - EHStack.pushCleanup(NormalAndEHCleanup, FinallyFunc); + EHStack.pushCleanup(NormalAndEHCleanup, FinallyFunc, + ContainsRetStmt); return; } @@ -2191,10 +2313,72 @@ CatchScope->setHandler(0, OpaqueFunc, createBasicBlock("__except.ret")); } -void CodeGenFunction::ExitSEHTryStmt(const SEHTryStmt &S) { +llvm::Function *CodeGenFunction::GenerateSEHIsLocalUnwindFunction() { + // IsLocalUnwind is a void dummy func just for readability. + if (llvm::Function *F = CGM.getModule().getFunction("__IsLocalUnwind")) + return F; + + llvm::LLVMContext &Ctx = getLLVMContext(); + llvm::Type *ArgTys[] = {llvm::Type::getInt8PtrTy(Ctx), + llvm::Type::getInt8PtrTy(Ctx)}; + return llvm::Function::Create( + llvm::FunctionType::get(llvm::Type::getVoidTy(Ctx), ArgTys, false), + llvm::GlobalVariable::ExternalWeakLinkage, "__IsLocalUnwind", + &CGM.getModule()); +} + +void CodeGenFunction::EmitSEHLocalUnwind() { + EmitRuntimeCallOrInvoke(CGM.getIntrinsic(llvm::Intrinsic::seh_localunwind)); +} + +void CodeGenFunction::ExitSEHTryStmt(const SEHTryStmt &S, + bool ContainsRetStmt) { // Just pop the cleanup if it's a __finally block. if (S.getFinallyHandler()) { PopCleanupBlock(); + if (ContainsRetStmt) { + // Create __except block and control flow handling for return from + // __finally. See comment in EnterSEHTryStmt. + // + // First, create the point where we check for a return + // from the __finally. + llvm::BasicBlock *ContBB = createBasicBlock("__finally.cont"); + if (HaveInsertPoint()) + Builder.CreateBr(ContBB); + + EmitBlock(ContBB); + + // On the normal path, check if we have a return-from-finally. + llvm::BasicBlock *AbnormalCont = createBasicBlock("if.then"); + llvm::BasicBlock *NormalCont = createBasicBlock("if.end"); + llvm::Value *ShouldRetLoad = Builder.CreateLoad(SEHRetNowStack.back()); + llvm::Value *ShouldRet = Builder.CreateIsNotNull(ShouldRetLoad); + + Builder.CreateCondBr(ShouldRet, AbnormalCont, NormalCont); + + // Check if our filter function returned true. + EHCatchScope &CatchScope = cast(*EHStack.begin()); + emitCatchDispatchBlock(*this, CatchScope); + + // Grab the block before we pop the handler. + llvm::BasicBlock *CatchPadBB = CatchScope.getHandler(0).Block; + EHStack.popCatch(); + + // The catch block only catches return-from-finally. + EmitBlockAfterUses(CatchPadBB); + llvm::CatchPadInst *CPI = + cast(CatchPadBB->getFirstNonPHI()); + Builder.CreateCatchRet(CPI, AbnormalCont); + EmitBlock(AbnormalCont); + + // If the try block is nested inside a finally block, forward the + // return from __finally to the parent function. + if (SEHRetNowParent.isValid()) + Builder.CreateStore(Builder.getInt8(1), SEHRetNowParent); + EmitBranchThroughCleanup(ReturnBlock); + + EmitBlock(NormalCont); + } return; } diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -1298,10 +1298,10 @@ ReturnLocation); } - // Returning from an outlined SEH helper is UB, and we already warn on it. + Address ReturnValue = this->ReturnValue; if (IsOutlinedSEHHelper) { - Builder.CreateUnreachable(); - Builder.ClearInsertionPoint(); + Builder.CreateStore(Builder.getInt8(1), SEHRetNowParent); + ReturnValue = SEHReturnValue; } // Emit the result value, even if unused, to evaluate the side effects. diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -678,6 +678,16 @@ /// a value from the top of the stack. SmallVector SEHCodeSlotStack; + /// Variable that indicates abnormal termination from the a child finally + /// block. + SmallVector SEHRetNowStack; + + /// Ponter to the parent function's SEHRetNow variable. + Address SEHRetNowParent = Address::invalid(); + + /// Ponter to the root function's ReturnValue variable. + Address SEHReturnValue = Address::invalid(); + /// Value returned by __exception_info intrinsic. llvm::Value *SEHInfo = nullptr; @@ -3286,11 +3296,14 @@ void EmitCXXTryStmt(const CXXTryStmt &S); void EmitSEHTryStmt(const SEHTryStmt &S); void EmitSEHLeaveStmt(const SEHLeaveStmt &S); - void EnterSEHTryStmt(const SEHTryStmt &S); - void ExitSEHTryStmt(const SEHTryStmt &S); + void EnterSEHTryStmt(const SEHTryStmt &S, bool &ContainsRetStmt); + void ExitSEHTryStmt(const SEHTryStmt &S, bool ContainsRetStmt); void VolatilizeTryBlocks(llvm::BasicBlock *BB, llvm::SmallPtrSet &V); + void EmitSEHLocalUnwind(); + llvm::Function *GenerateSEHIsLocalUnwindFunction(); + void pushSEHCleanup(CleanupKind kind, llvm::Function *FinallyFunc); void startOutlinedSEHHelper(CodeGenFunction &ParentCGF, bool IsFilter, diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -3895,8 +3895,6 @@ CurScope->updateNRVOCandidate(VD); - CheckJumpOutOfSEHFinally(*this, ReturnLoc, *CurScope->getFnParent()); - return R; } diff --git a/clang/test/CodeGen/exceptions-seh-finally.c b/clang/test/CodeGen/exceptions-seh-finally.c --- a/clang/test/CodeGen/exceptions-seh-finally.c +++ b/clang/test/CodeGen/exceptions-seh-finally.c @@ -181,7 +181,8 @@ // // CHECK: [[outercont]] // CHECK: call void @"?fin$0@0@nested___finally___finally@@"({{.*}}) -// CHECK-NEXT: ret i32 0 +// CHECK-NEXT: load i32 +// CHECK-NEXT: switch i32 // // CHECK: [[lpad]] // CHECK-NEXT: %[[pad:[^ ]*]] = cleanuppad @@ -194,7 +195,8 @@ // CHECK-LABEL: define internal void @"?fin$1@0@nested___finally___finally@@"({{.*}}) // CHECK-SAME: [[finally_attrs]] -// CHECK: unreachable +// CHECK: store i32 1 +// CHECK-NEXT: ret void // FIXME: Our behavior seems suspiciously different. @@ -219,18 +221,23 @@ // CHECK-NEXT: to label %[[outercont:[^ ]*]] unwind label %[[lpad2:[^ ]*]] // // CHECK: [[outercont]] -// CHECK: call void @"?fin$0@0@nested___finally___finally_with_eh_edge@@"({{.*}}) -// CHECK-NEXT: ret i32 912 +// CHECK-NEXT: br label // // CHECK: [[lpad1]] // CHECK-NEXT: %[[innerpad:[^ ]*]] = cleanuppad // CHECK: invoke void @"?fin$1@0@nested___finally___finally_with_eh_edge@@"({{.*}}) -// CHECK-NEXT: label %[[innercleanupretbb:[^ ]*]] unwind label %[[lpad2:[^ ]*]] +// CHECK-NEXT: label %[[invokecont2:[^ ]*]] unwind label %[[lpad2:[^ ]*]] // -// CHECK: [[innercleanupretbb]] +// CHECK: [[invokecont2]] +// CHECK: br i1 {{.*}}, label %[[ifthen:[^ ]*]], label %[[ifend:[^ ]*]] +// +// CHECK: [[ifend]] // CHECK-NEXT: cleanupret from %[[innerpad]] unwind label %[[lpad2]] // // CHECK: [[lpad2]] +// CHECK: catchswitch {{.*}} unwind label %[[lpad3:[^ ]*]] +// +// CHECK: [[lpad3]] // CHECK-NEXT: %[[outerpad:[^ ]*]] = cleanuppad // CHECK: call void @"?fin$0@0@nested___finally___finally_with_eh_edge@@"({{.*}}) // CHECK-NEXT: cleanupret from %[[outerpad]] unwind to caller @@ -241,7 +248,7 @@ // CHECK-LABEL: define internal void @"?fin$1@0@nested___finally___finally_with_eh_edge@@"({{.*}}) // CHECK-SAME: [[finally_attrs]] -// CHECK: unreachable +// CHECK: store i32 899 void finally_within_finally(void) { __try { diff --git a/clang/test/CodeGen/exceptions-seh-leave.c b/clang/test/CodeGen/exceptions-seh-leave.c --- a/clang/test/CodeGen/exceptions-seh-leave.c +++ b/clang/test/CodeGen/exceptions-seh-leave.c @@ -75,7 +75,7 @@ // CHECK-NOT: store i32 23 // CHECK: [[tryleave]] // CHECK-NEXT: %[[fp:[^ ]*]] = call ptr @llvm.localaddress() -// CHECK-NEXT: call void @"?fin$0@0@__leave_with___finally_simple@@"(i8 noundef 0, ptr noundef %[[fp]]) +// CHECK-NEXT: invoke void @"?fin$0@0@__leave_with___finally_simple@@"(i8 noundef 0, ptr noundef %[[fp]]) // __finally block doesn't return, __finally.cont doesn't exist. int __leave_with___finally_noreturn(void) { @@ -119,7 +119,7 @@ // CHECK-NOT: store i32 23 // CHECK: [[tryleave]] // CHECK-NEXT: %[[fp:[^ ]*]] = call ptr @llvm.localaddress() -// CHECK-NEXT: call void @"?fin$0@0@__leave_with___finally@@"(i8 noundef 0, ptr noundef %[[fp]]) +// CHECK-NEXT: invoke void @"?fin$0@0@__leave_with___finally@@"(i8 noundef 0, ptr noundef %[[fp]]) ////////////////////////////////////////////////////////////////////////////// @@ -152,10 +152,6 @@ // CHECK-NEXT: invoke void @"?fin$0@0@nested___except___finally@@"(i8 noundef 0, ptr noundef %[[fp]]) // CHECK-NEXT: to label %[[fin_cont:.*]] unwind label %[[g2_lpad:.*]] -// CHECK: [[fin_cont]] -// CHECK: store i32 51, ptr % -// CHECK-NEXT: br label %[[trycont:[^ ]*]] - // CHECK: [[g1_lpad]] // CHECK-NEXT: cleanuppad // CHECK-NEXT: %[[fp:[^ ]*]] = call ptr @llvm.localaddress() @@ -166,10 +162,6 @@ // CHECK: [[g2_lpad]] // CHECK: catchpad {{.*}} [ptr null] // CHECK: catchret -// CHECK: br label %[[trycont]] - -// CHECK: [[trycont]] -// CHECK-NEXT: ret i32 1 // CHECK-LABEL: define internal void @"?fin$0@0@nested___except___finally@@"(i8 noundef %abnormal_termination, ptr noundef %frame_pointer) // CHECK: call void @g() @@ -311,21 +303,18 @@ // CHECK: store i32 16, ptr %[[myres:[^ ]*]], // CHECK: %[[fp:[^ ]*]] = call ptr @llvm.localaddress() // CHECK-NEXT: invoke void @"?fin$1@0@nested___finally___finally@@"(i8 noundef 0, ptr noundef %[[fp]]) -// CHECK-NEXT: to label %[[finally_cont:.*]] unwind label %[[g2_lpad:.*]] - -// CHECK: [[finally_cont]] -// CHECK: store i32 51, ptr %[[myres]] -// CHECK: %[[fp:[^ ]*]] = call ptr @llvm.localaddress() -// CHECK-NEXT: call void @"?fin$0@0@nested___finally___finally@@"(i8 noundef 0, ptr noundef %[[fp]]) -// CHECK-NEXT: ret i32 1 +// CHECK-NEXT: to label %[[finally_cont:.*]] unwind label %[[dispatch:.*]] // CHECK: [[g1_lpad]] // CHECK-NEXT: %[[padtoken:[^ ]*]] = cleanuppad within none [] // CHECK-NEXT: %[[fp:[^ ]*]] = call ptr @llvm.localaddress() // CHECK-NEXT: invoke void @"?fin$1@0@nested___finally___finally@@"(i8 noundef 1, ptr noundef %[[fp]]) -// CHECK-NEXT: to label %[[finally_cont2:.*]] unwind label %[[g2_lpad]] +// CHECK-NEXT: to label %[[finally_cont2:.*]] unwind label %[[dispatch]] // CHECK: [[finally_cont2]] -// CHECK: cleanupret from %[[padtoken]] unwind label %[[g2_lpad]] +// CHECK: cleanupret from %[[padtoken]] unwind label %[[dispatch]] + +// CHECK: [[dispatch]] +// CHECK: catchswitch {{.*}} unwind label %[[g2_lpad:.*]] // CHECK: [[g2_lpad]] // CHECK-NEXT: %[[padtoken:[^ ]*]] = cleanuppad within none [] diff --git a/clang/test/CodeGen/windows-seh-return-finally.c b/clang/test/CodeGen/windows-seh-return-finally.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/windows-seh-return-finally.c @@ -0,0 +1,83 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --include-generated-funcs --version 2 +// RUN: %clang_cc1 -triple x86_64-windows -fcxx-exceptions -fexceptions -fms-extensions -S -emit-llvm %s -o - | FileCheck %s + +void f1(), f2(), f3(); +void f(int z) { + __try { + f1(); + } __finally { + f2(); + if (z) + return; + f3(); + } +} +// CHECK-LABEL: define dso_local void @f +// CHECK-SAME: (i32 noundef [[Z:%.*]]) #[[ATTR0:[0-9]+]] personality ptr @__C_specific_handler { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[RETNOW:%.*]] = alloca i8, align 1 +// CHECK-NEXT: call void (...) @llvm.localescape(ptr [[Z_ADDR]], ptr [[RETNOW]]) +// CHECK-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4 +// CHECK-NEXT: store i8 0, ptr [[RETNOW]], align 1 +// CHECK-NEXT: invoke void @f1() #[[ATTR6:[0-9]+]] +// CHECK-NEXT: to label [[INVOKE_CONT:%.*]] unwind label [[EHCLEANUP:%.*]] +// CHECK: invoke.cont: +// CHECK-NEXT: [[TMP0:%.*]] = call ptr @llvm.localaddress() +// CHECK-NEXT: invoke void @"?fin$0@0@f@@"(i8 noundef 0, ptr noundef [[TMP0]]) +// CHECK-NEXT: to label [[INVOKE_CONT1:%.*]] unwind label [[CATCH_DISPATCH:%.*]] +// CHECK: invoke.cont1: +// CHECK-NEXT: br label [[__FINALLY_CONT:%.*]] +// CHECK: __finally.cont: +// CHECK-NEXT: [[TMP1:%.*]] = load i8, ptr [[RETNOW]], align 1 +// CHECK-NEXT: [[TMP2:%.*]] = icmp ne i8 [[TMP1]], 0 +// CHECK-NEXT: br i1 [[TMP2]], label [[IF_THEN4:%.*]], label [[IF_END5:%.*]] +// CHECK: ehcleanup: +// CHECK-NEXT: [[TMP3:%.*]] = cleanuppad within none [] +// CHECK-NEXT: [[TMP4:%.*]] = call ptr @llvm.localaddress() +// CHECK-NEXT: invoke void @"?fin$0@0@f@@"(i8 noundef 1, ptr noundef [[TMP4]]) [ "funclet"(token [[TMP3]]) ] +// CHECK-NEXT: to label [[INVOKE_CONT2:%.*]] unwind label [[CATCH_DISPATCH]] +// CHECK: invoke.cont2: +// CHECK-NEXT: [[TMP5:%.*]] = load i8, ptr [[RETNOW]], align 1 +// CHECK-NEXT: [[TMP6:%.*]] = icmp ne i8 [[TMP5]], 0 +// CHECK-NEXT: br i1 [[TMP6]], label [[IF_THEN:%.*]], label [[IF_END:%.*]] +// CHECK: if.then: +// CHECK-NEXT: invoke void @llvm.seh.localunwind() +// CHECK-NEXT: to label [[INVOKE_CONT3:%.*]] unwind label [[CATCH_DISPATCH]] +// CHECK: invoke.cont3: +// CHECK-NEXT: unreachable +// CHECK: if.end: +// CHECK-NEXT: cleanupret from [[TMP3]] unwind label [[CATCH_DISPATCH]] +// CHECK: catch.dispatch: +// CHECK-NEXT: [[TMP7:%.*]] = catchswitch within none [label %__except.ret] unwind to caller +// CHECK: __except.ret: +// CHECK-NEXT: [[TMP8:%.*]] = catchpad within [[TMP7]] [ptr @__IsLocalUnwind] +// CHECK-NEXT: catchret from [[TMP8]] to label [[IF_THEN4]] +// CHECK: if.then4: +// CHECK-NEXT: br label [[IF_END5]] +// CHECK: if.end5: +// CHECK-NEXT: ret void +// +// +// CHECK-LABEL: define internal void @"?fin$0@0@f@@" +// CHECK-SAME: (i8 noundef [[ABNORMAL_TERMINATION:%.*]], ptr noundef [[FRAME_POINTER:%.*]]) #[[ATTR1:[0-9]+]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[FRAME_POINTER_ADDR:%.*]] = alloca ptr, align 8 +// CHECK-NEXT: [[ABNORMAL_TERMINATION_ADDR:%.*]] = alloca i8, align 1 +// CHECK-NEXT: [[Z_ADDR:%.*]] = call ptr @llvm.localrecover(ptr @f, ptr [[FRAME_POINTER]], i32 0) +// CHECK-NEXT: [[RETNOW:%.*]] = call ptr @llvm.localrecover(ptr @f, ptr [[FRAME_POINTER]], i32 1) +// CHECK-NEXT: store ptr [[FRAME_POINTER]], ptr [[FRAME_POINTER_ADDR]], align 8 +// CHECK-NEXT: store i8 [[ABNORMAL_TERMINATION]], ptr [[ABNORMAL_TERMINATION_ADDR]], align 1 +// CHECK-NEXT: call void @f2() +// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[Z_ADDR]], align 4 +// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0 +// CHECK-NEXT: br i1 [[TOBOOL]], label [[IF_THEN:%.*]], label [[IF_END:%.*]] +// CHECK: if.then: +// CHECK-NEXT: store i8 1, ptr [[RETNOW]], align 1 +// CHECK-NEXT: br label [[RETURN:%.*]] +// CHECK: if.end: +// CHECK-NEXT: call void @f3() +// CHECK-NEXT: br label [[RETURN]] +// CHECK: return: +// CHECK-NEXT: ret void +// diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td --- a/llvm/include/llvm/IR/Intrinsics.td +++ b/llvm/include/llvm/IR/Intrinsics.td @@ -855,6 +855,9 @@ def int_seh_scope_begin : Intrinsic<[], [], [IntrNoMem]>; def int_seh_scope_end : Intrinsic<[], [], [IntrNoMem]>; +// Call _local_unwind to unwind to a local catchpad. +def int_seh_localunwind : Intrinsic<[], [], [IntrNoReturn]>; + // Note: we treat stacksave/stackrestore as writemem because we don't otherwise // model their dependencies on allocas. def int_stacksave : DefaultAttrsIntrinsic<[llvm_ptr_ty]>, diff --git a/llvm/lib/CodeGen/AsmPrinter/WinException.cpp b/llvm/lib/CodeGen/AsmPrinter/WinException.cpp --- a/llvm/lib/CodeGen/AsmPrinter/WinException.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/WinException.cpp @@ -618,6 +618,17 @@ LastStartLabel = StateChange.NewStartLabel; LastEHState = StateChange.NewState; } + for (auto Entry : FuncInfo.SEHUnwindMap) { + if (!Entry.IsFinally && Entry.ToState != -1) { + // Mark up the destination of _local_unwind so it doesn't unwind + // too far. + // + // FIXME: Can this overlap with the EH_LABEL for an invoke? + auto *Handler = Entry.Handler.get(); + const MCSymbol *Begin = Handler->getSymbol(); + emitSEHActionsForRange(FuncInfo, Begin, Begin, Entry.ToState); + } + } OS.emitLabel(TableEnd); } diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -3035,6 +3035,44 @@ DAG.setRoot(DAG.getNode(ISD::INTRINSIC_VOID, getCurSDLoc(), VTs, Ops)); break; } + case Intrinsic::seh_localunwind: { + if (!isa(EHPadBB->getTerminator())) { + report_fatal_error("localunwind doesn't point to catchswitch"); + } + auto *CatchSwitch = cast(EHPadBB->getTerminator()); + if (CatchSwitch->getNumHandlers() == 0) { + report_fatal_error("catchswitch with no handler"); + } + + const TargetLowering &TLI = DAG.getTargetLoweringInfo(); + TargetLowering::ArgListEntry SP, DestBB; + Type *PtrTy = PointerType::getInt8PtrTy(*DAG.getContext()); + EVT PtrVT = TLI.getPointerTy(DAG.getDataLayout()); + SP.Node = DAG.getNode(ISD::FRAMEADDR, getCurSDLoc(), PtrVT, + DAG.getIntPtrConstant(0, getCurSDLoc())); + SP.Ty = PtrTy; + // FIXME: Using a BlockAddress here is messy, but there isn't any + // node that directly represents taking the address of an MBB. + BasicBlock *HandlerBB = + const_cast(*CatchSwitch->handler_begin()); + MachineBasicBlock *HandlerMBB = FuncInfo.MBBMap[HandlerBB]; + HandlerMBB->setMachineBlockAddressTaken(); + HandlerMBB->setAddressTakenIRBlock(HandlerBB); + DestBB.Node = DAG.getBlockAddress(BlockAddress::get(HandlerBB), PtrVT); + DestBB.Ty = PtrTy; + TargetLowering::ArgListTy Args{SP, DestBB}; + + SDValue Callee = DAG.getExternalSymbol("_local_unwind", PtrVT); + TargetLowering::CallLoweringInfo CLI(DAG); + CLI.setDebugLoc(getCurSDLoc()) + .setChain(getRoot()) + .setCallee(CallingConv::C, Type::getVoidTy(*DAG.getContext()), Callee, + std::move(Args)) + .setNoReturn(); + CLI.CB = &I; + lowerInvokable(CLI, EHPadBB); + break; + } } } else if (I.countOperandBundlesOfType(LLVMContext::OB_deopt)) { // Currently we do not lower any intrinsic calls with deopt operand bundles. diff --git a/llvm/lib/CodeGen/WinEHPrepare.cpp b/llvm/lib/CodeGen/WinEHPrepare.cpp --- a/llvm/lib/CodeGen/WinEHPrepare.cpp +++ b/llvm/lib/CodeGen/WinEHPrepare.cpp @@ -502,6 +502,20 @@ const Function *Filter = dyn_cast(FilterOrNull); assert((Filter || FilterOrNull->isNullValue()) && "unexpected filter value"); + // Filters named __IsLocalUnwind are treated specially: we want to catch + // unwinds from _local_unwind, but not catchrets in the same funclet. + // (They both need to point at the same catchswitch to pass the verifier + // checks for nesting.) To make this work, we mess with the state + // numbering: the "parent" of any cleanupret pointing to this catchpad is + // actually this catchpad's parent. + // + // Note that _local_unwind looks for unwind table entries for the + // catchpad; if there aren't any, it assumes the catchpad doesn't have a + // parent. + bool IsLocalUnwind = + Filter && Filter->getName().startswith("__IsLocalUnwind"); + if (IsLocalUnwind) + Filter = nullptr; int TryState = addSEHExcept(FuncInfo, ParentState, Filter, CatchPadBB); // Everything in the __try block uses TryState as its parent state. @@ -513,7 +527,7 @@ if ((PredBlock = getEHPadFromPredecessor(PredBlock, CatchSwitch->getParentPad()))) calculateSEHStateNumbers(FuncInfo, PredBlock->getFirstNonPHI(), - TryState); + IsLocalUnwind ? ParentState : TryState); // Everything in the __except block unwinds to ParentState, just like code // outside the __try. diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -4824,6 +4824,7 @@ F->getIntrinsicID() == Intrinsic::experimental_patchpoint_i64 || F->getIntrinsicID() == Intrinsic::experimental_gc_statepoint || F->getIntrinsicID() == Intrinsic::wasm_rethrow || + F->getIntrinsicID() == Intrinsic::seh_localunwind || IsAttachedCallOperand(F, CBI, i), "Cannot invoke an intrinsic other than donothing, patchpoint, " "statepoint, coro_resume, coro_destroy or clang.arc.attachedcall", diff --git a/llvm/test/CodeGen/X86/seh-catchpad.ll b/llvm/test/CodeGen/X86/seh-catchpad.ll --- a/llvm/test/CodeGen/X86/seh-catchpad.ll +++ b/llvm/test/CodeGen/X86/seh-catchpad.ll @@ -140,6 +140,10 @@ ; CHECK-NEXT: .long .Ltmp7@IMGREL+1 ; CHECK-NEXT: .long "?filt$0@0@main@@"@IMGREL ; CHECK-NEXT: .long .LBB1_3@IMGREL +; CHECK-NEXT: .long .LBB1_1@IMGREL +; CHECK-NEXT: .long .LBB1_1@IMGREL+1 +; CHECK-NEXT: .long "?filt$0@0@main@@"@IMGREL +; CHECK-NEXT: .long .LBB1_3@IMGREL ; CHECK-NEXT: .Llsda_end0: ; CHECK: .text diff --git a/llvm/test/CodeGen/X86/seh-return-from-finally.ll b/llvm/test/CodeGen/X86/seh-return-from-finally.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/X86/seh-return-from-finally.ll @@ -0,0 +1,237 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --include-generated-funcs --version 2 +; RUN: llc < %s | FileCheck %s +target triple = "x86_64-unknown-windows-msvc19.20.0" + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local void @f(i32 noundef %z) #0 personality ptr @__C_specific_handler { +entry: + %z.addr = alloca i32, align 4 + %retnow = alloca i8, align 1 + call void (...) @llvm.localescape(ptr %z.addr, ptr %retnow) + store i32 %z, ptr %z.addr, align 4 + store i8 0, ptr %retnow, align 1 + invoke void @f1() #6 + to label %invoke.cont unwind label %ehcleanup + +invoke.cont: ; preds = %entry + %0 = call ptr @llvm.localaddress() + invoke void @"?fin$0@0@f@@"(i8 noundef 0, ptr noundef %0) + to label %invoke.cont1 unwind label %catch.dispatch + +invoke.cont1: ; preds = %invoke.cont + br label %__finally.cont + +__finally.cont: ; preds = %invoke.cont1 + %1 = load i8, ptr %retnow, align 1 + %2 = icmp ne i8 %1, 0 + br i1 %2, label %if.then4, label %if.end5 + +ehcleanup: ; preds = %entry + %3 = cleanuppad within none [] + %4 = call ptr @llvm.localaddress() + invoke void @"?fin$0@0@f@@"(i8 noundef 1, ptr noundef %4) [ "funclet"(token %3) ] + to label %invoke.cont2 unwind label %catch.dispatch + +invoke.cont2: ; preds = %ehcleanup + %5 = load i8, ptr %retnow, align 1 + %6 = icmp ne i8 %5, 0 + br i1 %6, label %if.then, label %if.end + +if.then: ; preds = %invoke.cont2 + invoke void @llvm.seh.localunwind() + to label %invoke.cont3 unwind label %catch.dispatch + +invoke.cont3: ; preds = %if.then + unreachable + +if.end: ; preds = %invoke.cont2 + cleanupret from %3 unwind label %catch.dispatch + +catch.dispatch: ; preds = %if.end, %if.then, %ehcleanup, %invoke.cont + %7 = catchswitch within none [label %__except.ret] unwind to caller + +__except.ret: ; preds = %catch.dispatch + %8 = catchpad within %7 [ptr @__IsLocalUnwind] + catchret from %8 to label %if.then4 + +if.then4: ; preds = %__except.ret, %__finally.cont + br label %if.end5 + +if.end5: ; preds = %if.then4, %__finally.cont + ret void +} + +declare extern_weak void @__IsLocalUnwind(ptr, ptr) + +; Function Attrs: noinline nounwind uwtable +define internal void @"?fin$0@0@f@@"(i8 noundef %abnormal_termination, ptr noundef %frame_pointer) #1 { +entry: + %frame_pointer.addr = alloca ptr, align 8 + %abnormal_termination.addr = alloca i8, align 1 + %z.addr = call ptr @llvm.localrecover(ptr @f, ptr %frame_pointer, i32 0) + %retnow = call ptr @llvm.localrecover(ptr @f, ptr %frame_pointer, i32 1) + store ptr %frame_pointer, ptr %frame_pointer.addr, align 8 + store i8 %abnormal_termination, ptr %abnormal_termination.addr, align 1 + call void @f2() + %0 = load i32, ptr %z.addr, align 4 + %tobool = icmp ne i32 %0, 0 + br i1 %tobool, label %if.then, label %if.end + +if.then: ; preds = %entry + store i8 1, ptr %retnow, align 1 + br label %return + +if.end: ; preds = %entry + call void @f3() + br label %return + +return: ; preds = %if.end, %if.then + ret void +} + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(none) +declare ptr @llvm.localrecover(ptr, ptr, i32 immarg) #2 + +declare dso_local void @f2(...) #3 + +declare dso_local void @f3(...) #3 + +declare dso_local void @f1(...) #3 + +declare dso_local i32 @__C_specific_handler(...) + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(none) +declare ptr @llvm.localaddress() #2 + +; Function Attrs: noreturn nounwind +declare void @llvm.seh.localunwind() #4 + +; Function Attrs: nocallback nofree nosync nounwind willreturn +declare void @llvm.localescape(...) #5 + +attributes #0 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +attributes #1 = { noinline nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +attributes #2 = { nocallback nofree nosync nounwind willreturn memory(none) } +attributes #3 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +attributes #4 = { noreturn nounwind } +attributes #5 = { nocallback nofree nosync nounwind willreturn } +attributes #6 = { noinline } +; CHECK-LABEL: f: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: pushq %rbp +; CHECK-NEXT: .seh_pushreg %rbp +; CHECK-NEXT: subq $48, %rsp +; CHECK-NEXT: .seh_stackalloc 48 +; CHECK-NEXT: leaq {{[0-9]+}}(%rsp), %rbp +; CHECK-NEXT: .seh_setframe %rbp, 48 +; CHECK-NEXT: .seh_endprologue +; CHECK-NEXT: .set .Lf$frame_escape_0, -8 +; CHECK-NEXT: .set .Lf$frame_escape_1, -1 +; CHECK-NEXT: movl %ecx, -8(%rbp) +; CHECK-NEXT: movb $0, -1(%rbp) +; CHECK-NEXT: .Ltmp0: +; CHECK-NEXT: callq f1 +; CHECK-NEXT: .Ltmp1: +; CHECK-NEXT: jmp .LBB0_1 +; CHECK-NEXT: .LBB0_1: # %invoke.cont +; CHECK-NEXT: .Ltmp6: +; CHECK-NEXT: xorl %ecx, %ecx +; CHECK-NEXT: movq %rbp, %rdx +; CHECK-NEXT: callq "?fin$0@0@f@@" +; CHECK-NEXT: .Ltmp7: +; CHECK-NEXT: jmp .LBB0_2 +; CHECK-NEXT: .LBB0_2: # %invoke.cont1 +; CHECK-NEXT: cmpb $0, -1(%rbp) +; CHECK-NEXT: jne .LBB0_9 +; CHECK-NEXT: jmp .LBB0_10 +; CHECK-NEXT: .Ltmp8: # Block address taken +; CHECK-NEXT: .LBB0_8: # %__except.ret +; CHECK-NEXT: jmp .LBB0_9 +; CHECK-NEXT: .LBB0_9: # %if.then4 +; CHECK-NEXT: $ehgcr_0_9: +; CHECK-NEXT: jmp .LBB0_10 +; CHECK-NEXT: .LBB0_10: # %if.end5 +; CHECK-NEXT: addq $48, %rsp +; CHECK-NEXT: popq %rbp +; CHECK-NEXT: retq +; CHECK-NEXT: .seh_handlerdata +; CHECK-NEXT: .set .Lf$parent_frame_offset, 48 +; CHECK-NEXT: .long (.Llsda_end0-.Llsda_begin0)/16 # Number of call sites +; CHECK-NEXT: .Llsda_begin0: +; CHECK-NEXT: .long .Ltmp0@IMGREL # LabelStart +; CHECK-NEXT: .long .Ltmp1@IMGREL+1 # LabelEnd +; CHECK-NEXT: .long "?dtor$3@?0?f@4HA"@IMGREL # FinallyFunclet +; CHECK-NEXT: .long 0 # Null +; CHECK-NEXT: .long .Ltmp6@IMGREL # LabelStart +; CHECK-NEXT: .long .Ltmp7@IMGREL+1 # LabelEnd +; CHECK-NEXT: .long 1 # CatchAll +; CHECK-NEXT: .long .LBB0_8@IMGREL # ExceptionHandler +; CHECK-NEXT: .Llsda_end0: +; CHECK-NEXT: .text +; CHECK-NEXT: .seh_endproc +; CHECK-NEXT: .def "?dtor$3@?0?f@4HA"; +; CHECK-NEXT: .scl 3; +; CHECK-NEXT: .type 32; +; CHECK-NEXT: .endef +; CHECK-NEXT: .p2align 4, 0x90 +; CHECK-NEXT: "?dtor$3@?0?f@4HA": +; CHECK-NEXT: .seh_proc "?dtor$3@?0?f@4HA" +; CHECK-NEXT: .LBB0_3: # %ehcleanup +; CHECK-NEXT: movq %rdx, {{[0-9]+}}(%rsp) +; CHECK-NEXT: pushq %rbp +; CHECK-NEXT: .seh_pushreg %rbp +; CHECK-NEXT: subq $32, %rsp +; CHECK-NEXT: .seh_stackalloc 32 +; CHECK-NEXT: leaq 48(%rdx), %rbp +; CHECK-NEXT: .seh_endprologue +; CHECK-NEXT: .Ltmp2: +; CHECK-NEXT: movb $1, %cl +; CHECK-NEXT: movq %rbp, %rdx +; CHECK-NEXT: callq "?fin$0@0@f@@" +; CHECK-NEXT: .Ltmp3: +; CHECK-NEXT: jmp .LBB0_4 +; CHECK-NEXT: .LBB0_4: # %invoke.cont2 +; CHECK-NEXT: cmpb $0, -1(%rbp) +; CHECK-NEXT: je .LBB0_7 +; CHECK-NEXT: # %bb.5: # %if.then +; CHECK-NEXT: .Ltmp4: +; CHECK-NEXT: leaq -48(%rbp), %rcx +; CHECK-NEXT: leaq .Ltmp8(%rip), %rdx +; CHECK-NEXT: callq _local_unwind +; CHECK-NEXT: .Ltmp5: +; CHECK-NEXT: jmp .LBB0_6 +; CHECK-NEXT: .LBB0_6: # %invoke.cont3 +; CHECK-NEXT: .LBB0_7: # %if.end +; CHECK-NEXT: addq $32, %rsp +; CHECK-NEXT: popq %rbp +; CHECK-NEXT: retq # CLEANUPRET +; CHECK-NEXT: .Lfunc_end0: +; CHECK-NEXT: .seh_handlerdata +; CHECK-NEXT: .text +; CHECK-NEXT: .seh_endproc +; +; CHECK-LABEL: "?fin$0@0@f@@": +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: pushq %rsi +; CHECK-NEXT: .seh_pushreg %rsi +; CHECK-NEXT: subq $48, %rsp +; CHECK-NEXT: .seh_stackalloc 48 +; CHECK-NEXT: .seh_endprologue +; CHECK-NEXT: movq %rdx, %rsi +; CHECK-NEXT: movq %rdx, {{[0-9]+}}(%rsp) +; CHECK-NEXT: movb %cl, {{[0-9]+}}(%rsp) +; CHECK-NEXT: callq f2 +; CHECK-NEXT: cmpl $0, .Lf$frame_escape_0(%rsi) +; CHECK-NEXT: je .LBB1_2 +; CHECK-NEXT: # %bb.1: # %if.then +; CHECK-NEXT: leaq .Lf$frame_escape_1(%rsi), %rax +; CHECK-NEXT: movb $1, (%rax) +; CHECK-NEXT: jmp .LBB1_3 +; CHECK-NEXT: .LBB1_2: # %if.end +; CHECK-NEXT: callq f3 +; CHECK-NEXT: .LBB1_3: # %return +; CHECK-NEXT: nop +; CHECK-NEXT: addq $48, %rsp +; CHECK-NEXT: popq %rsi +; CHECK-NEXT: retq +; CHECK-NEXT: .seh_endproc diff --git a/llvm/test/CodeGen/X86/seh-safe-div.ll b/llvm/test/CodeGen/X86/seh-safe-div.ll --- a/llvm/test/CodeGen/X86/seh-safe-div.ll +++ b/llvm/test/CodeGen/X86/seh-safe-div.ll @@ -89,6 +89,10 @@ ; CHECK-NEXT: .long .Ltmp1@IMGREL+1 ; CHECK-NEXT: .long safe_div_filt1@IMGREL ; CHECK-NEXT: .long [[handler1]]@IMGREL +; CHECK-NEXT: .long .LBB0_1@IMGREL +; CHECK-NEXT: .long .LBB0_1@IMGREL+1 +; CHECK-NEXT: .long safe_div_filt1@IMGREL +; CHECK-NEXT: .long .LBB0_2@IMGREL ; CHECK-NEXT: .Llsda_end0: ; CHECK: .text ; CHECK: .seh_endproc diff --git a/llvm/utils/UpdateTestChecks/common.py b/llvm/utils/UpdateTestChecks/common.py --- a/llvm/utils/UpdateTestChecks/common.py +++ b/llvm/utils/UpdateTestChecks/common.py @@ -353,7 +353,7 @@ UNUSED_NOTE = 'NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:' OPT_FUNCTION_RE = re.compile( - r'^(\s*;\s*Function\sAttrs:\s(?P[\w\s():,]+?))?\s*define\s+(?P[^@]*)@(?P[\w.$-]+?)\s*' + r'^(\s*;\s*Function\sAttrs:\s(?P[\w\s():,]+?))?\s*define\s+(?P[^@]*)@(?P([\w.$-]+?|"[\w.$-?@]+?"))\s*' r'(?P\((\)|(.*?[\w.-]+?)\))[^{]*\{)\n(?P.*?)^\}$', flags=(re.M | re.S))