[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
Thu Sep 3 08:22:17 PDT 2026
https://github.com/DanielCChen updated https://github.com/llvm/llvm-project/pull/216164
>From fdcb966294653a82b9606a5a3dee533d937afef9 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/17] [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 a5705fe18983c..bc95450dbdc04 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -79,6 +79,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 ff4c7f6a18624..79273640c0c68 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -1782,6 +1782,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 8438b39c990a9..24e4a08533fa3 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 50742eb106421..0aa4b86cb2252 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 "
@@ -506,6 +520,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 4beb4e1a9caa620d4e8fd55858dc7851662cdfad 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/17] [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 bc95450dbdc04..d291011a86274 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -79,7 +79,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 24e4a08533fa3..4538c361b29f6 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 e7e66741e290eddc14be0aa5319b32d087affc0c 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/17] [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 4538c361b29f6..f1848db090ed0 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 0aa4b86cb2252..e6c088febf008 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -520,11 +520,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") {
@@ -540,11 +537,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 b16a2d3724c8efb68f787db8209e9660699ce021 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/17] [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 f1848db090ed0..a1c986084a4cb 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 e6c088febf008..bdee3e8746e20 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -522,8 +522,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 d46200d547376d178f71f0c737855752956438f5 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/17] [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 a1c986084a4cb..bcbad7bbcaad7 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 bdee3e8746e20..db7201e26a567 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -521,8 +521,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";
@@ -548,11 +554,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 c17824ada495b8d5eee317659dc5da01b0f70894 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/17] [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 d291011a86274..0760ac8b0f735 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -83,6 +83,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 bcbad7bbcaad7..749bad929cb27 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 7f8139b5af6f6b887afe5ad995a953a5162fdf7d 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/17] 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 0760ac8b0f735..a134076b3a391 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -81,12 +81,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 79273640c0c68..6b0b3fe2c7c0f 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -1782,7 +1782,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 =
@@ -1790,10 +1790,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 749bad929cb27..13bc5b1e93919 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 db7201e26a567..e64daf88d2596 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(
@@ -536,10 +536,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 c38b350af41990c68dec373bb2104382e74af6f5 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/17] [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 13bc5b1e93919..86ef414e93038 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 facb6a3672fab2fec46a1e9eb86a6420d3336245 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/17] [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 86ef414e93038..2c6693d129a65 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 741ea925860c54cea05b45a0a992cc2179c9341a 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/17] [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>
>From fe448889df48d8c33fa583cddfde055e6c086421 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Tue, 1 Sep 2026 23:43:32 -0400
Subject: [PATCH 11/17] [flang] Address MattPD's sixth round of review comments
on -finit-local=
1. Exclude PowerPC vector locals from initialization.
vector(real(4)) and related types lower to fir::VectorType, which
does not implement DataLayoutTypeInterface at the HLFIR level and
causes a fatal crash when -finit-local= is active. Exclude them in
shouldInitLocal via DerivedTypeSpec::IsVectorType(). Replace the
silent fir.ZeroOp fallback in genByteSplatInit with llvm_unreachable
to catch any future unhandled type. Add a HLFIR-level regression test
and update ReleaseNotes.md.
2. Fix CUDA device-context initialization for host_device routines and
BLOCK-construct locals inside global kernels. Move the Device check
from shouldInitLocal into genInitLocal using
cuf::isCUDADeviceContext(builder.getRegion()), which mirrors the
allocation decision and handles BLOCK scopes correctly. Add
test_host_device_local (CHECK-NOT) and test_global_block_local to
finit-local-cuda.cuf; tighten test_global_local to anchor the store
to the declared variable via %[[N]]#0.
3. Strengthen higher-kind CHARACTER test assertions: require the exact
upper-bound constants (5 and 7) and arith.muli factor-of-2 for the
runtime kind=2 case in finit-local.f90.
4. Add flang-rt/test/Driver/finit-local-char2-exec.f90: an executable
test verifying every code unit of a character(kind=2) local equals
int(z'AAAA') after -finit-local=0xAA, for both fixed and runtime
lengths including the n=0 edge case.
---
.../test/Driver/finit-local-char2-exec.f90 | 46 +++++++++++++
flang/docs/ReleaseNotes.md | 1 +
flang/lib/Lower/ConvertVariable.cpp | 66 +++++++++++++------
flang/test/Lower/CUDA/finit-local-cuda.cuf | 35 +++++++++-
.../Lower/finit-local-ppc-vector-llvm.f90 | 24 +++++++
flang/test/Lower/finit-local.f90 | 7 ++
6 files changed, 156 insertions(+), 23 deletions(-)
create mode 100644 flang-rt/test/Driver/finit-local-char2-exec.f90
create mode 100644 flang/test/Lower/finit-local-ppc-vector-llvm.f90
diff --git a/flang-rt/test/Driver/finit-local-char2-exec.f90 b/flang-rt/test/Driver/finit-local-char2-exec.f90
new file mode 100644
index 0000000000000..c48295feb775a
--- /dev/null
+++ b/flang-rt/test/Driver/finit-local-char2-exec.f90
@@ -0,0 +1,46 @@
+! Executable regression test for -finit-local= with kind-2 CHARACTER.
+! Verifies that each code unit of a character(kind=2) local equals
+! int(z'AAAA'): the 0xAA byte pattern covers both bytes of every
+! 2-byte code unit so the full 16-bit value must be 0xAAAA (= 43690).
+!
+! UNSUPPORTED: offload-cuda
+!
+! RUN: %flang %isysroot -L"%libdir" -finit-local=0xAA %s -o %t
+! RUN: env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:%libdir" %t
+
+program test_finit_local_char2
+ implicit none
+
+ call check_char2_fixed()
+ call check_char2_runtime(3)
+ call check_char2_runtime(1)
+ call check_char2_runtime(0) ! empty: must not crash
+end program
+
+! Fixed-length character(kind=2, len=3): 3 code units, each must be 0xAAAA.
+subroutine check_char2_fixed()
+ character(kind=2, len=3) :: x
+ integer :: i
+ do i = 1, 3
+ if (ichar(x(i:i)) /= int(z'AAAA')) then
+ write(*,*) 'FAIL check_char2_fixed: code unit', i, '=', ichar(x(i:i)), &
+ 'expected', int(z'AAAA')
+ error stop 1
+ end if
+ end do
+end subroutine
+
+! Runtime-length character(kind=2, len=n): each code unit must be 0xAAAA.
+subroutine check_char2_runtime(n)
+ integer, intent(in) :: n
+ character(kind=2, len=n) :: x
+ integer :: i
+ do i = 1, n
+ if (ichar(x(i:i)) /= int(z'AAAA')) then
+ write(*,*) 'FAIL check_char2_runtime: code unit', i, &
+ 'of character(kind=2,len=', n, ') =', ichar(x(i:i)), &
+ 'expected', int(z'AAAA')
+ error stop 1
+ end if
+ end do
+end subroutine
diff --git a/flang/docs/ReleaseNotes.md b/flang/docs/ReleaseNotes.md
index a134076b3a391..ee2960ac4641e 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -84,6 +84,7 @@ page](https://llvm.org/releases/).
are `zero` and `0x<hex-byte>` (e.g. `0xAA`). The gfortran
compatibility alias `-finit-local-zero` is equivalent to `-finit-local=zero`.
Both modes fill every storage byte including struct padding.
+ PowerPC vector locals (`vector(real(4))` etc.) are not initialized.
## Windows Support
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 2c6693d129a65..0a3542db6f49f 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1261,8 +1261,10 @@ getSafeRepackAttrs(Fortran::lower::AbstractConverter &converter) {
/// -finit-local= initialization. Excluded: variables without a symbol,
/// globals, dummy arguments, SAVE'd vars, ALLOCATABLE/POINTER, vars in
/// an EQUIVALENCE set, vars with explicit or default initialization, and
-/// CUDA variables whose storage is not host-accessible (device, constant,
-/// shared, usedevice).
+/// CUDA variables whose storage is always unreachable by a plain fir.store
+/// (constant, shared, usedevice). The Device case is deferred to genInitLocal
+/// which applies cuf::isCUDADeviceContext to distinguish cuf.alloc from
+/// fir.alloca storage.
static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
if (!var.hasSymbol() || var.isGlobal())
return false;
@@ -1286,14 +1288,22 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
// descriptor, not the pointee storage.
if (sym.test(Fortran::semantics::Symbol::Flag::CrayPointee))
return false;
+ // PowerPC vector types (vector(real(4)) etc.) lower to fir::VectorType
+ // which does not implement DataLayoutTypeInterface at the HLFIR level.
+ // Exclude them here so no initialization is attempted.
+ if (const Fortran::semantics::DeclTypeSpec *declTy = sym.GetType())
+ if (const Fortran::semantics::DerivedTypeSpec *derived =
+ declTy->AsDerived())
+ if (derived->IsVectorType())
+ return false;
// CUDA storage accessibility:
// constant / shared / usedevice: always unreachable by a plain fir.store
// from the host -- skip.
- // device in a HOST context: lives in global device memory; a host
- // fir.store cannot reach it -- skip.
- // device in a DEVICE subprogram: implicitly set by SetImplicitCUDADevice
- // for every local in a device kernel; these are per-thread stack
- // allocations reachable by a device fir.store -- initialize.
+ // device: the allocation choice (cuf.alloc vs fir.alloca) depends on
+ // whether the insertion point is in a device context; that check
+ // requires the MLIR builder and is deferred to genInitLocal, which
+ // calls cuf::isCUDADeviceContext(builder.getRegion()) after this
+ // predicate returns true.
// managed / unified / pinned: host-accessible unified memory -- initialize.
if (auto cudaAttr = Fortran::semantics::GetCUDADataAttr(&sym)) {
switch (*cudaAttr) {
@@ -1301,13 +1311,6 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
case Fortran::common::CUDADataAttr::Shared:
case Fortran::common::CUDADataAttr::UseDevice:
return false;
- case Fortran::common::CUDADataAttr::Device:
- // In a device subprogram the attribute is implicit
- // (SetImplicitCUDADevice) and the variable is a thread-local stack
- // allocation -- initialize it.
- if (!Fortran::semantics::IsCUDADeviceContext(&sym.owner()))
- return false;
- break;
default:
break;
}
@@ -1316,10 +1319,12 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
}
/// Build a constant whose every byte equals \p bytePat.
-/// FP types: bitcast from an integer splat. Complex: apply to both parts.
-/// LOGICAL(k): returns a raw iN integer (caller stores via bitcasted address).
-/// Character: falls back to fir.zero_bits (see TODO). Derived types are
-/// handled by the caller before this function is reached.
+/// Handles: integer, float (bitcast from integer splat), complex (both parts),
+/// and logical (raw integer, stored via bitcasted address by the caller).
+/// Character, derived-type, and sequence types are all intercepted by
+/// genInitLocalStore or initAddr before this function is called and must
+/// not reach it. fir::VectorType (PowerPC vector locals) is excluded
+/// upstream by shouldInitLocal and will never reach this function.
static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
mlir::Location loc, mlir::Type ty,
uint8_t bytePat) {
@@ -1355,8 +1360,13 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
return makeIntCst(logTy.getFKind() * 8);
}
- // Fallback (e.g. fir.char reached via an unexpected path): zero-initialise.
- return fir::ZeroOp::create(builder, loc, eleTy);
+ // All types that pass shouldInitLocal and reach genInitLocalStore are
+ // handled explicitly above (integer, float, complex, logical) or are
+ // intercepted before this call (character, record, sequence).
+ // PowerPC vector types are excluded at shouldInitLocal and never reach here.
+ // A silent zero for an unhandled type would violate the hex-mode contract,
+ // so assert rather than fall back silently.
+ llvm_unreachable("genByteSplatInit: unhandled type in hex mode");
}
/// Emit a store of the -finit-local= pattern for a single scalar address.
@@ -1483,7 +1493,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
/// Arrays: all modes use a flat fir.do_loop + fir.coordinate_of over a
/// rank-1 view to avoid both the llvm.mlir.constant crash on non-zero
/// ArrayAttrs and the quadratic compile time of fir.insert_on_range.
-/// Derived types use a whole-struct byte-loop for hex. Scalars store directly.
+/// Derived types use a byte-fill loop. PowerPC vector locals are
+/// excluded upstream by shouldInitLocal and never reach this function.
+/// Scalars store directly via genInitLocalStore.
static void genInitLocal(Fortran::lower::AbstractConverter &converter,
const Fortran::lower::pft::Variable &var,
Fortran::lower::SymMap &symMap) {
@@ -1498,6 +1510,18 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::Location loc = converter.getCurrentLocation();
uint8_t hexByte = converter.getLoweringOptions().getInitLocalPattern();
+ // Mirror the allocation decision: a CUDA Device variable is allocated via
+ // cuf.alloc when cuf::isCUDADeviceContext is false (host or host_device
+ // subprograms), and via fir.alloca when it is true (device/global kernels,
+ // including BLOCK-construct locals inside such kernels). Only the fir.alloca
+ // path is reachable by a plain fir.store, so skip initialization if the
+ // variable has a Device attribute but the current region is not a device
+ // context (which would mean the storage is a cuf.alloc).
+ if (auto cudaAttr = Fortran::semantics::GetCUDADataAttr(&var.getSymbol()))
+ if (*cudaAttr == Fortran::common::CUDADataAttr::Device &&
+ !cuf::isCUDADeviceContext(builder.getRegion()))
+ return;
+
fir::ExtendedValue exv =
converter.getSymbolExtendedValue(var.getSymbol(), &symMap);
mlir::Value base = fir::getBase(exv);
diff --git a/flang/test/Lower/CUDA/finit-local-cuda.cuf b/flang/test/Lower/CUDA/finit-local-cuda.cuf
index 297623c97dc0e..85fdec3b2afda 100644
--- a/flang/test/Lower/CUDA/finit-local-cuda.cuf
+++ b/flang/test/Lower/CUDA/finit-local-cuda.cuf
@@ -61,6 +61,37 @@ attributes(global) subroutine test_global_local()
n = 42
end subroutine
! CHECK-LABEL: func.func @{{.*}}test_global_local
-! CHECK: hlfir.declare {{.*}}"_QFtest_global_localEn"
+! CHECK: %[[N:.*]]:2 = hlfir.declare {{.*}}"_QFtest_global_localEn"
! CHECK: fir.zero_bits i32
-! CHECK: fir.store {{.*}} : !fir.ref<i32>
+! CHECK: fir.store %{{.*}} to %[[N]]#0 : !fir.ref<i32>
+
+! An explicit 'device' local in an attributes(host,device) routine is
+! allocated via cuf.alloc (not fir.alloca) because cuf::isCUDADeviceContext
+! returns false for HostDevice. A plain fir.store cannot reach cuf.alloc
+! storage, so shouldInitLocal must return false for this case.
+attributes(host,device) subroutine test_host_device_local()
+ real, device :: x
+ x = 1.0
+end subroutine
+! CHECK-LABEL: func.func @{{.*}}test_host_device_local
+! CHECK-NOT: fir.zero_bits f32
+
+! A BLOCK-construct local inside an attributes(global) kernel lives in
+! device-side fir.alloca storage and MUST be initialized. The old code
+! used IsCUDADeviceContext on the BlockConstruct scope (which has no
+! subprogram symbol) and returned false, so the variable was skipped.
+! The fix uses cuf::isCUDADeviceContext(builder.getRegion()) in genInitLocal,
+! which walks up through nested regions to the enclosing func and returns
+! true for global kernels regardless of whether the variable is in a BLOCK.
+attributes(global) subroutine test_global_block_local()
+ integer :: m
+ block
+ integer :: n
+ n = 99
+ m = n
+ end block
+end subroutine
+! CHECK-LABEL: func.func @{{.*}}test_global_block_local
+! CHECK: %[[N:.*]]:2 = hlfir.declare {{.*}}"_QFtest_global_block_local{{.*}}En"
+! CHECK: fir.zero_bits i32
+! CHECK: fir.store %{{.*}} to %[[N]]#0 : !fir.ref<i32>
diff --git a/flang/test/Lower/finit-local-ppc-vector-llvm.f90 b/flang/test/Lower/finit-local-ppc-vector-llvm.f90
new file mode 100644
index 0000000000000..0fd3b0613c572
--- /dev/null
+++ b/flang/test/Lower/finit-local-ppc-vector-llvm.f90
@@ -0,0 +1,24 @@
+! Tests that PowerPC vector locals (fir::VectorType) are excluded from
+! -finit-local= initialization because fir::VectorType does not implement
+! DataLayoutTypeInterface at the HLFIR level.
+! The local variable 'x' must not receive any initialization store.
+!
+! REQUIRES: target=powerpc{{.*}}
+!
+! RUN: %flang_fc1 -emit-hlfir -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
+! RUN: %flang_fc1 -emit-hlfir -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+
+! vector(real(4)) -- excluded; must not produce any fir.store for 'x'.
+subroutine test_vec_real4(res)
+ vector(real(4)) :: res
+ vector(real(4)) :: x
+ res = x
+end subroutine
+
+! HEX-LABEL: func.func @_QPtest_vec_real4(
+! HEX: hlfir.declare {{.*}}_QFtest_vec_real4Ex
+! HEX-NOT: fir.store {{.*}} to %{{.*}}#0
+
+! ZERO-LABEL: func.func @_QPtest_vec_real4(
+! ZERO: hlfir.declare {{.*}}_QFtest_vec_real4Ex
+! ZERO-NOT: fir.store {{.*}} to %{{.*}}#0
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 6e9aca1c3161d..8285240eab54e 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -488,6 +488,7 @@ subroutine test_char2_fixed(res)
! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<2,3>>
! HEX-LABEL: func.func @_QPtest_char2_fixed
+! HEX: arith.constant 5 : index
! HEX: fir.do_loop %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} {
! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
! HEX: arith.constant {{.*}} : i8
@@ -507,6 +508,7 @@ subroutine test_char4_fixed(res)
! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<4,2>>
! HEX-LABEL: func.func @_QPtest_char4_fixed
+! HEX: arith.constant 7 : index
! HEX: fir.do_loop %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} {
! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
! HEX: arith.constant {{.*}} : i8
@@ -515,6 +517,7 @@ subroutine test_char4_fixed(res)
! ---------------------------------------------------------------------------
! CHARACTER(kind=2, len=n) -- runtime-length, higher kind.
! hex/zero: byte-loop over n*2 bytes; skipped when n==0.
+! The multiply-by-kind must appear as arith.muli with a factor of 2.
! ---------------------------------------------------------------------------
subroutine test_char2_runtime(res, n)
integer, intent(in) :: n
@@ -523,12 +526,16 @@ subroutine test_char2_runtime(res, n)
res = x
end subroutine
! ZERO-LABEL: func.func @_QPtest_char2_runtime
+! ZERO: arith.constant 2 : index
+! ZERO: arith.muli {{.*}}, %{{.*}} : index
! ZERO: fir.do_loop
! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
! ZERO: fir.zero_bits !fir.char<1>
! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<1>>
! HEX-LABEL: func.func @_QPtest_char2_runtime
+! HEX: arith.constant 2 : index
+! HEX: arith.muli {{.*}}, %{{.*}} : index
! HEX: fir.do_loop
! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
! HEX: arith.constant {{.*}} : i8
>From de297e2aa9218108524dee49c507ba40ff499fa6 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Wed, 2 Sep 2026 13:08:51 -0400
Subject: [PATCH 12/17] Address MattPD's seventh round of review comments on
-finit-local=
- ConvertVariable.cpp: replace top-level IsVectorType() check in
shouldInitLocal with a new containsVectorComponent() helper that
recurses through derived-type component scopes, excluding records
that contain a vector component (would crash record-size calculation).
Update all related comments to describe the pre-fix behaviour (direct
vector local -> silent zero; derived-with-vector -> crash).
Add test_derived_with_vec to finit-local-ppc-vector-llvm.f90.
- ConvertVariable.cpp: fix makeIntCst for unsigned integer locals.
arith.constant requires a signless integer type; the integer branch
now emits a signless iN constant and follows it with fir.convert when
the declared element type is signed or unsigned, preserving the bit
pattern and satisfying FIR verification for !fir.ref<ui32>.
Add finit-local-unsigned.f90 covering hex and zero modes.
- FIRType.cpp: fix packed struct size to use allocation size per field.
The packed branch of getTypeSizeAndAlignment was summing store sizes
(compSize) instead of allocation sizes (alignTo(compSize, compAlign)),
diverging from LLVM's StructLayout::getTypeAllocSize. Example:
x86 {f80, i8} packed = alignTo(10,16)+1 = 17B, not 10+1 = 11B.
Add @fold_packed_f80_i8 to box-elesize-canonicalize.fir asserting 17.
- finit-local.f90: strengthen higher-kind CHARACTER checks.
Capture arith.constant values 5, 7, and 2 with named variables and
require each as the corresponding fir.do_loop upper bound or
arith.muli factor, so shortened bounds or a factor of 1 will fail.
- finit-local-packed.f90: fix header comment to name COMPLEX(8)
(not REAL(8)) and state the 24-vs-20 byte ordinary/packed distinction.
---
flang/lib/Lower/ConvertVariable.cpp | 67 ++++++++++++++++---
flang/lib/Optimizer/Dialect/FIRType.cpp | 13 ++--
flang/test/Fir/box-elesize-canonicalize.fir | 20 +++++-
flang/test/Lower/finit-local-packed.f90 | 9 ++-
.../Lower/finit-local-ppc-vector-llvm.f90 | 25 +++++++
flang/test/Lower/finit-local-unsigned.f90 | 29 ++++++++
flang/test/Lower/finit-local.f90 | 16 ++---
7 files changed, 148 insertions(+), 31 deletions(-)
create mode 100644 flang/test/Lower/finit-local-unsigned.f90
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 0a3542db6f49f..1a5af03249a2f 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1257,6 +1257,38 @@ getSafeRepackAttrs(Fortran::lower::AbstractConverter &converter) {
// -finit-local= helpers
//===----------------------------------------------------------------------===//
+/// Returns true if \p derived or any of its components (recursively) is a
+/// PowerPC vector type. fir::VectorType does not implement
+/// DataLayoutTypeInterface, so any record containing a vector component would
+/// crash record-size calculation. Excluding such records at eligibility time
+/// avoids the crash.
+static bool containsVectorComponent(
+ const Fortran::semantics::DerivedTypeSpec &derived) {
+ if (derived.IsVectorType())
+ return true;
+ const Fortran::semantics::Scope *scope = derived.GetScope();
+ if (!scope)
+ return false;
+ const Fortran::semantics::Symbol &typeSym = derived.typeSymbol();
+ const auto *details =
+ typeSym.detailsIf<Fortran::semantics::DerivedTypeDetails>();
+ if (!details)
+ return false;
+ for (const Fortran::semantics::SourceName &compName :
+ details->componentNames()) {
+ auto it = scope->find(compName);
+ if (it == scope->cend())
+ continue;
+ const Fortran::semantics::Symbol &comp = it->second.get();
+ if (const Fortran::semantics::DeclTypeSpec *compTy = comp.GetType())
+ if (const Fortran::semantics::DerivedTypeSpec *compDerived =
+ compTy->AsDerived())
+ if (containsVectorComponent(*compDerived))
+ return true;
+ }
+ return false;
+}
+
/// Returns true when \p var is an automatic local variable eligible for
/// -finit-local= initialization. Excluded: variables without a symbol,
/// globals, dummy arguments, SAVE'd vars, ALLOCATABLE/POINTER, vars in
@@ -1290,11 +1322,14 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
return false;
// PowerPC vector types (vector(real(4)) etc.) lower to fir::VectorType
// which does not implement DataLayoutTypeInterface at the HLFIR level.
- // Exclude them here so no initialization is attempted.
+ // Without this guard, a direct vector local would silently fall back to
+ // zero initialization regardless of the requested mode, and a derived-type
+ // local whose component is a vector type would crash record-size
+ // calculation. Exclude both cases here by walking components recursively.
if (const Fortran::semantics::DeclTypeSpec *declTy = sym.GetType())
if (const Fortran::semantics::DerivedTypeSpec *derived =
declTy->AsDerived())
- if (derived->IsVectorType())
+ if (containsVectorComponent(*derived))
return false;
// CUDA storage accessibility:
// constant / shared / usedevice: always unreachable by a plain fir.store
@@ -1323,14 +1358,17 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
/// and logical (raw integer, stored via bitcasted address by the caller).
/// Character, derived-type, and sequence types are all intercepted by
/// genInitLocalStore or initAddr before this function is called and must
-/// not reach it. fir::VectorType (PowerPC vector locals) is excluded
-/// upstream by shouldInitLocal and will never reach this function.
+/// not reach it. fir::VectorType (PowerPC vector types, direct or as a
+/// derived-type component) is excluded upstream by shouldInitLocal via
+/// containsVectorComponent and will never reach this function.
static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
mlir::Location loc, mlir::Type ty,
uint8_t bytePat) {
mlir::Type eleTy = fir::unwrapSequenceType(ty);
- // Build an integer constant of the given bit width from a byte splat.
+ // Build a signless integer constant from a byte splat. arith.constant
+ // requires a signless integer type; callers that need a non-signless result
+ // (e.g. unsigned ui32) must fir.convert the returned value themselves.
auto makeIntCst = [&](unsigned bits) -> mlir::Value {
llvm::APInt byteVal(8, bytePat);
llvm::APInt splat = llvm::APInt::getSplat(bits, byteVal);
@@ -1340,12 +1378,17 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
};
if (auto fpTy = mlir::dyn_cast<mlir::FloatType>(eleTy)) {
- unsigned bits = fpTy.getWidth();
- mlir::Value intCst = makeIntCst(bits);
+ mlir::Value intCst = makeIntCst(fpTy.getWidth());
return mlir::arith::BitcastOp::create(builder, loc, fpTy, intCst);
}
if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(eleTy)) {
- return makeIntCst(intTy.getWidth());
+ mlir::Value cst = makeIntCst(intTy.getWidth());
+ // arith.constant only supports signless integers; fir.convert reinterprets
+ // the bit pattern into the declared signed or unsigned type without
+ // changing any bits, satisfying FIR verification for !fir.ref<ui32> etc.
+ if (!intTy.isSignless())
+ cst = builder.createConvert(loc, intTy, cst);
+ return cst;
}
// Complex: apply the byte pattern to each (real, imag) part.
if (auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(eleTy)) {
@@ -1363,7 +1406,8 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
// All types that pass shouldInitLocal and reach genInitLocalStore are
// handled explicitly above (integer, float, complex, logical) or are
// intercepted before this call (character, record, sequence).
- // PowerPC vector types are excluded at shouldInitLocal and never reach here.
+ // PowerPC vector types (direct or as a derived-type component) are excluded
+ // by shouldInitLocal via containsVectorComponent and never reach here.
// A silent zero for an unhandled type would violate the hex-mode contract,
// so assert rather than fall back silently.
llvm_unreachable("genByteSplatInit: unhandled type in hex mode");
@@ -1493,8 +1537,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
/// Arrays: all modes use a flat fir.do_loop + fir.coordinate_of over a
/// rank-1 view to avoid both the llvm.mlir.constant crash on non-zero
/// ArrayAttrs and the quadratic compile time of fir.insert_on_range.
-/// Derived types use a byte-fill loop. PowerPC vector locals are
-/// excluded upstream by shouldInitLocal and never reach this function.
+/// Derived types use a byte-fill loop. PowerPC vector types (direct or as a
+/// derived-type component) are excluded upstream by shouldInitLocal via
+/// containsVectorComponent and never reach this function.
/// Scalars store directly via genInitLocalStore.
static void genInitLocal(Fortran::lower::AbstractConverter &converter,
const Fortran::lower::pft::Variable &var,
diff --git a/flang/lib/Optimizer/Dialect/FIRType.cpp b/flang/lib/Optimizer/Dialect/FIRType.cpp
index 493637521ca25..ff3f0a661bfda 100644
--- a/flang/lib/Optimizer/Dialect/FIRType.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRType.cpp
@@ -1664,16 +1664,19 @@ fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
if (recTy.isPacked()) {
// LLVM packed structs (<{ ... }>) place fields back-to-back with no
// inter-field alignment padding and no tail padding. Each component
- // occupies exactly its store size (dl.getTypeSize(), which is what
- // getTypeSizeAndAlignment returns as compSize). Unlike ordinary
- // structs, there is no per-component rounding of compSize to compAlign.
- // The packed struct ABI alignment is always 1.
+ // still occupies its allocation size (llvm::alignTo(storeSize, ABI
+ // alignment)), because LLVM's packed StructLayout advances by
+ // getTypeAllocSize, not getTypeStoreSize. For example, x86 f80 has
+ // store size 10 bytes but ABI alignment 16 bytes, so its allocation
+ // size is 16 bytes; a packed {f80, i8} therefore occupies 17 bytes,
+ // not 11. The packed struct's own ABI alignment is always 1.
for (auto component : recTy.getTypeList()) {
auto result =
getTypeSizeAndAlignment(loc, component.second, dl, kindMap);
if (!result)
return result;
- size += result->first; // store size only; no alignment rounding
+ auto [compSize, compAlign] = *result;
+ size += llvm::alignTo(compSize, compAlign); // allocation size per field
}
return std::pair{size, static_cast<unsigned short>(1)};
}
diff --git a/flang/test/Fir/box-elesize-canonicalize.fir b/flang/test/Fir/box-elesize-canonicalize.fir
index 64ae66faac66b..7b45264c9e2fc 100644
--- a/flang/test/Fir/box-elesize-canonicalize.fir
+++ b/flang/test/Fir/box-elesize-canonicalize.fir
@@ -106,8 +106,9 @@ module attributes { dlti.dl_spec = #dlti.dl_spec< i1 = dense<8> : vector<2xi64>,
// Fold: packed derived type -- fir.type<tp<{i:i32,d:f64}>>.
// This is the exact example from MattPD's review: on x86-64
// (f64 ABI align=8B) the non-packed size is 16B and the packed
- // size is 12B (i32 store=4B + f64 store=8B, no inter-field gap,
- // no tail padding). The element distance must be 12, not 16.
+ // size is 12B (i32 allocSize=4B + f64 allocSize=alignTo(8,8)=8B,
+ // no inter-field gap, no tail padding).
+ // The element distance must be 12, not 16.
func.func @fold_packed_record(%arg0: !fir.box<!fir.type<tp<{i:i32,d:f64}>>>) -> index {
%0 = fir.box_elesize %arg0 : (!fir.box<!fir.type<tp<{i:i32,d:f64}>>>) -> index
return %0 : index
@@ -130,4 +131,19 @@ module attributes { dlti.dl_spec = #dlti.dl_spec< i1 = dense<8> : vector<2xi64>,
// CHECK: %[[C:.*]] = arith.constant 20 : index
// CHECK: return %[[C]]
// CHECK-NOT: fir.box_elesize
+
+ // Fold: packed derived type with f80 component.
+ // f80 on x86-64: store size = 10B, ABI alignment = 16B,
+ // allocation size = alignTo(10, 16) = 16B.
+ // i8: store size = 1B, ABI alignment = 1B, allocation size = 1B.
+ // Packed size = 16 + 1 = 17B (not 10 + 1 = 11B).
+ // This case distinguishes allocation-size from store-size in the packed path.
+ func.func @fold_packed_f80_i8(%arg0: !fir.box<!fir.type<tp3<{x:f80,b:i8}>>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<!fir.type<tp3<{x:f80,b:i8}>>>) -> index
+ return %0 : index
+ }
+ // CHECK-LABEL: func.func @fold_packed_f80_i8(
+ // CHECK: %[[C:.*]] = arith.constant 17 : index
+ // CHECK: return %[[C]]
+ // CHECK-NOT: fir.box_elesize
}
diff --git a/flang/test/Lower/finit-local-packed.f90 b/flang/test/Lower/finit-local-packed.f90
index 1897d6452df82..1d10a642b3c0f 100644
--- a/flang/test/Lower/finit-local-packed.f90
+++ b/flang/test/Lower/finit-local-packed.f90
@@ -1,14 +1,13 @@
! AIX BIND(C) regression test for -finit-local= with packed derived types.
!
! On AIX, ConvertType creates a packed layout for a BIND(C) record whose
-! REAL(8) component is not first. The initialization loop must use the
+! COMPLEX(8) component is not first. The initialization loop must use the
! correct packed size.
!
! For {integer(4), complex(8)}: i32 store=4B, complex<f64> store=16B.
-! Packed size = 4+16 = 20B. Loop upper bound must be 19 (trip count 20).
-!
-! Note: on AIX f64 has 4-byte ABI alignment, so the non-packed size of
-! {i32, complex<f64>} also happens to be 20B (no inter-field gap needed).
+! Ordinary (non-packed) size = 24B (inter-field and tail padding included).
+! Packed size = 4+16 = 20B (fields placed back-to-back, no padding).
+! Loop upper bound 19 (trip count 20) distinguishes the two calculations.
! The platform-independent test that distinguishes packed from non-packed
! (using x86-64 alignment where they differ) lives in
! flang/test/Fir/box-elesize-canonicalize.fir.
diff --git a/flang/test/Lower/finit-local-ppc-vector-llvm.f90 b/flang/test/Lower/finit-local-ppc-vector-llvm.f90
index 0fd3b0613c572..263d5628aea19 100644
--- a/flang/test/Lower/finit-local-ppc-vector-llvm.f90
+++ b/flang/test/Lower/finit-local-ppc-vector-llvm.f90
@@ -1,6 +1,10 @@
! Tests that PowerPC vector locals (fir::VectorType) are excluded from
! -finit-local= initialization because fir::VectorType does not implement
! DataLayoutTypeInterface at the HLFIR level.
+! Before the fix, a direct vector local silently fell back to zero
+! initialization regardless of the requested mode (not a DataLayout crash).
+! A derived-type local with a vector component would crash record-size
+! calculation; that case is covered by test_derived_with_vec below.
! The local variable 'x' must not receive any initialization store.
!
! REQUIRES: target=powerpc{{.*}}
@@ -22,3 +26,24 @@ subroutine test_vec_real4(res)
! ZERO-LABEL: func.func @_QPtest_vec_real4(
! ZERO: hlfir.declare {{.*}}_QFtest_vec_real4Ex
! ZERO-NOT: fir.store {{.*}} to %{{.*}}#0
+
+! Derived type with a vector(real(4)) component -- the eligibility check must
+! walk components recursively and exclude this record, just as it excludes a
+! direct vector local. Before the fix, compilation would crash in record-size
+! calculation when it encountered the unsupported fir::VectorType component.
+subroutine test_derived_with_vec(res)
+ type :: tv
+ integer(4) :: i
+ vector(real(4)) :: v
+ end type
+ type(tv) :: x
+ res = x%i
+end subroutine
+
+! HEX-LABEL: func.func @_QPtest_derived_with_vec(
+! HEX: hlfir.declare {{.*}}_QFtest_derived_with_vecEx
+! HEX-NOT: fir.store {{.*}} to %{{.*}}#0
+
+! ZERO-LABEL: func.func @_QPtest_derived_with_vec(
+! ZERO: hlfir.declare {{.*}}_QFtest_derived_with_vecEx
+! ZERO-NOT: fir.store {{.*}} to %{{.*}}#0
diff --git a/flang/test/Lower/finit-local-unsigned.f90 b/flang/test/Lower/finit-local-unsigned.f90
new file mode 100644
index 0000000000000..cfc3d8ee91b4d
--- /dev/null
+++ b/flang/test/Lower/finit-local-unsigned.f90
@@ -0,0 +1,29 @@
+! Tests that -finit-local= preserves the declared unsigned integer type.
+! arith.constant only accepts signless integer types, so makeIntCst always
+! produces a signless iN constant. For unsigned locals the result is then
+! reinterpreted via fir.convert (iN -> uiN) before the store, which preserves
+! the bit pattern and satisfies FIR verification for !fir.ref<ui32>.
+! Before the fix, the signless i32 constant was stored directly into
+! !fir.ref<ui32>, failing FIR verification with
+! "store value type must match memory reference type".
+! Zero mode uses fir.zero_bits which is type-polymorphic and was not affected.
+!
+! RUN: %flang_fc1 -emit-hlfir -funsigned -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
+! RUN: %flang_fc1 -emit-hlfir -funsigned -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+
+subroutine test_unsigned(res)
+ unsigned :: res
+ unsigned :: x
+ res = x
+end subroutine
+
+! HEX-LABEL: func.func @_QPtest_unsigned(
+! HEX: %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_unsignedEx
+! HEX: %[[C:.*]] = arith.constant {{.*}} : i32
+! HEX: %[[U:.*]] = fir.convert %[[C]] : (i32) -> ui32
+! HEX: fir.store %[[U]] to %[[X]]#0 : !fir.ref<ui32>
+
+! ZERO-LABEL: func.func @_QPtest_unsigned(
+! ZERO: %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_unsignedEx
+! ZERO: %[[Z:.*]] = fir.zero_bits ui32
+! ZERO: fir.store %[[Z]] to %[[X]]#0 : !fir.ref<ui32>
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 8285240eab54e..995bc886f05e6 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -488,8 +488,8 @@ subroutine test_char2_fixed(res)
! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<2,3>>
! HEX-LABEL: func.func @_QPtest_char2_fixed
-! HEX: arith.constant 5 : index
-! HEX: fir.do_loop %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} {
+! HEX: %[[C5:.*]] = arith.constant 5 : index
+! HEX: fir.do_loop %{{.*}} = %{{.*}} to %[[C5]] step %{{.*}} {
! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
! HEX: arith.constant {{.*}} : i8
! HEX: fir.store {{.*}} : !fir.ref<i8>
@@ -508,8 +508,8 @@ subroutine test_char4_fixed(res)
! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<4,2>>
! HEX-LABEL: func.func @_QPtest_char4_fixed
-! HEX: arith.constant 7 : index
-! HEX: fir.do_loop %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} {
+! HEX: %[[C7:.*]] = arith.constant 7 : index
+! HEX: fir.do_loop %{{.*}} = %{{.*}} to %[[C7]] step %{{.*}} {
! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
! HEX: arith.constant {{.*}} : i8
! HEX: fir.store {{.*}} : !fir.ref<i8>
@@ -526,16 +526,16 @@ subroutine test_char2_runtime(res, n)
res = x
end subroutine
! ZERO-LABEL: func.func @_QPtest_char2_runtime
-! ZERO: arith.constant 2 : index
-! ZERO: arith.muli {{.*}}, %{{.*}} : index
+! ZERO: %[[C2:.*]] = arith.constant 2 : index
+! ZERO: arith.muli {{.*}}, %[[C2]] : index
! ZERO: fir.do_loop
! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
! ZERO: fir.zero_bits !fir.char<1>
! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<1>>
! HEX-LABEL: func.func @_QPtest_char2_runtime
-! HEX: arith.constant 2 : index
-! HEX: arith.muli {{.*}}, %{{.*}} : index
+! HEX: %[[C2:.*]] = arith.constant 2 : index
+! HEX: arith.muli {{.*}}, %[[C2]] : index
! HEX: fir.do_loop
! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
! HEX: arith.constant {{.*}} : i8
>From a6a6d4485ca5fbf8339988ac4f32f74818e88551 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Wed, 2 Sep 2026 16:03:09 -0400
Subject: [PATCH 13/17] Address eugeneepshteyn's review comments on
-finit-local= volatility and portability
- ConvertVariable.cpp: replace builder.createConvert with
builder.createConvertWithVolatileCast at all four byte-view
conversion sites (fixed-length CHARACTER hex loop, REAL/COMPLEX
allocation-gap loop, record byte-fill loop, runtime CHARACTER loop).
A plain fir.convert from a volatile ref to a non-volatile byte-sequence
ref fails the strict FIR volatile verifier; the new helper inserts
fir.volatile_cast before the fir.convert when the source is volatile.
- finit-local-real10-llvm.f90: pin both RUN lines to
-triple x86_64-unknown-linux-gnu so the test does not run with the
host triple on aarch64/ppc64le builders where real(10) is disabled.
- finit-local-volatile.f90 (new): regression test exercising both
affected code paths under -mmlir --strict-fir-volatile-verifier:
a volatile derived-type local (record byte-fill loop) and a volatile
real(10) local (allocation-gap loop, x86 only). Verifies that
fir.volatile_cast precedes the byte-sequence fir.convert in both
zero and hex modes.
---
flang/lib/Lower/ConvertVariable.cpp | 12 ++--
flang/test/Lower/finit-local-real10-llvm.f90 | 4 +-
flang/test/Lower/finit-local-volatile.f90 | 72 ++++++++++++++++++++
3 files changed, 82 insertions(+), 6 deletions(-)
create mode 100644 flang/test/Lower/finit-local-volatile.f90
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 1a5af03249a2f..3683c99e5a0db 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1446,7 +1446,8 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Type byteSeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, byteTy);
mlir::Value byteBase =
- builder.createConvert(loc, builder.getRefType(byteSeqTy), addr);
+ builder.createConvertWithVolatileCast(
+ loc, builder.getRefType(byteSeqTy), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last =
builder.createIntegerConstant(loc, idxTy, nBytes - 1);
@@ -1478,7 +1479,8 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Type i8SeqTy =
fir::SequenceType::get({fir::SequenceType::getUnknownExtent()}, i8Ty);
mlir::Value byteBase =
- builder.createConvert(loc, builder.getRefType(i8SeqTy), addr);
+ builder.createConvertWithVolatileCast(
+ loc, builder.getRefType(i8SeqTy), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last = builder.createIntegerConstant(
loc, idxTy, static_cast<int64_t>(nBytes) - 1);
@@ -1628,7 +1630,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::Type i8SeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, i8Ty);
mlir::Value byteBase =
- builder.createConvert(loc, builder.getRefType(i8SeqTy), addr);
+ builder.createConvertWithVolatileCast(
+ loc, builder.getRefType(i8SeqTy), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last = builder.createIntegerConstant(
loc, idxTy, static_cast<int64_t>(byteSize) - 1);
@@ -1697,7 +1700,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::Type byteSeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, byteTy);
mlir::Value byteBase =
- builder.createConvert(loc, builder.getRefType(byteSeqTy), base);
+ builder.createConvertWithVolatileCast(
+ loc, builder.getRefType(byteSeqTy), base);
mlir::Value byteAddr =
fir::CoordinateOp::create(builder, loc, builder.getRefType(byteTy),
byteBase, mlir::ValueRange{iv});
diff --git a/flang/test/Lower/finit-local-real10-llvm.f90 b/flang/test/Lower/finit-local-real10-llvm.f90
index 7456641e111e9..523831129a685 100644
--- a/flang/test/Lower/finit-local-real10-llvm.f90
+++ b/flang/test/Lower/finit-local-real10-llvm.f90
@@ -6,8 +6,8 @@
!
! REQUIRES: x86-registered-target
!
-! RUN: %flang_fc1 -emit-llvm -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
-! RUN: %flang_fc1 -emit-llvm -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+! RUN: %flang_fc1 -triple x86_64-unknown-linux-gnu -emit-llvm -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
+! RUN: %flang_fc1 -triple x86_64-unknown-linux-gnu -emit-llvm -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
! ---------------------------------------------------------------------------
! REAL(10) -- x86_fp80: store=10 bytes, alloc=16 bytes on x86-64.
diff --git a/flang/test/Lower/finit-local-volatile.f90 b/flang/test/Lower/finit-local-volatile.f90
new file mode 100644
index 0000000000000..57b5157cb9dbd
--- /dev/null
+++ b/flang/test/Lower/finit-local-volatile.f90
@@ -0,0 +1,72 @@
+! Tests that -finit-local= byte-fill loops correctly handle volatile locals.
+! Each byte-view conversion must go through fir.volatile_cast before the
+! fir.convert that reinterprets the address as a byte sequence, so that the
+! strict FIR volatile verifier does not see a "mismatched volatility" error.
+!
+! Before the fix both zero and hex modes emitted a plain fir.convert from
+! a volatile ref directly to a non-volatile byte-sequence ref, which the
+! strict verifier rejects with "mismatched volatility".
+!
+! Two shapes are covered:
+! 1. volatile derived-type local -- exercises the record byte-fill loop
+! in initAddr (fir.volatile_cast before the i8-array view convert).
+! 2. volatile real(10) local -- exercises the allocation-gap byte-fill
+! loop in genInitLocalStore (same fix, different site).
+! Requires x86 so that real(10) = x86_fp80 with a 6-byte padding gap.
+!
+! RUN: %flang_fc1 -emit-hlfir -mmlir --strict-fir-volatile-verifier \
+! RUN: -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+! RUN: %flang_fc1 -emit-hlfir -mmlir --strict-fir-volatile-verifier \
+! RUN: -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
+
+! ---------------------------------------------------------------------------
+! Volatile derived-type local -- record byte-fill loop path.
+! ---------------------------------------------------------------------------
+subroutine test_volatile_derived(oa)
+ type :: t
+ integer(4) :: a
+ integer(1) :: b
+ end type
+ type(t), volatile :: v
+ integer :: oa
+ v%a = 1
+ oa = v%a
+end subroutine
+
+! ZERO-LABEL: func.func @_QPtest_volatile_derived(
+! ZERO: %[[V:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_derivedEv
+! ZERO: %[[NV:.*]] = fir.volatile_cast %[[V]]#0 : (!fir.ref<{{.*}}, volatile>) -> !fir.ref<{{.*}}>
+! ZERO: fir.convert %[[NV]] : (!fir.ref<{{.*}}>) -> !fir.ref<!fir.array<?xi8>>
+! ZERO: fir.do_loop
+! ZERO: fir.store {{.*}} : !fir.ref<i8>
+
+! HEX-LABEL: func.func @_QPtest_volatile_derived(
+! HEX: %[[V:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_derivedEv
+! HEX: %[[NV:.*]] = fir.volatile_cast %[[V]]#0 : (!fir.ref<{{.*}}, volatile>) -> !fir.ref<{{.*}}>
+! HEX: fir.convert %[[NV]] : (!fir.ref<{{.*}}>) -> !fir.ref<!fir.array<?xi8>>
+! HEX: fir.do_loop
+! HEX: fir.store {{.*}} : !fir.ref<i8>
+
+! ---------------------------------------------------------------------------
+! Volatile real(10) local -- allocation-gap byte-fill loop path (x86 only).
+! ---------------------------------------------------------------------------
+! REQUIRES: x86-registered-target
+subroutine test_volatile_real10(res)
+ real(10), volatile :: x
+ real(10) :: res
+ res = x
+end subroutine
+
+! ZERO-LABEL: func.func @_QPtest_volatile_real10(
+! ZERO: %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_real10Ex
+! ZERO: %[[NV:.*]] = fir.volatile_cast %[[X]]#0 : (!fir.ref<f80, volatile>) -> !fir.ref<f80>
+! ZERO: fir.convert %[[NV]] : (!fir.ref<f80>) -> !fir.ref<!fir.array<?xi8>>
+! ZERO: fir.do_loop
+! ZERO: fir.store {{.*}} : !fir.ref<i8>
+
+! HEX-LABEL: func.func @_QPtest_volatile_real10(
+! HEX: %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_real10Ex
+! HEX: %[[NV:.*]] = fir.volatile_cast %[[X]]#0 : (!fir.ref<f80, volatile>) -> !fir.ref<f80>
+! HEX: fir.convert %[[NV]] : (!fir.ref<f80>) -> !fir.ref<!fir.array<?xi8>>
+! HEX: fir.do_loop
+! HEX: fir.store {{.*}} : !fir.ref<i8>
>From 45d65d2e6f1808fba6c3aeef6c3f73418d265368 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Wed, 2 Sep 2026 20:02:48 -0400
Subject: [PATCH 14/17] [flang] Fix -finit-local= exclusions, kind-mapping
width, and CUDA tests
- shouldInitLocal: exclude function result variables (IsFuncResult)
and main-program locals (implicit SAVE, Scope::Kind::MainProgram).
- Add finit-local-exclusions.f90: pinned %flang_fc1 -emit-hlfir
-finit-local=0xAA + CHECK-NOT tests for all documented exclusions
(function result, main-program implicit SAVE, runtime-sized array,
CHARACTER array, POINTER-component derived type).
- Fix getFKind() used as byte width at logical/character sites; use
KindMapping::getLogicalBitsize()/getCharacterBitsize() instead.
Addresses eugeneepshteyn's review comment.
- Add finit-local-kind-mapping.f90: --kind-mapping=l4:8 regression test.
- finit-local-cuda.cuf: tighten test_global_local with CHECK-NEXT and
%[[NZ]] to bind the zero-init store to the user-declared n.
Addresses MattPD's review comment.
---
flang/lib/Lower/ConvertVariable.cpp | 57 +++++++-----
flang/test/Lower/CUDA/finit-local-cuda.cuf | 6 +-
flang/test/Lower/finit-local-exclusions.f90 | 92 +++++++++++++++++++
flang/test/Lower/finit-local-kind-mapping.f90 | 33 +++++++
4 files changed, 163 insertions(+), 25 deletions(-)
create mode 100644 flang/test/Lower/finit-local-exclusions.f90
create mode 100644 flang/test/Lower/finit-local-kind-mapping.f90
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 3683c99e5a0db..736358077f91d 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1262,8 +1262,8 @@ getSafeRepackAttrs(Fortran::lower::AbstractConverter &converter) {
/// DataLayoutTypeInterface, so any record containing a vector component would
/// crash record-size calculation. Excluding such records at eligibility time
/// avoids the crash.
-static bool containsVectorComponent(
- const Fortran::semantics::DerivedTypeSpec &derived) {
+static bool
+containsVectorComponent(const Fortran::semantics::DerivedTypeSpec &derived) {
if (derived.IsVectorType())
return true;
const Fortran::semantics::Scope *scope = derived.GetScope();
@@ -1303,6 +1303,15 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
const Fortran::semantics::Symbol &sym = var.getSymbol();
if (Fortran::semantics::IsDummy(sym))
return false;
+ // Function result variables own the return-value storage and must not be
+ // pre-initialized: the function body is responsible for setting the result.
+ if (sym.IsFuncResult())
+ return false;
+ // Main-program locals have implicit SAVE semantics (Fortran 2018 8.5.16p4).
+ // IsSaved() does not catch this case because the SAVE attribute is implicit
+ // rather than explicit, so check the enclosing scope kind directly.
+ if (sym.owner().kind() == Fortran::semantics::Scope::Kind::MainProgram)
+ return false;
if (Fortran::semantics::IsSaved(sym))
return false;
if (Fortran::semantics::IsAllocatableOrPointer(sym))
@@ -1397,11 +1406,14 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
return mlir::complex::CreateOp::create(builder, loc, cplxTy, partVal,
partVal);
}
- // LOGICAL(k) has a fixed size of k bytes. Return the raw integer splat;
- // the caller stores it via a bitcasted address to preserve the bit pattern
+ // LOGICAL(k) has a fixed size of k bytes under the default kind mapping,
+ // but a non-default mapping (e.g. --kind-mapping=l4:8) may map LOGICAL(4)
+ // to a single byte. Use KindMapping::getLogicalBitsize so the constant
+ // width matches the actual allocation size.
+ // The caller stores it via a bitcasted address to preserve the bit pattern
// (fir.convert from integer to !fir.logical normalizes nonzero -> true).
if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
- return makeIntCst(logTy.getFKind() * 8);
+ return makeIntCst(builder.getKindMap().getLogicalBitsize(logTy.getFKind()));
}
// All types that pass shouldInitLocal and reach genInitLocalStore are
// handled explicitly above (integer, float, complex, logical) or are
@@ -1433,9 +1445,11 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
// code unit is one byte; for kind=2/4 (UTF-16/32) each code unit is
// kind bytes wide. We use a kind=1 singleton as the view element so
// fir.coordinate_of advances exactly one byte per step, and iterate
- // nUnits * kind times to cover all bytes.
+ // nUnits * kindBytes times to cover all bytes. Use KindMapping to
+ // get the true byte width under any --kind-mapping override.
int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
- int64_t kindBytes = charTy.getFKind();
+ int64_t kindBytes =
+ builder.getKindMap().getCharacterBitsize(charTy.getFKind()) / 8;
int64_t nBytes = nUnits * kindBytes;
if (nBytes > 0) {
mlir::Type idxTy = builder.getIndexType();
@@ -1445,9 +1459,8 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
fir::CharacterType::getSingleton(builder.getContext(), 1);
mlir::Type byteSeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, byteTy);
- mlir::Value byteBase =
- builder.createConvertWithVolatileCast(
- loc, builder.getRefType(byteSeqTy), addr);
+ mlir::Value byteBase = builder.createConvertWithVolatileCast(
+ loc, builder.getRefType(byteSeqTy), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last =
builder.createIntegerConstant(loc, idxTy, nBytes - 1);
@@ -1478,9 +1491,8 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Type i8Ty = builder.getIntegerType(8);
mlir::Type i8SeqTy =
fir::SequenceType::get({fir::SequenceType::getUnknownExtent()}, i8Ty);
- mlir::Value byteBase =
- builder.createConvertWithVolatileCast(
- loc, builder.getRefType(i8SeqTy), addr);
+ mlir::Value byteBase = builder.createConvertWithVolatileCast(
+ loc, builder.getRefType(i8SeqTy), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last = builder.createIntegerConstant(
loc, idxTy, static_cast<int64_t>(nBytes) - 1);
@@ -1526,7 +1538,8 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
// logical true).
if (mode == Fortran::lower::InitLocalKind::Hex &&
mlir::isa<fir::LogicalType>(ty)) {
- unsigned bits = mlir::cast<fir::LogicalType>(ty).getFKind() * 8;
+ auto logTy = mlir::cast<fir::LogicalType>(ty);
+ unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
mlir::Type intRefTy = builder.getRefType(builder.getIntegerType(bits));
mlir::Value intAddr = builder.createConvert(loc, intRefTy, addr);
fir::StoreOp::create(builder, loc, val, intAddr);
@@ -1629,9 +1642,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::Type i8Ty = builder.getIntegerType(8);
mlir::Type i8SeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, i8Ty);
- mlir::Value byteBase =
- builder.createConvertWithVolatileCast(
- loc, builder.getRefType(i8SeqTy), addr);
+ mlir::Value byteBase = builder.createConvertWithVolatileCast(
+ loc, builder.getRefType(i8SeqTy), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last = builder.createIntegerConstant(
loc, idxTy, static_cast<int64_t>(byteSize) - 1);
@@ -1679,9 +1691,11 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
// For kind>1 (UTF-16/32) the runtime length is in code units; multiply
- // by kind to get the total byte count before computing the loop bound.
+ // by the kind byte width to get the total byte count. Use KindMapping
+ // so a --kind-mapping override is respected.
// Use a kind=1 singleton so fir.coordinate_of advances 1 byte per step.
- int64_t kindBytes = charTy.getFKind();
+ int64_t kindBytes =
+ builder.getKindMap().getCharacterBitsize(charTy.getFKind()) / 8;
if (kindBytes > 1) {
mlir::Value kindCst =
builder.createIntegerConstant(loc, idxTy, kindBytes);
@@ -1699,9 +1713,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
fir::CharacterType::getSingleton(builder.getContext(), 1);
mlir::Type byteSeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, byteTy);
- mlir::Value byteBase =
- builder.createConvertWithVolatileCast(
- loc, builder.getRefType(byteSeqTy), base);
+ mlir::Value byteBase = builder.createConvertWithVolatileCast(
+ loc, builder.getRefType(byteSeqTy), base);
mlir::Value byteAddr =
fir::CoordinateOp::create(builder, loc, builder.getRefType(byteTy),
byteBase, mlir::ValueRange{iv});
diff --git a/flang/test/Lower/CUDA/finit-local-cuda.cuf b/flang/test/Lower/CUDA/finit-local-cuda.cuf
index 85fdec3b2afda..2a3233d87caaf 100644
--- a/flang/test/Lower/CUDA/finit-local-cuda.cuf
+++ b/flang/test/Lower/CUDA/finit-local-cuda.cuf
@@ -61,9 +61,9 @@ attributes(global) subroutine test_global_local()
n = 42
end subroutine
! CHECK-LABEL: func.func @{{.*}}test_global_local
-! CHECK: %[[N:.*]]:2 = hlfir.declare {{.*}}"_QFtest_global_localEn"
-! CHECK: fir.zero_bits i32
-! CHECK: fir.store %{{.*}} to %[[N]]#0 : !fir.ref<i32>
+! CHECK: %[[N:.*]]:2 = hlfir.declare {{.*}}"_QFtest_global_localEn"
+! CHECK-NEXT: %[[NZ:.*]] = fir.zero_bits i32
+! CHECK-NEXT: fir.store %[[NZ]] to %[[N]]#0 : !fir.ref<i32>
! An explicit 'device' local in an attributes(host,device) routine is
! allocated via cuf.alloc (not fir.alloca) because cuf::isCUDADeviceContext
diff --git a/flang/test/Lower/finit-local-exclusions.f90 b/flang/test/Lower/finit-local-exclusions.f90
new file mode 100644
index 0000000000000..faa01f0023ad6
--- /dev/null
+++ b/flang/test/Lower/finit-local-exclusions.f90
@@ -0,0 +1,92 @@
+! Pinned negative tests for -finit-local= exclusions.
+!
+! Each case uses %flang_fc1 -emit-hlfir -finit-local=0xAA and asserts that
+! no synthesized initialization store appears for the excluded variable.
+!
+! Exclusions verified here:
+! 1. Function result variable
+! 2. Main-program local (implicit SAVE per Fortran 2018 8.5.16p4)
+! 3. Runtime-sized automatic array (unknown extent at compile time)
+! 4. CHARACTER array (pending memset infrastructure, PR #159788)
+! 5. Derived type with POINTER component (IsAllocatableOrPointer on component)
+! 6. host,device CUDA local (allocated via cuf.alloc, not fir.alloca)
+! -- see finit-local-cuda.cuf for the CUDA cases
+! 7. PowerPC vector locals (direct or as a derived-type component)
+! -- see finit-local-ppc-vector-llvm.f90 for the vector cases
+
+! RUN: %flang_fc1 -emit-hlfir -finit-local=0xAA %s -o - | FileCheck %s
+
+! ---------------------------------------------------------------------------
+! 1. Function result variable -- must NOT be pre-initialized.
+! The function body owns the result; a synthesized store would overwrite
+! a value already assigned before the body's first user store.
+! ---------------------------------------------------------------------------
+function test_func_result() result(x)
+ integer :: x
+ x = 42
+end function
+! CHECK-LABEL: func.func @_QPtest_func_result
+! CHECK: hlfir.declare {{.*}} "_QFtest_func_resultEx"
+! CHECK-NOT: fir.store
+! CHECK: hlfir.assign
+! CHECK: return
+
+! ---------------------------------------------------------------------------
+! 2. Main-program local -- implicit SAVE (Fortran 2018 8.5.16p4).
+! IsSaved() misses the implicit case; the scope-kind guard catches it.
+! ---------------------------------------------------------------------------
+program test_main_prog
+ integer :: n
+ n = 1
+end program
+! CHECK-LABEL: func.func @_QQmain
+! CHECK: hlfir.declare {{.*}} "_QFEn"
+! CHECK-NOT: fir.store
+! CHECK: hlfir.assign
+! CHECK: return
+
+! ---------------------------------------------------------------------------
+! 3. Runtime-sized automatic array -- extent is unknown at compile time.
+! The array base is a fir.box; no compile-time trip count is available.
+! ---------------------------------------------------------------------------
+subroutine test_runtime_array(res, n)
+ integer, intent(in) :: n
+ integer :: x(n)
+ integer :: res
+ res = x(1)
+end subroutine
+! CHECK-LABEL: func.func @_QPtest_runtime_array
+! CHECK: hlfir.declare {{.*}} "_QFtest_runtime_arrayEx"
+! CHECK-NOT: fir.do_loop
+! CHECK: return
+
+! ---------------------------------------------------------------------------
+! 4. CHARACTER array -- pending memset infrastructure (PR #159788).
+! Static CHARACTER scalars are initialized; arrays are silently skipped.
+! ---------------------------------------------------------------------------
+subroutine test_char_array(res)
+ character(4) :: x(3)
+ character(4) :: res
+ res = x(1)
+end subroutine
+! CHECK-LABEL: func.func @_QPtest_char_array
+! CHECK: hlfir.declare {{.*}} "_QFtest_char_arrayEx"
+! CHECK-NOT: fir.do_loop
+! CHECK: return
+
+! ---------------------------------------------------------------------------
+! 5. Derived type with POINTER component -- the component descriptor must
+! not be byte-stomped; IsAllocatableOrPointer on the record catches it.
+! ---------------------------------------------------------------------------
+subroutine test_pointer_comp(res)
+ type t
+ integer, pointer :: p
+ end type
+ type(t) :: x
+ integer :: res
+ res = 0
+end subroutine
+! CHECK-LABEL: func.func @_QPtest_pointer_comp
+! CHECK: hlfir.declare {{.*}} "_QFtest_pointer_compEx"
+! CHECK-NOT: fir.do_loop
+! CHECK: return
diff --git a/flang/test/Lower/finit-local-kind-mapping.f90 b/flang/test/Lower/finit-local-kind-mapping.f90
new file mode 100644
index 0000000000000..5e8f92813903a
--- /dev/null
+++ b/flang/test/Lower/finit-local-kind-mapping.f90
@@ -0,0 +1,33 @@
+! Tests that -finit-local= respects non-default kind mappings.
+! Under the default mapping getFKind() happens to equal the byte width, but
+! with --kind-mapping overrides the two diverge. All width calculations must
+! use KindMapping::getLogicalBitsize() / getCharacterBitsize() rather than
+! getFKind() * 8 or getFKind() directly.
+!
+! Reproducer: --kind-mapping=l4:8 maps LOGICAL(4) to 8 bits (1 byte).
+! Before the fix, the synthesized init emitted a 4-byte i32 constant stored
+! via a bitcasted i32* into a 1-byte alloca -- a 3-byte out-of-bounds write.
+! After the fix the constant and store are both i8.
+!
+! RUN: bbc -emit-hlfir --kind-mapping=l4:8 -finit-local=0xAA %s -o - | \
+! RUN: FileCheck --check-prefix=HEX %s
+! RUN: bbc -emit-hlfir --kind-mapping=l4:8 -finit-local=zero %s -o - | \
+! RUN: FileCheck --check-prefix=ZERO %s
+
+! LOGICAL(4) remapped to 8 bits -- init must use i8, not i32.
+subroutine test_logical4_km(res)
+ logical(kind=4) :: l
+ integer :: res
+ if (l) res = 1
+end subroutine
+
+! HEX-LABEL: func.func @_QPtest_logical4_km(
+! HEX: %[[L:.*]]:2 = hlfir.declare {{.*}}_QFtest_logical4_kmEl
+! HEX: %[[C:.*]] = arith.constant {{.*}} : i8
+! HEX: %[[ADDR:.*]] = fir.convert %[[L]]#0 : (!fir.ref<!fir.logical<4>>) -> !fir.ref<i8>
+! HEX: fir.store %[[C]] to %[[ADDR]] : !fir.ref<i8>
+
+! ZERO-LABEL: func.func @_QPtest_logical4_km(
+! ZERO: %[[L:.*]]:2 = hlfir.declare {{.*}}_QFtest_logical4_kmEl
+! ZERO: %[[Z:.*]] = fir.zero_bits !fir.logical<4>
+! ZERO: fir.store %[[Z]] to %[[L]]#0 : !fir.ref<!fir.logical<4>>
>From 19b99fac42401d6d886f8308e0246736bdceeb17 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Wed, 2 Sep 2026 21:00:33 -0400
Subject: [PATCH 15/17] [flang] Fix -finit-local= to preserve volatility
end-to-end in byte-fill loops
Pass the source address volatility flag to getRefType() at all byte-fill
sites (fixed-length CHARACTER, REAL/COMPLEX allocation-gap, record fill,
runtime CHARACTER, rank-1 array view, LOGICAL bitcast) so that byte-seq
refs and CoordinateOp result types are volatile when the source is.
createConvertWithVolatileCast then emits a direct volatile fir.convert
(no fir.volatile_cast needed) and final stores are "store volatile".
Also fix two missing volatile flags from the earlier PRRT_kwDOBITxeM6etGbi
fix: rank1SeqTy and intRefTy were built without the volatile flag.
Add finit-local-volatile-real10.f90 (pinned -triple x86_64-unknown-linux-gnu)
for the real(10) allocation-gap path, removing it from the unpinned
finit-local-volatile.f90. Add finit-local-volatile-llvm.f90 for LLVM IR
"store volatile" checks across derived-type, real(10), and array paths.
Addresses MattPD's review comments PRRT_kwDOBITxeM6etGbn and
PRRT_kwDOBITxeM6etGbr.
---
flang/lib/Lower/ConvertVariable.cpp | 82 ++++++++++++------
.../Lower/finit-local-ppc-vector-llvm.f90 | 72 ++++++++++++----
flang/test/Lower/finit-local-unsigned.f90 | 2 +-
.../test/Lower/finit-local-volatile-llvm.f90 | 65 +++++++++++++++
.../Lower/finit-local-volatile-real10.f90 | 30 +++++++
flang/test/Lower/finit-local-volatile.f90 | 83 +++++++++++--------
6 files changed, 258 insertions(+), 76 deletions(-)
create mode 100644 flang/test/Lower/finit-local-volatile-llvm.f90
create mode 100644 flang/test/Lower/finit-local-volatile-real10.f90
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 736358077f91d..54ccb825c7498 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1259,9 +1259,13 @@ getSafeRepackAttrs(Fortran::lower::AbstractConverter &converter) {
/// Returns true if \p derived or any of its components (recursively) is a
/// PowerPC vector type. fir::VectorType does not implement
-/// DataLayoutTypeInterface, so any record containing a vector component would
-/// crash record-size calculation. Excluding such records at eligibility time
-/// avoids the crash.
+/// DataLayoutTypeInterface. Two pre-fix failure modes existed:
+/// - Direct vector local: silently initialized to zero regardless of mode
+/// (historical behavior); at the current head genByteSplatInit would
+/// hit llvm_unreachable instead.
+/// - Derived-type local with a vector component: crashed in record-size
+/// calculation when DataLayoutTypeInterface was queried.
+/// Excluding both cases at eligibility time avoids both failure modes.
static bool
containsVectorComponent(const Fortran::semantics::DerivedTypeSpec &derived) {
if (derived.IsVectorType())
@@ -1331,10 +1335,13 @@ static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
return false;
// PowerPC vector types (vector(real(4)) etc.) lower to fir::VectorType
// which does not implement DataLayoutTypeInterface at the HLFIR level.
- // Without this guard, a direct vector local would silently fall back to
- // zero initialization regardless of the requested mode, and a derived-type
- // local whose component is a vector type would crash record-size
- // calculation. Exclude both cases here by walking components recursively.
+ // Without this guard:
+ // - A direct vector local would hit llvm_unreachable in genByteSplatInit
+ // (historically it silently fell back to zero before that assert was
+ // added).
+ // - A derived-type local with a vector component would crash in
+ // record-size calculation when DataLayoutTypeInterface was queried.
+ // Exclude both cases by walking components recursively.
if (const Fortran::semantics::DeclTypeSpec *declTy = sym.GetType())
if (const Fortran::semantics::DerivedTypeSpec *derived =
declTy->AsDerived())
@@ -1429,6 +1436,9 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
/// Fixed-length CHARACTER in hex mode: byte-loop over every byte of storage.
/// LOGICAL stores via a bitcasted integer address to preserve the raw bit
/// pattern past fir.convert normalization.
+/// All byte-view and coordinate types carry the source address volatility so
+/// that final stores are emitted as "store volatile" when the variable is
+/// volatile.
static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Type ty, mlir::Value addr,
Fortran::lower::InitLocalKind mode,
@@ -1459,8 +1469,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
fir::CharacterType::getSingleton(builder.getContext(), 1);
mlir::Type byteSeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, byteTy);
+ bool addrVolatile1 = fir::isa_volatile_type(addr.getType());
mlir::Value byteBase = builder.createConvertWithVolatileCast(
- loc, builder.getRefType(byteSeqTy), addr);
+ loc, builder.getRefType(byteSeqTy, addrVolatile1), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last =
builder.createIntegerConstant(loc, idxTy, nBytes - 1);
@@ -1471,13 +1482,15 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(loop.getBody());
mlir::Value iv = loop.getInductionVar();
+ bool byteVolatile1 = fir::isa_volatile_type(byteBase.getType());
mlir::Value byteAddr =
- fir::CoordinateOp::create(builder, loc, builder.getRefType(byteTy),
+ fir::CoordinateOp::create(builder, loc,
+ builder.getRefType(byteTy, byteVolatile1),
byteBase, mlir::ValueRange{iv});
mlir::Value pat = builder.createIntegerConstant(
loc, i8Ty, static_cast<int64_t>(hexByte));
- mlir::Value i8Addr =
- builder.createConvert(loc, builder.getRefType(i8Ty), byteAddr);
+ mlir::Value i8Addr = builder.createConvertWithVolatileCast(
+ loc, builder.getRefType(i8Ty, byteVolatile1), byteAddr);
fir::StoreOp::create(builder, loc, pat, i8Addr);
}
return;
@@ -1491,8 +1504,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Type i8Ty = builder.getIntegerType(8);
mlir::Type i8SeqTy =
fir::SequenceType::get({fir::SequenceType::getUnknownExtent()}, i8Ty);
+ bool addrVolatile2 = fir::isa_volatile_type(addr.getType());
mlir::Value byteBase = builder.createConvertWithVolatileCast(
- loc, builder.getRefType(i8SeqTy), addr);
+ loc, builder.getRefType(i8SeqTy, addrVolatile2), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last = builder.createIntegerConstant(
loc, idxTy, static_cast<int64_t>(nBytes) - 1);
@@ -1503,8 +1517,10 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(loop.getBody());
mlir::Value iv = loop.getInductionVar();
+ bool byteVolatile2 = fir::isa_volatile_type(byteBase.getType());
mlir::Value byteAddr = fir::CoordinateOp::create(
- builder, loc, builder.getRefType(i8Ty), byteBase, mlir::ValueRange{iv});
+ builder, loc, builder.getRefType(i8Ty, byteVolatile2), byteBase,
+ mlir::ValueRange{iv});
int64_t fillByte =
(mode == Fortran::lower::InitLocalKind::Zero) ? 0 : hexByte;
mlir::Value pat = builder.createIntegerConstant(loc, i8Ty, fillByte);
@@ -1540,8 +1556,11 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::isa<fir::LogicalType>(ty)) {
auto logTy = mlir::cast<fir::LogicalType>(ty);
unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
- mlir::Type intRefTy = builder.getRefType(builder.getIntegerType(bits));
- mlir::Value intAddr = builder.createConvert(loc, intRefTy, addr);
+ bool logVolatile = fir::isa_volatile_type(addr.getType());
+ mlir::Type intRefTy =
+ builder.getRefType(builder.getIntegerType(bits), logVolatile);
+ mlir::Value intAddr =
+ builder.createConvertWithVolatileCast(loc, intRefTy, addr);
fir::StoreOp::create(builder, loc, val, intAddr);
} else {
fir::StoreOp::create(builder, loc, val, addr);
@@ -1552,8 +1571,11 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
/// Arrays: all modes use a flat fir.do_loop + fir.coordinate_of over a
/// rank-1 view to avoid both the llvm.mlir.constant crash on non-zero
/// ArrayAttrs and the quadratic compile time of fir.insert_on_range.
-/// Derived types use a byte-fill loop. PowerPC vector types (direct or as a
-/// derived-type component) are excluded upstream by shouldInitLocal via
+/// Derived types use a byte-fill loop. All byte-view and coordinate types
+/// carry the source address volatility so that stores into volatile variables
+/// are emitted as "store volatile" end-to-end.
+/// PowerPC vector types (direct or as a derived-type component) are excluded
+/// upstream by shouldInitLocal via
/// containsVectorComponent and never reach this function.
/// Scalars store directly via genInitLocalStore.
static void genInitLocal(Fortran::lower::AbstractConverter &converter,
@@ -1613,8 +1635,10 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::Type idxTy = builder.getIndexType();
mlir::Type rank1SeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, eleTy);
- mlir::Value rank1Addr = builder.createConvert(
- loc, builder.getRefType(rank1SeqTy), addr);
+ bool rank1Volatile = fir::isa_volatile_type(addr.getType());
+ mlir::Value rank1Addr =
+ builder.createConvertWithVolatileCast(
+ loc, builder.getRefType(rank1SeqTy, rank1Volatile), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last =
builder.createIntegerConstant(loc, idxTy, totalElems - 1);
@@ -1625,9 +1649,10 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(loop.getBody());
mlir::Value iv = loop.getInductionVar();
+ bool elemVolatile = fir::isa_volatile_type(rank1Addr.getType());
mlir::Value elemAddr = fir::CoordinateOp::create(
- builder, loc, builder.getRefType(eleTy), rank1Addr,
- mlir::ValueRange{iv});
+ builder, loc, builder.getRefType(eleTy, elemVolatile),
+ rank1Addr, mlir::ValueRange{iv});
initAddr(eleTy, elemAddr);
}
} else if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty)) {
@@ -1642,8 +1667,9 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::Type i8Ty = builder.getIntegerType(8);
mlir::Type i8SeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, i8Ty);
+ bool addrVolatile3 = fir::isa_volatile_type(addr.getType());
mlir::Value byteBase = builder.createConvertWithVolatileCast(
- loc, builder.getRefType(i8SeqTy), addr);
+ loc, builder.getRefType(i8SeqTy, addrVolatile3), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last = builder.createIntegerConstant(
loc, idxTy, static_cast<int64_t>(byteSize) - 1);
@@ -1654,9 +1680,10 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(loop.getBody());
mlir::Value iv = loop.getInductionVar();
+ bool byteVolatile3 = fir::isa_volatile_type(byteBase.getType());
mlir::Value byteAddr = fir::CoordinateOp::create(
- builder, loc, builder.getRefType(i8Ty), byteBase,
- mlir::ValueRange{iv});
+ builder, loc, builder.getRefType(i8Ty, byteVolatile3),
+ byteBase, mlir::ValueRange{iv});
int64_t fillByte =
(mode == Fortran::lower::InitLocalKind::Zero) ? 0 : hexByte;
mlir::Value pat =
@@ -1713,10 +1740,13 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
fir::CharacterType::getSingleton(builder.getContext(), 1);
mlir::Type byteSeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, byteTy);
+ bool baseVolatile = fir::isa_volatile_type(base.getType());
mlir::Value byteBase = builder.createConvertWithVolatileCast(
- loc, builder.getRefType(byteSeqTy), base);
+ loc, builder.getRefType(byteSeqTy, baseVolatile), base);
+ bool byteVolatile4 = fir::isa_volatile_type(byteBase.getType());
mlir::Value byteAddr =
- fir::CoordinateOp::create(builder, loc, builder.getRefType(byteTy),
+ fir::CoordinateOp::create(builder, loc,
+ builder.getRefType(byteTy, byteVolatile4),
byteBase, mlir::ValueRange{iv});
genInitLocalStore(builder, loc, byteTy, byteAddr, mode, hexByte);
return;
diff --git a/flang/test/Lower/finit-local-ppc-vector-llvm.f90 b/flang/test/Lower/finit-local-ppc-vector-llvm.f90
index 263d5628aea19..ee4654f55dff9 100644
--- a/flang/test/Lower/finit-local-ppc-vector-llvm.f90
+++ b/flang/test/Lower/finit-local-ppc-vector-llvm.f90
@@ -1,18 +1,50 @@
! Tests that PowerPC vector locals (fir::VectorType) are excluded from
! -finit-local= initialization because fir::VectorType does not implement
! DataLayoutTypeInterface at the HLFIR level.
-! Before the fix, a direct vector local silently fell back to zero
-! initialization regardless of the requested mode (not a DataLayout crash).
-! A derived-type local with a vector component would crash record-size
-! calculation; that case is covered by test_derived_with_vec below.
-! The local variable 'x' must not receive any initialization store.
+!
+! History of pre-fix behavior:
+! - Direct vector local: silently fell back to zero initialization
+! regardless of the requested mode (i.e. hex mode produced zero, not
+! the requested byte pattern).
+! - Derived-type local with a vector component: compilation crashed in
+! record-size calculation when it encountered the unsupported
+! fir::VectorType. The crash occurred before any store was emitted.
+! At the current head, if a vector type somehow reached genByteSplatInit,
+! it would hit llvm_unreachable rather than silently produce zero.
!
! REQUIRES: target=powerpc{{.*}}
!
! RUN: %flang_fc1 -emit-hlfir -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
! RUN: %flang_fc1 -emit-hlfir -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
-! vector(real(4)) -- excluded; must not produce any fir.store for 'x'.
+! ---------------------------------------------------------------------------
+! Positive control: ordinary record (no vector component) IS initialized.
+! This ensures the vector-exclusion checks below are not trivially satisfied
+! by a compiler that never initializes any record.
+! ---------------------------------------------------------------------------
+subroutine test_plain_record(res)
+ type :: tp
+ integer(4) :: a
+ integer(4) :: b
+ end type
+ type(tp) :: x
+ integer :: res
+ res = x%a
+end subroutine
+
+! HEX-LABEL: func.func @_QPtest_plain_record(
+! HEX: hlfir.declare {{.*}}_QFtest_plain_recordEx
+! HEX: fir.do_loop
+! HEX: return
+
+! ZERO-LABEL: func.func @_QPtest_plain_record(
+! ZERO: hlfir.declare {{.*}}_QFtest_plain_recordEx
+! ZERO: fir.do_loop
+! ZERO: return
+
+! ---------------------------------------------------------------------------
+! Direct vector(real(4)) local -- excluded; no initialization store or loop.
+! ---------------------------------------------------------------------------
subroutine test_vec_real4(res)
vector(real(4)) :: res
vector(real(4)) :: x
@@ -21,29 +53,39 @@ subroutine test_vec_real4(res)
! HEX-LABEL: func.func @_QPtest_vec_real4(
! HEX: hlfir.declare {{.*}}_QFtest_vec_real4Ex
-! HEX-NOT: fir.store {{.*}} to %{{.*}}#0
+! HEX-NOT: fir.do_loop
+! HEX-NOT: fir.store
+! HEX: return
! ZERO-LABEL: func.func @_QPtest_vec_real4(
! ZERO: hlfir.declare {{.*}}_QFtest_vec_real4Ex
-! ZERO-NOT: fir.store {{.*}} to %{{.*}}#0
+! ZERO-NOT: fir.do_loop
+! ZERO-NOT: fir.store
+! ZERO: return
-! Derived type with a vector(real(4)) component -- the eligibility check must
-! walk components recursively and exclude this record, just as it excludes a
-! direct vector local. Before the fix, compilation would crash in record-size
-! calculation when it encountered the unsupported fir::VectorType component.
+! ---------------------------------------------------------------------------
+! Derived type with a vector(real(4)) component -- excluded recursively.
+! The containsVectorComponent helper walks component scopes so that record-
+! size calculation is never attempted for this type.
+! ---------------------------------------------------------------------------
subroutine test_derived_with_vec(res)
type :: tv
- integer(4) :: i
+ integer(4) :: i
vector(real(4)) :: v
end type
type(tv) :: x
+ integer :: res
res = x%i
end subroutine
! HEX-LABEL: func.func @_QPtest_derived_with_vec(
! HEX: hlfir.declare {{.*}}_QFtest_derived_with_vecEx
-! HEX-NOT: fir.store {{.*}} to %{{.*}}#0
+! HEX-NOT: fir.do_loop
+! HEX-NOT: fir.store
+! HEX: return
! ZERO-LABEL: func.func @_QPtest_derived_with_vec(
! ZERO: hlfir.declare {{.*}}_QFtest_derived_with_vecEx
-! ZERO-NOT: fir.store {{.*}} to %{{.*}}#0
+! ZERO-NOT: fir.do_loop
+! ZERO-NOT: fir.store
+! ZERO: return
diff --git a/flang/test/Lower/finit-local-unsigned.f90 b/flang/test/Lower/finit-local-unsigned.f90
index cfc3d8ee91b4d..aff940c259122 100644
--- a/flang/test/Lower/finit-local-unsigned.f90
+++ b/flang/test/Lower/finit-local-unsigned.f90
@@ -19,7 +19,7 @@ subroutine test_unsigned(res)
! HEX-LABEL: func.func @_QPtest_unsigned(
! HEX: %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_unsignedEx
-! HEX: %[[C:.*]] = arith.constant {{.*}} : i32
+! HEX: %[[C:.*]] = arith.constant -1431655766 : i32
! HEX: %[[U:.*]] = fir.convert %[[C]] : (i32) -> ui32
! HEX: fir.store %[[U]] to %[[X]]#0 : !fir.ref<ui32>
diff --git a/flang/test/Lower/finit-local-volatile-llvm.f90 b/flang/test/Lower/finit-local-volatile-llvm.f90
new file mode 100644
index 0000000000000..f51f86436659e
--- /dev/null
+++ b/flang/test/Lower/finit-local-volatile-llvm.f90
@@ -0,0 +1,65 @@
+! LLVM IR-level regression test for volatile byte-fill stores.
+! Verifies that volatile locals produce "store volatile" in LLVM IR for
+! every byte written by the -finit-local= byte-fill paths.
+!
+! At -O2 a non-volatile fill of a volatile variable can be eliminated
+! entirely. These checks confirm the stores carry the volatile flag.
+!
+! REQUIRES: x86-registered-target
+!
+! RUN: %flang_fc1 -emit-llvm -triple x86_64-unknown-linux-gnu \
+! RUN: -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
+! RUN: %flang_fc1 -emit-llvm -triple x86_64-unknown-linux-gnu \
+! RUN: -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+
+! ---------------------------------------------------------------------------
+! Volatile derived-type local -- record byte-fill loop.
+! Every store into the record's bytes must be "store volatile i8".
+! ---------------------------------------------------------------------------
+subroutine test_volatile_derived(oa)
+ type :: t
+ integer(4) :: a
+ integer(1) :: b
+ end type
+ type(t), volatile :: v
+ integer :: oa
+ v%a = 1
+ oa = v%a
+end subroutine
+
+! HEX-LABEL: define {{.*}}@_QPtest_volatile_derived
+! HEX: store volatile i8
+
+! ZERO-LABEL: define {{.*}}@_QPtest_volatile_derived
+! ZERO: store volatile i8
+
+! ---------------------------------------------------------------------------
+! Volatile real(10) local -- allocation-gap byte-fill loop.
+! real(10) = x86_fp80: 10-byte store size, 16-byte allocation.
+! ---------------------------------------------------------------------------
+subroutine test_volatile_real10(res)
+ real(10), volatile :: x
+ real(10) :: res
+ res = x
+end subroutine
+
+! HEX-LABEL: define {{.*}}@_QPtest_volatile_real10
+! HEX: store volatile i8
+
+! ZERO-LABEL: define {{.*}}@_QPtest_volatile_real10
+! ZERO: store volatile i8
+
+! ---------------------------------------------------------------------------
+! Volatile integer array local -- rank-1 array-view byte-fill loop.
+! ---------------------------------------------------------------------------
+subroutine test_volatile_array(res)
+ integer(4), volatile :: x(4)
+ integer :: res
+ res = x(1)
+end subroutine
+
+! HEX-LABEL: define {{.*}}@_QPtest_volatile_array
+! HEX: store volatile i32
+
+! ZERO-LABEL: define {{.*}}@_QPtest_volatile_array
+! ZERO: store volatile i32
diff --git a/flang/test/Lower/finit-local-volatile-real10.f90 b/flang/test/Lower/finit-local-volatile-real10.f90
new file mode 100644
index 0000000000000..c7a26c0da0d77
--- /dev/null
+++ b/flang/test/Lower/finit-local-volatile-real10.f90
@@ -0,0 +1,30 @@
+! Volatile real(10) local -- allocation-gap byte-fill loop path.
+! Pinned to x86_64 triple so the test uses x86_fp80 (10-byte store,
+! 16-byte allocation) regardless of host architecture.
+!
+! REQUIRES: x86-registered-target
+!
+! RUN: %flang_fc1 -emit-hlfir -triple x86_64-unknown-linux-gnu \
+! RUN: -mmlir --strict-fir-volatile-verifier \
+! RUN: -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
+! RUN: %flang_fc1 -emit-hlfir -triple x86_64-unknown-linux-gnu \
+! RUN: -mmlir --strict-fir-volatile-verifier \
+! RUN: -finit-local=0xAA %s -o - | FileCheck --check-prefix=HEX %s
+
+subroutine test_volatile_real10(res)
+ real(10), volatile :: x
+ real(10) :: res
+ res = x
+end subroutine
+
+! ZERO-LABEL: func.func @_QPtest_volatile_real10(
+! ZERO: %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_real10Ex
+! ZERO: fir.convert %[[X]]#0 : (!fir.ref<f80, volatile>) -> !fir.ref<!fir.array<?xi8>, volatile>
+! ZERO: fir.do_loop
+! ZERO: fir.store {{.*}} : !fir.ref<i8, volatile>
+
+! HEX-LABEL: func.func @_QPtest_volatile_real10(
+! HEX: %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_real10Ex
+! HEX: fir.convert %[[X]]#0 : (!fir.ref<f80, volatile>) -> !fir.ref<!fir.array<?xi8>, volatile>
+! HEX: fir.do_loop
+! HEX: fir.store {{.*}} : !fir.ref<i8, volatile>
diff --git a/flang/test/Lower/finit-local-volatile.f90 b/flang/test/Lower/finit-local-volatile.f90
index 57b5157cb9dbd..ad2f4f9d3808c 100644
--- a/flang/test/Lower/finit-local-volatile.f90
+++ b/flang/test/Lower/finit-local-volatile.f90
@@ -1,18 +1,22 @@
! Tests that -finit-local= byte-fill loops correctly handle volatile locals.
-! Each byte-view conversion must go through fir.volatile_cast before the
-! fir.convert that reinterprets the address as a byte sequence, so that the
-! strict FIR volatile verifier does not see a "mismatched volatility" error.
+! The byte-seq ref type and all CoordinateOp result types must carry the
+! source volatility flag so that final stores are emitted as "store volatile".
+! LLVM IR-level volatile store checks are in finit-local-volatile-llvm.f90.
!
-! Before the fix both zero and hex modes emitted a plain fir.convert from
-! a volatile ref directly to a non-volatile byte-sequence ref, which the
-! strict verifier rejects with "mismatched volatility".
+! When source and target types already have matching volatility,
+! createConvertWithVolatileCast emits a plain fir.convert (no fir.volatile_cast
+! needed). The strict FIR volatile verifier accepts this because both sides
+! carry the volatile flag. Before the fix, the target type was non-volatile
+! so the verifier rejected the convert with "mismatched volatility".
!
-! Two shapes are covered:
-! 1. volatile derived-type local -- exercises the record byte-fill loop
-! in initAddr (fir.volatile_cast before the i8-array view convert).
-! 2. volatile real(10) local -- exercises the allocation-gap byte-fill
-! loop in genInitLocalStore (same fix, different site).
-! Requires x86 so that real(10) = x86_fp80 with a 6-byte padding gap.
+! Four paths are covered:
+! 1. volatile derived-type local -- record byte-fill loop in initAddr.
+! 2. volatile real(10) local -- allocation-gap byte-fill loop in
+! genInitLocalStore (requires x86; see finit-local-volatile-real10.f90).
+! 3. volatile logical local (hex) -- LOGICAL bitcast address convert in
+! genInitLocalStore must preserve volatility.
+! 4. volatile integer array local -- rank-1 array-view convert in initAddr
+! must preserve volatility (both zero and hex modes).
!
! RUN: %flang_fc1 -emit-hlfir -mmlir --strict-fir-volatile-verifier \
! RUN: -finit-local=zero %s -o - | FileCheck --check-prefix=ZERO %s
@@ -35,38 +39,49 @@ subroutine test_volatile_derived(oa)
! ZERO-LABEL: func.func @_QPtest_volatile_derived(
! ZERO: %[[V:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_derivedEv
-! ZERO: %[[NV:.*]] = fir.volatile_cast %[[V]]#0 : (!fir.ref<{{.*}}, volatile>) -> !fir.ref<{{.*}}>
-! ZERO: fir.convert %[[NV]] : (!fir.ref<{{.*}}>) -> !fir.ref<!fir.array<?xi8>>
+! ZERO: fir.convert %[[V]]#0 : (!fir.ref<{{.*}}, volatile>) -> !fir.ref<!fir.array<?xi8>, volatile>
! ZERO: fir.do_loop
-! ZERO: fir.store {{.*}} : !fir.ref<i8>
+! ZERO: fir.store {{.*}} : !fir.ref<i8, volatile>
! HEX-LABEL: func.func @_QPtest_volatile_derived(
! HEX: %[[V:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_derivedEv
-! HEX: %[[NV:.*]] = fir.volatile_cast %[[V]]#0 : (!fir.ref<{{.*}}, volatile>) -> !fir.ref<{{.*}}>
-! HEX: fir.convert %[[NV]] : (!fir.ref<{{.*}}>) -> !fir.ref<!fir.array<?xi8>>
+! HEX: fir.convert %[[V]]#0 : (!fir.ref<{{.*}}, volatile>) -> !fir.ref<!fir.array<?xi8>, volatile>
! HEX: fir.do_loop
-! HEX: fir.store {{.*}} : !fir.ref<i8>
+! HEX: fir.store {{.*}} : !fir.ref<i8, volatile>
! ---------------------------------------------------------------------------
-! Volatile real(10) local -- allocation-gap byte-fill loop path (x86 only).
+! Volatile logical local (hex mode) -- LOGICAL bitcast path.
+! The bitcast convert preserves volatility: !fir.ref<i32, volatile>.
! ---------------------------------------------------------------------------
-! REQUIRES: x86-registered-target
-subroutine test_volatile_real10(res)
- real(10), volatile :: x
- real(10) :: res
- res = x
+subroutine test_volatile_logical(res)
+ logical(4), volatile :: l
+ integer :: res
+ if (l) res = 1
end subroutine
-! ZERO-LABEL: func.func @_QPtest_volatile_real10(
-! ZERO: %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_real10Ex
-! ZERO: %[[NV:.*]] = fir.volatile_cast %[[X]]#0 : (!fir.ref<f80, volatile>) -> !fir.ref<f80>
-! ZERO: fir.convert %[[NV]] : (!fir.ref<f80>) -> !fir.ref<!fir.array<?xi8>>
+! HEX-LABEL: func.func @_QPtest_volatile_logical(
+! HEX: %[[L:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_logicalEl
+! HEX: fir.convert %[[L]]#0 : (!fir.ref<!fir.logical<4>, volatile>) -> !fir.ref<i32, volatile>
+! HEX: fir.store {{.*}} : !fir.ref<i32, volatile>
+
+! ---------------------------------------------------------------------------
+! Volatile integer array local -- rank-1 array-view path.
+! The rank-1 view convert preserves volatility end-to-end.
+! ---------------------------------------------------------------------------
+subroutine test_volatile_array(res)
+ integer(4), volatile :: x(4)
+ integer :: res
+ res = x(1)
+end subroutine
+
+! ZERO-LABEL: func.func @_QPtest_volatile_array(
+! ZERO: %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_arrayEx
+! ZERO: fir.convert %[[X]]#0 : (!fir.ref<!fir.array<4xi32>, volatile>) -> !fir.ref<!fir.array<?xi32>, volatile>
! ZERO: fir.do_loop
-! ZERO: fir.store {{.*}} : !fir.ref<i8>
+! ZERO: fir.store {{.*}} : !fir.ref<i32, volatile>
-! HEX-LABEL: func.func @_QPtest_volatile_real10(
-! HEX: %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_real10Ex
-! HEX: %[[NV:.*]] = fir.volatile_cast %[[X]]#0 : (!fir.ref<f80, volatile>) -> !fir.ref<f80>
-! HEX: fir.convert %[[NV]] : (!fir.ref<f80>) -> !fir.ref<!fir.array<?xi8>>
+! HEX-LABEL: func.func @_QPtest_volatile_array(
+! HEX: %[[X:.*]]:2 = hlfir.declare {{.*}}_QFtest_volatile_arrayEx
+! HEX: fir.convert %[[X]]#0 : (!fir.ref<!fir.array<4xi32>, volatile>) -> !fir.ref<!fir.array<?xi32>, volatile>
! HEX: fir.do_loop
-! HEX: fir.store {{.*}} : !fir.ref<i8>
+! HEX: fir.store {{.*}} : !fir.ref<i32, volatile>
>From 433177985ef7b578f14c6481098089d19330f7f7 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Thu, 3 Sep 2026 08:26:42 -0400
Subject: [PATCH 16/17] [flang] Fix -finit-local= CHARACTER byte-fill stride
and improve tests
Use i8/fir.seq<i8> instead of fir.char<1>/fir.seq<fir.char<1>> as the
view element in both CHARACTER byte-fill loops. Under --kind-mapping=a1:16,
fir.char<1> resolves to i16 through the LLVM type converter, so each
fir.coordinate_of step strided 2 bytes instead of 1. i8 is invariant.
The byte count (nUnits/rtLen * kindBytes) is unchanged.
Guard genByteSplatInit against sub-byte LOGICAL kind mappings
(e.g. --kind-mapping=l4:1): emit a TODO fatal error instead of hitting
the APInt::getSplat assertion.
Fix finit-local-volatile-llvm.f90 CHECK-LABEL directives to use the
portable define {{.*}}@{{.*}}name{{.*}}( form; add volatile fixed-length
and runtime-length CHARACTER cases. Update finit-local.f90 FIR-level
checks for the new i8 coordinate_of signatures. Add
finit-local-kind-mapping-subbyte.f90 for the sub-byte LOGICAL diagnostic.
Add positive controls and tighten CHECK-NOT to arith.constant -1431655766
in finit-local-exclusions.f90.
---
flang/lib/Lower/ConvertVariable.cpp | 64 +++++++++----------
flang/test/Lower/finit-local-exclusions.f90 | 17 ++++-
.../finit-local-kind-mapping-subbyte.f90 | 18 ++++++
.../test/Lower/finit-local-volatile-llvm.f90 | 51 +++++++++++++--
flang/test/Lower/finit-local.f90 | 26 ++++----
5 files changed, 121 insertions(+), 55 deletions(-)
create mode 100644 flang/test/Lower/finit-local-kind-mapping-subbyte.f90
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 54ccb825c7498..f97b5c8ebd9b4 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -1419,8 +1419,13 @@ static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
// width matches the actual allocation size.
// The caller stores it via a bitcasted address to preserve the bit pattern
// (fir.convert from integer to !fir.logical normalizes nonzero -> true).
+ // A sub-byte mapping (e.g. --kind-mapping=l4:1) is not supported: the
+ // APInt::getSplat precondition requires the destination width >= 8.
if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
- return makeIntCst(builder.getKindMap().getLogicalBitsize(logTy.getFKind()));
+ unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
+ if (bits < 8)
+ TODO(loc, "-finit-local= with a sub-byte LOGICAL kind mapping");
+ return makeIntCst(bits);
}
// All types that pass shouldInitLocal and reach genInitLocalStore are
// handled explicitly above (integer, float, complex, logical) or are
@@ -1453,10 +1458,9 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
if (mode == Fortran::lower::InitLocalKind::Hex) {
// Loop over every byte of the character storage. For kind=1 each
// code unit is one byte; for kind=2/4 (UTF-16/32) each code unit is
- // kind bytes wide. We use a kind=1 singleton as the view element so
- // fir.coordinate_of advances exactly one byte per step, and iterate
- // nUnits * kindBytes times to cover all bytes. Use KindMapping to
- // get the true byte width under any --kind-mapping override.
+ // kind bytes wide. We iterate nUnits * kindBytes times to cover all
+ // bytes. Use KindMapping to get the true byte width under any
+ // --kind-mapping override.
int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
int64_t kindBytes =
builder.getKindMap().getCharacterBitsize(charTy.getFKind()) / 8;
@@ -1464,14 +1468,13 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
if (nBytes > 0) {
mlir::Type idxTy = builder.getIndexType();
mlir::Type i8Ty = builder.getIntegerType(8);
- // Use a kind=1 singleton so fir.coordinate_of strides by 1 byte.
- fir::CharacterType byteTy =
- fir::CharacterType::getSingleton(builder.getContext(), 1);
- mlir::Type byteSeqTy = fir::SequenceType::get(
- {fir::SequenceType::getUnknownExtent()}, byteTy);
+ // Use an i8 sequence so fir.coordinate_of strides by exactly 1 byte,
+ // regardless of any --kind-mapping override for character kind 1.
+ mlir::Type i8SeqTy = fir::SequenceType::get(
+ {fir::SequenceType::getUnknownExtent()}, i8Ty);
bool addrVolatile1 = fir::isa_volatile_type(addr.getType());
mlir::Value byteBase = builder.createConvertWithVolatileCast(
- loc, builder.getRefType(byteSeqTy, addrVolatile1), addr);
+ loc, builder.getRefType(i8SeqTy, addrVolatile1), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last =
builder.createIntegerConstant(loc, idxTy, nBytes - 1);
@@ -1483,15 +1486,12 @@ static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
builder.setInsertionPointToStart(loop.getBody());
mlir::Value iv = loop.getInductionVar();
bool byteVolatile1 = fir::isa_volatile_type(byteBase.getType());
- mlir::Value byteAddr =
- fir::CoordinateOp::create(builder, loc,
- builder.getRefType(byteTy, byteVolatile1),
- byteBase, mlir::ValueRange{iv});
+ mlir::Value byteAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(i8Ty, byteVolatile1), byteBase,
+ mlir::ValueRange{iv});
mlir::Value pat = builder.createIntegerConstant(
loc, i8Ty, static_cast<int64_t>(hexByte));
- mlir::Value i8Addr = builder.createConvertWithVolatileCast(
- loc, builder.getRefType(i8Ty, byteVolatile1), byteAddr);
- fir::StoreOp::create(builder, loc, pat, i8Addr);
+ fir::StoreOp::create(builder, loc, pat, byteAddr);
}
return;
}
@@ -1636,9 +1636,8 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::Type rank1SeqTy = fir::SequenceType::get(
{fir::SequenceType::getUnknownExtent()}, eleTy);
bool rank1Volatile = fir::isa_volatile_type(addr.getType());
- mlir::Value rank1Addr =
- builder.createConvertWithVolatileCast(
- loc, builder.getRefType(rank1SeqTy, rank1Volatile), addr);
+ mlir::Value rank1Addr = builder.createConvertWithVolatileCast(
+ loc, builder.getRefType(rank1SeqTy, rank1Volatile), addr);
mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
mlir::Value last =
builder.createIntegerConstant(loc, idxTy, totalElems - 1);
@@ -1720,7 +1719,6 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
// For kind>1 (UTF-16/32) the runtime length is in code units; multiply
// by the kind byte width to get the total byte count. Use KindMapping
// so a --kind-mapping override is respected.
- // Use a kind=1 singleton so fir.coordinate_of advances 1 byte per step.
int64_t kindBytes =
builder.getKindMap().getCharacterBitsize(charTy.getFKind()) / 8;
if (kindBytes > 1) {
@@ -1736,19 +1734,21 @@ static void genInitLocal(Fortran::lower::AbstractConverter &converter,
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(loop.getBody());
mlir::Value iv = loop.getInductionVar();
- fir::CharacterType byteTy =
- fir::CharacterType::getSingleton(builder.getContext(), 1);
- mlir::Type byteSeqTy = fir::SequenceType::get(
- {fir::SequenceType::getUnknownExtent()}, byteTy);
+ // Use an i8 sequence so fir.coordinate_of strides by exactly 1 byte,
+ // regardless of any --kind-mapping override for character kind 1.
+ mlir::Type i8Ty = builder.getIntegerType(8);
+ mlir::Type i8SeqTy =
+ fir::SequenceType::get({fir::SequenceType::getUnknownExtent()}, i8Ty);
bool baseVolatile = fir::isa_volatile_type(base.getType());
mlir::Value byteBase = builder.createConvertWithVolatileCast(
- loc, builder.getRefType(byteSeqTy, baseVolatile), base);
+ loc, builder.getRefType(i8SeqTy, baseVolatile), base);
bool byteVolatile4 = fir::isa_volatile_type(byteBase.getType());
- mlir::Value byteAddr =
- fir::CoordinateOp::create(builder, loc,
- builder.getRefType(byteTy, byteVolatile4),
- byteBase, mlir::ValueRange{iv});
- genInitLocalStore(builder, loc, byteTy, byteAddr, mode, hexByte);
+ mlir::Value byteAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(i8Ty, byteVolatile4), byteBase,
+ mlir::ValueRange{iv});
+ mlir::Value pat = builder.createIntegerConstant(
+ loc, i8Ty, static_cast<int64_t>(hexByte));
+ fir::StoreOp::create(builder, loc, pat, byteAddr);
return;
}
}
diff --git a/flang/test/Lower/finit-local-exclusions.f90 b/flang/test/Lower/finit-local-exclusions.f90
index faa01f0023ad6..2ac6f279fb2c1 100644
--- a/flang/test/Lower/finit-local-exclusions.f90
+++ b/flang/test/Lower/finit-local-exclusions.f90
@@ -51,12 +51,16 @@ program test_main_prog
! ---------------------------------------------------------------------------
subroutine test_runtime_array(res, n)
integer, intent(in) :: n
+ integer :: ctrl
integer :: x(n)
integer :: res
- res = x(1)
+ res = x(1) + ctrl
end subroutine
! CHECK-LABEL: func.func @_QPtest_runtime_array
+! CHECK: hlfir.declare {{.*}} "_QFtest_runtime_arrayEctrl"
+! CHECK: arith.constant -1431655766 : i32
! CHECK: hlfir.declare {{.*}} "_QFtest_runtime_arrayEx"
+! CHECK-NOT: arith.constant -1431655766 : i32
! CHECK-NOT: fir.do_loop
! CHECK: return
@@ -65,12 +69,17 @@ subroutine test_runtime_array(res, n)
! Static CHARACTER scalars are initialized; arrays are silently skipped.
! ---------------------------------------------------------------------------
subroutine test_char_array(res)
+ integer :: ctrl
character(4) :: x(3)
character(4) :: res
res = x(1)
+ ctrl = 0
end subroutine
! CHECK-LABEL: func.func @_QPtest_char_array
+! CHECK: hlfir.declare {{.*}} "_QFtest_char_arrayEctrl"
+! CHECK: arith.constant -1431655766 : i32
! CHECK: hlfir.declare {{.*}} "_QFtest_char_arrayEx"
+! CHECK-NOT: arith.constant -1431655766 : i32
! CHECK-NOT: fir.do_loop
! CHECK: return
@@ -82,11 +91,15 @@ subroutine test_pointer_comp(res)
type t
integer, pointer :: p
end type
+ integer :: ctrl
type(t) :: x
integer :: res
- res = 0
+ res = ctrl
end subroutine
! CHECK-LABEL: func.func @_QPtest_pointer_comp
+! CHECK: hlfir.declare {{.*}} "_QFtest_pointer_compEctrl"
+! CHECK: arith.constant -1431655766 : i32
! CHECK: hlfir.declare {{.*}} "_QFtest_pointer_compEx"
+! CHECK-NOT: arith.constant -1431655766 : i32
! CHECK-NOT: fir.do_loop
! CHECK: return
diff --git a/flang/test/Lower/finit-local-kind-mapping-subbyte.f90 b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
new file mode 100644
index 0000000000000..24291054f4c4d
--- /dev/null
+++ b/flang/test/Lower/finit-local-kind-mapping-subbyte.f90
@@ -0,0 +1,18 @@
+! Tests that -finit-local= emits a controlled diagnostic rather than crashing
+! when a kind mapping reduces a LOGICAL type to sub-byte storage.
+!
+! Reproducer: --kind-mapping=l4:1 maps LOGICAL(4) to 1 bit. Before this fix,
+! APInt::getSplat(1, APInt(8, 0xAA)) asserted because the destination width (1)
+! is less than the source width (8). The fix guards this path with a TODO
+! diagnostic before reaching getSplat.
+!
+! RUN: %not_todo_cmd bbc -emit-hlfir --kind-mapping=l4:1 -finit-local=0xAA %s -o - 2>&1 | \
+! RUN: FileCheck %s
+
+! CHECK: not yet implemented: -finit-local= with a sub-byte LOGICAL kind mapping
+
+subroutine test_logical4_subbyte(res)
+ logical(kind=4) :: l
+ integer :: res
+ if (l) res = 1
+end subroutine
diff --git a/flang/test/Lower/finit-local-volatile-llvm.f90 b/flang/test/Lower/finit-local-volatile-llvm.f90
index f51f86436659e..6fb2ce33685be 100644
--- a/flang/test/Lower/finit-local-volatile-llvm.f90
+++ b/flang/test/Lower/finit-local-volatile-llvm.f90
@@ -1,6 +1,8 @@
! LLVM IR-level regression test for volatile byte-fill stores.
! Verifies that volatile locals produce "store volatile" in LLVM IR for
-! every byte written by the -finit-local= byte-fill paths.
+! every byte written by the -finit-local= byte-fill paths: derived-type
+! record fill, real(10) allocation-gap fill, integer array element fill,
+! and both the compile-time-length and runtime-length CHARACTER byte loops.
!
! At -O2 a non-volatile fill of a volatile variable can be eliminated
! entirely. These checks confirm the stores carry the volatile flag.
@@ -27,10 +29,10 @@ subroutine test_volatile_derived(oa)
oa = v%a
end subroutine
-! HEX-LABEL: define {{.*}}@_QPtest_volatile_derived
+! HEX-LABEL: define {{.*}}@{{.*}}test_volatile_derived{{.*}}(
! HEX: store volatile i8
-! ZERO-LABEL: define {{.*}}@_QPtest_volatile_derived
+! ZERO-LABEL: define {{.*}}@{{.*}}test_volatile_derived{{.*}}(
! ZERO: store volatile i8
! ---------------------------------------------------------------------------
@@ -43,10 +45,10 @@ subroutine test_volatile_real10(res)
res = x
end subroutine
-! HEX-LABEL: define {{.*}}@_QPtest_volatile_real10
+! HEX-LABEL: define {{.*}}@{{.*}}test_volatile_real10{{.*}}(
! HEX: store volatile i8
-! ZERO-LABEL: define {{.*}}@_QPtest_volatile_real10
+! ZERO-LABEL: define {{.*}}@{{.*}}test_volatile_real10{{.*}}(
! ZERO: store volatile i8
! ---------------------------------------------------------------------------
@@ -58,8 +60,43 @@ subroutine test_volatile_array(res)
res = x(1)
end subroutine
-! HEX-LABEL: define {{.*}}@_QPtest_volatile_array
+! HEX-LABEL: define {{.*}}@{{.*}}test_volatile_array{{.*}}(
! HEX: store volatile i32
-! ZERO-LABEL: define {{.*}}@_QPtest_volatile_array
+! ZERO-LABEL: define {{.*}}@{{.*}}test_volatile_array{{.*}}(
! ZERO: store volatile i32
+
+! ---------------------------------------------------------------------------
+! Volatile fixed-length CHARACTER local -- compile-time-length byte-fill loop.
+! hex: loop over nLen bytes -> store volatile i8.
+! zero: fir.zero_bits store -> store volatile i8 0.
+! ---------------------------------------------------------------------------
+subroutine test_volatile_char_fixed(res)
+ character(10), volatile :: x
+ character(10) :: res
+ res = x
+end subroutine
+
+! HEX-LABEL: define {{.*}}@{{.*}}test_volatile_char_fixed{{.*}}(
+! HEX: store volatile i8
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_volatile_char_fixed{{.*}}(
+! ZERO: store volatile i8
+
+! ---------------------------------------------------------------------------
+! Volatile runtime-length CHARACTER local -- dynamic-length byte-fill loop.
+! hex: loop over rtLen bytes -> store volatile i8.
+! zero: loop over rtLen bytes -> store volatile i8 0.
+! ---------------------------------------------------------------------------
+subroutine test_volatile_char_runtime(res, n)
+ integer, intent(in) :: n
+ character(n), volatile :: x
+ character(n) :: res
+ res = x
+end subroutine
+
+! HEX-LABEL: define {{.*}}@{{.*}}test_volatile_char_runtime{{.*}}(
+! HEX: store volatile i8
+
+! ZERO-LABEL: define {{.*}}@{{.*}}test_volatile_char_runtime{{.*}}(
+! ZERO: store volatile i8
diff --git a/flang/test/Lower/finit-local.f90 b/flang/test/Lower/finit-local.f90
index 995bc886f05e6..979ad0bd9dc61 100644
--- a/flang/test/Lower/finit-local.f90
+++ b/flang/test/Lower/finit-local.f90
@@ -215,7 +215,7 @@ subroutine test_logical4(res)
! ---------------------------------------------------------------------------
! CHARACTER(10) -- fixed-length scalar.
! zero: fir.zero_bits over the whole character type.
-! hex: byte-loop over 10 singleton code-units.
+! hex: byte-loop over 10 bytes via an i8 view.
! ---------------------------------------------------------------------------
subroutine test_char10(res)
character(10) :: res
@@ -230,7 +230,7 @@ subroutine test_char10(res)
! HEX-LABEL: func.func @_QPtest_char10
! HEX: fir.do_loop
-! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
! HEX: arith.constant {{.*}} : i8
! HEX: fir.store {{.*}} : !fir.ref<i8>
@@ -263,15 +263,14 @@ subroutine test_charN(res, n)
end subroutine
! ZERO-LABEL: func.func @_QPtest_charn
! ZERO: fir.do_loop
-! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
-! ZERO: fir.zero_bits !fir.char<1>
-! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<1>>
+! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! ZERO: fir.store {{.*}} : !fir.ref<i8>
! HEX-LABEL: func.func @_QPtest_charn
! HEX: fir.do_loop
-! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
! HEX: arith.constant {{.*}} : i8
! HEX: fir.store {{.*}} : !fir.ref<i8>
@@ -475,7 +474,7 @@ subroutine test_equivalence(res)
! ---------------------------------------------------------------------------
! CHARACTER(kind=2, len=3) -- fixed-length, higher kind.
-! hex: byte-loop over 3*2=6 bytes using a kind=1 singleton view.
+! hex: byte-loop over 3*2=6 bytes via an i8 view.
! zero: fir.zero_bits over the whole type.
! ---------------------------------------------------------------------------
subroutine test_char2_fixed(res)
@@ -490,13 +489,13 @@ subroutine test_char2_fixed(res)
! HEX-LABEL: func.func @_QPtest_char2_fixed
! HEX: %[[C5:.*]] = arith.constant 5 : index
! HEX: fir.do_loop %{{.*}} = %{{.*}} to %[[C5]] step %{{.*}} {
-! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
! HEX: arith.constant {{.*}} : i8
! HEX: fir.store {{.*}} : !fir.ref<i8>
! ---------------------------------------------------------------------------
! CHARACTER(kind=4, len=2) -- fixed-length, wider kind.
-! hex: byte-loop over 2*4=8 bytes using a kind=1 singleton view.
+! hex: byte-loop over 2*4=8 bytes via an i8 view.
! ---------------------------------------------------------------------------
subroutine test_char4_fixed(res)
character(kind=4, len=2) :: res
@@ -510,7 +509,7 @@ subroutine test_char4_fixed(res)
! HEX-LABEL: func.func @_QPtest_char4_fixed
! HEX: %[[C7:.*]] = arith.constant 7 : index
! HEX: fir.do_loop %{{.*}} = %{{.*}} to %[[C7]] step %{{.*}} {
-! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
! HEX: arith.constant {{.*}} : i8
! HEX: fir.store {{.*}} : !fir.ref<i8>
@@ -529,14 +528,13 @@ subroutine test_char2_runtime(res, n)
! ZERO: %[[C2:.*]] = arith.constant 2 : index
! ZERO: arith.muli {{.*}}, %[[C2]] : index
! ZERO: fir.do_loop
-! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
-! ZERO: fir.zero_bits !fir.char<1>
-! ZERO: fir.store {{.*}} : !fir.ref<!fir.char<1>>
+! ZERO: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! ZERO: fir.store {{.*}} : !fir.ref<i8>
! HEX-LABEL: func.func @_QPtest_char2_runtime
! HEX: %[[C2:.*]] = arith.constant 2 : index
! HEX: arith.muli {{.*}}, %[[C2]] : index
! HEX: fir.do_loop
-! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?x!fir.char<1>>>, index) -> !fir.ref<!fir.char<1>>
+! HEX: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
! HEX: arith.constant {{.*}} : i8
! HEX: fir.store {{.*}} : !fir.ref<i8>
>From 0eea1dbd64e66458d872628a2363e215602ebe2f Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Thu, 3 Sep 2026 11:21:42 -0400
Subject: [PATCH 17/17] Fix Linux and Windows test failure.
---
flang/test/Lower/finit-local-volatile-llvm.f90 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flang/test/Lower/finit-local-volatile-llvm.f90 b/flang/test/Lower/finit-local-volatile-llvm.f90
index 6fb2ce33685be..7642e111fb260 100644
--- a/flang/test/Lower/finit-local-volatile-llvm.f90
+++ b/flang/test/Lower/finit-local-volatile-llvm.f90
@@ -81,7 +81,7 @@ subroutine test_volatile_char_fixed(res)
! HEX: store volatile i8
! ZERO-LABEL: define {{.*}}@{{.*}}test_volatile_char_fixed{{.*}}(
-! ZERO: store volatile i8
+! ZERO: store volatile [10 x i8] zeroinitializer
! ---------------------------------------------------------------------------
! Volatile runtime-length CHARACTER local -- dynamic-length byte-fill loop.
More information about the flang-commits
mailing list