[clang] [flang] [clang][flang] Add support for -finit-local-zero (PR #159788)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 19 08:08:38 PDT 2025
https://github.com/NimishMishra created https://github.com/llvm/llvm-project/pull/159788
This PR adds support for -finit-local-zero. It follows the following semantics:
1. Initialises local INTEGER, REAL, and COMPLEX variables to zero, LOGICAL variables to false, and CHARACTER variables to a string of null bytes.
2. These options do not initialise: (1) objects with the POINTER attribute, (2) allocatable arrays, and (3) variables that appear in an EQUIVALENCE statement.
Fixes https://github.com/llvm/llvm-project/issues/157042
>From 48fc0eaf7ff6c3d9e2b7b5c1317412e2d80072d6 Mon Sep 17 00:00:00 2001
From: Nimish Mishra <neelam.nimish at gmail.com>
Date: Fri, 19 Sep 2025 20:31:58 +0530
Subject: [PATCH] [clang][flang] Add support for -finit-local-zero
---
clang/include/clang/Driver/Options.td | 7 +-
clang/lib/Driver/ToolChains/Flang.cpp | 2 +-
flang/include/flang/Lower/LoweringOptions.def | 3 +
flang/lib/Frontend/CompilerInvocation.cpp | 5 +
flang/lib/Lower/Bridge.cpp | 204 ++++++++++++++++++
flang/test/Lower/init-local-zero.f90 | 52 +++++
6 files changed, 271 insertions(+), 2 deletions(-)
create mode 100644 flang/test/Lower/init-local-zero.f90
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 16e1c396fedbe..f5a982a512095 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -7065,7 +7065,12 @@ 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 init_local_zero
+ : BooleanFFlag<"init-local-zero">,
+ Group<gfortran_Group>,
+ Visibility<[FlangOption, FC1Option]>,
+ HelpText<"Initialize real/integer/character/logical/complex type "
+ "to zero.">;
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>;
diff --git a/clang/lib/Driver/ToolChains/Flang.cpp b/clang/lib/Driver/ToolChains/Flang.cpp
index 6fc372eb75eb7..09e5e2646bc0a 100644
--- a/clang/lib/Driver/ToolChains/Flang.cpp
+++ b/clang/lib/Driver/ToolChains/Flang.cpp
@@ -181,7 +181,7 @@ void Flang::addCodegenOptions(const ArgList &Args,
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_repack_arrays, options::OPT_finit_local_zero,
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/include/flang/Lower/LoweringOptions.def b/flang/include/flang/Lower/LoweringOptions.def
index 39f197d8d35c8..a7389ad62131a 100644
--- a/flang/include/flang/Lower/LoweringOptions.def
+++ b/flang/include/flang/Lower/LoweringOptions.def
@@ -79,5 +79,8 @@ ENUM_LOWERINGOPT(ComplexDivisionToRuntime, unsigned, 1, 1)
/// of the lowering pipeline.
ENUM_LOWERINGOPT(RegisterMLIRDiagnosticsHandler, unsigned, 1, 1)
+/// When true, it enables semantics for -finit-local-zero during codegen.
+ENUM_LOWERINGOPT(InitLocalZeroDef, unsigned, 1, 0)
+
#undef LOWERINGOPT
#undef ENUM_LOWERINGOPT
diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp
index 09b51730d6216..13f5e04c085ba 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -1608,6 +1608,11 @@ bool CompilerInvocation::createFromArgs(
else
invoc.loweringOpts.setInitGlobalZero(false);
+ // -finit-local-zero
+ if (args.hasArg(clang::driver::options::OPT_finit_local_zero)) {
+ invoc.loweringOpts.setInitLocalZeroDef(1);
+ }
+
// 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/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 4a5b9885bb7c4..c89b8c82aed9d 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -383,6 +383,159 @@ class TypeInfoConverter {
llvm::SmallSetVector<Fortran::semantics::SymbolRef, 32> seen;
};
+// Helper class to encapsulate utilities related to emission of implicit
+// assignments. `Implicit` here implies the assignment does not
+// exist in the Fortran source, but is implicit through definition
+// of one or more flagsets (like -finit-* family of flags).
+// General purpose usage of these utilities outside the
+// scope detailed here is discouraged, and is probably wrong.
+class ImplicitAssignmentGenerator {
+private:
+ Fortran::lower::LoweringBridge &bridge;
+ bool isInitLocalZeroFlagDefined;
+
+ /*
+ * Generates a parser::Variable for `sym` and fills
+ * in the correct source location for the designator.
+ */
+ Fortran::parser::Variable generateLHS(const Fortran::semantics::Symbol &sym) {
+ Fortran::parser::Designator designator = Fortran::parser::Designator{
+ Fortran::parser::DataRef{Fortran::parser::Name{
+ Fortran::parser::FindSourceLocation(sym.name()),
+ const_cast<Fortran::semantics::Symbol *>(&sym)}}};
+ designator.source = Fortran::parser::FindSourceLocation(sym.name());
+ Fortran::parser::Variable variable = Fortran::parser::Variable{
+ Fortran::common::Indirection<Fortran::parser::Designator>{
+ std::move(designator)}};
+ return variable;
+ }
+
+ /*
+ * Integer, Real, and Complex types can be initialized through
+ * IntLiteralConstant. Further implicit casts will ensure
+ * type compatiblity. Default initialization to `0`.
+ */
+ Fortran::parser::Expr generateIntLiteralConstant(uint32_t init = 0) {
+ std::string val = std::to_string(init);
+ return Fortran::parser::Expr{
+ Fortran::parser::LiteralConstant{Fortran::parser::IntLiteralConstant{
+ Fortran::parser::CharBlock{val.c_str(), val.size()},
+ std::optional<Fortran::parser::KindParam>{}}}};
+ }
+
+ /*
+ * Logical types can be initialized with a LogicalLiteralConstant
+ * set to <true/false>. Defaults to `false`.
+ */
+ Fortran::parser::Expr generateLogicalLiteralConstant(bool init = false) {
+ return (init == false)
+ ? Fortran::parser::Expr{Fortran::parser::LiteralConstant{
+ Fortran::parser::LogicalLiteralConstant{
+ false, std::optional<Fortran::parser::KindParam>{}}}}
+ : Fortran::parser::Expr{Fortran::parser::LiteralConstant{
+ Fortran::parser::LogicalLiteralConstant{
+ true, std::optional<Fortran::parser::KindParam>{}}}};
+ }
+
+ /*
+ * Character types can be initialized with a FunctionReference
+ * to `achar(init)`. Defaults to `achar(0)`.
+ */
+ Fortran::parser::Expr
+ generateACharReference(Fortran::parser::CharBlock ¤tPosition,
+ const Fortran::semantics::Symbol &sym,
+ std::string init = "0") {
+ // Construct a parser::Name for `achar`
+ std::string funcNameStr = "achar";
+ Fortran::parser::CharBlock funcCharBlock =
+ Fortran::parser::CharBlock{funcNameStr.c_str(), funcNameStr.size()};
+ Fortran::parser::Name funcName = Fortran::parser::Name{
+ Fortran::parser::CharBlock{funcNameStr.c_str(), funcNameStr.size()}};
+ Fortran::semantics::Scope &scope =
+ bridge.getSemanticsContext().FindScope(currentPosition);
+ Fortran::semantics::Symbol *funcSym = scope.FindSymbol(funcCharBlock);
+ if (funcSym) {
+ funcName.symbol = std::move(funcSym);
+ } else {
+ Fortran::semantics::Symbol &symbol =
+ bridge.getSemanticsContext()
+ .FindScope(currentPosition)
+ .MakeSymbol(
+ funcCharBlock,
+ Fortran::semantics::Attrs{Fortran::semantics::Attr::ELEMENTAL,
+ Fortran::semantics::Attr::INTRINSIC,
+ Fortran::semantics::Attr::PURE},
+ Fortran::semantics::ProcEntityDetails{});
+ funcName.symbol = std::move(&symbol);
+ }
+
+ // Construct a RHS expression including a FunctionReference
+ // to `achar(init)`
+ Fortran::parser::Expr intExpr = Fortran::parser::Expr{
+ Fortran::parser::LiteralConstant{Fortran::parser::IntLiteralConstant{
+ Fortran::parser::CharBlock{init.c_str(), init.size()},
+ std::optional<Fortran::parser::KindParam>{}}}};
+ Fortran::common::Indirection<Fortran::parser::Expr> indirExpr{
+ std::move(intExpr)};
+ Fortran::parser::ActualArg actualArg{std::move(indirExpr)};
+
+ Fortran::parser::ActualArgSpec argSpec = Fortran::parser::ActualArgSpec{
+ std::make_tuple(std::nullopt, std::move(actualArg))};
+ std::list<Fortran::parser::ActualArgSpec> argSpecList;
+ argSpecList.push_back(std::move(argSpec));
+ Fortran::parser::ProcedureDesignator procDesignator =
+ Fortran::parser::ProcedureDesignator{std::move(funcName)};
+ Fortran::parser::Call call = Fortran::parser::Call{
+ std::make_tuple(std::move(procDesignator), std::move(argSpecList))};
+ Fortran::parser::FunctionReference funcRef =
+ Fortran::parser::FunctionReference{std::move(call)};
+ funcRef.source = Fortran::parser::FindSourceLocation(sym.name());
+ return Fortran::parser::Expr{std::move(funcRef)};
+ }
+
+ /*
+ * Utility to wrap the LHS variable `var` and RHS expression `expr` into
+ * an assignment.
+ */
+ inline std::unique_ptr<Fortran::parser::AssignmentStmt>
+ makeAssignment(Fortran::parser::Variable &var, Fortran::parser::Expr expr) {
+ Fortran::parser::AssignmentStmt stmt = Fortran::parser::AssignmentStmt{
+ std::make_tuple(std::move(var), std::move(expr))};
+ return std::make_unique<Fortran::parser::AssignmentStmt>(std::move(stmt));
+ }
+
+public:
+ ImplicitAssignmentGenerator(Fortran::lower::LoweringBridge &bridge,
+ bool isInitLocalZeroFlagDefined)
+ : bridge(bridge), isInitLocalZeroFlagDefined(isInitLocalZeroFlagDefined) {
+ }
+
+ std::unique_ptr<Fortran::parser::AssignmentStmt>
+ emitAssignment(Fortran::parser::CharBlock ¤tPosition,
+ const Fortran::semantics::Symbol &sym) {
+ if (isInitLocalZeroFlagDefined) {
+ Fortran::parser::Variable var = generateLHS(sym);
+ const Fortran::semantics::DeclTypeSpec *declTy = sym.GetType();
+
+ if (declTy->IsNumeric(Fortran::semantics::TypeCategory::Integer) ||
+ declTy->IsNumeric(Fortran::semantics::TypeCategory::Real) ||
+ declTy->IsNumeric(Fortran::semantics::TypeCategory::Complex))
+ return makeAssignment(var, generateIntLiteralConstant());
+
+ else if (declTy->category() ==
+ Fortran::semantics::DeclTypeSpec::Category::Logical)
+ return makeAssignment(var, generateLogicalLiteralConstant());
+
+ else if (declTy->category() ==
+ Fortran::semantics::DeclTypeSpec::Category::Character)
+ return makeAssignment(var,
+ generateACharReference(currentPosition, sym));
+ }
+
+ return nullptr;
+ }
+};
+
using IncrementLoopNestInfo = llvm::SmallVector<IncrementLoopInfo, 8>;
} // namespace
@@ -5820,6 +5973,57 @@ class FirConverter : public Fortran::lower::AbstractConverter {
void instantiateVar(const Fortran::lower::pft::Variable &var,
Fortran::lower::AggregateStoreMap &storeMap) {
Fortran::lower::instantiateVariable(*this, var, localSymbols, storeMap);
+
+ /// Implicit assignment is defined by the `-finit-*` family of flags.
+ /// These options do not initialize:
+ /// 1) Any variable already initialized
+ /// 2) objects with the POINTER attribute
+ /// 3) allocatable arrays
+ /// 4) variables that appear in an EQUIVALENCE statement
+
+ auto isEligibleForImplicitAssignment = [&var]() -> bool {
+ if (!var.hasSymbol())
+ return false;
+
+ const Fortran::semantics::Symbol &sym = var.getSymbol();
+ if (const auto *details =
+ sym.detailsIf<Fortran::semantics::ObjectEntityDetails>()) {
+ if (details->init())
+ return false;
+ }
+
+ if (sym.attrs().test(Fortran::semantics::Attr::POINTER))
+ return false;
+
+ if (sym.Rank() > 0 &&
+ sym.attrs().test(Fortran::semantics::Attr::ALLOCATABLE))
+ return false;
+
+ if (Fortran::lower::pft::getDependentVariableList(sym).size() > 1)
+ return false;
+
+ return true;
+ };
+
+ if (isEligibleForImplicitAssignment()) {
+ // Internal state of this class holds only the -finit-* flagsets. Hence
+ // can be reused for different symbols. Also minimizes the number of
+ // calls to `getLoweringOptions()`.
+ static ImplicitAssignmentGenerator implicitAssignmentGenerator{
+ bridge,
+ /*isInitLocalZeroFlagDefined=*/getLoweringOptions()
+ .getInitLocalZeroDef() == 1};
+ std::unique_ptr<Fortran::parser::AssignmentStmt> stmt =
+ implicitAssignmentGenerator.emitAssignment(currentPosition,
+ var.getSymbol());
+ if (stmt.get()) {
+ Fortran::evaluate::ExpressionAnalyzer ea{bridge.getSemanticsContext()};
+ const Fortran::evaluate::Assignment *assign = ea.Analyze(*stmt.get());
+ if (assign)
+ genAssignment(*assign);
+ }
+ }
+
if (var.hasSymbol())
genOpenMPSymbolProperties(*this, var);
}
diff --git a/flang/test/Lower/init-local-zero.f90 b/flang/test/Lower/init-local-zero.f90
new file mode 100644
index 0000000000000..cd19ef261c55d
--- /dev/null
+++ b/flang/test/Lower/init-local-zero.f90
@@ -0,0 +1,52 @@
+! RUN: %flang_fc1 -emit-fir -finit-local-zero -o - %s | FileCheck %s
+
+!CHECK: %[[const:.*]] = arith.constant 0 : i32
+!CHECK: %[[X:.*]] = fir.alloca i32 {bindc_name = "x", uniq_name = "_QFuninitialized_integerEx"}
+!CHECK: %[[X_DECL:.*]] = fir.declare %[[X]] {uniq_name = "_QFuninitialized_integerEx"} : (!fir.ref<i32>) -> !fir.ref<i32>
+!CHECK: fir.store %[[const]] to %[[X_DECL]] : !fir.ref<i32>
+subroutine uninitialized_integer
+ integer :: x
+end subroutine
+
+!CHECK: %[[const:.*]] = arith.constant 0.000000e+00 : f32
+!CHECK: %[[X:.*]] = fir.alloca f32 {bindc_name = "x", uniq_name = "_QFuninitialized_realEx"}
+!CHECK: %[[X_DECL:.*]] = fir.declare %[[X]] {uniq_name = "_QFuninitialized_realEx"} : (!fir.ref<f32>) -> !fir.ref<f32>
+!CHECK: fir.store %[[const]] to %[[X_DECL]] : !fir.ref<f32>
+subroutine uninitialized_real
+ real :: x
+end subroutine
+
+!CHECK: %false = arith.constant false
+!CHECK: %[[X:.*]] = fir.alloca !fir.logical<4> {bindc_name = "x", uniq_name = "_QFuninitialized_logicalEx"}
+!CHECK: %[[X_DECL:.*]] = fir.declare %[[X]] {uniq_name = "_QFuninitialized_logicalEx"} : (!fir.ref<!fir.logical<4>>) -> !fir.ref<!fir.logical<4>>
+!CHECK: %[[CVT:.*]] = fir.convert %false : (i1) -> !fir.logical<4>
+!CHECK: fir.store %[[CVT]] to %[[X_DECL]] : !fir.ref<!fir.logical<4>>
+subroutine uninitialized_logical
+ logical :: x
+end subroutine
+
+!CHECK: %[[const:.*]] = arith.constant 0.000000e+00 : f32
+!CHECK: %[[X:.*]] = fir.alloca complex<f32> {bindc_name = "x", uniq_name = "_QFuninitialized_complexEx"}
+!CHECK: %[[X_DECL:.*]] = fir.declare %[[X]] {uniq_name = "_QFuninitialized_complexEx"} : (!fir.ref<complex<f32>>) -> !fir.ref<complex<f32>>
+!CHECK: %[[undef:.*]] = fir.undefined complex<f32>
+!CHECK: %[[REAL:.*]] = fir.insert_value %[[undef]], %[[const]], [0 : index] : (complex<f32>, f32) -> complex<f32>
+!CHECK: %[[COMPLEX:.*]] = fir.insert_value %[[REAL]], %[[const]], [1 : index] : (complex<f32>, f32) -> complex<f32>
+!CHECK: fir.store %[[COMPLEX]] to %[[X_DECL]] : !fir.ref<complex<f32>>
+subroutine uninitialized_complex
+ complex :: x
+end subroutine
+
+!CHECK: %[[X:.*]] = fir.alloca !fir.char<1> {bindc_name = "x", uniq_name = "_QFuninitialized_characterEx"}
+!CHECK: %[[X_DECL:.*]] = fir.declare %[[X]] typeparams %c1 {uniq_name = "_QFuninitialized_characterEx"} : (!fir.ref<!fir.char<1>>, index) -> !fir.ref<!fir.char<1>>
+!CHECK: %[[ADDR:.*]] = fir.address_of(@_QQclX00) : !fir.ref<!fir.char<1>>
+!CHECK: %[[FUNC_DECL:.*]] = fir.declare %[[ADDR]] {{.*}}
+!CHECK: %[[LOAD:.*]] = fir.load %[[FUNC_DECL]]
+!CHECK: fir.store %[[LOAD]] to %[[X_DECL]]
+subroutine uninitialized_character
+ character :: x
+end subroutine
+
+!CHECK: fir.global linkonce @_QQclX00 constant : !fir.char<1> {
+!CHECK: %[[VAL:.*]] = fir.string_lit "\00"(1) : !fir.char<1>
+!CHECK: fir.has_value %[[VAL]] : !fir.char<1>
+!CHECK: }
More information about the cfe-commits
mailing list