[clang] [mlir] [CIR] Lower pointer const_array globals without insertvalue chains (PR #198427)
via cfe-commits
cfe-commits at lists.llvm.org
Wed May 20 13:04:38 PDT 2026
https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/198427
>From 5240882812847cdf572e7e7e6e8b6ca781fc0005 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Mon, 18 May 2026 16:43:16 -0700
Subject: [PATCH 1/4] [mlir][LLVMIR] Allow address-of-global as a leaf in array
constants
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Large aggregate globals built as nested llvm.insertvalue chains force
LLVMModuleTranslation to call ConstantFoldInsertValueInstruction on each
step, rebuilding the whole ConstantArray each time (O(N²) in the number
of elements). CIR and other frontends hit this when lowering big tables
of pointers (e.g. gcc insn-automata.cc).
Extend llvm.mlir.constant verification so pointer-array aggregates may
use FlatSymbolRefAttr, ZeroAttr, or UndefAttr leaves (not only function
refs). Teach ModuleTranslation::getLLVMConstant to resolve those symbol
refs against globals already registered in the module, and add a
translate test that checks a single LLVM constant array initializer.
---
.../mlir/Target/LLVMIR/ModuleTranslation.h | 11 ++++
mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp | 14 ++++-
mlir/lib/Target/LLVMIR/ModuleTranslation.cpp | 53 +++++++++++++++----
mlir/test/Dialect/LLVMIR/invalid.mlir | 10 ++++
.../LLVMIR/llvmir-global-addressof-leaf.mlir | 19 +++++++
5 files changed, 96 insertions(+), 11 deletions(-)
create mode 100644 mlir/test/Target/LLVMIR/llvmir-global-addressof-leaf.mlir
diff --git a/mlir/include/mlir/Target/LLVMIR/ModuleTranslation.h b/mlir/include/mlir/Target/LLVMIR/ModuleTranslation.h
index 2516818b320aa..c5c4b105ee152 100644
--- a/mlir/include/mlir/Target/LLVMIR/ModuleTranslation.h
+++ b/mlir/include/mlir/Target/LLVMIR/ModuleTranslation.h
@@ -262,6 +262,11 @@ class ModuleTranslation {
return globalsMapping.lookup(op);
}
+ /// Finds an LLVM IR global value by the mlir.global symbol name.
+ llvm::GlobalValue *lookupGlobal(StringRef name) const {
+ return globalsByNameMapping.lookup(name);
+ }
+
/// Finds an LLVM IR global value that corresponds to the given MLIR operation
/// defining a global alias value.
llvm::GlobalValue *lookupAlias(Operation *op) {
@@ -470,6 +475,12 @@ class ModuleTranslation {
/// Mappings between llvm.mlir.global definitions and corresponding globals.
DenseMap<Operation *, llvm::GlobalValue *> globalsMapping;
+ /// Name-keyed mirror of `globalsMapping`, populated alongside it during
+ /// `convertGlobalsAndAliases`. Lets `getLLVMConstant` resolve
+ /// `FlatSymbolRefAttr` leaves of aggregate constants that name a global
+ /// (mirroring how `functionMapping` resolves function names).
+ llvm::StringMap<llvm::GlobalValue *> globalsByNameMapping;
+
/// Mappings between llvm.mlir.alias definitions and corresponding global
/// aliases.
DenseMap<Operation *, llvm::GlobalValue *> aliasesMapping;
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 2b603efadb476..63bd9f8a3d625 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -3469,7 +3469,19 @@ static LogicalResult verifyStructArrayConstant(LLVM::ConstantOp op,
// from a symbol and cannot be represented in a DenseElementsAttr, but no MLIR
// user needs this so far, and it seems better to avoid people misusing the
// ArrayAttr for simple types.
- auto structType = dyn_cast<LLVM::LLVMStructType>(arrayType.getElementType());
+ Type elementType = arrayType.getElementType();
+ if (isa<LLVM::LLVMPointerType>(elementType)) {
+ for (auto [idx, elementAttr] : llvm::enumerate(arrayAttr)) {
+ if (isa<FlatSymbolRefAttr, LLVM::ZeroAttr, LLVM::UndefAttr,
+ LLVM::PoisonAttr>(elementAttr))
+ continue;
+ return op.emitOpError()
+ << "pointer array element at index " << idx
+ << " must be a flat symbol reference, zero, undef, or poison";
+ }
+ return success();
+ }
+ auto structType = dyn_cast<LLVM::LLVMStructType>(elementType);
if (!structType)
return op.emitOpError() << "for array with an array attribute must have a "
"struct element type";
diff --git a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
index 9d0ce97ddbbdf..8b1c2bcf6e1d7 100644
--- a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
@@ -618,9 +618,15 @@ llvm::Constant *mlir::LLVM::detail::getLLVMConstant(
}
return llvm::ConstantFP::get(llvmType, floatAttr.getValue());
}
- if (auto funcAttr = dyn_cast<FlatSymbolRefAttr>(attr))
- return llvm::ConstantExpr::getBitCast(
- moduleTranslation.lookupFunction(funcAttr.getValue()), llvmType);
+ if (auto symAttr = dyn_cast<FlatSymbolRefAttr>(attr)) {
+ StringRef name = symAttr.getValue();
+ if (llvm::Function *func = moduleTranslation.lookupFunction(name))
+ return llvm::ConstantExpr::getBitCast(func, llvmType);
+ if (llvm::GlobalValue *global = moduleTranslation.lookupGlobal(name))
+ return llvm::ConstantExpr::getBitCast(global, llvmType);
+ emitError(loc, "unknown symbol reference '") << name << "' in constant";
+ return nullptr;
+ }
if (auto splatAttr = dyn_cast<SplatElementsAttr>(attr)) {
llvm::Type *elementType;
uint64_t numElements;
@@ -1197,16 +1203,16 @@ LogicalResult ModuleTranslation::convertGlobalsAndAliases() {
for (auto op : getModuleBody(mlirModule).getOps<LLVM::GlobalOp>()) {
llvm::Type *type = convertType(op.getType());
llvm::Constant *cst = nullptr;
- if (op.getValueOrNull()) {
+ const bool deferValueAttrToPass2 = op.getValueOrNull() &&
+ !op.getInitializerBlock() &&
+ !isa<StringAttr>(op.getValueOrNull());
+ if (op.getValueOrNull() && !deferValueAttrToPass2) {
// String attributes are treated separately because they cannot appear as
// in-function constants and are thus not supported by getLLVMConstant.
if (auto strAttr = dyn_cast_or_null<StringAttr>(op.getValueOrNull())) {
cst = llvm::ConstantDataArray::getString(
llvmModule->getContext(), strAttr.getValue(), /*AddNull=*/false);
type = cst->getType();
- } else if (!(cst = getLLVMConstant(type, op.getValueOrNull(), op.getLoc(),
- *this))) {
- return failure();
}
}
@@ -1216,10 +1222,14 @@ LogicalResult ModuleTranslation::convertGlobalsAndAliases() {
// external to have initializers. If MLIR does not provide an initializer,
// default to undef.
bool dropInitializer = shouldDropGlobalInitializer(linkage, cst);
- if (!dropInitializer && !cst)
- cst = llvm::UndefValue::get(type);
- else if (dropInitializer && cst)
+ if (!deferValueAttrToPass2) {
+ if (!dropInitializer && !cst)
+ cst = llvm::UndefValue::get(type);
+ else if (dropInitializer && cst)
+ cst = nullptr;
+ } else {
cst = nullptr;
+ }
auto *var = new llvm::GlobalVariable(
*llvmModule, type, op.getConstant(), linkage, cst, op.getSymName(),
@@ -1249,6 +1259,7 @@ LogicalResult ModuleTranslation::convertGlobalsAndAliases() {
var->setVisibility(convertVisibilityToLLVM(op.getVisibility_()));
globalsMapping.try_emplace(op, var);
+ globalsByNameMapping.try_emplace(op.getSymName(), var);
// Add debug information if present.
if (op.getDbgExprs()) {
@@ -1306,6 +1317,28 @@ LogicalResult ModuleTranslation::convertGlobalsAndAliases() {
var->addAttributes(*convertedTargetSpecificAttrs);
}
+ // Value-attribute initializers may reference other globals by symbol name.
+ // Register every global above before materializing those constants.
+ for (auto op : getModuleBody(mlirModule).getOps<LLVM::GlobalOp>()) {
+ if (!op.getValueOrNull() || op.getInitializerBlock() ||
+ isa<StringAttr>(op.getValueOrNull()))
+ continue;
+
+ llvm::Type *type = convertType(op.getType());
+ llvm::Constant *cst =
+ getLLVMConstant(type, op.getValueOrNull(), op.getLoc(), *this);
+ if (!cst)
+ return failure();
+
+ auto linkage = convertLinkageToLLVM(op.getLinkage());
+ bool dropInitializer = shouldDropGlobalInitializer(linkage, cst);
+ auto *var = cast<llvm::GlobalVariable>(lookupGlobal(op));
+ if (dropInitializer)
+ var->setInitializer(nullptr);
+ else
+ var->setInitializer(cst);
+ }
+
// Create all llvm::GlobalAlias
for (auto op : getModuleBody(mlirModule).getOps<LLVM::AliasOp>()) {
llvm::Type *type = convertType(op.getType());
diff --git a/mlir/test/Dialect/LLVMIR/invalid.mlir b/mlir/test/Dialect/LLVMIR/invalid.mlir
index cb5148a0ca9ae..e80094df1eed2 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -1957,6 +1957,16 @@ llvm.mlir.global internal constant @bad_array_attr_simple_type() : !llvm.array<2
// -----
+llvm.mlir.global internal constant @bad_ptr_array_attr_leaf() : !llvm.array<2 x ptr> {
+ // expected-error at below {{pointer array element at index 0 must be a flat symbol reference, zero, undef, or poison}}
+ %0 = llvm.mlir.constant([1 : i32, @s0]) : !llvm.array<2 x ptr>
+ llvm.return %0 : !llvm.array<2 x ptr>
+}
+
+llvm.mlir.global private constant @s0("a\00") {addr_space = 0 : i32} : !llvm.array<2 x i8>
+
+// -----
+
llvm.func @inlineAsmMustTail(%arg0: i32, %arg1 : !llvm.ptr) {
// expected-error at +1 {{op tail call kind 'musttail' is not supported}}
%8 = llvm.inline_asm tail_call_kind = <musttail> "foo", "=r,=r,r" %arg0 : (i32) -> !llvm.struct<(i8, i8)>
diff --git a/mlir/test/Target/LLVMIR/llvmir-global-addressof-leaf.mlir b/mlir/test/Target/LLVMIR/llvmir-global-addressof-leaf.mlir
new file mode 100644
index 0000000000000..93b43537ed71f
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/llvmir-global-addressof-leaf.mlir
@@ -0,0 +1,19 @@
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
+
+// Per-element string globals referenced by the array-of-pointers global below.
+llvm.mlir.global private constant @s0("a\00") {addr_space = 0 : i32} : !llvm.array<2 x i8>
+llvm.mlir.global private constant @s1("b\00") {addr_space = 0 : i32} : !llvm.array<2 x i8>
+llvm.mlir.global private constant @s2("c\00") {addr_space = 0 : i32} : !llvm.array<2 x i8>
+
+// Single llvm.mlir.constant with a FlatSymbolRefAttr leaf per array element,
+// emitted inside the global's initializer region. This shape lets MLIR's
+// translator route the lowering through llvm::ConstantArray::get once
+// (the existing array-of-ArrayAttr path in getLLVMConstant), avoiding the
+// O(N^2) per-element ConstantFoldInsertValueInstruction blowup that a chain
+// of llvm.insertvalue ops would otherwise force on large arrays.
+llvm.mlir.global external constant @ptrs() : !llvm.array<3 x ptr> {
+ %0 = llvm.mlir.constant([@s0, @s1, @s2]) : !llvm.array<3 x ptr>
+ llvm.return %0 : !llvm.array<3 x ptr>
+}
+
+// CHECK: @ptrs = constant [3 x ptr] [ptr @s0, ptr @s1, ptr @s2]
>From 28cce6cb25599e6beb38347fde7641ee20f2a632 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Mon, 18 May 2026 16:44:51 -0700
Subject: [PATCH 2/4] [CIR] Lower pointer const_array globals without
insertvalue chains
When a cir.global initializer is a const_array of global_view or null
pointer elements with no trailing zero padding, emit the global with a
single aggregate value attribute instead of an initializer region full
of llvm.insertvalue ops. That pairs with the MLIR change that lets
llvm.mlir.global carry [@g0, @g1, ...] on pointer arrays and translates
them to one llvm::ConstantArray.
Indexed global_view elements and non-attribute representable cases still
use the existing insertvalue lowering path.
---
clang/include/clang/CIR/LoweringHelpers.h | 3 +-
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 20 ++++-
clang/lib/CIR/Lowering/LoweringHelpers.cpp | 90 ++++++++++++++++++-
.../global-pointer-array-fast-lowering.cpp | 16 ++++
.../CIR/Lowering/const-array-of-pointers.cir | 18 ++++
5 files changed, 144 insertions(+), 3 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/global-pointer-array-fast-lowering.cpp
create mode 100644 clang/test/CIR/Lowering/const-array-of-pointers.cir
diff --git a/clang/include/clang/CIR/LoweringHelpers.h b/clang/include/clang/CIR/LoweringHelpers.h
index 66e99c7e84416..3f3e939621c37 100644
--- a/clang/include/clang/CIR/LoweringHelpers.h
+++ b/clang/include/clang/CIR/LoweringHelpers.h
@@ -35,7 +35,8 @@ convertToDenseElementsAttr(cir::ConstArrayAttr attr,
std::optional<mlir::Attribute>
lowerConstArrayAttr(cir::ConstArrayAttr constArr,
- const mlir::TypeConverter *converter);
+ const mlir::TypeConverter *converter,
+ mlir::ModuleOp moduleOp = {});
mlir::Value getConstAPInt(mlir::OpBuilder &bld, mlir::Location loc,
mlir::Type typ, const llvm::APInt &val);
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 2720dd2500a94..ddf4df9ff42bc 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -483,7 +483,6 @@ mlir::Value CIRAttrToValue::visitCirAttr(cir::ConstArrayAttr attr) {
// Iteratively lower each constant element of the array.
if (auto arrayAttr = mlir::dyn_cast<mlir::ArrayAttr>(attr.getElts())) {
for (auto [idx, elt] : llvm::enumerate(arrayAttr)) {
- mlir::DataLayout dataLayout(parentOp->getParentOfType<mlir::ModuleOp>());
mlir::Value init = visit(elt);
result =
mlir::LLVM::InsertValueOp::create(rewriter, loc, result, init, idx);
@@ -2510,6 +2509,25 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite(
cir::ConstComplexAttr, cir::GlobalViewAttr,
cir::TypeInfoAttr, cir::UndefAttr, cir::PoisonAttr,
cir::VTableAttr, cir::ZeroAttr>(init.value())) {
+ if (auto constArr = mlir::dyn_cast<cir::ConstArrayAttr>(init.value())) {
+ if (!hasTrailingZeros(constArr)) {
+ mlir::Type elTy = constArr.getType();
+ while (auto arrTy = mlir::dyn_cast<cir::ArrayType>(elTy))
+ elTy = arrTy.getElementType();
+ if (mlir::isa<cir::PointerType>(elTy)) {
+ mlir::ModuleOp module = op->getParentOfType<mlir::ModuleOp>();
+ if (std::optional<mlir::Attribute> bulkInit =
+ lowerConstArrayAttr(constArr, typeConverter, module)) {
+ mlir::SymbolRefAttr comdatAttr = getComdatAttr(op, rewriter);
+ rewriter.replaceOpWithNewOp<mlir::LLVM::GlobalOp>(
+ op, llvmType, isConst, linkage, symbol, bulkInit.value(),
+ alignment, addrSpace, isDsoLocal, isThreadLocal, comdatAttr,
+ attributes);
+ return mlir::success();
+ }
+ }
+ }
+ }
// TODO(cir): once LLVM's dialect has proper equivalent attributes this
// should be updated. For now, we use a custom op to initialize globals
// to the appropriate value.
diff --git a/clang/lib/CIR/Lowering/LoweringHelpers.cpp b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
index 0786579a601b1..5244b84548945 100644
--- a/clang/lib/CIR/Lowering/LoweringHelpers.cpp
+++ b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
@@ -12,6 +12,8 @@
#include "clang/CIR/LoweringHelpers.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
+#include "mlir/Dialect/LLVMIR/LLVMTypes.h"
+#include "mlir/IR/SymbolTable.h"
#include "clang/CIR/MissingFeatures.h"
mlir::DenseElementsAttr
@@ -115,9 +117,71 @@ mlir::DenseElementsAttr convertToDenseElementsAttr(
llvm::ArrayRef(values));
}
+/// Return true when \p gv can be lowered to a \c FlatSymbolRefAttr leaf without
+/// addrspacecast or bitcast (mirrors \c CIRAttrToValue::visitCirAttr).
+static bool globalViewMatchesPointerLeaf(cir::GlobalViewAttr gv,
+ mlir::ModuleOp moduleOp,
+ const mlir::TypeConverter *converter) {
+ if (gv.getIndices() || mlir::isa<cir::IntType, cir::VPtrType>(gv.getType()))
+ return false;
+
+ auto ptrTy = mlir::dyn_cast<cir::PointerType>(gv.getType());
+ if (!ptrTy)
+ return false;
+
+ unsigned sourceAddrSpace = 0;
+ mlir::Type sourceType;
+ auto sourceSymbol =
+ mlir::SymbolTable::lookupSymbolIn(moduleOp, gv.getSymbol());
+ if (auto llvmSymbol = mlir::dyn_cast<mlir::LLVM::GlobalOp>(sourceSymbol)) {
+ sourceType = llvmSymbol.getType();
+ sourceAddrSpace = llvmSymbol.getAddrSpace();
+ } else if (auto cirSymbol = mlir::dyn_cast<cir::GlobalOp>(sourceSymbol)) {
+ sourceType = converter->convertType(cirSymbol.getSymType());
+ if (auto targetAS = mlir::dyn_cast_if_present<cir::TargetAddressSpaceAttr>(
+ cirSymbol.getAddrSpaceAttr()))
+ sourceAddrSpace = targetAS.getValue();
+ } else {
+ return false;
+ }
+
+ auto llvmDstTy = converter->convertType<mlir::LLVM::LLVMPointerType>(ptrTy);
+ if (llvmDstTy.getAddressSpace() != sourceAddrSpace)
+ return false;
+
+ mlir::Type llvmEltTy = converter->convertType(ptrTy.getPointee());
+ if (llvmEltTy == sourceType)
+ return true;
+ if (auto arrTy = mlir::dyn_cast<mlir::LLVM::LLVMArrayType>(sourceType))
+ return llvmEltTy == arrTy.getElementType();
+ return false;
+}
+
+/// Lower a single pointer-element of a \c cir.const_array to an LLVM-dialect
+/// constant leaf suitable for a bulk \c llvm.mlir.constant. Only handles
+/// address-of-global without indices and null pointers; indexed global views
+/// must use the per-element \c llvm.insertvalue fallback.
+static std::optional<mlir::Attribute>
+lowerPointerElementAttr(mlir::Attribute elt, mlir::MLIRContext *ctx,
+ mlir::ModuleOp moduleOp,
+ const mlir::TypeConverter *converter) {
+ if (auto gv = mlir::dyn_cast<cir::GlobalViewAttr>(elt)) {
+ if (!moduleOp || !globalViewMatchesPointerLeaf(gv, moduleOp, converter))
+ return std::nullopt;
+ return gv.getSymbol();
+ }
+ if (auto nullPtr = mlir::dyn_cast<cir::ConstPtrAttr>(elt)) {
+ if (nullPtr.isNullValue())
+ return mlir::LLVM::ZeroAttr::get(ctx);
+ return std::nullopt;
+ }
+ return std::nullopt;
+}
+
std::optional<mlir::Attribute>
lowerConstArrayAttr(cir::ConstArrayAttr constArr,
- const mlir::TypeConverter *converter) {
+ const mlir::TypeConverter *converter,
+ mlir::ModuleOp moduleOp) {
// Ensure ConstArrayAttr has a type.
const auto typedConstArr = mlir::cast<mlir::TypedAttr>(constArr);
@@ -132,6 +196,13 @@ lowerConstArrayAttr(cir::ConstArrayAttr constArr,
type = arrayType.getElementType();
}
+ if (auto eltsArr = mlir::dyn_cast<mlir::ArrayAttr>(constArr.getElts())) {
+ for (mlir::Attribute elt : eltsArr) {
+ if (mlir::isa<cir::PoisonAttr>(elt))
+ return std::nullopt;
+ }
+ }
+
if (mlir::isa<mlir::StringAttr>(constArr.getElts()))
return convertStringAttrToDenseElementsAttr(constArr,
converter->convertType(type));
@@ -143,6 +214,23 @@ lowerConstArrayAttr(cir::ConstArrayAttr constArr,
return convertToDenseElementsAttr<cir::FPAttr, mlir::APFloat>(
constArr, dims, type, converter->convertType(type));
+ if (mlir::isa<cir::PointerType>(type)) {
+ auto eltsArr = mlir::dyn_cast<mlir::ArrayAttr>(constArr.getElts());
+ if (!eltsArr)
+ return std::nullopt;
+ llvm::SmallVector<mlir::Attribute> lowered;
+ lowered.reserve(eltsArr.size());
+ mlir::MLIRContext *ctx = constArr.getContext();
+ for (mlir::Attribute elt : eltsArr) {
+ std::optional<mlir::Attribute> llvmElt =
+ lowerPointerElementAttr(elt, ctx, moduleOp, converter);
+ if (!llvmElt)
+ return std::nullopt;
+ lowered.push_back(*llvmElt);
+ }
+ return mlir::ArrayAttr::get(ctx, lowered);
+ }
+
return std::nullopt;
}
diff --git a/clang/test/CIR/CodeGen/global-pointer-array-fast-lowering.cpp b/clang/test/CIR/CodeGen/global-pointer-array-fast-lowering.cpp
new file mode 100644
index 0000000000000..ecb4fb8e4722d
--- /dev/null
+++ b/clang/test/CIR/CodeGen/global-pointer-array-fast-lowering.cpp
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// 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
+
+const char *names[] = { "a", "b", "c" };
+int len() { return sizeof(names)/sizeof(*names); }
+
+// CIR: cir.global {{.*}}@names = #cir.const_array<[#cir.global_view<@".str"> : !cir.ptr<!s8i>, #cir.global_view<@".str.1"> : !cir.ptr<!s8i>, #cir.global_view<@".str.2"> : !cir.ptr<!s8i>]>
+
+// LLVM: @names = {{.*}}global [3 x ptr] [ptr @.str{{.*}}, ptr @.str{{.*}}, ptr @.str{{.*}}]
+// LLVM-NOT: insertvalue
+
+// OGCG: @names = {{.*}}global [3 x ptr] [ptr @.str{{.*}}, ptr @.str{{.*}}, ptr @.str{{.*}}]
diff --git a/clang/test/CIR/Lowering/const-array-of-pointers.cir b/clang/test/CIR/Lowering/const-array-of-pointers.cir
new file mode 100644
index 0000000000000..47797c0cf3271
--- /dev/null
+++ b/clang/test/CIR/Lowering/const-array-of-pointers.cir
@@ -0,0 +1,18 @@
+// RUN: cir-opt %s --cir-to-llvm -o - | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+ cir.global "private" constant cir_private dso_local @g0 = #cir.int<0> : !s32i
+ cir.global "private" constant cir_private dso_local @g1 = #cir.int<1> : !s32i
+ cir.global "private" constant cir_private dso_local @g2 = #cir.int<2> : !s32i
+
+ cir.global constant external dso_local @ptrs = #cir.const_array<[
+ #cir.global_view<@g0> : !cir.ptr<!s32i>,
+ #cir.global_view<@g1> : !cir.ptr<!s32i>,
+ #cir.global_view<@g2> : !cir.ptr<!s32i>
+ ]> : !cir.array<!cir.ptr<!s32i> x 3>
+}
+
+// CHECK-LABEL: llvm.mlir.global external constant @ptrs(
+// CHECK-NOT: llvm.insertvalue
>From 7c831ffdb94f4ed05caff5790a6b6e552fb99944 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Wed, 20 May 2026 10:46:01 -0700
Subject: [PATCH 3/4] [CIR] Extend bulk const_array global lowering for review
on #198427
Review follow-up: allow the fast llvm.mlir.global aggregate path for
const_array globals whose leaves are integers or floats (not only
pointers), document when bulk lowering is skipped, speed up
hasTrailingZeros on large arrays, recurse poison checks through nested
const_array attributes, and hoist const_array handling out of the
shared region-initializer branch.
Tests cover int and multi-dim int codegen, trailing-zero and
address-space mismatch fallbacks (mlir insertvalue regions), and
multi-dim int bulk lowering in cir-opt.
---
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 79 ++++++++++++-------
clang/lib/CIR/Lowering/LoweringHelpers.cpp | 24 ++++--
.../global-pointer-array-fast-lowering.cpp | 16 +++-
.../const-array-bulk-lowering-fallbacks.cir | 29 +++++++
4 files changed, 113 insertions(+), 35 deletions(-)
create mode 100644 clang/test/CIR/Lowering/const-array-bulk-lowering-fallbacks.cir
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index ddf4df9ff42bc..b14d4ae3a9051 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -1958,13 +1958,33 @@ mlir::LogicalResult CIRToLLVMStoreOpLowering::matchAndRewrite(
return mlir::LogicalResult::success();
}
+static mlir::Type getConstArrayLeafType(mlir::Type ty) {
+ while (auto arrTy = mlir::dyn_cast<cir::ArrayType>(ty))
+ ty = arrTy.getElementType();
+ return ty;
+}
+
+static bool isBulkLowerableConstArrayLeaf(mlir::Type leafTy) {
+ return mlir::isa<cir::PointerType, cir::IntType, cir::FPTypeInterface>(
+ leafTy);
+}
+
bool hasTrailingZeros(cir::ConstArrayAttr attr) {
- auto array = mlir::dyn_cast<mlir::ArrayAttr>(attr.getElts());
- return attr.hasTrailingZeros() ||
- (array && std::count_if(array.begin(), array.end(), [](auto elt) {
- auto ar = dyn_cast<cir::ConstArrayAttr>(elt);
- return ar && hasTrailingZeros(ar);
- }));
+ if (attr.hasTrailingZeros())
+ return true;
+
+ auto elts = mlir::dyn_cast<mlir::ArrayAttr>(attr.getElts());
+ if (!elts)
+ return false;
+
+ auto cirArrTy = mlir::dyn_cast<cir::ArrayType>(attr.getType());
+ if (!cirArrTy || !mlir::isa<cir::ArrayType>(cirArrTy.getElementType()))
+ return false;
+
+ return llvm::any_of(elts, [](mlir::Attribute elt) {
+ auto nested = mlir::dyn_cast<cir::ConstArrayAttr>(elt);
+ return nested && hasTrailingZeros(nested);
+ });
}
mlir::LogicalResult CIRToLLVMConstantOpLowering::matchAndRewrite(
@@ -2504,30 +2524,33 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite(
op.emitError() << "unsupported initializer '" << init.value() << "'";
return mlir::failure();
}
- } else if (mlir::isa<cir::ConstArrayAttr, cir::ConstVectorAttr,
- cir::ConstRecordAttr, cir::ConstPtrAttr,
- cir::ConstComplexAttr, cir::GlobalViewAttr,
- cir::TypeInfoAttr, cir::UndefAttr, cir::PoisonAttr,
- cir::VTableAttr, cir::ZeroAttr>(init.value())) {
- if (auto constArr = mlir::dyn_cast<cir::ConstArrayAttr>(init.value())) {
- if (!hasTrailingZeros(constArr)) {
- mlir::Type elTy = constArr.getType();
- while (auto arrTy = mlir::dyn_cast<cir::ArrayType>(elTy))
- elTy = arrTy.getElementType();
- if (mlir::isa<cir::PointerType>(elTy)) {
- mlir::ModuleOp module = op->getParentOfType<mlir::ModuleOp>();
- if (std::optional<mlir::Attribute> bulkInit =
- lowerConstArrayAttr(constArr, typeConverter, module)) {
- mlir::SymbolRefAttr comdatAttr = getComdatAttr(op, rewriter);
- rewriter.replaceOpWithNewOp<mlir::LLVM::GlobalOp>(
- op, llvmType, isConst, linkage, symbol, bulkInit.value(),
- alignment, addrSpace, isDsoLocal, isThreadLocal, comdatAttr,
- attributes);
- return mlir::success();
- }
- }
+ } else if (auto constArr =
+ mlir::dyn_cast<cir::ConstArrayAttr>(init.value())) {
+ // Bulk-emit llvm.mlir.global when lowerConstArrayAttr can build the
+ // whole initializer as one aggregate attribute (no insertvalue
+ // region). Skip arrays with trailing-zero compression: those need
+ // per-element zero/undef setup. Leaf type must match what
+ // lowerConstArrayAttr handles (pointers, integers, floats today).
+ if (!hasTrailingZeros(constArr) &&
+ isBulkLowerableConstArrayLeaf(
+ getConstArrayLeafType(constArr.getType()))) {
+ mlir::ModuleOp module = op->getParentOfType<mlir::ModuleOp>();
+ if (std::optional<mlir::Attribute> bulkInit =
+ lowerConstArrayAttr(constArr, typeConverter, module)) {
+ mlir::SymbolRefAttr comdatAttr = getComdatAttr(op, rewriter);
+ rewriter.replaceOpWithNewOp<mlir::LLVM::GlobalOp>(
+ op, llvmType, isConst, linkage, symbol, bulkInit.value(),
+ alignment, addrSpace, isDsoLocal, isThreadLocal, comdatAttr,
+ attributes);
+ return mlir::success();
}
}
+ return matchAndRewriteRegionInitializedGlobal(op, init.value(), rewriter);
+ } else if (mlir::isa<cir::ConstVectorAttr, cir::ConstRecordAttr,
+ cir::ConstPtrAttr, cir::ConstComplexAttr,
+ cir::GlobalViewAttr, cir::TypeInfoAttr, cir::UndefAttr,
+ cir::PoisonAttr, cir::VTableAttr, cir::ZeroAttr>(
+ init.value())) {
// TODO(cir): once LLVM's dialect has proper equivalent attributes this
// should be updated. For now, we use a custom op to initialize globals
// to the appropriate value.
diff --git a/clang/lib/CIR/Lowering/LoweringHelpers.cpp b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
index 5244b84548945..a89816949ef43 100644
--- a/clang/lib/CIR/Lowering/LoweringHelpers.cpp
+++ b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
@@ -142,6 +142,8 @@ static bool globalViewMatchesPointerLeaf(cir::GlobalViewAttr gv,
cirSymbol.getAddrSpaceAttr()))
sourceAddrSpace = targetAS.getValue();
} else {
+ // cir.func and other symbols not yet lowered to globals cannot be used as
+ // bulk constant leaves; those cases keep the insertvalue fallback.
return false;
}
@@ -178,6 +180,20 @@ lowerPointerElementAttr(mlir::Attribute elt, mlir::MLIRContext *ctx,
return std::nullopt;
}
+static bool containsPoison(mlir::Attribute attr) {
+ if (mlir::isa<cir::PoisonAttr>(attr))
+ return true;
+ if (auto elts = mlir::dyn_cast<mlir::ArrayAttr>(attr))
+ return llvm::any_of(elts, containsPoison);
+ if (auto constArr = mlir::dyn_cast<cir::ConstArrayAttr>(attr)) {
+ if (mlir::isa<mlir::StringAttr>(constArr.getElts()))
+ return false;
+ if (auto elts = mlir::dyn_cast<mlir::ArrayAttr>(constArr.getElts()))
+ return llvm::any_of(elts, containsPoison);
+ }
+ return false;
+}
+
std::optional<mlir::Attribute>
lowerConstArrayAttr(cir::ConstArrayAttr constArr,
const mlir::TypeConverter *converter,
@@ -196,12 +212,8 @@ lowerConstArrayAttr(cir::ConstArrayAttr constArr,
type = arrayType.getElementType();
}
- if (auto eltsArr = mlir::dyn_cast<mlir::ArrayAttr>(constArr.getElts())) {
- for (mlir::Attribute elt : eltsArr) {
- if (mlir::isa<cir::PoisonAttr>(elt))
- return std::nullopt;
- }
- }
+ if (containsPoison(constArr))
+ return std::nullopt;
if (mlir::isa<mlir::StringAttr>(constArr.getElts()))
return convertStringAttrToDenseElementsAttr(constArr,
diff --git a/clang/test/CIR/CodeGen/global-pointer-array-fast-lowering.cpp b/clang/test/CIR/CodeGen/global-pointer-array-fast-lowering.cpp
index ecb4fb8e4722d..1252ccfa72de1 100644
--- a/clang/test/CIR/CodeGen/global-pointer-array-fast-lowering.cpp
+++ b/clang/test/CIR/CodeGen/global-pointer-array-fast-lowering.cpp
@@ -6,7 +6,11 @@
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
const char *names[] = { "a", "b", "c" };
-int len() { return sizeof(names)/sizeof(*names); }
+const int table[] = { 0, 1, 2, 3 };
+const int matrix[2][2] = { { 1, 2 }, { 3, 4 } };
+int len() {
+ return sizeof(names) / sizeof(*names) + table[0] + matrix[1][1];
+}
// CIR: cir.global {{.*}}@names = #cir.const_array<[#cir.global_view<@".str"> : !cir.ptr<!s8i>, #cir.global_view<@".str.1"> : !cir.ptr<!s8i>, #cir.global_view<@".str.2"> : !cir.ptr<!s8i>]>
@@ -14,3 +18,13 @@ int len() { return sizeof(names)/sizeof(*names); }
// LLVM-NOT: insertvalue
// OGCG: @names = {{.*}}global [3 x ptr] [ptr @.str{{.*}}, ptr @.str{{.*}}, ptr @.str{{.*}}]
+
+// CIR: cir.global {{.*}}table = #cir.const_array<[#cir.int<0> : !s32i, #cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.int<3> : !s32i]>
+// LLVM: {{.*}}table{{.*}} = {{.*}}constant [4 x i32] [i32 0, i32 1, i32 2, i32 3]
+// LLVM-NOT: insertvalue
+// OGCG: {{.*}}table{{.*}} = {{.*}}constant [4 x i32] [i32 0, i32 1, i32 2, i32 3]
+
+// CIR: cir.global {{.*}}matrix = #cir.const_array<
+// LLVM: {{.*}}matrix{{.*}} = {{.*}}constant [2 x [2 x i32]]
+// LLVM-NOT: insertvalue
+// OGCG: {{.*}}matrix{{.*}} = {{.*}}constant [2 x [2 x i32]]
diff --git a/clang/test/CIR/Lowering/const-array-bulk-lowering-fallbacks.cir b/clang/test/CIR/Lowering/const-array-bulk-lowering-fallbacks.cir
new file mode 100644
index 0000000000000..1e305e64c86d2
--- /dev/null
+++ b/clang/test/CIR/Lowering/const-array-bulk-lowering-fallbacks.cir
@@ -0,0 +1,29 @@
+// RUN: cir-opt %s --cir-to-llvm -o - | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+!s8i = !cir.int<s, 8>
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+ cir.global external @tz_arr =
+ #cir.const_array<"ab" : !cir.array<!s8i x 2>, trailing_zeros> :
+ !cir.array<!s8i x 8>
+
+ cir.global external target_address_space(1) @as1 = #cir.int<1> : !s32i
+ cir.global external @as_mismatch =
+ #cir.const_array<[#cir.global_view<@as1> : !cir.ptr<!s32i>]> :
+ !cir.array<!cir.ptr<!s32i> x 1>
+
+ cir.global external @matrix = #cir.const_array<[
+ #cir.const_array<[#cir.int<1> : !s32i, #cir.int<2> : !s32i]> : !cir.array<!s32i x 2>,
+ #cir.const_array<[#cir.int<3> : !s32i, #cir.int<4> : !s32i]> : !cir.array<!s32i x 2>
+ ]> : !cir.array<!cir.array<!s32i x 2> x 2>
+}
+
+// CHECK-LABEL: llvm.mlir.global external @tz_arr
+// CHECK: llvm.insertvalue
+
+// CHECK-LABEL: llvm.mlir.global external @as_mismatch
+// CHECK: llvm.insertvalue
+
+// CHECK-LABEL: llvm.mlir.global external @matrix(
+// CHECK-SAME: dense<
>From 8fa2f0d6a94afc6425a969d69ea6dcad8f2d0635 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Wed, 20 May 2026 13:03:47 -0700
Subject: [PATCH 4/4] [CIR] Add bool bulk globals and #198427 fallback tests
Extend bulk const_array global lowering to cir.bool leaves and add
review-requested coverage: multi-dim pointer-array insertvalue fallback
(@ptr2d) and bool constant-array codegen without insertvalue chains.
---
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 6 +++---
clang/lib/CIR/Lowering/LoweringHelpers.cpp | 13 +++++++++++-
.../global-pointer-array-fast-lowering.cpp | 9 ++++++++-
.../const-array-bulk-lowering-fallbacks.cir | 20 +++++++++++++++++++
4 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index b14d4ae3a9051..ba0b62ed710d1 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -1965,8 +1965,8 @@ static mlir::Type getConstArrayLeafType(mlir::Type ty) {
}
static bool isBulkLowerableConstArrayLeaf(mlir::Type leafTy) {
- return mlir::isa<cir::PointerType, cir::IntType, cir::FPTypeInterface>(
- leafTy);
+ return mlir::isa<cir::PointerType, cir::IntType, cir::BoolType,
+ cir::FPTypeInterface>(leafTy);
}
bool hasTrailingZeros(cir::ConstArrayAttr attr) {
@@ -2530,7 +2530,7 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite(
// whole initializer as one aggregate attribute (no insertvalue
// region). Skip arrays with trailing-zero compression: those need
// per-element zero/undef setup. Leaf type must match what
- // lowerConstArrayAttr handles (pointers, integers, floats today).
+ // lowerConstArrayAttr handles (pointers, integers, bools, floats).
if (!hasTrailingZeros(constArr) &&
isBulkLowerableConstArrayLeaf(
getConstArrayLeafType(constArr.getType()))) {
diff --git a/clang/lib/CIR/Lowering/LoweringHelpers.cpp b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
index a89816949ef43..3f6b9ac0dcdf4 100644
--- a/clang/lib/CIR/Lowering/LoweringHelpers.cpp
+++ b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
@@ -35,7 +35,8 @@ convertStringAttrToDenseElementsAttr(cir::ConstArrayAttr attr,
}
template <> mlir::APInt getZeroInitFromType(mlir::Type ty) {
- assert(mlir::isa<cir::IntType>(ty) && "expected int type");
+ if (mlir::isa<cir::BoolType>(ty))
+ return mlir::APInt::getZero(1);
const auto intTy = mlir::cast<cir::IntType>(ty);
return mlir::APInt::getZero(intTy.getWidth());
}
@@ -80,6 +81,12 @@ void convertToDenseElementsAttrImpl(
auto arrayAttr = mlir::cast<mlir::ArrayAttr>(attr.getElts());
for (auto eltAttr : arrayAttr) {
+ if (auto boolAttr = mlir::dyn_cast<cir::BoolAttr>(eltAttr)) {
+ if constexpr (std::is_same_v<StorageTy, mlir::APInt>) {
+ values[currentIndex++] = mlir::APInt(1, (uint64_t)boolAttr.getValue());
+ continue;
+ }
+ }
if (auto valueAttr = mlir::dyn_cast<AttrTy>(eltAttr)) {
values[currentIndex++] = valueAttr.getValue();
continue;
@@ -222,6 +229,10 @@ lowerConstArrayAttr(cir::ConstArrayAttr constArr,
return convertToDenseElementsAttr<cir::IntAttr, mlir::APInt>(
constArr, dims, type, converter->convertType(type));
+ if (mlir::isa<cir::BoolType>(type))
+ return convertToDenseElementsAttr<cir::IntAttr, mlir::APInt>(
+ constArr, dims, type, converter->convertType(type));
+
if (mlir::isa<cir::FPTypeInterface>(type))
return convertToDenseElementsAttr<cir::FPAttr, mlir::APFloat>(
constArr, dims, type, converter->convertType(type));
diff --git a/clang/test/CIR/CodeGen/global-pointer-array-fast-lowering.cpp b/clang/test/CIR/CodeGen/global-pointer-array-fast-lowering.cpp
index 1252ccfa72de1..8cab446ad63ac 100644
--- a/clang/test/CIR/CodeGen/global-pointer-array-fast-lowering.cpp
+++ b/clang/test/CIR/CodeGen/global-pointer-array-fast-lowering.cpp
@@ -8,8 +8,10 @@
const char *names[] = { "a", "b", "c" };
const int table[] = { 0, 1, 2, 3 };
const int matrix[2][2] = { { 1, 2 }, { 3, 4 } };
+const bool flags[] = { true, false, true };
int len() {
- return sizeof(names) / sizeof(*names) + table[0] + matrix[1][1];
+ return sizeof(names) / sizeof(*names) + table[0] + matrix[1][1] +
+ flags[2];
}
// CIR: cir.global {{.*}}@names = #cir.const_array<[#cir.global_view<@".str"> : !cir.ptr<!s8i>, #cir.global_view<@".str.1"> : !cir.ptr<!s8i>, #cir.global_view<@".str.2"> : !cir.ptr<!s8i>]>
@@ -28,3 +30,8 @@ int len() {
// LLVM: {{.*}}matrix{{.*}} = {{.*}}constant [2 x [2 x i32]]
// LLVM-NOT: insertvalue
// OGCG: {{.*}}matrix{{.*}} = {{.*}}constant [2 x [2 x i32]]
+
+// CIR: cir.global {{.*}}flags = #cir.const_array<[#true, #false, #true]> : !cir.array<!cir.bool x 3>
+// LLVM: {{.*}}flags{{.*}} = {{.*}}constant [3 x i8]
+// LLVM-NOT: insertvalue
+// OGCG: {{.*}}flags{{.*}} = {{.*}}constant [3 x i8]
diff --git a/clang/test/CIR/Lowering/const-array-bulk-lowering-fallbacks.cir b/clang/test/CIR/Lowering/const-array-bulk-lowering-fallbacks.cir
index 1e305e64c86d2..41864da616e3c 100644
--- a/clang/test/CIR/Lowering/const-array-bulk-lowering-fallbacks.cir
+++ b/clang/test/CIR/Lowering/const-array-bulk-lowering-fallbacks.cir
@@ -17,6 +17,23 @@ module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
#cir.const_array<[#cir.int<1> : !s32i, #cir.int<2> : !s32i]> : !cir.array<!s32i x 2>,
#cir.const_array<[#cir.int<3> : !s32i, #cir.int<4> : !s32i]> : !cir.array<!s32i x 2>
]> : !cir.array<!cir.array<!s32i x 2> x 2>
+
+ cir.global "private" constant cir_private dso_local @s0 =
+ #cir.const_array<"x" : !cir.array<!s8i x 1>, trailing_zeros> :
+ !cir.array<!s8i x 2>
+ cir.global "private" constant cir_private dso_local @s1 =
+ #cir.const_array<"y" : !cir.array<!s8i x 1>, trailing_zeros> :
+ !cir.array<!s8i x 2>
+ cir.global external @ptr2d = #cir.const_array<[
+ #cir.const_array<[
+ #cir.global_view<@s0> : !cir.ptr<!s8i>,
+ #cir.global_view<@s1> : !cir.ptr<!s8i>
+ ]> : !cir.array<!cir.ptr<!s8i> x 2>,
+ #cir.const_array<[
+ #cir.global_view<@s0> : !cir.ptr<!s8i>,
+ #cir.global_view<@s1> : !cir.ptr<!s8i>
+ ]> : !cir.array<!cir.ptr<!s8i> x 2>
+ ]> : !cir.array<!cir.array<!cir.ptr<!s8i> x 2> x 2>
}
// CHECK-LABEL: llvm.mlir.global external @tz_arr
@@ -27,3 +44,6 @@ module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
// CHECK-LABEL: llvm.mlir.global external @matrix(
// CHECK-SAME: dense<
+
+// CHECK-LABEL: llvm.mlir.global external @ptr2d
+// CHECK: llvm.insertvalue
More information about the cfe-commits
mailing list