Skip to content

Commit d11001f

Browse files
committed
[CIR] Add support for polymorphic typeid with vtable lookup
This commit implements runtime type identification for polymorphic types, completing typeid operator support in ClangIR. Previously, only non-polymorphic (static) typeid was supported. This adds: 1. **Polymorphic typeid emission**: When typeid is applied to an expression of polymorphic class type, we now emit vtable lookup to get the runtime type_info pointer. 2. **Null pointer checking**: For pointer operands, we emit null checks and call emitBadTypeidCall (currently unreachable until exception support). Matches CodeGen behavior of always checking, even for references. 3. **Type safety checks**: emitTypeCheck ensures typeid is not used unsafely during object construction or destruction. 4. **Vtable layouts**: Supports both traditional (vtable[-1]) and relative (load.relative(vtable, -4)) layouts for Itanium ABI. Implementation follows CodeGen closely: - emitTypeidFromVTable handles the main logic with null checking - CIRGenItaniumCXXABI::emitTypeid handles ABI-specific vtable access - Uses existing getVTablePtr infrastructure - shouldTypeidBeNullChecked matches CodeGen (always returns true) The vtable layout for type_info access: - Absolute: type_info* at vtable[-1] (8 bytes before vptr) - Relative: type_info offset at vtable[-4] (4 bytes before vptr) Test Plan: - Added typeid-polymorphic.cpp with comprehensive tests - Tests verify CIR output, LLVM lowering, and CodeGen comparison (OGCG) - All 4 test cases have CIR + LLVM + OGCG checks - Covers: basic usage, references, derived classes, const pointers - Note: CIR lowering of PtrStrideOp does not emit 'inbounds' flag (general CIR lowering issue, not specific to this feature) - All CIR tests pass ghstack-source-id: b4dc46b Pull-Request: #2007
1 parent 13175b1 commit d11001f

File tree

4 files changed

+242
-4
lines changed

4 files changed

