r356425 - [MS] Skip vbase construction in abstract class ctors

Reid Kleckner via cfe-commits cfe-commits at lists.llvm.org
Mon Mar 18 15:41:51 PDT 2019


Author: rnk
Date: Mon Mar 18 15:41:50 2019
New Revision: 356425

URL: http://llvm.org/viewvc/llvm-project?rev=356425&view=rev
Log:
[MS] Skip vbase construction in abstract class ctors

As background, when constructing a complete object, virtual bases are
constructed first. If an exception is thrown later in the ctor, those
virtual bases are destroyed, so sema marks the relevant constructors and
destructors of virtual bases as referenced. If necessary, they are
emitted.

However, an abstract class can never be used to construct a complete
object. In the Itanium C++ ABI, this works out nicely, because we never
end up emitting the "complete" constructor variant, only the "base"
constructor variant, which can be called by constructors of derived
classes. Clang's Sema::MarkBaseAndMemberDestructorsReferenced is aware
of this optimization, and it does not mark ctors and dtors of virtual
bases referenced when the constructor of an abstract class is emitted.

In the Microsoft ABI, there are no complete/base variants, so before
this change, the constructor of an abstract class could reference ctors
and dtors of a virtual base without marking them referenced. This could
lead to unresolved symbol errors at link time, as reported in PR41065.

The fix is to implement the same optimization as Sema: If the class is
abstract, don't bother initializing its virtual bases. The "is this
class the most derived class" check in the constructor will never pass,
and the virtual base constructor calls are always dead. Skip them.

I think Richard noticed this missed optimization back in 2016 when he
was implementing inheriting constructors. I wasn't able to find any bugs
or email about it, though.

Fixes PR41065

Added:
    cfe/trunk/test/CodeGenCXX/msabi-ctor-abstract-vbase.cpp
Modified:
    cfe/trunk/lib/CodeGen/CGClass.cpp
    cfe/trunk/test/CodeGenCXX/inheriting-constructor.cpp

