Skip to content

Commit 20103ee

Browse files
authored
[flang] Fix exposed "free" instances of ac-implied-do indices (llvm#178516)
Tweak the implementations of IsConstantExpr, IsInitialDataTarget, and related utilities so that "free" instances of array constructor implied DO indices are not treated as constant expressions when the surrounding context (if any) doesn't contain their bounds. This fixes a current bug in which a "free" implied DO index in a structure constructor got wrapped up an a Constant<SomeDerived>, which led to a crash in lowering.
1 parent bc45ea2 commit 20103ee

11 files changed

Lines changed: 119 additions & 58 deletions

File tree

flang/include/flang/Evaluate/check-expression.h

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,34 @@ namespace Fortran::evaluate {
2828
// Predicate: true when an expression is a constant expression (in the
2929
// strict sense of the Fortran standard); it may not (yet) be a hard
3030
// constant value.
31-
template <typename A> bool IsConstantExpr(const A &);
32-
extern template bool IsConstantExpr(const Expr<SomeType> &);
33-
extern template bool IsConstantExpr(const Expr<SomeInteger> &);
34-
extern template bool IsConstantExpr(const Expr<SubscriptInteger> &);
35-
extern template bool IsConstantExpr(const StructureConstructor &);
31+
// The FoldingContext pointer, if not null, prevents a "free" implied
32+
// DO index from being allowed in a constant expression or initial data
33+
// target if it is not registered in the FoldingContext. (Array constructor
34+
// implied DO indices are always allowed when the implied DO is within
35+
// the expression.)
36+
template <typename A>
37+
bool IsConstantExpr(const A &, const FoldingContext * = nullptr);
38+
extern template bool IsConstantExpr(
39+
const Expr<SomeType> &, const FoldingContext *);
40+
extern template bool IsConstantExpr(
41+
const Expr<SomeInteger> &, const FoldingContext *);
42+
extern template bool IsConstantExpr(
43+
const Expr<SubscriptInteger> &, const FoldingContext *);
44+
extern template bool IsConstantExpr(
45+
const StructureConstructor &, const FoldingContext *);
3646

3747
// Predicate: true when an expression is a constant expression (in the
3848
// strict sense of the Fortran standard) or a dummy argument with
3949
// INTENT(IN) and no VALUE. This is useful for representing explicit
4050
// shapes of other dummy arguments.
41-
template <typename A> bool IsScopeInvariantExpr(const A &);
42-
extern template bool IsScopeInvariantExpr(const Expr<SomeType> &);
43-
extern template bool IsScopeInvariantExpr(const Expr<SomeInteger> &);
44-
extern template bool IsScopeInvariantExpr(const Expr<SubscriptInteger> &);
51+
template <typename A>
52+
bool IsScopeInvariantExpr(const A &, const FoldingContext * = nullptr);
53+
extern template bool IsScopeInvariantExpr(
54+
const Expr<SomeType> &, const FoldingContext *);
55+
extern template bool IsScopeInvariantExpr(
56+
const Expr<SomeInteger> &, const FoldingContext *);
57+
extern template bool IsScopeInvariantExpr(
58+
const Expr<SubscriptInteger> &, const FoldingContext *);
4559

4660
// Predicate: true when an expression actually is a typed Constant<T>,
4761
// perhaps with parentheses and wrapping around it. False for all typeless
@@ -57,8 +71,8 @@ extern template bool IsActuallyConstant(
5771
// constant addressing and no vector-valued subscript.
5872
// If a non-null ContextualMessages pointer is passed, an error message
5973
// will be generated if and only if the result of the function is false.
60-
bool IsInitialDataTarget(
61-
const Expr<SomeType> &, parser::ContextualMessages * = nullptr);
74+
bool IsInitialDataTarget(const Expr<SomeType> &,
75+
parser::ContextualMessages * = nullptr, const FoldingContext * = nullptr);
6276

6377
bool IsInitialProcedureTarget(const Symbol &);
6478
bool IsInitialProcedureTarget(const ProcedureDesignator &);

flang/lib/Evaluate/check-expression.cpp

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ class IsConstantExprHelper
3434
: public AllTraverse<IsConstantExprHelper<INVARIANT>, true> {
3535
public:
3636
using Base = AllTraverse<IsConstantExprHelper, true>;
37-
IsConstantExprHelper() : Base{*this} {}
37+
explicit IsConstantExprHelper(const FoldingContext *c)
38+
: Base{*this}, context_{c} {}
3839
using Base::operator();
3940

4041
// A missing expression is not considered to be constant.
@@ -91,10 +92,33 @@ class IsConstantExprHelper
9192
!sym.attrs().test(semantics::Attr::VALUE)));
9293
}
9394

95+
bool operator()(const ImpliedDoIndex &ido) const {
96+
return acImpliedDos_.find(ido.name) != acImpliedDos_.end() || !context_ ||
97+
context_->GetImpliedDo(ido.name).has_value();
98+
}
99+
template <typename T> bool operator()(const ImpliedDo<T> &ido) {
100+
if (!(*this)(ido.lower()) || !(*this)(ido.upper()) ||
101+
!(*this)(ido.stride())) {
102+
return false;
103+
}
104+
bool deleteAfter{acImpliedDos_.insert(ido.name()).second};
105+
bool result{true};
106+
for (const auto &vals : ido.values()) {
107+
result &= (*this)(vals);
108+
}
109+
if (deleteAfter) {
110+
acImpliedDos_.erase(ido.name());
111+
}
112+
return result;
113+
}
114+
94115
private:
95116
bool IsConstantStructureConstructorComponent(
96117
const Symbol &, const Expr<SomeType> &) const;
97118
bool IsConstantExprShape(const Shape &) const;
119+
120+
std::set<parser::CharBlock> acImpliedDos_;
121+
const FoldingContext *context_{nullptr};
98122
};
99123

