[flang-commits] [flang] [flang][CodeGen] Fold array/struct global initializers to dense constants (PR #226520)

John Otken via flang-commits flang-commits at lists.llvm.org
Mon Oct 26 05:54:20 PDT 2026


https://github.com/jotken updated https://github.com/llvm/llvm-project/pull/226520

>From 8282f946068e7c884219f2cfb1f04323997a29e5 Mon Sep 17 00:00:00 2001
From: John Otken <john at otken.com>
Date: Fri, 25 Sep 2026 10:39:56 -0500
Subject: [PATCH 1/2] [flang][CodeGen] Fold array/struct global initializers to
 dense constants

Initializing a large COMMON block array with a partial DATA statement
produced a long chain of fir.insert_value/fir.insert_on_range ops that
GlobalOpConversion lowered one-by-one, making compilation of programs
with big COMMON blocks extremely slow (issue #209393).

Add folding helpers to GlobalOpConversion that walk the initializer
insert chain a single time and build an equivalent llvm.mlir constant:

- foldScalarArrayConstant: collects all elements of a scalar array
  (handling fir.insert_value, fir.insert_on_range, and logical
  fir.convert normalization) into a DenseElementsAttr, emitting a
  bodyless global when an explicit initializer is present.

- foldStructMemberConstant / foldGlobalStructInitializer: fold tuple
  (COMMON block) globals whose members are scalars or scalar arrays,
  emitting a small region that builds the struct with insertvalue.

Globals with only zero/undef initializers, non-scalar element types,
or derived-type/named struct fields fall back to the original lowering.

This reduces compile time for the affected COMMON block cases from many
seconds to well under a second while producing identical initializers.

Fixes #209393

Co-authored-by: John Otken john.otken at hpe.com
Assisted-by: Copilot and Claude Opus 4.8.
---
 flang/lib/Optimizer/CodeGen/CodeGen.cpp       | 440 ++++++++++++++++--
 flang/test/Fir/convert-to-llvm.fir            |   7 +-
 flang/test/Fir/global-constant-fold.fir       | 153 ++++++
 flang/test/Fir/global-initialization.fir      |  27 +-
 flang/test/Fir/omp-declare-target-data.fir    |   2 +-
 .../Integration/common-block-large-init.f90   |  15 +
 6 files changed, 576 insertions(+), 68 deletions(-)
 create mode 100644 flang/test/Fir/global-constant-fold.fir
 create mode 100644 flang/test/Integration/common-block-large-init.f90

diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index 1962beb70174ba..39faee66117430 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -3790,6 +3790,326 @@ static inline bool attributeTypeIsCompatible(mlir::MLIRContext *ctx,
 }
 #endif
 
+/// Fold the initializer body of a scalar array `fir.global` into a single
+/// dense constant attribute, walking the region exactly once.
+///
+/// The body of a `fir.global` for a constant array is a chain of
+/// `fir.insert_value` (single elements) and `fir.insert_on_range` (contiguous
+/// runs of equal elements) operations, rooted at a `fir.undefined` or
+/// `fir.zero_bits`, and terminated by `fir.has_value`. Lowering each
+/// `fir.insert_on_range` to an `llvm.insertvalue` chain (as the fallback does)
+/// is O(N) operations that are then O(N^2) to fold back into a constant when
+/// generating LLVM IR. This is pathologically slow for large arrays that are
+/// only partially initialized (e.g. a big COMMON block array set by a DATA
+/// statement on a handful of leading elements).
+///
+/// Instead of folding each `fir.insert_on_range` individually from its `seq`
+/// operand, this walks the whole region once, from `fir.has_value` back to the
+/// root `fir.undefined`/`fir.zero_bits`, filling a single flat element vector.
+/// It returns a `DenseElementsAttr` that can be used directly as the
+/// initializer of a bodyless `llvm.mlir.global`, or failure if the region is
+/// not a foldable scalar array constant (in which case the caller falls back
+/// to the regular per-operation lowering).
+///
+/// Only arrays whose element lowers to a scalar integer or floating point type
+/// are handled here: these are the ones representable with a
+/// `DenseElementsAttr`. Initializers involving pointers, descriptors
+/// (`fir.box`) or derived-type element values are intentionally left to the
+/// fallback path, as flang has no attribute representation for descriptor
+/// values and cannot always emit constant attributes for them.
+///
+/// `arrayVal` is the array value whose defining chain is walked (typically the
+/// operand of `fir.has_value`, or the value inserted into a struct member).
+/// `llvmArrType` is its lowered `!llvm.array<...>` type. When
+/// `requireExplicitInit` is set, an array that has no explicit element
+/// initialization at all (a bare `fir.zero_bits`/`fir.undefined`) is left to
+/// the regular lowering: there is no long insertvalue chain to collapse in that
+/// case, and a zero/undef global is better emitted as such.
+static llvm::FailureOr<mlir::DenseElementsAttr>
+foldScalarArrayConstant(mlir::Value arrayVal, mlir::Type llvmArrType,
+                        bool requireExplicitInit = true) {
+  auto seqTy = mlir::dyn_cast<fir::SequenceType>(arrayVal.getType());
+  if (!seqTy || seqTy.hasDynamicExtents())
+    return llvm::failure();
+  llvm::ArrayRef<int64_t> firShape = seqTy.getShape();
+  if (firShape.empty())
+    return llvm::failure();
+
+  // The element must lower to a scalar integer or floating point type for the
+  // whole array to be representable with a DenseElementsAttr. Peel exactly one
+  // llvm.array level per Fortran dimension so aggregate elements (character,
+  // complex, derived types, descriptors) are rejected.
+  mlir::Type llvmEleTy = llvmArrType;
+  for (size_t i = 0, rank = firShape.size(); i < rank; ++i) {
+    auto arrTy = mlir::dyn_cast<mlir::LLVM::LLVMArrayType>(llvmEleTy);
+    if (!arrTy)
+      return llvm::failure();
+    llvmEleTy = arrTy.getElementType();
+  }
+  if (!mlir::isa<mlir::IntegerType, mlir::FloatType>(llvmEleTy))
+    return llvm::failure();
+
+  // Total number of scalar elements and the per-dimension strides for a
+  // column-major flattening (first Fortran dimension varies fastest). This
+  // matches the coordinate order of fir.insert_value/fir.insert_on_range and
+  // makes an insert_on_range map to a contiguous [start, end] flat range.
+  int64_t total = 1;
+  llvm::SmallVector<int64_t> strides(firShape.size());
+  for (size_t i = 0; i < firShape.size(); ++i) {
+    if (firShape[i] < 0)
+      return llvm::failure();
+    strides[i] = total;
+    total *= firShape[i];
+  }
+  if (total == 0)
+    return llvm::failure();
+
+  auto flatIndex = [&](llvm::ArrayRef<int64_t> coord) -> int64_t {
+    int64_t idx = 0;
+    for (size_t i = 0; i < coord.size(); ++i)
+      idx += coord[i] * strides[i];
+    return idx;
+  };
+
+  // Fold a scalar element value being inserted into a typed attribute of the
+  // LLVM element type. Handles the fir.convert introduced for logical values,
+  // normalizing them to a canonical 0/1 exactly like ConvertOpConversion does.
+  // Returns a null attribute if the value is not a foldable scalar constant.
+  auto foldElement = [&](mlir::Value val) -> mlir::TypedAttr {
+    auto convertOp = val.getDefiningOp<fir::ConvertOp>();
+    mlir::Value src = convertOp ? convertOp.getValue() : val;
+    auto cst = src.getDefiningOp<mlir::arith::ConstantOp>();
+    if (!cst)
+      return {};
+    mlir::TypedAttr valueAttr = cst.getValue();
+    if (valueAttr.getType() == llvmEleTy)
+      return valueAttr;
+    // Only an integer<->logical fir.convert is folded here: it normalizes the
+    // operand to a canonical 0/1 (see ConvertOpConversion).
+    auto intAttr = mlir::dyn_cast<mlir::IntegerAttr>(valueAttr);
+    auto intType = mlir::dyn_cast<mlir::IntegerType>(llvmEleTy);
+    if (!intAttr || !intType || !convertOp ||
+        (!mlir::isa<fir::LogicalType>(convertOp.getType()) &&
+         !mlir::isa<fir::LogicalType>(convertOp.getValue().getType())))
+      return {};
+    return mlir::IntegerAttr::get(intType, intAttr.getValue().isZero() ? 0 : 1);
+  };
+
+  // Element attribute for each flat index. A null entry means "not yet set".
+  std::vector<mlir::Attribute> elements(total, mlir::Attribute{});
+  mlir::Attribute defaultValue; // value for elements never explicitly set
+
+  // Walk the initializer chain once, from the array value back to the root.
+  bool sawExplicitInit = false;
+  mlir::Value cur = arrayVal;
+  while (true) {
+    mlir::Operation *op = cur.getDefiningOp();
+    if (!op)
+      return llvm::failure();
+    if (mlir::isa<fir::UndefOp>(op))
+      break; // undefined root: every element must be set explicitly.
+    if (mlir::isa<fir::ZeroOp>(op)) {
+      if (auto intType = mlir::dyn_cast<mlir::IntegerType>(llvmEleTy))
+        defaultValue = mlir::IntegerAttr::get(intType, 0);
+      else
+        defaultValue = mlir::FloatAttr::get(llvmEleTy, 0.0);
+      break;
+    }
+    if (auto insert = mlir::dyn_cast<fir::InsertValueOp>(op)) {
+      llvm::SmallVector<int64_t> coord;
+      for (mlir::Attribute a : insert.getCoor()) {
+        auto intAttr = mlir::dyn_cast<mlir::IntegerAttr>(a);
+        if (!intAttr) // a named field: not a plain scalar array element.
+          return llvm::failure();
+        coord.push_back(intAttr.getInt());
+      }
+      if (coord.size() != firShape.size())
+        return llvm::failure();
+      mlir::TypedAttr eleAttr = foldElement(insert.getVal());
+      if (!eleAttr)
+        return llvm::failure();
+      int64_t idx = flatIndex(coord);
+      if (idx < 0 || idx >= total)
+        return llvm::failure();
+      // Walking backward: the first value seen for an index wins (it is the
+      // last insert in program order).
+      if (!elements[idx])
+        elements[idx] = eleAttr;
+      sawExplicitInit = true;
+      cur = insert.getAdt();
+      continue;
+    }
+    if (auto range = mlir::dyn_cast<fir::InsertOnRangeOp>(op)) {
+      mlir::TypedAttr eleAttr = foldElement(range.getVal());
+      if (!eleAttr)
+        return llvm::failure();
+      // coor holds [lb0, ub0, lb1, ub1, ...] in Fortran dimension order.
+      auto bounds = range.getCoor().getValues<int64_t>();
+      if (bounds.size() != 2 * firShape.size())
+        return llvm::failure();
+      llvm::SmallVector<int64_t> lb, ub;
+      for (size_t i = 0; i < firShape.size(); ++i) {
+        lb.push_back(bounds[2 * i]);
+        ub.push_back(bounds[2 * i + 1]);
+      }
+      int64_t start = flatIndex(lb);
+      int64_t end = flatIndex(ub);
+      if (start < 0 || end >= total || start > end)
+        return llvm::failure();
+      for (int64_t i = start; i <= end; ++i)
+        if (!elements[i])
+          elements[i] = eleAttr;
+      sawExplicitInit = true;
+      cur = range.getSeq();
+      continue;
+    }
+    // Any other producer (embox, address_of, aggregate insert, ...) cannot be
+    // folded to a scalar array constant.
+    return llvm::failure();
+  }
+
+  // A bare zero/undef array with no explicit element initialization has no
+  // insertvalue chain to collapse: leave it to the regular lowering.
+  if (requireExplicitInit && !sawExplicitInit)
+    return llvm::failure();
+
+  // Fill any element that was not explicitly initialized.
+  for (mlir::Attribute &a : elements)
+    if (!a) {
+      if (!defaultValue) // undefined element with no zero default.
+        return llvm::failure();
+      a = defaultValue;
+    }
+
+  // Build a DenseElementsAttr over a tensor shape matching the row-major LLVM
+  // array layout (the reverse of the Fortran dimension order). The element
+  // vector is already in that row-major order (first Fortran dimension
+  // fastest).
+  llvm::SmallVector<int64_t> tensorShape(firShape.rbegin(), firShape.rend());
+  auto tensorTy = mlir::RankedTensorType::get(tensorShape, llvmEleTy);
+  return mlir::DenseElementsAttr::get(tensorTy, elements);
+}
+
+/// A member of a struct/tuple `fir.global` initializer that has been folded to
+/// a single constant attribute, ready to be materialized as one
+/// `llvm.mlir.constant` and inserted into the aggregate.
+struct FoldedGlobalMember {
+  int64_t index;            // position of the member in the struct
+  mlir::Type llvmType;      // lowered LLVM type of the member
+  mlir::TypedAttr constant; // the folded constant (dense array or scalar)
+};
+
+/// Fold a scalar array value or a scalar value being inserted into a struct
+/// member into a single typed constant attribute. Returns a null attribute if
+/// the value is not foldable.
+static mlir::TypedAttr foldStructMemberConstant(mlir::Value val,
+                                                mlir::Type llvmMemberType) {
+  // Scalar array member: fold to a DenseElementsAttr. A struct member is
+  // folded even when it is entirely zero/undef, so that a data-bearing sibling
+  // member still takes the fast path (the whole struct is folded or none of
+  // it).
+  if (mlir::isa<mlir::LLVM::LLVMArrayType>(llvmMemberType)) {
+    llvm::FailureOr<mlir::DenseElementsAttr> folded =
+        foldScalarArrayConstant(val, llvmMemberType,
+                                /*requireExplicitInit=*/false);
+    if (llvm::succeeded(folded))
+      return *folded;
+    return {};
+  }
+  // Scalar member: fold to a scalar constant, looking through the fir.convert
+  // introduced for logical values (normalizing them to a canonical 0/1).
+  if (mlir::isa<mlir::IntegerType, mlir::FloatType>(llvmMemberType)) {
+    auto convertOp = val.getDefiningOp<fir::ConvertOp>();
+    mlir::Value src = convertOp ? convertOp.getValue() : val;
+    auto cst = src.getDefiningOp<mlir::arith::ConstantOp>();
+    if (!cst)
+      return {};
+    mlir::TypedAttr valueAttr = cst.getValue();
+    if (valueAttr.getType() == llvmMemberType)
+      return valueAttr;
+    auto intAttr = mlir::dyn_cast<mlir::IntegerAttr>(valueAttr);
+    auto intType = mlir::dyn_cast<mlir::IntegerType>(llvmMemberType);
+    if (!intAttr || !intType || !convertOp ||
+        (!mlir::isa<fir::LogicalType>(convertOp.getType()) &&
+         !mlir::isa<fir::LogicalType>(convertOp.getValue().getType())))
+      return {};
+    return mlir::IntegerAttr::get(intType, intAttr.getValue().isZero() ? 0 : 1);
+  }
+  return {};
+}
+
+/// Fold the initializer body of a struct/tuple `fir.global` whose members are
+/// scalar arrays or scalars (as generated for COMMON blocks) into a list of
+/// per-member constant attributes. The caller emits a small region that
+/// materializes each member with one `llvm.mlir.constant` and inserts it into
+/// the aggregate: a single struct-level `llvm.mlir.constant` cannot represent a
+/// struct containing an array.
+///
+/// Returns failure if the global is not such a struct, or if any member is not
+/// a foldable scalar array/scalar (e.g. it involves a descriptor, pointer or
+/// nested aggregate), in which case the caller falls back to the regular
+/// per-operation lowering.
+static llvm::FailureOr<llvm::SmallVector<FoldedGlobalMember>>
+foldGlobalStructInitializer(fir::GlobalOp global, mlir::Type llvmType) {
+  auto structTy = mlir::dyn_cast<mlir::LLVM::LLVMStructType>(llvmType);
+  if (!structTy || structTy.isOpaque())
+    return llvm::failure();
+  llvm::ArrayRef<mlir::Type> body = structTy.getBody();
+  if (body.empty())
+    return llvm::failure();
+
+  mlir::Region &region = global.getRegion();
+  if (region.empty())
+    return llvm::failure();
+  auto hasValue =
+      mlir::dyn_cast<fir::HasValueOp>(region.front().getTerminator());
+  if (!hasValue)
+    return llvm::failure();
+
+  // Walk the struct-level fir.insert_value chain once, back to the root
+  // fir.undefined. Each insert sets one member by its integer field index.
+  llvm::SmallVector<mlir::TypedAttr> members(body.size(), mlir::TypedAttr{});
+  mlir::Value cur = hasValue.getResval();
+  while (true) {
+    mlir::Operation *op = cur.getDefiningOp();
+    if (!op)
+      return llvm::failure();
+    if (mlir::isa<fir::UndefOp, fir::ZeroOp>(op))
+      break;
+    auto insert = mlir::dyn_cast<fir::InsertValueOp>(op);
+    if (!insert)
+      return llvm::failure();
+    // A struct field is addressed by a single integer index.
+    if (insert.getCoor().size() != 1)
+      return llvm::failure();
+    auto idxAttr = mlir::dyn_cast<mlir::IntegerAttr>(insert.getCoor()[0]);
+    if (!idxAttr)
+      return llvm::failure();
+    int64_t field = idxAttr.getInt();
+    if (field < 0 || field >= static_cast<int64_t>(body.size()))
+      return llvm::failure();
+    // Walking backward: the first value seen for a field wins.
+    if (!members[field]) {
+      mlir::TypedAttr memberAttr =
+          foldStructMemberConstant(insert.getVal(), body[field]);
+      if (!memberAttr)
+        return llvm::failure();
+      members[field] = memberAttr;
+    }
+    cur = insert.getAdt();
+  }
+
+  // Every member must have been explicitly set: there is no zero default here
+  // because a struct member may itself need a specific per-element value.
+  llvm::SmallVector<FoldedGlobalMember> folded;
+  for (auto [i, memberAttr] : llvm::enumerate(members)) {
+    if (!memberAttr)
+      return llvm::failure();
+    folded.push_back({static_cast<int64_t>(i), body[i], memberAttr});
+  }
+  return folded;
+}
+
 /// Lower `fir.global` operation to `llvm.global` operation.
 /// `fir.insert_on_range` operations are replaced with constant dense attribute
 /// if they are applied on the full range.
@@ -3818,6 +4138,31 @@ struct GlobalOpConversion : public fir::FIROpConversion<fir::GlobalOp> {
       tyAttr = this->lowerTy().convertBoxTypeAsStruct(boxType);
     auto loc = global.getLoc();
     mlir::Attribute initAttr = global.getInitVal().value_or(mlir::Attribute());
+    // Try to fold the initializer body into constant attributes so a compact
+    // llvm.mlir.global can be emitted, avoiding the O(N^2) materialization and
+    // folding of long insertvalue chains for large, partially initialized
+    // arrays. Two forms are handled:
+    //  - a top-level scalar array: a bodyless global with a dense init;
+    //  - a struct/tuple of scalar arrays/scalars (COMMON blocks): a region
+    //    that materializes each member with a single llvm.mlir.constant.
+    bool foldedInit = false;
+    llvm::SmallVector<FoldedGlobalMember> foldedMembers;
+    if (!initAttr && !global.getRegion().empty()) {
+      if (auto hasValue = mlir::dyn_cast<fir::HasValueOp>(
+              global.getRegion().front().getTerminator())) {
+        llvm::FailureOr<mlir::DenseElementsAttr> foldedArray =
+            foldScalarArrayConstant(hasValue.getResval(), tyAttr);
+        if (llvm::succeeded(foldedArray)) {
+          initAttr = *foldedArray;
+          foldedInit = true;
+        } else {
+          llvm::FailureOr<llvm::SmallVector<FoldedGlobalMember>> foldedStruct =
+              foldGlobalStructInitializer(global, tyAttr);
+          if (llvm::succeeded(foldedStruct))
+            foldedMembers = std::move(*foldedStruct);
+        }
+      }
+    }
     assert(attributeTypeIsCompatible(global.getContext(), initAttr, tyAttr));
     auto linkage = convertLinkage(global.getLinkage());
     auto isConst = global.getConstant().has_value();
@@ -3867,47 +4212,64 @@ struct GlobalOpConversion : public fir::FIROpConversion<fir::GlobalOp> {
     g->setDiscardableAttrs(global->getDiscardableAttrDictionary());
 
     auto &gr = g.getInitializerRegion();
-    rewriter.inlineRegionBefore(global.getRegion(), gr, gr.end());
-    if (!gr.empty()) {
-      // Replace insert_on_range with a constant dense attribute if the
-      // initialization is on the full range.
-      auto insertOnRangeOps = gr.front().getOps<fir::InsertOnRangeOp>();
-      for (auto insertOp : insertOnRangeOps) {
-        if (!insertOp.isFullRange())
-          continue;
-        // The dense attribute must use the converted element type of the
-        // array, not the type of whatever constant feeds the insertion.
-        mlir::Type elementType = convertType(insertOp.getType().getEleTy());
-        mlir::Value val = insertOp.getVal();
-        // Logical constants reach the insertion through a `fir.convert`.
-        auto convertOp = val.getDefiningOp<fir::ConvertOp>();
-        if (convertOp)
-          val = convertOp.getValue();
-        auto constant = val.getDefiningOp<mlir::arith::ConstantOp>();
-        if (!constant)
-          continue;
-        mlir::TypedAttr valueAttr = constant.getValue();
-        if (valueAttr.getType() != elementType) {
-          // Looking through the `fir.convert` leaves the constant with the
-          // source type. Only an integer<->logical conversion is folded here:
-          // it normalizes any integer operand to a canonical 0/1, see
-          // ConvertOpConversion. Any other mismatching conversion is left to
-          // the regular lowering.
-          auto intAttr = mlir::dyn_cast<mlir::IntegerAttr>(valueAttr);
-          auto intType = mlir::dyn_cast<mlir::IntegerType>(elementType);
-          if (!intAttr || !intType || !convertOp ||
-              (!mlir::isa<fir::LogicalType>(convertOp.getType()) &&
-               !mlir::isa<fir::LogicalType>(convertOp.getValue().getType())))
+    if (!foldedMembers.empty()) {
+      // Materialize the folded struct/tuple members: one llvm.mlir.constant per
+      // member inserted into an undef aggregate, then returned. A single
+      // struct-level llvm.mlir.constant cannot represent a struct containing an
+      // array, so the aggregate is assembled with llvm.insertvalue.
+      mlir::Block *block = rewriter.createBlock(&gr);
+      rewriter.setInsertionPointToStart(block);
+      mlir::Value agg = mlir::LLVM::UndefOp::create(rewriter, loc, tyAttr);
+      for (const FoldedGlobalMember &member : foldedMembers) {
+        mlir::Value cst = mlir::LLVM::ConstantOp::create(
+            rewriter, loc, member.llvmType, member.constant);
+        agg = mlir::LLVM::InsertValueOp::create(rewriter, loc, agg, cst,
+                                                member.index);
+      }
+      mlir::LLVM::ReturnOp::create(rewriter, loc, agg);
+    } else if (!foldedInit) {
+      rewriter.inlineRegionBefore(global.getRegion(), gr, gr.end());
+      if (!gr.empty()) {
+        // Replace insert_on_range with a constant dense attribute if the
+        // initialization is on the full range.
+        auto insertOnRangeOps = gr.front().getOps<fir::InsertOnRangeOp>();
+        for (auto insertOp : insertOnRangeOps) {
+          if (!insertOp.isFullRange())
+            continue;
+          // The dense attribute must use the converted element type of the
+          // array, not the type of whatever constant feeds the insertion.
+          mlir::Type elementType = convertType(insertOp.getType().getEleTy());
+          mlir::Value val = insertOp.getVal();
+          // Logical constants reach the insertion through a `fir.convert`.
+          auto convertOp = val.getDefiningOp<fir::ConvertOp>();
+          if (convertOp)
+            val = convertOp.getValue();
+          auto constant = val.getDefiningOp<mlir::arith::ConstantOp>();
+          if (!constant)
             continue;
-          valueAttr = mlir::IntegerAttr::get(
-              intType, intAttr.getValue().isZero() ? 0 : 1);
+          mlir::TypedAttr valueAttr = constant.getValue();
+          if (valueAttr.getType() != elementType) {
+            // Looking through the `fir.convert` leaves the constant with the
+            // source type. Only an integer<->logical conversion is folded here:
+            // it normalizes any integer operand to a canonical 0/1, see
+            // ConvertOpConversion. Any other mismatching conversion is left to
+            // the regular lowering.
+            auto intAttr = mlir::dyn_cast<mlir::IntegerAttr>(valueAttr);
+            auto intType = mlir::dyn_cast<mlir::IntegerType>(elementType);
+            if (!intAttr || !intType || !convertOp ||
+                (!mlir::isa<fir::LogicalType>(convertOp.getType()) &&
+                 !mlir::isa<fir::LogicalType>(convertOp.getValue().getType())))
+              continue;
+            valueAttr = mlir::IntegerAttr::get(
+                intType, intAttr.getValue().isZero() ? 0 : 1);
+          }
+          auto vecType =
+              mlir::VectorType::get(insertOp.getType().getShape(), elementType);
+          auto denseAttr = mlir::DenseElementsAttr::get(vecType, valueAttr);
+          rewriter.setInsertionPointAfter(insertOp);
+          rewriter.replaceOpWithNewOp<mlir::arith::ConstantOp>(
+              insertOp, convertType(insertOp.getType()), denseAttr);
         }
-        auto vecType =
-            mlir::VectorType::get(insertOp.getType().getShape(), elementType);
-        auto denseAttr = mlir::DenseElementsAttr::get(vecType, valueAttr);
-        rewriter.setInsertionPointAfter(insertOp);
-        rewriter.replaceOpWithNewOp<mlir::arith::ConstantOp>(
-            insertOp, convertType(insertOp.getType()), denseAttr);
       }
     }
 
diff --git a/flang/test/Fir/convert-to-llvm.fir b/flang/test/Fir/convert-to-llvm.fir
index d00aae198bf138..7261b4df690b23 100644
--- a/flang/test/Fir/convert-to-llvm.fir
+++ b/flang/test/Fir/convert-to-llvm.fir
@@ -107,13 +107,10 @@ fir.global internal @_QEmultiarray : !fir.array<32x32xi32> {
   fir.has_value %2 : !fir.array<32x32xi32>
 }
 
-// CHECK: llvm.mlir.global internal @_QEmultiarray() 
+// CHECK: llvm.mlir.global internal @_QEmultiarray(dense<1> : tensor<32x32xi32>) 
 // GENERIC-SAME: {addr_space = 0 : i32}
 // AMDGPU-SAME: {addr_space = 1 : i32}
-// CHECK-SAME: : !llvm.array<32 x array<32 x i32>> {
-// CHECK:   %[[CST:.*]] = llvm.mlir.constant(dense<1> : vector<32x32xi32>) : !llvm.array<32 x array<32 x i32>>
-// CHECK:   llvm.return %[[CST]] : !llvm.array<32 x array<32 x i32>>
-// CHECK: }
+// CHECK-SAME: : !llvm.array<32 x array<32 x i32>>
 
 // -----
 
diff --git a/flang/test/Fir/global-constant-fold.fir b/flang/test/Fir/global-constant-fold.fir
new file mode 100644
index 00000000000000..17b28b31f9a22b
--- /dev/null
+++ b/flang/test/Fir/global-constant-fold.fir
@@ -0,0 +1,153 @@
+// Test the single-pass folding of constant array `fir.global` initializer
+// bodies into dense constants. A partially initialized large array must be
+// lowered to a compact constant instead of a long `llvm.insertvalue` chain
+// (which is pathologically slow to materialize and fold back, see
+// https://github.com/llvm/llvm-project/issues/209393).
+
+// RUN: fir-opt --split-input-file --fir-to-llvm-ir %s | FileCheck %s
+
+// A 1D array initialized with a couple of leading elements and a trailing
+// range folds to a bodyless dense global.
+fir.global @oned : !fir.array<8xi32> {
+  %c7 = arith.constant 7 : i32
+  %c77 = arith.constant 77 : i32
+  %c0 = arith.constant 0 : i32
+  %0 = fir.undefined !fir.array<8xi32>
+  %1 = fir.insert_value %0, %c7, [0 : index] : (!fir.array<8xi32>, i32) -> !fir.array<8xi32>
+  %2 = fir.insert_value %1, %c77, [1 : index] : (!fir.array<8xi32>, i32) -> !fir.array<8xi32>
+  %3 = fir.insert_on_range %2, %c0 from (2) to (7) : (!fir.array<8xi32>, i32) -> !fir.array<8xi32>
+  fir.has_value %3 : !fir.array<8xi32>
+}
+
+// CHECK: llvm.mlir.global external @oned(dense<[7, 77, 0, 0, 0, 0, 0, 0]> : tensor<8xi32>) {{.*}} : !llvm.array<8 x i32>
+// CHECK-NOT: llvm.insertvalue
+
+// -----
+
+// A 2D array with a mix of a single-element insert and a range insert folds to
+// a dense global. The tensor shape is the reverse of the Fortran dimensions,
+// and the element order is column-major (first Fortran dimension fastest).
+fir.global @twod : !fir.array<3x2xi32> {
+  %c9 = arith.constant 9 : i32
+  %c0 = arith.constant 0 : i32
+  %0 = fir.undefined !fir.array<3x2xi32>
+  %1 = fir.insert_value %0, %c9, [0 : index, 0 : index] : (!fir.array<3x2xi32>, i32) -> !fir.array<3x2xi32>
+  %2 = fir.insert_on_range %1, %c0 from (1,0) to (2,1) : (!fir.array<3x2xi32>, i32) -> !fir.array<3x2xi32>
+  fir.has_value %2 : !fir.array<3x2xi32>
+}
+
+// CHECK: llvm.mlir.global external @twod(dense<{{\[}}[9, 0, 0], [0, 0, 0]]> : tensor<2x3xi32>) {{.*}} : !llvm.array<2 x array<3 x i32>>
+// CHECK-NOT: llvm.insertvalue
+
+// -----
+
+// A 3D array fully initialized by a single range folds to a dense splat.
+fir.global @threed : !fir.array<2x2x2xi32> {
+  %c5 = arith.constant 5 : i32
+  %0 = fir.undefined !fir.array<2x2x2xi32>
+  %1 = fir.insert_on_range %0, %c5 from (0,0,0) to (1,1,1) : (!fir.array<2x2x2xi32>, i32) -> !fir.array<2x2x2xi32>
+  fir.has_value %1 : !fir.array<2x2x2xi32>
+}
+
+// CHECK: llvm.mlir.global external @threed(dense<5> : tensor<2x2x2xi32>) {{.*}} : !llvm.array<2 x array<2 x array<2 x i32>>>
+// CHECK-NOT: llvm.insertvalue
+
+// -----
+
+// A real (floating point) array folds as well.
+fir.global @reals : !fir.array<4xf32> {
+  %c1 = arith.constant 1.5 : f32
+  %c0 = arith.constant 0.0 : f32
+  %0 = fir.undefined !fir.array<4xf32>
+  %1 = fir.insert_value %0, %c1, [0 : index] : (!fir.array<4xf32>, f32) -> !fir.array<4xf32>
+  %2 = fir.insert_on_range %1, %c0 from (1) to (3) : (!fir.array<4xf32>, f32) -> !fir.array<4xf32>
+  fir.has_value %2 : !fir.array<4xf32>
+}
+
+// CHECK: llvm.mlir.global external @reals(dense<[1.500000e+00, 0.000000e+00, 0.000000e+00, 0.000000e+00]> : tensor<4xf32>) {{.*}} : !llvm.array<4 x f32>
+// CHECK-NOT: llvm.insertvalue
+
+// -----
+
+// A logical array reaches the insertion through a fir.convert; the fold
+// normalizes the value to a canonical 0/1 of the lowered element type.
+fir.global @logicals : !fir.array<4x!fir.logical<4>> {
+  %true = arith.constant true
+  %0 = fir.undefined !fir.array<4x!fir.logical<4>>
+  %1 = fir.convert %true : (i1) -> !fir.logical<4>
+  %2 = fir.insert_on_range %0, %1 from (0) to (3) : (!fir.array<4x!fir.logical<4>>, !fir.logical<4>) -> !fir.array<4x!fir.logical<4>>
+  fir.has_value %2 : !fir.array<4x!fir.logical<4>>
+}
+
+// CHECK: llvm.mlir.global external @logicals(dense<1> : tensor<4xi32>) {{.*}} : !llvm.array<4 x i32>
+// CHECK-NOT: llvm.insertvalue
+
+// -----
+
+// A COMMON block lowers to a single-element tuple wrapping the array. A single
+// struct-level llvm.mlir.constant cannot represent a struct containing an
+// array, so the array member is materialized with one llvm.mlir.constant and
+// inserted into the aggregate.
+fir.global @common1 {alignment = 4 : i64} : tuple<!fir.array<8xi32>> {
+  %c7 = arith.constant 7 : i32
+  %c77 = arith.constant 77 : i32
+  %c0 = arith.constant 0 : i32
+  %0 = fir.zero_bits tuple<!fir.array<8xi32>>
+  %1 = fir.undefined !fir.array<8xi32>
+  %2 = fir.insert_value %1, %c7, [0 : index] : (!fir.array<8xi32>, i32) -> !fir.array<8xi32>
+  %3 = fir.insert_value %2, %c77, [1 : index] : (!fir.array<8xi32>, i32) -> !fir.array<8xi32>
+  %4 = fir.insert_on_range %3, %c0 from (2) to (7) : (!fir.array<8xi32>, i32) -> !fir.array<8xi32>
+  %5 = fir.insert_value %0, %4, [0 : index] : (tuple<!fir.array<8xi32>>, !fir.array<8xi32>) -> tuple<!fir.array<8xi32>>
+  fir.has_value %5 : tuple<!fir.array<8xi32>>
+}
+
+// CHECK: llvm.mlir.global external @common1() {{.*}} : !llvm.struct<(array<8 x i32>)> {
+// CHECK:   %[[UNDEF:.*]] = llvm.mlir.undef : !llvm.struct<(array<8 x i32>)>
+// CHECK:   %[[CST:.*]] = llvm.mlir.constant(dense<[7, 77, 0, 0, 0, 0, 0, 0]> : tensor<8xi32>) : !llvm.array<8 x i32>
+// CHECK:   %[[INS:.*]] = llvm.insertvalue %[[CST]], %[[UNDEF]][0] : !llvm.struct<(array<8 x i32>)>
+// CHECK:   llvm.return %[[INS]] : !llvm.struct<(array<8 x i32>)>
+// CHECK: }
+
+// -----
+
+// A COMMON block with several members: each member folds to one constant and
+// is inserted at its field index.
+fir.global @common2 : tuple<!fir.array<4xi32>, !fir.array<2xi32>> {
+  %c1 = arith.constant 1 : i32
+  %c0 = arith.constant 0 : i32
+  %c3 = arith.constant 3 : i32
+  %0 = fir.zero_bits tuple<!fir.array<4xi32>, !fir.array<2xi32>>
+  %1 = fir.undefined !fir.array<4xi32>
+  %2 = fir.insert_on_range %1, %c1 from (0) to (3) : (!fir.array<4xi32>, i32) -> !fir.array<4xi32>
+  %3 = fir.insert_value %0, %2, [0 : index] : (tuple<!fir.array<4xi32>, !fir.array<2xi32>>, !fir.array<4xi32>) -> tuple<!fir.array<4xi32>, !fir.array<2xi32>>
+  %4 = fir.undefined !fir.array<2xi32>
+  %5 = fir.insert_value %4, %c3, [0 : index] : (!fir.array<2xi32>, i32) -> !fir.array<2xi32>
+  %6 = fir.insert_value %5, %c0, [1 : index] : (!fir.array<2xi32>, i32) -> !fir.array<2xi32>
+  %7 = fir.insert_value %3, %6, [1 : index] : (tuple<!fir.array<4xi32>, !fir.array<2xi32>>, !fir.array<2xi32>) -> tuple<!fir.array<4xi32>, !fir.array<2xi32>>
+  fir.has_value %7 : tuple<!fir.array<4xi32>, !fir.array<2xi32>>
+}
+
+// CHECK: llvm.mlir.global external @common2() {{.*}} : !llvm.struct<(array<4 x i32>, array<2 x i32>)> {
+// CHECK:   %[[UNDEF:.*]] = llvm.mlir.undef : !llvm.struct<(array<4 x i32>, array<2 x i32>)>
+// CHECK:   %[[CST0:.*]] = llvm.mlir.constant(dense<1> : tensor<4xi32>) : !llvm.array<4 x i32>
+// CHECK:   %[[INS0:.*]] = llvm.insertvalue %[[CST0]], %[[UNDEF]][0] : !llvm.struct<(array<4 x i32>, array<2 x i32>)>
+// CHECK:   %[[CST1:.*]] = llvm.mlir.constant(dense<[3, 0]> : tensor<2xi32>) : !llvm.array<2 x i32>
+// CHECK:   %[[INS1:.*]] = llvm.insertvalue %[[CST1]], %[[INS0]][1] : !llvm.struct<(array<4 x i32>, array<2 x i32>)>
+// CHECK:   llvm.return %[[INS1]] : !llvm.struct<(array<4 x i32>, array<2 x i32>)>
+// CHECK: }
+
+// -----
+
+// An array whose leading elements are left undefined (no zero base) has no
+// constant to fold to for those elements: it must stay on the regular
+// per-operation lowering rather than being force-folded.
+fir.global @partialundef : !fir.array<8xi32> {
+  %c1 = arith.constant 1 : i32
+  %0 = fir.undefined !fir.array<8xi32>
+  %1 = fir.insert_on_range %0, %c1 from (5) to (7) : (!fir.array<8xi32>, i32) -> !fir.array<8xi32>
+  fir.has_value %1 : !fir.array<8xi32>
+}
+
+// CHECK: llvm.mlir.global external @partialundef() {{.*}} : !llvm.array<8 x i32> {
+// CHECK:   llvm.insertvalue
+// CHECK: }
diff --git a/flang/test/Fir/global-initialization.fir b/flang/test/Fir/global-initialization.fir
index a683b46de2541a..73cba152d0c202 100644
--- a/flang/test/Fir/global-initialization.fir
+++ b/flang/test/Fir/global-initialization.fir
@@ -8,12 +8,7 @@ fir.global internal @_QEmask : !fir.array<32xi32> {
   fir.has_value %2 : !fir.array<32xi32>
 }
 
-// CHECK: llvm.mlir.global internal @_QEmask() {addr_space = 0 : i32} : !llvm.array<32 x i32> {
-// CHECK:   [[VAL0:%.*]] = llvm.mlir.constant(1 : i32) : i32
-// CHECK:   [[VAL1:%.*]] = llvm.mlir.undef : !llvm.array<32 x i32>
-// CHECK:   [[VAL2:%.*]] = llvm.mlir.constant(dense<1> : vector<32xi32>) : !llvm.array<32 x i32>
-// CHECK:   llvm.return [[VAL2]] : !llvm.array<32 x i32>
-// CHECK: }
+// CHECK: llvm.mlir.global internal @_QEmask(dense<1> : tensor<32xi32>) {addr_space = 0 : i32} : !llvm.array<32 x i32>
 
 fir.global internal @_QEmultiarray : !fir.array<32x32xi32> {
   %c0_i32 = arith.constant 1 : i32
@@ -22,12 +17,7 @@ fir.global internal @_QEmultiarray : !fir.array<32x32xi32> {
   fir.has_value %2 : !fir.array<32x32xi32>
 }
 
-// CHECK: llvm.mlir.global internal @_QEmultiarray() {addr_space = 0 : i32} : !llvm.array<32 x array<32 x i32>> {
-// CHECK:   [[VAL0:%.*]] = llvm.mlir.constant(1 : i32) : i32
-// CHECK:   [[VAL1:%.*]] = llvm.mlir.undef : !llvm.array<32 x array<32 x i32>>
-// CHECK:   [[VAL2:%.*]] = llvm.mlir.constant(dense<1> : vector<32x32xi32>) : !llvm.array<32 x array<32 x i32>>
-// CHECK:   llvm.return [[VAL2]] : !llvm.array<32 x array<32 x i32>>
-// CHECK: }
+// CHECK: llvm.mlir.global internal @_QEmultiarray(dense<1> : tensor<32x32xi32>) {addr_space = 0 : i32} : !llvm.array<32 x array<32 x i32>>
 
 fir.global internal @_QEmasklogical : !fir.array<32768x!fir.logical<4>> {
   %true = arith.constant true
@@ -37,13 +27,7 @@ fir.global internal @_QEmasklogical : !fir.array<32768x!fir.logical<4>> {
   fir.has_value %2 : !fir.array<32768x!fir.logical<4>>
 }
 
-// CHECK: llvm.mlir.global internal @_QEmasklogical() {addr_space = 0 : i32} : !llvm.array<32768 x i32> {
-// CHECK:   [[VAL0:%.*]] = llvm.mlir.constant(true) : i1
-// CHECK:   [[VAL1:%.*]] = llvm.mlir.undef : !llvm.array<32768 x i32>
-// CHECK:   [[VAL2:%.*]] = llvm.mlir.constant(1 : i32) : i32
-// CHECK:   [[VAL3:%.*]] = llvm.mlir.constant(dense<1> : vector<32768xi32>) : !llvm.array<32768 x i32>
-// CHECK:   llvm.return [[VAL3]] : !llvm.array<32768 x i32>
-// CHECK: }
+// CHECK: llvm.mlir.global internal @_QEmasklogical(dense<1> : tensor<32768xi32>) {addr_space = 0 : i32} : !llvm.array<32768 x i32>
 
 // A logical conversion normalizes any integer operand to a canonical 0/1, not
 // just an `i1` one, so the full-range fold must apply here as well.
@@ -55,10 +39,7 @@ fir.global internal @_QEmasklogicalkind : !fir.array<8x!fir.logical<8>> {
   fir.has_value %2 : !fir.array<8x!fir.logical<8>>
 }
 
-// CHECK: llvm.mlir.global internal @_QEmasklogicalkind() {addr_space = 0 : i32} : !llvm.array<8 x i64> {
-// CHECK:   [[VAL2:%.*]] = llvm.mlir.constant(dense<1> : vector<8xi64>) : !llvm.array<8 x i64>
-// CHECK:   llvm.return [[VAL2]] : !llvm.array<8 x i64>
-// CHECK: }
+// CHECK: llvm.mlir.global internal @_QEmasklogicalkind(dense<1> : tensor<8xi64>) {addr_space = 0 : i32} : !llvm.array<8 x i64>
 
 fir.global internal @_QElookforme : !fir.type<_QTt{i:!fir.array<500xi32>,j:!fir.array<500xi32>}> {
   %c2_i32 = arith.constant 2 : i32
diff --git a/flang/test/Fir/omp-declare-target-data.fir b/flang/test/Fir/omp-declare-target-data.fir
index 539a7acf267f2d..9ad41f08e8b4a1 100644
--- a/flang/test/Fir/omp-declare-target-data.fir
+++ b/flang/test/Fir/omp-declare-target-data.fir
@@ -5,7 +5,7 @@ module attributes {omp.is_target_device = false} {
   // CHECK: llvm.mlir.global external @_QMtest_0Earray_1d(dense<[1, 2, 3]> : tensor<3xi32>) {{{.*}}omp.declare_target = #omp.declaretarget<device_type = any, capture_clause = link>{{.*}}} : !llvm.array<3 x i32>
   fir.global @_QMtest_0Earray_1d(dense<[1, 2, 3]> : tensor<3xi32>) {omp.declare_target = #omp.declaretarget<device_type = any, capture_clause = link>} : !fir.array<3xi32>
 
-  // CHECK: llvm.mlir.global external @_QMtest_0Earray_2d() {{{.*}}omp.declare_target = #omp.declaretarget<device_type = any, capture_clause = link>{{.*}}} : !llvm.array<2 x array<2 x i32>>
+  // CHECK: llvm.mlir.global external @_QMtest_0Earray_2d(dense<{{\[}}[1, 2], [3, 4]]> : tensor<2x2xi32>) {{{.*}}omp.declare_target = #omp.declaretarget<device_type = any, capture_clause = link>{{.*}}} : !llvm.array<2 x array<2 x i32>>
   fir.global @_QMtest_0Earray_2d {omp.declare_target = #omp.declaretarget<device_type = any, capture_clause = link>} : !fir.array<2x2xi32> {
     %0 = fir.undefined !fir.array<2x2xi32>
     %c1_i32 = arith.constant 1 : i32
diff --git a/flang/test/Integration/common-block-large-init.f90 b/flang/test/Integration/common-block-large-init.f90
new file mode 100644
index 00000000000000..4fd563707aff10
--- /dev/null
+++ b/flang/test/Integration/common-block-large-init.f90
@@ -0,0 +1,15 @@
+! Verify that a large COMMON block array initialized on only a few elements by a
+! DATA statement lowers to a compact constant global instead of an enormous
+! `insertvalue` chain (which is pathologically slow to generate, see
+! https://github.com/llvm/llvm-project/issues/209393).
+
+! RUN: %flang_fc1 -emit-llvm %s -o - | FileCheck %s
+
+block data
+  integer i(55000)
+  common /b/ i
+  data (i(j), j = 1, 2) / 7, 77 /
+end block data
+
+! CHECK: @b_ = {{.*}}global { [55000 x i32] } { [55000 x i32] [i32 7, i32 77, i32 0
+! CHECK-NOT: insertvalue

>From a2333c07f142cc683172db96a3d1a0340c30f9d5 Mon Sep 17 00:00:00 2001
From: John Otken <john at otken.com>
Date: Mon, 26 Oct 2026 07:46:28 -0500
Subject: [PATCH 2/2] [flang][CodeGen] Fold large partially-initialized global
 arrays in one pass

Combine duplicate code into static functions: foldConstantInitializer(), foldElement().
Use getZeroAttr() for zero constants.  Move structures to anonymous namespace.  Zero
uninitialized variables in COMMON blocks.  Add a rank-3 test case.
---
 flang/lib/Optimizer/CodeGen/CodeGen.cpp       | 428 +++++++++---------
 flang/test/Fir/global-constant-fold.fir       |  78 ++++
 .../Integration/common-block-large-init.f90   |  11 +
 3 files changed, 292 insertions(+), 225 deletions(-)

diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index 39faee66117430..fb7f75d9bbd318 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -3790,45 +3790,189 @@ static inline bool attributeTypeIsCompatible(mlir::MLIRContext *ctx,
 }
 #endif
 
-/// Fold the initializer body of a scalar array `fir.global` into a single
-/// dense constant attribute, walking the region exactly once.
-///
-/// The body of a `fir.global` for a constant array is a chain of
-/// `fir.insert_value` (single elements) and `fir.insert_on_range` (contiguous
-/// runs of equal elements) operations, rooted at a `fir.undefined` or
-/// `fir.zero_bits`, and terminated by `fir.has_value`. Lowering each
-/// `fir.insert_on_range` to an `llvm.insertvalue` chain (as the fallback does)
-/// is O(N) operations that are then O(N^2) to fold back into a constant when
-/// generating LLVM IR. This is pathologically slow for large arrays that are
-/// only partially initialized (e.g. a big COMMON block array set by a DATA
-/// statement on a handful of leading elements).
-///
-/// Instead of folding each `fir.insert_on_range` individually from its `seq`
-/// operand, this walks the whole region once, from `fir.has_value` back to the
-/// root `fir.undefined`/`fir.zero_bits`, filling a single flat element vector.
-/// It returns a `DenseElementsAttr` that can be used directly as the
-/// initializer of a bodyless `llvm.mlir.global`, or failure if the region is
-/// not a foldable scalar array constant (in which case the caller falls back
-/// to the regular per-operation lowering).
-///
-/// Only arrays whose element lowers to a scalar integer or floating point type
-/// are handled here: these are the ones representable with a
-/// `DenseElementsAttr`. Initializers involving pointers, descriptors
-/// (`fir.box`) or derived-type element values are intentionally left to the
-/// fallback path, as flang has no attribute representation for descriptor
-/// values and cannot always emit constant attributes for them.
-///
-/// `arrayVal` is the array value whose defining chain is walked (typically the
-/// operand of `fir.has_value`, or the value inserted into a struct member).
-/// `llvmArrType` is its lowered `!llvm.array<...>` type. When
-/// `requireExplicitInit` is set, an array that has no explicit element
-/// initialization at all (a bare `fir.zero_bits`/`fir.undefined`) is left to
-/// the regular lowering: there is no long insertvalue chain to collapse in that
-/// case, and a zero/undef global is better emitted as such.
-static llvm::FailureOr<mlir::DenseElementsAttr>
-foldScalarArrayConstant(mlir::Value arrayVal, mlir::Type llvmArrType,
-                        bool requireExplicitInit = true) {
-  auto seqTy = mlir::dyn_cast<fir::SequenceType>(arrayVal.getType());
+namespace {
+/// A member of a struct/tuple `fir.global` initializer that has been folded to
+/// a single constant attribute, ready to be materialized as one
+/// `llvm.mlir.constant` and inserted into the aggregate.
+struct FoldedGlobalMember {
+  int64_t index;            // position of the member in the struct
+  mlir::Type llvmType;      // lowered LLVM type of the member
+  mlir::TypedAttr constant; // the folded constant (dense array or scalar)
+};
+
+/// Result of folding a `fir.global` initializer body. On success exactly one
+/// form is populated: a scalar array folds to `arrayInit`; a struct/tuple of
+/// scalar arrays/scalars folds to `structMembers`.
+struct FoldedInitializer {
+  mlir::DenseElementsAttr arrayInit;                   // scalar array form
+  llvm::SmallVector<FoldedGlobalMember> structMembers; // struct/tuple form
+};
+} // namespace
+
+static llvm::FailureOr<FoldedInitializer>
+foldConstantInitializer(mlir::Value val, mlir::Type llvmType,
+                        bool requireExplicitInit = true);
+
+// Fold a scalar element value being inserted into a typed attribute of the
+// LLVM element type `llvmEleTy`. Handles the fir.convert introduced for logical
+// values, normalizing them to a canonical 0/1 exactly like ConvertOpConversion
+// does. Returns a null attribute if the value is not a foldable scalar
+// constant.
+static mlir::TypedAttr foldElement(mlir::Value val, mlir::Type llvmEleTy) {
+  auto convertOp = val.getDefiningOp<fir::ConvertOp>();
+  mlir::Value src = convertOp ? convertOp.getValue() : val;
+  auto cst = src.getDefiningOp<mlir::arith::ConstantOp>();
+  if (!cst)
+    return {};
+  mlir::TypedAttr valueAttr = cst.getValue();
+  if (valueAttr.getType() == llvmEleTy)
+    return valueAttr;
+  // Only an integer<->logical fir.convert is folded here: it normalizes the
+  // operand to a canonical 0/1 (see ConvertOpConversion).
+  auto intAttr = mlir::dyn_cast<mlir::IntegerAttr>(valueAttr);
+  auto intType = mlir::dyn_cast<mlir::IntegerType>(llvmEleTy);
+  if (!intAttr || !intType || !convertOp ||
+      (!mlir::isa<fir::LogicalType>(convertOp.getType()) &&
+       !mlir::isa<fir::LogicalType>(convertOp.getValue().getType())))
+    return {};
+  return mlir::IntegerAttr::get(intType, intAttr.getValue().isZero() ? 0 : 1);
+}
+
+/// Fold a scalar array value or a scalar value being inserted into a struct
+/// member into a single typed constant attribute. Returns a null attribute if
+/// the value is not foldable.
+static mlir::TypedAttr foldStructMemberConstant(mlir::Value val,
+                                                mlir::Type llvmMemberType) {
+  // Scalar array member: fold to a DenseElementsAttr. A struct member is
+  // folded even when it is entirely zero/undef, so that a data-bearing sibling
+  // member still takes the fast path (the whole struct is folded or none of
+  // it).
+  if (mlir::isa<mlir::LLVM::LLVMArrayType>(llvmMemberType)) {
+    llvm::FailureOr<FoldedInitializer> folded =
+        foldConstantInitializer(val, llvmMemberType,
+                                /*requireExplicitInit=*/false);
+    if (llvm::succeeded(folded))
+      return folded->arrayInit;
+    return {};
+  }
+  // Scalar member: fold to a scalar constant, looking through the fir.convert
+  // introduced for logical values (normalizing them to a canonical 0/1).
+  if (mlir::isa<mlir::IntegerType, mlir::FloatType>(llvmMemberType))
+    return foldElement(val, llvmMemberType);
+  return {};
+}
+
+/// Build the zero constant for a struct/tuple member that a `fir.zero_bits`
+/// root leaves implicitly zero-initialized (e.g. an uninitialized COMMON block
+/// variable). A scalar member yields a zero scalar attribute; a scalar array
+/// member yields a zero-splat `DenseElementsAttr` over a tensor whose shape
+/// matches the nested `llvm.array` layout. Returns a null attribute for member
+/// types that are not foldable (aggregate/descriptor elements).
+static mlir::TypedAttr makeZeroMemberConstant(mlir::Type llvmMemberType) {
+  if (mlir::isa<mlir::IntegerType, mlir::FloatType>(llvmMemberType))
+    return mlir::Builder(llvmMemberType.getContext())
+        .getZeroAttr(llvmMemberType);
+  if (mlir::isa<mlir::LLVM::LLVMArrayType>(llvmMemberType)) {
+    llvm::SmallVector<int64_t> shape;
+    mlir::Type eleTy = llvmMemberType;
+    while (auto arrTy = mlir::dyn_cast<mlir::LLVM::LLVMArrayType>(eleTy)) {
+      shape.push_back(arrTy.getNumElements());
+      eleTy = arrTy.getElementType();
+    }
+    if (!mlir::isa<mlir::IntegerType, mlir::FloatType>(eleTy))
+      return {};
+    auto tensorTy = mlir::RankedTensorType::get(shape, eleTy);
+    return mlir::DenseElementsAttr::get(
+        tensorTy, mlir::Builder(eleTy.getContext()).getZeroAttr(eleTy));
+  }
+  return {};
+}
+
+/// Fold the initializer body of a `fir.global` into constant attributes,
+/// walking the region once from `fir.has_value` back to its
+/// `fir.undefined`/`fir.zero_bits` root. A scalar array folds to a single
+/// `DenseElementsAttr`; a struct/tuple of scalar arrays/scalars (COMMON blocks)
+/// folds to a per-member list. This avoids the O(N^2) materialize-and-refold of
+/// the long `llvm.insertvalue` chain the fallback emits for large, partially
+/// initialized arrays. Only integer/float scalar elements are handled; anything
+/// else (pointers, `fir.box`, derived types) returns failure so the caller uses
+/// the regular lowering. `requireExplicitInit` leaves a bare zero/undef as-is.
+static llvm::FailureOr<FoldedInitializer>
+foldConstantInitializer(mlir::Value val, mlir::Type llvmType,
+                        bool requireExplicitInit) {
+  // Struct/tuple form (COMMON blocks): fold each member into its own constant
+  // attribute. The caller emits a small region that materializes each member
+  // with one `llvm.mlir.constant` and inserts it into the aggregate: a single
+  // struct-level `llvm.mlir.constant` cannot represent a struct containing an
+  // array.
+  if (auto structTy = mlir::dyn_cast<mlir::LLVM::LLVMStructType>(llvmType)) {
+    if (structTy.isOpaque())
+      return llvm::failure();
+    llvm::ArrayRef<mlir::Type> body = structTy.getBody();
+    if (body.empty())
+      return llvm::failure();
+
+    // Walk the struct-level fir.insert_value chain once, back to the root
+    // fir.undefined. Each insert sets one member by its integer field index.
+    llvm::SmallVector<mlir::TypedAttr> members(body.size(), mlir::TypedAttr{});
+    mlir::Value cur = val;
+    while (true) {
+      mlir::Operation *op = cur.getDefiningOp();
+      if (!op)
+        return llvm::failure();
+      if (mlir::isa<fir::UndefOp>(op))
+        break;
+      if (mlir::isa<fir::ZeroOp>(op)) {
+        // A fir.zero_bits root zero-initializes every member not set by an
+        // insert above (e.g. an uninitialized COMMON block variable). Without
+        // this, such a struct falls back to the slow per-operation lowering.
+        for (auto [i, memberTy] : llvm::enumerate(body)) {
+          if (!members[i]) {
+            members[i] = makeZeroMemberConstant(memberTy);
+            if (!members[i])
+              return llvm::failure();
+          }
+        }
+        break;
+      }
+      auto insert = mlir::dyn_cast<fir::InsertValueOp>(op);
+      if (!insert)
+        return llvm::failure();
+      // A struct field is addressed by a single integer index.
+      if (insert.getCoor().size() != 1)
+        return llvm::failure();
+      auto idxAttr = mlir::dyn_cast<mlir::IntegerAttr>(insert.getCoor()[0]);
+      if (!idxAttr)
+        return llvm::failure();
+      int64_t field = idxAttr.getInt();
+      if (field < 0 || field >= static_cast<int64_t>(body.size()))
+        return llvm::failure();
+      // Walking backward: the first value seen for a field wins.
+      if (!members[field]) {
+        mlir::TypedAttr memberAttr =
+            foldStructMemberConstant(insert.getVal(), body[field]);
+        if (!memberAttr)
+          return llvm::failure();
+        members[field] = memberAttr;
+      }
+      cur = insert.getAdt();
+    }
+
+    // Every member must have a constant by now: those set by an insert, plus
+    // any zero-defaulted above for a fir.zero_bits root. A member still unset
+    // here came from a fir.undefined root, which has no default, so the struct
+    // is left to the regular lowering.
+    llvm::SmallVector<FoldedGlobalMember> folded;
+    for (auto [i, memberAttr] : llvm::enumerate(members)) {
+      if (!memberAttr)
+        return llvm::failure();
+      folded.push_back({static_cast<int64_t>(i), body[i], memberAttr});
+    }
+    return FoldedInitializer{/*arrayInit=*/nullptr, std::move(folded)};
+  }
+
+  // Scalar array form: fold the whole array to a single dense constant.
+  auto seqTy = mlir::dyn_cast<fir::SequenceType>(val.getType());
   if (!seqTy || seqTy.hasDynamicExtents())
     return llvm::failure();
   llvm::ArrayRef<int64_t> firShape = seqTy.getShape();
@@ -3839,7 +3983,7 @@ foldScalarArrayConstant(mlir::Value arrayVal, mlir::Type llvmArrType,
   // whole array to be representable with a DenseElementsAttr. Peel exactly one
   // llvm.array level per Fortran dimension so aggregate elements (character,
   // complex, derived types, descriptors) are rejected.
-  mlir::Type llvmEleTy = llvmArrType;
+  mlir::Type llvmEleTy = llvmType;
   for (size_t i = 0, rank = firShape.size(); i < rank; ++i) {
     auto arrTy = mlir::dyn_cast<mlir::LLVM::LLVMArrayType>(llvmEleTy);
     if (!arrTy)
@@ -3871,37 +4015,13 @@ foldScalarArrayConstant(mlir::Value arrayVal, mlir::Type llvmArrType,
     return idx;
   };
 
-  // Fold a scalar element value being inserted into a typed attribute of the
-  // LLVM element type. Handles the fir.convert introduced for logical values,
-  // normalizing them to a canonical 0/1 exactly like ConvertOpConversion does.
-  // Returns a null attribute if the value is not a foldable scalar constant.
-  auto foldElement = [&](mlir::Value val) -> mlir::TypedAttr {
-    auto convertOp = val.getDefiningOp<fir::ConvertOp>();
-    mlir::Value src = convertOp ? convertOp.getValue() : val;
-    auto cst = src.getDefiningOp<mlir::arith::ConstantOp>();
-    if (!cst)
-      return {};
-    mlir::TypedAttr valueAttr = cst.getValue();
-    if (valueAttr.getType() == llvmEleTy)
-      return valueAttr;
-    // Only an integer<->logical fir.convert is folded here: it normalizes the
-    // operand to a canonical 0/1 (see ConvertOpConversion).
-    auto intAttr = mlir::dyn_cast<mlir::IntegerAttr>(valueAttr);
-    auto intType = mlir::dyn_cast<mlir::IntegerType>(llvmEleTy);
-    if (!intAttr || !intType || !convertOp ||
-        (!mlir::isa<fir::LogicalType>(convertOp.getType()) &&
-         !mlir::isa<fir::LogicalType>(convertOp.getValue().getType())))
-      return {};
-    return mlir::IntegerAttr::get(intType, intAttr.getValue().isZero() ? 0 : 1);
-  };
-
   // Element attribute for each flat index. A null entry means "not yet set".
   std::vector<mlir::Attribute> elements(total, mlir::Attribute{});
   mlir::Attribute defaultValue; // value for elements never explicitly set
 
   // Walk the initializer chain once, from the array value back to the root.
   bool sawExplicitInit = false;
-  mlir::Value cur = arrayVal;
+  mlir::Value cur = val;
   while (true) {
     mlir::Operation *op = cur.getDefiningOp();
     if (!op)
@@ -3909,10 +4029,8 @@ foldScalarArrayConstant(mlir::Value arrayVal, mlir::Type llvmArrType,
     if (mlir::isa<fir::UndefOp>(op))
       break; // undefined root: every element must be set explicitly.
     if (mlir::isa<fir::ZeroOp>(op)) {
-      if (auto intType = mlir::dyn_cast<mlir::IntegerType>(llvmEleTy))
-        defaultValue = mlir::IntegerAttr::get(intType, 0);
-      else
-        defaultValue = mlir::FloatAttr::get(llvmEleTy, 0.0);
+      defaultValue =
+          mlir::Builder(llvmEleTy.getContext()).getZeroAttr(llvmEleTy);
       break;
     }
     if (auto insert = mlir::dyn_cast<fir::InsertValueOp>(op)) {
@@ -3925,7 +4043,7 @@ foldScalarArrayConstant(mlir::Value arrayVal, mlir::Type llvmArrType,
       }
       if (coord.size() != firShape.size())
         return llvm::failure();
-      mlir::TypedAttr eleAttr = foldElement(insert.getVal());
+      mlir::TypedAttr eleAttr = foldElement(insert.getVal(), llvmEleTy);
       if (!eleAttr)
         return llvm::failure();
       int64_t idx = flatIndex(coord);
@@ -3940,7 +4058,7 @@ foldScalarArrayConstant(mlir::Value arrayVal, mlir::Type llvmArrType,
       continue;
     }
     if (auto range = mlir::dyn_cast<fir::InsertOnRangeOp>(op)) {
-      mlir::TypedAttr eleAttr = foldElement(range.getVal());
+      mlir::TypedAttr eleAttr = foldElement(range.getVal(), llvmEleTy);
       if (!eleAttr)
         return llvm::failure();
       // coor holds [lb0, ub0, lb1, ub1, ...] in Fortran dimension order.
@@ -3987,127 +4105,8 @@ foldScalarArrayConstant(mlir::Value arrayVal, mlir::Type llvmArrType,
   // fastest).
   llvm::SmallVector<int64_t> tensorShape(firShape.rbegin(), firShape.rend());
   auto tensorTy = mlir::RankedTensorType::get(tensorShape, llvmEleTy);
-  return mlir::DenseElementsAttr::get(tensorTy, elements);
-}
-
-/// A member of a struct/tuple `fir.global` initializer that has been folded to
-/// a single constant attribute, ready to be materialized as one
-/// `llvm.mlir.constant` and inserted into the aggregate.
-struct FoldedGlobalMember {
-  int64_t index;            // position of the member in the struct
-  mlir::Type llvmType;      // lowered LLVM type of the member
-  mlir::TypedAttr constant; // the folded constant (dense array or scalar)
-};
-
-/// Fold a scalar array value or a scalar value being inserted into a struct
-/// member into a single typed constant attribute. Returns a null attribute if
-/// the value is not foldable.
-static mlir::TypedAttr foldStructMemberConstant(mlir::Value val,
-                                                mlir::Type llvmMemberType) {
-  // Scalar array member: fold to a DenseElementsAttr. A struct member is
-  // folded even when it is entirely zero/undef, so that a data-bearing sibling
-  // member still takes the fast path (the whole struct is folded or none of
-  // it).
-  if (mlir::isa<mlir::LLVM::LLVMArrayType>(llvmMemberType)) {
-    llvm::FailureOr<mlir::DenseElementsAttr> folded =
-        foldScalarArrayConstant(val, llvmMemberType,
-                                /*requireExplicitInit=*/false);
-    if (llvm::succeeded(folded))
-      return *folded;
-    return {};
-  }
-  // Scalar member: fold to a scalar constant, looking through the fir.convert
-  // introduced for logical values (normalizing them to a canonical 0/1).
-  if (mlir::isa<mlir::IntegerType, mlir::FloatType>(llvmMemberType)) {
-    auto convertOp = val.getDefiningOp<fir::ConvertOp>();
-    mlir::Value src = convertOp ? convertOp.getValue() : val;
-    auto cst = src.getDefiningOp<mlir::arith::ConstantOp>();
-    if (!cst)
-      return {};
-    mlir::TypedAttr valueAttr = cst.getValue();
-    if (valueAttr.getType() == llvmMemberType)
-      return valueAttr;
-    auto intAttr = mlir::dyn_cast<mlir::IntegerAttr>(valueAttr);
-    auto intType = mlir::dyn_cast<mlir::IntegerType>(llvmMemberType);
-    if (!intAttr || !intType || !convertOp ||
-        (!mlir::isa<fir::LogicalType>(convertOp.getType()) &&
-         !mlir::isa<fir::LogicalType>(convertOp.getValue().getType())))
-      return {};
-    return mlir::IntegerAttr::get(intType, intAttr.getValue().isZero() ? 0 : 1);
-  }
-  return {};
-}
-
-/// Fold the initializer body of a struct/tuple `fir.global` whose members are
-/// scalar arrays or scalars (as generated for COMMON blocks) into a list of
-/// per-member constant attributes. The caller emits a small region that
-/// materializes each member with one `llvm.mlir.constant` and inserts it into
-/// the aggregate: a single struct-level `llvm.mlir.constant` cannot represent a
-/// struct containing an array.
-///
-/// Returns failure if the global is not such a struct, or if any member is not
-/// a foldable scalar array/scalar (e.g. it involves a descriptor, pointer or
-/// nested aggregate), in which case the caller falls back to the regular
-/// per-operation lowering.
-static llvm::FailureOr<llvm::SmallVector<FoldedGlobalMember>>
-foldGlobalStructInitializer(fir::GlobalOp global, mlir::Type llvmType) {
-  auto structTy = mlir::dyn_cast<mlir::LLVM::LLVMStructType>(llvmType);
-  if (!structTy || structTy.isOpaque())
-    return llvm::failure();
-  llvm::ArrayRef<mlir::Type> body = structTy.getBody();
-  if (body.empty())
-    return llvm::failure();
-
-  mlir::Region &region = global.getRegion();
-  if (region.empty())
-    return llvm::failure();
-  auto hasValue =
-      mlir::dyn_cast<fir::HasValueOp>(region.front().getTerminator());
-  if (!hasValue)
-    return llvm::failure();
-
-  // Walk the struct-level fir.insert_value chain once, back to the root
-  // fir.undefined. Each insert sets one member by its integer field index.
-  llvm::SmallVector<mlir::TypedAttr> members(body.size(), mlir::TypedAttr{});
-  mlir::Value cur = hasValue.getResval();
-  while (true) {
-    mlir::Operation *op = cur.getDefiningOp();
-    if (!op)
-      return llvm::failure();
-    if (mlir::isa<fir::UndefOp, fir::ZeroOp>(op))
-      break;
-    auto insert = mlir::dyn_cast<fir::InsertValueOp>(op);
-    if (!insert)
-      return llvm::failure();
-    // A struct field is addressed by a single integer index.
-    if (insert.getCoor().size() != 1)
-      return llvm::failure();
-    auto idxAttr = mlir::dyn_cast<mlir::IntegerAttr>(insert.getCoor()[0]);
-    if (!idxAttr)
-      return llvm::failure();
-    int64_t field = idxAttr.getInt();
-    if (field < 0 || field >= static_cast<int64_t>(body.size()))
-      return llvm::failure();
-    // Walking backward: the first value seen for a field wins.
-    if (!members[field]) {
-      mlir::TypedAttr memberAttr =
-          foldStructMemberConstant(insert.getVal(), body[field]);
-      if (!memberAttr)
-        return llvm::failure();
-      members[field] = memberAttr;
-    }
-    cur = insert.getAdt();
-  }
-
-  // Every member must have been explicitly set: there is no zero default here
-  // because a struct member may itself need a specific per-element value.
-  llvm::SmallVector<FoldedGlobalMember> folded;
-  for (auto [i, memberAttr] : llvm::enumerate(members)) {
-    if (!memberAttr)
-      return llvm::failure();
-    folded.push_back({static_cast<int64_t>(i), body[i], memberAttr});
-  }
-  return folded;
+  return FoldedInitializer{mlir::DenseElementsAttr::get(tensorTy, elements),
+                           {}};
 }
 
 /// Lower `fir.global` operation to `llvm.global` operation.
@@ -4150,16 +4149,15 @@ struct GlobalOpConversion : public fir::FIROpConversion<fir::GlobalOp> {
     if (!initAttr && !global.getRegion().empty()) {
       if (auto hasValue = mlir::dyn_cast<fir::HasValueOp>(
               global.getRegion().front().getTerminator())) {
-        llvm::FailureOr<mlir::DenseElementsAttr> foldedArray =
-            foldScalarArrayConstant(hasValue.getResval(), tyAttr);
-        if (llvm::succeeded(foldedArray)) {
-          initAttr = *foldedArray;
-          foldedInit = true;
-        } else {
-          llvm::FailureOr<llvm::SmallVector<FoldedGlobalMember>> foldedStruct =
-              foldGlobalStructInitializer(global, tyAttr);
-          if (llvm::succeeded(foldedStruct))
-            foldedMembers = std::move(*foldedStruct);
+        llvm::FailureOr<FoldedInitializer> folded =
+            foldConstantInitializer(hasValue.getResval(), tyAttr);
+        if (llvm::succeeded(folded)) {
+          if (folded->arrayInit) {
+            initAttr = folded->arrayInit;
+            foldedInit = true;
+          } else {
+            foldedMembers = std::move(folded->structMembers);
+          }
         }
       }
     }
@@ -4239,30 +4237,10 @@ struct GlobalOpConversion : public fir::FIROpConversion<fir::GlobalOp> {
           // The dense attribute must use the converted element type of the
           // array, not the type of whatever constant feeds the insertion.
           mlir::Type elementType = convertType(insertOp.getType().getEleTy());
-          mlir::Value val = insertOp.getVal();
-          // Logical constants reach the insertion through a `fir.convert`.
-          auto convertOp = val.getDefiningOp<fir::ConvertOp>();
-          if (convertOp)
-            val = convertOp.getValue();
-          auto constant = val.getDefiningOp<mlir::arith::ConstantOp>();
-          if (!constant)
+          mlir::TypedAttr valueAttr =
+              foldElement(insertOp.getVal(), elementType);
+          if (!valueAttr)
             continue;
-          mlir::TypedAttr valueAttr = constant.getValue();
-          if (valueAttr.getType() != elementType) {
-            // Looking through the `fir.convert` leaves the constant with the
-            // source type. Only an integer<->logical conversion is folded here:
-            // it normalizes any integer operand to a canonical 0/1, see
-            // ConvertOpConversion. Any other mismatching conversion is left to
-            // the regular lowering.
-            auto intAttr = mlir::dyn_cast<mlir::IntegerAttr>(valueAttr);
-            auto intType = mlir::dyn_cast<mlir::IntegerType>(elementType);
-            if (!intAttr || !intType || !convertOp ||
-                (!mlir::isa<fir::LogicalType>(convertOp.getType()) &&
-                 !mlir::isa<fir::LogicalType>(convertOp.getValue().getType())))
-              continue;
-            valueAttr = mlir::IntegerAttr::get(
-                intType, intAttr.getValue().isZero() ? 0 : 1);
-          }
           auto vecType =
               mlir::VectorType::get(insertOp.getType().getShape(), elementType);
           auto denseAttr = mlir::DenseElementsAttr::get(vecType, valueAttr);
diff --git a/flang/test/Fir/global-constant-fold.fir b/flang/test/Fir/global-constant-fold.fir
index 17b28b31f9a22b..e2109f04a89dae 100644
--- a/flang/test/Fir/global-constant-fold.fir
+++ b/flang/test/Fir/global-constant-fold.fir
@@ -54,6 +54,33 @@ fir.global @threed : !fir.array<2x2x2xi32> {
 
 // -----
 
+// A 3D array with distinct values inserted at asymmetric coordinates, with the
+// remaining elements zero-filled by a full-range insert. The fold flattens in
+// column-major order (first Fortran dimension fastest) and builds a tensor with
+// the reversed shape, so each value must land at its exact nested position:
+// coordinate (i0, i1, i2) maps to dense[i2][i1][i0].
+fir.global @threed_distinct : !fir.array<2x3x2xi32> {
+  %c0 = arith.constant 0 : i32
+  %c11 = arith.constant 11 : i32
+  %c22 = arith.constant 22 : i32
+  %c33 = arith.constant 33 : i32
+  %c44 = arith.constant 44 : i32
+  %c55 = arith.constant 55 : i32
+  %0 = fir.undefined !fir.array<2x3x2xi32>
+  %1 = fir.insert_on_range %0, %c0 from (0,0,0) to (1,2,1) : (!fir.array<2x3x2xi32>, i32) -> !fir.array<2x3x2xi32>
+  %2 = fir.insert_value %1, %c11, [0 : index, 0 : index, 0 : index] : (!fir.array<2x3x2xi32>, i32) -> !fir.array<2x3x2xi32>
+  %3 = fir.insert_value %2, %c22, [1 : index, 0 : index, 0 : index] : (!fir.array<2x3x2xi32>, i32) -> !fir.array<2x3x2xi32>
+  %4 = fir.insert_value %3, %c33, [0 : index, 2 : index, 0 : index] : (!fir.array<2x3x2xi32>, i32) -> !fir.array<2x3x2xi32>
+  %5 = fir.insert_value %4, %c44, [1 : index, 1 : index, 1 : index] : (!fir.array<2x3x2xi32>, i32) -> !fir.array<2x3x2xi32>
+  %6 = fir.insert_value %5, %c55, [0 : index, 0 : index, 1 : index] : (!fir.array<2x3x2xi32>, i32) -> !fir.array<2x3x2xi32>
+  fir.has_value %6 : !fir.array<2x3x2xi32>
+}
+
+// CHECK: llvm.mlir.global external @threed_distinct(dense<{{\[}}{{\[}}[11, 22], [0, 0], [33, 0]], {{\[}}[55, 0], [0, 44], [0, 0]]]> : tensor<2x3x2xi32>) {{.*}} : !llvm.array<2 x array<3 x array<2 x i32>>>
+// CHECK-NOT: llvm.insertvalue
+
+// -----
+
 // A real (floating point) array folds as well.
 fir.global @reals : !fir.array<4xf32> {
   %c1 = arith.constant 1.5 : f32
@@ -151,3 +178,54 @@ fir.global @partialundef : !fir.array<8xi32> {
 // CHECK: llvm.mlir.global external @partialundef() {{.*}} : !llvm.array<8 x i32> {
 // CHECK:   llvm.insertvalue
 // CHECK: }
+
+// -----
+
+// A COMMON block with an uninitialized trailing scalar (e.g. `integer m(N), k`
+// with only `m` set by a DATA statement). The fir.zero_bits root
+// zero-initializes any member not set by an insert, so the whole struct still
+// folds to constants instead of falling back to the pathologically slow
+// per-operation lowering (https://github.com/llvm/llvm-project/issues/209393).
+fir.global @common_uninit_scalar : tuple<!fir.array<8xi32>, i32> {
+  %c7 = arith.constant 7 : i32
+  %c77 = arith.constant 77 : i32
+  %c0 = arith.constant 0 : i32
+  %0 = fir.zero_bits tuple<!fir.array<8xi32>, i32>
+  %1 = fir.undefined !fir.array<8xi32>
+  %2 = fir.insert_value %1, %c7, [0 : index] : (!fir.array<8xi32>, i32) -> !fir.array<8xi32>
+  %3 = fir.insert_value %2, %c77, [1 : index] : (!fir.array<8xi32>, i32) -> !fir.array<8xi32>
+  %4 = fir.insert_on_range %3, %c0 from (2) to (7) : (!fir.array<8xi32>, i32) -> !fir.array<8xi32>
+  %5 = fir.insert_value %0, %4, [0 : index] : (tuple<!fir.array<8xi32>, i32>, !fir.array<8xi32>) -> tuple<!fir.array<8xi32>, i32>
+  fir.has_value %5 : tuple<!fir.array<8xi32>, i32>
+}
+
+// CHECK: llvm.mlir.global external @common_uninit_scalar() {{.*}} : !llvm.struct<(array<8 x i32>, i32)> {
+// CHECK:   %[[UNDEF:.*]] = llvm.mlir.undef : !llvm.struct<(array<8 x i32>, i32)>
+// CHECK:   %[[CST0:.*]] = llvm.mlir.constant(dense<[7, 77, 0, 0, 0, 0, 0, 0]> : tensor<8xi32>) : !llvm.array<8 x i32>
+// CHECK:   %[[INS0:.*]] = llvm.insertvalue %[[CST0]], %[[UNDEF]][0] : !llvm.struct<(array<8 x i32>, i32)>
+// CHECK:   %[[CST1:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK:   %[[INS1:.*]] = llvm.insertvalue %[[CST1]], %[[INS0]][1] : !llvm.struct<(array<8 x i32>, i32)>
+// CHECK:   llvm.return %[[INS1]] : !llvm.struct<(array<8 x i32>, i32)>
+// CHECK: }
+
+// -----
+
+// A COMMON block where an entire array member is left uninitialized: the
+// fir.zero_bits root zero-initializes it to a dense zero constant.
+fir.global @common_uninit_array : tuple<!fir.array<4xi32>, !fir.array<2xi32>> {
+  %c1 = arith.constant 1 : i32
+  %0 = fir.zero_bits tuple<!fir.array<4xi32>, !fir.array<2xi32>>
+  %1 = fir.undefined !fir.array<4xi32>
+  %2 = fir.insert_on_range %1, %c1 from (0) to (3) : (!fir.array<4xi32>, i32) -> !fir.array<4xi32>
+  %3 = fir.insert_value %0, %2, [0 : index] : (tuple<!fir.array<4xi32>, !fir.array<2xi32>>, !fir.array<4xi32>) -> tuple<!fir.array<4xi32>, !fir.array<2xi32>>
+  fir.has_value %3 : tuple<!fir.array<4xi32>, !fir.array<2xi32>>
+}
+
+// CHECK: llvm.mlir.global external @common_uninit_array() {{.*}} : !llvm.struct<(array<4 x i32>, array<2 x i32>)> {
+// CHECK:   %[[UNDEF:.*]] = llvm.mlir.undef : !llvm.struct<(array<4 x i32>, array<2 x i32>)>
+// CHECK:   %[[CST0:.*]] = llvm.mlir.constant(dense<1> : tensor<4xi32>) : !llvm.array<4 x i32>
+// CHECK:   %[[INS0:.*]] = llvm.insertvalue %[[CST0]], %[[UNDEF]][0] : !llvm.struct<(array<4 x i32>, array<2 x i32>)>
+// CHECK:   %[[CST1:.*]] = llvm.mlir.constant(dense<0> : tensor<2xi32>) : !llvm.array<2 x i32>
+// CHECK:   %[[INS1:.*]] = llvm.insertvalue %[[CST1]], %[[INS0]][1] : !llvm.struct<(array<4 x i32>, array<2 x i32>)>
+// CHECK:   llvm.return %[[INS1]] : !llvm.struct<(array<4 x i32>, array<2 x i32>)>
+// CHECK: }
diff --git a/flang/test/Integration/common-block-large-init.f90 b/flang/test/Integration/common-block-large-init.f90
index 4fd563707aff10..33f4a56a54f8c7 100644
--- a/flang/test/Integration/common-block-large-init.f90
+++ b/flang/test/Integration/common-block-large-init.f90
@@ -13,3 +13,14 @@
 
 ! CHECK: @b_ = {{.*}}global { [55000 x i32] } { [55000 x i32] [i32 7, i32 77, i32 0
 ! CHECK-NOT: insertvalue
+
+! A trailing uninitialized scalar in the same COMMON block (zero-initialized by
+! the fir.zero_bits root) must not defeat the fold.
+block data
+  integer m(55000), k
+  common /c/ m, k
+  data (m(j), j = 1, 2) / 7, 77 /
+end block data
+
+! CHECK: @c_ = {{.*}}global { [55000 x i32], [4 x i8] } { [55000 x i32] [i32 7, i32 77, i32 0{{.*}}, [4 x i8] zeroinitializer }
+! CHECK-NOT: insertvalue



More information about the flang-commits mailing list