[flang-commits] [flang] [llvm] [flang][OpenMP] Add parsing and semantic checks for USES_ALLOCATORS (PR #213955)
Sairudra More via flang-commits
flang-commits at lists.llvm.org
Mon Aug 10 23:08:59 PDT 2026
================
@@ -5388,6 +5390,393 @@ void OmpStructureChecker::Enter(const parser::OmpClause::UseDeviceAddr &x) {
}
}
+static constexpr std::string_view predefinedAllocatorNames[]{
+ "omp_default_mem_alloc", "omp_large_cap_mem_alloc", "omp_const_mem_alloc",
+ "omp_high_bw_mem_alloc", "omp_low_lat_mem_alloc", "omp_cgroup_mem_alloc",
+ "omp_pteam_mem_alloc", "omp_thread_mem_alloc"};
+
+static constexpr std::string_view predefinedMemSpaceNames[]{
+ "omp_default_mem_space", "omp_large_cap_mem_space", "omp_const_mem_space",
+ "omp_high_bw_mem_space", "omp_low_lat_mem_space"};
+
+// omp_null_allocator and omp_null_mem_space are not themselves predefined
+// handles, but [6.0:315-316] gives each its own allowance.
+static constexpr std::string_view nullAllocatorName[]{"omp_null_allocator"};
+static constexpr std::string_view nullMemSpaceName[]{"omp_null_mem_space"};
+
+// Whether the ultimate symbol is an entity of the intrinsic omp_lib module
+// shipped with the compiler, as opposed to a same-named entity of a
+// user-defined module.
+static bool IsIntrinsicOmpLibEntity(const Symbol &ultimate) {
+ const Scope &scope{ultimate.owner()};
+ if (!scope.IsModule()) {
+ return false;
+ }
+ const Symbol *module{scope.symbol()};
+ return module && module->name() == "omp_lib" &&
+ scope.parent().IsIntrinsicModules();
+}
+
+static bool IsIntrinsicOmpAlloctrait(
+ const DerivedTypeSpec &derived, SemanticsContext &context) {
+ const Scope *scope{context.GetBuiltinModule("omp_lib")};
+ const Symbol *symbol{scope ? scope->FindSymbol(SourceName{"omp_alloctrait",
+ std::strlen("omp_alloctrait")})
+ : nullptr};
+ return symbol && IsIntrinsicOmpLibEntity(*symbol) &&
+ &derived.typeSymbol() == &symbol->GetUltimate();
+}
+
+// Recognition of a predefined allocator or memory space differs by version.
+//
+// [5.2:182] asks whether the allocator *is* a predefined allocator, so it
+// identifies the entity: a use-associated rename of the intrinsic omp_lib
+// entity still denotes it, while an unrelated declaration -- even one with the
+// same spelling, the same value, or in a user module named omp_lib -- does not.
+//
+// [6.0:315] instead asks whether the allocator is an identifier that *matches
+// the name of* a predefined allocator, which is a property of the identifier
+// written in the clause, not of the entity it resolves to. A local declaration
+// of a predefined spelling therefore qualifies, while a rename to some other
+// name does not, even when it denotes the intrinsic entity.
+static bool IsPredefinedHandle(const parser::Name &name,
+ llvm::ArrayRef<std::string_view> names, unsigned version) {
+ if (version >= 60) {
+ return llvm::is_contained(names, name.ToString());
+ }
+ if (const Symbol *symbol{name.symbol}) {
+ const Symbol &ultimate{symbol->GetUltimate()};
+ return IsIntrinsicOmpLibEntity(ultimate) &&
+ llvm::is_contained(names, ultimate.name().ToString());
+ }
+ return false;
+}
+
+// The integer kind of an OpenMP allocator or memory-space handle, which
+// omp_lib declares as c_intptr_t. iso_c_binding is an intrinsic module and is
+// read on demand, so the kind is available even when the source uses neither
+// module.
+static std::optional<std::int64_t> GetOmpHandleKind(SemanticsContext &context) {
+ const Scope *scope{context.GetBuiltinModule("iso_c_binding")};
+ if (!scope) {
+ return std::nullopt;
+ }
+ const Symbol *kindSymbol{
+ scope->FindSymbol(SourceName{"c_intptr_t", std::strlen("c_intptr_t")})};
+ if (!kindSymbol) {
+ return std::nullopt;
+ }
+ const auto *object{
+ kindSymbol->GetUltimate().detailsIf<ObjectEntityDetails>()};
+ const auto *init{object ? &object->init() : nullptr};
+ return init && *init ? evaluate::ToInt64(**init) : std::nullopt;
+}
+
+// Whether `symbol` has the integer kind that omp_lib gives its handles. The
+// check is skipped for a non-integer allocator, whose type is diagnosed
+// separately.
+static bool HasOmpHandleKind(
+ const Symbol &symbol, SemanticsContext &context, std::int64_t &expected) {
+ const DeclTypeSpec *type{symbol.GetUltimate().GetType()};
+ if (!type || !type->IsNumeric(TypeCategory::Integer)) {
+ return true;
+ }
+ auto want{GetOmpHandleKind(context)};
+ if (!want) {
+ return true;
+ }
+ expected = *want;
+ auto got{evaluate::ToInt64(type->numericTypeSpec().kind())};
+ return !got || *got == *want;
+}
+
+static bool ClauseHasTargetEffect(
+ llvm::omp::Directive directive, llvm::omp::Clause clause) {
+ llvm::ArrayRef<llvm::omp::Directive> leafs{
+ llvm::omp::getLeafConstructsOrSelf(directive)};
+ if (!llvm::is_contained(leafs, llvm::omp::Directive::OMPD_target)) {
+ return false;
+ }
+ if (leafs.size() == 1) {
+ return true;
+ }
+
+ // Keep this narrow mirror synchronized with the authoritative distribution
+ // rules in llvm/include/llvm/Frontend/OpenMP/ConstructDecompositionT.h.
+ switch (clause) {
+ case llvm::omp::Clause::OMPC_private:
+ // [5.2:340:1-2] Applies to the innermost permitting leaf.
+ return false;
+ case llvm::omp::Clause::OMPC_shared:
+ // [5.2:340:31-32] TARGET is not a permitting leaf.
+ return false;
+ case llvm::omp::Clause::OMPC_firstprivate:
+ // [5.2:340:3-14] Applies to TARGET.
+ return true;
+ case llvm::omp::Clause::OMPC_map:
+ return true;
+ case llvm::omp::Clause::OMPC_lastprivate:
+ // [5.2:340:21-30] Synthesizes TARGET map(tofrom).
+ return true;
+ case llvm::omp::Clause::OMPC_reduction:
+ // [5.2:341:11-13] Synthesizes TARGET map(tofrom).
+ return true;
+ case llvm::omp::Clause::OMPC_linear:
+ // [5.2:341:15-22] Creates outer FIRSTPRIVATE/LASTPRIVATE state.
+ return true;
+ case llvm::omp::Clause::OMPC_in_reduction:
+ case llvm::omp::Clause::OMPC_is_device_ptr:
+ case llvm::omp::Clause::OMPC_has_device_addr:
+ // [5.2:339-341] These clauses are TARGET-owned.
+ return true;
+ default:
+ return true;
+ }
+}
+
+void OmpStructureChecker::CheckUsesAllocatorsSpec(
+ const parser::OmpUsesAllocatorsClause::AllocatorSpec &spec) {
+ unsigned version{context_.langOptions().OpenMPVersion};
+ bool isLegacySyntax{std::get<bool>(spec.t)};
+
+ // The traits of the deprecated syntax are stored as a traits-array modifier,
+ // but they are not the 5.2 modifier, so they must not be version-checked.
+ // A modifier that postdates the OpenMP version in effect is only warned
+ // about, so the specification is accepted as an extension and must still be
+ // checked, otherwise a malformed one would reach lowering unvalidated.
+ if (!isLegacySyntax) {
+ OmpVerifyModifiers(spec, llvm::omp::OMPC_uses_allocators,
+ GetContext().clauseSource, context_);
+ }
+
+ auto &modifiers{OmpGetModifiers(spec)};
+ const auto *memSpace{OmpGetUniqueModifier<parser::OmpMemSpace>(modifiers)};
+ const auto *traits{OmpGetUniqueModifier<parser::OmpTraitsArray>(modifiers)};
+
+ const parser::Expr &allocatorExpr{
+ std::get<parser::ScalarIntExpr>(spec.t).thing.thing.value()};
+ parser::CharBlock allocatorSource{allocatorExpr.source};
+ const parser::Name *allocatorName{
+ parser::Unwrap<parser::Name>(allocatorExpr)};
+
+ // [5.2:182] The allocator expression must be a base language identifier.
+ if (!allocatorName) {
+ context_.Say(allocatorSource,
+ "The allocator in a USES_ALLOCATORS clause must be a base language identifier"_err_en_US);
+ return;
+ }
+
+ bool isPredefined{
+ IsPredefinedHandle(*allocatorName, predefinedAllocatorNames, version)};
+ // [6.0:315] The clause has no effect for an allocator argument value of
+ // omp_null_allocator, and [6.0:316] exempts it from the variable rule. It
+ // has no such allowance before 6.0.
+ bool isNullAllocator{version >= 60 &&
+ IsPredefinedHandle(*allocatorName, nullAllocatorName, version)};
+
+ // [5.2:182] If allocator is a predefined allocator, no modifiers may be
+ // specified. This also covers the pre-5.2 rule that predefined allocators
+ // cannot have traits specified.
+ if (isPredefined && (memSpace || traits)) {
+ context_.Say(allocatorSource,
+ "A predefined allocator '%s' in a USES_ALLOCATORS clause cannot have modifiers or traits specified"_err_en_US,
+ allocatorName->ToString());
+ }
+
+ if (!isPredefined && !isNullAllocator) {
+ // [5.2:182] If allocator is not a predefined allocator, it must be a
+ // variable. Before 6.0 this also rejects omp_null_allocator, which is a
+ // named constant and is not one of the predefined allocators.
+ const Symbol *symbol{allocatorName->symbol};
+ if (!symbol || !IsVariableName(*symbol)) {
+ context_.Say(allocatorSource,
+ "A non-predefined allocator '%s' in a USES_ALLOCATORS clause must be a variable"_err_en_US,
+ allocatorName->ToString());
+ } else if (std::int64_t kind{0};
+ !HasOmpHandleKind(*symbol, context_, kind)) {
+ // [5.2:181], [6.0:315] The allocator argument is an expression of
+ // allocator_handle type.
+ context_.Say(allocatorSource,
+ "The allocator '%s' in a USES_ALLOCATORS clause must be of type INTEGER(KIND=%jd), i.e. OMP_ALLOCATOR_HANDLE_KIND"_err_en_US,
+ allocatorName->ToString(), static_cast<std::intmax_t>(kind));
+ }
+ // [5.0:175], [5.1:203] Non-predefined allocators appearing in a
+ // uses_allocators clause must have traits specified. The requirement was
+ // removed in 5.2, where omitted traits mean an empty traits array.
+ if (version < 52 && !traits) {
+ context_.Say(allocatorSource,
+ "A non-predefined allocator '%s' in a USES_ALLOCATORS clause must have traits specified in OpenMP v%d.%d"_err_en_US,
+ allocatorName->ToString(), version / 10, version % 10);
+ }
+ }
+
+ // [5.2:182] The allocator argument must not appear in other data-sharing
+ // attribute clauses or data-mapping attribute clauses on the same construct.
+ if (const Symbol *symbol{allocatorName->symbol}) {
+ const Symbol &ultimate{symbol->GetUltimate()};
+ const parser::OmpDirectiveSpecification &dirSpec{*dirStack_.back()};
+ for (const parser::OmpClause &clause : dirSpec.Clauses().v) {
+ llvm::omp::Clause id{clause.Id()};
+ if (id == llvm::omp::Clause::OMPC_uses_allocators) {
+ continue;
+ }
+ if (!llvm::omp::isDataSharingAttributeClause(id, version) &&
+ id != llvm::omp::Clause::OMPC_map) {
+ continue;
+ }
+ if (!ClauseHasTargetEffect(GetContext().directive, id)) {
+ continue;
+ }
+ const parser::OmpObjectList *objects{GetOmpObjectList(clause)};
+ if (!objects) {
+ continue;
+ }
+ for (const parser::OmpObject &object : objects->v) {
+ if (const Symbol *other{GetObjectSymbol(object, /*ultimate=*/true)};
+ other == &ultimate) {
+ context_.Say(allocatorSource,
+ "An allocator in a USES_ALLOCATORS clause cannot also appear in the %s clause on the same construct"_err_en_US,
+ parser::ToUpperCaseLetters(llvm::omp::getOpenMPClauseName(id)));
+ break;
+ }
+ }
+ }
+ }
+
+ if (memSpace) {
+ // [5.2:182] The memspace-handle argument for the mem-space modifier must
+ // be an identifier that matches one of the predefined memory space names.
+ // [6.0:315] additionally gives omp_null_mem_space the meaning of
+ // omp_default_mem_space, so it is accepted from 6.0 onwards.
+ const parser::Expr &memSpaceExpr{memSpace->v.thing.thing.value()};
+ parser::CharBlock memSpaceSource{OmpGetModifierSource(modifiers, memSpace)};
+ const parser::Name *memSpaceName{
+ parser::Unwrap<parser::Name>(memSpaceExpr)};
+ bool ok{memSpaceName &&
+ (llvm::is_contained(
+ predefinedMemSpaceNames, memSpaceName->ToString()) ||
+ (version >= 60 &&
+ llvm::is_contained(
+ nullMemSpaceName, memSpaceName->ToString())))};
+ if (!ok) {
+ context_.Say(memSpaceSource,
+ "The MEMSPACE modifier must name a predefined memory space"_err_en_US);
----------------
Saieiei wrote:
Done in 1b14f92b0edd
https://github.com/llvm/llvm-project/pull/213955
More information about the flang-commits
mailing list