Skip to content

Commit 1ff6122

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: 57a74ba Pull-Request: #2005
1 parent 7620523 commit 1ff6122

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
@@ -366,6 +366,13 @@ class CIRGenCXXABI {
366366

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

369+
virtual void emitBadTypeidCall(CIRGenFunction &CGF) = 0;
370+
virtual bool shouldTypeidBeNullChecked(QualType SrcRecordTy) = 0;
371+
372+
virtual mlir::Value emitTypeid(CIRGenFunction &CGF, mlir::Location loc,
373+
QualType SrcRecordTy, Address ThisPtr,
374+
mlir::Type StdTypeInfoPtrTy) = 0;
375+
369376
virtual mlir::Value
370377
getVirtualBaseClassOffset(mlir::Location loc, CIRGenFunction &CGF,
371378
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
@@ -333,6 +333,12 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
333333

334334
void emitBadCastCall(CIRGenFunction &CGF, mlir::Location loc) override;
335335

336+
void emitBadTypeidCall(CIRGenFunction &CGF) override;
337+
bool shouldTypeidBeNullChecked(QualType SrcRecordTy) override;
338+
mlir::Value emitTypeid(CIRGenFunction &CGF, mlir::Location loc,
339+
QualType SrcRecordTy, Address ThisPtr,
340+
mlir::Type StdTypeInfoPtrTy) override;
341+
336342
mlir::Value
337343
getVirtualBaseClassOffset(mlir::Location loc, CIRGenFunction &CGF,
338344
Address This, const CXXRecordDecl *ClassDecl,
@@ -2485,6 +2491,88 @@ void CIRGenItaniumCXXABI::emitBadCastCall(CIRGenFunction &CGF,
24852491
emitCallToBadCast(CGF, loc);
24862492
}
24872493

2494+
void CIRGenItaniumCXXABI::emitBadTypeidCall(CIRGenFunction &CGF) {
2495+
auto loc = CGF.getLoc(SourceLocation());
2496+
// TODO: When exception support is complete, emit throw std::bad_typeid
2497+
// For now, emit unreachable since calling typeid on null is UB
2498+
cir::UnreachableOp::create(CGF.getBuilder(), loc);
2499+
CGF.getBuilder().clearInsertionPoint();
2500+
}
2501+
2502+
bool CIRGenItaniumCXXABI::shouldTypeidBeNullChecked(QualType srcRecordTy) {
2503+
// Match CodeGen behavior: always check for null.
2504+
// TODO(cir): The Itanium ABI 2.9.5p3 states only pointer operands need
2505+
// null checking (references cannot be null), but CodeGen conservatively
2506+
// always returns true. We match this behavior for consistency.
2507+
// Consider optimizing to: return srcRecordTy->isPointerType();
2508+
return true;
2509+
}
2510+
2511+
mlir::Value CIRGenItaniumCXXABI::emitTypeid(CIRGenFunction &CGF,
2512+
mlir::Location loc,
2513+
QualType srcRecordTy,
2514+
Address thisPtr,
2515+
mlir::Type stdTypeInfoPtrTy) {
2516+
auto &builder = CGF.getBuilder();
2517+
auto *classDecl = srcRecordTy->castAsCXXRecordDecl();
2518+
2519+
// Get the vtable pointer from the object
2520+
mlir::Value vTable = CGF.getVTablePtr(loc, thisPtr, classDecl);
2521+
2522+
mlir::Value typeInfoPtr;
2523+
2524+
if (CGM.getItaniumVTableContext().isRelativeLayout()) {
2525+
// Relative layout: type_info offset is at vptr[-4] (4 bytes before vptr)
2526+
// Load the offset and add it to the vtable pointer
2527+
auto int32Ty = builder.getSInt32Ty();
2528+
auto int8PtrTy = builder.getUInt8PtrTy();
2529+
2530+
// Cast vtable to i8* for byte arithmetic
2531+
auto vTableBytes = builder.createBitcast(loc, vTable, int8PtrTy);
2532+
2533+
// Get address of the offset: vtable - 4
2534+
auto offsetAddrPtr = cir::PtrStrideOp::create(
2535+
builder, loc, builder.getPointerTo(int32Ty), vTableBytes,
2536+
builder.getConstInt(loc, builder.getSInt64Ty(), -4));
2537+
2538+
// Load the 32-bit offset
2539+
auto offsetAddr =
2540+
Address(offsetAddrPtr, int32Ty, CharUnits::fromQuantity(4));
2541+
auto offset = builder.createLoad(loc, offsetAddr);
2542+
2543+
// Sign-extend offset to pointer width
2544+
auto offset64 = cir::CastOp::create(builder, loc, builder.getSInt64Ty(),
2545+
cir::CastKind::integral, offset);
2546+
2547+
// Add offset to vtable pointer: vtable + offset
2548+
auto typeInfoBytes = cir::PtrStrideOp::create(builder, loc, int8PtrTy,
2549+
vTableBytes, offset64);
2550+
2551+
// Cast result to type_info pointer type
2552+
typeInfoPtr = builder.createBitcast(loc, typeInfoBytes, stdTypeInfoPtrTy);
2553+
2554+
} else {
2555+
// Absolute layout: type_info* is at vtable[-1]
2556+
// GEP to get address, then load
2557+
2558+
// Cast vtable pointer to a regular pointer type for ptr_stride
2559+
auto vTablePtr = builder.createBitcast(
2560+
loc, vTable, builder.getPointerTo(stdTypeInfoPtrTy));
2561+
2562+
// Get vtable[-1]
2563+
auto typeInfoAddrPtr = cir::PtrStrideOp::create(
2564+
builder, loc, builder.getPointerTo(stdTypeInfoPtrTy), vTablePtr,
2565+
builder.getConstInt(loc, builder.getSInt64Ty(), -1));
2566+
2567+
// Load the type_info pointer
2568+
auto typeInfoAddr =
2569+
Address(typeInfoAddrPtr, stdTypeInfoPtrTy, CGF.getPointerAlign());
2570+
typeInfoPtr = builder.createLoad(loc, typeInfoAddr);
2571+
}
2572+
2573+
return typeInfoPtr;
2574+
}
2575+
24882576
static CharUnits computeOffsetHint(ASTContext &astContext,
24892577
const CXXRecordDecl *Src,
24902578
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)