[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