[flang] Don't duplicate impure function call for UBOUND() (#153648)

Because the per-dimension information in a descriptor holds an extent
and a lower bound, but not an upper bound, the calculation of the upper
bound sometimes requires that the extent and lower bound be extracted
from a descriptor and added together, minus 1. This shouldn't be
attempted when the NamedEntity of the descriptor is something that
shouldn't be duplicated and used twice; specifically, it shouldn't apply
to NamedEntities containing references to impure functions as parts of
subscript expressions.

Fixes https://github.com/llvm/llvm-project/issues/153031.
This commit is contained in:
Peter Klausler 2025-08-18 14:43:13 -07:00 committed by GitHub
parent 48232594a0
commit 2cf982c0f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 41 additions and 14 deletions

View File

@ -1144,15 +1144,14 @@ std::optional<std::string> FindImpureCall(
std::optional<std::string> FindImpureCall( std::optional<std::string> FindImpureCall(
FoldingContext &, const ProcedureRef &); FoldingContext &, const ProcedureRef &);
// Predicate: is a scalar expression suitable for naive scalar expansion // Predicate: does an expression contain anything that would prevent it from
// in the flattening of an array expression? // being duplicated so that two instances of it then appear in the same
// TODO: capture such scalar expansions in temporaries, flatten everything // expression?
class UnexpandabilityFindingVisitor class UnsafeToCopyVisitor : public AnyTraverse<UnsafeToCopyVisitor> {
: public AnyTraverse<UnexpandabilityFindingVisitor> {
public: public:
using Base = AnyTraverse<UnexpandabilityFindingVisitor>; using Base = AnyTraverse<UnsafeToCopyVisitor>;
using Base::operator(); using Base::operator();
explicit UnexpandabilityFindingVisitor(bool admitPureCall) explicit UnsafeToCopyVisitor(bool admitPureCall)
: Base{*this}, admitPureCall_{admitPureCall} {} : Base{*this}, admitPureCall_{admitPureCall} {}
template <typename T> bool operator()(const FunctionRef<T> &procRef) { template <typename T> bool operator()(const FunctionRef<T> &procRef) {
return !admitPureCall_ || !procRef.proc().IsPure(); return !admitPureCall_ || !procRef.proc().IsPure();
@ -1163,14 +1162,22 @@ private:
bool admitPureCall_{false}; bool admitPureCall_{false};
}; };
template <typename A>
bool IsSafelyCopyable(const A &x, bool admitPureCall = false) {
return !UnsafeToCopyVisitor{admitPureCall}(x);
}
// Predicate: is a scalar expression suitable for naive scalar expansion
// in the flattening of an array expression?
// TODO: capture such scalar expansions in temporaries, flatten everything
template <typename T> template <typename T>
bool IsExpandableScalar(const Expr<T> &expr, FoldingContext &context, bool IsExpandableScalar(const Expr<T> &expr, FoldingContext &context,
const Shape &shape, bool admitPureCall = false) { const Shape &shape, bool admitPureCall = false) {
if (UnexpandabilityFindingVisitor{admitPureCall}(expr)) { if (IsSafelyCopyable(expr, admitPureCall)) {
return true;
} else {
auto extents{AsConstantExtents(context, shape)}; auto extents{AsConstantExtents(context, shape)};
return extents && !HasNegativeExtent(*extents) && GetSize(*extents) == 1; return extents && !HasNegativeExtent(*extents) && GetSize(*extents) == 1;
} else {
return true;
} }
} }

View File

@ -623,7 +623,7 @@ MaybeExtentExpr GetRawUpperBound(
} else if (semantics::IsAssumedSizeArray(symbol) && } else if (semantics::IsAssumedSizeArray(symbol) &&
dimension + 1 == symbol.Rank()) { dimension + 1 == symbol.Rank()) {
return std::nullopt; return std::nullopt;
} else { } else if (IsSafelyCopyable(base, /*admitPureCall=*/true)) {
return ComputeUpperBound( return ComputeUpperBound(
GetRawLowerBound(base, dimension), GetExtent(base, dimension)); GetRawLowerBound(base, dimension), GetExtent(base, dimension));
} }
@ -678,11 +678,13 @@ static MaybeExtentExpr GetUBOUND(FoldingContext *context,
} else if (semantics::IsAssumedSizeArray(symbol) && } else if (semantics::IsAssumedSizeArray(symbol) &&
dimension + 1 == symbol.Rank()) { dimension + 1 == symbol.Rank()) {
return std::nullopt; // UBOUND() folding replaces with -1 return std::nullopt; // UBOUND() folding replaces with -1
} else if (auto lb{GetLBOUND(base, dimension, invariantOnly)}) { } else if (IsSafelyCopyable(base, /*admitPureCall=*/true)) {
if (auto lb{GetLBOUND(base, dimension, invariantOnly)}) {
return ComputeUpperBound( return ComputeUpperBound(
std::move(*lb), GetExtent(base, dimension, invariantOnly)); std::move(*lb), GetExtent(base, dimension, invariantOnly));
} }
} }
}
} else if (const auto *assoc{ } else if (const auto *assoc{
symbol.detailsIf<semantics::AssocEntityDetails>()}) { symbol.detailsIf<semantics::AssocEntityDetails>()}) {
if (assoc->IsAssumedSize() || assoc->IsAssumedRank()) { if (assoc->IsAssumedSize() || assoc->IsAssumedRank()) {

View File

@ -0,0 +1,18 @@
! RUN: %flang_fc1 -fdebug-unparse %s 2>&1 | FileCheck %s
! Ensure that UBOUND() calculation from LBOUND()+SIZE() isn't applied to
! variables containing references to impure functions.
type t
real, allocatable :: a(:)
end type
interface
pure integer function pure(n)
integer, intent(in) :: n
end
end interface
type(t) :: x(10)
allocate(x(1)%a(2))
!CHECK: PRINT *, ubound(x(int(impure(1_4),kind=8))%a,dim=1_4)
print *, ubound(x(impure(1))%a, dim=1)
!CHECK: PRINT *, int(size(x(int(pure(1_4),kind=8))%a,dim=1,kind=8)+lbound(x(int(pure(1_4),kind=8))%a,dim=1,kind=8)-1_8,kind=4)
print *, ubound(x(pure(1))%a, dim=1)
end