From 0ada50f17fd38aa537c80248e24a18541418d8c6 Mon Sep 17 00:00:00 2001 From: Reid Kleckner Date: Mon, 6 Apr 2015 23:51:44 +0000 Subject: [PATCH] [SEH] Implement filter capturing in CodeGen While capturing filters aren't very common, we'd like to outline __finally blocks in the frontend to simplify -O0 EH preparation and reduce code size. Finally blocks are usually have captures, and this is the first step towards that. Currently we don't support capturing 'this' or VLAs. Reviewers: majnemer Differential Revision: http://reviews.llvm.org/D8825 llvm-svn: 234261 --- clang/lib/CodeGen/CGException.cpp | 129 +++++++++++++++++---- clang/lib/CodeGen/CodeGenFunction.cpp | 14 +++ clang/lib/CodeGen/CodeGenFunction.h | 10 ++ .../CodeGenCXX/exceptions-seh-filter-captures.cpp | 81 +++++++++++++ 4 files changed, 214 insertions(+), 20 deletions(-) create mode 100644 clang/test/CodeGenCXX/exceptions-seh-filter-captures.cpp diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp index f96c0a3..cc16053 100644 --- a/clang/lib/CodeGen/CGException.cpp +++ b/clang/lib/CodeGen/CGException.cpp @@ -19,8 +19,10 @@ #include "clang/AST/Mangle.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/StmtObjC.h" +#include "clang/AST/StmtVisitor.h" #include "llvm/IR/CallSite.h" #include "llvm/IR/Intrinsics.h" +#include "llvm/IR/IntrinsicInst.h" using namespace clang; using namespace CodeGen; @@ -1339,6 +1341,110 @@ struct PerformSEHFinally : EHScopeStack::Cleanup { }; } +namespace { +/// Find all local variable captures in the statement. +struct CaptureFinder : ConstStmtVisitor { + CodeGenFunction &ParentCGF; + const VarDecl *ParentThis; + SmallVector Captures; + CaptureFinder(CodeGenFunction &ParentCGF, const VarDecl *ParentThis) + : ParentCGF(ParentCGF), ParentThis(ParentThis) {} + + void Visit(const Stmt *S) { + // See if this is a capture, then recurse. + ConstStmtVisitor::Visit(S); + for (const Stmt *Child : S->children()) + Visit(Child); + } + + void VisitDeclRefExpr(const DeclRefExpr *E) { + // If this is already a capture, just make sure we capture 'this'. + if (E->refersToEnclosingVariableOrCapture()) { + Captures.push_back(ParentThis); + return; + } + + const auto *D = dyn_cast(E->getDecl()); + if (D && D->isLocalVarDeclOrParm() && D->hasLocalStorage()) + Captures.push_back(D); + } + + void VisitCXXThisExpr(const CXXThisExpr *E) { + Captures.push_back(ParentThis); + } +}; +} + +void CodeGenFunction::EmitCapturedLocals(CodeGenFunction &ParentCGF, + const Stmt *OutlinedStmt, + llvm::Value *ParentFP) { + // Find all captures in the Stmt. + CaptureFinder Finder(ParentCGF, ParentCGF.CXXABIThisDecl); + Finder.Visit(OutlinedStmt); + + // Typically there are no captures and we can exit early. + if (Finder.Captures.empty()) + return; + + // Prepare the first two arguments to llvm.framerecover. + llvm::Function *FrameRecoverFn = llvm::Intrinsic::getDeclaration( + &CGM.getModule(), llvm::Intrinsic::framerecover); + llvm::Constant *ParentI8Fn = + llvm::ConstantExpr::getBitCast(ParentCGF.CurFn, Int8PtrTy); + + // Create llvm.framerecover calls for all captures. + for (const VarDecl *VD : Finder.Captures) { + if (isa(VD)) { + CGM.ErrorUnsupported(VD, "'this' captured by SEH"); + CXXThisValue = llvm::UndefValue::get(ConvertTypeForMem(VD->getType())); + continue; + } + if (VD->getType()->isVariablyModifiedType()) { + CGM.ErrorUnsupported(VD, "VLA captured by SEH"); + continue; + } + + assert((isa(VD) || VD->isLocalVarDeclOrParm()) && + "captured non-local variable"); + + llvm::Value *ParentVar = ParentCGF.LocalDeclMap[VD]; + assert(ParentVar && "capture was not a local decl"); + llvm::CallInst *RecoverCall = nullptr; + CGBuilderTy Builder(AllocaInsertPt); + if (auto *ParentAlloca = dyn_cast(ParentVar)) { + // Mark the variable escaped if nobody else referenced it and compute the + // frameescape index. + auto InsertPair = + ParentCGF.EscapedLocals.insert(std::make_pair(ParentAlloca, -1)); + if (InsertPair.second) + InsertPair.first->second = ParentCGF.EscapedLocals.size() - 1; + int FrameEscapeIdx = InsertPair.first->second; + // call i8* @llvm.framerecover(i8* bitcast(@parentFn), i8* %fp, i32 N) + RecoverCall = + Builder.CreateCall3(FrameRecoverFn, ParentI8Fn, ParentFP, + llvm::ConstantInt::get(Int32Ty, FrameEscapeIdx)); + + } else { + // If the parent didn't have an alloca, we're doing some nested outlining. + // Just clone the existing framerecover call, but tweak the FP argument to + // use our FP value. All other arguments are constants. + auto *ParentRecover = + cast(ParentVar->stripPointerCasts()); + assert(ParentRecover->getIntrinsicID() == llvm::Intrinsic::framerecover && + "expected alloca or framerecover in parent LocalDeclMap"); + RecoverCall = cast(ParentRecover->clone()); + RecoverCall->setArgOperand(1, ParentFP); + RecoverCall->insertBefore(AllocaInsertPt); + } + + // Bitcast the variable, rename it, and insert it in the local decl map. + llvm::Value *ChildVar = + Builder.CreateBitCast(RecoverCall, ParentVar->getType()); + ChildVar->setName(ParentVar->getName()); + LocalDeclMap[VD] = ChildVar; + } +} + /// Create a stub filter function that will ultimately hold the code of the /// filter expression. The EH preparation passes in LLVM will outline the code /// from the main function body into this stub. @@ -1392,15 +1498,9 @@ CodeGenFunction::GenerateSEHFilterFunction(CodeGenFunction &ParentCGF, EmitSEHExceptionCodeSave(); - // Insert dummy allocas for every local variable in scope. We'll initialize - // them and prune the unused ones after we find out which ones were - // referenced. - for (const auto &DeclPtrs : ParentCGF.LocalDeclMap) { - const Decl *VD = DeclPtrs.first; - llvm::Value *Ptr = DeclPtrs.second; - auto *ValTy = cast(Ptr->getType())->getElementType(); - LocalDeclMap[VD] = CreateTempAlloca(ValTy, Ptr->getName() + ".filt"); - } + auto AI = Fn->arg_begin(); + ++AI; + EmitCapturedLocals(ParentCGF, FilterExpr, &*AI); // Emit the original filter expression, convert to i32, and return. llvm::Value *R = EmitScalarExpr(FilterExpr); @@ -1410,17 +1510,6 @@ CodeGenFunction::GenerateSEHFilterFunction(CodeGenFunction &ParentCGF, FinishFunction(FilterExpr->getLocEnd()); - for (const auto &DeclPtrs : ParentCGF.LocalDeclMap) { - const Decl *VD = DeclPtrs.first; - auto *Alloca = cast(LocalDeclMap[VD]); - if (Alloca->hasNUses(0)) { - Alloca->eraseFromParent(); - continue; - } - ErrorUnsupported(FilterExpr, - "SEH filter expression local variable capture"); - } - return Fn; } diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index 7de61e3..e59a50e 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -279,6 +279,20 @@ void CodeGenFunction::FinishFunction(SourceLocation EndLoc) { Builder.ClearInsertionPoint(); } + // If some of our locals escaped, insert a call to llvm.frameescape in the + // entry block. + if (!EscapedLocals.empty()) { + // Invert the map from local to index into a simple vector. There should be + // no holes. + SmallVector EscapeArgs; + EscapeArgs.resize(EscapedLocals.size()); + for (auto &Pair : EscapedLocals) + EscapeArgs[Pair.second] = Pair.first; + llvm::Function *FrameEscapeFn = llvm::Intrinsic::getDeclaration( + &CGM.getModule(), llvm::Intrinsic::frameescape); + CGBuilderTy(AllocaInsertPt).CreateCall(FrameEscapeFn, EscapeArgs); + } + // Remove the AllocaInsertPt instruction, which is just a convenience for us. llvm::Instruction *Ptr = AllocaInsertPt; AllocaInsertPt = nullptr; diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 8d3408a..6183b49 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -877,6 +877,10 @@ private: typedef llvm::DenseMap DeclMapTy; DeclMapTy LocalDeclMap; + /// Track escaped local variables with auto storage. Used during SEH + /// outlining to produce a call to llvm.frameescape. + llvm::DenseMap EscapedLocals; + /// LabelMap - This keeps track of the LLVM basic block for each C label. llvm::DenseMap LabelMap; @@ -2007,6 +2011,12 @@ public: llvm::Value *EmitSEHExceptionInfo(); llvm::Value *EmitSEHAbnormalTermination(); + /// Scan the outlined statement for captures from the parent function. For + /// each capture, mark the capture as escaped and emit a call to + /// llvm.framerecover. Insert the framerecover result into the LocalDeclMap. + void EmitCapturedLocals(CodeGenFunction &ParentCGF, const Stmt *OutlinedStmt, + llvm::Value *ParentFP); + void EmitCXXForRangeStmt(const CXXForRangeStmt &S, ArrayRef Attrs = None); diff --git a/clang/test/CodeGenCXX/exceptions-seh-filter-captures.cpp b/clang/test/CodeGenCXX/exceptions-seh-filter-captures.cpp new file mode 100644 index 0000000..2175de5 --- /dev/null +++ b/clang/test/CodeGenCXX/exceptions-seh-filter-captures.cpp @@ -0,0 +1,81 @@ +// RUN: %clang_cc1 -std=c++11 -fblocks -fms-extensions %s -triple=x86_64-windows-msvc -emit-llvm \ +// RUN: -o - -mconstructor-aliases -fcxx-exceptions -fexceptions | \ +// RUN: FileCheck %s --check-prefix=CHECK --check-prefix=CXXEH + +extern "C" int basic_filter(int v, ...); +extern "C" void might_crash(); + +extern "C" void test_freefunc(int p1) { + int l1 = 13; + static int s1 = 42; + __try { + might_crash(); + } __except(basic_filter(p1, l1, s1)) { + } +} + +// CHECK-LABEL: define void @test_freefunc(i32 %p1) +// CHECK: @llvm.frameescape(i32* %[[p1_ptr:[^, ]*]], i32* %[[l1_ptr:[^, ]*]]) +// CHECK: store i32 %p1, i32* %[[p1_ptr]], align 4 +// CHECK: store i32 13, i32* %[[l1_ptr]], align 4 +// CHECK: invoke void @might_crash() + +// CHECK-LABEL: define internal i32 @"\01?filt$0@0@test_freefunc@@"(i8* %exception_pointers, i8* %frame_pointer) +// CHECK: %[[p1_i8:[^ ]*]] = call i8* @llvm.framerecover(i8* bitcast (void (i32)* @test_freefunc to i8*), i8* %frame_pointer, i32 0) +// CHECK: %[[p1_ptr:[^ ]*]] = bitcast i8* %[[p1_i8]] to i32* +// CHECK: %[[l1_i8:[^ ]*]] = call i8* @llvm.framerecover(i8* bitcast (void (i32)* @test_freefunc to i8*), i8* %frame_pointer, i32 1) +// CHECK: %[[l1_ptr:[^ ]*]] = bitcast i8* %[[l1_i8]] to i32* +// CHECK: %[[s1:[^ ]*]] = load i32, i32* @"\01?s1@?1??test_freefunc@@9@4HA", align 4 +// CHECK: %[[l1:[^ ]*]] = load i32, i32* %[[l1_ptr]] +// CHECK: %[[p1:[^ ]*]] = load i32, i32* %[[p1_ptr]] +// CHECK: call i32 (i32, ...)* @basic_filter(i32 %[[p1]], i32 %[[l1]], i32 %[[s1]]) + +struct S { + int m1; + void test_method(void); +}; + +void S::test_method() { + int l1 = 13; + __try { + might_crash(); + } __except(basic_filter(l1)) { + // FIXME: Test capturing 'this' and 'm1'. + } +} + +// CHECK-LABEL: define void @"\01?test_method@S@@QEAAXXZ"(%struct.S* %this) +// CHECK: @llvm.frameescape(i32* %[[l1_addr:[^, ]*]]) +// CHECK: store i32 13, i32* %[[l1_addr]], align 4 +// CHECK: invoke void @might_crash() + +// CHECK-LABEL: define internal i32 @"\01?filt$0@0@test_method@S@@"(i8* %exception_pointers, i8* %frame_pointer) +// CHECK: %[[l1_i8:[^ ]*]] = call i8* @llvm.framerecover(i8* bitcast (void (%struct.S*)* @"\01?test_method@S@@QEAAXXZ" to i8*), i8* %frame_pointer, i32 0) +// CHECK: %[[l1_ptr:[^ ]*]] = bitcast i8* %[[l1_i8]] to i32* +// CHECK: %[[l1:[^ ]]] = load i32, i32* %[[l1_ptr]] +// CHECK: call i32 (i32, ...)* @basic_filter(i32 %[[l1]]) + +void test_lambda() { + int l1 = 13; + auto lambda = [&]() { + int l2 = 42; + __try { + might_crash(); + } __except(basic_filter(l2)) { + // FIXME: Test 'l1' when we can capture the lambda's 'this' decl. + } + }; + lambda(); +} + +// CHECK-LABEL: define internal void @"\01??R@?test_lambda@@YAXXZ@QEBAXXZ"(%class.anon* %this) +// CHECK: @llvm.frameescape(i32* %[[l2_addr:[^, ]*]]) +// CHECK: store i32 42, i32* %[[l2_addr]], align 4 +// CHECK: invoke void @might_crash() + +// CHECK-LABEL: define internal i32 @"\01?filt$0@0@?R@?test_lambda@@YAXXZ@"(i8* %exception_pointers, i8* %frame_pointer) +// CHECK: %[[l2_i8:[^ ]*]] = call i8* @llvm.framerecover(i8* bitcast (void (%class.anon*)* @"\01??R@?test_lambda@@YAXXZ@QEBAXXZ" to i8*), i8* %frame_pointer, i32 0) +// CHECK: %[[l2_ptr:[^ ]*]] = bitcast i8* %[[l2_i8]] to i32* +// CHECK: %[[l2:[^ ]]] = load i32, i32* %[[l2_ptr]] +// CHECK: call i32 (i32, ...)* @basic_filter(i32 %[[l2]]) + -- 2.7.4