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
