[flang-commits] [flang] [flang] Lower initial values via ConvertConstant instead of the legacy expression lowering (PR #210621)

Eugene Epshteyn via flang-commits flang-commits at lists.llvm.org
Sun Jul 19 14:07:25 PDT 2026


https://github.com/eugeneepshteyn updated https://github.com/llvm/llvm-project/pull/210621

>From 52dc65b3bd56306e629e38c58ce49b6cb4653052 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Tue, 14 Jul 2026 19:23:43 -0400
Subject: [PATCH 1/3] [flang] Lower initial values via ConvertConstant instead
 of the legacy expression lowering

Global and component initial values were lowered with
createSomeInitializerExpression (the legacy, non-HLFIR expression lowering).
Route them through ConvertConstant instead, which is self-contained and safe
inside a fir.global initializer region: it never consults the symbol map,
allocates temporaries, or outlines constants into memory.

Expose ConvertConstant's folded-constant lowering as a public entry point,
genConstantExprValue, and use it from ConvertVariable's genInitializerExprValue.
The initializer forms the front end can deliver here are bounded (a folded
Constant<T>, possibly parenthesized, or a constant StructureConstructor):

  - Per-kind constant lowering uses evaluate::UnwrapConstantValue<T>, which
    looks through Parentheses<T>, so a parenthesized scalar constant such as
    "integer :: i = (42)" is lowered to a plain constant rather than wrapped in
    a fir.no_reassoc.
  - The derived-type overload additionally unwraps Parentheses<SomeDerived> and
    recurses, since a parenthesized structure constructor "(t(7))" is not a
    Constant<SomeDerived> and UnwrapConstantValue cannot see through it.
  - The two emitFatalError backstops include the offending expression and
    "in initializer" context.

Because genConstantExprValue takes no StatementContext, the now-dead context
plumbing is removed from the genInitializerExprValue / genComponentDefaultInit /
genDefaultInitializerValue / genScalarDefaultInitializerValue chain and its call
sites (the cleanupProhibited=true contexts guarded against cleanups that can no
longer be attached).

Observable IR change: fir.no_reassoc no longer appears in fir.global
initializer regions for parenthesized scalar/derived constants. Update
global-initialization-parens.f90 -- previously a pre-commit baseline pinning the
old fir.no_reassoc behavior -- to assert the new folded form, and add
common-block-derived-initialization.f90 (a DATA-initialized common block with a
derived-type member).
---
 flang/include/flang/Lower/ConvertConstant.h   |  8 ++
 flang/lib/Lower/ConvertConstant.cpp           | 27 ++++++-
 flang/lib/Lower/ConvertVariable.cpp           | 76 +++++++------------
 .../common-block-derived-initialization.f90   | 35 +++++++++
 .../Lower/global-initialization-parens.f90    | 48 +++++-------
 5 files changed, 113 insertions(+), 81 deletions(-)
 create mode 100644 flang/test/Lower/common-block-derived-initialization.f90

diff --git a/flang/include/flang/Lower/ConvertConstant.h b/flang/include/flang/Lower/ConvertConstant.h
index 955e1a3e30f8b..dd19507d4c005 100644
--- a/flang/include/flang/Lower/ConvertConstant.h
+++ b/flang/include/flang/Lower/ConvertConstant.h
@@ -78,6 +78,14 @@ genInlinedStructureCtorLit(Fortran::lower::AbstractConverter &converter,
                            mlir::Location loc,
                            const Fortran::evaluate::StructureConstructor &ctor);
 
+/// Lower a constant expression from an initializer (semantics guarantees a
+/// folded Constant<T>, possibly parenthesized, or a constant
+/// StructureConstructor). Safe inside fir.global initializer regions: never
+/// allocates temporaries or outlines constants into memory.
+fir::ExtendedValue genConstantExprValue(AbstractConverter &converter,
+                                        mlir::Location loc,
+                                        const SomeExpr &expr);
+
 } // namespace Fortran::lower
 
 #endif // FORTRAN_LOWER_CONVERTCONSTANT_H
diff --git a/flang/lib/Lower/ConvertConstant.cpp b/flang/lib/Lower/ConvertConstant.cpp
index ca24965c51668..231cd80c45b31 100644
--- a/flang/lib/Lower/ConvertConstant.cpp
+++ b/flang/lib/Lower/ConvertConstant.cpp
@@ -12,6 +12,7 @@
 
 #include "flang/Lower/ConvertConstant.h"
 #include "flang/Evaluate/expression.h"
+#include "flang/Evaluate/fold.h"
 #include "flang/Lower/AbstractConverter.h"
 #include "flang/Lower/BuiltinModules.h"
 #include "flang/Lower/ConvertExprToHLFIR.h"
@@ -825,7 +826,17 @@ genConstantValue(Fortran::lower::AbstractConverter &converter,
           std::get_if<Fortran::evaluate::StructureConstructor>(&constantExpr.u))
     return Fortran::lower::genInlinedStructureCtorLit(converter, loc,
                                                       *structCtor);
-  fir::emitFatalError(loc, "not a constant derived type expression");
+  // Initializer folding preserves parentheses around a derived constant or a
+  // structure constructor (e.g. "type(t) :: x = (t(7))"). Look through them.
+  // UnwrapConstantValue cannot see through Parentheses<SomeDerived> because a
+  // StructureConstructor is not a Constant<SomeDerived>.
+  if (const auto *parens = std::get_if<
+          Fortran::evaluate::Parentheses<Fortran::evaluate::SomeDerived>>(
+          &constantExpr.u))
+    return genConstantValue(converter, loc, parens->left());
+  fir::emitFatalError(loc, "expected a constant derived type expression in "
+                           "initializer, but got: " +
+                               constantExpr.AsFortran());
 }
 
 template <Fortran::common::TypeCategory TC, int KIND>
@@ -834,11 +845,15 @@ static fir::ExtendedValue genConstantValue(
     const Fortran::evaluate::Expr<Fortran::evaluate::Type<TC, KIND>>
         &constantExpr) {
   using T = Fortran::evaluate::Type<TC, KIND>;
+  // Initializer folding preserves parentheses around a scalar constant (e.g.
+  // "integer :: i = (42)"). UnwrapConstantValue looks through them.
   if (const auto *constant =
-          std::get_if<Fortran::evaluate::Constant<T>>(&constantExpr.u))
+          Fortran::evaluate::UnwrapConstantValue<T>(constantExpr))
     return Fortran::lower::convertConstant(converter, loc, *constant,
                                            /*outline=*/false);
-  fir::emitFatalError(loc, "not an evaluate::Constant<T>");
+  fir::emitFatalError(loc, "expected a constant expression in initializer, "
+                           "but got: " +
+                               constantExpr.AsFortran());
 }
 
 static fir::ExtendedValue
@@ -867,6 +882,12 @@ genConstantValue(Fortran::lower::AbstractConverter &converter,
       constantExpr.u);
 }
 
