[CIR] Upstream support for record packing and padding (#136036)
This change adds support for packing and padding record types in ClangIR
and introduces some infrastructure needed for this computation.
Although union support has not been upstreamed yet, there is no good way
to report unions as NYI in the layout computation, so the code added
here includes layout computation for unions. Unions will be added soon.
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDataLayout.h b/clang/include/clang/CIR/Dialect/IR/CIRDataLayout.h
new file mode 100644
index 0000000..62fc53f
--- /dev/null
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDataLayout.h
@@ -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
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
index 23e2075..b028bc7 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
@@ -502,6 +502,11 @@
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;
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 0b66ed3..0105d1b 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -102,13 +102,10 @@
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; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
index 4e3adea..5e209b5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -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>
@@ -57,8 +58,18 @@
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) {
@@ -66,12 +77,33 @@
}
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) {
@@ -100,6 +132,7 @@
// 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;
@@ -121,6 +154,7 @@
astContext(cirGenTypes.getASTContext()), recordDecl(recordDecl),
astRecordLayout(
cirGenTypes.getASTContext().getASTRecordLayout(recordDecl)),
+ dataLayout(cirGenTypes.getCGModule().getModule()),
zeroInitializable(true), packed(isPacked), padded(false) {}
void CIRRecordLowering::lower() {
@@ -138,18 +172,20 @@
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();
}
@@ -186,6 +222,56 @@
}
}
+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);
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 160732d..8b5646f 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -177,6 +177,12 @@
// Type not yet printed: continue printing the entire record.
printer << ' ';
+ if (getPacked())
+ printer << "packed ";
+
+ if (getPadded())
+ printer << "padded ";
+
if (isIncomplete()) {
printer << "incomplete";
} else {
@@ -210,6 +216,10 @@
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;
}
@@ -225,17 +235,78 @@
//===----------------------------------------------------------------------===//
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 {
+ 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;
}
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index cb318c8..8c4a672 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -1356,12 +1356,11 @@
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;
diff --git a/clang/test/CIR/CodeGen/struct.c b/clang/test/CIR/CodeGen/struct.c
index e1b01e6..55f316f 100644
--- a/clang/test/CIR/CodeGen/struct.c
+++ b/clang/test/CIR/CodeGen/struct.c
@@ -5,9 +5,19 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+// For LLVM IR checks, the structs are defined before the variables, so these
+// checks are at the top.
+// LLVM: %struct.CompleteS = type { i32, i8 }
+// LLVM: %struct.PackedS = type <{ i32, i8 }>
+// LLVM: %struct.PackedAndPaddedS = type <{ i32, i8, i8 }>
+// OGCG: %struct.CompleteS = type { i32, i8 }
+// OGCG: %struct.PackedS = type <{ i32, i8 }>
+// OGCG: %struct.PackedAndPaddedS = type <{ i32, i8, i8 }>
+
struct IncompleteS *p;
-// CIR: cir.global external @p = #cir.ptr<null> : !cir.ptr<!cir.record<struct "IncompleteS" incomplete>>
+// CIR: cir.global external @p = #cir.ptr<null> : !cir.ptr<!cir.record<struct
+// CIR-SAME: "IncompleteS" incomplete>>
// LLVM: @p = dso_local global ptr null
// OGCG: @p = global ptr null, align 8
@@ -16,17 +26,44 @@
char b;
} cs;
-// CIR: cir.global external @cs = #cir.zero : !cir.record<struct "CompleteS" {!s32i, !s8i}>
+// CIR: cir.global external @cs = #cir.zero : !cir.record<struct
+// CIR-SAME: "CompleteS" {!s32i, !s8i}>
// LLVM: @cs = dso_local global %struct.CompleteS zeroinitializer
// OGCG: @cs = global %struct.CompleteS zeroinitializer, align 4
+#pragma pack(push)
+#pragma pack(1)
+
+struct PackedS {
+ int a0;
+ char a1;
+} ps;
+
+// CIR: cir.global external @ps = #cir.zero : !cir.record<struct "PackedS"
+// CIR-SAME: packed {!s32i, !s8i}>
+// LLVM: @ps = dso_local global %struct.PackedS zeroinitializer
+// OGCG: @ps = global %struct.PackedS zeroinitializer, align 1
+
+struct PackedAndPaddedS {
+ int b0;
+ char b1;
+} __attribute__((aligned(2))) pps;
+
+// CIR: cir.global external @pps = #cir.zero : !cir.record<struct
+// CIR-SAME: "PackedAndPaddedS" packed padded {!s32i, !s8i, !u8i}>
+// LLVM: @pps = dso_local global %struct.PackedAndPaddedS zeroinitializer
+// OGCG: @pps = global %struct.PackedAndPaddedS zeroinitializer, align 2
+
+#pragma pack(pop)
+
void f(void) {
struct IncompleteS *p;
}
// CIR: cir.func @f()
// CIR-NEXT: cir.alloca !cir.ptr<!cir.record<struct "IncompleteS" incomplete>>,
-// CIR-SAME: !cir.ptr<!cir.ptr<!cir.record<struct "IncompleteS" incomplete>>>, ["p"]
+// CIR-SAME: !cir.ptr<!cir.ptr<!cir.record<struct
+// CIR-SAME: "IncompleteS" incomplete>>>, ["p"]
// CIR-NEXT: cir.return
// LLVM: define void @f()
@@ -44,7 +81,8 @@
// CIR: cir.func @f2()
// CIR-NEXT: cir.alloca !cir.record<struct "CompleteS" {!s32i, !s8i}>,
-// CIR-SAME: !cir.ptr<!cir.record<struct "CompleteS" {!s32i, !s8i}>>, ["s"]
+// CIR-SAME: !cir.ptr<!cir.record<struct "CompleteS" {!s32i, !s8i}>>,
+// CIR-SAME: ["s"] {alignment = 4 : i64}
// CIR-NEXT: cir.return
// LLVM: define void @f2()