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

Daniel Chen via flang-commits flang-commits at lists.llvm.org
Thu Aug 13 13:01:46 PDT 2026


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

This patch implements the `-finit-local=<val>` compiler option, which
initializes automatic (local, stack-allocated) Fortran variables that
have no explicit or default initialization.

RFC: https://discourse.llvm.org/t/rfc-add-finit-local-to-flang-for-initializing-automatic-variables/91545

## Accepted values

| Value    | Effect                                                 |
|----------|--------------------------------------------------------|
| `zero`   | Fill with zero bits                                    |
| `nan`    | Fill with a quiet NaN (FP types) or 0xAA... (int)     |
| `snan`   | Fill with a signaling NaN (FP types) or 0xAA... (int) |
| `0x<hh>` | Fill every byte with the given hex byte pattern        |

The gfortran compatibility alias `-finit-local-zero` is equivalent to
`-finit-local=zero`.

## Excluded variables (not initialized)

- Variables with explicit initializers (`integer :: x = 42`)
- Variables initialized via DATA statement
- Derived-type variables whose type has default component initialization
- SAVE variables (static storage)
- Dummy arguments
- ALLOCATABLE and POINTER variables
- Variables in EQUIVALENCE sets

## Implementation

| File | Change |
|------|--------|
| `clang/include/clang/Options/FlangOptions.td` | Adds `finit_local_EQ` joined option and `finit_local_zero` alias |
| `clang/lib/Driver/ToolChains/Flang.cpp` | Forwards the option to the frontend via `addAllArgs` |
| `flang/include/flang/Lower/LoweringOptions.h` | Adds `InitLocalKind` enum and `InitLocalMode`/`InitLocalPattern` fields |
| `flang/include/flang/Lower/LoweringOptions.def` | Registers `InitLocalMode` lowering option |
| `flang/lib/Frontend/CompilerInvocation.cpp` | Parses option values and populates lowering options |
| `flang/lib/Lower/ConvertVariable.cpp` | Core lowering: `genInitLocal()` called from `instantiateLocal()` for every auto variable (else-branch of `mustBeDefaultInitializedAtRuntime`). Zero emits `fir.zero_bits`. Hex splats the byte pattern via `APInt::getSplat` at the natural bit width of the element type (`fpTy.getWidth()` for FP, `intTy.getWidth()` for integers) and bitcasts to the FP type for real types; for COMPLEX the splat is applied to each part and combined with `complex.create` — REAL(16)/COMPLEX(16) work without special-casing via a natural i128→f128 bitcast. NaN/SNaN calls `APFloat::getQNaN`/`getSNaN` with all-ones mantissa payload and negative sign using `fpTy.getFloatSemantics()` (IEEEsingle/IEEEdouble/IEEEquad); integer types in nan/snan mode fall back to a hardcoded `0xAA` byte-splat. `fir.logical` and CHARACTER fall back to `fir.zero_bits` for all non-zero modes. Derived types use a single `fir.zero_bits` store for zero mode, and walk fields via `fir.coordinate_of` for all other modes. Arrays use `fir.insert_on_range`. |
| `flang/tools/bbc/bbc.cpp` | Adds matching `cl::opt` entries for standalone `bbc` testing |

## Tests

| File | Coverage |
|------|----------|
| `flang/test/Driver/finit-local.f90` | Driver-level pass-through checks |
| `flang/test/Lower/finit-local.f90` | HLFIR lowering checks covering all types (INTEGER(1/2/4/8), REAL(4/8), COMPLEX(4/8), LOGICAL(1/4), CHARACTER, derived type, 1-D and 2-D arrays), all modes, and all exclusion cases |
| `flang/test/Lower/finit-local-f128.f90` | HLFIR lowering checks for REAL(16) and COMPLEX(16); requires `flang-supports-f128-math` |

>From 3d07ee2be553c71e7dd0591358f03220a97537ec 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] [flang] Add -finit-local= to initialize automatic variables

---
 clang/include/clang/Options/FlangOptions.td   |  18 +-
 clang/lib/Driver/ToolChains/Flang.cpp         |   3 +-
 flang/docs/ReleaseNotes.md                    |   5 +
 flang/include/flang/Lower/LoweringOptions.def |   6 +
 flang/include/flang/Lower/LoweringOptions.h   |  23 +
 flang/lib/Frontend/CompilerInvocation.cpp     |  32 ++
 flang/lib/Lower/ConvertVariable.cpp           | 239 +++++++++
 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                       |  38 ++
 11 files changed, 955 insertions(+), 2 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..2928b62f99d20 100644
--- a/clang/lib/Driver/ToolChains/Flang.cpp
+++ b/clang/lib/Driver/ToolChains/Flang.cpp
@@ -359,7 +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_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,
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..14f38ea77b34a 100644
--- a/flang/include/flang/Lower/LoweringOptions.h
+++ b/flang/include/flang/Lower/LoweringOptions.h
@@ -16,10 +16,22 @@
 #define FLANG_LOWER_LOWERINGOPTIONS_H
 
 #include "flang/Support/FPMaxminBehavior.h"
+#include <cstdint>
 #include "flang/Support/MathOptionsBase.h"
 
 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 +64,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..d21b8f55fef74 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -1755,6 +1755,38 @@ 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..2ebd03dd86149 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -11,6 +11,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "flang/Lower/ConvertVariable.h"
+#include "flang/Lower/LoweringOptions.h"
 #include "flang/Lower/AbstractConverter.h"
 #include "flang/Lower/Allocatable.h"
 #include "flang/Lower/BoxAnalyzer.h"
@@ -47,6 +48,9 @@
 #include "flang/Semantics/type.h"
 #include "mlir/Dialect/OpenACC/OpenACC.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/APFloat.h"
+#include "llvm/ADT/APInt.h"
+#include "mlir/Dialect/Complex/IR/Complex.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
 #include <optional>
@@ -1250,6 +1254,239 @@ 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 +1510,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..9f2225ab28f6f 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -272,6 +272,19 @@ 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 +514,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);



More information about the flang-commits mailing list