[clang] [CIR] Upstream missing support for vtables and virtual bases (PR #192617)

via cfe-commits cfe-commits at lists.llvm.org
Fri Apr 17 10:52:18 PDT 2026


https://github.com/xiongzile updated https://github.com/llvm/llvm-project/pull/192617

>From b58c488b3db34f0a5c9b0e6c86d150250d1f9ec7 Mon Sep 17 00:00:00 2001
From: Zile Xiong <xiongzile at bytedance.com>
Date: Fri, 17 Apr 2026 17:18:23 +0800
Subject: [PATCH 1/3] [CIR] relative vtable layout & apply virtual and
 non-virtual offset

---
 clang/lib/CIR/CodeGen/CIRGenClass.cpp         | 12 ++--
 clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp |  8 ++-
 clang/test/CIR/CodeGen/virtual-base-cast.cpp  | 60 +++++++++++++++++++
 3 files changed, 74 insertions(+), 6 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/virtual-base-cast.cpp

diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index 5f9fdfba9d31b..9755acb883675 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -399,10 +399,14 @@ static Address applyNonVirtualAndVirtualOffset(
   mlir::Value baseOffset;
   if (!nonVirtualOffset.isZero()) {
     if (virtualOffset) {
-      cgf.cgm.errorNYI(
-          loc,
-          "applyNonVirtualAndVirtualOffset: virtual and non-virtual offset");
-      return Address::invalid();
+      mlir::Type offsetType =
+          (cgf.cgm.getTarget().getCXXABI().isItaniumFamily() &&
+           cgf.cgm.getLangOpts().RelativeCXXABIVTables)
+              ? cgf.sInt32Ty
+              : cgf.ptrDiffTy;
+      baseOffset = cgf.getBuilder().getConstInt(loc, offsetType,
+                                                nonVirtualOffset.getQuantity());
+      baseOffset = cgf.getBuilder().createAdd(loc, virtualOffset, baseOffset);
     } else {
       assert(baseValueTy && "expected base type");
       // If no virtualOffset is present this is the final stop.
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index 75658b23790bf..982349abb48c4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -2056,6 +2056,7 @@ bool CIRGenItaniumCXXABI::isVirtualOffsetNeededForVTableField(
 mlir::Value CIRGenItaniumCXXABI::getVirtualBaseClassOffset(
     mlir::Location loc, CIRGenFunction &cgf, Address thisAddr,
     const CXXRecordDecl *classDecl, const CXXRecordDecl *baseClassDecl) {
+
   CIRGenBuilderTy &builder = cgf.getBuilder();
   mlir::Value vtablePtr = cgf.getVTablePtr(loc, thisAddr, classDecl);
   mlir::Value vtableBytePtr = builder.createBitcast(vtablePtr, cgm.uInt8PtrTy);
@@ -2069,8 +2070,11 @@ mlir::Value CIRGenItaniumCXXABI::getVirtualBaseClassOffset(
 
   mlir::Value vbaseOffset;
   if (cgm.getLangOpts().RelativeCXXABIVTables) {
-    assert(!cir::MissingFeatures::vtableRelativeLayout());
-    cgm.errorNYI(loc, "getVirtualBaseClassOffset: relative layout");
+    mlir::Value offsetPtr = builder.createBitcast(
+        vbaseOffsetPtr, builder.getPointerTo(cgm.sInt32Ty));
+    vbaseOffset = cgf.getBuilder().createLoad(
+        loc, Address(offsetPtr, cgm.sInt32Ty,
+                     CharUnits::fromQuantity(4))); // vbase.offset
   } else {
     mlir::Value offsetPtr = builder.createBitcast(
         vbaseOffsetPtr, builder.getPointerTo(cgm.ptrDiffTy));
diff --git a/clang/test/CIR/CodeGen/virtual-base-cast.cpp b/clang/test/CIR/CodeGen/virtual-base-cast.cpp
new file mode 100644
index 0000000000000..04fad6d4143c7
--- /dev/null
+++ b/clang/test/CIR/CodeGen/virtual-base-cast.cpp
@@ -0,0 +1,60 @@
+// RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -mconstructor-aliases -O0 -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -mconstructor-aliases -O0 -fclangir -emit-llvm -fno-clangir-call-conv-lowering %s -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
+
+struct A { int a; virtual int aa(); };
+struct B { int b; virtual int bb(); };
+struct C : virtual A, virtual B { int c; virtual int aa(); virtual int bb(); };
+struct AA { int a; virtual int aa(); };
+struct BB { int b; virtual int bb(); };
+struct CC : AA, BB { virtual int aa(); virtual int bb(); virtual int cc(); };
+struct D : virtual C, virtual CC { int e; };
+
+D* x;
+
+A* a() { return x; }
+// CIR-LABEL: @_Z1av()
+
+// This uses the vtable to get the offset to the base object. The offset from
+// the vptr to the base object offset in the vtable is a compile-time constant.
+// CIR: %[[X_ADDR:.*]] = cir.get_global @x : !cir.ptr<!cir.ptr<!rec_D>>
+// CIR: %[[X:.*]] = cir.load{{.*}} %[[X_ADDR]]
+// CIR: %[[X_VPTR_ADDR:.*]] = cir.vtable.get_vptr %[[X]] : !cir.ptr<!rec_D> -> !cir.ptr<!cir.vptr>
+// CIR: %[[X_VPTR_BASE:.*]] = cir.load{{.*}} %[[X_VPTR_ADDR]] : !cir.ptr<!cir.vptr>, !cir.vptr
+// CIR: %[[X_BASE_I8PTR:.*]] = cir.cast bitcast %[[X_VPTR_BASE]] : !cir.vptr -> !cir.ptr<!u8i>
+// CIR:  %[[OFFSET_OFFSET:.*]] = cir.const #cir.int<-32> : !s64i
+// CIR:  %[[OFFSET_PTR:.*]] = cir.ptr_stride %[[X_BASE_I8PTR]], %[[OFFSET_OFFSET]] : (!cir.ptr<!u8i>, !s64i) -> !cir.ptr<!u8i>
+// CIR:  %[[OFFSET_PTR_CAST:.*]] = cir.cast bitcast %[[OFFSET_PTR]] : !cir.ptr<!u8i> -> !cir.ptr<!s64i>
+// CIR:  %[[OFFSET:.*]] = cir.load{{.*}} %[[OFFSET_PTR_CAST]] : !cir.ptr<!s64i>, !s64i
+// CIR:  %[[VBASE_ADDR:.*]] = cir.ptr_stride {{.*}}, %[[OFFSET]] : (!cir.ptr<!u8i>, !s64i) -> !cir.ptr<!u8i>
+// CIR:  cir.cast bitcast %[[VBASE_ADDR]] : !cir.ptr<!u8i> -> !cir.ptr<!rec_D>
+
+// FIXME: this version should include null check.
+// LLVM-LABEL: @_Z1av()
+// LLVM:  %[[OFFSET_OFFSET:.*]] = getelementptr i8, ptr {{.*}}, i64 -32
+// LLVM:  %[[OFFSET_PTR:.*]] = load i64, ptr %[[OFFSET_OFFSET]], align 8
+// LLVM:  %[[VBASE_ADDR:.*]] = getelementptr i8, ptr {{.*}}, i64 %[[OFFSET_PTR]]
+// LLVM:  store ptr %[[VBASE_ADDR]], ptr {{.*}}, align 8
+
+B* b() { return x; }
+BB* c() { return x; }
+
+// Put the vbptr at a non-zero offset inside a non-virtual base.
+struct E { int e; };
+struct F : E, D { int f; };
+
+F* y;
+
+BB* d() { return y; }
+// CIR-LABEL: @_Z1dv
+// CIR: %[[OFFSET:.*]] = cir.load{{.*}} {{.*}} : !cir.ptr<!s64i>, !s64i
+// CIR: %[[ADJUST:.*]] = cir.const #cir.int<16> : !s64i
+// CIR: cir.binop(add, %[[OFFSET]], %[[ADJUST]]) : !s64i
+
+// LLVM-LABEL: @_Z1dv
+// LLVM: %[[OFFSET_OFFSET:.*]] = getelementptr i8, ptr {{.*}}, i64 -48
+// LLVM: %[[OFFSET_PTR:.*]] = load i64, ptr %[[OFFSET_OFFSET]], align 8
+// LLVM: %[[ADJUST:.*]] = add i64 %[[OFFSET_PTR]], 16
+// LLVM: %[[VBASE_ADDR:.*]] = getelementptr i8, ptr {{.*}}, i64 %[[ADJUST]]
+// LLVM: store ptr %[[VBASE_ADDR]],

>From a98b85173ddb68a8afb778e5e2ba5343ff3c28a6 Mon Sep 17 00:00:00 2001
From: Zile Xiong <xiongzile at bytedance.com>
Date: Fri, 17 Apr 2026 20:12:19 +0800
Subject: [PATCH 2/3] [CIR] virtual-base-cast test diff

---
 clang/test/CIR/CodeGen/virtual-base-cast.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/test/CIR/CodeGen/virtual-base-cast.cpp b/clang/test/CIR/CodeGen/virtual-base-cast.cpp
index 04fad6d4143c7..a37edd622dd86 100644
--- a/clang/test/CIR/CodeGen/virtual-base-cast.cpp
+++ b/clang/test/CIR/CodeGen/virtual-base-cast.cpp
@@ -1,6 +1,6 @@
 // RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -mconstructor-aliases -O0 -fclangir -emit-cir %s -o %t.cir
 // RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
-// RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -mconstructor-aliases -O0 -fclangir -emit-llvm -fno-clangir-call-conv-lowering %s -o %t.ll
+// RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -mconstructor-aliases -O0 -fclangir -emit-llvm %s -o %t.ll
 // RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
 
 struct A { int a; virtual int aa(); };
@@ -50,7 +50,7 @@ BB* d() { return y; }
 // CIR-LABEL: @_Z1dv
 // CIR: %[[OFFSET:.*]] = cir.load{{.*}} {{.*}} : !cir.ptr<!s64i>, !s64i
 // CIR: %[[ADJUST:.*]] = cir.const #cir.int<16> : !s64i
-// CIR: cir.binop(add, %[[OFFSET]], %[[ADJUST]]) : !s64i
+// CIR: cir.add %[[OFFSET]], %[[ADJUST]] : !s64i
 
 // LLVM-LABEL: @_Z1dv
 // LLVM: %[[OFFSET_OFFSET:.*]] = getelementptr i8, ptr {{.*}}, i64 -48

>From 01887db60aa9057f44f2e7a2510802a7d6d4d7b0 Mon Sep 17 00:00:00 2001
From: Zile Xiong <xiongzile at bytedance.com>
Date: Fri, 17 Apr 2026 22:09:20 +0800
Subject: [PATCH 3/3] [CIR] add WholeProgramVTables metadata support(NYI)

---
 clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 150 +++++++++++++++++-
 clang/lib/CIR/CodeGen/CIRGenModule.h          |   9 ++
 2 files changed, 156 insertions(+), 3 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index 982349abb48c4..4a419669491ab 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -198,6 +198,8 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
                                 QualType elementType) override;
 
   bool isZeroInitializable(const MemberPointerType *MPT) override;
+  void emitVTableTypeMetadata(const CXXRecordDecl *rd, cir::GlobalOp vTable,
+                              const VTableLayout &vtLayout);
 
 protected:
   CharUnits getArrayCookieSizeImpl(QualType elementType) override;
@@ -477,6 +479,142 @@ bool CIRGenItaniumCXXABI::needsVTTParameter(GlobalDecl gd) {
   return false;
 }
 
+mlir::StringAttr CIRGenModule::getOrCreateMetadataIdentifierForType(QualType T, llvm::StringRef suffix) {
+  if (auto *fnType = T->getAs<FunctionProtoType>())
+    T = getASTContext().getFunctionType(
+        fnType->getReturnType(), fnType->getParamTypes(),
+        fnType->getExtProtoInfo().withExceptionSpec(EST_None));
+
+  auto key = getASTContext().getCanonicalType(T);
+  auto it = vTableTypeMetadataIdMap.find(key);
+  if (it != vTableTypeMetadataIdMap.end())
+    return it->second;
+
+  mlir::StringAttr result;
+  if (isExternallyVisible(T->getLinkage())) {
+    std::string OutName;
+    llvm::raw_string_ostream Out(OutName);
+    getCXXABI().getMangleContext().mangleCanonicalTypeName(
+        T, Out, getCodeGenOpts().SanitizeCfiICallNormalizeIntegers);
+
+    if (getCodeGenOpts().SanitizeCfiICallNormalizeIntegers)
+      Out << ".normalized";
+
+    Out << suffix;
+
+    result = mlir::StringAttr::get(&getMLIRContext(), Out.str());
+  } else {
+    std::string name = "__cir.internal_typeid." + std::to_string(NextTypeId++);
+    result = mlir::StringAttr::get(&getMLIRContext(), name);
+  }
+
+  return result;
+}
+
+void CIRGenModule::addVTableTypeMetadata(cir::GlobalOp vTable, CharUnits offset,
+                                         const CXXRecordDecl *rd) {
+  CanQualType T = getASTContext().getCanonicalTagType(rd);
+  mlir::StringAttr attrId = getOrCreateMetadataIdentifierForType(T, "");
+  mlir::MLIRContext *ctx = &getMLIRContext();
+
+  auto offsetAttr =
+      mlir::IntegerAttr::get(mlir::IntegerType::get(ctx, 64),
+                             static_cast<int64_t>(offset.getQuantity()));
+
+  auto entry = mlir::DictionaryAttr::get(
+      ctx, {
+               mlir::NamedAttribute(mlir::StringAttr::get(ctx, "offset"),
+                                    offsetAttr),
+               mlir::NamedAttribute(mlir::StringAttr::get(ctx, "type"), attrId),
+           });
+  constexpr llvm::StringLiteral attrName = "cir.vtable.type_metadata";
+  llvm::SmallVector<mlir::Attribute> entries;
+  if (auto old = vTable->getAttrOfType<mlir::ArrayAttr>(attrName))
+    entries.append(old.begin(), old.end());
+
+  entries.push_back(entry);
+  vTable->setAttr(attrName, mlir::ArrayAttr::get(ctx, entries));
+
+  errorNYI(rd->getSourceRange(), "addVTableTypeMetadata not implemented");
+  if (getCodeGenOpts().SanitizeCfiCrossDso) {
+    errorNYI(rd->getSourceRange(), "addVTableTypeMetadata not implemented");
+  }
+
+  errorNYI(rd->getSourceRange(), "addVTableTypeMetadata not implemented");
+}
+
+void CIRGenItaniumCXXABI::emitVTableTypeMetadata(const CXXRecordDecl *rd,
+                                                 cir::GlobalOp vTable,
+                                                 const VTableLayout &vtLayout) {
+  // Emit type metadata on vtables with LTO or IR instrumentation or
+  // speculative devirtualization.
+  // In IR instrumentation, the type metadata is used to find out vtable
+  // definitions (for type profiling) among all global variables.
+  if (!cgm.getCodeGenOpts().LTOUnit &&
+      !cgm.getCodeGenOpts().hasProfileIRInstr() &&
+      !cgm.getCodeGenOpts().DevirtualizeSpeculatively)
+    return;
+  CharUnits componentWidth = CharUnits::fromQuantity(cgm.PointerSizeInBytes);
+  struct AddressPoint {
+    const CXXRecordDecl *Base;
+    size_t Offset;
+    std::string TypeName;
+    bool operator<(const AddressPoint &RHS) const {
+      int D = TypeName.compare(RHS.TypeName);
+      return D < 0 || (D == 0 && Offset < RHS.Offset);
+    }
+  };
+  std::vector<AddressPoint> AddressPoints;
+  for (auto &&AP : vtLayout.getAddressPoints()) {
+    AddressPoint N{AP.first.getBase(),
+                   vtLayout.getVTableOffset(AP.second.VTableIndex) +
+                       AP.second.AddressPointIndex,
+                   {}};
+    llvm::raw_string_ostream stream(N.TypeName);
+    CanQualType T = getMangleContext().getASTContext().getCanonicalTagType(N.Base);
+    getMangleContext().mangleCanonicalTypeName(T, stream);
+    AddressPoints.push_back(std::move(N));
+  }
+
+  // Sort the address points for determinism.
+  llvm::sort(AddressPoints);
+
+  ArrayRef<VTableComponent> Comps = vtLayout.vtable_components();
+  for (auto AP : AddressPoints) {
+    // Create type metadata for the address point.
+    cgm.addVTableTypeMetadata(vTable, componentWidth * AP.Offset, AP.Base);
+    // The class associated with each address point could also potentially be
+    // used for indirect calls via a member function pointer, so we need to
+    // annotate the address of each function pointer with the appropriate member
+
+    // TODO:vtable
+    // function pointer type.
+    //    for (unsigned I = 0; I != Comps.size(); ++I) {
+    //      if (Comps[I].getKind() != VTableComponent::CK_FunctionPointer)
+    //        continue;
+    //      //      llvm::Metadata *MD = CreateMetadataIdentifierForVirtualMemPtrType(
+    //      //          getMangleContext().getASTContext().getMemberPointerType(Comps[I].getFunctionDecl()->getType(),
+    //      //                                       /*Qualifier=*/std::nullopt,
+    //      //                                       AP.Base));
+    //      llvm::Metadata *md = nullptr;
+    //      vTable->set((ComponentWidth * I).getQuantity(), MD);
+    //    }
+  }
+
+  // TODO:vtable
+
+//  if (cgm.getCodeGenOpts().VirtualFunctionElimination ||
+//      cgm.getCodeGenOpts().WholeProgramVTables) {
+//    llvm::DenseSet<const CXXRecordDecl *> visited;
+//    llvm::GlobalObject::VCallVisibility typeVis =
+//        GetVCallVisibilityLevel(rd, Visited);
+//    if (TypeVis != llvm::GlobalObject::VCallVisibilityPublic)
+//      vTable->setVCallVisibilityMetadata(TypeVis);
+//  }
+
+  cgm.errorNYI(rd->getSourceRange(), "emitVTableTypeMetadata");
+}
+
 void CIRGenItaniumCXXABI::emitVTableDefinitions(CIRGenVTables &cgvt,
                                                 const CXXRecordDecl *rd) {
   cir::GlobalOp vtable = getAddrOfVTable(rd, CharUnits());
@@ -521,6 +659,8 @@ void CIRGenItaniumCXXABI::emitVTableDefinitions(CIRGenVTables &cgvt,
   [[maybe_unused]] auto vtableAsGlobalValue =
       dyn_cast<cir::CIRGlobalValueInterface>(*vtable);
   assert(vtableAsGlobalValue && "VTable must support CIRGlobalValueInterface");
+  bool isDeclarationForLinker = vtableAsGlobalValue.isDeclarationForLinker();
+
   // Always emit type metadata on non-available_externally definitions, and on
   // available_externally definitions if we are performing whole program
   // devirtualization. For WPD we need the type metadata on all vtable
@@ -528,9 +668,13 @@ void CIRGenItaniumCXXABI::emitVTableDefinitions(CIRGenVTables &cgvt,
   // defined in headers but with a strong definition only in a shared
   // library.
   assert(!cir::MissingFeatures::vtableEmitMetadata());
-  if (cgm.getCodeGenOpts().WholeProgramVTables) {
-    cgm.errorNYI(rd->getSourceRange(),
-                 "emitVTableDefinitions: WholeProgramVTables");
+  if (!isDeclarationForLinker || cgm.getCodeGenOpts().WholeProgramVTables) {
+    emitVTableTypeMetadata(rd, vtable, vtLayout);
+
+    if (isDeclarationForLinker) {
+      // available_externally
+      cgm.errorNYI(rd->getSourceRange(), "available_externally");
+    }
   }
 
   assert(!cir::MissingFeatures::vtableRelativeLayout());
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index ba3f936106d31..fc13f2c5a5f9e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -66,6 +66,9 @@ class CIRGenModule : public CIRGenTypeCache {
 
   ~CIRGenModule();
 
+  void addVTableTypeMetadata(cir::GlobalOp vTable, CharUnits offset,
+                             const CXXRecordDecl *rd);
+
 private:
   mutable std::unique_ptr<TargetCIRGenInfo> theTargetCIRGenInfo;
 
@@ -202,6 +205,7 @@ class CIRGenModule : public CIRGenTypeCache {
 
   llvm::DenseMap<const Decl *, cir::GlobalOp> staticLocalDeclMap;
   llvm::DenseMap<const VarDecl *, cir::GlobalOp> initializerConstants;
+  llvm::DenseMap<QualType, mlir::StringAttr> vTableTypeMetadataIdMap;
 
   mlir::Operation *getGlobalValue(llvm::StringRef ref);
 
@@ -291,6 +295,8 @@ class CIRGenModule : public CIRGenTypeCache {
     return !getLangOpts().CPlusPlus;
   }
 
+  unsigned NextTypeId = 0;
+
   llvm::StringMap<unsigned> cgGlobalNames;
   std::string getUniqueGlobalName(const std::string &baseName);
 
@@ -816,6 +822,8 @@ class CIRGenModule : public CIRGenTypeCache {
   /// Emits AMDGPU specific Metadata.
   void emitAMDGPUMetadata();
 
+  mlir::StringAttr getOrCreateMetadataIdentifierForType(QualType T, llvm::StringRef suffix);
+
 private:
   // An ordered map of canonical GlobalDecls to their mangled names.
   llvm::MapVector<clang::GlobalDecl, llvm::StringRef> mangledDeclNames;
@@ -840,6 +848,7 @@ class CIRGenModule : public CIRGenTypeCache {
   /// For languages without explicit address spaces, if D has default address
   /// space, target-specific global or constant address space may be returned.
   LangAS getGlobalVarAddressSpace(const VarDecl *decl);
+  mlir::Type *getVTableComponentType() const;
 };
 } // namespace CIRGen
 



More information about the cfe-commits mailing list