+fir::ExtendedValue Fortran::lower::genConstantExprValue(
+    Fortran::lower::AbstractConverter &converter, mlir::Location loc,
+    const Fortran::lower::SomeExpr &expr) {
+  return genConstantValue(converter, loc, expr);
+}
+
 fir::ExtendedValue Fortran::lower::genInlinedStructureCtorLit(
     Fortran::lower::AbstractConverter &converter, mlir::Location loc,
     const Fortran::evaluate::StructureConstructor &ctor) {
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 8031eb24b338b..f9d897a3e6a7a 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -17,7 +17,6 @@
 #include "flang/Lower/CUDA.h"
 #include "flang/Lower/CallInterface.h"
 #include "flang/Lower/ConvertConstant.h"
-#include "flang/Lower/ConvertExpr.h"
 #include "flang/Lower/ConvertExprToHLFIR.h"
 #include "flang/Lower/ConvertProcedureDesignator.h"
 #include "flang/Lower/Mangler.h"
@@ -127,16 +126,11 @@ hasAllocatableDirectComponent(const Fortran::semantics::Symbol &sym) {
 static fir::ExtendedValue
 genInitializerExprValue(Fortran::lower::AbstractConverter &converter,
                         mlir::Location loc,
-                        const Fortran::lower::SomeExpr &expr,
-                        Fortran::lower::StatementContext &stmtCtx) {
-  // Data initializer are constant value and should not depend on other symbols
-  // given the front-end fold parameter references. In any case, the "current"
-  // map of the converter should not be used since it holds mapping to
-  // mlir::Value from another mlir region. If these value are used by accident
-  // in the initializer, this will lead to segfaults in mlir code.
-  Fortran::lower::SymMap emptyMap;
-  return Fortran::lower::createSomeInitializerExpression(loc, converter, expr,
-                                                         emptyMap, stmtCtx);
+                        const Fortran::lower::SomeExpr &expr) {
+  // Initializers are folded constants; lower them via ConvertConstant, which
+  // never consults the symbol map or allocates temporaries and is therefore
+  // safe inside a fir.global initializer region.
+  return Fortran::lower::genConstantExprValue(converter, loc, expr);
 }
 
 /// Can this symbol constant be placed in read-only memory?
@@ -342,16 +336,16 @@ mlir::Value Fortran::lower::genInitialDataTarget(
 /// type \p symTy.
 static mlir::Value genDefaultInitializerValue(
     Fortran::lower::AbstractConverter &converter, mlir::Location loc,
-    const Fortran::semantics::Symbol &sym, mlir::Type symTy,
-    Fortran::lower::StatementContext &stmtCtx);
+    const Fortran::semantics::Symbol &sym, mlir::Type symTy);
 
 /// Generate the initial value of a derived component \p component and insert
 /// it into the derived type initial value \p insertInto of type \p recTy.
 /// Return the new derived type initial value after the insertion.
-static mlir::Value genComponentDefaultInit(
-    Fortran::lower::AbstractConverter &converter, mlir::Location loc,
-    const Fortran::semantics::Symbol &component, fir::RecordType recTy,
-    mlir::Value insertInto, Fortran::lower::StatementContext &stmtCtx) {
+static mlir::Value
+genComponentDefaultInit(Fortran::lower::AbstractConverter &converter,
+                        mlir::Location loc,
+                        const Fortran::semantics::Symbol &component,
+                        fir::RecordType recTy, mlir::Value insertInto) {
   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
   std::string name = converter.getRecordTypeFieldName(component);
   mlir::Type componentTy = recTy.getType(name);
@@ -367,8 +361,8 @@ static mlir::Value genComponentDefaultInit(
             genInitialDataTarget(converter, loc, componentTy, *init);
       else
         // Initial value.
-        componentValue = fir::getBase(
-            genInitializerExprValue(converter, loc, *init, stmtCtx));
+        componentValue =
+            fir::getBase(genInitializerExprValue(converter, loc, *init));
     } else if (Fortran::semantics::IsAllocatableOrPointer(component)) {
       // Pointer or allocatable without initialization.
       // Create deallocated/disassociated value.
@@ -379,8 +373,8 @@ static mlir::Value genComponentDefaultInit(
           fir::factory::createUnallocatedBox(builder, loc, componentTy, {});
     } else if (Fortran::lower::hasDefaultInitialization(component)) {
       // Component type has default initialization.
-      componentValue = genDefaultInitializerValue(converter, loc, component,
-                                                  componentTy, stmtCtx);
+      componentValue =
+          genDefaultInitializerValue(converter, loc, component, componentTy);
     } else {
       // Component has no initial value. Set its bits to zero by extension
       // to match what is expected because other compilers are doing it.
@@ -415,8 +409,7 @@ static mlir::Value genComponentDefaultInit(
 
 static mlir::Value genDefaultInitializerValue(
     Fortran::lower::AbstractConverter &converter, mlir::Location loc,
-    const Fortran::semantics::Symbol &sym, mlir::Type symTy,
-    Fortran::lower::StatementContext &stmtCtx) {
+    const Fortran::semantics::Symbol &sym, mlir::Type symTy) {
   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
   mlir::Type scalarType = symTy;
   fir::SequenceType sequenceType;
@@ -444,8 +437,8 @@ static mlir::Value genDefaultInitializerValue(
     assert(scopeIter != derivedScope->cend() &&
            "failed to find derived type component symbol");
     const Fortran::semantics::Symbol &component = scopeIter->second.get();
-    initialValue = genComponentDefaultInit(converter, loc, component, recTy,
-                                           initialValue, stmtCtx);
+    initialValue =
+        genComponentDefaultInit(converter, loc, component, recTy, initialValue);
   }
 
   if (sequenceType) {
@@ -470,11 +463,7 @@ static mlir::Value genDefaultInitializerValue(
 mlir::Value Fortran::lower::genScalarDefaultInitializerValue(
     Fortran::lower::AbstractConverter &converter, mlir::Location loc,
     const Fortran::semantics::Symbol &sym, mlir::Type symTy) {
-  Fortran::lower::StatementContext stmtCtx;
-  mlir::Value result =
-      genDefaultInitializerValue(converter, loc, sym, symTy, stmtCtx);
-  stmtCtx.finalizeAndPop();
-  return result;
+  return genDefaultInitializerValue(converter, loc, sym, symTy);
 }
 
 /// Does this global already have an initializer ?
@@ -570,10 +559,8 @@ fir::GlobalOp Fortran::lower::defineGlobal(
     if (oeDetails->init()) {
       createGlobalInitialization(
           builder, global, [&](fir::FirOpBuilder &builder) {
-            Fortran::lower::StatementContext stmtCtx(
-                /*cleanupProhibited=*/true);
             fir::ExtendedValue initVal = genInitializerExprValue(
-                converter, loc, oeDetails->init().value(), stmtCtx);
+                converter, loc, oeDetails->init().value());
             mlir::Value castTo =
                 builder.createConvert(loc, symTy, fir::getBase(initVal));
             fir::HasValueOp::create(builder, loc, castTo);
@@ -581,10 +568,8 @@ fir::GlobalOp Fortran::lower::defineGlobal(
     } else if (Fortran::lower::hasDefaultInitialization(sym)) {
       createGlobalInitialization(
           builder, global, [&](fir::FirOpBuilder &builder) {
-            Fortran::lower::StatementContext stmtCtx(
-                /*cleanupProhibited=*/true);
             mlir::Value initVal =
-                genDefaultInitializerValue(converter, loc, sym, symTy, stmtCtx);
+                genDefaultInitializerValue(converter, loc, sym, symTy);
             mlir::Value castTo = builder.createConvert(loc, symTy, initVal);
             fir::HasValueOp::create(builder, loc, castTo);
           });
@@ -595,8 +580,6 @@ fir::GlobalOp Fortran::lower::defineGlobal(
       auto sym{*details->init()};
       if (sym) // Has a procedure target.
         createGlobalInitialization(builder, global, [&](fir::FirOpBuilder &b) {
-          Fortran::lower::StatementContext stmtCtx(
-              /*cleanupProhibited=*/true);
           auto box{Fortran::lower::convertProcedureDesignatorInitialTarget(
               converter, loc, *sym)};
           auto castTo{builder.createConvert(loc, symTy, box)};
@@ -907,10 +890,8 @@ genInlinedInitWithMemcpy(Fortran::lower::AbstractConverter &converter,
                                   /*dataAttr=*/{});
     createGlobalInitialization(
         builder, global, [&](fir::FirOpBuilder &builder) {
-          Fortran::lower::StatementContext stmtCtx(
-              /*cleanupProhibited=*/true);
           fir::ExtendedValue initVal = genInitializerExprValue(
-              converter, symLoc, details->init().value(), stmtCtx);
+              converter, symLoc, details->init().value());
           mlir::Value castTo =
               builder.createConvert(symLoc, symTy, fir::getBase(initVal));
           fir::HasValueOp::create(builder, symLoc, castTo);
@@ -923,10 +904,8 @@ genInlinedInitWithMemcpy(Fortran::lower::AbstractConverter &converter,
                                   /*dataAttr=*/{});
     createGlobalInitialization(
         builder, global, [&](fir::FirOpBuilder &builder) {
-          Fortran::lower::StatementContext stmtCtx(
-              /*cleanupProhibited=*/true);
-          mlir::Value initVal = genDefaultInitializerValue(converter, symLoc,
-                                                           sym, symTy, stmtCtx);
+          mlir::Value initVal =
+              genDefaultInitializerValue(converter, symLoc, sym, symTy);
           mlir::Value castTo = builder.createConvert(symLoc, symTy, initVal);
           fir::HasValueOp::create(builder, symLoc, castTo);
         });
@@ -1399,9 +1378,8 @@ static fir::GlobalOp defineGlobalAggregateStore(
       if (objectDetails->init()) {
         createGlobalInitialization(
             builder, global, [&](fir::FirOpBuilder &builder) {
-              Fortran::lower::StatementContext stmtCtx;
               mlir::Value initVal = fir::getBase(genInitializerExprValue(
-                  converter, loc, objectDetails->init().value(), stmtCtx));
+                  converter, loc, objectDetails->init().value()));
               fir::HasValueOp::create(builder, loc, initVal);
             });
         return global;
@@ -1410,7 +1388,6 @@ static fir::GlobalOp defineGlobalAggregateStore(
   // value to ensure this is consider an object definition in the IR regardless
   // of the linkage.
   createGlobalInitialization(builder, global, [&](fir::FirOpBuilder &builder) {
-    Fortran::lower::StatementContext stmtCtx;
     mlir::Value initVal = fir::ZeroOp::create(builder, loc, aggTy);
     fir::HasValueOp::create(builder, loc, initVal);
   });
@@ -1712,13 +1689,12 @@ static void finalizeCommonBlockDefinition(
         if (memDet->init()) {
           LLVM_DEBUG(llvm::dbgs()
                      << "offset: " << mem->offset() << " is " << *mem << '\n');
-          Fortran::lower::StatementContext stmtCtx;
           auto initExpr = memDet->init().value();
           fir::ExtendedValue initVal =
               Fortran::semantics::IsPointer(*mem)
                   ? Fortran::lower::genInitialDataTarget(
                         converter, loc, converter.genType(*mem), initExpr)
-                  : genInitializerExprValue(converter, loc, initExpr, stmtCtx);
+                  : genInitializerExprValue(converter, loc, initExpr);
           mlir::IntegerAttr offVal = builder.getIntegerAttr(idxTy, tupIdx);
           mlir::Value castVal = builder.createConvert(
               loc, commonTy.getType(tupIdx), fir::getBase(initVal));
diff --git a/flang/test/Lower/common-block-derived-initialization.f90 b/flang/test/Lower/common-block-derived-initialization.f90
new file mode 100644
index 0000000000000..1eef7e69e9e11
--- /dev/null
+++ b/flang/test/Lower/common-block-derived-initialization.f90
@@ -0,0 +1,35 @@
+! Test lowering of a DATA-initialized common block, including a derived-type
+! (structure-constructor) member, through the fir.global initializer region.
+!
+! Initial values are lowered via ConvertConstant. This pins the common-block
+! initialization path (which reroutes every member's initial value through the
+! same constant-lowering entry point) for a scalar, a real, and a derived-type
+! member built from a structure constructor. DATA values are never
+! parenthesized, so no fir.no_reassoc is expected.
+
+! RUN: %flang_fc1 -emit-hlfir %s -o - | FileCheck %s --implicit-check-not=fir.no_reassoc
+
+block data bd
+  type sq
+    sequence
+    integer :: n
+  end type
+  integer :: cbi
+  real :: cbr
+  type(sq) :: cbx
+  common /blk/ cbi, cbr, cbx
+  data cbi /42/, cbr /3.5/, cbx /sq(9)/
+end block data
+
+! CHECK-LABEL: fir.global @blk_
+! CHECK-SAME:    tuple<i32, f32, !fir.type<_QTsq,sequence{n:i32}>>
+! CHECK:         %[[AGG0:.*]] = fir.zero_bits tuple<i32, f32, !fir.type<_QTsq,sequence{n:i32}>>
+! CHECK:         %[[C42:.*]] = arith.constant 42 : i32
+! CHECK:         %[[AGG1:.*]] = fir.insert_value %[[AGG0]], %[[C42]], [0 : index]
+! CHECK:         %[[CR:.*]] = arith.constant 3.500000e+00 : f32
+! CHECK:         %[[AGG2:.*]] = fir.insert_value %[[AGG1]], %[[CR]], [1 : index]
+! CHECK:         %[[SQ0:.*]] = fir.undefined !fir.type<_QTsq,sequence{n:i32}>
+! CHECK:         %[[C9:.*]] = arith.constant 9 : i32
+! CHECK:         %[[SQ1:.*]] = fir.insert_value %[[SQ0]], %[[C9]], ["n", !fir.type<_QTsq,sequence{n:i32}>]
+! CHECK:         %[[AGG3:.*]] = fir.insert_value %[[AGG2]], %[[SQ1]], [2 : index]
+! CHECK:         fir.has_value %[[AGG3]] : tuple<i32, f32, !fir.type<_QTsq,sequence{n:i32}>>
diff --git a/flang/test/Lower/global-initialization-parens.f90 b/flang/test/Lower/global-initialization-parens.f90
index 6769ad5775a7e..a9851522438d3 100644
--- a/flang/test/Lower/global-initialization-parens.f90
+++ b/flang/test/Lower/global-initialization-parens.f90
@@ -1,20 +1,21 @@
 ! Test lowering of parenthesized initial values in fir.global initializer
 ! regions.
 !
-! This test baselines the *current* lowering behavior of parenthesized
-! initializers, which an upcoming change to the initializer-lowering path will
-! modify: today a parenthesized scalar or derived constant survives folding as
-! a Parentheses node and is lowered to a fir.no_reassoc operation inside the
-! global init region. The forms whose parentheses are stripped before lowering
+! Initial values are lowered through ConvertConstant, which folds a
+! parenthesized scalar or derived constant to a plain constant: no
+! fir.no_reassoc operation is emitted inside the global init region. (An earlier
+! baseline of this test pinned the previous behavior, where a parenthesized
+! constant survived folding as a Parentheses node and lowered to a
+! fir.no_reassoc.) The forms whose parentheses are stripped before lowering
 ! (character, array named-constant, and parenthesized structure-constructor
-! components) are pinned here belt-and-braces so the follow-up change is shown
-! to leave them untouched.
+! components) are unaffected and pinned here belt-and-braces.
 !
 ! Each case is a SAVE'd local in its own subroutine so that the fir.global for
 ! it is emitted in source order, keeping every CHECK block next to the Fortran
-! it checks.
+! it checks. The --implicit-check-not on the RUN line asserts that no
+! fir.no_reassoc is emitted for any of the parenthesized initializers.
 
-! RUN: %flang_fc1 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -emit-hlfir %s -o - | FileCheck %s --implicit-check-not=fir.no_reassoc
 
 module types
   type t
@@ -31,24 +32,21 @@ subroutine scalar_int()
 end subroutine
 ! CHECK-LABEL: fir.global internal @_QFscalar_intEi : i32 {
 ! CHECK:         %[[C:.*]] = arith.constant 42 : i32
-! CHECK:         %[[NR:.*]] = fir.no_reassoc %[[C]] : i32
-! CHECK:         fir.has_value %[[NR]] : i32
+! CHECK:         fir.has_value %[[C]] : i32
 
 subroutine scalar_real()
   real, save :: r = (3.5)
 end subroutine
 ! CHECK-LABEL: fir.global internal @_QFscalar_realEr : f32 {
 ! CHECK:         %[[C:.*]] = arith.constant 3.500000e+00 : f32
-! CHECK:         %[[NR:.*]] = fir.no_reassoc %[[C]] : f32
-! CHECK:         fir.has_value %[[NR]] : f32
+! CHECK:         fir.has_value %[[C]] : f32
 
 subroutine scalar_logical()
   logical, save :: l = (.true.)
 end subroutine
 ! CHECK-LABEL: fir.global internal @_QFscalar_logicalEl : !fir.logical<4> {
 ! CHECK:         %[[C:.*]] = arith.constant true
-! CHECK:         %[[NR:.*]] = fir.no_reassoc %[[C]] : i1
-! CHECK:         %[[CV:.*]] = fir.convert %[[NR]] : (i1) -> !fir.logical<4>
+! CHECK:         %[[CV:.*]] = fir.convert %[[C]] : (i1) -> !fir.logical<4>
 ! CHECK:         fir.has_value %[[CV]] : !fir.logical<4>
 
 subroutine scalar_complex()
@@ -58,31 +56,27 @@ subroutine scalar_complex()
 end subroutine
 ! CHECK-LABEL: fir.global internal @_QFscalar_complexEz : complex<f32> {
 ! CHECK:         fir.insert_value
-! CHECK:         %[[NR:.*]] = fir.no_reassoc %{{.*}} : complex<f32>
-! CHECK:         fir.has_value %[[NR]] : complex<f32>
+! CHECK:         %[[IV:.*]] = fir.insert_value
+! CHECK:         fir.has_value %[[IV]] : complex<f32>
 
 subroutine paren_ctor()
   use types
-  ! Parenthesized structure constructor: the parentheses survive folding and
-  ! wrap the insert_value chain in a fir.no_reassoc.
+  ! Parenthesized structure constructor: folded to a plain insert_value chain.
   type(t), save :: x = (t(7))
 end subroutine
 ! CHECK-LABEL: fir.global internal @_QFparen_ctorEx : !fir.type<_QMtypesTt{n:i32}> {
 ! CHECK:         %[[IV:.*]] = fir.insert_value %{{.*}}, %{{.*}}, ["n", !fir.type<_QMtypesTt{n:i32}>]
-! CHECK:         %[[NR:.*]] = fir.no_reassoc %[[IV]] : !fir.type<_QMtypesTt{n:i32}>
-! CHECK:         fir.has_value %[[NR]] : !fir.type<_QMtypesTt{n:i32}>
+! CHECK:         fir.has_value %[[IV]] : !fir.type<_QMtypesTt{n:i32}>
 
 subroutine comp_default()
   use types
   ! Default-initialized object exercising the parenthesized component default:
-  ! the parentheses wrap the component value in a fir.no_reassoc before it is
-  ! inserted.
+  ! folded to a plain component value.
   type(t2), save :: w
 end subroutine
 ! CHECK-LABEL: fir.global internal @_QFcomp_defaultEw : !fir.type<_QMtypesTt2{n:i32}> {
 ! CHECK:         %[[C:.*]] = arith.constant 5 : i32
-! CHECK:         %[[NR:.*]] = fir.no_reassoc %[[C]] : i32
-! CHECK:         %[[IV:.*]] = fir.insert_value %{{.*}}, %[[NR]], ["n", !fir.type<_QMtypesTt2{n:i32}>]
+! CHECK:         %[[IV:.*]] = fir.insert_value %{{.*}}, %[[C]], ["n", !fir.type<_QMtypesTt2{n:i32}>]
 ! CHECK:         fir.has_value %[[IV]] : !fir.type<_QMtypesTt2{n:i32}>
 
 subroutine char_paren()
@@ -91,7 +85,6 @@ subroutine char_paren()
 end subroutine
 ! CHECK-LABEL: fir.global internal @_QFchar_parenEc : !fir.char<1,2> {
 ! CHECK:         %[[S:.*]] = fir.string_lit "ab"(2) : !fir.char<1,2>
-! CHECK-NOT:     fir.no_reassoc
 ! CHECK:         fir.has_value %[[S]] : !fir.char<1,2>
 
 subroutine array_named_const()
@@ -104,10 +97,9 @@ subroutine array_named_const()
 subroutine paren_ctor_comp()
   use types
   ! Parenthesized structure-constructor component: parentheses stripped, plain
-  ! insert_value chain with no fir.no_reassoc.
+  ! insert_value chain.
   type(t), save :: y = t((5))
 end subroutine
 ! CHECK-LABEL: fir.global internal @_QFparen_ctor_compEy : !fir.type<_QMtypesTt{n:i32}> {
 ! CHECK:         %[[IV:.*]] = fir.insert_value %{{.*}}, %{{.*}}, ["n", !fir.type<_QMtypesTt{n:i32}>]
-! CHECK-NOT:     fir.no_reassoc
 ! CHECK:         fir.has_value %[[IV]] : !fir.type<_QMtypesTt{n:i32}>

>From 52d17c66dcd9218287e9d6a590f28f85be7c1da3 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 19 Jul 2026 13:57:21 -0400
Subject: [PATCH 2/3] [flang][NFC] Document that the initializer fatal errors
 are semantics-escape backstops

Semantics gates every non-pointer initializer through
NonPointerInitializationExpr, which only accepts expressions admitted by
evaluate::IsActuallyConstant -- exactly the forms genConstantValue handles.
Note at both emitFatalError sites that hitting them means an initializer
escaped that semantic check.
---
 flang/lib/Lower/ConvertConstant.cpp | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/flang/lib/Lower/ConvertConstant.cpp b/flang/lib/Lower/ConvertConstant.cpp
index 231cd80c45b31..70dc4c77ab869 100644
--- a/flang/lib/Lower/ConvertConstant.cpp
+++ b/flang/lib/Lower/ConvertConstant.cpp
@@ -834,6 +834,10 @@ genConstantValue(Fortran::lower::AbstractConverter &converter,
           Fortran::evaluate::Parentheses<Fortran::evaluate::SomeDerived>>(
           &constantExpr.u))
     return genConstantValue(converter, loc, parens->left());
+  // Semantics gates every initializer through NonPointerInitializationExpr,
+  // which only accepts expressions admitted by evaluate::IsActuallyConstant --
+  // exactly the forms handled above. If this fatal error is ever hit, an
+  // initializer escaped that semantic check.
   fir::emitFatalError(loc, "expected a constant derived type expression in "
                            "initializer, but got: " +
                                constantExpr.AsFortran());
@@ -851,6 +855,11 @@ static fir::ExtendedValue genConstantValue(
           Fortran::evaluate::UnwrapConstantValue<T>(constantExpr))
     return Fortran::lower::convertConstant(converter, loc, *constant,
                                            /*outline=*/false);
+  // Semantics gates every initializer through NonPointerInitializationExpr,
+  // which only accepts expressions admitted by evaluate::IsActuallyConstant --
+  // a Constant<T>, possibly parenthesized, once the derived-type forms are
+  // handled by the overload above. If this fatal error is ever hit, an
+  // initializer escaped that semantic check.
   fir::emitFatalError(loc, "expected a constant expression in initializer, "
                            "but got: " +
                                constantExpr.AsFortran());

>From 78bda8372a6d10f1fa580e8230a88072b7a9e77b Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 19 Jul 2026 15:50:08 -0400
Subject: [PATCH 3/3] [flang][NFC] Pin nested-paren and paren'd derived
 named-constant initializers

Cover the two new recursion paths in the initializer constant lowering:
stacked parentheses on a scalar (UnwrapConstantValue's recursive descent
through nested Parentheses<T>), and a parenthesized derived named constant
(the Parentheses<SomeDerived> unwrap landing on a Constant<SomeDerived>
rather than a structure constructor).
---
 .../Lower/global-initialization-parens.f90    | 23 +++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/flang/test/Lower/global-initialization-parens.f90 b/flang/test/Lower/global-initialization-parens.f90
index a9851522438d3..bdc96751b8708 100644
--- a/flang/test/Lower/global-initialization-parens.f90
+++ b/flang/test/Lower/global-initialization-parens.f90
@@ -34,6 +34,15 @@ subroutine scalar_int()
 ! CHECK:         %[[C:.*]] = arith.constant 42 : i32
 ! CHECK:         fir.has_value %[[C]] : i32
 
+subroutine nested_parens()
+  ! Stacked parentheses: exercises the recursive descent through nested
+  ! Parentheses<T> nodes.
+  integer, save :: n2 = ((42))
+end subroutine
+! CHECK-LABEL: fir.global internal @_QFnested_parensEn2 : i32 {
+! CHECK:         %[[C:.*]] = arith.constant 42 : i32
+! CHECK:         fir.has_value %[[C]] : i32
+
 subroutine scalar_real()
   real, save :: r = (3.5)
 end subroutine
@@ -68,6 +77,20 @@ subroutine paren_ctor()
 ! CHECK:         %[[IV:.*]] = fir.insert_value %{{.*}}, %{{.*}}, ["n", !fir.type<_QMtypesTt{n:i32}>]
 ! CHECK:         fir.has_value %[[IV]] : !fir.type<_QMtypesTt{n:i32}>
 
+subroutine paren_derived_named_const()
+  use types
+  ! Parenthesized derived named constant: the parentheses wrap a
+  ! Constant<SomeDerived> rather than a structure constructor.
+  type(t), parameter :: tp = t(3)
+  type(t), save :: pt2 = (tp)
+end subroutine
+! (The named constant tp also gets its own constant global; its position
+! relative to pt2's global is unpinned.)
+! CHECK-LABEL: fir.global internal @_QFparen_derived_named_constEpt2 : !fir.type<_QMtypesTt{n:i32}> {
+! CHECK:         %[[C:.*]] = arith.constant 3 : i32
+! CHECK:         %[[IV:.*]] = fir.insert_value %{{.*}}, %[[C]], ["n", !fir.type<_QMtypesTt{n:i32}>]
+! CHECK:         fir.has_value %[[IV]] : !fir.type<_QMtypesTt{n:i32}>
+
 subroutine comp_default()
   use types
   ! Default-initialized object exercising the parenthesized component default:



More information about the flang-commits mailing list