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

Daniel Chen via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 16 20:27:55 PDT 2026


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

>From 940ae6b6b2379a76c4bbddf3c8a94cd7a58ee34e 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 1/6] [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 cf40d0b909d8f..d3402467b6f8e 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>;
@@ -394,6 +393,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 a48e41159f367..096c592beb7ad 100644
--- a/clang/lib/Driver/ToolChains/Flang.cpp
+++ b/clang/lib/Driver/ToolChains/Flang.cpp
@@ -359,8 +359,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 bbc7084c4a757..660166e8099d0 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -57,6 +57,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 b57bc4583be38..5380a51a16345 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -1755,6 +1755,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 a808905850922..03ebe82cb52ce 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"
@@ -1250,6 +1254,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.
@@ -1273,6 +1507,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 4d6b0a22f426e..0bf66c72c05a7 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -272,6 +272,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 "
@@ -501,6 +515,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.setStackRepackArrays(stackRepackArrays);
   loweringOptions.setRepackArrays(repackArrays);

>From 98b4b99f3a5ac23f22007d44028554179b2ccc8f 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 2/6] [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 d3402467b6f8e..881bf58aa1e3f 100644
--- a/clang/include/clang/Options/FlangOptions.td
+++ b/clang/include/clang/Options/FlangOptions.td
@@ -393,10 +393,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 660166e8099d0..3cc8794785803 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -57,7 +57,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 03ebe82cb52ce..202222d299982 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1420,26 +1420,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 be5c79d201af7914f42881a594f9e95c81e185fb 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 3/6] [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 202222d299982..bbb65619935f4 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1280,6 +1280,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;
 }
 
@@ -1316,6 +1320,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);
@@ -1406,84 +1415,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 0bf66c72c05a7..4adbb5e7764d4 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -515,11 +515,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") {
@@ -535,11 +532,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.setStackRepackArrays(stackRepackArrays);
   loweringOptions.setRepackArrays(repackArrays);

>From 675f3bec14382e5570a5d2c8b036e15213a56747 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 4/6] [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 bbb65619935f4..46a444f0e1907 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1280,15 +1280,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,
@@ -1320,10 +1330,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.
@@ -1345,8 +1356,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,
@@ -1390,12 +1402,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) {
@@ -1461,26 +1485,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 4adbb5e7764d4..8fd72df336ab3 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -517,8 +517,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 a85a3c9b51bafa652505324c85976c4438d1af76 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 5/6] [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 46a444f0e1907..5a60c23fc5764 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1261,7 +1261,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;
@@ -1281,12 +1283,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) {
@@ -1363,6 +1365,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);
@@ -1417,8 +1423,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,
@@ -1444,49 +1451,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()) {
@@ -1497,39 +1469,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 8fd72df336ab3..18a103cbf5a48 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -516,8 +516,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";
@@ -543,11 +549,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.setStackRepackArrays(stackRepackArrays);
   loweringOptions.setRepackArrays(repackArrays);

>From 5e2df72b26cf35070c95eef5c81aba4b6ec52aaa 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 6/6] [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 instead of falling back
  to fir.zero_bits.
- Fix hex mode for derived types: byte-loop over the full struct size
  (from fir::getTypeSizeAndAlignmentOrCrash) to cover both 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 3cc8794785803..9040d391336e8 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -61,6 +61,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 5a60c23fc5764..7460f98126956 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1282,17 +1282,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;
@@ -1338,8 +1355,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);
 }
 
@@ -1358,6 +1376,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.
@@ -1365,10 +1384,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);
@@ -1490,12 +1554,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()),
@@ -1506,12 +1606,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>
 
 
 ! ---------------------------------------------------------------------------



More information about the llvm-commits mailing list