https://github.com/erichkeane created
https://github.com/llvm/llvm-project/pull/219505
Anonymous structs/unions cause an 'indirect field' declaration, which contains
the path with how to get through it. We were missing handling in 2 places:
1- At emitTopLevelDecl, which we just need to ignore it (as the global
itself handles the emit)
2- When generating the get-member pointer value. This just requires us to
properly follow the 'field' chain.
>From ac11c4b41d272b2b5b8b0c26470d389ddfefad08 Mon Sep 17 00:00:00 2001
From: erichkeane <[email protected]>
Date: Fri, 28 Aug 2026 08:49:11 -0700
Subject: [PATCH] [CIR] Implement indirect-field accesses
Anonymous structs/unions cause an 'indirect field' declaration, which
contains the path with how to get through it. We were missing handling
in 2 places:
1- At emitTopLevelDecl, which we just need to ignore it (as the global
itself handles the emit)
2- When generating the get-member pointer value. This just requires us
to properly follow the 'field' chain.
---
clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 6 +-
clang/lib/CIR/CodeGen/CIRGenModule.cpp | 32 +++++--
clang/lib/CIR/CodeGen/CIRGenModule.h | 6 +-
.../pointer-to-data-member-indirect-field.cpp | 92 +++++++++++++++++++
4 files changed, 125 insertions(+), 11 deletions(-)
create mode 100644
clang/test/CIR/CodeGen/pointer-to-data-member-indirect-field.cpp
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index b3651960bd2cb..385568c7f6f0f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -1463,13 +1463,13 @@ mlir::Attribute ConstantEmitter::tryEmitPrivate(const
APValue &value,
}
auto cirTy = mlir::cast<cir::DataMemberType>(cgm.convertType(destType));
- const auto *fieldDecl = cast<FieldDecl>(memberDecl);
const auto *mpt = destType->castAs<MemberPointerType>();
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.
- if (cgm.isEmptyFieldForMemberPointer(fieldDecl)) {
+ if (const auto *fieldDecl = dyn_cast<FieldDecl>(memberDecl);
+ fieldDecl && cgm.isEmptyFieldForMemberPointer(fieldDecl)) {
const ASTContext &astContext = cgm.getASTContext();
CharUnits offset =
astContext.getMemberPointerPathAdjustment(value) +
@@ -1478,7 +1478,7 @@ mlir::Attribute ConstantEmitter::tryEmitPrivate(const
APValue &value,
}
std::optional<llvm::SmallVector<int32_t>> path =
- cgm.buildMemberPath(destClass, fieldDecl);
+ cgm.buildMemberPath(destClass, memberDecl);
if (!path)
return {};
return builder.getDataMemberAttr(cirTy, *path);
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index cc4ce9a786327..d61743de3a8e4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -2338,7 +2338,7 @@ mlir::Value CIRGenModule::emitMemberPointerConstant(const
UnaryOperator *e) {
mlir::Location loc = getLoc(e->getSourceRange());
- const auto *decl = cast<DeclRefExpr>(e->getSubExpr())->getDecl();
+ const ValueDecl *decl = cast<DeclRefExpr>(e->getSubExpr())->getDecl();
// A member function pointer.
if (const auto *methodDecl = dyn_cast<CXXMethodDecl>(decl)) {
@@ -2357,13 +2357,13 @@ mlir::Value
CIRGenModule::emitMemberPointerConstant(const UnaryOperator *e) {
// Otherwise, a member data pointer.
auto ty = mlir::cast<cir::DataMemberType>(convertType(e->getType()));
- const auto *fieldDecl = cast<FieldDecl>(decl);
const auto *mpt = e->getType()->castAs<MemberPointerType>();
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 (isEmptyFieldForMemberPointer(fieldDecl)) {
+ if (const auto *fieldDecl = dyn_cast<FieldDecl>(decl);
+ fieldDecl && 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.
@@ -2377,7 +2377,7 @@ mlir::Value CIRGenModule::emitMemberPointerConstant(const
UnaryOperator *e) {
}
std::optional<llvm::SmallVector<int32_t>> path =
- buildMemberPath(destClass, fieldDecl);
+ buildMemberPath(destClass, decl);
if (!path)
return {};
return cir::ConstantOp::create(builder, loc,
@@ -2386,9 +2386,24 @@ mlir::Value
CIRGenModule::emitMemberPointerConstant(const UnaryOperator *e) {
std::optional<llvm::SmallVector<int32_t>>
CIRGenModule::buildMemberPath(const CXXRecordDecl *destClass,
- const FieldDecl *field) {
+ const ValueDecl *decl) {
llvm::SmallVector<int32_t> path;
- if (!findFieldMemberPath(destClass, field, path))
+
+ // Members of an anonymous struct/union have an IndirectFieldDecl, which
+ // contains the whole chain of how to get to it, so to get the 'path', we dig
+ // through those rather than searching.
+ if (const auto *indirectField = dyn_cast<IndirectFieldDecl>(decl)) {
+ const CXXRecordDecl *currentClass = destClass;
+ for (const NamedDecl *nd : indirectField->chain()) {
+ const auto *field = cast<FieldDecl>(nd);
+ if (!findFieldMemberPath(currentClass, field, path))
+ return std::nullopt;
+ currentClass = field->getType()->getAsCXXRecordDecl();
+ }
+ return path;
+ }
+
+ if (!findFieldMemberPath(destClass, cast<FieldDecl>(decl), path))
return std::nullopt;
return path;
}
@@ -2549,6 +2564,11 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) {
assert(!cir::MissingFeatures::generateDebugInfo());
break;
+ // Indirect fields from global anonymous structs and unions can be
+ // ignored; only the actual variable requires IR gen support.
+ case Decl::IndirectField:
+ break;
+
// No code generation needed.
case Decl::ClassTemplate:
case Decl::Concept:
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h
b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 7ea47ba6d5d24..012478cc2ad23 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -728,10 +728,12 @@ class CIRGenModule : public CIRGenTypeCache {
/// member, depending on the type of mpt.
mlir::TypedAttr emitNullMemberAttr(QualType t, const MemberPointerType *mpt);
- /// Build a GEP-style field-index path from \p destClass to \p field.
+ /// Build a GEP-style field-index path from \p destClass to \p decl.
+ /// \p decl may be a FieldDecl, or an IndirectFieldDecl(in the case of an
+ /// anonymous struct/union).
/// Returns std::nullopt and emits errorNYI for virtual-base paths.
std::optional<llvm::SmallVector<int32_t>>
- buildMemberPath(const CXXRecordDecl *destClass, const FieldDecl *field);
+ buildMemberPath(const CXXRecordDecl *destClass, const ValueDecl *decl);
/// 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
diff --git a/clang/test/CIR/CodeGen/pointer-to-data-member-indirect-field.cpp
b/clang/test/CIR/CodeGen/pointer-to-data-member-indirect-field.cpp
new file mode 100644
index 0000000000000..c4917344260f8
--- /dev/null
+++ b/clang/test/CIR/CodeGen/pointer-to-data-member-indirect-field.cpp
@@ -0,0 +1,92 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -fclangir
-Wno-unused-value -emit-cir -mmlir -mlir-print-ir-before=cir-cxxabi-lowering %s
-o %t.cir 2> %t-before.cir
+// RUN: FileCheck --check-prefix=CIR-BEFORE --input-file=%t-before.cir %s
+// RUN: FileCheck --check-prefix=CIR-AFTER --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -fclangir
-Wno-unused-value -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17
-Wno-unused-value -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
+
+struct A {
+ int b;
+ union { int c; };
+};
+
+int A::*pt_anon_union_member = &A::c;
+// CIR-BEFORE: cir.global external @pt_anon_union_member =
#cir.data_member<[1, 0]> : !cir.data_member<!s32i in !rec_A>
+// CIR-AFTER: cir.global external @pt_anon_union_member = #cir.int<4> : !s64i
+// LLVM: @pt_anon_union_member = global i64 4
+
+struct B {
+ int b;
+ union {
+ struct {
+ int c;
+ int e;
+ };
+ float f;
+ };
+};
+
+int B::*pt_nested_anon_first = &B::c;
+// CIR-BEFORE: cir.global external @pt_nested_anon_first =
#cir.data_member<[1, 0, 0]> : !cir.data_member<!s32i in !rec_B>
+// CIR-AFTER: cir.global external @pt_nested_anon_first = #cir.int<4> : !s64i
+// LLVM: @pt_nested_anon_first = global i64 4
+
+int B::*pt_nested_anon_second = &B::e;
+// CIR-BEFORE: cir.global external @pt_nested_anon_second =
#cir.data_member<[1, 0, 1]> : !cir.data_member<!s32i in !rec_B>
+// CIR-AFTER: cir.global external @pt_nested_anon_second = #cir.int<8> : !s64i
+// LLVM: @pt_nested_anon_second = global i64 8
+
+static union { int gx; float gy; };
+// CIR-BEFORE: cir.global "private" internal dso_local @_Z2gx = #cir.zero :
!rec_anon2E3 {alignment = 4 : i64}
+// CIR-AFTER: cir.global "private" internal dso_local @_Z2gx = #cir.zero :
!rec_anon2E3 {alignment = 4 : i64}
+// LLVM: @_Z2gx = internal global %union.anon{{.*}} zeroinitializer, align 4
+
+int test_use(A &a, int A::*member) {
+ return a.*member;
+}
+
+// CIR-BEFORE-LABEL: cir.func {{.*}}@_Z8test_useR1AMS_i(
+// CIR-BEFORE: %[[LOAD_ARG:.*]] = cir.load %{{.*}} :
!cir.ptr<!cir.ptr<!rec_A>>, !cir.ptr<!rec_A>
+// CIR-BEFORE: %[[LOAD_PTR:.*]] = cir.load align(8) %{{.*}} :
!cir.ptr<!cir.data_member<!s32i in !rec_A>>, !cir.data_member<!s32i in !rec_A>
+// CIR-BEFORE: cir.get_runtime_member %[[LOAD_ARG]][%[[LOAD_PTR]] :
!cir.data_member<!s32i in !rec_A>] : !cir.ptr<!rec_A> -> !cir.ptr<!s32i>
+
+// CIR-AFTER-LABEL: cir.func {{.*}}@_Z8test_useR1AMS_i(
+// CIR-AFTER: %[[LOAD_ARG:.*]] = cir.load %{{.*}} :
!cir.ptr<!cir.ptr<!rec_A>>, !cir.ptr<!rec_A>
+// CIR-AFTER: %[[LOAD_PTR:.*]] = cir.load align(8) %{{.*}} : !cir.ptr<!s64i>,
!s64i
+// CIR-AFTER: %[[ARG_TO_CHARPTR:.*]] = cir.cast bitcast %[[LOAD_ARG]] :
!cir.ptr<!rec_A> -> !cir.ptr<!s8i>
+// CIR-AFTER: %[[OFFSET:.*]] = cir.ptr_stride %[[ARG_TO_CHARPTR]],
%[[LOAD_PTR]] : (!cir.ptr<!s8i>, !s64i) -> !cir.ptr<!s8i>
+// CIR-AFTER: cir.cast bitcast %[[OFFSET]] : !cir.ptr<!s8i> -> !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @_Z8test_useR1AMS_i(
+// LLVM: %[[LOAD_ARG:.*]] = load ptr, ptr %{{.*}}, align 8
+// LLVM: %[[LOAD_PTR:.*]] = load i64, ptr %{{.*}}, align 8
+// LLVM: getelementptr {{.*}}i8, ptr %[[LOAD_ARG]], i64 %[[LOAD_PTR]]
+
+int test_call_use(A &a) {
+ return test_use(a, &A::c);
+}
+
+// CIR-BEFORE-LABEL: cir.func {{.*}}@_Z13test_call_useR1A
+// CIR-BEFORE: %[[MEMBER:.*]] = cir.const #cir.data_member<[1, 0]> :
!cir.data_member<!s32i in !rec_A>
+// CIR-BEFORE: cir.call @_Z8test_useR1AMS_i(%{{.*}}, %[[MEMBER]])
+
+// CIR-AFTER-LABEL: cir.func {{.*}}@_Z13test_call_useR1A
+// CIR-AFTER: %[[MEMBER:.*]] = cir.const #cir.int<4> : !s64i
+// CIR-AFTER: cir.call @_Z8test_useR1AMS_i({{.*}}, %[[MEMBER]])
+
+// LLVM-LABEL: define {{.*}} i32 @_Z13test_call_useR1A(
+// LLVM: call {{.*}} i32 @_Z8test_useR1AMS_i({{.*}}, i64 4)
+
+int use_global_anon_union() { return gy; }
+
+// CIR-BEFORE-LABEL: cir.func {{.*}}@_Z21use_global_anon_unionv()
+// CIR-BEFORE: %[[GET_GLOB:.*]] = cir.get_global @_Z2gx :
!cir.ptr<!rec_anon2E3>
+// CIR-BEFORE: cir.get_member %[[GET_GLOB]][1] {name = "gy"} :
!cir.ptr<!rec_anon2E3> -> !cir.ptr<!cir.float>
+
+// CIR-AFTER-LABEL: cir.func {{.*}}@_Z21use_global_anon_unionv()
+// CIR-AFTER: %[[GET_GLOB:.*]] = cir.get_global @_Z2gx : !cir.ptr<!rec_anon2E3>
+// CIR-AFTER: cir.get_member %[[GET_GLOB]][1] {name = "gy"} :
!cir.ptr<!rec_anon2E3> -> !cir.ptr<!cir.float>
+
+// LLVM-LABEL: define {{.*}}i32 @_Z21use_global_anon_unionv()
+// LLVM: load float, ptr @_Z2gx, align 4
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits