diff options
Diffstat (limited to 'flang/lib/Semantics')
| -rw-r--r-- | flang/lib/Semantics/check-allocate.cpp | 2 | ||||
| -rw-r--r-- | flang/lib/Semantics/check-call.cpp | 217 | ||||
| -rw-r--r-- | flang/lib/Semantics/check-declarations.cpp | 58 | ||||
| -rw-r--r-- | flang/lib/Semantics/check-omp-atomic.cpp | 112 | ||||
| -rw-r--r-- | flang/lib/Semantics/check-omp-loop.cpp | 2 | ||||
| -rw-r--r-- | flang/lib/Semantics/check-omp-structure.cpp | 217 | ||||
| -rw-r--r-- | flang/lib/Semantics/check-omp-structure.h | 9 | ||||
| -rw-r--r-- | flang/lib/Semantics/check-select-rank.cpp | 2 | ||||
| -rw-r--r-- | flang/lib/Semantics/check-select-type.cpp | 2 | ||||
| -rw-r--r-- | flang/lib/Semantics/compute-offsets.cpp | 4 | ||||
| -rw-r--r-- | flang/lib/Semantics/expression.cpp | 17 | ||||
| -rw-r--r-- | flang/lib/Semantics/pointer-assignment.cpp | 2 | ||||
| -rw-r--r-- | flang/lib/Semantics/resolve-directives.cpp | 257 | ||||
| -rw-r--r-- | flang/lib/Semantics/resolve-names.cpp | 92 | ||||
| -rw-r--r-- | flang/lib/Semantics/rewrite-parse-tree.cpp | 10 | ||||
| -rw-r--r-- | flang/lib/Semantics/runtime-type-info.cpp | 16 | ||||
| -rw-r--r-- | flang/lib/Semantics/symbol.cpp | 18 | ||||
| -rw-r--r-- | flang/lib/Semantics/tools.cpp | 24 | ||||
| -rw-r--r-- | flang/lib/Semantics/unparse-with-symbols.cpp | 12 |
19 files changed, 748 insertions, 325 deletions
diff --git a/flang/lib/Semantics/check-allocate.cpp b/flang/lib/Semantics/check-allocate.cpp index 08053594c12e..823aa4e795e3 100644 --- a/flang/lib/Semantics/check-allocate.cpp +++ b/flang/lib/Semantics/check-allocate.cpp @@ -548,7 +548,7 @@ bool AllocationCheckerHelper::RunChecks(SemanticsContext &context) { } } // Shape related checks - if (ultimate_ && evaluate::IsAssumedRank(*ultimate_)) { + if (ultimate_ && IsAssumedRank(*ultimate_)) { context.Say(name_.source, "An assumed-rank dummy argument may not appear in an ALLOCATE statement"_err_en_US); return false; diff --git a/flang/lib/Semantics/check-call.cpp b/flang/lib/Semantics/check-call.cpp index 83f59f0cac3d..f0078fda3600 100644 --- a/flang/lib/Semantics/check-call.cpp +++ b/flang/lib/Semantics/check-call.cpp @@ -67,7 +67,7 @@ static void CheckImplicitInterfaceArg(evaluate::ActualArgument &arg, "Null pointer argument requires an explicit interface"_err_en_US); } else if (auto named{evaluate::ExtractNamedEntity(*expr)}) { const Symbol &symbol{named->GetLastSymbol()}; - if (evaluate::IsAssumedRank(symbol)) { + if (IsAssumedRank(symbol)) { messages.Say( "Assumed rank argument requires an explicit interface"_err_en_US); } @@ -131,7 +131,7 @@ static void CheckCharacterActual(evaluate::Expr<evaluate::SomeType> &actual, dummy.type.type().kind() == actualType.type().kind() && !dummy.attrs.test( characteristics::DummyDataObject::Attr::DeducedFromActual)) { - bool actualIsAssumedRank{evaluate::IsAssumedRank(actual)}; + bool actualIsAssumedRank{IsAssumedRank(actual)}; if (actualIsAssumedRank && !dummy.type.attrs().test( characteristics::TypeAndShape::Attr::AssumedRank)) { @@ -140,7 +140,8 @@ static void CheckCharacterActual(evaluate::Expr<evaluate::SomeType> &actual, messages.Say( "Assumed-rank character array may not be associated with a dummy argument that is not assumed-rank"_err_en_US); } else { - context.Warn(common::LanguageFeature::AssumedRankPassedToNonAssumedRank, + context.Warn(messages, + common::LanguageFeature::AssumedRankPassedToNonAssumedRank, messages.at(), "Assumed-rank character array should not be associated with a dummy argument that is not assumed-rank"_port_en_US); } @@ -187,9 +188,9 @@ static void CheckCharacterActual(evaluate::Expr<evaluate::SomeType> &actual, "Actual argument has fewer characters remaining in storage sequence (%jd) than %s (%jd)"_err_en_US, static_cast<std::intmax_t>(actualChars), dummyName, static_cast<std::intmax_t>(dummyChars)); - } else if (context.ShouldWarn( - common::UsageWarning::ShortCharacterActual)) { - messages.Say(common::UsageWarning::ShortCharacterActual, + } else { + context.Warn(messages, + common::UsageWarning::ShortCharacterActual, "Actual argument has fewer characters remaining in storage sequence (%jd) than %s (%jd)"_warn_en_US, static_cast<std::intmax_t>(actualChars), dummyName, static_cast<std::intmax_t>(dummyChars)); @@ -207,9 +208,9 @@ static void CheckCharacterActual(evaluate::Expr<evaluate::SomeType> &actual, static_cast<std::intmax_t>(*actualSize * *actualLength), dummyName, static_cast<std::intmax_t>(*dummySize * *dummyLength)); - } else if (context.ShouldWarn( - common::UsageWarning::ShortCharacterActual)) { - messages.Say(common::UsageWarning::ShortCharacterActual, + } else { + context.Warn(messages, + common::UsageWarning::ShortCharacterActual, "Actual argument array has fewer characters (%jd) than %s array (%jd)"_warn_en_US, static_cast<std::intmax_t>(*actualSize * *actualLength), dummyName, @@ -229,17 +230,14 @@ static void CheckCharacterActual(evaluate::Expr<evaluate::SomeType> &actual, } else if (*actualLength < *dummyLength) { CHECK(dummy.type.Rank() == 0); bool isVariable{evaluate::IsVariable(actual)}; - if (context.ShouldWarn( - common::UsageWarning::ShortCharacterActual)) { - if (isVariable) { - messages.Say(common::UsageWarning::ShortCharacterActual, - "Actual argument variable length '%jd' is less than expected length '%jd'"_warn_en_US, - *actualLength, *dummyLength); - } else { - messages.Say(common::UsageWarning::ShortCharacterActual, - "Actual argument expression length '%jd' is less than expected length '%jd'"_warn_en_US, - *actualLength, *dummyLength); - } + if (isVariable) { + context.Warn(messages, common::UsageWarning::ShortCharacterActual, + "Actual argument variable length '%jd' is less than expected length '%jd'"_warn_en_US, + *actualLength, *dummyLength); + } else { + context.Warn(messages, common::UsageWarning::ShortCharacterActual, + "Actual argument expression length '%jd' is less than expected length '%jd'"_warn_en_US, + *actualLength, *dummyLength); } if (!isVariable) { auto converted{ @@ -279,9 +277,8 @@ static void ConvertIntegerActual(evaluate::Expr<evaluate::SomeType> &actual, messages.Say( "Actual argument scalar expression of type INTEGER(%d) cannot be implicitly converted to smaller dummy argument type INTEGER(%d)"_err_en_US, actualType.type().kind(), dummyType.type().kind()); - } else if (semanticsContext.ShouldWarn(common::LanguageFeature:: - ActualIntegerConvertedToSmallerKind)) { - messages.Say( + } else { + semanticsContext.Warn(messages, common::LanguageFeature::ActualIntegerConvertedToSmallerKind, "Actual argument scalar expression of type INTEGER(%d) was converted to smaller dummy argument type INTEGER(%d)"_port_en_US, actualType.type().kind(), dummyType.type().kind()); @@ -364,20 +361,16 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy, if (const auto *constantChar{ evaluate::UnwrapConstantValue<evaluate::Ascii>(actual)}; constantChar && constantChar->wasHollerith() && - dummy.type.type().IsUnlimitedPolymorphic() && - context.ShouldWarn(common::LanguageFeature::HollerithPolymorphic)) { - messages.Say(common::LanguageFeature::HollerithPolymorphic, + dummy.type.type().IsUnlimitedPolymorphic()) { + foldingContext.Warn(common::LanguageFeature::HollerithPolymorphic, "passing Hollerith to unlimited polymorphic as if it were CHARACTER"_port_en_US); } } else if (dummyRank == 0 && allowActualArgumentConversions) { // Extension: pass Hollerith literal to scalar as if it had been BOZ if (auto converted{evaluate::HollerithToBOZ( foldingContext, actual, dummy.type.type())}) { - if (context.ShouldWarn( - common::LanguageFeature::HollerithOrCharacterAsBOZ)) { - messages.Say(common::LanguageFeature::HollerithOrCharacterAsBOZ, - "passing Hollerith or character literal as if it were BOZ"_port_en_US); - } + foldingContext.Warn(common::LanguageFeature::HollerithOrCharacterAsBOZ, + "passing Hollerith or character literal as if it were BOZ"_port_en_US); actual = *converted; actualType.type() = dummy.type.type(); typesCompatible = true; @@ -387,7 +380,7 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy, characteristics::TypeAndShape::Attr::AssumedRank)}; bool actualIsAssumedSize{actualType.attrs().test( characteristics::TypeAndShape::Attr::AssumedSize)}; - bool actualIsAssumedRank{evaluate::IsAssumedRank(actual)}; + bool actualIsAssumedRank{IsAssumedRank(actual)}; bool actualIsPointer{evaluate::IsObjectPointer(actual)}; bool actualIsAllocatable{evaluate::IsAllocatableDesignator(actual)}; bool actualMayBeAssumedSize{actualIsAssumedSize || @@ -411,7 +404,7 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy, "%s actual argument may not be associated with INTENT(OUT) assumed-rank dummy argument requiring finalization, destruction, or initialization"_err_en_US, actualDesc); } else { - context.Warn(common::UsageWarning::Portability, messages.at(), + foldingContext.Warn(common::UsageWarning::Portability, messages.at(), "%s actual argument should not be associated with INTENT(OUT) assumed-rank dummy argument"_port_en_US, actualDesc); } @@ -671,9 +664,8 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy, "Actual argument has fewer elements remaining in storage sequence (%jd) than %s array (%jd)"_err_en_US, static_cast<std::intmax_t>(*actualElements), dummyName, static_cast<std::intmax_t>(*dummySize)); - } else if (context.ShouldWarn( - common::UsageWarning::ShortArrayActual)) { - messages.Say(common::UsageWarning::ShortArrayActual, + } else { + context.Warn(common::UsageWarning::ShortArrayActual, "Actual argument has fewer elements remaining in storage sequence (%jd) than %s array (%jd)"_warn_en_US, static_cast<std::intmax_t>(*actualElements), dummyName, static_cast<std::intmax_t>(*dummySize)); @@ -690,9 +682,8 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy, "Actual argument array has fewer elements (%jd) than %s array (%jd)"_err_en_US, static_cast<std::intmax_t>(*actualSize), dummyName, static_cast<std::intmax_t>(*dummySize)); - } else if (context.ShouldWarn( - common::UsageWarning::ShortArrayActual)) { - messages.Say(common::UsageWarning::ShortArrayActual, + } else { + context.Warn(common::UsageWarning::ShortArrayActual, "Actual argument array has fewer elements (%jd) than %s array (%jd)"_warn_en_US, static_cast<std::intmax_t>(*actualSize), dummyName, static_cast<std::intmax_t>(*dummySize)); @@ -779,24 +770,36 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy, // Cases when temporaries might be needed but must not be permitted. bool dummyIsAssumedShape{dummy.type.attrs().test( characteristics::TypeAndShape::Attr::AssumedShape)}; - if ((actualIsAsynchronous || actualIsVolatile) && - (dummyIsAsynchronous || dummyIsVolatile) && !dummyIsValue) { - if (actualCoarrayRef) { // C1538 - messages.Say( - "Coindexed ASYNCHRONOUS or VOLATILE actual argument may not be associated with %s with ASYNCHRONOUS or VOLATILE attributes unless VALUE"_err_en_US, - dummyName); - } - if ((actualRank > 0 || actualIsAssumedRank) && !actualIsContiguous) { - if (dummyIsContiguous || - !(dummyIsAssumedShape || dummyIsAssumedRank || - (actualIsPointer && dummyIsPointer))) { // C1539 & C1540 + if (!dummyIsValue && (dummyIsAsynchronous || dummyIsVolatile)) { + if (actualIsAsynchronous || actualIsVolatile) { + if (actualCoarrayRef) { // F'2023 C1547 messages.Say( - "ASYNCHRONOUS or VOLATILE actual argument that is not simply contiguous may not be associated with a contiguous ASYNCHRONOUS or VOLATILE %s"_err_en_US, + "Coindexed ASYNCHRONOUS or VOLATILE actual argument may not be associated with %s with ASYNCHRONOUS or VOLATILE attributes unless VALUE"_err_en_US, dummyName); } + if ((actualRank > 0 || actualIsAssumedRank) && !actualIsContiguous) { + if (dummyIsContiguous || + !(dummyIsAssumedShape || dummyIsAssumedRank || + (actualIsPointer && dummyIsPointer))) { // F'2023 C1548 & C1549 + messages.Say( + "ASYNCHRONOUS or VOLATILE actual argument that is not simply contiguous may not be associated with a contiguous ASYNCHRONOUS or VOLATILE %s"_err_en_US, + dummyName); + } + } + // The vector subscript case is handled by the definability check above. + // The copy-in/copy-out cases are handled by the previous checks. + // Nag, GFortran, and NVFortran all error on this case, even though it is + // ok, prossibly as an over-restriction of C1548. + } else if (!(dummyIsAssumedShape || dummyIsAssumedRank || + (actualIsPointer && dummyIsPointer)) && + evaluate::IsArraySection(actual) && + !evaluate::HasVectorSubscript(actual)) { + context.Warn(common::UsageWarning::Portability, messages.at(), + "The array section '%s' should not be associated with %s with %s attribute, unless the dummy is assumed-shape or assumed-rank"_port_en_US, + actual.AsFortran(), dummyName, + dummyIsAsynchronous ? "ASYNCHRONOUS" : "VOLATILE"); } } - // 15.5.2.6 -- dummy is ALLOCATABLE bool dummyIsOptional{ dummy.attrs.test(characteristics::DummyDataObject::Attr::Optional)}; @@ -821,10 +824,8 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy, messages.Say( "A null pointer should not be associated with allocatable %s without INTENT(IN)"_warn_en_US, dummyName); - } else if (dummy.intent == common::Intent::In && - context.ShouldWarn( - common::LanguageFeature::NullActualForAllocatable)) { - messages.Say(common::LanguageFeature::NullActualForAllocatable, + } else if (dummy.intent == common::Intent::In) { + foldingContext.Warn(common::LanguageFeature::NullActualForAllocatable, "Allocatable %s is associated with a null pointer"_port_en_US, dummyName); } @@ -878,11 +879,8 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy, checkTypeCompatibility = false; if (dummyIsUnlimited && dummy.intent == common::Intent::In && context.IsEnabled(common::LanguageFeature::RelaxedIntentInChecking)) { - if (context.ShouldWarn( - common::LanguageFeature::RelaxedIntentInChecking)) { - messages.Say(common::LanguageFeature::RelaxedIntentInChecking, - "If a POINTER or ALLOCATABLE dummy or actual argument is unlimited polymorphic, both should be so"_port_en_US); - } + foldingContext.Warn(common::LanguageFeature::RelaxedIntentInChecking, + "If a POINTER or ALLOCATABLE dummy or actual argument is unlimited polymorphic, both should be so"_port_en_US); } else { messages.Say( "If a POINTER or ALLOCATABLE dummy or actual argument is unlimited polymorphic, both must be so"_err_en_US); @@ -890,21 +888,15 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy, } else if (dummyIsPolymorphic != actualIsPolymorphic) { if (dummyIsPolymorphic && dummy.intent == common::Intent::In && context.IsEnabled(common::LanguageFeature::RelaxedIntentInChecking)) { - if (context.ShouldWarn( - common::LanguageFeature::RelaxedIntentInChecking)) { - messages.Say(common::LanguageFeature::RelaxedIntentInChecking, - "If a POINTER or ALLOCATABLE dummy or actual argument is polymorphic, both should be so"_port_en_US); - } + foldingContext.Warn(common::LanguageFeature::RelaxedIntentInChecking, + "If a POINTER or ALLOCATABLE dummy or actual argument is polymorphic, both should be so"_port_en_US); } else if (actualIsPolymorphic && context.IsEnabled(common::LanguageFeature:: PolymorphicActualAllocatableOrPointerToMonomorphicDummy)) { - if (context.ShouldWarn(common::LanguageFeature:: - PolymorphicActualAllocatableOrPointerToMonomorphicDummy)) { - messages.Say( - common::LanguageFeature:: - PolymorphicActualAllocatableOrPointerToMonomorphicDummy, - "If a POINTER or ALLOCATABLE actual argument is polymorphic, the corresponding dummy argument should also be so"_port_en_US); - } + foldingContext.Warn( + common::LanguageFeature:: + PolymorphicActualAllocatableOrPointerToMonomorphicDummy, + "If a POINTER or ALLOCATABLE actual argument is polymorphic, the corresponding dummy argument should also be so"_port_en_US); } else { checkTypeCompatibility = false; messages.Say( @@ -916,11 +908,8 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy, if (dummy.intent == common::Intent::In && context.IsEnabled( common::LanguageFeature::RelaxedIntentInChecking)) { - if (context.ShouldWarn( - common::LanguageFeature::RelaxedIntentInChecking)) { - messages.Say(common::LanguageFeature::RelaxedIntentInChecking, - "POINTER or ALLOCATABLE dummy and actual arguments should have the same declared type and kind"_port_en_US); - } + foldingContext.Warn(common::LanguageFeature::RelaxedIntentInChecking, + "POINTER or ALLOCATABLE dummy and actual arguments should have the same declared type and kind"_port_en_US); } else { messages.Say( "POINTER or ALLOCATABLE dummy and actual arguments must have the same declared type and kind"_err_en_US); @@ -991,13 +980,13 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy, bool actualIsTemp{ !actualIsVariable || HasVectorSubscript(actual) || actualCoarrayRef}; if (actualIsTemp) { - messages.Say(common::UsageWarning::NonTargetPassedToTarget, + foldingContext.Warn(common::UsageWarning::NonTargetPassedToTarget, "Any pointer associated with TARGET %s during this call will not be associated with the value of '%s' afterwards"_warn_en_US, dummyName, actual.AsFortran()); } else { auto actualSymbolVector{GetSymbolVector(actual)}; if (!evaluate::GetLastTarget(actualSymbolVector)) { - messages.Say(common::UsageWarning::NonTargetPassedToTarget, + foldingContext.Warn(common::UsageWarning::NonTargetPassedToTarget, "Any pointer associated with TARGET %s during this call must not be used afterwards, as '%s' is not a target"_warn_en_US, dummyName, actual.AsFortran()); } @@ -1126,9 +1115,8 @@ static void CheckProcedureArg(evaluate::ActualArgument &arg, evaluate::SayWithDeclaration(messages, *argProcSymbol, "Procedure binding '%s' passed as an actual argument"_err_en_US, argProcSymbol->name()); - } else if (context.ShouldWarn( - common::LanguageFeature::BindingAsProcedure)) { - evaluate::SayWithDeclaration(messages, *argProcSymbol, + } else { + evaluate::WarnWithDeclaration(foldingContext, *argProcSymbol, common::LanguageFeature::BindingAsProcedure, "Procedure binding '%s' passed as an actual argument"_port_en_US, argProcSymbol->name()); @@ -1180,15 +1168,14 @@ static void CheckProcedureArg(evaluate::ActualArgument &arg, messages.Say( "Actual procedure argument for %s of a PURE procedure must have an explicit interface"_err_en_US, dummyName); - } else if (context.ShouldWarn( - common::UsageWarning::ImplicitInterfaceActual)) { - messages.Say(common::UsageWarning::ImplicitInterfaceActual, + } else { + foldingContext.Warn( + common::UsageWarning::ImplicitInterfaceActual, "Actual procedure argument has an implicit interface which is not known to be compatible with %s which has an explicit interface"_warn_en_US, dummyName); } - } else if (warning && - context.ShouldWarn(common::UsageWarning::ProcDummyArgShapes)) { - messages.Say(common::UsageWarning::ProcDummyArgShapes, + } else if (warning) { + foldingContext.Warn(common::UsageWarning::ProcDummyArgShapes, "Actual procedure argument has possible interface incompatibility with %s: %s"_warn_en_US, dummyName, std::move(*warning)); } @@ -1363,16 +1350,14 @@ static void CheckExplicitInterfaceArg(evaluate::ActualArgument &arg, messages.Say( "NULL() actual argument '%s' may not be associated with allocatable dummy argument %s that is INTENT(OUT) or INTENT(IN OUT)"_err_en_US, expr->AsFortran(), dummyName); - } else if (object.intent == common::Intent::Default && - context.ShouldWarn(common::UsageWarning:: - NullActualForDefaultIntentAllocatable)) { - messages.Say(common::UsageWarning:: - NullActualForDefaultIntentAllocatable, + } else if (object.intent == common::Intent::Default) { + foldingContext.Warn( + common::UsageWarning:: + NullActualForDefaultIntentAllocatable, "NULL() actual argument '%s' should not be associated with allocatable dummy argument %s without INTENT(IN)"_warn_en_US, expr->AsFortran(), dummyName); - } else if (context.ShouldWarn(common::LanguageFeature:: - NullActualForAllocatable)) { - messages.Say( + } else { + foldingContext.Warn( common::LanguageFeature::NullActualForAllocatable, "Allocatable %s is associated with %s"_port_en_US, dummyName, expr->AsFortran()); @@ -1390,8 +1375,7 @@ static void CheckExplicitInterfaceArg(evaluate::ActualArgument &arg, assumed.name(), dummyName); } else if (object.type.attrs().test(characteristics:: TypeAndShape::Attr::AssumedRank) && - !IsAssumedShape(assumed) && - !evaluate::IsAssumedRank(assumed)) { + !IsAssumedShape(assumed) && !IsAssumedRank(assumed)) { messages.Say( // C711 "Assumed-type '%s' must be either assumed shape or assumed rank to be associated with assumed rank %s"_err_en_US, assumed.name(), dummyName); @@ -1562,7 +1546,7 @@ static void CheckAssociated(evaluate::ActualArguments &arguments, if (semanticsContext.ShouldWarn(common::UsageWarning::Portability)) { if (!evaluate::ExtractDataRef(*pointerExpr) && !evaluate::IsProcedurePointer(*pointerExpr)) { - messages.Say(common::UsageWarning::Portability, + foldingContext.Warn(common::UsageWarning::Portability, pointerArg->sourceLocation(), "POINTER= argument of ASSOCIATED() is required by some other compilers to be a pointer"_port_en_US); } else if (scope && !evaluate::UnwrapProcedureRef(*pointerExpr)) { @@ -1573,7 +1557,8 @@ static void CheckAssociated(evaluate::ActualArguments &arguments, DefinabilityFlag::DoNotNoteDefinition}, *pointerExpr)}) { if (whyNot->IsFatal()) { - if (auto *msg{messages.Say(common::UsageWarning::Portability, + if (auto *msg{foldingContext.Warn( + common::UsageWarning::Portability, pointerArg->sourceLocation(), "POINTER= argument of ASSOCIATED() is required by some other compilers to be a valid left-hand side of a pointer assignment statement"_port_en_US)}) { msg->Attach(std::move( @@ -2000,8 +1985,9 @@ static void CheckReduce( } } } - const auto *result{ - procChars ? procChars->functionResult->GetTypeAndShape() : nullptr}; + const auto *result{procChars && procChars->functionResult + ? procChars->functionResult->GetTypeAndShape() + : nullptr}; if (!procChars || !procChars->IsPure() || procChars->dummyArguments.size() != 2 || !procChars->functionResult) { messages.Say( @@ -2087,10 +2073,8 @@ static void CheckReduce( // TRANSFER (16.9.193) static void CheckTransferOperandType(SemanticsContext &context, const evaluate::DynamicType &type, const char *which) { - if (type.IsPolymorphic() && - context.ShouldWarn(common::UsageWarning::PolymorphicTransferArg)) { - context.foldingContext().messages().Say( - common::UsageWarning::PolymorphicTransferArg, + if (type.IsPolymorphic()) { + context.foldingContext().Warn(common::UsageWarning::PolymorphicTransferArg, "%s of TRANSFER is polymorphic"_warn_en_US, which); } else if (!type.IsUnlimitedPolymorphic() && type.category() == TypeCategory::Derived && @@ -2098,7 +2082,7 @@ static void CheckTransferOperandType(SemanticsContext &context, DirectComponentIterator directs{type.GetDerivedTypeSpec()}; if (auto bad{std::find_if(directs.begin(), directs.end(), IsDescriptor)}; bad != directs.end()) { - evaluate::SayWithDeclaration(context.foldingContext().messages(), *bad, + evaluate::WarnWithDeclaration(context.foldingContext(), *bad, common::UsageWarning::PointerComponentTransferArg, "%s of TRANSFER contains allocatable or pointer component %s"_warn_en_US, which, bad.BuildResultDesignatorName()); @@ -2128,8 +2112,8 @@ static void CheckTransfer(evaluate::ActualArguments &arguments, messages.Say( "Element size of MOLD= array may not be zero when SOURCE= is not empty"_err_en_US); } - } else if (context.ShouldWarn(common::UsageWarning::VoidMold)) { - messages.Say(common::UsageWarning::VoidMold, + } else { + foldingContext.Warn(common::UsageWarning::VoidMold, "Element size of MOLD= array may not be zero unless SOURCE= is empty"_warn_en_US); } } @@ -2145,7 +2129,7 @@ static void CheckTransfer(evaluate::ActualArguments &arguments, } else if (context.ShouldWarn( common::UsageWarning::TransferSizePresence) && IsAllocatableOrObjectPointer(whole)) { - messages.Say(common::UsageWarning::TransferSizePresence, + foldingContext.Warn(common::UsageWarning::TransferSizePresence, "SIZE= argument that is allocatable or pointer must be present at execution; parenthesize to silence this warning"_warn_en_US); } } @@ -2368,13 +2352,10 @@ bool CheckArguments(const characteristics::Procedure &proc, /*extentErrors=*/true, ignoreImplicitVsExplicit)}; if (!buffer.empty()) { if (treatingExternalAsImplicit) { - if (context.ShouldWarn( - common::UsageWarning::KnownBadImplicitInterface)) { - if (auto *msg{messages.Say( - common::UsageWarning::KnownBadImplicitInterface, - "If the procedure's interface were explicit, this reference would be in error"_warn_en_US)}) { - buffer.AttachTo(*msg, parser::Severity::Because); - } + if (auto *msg{foldingContext.Warn( + common::UsageWarning::KnownBadImplicitInterface, + "If the procedure's interface were explicit, this reference would be in error"_warn_en_US)}) { + buffer.AttachTo(*msg, parser::Severity::Because); } else { buffer.clear(); } diff --git a/flang/lib/Semantics/check-declarations.cpp b/flang/lib/Semantics/check-declarations.cpp index d769f221b198..84edcebc6497 100644 --- a/flang/lib/Semantics/check-declarations.cpp +++ b/flang/lib/Semantics/check-declarations.cpp @@ -130,21 +130,14 @@ private: } template <typename FeatureOrUsageWarning, typename... A> parser::Message *Warn(FeatureOrUsageWarning warning, A &&...x) { - if (!context_.ShouldWarn(warning) || InModuleFile()) { - return nullptr; - } else { - return messages_.Say(warning, std::forward<A>(x)...); - } + return messages_.Warn(InModuleFile(), context_.languageFeatures(), warning, + std::forward<A>(x)...); } template <typename FeatureOrUsageWarning, typename... A> parser::Message *Warn( FeatureOrUsageWarning warning, parser::CharBlock source, A &&...x) { - if (!context_.ShouldWarn(warning) || - FindModuleFileContaining(context_.FindScope(source))) { - return nullptr; - } else { - return messages_.Say(warning, source, std::forward<A>(x)...); - } + return messages_.Warn(FindModuleFileContaining(context_.FindScope(source)), + context_.languageFeatures(), warning, source, std::forward<A>(x)...); } bool IsResultOkToDiffer(const FunctionResult &); void CheckGlobalName(const Symbol &); @@ -326,7 +319,7 @@ void CheckHelper::Check(const Symbol &symbol) { !IsDummy(symbol)) { if (context_.IsEnabled( common::LanguageFeature::IgnoreIrrelevantAttributes)) { - context_.Warn(common::LanguageFeature::IgnoreIrrelevantAttributes, + Warn(common::LanguageFeature::IgnoreIrrelevantAttributes, "Only a dummy argument should have an INTENT, VALUE, or OPTIONAL attribute"_warn_en_US); } else { messages_.Say( @@ -633,7 +626,7 @@ void CheckHelper::CheckValue( "VALUE attribute may not apply to a type with a coarray ultimate component"_err_en_US); } } - if (evaluate::IsAssumedRank(symbol)) { + if (IsAssumedRank(symbol)) { messages_.Say( "VALUE attribute may not apply to an assumed-rank array"_err_en_US); } @@ -743,7 +736,7 @@ void CheckHelper::CheckObjectEntity( "Coarray '%s' may not have type TEAM_TYPE, C_PTR, or C_FUNPTR"_err_en_US, symbol.name()); } - if (evaluate::IsAssumedRank(symbol)) { + if (IsAssumedRank(symbol)) { messages_.Say("Coarray '%s' may not be an assumed-rank array"_err_en_US, symbol.name()); } @@ -889,7 +882,7 @@ void CheckHelper::CheckObjectEntity( "!DIR$ IGNORE_TKR may not apply to an allocatable or pointer"_err_en_US); } } else if (ignoreTKR.test(common::IgnoreTKR::Rank)) { - if (ignoreTKR.count() == 1 && evaluate::IsAssumedRank(symbol)) { + if (ignoreTKR.count() == 1 && IsAssumedRank(symbol)) { Warn(common::UsageWarning::IgnoreTKRUsage, "!DIR$ IGNORE_TKR(R) is not meaningful for an assumed-rank array"_warn_en_US); } else if (inExplicitExternalInterface) { @@ -1214,7 +1207,7 @@ void CheckHelper::CheckObjectEntity( SayWithDeclaration(symbol, "Deferred-shape entity of %s type is not supported"_err_en_US, typeName); - } else if (evaluate::IsAssumedRank(symbol)) { + } else if (IsAssumedRank(symbol)) { SayWithDeclaration(symbol, "Assumed rank entity of %s type is not supported"_err_en_US, typeName); @@ -2428,7 +2421,7 @@ void CheckHelper::CheckVolatile(const Symbol &symbol, void CheckHelper::CheckContiguous(const Symbol &symbol) { if (evaluate::IsVariable(symbol) && ((IsPointer(symbol) && symbol.Rank() > 0) || IsAssumedShape(symbol) || - evaluate::IsAssumedRank(symbol))) { + IsAssumedRank(symbol))) { } else { parser::MessageFixedText msg{symbol.owner().IsDerivedType() ? "CONTIGUOUS component '%s' should be an array with the POINTER attribute"_port_en_US @@ -2957,7 +2950,7 @@ static bool IsSubprogramDefinition(const Symbol &symbol) { static bool IsExternalProcedureDefinition(const Symbol &symbol) { return IsBlockData(symbol) || - (IsSubprogramDefinition(symbol) && + ((IsSubprogramDefinition(symbol) || IsAlternateEntry(&symbol)) && (IsExternal(symbol) || symbol.GetBindName())); } @@ -3141,16 +3134,14 @@ parser::Messages CheckHelper::WhyNotInteroperableDerivedType( *dyType, &context_.languageFeatures()) .value_or(false)) { if (type->category() == DeclTypeSpec::Logical) { - if (context_.ShouldWarn(common::UsageWarning::LogicalVsCBool)) { - msgs.Say(common::UsageWarning::LogicalVsCBool, component.name(), - "A LOGICAL component of an interoperable type should have the interoperable KIND=C_BOOL"_port_en_US); - } + context().Warn(msgs, common::UsageWarning::LogicalVsCBool, + component.name(), + "A LOGICAL component of an interoperable type should have the interoperable KIND=C_BOOL"_port_en_US); } else if (type->category() == DeclTypeSpec::Character && dyType && dyType->kind() == 1) { - if (context_.ShouldWarn(common::UsageWarning::BindCCharLength)) { - msgs.Say(common::UsageWarning::BindCCharLength, component.name(), - "A CHARACTER component of an interoperable type should have length 1"_port_en_US); - } + context().Warn(msgs, common::UsageWarning::BindCCharLength, + component.name(), + "A CHARACTER component of an interoperable type should have length 1"_port_en_US); } else { msgs.Say(component.name(), "Each component of an interoperable derived type must have an interoperable type"_err_en_US); @@ -3165,10 +3156,9 @@ parser::Messages CheckHelper::WhyNotInteroperableDerivedType( } } if (derived->componentNames().empty()) { // F'2023 C1805 - if (context_.ShouldWarn(common::LanguageFeature::EmptyBindCDerivedType)) { - msgs.Say(common::LanguageFeature::EmptyBindCDerivedType, symbol.name(), - "A derived type with the BIND attribute should not be empty"_warn_en_US); - } + context().Warn(msgs, common::LanguageFeature::EmptyBindCDerivedType, + symbol.name(), + "A derived type with the BIND attribute should not be empty"_warn_en_US); } } if (msgs.AnyFatalError()) { @@ -3218,7 +3208,7 @@ parser::Messages CheckHelper::WhyNotInteroperableObject( if (derived && !derived->typeSymbol().attrs().test(Attr::BIND_C)) { if (allowNonInteroperableType) { // portability warning only evaluate::AttachDeclaration( - context_.Warn(common::UsageWarning::Portability, symbol.name(), + Warn(common::UsageWarning::Portability, symbol.name(), "The derived type of this interoperable object should be BIND(C)"_port_en_US), derived->typeSymbol()); } else if (!context_.IsEnabled( @@ -3260,10 +3250,10 @@ parser::Messages CheckHelper::WhyNotInteroperableObject( } else if (type->category() == DeclTypeSpec::Logical) { if (context_.ShouldWarn(common::UsageWarning::LogicalVsCBool)) { if (IsDummy(symbol)) { - msgs.Say(common::UsageWarning::LogicalVsCBool, symbol.name(), + Warn(common::UsageWarning::LogicalVsCBool, symbol.name(), "A BIND(C) LOGICAL dummy argument should have the interoperable KIND=C_BOOL"_port_en_US); } else { - msgs.Say(common::UsageWarning::LogicalVsCBool, symbol.name(), + Warn(common::UsageWarning::LogicalVsCBool, symbol.name(), "A BIND(C) LOGICAL object should have the interoperable KIND=C_BOOL"_port_en_US); } } @@ -3459,7 +3449,7 @@ void CheckHelper::CheckBindC(const Symbol &symbol) { bool CheckHelper::CheckDioDummyIsData( const Symbol &subp, const Symbol *arg, std::size_t position) { if (arg && arg->detailsIf<ObjectEntityDetails>()) { - if (evaluate::IsAssumedRank(*arg)) { + if (IsAssumedRank(*arg)) { messages_.Say(arg->name(), "Dummy argument '%s' may not be assumed-rank"_err_en_US, arg->name()); return false; diff --git a/flang/lib/Semantics/check-omp-atomic.cpp b/flang/lib/Semantics/check-omp-atomic.cpp index 50e63d356be0..351af5c099ae 100644 --- a/flang/lib/Semantics/check-omp-atomic.cpp +++ b/flang/lib/Semantics/check-omp-atomic.cpp @@ -61,16 +61,46 @@ template <common::TypeCategory C, int K> struct IsIntegral<evaluate::Type<C, K>> { static constexpr bool value{// C == common::TypeCategory::Integer || - C == common::TypeCategory::Unsigned || - C == common::TypeCategory::Logical}; + C == common::TypeCategory::Unsigned}; }; template <typename T> constexpr bool is_integral_v{IsIntegral<T>::value}; +template <typename...> struct IsFloatingPoint { + static constexpr bool value{false}; +}; + +template <common::TypeCategory C, int K> +struct IsFloatingPoint<evaluate::Type<C, K>> { + static constexpr bool value{// + C == common::TypeCategory::Real || C == common::TypeCategory::Complex}; +}; + +template <typename T> +constexpr bool is_floating_point_v{IsFloatingPoint<T>::value}; + +template <typename T> +constexpr bool is_numeric_v{is_integral_v<T> || is_floating_point_v<T>}; + +template <typename...> struct IsLogical { + static constexpr bool value{false}; +}; + +template <common::TypeCategory C, int K> +struct IsLogical<evaluate::Type<C, K>> { + static constexpr bool value{C == common::TypeCategory::Logical}; +}; + +template <typename T> constexpr bool is_logical_v{IsLogical<T>::value}; + template <typename T, typename Op0, typename Op1> using ReassocOpBase = evaluate::match::AnyOfPattern< // evaluate::match::Add<T, Op0, Op1>, // - evaluate::match::Mul<T, Op0, Op1>>; + evaluate::match::Mul<T, Op0, Op1>, // + evaluate::match::LogicalOp<common::LogicalOperator::And, T, Op0, Op1>, + evaluate::match::LogicalOp<common::LogicalOperator::Or, T, Op0, Op1>, + evaluate::match::LogicalOp<common::LogicalOperator::Eqv, T, Op0, Op1>, + evaluate::match::LogicalOp<common::LogicalOperator::Neqv, T, Op0, Op1>>; template <typename T, typename Op0, typename Op1> struct ReassocOp : public ReassocOpBase<T, Op0, Op1> { @@ -88,13 +118,14 @@ struct ReassocRewriter : public evaluate::rewrite::Identity { using Id = evaluate::rewrite::Identity; struct NonIntegralTag {}; - ReassocRewriter(const SomeExpr &atom) : atom_(atom) {} + ReassocRewriter(const SomeExpr &atom, const SemanticsContext &context) + : atom_(atom), context_(context) {} // Try to find cases where the input expression is of the form // (1) (a . b) . c, or // (2) a . (b . c), - // where . denotes an associative operation (currently + or *), and a, b, c - // are some subexpresions. + // where . denotes an associative operation, and a, b, c are some + // subexpresions. // If one of the operands in the nested operation is the atomic variable // (with some possible type conversions applied to it), bring it to the // top-level operation, and move the top-level operand into the nested @@ -102,8 +133,13 @@ struct ReassocRewriter : public evaluate::rewrite::Identity { // For example, assuming x is the atomic variable: // (a + x) + b -> (a + b) + x, i.e. (conceptually) swap x and b. template <typename T, typename U, - typename = std::enable_if_t<is_integral_v<T>>> + typename = std::enable_if_t<is_numeric_v<T> || is_logical_v<T>>> evaluate::Expr<T> operator()(evaluate::Expr<T> &&x, const U &u) { + if constexpr (is_floating_point_v<T>) { + if (!context_.langOptions().AssociativeMath) { + return Id::operator()(std::move(x), u); + } + } // As per the above comment, there are 3 subexpressions involved in this // transformation. A match::Expr<T> will match evaluate::Expr<U> when T is // same as U, plus it will store a pointer (ref) to the matched expression. @@ -111,8 +147,8 @@ struct ReassocRewriter : public evaluate::rewrite::Identity { // some order) from the example above. evaluate::match::Expr<T> sub[3]; auto inner{reassocOp<T>(sub[0], sub[1])}; - auto outer1{reassocOp<T>(inner, sub[2])}; // inner + something - auto outer2{reassocOp<T>(sub[2], inner)}; // something + inner + auto outer1{reassocOp<T>(inner, sub[2])}; // inner . something + auto outer2{reassocOp<T>(sub[2], inner)}; // something . inner #if !defined(__clang__) && !defined(_MSC_VER) && \ (__GNUC__ < 8 || (__GNUC__ == 8 && __GNUC_MINOR__ < 5)) // If GCC version < 8.5, use this definition. For the other definition @@ -121,7 +157,8 @@ struct ReassocRewriter : public evaluate::rewrite::Identity { // inside of the visitor function in common::visit. // Since this works with clang, MSVC and at least GCC 8.5, I'm assuming // that this is some kind of a GCC issue. - using MatchTypes = std::tuple<evaluate::Add<T>, evaluate::Multiply<T>>; + using MatchTypes = std::tuple<evaluate::Add<T>, evaluate::Multiply<T>, + evaluate::LogicalOperation<T::kind>>; #else using MatchTypes = typename decltype(outer1)::MatchTypes; #endif @@ -145,23 +182,9 @@ struct ReassocRewriter : public evaluate::rewrite::Identity { } return common::visit( [&](auto &&s) { - using Expr = evaluate::Expr<T>; - using TypeS = llvm::remove_cvref_t<decltype(s)>; - // This visitor has to be semantically correct for all possible - // types of s even though at runtime s will only be one of the - // matched types. - // Limit the construction to the operation types that we tried - // to match (otherwise TypeS(op1, op2) would fail for non-binary - // operations). - if constexpr (common::HasMember<TypeS, MatchTypes>) { - Expr atom{*sub[atomIdx].ref}; - Expr op1{*sub[(atomIdx + 1) % 3].ref}; - Expr op2{*sub[(atomIdx + 2) % 3].ref}; - return Expr( - TypeS(atom, Expr(TypeS(std::move(op1), std::move(op2))))); - } else { - return Expr(TypeS(s)); - } + // Build the new expression from the matched components. + return Reconstruct<T, MatchTypes>(s, *sub[atomIdx].ref, + *sub[(atomIdx + 1) % 3].ref, *sub[(atomIdx + 2) % 3].ref); }, evaluate::match::deparen(x).u); } @@ -169,18 +192,49 @@ struct ReassocRewriter : public evaluate::rewrite::Identity { } template <typename T, typename U, - typename = std::enable_if_t<!is_integral_v<T>>> + typename = std::enable_if_t<!is_numeric_v<T> && !is_logical_v<T>>> evaluate::Expr<T> operator()( evaluate::Expr<T> &&x, const U &u, NonIntegralTag = {}) { return Id::operator()(std::move(x), u); } private: + template <typename T, typename MatchTypes, typename S> + evaluate::Expr<T> Reconstruct(const S &op, evaluate::Expr<T> atom, + evaluate::Expr<T> op1, evaluate::Expr<T> op2) { + using TypeS = llvm::remove_cvref_t<decltype(op)>; + // This function has to be semantically correct for all possible types + // of S even though at runtime s will only be one of the matched types. + // Limit the construction to the operation types that we tried to match + // (otherwise TypeS(op1, op2) would fail for non-binary operations). + if constexpr (!common::HasMember<TypeS, MatchTypes>) { + return evaluate::Expr<T>(TypeS(op)); + } else if constexpr (is_logical_v<T>) { + constexpr int K{T::kind}; + if constexpr (std::is_same_v<TypeS, evaluate::LogicalOperation<K>>) { + // Logical operators take an extra argument in their constructor, + // so they need their own reconstruction code. + common::LogicalOperator opCode{op.logicalOperator}; + return evaluate::Expr<T>(TypeS( // + opCode, std::move(atom), + evaluate::Expr<T>(TypeS( // + opCode, std::move(op1), std::move(op2))))); + } + } else { + // Generic reconstruction. + return evaluate::Expr<T>(TypeS( // + std::move(atom), + evaluate::Expr<T>(TypeS( // + std::move(op1), std::move(op2))))); + } + } + template <typename T> bool IsAtom(const evaluate::Expr<T> &x) const { return IsSameOrConvertOf(evaluate::AsGenericExpr(AsRvalue(x)), atom_); } const SomeExpr &atom_; + const SemanticsContext &context_; }; struct AnalyzedCondStmt { @@ -809,7 +863,7 @@ OmpStructureChecker::CheckAtomicUpdateAssignment( CheckStorageOverlap(atom, GetNonAtomArguments(atom, update.rhs), source); return std::nullopt; } else if (tryReassoc) { - ReassocRewriter ra(atom); + ReassocRewriter ra(atom, context_); SomeExpr raRhs{evaluate::rewrite::Mutator(ra)(update.rhs)}; std::tie(hasErrors, tryReassoc) = CheckAtomicUpdateAssignmentRhs( diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp index 8dad1f5d605e..9384e039cf3f 100644 --- a/flang/lib/Semantics/check-omp-loop.cpp +++ b/flang/lib/Semantics/check-omp-loop.cpp @@ -196,7 +196,7 @@ void OmpStructureChecker::CheckSIMDNest(const parser::OpenMPConstruct &c) { common::visit( common::visitors{ // Allow `!$OMP ORDERED SIMD` - [&](const parser::OpenMPBlockConstruct &c) { + [&](const parser::OmpBlockConstruct &c) { const parser::OmpDirectiveSpecification &beginSpec{c.BeginDir()}; if (beginSpec.DirId() == llvm::omp::Directive::OMPD_ordered) { for (const auto &clause : beginSpec.Clauses().v) { diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp index 4a5e0b412874..d1654a3adcc9 100644 --- a/flang/lib/Semantics/check-omp-structure.cpp +++ b/flang/lib/Semantics/check-omp-structure.cpp @@ -143,6 +143,64 @@ private: parser::CharBlock source_; }; +// 'OmpWorkdistributeBlockChecker' is used to check the validity of the +// assignment statements and the expressions enclosed in an OpenMP +// WORKDISTRIBUTE construct +class OmpWorkdistributeBlockChecker { +public: + OmpWorkdistributeBlockChecker( + SemanticsContext &context, parser::CharBlock source) + : context_{context}, source_{source} {} + + template <typename T> bool Pre(const T &) { return true; } + template <typename T> void Post(const T &) {} + + bool Pre(const parser::AssignmentStmt &assignment) { + const auto &var{std::get<parser::Variable>(assignment.t)}; + const auto &expr{std::get<parser::Expr>(assignment.t)}; + const auto *lhs{GetExpr(context_, var)}; + const auto *rhs{GetExpr(context_, expr)}; + if (lhs && rhs) { + Tristate isDefined{semantics::IsDefinedAssignment( + lhs->GetType(), lhs->Rank(), rhs->GetType(), rhs->Rank())}; + if (isDefined == Tristate::Yes) { + context_.Say(expr.source, + "Defined assignment statement is not allowed in a WORKDISTRIBUTE construct"_err_en_US); + } + } + return true; + } + + bool Pre(const parser::Expr &expr) { + if (const auto *e{GetExpr(context_, expr)}) { + if (!e) + return false; + for (const Symbol &symbol : evaluate::CollectSymbols(*e)) { + const Symbol &root{GetAssociationRoot(symbol)}; + if (IsFunction(root)) { + std::vector<std::string> attrs; + if (!IsElementalProcedure(root)) { + attrs.push_back("non-ELEMENTAL"); + } + if (root.attrs().test(Attr::IMPURE)) { + attrs.push_back("IMPURE"); + } + std::string attrsStr = + attrs.empty() ? "" : " " + llvm::join(attrs, ", "); + context_.Say(expr.source, + "User defined%s function '%s' is not allowed in a WORKDISTRIBUTE construct"_err_en_US, + attrsStr, root.name()); + } + } + } + return false; + } + +private: + SemanticsContext &context_; + parser::CharBlock source_; +}; + // `OmpUnitedTaskDesignatorChecker` is used to check if the designator // can appear within the TASK construct class OmpUnitedTaskDesignatorChecker { @@ -210,6 +268,41 @@ bool OmpStructureChecker::CheckAllowedClause(llvmOmpClause clause) { return CheckAllowed(clause); } +void OmpStructureChecker::AnalyzeObject(const parser::OmpObject &object) { + if (std::holds_alternative<parser::Name>(object.u)) { + // Do not analyze common block names. The analyzer will flag an error + // on those. + return; + } + if (auto *symbol{GetObjectSymbol(object)}) { + // Eliminate certain kinds of symbols before running the analyzer to + // avoid confusing error messages. The analyzer assumes that the context + // of the object use is an expression, and some diagnostics are tailored + // to that. + if (symbol->has<DerivedTypeDetails>() || symbol->has<MiscDetails>()) { + // Type names, construct names, etc. + return; + } + if (auto *typeSpec{symbol->GetType()}) { + if (typeSpec->category() == DeclTypeSpec::Category::Character) { + // Don't pass character objects to the analyzer, it can emit somewhat + // cryptic errors (e.g. "'obj' is not an array"). Substrings are + // checked elsewhere in OmpStructureChecker. + return; + } + } + } + evaluate::ExpressionAnalyzer ea{context_}; + auto restore{ea.AllowWholeAssumedSizeArray(true)}; + common::visit([&](auto &&s) { ea.Analyze(s); }, object.u); +} + +void OmpStructureChecker::AnalyzeObjects(const parser::OmpObjectList &objects) { + for (const parser::OmpObject &object : objects.v) { + AnalyzeObject(object); + } +} + bool OmpStructureChecker::IsCloselyNestedRegion(const OmpDirectiveSet &set) { // Definition of close nesting: // @@ -531,14 +624,6 @@ template <typename Checker> struct DirectiveSpellingVisitor { checker_(GetDirName(x.t).source, Directive::OMPD_allocators); return false; } - bool Pre(const parser::OmpAssumeDirective &x) { - checker_(std::get<parser::Verbatim>(x.t).source, Directive::OMPD_assume); - return false; - } - bool Pre(const parser::OmpEndAssumeDirective &x) { - checker_(x.v.source, Directive::OMPD_assume); - return false; - } bool Pre(const parser::OmpMetadirectiveDirective &x) { checker_( std::get<parser::Verbatim>(x.t).source, Directive::OMPD_metadirective); @@ -729,7 +814,7 @@ void OmpStructureChecker::CheckTargetNest(const parser::OpenMPConstruct &c) { parser::CharBlock source; common::visit( common::visitors{ - [&](const parser::OpenMPBlockConstruct &c) { + [&](const parser::OmpBlockConstruct &c) { const parser::OmpDirectiveSpecification &beginSpec{c.BeginDir()}; source = beginSpec.DirName().source; if (beginSpec.DirId() == llvm::omp::Directive::OMPD_target_data) { @@ -779,12 +864,44 @@ void OmpStructureChecker::CheckTargetNest(const parser::OpenMPConstruct &c) { } } -void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) { +void OmpStructureChecker::Enter(const parser::OmpBlockConstruct &x) { const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()}; const std::optional<parser::OmpEndDirective> &endSpec{x.EndDir()}; const parser::Block &block{std::get<parser::Block>(x.t)}; PushContextAndClauseSets(beginSpec.DirName().source, beginSpec.DirId()); + + // Missing mandatory end block: this is checked in semantics because that + // makes it easier to control the error messages. + // The end block is mandatory when the construct is not applied to a strictly + // structured block (aka it is applied to a loosely structured block). In + // other words, the body doesn't contain exactly one parser::BlockConstruct. + auto isStrictlyStructuredBlock{[](const parser::Block &block) -> bool { + if (block.size() != 1) { + return false; + } + const parser::ExecutionPartConstruct &contents{block.front()}; + auto *executableConstruct{ + std::get_if<parser::ExecutableConstruct>(&contents.u)}; + if (!executableConstruct) { + return false; + } + return std::holds_alternative<common::Indirection<parser::BlockConstruct>>( + executableConstruct->u); + }}; + if (!endSpec && !isStrictlyStructuredBlock(block)) { + llvm::omp::Directive dirId{beginSpec.DirId()}; + auto &msg{context_.Say(beginSpec.source, + "Expected OpenMP END %s directive"_err_en_US, + parser::ToUpperCaseLetters(getDirectiveName(dirId)))}; + // ORDERED has two variants, so be explicit about which variant we think + // this is. + if (dirId == llvm::omp::Directive::OMPD_ordered) { + msg.Attach( + beginSpec.source, "The ORDERED directive is block-associated"_en_US); + } + } + if (llvm::omp::allTargetSet.test(GetContext().directive)) { EnterDirectiveNest(TargetNest); } @@ -815,6 +932,12 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) { "TARGET construct with nested TEAMS region contains statements or " "directives outside of the TEAMS construct"_err_en_US); } + if (GetContext().directive == llvm::omp::Directive::OMPD_workdistribute && + GetContextParent().directive != llvm::omp::Directive::OMPD_teams) { + context_.Say(x.BeginDir().DirName().source, + "%s region can only be strictly nested within TEAMS region"_err_en_US, + ContextDirectiveAsFortran()); + } } CheckNoBranching(block, beginSpec.DirId(), beginSpec.source); @@ -898,6 +1021,17 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) { HasInvalidWorksharingNesting( beginSpec.source, llvm::omp::nestedWorkshareErrSet); break; + case llvm::omp::OMPD_workdistribute: + if (!CurrentDirectiveIsNested()) { + context_.Say(beginSpec.source, + "A WORKDISTRIBUTE region must be nested inside TEAMS region only."_err_en_US); + } + CheckWorkdistributeBlockStmts(block, beginSpec.source); + break; + case llvm::omp::OMPD_teams_workdistribute: + case llvm::omp::OMPD_target_teams_workdistribute: + CheckWorkdistributeBlockStmts(block, beginSpec.source); + break; case llvm::omp::Directive::OMPD_scope: case llvm::omp::Directive::OMPD_single: // TODO: This check needs to be extended while implementing nesting of @@ -919,7 +1053,7 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) { } void OmpStructureChecker::CheckMasterNesting( - const parser::OpenMPBlockConstruct &x) { + const parser::OmpBlockConstruct &x) { // A MASTER region may not be `closely nested` inside a worksharing, loop, // task, taskloop, or atomic region. // TODO: Expand the check to include `LOOP` construct as well when it is @@ -948,7 +1082,7 @@ void OmpStructureChecker::Leave(const parser::OpenMPDeclarativeAssumes &) { dirContext_.pop_back(); } -void OmpStructureChecker::Leave(const parser::OpenMPBlockConstruct &) { +void OmpStructureChecker::Leave(const parser::OmpBlockConstruct &) { if (GetDirectiveNest(TargetBlockOnlyTeams)) { ExitDirectiveNest(TargetBlockOnlyTeams); } @@ -1039,14 +1173,23 @@ void OmpStructureChecker::Leave(const parser::OmpBeginDirective &) { void OmpStructureChecker::Enter(const parser::OpenMPSectionsConstruct &x) { const auto &beginSectionsDir{ std::get<parser::OmpBeginSectionsDirective>(x.t)}; - const auto &endSectionsDir{std::get<parser::OmpEndSectionsDirective>(x.t)}; + const auto &endSectionsDir{ + std::get<std::optional<parser::OmpEndSectionsDirective>>(x.t)}; const auto &beginDir{ std::get<parser::OmpSectionsDirective>(beginSectionsDir.t)}; - const auto &endDir{std::get<parser::OmpSectionsDirective>(endSectionsDir.t)}; + PushContextAndClauseSets(beginDir.source, beginDir.v); + + if (!endSectionsDir) { + context_.Say(beginSectionsDir.source, + "Expected OpenMP END SECTIONS directive"_err_en_US); + // Following code assumes the option is present. + return; + } + + const auto &endDir{std::get<parser::OmpSectionsDirective>(endSectionsDir->t)}; CheckMatching<parser::OmpSectionsDirective>(beginDir, endDir); - PushContextAndClauseSets(beginDir.source, beginDir.v); - AddEndDirectiveClauses(std::get<parser::OmpClauseList>(endSectionsDir.t)); + AddEndDirectiveClauses(std::get<parser::OmpClauseList>(endSectionsDir->t)); const auto §ionBlocks{std::get<std::list<parser::OpenMPConstruct>>(x.t)}; for (const parser::OpenMPConstruct &construct : sectionBlocks) { @@ -2597,8 +2740,9 @@ void OmpStructureChecker::Leave(const parser::OmpClauseList &) { void OmpStructureChecker::Enter(const parser::OmpClause &x) { SetContextClause(x); + llvm::omp::Clause id{x.Id()}; // The visitors for these clauses do their own checks. - switch (x.Id()) { + switch (id) { case llvm::omp::Clause::OMPC_copyprivate: case llvm::omp::Clause::OMPC_enter: case llvm::omp::Clause::OMPC_lastprivate: @@ -2612,7 +2756,7 @@ void OmpStructureChecker::Enter(const parser::OmpClause &x) { // Named constants are OK to be used within 'shared' and 'firstprivate' // clauses. The check for this happens a few lines below. bool SharedOrFirstprivate = false; - switch (x.Id()) { + switch (id) { case llvm::omp::Clause::OMPC_shared: case llvm::omp::Clause::OMPC_firstprivate: SharedOrFirstprivate = true; @@ -2622,6 +2766,7 @@ void OmpStructureChecker::Enter(const parser::OmpClause &x) { } if (const parser::OmpObjectList *objList{GetOmpObjectList(x)}) { + AnalyzeObjects(*objList); SymbolSourceMap symbols; GetSymbolsInObjectList(*objList, symbols); for (const auto &[symbol, source] : symbols) { @@ -2648,6 +2793,8 @@ CHECK_SIMPLE_CLAUSE(Final, OMPC_final) CHECK_SIMPLE_CLAUSE(Flush, OMPC_flush) CHECK_SIMPLE_CLAUSE(Full, OMPC_full) CHECK_SIMPLE_CLAUSE(Grainsize, OMPC_grainsize) +CHECK_SIMPLE_CLAUSE(GraphId, OMPC_graph_id) +CHECK_SIMPLE_CLAUSE(GraphReset, OMPC_graph_reset) CHECK_SIMPLE_CLAUSE(Holds, OMPC_holds) CHECK_SIMPLE_CLAUSE(Inclusive, OMPC_inclusive) CHECK_SIMPLE_CLAUSE(Initializer, OMPC_initializer) @@ -3069,7 +3216,7 @@ void OmpStructureChecker::CheckReductionObjectTypes( // r = 0; r = r + r2 // But it might be valid to use these with DECLARE REDUCTION. // Assumed size is already caught elsewhere. - bool cannotBeBuiltinReduction{evaluate::IsAssumedRank(*symbol)}; + bool cannotBeBuiltinReduction{IsAssumedRank(*symbol)}; if (auto *type{symbol->GetType()}) { const auto &scope{context_.FindScope(symbol->name())}; if (!IsReductionAllowedForType( @@ -3352,9 +3499,14 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Aligned &x) { x.v, llvm::omp::OMPC_aligned, GetContext().clauseSource, context_)) { auto &modifiers{OmpGetModifiers(x.v)}; if (auto *align{OmpGetUniqueModifier<parser::OmpAlignment>(modifiers)}) { - if (const auto &v{GetIntValue(align->v)}; !v || *v <= 0) { + const auto &v{GetIntValue(align->v)}; + if (!v || *v <= 0) { context_.Say(OmpGetModifierSource(modifiers, align), "The alignment value should be a constant positive integer"_err_en_US); + } else if (((*v) & (*v - 1)) != 0) { + context_.Warn(common::UsageWarning::OpenMPUsage, + OmpGetModifierSource(modifiers, align), + "Alignment is not a power of 2, Aligned clause will be ignored"_warn_en_US); } } } @@ -4463,7 +4615,7 @@ bool OmpStructureChecker::CheckTargetBlockOnlyTeams( if (const auto *ompConstruct{ parser::Unwrap<parser::OpenMPConstruct>(*it)}) { if (const auto *ompBlockConstruct{ - std::get_if<parser::OpenMPBlockConstruct>(&ompConstruct->u)}) { + std::get_if<parser::OmpBlockConstruct>(&ompConstruct->u)}) { llvm::omp::Directive dirId{ompBlockConstruct->BeginDir().DirId()}; if (dirId == llvm::omp::Directive::OMPD_teams) { nestedTeams = true; @@ -4510,7 +4662,7 @@ void OmpStructureChecker::CheckWorkshareBlockStmts( // 'Parallel' constructs auto currentDir{llvm::omp::Directive::OMPD_unknown}; if (const auto *ompBlockConstruct{ - std::get_if<parser::OpenMPBlockConstruct>(&ompConstruct->u)}) { + std::get_if<parser::OmpBlockConstruct>(&ompConstruct->u)}) { currentDir = ompBlockConstruct->BeginDir().DirId(); } else if (const auto *ompLoopConstruct{ std::get_if<parser::OpenMPLoopConstruct>( @@ -4546,6 +4698,27 @@ void OmpStructureChecker::CheckWorkshareBlockStmts( } } +void OmpStructureChecker::CheckWorkdistributeBlockStmts( + const parser::Block &block, parser::CharBlock source) { + unsigned version{context_.langOptions().OpenMPVersion}; + unsigned since{60}; + if (version < since) + context_.Say(source, + "WORKDISTRIBUTE construct is not allowed in %s, %s"_err_en_US, + ThisVersion(version), TryVersion(since)); + + OmpWorkdistributeBlockChecker ompWorkdistributeBlockChecker{context_, source}; + + for (auto it{block.begin()}; it != block.end(); ++it) { + if (parser::Unwrap<parser::AssignmentStmt>(*it)) { + parser::Walk(*it, ompWorkdistributeBlockChecker); + } else { + context_.Say(source, + "The structured block in a WORKDISTRIBUTE construct may consist of only SCALAR or ARRAY assignments"_err_en_US); + } + } +} + void OmpStructureChecker::CheckIfContiguous(const parser::OmpObject &object) { if (auto contig{IsContiguous(context_, object)}; contig && !*contig) { const parser::Name *name{GetObjectName(object)}; diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h index fd77fed73acb..ce074f5f3f86 100644 --- a/flang/lib/Semantics/check-omp-structure.h +++ b/flang/lib/Semantics/check-omp-structure.h @@ -88,8 +88,8 @@ public: void Leave(const parser::OpenMPAssumeConstruct &); void Enter(const parser::OpenMPDeclarativeAssumes &); void Leave(const parser::OpenMPDeclarativeAssumes &); - void Enter(const parser::OpenMPBlockConstruct &); - void Leave(const parser::OpenMPBlockConstruct &); + void Enter(const parser::OmpBlockConstruct &); + void Leave(const parser::OmpBlockConstruct &); void Leave(const parser::OmpBeginDirective &); void Enter(const parser::OmpEndDirective &); void Leave(const parser::OmpEndDirective &); @@ -167,6 +167,8 @@ private: void CheckVariableListItem(const SymbolSourceMap &symbols); void CheckDirectiveSpelling( parser::CharBlock spelling, llvm::omp::Directive id); + void AnalyzeObject(const parser::OmpObject &object); + void AnalyzeObjects(const parser::OmpObjectList &objects); void CheckMultipleOccurrence(semantics::UnorderedSymbolSet &listVars, const std::list<parser::Name> &nameList, const parser::CharBlock &item, const std::string &clauseName); @@ -245,6 +247,7 @@ private: llvmOmpClause clause, const parser::OmpObjectList &ompObjectList); bool CheckTargetBlockOnlyTeams(const parser::Block &); void CheckWorkshareBlockStmts(const parser::Block &, parser::CharBlock); + void CheckWorkdistributeBlockStmts(const parser::Block &, parser::CharBlock); void CheckIteratorRange(const parser::OmpIteratorSpecifier &x); void CheckIteratorModifier(const parser::OmpIterator &x); @@ -312,7 +315,7 @@ private: const parser::OmpReductionIdentifier &ident); void CheckReductionModifier(const parser::OmpReductionModifier &); void CheckLastprivateModifier(const parser::OmpLastprivateModifier &); - void CheckMasterNesting(const parser::OpenMPBlockConstruct &x); + void CheckMasterNesting(const parser::OmpBlockConstruct &x); void ChecksOnOrderedAsBlock(); void CheckBarrierNesting(const parser::OpenMPSimpleStandaloneConstruct &x); void CheckScan(const parser::OpenMPSimpleStandaloneConstruct &x); diff --git a/flang/lib/Semantics/check-select-rank.cpp b/flang/lib/Semantics/check-select-rank.cpp index b227bbaaef4b..5dade2ca696c 100644 --- a/flang/lib/Semantics/check-select-rank.cpp +++ b/flang/lib/Semantics/check-select-rank.cpp @@ -32,7 +32,7 @@ void SelectRankConstructChecker::Leave( const Symbol *saveSelSymbol{nullptr}; if (const auto selExpr{GetExprFromSelector(selectRankStmtSel)}) { if (const Symbol * sel{evaluate::UnwrapWholeSymbolDataRef(*selExpr)}) { - if (!evaluate::IsAssumedRank(*sel)) { // C1150 + if (!semantics::IsAssumedRank(*sel)) { // C1150 context_.Say(parser::FindSourceLocation(selectRankStmtSel), "Selector '%s' is not an assumed-rank array variable"_err_en_US, sel->name().ToString()); diff --git a/flang/lib/Semantics/check-select-type.cpp b/flang/lib/Semantics/check-select-type.cpp index 94d16a719277..b1b22c3e7c4a 100644 --- a/flang/lib/Semantics/check-select-type.cpp +++ b/flang/lib/Semantics/check-select-type.cpp @@ -252,7 +252,7 @@ void SelectTypeChecker::Enter(const parser::SelectTypeConstruct &construct) { if (IsProcedure(*selector)) { context_.Say( selectTypeStmt.source, "Selector may not be a procedure"_err_en_US); - } else if (evaluate::IsAssumedRank(*selector)) { + } else if (IsAssumedRank(*selector)) { context_.Say(selectTypeStmt.source, "Assumed-rank variable may only be used as actual argument"_err_en_US); } else if (auto exprType{selector->GetType()}) { diff --git a/flang/lib/Semantics/compute-offsets.cpp b/flang/lib/Semantics/compute-offsets.cpp index 6d4fce2f00a6..1c48d33549a2 100644 --- a/flang/lib/Semantics/compute-offsets.cpp +++ b/flang/lib/Semantics/compute-offsets.cpp @@ -239,7 +239,9 @@ void ComputeOffsetsHelper::DoCommonBlock(Symbol &commonBlock) { std::size_t minAlignment{0}; UnorderedSymbolSet previous; for (auto object : details.objects()) { - Symbol &symbol{*object}; + // Allow for host association when the common block is + // OpenMP firstprivate. + Symbol &symbol{object->GetUltimate()}; auto errorSite{ commonBlock.name().empty() ? symbol.name() : commonBlock.name()}; if (std::size_t padding{DoSymbol(symbol.GetUltimate())}) { diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp index d022378ce145..3f048ab6f7a4 100644 --- a/flang/lib/Semantics/expression.cpp +++ b/flang/lib/Semantics/expression.cpp @@ -2198,7 +2198,8 @@ MaybeExpr ExpressionAnalyzer::CheckStructureConstructor( } if (symbol) { const semantics::Scope &innermost{context_.FindScope(exprSource)}; - if (auto msg{CheckAccessibleSymbol(innermost, *symbol)}) { + if (auto msg{CheckAccessibleSymbol( + innermost, *symbol, /*inStructureConstructor=*/true)}) { Say(exprSource, std::move(*msg)); } if (checkConflicts) { @@ -3699,7 +3700,10 @@ static MaybeExpr NumericUnaryHelper(ExpressionAnalyzer &context, analyzer.CheckForNullPointer(); analyzer.CheckForAssumedRank(); if (opr == NumericOperator::Add) { - return analyzer.MoveExpr(0); + // +x -> (x), not a bare x, because the bounds of the argument must + // not be exposed to allocatable assignments or structure constructor + // components. + return Parenthesize(analyzer.MoveExpr(0)); } else { return Negation(context.GetContextualMessages(), analyzer.MoveExpr(0)); } @@ -3783,10 +3787,9 @@ MaybeExpr NumericBinaryHelper( analyzer.CheckForNullPointer(); analyzer.CheckForAssumedRank(); analyzer.CheckConformance(); - constexpr bool canBeUnsigned{opr != NumericOperator::Power}; - return NumericOperation<OPR, canBeUnsigned>( - context.GetContextualMessages(), analyzer.MoveExpr(0), - analyzer.MoveExpr(1), context.GetDefaultKind(TypeCategory::Real)); + return NumericOperation<OPR>(context.GetContextualMessages(), + analyzer.MoveExpr(0), analyzer.MoveExpr(1), + context.GetDefaultKind(TypeCategory::Real)); } else { return analyzer.TryDefinedOp(AsFortran(opr), "Operands of %s must be numeric; have %s and %s"_err_en_US); @@ -4632,7 +4635,7 @@ bool ArgumentAnalyzer::CheckForNullPointer(const char *where) { bool ArgumentAnalyzer::CheckForAssumedRank(const char *where) { for (const std::optional<ActualArgument> &arg : actuals_) { - if (arg && IsAssumedRank(arg->UnwrapExpr())) { + if (arg && semantics::IsAssumedRank(arg->UnwrapExpr())) { context_.Say(source_, "An assumed-rank dummy argument is not allowed %s"_err_en_US, where); fatalErrors_ = true; diff --git a/flang/lib/Semantics/pointer-assignment.cpp b/flang/lib/Semantics/pointer-assignment.cpp index e767bf840957..5508ba837894 100644 --- a/flang/lib/Semantics/pointer-assignment.cpp +++ b/flang/lib/Semantics/pointer-assignment.cpp @@ -159,7 +159,7 @@ bool PointerAssignmentChecker::CheckLeftHandSide(const SomeExpr &lhs) { msg->Attach(std::move(whyNot->set_severity(parser::Severity::Because))); } return false; - } else if (evaluate::IsAssumedRank(lhs)) { + } else if (IsAssumedRank(lhs)) { Say("The left-hand side of a pointer assignment must not be an assumed-rank dummy argument"_err_en_US); return false; } else if (evaluate::ExtractCoarrayRef(lhs)) { // F'2023 C1027 diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp index 96d95162beb7..1b7718d1314d 100644 --- a/flang/lib/Semantics/resolve-directives.cpp +++ b/flang/lib/Semantics/resolve-directives.cpp @@ -29,7 +29,6 @@ #include "llvm/Support/Debug.h" #include <list> #include <map> -#include <sstream> template <typename T> static Fortran::semantics::Scope *GetScope( @@ -61,6 +60,13 @@ protected: parser::OmpDefaultmapClause::ImplicitBehavior> defaultMap; + std::optional<Symbol::Flag> FindSymbolWithDSA(const Symbol &symbol) { + if (auto it{objectWithDSA.find(&symbol)}; it != objectWithDSA.end()) { + return it->second; + } + return std::nullopt; + } + bool withinConstruct{false}; std::int64_t associatedLoopLevel{0}; }; @@ -75,10 +81,19 @@ protected: : std::make_optional<DirContext>(dirContext_.back()); } void PushContext(const parser::CharBlock &source, T dir, Scope &scope) { - dirContext_.emplace_back(source, dir, scope); + if constexpr (std::is_same_v<T, llvm::acc::Directive>) { + dirContext_.emplace_back(source, dir, scope); + if (std::size_t size{dirContext_.size()}; size > 1) { + std::size_t lastIndex{size - 1}; + dirContext_[lastIndex].defaultDSA = + dirContext_[lastIndex - 1].defaultDSA; + } + } else { + dirContext_.emplace_back(source, dir, scope); + } } void PushContext(const parser::CharBlock &source, T dir) { - dirContext_.emplace_back(source, dir, context_.FindScope(source)); + PushContext(source, dir, context_.FindScope(source)); } void PopContext() { dirContext_.pop_back(); } void SetContextDirectiveSource(parser::CharBlock &dir) { @@ -100,9 +115,21 @@ protected: AddToContextObjectWithDSA(symbol, flag, GetContext()); } bool IsObjectWithDSA(const Symbol &symbol) { - auto it{GetContext().objectWithDSA.find(&symbol)}; - return it != GetContext().objectWithDSA.end(); + return GetContext().FindSymbolWithDSA(symbol).has_value(); } + bool IsObjectWithVisibleDSA(const Symbol &symbol) { + for (std::size_t i{dirContext_.size()}; i != 0; i--) { + if (dirContext_[i - 1].FindSymbolWithDSA(symbol).has_value()) { + return true; + } + } + return false; + } + + bool WithinConstruct() { + return !dirContext_.empty() && GetContext().withinConstruct; + } + void SetContextAssociatedLoopLevel(std::int64_t level) { GetContext().associatedLoopLevel = level; } @@ -277,6 +304,12 @@ public: return false; } + bool Pre(const parser::AccClause::Reduction &x) { + const auto &objectList{std::get<parser::AccObjectList>(x.v.t)}; + ResolveAccObjectList(objectList, Symbol::Flag::AccReduction); + return false; + } + void Post(const parser::Name &); private: @@ -384,8 +417,8 @@ public: } void Post(const parser::OmpMetadirectiveDirective &) { PopContext(); } - bool Pre(const parser::OpenMPBlockConstruct &); - void Post(const parser::OpenMPBlockConstruct &); + bool Pre(const parser::OmpBlockConstruct &); + void Post(const parser::OmpBlockConstruct &); void Post(const parser::OmpBeginDirective &x) { GetContext().withinConstruct = true; @@ -531,6 +564,9 @@ public: bool Pre(const parser::OpenMPDeclarativeAllocate &); void Post(const parser::OpenMPDeclarativeAllocate &) { PopContext(); } + bool Pre(const parser::OpenMPAssumeConstruct &); + void Post(const parser::OpenMPAssumeConstruct &) { PopContext(); } + bool Pre(const parser::OpenMPAtomicConstruct &); void Post(const parser::OpenMPAtomicConstruct &) { PopContext(); } @@ -820,7 +856,23 @@ public: const parser::OmpClause *GetAssociatedClause() { return associatedClause; } private: - std::int64_t GetAssociatedLoopLevelFromClauses(const parser::OmpClauseList &); + /// Given a vector of loop levels and a vector of corresponding clauses find + /// the largest loop level and set the associated loop level to the found + /// maximum. This is used for error handling to ensure that the number of + /// affected loops is not larger that the number of available loops. + std::int64_t SetAssociatedMaxClause(llvm::SmallVector<std::int64_t> &, + llvm::SmallVector<const parser::OmpClause *> &); + std::int64_t GetNumAffectedLoopsFromLoopConstruct( + const parser::OpenMPLoopConstruct &); + void CollectNumAffectedLoopsFromLoopConstruct( + const parser::OpenMPLoopConstruct &, llvm::SmallVector<std::int64_t> &, + llvm::SmallVector<const parser::OmpClause *> &); + void CollectNumAffectedLoopsFromInnerLoopContruct( + const parser::OpenMPLoopConstruct &, llvm::SmallVector<std::int64_t> &, + llvm::SmallVector<const parser::OmpClause *> &); + void CollectNumAffectedLoopsFromClauses(const parser::OmpClauseList &, + llvm::SmallVector<std::int64_t> &, + llvm::SmallVector<const parser::OmpClause *> &); Symbol::Flags dataSharingAttributeFlags{Symbol::Flag::OmpShared, Symbol::Flag::OmpPrivate, Symbol::Flag::OmpFirstPrivate, @@ -1570,10 +1622,10 @@ void AccAttributeVisitor::Post(const parser::AccDefaultClause &x) { // and adjust the symbol for each Name if necessary void AccAttributeVisitor::Post(const parser::Name &name) { auto *symbol{name.symbol}; - if (symbol && !dirContext_.empty() && GetContext().withinConstruct) { + if (symbol && WithinConstruct()) { symbol = &symbol->GetUltimate(); if (!symbol->owner().IsDerivedType() && !symbol->has<ProcEntityDetails>() && - !symbol->has<SubprogramDetails>() && !IsObjectWithDSA(*symbol)) { + !symbol->has<SubprogramDetails>() && !IsObjectWithVisibleDSA(*symbol)) { if (Symbol * found{currScope().FindSymbol(name.source)}) { if (symbol != found) { name.symbol = found; // adjust the symbol within region @@ -1723,7 +1775,7 @@ static std::string ScopeSourcePos(const Fortran::semantics::Scope &scope); #endif -bool OmpAttributeVisitor::Pre(const parser::OpenMPBlockConstruct &x) { +bool OmpAttributeVisitor::Pre(const parser::OmpBlockConstruct &x) { const parser::OmpDirectiveSpecification &dirSpec{x.BeginDir()}; llvm::omp::Directive dirId{dirSpec.DirId()}; switch (dirId) { @@ -1740,10 +1792,13 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPBlockConstruct &x) { case llvm::omp::Directive::OMPD_task: case llvm::omp::Directive::OMPD_taskgroup: case llvm::omp::Directive::OMPD_teams: + case llvm::omp::Directive::OMPD_workdistribute: case llvm::omp::Directive::OMPD_workshare: case llvm::omp::Directive::OMPD_parallel_workshare: case llvm::omp::Directive::OMPD_target_teams: + case llvm::omp::Directive::OMPD_target_teams_workdistribute: case llvm::omp::Directive::OMPD_target_parallel: + case llvm::omp::Directive::OMPD_teams_workdistribute: PushContext(dirSpec.source, dirId); break; default: @@ -1759,7 +1814,7 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPBlockConstruct &x) { return true; } -void OmpAttributeVisitor::Post(const parser::OpenMPBlockConstruct &x) { +void OmpAttributeVisitor::Post(const parser::OmpBlockConstruct &x) { const parser::OmpDirectiveSpecification &dirSpec{x.BeginDir()}; llvm::omp::Directive dirId{dirSpec.DirId()}; switch (dirId) { @@ -1773,9 +1828,12 @@ void OmpAttributeVisitor::Post(const parser::OpenMPBlockConstruct &x) { case llvm::omp::Directive::OMPD_target: case llvm::omp::Directive::OMPD_task: case llvm::omp::Directive::OMPD_teams: + case llvm::omp::Directive::OMPD_workdistribute: case llvm::omp::Directive::OMPD_parallel_workshare: case llvm::omp::Directive::OMPD_target_teams: - case llvm::omp::Directive::OMPD_target_parallel: { + case llvm::omp::Directive::OMPD_target_parallel: + case llvm::omp::Directive::OMPD_target_teams_workdistribute: + case llvm::omp::Directive::OMPD_teams_workdistribute: { bool hasPrivate; for (const auto *allocName : allocateNames_) { hasPrivate = false; @@ -1826,7 +1884,6 @@ bool OmpAttributeVisitor::Pre( bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) { const auto &beginLoopDir{std::get<parser::OmpBeginLoopDirective>(x.t)}; const auto &beginDir{std::get<parser::OmpLoopDirective>(beginLoopDir.t)}; - const auto &clauseList{std::get<parser::OmpClauseList>(beginLoopDir.t)}; switch (beginDir.v) { case llvm::omp::Directive::OMPD_distribute: case llvm::omp::Directive::OMPD_distribute_parallel_do: @@ -1877,7 +1934,7 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) { beginDir.v == llvm::omp::Directive::OMPD_target_loop) IssueNonConformanceWarning(beginDir.v, beginDir.source, 52); ClearDataSharingAttributeObjects(); - SetContextAssociatedLoopLevel(GetAssociatedLoopLevelFromClauses(clauseList)); + SetContextAssociatedLoopLevel(GetNumAffectedLoopsFromLoopConstruct(x)); if (beginDir.v == llvm::omp::Directive::OMPD_do) { auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t); @@ -1891,7 +1948,7 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) { } } PrivatizeAssociatedLoopIndexAndCheckLoopLevel(x); - ordCollapseLevel = GetAssociatedLoopLevelFromClauses(clauseList) + 1; + ordCollapseLevel = GetNumAffectedLoopsFromLoopConstruct(x) + 1; return true; } @@ -1950,7 +2007,7 @@ void OmpAttributeVisitor::ResolveSeqLoopIndexInParallelOrTaskConstruct( // till OpenMP-5.0 standard. // In above both cases we skip the privatization of iteration variables. bool OmpAttributeVisitor::Pre(const parser::DoConstruct &x) { - if (!dirContext_.empty() && GetContext().withinConstruct) { + if (WithinConstruct()) { llvm::SmallVector<const parser::Name *> ivs; if (x.IsDoNormal()) { const parser::Name *iv{GetLoopIndex(x)}; @@ -1979,44 +2036,111 @@ bool OmpAttributeVisitor::Pre(const parser::DoConstruct &x) { return true; } -std::int64_t OmpAttributeVisitor::GetAssociatedLoopLevelFromClauses( - const parser::OmpClauseList &x) { - std::int64_t orderedLevel{0}; - std::int64_t collapseLevel{0}; +static bool isSizesClause(const parser::OmpClause *clause) { + return std::holds_alternative<parser::OmpClause::Sizes>(clause->u); +} + +std::int64_t OmpAttributeVisitor::SetAssociatedMaxClause( + llvm::SmallVector<std::int64_t> &levels, + llvm::SmallVector<const parser::OmpClause *> &clauses) { + + // Find the tile level to ensure that the COLLAPSE clause value + // does not exeed the number of tiled loops. + std::int64_t tileLevel = 0; + for (auto [level, clause] : llvm::zip_equal(levels, clauses)) + if (isSizesClause(clause)) + tileLevel = level; + + std::int64_t maxLevel = 1; + const parser::OmpClause *maxClause = nullptr; + for (auto [level, clause] : llvm::zip_equal(levels, clauses)) { + if (tileLevel > 0 && tileLevel < level) { + context_.Say(clause->source, + "The value of the parameter in the COLLAPSE clause must" + " not be larger than the number of the number of tiled loops" + " because collapse currently is limited to independent loop" + " iterations."_err_en_US); + return 1; + } + + if (level > maxLevel) { + maxLevel = level; + maxClause = clause; + } + } + if (maxClause) + SetAssociatedClause(maxClause); + return maxLevel; +} + +std::int64_t OmpAttributeVisitor::GetNumAffectedLoopsFromLoopConstruct( + const parser::OpenMPLoopConstruct &x) { + llvm::SmallVector<std::int64_t> levels; + llvm::SmallVector<const parser::OmpClause *> clauses; + + CollectNumAffectedLoopsFromLoopConstruct(x, levels, clauses); + return SetAssociatedMaxClause(levels, clauses); +} + +void OmpAttributeVisitor::CollectNumAffectedLoopsFromLoopConstruct( + const parser::OpenMPLoopConstruct &x, + llvm::SmallVector<std::int64_t> &levels, + llvm::SmallVector<const parser::OmpClause *> &clauses) { + const auto &beginLoopDir{std::get<parser::OmpBeginLoopDirective>(x.t)}; + const auto &clauseList{std::get<parser::OmpClauseList>(beginLoopDir.t)}; + + CollectNumAffectedLoopsFromClauses(clauseList, levels, clauses); + CollectNumAffectedLoopsFromInnerLoopContruct(x, levels, clauses); +} + +void OmpAttributeVisitor::CollectNumAffectedLoopsFromInnerLoopContruct( + const parser::OpenMPLoopConstruct &x, + llvm::SmallVector<std::int64_t> &levels, + llvm::SmallVector<const parser::OmpClause *> &clauses) { - const parser::OmpClause *ordClause{nullptr}; - const parser::OmpClause *collClause{nullptr}; + const auto &nestedOptional = + std::get<std::optional<parser::NestedConstruct>>(x.t); + assert(nestedOptional.has_value() && + "Expected a DoConstruct or OpenMPLoopConstruct"); + const auto *innerConstruct = + std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>( + &(nestedOptional.value())); + if (innerConstruct) { + CollectNumAffectedLoopsFromLoopConstruct( + innerConstruct->value(), levels, clauses); + } +} + +void OmpAttributeVisitor::CollectNumAffectedLoopsFromClauses( + const parser::OmpClauseList &x, llvm::SmallVector<std::int64_t> &levels, + llvm::SmallVector<const parser::OmpClause *> &clauses) { for (const auto &clause : x.v) { - if (const auto *orderedClause{ + if (const auto oclause{ std::get_if<parser::OmpClause::Ordered>(&clause.u)}) { - if (const auto v{EvaluateInt64(context_, orderedClause->v)}) { - orderedLevel = *v; + std::int64_t level = 0; + if (const auto v{EvaluateInt64(context_, oclause->v)}) { + level = *v; } - ordClause = &clause; + levels.push_back(level); + clauses.push_back(&clause); } - if (const auto *collapseClause{ + + if (const auto cclause{ std::get_if<parser::OmpClause::Collapse>(&clause.u)}) { - if (const auto v{EvaluateInt64(context_, collapseClause->v)}) { - collapseLevel = *v; + std::int64_t level = 0; + if (const auto v{EvaluateInt64(context_, cclause->v)}) { + level = *v; } - collClause = &clause; + levels.push_back(level); + clauses.push_back(&clause); } - } - if (orderedLevel && (!collapseLevel || orderedLevel >= collapseLevel)) { - SetAssociatedClause(ordClause); - return orderedLevel; - } else if (!orderedLevel && collapseLevel) { - SetAssociatedClause(collClause); - return collapseLevel; - } else { - SetAssociatedClause(nullptr); + if (const auto tclause{std::get_if<parser::OmpClause::Sizes>(&clause.u)}) { + levels.push_back(tclause->v.size()); + clauses.push_back(&clause); + } } - // orderedLevel < collapseLevel is an error handled in structural - // checks - - return 1; // default is outermost loop } // 2.15.1.1 Data-sharing Attribute Rules - Predetermined @@ -2048,10 +2172,21 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel( const parser::OmpClause *clause{GetAssociatedClause()}; bool hasCollapseClause{ clause ? (clause->Id() == llvm::omp::OMPC_collapse) : false}; + const parser::OpenMPLoopConstruct *innerMostLoop = &x; + const parser::NestedConstruct *innerMostNest = nullptr; + while (auto &optLoopCons{ + std::get<std::optional<parser::NestedConstruct>>(innerMostLoop->t)}) { + innerMostNest = &(optLoopCons.value()); + if (const auto *innerLoop{ + std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>( + innerMostNest)}) { + innerMostLoop = &(innerLoop->value()); + } else + break; + } - auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t); - if (optLoopCons.has_value()) { - if (const auto &outer{std::get_if<parser::DoConstruct>(&*optLoopCons)}) { + if (innerMostNest) { + if (const auto &outer{std::get_if<parser::DoConstruct>(innerMostNest)}) { for (const parser::DoConstruct *loop{&*outer}; loop && level > 0; --level) { if (loop->IsDoConcurrent()) { @@ -2087,7 +2222,7 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel( CheckAssocLoopLevel(level, GetAssociatedClause()); } else if (const auto &loop{std::get_if< common::Indirection<parser::OpenMPLoopConstruct>>( - &*optLoopCons)}) { + innerMostNest)}) { auto &beginDirective = std::get<parser::OmpBeginLoopDirective>(loop->value().t); auto &beginLoopDirective = @@ -2214,6 +2349,11 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPDeclarativeAllocate &x) { return false; } +bool OmpAttributeVisitor::Pre(const parser::OpenMPAssumeConstruct &x) { + PushContext(x.source, llvm::omp::Directive::OMPD_assume); + return true; +} + bool OmpAttributeVisitor::Pre(const parser::OpenMPAtomicConstruct &x) { PushContext(x.source, llvm::omp::Directive::OMPD_atomic); return true; @@ -2455,7 +2595,7 @@ static bool IsTargetCaptureImplicitlyFirstprivatizeable(const Symbol &symbol, // investigate the flags we can intermix with. if (!(dsa & (dataSharingAttributeFlags | dataMappingAttributeFlags)) .none() || - !checkSym.flags().none() || semantics::IsAssumedShape(checkSym) || + !checkSym.flags().none() || IsAssumedShape(checkSym) || semantics::IsAllocatableOrPointer(checkSym)) { return false; } @@ -2476,14 +2616,15 @@ static bool IsTargetCaptureImplicitlyFirstprivatizeable(const Symbol &symbol, return false; }; - if (checkSymbol(symbol)) { - const auto *hostAssoc{symbol.detailsIf<HostAssocDetails>()}; - if (hostAssoc) { - return checkSymbol(hostAssoc->symbol()); - } - return true; - } - return false; + return common::visit( + common::visitors{ + [&](const UseDetails &x) -> bool { return checkSymbol(x.symbol()); }, + [&](const HostAssocDetails &x) -> bool { + return checkSymbol(x.symbol()); + }, + [&](const auto &) -> bool { return checkSymbol(symbol); }, + }, + symbol.details()); } void OmpAttributeVisitor::CreateImplicitSymbols(const Symbol *symbol) { @@ -2671,7 +2812,7 @@ void OmpAttributeVisitor::CreateImplicitSymbols(const Symbol *symbol) { void OmpAttributeVisitor::Post(const parser::Name &name) { auto *symbol{name.symbol}; - if (symbol && !dirContext_.empty() && GetContext().withinConstruct) { + if (symbol && WithinConstruct()) { if (IsPrivatizable(symbol) && !IsObjectWithDSA(*symbol)) { // TODO: create a separate function to go through the rules for // predetermined, explicitly determined, and implicitly diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp index 5808b4b3cc4f..077bee930675 100644 --- a/flang/lib/Semantics/resolve-names.cpp +++ b/flang/lib/Semantics/resolve-names.cpp @@ -488,6 +488,10 @@ public: // Result symbol Symbol *resultSymbol{nullptr}; bool inFunctionStmt{false}; // true between Pre/Post of FunctionStmt + // Functions with previous implicitly-typed references get those types + // checked against their later definitions. + const DeclTypeSpec *previousImplicitType{nullptr}; + SourceName previousName; }; // Completes the definition of the top function's result. @@ -642,12 +646,18 @@ public: } if (symbol->CanReplaceDetails(details)) { // update the existing symbol - CheckDuplicatedAttrs(name, *symbol, attrs); - SetExplicitAttrs(*symbol, attrs); if constexpr (std::is_same_v<SubprogramDetails, D>) { // Dummy argument defined by explicit interface? details.set_isDummy(IsDummy(*symbol)); + if (symbol->has<ProcEntityDetails>()) { + // Bare "EXTERNAL" dummy replaced with explicit INTERFACE + context().Warn(common::LanguageFeature::RedundantAttribute, name, + "Dummy argument '%s' was declared earlier as EXTERNAL"_warn_en_US, + name); + } } + CheckDuplicatedAttrs(name, *symbol, attrs); + SetExplicitAttrs(*symbol, attrs); symbol->set_details(std::move(details)); return *symbol; } else if constexpr (std::is_same_v<UnknownDetails, D>) { @@ -943,7 +953,7 @@ private: // Edits an existing symbol created for earlier calls to a subprogram or ENTRY // so that it can be replaced by a later definition. bool HandlePreviousCalls(const parser::Name &, Symbol &, Symbol::Flag); - void CheckExtantProc(const parser::Name &, Symbol::Flag); + const Symbol *CheckExtantProc(const parser::Name &, Symbol::Flag); // Create a subprogram symbol in the current scope and push a new scope. Symbol &PushSubprogramScope(const parser::Name &, Symbol::Flag, const parser::LanguageBindingSpec * = nullptr, @@ -1466,7 +1476,7 @@ class OmpVisitor : public virtual DeclarationVisitor { public: void AddOmpSourceRange(const parser::CharBlock &); - static bool NeedsScope(const parser::OpenMPBlockConstruct &); + static bool NeedsScope(const parser::OmpBlockConstruct &); static bool NeedsScope(const parser::OmpClause &); bool Pre(const parser::OmpMetadirectiveDirective &x) { // @@ -1483,8 +1493,8 @@ public: AddOmpSourceRange(x.source); return true; } - bool Pre(const parser::OpenMPBlockConstruct &); - void Post(const parser::OpenMPBlockConstruct &); + bool Pre(const parser::OmpBlockConstruct &); + void Post(const parser::OmpBlockConstruct &); bool Pre(const parser::OmpBeginDirective &x) { AddOmpSourceRange(x.source); // Manually resolve names in CRITICAL directives. This is because these @@ -1729,7 +1739,7 @@ private: const parser::OmpMetadirectiveDirective *metaDirective_{nullptr}; }; -bool OmpVisitor::NeedsScope(const parser::OpenMPBlockConstruct &x) { +bool OmpVisitor::NeedsScope(const parser::OmpBlockConstruct &x) { switch (x.BeginDir().DirId()) { case llvm::omp::Directive::OMPD_master: case llvm::omp::Directive::OMPD_ordered: @@ -1750,14 +1760,14 @@ void OmpVisitor::AddOmpSourceRange(const parser::CharBlock &source) { currScope().AddSourceRange(source); } -bool OmpVisitor::Pre(const parser::OpenMPBlockConstruct &x) { +bool OmpVisitor::Pre(const parser::OmpBlockConstruct &x) { if (NeedsScope(x)) { PushScope(Scope::Kind::OtherConstruct, nullptr); } return true; } -void OmpVisitor::Post(const parser::OpenMPBlockConstruct &x) { +void OmpVisitor::Post(const parser::OmpBlockConstruct &x) { if (NeedsScope(x)) { PopScope(); } @@ -2691,11 +2701,24 @@ void ArraySpecVisitor::PostAttrSpec() { FuncResultStack::~FuncResultStack() { CHECK(stack_.empty()); } +// True when either type is absent, or if they are both present and are +// equivalent for interface compatibility purposes. +static bool TypesMismatchIfNonNull( + const DeclTypeSpec *type1, const DeclTypeSpec *type2) { + if (auto t1{evaluate::DynamicType::From(type1)}) { + if (auto t2{evaluate::DynamicType::From(type2)}) { + return !t1->IsEquivalentTo(*t2); + } + } + return false; +} + void FuncResultStack::CompleteFunctionResultType() { // If the function has a type in the prefix, process it now. FuncInfo *info{Top()}; - if (info && &info->scope == &scopeHandler_.currScope()) { - if (info->parsedType && info->resultSymbol) { + if (info && &info->scope == &scopeHandler_.currScope() && + info->resultSymbol) { + if (info->parsedType) { scopeHandler_.messageHandler().set_currStmtSource(info->source); if (const auto *type{ scopeHandler_.ProcessTypeSpec(*info->parsedType, true)}) { @@ -2712,6 +2735,16 @@ void FuncResultStack::CompleteFunctionResultType() { } info->parsedType = nullptr; } + if (TypesMismatchIfNonNull( + info->resultSymbol->GetType(), info->previousImplicitType)) { + scopeHandler_ + .Say(info->resultSymbol->name(), + "Function '%s' has a result type that differs from the implicit type it obtained in a previous reference"_err_en_US, + info->previousName) + .Attach(info->previousName, + "Previous reference implicitly typed as %s\n"_en_US, + info->previousImplicitType->AsFortran()); + } } } @@ -4761,9 +4794,7 @@ void SubprogramVisitor::Post(const parser::FunctionStmt &stmt) { if (info.resultName && !distinctResultName) { context().Warn(common::UsageWarning::HomonymousResult, info.resultName->source, - "The function name should not appear in RESULT; references to '%s' " - "inside the function will be considered as references to the " - "result only"_warn_en_US, + "The function name should not appear in RESULT; references to '%s' inside the function will be considered as references to the result only"_warn_en_US, name.source); // RESULT name was ignored above, the only side effect from doing so will be // the inability to make recursive calls. The related parser::Name is still @@ -5074,8 +5105,7 @@ bool SubprogramVisitor::BeginSubprogram(const parser::Name &name, if (hasModulePrefix && !currScope().IsModule() && !currScope().IsSubmodule()) { // C1547 Say(name, - "'%s' is a MODULE procedure which must be declared within a " - "MODULE or SUBMODULE"_err_en_US); + "'%s' is a MODULE procedure which must be declared within a MODULE or SUBMODULE"_err_en_US); // Don't return here because it can be useful to have the scope set for // other semantic checks run before we print the errors isValid = false; @@ -5196,9 +5226,10 @@ bool SubprogramVisitor::HandlePreviousCalls( } } -void SubprogramVisitor::CheckExtantProc( +const Symbol *SubprogramVisitor::CheckExtantProc( const parser::Name &name, Symbol::Flag subpFlag) { - if (auto *prev{FindSymbol(name)}) { + Symbol *prev{FindSymbol(name)}; + if (prev) { if (IsDummy(*prev)) { } else if (auto *entity{prev->detailsIf<EntityDetails>()}; IsPointer(*prev) && entity && !entity->type()) { @@ -5210,12 +5241,15 @@ void SubprogramVisitor::CheckExtantProc( SayAlreadyDeclared(name, *prev); } } + return prev; } Symbol &SubprogramVisitor::PushSubprogramScope(const parser::Name &name, Symbol::Flag subpFlag, const parser::LanguageBindingSpec *bindingSpec, bool hasModulePrefix) { Symbol *symbol{GetSpecificFromGeneric(name)}; + const DeclTypeSpec *previousImplicitType{nullptr}; + SourceName previousName; if (!symbol) { if (bindingSpec && currScope().IsGlobal() && std::get<std::optional<parser::ScalarDefaultCharConstantExpr>>( @@ -5228,14 +5262,25 @@ Symbol &SubprogramVisitor::PushSubprogramScope(const parser::Name &name, &MakeSymbol(context().GetTempName(currScope()), Attrs{}, MiscDetails{MiscDetails::Kind::ScopeName})); } - CheckExtantProc(name, subpFlag); + if (const Symbol *previous{CheckExtantProc(name, subpFlag)}) { + if (previous->test(Symbol::Flag::Function) && + previous->test(Symbol::Flag::Implicit)) { + // Function was implicitly typed in previous compilation unit. + previousImplicitType = previous->GetType(); + previousName = previous->name(); + } + } symbol = &MakeSymbol(name, SubprogramDetails{}); } symbol->ReplaceName(name.source); symbol->set(subpFlag); PushScope(Scope::Kind::Subprogram, symbol); if (subpFlag == Symbol::Flag::Function) { - funcResultStack().Push(currScope(), name.source); + auto &funcResultTop{funcResultStack().Push(currScope(), name.source)}; + funcResultTop.previousImplicitType = previousImplicitType; + ; + funcResultTop.previousName = previousName; + ; } if (inInterfaceBlock()) { auto &details{symbol->get<SubprogramDetails>()}; @@ -7913,7 +7958,7 @@ void ConstructVisitor::Post(const parser::AssociateStmt &x) { if (ExtractCoarrayRef(expr)) { // C1103 Say("Selector must not be a coindexed object"_err_en_US); } - if (evaluate::IsAssumedRank(expr)) { + if (IsAssumedRank(expr)) { Say("Selector must not be assumed-rank"_err_en_US); } SetTypeFromAssociation(*symbol); @@ -8669,11 +8714,6 @@ const parser::Name *DeclarationVisitor::ResolveDataRef( x.u); } -static bool TypesMismatchIfNonNull( - const DeclTypeSpec *type1, const DeclTypeSpec *type2) { - return type1 && type2 && *type1 != *type2; -} - // If implicit types are allowed, ensure name is in the symbol table. // Otherwise, report an error if it hasn't been declared. const parser::Name *DeclarationVisitor::ResolveName(const parser::Name &name) { diff --git a/flang/lib/Semantics/rewrite-parse-tree.cpp b/flang/lib/Semantics/rewrite-parse-tree.cpp index b3019762ead1..eae22dc257fa 100644 --- a/flang/lib/Semantics/rewrite-parse-tree.cpp +++ b/flang/lib/Semantics/rewrite-parse-tree.cpp @@ -80,9 +80,9 @@ public: bool Pre(parser::EndSubroutineStmt &) { return false; } bool Pre(parser::EndTypeStmt &) { return false; } - bool Pre(parser::OpenMPBlockConstruct &); + bool Pre(parser::OmpBlockConstruct &); bool Pre(parser::OpenMPLoopConstruct &); - void Post(parser::OpenMPBlockConstruct &); + void Post(parser::OmpBlockConstruct &); void Post(parser::OpenMPLoopConstruct &); private: @@ -187,7 +187,7 @@ void RewriteMutator::OpenMPSimdOnly( continue; } } - } else if (auto *ompBlock{std::get_if<parser::OpenMPBlockConstruct>( + } else if (auto *ompBlock{std::get_if<parser::OmpBlockConstruct>( &omp->value().u)}) { it = replaceInlineBlock(std::get<parser::Block>(ompBlock->t), it); continue; @@ -368,7 +368,7 @@ bool RewriteMutator::Pre(parser::Block &block) { void RewriteMutator::Post(parser::Block &block) { this->Pre(block); } -bool RewriteMutator::Pre(parser::OpenMPBlockConstruct &block) { +bool RewriteMutator::Pre(parser::OmpBlockConstruct &block) { if (context_.langOptions().OpenMPSimd) { auto &innerBlock = std::get<parser::Block>(block.t); OpenMPSimdOnly(innerBlock); @@ -376,7 +376,7 @@ bool RewriteMutator::Pre(parser::OpenMPBlockConstruct &block) { return true; } -void RewriteMutator::Post(parser::OpenMPBlockConstruct &block) { +void RewriteMutator::Post(parser::OmpBlockConstruct &block) { this->Pre(block); } diff --git a/flang/lib/Semantics/runtime-type-info.cpp b/flang/lib/Semantics/runtime-type-info.cpp index 5916a07df774..b8c3db872396 100644 --- a/flang/lib/Semantics/runtime-type-info.cpp +++ b/flang/lib/Semantics/runtime-type-info.cpp @@ -771,6 +771,8 @@ evaluate::StructureConstructor RuntimeTableBuilder::DescribeComponent( auto &foldingContext{context_.foldingContext()}; auto typeAndShape{evaluate::characteristics::TypeAndShape::Characterize( symbol, foldingContext)}; + bool isDevice{object.cudaDataAttr() && + *object.cudaDataAttr() == common::CUDADataAttr::Device}; CHECK(typeAndShape.has_value()); auto dyType{typeAndShape->type()}; int rank{typeAndShape->Rank()}; @@ -883,9 +885,19 @@ evaluate::StructureConstructor RuntimeTableBuilder::DescribeComponent( // Default component initialization bool hasDataInit{false}; if (IsAllocatable(symbol)) { - AddValue(values, componentSchema_, "genre"s, GetEnumValue("allocatable")); + if (isDevice) { + AddValue(values, componentSchema_, "genre"s, + GetEnumValue("allocatabledevice")); + } else { + AddValue(values, componentSchema_, "genre"s, GetEnumValue("allocatable")); + } } else if (IsPointer(symbol)) { - AddValue(values, componentSchema_, "genre"s, GetEnumValue("pointer")); + if (isDevice) { + AddValue( + values, componentSchema_, "genre"s, GetEnumValue("pointerdevice")); + } else { + AddValue(values, componentSchema_, "genre"s, GetEnumValue("pointer")); + } hasDataInit = InitializeDataPointer( values, symbol, object, scope, dtScope, distinctName); } else if (IsAutomatic(symbol)) { diff --git a/flang/lib/Semantics/symbol.cpp b/flang/lib/Semantics/symbol.cpp index 2259cfcf23ec..ea7eeac80a2d 100644 --- a/flang/lib/Semantics/symbol.cpp +++ b/flang/lib/Semantics/symbol.cpp @@ -330,8 +330,14 @@ bool Symbol::CanReplaceDetails(const Details &details) const { common::visitors{ [](const UseErrorDetails &) { return true; }, [&](const ObjectEntityDetails &) { return has<EntityDetails>(); }, - [&](const ProcEntityDetails &) { return has<EntityDetails>(); }, + [&](const ProcEntityDetails &x) { return has<EntityDetails>(); }, [&](const SubprogramDetails &) { + if (const auto *oldProc{detailsIf<ProcEntityDetails>()}) { + // Can replace bare "EXTERNAL dummy" with explicit INTERFACE + return oldProc->isDummy() && !oldProc->procInterface() && + attrs().test(Attr::EXTERNAL) && !test(Flag::Function) && + !test(Flag::Subroutine); + } return has<SubprogramNameDetails>() || has<EntityDetails>(); }, [&](const DerivedTypeDetails &) { @@ -339,14 +345,12 @@ bool Symbol::CanReplaceDetails(const Details &details) const { return derived && derived->isForwardReferenced(); }, [&](const UseDetails &x) { - const auto *use{this->detailsIf<UseDetails>()}; + const auto *use{detailsIf<UseDetails>()}; return use && use->symbol() == x.symbol(); }, - [&](const HostAssocDetails &) { - return this->has<HostAssocDetails>(); - }, + [&](const HostAssocDetails &) { return has<HostAssocDetails>(); }, [&](const UserReductionDetails &) { - return this->has<UserReductionDetails>(); + return has<UserReductionDetails>(); }, [](const auto &) { return false; }, }, @@ -611,7 +615,7 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const Details &details) { sep = ','; } }, - [](const HostAssocDetails &) {}, + [&os](const HostAssocDetails &x) { os << " => " << x.symbol(); }, [&](const ProcBindingDetails &x) { os << " => " << x.symbol().name(); DumpOptional(os, "passName", x.passName()); diff --git a/flang/lib/Semantics/tools.cpp b/flang/lib/Semantics/tools.cpp index 913bf08cd0d9..28829d3eda30 100644 --- a/flang/lib/Semantics/tools.cpp +++ b/flang/lib/Semantics/tools.cpp @@ -705,7 +705,7 @@ SymbolVector FinalsForDerivedTypeInstantiation(const DerivedTypeSpec &spec) { const Symbol *IsFinalizable(const Symbol &symbol, std::set<const DerivedTypeSpec *> *inProgress, bool withImpureFinalizer) { - if (IsPointer(symbol) || evaluate::IsAssumedRank(symbol)) { + if (IsPointer(symbol) || IsAssumedRank(symbol)) { return nullptr; } if (const auto *object{symbol.detailsIf<ObjectEntityDetails>()}) { @@ -741,7 +741,7 @@ const Symbol *IsFinalizable(const DerivedTypeSpec &derived, if (const SubprogramDetails * subp{symbol->detailsIf<SubprogramDetails>()}) { if (const auto &args{subp->dummyArgs()}; !args.empty() && - args.at(0) && !evaluate::IsAssumedRank(*args.at(0)) && + args.at(0) && !IsAssumedRank(*args.at(0)) && args.at(0)->Rank() != *rank) { continue; // not a finalizer for this rank } @@ -790,7 +790,7 @@ const Symbol *HasImpureFinal(const Symbol &original, std::optional<int> rank) { if (symbol.has<ObjectEntityDetails>()) { if (const DeclTypeSpec * symType{symbol.GetType()}) { if (const DerivedTypeSpec * derived{symType->AsDerived()}) { - if (evaluate::IsAssumedRank(symbol)) { + if (IsAssumedRank(symbol)) { // finalizable assumed-rank not allowed (C839) return nullptr; } else { @@ -1170,7 +1170,7 @@ bool IsAccessible(const Symbol &original, const Scope &scope) { } std::optional<parser::MessageFormattedText> CheckAccessibleSymbol( - const Scope &scope, const Symbol &symbol) { + const Scope &scope, const Symbol &symbol, bool inStructureConstructor) { if (IsAccessible(symbol, scope)) { return std::nullopt; } else if (FindModuleFileContaining(scope)) { @@ -1179,10 +1179,20 @@ std::optional<parser::MessageFormattedText> CheckAccessibleSymbol( // whose structure constructors reference private components. return std::nullopt; } else { + const Scope &module{DEREF(FindModuleContaining(symbol.owner()))}; + // Subtlety: Sometimes we want to be able to convert a generated + // module file back into Fortran, perhaps to convert it into a + // hermetic module file. Don't emit a fatal error for things like + // "__builtin_c_ptr(__address=0)" that came from expansions of + // "cptr_null()"; specifically, just warn about structure constructor + // component names from intrinsic modules when in a module. + parser::MessageFixedText text{FindModuleContaining(scope) && + module.parent().IsIntrinsicModules() && + inStructureConstructor && symbol.owner().IsDerivedType() + ? "PRIVATE name '%s' is accessible only within module '%s'"_warn_en_US + : "PRIVATE name '%s' is accessible only within module '%s'"_err_en_US}; return parser::MessageFormattedText{ - "PRIVATE name '%s' is accessible only within module '%s'"_err_en_US, - symbol.name(), - DEREF(FindModuleContaining(symbol.owner())).GetName().value()}; + std::move(text), symbol.name(), module.GetName().value()}; } } diff --git a/flang/lib/Semantics/unparse-with-symbols.cpp b/flang/lib/Semantics/unparse-with-symbols.cpp index b19948113106..ec5b3ffadf30 100644 --- a/flang/lib/Semantics/unparse-with-symbols.cpp +++ b/flang/lib/Semantics/unparse-with-symbols.cpp @@ -37,6 +37,8 @@ public: template <typename T> void Post(const parser::Statement<T> &) { currStmt_ = std::nullopt; } + void Post(const parser::Name &name); + bool Pre(const parser::AccClause &clause) { currStmt_ = clause.source; return true; @@ -57,7 +59,6 @@ public: return true; } void Post(const parser::OpenMPThreadprivate &) { currStmt_ = std::nullopt; } - void Post(const parser::Name &name); bool Pre(const parser::OpenMPDeclareMapperConstruct &x) { currStmt_ = x.source; @@ -67,6 +68,14 @@ public: currStmt_ = std::nullopt; } + bool Pre(const parser::OpenMPDeclareReductionConstruct &x) { + currStmt_ = x.source; + return true; + } + void Post(const parser::OpenMPDeclareReductionConstruct &) { + currStmt_ = std::nullopt; + } + bool Pre(const parser::OpenMPDeclareTargetConstruct &x) { currStmt_ = x.source; return true; @@ -120,6 +129,7 @@ void SymbolDumpVisitor::Indent(llvm::raw_ostream &out, int indent) const { void SymbolDumpVisitor::Post(const parser::Name &name) { if (const auto *symbol{name.symbol}) { if (!symbol->has<MiscDetails>()) { + CHECK(currStmt_.has_value()); symbols_.emplace(currStmt_.value().begin(), symbol); } } |
