Skip to content

Conversation

andykaylor
Copy link
Contributor

This adds support for declaring a class with a virtual base class and initializing the vptr in the constructor. This does not yet handle constructors that require a virtual table table (VTT) implicit argument.

This adds support for declaring a class with a virtual base class and
initializing the vptr in the constructor. This does not yet handle
constructors that require a virtual table table (VTT) implicit
argument.
@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Aug 27, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 27, 2025

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: Andy Kaylor (andykaylor)

Changes

This adds support for declaring a class with a virtual base class and initializing the vptr in the constructor. This does not yet handle constructors that require a virtual table table (VTT) implicit argument.


Patch is 26.14 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/155534.diff

8 Files Affected:

  • (modified) clang/lib/CIR/CodeGen/CIRGenCXXABI.h (+6)
  • (modified) clang/lib/CIR/CodeGen/CIRGenClass.cpp (+41-19)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+2-6)
  • (modified) clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp (+3-2)
  • (modified) clang/lib/CIR/CodeGen/CIRGenRecordLayout.h (+4)
  • (modified) clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp (+116-36)
  • (added) clang/test/CIR/CodeGen/vbase.cpp (+70)
  • (added) clang/test/CIR/CodeGen/vtt.cpp (+45)
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
index b5f2e1a067274..df7ffbb4a2759 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
@@ -37,6 +37,12 @@ class CIRGenCXXABI {
 
   void setCXXABIThisValue(CIRGenFunction &cgf, mlir::Value thisPtr);
 
+  /// Emit the code to initialize hidden members required to handle virtual
+  /// inheritance, if needed by the ABI.
+  virtual void
+  initializeHiddenVirtualInheritanceMembers(CIRGenFunction &cgf,
+                                            const CXXRecordDecl *rd) {}
+
   /// Emit a single constructor/destructor with the gen type from a C++
   /// constructor/destructor Decl.
   virtual void emitCXXStructor(clang::GlobalDecl gd) = 0;
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index 9106e5e4824ff..ac923635192aa 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -238,31 +238,44 @@ void CIRGenFunction::emitCtorPrologue(const CXXConstructorDecl *cd,
   bool constructVBases = ctorType != Ctor_Base &&
                          classDecl->getNumVBases() != 0 &&
                          !classDecl->isAbstract();
-  if (constructVBases) {
-    cgm.errorNYI(cd->getSourceRange(), "emitCtorPrologue: virtual base");
-    return;
-  }
-
-  const mlir::Value oldThisValue = cxxThisValue;
-  if (!constructVBases && b != e && (*b)->isBaseInitializer() &&
-      (*b)->isBaseVirtual()) {
+  if (constructVBases &&
+      !cgm.getTarget().getCXXABI().hasConstructorVariants()) {
     cgm.errorNYI(cd->getSourceRange(),
-                 "emitCtorPrologue: virtual base initializer");
+                 "emitCtorPrologue: virtual base without variants");
     return;
   }
 
-  // Handle non-virtual base initializers.
-  for (; b != e && (*b)->isBaseInitializer(); b++) {
-    assert(!(*b)->isBaseVirtual());
+  const mlir::Value oldThisValue = cxxThisValue;
 
+  // Initialize virtual bases.
+  auto emitInitializer = [&](CXXCtorInitializer *baseInit) {
     if (cgm.getCodeGenOpts().StrictVTablePointers &&
         cgm.getCodeGenOpts().OptimizationLevel > 0 &&
-        isInitializerOfDynamicClass(*b)) {
+        isInitializerOfDynamicClass(baseInit)) {
+      // It's OK to continue after emitting the error here. The missing code
+      // just "launders" the 'this' pointer.
       cgm.errorNYI(cd->getSourceRange(),
-                   "emitCtorPrologue: strict vtable pointers");
-      return;
+                   "emitCtorPrologue: strict vtable pointers for vbase");
     }
-    emitBaseInitializer(getLoc(cd->getBeginLoc()), classDecl, *b);
+    emitBaseInitializer(getLoc(cd->getBeginLoc()), classDecl, baseInit);
+  };
+
+  for (; b != e && (*b)->isBaseInitializer() && (*b)->isBaseVirtual(); b++) {
+    if (!constructVBases)
+      continue;
+    emitInitializer(*b);
+  }
+
+  // The loop above and the loop below could obviously be merged in their
+  // current form, but when we implement support for the MS C++ ABI, we will
+  // need to insert a branch after the last virtual base initializer, so
+  // separate loops will be useful then. The missing code is covered by the
+  // "virtual base without variants" diagnostic above.
+
+  // Handle non-virtual base initializers.
+  for (; b != e && (*b)->isBaseInitializer(); b++) {
+    assert(!(*b)->isBaseVirtual());
+    emitInitializer(*b);
   }
 
   cxxThisValue = oldThisValue;
@@ -370,7 +383,7 @@ void CIRGenFunction::initializeVTablePointers(mlir::Location loc,
       initializeVTablePointer(loc, vptr);
 
   if (rd->getNumVBases())
-    cgm.errorNYI(loc, "initializeVTablePointers: virtual base");
+    cgm.getCXXABI().initializeHiddenVirtualInheritanceMembers(*this, rd);
 }
 
 CIRGenFunction::VPtrsVector
@@ -418,8 +431,17 @@ void CIRGenFunction::getVTablePointers(BaseSubobject base,
     const CXXRecordDecl *nextBaseDecl;
 
     if (nextBase.isVirtual()) {
-      cgm.errorNYI(rd->getSourceRange(), "getVTablePointers: virtual base");
-      return;
+      // Check if we've visited this virtual base before.
+      if (!vbases.insert(baseDecl).second)
+        continue;
+
+      const ASTRecordLayout &layout =
+          getContext().getASTRecordLayout(vtableClass);
+
+      nextBaseDecl = nearestVBase;
+      baseOffset = layout.getVBaseClassOffset(baseDecl);
+      baseOffsetFromNearestVBase = CharUnits::Zero();
+      baseDeclIsNonVirtualPrimaryBase = false;
     } else {
       const ASTRecordLayout &layout = getContext().getASTRecordLayout(rd);
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 1afac6dd52c2d..469879371eb1d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -1972,12 +1972,8 @@ void CIRGenFunction::emitCXXConstructExpr(const CXXConstructExpr *e,
       delegating = true;
       break;
     case CXXConstructionKind::VirtualBase:
-      // This should just set 'forVirtualBase' to true and fall through, but
-      // virtual base class support is otherwise missing, so this needs to wait
-      // until it can be tested.
-      cgm.errorNYI(e->getSourceRange(),
-                   "emitCXXConstructExpr: virtual base constructor");
-      return;
+      forVirtualBase = true;
+      [[fallthrough]];
     case CXXConstructionKind::NonVirtualBase:
       type = Ctor_Base;
       break;
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index aaf7dc767d888..4fd5a278e1a99 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -487,9 +487,10 @@ mlir::Value CIRGenItaniumCXXABI::getVTableAddressPointInStructor(
     CIRGenFunction &cgf, const clang::CXXRecordDecl *vtableClass,
     clang::BaseSubobject base, const clang::CXXRecordDecl *nearestVBase) {
 
-  if (base.getBase()->getNumVBases()) {
+  if ((base.getBase()->getNumVBases() || nearestVBase != nullptr) &&
+      needsVTTParameter(cgf.curGD)) {
     cgm.errorNYI(cgf.curFuncDecl->getLocation(),
-                 "getVTableAddressPointInStructor: virtual base");
+                 "getVTableAddressPointInStructorWithVTT");
   }
   return getVTableAddressPoint(base, vtableClass);
 }
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
index b28afe42c39a0..914ef16c2a5ee 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
@@ -141,6 +141,10 @@ class CIRGenRecordLayout {
   // for both virtual and non-virtual bases.
   llvm::DenseMap<const clang::CXXRecordDecl *, unsigned> nonVirtualBases;
 
+  /// Map from virtual bases to their field index in the complete object.
+  llvm::DenseMap<const clang::CXXRecordDecl *, unsigned>
+      completeObjectVirtualBases;
+
   /// Map from (bit-field) record field to the corresponding CIR record type
   /// field no. This info is populated by record builder.
   llvm::DenseMap<const clang::FieldDecl *, CIRGenBitFieldInfo> bitFields;
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
index 1764967329969..b27d89707101e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -41,7 +41,7 @@ struct CIRRecordLowering final {
   // member type that ensures correct rounding.
   struct MemberInfo final {
     CharUnits offset;
-    enum class InfoKind { VFPtr, Field, Base } kind;
+    enum class InfoKind { VFPtr, Field, Base, VBase, Scissor } kind;
     mlir::Type data;
     union {
       const FieldDecl *fieldDecl;
@@ -71,17 +71,18 @@ struct CIRRecordLowering final {
   void setBitFieldInfo(const FieldDecl *fd, CharUnits startOffset,
                        mlir::Type storageType);
 
-  void lower();
+  void lower(bool NonVirtualBaseType);
   void lowerUnion();
 
   /// Determines if we need a packed llvm struct.
-  void determinePacked();
+  void determinePacked(bool nvBaseType);
   /// Inserts padding everywhere it's needed.
   void insertPadding();
 
   void computeVolatileBitfields();
-  void accumulateBases(const CXXRecordDecl *cxxRecordDecl);
+  void accumulateBases();
   void accumulateVPtrs();
+  void accumulateVBases();
   void accumulateFields();
   RecordDecl::field_iterator
   accumulateBitFields(RecordDecl::field_iterator field,
@@ -96,6 +97,17 @@ struct CIRRecordLowering final {
   /// Helper function to check if the target machine is BigEndian.
   bool isBigEndian() const { return astContext.getTargetInfo().isBigEndian(); }
 
+  // The Itanium base layout rule allows virtual bases to overlap
+  // other bases, which complicates layout in specific ways.
+  //
+  // Note specifically that the ms_struct attribute doesn't change this.
+  bool isOverlappingVBaseABI() {
+    return !astContext.getTargetInfo().getCXXABI().isMicrosoft();
+  }
+  // Recursively searches all of the bases to find out if a vbase is
+  // not the primary vbase of some base class.
+  bool hasOwnStorage(const CXXRecordDecl *decl, const CXXRecordDecl *query);
+
   CharUnits bitsToCharUnits(uint64_t bitOffset) {
     return astContext.toCharUnitsFromBits(bitOffset);
   }
@@ -184,6 +196,7 @@ struct CIRRecordLowering final {
   CIRGenBuilderTy &builder;
   const ASTContext &astContext;
   const RecordDecl *recordDecl;
+  const CXXRecordDecl *cxxRecordDecl;
   const ASTRecordLayout &astRecordLayout;
   // Helpful intermediate data-structures
   std::vector<MemberInfo> members;
@@ -192,6 +205,7 @@ struct CIRRecordLowering final {
   llvm::DenseMap<const FieldDecl *, CIRGenBitFieldInfo> bitFields;
   llvm::DenseMap<const FieldDecl *, unsigned> fieldIdxMap;
   llvm::DenseMap<const CXXRecordDecl *, unsigned> nonVirtualBases;
+  llvm::DenseMap<const CXXRecordDecl *, unsigned> virtualBases;
   cir::CIRDataLayout dataLayout;
 
   LLVM_PREFERRED_TYPE(bool)
@@ -211,13 +225,14 @@ struct CIRRecordLowering final {
 
 CIRRecordLowering::CIRRecordLowering(CIRGenTypes &cirGenTypes,
                                      const RecordDecl *recordDecl, bool packed)
-    : cirGenTypes(cirGenTypes), builder(cirGenTypes.getBuilder()),
-      astContext(cirGenTypes.getASTContext()), recordDecl(recordDecl),
-      astRecordLayout(
-          cirGenTypes.getASTContext().getASTRecordLayout(recordDecl)),
-      dataLayout(cirGenTypes.getCGModule().getModule()),
-      zeroInitializable(true), zeroInitializableAsBase(true), packed(packed),
-      padded(false) {}
+    : cirGenTypes{cirGenTypes}, builder{cirGenTypes.getBuilder()},
+      astContext{cirGenTypes.getASTContext()}, recordDecl{recordDecl},
+      cxxRecordDecl{llvm::dyn_cast<CXXRecordDecl>(recordDecl)},
+      astRecordLayout{
+          cirGenTypes.getASTContext().getASTRecordLayout(recordDecl)},
+      dataLayout{cirGenTypes.getCGModule().getModule()},
+      zeroInitializable{true}, zeroInitializableAsBase{true}, packed{packed},
+      padded{false} {}
 
 void CIRRecordLowering::setBitFieldInfo(const FieldDecl *fd,
                                         CharUnits startOffset,
@@ -246,27 +261,28 @@ void CIRRecordLowering::setBitFieldInfo(const FieldDecl *fd,
   info.volatileStorageOffset = CharUnits::Zero();
 }
 
-void CIRRecordLowering::lower() {
+void CIRRecordLowering::lower(bool nonVirtualBaseType) {
   if (recordDecl->isUnion()) {
     lowerUnion();
     computeVolatileBitfields();
     return;
   }
 
-  assert(!cir::MissingFeatures::recordLayoutVirtualBases());
-  CharUnits size = astRecordLayout.getSize();
+  CharUnits size = nonVirtualBaseType ? astRecordLayout.getNonVirtualSize()
+                                      : astRecordLayout.getSize();
 
   accumulateFields();
 
-  if (const auto *cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl)) {
+  if (cxxRecordDecl) {
     accumulateVPtrs();
-    accumulateBases(cxxRecordDecl);
+    accumulateBases();
     if (members.empty()) {
       appendPaddingBytes(size);
       computeVolatileBitfields();
       return;
     }
-    assert(!cir::MissingFeatures::recordLayoutVirtualBases());
+    if (!nonVirtualBaseType)
+      accumulateVBases();
   }
 
   llvm::stable_sort(members);
@@ -275,7 +291,7 @@ void CIRRecordLowering::lower() {
   assert(!cir::MissingFeatures::recordZeroInit());
 
   members.push_back(makeStorageInfo(size, getUIntNType(8)));
-  determinePacked();
+  determinePacked(nonVirtualBaseType);
   insertPadding();
   members.pop_back();
 
@@ -298,8 +314,9 @@ void CIRRecordLowering::fillOutputFields() {
         setBitFieldInfo(member.fieldDecl, member.offset, fieldTypes.back());
     } else if (member.kind == MemberInfo::InfoKind::Base) {
       nonVirtualBases[member.cxxRecordDecl] = fieldTypes.size() - 1;
+    } else if (member.kind == MemberInfo::InfoKind::VBase) {
+      virtualBases[member.cxxRecordDecl] = fieldTypes.size() - 1;
     }
-    assert(!cir::MissingFeatures::recordLayoutVirtualBases());
   }
 }
 
@@ -426,8 +443,9 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field,
               limitOffset = bitsToCharUnits(getFieldBitOffset(*probe));
               goto FoundLimit;
             }
-          assert(!cir::MissingFeatures::cxxSupport());
-          limitOffset = astRecordLayout.getDataSize();
+          limitOffset = cxxRecordDecl ? astRecordLayout.getNonVirtualSize()
+                                      : astRecordLayout.getDataSize();
+
         FoundLimit:
           CharUnits typeSize = getSize(type);
           if (beginOffset + typeSize <= limitOffset) {
@@ -524,24 +542,25 @@ void CIRRecordLowering::calculateZeroInit() {
         continue;
       zeroInitializable = zeroInitializableAsBase = false;
       return;
-    } else if (member.kind == MemberInfo::InfoKind::Base) {
+    } else if (member.kind == MemberInfo::InfoKind::Base ||
+               member.kind == MemberInfo::InfoKind::VBase) {
       if (isZeroInitializable(member.cxxRecordDecl))
         continue;
       zeroInitializable = false;
       if (member.kind == MemberInfo::InfoKind::Base)
         zeroInitializableAsBase = false;
     }
-    assert(!cir::MissingFeatures::recordLayoutVirtualBases());
   }
 }
 
-void CIRRecordLowering::determinePacked() {
+void CIRRecordLowering::determinePacked(bool nvBaseType) {
   if (packed)
     return;
   CharUnits alignment = CharUnits::One();
-
-  // TODO(cir): handle non-virtual base types
-  assert(!cir::MissingFeatures::cxxSupport());
+  CharUnits nvAlignment = CharUnits::One();
+  CharUnits nvSize = !nvBaseType && cxxRecordDecl
+                         ? astRecordLayout.getNonVirtualSize()
+                         : CharUnits::Zero();
 
   for (const MemberInfo &member : members) {
     if (!member.data)
@@ -550,12 +569,19 @@ void CIRRecordLowering::determinePacked() {
     // then the entire record must be packed.
     if (member.offset % getAlignment(member.data))
       packed = true;
+    if (member.offset < nvSize)
+      nvAlignment = std::max(nvAlignment, getAlignment(member.data));
     alignment = std::max(alignment, getAlignment(member.data));
   }
   // If the size of the record (the capstone's offset) is not a multiple of the
   // record's alignment, it must be packed.
   if (members.back().offset % alignment)
     packed = true;
+  // If the non-virtual sub-object is not a multiple of the non-virtual
+  // sub-object's alignment, it must be packed.  We cannot have a packed
+  // non-virtual sub-object and an unpacked complete object or vise versa.
+  if (nvSize % nvAlignment)
+    packed = true;
   // Update the alignment of the sentinel.
   if (!packed)
     members.back().data = getUIntNType(astContext.toBits(alignment));
@@ -589,7 +615,7 @@ std::unique_ptr<CIRGenRecordLayout>
 CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
   CIRRecordLowering lowering(*this, rd, /*packed=*/false);
   assert(ty->isIncomplete() && "recomputing record layout?");
-  lowering.lower();
+  lowering.lower(/*nonVirtualBaseType=*/false);
 
   // If we're in C++, compute the base subobject type.
   cir::RecordType baseTy;
@@ -599,7 +625,7 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
     if (lowering.astRecordLayout.getNonVirtualSize() !=
         lowering.astRecordLayout.getSize()) {
       CIRRecordLowering baseLowering(*this, rd, /*Packed=*/lowering.packed);
-      baseLowering.lower();
+      baseLowering.lower(/*NonVirtualBaseType=*/true);
       std::string baseIdentifier = getRecordTypeName(rd, ".base");
       baseTy =
           builder.getCompleteRecordTy(baseLowering.fieldTypes, baseIdentifier,
@@ -626,8 +652,8 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
   assert(!cir::MissingFeatures::recordZeroInit());
 
   rl->nonVirtualBases.swap(lowering.nonVirtualBases);
+  rl->completeObjectVirtualBases.swap(lowering.virtualBases);
 
-  assert(!cir::MissingFeatures::cxxSupport());
   assert(!cir::MissingFeatures::bitfields());
 
   // Add all the field numbers.
@@ -754,6 +780,17 @@ void CIRRecordLowering::lowerUnion() {
     packed = true;
 }
 
+bool CIRRecordLowering::hasOwnStorage(const CXXRecordDecl *decl,
+                                      const CXXRecordDecl *query) {
+  const ASTRecordLayout &declLayout = astContext.getASTRecordLayout(decl);
+  if (declLayout.isPrimaryBaseVirtual() && declLayout.getPrimaryBase() == query)
+    return false;
+  for (const auto &base : decl->bases())
+    if (!hasOwnStorage(base.getType()->getAsCXXRecordDecl(), query))
+      return false;
+  return true;
+}
+
 /// The AAPCS that defines that, when possible, bit-fields should
 /// be accessed using containers of the declared type width:
 /// When a volatile bit-field is read, and its container does not overlap with
@@ -873,7 +910,7 @@ void CIRRecordLowering::computeVolatileBitfields() {
   }
 }
 
-void CIRRecordLowering::accumulateBases(const CXXRecordDecl *cxxRecordDecl) {
+void CIRRecordLowering::accumulateBases() {
   // If we've got a primary virtual base, we need to add it with the bases.
   if (astRecordLayout.isPrimaryBaseVirtual()) {
     cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
@@ -881,12 +918,9 @@ void CIRRecordLowering::accumulateBases(const CXXRecordDecl *cxxRecordDecl) {
   }
 
   // Accumulate the non-virtual bases.
-  for ([[maybe_unused]] const auto &base : cxxRecordDecl->bases()) {
-    if (base.isVirtual()) {
-      cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
-                                         "accumulateBases: virtual base");
+  for (const auto &base : cxxRecordDecl->bases()) {
+    if (base.isVirtual())
       continue;
-    }
     // Bases can be zero-sized even if not technically empty if they
     // contain only a trailing array member.
     const CXXRecordDecl *baseDecl = base.getType()->getAsCXXRecordDecl();
@@ -899,6 +933,52 @@ void CIRRecordLowering::accumulateBases(const CXXRecordDecl *cxxRecordDecl) {
   }
 }
 
+void CIRRecordLowering::accumulateVBases() {
+  CharUnits scissorOffset = astRecordLayout.getNonVirtualSize();
+  // In the itanium ABI, it's possible to place a vbase at a dsize that is
+  // smaller than the nvsize.  Here we check to see if such a base is placed
+  // before the nvsize and set the scissor offset to that, instead of the
+  // nvsize.
+  if (isOverlappingVBaseABI()) {
+    for (const auto &base : cxxRecordDecl->vbases()) {
+      const CXXRecordDecl *baseDecl = base.getType()->getAsCXXRecordDecl();
+      if (baseDecl->isEmpty())
+        continue;
+      // If the vbase is a primary virtual base of some base, then it doesn't
+      // get its own storage location but instead lives inside of that base.
+      if (astContext.isNearlyEmpty(baseDecl) &&
+          !hasOwnStorage(cxxRecordDecl, baseDecl))
+        continue;
+      scissorOffset = std::min(scissorOffset,
+                               astRecordLayout.getVBaseClassOffset(baseDecl));
+    }
+  }
+  members.push_back(MemberInfo(scissorOffset, MemberInfo::InfoKind::Scissor,
+                               mlir::Type{}, cxxRecordDecl));
+  for (const auto &base : cxxRecordDecl->vbases()) {
+    const CXXRecordDecl *baseDecl = base.getType()->getAsCXXRecordDecl();
+    if (baseDecl->isEmpty())
+      continue;
+    CharUnits offset = astRecordLayout.getVBaseClassOffset(baseDecl);
+    // If the vbase is a primary virtual base of some base, the...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Aug 27, 2025

@llvm/pr-subscribers-clangir

Author: Andy Kaylor (andykaylor)

Changes

This adds support for declaring a class with a virtual base class and initializing the vptr in the constructor. This does not yet handle constructors that require a virtual table table (VTT) implicit argument.


Patch is 26.14 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/155534.diff

8 Files Affected:

  • (modified) clang/lib/CIR/CodeGen/CIRGenCXXABI.h (+6)
  • (modified) clang/lib/CIR/CodeGen/CIRGenClass.cpp (+41-19)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+2-6)
  • (modified) clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp (+3-2)
  • (modified) clang/lib/CIR/CodeGen/CIRGenRecordLayout.h (+4)
  • (modified) clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp (+116-36)
  • (added) clang/test/CIR/CodeGen/vbase.cpp (+70)
  • (added) clang/test/CIR/CodeGen/vtt.cpp (+45)
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
index b5f2e1a067274..df7ffbb4a2759 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
@@ -37,6 +37,12 @@ class CIRGenCXXABI {
 
   void setCXXABIThisValue(CIRGenFunction &cgf, mlir::Value thisPtr);
 
+  /// Emit the code to initialize hidden members required to handle virtual
+  /// inheritance, if needed by the ABI.
+  virtual void
+  initializeHiddenVirtualInheritanceMembers(CIRGenFunction &cgf,
+                                            const CXXRecordDecl *rd) {}
+
   /// Emit a single constructor/destructor with the gen type from a C++
   /// constructor/destructor Decl.
   virtual void emitCXXStructor(clang::GlobalDecl gd) = 0;
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index 9106e5e4824ff..ac923635192aa 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -238,31 +238,44 @@ void CIRGenFunction::emitCtorPrologue(const CXXConstructorDecl *cd,
   bool constructVBases = ctorType != Ctor_Base &&
                          classDecl->getNumVBases() != 0 &&
                          !classDecl->isAbstract();
-  if (constructVBases) {
-    cgm.errorNYI(cd->getSourceRange(), "emitCtorPrologue: virtual base");
-    return;
-  }
-
-  const mlir::Value oldThisValue = cxxThisValue;
-  if (!constructVBases && b != e && (*b)->isBaseInitializer() &&
-      (*b)->isBaseVirtual()) {
+  if (constructVBases &&
+      !cgm.getTarget().getCXXABI().hasConstructorVariants()) {
     cgm.errorNYI(cd->getSourceRange(),
-                 "emitCtorPrologue: virtual base initializer");
+                 "emitCtorPrologue: virtual base without variants");
     return;
   }
 
-  // Handle non-virtual base initializers.
-  for (; b != e && (*b)->isBaseInitializer(); b++) {
-    assert(!(*b)->isBaseVirtual());
+  const mlir::Value oldThisValue = cxxThisValue;
 
+  // Initialize virtual bases.
+  auto emitInitializer = [&](CXXCtorInitializer *baseInit) {
     if (cgm.getCodeGenOpts().StrictVTablePointers &&
         cgm.getCodeGenOpts().OptimizationLevel > 0 &&
-        isInitializerOfDynamicClass(*b)) {
+        isInitializerOfDynamicClass(baseInit)) {
+      // It's OK to continue after emitting the error here. The missing code
+      // just "launders" the 'this' pointer.
       cgm.errorNYI(cd->getSourceRange(),
-                   "emitCtorPrologue: strict vtable pointers");
-      return;
+                   "emitCtorPrologue: strict vtable pointers for vbase");
     }
-    emitBaseInitializer(getLoc(cd->getBeginLoc()), classDecl, *b);
+    emitBaseInitializer(getLoc(cd->getBeginLoc()), classDecl, baseInit);
+  };
+
+  for (; b != e && (*b)->isBaseInitializer() && (*b)->isBaseVirtual(); b++) {
+    if (!constructVBases)
+      continue;
+    emitInitializer(*b);
+  }
+
+  // The loop above and the loop below could obviously be merged in their
+  // current form, but when we implement support for the MS C++ ABI, we will
+  // need to insert a branch after the last virtual base initializer, so
+  // separate loops will be useful then. The missing code is covered by the
+  // "virtual base without variants" diagnostic above.
+
+  // Handle non-virtual base initializers.
+  for (; b != e && (*b)->isBaseInitializer(); b++) {
+    assert(!(*b)->isBaseVirtual());
+    emitInitializer(*b);
   }
 
   cxxThisValue = oldThisValue;
@@ -370,7 +383,7 @@ void CIRGenFunction::initializeVTablePointers(mlir::Location loc,
       initializeVTablePointer(loc, vptr);
 
   if (rd->getNumVBases())
-    cgm.errorNYI(loc, "initializeVTablePointers: virtual base");
+    cgm.getCXXABI().initializeHiddenVirtualInheritanceMembers(*this, rd);
 }
 
 CIRGenFunction::VPtrsVector
@@ -418,8 +431,17 @@ void CIRGenFunction::getVTablePointers(BaseSubobject base,
     const CXXRecordDecl *nextBaseDecl;
 
     if (nextBase.isVirtual()) {
-      cgm.errorNYI(rd->getSourceRange(), "getVTablePointers: virtual base");
-      return;
+      // Check if we've visited this virtual base before.
+      if (!vbases.insert(baseDecl).second)
+        continue;
+
+      const ASTRecordLayout &layout =
+          getContext().getASTRecordLayout(vtableClass);
+
+      nextBaseDecl = nearestVBase;
+      baseOffset = layout.getVBaseClassOffset(baseDecl);
+      baseOffsetFromNearestVBase = CharUnits::Zero();
+      baseDeclIsNonVirtualPrimaryBase = false;
     } else {
       const ASTRecordLayout &layout = getContext().getASTRecordLayout(rd);
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 1afac6dd52c2d..469879371eb1d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -1972,12 +1972,8 @@ void CIRGenFunction::emitCXXConstructExpr(const CXXConstructExpr *e,
       delegating = true;
       break;
     case CXXConstructionKind::VirtualBase:
-      // This should just set 'forVirtualBase' to true and fall through, but
-      // virtual base class support is otherwise missing, so this needs to wait
-      // until it can be tested.
-      cgm.errorNYI(e->getSourceRange(),
-                   "emitCXXConstructExpr: virtual base constructor");
-      return;
+      forVirtualBase = true;
+      [[fallthrough]];
     case CXXConstructionKind::NonVirtualBase:
       type = Ctor_Base;
       break;
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index aaf7dc767d888..4fd5a278e1a99 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -487,9 +487,10 @@ mlir::Value CIRGenItaniumCXXABI::getVTableAddressPointInStructor(
     CIRGenFunction &cgf, const clang::CXXRecordDecl *vtableClass,
     clang::BaseSubobject base, const clang::CXXRecordDecl *nearestVBase) {
 
-  if (base.getBase()->getNumVBases()) {
+  if ((base.getBase()->getNumVBases() || nearestVBase != nullptr) &&
+      needsVTTParameter(cgf.curGD)) {
     cgm.errorNYI(cgf.curFuncDecl->getLocation(),
-                 "getVTableAddressPointInStructor: virtual base");
+                 "getVTableAddressPointInStructorWithVTT");
   }
   return getVTableAddressPoint(base, vtableClass);
 }
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
index b28afe42c39a0..914ef16c2a5ee 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
@@ -141,6 +141,10 @@ class CIRGenRecordLayout {
   // for both virtual and non-virtual bases.
   llvm::DenseMap<const clang::CXXRecordDecl *, unsigned> nonVirtualBases;
 
+  /// Map from virtual bases to their field index in the complete object.
+  llvm::DenseMap<const clang::CXXRecordDecl *, unsigned>
+      completeObjectVirtualBases;
+
   /// Map from (bit-field) record field to the corresponding CIR record type
   /// field no. This info is populated by record builder.
   llvm::DenseMap<const clang::FieldDecl *, CIRGenBitFieldInfo> bitFields;
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
index 1764967329969..b27d89707101e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -41,7 +41,7 @@ struct CIRRecordLowering final {
   // member type that ensures correct rounding.
   struct MemberInfo final {
     CharUnits offset;
-    enum class InfoKind { VFPtr, Field, Base } kind;
+    enum class InfoKind { VFPtr, Field, Base, VBase, Scissor } kind;
     mlir::Type data;
     union {
       const FieldDecl *fieldDecl;
@@ -71,17 +71,18 @@ struct CIRRecordLowering final {
   void setBitFieldInfo(const FieldDecl *fd, CharUnits startOffset,
                        mlir::Type storageType);
 
-  void lower();
+  void lower(bool NonVirtualBaseType);
   void lowerUnion();
 
   /// Determines if we need a packed llvm struct.
-  void determinePacked();
+  void determinePacked(bool nvBaseType);
   /// Inserts padding everywhere it's needed.
   void insertPadding();
 
   void computeVolatileBitfields();
-  void accumulateBases(const CXXRecordDecl *cxxRecordDecl);
+  void accumulateBases();
   void accumulateVPtrs();
+  void accumulateVBases();
   void accumulateFields();
   RecordDecl::field_iterator
   accumulateBitFields(RecordDecl::field_iterator field,
@@ -96,6 +97,17 @@ struct CIRRecordLowering final {
   /// Helper function to check if the target machine is BigEndian.
   bool isBigEndian() const { return astContext.getTargetInfo().isBigEndian(); }
 
+  // The Itanium base layout rule allows virtual bases to overlap
+  // other bases, which complicates layout in specific ways.
+  //
+  // Note specifically that the ms_struct attribute doesn't change this.
+  bool isOverlappingVBaseABI() {
+    return !astContext.getTargetInfo().getCXXABI().isMicrosoft();
+  }
+  // Recursively searches all of the bases to find out if a vbase is
+  // not the primary vbase of some base class.
+  bool hasOwnStorage(const CXXRecordDecl *decl, const CXXRecordDecl *query);
+
   CharUnits bitsToCharUnits(uint64_t bitOffset) {
     return astContext.toCharUnitsFromBits(bitOffset);
   }
@@ -184,6 +196,7 @@ struct CIRRecordLowering final {
   CIRGenBuilderTy &builder;
   const ASTContext &astContext;
   const RecordDecl *recordDecl;
+  const CXXRecordDecl *cxxRecordDecl;
   const ASTRecordLayout &astRecordLayout;
   // Helpful intermediate data-structures
   std::vector<MemberInfo> members;
@@ -192,6 +205,7 @@ struct CIRRecordLowering final {
   llvm::DenseMap<const FieldDecl *, CIRGenBitFieldInfo> bitFields;
   llvm::DenseMap<const FieldDecl *, unsigned> fieldIdxMap;
   llvm::DenseMap<const CXXRecordDecl *, unsigned> nonVirtualBases;
+  llvm::DenseMap<const CXXRecordDecl *, unsigned> virtualBases;
   cir::CIRDataLayout dataLayout;
 
   LLVM_PREFERRED_TYPE(bool)
@@ -211,13 +225,14 @@ struct CIRRecordLowering final {
 
 CIRRecordLowering::CIRRecordLowering(CIRGenTypes &cirGenTypes,
                                      const RecordDecl *recordDecl, bool packed)
-    : cirGenTypes(cirGenTypes), builder(cirGenTypes.getBuilder()),
-      astContext(cirGenTypes.getASTContext()), recordDecl(recordDecl),
-      astRecordLayout(
-          cirGenTypes.getASTContext().getASTRecordLayout(recordDecl)),
-      dataLayout(cirGenTypes.getCGModule().getModule()),
-      zeroInitializable(true), zeroInitializableAsBase(true), packed(packed),
-      padded(false) {}
+    : cirGenTypes{cirGenTypes}, builder{cirGenTypes.getBuilder()},
+      astContext{cirGenTypes.getASTContext()}, recordDecl{recordDecl},
+      cxxRecordDecl{llvm::dyn_cast<CXXRecordDecl>(recordDecl)},
+      astRecordLayout{
+          cirGenTypes.getASTContext().getASTRecordLayout(recordDecl)},
+      dataLayout{cirGenTypes.getCGModule().getModule()},
+      zeroInitializable{true}, zeroInitializableAsBase{true}, packed{packed},
+      padded{false} {}
 
 void CIRRecordLowering::setBitFieldInfo(const FieldDecl *fd,
                                         CharUnits startOffset,
@@ -246,27 +261,28 @@ void CIRRecordLowering::setBitFieldInfo(const FieldDecl *fd,
   info.volatileStorageOffset = CharUnits::Zero();
 }
 
-void CIRRecordLowering::lower() {
+void CIRRecordLowering::lower(bool nonVirtualBaseType) {
   if (recordDecl->isUnion()) {
     lowerUnion();
     computeVolatileBitfields();
     return;
   }
 
-  assert(!cir::MissingFeatures::recordLayoutVirtualBases());
-  CharUnits size = astRecordLayout.getSize();
+  CharUnits size = nonVirtualBaseType ? astRecordLayout.getNonVirtualSize()
+                                      : astRecordLayout.getSize();
 
   accumulateFields();
 
-  if (const auto *cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl)) {
+  if (cxxRecordDecl) {
     accumulateVPtrs();
-    accumulateBases(cxxRecordDecl);
+    accumulateBases();
     if (members.empty()) {
       appendPaddingBytes(size);
       computeVolatileBitfields();
       return;
     }
-    assert(!cir::MissingFeatures::recordLayoutVirtualBases());
+    if (!nonVirtualBaseType)
+      accumulateVBases();
   }
 
   llvm::stable_sort(members);
@@ -275,7 +291,7 @@ void CIRRecordLowering::lower() {
   assert(!cir::MissingFeatures::recordZeroInit());
 
   members.push_back(makeStorageInfo(size, getUIntNType(8)));
-  determinePacked();
+  determinePacked(nonVirtualBaseType);
   insertPadding();
   members.pop_back();
 
@@ -298,8 +314,9 @@ void CIRRecordLowering::fillOutputFields() {
         setBitFieldInfo(member.fieldDecl, member.offset, fieldTypes.back());
     } else if (member.kind == MemberInfo::InfoKind::Base) {
       nonVirtualBases[member.cxxRecordDecl] = fieldTypes.size() - 1;
+    } else if (member.kind == MemberInfo::InfoKind::VBase) {
+      virtualBases[member.cxxRecordDecl] = fieldTypes.size() - 1;
     }
-    assert(!cir::MissingFeatures::recordLayoutVirtualBases());
   }
 }
 
@@ -426,8 +443,9 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field,
               limitOffset = bitsToCharUnits(getFieldBitOffset(*probe));
               goto FoundLimit;
             }
-          assert(!cir::MissingFeatures::cxxSupport());
-          limitOffset = astRecordLayout.getDataSize();
+          limitOffset = cxxRecordDecl ? astRecordLayout.getNonVirtualSize()
+                                      : astRecordLayout.getDataSize();
+
         FoundLimit:
           CharUnits typeSize = getSize(type);
           if (beginOffset + typeSize <= limitOffset) {
@@ -524,24 +542,25 @@ void CIRRecordLowering::calculateZeroInit() {
         continue;
       zeroInitializable = zeroInitializableAsBase = false;
       return;
-    } else if (member.kind == MemberInfo::InfoKind::Base) {
+    } else if (member.kind == MemberInfo::InfoKind::Base ||
+               member.kind == MemberInfo::InfoKind::VBase) {
       if (isZeroInitializable(member.cxxRecordDecl))
         continue;
       zeroInitializable = false;
       if (member.kind == MemberInfo::InfoKind::Base)
         zeroInitializableAsBase = false;
     }
-    assert(!cir::MissingFeatures::recordLayoutVirtualBases());
   }
 }
 
-void CIRRecordLowering::determinePacked() {
+void CIRRecordLowering::determinePacked(bool nvBaseType) {
   if (packed)
     return;
   CharUnits alignment = CharUnits::One();
-
-  // TODO(cir): handle non-virtual base types
-  assert(!cir::MissingFeatures::cxxSupport());
+  CharUnits nvAlignment = CharUnits::One();
+  CharUnits nvSize = !nvBaseType && cxxRecordDecl
+                         ? astRecordLayout.getNonVirtualSize()
+                         : CharUnits::Zero();
 
   for (const MemberInfo &member : members) {
     if (!member.data)
@@ -550,12 +569,19 @@ void CIRRecordLowering::determinePacked() {
     // then the entire record must be packed.
     if (member.offset % getAlignment(member.data))
       packed = true;
+    if (member.offset < nvSize)
+      nvAlignment = std::max(nvAlignment, getAlignment(member.data));
     alignment = std::max(alignment, getAlignment(member.data));
   }
   // If the size of the record (the capstone's offset) is not a multiple of the
   // record's alignment, it must be packed.
   if (members.back().offset % alignment)
     packed = true;
+  // If the non-virtual sub-object is not a multiple of the non-virtual
+  // sub-object's alignment, it must be packed.  We cannot have a packed
+  // non-virtual sub-object and an unpacked complete object or vise versa.
+  if (nvSize % nvAlignment)
+    packed = true;
   // Update the alignment of the sentinel.
   if (!packed)
     members.back().data = getUIntNType(astContext.toBits(alignment));
@@ -589,7 +615,7 @@ std::unique_ptr<CIRGenRecordLayout>
 CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
   CIRRecordLowering lowering(*this, rd, /*packed=*/false);
   assert(ty->isIncomplete() && "recomputing record layout?");
-  lowering.lower();
+  lowering.lower(/*nonVirtualBaseType=*/false);
 
   // If we're in C++, compute the base subobject type.
   cir::RecordType baseTy;
@@ -599,7 +625,7 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
     if (lowering.astRecordLayout.getNonVirtualSize() !=
         lowering.astRecordLayout.getSize()) {
       CIRRecordLowering baseLowering(*this, rd, /*Packed=*/lowering.packed);
-      baseLowering.lower();
+      baseLowering.lower(/*NonVirtualBaseType=*/true);
       std::string baseIdentifier = getRecordTypeName(rd, ".base");
       baseTy =
           builder.getCompleteRecordTy(baseLowering.fieldTypes, baseIdentifier,
@@ -626,8 +652,8 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
   assert(!cir::MissingFeatures::recordZeroInit());
 
   rl->nonVirtualBases.swap(lowering.nonVirtualBases);
+  rl->completeObjectVirtualBases.swap(lowering.virtualBases);
 
-  assert(!cir::MissingFeatures::cxxSupport());
   assert(!cir::MissingFeatures::bitfields());
 
   // Add all the field numbers.
@@ -754,6 +780,17 @@ void CIRRecordLowering::lowerUnion() {
     packed = true;
 }
 
+bool CIRRecordLowering::hasOwnStorage(const CXXRecordDecl *decl,
+                                      const CXXRecordDecl *query) {
+  const ASTRecordLayout &declLayout = astContext.getASTRecordLayout(decl);
+  if (declLayout.isPrimaryBaseVirtual() && declLayout.getPrimaryBase() == query)
+    return false;
+  for (const auto &base : decl->bases())
+    if (!hasOwnStorage(base.getType()->getAsCXXRecordDecl(), query))
+      return false;
+  return true;
+}
+
 /// The AAPCS that defines that, when possible, bit-fields should
 /// be accessed using containers of the declared type width:
 /// When a volatile bit-field is read, and its container does not overlap with
@@ -873,7 +910,7 @@ void CIRRecordLowering::computeVolatileBitfields() {
   }
 }
 
-void CIRRecordLowering::accumulateBases(const CXXRecordDecl *cxxRecordDecl) {
+void CIRRecordLowering::accumulateBases() {
   // If we've got a primary virtual base, we need to add it with the bases.
   if (astRecordLayout.isPrimaryBaseVirtual()) {
     cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
@@ -881,12 +918,9 @@ void CIRRecordLowering::accumulateBases(const CXXRecordDecl *cxxRecordDecl) {
   }
 
   // Accumulate the non-virtual bases.
-  for ([[maybe_unused]] const auto &base : cxxRecordDecl->bases()) {
-    if (base.isVirtual()) {
-      cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
-                                         "accumulateBases: virtual base");
+  for (const auto &base : cxxRecordDecl->bases()) {
+    if (base.isVirtual())
       continue;
-    }
     // Bases can be zero-sized even if not technically empty if they
     // contain only a trailing array member.
     const CXXRecordDecl *baseDecl = base.getType()->getAsCXXRecordDecl();
@@ -899,6 +933,52 @@ void CIRRecordLowering::accumulateBases(const CXXRecordDecl *cxxRecordDecl) {
   }
 }
 
+void CIRRecordLowering::accumulateVBases() {
+  CharUnits scissorOffset = astRecordLayout.getNonVirtualSize();
+  // In the itanium ABI, it's possible to place a vbase at a dsize that is
+  // smaller than the nvsize.  Here we check to see if such a base is placed
+  // before the nvsize and set the scissor offset to that, instead of the
+  // nvsize.
+  if (isOverlappingVBaseABI()) {
+    for (const auto &base : cxxRecordDecl->vbases()) {
+      const CXXRecordDecl *baseDecl = base.getType()->getAsCXXRecordDecl();
+      if (baseDecl->isEmpty())
+        continue;
+      // If the vbase is a primary virtual base of some base, then it doesn't
+      // get its own storage location but instead lives inside of that base.
+      if (astContext.isNearlyEmpty(baseDecl) &&
+          !hasOwnStorage(cxxRecordDecl, baseDecl))
+        continue;
+      scissorOffset = std::min(scissorOffset,
+                               astRecordLayout.getVBaseClassOffset(baseDecl));
+    }
+  }
+  members.push_back(MemberInfo(scissorOffset, MemberInfo::InfoKind::Scissor,
+                               mlir::Type{}, cxxRecordDecl));
+  for (const auto &base : cxxRecordDecl->vbases()) {
+    const CXXRecordDecl *baseDecl = base.getType()->getAsCXXRecordDecl();
+    if (baseDecl->isEmpty())
+      continue;
+    CharUnits offset = astRecordLayout.getVBaseClassOffset(baseDecl);
+    // If the vbase is a primary virtual base of some base, the...
[truncated]

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 small comments, else this lgtm.

};

for (; b != e && (*b)->isBaseInitializer() && (*b)->isBaseVirtual(); b++) {
if (!constructVBases)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this inside the loop rather than preventing this loop from happening at all?

Also, is there any sort of range loop we can use here? Thisis awful ugly...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is inside the loop because if we are not constructing vbases we need to update the iterator to move past them. The initializers are ordered such that all virtual bases occur before the first non-virtual base. This loop is necessary to position the iterator for the loop below.

The MS CXXABI throws a bit of a wrench into the possibility of a range loop, because we need to insert a branch to a continue block between the virtual initializers and the non-virtual initializers, but we can probably use a flag to check for the conditions to do that. I'll play around with this loop in OGCG and see if I can simplify it without breaking anything.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right! I missed the same iterators. Anything you can do to make this less awful would be appreciated :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm proceeding with the implementation change modeled after the classic codegen change proposed in #155668. If that ends up being rejected, I'll update this accordingly.

Copy link
Contributor

@Andres-Salamanca Andres-Salamanca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just one question:

@@ -899,6 +933,52 @@ void CIRRecordLowering::accumulateBases(const CXXRecordDecl *cxxRecordDecl) {
}
}

void CIRRecordLowering::accumulateVBases() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that the implementation we’re using is the one from classic CodeGen in version 18, but it changed in version 19. It might be worth updating, since the newer version looks shorter and cleaner:

Version 18:

void CGRecordLowering::accumulateVBases() {
CharUnits ScissorOffset = Layout.getNonVirtualSize();
// In the itanium ABI, it's possible to place a vbase at a dsize that is
// smaller than the nvsize. Here we check to see if such a base is placed
// before the nvsize and set the scissor offset to that, instead of the
// nvsize.
if (isOverlappingVBaseABI())

Version 19:

void CGRecordLowering::accumulateVBases() {
for (const auto &Base : RD->vbases()) {
const CXXRecordDecl *BaseDecl = Base.getType()->getAsCXXRecordDecl();
if (isEmptyRecordForLayout(Context, Base.getType()))
continue;
CharUnits Offset = Layout.getVBaseClassOffset(BaseDecl);

What do you think, @andykaylor ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should definitely update to the newer code structure. Thanks for catching that!

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing else to add - LGTM after comments from others are addressed

@llvmbot llvmbot added the clang:codegen IR generation bugs: mangling, exceptions, etc. label Aug 27, 2025
@@ -180,7 +180,11 @@ CharUnits CodeGenModule::computeNonVirtualBaseClassOffset(
// Get the layout.
const ASTRecordLayout &Layout = Context.getASTRecordLayout(RD);

const auto *BaseDecl = Base->getType()->castAsCXXRecordDecl();
const auto *BaseDecl =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore these changes. They're artifacts from another PR.

Copy link

github-actions bot commented Aug 27, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@andykaylor andykaylor removed the clang:codegen IR generation bugs: mangling, exceptions, etc. label Aug 27, 2025
@andykaylor andykaylor merged commit e7c9f2d into llvm:main Aug 27, 2025
9 checks passed
@andykaylor andykaylor deleted the cir-virtual-base branch August 27, 2025 22:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants