Skip to content

[CIR] Upstream support for record packing and padding #136036

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRDataLayout.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
// Provides an LLVM-like API wrapper to DLTI and MLIR layout queries. This
// makes it easier to port some of LLVM codegen layout logic to CIR.
//===----------------------------------------------------------------------===//

#ifndef CLANG_CIR_DIALECT_IR_CIRDATALAYOUT_H
#define CLANG_CIR_DIALECT_IR_CIRDATALAYOUT_H

#include "mlir/IR/BuiltinOps.h"

namespace cir {

// TODO(cir): This might be replaced by a CIRDataLayout interface which can
// provide the same functionalities.
class CIRDataLayout {
// This is starting with the minimum functionality needed for code that is
// being upstreamed. Additional methods and members will be added as needed.
public:
mlir::DataLayout layout;

/// Constructs a DataLayout the module's data layout attribute.
CIRDataLayout(mlir::ModuleOp modOp) : layout{modOp} {}
};

} // namespace cir

#endif // CLANG_CIR_DIALECT_IR_CIRDATALAYOUT_H
5 changes: 5 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRTypes.td
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,11 @@ def CIR_RecordType : CIR_Type<"Record", "record",

void complete(llvm::ArrayRef<mlir::Type> members, bool packed,
bool isPadded);

private:
unsigned computeStructSize(const mlir::DataLayout &dataLayout) const;
uint64_t computeStructAlignment(const mlir::DataLayout &dataLayout) const;
public:
}];

let hasCustomAssemblyFormat = 1;
Expand Down
3 changes: 0 additions & 3 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,10 @@ struct MissingFeatures {
static bool shouldReverseUnaryCondOnBoolExpr() { return false; }

// RecordType
static bool recordTypeLayoutInfo() { return false; }
static bool recursiveRecordLayout() { return false; }
static bool skippedLayout() { return false; }
static bool astRecordDeclAttr() { return false; }
static bool cxxSupport() { return false; }
static bool packedRecords() { return false; }
static bool recordPadding() { return false; }
static bool recordZeroInit() { return false; }
static bool zeroSizeRecordMembers() { return false; }

Expand Down
104 changes: 95 additions & 9 deletions clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "clang/AST/DeclCXX.h"
#include "clang/AST/RecordLayout.h"
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/Dialect/IR/CIRDataLayout.h"
#include "llvm/Support/Casting.h"

#include <memory>
Expand Down Expand Up @@ -57,21 +58,52 @@ struct CIRRecordLowering final {
CIRRecordLowering(CIRGenTypes &cirGenTypes, const RecordDecl *recordDecl,
bool isPacked);

/// Constructs a MemberInfo instance from an offset and mlir::Type.
MemberInfo makeStorageInfo(CharUnits offset, mlir::Type data) {
return MemberInfo(offset, MemberInfo::InfoKind::Field, data);
}

void lower();

/// Determines if we need a packed llvm struct.
void determinePacked();
/// Inserts padding everywhere it's needed.
void insertPadding();

void accumulateFields();

CharUnits bitsToCharUnits(uint64_t bitOffset) {
return astContext.toCharUnitsFromBits(bitOffset);
}

CharUnits getSize(mlir::Type Ty) {
assert(!cir::MissingFeatures::recordTypeLayoutInfo());
return CharUnits::One();
return CharUnits::fromQuantity(dataLayout.layout.getTypeSize(Ty));
}
CharUnits getAlignment(mlir::Type Ty) {
assert(!cir::MissingFeatures::recordTypeLayoutInfo());
return CharUnits::One();
return CharUnits::fromQuantity(dataLayout.layout.getTypeABIAlignment(Ty));
}

/// Wraps cir::IntType with some implicit arguments.
mlir::Type getUIntNType(uint64_t numBits) {
unsigned alignedBits = llvm::PowerOf2Ceil(numBits);
alignedBits = std::max(8u, alignedBits);
return cir::IntType::get(&cirGenTypes.getMLIRContext(), alignedBits,
/*isSigned=*/false);
}

mlir::Type getCharType() {
return cir::IntType::get(&cirGenTypes.getMLIRContext(),
astContext.getCharWidth(),
/*isSigned=*/false);
}

mlir::Type getByteArrayType(CharUnits numberOfChars) {
assert(!numberOfChars.isZero() && "Empty byte arrays aren't allowed.");
mlir::Type type = getCharType();
return numberOfChars == CharUnits::One()
? type
: cir::ArrayType::get(type.getContext(), type,
numberOfChars.getQuantity());
}

mlir::Type getStorageType(const FieldDecl *fieldDecl) {
Expand Down Expand Up @@ -100,6 +132,7 @@ struct CIRRecordLowering final {
// Output fields, consumed by CIRGenTypes::computeRecordLayout
llvm::SmallVector<mlir::Type, 16> fieldTypes;
llvm::DenseMap<const FieldDecl *, unsigned> fields;
cir::CIRDataLayout dataLayout;

LLVM_PREFERRED_TYPE(bool)
unsigned zeroInitializable : 1;
Expand All @@ -121,6 +154,7 @@ CIRRecordLowering::CIRRecordLowering(CIRGenTypes &cirGenTypes,
astContext(cirGenTypes.getASTContext()), recordDecl(recordDecl),
astRecordLayout(
cirGenTypes.getASTContext().getASTRecordLayout(recordDecl)),
dataLayout(cirGenTypes.getCGModule().getModule()),
zeroInitializable(true), packed(isPacked), padded(false) {}

void CIRRecordLowering::lower() {
Expand All @@ -138,18 +172,20 @@ void CIRRecordLowering::lower() {

assert(!cir::MissingFeatures::cxxSupport());

CharUnits size = astRecordLayout.getSize();

accumulateFields();

llvm::stable_sort(members);
// TODO: implement clipTailPadding once bitfields are implemented
assert(!cir::MissingFeatures::bitfields());
// TODO: implemented packed records
assert(!cir::MissingFeatures::packedRecords());
// TODO: implement padding
assert(!cir::MissingFeatures::recordPadding());
// TODO: support zeroInit
assert(!cir::MissingFeatures::recordZeroInit());

members.push_back(makeStorageInfo(size, getUIntNType(8)));
determinePacked();
insertPadding();
members.pop_back();

fillOutputFields();
}

Expand Down Expand Up @@ -186,6 +222,56 @@ void CIRRecordLowering::accumulateFields() {
}
}

void CIRRecordLowering::determinePacked() {
if (packed)
return;
CharUnits alignment = CharUnits::One();

// TODO(cir): handle non-virtual base types
assert(!cir::MissingFeatures::cxxSupport());

for (const MemberInfo &member : members) {
if (!member.data)
continue;
// If any member falls at an offset that it not a multiple of its alignment,
// then the entire record must be packed.
if (member.offset % getAlignment(member.data))
packed = true;
alignment = std::max(alignment, getAlignment(member.data));
}
// If the size of the record (the capstone's offset) is not a multiple of the
// record's alignment, it must be packed.
if (members.back().offset % alignment)
packed = true;
// Update the alignment of the sentinel.
if (!packed)
members.back().data = getUIntNType(astContext.toBits(alignment));
}

void CIRRecordLowering::insertPadding() {
std::vector<std::pair<CharUnits, CharUnits>> padding;
CharUnits size = CharUnits::Zero();
for (const MemberInfo &member : members) {
if (!member.data)
continue;
CharUnits offset = member.offset;
assert(offset >= size);
// Insert padding if we need to.
if (offset !=
size.alignTo(packed ? CharUnits::One() : getAlignment(member.data)))
padding.push_back(std::make_pair(size, offset - size));
size = offset + getSize(member.data);
}
if (padding.empty())
return;
padded = true;
// Add the padding to the Members list and sort it.
for (const std::pair<CharUnits, CharUnits> &paddingPair : padding)
members.push_back(makeStorageInfo(paddingPair.first,
getByteArrayType(paddingPair.second)));
llvm::stable_sort(members);
}

std::unique_ptr<CIRGenRecordLayout>
CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
CIRRecordLowering lowering(*this, rd, /*packed=*/false);
Expand Down
83 changes: 77 additions & 6 deletions clang/lib/CIR/Dialect/IR/CIRTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ void RecordType::print(mlir::AsmPrinter &printer) const {
// Type not yet printed: continue printing the entire record.
printer << ' ';

if (getPacked())
printer << "packed ";

if (getPadded())
printer << "padded ";

if (isIncomplete()) {
printer << "incomplete";
} else {
Expand Down Expand Up @@ -210,6 +216,10 @@ mlir::StringAttr RecordType::getName() const { return getImpl()->name; }

bool RecordType::getIncomplete() const { return getImpl()->incomplete; }

bool RecordType::getPacked() const { return getImpl()->packed; }

bool RecordType::getPadded() const { return getImpl()->padded; }

cir::RecordType::RecordKind RecordType::getKind() const {
return getImpl()->kind;
}
Expand All @@ -225,17 +235,78 @@ void RecordType::complete(ArrayRef<Type> members, bool packed, bool padded) {
//===----------------------------------------------------------------------===//

llvm::TypeSize
RecordType::getTypeSizeInBits(const ::mlir::DataLayout &dataLayout,
::mlir::DataLayoutEntryListRef params) const {
assert(!cir::MissingFeatures::recordTypeLayoutInfo());
return llvm::TypeSize::getFixed(8);
RecordType::getTypeSizeInBits(const mlir::DataLayout &dataLayout,
mlir::DataLayoutEntryListRef params) const {
if (isUnion()) {
// TODO(CIR): Implement union layout.
return llvm::TypeSize::getFixed(8);
}

unsigned recordSize = computeStructSize(dataLayout);
return llvm::TypeSize::getFixed(recordSize * 8);
}

uint64_t
RecordType::getABIAlignment(const ::mlir::DataLayout &dataLayout,
::mlir::DataLayoutEntryListRef params) const {
assert(!cir::MissingFeatures::recordTypeLayoutInfo());
return 4;
if (isUnion()) {
// TODO(CIR): Implement union layout.
return 8;
}

// Packed structures always have an ABI alignment of 1.
if (getPacked())
return 1;
return computeStructAlignment(dataLayout);
}

unsigned
RecordType::computeStructSize(const mlir::DataLayout &dataLayout) const {
Copy link
Contributor Author

@andykaylor andykaylor Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches what the LLVM dialect does for LLVMStructType::getTypeSizeInBits

assert(isComplete() && "Cannot get layout of incomplete records");

// This is a similar algorithm to LLVM's StructLayout.
unsigned recordSize = 0;
uint64_t recordAlignment = 1;

// We can't use a range-based for loop here because we might be ignoring the
// last element.
for (mlir::Type ty : getMembers()) {
// This assumes that we're calculating size based on the ABI alignment, not
// the preferred alignment for each type.
const uint64_t tyAlign =
(getPacked() ? 1 : dataLayout.getTypeABIAlignment(ty));

// Add padding to the struct size to align it to the abi alignment of the
// element type before than adding the size of the element.
recordSize = llvm::alignTo(recordSize, tyAlign);
recordSize += dataLayout.getTypeSize(ty);

// The alignment requirement of a struct is equal to the strictest alignment
// requirement of its elements.
recordAlignment = std::max(tyAlign, recordAlignment);
}

// At the end, add padding to the struct to satisfy its own alignment
// requirement. Otherwise structs inside of arrays would be misaligned.
recordSize = llvm::alignTo(recordSize, recordAlignment);
return recordSize;
}

// We also compute the alignment as part of computeStructSize, but this is more
// efficient. Ideally, we'd like to compute both at once and cache the result,
// but that's implemented yet.
// TODO(CIR): Implement a way to cache the result.
uint64_t
RecordType::computeStructAlignment(const mlir::DataLayout &dataLayout) const {
assert(isComplete() && "Cannot get layout of incomplete records");

// This is a similar algorithm to LLVM's StructLayout.
uint64_t recordAlignment = 1;
for (mlir::Type ty : getMembers())
recordAlignment =
std::max(dataLayout.getTypeABIAlignment(ty), recordAlignment);

return recordAlignment;
}

//===----------------------------------------------------------------------===//
Expand Down
5 changes: 2 additions & 3 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1356,12 +1356,11 @@ static void prepareTypeConverter(mlir::LLVMTypeConverter &converter,
if (type.getName()) {
llvmStruct = mlir::LLVM::LLVMStructType::getIdentified(
type.getContext(), type.getPrefixedName());
assert(!cir::MissingFeatures::packedRecords());
if (llvmStruct.setBody(llvmMembers, /*isPacked=*/true).failed())
if (llvmStruct.setBody(llvmMembers, type.getPacked()).failed())
llvm_unreachable("Failed to set body of record");
} else { // Record has no name: lower as literal record.
llvmStruct = mlir::LLVM::LLVMStructType::getLiteral(
type.getContext(), llvmMembers, /*isPacked=*/true);
type.getContext(), llvmMembers, type.getPacked());
}

return llvmStruct;
Expand Down
Loading
Loading