Modified: cfe/trunk/lib/CodeGen/CGClass.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/CodeGen/CGClass.cpp?rev=356425&r1=356424&r2=356425&view=diff
==============================================================================
--- cfe/trunk/lib/CodeGen/CGClass.cpp (original)
+++ cfe/trunk/lib/CodeGen/CGClass.cpp Mon Mar 18 15:41:50 2019
@@ -526,8 +526,7 @@ static bool BaseInitializerUsesThis(ASTC
 
 static void EmitBaseInitializer(CodeGenFunction &CGF,
                                 const CXXRecordDecl *ClassDecl,
-                                CXXCtorInitializer *BaseInit,
-                                CXXCtorType CtorType) {
+                                CXXCtorInitializer *BaseInit) {
   assert(BaseInit->isBaseInitializer() &&
          "Must have base initializer!");
 
@@ -539,10 +538,6 @@ static void EmitBaseInitializer(CodeGenF
 
   bool isBaseVirtual = BaseInit->isBaseVirtual();
 
-  // The base constructor doesn't construct virtual bases.
-  if (CtorType == Ctor_Base && isBaseVirtual)
-    return;
-
   // If the initializer for the base (other than the constructor
   // itself) accesses 'this' in any way, we need to initialize the
   // vtables.
@@ -1264,24 +1259,37 @@ void CodeGenFunction::EmitCtorPrologue(c
   CXXConstructorDecl::init_const_iterator B = CD->init_begin(),
                                           E = CD->init_end();
 
+  // Virtual base initializers first, if any. They aren't needed if:
+  // - This is a base ctor variant
+  // - There are no vbases
+  // - The class is abstract, so a complete object of it cannot be constructed
+  //
+  // The check for an abstract class is necessary because sema may not have
+  // marked virtual base destructors referenced.
+  bool ConstructVBases = CtorType != Ctor_Base &&
+                         ClassDecl->getNumVBases() != 0 &&
+                         !ClassDecl->isAbstract();
+
+  // In the Microsoft C++ ABI, there are no constructor variants. Instead, the
+  // constructor of a class with virtual bases takes an additional parameter to
+  // conditionally construct the virtual bases. Emit that check here.
   llvm::BasicBlock *BaseCtorContinueBB = nullptr;
-  if (ClassDecl->getNumVBases() &&
+  if (ConstructVBases &&
       !CGM.getTarget().getCXXABI().hasConstructorVariants()) {
-    // The ABIs that don't have constructor variants need to put a branch
-    // before the virtual base initialization code.
     BaseCtorContinueBB =
-      CGM.getCXXABI().EmitCtorCompleteObjectHandler(*this, ClassDecl);
+        CGM.getCXXABI().EmitCtorCompleteObjectHandler(*this, ClassDecl);
     assert(BaseCtorContinueBB);
   }
 
   llvm::Value *const OldThis = CXXThisValue;
-  // Virtual base initializers first.
   for (; B != E && (*B)->isBaseInitializer() && (*B)->isBaseVirtual(); B++) {
+    if (!ConstructVBases)
+      continue;
     if (CGM.getCodeGenOpts().StrictVTablePointers &&
         CGM.getCodeGenOpts().OptimizationLevel > 0 &&
         isInitializerOfDynamicClass(*B))
       CXXThisValue = Builder.CreateLaunderInvariantGroup(LoadCXXThis());
-    EmitBaseInitializer(*this, ClassDecl, *B, CtorType);
+    EmitBaseInitializer(*this, ClassDecl, *B);
   }
 
   if (BaseCtorContinueBB) {
@@ -1298,7 +1306,7 @@ void CodeGenFunction::EmitCtorPrologue(c
         CGM.getCodeGenOpts().OptimizationLevel > 0 &&
         isInitializerOfDynamicClass(*B))
       CXXThisValue = Builder.CreateLaunderInvariantGroup(LoadCXXThis());
-    EmitBaseInitializer(*this, ClassDecl, *B, CtorType);
+    EmitBaseInitializer(*this, ClassDecl, *B);
   }
 
   CXXThisValue = OldThis;

Modified: cfe/trunk/test/CodeGenCXX/inheriting-constructor.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CodeGenCXX/inheriting-constructor.cpp?rev=356425&r1=356424&r2=356425&view=diff
==============================================================================
--- cfe/trunk/test/CodeGenCXX/inheriting-constructor.cpp (original)
+++ cfe/trunk/test/CodeGenCXX/inheriting-constructor.cpp Mon Mar 18 15:41:50 2019
@@ -270,15 +270,6 @@ namespace inalloca_virt {
   // WIN32: call void @llvm.stackrestore(
   // WIN32: br
   //
-  // WIN32: store i32 0, i32* %[[IS_MOST_DERIVED_ADDR:.*]]
-  // WIN32: %[[IS_MOST_DERIVED:.*]] = load i32, i32* %[[IS_MOST_DERIVED_ADDR]]
-  // WIN32: %[[IS_MOST_DERIVED_i1:.*]] = icmp ne i32 %[[IS_MOST_DERIVED]], 0
-  // WIN32: br i1 %[[IS_MOST_DERIVED_i1]]
-  //
-  // Note: this block is unreachable.
-  // WIN32: store {{.*}} @"??_8B at inalloca_virt@@7B@"
-  // WIN32: br
-  //
   // WIN32: call {{.*}} @"??0Z@@QAE at XZ"(
   // WIN32: call {{.*}} @"??0Z@@QAE at XZ"(
   // WIN32: call {{.*}} @"??1Q@@QAE at XZ"(
@@ -294,11 +285,6 @@ namespace inalloca_virt {
   // WIN64: br i1
   // WIN64: store {{.*}} @"??_8C at inalloca_virt@@7B@"
   // WIN64: call {{.*}} @"??0A at inalloca_virt@@QEAA at UQ@@H0$$QEAU2@@Z"(%{{.*}}, %{{.*}}* %[[ARG1]], i32 2, %{{.*}}* %[[ARG3]], %{{.*}} %[[TMP]])
-  // WIN64: br
-  // WIN64: br i1
-  // (Unreachable block)
-  // WIN64: store {{.*}} @"??_8B at inalloca_virt@@7B@"
-  // WIN64: br
   // WIN64: call {{.*}} @"??0Z@@QEAA at XZ"(
   // WIN64: call {{.*}} @"??0Z@@QEAA at XZ"(
   // WIN64: call void @"??1Q@@QEAA at XZ"({{.*}}* %[[TMP]])

Added: cfe/trunk/test/CodeGenCXX/msabi-ctor-abstract-vbase.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CodeGenCXX/msabi-ctor-abstract-vbase.cpp?rev=356425&view=auto
==============================================================================
--- cfe/trunk/test/CodeGenCXX/msabi-ctor-abstract-vbase.cpp (added)
+++ cfe/trunk/test/CodeGenCXX/msabi-ctor-abstract-vbase.cpp Mon Mar 18 15:41:50 2019
@@ -0,0 +1,82 @@
+// RUN: %clang_cc1 -triple x86_64-windows-msvc %s -emit-llvm -fexceptions -o - | FileCheck %s
+
+// PR41065: As background, when constructing a complete object, virtual bases
+// are constructed first. If an exception is thrown while constructing a
+// subobject later, those virtual bases are destroyed, so sema references the
+// relevant constructors and destructors of every base class in case they are
+// needed.
+//
+// However, an abstract class can never be used to construct a complete object.
+// In the Itanium C++ ABI, this works out nicely, because we never end up
+// emitting the "complete" constructor variant, only the "base" constructor
+// variant, which can be called by constructors of derived classes. For various
+// reasons, Sema does not mark ctors and dtors of virtual bases referenced when
+// the constructor of an abstract class is emitted.
+//
+// In the Microsoft ABI, there are no complete/base variants, so before PR41065
+// was fixed, the constructor of an abstract class could reference special
+// members of a virtual base without marking them referenced. This could lead to
+// unresolved symbol errors at link time.
+//
+// The fix is to implement the same optimization as Sema: If the class is
+// abstract, don't bother initializing its virtual bases. The "is this class the
+// most derived class" check in the constructor will never pass, and the virtual
+// base constructor calls are always dead. Skip them.
+
+struct A {
+  A();
+  virtual void f() = 0;
+  virtual ~A();
+};
+
+// B has an implicit inline dtor, but is still abstract.
+struct B : A {
+  B(int n);
+  int n;
+};
+
+// Still abstract
+struct C : virtual B {
+  C(int n);
+  //void f() override;
+};
+
+// Not abstract, D::D calls C::C and B::B.
+struct D : C {
+  D(int n);
+  void f() override;
+};
+
+void may_throw();
+C::C(int n) : B(n) { may_throw(); }
+
+// No branches, no constructor calls before may_throw();
+//
+// CHECK-LABEL: define dso_local %struct.C* @"??0C@@QEAA at H@Z"(%struct.C* returned %this, i32 %n, i32 %is_most_derived)
+// CHECK-NOT: br i1
+// CHECK-NOT: {{call.*@"\?0}}
+// CHECK: call void @"?may_throw@@YAXXZ"()
+// no cleanups
+
+
+D::D(int n) : C(n), B(n) { may_throw(); }
+
+// Conditionally construct (and destroy) vbase B, unconditionally C.
+//
+// CHECK-LABEL: define dso_local %struct.D* @"??0D@@QEAA at H@Z"(%struct.D* returned %this, i32 %n, i32 %is_most_derived)
+// CHECK: icmp ne i32 {{.*}}, 0
+// CHECK: br i1
+// CHECK: call %struct.B* @"??0B@@QEAA at H@Z"
+// CHECK: br label
+// CHECK: invoke %struct.C* @"??0C@@QEAA at H@Z"
+// CHECK: invoke void @"?may_throw@@YAXXZ"()
+// CHECK: cleanuppad
+// CHECK: call void @"??1C@@UEAA at XZ"
+// CHECK: cleanupret
+//
+// CHECK: cleanuppad
+// CHECK: icmp ne i32 {{.*}}, 0
+// CHECK: br i1
+// CHECK: call void @"??1B@@UEAA at XZ"
+// CHECK: br label
+// CHECK: cleanupret




More information about the cfe-commits mailing list