Author: adams381
Date: 2026-04-10T14:50:59-05:00
New Revision: 2490ee3f0ee60b77de4201f0e5008a959ef9d1ca

URL: 
https://github.com/llvm/llvm-project/commit/2490ee3f0ee60b77de4201f0e5008a959ef9d1ca
DIFF: 
https://github.com/llvm/llvm-project/commit/2490ee3f0ee60b77de4201f0e5008a959ef9d1ca.diff

LOG: [CIR][ABI] Add ABI metadata fields to RecordType (#188300)

Store AST-derived layout information on `cir::RecordType` so that ABI
lowering passes (which have no AST access) can make correct calling
convention decisions.

The five new fields on `RecordTypeStorage` are: `triviallyCopyable`
(from `canPassInRegisters`), `triviallyDestructible` (from
`hasTrivialDestructor`), `isEmpty` (from
`CXXRecordDecl::isEmpty`/`field_empty`), `dataSizeInBits` (from
`ASTRecordLayout::getDataSize`), and `recordAlignInBytes` (from
`ASTRecordLayout::getAlignment`). They're set during
`computeRecordLayout` and are not part of the printed/parsed CIR text.
The `complete()` signature uses defaults so existing callers don't need
changes.

Anonymous records (created by passes, not CIRGen) default to trivially
copyable/destructible since they represent synthetic aggregates like
member pointer lowering tuples.

Unit test with 9 cases exercises all fields, anonymous defaults, and
backward compatibility.

Added: 
    clang/test/CIR/CodeGen/record-type-metadata.cpp
    clang/unittests/CIR/RecordTypeMetadataTest.cpp

Modified: 
    clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
    clang/include/clang/CIR/Dialect/IR/CIRDialect.h
    clang/include/clang/CIR/Dialect/IR/CIRDialect.td
    clang/lib/CIR/CodeGen/CIRGenModule.cpp
    clang/lib/CIR/CodeGen/CIRGenModule.h
    clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
    clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
    clang/lib/CIR/Dialect/IR/CIRTypes.cpp
    clang/unittests/CIR/CMakeLists.txt
    llvm/utils/gn/secondary/clang/unittests/CIR/BUILD.gn

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 01bac73e441a8..c6f95c301cdad 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -110,6 +110,68 @@ def CIR_SourceLanguageAttr : 
CIR_EnumAttr<CIR_SourceLanguage, "lang"> {
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// ArgPassingKind + RecordLayoutAttr
+//===----------------------------------------------------------------------===//
+
+def CIR_ArgPassingKind : CIR_I32EnumAttr<
+    "ArgPassingKind", "record argument passing eligibility", [
+  I32EnumAttrCase<"CanPassInRegs", 0, "can_pass_in_regs">,
+  I32EnumAttrCase<"CannotPassInRegs", 1, "cannot_pass_in_regs">,
+  I32EnumAttrCase<"CanNeverPassInRegs", 2, "can_never_pass_in_regs">
+]> {
+  let genSpecializedAttr = 0;
+}
+
+def CIR_RecordLayoutAttr : CIR_Attr<"RecordLayout", "record_layout"> {
+  let summary = "ABI layout metadata for a record type";
+  let description = [{
+    Holds AST-derived ABI metadata for a named record type.  These
+    properties are translation-unit / target properties, not intrinsic
+    to the type, so they live on the module rather than on RecordType.
+
+    Fields:
+    - `arg_passing_kind`: whether the record can be passed in registers
+      per the C++ ABI (mirrors `RecordDecl::getArgPassingRestrictions()`).
+    - `has_trivial_destructor`: from `CXXRecordDecl::hasTrivialDestructor()`.
+    - `record_align_in_bytes`: from `ASTRecordLayout::getAlignment()`.
+      Needed because CIR's DataLayout cannot account for
+      `__attribute__((aligned(N)))`.
+
+    Example:
+    ```
+    module attributes {
+      cir.record_layouts = {
+        "Trivial" = #cir.record_layout<
+          arg_passing_kind = can_pass_in_regs,
+          has_trivial_dtor = true,
+          record_align = 4>,
+        "NonTrivialDtor" = #cir.record_layout<
+          arg_passing_kind = cannot_pass_in_regs,
+          has_trivial_dtor = false,
+          record_align = 4>
+      }
+    }
+    ```
+  }];
+
+  let parameters = (ins
+    EnumParameter<CIR_ArgPassingKind>:$arg_passing_kind,
+    "bool":$has_trivial_dtor,
+    "uint64_t":$record_align
+  );
+
+  let assemblyFormat = [{
+    `<`
+      `arg_passing_kind` `=` $arg_passing_kind `,`
+      `has_trivial_dtor` `=` $has_trivial_dtor `,`
+      `record_align` `=` $record_align
+    `>`
+  }];
+
+  let canHaveIllegalCXXABIType = 0;
+}
+
 
//===----------------------------------------------------------------------===//
 // OptInfoAttr
 
//===----------------------------------------------------------------------===//

diff  --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h 
b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
index ddcb988f9ea84..970a6984a5b05 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
@@ -41,6 +41,10 @@ using BuilderOpStateCallbackRef = llvm::function_ref<void(
 
 namespace cir {
 void buildTerminatedBody(mlir::OpBuilder &builder, mlir::Location loc);
+
+/// Look up the RecordLayoutAttr for a named record in the module's
+/// cir.record_layouts dictionary.  Asserts if the entry is missing.
+RecordLayoutAttr getRecordLayout(mlir::ModuleOp module, mlir::StringAttr name);
 } // namespace cir
 
 // TableGen'erated files for MLIR dialects require that a macro be defined when

diff  --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td 
b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
index f1f94c868e5b0..13095464a3fd2 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
@@ -75,6 +75,7 @@ def CIR_Dialect : Dialect {
     static llvm::StringRef getDefaultFuncAttrsAttrName() { return 
"default_func_attrs"; }
     static llvm::StringRef getResAttrsAttrName() { return "res_attrs"; }
     static llvm::StringRef getArgAttrsAttrName() { return "arg_attrs"; }
+    static llvm::StringRef getRecordLayoutsAttrName() { return 
"cir.record_layouts"; }
 
     static llvm::StringRef getAMDGPUCodeObjectVersionAttrName() { return 
"cir.amdhsa_code_object_version"; }
     static llvm::StringRef getAMDGPUPrintfKindAttrName() { return 
"cir.amdgpu_printf_kind"; }

diff  --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp 
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 3f0f493f5a50b..46635ea1e2482 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -3100,6 +3100,11 @@ void CIRGenModule::release() {
   theModule->setAttr(cir::CIRDialect::getModuleLevelAsmAttrName(),
                      builder.getArrayAttr(globalScopeAsm));
 
+  if (!recordLayoutEntries.empty())
+    theModule->setAttr(
+        cir::CIRDialect::getRecordLayoutsAttrName(),
+        mlir::DictionaryAttr::get(&getMLIRContext(), recordLayoutEntries));
+
   if (getTriple().isAMDGPU() ||
       (getTriple().isSPIRV() && getTriple().getVendor() == llvm::Triple::AMD))
     emitAMDGPUMetadata();

diff  --git a/clang/lib/CIR/CodeGen/CIRGenModule.h 
b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 0bb07f0815d43..ca59f56366822 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -101,6 +101,9 @@ class CIRGenModule : public CIRGenTypeCache {
 
   llvm::SmallVector<mlir::Attribute> globalScopeAsm;
 
+  /// Accumulated record layout entries, materialized in release().
+  llvm::SmallVector<mlir::NamedAttribute> recordLayoutEntries;
+
   llvm::DenseSet<clang::GlobalDecl> diagnosedConflictingDefinitions;
 
   /// A queue of (optional) vtables to consider emitting.
@@ -132,6 +135,11 @@ class CIRGenModule : public CIRGenTypeCache {
 public:
   mlir::ModuleOp getModule() const { return theModule; }
   CIRGenBuilderTy &getBuilder() { return builder; }
+
+  /// Queue a record layout entry for materialization in release().
+  void addRecordLayout(mlir::StringAttr name, cir::RecordLayoutAttr attr) {
+    recordLayoutEntries.push_back(mlir::NamedAttribute(name, attr));
+  }
   clang::ASTContext &getASTContext() const { return astContext; }
   const clang::TargetInfo &getTarget() const { return target; }
   const clang::CodeGenOptions &getCodeGenOpts() const { return codeGenOpts; }

diff  --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp 
b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
index 7e1f9c6768e61..6947fd257f47f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -666,6 +666,19 @@ void CIRRecordLowering::insertPadding() {
   llvm::stable_sort(members);
 }
 
+static cir::ArgPassingKind
+convertRecordArgPassingKind(RecordArgPassingKind kind) {
+  switch (kind) {
+  case RecordArgPassingKind::CanPassInRegs:
+    return cir::ArgPassingKind::CanPassInRegs;
+  case RecordArgPassingKind::CannotPassInRegs:
+    return cir::ArgPassingKind::CannotPassInRegs;
+  case RecordArgPassingKind::CanNeverPassInRegs:
+    return cir::ArgPassingKind::CanNeverPassInRegs;
+  }
+  llvm_unreachable("unknown RecordArgPassingKind");
+}
+
 std::unique_ptr<CIRGenRecordLayout>
 CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
   CIRRecordLowering lowering(*this, rd, /*packed=*/false);
@@ -700,6 +713,23 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, 
cir::RecordType *ty) {
   assert(!cir::MissingFeatures::astRecordDeclAttr());
   ty->complete(lowering.fieldTypes, lowering.packed, lowering.padded);
 
+  // Queue ABI metadata for the module-level cir.record_layouts attribute.
+  if (ty->getName()) {
+    mlir::MLIRContext *mlirCtx = ty->getContext();
+    cir::ArgPassingKind apk =
+        convertRecordArgPassingKind(rd->getArgPassingRestrictions());
+
+    bool hasTrivialDestructor = true;
+    if (auto *cxxRD = dyn_cast<CXXRecordDecl>(rd))
+      hasTrivialDestructor = cxxRD->hasTrivialDestructor();
+    const auto &astLayout = astContext.getASTRecordLayout(rd);
+    uint64_t recordAlignInBytes = astLayout.getAlignment().getQuantity();
+
+    cgm.addRecordLayout(ty->getName(), cir::RecordLayoutAttr::get(
+                                           mlirCtx, apk, hasTrivialDestructor,
+                                           recordAlignInBytes));
+  }
+
   auto rl = std::make_unique<CIRGenRecordLayout>(
       ty ? *ty : cir::RecordType{}, baseTy ? baseTy : cir::RecordType{},
       (bool)lowering.zeroInitializable, 
(bool)lowering.zeroInitializableAsBase);

diff  --git a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp 
b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
index 27cba6a20b445..270c55dfc4541 100644
--- a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
@@ -764,6 +764,20 @@ LogicalResult DynamicCastInfoAttr::verify(
   return success();
 }
 
+//===----------------------------------------------------------------------===//
+// RecordLayout lookup
+//===----------------------------------------------------------------------===//
+
+RecordLayoutAttr cir::getRecordLayout(mlir::ModuleOp module,
+                                      mlir::StringAttr name) {
+  auto dict = module->getAttrOfType<mlir::DictionaryAttr>(
+      CIRDialect::getRecordLayoutsAttrName());
+  assert(dict && "module missing cir.record_layouts attribute");
+  auto attr = dict.getAs<RecordLayoutAttr>(name);
+  assert(attr && "record layout entry missing for named record");
+  return attr;
+}
+
 
//===----------------------------------------------------------------------===//
 // CIR Dialect
 
//===----------------------------------------------------------------------===//

diff  --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp 
b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 466064d783f2c..9f2342254a882 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -336,14 +336,13 @@ Type RecordType::getLargestMember(const 
::mlir::DataLayout &dataLayout) const {
   auto endIt = getPadded() ? std::prev(members.end()) : members.end();
   if (endIt == members.begin())
     return {};
-  return *std::max_element(
-      members.begin(), endIt, [&](Type lhs, Type rhs) {
-        return dataLayout.getTypeABIAlignment(lhs) <
-                   dataLayout.getTypeABIAlignment(rhs) ||
-               (dataLayout.getTypeABIAlignment(lhs) ==
-                    dataLayout.getTypeABIAlignment(rhs) &&
-                dataLayout.getTypeSize(lhs) < dataLayout.getTypeSize(rhs));
-      });
+  return *std::max_element(members.begin(), endIt, [&](Type lhs, Type rhs) {
+    return dataLayout.getTypeABIAlignment(lhs) <
+               dataLayout.getTypeABIAlignment(rhs) ||
+           (dataLayout.getTypeABIAlignment(lhs) ==
+                dataLayout.getTypeABIAlignment(rhs) &&
+            dataLayout.getTypeSize(lhs) < dataLayout.getTypeSize(rhs));
+  });
 }
 
 bool RecordType::isLayoutIdentical(const RecordType &other) {

diff  --git a/clang/test/CIR/CodeGen/record-type-metadata.cpp 
b/clang/test/CIR/CodeGen/record-type-metadata.cpp
new file mode 100644
index 0000000000000..39d7d128618ea
--- /dev/null
+++ b/clang/test/CIR/CodeGen/record-type-metadata.cpp
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -fclangir 
-emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+
+struct Trivial { int x, y; };
+struct Empty {};
+struct __attribute__((aligned(16))) Aligned { int a, b; };
+
+class NonTrivialDtor {
+  int val;
+public:
+  ~NonTrivialDtor();
+};
+
+void takesTrivial(Trivial t) {}
+void takesEmpty(Empty e) {}
+void takesAligned(Aligned a) {}
+void takesNTD(NonTrivialDtor n) {}
+
+// Record types should NOT contain ABI metadata keywords.
+// CIR-DAG: !rec_Trivial = !cir.record<struct "Trivial" {!s32i, !s32i}>
+// CIR-DAG: !rec_Empty = !cir.record<struct "Empty" padded {!u8i}>
+// CIR-DAG: !rec_Aligned = !cir.record<struct "Aligned" padded {!s32i, !s32i, 
!cir.array<!u8i x 8>}>
+// CIR-DAG: !rec_NonTrivialDtor = !cir.record<class "NonTrivialDtor" {!s32i}>
+
+// ABI metadata lives in module-level cir.record_layouts attribute.
+// CIR-DAG: Trivial = #cir.record_layout<arg_passing_kind = can_pass_in_regs, 
has_trivial_dtor = true, record_align = 4>
+// CIR-DAG: Empty = #cir.record_layout<arg_passing_kind = can_pass_in_regs, 
has_trivial_dtor = true, record_align = 1>
+// CIR-DAG: Aligned = #cir.record_layout<arg_passing_kind = can_pass_in_regs, 
has_trivial_dtor = true, record_align = 16>
+// CIR-DAG: NonTrivialDtor = #cir.record_layout<arg_passing_kind = 
cannot_pass_in_regs, has_trivial_dtor = false, record_align = 4>

diff  --git a/clang/unittests/CIR/CMakeLists.txt 
b/clang/unittests/CIR/CMakeLists.txt
index 650fde38c48a9..0514dd961b7b9 100644
--- a/clang/unittests/CIR/CMakeLists.txt
+++ b/clang/unittests/CIR/CMakeLists.txt
@@ -5,6 +5,7 @@ include_directories(${MLIR_TABLEGEN_OUTPUT_DIR})
 
 add_distinct_clang_unittest(CIRUnitTests
   PointerLikeTest.cpp
+  RecordTypeMetadataTest.cpp
   LLVM_COMPONENTS
   Core
 

diff  --git a/clang/unittests/CIR/RecordTypeMetadataTest.cpp 
b/clang/unittests/CIR/RecordTypeMetadataTest.cpp
new file mode 100644
index 0000000000000..79d278d9de3a1
--- /dev/null
+++ b/clang/unittests/CIR/RecordTypeMetadataTest.cpp
@@ -0,0 +1,91 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Unit tests for CIR RecordLayoutAttr (module-level ABI metadata).
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/MLIRContext.h"
+#include "clang/CIR/Dialect/IR/CIRAttrs.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
+#include "gtest/gtest.h"
+
+using namespace mlir;
+using namespace cir;
+
+class RecordLayoutAttrTest : public ::testing::Test {
+protected:
+  RecordLayoutAttrTest() { context.loadDialect<cir::CIRDialect>(); }
+
+  MLIRContext context;
+
+  mlir::StringAttr getName(llvm::StringRef name) {
+    return mlir::StringAttr::get(&context, name);
+  }
+};
+
+TEST_F(RecordLayoutAttrTest, CanPassInRegs) {
+  auto attr =
+      RecordLayoutAttr::get(&context, ArgPassingKind::CanPassInRegs, true, 4);
+  EXPECT_EQ(attr.getArgPassingKind(), ArgPassingKind::CanPassInRegs);
+  EXPECT_TRUE(attr.getHasTrivialDtor());
+  EXPECT_EQ(attr.getRecordAlign(), 4u);
+}
+
+TEST_F(RecordLayoutAttrTest, CannotPassInRegs) {
+  auto attr = RecordLayoutAttr::get(&context, ArgPassingKind::CannotPassInRegs,
+                                    false, 4);
+  EXPECT_EQ(attr.getArgPassingKind(), ArgPassingKind::CannotPassInRegs);
+  EXPECT_FALSE(attr.getHasTrivialDtor());
+}
+
+TEST_F(RecordLayoutAttrTest, CanNeverPassInRegs) {
+  auto attr = RecordLayoutAttr::get(
+      &context, ArgPassingKind::CanNeverPassInRegs, false, 8);
+  EXPECT_EQ(attr.getArgPassingKind(), ArgPassingKind::CanNeverPassInRegs);
+  EXPECT_FALSE(attr.getHasTrivialDtor());
+  EXPECT_EQ(attr.getRecordAlign(), 8u);
+}
+
+TEST_F(RecordLayoutAttrTest, HighAlignment) {
+  auto attr =
+      RecordLayoutAttr::get(&context, ArgPassingKind::CanPassInRegs, true, 32);
+  EXPECT_EQ(attr.getRecordAlign(), 32u);
+}
+
+TEST_F(RecordLayoutAttrTest, RecordTypeUnchanged) {
+  IntType i32 = IntType::get(&context, 32, true);
+  auto ty =
+      RecordType::get(&context, getName("Foo"), 
RecordType::RecordKind::Struct);
+  ty.complete({i32, i32}, /*packed=*/false, /*padded=*/false);
+  EXPECT_TRUE(ty.isComplete());
+  EXPECT_EQ(ty.getMembers().size(), 2u);
+}
+
+TEST_F(RecordLayoutAttrTest, ModuleLevelLookup) {
+  mlir::OpBuilder builder(&context);
+  auto loc = builder.getUnknownLoc();
+  auto module = mlir::ModuleOp::create(loc);
+
+  auto layoutAttr =
+      RecordLayoutAttr::get(&context, ArgPassingKind::CanPassInRegs, true, 8);
+
+  llvm::SmallVector<mlir::NamedAttribute> entries;
+  entries.push_back(mlir::NamedAttribute(getName("TestRecord"), layoutAttr));
+  module->setAttr(CIRDialect::getRecordLayoutsAttrName(),
+                  mlir::DictionaryAttr::get(&context, entries));
+
+  RecordLayoutAttr result = cir::getRecordLayout(module, 
getName("TestRecord"));
+  EXPECT_EQ(result.getArgPassingKind(), ArgPassingKind::CanPassInRegs);
+  EXPECT_TRUE(result.getHasTrivialDtor());
+  EXPECT_EQ(result.getRecordAlign(), 8u);
+
+  module->erase();
+}

diff  --git a/llvm/utils/gn/secondary/clang/unittests/CIR/BUILD.gn 
b/llvm/utils/gn/secondary/clang/unittests/CIR/BUILD.gn
index c480200e18c8c..4ae09493007eb 100644
--- a/llvm/utils/gn/secondary/clang/unittests/CIR/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang/unittests/CIR/BUILD.gn
@@ -1,5 +1,8 @@
 # Dummy target because real CIRUnitTests depends on //mlir, which isn't
 # part of the GN build.
 group("CIRUnitTests") {
-  sources = [ "PointerLikeTest.cpp" ]
+  sources = [
+    "PointerLikeTest.cpp",
+    "RecordTypeMetadataTest.cpp",
+  ]
 }


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to