Skip to content

Commit 4b6a4aa

Browse files
[flang] Consolidate copy-in/copy-out determination in evaluate framework (#151408)
New implementation of `MayNeedCopy()` is used to consolidate copy-in/copy-out checks. `IsAssumedShape()` and `IsAssumedRank()` were simplified and are both now in `Fortran::semantics` workspace. `preparePresentUserCallActualArgument()` in lowering was modified to use `MayNeedCopyInOut()` Fixes #138471
1 parent 4b84223 commit 4b6a4aa

24 files changed

+381
-109
lines changed

flang/include/flang/Evaluate/characteristics.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,12 @@ class TypeAndShape {
203203
std::optional<Expr<SubscriptInteger>> MeasureSizeInBytes(
204204
FoldingContext &) const;
205205

206+
bool IsExplicitShape() const {
207+
// If it's array and no special attributes are set, then must be
208+
// explicit shape.
209+
return Rank() > 0 && attrs_.none();
210+
}
211+
206212
// called by Fold() to rewrite in place
207213
TypeAndShape &Rewrite(FoldingContext &);
208214

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ std::optional<bool> IsContiguous(const A &, FoldingContext &,
125125
extern template std::optional<bool> IsContiguous(const Expr<SomeType> &,
126126
FoldingContext &, bool namedConstantSectionsAreContiguous,
127127
bool firstDimensionStride1);
128+
extern template std::optional<bool> IsContiguous(const ActualArgument &,
129+
FoldingContext &, bool namedConstantSectionsAreContiguous,
130+
bool firstDimensionStride1);
128131
extern template std::optional<bool> IsContiguous(const ArrayRef &,
129132
FoldingContext &, bool namedConstantSectionsAreContiguous,
130133
bool firstDimensionStride1);
@@ -160,5 +163,8 @@ extern template bool IsErrorExpr(const Expr<SomeType> &);
160163
std::optional<parser::Message> CheckStatementFunction(
161164
const Symbol &, const Expr<SomeType> &, FoldingContext &);
162165

166+
bool MayNeedCopy(const ActualArgument *, const characteristics::DummyArgument *,
167+
FoldingContext &, bool forCopyOut);
168+
163169
} // namespace Fortran::evaluate
164170
#endif

flang/include/flang/Evaluate/tools.h

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -82,27 +82,6 @@ template <typename A> bool IsVariable(const A &x) {
8282
}
8383
}
8484

85-
// Predicate: true when an expression is assumed-rank
86-
bool IsAssumedRank(const Symbol &);
87-
bool IsAssumedRank(const ActualArgument &);
88-
template <typename A> bool IsAssumedRank(const A &) { return false; }
89-
template <typename A> bool IsAssumedRank(const Designator<A> &designator) {
90-
if (const auto *symbol{std::get_if<SymbolRef>(&designator.u)}) {
91-
return IsAssumedRank(symbol->get());
92-
} else {
93-
return false;
94-
}
95-
}
96-
template <typename T> bool IsAssumedRank(const Expr<T> &expr) {
97-
return common::visit([](const auto &x) { return IsAssumedRank(x); }, expr.u);
98-
}
99-
template <typename A> bool IsAssumedRank(const std::optional<A> &x) {
100-
return x && IsAssumedRank(*x);
101-
}
102-
template <typename A> bool IsAssumedRank(const A *x) {
103-
return x && IsAssumedRank(*x);
104-
}
105-
10685
// Finds the corank of an entity, possibly packaged in various ways.
10786
// Unlike rank, only data references have corank > 0.
10887
int GetCorank(const ActualArgument &);
@@ -1123,6 +1102,7 @@ extern template semantics::UnorderedSymbolSet CollectCudaSymbols(
11231102

11241103
// Predicate: does a variable contain a vector-valued subscript (not a triplet)?
11251104
bool HasVectorSubscript(const Expr<SomeType> &);
1105+
bool HasVectorSubscript(const ActualArgument &);
11261106

11271107
// Predicate: does an expression contain constant?
11281108
bool HasConstant(const Expr<SomeType> &);
@@ -1555,7 +1535,19 @@ bool IsAllocatableOrObjectPointer(const Symbol *);
15551535
bool IsAutomatic(const Symbol &);
15561536
bool IsSaved(const Symbol &); // saved implicitly or explicitly
15571537
bool IsDummy(const Symbol &);
1538+
1539+
bool IsAssumedRank(const Symbol &);
1540+
template <typename A> bool IsAssumedRank(const A &x) {
1541+
auto *symbol{UnwrapWholeSymbolDataRef(x)};
1542+
return symbol && IsAssumedRank(*symbol);
1543+
}
1544+
15581545
bool IsAssumedShape(const Symbol &);
1546+
template <typename A> bool IsAssumedShape(const A &x) {
1547+
auto *symbol{UnwrapWholeSymbolDataRef(x)};
1548+
return symbol && IsAssumedShape(*symbol);
1549+
}
1550+
15591551
bool IsDeferredShape(const Symbol &);
15601552
bool IsFunctionResult(const Symbol &);
15611553
bool IsKindTypeParameter(const Symbol &);

flang/lib/Evaluate/check-expression.cpp

Lines changed: 187 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,8 +1001,8 @@ class IsContiguousHelper
10011001
} else {
10021002
return Base::operator()(ultimate); // use expr
10031003
}
1004-
} else if (semantics::IsPointer(ultimate) ||
1005-
semantics::IsAssumedShape(ultimate) || IsAssumedRank(ultimate)) {
1004+
} else if (semantics::IsPointer(ultimate) || IsAssumedShape(ultimate) ||
1005+
IsAssumedRank(ultimate)) {
10061006
return std::nullopt;
10071007
} else if (ultimate.has<semantics::ObjectEntityDetails>()) {
10081008
return true;
@@ -1282,9 +1282,21 @@ std::optional<bool> IsContiguous(const A &x, FoldingContext &context,
12821282
}
12831283
}
12841284

