diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h --- a/flang/lib/Semantics/check-omp-structure.h +++ b/flang/lib/Semantics/check-omp-structure.h @@ -113,6 +113,8 @@ void Enter(const parser::OmpAtomicCapture &); void Leave(const parser::OmpAtomic &); + void Enter(const parser::UseStmt &); + #define GEN_FLANG_CLAUSE_CHECK_ENTER #include "llvm/Frontend/OpenMP/OMP.inc" diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp --- a/flang/lib/Semantics/check-omp-structure.cpp +++ b/flang/lib/Semantics/check-omp-structure.cpp @@ -1811,6 +1811,22 @@ dirContext_.pop_back(); } +void OmpStructureChecker::Enter(const parser::UseStmt &x) { + semantics::Symbol *symbol{x.moduleName.symbol}; + if (!symbol) { + // Cannot check used module if it wasn't resolved. + return; + } + + auto &details = std::get(symbol->details()); + if (details.has_ompRequires() && deviceConstructFound_) { + context_.Say(x.moduleName.source, + "'%s' module containing device-related REQUIRES directive imported " + "lexically after device construct"_err_en_US, + x.moduleName.ToString()); + } +} + // Clauses // Mainly categorized as // 1. Checks on 'OmpClauseList' from 'parse-tree.h'. diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp --- a/flang/lib/Semantics/mod-file.cpp +++ b/flang/lib/Semantics/mod-file.cpp @@ -57,6 +57,8 @@ static llvm::raw_ostream &PutAttr(llvm::raw_ostream &, Attr); static llvm::raw_ostream &PutType(llvm::raw_ostream &, const DeclTypeSpec &); static llvm::raw_ostream &PutLower(llvm::raw_ostream &, std::string_view); +static llvm::raw_ostream &PutOmpRequires( + llvm::raw_ostream &, const WithOmpDeclarative &); static std::error_code WriteFile( const std::string &, const std::string &, bool = true); static bool FileContentsMatch( @@ -162,6 +164,7 @@ uses_.str().clear(); all << useExtraAttrs_.str(); useExtraAttrs_.str().clear(); + PutOmpRequires(all, details); all << decls_.str(); decls_.str().clear(); auto str{contains_.str()}; @@ -496,6 +499,8 @@ } } os << '\n'; + // print OpenMP requires + PutOmpRequires(os, details); // walk symbols, collect ones needed for interface const Scope &scope{ details.entryScope() ? *details.entryScope() : DEREF(symbol.scope())}; @@ -864,6 +869,44 @@ return os; } +llvm::raw_ostream &PutOmpRequires( + llvm::raw_ostream &os, const WithOmpDeclarative &details) { + if (details.has_ompRequires() || details.has_ompAtomicDefaultMemOrder()) { + os << "!$omp requires"; + if (auto *flags{details.ompRequires()}) { + if (flags->test(WithOmpDeclarative::RequiresFlag::ReverseOffload)) { + os << " reverse_offload"; + } + if (flags->test(WithOmpDeclarative::RequiresFlag::UnifiedAddress)) { + os << " unified_address"; + } + if (flags->test(WithOmpDeclarative::RequiresFlag::UnifiedSharedMemory)) { + os << " unified_shared_memory"; + } + if (flags->test(WithOmpDeclarative::RequiresFlag::DynamicAllocators)) { + os << " dynamic_allocators"; + } + } + if (auto *memOrder{details.ompAtomicDefaultMemOrder()}) { + os << " atomic_default_mem_order("; + switch (*memOrder) { + case parser::OmpAtomicDefaultMemOrderClause::Type::SeqCst: + os << "seq_cst"; + break; + case parser::OmpAtomicDefaultMemOrderClause::Type::AcqRel: + os << "acq_rel"; + break; + case parser::OmpAtomicDefaultMemOrderClause::Type::Relaxed: + os << "relaxed"; + break; + } + os << ')'; + } + os << '\n'; + } + return os; +} + void PutOpenACCDirective(llvm::raw_ostream &os, const Symbol &symbol) { if (symbol.test(Symbol::Flag::AccDeclare)) { os << "!$acc declare "; diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp --- a/flang/lib/Semantics/resolve-directives.cpp +++ b/flang/lib/Semantics/resolve-directives.cpp @@ -64,6 +64,12 @@ dirContext_.emplace_back(source, dir, context_.FindScope(source)); } void PopContext() { dirContext_.pop_back(); } + Scope *GetDeclScope() { + CHECK(!declScope_.empty()); + return declScope_.back(); + } + void PushDeclScope(Scope *scope) { declScope_.push_back(scope); } + void PopDeclScope() { declScope_.pop_back(); } void SetContextDirectiveSource(parser::CharBlock &dir) { GetContext().directiveSource = dir; } @@ -110,6 +116,7 @@ UnorderedSymbolSet dataSharingAttributeObjects_; // on one directive SemanticsContext &context_; std::vector dirContext_; // used as a stack + std::vector declScope_; // used as a stack }; class AccAttributeVisitor : DirectiveAttributeVisitor { @@ -412,6 +419,33 @@ bool Pre(const parser::OpenMPDeclareTargetConstruct &); void Post(const parser::OpenMPDeclareTargetConstruct &) { PopContext(); } + void Post(const parser::UseStmt &x) { + Symbol *symbol{x.moduleName.symbol}; + if (!symbol) { + return; + } + + // Gather information from the imported module's symbol details. + WithOmpDeclarative::RequiresFlags flags; + std::optional memOrder; + common::visit( + [&](auto &details) { + if constexpr (std::is_base_of_v>) { + if (details.has_ompRequires()) { + flags = *details.ompRequires(); + } + if (details.has_ompAtomicDefaultMemOrder()) { + memOrder = *details.ompAtomicDefaultMemOrder(); + } + } + }, + symbol->details()); + + // Merge requires clauses into USE statement's parents. + AddOmpRequiresToScope(GetDeclScope(), flags, memOrder); + } + bool Pre(const parser::OpenMPThreadprivate &); void Post(const parser::OpenMPThreadprivate &) { PopContext(); } @@ -533,6 +567,59 @@ void Post(const parser::Name &); + // Keep track of contexts inside of which `SpecificationPart`s can be found to + // allow matching USE statements with their parent scopes. + bool Pre(const parser::MainProgram &x) { + PushDeclScope(GetScope(context_, x)); + return true; + } + bool Pre(const parser::Module &x) { + PushDeclScope(GetScope(context_, x)); + return true; + } + bool Pre(const parser::Submodule &x) { + PushDeclScope(GetScope(context_, x)); + return true; + } + bool Pre(const parser::FunctionSubprogram &x) { + PushDeclScope(GetScope(context_, x)); + return true; + } + bool Pre(const parser::InterfaceBody::Function &x) { + PushDeclScope(GetScope(context_, x)); + return true; + } + bool Pre(const parser::SubroutineSubprogram &x) { + PushDeclScope(GetScope(context_, x)); + return true; + } + bool Pre(const parser::InterfaceBody::Subroutine &x) { + PushDeclScope(GetScope(context_, x)); + return true; + } + bool Pre(const parser::SeparateModuleSubprogram &x) { + PushDeclScope(GetScope(context_, x)); + return true; + } + bool Pre(const parser::BlockConstruct &x) { + PushDeclScope(GetScope(context_, x)); + return true; + } + bool Pre(const parser::BlockData &x) { + PushDeclScope(GetScope(context_, x)); + return true; + } + void Post(const parser::MainProgram &) { PopDeclScope(); } + void Post(const parser::Module &) { PopDeclScope(); } + void Post(const parser::Submodule &) { PopDeclScope(); } + void Post(const parser::FunctionSubprogram &) { PopDeclScope(); } + void Post(const parser::InterfaceBody::Function &) { PopDeclScope(); } + void Post(const parser::SubroutineSubprogram &) { PopDeclScope(); } + void Post(const parser::InterfaceBody::Subroutine &) { PopDeclScope(); } + void Post(const parser::SeparateModuleSubprogram &) { PopDeclScope(); } + void Post(const parser::BlockConstruct &) { PopDeclScope(); } + void Post(const parser::BlockData &) { PopDeclScope(); } + // Keep track of labels in the statements that causes jumps to target labels void Post(const parser::GotoStmt &gotoStmt) { CheckSourceLabel(gotoStmt.v); } void Post(const parser::ComputedGotoStmt &computedGotoStmt) { @@ -2056,7 +2143,7 @@ // Gather REQUIRES clauses from all non-module top-level program unit symbols, // combine them together ensuring compatibility and apply them to all these - // program units. Modules are skipped because their REQUIRES clauses should be + // program units. Modules are skipped because their REQUIRES clauses are // propagated via USE statements instead. WithOmpDeclarative::RequiresFlags combinedFlags; std::optional combinedMemOrder; diff --git a/flang/lib/Semantics/rewrite-directives.cpp b/flang/lib/Semantics/rewrite-directives.cpp --- a/flang/lib/Semantics/rewrite-directives.cpp +++ b/flang/lib/Semantics/rewrite-directives.cpp @@ -46,6 +46,7 @@ bool Pre(parser::OpenMPAtomicConstruct &); bool Pre(parser::OpenMPRequiresConstruct &); void Post(parser::OmpAtomicDefaultMemOrderClause &); + void Post(parser::UseStmt &); private: parser::CharBlock requiresClauseSource_{nullptr}; @@ -163,6 +164,29 @@ } } +// Check that a module containing a REQUIRES statement with the +// `atomic_default_mem_order` clause is not USEd after an atomic operation +// without memory order defined. +void OmpRewriteMutator::Post(parser::UseStmt &x) { + semantics::Symbol *symbol{x.moduleName.symbol}; + if (!symbol) { + // Cannot check used module if it wasn't resolved. + return; + } + + auto *details = symbol->detailsIf(); + if (atomicDirectiveDefaultOrderFound_ && details && + details->has_ompAtomicDefaultMemOrder()) { + context_.Say(x.moduleName.source, + "'%s' module containing '%s' REQUIRES clause imported lexically after " + "atomic operation without a memory order clause"_err_en_US, + x.moduleName.ToString(), + parser::ToUpperCaseLetters(llvm::omp::getOpenMPClauseName( + llvm::omp::OMPC_atomic_default_mem_order) + .str())); + } +} + bool RewriteOmpParts(SemanticsContext &context, parser::Program &program) { if (!context.IsEnabled(common::LanguageFeature::OpenMP)) { return true; diff --git a/flang/test/Semantics/Inputs/requires_module.f90 b/flang/test/Semantics/Inputs/requires_module.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Semantics/Inputs/requires_module.f90 @@ -0,0 +1,3 @@ +module requires_module + !$omp requires atomic_default_mem_order(seq_cst), unified_shared_memory +end module diff --git a/flang/test/Semantics/OpenMP/requires10.f90 b/flang/test/Semantics/OpenMP/requires10.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Semantics/OpenMP/requires10.f90 @@ -0,0 +1,14 @@ +! RUN: rm -rf %t && mkdir %t +! RUN: %flang_fc1 -fsyntax-only -fopenmp -module-dir %t '%S/../Inputs/requires_module.f90' +! RUN: %python %S/../test_errors.py %s %flang -fopenmp -module-dir %t +! OpenMP Version 5.0 +! 2.4 Requires directive +! All atomic_default_mem_order clauses in REQUIRES directives found within a +! compilation unit must specify the same ordering. Test that this is propagated +! from imported modules + +!ERROR: Conflicting 'ATOMIC_DEFAULT_MEM_ORDER' REQUIRES clauses found in compilation unit +use requires_module +!$omp requires atomic_default_mem_order(relaxed) + +end program diff --git a/flang/test/Semantics/OpenMP/requires11.f90 b/flang/test/Semantics/OpenMP/requires11.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Semantics/OpenMP/requires11.f90 @@ -0,0 +1,17 @@ +! RUN: rm -rf %t && mkdir %t +! RUN: %flang_fc1 -fsyntax-only -fopenmp -module-dir %t '%S/../Inputs/requires_module.f90' +! RUN: %python %S/../test_errors.py %s %flang -fopenmp -module-dir %t +! OpenMP Version 5.0 +! 2.4 Requires directive +! Target-related clauses in REQUIRES directives must come strictly before any +! device constructs, such as declare target with extended list. Test that this +! is propagated from imported modules. + +subroutine f + !$omp declare target (f) +end subroutine f + +program requires + !ERROR: 'requires_module' module containing device-related REQUIRES directive imported lexically after device construct + use requires_module +end program requires diff --git a/flang/test/Semantics/OpenMP/requires12.f90 b/flang/test/Semantics/OpenMP/requires12.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Semantics/OpenMP/requires12.f90 @@ -0,0 +1,19 @@ +! RUN: rm -rf %t && mkdir %t +! RUN: %flang_fc1 -fsyntax-only -fopenmp -module-dir %t '%S/../Inputs/requires_module.f90' +! RUN: %python %S/../test_errors.py %s %flang -fopenmp -module-dir %t +! OpenMP Version 5.0 +! 2.4 Requires directive +! atomic_default_mem_order clauses in REQUIRES directives must come strictly +! before any atomic constructs with no explicit memory order set. Test that this +! is propagated from imported modules. + +subroutine f + integer :: a = 0 + !$omp atomic + a = a + 1 +end subroutine f + +program requires + !ERROR: 'requires_module' module containing 'ATOMIC_DEFAULT_MEM_ORDER' REQUIRES clause imported lexically after atomic operation without a memory order clause + use requires_module +end program requires