Skip to content

Commit 9d70080

Browse files
committed
[CIR] Add support for typeid l-value emission
Summary: This patch implements support for emitting typeid expressions as l-values in ClangIR, enabling code patterns such as: const std::type_info& ti = typeid(MyClass); auto& ref = typeid(int); void foo(const std::type_info& ti) { foo(typeid(SomeType)); } This is a common pattern in C++ code that uses RTTI for logging, serialization, debugging, and type introspection. Implementation: The implementation follows the traditional LLVM CodeGen approach: 1. Added emitCXXTypeidExpr() in CIRGenExprCXX.cpp - Handles both type operands (typeid(Type)) and expression operands (typeid(expr)) - Retrieves the RTTI descriptor via getAddrOfRTTIDescriptor() - Looks up the GlobalOp to determine the correct pointer type - Creates a GetGlobalOp to materialize the address at runtime - Polymorphic type vtable lookup is marked as NYI 2. Added emitCXXTypeidLValue() in CIRGenExpr.cpp - Calls emitCXXTypeidExpr() to get the RTTI descriptor pointer - Performs a bitcast from !u8i pointer to the proper type_info type - Creates an LValue with appropriate alignment for reference binding 3. Extended the emitLValue() switch statement to handle CXXTypeidExpr What works: - typeid with type operands: typeid(int), typeid(MyClass) - typeid with non-polymorphic expression operands: typeid(obj) - Reference binding: const std::type_info& ti = typeid(...) - Function argument passing: foo(typeid(...)) - Built-in types, class types, and struct types What doesn't work yet: - typeid with polymorphic types requiring vtable lookup (e.g., typeid(*basePtr) where the dynamic type differs from static type) This is marked with llvm_unreachable() and will be implemented separately. Test Plan: Added comprehensive test coverage in clang/test/CIR/CodeGen/typeid-lvalue.cpp - Non-polymorphic struct types - Expression operands - Built-in types (int, double) - Function argument passing - All tests pass Regression testing: - All 394 CIR CodeGen tests pass (379 passed, 14 unsupported, 1 expected fail) - Code formatted with clang-format - Code checked with clang-tidy (warnings consistent with existing codebase) Differential Revision: N/A ghstack-source-id: 261e71d Pull-Request: #2000
1 parent 78c0ba0 commit 9d70080

File tree

4 files changed

+196
-0
lines changed

4 files changed

+196
-0
lines changed

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2769,6 +2769,8 @@ LValue CIRGenFunction::emitLValue(const Expr *E) {
27692769
return emitStmtExprLValue(cast<StmtExpr>(E));
27702770
case Expr::ChooseExprClass:
27712771
return emitLValue(cast<ChooseExpr>(E)->getChosenSubExpr());
2772+
case Expr::CXXTypeidExprClass:
2773+
return emitCXXTypeidLValue(cast<CXXTypeidExpr>(E));
27722774
}
27732775

