Skip to content

[CIR] Add support for accessing members of base classes #143195

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,11 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
return createCast(loc, cir::CastKind::bitcast, src, newTy);
}

mlir::Value createPtrBitcast(mlir::Value src, mlir::Type newPointeeTy) {
assert(mlir::isa<cir::PointerType>(src.getType()) && "expected ptr src");
return createBitcast(src, getPointerTo(newPointeeTy));
}

//===--------------------------------------------------------------------===//
// Binary Operators
//===--------------------------------------------------------------------===//
Expand Down
43 changes: 43 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -2231,4 +2231,47 @@ def VecTernaryOp : CIR_Op<"vec.ternary",
let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// BaseClassAddrOp
//===----------------------------------------------------------------------===//

def BaseClassAddrOp : CIR_Op<"base_class_addr"> {
let summary = "Get the base class address for a class/struct";
let description = [{
The `cir.base_class_addr` operaration gets the address of a particular
non-virtual base class given a derived class pointer. The offset in bytes
of the base class must be passed in, since it is easier for the front end
to calculate that than the MLIR passes. The operation contains a flag for
whether or not the operand may be nullptr. That depends on the context and
cannot be known by the operation, and that information affects how the
operation is lowered.

Example:
```c++
struct Base { };
struct Derived : Base { };
Derived d;
Base& b = d;
```
will generate
```mlir
%3 = cir.base_class_addr %1 : !cir.ptr<!rec_Derived> nonnull [0] -> !cir.ptr<!rec_Base>
```
}];

// The validity of the relationship of derived and base cannot yet be
// verified, currently not worth adding a verifier.
let arguments = (ins
Arg<CIR_PointerType, "derived class pointer", [MemRead]>:$derived_addr,
IndexAttr:$offset, UnitAttr:$assume_not_null);

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

let assemblyFormat = [{
$derived_addr `:` qualified(type($derived_addr))
(`nonnull` $assume_not_null^)?
` ` `[` $offset `]` `->` qualified(type($base_addr)) attr-dict
}];
}

