Skip to content

Commit 12a7ef3

Browse files
committed
[flang] Further work on NULL(MOLD=allocatable)
Refine handling of NULL(...) in semantics to properly distinguish NULL(), NULL(objectPointer), NULL(procPointer), and NULL(allocatable) from each other in relevant contexts. Add IsNullAllocatable() and IsNullPointerOrAllocatable() utility functions. IsNullAllocatable() is true only for NULL(allocatable); it is false for a bare NULL(), which can be detected independently with IsBareNullPointer(). IsNullPointer() now returns false for NULL(allocatable). ALLOCATED(NULL(allocatable)) now works, and folds to .FALSE. These utilities were modified to accept const pointer arguments rather than const references; I usually prefer this style when the result should clearly be false for a null argument (in the C sense), and it helped me find all of their use sites in the code.
1 parent 51dc526 commit 12a7ef3

File tree

20 files changed

+155
-104
lines changed

20 files changed

+155
-104
lines changed

flang/include/flang/Evaluate/characteristics.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,8 @@ struct FunctionResult {
349349

350350
// 15.3.1
351351
struct Procedure {
352-
ENUM_CLASS(
353-
Attr, Pure, Elemental, BindC, ImplicitInterface, NullPointer, Subroutine)
352+
ENUM_CLASS(Attr, Pure, Elemental, BindC, ImplicitInterface, NullPointer,
353+
NullAllocatable, Subroutine)
354354
using Attrs = common::EnumSet<Attr, Attr_enumSize>;
355355
Procedure(){};
356356
Procedure(FunctionResult &&, DummyArguments &&, Attrs);

flang/include/flang/Evaluate/tools.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,9 +1059,11 @@ bool IsProcedurePointer(const Expr<SomeType> &);
10591059
bool IsProcedure(const Expr<SomeType> &);
10601060
bool IsProcedurePointerTarget(const Expr<SomeType> &);
10611061
bool IsBareNullPointer(const Expr<SomeType> *); // NULL() w/o MOLD= or type
1062-
bool IsNullObjectPointer(const Expr<SomeType> &);
1063-
bool IsNullProcedurePointer(const Expr<SomeType> &);
1064-
bool IsNullPointer(const Expr<SomeType> &);
1062+
bool IsNullObjectPointer(const Expr<SomeType> *); // NULL() or NULL(objptr)
1063+
bool IsNullProcedurePointer(const Expr<SomeType> *); // NULL() or NULL(procptr)
1064+
bool IsNullPointer(const Expr<SomeType> *); // NULL() or NULL(pointer)
1065+
bool IsNullAllocatable(const Expr<SomeType> *); // NULL(allocatable)
1066+
bool IsNullPointerOrAllocatable(const Expr<SomeType> *); // NULL of any form
10651067
bool IsObjectPointer(const Expr<SomeType> &);
10661068

10671069
// Can Expr be passed as absent to an optional dummy argument.

flang/lib/Evaluate/check-expression.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ template <bool INVARIANT>
100100
bool IsConstantExprHelper<INVARIANT>::IsConstantStructureConstructorComponent(
101101
const Symbol &component, const Expr<SomeType> &expr) const {
102102
if (IsAllocatable(component)) {
103-
return IsNullObjectPointer(expr);
103+
return IsNullObjectPointer(&expr);
104104
} else if (IsPointer(component)) {
105-
return IsNullPointer(expr) || IsInitialDataTarget(expr) ||
105+
return IsNullPointerOrAllocatable(&expr) || IsInitialDataTarget(expr) ||
106106
IsInitialProcedureTarget(expr);
107107
} else {
108108
return (*this)(expr);
@@ -194,7 +194,7 @@ struct IsActuallyConstantHelper {
194194
const bool compIsConstant{(*this)(y)};
195195
// If an allocatable component is initialized by a constant,
196196
// the structure constructor is not a constant.
197-
if ((!compIsConstant && !IsNullPointer(y)) ||
197+
if ((!compIsConstant && !IsNullPointerOrAllocatable(&y)) ||
198198
(compIsConstant && IsAllocatable(sym))) {
199199
return false;
200200
}
@@ -311,7 +311,9 @@ class IsInitialDataTargetHelper
311311
bool operator()(const ProcedureRef &x) const {
312312
if (const SpecificIntrinsic * intrinsic{x.proc().GetSpecificIntrinsic()}) {
313313
return intrinsic->characteristics.value().attrs.test(
314-
characteristics::Procedure::Attr::NullPointer);
314+
characteristics::Procedure::Attr::NullPointer) ||
315+
intrinsic->characteristics.value().attrs.test(
316+
characteristics::Procedure::Attr::NullAllocatable);
315317
}
316318
return false;
317319
}
@@ -388,7 +390,7 @@ bool IsInitialProcedureTarget(const Expr<SomeType> &expr) {
388390
if (const auto *proc{std::get_if<ProcedureDesignator>(&expr.u)}) {
389391
return IsInitialProcedureTarget(*proc);
390392
} else {
391-
return IsNullProcedurePointer(expr);
393+
return IsNullProcedurePointer(&expr);
392394
}
393395
}
394396

flang/lib/Evaluate/fold-logical.cpp

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -652,21 +652,18 @@ Expr<Type<TypeCategory::Logical, KIND>> FoldIntrinsicFunction(
652652
if (name == "all") {
653653
return FoldAllAnyParity(
654654
context, std::move(funcRef), &Scalar<T>::AND, Scalar<T>{true});
655+
} else if (name == "allocated") {
656+
if (IsNullAllocatable(args[0]->UnwrapExpr())) {
657+
return Expr<T>{false};
658+
}
655659
} else if (name == "any") {
656660
return FoldAllAnyParity(
657661
context, std::move(funcRef), &Scalar<T>::OR, Scalar<T>{false});
658662
} else if (name == "associated") {
659-
bool gotConstant{true};
660-
const Expr<SomeType> *firstArgExpr{args[0]->UnwrapExpr()};
661-
if (!firstArgExpr || !IsNullPointer(*firstArgExpr)) {
662-
gotConstant = false;
663-
} else if (args[1]) { // There's a second argument
664-
const Expr<SomeType> *secondArgExpr{args[1]->UnwrapExpr()};
665-
if (!secondArgExpr || !IsNullPointer(*secondArgExpr)) {
666-
gotConstant = false;
667-
}
663+
if (IsNullPointer(args[0]->UnwrapExpr()) ||
664+
(args[1] && IsNullPointer(args[1]->UnwrapExpr()))) {
665+
return Expr<T>{false};
668666
}
669-
return gotConstant ? Expr<T>{false} : Expr<T>{std::move(funcRef)};
670667
} else if (name == "bge" || name == "bgt" || name == "ble" || name == "blt") {
671668
static_assert(std::is_same_v<Scalar<LargestInt>, BOZLiteralConstant>);
672669

flang/lib/Evaluate/fold.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ Expr<SomeDerived> FoldOperation(
7373
for (auto &&[symbol, value] : std::move(structure)) {
7474
auto expr{Fold(context, std::move(value.value()))};
7575
if (IsPointer(symbol)) {
76-
if (IsNullPointer(expr)) {
76+
if (IsNullPointer(&expr)) {
7777
// Handle x%c when x designates a named constant of derived
7878
// type and %c is NULL() in that constant.
7979
expr = Expr<SomeType>{NullPointer{}};
@@ -86,9 +86,10 @@ Expr<SomeDerived> FoldOperation(
8686
// F2023: 10.1.12 (3)(a)
8787
// If comp-spec is not null() for the allocatable component the
8888
// structure constructor is not a constant expression.
89-
isConstant &= IsNullPointer(expr);
89+
isConstant &= IsNullAllocatable(&expr) || IsBareNullPointer(&expr);
9090
} else {
91-
isConstant &= IsActuallyConstant(expr) || IsNullPointer(expr);
91+
isConstant &=
92+
IsActuallyConstant(expr) || IsNullPointerOrAllocatable(&expr);
9293
if (auto valueShape{GetConstantExtents(context, expr)}) {
9394
if (auto componentShape{GetConstantExtents(context, symbol)}) {
9495
if (GetRank(*componentShape) > 0 && GetRank(*valueShape) == 0) {

flang/lib/Evaluate/intrinsics.cpp

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,10 @@ ENUM_CLASS(Optionality, required,
247247
)
248248

249249
ENUM_CLASS(ArgFlag, none,
250-
canBeNull, // actual argument can be NULL(with or without MOLD=)
251-
canBeMoldNull, // actual argument can be NULL(with MOLD=)
250+
canBeNullPointer, // actual argument can be NULL(with or without
251+
// MOLD=pointer)
252+
canBeMoldNull, // actual argument can be NULL(MOLD=any)
253+
canBeNullAllocatable, // actual argument can be NULL(MOLD=allocatable)
252254
defaultsToSameKind, // for MatchingDefaultKIND
253255
defaultsToSizeKind, // for SizeDefaultKIND
254256
defaultsToDefaultForResult, // for DefaultingKIND
@@ -343,8 +345,10 @@ static const IntrinsicInterface genericIntrinsicFunction[]{
343345
Rank::dimReduced, IntrinsicClass::transformationalFunction},
344346
{"allocated", {{"scalar", AnyData, Rank::scalar}}, DefaultLogical,
345347
Rank::elemental, IntrinsicClass::inquiryFunction},
346-
{"allocated", {{"array", AnyData, Rank::anyOrAssumedRank}}, DefaultLogical,
347-
Rank::elemental, IntrinsicClass::inquiryFunction},
348+
{"allocated",
349+
{{"array", AnyData, Rank::anyOrAssumedRank, Optionality::required,
350+
common::Intent::In, {ArgFlag::canBeNullAllocatable}}},
351+
DefaultLogical, Rank::elemental, IntrinsicClass::inquiryFunction},
348352
{"anint", {{"a", SameReal}, MatchingDefaultKIND}, KINDReal},
349353
{"any", {{"mask", SameLogical, Rank::array}, OptionalDIM}, SameLogical,
350354
Rank::dimReduced, IntrinsicClass::transformationalFunction},
@@ -353,10 +357,10 @@ static const IntrinsicInterface genericIntrinsicFunction[]{
353357
{"asinh", {{"x", SameFloating}}, SameFloating},
354358
{"associated",
355359
{{"pointer", AnyPointer, Rank::anyOrAssumedRank, Optionality::required,
356-
common::Intent::In, {ArgFlag::canBeNull}},
360+
common::Intent::In, {ArgFlag::canBeNullPointer}},
357361
{"target", Addressable, Rank::anyOrAssumedRank,
358362
Optionality::optional, common::Intent::In,
359-
{ArgFlag::canBeNull}}},
363+
{ArgFlag::canBeNullPointer}}},
360364
DefaultLogical, Rank::elemental, IntrinsicClass::inquiryFunction},
361365
{"atan", {{"x", SameFloating}}, SameFloating},
362366
{"atan", {{"y", OperandReal}, {"x", OperandReal}}, OperandReal},
@@ -1883,9 +1887,9 @@ std::optional<SpecificCall> IntrinsicInterface::Match(
18831887
d.keyword);
18841888
return std::nullopt;
18851889
}
1886-
if (!d.flags.test(ArgFlag::canBeNull)) {
1887-
if (const auto *expr{arg->UnwrapExpr()}; expr && IsNullPointer(*expr)) {
1888-
if (!IsBareNullPointer(expr) && IsNullObjectPointer(*expr) &&
1890+
if (!d.flags.test(ArgFlag::canBeNullPointer)) {
1891+
if (const auto *expr{arg->UnwrapExpr()}; IsNullPointer(expr)) {
1892+
if (!IsBareNullPointer(expr) && IsNullObjectPointer(expr) &&
18891893
d.flags.test(ArgFlag::canBeMoldNull)) {
18901894
// ok
18911895
} else {
@@ -1896,6 +1900,13 @@ std::optional<SpecificCall> IntrinsicInterface::Match(
18961900
}
18971901
}
18981902
}
1903+
if (!d.flags.test(ArgFlag::canBeNullAllocatable) &&
1904+
IsNullAllocatable(arg->UnwrapExpr())) {
1905+
messages.Say(arg->sourceLocation(),
1906+
"A NULL() allocatable is not allowed for '%s=' intrinsic argument"_err_en_US,
1907+
d.keyword);
1908+
return std::nullopt;
1909+
}
18991910
if (d.flags.test(ArgFlag::notAssumedSize)) {
19001911
if (auto named{ExtractNamedEntity(*arg)}) {
19011912
if (semantics::IsAssumedSizeArray(named->GetLastSymbol())) {
@@ -2853,14 +2864,15 @@ SpecificCall IntrinsicProcTable::Implementation::HandleNull(
28532864
"MOLD= argument to NULL() must not be assumed-rank"_err_en_US);
28542865
}
28552866
bool isProcPtrTarget{
2856-
IsProcedurePointerTarget(*mold) && !IsNullObjectPointer(*mold)};
2867+
IsProcedurePointerTarget(*mold) && !IsNullObjectPointer(mold)};
28572868
if (isProcPtrTarget || IsAllocatableOrPointerObject(*mold)) {
28582869
characteristics::DummyArguments args;
28592870
std::optional<characteristics::FunctionResult> fResult;
2871+
bool isAllocatableMold{false};
28602872
if (isProcPtrTarget) {
28612873
// MOLD= procedure pointer
28622874
std::optional<characteristics::Procedure> procPointer;
2863-
if (IsNullProcedurePointer(*mold)) {
2875+
if (IsNullProcedurePointer(mold)) {
28642876
procPointer =
28652877
characteristics::Procedure::Characterize(*mold, context);
28662878
} else {
@@ -2876,20 +2888,23 @@ SpecificCall IntrinsicProcTable::Implementation::HandleNull(
28762888
fResult.emplace(std::move(*procPointer));
28772889
}
28782890
} else if (auto type{mold->GetType()}) {
2879-
// MOLD= object pointer
2891+
// MOLD= object pointer or allocatable
28802892
characteristics::TypeAndShape typeAndShape{
28812893
*type, GetShape(context, *mold)};
28822894
args.emplace_back(
28832895
"mold"s, characteristics::DummyDataObject{typeAndShape});
28842896
fResult.emplace(std::move(typeAndShape));
2897+
isAllocatableMold = IsAllocatableDesignator(*mold);
28852898
} else {
28862899
context.messages().Say(arguments[0]->sourceLocation(),
28872900
"MOLD= argument to NULL() lacks type"_err_en_US);
28882901
}
28892902
if (fResult) {
28902903
fResult->attrs.set(characteristics::FunctionResult::Attr::Pointer);
28912904
characteristics::Procedure::Attrs attrs;
2892-
attrs.set(characteristics::Procedure::Attr::NullPointer);
2905+
attrs.set(isAllocatableMold
2906+
? characteristics::Procedure::Attr::NullAllocatable
2907+
: characteristics::Procedure::Attr::NullPointer);
28932908
characteristics::Procedure chars{
28942909
std::move(*fResult), std::move(args), attrs};
28952910
return SpecificCall{SpecificIntrinsic{"null"s, std::move(chars)},
@@ -3248,7 +3263,7 @@ static bool ApplySpecificChecks(SpecificCall &call, FoldingContext &context) {
32483263
const auto &arg{call.arguments[0]};
32493264
if (arg) {
32503265
if (const auto *expr{arg->UnwrapExpr()}) {
3251-
ok = evaluate::IsAllocatableDesignator(*expr);
3266+
ok = IsAllocatableDesignator(*expr) || IsNullAllocatable(expr);
32523267
}
32533268
}
32543269
if (!ok) {

flang/lib/Evaluate/shape.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,8 +1173,10 @@ auto GetShapeHelper::operator()(const ProcedureRef &call) const -> Result {
11731173
if (call.arguments().size() >= 2) {
11741174
return (*this)(call.arguments()[1]); // MASK=
11751175
}
1176-
} else if (intrinsic->characteristics.value().attrs.test(characteristics::
1177-
Procedure::Attr::NullPointer)) { // NULL(MOLD=)
1176+
} else if (intrinsic->characteristics.value().attrs.test(
1177+
characteristics::Procedure::Attr::NullPointer) ||
1178+
intrinsic->characteristics.value().attrs.test(
1179+
characteristics::Procedure::Attr::NullAllocatable)) { // NULL(MOLD=)
11781180
return (*this)(call.arguments());
11791181
} else {
11801182
// TODO: shapes of other non-elemental intrinsic results

flang/lib/Evaluate/tools.cpp

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -929,7 +929,7 @@ bool IsPointer(const Expr<SomeType> &expr) {
929929
}
930930

931931
bool IsProcedurePointer(const Expr<SomeType> &expr) {
932-
if (IsNullProcedurePointer(expr)) {
932+
if (IsNullProcedurePointer(&expr)) {
933933
return true;
934934
} else if (const auto *funcRef{UnwrapProcedureRef(expr)}) {
935935
if (const Symbol * proc{funcRef->proc().GetSymbol()}) {
@@ -963,7 +963,7 @@ bool IsProcedurePointerTarget(const Expr<SomeType> &expr) {
963963
}
964964

965965
bool IsObjectPointer(const Expr<SomeType> &expr) {
966-
if (IsNullObjectPointer(expr)) {
966+
if (IsNullObjectPointer(&expr)) {
967967
return true;
968968
} else if (IsProcedurePointerTarget(expr)) {
969969
return false;
@@ -1030,22 +1030,46 @@ template <bool IS_PROC_PTR> struct IsNullPointerHelper {
10301030
}
10311031
};
10321032

1033-
bool IsNullObjectPointer(const Expr<SomeType> &expr) {
1034-
return IsNullPointerHelper<false>{}(expr);
1033+
bool IsNullObjectPointer(const Expr<SomeType> *expr) {
1034+
return expr && IsNullPointerHelper<false>{}(*expr);
10351035
}
10361036

1037-
bool IsNullProcedurePointer(const Expr<SomeType> &expr) {
1038-
return IsNullPointerHelper<true>{}(expr);
1037+
bool IsNullProcedurePointer(const Expr<SomeType> *expr) {
1038+
return expr && IsNullPointerHelper<true>{}(*expr);
10391039
}
10401040

1041-
bool IsNullPointer(const Expr<SomeType> &expr) {
1041+
bool IsNullPointer(const Expr<SomeType> *expr) {
10421042
return IsNullObjectPointer(expr) || IsNullProcedurePointer(expr);
10431043
}
10441044

10451045
bool IsBareNullPointer(const Expr<SomeType> *expr) {
10461046
return expr && std::holds_alternative<NullPointer>(expr->u);
10471047
}
10481048

1049+
struct IsNullAllocatableHelper {
1050+
template <typename A> bool operator()(const A &) const { return false; }
1051+
template <typename T> bool operator()(const FunctionRef<T> &call) const {
1052+
const auto *intrinsic{call.proc().GetSpecificIntrinsic()};
1053+
return intrinsic &&
1054+
intrinsic->characteristics.value().attrs.test(
1055+
characteristics::Procedure::Attr::NullAllocatable);
1056+
}
1057+
template <typename T> bool operator()(const Parentheses<T> &x) const {
1058+
return (*this)(x.left());
1059+
}
1060+
template <typename T> bool operator()(const Expr<T> &x) const {
1061+
return common::visit(*this, x.u);
1062+
}
1063+
};
1064+
1065+
bool IsNullAllocatable(const Expr<SomeType> *x) {
1066+
return x && IsNullAllocatableHelper{}(*x);
1067+
}
1068+
1069+
bool IsNullPointerOrAllocatable(const Expr<SomeType> *x) {
1070+
return IsNullPointer(x) || IsNullAllocatable(x);
1071+
}
1072+
10491073
// GetSymbolVector()
10501074
auto GetSymbolVectorHelper::operator()(const Symbol &x) const -> Result {
10511075
if (const auto *details{x.detailsIf<semantics::AssocEntityDetails>()}) {

flang/lib/Lower/ConvertConstant.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ static mlir::Value genStructureComponentInit(
370370
/*typeParams=*/mlir::ValueRange{} /*TODO*/);
371371

372372
if (Fortran::semantics::IsAllocatable(sym)) {
373-
if (!Fortran::evaluate::IsNullPointer(expr)) {
373+
if (!Fortran::evaluate::IsNullPointerOrAllocatable(&expr)) {
374374
fir::emitFatalError(loc, "constant structure constructor with an "
375375
"allocatable component value that is not NULL");
376376
} else {
@@ -414,7 +414,7 @@ static mlir::Value genStructureComponentInit(
414414
// must fall through to genConstantValue() below.
415415
if (Fortran::semantics::IsBuiltinCPtr(sym) && sym.Rank() == 0 &&
416416
(Fortran::evaluate::GetLastSymbol(expr) ||
417-
Fortran::evaluate::IsNullPointer(expr))) {
417+
Fortran::evaluate::IsNullPointer(&expr))) {
418418
// Builtin c_ptr and c_funptr have special handling because designators
419419
// and NULL() are handled as initial values for them as an extension
420420
// (otherwise only c_ptr_null/c_funptr_null are allowed and these are

0 commit comments

Comments
 (0)