https://github.com/erichkeane updated https://github.com/llvm/llvm-project/pull/208777
>From c08da135b07fd247ccad372cec9e38278fc27699 Mon Sep 17 00:00:00 2001 From: erichkeane <[email protected]> Date: Thu, 9 Jul 2026 08:45:11 -0700 Subject: [PATCH 1/3] [CIR] Introduce data_member_offset attribute Some fields, like no_unique_address marked ones, aren't present in the CIR struct type. This attribute represents an offset into a struct instead, since get_member isn't possible without a member. I also considered representing these types in the struct, but it ends up causing a lot of awful looking structs, and forces the LLVMIR to contain empty fields for each of these, but not in lexical order, which is particularly awful looking. I considered making this part of data-member, but it seems like not a particularly good idea, since this isn't really getting a 'member' in the traditional sense. --- .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 39 +++++++++++++ clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 12 ++-- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 48 ++++++++++++--- clang/lib/CIR/CodeGen/CIRGenModule.h | 9 +++ clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 3 +- .../CIR/Dialect/Transforms/CXXABILowering.cpp | 9 ++- .../Transforms/TargetLowering/CIRCXXABI.h | 7 +++ .../TargetLowering/LowerItaniumCXXABI.cpp | 15 +++++ .../CodeGen/pointer-to-empty-data-member.cpp | 58 ++++++++++++++----- 9 files changed, 170 insertions(+), 30 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index ec99899355b17..4316c639e0bbd 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -598,6 +598,45 @@ def CIR_DataMemberAttr : CIR_ValueLikeAttr<"DataMember", "data_member"> { }]; } +//===----------------------------------------------------------------------===// +// DataMemberOffsetAttr +//===----------------------------------------------------------------------===// + +def CIR_DataMemberOffsetAttr + : CIR_ValueLikeAttr<"DataMemberOffset", "data_member_offset"> { + let summary = "Constant pointer-to-data-member given by a byte offset"; + let parameters = (ins AttributeSelfTypeParameter< + "", "cir::DataMemberType">:$type, + "uint64_t":$offset); + let description = [{ + Like `#cir.data_member`, this is a literal attribute representing a constant + (non-null) pointer-to-data-member value. It is used for members that are + not laid out in the CIR record and therefore have no field-index path -- in + particular `[[no_unique_address]]` empty fields. The concrete byte offset + of the member within the class is stored directly. + + Example: + ``` + // Empty S::*p = &S::e (e is a no_unique_address empty field at byte 1) + #cir.data_member_offset<1> : !cir.data_member<!rec_Empty in !rec_S> + ``` + }]; + + let builders = [ + AttrBuilderWithInferredContext<(ins "cir::DataMemberType":$type, + "uint64_t":$offset), [{ + return $_get(type.getContext(), type, offset); + }]>, + ]; + + // This attribute gets lowered during CXXABILowering. + let hasAttrToValueLowering = 0; + + let assemblyFormat = [{ + `<` $offset `>` + }]; +} + //===----------------------------------------------------------------------===// // MethodAttr //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp index 4e16351c9a951..2a90cdaac92f5 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp @@ -1415,11 +1415,13 @@ mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &value, const auto *fieldDecl = cast<FieldDecl>(memberDecl); const auto *mpt = destType->castAs<MemberPointerType>(); const auto *destClass = mpt->getMostRecentCXXRecordDecl(); - if (fieldDecl->hasAttr<NoUniqueAddressAttr>()) { - assert(!cir::MissingFeatures::noUniqueAddressLayout()); - cgm.errorNYI("ConstExprEmitter::tryEmitPrivate: no_unique_address field"); - return {}; - } + + // Empty [[no_unique_address]] fields have no CIR field index; represent the + // pointer-to-data-member by its concrete byte offset within the class. + if (std::optional<uint64_t> offset = + cgm.getEmptyFieldMemberPointerOffset(destClass, fieldDecl)) + return cir::DataMemberOffsetAttr::get(cirTy, *offset); + std::optional<llvm::SmallVector<int32_t>> path = cgm.buildMemberPath(destClass, fieldDecl); if (!path) diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 6b7d44b09118b..94478052221fc 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -2306,12 +2306,12 @@ mlir::Value CIRGenModule::emitMemberPointerConstant(const UnaryOperator *e) { const auto *mpt = e->getType()->castAs<MemberPointerType>(); const auto *destClass = mpt->getMostRecentCXXRecordDecl(); - if (fieldDecl->hasAttr<NoUniqueAddressAttr>()) { - assert(!cir::MissingFeatures::noUniqueAddressLayout()); - errorNYI(e->getExprLoc(), - "emitMemberPointerConstant: no_unique_address field"); - return {}; - } + // Empty [[no_unique_address]] fields have no CIR field index; represent the + // pointer-to-data-member by its concrete byte offset within the class. + if (std::optional<uint64_t> offset = + getEmptyFieldMemberPointerOffset(destClass, fieldDecl)) + return cir::ConstantOp::create(builder, loc, + cir::DataMemberOffsetAttr::get(ty, *offset)); std::optional<llvm::SmallVector<int32_t>> path = buildMemberPath(destClass, fieldDecl); @@ -2324,8 +2324,6 @@ mlir::Value CIRGenModule::emitMemberPointerConstant(const UnaryOperator *e) { std::optional<llvm::SmallVector<int32_t>> CIRGenModule::buildMemberPath(const CXXRecordDecl *destClass, const FieldDecl *field) { - assert(!field->hasAttr<NoUniqueAddressAttr>()); - llvm::SmallVector<int32_t> path; if (!findFieldMemberPath(destClass, field, path)) return std::nullopt; @@ -2395,6 +2393,40 @@ bool CIRGenModule::findFieldMemberPath(const CXXRecordDecl *currentClass, return false; } +/// Computes the byte offset of \p field within \p cls, descending through base +/// subobjects when the field is inherited. +static uint64_t getFieldByteOffsetInClass(const ASTContext &ctx, + const CXXRecordDecl *cls, + const FieldDecl *field) { + const ASTRecordLayout &layout = ctx.getASTRecordLayout(cls); + const auto *fieldParent = cast<CXXRecordDecl>(field->getParent()); + if (fieldParent == cls) + return ctx + .toCharUnitsFromBits(layout.getFieldOffset(field->getFieldIndex())) + .getQuantity(); + + for (const CXXBaseSpecifier &base : cls->bases()) { + const auto *baseDecl = base.getType()->getAsCXXRecordDecl(); + if (baseDecl == fieldParent || baseDecl->isDerivedFrom(fieldParent)) + return layout.getBaseClassOffset(baseDecl).getQuantity() + + getFieldByteOffsetInClass(ctx, baseDecl, field); + } + + llvm_unreachable("field is not a member of the given class"); +} + +std::optional<uint64_t> +CIRGenModule::getEmptyFieldMemberPointerOffset(const CXXRecordDecl *destClass, + const FieldDecl *field) { + // Only empty, potentially-overlapping fields are omitted from the CIR record + // (see CIRRecordLowering::accumulateFields). + if (!isEmptyFieldForLayout(astContext, field) || + !field->isPotentiallyOverlapping()) + return std::nullopt; + + return getFieldByteOffsetInClass(astContext, destClass, field); +} + void CIRGenModule::emitDeclContext(const DeclContext *dc) { for (Decl *decl : dc->decls()) { // Unlike other DeclContexts, the contents of an ObjCImplDecl at TU scope diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 144f8c7b9f3e7..66008d240b4e0 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -726,6 +726,15 @@ class CIRGenModule : public CIRGenTypeCache { std::optional<llvm::SmallVector<int32_t>> buildMemberPath(const CXXRecordDecl *destClass, const FieldDecl *field); + /// If \p field is an empty field that isn't laid out in the CIR record + /// (e.g. a [[no_unique_address]] empty member), returns its concrete byte + /// offset within \p destClass. Such fields have no CIR field index, so a + /// pointer-to-data-member to them is represented by an explicit offset + /// rather than a field-index path. Returns std::nullopt otherwise. + std::optional<uint64_t> + getEmptyFieldMemberPointerOffset(const CXXRecordDecl *destClass, + const FieldDecl *field); + llvm::StringRef getMangledName(clang::GlobalDecl gd); // This function is to support the OpenACC 'bind' clause, which names an // alternate name for the function to be called by. This function mangles diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 67cc5e09f26d0..24883fc3a2adb 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -546,7 +546,8 @@ static LogicalResult checkConstantTypes(mlir::Operation *op, mlir::Type opType, return success(); } - if (isa<cir::DataMemberAttr, cir::MethodAttr>(attrType)) { + if (isa<cir::DataMemberAttr, cir::DataMemberOffsetAttr, cir::MethodAttr>( + attrType)) { // More detailed type verifications are already done in // DataMemberAttr::verify or MethodAttr::verify. Don't need to repeat here. return success(); diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp index 704ebbeb1ecd9..50a51d9618118 100644 --- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp @@ -67,7 +67,8 @@ bool isCXXABIAttributeLegal(const mlir::TypeConverter &tc, return true; // Data Member and method are ALWAYS illegal. - if (isa<cir::DataMemberAttr, cir::MethodAttr>(attr)) + if (isa<cir::DataMemberAttr, cir::DataMemberOffsetAttr, cir::MethodAttr>( + attr)) return false; return llvm::TypeSwitch<mlir::Attribute, bool>(attr) @@ -394,6 +395,12 @@ static mlir::TypedAttr lowerInitialValue(const LowerModule *lowerModule, mlir::Type ty, mlir::Attribute initVal) { if (mlir::isa<cir::DataMemberType>(ty)) { + // Members without a CIR field index (e.g. no_unique_address empty fields) + // are represented by an explicit byte offset instead of a field path. + if (auto offsetVal = + mlir::dyn_cast_if_present<cir::DataMemberOffsetAttr>(initVal)) + return lowerModule->getCXXABI().lowerDataMemberOffsetConstant(offsetVal, + layout, tc); auto dataMemberVal = mlir::cast_if_present<cir::DataMemberAttr>(initVal); return lowerModule->getCXXABI().lowerDataMemberConstant(dataMemberVal, layout, tc); diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h index 55607e24ce836..5c5d6421aea71 100644 --- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h +++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h @@ -57,6 +57,13 @@ class CIRCXXABI { const mlir::DataLayout &layout, const mlir::TypeConverter &typeConverter) const = 0; + /// Lower the given by-offset data member pointer constant (used for members + /// with no CIR field index, e.g. no_unique_address empty fields) to a + /// constant of the ABI type. + virtual mlir::TypedAttr lowerDataMemberOffsetConstant( + cir::DataMemberOffsetAttr attr, const mlir::DataLayout &layout, + const mlir::TypeConverter &typeConverter) const = 0; + /// Lower the given member function pointer constant to a constant of the ABI /// type. The returned constant is represented as an attribute as well. virtual mlir::TypedAttr diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp index 5218d4979f353..6c276a83f18cf 100644 --- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp +++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp @@ -55,6 +55,10 @@ class LowerItaniumCXXABI : public CIRCXXABI { cir::DataMemberAttr attr, const mlir::DataLayout &layout, const mlir::TypeConverter &typeConverter) const override; + mlir::TypedAttr lowerDataMemberOffsetConstant( + cir::DataMemberOffsetAttr attr, const mlir::DataLayout &layout, + const mlir::TypeConverter &typeConverter) const override; + mlir::TypedAttr lowerMethodConstant(cir::MethodAttr attr, const mlir::DataLayout &layout, const mlir::TypeConverter &typeConverter) const override; @@ -217,6 +221,17 @@ mlir::TypedAttr LowerItaniumCXXABI::lowerDataMemberConstant( return cir::IntAttr::get(abiTy, memberOffset); } +mlir::TypedAttr LowerItaniumCXXABI::lowerDataMemberOffsetConstant( + cir::DataMemberOffsetAttr attr, const mlir::DataLayout &layout, + const mlir::TypeConverter &typeConverter) const { + // Itanium C++ ABI 2.3: + // A pointer to data member is an offset from the base address of the class + // object containing it, represented as a ptrdiff_t. + // The offset is already known (the member has no CIR field index). + mlir::Type abiTy = lowerDataMemberType(attr.getType(), typeConverter); + return cir::IntAttr::get(abiTy, static_cast<int64_t>(attr.getOffset())); +} + mlir::TypedAttr LowerItaniumCXXABI::lowerMethodConstant( cir::MethodAttr attr, const mlir::DataLayout &layout, const mlir::TypeConverter &typeConverter) const { diff --git a/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp b/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp index 5d368f27033f5..4251c659565bd 100644 --- a/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp +++ b/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp @@ -87,7 +87,6 @@ struct hasNUA { [[no_unique_address]] EmptyBase eb6; int i; }; -// FIXME(cir): We should represent eb1-6 somehow // CIR-DAG: !rec_hasNUA = !cir.struct<"hasNUA" padded {!s32i, !cir.array<!u8i x 4>}> // LLVM-DAG: %struct.hasNUA = type { i32, [4 x i8] } @@ -96,14 +95,33 @@ const hasNUA nua = {{},{},{},{},{},{}, 1}; // LLVMCIR-DAG: @_ZL3nua = internal constant %struct.hasNUA { i32 1, [4 x i8] zeroinitializer } // OGCG-DAG: @_ZL3nua = internal constant %struct.hasNUA { i32 1, [4 x i8] undef } -// FIXME(cir): These are still an NYI, and we should enable tests here once we -// figure out how to represent these in IR. -//EmptyBase hasNUA::* eb1 = &hasNUA::eb1; -//EmptyBase hasNUA::* eb2 = &hasNUA::eb2; -//EmptyBase hasNUA::* eb3 = &hasNUA::eb3; -//EmptyBase hasNUA::* eb4 = &hasNUA::eb4; -//EmptyBase hasNUA::* eb5 = &hasNUA::eb5; -//EmptyBase hasNUA::* eb6 = &hasNUA::eb6; +// A pointer-to-data-member for a no_unique_address empty field has no CIR +// field index (the field isn't laid out), so it is represented by its concrete +// byte offset via #cir.data_member_offset. +EmptyBase hasNUA::* eb1 = &hasNUA::eb1; +// CIR-BEFORE-DAG: cir.global external @eb1 = #cir.data_member_offset<0> : !cir.data_member<!rec_EmptyBase in !rec_hasNUA> +// CIR-AFTER-DAG: cir.global external @eb1 = #cir.int<0> : !s64i +// LLVM-DAG: @eb1 = global i64 0 +EmptyBase hasNUA::* eb2 = &hasNUA::eb2; +// CIR-BEFORE-DAG: cir.global external @eb2 = #cir.data_member_offset<1> : !cir.data_member<!rec_EmptyBase in !rec_hasNUA> +// CIR-AFTER-DAG: cir.global external @eb2 = #cir.int<1> : !s64i +// LLVM-DAG: @eb2 = global i64 1 +EmptyBase hasNUA::* eb3 = &hasNUA::eb3; +// CIR-BEFORE-DAG: cir.global external @eb3 = #cir.data_member_offset<2> : !cir.data_member<!rec_EmptyBase in !rec_hasNUA> +// CIR-AFTER-DAG: cir.global external @eb3 = #cir.int<2> : !s64i +// LLVM-DAG: @eb3 = global i64 2 +EmptyBase hasNUA::* eb4 = &hasNUA::eb4; +// CIR-BEFORE-DAG: cir.global external @eb4 = #cir.data_member_offset<3> : !cir.data_member<!rec_EmptyBase in !rec_hasNUA> +// CIR-AFTER-DAG: cir.global external @eb4 = #cir.int<3> : !s64i +// LLVM-DAG: @eb4 = global i64 3 +EmptyBase hasNUA::* eb5 = &hasNUA::eb5; +// CIR-BEFORE-DAG: cir.global external @eb5 = #cir.data_member_offset<4> : !cir.data_member<!rec_EmptyBase in !rec_hasNUA> +// CIR-AFTER-DAG: cir.global external @eb5 = #cir.int<4> : !s64i +// LLVM-DAG: @eb5 = global i64 4 +EmptyBase hasNUA::* eb6 = &hasNUA::eb6; +// CIR-BEFORE-DAG: cir.global external @eb6 = #cir.data_member_offset<5> : !cir.data_member<!rec_EmptyBase in !rec_hasNUA> +// CIR-AFTER-DAG: cir.global external @eb6 = #cir.int<5> : !s64i +// LLVM-DAG: @eb6 = global i64 5 int hasNUA::* nua_i = &hasNUA::i; // CIR-BEFORE-DAG: cir.global external @nua_i = #cir.data_member<[0]> : !cir.data_member<!s32i in !rec_hasNUA> // CIR-AFTER-DAG: cir.global external @nua_i = #cir.int<0> : !s64i @@ -205,9 +223,14 @@ int U6::* u6i = &U6::i; // CIR-AFTER-DAG: cir.global external @u6i = #cir.int<0> : !s64i // LLVM-DAG: @u6i = global i64 0 -// FIXME(cir): See above. -//EmptyBase U6::* u6eb = &U6::eb; -//EmptyBase U6::* u6eb2 = &U6::eb2; +EmptyBase U6::* u6eb = &U6::eb; +// CIR-BEFORE-DAG: cir.global external @u6eb = #cir.data_member_offset<0> : !cir.data_member<!rec_EmptyBase in !rec_U6> +// CIR-AFTER-DAG: cir.global external @u6eb = #cir.int<0> : !s64i +// LLVM-DAG: @u6eb = global i64 0 +EmptyBase U6::* u6eb2 = &U6::eb2; +// CIR-BEFORE-DAG: cir.global external @u6eb2 = #cir.data_member_offset<0> : !cir.data_member<!rec_EmptyBase in !rec_U6> +// CIR-AFTER-DAG: cir.global external @u6eb2 = #cir.int<0> : !s64i +// LLVM-DAG: @u6eb2 = global i64 0 union U7 { int i; @@ -222,9 +245,14 @@ int U7::* u7i = &U7::i; // CIR-AFTER-DAG: cir.global external @u7i = #cir.int<0> : !s64i // LLVM-DAG: @u7i = global i64 0 -// FIXME(cir): See above. -//EmptyBase U7::* u7eb = &U7::eb; -//EmptyBase U7::* u7eb2 = &U7::eb2; +EmptyBase U7::* u7eb = &U7::eb; +// CIR-BEFORE-DAG: cir.global external @u7eb = #cir.data_member_offset<0> : !cir.data_member<!rec_EmptyBase in !rec_U7> +// CIR-AFTER-DAG: cir.global external @u7eb = #cir.int<0> : !s64i +// LLVM-DAG: @u7eb = global i64 0 +EmptyBase U7::* u7eb2 = &U7::eb2; +// CIR-BEFORE-DAG: cir.global external @u7eb2 = #cir.data_member_offset<0> : !cir.data_member<!rec_EmptyBase in !rec_U7> +// CIR-AFTER-DAG: cir.global external @u7eb2 = #cir.int<0> : !s64i +// LLVM-DAG: @u7eb2 = global i64 0 void uses() { auto x = &HasEmpty::s; >From 46db8234ff7752c70b9f04cc05f5b12766cbefc7 Mon Sep 17 00:00:00 2001 From: erichkeane <[email protected]> Date: Mon, 13 Jul 2026 15:11:34 -0700 Subject: [PATCH 2/3] simplify how I calculate the offset of empty fields. Self-review revealed that I was probably doing this the 'hard' way, so I went back to classic codegen and realized I was mis-understanding the use in emitMemberPointerCast, and could do it just with getFieldOffset. This reverts the calculation I was doing to look into each base, and instead replaces it with the way that classic codegen does it. --- clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 12 +++-- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 49 ++++++------------- clang/lib/CIR/CodeGen/CIRGenModule.h | 14 +++--- .../CodeGen/pointer-to-empty-data-member.cpp | 8 +++ 4 files changed, 36 insertions(+), 47 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp index 2a90cdaac92f5..30b2e9a6d6d9f 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp @@ -1417,10 +1417,14 @@ mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &value, const auto *destClass = mpt->getMostRecentCXXRecordDecl(); // Empty [[no_unique_address]] fields have no CIR field index; represent the - // pointer-to-data-member by its concrete byte offset within the class. - if (std::optional<uint64_t> offset = - cgm.getEmptyFieldMemberPointerOffset(destClass, fieldDecl)) - return cir::DataMemberOffsetAttr::get(cirTy, *offset); + // pointer-to-data-member by its concrete byte offset. + if (cgm.isEmptyFieldForMemberPointer(fieldDecl)) { + const ASTContext &astContext = cgm.getASTContext(); + CharUnits offset = + astContext.getMemberPointerPathAdjustment(value) + + astContext.toCharUnitsFromBits(astContext.getFieldOffset(fieldDecl)); + return cir::DataMemberOffsetAttr::get(cirTy, offset.getQuantity()); + } std::optional<llvm::SmallVector<int32_t>> path = cgm.buildMemberPath(destClass, fieldDecl); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 94478052221fc..a3764f0844738 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -2308,10 +2308,18 @@ mlir::Value CIRGenModule::emitMemberPointerConstant(const UnaryOperator *e) { // Empty [[no_unique_address]] fields have no CIR field index; represent the // pointer-to-data-member by its concrete byte offset within the class. - if (std::optional<uint64_t> offset = - getEmptyFieldMemberPointerOffset(destClass, fieldDecl)) + if (isEmptyFieldForMemberPointer(fieldDecl)) { + // This function should ONLY be accessed in reference to itself, I don't see + // any cases/couldn't find any cases where anything else could get here, and + // classic-codegen does the same. + assert(fieldDecl->getParent() == destClass && + "scalar member pointer should be relative to the declaring class"); + uint64_t offset = + astContext.toCharUnitsFromBits(astContext.getFieldOffset(fieldDecl)) + .getQuantity(); return cir::ConstantOp::create(builder, loc, - cir::DataMemberOffsetAttr::get(ty, *offset)); + cir::DataMemberOffsetAttr::get(ty, offset)); + } std::optional<llvm::SmallVector<int32_t>> path = buildMemberPath(destClass, fieldDecl); @@ -2393,38 +2401,9 @@ bool CIRGenModule::findFieldMemberPath(const CXXRecordDecl *currentClass, return false; } -/// Computes the byte offset of \p field within \p cls, descending through base -/// subobjects when the field is inherited. -static uint64_t getFieldByteOffsetInClass(const ASTContext &ctx, - const CXXRecordDecl *cls, - const FieldDecl *field) { - const ASTRecordLayout &layout = ctx.getASTRecordLayout(cls); - const auto *fieldParent = cast<CXXRecordDecl>(field->getParent()); - if (fieldParent == cls) - return ctx - .toCharUnitsFromBits(layout.getFieldOffset(field->getFieldIndex())) - .getQuantity(); - - for (const CXXBaseSpecifier &base : cls->bases()) { - const auto *baseDecl = base.getType()->getAsCXXRecordDecl(); - if (baseDecl == fieldParent || baseDecl->isDerivedFrom(fieldParent)) - return layout.getBaseClassOffset(baseDecl).getQuantity() + - getFieldByteOffsetInClass(ctx, baseDecl, field); - } - - llvm_unreachable("field is not a member of the given class"); -} - -std::optional<uint64_t> -CIRGenModule::getEmptyFieldMemberPointerOffset(const CXXRecordDecl *destClass, - const FieldDecl *field) { - // Only empty, potentially-overlapping fields are omitted from the CIR record - // (see CIRRecordLowering::accumulateFields). - if (!isEmptyFieldForLayout(astContext, field) || - !field->isPotentiallyOverlapping()) - return std::nullopt; - - return getFieldByteOffsetInClass(astContext, destClass, field); +bool CIRGenModule::isEmptyFieldForMemberPointer(const FieldDecl *field) { + return isEmptyFieldForLayout(astContext, field) && + field->isPotentiallyOverlapping(); } void CIRGenModule::emitDeclContext(const DeclContext *dc) { diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 66008d240b4e0..82fd0d4405ef3 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -726,14 +726,12 @@ class CIRGenModule : public CIRGenTypeCache { std::optional<llvm::SmallVector<int32_t>> buildMemberPath(const CXXRecordDecl *destClass, const FieldDecl *field); - /// If \p field is an empty field that isn't laid out in the CIR record - /// (e.g. a [[no_unique_address]] empty member), returns its concrete byte - /// offset within \p destClass. Such fields have no CIR field index, so a - /// pointer-to-data-member to them is represented by an explicit offset - /// rather than a field-index path. Returns std::nullopt otherwise. - std::optional<uint64_t> - getEmptyFieldMemberPointerOffset(const CXXRecordDecl *destClass, - const FieldDecl *field); + /// Returns true if \p field is an empty field that isn't laid out in the CIR + /// record (e.g. a [[no_unique_address]] empty member). Such fields have no + /// CIR field index, so a pointer-to-data-member to them is represented by an + /// explicit byte offset (#cir.data_member_offset) rather than a field-index + /// path. + bool isEmptyFieldForMemberPointer(const FieldDecl *field); llvm::StringRef getMangledName(clang::GlobalDecl gd); // This function is to support the OpenACC 'bind' clause, which names an diff --git a/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp b/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp index 4251c659565bd..4ce8620a496f1 100644 --- a/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp +++ b/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp @@ -127,6 +127,14 @@ int hasNUA::* nua_i = &hasNUA::i; // CIR-AFTER-DAG: cir.global external @nua_i = #cir.int<0> : !s64i // LLVM-DAG: @nua_i = global i64 0 +struct FirstField { int a; }; +struct HoldsNUA { int b; [[no_unique_address]] EmptyBase e; }; +struct DerivesNUA : FirstField, HoldsNUA {}; +EmptyBase DerivesNUA::* nua_in_base = &DerivesNUA::e; +// CIR-BEFORE-DAG: cir.global external @nua_in_base = #cir.data_member_offset<4> : !cir.data_member<!rec_EmptyBase in !rec_DerivesNUA> +// CIR-AFTER-DAG: cir.global external @nua_in_base = #cir.int<4> : !s64i +// LLVM-DAG: @nua_in_base = global i64 4 + union U1 { EmptyBase eb; }; >From 43cbc6b2bd4cb707654f4ba9b90f9a1a5a04181e Mon Sep 17 00:00:00 2001 From: erichkeane <[email protected]> Date: Wed, 15 Jul 2026 07:19:44 -0700 Subject: [PATCH 3/3] Add an additional field offset test --- clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp b/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp index 4ce8620a496f1..0b493dc306594 100644 --- a/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp +++ b/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp @@ -128,12 +128,16 @@ int hasNUA::* nua_i = &hasNUA::i; // LLVM-DAG: @nua_i = global i64 0 struct FirstField { int a; }; -struct HoldsNUA { int b; [[no_unique_address]] EmptyBase e; }; +struct HoldsNUA { int b; [[no_unique_address]] EmptyBase e; [[no_unique_address]] EmptyBase e2; }; struct DerivesNUA : FirstField, HoldsNUA {}; EmptyBase DerivesNUA::* nua_in_base = &DerivesNUA::e; // CIR-BEFORE-DAG: cir.global external @nua_in_base = #cir.data_member_offset<4> : !cir.data_member<!rec_EmptyBase in !rec_DerivesNUA> // CIR-AFTER-DAG: cir.global external @nua_in_base = #cir.int<4> : !s64i // LLVM-DAG: @nua_in_base = global i64 4 +EmptyBase DerivesNUA::* nua_in_base2 = &DerivesNUA::e2; +// CIR-BEFORE-DAG: cir.global external @nua_in_base2 = #cir.data_member_offset<8> : !cir.data_member<!rec_EmptyBase in !rec_DerivesNUA> +// CIR-AFTER-DAG: cir.global external @nua_in_base2 = #cir.int<8> : !s64i +// LLVM-DAG: @nua_in_base2 = global i64 8 union U1 { EmptyBase eb; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
