[llvm] 2490ee3 - [CIR][ABI] Add ABI metadata fields to RecordType (#188300)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Apr 10 12:51:05 PDT 2026
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",
+ ]
}
More information about the llvm-commits
mailing list