r207889 - MS ABI x64: Pass small objects with dtors but no copy ctors directly

Reid Kleckner reid at kleckner.net
Fri May 2 17:33:28 PDT 2014


Author: rnk
Date: Fri May  2 19:33:28 2014
New Revision: 207889

URL: http://llvm.org/viewvc/llvm-project?rev=207889&view=rev
Log:
MS ABI x64: Pass small objects with dtors but no copy ctors directly

Passing objects directly (in registers or memory) creates a second copy
of the object in the callee.  The callee always destroys its copy, but
we also have to destroy any temporary created in the caller.  In other
words, copy elision of these kinds of objects is impossible.

Objects larger than 8 bytes with non-trivial dtors and trivial copy
ctors are still passed indirectly, and we can still elide copies of
them.

Fixes PR19640.

Modified:
    cfe/trunk/lib/CodeGen/CGCall.cpp
    cfe/trunk/lib/CodeGen/MicrosoftCXXABI.cpp
    cfe/trunk/test/CodeGenCXX/microsoft-abi-arg-order.cpp
    cfe/trunk/test/CodeGenCXX/microsoft-abi-sret-and-byval.cpp

Modified: cfe/trunk/lib/CodeGen/CGCall.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/CodeGen/CGCall.cpp?rev=207889&r1=207888&r2=207889&view=diff
==============================================================================
--- cfe/trunk/lib/CodeGen/CGCall.cpp (original)
+++ cfe/trunk/lib/CodeGen/CGCall.cpp Fri May  2 19:33:28 2014
@@ -2289,20 +2289,25 @@ void CodeGenFunction::EmitCallArg(CallAr
   if (HasAggregateEvalKind &&
       CGM.getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) {
     // If we're using inalloca, use the argument memory.  Otherwise, use a
-    // temporary.  Either way, the aggregate is destroyed externally in the
-    // callee.
+    // temporary.
     AggValueSlot Slot;
     if (args.isUsingInAlloca())
       Slot = createPlaceholderSlot(*this, type);
     else
       Slot = CreateAggTemp(type, "agg.tmp");
-    Slot.setExternallyDestructed();
+
+    const CXXRecordDecl *RD = type->getAsCXXRecordDecl();
+    bool DestroyedInCallee =
+        RD && RD->hasNonTrivialDestructor() &&
+        CGM.getCXXABI().getRecordArgABI(RD) != CGCXXABI::RAA_Default;
+    if (DestroyedInCallee)
+      Slot.setExternallyDestructed();
+
     EmitAggExpr(E, Slot);
     RValue RV = Slot.asRValue();
     args.add(RV, type);
 
-    const CXXRecordDecl *RD = type->getAsCXXRecordDecl();
-    if (RD && RD->hasNonTrivialDestructor()) {
+    if (DestroyedInCallee) {
       // Create a no-op GEP between the placeholder and the cleanup so we can
       // RAUW it successfully.  It also serves as a marker of the first
       // instruction where the cleanup is active.

Modified: cfe/trunk/lib/CodeGen/MicrosoftCXXABI.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/CodeGen/MicrosoftCXXABI.cpp?rev=207889&r1=207888&r2=207889&view=diff
==============================================================================
--- cfe/trunk/lib/CodeGen/MicrosoftCXXABI.cpp (original)
+++ cfe/trunk/lib/CodeGen/MicrosoftCXXABI.cpp Fri May  2 19:33:28 2014
@@ -44,17 +44,7 @@ public:
     return !RD->isPOD();
   }
 
-  RecordArgABI getRecordArgABI(const CXXRecordDecl *RD) const override {
-    if (RD->hasNonTrivialCopyConstructor() || RD->hasNonTrivialDestructor()) {
-      llvm::Triple::ArchType Arch = CGM.getTarget().getTriple().getArch();
-      if (Arch == llvm::Triple::x86)
-        return RAA_DirectInMemory;
-      // On x64, pass non-trivial records indirectly.
-      // FIXME: Test other Windows architectures.
-      return RAA_Indirect;
-    }
-    return RAA_Default;
-  }
+  RecordArgABI getRecordArgABI(const CXXRecordDecl *RD) const override;
 
   StringRef GetPureVirtualCallName() override { return "_purecall"; }
   // No known support for deleted functions in MSVC yet, so this choice is
@@ -407,6 +397,33 @@ private:
 
 }
 
+CGCXXABI::RecordArgABI
+MicrosoftCXXABI::getRecordArgABI(const CXXRecordDecl *RD) const {
+  switch (CGM.getTarget().getTriple().getArch()) {
+  default:
+    // FIXME: Implement for other architectures.
+    return RAA_Default;
+
+  case llvm::Triple::x86:
+    // 32-bit x86 constructs non-trivial objects directly in outgoing argument
+    // slots.  LLVM uses the inalloca attribute to implement this.
+    if (RD->hasNonTrivialCopyConstructor() || RD->hasNonTrivialDestructor())
+      return RAA_DirectInMemory;
+    return RAA_Default;
+
+  case llvm::Triple::x86_64:
+    // Win64 passes objects with non-trivial copy ctors indirectly.
+    if (RD->hasNonTrivialCopyConstructor())
+      return RAA_Indirect;
+    // Win64 passes objects larger than 8 bytes indirectly.
+    if (getContext().getTypeSize(RD->getTypeForDecl()) > 64)
+      return RAA_Indirect;
+    return RAA_Default;
+  }
+
+  llvm_unreachable("invalid enum");
+}
+
 llvm::Value *MicrosoftCXXABI::adjustToCompleteObject(CodeGenFunction &CGF,
                                                      llvm::Value *ptr,
                                                      QualType type) {

Modified: cfe/trunk/test/CodeGenCXX/microsoft-abi-arg-order.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CodeGenCXX/microsoft-abi-arg-order.cpp?rev=207889&r1=207888&r2=207889&view=diff
==============================================================================
--- cfe/trunk/test/CodeGenCXX/microsoft-abi-arg-order.cpp (original)
+++ cfe/trunk/test/CodeGenCXX/microsoft-abi-arg-order.cpp Fri May  2 19:33:28 2014
@@ -3,6 +3,7 @@
 
 struct A {
   A(int a);
+  A(const A &o);
   ~A();
   int a;
 };

Modified: cfe/trunk/test/CodeGenCXX/microsoft-abi-sret-and-byval.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CodeGenCXX/microsoft-abi-sret-and-byval.cpp?rev=207889&r1=207888&r2=207889&view=diff
==============================================================================
--- cfe/trunk/test/CodeGenCXX/microsoft-abi-sret-and-byval.cpp (original)
+++ cfe/trunk/test/CodeGenCXX/microsoft-abi-sret-and-byval.cpp Fri May  2 19:33:28 2014
@@ -47,6 +47,12 @@ struct Big {
   int a, b, c, d, e, f;
 };
 
+struct BigWithDtor {
+  BigWithDtor();
+  ~BigWithDtor();
+  int a, b, c, d, e, f;
+};
+
 // WIN32: declare void @"{{.*take_bools_and_chars.*}}"
 // WIN32:       (<{ i8, [3 x i8], i8, [3 x i8], %struct.SmallWithDtor,
 // WIN32:           i8, [3 x i8], i8, [3 x i8], i32, i8, [3 x i8] }>* inalloca)
@@ -114,15 +120,43 @@ void small_arg_with_dtor(SmallWithDtor s
 // WIN32: define void @"\01?small_arg_with_dtor@@YAXUSmallWithDtor@@@Z"(<{ %struct.SmallWithDtor }>* inalloca) {{.*}} {
 // WIN32:   call x86_thiscallcc void @"\01??1SmallWithDtor@@QAE at XZ"
 // WIN32: }
-// WIN64: define void @"\01?small_arg_with_dtor@@YAXUSmallWithDtor@@@Z"(%struct.SmallWithDtor* %s) {{.*}} {
+// WIN64: define void @"\01?small_arg_with_dtor@@YAXUSmallWithDtor@@@Z"(i32 %s.coerce) {{.*}} {
 // WIN64:   call void @"\01??1SmallWithDtor@@QEAA at XZ"
 // WIN64: }
 
+void call_small_arg_with_dtor() {
+  small_arg_with_dtor(SmallWithDtor());
+}
+// The temporary is copied, so it's destroyed in the caller as well as the
+// callee.
+// WIN64-LABEL: define void @"\01?call_small_arg_with_dtor@@YAXXZ"()
+// WIN64:   call %struct.SmallWithDtor* @"\01??0SmallWithDtor@@QEAA at XZ"
+// WIN64:   call void @"\01?small_arg_with_dtor@@YAXUSmallWithDtor@@@Z"(i32 %{{.*}})
+// WIN64:   call void @"\01??1SmallWithDtor@@QEAA at XZ"
+// WIN64:   ret void
+
 // Test that references aren't destroyed in the callee.
 void ref_small_arg_with_dtor(const SmallWithDtor &s) { }
 // WIN32: define void @"\01?ref_small_arg_with_dtor@@YAXABUSmallWithDtor@@@Z"(%struct.SmallWithDtor* %s) {{.*}} {
 // WIN32-NOT:   call x86_thiscallcc void @"\01??1SmallWithDtor@@QAE at XZ"
 // WIN32: }
+// WIN64-LABEL: define void @"\01?ref_small_arg_with_dtor@@YAXAEBUSmallWithDtor@@@Z"(%struct.SmallWithDtor* %s)
+
+void big_arg_with_dtor(BigWithDtor s) {}
+// WIN64-LABEL: define void @"\01?big_arg_with_dtor@@YAXUBigWithDtor@@@Z"(%struct.BigWithDtor* %s)
+// WIN64:   call void @"\01??1BigWithDtor@@QEAA at XZ"
+// WIN64: }
+
+void call_big_arg_with_dtor() {
+  big_arg_with_dtor(BigWithDtor());
+}
+// We can elide the copy of the temporary in the caller, because this object is
+// larger than 8 bytes and is passed indirectly.
+// WIN64-LABEL: define void @"\01?call_big_arg_with_dtor@@YAXXZ"()
+// WIN64:   call %struct.BigWithDtor* @"\01??0BigWithDtor@@QEAA at XZ"
+// WIN64:   call void @"\01?big_arg_with_dtor@@YAXUBigWithDtor@@@Z"(%struct.BigWithDtor* %{{.*}})
+// WIN64-NOT: call void @"\01??1BigWithDtor@@QEAA at XZ"
+// WIN64:   ret void
 
 // Test that temporaries passed by reference are destroyed in the caller.
 void temporary_ref_with_dtor() {





More information about the cfe-commits mailing list