Skip to content

Commit

Permalink
Add cir.dynamic_cast operation and add CIRGen for it
Browse files Browse the repository at this point in the history
  • Loading branch information
Lancern committed Feb 25, 2024
1 parent 0853a01 commit 6db25e3
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 68 deletions.
66 changes: 66 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,72 @@ def VTableAttr : CIR_Attr<"VTable", "vtable", [TypedAttrInterface]> {
}];
}

//===----------------------------------------------------------------------===//
// DynamicCastInfoAttr
//===----------------------------------------------------------------------===//

def DowncastInfoAttr : CIR_Attr<"DowncastInfo", "downcast_info"> {
let summary = "Provide information about a down-cast operation";
let description = [{
Provide information about a dynamic operation which is a down-cast.

When casting a pointer to class `From` to a pointer to class `To` and `From`
is a public base class of `To`, this attribute provides the following
parameters that give more detailed information about the cast:

- The `offset` parameter gives the offset of the `To` subobject within a
`From` object if `From` is a unique non-virtual public base of `To`.
- The `isVirtualBase` parameter indicates whether `From` is a virtual base
class of `To`.
}];

let parameters = (ins "std::optional<uint64_t>":$offset,
"bool":$isVirtualBase);

let genVerifyDecl = 1;
let assemblyFormat = [{
`<` $offset `,` $isVirtualBase `>`
}];
}

def DynamicCastInfoAttr : CIR_Attr<"DynamicCastInfo", "dyn_cast_info"> {
let summary = "Provide information about a dynamic cast operation";
let description = [{
Provide information about a dynamic cast operation.

The `srcRtti` and the `destRtti` parameters give the RTTI of the source
struct type and the destination struct type, respectively.

If the `downcastInfo` parameter is present, the dynamic cast operation that
this attribute attaches to is a down-cast. In such cases, the `downcastInfo`
parameter will provide more detailed information about the down-cast.
}];

let parameters = (ins GlobalViewAttr:$srcRtti,
GlobalViewAttr:$destRtti,
OptionalParameter<"DowncastInfoAttr">:$downcastInfo);

let builders = [
AttrBuilderWithInferredContext<(ins "GlobalViewAttr":$srcRtti,
"GlobalViewAttr":$destRtti), [{
return $_get(srcRtti.getContext(), srcRtti, destRtti, DowncastInfoAttr{});
}]>,
AttrBuilderWithInferredContext<(ins "GlobalViewAttr":$srcRtti,
"GlobalViewAttr":$destRtti,
"DowncastInfoAttr":$downcastInfo), [{
return $_get(srcRtti.getContext(), srcRtti, destRtti, downcastInfo);
}]>,
];

let genVerifyDecl = 1;
let assemblyFormat = [{
`<`
qualified($srcRtti) `,` qualified($destRtti)
(`,` qualified($downcastInfo)^)?
`>`
}];
}

//===----------------------------------------------------------------------===//
// AST Wrappers
//===----------------------------------------------------------------------===//
Expand Down
53 changes: 53 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,59 @@ def CastOp : CIR_Op<"cast", [Pure]> {
let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// DynamicCastOp
//===----------------------------------------------------------------------===//

def DynamicCastOp : CIR_Op<"dyn_cast"> {
let summary = "Perform dynamic cast on struct pointers";
let description = [{
The `cir.dyn_cast` operation models part of the semantics of the
`dynamic_cast` operator in C++. It can be used to perform 2 kinds of casts
on struct pointers:

- Down-cast, which casts a base class pointer to a derived class pointer;
- Side-cast, which casts a class pointer to a sibling class pointer.

The input of the operation must be a struct pointer. The result of the
operation is also a struct pointer.

An optional flag `refcast` can be used to further specify the semantics of
this operation. If the flag is present, the operation models dynamic casts
on references in C++; otherwise, the operation models dynamic casts on
pointers in C++. Specifically, the differences are:

- When the input pointer is a null pointer value:
- If the `refcast` flag is present, the operation will invoke undefined
behavior. A sanitizer check will be emitted if sanitizer is on.
- Otherwise, the operation will return a null pointer value as its result.
- When the runtime type check fails:
- If the `refcast` flag is present, the operation will throw a bad_cast
exception.
- Otherwise, the operation will return a null pointer value as its result.

The `info` argument gives detailed information about the requested dynamic
cast operation.
}];

let arguments = (ins StructPtr:$src,
UnitAttr:$refcast,
DynamicCastInfoAttr:$info);
let results = (outs StructPtr:$result);

let assemblyFormat = [{
`(` $src `:` type($src) (`,` `refcast` $refcast^)? `,` qualified($info) `)`
`->` type($result) attr-dict
}];

let extraClassDeclaration = [{
/// Determine whether this operation models reference casting in C++.
bool isRefcast() {
return getRefcast();
}
}];
}

//===----------------------------------------------------------------------===//
// ObjSizeOp
//===----------------------------------------------------------------------===//
Expand Down
7 changes: 7 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,13 @@ class CIRGenBuilderTy : public CIRBaseBuilderTy {
mlir::cir::CastKind::ptr_to_bool, v);
}