27742776
llvm_unreachable("NYI");
@@ -3489,3 +3491,21 @@ RValue CIRGenFunction::emitPseudoObjectRValue(const PseudoObjectExpr *expr,
34893491
LValue CIRGenFunction::emitPseudoObjectLValue(const PseudoObjectExpr *expr) {
34903492
return emitPseudoObjectExpr(*this, expr, true, AggValueSlot::ignored()).lv;
34913493
}
3494+
3495+
LValue CIRGenFunction::emitCXXTypeidLValue(const CXXTypeidExpr *E) {
3496+
// Emit the typeid expression, which returns a !u8i pointer to the type_info
3497+
// object.
3498+
mlir::Value typeInfoPtr = emitCXXTypeidExpr(E);
3499+
3500+
// Cast the !u8i pointer to a pointer to the actual type_info type.
3501+
auto typeInfoTy = convertTypeForMem(E->getType());
3502+
auto typeInfoPtrTy = builder.getPointerTo(typeInfoTy);
3503+
typeInfoPtr = builder.createBitcast(getLoc(E->getSourceRange()), typeInfoPtr,
3504+
typeInfoPtrTy);
3505+
3506+
// Create an LValue from the pointer with natural alignment.
3507+
Address addr(typeInfoPtr, typeInfoTy,
3508+
getContext().getTypeAlignInChars(E->getType()));
3509+
3510+
return makeAddrLValue(addr, E->getType(), AlignmentSource::Decl);
3511+
}

clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,3 +1809,54 @@ mlir::Value CIRGenFunction::emitDynamicCast(Address ThisAddr,
18091809
return CGM.getCXXABI().emitDynamicCast(*this, loc, srcRecordTy, destRecordTy,
18101810
destCirTy, isRefCast, ThisAddr);
18111811
}
1812+
1813+
mlir::Value CIRGenFunction::emitCXXTypeidExpr(const CXXTypeidExpr *E) {
1814+
auto loc = getLoc(E->getSourceRange());
1815+
1816+
// If this is a type operand, just get the address of the RTTI descriptor.
1817+
if (E->isTypeOperand()) {
1818+
QualType operandTy = E->getTypeOperand(getContext());
1819+
mlir::Attribute typeInfo = CGM.getAddrOfRTTIDescriptor(loc, operandTy);
1820+
1821+
// The RTTI descriptor is a GlobalViewAttr. Extract the symbol and look up
1822+
// the GlobalOp to get its symbol type, then create a GetGlobalOp.
1823+
auto globalView = mlir::cast<cir::GlobalViewAttr>(typeInfo);
1824+
auto *globalOp = mlir::SymbolTable::lookupSymbolIn(CGM.getModule(),
1825+
globalView.getSymbol());
1826+
assert(globalOp && "RTTI global not found");
1827+
auto global = mlir::cast<cir::GlobalOp>(globalOp);
1828+
1829+
// The result type of GetGlobalOp is a pointer to the global's symbol type.
1830+
auto ptrTy = builder.getPointerTo(global.getSymType());
1831+
return cir::GetGlobalOp::create(builder, loc, ptrTy,
1832+
globalView.getSymbol());
1833+
}
1834+
1835+
// C++ [expr.typeid]p2:
1836+
// When typeid is applied to a glvalue expression whose type is a
1837+
// polymorphic class type, the result refers to a std::type_info object
1838+
// representing the type of the most derived object (that is, the dynamic
1839+
// type) to which the glvalue refers.
1840+
1841+
// If the operand is already the most derived object, no need to look up
1842+
// vtable.
1843+
if (E->isPotentiallyEvaluated() && !E->isMostDerived(getContext())) {
1844+
// TODO: Implement vtable lookup for polymorphic types.
1845+
// This requires emitting code similar to dynamic_cast that looks up the
1846+
// type_info pointer from the vtable.
1847+
llvm_unreachable("NYI: typeid with polymorphic types (vtable lookup)");
1848+
}
1849+
1850+
// For non-polymorphic types, just return the static RTTI descriptor.
1851+
QualType operandTy = E->getExprOperand()->getType();
1852+
mlir::Attribute typeInfo = CGM.getAddrOfRTTIDescriptor(loc, operandTy);
1853+
1854+
auto globalView = mlir::cast<cir::GlobalViewAttr>(typeInfo);
1855+
auto *globalOp = mlir::SymbolTable::lookupSymbolIn(CGM.getModule(),
1856+
globalView.getSymbol());
1857+
assert(globalOp && "RTTI global not found");
1858+
auto global = mlir::cast<cir::GlobalOp>(globalOp);
1859+
1860+
auto ptrTy = builder.getPointerTo(global.getSymType());
1861+
return cir::GetGlobalOp::create(builder, loc, ptrTy, globalView.getSymbol());
1862+
}

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2134,6 +2134,12 @@ class CIRGenFunction : public CIRGenTypeCache {
21342134

21352135
mlir::Value emitDynamicCast(Address ThisAddr, const CXXDynamicCastExpr *DCE);
21362136

2137+
/// Emit the operand of a typeid expression as an mlir::Value.
2138+
mlir::Value emitCXXTypeidExpr(const CXXTypeidExpr *E);
2139+
2140+
/// Emit a typeid expression as an l-value.
2141+
LValue emitCXXTypeidLValue(const CXXTypeidExpr *E);
2142+
21372143
/// Emits try/catch information for the current EH stack.
21382144
void emitEHResumeBlock(bool isCleanup, mlir::Block *ehResumeBlock,
21392145
mlir::Location loc);
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// RUN: %clang_cc1 -std=c++17 -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 -std=c++17 -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 -std=c++17 -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+
const char* name() const { return __name; }
13+
bool operator==(const type_info& __arg) const {
14+
return __name == __arg.__name;
15+
}
16+
17+
bool operator!=(const type_info& __arg) const {
18+
return !operator==(__arg);
19+
}
20+
21+
bool before(const type_info& __arg) const {
22+
return __name < __arg.__name;
23+
}
24+
25+
unsigned long hash_code() const {
26+
return reinterpret_cast<unsigned long long>(__name);
27+
}
28+
protected:
29+
const char *__name;
30+
};
31+
}
32+
33+
// Test 1: Non-polymorphic type - simple struct
34+
struct Simple {
35+
int x;
36+
};
37+
38+
void test_simple_type(const std::type_info*& out) {
39+
// CIR-LABEL: cir.func {{.*}}@_Z16test_simple_type
40+
out = &typeid(Simple);
41+
// CIR: cir.get_global @_ZTI6Simple
42+
43+
// LLVM-LABEL: define {{.*}}@_Z16test_simple_type
44+
// LLVM: store ptr @_ZTI6Simple
45+
46+
// OGCG-LABEL: define {{.*}}@_Z16test_simple_type
47+
// OGCG: store ptr @_ZTI6Simple
48+
}
49+
50+
// Test 2: Non-polymorphic type - expression operand
51+
void test_expression_operand(const std::type_info*& out) {
52+
// CIR-LABEL: cir.func {{.*}}@_Z23test_expression_operand
53+
Simple s;
54+
out = &typeid(s);
55+
// CIR: cir.get_global @_ZTI6Simple
56+
57+
// LLVM-LABEL: define {{.*}}@_Z23test_expression_operand
58+
// LLVM: store ptr @_ZTI6Simple
59+
60+
// OGCG-LABEL: define {{.*}}@_Z23test_expression_operand
61+
// OGCG: store ptr @_ZTI6Simple
62+
}
63+
64+
// Test 3: Polymorphic base class
65+
struct Base {
66+
virtual ~Base() = default;
67+
};
68+
69+
struct Derived : Base {
70+
int y;
71+
};
72+
73+
// Test with non-polymorphic lookup (type operand)
74+
void test_polymorphic_type(const std::type_info*& out) {
75+
// CIR-LABEL: cir.func {{.*}}@_Z21test_polymorphic_type
76+
out = &typeid(Base);
77+
// CIR: cir.get_global @_ZTI4Base
78+
79+
// LLVM-LABEL: define {{.*}}@_Z21test_polymorphic_type
80+
// LLVM: store ptr @_ZTI4Base
81+
82+
// OGCG-LABEL: define {{.*}}@_Z21test_polymorphic_type
83+
// OGCG: store ptr @_ZTI4Base
84+
}
85+
86+
// Test 4: Built-in type
87+
void test_builtin_type(const std::type_info*& out) {
88+
// CIR-LABEL: cir.func {{.*}}@_Z17test_builtin_type
89+
out = &typeid(int);
90+
// CIR: cir.get_global @_ZTIi
91+
92+
// LLVM-LABEL: define {{.*}}@_Z17test_builtin_type
93+
// LLVM: store ptr @_ZTIi
94+
95+
// OGCG-LABEL: define {{.*}}@_Z17test_builtin_type
96+
// OGCG: store ptr @_ZTIi
97+
}
98+
99+
// Test 5: Passing typeid as function argument
100+
void consume_type_info(const std::type_info& ti) {
101+
// CIR-LABEL: cir.func {{.*}}@_Z17consume_type_info
102+
103+
// LLVM-LABEL: define {{.*}}@_Z17consume_type_info
104+
105+
// OGCG-LABEL: define {{.*}}@_Z17consume_type_info
106+
}
107+
108+
void test_function_argument() {
109+
// CIR-LABEL: cir.func {{.*}}@_Z22test_function_argumentv
110+
consume_type_info(typeid(int));
111+
// CIR: cir.get_global @_ZTIi
112+
// CIR: cir.call @_Z17consume_type_info
113+
114+
// LLVM-LABEL: define {{.*}}@_Z22test_function_argumentv
115+
// LLVM: call {{.*}}@_Z17consume_type_infoRKSt9type_info(ptr @_ZTIi)
116+
117+
// OGCG-LABEL: define {{.*}}@_Z22test_function_argumentv
118+
// OGCG: call {{.*}}@_Z17consume_type_infoRKSt9type_info(ptr {{.*}}@_ZTIi)
119+
}

0 commit comments

Comments
 (0)