[clang] [flang] [clang][flang] Add support for -finit-local-zero (PR #159788)

via cfe-commits cfe-commits at lists.llvm.org
Tue Oct 21 23:23:16 PDT 2025


https://github.com/NimishMishra updated https://github.com/llvm/llvm-project/pull/159788

>From de491051eb094906381cdfc895d207a1ceac6470 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 1/2] [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 7ae153deb9a55..321695306e3c1 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -7113,7 +7113,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 a56fa41c49d34..d6ea83800a6d8 100644
--- a/clang/lib/Driver/ToolChains/Flang.cpp
+++ b/clang/lib/Driver/ToolChains/Flang.cpp
@@ -225,7 +225,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 548ca675db5ea..fa4edb35e12eb 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -1617,6 +1617,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 3b711ccbe786a..87a53ce94181f 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -384,6 +384,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 &currentPosition,
+                         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 &currentPosition,
+                 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
 
@@ -5877,6 +6030,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: }

>From f2ea714ea355ef797c158107c3b7fbe8e1da5ebf Mon Sep 17 00:00:00 2001
From: Nimish Mishra <neelam.nimish at gmail.com>
Date: Wed, 22 Oct 2025 11:51:24 +0530
Subject: [PATCH 2/2] Fix tests

---
 flang/test/Driver/finit-local-zero.f90 | 8 ++++++++
 flang/test/Lower/init-local-zero.f90   | 4 ++--
 2 files changed, 10 insertions(+), 2 deletions(-)
 create mode 100644 flang/test/Driver/finit-local-zero.f90

diff --git a/flang/test/Driver/finit-local-zero.f90 b/flang/test/Driver/finit-local-zero.f90
new file mode 100644
index 0000000000000..e24ea8ba2f1f0
--- /dev/null
+++ b/flang/test/Driver/finit-local-zero.f90
@@ -0,0 +1,8 @@
+! Check that the driver passes through -finit-global-zero:
+! RUN: %flang -### -S -finit-local-zero %s -o - 2>&1 | FileCheck %s
+      
+! Check that the compiler accepts -finit-local-zero:
+! RUN: %flang_fc1 -emit-hlfir -finit-local-zero %s -o -
+
+
+! CHECK: "-fc1"{{.*}}"-finit-local-zero"
diff --git a/flang/test/Lower/init-local-zero.f90 b/flang/test/Lower/init-local-zero.f90
index cd19ef261c55d..2fcd0f874bfef 100644
--- a/flang/test/Lower/init-local-zero.f90
+++ b/flang/test/Lower/init-local-zero.f90
@@ -38,7 +38,7 @@ subroutine uninitialized_complex
 
 !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: %[[ADDR:.*]] = fir.address_of(@{{.*}}) : !fir.ref<!fir.char<1>>
 !CHECK: %[[FUNC_DECL:.*]] = fir.declare %[[ADDR]] {{.*}}
 !CHECK: %[[LOAD:.*]] = fir.load %[[FUNC_DECL]]
 !CHECK: fir.store %[[LOAD]] to %[[X_DECL]]
@@ -46,7 +46,7 @@ subroutine uninitialized_character
    character :: x
 end subroutine
 
-!CHECK: fir.global linkonce @_QQclX00 constant : !fir.char<1> {
+!CHECK: fir.global linkonce @{{.*}} 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