mlir::Value createDynCast(mlir::Location loc, mlir::Value src,
mlir::cir::PointerType destType, bool isRefCast,
mlir::cir::DynamicCastInfoAttr info) {
return create<mlir::cir::DynamicCastOp>(loc, destType, src, isRefCast,
info);
}

cir::Address createBaseClassAddr(mlir::Location loc, cir::Address addr,
mlir::Type destType) {
if (destType == addr.getElementType())
Expand Down
127 changes: 94 additions & 33 deletions clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//
//===----------------------------------------------------------------------===//

#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include <CIRGenCXXABI.h>
#include <CIRGenFunction.h>
#include <CIRGenModule.h>
Expand Down Expand Up @@ -931,6 +932,74 @@ static mlir::Value buildDynamicCastToNull(CIRGenFunction &CGF,
return NullPtrValue;
}

static mlir::cir::DynamicCastInfoAttr
computeDynamicCastInfo(CIRGenFunction &CGF, mlir::Location loc,
QualType srcRecordTy, QualType destRecordTy) {
auto srcRtti = CGF.CGM.getAddrOfRTTIDescriptor(loc, srcRecordTy)
.cast<mlir::cir::GlobalViewAttr>();
auto destRtti = CGF.CGM.getAddrOfRTTIDescriptor(loc, destRecordTy)
.cast<mlir::cir::GlobalViewAttr>();

// Query the inheritance paths from dest to src. Based on the returned
// information we can further determine whether the cast is a down-cast, and
// the details of the down-cast.

CXXBasePaths paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true,
/*DetectVirtual=*/false);

const CXXRecordDecl *srcDecl = srcRecordTy->getAsCXXRecordDecl();
const CXXRecordDecl *destDecl = destRecordTy->getAsCXXRecordDecl();
if (!destDecl->isDerivedFrom(srcDecl, paths)) {
// src type is not a base class of dest type.
return mlir::cir::DynamicCastInfoAttr::get(srcRtti, destRtti);
}

unsigned numPublicPaths = 0;
bool isVirtualBase = false;
CharUnits offsetInCharUnits;

for (const CXXBasePath &p : paths) {
// Ignore non-public inheritance.
if (p.Access != AS_public)
continue;

++numPublicPaths;

for (const CXXBasePathElement &el : p) {
if (el.Base->isVirtual()) {
isVirtualBase = true;
break;
}

if (numPublicPaths > 1) {
// The inheritance paths are ambiguous. No need to compute the offset.
continue;
}

// Accumulate the base class offsets.
const ASTRecordLayout &layout =
CGF.getContext().getASTRecordLayout(el.Class);
offsetInCharUnits +=
layout.getBaseClassOffset(el.Base->getType()->getAsCXXRecordDecl());
}
}

if (numPublicPaths == 0) {
// `src` is not a public base of `dest`.
return mlir::cir::DynamicCastInfoAttr::get(srcRtti, destRtti);
}

std::optional<uint64_t> offset;
if (!isVirtualBase && numPublicPaths == 1) {
// `src` is a unique public non-virtual base of `dest`.
offset = offsetInCharUnits.getQuantity();
}

auto downcastInfo = mlir::cir::DowncastInfoAttr::get(
CGF.getBuilder().getContext(), offset, isVirtualBase);
return mlir::cir::DynamicCastInfoAttr::get(srcRtti, destRtti, downcastInfo);
}

