[flang-commits] [clang] [flang] [llvm] [flang] Add -finit-local= to initialize automatic variables (PR #216164)
Daniel Chen via flang-commits
flang-commits at lists.llvm.org
Tue Sep 1 12:47:55 PDT 2026
https://github.com/DanielCChen updated https://github.com/llvm/llvm-project/pull/216164
>From 7854a2f8d0ce5d9c4054e7c96c46b9f5fcc7a4f4 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Thu, 13 Aug 2026 15:30:12 -0400
Subject: [PATCH 01/10] [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 7a3dfd84fd4e7..ccb41daa2b866 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>;
@@ -397,6 +396,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 7562206f93438..1c1005baddd6a 100644
--- a/clang/lib/Driver/ToolChains/Flang.cpp
+++ b/clang/lib/Driver/ToolChains/Flang.cpp
@@ -369,8 +369,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 0815355a86844..ba16905d07c77 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -67,6 +67,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 43986666cc734..f3c1f2496258f 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -1779,6 +1779,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 eddc0415b4257..e1f8fa8c506cd 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -276,6 +276,20 @@ static llvm::cl::opt<bool> initGlobalZero(
llvm::cl::desc("Zero initialize globals without default initialization"),
llvm::cl::init(true));
+static llvm::cl::opt<std::string>
+ initLocalMode("finit-local",
+ llvm::cl::desc("Initialize local variables without explicit "
+ "or default initialization. "
+ "Accepts: zero, nan, snan, or 0x<hex-byte>."),
+ llvm::cl::init(""));
+
+static llvm::cl::opt<bool> initLocalZero(
+ "finit-local-zero",
+ llvm::cl::desc(
+ "Zero-initialize local variables without explicit or default "
+ "initialization (alias for -finit-local=zero)"),
+ llvm::cl::init(false));
+
static llvm::cl::opt<bool>
reallocateLHS("frealloc-lhs",
llvm::cl::desc("Follow Fortran 2003 rules for (re)allocating "
@@ -505,6 +519,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 3eee82f969d42d27d10543680a301af26fe87dce Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Fri, 14 Aug 2026 10:34:37 -0400
Subject: [PATCH 02/10] [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 ccb41daa2b866..f3ae4409e1a28 100644
--- a/clang/include/clang/Options/FlangOptions.td
+++ b/clang/include/clang/Options/FlangOptions.td
@@ -396,10 +396,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 ba16905d07c77..075bf24eb7a15 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -67,7 +67,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 a660331bb3f4653f247ffd406bf3f657375c6807 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Fri, 14 Aug 2026 13:14:20 -0400
Subject: [PATCH 03/10] [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 e1f8fa8c506cd..2c127872e7ab5 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -519,11 +519,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") {
@@ -539,11 +536,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 622e015a9d579530d308f21b01f16958c1f242ab Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Sat, 15 Aug 2026 11:05:00 -0400
Subject: [PATCH 04/10] [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 2c127872e7ab5..fea83ddb01c84 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -521,8 +521,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 869dca795b4b9084d980c34b0027674ff22e4bb0 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Sat, 15 Aug 2026 22:05:18 -0400
Subject: [PATCH 05/10] [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 fea83ddb01c84..eb543524f19f5 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -520,8 +520,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";
@@ -547,11 +553,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 0c26d58453399bb87a0c1bb66232ce2df3978a50 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Sun, 16 Aug 2026 23:25:20 -0400
Subject: [PATCH 06/10] [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 075bf24eb7a15..bb7c2f8ebeaad 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -71,6 +71,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>
! ---------------------------------------------------------------------------
>From 7cbeede9fbff00ec533854e18c695c21b5b3065a Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Mon, 17 Aug 2026 00:33:43 -0400
Subject: [PATCH 07/10] To remove the support for nan and snan.
---
clang/include/clang/Options/FlangOptions.td | 2 +-
flang/docs/ReleaseNotes.md | 7 +-
flang/include/flang/Lower/LoweringOptions.h | 9 +-
flang/lib/Frontend/CompilerInvocation.cpp | 6 +-
flang/lib/Lower/ConvertVariable.cpp | 121 +++++-------------
flang/test/Driver/finit-local.f90 | 6 +-
flang/test/Lower/finit-local-array-llvm.f90 | 27 +---
flang/test/Lower/finit-local-f128.f90 | 16 ---
flang/test/Lower/finit-local-logical-llvm.f90 | 7 -
flang/test/Lower/finit-local.f90 | 109 +---------------
flang/tools/bbc/bbc.cpp | 6 +-
11 files changed, 45 insertions(+), 271 deletions(-)
diff --git a/clang/include/clang/Options/FlangOptions.td b/clang/include/clang/Options/FlangOptions.td
index f3ae4409e1a28..94ad9ad2000bb 100644
--- a/clang/include/clang/Options/FlangOptions.td
+++ b/clang/include/clang/Options/FlangOptions.td
@@ -400,7 +400,7 @@ def finit_local_EQ : Joined<["-"], "finit-local=">,
Group<f_Group>,
Visibility<[FC1Option, FlangOption]>,
HelpText<"Initialize local variables without explicit or default initialization. "
- "Accepts: zero, nan, snan, or 0x<hex-byte>.">;
+ "Accepts: zero or 0x<hex-byte>.">;
def finit_local_zero : Flag<["-"], "finit-local-zero">,
Group<f_Group>,
diff --git a/flang/docs/ReleaseNotes.md b/flang/docs/ReleaseNotes.md
index bb7c2f8ebeaad..8cd34b7c9307c 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -69,12 +69,9 @@ page](https://llvm.org/releases/).
- Added `-finit-local=<val>` to initialize automatic local
variables that have no explicit or default initialization. Accepted values
- are `zero`, `nan`, `snan`, and `0x<hex-byte>` (e.g. `0xAA`). The gfortran
+ are `zero` and `0x<hex-byte>` (e.g. `0xAA`). The gfortran
compatibility alias `-finit-local-zero` is equivalent to `-finit-local=zero`.
- `zero` and `0x<hex-byte>` fill every storage byte including struct padding.
- The `nan` and `snan` modes currently initialize each typed field individually;
- padding bytes inside derived-type variables are not yet initialized for
- those modes.
+ Both modes fill every storage byte including struct padding.
## Windows Support
diff --git a/flang/include/flang/Lower/LoweringOptions.h b/flang/include/flang/Lower/LoweringOptions.h
index 9463ba0258087..63693c5806f0e 100644
--- a/flang/include/flang/Lower/LoweringOptions.h
+++ b/flang/include/flang/Lower/LoweringOptions.h
@@ -25,15 +25,12 @@ namespace Fortran::lower {
/// or default initialization, selected via -finit-local=.
///
/// Zero and Hex fill every storage byte including struct padding and
-/// CHARACTER storage. QNaN and SNaN initialize each typed field
-/// individually; struct padding is not yet covered for those modes
-/// (TODO: use whole-struct memset once PR #159788 lands).
+/// CHARACTER storage.
enum class InitLocalKind {
Off, ///< No initialization (default)
Zero, ///< Fill with 0x00 bytes (all types, all storage including padding)
- Hex, ///< Fill with a user-supplied byte pattern (all types, all storage including padding)
- QNaN, ///< Quiet NaN for FP fields; 0xAA byte-splat for non-FP fields (struct padding not yet covered)
- SNaN, ///< Signalling NaN for FP fields; 0xAA byte-splat for non-FP fields (struct padding not yet covered)
+ Hex, ///< Fill with a user-supplied byte pattern (all types, all storage
+ ///< including padding)
};
class LoweringOptionsBase {
diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp
index f3c1f2496258f..b214a7ee2d157 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -1779,7 +1779,7 @@ bool CompilerInvocation::createFromArgs(
else
invoc.loweringOpts.setInitGlobalZero(false);
- // -finit-local=<zero|nan|snan|0x<hex>> and -finit-local-zero
+ // -finit-local=<zero|0x<hex>> and -finit-local-zero
// (-finit-local-zero is an alias that the driver already expands to
// -finit-local=zero, so we only need to handle OPT_finit_local_EQ here.)
if (const llvm::opt::Arg *a =
@@ -1787,10 +1787,6 @@ bool CompilerInvocation::createFromArgs(
llvm::StringRef val = a->getValue();
if (val == "zero") {
invoc.loweringOpts.setInitLocalMode(Fortran::lower::InitLocalKind::Zero);
- } else if (val == "nan") {
- invoc.loweringOpts.setInitLocalMode(Fortran::lower::InitLocalKind::QNaN);
- } else if (val == "snan") {
- invoc.loweringOpts.setInitLocalMode(Fortran::lower::InitLocalKind::SNaN);
} else if (val.starts_with("0x") || val.starts_with("0X")) {
unsigned long long hexVal = 0;
if (val.drop_front(2).getAsInteger(16, hexVal) || hexVal > 0xFF) {
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 7460f98126956..c093ff258f729 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -48,7 +48,6 @@
#include "flang/Semantics/type.h"
#include "mlir/Dialect/Complex/IR/Complex.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
-#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/CommandLine.h"
@@ -1303,8 +1302,9 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
case Fortran::common::CUDADataAttr::UseDevice:
return false;
case Fortran::common::CUDADataAttr::Device:
- // In a device subprogram the attribute is implicit (SetImplicitCUDADevice)
- // and the variable is a thread-local stack allocation -- initialize it.
+ // In a device subprogram the attribute is implicit
+ // (SetImplicitCUDADevice) and the variable is a thread-local stack
+ // allocation -- initialize it.
if (!Fortran::semantics::IsCUDADeviceContext(&sym.owner()))
return false;
break;
@@ -1355,40 +1355,21 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
return makeIntCst(logTy.getFKind() * 8);
}
- // CHARACTER with hex mode is handled upstream in genInitLocalStore before
- // this function is reached. For zero/nan/snan, fir.zero_bits is correct
- // (zero fills all bytes; nan/snan have no meaningful character value).
+ // Fallback (e.g. fir.char reached via an unexpected path): zero-initialise.
return fir::ZeroOp::create(builder, loc, eleTy);
}
-/// Build a quiet or signalling NaN constant of the given FP type.
-/// The payload is all-ones (matching clang's initializationPatternFor() and
-/// the RFC spec), and the sign bit is set (negative NaN).
-static mlir::Value genFPNaNInit(fir::FirOpBuilder &builder, mlir::Location loc,
- mlir::FloatType fpTy, bool isSignalling) {
- const llvm::fltSemantics &sem = fpTy.getFloatSemantics();
- // All-ones payload (precision-1 mantissa bits), negative sign, per RFC.
- llvm::APInt payload = llvm::APInt::getAllOnes(sem.precision - 1);
- llvm::APFloat apf =
- isSignalling ? llvm::APFloat::getSNaN(sem, /*Negative=*/true, &payload)
- : llvm::APFloat::getQNaN(sem, /*Negative=*/true, &payload);
- return mlir::arith::ConstantFloatOp::create(builder, loc, fpTy, apf);
-}
-
/// Emit a store of the -finit-local= pattern for a single scalar address.
/// Fixed-length CHARACTER in hex mode: byte-loop over each code unit.
-/// Complex types get NaN on both parts; integer/logical non-FP types use a
-/// 0xAA byte-splat for nan/snan modes. LOGICAL stores via a bitcasted integer
-/// address to preserve the raw bit pattern past fir.convert normalization.
+/// LOGICAL stores via a bitcasted integer address to preserve the raw bit
+/// pattern past fir.convert normalization.
static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Type ty, mlir::Value addr,
Fortran::lower::InitLocalKind mode,
uint8_t hexByte) {
// Fixed-length CHARACTER: for hex mode emit a compile-time byte-loop so
- // every code-unit gets the requested pattern. Zero uses fir.zero_bits
- // (handled below). nan/snan fall through to the zero fallback in
- // genByteSplatInit -- those modes have no meaningful value for a character
- // storage unit anyway.
+ // every code-unit gets the requested pattern. Zero falls through to
+ // fir.zero_bits below.
if (auto charTy = mlir::dyn_cast<fir::CharacterType>(ty)) {
// CHARACTER(0) has zero-length storage -- nothing to initialize.
if (charTy.getLen() == 0)
@@ -1400,10 +1381,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
if (nUnits > 0) {
mlir::Type idxTy = builder.getIndexType();
- mlir::Type i8Ty = builder.getIntegerType(8);
- fir::CharacterType byteTy =
- fir::CharacterType::getSingleton(builder.getContext(),
- charTy.getFKind());
+ mlir::Type i8Ty = builder.getIntegerType(8);
+ fir::CharacterType byteTy = fir::CharacterType::getSingleton(
+ builder.getContext(), charTy.getFKind());
mlir::Type byteSeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, byteTy);
mlir::Value byteBase =
@@ -1418,9 +1398,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(loop.getBody());
mlir::Value iv = loop.getInductionVar();
- mlir::Value byteAddr = fir::CoordinateOp::create(
- builder, loc, builder.getRefType(byteTy), byteBase,
- mlir::ValueRange{iv});
+ mlir::Value byteAddr =
+ fir::CoordinateOp::create(builder, loc, builder.getRefType(byteTy),
+ byteBase, mlir::ValueRange{iv});
mlir::Value pat = builder.createIntegerConstant(
loc, i8Ty, static_cast<int64_t>(hexByte));
// Store via i8 pointer so the byte pattern is written verbatim
@@ -1434,8 +1414,6 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
}
}
mlir::Value val;
- auto fpTy = mlir::dyn_cast<mlir::FloatType>(ty);
- auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(ty);
switch (mode) {
case Fortran::lower::InitLocalKind::Zero:
val = fir::ZeroOp::create(builder, loc, ty);
@@ -1443,39 +1421,14 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
case Fortran::lower::InitLocalKind::Hex:
val = genByteSplatInit(builder, loc, ty, hexByte);
break;
- case Fortran::lower::InitLocalKind::QNaN:
- if (fpTy) {
- val = genFPNaNInit(builder, loc, fpTy, /*signalling=*/false);
- } else if (cplxTy) {
- auto partFpTy = mlir::cast<mlir::FloatType>(cplxTy.getElementType());
- mlir::Value nanPart =
- genFPNaNInit(builder, loc, partFpTy, /*signalling=*/false);
- val = mlir::complex::CreateOp::create(builder, loc, cplxTy, nanPart,
- nanPart);
- } else {
- val = genByteSplatInit(builder, loc, ty, 0xAA);
- }
- break;
- case Fortran::lower::InitLocalKind::SNaN:
- if (fpTy) {
- val = genFPNaNInit(builder, loc, fpTy, /*signalling=*/true);
- } else if (cplxTy) {
- auto partFpTy = mlir::cast<mlir::FloatType>(cplxTy.getElementType());
- mlir::Value nanPart =
- genFPNaNInit(builder, loc, partFpTy, /*signalling=*/true);
- val = mlir::complex::CreateOp::create(builder, loc, cplxTy, nanPart,
- nanPart);
- } else {
- val = genByteSplatInit(builder, loc, ty, 0xAA);
- }
- break;
default:
llvm_unreachable("unexpected InitLocalKind in genInitLocalStore");
}
- // For LOGICAL types, genByteSplatInit returns a raw integer to preserve
- // the bit pattern. Store it via a bitcasted address to avoid fir.convert
- // normalization (which would reduce any nonzero value to logical true).
- if (mode != Fortran::lower::InitLocalKind::Zero &&
+ // For LOGICAL in hex mode, genByteSplatInit returns a raw integer to
+ // preserve the bit pattern. Store it via a bitcasted address to avoid
+ // fir.convert normalization (which would reduce any nonzero value to
+ // logical true).
+ if (mode == Fortran::lower::InitLocalKind::Hex &&
mlir::isa<fir::LogicalType>(ty)) {
unsigned bits = mlir::cast<fir::LogicalType>(ty).getFKind() * 8;
mlir::Type intRefTy = builder.getRefType(builder.getIntegerType(bits));
@@ -1490,7 +1443,7 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
/// Arrays: all modes use a flat fir.do_loop + fir.coordinate_of over a
/// rank-1 view to avoid both the llvm.mlir.constant crash on non-zero
/// ArrayAttrs and the quadratic compile time of fir.insert_on_range.
-/// Derived types walk fields for nan/snan/hex. Scalars store directly.
+/// Derived types use a whole-struct byte-loop for hex. Scalars store directly.
static void genInitLocal(Fortran::lower::AbstractConverter &converter,
const Fortran::lower::pft::Variable &var,
Fortran::lower::SymMap &symMap) {
@@ -1560,9 +1513,6 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
// hex: byte-loop over the whole struct using the compile-time
// struct size from fir::getTypeSizeAndAlignmentOrCrash --
// also covers padding bytes, giving a uniform byte pattern.
- // nan/snan: field-by-field walk with typed NaN stores; padding
- // bytes between fields are not yet covered (TODO pending
- // PR #159788 which will provide memset infrastructure).
if (mode == Fortran::lower::InitLocalKind::Zero) {
fir::StoreOp::create(
builder, loc, fir::ZeroOp::create(builder, loc, recTy), addr);
@@ -1575,8 +1525,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::Type i8Ty = builder.getIntegerType(8);
mlir::Type i8SeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, i8Ty);
- mlir::Value byteBase = builder.createConvert(
- loc, builder.getRefType(i8SeqTy), addr);
+ mlir::Value byteBase =
+ builder.createConvert(loc, builder.getRefType(i8SeqTy), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last = builder.createIntegerConstant(
loc, idxTy, static_cast<int64_t>(byteSize) - 1);
@@ -1594,17 +1544,6 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
loc, i8Ty, static_cast<int64_t>(hexByte));
fir::StoreOp::create(builder, loc, pat, byteAddr);
}
- } else {
- // nan / snan: typed field stores; padding bytes not yet covered.
- for (auto [fieldName, fieldTy] : recTy.getTypeList()) {
- auto fieldIdx = fir::FieldIndexOp::create(
- builder, loc, fir::FieldType::get(recTy.getContext()),
- fieldName, recTy, mlir::ValueRange{});
- mlir::Value fieldAddr = fir::CoordinateOp::create(
- builder, loc, builder.getRefType(fieldTy), addr,
- mlir::ValueRange{fieldIdx});
- initAddr(fieldTy, fieldAddr);
- }
}
} else if (!mlir::isa<fir::BaseBoxType>(ty)) {
// Scalar (integer, real, complex, logical, character): delegate to
@@ -1631,11 +1570,10 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::Type idxTy = builder.getIndexType();
mlir::Value lenIdx = builder.createConvert(loc, idxTy, rtLen);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
- mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+ mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
// last = lenIdx - 1; the fir.do_loop trip count is lenIdx so the
// loop body is skipped entirely when lenIdx == 0 (empty string).
- mlir::Value last =
- mlir::arith::SubIOp::create(builder, loc, lenIdx, one);
+ mlir::Value last = mlir::arith::SubIOp::create(builder, loc, lenIdx, one);
auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
/*unordered=*/false,
/*finalCount=*/false);
@@ -1644,16 +1582,15 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::Value iv = loop.getInductionVar();
// Treat the storage as an array of singleton characters so that
// fir.coordinate_of advances by one code-unit per step.
- fir::CharacterType byteTy =
- fir::CharacterType::getSingleton(builder.getContext(),
- charTy.getFKind());
+ fir::CharacterType byteTy = fir::CharacterType::getSingleton(
+ builder.getContext(), charTy.getFKind());
mlir::Type byteSeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, byteTy);
mlir::Value byteBase =
builder.createConvert(loc, builder.getRefType(byteSeqTy), base);
- mlir::Value byteAddr = fir::CoordinateOp::create(
- builder, loc, builder.getRefType(byteTy), byteBase,
- mlir::ValueRange{iv});
+ mlir::Value byteAddr =
+ fir::CoordinateOp::create(builder, loc, builder.getRefType(byteTy),
+ byteBase, mlir::ValueRange{iv});
genInitLocalStore(builder, loc, byteTy, byteAddr, mode, hexByte);
return;
}
diff --git a/flang/test/Driver/finit-local.f90 b/flang/test/Driver/finit-local.f90
index 1d39a83b4ce01..d4199073abb3d 100644
--- a/flang/test/Driver/finit-local.f90
+++ b/flang/test/Driver/finit-local.f90
@@ -1,10 +1,8 @@
! Tests that -finit-local= and -finit-local-zero are accepted by the Flang
! driver and forwarded correctly to -fc1.
-! --- Valid values: zero, nan, snan, hex byte ---
+! --- Valid values: zero, hex byte ---
! RUN: %flang -### -S -finit-local=zero %s 2>&1 | FileCheck --check-prefix=ZERO %s
-! RUN: %flang -### -S -finit-local=nan %s 2>&1 | FileCheck --check-prefix=NAN %s
-! RUN: %flang -### -S -finit-local=snan %s 2>&1 | FileCheck --check-prefix=SNAN %s
! RUN: %flang -### -S -finit-local=0xAA %s 2>&1 | FileCheck --check-prefix=HEX %s
! RUN: %flang -### -S -finit-local=0xff %s 2>&1 | FileCheck --check-prefix=HEX2 %s
@@ -14,8 +12,6 @@
! RUN: not %flang_fc1 -emit-hlfir -finit-local=bogus %s 2>&1 | FileCheck --check-prefix=ERR %s
! ZERO: "-fc1"{{.*}} "-finit-local=zero"
-! NAN: "-fc1"{{.*}} "-finit-local=nan"
-! SNAN: "-fc1"{{.*}} "-finit-local=snan"
! HEX: "-fc1"{{.*}} "-finit-local=0xAA"
! HEX2: "-fc1"{{.*}} "-finit-local=0xff"
! ERR: error: invalid value 'bogus' in '-finit-local=bogus'
diff --git a/flang/test/Lower/finit-local-array-llvm.f90 b/flang/test/Lower/finit-local-array-llvm.f90
index 16c4338ea67ef..17290d6e9e370 100644
--- a/flang/test/Lower/finit-local-array-llvm.f90
+++ b/flang/test/Lower/finit-local-array-llvm.f90
@@ -4,7 +4,7 @@
! does not accept ArrayAttr of non-zero scalars. The fix uses a do_loop +
! coordinate_of instead, which lowers correctly through to LLVM IR.
!
-! The HEX checks verify:
+! The HEX and ZERO checks verify:
! - the loop trip counter PHI starts at the expected element count,
! - the GEP uses the element type as the unit stride (so all elements are
! reached, not just element 0), and
@@ -12,7 +12,6 @@
! These three properties together prove that every element is initialized.
!
! RUN: %flang_fc1 -emit-llvm -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
-! RUN: %flang_fc1 -emit-llvm -finit-local=nan %s -o - | FileCheck --check-prefix=NAN %s
! RUN: %flang_fc1 -emit-llvm -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
! ---------------------------------------------------------------------------
@@ -29,10 +28,6 @@ subroutine test_int_array(res)
! HEX: store i32 -1431655766,
! HEX-NOT: store i32 0,
-! NAN-LABEL: define {{.*}}@{{.*}}test_int_array{{.*}}(
-! NAN: phi i64 [ {{.*}}, {{.*}} ], [ 4, %{{.*}} ]
-! NAN: getelementptr i32, ptr {{.*}}, i64
-! NAN: store i32 -1431655766,
! ZERO-LABEL: define {{.*}}@{{.*}}test_int_array{{.*}}(
! ZERO: phi i64 [ {{.*}}, {{.*}} ], [ 4, %{{.*}} ]
@@ -56,10 +51,6 @@ subroutine test_array_in_struct(res)
! HEX: getelementptr i8, ptr {{.*}}, i64
! HEX: store i8 -86,
-! NAN-LABEL: define {{.*}}@{{.*}}test_array_in_struct{{.*}}(
-! NAN: phi i64 [ {{.*}}, {{.*}} ], [ 2, %{{.*}} ]
-! NAN: getelementptr i32, ptr {{.*}}, i64
-! NAN: store i32 -1431655766,
! ZERO-LABEL: define {{.*}}@{{.*}}test_array_in_struct{{.*}}(
! ZERO: store {{.*}} zeroinitializer,
@@ -78,10 +69,6 @@ subroutine test_int_array_2d(res)
! HEX: store i32 -1431655766,
! HEX-NOT: store i32 0,
-! NAN-LABEL: define {{.*}}@{{.*}}test_int_array_2d{{.*}}(
-! NAN: phi i64 [ {{.*}}, {{.*}} ], [ 12, %{{.*}} ]
-! NAN: getelementptr i32, ptr {{.*}}, i64
-! NAN: store i32 -1431655766,
! ZERO-LABEL: define {{.*}}@{{.*}}test_int_array_2d{{.*}}(
! ZERO: phi i64 [ {{.*}}, {{.*}} ], [ 12, %{{.*}} ]
@@ -102,10 +89,6 @@ subroutine test_int_array_3d(res)
! HEX: store i32 -1431655766,
! HEX-NOT: store i32 0,
-! NAN-LABEL: define {{.*}}@{{.*}}test_int_array_3d{{.*}}(
-! NAN: phi i64 [ {{.*}}, {{.*}} ], [ 24, %{{.*}} ]
-! NAN: getelementptr i32, ptr {{.*}}, i64
-! NAN: store i32 -1431655766,
! ZERO-LABEL: define {{.*}}@{{.*}}test_int_array_3d{{.*}}(
! ZERO: phi i64 [ {{.*}}, {{.*}} ], [ 24, %{{.*}} ]
@@ -133,10 +116,6 @@ subroutine test_array_of_struct(res)
! HEX: store i8 -86,
! HEX-NOT: store {{.*}} zeroinitializer,
-! NAN-LABEL: define {{.*}}@{{.*}}test_array_of_struct{{.*}}(
-! NAN: phi i64 [ {{.*}}, {{.*}} ], [ 2, %{{.*}} ]
-! NAN: getelementptr %{{.*}}t, ptr {{.*}}, i64
-! NAN: store i32 -1431655766,
! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_struct{{.*}}(
! ZERO: phi i64 [ {{.*}}, {{.*}} ], [ 2, %{{.*}} ]
@@ -164,10 +143,6 @@ subroutine test_array_of_struct_2d(res)
! HEX: store i8 -86,
! HEX-NOT: store {{.*}} zeroinitializer,
-! NAN-LABEL: define {{.*}}@{{.*}}test_array_of_struct_2d{{.*}}(
-! NAN: phi i64 [ {{.*}}, {{.*}} ], [ 6, %{{.*}} ]
-! NAN: getelementptr %{{.*}}t, ptr {{.*}}, i64
-! NAN: store i32 -1431655766,
! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_struct_2d{{.*}}(
! ZERO: phi i64 [ {{.*}}, {{.*}} ], [ 6, %{{.*}} ]
diff --git a/flang/test/Lower/finit-local-f128.f90 b/flang/test/Lower/finit-local-f128.f90
index 937b2e86bb649..a0449f4b56a65 100644
--- a/flang/test/Lower/finit-local-f128.f90
+++ b/flang/test/Lower/finit-local-f128.f90
@@ -4,8 +4,6 @@
! REQUIRES: flang-supports-f128-math
!
! RUN: %flang_fc1 -emit-hlfir -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
-! RUN: %flang_fc1 -emit-hlfir -finit-local=nan %s -o - | FileCheck --check-prefix=NAN %s
-! RUN: %flang_fc1 -emit-hlfir -finit-local=snan %s -o - | FileCheck --check-prefix=SNAN %s
! RUN: %flang_fc1 -emit-hlfir -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
! ---------------------------------------------------------------------------
@@ -21,13 +19,7 @@ subroutine test_real16(res)
! ZERO: fir.zero_bits f128
! ZERO: fir.store {{.*}} : !fir.ref<f128>
-! NAN-LABEL: func.func @_QPtest_real16
-! NAN: arith.constant {{.*}} : f128
-! NAN: fir.store {{.*}} : !fir.ref<f128>
-! SNAN-LABEL: func.func @_QPtest_real16
-! SNAN: arith.constant {{.*}} : f128
-! SNAN: fir.store {{.*}} : !fir.ref<f128>
! HEX-LABEL: func.func @_QPtest_real16
! HEX: arith.constant -113427455640312821154458202477256070486 : i128
@@ -47,15 +39,7 @@ subroutine test_complex16(res)
! ZERO: fir.zero_bits complex<f128>
! ZERO: fir.store {{.*}} : !fir.ref<complex<f128>>
-! NAN-LABEL: func.func @_QPtest_complex16
-! NAN: arith.constant {{.*}} : f128
-! NAN: complex.create {{.*}}, {{.*}} : complex<f128>
-! NAN: fir.store {{.*}} : !fir.ref<complex<f128>>
-! SNAN-LABEL: func.func @_QPtest_complex16
-! SNAN: arith.constant {{.*}} : f128
-! SNAN: complex.create {{.*}}, {{.*}} : complex<f128>
-! SNAN: fir.store {{.*}} : !fir.ref<complex<f128>>
! HEX-LABEL: func.func @_QPtest_complex16
! HEX: arith.constant -113427455640312821154458202477256070486 : i128
diff --git a/flang/test/Lower/finit-local-logical-llvm.f90 b/flang/test/Lower/finit-local-logical-llvm.f90
index 6f41b78883b59..7adc55b33e363 100644
--- a/flang/test/Lower/finit-local-logical-llvm.f90
+++ b/flang/test/Lower/finit-local-logical-llvm.f90
@@ -4,7 +4,6 @@
! bitcasted integer address instead so the bit pattern is preserved.
!
! RUN: %flang_fc1 -emit-llvm -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
-! RUN: %flang_fc1 -emit-llvm -finit-local=nan %s -o - | FileCheck --check-prefix=NAN %s
! RUN: %flang_fc1 -emit-llvm -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
! ---------------------------------------------------------------------------
@@ -19,9 +18,6 @@ subroutine test_logical1(res)
! HEX: store i8 -86,
! HEX-NOT: store i8 1,
-! NAN-LABEL: define {{.*}}@{{.*}}test_logical1{{.*}}(
-! NAN: store i8 -86,
-! NAN-NOT: store i8 1,
! ZERO-LABEL: define {{.*}}@{{.*}}test_logical1{{.*}}(
! ZERO: store i8 0,
@@ -38,9 +34,6 @@ subroutine test_logical4(res)
! HEX: store i32 -1431655766,
! HEX-NOT: store i32 1,
-! NAN-LABEL: define {{.*}}@{{.*}}test_logical4{{.*}}(
-! NAN: store i32 -1431655766,
-! NAN-NOT: store i32 1,
! ZERO-LABEL: define {{.*}}@{{.*}}test_logical4{{.*}}(
! ZERO: store i32 0,
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 23d92a3a221d4..6835b6a8313a0 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -9,11 +9,9 @@
! Derived type (struct with plain-int and real components)
! Arrays of integer and real
!
-! Modes exercised: zero, nan, snan, 0xAA (hex), and off (no flag).
+! Modes exercised: zero, 0xAA (hex), and off (no flag).
!
! RUN: bbc -emit-hlfir -finit-local=zero -o - %s | FileCheck --check-prefix=ZERO %s
-! RUN: bbc -emit-hlfir -finit-local=nan -o - %s | FileCheck --check-prefix=NAN %s
-! RUN: bbc -emit-hlfir -finit-local=snan -o - %s | FileCheck --check-prefix=SNAN %s
! RUN: bbc -emit-hlfir -finit-local=0xAA -o - %s | FileCheck --check-prefix=HEX %s
! RUN: bbc -emit-hlfir -o - %s | FileCheck --check-prefix=OFF %s
! RUN: bbc -emit-hlfir -finit-local-zero -o - %s | FileCheck --check-prefix=ZERO %s
@@ -38,9 +36,6 @@ subroutine test_int1(res)
! ZERO: fir.zero_bits i8
! ZERO: fir.store {{.*}} : !fir.ref<i8>
-! NAN-LABEL: func.func @_QPtest_int1
-! NAN: arith.constant -86 : i8
-! NAN: fir.store {{.*}} : !fir.ref<i8>
! HEX-LABEL: func.func @_QPtest_int1
! HEX: arith.constant -86 : i8
@@ -60,9 +55,6 @@ subroutine test_int2(res)
! ZERO-LABEL: func.func @_QPtest_int2
! ZERO: fir.zero_bits i16
-! NAN-LABEL: func.func @_QPtest_int2
-! NAN: arith.constant -21846 : i16
-! NAN: fir.store {{.*}} : !fir.ref<i16>
! HEX-LABEL: func.func @_QPtest_int2
! HEX: arith.constant -21846 : i16
@@ -79,13 +71,7 @@ subroutine test_int4(res)
! ZERO-LABEL: func.func @_QPtest_int4
! ZERO: fir.zero_bits i32
-! NAN-LABEL: func.func @_QPtest_int4
-! NAN: arith.constant -1431655766 : i32
-! NAN: fir.store {{.*}} : !fir.ref<i32>
-! SNAN-LABEL: func.func @_QPtest_int4
-! SNAN: arith.constant -1431655766 : i32
-! SNAN: fir.store {{.*}} : !fir.ref<i32>
! HEX-LABEL: func.func @_QPtest_int4
! HEX: arith.constant -1431655766 : i32
@@ -105,16 +91,13 @@ subroutine test_int8(res)
! ZERO-LABEL: func.func @_QPtest_int8
! ZERO: fir.zero_bits i64
-! NAN-LABEL: func.func @_QPtest_int8
-! NAN: arith.constant -6148914691236517206 : i64
-! NAN: fir.store {{.*}} : !fir.ref<i64>
! HEX-LABEL: func.func @_QPtest_int8
! HEX: arith.constant -6148914691236517206 : i64
! HEX: fir.store {{.*}} : !fir.ref<i64>
! ---------------------------------------------------------------------------
-! REAL(4) -- zero fills with fir.zero_bits; nan/snan with FP constant; hex bitcast
+! REAL(4) -- zero: fir.zero_bits; hex: bitcast from integer splat
! ---------------------------------------------------------------------------
subroutine test_real4(res)
real(4) :: res
@@ -125,13 +108,7 @@ subroutine test_real4(res)
! ZERO: fir.zero_bits f32
! ZERO: fir.store {{.*}} : !fir.ref<f32>
-! NAN-LABEL: func.func @_QPtest_real4
-! NAN: arith.constant {{.*}} : f32
-! NAN: fir.store {{.*}} : !fir.ref<f32>
-! SNAN-LABEL: func.func @_QPtest_real4
-! SNAN: arith.constant {{.*}} : f32
-! SNAN: fir.store {{.*}} : !fir.ref<f32>
! HEX-LABEL: func.func @_QPtest_real4
! HEX: arith.constant -1431655766 : i32
@@ -153,13 +130,7 @@ subroutine test_real8(res)
! ZERO: fir.zero_bits f64
! ZERO: fir.store {{.*}} : !fir.ref<f64>
-! NAN-LABEL: func.func @_QPtest_real8
-! NAN: arith.constant {{.*}} : f64
-! NAN: fir.store {{.*}} : !fir.ref<f64>
-! SNAN-LABEL: func.func @_QPtest_real8
-! SNAN: arith.constant {{.*}} : f64
-! SNAN: fir.store {{.*}} : !fir.ref<f64>
! HEX-LABEL: func.func @_QPtest_real8
! HEX: arith.constant -6148914691236517206 : i64
@@ -168,7 +139,7 @@ subroutine test_real8(res)
! ---------------------------------------------------------------------------
! COMPLEX(4) -- two f32 parts; stored as complex<f32>
-! nan/snan: both parts get NaN; hex: both parts get bitcast pattern
+! hex: both parts get bitcast pattern
! ---------------------------------------------------------------------------
subroutine test_complex4(res)
complex(4) :: res
@@ -179,15 +150,7 @@ subroutine test_complex4(res)
! ZERO: fir.zero_bits complex<f32>
! ZERO: fir.store {{.*}} : !fir.ref<complex<f32>>
-! NAN-LABEL: func.func @_QPtest_complex4
-! NAN: arith.constant {{.*}} : f32
-! NAN: complex.create {{.*}} : complex<f32>
-! NAN: fir.store {{.*}} : !fir.ref<complex<f32>>
-! SNAN-LABEL: func.func @_QPtest_complex4
-! SNAN: arith.constant {{.*}} : f32
-! SNAN: complex.create {{.*}} : complex<f32>
-! SNAN: fir.store {{.*}} : !fir.ref<complex<f32>>
! HEX-LABEL: func.func @_QPtest_complex4
! HEX: arith.constant -1431655766 : i32
@@ -207,15 +170,7 @@ subroutine test_complex8(res)
! ZERO: fir.zero_bits complex<f64>
! ZERO: fir.store {{.*}} : !fir.ref<complex<f64>>
-! NAN-LABEL: func.func @_QPtest_complex8
-! NAN: arith.constant {{.*}} : f64
-! NAN: complex.create {{.*}} : complex<f64>
-! NAN: fir.store {{.*}} : !fir.ref<complex<f64>>
-! SNAN-LABEL: func.func @_QPtest_complex8
-! SNAN: arith.constant {{.*}} : f64
-! SNAN: complex.create {{.*}} : complex<f64>
-! SNAN: fir.store {{.*}} : !fir.ref<complex<f64>>
! HEX-LABEL: func.func @_QPtest_complex8
! HEX: arith.constant -6148914691236517206 : i64
@@ -234,10 +189,6 @@ subroutine test_logical1(res)
! ZERO-LABEL: func.func @_QPtest_logical1
! ZERO: fir.zero_bits !fir.logical<1>
-! NAN-LABEL: func.func @_QPtest_logical1
-! NAN: arith.constant -86 : i8
-! NAN: fir.convert {{.*}} : (!fir.ref<!fir.logical<1>>) -> !fir.ref<i8>
-! NAN: fir.store {{.*}} : !fir.ref<i8>
! HEX-LABEL: func.func @_QPtest_logical1
! HEX: arith.constant {{.*}} : i8
@@ -255,10 +206,6 @@ subroutine test_logical4(res)
! ZERO-LABEL: func.func @_QPtest_logical4
! ZERO: fir.zero_bits !fir.logical<4>
-! NAN-LABEL: func.func @_QPtest_logical4
-! NAN: arith.constant -1431655766 : i32
-! NAN: fir.convert {{.*}} : (!fir.ref<!fir.logical<4>>) -> !fir.ref<i32>
-! NAN: fir.store {{.*}} : !fir.ref<i32>
! HEX-LABEL: func.func @_QPtest_logical4
! HEX: arith.constant {{.*}} : i32
@@ -267,7 +214,7 @@ subroutine test_logical4(res)
! ---------------------------------------------------------------------------
! CHARACTER(10) -- fixed-length scalar.
-! zero/nan/snan: fir.zero_bits over the whole character type.
+! zero: fir.zero_bits over the whole character type.
! hex: byte-loop over 10 singleton code-units.
! ---------------------------------------------------------------------------
subroutine test_char10(res)
@@ -279,13 +226,7 @@ subroutine test_char10(res)
! ZERO: fir.zero_bits !fir.char<1,10>
! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
-! NAN-LABEL: func.func @_QPtest_char10
-! NAN: fir.zero_bits !fir.char<1,10>
-! NAN: fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
-! SNAN-LABEL: func.func @_QPtest_char10
-! SNAN: fir.zero_bits !fir.char<1,10>
-! SNAN: fir.store {{.*}} : !fir.ref<!fir.char<1,10>>
! HEX-LABEL: func.func @_QPtest_char10
! HEX: fir.do_loop
@@ -326,17 +267,7 @@ subroutine test_charN(res, n)
! ZERO: fir.zero_bits !fir.char<1>
! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<1>>
-! NAN-LABEL: func.func @_QPtest_charn
-! NAN: fir.do_loop
-! NAN: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
-! NAN: fir.zero_bits !fir.char<1>
-! NAN: fir.store {{.*}} : !fir.ref<!fir.char<1>>
-! SNAN-LABEL: func.func @_QPtest_charn
-! SNAN: fir.do_loop
-! SNAN: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
-! SNAN: fir.zero_bits !fir.char<1>
-! SNAN: fir.store {{.*}} : !fir.ref<!fir.char<1>>
! HEX-LABEL: func.func @_QPtest_charn
! HEX: fir.do_loop
@@ -346,7 +277,7 @@ subroutine test_charN(res, n)
! ---------------------------------------------------------------------------
! Derived type -- struct with an INTEGER(4) and a REAL(4) field
-! nan/hex: field-by-field walk (integer: 0xAA; real: NaN or bitcast)
+! hex: byte-loop over the whole struct (covers typed fields and padding)
! ---------------------------------------------------------------------------
subroutine test_derived(res)
type :: mytype
@@ -361,13 +292,6 @@ subroutine test_derived(res)
! ZERO: fir.zero_bits !fir.type<{{.*}}>
! ZERO: fir.store {{.*}} : !fir.ref<!fir.type<{{.*}}>>
-! NAN-LABEL: func.func @_QPtest_derived
-! NAN: fir.coordinate_of {{.*}} -> !fir.ref<i32>
-! NAN: arith.constant {{.*}} : i32
-! NAN: fir.store {{.*}} : !fir.ref<i32>
-! NAN: fir.coordinate_of {{.*}} -> !fir.ref<f32>
-! NAN: arith.constant {{.*}} : f32
-! NAN: fir.store {{.*}} : !fir.ref<f32>
! HEX-LABEL: func.func @_QPtest_derived
! HEX: fir.do_loop
@@ -389,10 +313,6 @@ subroutine test_int_array(res)
! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi32>>, index) -> !fir.ref<i32>
! ZERO: fir.store {{.*}} : !fir.ref<i32>
-! NAN-LABEL: func.func @_QPtest_int_array
-! NAN: fir.do_loop
-! NAN: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi32>>, index) -> !fir.ref<i32>
-! NAN: fir.store {{.*}} : !fir.ref<i32>
! HEX-LABEL: func.func @_QPtest_int_array
! HEX: fir.do_loop
@@ -404,7 +324,7 @@ subroutine test_int_array(res)
! OFF-NOT: fir.insert_on_range
! ---------------------------------------------------------------------------
-! Array REAL(4)(4) -- 1-D; nan/snan: NaN element; hex: bitcast element
+! Array REAL(4)(4) -- 1-D; hex: bitcast element
! ---------------------------------------------------------------------------
subroutine test_real_array(res)
real(4) :: res(4)
@@ -416,15 +336,7 @@ subroutine test_real_array(res)
! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf32>>, index) -> !fir.ref<f32>
! ZERO: fir.store {{.*}} : !fir.ref<f32>
-! NAN-LABEL: func.func @_QPtest_real_array
-! NAN: fir.do_loop
-! NAN: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf32>>, index) -> !fir.ref<f32>
-! NAN: fir.store {{.*}} : !fir.ref<f32>
-! SNAN-LABEL: func.func @_QPtest_real_array
-! SNAN: fir.do_loop
-! SNAN: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf32>>, index) -> !fir.ref<f32>
-! SNAN: fir.store {{.*}} : !fir.ref<f32>
! HEX-LABEL: func.func @_QPtest_real_array
! HEX: fir.do_loop
@@ -460,8 +372,6 @@ subroutine test_explicit_init(res)
! ZERO-LABEL: func.func @_QPtest_explicit_init
! ZERO-NOT: fir.zero_bits
-! NAN-LABEL: func.func @_QPtest_explicit_init
-! NAN-NOT: arith.constant -1431655766 : i32
! HEX-LABEL: func.func @_QPtest_explicit_init
! HEX-NOT: arith.bitcast
@@ -478,8 +388,6 @@ subroutine test_data_init(res)
! ZERO-LABEL: func.func @_QPtest_data_init
! ZERO-NOT: fir.zero_bits i32
-! NAN-LABEL: func.func @_QPtest_data_init
-! NAN-NOT: arith.constant -1431655766 : i32
! HEX-LABEL: func.func @_QPtest_data_init
! HEX-NOT: arith.bitcast
@@ -499,8 +407,6 @@ subroutine test_default_comp_init(res)
! ZERO-LABEL: func.func @_QPtest_default_comp_init
! ZERO-NOT: fir.zero_bits
-! NAN-LABEL: func.func @_QPtest_default_comp_init
-! NAN-NOT: arith.constant -1431655766 : i32
! HEX-LABEL: func.func @_QPtest_default_comp_init
! HEX-NOT: arith.bitcast
@@ -560,9 +466,6 @@ subroutine test_equivalence(res)
! ZERO-NOT: fir.zero_bits
! ZERO: return
-! NAN-LABEL: func.func @_QPtest_equivalence
-! NAN-NOT: arith.constant -1431655766 : i32
-! NAN: return
! HEX-LABEL: func.func @_QPtest_equivalence
! HEX-NOT: arith.bitcast
diff --git a/flang/tools/bbc/bbc.cpp b/flang/tools/bbc/bbc.cpp
index eb543524f19f5..b4bef6e576488 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -280,7 +280,7 @@ static llvm::cl::opt<std::string>
initLocalMode("finit-local",
llvm::cl::desc("Initialize local variables without explicit "
"or default initialization. "
- "Accepts: zero, nan, snan, or 0x<hex-byte>."),
+ "Accepts: zero or 0x<hex-byte>."),
llvm::cl::init(""));
static llvm::cl::opt<bool> initLocalZero(
@@ -535,10 +535,6 @@ static llvm::LogicalResult convertFortranSourceToMLIR(
}
if (val == "zero") {
loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::Zero);
- } else if (val == "nan") {
- loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::QNaN);
- } else if (val == "snan") {
- loweringOptions.setInitLocalMode(Fortran::lower::InitLocalKind::SNaN);
} else if (val.starts_with("0x") || val.starts_with("0X")) {
unsigned long long hexVal = 0;
if (!val.drop_front(2).getAsInteger(16, hexVal) && hexVal <= 0xFF) {
>From 78ed00772695149fece0c647da2d026076a95432 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Mon, 17 Aug 2026 22:46:34 -0400
Subject: [PATCH 08/10] [flang] Address MattPD's fifth round of review comments
on -finit-local= (Part 1)
Fix CHARACTER initialization for kind>1 (UTF-16/32). Both the
fixed-length and runtime-length scalar paths used fir.char<kind,1> as
the view element, so fir.coordinate_of strided by kind bytes while
storing only one i8 per iteration, leaving kind-1 bytes per code unit
unwritten.
Fix: use fir.char<1,1> (1-byte stride) and scale the loop trip count by
kind in both paths. Add tests for kind=2 and kind=4 fixed-length and
kind=2 runtime-length in finit-local.f90.
---
flang/lib/Lower/ConvertVariable.cpp | 42 ++++++++++++--------
flang/test/Lower/finit-local.f90 | 61 +++++++++++++++++++++++++++++
2 files changed, 86 insertions(+), 17 deletions(-)
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index c093ff258f729..1f1e4296f4d46 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1360,7 +1360,7 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
}
/// Emit a store of the -finit-local= pattern for a single scalar address.
-/// Fixed-length CHARACTER in hex mode: byte-loop over each code unit.
+/// Fixed-length CHARACTER in hex mode: byte-loop over every byte of storage.
/// LOGICAL stores via a bitcasted integer address to preserve the raw bit
/// pattern past fir.convert normalization.
static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
@@ -1375,22 +1375,27 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
if (charTy.getLen() == 0)
return;
if (mode == Fortran::lower::InitLocalKind::Hex) {
- // Loop over each code unit of the character storage. The loop body
- // stores one i8 per iteration via a singleton fir.char<kind,1>
- // coordinate, so every byte of every code unit is written.
+ // Loop over every byte of the character storage. For kind=1 each
+ // code unit is one byte; for kind=2/4 (UTF-16/32) each code unit is
+ // kind bytes wide. We use a kind=1 singleton as the view element so
+ // fir.coordinate_of advances exactly one byte per step, and iterate
+ // nUnits * kind times to cover all bytes.
int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
- if (nUnits > 0) {
+ int64_t kindBytes = charTy.getFKind();
+ int64_t nBytes = nUnits * kindBytes;
+ if (nBytes > 0) {
mlir::Type idxTy = builder.getIndexType();
mlir::Type i8Ty = builder.getIntegerType(8);
- fir::CharacterType byteTy = fir::CharacterType::getSingleton(
- builder.getContext(), charTy.getFKind());
+ // Use a kind=1 singleton so fir.coordinate_of strides by 1 byte.
+ fir::CharacterType byteTy =
+ fir::CharacterType::getSingleton(builder.getContext(), 1);
mlir::Type byteSeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, byteTy);
mlir::Value byteBase =
builder.createConvert(loc, builder.getRefType(byteSeqTy), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last =
- builder.createIntegerConstant(loc, idxTy, nUnits - 1);
+ builder.createIntegerConstant(loc, idxTy, nBytes - 1);
mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
/*unordered=*/false,
@@ -1403,9 +1408,6 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
byteBase, mlir::ValueRange{iv});
mlir::Value pat = builder.createIntegerConstant(
loc, i8Ty, static_cast<int64_t>(hexByte));
- // Store via i8 pointer so the byte pattern is written verbatim
- // regardless of character kind (UTF-16/32 code units are also
- // initialised byte-by-byte).
mlir::Value i8Addr =
builder.createConvert(loc, builder.getRefType(i8Ty), byteAddr);
fir::StoreOp::create(builder, loc, pat, i8Addr);
@@ -1571,8 +1573,16 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::Value lenIdx = builder.createConvert(loc, idxTy, rtLen);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
- // last = lenIdx - 1; the fir.do_loop trip count is lenIdx so the
- // loop body is skipped entirely when lenIdx == 0 (empty string).
+ // For kind>1 (UTF-16/32) the runtime length is in code units; multiply
+ // by kind to get the total byte count before computing the loop bound.
+ // Use a kind=1 singleton so fir.coordinate_of advances 1 byte per step.
+ int64_t kindBytes = charTy.getFKind();
+ if (kindBytes > 1) {
+ mlir::Value kindCst =
+ builder.createIntegerConstant(loc, idxTy, kindBytes);
+ lenIdx = mlir::arith::MulIOp::create(builder, loc, lenIdx, kindCst);
+ }
+ // last = byteCount - 1; the loop is skipped entirely when byteCount == 0.
mlir::Value last = mlir::arith::SubIOp::create(builder, loc, lenIdx, one);
auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
/*unordered=*/false,
@@ -1580,10 +1590,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(loop.getBody());
mlir::Value iv = loop.getInductionVar();
- // Treat the storage as an array of singleton characters so that
- // fir.coordinate_of advances by one code-unit per step.
- fir::CharacterType byteTy = fir::CharacterType::getSingleton(
- builder.getContext(), charTy.getFKind());
+ fir::CharacterType byteTy =
+ fir::CharacterType::getSingleton(builder.getContext(), 1);
mlir::Type byteSeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, byteTy);
mlir::Value byteBase =
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 6835b6a8313a0..15c5bbd44f780 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -470,3 +470,64 @@ subroutine test_equivalence(res)
! HEX-LABEL: func.func @_QPtest_equivalence
! HEX-NOT: arith.bitcast
! HEX: return
+
+! ---------------------------------------------------------------------------
+! CHARACTER(kind=2, len=3) -- fixed-length, higher kind.
+! hex: byte-loop over 3*2=6 bytes using a kind=1 singleton view.
+! zero: fir.zero_bits over the whole type.
+! ---------------------------------------------------------------------------
+subroutine test_char2_fixed(res)
+ character(kind=2, len=3) :: res
+ character(kind=2, len=3) :: x
+ res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_char2_fixed
+! ZERO: fir.zero_bits !fir.char<2,3>
+! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<2,3>>
+
+! HEX-LABEL: func.func @_QPtest_char2_fixed
+! HEX: fir.do_loop %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} {
+! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX: arith.constant {{.*}} : i8
+! HEX: fir.store {{.*}} : !fir.ref<i8>
+
+! ---------------------------------------------------------------------------
+! CHARACTER(kind=4, len=2) -- fixed-length, wider kind.
+! hex: byte-loop over 2*4=8 bytes using a kind=1 singleton view.
+! ---------------------------------------------------------------------------
+subroutine test_char4_fixed(res)
+ character(kind=4, len=2) :: res
+ character(kind=4, len=2) :: x
+ res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_char4_fixed
+! ZERO: fir.zero_bits !fir.char<4,2>
+! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<4,2>>
+
+! HEX-LABEL: func.func @_QPtest_char4_fixed
+! HEX: fir.do_loop %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} {
+! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX: arith.constant {{.*}} : i8
+! HEX: fir.store {{.*}} : !fir.ref<i8>
+
+! ---------------------------------------------------------------------------
+! CHARACTER(kind=2, len=n) -- runtime-length, higher kind.
+! hex/zero: byte-loop over n*2 bytes; skipped when n==0.
+! ---------------------------------------------------------------------------
+subroutine test_char2_runtime(res, n)
+ integer, intent(in) :: n
+ character(kind=2, len=n) :: res
+ character(kind=2, len=n) :: x
+ res = x
+end subroutine
+! ZERO-LABEL: func.func @_QPtest_char2_runtime
+! ZERO: fir.do_loop
+! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! ZERO: fir.zero_bits !fir.char<1>
+! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<1>>
+
+! HEX-LABEL: func.func @_QPtest_char2_runtime
+! HEX: fir.do_loop
+! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX: arith.constant {{.*}} : i8
+! HEX: fir.store {{.*}} : !fir.ref<i8>
>From 6fc990b25cb3b986c2d0075e570e918b554a406b Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Wed, 19 Aug 2026 01:52:43 -0400
Subject: [PATCH 09/10] [flang] Address MattPD's fifth round of review comments
on -finit-local= (Part 2)
Fix tail padding and x86_fp80 allocation padding for zero and hex modes.
- FIRType.cpp: round up RecordType size to alignment after the field loop
so getTypeSizeAndAlignment returns the allocation size including tail
padding (e.g. { i32, i8 } now returns 8 instead of 5).
- ConvertVariable.cpp: replace the fir.zero_bits aggregate store for
RecordType with a byte-fill loop, matching hex mode. A typed aggregate
store leaves tail-padding bytes as undef, which LLVM may not zero at -O2.
- ConvertVariable.cpp: for FloatType/ComplexType, detect when allocSize >
storeSize via DataLayout (e.g. x86_fp80: 10-byte value, 16-byte
allocation) and emit a byte-fill loop over the full allocation instead
of a typed store.
- Tests: update finit-local.f90 ZERO check for derived types; add
tail-padding scalar/array tests in finit-local-array-llvm.f90; add
finit-local-real10-llvm.f90 (REQUIRES: x86-registered-target) for
REAL(10)/COMPLEX(10) cases.
---
flang/lib/Lower/ConvertVariable.cpp | 59 +++++++++++++----
flang/lib/Optimizer/Dialect/FIRType.cpp | 2 +
flang/test/Lower/finit-local-array-llvm.f90 | 66 +++++++++++++++++++-
flang/test/Lower/finit-local-real10-llvm.f90 | 46 ++++++++++++++
flang/test/Lower/finit-local.f90 | 6 +-
5 files changed, 161 insertions(+), 18 deletions(-)
create mode 100644 flang/test/Lower/finit-local-real10-llvm.f90
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 1f1e4296f4d46..e7771db0bc942 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1415,6 +1415,44 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
return;
}
}
+ // REAL and COMPLEX: when the allocation size exceeds the store size
+ // (e.g. x86_fp80 stores 10 bytes but occupies 16), fill the full
+ // allocation with a byte loop so padding bytes are also initialized.
+ auto emitByteLoop = [&](uint64_t nBytes) {
+ mlir::Type idxTy = builder.getIndexType();
+ mlir::Type i8Ty = builder.getIntegerType(8);
+ mlir::Type i8SeqTy =
+ fir::SequenceType::get({fir::SequenceType::getUnknownExtent()}, i8Ty);
+ mlir::Value byteBase =
+ builder.createConvert(loc, builder.getRefType(i8SeqTy), addr);
+ mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
+ mlir::Value last = builder.createIntegerConstant(
+ loc, idxTy, static_cast<int64_t>(nBytes) - 1);
+ mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+ auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
+ /*unordered=*/false,
+ /*finalCount=*/false);
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPointToStart(loop.getBody());
+ mlir::Value iv = loop.getInductionVar();
+ mlir::Value byteAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(i8Ty), byteBase, mlir::ValueRange{iv});
+ int64_t fillByte =
+ (mode == Fortran::lower::InitLocalKind::Zero) ? 0 : hexByte;
+ mlir::Value pat = builder.createIntegerConstant(loc, i8Ty, fillByte);
+ fir::StoreOp::create(builder, loc, pat, byteAddr);
+ };
+
+ if (mlir::isa<mlir::FloatType, mlir::ComplexType>(ty)) {
+ const mlir::DataLayout &dl = builder.getDataLayout();
+ uint64_t storeSize = dl.getTypeSize(ty);
+ uint64_t allocSize = llvm::alignTo(storeSize, dl.getTypeABIAlignment(ty));
+ if (allocSize > storeSize) {
+ emitByteLoop(allocSize);
+ return;
+ }
+ }
+
mlir::Value val;
switch (mode) {
case Fortran::lower::InitLocalKind::Zero:
@@ -1509,17 +1547,10 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
initAddr(eleTy, elemAddr);
}
} else if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty)) {
- // Derived type initialization:
- // zero: fir.zero_bits over the whole struct -- covers all typed
- // fields and any padding bytes between them.
- // hex: byte-loop over the whole struct using the compile-time
- // struct size from fir::getTypeSizeAndAlignmentOrCrash --
- // also covers padding bytes, giving a uniform byte pattern.
- if (mode == Fortran::lower::InitLocalKind::Zero) {
- fir::StoreOp::create(
- builder, loc, fir::ZeroOp::create(builder, loc, recTy), addr);
- } else if (mode == Fortran::lower::InitLocalKind::Hex) {
- // Fill every byte (typed fields + padding) with hexByte.
+ // Byte-fill the full allocation (fields + internal padding +
+ // tail padding). A typed fir.zero_bits store would leave tail
+ // padding as 'undef', which LLVM may not zero at -O2.
+ {
auto [byteSize, _align] = fir::getTypeSizeAndAlignmentOrCrash(
loc, recTy, builder.getDataLayout(), builder.getKindMap());
if (byteSize > 0) {
@@ -1542,8 +1573,10 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::Value byteAddr = fir::CoordinateOp::create(
builder, loc, builder.getRefType(i8Ty), byteBase,
mlir::ValueRange{iv});
- mlir::Value pat = builder.createIntegerConstant(
- loc, i8Ty, static_cast<int64_t>(hexByte));
+ int64_t fillByte =
+ (mode == Fortran::lower::InitLocalKind::Zero) ? 0 : hexByte;
+ mlir::Value pat =
+ builder.createIntegerConstant(loc, i8Ty, fillByte);
fir::StoreOp::create(builder, loc, pat, byteAddr);
}
}
diff --git a/flang/lib/Optimizer/Dialect/FIRType.cpp b/flang/lib/Optimizer/Dialect/FIRType.cpp
index 178860239e17d..0aeef208a5a1e 100644
--- a/flang/lib/Optimizer/Dialect/FIRType.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRType.cpp
@@ -1670,6 +1670,8 @@ fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
llvm::alignTo(size, compAlign) + llvm::alignTo(compSize, compAlign);
align = std::max(align, compAlign);
}
+ // Include tail padding so the size matches the allocation size.
+ size = llvm::alignTo(size, align);
return std::pair{size, align};
}
if (auto logical = mlir::dyn_cast<fir::LogicalType>(ty)) {
diff --git a/flang/test/Lower/finit-local-array-llvm.f90 b/flang/test/Lower/finit-local-array-llvm.f90
index 17290d6e9e370..a8badfb509f7d 100644
--- a/flang/test/Lower/finit-local-array-llvm.f90
+++ b/flang/test/Lower/finit-local-array-llvm.f90
@@ -53,7 +53,8 @@ subroutine test_array_in_struct(res)
! ZERO-LABEL: define {{.*}}@{{.*}}test_array_in_struct{{.*}}(
-! ZERO: store {{.*}} zeroinitializer,
+! ZERO: getelementptr i8, ptr {{.*}}, i64
+! ZERO: store i8 0,
! ---------------------------------------------------------------------------
! Rank-2 array INTEGER(4)(3,4) -- 12 elements; flat loop via rank-1 view
@@ -120,7 +121,8 @@ subroutine test_array_of_struct(res)
! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_struct{{.*}}(
! ZERO: phi i64 [ {{.*}}, {{.*}} ], [ 2, %{{.*}} ]
! ZERO: getelementptr %{{.*}}t, ptr {{.*}}, i64
-! ZERO: store {{.*}} zeroinitializer,
+! ZERO: getelementptr i8, ptr {{.*}}, i64
+! ZERO: store i8 0,
! ---------------------------------------------------------------------------
! Rank-2 array of derived type type(t) :: x(2,3) -- 6 elements
@@ -147,4 +149,62 @@ subroutine test_array_of_struct_2d(res)
! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_struct_2d{{.*}}(
! ZERO: phi i64 [ {{.*}}, {{.*}} ], [ 6, %{{.*}} ]
! ZERO: getelementptr %{{.*}}t, ptr {{.*}}, i64
-! ZERO: store {{.*}} zeroinitializer,
+! ZERO: getelementptr i8, ptr {{.*}}, i64
+! ZERO: store i8 0,
+
+! ---------------------------------------------------------------------------
+! Tail-padded scalar: integer(4) :: a followed by integer(1) :: b
+! LLVM lays this out as { i32, i8 } with 3 bytes of tail padding (alloc=8B).
+! Both zero and hex modes must write all 8 bytes, not just the 5 typed bytes.
+! ---------------------------------------------------------------------------
+subroutine test_tail_padded_scalar(res_a, res_b)
+ type :: t
+ integer(4) :: a
+ integer(1) :: b
+ end type
+ type(t) :: x
+ res_a = x%a
+ res_b = x%b
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_tail_padded_scalar{{.*}}(
+! HEX: phi i64 [ {{.*}}, {{.*}} ], [ 8, %{{.*}} ]
+! HEX: getelementptr i8, ptr {{.*}}, i64
+! HEX: store i8 -86,
+! HEX-NOT: store {{.*}} zeroinitializer,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_tail_padded_scalar{{.*}}(
+! ZERO: phi i64 [ {{.*}}, {{.*}} ], [ 8, %{{.*}} ]
+! ZERO: getelementptr i8, ptr {{.*}}, i64
+! ZERO: store i8 0,
+! ZERO-NOT: store {{.*}} zeroinitializer,
+
+! ---------------------------------------------------------------------------
+! Array of tail-padded records: type(t) :: x(3)
+! Each element is 8 bytes (5 typed + 3 tail padding); 3 elements = 24 bytes.
+! Both zero and hex modes must write all 8 bytes per element.
+! ---------------------------------------------------------------------------
+subroutine test_array_of_tail_padded(res_a, res_b)
+ type :: t
+ integer(4) :: a
+ integer(1) :: b
+ end type
+ type(t) :: x(3)
+ res_a = x(1)%a
+ res_b = x(1)%b
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_array_of_tail_padded{{.*}}(
+! HEX: phi i64 [ {{.*}}, {{.*}} ], [ 3, %{{.*}} ]
+! HEX: getelementptr %{{.*}}t, ptr {{.*}}, i64
+! HEX: phi i64 [ {{.*}}, {{.*}} ], [ 8, %{{.*}} ]
+! HEX: getelementptr i8, ptr {{.*}}, i64
+! HEX: store i8 -86,
+! HEX-NOT: store {{.*}} zeroinitializer,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_array_of_tail_padded{{.*}}(
+! ZERO: phi i64 [ {{.*}}, {{.*}} ], [ 3, %{{.*}} ]
+! ZERO: getelementptr %{{.*}}t, ptr {{.*}}, i64
+! ZERO: phi i64 [ {{.*}}, {{.*}} ], [ 8, %{{.*}} ]
+! ZERO: getelementptr i8, ptr {{.*}}, i64
+! ZERO: store i8 0,
+! ZERO-NOT: store {{.*}} zeroinitializer,
+
diff --git a/flang/test/Lower/finit-local-real10-llvm.f90 b/flang/test/Lower/finit-local-real10-llvm.f90
new file mode 100644
index 0000000000000..7456641e111e9
--- /dev/null
+++ b/flang/test/Lower/finit-local-real10-llvm.f90
@@ -0,0 +1,46 @@
+! Tests that -finit-local= fills the full allocation for REAL(10) and
+! COMPLEX(10) on x86. x86_fp80 has a 10-byte store size but a 16-byte
+! allocation size; a typed store would leave 6 bytes uninitialized.
+! The fix detects the gap via DataLayout and emits a byte-fill loop over
+! the full allocation instead.
+!
+! REQUIRES: x86-registered-target
+!
+! RUN: %flang_fc1 -emit-llvm -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
+! RUN: %flang_fc1 -emit-llvm -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+
+! ---------------------------------------------------------------------------
+! REAL(10) -- x86_fp80: store=10 bytes, alloc=16 bytes on x86-64.
+! ---------------------------------------------------------------------------
+subroutine test_real10(res)
+ real(10) :: res
+ real(10) :: x
+ res = x
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_real10{{.*}}(
+! HEX: phi i64 [ {{.*}}, {{.*}} ], [ 16, %{{.*}} ]
+! HEX: getelementptr i8, ptr {{.*}}, i64
+! HEX: store i8 -86,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_real10{{.*}}(
+! ZERO: phi i64 [ {{.*}}, {{.*}} ], [ 16, %{{.*}} ]
+! ZERO: getelementptr i8, ptr {{.*}}, i64
+! ZERO: store i8 0,
+
+! ---------------------------------------------------------------------------
+! COMPLEX(10) -- two x86_fp80 parts: store=20 bytes, alloc=32 bytes on x86-64.
+! ---------------------------------------------------------------------------
+subroutine test_complex10(res)
+ complex(10) :: res
+ complex(10) :: x
+ res = x
+end subroutine
+! HEX-LABEL: define {{.*}}@{{.*}}test_complex10{{.*}}(
+! HEX: phi i64 [ {{.*}}, {{.*}} ], [ 32, %{{.*}} ]
+! HEX: getelementptr i8, ptr {{.*}}, i64
+! HEX: store i8 -86,
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_complex10{{.*}}(
+! ZERO: phi i64 [ {{.*}}, {{.*}} ], [ 32, %{{.*}} ]
+! ZERO: getelementptr i8, ptr {{.*}}, i64
+! ZERO: store i8 0,
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 15c5bbd44f780..6e9aca1c3161d 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -289,8 +289,10 @@ subroutine test_derived(res)
res = x
end subroutine
! ZERO-LABEL: func.func @_QPtest_derived
-! ZERO: fir.zero_bits !fir.type<{{.*}}>
-! ZERO: fir.store {{.*}} : !fir.ref<!fir.type<{{.*}}>>
+! ZERO: fir.do_loop
+! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! ZERO: arith.constant 0 : i8
+! ZERO: fir.store {{.*}} : !fir.ref<i8>
! HEX-LABEL: func.func @_QPtest_derived
>From 4e3644ef1d7b0f15b33b3e7789202a5109e0150e Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Tue, 1 Sep 2026 10:48:43 -0400
Subject: [PATCH 10/10] [flang] Fix getTypeSizeAndAlignment to account for
packed RecordType
fir::getTypeSizeAndAlignment ignored RecordType::isPacked(), computing
the wrong size for packed structs in two ways:
1. Inter-field alignment gaps were added (alignTo(size, compAlign)),
which packed structs do not have.
2. Each component size was rounded up to its ABI alignment
(alignTo(compSize, compAlign)), but LLVM packed structs use the
raw store size (dl.getTypeSize()) per component with no rounding.
For example, for a packed {i32, complex<f64>} on x86-64 (where f64
has 8-byte ABI alignment), the old code returned 24 bytes (the
non-packed aligned size) instead of the correct 20 bytes
(4 + 16, store sizes only).
On AIX, this caused the -finit-local= byte-fill loop to write more
bytes than the packed allocation actually occupies.
LLVM packed structs (<{ ... }>) place fields back-to-back with no
inter-field padding and no tail padding. Each component occupies
exactly its store size with no alignment rounding. The packed struct
ABI alignment is always 1.
Fix the RecordType branch to sum per-component store sizes without
any rounding, and return ABI alignment 1, when isPacked() is true.
Add two tests:
- flang/test/Fir/box-elesize-canonicalize.fir: checks fir.box_elesize
folds to 20 for a packed {i32, complex<f64>} type using the x86-64
DLTI spec (non-packed=24B, packed=20B; runs on all platforms).
- flang/test/Lower/finit-local-packed.f90: AIX BIND(C) regression
verifying the byte-fill loop uses the correct packed size for
{integer(4), complex(8)} (system-aix).
---
flang/lib/Optimizer/Dialect/FIRType.cpp | 16 +++++++++
flang/test/Fir/box-elesize-canonicalize.fir | 28 +++++++++++++++
flang/test/Lower/finit-local-packed.f90 | 38 +++++++++++++++++++++
3 files changed, 82 insertions(+)
create mode 100644 flang/test/Lower/finit-local-packed.f90
diff --git a/flang/lib/Optimizer/Dialect/FIRType.cpp b/flang/lib/Optimizer/Dialect/FIRType.cpp
index 0aeef208a5a1e..493637521ca25 100644
--- a/flang/lib/Optimizer/Dialect/FIRType.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRType.cpp
@@ -1661,6 +1661,22 @@ fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty)) {
std::uint64_t size = 0;
unsigned short align = 1;
+ if (recTy.isPacked()) {
+ // LLVM packed structs (<{ ... }>) place fields back-to-back with no
+ // inter-field alignment padding and no tail padding. Each component
+ // occupies exactly its store size (dl.getTypeSize(), which is what
+ // getTypeSizeAndAlignment returns as compSize). Unlike ordinary
+ // structs, there is no per-component rounding of compSize to compAlign.
+ // The packed struct ABI alignment is always 1.
+ for (auto component : recTy.getTypeList()) {
+ auto result =
+ getTypeSizeAndAlignment(loc, component.second, dl, kindMap);
+ if (!result)
+ return result;
+ size += result->first; // store size only; no alignment rounding
+ }
+ return std::pair{size, static_cast<unsigned short>(1)};
+ }
for (auto component : recTy.getTypeList()) {
auto result = getTypeSizeAndAlignment(loc, component.second, dl, kindMap);
if (!result)
diff --git a/flang/test/Fir/box-elesize-canonicalize.fir b/flang/test/Fir/box-elesize-canonicalize.fir
index dd2b06ed3ef77..64ae66faac66b 100644
--- a/flang/test/Fir/box-elesize-canonicalize.fir
+++ b/flang/test/Fir/box-elesize-canonicalize.fir
@@ -102,4 +102,32 @@ module attributes { dlti.dl_spec = #dlti.dl_spec< i1 = dense<8> : vector<2xi64>,
// CHECK: %[[C:.*]] = arith.constant 16 : index
// CHECK: return %[[C]]
// CHECK-NOT: fir.box_elesize
+
+ // Fold: packed derived type -- fir.type<tp<{i:i32,d:f64}>>.
+ // This is the exact example from MattPD's review: on x86-64
+ // (f64 ABI align=8B) the non-packed size is 16B and the packed
+ // size is 12B (i32 store=4B + f64 store=8B, no inter-field gap,
+ // no tail padding). The element distance must be 12, not 16.
+ func.func @fold_packed_record(%arg0: !fir.box<!fir.type<tp<{i:i32,d:f64}>>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<!fir.type<tp<{i:i32,d:f64}>>>) -> index
+ return %0 : index
+ }
+ // CHECK-LABEL: func.func @fold_packed_record(
+ // CHECK: %[[C:.*]] = arith.constant 12 : index
+ // CHECK: return %[[C]]
+ // CHECK-NOT: fir.box_elesize
+
+ // Fold: packed derived type with a wider alignment gap.
+ // On x86-64: i32 store=4B, complex<f64> store=16B.
+ // Non-packed: alignTo(4,4)=4 + alignTo(4,8)+alignTo(16,8)=24, tail=24 -> 24B.
+ // Packed: 4 + 16 = 20B, align=1.
+ // The element distance must be 20, not 24.
+ func.func @fold_packed_record2(%arg0: !fir.box<!fir.type<tp2<{i:i32,z:complex<f64>}>>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<!fir.type<tp2<{i:i32,z:complex<f64>}>>>) -> index
+ return %0 : index
+ }
+ // CHECK-LABEL: func.func @fold_packed_record2(
+ // CHECK: %[[C:.*]] = arith.constant 20 : index
+ // CHECK: return %[[C]]
+ // CHECK-NOT: fir.box_elesize
}
diff --git a/flang/test/Lower/finit-local-packed.f90 b/flang/test/Lower/finit-local-packed.f90
new file mode 100644
index 0000000000000..1897d6452df82
--- /dev/null
+++ b/flang/test/Lower/finit-local-packed.f90
@@ -0,0 +1,38 @@
+! AIX BIND(C) regression test for -finit-local= with packed derived types.
+!
+! On AIX, ConvertType creates a packed layout for a BIND(C) record whose
+! REAL(8) component is not first. The initialization loop must use the
+! correct packed size.
+!
+! For {integer(4), complex(8)}: i32 store=4B, complex<f64> store=16B.
+! Packed size = 4+16 = 20B. Loop upper bound must be 19 (trip count 20).
+!
+! Note: on AIX f64 has 4-byte ABI alignment, so the non-packed size of
+! {i32, complex<f64>} also happens to be 20B (no inter-field gap needed).
+! The platform-independent test that distinguishes packed from non-packed
+! (using x86-64 alignment where they differ) lives in
+! flang/test/Fir/box-elesize-canonicalize.fir.
+!
+! REQUIRES: system-aix
+!
+! RUN: bbc -emit-hlfir -finit-local=0xAA -o - %s 2>/dev/null | FileCheck --check-prefix=HEX %s
+! RUN: bbc -emit-hlfir -finit-local=zero -o - %s 2>/dev/null | FileCheck --check-prefix=ZERO %s
+
+subroutine test_bindc_aix(res)
+ type, bind(c) :: tp
+ integer(4) :: i
+ complex(8) :: z
+ end type
+ type(tp) :: x
+ res = x%i
+end subroutine
+
+! HEX-LABEL: func.func @_QPtest_bindc_aix
+! HEX: arith.constant 19 : index
+! HEX: fir.do_loop
+! HEX: fir.store %{{.*}} : !fir.ref<i8>
+
+! ZERO-LABEL: func.func @_QPtest_bindc_aix
+! ZERO: arith.constant 19 : index
+! ZERO: fir.do_loop
+! ZERO: fir.store %{{.*}} : !fir.ref<i8>
More information about the flang-commits
mailing list