Author: Erich Keane Date: 2026-08-28T11:59:29-07:00 New Revision: 102e33fca6d7f5c2c968bb1ac21610e8e557524b
URL: https://github.com/llvm/llvm-project/commit/102e33fca6d7f5c2c968bb1ac21610e8e557524b DIFF: https://github.com/llvm/llvm-project/commit/102e33fca6d7f5c2c968bb1ac21610e8e557524b.diff LOG: [CIR] Implement indirect-field accesses (#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. Added: clang/test/CIR/CodeGen/pointer-to-data-member-indirect-field.cpp Modified: clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp clang/lib/CIR/CodeGen/CIRGenModule.cpp clang/lib/CIR/CodeGen/CIRGenModule.h Removed: ################################################################################ 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
