[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
Tue Aug 4 07:07:14 PDT 2026


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

## Summary

- Add parse-tree, parser, unparser, and semantic support for the OpenMP `USES_ALLOCATORS` clause.
- Support allocator traits and version-aware handling of the deprecated comma-separated list syntax.
- In OpenMP 6.0 mode, make predefined allocator names available by name without an additional local handle-kind check.
- Diagnose the unsupported multiple-specification form while keeping lowering explicitly unimplemented.
- Add parser, semantics, version, handle-kind, module-use, lowering-NYI, and documentation coverage.


>From 150c003a0da6ed97892733300a327569d19323f9 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] [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 729bcbc9b7cab..1c66724aa9eb3 100644
--- a/flang/include/flang/Parser/dump-parse-tree.h
+++ b/flang/include/flang/Parser/dump-parse-tree.h
@@ -689,6 +689,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)
@@ -759,6 +760,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)
@@ -778,6 +780,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 497d985c1c8c5..ea4f9187911d8 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -4217,6 +4217,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 ->
@@ -4353,6 +4361,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 ->
@@ -5138,6 +5154,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 d6f225f329f41..c8788e2e89547 100644
--- a/flang/include/flang/Semantics/openmp-modifiers.h
+++ b/flang/include/flang/Semantics/openmp-modifiers.h
@@ -95,6 +95,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);
@@ -107,6 +108,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 941e875259a20..6bcfa67a5b814 100644
--- a/flang/lib/Lower/OpenMP/Clauses.cpp
+++ b/flang/lib/Lower/OpenMP/Clauses.cpp
@@ -1785,8 +1785,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 7b467c73d8fc1..f9ca489646527 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -987,6 +987,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)))
@@ -1042,6 +1045,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) ||
@@ -1162,6 +1168,12 @@ TYPE_PARSER(sourced(construct<OmpToClause::Modifier>(
         construct<OmpToClause::Modifier>(Parser<OmpMapper>{}) ||
         construct<OmpToClause::Modifier>(Parser<OmpIterator>{})))))
 
+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>{})))
 
@@ -1521,6 +1533,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) >>
@@ -1744,6 +1805,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 23d04f2e3ea42..8286030952158 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 8e845fbf5993b..6e2f17d261b2f 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -5385,6 +5385,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 f3ee8917356fa..11e9d4e0f3b7d 100644
--- a/flang/lib/Semantics/openmp-modifiers.cpp
+++ b/flang/lib/Semantics/openmp-modifiers.cpp
@@ -538,6 +538,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{
@@ -737,6 +753,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 a119dff67c30e..4e0e427b9fbfe 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";



More information about the flang-commits mailing list