100124
template <bool INVARIANT>
@@ -103,7 +127,8 @@ bool IsConstantExprHelper<INVARIANT>::IsConstantStructureConstructorComponent(
103127
if (IsAllocatable(component)) {
104128
return IsNullObjectPointer(&expr);
105129
} else if (IsPointer(component)) {
106-
return IsNullPointerOrAllocatable(&expr) || IsInitialDataTarget(expr) ||
130+
return IsNullPointerOrAllocatable(&expr) ||
131+
IsInitialDataTarget(expr, /*messages=*/nullptr, context_) ||
107132
IsInitialProcedureTarget(expr);
108133
} else {
109134
return (*this)(expr);
@@ -175,21 +200,27 @@ bool IsConstantExprHelper<INVARIANT>::IsConstantExprShape(
175200
return true;
176201
}
177202

178-
template <typename A> bool IsConstantExpr(const A &x) {
179-
return IsConstantExprHelper<false>{}(x);
203+
template <typename A> bool IsConstantExpr(const A &x, const FoldingContext *c) {
204+
return IsConstantExprHelper<false>{c}(x);
180205
}
181-
template bool IsConstantExpr(const Expr<SomeType> &);
182-
template bool IsConstantExpr(const Expr<SomeInteger> &);
183-
template bool IsConstantExpr(const Expr<SubscriptInteger> &);
184-
template bool IsConstantExpr(const StructureConstructor &);
206+
template bool IsConstantExpr(const Expr<SomeType> &, const FoldingContext *);
207+
template bool IsConstantExpr(const Expr<SomeInteger> &, const FoldingContext *);
208+
template bool IsConstantExpr(
209+
const Expr<SubscriptInteger> &, const FoldingContext *);
210+
template bool IsConstantExpr(
211+
const StructureConstructor &, const FoldingContext *);
185212

186213
// IsScopeInvariantExpr()
187-
template <typename A> bool IsScopeInvariantExpr(const A &x) {
188-
return IsConstantExprHelper<true>{}(x);
214+
template <typename A>
215+
bool IsScopeInvariantExpr(const A &x, const FoldingContext *c) {
216+
return IsConstantExprHelper<true>{c}(x);
189217
}
190-
template bool IsScopeInvariantExpr(const Expr<SomeType> &);
191-
template bool IsScopeInvariantExpr(const Expr<SomeInteger> &);
192-
template bool IsScopeInvariantExpr(const Expr<SubscriptInteger> &);
218+
template bool IsScopeInvariantExpr(
219+
const Expr<SomeType> &, const FoldingContext *);
220+
template bool IsScopeInvariantExpr(
221+
const Expr<SomeInteger> &, const FoldingContext *);
222+
template bool IsScopeInvariantExpr(
223+
const Expr<SubscriptInteger> &, const FoldingContext *);
193224

194225
// IsActuallyConstant()
195226
struct IsActuallyConstantHelper {
@@ -207,13 +238,16 @@ struct IsActuallyConstantHelper {
207238
bool operator()(const StructureConstructor &x) {
208239
for (const auto &pair : x) {
209240
const Expr<SomeType> &y{pair.second.value()};
210-
const auto sym{pair.first};
211-
const bool compIsConstant{(*this)(y)};
212241
// If an allocatable component is initialized by a constant,
213242
// the structure constructor is not a constant.
214-
if ((!compIsConstant && !IsNullPointerOrAllocatable(&y)) ||
215-
(compIsConstant && IsAllocatable(sym))) {
216-
return false;
243+
if ((*this)(y)) {
244+
if (IsAllocatable(pair.first)) {
245+
return false;
246+
}
247+
} else {
248+
if (!IsNullPointerOrAllocatable(&y)) {
249+
return false;
250+
}
217251
}
218252
}
219253
return true;
@@ -241,8 +275,9 @@ class IsInitialDataTargetHelper
241275
public:
242276
using Base = AllTraverse<IsInitialDataTargetHelper, true>;
243277
using Base::operator();
244-
explicit IsInitialDataTargetHelper(parser::ContextualMessages *m)
245-
: Base{*this}, messages_{m} {}
278+
explicit IsInitialDataTargetHelper(
279+
parser::ContextualMessages *m, const FoldingContext *c)
280+
: Base{*this}, messages_{m}, context_{c} {}
246281

247282
bool emittedMessage() const { return emittedMessage_; }
248283

@@ -292,15 +327,16 @@ class IsInitialDataTargetHelper
292327
bool operator()(const StaticDataObject &) const { return false; }
293328
bool operator()(const TypeParamInquiry &) const { return false; }
294329
bool operator()(const Triplet &x) const {
295-
return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) &&
296-
IsConstantExpr(x.stride());
330+
return IsConstantExpr(x.lower(), context_) &&
331+
IsConstantExpr(x.upper(), context_) &&
332+
IsConstantExpr(x.stride(), context_);
297333
}
298334
bool operator()(const Subscript &x) const {
299335
return common::visit(common::visitors{
300336
[&](const Triplet &t) { return (*this)(t); },
301337
[&](const auto &y) {
302338
return y.value().Rank() == 0 &&
303-
IsConstantExpr(y.value());
339+
IsConstantExpr(y.value(), context_);
304340
},
305341
},
306342
x.u);
@@ -310,8 +346,8 @@ class IsInitialDataTargetHelper
310346
return CheckVarOrComponent(x.GetLastSymbol()) && (*this)(x.base());
311347
}
312348
bool operator()(const Substring &x) const {
313-
return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) &&
314-
(*this)(x.parent());
349+
return IsConstantExpr(x.lower(), context_) &&
350+
IsConstantExpr(x.upper(), context_) && (*this)(x.parent());
315351
}
316352
bool operator()(const DescriptorInquiry &) const { return false; }
317353
template <typename T> bool operator()(const ArrayConstructor<T> &) const {
@@ -358,13 +394,14 @@ class IsInitialDataTargetHelper
358394
return false;
359395
}
360396

361-
parser::ContextualMessages *messages_;
397+
parser::ContextualMessages *messages_{nullptr};
398+
const FoldingContext *context_{nullptr};
362399
bool emittedMessage_{false};
363400
};
364401

365-
bool IsInitialDataTarget(
366-
const Expr<SomeType> &x, parser::ContextualMessages *messages) {
367-
IsInitialDataTargetHelper helper{messages};
402+
bool IsInitialDataTarget(const Expr<SomeType> &x,
403+
parser::ContextualMessages *messages, const FoldingContext *context) {
404+
IsInitialDataTargetHelper helper{messages, context};
368405
bool result{helper(x)};
369406
if (!result && messages && !helper.emittedMessage()) {
370407
messages->Say(
@@ -732,7 +769,7 @@ class CheckSpecificationExprHelper
732769
x.base().GetFirstSymbol(), x.base().GetLastSymbol(), x.field())) {
733770
auto restorer{common::ScopedSet(inInquiry_, true)};
734771
return (*this)(x.base());
735-
} else if (IsConstantExpr(x)) {
772+
} else if (IsConstantExpr(x, &context_)) {
736773
return std::nullopt;
737774
} else {
738775
return "non-constant descriptor inquiry not allowed for local object";
@@ -741,7 +778,7 @@ class CheckSpecificationExprHelper
741778

742779
Result operator()(const TypeParamInquiry &inq) const {
743780
if (scope_.IsDerivedType()) {
744-
if (!IsConstantExpr(inq) &&
781+
if (!IsConstantExpr(inq, &context_) &&
745782
inq.base() /* X%T, not local T */) { // C750, C754
746783
return "non-constant reference to a type parameter inquiry not allowed "
747784
"for derived type components or type parameter values";
@@ -750,7 +787,7 @@ class CheckSpecificationExprHelper
750787
IsInquiryAlwaysPermissible(inq.base()->GetFirstSymbol())) {
751788
auto restorer{common::ScopedSet(inInquiry_, true)};
752789
return (*this)(inq.base());
753-
} else if (!IsConstantExpr(inq)) {
790+
} else if (!IsConstantExpr(inq, &context_)) {
754791
return "non-constant type parameter inquiry not allowed for local object";
755792
}
756793
return std::nullopt;
@@ -802,7 +839,7 @@ class CheckSpecificationExprHelper
802839
"' not allowed for derived type components or type parameter"
803840
" values";
804841
}
805-
if (inInquiry && !IsConstantExpr(x)) {
842+
if (inInquiry && !IsConstantExpr(x, &context_)) {
806843
return "non-constant reference to inquiry intrinsic '"s +
807844
intrin.name +
808845
"' not allowed for derived type components or type"
@@ -814,7 +851,7 @@ class CheckSpecificationExprHelper
814851
// DescriptorInquiry operations (LBOUND) are checked elsewhere. If a
815852
// call that makes it to here satisfies the requirements of a constant
816853
// expression (as Fortran defines it), it's fine.
817-
if (IsConstantExpr(x)) {
854+
if (IsConstantExpr(x, &context_)) {
818855
return std::nullopt;
819856
}
820857
if (intrin.name == "present") {

flang/lib/Evaluate/fold-implementation.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,8 +1295,6 @@ Expr<T> FoldOperation(FoldingContext &context, FunctionRef<T> &&funcRef) {
12951295
return Expr<T>{std::move(funcRef)};
12961296
}
12971297

1298-
Expr<ImpliedDoIndex::Result> FoldOperation(FoldingContext &, ImpliedDoIndex &&);
1299-
13001298
// Array constructor folding
13011299
template <typename T> class ArrayConstructorFolder {
13021300
public:

flang/lib/Evaluate/fold-integer.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,7 +1187,7 @@ Expr<Type<TypeCategory::Integer, KIND>> FoldIntrinsicFunction(
11871187
return common::visit(
11881188
[&](auto &kx) {
11891189
if (auto len{kx.LEN()}) {
1190-
if (IsScopeInvariantExpr(*len)) {
1190+
if (IsScopeInvariantExpr(*len, &context)) {
11911191
return Fold(context, ConvertToType<T>(*std::move(len)));
11921192
} else {
11931193
return Expr<T>{std::move(funcRef)};
@@ -1509,7 +1509,7 @@ Expr<TypeParamInquiry::Result> FoldOperation(
15091509
paramValue{
15101510
declType->derivedTypeSpec().FindParameter(parameterName)}) {
15111511
const semantics::MaybeIntExpr &paramExpr{paramValue->GetExplicit()};
1512-
if (paramExpr && IsConstantExpr(*paramExpr)) {
1512+
if (paramExpr && IsConstantExpr(*paramExpr, &context)) {
15131513
Expr<SomeInteger> intExpr{*paramExpr};
15141514
return Fold(context,
15151515
ConvertToType<TypeParamInquiry::Result>(std::move(intExpr)));
@@ -1530,7 +1530,7 @@ Expr<TypeParamInquiry::Result> FoldOperation(
15301530
if (details) {
15311531
isLen = details->attr() == common::TypeParamAttr::Len;
15321532
const semantics::MaybeIntExpr &initExpr{details->init()};
1533-
if (initExpr && IsConstantExpr(*initExpr) &&
1533+
if (initExpr && IsConstantExpr(*initExpr, &context) &&
15341534
(!isLen || ToInt64(*initExpr))) {
15351535
Expr<SomeInteger> expr{*initExpr};
15361536
return Fold(context,

flang/lib/Evaluate/fold.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Expr<SomeDerived> FoldOperation(
8080
} else if (IsProcedure(symbol)) {
8181
isConstant &= IsInitialProcedureTarget(expr);
8282
} else {
83-
isConstant &= IsInitialDataTarget(expr);
83+
isConstant &= IsInitialDataTarget(expr, /*messages=*/nullptr, &context);
8484
}
8585
} else if (IsAllocatable(symbol)) {
8686
// F2023: 10.1.12 (3)(a)

flang/lib/Evaluate/intrinsics.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2564,7 +2564,7 @@ std::optional<SpecificCall> IntrinsicInterface::Match(
25642564
}
25652565
}
25662566
if (context.analyzingPDTComponentKindSelector() && expr &&
2567-
IsConstantExpr(*expr)) {
2567+
IsConstantExpr(*expr, &context)) {
25682568
// Don't emit an error about a KIND= actual argument value when
25692569
// processing a kind selector in a PDT component declaration before
25702570
// it is instantianted, so long as it's a constant expression.

flang/lib/Evaluate/shape.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ class GetLowerBoundHelper
268268
semantics::IsAssumedSizeArray(symbol)) {
269269
// last dimension of assumed-size dummy array: don't worry
270270
// about handling an empty dimension
271-
ok = !invariantOnly_ || IsScopeInvariantExpr(*lbound);
271+
ok = !invariantOnly_ || IsScopeInvariantExpr(*lbound, context_);
272272
} else if (lbValue.value_or(0) == 1) {
273273
// Lower bound is 1, regardless of extent
274274
ok = true;
@@ -651,7 +651,7 @@ static MaybeExtentExpr GetExplicitUBOUND(FoldingContext *context,
651651
const semantics::ShapeSpec &shapeSpec, bool invariantOnly) {
652652
const auto &ubound{shapeSpec.ubound().GetExplicit()};
653653
if (ubound && ubound->Rank() == 0 &&
654-
(!invariantOnly || IsScopeInvariantExpr(*ubound))) {
654+
(!invariantOnly || IsScopeInvariantExpr(*ubound, context))) {
655655
if (auto extent{GetNonNegativeExtent(shapeSpec, invariantOnly)}) {
656656
if (auto cstExtent{ToInt64(
657657
context ? Fold(*context, std::move(*extent)) : *extent)}) {

flang/lib/Semantics/check-data.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ class DataVarChecker : public evaluate::AllTraverse<DataVarChecker, true> {
185185
}
186186
bool CheckSubscriptExpr(
187187
const evaluate::Expr<evaluate::SubscriptInteger> &expr) const {
188-
if (!evaluate::IsConstantExpr(expr)) { // C875,C881
188+
if (!evaluate::IsConstantExpr(expr, /*context=*/
189+
nullptr /* to accept unbound implied DO indices */)) { // C875,C881
189190
context_.Say(
190191
source_, "Data object must have constant subscripts"_err_en_US);
191192
return false;

flang/lib/Semantics/expression.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1731,7 +1731,9 @@ class ArrayConstructorContext {
17311731
if (type_) {
17321732
auto len{type_->LEN()};
17331733
if (explicitType_ ||
1734-
(len && IsConstantExpr(*len) && !ContainsAnyImpliedDoIndex(*len))) {
1734+
(len &&
1735+
IsConstantExpr(
1736+
*len, &exprAnalyzer_.context().foldingContext()))) {
17351737
return len;
17361738
}
17371739
}

flang/lib/Semantics/pointer-assignment.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -614,8 +614,8 @@ bool CheckPointerAssignment(SemanticsContext &context, parser::CharBlock source,
614614

615615
bool CheckInitialDataPointerTarget(SemanticsContext &context,
616616
const SomeExpr &pointer, const SomeExpr &init, const Scope &scope) {
617-
return evaluate::IsInitialDataTarget(
618-
init, &context.foldingContext().messages()) &&
617+
return evaluate::IsInitialDataTarget(init,
618+
&context.foldingContext().messages(), &context.foldingContext()) &&
619619
CheckPointerAssignment(context, pointer, init, scope,
620620
/*isBoundsRemapping=*/false,
621621
/*isAssumedRank=*/false);

0 commit comments

Comments
 (0)