https://github.com/xlauko updated https://github.com/llvm/llvm-project/pull/186701
>From dc85ad31876c1d35d344c869635ecadf195be732 Mon Sep 17 00:00:00 2001 From: xlauko <[email protected]> Date: Sun, 15 Mar 2026 17:30:27 +0100 Subject: [PATCH] [CIR] Fix record layout for [[no_unique_address]] fields --- clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 39 +++++++++++--- .../CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp | 12 +++-- clang/test/CIR/CodeGen/no-unique-address.cpp | 53 +++++++++++++++++++ 3 files changed, 93 insertions(+), 11 deletions(-) create mode 100644 clang/test/CIR/CodeGen/no-unique-address.cpp diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index 65644bc1a3fd9..67862ff9748ea 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -49,22 +49,45 @@ Address CIRGenFunction::emitAddrOfFieldStorage(Address base, mlir::Location loc = getLoc(field->getLocation()); + // Retrieve layout information for both type resolution and alignment. + const RecordDecl *rec = field->getParent(); + const CIRGenRecordLayout &layout = cgm.getTypes().getCIRGenRecordLayout(rec); + unsigned idx = layout.getCIRFieldNo(field); + + // For potentially-overlapping fields (e.g. [[no_unique_address]]), the + // record stores the base subobject type (without tail padding) rather than + // the complete object type. Use the record's member type for get_member, + // then bitcast to the complete type for downstream use. + // + // For unions, all fields map to index 0, so we use the field's declared type + // directly instead of looking up the member type from the layout. mlir::Type fieldType = convertType(field->getType()); auto fieldPtr = cir::PointerType::get(fieldType); + bool needsBitcast = false; + + if (!rec->isUnion() && field->isPotentiallyOverlapping()) { + mlir::Type memberType = layout.getCIRType().getMembers()[idx]; + fieldPtr = cir::PointerType::get(memberType); + needsBitcast = true; + } + // For most cases fieldName is the same as field->getName() but for lambdas, // which do not currently carry the name, so it can be passed down from the // CaptureStmt. - cir::GetMemberOp memberAddr = builder.createGetMember( - loc, fieldPtr, base.getPointer(), fieldName, fieldIndex); + mlir::Value addr = builder.createGetMember(loc, fieldPtr, base.getPointer(), + fieldName, fieldIndex); + + // If the field is potentially overlapping, the record member uses the base + // subobject type. Cast to the complete object pointer type expected by + // callers (analogous to OG's opaque pointer behavior). + if (needsBitcast) { + auto completePtr = cir::PointerType::get(fieldType); + addr = builder.createBitcast(addr, completePtr); + } - // Retrieve layout information, compute alignment and return the final - // address. - const RecordDecl *rec = field->getParent(); - const CIRGenRecordLayout &layout = cgm.getTypes().getCIRGenRecordLayout(rec); - unsigned idx = layout.getCIRFieldNo(field); CharUnits offset = CharUnits::fromQuantity( layout.getCIRType().getElementOffset(cgm.getDataLayout().layout, idx)); - return Address(memberAddr, base.getAlignment().alignmentAtOffset(offset)); + return Address(addr, base.getAlignment().alignmentAtOffset(offset)); } /// Given an expression of pointer type, try to diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp index 29eda175c208c..0d2e48f248869 100644 --- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp @@ -572,9 +572,15 @@ void CIRRecordLowering::accumulateFields() { assert((field == fieldEnd || !field->isBitField()) && "Failed to accumulate all the bitfields"); } else if (!field->isZeroSize(astContext)) { - members.push_back(MemberInfo(bitsToCharUnits(getFieldBitOffset(*field)), - MemberInfo::InfoKind::Field, - getStorageType(*field), *field)); + // Use base subobject layout for potentially-overlapping fields, + // as it is done in RecordLayoutBuilder. + members.push_back(MemberInfo( + bitsToCharUnits(getFieldBitOffset(*field)), + MemberInfo::InfoKind::Field, + field->isPotentiallyOverlapping() + ? getStorageType(field->getType()->getAsCXXRecordDecl()) + : getStorageType(*field), + *field)); ++field; } else { // TODO(cir): do we want to do anything special about zero size members? diff --git a/clang/test/CIR/CodeGen/no-unique-address.cpp b/clang/test/CIR/CodeGen/no-unique-address.cpp new file mode 100644 index 0000000000000..4f81e194783d3 --- /dev/null +++ b/clang/test/CIR/CodeGen/no-unique-address.cpp @@ -0,0 +1,53 @@ +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu \ +// RUN: -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu \ +// RUN: -fclangir -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu \ +// RUN: -emit-llvm %s -o %t.og.ll +// RUN: FileCheck --check-prefix=OGCG --input-file=%t.og.ll %s + +struct Base { + int x; +}; + +struct Middle : Base { + char c; + // sizeof(Middle) = 8 (4 for x, 1 for c, 3 tail padding) + // data size = 5 +}; + +struct Outer { + [[no_unique_address]] Middle m; + char extra; + Outer(const Middle &m, char e) : m(m), extra(e) {} +}; + +// The record layout should use the base subobject type for the +// [[no_unique_address]] field, allowing 'extra' to overlap with +// Middle's tail padding. + +// CIR: !rec_Middle2Ebase = !cir.record<struct "Middle.base" packed {!rec_Base, !s8i}> +// CIR: !rec_Outer = !cir.record<struct "Outer" padded {!rec_Middle2Ebase, !s8i, + +// CIR-LABEL: cir.func {{.*}} @_ZN5OuterC2ERK6Middlec( +// CIR: %[[THIS:.*]] = cir.load %{{.+}} : !cir.ptr<!cir.ptr<!rec_Outer>>, !cir.ptr<!rec_Outer> +// CIR: %[[M_BASE:.*]] = cir.get_member %[[THIS]][0] {name = "m"} : !cir.ptr<!rec_Outer> -> !cir.ptr<!rec_Middle2Ebase> +// CIR-NEXT: %[[M_COMPLETE:.*]] = cir.cast bitcast %[[M_BASE]] : !cir.ptr<!rec_Middle2Ebase> -> !cir.ptr<!rec_Middle> +// CIR: cir.copy %{{.+}} to %[[M_COMPLETE]] : !cir.ptr<!rec_Middle> +// CIR: %[[EXTRA:.*]] = cir.get_member %[[THIS]][1] {name = "extra"} : !cir.ptr<!rec_Outer> -> !cir.ptr<!s8i> + +// LLVM-LABEL: define {{.*}} void @_ZN5OuterC2ERK6Middlec( +// LLVM: %[[GEP:.*]] = getelementptr %struct.Outer, ptr %{{.+}}, i32 0, i32 0 +// LLVM: call void @llvm.memcpy.p0.p0.i64(ptr %[[GEP]], ptr %{{.+}}, i64 8, i1 false) + +// OGCG-LABEL: define {{.*}} void @_ZN5OuterC2ERK6Middlec( +// OGCG: %[[GEP:.*]] = getelementptr inbounds nuw %struct.Outer, ptr %{{.+}}, i32 0, i32 0 +// TODO(CIR): OG emits i64 5 here via ConstructorMemcpyizer, which CIR +// doesn't have yet. CIR copies the full 8-byte type instead. +// OGCG: call void @llvm.memcpy.p0.p0.i64(ptr {{.*}} %[[GEP]], ptr {{.*}} %{{.+}}, i64 5, i1 false) + +void test(const Middle &m) { + Outer o(m, 'x'); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
