Skip to content

Commit e6b7559

Browse files
andykaylorsvkeerthy
authored andcommitted
[CIR] Add initial support for dynamic cast (#162337)
This adds support for dynamic cast handling and generating `cir.dyn_cast` operations and `cir.dyn_cast_info` attributes. This does not include support for lowering the dynamic cast to LLVM IR, which will require changes to the LoweringPrepare pass that will be made in a future change. This also does not yet handle dynamic cast to void or exact dynamic casts.
1 parent 35dfc77 commit e6b7559

File tree

12 files changed

+315
-3
lines changed

12 files changed

+315
-3
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,12 @@ struct MissingFeatures {
7979
static bool opFuncExtraAttrs() { return false; }
8080
static bool opFuncMaybeHandleStaticInExternC() { return false; }
8181
static bool opFuncMultipleReturnVals() { return false; }
82+
static bool opFuncNoUnwind() { return false; }
8283
static bool opFuncOperandBundles() { return false; }
8384
static bool opFuncParameterAttributes() { return false; }
85+
static bool opFuncReadOnly() { return false; }
8486
static bool opFuncSection() { return false; }
87+
static bool opFuncWillReturn() { return false; }
8588
static bool setLLVMFunctionFEnvAttributes() { return false; }
8689
static bool setFunctionAttributes() { return false; }
8790

clang/lib/CIR/CodeGen/CIRGenBuilder.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
122122
return getPointerTo(cir::VPtrType::get(getContext()));
123123
}
124124

125+
cir::FuncType getFuncType(llvm::ArrayRef<mlir::Type> params, mlir::Type retTy,
126+
bool isVarArg = false) {
127+
return cir::FuncType::get(params, retTy, isVarArg);
128+
}
129+
125130
/// Get a CIR record kind from a AST declaration tag.
126131
cir::RecordType::RecordKind getRecordKind(const clang::TagTypeKind kind) {
127132
switch (kind) {
@@ -372,6 +377,15 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
372377
return cir::BinOp::create(*this, loc, cir::BinOpKind::Div, lhs, rhs);
373378
}
374379

380+
mlir::Value createDynCast(mlir::Location loc, mlir::Value src,
381+
cir::PointerType destType, bool isRefCast,
382+
cir::DynamicCastInfoAttr info) {
383+
auto castKind =
384+
isRefCast ? cir::DynamicCastKind::Ref : cir::DynamicCastKind::Ptr;
385+
return cir::DynamicCastOp::create(*this, loc, destType, castKind, src, info,
386+
/*relative_layout=*/false);
387+
}
388+
375389
Address createBaseClassAddr(mlir::Location loc, Address addr,
376390
mlir::Type destType, unsigned offset,
377391
bool assumeNotNull) {

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ class CIRGenCXXABI {
5454
Address thisAddr, const CXXRecordDecl *classDecl,
5555
const CXXRecordDecl *baseClassDecl) = 0;
5656

57+
virtual mlir::Value emitDynamicCast(CIRGenFunction &cgf, mlir::Location loc,
58+
QualType srcRecordTy,
59+
QualType destRecordTy,
60+
cir::PointerType destCIRTy,
61+
bool isRefCast, Address src) = 0;
62+
5763
public:
5864
/// Similar to AddedStructorArgs, but only notes the number of additional
5965
/// arguments.

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1185,10 +1185,16 @@ LValue CIRGenFunction::emitCastLValue(const CastExpr *e) {
11851185
case CK_BuiltinFnToFnPtr:
11861186
llvm_unreachable("builtin functions are handled elsewhere");
11871187

1188+
case CK_Dynamic: {
1189+
LValue lv = emitLValue(e->getSubExpr());
1190+
Address v = lv.getAddress();
1191+
const auto *dce = cast<CXXDynamicCastExpr>(e);
1192+
return makeNaturalAlignAddrLValue(emitDynamicCast(v, dce), e->getType());
1193+
}
1194+
11881195
// These are never l-values; just use the aggregate emission code.
11891196
case CK_NonAtomicToAtomic:
11901197
case CK_AtomicToNonAtomic:
1191-
case CK_Dynamic:
11921198
case CK_ToUnion:
11931199
case CK_BaseToDerived:
11941200
case CK_AddressSpaceConversion:

clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,3 +722,43 @@ void CIRGenFunction::emitDeleteCall(const FunctionDecl *deleteFD,
722722
// Emit the call to delete.
723723
emitNewDeleteCall(*this, deleteFD, deleteFTy, deleteArgs);
724724
}
725+
726+
mlir::Value CIRGenFunction::emitDynamicCast(Address thisAddr,
727+
const CXXDynamicCastExpr *dce) {
728+
mlir::Location loc = getLoc(dce->getSourceRange());
729+
730+
cgm.emitExplicitCastExprType(dce, this);
731+
QualType destTy = dce->getTypeAsWritten();
732+
QualType srcTy = dce->getSubExpr()->getType();
733+
734+
// C++ [expr.dynamic.cast]p7:
735+
// If T is "pointer to cv void," then the result is a pointer to the most
736+
// derived object pointed to by v.
737+
bool isDynCastToVoid = destTy->isVoidPointerType();
738+
bool isRefCast = destTy->isReferenceType();
739+
740+
QualType srcRecordTy;
741+
QualType destRecordTy;
742+
if (isDynCastToVoid) {
743+
srcRecordTy = srcTy->getPointeeType();
744+
// No destRecordTy.
745+
} else if (const PointerType *destPTy = destTy->getAs<PointerType>()) {
746+
srcRecordTy = srcTy->castAs<PointerType>()->getPointeeType();
747+
destRecordTy = destPTy->getPointeeType();
748+
} else {
749+
srcRecordTy = srcTy;
750+
destRecordTy = destTy->castAs<ReferenceType>()->getPointeeType();
751+
}
752+
753+
assert(srcRecordTy->isRecordType() && "source type must be a record type!");
754+
assert(!cir::MissingFeatures::emitTypeCheck());
755+
756+
if (dce->isAlwaysNull()) {
757+
cgm.errorNYI(dce->getSourceRange(), "emitDynamicCastToNull");
758+
return {};
759+
}
760+
761+
auto destCirTy = mlir::cast<cir::PointerType>(convertType(destTy));
762+
return cgm.getCXXABI().emitDynamicCast(*this, loc, srcRecordTy, destRecordTy,
763+
destCirTy, isRefCast, thisAddr);
764+
}

clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,6 +1916,11 @@ mlir::Value ScalarExprEmitter::VisitCastExpr(CastExpr *ce) {
19161916
return builder.createIntToPtr(middleVal, destCIRTy);
19171917
}
19181918

1919+
case CK_Dynamic: {
1920+
Address v = cgf.emitPointerWithAlignment(subExpr);
1921+
const auto *dce = cast<CXXDynamicCastExpr>(ce);
1922+
return cgf.emitDynamicCast(v, dce);
1923+
}
19191924
case CK_ArrayToPointerDecay:
19201925
return cgf.emitArrayToPointerDecay(subExpr).getPointer();
19211926

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,6 +1312,8 @@ class CIRGenFunction : public CIRGenTypeCache {
13121312

13131313
mlir::LogicalResult emitDoStmt(const clang::DoStmt &s);
13141314

1315+
mlir::Value emitDynamicCast(Address thisAddr, const CXXDynamicCastExpr *dce);
1316+
13151317
/// Emit an expression as an initializer for an object (variable, field, etc.)
13161318
/// at the given location. The expression is not necessarily the normal
13171319
/// initializer for the object, and the address is not necessarily

clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,16 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
118118
Address thisAddr, const CXXRecordDecl *classDecl,
119119
const CXXRecordDecl *baseClassDecl) override;
120120

121+
// The traditional clang CodeGen emits calls to `__dynamic_cast` directly into
122+
// LLVM in the `emitDynamicCastCall` function. In CIR, `dynamic_cast`
123+
// expressions are lowered to `cir.dyn_cast` ops instead of calls to runtime
124+
// functions. So during CIRGen we don't need the `emitDynamicCastCall`
125+
// function that clang CodeGen has.
126+
mlir::Value emitDynamicCast(CIRGenFunction &cgf, mlir::Location loc,
127+
QualType srcRecordTy, QualType destRecordTy,
128+
cir::PointerType destCIRTy, bool isRefCast,
129+
Address src) override;
130+
121131
/**************************** RTTI Uniqueness ******************************/
122132
protected:
123133
/// Returns true if the ABI requires RTTI type_info objects to be unique
@@ -1819,3 +1829,143 @@ mlir::Value CIRGenItaniumCXXABI::getVirtualBaseClassOffset(
18191829
}
18201830
return vbaseOffset;
18211831
}
1832+
1833+
static cir::FuncOp getBadCastFn(CIRGenFunction &cgf) {
1834+
// Prototype: void __cxa_bad_cast();
1835+
1836+
// TODO(cir): set the calling convention of the runtime function.
1837+
assert(!cir::MissingFeatures::opFuncCallingConv());
1838+
1839+
cir::FuncType fnTy =
1840+
cgf.getBuilder().getFuncType({}, cgf.getBuilder().getVoidTy());
1841+
return cgf.cgm.createRuntimeFunction(fnTy, "__cxa_bad_cast");
1842+
}
1843+
1844+
// TODO(cir): This could be shared with classic codegen.
1845+
static CharUnits computeOffsetHint(ASTContext &astContext,
1846+
const CXXRecordDecl *src,
1847+
const CXXRecordDecl *dst) {
1848+
CXXBasePaths paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true,
1849+
/*DetectVirtual=*/false);
1850+
1851+
// If Dst is not derived from Src we can skip the whole computation below and
1852+
// return that Src is not a public base of Dst. Record all inheritance paths.
1853+
if (!dst->isDerivedFrom(src, paths))
1854+
return CharUnits::fromQuantity(-2ULL);
1855+
1856+
unsigned numPublicPaths = 0;
1857+
CharUnits offset;
1858+
1859+
// Now walk all possible inheritance paths.
1860+
for (const CXXBasePath &path : paths) {
1861+
if (path.Access != AS_public) // Ignore non-public inheritance.
1862+
continue;
1863+
1864+
++numPublicPaths;
1865+
1866+
for (const CXXBasePathElement &pathElement : path) {
1867+
// If the path contains a virtual base class we can't give any hint.
1868+
// -1: no hint.
1869+
if (pathElement.Base->isVirtual())
1870+
return CharUnits::fromQuantity(-1ULL);
1871+
1872+
if (numPublicPaths > 1) // Won't use offsets, skip computation.
1873+
continue;
1874+
1875+
// Accumulate the base class offsets.
1876+
const ASTRecordLayout &L =
1877+
astContext.getASTRecordLayout(pathElement.Class);
1878+
offset += L.getBaseClassOffset(
1879+
pathElement.Base->getType()->getAsCXXRecordDecl());
1880+
}
1881+
}
1882+
1883+
// -2: Src is not a public base of Dst.
1884+
if (numPublicPaths == 0)
1885+
return CharUnits::fromQuantity(-2ULL);
1886+
1887+
// -3: Src is a multiple public base type but never a virtual base type.
1888+
if (numPublicPaths > 1)
1889+
return CharUnits::fromQuantity(-3ULL);
1890+
1891+
// Otherwise, the Src type is a unique public nonvirtual base type of Dst.
1892+
// Return the offset of Src from the origin of Dst.
1893+
return offset;
1894+
}
1895+
1896+
static cir::FuncOp getItaniumDynamicCastFn(CIRGenFunction &cgf) {
1897+
// Prototype:
1898+
// void *__dynamic_cast(const void *sub,
1899+
// global_as const abi::__class_type_info *src,
1900+
// global_as const abi::__class_type_info *dst,
1901+
// std::ptrdiff_t src2dst_offset);
1902+
1903+
mlir::Type voidPtrTy = cgf.getBuilder().getVoidPtrTy();
1904+
mlir::Type rttiPtrTy = cgf.getBuilder().getUInt8PtrTy();
1905+
mlir::Type ptrDiffTy = cgf.convertType(cgf.getContext().getPointerDiffType());
1906+
1907+
// TODO(cir): mark the function as nowind willreturn readonly.
1908+
assert(!cir::MissingFeatures::opFuncNoUnwind());
1909+
assert(!cir::MissingFeatures::opFuncWillReturn());
1910+
assert(!cir::MissingFeatures::opFuncReadOnly());
1911+
1912+
// TODO(cir): set the calling convention of the runtime function.
1913+
assert(!cir::MissingFeatures::opFuncCallingConv());
1914+
1915+
cir::FuncType FTy = cgf.getBuilder().getFuncType(
1916+
{voidPtrTy, rttiPtrTy, rttiPtrTy, ptrDiffTy}, voidPtrTy);
1917+
return cgf.cgm.createRuntimeFunction(FTy, "__dynamic_cast");
1918+
}
1919+
1920+
static cir::DynamicCastInfoAttr emitDynamicCastInfo(CIRGenFunction &cgf,
1921+
mlir::Location loc,
1922+
QualType srcRecordTy,
1923+
QualType destRecordTy) {
1924+
auto srcRtti = mlir::cast<cir::GlobalViewAttr>(
1925+
cgf.cgm.getAddrOfRTTIDescriptor(loc, srcRecordTy));
1926+
auto destRtti = mlir::cast<cir::GlobalViewAttr>(
1927+
cgf.cgm.getAddrOfRTTIDescriptor(loc, destRecordTy));
1928+
1929+
cir::FuncOp runtimeFuncOp = getItaniumDynamicCastFn(cgf);
1930+
cir::FuncOp badCastFuncOp = getBadCastFn(cgf);
1931+
auto runtimeFuncRef = mlir::FlatSymbolRefAttr::get(runtimeFuncOp);
1932+
auto badCastFuncRef = mlir::FlatSymbolRefAttr::get(badCastFuncOp);
1933+
1934+
const CXXRecordDecl *srcDecl = srcRecordTy->getAsCXXRecordDecl();
1935+
const CXXRecordDecl *destDecl = destRecordTy->getAsCXXRecordDecl();
1936+
CharUnits offsetHint = computeOffsetHint(cgf.getContext(), srcDecl, destDecl);
1937+
1938+
mlir::Type ptrdiffTy = cgf.convertType(cgf.getContext().getPointerDiffType());
1939+
auto offsetHintAttr = cir::IntAttr::get(ptrdiffTy, offsetHint.getQuantity());
1940+
1941+
return cir::DynamicCastInfoAttr::get(srcRtti, destRtti, runtimeFuncRef,
1942+
badCastFuncRef, offsetHintAttr);
1943+
}
1944+
1945+
mlir::Value CIRGenItaniumCXXABI::emitDynamicCast(CIRGenFunction &cgf,
1946+
mlir::Location loc,
1947+
QualType srcRecordTy,
1948+
QualType destRecordTy,
1949+
cir::PointerType destCIRTy,
1950+
bool isRefCast, Address src) {
1951+
bool isCastToVoid = destRecordTy.isNull();
1952+
assert((!isCastToVoid || !isRefCast) && "cannot cast to void reference");
1953+
1954+
if (isCastToVoid) {
1955+
cgm.errorNYI(loc, "emitDynamicCastToVoid");
1956+
return {};
1957+
}
1958+
1959+
// If the destination is effectively final, the cast succeeds if and only
1960+
// if the dynamic type of the pointer is exactly the destination type.
1961+
if (destRecordTy->getAsCXXRecordDecl()->isEffectivelyFinal() &&
1962+
cgf.cgm.getCodeGenOpts().OptimizationLevel > 0) {
1963+
cgm.errorNYI(loc, "emitExactDynamicCast");
1964+
return {};
1965+
}
1966+
1967+
cir::DynamicCastInfoAttr castInfo =
1968+
emitDynamicCastInfo(cgf, loc, srcRecordTy, destRecordTy);
1969+
return cgf.getBuilder().createDynCast(loc, src.getPointer(), destCIRTy,
1970+
isRefCast, castInfo);
1971+
}

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2079,6 +2079,29 @@ CIRGenModule::createCIRBuiltinFunction(mlir::Location loc, StringRef name,
20792079
return fnOp;
20802080
}
20812081

2082+
cir::FuncOp CIRGenModule::createRuntimeFunction(cir::FuncType ty,
2083+
StringRef name, mlir::ArrayAttr,
2084+
[[maybe_unused]] bool isLocal,
2085+
bool assumeConvergent) {
2086+
if (assumeConvergent)
2087+
errorNYI("createRuntimeFunction: assumeConvergent");
2088+
if (isLocal)
2089+
errorNYI("createRuntimeFunction: local");
2090+
2091+
cir::FuncOp entry = getOrCreateCIRFunction(name, ty, GlobalDecl(),
2092+
/*forVtable=*/false);
2093+
2094+
if (entry) {
2095+
// TODO(cir): set the attributes of the function.
2096+
assert(!cir::MissingFeatures::setLLVMFunctionFEnvAttributes());
2097+
assert(!cir::MissingFeatures::opFuncCallingConv());
2098+
assert(!cir::MissingFeatures::opGlobalDLLImportExport());
2099+
entry.setDSOLocal(true);
2100+
}
2101+
2102+
return entry;
2103+
}
2104+
20822105
mlir::SymbolTable::Visibility
20832106
CIRGenModule::getMLIRVisibility(cir::GlobalOp op) {
20842107
// MLIR doesn't accept public symbols declarations (only

clang/lib/CIR/CodeGen/CIRGenModule.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,10 @@ class CIRGenModule : public CIRGenTypeCache {
480480
cir::FuncType ty,
481481
const clang::FunctionDecl *fd);
482482

483+
cir::FuncOp createRuntimeFunction(cir::FuncType ty, llvm::StringRef name,
484+
mlir::ArrayAttr = {}, bool isLocal = false,
485+
bool assumeConvergent = false);
486+
483487
static constexpr const char *builtinCoroId = "__builtin_coro_id";
484488

485489
/// Given a builtin id for a function like "__builtin_fabsf", return a

0 commit comments

Comments
 (0)