[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