Skip to content

Commit

Permalink
[CIR] initial support for pointer-to-data-member type (llvm#401)
Browse files Browse the repository at this point in the history
This patch adds initial support for the pointer-to-data-member type.
Specifically, this commit includes:

- New ops, types, and attributes:
- CodeGen for pointer-to-data-member types and values
  - Lower C++ pointer-to-member type
  - Lower C++ expression `&C::D`
  - Lower C++ expression `c.*p` and `c->*p`

This patch only includes an initial support. The following stuff related
to pointer-to-member types are not supported yet:

- Pointer to member function;
- Conversion from `T Base::*` to `T Derived::*`;
- LLVMIR lowering.
  • Loading branch information
Lancern authored and lanza committed Oct 1, 2024
1 parent 669930b commit d822ec9
Show file tree
Hide file tree
Showing 18 changed files with 589 additions and 18 deletions.
34 changes: 34 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,40 @@ def ConstPtrAttr : CIR_Attr<"ConstPtr", "ptr", [TypedAttrInterface]> {
let hasCustomAssemblyFormat = 1;
}

//===----------------------------------------------------------------------===//
// DataMemberAttr
//===----------------------------------------------------------------------===//

def DataMemberAttr : CIR_Attr<"DataMember", "data_member",
[TypedAttrInterface]> {
let summary = "Holds a constant data member pointer value";
let parameters = (ins AttributeSelfTypeParameter<
"", "mlir::cir::DataMemberType">:$type,
OptionalParameter<
"std::optional<size_t>">:$memberIndex);
let description = [{
A data member attribute is a literal attribute that represents a constant
pointer-to-data-member value.

The `memberIndex` parameter represents the index of the pointed-to member
within its containing struct. It is an optional parameter; lack of this
parameter indicates a null pointer-to-data-member value.

Example:
```
#ptr = #cir.data_member<1> : !cir.data_member<!s32i in !ty_22Point22>

#null = #cir.data_member<null> : !cir.data_member<!s32i in !ty_22Point22>
```
}];

let genVerifyDecl = 1;

let assemblyFormat = [{
`<` ($memberIndex^):(`null`)? `>`
}];
}

//===----------------------------------------------------------------------===//
// SignedOverflowBehaviorAttr
//===----------------------------------------------------------------------===//
Expand Down
57 changes: 57 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,63 @@ def GetMemberOp : CIR_Op<"get_member"> {
let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// GetRuntimeMemberOp
//===----------------------------------------------------------------------===//

def GetRuntimeMemberOp : CIR_Op<"get_runtime_member"> {
let summary = "Get the address of a member of a struct";
let description = [{
The `cir.get_runtime_member` operation gets the address of a member from
the input record. The target member is given by a value of type
`!cir.data_member` (i.e. a pointer-to-data-member value).

This operation differs from `cir.get_member` in when the target member can
be determined. For the `cir.get_member` operation, the target member is
specified as a constant index so the member it returns access to is known
when the operation is constructed. For the `cir.get_runtime_member`
operation, the target member is given through a pointer-to-data-member
value which is unknown until the program being compiled is executed. In
other words, `cir.get_member` represents a normal member access through the
`.` operator in C/C++:

```cpp
struct Foo { int x; };
Foo f;
(void)f.x; // cir.get_member
```

And `cir.get_runtime_member` represents a member access through the `.*` or
the `->*` operator in C++:

```cpp
struct Foo { int x; }
Foo f;
Foo *p;
int Foo::*member;

(void)f.*member; // cir.get_runtime_member
(void)f->*member; // cir.get_runtime_member
```

This operation expects a pointer to the base record as well as the pointer
to the target member.
}];

let arguments = (ins
Arg<StructPtr, "address of the struct object", [MemRead]>:$addr,
Arg<CIR_DataMemberType, "pointer to the target member">:$member);

let results = (outs Res<CIR_PointerType, "">:$result);

let assemblyFormat = [{
$addr `[` $member `:` qualified(type($member)) `]` attr-dict
`:` qualified(type($addr)) `->` qualified(type($result))
}];

let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// VecInsertOp
//===----------------------------------------------------------------------===//
Expand Down
14 changes: 7 additions & 7 deletions clang/include/clang/CIR/Dialect/IR/CIRTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,6 @@

#include "clang/CIR/Interfaces/ASTAttrInterfaces.h"

//===----------------------------------------------------------------------===//
// CIR Dialect Tablegen'd Types
//===----------------------------------------------------------------------===//

#define GET_TYPEDEF_CLASSES
#include "clang/CIR/Dialect/IR/CIROpsTypes.h.inc"

//===----------------------------------------------------------------------===//
// CIR StructType
//
Expand Down Expand Up @@ -184,4 +177,11 @@ class StructType
} // namespace cir
} // namespace mlir

//===----------------------------------------------------------------------===//
// CIR Dialect Tablegen'd Types
//===----------------------------------------------------------------------===//

#define GET_TYPEDEF_CLASSES
#include "clang/CIR/Dialect/IR/CIROpsTypes.h.inc"

#endif // MLIR_DIALECT_CIR_IR_CIRTYPES_H_
36 changes: 34 additions & 2 deletions clang/include/clang/CIR/Dialect/IR/CIRTypes.td
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,28 @@ def CIR_PointerType : CIR_Type<"Pointer", "ptr",
let hasCustomAssemblyFormat = 1;
}