#endif // CLANG_CIR_DIALECT_IR_CIROPS_TD
5 changes: 5 additions & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ struct MissingFeatures {
static bool cxxabiAppleARM64CXXABI() { return false; }
static bool cxxabiStructorImplicitParam() { return false; }

// Address class
static bool addressOffset() { return false; }
static bool addressIsKnownNonNull() { return false; }
static bool addressPointerAuthInfo() { return false; }

// Misc
static bool cirgenABIInfo() { return false; }
static bool abiArgInfo() { return false; }
Expand Down
14 changes: 14 additions & 0 deletions clang/lib/CIR/CodeGen/Address.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

namespace clang::CIRGen {

// Forward declaration to avoid a circular dependency
class CIRGenBuilderTy;

class Address {

// The boolean flag indicates whether the pointer is known to be non-null.
Expand Down Expand Up @@ -65,11 +68,22 @@ class Address {
return pointerAndKnownNonNull.getPointer() != nullptr;
}

/// Return address with different element type, a bitcast pointer, and
/// the same alignment.
Address withElementType(CIRGenBuilderTy &builder, mlir::Type ElemTy) const;

mlir::Value getPointer() const {
assert(isValid());
return pointerAndKnownNonNull.getPointer();
}

mlir::Value getBasePointer() const {
// TODO(cir): Remove the version above when we catchup with OG codegen on
// ptr auth.
assert(isValid() && "pointer isn't valid");
return getPointer();
}

mlir::Type getType() const {
assert(mlir::cast<cir::PointerType>(
pointerAndKnownNonNull.getPointer().getType())
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,15 @@ mlir::Value CIRGenBuilderTy::getArrayElement(mlir::Location arrayLocBegin,
const mlir::Type flatPtrTy = basePtr.getType();
return create<cir::PtrStrideOp>(arrayLocEnd, flatPtrTy, basePtr, idx);
}

// This can't be defined in Address.h because that file is included by
// CIRGenBuilder.h
Address Address::withElementType(CIRGenBuilderTy &builder,
mlir::Type elemTy) const {
assert(!cir::MissingFeatures::addressOffset());
assert(!cir::MissingFeatures::addressIsKnownNonNull());
assert(!cir::MissingFeatures::addressPointerAuthInfo());

return Address(builder.createPtrBitcast(getBasePointer(), elemTy), elemTy,
getAlignment());
}
12 changes: 12 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,18 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
return create<cir::BinOp>(loc, cir::BinOpKind::Div, lhs, rhs);
}

Address createBaseClassAddr(mlir::Location loc, Address addr,
mlir::Type destType, unsigned offset,
bool assumeNotNull) {
if (destType == addr.getElementType())
return addr;

auto ptrTy = getPointerTo(destType);
auto baseAddr = create<cir::BaseClassAddrOp>(
loc, ptrTy, addr.getPointer(), mlir::APInt(64, offset), assumeNotNull);
return Address(baseAddr, destType, addr.getAlignment());
}

cir::LoadOp createLoad(mlir::Location loc, Address addr,
bool isVolatile = false) {
mlir::IntegerAttr align = getAlignmentAttr(addr.getAlignment());
Expand Down
65 changes: 65 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenClass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This contains code dealing with C++ code generation of classes
//
//===----------------------------------------------------------------------===//

#include "CIRGenFunction.h"

#include "clang/AST/RecordLayout.h"
#include "clang/CIR/MissingFeatures.h"

using namespace clang;
using namespace clang::CIRGen;

Address CIRGenFunction::getAddressOfBaseClass(
Address value, const CXXRecordDecl *derived,
llvm::iterator_range<CastExpr::path_const_iterator> path,
bool nullCheckValue, SourceLocation loc) {
assert(!path.empty() && "Base path should not be empty!");

if ((*path.begin())->isVirtual()) {
// The implementation here is actually complete, but let's flag this
// as an error until the rest of the virtual base class support is in place.
cgm.errorNYI(loc, "getAddrOfBaseClass: virtual base");
return Address::invalid();
}

// Compute the static offset of the ultimate destination within its
// allocating subobject (the virtual base, if there is one, or else
// the "complete" object that we see).
CharUnits nonVirtualOffset =
cgm.computeNonVirtualBaseClassOffset(derived, path);

// Get the base pointer type.
mlir::Type baseValueTy = convertType((path.end()[-1])->getType());
assert(!cir::MissingFeatures::addressSpace());

// The if statement here is redundant now, but it will be needed when we add
// support for virtual base classes.
// If there is no virtual base, use cir.base_class_addr. It takes care of
// the adjustment and the null pointer check.
if (nonVirtualOffset.isZero()) {
assert(!cir::MissingFeatures::sanitizers());
return builder.createBaseClassAddr(getLoc(loc), value, baseValueTy, 0,
/*assumeNotNull=*/true);
}

assert(!cir::MissingFeatures::sanitizers());

// Apply the offset
value = builder.createBaseClassAddr(getLoc(loc), value, baseValueTy,
nonVirtualOffset.getQuantity(),
/*assumeNotNull=*/true);

// Cast to the destination type.
value = value.withElementType(builder, baseValueTy);

return value;
}
34 changes: 29 additions & 5 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,14 @@ Address CIRGenFunction::emitPointerWithAlignment(const Expr *expr,

case CK_UncheckedDerivedToBase:
case CK_DerivedToBase: {
cgm.errorNYI(expr->getSourceRange(),
"emitPointerWithAlignment: derived-to-base cast");
return Address::invalid();
assert(!cir::MissingFeatures::opTBAA());
assert(!cir::MissingFeatures::addressIsKnownNonNull());
Address addr = emitPointerWithAlignment(ce->getSubExpr(), baseInfo);
const CXXRecordDecl *derived =
ce->getSubExpr()->getType()->getPointeeCXXRecordDecl();
return getAddressOfBaseClass(addr, derived, ce->path(),
shouldNullCheckClassCastValue(ce),
ce->getExprLoc());
}

case CK_AnyPointerToBlockPointerCast:
Expand Down Expand Up @@ -823,8 +828,6 @@ LValue CIRGenFunction::emitCastLValue(const CastExpr *e) {
case CK_NonAtomicToAtomic:
case CK_AtomicToNonAtomic:
case CK_Dynamic:
case CK_UncheckedDerivedToBase:
case CK_DerivedToBase:
case CK_ToUnion:
case CK_BaseToDerived:
case CK_LValueBitCast:
Expand Down Expand Up @@ -863,6 +866,27 @@ LValue CIRGenFunction::emitCastLValue(const CastExpr *e) {
return lv;
}

case CK_UncheckedDerivedToBase:
case CK_DerivedToBase: {
const auto *derivedClassTy =
e->getSubExpr()->getType()->castAs<clang::RecordType>();
auto *derivedClassDecl = cast<CXXRecordDecl>(derivedClassTy->getDecl());

LValue lv = emitLValue(e->getSubExpr());
Address thisAddr = lv.getAddress();

// Perform the derived-to-base conversion
Address baseAddr =
getAddressOfBaseClass(thisAddr, derivedClassDecl, e->path(),
/*NullCheckValue=*/false, e->getExprLoc());

// TODO: Support accesses to members of base classes in TBAA. For now, we
// conservatively pretend that the complete object is of the base class
// type.
assert(!cir::MissingFeatures::opTBAA());
return makeAddrLValue(baseAddr, e->getType(), lv.getBaseInfo());
}

case CK_ZeroToOCLOpaqueType:
llvm_unreachable("NULL to OpenCL opaque type lvalue cast is not valid");
}
Expand Down
22 changes: 22 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "CIRGenCall.h"
#include "CIRGenValue.h"
#include "mlir/IR/Location.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/GlobalDecl.h"
#include "clang/CIR/MissingFeatures.h"

Expand Down Expand Up @@ -629,4 +630,25 @@ void CIRGenFunction::emitNullInitialization(mlir::Location loc, Address destPtr,
builder.createStore(loc, zeroValue, destPtr);
}

// TODO(cir): should be shared with LLVM codegen.
bool CIRGenFunction::shouldNullCheckClassCastValue(const CastExpr *ce) {
const Expr *e = ce->getSubExpr();

if (ce->getCastKind() == CK_UncheckedDerivedToBase)
return false;

if (isa<CXXThisExpr>(e->IgnoreParens())) {
// We always assume that 'this' is never null.
return false;
}

if (const ImplicitCastExpr *ice = dyn_cast<ImplicitCastExpr>(ce)) {
// And that glvalue casts are never null.
if (ice->isGLValue())
return false;
}

return true;
}

} // namespace clang::CIRGen
7 changes: 7 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,8 @@ class CIRGenFunction : public CIRGenTypeCache {
// TODO: Add symbol table support
}

bool shouldNullCheckClassCastValue(const CastExpr *ce);

LValue makeNaturalAlignPointeeAddrLValue(mlir::Value v, clang::QualType t);

/// Construct an address with the natural alignment of T. If a pointer to T
Expand All @@ -445,6 +447,11 @@ class CIRGenFunction : public CIRGenTypeCache {
return Address(ptr, convertTypeForMem(t), alignment);
}

Address getAddressOfBaseClass(
Address value, const CXXRecordDecl *derived,
llvm::iterator_range<CastExpr::path_const_iterator> path,
bool nullCheckValue, SourceLocation loc);

LValue makeAddrLValue(Address addr, QualType ty,
AlignmentSource source = AlignmentSource::Type) {
return makeAddrLValue(addr, ty, LValueBaseInfo(source));
Expand Down
28 changes: 28 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclOpenACC.h"
#include "clang/AST/GlobalDecl.h"
#include "clang/AST/RecordLayout.h"
#include "clang/Basic/SourceManager.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Interfaces/CIROpInterfaces.h"
Expand Down Expand Up @@ -1677,6 +1678,33 @@ bool CIRGenModule::verifyModule() const {
return mlir::verify(theModule).succeeded();
}

// TODO(cir): this can be shared with LLVM codegen.
CharUnits CIRGenModule::computeNonVirtualBaseClassOffset(
const CXXRecordDecl *derivedClass,
llvm::iterator_range<CastExpr::path_const_iterator> path) {
CharUnits offset = CharUnits::Zero();

const ASTContext &astContext = getASTContext();
const CXXRecordDecl *rd = derivedClass;

for (const CXXBaseSpecifier *base : path) {
assert(!base->isVirtual() && "Should not see virtual bases here!");

// Get the layout.
const ASTRecordLayout &layout = astContext.getASTRecordLayout(rd);

const auto *baseDecl = cast<CXXRecordDecl>(
base->getType()->castAs<clang::RecordType>()->getDecl());

// Add the offset.
offset += layout.getBaseClassOffset(baseDecl);

rd = baseDecl;
}

return offset;
}

DiagnosticBuilder CIRGenModule::errorNYI(SourceLocation loc,
llvm::StringRef feature) {
unsigned diagID = diags.getCustomDiagID(
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ class CIRGenModule : public CIRGenTypeCache {
getAddrOfGlobalVar(const VarDecl *d, mlir::Type ty = {},
ForDefinition_t isForDefinition = NotForDefinition);

CharUnits computeNonVirtualBaseClassOffset(
const CXXRecordDecl *derivedClass,
llvm::iterator_range<CastExpr::path_const_iterator> path);

/// Return a constant array for the given string.
mlir::Attribute getConstantArrayFromStringLiteral(const StringLiteral *e);

Expand Down
1 change: 1 addition & 0 deletions clang/lib/CIR/CodeGen/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ add_clang_library(clangCIR
CIRGenerator.cpp
CIRGenBuilder.cpp
CIRGenCall.cpp
CIRGenClass.cpp
CIRGenCXXABI.cpp
CIRGenCXXExpr.cpp
CIRGenDecl.cpp
Expand Down
Loading
Loading