r326988 - [CodeGen] Emit lifetime.ends in both EH and non-EH blocks

George Burgess IV via cfe-commits cfe-commits at lists.llvm.org
Wed Mar 7 21:32:30 PST 2018


Author: gbiv
Date: Wed Mar  7 21:32:30 2018
New Revision: 326988

URL: http://llvm.org/viewvc/llvm-project?rev=326988&view=rev
Log:
[CodeGen] Emit lifetime.ends in both EH and non-EH blocks

Before this, we'd only emit lifetime.ends for these temps in
non-exceptional paths. This potentially made our stack larger than it
needed to be for any code that follows an EH cleanup. e.g. in

```
struct Foo { char cs[32]; };

void escape(void *);

struct Bar { ~Bar() { char cs[64]; escape(cs); } };

Foo getFoo();

void baz() {
  Bar b;
  getFoo();
}
```

baz() would require 96 bytes of stack, since the temporary from getFoo()
only had a lifetime.end on the non-exceptional path.

This also makes us keep hold of the Value* returned by
EmitLifetimeStart, so we don't have to remake it later.

Added:
    cfe/trunk/test/CodeGenCXX/stack-reuse-exceptions.cpp
Modified:
    cfe/trunk/lib/CodeGen/CGCall.cpp

Modified: cfe/trunk/lib/CodeGen/CGCall.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/CodeGen/CGCall.cpp?rev=326988&r1=326987&r2=326988&view=diff
==============================================================================
--- cfe/trunk/lib/CodeGen/CGCall.cpp (original)
+++ cfe/trunk/lib/CodeGen/CGCall.cpp Wed Mar  7 21:32:30 2018
@@ -3790,7 +3790,7 @@ RValue CodeGenFunction::EmitCall(const C
   // If the call returns a temporary with struct return, create a temporary
   // alloca to hold the result, unless one is given to us.
   Address SRetPtr = Address::invalid();
-  size_t UnusedReturnSize = 0;
+  llvm::Value *UnusedReturnSizePtr = nullptr;
   if (RetAI.isIndirect() || RetAI.isInAlloca() || RetAI.isCoerceAndExpand()) {
     if (!ReturnValue.isNull()) {
       SRetPtr = ReturnValue.getValue();
@@ -3799,8 +3799,7 @@ RValue CodeGenFunction::EmitCall(const C
       if (HaveInsertPoint() && ReturnValue.isUnused()) {
         uint64_t size =
             CGM.getDataLayout().getTypeAllocSize(ConvertTypeForMem(RetTy));
-        if (EmitLifetimeStart(size, SRetPtr.getPointer()))
-          UnusedReturnSize = size;
+        UnusedReturnSizePtr = EmitLifetimeStart(size, SRetPtr.getPointer());
       }
     }
     if (IRFunctionArgs.hasSRetArg()) {
@@ -4231,6 +4230,15 @@ RValue CodeGenFunction::EmitCall(const C
     CannotThrow = Attrs.hasAttribute(llvm::AttributeList::FunctionIndex,
                                      llvm::Attribute::NoUnwind);
   }
+
+  // If we made a temporary, be sure to clean up after ourselves. Note that we
+  // can't depend on being inside of an ExprWithCleanups, so we need to manually
+  // pop this cleanup later on. Being eager about this is OK, since this
+  // temporary is 'invisible' outside of the callee.
+  if (UnusedReturnSizePtr)
+    pushFullExprCleanup<CallLifetimeEnd>(NormalEHLifetimeMarker, SRetPtr,
+                                         UnusedReturnSizePtr);
+
   llvm::BasicBlock *InvokeDest = CannotThrow ? nullptr : getInvokeDest();
 
   SmallVector<llvm::OperandBundleDef, 1> BundleList =
@@ -4284,9 +4292,8 @@ RValue CodeGenFunction::EmitCall(const C
   // insertion point; this allows the rest of IRGen to discard
   // unreachable code.
   if (CS.doesNotReturn()) {
-    if (UnusedReturnSize)
-      EmitLifetimeEnd(llvm::ConstantInt::get(Int64Ty, UnusedReturnSize),
-                      SRetPtr.getPointer());
+    if (UnusedReturnSizePtr)
+      PopCleanupBlock();
 
     // Strip away the noreturn attribute to better diagnose unreachable UB.
     if (SanOpts.has(SanitizerKind::Unreachable)) {
@@ -4355,9 +4362,8 @@ RValue CodeGenFunction::EmitCall(const C
     case ABIArgInfo::InAlloca:
     case ABIArgInfo::Indirect: {
       RValue ret = convertTempToRValue(SRetPtr, RetTy, SourceLocation());
-      if (UnusedReturnSize)
-        EmitLifetimeEnd(llvm::ConstantInt::get(Int64Ty, UnusedReturnSize),
-                        SRetPtr.getPointer());
+      if (UnusedReturnSizePtr)
+        PopCleanupBlock();
       return ret;
     }
 

Added: cfe/trunk/test/CodeGenCXX/stack-reuse-exceptions.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CodeGenCXX/stack-reuse-exceptions.cpp?rev=326988&view=auto
==============================================================================
--- cfe/trunk/test/CodeGenCXX/stack-reuse-exceptions.cpp (added)
+++ cfe/trunk/test/CodeGenCXX/stack-reuse-exceptions.cpp Wed Mar  7 21:32:30 2018
@@ -0,0 +1,189 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu %s -o - -emit-llvm -O1 \
+// RUN:     -fexceptions -fcxx-exceptions | FileCheck %s
+//
+// We should emit lifetime.ends for these temporaries in both the 'exception'
+// and 'normal' paths in functions.
+//
+// -O1 is necessary to make lifetime markers appear.
+
+struct Large {
+  int cs[32];
+};
+
+Large getLarge();
+
+// Used to ensure we emit invokes.
+struct NontrivialDtor {
+  int i;
+  ~NontrivialDtor();
+};
+
+// CHECK-LABEL: define void @_Z33cleanupsAreEmittedWithoutTryCatchv
+void cleanupsAreEmittedWithoutTryCatch() {
+// CHECK: %[[CLEAN:[^ ]+]] = bitcast %struct.NontrivialDtor* %{{[^ ]+}} to i8*
+// CHECK: call void @llvm.lifetime.start.p0i8({{[^,]+}}, i8* nonnull %[[CLEAN]])
+// CHECK: %[[T1:[^ ]+]] = bitcast %struct.Large* %{{[^ ]+}} to i8*
+// CHECK: call void @llvm.lifetime.start.p0i8({{[^,]+}}, i8* nonnull %[[T1]])
+// CHECK-NEXT: invoke void @_Z8getLargev
+// CHECK-NEXT:     to label %[[CONT:[^ ]+]] unwind label %[[LPAD:[^ ]+]]
+//
+// CHECK: [[CONT]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T1]])
+// CHECK: %[[T2:[^ ]+]] = bitcast %struct.Large* %{{[^ ]+}} to i8*
+// CHECK: call void @llvm.lifetime.start.p0i8({{[^,]+}}, i8* nonnull %[[T2]])
+// CHECK-NEXT: invoke void @_Z8getLargev
+// CHECK-NEXT:     to label %[[CONT2:[^ ]+]] unwind label %[[LPAD2:.+]]
+//
+// CHECK: [[CONT2]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T2]])
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[CLEAN]])
+// CHECK: ret void
+//
+// CHECK: [[LPAD]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T1]])
+// CHECK: br label %[[EHCLEANUP:.+]]
+//
+// CHECK: [[LPAD2]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T2]])
+// CHECK: br label %[[EHCLEANUP]]
+//
+// CHECK: [[EHCLEANUP]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[CLEAN]])
+
+  NontrivialDtor clean;
+
+  getLarge();
+  getLarge();
+}
+
+// CHECK-LABEL: define void @_Z30cleanupsAreEmittedWithTryCatchv
+void cleanupsAreEmittedWithTryCatch() {
+// CHECK: %[[CLEAN:[^ ]+]] = bitcast %struct.NontrivialDtor* %{{[^ ]+}} to i8*
+// CHECK: call void @llvm.lifetime.start.p0i8({{[^,]+}}, i8* nonnull %[[CLEAN]])
+// CHECK: %[[T1:[^ ]+]] = bitcast %struct.Large* %{{[^ ]+}} to i8*
+// CHECK: call void @llvm.lifetime.start.p0i8({{[^,]+}}, i8* nonnull %[[T1]])
+// CHECK-NEXT: invoke void @_Z8getLargev
+// CHECK-NEXT:     to label %[[CONT:[^ ]+]] unwind label %[[LPAD:[^ ]+]]
+//
+// CHECK: [[CONT]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T1]])
+// CHECK: %[[T2:[^ ]+]] = bitcast %struct.Large* %{{[^ ]+}} to i8*
+// CHECK: call void @llvm.lifetime.start.p0i8({{[^,]+}}, i8* nonnull %[[T2]])
+// CHECK-NEXT: invoke void @_Z8getLargev
+// CHECK-NEXT:     to label %[[CONT2:[^ ]+]] unwind label %[[LPAD2:.+]]
+//
+// CHECK: [[CONT2]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T2]])
+// CHECK: br label %[[TRY_CONT:.+]]
+//
+// CHECK: [[LPAD]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T1]])
+// CHECK: br label %[[CATCH:.+]]
+//
+// CHECK: [[LPAD2]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T2]])
+// CHECK: br label %[[CATCH]]
+//
+// CHECK: [[CATCH]]:
+// CHECK-NOT: call void @llvm.lifetime
+// CHECK: invoke void
+// CHECK-NEXT: to label %[[TRY_CONT]] unwind label %[[OUTER_LPAD:.+]]
+//
+// CHECK: [[TRY_CONT]]:
+// CHECK: %[[T_OUTER:[^ ]+]] = bitcast %struct.Large* %{{[^ ]+}} to i8*
+// CHECK: call void @llvm.lifetime.start.p0i8({{[^,]+}}, i8* nonnull %[[T_OUTER]])
+// CHECK-NEXT: invoke void @_Z8getLargev
+// CHECK-NEXT:     to label %[[OUTER_CONT:[^ ]+]] unwind label %[[OUTER_LPAD2:.+]]
+//
+// CHECK: [[OUTER_CONT]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T_OUTER]])
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[CLEAN]])
+// CHECK: ret void
+//
+// CHECK: [[OUTER_LPAD]]:
+// CHECK-NOT: call void @llvm.lifetime
+// CHECK: br label %[[EHCLEANUP:.+]]
+//
+// CHECK: [[OUTER_LPAD2]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T_OUTER]])
+// CHECK: br label %[[EHCLEANUP]]
+//
+// CHECK: [[EHCLEANUP]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[CLEAN]])
+
+  NontrivialDtor clean;
+
+  try {
+    getLarge();
+    getLarge();
+  } catch (...) {}
+
+  getLarge();
+}
+
+// CHECK-LABEL: define void @_Z39cleanupInTryHappensBeforeCleanupInCatchv
+void cleanupInTryHappensBeforeCleanupInCatch() {
+// CHECK: %[[T1:[^ ]+]] = bitcast %struct.Large* %{{[^ ]+}} to i8*
+// CHECK: call void @llvm.lifetime.start.p0i8({{[^,]+}}, i8* nonnull %[[T1]])
+// CHECK-NEXT: invoke void @_Z8getLargev
+// CHECK-NEXT:     to label %[[CONT:[^ ]+]] unwind label %[[LPAD:[^ ]+]]
+//
+// CHECK: [[CONT]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T1]])
+// CHECK: br label %[[TRY_CONT]]
+//
+// CHECK: [[LPAD]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T1]])
+// CHECK: br i1 {{[^,]+}}, label %[[CATCH_INT_MATCH:[^,]+]], label %[[CATCH_ALL:.+]]
+//
+// CHECK: [[CATCH_INT_MATCH]]:
+// CHECK: %[[T2:[^ ]+]] = bitcast %struct.Large* %{{[^ ]+}} to i8*
+// CHECK: call void @llvm.lifetime.start.p0i8({{[^,]+}}, i8* nonnull %[[T2]])
+// CHECK-NEXT: invoke void @_Z8getLargev
+// CHECK-NEXT:     to label %[[CATCH_INT_CONT:[^ ]+]] unwind label %[[CATCH_INT_LPAD:[^ ]+]]
+//
+// CHECK: [[CATCH_INT_CONT]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T2]])
+// CHECK: br label %[[TRY_CONT]]
+//
+// CHECK: [[TRY_CONT]]:
+// CHECK: ret void
+//
+// CHECK: [[CATCH_ALL]]:
+// CHECK: %[[T3:[^ ]+]] = bitcast %struct.Large* %{{[^ ]+}} to i8*
+// CHECK: call void @llvm.lifetime.start.p0i8({{[^,]+}}, i8* nonnull %[[T3]])
+// CHECK-NEXT: invoke void @_Z8getLargev
+// CHECK-NEXT:     to label %[[CATCH_ALL_CONT:[^ ]+]] unwind label %[[CATCH_ALL_LPAD:[^ ]+]]
+//
+// CHECK: [[CATCH_ALL_CONT]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T3]])
+// CHECK: br label %[[TRY_CONT]]
+//
+// CHECK: [[CATCH_ALL_LPAD]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T3]])
+//
+// CHECK: [[CATCH_INT_LPAD]]:
+// CHECK: call void @llvm.lifetime.end.p0i8({{[^,]+}}, i8* nonnull %[[T2]])
+// CHECK-NOT: call void @llvm.lifetime
+
+  try {
+    getLarge();
+  } catch (const int &) {
+    getLarge();
+  } catch (...) {
+    getLarge();
+  }
+}
+
+// FIXME: We don't currently emit lifetime markers for aggregate by-value
+// temporaries (e.g. given a function `Large combine(Large, Large);`
+// combine(getLarge(), getLarge()) "leaks" two `Large`s). We probably should. We
+// also don't emit markers for things like:
+//
+// {
+//   Large L = getLarge();
+//   combine(L, L);
+// }
+//
+// Though this arguably isn't as bad, since we pass a pointer to `L` as one of
+// the two args.




More information about the cfe-commits mailing list