//===----------------------------------------------------------------------===//
// DataMemberType
//===----------------------------------------------------------------------===//

def CIR_DataMemberType : CIR_Type<"DataMember", "data_member",
[DeclareTypeInterfaceMethods<DataLayoutTypeInterface>]> {

let summary = "CIR type that represents pointer-to-data-member type in C++";
let description = [{
`cir.member_ptr` models the pointer-to-data-member type in C++. Values of
this type are essentially offsets of the pointed-to member within one of
its containing struct.
}];

let parameters = (ins "mlir::Type":$memberTy,
"mlir::cir::StructType":$clsTy);

let assemblyFormat = [{
`<` $memberTy `in` $clsTy `>`
}];
}

//===----------------------------------------------------------------------===//
// BoolType
//
Expand Down Expand Up @@ -309,6 +331,15 @@ def VoidPtr : Type<
"mlir::cir::VoidType::get($_builder.getContext()))"> {
}

// Pointer to struct
def StructPtr : Type<
And<[
CPred<"$_self.isa<::mlir::cir::PointerType>()">,
CPred<"$_self.cast<::mlir::cir::PointerType>()"
".getPointee().isa<::mlir::cir::StructType>()">,
]>, "!cir.struct*"> {
}

// Pointers to exception info
def ExceptionInfoPtr : Type<
And<[
Expand Down Expand Up @@ -351,8 +382,9 @@ def CIR_StructType : Type<CPred<"$_self.isa<::mlir::cir::StructType>()">,
//===----------------------------------------------------------------------===//

def CIR_AnyType : AnyTypeOf<[
CIR_IntType, CIR_PointerType, CIR_BoolType, CIR_ArrayType, CIR_VectorType,
CIR_FuncType, CIR_VoidType, CIR_StructType, CIR_ExceptionInfo, CIR_AnyFloat,
CIR_IntType, CIR_PointerType, CIR_DataMemberType, CIR_BoolType, CIR_ArrayType,
CIR_VectorType, CIR_FuncType, CIR_VoidType, CIR_StructType, CIR_ExceptionInfo,
CIR_AnyFloat,
]>;

#endif // MLIR_CIR_DIALECT_CIR_TYPES
29 changes: 29 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,16 @@ class CIRGenBuilderTy : public CIRBaseBuilderTy {
return mlir::cir::TypeInfoAttr::get(anonStruct.getType(), fieldsAttr);
}

mlir::cir::DataMemberAttr getDataMemberAttr(mlir::cir::DataMemberType ty,
size_t memberIndex) {
return mlir::cir::DataMemberAttr::get(getContext(), ty, memberIndex);
}

mlir::cir::DataMemberAttr
getNullDataMemberAttr(mlir::cir::DataMemberType ty) {
return mlir::cir::DataMemberAttr::get(getContext(), ty, std::nullopt);
}

mlir::TypedAttr getZeroInitAttr(mlir::Type ty) {
if (ty.isa<mlir::cir::IntType>())
return mlir::cir::IntAttr::get(ty, 0);
Expand Down Expand Up @@ -551,6 +561,12 @@ class CIRGenBuilderTy : public CIRBaseBuilderTy {
return create<mlir::cir::ConstantOp>(loc, ty, getConstPtrAttr(ty, 0));
}

/// Create constant nullptr for pointer-to-data-member type ty.
mlir::cir::ConstantOp getNullDataMemberPtr(mlir::cir::DataMemberType ty,
mlir::Location loc) {
return create<mlir::cir::ConstantOp>(loc, ty, getNullDataMemberAttr(ty));
}

// Creates constant null value for integral type ty.
mlir::cir::ConstantOp getNullValue(mlir::Type ty, mlir::Location loc) {
return create<mlir::cir::ConstantOp>(loc, ty, getZeroInitAttr(ty));
Expand Down Expand Up @@ -866,6 +882,19 @@ class CIRGenBuilderTy : public CIRBaseBuilderTy {
}
}

mlir::cir::GetRuntimeMemberOp createGetIndirectMember(mlir::Location loc,
mlir::Value objectPtr,
mlir::Value memberPtr) {
auto memberPtrTy = memberPtr.getType().cast<mlir::cir::DataMemberType>();

// TODO(cir): consider address space.
assert(!UnimplementedFeature::addressSpace());
auto resultTy = getPointerTo(memberPtrTy.getMemberTy());

return create<mlir::cir::GetRuntimeMemberOp>(loc, resultTy, objectPtr,
memberPtr);
}

mlir::Value createPtrIsNull(mlir::Value ptr) {
return createNot(createPtrToBoolCast(ptr));
}
Expand Down
58 changes: 58 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1475,3 +1475,61 @@ mlir::Value CIRGenFunction::getVTablePtr(mlir::Location Loc, Address This,

return VTable;
}

Address CIRGenFunction::buildCXXMemberDataPointerAddress(
const Expr *E, Address base, mlir::Value memberPtr,
const MemberPointerType *memberPtrType, LValueBaseInfo *baseInfo) {
assert(!UnimplementedFeature::cxxABI());

auto op = builder.createGetIndirectMember(getLoc(E->getSourceRange()),
base.getPointer(), memberPtr);

QualType memberType = memberPtrType->getPointeeType();
CharUnits memberAlign = CGM.getNaturalTypeAlignment(memberType, baseInfo);
memberAlign = CGM.getDynamicOffsetAlignment(
base.getAlignment(), memberPtrType->getClass()->getAsCXXRecordDecl(),
memberAlign);

return Address(op, convertTypeForMem(memberPtrType->getPointeeType()),
memberAlign);
}

clang::CharUnits
CIRGenModule::getDynamicOffsetAlignment(clang::CharUnits actualBaseAlign,
const clang::CXXRecordDecl *baseDecl,
clang::CharUnits expectedTargetAlign) {
// If the base is an incomplete type (which is, alas, possible with
// member pointers), be pessimistic.
if (!baseDecl->isCompleteDefinition())
return std::min(actualBaseAlign, expectedTargetAlign);

auto &baseLayout = getASTContext().getASTRecordLayout(baseDecl);
CharUnits expectedBaseAlign = baseLayout.getNonVirtualAlignment();

// If the class is properly aligned, assume the target offset is, too.
//
// This actually isn't necessarily the right thing to do --- if the
// class is a complete object, but it's only properly aligned for a
// base subobject, then the alignments of things relative to it are
// probably off as well. (Note that this requires the alignment of
// the target to be greater than the NV alignment of the derived
// class.)
//
// However, our approach to this kind of under-alignment can only
// ever be best effort; after all, we're never going to propagate
// alignments through variables or parameters. Note, in particular,
// that constructing a polymorphic type in an address that's less
// than pointer-aligned will generally trap in the constructor,
// unless we someday add some sort of attribute to change the
// assumed alignment of 'this'. So our goal here is pretty much
// just to allow the user to explicitly say that a pointer is
// under-aligned and then safely access its fields and vtables.
if (actualBaseAlign >= expectedBaseAlign) {
return expectedTargetAlign;
}

// Otherwise, we might be offset by an arbitrary multiple of the
// actual alignment. The correct adjustment is to take the min of
// the two alignments.
return std::min(actualBaseAlign, expectedTargetAlign);
}
26 changes: 25 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,30 @@ LValue CIRGenFunction::buildDeclRefLValue(const DeclRefExpr *E) {
llvm_unreachable("Unhandled DeclRefExpr");
}

LValue
CIRGenFunction::buildPointerToDataMemberBinaryExpr(const BinaryOperator *E) {
assert((E->getOpcode() == BO_PtrMemD || E->getOpcode() == BO_PtrMemI) &&
"unexpected binary operator opcode");

auto baseAddr = Address::invalid();
if (E->getOpcode() == BO_PtrMemD)
baseAddr = buildLValue(E->getLHS()).getAddress();
else
baseAddr = buildPointerWithAlignment(E->getLHS());

const auto *memberPtrTy = E->getRHS()->getType()->castAs<MemberPointerType>();

auto memberPtr = buildScalarExpr(E->getRHS());

LValueBaseInfo baseInfo;
// TODO(cir): add TBAA
assert(!UnimplementedFeature::tbaa());
auto memberAddr = buildCXXMemberDataPointerAddress(E, baseAddr, memberPtr,
memberPtrTy, &baseInfo);

return makeAddrLValue(memberAddr, memberPtrTy->getPointeeType(), baseInfo);
}

LValue CIRGenFunction::buildBinaryOperatorLValue(const BinaryOperator *E) {
// Comma expressions just emit their LHS then their RHS as an l-value.
if (E->getOpcode() == BO_Comma) {
Expand All @@ -899,7 +923,7 @@ LValue CIRGenFunction::buildBinaryOperatorLValue(const BinaryOperator *E) {
}

if (E->getOpcode() == BO_PtrMemD || E->getOpcode() == BO_PtrMemI)
assert(0 && "not implemented");
return buildPointerToDataMemberBinaryExpr(E);

assert(E->getOpcode() == BO_Assign && "unexpected binary l-value");

Expand Down
36 changes: 35 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenExprConst.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1763,6 +1763,21 @@ mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &Value,
return buildArrayConstant(CGM, Desired, CommonElementType, NumElements,
Elts, typedFiller);
}
case APValue::MemberPointer: {
assert(!UnimplementedFeature::cxxABI());

const ValueDecl *memberDecl = Value.getMemberPointerDecl();
assert(!Value.isMemberPointerToDerivedMember() && "NYI");

if (const auto *memberFuncDecl = dyn_cast<CXXMethodDecl>(memberDecl))
assert(0 && "not implemented");

auto cirTy =
CGM.getTypes().ConvertType(DestType).cast<mlir::cir::DataMemberType>();

const auto *fieldDecl = cast<FieldDecl>(memberDecl);
return builder.getDataMemberAttr(cirTy, fieldDecl->getFieldIndex());
}
case APValue::LValue:
return ConstantLValueEmitter(*this, Value, DestType).tryEmit();
case APValue::Struct:
Expand All @@ -1773,7 +1788,6 @@ mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &Value,
case APValue::ComplexFloat:
case APValue::Vector:
case APValue::AddrLabelDiff:
case APValue::MemberPointer:
assert(0 && "not implemented");
}
llvm_unreachable("Unknown APValue kind");
Expand Down Expand Up @@ -1802,6 +1816,26 @@ mlir::Value CIRGenModule::buildNullConstant(QualType T, mlir::Location loc) {
return {};
}

mlir::Value CIRGenModule::buildMemberPointerConstant(const UnaryOperator *E) {
assert(!UnimplementedFeature::cxxABI());

auto loc = getLoc(E->getSourceRange());

const auto *decl = cast<DeclRefExpr>(E->getSubExpr())->getDecl();

// A member function pointer.
// Member function pointer is not supported yet.
if (const auto *methodDecl = dyn_cast<CXXMethodDecl>(decl))
assert(0 && "not implemented");

auto ty = getCIRType(E->getType()).cast<mlir::cir::DataMemberType>();

// Otherwise, a member data pointer.
const auto *fieldDecl = cast<FieldDecl>(decl);
return builder.create<mlir::cir::ConstantOp>(
loc, ty, builder.getDataMemberAttr(ty, fieldDecl->getFieldIndex()));
}

mlir::Attribute ConstantEmitter::emitAbstract(const Expr *E,
QualType destType) {
auto state = pushAbstract();
Expand Down
Loading

0 comments on commit d822ec9

Please sign in to comment.