[clang] [CIR] Add constant record ILE support (PR #155663)
Morris Hafner via cfe-commits
cfe-commits at lists.llvm.org
Wed Sep 3 17:57:16 PDT 2025
https://github.com/mmha updated https://github.com/llvm/llvm-project/pull/155663
>From ce55bc0b9ef72e399ea012eda451299fc5a2b636 Mon Sep 17 00:00:00 2001
From: Morris Hafner <mhafner at nvidia.com>
Date: Tue, 2 Sep 2025 19:11:42 +0200
Subject: [PATCH 1/6] [CIR] Add constant record ILE support
This patch adds basic support for constant record initializer list expressions. There's a couple of limitations:
* No zero initialized padding bytes in C mode
* No bitfields
* No designated initializer lists
* Record alignments are not calculated, yet
* ILEs of derived records don't work, yet
* The constant attribute is not propagated to the backend, resulting in non-constants being emitted in the LLVM IR
---
.../include/clang/CIR/Dialect/IR/CIRTypes.td | 2 +
clang/include/clang/CIR/MissingFeatures.h | 1 +
clang/lib/CIR/CodeGen/CIRGenBuilder.cpp | 40 ++
clang/lib/CIR/CodeGen/CIRGenBuilder.h | 20 +-
clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h | 11 +-
clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 605 +++++++++++++++++-
clang/lib/CIR/CodeGen/CIRGenModule.cpp | 3 +
clang/lib/CIR/CodeGen/CIRGenModule.h | 50 ++
clang/lib/CIR/CodeGen/CIRGenTypeCache.h | 3 +
clang/lib/CIR/Dialect/IR/CIRTypes.cpp | 10 +
clang/test/CIR/CodeGen/constant-inits.cpp | 55 ++
11 files changed, 786 insertions(+), 14 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/constant-inits.cpp
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
index 312d0a9422673..4eec34cb299ab 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
@@ -643,6 +643,8 @@ def CIR_RecordType : CIR_Type<"Record", "record", [
uint64_t getElementOffset(const mlir::DataLayout &dataLayout,
unsigned idx) const;
+ bool isLayoutIdentical(const RecordType &other);
+
private:
unsigned computeStructSize(const mlir::DataLayout &dataLayout) const;
uint64_t computeStructAlignment(const mlir::DataLayout &dataLayout) const;
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 70e0abe30e416..1d35509641e3b 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -134,6 +134,7 @@ struct MissingFeatures {
static bool astRecordDeclAttr() { return false; }
static bool cxxSupport() { return false; }
static bool recordZeroInit() { return false; }
+ static bool recordZeroInitPadding() { return false; }
static bool zeroSizeRecordMembers() { return false; }
static bool recordLayoutVirtualBases() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
index 755c76c89a645..999c2fcbfa490 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
@@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//
#include "CIRGenBuilder.h"
+#include "mlir/IR/BuiltinAttributes.h"
+#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/TypeSwitch.h"
using namespace clang::CIRGen;
@@ -130,6 +132,44 @@ void CIRGenBuilderTy::computeGlobalViewIndicesFromFlatOffset(
computeGlobalViewIndicesFromFlatOffset(offset, subType, layout, indices);
}
+static mlir::Type getAttributeType(mlir::Attribute attr) {
+ return mlir::cast<mlir::TypedAttr>(attr).getType();
+}
+
+cir::RecordType clang::CIRGen::CIRGenBuilderTy::getCompleteRecordType(
+ mlir::ArrayAttr fields, bool packed, bool padded, llvm::StringRef name,
+ const clang::RecordDecl *ast) {
+ llvm::SmallVector<mlir::Type, 8> members;
+ members.reserve(fields.size());
+ llvm::transform(fields, std::back_inserter(members), getAttributeType);
+
+ if (name.empty())
+ return getAnonRecordTy(members, packed, padded);
+
+ return getCompleteRecordTy(members, name, packed, padded);
+}
+
+mlir::Attribute clang::CIRGen::CIRGenBuilderTy::getConstRecordOrZeroAttr(
+ mlir::ArrayAttr arrayAttr, bool packed, bool padded, mlir::Type type) {
+ auto recordTy = mlir::cast_or_null<cir::RecordType>(type);
+
+ // Record type not specified: create anon record type from members.
+ if (!recordTy) {
+ llvm::SmallVector<mlir::Type, 8> members;
+ members.reserve(arrayAttr.size());
+ llvm::transform(arrayAttr, std::back_inserter(members), getAttributeType);
+ recordTy = getType<cir::RecordType>(members, packed, padded,
+ cir::RecordType::Struct);
+ }
+
+ // Return zero or anonymous constant record.
+ const bool isZero = llvm::all_of(
+ arrayAttr, [&](mlir::Attribute a) { return isNullValue(a); });
+ if (isZero)
+ return cir::ZeroAttr::get(recordTy);
+ return cir::ConstRecordAttr::get(recordTy, arrayAttr);
+}
+
// This can't be defined in Address.h because that file is included by
// CIRGenBuilder.h
Address Address::withElementType(CIRGenBuilderTy &builder,
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index 41207afd6a43b..22c0f7d34e4a9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -12,8 +12,10 @@
#include "Address.h"
#include "CIRGenRecordLayout.h"
#include "CIRGenTypeCache.h"
+#include "mlir/IR/Attributes.h"
+#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/Support/LLVM.h"
#include "clang/CIR/Dialect/IR/CIRDataLayout.h"
-#include "clang/CIR/Interfaces/CIRTypeInterfaces.h"
#include "clang/CIR/MissingFeatures.h"
#include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h"
@@ -60,6 +62,16 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
trailingZerosNum);
}
+ cir::ConstArrayAttr getConstArray(mlir::Attribute attrs,
+ cir::ArrayType arrayTy) const {
+ return cir::ConstArrayAttr::get(arrayTy, attrs);
+ }
+
+ mlir::Attribute getConstRecordOrZeroAttr(mlir::ArrayAttr arrayAttr,
+ bool packed = false,
+ bool padded = false,
+ mlir::Type type = {});
+
cir::ConstRecordAttr getAnonConstRecord(mlir::ArrayAttr arrayAttr,
bool packed = false,
bool padded = false,
@@ -150,6 +162,12 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
return type;
}
+ cir::RecordType getCompleteRecordType(mlir::ArrayAttr fields,
+ bool packed = false,
+ bool padded = false,
+ llvm::StringRef name = "",
+ const clang::RecordDecl *ast = nullptr);
+
/// Get an incomplete CIR struct type. If we have a complete record
/// declaration, we may create an incomplete type and then add the
/// members, so \p rd here may be complete.
diff --git a/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h b/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h
index d455f6e283406..7af62b9845e40 100644
--- a/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h
+++ b/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h
@@ -81,6 +81,14 @@ class ConstantEmitter {
// side effects, or emitting an initialization that requires a
// reference to its current location.
mlir::Attribute emitForMemory(mlir::Attribute c, QualType destType);
+ static mlir::Attribute emitForMemory(CIRGenModule &cgm, mlir::Attribute c,
+ clang::QualType destTy);
+
+ mlir::Attribute emitNullForMemory(mlir::Location loc, QualType t) {
+ return emitNullForMemory(loc, cgm, t);
+ }
+ static mlir::Attribute emitNullForMemory(mlir::Location loc,
+ CIRGenModule &cgm, QualType t);
/// Try to emit the initializer of the given declaration as an abstract
/// constant.
@@ -104,7 +112,8 @@ class ConstantEmitter {
mlir::TypedAttr tryEmitPrivate(const Expr *e, QualType destType);
mlir::Attribute tryEmitPrivate(const APValue &value, QualType destType);
- mlir::Attribute tryEmitPrivateForMemory(const APValue &value, QualType t);
+ mlir::Attribute tryEmitPrivateForMemory(const Expr *e, QualType destTy);
+ mlir::Attribute tryEmitPrivateForMemory(const APValue &value, QualType destTy);
private:
#ifndef NDEBUG
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index 262d2548d5c39..81d575f9a4e28 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -12,7 +12,6 @@
#include "Address.h"
#include "CIRGenConstantEmitter.h"
-#include "CIRGenFunction.h"
#include "CIRGenModule.h"
#include "CIRGenRecordLayout.h"
#include "mlir/IR/Attributes.h"
@@ -21,20 +20,571 @@
#include "clang/AST/APValue.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
+#include "clang/AST/CharUnits.h"
#include "clang/AST/OperationKinds.h"
#include "clang/AST/RecordLayout.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/Basic/Builtins.h"
-#include "clang/Basic/Specifiers.h"
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
+#include "clang/CIR/MissingFeatures.h"
#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/Sequence.h"
#include "llvm/Support/ErrorHandling.h"
+#include <iterator>
using namespace clang;
using namespace clang::CIRGen;
+//===----------------------------------------------------------------------===//
+// ConstantAggregateBuilder
+//===----------------------------------------------------------------------===//
+
+namespace {
+class ConstExprEmitter;
+
+static mlir::TypedAttr computePadding(CIRGenModule &cgm, CharUnits size) {
+ mlir::Type eltTy = cgm.UCharTy;
+ clang::CharUnits::QuantityType arSize = size.getQuantity();
+ CIRGenBuilderTy &bld = cgm.getBuilder();
+ if (size > CharUnits::One()) {
+ SmallVector<mlir::Attribute, 4> elts(arSize, cir::ZeroAttr::get(eltTy));
+ return bld.getConstArray(mlir::ArrayAttr::get(bld.getContext(), elts),
+ cir::ArrayType::get(eltTy, arSize));
+ }
+
+ return cir::ZeroAttr::get(eltTy);
+}
+
+static mlir::Attribute
+emitArrayConstant(CIRGenModule &cgm, mlir::Type desiredType,
+ mlir::Type commonElementType, unsigned arrayBound,
+ SmallVectorImpl<mlir::TypedAttr> &elements,
+ mlir::TypedAttr filler);
+
+struct ConstantAggregateBuilderUtils {
+ CIRGenModule &cgm;
+ cir::CIRDataLayout dataLayout;
+
+ ConstantAggregateBuilderUtils(CIRGenModule &cgm)
+ : cgm(cgm), dataLayout{cgm.getModule()} {}
+
+ CharUnits getAlignment(const mlir::TypedAttr c) const {
+ return CharUnits::fromQuantity(
+ dataLayout.getAlignment(c.getType(), /*abiOrPref=*/true));
+ }
+
+ CharUnits getSize(mlir::Type ty) const {
+ return CharUnits::fromQuantity(dataLayout.getTypeAllocSize(ty));
+ }
+
+ CharUnits getSize(const mlir::TypedAttr c) const {
+ return getSize(c.getType());
+ }
+
+ mlir::TypedAttr getPadding(CharUnits size) const {
+ return computePadding(cgm, size);
+ }
+};
+
+/// Incremental builder for an mlir::TypedAttr holding a record or array
+/// constant.
+class ConstantAggregateBuilder : private ConstantAggregateBuilderUtils {
+ /// The elements of the constant. These two arrays must have the same size;
+ /// Offsets[i] describes the offset of Elems[i] within the constant. The
+ /// elements are kept in increasing offset order, and we ensure that there
+ /// is no overlap: Offsets[i+1] >= Offsets[i] + getSize(Elemes[i]).
+ ///
+ /// This may contain explicit padding elements (in order to create a
+ /// natural layout), but need not. Gaps between elements are implicitly
+ /// considered to be filled with undef.
+ llvm::SmallVector<mlir::TypedAttr, 32> elems;
+ llvm::SmallVector<CharUnits, 32> offsets;
+
+ /// The size of the constant (the maximum end offset of any added element).
+ /// May be larger than the end of Elems.back() if we split the last element
+ /// and removed some trailing undefs.
+ CharUnits size = CharUnits::Zero();
+
+ /// This is true only if laying out Elems in order as the elements of a
+ /// non-packed LLVM struct will give the correct layout.
+ bool naturalLayout = true;
+
+ std::optional<size_t> splitAt(CharUnits pos);
+
+ static mlir::Attribute
+ buildFrom(CIRGenModule &cgm, ArrayRef<mlir::TypedAttr> elems,
+ ArrayRef<CharUnits> offsets, CharUnits startOffset, CharUnits size,
+ bool naturalLayout, mlir::Type desiredTy, bool allowOversized);
+
+public:
+ ConstantAggregateBuilder(CIRGenModule &cgm)
+ : ConstantAggregateBuilderUtils(cgm) {}
+
+ /// Update or overwrite the value starting at \p offset with \c c.
+ ///
+ /// \param allowOverwrite If \c true, this constant might overwrite (part of)
+ /// a constant that has already been added. This flag is only used to
+ /// detect bugs.
+ bool add(mlir::TypedAttr typedAttr, CharUnits offset, bool allowOverwrite);
+
+ /// Update or overwrite the bits starting at \p offsetInBits with \p bits.
+ bool addBits(llvm::APInt bits, uint64_t offsetInBits, bool allowOverwrite);
+
+ /// Produce a constant representing the entire accumulated value, ideally of
+ /// the specified type. If \p allowOversized, the constant might be larger
+ /// than implied by \p desiredTy (eg, if there is a flexible array member).
+ /// Otherwise, the constant will be of exactly the same size as \p desiredTy
+ /// even if we can't represent it as that type.
+ mlir::Attribute build(mlir::Type desiredTy, bool allowOversized) const {
+ return buildFrom(cgm, elems, offsets, CharUnits::Zero(), size,
+ naturalLayout, desiredTy, allowOversized);
+ }
+};
+
+template <typename Container, typename Range = std::initializer_list<
+ typename Container::value_type>>
+static void replace(Container &c, size_t beginOff, size_t endOff, Range vals) {
+ assert(beginOff <= endOff && "invalid replacement range");
+ llvm::replace(c, c.begin() + beginOff, c.begin() + endOff, vals);
+}
+
+bool ConstantAggregateBuilder::add(mlir::TypedAttr typedAttr, CharUnits offset,
+ bool allowOverwrite) {
+ // Common case: appending to a layout.
+ if (offset >= size) {
+ CharUnits align = getAlignment(typedAttr);
+ CharUnits alignedSize = size.alignTo(align);
+ if (alignedSize > offset || offset.alignTo(align) != offset)
+ naturalLayout = false;
+ else if (alignedSize < offset) {
+ elems.push_back(getPadding(offset - size));
+ offsets.push_back(size);
+ }
+ elems.push_back(typedAttr);
+ offsets.push_back(offset);
+ size = offset + getSize(typedAttr);
+ return true;
+ }
+
+ // Uncommon case: constant overlaps what we've already created.
+ std::optional<size_t> firstElemToReplace = splitAt(offset);
+ if (!firstElemToReplace)
+ return false;
+
+ CharUnits cSize = getSize(typedAttr);
+ std::optional<size_t> lastElemToReplace = splitAt(offset + cSize);
+ if (!lastElemToReplace)
+ return false;
+
+ assert((firstElemToReplace == lastElemToReplace || allowOverwrite) &&
+ "unexpectedly overwriting field");
+
+ replace(elems, *firstElemToReplace, *lastElemToReplace, {typedAttr});
+ replace(offsets, *firstElemToReplace, *lastElemToReplace, {offset});
+ size = std::max(size, offset + cSize);
+ naturalLayout = false;
+ return true;
+}
+
+/// Returns a position within Elems and Offsets such that all elements
+/// before the returned index end before Pos and all elements at or after
+/// the returned index begin at or after Pos. Splits elements as necessary
+/// to ensure this. Returns None if we find something we can't split.
+std::optional<size_t> ConstantAggregateBuilder::splitAt(CharUnits pos) {
+ if (pos >= size)
+ return offsets.size();
+
+ clang::CharUnits *firstAfterPos = llvm::upper_bound(offsets, pos);
+ if (firstAfterPos == offsets.begin())
+ return 0;
+
+ // If we already have an element starting at Pos, we're done.
+ size_t lastAtOrBeforePosIndex = firstAfterPos - offsets.begin() - 1;
+ if (offsets[lastAtOrBeforePosIndex] == pos)
+ return lastAtOrBeforePosIndex;
+
+ // We found an element starting before Pos. Check for overlap.
+ mlir::TypedAttr c =
+ mlir::cast<mlir::TypedAttr>(elems[lastAtOrBeforePosIndex]);
+ if (offsets[lastAtOrBeforePosIndex] + getSize(c) <= pos)
+ return lastAtOrBeforePosIndex + 1;
+
+ // Try to decompose it into smaller constants.
+ cgm.errorNYI("split into smaller constants");
+ return std::nullopt;
+}
+
+mlir::Attribute ConstantAggregateBuilder::buildFrom(
+ CIRGenModule &cgm, ArrayRef<mlir::TypedAttr> elems,
+ ArrayRef<CharUnits> offsets, CharUnits startOffset, CharUnits size,
+ bool naturalLayout, mlir::Type desiredTy, bool allowOversized) {
+ ConstantAggregateBuilderUtils utils(cgm);
+
+ if (elems.empty())
+ return cir::UndefAttr::get(desiredTy);
+
+ auto offset = [&](size_t i) { return offsets[i] - startOffset; };
+
+ // If we want an array type, see if all the elements are the same type and
+ // appropriately spaced.
+ if (mlir::isa<cir::ArrayType>(desiredTy)) {
+ cgm.errorNYI("array aggregate constants");
+ return {};
+ }
+
+ // The size of the constant we plan to generate. This is usually just the size
+ // of the initialized type, but in AllowOversized mode (i.e. flexible array
+ // init), it can be larger.
+ CharUnits desiredSize = utils.getSize(desiredTy);
+ if (size > desiredSize) {
+ assert(allowOversized && "Elems are oversized");
+ desiredSize = size;
+ }
+
+ // The natural alignment of an unpacked CIR record with the given elements.
+ CharUnits align = CharUnits::One();
+ for (mlir::TypedAttr e : elems) {
+ align = std::max(align, utils.getAlignment(e));
+ }
+
+ // The natural size of an unpacked LLVM struct with the given elements.
+ CharUnits alignedSize = size.alignTo(align);
+
+ bool packed = false;
+ bool padded = false;
+ ArrayRef<mlir::TypedAttr> unpackedElems = elems;
+
+ llvm::SmallVector<mlir::TypedAttr, 32> unpackedElemStorage;
+ if (desiredSize < alignedSize || desiredSize.alignTo(align) != desiredSize) {
+ naturalLayout = false;
+ packed = true;
+ } else if (desiredSize > alignedSize) {
+ // The natural layout would be too small. Add padding to fix it. (This
+ // is ignored if we choose a packed layout.)
+ unpackedElemStorage.assign(unpackedElems.begin(), unpackedElems.end());
+ unpackedElemStorage.push_back(utils.getPadding(desiredSize - size));
+ unpackedElems = unpackedElemStorage;
+ }
+
+ // If we don't have a natural layout, insert padding as necessary.
+ // As we go, double-check to see if we can actually just emit Elems
+ // as a non-packed record and do so opportunistically if possible.
+ llvm::SmallVector<mlir::TypedAttr, 32> packedElems;
+ if (!naturalLayout) {
+ CharUnits sizeSoFar = CharUnits::Zero();
+ for (auto [index, element] : llvm::enumerate(elems)) {
+ CharUnits align = utils.getAlignment(element);
+ CharUnits naturalOffset = sizeSoFar.alignTo(align);
+ CharUnits desiredOffset = offset(index);
+ assert(desiredOffset >= sizeSoFar && "elements out of order");
+
+ if (desiredOffset != naturalOffset)
+ packed = true;
+ if (desiredOffset != sizeSoFar)
+ packedElems.push_back(utils.getPadding(desiredOffset - sizeSoFar));
+ packedElems.push_back(element);
+ sizeSoFar = desiredOffset + utils.getSize(element);
+ }
+ // If we're using the packed layout, pad it out to the desired size if
+ // necessary.
+ if (packed) {
+ assert(sizeSoFar <= desiredSize &&
+ "requested size is too small for contents");
+
+ if (sizeSoFar < desiredSize)
+ packedElems.push_back(utils.getPadding(desiredSize - sizeSoFar));
+ }
+ }
+
+ CIRGenBuilderTy &builder = cgm.getBuilder();
+ llvm::SmallVector<mlir::Attribute, 32> arrayElements;
+ arrayElements.reserve(elems.size());
+ if (packed)
+ llvm::copy(packedElems, std::back_inserter(arrayElements));
+ else
+ llvm::copy(unpackedElems, std::back_inserter(arrayElements));
+ auto arrAttr = mlir::ArrayAttr::get(builder.getContext(), arrayElements);
+
+ cir::RecordType strType = builder.getCompleteRecordType(arrAttr, packed);
+ if (auto desired = mlir::dyn_cast<cir::RecordType>(desiredTy))
+ if (desired.isLayoutIdentical(strType))
+ strType = desired;
+
+ return builder.getConstRecordOrZeroAttr(arrAttr, packed, padded, strType);
+}
+
+//===----------------------------------------------------------------------===//
+// ConstRecordBuilder
+//===----------------------------------------------------------------------===//
+
+class ConstRecordBuilder {
+ CIRGenModule &cgm;
+ ConstantEmitter &emitter;
+ ConstantAggregateBuilder &builder;
+ CharUnits startOffset;
+
+public:
+ static mlir::Attribute buildRecord(ConstantEmitter &emitter,
+ InitListExpr *ile, QualType valTy);
+ static mlir::Attribute buildRecord(ConstantEmitter &emitter,
+ const APValue &value, QualType valTy);
+ static bool updateRecord(ConstantEmitter &emitter,
+ ConstantAggregateBuilder &constant, CharUnits offset,
+ InitListExpr *updater);
+
+private:
+ ConstRecordBuilder(ConstantEmitter &emitter,
+ ConstantAggregateBuilder &builder, CharUnits startOffset)
+ : cgm(emitter.cgm), emitter(emitter), builder(builder),
+ startOffset(startOffset) {}
+
+ bool appendField(const FieldDecl *field, uint64_t fieldOffset,
+ mlir::TypedAttr initCst, bool allowOverwrite = false);
+
+ bool appendBytes(CharUnits fieldOffsetInChars, mlir::TypedAttr initCst,
+ bool allowOverwrite = false);
+
+ bool build(InitListExpr *ile, bool allowOverwrite);
+ bool build(const APValue &val, const RecordDecl *rd, bool isPrimaryBase,
+ const CXXRecordDecl *vTableClass, CharUnits baseOffset);
+
+ mlir::Attribute finalize(QualType ty);
+};
+
+bool ConstRecordBuilder::appendField(const FieldDecl *field,
+ uint64_t fieldOffset,
+ mlir::TypedAttr initCst,
+ bool allowOverwrite) {
+ const ASTContext &astContext = cgm.getASTContext();
+
+ CharUnits fieldOffsetInChars = astContext.toCharUnitsFromBits(fieldOffset);
+
+ return appendBytes(fieldOffsetInChars, initCst, allowOverwrite);
+}
+
+bool ConstRecordBuilder::appendBytes(CharUnits fieldOffsetInChars,
+ mlir::TypedAttr initCst,
+ bool allowOverwrite) {
+ return builder.add(initCst, startOffset + fieldOffsetInChars, allowOverwrite);
+}
+
+bool ConstRecordBuilder::build(InitListExpr *ile, bool allowOverwrite) {
+ RecordDecl *rd = ile->getType()
+ ->castAs<clang::RecordType>()
+ ->getOriginalDecl()
+ ->getDefinitionOrSelf();
+ const ASTRecordLayout &layout = cgm.getASTContext().getASTRecordLayout(rd);
+
+ // Bail out if we have base classes. We could support these, but they only
+ // arise in C++1z where we will have already constant folded most interesting
+ // cases. FIXME: There are still a few more cases we can handle this way.
+ if (auto *cxxrd = dyn_cast<CXXRecordDecl>(rd))
+ if (cxxrd->getNumBases())
+ return false;
+
+ if (cgm.shouldZeroInitPadding()) {
+ assert(!cir::MissingFeatures::recordZeroInitPadding());
+ cgm.errorNYI(rd->getSourceRange(), "zero init padding");
+ return false;
+ }
+
+ unsigned elementNo = 0;
+ for (auto [index, field] : llvm::enumerate(rd->fields())) {
+
+ // If this is a union, skip all the fields that aren't being initialized.
+ if (rd->isUnion() &&
+ !declaresSameEntity(ile->getInitializedFieldInUnion(), field))
+ continue;
+
+ // Don't emit anonymous bitfields.
+ if (field->isUnnamedBitField())
+ continue;
+
+ // Get the initializer. A record can include fields without initializers,
+ // we just use explicit null values for them.
+ Expr *init = nullptr;
+ if (elementNo < ile->getNumInits())
+ init = ile->getInit(elementNo++);
+ if (isa_and_nonnull<NoInitExpr>(init))
+ continue;
+
+ // Zero-sized fields are not emitted, but their initializers may still
+ // prevent emission of this record as a constant.
+ if (field->isZeroSize(cgm.getASTContext())) {
+ if (init->HasSideEffects(cgm.getASTContext()))
+ return false;
+ continue;
+ }
+
+ assert(!cir::MissingFeatures::recordZeroInitPadding());
+
+ // When emitting a DesignatedInitUpdateExpr, a nested InitListExpr
+ // represents additional overwriting of our current constant value, and not
+ // a new constant to emit independently.
+ if (allowOverwrite &&
+ (field->getType()->isArrayType() || field->getType()->isRecordType())) {
+ cgm.errorNYI(field->getSourceRange(), "designated init lists");
+ return false;
+ }
+
+ mlir::TypedAttr eltInit;
+ if (init)
+ eltInit = mlir::cast<mlir::TypedAttr>(
+ emitter.tryEmitPrivateForMemory(init, field->getType()));
+ else
+ eltInit = mlir::cast<mlir::TypedAttr>(emitter.emitNullForMemory(
+ cgm.getLoc(ile->getSourceRange()), field->getType()));
+
+ if (!eltInit)
+ return false;
+
+ if (!field->isBitField()) {
+ // Handle non-bitfield members.
+ if (!appendField(field, layout.getFieldOffset(index), eltInit,
+ allowOverwrite))
+ return false;
+ // After emitting a non-empty field with [[no_unique_address]], we may
+ // need to overwrite its tail padding.
+ if (field->hasAttr<NoUniqueAddressAttr>())
+ allowOverwrite = true;
+ } else {
+ // Otherwise we have a bitfield.
+ if (auto constInt = dyn_cast<cir::IntAttr>(eltInit)) {
+ assert(!cir::MissingFeatures::bitfields());
+ cgm.errorNYI(field->getSourceRange(), "bitfields");
+ }
+ // We are trying to initialize a bitfield with a non-trivial constant,
+ // this must require run-time code.
+ return false;
+ }
+ }
+
+ assert(!cir::MissingFeatures::recordZeroInitPadding());
+ return true;
+}
+
+namespace {
+struct BaseInfo {
+ BaseInfo(const CXXRecordDecl *decl, CharUnits offset, unsigned index)
+ : decl(decl), offset(offset), index(index) {}
+
+ const CXXRecordDecl *decl;
+ CharUnits offset;
+ unsigned index;
+
+ bool operator<(const BaseInfo &o) const { return offset < o.offset; }
+};
+} // namespace
+
+bool ConstRecordBuilder::build(const APValue &val, const RecordDecl *rd,
+ bool isPrimaryBase,
+ const CXXRecordDecl *vTableClass,
+ CharUnits offset) {
+ const ASTRecordLayout &layout = cgm.getASTContext().getASTRecordLayout(rd);
+ if (const CXXRecordDecl *cd = dyn_cast<CXXRecordDecl>(rd)) {
+ assert(!cir::MissingFeatures::vtableInitialization());
+
+ // Accumulate and sort bases, in order to visit them in address order, which
+ // may not be the same as declaration order.
+ SmallVector<BaseInfo, 8> bases;
+ bases.reserve(cd->getNumBases());
+ for (auto [index, base] : llvm::enumerate(cd->bases())) {
+ assert(!base.isVirtual() && "should not have virtual bases here");
+ const CXXRecordDecl *bd = base.getType()->getAsCXXRecordDecl();
+ CharUnits baseOffset = layout.getBaseClassOffset(bd);
+ bases.push_back(BaseInfo(bd, baseOffset, index));
+ }
+ llvm::stable_sort(bases);
+
+ for (BaseInfo &base : bases) {
+ bool isPrimaryBase = layout.getPrimaryBase() == base.decl;
+ build(val.getStructBase(base.index), base.decl, isPrimaryBase,
+ vTableClass, offset + base.offset);
+ }
+ }
+
+ uint64_t offsetBits = cgm.getASTContext().toBits(offset);
+
+ bool allowOverwrite = false;
+ for (auto [index, field] : llvm::enumerate(rd->fields())) {
+ // If this is a union, skip all the fields that aren't being initialized.
+ if (rd->isUnion() && !declaresSameEntity(val.getUnionField(), field))
+ continue;
+
+ // Don't emit anonymous bitfields or zero-sized fields.
+ if (field->isUnnamedBitField() || field->isZeroSize(cgm.getASTContext()))
+ continue;
+
+ // Emit the value of the initializer.
+ const APValue &fieldValue =
+ rd->isUnion() ? val.getUnionValue() : val.getStructField(index);
+ mlir::TypedAttr eltInit = mlir::cast<mlir::TypedAttr>(
+ emitter.tryEmitPrivateForMemory(fieldValue, field->getType()));
+ if (!eltInit)
+ return false;
+
+ if (!field->isBitField()) {
+ // Handle non-bitfield members.
+ if (!appendField(field, layout.getFieldOffset(index) + offsetBits,
+ eltInit, allowOverwrite))
+ return false;
+ // After emitting a non-empty field with [[no_unique_address]], we may
+ // need to overwrite its tail padding.
+ if (field->hasAttr<NoUniqueAddressAttr>())
+ allowOverwrite = true;
+ } else {
+ assert(!cir::MissingFeatures::bitfields());
+ cgm.errorNYI(field->getSourceRange(), "bitfields");
+ }
+ }
+
+ return true;
+}
+
+mlir::Attribute ConstRecordBuilder::finalize(QualType type) {
+ type = type.getNonReferenceType();
+ RecordDecl *rd = type->castAs<clang::RecordType>()
+ ->getOriginalDecl()
+ ->getDefinitionOrSelf();
+ mlir::Type valTy = cgm.convertType(type);
+ return builder.build(valTy, rd->hasFlexibleArrayMember());
+}
+
+mlir::Attribute ConstRecordBuilder::buildRecord(ConstantEmitter &emitter,
+ InitListExpr *ile,
+ QualType valTy) {
+ ConstantAggregateBuilder constant(emitter.cgm);
+ ConstRecordBuilder builder(emitter, constant, CharUnits::Zero());
+
+ if (!builder.build(ile, /*allowOverwrite*/ false))
+ return nullptr;
+
+ return builder.finalize(valTy);
+}
+
+mlir::Attribute ConstRecordBuilder::buildRecord(ConstantEmitter &emitter,
+ const APValue &val,
+ QualType valTy) {
+ ConstantAggregateBuilder constant(emitter.cgm);
+ ConstRecordBuilder builder(emitter, constant, CharUnits::Zero());
+
+ const RecordDecl *rd = valTy->castAs<clang::RecordType>()
+ ->getOriginalDecl()
+ ->getDefinitionOrSelf();
+ const CXXRecordDecl *cd = dyn_cast<CXXRecordDecl>(rd);
+ if (!builder.build(val, rd, false, cd, CharUnits::Zero()))
+ return nullptr;
+
+ return builder.finalize(valTy);
+}
+
+bool ConstRecordBuilder::updateRecord(ConstantEmitter &emitter,
+ ConstantAggregateBuilder &constant,
+ CharUnits offset, InitListExpr *updater) {
+ return ConstRecordBuilder(emitter, constant, offset)
+ .build(updater, /*allowOverwrite*/ true);
+}
+
//===----------------------------------------------------------------------===//
// ConstExprEmitter
//===----------------------------------------------------------------------===//
@@ -59,7 +609,7 @@ class ConstExprEmitter
// Visitor Methods
//===--------------------------------------------------------------------===//
- mlir::Attribute VisitStmt(Stmt *S, QualType T) { return {}; }
+ mlir::Attribute VisitStmt(Stmt *s, QualType t) { return {}; }
mlir::Attribute VisitConstantExpr(ConstantExpr *ce, QualType t) {
if (mlir::Attribute result = emitter.tryEmitConstantExpr(ce))
@@ -204,9 +754,9 @@ class ConstExprEmitter
return Visit(e->getSubExpr(), t);
}
- mlir::Attribute VisitImplicitValueInitExpr(ImplicitValueInitExpr *E,
- QualType T) {
- cgm.errorNYI(E->getBeginLoc(),
+ mlir::Attribute VisitImplicitValueInitExpr(ImplicitValueInitExpr *e,
+ QualType t) {
+ cgm.errorNYI(e->getBeginLoc(),
"ConstExprEmitter::VisitImplicitValueInitExpr");
return {};
}
@@ -223,8 +773,7 @@ class ConstExprEmitter
}
if (ile->getType()->isRecordType()) {
- cgm.errorNYI(ile->getBeginLoc(), "ConstExprEmitter: record ILE");
- return {};
+ return ConstRecordBuilder::buildRecord(emitter, ile, t);
}
if (ile->getType()->isVectorType()) {
@@ -358,9 +907,11 @@ emitArrayConstant(CIRGenModule &cgm, mlir::Type desiredType,
eles.push_back(element);
auto arrAttr = mlir::ArrayAttr::get(builder.getContext(), eles);
- return builder.getAnonConstRecord(arrAttr, /*isPacked=*/true);
+ return builder.getAnonConstRecord(arrAttr, /*packed=*/true);
}
+} // namespace
+
//===----------------------------------------------------------------------===//
// ConstantLValueEmitter
//===----------------------------------------------------------------------===//
@@ -732,6 +1283,17 @@ mlir::Attribute ConstantEmitter::tryEmitConstantExpr(const ConstantExpr *ce) {
return emitAbstract(ce->getBeginLoc(), ce->getAPValueResult(), retType);
}
+mlir::Attribute ConstantEmitter::tryEmitPrivateForMemory(const Expr *e,
+ QualType destType) {
+ QualType nonMemoryDestType = getNonMemoryType(cgm, destType);
+ mlir::TypedAttr c = tryEmitPrivate(e, nonMemoryDestType);
+ if (c) {
+ mlir::Attribute attr = emitForMemory(c, destType);
+ return mlir::cast<mlir::TypedAttr>(attr);
+ }
+ return nullptr;
+}
+
mlir::Attribute ConstantEmitter::tryEmitPrivateForMemory(const APValue &value,
QualType destType) {
QualType nonMemoryDestType = getNonMemoryType(cgm, destType);
@@ -759,6 +1321,15 @@ mlir::Attribute ConstantEmitter::emitAbstract(SourceLocation loc,
return c;
}
+mlir::Attribute ConstantEmitter::emitNullForMemory(mlir::Location loc,
+ CIRGenModule &cgm,
+ QualType t) {
+ cir::ConstantOp cstOp =
+ cgm.emitNullConstant(t, loc).getDefiningOp<cir::ConstantOp>();
+ assert(cstOp && "expected cir.const op");
+ return emitForMemory(cgm, cstOp.getValue(), t);
+}
+
mlir::Attribute ConstantEmitter::emitForMemory(mlir::Attribute c,
QualType destType) {
// For an _Atomic-qualified constant, we may need to add tail padding.
@@ -770,6 +1341,17 @@ mlir::Attribute ConstantEmitter::emitForMemory(mlir::Attribute c,
return c;
}
+mlir::Attribute ConstantEmitter::emitForMemory(CIRGenModule &cgm,
+ mlir::Attribute c,
+ QualType destType) {
+ // For an _Atomic-qualified constant, we may need to add tail padding.
+ if (destType->getAs<AtomicType>()) {
+ cgm.errorNYI("atomic constants");
+ }
+
+ return c;
+}
+
mlir::TypedAttr ConstantEmitter::tryEmitPrivate(const Expr *e,
QualType destType) {
assert(!destType->isVoidType() && "can't emit a void constant");
@@ -901,8 +1483,7 @@ mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &value,
return ConstantLValueEmitter(*this, value, destType).tryEmit();
case APValue::Struct:
case APValue::Union:
- cgm.errorNYI("ConstExprEmitter::tryEmitPrivate struct or union");
- return {};
+ return ConstRecordBuilder::buildRecord(*this, value, destType);
case APValue::ComplexInt:
case APValue::ComplexFloat: {
mlir::Type desiredType = cgm.convertType(destType);
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index c7f548498c5cb..8c0e49bc10a47 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -93,6 +93,9 @@ CIRGenModule::CIRGenModule(mlir::MLIRContext &mlirContext,
astContext.getTargetInfo().getPointerAlign(LangAS::Default))
.getQuantity();
+ const unsigned charSize = astContext.getTargetInfo().getCharWidth();
+ UCharTy = cir::IntType::get(&getMLIRContext(), charSize, /*isSigned=*/false);
+
// TODO(CIR): Should be updated once TypeSizeInfoAttr is upstreamed
const unsigned sizeTypeSize =
astContext.getTypeSize(astContext.getSignedSizeType());
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 4f5c7f898af8c..1e72b8f7e9708 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -153,6 +153,56 @@ class CIRGenModule : public CIRGenTypeCache {
bool isConstant = false,
mlir::Operation *insertPoint = nullptr);
+ bool shouldZeroInitPadding() const {
+ // In C23 (N3096) $6.7.10:
+ // """
+ // If any object is initialized with an empty initializer, then it is
+ // subject to default initialization:
+ // - if it is an aggregate, every member is initialized (recursively)
+ // according to these rules, and any padding is initialized to zero bits;
+ // - if it is a union, the first named member is initialized (recursively)
+ // according to these rules, and any padding is initialized to zero bits.
+ //
+ // If the aggregate or union contains elements or members that are
+ // aggregates or unions, these rules apply recursively to the subaggregates
+ // or contained unions.
+ //
+ // If there are fewer initializers in a brace-enclosed list than there are
+ // elements or members of an aggregate, or fewer characters in a string
+ // literal used to initialize an array of known size than there are elements
+ // in the array, the remainder of the aggregate is subject to default
+ // initialization.
+ // """
+ //
+ // The standard seems ambiguous in the following two areas:
+ // 1. For a union type with empty initializer, if the first named member is
+ // not the largest member, then the bytes comes after the first named member
+ // but before padding are left unspecified. An example is:
+ // union U { int a; long long b;};
+ // union U u = {}; // The first 4 bytes are 0, but 4-8 bytes are left
+ // unspecified.
+ //
+ // 2. It only mentions padding for empty initializer, but doesn't mention
+ // padding for a non empty initialization list. And if the aggregation or
+ // union contains elements or members that are aggregates or unions, and
+ // some are non empty initializers, while others are empty initializers,
+ // the padding initialization is unclear. An example is:
+ // struct S1 { int a; long long b; };
+ // struct S2 { char c; struct S1 s1; };
+ // // The values for paddings between s2.c and s2.s1.a, between s2.s1.a
+ // and s2.s1.b are unclear.
+ // struct S2 s2 = { 'c' };
+ //
+ // Here we choose to zero initiailize left bytes of a union type because
+ // projects like the Linux kernel are relying on this behavior. If we don't
+ // explicitly zero initialize them, the undef values can be optimized to
+ // return garbage data. We also choose to zero initialize paddings for
+ // aggregates and unions, no matter they are initialized by empty
+ // initializers or non empty initializers. This can provide a consistent
+ // behavior. So projects like the Linux kernel can rely on it.
+ return !getLangOpts().CPlusPlus;
+ }
+
llvm::StringMap<unsigned> cgGlobalNames;
std::string getUniqueGlobalName(const std::string &baseName);
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypeCache.h b/clang/lib/CIR/CodeGen/CIRGenTypeCache.h
index eb8dcd67a4a81..cc3ce09be4f95 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypeCache.h
+++ b/clang/lib/CIR/CodeGen/CIRGenTypeCache.h
@@ -49,6 +49,9 @@ struct CIRGenTypeCache {
cir::FP80Type FP80Ty;
cir::FP128Type FP128Ty;
+ /// ClangIR char
+ mlir::Type UCharTy;
+
/// intptr_t, size_t, and ptrdiff_t, which we assume are the same size.
union {
mlir::Type UIntPtrTy;
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 4fecb0108e001..35b4513c5789f 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -283,6 +283,16 @@ Type RecordType::getLargestMember(const ::mlir::DataLayout &dataLayout) const {
});
}
+bool RecordType::isLayoutIdentical(const RecordType &other) {
+ if (getImpl() == other.getImpl())
+ return true;
+
+ if (getPacked() != other.getPacked())
+ return false;
+
+ return getMembers() == other.getMembers();
+}
+
//===----------------------------------------------------------------------===//
// Data Layout information for types
//===----------------------------------------------------------------------===//
diff --git a/clang/test/CIR/CodeGen/constant-inits.cpp b/clang/test/CIR/CodeGen/constant-inits.cpp
new file mode 100644
index 0000000000000..44a7a4506bee9
--- /dev/null
+++ b/clang/test/CIR/CodeGen/constant-inits.cpp
@@ -0,0 +1,55 @@
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
+
+struct empty{};
+
+struct Point {
+ int x;
+ int y;
+ char c[3];
+ int z;
+ [[no_unique_address]] empty e;
+};
+
+void function() {
+ constexpr static empty e;
+
+ constexpr static Point p1{10, 20, {99, 88, 77}, 40, e};
+
+ constexpr static Point array[] {
+ {123, 456, {11, 22, 33}, 789, {}},
+ {10, 20, {0, 0 ,0}, 40}
+ };
+}
+
+// CIR: cir.global "private" internal dso_local @_ZZ8functionvE5array = #cir.const_array<[
+// CIR-SAME: #cir.const_record<{#cir.int<123> : !s32i, #cir.int<456> : !s32i, #cir.const_array<[#cir.int<11> : !s8i, #cir.int<22> : !s8i, #cir.int<33> : !s8i]> : !cir.array<!s8i x 3>, #cir.int<789> : !s32i}> : !rec_Point
+// CIR-SAME: #cir.const_record<{#cir.int<10> : !s32i, #cir.int<20> : !s32i, #cir.zero : !cir.array<!s8i x 3>, #cir.int<40> : !s32i}> : !rec_Point
+// CIR-SAME: ]> : !cir.array<!rec_Point x 2>
+
+// CIR: cir.global "private" internal dso_local @_ZZ8functionvE2p1 = #cir.const_record<{#cir.int<10> : !s32i, #cir.int<20> : !s32i, #cir.const_array<[#cir.int<99> : !s8i, #cir.int<88> : !s8i, #cir.int<77> : !s8i]> : !cir.array<!s8i x 3>, #cir.int<40> : !s32i}> : !rec_Point
+
+// CIR: cir.global "private" internal dso_local @_ZZ8functionvE1e = #cir.zero : !rec_empty
+
+// CIR-LABEL: cir.func dso_local @_Z8functionv()
+// CIR: cir.return
+
+
+// LLVM-DAG: @_ZZ8functionvE5array = internal global [2 x %struct.Point] [%struct.Point { i32 123, i32 456, [3 x i8] c"\0B\16!", i32 789 }, %struct.Point { i32 10, i32 20, [3 x i8] zeroinitializer, i32 40 }]
+// LLVM-DAG: @_ZZ8functionvE2p1 = internal global %struct.Point { i32 10, i32 20, [3 x i8] c"cXM", i32 40 }
+// LLVM-DAG: @_ZZ8functionvE1e = internal global %struct.empty zeroinitializer
+
+// LLVM-LABEL: define{{.*}} void @_Z8functionv
+// LLVM: ret void
+
+
+// OGCG-DAG: @_ZZ8functionvE5array = internal constant [2 x %struct.Point] [%struct.Point { i32 123, i32 456, [3 x i8] c"\0B\16!", i32 789 }, %struct.Point { i32 10, i32 20, [3 x i8] zeroinitializer, i32 40 }]
+// OGCG-DAG: @_ZZ8functionvE2p1 = internal constant %struct.Point { i32 10, i32 20, [3 x i8] c"cXM", i32 40 }
+// OGCG-DAG: @_ZZ8functionvE1e = internal constant %struct.empty zeroinitializer
+
+// OGCG-LABEL: define{{.*}} void @_Z8functionv
+// OGCG: ret void
>From 6b34cff06e532fdd4abe0e0433d8b901665eb4b9 Mon Sep 17 00:00:00 2001
From: Morris Hafner <mhafner at nvidia.com>
Date: Wed, 27 Aug 2025 19:48:05 +0200
Subject: [PATCH 2/6] clang-format
---
clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h b/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h
index 7af62b9845e40..ccded3dacf031 100644
--- a/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h
+++ b/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h
@@ -113,7 +113,8 @@ class ConstantEmitter {
mlir::TypedAttr tryEmitPrivate(const Expr *e, QualType destType);
mlir::Attribute tryEmitPrivate(const APValue &value, QualType destType);
mlir::Attribute tryEmitPrivateForMemory(const Expr *e, QualType destTy);
- mlir::Attribute tryEmitPrivateForMemory(const APValue &value, QualType destTy);
+ mlir::Attribute tryEmitPrivateForMemory(const APValue &value,
+ QualType destTy);
private:
#ifndef NDEBUG
>From 668d73f01770bd82f02d9966c5546cc8d92aebc9 Mon Sep 17 00:00:00 2001
From: Morris Hafner <mhafner at nvidia.com>
Date: Thu, 28 Aug 2025 16:23:57 +0200
Subject: [PATCH 3/6] Address review feedback
---
clang/lib/CIR/CodeGen/CIRGenBuilder.cpp | 24 ++++-----
clang/lib/CIR/CodeGen/CIRGenBuilder.h | 9 ++--
clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 49 +------------------
.../CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp | 6 +--
4 files changed, 19 insertions(+), 69 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
index 999c2fcbfa490..670a431e47f40 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
@@ -8,6 +8,7 @@
#include "CIRGenBuilder.h"
#include "mlir/IR/BuiltinAttributes.h"
+#include "clang/CIR/MissingFeatures.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/TypeSwitch.h"
@@ -132,21 +133,20 @@ void CIRGenBuilderTy::computeGlobalViewIndicesFromFlatOffset(
computeGlobalViewIndicesFromFlatOffset(offset, subType, layout, indices);
}
-static mlir::Type getAttributeType(mlir::Attribute attr) {
- return mlir::cast<mlir::TypedAttr>(attr).getType();
-}
-
cir::RecordType clang::CIRGen::CIRGenBuilderTy::getCompleteRecordType(
- mlir::ArrayAttr fields, bool packed, bool padded, llvm::StringRef name,
- const clang::RecordDecl *ast) {
- llvm::SmallVector<mlir::Type, 8> members;
+ mlir::ArrayAttr fields, bool packed, bool padded, llvm::StringRef name) {
+ assert(!cir::MissingFeatures::astRecordDeclAttr());
+ llvm::SmallVector<mlir::Type> members;
members.reserve(fields.size());
- llvm::transform(fields, std::back_inserter(members), getAttributeType);
+ llvm::transform(fields, std::back_inserter(members),
+ [](mlir::Attribute attr) {
+ return mlir::cast<mlir::TypedAttr>(attr).getType();
+ });
if (name.empty())
return getAnonRecordTy(members, packed, padded);
- return getCompleteRecordTy(members, name, packed, padded);
+ return getCompleteNamedRecordType(members, packed, padded, name);
}
mlir::Attribute clang::CIRGen::CIRGenBuilderTy::getConstRecordOrZeroAttr(
@@ -155,11 +155,7 @@ mlir::Attribute clang::CIRGen::CIRGenBuilderTy::getConstRecordOrZeroAttr(
// Record type not specified: create anon record type from members.
if (!recordTy) {
- llvm::SmallVector<mlir::Type, 8> members;
- members.reserve(arrayAttr.size());
- llvm::transform(arrayAttr, std::back_inserter(members), getAttributeType);
- recordTy = getType<cir::RecordType>(members, packed, padded,
- cir::RecordType::Struct);
+ recordTy = getCompleteRecordType(arrayAttr, packed, padded);
}
// Return zero or anonymous constant record.
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index 22c0f7d34e4a9..7994bcb693f1b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -138,9 +138,9 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
///
/// If a record already exists and is complete, but the client tries to fetch
/// it with a different set of attributes, this method will crash.
- cir::RecordType getCompleteRecordTy(llvm::ArrayRef<mlir::Type> members,
- llvm::StringRef name, bool packed,
- bool padded) {
+ cir::RecordType getCompleteNamedRecordType(llvm::ArrayRef<mlir::Type> members,
+ bool packed, bool padded,
+ llvm::StringRef name) {
const auto nameAttr = getStringAttr(name);
auto kind = cir::RecordType::RecordKind::Struct;
assert(!cir::MissingFeatures::astRecordDeclAttr());
@@ -165,8 +165,7 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
cir::RecordType getCompleteRecordType(mlir::ArrayAttr fields,
bool packed = false,
bool padded = false,
- llvm::StringRef name = "",
- const clang::RecordDecl *ast = nullptr);
+ llvm::StringRef name = "");
/// Get an incomplete CIR struct type. If we have a complete record
/// declaration, we may create an incomplete type and then add the
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index 81d575f9a4e28..2fd418eda01a5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -109,8 +109,6 @@ class ConstantAggregateBuilder : private ConstantAggregateBuilderUtils {
/// non-packed LLVM struct will give the correct layout.
bool naturalLayout = true;
- std::optional<size_t> splitAt(CharUnits pos);
-
static mlir::Attribute
buildFrom(CIRGenModule &cgm, ArrayRef<mlir::TypedAttr> elems,
ArrayRef<CharUnits> offsets, CharUnits startOffset, CharUnits size,
@@ -167,51 +165,8 @@ bool ConstantAggregateBuilder::add(mlir::TypedAttr typedAttr, CharUnits offset,
}
// Uncommon case: constant overlaps what we've already created.
- std::optional<size_t> firstElemToReplace = splitAt(offset);
- if (!firstElemToReplace)
- return false;
-
- CharUnits cSize = getSize(typedAttr);
- std::optional<size_t> lastElemToReplace = splitAt(offset + cSize);
- if (!lastElemToReplace)
- return false;
-
- assert((firstElemToReplace == lastElemToReplace || allowOverwrite) &&
- "unexpectedly overwriting field");
-
- replace(elems, *firstElemToReplace, *lastElemToReplace, {typedAttr});
- replace(offsets, *firstElemToReplace, *lastElemToReplace, {offset});
- size = std::max(size, offset + cSize);
- naturalLayout = false;
- return true;
-}
-
-/// Returns a position within Elems and Offsets such that all elements
-/// before the returned index end before Pos and all elements at or after
-/// the returned index begin at or after Pos. Splits elements as necessary
-/// to ensure this. Returns None if we find something we can't split.
-std::optional<size_t> ConstantAggregateBuilder::splitAt(CharUnits pos) {
- if (pos >= size)
- return offsets.size();
-
- clang::CharUnits *firstAfterPos = llvm::upper_bound(offsets, pos);
- if (firstAfterPos == offsets.begin())
- return 0;
-
- // If we already have an element starting at Pos, we're done.
- size_t lastAtOrBeforePosIndex = firstAfterPos - offsets.begin() - 1;
- if (offsets[lastAtOrBeforePosIndex] == pos)
- return lastAtOrBeforePosIndex;
-
- // We found an element starting before Pos. Check for overlap.
- mlir::TypedAttr c =
- mlir::cast<mlir::TypedAttr>(elems[lastAtOrBeforePosIndex]);
- if (offsets[lastAtOrBeforePosIndex] + getSize(c) <= pos)
- return lastAtOrBeforePosIndex + 1;
-
- // Try to decompose it into smaller constants.
- cgm.errorNYI("split into smaller constants");
- return std::nullopt;
+ cgm.errorNYI("overlapping constants");
+ return false;
}
mlir::Attribute ConstantAggregateBuilder::buildFrom(
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
index 6c7cf75aa2c99..bb1ac753d85af 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -627,9 +627,9 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
CIRRecordLowering baseLowering(*this, rd, /*Packed=*/lowering.packed);
baseLowering.lower(/*NonVirtualBaseType=*/true);
std::string baseIdentifier = getRecordTypeName(rd, ".base");
- baseTy =
- builder.getCompleteRecordTy(baseLowering.fieldTypes, baseIdentifier,
- baseLowering.packed, baseLowering.padded);
+ baseTy = builder.getCompleteNamedRecordType(
+ baseLowering.fieldTypes, baseLowering.packed, baseLowering.padded,
+ baseIdentifier);
// TODO(cir): add something like addRecordTypeName
// BaseTy and Ty must agree on their packedness for getCIRFieldNo to work
>From 2d5af9e360aba121c51a8153f81ad39de441758c Mon Sep 17 00:00:00 2001
From: Morris Hafner <mhafner at nvidia.com>
Date: Thu, 28 Aug 2025 16:59:07 +0200
Subject: [PATCH 4/6] Add more tests
---
clang/test/CIR/CodeGen/constant-inits.cpp | 60 +++++++++++++++++++----
1 file changed, 50 insertions(+), 10 deletions(-)
diff --git a/clang/test/CIR/CodeGen/constant-inits.cpp b/clang/test/CIR/CodeGen/constant-inits.cpp
index 44a7a4506bee9..7e02b8ccc5872 100644
--- a/clang/test/CIR/CodeGen/constant-inits.cpp
+++ b/clang/test/CIR/CodeGen/constant-inits.cpp
@@ -15,6 +15,17 @@ struct Point {
[[no_unique_address]] empty e;
};
+struct [[gnu::packed]] packed {
+ char c;
+ int i;
+};
+
+struct [[gnu::packed]] alignas(8) packed_and_aligned {
+ short s;
+ char c;
+ float f;
+};
+
void function() {
constexpr static empty e;
@@ -24,32 +35,61 @@ void function() {
{123, 456, {11, 22, 33}, 789, {}},
{10, 20, {0, 0 ,0}, 40}
};
+
+ constexpr static packed p2 {123, 456};
+ constexpr static packed packed_array[] {
+ p2, p2
+ };
+
+ constexpr static packed_and_aligned paa {1, 2, 3.f};
+ constexpr static packed_and_aligned paa_array[2] {
+ {1, 2, 3.f}
+ };
}
-// CIR: cir.global "private" internal dso_local @_ZZ8functionvE5array = #cir.const_array<[
-// CIR-SAME: #cir.const_record<{#cir.int<123> : !s32i, #cir.int<456> : !s32i, #cir.const_array<[#cir.int<11> : !s8i, #cir.int<22> : !s8i, #cir.int<33> : !s8i]> : !cir.array<!s8i x 3>, #cir.int<789> : !s32i}> : !rec_Point
-// CIR-SAME: #cir.const_record<{#cir.int<10> : !s32i, #cir.int<20> : !s32i, #cir.zero : !cir.array<!s8i x 3>, #cir.int<40> : !s32i}> : !rec_Point
-// CIR-SAME: ]> : !cir.array<!rec_Point x 2>
+// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE1e = #cir.zero : !rec_empty
+// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE2p1 = #cir.const_record<{#cir.int<10> : !s32i, #cir.int<20> : !s32i, #cir.const_array<[#cir.int<99> : !s8i, #cir.int<88> : !s8i, #cir.int<77> : !s8i]> : !cir.array<!s8i x 3>, #cir.int<40> : !s32i}> : !rec_Point
+// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE2p2 = #cir.const_record<{#cir.int<123> : !s8i, #cir.int<456> : !s32i}> : !rec_packed
+// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE3paa = #cir.const_record<{#cir.int<1> : !s16i, #cir.int<2> : !s8i, #cir.fp<3.000000e+00> : !cir.float, #cir.zero : !u8i}> : !rec_packed_and_aligned
+
+// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE5array = #cir.const_array<[
+// CIR-DAG-SAME: #cir.const_record<{#cir.int<123> : !s32i, #cir.int<456> : !s32i, #cir.const_array<[#cir.int<11> : !s8i, #cir.int<22> : !s8i, #cir.int<33> : !s8i]> : !cir.array<!s8i x 3>, #cir.int<789> : !s32i}> : !rec_Point
+// CIR-DAG-SAME: #cir.const_record<{#cir.int<10> : !s32i, #cir.int<20> : !s32i, #cir.zero : !cir.array<!s8i x 3>, #cir.int<40> : !s32i}> : !rec_Point
+// CIR-DAG-SAME: ]> : !cir.array<!rec_Point x 2>
-// CIR: cir.global "private" internal dso_local @_ZZ8functionvE2p1 = #cir.const_record<{#cir.int<10> : !s32i, #cir.int<20> : !s32i, #cir.const_array<[#cir.int<99> : !s8i, #cir.int<88> : !s8i, #cir.int<77> : !s8i]> : !cir.array<!s8i x 3>, #cir.int<40> : !s32i}> : !rec_Point
+// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE12packed_array = #cir.const_array<[
+// CIR-DAG-SAME: #cir.const_record<{#cir.int<123> : !s8i, #cir.int<456> : !s32i}> : !rec_packed,
+// CIR-DAG-SAME: #cir.const_record<{#cir.int<123> : !s8i, #cir.int<456> : !s32i}> : !rec_packed
+// CIR-DAG-SAME: ]> : !cir.array<!rec_packed x 2>
-// CIR: cir.global "private" internal dso_local @_ZZ8functionvE1e = #cir.zero : !rec_empty
+// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE9paa_array = #cir.const_array<[
+// CIR-DAG-SAME: #cir.const_record<{#cir.int<1> : !s16i, #cir.int<2> : !s8i, #cir.fp<3.000000e+00> : !cir.float, #cir.zero : !u8i}> : !rec_packed_and_aligned,
+// CIR-DAG-SAME: #cir.zero : !rec_packed_and_aligned
+// CIR-DAG-SAME: ]> : !cir.array<!rec_packed_and_aligned x 2>
// CIR-LABEL: cir.func dso_local @_Z8functionv()
// CIR: cir.return
-// LLVM-DAG: @_ZZ8functionvE5array = internal global [2 x %struct.Point] [%struct.Point { i32 123, i32 456, [3 x i8] c"\0B\16!", i32 789 }, %struct.Point { i32 10, i32 20, [3 x i8] zeroinitializer, i32 40 }]
-// LLVM-DAG: @_ZZ8functionvE2p1 = internal global %struct.Point { i32 10, i32 20, [3 x i8] c"cXM", i32 40 }
+// LLVM-DAG: @_ZZ8functionvE12packed_array = internal global [2 x %struct.packed] [%struct.packed <{ i8 123, i32 456 }>, %struct.packed <{ i8 123, i32 456 }>]
// LLVM-DAG: @_ZZ8functionvE1e = internal global %struct.empty zeroinitializer
+// LLVM-DAG: @_ZZ8functionvE2p1 = internal global %struct.Point { i32 10, i32 20, [3 x i8] c"cXM", i32 40 }
+// LLVM-DAG: @_ZZ8functionvE2p2 = internal global %struct.packed <{ i8 123, i32 456 }>
+// LLVM-DAG: @_ZZ8functionvE3paa = internal global %struct.packed_and_aligned <{ i16 1, i8 2, float 3.000000e+00, i8 0 }>
+// LLVM-DAG: @_ZZ8functionvE5array = internal global [2 x %struct.Point] [%struct.Point { i32 123, i32 456, [3 x i8] c"\0B\16!", i32 789 }, %struct.Point { i32 10, i32 20, [3 x i8] zeroinitializer, i32 40 }]
+// LLVM-DAG: @_ZZ8functionvE9paa_array = internal global [2 x %struct.packed_and_aligned] [%struct.packed_and_aligned <{ i16 1, i8 2, float 3.000000e+00, i8 0 }>, %struct.packed_and_aligned zeroinitializer]
// LLVM-LABEL: define{{.*}} void @_Z8functionv
// LLVM: ret void
-// OGCG-DAG: @_ZZ8functionvE5array = internal constant [2 x %struct.Point] [%struct.Point { i32 123, i32 456, [3 x i8] c"\0B\16!", i32 789 }, %struct.Point { i32 10, i32 20, [3 x i8] zeroinitializer, i32 40 }]
-// OGCG-DAG: @_ZZ8functionvE2p1 = internal constant %struct.Point { i32 10, i32 20, [3 x i8] c"cXM", i32 40 }
+// OGCG-DAG: @_ZZ8functionvE12packed_array = internal constant [2 x %struct.packed] [%struct.packed <{ i8 123, i32 456 }>, %struct.packed <{ i8 123, i32 456 }>]
// OGCG-DAG: @_ZZ8functionvE1e = internal constant %struct.empty zeroinitializer
+// OGCG-DAG: @_ZZ8functionvE2p1 = internal constant %struct.Point { i32 10, i32 20, [3 x i8] c"cXM", i32 40 }
+// OGCG-DAG: @_ZZ8functionvE2p2 = internal constant %struct.packed <{ i8 123, i32 456 }>
+// OGCG-DAG: @_ZZ8functionvE3paa = internal constant %struct.packed_and_aligned <{ i16 1, i8 2, float 3.000000e+00, i8 undef }>
+// OGCG-DAG: @_ZZ8functionvE5array = internal constant [2 x %struct.Point] [%struct.Point { i32 123, i32 456, [3 x i8] c"\0B\16!", i32 789 }, %struct.Point { i32 10, i32 20, [3 x i8] zeroinitializer, i32 40 }]
+// OGCG-DAG: @_ZZ8functionvE9paa_array = internal constant [2 x %struct.packed_and_aligned] [%struct.packed_and_aligned <{ i16 1, i8 2, float 3.000000e+00, i8 undef }>, %struct.packed_and_aligned <{ i16 0, i8 0, float 0.000000e+00, i8 undef }>]
// OGCG-LABEL: define{{.*}} void @_Z8functionv
// OGCG: ret void
>From 52e2f6e619c23f5226d498441d2a9243b9b22ffc Mon Sep 17 00:00:00 2001
From: Morris Hafner <mhafner at nvidia.com>
Date: Thu, 28 Aug 2025 17:30:16 +0200
Subject: [PATCH 5/6] Add simple test case
---
clang/test/CIR/CodeGen/constant-inits.cpp | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/clang/test/CIR/CodeGen/constant-inits.cpp b/clang/test/CIR/CodeGen/constant-inits.cpp
index 7e02b8ccc5872..c9153c91ebc22 100644
--- a/clang/test/CIR/CodeGen/constant-inits.cpp
+++ b/clang/test/CIR/CodeGen/constant-inits.cpp
@@ -26,6 +26,10 @@ struct [[gnu::packed]] alignas(8) packed_and_aligned {
float f;
};
+struct simple {
+ int a, b;
+};
+
void function() {
constexpr static empty e;
@@ -45,9 +49,15 @@ void function() {
constexpr static packed_and_aligned paa_array[2] {
{1, 2, 3.f}
};
+
+ constexpr static simple s{0, -1};
+ constexpr static simple simple_array[] {
+ s, {1111, 2222}, s
+ };
}
// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE1e = #cir.zero : !rec_empty
+// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE1s = #cir.const_record<{#cir.int<0> : !s32i, #cir.int<-1> : !s32i}> : !rec_simple
// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE2p1 = #cir.const_record<{#cir.int<10> : !s32i, #cir.int<20> : !s32i, #cir.const_array<[#cir.int<99> : !s8i, #cir.int<88> : !s8i, #cir.int<77> : !s8i]> : !cir.array<!s8i x 3>, #cir.int<40> : !s32i}> : !rec_Point
// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE2p2 = #cir.const_record<{#cir.int<123> : !s8i, #cir.int<456> : !s32i}> : !rec_packed
// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE3paa = #cir.const_record<{#cir.int<1> : !s16i, #cir.int<2> : !s8i, #cir.fp<3.000000e+00> : !cir.float, #cir.zero : !u8i}> : !rec_packed_and_aligned
@@ -57,6 +67,12 @@ void function() {
// CIR-DAG-SAME: #cir.const_record<{#cir.int<10> : !s32i, #cir.int<20> : !s32i, #cir.zero : !cir.array<!s8i x 3>, #cir.int<40> : !s32i}> : !rec_Point
// CIR-DAG-SAME: ]> : !cir.array<!rec_Point x 2>
+// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE12simple_array = #cir.const_array<[
+// CIR-DAG-SAME: #cir.const_record<{#cir.int<0> : !s32i, #cir.int<-1> : !s32i}> : !rec_simple,
+// CIR-DAG-SAME: #cir.const_record<{#cir.int<1111> : !s32i, #cir.int<2222> : !s32i}> : !rec_simple,
+// CIR-DAG-SAME: #cir.const_record<{#cir.int<0> : !s32i, #cir.int<-1> : !s32i}> : !rec_simple
+// CIR-DAG-SAME: ]> : !cir.array<!rec_simple x 3>
+
// CIR-DAG: cir.global "private" internal dso_local @_ZZ8functionvE12packed_array = #cir.const_array<[
// CIR-DAG-SAME: #cir.const_record<{#cir.int<123> : !s8i, #cir.int<456> : !s32i}> : !rec_packed,
// CIR-DAG-SAME: #cir.const_record<{#cir.int<123> : !s8i, #cir.int<456> : !s32i}> : !rec_packed
@@ -72,7 +88,9 @@ void function() {
// LLVM-DAG: @_ZZ8functionvE12packed_array = internal global [2 x %struct.packed] [%struct.packed <{ i8 123, i32 456 }>, %struct.packed <{ i8 123, i32 456 }>]
+// LLVM-DAG: @_ZZ8functionvE12simple_array = internal global [3 x %struct.simple] [%struct.simple { i32 0, i32 -1 }, %struct.simple { i32 1111, i32 2222 }, %struct.simple { i32 0, i32 -1 }]
// LLVM-DAG: @_ZZ8functionvE1e = internal global %struct.empty zeroinitializer
+// LLVM-DAG: @_ZZ8functionvE1s = internal global %struct.simple { i32 0, i32 -1 }
// LLVM-DAG: @_ZZ8functionvE2p1 = internal global %struct.Point { i32 10, i32 20, [3 x i8] c"cXM", i32 40 }
// LLVM-DAG: @_ZZ8functionvE2p2 = internal global %struct.packed <{ i8 123, i32 456 }>
// LLVM-DAG: @_ZZ8functionvE3paa = internal global %struct.packed_and_aligned <{ i16 1, i8 2, float 3.000000e+00, i8 0 }>
@@ -84,7 +102,9 @@ void function() {
// OGCG-DAG: @_ZZ8functionvE12packed_array = internal constant [2 x %struct.packed] [%struct.packed <{ i8 123, i32 456 }>, %struct.packed <{ i8 123, i32 456 }>]
+// OGCG-DAG: @_ZZ8functionvE12simple_array = internal constant [3 x %struct.simple] [%struct.simple { i32 0, i32 -1 }, %struct.simple { i32 1111, i32 2222 }, %struct.simple { i32 0, i32 -1 }]
// OGCG-DAG: @_ZZ8functionvE1e = internal constant %struct.empty zeroinitializer
+// OGCG-DAG: @_ZZ8functionvE1s = internal constant %struct.simple { i32 0, i32 -1 }
// OGCG-DAG: @_ZZ8functionvE2p1 = internal constant %struct.Point { i32 10, i32 20, [3 x i8] c"cXM", i32 40 }
// OGCG-DAG: @_ZZ8functionvE2p2 = internal constant %struct.packed <{ i8 123, i32 456 }>
// OGCG-DAG: @_ZZ8functionvE3paa = internal constant %struct.packed_and_aligned <{ i16 1, i8 2, float 3.000000e+00, i8 undef }>
>From d637f0673af855d9010f423b4b5b424e1ad31b9b Mon Sep 17 00:00:00 2001
From: Morris Hafner <mhafner at nvidia.com>
Date: Thu, 4 Sep 2025 02:56:53 +0200
Subject: [PATCH 6/6] Address review feedback
---
.../CIR/Dialect/Builder/CIRBaseBuilder.h | 8 ++
.../clang/CIR/Dialect/IR/CIRDataLayout.h | 2 +-
clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 136 ++++++++++--------
3 files changed, 88 insertions(+), 58 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index b2e3841c8d7df..74bee3220ee03 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -227,6 +227,14 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
return cir::AllocaOp::create(*this, loc, addrType, type, name, alignment);
}
+ /// Get constant address of a global variable as an MLIR attribute.
+ /// This wrapper infers the attribute type through the global op.
+ cir::GlobalViewAttr getGlobalViewAttr(cir::GlobalOp globalOp,
+ mlir::ArrayAttr indices = {}) {
+ cir::PointerType type = getPointerTo(globalOp.getSymType());
+ return getGlobalViewAttr(type, globalOp, indices);
+ }
+
/// Get constant address of a global variable as an MLIR attribute.
cir::GlobalViewAttr getGlobalViewAttr(cir::PointerType type,
cir::GlobalOp globalOp,
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDataLayout.h b/clang/include/clang/CIR/Dialect/IR/CIRDataLayout.h
index ecc681ee310e3..c0bb666522c37 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDataLayout.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDataLayout.h
@@ -36,7 +36,7 @@ class CIRDataLayout {
bool isBigEndian() const { return bigEndian; }
/// Internal helper method that returns requested alignment for type.
- llvm::Align getAlignment(mlir::Type ty, bool abiOrPref) const;
+ llvm::Align getAlignment(mlir::Type ty, bool useABIAlign) const;
llvm::Align getABITypeAlign(mlir::Type ty) const {
return getAlignment(ty, true);
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index 2fd418eda01a5..f660544d13cfa 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//
#include "Address.h"
+#include "CIRGenCXXABI.h"
#include "CIRGenConstantEmitter.h"
#include "CIRGenModule.h"
#include "CIRGenRecordLayout.h"
@@ -28,8 +29,10 @@
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "clang/CIR/MissingFeatures.h"
+#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/ErrorHandling.h"
+#include <functional>
#include <iterator>
using namespace clang;
@@ -47,7 +50,7 @@ static mlir::TypedAttr computePadding(CIRGenModule &cgm, CharUnits size) {
clang::CharUnits::QuantityType arSize = size.getQuantity();
CIRGenBuilderTy &bld = cgm.getBuilder();
if (size > CharUnits::One()) {
- SmallVector<mlir::Attribute, 4> elts(arSize, cir::ZeroAttr::get(eltTy));
+ SmallVector<mlir::Attribute> elts(arSize, cir::ZeroAttr::get(eltTy));
return bld.getConstArray(mlir::ArrayAttr::get(bld.getContext(), elts),
cir::ArrayType::get(eltTy, arSize));
}
@@ -70,7 +73,7 @@ struct ConstantAggregateBuilderUtils {
CharUnits getAlignment(const mlir::TypedAttr c) const {
return CharUnits::fromQuantity(
- dataLayout.getAlignment(c.getType(), /*abiOrPref=*/true));
+ dataLayout.getAlignment(c.getType(), /*useABIAlign=*/true));
}
CharUnits getSize(mlir::Type ty) const {
@@ -89,30 +92,36 @@ struct ConstantAggregateBuilderUtils {
/// Incremental builder for an mlir::TypedAttr holding a record or array
/// constant.
class ConstantAggregateBuilder : private ConstantAggregateBuilderUtils {
- /// The elements of the constant. These two arrays must have the same size;
- /// Offsets[i] describes the offset of Elems[i] within the constant. The
- /// elements are kept in increasing offset order, and we ensure that there
- /// is no overlap: Offsets[i+1] >= Offsets[i] + getSize(Elemes[i]).
+ struct Element {
+ Element(mlir::TypedAttr element, CharUnits offset)
+ : element(element), offset(offset) {}
+
+ mlir::TypedAttr element;
+ /// Describes the offset of `element` within the constant.
+ CharUnits offset;
+ };
+ /// The elements of the constant. The elements are kept in increasing offset
+ /// order, and we ensure that there is no overlap:
+ /// elements.offset[i+1] >= elements.offset[i] + getSize(elements.element[i])
///
/// This may contain explicit padding elements (in order to create a
/// natural layout), but need not. Gaps between elements are implicitly
/// considered to be filled with undef.
- llvm::SmallVector<mlir::TypedAttr, 32> elems;
- llvm::SmallVector<CharUnits, 32> offsets;
+ llvm::SmallVector<Element, 32> elements;
/// The size of the constant (the maximum end offset of any added element).
- /// May be larger than the end of Elems.back() if we split the last element
+ /// May be larger than the end of elems.back() if we split the last element
/// and removed some trailing undefs.
CharUnits size = CharUnits::Zero();
- /// This is true only if laying out Elems in order as the elements of a
+ /// This is true only if laying out elems in order as the elements of a
/// non-packed LLVM struct will give the correct layout.
bool naturalLayout = true;
- static mlir::Attribute
- buildFrom(CIRGenModule &cgm, ArrayRef<mlir::TypedAttr> elems,
- ArrayRef<CharUnits> offsets, CharUnits startOffset, CharUnits size,
- bool naturalLayout, mlir::Type desiredTy, bool allowOversized);
+ static mlir::Attribute buildFrom(CIRGenModule &cgm, ArrayRef<Element> elems,
+ CharUnits startOffset, CharUnits size,
+ bool naturalLayout, mlir::Type desiredTy,
+ bool allowOversized);
public:
ConstantAggregateBuilder(CIRGenModule &cgm)
@@ -134,8 +143,8 @@ class ConstantAggregateBuilder : private ConstantAggregateBuilderUtils {
/// Otherwise, the constant will be of exactly the same size as \p desiredTy
/// even if we can't represent it as that type.
mlir::Attribute build(mlir::Type desiredTy, bool allowOversized) const {
- return buildFrom(cgm, elems, offsets, CharUnits::Zero(), size,
- naturalLayout, desiredTy, allowOversized);
+ return buildFrom(cgm, elements, CharUnits::Zero(), size, naturalLayout,
+ desiredTy, allowOversized);
}
};
@@ -152,14 +161,12 @@ bool ConstantAggregateBuilder::add(mlir::TypedAttr typedAttr, CharUnits offset,
if (offset >= size) {
CharUnits align = getAlignment(typedAttr);
CharUnits alignedSize = size.alignTo(align);
- if (alignedSize > offset || offset.alignTo(align) != offset)
+ if (alignedSize > offset || offset.alignTo(align) != offset) {
naturalLayout = false;
- else if (alignedSize < offset) {
- elems.push_back(getPadding(offset - size));
- offsets.push_back(size);
+ } else if (alignedSize < offset) {
+ elements.emplace_back(getPadding(offset - size), size);
}
- elems.push_back(typedAttr);
- offsets.push_back(offset);
+ elements.emplace_back(typedAttr, offset);
size = offset + getSize(typedAttr);
return true;
}
@@ -169,17 +176,16 @@ bool ConstantAggregateBuilder::add(mlir::TypedAttr typedAttr, CharUnits offset,
return false;
}
-mlir::Attribute ConstantAggregateBuilder::buildFrom(
- CIRGenModule &cgm, ArrayRef<mlir::TypedAttr> elems,
- ArrayRef<CharUnits> offsets, CharUnits startOffset, CharUnits size,
- bool naturalLayout, mlir::Type desiredTy, bool allowOversized) {
+mlir::Attribute
+ConstantAggregateBuilder::buildFrom(CIRGenModule &cgm, ArrayRef<Element> elems,
+ CharUnits startOffset, CharUnits size,
+ bool naturalLayout, mlir::Type desiredTy,
+ bool allowOversized) {
ConstantAggregateBuilderUtils utils(cgm);
if (elems.empty())
return cir::UndefAttr::get(desiredTy);
- auto offset = [&](size_t i) { return offsets[i] - startOffset; };
-
// If we want an array type, see if all the elements are the same type and
// appropriately spaced.
if (mlir::isa<cir::ArrayType>(desiredTy)) {
@@ -192,45 +198,46 @@ mlir::Attribute ConstantAggregateBuilder::buildFrom(
// init), it can be larger.
CharUnits desiredSize = utils.getSize(desiredTy);
if (size > desiredSize) {
- assert(allowOversized && "Elems are oversized");
+ assert(allowOversized && "elems are oversized");
desiredSize = size;
}
// The natural alignment of an unpacked CIR record with the given elements.
CharUnits align = CharUnits::One();
- for (mlir::TypedAttr e : elems) {
+ for (auto [e, offset] : elems)
align = std::max(align, utils.getAlignment(e));
- }
// The natural size of an unpacked LLVM struct with the given elements.
CharUnits alignedSize = size.alignTo(align);
bool packed = false;
bool padded = false;
- ArrayRef<mlir::TypedAttr> unpackedElems = elems;
- llvm::SmallVector<mlir::TypedAttr, 32> unpackedElemStorage;
+ llvm::SmallVector<mlir::Attribute, 32> unpackedElems;
if (desiredSize < alignedSize || desiredSize.alignTo(align) != desiredSize) {
naturalLayout = false;
packed = true;
- } else if (desiredSize > alignedSize) {
+ } else {
// The natural layout would be too small. Add padding to fix it. (This
// is ignored if we choose a packed layout.)
- unpackedElemStorage.assign(unpackedElems.begin(), unpackedElems.end());
- unpackedElemStorage.push_back(utils.getPadding(desiredSize - size));
- unpackedElems = unpackedElemStorage;
+ unpackedElems.reserve(elems.size() + 1);
+ llvm::transform(elems, std::back_inserter(unpackedElems),
+ std::mem_fn(&Element::element));
+ if (desiredSize > alignedSize)
+ unpackedElems.push_back(utils.getPadding(desiredSize - size));
}
// If we don't have a natural layout, insert padding as necessary.
// As we go, double-check to see if we can actually just emit Elems
// as a non-packed record and do so opportunistically if possible.
- llvm::SmallVector<mlir::TypedAttr, 32> packedElems;
+ llvm::SmallVector<mlir::Attribute, 32> packedElems;
+ packedElems.reserve(elems.size());
if (!naturalLayout) {
CharUnits sizeSoFar = CharUnits::Zero();
- for (auto [index, element] : llvm::enumerate(elems)) {
+ for (auto [element, offset] : elems) {
CharUnits align = utils.getAlignment(element);
CharUnits naturalOffset = sizeSoFar.alignTo(align);
- CharUnits desiredOffset = offset(index);
+ CharUnits desiredOffset = offset - startOffset;
assert(desiredOffset >= sizeSoFar && "elements out of order");
if (desiredOffset != naturalOffset)
@@ -252,20 +259,15 @@ mlir::Attribute ConstantAggregateBuilder::buildFrom(
}
CIRGenBuilderTy &builder = cgm.getBuilder();
- llvm::SmallVector<mlir::Attribute, 32> arrayElements;
- arrayElements.reserve(elems.size());
- if (packed)
- llvm::copy(packedElems, std::back_inserter(arrayElements));
- else
- llvm::copy(unpackedElems, std::back_inserter(arrayElements));
- auto arrAttr = mlir::ArrayAttr::get(builder.getContext(), arrayElements);
+ auto arrAttr = mlir::ArrayAttr::get(builder.getContext(),
+ packed ? packedElems : unpackedElems);
- cir::RecordType strType = builder.getCompleteRecordType(arrAttr, packed);
+ cir::RecordType recordType = builder.getCompleteRecordType(arrAttr, packed);
if (auto desired = mlir::dyn_cast<cir::RecordType>(desiredTy))
- if (desired.isLayoutIdentical(strType))
- strType = desired;
+ if (desired.isLayoutIdentical(recordType))
+ recordType = desired;
- return builder.getConstRecordOrZeroAttr(arrAttr, packed, padded, strType);
+ return builder.getConstRecordOrZeroAttr(arrAttr, packed, padded, recordType);
}
//===----------------------------------------------------------------------===//
@@ -437,11 +439,29 @@ bool ConstRecordBuilder::build(const APValue &val, const RecordDecl *rd,
CharUnits offset) {
const ASTRecordLayout &layout = cgm.getASTContext().getASTRecordLayout(rd);
if (const CXXRecordDecl *cd = dyn_cast<CXXRecordDecl>(rd)) {
- assert(!cir::MissingFeatures::vtableInitialization());
+ // Add a vtable pointer, if we need one and it hasn't already been added.
+ if (layout.hasOwnVFPtr()) {
+ CIRGenBuilderTy &builder = cgm.getBuilder();
+ cir::GlobalOp vtable =
+ cgm.getCXXABI().getAddrOfVTable(vTableClass, CharUnits());
+ clang::VTableLayout::AddressPointLocation addressPoint =
+ cgm.getItaniumVTableContext()
+ .getVTableLayout(vTableClass)
+ .getAddressPoint(BaseSubobject(cd, offset));
+ assert(!cir::MissingFeatures::addressPointerAuthInfo());
+ mlir::ArrayAttr indices = builder.getArrayAttr({
+ builder.getI32IntegerAttr(addressPoint.VTableIndex),
+ builder.getI32IntegerAttr(addressPoint.AddressPointIndex),
+ });
+ cir::GlobalViewAttr vtableInit =
+ cgm.getBuilder().getGlobalViewAttr(vtable, indices);
+ if (!appendBytes(offset, vtableInit))
+ return false;
+ }
// Accumulate and sort bases, in order to visit them in address order, which
// may not be the same as declaration order.
- SmallVector<BaseInfo, 8> bases;
+ SmallVector<BaseInfo> bases;
bases.reserve(cd->getNumBases());
for (auto [index, base] : llvm::enumerate(cd->bases())) {
assert(!base.isVirtual() && "should not have virtual bases here");
@@ -449,7 +469,9 @@ bool ConstRecordBuilder::build(const APValue &val, const RecordDecl *rd,
CharUnits baseOffset = layout.getBaseClassOffset(bd);
bases.push_back(BaseInfo(bd, baseOffset, index));
}
- llvm::stable_sort(bases);
+#ifdef EXPENSIVE_CHECKS
+ assert(llvm::is_sorted(bases) && "bases not sorted by offset");
+#endif
for (BaseInfo &base : bases) {
bool isPrimaryBase = layout.getPrimaryBase() == base.decl;
@@ -815,7 +837,7 @@ emitArrayConstant(CIRGenModule &cgm, mlir::Type desiredType,
// If all the elements had the same type up to the trailing zeroes and
// there are eight or more nonzero elements, emit a struct of two arrays
// (the nonzero data and the zeroinitializer).
- SmallVector<mlir::Attribute, 4> eles;
+ SmallVector<mlir::Attribute> eles;
eles.reserve(nonzeroLength);
for (const auto &element : elements)
eles.push_back(element);
@@ -845,7 +867,7 @@ emitArrayConstant(CIRGenModule &cgm, mlir::Type desiredType,
}
if (commonElementType) {
- SmallVector<mlir::Attribute, 4> eles;
+ SmallVector<mlir::Attribute> eles;
eles.reserve(elements.size());
for (const auto &element : elements)
@@ -856,7 +878,7 @@ emitArrayConstant(CIRGenModule &cgm, mlir::Type desiredType,
mlir::ArrayAttr::get(builder.getContext(), eles));
}
- SmallVector<mlir::Attribute, 4> eles;
+ SmallVector<mlir::Attribute> eles;
eles.reserve(elements.size());
for (auto const &element : elements)
eles.push_back(element);
More information about the cfe-commits
mailing list