[clang] [CIR] Upstream missing support for vtables and virtual bases (PR #192617)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Apr 18 00:44:22 PDT 2026
https://github.com/xiongzile updated https://github.com/llvm/llvm-project/pull/192617
>From d680c4c14b0598f4b776778d2aa4352ef48c5a2e 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 ef4f102cfeb9d8da9150308f4f7b9c5971d5f9b8 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 092d120fc088264e91483c3e23a5253da71573c8 Mon Sep 17 00:00:00 2001
From: Zile Xiong <xiongzile at bytedance.com>
Date: Sat, 18 Apr 2026 15:44:06 +0800
Subject: [PATCH 3/3] [CIR] vtable type metadata portion
---
clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 13 ++++++++++---
clang/lib/CIR/CodeGen/CIRGenModule.cpp | 8 ++++++++
clang/lib/CIR/CodeGen/CIRGenModule.h | 4 ++++
3 files changed, 22 insertions(+), 3 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index 982349abb48c4..74e50c3f9cab4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -521,6 +521,7 @@ 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 +529,15 @@ 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) {
+ cgm.emitVTableTypeMetadata(rd, vtable, vtLayout);
+ // For available_externally definitions, add the vtable to
+ // @llvm.compiler.used so that it isn't deleted before whole program
+ // analysis.
+ if (isDeclarationForLinker) {
+ cgm.errorNYI(rd->getSourceRange(), "isDeclarationForLinker");
+ assert(cgm.getCodeGenOpts().WholeProgramVTables);
+ }
}
assert(!cir::MissingFeatures::vtableRelativeLayout());
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 2f64fe45a694d..6155ab3ec39b2 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -3509,3 +3509,11 @@ CIRGenModule::getAddrOfGlobalTemporary(const MaterializeTemporaryExpr *mte,
return cv;
}
+
+void CIRGenModule::emitVTableTypeMetadata(const CXXRecordDecl *rd,
+ cir::GlobalOp vTable,
+ const VTableLayout &vtLayout) {
+ if (!getCodeGenOpts().LTOUnit)
+ return;
+ errorNYI(rd->getSourceRange(), "emitVTableTypeMetadata");
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index ba3f936106d31..8ae414ff43e3e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -467,6 +467,10 @@ class CIRGenModule : public CIRGenTypeCache {
std::vector<cir::CIRGlobalValueInterface> llvmUsed;
std::vector<cir::CIRGlobalValueInterface> llvmCompilerUsed;
+ /// Emit type metadata for the given vtable using the given layout.
+ void emitVTableTypeMetadata(const CXXRecordDecl *rd, cir::GlobalOp vTable,
+ const VTableLayout &vtLayout);
+
mlir::Type getVTableComponentType();
CIRGenVTables &getVTables() { return vtables; }
More information about the cfe-commits
mailing list