[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
Sat Aug 8 22:58:20 PDT 2026


https://github.com/Saieiei updated https://github.com/llvm/llvm-project/pull/213955

>From e4c3da62f875267e027499f4250710cce0ba22d1 Mon Sep 17 00:00:00 2001
From: Sairudra More <sairudra60 at gmail.com>
Date: Tue, 4 Aug 2026 08:43:09 -0500
Subject: [PATCH 1/3] [flang][OpenMP] Add parsing and semantic checks for
 USES_ALLOCATORS

Add parse-tree, parser, unparser, and semantic support for the
USES_ALLOCATORS clause, including allocator traits and version-aware
handling of the deprecated comma-separated list syntax.

In OpenMP 6.0 mode, predefined allocator names are made available by
name without an additional local handle-kind check, matching the
specification's predefined-name rule. Diagnose the unsupported
multiple-specification form and keep lowering explicitly
unimplemented.

Add parser, semantics, version, kind, module, and lowering-NYI tests,
and document the supported syntax boundaries.
---
 flang/docs/OpenMPSupport.md                   |   4 +-
 flang/include/flang/Parser/dump-parse-tree.h  |   5 +
 flang/include/flang/Parser/parse-tree.h       |  39 +++
 .../flang/Semantics/openmp-modifiers.h        |   2 +
 flang/lib/Lower/OpenMP/Clauses.cpp            |  26 +-
 flang/lib/Parser/openmp-parsers.cpp           |  64 ++++
 flang/lib/Parser/unparse.cpp                  |  31 ++
 flang/lib/Semantics/check-omp-loop.cpp        |  10 +-
 flang/lib/Semantics/check-omp-structure.cpp   | 326 ++++++++++++++++++
 flang/lib/Semantics/check-omp-structure.h     |   6 +
 flang/lib/Semantics/openmp-modifiers.cpp      |  33 ++
 .../OpenMP/Todo/target-uses-allocators.f90    |  42 +++
 .../OpenMP/uses-allocators-bad-syntax.f90     |  35 ++
 flang/test/Parser/OpenMP/uses-allocators.f90  | 118 +++++++
 .../OpenMP/uses-allocators-handle-kind.f90    |  32 ++
 .../OpenMP/uses-allocators-no-omp-lib.f90     |  16 +
 .../OpenMP/uses-allocators-user-module.f90    |  29 ++
 .../OpenMP/uses-allocators-version50.f90      |  56 +++
 .../OpenMP/uses-allocators-version51.f90      |  45 +++
 .../OpenMP/uses-allocators-version60.f90      |  99 ++++++
 .../test/Semantics/OpenMP/uses-allocators.f90 | 314 +++++++++++++++++
 llvm/include/llvm/Frontend/OpenMP/OMP.td      |   1 +
 22 files changed, 1327 insertions(+), 6 deletions(-)
 create mode 100644 flang/test/Lower/OpenMP/Todo/target-uses-allocators.f90
 create mode 100644 flang/test/Parser/OpenMP/uses-allocators-bad-syntax.f90
 create mode 100644 flang/test/Parser/OpenMP/uses-allocators.f90
 create mode 100644 flang/test/Semantics/OpenMP/uses-allocators-handle-kind.f90
 create mode 100644 flang/test/Semantics/OpenMP/uses-allocators-no-omp-lib.f90
 create mode 100644 flang/test/Semantics/OpenMP/uses-allocators-user-module.f90
 create mode 100644 flang/test/Semantics/OpenMP/uses-allocators-version50.f90
 create mode 100644 flang/test/Semantics/OpenMP/uses-allocators-version51.f90
 create mode 100644 flang/test/Semantics/OpenMP/uses-allocators-version60.f90
 create mode 100644 flang/test/Semantics/OpenMP/uses-allocators.f90

diff --git a/flang/docs/OpenMPSupport.md b/flang/docs/OpenMPSupport.md
index fd6731fd28a2b..755e6cac7169e 100644
--- a/flang/docs/OpenMPSupport.md
+++ b/flang/docs/OpenMPSupport.md
@@ -124,7 +124,7 @@ Parser/Semantics, MLIR, Lowering, or the OpenMPIRBuilder.
 | allocate directive and allocate clause | <span class="part">partial</span> | | Semantics coverage exists in `flang/test/Semantics/OpenMP/allocate-directive.f90` and `flang/test/Semantics/OpenMP/allocate-clause01.f90`; lowering support expanded, with remaining TODO coverage (`flang/test/Lower/OpenMP/Todo/allocate-clause-align.f90`, `flang/test/Lower/OpenMP/Todo/allocate-clause-allocator.f90`). | [llvm/llvm-project#121356](https://github.com/llvm/llvm-project/pull/121356), [llvm/llvm-project#165719](https://github.com/llvm/llvm-project/pull/165719), [llvm/llvm-project#165865](https://github.com/llvm/llvm-project/pull/165865), [llvm/llvm-project#187167](https://github.com/llvm/llvm-project/pull/187167) |
 | metadirective | <span class="part">partial</span> | | Semantics coverage exists in `flang/test/Semantics/OpenMP/metadirective-construct.f90`; lowering support exists for several construct-selector paths (`flang/test/Lower/OpenMP/metadirective-construct.f90`, `flang/test/Lower/OpenMP/metadirective-nothing.f90`), but some selector/variant paths remain TODO-tracked in lowering. | [llvm/llvm-project#159945](https://github.com/llvm/llvm-project/pull/159945), [llvm/llvm-project#193664](https://github.com/llvm/llvm-project/pull/193664), [llvm/llvm-project#194402](https://github.com/llvm/llvm-project/pull/194402), [llvm/llvm-project#194424](https://github.com/llvm/llvm-project/pull/194424) |
 | support full defaultmap functionality | <span class="part">partial</span> | | Core coverage exists (`flang/test/Lower/OpenMP/defaultmap.f90`, `flang/test/Semantics/OpenMP/defaultmap-clause-v50.f90`), but lowering has known partial paths (for example defaultmap-firstprivate TODO tests). | [llvm/llvm-project#135226](https://github.com/llvm/llvm-project/pull/135226), [llvm/llvm-project#166715](https://github.com/llvm/llvm-project/pull/166715), [llvm/llvm-project#167806](https://github.com/llvm/llvm-project/pull/167806), [llvm/llvm-project#177389](https://github.com/llvm/llvm-project/pull/177389), [llvm/llvm-project#190764](https://github.com/llvm/llvm-project/pull/190764) |
-| clause: uses_allocators | <span class="part">partial</span> | | Parsed/checked in semantics allocator tests, but lowering has explicit TODO handling (`cp.processTODO<...UsesAllocators>` in `flang/lib/Lower/OpenMP/OpenMP.cpp`). | |
+| clause: uses_allocators | <span class="part">partial</span> | | Parsing and semantic checking are supported for the OpenMP 5.2 syntax and for the pre-5.2 comma-separated list syntax, the latter accepted in 5.0/5.1, accepted with a deprecation warning in 5.2, and removed and rejected in 6.0 and later (`flang/test/Parser/OpenMP/uses-allocators.f90`, `flang/test/Semantics/OpenMP/uses-allocators.f90`, `flang/test/Semantics/OpenMP/uses-allocators-version60.f90`); lowering is unimplemented and reports a `not yet implemented` diagnostic (`flang/test/Lower/OpenMP/Todo/target-uses-allocators.f90`). The OpenMP 6.0 form with more than one clause-argument-specification is diagnosed as not yet supported. | |
 | clause: in_reduction | <span class="part">partial</span> | | Semantics and lowering coverage exists for several task/taskgroup/taskloop forms (`flang/test/Semantics/OpenMP/in-reduction.f90`, `flang/test/Lower/OpenMP/task-inreduction.f90`, `flang/test/Lower/OpenMP/taskloop-inreduction.f90`, `flang/test/Lower/OpenMP/taskgroup-task_reduction02.f90`); some target-related forms remain TODO (for example `flang/test/Lower/OpenMP/Todo/target-inreduction.f90`). | [llvm/llvm-project#139704](https://github.com/llvm/llvm-project/pull/139704), [llvm/llvm-project#205124](https://github.com/llvm/llvm-project/pull/205124) |
 | user-defined mappers | <span class="good">done</span> | | Supported with semantics/lowering/transform coverage. | [llvm/llvm-project#140560](https://github.com/llvm/llvm-project/pull/140560), [llvm/llvm-project#163860](https://github.com/llvm/llvm-project/pull/163860), [llvm/llvm-project#167903](https://github.com/llvm/llvm-project/pull/167903), [llvm/llvm-project#179936](https://github.com/llvm/llvm-project/pull/179936), [llvm/llvm-project#189136](https://github.com/llvm/llvm-project/pull/189136) |
 | map array-section with implicit mapper | <span class="part">partial</span> | | Mapper and map coverage exists (`flang/test/Lower/OpenMP/map-mapper.f90`, `flang/test/Lower/OpenMP/target-data-skip-mapper-calls.f90`), with remaining iterator/modifier gaps in lowering (`flang/lib/Lower/OpenMP/ClauseProcessor.cpp` TODOs). | [llvm/llvm-project#175133](https://github.com/llvm/llvm-project/pull/175133), [llvm/llvm-project#177389](https://github.com/llvm/llvm-project/pull/177389) |
@@ -214,7 +214,7 @@ Parser/Semantics, MLIR, Lowering, or the OpenMPIRBuilder.
 | minus operator deprecation handling | <span class="part">partial</span> | | Partially handled in semantic diagnostics. | |
 | linear clause syntax deprecation | <span class="none">unclaimed</span> | | Add semantic deprecation diagnostics with fix-it guidance and tests for accepted/deprecated spellings. | |
 | map clause modifiers without commas (deprecation) | <span class="none">unclaimed</span> | | Add parser/semantics deprecation diagnostics for comma-less map modifiers and tests that verify warning text and accepted replacements. | |
-| uses_allocators list syntax (deprecation) | <span class="none">unclaimed</span> | | Add diagnostics for deprecated uses_allocators list syntax and ensure lowering still handles canonical replacements. | |
+| uses_allocators list syntax (deprecation) | <span class="part">partial</span> | | The comma-separated list syntax is accepted in OpenMP 5.0 and 5.1, accepted with a deprecation/portability warning in 5.2, and removed and rejected in 6.0 and later, where [6.0:B.2] removes all features deprecated in 5.0, 5.1 and 5.2 (`flang/test/Semantics/OpenMP/uses-allocators.f90`, `flang/test/Semantics/OpenMP/uses-allocators-version60.f90`); lowering of the clause is still unimplemented. | |
 | default clause on metadirectives (deprecation) | <span class="none">unclaimed</span> | | Add dedicated metadirective deprecation diagnostics and tests for legacy/default-clause usage. | |
 | destroy clause syntax on depobj (deprecation) | <span class="part">partial</span> | | Deprecation diagnostics are implemented (for example warning coverage in `flang/test/Semantics/OpenMP/depobj-construct-v52.f90`). | |
 | source and sink task-dependence modifiers (deprecation) | <span class="part">partial</span> | | Deprecation diagnostics are implemented (for example warning coverage in `flang/test/Semantics/OpenMP/depobj-construct-v52.f90`). | |
diff --git a/flang/include/flang/Parser/dump-parse-tree.h b/flang/include/flang/Parser/dump-parse-tree.h
index b7d5634dafc2c..dc0fd9effe96e 100644
--- a/flang/include/flang/Parser/dump-parse-tree.h
+++ b/flang/include/flang/Parser/dump-parse-tree.h
@@ -690,6 +690,7 @@ class ParseTreeDumper {
   NODE(parser, OmpMapTypeModifier)
   NODE_ENUM(OmpMapTypeModifier, Value)
   NODE(parser, OmpMatchClause)
+  NODE(parser, OmpMemspaceModifier)
   NODE(parser, OmpMessageClause)
   NODE(parser, OmpMetadirectiveDirective)
   NODE(parser, OmpNoOpenMPClause)
@@ -760,6 +761,7 @@ class ParseTreeDumper {
   NODE_ENUM(OmpThreadsetClause, ThreadsetPolicy)
   NODE(parser, OmpToClause)
   NODE(OmpToClause, Modifier)
+  NODE(parser, OmpTraitsArrayModifier)
   NODE(parser, OmpTraitProperty)
   NODE(parser, OmpTraitPropertyExtension)
   NODE(OmpTraitPropertyExtension, Complex)
@@ -779,6 +781,9 @@ class ParseTreeDumper {
   NODE(parser, OmpUnifiedSharedMemoryClause)
   NODE(parser, OmpUpdateDependObjectsClause)
   NODE(parser, OmpUseClause)
+  NODE(parser, OmpUsesAllocatorsClause)
+  NODE(OmpUsesAllocatorsClause, AllocatorSpec)
+  NODE(OmpUsesAllocatorsClause::AllocatorSpec, Modifier)
   NODE(parser, OmpUtilityDirective)
   NODE(parser, OmpVariableCategory)
   NODE_ENUM(OmpVariableCategory, Value)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 6a21d5d8c79d7..a835f92c5e32e 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -4225,6 +4225,14 @@ struct OmpMapTypeModifier {
   WRAPPER_CLASS_BOILERPLATE(OmpMapTypeModifier, Value);
 };
 
+// Ref: [5.2:181-182]
+//
+// mem-space-modifier ->
+//    MEMSPACE(memspace-handle)                     // since 5.2
+struct OmpMemspaceModifier {
+  WRAPPER_CLASS_BOILERPLATE(OmpMemspaceModifier, ScalarIntExpr);
+};
+
 // Ref: [4.5:56-63], [5.0:101-109], [5.1:126-133], [5.2:252-254]
 //
 // modifier ->
@@ -4363,6 +4371,14 @@ struct OmpTaskDependenceType {
   WRAPPER_CLASS_BOILERPLATE(OmpTaskDependenceType, Value);
 };
 
+// Ref: [5.2:181-182]
+//
+// traits-array-modifier ->
+//    TRAITS(traits-array)                          // since 5.2
+struct OmpTraitsArrayModifier {
+  WRAPPER_CLASS_BOILERPLATE(OmpTraitsArrayModifier, common::Indirection<Expr>);
+};
+
 // Ref: [4.5:229-230], [5.0:324-325], [5.1:357-358], [5.2:161-162]
 //
 // variable-category ->
@@ -5165,6 +5181,29 @@ struct OmpUseClause {
   WRAPPER_CLASS_BOILERPLATE(OmpUseClause, OmpObject);
 };
 
+// Ref: [5.0:170-175], [5.1:197-203], [5.2:181-182]
+//
+// uses-allocators-clause ->
+//    USES_ALLOCATORS(allocator[(traits-array)]
+//        [, allocator[(traits-array)]]...) |       // since 5.0, dep. 5.2
+//    USES_ALLOCATORS([modifier...:] allocator)     // since 5.2
+// modifier ->
+//    mem-space-modifier |
+//    traits-array-modifier                         // since 5.2
+struct OmpUsesAllocatorsClause {
+  struct AllocatorSpec {
+    TUPLE_CLASS_BOILERPLATE(AllocatorSpec);
+    MODIFIER_BOILERPLATE(OmpMemspaceModifier, OmpTraitsArrayModifier);
+    CharBlock source;
+    // The traits of the deprecated "allocator(traits-array)" form are stored
+    // as a traits-array modifier. The flag records which of the two surface
+    // syntaxes was written, which both the unparser and the deprecation
+    // diagnostic need.
+    std::tuple<MODIFIERS(), ScalarIntExpr, /*IsLegacySyntax=*/bool> t;
+  };
+  WRAPPER_CLASS_BOILERPLATE(OmpUsesAllocatorsClause, std::list<AllocatorSpec>);
+};
+
 // OpenMP Clauses
 struct OmpClause {
   UNION_CLASS_BOILERPLATE(OmpClause);
diff --git a/flang/include/flang/Semantics/openmp-modifiers.h b/flang/include/flang/Semantics/openmp-modifiers.h
index 41a4a87f60949..79913fda05af1 100644
--- a/flang/include/flang/Semantics/openmp-modifiers.h
+++ b/flang/include/flang/Semantics/openmp-modifiers.h
@@ -96,6 +96,7 @@ DECLARE_DESCRIPTOR(parser::OmpLowerBound);
 DECLARE_DESCRIPTOR(parser::OmpMapper);
 DECLARE_DESCRIPTOR(parser::OmpMapType);
 DECLARE_DESCRIPTOR(parser::OmpMapTypeModifier);
+DECLARE_DESCRIPTOR(parser::OmpMemspaceModifier);
 DECLARE_DESCRIPTOR(parser::OmpOrderModifier);
 DECLARE_DESCRIPTOR(parser::OmpOrderingModifier);
 DECLARE_DESCRIPTOR(parser::OmpPreferType);
@@ -108,6 +109,7 @@ DECLARE_DESCRIPTOR(parser::OmpSelfModifier);
 DECLARE_DESCRIPTOR(parser::OmpStepComplexModifier);
 DECLARE_DESCRIPTOR(parser::OmpStepSimpleModifier);
 DECLARE_DESCRIPTOR(parser::OmpTaskDependenceType);
+DECLARE_DESCRIPTOR(parser::OmpTraitsArrayModifier);
 DECLARE_DESCRIPTOR(parser::OmpVariableCategory);
 DECLARE_DESCRIPTOR(parser::OmpxHoldModifier);
 
diff --git a/flang/lib/Lower/OpenMP/Clauses.cpp b/flang/lib/Lower/OpenMP/Clauses.cpp
index 0d315c218770a..ac68fbe6adaff 100644
--- a/flang/lib/Lower/OpenMP/Clauses.cpp
+++ b/flang/lib/Lower/OpenMP/Clauses.cpp
@@ -1811,8 +1811,30 @@ UseDevicePtr make(const parser::OmpClause::UseDevicePtr &inp,
 
 UsesAllocators make(const parser::OmpClause::UsesAllocators &inp,
                     semantics::SemanticsContext &semaCtx) {
-  // inp -> empty
-  llvm_unreachable("Empty: uses_allocators");
+  // inp.v -> parser::OmpUsesAllocatorsClause
+  using AllocatorSpec = parser::OmpUsesAllocatorsClause::AllocatorSpec;
+
+  auto makeSpec = [&](const AllocatorSpec &spec) {
+    auto &mods = semantics::OmpGetModifiers(spec);
+    auto *memSpace =
+        semantics::OmpGetUniqueModifier<parser::OmpMemspaceModifier>(mods);
+    auto *traits =
+        semantics::OmpGetUniqueModifier<parser::OmpTraitsArrayModifier>(mods);
+
+    std::optional<UsesAllocators::TraitsArray> traitsArray;
+    if (traits) {
+      // Semantics has verified that the traits array is a named constant.
+      const parser::Name *name{parser::Unwrap<parser::Name>(traits->v.value())};
+      traitsArray = makeObject(DEREF(name), semaCtx);
+    }
+    return UsesAllocators::AllocatorSpec{
+        {/*MemSpace=*/maybeApplyToV(makeExprFn(semaCtx), memSpace),
+         /*TraitsArray=*/std::move(traitsArray),
+         /*Allocator=*/
+         makeExpr(std::get<parser::ScalarIntExpr>(spec.t), semaCtx)}};
+  };
+
+  return UsesAllocators{/*Allocators=*/makeList(inp.v.v, makeSpec)};
 }
 
 // Weak: empty
diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index 7c4e49a673bb0..be9751ba80ba9 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -989,6 +989,9 @@ struct OmpMapTypeModifierParser {
 
 TYPE_PARSER(OmpMapTypeModifierParser{})
 
+TYPE_PARSER(construct<OmpMemspaceModifier>( //
+    "MEMSPACE" >> parenthesized(scalarIntExpr)))
+
 TYPE_PARSER(construct<OmpOrderModifier>(
     "REPRODUCIBLE" >> pure(OmpOrderModifier::Value::Reproducible) ||
     "UNCONSTRAINED" >> pure(OmpOrderModifier::Value::Unconstrained)))
@@ -1044,6 +1047,9 @@ TYPE_PARSER(construct<OmpTaskDependenceType>(
     "MUTEXINOUTSET" >> pure(OmpTaskDependenceType::Value::Mutexinoutset) ||
     "OUT" >> pure(OmpTaskDependenceType::Value::Out)))
 
+TYPE_PARSER(construct<OmpTraitsArrayModifier>( //
+    "TRAITS" >> parenthesized(indirect(expr))))
+
 TYPE_PARSER(construct<OmpVariableCategory>(
     "AGGREGATE" >> pure(OmpVariableCategory::Value::Aggregate) ||
     "ALL"_id >> pure(OmpVariableCategory::Value::All) ||
@@ -1193,6 +1199,12 @@ TYPE_PARSER(sourced(construct<OmpTaskReductionClause::Modifier>(
 TYPE_PARSER(sourced(
     construct<OmpThreadLimitClause::Modifier>(Parser<OmpDimsModifier>{})))
 
+TYPE_PARSER(sourced(construct<OmpUsesAllocatorsClause::AllocatorSpec::Modifier>(
+    sourced(construct<OmpUsesAllocatorsClause::AllocatorSpec::Modifier>(
+                Parser<OmpMemspaceModifier>{}) ||
+        construct<OmpUsesAllocatorsClause::AllocatorSpec::Modifier>(
+            Parser<OmpTraitsArrayModifier>{})))))
+
 TYPE_PARSER(sourced(construct<OmpWhenClause::Modifier>( //
     Parser<OmpContextSelector>{})))
 
@@ -1554,6 +1566,55 @@ TYPE_PARSER(construct<OmpSeverityClause>(
 TYPE_PARSER(construct<OmpMessageClause>(expr))
 
 TYPE_PARSER(construct<OmpHoldsClause>(indirect(expr)))
+
+// The deprecated "allocator[(traits-array)]" form is normalized into the 5.2
+// representation so that both surface syntaxes share one parse tree shape.
+static OmpUsesAllocatorsClause::AllocatorSpec makeLegacyAllocatorSpec(
+    Name &&name, common::Indirection<Expr> &&traits) {
+  using AllocatorSpec = OmpUsesAllocatorsClause::AllocatorSpec;
+  CharBlock traitsSource{traits.value().source};
+  AllocatorSpec::Modifier mod{OmpTraitsArrayModifier{std::move(traits)}};
+  mod.source = traitsSource;
+  std::list<AllocatorSpec::Modifier> mods;
+  mods.emplace_back(std::move(mod));
+
+  CharBlock allocatorSource{name.source};
+  Expr allocator{Designator{DataRef{std::move(name)}}};
+  allocator.source = allocatorSource;
+  return AllocatorSpec{std::move(mods),
+      ScalarIntExpr{IntExpr{common::Indirection<Expr>{std::move(allocator)}}},
+      /*IsLegacySyntax=*/true};
+}
+
+static OmpUsesAllocatorsClause::AllocatorSpec makeCanonicalAllocatorSpec(
+    std::list<OmpUsesAllocatorsClause::AllocatorSpec::Modifier> &&mods,
+    ScalarIntExpr &&allocator) {
+  return OmpUsesAllocatorsClause::AllocatorSpec{
+      std::move(mods), std::move(allocator), /*IsLegacySyntax=*/false};
+}
+
+// Parse "modifier...: allocator" first: a modifier list is followed by a
+// colon, which neither of the other two forms can contain.
+TYPE_PARSER(sourced( //
+    applyFunction<OmpUsesAllocatorsClause::AllocatorSpec>(
+        makeCanonicalAllocatorSpec,
+        nonemptyList(
+            Parser<OmpUsesAllocatorsClause::AllocatorSpec::Modifier>{}) /
+            ":",
+        scalarIntExpr) ||
+    // Parse the deprecated "allocator(traits-array)" before a bare allocator,
+    // because it is also syntactically an array element reference.
+    applyFunction<OmpUsesAllocatorsClause::AllocatorSpec>(
+        makeLegacyAllocatorSpec, Parser<Name>{},
+        parenthesized(indirect(expr))) ||
+    construct<OmpUsesAllocatorsClause::AllocatorSpec>(
+        pure<std::optional<
+            std::list<OmpUsesAllocatorsClause::AllocatorSpec::Modifier>>>(),
+        scalarIntExpr, pure(false))))
+
+TYPE_PARSER(construct<OmpUsesAllocatorsClause>(
+    nonemptyList(Parser<OmpUsesAllocatorsClause::AllocatorSpec>{})))
+
 TYPE_PARSER(construct<OmpAbsentClause>(many(maybe(","_tok) >>
     construct<llvm::omp::Directive>(unwrap(OmpDirectiveNameParser{})))))
 TYPE_PARSER(construct<OmpContainsClause>(many(maybe(","_tok) >>
@@ -1777,6 +1838,9 @@ TYPE_PARSER( //
     "USE_DEVICE_ADDR" >>
         construct<OmpClause>(construct<OmpClause::UseDeviceAddr>(
             parenthesized(Parser<OmpObjectList>{}))) ||
+    "USES_ALLOCATORS" >>
+        construct<OmpClause>(construct<OmpClause::UsesAllocators>(
+            parenthesized(Parser<OmpUsesAllocatorsClause>{}))) ||
     "UNIFIED_ADDRESS" >>
         construct<OmpClause>(construct<OmpClause::UnifiedAddress>(
             maybe(parenthesized(scalarLogicalConstantExpr)))) ||
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index cde24b91fd246..816b91cf1628f 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -2636,6 +2636,37 @@ class UnparseVisitor {
     Walk(std::get<std::optional<std::list<Modifier>>>(x.t), ": ");
     Walk(std::get<OmpObjectList>(x.t));
   }
+  void Unparse(const OmpMemspaceModifier &x) {
+    Word("MEMSPACE(");
+    Walk(x.v);
+    Put(")");
+  }
+  void Unparse(const OmpTraitsArrayModifier &x) {
+    Word("TRAITS(");
+    Walk(x.v);
+    Put(")");
+  }
+  void Unparse(const OmpUsesAllocatorsClause &x) { Walk(x.v, ", "); }
+  void Unparse(const OmpUsesAllocatorsClause::AllocatorSpec &x) {
+    using Modifier = OmpUsesAllocatorsClause::AllocatorSpec::Modifier;
+    const auto &modifiers{std::get<std::optional<std::list<Modifier>>>(x.t)};
+    if (std::get<bool>(x.t)) {
+      // Unparse using the deprecated pre-5.2 syntax.
+      Walk(std::get<ScalarIntExpr>(x.t));
+      if (modifiers) {
+        for (const Modifier &m : *modifiers) {
+          if (auto *traits{std::get_if<OmpTraitsArrayModifier>(&m.u)}) {
+            Put("(");
+            Walk(traits->v);
+            Put(")");
+          }
+        }
+      }
+    } else {
+      Walk(modifiers, ": ");
+      Walk(std::get<ScalarIntExpr>(x.t));
+    }
+  }
   void Unparse(const OmpTraitPropertyExtension::Complex &x) {
     using PropList = std::list<common::Indirection<OmpTraitPropertyExtension>>;
     Walk(std::get<OmpTraitPropertyName>(x.t));
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 65d097b5a31f7..e0c221a2ffa32 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -472,8 +472,14 @@ void OmpStructureChecker::CheckIterationVariables(
   for (const parser::OmpClause &clause : spec.Clauses().v) {
     llvm::omp::Clause clauseId{clause.Id()};
     if (llvm::omp::isDataSharingAttributeClause(clauseId, version)) {
-      for (const parser::OmpObject &object :
-          parser::omp::GetOmpObjectList(clause)->v) {
+      // Not every data-sharing attribute clause takes an object list, e.g.
+      // USES_ALLOCATORS takes allocator specifications instead.
+      const parser::OmpObjectList *objects{
+          parser::omp::GetOmpObjectList(clause)};
+      if (!objects) {
+        continue;
+      }
+      for (const parser::OmpObject &object : objects->v) {
         if (const Symbol *symbol{GetObjectSymbol(object, /*ultimate=*/true)}) {
           auto maybeSource{parser::omp::GetObjectSource(object)};
           assert(maybeSource && "Expecting object source");
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 7bd5f1720fb3c..880f9faf07754 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -5388,6 +5388,332 @@ 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. `IsIntrinsicModules()` on the module scope's parent is
+// the provenance test that mod-file.cpp and expression.cpp already use.
+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();
+}
+
+// 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, so it
+// can be read on demand the way semantics.cpp reads its other builtin modules;
+// the kind is therefore 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, so that the two do not double up.
+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;
+}
+
+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.
+  if (!isLegacySyntax) {
+    if (!OmpVerifyModifiers(spec, llvm::omp::OMPC_uses_allocators,
+            GetContext().clauseSource, context_)) {
+      return;
+    }
+  }
+
+  auto &modifiers{OmpGetModifiers(spec)};
+  const auto *memSpace{
+      OmpGetUniqueModifier<parser::OmpMemspaceModifier>(modifiers)};
+  const auto *traits{
+      OmpGetUniqueModifier<parser::OmpTraitsArrayModifier>(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}) {
+    static const llvm::omp::Clause conflictingClauses[]{
+        llvm::omp::Clause::OMPC_firstprivate,
+        llvm::omp::Clause::OMPC_has_device_addr,
+        llvm::omp::Clause::OMPC_is_device_ptr,
+        llvm::omp::Clause::OMPC_map,
+        llvm::omp::Clause::OMPC_private,
+    };
+    const parser::OmpDirectiveSpecification &dirSpec{*dirStack_.back()};
+    for (llvm::omp::Clause id : conflictingClauses) {
+      const parser::OmpClause *found{parser::omp::FindClause(dirSpec, id)};
+      if (!found) {
+        continue;
+      }
+      if (const parser::OmpObjectList *objects{GetOmpObjectList(*found)}) {
+        for (const parser::OmpObject &object : objects->v) {
+          if (const Symbol *other{GetObjectSymbol(object, /*ultimate=*/true)};
+              other == &symbol->GetUltimate()) {
+            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)));
+          }
+        }
+      }
+    }
+  }
+
+  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 &&
+        (IsPredefinedHandle(*memSpaceName, predefinedMemSpaceNames, version) ||
+            (version >= 60 &&
+                IsPredefinedHandle(*memSpaceName, nullMemSpaceName, version)))};
+    if (!ok) {
+      context_.Say(memSpaceSource,
+          "The MEMSPACE modifier must name a predefined memory space"_err_en_US);
+    }
+  }
+
+  if (traits) {
+    CheckUsesAllocatorsTraits(*traits,
+        isLegacySyntax ? allocatorSource
+                       : OmpGetModifierSource(modifiers, traits));
+  }
+}
+
+void OmpStructureChecker::CheckUsesAllocatorsTraits(
+    const parser::OmpTraitsArrayModifier &traits, parser::CharBlock source) {
+  const parser::Expr &expr{traits.v.value()};
+  parser::CharBlock traitsSource{source.empty() ? expr.source : source};
+  const SomeExpr *value{GetExpr(context_, expr)};
+  if (!value) {
+    return;
+  }
+
+  const parser::Name *name{parser::Unwrap<parser::Name>(expr)};
+  const Symbol *symbol{name ? name->symbol : nullptr};
+
+  // [5.2:182] publishes one restriction for both languages: the traits
+  // argument must be a constant array, have constant values, and be defined in
+  // the same scope as the construct. [6.0:317] later splits that per language,
+  // keeping the same-scope requirement for C/C++ and stating the Fortran rule
+  // as a named constant of rank one. Follow the 6.0 clarification, so an
+  // otherwise valid host- or use-associated named constant is accepted.
+  if (!symbol) {
+    context_.Say(traitsSource,
+        "The traits array must be a named constant array"_err_en_US);
+    return;
+  }
+  if (value->Rank() != 1) {
+    context_.Say(traitsSource,
+        "The traits array '%s' must be a rank-one array"_err_en_US,
+        name->ToString());
+  }
+  if (!IsNamedConstant(symbol->GetUltimate()) ||
+      !evaluate::IsConstantExpr(*value)) {
+    context_.Say(traitsSource,
+        "The traits array '%s' must be a constant array with constant values"_err_en_US,
+        name->ToString());
+  }
+  const DeclTypeSpec *type{symbol->GetUltimate().GetType()};
+  const DerivedTypeSpec *derived{type ? type->AsDerived() : nullptr};
+  if (!derived || derived->name().ToString() != "omp_alloctrait") {
+    context_.Say(traitsSource,
+        "The traits array '%s' must be of type OMP_ALLOCTRAIT"_err_en_US,
+        name->ToString());
+  }
+}
+
+void OmpStructureChecker::Enter(const parser::OmpClause::UsesAllocators &x) {
+  // If the clause is not allowed, don't diagnose specific problems with it.
+  if (!IsAllowedClause(llvm::omp::Clause::OMPC_uses_allocators)) {
+    return;
+  }
+
+  unsigned version{context_.langOptions().OpenMPVersion};
+  const std::list<parser::OmpUsesAllocatorsClause::AllocatorSpec> &specs{x.v.v};
+
+  // Classify by the syntax each specification was written in, which the parse
+  // tree records, rather than by whether a modifier happens to be present.
+  bool anyLegacyItem{false}, anyModifierItem{false};
+  for (auto &spec : specs) {
+    if (std::get<bool>(spec.t)) {
+      anyLegacyItem = true;
+    } else if (OmpGetModifiers(spec)) {
+      anyModifierItem = true;
+    }
+  }
+  // Only the deprecated grammar separates specifications with commas, so any
+  // parseable list of them is that grammar. A comma list whose items use the
+  // 5.2 modifier syntax is expressible in neither grammar; it is treated as an
+  // approximation of the 6.0 multiple-specification form.
+  bool isLegacyList{anyLegacyItem || (specs.size() > 1 && !anyModifierItem)};
+
+  if (version >= 60 && isLegacyList) {
+    // [6.0:B.2] "All features deprecated in versions 5.0, 5.1 and 5.2 were
+    // removed", which includes the comma-separated list syntax deprecated by
+    // [5.2:181].
+    context_.Say(GetContext().clauseSource,
+        "The comma-separated list syntax for the USES_ALLOCATORS clause was deprecated in OpenMP 5.2 and removed in OpenMP 6.0, use 'USES_ALLOCATORS([TRAITS(traits):] allocator)' instead"_err_en_US);
+    return;
+  }
+
+  if (specs.size() > 1 && version >= 60) {
+    // [6.0:315] permits more than one clause-argument-specification, but only
+    // the single-specification form is implemented so far.
+    context_.Say(GetContext().clauseSource,
+        "Multiple allocator specifications in a USES_ALLOCATORS clause are not yet supported"_err_en_US);
+    return;
+  }
+
+  if (specs.size() > 1 && anyModifierItem) {
+    // [5.2:181] uses_allocators takes a single clause-argument-specification,
+    // and only the deprecated list syntax may repeat it.
+    context_.Say(GetContext().clauseSource,
+        "The USES_ALLOCATORS clause accepts a single allocator specification in OpenMP v%d.%d"_err_en_US,
+        version / 10, version % 10);
+  } else if (version >= 52 && isLegacyList) {
+    // [5.2:181] The comma-separated "allocator[(traits)]" list syntax has been
+    // deprecated.
+    context_.Say(GetContext().clauseSource,
+        "The comma-separated list syntax for the USES_ALLOCATORS clause has been deprecated in OpenMP 5.2, use 'USES_ALLOCATORS([TRAITS(traits):] allocator)' instead"_port_en_US);
+  }
+
+  for (auto &spec : specs) {
+    CheckUsesAllocatorsSpec(spec);
+  }
+}
+
 void OmpStructureChecker::Enter(const parser::OmpClause::IsDevicePtr &x) {
   SymbolSourceMap currSymbols;
   GetSymbolsInObjectList(x.v, currSymbols);
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 4acec2ad05ba1..53d47eaacda24 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -241,6 +241,7 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   void Enter(const parser::OmpClause::UpdateDependObjects &x);
   void Enter(const parser::OmpClause::UseDeviceAddr &x);
   void Enter(const parser::OmpClause::UseDevicePtr &x);
+  void Enter(const parser::OmpClause::UsesAllocators &x);
   void Enter(const parser::OmpClause::When &x);
 
 private:
@@ -424,6 +425,11 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
       const parser::OmpAllocateDirective &x, bool isExecutable);
   void CheckExecutableAllocateDirective(const parser::OmpAllocateDirective &x);
 
+  void CheckUsesAllocatorsSpec(
+      const parser::OmpUsesAllocatorsClause::AllocatorSpec &spec);
+  void CheckUsesAllocatorsTraits(
+      const parser::OmpTraitsArrayModifier &traits, parser::CharBlock source);
+
   void CheckIteratorRange(const parser::OmpIteratorSpecifier &x);
   void CheckIteratorModifier(const parser::OmpIterator &x);
 
diff --git a/flang/lib/Semantics/openmp-modifiers.cpp b/flang/lib/Semantics/openmp-modifiers.cpp
index 293136d3649ad..d4a2bddc2c3c6 100644
--- a/flang/lib/Semantics/openmp-modifiers.cpp
+++ b/flang/lib/Semantics/openmp-modifiers.cpp
@@ -556,6 +556,22 @@ const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpMapTypeModifier>() {
   return desc;
 }
 
+template <>
+const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpMemspaceModifier>() {
+  static const OmpModifierDescriptor desc{
+      /*name=*/"mem-space-modifier",
+      /*props=*/
+      {
+          {52, {OmpProperty::Unique}},
+      },
+      /*clauses=*/
+      {
+          {52, {Clause::OMPC_uses_allocators}},
+      },
+  };
+  return desc;
+}
+
 template <>
 const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpOrderModifier>() {
   static const OmpModifierDescriptor desc{
@@ -756,6 +772,23 @@ const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpTaskDependenceType>() {
   return desc;
 }
 
+template <>
+const OmpModifierDescriptor &
+OmpGetDescriptor<parser::OmpTraitsArrayModifier>() {
+  static const OmpModifierDescriptor desc{
+      /*name=*/"traits-array-modifier",
+      /*props=*/
+      {
+          {52, {OmpProperty::Unique}},
+      },
+      /*clauses=*/
+      {
+          {52, {Clause::OMPC_uses_allocators}},
+      },
+  };
+  return desc;
+}
+
 template <>
 const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpVariableCategory>() {
   static const OmpModifierDescriptor desc{
diff --git a/flang/test/Lower/OpenMP/Todo/target-uses-allocators.f90 b/flang/test/Lower/OpenMP/Todo/target-uses-allocators.f90
new file mode 100644
index 0000000000000..fbe2e9efe9ac3
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/target-uses-allocators.f90
@@ -0,0 +1,42 @@
+! REQUIRES: openmp_runtime
+! RUN: %not_todo_cmd %flang_fc1 -cpp -DBARE -emit-llvm %openmp_flags -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -cpp -DTRAITS -emit-llvm %openmp_flags -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -cpp -DMEMSPACE -emit-llvm %openmp_flags -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -cpp -DMEMSPACE_TRAITS -emit-llvm %openmp_flags -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -cpp -DTRAITS_MEMSPACE -emit-llvm %openmp_flags -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -cpp -DLEGACY -emit-llvm %openmp_flags -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+! Every shape that semantics accepts must reach the established "not yet
+! implemented" lowering boundary for USES_ALLOCATORS, rather than aborting
+! while the clause is converted for lowering. Each shape needs its own
+! compilation because lowering stops at the first unimplemented clause.
+
+! CHECK: not yet implemented: USES_ALLOCATORS clause is not implemented yet
+program p
+  use omp_lib
+  integer(omp_allocator_handle_kind) :: my_alloc
+  type(omp_alloctrait), parameter :: tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+  integer :: x
+
+#ifdef BARE
+  !$omp target uses_allocators(my_alloc)
+#endif
+#ifdef TRAITS
+  !$omp target uses_allocators(traits(tr): my_alloc)
+#endif
+#ifdef MEMSPACE
+  !$omp target uses_allocators(memspace(omp_high_bw_mem_space): my_alloc)
+#endif
+#ifdef MEMSPACE_TRAITS
+  !$omp target uses_allocators(memspace(omp_const_mem_space), traits(tr): my_alloc)
+#endif
+#ifdef TRAITS_MEMSPACE
+  !$omp target uses_allocators(traits(tr), memspace(omp_const_mem_space): my_alloc)
+#endif
+#ifdef LEGACY
+  !$omp target uses_allocators(my_alloc(tr))
+#endif
+  x = 1
+  !$omp end target
+end program p
diff --git a/flang/test/Parser/OpenMP/uses-allocators-bad-syntax.f90 b/flang/test/Parser/OpenMP/uses-allocators-bad-syntax.f90
new file mode 100644
index 0000000000000..71317861883fb
--- /dev/null
+++ b/flang/test/Parser/OpenMP/uses-allocators-bad-syntax.f90
@@ -0,0 +1,35 @@
+! REQUIRES: openmp_runtime
+
+! RUN: %python %S/../../Semantics/test_errors.py %s %flang_fc1 %openmp_flags -fopenmp-version=52
+! RUN: %python %S/../../Semantics/test_errors.py %s %flang_fc1 %openmp_flags -fopenmp-version=60
+
+! Forms that the USES_ALLOCATORS parser does not accept.
+!
+! The first construct is a valid clause carrying no error annotation. It is the
+! reason this test cannot pass vacuously: a compiler without USES_ALLOCATORS
+! parser support does not parse it either, and the resulting unexpected
+! diagnostic makes the harness fail rather than silently agree.
+
+subroutine uses_allocators_bad_syntax
+  use omp_lib
+  integer(omp_allocator_handle_kind) :: a, b
+  integer :: x
+
+  ! Sentinel: the canonical OpenMP 5.2 form parses.
+  !$omp target uses_allocators(a)
+  x = 1
+  !$omp end target
+
+  ! The clause takes at least one allocator specification.
+  !ERROR: expected '='
+  !$omp target uses_allocators()
+  x = 2
+  !$omp end target
+
+  ! The OpenMP 6.0 semicolon-separated form for more than one
+  ! clause-argument-specification is not implemented.
+  !ERROR: expected ':'
+  !$omp target uses_allocators(a; b)
+  x = 3
+  !$omp end target
+end subroutine
diff --git a/flang/test/Parser/OpenMP/uses-allocators.f90 b/flang/test/Parser/OpenMP/uses-allocators.f90
new file mode 100644
index 0000000000000..42ac11512e9bf
--- /dev/null
+++ b/flang/test/Parser/OpenMP/uses-allocators.f90
@@ -0,0 +1,118 @@
+! REQUIRES: openmp_runtime
+
+! RUN: %flang_fc1 %openmp_flags -fopenmp-version=52 -fdebug-unparse-no-sema %s | FileCheck %s --check-prefix=UNPARSE
+! RUN: %flang_fc1 %openmp_flags -fopenmp-version=52 -fdebug-dump-parse-tree %s | FileCheck %s --check-prefix=PARSE-TREE
+
+! Both surface syntaxes of the USES_ALLOCATORS clause must round-trip through
+! the unparser, and the deprecated form must remain distinguishable from the
+! OpenMP 5.2 form in the parse tree.
+
+subroutine uses_allocators_syntax
+  use omp_lib
+  integer(omp_allocator_handle_kind) :: a, b
+  type(omp_alloctrait), parameter :: tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+  integer :: x
+
+  !$omp target uses_allocators(omp_default_mem_alloc)
+  x = 1
+  !$omp end target
+
+  !$omp target uses_allocators(traits(tr): a)
+  x = 2
+  !$omp end target
+
+  !$omp target uses_allocators(memspace(omp_default_mem_space): a)
+  x = 3
+  !$omp end target
+
+  !$omp target uses_allocators(memspace(omp_low_lat_mem_space), traits(tr): a)
+  x = 4
+  !$omp end target
+
+  !$omp target uses_allocators(traits(tr), memspace(omp_low_lat_mem_space): a)
+  x = 5
+  !$omp end target
+
+  !$omp target uses_allocators(a(tr))
+  x = 6
+  !$omp end target
+
+  !$omp target uses_allocators(a(tr), b(tr))
+  x = 7
+  !$omp end target
+end subroutine
+
+!UNPARSE: SUBROUTINE uses_allocators_syntax
+!UNPARSE: !$OMP TARGET  USES_ALLOCATORS(omp_default_mem_alloc)
+!UNPARSE: !$OMP TARGET  USES_ALLOCATORS(TRAITS(tr): a)
+!UNPARSE: !$OMP TARGET  USES_ALLOCATORS(MEMSPACE(omp_default_mem_space): a)
+!UNPARSE: !$OMP TARGET  USES_ALLOCATORS(MEMSPACE(omp_low_lat_mem_space), TRAITS(tr): a)
+!UNPARSE: !$OMP TARGET  USES_ALLOCATORS(TRAITS(tr), MEMSPACE(omp_low_lat_mem_space): a)
+!UNPARSE: !$OMP TARGET  USES_ALLOCATORS(a(tr))
+!UNPARSE: !$OMP TARGET  USES_ALLOCATORS(a(tr), b(tr))
+
+! A bare allocator is the canonical 5.2 form: no modifier is recorded, and the
+! specification is not flagged as the legacy syntax.
+!PARSE-TREE: OmpClause -> UsesAllocators -> OmpUsesAllocatorsClause -> AllocatorSpec
+!PARSE-TREE-NEXT: Scalar -> Integer -> Expr = '1_8'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'omp_default_mem_alloc'
+!PARSE-TREE-NEXT: bool = 'false'
+
+! TRAITS(tr): a
+!PARSE-TREE: OmpClause -> UsesAllocators -> OmpUsesAllocatorsClause -> AllocatorSpec
+!PARSE-TREE-NEXT: Modifier -> OmpTraitsArrayModifier -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'tr'
+!PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'a'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'a'
+!PARSE-TREE-NEXT: bool = 'false'
+
+! MEMSPACE(omp_default_mem_space): a
+!PARSE-TREE: OmpClause -> UsesAllocators -> OmpUsesAllocatorsClause -> AllocatorSpec
+!PARSE-TREE-NEXT: Modifier -> OmpMemspaceModifier -> Scalar -> Integer -> Expr = '99_8'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'omp_default_mem_space'
+!PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'a'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'a'
+!PARSE-TREE-NEXT: bool = 'false'
+
+! Both modifiers, in either order.
+!PARSE-TREE: OmpClause -> UsesAllocators -> OmpUsesAllocatorsClause -> AllocatorSpec
+!PARSE-TREE-NEXT: Modifier -> OmpMemspaceModifier -> Scalar -> Integer -> Expr = '4_8'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'omp_low_lat_mem_space'
+!PARSE-TREE-NEXT: Modifier -> OmpTraitsArrayModifier -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'tr'
+!PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'a'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'a'
+!PARSE-TREE-NEXT: bool = 'false'
+
+!PARSE-TREE: OmpClause -> UsesAllocators -> OmpUsesAllocatorsClause -> AllocatorSpec
+!PARSE-TREE-NEXT: Modifier -> OmpTraitsArrayModifier -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'tr'
+!PARSE-TREE-NEXT: Modifier -> OmpMemspaceModifier -> Scalar -> Integer -> Expr = '4_8'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'omp_low_lat_mem_space'
+!PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'a'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'a'
+!PARSE-TREE-NEXT: bool = 'false'
+
+! The deprecated "allocator(traits)" form is stored as a traits modifier, and
+! is flagged so that it unparses back to the syntax that was written.
+!PARSE-TREE: OmpClause -> UsesAllocators -> OmpUsesAllocatorsClause -> AllocatorSpec
+!PARSE-TREE-NEXT: Modifier -> OmpTraitsArrayModifier -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'tr'
+!PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'a'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'a'
+!PARSE-TREE-NEXT: bool = 'true'
+
+! The deprecated syntax can list more than one allocator specification.
+!PARSE-TREE: OmpClause -> UsesAllocators -> OmpUsesAllocatorsClause -> AllocatorSpec
+!PARSE-TREE-NEXT: Modifier -> OmpTraitsArrayModifier -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'tr'
+!PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'a'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'a'
+!PARSE-TREE-NEXT: bool = 'true'
+!PARSE-TREE-NEXT: AllocatorSpec
+!PARSE-TREE-NEXT: Modifier -> OmpTraitsArrayModifier -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'tr'
+!PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'b'
+!PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'b'
+!PARSE-TREE-NEXT: bool = 'true'
diff --git a/flang/test/Semantics/OpenMP/uses-allocators-handle-kind.f90 b/flang/test/Semantics/OpenMP/uses-allocators-handle-kind.f90
new file mode 100644
index 0000000000000..b9c6a764c67f8
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/uses-allocators-handle-kind.f90
@@ -0,0 +1,32 @@
+! RUN: not %flang_fc1 -fopenmp -fopenmp-version=50 -fsyntax-only %s 2>&1 | FileCheck %s
+! RUN: not %flang_fc1 -fopenmp -fopenmp-version=51 -fsyntax-only %s 2>&1 | FileCheck %s
+! RUN: not %flang_fc1 -fopenmp -fopenmp-version=52 -fsyntax-only %s 2>&1 | FileCheck %s
+! RUN: not %flang_fc1 -fopenmp -fopenmp-version=60 -fsyntax-only %s 2>&1 | FileCheck %s
+
+! [5.2:181], [6.0:315] The allocator argument of USES_ALLOCATORS is an
+! expression of allocator_handle type, which omp_lib declares as c_intptr_t.
+! The rule holds whether or not the source uses omp_lib, so none of these
+! subroutines imports it. The expected kind is matched as a number rather than
+! spelled out, because it is the target's C_INTPTR_T kind.
+
+! CHECK: error: The allocator 'wrong_kind_alloc' in a USES_ALLOCATORS clause must be of type INTEGER(KIND={{[0-9]+}}), i.e. OMP_ALLOCATOR_HANDLE_KIND
+subroutine uses_allocators_wrong_handle_kind
+  ! Deliberately a kind that cannot be C_INTPTR_T on any supported target.
+  integer(kind=2) :: wrong_kind_alloc
+  integer :: x
+  !$omp target uses_allocators(wrong_kind_alloc)
+  x = 1
+  !$omp end target
+end subroutine
+
+! A REAL allocator is diagnosed for its type; the kind rule must not pile a
+! second diagnostic onto the same allocator.
+! CHECK: error: Must have INTEGER type, but is REAL(4)
+! CHECK-NOT: 'not_an_integer' in a USES_ALLOCATORS clause must be of type
+subroutine uses_allocators_real_allocator
+  real :: not_an_integer
+  integer :: x
+  !$omp target uses_allocators(not_an_integer)
+  x = 1
+  !$omp end target
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/uses-allocators-no-omp-lib.f90 b/flang/test/Semantics/OpenMP/uses-allocators-no-omp-lib.f90
new file mode 100644
index 0000000000000..a420ba83badbd
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/uses-allocators-no-omp-lib.f90
@@ -0,0 +1,16 @@
+! RUN: %python %S/../test_errors.py %s %flang_fc1 -fopenmp -fopenmp-version=52
+! RUN: %python %S/../test_errors.py %s %flang_fc1 -fopenmp -fopenmp-version=60
+
+! The allocator_handle kind is the target's C_INTPTR_T, so a correctly typed
+! allocator is accepted without importing omp_lib. This file deliberately does
+! not USE omp_lib and so needs no OpenMP runtime modules.
+
+subroutine uses_allocators_without_omp_lib
+  use iso_c_binding, only: c_intptr_t
+  integer(c_intptr_t) :: my_alloc
+  integer :: x
+
+  !$omp target uses_allocators(my_alloc)
+  x = 1
+  !$omp end target
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/uses-allocators-user-module.f90 b/flang/test/Semantics/OpenMP/uses-allocators-user-module.f90
new file mode 100644
index 0000000000000..55dbb130f385f
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/uses-allocators-user-module.f90
@@ -0,0 +1,29 @@
+! RUN: not %flang_fc1 -fopenmp -fopenmp-version=50 -fsyntax-only %s 2>&1 | FileCheck %s
+! RUN: not %flang_fc1 -fopenmp -fopenmp-version=51 -fsyntax-only %s 2>&1 | FileCheck %s
+! RUN: not %flang_fc1 -fopenmp -fopenmp-version=52 -fsyntax-only %s 2>&1 | FileCheck %s
+
+! Before OpenMP 6.0, [5.2:182] asks whether the allocator *is* a predefined
+! allocator, so predefined treatment belongs to the entity of the intrinsic
+! omp_lib module. A user-defined module named omp_lib does not confer it, even
+! when its named constant has the right name and the right handle kind.
+!
+! This module shadows the intrinsic omp_lib for the whole file, so it is kept
+! in a test of its own rather than contaminating tests that need the real
+! intrinsic module.
+
+! CHECK: error: A non-predefined allocator 'omp_const_mem_alloc' in a USES_ALLOCATORS clause must be a variable
+
+module omp_lib
+  use iso_c_binding, only: c_intptr_t
+  integer, parameter :: omp_allocator_handle_kind = c_intptr_t
+  integer(omp_allocator_handle_kind), parameter :: omp_const_mem_alloc = 4242
+end module
+
+subroutine uses_allocators_user_omp_lib
+  use omp_lib
+  integer :: x
+
+  !$omp target uses_allocators(omp_const_mem_alloc)
+  x = 1
+  !$omp end target
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/uses-allocators-version50.f90 b/flang/test/Semantics/OpenMP/uses-allocators-version50.f90
new file mode 100644
index 0000000000000..a8491fa764f81
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/uses-allocators-version50.f90
@@ -0,0 +1,56 @@
+! REQUIRES: openmp_runtime
+
+! RUN: %python %S/../test_errors.py %s %flang_fc1 %openmp_flags -fopenmp-version=50
+
+! In OpenMP 5.0 the USES_ALLOCATORS clause only accepts the
+! "allocator[(traits-array)]" list syntax, and [5.0:175] requires a
+! non-predefined allocator to specify traits.
+
+subroutine uses_allocators_50
+  use omp_lib
+  integer(omp_allocator_handle_kind) :: my_alloc, other_alloc
+  type(omp_alloctrait), parameter :: tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+  integer :: x
+
+  ! The list syntax is the only syntax in 5.0, so it is not diagnosed.
+  !$omp target uses_allocators(my_alloc(tr), other_alloc(tr))
+  x = 1
+  !$omp end target
+
+  !$omp target uses_allocators(omp_default_mem_alloc)
+  x = 2
+  !$omp end target
+
+  !ERROR: A non-predefined allocator 'my_alloc' in a USES_ALLOCATORS clause must have traits specified in OpenMP v5.0
+  !$omp target uses_allocators(my_alloc)
+  x = 3
+  !$omp end target
+
+  !WARNING: 'traits-array-modifier' modifier is not supported in OpenMP v5.0, try -fopenmp-version=52
+  !$omp target uses_allocators(traits(tr): my_alloc)
+  x = 4
+  !$omp end target
+
+  !WARNING: 'mem-space-modifier' modifier is not supported in OpenMP v5.0, try -fopenmp-version=52
+  !$omp target uses_allocators(memspace(omp_default_mem_space): my_alloc)
+  x = 5
+  !$omp end target
+
+  !ERROR: A non-predefined allocator 'omp_null_allocator' in a USES_ALLOCATORS clause must be a variable
+  !$omp target uses_allocators(omp_null_allocator(tr))
+  x = 6
+  !$omp end target
+end subroutine
+
+subroutine uses_allocators_50_rename
+  use omp_lib, only: renamed_predef => omp_const_mem_alloc
+  integer :: x
+
+  ! Predefined recognition follows the entity before 6.0, so a rename of the
+  ! intrinsic omp_lib allocator is still predefined and needs no traits. The
+  ! same rename is rejected at 6.0; see uses-allocators-version60.f90.
+  !$omp target uses_allocators(renamed_predef)
+  x = 1
+  !$omp end target
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/uses-allocators-version51.f90 b/flang/test/Semantics/OpenMP/uses-allocators-version51.f90
new file mode 100644
index 0000000000000..921eb273df6a0
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/uses-allocators-version51.f90
@@ -0,0 +1,45 @@
+! REQUIRES: openmp_runtime
+
+! RUN: %python %S/../test_errors.py %s %flang_fc1 %openmp_flags -fopenmp-version=51
+
+! OpenMP 5.1 keeps the pre-5.2 USES_ALLOCATORS syntax and the [5.1:203]
+! requirement that a non-predefined allocator specifies traits.
+
+subroutine uses_allocators_51
+  use omp_lib
+  integer(omp_allocator_handle_kind) :: my_alloc, other_alloc
+  type(omp_alloctrait), parameter :: tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+  integer :: x
+
+  !$omp target uses_allocators(my_alloc(tr), other_alloc(tr))
+  x = 1
+  !$omp end target
+
+  !ERROR: A non-predefined allocator 'my_alloc' in a USES_ALLOCATORS clause must have traits specified in OpenMP v5.1
+  !$omp target uses_allocators(my_alloc)
+  x = 2
+  !$omp end target
+
+  !WARNING: 'traits-array-modifier' modifier is not supported in OpenMP v5.1, try -fopenmp-version=52
+  !$omp target uses_allocators(traits(tr): my_alloc)
+  x = 3
+  !$omp end target
+
+  !ERROR: A predefined allocator 'omp_default_mem_alloc' in a USES_ALLOCATORS clause cannot have modifiers or traits specified
+  !$omp target uses_allocators(omp_default_mem_alloc(tr))
+  x = 4
+  !$omp end target
+end subroutine
+
+subroutine uses_allocators_51_rename
+  use omp_lib, only: renamed_predef => omp_const_mem_alloc
+  integer :: x
+
+  ! Predefined recognition follows the entity before 6.0, so a rename of the
+  ! intrinsic omp_lib allocator is still predefined and needs no traits. The
+  ! same rename is rejected at 6.0; see uses-allocators-version60.f90.
+  !$omp target uses_allocators(renamed_predef)
+  x = 1
+  !$omp end target
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/uses-allocators-version60.f90 b/flang/test/Semantics/OpenMP/uses-allocators-version60.f90
new file mode 100644
index 0000000000000..9b64f53be8a8f
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/uses-allocators-version60.f90
@@ -0,0 +1,99 @@
+! REQUIRES: openmp_runtime
+
+! RUN: %python %S/../test_errors.py %s %flang_fc1 %openmp_flags -fopenmp-version=60
+
+! OpenMP Version 6.0, Section 8.8: uses_allocators clause.
+! 6.0 restates the allocator rule in terms of the *name* of a predefined
+! allocator, gives omp_null_allocator and omp_null_mem_space their own
+! allowances, and permits more than one clause-argument-specification.
+! [6.0:B.2] also removes every feature deprecated in 5.0, 5.1 and 5.2, which
+! includes the comma-separated list syntax deprecated by [5.2:181].
+
+subroutine uses_allocators_v60_null
+  use omp_lib
+  integer(omp_allocator_handle_kind) :: my_alloc
+  integer :: x
+
+  ! [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.
+  !$omp target uses_allocators(omp_null_allocator)
+  x = 1
+  !$omp end target
+
+  ! [6.0:315] omp_null_mem_space means omp_default_mem_space here, so it is
+  ! accepted even though it is not one of the five predefined memory spaces.
+  !$omp target uses_allocators(memspace(omp_null_mem_space): my_alloc)
+  x = 2
+  !$omp end target
+end subroutine
+
+subroutine uses_allocators_v60_name_matching
+  use omp_lib, only: omp_allocator_handle_kind
+  ! A local named constant that shadows a predefined allocator's spelling.
+  integer(omp_allocator_handle_kind), parameter :: omp_const_mem_alloc = 999
+  integer :: x
+
+  ! [6.0:315] matches the *name* of a predefined allocator, so this shadowing
+  ! declaration is treated as predefined and needs no variable. Before 6.0 the
+  ! same program is rejected; see uses-allocators.f90.
+  !$omp target uses_allocators(omp_const_mem_alloc)
+  x = 1
+  !$omp end target
+end subroutine
+
+subroutine uses_allocators_v60_rename
+  use omp_lib, only: renamed => omp_const_mem_alloc
+  integer :: x
+
+  ! [6.0:315] asks whether the identifier in the clause matches the name of a
+  ! predefined allocator. 'renamed' does not, so it is a non-predefined
+  ! allocator, and being a named constant it violates the variable rule. The
+  ! same rename is accepted at 5.0-5.2, where the entity is what matters; see
+  ! uses-allocators.f90.
+  !ERROR: A non-predefined allocator 'renamed' in a USES_ALLOCATORS clause must be a variable
+  !$omp target uses_allocators(renamed)
+  x = 1
+  !$omp end target
+end subroutine
+
+subroutine uses_allocators_v60_legacy_removed
+  use omp_lib
+  integer(omp_allocator_handle_kind) :: my_alloc, other_alloc
+  type(omp_alloctrait), parameter :: tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+  integer :: x
+
+  ! [6.0:B.2] The comma-separated list syntax was deprecated in 5.2 and is
+  ! removed in 6.0.
+  !ERROR: The comma-separated list syntax for the USES_ALLOCATORS clause was deprecated in OpenMP 5.2 and removed in OpenMP 6.0, use 'USES_ALLOCATORS([TRAITS(traits):] allocator)' instead
+  !$omp target uses_allocators(my_alloc, other_alloc)
+  x = 1
+  !$omp end target
+
+  !ERROR: The comma-separated list syntax for the USES_ALLOCATORS clause was deprecated in OpenMP 5.2 and removed in OpenMP 6.0, use 'USES_ALLOCATORS([TRAITS(traits):] allocator)' instead
+  !$omp target uses_allocators(my_alloc(tr))
+  x = 2
+  !$omp end target
+end subroutine
+
+subroutine uses_allocators_v60_multiple
+  use omp_lib
+  integer(omp_allocator_handle_kind) :: my_alloc, other_alloc
+  type(omp_alloctrait), parameter :: tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+  integer :: x
+
+  ! [6.0:315] permits more than one clause-argument-specification. The
+  ! semicolon-separated grammar is not parsed at all (see
+  ! flang/test/Parser/OpenMP/uses-allocators-bad-syntax.f90); this shape is
+  ! neither grammar, and reaching it reports the implementation gap.
+  !ERROR: Multiple allocator specifications in a USES_ALLOCATORS clause are not yet supported
+  !$omp target uses_allocators(traits(tr): my_alloc, traits(tr): other_alloc)
+  x = 1
+  !$omp end target
+
+  ! A single specification is still accepted.
+  !$omp target uses_allocators(traits(tr): my_alloc)
+  x = 2
+  !$omp end target
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/uses-allocators.f90 b/flang/test/Semantics/OpenMP/uses-allocators.f90
new file mode 100644
index 0000000000000..1bbb09d16062c
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/uses-allocators.f90
@@ -0,0 +1,314 @@
+! REQUIRES: openmp_runtime
+
+! RUN: %python %S/../test_errors.py %s %flang_fc1 %openmp_flags -fopenmp-version=52
+
+! OpenMP Version 5.2, Section 6.8: uses_allocators clause.
+! Each negative case is paired with the nearest valid case so that the checks
+! cannot reject conforming code.
+
+module uses_allocators_traits_module
+  use omp_lib
+  type(omp_alloctrait), parameter :: module_tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+end module
+
+subroutine uses_allocators_ok
+  use omp_lib
+  integer(omp_allocator_handle_kind) :: my_alloc
+  type(omp_alloctrait), parameter :: tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+  integer :: x
+
+  ! A predefined allocator without modifiers.
+  !$omp target uses_allocators(omp_default_mem_alloc)
+  x = 1
+  !$omp end target
+
+  ! Since 5.2, a non-predefined allocator may omit its traits.
+  !$omp target uses_allocators(my_alloc)
+  x = 2
+  !$omp end target
+
+  !$omp target uses_allocators(traits(tr): my_alloc)
+  x = 3
+  !$omp end target
+
+  !$omp target uses_allocators(memspace(omp_high_bw_mem_space): my_alloc)
+  x = 4
+  !$omp end target
+
+  !$omp target uses_allocators(memspace(omp_const_mem_space), traits(tr): my_alloc)
+  x = 5
+  !$omp end target
+
+  ! The clause is repeatable on TARGET.
+  !$omp target uses_allocators(my_alloc) uses_allocators(omp_const_mem_alloc)
+  x = 6
+  !$omp end target
+
+  ! The clause reaches the TARGET leaf of a combined construct.
+  !$omp target teams uses_allocators(traits(tr): my_alloc)
+  x = 7
+  !$omp end target teams
+end subroutine
+
+subroutine uses_allocators_loop_construct
+  use omp_lib
+  integer(omp_allocator_handle_kind) :: my_alloc
+  type(omp_alloctrait), parameter :: tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+  integer :: i, x(10)
+
+  ! USES_ALLOCATORS is a data-sharing attribute clause that carries allocator
+  ! specifications rather than an object list, so the loop-construct checks
+  ! must not treat it as an object-list clause.
+  !$omp target teams distribute parallel do uses_allocators(traits(tr): my_alloc)
+  do i = 1, 10
+    x(i) = i
+  end do
+
+  !$omp target simd uses_allocators(my_alloc)
+  do i = 1, 10
+    x(i) = i
+  end do
+end subroutine
+
+subroutine uses_allocators_errors
+  use omp_lib
+  integer(omp_allocator_handle_kind) :: my_alloc, other_alloc
+  integer(omp_memspace_handle_kind) :: my_space
+  type(omp_alloctrait), parameter :: tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+  type(omp_alloctrait) :: nonconst(1)
+  type(omp_alloctrait), parameter :: tr2(1,1) = &
+      reshape([omp_alloctrait(omp_atk_alignment, 64)], [1,1])
+  real, parameter :: notatrait(1) = [1.0]
+  real :: notaninteger
+  integer :: x
+
+  ! The allocator must be a base language identifier.
+  !ERROR: The allocator in a USES_ALLOCATORS clause must be a base language identifier
+  !$omp target uses_allocators(traits(tr): my_alloc + 1)
+  x = 1
+  !$omp end target
+
+  ! A predefined allocator cannot have modifiers.
+  !ERROR: A predefined allocator 'omp_default_mem_alloc' in a USES_ALLOCATORS clause cannot have modifiers or traits specified
+  !$omp target uses_allocators(traits(tr): omp_default_mem_alloc)
+  x = 2
+  !$omp end target
+
+  !ERROR: A predefined allocator 'omp_const_mem_alloc' in a USES_ALLOCATORS clause cannot have modifiers or traits specified
+  !$omp target uses_allocators(memspace(omp_const_mem_space): omp_const_mem_alloc)
+  x = 3
+  !$omp end target
+
+  ! omp_null_allocator is a named constant and is not a predefined allocator.
+  !ERROR: A non-predefined allocator 'omp_null_allocator' in a USES_ALLOCATORS clause must be a variable
+  !$omp target uses_allocators(omp_null_allocator)
+  x = 4
+  !$omp end target
+
+  ! The allocator must be a scalar integer.
+  !ERROR: Must have INTEGER type, but is REAL(4)
+  !$omp target uses_allocators(notaninteger)
+  x = 5
+  !$omp end target
+
+  ! The memspace-handle must name a predefined memory space.
+  !ERROR: The MEMSPACE modifier must name a predefined memory space
+  !$omp target uses_allocators(memspace(omp_null_mem_space): my_alloc)
+  x = 6
+  !$omp end target
+
+  !ERROR: The MEMSPACE modifier must name a predefined memory space
+  !$omp target uses_allocators(memspace(my_space): my_alloc)
+  x = 7
+  !$omp end target
+
+  ! The traits array must be a constant array of OMP_ALLOCTRAIT.
+  !ERROR: The traits array 'nonconst' must be a constant array with constant values
+  !$omp target uses_allocators(traits(nonconst): my_alloc)
+  x = 8
+  !$omp end target
+
+  !ERROR: The traits array 'tr2' must be a rank-one array
+  !$omp target uses_allocators(traits(tr2): my_alloc)
+  x = 9
+  !$omp end target
+
+  !ERROR: The traits array 'notatrait' must be of type OMP_ALLOCTRAIT
+  !$omp target uses_allocators(traits(notatrait): my_alloc)
+  x = 10
+  !$omp end target
+
+  ! The allocator must not appear in a data-sharing or data-mapping clause.
+  !ERROR: An allocator in a USES_ALLOCATORS clause cannot also appear in the PRIVATE clause on the same construct
+  !ERROR: Variable 'my_alloc' may not appear on both MAP and PRIVATE clauses on a TARGET construct
+  !$omp target uses_allocators(traits(tr): my_alloc) private(my_alloc)
+  x = 11
+  !$omp end target
+
+  !ERROR: An allocator in a USES_ALLOCATORS clause cannot also appear in the MAP clause on the same construct
+  !$omp target map(tofrom: my_alloc) uses_allocators(traits(tr): my_alloc)
+  x = 12
+  !$omp end target
+
+  ! A modifier may appear at most once in one allocator specification.
+  !ERROR: 'traits-array-modifier' modifier cannot occur multiple times
+  !$omp target uses_allocators(traits(tr), traits(tr): my_alloc)
+  x = 13
+  !$omp end target
+
+  !ERROR: 'mem-space-modifier' modifier cannot occur multiple times
+  !$omp target uses_allocators(memspace(omp_const_mem_space), memspace(omp_high_bw_mem_space): my_alloc)
+  x = 131
+  !$omp end target
+
+  ! OpenMP 5.2 allows a single allocator specification.
+  !ERROR: The USES_ALLOCATORS clause accepts a single allocator specification in OpenMP v5.2
+  !$omp target uses_allocators(traits(tr): my_alloc, traits(tr): other_alloc)
+  x = 14
+  !$omp end target
+
+  ! The clause is not allowed on a non-TARGET construct.
+  !ERROR: USES_ALLOCATORS clause is not allowed on PARALLEL directive
+  !$omp parallel uses_allocators(my_alloc)
+  x = 15
+  !$omp end parallel
+end subroutine
+
+subroutine uses_allocators_deprecated
+  use omp_lib
+  integer(omp_allocator_handle_kind) :: my_alloc, other_alloc
+  type(omp_alloctrait), parameter :: tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+  integer :: x
+
+  ! [5.2:181] The "allocator[(traits)]" list syntax is deprecated, but it is
+  ! still accepted.
+  !PORTABILITY: The comma-separated list syntax for the USES_ALLOCATORS clause has been deprecated in OpenMP 5.2, use 'USES_ALLOCATORS([TRAITS(traits):] allocator)' instead
+  !$omp target uses_allocators(my_alloc(tr))
+  x = 1
+  !$omp end target
+
+  !PORTABILITY: The comma-separated list syntax for the USES_ALLOCATORS clause has been deprecated in OpenMP 5.2, use 'USES_ALLOCATORS([TRAITS(traits):] allocator)' instead
+  !$omp target uses_allocators(my_alloc(tr), other_alloc(tr))
+  x = 2
+  !$omp end target
+
+  ! A bare comma list is also the deprecated syntax.
+  !PORTABILITY: The comma-separated list syntax for the USES_ALLOCATORS clause has been deprecated in OpenMP 5.2, use 'USES_ALLOCATORS([TRAITS(traits):] allocator)' instead
+  !$omp target uses_allocators(my_alloc, other_alloc)
+  x = 3
+  !$omp end target
+
+  ! A single bare allocator is the canonical 5.2 syntax and is not deprecated.
+  !$omp target uses_allocators(my_alloc)
+  x = 4
+  !$omp end target
+end subroutine
+
+subroutine uses_allocators_traits_association
+  use omp_lib
+  use uses_allocators_traits_module
+  integer(omp_allocator_handle_kind) :: my_alloc
+  integer :: x
+
+  ! [5.2:182] pairs the constant-array requirement with a "same scope as the
+  ! construct" requirement, which [6.0:317] keeps only for C/C++ while stating
+  ! the Fortran rule as a named constant of rank one. A use-associated named
+  ! constant therefore satisfies the Fortran rule.
+  !$omp target uses_allocators(traits(module_tr): my_alloc)
+  x = 1
+  !$omp end target
+
+  ! The same holds for a host-associated named constant.
+  call inner
+contains
+  subroutine inner
+    !$omp target uses_allocators(traits(module_tr): my_alloc)
+    x = 2
+    !$omp end target
+  end subroutine
+end subroutine
+
+subroutine uses_allocators_conflicting_clauses
+  use omp_lib
+  ! Each case uses its own allocator so that the once-per-symbol
+  ! "both MAP and ..." diagnostic of TARGET does not interleave with them.
+  integer(omp_allocator_handle_kind) :: fp_alloc, idp_alloc, hda_alloc
+  type(omp_alloctrait), parameter :: tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+  integer :: x
+
+  !ERROR: An allocator in a USES_ALLOCATORS clause cannot also appear in the FIRSTPRIVATE clause on the same construct
+  !$omp target uses_allocators(traits(tr): fp_alloc) firstprivate(fp_alloc)
+  x = 1
+  !$omp end target
+
+  !ERROR: An allocator in a USES_ALLOCATORS clause cannot also appear in the IS_DEVICE_PTR clause on the same construct
+  !ERROR: Variable 'idp_alloc' in IS_DEVICE_PTR clause must be of type C_PTR
+  !$omp target uses_allocators(traits(tr): idp_alloc) is_device_ptr(idp_alloc)
+  x = 2
+  !$omp end target
+
+  !ERROR: An allocator in a USES_ALLOCATORS clause cannot also appear in the HAS_DEVICE_ADDR clause on the same construct
+  !$omp target uses_allocators(traits(tr): hda_alloc) has_device_addr(hda_alloc)
+  x = 3
+  !$omp end target
+end subroutine
+
+subroutine uses_allocators_predefined_identity
+  use omp_lib
+  use omp_lib, only: renamed_predef => omp_default_mem_alloc
+  use omp_lib, only: renamed_space => omp_const_mem_space
+  integer(omp_allocator_handle_kind) :: my_alloc
+  integer(omp_allocator_handle_kind), parameter :: same_value_as_predef = 1
+  type(omp_alloctrait), parameter :: tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+  integer :: x
+
+  ! Before 6.0 predefined recognition follows the entity, so a rename of the
+  ! intrinsic omp_lib allocator still denotes it and is accepted bare. At 6.0
+  ! the clause identifier is what matters and the rename is rejected; see
+  ! uses-allocators-version60.f90.
+  !$omp target uses_allocators(renamed_predef)
+  x = 1
+  !$omp end target
+
+  ! ... and, being predefined, it still rejects modifiers.
+  !ERROR: A predefined allocator 'renamed_predef' in a USES_ALLOCATORS clause cannot have modifiers or traits specified
+  !$omp target uses_allocators(traits(tr): renamed_predef)
+  x = 2
+  !$omp end target
+
+  ! A named constant that merely shares a predefined allocator's numeric value
+  ! is neither the predefined entity nor a predefined name, so the variable
+  ! rule applies to it at every version.
+  !ERROR: A non-predefined allocator 'same_value_as_predef' in a USES_ALLOCATORS clause must be a variable
+  !$omp target uses_allocators(same_value_as_predef)
+  x = 3
+  !$omp end target
+
+  ! A renamed memory space likewise still denotes a predefined memory space.
+  !$omp target uses_allocators(memspace(renamed_space): my_alloc)
+  x = 4
+  !$omp end target
+end subroutine
+
+subroutine uses_allocators_predefined_shadow
+  use omp_lib, only: omp_allocator_handle_kind
+  ! A local named constant of the correct handle kind that shadows the spelling
+  ! of a predefined allocator. [5.2:182] asks whether the allocator *is* a
+  ! predefined allocator, which this is not, so the variable rule applies.
+  ! [6.0:315] matches the name instead and accepts it; see
+  ! uses-allocators-version60.f90.
+  integer(omp_allocator_handle_kind), parameter :: omp_const_mem_alloc = 999
+  integer :: x
+
+  !ERROR: A non-predefined allocator 'omp_const_mem_alloc' in a USES_ALLOCATORS clause must be a variable
+  !$omp target uses_allocators(omp_const_mem_alloc)
+  x = 1
+  !$omp end target
+end subroutine
diff --git a/llvm/include/llvm/Frontend/OpenMP/OMP.td b/llvm/include/llvm/Frontend/OpenMP/OMP.td
index 037a506c8b175..0048e05d45e76 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMP.td
+++ b/llvm/include/llvm/Frontend/OpenMP/OMP.td
@@ -641,6 +641,7 @@ def OMPC_UseDevicePtr : Clause<[Spelling<"use_device_ptr">]> {
 }
 def OMPC_UsesAllocators : Clause<[Spelling<"uses_allocators">]> {
   let clangClass = "OMPUsesAllocatorsClause";
+  let flangClass = "OmpUsesAllocatorsClause";
 }
 def OMPC_Weak : Clause<[Spelling<"weak">]> {
   let clangClass = "OMPWeakClause";

>From 99f1585f999b76081e2c1a62c636859ac25b4966 Mon Sep 17 00:00:00 2001
From: Sairudra More <sairudra60 at gmail.com>
Date: Tue, 4 Aug 2026 12:46:10 -0500
Subject: [PATCH 2/3] [flang][OpenMP] Address review comments on
 USES_ALLOCATORS

Rename the modifiers to match the names used in the specification:
OmpMemspaceModifier -> OmpMemSpace and OmpTraitsArrayModifier ->
OmpTraitsArray, with descriptor names "mem-space" and "traits-array".
The MEMSPACE and TRAITS keyword spellings are unchanged.

Widen the conflicting-clause check. It tested a fixed list of five
clauses via FindClause, which returns only the first clause of a given
id, so a conflict in a repeated clause was not diagnosed. It now scans
all clauses using isDataSharingAttributeClause plus map.

Also correct the OpenMPSupport.md description of the OpenMP 6.0
multiple-specification form, add a missing <cstring> include, and trim
comments that restate the code or justify decisions.
---
 flang/docs/OpenMPSupport.md                   |  2 +-
 flang/include/flang/Parser/dump-parse-tree.h  |  4 +-
 flang/include/flang/Parser/parse-tree.h       | 21 +++----
 .../flang/Semantics/openmp-modifiers.h        |  4 +-
 flang/lib/Lower/OpenMP/Clauses.cpp            |  5 +-
 flang/lib/Parser/openmp-parsers.cpp           | 10 +--
 flang/lib/Parser/unparse.cpp                  |  6 +-
 flang/lib/Semantics/check-omp-structure.cpp   | 62 +++++++++----------
 flang/lib/Semantics/check-omp-structure.h     |  2 +-
 flang/lib/Semantics/openmp-modifiers.cpp      |  9 ++-
 .../OpenMP/Todo/target-uses-allocators.f90    |  3 +-
 .../OpenMP/uses-allocators-bad-syntax.f90     |  7 +--
 flang/test/Parser/OpenMP/uses-allocators.f90  | 18 +++---
 .../OpenMP/uses-allocators-handle-kind.f90    |  4 +-
 .../OpenMP/uses-allocators-user-module.f90    |  3 +-
 .../OpenMP/uses-allocators-version50.f90      |  4 +-
 .../OpenMP/uses-allocators-version51.f90      |  2 +-
 .../test/Semantics/OpenMP/uses-allocators.f90 | 38 ++++++++++--
 18 files changed, 113 insertions(+), 91 deletions(-)

diff --git a/flang/docs/OpenMPSupport.md b/flang/docs/OpenMPSupport.md
index 755e6cac7169e..1c532e1de7011 100644
--- a/flang/docs/OpenMPSupport.md
+++ b/flang/docs/OpenMPSupport.md
@@ -124,7 +124,7 @@ Parser/Semantics, MLIR, Lowering, or the OpenMPIRBuilder.
 | allocate directive and allocate clause | <span class="part">partial</span> | | Semantics coverage exists in `flang/test/Semantics/OpenMP/allocate-directive.f90` and `flang/test/Semantics/OpenMP/allocate-clause01.f90`; lowering support expanded, with remaining TODO coverage (`flang/test/Lower/OpenMP/Todo/allocate-clause-align.f90`, `flang/test/Lower/OpenMP/Todo/allocate-clause-allocator.f90`). | [llvm/llvm-project#121356](https://github.com/llvm/llvm-project/pull/121356), [llvm/llvm-project#165719](https://github.com/llvm/llvm-project/pull/165719), [llvm/llvm-project#165865](https://github.com/llvm/llvm-project/pull/165865), [llvm/llvm-project#187167](https://github.com/llvm/llvm-project/pull/187167) |
 | metadirective | <span class="part">partial</span> | | Semantics coverage exists in `flang/test/Semantics/OpenMP/metadirective-construct.f90`; lowering support exists for several construct-selector paths (`flang/test/Lower/OpenMP/metadirective-construct.f90`, `flang/test/Lower/OpenMP/metadirective-nothing.f90`), but some selector/variant paths remain TODO-tracked in lowering. | [llvm/llvm-project#159945](https://github.com/llvm/llvm-project/pull/159945), [llvm/llvm-project#193664](https://github.com/llvm/llvm-project/pull/193664), [llvm/llvm-project#194402](https://github.com/llvm/llvm-project/pull/194402), [llvm/llvm-project#194424](https://github.com/llvm/llvm-project/pull/194424) |
 | support full defaultmap functionality | <span class="part">partial</span> | | Core coverage exists (`flang/test/Lower/OpenMP/defaultmap.f90`, `flang/test/Semantics/OpenMP/defaultmap-clause-v50.f90`), but lowering has known partial paths (for example defaultmap-firstprivate TODO tests). | [llvm/llvm-project#135226](https://github.com/llvm/llvm-project/pull/135226), [llvm/llvm-project#166715](https://github.com/llvm/llvm-project/pull/166715), [llvm/llvm-project#167806](https://github.com/llvm/llvm-project/pull/167806), [llvm/llvm-project#177389](https://github.com/llvm/llvm-project/pull/177389), [llvm/llvm-project#190764](https://github.com/llvm/llvm-project/pull/190764) |
-| clause: uses_allocators | <span class="part">partial</span> | | Parsing and semantic checking are supported for the OpenMP 5.2 syntax and for the pre-5.2 comma-separated list syntax, the latter accepted in 5.0/5.1, accepted with a deprecation warning in 5.2, and removed and rejected in 6.0 and later (`flang/test/Parser/OpenMP/uses-allocators.f90`, `flang/test/Semantics/OpenMP/uses-allocators.f90`, `flang/test/Semantics/OpenMP/uses-allocators-version60.f90`); lowering is unimplemented and reports a `not yet implemented` diagnostic (`flang/test/Lower/OpenMP/Todo/target-uses-allocators.f90`). The OpenMP 6.0 form with more than one clause-argument-specification is diagnosed as not yet supported. | |
+| clause: uses_allocators | <span class="part">partial</span> | | Parsing and semantic checking are supported for the OpenMP 5.2 syntax and for the pre-5.2 comma-separated list syntax, the latter accepted in 5.0/5.1, accepted with a deprecation warning in 5.2, and removed and rejected in 6.0 and later (`flang/test/Parser/OpenMP/uses-allocators.f90`, `flang/test/Semantics/OpenMP/uses-allocators.f90`, `flang/test/Semantics/OpenMP/uses-allocators-version60.f90`); lowering is unimplemented and reports a `not yet implemented` diagnostic (`flang/test/Lower/OpenMP/Todo/target-uses-allocators.f90`). Only a single clause-argument-specification is supported; the OpenMP 6.0 form that separates more than one specification with ';' is not parsed. | |
 | clause: in_reduction | <span class="part">partial</span> | | Semantics and lowering coverage exists for several task/taskgroup/taskloop forms (`flang/test/Semantics/OpenMP/in-reduction.f90`, `flang/test/Lower/OpenMP/task-inreduction.f90`, `flang/test/Lower/OpenMP/taskloop-inreduction.f90`, `flang/test/Lower/OpenMP/taskgroup-task_reduction02.f90`); some target-related forms remain TODO (for example `flang/test/Lower/OpenMP/Todo/target-inreduction.f90`). | [llvm/llvm-project#139704](https://github.com/llvm/llvm-project/pull/139704), [llvm/llvm-project#205124](https://github.com/llvm/llvm-project/pull/205124) |
 | user-defined mappers | <span class="good">done</span> | | Supported with semantics/lowering/transform coverage. | [llvm/llvm-project#140560](https://github.com/llvm/llvm-project/pull/140560), [llvm/llvm-project#163860](https://github.com/llvm/llvm-project/pull/163860), [llvm/llvm-project#167903](https://github.com/llvm/llvm-project/pull/167903), [llvm/llvm-project#179936](https://github.com/llvm/llvm-project/pull/179936), [llvm/llvm-project#189136](https://github.com/llvm/llvm-project/pull/189136) |
 | map array-section with implicit mapper | <span class="part">partial</span> | | Mapper and map coverage exists (`flang/test/Lower/OpenMP/map-mapper.f90`, `flang/test/Lower/OpenMP/target-data-skip-mapper-calls.f90`), with remaining iterator/modifier gaps in lowering (`flang/lib/Lower/OpenMP/ClauseProcessor.cpp` TODOs). | [llvm/llvm-project#175133](https://github.com/llvm/llvm-project/pull/175133), [llvm/llvm-project#177389](https://github.com/llvm/llvm-project/pull/177389) |
diff --git a/flang/include/flang/Parser/dump-parse-tree.h b/flang/include/flang/Parser/dump-parse-tree.h
index dc0fd9effe96e..9e3ed0ddc4396 100644
--- a/flang/include/flang/Parser/dump-parse-tree.h
+++ b/flang/include/flang/Parser/dump-parse-tree.h
@@ -690,7 +690,7 @@ class ParseTreeDumper {
   NODE(parser, OmpMapTypeModifier)
   NODE_ENUM(OmpMapTypeModifier, Value)
   NODE(parser, OmpMatchClause)
-  NODE(parser, OmpMemspaceModifier)
+  NODE(parser, OmpMemSpace)
   NODE(parser, OmpMessageClause)
   NODE(parser, OmpMetadirectiveDirective)
   NODE(parser, OmpNoOpenMPClause)
@@ -761,7 +761,7 @@ class ParseTreeDumper {
   NODE_ENUM(OmpThreadsetClause, ThreadsetPolicy)
   NODE(parser, OmpToClause)
   NODE(OmpToClause, Modifier)
-  NODE(parser, OmpTraitsArrayModifier)
+  NODE(parser, OmpTraitsArray)
   NODE(parser, OmpTraitProperty)
   NODE(parser, OmpTraitPropertyExtension)
   NODE(OmpTraitPropertyExtension, Complex)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index a835f92c5e32e..e50c1438a1407 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -4227,10 +4227,10 @@ struct OmpMapTypeModifier {
 
 // Ref: [5.2:181-182]
 //
-// mem-space-modifier ->
+// mem-space ->
 //    MEMSPACE(memspace-handle)                     // since 5.2
-struct OmpMemspaceModifier {
-  WRAPPER_CLASS_BOILERPLATE(OmpMemspaceModifier, ScalarIntExpr);
+struct OmpMemSpace {
+  WRAPPER_CLASS_BOILERPLATE(OmpMemSpace, ScalarIntExpr);
 };
 
 // Ref: [4.5:56-63], [5.0:101-109], [5.1:126-133], [5.2:252-254]
@@ -4373,10 +4373,10 @@ struct OmpTaskDependenceType {
 
 // Ref: [5.2:181-182]
 //
-// traits-array-modifier ->
+// traits-array ->
 //    TRAITS(traits-array)                          // since 5.2
-struct OmpTraitsArrayModifier {
-  WRAPPER_CLASS_BOILERPLATE(OmpTraitsArrayModifier, common::Indirection<Expr>);
+struct OmpTraitsArray {
+  WRAPPER_CLASS_BOILERPLATE(OmpTraitsArray, common::Indirection<Expr>);
 };
 
 // Ref: [4.5:229-230], [5.0:324-325], [5.1:357-358], [5.2:161-162]
@@ -5188,17 +5188,16 @@ struct OmpUseClause {
 //        [, allocator[(traits-array)]]...) |       // since 5.0, dep. 5.2
 //    USES_ALLOCATORS([modifier...:] allocator)     // since 5.2
 // modifier ->
-//    mem-space-modifier |
-//    traits-array-modifier                         // since 5.2
+//    mem-space |
+//    traits-array                                  // since 5.2
 struct OmpUsesAllocatorsClause {
   struct AllocatorSpec {
     TUPLE_CLASS_BOILERPLATE(AllocatorSpec);
-    MODIFIER_BOILERPLATE(OmpMemspaceModifier, OmpTraitsArrayModifier);
+    MODIFIER_BOILERPLATE(OmpMemSpace, OmpTraitsArray);
     CharBlock source;
     // The traits of the deprecated "allocator(traits-array)" form are stored
     // as a traits-array modifier. The flag records which of the two surface
-    // syntaxes was written, which both the unparser and the deprecation
-    // diagnostic need.
+    // syntaxes was written.
     std::tuple<MODIFIERS(), ScalarIntExpr, /*IsLegacySyntax=*/bool> t;
   };
   WRAPPER_CLASS_BOILERPLATE(OmpUsesAllocatorsClause, std::list<AllocatorSpec>);
diff --git a/flang/include/flang/Semantics/openmp-modifiers.h b/flang/include/flang/Semantics/openmp-modifiers.h
index 79913fda05af1..1eae01b717e1e 100644
--- a/flang/include/flang/Semantics/openmp-modifiers.h
+++ b/flang/include/flang/Semantics/openmp-modifiers.h
@@ -96,7 +96,7 @@ DECLARE_DESCRIPTOR(parser::OmpLowerBound);
 DECLARE_DESCRIPTOR(parser::OmpMapper);
 DECLARE_DESCRIPTOR(parser::OmpMapType);
 DECLARE_DESCRIPTOR(parser::OmpMapTypeModifier);
-DECLARE_DESCRIPTOR(parser::OmpMemspaceModifier);
+DECLARE_DESCRIPTOR(parser::OmpMemSpace);
 DECLARE_DESCRIPTOR(parser::OmpOrderModifier);
 DECLARE_DESCRIPTOR(parser::OmpOrderingModifier);
 DECLARE_DESCRIPTOR(parser::OmpPreferType);
@@ -109,7 +109,7 @@ DECLARE_DESCRIPTOR(parser::OmpSelfModifier);
 DECLARE_DESCRIPTOR(parser::OmpStepComplexModifier);
 DECLARE_DESCRIPTOR(parser::OmpStepSimpleModifier);
 DECLARE_DESCRIPTOR(parser::OmpTaskDependenceType);
-DECLARE_DESCRIPTOR(parser::OmpTraitsArrayModifier);
+DECLARE_DESCRIPTOR(parser::OmpTraitsArray);
 DECLARE_DESCRIPTOR(parser::OmpVariableCategory);
 DECLARE_DESCRIPTOR(parser::OmpxHoldModifier);
 
diff --git a/flang/lib/Lower/OpenMP/Clauses.cpp b/flang/lib/Lower/OpenMP/Clauses.cpp
index ac68fbe6adaff..3a77371c99276 100644
--- a/flang/lib/Lower/OpenMP/Clauses.cpp
+++ b/flang/lib/Lower/OpenMP/Clauses.cpp
@@ -1816,10 +1816,9 @@ UsesAllocators make(const parser::OmpClause::UsesAllocators &inp,
 
   auto makeSpec = [&](const AllocatorSpec &spec) {
     auto &mods = semantics::OmpGetModifiers(spec);
-    auto *memSpace =
-        semantics::OmpGetUniqueModifier<parser::OmpMemspaceModifier>(mods);
+    auto *memSpace = semantics::OmpGetUniqueModifier<parser::OmpMemSpace>(mods);
     auto *traits =
-        semantics::OmpGetUniqueModifier<parser::OmpTraitsArrayModifier>(mods);
+        semantics::OmpGetUniqueModifier<parser::OmpTraitsArray>(mods);
 
     std::optional<UsesAllocators::TraitsArray> traitsArray;
     if (traits) {
diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index be9751ba80ba9..b4dfe740379ba 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -989,7 +989,7 @@ struct OmpMapTypeModifierParser {
 
 TYPE_PARSER(OmpMapTypeModifierParser{})
 
-TYPE_PARSER(construct<OmpMemspaceModifier>( //
+TYPE_PARSER(construct<OmpMemSpace>( //
     "MEMSPACE" >> parenthesized(scalarIntExpr)))
 
 TYPE_PARSER(construct<OmpOrderModifier>(
@@ -1047,7 +1047,7 @@ TYPE_PARSER(construct<OmpTaskDependenceType>(
     "MUTEXINOUTSET" >> pure(OmpTaskDependenceType::Value::Mutexinoutset) ||
     "OUT" >> pure(OmpTaskDependenceType::Value::Out)))
 
-TYPE_PARSER(construct<OmpTraitsArrayModifier>( //
+TYPE_PARSER(construct<OmpTraitsArray>( //
     "TRAITS" >> parenthesized(indirect(expr))))
 
 TYPE_PARSER(construct<OmpVariableCategory>(
@@ -1201,9 +1201,9 @@ TYPE_PARSER(sourced(
 
 TYPE_PARSER(sourced(construct<OmpUsesAllocatorsClause::AllocatorSpec::Modifier>(
     sourced(construct<OmpUsesAllocatorsClause::AllocatorSpec::Modifier>(
-                Parser<OmpMemspaceModifier>{}) ||
+                Parser<OmpMemSpace>{}) ||
         construct<OmpUsesAllocatorsClause::AllocatorSpec::Modifier>(
-            Parser<OmpTraitsArrayModifier>{})))))
+            Parser<OmpTraitsArray>{})))))
 
 TYPE_PARSER(sourced(construct<OmpWhenClause::Modifier>( //
     Parser<OmpContextSelector>{})))
@@ -1573,7 +1573,7 @@ static OmpUsesAllocatorsClause::AllocatorSpec makeLegacyAllocatorSpec(
     Name &&name, common::Indirection<Expr> &&traits) {
   using AllocatorSpec = OmpUsesAllocatorsClause::AllocatorSpec;
   CharBlock traitsSource{traits.value().source};
-  AllocatorSpec::Modifier mod{OmpTraitsArrayModifier{std::move(traits)}};
+  AllocatorSpec::Modifier mod{OmpTraitsArray{std::move(traits)}};
   mod.source = traitsSource;
   std::list<AllocatorSpec::Modifier> mods;
   mods.emplace_back(std::move(mod));
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 816b91cf1628f..accb80ddbe8f2 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -2636,12 +2636,12 @@ class UnparseVisitor {
     Walk(std::get<std::optional<std::list<Modifier>>>(x.t), ": ");
     Walk(std::get<OmpObjectList>(x.t));
   }
-  void Unparse(const OmpMemspaceModifier &x) {
+  void Unparse(const OmpMemSpace &x) {
     Word("MEMSPACE(");
     Walk(x.v);
     Put(")");
   }
-  void Unparse(const OmpTraitsArrayModifier &x) {
+  void Unparse(const OmpTraitsArray &x) {
     Word("TRAITS(");
     Walk(x.v);
     Put(")");
@@ -2655,7 +2655,7 @@ class UnparseVisitor {
       Walk(std::get<ScalarIntExpr>(x.t));
       if (modifiers) {
         for (const Modifier &m : *modifiers) {
-          if (auto *traits{std::get_if<OmpTraitsArrayModifier>(&m.u)}) {
+          if (auto *traits{std::get_if<OmpTraitsArray>(&m.u)}) {
             Put("(");
             Walk(traits->v);
             Put(")");
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 880f9faf07754..7f998c2e3ec42 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -46,6 +46,7 @@
 #include <algorithm>
 #include <cassert>
 #include <cstdint>
+#include <cstring>
 #include <iterator>
 #include <list>
 #include <map>
@@ -5404,8 +5405,7 @@ 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. `IsIntrinsicModules()` on the module scope's parent is
-// the provenance test that mod-file.cpp and expression.cpp already use.
+// user-defined module.
 static bool IsIntrinsicOmpLibEntity(const Symbol &ultimate) {
   const Scope &scope{ultimate.owner()};
   if (!scope.IsModule()) {
@@ -5442,9 +5442,9 @@ static bool IsPredefinedHandle(const parser::Name &name,
 }
 
 // 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, so it
-// can be read on demand the way semantics.cpp reads its other builtin modules;
-// the kind is therefore available even when the source uses neither module.
+// 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) {
@@ -5463,7 +5463,7 @@ static std::optional<std::int64_t> GetOmpHandleKind(SemanticsContext &context) {
 
 // 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, so that the two do not double up.
+// separately.
 static bool HasOmpHandleKind(
     const Symbol &symbol, SemanticsContext &context, std::int64_t &expected) {
   const DeclTypeSpec *type{symbol.GetUltimate().GetType()};
@@ -5494,10 +5494,8 @@ void OmpStructureChecker::CheckUsesAllocatorsSpec(
   }
 
   auto &modifiers{OmpGetModifiers(spec)};
-  const auto *memSpace{
-      OmpGetUniqueModifier<parser::OmpMemspaceModifier>(modifiers)};
-  const auto *traits{
-      OmpGetUniqueModifier<parser::OmpTraitsArrayModifier>(modifiers)};
+  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()};
@@ -5559,27 +5557,28 @@ void OmpStructureChecker::CheckUsesAllocatorsSpec(
   // [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}) {
-    static const llvm::omp::Clause conflictingClauses[]{
-        llvm::omp::Clause::OMPC_firstprivate,
-        llvm::omp::Clause::OMPC_has_device_addr,
-        llvm::omp::Clause::OMPC_is_device_ptr,
-        llvm::omp::Clause::OMPC_map,
-        llvm::omp::Clause::OMPC_private,
-    };
+    const Symbol &ultimate{symbol->GetUltimate()};
     const parser::OmpDirectiveSpecification &dirSpec{*dirStack_.back()};
-    for (llvm::omp::Clause id : conflictingClauses) {
-      const parser::OmpClause *found{parser::omp::FindClause(dirSpec, id)};
-      if (!found) {
+    for (const parser::OmpClause &clause : dirSpec.Clauses().v) {
+      llvm::omp::Clause id{clause.Id()};
+      if (id == llvm::omp::Clause::OMPC_uses_allocators) {
         continue;
       }
-      if (const parser::OmpObjectList *objects{GetOmpObjectList(*found)}) {
-        for (const parser::OmpObject &object : objects->v) {
-          if (const Symbol *other{GetObjectSymbol(object, /*ultimate=*/true)};
-              other == &symbol->GetUltimate()) {
-            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)));
-          }
+      if (!llvm::omp::isDataSharingAttributeClause(id, version) &&
+          id != llvm::omp::Clause::OMPC_map) {
+        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;
         }
       }
     }
@@ -5612,7 +5611,7 @@ void OmpStructureChecker::CheckUsesAllocatorsSpec(
 }
 
 void OmpStructureChecker::CheckUsesAllocatorsTraits(
-    const parser::OmpTraitsArrayModifier &traits, parser::CharBlock source) {
+    const parser::OmpTraitsArray &traits, parser::CharBlock source) {
   const parser::Expr &expr{traits.v.value()};
   parser::CharBlock traitsSource{source.empty() ? expr.source : source};
   const SomeExpr *value{GetExpr(context_, expr)};
@@ -5664,7 +5663,7 @@ void OmpStructureChecker::Enter(const parser::OmpClause::UsesAllocators &x) {
   const std::list<parser::OmpUsesAllocatorsClause::AllocatorSpec> &specs{x.v.v};
 
   // Classify by the syntax each specification was written in, which the parse
-  // tree records, rather than by whether a modifier happens to be present.
+  // tree records.
   bool anyLegacyItem{false}, anyModifierItem{false};
   for (auto &spec : specs) {
     if (std::get<bool>(spec.t)) {
@@ -5689,8 +5688,7 @@ void OmpStructureChecker::Enter(const parser::OmpClause::UsesAllocators &x) {
   }
 
   if (specs.size() > 1 && version >= 60) {
-    // [6.0:315] permits more than one clause-argument-specification, but only
-    // the single-specification form is implemented so far.
+    // [6.0:315] permits more than one clause-argument-specification.
     context_.Say(GetContext().clauseSource,
         "Multiple allocator specifications in a USES_ALLOCATORS clause are not yet supported"_err_en_US);
     return;
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 53d47eaacda24..ce692d0c99895 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -428,7 +428,7 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   void CheckUsesAllocatorsSpec(
       const parser::OmpUsesAllocatorsClause::AllocatorSpec &spec);
   void CheckUsesAllocatorsTraits(
-      const parser::OmpTraitsArrayModifier &traits, parser::CharBlock source);
+      const parser::OmpTraitsArray &traits, parser::CharBlock source);
 
   void CheckIteratorRange(const parser::OmpIteratorSpecifier &x);
   void CheckIteratorModifier(const parser::OmpIterator &x);
diff --git a/flang/lib/Semantics/openmp-modifiers.cpp b/flang/lib/Semantics/openmp-modifiers.cpp
index d4a2bddc2c3c6..2d5487487d0b1 100644
--- a/flang/lib/Semantics/openmp-modifiers.cpp
+++ b/flang/lib/Semantics/openmp-modifiers.cpp
@@ -557,9 +557,9 @@ const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpMapTypeModifier>() {
 }
 
 template <>
-const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpMemspaceModifier>() {
+const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpMemSpace>() {
   static const OmpModifierDescriptor desc{
-      /*name=*/"mem-space-modifier",
+      /*name=*/"mem-space",
       /*props=*/
       {
           {52, {OmpProperty::Unique}},
@@ -773,10 +773,9 @@ const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpTaskDependenceType>() {
 }
 
 template <>
-const OmpModifierDescriptor &
-OmpGetDescriptor<parser::OmpTraitsArrayModifier>() {
+const OmpModifierDescriptor &OmpGetDescriptor<parser::OmpTraitsArray>() {
   static const OmpModifierDescriptor desc{
-      /*name=*/"traits-array-modifier",
+      /*name=*/"traits-array",
       /*props=*/
       {
           {52, {OmpProperty::Unique}},
diff --git a/flang/test/Lower/OpenMP/Todo/target-uses-allocators.f90 b/flang/test/Lower/OpenMP/Todo/target-uses-allocators.f90
index fbe2e9efe9ac3..6166f3cd5984f 100644
--- a/flang/test/Lower/OpenMP/Todo/target-uses-allocators.f90
+++ b/flang/test/Lower/OpenMP/Todo/target-uses-allocators.f90
@@ -7,8 +7,7 @@
 ! RUN: %not_todo_cmd %flang_fc1 -cpp -DLEGACY -emit-llvm %openmp_flags -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
 
 ! Every shape that semantics accepts must reach the established "not yet
-! implemented" lowering boundary for USES_ALLOCATORS, rather than aborting
-! while the clause is converted for lowering. Each shape needs its own
+! implemented" lowering boundary for USES_ALLOCATORS. Each shape needs its own
 ! compilation because lowering stops at the first unimplemented clause.
 
 ! CHECK: not yet implemented: USES_ALLOCATORS clause is not implemented yet
diff --git a/flang/test/Parser/OpenMP/uses-allocators-bad-syntax.f90 b/flang/test/Parser/OpenMP/uses-allocators-bad-syntax.f90
index 71317861883fb..81333d6966b58 100644
--- a/flang/test/Parser/OpenMP/uses-allocators-bad-syntax.f90
+++ b/flang/test/Parser/OpenMP/uses-allocators-bad-syntax.f90
@@ -5,10 +5,9 @@
 
 ! Forms that the USES_ALLOCATORS parser does not accept.
 !
-! The first construct is a valid clause carrying no error annotation. It is the
-! reason this test cannot pass vacuously: a compiler without USES_ALLOCATORS
-! parser support does not parse it either, and the resulting unexpected
-! diagnostic makes the harness fail rather than silently agree.
+! The first construct is a valid clause carrying no error annotation. A
+! compiler without USES_ALLOCATORS parser support does not parse it either, so
+! this test cannot pass vacuously.
 
 subroutine uses_allocators_bad_syntax
   use omp_lib
diff --git a/flang/test/Parser/OpenMP/uses-allocators.f90 b/flang/test/Parser/OpenMP/uses-allocators.f90
index 42ac11512e9bf..cc474134c5789 100644
--- a/flang/test/Parser/OpenMP/uses-allocators.f90
+++ b/flang/test/Parser/OpenMP/uses-allocators.f90
@@ -61,7 +61,7 @@ subroutine uses_allocators_syntax
 
 ! TRAITS(tr): a
 !PARSE-TREE: OmpClause -> UsesAllocators -> OmpUsesAllocatorsClause -> AllocatorSpec
-!PARSE-TREE-NEXT: Modifier -> OmpTraitsArrayModifier -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
+!PARSE-TREE-NEXT: Modifier -> OmpTraitsArray -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'tr'
 !PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'a'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'a'
@@ -69,7 +69,7 @@ subroutine uses_allocators_syntax
 
 ! MEMSPACE(omp_default_mem_space): a
 !PARSE-TREE: OmpClause -> UsesAllocators -> OmpUsesAllocatorsClause -> AllocatorSpec
-!PARSE-TREE-NEXT: Modifier -> OmpMemspaceModifier -> Scalar -> Integer -> Expr = '99_8'
+!PARSE-TREE-NEXT: Modifier -> OmpMemSpace -> Scalar -> Integer -> Expr = '99_8'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'omp_default_mem_space'
 !PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'a'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'a'
@@ -77,18 +77,18 @@ subroutine uses_allocators_syntax
 
 ! Both modifiers, in either order.
 !PARSE-TREE: OmpClause -> UsesAllocators -> OmpUsesAllocatorsClause -> AllocatorSpec
-!PARSE-TREE-NEXT: Modifier -> OmpMemspaceModifier -> Scalar -> Integer -> Expr = '4_8'
+!PARSE-TREE-NEXT: Modifier -> OmpMemSpace -> Scalar -> Integer -> Expr = '4_8'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'omp_low_lat_mem_space'
-!PARSE-TREE-NEXT: Modifier -> OmpTraitsArrayModifier -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
+!PARSE-TREE-NEXT: Modifier -> OmpTraitsArray -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'tr'
 !PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'a'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'a'
 !PARSE-TREE-NEXT: bool = 'false'
 
 !PARSE-TREE: OmpClause -> UsesAllocators -> OmpUsesAllocatorsClause -> AllocatorSpec
-!PARSE-TREE-NEXT: Modifier -> OmpTraitsArrayModifier -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
+!PARSE-TREE-NEXT: Modifier -> OmpTraitsArray -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'tr'
-!PARSE-TREE-NEXT: Modifier -> OmpMemspaceModifier -> Scalar -> Integer -> Expr = '4_8'
+!PARSE-TREE-NEXT: Modifier -> OmpMemSpace -> Scalar -> Integer -> Expr = '4_8'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'omp_low_lat_mem_space'
 !PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'a'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'a'
@@ -97,7 +97,7 @@ subroutine uses_allocators_syntax
 ! The deprecated "allocator(traits)" form is stored as a traits modifier, and
 ! is flagged so that it unparses back to the syntax that was written.
 !PARSE-TREE: OmpClause -> UsesAllocators -> OmpUsesAllocatorsClause -> AllocatorSpec
-!PARSE-TREE-NEXT: Modifier -> OmpTraitsArrayModifier -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
+!PARSE-TREE-NEXT: Modifier -> OmpTraitsArray -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'tr'
 !PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'a'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'a'
@@ -105,13 +105,13 @@ subroutine uses_allocators_syntax
 
 ! The deprecated syntax can list more than one allocator specification.
 !PARSE-TREE: OmpClause -> UsesAllocators -> OmpUsesAllocatorsClause -> AllocatorSpec
-!PARSE-TREE-NEXT: Modifier -> OmpTraitsArrayModifier -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
+!PARSE-TREE-NEXT: Modifier -> OmpTraitsArray -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'tr'
 !PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'a'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'a'
 !PARSE-TREE-NEXT: bool = 'true'
 !PARSE-TREE-NEXT: AllocatorSpec
-!PARSE-TREE-NEXT: Modifier -> OmpTraitsArrayModifier -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
+!PARSE-TREE-NEXT: Modifier -> OmpTraitsArray -> Expr = '[omp_alloctrait::omp_alloctrait(key=2_4,value=64_8)]'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'tr'
 !PARSE-TREE-NEXT: Scalar -> Integer -> Expr = 'b'
 !PARSE-TREE-NEXT: Designator -> DataRef -> Name = 'b'
diff --git a/flang/test/Semantics/OpenMP/uses-allocators-handle-kind.f90 b/flang/test/Semantics/OpenMP/uses-allocators-handle-kind.f90
index b9c6a764c67f8..e1d693c69df45 100644
--- a/flang/test/Semantics/OpenMP/uses-allocators-handle-kind.f90
+++ b/flang/test/Semantics/OpenMP/uses-allocators-handle-kind.f90
@@ -6,8 +6,8 @@
 ! [5.2:181], [6.0:315] The allocator argument of USES_ALLOCATORS is an
 ! expression of allocator_handle type, which omp_lib declares as c_intptr_t.
 ! The rule holds whether or not the source uses omp_lib, so none of these
-! subroutines imports it. The expected kind is matched as a number rather than
-! spelled out, because it is the target's C_INTPTR_T kind.
+! subroutines imports it. The expected kind is matched as a number because it
+! is the target's C_INTPTR_T kind.
 
 ! CHECK: error: The allocator 'wrong_kind_alloc' in a USES_ALLOCATORS clause must be of type INTEGER(KIND={{[0-9]+}}), i.e. OMP_ALLOCATOR_HANDLE_KIND
 subroutine uses_allocators_wrong_handle_kind
diff --git a/flang/test/Semantics/OpenMP/uses-allocators-user-module.f90 b/flang/test/Semantics/OpenMP/uses-allocators-user-module.f90
index 55dbb130f385f..4eb441820dce9 100644
--- a/flang/test/Semantics/OpenMP/uses-allocators-user-module.f90
+++ b/flang/test/Semantics/OpenMP/uses-allocators-user-module.f90
@@ -8,8 +8,7 @@
 ! when its named constant has the right name and the right handle kind.
 !
 ! This module shadows the intrinsic omp_lib for the whole file, so it is kept
-! in a test of its own rather than contaminating tests that need the real
-! intrinsic module.
+! in a test of its own.
 
 ! CHECK: error: A non-predefined allocator 'omp_const_mem_alloc' in a USES_ALLOCATORS clause must be a variable
 
diff --git a/flang/test/Semantics/OpenMP/uses-allocators-version50.f90 b/flang/test/Semantics/OpenMP/uses-allocators-version50.f90
index a8491fa764f81..474fea2e55707 100644
--- a/flang/test/Semantics/OpenMP/uses-allocators-version50.f90
+++ b/flang/test/Semantics/OpenMP/uses-allocators-version50.f90
@@ -27,12 +27,12 @@ subroutine uses_allocators_50
   x = 3
   !$omp end target
 
-  !WARNING: 'traits-array-modifier' modifier is not supported in OpenMP v5.0, try -fopenmp-version=52
+  !WARNING: 'traits-array' modifier is not supported in OpenMP v5.0, try -fopenmp-version=52
   !$omp target uses_allocators(traits(tr): my_alloc)
   x = 4
   !$omp end target
 
-  !WARNING: 'mem-space-modifier' modifier is not supported in OpenMP v5.0, try -fopenmp-version=52
+  !WARNING: 'mem-space' modifier is not supported in OpenMP v5.0, try -fopenmp-version=52
   !$omp target uses_allocators(memspace(omp_default_mem_space): my_alloc)
   x = 5
   !$omp end target
diff --git a/flang/test/Semantics/OpenMP/uses-allocators-version51.f90 b/flang/test/Semantics/OpenMP/uses-allocators-version51.f90
index 921eb273df6a0..231774fdd9970 100644
--- a/flang/test/Semantics/OpenMP/uses-allocators-version51.f90
+++ b/flang/test/Semantics/OpenMP/uses-allocators-version51.f90
@@ -21,7 +21,7 @@ subroutine uses_allocators_51
   x = 2
   !$omp end target
 
-  !WARNING: 'traits-array-modifier' modifier is not supported in OpenMP v5.1, try -fopenmp-version=52
+  !WARNING: 'traits-array' modifier is not supported in OpenMP v5.1, try -fopenmp-version=52
   !$omp target uses_allocators(traits(tr): my_alloc)
   x = 3
   !$omp end target
diff --git a/flang/test/Semantics/OpenMP/uses-allocators.f90 b/flang/test/Semantics/OpenMP/uses-allocators.f90
index 1bbb09d16062c..a3f1cdc01b387 100644
--- a/flang/test/Semantics/OpenMP/uses-allocators.f90
+++ b/flang/test/Semantics/OpenMP/uses-allocators.f90
@@ -3,8 +3,6 @@
 ! RUN: %python %S/../test_errors.py %s %flang_fc1 %openmp_flags -fopenmp-version=52
 
 ! OpenMP Version 5.2, Section 6.8: uses_allocators clause.
-! Each negative case is paired with the nearest valid case so that the checks
-! cannot reject conforming code.
 
 module uses_allocators_traits_module
   use omp_lib
@@ -155,12 +153,12 @@ subroutine uses_allocators_errors
   !$omp end target
 
   ! A modifier may appear at most once in one allocator specification.
-  !ERROR: 'traits-array-modifier' modifier cannot occur multiple times
+  !ERROR: 'traits-array' modifier cannot occur multiple times
   !$omp target uses_allocators(traits(tr), traits(tr): my_alloc)
   x = 13
   !$omp end target
 
-  !ERROR: 'mem-space-modifier' modifier cannot occur multiple times
+  !ERROR: 'mem-space' modifier cannot occur multiple times
   !$omp target uses_allocators(memspace(omp_const_mem_space), memspace(omp_high_bw_mem_space): my_alloc)
   x = 131
   !$omp end target
@@ -312,3 +310,35 @@ subroutine uses_allocators_predefined_shadow
   x = 1
   !$omp end target
 end subroutine
+
+subroutine uses_allocators_repeated_clauses
+  use omp_lib
+  ! As above, each case uses its own allocator.
+  integer(omp_allocator_handle_kind) :: map_alloc, priv_alloc, ir_alloc
+  type(omp_alloctrait), parameter :: tr(1) = &
+      [omp_alloctrait(omp_atk_alignment, 64)]
+  integer :: x, y
+
+  ! The conflict is in the second MAP clause, which is invisible to a scan
+  ! that inspects only the first occurrence of each clause id.
+  !ERROR: An allocator in a USES_ALLOCATORS clause cannot also appear in the MAP clause on the same construct
+  !$omp target map(to: y) map(tofrom: map_alloc) uses_allocators(traits(tr): map_alloc)
+  x = 1
+  !$omp end target
+
+  ! The same, for a repeated PRIVATE clause.
+  !ERROR: An allocator in a USES_ALLOCATORS clause cannot also appear in the PRIVATE clause on the same construct
+  !ERROR: Variable 'y' may not appear on both MAP and PRIVATE clauses on a TARGET construct
+  !$omp target private(y) private(priv_alloc) uses_allocators(traits(tr): priv_alloc)
+  x = 2
+  !$omp end target
+
+  ! IN_REDUCTION is a data-sharing attribute clause allowed on TARGET, but it
+  ! is not one of the privatizing clauses.
+  !$omp taskgroup task_reduction(+: ir_alloc)
+  !ERROR: An allocator in a USES_ALLOCATORS clause cannot also appear in the IN_REDUCTION clause on the same construct
+  !$omp target in_reduction(+: ir_alloc) uses_allocators(traits(tr): ir_alloc)
+  x = 3
+  !$omp end target
+  !$omp end taskgroup
+end subroutine

>From 8344bd4713d5a0068050193898040ea3ed8ee7d5 Mon Sep 17 00:00:00 2001
From: Sairudra More <sairudra60 at gmail.com>
Date: Wed, 5 Aug 2026 03:19:52 -0500
Subject: [PATCH 3/3] [flang][OpenMP] Continue checking USES_ALLOCATORS
 extensions

Continue semantic validation when newer modifier syntax is accepted with a
version warning, preventing malformed traits from reaching lowering.
---
 flang/lib/Semantics/check-omp-structure.cpp         | 10 ++++++----
 .../Semantics/OpenMP/uses-allocators-version50.f90  | 13 +++++++++++--
 .../Semantics/OpenMP/uses-allocators-version51.f90  | 10 +++++++++-
 3 files changed, 26 insertions(+), 7 deletions(-)

diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 7f998c2e3ec42..f593762eb377e 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -53,6 +53,7 @@
 #include <optional>
 #include <set>
 #include <string>
+#include <string_view>
 #include <tuple>
 #include <type_traits>
 #include <utility>
@@ -5486,11 +5487,12 @@ void OmpStructureChecker::CheckUsesAllocatorsSpec(
 
   // 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) {
-    if (!OmpVerifyModifiers(spec, llvm::omp::OMPC_uses_allocators,
-            GetContext().clauseSource, context_)) {
-      return;
-    }
+    OmpVerifyModifiers(spec, llvm::omp::OMPC_uses_allocators,
+        GetContext().clauseSource, context_);
   }
 
   auto &modifiers{OmpGetModifiers(spec)};
diff --git a/flang/test/Semantics/OpenMP/uses-allocators-version50.f90 b/flang/test/Semantics/OpenMP/uses-allocators-version50.f90
index 474fea2e55707..197b8385fdafa 100644
--- a/flang/test/Semantics/OpenMP/uses-allocators-version50.f90
+++ b/flang/test/Semantics/OpenMP/uses-allocators-version50.f90
@@ -32,14 +32,23 @@ subroutine uses_allocators_50
   x = 4
   !$omp end target
 
+  ! Accepting the newer syntax with a warning does not skip the remaining
+  ! checks on the specification.
+  !WARNING: 'traits-array' modifier is not supported in OpenMP v5.0, try -fopenmp-version=52
+  !ERROR: The traits array must be a named constant array
+  !$omp target uses_allocators(traits(1): my_alloc)
+  x = 5
+  !$omp end target
+
   !WARNING: 'mem-space' modifier is not supported in OpenMP v5.0, try -fopenmp-version=52
+  !ERROR: A non-predefined allocator 'my_alloc' in a USES_ALLOCATORS clause must have traits specified in OpenMP v5.0
   !$omp target uses_allocators(memspace(omp_default_mem_space): my_alloc)
-  x = 5
+  x = 6
   !$omp end target
 
   !ERROR: A non-predefined allocator 'omp_null_allocator' in a USES_ALLOCATORS clause must be a variable
   !$omp target uses_allocators(omp_null_allocator(tr))
-  x = 6
+  x = 7
   !$omp end target
 end subroutine
 
diff --git a/flang/test/Semantics/OpenMP/uses-allocators-version51.f90 b/flang/test/Semantics/OpenMP/uses-allocators-version51.f90
index 231774fdd9970..bb262b5ecb123 100644
--- a/flang/test/Semantics/OpenMP/uses-allocators-version51.f90
+++ b/flang/test/Semantics/OpenMP/uses-allocators-version51.f90
@@ -26,9 +26,17 @@ subroutine uses_allocators_51
   x = 3
   !$omp end target
 
+  ! Accepting the newer syntax with a warning does not skip the remaining
+  ! checks on the specification.
+  !WARNING: 'traits-array' modifier is not supported in OpenMP v5.1, try -fopenmp-version=52
+  !ERROR: The traits array must be a named constant array
+  !$omp target uses_allocators(traits(1): my_alloc)
+  x = 4
+  !$omp end target
+
   !ERROR: A predefined allocator 'omp_default_mem_alloc' in a USES_ALLOCATORS clause cannot have modifiers or traits specified
   !$omp target uses_allocators(omp_default_mem_alloc(tr))
-  x = 4
+  x = 5
   !$omp end target
 end subroutine
 



More information about the flang-commits mailing list