1285+
std::optional<bool> IsContiguous(const ActualArgument &actual,
1286+
FoldingContext &fc, bool namedConstantSectionsAreContiguous,
1287+
bool firstDimensionStride1) {
1288+
auto *expr{actual.UnwrapExpr()};
1289+
return expr &&
1290+
IsContiguous(
1291+
*expr, fc, namedConstantSectionsAreContiguous, firstDimensionStride1);
1292+
}
1293+
12851294
template std::optional<bool> IsContiguous(const Expr<SomeType> &,
12861295
FoldingContext &, bool namedConstantSectionsAreContiguous,
12871296
bool firstDimensionStride1);
1297+
template std::optional<bool> IsContiguous(const ActualArgument &,
1298+
FoldingContext &, bool namedConstantSectionsAreContiguous,
1299+
bool firstDimensionStride1);
12881300
template std::optional<bool> IsContiguous(const ArrayRef &, FoldingContext &,
12891301
bool namedConstantSectionsAreContiguous, bool firstDimensionStride1);
12901302
template std::optional<bool> IsContiguous(const Substring &, FoldingContext &,
@@ -1434,4 +1446,177 @@ std::optional<parser::Message> CheckStatementFunction(
14341446
return StmtFunctionChecker{sf, context}(expr);
14351447
}
14361448

1449+
// Helper class for checking differences between actual and dummy arguments
1450+
class CopyInOutExplicitInterface {
1451+
public:
1452+
explicit CopyInOutExplicitInterface(FoldingContext &fc,
1453+
const ActualArgument &actual,
1454+
const characteristics::DummyDataObject &dummyObj)
1455+
: fc_{fc}, actual_{actual}, dummyObj_{dummyObj} {}
1456+
1457+
// Returns true, if actual and dummy have different contiguity requirements
1458+
bool HaveContiguityDifferences() const {
1459+
// Check actual contiguity, unless dummy doesn't care
1460+
bool dummyTreatAsArray{dummyObj_.ignoreTKR.test(common::IgnoreTKR::Rank)};
1461+
bool actualTreatAsContiguous{
1462+
dummyObj_.ignoreTKR.test(common::IgnoreTKR::Contiguous) ||
1463+
IsSimplyContiguous(actual_, fc_)};
1464+
bool dummyIsExplicitShape{dummyObj_.type.IsExplicitShape()};
1465+
bool dummyIsAssumedSize{dummyObj_.type.attrs().test(
1466+
characteristics::TypeAndShape::Attr::AssumedSize)};
1467+
bool dummyIsPolymorphic{dummyObj_.type.type().IsPolymorphic()};
1468+
// type(*) with IGNORE_TKR(tkr) is often used to interface with C "void*".
1469+
// Since the other languages don't know about Fortran's discontiguity
1470+
// handling, such cases should require contiguity.
1471+
bool dummyIsVoidStar{dummyObj_.type.type().IsAssumedType() &&
1472+
dummyObj_.ignoreTKR.test(common::IgnoreTKR::Type) &&
1473+
dummyObj_.ignoreTKR.test(common::IgnoreTKR::Rank) &&
1474+
dummyObj_.ignoreTKR.test(common::IgnoreTKR::Kind)};
1475+
// Explicit shape and assumed size arrays must be contiguous
1476+
bool dummyNeedsContiguity{dummyIsExplicitShape || dummyIsAssumedSize ||
1477+
(dummyTreatAsArray && !dummyIsPolymorphic) || dummyIsVoidStar ||
1478+
dummyObj_.attrs.test(
1479+
characteristics::DummyDataObject::Attr::Contiguous)};
1480+
return !actualTreatAsContiguous && dummyNeedsContiguity;
1481+
}
1482+
1483+
// Returns true, if actual and dummy have polymorphic differences
1484+
bool HavePolymorphicDifferences() const {
1485+
bool dummyIsAssumedRank{dummyObj_.type.attrs().test(
1486+
characteristics::TypeAndShape::Attr::AssumedRank)};
1487+
bool actualIsAssumedRank{semantics::IsAssumedRank(actual_)};
1488+
bool dummyIsAssumedShape{dummyObj_.type.attrs().test(
1489+
characteristics::TypeAndShape::Attr::AssumedShape)};
1490+
bool actualIsAssumedShape{semantics::IsAssumedShape(actual_)};
1491+
if ((actualIsAssumedRank && dummyIsAssumedRank) ||
1492+
(actualIsAssumedShape && dummyIsAssumedShape)) {
1493+
// Assumed-rank and assumed-shape arrays are represented by descriptors,
1494+
// so don't need to do polymorphic check.
1495+
} else if (!dummyObj_.ignoreTKR.test(common::IgnoreTKR::Type)) {
1496+
// flang supports limited cases of passing polymorphic to non-polimorphic.
1497+
// These cases require temporary of non-polymorphic type. (For example,
1498+
// the actual argument could be polymorphic array of child type,
1499+
// while the dummy argument could be non-polymorphic array of parent
1500+
// type.)
1501+
bool dummyIsPolymorphic{dummyObj_.type.type().IsPolymorphic()};
1502+
auto actualType{
1503+
characteristics::TypeAndShape::Characterize(actual_, fc_)};
1504+
bool actualIsPolymorphic{
1505+
actualType && actualType->type().IsPolymorphic()};
1506+
if (actualIsPolymorphic && !dummyIsPolymorphic) {
1507+
return true;
1508+
}
1509+
}
1510+
return false;
1511+
}
1512+
1513+
bool HaveArrayOrAssumedRankArgs() const {
1514+
bool dummyTreatAsArray{dummyObj_.ignoreTKR.test(common::IgnoreTKR::Rank)};
1515+
return IsArrayOrAssumedRank(actual_) &&
1516+
(IsArrayOrAssumedRank(dummyObj_) || dummyTreatAsArray);
1517+
}
1518+
1519+
bool PassByValue() const {
1520+
return dummyObj_.attrs.test(characteristics::DummyDataObject::Attr::Value);
1521+
}
1522+
1523+
bool HaveCoarrayDifferences() const {
1524+
return ExtractCoarrayRef(actual_) && dummyObj_.type.corank() == 0;
1525+
}
1526+
1527+
bool HasIntentOut() const { return dummyObj_.intent == common::Intent::Out; }
1528+
1529+
bool HasIntentIn() const { return dummyObj_.intent == common::Intent::In; }
1530+
1531+
static bool IsArrayOrAssumedRank(const ActualArgument &actual) {
1532+
return semantics::IsAssumedRank(actual) || actual.Rank() > 0;
1533+
}
1534+
1535+
static bool IsArrayOrAssumedRank(
1536+
const characteristics::DummyDataObject &dummy) {
1537+
return dummy.type.attrs().test(
1538+
characteristics::TypeAndShape::Attr::AssumedRank) ||
1539+
dummy.type.Rank() > 0;
1540+
}
1541+
1542+
private:
1543+
FoldingContext &fc_;
1544+
const ActualArgument &actual_;
1545+
const characteristics::DummyDataObject &dummyObj_;
1546+
};
1547+
1548+
// If forCopyOut is false, returns if a particular actual/dummy argument
1549+
// combination may need a temporary creation with copy-in operation. If
1550+
// forCopyOut is true, returns the same for copy-out operation. For
1551+
// procedures with explicit interface, it's expected that "dummy" is not null.
1552+
// For procedures with implicit interface dummy may be null.
1553+
//
1554+
// Note that these copy-in and copy-out checks are done from the caller's
1555+
// perspective, meaning that for copy-in the caller need to do the copy
1556+
// before calling the callee. Similarly, for copy-out the caller is expected
1557+
// to do the copy after the callee returns.
1558+
bool MayNeedCopy(const ActualArgument *actual,
1559+
const characteristics::DummyArgument *dummy, FoldingContext &fc,
1560+
bool forCopyOut) {
1561+
if (!actual) {
1562+
return false;
1563+
}
1564+
if (actual->isAlternateReturn()) {
1565+
return false;
1566+
}
1567+
const auto *dummyObj{dummy
1568+
? std::get_if<characteristics::DummyDataObject>(&dummy->u)
1569+
: nullptr};
1570+
const bool forCopyIn = !forCopyOut;
1571+
if (!evaluate::IsVariable(*actual)) {
1572+
// Actual argument expressions that aren’t variables are copy-in, but
1573+
// not copy-out.
1574+
return forCopyIn;
1575+
}
1576+
if (dummyObj) { // Explict interface
1577+
CopyInOutExplicitInterface check{fc, *actual, *dummyObj};
1578+
if (forCopyOut && check.HasIntentIn()) {
1579+
// INTENT(IN) dummy args never need copy-out
1580+
return false;
1581+
}
1582+
if (forCopyIn && check.HasIntentOut()) {
1583+
// INTENT(OUT) dummy args never need copy-in
1584+
return false;
1585+
}
1586+
if (check.PassByValue()) {
1587+
// Pass by value, always copy-in, never copy-out
1588+
return forCopyIn;
1589+
}
1590+
if (check.HaveCoarrayDifferences()) {
1591+
return true;
1592+
}
1593+
// Note: contiguity and polymorphic checks deal with array or assumed rank
1594+
// arguments
1595+
if (!check.HaveArrayOrAssumedRankArgs()) {
1596+
return false;
1597+
}
1598+
if (check.HaveContiguityDifferences()) {
1599+
return true;
1600+
}
1601+
if (check.HavePolymorphicDifferences()) {
1602+
return true;
1603+
}
1604+
} else { // Implicit interface
1605+
if (ExtractCoarrayRef(*actual)) {
1606+
// Coindexed actual args may need copy-in and copy-out with implicit
1607+
// interface
1608+
return true;
1609+
}
1610+
if (!IsSimplyContiguous(*actual, fc)) {
1611+
// Copy-in: actual arguments that are variables are copy-in when
1612+
// non-contiguous.
1613+
// Copy-out: vector subscripts could refer to duplicate elements, can't
1614+
// copy out.
1615+
return !(forCopyOut && HasVectorSubscript(*actual));
1616+
}
1617+
}
1618+
// For everything else, no copy-in or copy-out
1619+
return false;
1620+
}
1621+
14371622
} // namespace Fortran::evaluate