mlir::Value CIRGenFunction::buildDynamicCast(Address ThisAddr,
const CXXDynamicCastExpr *DCE) {
auto loc = getLoc(DCE->getSourceRange());
Expand All @@ -943,6 +1012,8 @@ mlir::Value CIRGenFunction::buildDynamicCast(Address ThisAddr,
// If T is "pointer to cv void," then the result is a pointer to the most
// derived object pointed to by v.
bool isDynCastToVoid = destTy->isVoidPointerType();
bool isRefCast = destTy->isReferenceType();

QualType srcRecordTy;
QualType destRecordTy;
if (isDynCastToVoid) {
Expand All @@ -956,45 +1027,35 @@ mlir::Value CIRGenFunction::buildDynamicCast(Address ThisAddr,
destRecordTy = destTy->castAs<ReferenceType>()->getPointeeType();
}

assert(srcRecordTy->isRecordType() && "source type must be a record type!");
buildTypeCheck(TCK_DynamicOperation, DCE->getExprLoc(), ThisAddr.getPointer(),
srcRecordTy);

if (DCE->isAlwaysNull())
return buildDynamicCastToNull(*this, loc, destTy);

assert(srcRecordTy->isRecordType() && "source type must be a record type!");
if (isDynCastToVoid) {
auto srcIsNull = builder.createPtrIsNull(ThisAddr.getPointer());
return builder
.create<mlir::cir::TernaryOp>(
loc, srcIsNull,
[&](mlir::OpBuilder &, mlir::Location) {
auto nullPtr =
builder.getNullPtr(builder.getVoidPtrTy(), loc).getResult();
builder.createYield(loc, nullPtr);
},
[&](mlir::OpBuilder &, mlir::Location) {
auto castedPtr = CGM.getCXXABI().buildDynamicCastToVoid(
*this, loc, ThisAddr, srcRecordTy);
builder.createYield(loc, castedPtr);
})
.getResult();
}

// C++ [expr.dynamic.cast]p4:
// If the value of v is a null pointer value in the pointer case, the result
// is the null pointer value of type T.
bool shouldNullCheckSrcValue =
CGM.getCXXABI().shouldDynamicCastCallBeNullChecked(srcTy->isPointerType(),
srcRecordTy);

auto buildDynamicCastAfterNullCheck = [&]() -> mlir::Value {
if (isDynCastToVoid)
return CGM.getCXXABI().buildDynamicCastToVoid(*this, loc, ThisAddr,
srcRecordTy);

assert(destRecordTy->isRecordType() &&
"destination type must be a record type!");
return CGM.getCXXABI().buildDynamicCastCall(
*this, loc, ThisAddr, srcRecordTy, destTy, destRecordTy);
};
assert(destRecordTy->isRecordType() && "dest type must be a record type!");

if (!shouldNullCheckSrcValue)
return buildDynamicCastAfterNullCheck();

mlir::Value srcValueIsNull = builder.createPtrIsNull(ThisAddr.getPointer());
return builder
.create<mlir::cir::TernaryOp>(
loc, srcValueIsNull,
[&](mlir::OpBuilder &, mlir::Location) {
builder.createYield(loc,
buildDynamicCastToNull(*this, loc, destTy));
},
[&](mlir::OpBuilder &, mlir::Location) {
builder.createYield(loc, buildDynamicCastAfterNullCheck());
})
.getResult();
auto destCirTy = ConvertType(destTy).cast<mlir::cir::PointerType>();
auto castInfo = computeDynamicCastInfo(*this, loc, srcRecordTy, destRecordTy);
return builder.createDynCast(loc, ThisAddr.getPointer(), destCirTy, isRefCast,
castInfo);
}
47 changes: 47 additions & 0 deletions clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,53 @@ DataMemberAttr::verify(function_ref<InFlightDiagnostic()> emitError,
return success();
}

//===----------------------------------------------------------------------===//
// DynamicCastInfoAttr definitions
//===----------------------------------------------------------------------===//

LogicalResult
DowncastInfoAttr::verify(function_ref<InFlightDiagnostic()> emitError,
std::optional<uint64_t> offset, bool isVirtualBase) {
if (isVirtualBase && offset.has_value()) {
emitError() << "cannot provide an offset for a virtual base";
return failure();
}

return success();
}

LogicalResult
DynamicCastInfoAttr::verify(function_ref<InFlightDiagnostic()> emitError,
mlir::cir::GlobalViewAttr srcRtti,
mlir::cir::GlobalViewAttr destRtti,
DowncastInfoAttr downcastInfo) {
auto isRttiPtr = [](mlir::Type ty) {
// RTTI pointers are !cir.ptr<!u8i>.

auto ptrTy = ty.dyn_cast<mlir::cir::PointerType>();
if (!ptrTy)
return false;

auto pointeeIntTy = ptrTy.getPointee().dyn_cast<mlir::cir::IntType>();
if (!pointeeIntTy)
return false;

return pointeeIntTy.isUnsigned() && pointeeIntTy.getWidth() == 8;
};

if (!isRttiPtr(srcRtti.getType())) {
emitError() << "srcRtti must be an RTTI pointer";
return failure();
}

if (!isRttiPtr(destRtti.getType())) {
emitError() << "destRtti must be an RTTI pointer";
return failure();
}

return success();
}

//===----------------------------------------------------------------------===//
// CIR Dialect
//===----------------------------------------------------------------------===//
Expand Down
Loading

0 comments on commit 6db25e3

Please sign in to comment.