[llvm-branch-commits] [clang] [CIR] Fix record layout for [[no_unique_address]] fields (PR #186701)
Henrich Lauko via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sun Mar 15 13:57:31 PDT 2026
https://github.com/xlauko created https://github.com/llvm/llvm-project/pull/186701
None
>From 6195e79e1804ae9dcbabf1f2f5a4de2da131a1a3 Mon Sep 17 00:00:00 2001
From: xlauko <xlauko at mail.muni.cz>
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 5328bb0a812a5..de511e340ab05 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');
+}
More information about the llvm-branch-commits
mailing list