From 4853cfbd3d8d66f3f7ba31ef15457b072883a438 Mon Sep 17 00:00:00 2001 From: Andy Kaylor Date: Thu, 17 Apr 2025 16:32:21 -0700 Subject: [PATCH] [CIR] Replace RecordType data layout calculations We have been using RecordLayoutAttr to "cache" data layout information calculated for records. Unfortunately, it wasn't actually caching the information, and because each call was calculating more information than it needed, it was doing extra work. This replaces the previous implementation with a set of functions that compute only the information needed. Ideally, we would like to have a mechanism to properly cache this information, but until such a mechanism is implemented, these new functions should be a small step forward. --- .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 39 ---- .../include/clang/CIR/Dialect/IR/CIRTypes.td | 11 +- clang/lib/CIR/Dialect/IR/CIRAttrs.cpp | 12 - clang/lib/CIR/Dialect/IR/CIRTypes.cpp | 211 +++++++++++------- 4 files changed, 133 insertions(+), 140 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index 0b1316fc4012..e78baa37bccd 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -838,45 +838,6 @@ def VTableAttr : CIR_Attr<"VTable", "vtable", [TypedAttrInterface]> { }]; } -//===----------------------------------------------------------------------===// -// RecordLayoutAttr -//===----------------------------------------------------------------------===// - -// Used to decouple layout information from the record type. RecordType's -// uses this attribute to cache that information. - -def RecordLayoutAttr : CIR_Attr<"RecordLayout", "record_layout"> { - let summary = "ABI specific information about a record layout"; - let description = [{ - Holds layout information often queried by !cir.record users - during lowering passes and optimizations. - }]; - - let parameters = (ins "unsigned":$size, - "unsigned":$alignment, - "bool":$padded, - "mlir::Type":$largest_member, - "mlir::ArrayAttr":$offsets); - - let builders = [ - AttrBuilderWithInferredContext<(ins "unsigned":$size, - "unsigned":$alignment, - "bool":$padded, - "mlir::Type":$largest_member, - "mlir::ArrayAttr":$offsets), [{ - return $_get(largest_member.getContext(), size, alignment, padded, - largest_member, offsets); - }]>, - ]; - - let genVerifyDecl = 1; - let assemblyFormat = [{ - `<` - struct($size, $alignment, $padded, $largest_member, $offsets) - `>` - }]; -} - //===----------------------------------------------------------------------===// // DynamicCastInfoAttr //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td index 194fb5360ee3..a00f172c1cc1 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td @@ -743,13 +743,12 @@ def CIR_RecordType : CIR_Type<"Record", "record", bool isLayoutIdentical(const RecordType &other); - // Utilities for lazily computing and cacheing data layout info. - // FIXME: currently opaque because there's a cycle if CIRTypes.types include - // from CIRAttrs.h. The implementation operates in terms of RecordLayoutAttr - // instead. + // Utilities for computing data layout info private: - mutable mlir::Attribute layoutInfo; - void computeSizeAndAlignment(const mlir::DataLayout &dataLayout) const; + unsigned computeStructSize(const mlir::DataLayout &dataLayout) const; + unsigned computeUnionSize(const mlir::DataLayout &dataLayout) const; + uint64_t computeStructAlignment(const mlir::DataLayout &dataLayout) const; + uint64_t computeUnionAlignment(const mlir::DataLayout &dataLayout) const; public: }]; diff --git a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp index 19da8f32519c..88d5bebd8e7e 100644 --- a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp @@ -170,18 +170,6 @@ LogicalResult ConstRecordAttr::verify( return success(); } -LogicalResult RecordLayoutAttr::verify( - ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError, unsigned size, - unsigned alignment, bool padded, mlir::Type largest_member, - mlir::ArrayAttr offsets) { - if (not std::all_of(offsets.begin(), offsets.end(), [](mlir::Attribute attr) { - return mlir::isa(attr); - })) { - return emitError() << "all index values must be integers"; - } - return success(); -} - //===----------------------------------------------------------------------===// // LangAttr definitions //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp index 4955edfde4de..125c9fe99f73 100644 --- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp @@ -112,9 +112,34 @@ void CIRDialect::printType(Type type, DialectAsmPrinter &os) const { /// /// Recurses into union members never returning a union as the largest member. Type RecordType::getLargestMember(const ::mlir::DataLayout &dataLayout) const { - if (!layoutInfo) - computeSizeAndAlignment(dataLayout); - return mlir::cast(layoutInfo).getLargestMember(); + assert(isUnion() && "getLargetMember called for non-union record"); + + // This is a similar algorithm to LLVM's StructLayout. + unsigned numElements = getNumElements(); + auto members = getMembers(); + mlir::Type largestMember; + unsigned largestMemberSize = 0; + + // Ignore the last member if this is a padded union. + if (getPadded()) + --numElements; + + for (unsigned i = 0, e = numElements; i != e; ++i) { + auto ty = members[i]; + + // Found a nested union: recurse into it to fetch its largest member. + if (!largestMember || + dataLayout.getTypeABIAlignment(ty) > + dataLayout.getTypeABIAlignment(largestMember) || + (dataLayout.getTypeABIAlignment(ty) == + dataLayout.getTypeABIAlignment(largestMember) && + dataLayout.getTypeSize(ty) > largestMemberSize)) { + largestMember = ty; + largestMemberSize = dataLayout.getTypeSize(largestMember); + } + } + + return largestMember; } Type RecordType::parse(mlir::AsmParser &parser) { @@ -385,117 +410,137 @@ cir::VectorType::getABIAlignment(const ::mlir::DataLayout &dataLayout, return llvm::NextPowerOf2(dataLayout.getTypeSizeInBits(*this)); } +// TODO(cir): Implement a way to cache the datalayout info calculated below. + llvm::TypeSize -RecordType::getTypeSizeInBits(const ::mlir::DataLayout &dataLayout, - ::mlir::DataLayoutEntryListRef params) const { - if (!layoutInfo) - computeSizeAndAlignment(dataLayout); - return llvm::TypeSize::getFixed( - mlir::cast(layoutInfo).getSize() * 8); +RecordType::getTypeSizeInBits(const mlir::DataLayout &dataLayout, + mlir::DataLayoutEntryListRef params) const { + if (isUnion()) + return llvm::TypeSize::getFixed(computeUnionSize(dataLayout) * 8); + + return llvm::TypeSize::getFixed(computeStructSize(dataLayout) * 8); } uint64_t RecordType::getABIAlignment(const ::mlir::DataLayout &dataLayout, ::mlir::DataLayoutEntryListRef params) const { - if (!layoutInfo) - computeSizeAndAlignment(dataLayout); - return mlir::cast(layoutInfo).getAlignment(); -} + // Packed structures always have an ABI alignment of 1. + if (getPacked()) + return 1; -uint64_t RecordType::getElementOffset(const ::mlir::DataLayout &dataLayout, - unsigned idx) const { - assert(idx < getMembers().size() && "access not valid"); - if (!layoutInfo) - computeSizeAndAlignment(dataLayout); - auto offsets = mlir::cast(layoutInfo).getOffsets(); - auto intAttr = mlir::cast(offsets[idx]); - return intAttr.getInt(); + if (isUnion()) + return computeUnionAlignment(dataLayout); + return computeStructAlignment(dataLayout); } -void RecordType::computeSizeAndAlignment( - const ::mlir::DataLayout &dataLayout) const { +unsigned +RecordType::computeUnionSize(const mlir::DataLayout &dataLayout) const { assert(isComplete() && "Cannot get layout of incomplete records"); - // Do not recompute. - if (layoutInfo) - return; + assert(isUnion() && "computeUnionSize called for non-union record"); // This is a similar algorithm to LLVM's StructLayout. unsigned recordSize = 0; llvm::Align recordAlignment{1}; - bool isPadded = false; unsigned numElements = getNumElements(); auto members = getMembers(); - mlir::Type largestMember; unsigned largestMemberSize = 0; - llvm::SmallVector memberOffsets; - bool dontCountLastElt = isUnion() && getPadded(); - if (dontCountLastElt) - numElements--; + auto largestMember = getLargestMember(dataLayout); + recordSize = dataLayout.getTypeSize(largestMember); - // Loop over each of the elements, placing them in memory. - memberOffsets.reserve(numElements); + // If the union is padded, add the padding to the size. + if (getPadded()) { + auto ty = getMembers()[numElements - 1]; + recordSize += dataLayout.getTypeSize(ty); + } - for (unsigned i = 0, e = numElements; i != e; ++i) { - auto ty = members[i]; + return recordSize; +} - // Found a nested union: recurse into it to fetch its largest member. - if (!largestMember || - dataLayout.getTypeABIAlignment(ty) > - dataLayout.getTypeABIAlignment(largestMember) || - (dataLayout.getTypeABIAlignment(ty) == - dataLayout.getTypeABIAlignment(largestMember) && - dataLayout.getTypeSize(ty) > largestMemberSize)) { - largestMember = ty; - largestMemberSize = dataLayout.getTypeSize(largestMember); - } +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; +} + +uint64_t +RecordType::computeUnionAlignment(const mlir::DataLayout &dataLayout) const { + auto largestMember = getLargestMember(dataLayout); + return dataLayout.getTypeABIAlignment(largestMember); +} + +uint64_t RecordType::getElementOffset(const ::mlir::DataLayout &dataLayout, + unsigned idx) const { + assert(idx < getMembers().size() && "access not valid"); + + // All union elements are at offset zero. + if (isUnion() || idx == 0) + return 0; + + assert(isComplete() && "Cannot get layout of incomplete records"); + assert(idx < getNumElements()); + auto members = getMembers(); + + unsigned offset = 0; + + for (unsigned i = 0, e = idx; i != e; ++i) { + auto ty = members[i]; // This matches LLVM since it uses the ABI instead of preferred alignment. const llvm::Align tyAlign = llvm::Align(getPacked() ? 1 : dataLayout.getTypeABIAlignment(ty)); // Add padding if necessary to align the data element properly. - if (!llvm::isAligned(tyAlign, recordSize)) { - isPadded = true; - recordSize = llvm::alignTo(recordSize, tyAlign); - } - - // Keep track of maximum alignment constraint. - recordAlignment = std::max(tyAlign, recordAlignment); - - // Record size up to each element is the element offset. - memberOffsets.push_back(mlir::IntegerAttr::get( - mlir::IntegerType::get(getContext(), 32), isUnion() ? 0 : recordSize)); + offset = llvm::alignTo(offset, tyAlign); // Consume space for this data item - recordSize += dataLayout.getTypeSize(ty); - } - - // For unions, the size and aligment is that of the largest element. - if (isUnion()) { - recordSize = largestMemberSize; - if (getPadded()) { - memberOffsets.push_back(mlir::IntegerAttr::get( - mlir::IntegerType::get(getContext(), 32), recordSize)); - auto ty = getMembers()[numElements]; - recordSize += dataLayout.getTypeSize(ty); - isPadded = true; - } else { - isPadded = false; - } - } else { - // Add padding to the end of the record so that it could be put in an array - // and all array elements would be aligned correctly. - if (!llvm::isAligned(recordAlignment, recordSize)) { - isPadded = true; - recordSize = llvm::alignTo(recordSize, recordAlignment); - } + offset += dataLayout.getTypeSize(ty); } - auto offsets = mlir::ArrayAttr::get(getContext(), memberOffsets); - layoutInfo = cir::RecordLayoutAttr::get(getContext(), recordSize, - recordAlignment.value(), isPadded, - largestMember, offsets); + return offset; } //===----------------------------------------------------------------------===//