+242
-4
lines changed

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,13 @@ class CIRGenCXXABI {
397397

398398
virtual void emitBadCastCall(CIRGenFunction &CGF, mlir::Location loc) = 0;
399399

400+
virtual void emitBadTypeidCall(CIRGenFunction &cgf) = 0;
401+
virtual bool shouldTypeidBeNullChecked(QualType srcRecordTy) = 0;
402+
403+
virtual mlir::Value emitTypeid(CIRGenFunction &cgf, mlir::Location loc,
404+
QualType srcRecordTy, Address thisPtr,
405+
mlir::Type stdTypeInfoPtrTy) = 0;
406+
400407
virtual mlir::Value
401408
getVirtualBaseClassOffset(mlir::Location loc, CIRGenFunction &CGF,
402409
Address This, const CXXRecordDecl *ClassDecl,

clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,6 +1810,58 @@ mlir::Value CIRGenFunction::emitDynamicCast(Address ThisAddr,
18101810
destCirTy, isRefCast, ThisAddr);
18111811
}
18121812

1813+
static mlir::Value emitTypeidFromVTable(CIRGenFunction &cgf, mlir::Location loc,
1814+
const Expr *e,
1815+
mlir::Type stdTypeInfoPtrTy,
1816+
bool hasNullCheck) {
1817+
// Get the vtable pointer.
1818+
Address thisPtr = cgf.emitLValue(e).getAddress();
1819+
1820+
QualType srcRecordTy = e->getType();
1821+
1822+
// C++ [class.cdtor]p4:
1823+
// If the operand of typeid refers to the object under construction or
1824+
// destruction and the static type of the operand is neither the constructor
1825+
// or destructor's class nor one of its bases, the behavior is undefined.
1826+
cgf.emitTypeCheck(CIRGenFunction::TCK_DynamicOperation, e->getExprLoc(),
1827+
thisPtr.getPointer(), srcRecordTy);
1828+
1829+
// Whether we need an explicit null pointer check. For example, with the
1830+
// Microsoft ABI, if this is a call to __RTtypeid, the null pointer check and
1831+
// exception throw is inside the __RTtypeid(nullptr) call
1832+
if (hasNullCheck &&
1833+
cgf.CGM.getCXXABI().shouldTypeidBeNullChecked(srcRecordTy)) {
1834+
auto &builder = cgf.getBuilder();
1835+
1836+
// Create blocks for null and non-null paths
1837+
auto *currBlock = builder.getInsertionBlock();
1838+
auto *parentOp = currBlock->getParent()->getParentOp();
1839+
auto &region = parentOp->getRegion(0);
1840+
1841+
mlir::Block *badTypeidBlock = builder.createBlock(&region);
1842+
mlir::Block *endBlock = builder.createBlock(&region);
1843+
1844+
// Check if pointer is null
1845+
builder.setInsertionPointToEnd(currBlock);
1846+
mlir::Value nullPtr =
1847+
builder.getNullPtr(thisPtr.getPointer().getType(), loc);
1848+
mlir::Value isNull = builder.createCompare(loc, cir::CmpOpKind::eq,
1849+
thisPtr.getPointer(), nullPtr);
1850+
1851+
builder.create<cir::BrCondOp>(loc, isNull, badTypeidBlock, endBlock);
1852+
1853+
// Emit bad typeid path
1854+
builder.setInsertionPointToEnd(badTypeidBlock);
1855+
cgf.CGM.getCXXABI().emitBadTypeidCall(cgf);
1856+
1857+
// Continue on non-null path
1858+
builder.setInsertionPointToEnd(endBlock);
1859+
}
1860+
1861+
return cgf.CGM.getCXXABI().emitTypeid(cgf, loc, srcRecordTy, thisPtr,
1862+
stdTypeInfoPtrTy);
1863+
}
1864+
18131865
mlir::Value CIRGenFunction::emitCXXTypeidExpr(const CXXTypeidExpr *E) {
18141866
auto loc = getLoc(E->getSourceRange());
18151867

@@ -1843,10 +1895,10 @@ mlir::Value CIRGenFunction::emitCXXTypeidExpr(const CXXTypeidExpr *E) {
18431895
// If the operand is already the most derived object, no need to look up
18441896
// vtable.
18451897
if (E->isPotentiallyEvaluated() && !E->isMostDerived(getContext())) {
1846-
// This requires emitting code similar to dynamic_cast that looks up the
1847-
// type_info pointer from the vtable. Note that this path also needs to
1848-
// handle null checking when E->hasNullCheck() is true.
1849-
llvm_unreachable("NYI: typeid with polymorphic types (vtable lookup)");
1898+
// Polymorphic case: need runtime vtable lookup
1899+
mlir::Type typeInfoPtrTy = builder.getUInt8PtrTy();
1900+
return emitTypeidFromVTable(*this, loc, E->getExprOperand(), typeInfoPtrTy,
1901+
E->hasNullCheck());
18501902
}
18511903

18521904
// For non-polymorphic types, just return the static RTTI descriptor.

clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,12 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
344344

345345
void emitBadCastCall(CIRGenFunction &CGF, mlir::Location loc) override;
346346

347+
void emitBadTypeidCall(CIRGenFunction &CGF) override;
348+
bool shouldTypeidBeNullChecked(QualType SrcRecordTy) override;
349+
mlir::Value emitTypeid(CIRGenFunction &CGF, mlir::Location loc,
350+
QualType SrcRecordTy, Address ThisPtr,
351+
mlir::Type StdTypeInfoPtrTy) override;
352+
347353
mlir::Value
348354
getVirtualBaseClassOffset(mlir::Location loc, CIRGenFunction &CGF,
349355
Address This, const CXXRecordDecl *ClassDecl,
@@ -2702,6 +2708,88 @@ void CIRGenItaniumCXXABI::emitBadCastCall(CIRGenFunction &CGF,
27022708
emitCallToBadCast(CGF, loc);
27032709
}
27042710

2711+
void CIRGenItaniumCXXABI::emitBadTypeidCall(CIRGenFunction &CGF) {
2712+
auto loc = CGF.getLoc(SourceLocation());
2713+
// TODO: When exception support is complete, emit throw std::bad_typeid
2714+
// For now, emit unreachable since calling typeid on null is UB
2715+
cir::UnreachableOp::create(CGF.getBuilder(), loc);
2716+
CGF.getBuilder().clearInsertionPoint();
2717+
}
2718+
2719+
bool CIRGenItaniumCXXABI::shouldTypeidBeNullChecked(QualType srcRecordTy) {
2720+
// Match CodeGen behavior: always check for null.
2721+
// TODO(cir): The Itanium ABI 2.9.5p3 states only pointer operands need
2722+
// null checking (references cannot be null), but CodeGen conservatively
2723+
// always returns true. We match this behavior for consistency.
2724+
// Consider optimizing to: return srcRecordTy->isPointerType();
2725+
return true;
2726+
}
2727+
2728+
mlir::Value CIRGenItaniumCXXABI::emitTypeid(CIRGenFunction &CGF,
2729+
mlir::Location loc,
2730+
QualType srcRecordTy,
2731+
Address thisPtr,
2732+
mlir::Type stdTypeInfoPtrTy) {
2733+
auto &builder = CGF.getBuilder();
2734+
auto *classDecl = srcRecordTy->castAsCXXRecordDecl();
2735+
2736+
// Get the vtable pointer from the object
2737+
mlir::Value vTable = CGF.getVTablePtr(loc, thisPtr, classDecl);
2738+
2739+
mlir::Value typeInfoPtr;
2740+
2741+
if (CGM.getItaniumVTableContext().isRelativeLayout()) {
2742+
// Relative layout: type_info offset is at vptr[-4] (4 bytes before vptr)
2743+
// Load the offset and add it to the vtable pointer
2744+
auto int32Ty = builder.getSInt32Ty();
2745+
auto int8PtrTy = builder.getUInt8PtrTy();
2746+
2747+
// Cast vtable to i8* for byte arithmetic
2748+
auto vTableBytes = builder.createBitcast(loc, vTable, int8PtrTy);
2749+
2750+
// Get address of the offset: vtable - 4
2751+
auto offsetAddrPtr = cir::PtrStrideOp::create(
2752+
builder, loc, builder.getPointerTo(int32Ty), vTableBytes,
2753+
builder.getConstInt(loc, builder.getSInt64Ty(), -4));
2754+
2755+
// Load the 32-bit offset
2756+
auto offsetAddr =
2757+
Address(offsetAddrPtr, int32Ty, CharUnits::fromQuantity(4));
2758+
auto offset = builder.createLoad(loc, offsetAddr);
2759+
2760+
// Sign-extend offset to pointer width
2761+
auto offset64 = cir::CastOp::create(builder, loc, builder.getSInt64Ty(),
2762+
cir::CastKind::integral, offset);
2763+
2764+
// Add offset to vtable pointer: vtable + offset
2765+
auto typeInfoBytes = cir::PtrStrideOp::create(builder, loc, int8PtrTy,
2766+
vTableBytes, offset64);
2767+
2768+
// Cast result to type_info pointer type
2769+
typeInfoPtr = builder.createBitcast(loc, typeInfoBytes, stdTypeInfoPtrTy);
2770+
2771+
} else {
2772+
// Absolute layout: type_info* is at vtable[-1]
2773+
// GEP to get address, then load
2774+
2775+
// Cast vtable pointer to a regular pointer type for ptr_stride
2776+
auto vTablePtr = builder.createBitcast(
2777+
loc, vTable, builder.getPointerTo(stdTypeInfoPtrTy));
2778+
2779+
// Get vtable[-1]
2780+
auto typeInfoAddrPtr = cir::PtrStrideOp::create(
2781+
builder, loc, builder.getPointerTo(stdTypeInfoPtrTy), vTablePtr,
2782+
builder.getConstInt(loc, builder.getSInt64Ty(), -1));
2783+
2784+
// Load the type_info pointer
2785+
auto typeInfoAddr =
2786+
Address(typeInfoAddrPtr, stdTypeInfoPtrTy, CGF.getPointerAlign());
2787+
typeInfoPtr = builder.createLoad(loc, typeInfoAddr);
2788+
}
2789+
2790+
return typeInfoPtr;
2791+
}
2792+
27052793
static CharUnits computeOffsetHint(ASTContext &astContext,
27062794
const CXXRecordDecl *Src,
27072795
const CXXRecordDecl *Dst) {
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR
3+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
4+
// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM
5+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.og.ll
6+
// RUN: FileCheck --input-file=%t.og.ll %s --check-prefix=OGCG
7+
8+
namespace std {
9+
class type_info {
10+
public:
11+
virtual ~type_info();
12+
};
13+
}
14+
15+
// Basic polymorphic class hierarchy
16+
struct Base {
17+
virtual ~Base() {}
18+
int x;
19+
};
20+
21+
struct Derived : Base {
22+
int y;
23+
};
24+
25+
extern void use_typeinfo(const std::type_info*);
26+
27+
// Test 1: Basic polymorphic typeid - pointer
28+
// CIR-LABEL: cir.func dso_local @_Z22test_polymorphic_basicP4Base
29+
void test_polymorphic_basic(Base* ptr) {
30+
// CIR: cir.vtable.get_vptr
31+
// CIR: cir.load{{.*}}!cir.vptr
32+
// CIR: cir.cast bitcast
33+
// CIR: cir.ptr_stride
34+
// CIR: cir.load
35+
36+
// LLVM-LABEL: @_Z22test_polymorphic_basicP4Base
37+
// LLVM: load ptr, ptr %
38+
// LLVM: getelementptr ptr, ptr %{{.*}}, i64 -1
39+
// LLVM: load ptr, ptr %
40+
41+
// OGCG-LABEL: @_Z22test_polymorphic_basicP4Base
42+
// OGCG: load ptr, ptr %
43+
// OGCG: getelementptr inbounds ptr, ptr %{{.*}}, i64 -1
44+
// OGCG: load ptr, ptr %
45+
use_typeinfo(&typeid(*ptr));
46+
}
47+
48+
// Test 2: Polymorphic typeid - reference (no null check)
49+
// CIR-LABEL: cir.func dso_local @_Z14test_referenceR4Base
50+
void test_reference(Base& ref) {
51+
// CIR-NOT: cir.cmp(eq
52+
// CIR: cir.vtable.get_vptr
53+
// CIR: cir.ptr_stride
54+
55+
// LLVM-LABEL: @_Z14test_referenceR4Base
56+
// LLVM: load ptr, ptr %
57+
// LLVM: getelementptr ptr, ptr %{{.*}}, i64 -1
58+
59+
// OGCG-LABEL: @_Z14test_referenceR4Base
60+
// OGCG: load ptr, ptr %
61+
// OGCG: getelementptr inbounds ptr, ptr %{{.*}}, i64 -1
62+
use_typeinfo(&typeid(ref));
63+
}
64+
65+
// Test 3: Derived class pointer
66+
// CIR-LABEL: cir.func dso_local @_Z20test_derived_pointerP7Derived
67+
void test_derived_pointer(Derived* ptr) {
68+
// CIR: cir.vtable.get_vptr
69+
// CIR: cir.ptr_stride
70+
71+
// LLVM-LABEL: @_Z20test_derived_pointerP7Derived
72+
// LLVM: getelementptr ptr, ptr %{{.*}}, i64 -1
73+
74+
// OGCG-LABEL: @_Z20test_derived_pointerP7Derived
75+
// OGCG: getelementptr inbounds ptr, ptr %{{.*}}, i64 -1
76+
use_typeinfo(&typeid(*ptr));
77+
}
78+
79+
// Test 4: Const qualified pointer
80+
// CIR-LABEL: cir.func dso_local @_Z14test_const_ptrPK4Base
81+
void test_const_ptr(const Base* ptr) {
82+
// CIR: cir.vtable.get_vptr
83+
// CIR: cir.ptr_stride
84+
85+
// LLVM-LABEL: @_Z14test_const_ptrPK4Base
86+
// LLVM: getelementptr ptr, ptr %{{.*}}, i64 -1
87+
88+
// OGCG-LABEL: @_Z14test_const_ptrPK4Base
89+
// OGCG: getelementptr inbounds ptr, ptr %{{.*}}, i64 -1
90+
use_typeinfo(&typeid(*ptr));
91+
}

0 commit comments

Comments
 (0)