[clang] [CIR] Upstream missing support for vtables and virtual bases (PR #192617)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Apr 17 11:10:13 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 20e3ee37d8e82fc3683a703c7b81e9921ef49fbb 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 | 10 ++
2 files changed, 157 insertions(+), 3 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index 982349abb48c4..b0f121cefeba2 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));
+
+ // TODO: by @Elio
+}
+
+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: by @Elio
+ // 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: by @Elio
+
+ // 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..52d489f5de336 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,9 @@ 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 +849,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