[clang] [flang] [llvm] [flang] Add -finit-local= to initialize automatic variables (PR #216164)

Daniel Chen via cfe-commits cfe-commits at lists.llvm.org
Sat Sep 12 02:38:59 PDT 2026


https://github.com/DanielCChen updated https://github.com/llvm/llvm-project/pull/216164

>From 8d85c45b5b36982e810e08e69b038f3315979db8 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Thu, 13 Aug 2026 15:30:12 -0400
Subject: [PATCH 01/23] [flang] Add -finit-local= to initialize automatic
 variables

---
 clang/include/clang/Options/FlangOptions.td   |  18 +-
 clang/lib/Driver/ToolChains/Flang.cpp         |   4 +-
 flang/docs/ReleaseNotes.md                    |   5 +
 flang/include/flang/Lower/LoweringOptions.def |   6 +
 flang/include/flang/Lower/LoweringOptions.h   |  22 +
 flang/lib/Frontend/CompilerInvocation.cpp     |  27 +
 flang/lib/Lower/ConvertVariable.cpp           | 236 +++++++++
 flang/test/Driver/finit-local.f90             |  33 ++
 flang/test/Lower/finit-local-f128.f90         |  64 +++
 flang/test/Lower/finit-local.f90              | 496 ++++++++++++++++++
 flang/tools/bbc/bbc.cpp                       |  39 ++
 11 files changed, 947 insertions(+), 3 deletions(-)
 create mode 100644 flang/test/Driver/finit-local.f90
 create mode 100644 flang/test/Lower/finit-local-f128.f90
 create mode 100644 flang/test/Lower/finit-local.f90

diff --git a/clang/include/clang/Options/FlangOptions.td b/clang/include/clang/Options/FlangOptions.td
index 8fd0d78a2659c..def883e88509c 100644
--- a/clang/include/clang/Options/FlangOptions.td
+++ b/clang/include/clang/Options/FlangOptions.td
@@ -58,7 +58,6 @@ defm dump_parse_tree : BooleanFFlag<"dump-parse-tree">, Group<gfortran_Group>;
 defm external_blas : BooleanFFlag<"external-blas">, Group<gfortran_Group>;
 defm f2c : BooleanFFlag<"f2c">, Group<gfortran_Group>;
 defm frontend_optimize : BooleanFFlag<"frontend-optimize">, Group<gfortran_Group>;
-defm init_local_zero : BooleanFFlag<"init-local-zero">, Group<gfortran_Group>;
 defm integer_4_integer_8 : BooleanFFlag<"integer-4-integer-8">, Group<gfortran_Group>;
 defm max_identifier_length : BooleanFFlag<"max-identifier-length">, Group<gfortran_Group>;
 defm module_private : BooleanFFlag<"module-private">, Group<gfortran_Group>;
@@ -392,6 +391,23 @@ defm init_global_zero : BoolOptionWithoutMarshalling<"f", "init-global-zero",
   PosFlag<SetTrue, [], [], "Zero initialize globals without default initialization (default)">,
   NegFlag<SetFalse, [], [], "Do not zero initialize globals without default initialization">>;
 
+// -finit-local=<zero|nan|snan|0x<hex>>
+// Initialize automatic (local, stack) variables that have no explicit or
+// default initialization.  -finit-local-zero is a GFortran compatibility alias
+// for -finit-local=zero.
+def finit_local_EQ : Joined<["-"], "finit-local=">,
+  Group<f_Group>,
+  Visibility<[FC1Option, FlangOption]>,
+  HelpText<"Initialize local variables without explicit or default initialization. "
+           "Accepts: zero, nan, snan, or 0x<hex-byte>.">;
+
+def finit_local_zero : Flag<["-"], "finit-local-zero">,
+  Group<f_Group>,
+  Visibility<[FC1Option, FlangOption]>,
+  HelpText<"Zero-initialize local variables without explicit or default initialization "
+           "(alias for -finit-local=zero, GFortran compatibility)">,
+  Alias<finit_local_EQ>, AliasArgs<["zero"]>;
+
 def fno_realloc_lhs : Flag<["-"], "fno-realloc-lhs">, Group<f_Group>,
   HelpText<"An allocatable left-hand side of an intrinsic assignment is assumed to be allocated and match the shape/type of the right-hand side">;
 def frealloc_lhs : Flag<["-"], "frealloc-lhs">, Group<f_Group>,
diff --git a/clang/lib/Driver/ToolChains/Flang.cpp b/clang/lib/Driver/ToolChains/Flang.cpp
index 0e6858d3bb645..7f54be260397b 100644
--- a/clang/lib/Driver/ToolChains/Flang.cpp
+++ b/clang/lib/Driver/ToolChains/Flang.cpp
@@ -377,8 +377,8 @@ void Flang::addCodegenOptions(const ArgList &Args,
       {options::OPT_fdo_concurrent_to_openmp_EQ,
        options::OPT_fno_ppc_native_vec_elem_order,
        options::OPT_fppc_native_vec_elem_order, options::OPT_finit_global_zero,
-       options::OPT_fno_init_global_zero, options::OPT_frepack_arrays,
-       options::OPT_fno_repack_arrays,
+       options::OPT_fno_init_global_zero, options::OPT_finit_local_EQ,
+       options::OPT_frepack_arrays, options::OPT_fno_repack_arrays,
        options::OPT_frepack_arrays_contiguity_EQ,
        options::OPT_fstack_repack_arrays, options::OPT_fno_stack_repack_arrays,
        options::OPT_ftime_report, options::OPT_ftime_report_EQ,
diff --git a/flang/docs/ReleaseNotes.md b/flang/docs/ReleaseNotes.md
index 76a6b95dd3ecc..519969eea9e35 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -97,6 +97,11 @@ page](https://llvm.org/releases/).
 - Added `-gz` and `-gz=<format>` flags to enable compression of DWARF debug
   sections. Supported formats are `zlib`, `zstd`, and `none`.
 
+- Added `-finit-local=<val>` to initialize automatic (local, stack-allocated)
+  variables that have no explicit or default initialization. Accepted values
+  are `zero`, `nan`, `snan`, and `0x<hex-byte>` (e.g. `0xAA`). The gfortran
+  compatibility alias `-finit-local-zero` is equivalent to `-finit-local=zero`.
+
 ## Windows Support
 
 ## Fortran Language Changes in Flang
diff --git a/flang/include/flang/Lower/LoweringOptions.def b/flang/include/flang/Lower/LoweringOptions.def
index 61ccb2ac19bdd..43c48503f382d 100644
--- a/flang/include/flang/Lower/LoweringOptions.def
+++ b/flang/include/flang/Lower/LoweringOptions.def
@@ -97,5 +97,11 @@ ENUM_LOWERINGOPT(FPMaxminBehavior, Fortran::common::FPMaxminBehavior, 2, 0)
 /// 0 means no trapping. Bit values match IEEE_FLAG_TYPE encoding.
 ENUM_LOWERINGOPT(FPExceptionTraps, unsigned, 8, 0)
 
+/// Initialization mode for automatic variables that have no explicit or
+/// default initialization (-finit-local= / -finit-local-zero).
+/// Off by default.
+ENUM_LOWERINGOPT(InitLocalMode, Fortran::lower::InitLocalKind, 3,
+                 Fortran::lower::InitLocalKind::Off)
+
 #undef LOWERINGOPT
 #undef ENUM_LOWERINGOPT
diff --git a/flang/include/flang/Lower/LoweringOptions.h b/flang/include/flang/Lower/LoweringOptions.h
index d44d5f73eeb67..7f24c02c57c79 100644
--- a/flang/include/flang/Lower/LoweringOptions.h
+++ b/flang/include/flang/Lower/LoweringOptions.h
@@ -17,9 +17,20 @@
 
 #include "flang/Support/FPMaxminBehavior.h"
 #include "flang/Support/MathOptionsBase.h"
+#include <cstdint>
 
 namespace Fortran::lower {
 
+/// Initialization mode for automatic (local) variables without explicit
+/// or default initialization, selected via -finit-local=.
+enum class InitLocalKind {
+  Off,  ///< No initialization (default)
+  Zero, ///< Fill with 0x00 bytes
+  Hex,  ///< Fill with a user-supplied byte pattern
+  QNaN, ///< Quiet NaN for FP; 0xAA byte-splat for non-FP types
+  SNaN, ///< Signalling NaN for FP; 0xAA byte-splat for non-FP types
+};
+
 class LoweringOptionsBase {
 public:
 #define LOWERINGOPT(Name, Bits, Default) unsigned Name : Bits;
@@ -52,7 +63,18 @@ class LoweringOptions : public LoweringOptionsBase {
 
   Fortran::common::MathOptionsBase &getMathOptions() { return MathOptions; }
 
+  /// Returns the byte pattern used for -finit-local=0x<hex>.
+  uint8_t getInitLocalPattern() const { return InitLocalPattern; }
+  LoweringOptions &setInitLocalPattern(uint8_t V) {
+    InitLocalPattern = V;
+    return *this;
+  }
+
 private:
+  /// Byte pattern for -finit-local=0x<hex>. Only meaningful when
+  /// getInitLocalMode() == InitLocalKind::Hex.
+  uint8_t InitLocalPattern = 0;
+
   /// Options for handling/optimizing mathematical computations.
   Fortran::common::MathOptionsBase MathOptions;
 };
diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp
index b76b38ff6497f..09dbcf629e086 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -1788,6 +1788,33 @@ bool CompilerInvocation::createFromArgs(
   else
     invoc.loweringOpts.setInitGlobalZero(false);
 
+  // -finit-local=<zero|nan|snan|0x<hex>>  and  -finit-local-zero
+  // (-finit-local-zero is an alias that the driver already expands to
+  //  -finit-local=zero, so we only need to handle OPT_finit_local_EQ here.)
+  if (const llvm::opt::Arg *a =
+          args.getLastArg(clang::options::OPT_finit_local_EQ)) {
+    llvm::StringRef val = a->getValue();
+    if (val == "zero") {
+      invoc.loweringOpts.setInitLocalMode(Fortran::lower::InitLocalKind::Zero);
+    } else if (val == "nan") {
+      invoc.loweringOpts.setInitLocalMode(Fortran::lower::InitLocalKind::QNaN);
+    } else if (val == "snan") {
+      invoc.loweringOpts.setInitLocalMode(Fortran::lower::InitLocalKind::SNaN);
+    } else if (val.starts_with("0x") || val.starts_with("0X")) {
+      unsigned long long hexVal = 0;
+      if (val.drop_front(2).getAsInteger(16, hexVal) || hexVal > 0xFF) {
+        diags.Report(clang::diag::err_drv_invalid_value)
+            << a->getAsString(args) << val;
+      } else {
+        invoc.loweringOpts.setInitLocalMode(Fortran::lower::InitLocalKind::Hex);
+        invoc.loweringOpts.setInitLocalPattern(static_cast<uint8_t>(hexVal));
+      }
+    } else {
+      diags.Report(clang::diag::err_drv_invalid_value)
+          << a->getAsString(args) << val;
+    }
+  }
+
   // Preserve all the remark options requested, i.e. -Rpass, -Rpass-missed or
   // -Rpass-analysis. This will be used later when processing and outputting the
   // remarks generated by LLVM in ExecuteCompilerInvocation.cpp.
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 5d9fdf48bd067..98e6c9ccfc8ad 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -19,6 +19,7 @@
 #include "flang/Lower/ConvertConstant.h"
 #include "flang/Lower/ConvertExprToHLFIR.h"
 #include "flang/Lower/ConvertProcedureDesignator.h"
+#include "flang/Lower/LoweringOptions.h"
 #include "flang/Lower/Mangler.h"
 #include "flang/Lower/MultiImageFortran.h"
 #include "flang/Lower/OpenACC.h"
@@ -45,7 +46,10 @@
 #include "flang/Runtime/allocator-registry-consts.h"
 #include "flang/Semantics/tools.h"
 #include "flang/Semantics/type.h"
+#include "mlir/Dialect/Complex/IR/Complex.h"
 #include "mlir/Dialect/OpenACC/OpenACC.h"
+#include "llvm/ADT/APFloat.h"
+#include "llvm/ADT/APInt.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
@@ -1271,6 +1275,236 @@ getSafeRepackAttrs(Fortran::lower::AbstractConverter &converter) {
   return attrs.empty() ? mlir::ArrayAttr{} : builder.getArrayAttr(attrs);
 }
 
+//===----------------------------------------------------------------------===//
+// -finit-local= helpers
+//===----------------------------------------------------------------------===//
+
+/// Returns true when \p var is an automatic local variable eligible for
+/// -finit-local= initialization. Excluded: variables without a symbol,
+/// globals, dummy arguments, SAVE'd vars, ALLOCATABLE/POINTER, vars in
+/// an EQUIVALENCE set, and vars with explicit or default initialization.
+static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
+  if (!var.hasSymbol() || var.isGlobal())
+    return false;
+  const Fortran::semantics::Symbol &sym = var.getSymbol();
+  if (Fortran::semantics::IsDummy(sym))
+    return false;
+  if (Fortran::semantics::IsSaved(sym))
+    return false;
+  if (Fortran::semantics::IsAllocatableOrPointer(sym))
+    return false;
+  if (Fortran::lower::hasDefaultInitialization(sym))
+    return false;
+  if (const auto *obj =
+          sym.detailsIf<Fortran::semantics::ObjectEntityDetails>())
+    if (obj->init())
+      return false;
+  if (Fortran::semantics::FindEquivalenceSet(sym))
+    return false;
+  return true;
+}
+
+/// Build a constant whose every byte equals \p bytePat.
+/// FP types: bitcast from an integer splat. Complex: apply to both parts.
+/// Character: falls back to fir.zero_bits (see TODO). Derived types are
+/// handled by the caller before this function is reached.
+static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
+                                    mlir::Location loc, mlir::Type ty,
+                                    uint8_t bytePat) {
+  mlir::Type eleTy = fir::unwrapSequenceType(ty);
+
+  // Build an integer constant of the given bit width from a byte splat.
+  auto makeIntCst = [&](unsigned bits) -> mlir::Value {
+    llvm::APInt byteVal(8, bytePat);
+    llvm::APInt splat = llvm::APInt::getSplat(bits, byteVal);
+    mlir::Type intTy = builder.getIntegerType(bits);
+    return mlir::arith::ConstantOp::create(
+        builder, loc, intTy, builder.getIntegerAttr(intTy, splat));
+  };
+
+  if (auto fpTy = mlir::dyn_cast<mlir::FloatType>(eleTy)) {
+    unsigned bits = fpTy.getWidth();
+    mlir::Value intCst = makeIntCst(bits);
+    return mlir::arith::BitcastOp::create(builder, loc, fpTy, intCst);
+  }
+  if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(eleTy)) {
+    return makeIntCst(intTy.getWidth());
+  }
+  // Complex: apply the byte pattern to each (real, imag) part.
+  if (auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(eleTy)) {
+    mlir::Type partTy = cplxTy.getElementType();
+    mlir::Value partVal = genByteSplatInit(builder, loc, partTy, bytePat);
+    return mlir::complex::CreateOp::create(builder, loc, cplxTy, partVal,
+                                           partVal);
+  }
+  // TODO: CHARACTER falls back to zero; a future improvement should fill each
+  // storage unit with the byte pattern.
+  return fir::ZeroOp::create(builder, loc, eleTy);
+}
+
+/// Build a quiet or signalling NaN constant of the given FP type.
+/// The payload is all-ones (matching clang's initializationPatternFor() and
+/// the RFC spec), and the sign bit is set (negative NaN).
+static mlir::Value genFPNaNInit(fir::FirOpBuilder &builder, mlir::Location loc,
+                                mlir::FloatType fpTy, bool isSignalling) {
+  const llvm::fltSemantics &sem = fpTy.getFloatSemantics();
+  // All-ones payload (precision-1 mantissa bits), negative sign, per RFC.
+  llvm::APInt payload = llvm::APInt::getAllOnes(sem.precision - 1);
+  llvm::APFloat apf =
+      isSignalling ? llvm::APFloat::getSNaN(sem, /*Negative=*/true, &payload)
+                   : llvm::APFloat::getQNaN(sem, /*Negative=*/true, &payload);
+  return mlir::arith::ConstantFloatOp::create(builder, loc, fpTy, apf);
+}
+
+/// Emit a store of the -finit-local= pattern for a single scalar address.
+/// Complex types get NaN on both parts; other non-FP types use 0xAA byte-splat
+/// for nan/snan modes.
+static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
+                              mlir::Type ty, mlir::Value addr,
+                              Fortran::lower::InitLocalKind mode,
+                              uint8_t hexByte) {
+  mlir::Value val;
+  auto fpTy = mlir::dyn_cast<mlir::FloatType>(ty);
+  auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(ty);
+  switch (mode) {
+  case Fortran::lower::InitLocalKind::Zero:
+    val = fir::ZeroOp::create(builder, loc, ty);
+    break;
+  case Fortran::lower::InitLocalKind::Hex:
+    val = genByteSplatInit(builder, loc, ty, hexByte);
+    break;
+  case Fortran::lower::InitLocalKind::QNaN:
+    if (fpTy) {
+      val = genFPNaNInit(builder, loc, fpTy, /*signalling=*/false);
+    } else if (cplxTy) {
+      auto partFpTy = mlir::cast<mlir::FloatType>(cplxTy.getElementType());
+      mlir::Value nanPart =
+          genFPNaNInit(builder, loc, partFpTy, /*signalling=*/false);
+      val = mlir::complex::CreateOp::create(builder, loc, cplxTy, nanPart,
+                                            nanPart);
+    } else {
+      val = genByteSplatInit(builder, loc, ty, 0xAA);
+    }
+    break;
+  case Fortran::lower::InitLocalKind::SNaN:
+    if (fpTy) {
+      val = genFPNaNInit(builder, loc, fpTy, /*signalling=*/true);
+    } else if (cplxTy) {
+      auto partFpTy = mlir::cast<mlir::FloatType>(cplxTy.getElementType());
+      mlir::Value nanPart =
+          genFPNaNInit(builder, loc, partFpTy, /*signalling=*/true);
+      val = mlir::complex::CreateOp::create(builder, loc, cplxTy, nanPart,
+                                            nanPart);
+    } else {
+      val = genByteSplatInit(builder, loc, ty, 0xAA);
+    }
+    break;
+  default:
+    llvm_unreachable("unexpected InitLocalKind in genInitLocalStore");
+  }
+  fir::StoreOp::create(builder, loc, val, addr);
+}
+
+/// Initialize all storage of the local variable \p var per -finit-local= mode.
+/// Arrays use insert_on_range. Derived types walk fields for nan/snan/hex.
+/// Scalars store directly.
+static void genInitLocal(Fortran::lower::AbstractConverter &converter,
+                         const Fortran::lower::pft::Variable &var,
+                         Fortran::lower::SymMap &symMap) {
+  Fortran::lower::InitLocalKind mode =
+      converter.getLoweringOptions().getInitLocalMode();
+  if (mode == Fortran::lower::InitLocalKind::Off)
+    return;
+  if (!shouldInitLocal(var))
+    return;
+
+  fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+  mlir::Location loc = converter.getCurrentLocation();
+  uint8_t hexByte = converter.getLoweringOptions().getInitLocalPattern();
+
+  fir::ExtendedValue exv =
+      converter.getSymbolExtendedValue(var.getSymbol(), &symMap);
+  mlir::Value base = fir::getBase(exv);
+  mlir::Type storeTy = fir::unwrapRefType(base.getType());
+
+  if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(storeTy)) {
+    // Array: build element constant and use insert_on_range.
+    mlir::Type eleTy = seqTy.getEleTy();
+    auto fpTy = mlir::dyn_cast<mlir::FloatType>(eleTy);
+    auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(eleTy);
+    mlir::Value elePat;
+    switch (mode) {
+    case Fortran::lower::InitLocalKind::Zero:
+      elePat = fir::ZeroOp::create(builder, loc, eleTy);
+      break;
+    case Fortran::lower::InitLocalKind::Hex:
+      elePat = genByteSplatInit(builder, loc, eleTy, hexByte);
+      break;
+    case Fortran::lower::InitLocalKind::QNaN:
+      if (fpTy)
+        elePat = genFPNaNInit(builder, loc, fpTy, false);
+      else if (cplxTy) {
+        auto partFpTy = mlir::cast<mlir::FloatType>(cplxTy.getElementType());
+        mlir::Value nanPart = genFPNaNInit(builder, loc, partFpTy, false);
+        elePat = mlir::complex::CreateOp::create(builder, loc, cplxTy, nanPart,
+                                                 nanPart);
+      } else
+        elePat = genByteSplatInit(builder, loc, eleTy, 0xAA);
+      break;
+    case Fortran::lower::InitLocalKind::SNaN:
+      if (fpTy)
+        elePat = genFPNaNInit(builder, loc, fpTy, true);
+      else if (cplxTy) {
+        auto partFpTy = mlir::cast<mlir::FloatType>(cplxTy.getElementType());
+        mlir::Value nanPart = genFPNaNInit(builder, loc, partFpTy, true);
+        elePat = mlir::complex::CreateOp::create(builder, loc, cplxTy, nanPart,
+                                                 nanPart);
+      } else
+        elePat = genByteSplatInit(builder, loc, eleTy, 0xAA);
+      break;
+    default:
+      llvm_unreachable("unexpected InitLocalKind");
+    }
+    // Build flat [lb0,ub0, lb1,ub1, ...] bounds vector.
+    llvm::SmallVector<int64_t> rangeBounds;
+    bool hasUnknown = false;
+    for (auto dim : seqTy.getShape()) {
+      if (dim == fir::SequenceType::getUnknownExtent()) {
+        hasUnknown = true;
+        break;
+      }
+      rangeBounds.push_back(0);
+      rangeBounds.push_back(dim - 1);
+    }
+    if (!hasUnknown) {
+      mlir::Value arrVal = fir::UndefOp::create(builder, loc, seqTy);
+      arrVal =
+          fir::InsertOnRangeOp::create(builder, loc, seqTy, arrVal, elePat,
+                                       builder.getIndexVectorAttr(rangeBounds));
+      fir::StoreOp::create(builder, loc, arrVal, base);
+    }
+  } else if (auto recTy = mlir::dyn_cast<fir::RecordType>(storeTy)) {
+    // Derived type: zero the whole struct, or walk fields for nan/snan/hex.
+    if (mode == Fortran::lower::InitLocalKind::Zero) {
+      fir::StoreOp::create(builder, loc,
+                           fir::ZeroOp::create(builder, loc, recTy), base);
+    } else {
+      for (auto [fieldName, fieldTy] : recTy.getTypeList()) {
+        auto fieldIdx = fir::FieldIndexOp::create(
+            builder, loc, fir::FieldType::get(recTy.getContext()), fieldName,
+            recTy, mlir::ValueRange{});
+        mlir::Value fieldAddr =
+            fir::CoordinateOp::create(builder, loc, builder.getRefType(fieldTy),
+                                      base, mlir::ValueRange{fieldIdx});
+        genInitLocalStore(builder, loc, fieldTy, fieldAddr, mode, hexByte);
+      }
+    }
+  } else {
+    // Scalar (integer, real, complex, logical, character): store directly.
+    genInitLocalStore(builder, loc, storeTy, base, mode, hexByte);
+  }
+}
+
 /// Instantiate a local variable. Precondition: Each variable will be visited
 /// such that if its properties depend on other variables, the variables upon
 /// which its properties depend will already have been visited.
@@ -1294,6 +1528,8 @@ static void instantiateLocal(Fortran::lower::AbstractConverter &converter,
   if (mustBeDefaultInitializedAtRuntime(var))
     Fortran::lower::defaultInitializeAtRuntime(converter, var.getSymbol(),
                                                symMap);
+  else
+    genInitLocal(converter, var, symMap);
   auto *builder = &converter.getFirOpBuilder();
   bool needsHostCudaCleanup = needCUDAAlloc(var.getSymbol()) &&
                               !cuf::isCUDADeviceContext(builder->getRegion());
diff --git a/flang/test/Driver/finit-local.f90 b/flang/test/Driver/finit-local.f90
new file mode 100644
index 0000000000000..01f288b7a8803
--- /dev/null
+++ b/flang/test/Driver/finit-local.f90
@@ -0,0 +1,33 @@
+! Tests that -finit-local= and -finit-local-zero are accepted by the Flang
+! driver and forwarded correctly to -fc1.
+
+! --- Valid values: zero, nan, snan, hex byte ---
+! RUN: %flang -### -S -finit-local=zero  %s -o - 2>&1 | FileCheck --check-prefix=CHECK-ZERO  %s
+! RUN: %flang -### -S -finit-local=nan   %s -o - 2>&1 | FileCheck --check-prefix=CHECK-NAN   %s
+! RUN: %flang -### -S -finit-local=snan  %s -o - 2>&1 | FileCheck --check-prefix=CHECK-SNAN  %s
+! RUN: %flang -### -S -finit-local=0xAA  %s -o - 2>&1 | FileCheck --check-prefix=CHECK-HEX   %s
+! RUN: %flang -### -S -finit-local=0xff  %s -o - 2>&1 | FileCheck --check-prefix=CHECK-HEX2  %s
+
+! --- GFortran alias: -finit-local-zero ---
+! RUN: %flang -### -S -finit-local-zero  %s -o - 2>&1 | FileCheck --check-prefix=CHECK-ALIAS %s
+
+! --- Compiler (fc1) directly accepts -finit-local= ---
+! RUN: %flang_fc1 -emit-hlfir -finit-local=zero  %s -o -
+! RUN: %flang_fc1 -emit-hlfir -finit-local=nan   %s -o -
+! RUN: %flang_fc1 -emit-hlfir -finit-local=snan  %s -o -
+! RUN: %flang_fc1 -emit-hlfir -finit-local=0xAA  %s -o -
+! RUN: %flang_fc1 -emit-hlfir -finit-local-zero  %s -o -
+
+! --- Invalid value should produce a diagnostic (fc1 level) ---
+! RUN: not %flang_fc1 -emit-hlfir -finit-local=bogus %s -o - 2>&1 | FileCheck --check-prefix=CHECK-ERR %s
+
+! CHECK-ZERO:  "-fc1"{{.*}}"-finit-local=zero"
+! CHECK-NAN:   "-fc1"{{.*}}"-finit-local=nan"
+! CHECK-SNAN:  "-fc1"{{.*}}"-finit-local=snan"
+! CHECK-HEX:   "-fc1"{{.*}}"-finit-local=0xAA"
+! CHECK-HEX2:  "-fc1"{{.*}}"-finit-local=0xff"
+! CHECK-ALIAS: "-fc1"{{.*}}"-finit-local=zero"
+! CHECK-ERR:   error: invalid value 'bogus' in '-finit-local=bogus'
+
+subroutine dummy_sub()
+end subroutine
diff --git a/flang/test/Lower/finit-local-f128.f90 b/flang/test/Lower/finit-local-f128.f90
new file mode 100644
index 0000000000000..a59322c6df139
--- /dev/null
+++ b/flang/test/Lower/finit-local-f128.f90
@@ -0,0 +1,64 @@
+! Tests for -finit-local= with REAL(16) and COMPLEX(16) (IEEE f128).
+! These types require f128 math support, which is not available on AIX.
+!
+! REQUIRES: flang-supports-f128-math
+!
+! RUN: bbc -emit-hlfir -finit-local=zero  -o - %s | FileCheck --check-prefix=ZERO  %s
+! RUN: bbc -emit-hlfir -finit-local=nan   -o - %s | FileCheck --check-prefix=NAN   %s
+! RUN: bbc -emit-hlfir -finit-local=snan  -o - %s | FileCheck --check-prefix=SNAN  %s
+! RUN: bbc -emit-hlfir -finit-local=0xAA  -o - %s | FileCheck --check-prefix=HEX   %s
+
+! ---------------------------------------------------------------------------
+! REAL(16) -- 16-byte FP (f128); hex uses 128-bit APInt splat + bitcast
+! 0xAA * 16 bytes = -113427455640312821154458202477256070486 (signed i128)
+! ---------------------------------------------------------------------------
+subroutine test_real16(res)
+  real(16) :: res
+  real(16) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_real16
+! ZERO: fir.zero_bits f128
+! ZERO: fir.store {{.*}} : !fir.ref<f128>
+
+! NAN-LABEL:  func.func @_QPtest_real16
+! NAN:  arith.constant {{.*}} : f128
+! NAN:  fir.store {{.*}} : !fir.ref<f128>
+
+! SNAN-LABEL: func.func @_QPtest_real16
+! SNAN: arith.constant {{.*}} : f128
+! SNAN: fir.store {{.*}} : !fir.ref<f128>
+
+! HEX-LABEL:  func.func @_QPtest_real16
+! HEX:  arith.constant -113427455640312821154458202477256070486 : i128
+! HEX:  arith.bitcast {{.*}} : i128 to f128
+! HEX:  fir.store {{.*}} : !fir.ref<f128>
+
+! ---------------------------------------------------------------------------
+! COMPLEX(16) -- two f128 parts; hex uses 128-bit APInt splat + bitcast
+! 0xAA * 16 bytes = -113427455640312821154458202477256070486 (signed i128)
+! ---------------------------------------------------------------------------
+subroutine test_complex16(res)
+  complex(16) :: res
+  complex(16) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_complex16
+! ZERO: fir.zero_bits !fir.complex<16>
+! ZERO: fir.store {{.*}} : !fir.ref<!fir.complex<16>>
+
+! NAN-LABEL:  func.func @_QPtest_complex16
+! NAN:  arith.constant {{.*}} : f128
+! NAN:  complex.create {{.*}}, {{.*}} : f128
+! NAN:  fir.store {{.*}} : !fir.ref<!fir.complex<16>>
+
+! SNAN-LABEL: func.func @_QPtest_complex16
+! SNAN: arith.constant {{.*}} : f128
+! SNAN: complex.create {{.*}}, {{.*}} : f128
+! SNAN: fir.store {{.*}} : !fir.ref<!fir.complex<16>>
+
+! HEX-LABEL:  func.func @_QPtest_complex16
+! HEX:  arith.constant -113427455640312821154458202477256070486 : i128
+! HEX:  arith.bitcast {{.*}} : i128 to f128
+! HEX:  complex.create {{.*}}, {{.*}} : f128
+! HEX:  fir.store {{.*}} : !fir.ref<!fir.complex<16>>
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
new file mode 100644
index 0000000000000..379a7842dd0ac
--- /dev/null
+++ b/flang/test/Lower/finit-local.f90
@@ -0,0 +1,496 @@
+! Tests for -finit-local= local variable initialization.
+!
+! Covers every Fortran type listed in the RFC type-mapping table:
+!   INTEGER(k) k=1,2,4,8
+!   REAL(k)    k=4,8  (k=16 in finit-local-f128.f90, requires flang-supports-f128-math)
+!   COMPLEX(k) k=4,8  (k=16 in finit-local-f128.f90, requires flang-supports-f128-math)
+!   LOGICAL(k) k=1,4
+!   CHARACTER(n)
+!   Derived type (struct with plain-int and real components)
+!   Arrays of integer and real
+!
+! Modes exercised: zero, nan, snan, 0xAA (hex), and off (no flag).
+!
+! RUN: bbc -emit-hlfir -finit-local=zero  -o - %s | FileCheck --check-prefix=ZERO  %s
+! RUN: bbc -emit-hlfir -finit-local=nan   -o - %s | FileCheck --check-prefix=NAN   %s
+! RUN: bbc -emit-hlfir -finit-local=snan  -o - %s | FileCheck --check-prefix=SNAN  %s
+! RUN: bbc -emit-hlfir -finit-local=0xAA  -o - %s | FileCheck --check-prefix=HEX   %s
+! RUN: bbc -emit-hlfir                    -o - %s | FileCheck --check-prefix=OFF   %s
+! RUN: bbc -emit-hlfir -finit-local-zero  -o - %s | FileCheck --check-prefix=ZERO  %s
+
+! ---------------------------------------------------------------------------
+! INTEGER(1) -- 1-byte: pattern 0xAA = -86 (signed) = 170 (unsigned)
+! ---------------------------------------------------------------------------
+subroutine test_int1(res)
+  integer(1) :: res
+  integer(1) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_int1
+! ZERO: fir.alloca i8
+! ZERO: fir.zero_bits i8
+! ZERO: fir.store {{.*}} : !fir.ref<i8>
+
+! NAN-LABEL:  func.func @_QPtest_int1
+! NAN:  arith.constant -86 : i8
+! NAN:  fir.store {{.*}} : !fir.ref<i8>
+
+! HEX-LABEL:  func.func @_QPtest_int1
+! HEX:  arith.constant -86 : i8
+! HEX:  fir.store {{.*}} : !fir.ref<i8>
+
+! OFF-LABEL: func.func @_QPtest_int1
+! OFF-NOT: fir.store {{.*}} : !fir.ref<i8>
+
+! ---------------------------------------------------------------------------
+! INTEGER(2) -- 2-byte: pattern 0xAAAA = -21846 (signed)
+! ---------------------------------------------------------------------------
+subroutine test_int2(res)
+  integer(2) :: res
+  integer(2) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_int2
+! ZERO: fir.zero_bits i16
+
+! NAN-LABEL:  func.func @_QPtest_int2
+! NAN:  arith.constant -21846 : i16
+! NAN:  fir.store {{.*}} : !fir.ref<i16>
+
+! HEX-LABEL:  func.func @_QPtest_int2
+! HEX:  arith.constant -21846 : i16
+! HEX:  fir.store {{.*}} : !fir.ref<i16>
+
+! ---------------------------------------------------------------------------
+! INTEGER(4) -- 4-byte: pattern 0xAAAAAAAA = -1431655766 (signed)
+! ---------------------------------------------------------------------------
+subroutine test_int4(res)
+  integer(4) :: res
+  integer(4) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_int4
+! ZERO: fir.zero_bits i32
+
+! NAN-LABEL:  func.func @_QPtest_int4
+! NAN:  arith.constant -1431655766 : i32
+! NAN:  fir.store {{.*}} : !fir.ref<i32>
+
+! SNAN-LABEL: func.func @_QPtest_int4
+! SNAN: arith.constant -1431655766 : i32
+! SNAN: fir.store {{.*}} : !fir.ref<i32>
+
+! HEX-LABEL:  func.func @_QPtest_int4
+! HEX:  arith.constant -1431655766 : i32
+! HEX:  fir.store {{.*}} : !fir.ref<i32>
+
+! OFF-LABEL: func.func @_QPtest_int4
+! OFF-NOT: fir.zero_bits
+
+! ---------------------------------------------------------------------------
+! INTEGER(8) -- 8-byte: pattern 0xAAAAAAAAAAAAAAAA = -6148914691236517206 (signed)
+! ---------------------------------------------------------------------------
+subroutine test_int8(res)
+  integer(8) :: res
+  integer(8) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_int8
+! ZERO: fir.zero_bits i64
+
+! NAN-LABEL:  func.func @_QPtest_int8
+! NAN:  arith.constant -6148914691236517206 : i64
+! NAN:  fir.store {{.*}} : !fir.ref<i64>
+
+! HEX-LABEL:  func.func @_QPtest_int8
+! HEX:  arith.constant -6148914691236517206 : i64
+! HEX:  fir.store {{.*}} : !fir.ref<i64>
+
+! ---------------------------------------------------------------------------
+! REAL(4) -- zero fills with fir.zero_bits; nan/snan with FP constant; hex bitcast
+! ---------------------------------------------------------------------------
+subroutine test_real4(res)
+  real(4) :: res
+  real(4) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_real4
+! ZERO: fir.zero_bits f32
+! ZERO: fir.store {{.*}} : !fir.ref<f32>
+
+! NAN-LABEL:  func.func @_QPtest_real4
+! NAN:  arith.constant {{.*}} : f32
+! NAN:  fir.store {{.*}} : !fir.ref<f32>
+
+! SNAN-LABEL: func.func @_QPtest_real4
+! SNAN: arith.constant {{.*}} : f32
+! SNAN: fir.store {{.*}} : !fir.ref<f32>
+
+! HEX-LABEL:  func.func @_QPtest_real4
+! HEX:  arith.constant -1431655766 : i32
+! HEX:  arith.bitcast {{.*}} : i32 to f32
+! HEX:  fir.store {{.*}} : !fir.ref<f32>
+
+! OFF-LABEL: func.func @_QPtest_real4
+! OFF-NOT: fir.zero_bits
+
+! ---------------------------------------------------------------------------
+! REAL(8) -- 8-byte FP
+! ---------------------------------------------------------------------------
+subroutine test_real8(res)
+  real(8) :: res
+  real(8) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_real8
+! ZERO: fir.zero_bits f64
+! ZERO: fir.store {{.*}} : !fir.ref<f64>
+
+! NAN-LABEL:  func.func @_QPtest_real8
+! NAN:  arith.constant {{.*}} : f64
+! NAN:  fir.store {{.*}} : !fir.ref<f64>
+
+! SNAN-LABEL: func.func @_QPtest_real8
+! SNAN: arith.constant {{.*}} : f64
+! SNAN: fir.store {{.*}} : !fir.ref<f64>
+
+! HEX-LABEL:  func.func @_QPtest_real8
+! HEX:  arith.constant -6148914691236517206 : i64
+! HEX:  arith.bitcast {{.*}} : i64 to f64
+! HEX:  fir.store {{.*}} : !fir.ref<f64>
+
+! ---------------------------------------------------------------------------
+! COMPLEX(4) -- two f32 parts; stored as complex<f32>
+! nan/snan: both parts get NaN; hex: both parts get bitcast pattern
+! ---------------------------------------------------------------------------
+subroutine test_complex4(res)
+  complex(4) :: res
+  complex(4) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_complex4
+! ZERO: fir.zero_bits complex<f32>
+! ZERO: fir.store {{.*}} : !fir.ref<complex<f32>>
+
+! NAN-LABEL:  func.func @_QPtest_complex4
+! NAN:  arith.constant {{.*}} : f32
+! NAN:  complex.create {{.*}} : complex<f32>
+! NAN:  fir.store {{.*}} : !fir.ref<complex<f32>>
+
+! SNAN-LABEL: func.func @_QPtest_complex4
+! SNAN: arith.constant {{.*}} : f32
+! SNAN: complex.create {{.*}} : complex<f32>
+! SNAN: fir.store {{.*}} : !fir.ref<complex<f32>>
+
+! HEX-LABEL:  func.func @_QPtest_complex4
+! HEX:  arith.constant -1431655766 : i32
+! HEX:  arith.bitcast {{.*}} : i32 to f32
+! HEX:  complex.create {{.*}} : complex<f32>
+! HEX:  fir.store {{.*}} : !fir.ref<complex<f32>>
+
+! ---------------------------------------------------------------------------
+! COMPLEX(8) -- two f64 parts; stored as complex<f64>
+! ---------------------------------------------------------------------------
+subroutine test_complex8(res)
+  complex(8) :: res
+  complex(8) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_complex8
+! ZERO: fir.zero_bits complex<f64>
+! ZERO: fir.store {{.*}} : !fir.ref<complex<f64>>
+
+! NAN-LABEL:  func.func @_QPtest_complex8
+! NAN:  arith.constant {{.*}} : f64
+! NAN:  complex.create {{.*}} : complex<f64>
+! NAN:  fir.store {{.*}} : !fir.ref<complex<f64>>
+
+! SNAN-LABEL: func.func @_QPtest_complex8
+! SNAN: arith.constant {{.*}} : f64
+! SNAN: complex.create {{.*}} : complex<f64>
+! SNAN: fir.store {{.*}} : !fir.ref<complex<f64>>
+
+! HEX-LABEL:  func.func @_QPtest_complex8
+! HEX:  arith.constant -6148914691236517206 : i64
+! HEX:  arith.bitcast {{.*}} : i64 to f64
+! HEX:  complex.create {{.*}} : complex<f64>
+! HEX:  fir.store {{.*}} : !fir.ref<complex<f64>>
+
+! ---------------------------------------------------------------------------
+! LOGICAL(1) -- stored as i8; pattern 0xAA = -86
+! ---------------------------------------------------------------------------
+subroutine test_logical1(res)
+  logical(1) :: res
+  logical(1) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_logical1
+! ZERO: fir.zero_bits !fir.logical<1>
+
+! NAN-LABEL:  func.func @_QPtest_logical1
+! NAN:  fir.zero_bits !fir.logical<1>
+! NAN:  fir.store {{.*}} : !fir.ref<!fir.logical<1>>
+
+! HEX-LABEL:  func.func @_QPtest_logical1
+! HEX:  fir.zero_bits !fir.logical<1>
+! HEX:  fir.store {{.*}} : !fir.ref<!fir.logical<1>>
+
+! ---------------------------------------------------------------------------
+! LOGICAL(4) -- stored as i32; pattern 0xAAAAAAAA = -1431655766
+! ---------------------------------------------------------------------------
+subroutine test_logical4(res)
+  logical(4) :: res
+  logical(4) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_logical4
+! ZERO: fir.zero_bits !fir.logical<4>
+
+! NAN-LABEL:  func.func @_QPtest_logical4
+! NAN:  fir.zero_bits !fir.logical<4>
+! NAN:  fir.store {{.*}} : !fir.ref<!fir.logical<4>>
+
+! HEX-LABEL:  func.func @_QPtest_logical4
+! HEX:  fir.zero_bits !fir.logical<4>
+! HEX:  fir.store {{.*}} : !fir.ref<!fir.logical<4>>
+
+! ---------------------------------------------------------------------------
+! CHARACTER(10) -- fir::CharacterType is not mlir::FloatType/IntegerType/ComplexType
+! nan/snan/hex: fall back to fir.zero_bits (known limitation, TODO)
+! ---------------------------------------------------------------------------
+subroutine test_char10(res)
+  character(10) :: res
+  character(10) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_char10
+! ZERO: fir.zero_bits !fir.char<1,10>
+! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
+
+! NAN-LABEL:  func.func @_QPtest_char10
+! NAN:  fir.zero_bits !fir.char<1,10>
+! NAN:  fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
+
+! SNAN-LABEL: func.func @_QPtest_char10
+! SNAN: fir.zero_bits !fir.char<1,10>
+! SNAN: fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
+
+! HEX-LABEL:  func.func @_QPtest_char10
+! HEX:  fir.zero_bits !fir.char<1,10>
+! HEX:  fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
+
+! ---------------------------------------------------------------------------
+! Derived type -- struct with an INTEGER(4) and a REAL(4) field
+! nan/hex: field-by-field walk (integer: 0xAA; real: NaN or bitcast)
+! ---------------------------------------------------------------------------
+subroutine test_derived(res)
+  type :: mytype
+    integer(4) :: i
+    real(4) :: r
+  end type
+  type(mytype) :: res
+  type(mytype) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_derived
+! ZERO: fir.zero_bits !fir.type<{{.*}}>
+! ZERO: fir.store {{.*}} : !fir.ref<!fir.type<{{.*}}>>
+
+! NAN-LABEL:  func.func @_QPtest_derived
+! NAN:  fir.coordinate_of {{.*}} -> !fir.ref<i32>
+! NAN:  arith.constant {{.*}} : i32
+! NAN:  fir.store {{.*}} : !fir.ref<i32>
+! NAN:  fir.coordinate_of {{.*}} -> !fir.ref<f32>
+! NAN:  arith.constant {{.*}} : f32
+! NAN:  fir.store {{.*}} : !fir.ref<f32>
+
+! HEX-LABEL:  func.func @_QPtest_derived
+! HEX:  fir.coordinate_of {{.*}} -> !fir.ref<i32>
+! HEX:  arith.constant {{.*}} : i32
+! HEX:  fir.store {{.*}} : !fir.ref<i32>
+! HEX:  fir.coordinate_of {{.*}} -> !fir.ref<f32>
+! HEX:  arith.bitcast {{.*}} : i32 to f32
+! HEX:  fir.store {{.*}} : !fir.ref<f32>
+
+
+! ---------------------------------------------------------------------------
+! Array INTEGER(4)(4) -- 1-D; filled via insert_on_range
+! ---------------------------------------------------------------------------
+subroutine test_int_array(res)
+  integer(4) :: res(4)
+  integer(4) :: x(4)
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_int_array
+! ZERO: fir.insert_on_range {{.*}} from (0) to (3)
+! ZERO: fir.store {{.*}} : !fir.ref<!fir.array<4xi32>>
+
+! NAN-LABEL:  func.func @_QPtest_int_array
+! NAN:  fir.insert_on_range {{.*}} from (0) to (3)
+! NAN:  fir.store {{.*}} : !fir.ref<!fir.array<4xi32>>
+
+! HEX-LABEL:  func.func @_QPtest_int_array
+! HEX:  fir.insert_on_range {{.*}} from (0) to (3)
+! HEX:  fir.store {{.*}} : !fir.ref<!fir.array<4xi32>>
+
+! OFF-LABEL: func.func @_QPtest_int_array
+! OFF-NOT: fir.insert_on_range
+
+! ---------------------------------------------------------------------------
+! Array REAL(4)(4) -- 1-D; nan/snan: NaN element; hex: bitcast element
+! ---------------------------------------------------------------------------
+subroutine test_real_array(res)
+  real(4) :: res(4)
+  real(4) :: x(4)
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_real_array
+! ZERO: fir.insert_on_range {{.*}} from (0) to (3)
+! ZERO: fir.store {{.*}} : !fir.ref<!fir.array<4xf32>>
+
+! NAN-LABEL:  func.func @_QPtest_real_array
+! NAN:  fir.insert_on_range {{.*}} from (0) to (3)
+! NAN:  fir.store {{.*}} : !fir.ref<!fir.array<4xf32>>
+
+! SNAN-LABEL: func.func @_QPtest_real_array
+! SNAN: fir.insert_on_range {{.*}} from (0) to (3)
+! SNAN: fir.store {{.*}} : !fir.ref<!fir.array<4xf32>>
+
+! HEX-LABEL:  func.func @_QPtest_real_array
+! HEX:  fir.insert_on_range {{.*}} from (0) to (3)
+! HEX:  fir.store {{.*}} : !fir.ref<!fir.array<4xf32>>
+
+! ---------------------------------------------------------------------------
+! Array INTEGER(4)(3,4) -- 2-D; insert_on_range with two-dimension bounds
+! ---------------------------------------------------------------------------
+subroutine test_int_array_2d(res)
+  integer(4) :: res(3,4)
+  integer(4) :: x(3,4)
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_int_array_2d
+! ZERO: fir.insert_on_range {{.*}} from (0, 0) to (2, 3)
+! ZERO: fir.store {{.*}} : !fir.ref<!fir.array<3x4xi32>>
+
+! HEX-LABEL: func.func @_QPtest_int_array_2d
+! HEX:  fir.insert_on_range {{.*}} from (0, 0) to (2, 3)
+! HEX:  fir.store {{.*}} : !fir.ref<!fir.array<3x4xi32>>
+
+! ---------------------------------------------------------------------------
+! Exclusion: explicit init (= 42) -- must NOT be touched
+! ---------------------------------------------------------------------------
+subroutine test_explicit_init(res)
+  integer(4) :: res
+  integer(4) :: x = 42
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_explicit_init
+! ZERO-NOT: fir.zero_bits
+
+! NAN-LABEL:  func.func @_QPtest_explicit_init
+! NAN-NOT:  arith.constant -1431655766 : i32
+
+! HEX-LABEL:  func.func @_QPtest_explicit_init
+! HEX-NOT:  arith.bitcast
+
+! ---------------------------------------------------------------------------
+! Exclusion: DATA statement init -- must NOT be touched
+! ---------------------------------------------------------------------------
+subroutine test_data_init(res)
+  integer(4) :: res
+  integer(4) :: x
+  data x /99/
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_data_init
+! ZERO-NOT: fir.zero_bits i32
+
+! NAN-LABEL:  func.func @_QPtest_data_init
+! NAN-NOT:  arith.constant -1431655766 : i32
+
+! HEX-LABEL:  func.func @_QPtest_data_init
+! HEX-NOT:  arith.bitcast
+
+! ---------------------------------------------------------------------------
+! Exclusion: derived-type default component init -- must NOT be touched
+! ---------------------------------------------------------------------------
+subroutine test_default_comp_init(res)
+  type :: inittype
+    integer(4) :: i = 7
+    real(4)    :: r = 3.14
+  end type
+  type(inittype) :: res
+  type(inittype) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_default_comp_init
+! ZERO-NOT: fir.zero_bits
+
+! NAN-LABEL:  func.func @_QPtest_default_comp_init
+! NAN-NOT:  arith.constant -1431655766 : i32
+
+! HEX-LABEL:  func.func @_QPtest_default_comp_init
+! HEX-NOT:  arith.bitcast
+
+! ---------------------------------------------------------------------------
+! Exclusion: SAVE -- must NOT be touched
+! ---------------------------------------------------------------------------
+subroutine test_save(res)
+  integer(4) :: res
+  integer(4), save :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_save
+! ZERO-NOT: fir.zero_bits i32
+
+! HEX-LABEL: func.func @_QPtest_save
+! HEX-NOT: arith.constant -1431655766 : i32
+
+! ---------------------------------------------------------------------------
+! Exclusion: dummy argument -- must NOT be touched
+! ---------------------------------------------------------------------------
+subroutine test_dummy(x, res)
+  integer(4), intent(in) :: x
+  integer(4) :: res
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_dummy
+! ZERO-NOT: fir.zero_bits i32
+
+! HEX-LABEL: func.func @_QPtest_dummy
+! HEX-NOT: arith.constant -1431655766 : i32
+
+! ---------------------------------------------------------------------------
+! Exclusion: ALLOCATABLE -- must NOT be touched
+! ---------------------------------------------------------------------------
+subroutine test_allocatable(res)
+  integer(4), allocatable :: x
+  integer(4) :: res
+  if (allocated(x)) res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_allocatable
+! ZERO-NOT: fir.zero_bits i32
+
+! HEX-LABEL: func.func @_QPtest_allocatable
+! HEX-NOT: arith.constant -1431655766 : i32
+
+! ---------------------------------------------------------------------------
+! Exclusion: EQUIVALENCE -- must NOT be touched
+! ---------------------------------------------------------------------------
+subroutine test_equivalence(res)
+  integer(4) :: res
+  integer(4) :: x, y
+  equivalence (x, y)
+  res = x + y
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_equivalence
+! ZERO-NOT: fir.zero_bits
+! ZERO: return
+
+! NAN-LABEL:  func.func @_QPtest_equivalence
+! NAN-NOT:  arith.constant -1431655766 : i32
+! NAN: return
+
+! HEX-LABEL:  func.func @_QPtest_equivalence
+! HEX-NOT:  arith.bitcast
+! HEX: return
diff --git a/flang/tools/bbc/bbc.cpp b/flang/tools/bbc/bbc.cpp
index d6e0643c813c2..bd5a7e6643cac 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -276,6 +276,20 @@ static llvm::cl::opt<bool> initGlobalZero(
     llvm::cl::desc("Zero initialize globals without default initialization"),
     llvm::cl::init(true));
 
+static llvm::cl::opt<std::string>
+    initLocalMode("finit-local",
+                  llvm::cl::desc("Initialize local variables without explicit "
+                                 "or default initialization. "
+                                 "Accepts: zero, nan, snan, or 0x<hex-byte>."),
+                  llvm::cl::init(""));
+
+static llvm::cl::opt<bool> initLocalZero(
+    "finit-local-zero",
+    llvm::cl::desc(
+        "Zero-initialize local variables without explicit or default "
+        "initialization (alias for -finit-local=zero)"),
+    llvm::cl::init(false));
+
 static llvm::cl::opt<bool>
     reallocateLHS("frealloc-lhs",
                   llvm::cl::desc("Follow Fortran 2003 rules for (re)allocating "
@@ -512,6 +526,31 @@ static llvm::LogicalResult convertFortranSourceToMLIR(
   loweringOptions.setNoPPCNativeVecElemOrder(enableNoPPCNativeVecElemOrder);
   loweringOptions.setIntegerWrapAround(integerWrapAround);
   loweringOptions.setInitGlobalZero(initGlobalZero);
+  // -finit-local-zero (alias for -finit-local=zero)
+  if (initLocalZero)
+    loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::Zero);
+
+  // -finit-local=
+  if (!initLocalMode.empty()) {
+    llvm::StringRef val = initLocalMode;
+    if (val == "zero") {
+      loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::Zero);
+    } else if (val == "nan") {
+      loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::QNaN);
+    } else if (val == "snan") {
+      loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::SNaN);
+    } else if (val.starts_with("0x") || val.starts_with("0X")) {
+      unsigned long long hexVal = 0;
+      if (!val.drop_front(2).getAsInteger(16, hexVal) && hexVal <= 0xFF) {
+        loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::Hex);
+        loweringOptions.setInitLocalPattern(static_cast<uint8_t>(hexVal));
+      } else {
+        llvm::errs() << "bbc: invalid -finit-local= value: " << val << "\n";
+      }
+    } else {
+      llvm::errs() << "bbc: invalid -finit-local= value: " << val << "\n";
+    }
+  }
   loweringOptions.setReallocateLHS(reallocateLHS);
   loweringOptions.setSplitSumExpressionTree(fpSumReassociation);
   loweringOptions.setStackRepackArrays(stackRepackArrays);

>From aad5087315055b6788aa77523924e45d5f34e024 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Fri, 14 Aug 2026 10:34:37 -0400
Subject: [PATCH 02/23] [flang] Address tarunprabhu's review comments on
 -finit-local=

- FlangOptions.td: remove redundant block comment above finit_local_EQ.
- ReleaseNotes.md: drop "stack-allocated" from the description.
- ConvertVariable.cpp: add braces to if/else branches in the QNaN and
  SNaN cases per LLVM coding standards.
- flang/test/Driver/finit-local.f90: drop -o -, rename CHECK-* prefixes,
  remove redundant fc1-acceptance RUN lines.
- flang/test/Lower/finit-local-f128.f90: switch from bbc to %flang_fc1.
---
 clang/include/clang/Options/FlangOptions.td |  4 ---
 flang/docs/ReleaseNotes.md                  |  2 +-
 flang/lib/Lower/ConvertVariable.cpp         | 14 +++++----
 flang/test/Driver/finit-local.f90           | 35 ++++++++-------------
 flang/test/Lower/finit-local-f128.f90       |  8 ++---
 5 files changed, 26 insertions(+), 37 deletions(-)

diff --git a/clang/include/clang/Options/FlangOptions.td b/clang/include/clang/Options/FlangOptions.td
index def883e88509c..15139474f97a2 100644
--- a/clang/include/clang/Options/FlangOptions.td
+++ b/clang/include/clang/Options/FlangOptions.td
@@ -391,10 +391,6 @@ defm init_global_zero : BoolOptionWithoutMarshalling<"f", "init-global-zero",
   PosFlag<SetTrue, [], [], "Zero initialize globals without default initialization (default)">,
   NegFlag<SetFalse, [], [], "Do not zero initialize globals without default initialization">>;
 
-// -finit-local=<zero|nan|snan|0x<hex>>
-// Initialize automatic (local, stack) variables that have no explicit or
-// default initialization.  -finit-local-zero is a GFortran compatibility alias
-// for -finit-local=zero.
 def finit_local_EQ : Joined<["-"], "finit-local=">,
   Group<f_Group>,
   Visibility<[FC1Option, FlangOption]>,
diff --git a/flang/docs/ReleaseNotes.md b/flang/docs/ReleaseNotes.md
index 519969eea9e35..e7761f237a7d4 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -97,7 +97,7 @@ page](https://llvm.org/releases/).
 - Added `-gz` and `-gz=<format>` flags to enable compression of DWARF debug
   sections. Supported formats are `zlib`, `zstd`, and `none`.
 
-- Added `-finit-local=<val>` to initialize automatic (local, stack-allocated)
+- Added `-finit-local=<val>` to initialize automatic local
   variables that have no explicit or default initialization. Accepted values
   are `zero`, `nan`, `snan`, and `0x<hex-byte>` (e.g. `0xAA`). The gfortran
   compatibility alias `-finit-local-zero` is equivalent to `-finit-local=zero`.
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 98e6c9ccfc8ad..f1a18bca8dc83 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1441,26 +1441,28 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       elePat = genByteSplatInit(builder, loc, eleTy, hexByte);
       break;
     case Fortran::lower::InitLocalKind::QNaN:
-      if (fpTy)
+      if (fpTy) {
         elePat = genFPNaNInit(builder, loc, fpTy, false);
-      else if (cplxTy) {
+      } else if (cplxTy) {
         auto partFpTy = mlir::cast<mlir::FloatType>(cplxTy.getElementType());
         mlir::Value nanPart = genFPNaNInit(builder, loc, partFpTy, false);
         elePat = mlir::complex::CreateOp::create(builder, loc, cplxTy, nanPart,
                                                  nanPart);
-      } else
+      } else {
         elePat = genByteSplatInit(builder, loc, eleTy, 0xAA);
+      }
       break;
     case Fortran::lower::InitLocalKind::SNaN:
-      if (fpTy)
+      if (fpTy) {
         elePat = genFPNaNInit(builder, loc, fpTy, true);
-      else if (cplxTy) {
+      } else if (cplxTy) {
         auto partFpTy = mlir::cast<mlir::FloatType>(cplxTy.getElementType());
         mlir::Value nanPart = genFPNaNInit(builder, loc, partFpTy, true);
         elePat = mlir::complex::CreateOp::create(builder, loc, cplxTy, nanPart,
                                                  nanPart);
-      } else
+      } else {
         elePat = genByteSplatInit(builder, loc, eleTy, 0xAA);
+      }
       break;
     default:
       llvm_unreachable("unexpected InitLocalKind");
diff --git a/flang/test/Driver/finit-local.f90 b/flang/test/Driver/finit-local.f90
index 01f288b7a8803..1d39a83b4ce01 100644
--- a/flang/test/Driver/finit-local.f90
+++ b/flang/test/Driver/finit-local.f90
@@ -2,32 +2,23 @@
 ! driver and forwarded correctly to -fc1.
 
 ! --- Valid values: zero, nan, snan, hex byte ---
-! RUN: %flang -### -S -finit-local=zero  %s -o - 2>&1 | FileCheck --check-prefix=CHECK-ZERO  %s
-! RUN: %flang -### -S -finit-local=nan   %s -o - 2>&1 | FileCheck --check-prefix=CHECK-NAN   %s
-! RUN: %flang -### -S -finit-local=snan  %s -o - 2>&1 | FileCheck --check-prefix=CHECK-SNAN  %s
-! RUN: %flang -### -S -finit-local=0xAA  %s -o - 2>&1 | FileCheck --check-prefix=CHECK-HEX   %s
-! RUN: %flang -### -S -finit-local=0xff  %s -o - 2>&1 | FileCheck --check-prefix=CHECK-HEX2  %s
+! RUN: %flang -### -S -finit-local=zero  %s 2>&1 | FileCheck --check-prefix=ZERO  %s
+! RUN: %flang -### -S -finit-local=nan   %s 2>&1 | FileCheck --check-prefix=NAN   %s
+! RUN: %flang -### -S -finit-local=snan  %s 2>&1 | FileCheck --check-prefix=SNAN  %s
+! RUN: %flang -### -S -finit-local=0xAA  %s 2>&1 | FileCheck --check-prefix=HEX   %s
+! RUN: %flang -### -S -finit-local=0xff  %s 2>&1 | FileCheck --check-prefix=HEX2  %s
 
 ! --- GFortran alias: -finit-local-zero ---
-! RUN: %flang -### -S -finit-local-zero  %s -o - 2>&1 | FileCheck --check-prefix=CHECK-ALIAS %s
-
-! --- Compiler (fc1) directly accepts -finit-local= ---
-! RUN: %flang_fc1 -emit-hlfir -finit-local=zero  %s -o -
-! RUN: %flang_fc1 -emit-hlfir -finit-local=nan   %s -o -
-! RUN: %flang_fc1 -emit-hlfir -finit-local=snan  %s -o -
-! RUN: %flang_fc1 -emit-hlfir -finit-local=0xAA  %s -o -
-! RUN: %flang_fc1 -emit-hlfir -finit-local-zero  %s -o -
-
+! RUN: %flang -### -S -finit-local-zero  %s 2>&1 | FileCheck --check-prefix=ZERO %s
 ! --- Invalid value should produce a diagnostic (fc1 level) ---
-! RUN: not %flang_fc1 -emit-hlfir -finit-local=bogus %s -o - 2>&1 | FileCheck --check-prefix=CHECK-ERR %s
+! RUN: not %flang_fc1 -emit-hlfir -finit-local=bogus %s 2>&1 | FileCheck --check-prefix=ERR %s
 
-! CHECK-ZERO:  "-fc1"{{.*}}"-finit-local=zero"
-! CHECK-NAN:   "-fc1"{{.*}}"-finit-local=nan"
-! CHECK-SNAN:  "-fc1"{{.*}}"-finit-local=snan"
-! CHECK-HEX:   "-fc1"{{.*}}"-finit-local=0xAA"
-! CHECK-HEX2:  "-fc1"{{.*}}"-finit-local=0xff"
-! CHECK-ALIAS: "-fc1"{{.*}}"-finit-local=zero"
-! CHECK-ERR:   error: invalid value 'bogus' in '-finit-local=bogus'
+! ZERO:  "-fc1"{{.*}} "-finit-local=zero"
+! NAN:   "-fc1"{{.*}} "-finit-local=nan"
+! SNAN:  "-fc1"{{.*}} "-finit-local=snan"
+! HEX:   "-fc1"{{.*}} "-finit-local=0xAA"
+! HEX2:  "-fc1"{{.*}} "-finit-local=0xff"
+! ERR:   error: invalid value 'bogus' in '-finit-local=bogus'
 
 subroutine dummy_sub()
 end subroutine
diff --git a/flang/test/Lower/finit-local-f128.f90 b/flang/test/Lower/finit-local-f128.f90
index a59322c6df139..69b6af69e4cee 100644
--- a/flang/test/Lower/finit-local-f128.f90
+++ b/flang/test/Lower/finit-local-f128.f90
@@ -3,10 +3,10 @@
 !
 ! REQUIRES: flang-supports-f128-math
 !
-! RUN: bbc -emit-hlfir -finit-local=zero  -o - %s | FileCheck --check-prefix=ZERO  %s
-! RUN: bbc -emit-hlfir -finit-local=nan   -o - %s | FileCheck --check-prefix=NAN   %s
-! RUN: bbc -emit-hlfir -finit-local=snan  -o - %s | FileCheck --check-prefix=SNAN  %s
-! RUN: bbc -emit-hlfir -finit-local=0xAA  -o - %s | FileCheck --check-prefix=HEX   %s
+! RUN: %flang_fc1 -emit-hlfir -finit-local=zero  %s -o - | FileCheck --check-prefix=ZERO  %s
+! RUN: %flang_fc1 -emit-hlfir -finit-local=nan   %s -o - | FileCheck --check-prefix=NAN   %s
+! RUN: %flang_fc1 -emit-hlfir -finit-local=snan  %s -o - | FileCheck --check-prefix=SNAN  %s
+! RUN: %flang_fc1 -emit-hlfir -finit-local=0xAA  %s -o - | FileCheck --check-prefix=HEX   %s
 
 ! ---------------------------------------------------------------------------
 ! REAL(16) -- 16-byte FP (f128); hex uses 128-bit APInt splat + bitcast

>From 886e9cc98b561265d17a4058ba1e717b2286ed12 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Fri, 14 Aug 2026 13:14:20 -0400
Subject: [PATCH 03/23] [flang] Address MattPD's first round of review comments
 on -finit-local=

- Refactor genInitLocal to use a recursive initAddr lambda so that
  array-valued derived-type fields dispatch correctly through the array
  path instead of having their sequence type stripped.
- Skip zero-extent arrays in shouldInitLocal. Exclude all CUDA storage.
- Add LOGICAL byte-splat support in genByteSplatInit.
- bbc: make -finit-local-zero and -finit-local= last-wins; invalid
  values now return a non-zero exit code.
- finit-local-f128.f90: accept both !fir.complex<16> and complex<f128>.
---
 flang/lib/Lower/ConvertVariable.cpp   | 178 +++++++++++++++-----------
 flang/test/Lower/finit-local-f128.f90 |  10 +-
 flang/test/Lower/finit-local.f90      |  12 +-
 flang/tools/bbc/bbc.cpp               |  14 +-
 4 files changed, 122 insertions(+), 92 deletions(-)

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index f1a18bca8dc83..00cb9f8e30172 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1301,6 +1301,10 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
       return false;
   if (Fortran::semantics::FindEquivalenceSet(sym))
     return false;
+  // CUDA device/managed/unified/shared/pinned variables must not be
+  // initialized with a plain host store; their storage lives in device memory.
+  if (Fortran::semantics::GetCUDADataAttr(&sym))
+    return false;
   return true;
 }
 
@@ -1337,6 +1341,11 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
     return mlir::complex::CreateOp::create(builder, loc, cplxTy, partVal,
                                            partVal);
   }
+  // LOGICAL(k) has a fixed size of k bytes; treat it like an integer splat.
+  if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
+    mlir::Value intCst = makeIntCst(logTy.getFKind() * 8);
+    return builder.createConvert(loc, logTy, intCst);
+  }
   // TODO: CHARACTER falls back to zero; a future improvement should fill each
   // storage unit with the byte pattern.
   return fir::ZeroOp::create(builder, loc, eleTy);
@@ -1427,84 +1436,97 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
   mlir::Value base = fir::getBase(exv);
   mlir::Type storeTy = fir::unwrapRefType(base.getType());
 
-  if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(storeTy)) {
-    // Array: build element constant and use insert_on_range.
-    mlir::Type eleTy = seqTy.getEleTy();
-    auto fpTy = mlir::dyn_cast<mlir::FloatType>(eleTy);
-    auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(eleTy);
-    mlir::Value elePat;
-    switch (mode) {
-    case Fortran::lower::InitLocalKind::Zero:
-      elePat = fir::ZeroOp::create(builder, loc, eleTy);
-      break;
-    case Fortran::lower::InitLocalKind::Hex:
-      elePat = genByteSplatInit(builder, loc, eleTy, hexByte);
-      break;
-    case Fortran::lower::InitLocalKind::QNaN:
-      if (fpTy) {
-        elePat = genFPNaNInit(builder, loc, fpTy, false);
-      } else if (cplxTy) {
-        auto partFpTy = mlir::cast<mlir::FloatType>(cplxTy.getElementType());
-        mlir::Value nanPart = genFPNaNInit(builder, loc, partFpTy, false);
-        elePat = mlir::complex::CreateOp::create(builder, loc, cplxTy, nanPart,
-                                                 nanPart);
-      } else {
-        elePat = genByteSplatInit(builder, loc, eleTy, 0xAA);
-      }
-      break;
-    case Fortran::lower::InitLocalKind::SNaN:
-      if (fpTy) {
-        elePat = genFPNaNInit(builder, loc, fpTy, true);
-      } else if (cplxTy) {
-        auto partFpTy = mlir::cast<mlir::FloatType>(cplxTy.getElementType());
-        mlir::Value nanPart = genFPNaNInit(builder, loc, partFpTy, true);
-        elePat = mlir::complex::CreateOp::create(builder, loc, cplxTy, nanPart,
-                                                 nanPart);
-      } else {
-        elePat = genByteSplatInit(builder, loc, eleTy, 0xAA);
-      }
-      break;
-    default:
-      llvm_unreachable("unexpected InitLocalKind");
-    }
-    // Build flat [lb0,ub0, lb1,ub1, ...] bounds vector.
-    llvm::SmallVector<int64_t> rangeBounds;
-    bool hasUnknown = false;
-    for (auto dim : seqTy.getShape()) {
-      if (dim == fir::SequenceType::getUnknownExtent()) {
-        hasUnknown = true;
-        break;
-      }
-      rangeBounds.push_back(0);
-      rangeBounds.push_back(dim - 1);
-    }
-    if (!hasUnknown) {
-      mlir::Value arrVal = fir::UndefOp::create(builder, loc, seqTy);
-      arrVal =
-          fir::InsertOnRangeOp::create(builder, loc, seqTy, arrVal, elePat,
-                                       builder.getIndexVectorAttr(rangeBounds));
-      fir::StoreOp::create(builder, loc, arrVal, base);
-    }
-  } else if (auto recTy = mlir::dyn_cast<fir::RecordType>(storeTy)) {
-    // Derived type: zero the whole struct, or walk fields for nan/snan/hex.
-    if (mode == Fortran::lower::InitLocalKind::Zero) {
-      fir::StoreOp::create(builder, loc,
-                           fir::ZeroOp::create(builder, loc, recTy), base);
-    } else {
-      for (auto [fieldName, fieldTy] : recTy.getTypeList()) {
-        auto fieldIdx = fir::FieldIndexOp::create(
-            builder, loc, fir::FieldType::get(recTy.getContext()), fieldName,
-            recTy, mlir::ValueRange{});
-        mlir::Value fieldAddr =
-            fir::CoordinateOp::create(builder, loc, builder.getRefType(fieldTy),
-                                      base, mlir::ValueRange{fieldIdx});
-        genInitLocalStore(builder, loc, fieldTy, fieldAddr, mode, hexByte);
-      }
-    }
-  } else {
-    // Scalar (integer, real, complex, logical, character): store directly.
-    genInitLocalStore(builder, loc, storeTy, base, mode, hexByte);
-  }
+  // Recursive helper: dispatch on type to initialize the storage at \p addr
+  // of type \p ty. Handles arrays, derived types, and scalars.
+  std::function<void(mlir::Type, mlir::Value)> initAddr =
+      [&](mlir::Type ty, mlir::Value addr) {
+        if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(ty)) {
+          // Array: build element constant and use insert_on_range.
+          mlir::Type eleTy = seqTy.getEleTy();
+          auto fpTy = mlir::dyn_cast<mlir::FloatType>(eleTy);
+          auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(eleTy);
+          mlir::Value elePat;
+          switch (mode) {
+          case Fortran::lower::InitLocalKind::Zero:
+            elePat = fir::ZeroOp::create(builder, loc, eleTy);
+            break;
+          case Fortran::lower::InitLocalKind::Hex:
+            elePat = genByteSplatInit(builder, loc, eleTy, hexByte);
+            break;
+          case Fortran::lower::InitLocalKind::QNaN:
+            if (fpTy) {
+              elePat = genFPNaNInit(builder, loc, fpTy, false);
+            } else if (cplxTy) {
+              auto partFpTy =
+                  mlir::cast<mlir::FloatType>(cplxTy.getElementType());
+              mlir::Value nanPart = genFPNaNInit(builder, loc, partFpTy, false);
+              elePat = mlir::complex::CreateOp::create(builder, loc, cplxTy,
+                                                       nanPart, nanPart);
+            } else {
+              elePat = genByteSplatInit(builder, loc, eleTy, 0xAA);
+            }
+            break;
+          case Fortran::lower::InitLocalKind::SNaN:
+            if (fpTy) {
+              elePat = genFPNaNInit(builder, loc, fpTy, true);
+            } else if (cplxTy) {
+              auto partFpTy =
+                  mlir::cast<mlir::FloatType>(cplxTy.getElementType());
+              mlir::Value nanPart = genFPNaNInit(builder, loc, partFpTy, true);
+              elePat = mlir::complex::CreateOp::create(builder, loc, cplxTy,
+                                                       nanPart, nanPart);
+            } else {
+              elePat = genByteSplatInit(builder, loc, eleTy, 0xAA);
+            }
+            break;
+          default:
+            llvm_unreachable("unexpected InitLocalKind");
+          }
+          // Build flat [lb0,ub0, lb1,ub1, ...] bounds vector.
+          llvm::SmallVector<int64_t> rangeBounds;
+          // Skip CHARACTER arrays: fir.zero_bits is not a valid insert_on_range
+          // element for character types (runtime-length or fixed). CHARACTER
+          // initialization is a known TODO.
+          bool hasUnknown = mlir::isa<fir::CharacterType>(eleTy);
+          for (auto dim : seqTy.getShape()) {
+            if (dim == fir::SequenceType::getUnknownExtent() || dim == 0) {
+              hasUnknown = true;
+              break;
+            }
+            rangeBounds.push_back(0);
+            rangeBounds.push_back(dim - 1);
+          }
+          if (!hasUnknown) {
+            mlir::Value arrVal = fir::UndefOp::create(builder, loc, seqTy);
+            arrVal = fir::InsertOnRangeOp::create(
+                builder, loc, seqTy, arrVal, elePat,
+                builder.getIndexVectorAttr(rangeBounds));
+            fir::StoreOp::create(builder, loc, arrVal, addr);
+          }
+        } else if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty)) {
+          // Derived type: zero the whole struct, or walk fields for
+          // nan/snan/hex.
+          if (mode == Fortran::lower::InitLocalKind::Zero) {
+            fir::StoreOp::create(
+                builder, loc, fir::ZeroOp::create(builder, loc, recTy), addr);
+          } else {
+            for (auto [fieldName, fieldTy] : recTy.getTypeList()) {
+              auto fieldIdx = fir::FieldIndexOp::create(
+                  builder, loc, fir::FieldType::get(recTy.getContext()),
+                  fieldName, recTy, mlir::ValueRange{});
+              mlir::Value fieldAddr = fir::CoordinateOp::create(
+                  builder, loc, builder.getRefType(fieldTy), addr,
+                  mlir::ValueRange{fieldIdx});
+              initAddr(fieldTy, fieldAddr);
+            }
+          }
+        } else {
+          // Scalar (integer, real, complex, logical, character): store
+          // directly.
+          genInitLocalStore(builder, loc, ty, addr, mode, hexByte);
+        }
+      };
+  initAddr(storeTy, base);
 }
 
 /// Instantiate a local variable. Precondition: Each variable will be visited
diff --git a/flang/test/Lower/finit-local-f128.f90 b/flang/test/Lower/finit-local-f128.f90
index 69b6af69e4cee..b8157e8dbd1e8 100644
--- a/flang/test/Lower/finit-local-f128.f90
+++ b/flang/test/Lower/finit-local-f128.f90
@@ -44,21 +44,21 @@ subroutine test_complex16(res)
   res = x
 end subroutine
 ! ZERO-LABEL: func.func @_QPtest_complex16
-! ZERO: fir.zero_bits !fir.complex<16>
-! ZERO: fir.store {{.*}} : !fir.ref<!fir.complex<16>>
+! ZERO: fir.zero_bits {{!fir\.complex<16>|complex<f128>}}
+! ZERO: fir.store {{.*}} : !fir.ref<{{!fir\.complex<16>|complex<f128>}}>
 
 ! NAN-LABEL:  func.func @_QPtest_complex16
 ! NAN:  arith.constant {{.*}} : f128
 ! NAN:  complex.create {{.*}}, {{.*}} : f128
-! NAN:  fir.store {{.*}} : !fir.ref<!fir.complex<16>>
+! NAN:  fir.store {{.*}} : !fir.ref<{{!fir\.complex<16>|complex<f128>}}>
 
 ! SNAN-LABEL: func.func @_QPtest_complex16
 ! SNAN: arith.constant {{.*}} : f128
 ! SNAN: complex.create {{.*}}, {{.*}} : f128
-! SNAN: fir.store {{.*}} : !fir.ref<!fir.complex<16>>
+! SNAN: fir.store {{.*}} : !fir.ref<{{!fir\.complex<16>|complex<f128>}}>
 
 ! HEX-LABEL:  func.func @_QPtest_complex16
 ! HEX:  arith.constant -113427455640312821154458202477256070486 : i128
 ! HEX:  arith.bitcast {{.*}} : i128 to f128
 ! HEX:  complex.create {{.*}}, {{.*}} : f128
-! HEX:  fir.store {{.*}} : !fir.ref<!fir.complex<16>>
+! HEX:  fir.store {{.*}} : !fir.ref<{{!fir\.complex<16>|complex<f128>}}>
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 379a7842dd0ac..fe2a4adca4066 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -228,11 +228,13 @@ subroutine test_logical1(res)
 ! ZERO: fir.zero_bits !fir.logical<1>
 
 ! NAN-LABEL:  func.func @_QPtest_logical1
-! NAN:  fir.zero_bits !fir.logical<1>
+! NAN:  arith.constant -86 : i8
+! NAN:  fir.convert {{.*}} : (i8) -> !fir.logical<1>
 ! NAN:  fir.store {{.*}} : !fir.ref<!fir.logical<1>>
 
 ! HEX-LABEL:  func.func @_QPtest_logical1
-! HEX:  fir.zero_bits !fir.logical<1>
+! HEX:  arith.constant {{.*}} : i8
+! HEX:  fir.convert {{.*}} : (i8) -> !fir.logical<1>
 ! HEX:  fir.store {{.*}} : !fir.ref<!fir.logical<1>>
 
 ! ---------------------------------------------------------------------------
@@ -247,11 +249,13 @@ subroutine test_logical4(res)
 ! ZERO: fir.zero_bits !fir.logical<4>
 
 ! NAN-LABEL:  func.func @_QPtest_logical4
-! NAN:  fir.zero_bits !fir.logical<4>
+! NAN:  arith.constant -1431655766 : i32
+! NAN:  fir.convert {{.*}} : (i32) -> !fir.logical<4>
 ! NAN:  fir.store {{.*}} : !fir.ref<!fir.logical<4>>
 
 ! HEX-LABEL:  func.func @_QPtest_logical4
-! HEX:  fir.zero_bits !fir.logical<4>
+! HEX:  arith.constant {{.*}} : i32
+! HEX:  fir.convert {{.*}} : (i32) -> !fir.logical<4>
 ! HEX:  fir.store {{.*}} : !fir.ref<!fir.logical<4>>
 
 ! ---------------------------------------------------------------------------
diff --git a/flang/tools/bbc/bbc.cpp b/flang/tools/bbc/bbc.cpp
index bd5a7e6643cac..4e2961259c443 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -526,11 +526,8 @@ static llvm::LogicalResult convertFortranSourceToMLIR(
   loweringOptions.setNoPPCNativeVecElemOrder(enableNoPPCNativeVecElemOrder);
   loweringOptions.setIntegerWrapAround(integerWrapAround);
   loweringOptions.setInitGlobalZero(initGlobalZero);
-  // -finit-local-zero (alias for -finit-local=zero)
-  if (initLocalZero)
-    loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::Zero);
-
-  // -finit-local=
+  // -finit-local= and -finit-local-zero: last occurrence on the command
+  // line wins. Use getPosition() to determine which came last.
   if (!initLocalMode.empty()) {
     llvm::StringRef val = initLocalMode;
     if (val == "zero") {
@@ -546,11 +543,18 @@ static llvm::LogicalResult convertFortranSourceToMLIR(
         loweringOptions.setInitLocalPattern(static_cast<uint8_t>(hexVal));
       } else {
         llvm::errs() << "bbc: invalid -finit-local= value: " << val << "\n";
+        return mlir::failure();
       }
     } else {
       llvm::errs() << "bbc: invalid -finit-local= value: " << val << "\n";
+      return mlir::failure();
     }
   }
+  // If -finit-local-zero appears after -finit-local= on the command line,
+  // it overrides; otherwise -finit-local= already set the mode above.
+  if (initLocalZero &&
+      initLocalZero.getPosition() > initLocalMode.getPosition())
+    loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::Zero);
   loweringOptions.setReallocateLHS(reallocateLHS);
   loweringOptions.setSplitSumExpressionTree(fpSumReassociation);
   loweringOptions.setStackRepackArrays(stackRepackArrays);

>From 03abf1d35ad38eae8b7bdfa5127388ffc08de0b4 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Sat, 15 Aug 2026 11:05:00 -0400
Subject: [PATCH 04/23] [flang] Address MattPD's second round of review
 comments on -finit-local=

- LOGICAL: store the raw iN value via a bitcasted address; fir.convert
  to !fir.logical normalizes any nonzero integer to 1.
- Arrays: use fir.do_loop + fir.coordinate_of for non-zero modes to
  avoid llvm.mlir.constant rejecting non-zero ArrayAttr; skip
  zero-extent and CHARACTER arrays.
- CUDA: allow pinned/unified initialization; exclude only device,
  managed, constant, shared, and usedevice storage.
- bbc: reject an explicitly empty -finit-local= value with non-zero
  exit code.
- finit-local-f128.f90: require complex<f128> exactly.
- Add finit-local-logical-llvm.f90, finit-local-array-llvm.f90,
  finit-local-cuda.cuf regression tests.
---
 flang/lib/Lower/ConvertVariable.cpp           | 97 ++++++++++++++-----
 flang/test/Lower/CUDA/finit-local-cuda.cuf    | 40 ++++++++
 flang/test/Lower/finit-local-array-llvm.f90   | 48 +++++++++
 flang/test/Lower/finit-local-f128.f90         | 16 +--
 flang/test/Lower/finit-local-logical-llvm.f90 | 46 +++++++++
 flang/test/Lower/finit-local.f90              | 52 ++++++----
 flang/tools/bbc/bbc.cpp                       |  6 +-
 7 files changed, 251 insertions(+), 54 deletions(-)
 create mode 100644 flang/test/Lower/CUDA/finit-local-cuda.cuf
 create mode 100644 flang/test/Lower/finit-local-array-llvm.f90
 create mode 100644 flang/test/Lower/finit-local-logical-llvm.f90

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 00cb9f8e30172..1cc07ed088353 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1301,15 +1301,25 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
       return false;
   if (Fortran::semantics::FindEquivalenceSet(sym))
     return false;
-  // CUDA device/managed/unified/shared/pinned variables must not be
-  // initialized with a plain host store; their storage lives in device memory.
-  if (Fortran::semantics::GetCUDADataAttr(&sym))
-    return false;
+  // Skip CUDA variables whose storage is not host-accessible via a plain
+  // store: device, managed, constant, shared, and usedevice all live in
+  // device memory. Pinned and unified memory are host-accessible and may
+  // be initialized normally.
+  if (auto cudaAttr = Fortran::semantics::GetCUDADataAttr(&sym)) {
+    if (*cudaAttr == Fortran::common::CUDADataAttr::Device ||
+        *cudaAttr == Fortran::common::CUDADataAttr::Managed ||
+        *cudaAttr == Fortran::common::CUDADataAttr::Constant ||
+        *cudaAttr == Fortran::common::CUDADataAttr::Shared ||
+        *cudaAttr == Fortran::common::CUDADataAttr::UseDevice) {
+      return false;
+    }
+  }
   return true;
 }
 
 /// Build a constant whose every byte equals \p bytePat.
 /// FP types: bitcast from an integer splat. Complex: apply to both parts.
+/// LOGICAL(k): returns a raw iN integer (caller stores via bitcasted address).
 /// Character: falls back to fir.zero_bits (see TODO). Derived types are
 /// handled by the caller before this function is reached.
 static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
@@ -1341,10 +1351,11 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
     return mlir::complex::CreateOp::create(builder, loc, cplxTy, partVal,
                                            partVal);
   }
-  // LOGICAL(k) has a fixed size of k bytes; treat it like an integer splat.
+  // LOGICAL(k) has a fixed size of k bytes. Return the raw integer splat;
+  // the caller stores it via a bitcasted address to preserve the bit pattern
+  // (fir.convert from integer to !fir.logical normalizes nonzero -> true).
   if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
-    mlir::Value intCst = makeIntCst(logTy.getFKind() * 8);
-    return builder.createConvert(loc, logTy, intCst);
+    return makeIntCst(logTy.getFKind() * 8);
   }
   // TODO: CHARACTER falls back to zero; a future improvement should fill each
   // storage unit with the byte pattern.
@@ -1366,8 +1377,9 @@ static mlir::Value genFPNaNInit(fir::FirOpBuilder &builder, mlir::Location loc,
 }
 
 /// Emit a store of the -finit-local= pattern for a single scalar address.
-/// Complex types get NaN on both parts; other non-FP types use 0xAA byte-splat
-/// for nan/snan modes.
+/// Complex types get NaN on both parts; integer/logical non-FP types use a
+/// 0xAA byte-splat for nan/snan modes. LOGICAL stores via a bitcasted integer
+/// address to preserve the raw bit pattern past fir.convert normalization.
 static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
                               mlir::Type ty, mlir::Value addr,
                               Fortran::lower::InitLocalKind mode,
@@ -1411,12 +1423,24 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
   default:
     llvm_unreachable("unexpected InitLocalKind in genInitLocalStore");
   }
-  fir::StoreOp::create(builder, loc, val, addr);
+  // For LOGICAL types, genByteSplatInit returns a raw integer to preserve
+  // the bit pattern. Store it via a bitcasted address to avoid fir.convert
+  // normalization (which would reduce any nonzero value to logical true).
+  if (mode != Fortran::lower::InitLocalKind::Zero &&
+      mlir::isa<fir::LogicalType>(ty)) {
+    unsigned bits = mlir::cast<fir::LogicalType>(ty).getFKind() * 8;
+    mlir::Type intRefTy = builder.getRefType(builder.getIntegerType(bits));
+    mlir::Value intAddr = builder.createConvert(loc, intRefTy, addr);
+    fir::StoreOp::create(builder, loc, val, intAddr);
+  } else {
+    fir::StoreOp::create(builder, loc, val, addr);
+  }
 }
 
 /// Initialize all storage of the local variable \p var per -finit-local= mode.
-/// Arrays use insert_on_range. Derived types walk fields for nan/snan/hex.
-/// Scalars store directly.
+/// Arrays: zero mode uses insert_on_range; non-zero modes use a do_loop +
+/// coordinate_of to avoid llvm.mlir.constant rejecting non-zero ArrayAttrs.
+/// Derived types walk fields for nan/snan/hex. Scalars store directly.
 static void genInitLocal(Fortran::lower::AbstractConverter &converter,
                          const Fortran::lower::pft::Variable &var,
                          Fortran::lower::SymMap &symMap) {
@@ -1482,26 +1506,51 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
           default:
             llvm_unreachable("unexpected InitLocalKind");
           }
-          // Build flat [lb0,ub0, lb1,ub1, ...] bounds vector.
-          llvm::SmallVector<int64_t> rangeBounds;
-          // Skip CHARACTER arrays: fir.zero_bits is not a valid insert_on_range
-          // element for character types (runtime-length or fixed). CHARACTER
-          // initialization is a known TODO.
+          // Compute static extents; skip arrays with unknown or zero extents,
+          // and CHARACTER arrays (known TODO, pending PR #159788).
           bool hasUnknown = mlir::isa<fir::CharacterType>(eleTy);
+          int64_t totalElems = 1;
           for (auto dim : seqTy.getShape()) {
             if (dim == fir::SequenceType::getUnknownExtent() || dim == 0) {
               hasUnknown = true;
               break;
             }
-            rangeBounds.push_back(0);
-            rangeBounds.push_back(dim - 1);
+            totalElems *= dim;
           }
           if (!hasUnknown) {
-            mlir::Value arrVal = fir::UndefOp::create(builder, loc, seqTy);
-            arrVal = fir::InsertOnRangeOp::create(
-                builder, loc, seqTy, arrVal, elePat,
-                builder.getIndexVectorAttr(rangeBounds));
-            fir::StoreOp::create(builder, loc, arrVal, addr);
+            if (mode == Fortran::lower::InitLocalKind::Zero) {
+              // Zero mode: fir.insert_on_range + store works correctly through
+              // LLVM lowering (ZeroAttr is accepted by llvm.mlir.constant).
+              llvm::SmallVector<int64_t> rangeBounds;
+              for (auto dim : seqTy.getShape()) {
+                rangeBounds.push_back(0);
+                rangeBounds.push_back(dim - 1);
+              }
+              mlir::Value arrVal = fir::UndefOp::create(builder, loc, seqTy);
+              arrVal = fir::InsertOnRangeOp::create(
+                  builder, loc, seqTy, arrVal, elePat,
+                  builder.getIndexVectorAttr(rangeBounds));
+              fir::StoreOp::create(builder, loc, arrVal, addr);
+            } else {
+              // Non-zero modes: fir.insert_on_range fails at LLVM lowering
+              // because llvm.mlir.constant does not accept ArrayAttr of
+              // non-zero scalars. Use a flat do_loop + coordinate_of instead.
+              mlir::Type idxTy = builder.getIndexType();
+              mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
+              mlir::Value last =
+                  builder.createIntegerConstant(loc, idxTy, totalElems - 1);
+              mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+              auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
+                                                /*unordered=*/false,
+                                                /*finalCount=*/false);
+              mlir::OpBuilder::InsertionGuard guard(builder);
+              builder.setInsertionPointToStart(loop.getBody());
+              mlir::Value iv = loop.getInductionVar();
+              mlir::Value elemAddr = fir::CoordinateOp::create(
+                  builder, loc, builder.getRefType(eleTy), addr,
+                  mlir::ValueRange{iv});
+              genInitLocalStore(builder, loc, eleTy, elemAddr, mode, hexByte);
+            }
           }
         } else if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty)) {
           // Derived type: zero the whole struct, or walk fields for
diff --git a/flang/test/Lower/CUDA/finit-local-cuda.cuf b/flang/test/Lower/CUDA/finit-local-cuda.cuf
new file mode 100644
index 0000000000000..3b45c4242df0f
--- /dev/null
+++ b/flang/test/Lower/CUDA/finit-local-cuda.cuf
@@ -0,0 +1,40 @@
+! Tests that -finit-local= respects CUDA data attributes in shouldInitLocal.
+! Variables in device-only memory (device, managed, constant, shared) must NOT
+! be initialized via a plain host store. Host-accessible storage (pinned,
+! unified) MAY be initialized normally.
+!
+! RUN: bbc -emit-hlfir -fcuda -finit-local=zero -o - %s | FileCheck %s
+
+module finit_cuda_mod
+contains
+
+  ! device: storage lives in device memory -- must NOT be initialized.
+  subroutine test_device(res)
+    real, device :: x
+    real :: res
+    res = x
+  end subroutine
+  ! CHECK-LABEL: func.func @_QMfinit_cuda_modPtest_device
+  ! CHECK-NOT: fir.zero_bits f32
+
+  ! unified: host-accessible -- MUST be initialized.
+  subroutine test_unified(res)
+    real, unified :: x
+    real :: res
+    res = x
+  end subroutine
+  ! CHECK-LABEL: func.func @_QMfinit_cuda_modPtest_unified
+  ! CHECK: fir.zero_bits f32
+  ! CHECK: fir.store
+
+  ! pinned (non-allocatable): host-accessible -- MUST be initialized.
+  subroutine test_pinned(res)
+    real, pinned :: x
+    real :: res
+    res = x
+  end subroutine
+  ! CHECK-LABEL: func.func @_QMfinit_cuda_modPtest_pinned
+  ! CHECK: fir.zero_bits f32
+  ! CHECK: fir.store
+
+end module
diff --git a/flang/test/Lower/finit-local-array-llvm.f90 b/flang/test/Lower/finit-local-array-llvm.f90
new file mode 100644
index 0000000000000..ff777639131e2
--- /dev/null
+++ b/flang/test/Lower/finit-local-array-llvm.f90
@@ -0,0 +1,48 @@
+! Tests that -finit-local= with non-zero patterns produces correct LLVM IR for
+! static arrays and arrays inside derived types. Previously, fir.insert_on_range
+! with a non-zero element failed at LLVM lowering because llvm.mlir.constant
+! does not accept ArrayAttr of non-zero scalars. The fix uses a do_loop +
+! coordinate_of instead, which lowers correctly through to LLVM IR.
+!
+! RUN: %flang_fc1 -emit-llvm -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
+! RUN: %flang_fc1 -emit-llvm -finit-local=nan  %s -o - | FileCheck --check-prefix=NAN %s
+! RUN: %flang_fc1 -emit-llvm -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+
+! ---------------------------------------------------------------------------
+! Static 1-D array INTEGER(4)(4)
+! ---------------------------------------------------------------------------
+subroutine test_int_array(res)
+  integer(4) :: res(4)
+  integer(4) :: x(4)
+  res = x
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_int_array{{.*}}(
+! HEX:  store i32 -1431655766,
+! HEX-NOT: store i32 0,
+
+! NAN-LABEL: define {{.*}}@{{.*}}test_int_array{{.*}}(
+! NAN:  store i32 -1431655766,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_int_array{{.*}}(
+! ZERO: store [4 x i32] zeroinitializer,
+
+! ---------------------------------------------------------------------------
+! Derived type with an array-valued field  (Thread 2 regression)
+! type t; integer :: a(2); end type; type(t) :: x
+! ---------------------------------------------------------------------------
+subroutine test_array_in_struct(res)
+  type :: t
+    integer(4) :: a(2)
+  end type
+  type(t) :: res
+  type(t) :: x
+  res = x
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_array_in_struct{{.*}}(
+! HEX:  store i32 -1431655766,
+
+! NAN-LABEL: define {{.*}}@{{.*}}test_array_in_struct{{.*}}(
+! NAN:  store i32 -1431655766,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_array_in_struct{{.*}}(
+! ZERO: store {{.*}} zeroinitializer,
diff --git a/flang/test/Lower/finit-local-f128.f90 b/flang/test/Lower/finit-local-f128.f90
index b8157e8dbd1e8..937b2e86bb649 100644
--- a/flang/test/Lower/finit-local-f128.f90
+++ b/flang/test/Lower/finit-local-f128.f90
@@ -44,21 +44,21 @@ subroutine test_complex16(res)
   res = x
 end subroutine
 ! ZERO-LABEL: func.func @_QPtest_complex16
-! ZERO: fir.zero_bits {{!fir\.complex<16>|complex<f128>}}
-! ZERO: fir.store {{.*}} : !fir.ref<{{!fir\.complex<16>|complex<f128>}}>
+! ZERO: fir.zero_bits complex<f128>
+! ZERO: fir.store {{.*}} : !fir.ref<complex<f128>>
 
 ! NAN-LABEL:  func.func @_QPtest_complex16
 ! NAN:  arith.constant {{.*}} : f128
-! NAN:  complex.create {{.*}}, {{.*}} : f128
-! NAN:  fir.store {{.*}} : !fir.ref<{{!fir\.complex<16>|complex<f128>}}>
+! NAN:  complex.create {{.*}}, {{.*}} : complex<f128>
+! NAN:  fir.store {{.*}} : !fir.ref<complex<f128>>
 
 ! SNAN-LABEL: func.func @_QPtest_complex16
 ! SNAN: arith.constant {{.*}} : f128
-! SNAN: complex.create {{.*}}, {{.*}} : f128
-! SNAN: fir.store {{.*}} : !fir.ref<{{!fir\.complex<16>|complex<f128>}}>
+! SNAN: complex.create {{.*}}, {{.*}} : complex<f128>
+! SNAN: fir.store {{.*}} : !fir.ref<complex<f128>>
 
 ! HEX-LABEL:  func.func @_QPtest_complex16
 ! HEX:  arith.constant -113427455640312821154458202477256070486 : i128
 ! HEX:  arith.bitcast {{.*}} : i128 to f128
-! HEX:  complex.create {{.*}}, {{.*}} : f128
-! HEX:  fir.store {{.*}} : !fir.ref<{{!fir\.complex<16>|complex<f128>}}>
+! HEX:  complex.create {{.*}}, {{.*}} : complex<f128>
+! HEX:  fir.store {{.*}} : !fir.ref<complex<f128>>
diff --git a/flang/test/Lower/finit-local-logical-llvm.f90 b/flang/test/Lower/finit-local-logical-llvm.f90
new file mode 100644
index 0000000000000..6f41b78883b59
--- /dev/null
+++ b/flang/test/Lower/finit-local-logical-llvm.f90
@@ -0,0 +1,46 @@
+! Tests that -finit-local= preserves the requested bit pattern for LOGICAL
+! variables in the final LLVM IR. fir.convert from integer to !fir.logical
+! normalizes any nonzero value to .TRUE. (i.e. 1); the fix stores via a
+! bitcasted integer address instead so the bit pattern is preserved.
+!
+! RUN: %flang_fc1 -emit-llvm -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
+! RUN: %flang_fc1 -emit-llvm -finit-local=nan  %s -o - | FileCheck --check-prefix=NAN %s
+! RUN: %flang_fc1 -emit-llvm -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+
+! ---------------------------------------------------------------------------
+! LOGICAL(1) -- 1-byte storage; 0xAA byte-splat = -86 (i8), NOT i8 1
+! ---------------------------------------------------------------------------
+subroutine test_logical1(res)
+  logical(1) :: res
+  logical(1) :: x
+  res = x
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_logical1{{.*}}(
+! HEX:  store i8 -86,
+! HEX-NOT: store i8 1,
+
+! NAN-LABEL: define {{.*}}@{{.*}}test_logical1{{.*}}(
+! NAN:  store i8 -86,
+! NAN-NOT: store i8 1,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_logical1{{.*}}(
+! ZERO: store i8 0,
+
+! ---------------------------------------------------------------------------
+! LOGICAL(4) -- 4-byte storage; 0xAA byte-splat = -1431655766 (i32), NOT i32 1
+! ---------------------------------------------------------------------------
+subroutine test_logical4(res)
+  logical(4) :: res
+  logical(4) :: x
+  res = x
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_logical4{{.*}}(
+! HEX:  store i32 -1431655766,
+! HEX-NOT: store i32 1,
+
+! NAN-LABEL: define {{.*}}@{{.*}}test_logical4{{.*}}(
+! NAN:  store i32 -1431655766,
+! NAN-NOT: store i32 1,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_logical4{{.*}}(
+! ZERO: store i32 0,
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index fe2a4adca4066..752b9c4f84c7d 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -17,6 +17,10 @@
 ! RUN: bbc -emit-hlfir -finit-local=0xAA  -o - %s | FileCheck --check-prefix=HEX   %s
 ! RUN: bbc -emit-hlfir                    -o - %s | FileCheck --check-prefix=OFF   %s
 ! RUN: bbc -emit-hlfir -finit-local-zero  -o - %s | FileCheck --check-prefix=ZERO  %s
+! --- Empty value should be rejected by bbc ---
+! RUN: not bbc -emit-hlfir -finit-local=   -o - %s 2>&1 | FileCheck --check-prefix=EMPTY %s
+
+! EMPTY: bbc: invalid -finit-local= value: (empty)
 
 ! ---------------------------------------------------------------------------
 ! INTEGER(1) -- 1-byte: pattern 0xAA = -86 (signed) = 170 (unsigned)
@@ -229,13 +233,13 @@ subroutine test_logical1(res)
 
 ! NAN-LABEL:  func.func @_QPtest_logical1
 ! NAN:  arith.constant -86 : i8
-! NAN:  fir.convert {{.*}} : (i8) -> !fir.logical<1>
-! NAN:  fir.store {{.*}} : !fir.ref<!fir.logical<1>>
+! NAN:  fir.convert {{.*}} : (!fir.ref<!fir.logical<1>>) -> !fir.ref<i8>
+! NAN:  fir.store {{.*}} : !fir.ref<i8>
 
 ! HEX-LABEL:  func.func @_QPtest_logical1
 ! HEX:  arith.constant {{.*}} : i8
-! HEX:  fir.convert {{.*}} : (i8) -> !fir.logical<1>
-! HEX:  fir.store {{.*}} : !fir.ref<!fir.logical<1>>
+! HEX:  fir.convert {{.*}} : (!fir.ref<!fir.logical<1>>) -> !fir.ref<i8>
+! HEX:  fir.store {{.*}} : !fir.ref<i8>
 
 ! ---------------------------------------------------------------------------
 ! LOGICAL(4) -- stored as i32; pattern 0xAAAAAAAA = -1431655766
@@ -250,13 +254,13 @@ subroutine test_logical4(res)
 
 ! NAN-LABEL:  func.func @_QPtest_logical4
 ! NAN:  arith.constant -1431655766 : i32
-! NAN:  fir.convert {{.*}} : (i32) -> !fir.logical<4>
-! NAN:  fir.store {{.*}} : !fir.ref<!fir.logical<4>>
+! NAN:  fir.convert {{.*}} : (!fir.ref<!fir.logical<4>>) -> !fir.ref<i32>
+! NAN:  fir.store {{.*}} : !fir.ref<i32>
 
 ! HEX-LABEL:  func.func @_QPtest_logical4
 ! HEX:  arith.constant {{.*}} : i32
-! HEX:  fir.convert {{.*}} : (i32) -> !fir.logical<4>
-! HEX:  fir.store {{.*}} : !fir.ref<!fir.logical<4>>
+! HEX:  fir.convert {{.*}} : (!fir.ref<!fir.logical<4>>) -> !fir.ref<i32>
+! HEX:  fir.store {{.*}} : !fir.ref<i32>
 
 ! ---------------------------------------------------------------------------
 ! CHARACTER(10) -- fir::CharacterType is not mlir::FloatType/IntegerType/ComplexType
@@ -330,12 +334,14 @@ subroutine test_int_array(res)
 ! ZERO: fir.store {{.*}} : !fir.ref<!fir.array<4xi32>>
 
 ! NAN-LABEL:  func.func @_QPtest_int_array
-! NAN:  fir.insert_on_range {{.*}} from (0) to (3)
-! NAN:  fir.store {{.*}} : !fir.ref<!fir.array<4xi32>>
+! NAN:  fir.do_loop
+! NAN:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<4xi32>>, index) -> !fir.ref<i32>
+! NAN:  fir.store {{.*}} : !fir.ref<i32>
 
 ! HEX-LABEL:  func.func @_QPtest_int_array
-! HEX:  fir.insert_on_range {{.*}} from (0) to (3)
-! HEX:  fir.store {{.*}} : !fir.ref<!fir.array<4xi32>>
+! HEX:  fir.do_loop
+! HEX:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<4xi32>>, index) -> !fir.ref<i32>
+! HEX:  fir.store {{.*}} : !fir.ref<i32>
 
 ! OFF-LABEL: func.func @_QPtest_int_array
 ! OFF-NOT: fir.insert_on_range
@@ -353,19 +359,22 @@ subroutine test_real_array(res)
 ! ZERO: fir.store {{.*}} : !fir.ref<!fir.array<4xf32>>
 
 ! NAN-LABEL:  func.func @_QPtest_real_array
-! NAN:  fir.insert_on_range {{.*}} from (0) to (3)
-! NAN:  fir.store {{.*}} : !fir.ref<!fir.array<4xf32>>
+! NAN:  fir.do_loop
+! NAN:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<4xf32>>, index) -> !fir.ref<f32>
+! NAN:  fir.store {{.*}} : !fir.ref<f32>
 
 ! SNAN-LABEL: func.func @_QPtest_real_array
-! SNAN: fir.insert_on_range {{.*}} from (0) to (3)
-! SNAN: fir.store {{.*}} : !fir.ref<!fir.array<4xf32>>
+! SNAN: fir.do_loop
+! SNAN: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<4xf32>>, index) -> !fir.ref<f32>
+! SNAN: fir.store {{.*}} : !fir.ref<f32>
 
 ! HEX-LABEL:  func.func @_QPtest_real_array
-! HEX:  fir.insert_on_range {{.*}} from (0) to (3)
-! HEX:  fir.store {{.*}} : !fir.ref<!fir.array<4xf32>>
+! HEX:  fir.do_loop
+! HEX:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<4xf32>>, index) -> !fir.ref<f32>
+! HEX:  fir.store {{.*}} : !fir.ref<f32>
 
 ! ---------------------------------------------------------------------------
-! Array INTEGER(4)(3,4) -- 2-D; insert_on_range with two-dimension bounds
+! Array INTEGER(4)(3,4) -- 2-D; zero uses insert_on_range, hex uses do_loop
 ! ---------------------------------------------------------------------------
 subroutine test_int_array_2d(res)
   integer(4) :: res(3,4)
@@ -377,8 +386,9 @@ subroutine test_int_array_2d(res)
 ! ZERO: fir.store {{.*}} : !fir.ref<!fir.array<3x4xi32>>
 
 ! HEX-LABEL: func.func @_QPtest_int_array_2d
-! HEX:  fir.insert_on_range {{.*}} from (0, 0) to (2, 3)
-! HEX:  fir.store {{.*}} : !fir.ref<!fir.array<3x4xi32>>
+! HEX:  fir.do_loop
+! HEX:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<3x4xi32>>, index) -> !fir.ref<i32>
+! HEX:  fir.store {{.*}} : !fir.ref<i32>
 
 ! ---------------------------------------------------------------------------
 ! Exclusion: explicit init (= 42) -- must NOT be touched
diff --git a/flang/tools/bbc/bbc.cpp b/flang/tools/bbc/bbc.cpp
index 4e2961259c443..2aef94d2eddda 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -528,8 +528,12 @@ static llvm::LogicalResult convertFortranSourceToMLIR(
   loweringOptions.setInitGlobalZero(initGlobalZero);
   // -finit-local= and -finit-local-zero: last occurrence on the command
   // line wins. Use getPosition() to determine which came last.
-  if (!initLocalMode.empty()) {
+  if (initLocalMode.getNumOccurrences() > 0) {
     llvm::StringRef val = initLocalMode;
+    if (val.empty()) {
+      llvm::errs() << "bbc: invalid -finit-local= value: (empty)\n";
+      return mlir::failure();
+    }
     if (val == "zero") {
       loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::Zero);
     } else if (val == "nan") {

>From 0424cc7943cd5a17eed5bd1490ab4f8d7d87f6be Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Sat, 15 Aug 2026 22:05:18 -0400
Subject: [PATCH 05/23] [flang] Address MattPD's third round of review comments
 on -finit-local=

- shouldInitLocal: remove Managed from CUDA exclusion list; managed
  (non-allocatable) storage is host-accessible via a plain fir.store,
  like unified and pinned
- genInitLocalStore: move CHARACTER(0) early-return before the switch
  so the zero-length guard fires before val is computed
- shouldInitLocal docstring: list CUDA exclusions explicitly
- genInitLocal docstring: reflect that all modes now use fir.do_loop
- bbc: evaluate getPosition() before validation so that sequences like
  -finit-local=bogus -finit-local-zero select zero rather than failing
  on the overridden value
- finit-local-cuda.cuf: add test_managed (managed non-allocatable
  scalar) and test_global_local (per-thread local in attributes(global)
  kernel)
- finit-local.f90: add RUN lines for -finit-local= -finit-local-zero
  and -finit-local=bogus -finit-local-zero last-wins sequences
---
 flang/lib/Lower/ConvertVariable.cpp         | 122 +++++++-------------
 flang/test/Lower/CUDA/finit-local-cuda.cuf  |  31 ++++-
 flang/test/Lower/finit-local-array-llvm.f90 |  86 +++++++++++++-
 flang/test/Lower/finit-local.f90            |  51 +++++---
 flang/tools/bbc/bbc.cpp                     |  15 +--
 5 files changed, 198 insertions(+), 107 deletions(-)

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 1cc07ed088353..114512a868acc 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1282,7 +1282,9 @@ getSafeRepackAttrs(Fortran::lower::AbstractConverter &converter) {
 /// Returns true when \p var is an automatic local variable eligible for
 /// -finit-local= initialization. Excluded: variables without a symbol,
 /// globals, dummy arguments, SAVE'd vars, ALLOCATABLE/POINTER, vars in
-/// an EQUIVALENCE set, and vars with explicit or default initialization.
+/// an EQUIVALENCE set, vars with explicit or default initialization, and
+/// CUDA variables whose storage is not host-accessible (device, constant,
+/// shared, usedevice).
 static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
   if (!var.hasSymbol() || var.isGlobal())
     return false;
@@ -1302,12 +1304,12 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
   if (Fortran::semantics::FindEquivalenceSet(sym))
     return false;
   // Skip CUDA variables whose storage is not host-accessible via a plain
-  // store: device, managed, constant, shared, and usedevice all live in
-  // device memory. Pinned and unified memory are host-accessible and may
-  // be initialized normally.
+  // fir.store: device, constant, shared, and usedevice live exclusively in
+  // device memory and cannot be initialized with a host store. Managed,
+  // unified, and pinned memory are host-accessible and may be initialized
+  // normally with a host store.
   if (auto cudaAttr = Fortran::semantics::GetCUDADataAttr(&sym)) {
     if (*cudaAttr == Fortran::common::CUDADataAttr::Device ||
-        *cudaAttr == Fortran::common::CUDADataAttr::Managed ||
         *cudaAttr == Fortran::common::CUDADataAttr::Constant ||
         *cudaAttr == Fortran::common::CUDADataAttr::Shared ||
         *cudaAttr == Fortran::common::CUDADataAttr::UseDevice) {
@@ -1384,6 +1386,10 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
                               mlir::Type ty, mlir::Value addr,
                               Fortran::lower::InitLocalKind mode,
                               uint8_t hexByte) {
+  // CHARACTER(0) has zero-length storage -- nothing to initialize.
+  if (auto charTy = mlir::dyn_cast<fir::CharacterType>(ty))
+    if (charTy.getLen() == 0)
+      return;
   mlir::Value val;
   auto fpTy = mlir::dyn_cast<mlir::FloatType>(ty);
   auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(ty);
@@ -1438,8 +1444,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
 }
 
 /// Initialize all storage of the local variable \p var per -finit-local= mode.
-/// Arrays: zero mode uses insert_on_range; non-zero modes use a do_loop +
-/// coordinate_of to avoid llvm.mlir.constant rejecting non-zero ArrayAttrs.
+/// Arrays: all modes use a flat fir.do_loop + fir.coordinate_of over a
+/// rank-1 view to avoid both the llvm.mlir.constant crash on non-zero
+/// ArrayAttrs and the quadratic compile time of fir.insert_on_range.
 /// Derived types walk fields for nan/snan/hex. Scalars store directly.
 static void genInitLocal(Fortran::lower::AbstractConverter &converter,
                          const Fortran::lower::pft::Variable &var,
@@ -1465,49 +1472,14 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
   std::function<void(mlir::Type, mlir::Value)> initAddr =
       [&](mlir::Type ty, mlir::Value addr) {
         if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(ty)) {
-          // Array: build element constant and use insert_on_range.
+          // Array: use a flat fir.do_loop over all elements. Cast to a
+          // rank-1 unknown-extent ref so the flat IV is a valid single-
+          // coordinate index regardless of array rank. This avoids both
+          // the LLVM lowering crash (non-zero ArrayAttr) and the quadratic
+          // compile time of fir.insert_on_range for large arrays.
           mlir::Type eleTy = seqTy.getEleTy();
-          auto fpTy = mlir::dyn_cast<mlir::FloatType>(eleTy);
-          auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(eleTy);
-          mlir::Value elePat;
-          switch (mode) {
-          case Fortran::lower::InitLocalKind::Zero:
-            elePat = fir::ZeroOp::create(builder, loc, eleTy);
-            break;
-          case Fortran::lower::InitLocalKind::Hex:
-            elePat = genByteSplatInit(builder, loc, eleTy, hexByte);
-            break;
-          case Fortran::lower::InitLocalKind::QNaN:
-            if (fpTy) {
-              elePat = genFPNaNInit(builder, loc, fpTy, false);
-            } else if (cplxTy) {
-              auto partFpTy =
-                  mlir::cast<mlir::FloatType>(cplxTy.getElementType());
-              mlir::Value nanPart = genFPNaNInit(builder, loc, partFpTy, false);
-              elePat = mlir::complex::CreateOp::create(builder, loc, cplxTy,
-                                                       nanPart, nanPart);
-            } else {
-              elePat = genByteSplatInit(builder, loc, eleTy, 0xAA);
-            }
-            break;
-          case Fortran::lower::InitLocalKind::SNaN:
-            if (fpTy) {
-              elePat = genFPNaNInit(builder, loc, fpTy, true);
-            } else if (cplxTy) {
-              auto partFpTy =
-                  mlir::cast<mlir::FloatType>(cplxTy.getElementType());
-              mlir::Value nanPart = genFPNaNInit(builder, loc, partFpTy, true);
-              elePat = mlir::complex::CreateOp::create(builder, loc, cplxTy,
-                                                       nanPart, nanPart);
-            } else {
-              elePat = genByteSplatInit(builder, loc, eleTy, 0xAA);
-            }
-            break;
-          default:
-            llvm_unreachable("unexpected InitLocalKind");
-          }
-          // Compute static extents; skip arrays with unknown or zero extents,
-          // and CHARACTER arrays (known TODO, pending PR #159788).
+          // Skip arrays with unknown or zero extents, and CHARACTER arrays
+          // (known TODO, pending PR #159788).
           bool hasUnknown = mlir::isa<fir::CharacterType>(eleTy);
           int64_t totalElems = 1;
           for (auto dim : seqTy.getShape()) {
@@ -1518,39 +1490,25 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
             totalElems *= dim;
           }
           if (!hasUnknown) {
-            if (mode == Fortran::lower::InitLocalKind::Zero) {
-              // Zero mode: fir.insert_on_range + store works correctly through
-              // LLVM lowering (ZeroAttr is accepted by llvm.mlir.constant).
-              llvm::SmallVector<int64_t> rangeBounds;
-              for (auto dim : seqTy.getShape()) {
-                rangeBounds.push_back(0);
-                rangeBounds.push_back(dim - 1);
-              }
-              mlir::Value arrVal = fir::UndefOp::create(builder, loc, seqTy);
-              arrVal = fir::InsertOnRangeOp::create(
-                  builder, loc, seqTy, arrVal, elePat,
-                  builder.getIndexVectorAttr(rangeBounds));
-              fir::StoreOp::create(builder, loc, arrVal, addr);
-            } else {
-              // Non-zero modes: fir.insert_on_range fails at LLVM lowering
-              // because llvm.mlir.constant does not accept ArrayAttr of
-              // non-zero scalars. Use a flat do_loop + coordinate_of instead.
-              mlir::Type idxTy = builder.getIndexType();
-              mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
-              mlir::Value last =
-                  builder.createIntegerConstant(loc, idxTy, totalElems - 1);
-              mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
-              auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
-                                                /*unordered=*/false,
-                                                /*finalCount=*/false);
-              mlir::OpBuilder::InsertionGuard guard(builder);
-              builder.setInsertionPointToStart(loop.getBody());
-              mlir::Value iv = loop.getInductionVar();
-              mlir::Value elemAddr = fir::CoordinateOp::create(
-                  builder, loc, builder.getRefType(eleTy), addr,
-                  mlir::ValueRange{iv});
-              genInitLocalStore(builder, loc, eleTy, elemAddr, mode, hexByte);
-            }
+            mlir::Type idxTy = builder.getIndexType();
+            mlir::Type rank1SeqTy = fir::SequenceType::get(
+                {fir::SequenceType::getUnknownExtent()}, eleTy);
+            mlir::Value rank1Addr = builder.createConvert(
+                loc, builder.getRefType(rank1SeqTy), addr);
+            mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
+            mlir::Value last =
+                builder.createIntegerConstant(loc, idxTy, totalElems - 1);
+            mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+            auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
+                                              /*unordered=*/false,
+                                              /*finalCount=*/false);
+            mlir::OpBuilder::InsertionGuard guard(builder);
+            builder.setInsertionPointToStart(loop.getBody());
+            mlir::Value iv = loop.getInductionVar();
+            mlir::Value elemAddr = fir::CoordinateOp::create(
+                builder, loc, builder.getRefType(eleTy), rank1Addr,
+                mlir::ValueRange{iv});
+            initAddr(eleTy, elemAddr);
           }
         } else if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty)) {
           // Derived type: zero the whole struct, or walk fields for
diff --git a/flang/test/Lower/CUDA/finit-local-cuda.cuf b/flang/test/Lower/CUDA/finit-local-cuda.cuf
index 3b45c4242df0f..a9b25e9676db0 100644
--- a/flang/test/Lower/CUDA/finit-local-cuda.cuf
+++ b/flang/test/Lower/CUDA/finit-local-cuda.cuf
@@ -1,7 +1,8 @@
 ! Tests that -finit-local= respects CUDA data attributes in shouldInitLocal.
-! Variables in device-only memory (device, managed, constant, shared) must NOT
-! be initialized via a plain host store. Host-accessible storage (pinned,
-! unified) MAY be initialized normally.
+! Variables in device-only memory (device, constant, shared) must NOT be
+! initialized via a plain host store. Host-accessible storage (managed, pinned,
+! unified) and per-thread locals in device procedures MAY be initialized
+! normally.
 !
 ! RUN: bbc -emit-hlfir -fcuda -finit-local=zero -o - %s | FileCheck %s
 
@@ -37,4 +38,28 @@ contains
   ! CHECK: fir.zero_bits f32
   ! CHECK: fir.store
 
+
+  ! managed (non-allocatable): host-accessible unified memory -- MUST be
+  ! initialized. An explicit host assignment to a managed scalar lowers to
+  ! hlfir.assign, so a plain fir.store initialization is valid here.
+  subroutine test_managed(res)
+    real, managed :: x
+    real :: res
+    res = x
+  end subroutine
+  ! CHECK-LABEL: func.func @_QMfinit_cuda_modPtest_managed
+  ! CHECK: fir.zero_bits f32
+  ! CHECK: fir.store
+
 end module
+
+! Per-thread locals in an attributes(global) kernel are stack-allocated via
+! fir.alloca and run on the device; a plain fir.store in device code IS the
+! correct initialization. Verify that shouldInitLocal includes them.
+attributes(global) subroutine test_global_local()
+  integer :: n
+  n = 42
+end subroutine
+! CHECK-LABEL: func.func @{{.*}}test_global_local
+! CHECK: fir.zero_bits i32
+! CHECK: fir.store
diff --git a/flang/test/Lower/finit-local-array-llvm.f90 b/flang/test/Lower/finit-local-array-llvm.f90
index ff777639131e2..718a3954f2ca7 100644
--- a/flang/test/Lower/finit-local-array-llvm.f90
+++ b/flang/test/Lower/finit-local-array-llvm.f90
@@ -24,7 +24,7 @@ subroutine test_int_array(res)
 ! NAN:  store i32 -1431655766,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_int_array{{.*}}(
-! ZERO: store [4 x i32] zeroinitializer,
+! ZERO: store i32 0,
 
 ! ---------------------------------------------------------------------------
 ! Derived type with an array-valued field  (Thread 2 regression)
@@ -46,3 +46,87 @@ subroutine test_array_in_struct(res)
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_array_in_struct{{.*}}(
 ! ZERO: store {{.*}} zeroinitializer,
+
+! ---------------------------------------------------------------------------
+! Rank-2 array INTEGER(4)(3,4) -- flat loop must index via rank-1 view
+! ---------------------------------------------------------------------------
+subroutine test_int_array_2d(res)
+  integer(4) :: res(3,4)
+  integer(4) :: x(3,4)
+  res = x
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_int_array_2d{{.*}}(
+! HEX:  store i32 -1431655766,
+! HEX-NOT: store i32 0,
+
+! NAN-LABEL: define {{.*}}@{{.*}}test_int_array_2d{{.*}}(
+! NAN:  store i32 -1431655766,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_int_array_2d{{.*}}(
+! ZERO: store i32 0,
+
+! ---------------------------------------------------------------------------
+! Rank-3 array INTEGER(4)(2,3,4) -- flat loop must index via rank-1 view
+! ---------------------------------------------------------------------------
+subroutine test_int_array_3d(res)
+  integer(4) :: res(2,3,4)
+  integer(4) :: x(2,3,4)
+  res = x
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_int_array_3d{{.*}}(
+! HEX:  store i32 -1431655766,
+! HEX-NOT: store i32 0,
+
+! NAN-LABEL: define {{.*}}@{{.*}}test_int_array_3d{{.*}}(
+! NAN:  store i32 -1431655766,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_int_array_3d{{.*}}(
+! ZERO: store i32 0,
+
+! ---------------------------------------------------------------------------
+! Array of derived type  type(t) :: x(2)  (Thread 3/4 regression)
+! Each element is a record; the loop must call initAddr per element so
+! record fields are walked rather than emitting zeroinitializer.
+! ---------------------------------------------------------------------------
+subroutine test_array_of_struct(res)
+  type :: t
+    integer(4) :: a
+    integer(4) :: b
+  end type
+  type(t) :: res(2)
+  type(t) :: x(2)
+  res = x
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_array_of_struct{{.*}}(
+! HEX:  store i32 -1431655766,
+! HEX-NOT: store {{.*}} zeroinitializer,
+
+! NAN-LABEL: define {{.*}}@{{.*}}test_array_of_struct{{.*}}(
+! NAN:  store i32 -1431655766,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_struct{{.*}}(
+! ZERO: store {{.*}} zeroinitializer,
+
+! ---------------------------------------------------------------------------
+! Rank-2 array of derived type  type(t) :: x(2,3)
+! Flat loop must stride by sizeof(%t) via rank-1 view; initAddr recurses
+! into the record so all fields of all 6 elements receive the pattern.
+! ---------------------------------------------------------------------------
+subroutine test_array_of_struct_2d(res)
+  type :: t
+    integer(4) :: a
+    integer(4) :: b
+  end type
+  type(t) :: res(2,3)
+  type(t) :: x(2,3)
+  res = x
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_array_of_struct_2d{{.*}}(
+! HEX:  store i32 -1431655766,
+! HEX-NOT: store {{.*}} zeroinitializer,
+
+! NAN-LABEL: define {{.*}}@{{.*}}test_array_of_struct_2d{{.*}}(
+! NAN:  store i32 -1431655766,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_struct_2d{{.*}}(
+! ZERO: store {{.*}} zeroinitializer,
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 752b9c4f84c7d..416625613d943 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -19,6 +19,9 @@
 ! RUN: bbc -emit-hlfir -finit-local-zero  -o - %s | FileCheck --check-prefix=ZERO  %s
 ! --- Empty value should be rejected by bbc ---
 ! RUN: not bbc -emit-hlfir -finit-local=   -o - %s 2>&1 | FileCheck --check-prefix=EMPTY %s
+! --- Last option wins before validation: -finit-local-zero after a bad value selects zero ---
+! RUN: bbc -emit-hlfir -finit-local=   -finit-local-zero -o - %s | FileCheck --check-prefix=ZERO %s
+! RUN: bbc -emit-hlfir -finit-local=bogus -finit-local-zero -o - %s | FileCheck --check-prefix=ZERO %s
 
 ! EMPTY: bbc: invalid -finit-local= value: (empty)
 
@@ -287,6 +290,22 @@ subroutine test_char10(res)
 ! HEX:  fir.zero_bits !fir.char<1,10>
 ! HEX:  fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
 
+! ---------------------------------------------------------------------------
+! CHARACTER(0) -- zero-length: no store should be emitted (guard for
+! zero-byte allocation; writing through it would be out of bounds).
+! ---------------------------------------------------------------------------
+subroutine test_char0(res)
+  character(0) :: res
+  character(0) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_char0
+! ZERO-NOT: fir.store {{.*}} : !fir.ref<!fir.char<1,0>>
+
+! HEX-LABEL:  func.func @_QPtest_char0
+! HEX-NOT:  fir.store {{.*}} : !fir.ref<!fir.char<1,0>>
+
+
 ! ---------------------------------------------------------------------------
 ! Derived type -- struct with an INTEGER(4) and a REAL(4) field
 ! nan/hex: field-by-field walk (integer: 0xAA; real: NaN or bitcast)
@@ -322,7 +341,7 @@ subroutine test_derived(res)
 
 
 ! ---------------------------------------------------------------------------
-! Array INTEGER(4)(4) -- 1-D; filled via insert_on_range
+! Array INTEGER(4)(4) -- 1-D; all modes use do_loop + rank-1 view
 ! ---------------------------------------------------------------------------
 subroutine test_int_array(res)
   integer(4) :: res(4)
@@ -330,20 +349,22 @@ subroutine test_int_array(res)
   res = x
 end subroutine
 ! ZERO-LABEL: func.func @_QPtest_int_array
-! ZERO: fir.insert_on_range {{.*}} from (0) to (3)
-! ZERO: fir.store {{.*}} : !fir.ref<!fir.array<4xi32>>
+! ZERO: fir.do_loop
+! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+! ZERO: fir.store {{.*}} : !fir.ref<i32>
 
 ! NAN-LABEL:  func.func @_QPtest_int_array
 ! NAN:  fir.do_loop
-! NAN:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<4xi32>>, index) -> !fir.ref<i32>
+! NAN:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi32>>, index) -> !fir.ref<i32>
 ! NAN:  fir.store {{.*}} : !fir.ref<i32>
 
 ! HEX-LABEL:  func.func @_QPtest_int_array
 ! HEX:  fir.do_loop
-! HEX:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<4xi32>>, index) -> !fir.ref<i32>
+! HEX:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi32>>, index) -> !fir.ref<i32>
 ! HEX:  fir.store {{.*}} : !fir.ref<i32>
 
 ! OFF-LABEL: func.func @_QPtest_int_array
+! OFF-NOT: fir.do_loop
 ! OFF-NOT: fir.insert_on_range
 
 ! ---------------------------------------------------------------------------
@@ -355,26 +376,27 @@ subroutine test_real_array(res)
   res = x
 end subroutine
 ! ZERO-LABEL: func.func @_QPtest_real_array
-! ZERO: fir.insert_on_range {{.*}} from (0) to (3)
-! ZERO: fir.store {{.*}} : !fir.ref<!fir.array<4xf32>>
+! ZERO: fir.do_loop
+! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf32>>, index) -> !fir.ref<f32>
+! ZERO: fir.store {{.*}} : !fir.ref<f32>
 
 ! NAN-LABEL:  func.func @_QPtest_real_array
 ! NAN:  fir.do_loop
-! NAN:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<4xf32>>, index) -> !fir.ref<f32>
+! NAN:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf32>>, index) -> !fir.ref<f32>
 ! NAN:  fir.store {{.*}} : !fir.ref<f32>
 
 ! SNAN-LABEL: func.func @_QPtest_real_array
 ! SNAN: fir.do_loop
-! SNAN: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<4xf32>>, index) -> !fir.ref<f32>
+! SNAN: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf32>>, index) -> !fir.ref<f32>
 ! SNAN: fir.store {{.*}} : !fir.ref<f32>
 
 ! HEX-LABEL:  func.func @_QPtest_real_array
 ! HEX:  fir.do_loop
-! HEX:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<4xf32>>, index) -> !fir.ref<f32>
+! HEX:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf32>>, index) -> !fir.ref<f32>
 ! HEX:  fir.store {{.*}} : !fir.ref<f32>
 
 ! ---------------------------------------------------------------------------
-! Array INTEGER(4)(3,4) -- 2-D; zero uses insert_on_range, hex uses do_loop
+! Array INTEGER(4)(3,4) -- 2-D; all modes use do_loop + rank-1 view
 ! ---------------------------------------------------------------------------
 subroutine test_int_array_2d(res)
   integer(4) :: res(3,4)
@@ -382,12 +404,13 @@ subroutine test_int_array_2d(res)
   res = x
 end subroutine
 ! ZERO-LABEL: func.func @_QPtest_int_array_2d
-! ZERO: fir.insert_on_range {{.*}} from (0, 0) to (2, 3)
-! ZERO: fir.store {{.*}} : !fir.ref<!fir.array<3x4xi32>>
+! ZERO: fir.do_loop
+! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+! ZERO: fir.store {{.*}} : !fir.ref<i32>
 
 ! HEX-LABEL: func.func @_QPtest_int_array_2d
 ! HEX:  fir.do_loop
-! HEX:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<3x4xi32>>, index) -> !fir.ref<i32>
+! HEX:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi32>>, index) -> !fir.ref<i32>
 ! HEX:  fir.store {{.*}} : !fir.ref<i32>
 
 ! ---------------------------------------------------------------------------
diff --git a/flang/tools/bbc/bbc.cpp b/flang/tools/bbc/bbc.cpp
index 2aef94d2eddda..694eac3cd6d2f 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -527,8 +527,14 @@ static llvm::LogicalResult convertFortranSourceToMLIR(
   loweringOptions.setIntegerWrapAround(integerWrapAround);
   loweringOptions.setInitGlobalZero(initGlobalZero);
   // -finit-local= and -finit-local-zero: last occurrence on the command
-  // line wins. Use getPosition() to determine which came last.
-  if (initLocalMode.getNumOccurrences() > 0) {
+  // line wins. Determine the winner by position before validating so that
+  // sequences like "-finit-local=bogus -finit-local-zero" accept zero
+  // rather than failing on the overridden invalid value.
+  bool zeroWins = initLocalZero &&
+                  initLocalZero.getPosition() > initLocalMode.getPosition();
+  if (zeroWins) {
+    loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::Zero);
+  } else if (initLocalMode.getNumOccurrences() > 0) {
     llvm::StringRef val = initLocalMode;
     if (val.empty()) {
       llvm::errs() << "bbc: invalid -finit-local= value: (empty)\n";
@@ -554,11 +560,6 @@ static llvm::LogicalResult convertFortranSourceToMLIR(
       return mlir::failure();
     }
   }
-  // If -finit-local-zero appears after -finit-local= on the command line,
-  // it overrides; otherwise -finit-local= already set the mode above.
-  if (initLocalZero &&
-      initLocalZero.getPosition() > initLocalMode.getPosition())
-    loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::Zero);
   loweringOptions.setReallocateLHS(reallocateLHS);
   loweringOptions.setSplitSumExpressionTree(fpSumReassociation);
   loweringOptions.setStackRepackArrays(stackRepackArrays);

>From 302dfd5340349b4de86e882834158297b7f9ade3 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Sun, 16 Aug 2026 23:25:20 -0400
Subject: [PATCH 06/23] [flang] Address MattPD's fourth round of review
 comments on -finit-local=

- Emit a fir.do_loop byte-loop for runtime-length character(n) locals
  so every code unit is initialized regardless of length.
- Fix hex mode for fixed-length CHARACTER: byte-loop over each code unit
  via fir.coordinate_of on a singleton char view.
- Fix hex mode for derived types: byte-loop over the full struct size
  (from fir::getTypeSizeAndAlignmentOrCrash) to cover typed fields and
  padding bytes.
- Exclude Cray pointees from initialization: their FIR base is a
  pointer-box descriptor, not value storage.
- Fix CUDA device-context check: per-thread locals in device kernels
  (implicit device attribute set by SetImplicitCUDADevice) are
  stack-allocated and should be initialized; host-side device variables
  should not.
- Add tests: finit-local-cray-pointee.f90, finit-local-charN-exec.f90,
  finit-local-array-exec.f90; update finit-local.f90 (add test_charN,
  fix singleton char type spelling !fir.char<1>) and finit-local-array-llvm.f90.
---
 .../test/Driver/finit-local-array-exec.f90    | 108 +++++++++++
 .../test/Driver/finit-local-charN-exec.f90    |  30 +++
 flang/docs/ReleaseNotes.md                    |   4 +
 flang/include/flang/Lower/LoweringOptions.h   |  13 +-
 flang/lib/Lower/ConvertVariable.cpp           | 182 ++++++++++++++++--
 flang/test/Lower/CUDA/finit-local-cuda.cuf    |   3 +-
 flang/test/Lower/finit-local-array-llvm.f90   | 105 +++++++---
 flang/test/Lower/finit-local-cray-pointee.f90 |  30 +++
 flang/test/Lower/finit-local.f90              |  56 +++++-
 9 files changed, 467 insertions(+), 64 deletions(-)
 create mode 100644 flang-rt/test/Driver/finit-local-array-exec.f90
 create mode 100644 flang-rt/test/Driver/finit-local-charN-exec.f90
 create mode 100644 flang/test/Lower/finit-local-cray-pointee.f90

diff --git a/flang-rt/test/Driver/finit-local-array-exec.f90 b/flang-rt/test/Driver/finit-local-array-exec.f90
new file mode 100644
index 0000000000000..8f7fc449e3ce7
--- /dev/null
+++ b/flang-rt/test/Driver/finit-local-array-exec.f90
@@ -0,0 +1,108 @@
+! Executable regression test for -finit-local= array initialization.
+! Verifies that every element of static arrays (1-D, 2-D, 3-D) and every
+! field of every element in arrays of derived types are initialized to the
+! requested bit pattern.  A previous bug caused the flat loop to use an
+! out-of-bounds GEP for rank > 1, so only the first row was written.
+!
+! UNSUPPORTED: offload-cuda
+!
+! RUN: %flang %isysroot -L"%libdir" -finit-local=0xAA %s -o %t
+! RUN: env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:%libdir" %t
+
+program test_finit_local_array
+  implicit none
+  integer(4), parameter :: EXPECTED = int(z'AAAAAAAA')
+
+  ! 1-D integer array x(4) -- 4 elements
+  call check_1d()
+
+  ! 2-D integer array x(3,4) -- 12 elements
+  call check_2d()
+
+  ! 3-D integer array x(2,3,4) -- 24 elements
+  call check_3d()
+
+  ! 1-D array of derived type x(2) -- 2 elements, 2 fields each
+  call check_struct_1d()
+
+  ! 2-D array of derived type x(2,3) -- 6 elements, 2 fields each
+  call check_struct_2d()
+
+contains
+
+  subroutine check_1d()
+    integer(4) :: x(4)
+    integer :: i
+    do i = 1, 4
+      if (x(i) /= EXPECTED) then
+        print *, "FAIL check_1d: element", i, "=", x(i), "expected", EXPECTED
+        error stop 1
+      end if
+    end do
+  end subroutine
+
+  subroutine check_2d()
+    integer(4) :: x(3,4)
+    integer :: i, j
+    do j = 1, 4
+      do i = 1, 3
+        if (x(i,j) /= EXPECTED) then
+          print *, "FAIL check_2d: element (", i, ",", j, ")=", x(i,j), &
+                   "expected", EXPECTED
+          error stop 1
+        end if
+      end do
+    end do
+  end subroutine
+
+  subroutine check_3d()
+    integer(4) :: x(2,3,4)
+    integer :: i, j, k
+    do k = 1, 4
+      do j = 1, 3
+        do i = 1, 2
+          if (x(i,j,k) /= EXPECTED) then
+            print *, "FAIL check_3d: element (", i, ",", j, ",", k, ")=", &
+                     x(i,j,k), "expected", EXPECTED
+            error stop 1
+          end if
+        end do
+      end do
+    end do
+  end subroutine
+
+  subroutine check_struct_1d()
+    type :: t
+      integer(4) :: a
+      integer(4) :: b
+    end type
+    type(t) :: x(2)
+    integer :: i
+    do i = 1, 2
+      if (x(i)%a /= EXPECTED .or. x(i)%b /= EXPECTED) then
+        print *, "FAIL check_struct_1d: element", i, &
+                 "a=", x(i)%a, "b=", x(i)%b, "expected", EXPECTED
+        error stop 1
+      end if
+    end do
+  end subroutine
+
+  subroutine check_struct_2d()
+    type :: t
+      integer(4) :: a
+      integer(4) :: b
+    end type
+    type(t) :: x(2,3)
+    integer :: i, j
+    do j = 1, 3
+      do i = 1, 2
+        if (x(i,j)%a /= EXPECTED .or. x(i,j)%b /= EXPECTED) then
+          print *, "FAIL check_struct_2d: element (", i, ",", j, &
+                   ") a=", x(i,j)%a, "b=", x(i,j)%b, "expected", EXPECTED
+          error stop 1
+        end if
+      end do
+    end do
+  end subroutine
+
+end program
diff --git a/flang-rt/test/Driver/finit-local-charN-exec.f90 b/flang-rt/test/Driver/finit-local-charN-exec.f90
new file mode 100644
index 0000000000000..7539c4051c6de
--- /dev/null
+++ b/flang-rt/test/Driver/finit-local-charN-exec.f90
@@ -0,0 +1,30 @@
+! Executable regression test for -finit-local= with runtime-length CHARACTER.
+! Verifies that every byte of a character(n) local is initialized to the
+! requested pattern, and that the empty-string (n=0) case runs without error.
+!
+! UNSUPPORTED: offload-cuda
+!
+! RUN: %flang %isysroot -L"%libdir" -finit-local=0xAA %s -o %t
+! RUN: env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:%libdir" %t
+
+program test_finit_local_charN
+  implicit none
+
+  call check_charN(5)   ! n > 0: all bytes must be 0xAA
+  call check_charN(1)   ! single byte
+  call check_charN(0)   ! empty string: no bytes to check, must not crash
+end program
+
+subroutine check_charN(n)
+  integer, intent(in) :: n
+  character(n) :: x
+  integer :: i
+  ! Inspect each byte through an equivalenced integer array.
+  ! For n == 0 the loop body is never entered.
+  do i = 1, n
+    if (ichar(x(i:i)) /= int(z'AA')) then
+      write(*,*) 'FAIL: byte', i, 'of character(', n, ') =', ichar(x(i:i))
+      stop 1
+    end if
+  end do
+end subroutine
diff --git a/flang/docs/ReleaseNotes.md b/flang/docs/ReleaseNotes.md
index e7761f237a7d4..48e53a497f953 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -101,6 +101,10 @@ page](https://llvm.org/releases/).
   variables that have no explicit or default initialization. Accepted values
   are `zero`, `nan`, `snan`, and `0x<hex-byte>` (e.g. `0xAA`). The gfortran
   compatibility alias `-finit-local-zero` is equivalent to `-finit-local=zero`.
+  `zero` and `0x<hex-byte>` fill every storage byte including struct padding.
+  The `nan` and `snan` modes currently initialize each typed field individually;
+  padding bytes inside derived-type variables are not yet initialized for
+  those modes.
 
 ## Windows Support
 
diff --git a/flang/include/flang/Lower/LoweringOptions.h b/flang/include/flang/Lower/LoweringOptions.h
index 7f24c02c57c79..9463ba0258087 100644
--- a/flang/include/flang/Lower/LoweringOptions.h
+++ b/flang/include/flang/Lower/LoweringOptions.h
@@ -23,12 +23,17 @@ namespace Fortran::lower {
 
 /// Initialization mode for automatic (local) variables without explicit
 /// or default initialization, selected via -finit-local=.
+///
+/// Zero and Hex fill every storage byte including struct padding and
+/// CHARACTER storage.  QNaN and SNaN initialize each typed field
+/// individually; struct padding is not yet covered for those modes
+/// (TODO: use whole-struct memset once PR #159788 lands).
 enum class InitLocalKind {
   Off,  ///< No initialization (default)
-  Zero, ///< Fill with 0x00 bytes
-  Hex,  ///< Fill with a user-supplied byte pattern
-  QNaN, ///< Quiet NaN for FP; 0xAA byte-splat for non-FP types
-  SNaN, ///< Signalling NaN for FP; 0xAA byte-splat for non-FP types
+  Zero, ///< Fill with 0x00 bytes (all types, all storage including padding)
+  Hex,  ///< Fill with a user-supplied byte pattern (all types, all storage including padding)
+  QNaN, ///< Quiet NaN for FP fields; 0xAA byte-splat for non-FP fields (struct padding not yet covered)
+  SNaN, ///< Signalling NaN for FP fields; 0xAA byte-splat for non-FP fields (struct padding not yet covered)
 };
 
 class LoweringOptionsBase {
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 114512a868acc..f6f64bbab83c1 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1303,17 +1303,34 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
       return false;
   if (Fortran::semantics::FindEquivalenceSet(sym))
     return false;
-  // Skip CUDA variables whose storage is not host-accessible via a plain
-  // fir.store: device, constant, shared, and usedevice live exclusively in
-  // device memory and cannot be initialized with a host store. Managed,
-  // unified, and pinned memory are host-accessible and may be initialized
-  // normally with a host store.
+  // Cray pointees own no storage of their own; their FIR base is a
+  // pointer-box descriptor. Initializing it would overwrite the
+  // descriptor, not the pointee storage.
+  if (sym.test(Fortran::semantics::Symbol::Flag::CrayPointee))
+    return false;
+  // CUDA storage accessibility:
+  //   constant / shared / usedevice: always unreachable by a plain fir.store
+  //     from the host -- skip.
+  //   device in a HOST context: lives in global device memory; a host
+  //     fir.store cannot reach it -- skip.
+  //   device in a DEVICE subprogram: implicitly set by SetImplicitCUDADevice
+  //     for every local in a device kernel; these are per-thread stack
+  //     allocations reachable by a device fir.store -- initialize.
+  //   managed / unified / pinned: host-accessible unified memory -- initialize.
   if (auto cudaAttr = Fortran::semantics::GetCUDADataAttr(&sym)) {
-    if (*cudaAttr == Fortran::common::CUDADataAttr::Device ||
-        *cudaAttr == Fortran::common::CUDADataAttr::Constant ||
-        *cudaAttr == Fortran::common::CUDADataAttr::Shared ||
-        *cudaAttr == Fortran::common::CUDADataAttr::UseDevice) {
+    switch (*cudaAttr) {
+    case Fortran::common::CUDADataAttr::Constant:
+    case Fortran::common::CUDADataAttr::Shared:
+    case Fortran::common::CUDADataAttr::UseDevice:
       return false;
+    case Fortran::common::CUDADataAttr::Device:
+      // In a device subprogram the attribute is implicit (SetImplicitCUDADevice)
+      // and the variable is a thread-local stack allocation -- initialize it.
+      if (!Fortran::semantics::IsCUDADeviceContext(&sym.owner()))
+        return false;
+      break;
+    default:
+      break;
     }
   }
   return true;
@@ -1359,8 +1376,9 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
   if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
     return makeIntCst(logTy.getFKind() * 8);
   }
-  // TODO: CHARACTER falls back to zero; a future improvement should fill each
-  // storage unit with the byte pattern.
+  // CHARACTER with hex mode is handled upstream in genInitLocalStore before
+  // this function is reached. For zero/nan/snan, fir.zero_bits is correct
+  // (zero fills all bytes; nan/snan have no meaningful character value).
   return fir::ZeroOp::create(builder, loc, eleTy);
 }
 
@@ -1379,6 +1397,7 @@ static mlir::Value genFPNaNInit(fir::FirOpBuilder &builder, mlir::Location loc,
 }
 
 /// Emit a store of the -finit-local= pattern for a single scalar address.
+/// Fixed-length CHARACTER in hex mode: byte-loop over each code unit.
 /// Complex types get NaN on both parts; integer/logical non-FP types use a
 /// 0xAA byte-splat for nan/snan modes. LOGICAL stores via a bitcasted integer
 /// address to preserve the raw bit pattern past fir.convert normalization.
@@ -1386,10 +1405,55 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
                               mlir::Type ty, mlir::Value addr,
                               Fortran::lower::InitLocalKind mode,
                               uint8_t hexByte) {
-  // CHARACTER(0) has zero-length storage -- nothing to initialize.
-  if (auto charTy = mlir::dyn_cast<fir::CharacterType>(ty))
+  // Fixed-length CHARACTER: for hex mode emit a compile-time byte-loop so
+  // every code-unit gets the requested pattern. Zero uses fir.zero_bits
+  // (handled below). nan/snan fall through to the zero fallback in
+  // genByteSplatInit -- those modes have no meaningful value for a character
+  // storage unit anyway.
+  if (auto charTy = mlir::dyn_cast<fir::CharacterType>(ty)) {
+    // CHARACTER(0) has zero-length storage -- nothing to initialize.
     if (charTy.getLen() == 0)
       return;
+    if (mode == Fortran::lower::InitLocalKind::Hex) {
+      // Loop over each code unit of the character storage. The loop body
+      // stores one i8 per iteration via a singleton fir.char<kind,1>
+      // coordinate, so every byte of every code unit is written.
+      int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
+      if (nUnits > 0) {
+        mlir::Type idxTy = builder.getIndexType();
+        mlir::Type i8Ty  = builder.getIntegerType(8);
+        fir::CharacterType byteTy =
+            fir::CharacterType::getSingleton(builder.getContext(),
+                                             charTy.getFKind());
+        mlir::Type byteSeqTy = fir::SequenceType::get(
+            {fir::SequenceType::getUnknownExtent()}, byteTy);
+        mlir::Value byteBase =
+            builder.createConvert(loc, builder.getRefType(byteSeqTy), addr);
+        mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
+        mlir::Value last =
+            builder.createIntegerConstant(loc, idxTy, nUnits - 1);
+        mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+        auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
+                                          /*unordered=*/false,
+                                          /*finalCount=*/false);
+        mlir::OpBuilder::InsertionGuard guard(builder);
+        builder.setInsertionPointToStart(loop.getBody());
+        mlir::Value iv = loop.getInductionVar();
+        mlir::Value byteAddr = fir::CoordinateOp::create(
+            builder, loc, builder.getRefType(byteTy), byteBase,
+            mlir::ValueRange{iv});
+        mlir::Value pat = builder.createIntegerConstant(
+            loc, i8Ty, static_cast<int64_t>(hexByte));
+        // Store via i8 pointer so the byte pattern is written verbatim
+        // regardless of character kind (UTF-16/32 code units are also
+        // initialised byte-by-byte).
+        mlir::Value i8Addr =
+            builder.createConvert(loc, builder.getRefType(i8Ty), byteAddr);
+        fir::StoreOp::create(builder, loc, pat, i8Addr);
+      }
+      return;
+    }
+  }
   mlir::Value val;
   auto fpTy = mlir::dyn_cast<mlir::FloatType>(ty);
   auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(ty);
@@ -1511,12 +1575,48 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
             initAddr(eleTy, elemAddr);
           }
         } else if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty)) {
-          // Derived type: zero the whole struct, or walk fields for
-          // nan/snan/hex.
+          // Derived type initialization:
+          //   zero: fir.zero_bits over the whole struct -- covers all typed
+          //         fields and any padding bytes between them.
+          //   hex:  byte-loop over the whole struct using the compile-time
+          //         struct size from fir::getTypeSizeAndAlignmentOrCrash --
+          //         also covers padding bytes, giving a uniform byte pattern.
+          //   nan/snan: field-by-field walk with typed NaN stores; padding
+          //         bytes between fields are not yet covered (TODO pending
+          //         PR #159788 which will provide memset infrastructure).
           if (mode == Fortran::lower::InitLocalKind::Zero) {
             fir::StoreOp::create(
                 builder, loc, fir::ZeroOp::create(builder, loc, recTy), addr);
+          } else if (mode == Fortran::lower::InitLocalKind::Hex) {
+            // Fill every byte (typed fields + padding) with hexByte.
+            auto [byteSize, _align] = fir::getTypeSizeAndAlignmentOrCrash(
+                loc, recTy, builder.getDataLayout(), builder.getKindMap());
+            if (byteSize > 0) {
+              mlir::Type idxTy = builder.getIndexType();
+              mlir::Type i8Ty = builder.getIntegerType(8);
+              mlir::Type i8SeqTy = fir::SequenceType::get(
+                  {fir::SequenceType::getUnknownExtent()}, i8Ty);
+              mlir::Value byteBase = builder.createConvert(
+                  loc, builder.getRefType(i8SeqTy), addr);
+              mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
+              mlir::Value last = builder.createIntegerConstant(
+                  loc, idxTy, static_cast<int64_t>(byteSize) - 1);
+              mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+              auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
+                                                /*unordered=*/false,
+                                                /*finalCount=*/false);
+              mlir::OpBuilder::InsertionGuard guard(builder);
+              builder.setInsertionPointToStart(loop.getBody());
+              mlir::Value iv = loop.getInductionVar();
+              mlir::Value byteAddr = fir::CoordinateOp::create(
+                  builder, loc, builder.getRefType(i8Ty), byteBase,
+                  mlir::ValueRange{iv});
+              mlir::Value pat = builder.createIntegerConstant(
+                  loc, i8Ty, static_cast<int64_t>(hexByte));
+              fir::StoreOp::create(builder, loc, pat, byteAddr);
+            }
           } else {
+            // nan / snan: typed field stores; padding bytes not yet covered.
             for (auto [fieldName, fieldTy] : recTy.getTypeList()) {
               auto fieldIdx = fir::FieldIndexOp::create(
                   builder, loc, fir::FieldType::get(recTy.getContext()),
@@ -1527,12 +1627,58 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
               initAddr(fieldTy, fieldAddr);
             }
           }
-        } else {
-          // Scalar (integer, real, complex, logical, character): store
-          // directly.
+        } else if (!mlir::isa<fir::BaseBoxType>(ty)) {
+          // Scalar (integer, real, complex, logical, character): delegate to
+          // genInitLocalStore, which handles each type and mode combination.
+          // Skip FIR box types (e.g. a Cray pointee descriptor) that do not
+          // represent initializable value storage.
           genInitLocalStore(builder, loc, ty, addr, mode, hexByte);
         }
       };
+
+  // Runtime-length CHARACTER: emit a byte-by-byte fir.do_loop guarded by
+  // the runtime length so we neither skip bytes (the old single-store
+  // behaviour) nor write through a zero-byte allocation when len == 0.
+  // Both HLFIR and non-HLFIR paths store a CharBoxValue in the symMap for
+  // a scalar character(n) local, so fir::getLen(exv) always returns the
+  // runtime length when the character type has dynamic length.
+  auto getRtCharLen = [&]() -> mlir::Value { return fir::getLen(exv); };
+
+  // Only handle the dynamic-length case here; fixed-length falls through to
+  // initAddr which calls genInitLocalStore directly.
+  if (auto charTy = mlir::dyn_cast<fir::CharacterType>(storeTy);
+      charTy && charTy.hasDynamicLen()) {
+    if (mlir::Value rtLen = getRtCharLen()) {
+      mlir::Type idxTy = builder.getIndexType();
+      mlir::Value lenIdx = builder.createConvert(loc, idxTy, rtLen);
+      mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
+      mlir::Value one  = builder.createIntegerConstant(loc, idxTy, 1);
+      // last = lenIdx - 1; the fir.do_loop trip count is lenIdx so the
+      // loop body is skipped entirely when lenIdx == 0 (empty string).
+      mlir::Value last =
+          mlir::arith::SubIOp::create(builder, loc, lenIdx, one);
+      auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
+                                        /*unordered=*/false,
+                                        /*finalCount=*/false);
+      mlir::OpBuilder::InsertionGuard guard(builder);
+      builder.setInsertionPointToStart(loop.getBody());
+      mlir::Value iv = loop.getInductionVar();
+      // Treat the storage as an array of singleton characters so that
+      // fir.coordinate_of advances by one code-unit per step.
+      fir::CharacterType byteTy =
+          fir::CharacterType::getSingleton(builder.getContext(),
+                                           charTy.getFKind());
+      mlir::Type byteSeqTy = fir::SequenceType::get(
+          {fir::SequenceType::getUnknownExtent()}, byteTy);
+      mlir::Value byteBase =
+          builder.createConvert(loc, builder.getRefType(byteSeqTy), base);
+      mlir::Value byteAddr = fir::CoordinateOp::create(
+          builder, loc, builder.getRefType(byteTy), byteBase,
+          mlir::ValueRange{iv});
+      genInitLocalStore(builder, loc, byteTy, byteAddr, mode, hexByte);
+      return;
+    }
+  }
   initAddr(storeTy, base);
 }
 
diff --git a/flang/test/Lower/CUDA/finit-local-cuda.cuf b/flang/test/Lower/CUDA/finit-local-cuda.cuf
index a9b25e9676db0..297623c97dc0e 100644
--- a/flang/test/Lower/CUDA/finit-local-cuda.cuf
+++ b/flang/test/Lower/CUDA/finit-local-cuda.cuf
@@ -61,5 +61,6 @@ attributes(global) subroutine test_global_local()
   n = 42
 end subroutine
 ! CHECK-LABEL: func.func @{{.*}}test_global_local
+! CHECK: hlfir.declare {{.*}}"_QFtest_global_localEn"
 ! CHECK: fir.zero_bits i32
-! CHECK: fir.store
+! CHECK: fir.store {{.*}} : !fir.ref<i32>
diff --git a/flang/test/Lower/finit-local-array-llvm.f90 b/flang/test/Lower/finit-local-array-llvm.f90
index 718a3954f2ca7..16c4338ea67ef 100644
--- a/flang/test/Lower/finit-local-array-llvm.f90
+++ b/flang/test/Lower/finit-local-array-llvm.f90
@@ -4,12 +4,19 @@
 ! does not accept ArrayAttr of non-zero scalars. The fix uses a do_loop +
 ! coordinate_of instead, which lowers correctly through to LLVM IR.
 !
+! The HEX checks verify:
+!   - the loop trip counter PHI starts at the expected element count,
+!   - the GEP uses the element type as the unit stride (so all elements are
+!     reached, not just element 0), and
+!   - the store writes the expected bit pattern on every iteration.
+! These three properties together prove that every element is initialized.
+!
 ! RUN: %flang_fc1 -emit-llvm -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
 ! RUN: %flang_fc1 -emit-llvm -finit-local=nan  %s -o - | FileCheck --check-prefix=NAN %s
 ! RUN: %flang_fc1 -emit-llvm -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
 
 ! ---------------------------------------------------------------------------
-! Static 1-D array INTEGER(4)(4)
+! Static 1-D array INTEGER(4)(4) -- 4 elements
 ! ---------------------------------------------------------------------------
 subroutine test_int_array(res)
   integer(4) :: res(4)
@@ -17,17 +24,23 @@ subroutine test_int_array(res)
   res = x
 end subroutine
 ! HEX-LABEL: define {{.*}}@{{.*}}test_int_array{{.*}}(
-! HEX:  store i32 -1431655766,
-! HEX-NOT: store i32 0,
+! HEX:         phi i64 [ {{.*}}, {{.*}} ], [ 4, %{{.*}} ]
+! HEX:         getelementptr i32, ptr {{.*}}, i64
+! HEX:         store i32 -1431655766,
+! HEX-NOT:     store i32 0,
 
 ! NAN-LABEL: define {{.*}}@{{.*}}test_int_array{{.*}}(
-! NAN:  store i32 -1431655766,
+! NAN:         phi i64 [ {{.*}}, {{.*}} ], [ 4, %{{.*}} ]
+! NAN:         getelementptr i32, ptr {{.*}}, i64
+! NAN:         store i32 -1431655766,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_int_array{{.*}}(
-! ZERO: store i32 0,
+! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 4, %{{.*}} ]
+! ZERO:        getelementptr i32, ptr {{.*}}, i64
+! ZERO:        store i32 0,
 
 ! ---------------------------------------------------------------------------
-! Derived type with an array-valued field  (Thread 2 regression)
+! Derived type with an array-valued field (Thread 2 regression)
 ! type t; integer :: a(2); end type; type(t) :: x
 ! ---------------------------------------------------------------------------
 subroutine test_array_in_struct(res)
@@ -39,16 +52,20 @@ subroutine test_array_in_struct(res)
   res = x
 end subroutine
 ! HEX-LABEL: define {{.*}}@{{.*}}test_array_in_struct{{.*}}(
-! HEX:  store i32 -1431655766,
+! HEX:         phi i64 [ {{.*}}, {{.*}} ], [ 8, %{{.*}} ]
+! HEX:         getelementptr i8, ptr {{.*}}, i64
+! HEX:         store i8 -86,
 
 ! NAN-LABEL: define {{.*}}@{{.*}}test_array_in_struct{{.*}}(
-! NAN:  store i32 -1431655766,
+! NAN:         phi i64 [ {{.*}}, {{.*}} ], [ 2, %{{.*}} ]
+! NAN:         getelementptr i32, ptr {{.*}}, i64
+! NAN:         store i32 -1431655766,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_array_in_struct{{.*}}(
-! ZERO: store {{.*}} zeroinitializer,
+! ZERO:        store {{.*}} zeroinitializer,
 
 ! ---------------------------------------------------------------------------
-! Rank-2 array INTEGER(4)(3,4) -- flat loop must index via rank-1 view
+! Rank-2 array INTEGER(4)(3,4) -- 12 elements; flat loop via rank-1 view
 ! ---------------------------------------------------------------------------
 subroutine test_int_array_2d(res)
   integer(4) :: res(3,4)
@@ -56,17 +73,23 @@ subroutine test_int_array_2d(res)
   res = x
 end subroutine
 ! HEX-LABEL: define {{.*}}@{{.*}}test_int_array_2d{{.*}}(
-! HEX:  store i32 -1431655766,
-! HEX-NOT: store i32 0,
+! HEX:         phi i64 [ {{.*}}, {{.*}} ], [ 12, %{{.*}} ]
+! HEX:         getelementptr i32, ptr {{.*}}, i64
+! HEX:         store i32 -1431655766,
+! HEX-NOT:     store i32 0,
 
 ! NAN-LABEL: define {{.*}}@{{.*}}test_int_array_2d{{.*}}(
-! NAN:  store i32 -1431655766,
+! NAN:         phi i64 [ {{.*}}, {{.*}} ], [ 12, %{{.*}} ]
+! NAN:         getelementptr i32, ptr {{.*}}, i64
+! NAN:         store i32 -1431655766,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_int_array_2d{{.*}}(
-! ZERO: store i32 0,
+! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 12, %{{.*}} ]
+! ZERO:        getelementptr i32, ptr {{.*}}, i64
+! ZERO:        store i32 0,
 
 ! ---------------------------------------------------------------------------
-! Rank-3 array INTEGER(4)(2,3,4) -- flat loop must index via rank-1 view
+! Rank-3 array INTEGER(4)(2,3,4) -- 24 elements; flat loop via rank-1 view
 ! ---------------------------------------------------------------------------
 subroutine test_int_array_3d(res)
   integer(4) :: res(2,3,4)
@@ -74,19 +97,25 @@ subroutine test_int_array_3d(res)
   res = x
 end subroutine
 ! HEX-LABEL: define {{.*}}@{{.*}}test_int_array_3d{{.*}}(
-! HEX:  store i32 -1431655766,
-! HEX-NOT: store i32 0,
+! HEX:         phi i64 [ {{.*}}, {{.*}} ], [ 24, %{{.*}} ]
+! HEX:         getelementptr i32, ptr {{.*}}, i64
+! HEX:         store i32 -1431655766,
+! HEX-NOT:     store i32 0,
 
 ! NAN-LABEL: define {{.*}}@{{.*}}test_int_array_3d{{.*}}(
-! NAN:  store i32 -1431655766,
+! NAN:         phi i64 [ {{.*}}, {{.*}} ], [ 24, %{{.*}} ]
+! NAN:         getelementptr i32, ptr {{.*}}, i64
+! NAN:         store i32 -1431655766,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_int_array_3d{{.*}}(
-! ZERO: store i32 0,
+! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 24, %{{.*}} ]
+! ZERO:        getelementptr i32, ptr {{.*}}, i64
+! ZERO:        store i32 0,
 
 ! ---------------------------------------------------------------------------
 ! Array of derived type  type(t) :: x(2)  (Thread 3/4 regression)
-! Each element is a record; the loop must call initAddr per element so
-! record fields are walked rather than emitting zeroinitializer.
+! Loop strides by sizeof(%t); initAddr recurses into each record element so
+! all fields receive the pattern rather than a zeroinitializer.
 ! ---------------------------------------------------------------------------
 subroutine test_array_of_struct(res)
   type :: t
@@ -98,19 +127,26 @@ subroutine test_array_of_struct(res)
   res = x
 end subroutine
 ! HEX-LABEL: define {{.*}}@{{.*}}test_array_of_struct{{.*}}(
-! HEX:  store i32 -1431655766,
-! HEX-NOT: store {{.*}} zeroinitializer,
+! HEX:         phi i64 [ {{.*}}, {{.*}} ], [ 2, %{{.*}} ]
+! HEX:         getelementptr %{{.*}}t, ptr {{.*}}, i64
+! HEX:         getelementptr i8, ptr {{.*}}, i64
+! HEX:         store i8 -86,
+! HEX-NOT:     store {{.*}} zeroinitializer,
 
 ! NAN-LABEL: define {{.*}}@{{.*}}test_array_of_struct{{.*}}(
-! NAN:  store i32 -1431655766,
+! NAN:         phi i64 [ {{.*}}, {{.*}} ], [ 2, %{{.*}} ]
+! NAN:         getelementptr %{{.*}}t, ptr {{.*}}, i64
+! NAN:         store i32 -1431655766,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_struct{{.*}}(
-! ZERO: store {{.*}} zeroinitializer,
+! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 2, %{{.*}} ]
+! ZERO:        getelementptr %{{.*}}t, ptr {{.*}}, i64
+! ZERO:        store {{.*}} zeroinitializer,
 
 ! ---------------------------------------------------------------------------
-! Rank-2 array of derived type  type(t) :: x(2,3)
+! Rank-2 array of derived type  type(t) :: x(2,3) -- 6 elements
 ! Flat loop must stride by sizeof(%t) via rank-1 view; initAddr recurses
-! into the record so all fields of all 6 elements receive the pattern.
+! into each record so all fields of all 6 elements receive the pattern.
 ! ---------------------------------------------------------------------------
 subroutine test_array_of_struct_2d(res)
   type :: t
@@ -122,11 +158,18 @@ subroutine test_array_of_struct_2d(res)
   res = x
 end subroutine
 ! HEX-LABEL: define {{.*}}@{{.*}}test_array_of_struct_2d{{.*}}(
-! HEX:  store i32 -1431655766,
-! HEX-NOT: store {{.*}} zeroinitializer,
+! HEX:         phi i64 [ {{.*}}, {{.*}} ], [ 6, %{{.*}} ]
+! HEX:         getelementptr %{{.*}}t, ptr {{.*}}, i64
+! HEX:         getelementptr i8, ptr {{.*}}, i64
+! HEX:         store i8 -86,
+! HEX-NOT:     store {{.*}} zeroinitializer,
 
 ! NAN-LABEL: define {{.*}}@{{.*}}test_array_of_struct_2d{{.*}}(
-! NAN:  store i32 -1431655766,
+! NAN:         phi i64 [ {{.*}}, {{.*}} ], [ 6, %{{.*}} ]
+! NAN:         getelementptr %{{.*}}t, ptr {{.*}}, i64
+! NAN:         store i32 -1431655766,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_struct_2d{{.*}}(
-! ZERO: store {{.*}} zeroinitializer,
+! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 6, %{{.*}} ]
+! ZERO:        getelementptr %{{.*}}t, ptr {{.*}}, i64
+! ZERO:        store {{.*}} zeroinitializer,
diff --git a/flang/test/Lower/finit-local-cray-pointee.f90 b/flang/test/Lower/finit-local-cray-pointee.f90
new file mode 100644
index 0000000000000..af126fcc442f2
--- /dev/null
+++ b/flang/test/Lower/finit-local-cray-pointee.f90
@@ -0,0 +1,30 @@
+! Tests that -finit-local= does not initialize Cray pointees. A Cray pointee
+! has no storage of its own; its FIR base is a pointer-box descriptor. Before
+! this fix, shouldInitLocal admitted the pointee and the scalar-fallback path
+! in initAddr emitted a memcpy from null into the descriptor. With -O2 this
+! caused the function to be optimized to `unreachable`.
+!
+! RUN: %flang_fc1 -emit-llvm -O0 -finit-local=zero %s -o - | FileCheck --check-prefix=O0 %s
+! RUN: %flang_fc1 -emit-llvm -O2 -finit-local=zero %s -o - | FileCheck --check-prefix=O2 %s
+
+! The pointee x must NOT be initialized; the only store must be the user
+! assignment x(3) = 7 (i32 7).  No memcpy from null and no zeroinitializer.
+
+subroutine test_cray_pointee(res)
+  integer :: res(10), x(10)
+  integer(8) :: p
+  pointer (p, x)
+  p = loc(res)
+  x(3) = 7
+  res = x
+end subroutine
+
+! O0-LABEL: define {{.*}}@{{.*}}test_cray_pointee{{.*}}(
+! O0-NOT:  call void @llvm.memcpy{{.*}}null
+! O0-NOT:  store {{.*}} zeroinitializer
+! O0:      store i32 7,
+
+! O2-LABEL: define {{.*}}@{{.*}}test_cray_pointee{{.*}}(
+! O2-NOT:  unreachable
+! O2-NOT:  call void @llvm.memcpy{{.*}}null
+! O2:      store i32 7,
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 416625613d943..23d92a3a221d4 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -266,8 +266,9 @@ subroutine test_logical4(res)
 ! HEX:  fir.store {{.*}} : !fir.ref<i32>
 
 ! ---------------------------------------------------------------------------
-! CHARACTER(10) -- fir::CharacterType is not mlir::FloatType/IntegerType/ComplexType
-! nan/snan/hex: fall back to fir.zero_bits (known limitation, TODO)
+! CHARACTER(10) -- fixed-length scalar.
+! zero/nan/snan: fir.zero_bits over the whole character type.
+! hex: byte-loop over 10 singleton code-units.
 ! ---------------------------------------------------------------------------
 subroutine test_char10(res)
   character(10) :: res
@@ -287,8 +288,10 @@ subroutine test_char10(res)
 ! SNAN: fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
 
 ! HEX-LABEL:  func.func @_QPtest_char10
-! HEX:  fir.zero_bits !fir.char<1,10>
-! HEX:  fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
+! HEX:        fir.do_loop
+! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX:          arith.constant {{.*}} : i8
+! HEX:          fir.store {{.*}} : !fir.ref<i8>
 
 ! ---------------------------------------------------------------------------
 ! CHARACTER(0) -- zero-length: no store should be emitted (guard for
@@ -305,6 +308,41 @@ subroutine test_char0(res)
 ! HEX-LABEL:  func.func @_QPtest_char0
 ! HEX-NOT:  fir.store {{.*}} : !fir.ref<!fir.char<1,0>>
 
+! ---------------------------------------------------------------------------
+! CHARACTER(n) -- runtime-length: emit a fir.do_loop over [0, n-1] so
+! every byte is initialised. The loop body uses fir.coordinate_of on a
+! rank-1 unknown-extent array view of the allocation.
+! When n == 0 the trip count is 0 and the body is never entered.
+! ---------------------------------------------------------------------------
+subroutine test_charN(res, n)
+  integer, intent(in) :: n
+  character(n) :: res
+  character(n) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_charn
+! ZERO:       fir.do_loop
+! ZERO:         fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! ZERO:         fir.zero_bits !fir.char<1>
+! ZERO:         fir.store {{.*}} : !fir.ref<!fir.char<1>>
+
+! NAN-LABEL:  func.func @_QPtest_charn
+! NAN:        fir.do_loop
+! NAN:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! NAN:          fir.zero_bits !fir.char<1>
+! NAN:          fir.store {{.*}} : !fir.ref<!fir.char<1>>
+
+! SNAN-LABEL: func.func @_QPtest_charn
+! SNAN:       fir.do_loop
+! SNAN:         fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! SNAN:         fir.zero_bits !fir.char<1>
+! SNAN:         fir.store {{.*}} : !fir.ref<!fir.char<1>>
+
+! HEX-LABEL:  func.func @_QPtest_charn
+! HEX:        fir.do_loop
+! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX:          arith.constant {{.*}} : i8
+! HEX:          fir.store {{.*}} : !fir.ref<i8>
 
 ! ---------------------------------------------------------------------------
 ! Derived type -- struct with an INTEGER(4) and a REAL(4) field
@@ -332,12 +370,10 @@ subroutine test_derived(res)
 ! NAN:  fir.store {{.*}} : !fir.ref<f32>
 
 ! HEX-LABEL:  func.func @_QPtest_derived
-! HEX:  fir.coordinate_of {{.*}} -> !fir.ref<i32>
-! HEX:  arith.constant {{.*}} : i32
-! HEX:  fir.store {{.*}} : !fir.ref<i32>
-! HEX:  fir.coordinate_of {{.*}} -> !fir.ref<f32>
-! HEX:  arith.bitcast {{.*}} : i32 to f32
-! HEX:  fir.store {{.*}} : !fir.ref<f32>
+! HEX:       fir.do_loop
+! HEX:         fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! HEX:         arith.constant {{.*}} : i8
+! HEX:         fir.store {{.*}} : !fir.ref<i8>
 
 
 ! ---------------------------------------------------------------------------

>From f0f1da54c91344916b55012e5ce4ebfa94372227 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Mon, 17 Aug 2026 00:33:43 -0400
Subject: [PATCH 07/23] To remove the support for nan and snan.

---
 clang/include/clang/Options/FlangOptions.td   |   2 +-
 flang/docs/ReleaseNotes.md                    |   7 +-
 flang/include/flang/Lower/LoweringOptions.h   |   9 +-
 flang/lib/Frontend/CompilerInvocation.cpp     |   6 +-
 flang/lib/Lower/ConvertVariable.cpp           | 121 +++++-------------
 flang/test/Driver/finit-local.f90             |   6 +-
 flang/test/Lower/finit-local-array-llvm.f90   |  27 +---
 flang/test/Lower/finit-local-f128.f90         |  16 ---
 flang/test/Lower/finit-local-logical-llvm.f90 |   7 -
 flang/test/Lower/finit-local.f90              | 109 +---------------
 flang/tools/bbc/bbc.cpp                       |   6 +-
 11 files changed, 45 insertions(+), 271 deletions(-)

diff --git a/clang/include/clang/Options/FlangOptions.td b/clang/include/clang/Options/FlangOptions.td
index 15139474f97a2..796c80de06b37 100644
--- a/clang/include/clang/Options/FlangOptions.td
+++ b/clang/include/clang/Options/FlangOptions.td
@@ -395,7 +395,7 @@ def finit_local_EQ : Joined<["-"], "finit-local=">,
   Group<f_Group>,
   Visibility<[FC1Option, FlangOption]>,
   HelpText<"Initialize local variables without explicit or default initialization. "
-           "Accepts: zero, nan, snan, or 0x<hex-byte>.">;
+           "Accepts: zero or 0x<hex-byte>.">;
 
 def finit_local_zero : Flag<["-"], "finit-local-zero">,
   Group<f_Group>,
diff --git a/flang/docs/ReleaseNotes.md b/flang/docs/ReleaseNotes.md
index 48e53a497f953..f817a889129a0 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -99,12 +99,9 @@ page](https://llvm.org/releases/).
 
 - Added `-finit-local=<val>` to initialize automatic local
   variables that have no explicit or default initialization. Accepted values
-  are `zero`, `nan`, `snan`, and `0x<hex-byte>` (e.g. `0xAA`). The gfortran
+  are `zero` and `0x<hex-byte>` (e.g. `0xAA`). The gfortran
   compatibility alias `-finit-local-zero` is equivalent to `-finit-local=zero`.
-  `zero` and `0x<hex-byte>` fill every storage byte including struct padding.
-  The `nan` and `snan` modes currently initialize each typed field individually;
-  padding bytes inside derived-type variables are not yet initialized for
-  those modes.
+  Both modes fill every storage byte including struct padding.
 
 ## Windows Support
 
diff --git a/flang/include/flang/Lower/LoweringOptions.h b/flang/include/flang/Lower/LoweringOptions.h
index 9463ba0258087..63693c5806f0e 100644
--- a/flang/include/flang/Lower/LoweringOptions.h
+++ b/flang/include/flang/Lower/LoweringOptions.h
@@ -25,15 +25,12 @@ namespace Fortran::lower {
 /// or default initialization, selected via -finit-local=.
 ///
 /// Zero and Hex fill every storage byte including struct padding and
-/// CHARACTER storage.  QNaN and SNaN initialize each typed field
-/// individually; struct padding is not yet covered for those modes
-/// (TODO: use whole-struct memset once PR #159788 lands).
+/// CHARACTER storage.
 enum class InitLocalKind {
   Off,  ///< No initialization (default)
   Zero, ///< Fill with 0x00 bytes (all types, all storage including padding)
-  Hex,  ///< Fill with a user-supplied byte pattern (all types, all storage including padding)
-  QNaN, ///< Quiet NaN for FP fields; 0xAA byte-splat for non-FP fields (struct padding not yet covered)
-  SNaN, ///< Signalling NaN for FP fields; 0xAA byte-splat for non-FP fields (struct padding not yet covered)
+  Hex,  ///< Fill with a user-supplied byte pattern (all types, all storage
+        ///< including padding)
 };
 
 class LoweringOptionsBase {
diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp
index 09dbcf629e086..3039b01ce91cc 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -1788,7 +1788,7 @@ bool CompilerInvocation::createFromArgs(
   else
     invoc.loweringOpts.setInitGlobalZero(false);
 
-  // -finit-local=<zero|nan|snan|0x<hex>>  and  -finit-local-zero
+  // -finit-local=<zero|0x<hex>>  and  -finit-local-zero
   // (-finit-local-zero is an alias that the driver already expands to
   //  -finit-local=zero, so we only need to handle OPT_finit_local_EQ here.)
   if (const llvm::opt::Arg *a =
@@ -1796,10 +1796,6 @@ bool CompilerInvocation::createFromArgs(
     llvm::StringRef val = a->getValue();
     if (val == "zero") {
       invoc.loweringOpts.setInitLocalMode(Fortran::lower::InitLocalKind::Zero);
-    } else if (val == "nan") {
-      invoc.loweringOpts.setInitLocalMode(Fortran::lower::InitLocalKind::QNaN);
-    } else if (val == "snan") {
-      invoc.loweringOpts.setInitLocalMode(Fortran::lower::InitLocalKind::SNaN);
     } else if (val.starts_with("0x") || val.starts_with("0X")) {
       unsigned long long hexVal = 0;
       if (val.drop_front(2).getAsInteger(16, hexVal) || hexVal > 0xFF) {
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index f6f64bbab83c1..f8a16c10ed726 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -48,7 +48,6 @@
 #include "flang/Semantics/type.h"
 #include "mlir/Dialect/Complex/IR/Complex.h"
 #include "mlir/Dialect/OpenACC/OpenACC.h"
-#include "llvm/ADT/APFloat.h"
 #include "llvm/ADT/APInt.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/CommandLine.h"
@@ -1324,8 +1323,9 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
     case Fortran::common::CUDADataAttr::UseDevice:
       return false;
     case Fortran::common::CUDADataAttr::Device:
-      // In a device subprogram the attribute is implicit (SetImplicitCUDADevice)
-      // and the variable is a thread-local stack allocation -- initialize it.
+      // In a device subprogram the attribute is implicit
+      // (SetImplicitCUDADevice) and the variable is a thread-local stack
+      // allocation -- initialize it.
       if (!Fortran::semantics::IsCUDADeviceContext(&sym.owner()))
         return false;
       break;
@@ -1376,40 +1376,21 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
   if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
     return makeIntCst(logTy.getFKind() * 8);
   }
-  // CHARACTER with hex mode is handled upstream in genInitLocalStore before
-  // this function is reached. For zero/nan/snan, fir.zero_bits is correct
-  // (zero fills all bytes; nan/snan have no meaningful character value).
+  // Fallback (e.g. fir.char reached via an unexpected path): zero-initialise.
   return fir::ZeroOp::create(builder, loc, eleTy);
 }
 
-/// Build a quiet or signalling NaN constant of the given FP type.
-/// The payload is all-ones (matching clang's initializationPatternFor() and
-/// the RFC spec), and the sign bit is set (negative NaN).
-static mlir::Value genFPNaNInit(fir::FirOpBuilder &builder, mlir::Location loc,
-                                mlir::FloatType fpTy, bool isSignalling) {
-  const llvm::fltSemantics &sem = fpTy.getFloatSemantics();
-  // All-ones payload (precision-1 mantissa bits), negative sign, per RFC.
-  llvm::APInt payload = llvm::APInt::getAllOnes(sem.precision - 1);
-  llvm::APFloat apf =
-      isSignalling ? llvm::APFloat::getSNaN(sem, /*Negative=*/true, &payload)
-                   : llvm::APFloat::getQNaN(sem, /*Negative=*/true, &payload);
-  return mlir::arith::ConstantFloatOp::create(builder, loc, fpTy, apf);
-}
-
 /// Emit a store of the -finit-local= pattern for a single scalar address.
 /// Fixed-length CHARACTER in hex mode: byte-loop over each code unit.
-/// Complex types get NaN on both parts; integer/logical non-FP types use a
-/// 0xAA byte-splat for nan/snan modes. LOGICAL stores via a bitcasted integer
-/// address to preserve the raw bit pattern past fir.convert normalization.
+/// LOGICAL stores via a bitcasted integer address to preserve the raw bit
+/// pattern past fir.convert normalization.
 static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
                               mlir::Type ty, mlir::Value addr,
                               Fortran::lower::InitLocalKind mode,
                               uint8_t hexByte) {
   // Fixed-length CHARACTER: for hex mode emit a compile-time byte-loop so
-  // every code-unit gets the requested pattern. Zero uses fir.zero_bits
-  // (handled below). nan/snan fall through to the zero fallback in
-  // genByteSplatInit -- those modes have no meaningful value for a character
-  // storage unit anyway.
+  // every code-unit gets the requested pattern. Zero falls through to
+  // fir.zero_bits below.
   if (auto charTy = mlir::dyn_cast<fir::CharacterType>(ty)) {
     // CHARACTER(0) has zero-length storage -- nothing to initialize.
     if (charTy.getLen() == 0)
@@ -1421,10 +1402,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
       int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
       if (nUnits > 0) {
         mlir::Type idxTy = builder.getIndexType();
-        mlir::Type i8Ty  = builder.getIntegerType(8);
-        fir::CharacterType byteTy =
-            fir::CharacterType::getSingleton(builder.getContext(),
-                                             charTy.getFKind());
+        mlir::Type i8Ty = builder.getIntegerType(8);
+        fir::CharacterType byteTy = fir::CharacterType::getSingleton(
+            builder.getContext(), charTy.getFKind());
         mlir::Type byteSeqTy = fir::SequenceType::get(
             {fir::SequenceType::getUnknownExtent()}, byteTy);
         mlir::Value byteBase =
@@ -1439,9 +1419,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
         mlir::OpBuilder::InsertionGuard guard(builder);
         builder.setInsertionPointToStart(loop.getBody());
         mlir::Value iv = loop.getInductionVar();
-        mlir::Value byteAddr = fir::CoordinateOp::create(
-            builder, loc, builder.getRefType(byteTy), byteBase,
-            mlir::ValueRange{iv});
+        mlir::Value byteAddr =
+            fir::CoordinateOp::create(builder, loc, builder.getRefType(byteTy),
+                                      byteBase, mlir::ValueRange{iv});
         mlir::Value pat = builder.createIntegerConstant(
             loc, i8Ty, static_cast<int64_t>(hexByte));
         // Store via i8 pointer so the byte pattern is written verbatim
@@ -1455,8 +1435,6 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
     }
   }
   mlir::Value val;
-  auto fpTy = mlir::dyn_cast<mlir::FloatType>(ty);
-  auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(ty);
   switch (mode) {
   case Fortran::lower::InitLocalKind::Zero:
     val = fir::ZeroOp::create(builder, loc, ty);
@@ -1464,39 +1442,14 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
   case Fortran::lower::InitLocalKind::Hex:
     val = genByteSplatInit(builder, loc, ty, hexByte);
     break;
-  case Fortran::lower::InitLocalKind::QNaN:
-    if (fpTy) {
-      val = genFPNaNInit(builder, loc, fpTy, /*signalling=*/false);
-    } else if (cplxTy) {
-      auto partFpTy = mlir::cast<mlir::FloatType>(cplxTy.getElementType());
-      mlir::Value nanPart =
-          genFPNaNInit(builder, loc, partFpTy, /*signalling=*/false);
-      val = mlir::complex::CreateOp::create(builder, loc, cplxTy, nanPart,
-                                            nanPart);
-    } else {
-      val = genByteSplatInit(builder, loc, ty, 0xAA);
-    }
-    break;
-  case Fortran::lower::InitLocalKind::SNaN:
-    if (fpTy) {
-      val = genFPNaNInit(builder, loc, fpTy, /*signalling=*/true);
-    } else if (cplxTy) {
-      auto partFpTy = mlir::cast<mlir::FloatType>(cplxTy.getElementType());
-      mlir::Value nanPart =
-          genFPNaNInit(builder, loc, partFpTy, /*signalling=*/true);
-      val = mlir::complex::CreateOp::create(builder, loc, cplxTy, nanPart,
-                                            nanPart);
-    } else {
-      val = genByteSplatInit(builder, loc, ty, 0xAA);
-    }
-    break;
   default:
     llvm_unreachable("unexpected InitLocalKind in genInitLocalStore");
   }
-  // For LOGICAL types, genByteSplatInit returns a raw integer to preserve
-  // the bit pattern. Store it via a bitcasted address to avoid fir.convert
-  // normalization (which would reduce any nonzero value to logical true).
-  if (mode != Fortran::lower::InitLocalKind::Zero &&
+  // For LOGICAL in hex mode, genByteSplatInit returns a raw integer to
+  // preserve the bit pattern. Store it via a bitcasted address to avoid
+  // fir.convert normalization (which would reduce any nonzero value to
+  // logical true).
+  if (mode == Fortran::lower::InitLocalKind::Hex &&
       mlir::isa<fir::LogicalType>(ty)) {
     unsigned bits = mlir::cast<fir::LogicalType>(ty).getFKind() * 8;
     mlir::Type intRefTy = builder.getRefType(builder.getIntegerType(bits));
@@ -1511,7 +1464,7 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
 /// Arrays: all modes use a flat fir.do_loop + fir.coordinate_of over a
 /// rank-1 view to avoid both the llvm.mlir.constant crash on non-zero
 /// ArrayAttrs and the quadratic compile time of fir.insert_on_range.
-/// Derived types walk fields for nan/snan/hex. Scalars store directly.
+/// Derived types use a whole-struct byte-loop for hex. Scalars store directly.
 static void genInitLocal(Fortran::lower::AbstractConverter &converter,
                          const Fortran::lower::pft::Variable &var,
                          Fortran::lower::SymMap &symMap) {
@@ -1581,9 +1534,6 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
           //   hex:  byte-loop over the whole struct using the compile-time
           //         struct size from fir::getTypeSizeAndAlignmentOrCrash --
           //         also covers padding bytes, giving a uniform byte pattern.
-          //   nan/snan: field-by-field walk with typed NaN stores; padding
-          //         bytes between fields are not yet covered (TODO pending
-          //         PR #159788 which will provide memset infrastructure).
           if (mode == Fortran::lower::InitLocalKind::Zero) {
             fir::StoreOp::create(
                 builder, loc, fir::ZeroOp::create(builder, loc, recTy), addr);
@@ -1596,8 +1546,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
               mlir::Type i8Ty = builder.getIntegerType(8);
               mlir::Type i8SeqTy = fir::SequenceType::get(
                   {fir::SequenceType::getUnknownExtent()}, i8Ty);
-              mlir::Value byteBase = builder.createConvert(
-                  loc, builder.getRefType(i8SeqTy), addr);
+              mlir::Value byteBase =
+                  builder.createConvert(loc, builder.getRefType(i8SeqTy), addr);
               mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
               mlir::Value last = builder.createIntegerConstant(
                   loc, idxTy, static_cast<int64_t>(byteSize) - 1);
@@ -1615,17 +1565,6 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
                   loc, i8Ty, static_cast<int64_t>(hexByte));
               fir::StoreOp::create(builder, loc, pat, byteAddr);
             }
-          } else {
-            // nan / snan: typed field stores; padding bytes not yet covered.
-            for (auto [fieldName, fieldTy] : recTy.getTypeList()) {
-              auto fieldIdx = fir::FieldIndexOp::create(
-                  builder, loc, fir::FieldType::get(recTy.getContext()),
-                  fieldName, recTy, mlir::ValueRange{});
-              mlir::Value fieldAddr = fir::CoordinateOp::create(
-                  builder, loc, builder.getRefType(fieldTy), addr,
-                  mlir::ValueRange{fieldIdx});
-              initAddr(fieldTy, fieldAddr);
-            }
           }
         } else if (!mlir::isa<fir::BaseBoxType>(ty)) {
           // Scalar (integer, real, complex, logical, character): delegate to
@@ -1652,11 +1591,10 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       mlir::Type idxTy = builder.getIndexType();
       mlir::Value lenIdx = builder.createConvert(loc, idxTy, rtLen);
       mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
-      mlir::Value one  = builder.createIntegerConstant(loc, idxTy, 1);
+      mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
       // last = lenIdx - 1; the fir.do_loop trip count is lenIdx so the
       // loop body is skipped entirely when lenIdx == 0 (empty string).
-      mlir::Value last =
-          mlir::arith::SubIOp::create(builder, loc, lenIdx, one);
+      mlir::Value last = mlir::arith::SubIOp::create(builder, loc, lenIdx, one);
       auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
                                         /*unordered=*/false,
                                         /*finalCount=*/false);
@@ -1665,16 +1603,15 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       mlir::Value iv = loop.getInductionVar();
       // Treat the storage as an array of singleton characters so that
       // fir.coordinate_of advances by one code-unit per step.
-      fir::CharacterType byteTy =
-          fir::CharacterType::getSingleton(builder.getContext(),
-                                           charTy.getFKind());
+      fir::CharacterType byteTy = fir::CharacterType::getSingleton(
+          builder.getContext(), charTy.getFKind());
       mlir::Type byteSeqTy = fir::SequenceType::get(
           {fir::SequenceType::getUnknownExtent()}, byteTy);
       mlir::Value byteBase =
           builder.createConvert(loc, builder.getRefType(byteSeqTy), base);
-      mlir::Value byteAddr = fir::CoordinateOp::create(
-          builder, loc, builder.getRefType(byteTy), byteBase,
-          mlir::ValueRange{iv});
+      mlir::Value byteAddr =
+          fir::CoordinateOp::create(builder, loc, builder.getRefType(byteTy),
+                                    byteBase, mlir::ValueRange{iv});
       genInitLocalStore(builder, loc, byteTy, byteAddr, mode, hexByte);
       return;
     }
diff --git a/flang/test/Driver/finit-local.f90 b/flang/test/Driver/finit-local.f90
index 1d39a83b4ce01..d4199073abb3d 100644
--- a/flang/test/Driver/finit-local.f90
+++ b/flang/test/Driver/finit-local.f90
@@ -1,10 +1,8 @@
 ! Tests that -finit-local= and -finit-local-zero are accepted by the Flang
 ! driver and forwarded correctly to -fc1.
 
-! --- Valid values: zero, nan, snan, hex byte ---
+! --- Valid values: zero, hex byte ---
 ! RUN: %flang -### -S -finit-local=zero  %s 2>&1 | FileCheck --check-prefix=ZERO  %s
-! RUN: %flang -### -S -finit-local=nan   %s 2>&1 | FileCheck --check-prefix=NAN   %s
-! RUN: %flang -### -S -finit-local=snan  %s 2>&1 | FileCheck --check-prefix=SNAN  %s
 ! RUN: %flang -### -S -finit-local=0xAA  %s 2>&1 | FileCheck --check-prefix=HEX   %s
 ! RUN: %flang -### -S -finit-local=0xff  %s 2>&1 | FileCheck --check-prefix=HEX2  %s
 
@@ -14,8 +12,6 @@
 ! RUN: not %flang_fc1 -emit-hlfir -finit-local=bogus %s 2>&1 | FileCheck --check-prefix=ERR %s
 
 ! ZERO:  "-fc1"{{.*}} "-finit-local=zero"
-! NAN:   "-fc1"{{.*}} "-finit-local=nan"
-! SNAN:  "-fc1"{{.*}} "-finit-local=snan"
 ! HEX:   "-fc1"{{.*}} "-finit-local=0xAA"
 ! HEX2:  "-fc1"{{.*}} "-finit-local=0xff"
 ! ERR:   error: invalid value 'bogus' in '-finit-local=bogus'
diff --git a/flang/test/Lower/finit-local-array-llvm.f90 b/flang/test/Lower/finit-local-array-llvm.f90
index 16c4338ea67ef..17290d6e9e370 100644
--- a/flang/test/Lower/finit-local-array-llvm.f90
+++ b/flang/test/Lower/finit-local-array-llvm.f90
@@ -4,7 +4,7 @@
 ! does not accept ArrayAttr of non-zero scalars. The fix uses a do_loop +
 ! coordinate_of instead, which lowers correctly through to LLVM IR.
 !
-! The HEX checks verify:
+! The HEX and ZERO checks verify:
 !   - the loop trip counter PHI starts at the expected element count,
 !   - the GEP uses the element type as the unit stride (so all elements are
 !     reached, not just element 0), and
@@ -12,7 +12,6 @@
 ! These three properties together prove that every element is initialized.
 !
 ! RUN: %flang_fc1 -emit-llvm -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
-! RUN: %flang_fc1 -emit-llvm -finit-local=nan  %s -o - | FileCheck --check-prefix=NAN %s
 ! RUN: %flang_fc1 -emit-llvm -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
 
 ! ---------------------------------------------------------------------------
@@ -29,10 +28,6 @@ subroutine test_int_array(res)
 ! HEX:         store i32 -1431655766,
 ! HEX-NOT:     store i32 0,
 
-! NAN-LABEL: define {{.*}}@{{.*}}test_int_array{{.*}}(
-! NAN:         phi i64 [ {{.*}}, {{.*}} ], [ 4, %{{.*}} ]
-! NAN:         getelementptr i32, ptr {{.*}}, i64
-! NAN:         store i32 -1431655766,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_int_array{{.*}}(
 ! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 4, %{{.*}} ]
@@ -56,10 +51,6 @@ subroutine test_array_in_struct(res)
 ! HEX:         getelementptr i8, ptr {{.*}}, i64
 ! HEX:         store i8 -86,
 
-! NAN-LABEL: define {{.*}}@{{.*}}test_array_in_struct{{.*}}(
-! NAN:         phi i64 [ {{.*}}, {{.*}} ], [ 2, %{{.*}} ]
-! NAN:         getelementptr i32, ptr {{.*}}, i64
-! NAN:         store i32 -1431655766,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_array_in_struct{{.*}}(
 ! ZERO:        store {{.*}} zeroinitializer,
@@ -78,10 +69,6 @@ subroutine test_int_array_2d(res)
 ! HEX:         store i32 -1431655766,
 ! HEX-NOT:     store i32 0,
 
-! NAN-LABEL: define {{.*}}@{{.*}}test_int_array_2d{{.*}}(
-! NAN:         phi i64 [ {{.*}}, {{.*}} ], [ 12, %{{.*}} ]
-! NAN:         getelementptr i32, ptr {{.*}}, i64
-! NAN:         store i32 -1431655766,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_int_array_2d{{.*}}(
 ! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 12, %{{.*}} ]
@@ -102,10 +89,6 @@ subroutine test_int_array_3d(res)
 ! HEX:         store i32 -1431655766,
 ! HEX-NOT:     store i32 0,
 
-! NAN-LABEL: define {{.*}}@{{.*}}test_int_array_3d{{.*}}(
-! NAN:         phi i64 [ {{.*}}, {{.*}} ], [ 24, %{{.*}} ]
-! NAN:         getelementptr i32, ptr {{.*}}, i64
-! NAN:         store i32 -1431655766,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_int_array_3d{{.*}}(
 ! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 24, %{{.*}} ]
@@ -133,10 +116,6 @@ subroutine test_array_of_struct(res)
 ! HEX:         store i8 -86,
 ! HEX-NOT:     store {{.*}} zeroinitializer,
 
-! NAN-LABEL: define {{.*}}@{{.*}}test_array_of_struct{{.*}}(
-! NAN:         phi i64 [ {{.*}}, {{.*}} ], [ 2, %{{.*}} ]
-! NAN:         getelementptr %{{.*}}t, ptr {{.*}}, i64
-! NAN:         store i32 -1431655766,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_struct{{.*}}(
 ! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 2, %{{.*}} ]
@@ -164,10 +143,6 @@ subroutine test_array_of_struct_2d(res)
 ! HEX:         store i8 -86,
 ! HEX-NOT:     store {{.*}} zeroinitializer,
 
-! NAN-LABEL: define {{.*}}@{{.*}}test_array_of_struct_2d{{.*}}(
-! NAN:         phi i64 [ {{.*}}, {{.*}} ], [ 6, %{{.*}} ]
-! NAN:         getelementptr %{{.*}}t, ptr {{.*}}, i64
-! NAN:         store i32 -1431655766,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_struct_2d{{.*}}(
 ! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 6, %{{.*}} ]
diff --git a/flang/test/Lower/finit-local-f128.f90 b/flang/test/Lower/finit-local-f128.f90
index 937b2e86bb649..a0449f4b56a65 100644
--- a/flang/test/Lower/finit-local-f128.f90
+++ b/flang/test/Lower/finit-local-f128.f90
@@ -4,8 +4,6 @@
 ! REQUIRES: flang-supports-f128-math
 !
 ! RUN: %flang_fc1 -emit-hlfir -finit-local=zero  %s -o - | FileCheck --check-prefix=ZERO  %s
-! RUN: %flang_fc1 -emit-hlfir -finit-local=nan   %s -o - | FileCheck --check-prefix=NAN   %s
-! RUN: %flang_fc1 -emit-hlfir -finit-local=snan  %s -o - | FileCheck --check-prefix=SNAN  %s
 ! RUN: %flang_fc1 -emit-hlfir -finit-local=0xAA  %s -o - | FileCheck --check-prefix=HEX   %s
 
 ! ---------------------------------------------------------------------------
@@ -21,13 +19,7 @@ subroutine test_real16(res)
 ! ZERO: fir.zero_bits f128
 ! ZERO: fir.store {{.*}} : !fir.ref<f128>
 
-! NAN-LABEL:  func.func @_QPtest_real16
-! NAN:  arith.constant {{.*}} : f128
-! NAN:  fir.store {{.*}} : !fir.ref<f128>
 
-! SNAN-LABEL: func.func @_QPtest_real16
-! SNAN: arith.constant {{.*}} : f128
-! SNAN: fir.store {{.*}} : !fir.ref<f128>
 
 ! HEX-LABEL:  func.func @_QPtest_real16
 ! HEX:  arith.constant -113427455640312821154458202477256070486 : i128
@@ -47,15 +39,7 @@ subroutine test_complex16(res)
 ! ZERO: fir.zero_bits complex<f128>
 ! ZERO: fir.store {{.*}} : !fir.ref<complex<f128>>
 
-! NAN-LABEL:  func.func @_QPtest_complex16
-! NAN:  arith.constant {{.*}} : f128
-! NAN:  complex.create {{.*}}, {{.*}} : complex<f128>
-! NAN:  fir.store {{.*}} : !fir.ref<complex<f128>>
 
-! SNAN-LABEL: func.func @_QPtest_complex16
-! SNAN: arith.constant {{.*}} : f128
-! SNAN: complex.create {{.*}}, {{.*}} : complex<f128>
-! SNAN: fir.store {{.*}} : !fir.ref<complex<f128>>
 
 ! HEX-LABEL:  func.func @_QPtest_complex16
 ! HEX:  arith.constant -113427455640312821154458202477256070486 : i128
diff --git a/flang/test/Lower/finit-local-logical-llvm.f90 b/flang/test/Lower/finit-local-logical-llvm.f90
index 6f41b78883b59..7adc55b33e363 100644
--- a/flang/test/Lower/finit-local-logical-llvm.f90
+++ b/flang/test/Lower/finit-local-logical-llvm.f90
@@ -4,7 +4,6 @@
 ! bitcasted integer address instead so the bit pattern is preserved.
 !
 ! RUN: %flang_fc1 -emit-llvm -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
-! RUN: %flang_fc1 -emit-llvm -finit-local=nan  %s -o - | FileCheck --check-prefix=NAN %s
 ! RUN: %flang_fc1 -emit-llvm -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
 
 ! ---------------------------------------------------------------------------
@@ -19,9 +18,6 @@ subroutine test_logical1(res)
 ! HEX:  store i8 -86,
 ! HEX-NOT: store i8 1,
 
-! NAN-LABEL: define {{.*}}@{{.*}}test_logical1{{.*}}(
-! NAN:  store i8 -86,
-! NAN-NOT: store i8 1,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_logical1{{.*}}(
 ! ZERO: store i8 0,
@@ -38,9 +34,6 @@ subroutine test_logical4(res)
 ! HEX:  store i32 -1431655766,
 ! HEX-NOT: store i32 1,
 
-! NAN-LABEL: define {{.*}}@{{.*}}test_logical4{{.*}}(
-! NAN:  store i32 -1431655766,
-! NAN-NOT: store i32 1,
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_logical4{{.*}}(
 ! ZERO: store i32 0,
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 23d92a3a221d4..6835b6a8313a0 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -9,11 +9,9 @@
 !   Derived type (struct with plain-int and real components)
 !   Arrays of integer and real
 !
-! Modes exercised: zero, nan, snan, 0xAA (hex), and off (no flag).
+! Modes exercised: zero, 0xAA (hex), and off (no flag).
 !
 ! RUN: bbc -emit-hlfir -finit-local=zero  -o - %s | FileCheck --check-prefix=ZERO  %s
-! RUN: bbc -emit-hlfir -finit-local=nan   -o - %s | FileCheck --check-prefix=NAN   %s
-! RUN: bbc -emit-hlfir -finit-local=snan  -o - %s | FileCheck --check-prefix=SNAN  %s
 ! RUN: bbc -emit-hlfir -finit-local=0xAA  -o - %s | FileCheck --check-prefix=HEX   %s
 ! RUN: bbc -emit-hlfir                    -o - %s | FileCheck --check-prefix=OFF   %s
 ! RUN: bbc -emit-hlfir -finit-local-zero  -o - %s | FileCheck --check-prefix=ZERO  %s
@@ -38,9 +36,6 @@ subroutine test_int1(res)
 ! ZERO: fir.zero_bits i8
 ! ZERO: fir.store {{.*}} : !fir.ref<i8>
 
-! NAN-LABEL:  func.func @_QPtest_int1
-! NAN:  arith.constant -86 : i8
-! NAN:  fir.store {{.*}} : !fir.ref<i8>
 
 ! HEX-LABEL:  func.func @_QPtest_int1
 ! HEX:  arith.constant -86 : i8
@@ -60,9 +55,6 @@ subroutine test_int2(res)
 ! ZERO-LABEL: func.func @_QPtest_int2
 ! ZERO: fir.zero_bits i16
 
-! NAN-LABEL:  func.func @_QPtest_int2
-! NAN:  arith.constant -21846 : i16
-! NAN:  fir.store {{.*}} : !fir.ref<i16>
 
 ! HEX-LABEL:  func.func @_QPtest_int2
 ! HEX:  arith.constant -21846 : i16
@@ -79,13 +71,7 @@ subroutine test_int4(res)
 ! ZERO-LABEL: func.func @_QPtest_int4
 ! ZERO: fir.zero_bits i32
 
-! NAN-LABEL:  func.func @_QPtest_int4
-! NAN:  arith.constant -1431655766 : i32
-! NAN:  fir.store {{.*}} : !fir.ref<i32>
 
-! SNAN-LABEL: func.func @_QPtest_int4
-! SNAN: arith.constant -1431655766 : i32
-! SNAN: fir.store {{.*}} : !fir.ref<i32>
 
 ! HEX-LABEL:  func.func @_QPtest_int4
 ! HEX:  arith.constant -1431655766 : i32
@@ -105,16 +91,13 @@ subroutine test_int8(res)
 ! ZERO-LABEL: func.func @_QPtest_int8
 ! ZERO: fir.zero_bits i64
 
-! NAN-LABEL:  func.func @_QPtest_int8
-! NAN:  arith.constant -6148914691236517206 : i64
-! NAN:  fir.store {{.*}} : !fir.ref<i64>
 
 ! HEX-LABEL:  func.func @_QPtest_int8
 ! HEX:  arith.constant -6148914691236517206 : i64
 ! HEX:  fir.store {{.*}} : !fir.ref<i64>
 
 ! ---------------------------------------------------------------------------
-! REAL(4) -- zero fills with fir.zero_bits; nan/snan with FP constant; hex bitcast
+! REAL(4) -- zero: fir.zero_bits; hex: bitcast from integer splat
 ! ---------------------------------------------------------------------------
 subroutine test_real4(res)
   real(4) :: res
@@ -125,13 +108,7 @@ subroutine test_real4(res)
 ! ZERO: fir.zero_bits f32
 ! ZERO: fir.store {{.*}} : !fir.ref<f32>
 
-! NAN-LABEL:  func.func @_QPtest_real4
-! NAN:  arith.constant {{.*}} : f32
-! NAN:  fir.store {{.*}} : !fir.ref<f32>
 
-! SNAN-LABEL: func.func @_QPtest_real4
-! SNAN: arith.constant {{.*}} : f32
-! SNAN: fir.store {{.*}} : !fir.ref<f32>
 
 ! HEX-LABEL:  func.func @_QPtest_real4
 ! HEX:  arith.constant -1431655766 : i32
@@ -153,13 +130,7 @@ subroutine test_real8(res)
 ! ZERO: fir.zero_bits f64
 ! ZERO: fir.store {{.*}} : !fir.ref<f64>
 
-! NAN-LABEL:  func.func @_QPtest_real8
-! NAN:  arith.constant {{.*}} : f64
-! NAN:  fir.store {{.*}} : !fir.ref<f64>
 
-! SNAN-LABEL: func.func @_QPtest_real8
-! SNAN: arith.constant {{.*}} : f64
-! SNAN: fir.store {{.*}} : !fir.ref<f64>
 
 ! HEX-LABEL:  func.func @_QPtest_real8
 ! HEX:  arith.constant -6148914691236517206 : i64
@@ -168,7 +139,7 @@ subroutine test_real8(res)
 
 ! ---------------------------------------------------------------------------
 ! COMPLEX(4) -- two f32 parts; stored as complex<f32>
-! nan/snan: both parts get NaN; hex: both parts get bitcast pattern
+! hex: both parts get bitcast pattern
 ! ---------------------------------------------------------------------------
 subroutine test_complex4(res)
   complex(4) :: res
@@ -179,15 +150,7 @@ subroutine test_complex4(res)
 ! ZERO: fir.zero_bits complex<f32>
 ! ZERO: fir.store {{.*}} : !fir.ref<complex<f32>>
 
-! NAN-LABEL:  func.func @_QPtest_complex4
-! NAN:  arith.constant {{.*}} : f32
-! NAN:  complex.create {{.*}} : complex<f32>
-! NAN:  fir.store {{.*}} : !fir.ref<complex<f32>>
 
-! SNAN-LABEL: func.func @_QPtest_complex4
-! SNAN: arith.constant {{.*}} : f32
-! SNAN: complex.create {{.*}} : complex<f32>
-! SNAN: fir.store {{.*}} : !fir.ref<complex<f32>>
 
 ! HEX-LABEL:  func.func @_QPtest_complex4
 ! HEX:  arith.constant -1431655766 : i32
@@ -207,15 +170,7 @@ subroutine test_complex8(res)
 ! ZERO: fir.zero_bits complex<f64>
 ! ZERO: fir.store {{.*}} : !fir.ref<complex<f64>>
 
-! NAN-LABEL:  func.func @_QPtest_complex8
-! NAN:  arith.constant {{.*}} : f64
-! NAN:  complex.create {{.*}} : complex<f64>
-! NAN:  fir.store {{.*}} : !fir.ref<complex<f64>>
 
-! SNAN-LABEL: func.func @_QPtest_complex8
-! SNAN: arith.constant {{.*}} : f64
-! SNAN: complex.create {{.*}} : complex<f64>
-! SNAN: fir.store {{.*}} : !fir.ref<complex<f64>>
 
 ! HEX-LABEL:  func.func @_QPtest_complex8
 ! HEX:  arith.constant -6148914691236517206 : i64
@@ -234,10 +189,6 @@ subroutine test_logical1(res)
 ! ZERO-LABEL: func.func @_QPtest_logical1
 ! ZERO: fir.zero_bits !fir.logical<1>
 
-! NAN-LABEL:  func.func @_QPtest_logical1
-! NAN:  arith.constant -86 : i8
-! NAN:  fir.convert {{.*}} : (!fir.ref<!fir.logical<1>>) -> !fir.ref<i8>
-! NAN:  fir.store {{.*}} : !fir.ref<i8>
 
 ! HEX-LABEL:  func.func @_QPtest_logical1
 ! HEX:  arith.constant {{.*}} : i8
@@ -255,10 +206,6 @@ subroutine test_logical4(res)
 ! ZERO-LABEL: func.func @_QPtest_logical4
 ! ZERO: fir.zero_bits !fir.logical<4>
 
-! NAN-LABEL:  func.func @_QPtest_logical4
-! NAN:  arith.constant -1431655766 : i32
-! NAN:  fir.convert {{.*}} : (!fir.ref<!fir.logical<4>>) -> !fir.ref<i32>
-! NAN:  fir.store {{.*}} : !fir.ref<i32>
 
 ! HEX-LABEL:  func.func @_QPtest_logical4
 ! HEX:  arith.constant {{.*}} : i32
@@ -267,7 +214,7 @@ subroutine test_logical4(res)
 
 ! ---------------------------------------------------------------------------
 ! CHARACTER(10) -- fixed-length scalar.
-! zero/nan/snan: fir.zero_bits over the whole character type.
+! zero: fir.zero_bits over the whole character type.
 ! hex: byte-loop over 10 singleton code-units.
 ! ---------------------------------------------------------------------------
 subroutine test_char10(res)
@@ -279,13 +226,7 @@ subroutine test_char10(res)
 ! ZERO: fir.zero_bits !fir.char<1,10>
 ! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
 
-! NAN-LABEL:  func.func @_QPtest_char10
-! NAN:  fir.zero_bits !fir.char<1,10>
-! NAN:  fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
 
-! SNAN-LABEL: func.func @_QPtest_char10
-! SNAN: fir.zero_bits !fir.char<1,10>
-! SNAN: fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
 
 ! HEX-LABEL:  func.func @_QPtest_char10
 ! HEX:        fir.do_loop
@@ -326,17 +267,7 @@ subroutine test_charN(res, n)
 ! ZERO:         fir.zero_bits !fir.char<1>
 ! ZERO:         fir.store {{.*}} : !fir.ref<!fir.char<1>>
 
-! NAN-LABEL:  func.func @_QPtest_charn
-! NAN:        fir.do_loop
-! NAN:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
-! NAN:          fir.zero_bits !fir.char<1>
-! NAN:          fir.store {{.*}} : !fir.ref<!fir.char<1>>
 
-! SNAN-LABEL: func.func @_QPtest_charn
-! SNAN:       fir.do_loop
-! SNAN:         fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
-! SNAN:         fir.zero_bits !fir.char<1>
-! SNAN:         fir.store {{.*}} : !fir.ref<!fir.char<1>>
 
 ! HEX-LABEL:  func.func @_QPtest_charn
 ! HEX:        fir.do_loop
@@ -346,7 +277,7 @@ subroutine test_charN(res, n)
 
 ! ---------------------------------------------------------------------------
 ! Derived type -- struct with an INTEGER(4) and a REAL(4) field
-! nan/hex: field-by-field walk (integer: 0xAA; real: NaN or bitcast)
+! hex: byte-loop over the whole struct (covers typed fields and padding)
 ! ---------------------------------------------------------------------------
 subroutine test_derived(res)
   type :: mytype
@@ -361,13 +292,6 @@ subroutine test_derived(res)
 ! ZERO: fir.zero_bits !fir.type<{{.*}}>
 ! ZERO: fir.store {{.*}} : !fir.ref<!fir.type<{{.*}}>>
 
-! NAN-LABEL:  func.func @_QPtest_derived
-! NAN:  fir.coordinate_of {{.*}} -> !fir.ref<i32>
-! NAN:  arith.constant {{.*}} : i32
-! NAN:  fir.store {{.*}} : !fir.ref<i32>
-! NAN:  fir.coordinate_of {{.*}} -> !fir.ref<f32>
-! NAN:  arith.constant {{.*}} : f32
-! NAN:  fir.store {{.*}} : !fir.ref<f32>
 
 ! HEX-LABEL:  func.func @_QPtest_derived
 ! HEX:       fir.do_loop
@@ -389,10 +313,6 @@ subroutine test_int_array(res)
 ! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi32>>, index) -> !fir.ref<i32>
 ! ZERO: fir.store {{.*}} : !fir.ref<i32>
 
-! NAN-LABEL:  func.func @_QPtest_int_array
-! NAN:  fir.do_loop
-! NAN:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi32>>, index) -> !fir.ref<i32>
-! NAN:  fir.store {{.*}} : !fir.ref<i32>
 
 ! HEX-LABEL:  func.func @_QPtest_int_array
 ! HEX:  fir.do_loop
@@ -404,7 +324,7 @@ subroutine test_int_array(res)
 ! OFF-NOT: fir.insert_on_range
 
 ! ---------------------------------------------------------------------------
-! Array REAL(4)(4) -- 1-D; nan/snan: NaN element; hex: bitcast element
+! Array REAL(4)(4) -- 1-D; hex: bitcast element
 ! ---------------------------------------------------------------------------
 subroutine test_real_array(res)
   real(4) :: res(4)
@@ -416,15 +336,7 @@ subroutine test_real_array(res)
 ! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf32>>, index) -> !fir.ref<f32>
 ! ZERO: fir.store {{.*}} : !fir.ref<f32>
 
-! NAN-LABEL:  func.func @_QPtest_real_array
-! NAN:  fir.do_loop
-! NAN:  fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf32>>, index) -> !fir.ref<f32>
-! NAN:  fir.store {{.*}} : !fir.ref<f32>
 
-! SNAN-LABEL: func.func @_QPtest_real_array
-! SNAN: fir.do_loop
-! SNAN: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf32>>, index) -> !fir.ref<f32>
-! SNAN: fir.store {{.*}} : !fir.ref<f32>
 
 ! HEX-LABEL:  func.func @_QPtest_real_array
 ! HEX:  fir.do_loop
@@ -460,8 +372,6 @@ subroutine test_explicit_init(res)
 ! ZERO-LABEL: func.func @_QPtest_explicit_init
 ! ZERO-NOT: fir.zero_bits
 
-! NAN-LABEL:  func.func @_QPtest_explicit_init
-! NAN-NOT:  arith.constant -1431655766 : i32
 
 ! HEX-LABEL:  func.func @_QPtest_explicit_init
 ! HEX-NOT:  arith.bitcast
@@ -478,8 +388,6 @@ subroutine test_data_init(res)
 ! ZERO-LABEL: func.func @_QPtest_data_init
 ! ZERO-NOT: fir.zero_bits i32
 
-! NAN-LABEL:  func.func @_QPtest_data_init
-! NAN-NOT:  arith.constant -1431655766 : i32
 
 ! HEX-LABEL:  func.func @_QPtest_data_init
 ! HEX-NOT:  arith.bitcast
@@ -499,8 +407,6 @@ subroutine test_default_comp_init(res)
 ! ZERO-LABEL: func.func @_QPtest_default_comp_init
 ! ZERO-NOT: fir.zero_bits
 
-! NAN-LABEL:  func.func @_QPtest_default_comp_init
-! NAN-NOT:  arith.constant -1431655766 : i32
 
 ! HEX-LABEL:  func.func @_QPtest_default_comp_init
 ! HEX-NOT:  arith.bitcast
@@ -560,9 +466,6 @@ subroutine test_equivalence(res)
 ! ZERO-NOT: fir.zero_bits
 ! ZERO: return
 
-! NAN-LABEL:  func.func @_QPtest_equivalence
-! NAN-NOT:  arith.constant -1431655766 : i32
-! NAN: return
 
 ! HEX-LABEL:  func.func @_QPtest_equivalence
 ! HEX-NOT:  arith.bitcast
diff --git a/flang/tools/bbc/bbc.cpp b/flang/tools/bbc/bbc.cpp
index 694eac3cd6d2f..fbc2e90707b3f 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -280,7 +280,7 @@ static llvm::cl::opt<std::string>
     initLocalMode("finit-local",
                   llvm::cl::desc("Initialize local variables without explicit "
                                  "or default initialization. "
-                                 "Accepts: zero, nan, snan, or 0x<hex-byte>."),
+                                 "Accepts: zero or 0x<hex-byte>."),
                   llvm::cl::init(""));
 
 static llvm::cl::opt<bool> initLocalZero(
@@ -542,10 +542,6 @@ static llvm::LogicalResult convertFortranSourceToMLIR(
     }
     if (val == "zero") {
       loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::Zero);
-    } else if (val == "nan") {
-      loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::QNaN);
-    } else if (val == "snan") {
-      loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::SNaN);
     } else if (val.starts_with("0x") || val.starts_with("0X")) {
       unsigned long long hexVal = 0;
       if (!val.drop_front(2).getAsInteger(16, hexVal) && hexVal <= 0xFF) {

>From 930a99c3209c967fa490ec50a1746b38aa412248 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Mon, 17 Aug 2026 22:46:34 -0400
Subject: [PATCH 08/23] [flang] Address MattPD's fifth round of review comments
 on -finit-local= (Part 1)

Fix CHARACTER initialization for kind>1 (UTF-16/32). Both the
fixed-length and runtime-length scalar paths used fir.char<kind,1> as
the view element, so fir.coordinate_of strided by kind bytes while
storing only one i8 per iteration, leaving kind-1 bytes per code unit
unwritten.

Fix: use fir.char<1,1> (1-byte stride) and scale the loop trip count by
kind in both paths. Add tests for kind=2 and kind=4 fixed-length and
kind=2 runtime-length in finit-local.f90.
---
 flang/lib/Lower/ConvertVariable.cpp | 42 ++++++++++++--------
 flang/test/Lower/finit-local.f90    | 61 +++++++++++++++++++++++++++++
 2 files changed, 86 insertions(+), 17 deletions(-)

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index f8a16c10ed726..e9cfcfe8014ca 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1381,7 +1381,7 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
 }
 
 /// Emit a store of the -finit-local= pattern for a single scalar address.
-/// Fixed-length CHARACTER in hex mode: byte-loop over each code unit.
+/// Fixed-length CHARACTER in hex mode: byte-loop over every byte of storage.
 /// LOGICAL stores via a bitcasted integer address to preserve the raw bit
 /// pattern past fir.convert normalization.
 static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
@@ -1396,22 +1396,27 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
     if (charTy.getLen() == 0)
       return;
     if (mode == Fortran::lower::InitLocalKind::Hex) {
-      // Loop over each code unit of the character storage. The loop body
-      // stores one i8 per iteration via a singleton fir.char<kind,1>
-      // coordinate, so every byte of every code unit is written.
+      // Loop over every byte of the character storage. For kind=1 each
+      // code unit is one byte; for kind=2/4 (UTF-16/32) each code unit is
+      // kind bytes wide. We use a kind=1 singleton as the view element so
+      // fir.coordinate_of advances exactly one byte per step, and iterate
+      // nUnits * kind times to cover all bytes.
       int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
-      if (nUnits > 0) {
+      int64_t kindBytes = charTy.getFKind();
+      int64_t nBytes = nUnits * kindBytes;
+      if (nBytes > 0) {
         mlir::Type idxTy = builder.getIndexType();
         mlir::Type i8Ty = builder.getIntegerType(8);
-        fir::CharacterType byteTy = fir::CharacterType::getSingleton(
-            builder.getContext(), charTy.getFKind());
+        // Use a kind=1 singleton so fir.coordinate_of strides by 1 byte.
+        fir::CharacterType byteTy =
+            fir::CharacterType::getSingleton(builder.getContext(), 1);
         mlir::Type byteSeqTy = fir::SequenceType::get(
             {fir::SequenceType::getUnknownExtent()}, byteTy);
         mlir::Value byteBase =
             builder.createConvert(loc, builder.getRefType(byteSeqTy), addr);
         mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
         mlir::Value last =
-            builder.createIntegerConstant(loc, idxTy, nUnits - 1);
+            builder.createIntegerConstant(loc, idxTy, nBytes - 1);
         mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
         auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
                                           /*unordered=*/false,
@@ -1424,9 +1429,6 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
                                       byteBase, mlir::ValueRange{iv});
         mlir::Value pat = builder.createIntegerConstant(
             loc, i8Ty, static_cast<int64_t>(hexByte));
-        // Store via i8 pointer so the byte pattern is written verbatim
-        // regardless of character kind (UTF-16/32 code units are also
-        // initialised byte-by-byte).
         mlir::Value i8Addr =
             builder.createConvert(loc, builder.getRefType(i8Ty), byteAddr);
         fir::StoreOp::create(builder, loc, pat, i8Addr);
@@ -1592,8 +1594,16 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       mlir::Value lenIdx = builder.createConvert(loc, idxTy, rtLen);
       mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
       mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
-      // last = lenIdx - 1; the fir.do_loop trip count is lenIdx so the
-      // loop body is skipped entirely when lenIdx == 0 (empty string).
+      // For kind>1 (UTF-16/32) the runtime length is in code units; multiply
+      // by kind to get the total byte count before computing the loop bound.
+      // Use a kind=1 singleton so fir.coordinate_of advances 1 byte per step.
+      int64_t kindBytes = charTy.getFKind();
+      if (kindBytes > 1) {
+        mlir::Value kindCst =
+            builder.createIntegerConstant(loc, idxTy, kindBytes);
+        lenIdx = mlir::arith::MulIOp::create(builder, loc, lenIdx, kindCst);
+      }
+      // last = byteCount - 1; the loop is skipped entirely when byteCount == 0.
       mlir::Value last = mlir::arith::SubIOp::create(builder, loc, lenIdx, one);
       auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
                                         /*unordered=*/false,
@@ -1601,10 +1611,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       mlir::OpBuilder::InsertionGuard guard(builder);
       builder.setInsertionPointToStart(loop.getBody());
       mlir::Value iv = loop.getInductionVar();
-      // Treat the storage as an array of singleton characters so that
-      // fir.coordinate_of advances by one code-unit per step.
-      fir::CharacterType byteTy = fir::CharacterType::getSingleton(
-          builder.getContext(), charTy.getFKind());
+      fir::CharacterType byteTy =
+          fir::CharacterType::getSingleton(builder.getContext(), 1);
       mlir::Type byteSeqTy = fir::SequenceType::get(
           {fir::SequenceType::getUnknownExtent()}, byteTy);
       mlir::Value byteBase =
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 6835b6a8313a0..15c5bbd44f780 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -470,3 +470,64 @@ subroutine test_equivalence(res)
 ! HEX-LABEL:  func.func @_QPtest_equivalence
 ! HEX-NOT:  arith.bitcast
 ! HEX: return
+
+! ---------------------------------------------------------------------------
+! CHARACTER(kind=2, len=3) -- fixed-length, higher kind.
+! hex: byte-loop over 3*2=6 bytes using a kind=1 singleton view.
+! zero: fir.zero_bits over the whole type.
+! ---------------------------------------------------------------------------
+subroutine test_char2_fixed(res)
+  character(kind=2, len=3) :: res
+  character(kind=2, len=3) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_char2_fixed
+! ZERO: fir.zero_bits !fir.char<2,3>
+! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<2,3>>
+
+! HEX-LABEL:  func.func @_QPtest_char2_fixed
+! HEX:        fir.do_loop %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} {
+! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX:          arith.constant {{.*}} : i8
+! HEX:          fir.store {{.*}} : !fir.ref<i8>
+
+! ---------------------------------------------------------------------------
+! CHARACTER(kind=4, len=2) -- fixed-length, wider kind.
+! hex: byte-loop over 2*4=8 bytes using a kind=1 singleton view.
+! ---------------------------------------------------------------------------
+subroutine test_char4_fixed(res)
+  character(kind=4, len=2) :: res
+  character(kind=4, len=2) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_char4_fixed
+! ZERO: fir.zero_bits !fir.char<4,2>
+! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<4,2>>
+
+! HEX-LABEL:  func.func @_QPtest_char4_fixed
+! HEX:        fir.do_loop %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} {
+! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX:          arith.constant {{.*}} : i8
+! HEX:          fir.store {{.*}} : !fir.ref<i8>
+
+! ---------------------------------------------------------------------------
+! CHARACTER(kind=2, len=n) -- runtime-length, higher kind.
+! hex/zero: byte-loop over n*2 bytes; skipped when n==0.
+! ---------------------------------------------------------------------------
+subroutine test_char2_runtime(res, n)
+  integer, intent(in) :: n
+  character(kind=2, len=n) :: res
+  character(kind=2, len=n) :: x
+  res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_char2_runtime
+! ZERO:       fir.do_loop
+! ZERO:         fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! ZERO:         fir.zero_bits !fir.char<1>
+! ZERO:         fir.store {{.*}} : !fir.ref<!fir.char<1>>
+
+! HEX-LABEL:  func.func @_QPtest_char2_runtime
+! HEX:        fir.do_loop
+! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX:          arith.constant {{.*}} : i8
+! HEX:          fir.store {{.*}} : !fir.ref<i8>

>From 947d11237f6f5fa0708f8d7ebe93b6e615fac9b9 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Wed, 19 Aug 2026 01:52:43 -0400
Subject: [PATCH 09/23] [flang] Address MattPD's fifth round of review comments
 on -finit-local= (Part 2)

Fix tail padding and x86_fp80 allocation padding for zero and hex modes.

- FIRType.cpp: round up RecordType size to alignment after the field loop
  so getTypeSizeAndAlignment returns the allocation size including tail
  padding (e.g. { i32, i8 } now returns 8 instead of 5).

- ConvertVariable.cpp: replace the fir.zero_bits aggregate store for
  RecordType with a byte-fill loop, matching hex mode. A typed aggregate
  store leaves tail-padding bytes as undef, which LLVM may not zero at -O2.

- ConvertVariable.cpp: for FloatType/ComplexType, detect when allocSize >
  storeSize via DataLayout (e.g. x86_fp80: 10-byte value, 16-byte
  allocation) and emit a byte-fill loop over the full allocation instead
  of a typed store.

- Tests: update finit-local.f90 ZERO check for derived types; add
  tail-padding scalar/array tests in finit-local-array-llvm.f90; add
  finit-local-real10-llvm.f90 (REQUIRES: x86-registered-target) for
  REAL(10)/COMPLEX(10) cases.
---
 flang/lib/Lower/ConvertVariable.cpp          | 59 +++++++++++++----
 flang/lib/Optimizer/Dialect/FIRType.cpp      |  2 +
 flang/test/Lower/finit-local-array-llvm.f90  | 66 +++++++++++++++++++-
 flang/test/Lower/finit-local-real10-llvm.f90 | 46 ++++++++++++++
 flang/test/Lower/finit-local.f90             |  6 +-
 5 files changed, 161 insertions(+), 18 deletions(-)
 create mode 100644 flang/test/Lower/finit-local-real10-llvm.f90

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index e9cfcfe8014ca..d0475dc7f5ecd 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1436,6 +1436,44 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
       return;
     }
   }
+  // REAL and COMPLEX: when the allocation size exceeds the store size
+  // (e.g. x86_fp80 stores 10 bytes but occupies 16), fill the full
+  // allocation with a byte loop so padding bytes are also initialized.
+  auto emitByteLoop = [&](uint64_t nBytes) {
+    mlir::Type idxTy = builder.getIndexType();
+    mlir::Type i8Ty = builder.getIntegerType(8);
+    mlir::Type i8SeqTy =
+        fir::SequenceType::get({fir::SequenceType::getUnknownExtent()}, i8Ty);
+    mlir::Value byteBase =
+        builder.createConvert(loc, builder.getRefType(i8SeqTy), addr);
+    mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
+    mlir::Value last = builder.createIntegerConstant(
+        loc, idxTy, static_cast<int64_t>(nBytes) - 1);
+    mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+    auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
+                                      /*unordered=*/false,
+                                      /*finalCount=*/false);
+    mlir::OpBuilder::InsertionGuard guard(builder);
+    builder.setInsertionPointToStart(loop.getBody());
+    mlir::Value iv = loop.getInductionVar();
+    mlir::Value byteAddr = fir::CoordinateOp::create(
+        builder, loc, builder.getRefType(i8Ty), byteBase, mlir::ValueRange{iv});
+    int64_t fillByte =
+        (mode == Fortran::lower::InitLocalKind::Zero) ? 0 : hexByte;
+    mlir::Value pat = builder.createIntegerConstant(loc, i8Ty, fillByte);
+    fir::StoreOp::create(builder, loc, pat, byteAddr);
+  };
+
+  if (mlir::isa<mlir::FloatType, mlir::ComplexType>(ty)) {
+    const mlir::DataLayout &dl = builder.getDataLayout();
+    uint64_t storeSize = dl.getTypeSize(ty);
+    uint64_t allocSize = llvm::alignTo(storeSize, dl.getTypeABIAlignment(ty));
+    if (allocSize > storeSize) {
+      emitByteLoop(allocSize);
+      return;
+    }
+  }
+
   mlir::Value val;
   switch (mode) {
   case Fortran::lower::InitLocalKind::Zero:
@@ -1530,17 +1568,10 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
             initAddr(eleTy, elemAddr);
           }
         } else if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty)) {
-          // Derived type initialization:
-          //   zero: fir.zero_bits over the whole struct -- covers all typed
-          //         fields and any padding bytes between them.
-          //   hex:  byte-loop over the whole struct using the compile-time
-          //         struct size from fir::getTypeSizeAndAlignmentOrCrash --
-          //         also covers padding bytes, giving a uniform byte pattern.
-          if (mode == Fortran::lower::InitLocalKind::Zero) {
-            fir::StoreOp::create(
-                builder, loc, fir::ZeroOp::create(builder, loc, recTy), addr);
-          } else if (mode == Fortran::lower::InitLocalKind::Hex) {
-            // Fill every byte (typed fields + padding) with hexByte.
+          // Byte-fill the full allocation (fields + internal padding +
+          // tail padding). A typed fir.zero_bits store would leave tail
+          // padding as 'undef', which LLVM may not zero at -O2.
+          {
             auto [byteSize, _align] = fir::getTypeSizeAndAlignmentOrCrash(
                 loc, recTy, builder.getDataLayout(), builder.getKindMap());
             if (byteSize > 0) {
@@ -1563,8 +1594,10 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
               mlir::Value byteAddr = fir::CoordinateOp::create(
                   builder, loc, builder.getRefType(i8Ty), byteBase,
                   mlir::ValueRange{iv});
-              mlir::Value pat = builder.createIntegerConstant(
-                  loc, i8Ty, static_cast<int64_t>(hexByte));
+              int64_t fillByte =
+                  (mode == Fortran::lower::InitLocalKind::Zero) ? 0 : hexByte;
+              mlir::Value pat =
+                  builder.createIntegerConstant(loc, i8Ty, fillByte);
               fir::StoreOp::create(builder, loc, pat, byteAddr);
             }
           }
diff --git a/flang/lib/Optimizer/Dialect/FIRType.cpp b/flang/lib/Optimizer/Dialect/FIRType.cpp
index 5f0dd68aa9396..c3842cda3d713 100644
--- a/flang/lib/Optimizer/Dialect/FIRType.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRType.cpp
@@ -1673,6 +1673,8 @@ fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
           llvm::alignTo(size, compAlign) + llvm::alignTo(compSize, compAlign);
       align = std::max(align, compAlign);
     }
+    // Include tail padding so the size matches the allocation size.
+    size = llvm::alignTo(size, align);
     return std::pair{size, align};
   }
   if (auto logical = mlir::dyn_cast<fir::LogicalType>(ty)) {
diff --git a/flang/test/Lower/finit-local-array-llvm.f90 b/flang/test/Lower/finit-local-array-llvm.f90
index 17290d6e9e370..a8badfb509f7d 100644
--- a/flang/test/Lower/finit-local-array-llvm.f90
+++ b/flang/test/Lower/finit-local-array-llvm.f90
@@ -53,7 +53,8 @@ subroutine test_array_in_struct(res)
 
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_array_in_struct{{.*}}(
-! ZERO:        store {{.*}} zeroinitializer,
+! ZERO:        getelementptr i8, ptr {{.*}}, i64
+! ZERO:        store i8 0,
 
 ! ---------------------------------------------------------------------------
 ! Rank-2 array INTEGER(4)(3,4) -- 12 elements; flat loop via rank-1 view
@@ -120,7 +121,8 @@ subroutine test_array_of_struct(res)
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_struct{{.*}}(
 ! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 2, %{{.*}} ]
 ! ZERO:        getelementptr %{{.*}}t, ptr {{.*}}, i64
-! ZERO:        store {{.*}} zeroinitializer,
+! ZERO:        getelementptr i8, ptr {{.*}}, i64
+! ZERO:        store i8 0,
 
 ! ---------------------------------------------------------------------------
 ! Rank-2 array of derived type  type(t) :: x(2,3) -- 6 elements
@@ -147,4 +149,62 @@ subroutine test_array_of_struct_2d(res)
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_struct_2d{{.*}}(
 ! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 6, %{{.*}} ]
 ! ZERO:        getelementptr %{{.*}}t, ptr {{.*}}, i64
-! ZERO:        store {{.*}} zeroinitializer,
+! ZERO:        getelementptr i8, ptr {{.*}}, i64
+! ZERO:        store i8 0,
+
+! ---------------------------------------------------------------------------
+! Tail-padded scalar: integer(4) :: a followed by integer(1) :: b
+! LLVM lays this out as { i32, i8 } with 3 bytes of tail padding (alloc=8B).
+! Both zero and hex modes must write all 8 bytes, not just the 5 typed bytes.
+! ---------------------------------------------------------------------------
+subroutine test_tail_padded_scalar(res_a, res_b)
+  type :: t
+    integer(4) :: a
+    integer(1) :: b
+  end type
+  type(t) :: x
+  res_a = x%a
+  res_b = x%b
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_tail_padded_scalar{{.*}}(
+! HEX:         phi i64 [ {{.*}}, {{.*}} ], [ 8, %{{.*}} ]
+! HEX:         getelementptr i8, ptr {{.*}}, i64
+! HEX:         store i8 -86,
+! HEX-NOT:     store {{.*}} zeroinitializer,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_tail_padded_scalar{{.*}}(
+! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 8, %{{.*}} ]
+! ZERO:        getelementptr i8, ptr {{.*}}, i64
+! ZERO:        store i8 0,
+! ZERO-NOT:    store {{.*}} zeroinitializer,
+
+! ---------------------------------------------------------------------------
+! Array of tail-padded records: type(t) :: x(3)
+! Each element is 8 bytes (5 typed + 3 tail padding); 3 elements = 24 bytes.
+! Both zero and hex modes must write all 8 bytes per element.
+! ---------------------------------------------------------------------------
+subroutine test_array_of_tail_padded(res_a, res_b)
+  type :: t
+    integer(4) :: a
+    integer(1) :: b
+  end type
+  type(t) :: x(3)
+  res_a = x(1)%a
+  res_b = x(1)%b
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_array_of_tail_padded{{.*}}(
+! HEX:         phi i64 [ {{.*}}, {{.*}} ], [ 3, %{{.*}} ]
+! HEX:         getelementptr %{{.*}}t, ptr {{.*}}, i64
+! HEX:         phi i64 [ {{.*}}, {{.*}} ], [ 8, %{{.*}} ]
+! HEX:         getelementptr i8, ptr {{.*}}, i64
+! HEX:         store i8 -86,
+! HEX-NOT:     store {{.*}} zeroinitializer,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_tail_padded{{.*}}(
+! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 3, %{{.*}} ]
+! ZERO:        getelementptr %{{.*}}t, ptr {{.*}}, i64
+! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 8, %{{.*}} ]
+! ZERO:        getelementptr i8, ptr {{.*}}, i64
+! ZERO:        store i8 0,
+! ZERO-NOT:    store {{.*}} zeroinitializer,
+
diff --git a/flang/test/Lower/finit-local-real10-llvm.f90 b/flang/test/Lower/finit-local-real10-llvm.f90
new file mode 100644
index 0000000000000..7456641e111e9
--- /dev/null
+++ b/flang/test/Lower/finit-local-real10-llvm.f90
@@ -0,0 +1,46 @@
+! Tests that -finit-local= fills the full allocation for REAL(10) and
+! COMPLEX(10) on x86. x86_fp80 has a 10-byte store size but a 16-byte
+! allocation size; a typed store would leave 6 bytes uninitialized.
+! The fix detects the gap via DataLayout and emits a byte-fill loop over
+! the full allocation instead.
+!
+! REQUIRES: x86-registered-target
+!
+! RUN: %flang_fc1 -emit-llvm -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
+! RUN: %flang_fc1 -emit-llvm -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+
+! ---------------------------------------------------------------------------
+! REAL(10) -- x86_fp80: store=10 bytes, alloc=16 bytes on x86-64.
+! ---------------------------------------------------------------------------
+subroutine test_real10(res)
+  real(10) :: res
+  real(10) :: x
+  res = x
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_real10{{.*}}(
+! HEX:         phi i64 [ {{.*}}, {{.*}} ], [ 16, %{{.*}} ]
+! HEX:         getelementptr i8, ptr {{.*}}, i64
+! HEX:         store i8 -86,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_real10{{.*}}(
+! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 16, %{{.*}} ]
+! ZERO:        getelementptr i8, ptr {{.*}}, i64
+! ZERO:        store i8 0,
+
+! ---------------------------------------------------------------------------
+! COMPLEX(10) -- two x86_fp80 parts: store=20 bytes, alloc=32 bytes on x86-64.
+! ---------------------------------------------------------------------------
+subroutine test_complex10(res)
+  complex(10) :: res
+  complex(10) :: x
+  res = x
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_complex10{{.*}}(
+! HEX:         phi i64 [ {{.*}}, {{.*}} ], [ 32, %{{.*}} ]
+! HEX:         getelementptr i8, ptr {{.*}}, i64
+! HEX:         store i8 -86,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_complex10{{.*}}(
+! ZERO:        phi i64 [ {{.*}}, {{.*}} ], [ 32, %{{.*}} ]
+! ZERO:        getelementptr i8, ptr {{.*}}, i64
+! ZERO:        store i8 0,
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 15c5bbd44f780..6e9aca1c3161d 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -289,8 +289,10 @@ subroutine test_derived(res)
   res = x
 end subroutine
 ! ZERO-LABEL: func.func @_QPtest_derived
-! ZERO: fir.zero_bits !fir.type<{{.*}}>
-! ZERO: fir.store {{.*}} : !fir.ref<!fir.type<{{.*}}>>
+! ZERO: fir.do_loop
+! ZERO:   fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! ZERO:   arith.constant 0 : i8
+! ZERO:   fir.store {{.*}} : !fir.ref<i8>
 
 
 ! HEX-LABEL:  func.func @_QPtest_derived

>From 0e8ad8922100796791a04ebbad7d9dba25d4b443 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Tue, 1 Sep 2026 10:48:43 -0400
Subject: [PATCH 10/23] [flang] Fix getTypeSizeAndAlignment to account for
 packed RecordType

fir::getTypeSizeAndAlignment ignored RecordType::isPacked(), computing
the wrong size for packed structs in two ways:
1. Inter-field alignment gaps were added (alignTo(size, compAlign)),
   which packed structs do not have.
2. Each component size was rounded up to its ABI alignment
   (alignTo(compSize, compAlign)), but LLVM packed structs use the
   raw store size (dl.getTypeSize()) per component with no rounding.

For example, for a packed {i32, complex<f64>} on x86-64 (where f64
has 8-byte ABI alignment), the old code returned 24 bytes (the
non-packed aligned size) instead of the correct 20 bytes
(4 + 16, store sizes only).

On AIX, this caused the -finit-local= byte-fill loop to write more
bytes than the packed allocation actually occupies.

LLVM packed structs (<{ ... }>) place fields back-to-back with no
inter-field padding and no tail padding. Each component occupies
exactly its store size with no alignment rounding. The packed struct
ABI alignment is always 1.

Fix the RecordType branch to sum per-component store sizes without
any rounding, and return ABI alignment 1, when isPacked() is true.

Add two tests:
- flang/test/Fir/box-elesize-canonicalize.fir: checks fir.box_elesize
  folds to 20 for a packed {i32, complex<f64>} type using the x86-64
  DLTI spec (non-packed=24B, packed=20B; runs on all platforms).
- flang/test/Lower/finit-local-packed.f90: AIX BIND(C) regression
  verifying the byte-fill loop uses the correct packed size for
  {integer(4), complex(8)} (system-aix).
---
 flang/lib/Optimizer/Dialect/FIRType.cpp     | 16 +++++++++
 flang/test/Fir/box-elesize-canonicalize.fir | 28 +++++++++++++++
 flang/test/Lower/finit-local-packed.f90     | 38 +++++++++++++++++++++
 3 files changed, 82 insertions(+)
 create mode 100644 flang/test/Lower/finit-local-packed.f90

diff --git a/flang/lib/Optimizer/Dialect/FIRType.cpp b/flang/lib/Optimizer/Dialect/FIRType.cpp
index c3842cda3d713..55fd57296edaf 100644
--- a/flang/lib/Optimizer/Dialect/FIRType.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRType.cpp
@@ -1664,6 +1664,22 @@ fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
   if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty)) {
     std::uint64_t size = 0;
     unsigned short align = 1;
+    if (recTy.isPacked()) {
+      // LLVM packed structs (<{ ... }>) place fields back-to-back with no
+      // inter-field alignment padding and no tail padding.  Each component
+      // occupies exactly its store size (dl.getTypeSize(), which is what
+      // getTypeSizeAndAlignment returns as compSize).  Unlike ordinary
+      // structs, there is no per-component rounding of compSize to compAlign.
+      // The packed struct ABI alignment is always 1.
+      for (auto component : recTy.getTypeList()) {
+        auto result =
+            getTypeSizeAndAlignment(loc, component.second, dl, kindMap);
+        if (!result)
+          return result;
+        size += result->first; // store size only; no alignment rounding
+      }
+      return std::pair{size, static_cast<unsigned short>(1)};
+    }
     for (auto component : recTy.getTypeList()) {
       auto result = getTypeSizeAndAlignment(loc, component.second, dl, kindMap);
       if (!result)
diff --git a/flang/test/Fir/box-elesize-canonicalize.fir b/flang/test/Fir/box-elesize-canonicalize.fir
index dd2b06ed3ef77..64ae66faac66b 100644
--- a/flang/test/Fir/box-elesize-canonicalize.fir
+++ b/flang/test/Fir/box-elesize-canonicalize.fir
@@ -102,4 +102,32 @@ module attributes { dlti.dl_spec = #dlti.dl_spec< i1 = dense<8> : vector<2xi64>,
   // CHECK:         %[[C:.*]] = arith.constant 16 : index
   // CHECK:         return %[[C]]
   // CHECK-NOT:     fir.box_elesize
+
+  // Fold: packed derived type -- fir.type<tp<{i:i32,d:f64}>>.
+  // This is the exact example from MattPD's review: on x86-64
+  // (f64 ABI align=8B) the non-packed size is 16B and the packed
+  // size is 12B (i32 store=4B + f64 store=8B, no inter-field gap,
+  // no tail padding).  The element distance must be 12, not 16.
+  func.func @fold_packed_record(%arg0: !fir.box<!fir.type<tp<{i:i32,d:f64}>>>) -> index {
+    %0 = fir.box_elesize %arg0 : (!fir.box<!fir.type<tp<{i:i32,d:f64}>>>) -> index
+    return %0 : index
+  }
+  // CHECK-LABEL: func.func @fold_packed_record(
+  // CHECK:         %[[C:.*]] = arith.constant 12 : index
+  // CHECK:         return %[[C]]
+  // CHECK-NOT:     fir.box_elesize
+
+  // Fold: packed derived type with a wider alignment gap.
+  // On x86-64: i32 store=4B, complex<f64> store=16B.
+  // Non-packed: alignTo(4,4)=4 + alignTo(4,8)+alignTo(16,8)=24, tail=24 -> 24B.
+  // Packed:     4 + 16 = 20B, align=1.
+  // The element distance must be 20, not 24.
+  func.func @fold_packed_record2(%arg0: !fir.box<!fir.type<tp2<{i:i32,z:complex<f64>}>>>) -> index {
+    %0 = fir.box_elesize %arg0 : (!fir.box<!fir.type<tp2<{i:i32,z:complex<f64>}>>>) -> index
+    return %0 : index
+  }
+  // CHECK-LABEL: func.func @fold_packed_record2(
+  // CHECK:         %[[C:.*]] = arith.constant 20 : index
+  // CHECK:         return %[[C]]
+  // CHECK-NOT:     fir.box_elesize
 }
diff --git a/flang/test/Lower/finit-local-packed.f90 b/flang/test/Lower/finit-local-packed.f90
new file mode 100644
index 0000000000000..1897d6452df82
--- /dev/null
+++ b/flang/test/Lower/finit-local-packed.f90
@@ -0,0 +1,38 @@
+! AIX BIND(C) regression test for -finit-local= with packed derived types.
+!
+! On AIX, ConvertType creates a packed layout for a BIND(C) record whose
+! REAL(8) component is not first.  The initialization loop must use the
+! correct packed size.
+!
+! For {integer(4), complex(8)}: i32 store=4B, complex<f64> store=16B.
+! Packed size = 4+16 = 20B.  Loop upper bound must be 19 (trip count 20).
+!
+! Note: on AIX f64 has 4-byte ABI alignment, so the non-packed size of
+! {i32, complex<f64>} also happens to be 20B (no inter-field gap needed).
+! The platform-independent test that distinguishes packed from non-packed
+! (using x86-64 alignment where they differ) lives in
+! flang/test/Fir/box-elesize-canonicalize.fir.
+!
+! REQUIRES: system-aix
+!
+! RUN: bbc -emit-hlfir -finit-local=0xAA -o - %s 2>/dev/null | FileCheck --check-prefix=HEX  %s
+! RUN: bbc -emit-hlfir -finit-local=zero  -o - %s 2>/dev/null | FileCheck --check-prefix=ZERO %s
+
+subroutine test_bindc_aix(res)
+  type, bind(c) :: tp
+    integer(4) :: i
+    complex(8) :: z
+  end type
+  type(tp) :: x
+  res = x%i
+end subroutine
+
+! HEX-LABEL:  func.func @_QPtest_bindc_aix
+! HEX:        arith.constant 19 : index
+! HEX:        fir.do_loop
+! HEX:        fir.store %{{.*}} : !fir.ref<i8>
+
+! ZERO-LABEL: func.func @_QPtest_bindc_aix
+! ZERO:       arith.constant 19 : index
+! ZERO:       fir.do_loop
+! ZERO:       fir.store %{{.*}} : !fir.ref<i8>

>From 01c5d3ffe8df3164d606e3f97fcfebcc0afc9518 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Tue, 1 Sep 2026 23:43:32 -0400
Subject: [PATCH 11/23] [flang] Address MattPD's sixth round of review comments
 on -finit-local=

1. Exclude PowerPC vector locals from initialization.
   vector(real(4)) and related types lower to fir::VectorType, which
   does not implement DataLayoutTypeInterface at the HLFIR level and
   causes a fatal crash when -finit-local= is active. Exclude them in
   shouldInitLocal via DerivedTypeSpec::IsVectorType(). Replace the
   silent fir.ZeroOp fallback in genByteSplatInit with llvm_unreachable
   to catch any future unhandled type. Add a HLFIR-level regression test
   and update ReleaseNotes.md.

2. Fix CUDA device-context initialization for host_device routines and
   BLOCK-construct locals inside global kernels. Move the Device check
   from shouldInitLocal into genInitLocal using
   cuf::isCUDADeviceContext(builder.getRegion()), which mirrors the
   allocation decision and handles BLOCK scopes correctly. Add
   test_host_device_local (CHECK-NOT) and test_global_block_local to
   finit-local-cuda.cuf; tighten test_global_local to anchor the store
   to the declared variable via %[[N]]#0.

3. Strengthen higher-kind CHARACTER test assertions: require the exact
   upper-bound constants (5 and 7) and arith.muli factor-of-2 for the
   runtime kind=2 case in finit-local.f90.

4. Add flang-rt/test/Driver/finit-local-char2-exec.f90: an executable
   test verifying every code unit of a character(kind=2) local equals
   int(z'AAAA') after -finit-local=0xAA, for both fixed and runtime
   lengths including the n=0 edge case.
---
 .../test/Driver/finit-local-char2-exec.f90    | 46 +++++++++++++
 flang/docs/ReleaseNotes.md                    |  1 +
 flang/lib/Lower/ConvertVariable.cpp           | 66 +++++++++++++------
 flang/test/Lower/CUDA/finit-local-cuda.cuf    | 35 +++++++++-
 .../Lower/finit-local-ppc-vector-llvm.f90     | 24 +++++++
 flang/test/Lower/finit-local.f90              |  7 ++
 6 files changed, 156 insertions(+), 23 deletions(-)
 create mode 100644 flang-rt/test/Driver/finit-local-char2-exec.f90
 create mode 100644 flang/test/Lower/finit-local-ppc-vector-llvm.f90

diff --git a/flang-rt/test/Driver/finit-local-char2-exec.f90 b/flang-rt/test/Driver/finit-local-char2-exec.f90
new file mode 100644
index 0000000000000..c48295feb775a
--- /dev/null
+++ b/flang-rt/test/Driver/finit-local-char2-exec.f90
@@ -0,0 +1,46 @@
+! Executable regression test for -finit-local= with kind-2 CHARACTER.
+! Verifies that each code unit of a character(kind=2) local equals
+! int(z'AAAA'): the 0xAA byte pattern covers both bytes of every
+! 2-byte code unit so the full 16-bit value must be 0xAAAA (= 43690).
+!
+! UNSUPPORTED: offload-cuda
+!
+! RUN: %flang %isysroot -L"%libdir" -finit-local=0xAA %s -o %t
+! RUN: env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:%libdir" %t
+
+program test_finit_local_char2
+  implicit none
+
+  call check_char2_fixed()
+  call check_char2_runtime(3)
+  call check_char2_runtime(1)
+  call check_char2_runtime(0)   ! empty: must not crash
+end program
+
+! Fixed-length character(kind=2, len=3): 3 code units, each must be 0xAAAA.
+subroutine check_char2_fixed()
+  character(kind=2, len=3) :: x
+  integer :: i
+  do i = 1, 3
+    if (ichar(x(i:i)) /= int(z'AAAA')) then
+      write(*,*) 'FAIL check_char2_fixed: code unit', i, '=', ichar(x(i:i)), &
+                 'expected', int(z'AAAA')
+      error stop 1
+    end if
+  end do
+end subroutine
+
+! Runtime-length character(kind=2, len=n): each code unit must be 0xAAAA.
+subroutine check_char2_runtime(n)
+  integer, intent(in) :: n
+  character(kind=2, len=n) :: x
+  integer :: i
+  do i = 1, n
+    if (ichar(x(i:i)) /= int(z'AAAA')) then
+      write(*,*) 'FAIL check_char2_runtime: code unit', i, &
+                 'of character(kind=2,len=', n, ') =', ichar(x(i:i)), &
+                 'expected', int(z'AAAA')
+      error stop 1
+    end if
+  end do
+end subroutine
diff --git a/flang/docs/ReleaseNotes.md b/flang/docs/ReleaseNotes.md
index f817a889129a0..05b346aaad7e4 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -102,6 +102,7 @@ page](https://llvm.org/releases/).
   are `zero` and `0x<hex-byte>` (e.g. `0xAA`). The gfortran
   compatibility alias `-finit-local-zero` is equivalent to `-finit-local=zero`.
   Both modes fill every storage byte including struct padding.
+  PowerPC vector locals (`vector(real(4))` etc.) are not initialized.
 
 ## Windows Support
 
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index d0475dc7f5ecd..3f61644e05e91 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1282,8 +1282,10 @@ getSafeRepackAttrs(Fortran::lower::AbstractConverter &converter) {
 /// -finit-local= initialization. Excluded: variables without a symbol,
 /// globals, dummy arguments, SAVE'd vars, ALLOCATABLE/POINTER, vars in
 /// an EQUIVALENCE set, vars with explicit or default initialization, and
-/// CUDA variables whose storage is not host-accessible (device, constant,
-/// shared, usedevice).
+/// CUDA variables whose storage is always unreachable by a plain fir.store
+/// (constant, shared, usedevice). The Device case is deferred to genInitLocal
+/// which applies cuf::isCUDADeviceContext to distinguish cuf.alloc from
+/// fir.alloca storage.
 static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
   if (!var.hasSymbol() || var.isGlobal())
     return false;
@@ -1307,14 +1309,22 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
   // descriptor, not the pointee storage.
   if (sym.test(Fortran::semantics::Symbol::Flag::CrayPointee))
     return false;
+  // PowerPC vector types (vector(real(4)) etc.) lower to fir::VectorType
+  // which does not implement DataLayoutTypeInterface at the HLFIR level.
+  // Exclude them here so no initialization is attempted.
+  if (const Fortran::semantics::DeclTypeSpec *declTy = sym.GetType())
+    if (const Fortran::semantics::DerivedTypeSpec *derived =
+            declTy->AsDerived())
+      if (derived->IsVectorType())
+        return false;
   // CUDA storage accessibility:
   //   constant / shared / usedevice: always unreachable by a plain fir.store
   //     from the host -- skip.
-  //   device in a HOST context: lives in global device memory; a host
-  //     fir.store cannot reach it -- skip.
-  //   device in a DEVICE subprogram: implicitly set by SetImplicitCUDADevice
-  //     for every local in a device kernel; these are per-thread stack
-  //     allocations reachable by a device fir.store -- initialize.
+  //   device: the allocation choice (cuf.alloc vs fir.alloca) depends on
+  //     whether the insertion point is in a device context; that check
+  //     requires the MLIR builder and is deferred to genInitLocal, which
+  //     calls cuf::isCUDADeviceContext(builder.getRegion()) after this
+  //     predicate returns true.
   //   managed / unified / pinned: host-accessible unified memory -- initialize.
   if (auto cudaAttr = Fortran::semantics::GetCUDADataAttr(&sym)) {
     switch (*cudaAttr) {
@@ -1322,13 +1332,6 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
     case Fortran::common::CUDADataAttr::Shared:
     case Fortran::common::CUDADataAttr::UseDevice:
       return false;
-    case Fortran::common::CUDADataAttr::Device:
-      // In a device subprogram the attribute is implicit
-      // (SetImplicitCUDADevice) and the variable is a thread-local stack
-      // allocation -- initialize it.
-      if (!Fortran::semantics::IsCUDADeviceContext(&sym.owner()))
-        return false;
-      break;
     default:
       break;
     }
@@ -1337,10 +1340,12 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
 }
 
 /// Build a constant whose every byte equals \p bytePat.
-/// FP types: bitcast from an integer splat. Complex: apply to both parts.
-/// LOGICAL(k): returns a raw iN integer (caller stores via bitcasted address).
-/// Character: falls back to fir.zero_bits (see TODO). Derived types are
-/// handled by the caller before this function is reached.
+/// Handles: integer, float (bitcast from integer splat), complex (both parts),
+/// and logical (raw integer, stored via bitcasted address by the caller).
+/// Character, derived-type, and sequence types are all intercepted by
+/// genInitLocalStore or initAddr before this function is called and must
+/// not reach it. fir::VectorType (PowerPC vector locals) is excluded
+/// upstream by shouldInitLocal and will never reach this function.
 static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
                                     mlir::Location loc, mlir::Type ty,
                                     uint8_t bytePat) {
@@ -1376,8 +1381,13 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
   if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
     return makeIntCst(logTy.getFKind() * 8);
   }
-  // Fallback (e.g. fir.char reached via an unexpected path): zero-initialise.
-  return fir::ZeroOp::create(builder, loc, eleTy);
+  // All types that pass shouldInitLocal and reach genInitLocalStore are
+  // handled explicitly above (integer, float, complex, logical) or are
+  // intercepted before this call (character, record, sequence).
+  // PowerPC vector types are excluded at shouldInitLocal and never reach here.
+  // A silent zero for an unhandled type would violate the hex-mode contract,
+  // so assert rather than fall back silently.
+  llvm_unreachable("genByteSplatInit: unhandled type in hex mode");
 }
 
 /// Emit a store of the -finit-local= pattern for a single scalar address.
@@ -1504,7 +1514,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
 /// Arrays: all modes use a flat fir.do_loop + fir.coordinate_of over a
 /// rank-1 view to avoid both the llvm.mlir.constant crash on non-zero
 /// ArrayAttrs and the quadratic compile time of fir.insert_on_range.
-/// Derived types use a whole-struct byte-loop for hex. Scalars store directly.
+/// Derived types use a byte-fill loop. PowerPC vector locals are
+/// excluded upstream by shouldInitLocal and never reach this function.
+/// Scalars store directly via genInitLocalStore.
 static void genInitLocal(Fortran::lower::AbstractConverter &converter,
                          const Fortran::lower::pft::Variable &var,
                          Fortran::lower::SymMap &symMap) {
@@ -1519,6 +1531,18 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
   mlir::Location loc = converter.getCurrentLocation();
   uint8_t hexByte = converter.getLoweringOptions().getInitLocalPattern();
 
+  // Mirror the allocation decision: a CUDA Device variable is allocated via
+  // cuf.alloc when cuf::isCUDADeviceContext is false (host or host_device
+  // subprograms), and via fir.alloca when it is true (device/global kernels,
+  // including BLOCK-construct locals inside such kernels).  Only the fir.alloca
+  // path is reachable by a plain fir.store, so skip initialization if the
+  // variable has a Device attribute but the current region is not a device
+  // context (which would mean the storage is a cuf.alloc).
+  if (auto cudaAttr = Fortran::semantics::GetCUDADataAttr(&var.getSymbol()))
+    if (*cudaAttr == Fortran::common::CUDADataAttr::Device &&
+        !cuf::isCUDADeviceContext(builder.getRegion()))
+      return;
+
   fir::ExtendedValue exv =
       converter.getSymbolExtendedValue(var.getSymbol(), &symMap);
   mlir::Value base = fir::getBase(exv);
diff --git a/flang/test/Lower/CUDA/finit-local-cuda.cuf b/flang/test/Lower/CUDA/finit-local-cuda.cuf
index 297623c97dc0e..85fdec3b2afda 100644
--- a/flang/test/Lower/CUDA/finit-local-cuda.cuf
+++ b/flang/test/Lower/CUDA/finit-local-cuda.cuf
@@ -61,6 +61,37 @@ attributes(global) subroutine test_global_local()
   n = 42
 end subroutine
 ! CHECK-LABEL: func.func @{{.*}}test_global_local
-! CHECK: hlfir.declare {{.*}}"_QFtest_global_localEn"
+! CHECK: %[[N:.*]]:2 = hlfir.declare {{.*}}"_QFtest_global_localEn"
 ! CHECK: fir.zero_bits i32
-! CHECK: fir.store {{.*}} : !fir.ref<i32>
+! CHECK: fir.store %{{.*}} to %[[N]]#0 : !fir.ref<i32>
+
+! An explicit 'device' local in an attributes(host,device) routine is
+! allocated via cuf.alloc (not fir.alloca) because cuf::isCUDADeviceContext
+! returns false for HostDevice.  A plain fir.store cannot reach cuf.alloc
+! storage, so shouldInitLocal must return false for this case.
+attributes(host,device) subroutine test_host_device_local()
+  real, device :: x
+  x = 1.0
+end subroutine
+! CHECK-LABEL: func.func @{{.*}}test_host_device_local
+! CHECK-NOT: fir.zero_bits f32
+
+! A BLOCK-construct local inside an attributes(global) kernel lives in
+! device-side fir.alloca storage and MUST be initialized.  The old code
+! used IsCUDADeviceContext on the BlockConstruct scope (which has no
+! subprogram symbol) and returned false, so the variable was skipped.
+! The fix uses cuf::isCUDADeviceContext(builder.getRegion()) in genInitLocal,
+! which walks up through nested regions to the enclosing func and returns
+! true for global kernels regardless of whether the variable is in a BLOCK.
+attributes(global) subroutine test_global_block_local()
+  integer :: m
+  block
+    integer :: n
+    n = 99
+    m = n
+  end block
+end subroutine
+! CHECK-LABEL: func.func @{{.*}}test_global_block_local
+! CHECK: %[[N:.*]]:2 = hlfir.declare {{.*}}"_QFtest_global_block_local{{.*}}En"
+! CHECK: fir.zero_bits i32
+! CHECK: fir.store %{{.*}} to %[[N]]#0 : !fir.ref<i32>
diff --git a/flang/test/Lower/finit-local-ppc-vector-llvm.f90 b/flang/test/Lower/finit-local-ppc-vector-llvm.f90
new file mode 100644
index 0000000000000..0fd3b0613c572
--- /dev/null
+++ b/flang/test/Lower/finit-local-ppc-vector-llvm.f90
@@ -0,0 +1,24 @@
+! Tests that PowerPC vector locals (fir::VectorType) are excluded from
+! -finit-local= initialization because fir::VectorType does not implement
+! DataLayoutTypeInterface at the HLFIR level.
+! The local variable 'x' must not receive any initialization store.
+!
+! REQUIRES: target=powerpc{{.*}}
+!
+! RUN: %flang_fc1 -emit-hlfir -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
+! RUN: %flang_fc1 -emit-hlfir -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+
+! vector(real(4)) -- excluded; must not produce any fir.store for 'x'.
+subroutine test_vec_real4(res)
+  vector(real(4)) :: res
+  vector(real(4)) :: x
+  res = x
+end subroutine
+
+! HEX-LABEL: func.func @_QPtest_vec_real4(
+! HEX:        hlfir.declare {{.*}}_QFtest_vec_real4Ex
+! HEX-NOT:    fir.store {{.*}} to %{{.*}}#0
+
+! ZERO-LABEL: func.func @_QPtest_vec_real4(
+! ZERO:        hlfir.declare {{.*}}_QFtest_vec_real4Ex
+! ZERO-NOT:    fir.store {{.*}} to %{{.*}}#0
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 6e9aca1c3161d..8285240eab54e 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -488,6 +488,7 @@ subroutine test_char2_fixed(res)
 ! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<2,3>>
 
 ! HEX-LABEL:  func.func @_QPtest_char2_fixed
+! HEX:        arith.constant 5 : index
 ! HEX:        fir.do_loop %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} {
 ! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
 ! HEX:          arith.constant {{.*}} : i8
@@ -507,6 +508,7 @@ subroutine test_char4_fixed(res)
 ! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<4,2>>
 
 ! HEX-LABEL:  func.func @_QPtest_char4_fixed
+! HEX:        arith.constant 7 : index
 ! HEX:        fir.do_loop %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} {
 ! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
 ! HEX:          arith.constant {{.*}} : i8
@@ -515,6 +517,7 @@ subroutine test_char4_fixed(res)
 ! ---------------------------------------------------------------------------
 ! CHARACTER(kind=2, len=n) -- runtime-length, higher kind.
 ! hex/zero: byte-loop over n*2 bytes; skipped when n==0.
+! The multiply-by-kind must appear as arith.muli with a factor of 2.
 ! ---------------------------------------------------------------------------
 subroutine test_char2_runtime(res, n)
   integer, intent(in) :: n
@@ -523,12 +526,16 @@ subroutine test_char2_runtime(res, n)
   res = x
 end subroutine
 ! ZERO-LABEL: func.func @_QPtest_char2_runtime
+! ZERO:       arith.constant 2 : index
+! ZERO:       arith.muli {{.*}}, %{{.*}} : index
 ! ZERO:       fir.do_loop
 ! ZERO:         fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
 ! ZERO:         fir.zero_bits !fir.char<1>
 ! ZERO:         fir.store {{.*}} : !fir.ref<!fir.char<1>>
 
 ! HEX-LABEL:  func.func @_QPtest_char2_runtime
+! HEX:        arith.constant 2 : index
+! HEX:        arith.muli {{.*}}, %{{.*}} : index
 ! HEX:        fir.do_loop
 ! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
 ! HEX:          arith.constant {{.*}} : i8

>From 3300d2822080da891c843d3f1238632ac0d0953a Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Wed, 2 Sep 2026 13:08:51 -0400
Subject: [PATCH 12/23] Address MattPD's seventh round of review comments on
 -finit-local=

- ConvertVariable.cpp: replace top-level IsVectorType() check in
  shouldInitLocal with a new containsVectorComponent() helper that
  recurses through derived-type component scopes, excluding records
  that contain a vector component (would crash record-size calculation).
  Update all related comments to describe the pre-fix behaviour (direct
  vector local -> silent zero; derived-with-vector -> crash).
  Add test_derived_with_vec to finit-local-ppc-vector-llvm.f90.

- ConvertVariable.cpp: fix makeIntCst for unsigned integer locals.
  arith.constant requires a signless integer type; the integer branch
  now emits a signless iN constant and follows it with fir.convert when
  the declared element type is signed or unsigned, preserving the bit
  pattern and satisfying FIR verification for !fir.ref<ui32>.
  Add finit-local-unsigned.f90 covering hex and zero modes.

- FIRType.cpp: fix packed struct size to use allocation size per field.
  The packed branch of getTypeSizeAndAlignment was summing store sizes
  (compSize) instead of allocation sizes (alignTo(compSize, compAlign)),
  diverging from LLVM's StructLayout::getTypeAllocSize. Example:
  x86 {f80, i8} packed = alignTo(10,16)+1 = 17B, not 10+1 = 11B.
  Add @fold_packed_f80_i8 to box-elesize-canonicalize.fir asserting 17.

- finit-local.f90: strengthen higher-kind CHARACTER checks.
  Capture arith.constant values 5, 7, and 2 with named variables and
  require each as the corresponding fir.do_loop upper bound or
  arith.muli factor, so shortened bounds or a factor of 1 will fail.

- finit-local-packed.f90: fix header comment to name COMPLEX(8)
  (not REAL(8)) and state the 24-vs-20 byte ordinary/packed distinction.
---
 flang/lib/Lower/ConvertVariable.cpp           | 67 ++++++++++++++++---
 flang/lib/Optimizer/Dialect/FIRType.cpp       | 13 ++--
 flang/test/Fir/box-elesize-canonicalize.fir   | 20 +++++-
 flang/test/Lower/finit-local-packed.f90       |  9 ++-
 .../Lower/finit-local-ppc-vector-llvm.f90     | 25 +++++++
 flang/test/Lower/finit-local-unsigned.f90     | 29 ++++++++
 flang/test/Lower/finit-local.f90              | 16 ++---
 7 files changed, 148 insertions(+), 31 deletions(-)
 create mode 100644 flang/test/Lower/finit-local-unsigned.f90

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 3f61644e05e91..1d94120177a29 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1278,6 +1278,38 @@ getSafeRepackAttrs(Fortran::lower::AbstractConverter &converter) {
 // -finit-local= helpers
 //===----------------------------------------------------------------------===//
 
+/// Returns true if \p derived or any of its components (recursively) is a
+/// PowerPC vector type. fir::VectorType does not implement
+/// DataLayoutTypeInterface, so any record containing a vector component would
+/// crash record-size calculation. Excluding such records at eligibility time
+/// avoids the crash.
+static bool containsVectorComponent(
+    const Fortran::semantics::DerivedTypeSpec &derived) {
+  if (derived.IsVectorType())
+    return true;
+  const Fortran::semantics::Scope *scope = derived.GetScope();
+  if (!scope)
+    return false;
+  const Fortran::semantics::Symbol &typeSym = derived.typeSymbol();
+  const auto *details =
+      typeSym.detailsIf<Fortran::semantics::DerivedTypeDetails>();
+  if (!details)
+    return false;
+  for (const Fortran::semantics::SourceName &compName :
+       details->componentNames()) {
+    auto it = scope->find(compName);
+    if (it == scope->cend())
+      continue;
+    const Fortran::semantics::Symbol &comp = it->second.get();
+    if (const Fortran::semantics::DeclTypeSpec *compTy = comp.GetType())
+      if (const Fortran::semantics::DerivedTypeSpec *compDerived =
+              compTy->AsDerived())
+        if (containsVectorComponent(*compDerived))
+          return true;
+  }
+  return false;
+}
+
 /// Returns true when \p var is an automatic local variable eligible for
 /// -finit-local= initialization. Excluded: variables without a symbol,
 /// globals, dummy arguments, SAVE'd vars, ALLOCATABLE/POINTER, vars in
@@ -1311,11 +1343,14 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
     return false;
   // PowerPC vector types (vector(real(4)) etc.) lower to fir::VectorType
   // which does not implement DataLayoutTypeInterface at the HLFIR level.
-  // Exclude them here so no initialization is attempted.
+  // Without this guard, a direct vector local would silently fall back to
+  // zero initialization regardless of the requested mode, and a derived-type
+  // local whose component is a vector type would crash record-size
+  // calculation.  Exclude both cases here by walking components recursively.
   if (const Fortran::semantics::DeclTypeSpec *declTy = sym.GetType())
     if (const Fortran::semantics::DerivedTypeSpec *derived =
             declTy->AsDerived())
-      if (derived->IsVectorType())
+      if (containsVectorComponent(*derived))
         return false;
   // CUDA storage accessibility:
   //   constant / shared / usedevice: always unreachable by a plain fir.store
@@ -1344,14 +1379,17 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
 /// and logical (raw integer, stored via bitcasted address by the caller).
 /// Character, derived-type, and sequence types are all intercepted by
 /// genInitLocalStore or initAddr before this function is called and must
-/// not reach it. fir::VectorType (PowerPC vector locals) is excluded
-/// upstream by shouldInitLocal and will never reach this function.
+/// not reach it. fir::VectorType (PowerPC vector types, direct or as a
+/// derived-type component) is excluded upstream by shouldInitLocal via
+/// containsVectorComponent and will never reach this function.
 static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
                                     mlir::Location loc, mlir::Type ty,
                                     uint8_t bytePat) {
   mlir::Type eleTy = fir::unwrapSequenceType(ty);
 
-  // Build an integer constant of the given bit width from a byte splat.
+  // Build a signless integer constant from a byte splat.  arith.constant
+  // requires a signless integer type; callers that need a non-signless result
+  // (e.g. unsigned ui32) must fir.convert the returned value themselves.
   auto makeIntCst = [&](unsigned bits) -> mlir::Value {
     llvm::APInt byteVal(8, bytePat);
     llvm::APInt splat = llvm::APInt::getSplat(bits, byteVal);
@@ -1361,12 +1399,17 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
   };
 
   if (auto fpTy = mlir::dyn_cast<mlir::FloatType>(eleTy)) {
-    unsigned bits = fpTy.getWidth();
-    mlir::Value intCst = makeIntCst(bits);
+    mlir::Value intCst = makeIntCst(fpTy.getWidth());
     return mlir::arith::BitcastOp::create(builder, loc, fpTy, intCst);
   }
   if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(eleTy)) {
-    return makeIntCst(intTy.getWidth());
+    mlir::Value cst = makeIntCst(intTy.getWidth());
+    // arith.constant only supports signless integers; fir.convert reinterprets
+    // the bit pattern into the declared signed or unsigned type without
+    // changing any bits, satisfying FIR verification for !fir.ref<ui32> etc.
+    if (!intTy.isSignless())
+      cst = builder.createConvert(loc, intTy, cst);
+    return cst;
   }
   // Complex: apply the byte pattern to each (real, imag) part.
   if (auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(eleTy)) {
@@ -1384,7 +1427,8 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
   // All types that pass shouldInitLocal and reach genInitLocalStore are
   // handled explicitly above (integer, float, complex, logical) or are
   // intercepted before this call (character, record, sequence).
-  // PowerPC vector types are excluded at shouldInitLocal and never reach here.
+  // PowerPC vector types (direct or as a derived-type component) are excluded
+  // by shouldInitLocal via containsVectorComponent and never reach here.
   // A silent zero for an unhandled type would violate the hex-mode contract,
   // so assert rather than fall back silently.
   llvm_unreachable("genByteSplatInit: unhandled type in hex mode");
@@ -1514,8 +1558,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
 /// Arrays: all modes use a flat fir.do_loop + fir.coordinate_of over a
 /// rank-1 view to avoid both the llvm.mlir.constant crash on non-zero
 /// ArrayAttrs and the quadratic compile time of fir.insert_on_range.
-/// Derived types use a byte-fill loop. PowerPC vector locals are
-/// excluded upstream by shouldInitLocal and never reach this function.
+/// Derived types use a byte-fill loop. PowerPC vector types (direct or as a
+/// derived-type component) are excluded upstream by shouldInitLocal via
+/// containsVectorComponent and never reach this function.
 /// Scalars store directly via genInitLocalStore.
 static void genInitLocal(Fortran::lower::AbstractConverter &converter,
                          const Fortran::lower::pft::Variable &var,
diff --git a/flang/lib/Optimizer/Dialect/FIRType.cpp b/flang/lib/Optimizer/Dialect/FIRType.cpp
index 55fd57296edaf..c1fbee15056ce 100644
--- a/flang/lib/Optimizer/Dialect/FIRType.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRType.cpp
@@ -1667,16 +1667,19 @@ fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
     if (recTy.isPacked()) {
       // LLVM packed structs (<{ ... }>) place fields back-to-back with no
       // inter-field alignment padding and no tail padding.  Each component
-      // occupies exactly its store size (dl.getTypeSize(), which is what
-      // getTypeSizeAndAlignment returns as compSize).  Unlike ordinary
-      // structs, there is no per-component rounding of compSize to compAlign.
-      // The packed struct ABI alignment is always 1.
+      // still occupies its allocation size (llvm::alignTo(storeSize, ABI
+      // alignment)), because LLVM's packed StructLayout advances by
+      // getTypeAllocSize, not getTypeStoreSize.  For example, x86 f80 has
+      // store size 10 bytes but ABI alignment 16 bytes, so its allocation
+      // size is 16 bytes; a packed {f80, i8} therefore occupies 17 bytes,
+      // not 11.  The packed struct's own ABI alignment is always 1.
       for (auto component : recTy.getTypeList()) {
         auto result =
             getTypeSizeAndAlignment(loc, component.second, dl, kindMap);
         if (!result)
           return result;
-        size += result->first; // store size only; no alignment rounding
+        auto [compSize, compAlign] = *result;
+        size += llvm::alignTo(compSize, compAlign); // allocation size per field
       }
       return std::pair{size, static_cast<unsigned short>(1)};
     }
diff --git a/flang/test/Fir/box-elesize-canonicalize.fir b/flang/test/Fir/box-elesize-canonicalize.fir
index 64ae66faac66b..7b45264c9e2fc 100644
--- a/flang/test/Fir/box-elesize-canonicalize.fir
+++ b/flang/test/Fir/box-elesize-canonicalize.fir
@@ -106,8 +106,9 @@ module attributes { dlti.dl_spec = #dlti.dl_spec< i1 = dense<8> : vector<2xi64>,
   // Fold: packed derived type -- fir.type<tp<{i:i32,d:f64}>>.
   // This is the exact example from MattPD's review: on x86-64
   // (f64 ABI align=8B) the non-packed size is 16B and the packed
-  // size is 12B (i32 store=4B + f64 store=8B, no inter-field gap,
-  // no tail padding).  The element distance must be 12, not 16.
+  // size is 12B (i32 allocSize=4B + f64 allocSize=alignTo(8,8)=8B,
+  // no inter-field gap, no tail padding).
+  // The element distance must be 12, not 16.
   func.func @fold_packed_record(%arg0: !fir.box<!fir.type<tp<{i:i32,d:f64}>>>) -> index {
     %0 = fir.box_elesize %arg0 : (!fir.box<!fir.type<tp<{i:i32,d:f64}>>>) -> index
     return %0 : index
@@ -130,4 +131,19 @@ module attributes { dlti.dl_spec = #dlti.dl_spec< i1 = dense<8> : vector<2xi64>,
   // CHECK:         %[[C:.*]] = arith.constant 20 : index
   // CHECK:         return %[[C]]
   // CHECK-NOT:     fir.box_elesize
+
+  // Fold: packed derived type with f80 component.
+  // f80 on x86-64: store size = 10B, ABI alignment = 16B,
+  //   allocation size = alignTo(10, 16) = 16B.
+  // i8:  store size = 1B, ABI alignment = 1B, allocation size = 1B.
+  // Packed size = 16 + 1 = 17B (not 10 + 1 = 11B).
+  // This case distinguishes allocation-size from store-size in the packed path.
+  func.func @fold_packed_f80_i8(%arg0: !fir.box<!fir.type<tp3<{x:f80,b:i8}>>>) -> index {
+    %0 = fir.box_elesize %arg0 : (!fir.box<!fir.type<tp3<{x:f80,b:i8}>>>) -> index
+    return %0 : index
+  }
+  // CHECK-LABEL: func.func @fold_packed_f80_i8(
+  // CHECK:         %[[C:.*]] = arith.constant 17 : index
+  // CHECK:         return %[[C]]
+  // CHECK-NOT:     fir.box_elesize
 }
diff --git a/flang/test/Lower/finit-local-packed.f90 b/flang/test/Lower/finit-local-packed.f90
index 1897d6452df82..1d10a642b3c0f 100644
--- a/flang/test/Lower/finit-local-packed.f90
+++ b/flang/test/Lower/finit-local-packed.f90
@@ -1,14 +1,13 @@
 ! AIX BIND(C) regression test for -finit-local= with packed derived types.
 !
 ! On AIX, ConvertType creates a packed layout for a BIND(C) record whose
-! REAL(8) component is not first.  The initialization loop must use the
+! COMPLEX(8) component is not first.  The initialization loop must use the
 ! correct packed size.
 !
 ! For {integer(4), complex(8)}: i32 store=4B, complex<f64> store=16B.
-! Packed size = 4+16 = 20B.  Loop upper bound must be 19 (trip count 20).
-!
-! Note: on AIX f64 has 4-byte ABI alignment, so the non-packed size of
-! {i32, complex<f64>} also happens to be 20B (no inter-field gap needed).
+! Ordinary (non-packed) size = 24B (inter-field and tail padding included).
+! Packed size = 4+16 = 20B (fields placed back-to-back, no padding).
+! Loop upper bound 19 (trip count 20) distinguishes the two calculations.
 ! The platform-independent test that distinguishes packed from non-packed
 ! (using x86-64 alignment where they differ) lives in
 ! flang/test/Fir/box-elesize-canonicalize.fir.
diff --git a/flang/test/Lower/finit-local-ppc-vector-llvm.f90 b/flang/test/Lower/finit-local-ppc-vector-llvm.f90
index 0fd3b0613c572..263d5628aea19 100644
--- a/flang/test/Lower/finit-local-ppc-vector-llvm.f90
+++ b/flang/test/Lower/finit-local-ppc-vector-llvm.f90
@@ -1,6 +1,10 @@
 ! Tests that PowerPC vector locals (fir::VectorType) are excluded from
 ! -finit-local= initialization because fir::VectorType does not implement
 ! DataLayoutTypeInterface at the HLFIR level.
+! Before the fix, a direct vector local silently fell back to zero
+! initialization regardless of the requested mode (not a DataLayout crash).
+! A derived-type local with a vector component would crash record-size
+! calculation; that case is covered by test_derived_with_vec below.
 ! The local variable 'x' must not receive any initialization store.
 !
 ! REQUIRES: target=powerpc{{.*}}
@@ -22,3 +26,24 @@ subroutine test_vec_real4(res)
 ! ZERO-LABEL: func.func @_QPtest_vec_real4(
 ! ZERO:        hlfir.declare {{.*}}_QFtest_vec_real4Ex
 ! ZERO-NOT:    fir.store {{.*}} to %{{.*}}#0
+
+! Derived type with a vector(real(4)) component -- the eligibility check must
+! walk components recursively and exclude this record, just as it excludes a
+! direct vector local.  Before the fix, compilation would crash in record-size
+! calculation when it encountered the unsupported fir::VectorType component.
+subroutine test_derived_with_vec(res)
+  type :: tv
+    integer(4)    :: i
+    vector(real(4)) :: v
+  end type
+  type(tv) :: x
+  res = x%i
+end subroutine
+
+! HEX-LABEL: func.func @_QPtest_derived_with_vec(
+! HEX:        hlfir.declare {{.*}}_QFtest_derived_with_vecEx
+! HEX-NOT:    fir.store {{.*}} to %{{.*}}#0
+
+! ZERO-LABEL: func.func @_QPtest_derived_with_vec(
+! ZERO:        hlfir.declare {{.*}}_QFtest_derived_with_vecEx
+! ZERO-NOT:    fir.store {{.*}} to %{{.*}}#0
diff --git a/flang/test/Lower/finit-local-unsigned.f90 b/flang/test/Lower/finit-local-unsigned.f90
new file mode 100644
index 0000000000000..cfc3d8ee91b4d
--- /dev/null
+++ b/flang/test/Lower/finit-local-unsigned.f90
@@ -0,0 +1,29 @@
+! Tests that -finit-local= preserves the declared unsigned integer type.
+! arith.constant only accepts signless integer types, so makeIntCst always
+! produces a signless iN constant.  For unsigned locals the result is then
+! reinterpreted via fir.convert (iN -> uiN) before the store, which preserves
+! the bit pattern and satisfies FIR verification for !fir.ref<ui32>.
+! Before the fix, the signless i32 constant was stored directly into
+! !fir.ref<ui32>, failing FIR verification with
+! "store value type must match memory reference type".
+! Zero mode uses fir.zero_bits which is type-polymorphic and was not affected.
+!
+! RUN: %flang_fc1 -emit-hlfir -funsigned -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX  %s
+! RUN: %flang_fc1 -emit-hlfir -funsigned -finit-local=zero  %s -o - | FileCheck --check-prefix=ZERO %s
+
+subroutine test_unsigned(res)
+  unsigned :: res
+  unsigned :: x
+  res = x
+end subroutine
+
+! HEX-LABEL:  func.func @_QPtest_unsigned(
+! HEX:         %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_unsignedEx
+! HEX:         %[[C:.*]] = arith.constant {{.*}} : i32
+! HEX:         %[[U:.*]] = fir.convert %[[C]] : (i32) -> ui32
+! HEX:         fir.store %[[U]] to %[[X]]#0 : !fir.ref<ui32>
+
+! ZERO-LABEL: func.func @_QPtest_unsigned(
+! ZERO:        %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_unsignedEx
+! ZERO:        %[[Z:.*]] = fir.zero_bits ui32
+! ZERO:        fir.store %[[Z]] to %[[X]]#0 : !fir.ref<ui32>
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 8285240eab54e..995bc886f05e6 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -488,8 +488,8 @@ subroutine test_char2_fixed(res)
 ! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<2,3>>
 
 ! HEX-LABEL:  func.func @_QPtest_char2_fixed
-! HEX:        arith.constant 5 : index
-! HEX:        fir.do_loop %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} {
+! HEX:        %[[C5:.*]] = arith.constant 5 : index
+! HEX:        fir.do_loop %{{.*}} = %{{.*}} to %[[C5]] step %{{.*}} {
 ! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
 ! HEX:          arith.constant {{.*}} : i8
 ! HEX:          fir.store {{.*}} : !fir.ref<i8>
@@ -508,8 +508,8 @@ subroutine test_char4_fixed(res)
 ! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<4,2>>
 
 ! HEX-LABEL:  func.func @_QPtest_char4_fixed
-! HEX:        arith.constant 7 : index
-! HEX:        fir.do_loop %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} {
+! HEX:        %[[C7:.*]] = arith.constant 7 : index
+! HEX:        fir.do_loop %{{.*}} = %{{.*}} to %[[C7]] step %{{.*}} {
 ! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
 ! HEX:          arith.constant {{.*}} : i8
 ! HEX:          fir.store {{.*}} : !fir.ref<i8>
@@ -526,16 +526,16 @@ subroutine test_char2_runtime(res, n)
   res = x
 end subroutine
 ! ZERO-LABEL: func.func @_QPtest_char2_runtime
-! ZERO:       arith.constant 2 : index
-! ZERO:       arith.muli {{.*}}, %{{.*}} : index
+! ZERO:       %[[C2:.*]] = arith.constant 2 : index
+! ZERO:       arith.muli {{.*}}, %[[C2]] : index
 ! ZERO:       fir.do_loop
 ! ZERO:         fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
 ! ZERO:         fir.zero_bits !fir.char<1>
 ! ZERO:         fir.store {{.*}} : !fir.ref<!fir.char<1>>
 
 ! HEX-LABEL:  func.func @_QPtest_char2_runtime
-! HEX:        arith.constant 2 : index
-! HEX:        arith.muli {{.*}}, %{{.*}} : index
+! HEX:        %[[C2:.*]] = arith.constant 2 : index
+! HEX:        arith.muli {{.*}}, %[[C2]] : index
 ! HEX:        fir.do_loop
 ! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
 ! HEX:          arith.constant {{.*}} : i8

>From 7dd13c2c680df2bc8f5b3c3634d4c2ea311e586c Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Wed, 2 Sep 2026 16:03:09 -0400
Subject: [PATCH 13/23] Address eugeneepshteyn's review comments on
 -finit-local= volatility and portability

- ConvertVariable.cpp: replace builder.createConvert with
  builder.createConvertWithVolatileCast at all four byte-view
  conversion sites (fixed-length CHARACTER hex loop, REAL/COMPLEX
  allocation-gap loop, record byte-fill loop, runtime CHARACTER loop).
  A plain fir.convert from a volatile ref to a non-volatile byte-sequence
  ref fails the strict FIR volatile verifier; the new helper inserts
  fir.volatile_cast before the fir.convert when the source is volatile.

- finit-local-real10-llvm.f90: pin both RUN lines to
  -triple x86_64-unknown-linux-gnu so the test does not run with the
  host triple on aarch64/ppc64le builders where real(10) is disabled.

- finit-local-volatile.f90 (new): regression test exercising both
  affected code paths under -mmlir --strict-fir-volatile-verifier:
  a volatile derived-type local (record byte-fill loop) and a volatile
  real(10) local (allocation-gap loop, x86 only). Verifies that
  fir.volatile_cast precedes the byte-sequence fir.convert in both
  zero and hex modes.
---
 flang/lib/Lower/ConvertVariable.cpp          | 12 ++--
 flang/test/Lower/finit-local-real10-llvm.f90 |  4 +-
 flang/test/Lower/finit-local-volatile.f90    | 72 ++++++++++++++++++++
 3 files changed, 82 insertions(+), 6 deletions(-)
 create mode 100644 flang/test/Lower/finit-local-volatile.f90

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 1d94120177a29..fc680a564540b 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1467,7 +1467,8 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
         mlir::Type byteSeqTy = fir::SequenceType::get(
             {fir::SequenceType::getUnknownExtent()}, byteTy);
         mlir::Value byteBase =
-            builder.createConvert(loc, builder.getRefType(byteSeqTy), addr);
+            builder.createConvertWithVolatileCast(
+                loc, builder.getRefType(byteSeqTy), addr);
         mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
         mlir::Value last =
             builder.createIntegerConstant(loc, idxTy, nBytes - 1);
@@ -1499,7 +1500,8 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
     mlir::Type i8SeqTy =
         fir::SequenceType::get({fir::SequenceType::getUnknownExtent()}, i8Ty);
     mlir::Value byteBase =
-        builder.createConvert(loc, builder.getRefType(i8SeqTy), addr);
+        builder.createConvertWithVolatileCast(
+            loc, builder.getRefType(i8SeqTy), addr);
     mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
     mlir::Value last = builder.createIntegerConstant(
         loc, idxTy, static_cast<int64_t>(nBytes) - 1);
@@ -1649,7 +1651,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
               mlir::Type i8SeqTy = fir::SequenceType::get(
                   {fir::SequenceType::getUnknownExtent()}, i8Ty);
               mlir::Value byteBase =
-                  builder.createConvert(loc, builder.getRefType(i8SeqTy), addr);
+                  builder.createConvertWithVolatileCast(
+                      loc, builder.getRefType(i8SeqTy), addr);
               mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
               mlir::Value last = builder.createIntegerConstant(
                   loc, idxTy, static_cast<int64_t>(byteSize) - 1);
@@ -1718,7 +1721,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       mlir::Type byteSeqTy = fir::SequenceType::get(
           {fir::SequenceType::getUnknownExtent()}, byteTy);
       mlir::Value byteBase =
-          builder.createConvert(loc, builder.getRefType(byteSeqTy), base);
+          builder.createConvertWithVolatileCast(
+              loc, builder.getRefType(byteSeqTy), base);
       mlir::Value byteAddr =
           fir::CoordinateOp::create(builder, loc, builder.getRefType(byteTy),
                                     byteBase, mlir::ValueRange{iv});
diff --git a/flang/test/Lower/finit-local-real10-llvm.f90 b/flang/test/Lower/finit-local-real10-llvm.f90
index 7456641e111e9..523831129a685 100644
--- a/flang/test/Lower/finit-local-real10-llvm.f90
+++ b/flang/test/Lower/finit-local-real10-llvm.f90
@@ -6,8 +6,8 @@
 !
 ! REQUIRES: x86-registered-target
 !
-! RUN: %flang_fc1 -emit-llvm -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
-! RUN: %flang_fc1 -emit-llvm -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+! RUN: %flang_fc1 -triple x86_64-unknown-linux-gnu -emit-llvm -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
+! RUN: %flang_fc1 -triple x86_64-unknown-linux-gnu -emit-llvm -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
 
 ! ---------------------------------------------------------------------------
 ! REAL(10) -- x86_fp80: store=10 bytes, alloc=16 bytes on x86-64.
diff --git a/flang/test/Lower/finit-local-volatile.f90 b/flang/test/Lower/finit-local-volatile.f90
new file mode 100644
index 0000000000000..57b5157cb9dbd
--- /dev/null
+++ b/flang/test/Lower/finit-local-volatile.f90
@@ -0,0 +1,72 @@
+! Tests that -finit-local= byte-fill loops correctly handle volatile locals.
+! Each byte-view conversion must go through fir.volatile_cast before the
+! fir.convert that reinterprets the address as a byte sequence, so that the
+! strict FIR volatile verifier does not see a "mismatched volatility" error.
+!
+! Before the fix both zero and hex modes emitted a plain fir.convert from
+! a volatile ref directly to a non-volatile byte-sequence ref, which the
+! strict verifier rejects with "mismatched volatility".
+!
+! Two shapes are covered:
+!   1. volatile derived-type local  -- exercises the record byte-fill loop
+!      in initAddr (fir.volatile_cast before the i8-array view convert).
+!   2. volatile real(10) local      -- exercises the allocation-gap byte-fill
+!      loop in genInitLocalStore (same fix, different site).
+!      Requires x86 so that real(10) = x86_fp80 with a 6-byte padding gap.
+!
+! RUN: %flang_fc1 -emit-hlfir -mmlir --strict-fir-volatile-verifier \
+! RUN:     -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+! RUN: %flang_fc1 -emit-hlfir -mmlir --strict-fir-volatile-verifier \
+! RUN:     -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX  %s
+
+! ---------------------------------------------------------------------------
+! Volatile derived-type local -- record byte-fill loop path.
+! ---------------------------------------------------------------------------
+subroutine test_volatile_derived(oa)
+  type :: t
+    integer(4) :: a
+    integer(1) :: b
+  end type
+  type(t), volatile :: v
+  integer :: oa
+  v%a = 1
+  oa = v%a
+end subroutine
+
+! ZERO-LABEL: func.func @_QPtest_volatile_derived(
+! ZERO:        %[[V:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_derivedEv
+! ZERO:        %[[NV:.*]] = fir.volatile_cast %[[V]]#0 : (!fir.ref<{{.*}}, volatile>) -> !fir.ref<{{.*}}>
+! ZERO:        fir.convert %[[NV]] : (!fir.ref<{{.*}}>) -> !fir.ref<!fir.array<?xi8>>
+! ZERO:        fir.do_loop
+! ZERO:        fir.store {{.*}} : !fir.ref<i8>
+
+! HEX-LABEL:  func.func @_QPtest_volatile_derived(
+! HEX:         %[[V:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_derivedEv
+! HEX:         %[[NV:.*]] = fir.volatile_cast %[[V]]#0 : (!fir.ref<{{.*}}, volatile>) -> !fir.ref<{{.*}}>
+! HEX:         fir.convert %[[NV]] : (!fir.ref<{{.*}}>) -> !fir.ref<!fir.array<?xi8>>
+! HEX:         fir.do_loop
+! HEX:         fir.store {{.*}} : !fir.ref<i8>
+
+! ---------------------------------------------------------------------------
+! Volatile real(10) local -- allocation-gap byte-fill loop path (x86 only).
+! ---------------------------------------------------------------------------
+! REQUIRES: x86-registered-target
+subroutine test_volatile_real10(res)
+  real(10), volatile :: x
+  real(10) :: res
+  res = x
+end subroutine
+
+! ZERO-LABEL: func.func @_QPtest_volatile_real10(
+! ZERO:        %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_real10Ex
+! ZERO:        %[[NV:.*]] = fir.volatile_cast %[[X]]#0 : (!fir.ref<f80, volatile>) -> !fir.ref<f80>
+! ZERO:        fir.convert %[[NV]] : (!fir.ref<f80>) -> !fir.ref<!fir.array<?xi8>>
+! ZERO:        fir.do_loop
+! ZERO:        fir.store {{.*}} : !fir.ref<i8>
+
+! HEX-LABEL:  func.func @_QPtest_volatile_real10(
+! HEX:         %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_real10Ex
+! HEX:         %[[NV:.*]] = fir.volatile_cast %[[X]]#0 : (!fir.ref<f80, volatile>) -> !fir.ref<f80>
+! HEX:         fir.convert %[[NV]] : (!fir.ref<f80>) -> !fir.ref<!fir.array<?xi8>>
+! HEX:         fir.do_loop
+! HEX:         fir.store {{.*}} : !fir.ref<i8>

>From 450b03c0bf0e48fbf875848621ba07ebe39dcf40 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Wed, 2 Sep 2026 20:02:48 -0400
Subject: [PATCH 14/23] [flang] Fix -finit-local= exclusions, kind-mapping
 width, and CUDA tests

- shouldInitLocal: exclude function result variables (IsFuncResult)
  and main-program locals (implicit SAVE, Scope::Kind::MainProgram).
- Add finit-local-exclusions.f90: pinned %flang_fc1 -emit-hlfir
  -finit-local=0xAA + CHECK-NOT tests for all documented exclusions
  (function result, main-program implicit SAVE, runtime-sized array,
  CHARACTER array, POINTER-component derived type).
- Fix getFKind() used as byte width at logical/character sites; use
  KindMapping::getLogicalBitsize()/getCharacterBitsize() instead.
  Addresses eugeneepshteyn's review comment.
- Add finit-local-kind-mapping.f90: --kind-mapping=l4:8 regression test.
- finit-local-cuda.cuf: tighten test_global_local with CHECK-NEXT and
  %[[NZ]] to bind the zero-init store to the user-declared n.
  Addresses MattPD's review comment.
---
 flang/lib/Lower/ConvertVariable.cpp           | 57 +++++++-----
 flang/test/Lower/CUDA/finit-local-cuda.cuf    |  6 +-
 flang/test/Lower/finit-local-exclusions.f90   | 92 +++++++++++++++++++
 flang/test/Lower/finit-local-kind-mapping.f90 | 33 +++++++
 4 files changed, 163 insertions(+), 25 deletions(-)
 create mode 100644 flang/test/Lower/finit-local-exclusions.f90
 create mode 100644 flang/test/Lower/finit-local-kind-mapping.f90

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index fc680a564540b..be8d555bbab3c 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1283,8 +1283,8 @@ getSafeRepackAttrs(Fortran::lower::AbstractConverter &converter) {
 /// DataLayoutTypeInterface, so any record containing a vector component would
 /// crash record-size calculation. Excluding such records at eligibility time
 /// avoids the crash.
-static bool containsVectorComponent(
-    const Fortran::semantics::DerivedTypeSpec &derived) {
+static bool
+containsVectorComponent(const Fortran::semantics::DerivedTypeSpec &derived) {
   if (derived.IsVectorType())
     return true;
   const Fortran::semantics::Scope *scope = derived.GetScope();
@@ -1324,6 +1324,15 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
   const Fortran::semantics::Symbol &sym = var.getSymbol();
   if (Fortran::semantics::IsDummy(sym))
     return false;
+  // Function result variables own the return-value storage and must not be
+  // pre-initialized: the function body is responsible for setting the result.
+  if (sym.IsFuncResult())
+    return false;
+  // Main-program locals have implicit SAVE semantics (Fortran 2018 8.5.16p4).
+  // IsSaved() does not catch this case because the SAVE attribute is implicit
+  // rather than explicit, so check the enclosing scope kind directly.
+  if (sym.owner().kind() == Fortran::semantics::Scope::Kind::MainProgram)
+    return false;
   if (Fortran::semantics::IsSaved(sym))
     return false;
   if (Fortran::semantics::IsAllocatableOrPointer(sym))
@@ -1418,11 +1427,14 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
     return mlir::complex::CreateOp::create(builder, loc, cplxTy, partVal,
                                            partVal);
   }
-  // LOGICAL(k) has a fixed size of k bytes. Return the raw integer splat;
-  // the caller stores it via a bitcasted address to preserve the bit pattern
+  // LOGICAL(k) has a fixed size of k bytes under the default kind mapping,
+  // but a non-default mapping (e.g. --kind-mapping=l4:8) may map LOGICAL(4)
+  // to a single byte.  Use KindMapping::getLogicalBitsize so the constant
+  // width matches the actual allocation size.
+  // The caller stores it via a bitcasted address to preserve the bit pattern
   // (fir.convert from integer to !fir.logical normalizes nonzero -> true).
   if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
-    return makeIntCst(logTy.getFKind() * 8);
+    return makeIntCst(builder.getKindMap().getLogicalBitsize(logTy.getFKind()));
   }
   // All types that pass shouldInitLocal and reach genInitLocalStore are
   // handled explicitly above (integer, float, complex, logical) or are
@@ -1454,9 +1466,11 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
       // code unit is one byte; for kind=2/4 (UTF-16/32) each code unit is
       // kind bytes wide. We use a kind=1 singleton as the view element so
       // fir.coordinate_of advances exactly one byte per step, and iterate
-      // nUnits * kind times to cover all bytes.
+      // nUnits * kindBytes times to cover all bytes.  Use KindMapping to
+      // get the true byte width under any --kind-mapping override.
       int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
-      int64_t kindBytes = charTy.getFKind();
+      int64_t kindBytes =
+          builder.getKindMap().getCharacterBitsize(charTy.getFKind()) / 8;
       int64_t nBytes = nUnits * kindBytes;
       if (nBytes > 0) {
         mlir::Type idxTy = builder.getIndexType();
@@ -1466,9 +1480,8 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
             fir::CharacterType::getSingleton(builder.getContext(), 1);
         mlir::Type byteSeqTy = fir::SequenceType::get(
             {fir::SequenceType::getUnknownExtent()}, byteTy);
-        mlir::Value byteBase =
-            builder.createConvertWithVolatileCast(
-                loc, builder.getRefType(byteSeqTy), addr);
+        mlir::Value byteBase = builder.createConvertWithVolatileCast(
+            loc, builder.getRefType(byteSeqTy), addr);
         mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
         mlir::Value last =
             builder.createIntegerConstant(loc, idxTy, nBytes - 1);
@@ -1499,9 +1512,8 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
     mlir::Type i8Ty = builder.getIntegerType(8);
     mlir::Type i8SeqTy =
         fir::SequenceType::get({fir::SequenceType::getUnknownExtent()}, i8Ty);
-    mlir::Value byteBase =
-        builder.createConvertWithVolatileCast(
-            loc, builder.getRefType(i8SeqTy), addr);
+    mlir::Value byteBase = builder.createConvertWithVolatileCast(
+        loc, builder.getRefType(i8SeqTy), addr);
     mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
     mlir::Value last = builder.createIntegerConstant(
         loc, idxTy, static_cast<int64_t>(nBytes) - 1);
@@ -1547,7 +1559,8 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
   // logical true).
   if (mode == Fortran::lower::InitLocalKind::Hex &&
       mlir::isa<fir::LogicalType>(ty)) {
-    unsigned bits = mlir::cast<fir::LogicalType>(ty).getFKind() * 8;
+    auto logTy = mlir::cast<fir::LogicalType>(ty);
+    unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
     mlir::Type intRefTy = builder.getRefType(builder.getIntegerType(bits));
     mlir::Value intAddr = builder.createConvert(loc, intRefTy, addr);
     fir::StoreOp::create(builder, loc, val, intAddr);
@@ -1650,9 +1663,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
               mlir::Type i8Ty = builder.getIntegerType(8);
               mlir::Type i8SeqTy = fir::SequenceType::get(
                   {fir::SequenceType::getUnknownExtent()}, i8Ty);
-              mlir::Value byteBase =
-                  builder.createConvertWithVolatileCast(
-                      loc, builder.getRefType(i8SeqTy), addr);
+              mlir::Value byteBase = builder.createConvertWithVolatileCast(
+                  loc, builder.getRefType(i8SeqTy), addr);
               mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
               mlir::Value last = builder.createIntegerConstant(
                   loc, idxTy, static_cast<int64_t>(byteSize) - 1);
@@ -1700,9 +1712,11 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
       mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
       // For kind>1 (UTF-16/32) the runtime length is in code units; multiply
-      // by kind to get the total byte count before computing the loop bound.
+      // by the kind byte width to get the total byte count.  Use KindMapping
+      // so a --kind-mapping override is respected.
       // Use a kind=1 singleton so fir.coordinate_of advances 1 byte per step.
-      int64_t kindBytes = charTy.getFKind();
+      int64_t kindBytes =
+          builder.getKindMap().getCharacterBitsize(charTy.getFKind()) / 8;
       if (kindBytes > 1) {
         mlir::Value kindCst =
             builder.createIntegerConstant(loc, idxTy, kindBytes);
@@ -1720,9 +1734,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
           fir::CharacterType::getSingleton(builder.getContext(), 1);
       mlir::Type byteSeqTy = fir::SequenceType::get(
           {fir::SequenceType::getUnknownExtent()}, byteTy);
-      mlir::Value byteBase =
-          builder.createConvertWithVolatileCast(
-              loc, builder.getRefType(byteSeqTy), base);
+      mlir::Value byteBase = builder.createConvertWithVolatileCast(
+          loc, builder.getRefType(byteSeqTy), base);
       mlir::Value byteAddr =
           fir::CoordinateOp::create(builder, loc, builder.getRefType(byteTy),
                                     byteBase, mlir::ValueRange{iv});
diff --git a/flang/test/Lower/CUDA/finit-local-cuda.cuf b/flang/test/Lower/CUDA/finit-local-cuda.cuf
index 85fdec3b2afda..2a3233d87caaf 100644
--- a/flang/test/Lower/CUDA/finit-local-cuda.cuf
+++ b/flang/test/Lower/CUDA/finit-local-cuda.cuf
@@ -61,9 +61,9 @@ attributes(global) subroutine test_global_local()
   n = 42
 end subroutine
 ! CHECK-LABEL: func.func @{{.*}}test_global_local
-! CHECK: %[[N:.*]]:2 = hlfir.declare {{.*}}"_QFtest_global_localEn"
-! CHECK: fir.zero_bits i32
-! CHECK: fir.store %{{.*}} to %[[N]]#0 : !fir.ref<i32>
+! CHECK:       %[[N:.*]]:2 = hlfir.declare {{.*}}"_QFtest_global_localEn"
+! CHECK-NEXT:  %[[NZ:.*]] = fir.zero_bits i32
+! CHECK-NEXT:  fir.store %[[NZ]] to %[[N]]#0 : !fir.ref<i32>
 
 ! An explicit 'device' local in an attributes(host,device) routine is
 ! allocated via cuf.alloc (not fir.alloca) because cuf::isCUDADeviceContext
diff --git a/flang/test/Lower/finit-local-exclusions.f90 b/flang/test/Lower/finit-local-exclusions.f90
new file mode 100644
index 0000000000000..faa01f0023ad6
--- /dev/null
+++ b/flang/test/Lower/finit-local-exclusions.f90
@@ -0,0 +1,92 @@
+! Pinned negative tests for -finit-local= exclusions.
+!
+! Each case uses %flang_fc1 -emit-hlfir -finit-local=0xAA and asserts that
+! no synthesized initialization store appears for the excluded variable.
+!
+! Exclusions verified here:
+!   1. Function result variable
+!   2. Main-program local (implicit SAVE per Fortran 2018 8.5.16p4)
+!   3. Runtime-sized automatic array (unknown extent at compile time)
+!   4. CHARACTER array (pending memset infrastructure, PR #159788)
+!   5. Derived type with POINTER component (IsAllocatableOrPointer on component)
+!   6. host,device CUDA local (allocated via cuf.alloc, not fir.alloca)
+!      -- see finit-local-cuda.cuf for the CUDA cases
+!   7. PowerPC vector locals (direct or as a derived-type component)
+!      -- see finit-local-ppc-vector-llvm.f90 for the vector cases
+
+! RUN: %flang_fc1 -emit-hlfir -finit-local=0xAA %s -o - | FileCheck %s
+
+! ---------------------------------------------------------------------------
+! 1. Function result variable -- must NOT be pre-initialized.
+!    The function body owns the result; a synthesized store would overwrite
+!    a value already assigned before the body's first user store.
+! ---------------------------------------------------------------------------
+function test_func_result() result(x)
+  integer :: x
+  x = 42
+end function
+! CHECK-LABEL: func.func @_QPtest_func_result
+! CHECK:       hlfir.declare {{.*}} "_QFtest_func_resultEx"
+! CHECK-NOT:   fir.store
+! CHECK:       hlfir.assign
+! CHECK:       return
+
+! ---------------------------------------------------------------------------
+! 2. Main-program local -- implicit SAVE (Fortran 2018 8.5.16p4).
+!    IsSaved() misses the implicit case; the scope-kind guard catches it.
+! ---------------------------------------------------------------------------
+program test_main_prog
+  integer :: n
+  n = 1
+end program
+! CHECK-LABEL: func.func @_QQmain
+! CHECK:       hlfir.declare {{.*}} "_QFEn"
+! CHECK-NOT:   fir.store
+! CHECK:       hlfir.assign
+! CHECK:       return
+
+! ---------------------------------------------------------------------------
+! 3. Runtime-sized automatic array -- extent is unknown at compile time.
+!    The array base is a fir.box; no compile-time trip count is available.
+! ---------------------------------------------------------------------------
+subroutine test_runtime_array(res, n)
+  integer, intent(in) :: n
+  integer :: x(n)
+  integer :: res
+  res = x(1)
+end subroutine
+! CHECK-LABEL: func.func @_QPtest_runtime_array
+! CHECK:       hlfir.declare {{.*}} "_QFtest_runtime_arrayEx"
+! CHECK-NOT:   fir.do_loop
+! CHECK:       return
+
+! ---------------------------------------------------------------------------
+! 4. CHARACTER array -- pending memset infrastructure (PR #159788).
+!    Static CHARACTER scalars are initialized; arrays are silently skipped.
+! ---------------------------------------------------------------------------
+subroutine test_char_array(res)
+  character(4) :: x(3)
+  character(4) :: res
+  res = x(1)
+end subroutine
+! CHECK-LABEL: func.func @_QPtest_char_array
+! CHECK:       hlfir.declare {{.*}} "_QFtest_char_arrayEx"
+! CHECK-NOT:   fir.do_loop
+! CHECK:       return
+
+! ---------------------------------------------------------------------------
+! 5. Derived type with POINTER component -- the component descriptor must
+!    not be byte-stomped; IsAllocatableOrPointer on the record catches it.
+! ---------------------------------------------------------------------------
+subroutine test_pointer_comp(res)
+  type t
+    integer, pointer :: p
+  end type
+  type(t) :: x
+  integer :: res
+  res = 0
+end subroutine
+! CHECK-LABEL: func.func @_QPtest_pointer_comp
+! CHECK:       hlfir.declare {{.*}} "_QFtest_pointer_compEx"
+! CHECK-NOT:   fir.do_loop
+! CHECK:       return
diff --git a/flang/test/Lower/finit-local-kind-mapping.f90 b/flang/test/Lower/finit-local-kind-mapping.f90
new file mode 100644
index 0000000000000..5e8f92813903a
--- /dev/null
+++ b/flang/test/Lower/finit-local-kind-mapping.f90
@@ -0,0 +1,33 @@
+! Tests that -finit-local= respects non-default kind mappings.
+! Under the default mapping getFKind() happens to equal the byte width, but
+! with --kind-mapping overrides the two diverge.  All width calculations must
+! use KindMapping::getLogicalBitsize() / getCharacterBitsize() rather than
+! getFKind() * 8 or getFKind() directly.
+!
+! Reproducer: --kind-mapping=l4:8 maps LOGICAL(4) to 8 bits (1 byte).
+! Before the fix, the synthesized init emitted a 4-byte i32 constant stored
+! via a bitcasted i32* into a 1-byte alloca -- a 3-byte out-of-bounds write.
+! After the fix the constant and store are both i8.
+!
+! RUN: bbc -emit-hlfir --kind-mapping=l4:8 -finit-local=0xAA %s -o - | \
+! RUN:     FileCheck --check-prefix=HEX %s
+! RUN: bbc -emit-hlfir --kind-mapping=l4:8 -finit-local=zero %s -o - | \
+! RUN:     FileCheck --check-prefix=ZERO %s
+
+! LOGICAL(4) remapped to 8 bits -- init must use i8, not i32.
+subroutine test_logical4_km(res)
+  logical(kind=4) :: l
+  integer :: res
+  if (l) res = 1
+end subroutine
+
+! HEX-LABEL:  func.func @_QPtest_logical4_km(
+! HEX:         %[[L:.*]]:2 = hlfir.declare {{.*}}_QFtest_logical4_kmEl
+! HEX:         %[[C:.*]] = arith.constant {{.*}} : i8
+! HEX:         %[[ADDR:.*]] = fir.convert %[[L]]#0 : (!fir.ref<!fir.logical<4>>) -> !fir.ref<i8>
+! HEX:         fir.store %[[C]] to %[[ADDR]] : !fir.ref<i8>
+
+! ZERO-LABEL: func.func @_QPtest_logical4_km(
+! ZERO:        %[[L:.*]]:2 = hlfir.declare {{.*}}_QFtest_logical4_kmEl
+! ZERO:        %[[Z:.*]] = fir.zero_bits !fir.logical<4>
+! ZERO:        fir.store %[[Z]] to %[[L]]#0 : !fir.ref<!fir.logical<4>>

>From cc2de674d2aebe7a9a3abe884f2c24168aea2f8d Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Wed, 2 Sep 2026 21:00:33 -0400
Subject: [PATCH 15/23] [flang] Fix -finit-local= to preserve volatility
 end-to-end in byte-fill loops

Pass the source address volatility flag to getRefType() at all byte-fill
sites (fixed-length CHARACTER, REAL/COMPLEX allocation-gap, record fill,
runtime CHARACTER, rank-1 array view, LOGICAL bitcast) so that byte-seq
refs and CoordinateOp result types are volatile when the source is.
createConvertWithVolatileCast then emits a direct volatile fir.convert
(no fir.volatile_cast needed) and final stores are "store volatile".

Also fix two missing volatile flags from the earlier PRRT_kwDOBITxeM6etGbi
fix: rank1SeqTy and intRefTy were built without the volatile flag.

Add finit-local-volatile-real10.f90 (pinned -triple x86_64-unknown-linux-gnu)
for the real(10) allocation-gap path, removing it from the unpinned
finit-local-volatile.f90. Add finit-local-volatile-llvm.f90 for LLVM IR
"store volatile" checks across derived-type, real(10), and array paths.

Addresses MattPD's review comments PRRT_kwDOBITxeM6etGbn and
PRRT_kwDOBITxeM6etGbr.
---
 flang/lib/Lower/ConvertVariable.cpp           | 82 ++++++++++++------
 .../Lower/finit-local-ppc-vector-llvm.f90     | 72 ++++++++++++----
 flang/test/Lower/finit-local-unsigned.f90     |  2 +-
 .../test/Lower/finit-local-volatile-llvm.f90  | 65 +++++++++++++++
 .../Lower/finit-local-volatile-real10.f90     | 30 +++++++
 flang/test/Lower/finit-local-volatile.f90     | 83 +++++++++++--------
 6 files changed, 258 insertions(+), 76 deletions(-)
 create mode 100644 flang/test/Lower/finit-local-volatile-llvm.f90
 create mode 100644 flang/test/Lower/finit-local-volatile-real10.f90

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index be8d555bbab3c..fb642edb96da9 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1280,9 +1280,13 @@ getSafeRepackAttrs(Fortran::lower::AbstractConverter &converter) {
 
 /// Returns true if \p derived or any of its components (recursively) is a
 /// PowerPC vector type. fir::VectorType does not implement
-/// DataLayoutTypeInterface, so any record containing a vector component would
-/// crash record-size calculation. Excluding such records at eligibility time
-/// avoids the crash.
+/// DataLayoutTypeInterface. Two pre-fix failure modes existed:
+///   - Direct vector local: silently initialized to zero regardless of mode
+///     (historical behavior); at the current head genByteSplatInit would
+///     hit llvm_unreachable instead.
+///   - Derived-type local with a vector component: crashed in record-size
+///     calculation when DataLayoutTypeInterface was queried.
+/// Excluding both cases at eligibility time avoids both failure modes.
 static bool
 containsVectorComponent(const Fortran::semantics::DerivedTypeSpec &derived) {
   if (derived.IsVectorType())
@@ -1352,10 +1356,13 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
     return false;
   // PowerPC vector types (vector(real(4)) etc.) lower to fir::VectorType
   // which does not implement DataLayoutTypeInterface at the HLFIR level.
-  // Without this guard, a direct vector local would silently fall back to
-  // zero initialization regardless of the requested mode, and a derived-type
-  // local whose component is a vector type would crash record-size
-  // calculation.  Exclude both cases here by walking components recursively.
+  // Without this guard:
+  //   - A direct vector local would hit llvm_unreachable in genByteSplatInit
+  //     (historically it silently fell back to zero before that assert was
+  //     added).
+  //   - A derived-type local with a vector component would crash in
+  //     record-size calculation when DataLayoutTypeInterface was queried.
+  // Exclude both cases by walking components recursively.
   if (const Fortran::semantics::DeclTypeSpec *declTy = sym.GetType())
     if (const Fortran::semantics::DerivedTypeSpec *derived =
             declTy->AsDerived())
@@ -1450,6 +1457,9 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
 /// Fixed-length CHARACTER in hex mode: byte-loop over every byte of storage.
 /// LOGICAL stores via a bitcasted integer address to preserve the raw bit
 /// pattern past fir.convert normalization.
+/// All byte-view and coordinate types carry the source address volatility so
+/// that final stores are emitted as "store volatile" when the variable is
+/// volatile.
 static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
                               mlir::Type ty, mlir::Value addr,
                               Fortran::lower::InitLocalKind mode,
@@ -1480,8 +1490,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
             fir::CharacterType::getSingleton(builder.getContext(), 1);
         mlir::Type byteSeqTy = fir::SequenceType::get(
             {fir::SequenceType::getUnknownExtent()}, byteTy);
+        bool addrVolatile1 = fir::isa_volatile_type(addr.getType());
         mlir::Value byteBase = builder.createConvertWithVolatileCast(
-            loc, builder.getRefType(byteSeqTy), addr);
+            loc, builder.getRefType(byteSeqTy, addrVolatile1), addr);
         mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
         mlir::Value last =
             builder.createIntegerConstant(loc, idxTy, nBytes - 1);
@@ -1492,13 +1503,15 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
         mlir::OpBuilder::InsertionGuard guard(builder);
         builder.setInsertionPointToStart(loop.getBody());
         mlir::Value iv = loop.getInductionVar();
+        bool byteVolatile1 = fir::isa_volatile_type(byteBase.getType());
         mlir::Value byteAddr =
-            fir::CoordinateOp::create(builder, loc, builder.getRefType(byteTy),
+            fir::CoordinateOp::create(builder, loc,
+                                      builder.getRefType(byteTy, byteVolatile1),
                                       byteBase, mlir::ValueRange{iv});
         mlir::Value pat = builder.createIntegerConstant(
             loc, i8Ty, static_cast<int64_t>(hexByte));
-        mlir::Value i8Addr =
-            builder.createConvert(loc, builder.getRefType(i8Ty), byteAddr);
+        mlir::Value i8Addr = builder.createConvertWithVolatileCast(
+            loc, builder.getRefType(i8Ty, byteVolatile1), byteAddr);
         fir::StoreOp::create(builder, loc, pat, i8Addr);
       }
       return;
@@ -1512,8 +1525,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
     mlir::Type i8Ty = builder.getIntegerType(8);
     mlir::Type i8SeqTy =
         fir::SequenceType::get({fir::SequenceType::getUnknownExtent()}, i8Ty);
+    bool addrVolatile2 = fir::isa_volatile_type(addr.getType());
     mlir::Value byteBase = builder.createConvertWithVolatileCast(
-        loc, builder.getRefType(i8SeqTy), addr);
+        loc, builder.getRefType(i8SeqTy, addrVolatile2), addr);
     mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
     mlir::Value last = builder.createIntegerConstant(
         loc, idxTy, static_cast<int64_t>(nBytes) - 1);
@@ -1524,8 +1538,10 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
     mlir::OpBuilder::InsertionGuard guard(builder);
     builder.setInsertionPointToStart(loop.getBody());
     mlir::Value iv = loop.getInductionVar();
+    bool byteVolatile2 = fir::isa_volatile_type(byteBase.getType());
     mlir::Value byteAddr = fir::CoordinateOp::create(
-        builder, loc, builder.getRefType(i8Ty), byteBase, mlir::ValueRange{iv});
+        builder, loc, builder.getRefType(i8Ty, byteVolatile2), byteBase,
+        mlir::ValueRange{iv});
     int64_t fillByte =
         (mode == Fortran::lower::InitLocalKind::Zero) ? 0 : hexByte;
     mlir::Value pat = builder.createIntegerConstant(loc, i8Ty, fillByte);
@@ -1561,8 +1577,11 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
       mlir::isa<fir::LogicalType>(ty)) {
     auto logTy = mlir::cast<fir::LogicalType>(ty);
     unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
-    mlir::Type intRefTy = builder.getRefType(builder.getIntegerType(bits));
-    mlir::Value intAddr = builder.createConvert(loc, intRefTy, addr);
+    bool logVolatile = fir::isa_volatile_type(addr.getType());
+    mlir::Type intRefTy =
+        builder.getRefType(builder.getIntegerType(bits), logVolatile);
+    mlir::Value intAddr =
+        builder.createConvertWithVolatileCast(loc, intRefTy, addr);
     fir::StoreOp::create(builder, loc, val, intAddr);
   } else {
     fir::StoreOp::create(builder, loc, val, addr);
@@ -1573,8 +1592,11 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
 /// Arrays: all modes use a flat fir.do_loop + fir.coordinate_of over a
 /// rank-1 view to avoid both the llvm.mlir.constant crash on non-zero
 /// ArrayAttrs and the quadratic compile time of fir.insert_on_range.
-/// Derived types use a byte-fill loop. PowerPC vector types (direct or as a
-/// derived-type component) are excluded upstream by shouldInitLocal via
+/// Derived types use a byte-fill loop. All byte-view and coordinate types
+/// carry the source address volatility so that stores into volatile variables
+/// are emitted as "store volatile" end-to-end.
+/// PowerPC vector types (direct or as a derived-type component) are excluded
+/// upstream by shouldInitLocal via
 /// containsVectorComponent and never reach this function.
 /// Scalars store directly via genInitLocalStore.
 static void genInitLocal(Fortran::lower::AbstractConverter &converter,
@@ -1634,8 +1656,10 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
             mlir::Type idxTy = builder.getIndexType();
             mlir::Type rank1SeqTy = fir::SequenceType::get(
                 {fir::SequenceType::getUnknownExtent()}, eleTy);
-            mlir::Value rank1Addr = builder.createConvert(
-                loc, builder.getRefType(rank1SeqTy), addr);
+            bool rank1Volatile = fir::isa_volatile_type(addr.getType());
+            mlir::Value rank1Addr =
+                builder.createConvertWithVolatileCast(
+                    loc, builder.getRefType(rank1SeqTy, rank1Volatile), addr);
             mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
             mlir::Value last =
                 builder.createIntegerConstant(loc, idxTy, totalElems - 1);
@@ -1646,9 +1670,10 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
             mlir::OpBuilder::InsertionGuard guard(builder);
             builder.setInsertionPointToStart(loop.getBody());
             mlir::Value iv = loop.getInductionVar();
+            bool elemVolatile = fir::isa_volatile_type(rank1Addr.getType());
             mlir::Value elemAddr = fir::CoordinateOp::create(
-                builder, loc, builder.getRefType(eleTy), rank1Addr,
-                mlir::ValueRange{iv});
+                builder, loc, builder.getRefType(eleTy, elemVolatile),
+                rank1Addr, mlir::ValueRange{iv});
             initAddr(eleTy, elemAddr);
           }
         } else if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty)) {
@@ -1663,8 +1688,9 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
               mlir::Type i8Ty = builder.getIntegerType(8);
               mlir::Type i8SeqTy = fir::SequenceType::get(
                   {fir::SequenceType::getUnknownExtent()}, i8Ty);
+              bool addrVolatile3 = fir::isa_volatile_type(addr.getType());
               mlir::Value byteBase = builder.createConvertWithVolatileCast(
-                  loc, builder.getRefType(i8SeqTy), addr);
+                  loc, builder.getRefType(i8SeqTy, addrVolatile3), addr);
               mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
               mlir::Value last = builder.createIntegerConstant(
                   loc, idxTy, static_cast<int64_t>(byteSize) - 1);
@@ -1675,9 +1701,10 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
               mlir::OpBuilder::InsertionGuard guard(builder);
               builder.setInsertionPointToStart(loop.getBody());
               mlir::Value iv = loop.getInductionVar();
+              bool byteVolatile3 = fir::isa_volatile_type(byteBase.getType());
               mlir::Value byteAddr = fir::CoordinateOp::create(
-                  builder, loc, builder.getRefType(i8Ty), byteBase,
-                  mlir::ValueRange{iv});
+                  builder, loc, builder.getRefType(i8Ty, byteVolatile3),
+                  byteBase, mlir::ValueRange{iv});
               int64_t fillByte =
                   (mode == Fortran::lower::InitLocalKind::Zero) ? 0 : hexByte;
               mlir::Value pat =
@@ -1734,10 +1761,13 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
           fir::CharacterType::getSingleton(builder.getContext(), 1);
       mlir::Type byteSeqTy = fir::SequenceType::get(
           {fir::SequenceType::getUnknownExtent()}, byteTy);
+      bool baseVolatile = fir::isa_volatile_type(base.getType());
       mlir::Value byteBase = builder.createConvertWithVolatileCast(
-          loc, builder.getRefType(byteSeqTy), base);
+          loc, builder.getRefType(byteSeqTy, baseVolatile), base);
+      bool byteVolatile4 = fir::isa_volatile_type(byteBase.getType());
       mlir::Value byteAddr =
-          fir::CoordinateOp::create(builder, loc, builder.getRefType(byteTy),
+          fir::CoordinateOp::create(builder, loc,
+                                    builder.getRefType(byteTy, byteVolatile4),
                                     byteBase, mlir::ValueRange{iv});
       genInitLocalStore(builder, loc, byteTy, byteAddr, mode, hexByte);
       return;
diff --git a/flang/test/Lower/finit-local-ppc-vector-llvm.f90 b/flang/test/Lower/finit-local-ppc-vector-llvm.f90
index 263d5628aea19..ee4654f55dff9 100644
--- a/flang/test/Lower/finit-local-ppc-vector-llvm.f90
+++ b/flang/test/Lower/finit-local-ppc-vector-llvm.f90
@@ -1,18 +1,50 @@
 ! Tests that PowerPC vector locals (fir::VectorType) are excluded from
 ! -finit-local= initialization because fir::VectorType does not implement
 ! DataLayoutTypeInterface at the HLFIR level.
-! Before the fix, a direct vector local silently fell back to zero
-! initialization regardless of the requested mode (not a DataLayout crash).
-! A derived-type local with a vector component would crash record-size
-! calculation; that case is covered by test_derived_with_vec below.
-! The local variable 'x' must not receive any initialization store.
+!
+! History of pre-fix behavior:
+!   - Direct vector local: silently fell back to zero initialization
+!     regardless of the requested mode (i.e. hex mode produced zero, not
+!     the requested byte pattern).
+!   - Derived-type local with a vector component: compilation crashed in
+!     record-size calculation when it encountered the unsupported
+!     fir::VectorType.  The crash occurred before any store was emitted.
+!   At the current head, if a vector type somehow reached genByteSplatInit,
+!   it would hit llvm_unreachable rather than silently produce zero.
 !
 ! REQUIRES: target=powerpc{{.*}}
 !
 ! RUN: %flang_fc1 -emit-hlfir -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
 ! RUN: %flang_fc1 -emit-hlfir -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
 
-! vector(real(4)) -- excluded; must not produce any fir.store for 'x'.
+! ---------------------------------------------------------------------------
+! Positive control: ordinary record (no vector component) IS initialized.
+! This ensures the vector-exclusion checks below are not trivially satisfied
+! by a compiler that never initializes any record.
+! ---------------------------------------------------------------------------
+subroutine test_plain_record(res)
+  type :: tp
+    integer(4) :: a
+    integer(4) :: b
+  end type
+  type(tp) :: x
+  integer :: res
+  res = x%a
+end subroutine
+
+! HEX-LABEL: func.func @_QPtest_plain_record(
+! HEX:        hlfir.declare {{.*}}_QFtest_plain_recordEx
+! HEX:        fir.do_loop
+! HEX:        return
+
+! ZERO-LABEL: func.func @_QPtest_plain_record(
+! ZERO:        hlfir.declare {{.*}}_QFtest_plain_recordEx
+! ZERO:        fir.do_loop
+! ZERO:        return
+
+! ---------------------------------------------------------------------------
+! Direct vector(real(4)) local -- excluded; no initialization store or loop.
+! ---------------------------------------------------------------------------
 subroutine test_vec_real4(res)
   vector(real(4)) :: res
   vector(real(4)) :: x
@@ -21,29 +53,39 @@ subroutine test_vec_real4(res)
 
 ! HEX-LABEL: func.func @_QPtest_vec_real4(
 ! HEX:        hlfir.declare {{.*}}_QFtest_vec_real4Ex
-! HEX-NOT:    fir.store {{.*}} to %{{.*}}#0
+! HEX-NOT:    fir.do_loop
+! HEX-NOT:    fir.store
+! HEX:        return
 
 ! ZERO-LABEL: func.func @_QPtest_vec_real4(
 ! ZERO:        hlfir.declare {{.*}}_QFtest_vec_real4Ex
-! ZERO-NOT:    fir.store {{.*}} to %{{.*}}#0
+! ZERO-NOT:    fir.do_loop
+! ZERO-NOT:    fir.store
+! ZERO:        return
 
-! Derived type with a vector(real(4)) component -- the eligibility check must
-! walk components recursively and exclude this record, just as it excludes a
-! direct vector local.  Before the fix, compilation would crash in record-size
-! calculation when it encountered the unsupported fir::VectorType component.
+! ---------------------------------------------------------------------------
+! Derived type with a vector(real(4)) component -- excluded recursively.
+! The containsVectorComponent helper walks component scopes so that record-
+! size calculation is never attempted for this type.
+! ---------------------------------------------------------------------------
 subroutine test_derived_with_vec(res)
   type :: tv
-    integer(4)    :: i
+    integer(4)      :: i
     vector(real(4)) :: v
   end type
   type(tv) :: x
+  integer :: res
   res = x%i
 end subroutine
 
 ! HEX-LABEL: func.func @_QPtest_derived_with_vec(
 ! HEX:        hlfir.declare {{.*}}_QFtest_derived_with_vecEx
-! HEX-NOT:    fir.store {{.*}} to %{{.*}}#0
+! HEX-NOT:    fir.do_loop
+! HEX-NOT:    fir.store
+! HEX:        return
 
 ! ZERO-LABEL: func.func @_QPtest_derived_with_vec(
 ! ZERO:        hlfir.declare {{.*}}_QFtest_derived_with_vecEx
-! ZERO-NOT:    fir.store {{.*}} to %{{.*}}#0
+! ZERO-NOT:    fir.do_loop
+! ZERO-NOT:    fir.store
+! ZERO:        return
diff --git a/flang/test/Lower/finit-local-unsigned.f90 b/flang/test/Lower/finit-local-unsigned.f90
index cfc3d8ee91b4d..aff940c259122 100644
--- a/flang/test/Lower/finit-local-unsigned.f90
+++ b/flang/test/Lower/finit-local-unsigned.f90
@@ -19,7 +19,7 @@ subroutine test_unsigned(res)
 
 ! HEX-LABEL:  func.func @_QPtest_unsigned(
 ! HEX:         %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_unsignedEx
-! HEX:         %[[C:.*]] = arith.constant {{.*}} : i32
+! HEX:         %[[C:.*]] = arith.constant -1431655766 : i32
 ! HEX:         %[[U:.*]] = fir.convert %[[C]] : (i32) -> ui32
 ! HEX:         fir.store %[[U]] to %[[X]]#0 : !fir.ref<ui32>
 
diff --git a/flang/test/Lower/finit-local-volatile-llvm.f90 b/flang/test/Lower/finit-local-volatile-llvm.f90
new file mode 100644
index 0000000000000..f51f86436659e
--- /dev/null
+++ b/flang/test/Lower/finit-local-volatile-llvm.f90
@@ -0,0 +1,65 @@
+! LLVM IR-level regression test for volatile byte-fill stores.
+! Verifies that volatile locals produce "store volatile" in LLVM IR for
+! every byte written by the -finit-local= byte-fill paths.
+!
+! At -O2 a non-volatile fill of a volatile variable can be eliminated
+! entirely.  These checks confirm the stores carry the volatile flag.
+!
+! REQUIRES: x86-registered-target
+!
+! RUN: %flang_fc1 -emit-llvm -triple x86_64-unknown-linux-gnu \
+! RUN:     -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
+! RUN: %flang_fc1 -emit-llvm -triple x86_64-unknown-linux-gnu \
+! RUN:     -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+
+! ---------------------------------------------------------------------------
+! Volatile derived-type local -- record byte-fill loop.
+! Every store into the record's bytes must be "store volatile i8".
+! ---------------------------------------------------------------------------
+subroutine test_volatile_derived(oa)
+  type :: t
+    integer(4) :: a
+    integer(1) :: b
+  end type
+  type(t), volatile :: v
+  integer :: oa
+  v%a = 1
+  oa = v%a
+end subroutine
+
+! HEX-LABEL: define {{.*}}@_QPtest_volatile_derived
+! HEX:        store volatile i8
+
+! ZERO-LABEL: define {{.*}}@_QPtest_volatile_derived
+! ZERO:        store volatile i8
+
+! ---------------------------------------------------------------------------
+! Volatile real(10) local -- allocation-gap byte-fill loop.
+! real(10) = x86_fp80: 10-byte store size, 16-byte allocation.
+! ---------------------------------------------------------------------------
+subroutine test_volatile_real10(res)
+  real(10), volatile :: x
+  real(10) :: res
+  res = x
+end subroutine
+
+! HEX-LABEL: define {{.*}}@_QPtest_volatile_real10
+! HEX:        store volatile i8
+
+! ZERO-LABEL: define {{.*}}@_QPtest_volatile_real10
+! ZERO:        store volatile i8
+
+! ---------------------------------------------------------------------------
+! Volatile integer array local -- rank-1 array-view byte-fill loop.
+! ---------------------------------------------------------------------------
+subroutine test_volatile_array(res)
+  integer(4), volatile :: x(4)
+  integer :: res
+  res = x(1)
+end subroutine
+
+! HEX-LABEL: define {{.*}}@_QPtest_volatile_array
+! HEX:        store volatile i32
+
+! ZERO-LABEL: define {{.*}}@_QPtest_volatile_array
+! ZERO:        store volatile i32
diff --git a/flang/test/Lower/finit-local-volatile-real10.f90 b/flang/test/Lower/finit-local-volatile-real10.f90
new file mode 100644
index 0000000000000..c7a26c0da0d77
--- /dev/null
+++ b/flang/test/Lower/finit-local-volatile-real10.f90
@@ -0,0 +1,30 @@
+! Volatile real(10) local -- allocation-gap byte-fill loop path.
+! Pinned to x86_64 triple so the test uses x86_fp80 (10-byte store,
+! 16-byte allocation) regardless of host architecture.
+!
+! REQUIRES: x86-registered-target
+!
+! RUN: %flang_fc1 -emit-hlfir -triple x86_64-unknown-linux-gnu \
+! RUN:     -mmlir --strict-fir-volatile-verifier \
+! RUN:     -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+! RUN: %flang_fc1 -emit-hlfir -triple x86_64-unknown-linux-gnu \
+! RUN:     -mmlir --strict-fir-volatile-verifier \
+! RUN:     -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX  %s
+
+subroutine test_volatile_real10(res)
+  real(10), volatile :: x
+  real(10) :: res
+  res = x
+end subroutine
+
+! ZERO-LABEL: func.func @_QPtest_volatile_real10(
+! ZERO:        %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_real10Ex
+! ZERO:        fir.convert %[[X]]#0 : (!fir.ref<f80, volatile>) -> !fir.ref<!fir.array<?xi8>, volatile>
+! ZERO:        fir.do_loop
+! ZERO:        fir.store {{.*}} : !fir.ref<i8, volatile>
+
+! HEX-LABEL:  func.func @_QPtest_volatile_real10(
+! HEX:         %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_real10Ex
+! HEX:         fir.convert %[[X]]#0 : (!fir.ref<f80, volatile>) -> !fir.ref<!fir.array<?xi8>, volatile>
+! HEX:         fir.do_loop
+! HEX:         fir.store {{.*}} : !fir.ref<i8, volatile>
diff --git a/flang/test/Lower/finit-local-volatile.f90 b/flang/test/Lower/finit-local-volatile.f90
index 57b5157cb9dbd..ad2f4f9d3808c 100644
--- a/flang/test/Lower/finit-local-volatile.f90
+++ b/flang/test/Lower/finit-local-volatile.f90
@@ -1,18 +1,22 @@
 ! Tests that -finit-local= byte-fill loops correctly handle volatile locals.
-! Each byte-view conversion must go through fir.volatile_cast before the
-! fir.convert that reinterprets the address as a byte sequence, so that the
-! strict FIR volatile verifier does not see a "mismatched volatility" error.
+! The byte-seq ref type and all CoordinateOp result types must carry the
+! source volatility flag so that final stores are emitted as "store volatile".
+! LLVM IR-level volatile store checks are in finit-local-volatile-llvm.f90.
 !
-! Before the fix both zero and hex modes emitted a plain fir.convert from
-! a volatile ref directly to a non-volatile byte-sequence ref, which the
-! strict verifier rejects with "mismatched volatility".
+! When source and target types already have matching volatility,
+! createConvertWithVolatileCast emits a plain fir.convert (no fir.volatile_cast
+! needed).  The strict FIR volatile verifier accepts this because both sides
+! carry the volatile flag.  Before the fix, the target type was non-volatile
+! so the verifier rejected the convert with "mismatched volatility".
 !
-! Two shapes are covered:
-!   1. volatile derived-type local  -- exercises the record byte-fill loop
-!      in initAddr (fir.volatile_cast before the i8-array view convert).
-!   2. volatile real(10) local      -- exercises the allocation-gap byte-fill
-!      loop in genInitLocalStore (same fix, different site).
-!      Requires x86 so that real(10) = x86_fp80 with a 6-byte padding gap.
+! Four paths are covered:
+!   1. volatile derived-type local  -- record byte-fill loop in initAddr.
+!   2. volatile real(10) local      -- allocation-gap byte-fill loop in
+!      genInitLocalStore (requires x86; see finit-local-volatile-real10.f90).
+!   3. volatile logical local (hex) -- LOGICAL bitcast address convert in
+!      genInitLocalStore must preserve volatility.
+!   4. volatile integer array local -- rank-1 array-view convert in initAddr
+!      must preserve volatility (both zero and hex modes).
 !
 ! RUN: %flang_fc1 -emit-hlfir -mmlir --strict-fir-volatile-verifier \
 ! RUN:     -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
@@ -35,38 +39,49 @@ subroutine test_volatile_derived(oa)
 
 ! ZERO-LABEL: func.func @_QPtest_volatile_derived(
 ! ZERO:        %[[V:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_derivedEv
-! ZERO:        %[[NV:.*]] = fir.volatile_cast %[[V]]#0 : (!fir.ref<{{.*}}, volatile>) -> !fir.ref<{{.*}}>
-! ZERO:        fir.convert %[[NV]] : (!fir.ref<{{.*}}>) -> !fir.ref<!fir.array<?xi8>>
+! ZERO:        fir.convert %[[V]]#0 : (!fir.ref<{{.*}}, volatile>) -> !fir.ref<!fir.array<?xi8>, volatile>
 ! ZERO:        fir.do_loop
-! ZERO:        fir.store {{.*}} : !fir.ref<i8>
+! ZERO:        fir.store {{.*}} : !fir.ref<i8, volatile>
 
 ! HEX-LABEL:  func.func @_QPtest_volatile_derived(
 ! HEX:         %[[V:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_derivedEv
-! HEX:         %[[NV:.*]] = fir.volatile_cast %[[V]]#0 : (!fir.ref<{{.*}}, volatile>) -> !fir.ref<{{.*}}>
-! HEX:         fir.convert %[[NV]] : (!fir.ref<{{.*}}>) -> !fir.ref<!fir.array<?xi8>>
+! HEX:         fir.convert %[[V]]#0 : (!fir.ref<{{.*}}, volatile>) -> !fir.ref<!fir.array<?xi8>, volatile>
 ! HEX:         fir.do_loop
-! HEX:         fir.store {{.*}} : !fir.ref<i8>
+! HEX:         fir.store {{.*}} : !fir.ref<i8, volatile>
 
 ! ---------------------------------------------------------------------------
-! Volatile real(10) local -- allocation-gap byte-fill loop path (x86 only).
+! Volatile logical local (hex mode) -- LOGICAL bitcast path.
+! The bitcast convert preserves volatility: !fir.ref<i32, volatile>.
 ! ---------------------------------------------------------------------------
-! REQUIRES: x86-registered-target
-subroutine test_volatile_real10(res)
-  real(10), volatile :: x
-  real(10) :: res
-  res = x
+subroutine test_volatile_logical(res)
+  logical(4), volatile :: l
+  integer :: res
+  if (l) res = 1
 end subroutine
 
-! ZERO-LABEL: func.func @_QPtest_volatile_real10(
-! ZERO:        %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_real10Ex
-! ZERO:        %[[NV:.*]] = fir.volatile_cast %[[X]]#0 : (!fir.ref<f80, volatile>) -> !fir.ref<f80>
-! ZERO:        fir.convert %[[NV]] : (!fir.ref<f80>) -> !fir.ref<!fir.array<?xi8>>
+! HEX-LABEL: func.func @_QPtest_volatile_logical(
+! HEX:        %[[L:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_logicalEl
+! HEX:        fir.convert %[[L]]#0 : (!fir.ref<!fir.logical<4>, volatile>) -> !fir.ref<i32, volatile>
+! HEX:        fir.store {{.*}} : !fir.ref<i32, volatile>
+
+! ---------------------------------------------------------------------------
+! Volatile integer array local -- rank-1 array-view path.
+! The rank-1 view convert preserves volatility end-to-end.
+! ---------------------------------------------------------------------------
+subroutine test_volatile_array(res)
+  integer(4), volatile :: x(4)
+  integer :: res
+  res = x(1)
+end subroutine
+
+! ZERO-LABEL: func.func @_QPtest_volatile_array(
+! ZERO:        %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_arrayEx
+! ZERO:        fir.convert %[[X]]#0 : (!fir.ref<!fir.array<4xi32>, volatile>) -> !fir.ref<!fir.array<?xi32>, volatile>
 ! ZERO:        fir.do_loop
-! ZERO:        fir.store {{.*}} : !fir.ref<i8>
+! ZERO:        fir.store {{.*}} : !fir.ref<i32, volatile>
 
-! HEX-LABEL:  func.func @_QPtest_volatile_real10(
-! HEX:         %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_real10Ex
-! HEX:         %[[NV:.*]] = fir.volatile_cast %[[X]]#0 : (!fir.ref<f80, volatile>) -> !fir.ref<f80>
-! HEX:         fir.convert %[[NV]] : (!fir.ref<f80>) -> !fir.ref<!fir.array<?xi8>>
+! HEX-LABEL:  func.func @_QPtest_volatile_array(
+! HEX:         %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_arrayEx
+! HEX:         fir.convert %[[X]]#0 : (!fir.ref<!fir.array<4xi32>, volatile>) -> !fir.ref<!fir.array<?xi32>, volatile>
 ! HEX:         fir.do_loop
-! HEX:         fir.store {{.*}} : !fir.ref<i8>
+! HEX:         fir.store {{.*}} : !fir.ref<i32, volatile>

>From 083b331ab4b3feb8cb7c2bad74b687951519007e Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Thu, 3 Sep 2026 08:26:42 -0400
Subject: [PATCH 16/23] [flang] Fix -finit-local= CHARACTER byte-fill stride
 and improve tests

Use i8/fir.seq<i8> instead of fir.char<1>/fir.seq<fir.char<1>> as the
view element in both CHARACTER byte-fill loops. Under --kind-mapping=a1:16,
fir.char<1> resolves to i16 through the LLVM type converter, so each
fir.coordinate_of step strided 2 bytes instead of 1. i8 is invariant.
The byte count (nUnits/rtLen * kindBytes) is unchanged.

Guard genByteSplatInit against sub-byte LOGICAL kind mappings
(e.g. --kind-mapping=l4:1): emit a TODO fatal error instead of hitting
the APInt::getSplat assertion.

Fix finit-local-volatile-llvm.f90 CHECK-LABEL directives to use the
portable define {{.*}}@{{.*}}name{{.*}}( form; add volatile fixed-length
and runtime-length CHARACTER cases. Update finit-local.f90 FIR-level
checks for the new i8 coordinate_of signatures. Add
finit-local-kind-mapping-subbyte.f90 for the sub-byte LOGICAL diagnostic.
Add positive controls and tighten CHECK-NOT to arith.constant -1431655766
in finit-local-exclusions.f90.
---
 flang/lib/Lower/ConvertVariable.cpp           | 64 +++++++++----------
 flang/test/Lower/finit-local-exclusions.f90   | 17 ++++-
 .../finit-local-kind-mapping-subbyte.f90      | 18 ++++++
 .../test/Lower/finit-local-volatile-llvm.f90  | 51 +++++++++++++--
 flang/test/Lower/finit-local.f90              | 26 ++++----
 5 files changed, 121 insertions(+), 55 deletions(-)
 create mode 100644 flang/test/Lower/finit-local-kind-mapping-subbyte.f90

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index fb642edb96da9..8aadf97a91603 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1440,8 +1440,13 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
   // width matches the actual allocation size.
   // The caller stores it via a bitcasted address to preserve the bit pattern
   // (fir.convert from integer to !fir.logical normalizes nonzero -> true).
+  // A sub-byte mapping (e.g. --kind-mapping=l4:1) is not supported: the
+  // APInt::getSplat precondition requires the destination width >= 8.
   if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
-    return makeIntCst(builder.getKindMap().getLogicalBitsize(logTy.getFKind()));
+    unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
+    if (bits < 8)
+      TODO(loc, "-finit-local= with a sub-byte LOGICAL kind mapping");
+    return makeIntCst(bits);
   }
   // All types that pass shouldInitLocal and reach genInitLocalStore are
   // handled explicitly above (integer, float, complex, logical) or are
@@ -1474,10 +1479,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
     if (mode == Fortran::lower::InitLocalKind::Hex) {
       // Loop over every byte of the character storage. For kind=1 each
       // code unit is one byte; for kind=2/4 (UTF-16/32) each code unit is
-      // kind bytes wide. We use a kind=1 singleton as the view element so
-      // fir.coordinate_of advances exactly one byte per step, and iterate
-      // nUnits * kindBytes times to cover all bytes.  Use KindMapping to
-      // get the true byte width under any --kind-mapping override.
+      // kind bytes wide. We iterate nUnits * kindBytes times to cover all
+      // bytes.  Use KindMapping to get the true byte width under any
+      // --kind-mapping override.
       int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
       int64_t kindBytes =
           builder.getKindMap().getCharacterBitsize(charTy.getFKind()) / 8;
@@ -1485,14 +1489,13 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
       if (nBytes > 0) {
         mlir::Type idxTy = builder.getIndexType();
         mlir::Type i8Ty = builder.getIntegerType(8);
-        // Use a kind=1 singleton so fir.coordinate_of strides by 1 byte.
-        fir::CharacterType byteTy =
-            fir::CharacterType::getSingleton(builder.getContext(), 1);
-        mlir::Type byteSeqTy = fir::SequenceType::get(
-            {fir::SequenceType::getUnknownExtent()}, byteTy);
+        // Use an i8 sequence so fir.coordinate_of strides by exactly 1 byte,
+        // regardless of any --kind-mapping override for character kind 1.
+        mlir::Type i8SeqTy = fir::SequenceType::get(
+            {fir::SequenceType::getUnknownExtent()}, i8Ty);
         bool addrVolatile1 = fir::isa_volatile_type(addr.getType());
         mlir::Value byteBase = builder.createConvertWithVolatileCast(
-            loc, builder.getRefType(byteSeqTy, addrVolatile1), addr);
+            loc, builder.getRefType(i8SeqTy, addrVolatile1), addr);
         mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
         mlir::Value last =
             builder.createIntegerConstant(loc, idxTy, nBytes - 1);
@@ -1504,15 +1507,12 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
         builder.setInsertionPointToStart(loop.getBody());
         mlir::Value iv = loop.getInductionVar();
         bool byteVolatile1 = fir::isa_volatile_type(byteBase.getType());
-        mlir::Value byteAddr =
-            fir::CoordinateOp::create(builder, loc,
-                                      builder.getRefType(byteTy, byteVolatile1),
-                                      byteBase, mlir::ValueRange{iv});
+        mlir::Value byteAddr = fir::CoordinateOp::create(
+            builder, loc, builder.getRefType(i8Ty, byteVolatile1), byteBase,
+            mlir::ValueRange{iv});
         mlir::Value pat = builder.createIntegerConstant(
             loc, i8Ty, static_cast<int64_t>(hexByte));
-        mlir::Value i8Addr = builder.createConvertWithVolatileCast(
-            loc, builder.getRefType(i8Ty, byteVolatile1), byteAddr);
-        fir::StoreOp::create(builder, loc, pat, i8Addr);
+        fir::StoreOp::create(builder, loc, pat, byteAddr);
       }
       return;
     }
@@ -1657,9 +1657,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
             mlir::Type rank1SeqTy = fir::SequenceType::get(
                 {fir::SequenceType::getUnknownExtent()}, eleTy);
             bool rank1Volatile = fir::isa_volatile_type(addr.getType());
-            mlir::Value rank1Addr =
-                builder.createConvertWithVolatileCast(
-                    loc, builder.getRefType(rank1SeqTy, rank1Volatile), addr);
+            mlir::Value rank1Addr = builder.createConvertWithVolatileCast(
+                loc, builder.getRefType(rank1SeqTy, rank1Volatile), addr);
             mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
             mlir::Value last =
                 builder.createIntegerConstant(loc, idxTy, totalElems - 1);
@@ -1741,7 +1740,6 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       // For kind>1 (UTF-16/32) the runtime length is in code units; multiply
       // by the kind byte width to get the total byte count.  Use KindMapping
       // so a --kind-mapping override is respected.
-      // Use a kind=1 singleton so fir.coordinate_of advances 1 byte per step.
       int64_t kindBytes =
           builder.getKindMap().getCharacterBitsize(charTy.getFKind()) / 8;
       if (kindBytes > 1) {
@@ -1757,19 +1755,21 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       mlir::OpBuilder::InsertionGuard guard(builder);
       builder.setInsertionPointToStart(loop.getBody());
       mlir::Value iv = loop.getInductionVar();
-      fir::CharacterType byteTy =
-          fir::CharacterType::getSingleton(builder.getContext(), 1);
-      mlir::Type byteSeqTy = fir::SequenceType::get(
-          {fir::SequenceType::getUnknownExtent()}, byteTy);
+      // Use an i8 sequence so fir.coordinate_of strides by exactly 1 byte,
+      // regardless of any --kind-mapping override for character kind 1.
+      mlir::Type i8Ty = builder.getIntegerType(8);
+      mlir::Type i8SeqTy =
+          fir::SequenceType::get({fir::SequenceType::getUnknownExtent()}, i8Ty);
       bool baseVolatile = fir::isa_volatile_type(base.getType());
       mlir::Value byteBase = builder.createConvertWithVolatileCast(
-          loc, builder.getRefType(byteSeqTy, baseVolatile), base);
+          loc, builder.getRefType(i8SeqTy, baseVolatile), base);
       bool byteVolatile4 = fir::isa_volatile_type(byteBase.getType());
-      mlir::Value byteAddr =
-          fir::CoordinateOp::create(builder, loc,
-                                    builder.getRefType(byteTy, byteVolatile4),
-                                    byteBase, mlir::ValueRange{iv});
-      genInitLocalStore(builder, loc, byteTy, byteAddr, mode, hexByte);
+      mlir::Value byteAddr = fir::CoordinateOp::create(
+          builder, loc, builder.getRefType(i8Ty, byteVolatile4), byteBase,
+          mlir::ValueRange{iv});
+      mlir::Value pat = builder.createIntegerConstant(
+          loc, i8Ty, static_cast<int64_t>(hexByte));
+      fir::StoreOp::create(builder, loc, pat, byteAddr);
       return;
     }
   }
diff --git a/flang/test/Lower/finit-local-exclusions.f90 b/flang/test/Lower/finit-local-exclusions.f90
index faa01f0023ad6..2ac6f279fb2c1 100644
--- a/flang/test/Lower/finit-local-exclusions.f90
+++ b/flang/test/Lower/finit-local-exclusions.f90
@@ -51,12 +51,16 @@ program test_main_prog
 ! ---------------------------------------------------------------------------
 subroutine test_runtime_array(res, n)
   integer, intent(in) :: n
+  integer :: ctrl
   integer :: x(n)
   integer :: res
-  res = x(1)
+  res = x(1) + ctrl
 end subroutine
 ! CHECK-LABEL: func.func @_QPtest_runtime_array
+! CHECK:       hlfir.declare {{.*}} "_QFtest_runtime_arrayEctrl"
+! CHECK:       arith.constant -1431655766 : i32
 ! CHECK:       hlfir.declare {{.*}} "_QFtest_runtime_arrayEx"
+! CHECK-NOT:   arith.constant -1431655766 : i32
 ! CHECK-NOT:   fir.do_loop
 ! CHECK:       return
 
@@ -65,12 +69,17 @@ subroutine test_runtime_array(res, n)
 !    Static CHARACTER scalars are initialized; arrays are silently skipped.
 ! ---------------------------------------------------------------------------
 subroutine test_char_array(res)
+  integer :: ctrl
   character(4) :: x(3)
   character(4) :: res
   res = x(1)
+  ctrl = 0
 end subroutine
 ! CHECK-LABEL: func.func @_QPtest_char_array
+! CHECK:       hlfir.declare {{.*}} "_QFtest_char_arrayEctrl"
+! CHECK:       arith.constant -1431655766 : i32
 ! CHECK:       hlfir.declare {{.*}} "_QFtest_char_arrayEx"
+! CHECK-NOT:   arith.constant -1431655766 : i32
 ! CHECK-NOT:   fir.do_loop
 ! CHECK:       return
 
@@ -82,11 +91,15 @@ subroutine test_pointer_comp(res)
   type t
     integer, pointer :: p
   end type
+  integer :: ctrl
   type(t) :: x
   integer :: res
-  res = 0
+  res = ctrl
 end subroutine
 ! CHECK-LABEL: func.func @_QPtest_pointer_comp
+! CHECK:       hlfir.declare {{.*}} "_QFtest_pointer_compEctrl"
+! CHECK:       arith.constant -1431655766 : i32
 ! CHECK:       hlfir.declare {{.*}} "_QFtest_pointer_compEx"
+! CHECK-NOT:   arith.constant -1431655766 : i32
 ! CHECK-NOT:   fir.do_loop
 ! CHECK:       return
diff --git a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90 b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
new file mode 100644
index 0000000000000..24291054f4c4d
--- /dev/null
+++ b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
@@ -0,0 +1,18 @@
+! Tests that -finit-local= emits a controlled diagnostic rather than crashing
+! when a kind mapping reduces a LOGICAL type to sub-byte storage.
+!
+! Reproducer: --kind-mapping=l4:1 maps LOGICAL(4) to 1 bit.  Before this fix,
+! APInt::getSplat(1, APInt(8, 0xAA)) asserted because the destination width (1)
+! is less than the source width (8).  The fix guards this path with a TODO
+! diagnostic before reaching getSplat.
+!
+! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=l4:1 -finit-local=0xAA %s -o - 2>&1 | \
+! RUN:     FileCheck %s
+
+! CHECK: not yet implemented: -finit-local= with a sub-byte LOGICAL kind mapping
+
+subroutine test_logical4_subbyte(res)
+  logical(kind=4) :: l
+  integer :: res
+  if (l) res = 1
+end subroutine
diff --git a/flang/test/Lower/finit-local-volatile-llvm.f90 b/flang/test/Lower/finit-local-volatile-llvm.f90
index f51f86436659e..6fb2ce33685be 100644
--- a/flang/test/Lower/finit-local-volatile-llvm.f90
+++ b/flang/test/Lower/finit-local-volatile-llvm.f90
@@ -1,6 +1,8 @@
 ! LLVM IR-level regression test for volatile byte-fill stores.
 ! Verifies that volatile locals produce "store volatile" in LLVM IR for
-! every byte written by the -finit-local= byte-fill paths.
+! every byte written by the -finit-local= byte-fill paths: derived-type
+! record fill, real(10) allocation-gap fill, integer array element fill,
+! and both the compile-time-length and runtime-length CHARACTER byte loops.
 !
 ! At -O2 a non-volatile fill of a volatile variable can be eliminated
 ! entirely.  These checks confirm the stores carry the volatile flag.
@@ -27,10 +29,10 @@ subroutine test_volatile_derived(oa)
   oa = v%a
 end subroutine
 
-! HEX-LABEL: define {{.*}}@_QPtest_volatile_derived
+! HEX-LABEL: define {{.*}}@{{.*}}test_volatile_derived{{.*}}(
 ! HEX:        store volatile i8
 
-! ZERO-LABEL: define {{.*}}@_QPtest_volatile_derived
+! ZERO-LABEL: define {{.*}}@{{.*}}test_volatile_derived{{.*}}(
 ! ZERO:        store volatile i8
 
 ! ---------------------------------------------------------------------------
@@ -43,10 +45,10 @@ subroutine test_volatile_real10(res)
   res = x
 end subroutine
 
-! HEX-LABEL: define {{.*}}@_QPtest_volatile_real10
+! HEX-LABEL: define {{.*}}@{{.*}}test_volatile_real10{{.*}}(
 ! HEX:        store volatile i8
 
-! ZERO-LABEL: define {{.*}}@_QPtest_volatile_real10
+! ZERO-LABEL: define {{.*}}@{{.*}}test_volatile_real10{{.*}}(
 ! ZERO:        store volatile i8
 
 ! ---------------------------------------------------------------------------
@@ -58,8 +60,43 @@ subroutine test_volatile_array(res)
   res = x(1)
 end subroutine
 
-! HEX-LABEL: define {{.*}}@_QPtest_volatile_array
+! HEX-LABEL: define {{.*}}@{{.*}}test_volatile_array{{.*}}(
 ! HEX:        store volatile i32
 
-! ZERO-LABEL: define {{.*}}@_QPtest_volatile_array
+! ZERO-LABEL: define {{.*}}@{{.*}}test_volatile_array{{.*}}(
 ! ZERO:        store volatile i32
+
+! ---------------------------------------------------------------------------
+! Volatile fixed-length CHARACTER local -- compile-time-length byte-fill loop.
+! hex: loop over nLen bytes -> store volatile i8.
+! zero: fir.zero_bits store -> store volatile i8 0.
+! ---------------------------------------------------------------------------
+subroutine test_volatile_char_fixed(res)
+  character(10), volatile :: x
+  character(10) :: res
+  res = x
+end subroutine
+
+! HEX-LABEL: define {{.*}}@{{.*}}test_volatile_char_fixed{{.*}}(
+! HEX:        store volatile i8
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_volatile_char_fixed{{.*}}(
+! ZERO:        store volatile i8
+
+! ---------------------------------------------------------------------------
+! Volatile runtime-length CHARACTER local -- dynamic-length byte-fill loop.
+! hex: loop over rtLen bytes -> store volatile i8.
+! zero: loop over rtLen bytes -> store volatile i8 0.
+! ---------------------------------------------------------------------------
+subroutine test_volatile_char_runtime(res, n)
+  integer, intent(in) :: n
+  character(n), volatile :: x
+  character(n) :: res
+  res = x
+end subroutine
+
+! HEX-LABEL: define {{.*}}@{{.*}}test_volatile_char_runtime{{.*}}(
+! HEX:        store volatile i8
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_volatile_char_runtime{{.*}}(
+! ZERO:        store volatile i8
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 995bc886f05e6..979ad0bd9dc61 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -215,7 +215,7 @@ subroutine test_logical4(res)
 ! ---------------------------------------------------------------------------
 ! CHARACTER(10) -- fixed-length scalar.
 ! zero: fir.zero_bits over the whole character type.
-! hex: byte-loop over 10 singleton code-units.
+! hex: byte-loop over 10 bytes via an i8 view.
 ! ---------------------------------------------------------------------------
 subroutine test_char10(res)
   character(10) :: res
@@ -230,7 +230,7 @@ subroutine test_char10(res)
 
 ! HEX-LABEL:  func.func @_QPtest_char10
 ! HEX:        fir.do_loop
-! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
 ! HEX:          arith.constant {{.*}} : i8
 ! HEX:          fir.store {{.*}} : !fir.ref<i8>
 
@@ -263,15 +263,14 @@ subroutine test_charN(res, n)
 end subroutine
 ! ZERO-LABEL: func.func @_QPtest_charn
 ! ZERO:       fir.do_loop
-! ZERO:         fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
-! ZERO:         fir.zero_bits !fir.char<1>
-! ZERO:         fir.store {{.*}} : !fir.ref<!fir.char<1>>
+! ZERO:         fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! ZERO:         fir.store {{.*}} : !fir.ref<i8>
 
 
 
 ! HEX-LABEL:  func.func @_QPtest_charn
 ! HEX:        fir.do_loop
-! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
 ! HEX:          arith.constant {{.*}} : i8
 ! HEX:          fir.store {{.*}} : !fir.ref<i8>
 
@@ -475,7 +474,7 @@ subroutine test_equivalence(res)
 
 ! ---------------------------------------------------------------------------
 ! CHARACTER(kind=2, len=3) -- fixed-length, higher kind.
-! hex: byte-loop over 3*2=6 bytes using a kind=1 singleton view.
+! hex: byte-loop over 3*2=6 bytes via an i8 view.
 ! zero: fir.zero_bits over the whole type.
 ! ---------------------------------------------------------------------------
 subroutine test_char2_fixed(res)
@@ -490,13 +489,13 @@ subroutine test_char2_fixed(res)
 ! HEX-LABEL:  func.func @_QPtest_char2_fixed
 ! HEX:        %[[C5:.*]] = arith.constant 5 : index
 ! HEX:        fir.do_loop %{{.*}} = %{{.*}} to %[[C5]] step %{{.*}} {
-! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
 ! HEX:          arith.constant {{.*}} : i8
 ! HEX:          fir.store {{.*}} : !fir.ref<i8>
 
 ! ---------------------------------------------------------------------------
 ! CHARACTER(kind=4, len=2) -- fixed-length, wider kind.
-! hex: byte-loop over 2*4=8 bytes using a kind=1 singleton view.
+! hex: byte-loop over 2*4=8 bytes via an i8 view.
 ! ---------------------------------------------------------------------------
 subroutine test_char4_fixed(res)
   character(kind=4, len=2) :: res
@@ -510,7 +509,7 @@ subroutine test_char4_fixed(res)
 ! HEX-LABEL:  func.func @_QPtest_char4_fixed
 ! HEX:        %[[C7:.*]] = arith.constant 7 : index
 ! HEX:        fir.do_loop %{{.*}} = %{{.*}} to %[[C7]] step %{{.*}} {
-! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
 ! HEX:          arith.constant {{.*}} : i8
 ! HEX:          fir.store {{.*}} : !fir.ref<i8>
 
@@ -529,14 +528,13 @@ subroutine test_char2_runtime(res, n)
 ! ZERO:       %[[C2:.*]] = arith.constant 2 : index
 ! ZERO:       arith.muli {{.*}}, %[[C2]] : index
 ! ZERO:       fir.do_loop
-! ZERO:         fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
-! ZERO:         fir.zero_bits !fir.char<1>
-! ZERO:         fir.store {{.*}} : !fir.ref<!fir.char<1>>
+! ZERO:         fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! ZERO:         fir.store {{.*}} : !fir.ref<i8>
 
 ! HEX-LABEL:  func.func @_QPtest_char2_runtime
 ! HEX:        %[[C2:.*]] = arith.constant 2 : index
 ! HEX:        arith.muli {{.*}}, %[[C2]] : index
 ! HEX:        fir.do_loop
-! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
 ! HEX:          arith.constant {{.*}} : i8
 ! HEX:          fir.store {{.*}} : !fir.ref<i8>

>From 5a00dc08e778512cca772ac22b190993a84a17dc Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Thu, 3 Sep 2026 11:21:42 -0400
Subject: [PATCH 17/23] [flang] Fix finit-local-volatile-llvm.f90: use
 aggregate-store check for fixed-length CHARACTER zero fill

Fixed-length CHARACTER initialization in zero mode emits a single
aggregate store ('store volatile [10 x i8] zeroinitializer'), not a
byte loop.  The previous 'store volatile i8' CHECK line could never
match that store, so the test failed deterministically everywhere it
ran (the file is pinned to -triple x86_64-unknown-linux-gnu and gated
REQUIRES: x86-registered-target, so the failure was not host-dependent).
Update the check to match the actual aggregate-store output.
---
 flang/test/Lower/finit-local-volatile-llvm.f90 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/test/Lower/finit-local-volatile-llvm.f90 b/flang/test/Lower/finit-local-volatile-llvm.f90
index 6fb2ce33685be..7642e111fb260 100644
--- a/flang/test/Lower/finit-local-volatile-llvm.f90
+++ b/flang/test/Lower/finit-local-volatile-llvm.f90
@@ -81,7 +81,7 @@ subroutine test_volatile_char_fixed(res)
 ! HEX:        store volatile i8
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_volatile_char_fixed{{.*}}(
-! ZERO:        store volatile i8
+! ZERO:        store volatile [10 x i8] zeroinitializer
 
 ! ---------------------------------------------------------------------------
 ! Volatile runtime-length CHARACTER local -- dynamic-length byte-fill loop.

>From 724801d7226bd077f17027c05ac641bb09e85417 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Fri, 4 Sep 2026 12:59:06 -0400
Subject: [PATCH 18/23] [flang] Fix -finit-local= CHARACTER sub-byte mapping
 and zero-mode fill byte

- Guard both the fixed-length and runtime-length CHARACTER hex paths
  against sub-byte (e.g. --kind-mapping=a1:1) and non-byte-multiple
  (e.g. --kind-mapping=a1:12) kind mappings.  Previously,
  getCharacterBitsize()/8 silently truncated to zero or gave a wrong
  stride; now a controlled TODO diagnostic is emitted, matching the
  existing LOGICAL guard.  Two regression cases added to
  finit-local-kind-mapping-subbyte.f90.

- Fix the runtime-length CHARACTER path to select fill byte via
  (mode == Zero) ? 0 : hexByte instead of unconditionally using hexByte.
  The bug was latent because InitLocalPattern defaults to zero, but the
  ZERO FileCheck prefix would pass against hex output.  Pin
  arith.constant 0 : i8 in the test_charN and test_char2_runtime ZERO
  checks in finit-local.f90 to catch future regressions.

- Fix the section comment in finit-local-volatile-llvm.f90 for
  test_volatile_char_fixed: zero mode emits one aggregate store
  (store volatile [10 x i8] zeroinitializer), not a byte store.
---
 flang/lib/Lower/ConvertVariable.cpp           | 32 +++++++++----
 .../finit-local-kind-mapping-subbyte.f90      | 46 +++++++++++++++++--
 .../test/Lower/finit-local-volatile-llvm.f90  |  2 +-
 flang/test/Lower/finit-local.f90              |  2 +
 4 files changed, 68 insertions(+), 14 deletions(-)

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 8aadf97a91603..36f1cb400328d 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1440,8 +1440,10 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
   // width matches the actual allocation size.
   // The caller stores it via a bitcasted address to preserve the bit pattern
   // (fir.convert from integer to !fir.logical normalizes nonzero -> true).
-  // A sub-byte mapping (e.g. --kind-mapping=l4:1) is not supported: the
-  // APInt::getSplat precondition requires the destination width >= 8.
+  // Sub-byte and non-byte-multiple LOGICAL mappings (e.g. --kind-mapping=l4:1)
+  // are not supported: APInt::getSplat requires the destination width >= 8.
+  // CHARACTER kind mappings with sub-byte or non-byte-multiple widths are
+  // similarly unsupported and are guarded in genInitLocalStore / genInitLocal.
   if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
     unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
     if (bits < 8)
@@ -1483,8 +1485,16 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
       // bytes.  Use KindMapping to get the true byte width under any
       // --kind-mapping override.
       int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
-      int64_t kindBytes =
-          builder.getKindMap().getCharacterBitsize(charTy.getFKind()) / 8;
+      // A sub-byte or non-byte-multiple CHARACTER kind mapping is not
+      // supported for hex initialization (same restriction as LOGICAL).
+      // getCharacterBitsize / 8 would truncate to zero (sub-byte) or give
+      // a wrong stride (non-byte-multiple), so emit a controlled diagnostic.
+      unsigned charBits =
+          builder.getKindMap().getCharacterBitsize(charTy.getFKind());
+      if (charBits < 8 || charBits % 8 != 0)
+        TODO(loc, "-finit-local= with a sub-byte or non-byte-multiple "
+                  "CHARACTER kind mapping");
+      int64_t kindBytes = charBits / 8;
       int64_t nBytes = nUnits * kindBytes;
       if (nBytes > 0) {
         mlir::Type idxTy = builder.getIndexType();
@@ -1740,8 +1750,13 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       // For kind>1 (UTF-16/32) the runtime length is in code units; multiply
       // by the kind byte width to get the total byte count.  Use KindMapping
       // so a --kind-mapping override is respected.
-      int64_t kindBytes =
-          builder.getKindMap().getCharacterBitsize(charTy.getFKind()) / 8;
+      // Same sub-byte / non-byte-multiple guard as the fixed-length path.
+      unsigned charBitsRt =
+          builder.getKindMap().getCharacterBitsize(charTy.getFKind());
+      if (charBitsRt < 8 || charBitsRt % 8 != 0)
+        TODO(loc, "-finit-local= with a sub-byte or non-byte-multiple "
+                  "CHARACTER kind mapping");
+      int64_t kindBytes = charBitsRt / 8;
       if (kindBytes > 1) {
         mlir::Value kindCst =
             builder.createIntegerConstant(loc, idxTy, kindBytes);
@@ -1767,8 +1782,9 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       mlir::Value byteAddr = fir::CoordinateOp::create(
           builder, loc, builder.getRefType(i8Ty, byteVolatile4), byteBase,
           mlir::ValueRange{iv});
-      mlir::Value pat = builder.createIntegerConstant(
-          loc, i8Ty, static_cast<int64_t>(hexByte));
+      int64_t fillByte =
+          (mode == Fortran::lower::InitLocalKind::Zero) ? 0 : hexByte;
+      mlir::Value pat = builder.createIntegerConstant(loc, i8Ty, fillByte);
       fir::StoreOp::create(builder, loc, pat, byteAddr);
       return;
     }
diff --git a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90 b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
index 24291054f4c4d..3306dea4d6a02 100644
--- a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
+++ b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
@@ -1,10 +1,16 @@
 ! Tests that -finit-local= emits a controlled diagnostic rather than crashing
-! when a kind mapping reduces a LOGICAL type to sub-byte storage.
+! or silently misbehaving when a kind mapping produces a sub-byte or
+! non-byte-multiple width for LOGICAL or CHARACTER types.
 !
-! Reproducer: --kind-mapping=l4:1 maps LOGICAL(4) to 1 bit.  Before this fix,
-! APInt::getSplat(1, APInt(8, 0xAA)) asserted because the destination width (1)
-! is less than the source width (8).  The fix guards this path with a TODO
-! diagnostic before reaching getSplat.
+! LOGICAL reproducer: --kind-mapping=l4:1 maps LOGICAL(4) to 1 bit.  Before
+! this fix, APInt::getSplat(1, APInt(8, 0xAA)) asserted because the destination
+! width (1) is less than the source width (8).
+!
+! CHARACTER reproducer: --kind-mapping=a1:1 maps CHARACTER(1) to 1 bit.
+! getCharacterBitsize / 8 truncates to zero, silently skipping initialization.
+! --kind-mapping=a1:12 produces a 12-bit width; integer division gives
+! kindBytes=1, so only half the bytes would be covered.
+! Both cases now emit a controlled TODO diagnostic.
 !
 ! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=l4:1 -finit-local=0xAA %s -o - 2>&1 | \
 ! RUN:     FileCheck %s
@@ -16,3 +22,33 @@ subroutine test_logical4_subbyte(res)
   integer :: res
   if (l) res = 1
 end subroutine
+
+! CHARACTER kind-mapping sub-byte case: --kind-mapping=a1:1 maps CHARACTER(1)
+! to 1 bit.  getCharacterBitsize(1) / 8 would truncate to zero, silently
+! producing no initialization.  The guard emits a controlled diagnostic instead.
+!
+! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=a1:1 -finit-local=0xAA %s -o - 2>&1 | \
+! RUN:     FileCheck --check-prefix=CHAR-SUBBYTE %s
+
+! CHAR-SUBBYTE: not yet implemented: -finit-local= with a sub-byte or non-byte-multiple CHARACTER kind mapping
+
+subroutine test_char1_subbyte(res)
+  character(kind=1, len=2) :: c
+  integer :: res
+  res = ichar(c(1:1))
+end subroutine
+
+! CHARACTER kind-mapping non-byte-multiple case: --kind-mapping=a1:12 maps
+! CHARACTER(1) to 12 bits.  getCharacterBitsize(1) / 8 = 1, so the loop
+! would run nUnits times with a 1-byte stride, covering only half the storage.
+!
+! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=a1:12 -finit-local=0xAA %s -o - 2>&1 | \
+! RUN:     FileCheck --check-prefix=CHAR-NONBYTE %s
+
+! CHAR-NONBYTE: not yet implemented: -finit-local= with a sub-byte or non-byte-multiple CHARACTER kind mapping
+
+subroutine test_char1_nonbyte(res)
+  character(kind=1, len=2) :: c
+  integer :: res
+  res = ichar(c(1:1))
+end subroutine
diff --git a/flang/test/Lower/finit-local-volatile-llvm.f90 b/flang/test/Lower/finit-local-volatile-llvm.f90
index 7642e111fb260..5c381b66777c6 100644
--- a/flang/test/Lower/finit-local-volatile-llvm.f90
+++ b/flang/test/Lower/finit-local-volatile-llvm.f90
@@ -69,7 +69,7 @@ subroutine test_volatile_array(res)
 ! ---------------------------------------------------------------------------
 ! Volatile fixed-length CHARACTER local -- compile-time-length byte-fill loop.
 ! hex: loop over nLen bytes -> store volatile i8.
-! zero: fir.zero_bits store -> store volatile i8 0.
+! zero: fir.zero_bits aggregate store -> store volatile [10 x i8] zeroinitializer.
 ! ---------------------------------------------------------------------------
 subroutine test_volatile_char_fixed(res)
   character(10), volatile :: x
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 979ad0bd9dc61..2433e0b2a1f62 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -264,6 +264,7 @@ subroutine test_charN(res, n)
 ! ZERO-LABEL: func.func @_QPtest_charn
 ! ZERO:       fir.do_loop
 ! ZERO:         fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! ZERO:         arith.constant 0 : i8
 ! ZERO:         fir.store {{.*}} : !fir.ref<i8>
 
 
@@ -529,6 +530,7 @@ subroutine test_char2_runtime(res, n)
 ! ZERO:       arith.muli {{.*}}, %[[C2]] : index
 ! ZERO:       fir.do_loop
 ! ZERO:         fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! ZERO:         arith.constant 0 : i8
 ! ZERO:         fir.store {{.*}} : !fir.ref<i8>
 
 ! HEX-LABEL:  func.func @_QPtest_char2_runtime

>From 855fa6bc3d4576b65769d6646caab675babad50f Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Sun, 6 Sep 2026 16:43:37 -0400
Subject: [PATCH 19/23] [flang] -finit-local=: use allocation stride for
 CHARACTER, fix LOGICAL non-byte-multiple guard

CHARACTER (fixed-length and runtime-length): replace charBits/8 with the
code unit's allocation stride alignTo(ceil(charBits/8), ABI_alignment(iN)).
charBits/8 is the semantic byte width but LLVM pads iN types to their ABI
alignment, so e.g. a1:24 (i24, 4-byte stride) would leave the last byte of
each code unit uninitialised. The stride formula handles all widths including
sub-byte ones (i1 rounds up to i8, stride 1), so no diagnostic is needed on
the CHARACTER paths.

The fixed-length path is hex-only; zero mode emits a single fir.zero_bits
over the whole object and is correct at any width. The runtime-length path
always strides by kindBytes regardless of mode, so the stride computation
applies to both zero and hex.

LOGICAL: extend the guard from bits < 8 to bits < 8 || bits % 8 != 0.
makeIntCst(12) produces an i12 splat of 0xAA -> 0xAAA, storing as AA 0A
rather than AA AA. Unlike CHARACTER there is no byte loop to fall back on,
so a diagnostic is the correct outcome.

Add finit-local-kind-mapping-rtchar.f90 to give the runtime-length
CHARACTER path its own isolated test; a single compilation cannot cover
both the fixed-length and runtime-length guards since the first TODO aborts
lowering.
---
 flang/lib/Lower/ConvertVariable.cpp           | 74 ++++++++++++------
 .../Lower/finit-local-kind-mapping-rtchar.f90 | 32 ++++++++
 .../finit-local-kind-mapping-subbyte.f90      | 77 ++++++++++++-------
 3 files changed, 133 insertions(+), 50 deletions(-)
 create mode 100644 flang/test/Lower/finit-local-kind-mapping-rtchar.f90

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 36f1cb400328d..09ee34f724a4e 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1440,14 +1440,16 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
   // width matches the actual allocation size.
   // The caller stores it via a bitcasted address to preserve the bit pattern
   // (fir.convert from integer to !fir.logical normalizes nonzero -> true).
-  // Sub-byte and non-byte-multiple LOGICAL mappings (e.g. --kind-mapping=l4:1)
-  // are not supported: APInt::getSplat requires the destination width >= 8.
-  // CHARACTER kind mappings with sub-byte or non-byte-multiple widths are
-  // similarly unsupported and are guarded in genInitLocalStore / genInitLocal.
+  // Sub-byte and non-byte-multiple LOGICAL mappings are not supported:
+  //   - sub-byte (e.g. l4:1): APInt::getSplat requires destination width >= 8.
+  //   - non-byte-multiple (e.g. l4:12): makeIntCst(12) builds an i12 splat of
+  //     0xAA -> 0xAAA, which occupies bytes AA 0A rather than AA AA -- the
+  //     high nibble of the second byte is not filled by the byte pattern.
   if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
     unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
-    if (bits < 8)
-      TODO(loc, "-finit-local= with a sub-byte LOGICAL kind mapping");
+    if (bits < 8 || bits % 8 != 0)
+      TODO(loc, "-finit-local= with a sub-byte or non-byte-multiple "
+                "LOGICAL kind mapping");
     return makeIntCst(bits);
   }
   // All types that pass shouldInitLocal and reach genInitLocalStore are
@@ -1485,16 +1487,29 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
       // bytes.  Use KindMapping to get the true byte width under any
       // --kind-mapping override.
       int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
-      // A sub-byte or non-byte-multiple CHARACTER kind mapping is not
-      // supported for hex initialization (same restriction as LOGICAL).
-      // getCharacterBitsize / 8 would truncate to zero (sub-byte) or give
-      // a wrong stride (non-byte-multiple), so emit a controlled diagnostic.
+      // This block is hex-only; zero mode falls through to fir.zero_bits
+      // below, which fills the whole !fir.char<k,n> object at once and is
+      // correct at any kind width.
+      //
+      // Use the code unit's allocation stride rather than charBits / 8.
+      // charBits / 8 is the semantic byte width, but LLVM pads iN types to
+      // their ABI alignment: e.g. i24 has a 4-byte stride on most targets,
+      // so charBits/8 = 3 would leave the last byte of each code unit
+      // uninitialised.  The stride formula alignTo(ceil(charBits/8), ABI)
+      // handles all widths including sub-byte ones (i1 rounds up to i8,
+      // which has a 1-byte stride) so no diagnostic is needed here.
       unsigned charBits =
           builder.getKindMap().getCharacterBitsize(charTy.getFKind());
-      if (charBits < 8 || charBits % 8 != 0)
-        TODO(loc, "-finit-local= with a sub-byte or non-byte-multiple "
-                  "CHARACTER kind mapping");
-      int64_t kindBytes = charBits / 8;
+      // ceil(charBits/8), clamped to at least 1 byte for sub-byte kinds.
+      unsigned charByteWidth = std::max(1u, (charBits + 7) / 8);
+      const mlir::DataLayout &charDL = builder.getDataLayout();
+      // Round up to the nearest byte-multiple before querying the DataLayout
+      // so that getIntegerType always receives a multiple-of-8 width.
+      mlir::Type charElemTy =
+          builder.getIntegerType(std::max(8u, llvm::alignTo(charBits, 8u)));
+      int64_t kindBytes = static_cast<int64_t>(llvm::alignTo(
+          static_cast<uint64_t>(charByteWidth),
+          static_cast<uint64_t>(charDL.getTypeABIAlignment(charElemTy))));
       int64_t nBytes = nUnits * kindBytes;
       if (nBytes > 0) {
         mlir::Type idxTy = builder.getIndexType();
@@ -1747,16 +1762,31 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       mlir::Value lenIdx = builder.createConvert(loc, idxTy, rtLen);
       mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
       mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
-      // For kind>1 (UTF-16/32) the runtime length is in code units; multiply
-      // by the kind byte width to get the total byte count.  Use KindMapping
-      // so a --kind-mapping override is respected.
-      // Same sub-byte / non-byte-multiple guard as the fixed-length path.
+      // This stride computation applies to all modes (zero and hex), not just
+      // hex.  Unlike the fixed-length path -- where zero mode emits a single
+      // fir.zero_bits over the whole !fir.char<k,n> object -- the runtime-
+      // length path always strides by kindBytes regardless of mode, so the
+      // correct stride is required in both cases.  Gating on hex would
+      // reintroduce a half-storage fill under a non-byte-multiple mapping
+      // (e.g. a1:12) in zero mode.
+      //
+      // Use the DataLayout allocation stride rather than charBits / 8.
+      // charBits / 8 is the semantic byte width; LLVM pads iN types to their
+      // ABI alignment, so e.g. i24 has a 4-byte stride on most targets.
+      // The formula alignTo(ceil(charBits/8), ABI) handles all widths
+      // including sub-byte ones (i1 rounds up to i8, 1-byte stride) so no
+      // diagnostic is needed here either.
+      // e.g. a1:1 (i1->i8, stride 1), a1:12 (i12->i16, stride 2),
+      //      a1:24 (i24->i32, stride 4).
       unsigned charBitsRt =
           builder.getKindMap().getCharacterBitsize(charTy.getFKind());
-      if (charBitsRt < 8 || charBitsRt % 8 != 0)
-        TODO(loc, "-finit-local= with a sub-byte or non-byte-multiple "
-                  "CHARACTER kind mapping");
-      int64_t kindBytes = charBitsRt / 8;
+      unsigned charByteWidthRt = std::max(1u, (charBitsRt + 7) / 8);
+      const mlir::DataLayout &charDLRt = builder.getDataLayout();
+      mlir::Type charElemTyRt =
+          builder.getIntegerType(std::max(8u, llvm::alignTo(charBitsRt, 8u)));
+      int64_t kindBytes = static_cast<int64_t>(llvm::alignTo(
+          static_cast<uint64_t>(charByteWidthRt),
+          static_cast<uint64_t>(charDLRt.getTypeABIAlignment(charElemTyRt))));
       if (kindBytes > 1) {
         mlir::Value kindCst =
             builder.createIntegerConstant(loc, idxTy, kindBytes);
diff --git a/flang/test/Lower/finit-local-kind-mapping-rtchar.f90 b/flang/test/Lower/finit-local-kind-mapping-rtchar.f90
new file mode 100644
index 0000000000000..f51f9f895e008
--- /dev/null
+++ b/flang/test/Lower/finit-local-kind-mapping-rtchar.f90
@@ -0,0 +1,32 @@
+! Tests that the runtime-length CHARACTER stride computation in genInitLocal
+! uses the code unit's allocation stride rather than charBits / 8.  This path
+! is separate from the fixed-length path in genInitLocalStore and requires its
+! own compilation: the first TODO in a run aborts lowering, so a file that
+! contains both a fixed-length and a runtime-length subroutine would stop at
+! the fixed-length one and never reach the runtime-length guard.
+!
+! A runtime-length CHARACTER local is declared as character(kind=1, len=n)
+! where n is a dummy argument, so the length is not known at compile time.
+!
+! RUN: bbc -emit-hlfir --kind-mapping=a1:24 -finit-local=0xAA %s -o - 2>&1 | \
+! RUN:     FileCheck --check-prefix=RT-24BIT %s
+! RUN: bbc -emit-hlfir --kind-mapping=a1:12 -finit-local=0xAA %s -o - 2>&1 | \
+! RUN:     FileCheck --check-prefix=RT-12BIT %s
+! RUN: bbc -emit-hlfir --kind-mapping=a1:1  -finit-local=0xAA %s -o - 2>&1 | \
+! RUN:     FileCheck --check-prefix=RT-1BIT  %s
+
+! RT-24BIT-NOT: not yet implemented
+! RT-24BIT: fir.do_loop
+
+! RT-12BIT-NOT: not yet implemented
+! RT-12BIT: fir.do_loop
+
+! RT-1BIT-NOT: not yet implemented
+! RT-1BIT: fir.do_loop
+
+subroutine test_rt_char(n, res)
+  integer, intent(in) :: n
+  character(kind=1, len=n) :: c
+  integer :: res
+  res = ichar(c(1:1))
+end subroutine
diff --git a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90 b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
index 3306dea4d6a02..ef545a3deb2ef 100644
--- a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
+++ b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
@@ -1,21 +1,26 @@
-! Tests that -finit-local= emits a controlled diagnostic rather than crashing
-! or silently misbehaving when a kind mapping produces a sub-byte or
-! non-byte-multiple width for LOGICAL or CHARACTER types.
+! Tests that -finit-local= handles all CHARACTER kind-mapping widths correctly
+! using the code unit's allocation stride (alignTo(ceil(charBits/8), ABI)),
+! and that LOGICAL sub-byte and non-byte-multiple mappings emit a controlled
+! diagnostic.
 !
-! LOGICAL reproducer: --kind-mapping=l4:1 maps LOGICAL(4) to 1 bit.  Before
-! this fix, APInt::getSplat(1, APInt(8, 0xAA)) asserted because the destination
-! width (1) is less than the source width (8).
+! LOGICAL sub-byte: --kind-mapping=l4:1 maps LOGICAL(4) to 1 bit.
+! APInt::getSplat(1, APInt(8, 0xAA)) asserts because the destination width is
+! less than 8; a TODO is emitted.
 !
-! CHARACTER reproducer: --kind-mapping=a1:1 maps CHARACTER(1) to 1 bit.
-! getCharacterBitsize / 8 truncates to zero, silently skipping initialization.
-! --kind-mapping=a1:12 produces a 12-bit width; integer division gives
-! kindBytes=1, so only half the bytes would be covered.
-! Both cases now emit a controlled TODO diagnostic.
+! LOGICAL non-byte-multiple: --kind-mapping=l4:12 maps LOGICAL(4) to 12 bits.
+! makeIntCst(12) would produce 0xAAA (i12), which stores as AA 0A -- the high
+! nibble of the second byte is not filled.  A TODO is emitted instead.
+!
+! All CHARACTER kind widths are now handled without diagnostics:
+!   a1:1  -- i1 rounds up to i8, stride = 1 byte (fills 1 byte per code unit)
+!   a1:12 -- i12 rounds up to i16, stride = 2 bytes
+!   a1:24 -- i24 rounds up to i32, stride = 4 bytes (was the motivating case:
+!             charBits/8 = 3 missed the last byte; stride = 4 is correct)
 !
 ! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=l4:1 -finit-local=0xAA %s -o - 2>&1 | \
 ! RUN:     FileCheck %s
 
-! CHECK: not yet implemented: -finit-local= with a sub-byte LOGICAL kind mapping
+! CHECK: not yet implemented: -finit-local= with a sub-byte or non-byte-multiple LOGICAL kind mapping
 
 subroutine test_logical4_subbyte(res)
   logical(kind=4) :: l
@@ -23,32 +28,48 @@ subroutine test_logical4_subbyte(res)
   if (l) res = 1
 end subroutine
 
-! CHARACTER kind-mapping sub-byte case: --kind-mapping=a1:1 maps CHARACTER(1)
-! to 1 bit.  getCharacterBitsize(1) / 8 would truncate to zero, silently
-! producing no initialization.  The guard emits a controlled diagnostic instead.
+! LOGICAL non-byte-multiple: --kind-mapping=l4:12 maps LOGICAL(4) to 12 bits.
+! makeIntCst(12) would splat 0xAA into i12 -> 0xAAA, which stores as AA 0A.
+! The guard (bits % 8 != 0) catches this and emits a TODO.
 !
-! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=a1:1 -finit-local=0xAA %s -o - 2>&1 | \
-! RUN:     FileCheck --check-prefix=CHAR-SUBBYTE %s
+! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=l4:12 -finit-local=0xAA %s -o - 2>&1 | \
+! RUN:     FileCheck --check-prefix=LOG-NONBYTE %s
 
-! CHAR-SUBBYTE: not yet implemented: -finit-local= with a sub-byte or non-byte-multiple CHARACTER kind mapping
+! LOG-NONBYTE: not yet implemented: -finit-local= with a sub-byte or non-byte-multiple LOGICAL kind mapping
 
-subroutine test_char1_subbyte(res)
-  character(kind=1, len=2) :: c
+subroutine test_logical4_nonbyte(res)
+  logical(kind=4) :: l
   integer :: res
-  res = ichar(c(1:1))
+  if (l) res = 1
 end subroutine
 
-! CHARACTER kind-mapping non-byte-multiple case: --kind-mapping=a1:12 maps
-! CHARACTER(1) to 12 bits.  getCharacterBitsize(1) / 8 = 1, so the loop
-! would run nUnits times with a 1-byte stride, covering only half the storage.
+! CHARACTER a1:1: i1 rounds up to i8 (stride = 1 byte).  No diagnostic.
 !
-! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=a1:12 -finit-local=0xAA %s -o - 2>&1 | \
-! RUN:     FileCheck --check-prefix=CHAR-NONBYTE %s
+! RUN: bbc -emit-hlfir --kind-mapping=a1:1 -finit-local=0xAA %s -o - 2>&1 | \
+! RUN:     FileCheck --check-prefix=CHAR-1BIT %s
 
-! CHAR-NONBYTE: not yet implemented: -finit-local= with a sub-byte or non-byte-multiple CHARACTER kind mapping
+! CHAR-1BIT-NOT: not yet implemented
+! CHAR-1BIT: fir.do_loop
 
-subroutine test_char1_nonbyte(res)
+subroutine test_char1_subbyte(res)
   character(kind=1, len=2) :: c
   integer :: res
   res = ichar(c(1:1))
 end subroutine
+
+! CHARACTER non-byte-multiple cases (a1:12, a1:24): both runs stop at
+! test_char1_subbyte, which has the same declarations.  That subroutine
+! already covers the % 8 branch of the stride formula (a1:12 gives stride 2,
+! a1:24 gives stride 4).  A positive check that no diagnostic fires and a
+! fir.do_loop is emitted is sufficient.
+!
+! RUN: bbc -emit-hlfir --kind-mapping=a1:12 -finit-local=0xAA %s -o - 2>&1 | \
+! RUN:     FileCheck --check-prefix=CHAR-12BIT %s
+! RUN: bbc -emit-hlfir --kind-mapping=a1:24 -finit-local=0xAA %s -o - 2>&1 | \
+! RUN:     FileCheck --check-prefix=CHAR-24BIT %s
+
+! CHAR-12BIT-NOT: not yet implemented
+! CHAR-12BIT: fir.do_loop
+
+! CHAR-24BIT-NOT: not yet implemented
+! CHAR-24BIT: fir.do_loop

>From c9e7930e3cbdd556cfd25cdfe23af1c374878b27 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Mon, 7 Sep 2026 20:41:07 -0400
Subject: [PATCH 20/23] [flang] -finit-local=: unify allocation-derived
 byte-fill and fix mapping padding

- Unify byte-fill loops into a shared emitByteLoop helper across fixed-length
  CHARACTER, RecordType, and padded REAL/COMPLEX (e.g. x86_fp80) paths.
- Switch fixed-length CHARACTER zero mode to use the allocation-derived byte
  loop so padding under non-default kind mappings (e.g. a1:24) is initialized.
- Add allocSize > storeSize check to diagnose LOGICAL kind mappings with
  allocation padding (e.g. l4:24).
- Query DataLayout alignment using the exact mapped integer type rather than
  a rounded proxy.
- Strengthen kind-mapping tests to check exact loop bounds and multipliers.
---
 flang/lib/Lower/ConvertVariable.cpp           | 209 ++++++------------
 .../Lower/finit-local-kind-mapping-rtchar.f90 |  14 +-
 .../finit-local-kind-mapping-subbyte.f90      |  25 ++-
 .../test/Lower/finit-local-volatile-llvm.f90  |   4 +-
 flang/test/Lower/finit-local.f90              |  20 +-
 5 files changed, 117 insertions(+), 155 deletions(-)

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 09ee34f724a4e..ed6bc4dd89a97 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1440,15 +1440,22 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
   // width matches the actual allocation size.
   // The caller stores it via a bitcasted address to preserve the bit pattern
   // (fir.convert from integer to !fir.logical normalizes nonzero -> true).
-  // Sub-byte and non-byte-multiple LOGICAL mappings are not supported:
+  // Sub-byte, non-byte-multiple, and padded LOGICAL mappings are not supported:
   //   - sub-byte (e.g. l4:1): APInt::getSplat requires destination width >= 8.
   //   - non-byte-multiple (e.g. l4:12): makeIntCst(12) builds an i12 splat of
   //     0xAA -> 0xAAA, which occupies bytes AA 0A rather than AA AA -- the
   //     high nibble of the second byte is not filled by the byte pattern.
+  //   - padded (e.g. l4:24): allocSize (4) > storeSize (3) leaves the trailing
+  //     allocation byte uninitialized.
   if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
     unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
-    if (bits < 8 || bits % 8 != 0)
-      TODO(loc, "-finit-local= with a sub-byte or non-byte-multiple "
+    const mlir::DataLayout &dl = builder.getDataLayout();
+    mlir::Type intTy = builder.getIntegerType(bits);
+    uint64_t storeSize = dl.getTypeSize(intTy);
+    uint64_t allocSize =
+        llvm::alignTo(storeSize, dl.getTypeABIAlignment(intTy));
+    if (bits < 8 || bits % 8 != 0 || allocSize > storeSize)
+      TODO(loc, "-finit-local= with a sub-byte, non-byte-multiple, or padded "
                 "LOGICAL kind mapping");
     return makeIntCst(bits);
   }
@@ -1462,123 +1469,75 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
   llvm_unreachable("genByteSplatInit: unhandled type in hex mode");
 }
 
-/// Emit a store of the -finit-local= pattern for a single scalar address.
-/// Fixed-length CHARACTER in hex mode: byte-loop over every byte of storage.
-/// LOGICAL stores via a bitcasted integer address to preserve the raw bit
-/// pattern past fir.convert normalization.
-/// All byte-view and coordinate types carry the source address volatility so
-/// that final stores are emitted as "store volatile" when the variable is
-/// volatile.
+/// Emit a byte-fill loop over [0, nBytes - 1] at \p addr.
+/// Uses an i8 sequence so fir.coordinate_of strides by exactly 1 byte.
+/// Preserves volatility of \p addr so that stores into volatile variables
+/// are emitted as "store volatile".
+static void emitByteLoop(fir::FirOpBuilder &builder, mlir::Location loc,
+                         mlir::Value addr, uint64_t nBytes,
+                         Fortran::lower::InitLocalKind mode, uint8_t hexByte) {
+  if (nBytes == 0)
+    return;
+  mlir::Type idxTy = builder.getIndexType();
+  mlir::Type i8Ty = builder.getIntegerType(8);
+  mlir::Type i8SeqTy =
+      fir::SequenceType::get({fir::SequenceType::getUnknownExtent()}, i8Ty);
+  bool addrVolatile = fir::isa_volatile_type(addr.getType());
+  mlir::Value byteBase = builder.createConvertWithVolatileCast(
+      loc, builder.getRefType(i8SeqTy, addrVolatile), addr);
+  mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
+  mlir::Value last = builder.createIntegerConstant(
+      loc, idxTy, static_cast<int64_t>(nBytes) - 1);
+  mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+  auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
+                                    /*unordered=*/false,
+                                    /*finalCount=*/false);
+  mlir::OpBuilder::InsertionGuard guard(builder);
+  builder.setInsertionPointToStart(loop.getBody());
+  mlir::Value iv = loop.getInductionVar();
+  bool byteVolatile = fir::isa_volatile_type(byteBase.getType());
+  mlir::Value byteAddr = fir::CoordinateOp::create(
+      builder, loc, builder.getRefType(i8Ty, byteVolatile), byteBase,
+      mlir::ValueRange{iv});
+  int64_t fillByte =
+      (mode == Fortran::lower::InitLocalKind::Zero) ? 0 : hexByte;
+  mlir::Value pat = builder.createIntegerConstant(loc, i8Ty, fillByte);
+  fir::StoreOp::create(builder, loc, pat, byteAddr);
+}
+
+/// Emit initialization for a single scalar address \p addr of type \p ty.
+/// If the allocation size exceeds the typed store size (or for CHARACTER where
+/// kind mappings or string length require byte coverage), an allocation-derived
+/// byte-fill loop is used. Otherwise, a single typed store is emitted.
 static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
                               mlir::Type ty, mlir::Value addr,
                               Fortran::lower::InitLocalKind mode,
                               uint8_t hexByte) {
-  // Fixed-length CHARACTER: for hex mode emit a compile-time byte-loop so
-  // every code-unit gets the requested pattern. Zero falls through to
-  // fir.zero_bits below.
+  // Fixed-length CHARACTER: byte-loop over the full allocation stride.
   if (auto charTy = mlir::dyn_cast<fir::CharacterType>(ty)) {
-    // CHARACTER(0) has zero-length storage -- nothing to initialize.
     if (charTy.getLen() == 0)
       return;
-    if (mode == Fortran::lower::InitLocalKind::Hex) {
-      // Loop over every byte of the character storage. For kind=1 each
-      // code unit is one byte; for kind=2/4 (UTF-16/32) each code unit is
-      // kind bytes wide. We iterate nUnits * kindBytes times to cover all
-      // bytes.  Use KindMapping to get the true byte width under any
-      // --kind-mapping override.
-      int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
-      // This block is hex-only; zero mode falls through to fir.zero_bits
-      // below, which fills the whole !fir.char<k,n> object at once and is
-      // correct at any kind width.
-      //
-      // Use the code unit's allocation stride rather than charBits / 8.
-      // charBits / 8 is the semantic byte width, but LLVM pads iN types to
-      // their ABI alignment: e.g. i24 has a 4-byte stride on most targets,
-      // so charBits/8 = 3 would leave the last byte of each code unit
-      // uninitialised.  The stride formula alignTo(ceil(charBits/8), ABI)
-      // handles all widths including sub-byte ones (i1 rounds up to i8,
-      // which has a 1-byte stride) so no diagnostic is needed here.
-      unsigned charBits =
-          builder.getKindMap().getCharacterBitsize(charTy.getFKind());
-      // ceil(charBits/8), clamped to at least 1 byte for sub-byte kinds.
-      unsigned charByteWidth = std::max(1u, (charBits + 7) / 8);
-      const mlir::DataLayout &charDL = builder.getDataLayout();
-      // Round up to the nearest byte-multiple before querying the DataLayout
-      // so that getIntegerType always receives a multiple-of-8 width.
-      mlir::Type charElemTy =
-          builder.getIntegerType(std::max(8u, llvm::alignTo(charBits, 8u)));
-      int64_t kindBytes = static_cast<int64_t>(llvm::alignTo(
-          static_cast<uint64_t>(charByteWidth),
-          static_cast<uint64_t>(charDL.getTypeABIAlignment(charElemTy))));
-      int64_t nBytes = nUnits * kindBytes;
-      if (nBytes > 0) {
-        mlir::Type idxTy = builder.getIndexType();
-        mlir::Type i8Ty = builder.getIntegerType(8);
-        // Use an i8 sequence so fir.coordinate_of strides by exactly 1 byte,
-        // regardless of any --kind-mapping override for character kind 1.
-        mlir::Type i8SeqTy = fir::SequenceType::get(
-            {fir::SequenceType::getUnknownExtent()}, i8Ty);
-        bool addrVolatile1 = fir::isa_volatile_type(addr.getType());
-        mlir::Value byteBase = builder.createConvertWithVolatileCast(
-            loc, builder.getRefType(i8SeqTy, addrVolatile1), addr);
-        mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
-        mlir::Value last =
-            builder.createIntegerConstant(loc, idxTy, nBytes - 1);
-        mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
-        auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
-                                          /*unordered=*/false,
-                                          /*finalCount=*/false);
-        mlir::OpBuilder::InsertionGuard guard(builder);
-        builder.setInsertionPointToStart(loop.getBody());
-        mlir::Value iv = loop.getInductionVar();
-        bool byteVolatile1 = fir::isa_volatile_type(byteBase.getType());
-        mlir::Value byteAddr = fir::CoordinateOp::create(
-            builder, loc, builder.getRefType(i8Ty, byteVolatile1), byteBase,
-            mlir::ValueRange{iv});
-        mlir::Value pat = builder.createIntegerConstant(
-            loc, i8Ty, static_cast<int64_t>(hexByte));
-        fir::StoreOp::create(builder, loc, pat, byteAddr);
-      }
-      return;
-    }
+    int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
+    unsigned charBits =
+        builder.getKindMap().getCharacterBitsize(charTy.getFKind());
+    unsigned charByteWidth = std::max(1u, (charBits + 7) / 8);
+    const mlir::DataLayout &charDL = builder.getDataLayout();
+    mlir::Type charElemTy = builder.getIntegerType(charBits);
+    int64_t kindBytes = static_cast<int64_t>(llvm::alignTo(
+        static_cast<uint64_t>(charByteWidth),
+        static_cast<uint64_t>(charDL.getTypeABIAlignment(charElemTy))));
+    emitByteLoop(builder, loc, addr, nUnits * kindBytes, mode, hexByte);
+    return;
   }
-  // REAL and COMPLEX: when the allocation size exceeds the store size
-  // (e.g. x86_fp80 stores 10 bytes but occupies 16), fill the full
-  // allocation with a byte loop so padding bytes are also initialized.
-  auto emitByteLoop = [&](uint64_t nBytes) {
-    mlir::Type idxTy = builder.getIndexType();
-    mlir::Type i8Ty = builder.getIntegerType(8);
-    mlir::Type i8SeqTy =
-        fir::SequenceType::get({fir::SequenceType::getUnknownExtent()}, i8Ty);
-    bool addrVolatile2 = fir::isa_volatile_type(addr.getType());
-    mlir::Value byteBase = builder.createConvertWithVolatileCast(
-        loc, builder.getRefType(i8SeqTy, addrVolatile2), addr);
-    mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
-    mlir::Value last = builder.createIntegerConstant(
-        loc, idxTy, static_cast<int64_t>(nBytes) - 1);
-    mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
-    auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
-                                      /*unordered=*/false,
-                                      /*finalCount=*/false);
-    mlir::OpBuilder::InsertionGuard guard(builder);
-    builder.setInsertionPointToStart(loop.getBody());
-    mlir::Value iv = loop.getInductionVar();
-    bool byteVolatile2 = fir::isa_volatile_type(byteBase.getType());
-    mlir::Value byteAddr = fir::CoordinateOp::create(
-        builder, loc, builder.getRefType(i8Ty, byteVolatile2), byteBase,
-        mlir::ValueRange{iv});
-    int64_t fillByte =
-        (mode == Fortran::lower::InitLocalKind::Zero) ? 0 : hexByte;
-    mlir::Value pat = builder.createIntegerConstant(loc, i8Ty, fillByte);
-    fir::StoreOp::create(builder, loc, pat, byteAddr);
-  };
 
+  // REAL and COMPLEX: when allocation size exceeds store size (e.g. x86_fp80),
+  // use the allocation-derived byte loop to cover padding bytes.
   if (mlir::isa<mlir::FloatType, mlir::ComplexType>(ty)) {
     const mlir::DataLayout &dl = builder.getDataLayout();
     uint64_t storeSize = dl.getTypeSize(ty);
     uint64_t allocSize = llvm::alignTo(storeSize, dl.getTypeABIAlignment(ty));
     if (allocSize > storeSize) {
-      emitByteLoop(allocSize);
+      emitByteLoop(builder, loc, addr, allocSize, mode, hexByte);
       return;
     }
   }
@@ -1704,38 +1663,9 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
           // Byte-fill the full allocation (fields + internal padding +
           // tail padding). A typed fir.zero_bits store would leave tail
           // padding as 'undef', which LLVM may not zero at -O2.
-          {
-            auto [byteSize, _align] = fir::getTypeSizeAndAlignmentOrCrash(
-                loc, recTy, builder.getDataLayout(), builder.getKindMap());
-            if (byteSize > 0) {
-              mlir::Type idxTy = builder.getIndexType();
-              mlir::Type i8Ty = builder.getIntegerType(8);
-              mlir::Type i8SeqTy = fir::SequenceType::get(
-                  {fir::SequenceType::getUnknownExtent()}, i8Ty);
-              bool addrVolatile3 = fir::isa_volatile_type(addr.getType());
-              mlir::Value byteBase = builder.createConvertWithVolatileCast(
-                  loc, builder.getRefType(i8SeqTy, addrVolatile3), addr);
-              mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
-              mlir::Value last = builder.createIntegerConstant(
-                  loc, idxTy, static_cast<int64_t>(byteSize) - 1);
-              mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
-              auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
-                                                /*unordered=*/false,
-                                                /*finalCount=*/false);
-              mlir::OpBuilder::InsertionGuard guard(builder);
-              builder.setInsertionPointToStart(loop.getBody());
-              mlir::Value iv = loop.getInductionVar();
-              bool byteVolatile3 = fir::isa_volatile_type(byteBase.getType());
-              mlir::Value byteAddr = fir::CoordinateOp::create(
-                  builder, loc, builder.getRefType(i8Ty, byteVolatile3),
-                  byteBase, mlir::ValueRange{iv});
-              int64_t fillByte =
-                  (mode == Fortran::lower::InitLocalKind::Zero) ? 0 : hexByte;
-              mlir::Value pat =
-                  builder.createIntegerConstant(loc, i8Ty, fillByte);
-              fir::StoreOp::create(builder, loc, pat, byteAddr);
-            }
-          }
+          auto [byteSize, _align] = fir::getTypeSizeAndAlignmentOrCrash(
+              loc, recTy, builder.getDataLayout(), builder.getKindMap());
+          emitByteLoop(builder, loc, addr, byteSize, mode, hexByte);
         } else if (!mlir::isa<fir::BaseBoxType>(ty)) {
           // Scalar (integer, real, complex, logical, character): delegate to
           // genInitLocalStore, which handles each type and mode combination.
@@ -1782,8 +1712,7 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
           builder.getKindMap().getCharacterBitsize(charTy.getFKind());
       unsigned charByteWidthRt = std::max(1u, (charBitsRt + 7) / 8);
       const mlir::DataLayout &charDLRt = builder.getDataLayout();
-      mlir::Type charElemTyRt =
-          builder.getIntegerType(std::max(8u, llvm::alignTo(charBitsRt, 8u)));
+      mlir::Type charElemTyRt = builder.getIntegerType(charBitsRt);
       int64_t kindBytes = static_cast<int64_t>(llvm::alignTo(
           static_cast<uint64_t>(charByteWidthRt),
           static_cast<uint64_t>(charDLRt.getTypeABIAlignment(charElemTyRt))));
diff --git a/flang/test/Lower/finit-local-kind-mapping-rtchar.f90 b/flang/test/Lower/finit-local-kind-mapping-rtchar.f90
index f51f9f895e008..b90b16f043e85 100644
--- a/flang/test/Lower/finit-local-kind-mapping-rtchar.f90
+++ b/flang/test/Lower/finit-local-kind-mapping-rtchar.f90
@@ -16,13 +16,21 @@
 ! RUN:     FileCheck --check-prefix=RT-1BIT  %s
 
 ! RT-24BIT-NOT: not yet implemented
-! RT-24BIT: fir.do_loop
+! RT-24BIT: %[[FACTOR24:.*]] = arith.constant 4 : index
+! RT-24BIT: %[[NBYTES24:.*]] = arith.muli %{{.*}}, %[[FACTOR24]] : index
+! RT-24BIT: %[[LAST24:.*]] = arith.subi %[[NBYTES24]], %{{.*}} : index
+! RT-24BIT: fir.do_loop %{{.*}} = %{{.*}} to %[[LAST24]] step %{{.*}}
 
 ! RT-12BIT-NOT: not yet implemented
-! RT-12BIT: fir.do_loop
+! RT-12BIT: %[[FACTOR12:.*]] = arith.constant 2 : index
+! RT-12BIT: %[[NBYTES12:.*]] = arith.muli %{{.*}}, %[[FACTOR12]] : index
+! RT-12BIT: %[[LAST12:.*]] = arith.subi %[[NBYTES12]], %{{.*}} : index
+! RT-12BIT: fir.do_loop %{{.*}} = %{{.*}} to %[[LAST12]] step %{{.*}}
 
 ! RT-1BIT-NOT: not yet implemented
-! RT-1BIT: fir.do_loop
+! RT-1BIT-NOT: arith.muli
+! RT-1BIT: %[[LAST1:.*]] = arith.subi %{{.*}}, %{{.*}} : index
+! RT-1BIT: fir.do_loop %{{.*}} = %{{.*}} to %[[LAST1]] step %{{.*}}
 
 subroutine test_rt_char(n, res)
   integer, intent(in) :: n
diff --git a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90 b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
index ef545a3deb2ef..240f5149f5353 100644
--- a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
+++ b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
@@ -20,7 +20,7 @@
 ! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=l4:1 -finit-local=0xAA %s -o - 2>&1 | \
 ! RUN:     FileCheck %s
 
-! CHECK: not yet implemented: -finit-local= with a sub-byte or non-byte-multiple LOGICAL kind mapping
+! CHECK: not yet implemented: -finit-local= with a sub-byte, non-byte-multiple, or padded LOGICAL kind mapping
 
 subroutine test_logical4_subbyte(res)
   logical(kind=4) :: l
@@ -35,7 +35,7 @@ subroutine test_logical4_subbyte(res)
 ! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=l4:12 -finit-local=0xAA %s -o - 2>&1 | \
 ! RUN:     FileCheck --check-prefix=LOG-NONBYTE %s
 
-! LOG-NONBYTE: not yet implemented: -finit-local= with a sub-byte or non-byte-multiple LOGICAL kind mapping
+! LOG-NONBYTE: not yet implemented: -finit-local= with a sub-byte, non-byte-multiple, or padded LOGICAL kind mapping
 
 subroutine test_logical4_nonbyte(res)
   logical(kind=4) :: l
@@ -43,6 +43,21 @@ subroutine test_logical4_nonbyte(res)
   if (l) res = 1
 end subroutine
 
+! LOGICAL padded mapping: --kind-mapping=l4:24 maps LOGICAL(4) to 24 bits.
+! An i24 has a 4-byte allocation size (storeSize=3, allocSize=4). A 3-byte store
+! would leave the 4th byte unwritten. The guard catches this and emits a TODO.
+!
+! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=l4:24 -finit-local=0xAA %s -o - 2>&1 | \
+! RUN:     FileCheck --check-prefix=LOG-PAD %s
+
+! LOG-PAD: not yet implemented: -finit-local= with a sub-byte, non-byte-multiple, or padded LOGICAL kind mapping
+
+subroutine test_logical4_padded(res)
+  logical(kind=4) :: l
+  integer :: res
+  if (l) res = 1
+end subroutine
+
 ! CHARACTER a1:1: i1 rounds up to i8 (stride = 1 byte).  No diagnostic.
 !
 ! RUN: bbc -emit-hlfir --kind-mapping=a1:1 -finit-local=0xAA %s -o - 2>&1 | \
@@ -69,7 +84,9 @@ subroutine test_char1_subbyte(res)
 ! RUN:     FileCheck --check-prefix=CHAR-24BIT %s
 
 ! CHAR-12BIT-NOT: not yet implemented
-! CHAR-12BIT: fir.do_loop
+! CHAR-12BIT: %[[C3:.*]] = arith.constant 3 : index
+! CHAR-12BIT: fir.do_loop %{{.*}} = %{{.*}} to %[[C3]] step %{{.*}}
 
 ! CHAR-24BIT-NOT: not yet implemented
-! CHAR-24BIT: fir.do_loop
+! CHAR-24BIT: %[[C7:.*]] = arith.constant 7 : index
+! CHAR-24BIT: fir.do_loop %{{.*}} = %{{.*}} to %[[C7]] step %{{.*}}
diff --git a/flang/test/Lower/finit-local-volatile-llvm.f90 b/flang/test/Lower/finit-local-volatile-llvm.f90
index 5c381b66777c6..a731b39509ea2 100644
--- a/flang/test/Lower/finit-local-volatile-llvm.f90
+++ b/flang/test/Lower/finit-local-volatile-llvm.f90
@@ -69,7 +69,7 @@ subroutine test_volatile_array(res)
 ! ---------------------------------------------------------------------------
 ! Volatile fixed-length CHARACTER local -- compile-time-length byte-fill loop.
 ! hex: loop over nLen bytes -> store volatile i8.
-! zero: fir.zero_bits aggregate store -> store volatile [10 x i8] zeroinitializer.
+! zero: loop over nLen bytes -> store volatile i8.
 ! ---------------------------------------------------------------------------
 subroutine test_volatile_char_fixed(res)
   character(10), volatile :: x
@@ -81,7 +81,7 @@ subroutine test_volatile_char_fixed(res)
 ! HEX:        store volatile i8
 
 ! ZERO-LABEL: define {{.*}}@{{.*}}test_volatile_char_fixed{{.*}}(
-! ZERO:        store volatile [10 x i8] zeroinitializer
+! ZERO:        store volatile i8
 
 ! ---------------------------------------------------------------------------
 ! Volatile runtime-length CHARACTER local -- dynamic-length byte-fill loop.
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 2433e0b2a1f62..583905f9a451b 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -223,8 +223,10 @@ subroutine test_char10(res)
   res = x
 end subroutine
 ! ZERO-LABEL: func.func @_QPtest_char10
-! ZERO: fir.zero_bits !fir.char<1,10>
-! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
+! ZERO:        fir.do_loop
+! ZERO:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! ZERO:          %[[C0_10:.*]] = arith.constant 0 : i8
+! ZERO:          fir.store %[[C0_10]] to {{.*}} : !fir.ref<i8>
 
 
 
@@ -484,8 +486,11 @@ subroutine test_char2_fixed(res)
   res = x
 end subroutine
 ! ZERO-LABEL: func.func @_QPtest_char2_fixed
-! ZERO: fir.zero_bits !fir.char<2,3>
-! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<2,3>>
+! ZERO:        %[[C5_Z:.*]] = arith.constant 5 : index
+! ZERO:        fir.do_loop %{{.*}} = %{{.*}} to %[[C5_Z]] step %{{.*}} {
+! ZERO:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! ZERO:          %[[C0_2:.*]] = arith.constant 0 : i8
+! ZERO:          fir.store %[[C0_2]] to {{.*}} : !fir.ref<i8>
 
 ! HEX-LABEL:  func.func @_QPtest_char2_fixed
 ! HEX:        %[[C5:.*]] = arith.constant 5 : index
@@ -504,8 +509,11 @@ subroutine test_char4_fixed(res)
   res = x
 end subroutine
 ! ZERO-LABEL: func.func @_QPtest_char4_fixed
-! ZERO: fir.zero_bits !fir.char<4,2>
-! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<4,2>>
+! ZERO:        %[[C7_Z:.*]] = arith.constant 7 : index
+! ZERO:        fir.do_loop %{{.*}} = %{{.*}} to %[[C7_Z]] step %{{.*}} {
+! ZERO:          fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! ZERO:          %[[C0_4:.*]] = arith.constant 0 : i8
+! ZERO:          fir.store %[[C0_4]] to {{.*}} : !fir.ref<i8>
 
 ! HEX-LABEL:  func.func @_QPtest_char4_fixed
 ! HEX:        %[[C7:.*]] = arith.constant 7 : index

>From f556e95e346af957c8710f8424576545afad9234 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Thu, 10 Sep 2026 12:56:33 -0400
Subject: [PATCH 21/23] [flang] Fix -finit-local= LOGICAL padding and CHARACTER
 record sizing

Two byte-coverage gaps under non-default kind mappings:

1. LOGICAL padded mappings (e.g. --kind-mapping=l4:24): the i24 store
   size is 3 bytes but the allocation size is 4 bytes on most targets.
   Zero mode previously emitted a typed fir.zero_bits store covering
   only 3 bytes; hex mode reached a TODO in genByteSplatInit.  Fix by
   adding an upfront allocSize > storeSize guard in genInitLocalStore
   (matching the existing REAL/COMPLEX guard) that redirects both modes
   to emitByteLoop over the full allocation size.

2. CHARACTER components inside derived types: getTypeSizeAndAlignment
   for fir::CharacterType multiplied the element store size by the
   string length, ignoring ABI alignment padding within each code unit.
   Under --kind-mapping=a1:24 an i24 code unit has a 3-byte store size
   but a 4-byte allocation stride, so character(kind=1,len=4) inside a
   record was sized at 12 bytes instead of 16.  Fix by using
   llvm::alignTo(compSize, compAlign) * length (matching the sequence
   type path).

Also fix a pre-existing test mismatch in
acc-fir-map-info-prep-privatize.mlir: the CHECK expected elementSize(12)
for a {f64,f32} record, but getTypeSizeAndAlignment has been returning 16
(allocation size with tail padding) since tail-padding was added to the
record path.

Tests:
- finit-local-kind-mapping-subbyte.f90: convert LOG-PAD from a
  %not_todo_cmd TODO-check to a positive fir.do_loop check (bound 3)
  for hex mode; add LOG-PAD-ZERO run for zero mode (same bound).
- finit-local-kind-mapping.f90: add test_record_char4 with
  --kind-mapping=a1:24, asserting loop bound 15 (16-byte fill) for
  both hex and zero modes.
---
 flang/lib/Lower/ConvertVariable.cpp           | 42 ++++++++++++++-----
 flang/lib/Optimizer/Dialect/FIRType.cpp       |  7 +++-
 .../acc-fir-map-info-prep-privatize.mlir      |  8 ++--
 .../finit-local-kind-mapping-subbyte.f90      | 29 ++++++++++---
 flang/test/Lower/finit-local-kind-mapping.f90 | 30 +++++++++++++
 5 files changed, 96 insertions(+), 20 deletions(-)

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index ed6bc4dd89a97..6eaea63bde679 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1440,13 +1440,15 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
   // width matches the actual allocation size.
   // The caller stores it via a bitcasted address to preserve the bit pattern
   // (fir.convert from integer to !fir.logical normalizes nonzero -> true).
-  // Sub-byte, non-byte-multiple, and padded LOGICAL mappings are not supported:
+  // Sub-byte and non-byte-multiple LOGICAL mappings are not supported here:
   //   - sub-byte (e.g. l4:1): APInt::getSplat requires destination width >= 8.
   //   - non-byte-multiple (e.g. l4:12): makeIntCst(12) builds an i12 splat of
   //     0xAA -> 0xAAA, which occupies bytes AA 0A rather than AA AA -- the
   //     high nibble of the second byte is not filled by the byte pattern.
-  //   - padded (e.g. l4:24): allocSize (4) > storeSize (3) leaves the trailing
-  //     allocation byte uninitialized.
+  // Padded mappings (e.g. l4:24, where allocSize=4 > storeSize=3) are
+  // intercepted upfront in genInitLocalStore via emitByteLoop before this
+  // function is called; the allocSize > storeSize branch below is a defensive
+  // guard in case this function is ever called directly for such a type.
   if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
     unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
     const mlir::DataLayout &dl = builder.getDataLayout();
@@ -1542,6 +1544,27 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
     }
   }
 
+  // LOGICAL(k): when the allocation size exceeds the store size (e.g.
+  // --kind-mapping=l4:24 maps LOGICAL(4) to i24 with 3-byte store size but
+  // 4-byte allocation size on most targets), use the allocation-derived byte
+  // loop to cover the tail padding byte.  This guard fires before
+  // genByteSplatInit is called, so both zero and hex modes use the byte loop
+  // and the padded case never reaches the TODO in genByteSplatInit.
+  if (auto logTy = mlir::dyn_cast<fir::LogicalType>(ty)) {
+    unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
+    if (bits % 8 == 0 && bits >= 8) {
+      const mlir::DataLayout &logDL = builder.getDataLayout();
+      mlir::Type intTy = builder.getIntegerType(bits);
+      uint64_t logStoreSize = logDL.getTypeSize(intTy);
+      uint64_t logAllocSize =
+          llvm::alignTo(logStoreSize, logDL.getTypeABIAlignment(intTy));
+      if (logAllocSize > logStoreSize) {
+        emitByteLoop(builder, loc, addr, logAllocSize, mode, hexByte);
+        return;
+      }
+    }
+  }
+
   mlir::Value val;
   switch (mode) {
   case Fortran::lower::InitLocalKind::Zero:
@@ -1692,13 +1715,12 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       mlir::Value lenIdx = builder.createConvert(loc, idxTy, rtLen);
       mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
       mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
-      // This stride computation applies to all modes (zero and hex), not just
-      // hex.  Unlike the fixed-length path -- where zero mode emits a single
-      // fir.zero_bits over the whole !fir.char<k,n> object -- the runtime-
-      // length path always strides by kindBytes regardless of mode, so the
-      // correct stride is required in both cases.  Gating on hex would
-      // reintroduce a half-storage fill under a non-byte-multiple mapping
-      // (e.g. a1:12) in zero mode.
+      // This stride computation applies to all modes (zero and hex).  Both
+      // the fixed-length and runtime-length paths always stride by kindBytes
+      // regardless of mode: fixed-length uses emitByteLoop directly, and
+      // runtime-length loops here.  The correct stride is required in both
+      // cases; gating on hex would reintroduce a half-storage fill under a
+      // non-byte-multiple mapping (e.g. a1:12) in zero mode.
       //
       // Use the DataLayout allocation stride rather than charBits / 8.
       // charBits / 8 is the semantic byte width; LLVM pads iN types to their
diff --git a/flang/lib/Optimizer/Dialect/FIRType.cpp b/flang/lib/Optimizer/Dialect/FIRType.cpp
index c1fbee15056ce..5de57a8608f66 100644
--- a/flang/lib/Optimizer/Dialect/FIRType.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRType.cpp
@@ -1710,7 +1710,12 @@ fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
       return result;
     auto [compSize, compAlign] = *result;
     if (character.hasConstantLen())
-      compSize *= character.getLen();
+      // Use the code unit's allocation stride (aligned store size) rather than
+      // the bare store size.  Under kind mappings like a1:24 (i24, 3-byte
+      // store, 4-byte stride), multiplying by the store size under-counts the
+      // total byte footprint; using alignTo(compSize, compAlign) matches what
+      // LLVM allocates for each code unit.
+      compSize = llvm::alignTo(compSize, compAlign) * character.getLen();
     return std::pair{compSize, compAlign};
   }
   return std::nullopt;
diff --git a/flang/test/Fir/OpenACC/acc-fir-map-info-prep-privatize.mlir b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-privatize.mlir
index 4f5d9dd2b44cd..c455f26ed0a51 100644
--- a/flang/test/Fir/OpenACC/acc-fir-map-info-prep-privatize.mlir
+++ b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-privatize.mlir
@@ -51,15 +51,17 @@ func.func @private_without_par_dims() {
 
 // -----
 
-// A record element uses the padded stride: real(8) + real(4) has a size of 12
-// and an alignment of 8, so consecutive elements are 16 bytes apart.
+// A record element's allocation size includes tail padding: real(8) + real(4)
+// has a store size of 12 but an allocation size of 16 (padded to the record's
+// 8-byte alignment), so elementSize reports 16.  The total array size
+// (8 * 16 = 128) matches the arith.constant below.
 
 // CHECK-LABEL: func.func @private_static_record
 // CHECK: %[[PRIV:.*]] = acc.privatize
 // CHECK: %[[SIZE:.*]] = arith.constant 128 : i64
 // CHECK: acc.map_info varPtr(%[[PRIV]]
 // CHECK-SAME: size(%[[SIZE]] : i64)
-// CHECK-SAME: elementSize(12)
+// CHECK-SAME: elementSize(16)
 // CHECK-SAME: mapFlags(private)
 func.func @private_static_record() {
   %priv = acc.privatize par_dims(#acc<par_dims[]>)
diff --git a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90 b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
index 240f5149f5353..0f356c5964d25 100644
--- a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
+++ b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
@@ -1,7 +1,8 @@
 ! Tests that -finit-local= handles all CHARACTER kind-mapping widths correctly
 ! using the code unit's allocation stride (alignTo(ceil(charBits/8), ABI)),
-! and that LOGICAL sub-byte and non-byte-multiple mappings emit a controlled
-! diagnostic.
+! that LOGICAL sub-byte and non-byte-multiple mappings emit a controlled
+! diagnostic, and that padded LOGICAL mappings (allocSize > storeSize) use a
+! byte-fill loop in both hex and zero modes.
 !
 ! LOGICAL sub-byte: --kind-mapping=l4:1 maps LOGICAL(4) to 1 bit.
 ! APInt::getSplat(1, APInt(8, 0xAA)) asserts because the destination width is
@@ -44,13 +45,16 @@ subroutine test_logical4_nonbyte(res)
 end subroutine
 
 ! LOGICAL padded mapping: --kind-mapping=l4:24 maps LOGICAL(4) to 24 bits.
-! An i24 has a 4-byte allocation size (storeSize=3, allocSize=4). A 3-byte store
-! would leave the 4th byte unwritten. The guard catches this and emits a TODO.
+! An i24 has a 4-byte allocation size (storeSize=3, allocSize=4).  genInitLocalStore
+! now intercepts this upfront and emits a byte-fill loop over all 4 bytes, so
+! neither hex nor zero mode stores only 3 bytes any more.
 !
-! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=l4:24 -finit-local=0xAA %s -o - 2>&1 | \
+! RUN: bbc -emit-hlfir --kind-mapping=l4:24 -finit-local=0xAA %s -o - 2>&1 | \
 ! RUN:     FileCheck --check-prefix=LOG-PAD %s
 
-! LOG-PAD: not yet implemented: -finit-local= with a sub-byte, non-byte-multiple, or padded LOGICAL kind mapping
+! LOG-PAD-LABEL: func.func @_QPtest_logical4_padded(
+! LOG-PAD: %[[C3:.*]] = arith.constant 3 : index
+! LOG-PAD: fir.do_loop %{{.*}} = %{{.*}} to %[[C3]] step %{{.*}}
 
 subroutine test_logical4_padded(res)
   logical(kind=4) :: l
@@ -90,3 +94,16 @@ subroutine test_char1_subbyte(res)
 ! CHAR-24BIT-NOT: not yet implemented
 ! CHAR-24BIT: %[[C7:.*]] = arith.constant 7 : index
 ! CHAR-24BIT: fir.do_loop %{{.*}} = %{{.*}} to %[[C7]] step %{{.*}}
+
+! LOGICAL padded mapping + zero mode: --kind-mapping=l4:24 maps LOGICAL(4) to
+! 24 bits (3-byte store) inside a 4-byte allocation.  Zero mode previously
+! emitted a single 3-byte fir.zero_bits store, leaving the tail byte
+! uninitialized.  It must now use a byte-fill loop over the full 4-byte
+! allocation, the same as hex mode.
+!
+! RUN: bbc -emit-hlfir --kind-mapping=l4:24 -finit-local=zero %s -o - 2>&1 | \
+! RUN:     FileCheck --check-prefix=LOG-PAD-ZERO %s
+
+! LOG-PAD-ZERO-LABEL: func.func @_QPtest_logical4_padded(
+! LOG-PAD-ZERO: %[[C3:.*]] = arith.constant 3 : index
+! LOG-PAD-ZERO: fir.do_loop %{{.*}} = %{{.*}} to %[[C3]] step %{{.*}}
diff --git a/flang/test/Lower/finit-local-kind-mapping.f90 b/flang/test/Lower/finit-local-kind-mapping.f90
index 5e8f92813903a..0cc4fb82381b0 100644
--- a/flang/test/Lower/finit-local-kind-mapping.f90
+++ b/flang/test/Lower/finit-local-kind-mapping.f90
@@ -31,3 +31,33 @@ subroutine test_logical4_km(res)
 ! ZERO:        %[[L:.*]]:2 = hlfir.declare {{.*}}_QFtest_logical4_kmEl
 ! ZERO:        %[[Z:.*]] = fir.zero_bits !fir.logical<4>
 ! ZERO:        fir.store %[[Z]] to %[[L]]#0 : !fir.ref<!fir.logical<4>>
+
+! Record with a CHARACTER(kind=1,len=4) component under a1:24.
+! --kind-mapping=a1:24 maps CHARACTER(1) code units to i24 (3-byte store, 4-byte
+! stride on most targets).  getTypeSizeAndAlignment for !fir.char<1,4> must
+! use the allocation stride (4 bytes) rather than the store size (3 bytes).
+! The record byte loop must therefore run 4 * 4 = 16 iterations (bound 15).
+! Previously, using the store size gave 4 * 3 = 12 iterations, leaving the
+! 4th byte of each code unit's allocation uninitialised.
+!
+! RUN: bbc -emit-hlfir --kind-mapping=a1:24 -finit-local=0xAA %s -o - | \
+! RUN:     FileCheck --check-prefix=RECHEX %s
+! RUN: bbc -emit-hlfir --kind-mapping=a1:24 -finit-local=zero %s -o - | \
+! RUN:     FileCheck --check-prefix=RECZERO %s
+
+subroutine test_record_char4(res)
+  type :: t
+    character(kind=1, len=4) :: c
+  end type
+  type(t) :: x
+  integer :: res
+  res = ichar(x%c(4:4))
+end subroutine
+
+! RECHEX-LABEL:  func.func @_QPtest_record_char4(
+! RECHEX:  %[[C15:.*]] = arith.constant 15 : index
+! RECHEX:  fir.do_loop %{{.*}} = %{{.*}} to %[[C15]] step %{{.*}}
+
+! RECZERO-LABEL: func.func @_QPtest_record_char4(
+! RECZERO:  %[[C15:.*]] = arith.constant 15 : index
+! RECZERO:  fir.do_loop %{{.*}} = %{{.*}} to %[[C15]] step %{{.*}}

>From 17a0b12957912ffd676ff45ff3ce9ee8da8efcca Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Fri, 11 Sep 2026 11:48:32 -0400
Subject: [PATCH 22/23] [flang] Fix -finit-local= zero-mode padding for
 non-byte-multiple LOGICAL mappings

genInitLocalStore's LOGICAL padding guard was conditioned on
`bits % 8 == 0 && bits >= 8`, so non-byte-multiple widths (e.g.
`--kind-mapping=l4:20`, bits=20) bypassed the allocSize > storeSize
check entirely.  On most targets i20 has a 4-byte allocation size
(storeSize=3, ABI align=4), so zero mode fell through to fir::ZeroOp
and emitted a 3-byte `store i20 0` into the 4-byte allocation, leaving
the tail byte uninitialized.  Hex mode already hit the existing TODO
in genByteSplatInit via the `bits % 8 != 0` guard.

Remove the `bits % 8 == 0 && bits >= 8` precondition so that the
`logAllocSize > logStoreSize` check is evaluated unconditionally for
any mapped width.  This unifies the l4:20 behaviour with the existing
l4:24 fix: both modes now emit a byte-fill loop over the full
allocation.

Add a regression test for l4:20 in finit-local-kind-mapping-subbyte.f90
covering both zero and hex modes; both produce a 4-byte loop (bound 3).
Also correct stale inline comments across five test files:
- finit-local.f90: CHARACTER zero mode now uses a byte loop, not
  fir.zero_bits; add missing zero-mode description for kind=4 block.
- finit-local-kind-mapping.f90: drop before/after framing from the
  l4:8 intro; fix record comment to say "fourth code unit's 4-byte
  allocation" rather than "4th byte of each code unit".
- finit-local-kind-mapping-subbyte.f90: update header to distinguish
  unpadded non-byte-multiples (TODO) from padded ones (byte loop);
  drop stale fir.zero_bits references; rename subroutine to
  test_logical4_padded_nonbyte.
- finit-local-array-llvm.f90: replace "initAddr recurses into each
  record element" (inaccurate) with "each element is byte-filled via
  an inner loop"; drop stale "(Thread N/M regression)" labels.
---
 flang/lib/Lower/ConvertVariable.cpp           | 41 +++++++-------
 flang/test/Lower/finit-local-array-llvm.f90   | 10 ++--
 .../finit-local-kind-mapping-subbyte.f90      | 53 +++++++++++++------
 flang/test/Lower/finit-local-kind-mapping.f90 |  8 ++-
 flang/test/Lower/finit-local.f90              |  5 +-
 5 files changed, 70 insertions(+), 47 deletions(-)

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 6eaea63bde679..13e999328c215 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1445,10 +1445,11 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
   //   - non-byte-multiple (e.g. l4:12): makeIntCst(12) builds an i12 splat of
   //     0xAA -> 0xAAA, which occupies bytes AA 0A rather than AA AA -- the
   //     high nibble of the second byte is not filled by the byte pattern.
-  // Padded mappings (e.g. l4:24, where allocSize=4 > storeSize=3) are
-  // intercepted upfront in genInitLocalStore via emitByteLoop before this
-  // function is called; the allocSize > storeSize branch below is a defensive
-  // guard in case this function is ever called directly for such a type.
+  // Padded mappings (e.g. l4:24 where allocSize=4 > storeSize=3, or
+  // l4:20 where allocSize=4 > storeSize=3 on x86-64) are intercepted upfront
+  // in genInitLocalStore via emitByteLoop before this function is called;
+  // the allocSize > storeSize branch below is a defensive guard in case this
+  // function is ever called directly for such a type.
   if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
     unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
     const mlir::DataLayout &dl = builder.getDataLayout();
@@ -1544,24 +1545,24 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
     }
   }
 
-  // LOGICAL(k): when the allocation size exceeds the store size (e.g.
-  // --kind-mapping=l4:24 maps LOGICAL(4) to i24 with 3-byte store size but
-  // 4-byte allocation size on most targets), use the allocation-derived byte
-  // loop to cover the tail padding byte.  This guard fires before
-  // genByteSplatInit is called, so both zero and hex modes use the byte loop
-  // and the padded case never reaches the TODO in genByteSplatInit.
+  // LOGICAL(k): when the allocation size exceeds the store size, use the
+  // allocation-derived byte loop to cover the tail padding bytes.  This
+  // applies to any mapped width, including non-byte-multiples (e.g.
+  // --kind-mapping=l4:20 maps LOGICAL(4) to i20; storeSize=3, allocSize=4
+  // on x86-64) and padded byte-multiple widths (e.g. l4:24: storeSize=3,
+  // allocSize=4).  This guard fires before genByteSplatInit is called, so
+  // both zero and hex modes use the byte loop; the padded/non-byte-multiple
+  // case never reaches the TODO in genByteSplatInit.
   if (auto logTy = mlir::dyn_cast<fir::LogicalType>(ty)) {
     unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
-    if (bits % 8 == 0 && bits >= 8) {
-      const mlir::DataLayout &logDL = builder.getDataLayout();
-      mlir::Type intTy = builder.getIntegerType(bits);
-      uint64_t logStoreSize = logDL.getTypeSize(intTy);
-      uint64_t logAllocSize =
-          llvm::alignTo(logStoreSize, logDL.getTypeABIAlignment(intTy));
-      if (logAllocSize > logStoreSize) {
-        emitByteLoop(builder, loc, addr, logAllocSize, mode, hexByte);
-        return;
-      }
+    const mlir::DataLayout &logDL = builder.getDataLayout();
+    mlir::Type intTy = builder.getIntegerType(bits);
+    uint64_t logStoreSize = logDL.getTypeSize(intTy);
+    uint64_t logAllocSize =
+        llvm::alignTo(logStoreSize, logDL.getTypeABIAlignment(intTy));
+    if (logAllocSize > logStoreSize) {
+      emitByteLoop(builder, loc, addr, logAllocSize, mode, hexByte);
+      return;
     }
   }
 
diff --git a/flang/test/Lower/finit-local-array-llvm.f90 b/flang/test/Lower/finit-local-array-llvm.f90
index a8badfb509f7d..de943398d79c1 100644
--- a/flang/test/Lower/finit-local-array-llvm.f90
+++ b/flang/test/Lower/finit-local-array-llvm.f90
@@ -97,9 +97,9 @@ subroutine test_int_array_3d(res)
 ! ZERO:        store i32 0,
 
 ! ---------------------------------------------------------------------------
-! Array of derived type  type(t) :: x(2)  (Thread 3/4 regression)
-! Loop strides by sizeof(%t); initAddr recurses into each record element so
-! all fields receive the pattern rather than a zeroinitializer.
+! Array of derived type  type(t) :: x(2)
+! Outer loop strides by sizeof(%t); each element is byte-filled via an inner
+! loop, covering all fields and internal padding.
 ! ---------------------------------------------------------------------------
 subroutine test_array_of_struct(res)
   type :: t
@@ -126,8 +126,8 @@ subroutine test_array_of_struct(res)
 
 ! ---------------------------------------------------------------------------
 ! Rank-2 array of derived type  type(t) :: x(2,3) -- 6 elements
-! Flat loop must stride by sizeof(%t) via rank-1 view; initAddr recurses
-! into each record so all fields of all 6 elements receive the pattern.
+! Flat loop strides by sizeof(%t) via a rank-1 view; each element is
+! byte-filled, covering all fields and padding of all 6 elements.
 ! ---------------------------------------------------------------------------
 subroutine test_array_of_struct_2d(res)
   type :: t
diff --git a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90 b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
index 0f356c5964d25..a0b77476533be 100644
--- a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
+++ b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
@@ -1,16 +1,18 @@
 ! Tests that -finit-local= handles all CHARACTER kind-mapping widths correctly
 ! using the code unit's allocation stride (alignTo(ceil(charBits/8), ABI)),
-! that LOGICAL sub-byte and non-byte-multiple mappings emit a controlled
-! diagnostic, and that padded LOGICAL mappings (allocSize > storeSize) use a
-! byte-fill loop in both hex and zero modes.
+! that sub-byte (l4:1) and non-byte-multiple-without-padding (l4:12) LOGICAL
+! mappings emit a controlled diagnostic, and that padded LOGICAL mappings
+! (allocSize > storeSize, e.g. l4:24 and l4:20) use a byte-fill loop in both
+! hex and zero modes.
 !
 ! LOGICAL sub-byte: --kind-mapping=l4:1 maps LOGICAL(4) to 1 bit.
 ! APInt::getSplat(1, APInt(8, 0xAA)) asserts because the destination width is
 ! less than 8; a TODO is emitted.
 !
-! LOGICAL non-byte-multiple: --kind-mapping=l4:12 maps LOGICAL(4) to 12 bits.
-! makeIntCst(12) would produce 0xAAA (i12), which stores as AA 0A -- the high
-! nibble of the second byte is not filled.  A TODO is emitted instead.
+! LOGICAL non-byte-multiple (unpadded): --kind-mapping=l4:12 maps LOGICAL(4)
+! to 12 bits (storeSize=2, allocSize=2 -- no padding).  makeIntCst(12) would
+! produce 0xAAA (i12), storing as AA 0A -- the high nibble unfilled.  A TODO
+! is emitted instead.
 !
 ! All CHARACTER kind widths are now handled without diagnostics:
 !   a1:1  -- i1 rounds up to i8, stride = 1 byte (fills 1 byte per code unit)
@@ -44,10 +46,9 @@ subroutine test_logical4_nonbyte(res)
   if (l) res = 1
 end subroutine
 
-! LOGICAL padded mapping: --kind-mapping=l4:24 maps LOGICAL(4) to 24 bits.
-! An i24 has a 4-byte allocation size (storeSize=3, allocSize=4).  genInitLocalStore
-! now intercepts this upfront and emits a byte-fill loop over all 4 bytes, so
-! neither hex nor zero mode stores only 3 bytes any more.
+! LOGICAL padded mapping: --kind-mapping=l4:24 maps LOGICAL(4) to 24 bits
+! (storeSize=3, allocSize=4).  Both hex and zero modes use a byte-fill loop
+! over all 4 bytes.
 !
 ! RUN: bbc -emit-hlfir --kind-mapping=l4:24 -finit-local=0xAA %s -o - 2>&1 | \
 ! RUN:     FileCheck --check-prefix=LOG-PAD %s
@@ -95,11 +96,7 @@ subroutine test_char1_subbyte(res)
 ! CHAR-24BIT: %[[C7:.*]] = arith.constant 7 : index
 ! CHAR-24BIT: fir.do_loop %{{.*}} = %{{.*}} to %[[C7]] step %{{.*}}
 
-! LOGICAL padded mapping + zero mode: --kind-mapping=l4:24 maps LOGICAL(4) to
-! 24 bits (3-byte store) inside a 4-byte allocation.  Zero mode previously
-! emitted a single 3-byte fir.zero_bits store, leaving the tail byte
-! uninitialized.  It must now use a byte-fill loop over the full 4-byte
-! allocation, the same as hex mode.
+! LOGICAL padded mapping, zero mode: same as above with -finit-local=zero.
 !
 ! RUN: bbc -emit-hlfir --kind-mapping=l4:24 -finit-local=zero %s -o - 2>&1 | \
 ! RUN:     FileCheck --check-prefix=LOG-PAD-ZERO %s
@@ -107,3 +104,29 @@ subroutine test_char1_subbyte(res)
 ! LOG-PAD-ZERO-LABEL: func.func @_QPtest_logical4_padded(
 ! LOG-PAD-ZERO: %[[C3:.*]] = arith.constant 3 : index
 ! LOG-PAD-ZERO: fir.do_loop %{{.*}} = %{{.*}} to %[[C3]] step %{{.*}}
+
+! LOGICAL padded non-byte-multiple: --kind-mapping=l4:20 maps LOGICAL(4) to
+! 20 bits (storeSize=3, allocSize=4 on most targets since i20 gets 4-byte ABI
+! alignment).  Unlike l4:12 (where allocSize==storeSize so the padding guard
+! does not fire and a TODO is emitted), l4:20 has allocSize > storeSize, so
+! the padding guard fires and both zero and hex modes use a byte-fill loop
+! over the full 4-byte allocation.
+!
+! RUN: bbc -emit-hlfir --kind-mapping=l4:20 -finit-local=zero %s -o - 2>&1 | \
+! RUN:     FileCheck --check-prefix=LOG-20-ZERO %s
+! RUN: bbc -emit-hlfir --kind-mapping=l4:20 -finit-local=0xAA %s -o - 2>&1 | \
+! RUN:     FileCheck --check-prefix=LOG-20-HEX %s
+
+! LOG-20-ZERO-LABEL: func.func @_QPtest_logical4_padded_nonbyte(
+! LOG-20-ZERO: %[[C3:.*]] = arith.constant 3 : index
+! LOG-20-ZERO: fir.do_loop %{{.*}} = %{{.*}} to %[[C3]] step %{{.*}}
+
+! LOG-20-HEX-LABEL: func.func @_QPtest_logical4_padded_nonbyte(
+! LOG-20-HEX: %[[C3:.*]] = arith.constant 3 : index
+! LOG-20-HEX: fir.do_loop %{{.*}} = %{{.*}} to %[[C3]] step %{{.*}}
+
+subroutine test_logical4_padded_nonbyte(res)
+  logical(kind=4) :: l
+  integer :: res
+  if (l) res = 1
+end subroutine
diff --git a/flang/test/Lower/finit-local-kind-mapping.f90 b/flang/test/Lower/finit-local-kind-mapping.f90
index 0cc4fb82381b0..ffe8331ee6f94 100644
--- a/flang/test/Lower/finit-local-kind-mapping.f90
+++ b/flang/test/Lower/finit-local-kind-mapping.f90
@@ -4,10 +4,8 @@
 ! use KindMapping::getLogicalBitsize() / getCharacterBitsize() rather than
 ! getFKind() * 8 or getFKind() directly.
 !
-! Reproducer: --kind-mapping=l4:8 maps LOGICAL(4) to 8 bits (1 byte).
-! Before the fix, the synthesized init emitted a 4-byte i32 constant stored
-! via a bitcasted i32* into a 1-byte alloca -- a 3-byte out-of-bounds write.
-! After the fix the constant and store are both i8.
+! With --kind-mapping=l4:8, LOGICAL(4) maps to 8 bits (1 byte).  The
+! synthesized init must use an i8 constant and store, not a 4-byte i32.
 !
 ! RUN: bbc -emit-hlfir --kind-mapping=l4:8 -finit-local=0xAA %s -o - | \
 ! RUN:     FileCheck --check-prefix=HEX %s
@@ -38,7 +36,7 @@ subroutine test_logical4_km(res)
 ! use the allocation stride (4 bytes) rather than the store size (3 bytes).
 ! The record byte loop must therefore run 4 * 4 = 16 iterations (bound 15).
 ! Previously, using the store size gave 4 * 3 = 12 iterations, leaving the
-! 4th byte of each code unit's allocation uninitialised.
+! fourth code unit's 4-byte allocation entirely uninitialised.
 !
 ! RUN: bbc -emit-hlfir --kind-mapping=a1:24 -finit-local=0xAA %s -o - | \
 ! RUN:     FileCheck --check-prefix=RECHEX %s
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 583905f9a451b..aab6a0fd8ba51 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -214,7 +214,7 @@ subroutine test_logical4(res)
 
 ! ---------------------------------------------------------------------------
 ! CHARACTER(10) -- fixed-length scalar.
-! zero: fir.zero_bits over the whole character type.
+! zero: byte-loop over 10 bytes via an i8 view.
 ! hex: byte-loop over 10 bytes via an i8 view.
 ! ---------------------------------------------------------------------------
 subroutine test_char10(res)
@@ -478,7 +478,7 @@ subroutine test_equivalence(res)
 ! ---------------------------------------------------------------------------
 ! CHARACTER(kind=2, len=3) -- fixed-length, higher kind.
 ! hex: byte-loop over 3*2=6 bytes via an i8 view.
-! zero: fir.zero_bits over the whole type.
+! zero: byte-loop over 3*2=6 bytes via an i8 view.
 ! ---------------------------------------------------------------------------
 subroutine test_char2_fixed(res)
   character(kind=2, len=3) :: res
@@ -502,6 +502,7 @@ subroutine test_char2_fixed(res)
 ! ---------------------------------------------------------------------------
 ! CHARACTER(kind=4, len=2) -- fixed-length, wider kind.
 ! hex: byte-loop over 2*4=8 bytes via an i8 view.
+! zero: byte-loop over 2*4=8 bytes via an i8 view.
 ! ---------------------------------------------------------------------------
 subroutine test_char4_fixed(res)
   character(kind=4, len=2) :: res

>From f4d27416d4b61f4669efa8310570b3efd517e65a Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Sat, 12 Sep 2026 05:38:01 -0400
Subject: [PATCH 23/23] Drop type-widening example comments for CHARACTER kind
 mappings in  and .

---
 flang/lib/Lower/ConvertVariable.cpp                   | 7 ++-----
 flang/test/Lower/finit-local-kind-mapping-subbyte.f90 | 6 +-----
 2 files changed, 3 insertions(+), 10 deletions(-)

diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 13e999328c215..62048b395fe21 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1726,11 +1726,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
       // Use the DataLayout allocation stride rather than charBits / 8.
       // charBits / 8 is the semantic byte width; LLVM pads iN types to their
       // ABI alignment, so e.g. i24 has a 4-byte stride on most targets.
-      // The formula alignTo(ceil(charBits/8), ABI) handles all widths
-      // including sub-byte ones (i1 rounds up to i8, 1-byte stride) so no
-      // diagnostic is needed here either.
-      // e.g. a1:1 (i1->i8, stride 1), a1:12 (i12->i16, stride 2),
-      //      a1:24 (i24->i32, stride 4).
+      // The formula alignTo(ceil(charBits/8), ABI) handles all widths,
+      // including sub-byte ones, so no diagnostic is needed here either.
       unsigned charBitsRt =
           builder.getKindMap().getCharacterBitsize(charTy.getFKind());
       unsigned charByteWidthRt = std::max(1u, (charBitsRt + 7) / 8);
diff --git a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90 b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
index a0b77476533be..a24d50a0c4918 100644
--- a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
+++ b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
@@ -14,11 +14,7 @@
 ! produce 0xAAA (i12), storing as AA 0A -- the high nibble unfilled.  A TODO
 ! is emitted instead.
 !
-! All CHARACTER kind widths are now handled without diagnostics:
-!   a1:1  -- i1 rounds up to i8, stride = 1 byte (fills 1 byte per code unit)
-!   a1:12 -- i12 rounds up to i16, stride = 2 bytes
-!   a1:24 -- i24 rounds up to i32, stride = 4 bytes (was the motivating case:
-!             charBits/8 = 3 missed the last byte; stride = 4 is correct)
+! All CHARACTER kind widths are now handled without diagnostics.
 !
 ! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=l4:1 -finit-local=0xAA %s -o - 2>&1 | \
 ! RUN:     FileCheck %s



More information about the cfe-commits mailing list