flang/lib/Evaluate/fold-integer.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ static bool CheckDimArg(const std::optional<ActualArgument> &dimArg,
3838
const Expr<SomeType> &array, parser::ContextualMessages &messages,
3939
bool isLBound, std::optional<int> &dimVal) {
4040
dimVal.reset();
41-
if (int rank{array.Rank()}; rank > 0 || IsAssumedRank(array)) {
41+
if (int rank{array.Rank()}; rank > 0 || semantics::IsAssumedRank(array)) {
4242
auto named{ExtractNamedEntity(array)};
4343
if (auto dim64{ToInt64(dimArg)}) {
4444
if (*dim64 < 1) {
4545
messages.Say("DIM=%jd dimension must be positive"_err_en_US, *dim64);
4646
return false;
47-
} else if (!IsAssumedRank(array) && *dim64 > rank) {
47+
} else if (!semantics::IsAssumedRank(array) && *dim64 > rank) {
4848
messages.Say(
4949
"DIM=%jd dimension is out of range for rank-%d array"_err_en_US,
5050
*dim64, rank);
@@ -56,7 +56,7 @@ static bool CheckDimArg(const std::optional<ActualArgument> &dimArg,
5656
"DIM=%jd dimension is out of range for rank-%d assumed-size array"_err_en_US,
5757
*dim64, rank);
5858
return false;
59-
} else if (IsAssumedRank(array)) {
59+
} else if (semantics::IsAssumedRank(array)) {
6060
if (*dim64 > common::maxRank) {
6161
messages.Say(
6262
"DIM=%jd dimension is too large for any array (maximum rank %d)"_err_en_US,
@@ -189,7 +189,7 @@ Expr<Type<TypeCategory::Integer, KIND>> LBOUND(FoldingContext &context,
189189
return Expr<T>{std::move(funcRef)};
190190
}
191191
}
192-
if (IsAssumedRank(*array)) {
192+
if (semantics::IsAssumedRank(*array)) {
193193
// Would like to return 1 if DIM=.. is present, but that would be
194194
// hiding a runtime error if the DIM= were too large (including
195195
// the case of an assumed-rank argument that's scalar).
@@ -240,7 +240,7 @@ Expr<Type<TypeCategory::Integer, KIND>> UBOUND(FoldingContext &context,
240240
return Expr<T>{std::move(funcRef)};
241241
}
242242
}
243-
if (IsAssumedRank(*array)) {
243+
if (semantics::IsAssumedRank(*array)) {
244244
} else if (int rank{array->Rank()}; rank > 0) {
245245
bool takeBoundsFromShape{true};
246246
if (auto named{ExtractNamedEntity(*array)}) {

flang/lib/Evaluate/intrinsics.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2260,7 +2260,7 @@ std::optional<SpecificCall> IntrinsicInterface::Match(
22602260
for (std::size_t j{0}; j < dummies; ++j) {
22612261
const IntrinsicDummyArgument &d{dummy[std::min(j, dummyArgPatterns - 1)]};
22622262
if (const ActualArgument *arg{actualForDummy[j]}) {
2263-
bool isAssumedRank{IsAssumedRank(*arg)};
2263+
bool isAssumedRank{semantics::IsAssumedRank(*arg)};
22642264
if (isAssumedRank && d.rank != Rank::anyOrAssumedRank &&
22652265
d.rank != Rank::arrayOrAssumedRank) {
22662266
messages.Say(arg->sourceLocation(),
@@ -3006,7 +3006,7 @@ SpecificCall IntrinsicProcTable::Implementation::HandleNull(
30063006
mold = nullptr;
30073007
}
30083008
if (mold) {
3009-
if (IsAssumedRank(*arguments[0])) {
3009+
if (semantics::IsAssumedRank(*arguments[0])) {
30103010
context.messages().Say(arguments[0]->sourceLocation(),
30113011
"MOLD= argument to NULL() must not be assumed-rank"_err_en_US);
30123012
}

flang/lib/Evaluate/shape.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -949,7 +949,7 @@ auto GetShapeHelper::operator()(const ProcedureRef &call) const -> Result {
949949
intrinsic->name == "ubound") {
950950
// For LBOUND/UBOUND, these are the array-valued cases (no DIM=)
951951
if (!call.arguments().empty() && call.arguments().front()) {
952-
if (IsAssumedRank(*call.arguments().front())) {
952+
if (semantics::IsAssumedRank(*call.arguments().front())) {
953953
return Shape{MaybeExtentExpr{}};
954954
} else {
955955
return Shape{

0 commit comments

Comments
 (0)