[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:29:31 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 cd5ab9a859c3e0c06d57869cce166cbb19dd23ca 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.
- Fix hex mode for derived types: byte-loop over the full struct size
(from fir::getTypeSizeAndAlignmentOrCrash) to cover typed fields and
padding bytes.
- Exclude Cray pointees from initialization: their FIR base is a
pointer-box descriptor, not value storage.
- Fix CUDA device-context check: per-thread locals in device kernels
(implicit device attribute set by SetImplicitCUDADevice) are
stack-allocated and should be initialized; host-side device variables
should not.
- Add tests: finit-local-cray-pointee.f90, finit-local-charN-exec.f90,
finit-local-array-exec.f90; update finit-local.f90 (add test_charN,
fix singleton char type spelling !fir.char<1>) and finit-local-array-llvm.f90.
---
.../test/Driver/finit-local-array-exec.f90 | 108 +++++++++++
.../test/Driver/finit-local-charN-exec.f90 | 30 +++
flang/docs/ReleaseNotes.md | 4 +
flang/include/flang/Lower/LoweringOptions.h | 13 +-
flang/lib/Lower/ConvertVariable.cpp | 182 ++++++++++++++++--
flang/test/Lower/CUDA/finit-local-cuda.cuf | 3 +-
flang/test/Lower/finit-local-array-llvm.f90 | 105 +++++++---
flang/test/Lower/finit-local-cray-pointee.f90 | 30 +++
flang/test/Lower/finit-local.f90 | 56 +++++-
9 files changed, 467 insertions(+), 64 deletions(-)
create mode 100644 flang-rt/test/Driver/finit-local-array-exec.f90
create mode 100644 flang-rt/test/Driver/finit-local-charN-exec.f90
create mode 100644 flang/test/Lower/finit-local-cray-pointee.f90
diff --git a/flang-rt/test/Driver/finit-local-array-exec.f90 b/flang-rt/test/Driver/finit-local-array-exec.f90
new file mode 100644
index 0000000000000..8f7fc449e3ce7
--- /dev/null
+++ b/flang-rt/test/Driver/finit-local-array-exec.f90
@@ -0,0 +1,108 @@
+! Executable regression test for -finit-local= array initialization.
+! Verifies that every element of static arrays (1-D, 2-D, 3-D) and every
+! field of every element in arrays of derived types are initialized to the
+! requested bit pattern. A previous bug caused the flat loop to use an
+! out-of-bounds GEP for rank > 1, so only the first row was written.
+!
+! UNSUPPORTED: offload-cuda
+!
+! RUN: %flang %isysroot -L"%libdir" -finit-local=0xAA %s -o %t
+! RUN: env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:%libdir" %t
+
+program test_finit_local_array
+ implicit none
+ integer(4), parameter :: EXPECTED = int(z'AAAAAAAA')
+
+ ! 1-D integer array x(4) -- 4 elements
+ call check_1d()
+
+ ! 2-D integer array x(3,4) -- 12 elements
+ call check_2d()
+
+ ! 3-D integer array x(2,3,4) -- 24 elements
+ call check_3d()
+
+ ! 1-D array of derived type x(2) -- 2 elements, 2 fields each
+ call check_struct_1d()
+
+ ! 2-D array of derived type x(2,3) -- 6 elements, 2 fields each
+ call check_struct_2d()
+
+contains
+
+ subroutine check_1d()
+ integer(4) :: x(4)
+ integer :: i
+ do i = 1, 4
+ if (x(i) /= EXPECTED) then
+ print *, "FAIL check_1d: element", i, "=", x(i), "expected", EXPECTED
+ error stop 1
+ end if
+ end do
+ end subroutine
+
+ subroutine check_2d()
+ integer(4) :: x(3,4)
+ integer :: i, j
+ do j = 1, 4
+ do i = 1, 3
+ if (x(i,j) /= EXPECTED) then
+ print *, "FAIL check_2d: element (", i, ",", j, ")=", x(i,j), &
+ "expected", EXPECTED
+ error stop 1
+ end if
+ end do
+ end do
+ end subroutine
+
+ subroutine check_3d()
+ integer(4) :: x(2,3,4)
+ integer :: i, j, k
+ do k = 1, 4
+ do j = 1, 3
+ do i = 1, 2
+ if (x(i,j,k) /= EXPECTED) then
+ print *, "FAIL check_3d: element (", i, ",", j, ",", k, ")=", &
+ x(i,j,k), "expected", EXPECTED
+ error stop 1
+ end if
+ end do
+ end do
+ end do
+ end subroutine
+
+ subroutine check_struct_1d()
+ type :: t
+ integer(4) :: a
+ integer(4) :: b
+ end type
+ type(t) :: x(2)
+ integer :: i
+ do i = 1, 2
+ if (x(i)%a /= EXPECTED .or. x(i)%b /= EXPECTED) then
+ print *, "FAIL check_struct_1d: element", i, &
+ "a=", x(i)%a, "b=", x(i)%b, "expected", EXPECTED
+ error stop 1
+ end if
+ end do
+ end subroutine
+
+ subroutine check_struct_2d()
+ type :: t
+ integer(4) :: a
+ integer(4) :: b
+ end type
+ type(t) :: x(2,3)
+ integer :: i, j
+ do j = 1, 3
+ do i = 1, 2
+ if (x(i,j)%a /= EXPECTED .or. x(i,j)%b /= EXPECTED) then
+ print *, "FAIL check_struct_2d: element (", i, ",", j, &
+ ") a=", x(i,j)%a, "b=", x(i,j)%b, "expected", EXPECTED
+ error stop 1
+ end if
+ end do
+ end do
+ end subroutine
+
+end program
diff --git a/flang-rt/test/Driver/finit-local-charN-exec.f90 b/flang-rt/test/Driver/finit-local-charN-exec.f90
new file mode 100644
index 0000000000000..7539c4051c6de
--- /dev/null
+++ b/flang-rt/test/Driver/finit-local-charN-exec.f90
@@ -0,0 +1,30 @@
+! Executable regression test for -finit-local= with runtime-length CHARACTER.
+! Verifies that every byte of a character(n) local is initialized to the
+! requested pattern, and that the empty-string (n=0) case runs without error.
+!
+! UNSUPPORTED: offload-cuda
+!
+! RUN: %flang %isysroot -L"%libdir" -finit-local=0xAA %s -o %t
+! RUN: env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:%libdir" %t
+
+program test_finit_local_charN
+ implicit none
+
+ call check_charN(5) ! n > 0: all bytes must be 0xAA
+ call check_charN(1) ! single byte
+ call check_charN(0) ! empty string: no bytes to check, must not crash
+end program
+
+subroutine check_charN(n)
+ integer, intent(in) :: n
+ character(n) :: x
+ integer :: i
+ ! Inspect each byte through an equivalenced integer array.
+ ! For n == 0 the loop body is never entered.
+ do i = 1, n
+ if (ichar(x(i:i)) /= int(z'AA')) then
+ write(*,*) 'FAIL: byte', i, 'of character(', n, ') =', ichar(x(i:i))
+ stop 1
+ end if
+ end do
+end subroutine
diff --git a/flang/docs/ReleaseNotes.md b/flang/docs/ReleaseNotes.md
index 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