[clang] [flang] [llvm] [Flang][OpenMP] Add OpenMP target-memory allocator runtime (PR #216751)

Michael Klemm via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 18 06:03:58 PDT 2026


https://github.com/mjklemm updated https://github.com/llvm/llvm-project/pull/216751

>From 983c11a0c78e447adf3d9080d868e20a7110e88a Mon Sep 17 00:00:00 2001
From: Michael Klemm <michael.klemm at amd.com>
Date: Fri, 14 Aug 2026 20:45:42 +0200
Subject: [PATCH 1/5] [flang-rt] Add OpenMP target-memory allocator runtime

Introduce a Fortran runtime component that registers an allocator (index 1
in the Fortran runtime's allocator registry) backed by the OpenMP host API
routines omp_target_alloc/omp_target_free.  Enables ALLOCATABLE variables
to live in device memory when compiled with -fopenmp-default-allocate=target
(compiler support added separately).

Contents:
  * flang-rt/lib/openmp/omp_alloc.cpp -- OpenMPAlloc/OpenMPFree callbacks
    plus the RTDEF entry points OpenMPRegisterAllocator and
    OpenMPAllocatableSetAllocIdx.
  * flang-rt/lib/openmp/omp_util.cpp -- PointerDeviceMap, a small
    thread-safe pointer->device-id map used so that OpenMPFree can pass
    the correct device to omp_target_free even when the default device
    changes between allocation and deallocation.
  * flang/include/flang/Runtime/OpenMP/{omp_alloc,omp_util}.h -- public
    declarations.
  * flang-rt/lib/openmp/CMakeLists.txt -- builds flang_rt.openmp as a
    STATIC+SHARED library, installed with the toolchain.
  * flang-rt/lib/CMakeLists.txt -- gates the new subdirectory on
    "openmp" being present in LLVM_ENABLE_RUNTIMES.

The OpenMP host entry points are declared locally to avoid a build-time
dependency on omp.h from the OpenMP runtime headers.

[flang][OpenMP] Add -fopenmp-default-allocate= driver flag and lowering

Introduce a new experimental driver flag, -fopenmp-default-allocate=,
that selects the default allocator for OpenMP-managed ALLOCATABLE
variables.  Two values are accepted:

  * =host   -- no behavior change (allocations stay in host memory).
  * =target -- allocations are routed through the Fortran runtime's
               allocator index 1, which is provided by the flang_rt.openmp
               component and dispatches to omp_target_alloc /
               omp_target_free on the current default OpenMP device.

Compiler pieces:
  * clang/include/clang/Options/Options.td -- flag definition.
  * clang/include/clang/Basic/DiagnosticDriverKinds.td -- warning that
    labels the option as experimental.
  * clang/lib/Driver/ToolChains/Flang.cpp -- validates the value,
    emits the experimental-feature warning, forwards the flag to -fc1,
    and (for =target) enables the existing -use-alloc-runtime MLIR
    pass so ALLOCATE always goes through the runtime path.
  * flang/include/flang/Support/Fortran-features.h -- adds a new
    LanguageFeature bit OpenMPDefaultAllocator that carries the choice
    into semantics/lowering.
  * flang/lib/Frontend/CompilerInvocation.cpp -- fc1 parses the flag
    and sets the language feature.
  * flang/lib/Optimizer/Builder/Runtime/Main.{h,cpp} -- genMain gains
    an enableOpenMPAllocator parameter; when set, it emits a call to
    _FortranAOpenMPRegisterAllocator so the allocator is registered
    once at program start.
  * flang/lib/Lower/Bridge.cpp -- passes the feature bit through to
    genMain.
  * flang/lib/Lower/Allocatable.cpp -- on the runtime-allocate path,
    emits _FortranAOpenMPAllocatableSetAllocIdx to tag the descriptor
    with allocator id 1 before ALLOCATE.  A helper skips the tag when
    the code is already inside an omp.target region or a declare-target
    function, where the target runtime handles allocations directly.

Depends on the flang_rt.openmp runtime component (added in the preceding
commit); the compiler references its RT entry points
OpenMPRegisterAllocator and OpenMPAllocatableSetAllocIdx.

Tests:
  * flang/test/Driver/fopenmp-default-allocate.f90 -- driver forwarding
    and value validation.
  * flang/test/Lower/OpenMP/omp_alloc_init{,_host}.f90 -- verifies the
    program-start OpenMPRegisterAllocator call is (or is not) emitted.
  * flang/test/Lower/AMDGPU/allocate_*_omp_*.f90,
    allocate_runtime_alloc_idx{,_host}.f90 -- verifies the
    OpenMPAllocatableSetAllocIdx tagging and its suppression in device
    code.
---
 .../clang/Basic/DiagnosticDriverKinds.td      |   2 +
 clang/include/clang/Options/Options.td        |   4 +
 clang/lib/Driver/ToolChain.cpp                |   4 +-
 clang/lib/Driver/ToolChains/Flang.cpp         |  15 +++
 clang/test/Misc/warning-flags.c               |   3 +-
 flang-rt/lib/CMakeLists.txt                   |   3 +
 flang-rt/lib/openmp/CMakeLists.txt            |  27 +++++
 flang-rt/lib/openmp/omp_alloc.cpp             | 107 ++++++++++++++++++
 flang-rt/lib/openmp/omp_util.cpp              |  73 ++++++++++++
 .../flang/Optimizer/Builder/Runtime/Main.h    |   3 +-
 .../include/flang/Runtime/OpenMP/omp_alloc.h  |  38 +++++++
 flang/include/flang/Runtime/OpenMP/omp_util.h |  53 +++++++++
 .../include/flang/Support/Fortran-features.h  |   1 +
 flang/lib/Frontend/CompilerInvocation.cpp     |  13 +++
 flang/lib/Lower/Allocatable.cpp               |  57 ++++++++++
 flang/lib/Lower/Bridge.cpp                    |   2 +
 flang/lib/Optimizer/Builder/Runtime/Main.cpp  |  10 +-
 flang/lib/Support/Fortran-features.cpp        |   1 +
 .../test/Driver/fopenmp-default-allocate.f90  |  28 +++++
 ...allocate_deallocate_omp_declare_target.f90 |  24 ++++
 ...e_deallocate_omp_declare_target_nested.f90 |  25 ++++
 .../AMDGPU/allocate_deallocate_omp_target.f90 |  24 ++++
 .../AMDGPU/allocate_runtime_alloc_idx.f90     |  20 ++++
 .../allocate_runtime_alloc_idx_host.f90       |  17 +++
 flang/test/Lower/OpenMP/omp_alloc_init.f90    |   5 +
 .../test/Lower/OpenMP/omp_alloc_init_host.f90 |   5 +
 26 files changed, 560 insertions(+), 4 deletions(-)
 create mode 100644 flang-rt/lib/openmp/CMakeLists.txt
 create mode 100644 flang-rt/lib/openmp/omp_alloc.cpp
 create mode 100644 flang-rt/lib/openmp/omp_util.cpp
 create mode 100644 flang/include/flang/Runtime/OpenMP/omp_alloc.h
 create mode 100644 flang/include/flang/Runtime/OpenMP/omp_util.h
 create mode 100644 flang/test/Driver/fopenmp-default-allocate.f90
 create mode 100644 flang/test/Lower/AMDGPU/allocate_deallocate_omp_declare_target.f90
 create mode 100644 flang/test/Lower/AMDGPU/allocate_deallocate_omp_declare_target_nested.f90
 create mode 100644 flang/test/Lower/AMDGPU/allocate_deallocate_omp_target.f90
 create mode 100644 flang/test/Lower/AMDGPU/allocate_runtime_alloc_idx.f90
 create mode 100644 flang/test/Lower/AMDGPU/allocate_runtime_alloc_idx_host.f90
 create mode 100644 flang/test/Lower/OpenMP/omp_alloc_init.f90
 create mode 100644 flang/test/Lower/OpenMP/omp_alloc_init_host.f90

diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index 91895d4957cf7..fe8a73e6c5dd3 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -186,6 +186,8 @@ def warn_openmp_spec_incomplete : Warning<
   "the specification for OpenMP version %0 is still under development; "
   "the syntax and semantics of new features may be subject to change">,
   InGroup<ExperimentalOption>;
+def warn_openmp_default_allocate_experimental
+    : Warning<"-fopenmp-default-allocate= is an experimental feature">;
 def err_drv_invalid_thread_model_for_target : Error<
   "invalid thread model '%0' in '%1' for this target">;
 def err_drv_invalid_linker_name : Error<
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 3abb04a5f36c7..6a9eceed05012 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4145,6 +4145,10 @@ def fomit_frame_pointer : Flag<["-"], "fomit-frame-pointer">, Group<f_Group>,
   "Some stack unwinding cases, such as profilers and sanitizers, may prefer specifying -fno-omit-frame-pointer. "
   "On many targets, -O1 and higher omit the frame pointer by default. "
   "-m[no-]omit-leaf-frame-pointer takes precedence for leaf functions">;
+def fopenmp_default_allocate_EQ : Joined<["-"], "fopenmp-default-allocate=">,
+  Group<f_Group>, Visibility<[FlangOption, FC1Option]>,
+  HelpText<"Set default allocator for OpenMP offloading (=target or =host)">,
+  Values<"target,host">;
 def fopenmp : Flag<["-"], "fopenmp">, Group<f_Group>,
   Flags<[NoArgumentUnused]>,
   Visibility<[ClangOption, CC1Option, FlangOption, FC1Option]>,
diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp
index 37db75a1bfc14..edb9b0e560825 100644
--- a/clang/lib/Driver/ToolChain.cpp
+++ b/clang/lib/Driver/ToolChain.cpp
@@ -997,9 +997,11 @@ void ToolChain::addFortranRuntimeLibs(const ArgList &Args,
       CmdArgs.push_back("-lexecinfo");
   }
 
-  // libomp needs libatomic for atomic operations if using libgcc
   if (Args.hasFlag(options::OPT_fopenmp, options::OPT_fopenmp_EQ,
                    options::OPT_fno_openmp, false)) {
+    CmdArgs.push_back("-lflang_rt.openmp");
+
+    // libomp needs libatomic for atomic operations if using libgcc
     Driver::OpenMPRuntimeKind OMPRuntime = getDriver().getOpenMPRuntime(Args);
     ToolChain::RuntimeLibType RuntimeLib = GetRuntimeLibType(Args);
     if ((OMPRuntime == Driver::OMPRT_OMP &&
diff --git a/clang/lib/Driver/ToolChains/Flang.cpp b/clang/lib/Driver/ToolChains/Flang.cpp
index a48e41159f367..84f1734cd19eb 100644
--- a/clang/lib/Driver/ToolChains/Flang.cpp
+++ b/clang/lib/Driver/ToolChains/Flang.cpp
@@ -1310,6 +1310,21 @@ void Flang::ConstructJob(Compilation &C, const JobAction &JA,
 
   addFortranDialectOptions(Args, CmdArgs);
 
+  if (const Arg *A =
+          Args.getLastArg(options::OPT_fopenmp_default_allocate_EQ)) {
+    StringRef Val(A->getValue());
+    if (Val != "target" && Val != "host") {
+      D.Diag(diag::err_drv_invalid_value) << A->getAsString(Args) << Val;
+    } else {
+      D.Diag(diag::warn_openmp_default_allocate_experimental);
+      CmdArgs.push_back(Args.MakeArgString("-fopenmp-default-allocate=" + Val));
+      if (Val == "target") {
+        CmdArgs.push_back("-mmlir");
+        CmdArgs.push_back("-use-alloc-runtime");
+      }
+    }
+  }
+
   // 'flang -E' always produces output that is suitable for use as fixed form
   // Fortran. However it is only valid free form source if the original is also
   // free form. Ensure this logic does not incorrectly assume fixed-form for
diff --git a/clang/test/Misc/warning-flags.c b/clang/test/Misc/warning-flags.c
index 3dc4bb55aa69c..f4d38386c25c9 100644
--- a/clang/test/Misc/warning-flags.c
+++ b/clang/test/Misc/warning-flags.c
@@ -18,7 +18,7 @@ This test serves two purposes:
 
 The list of warnings below should NEVER grow.  It should gradually shrink to 0.
 
-CHECK: Warnings without flags (56):
+CHECK: Warnings without flags (57):
 
 CHECK-NEXT:   ext_expected_semi_decl_list
 CHECK-NEXT:   ext_missing_whitespace_after_macro_name
@@ -61,6 +61,7 @@ CHECK-NEXT:   warn_not_compound_assign
 CHECK-NEXT:   warn_objc_property_copy_missing_on_block
 CHECK-NEXT:   warn_objc_protocol_qualifier_missing_id
 CHECK-NEXT:   warn_on_superclass_use
+CHECK-NEXT:   warn_openmp_default_allocate_experimental
 CHECK-NEXT:   warn_pp_convert_to_positive
 CHECK-NEXT:   warn_pp_expr_overflow
 CHECK-NEXT:   warn_pp_line_decimal
diff --git a/flang-rt/lib/CMakeLists.txt b/flang-rt/lib/CMakeLists.txt
index 58a7a24c19e0c..7b84e38ff6498 100644
--- a/flang-rt/lib/CMakeLists.txt
+++ b/flang-rt/lib/CMakeLists.txt
@@ -9,6 +9,9 @@
 if (FLANG_RT_ENABLE_STATIC OR FLANG_RT_ENABLE_SHARED)
   add_subdirectory(quadmath)
   add_subdirectory(runtime)
+  if ("openmp" IN_LIST LLVM_ENABLE_RUNTIMES)
+    add_subdirectory(openmp)
+  endif()
   if (FLANG_RT_INCLUDE_CUF)
     add_subdirectory(cuda)
   endif()
diff --git a/flang-rt/lib/openmp/CMakeLists.txt b/flang-rt/lib/openmp/CMakeLists.txt
new file mode 100644
index 0000000000000..ced202478d253
--- /dev/null
+++ b/flang-rt/lib/openmp/CMakeLists.txt
@@ -0,0 +1,27 @@
+#===-- lib/openmp/CMakeLists.txt --------------------------------------------===#
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+#===------------------------------------------------------------------------===#
+
+# Check that Umpire exits in the directory given at CMake
+# TODO: this was disabled to get to an easier build procedure for now
+#message(STATUS "Using Umpire in directory ${FLANG_RT_UMPIRE_DIR}")
+#set(umpire_DIR ${FLANG_RT_UMPIRE_DIR})
+#find_package(umpire REQUIRED PATHS ${FLANG_RT_UMPIRE_DIR}/lib/cmake/umpire)
+
+add_flangrt_library(flang_rt.openmp STATIC SHARED
+  omp_alloc.cpp
+  omp_util.cpp
+  INSTALL_WITH_TOOLCHAIN
+)
+
+#if (TARGET flang_rt.openmp.static)
+#  target_include_directories(flang_rt.openmp.static PRIVATE ${FLANG_RT_UMPIRE_DIR}/include)
+#endif()
+#
+#if (TARGET flang_rt.openmp.shared)
+#  target_include_directories(flang_rt.openmp.shared PRIVATE ${FLANG_RT_UMPIRE_DIR}/include)
+#endif()
diff --git a/flang-rt/lib/openmp/omp_alloc.cpp b/flang-rt/lib/openmp/omp_alloc.cpp
new file mode 100644
index 0000000000000..4cc93c4b0ca40
--- /dev/null
+++ b/flang-rt/lib/openmp/omp_alloc.cpp
@@ -0,0 +1,107 @@
+//===-- lib/openmp/omp_alloc.cpp ---------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#define ALLOC_DEBUG 1
+
+#include "flang/Runtime/OpenMP/omp_alloc.h"
+#include "flang-rt/runtime/allocator-registry.h"
+#include "flang-rt/runtime/descriptor.h"
+#include "flang-rt/runtime/terminator.h"
+#include "flang/Runtime/OpenMP/omp_util.h"
+#include "flang/Support/Fortran.h"
+#include <cstdio>
+#include <cstdlib>
+
+namespace Fortran::runtime::omp {
+
+static bool debugEnabled;
+
+// Declare OpenMP memory management routines to avoid importing
+// definitions via "omp.h" (and thus create a dependency to the
+// OpenMP runtime library code).
+extern "C" int omp_get_default_device(void);
+extern "C" void *omp_target_alloc(std::size_t, int);
+extern "C" void omp_target_free(void *, int);
+
+// Track which device each pointer was allocated on so that
+// OpenMPFree can pass the correct device ID to omp_target_free,
+// even if omp_set_default_device() was called between ALLOCATE
+// and DEALLOCATE.
+static PointerDeviceMap allocDeviceMap;
+
+/// Allocate \p AllocationSize bytes on the current default OpenMP device.
+static void *OpenMPAlloc(std::size_t AllocationSize, std::size_t, std::int64_t *) {
+#if ALLOC_DEBUG
+  if (debugEnabled) {
+    std::fprintf(stderr, "[OMP_ALLOC] %s(%zu) (%s:%d)\n", __PRETTY_FUNCTION__,
+        AllocationSize, __FILE__, __LINE__);
+  }
+#endif
+  int device{omp_get_default_device()};
+  void *pointer{omp_target_alloc(AllocationSize, device)};
+  if (pointer) {
+    allocDeviceMap.insert(pointer, device);
+  }
+#if ALLOC_DEBUG
+  if (debugEnabled) {
+    std::fprintf(stderr,
+        "[OMP_ALLOC] pointer of size %zu allocated at %p"
+        " on device %d.\n",
+        AllocationSize, pointer, device);
+  }
+#endif
+  return pointer;
+}
+
+/// Free a pointer previously allocated by OpenMPAlloc on the correct device.
+static void OpenMPFree(void *pointer) {
+  int device{allocDeviceMap.removeAndGet(pointer)};
+  if (device == -1) {
+    Terminator{__FILE__, __LINE__}.Crash(
+        "OpenMPFree: pointer %p was not allocated by OpenMPAlloc", pointer);
+  }
+#if ALLOC_DEBUG
+  if (debugEnabled) {
+    std::fprintf(stderr, "[OMP_ALLOC] %s(%p) device %d (%s:%d)\n",
+        __PRETTY_FUNCTION__, pointer, device, __FILE__, __LINE__);
+  }
+#endif
+  omp_target_free(pointer, device);
+}
+
+extern "C" {
+void RTDEF(OpenMPRegisterAllocator)() {
+#if ALLOC_DEBUG
+  debugEnabled = false;
+  if (const char *env = std::getenv("OMP_ALLOC_DEBUG")) {
+    debugEnabled = env[0] != '0' && env[0] != '\0';
+  }
+  if (debugEnabled) {
+    std::fprintf(stderr, "[OMP_ALLOC] %s (%s:%d)\n", __PRETTY_FUNCTION__,
+        __FILE__, __LINE__);
+    std::fprintf(
+        stderr, "[OMP_ALLOC] registering OpenMP device memory allocator\n");
+  }
+#endif
+  allocatorRegistry.Register(1, {&OpenMPAlloc, &OpenMPFree});
+}
+
+void RTDEF(OpenMPAllocatableSetAllocIdx)(Descriptor &descriptor, int pos) {
+  if (descriptor.IsAllocatable() && !descriptor.IsAllocated()) {
+#if ALLOC_DEBUG
+    if (debugEnabled) {
+      std::fprintf(
+          stderr, "[OMP_ALLOC] OpenMPAllocatableSetAllocIdx = %d \n", pos);
+    }
+#endif
+    descriptor.SetAllocIdx(pos);
+  }
+}
+} // extern "C"
+
+} // namespace Fortran::runtime::omp
diff --git a/flang-rt/lib/openmp/omp_util.cpp b/flang-rt/lib/openmp/omp_util.cpp
new file mode 100644
index 0000000000000..cabef7574363d
--- /dev/null
+++ b/flang-rt/lib/openmp/omp_util.cpp
@@ -0,0 +1,73 @@
+//===-- lib/openmp/omp_util.cpp ----------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Implementation of PointerDeviceMap -- a thread-safe pointer-to-device-ID
+// map used by the OpenMP allocator runtime to track allocation origins.
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Runtime/OpenMP/omp_util.h"
+#include "flang-rt/runtime/lock.h"
+#include "flang-rt/runtime/terminator.h"
+#include <cstdlib>
+#include <cstring>
+
+namespace Fortran::runtime::omp {
+
+static constexpr std::size_t initialCapacity{256};
+
+static Lock pointerDeviceMapLock;
+
+/// Double the capacity of the entries array (or set it to initialCapacity if
+/// empty).  Crashes on allocation failure.  Must be called under the lock.
+void PointerDeviceMap::grow() {
+  std::size_t newCapacity = capacity_ ? capacity_ * 2 : initialCapacity;
+  Entry *newEntries =
+      static_cast<Entry *>(std::realloc(entries_, newCapacity * sizeof(Entry)));
+  if (!newEntries) {
+    Terminator{__FILE__, __LINE__}.Crash(
+        "PointerDeviceMap: realloc failed (capacity %zu)", newCapacity);
+  }
+  entries_ = newEntries;
+  capacity_ = newCapacity;
+}
+
+/// Record that \p pointer was allocated on \p device.  Thread-safe.
+void PointerDeviceMap::insert(void *pointer, int device) {
+  CriticalSection guard(pointerDeviceMapLock);
+  if (count_ == capacity_) {
+    grow();
+  }
+  entries_[count_++] = {pointer, device};
+}
+
+/// Remove \p pointer from the map and return its device ID, or -1 if not
+/// found.  Uses swap-with-last for O(1) removal.  Thread-safe.
+int PointerDeviceMap::removeAndGet(void *pointer) {
+  CriticalSection guard(pointerDeviceMapLock);
+  for (std::size_t i = 0; i < count_; ++i) {
+    if (entries_[i].pointer == pointer) {
+      int device = entries_[i].device;
+      // Swap with last entry and shrink.
+      entries_[i] = entries_[--count_];
+      return device;
+    }
+  }
+  return -1;
+}
+
+/// Print all (pointer, device) entries to stderr.  Thread-safe.
+/// Can be used for debugging purposes.
+void PointerDeviceMap::dump() const {
+  CriticalSection guard(pointerDeviceMapLock);
+  for (std::size_t i = 0; i < count_; ++i) {
+    std::fprintf(stderr, "%p -> %d\n", entries_[i].pointer, entries_[i].device);
+  }
+}
+
+} // namespace Fortran::runtime::omp
diff --git a/flang/include/flang/Optimizer/Builder/Runtime/Main.h b/flang/include/flang/Optimizer/Builder/Runtime/Main.h
index 1acc3cbed35c5..4bce249c2c3b2 100644
--- a/flang/include/flang/Optimizer/Builder/Runtime/Main.h
+++ b/flang/include/flang/Optimizer/Builder/Runtime/Main.h
@@ -25,7 +25,8 @@ namespace fir::runtime {
 
 void genMain(fir::FirOpBuilder &builder, mlir::Location loc,
              const std::vector<Fortran::lower::EnvironmentDefault> &defs,
-             bool initCuda = false, bool initCoarrayEnv = false,
+             bool initCuda = false, bool enableOpenMPAllocator = false,
+             bool initCoarrayEnv = false,
              unsigned fpExceptionTraps = 0);
 }
 
diff --git a/flang/include/flang/Runtime/OpenMP/omp_alloc.h b/flang/include/flang/Runtime/OpenMP/omp_alloc.h
new file mode 100644
index 0000000000000..ba5fb95c2cade
--- /dev/null
+++ b/flang/include/flang/Runtime/OpenMP/omp_alloc.h
@@ -0,0 +1,38 @@
+//===-- include/flang/Runtime/OpenMP/omp_alloc.h ----------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef FORTRAN_RUNTIME_OMP_ALLOC_H_
+#define FORTRAN_RUNTIME_OMP_ALLOC_H_
+
+#include "flang/Runtime/descriptor-consts.h"
+#include "flang/Runtime/entry-names.h"
+
+namespace Fortran::runtime::omp {
+
+extern "C" {
+
+/// Register the OpenMP target device allocator with the Fortran runtime's
+/// allocator registry.  Called once from the generated main() when
+/// -fopenmp-default-allocate=target is active.  The allocator uses
+/// omp_target_alloc/omp_target_free to place Fortran ALLOCATABLE storage
+/// on the current default device.  The environment variable OMP_ALLOC
+/// (default: "openmp") selects the allocator backend; OMP_ALLOC_DEBUG
+/// enables diagnostic tracing to stderr.
+void RTDECL(OpenMPRegisterAllocator)();
+
+/// Set the allocator index on an allocatable descriptor so that subsequent
+/// AllocatableAllocate calls route through the registered OpenMP allocator.
+/// \p descriptor must be an unallocated ALLOCATABLE; \p pos is the allocator
+/// registry slot (typically 1).  No-op if the descriptor is already allocated
+/// or is not allocatable.
+void RTDECL(OpenMPAllocatableSetAllocIdx)(Descriptor &descriptor, int pos);
+
+}
+
+} // namespace Fortran::runtime::omp
+#endif // FORTRAN_RUNTIME_OMP_ALLOC_H_
diff --git a/flang/include/flang/Runtime/OpenMP/omp_util.h b/flang/include/flang/Runtime/OpenMP/omp_util.h
new file mode 100644
index 0000000000000..447954cdc1240
--- /dev/null
+++ b/flang/include/flang/Runtime/OpenMP/omp_util.h
@@ -0,0 +1,53 @@
+//===-- include/flang/Runtime/OpenMP/omp_util.h -----------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef FORTRAN_RUNTIME_OMP_UTIL_H_
+#define FORTRAN_RUNTIME_OMP_UTIL_H_
+
+#include <cstddef>
+
+namespace Fortran::runtime::omp {
+
+/// A thread-safe map from allocation pointer to device ID.
+///
+/// Used to remember which OpenMP device each pointer was allocated on,
+/// so that deallocation can target the correct device even if
+/// omp_set_default_device() was called in between.
+///
+/// Implemented as a dynamically-grown flat array with linear search and
+/// a global lock, to avoid pulling in C++ runtime dependencies (e.g.
+/// std::unordered_map).  This is adequate for the expected allocation
+/// counts in typical Fortran programs.
+class PointerDeviceMap {
+public:
+  /// Record that \p pointer was allocated on \p device.
+  void insert(void *pointer, int device);
+
+  /// Remove the entry for \p pointer and return the device ID it was
+  /// allocated on.  Returns -1 if \p pointer is not in the map.
+  int removeAndGet(void *pointer);
+
+  /// Print all entries to stderr (for debugging).
+  void dump() const;
+
+private:
+  struct Entry {
+    void *pointer;
+    int device;
+  };
+
+  void grow();
+
+  Entry *entries_{nullptr};
+  std::size_t count_{0};
+  std::size_t capacity_{0};
+};
+
+} // namespace Fortran::runtime::omp
+
+#endif // FORTRAN_RUNTIME_OMP_UTIL_H_
diff --git a/flang/include/flang/Support/Fortran-features.h b/flang/include/flang/Support/Fortran-features.h
index 4ee956a0b4a4f..6f835bc82388f 100644
--- a/flang/include/flang/Support/Fortran-features.h
+++ b/flang/include/flang/Support/Fortran-features.h
@@ -55,6 +55,7 @@ ENUM_CLASS(LanguageFeature, BackslashEscapes, OldDebugLines,
     SavedLocalInSpecExpr, PrintNamelist, AssumedRankPassedToNonAssumedRank,
     IgnoreIrrelevantAttributes, Unsigned, ContiguousOkForSeqAssociation,
     ForwardRefExplicitTypeDummy, InaccessibleDeferredOverride,
+    OpenMPDefaultAllocator,
     CudaWarpMatchFunction, DoConcurrentOffload, TransferBOZ, Coarray,
     PointerPassObject, MultipleIdenticalDATA,
     DefaultStructConstructorNullPointer, AssumedRankIoItem,
diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp
index b57bc4583be38..f14c990bbf0da 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -947,6 +947,19 @@ static bool parseFrontendArgs(FrontendOptions &opts, llvm::opt::ArgList &args,
                                     clang::options::OPT_fno_save_main_program,
                                     false));
 
+  // -fopenmp-default-allocate={target,host}
+  if (const auto *arg =
+          args.getLastArg(clang::options::OPT_fopenmp_default_allocate_EQ)) {
+    llvm::StringRef val = arg->getValue();
+    if (val == "target") {
+      opts.features.Enable(
+          Fortran::common::LanguageFeature::OpenMPDefaultAllocator);
+    } else if (val != "host") {
+      diags.Report(clang::diag::err_drv_invalid_value)
+          << arg->getAsString(args) << val;
+    }
+  }
+
   if (args.hasArg(clang::options::OPT_falternative_parameter_statement)) {
     opts.features.Enable(Fortran::common::LanguageFeature::OldStyleParameter);
   }
diff --git a/flang/lib/Lower/Allocatable.cpp b/flang/lib/Lower/Allocatable.cpp
index f51342b27a19d..865bc5fd38f9f 100644
--- a/flang/lib/Lower/Allocatable.cpp
+++ b/flang/lib/Lower/Allocatable.cpp
@@ -33,8 +33,11 @@
 #include "flang/Parser/parse-tree.h"
 #include "flang/Runtime/allocatable.h"
 #include "flang/Runtime/pointer.h"
+#include "flang/Runtime/OpenMP/omp_alloc.h"
 #include "flang/Semantics/tools.h"
 #include "flang/Semantics/type.h"
+#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
+#include "mlir/Dialect/OpenMP/OpenMPInterfaces.h"
 #include "flang/Support/Fortran-features.h"
 #include "llvm/Support/CommandLine.h"
 
@@ -163,6 +166,50 @@ static void genRuntimeInitCharacter(fir::FirOpBuilder &builder,
   fir::CallOp::create(builder, loc, callee, convertedArgs);
 }
 
+/// Return true if \p region is (transitively) nested inside an omp.target
+/// region or inside a function marked as declare target.
+static bool isRegionNestedInOmpTarget(mlir::Region &region) {
+  mlir::Operation *parentOp = region.getParentOp();
+  while (parentOp) {
+    if (auto declareTargetOp =
+            llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(parentOp)) {
+      if (declareTargetOp.isDeclareTarget())
+        return true;
+    }
+    if (llvm::isa<mlir::omp::TargetOp>(parentOp))
+      return true;
+    mlir::Region *parentRegion = parentOp->getParentRegion();
+    if (!parentRegion)
+      break;
+    parentOp = parentRegion->getParentOp();
+  }
+  return false;
+}
+
+/// Emit a call to the runtime that records the allocator index (\p allocatorId)
+/// on \p box so subsequent runtime allocations for \p box are serviced by the
+/// OpenMP target allocator.  Skipped inside device code, where allocations are
+/// handled directly by the target runtime.
+static void genOpenMPRuntimeDescriptorSetAllocIdx(
+    fir::FirOpBuilder &builder, mlir::Location loc,
+    const fir::MutableBoxValue &box, int allocatorId) {
+  if (isRegionNestedInOmpTarget(builder.getRegion()))
+    return;
+  auto *context = builder.getContext();
+  mlir::Type descriptorTy = box.getAddr().getType();
+  mlir::IntegerType posTy = builder.getI32Type();
+  mlir::func::FuncOp callee = builder.createFunction(
+      loc, RTNAME_STRING(OpenMPAllocatableSetAllocIdx),
+      mlir::FunctionType::get(context, {descriptorTy, posTy}, {}));
+  llvm::SmallVector<mlir::Value> args{box.getAddr()};
+  args.push_back(
+      builder.createIntegerConstant(loc, builder.getI32Type(), allocatorId));
+  llvm::SmallVector<mlir::Value> operands;
+  for (auto [fst, snd] : llvm::zip(args, callee.getFunctionType().getInputs()))
+    operands.emplace_back(builder.createConvert(loc, snd, fst));
+  fir::CallOp::create(builder, loc, callee, operands);
+}
+
 /// Generate a sequence of runtime calls to allocate memory.
 static mlir::Value genRuntimeAllocate(fir::FirOpBuilder &builder,
                                       mlir::Location loc,
@@ -534,6 +581,9 @@ class AllocateStmtHelper {
                             !alloc.type.IsPolymorphic() &&
                             !alloc.hasCoarraySpec() && !useAllocateRuntime &&
                             !box.isPointer() && !implicitManagedBacking;
+    const auto &langFeatures = converter.getFoldingContext().languageFeatures();
+    bool isOpenMPAllocatorEnabled = langFeatures.IsEnabled(
+        Fortran::common::LanguageFeature::OpenMPDefaultAllocator);
 
     if (inlineAllocation && !alloc.hasCoarraySpec() &&
         ((isCudaAllocate && isCudaDeviceContext) || !isCudaAllocate)) {
@@ -569,6 +619,8 @@ class AllocateStmtHelper {
           alloc.getCoarraySpec(), errorManager.errMsgAddr,
           errorManager.hasStatSpec());
     } else if (!isCudaAllocate) {
+      if (isOpenMPAllocatorEnabled)
+        genOpenMPRuntimeDescriptorSetAllocIdx(builder, loc, box, 1);
       stat = genRuntimeAllocate(builder, loc, box, errorManager);
       setPinnedToFalse();
     } else {
@@ -700,6 +752,9 @@ class AllocateStmtHelper {
       isCudaAllocate = propagateCUDAAttrsFromParent(alloc, cudaSymForAlloc);
     unsigned allocatorIdx = Fortran::lower::getAllocatorIdx(*cudaSymForAlloc);
     fir::ExtendedValue exv = isSource ? sourceExv : moldExv;
+    const auto &langFeatures = converter.getFoldingContext().languageFeatures();
+    bool isOpenMPAllocatorEnabled = langFeatures.IsEnabled(
+        Fortran::common::LanguageFeature::OpenMPDefaultAllocator);
 
     bool sourceIsDevice = false;
     if (const Fortran::semantics::Symbol *sym{GetLastSymbol(sourceExpr)})
@@ -730,6 +785,8 @@ class AllocateStmtHelper {
     } else if (isCudaAllocate || sourceIsDevice) {
       stat = genCudaAllocate(builder, loc, box, errorManager, *cudaSymForAlloc);
     } else {
+      if (isOpenMPAllocatorEnabled)
+        genOpenMPRuntimeDescriptorSetAllocIdx(builder, loc, box, 1);
       if (isSource)
         stat = genRuntimeAllocateSource(builder, loc, box, exv, errorManager);
       else
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index bff6b51e50e18..93b7c44610eeb 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -620,6 +620,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
                  Fortran::common::LanguageFeature::CUDA) &&
              getFoldingContext().languageFeatures().IsEnabled(
                  Fortran::common::LanguageFeature::CUDAInit)),
+            getFoldingContext().languageFeatures().IsEnabled(
+                Fortran::common::LanguageFeature::OpenMPDefaultAllocator),
             getFoldingContext().languageFeatures().IsEnabled(
                 Fortran::common::LanguageFeature::Coarray),
             bridge.getLoweringOptions().getFPExceptionTraps());
diff --git a/flang/lib/Optimizer/Builder/Runtime/Main.cpp b/flang/lib/Optimizer/Builder/Runtime/Main.cpp
index 2427088ec7f83..4a14331671e8a 100644
--- a/flang/lib/Optimizer/Builder/Runtime/Main.cpp
+++ b/flang/lib/Optimizer/Builder/Runtime/Main.cpp
@@ -24,7 +24,8 @@ using namespace Fortran::runtime;
 void fir::runtime::genMain(
     fir::FirOpBuilder &builder, mlir::Location loc,
     const std::vector<Fortran::lower::EnvironmentDefault> &defs, bool initCuda,
-    bool initCoarrayEnv, unsigned fpExceptionTraps) {
+    bool enableOpenMPAllocator, bool initCoarrayEnv,
+    unsigned fpExceptionTraps) {
   auto *context = builder.getContext();
   auto argcTy = builder.getDefaultIntegerType();
   auto ptrTy = mlir::LLVM::LLVMPointerType::get(context);
@@ -72,6 +73,13 @@ void fir::runtime::genMain(
   if (initCoarrayEnv)
     mif::InitOp::create(builder, loc);
 
+  if (enableOpenMPAllocator) {
+    auto registerFn =
+        builder.createFunction(loc, RTNAME_STRING(OpenMPRegisterAllocator),
+                               mlir::FunctionType::get(context, {}, {}));
+    fir::CallOp::create(builder, loc, registerFn);
+  }
+
   if (fpExceptionTraps != 0) {
     auto i32Ty = builder.getI32Type();
     auto enableFn =
diff --git a/flang/lib/Support/Fortran-features.cpp b/flang/lib/Support/Fortran-features.cpp
index 0af3ff61d18e1..cf2cd3c664cf7 100644
--- a/flang/lib/Support/Fortran-features.cpp
+++ b/flang/lib/Support/Fortran-features.cpp
@@ -156,6 +156,7 @@ LanguageFeatureControl::LanguageFeatureControl() {
   disable_.set(LanguageFeature::AssumedRankPassedToNonAssumedRank);
   disable_.set(LanguageFeature::Coarray);
   disable_.set(LanguageFeature::OpenAccDefaultNoneScalarsStrict);
+  disable_.set(LanguageFeature::OpenMPDefaultAllocator);
   // These warnings are enabled by default, but only because they used
   // to be unconditional.  TODO: prune this list
   warnLanguage_.set(LanguageFeature::ExponentMatchingKindParam);
diff --git a/flang/test/Driver/fopenmp-default-allocate.f90 b/flang/test/Driver/fopenmp-default-allocate.f90
new file mode 100644
index 0000000000000..9e637d2add4ec
--- /dev/null
+++ b/flang/test/Driver/fopenmp-default-allocate.f90
@@ -0,0 +1,28 @@
+! Check that the driver passes -fopenmp-default-allocate= through to fc1
+! and only adds -mmlir -use-alloc-runtime for target mode.
+
+! RUN: %flang -### -S -fopenmp-default-allocate=target %s -o - 2>&1 | FileCheck %s --check-prefix=TARGET
+! RUN: %flang -### -S -fopenmp-default-allocate=host %s -o - 2>&1 | FileCheck %s --check-prefix=HOST
+
+! TARGET: warning: -fopenmp-default-allocate= is an experimental feature
+! TARGET: "-fc1"
+! TARGET-SAME: "-fopenmp-default-allocate=target"
+! TARGET-SAME: "-mmlir" "-use-alloc-runtime"
+
+! HOST: warning: -fopenmp-default-allocate= is an experimental feature
+! HOST: "-fc1"
+! HOST-SAME: "-fopenmp-default-allocate=host"
+! HOST-NOT: "-mmlir"
+! HOST-NOT: "-use-alloc-runtime"
+
+! Check that invalid values are rejected at the driver level.
+! RUN: not %flang -fopenmp-default-allocate=invalid -S %s 2>&1 | FileCheck %s --check-prefix=DRV-INVALID
+! DRV-INVALID: error: invalid value 'invalid' in '-fopenmp-default-allocate=invalid'
+
+! Check that invalid values are also rejected at the frontend level.
+! RUN: not %flang_fc1 -fopenmp-default-allocate=invalid -S %s 2>&1 | FileCheck %s --check-prefix=FC1-INVALID
+! FC1-INVALID: error: invalid value 'invalid' in '-fopenmp-default-allocate=invalid'
+
+program fopenmp_default_allocate
+    ! do nothing
+end program fopenmp_default_allocate
\ No newline at end of file
diff --git a/flang/test/Lower/AMDGPU/allocate_deallocate_omp_declare_target.f90 b/flang/test/Lower/AMDGPU/allocate_deallocate_omp_declare_target.f90
new file mode 100644
index 0000000000000..b3adac6e331d6
--- /dev/null
+++ b/flang/test/Lower/AMDGPU/allocate_deallocate_omp_declare_target.f90
@@ -0,0 +1,24 @@
+! RUN: %flang -fopenmp-default-allocate=target -S -emit-llvm -fopenmp -fopenmp-targets=amdgcn-amd-amdhsa --offload-arch=gfx90a -o - %s | FileCheck %s --check-prefix=CHECK-OMP
+! RUN: %flang -fopenmp-default-allocate=target -S -emit-llvm -target amdgcn-- -o - %s | FileCheck %s --check-prefix=CHECK
+!REQUIRES: AFAR
+subroutine func_t_device()
+  !$omp declare target enter(func_t_device) device_type(nohost)
+    integer, ALLOCATABLE :: poly
+
+! CHECK-OMP-NOT: call void @_FortranAOpenMPAllocatableSetAllocIdx({{.*}}, i32 1)
+! CHECK: call void @_FortranAOpenMPAllocatableSetAllocIdx({{.*}}, i32 1)
+! CHECK-OMP: call i32 @_FortranAAllocatableAllocate
+! CHECK: call i32 @_FortranAAllocatableAllocate   
+    ALLOCATE(poly)
+
+! CHECK-OMP: call i32 @_FortranAAllocatableDeallocate
+! CHECK: call i32 @_FortranAAllocatableDeallocate
+    DEALLOCATE(poly)
+end subroutine func_t_device
+
+program main
+  implicit none
+  !$omp target
+    call func_t_device()
+  !$omp end target
+end program
diff --git a/flang/test/Lower/AMDGPU/allocate_deallocate_omp_declare_target_nested.f90 b/flang/test/Lower/AMDGPU/allocate_deallocate_omp_declare_target_nested.f90
new file mode 100644
index 0000000000000..d794cc6cc7cc5
--- /dev/null
+++ b/flang/test/Lower/AMDGPU/allocate_deallocate_omp_declare_target_nested.f90
@@ -0,0 +1,25 @@
+! RUN: %flang -fopenmp-default-allocate=target -S -emit-llvm -fopenmp -fopenmp-targets=amdgcn-amd-amdhsa --offload-arch=gfx90a -o - %s | FileCheck %s --check-prefix=CHECK-OMP
+! RUN: %flang -fopenmp-default-allocate=target -S -emit-llvm -target amdgcn-- -o - %s | FileCheck %s --check-prefix=CHECK
+!REQUIRES: AFAR
+subroutine func_t_device()
+  !$omp declare target enter(func_t_device) device_type(nohost)
+    integer, ALLOCATABLE :: poly
+    do j=1,10
+! CHECK-OMP-NOT: call void @_FortranAOpenMPAllocatableSetAllocIdx({{.*}}, i32 1)
+! CHECK: call void @_FortranAOpenMPAllocatableSetAllocIdx({{.*}}, i32 1)
+! CHECK-OMP: call i32 @_FortranAAllocatableAllocate
+! CHECK: call i32 @_FortranAAllocatableAllocate
+      ALLOCATE(poly)
+
+! CHECK-OMP: call i32 @_FortranAAllocatableDeallocate
+! CHECK: call i32 @_FortranAAllocatableDeallocate
+      DEALLOCATE(poly)
+    end do
+end subroutine func_t_device
+
+program main
+  implicit none
+  !$omp target
+    call func_t_device()
+  !$omp end target
+end program
diff --git a/flang/test/Lower/AMDGPU/allocate_deallocate_omp_target.f90 b/flang/test/Lower/AMDGPU/allocate_deallocate_omp_target.f90
new file mode 100644
index 0000000000000..f425e6b9d93ca
--- /dev/null
+++ b/flang/test/Lower/AMDGPU/allocate_deallocate_omp_target.f90
@@ -0,0 +1,24 @@
+! RUN: %flang -fopenmp-default-allocate=target -S -emit-llvm -fopenmp -fopenmp-targets=amdgcn-amd-amdhsa --offload-arch=gfx90a -o - %s | FileCheck %s --check-prefix=CHECK-OMP
+! RUN: %flang -fopenmp-default-allocate=target -S -emit-llvm -target amdgcn-- -o - %s | FileCheck %s --check-prefix=CHECK
+!REQUIRES: AFAR
+program main
+   implicit none
+   !$omp requires unified_shared_memory
+   REAL, DIMENSION(:), ALLOCATABLE :: poly
+   integer,parameter :: n = 10
+   integer :: i,j
+     !$omp target teams distribute parallel do private(poly)
+     do j=1,n
+
+! CHECK-OMP-NOT: call void @_FortranAOpenMPAllocatableSetAllocIdx({{.*}}, i32 1)
+! CHECK: call void @_FortranAOpenMPAllocatableSetAllocIdx({{.*}}, i32 1)
+! CHECK-OMP: call i32 @_FortranAAllocatableAllocate
+! CHECK: call i32 @_FortranAAllocatableAllocate
+       ALLOCATE(poly(1:3))
+       poly = 2.0_8
+! CHECK-OMP: call i32 @_FortranAAllocatableDeallocate
+! CHECK: call i32 @_FortranAAllocatableDeallocate
+       DEALLOCATE(poly)
+     enddo
+     !$omp end target teams distribute parallel do
+end program
diff --git a/flang/test/Lower/AMDGPU/allocate_runtime_alloc_idx.f90 b/flang/test/Lower/AMDGPU/allocate_runtime_alloc_idx.f90
new file mode 100644
index 0000000000000..6c215ec6c97e7
--- /dev/null
+++ b/flang/test/Lower/AMDGPU/allocate_runtime_alloc_idx.f90
@@ -0,0 +1,20 @@
+! RUN: %flang -fopenmp-default-allocate=target -S -emit-llvm --offload-targets=amdgcn-amd-amdhsa -o - %s | FileCheck %s
+
+subroutine allocate_deallocate()
+  real, allocatable :: x
+! CHECK: call void @_FortranAOpenMPAllocatableSetAllocIdx({{.*}}, i32 1)
+! CHECK: call i32 @_FortranAAllocatableAllocate
+  allocate(x)
+
+! CHECK: call i32 @_FortranAAllocatableDeallocate
+  deallocate(x)
+end subroutine
+
+subroutine test_allocatable_scalar(a)
+  real, save, allocatable :: x1, x2
+  real :: a
+
+! CHECK: call void @_FortranAOpenMPAllocatableSetAllocIdx({{.*}}, i32 1)
+! CHECK: call i32 @_FortranAAllocatableAllocateSource
+  allocate(x1, x2, source = a)
+end
diff --git a/flang/test/Lower/AMDGPU/allocate_runtime_alloc_idx_host.f90 b/flang/test/Lower/AMDGPU/allocate_runtime_alloc_idx_host.f90
new file mode 100644
index 0000000000000..2ebccd537d784
--- /dev/null
+++ b/flang/test/Lower/AMDGPU/allocate_runtime_alloc_idx_host.f90
@@ -0,0 +1,17 @@
+! RUN: %flang -fopenmp-default-allocate=host -S -emit-llvm --offload-targets=amdgcn-amd-amdhsa -o - %s 2>&1 | FileCheck %s
+
+! Verify that host mode does not insert OpenMPAllocatableSetAllocIdx calls.
+
+! CHECK-NOT: call void @_FortranAOpenMPAllocatableSetAllocIdx
+
+subroutine allocate_deallocate()
+  real, allocatable :: x
+  allocate(x)
+  deallocate(x)
+end subroutine
+
+subroutine test_allocatable_scalar(a)
+  real, save, allocatable :: x1, x2
+  real :: a
+  allocate(x1, x2, source = a)
+end
\ No newline at end of file
diff --git a/flang/test/Lower/OpenMP/omp_alloc_init.f90 b/flang/test/Lower/OpenMP/omp_alloc_init.f90
new file mode 100644
index 0000000000000..2375a9d668c39
--- /dev/null
+++ b/flang/test/Lower/OpenMP/omp_alloc_init.f90
@@ -0,0 +1,5 @@
+!RUN: %flang_fc1 -fopenmp -fopenmp-default-allocate=target -emit-fir %s -o - | FileCheck %s
+
+program omp_alloc_init
+    !CHECK: fir.call @_FortranAOpenMPRegisterAllocator()
+end program omp_alloc_init
diff --git a/flang/test/Lower/OpenMP/omp_alloc_init_host.f90 b/flang/test/Lower/OpenMP/omp_alloc_init_host.f90
new file mode 100644
index 0000000000000..8fec15d83932c
--- /dev/null
+++ b/flang/test/Lower/OpenMP/omp_alloc_init_host.f90
@@ -0,0 +1,5 @@
+!RUN: %flang_fc1 -fopenmp -fopenmp-default-allocate=host -emit-fir %s -o - | FileCheck %s
+
+program omp_alloc_init_host
+    !CHECK-NOT: fir.call @_FortranAOpenMPRegisterAllocator()
+end program omp_alloc_init_host

>From 0905dc68a7e3752a49c74e7e7e8b9745df811cbe Mon Sep 17 00:00:00 2001
From: Michael Klemm <michael.klemm at amd.com>
Date: Tue, 18 Aug 2026 09:46:07 +0200
Subject: [PATCH 2/5] Remove obsolete comments about Umpire

---
 flang-rt/lib/openmp/CMakeLists.txt | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/flang-rt/lib/openmp/CMakeLists.txt b/flang-rt/lib/openmp/CMakeLists.txt
index ced202478d253..8f580fafee3e7 100644
--- a/flang-rt/lib/openmp/CMakeLists.txt
+++ b/flang-rt/lib/openmp/CMakeLists.txt
@@ -6,22 +6,9 @@
 #
 #===------------------------------------------------------------------------===#
 
-# Check that Umpire exits in the directory given at CMake
-# TODO: this was disabled to get to an easier build procedure for now
-#message(STATUS "Using Umpire in directory ${FLANG_RT_UMPIRE_DIR}")
-#set(umpire_DIR ${FLANG_RT_UMPIRE_DIR})
-#find_package(umpire REQUIRED PATHS ${FLANG_RT_UMPIRE_DIR}/lib/cmake/umpire)
-
 add_flangrt_library(flang_rt.openmp STATIC SHARED
   omp_alloc.cpp
   omp_util.cpp
   INSTALL_WITH_TOOLCHAIN
 )
 
-#if (TARGET flang_rt.openmp.static)
-#  target_include_directories(flang_rt.openmp.static PRIVATE ${FLANG_RT_UMPIRE_DIR}/include)
-#endif()
-#
-#if (TARGET flang_rt.openmp.shared)
-#  target_include_directories(flang_rt.openmp.shared PRIVATE ${FLANG_RT_UMPIRE_DIR}/include)
-#endif()

>From fdd864ae100fdfd7c51953ad15e87dd8bd42b5d7 Mon Sep 17 00:00:00 2001
From: Michael Klemm <michael.klemm at amd.com>
Date: Tue, 18 Aug 2026 10:24:12 +0200
Subject: [PATCH 3/5] Remove obsolete compiler flags

---
 flang/test/Driver/fopenmp-default-allocate.f90 | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/flang/test/Driver/fopenmp-default-allocate.f90 b/flang/test/Driver/fopenmp-default-allocate.f90
index 9e637d2add4ec..96f2d523582d1 100644
--- a/flang/test/Driver/fopenmp-default-allocate.f90
+++ b/flang/test/Driver/fopenmp-default-allocate.f90
@@ -1,8 +1,8 @@
 ! Check that the driver passes -fopenmp-default-allocate= through to fc1
 ! and only adds -mmlir -use-alloc-runtime for target mode.
 
-! RUN: %flang -### -S -fopenmp-default-allocate=target %s -o - 2>&1 | FileCheck %s --check-prefix=TARGET
-! RUN: %flang -### -S -fopenmp-default-allocate=host %s -o - 2>&1 | FileCheck %s --check-prefix=HOST
+! RUN: %flang -### -fopenmp-default-allocate=target %s 2>&1 | FileCheck %s --check-prefix=TARGET
+! RUN: %flang -### -fopenmp-default-allocate=host %s 2>&1 | FileCheck %s --check-prefix=HOST
 
 ! TARGET: warning: -fopenmp-default-allocate= is an experimental feature
 ! TARGET: "-fc1"
@@ -25,4 +25,4 @@
 
 program fopenmp_default_allocate
     ! do nothing
-end program fopenmp_default_allocate
\ No newline at end of file
+end program fopenmp_default_allocate

>From 4fbe94689271aa450677e646f00b610d5e136ab4 Mon Sep 17 00:00:00 2001
From: Michael Klemm <michael.klemm at amd.com>
Date: Tue, 18 Aug 2026 14:41:26 +0200
Subject: [PATCH 4/5] Don't use a shortcut for the warning message

---
 clang/include/clang/Basic/DiagnosticDriverKinds.td | 3 ++-
 clang/test/Misc/warning-flags.c                    | 3 +--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index fe8a73e6c5dd3..f38dc60093689 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -187,7 +187,8 @@ def warn_openmp_spec_incomplete : Warning<
   "the syntax and semantics of new features may be subject to change">,
   InGroup<ExperimentalOption>;
 def warn_openmp_default_allocate_experimental
-    : Warning<"-fopenmp-default-allocate= is an experimental feature">;
+    : Warning<"-fopenmp-default-allocate= is an experimental feature">,
+      InGroup<ExperimentalOption>;
 def err_drv_invalid_thread_model_for_target : Error<
   "invalid thread model '%0' in '%1' for this target">;
 def err_drv_invalid_linker_name : Error<
diff --git a/clang/test/Misc/warning-flags.c b/clang/test/Misc/warning-flags.c
index f4d38386c25c9..3dc4bb55aa69c 100644
--- a/clang/test/Misc/warning-flags.c
+++ b/clang/test/Misc/warning-flags.c
@@ -18,7 +18,7 @@ This test serves two purposes:
 
 The list of warnings below should NEVER grow.  It should gradually shrink to 0.
 
-CHECK: Warnings without flags (57):
+CHECK: Warnings without flags (56):
 
 CHECK-NEXT:   ext_expected_semi_decl_list
 CHECK-NEXT:   ext_missing_whitespace_after_macro_name
@@ -61,7 +61,6 @@ CHECK-NEXT:   warn_not_compound_assign
 CHECK-NEXT:   warn_objc_property_copy_missing_on_block
 CHECK-NEXT:   warn_objc_protocol_qualifier_missing_id
 CHECK-NEXT:   warn_on_superclass_use
-CHECK-NEXT:   warn_openmp_default_allocate_experimental
 CHECK-NEXT:   warn_pp_convert_to_positive
 CHECK-NEXT:   warn_pp_expr_overflow
 CHECK-NEXT:   warn_pp_line_decimal

>From fda98eacb1d317aeab26eebe6e9c799d3dbf31ad Mon Sep 17 00:00:00 2001
From: Michael Klemm <michael.klemm at amd.com>
Date: Tue, 18 Aug 2026 14:43:53 +0200
Subject: [PATCH 5/5] Make clang-format happy

---
 flang-rt/lib/openmp/omp_alloc.cpp                    | 6 ++++--
 flang-rt/lib/openmp/omp_util.cpp                     | 3 ++-
 flang/include/flang/Optimizer/Builder/Runtime/Main.h | 3 +--
 flang/include/flang/Runtime/OpenMP/omp_alloc.h       | 1 -
 flang/include/flang/Support/Fortran-features.h       | 5 ++---
 flang/lib/Lower/Allocatable.cpp                      | 4 ++--
 6 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/flang-rt/lib/openmp/omp_alloc.cpp b/flang-rt/lib/openmp/omp_alloc.cpp
index 4cc93c4b0ca40..8fe661625bdf7 100644
--- a/flang-rt/lib/openmp/omp_alloc.cpp
+++ b/flang-rt/lib/openmp/omp_alloc.cpp
@@ -1,4 +1,5 @@
-//===-- lib/openmp/omp_alloc.cpp ---------------------------------*- C++ -*-===//
+//===-- lib/openmp/omp_alloc.cpp ---------------------------------*- C++
+//-*-===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -35,7 +36,8 @@ extern "C" void omp_target_free(void *, int);
 static PointerDeviceMap allocDeviceMap;
 
 /// Allocate \p AllocationSize bytes on the current default OpenMP device.
-static void *OpenMPAlloc(std::size_t AllocationSize, std::size_t, std::int64_t *) {
+static void *OpenMPAlloc(
+    std::size_t AllocationSize, std::size_t, std::int64_t *) {
 #if ALLOC_DEBUG
   if (debugEnabled) {
     std::fprintf(stderr, "[OMP_ALLOC] %s(%zu) (%s:%d)\n", __PRETTY_FUNCTION__,
diff --git a/flang-rt/lib/openmp/omp_util.cpp b/flang-rt/lib/openmp/omp_util.cpp
index cabef7574363d..cd14016b01705 100644
--- a/flang-rt/lib/openmp/omp_util.cpp
+++ b/flang-rt/lib/openmp/omp_util.cpp
@@ -1,4 +1,5 @@
-//===-- lib/openmp/omp_util.cpp ----------------------------------*- C++ -*-===//
+//===-- lib/openmp/omp_util.cpp ----------------------------------*- C++
+//-*-===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
diff --git a/flang/include/flang/Optimizer/Builder/Runtime/Main.h b/flang/include/flang/Optimizer/Builder/Runtime/Main.h
index 4bce249c2c3b2..7df704a8fc4ae 100644
--- a/flang/include/flang/Optimizer/Builder/Runtime/Main.h
+++ b/flang/include/flang/Optimizer/Builder/Runtime/Main.h
@@ -26,8 +26,7 @@ namespace fir::runtime {
 void genMain(fir::FirOpBuilder &builder, mlir::Location loc,
              const std::vector<Fortran::lower::EnvironmentDefault> &defs,
              bool initCuda = false, bool enableOpenMPAllocator = false,
-             bool initCoarrayEnv = false,
-             unsigned fpExceptionTraps = 0);
+             bool initCoarrayEnv = false, unsigned fpExceptionTraps = 0);
 }
 
 #endif // FORTRAN_OPTIMIZER_BUILDER_RUNTIME_MAIN_H
diff --git a/flang/include/flang/Runtime/OpenMP/omp_alloc.h b/flang/include/flang/Runtime/OpenMP/omp_alloc.h
index ba5fb95c2cade..010f044f1b4d9 100644
--- a/flang/include/flang/Runtime/OpenMP/omp_alloc.h
+++ b/flang/include/flang/Runtime/OpenMP/omp_alloc.h
@@ -31,7 +31,6 @@ void RTDECL(OpenMPRegisterAllocator)();
 /// registry slot (typically 1).  No-op if the descriptor is already allocated
 /// or is not allocatable.
 void RTDECL(OpenMPAllocatableSetAllocIdx)(Descriptor &descriptor, int pos);
-
 }
 
 } // namespace Fortran::runtime::omp
diff --git a/flang/include/flang/Support/Fortran-features.h b/flang/include/flang/Support/Fortran-features.h
index 6f835bc82388f..c0d83902dfcb6 100644
--- a/flang/include/flang/Support/Fortran-features.h
+++ b/flang/include/flang/Support/Fortran-features.h
@@ -55,9 +55,8 @@ ENUM_CLASS(LanguageFeature, BackslashEscapes, OldDebugLines,
     SavedLocalInSpecExpr, PrintNamelist, AssumedRankPassedToNonAssumedRank,
     IgnoreIrrelevantAttributes, Unsigned, ContiguousOkForSeqAssociation,
     ForwardRefExplicitTypeDummy, InaccessibleDeferredOverride,
-    OpenMPDefaultAllocator,
-    CudaWarpMatchFunction, DoConcurrentOffload, TransferBOZ, Coarray,
-    PointerPassObject, MultipleIdenticalDATA,
+    OpenMPDefaultAllocator, CudaWarpMatchFunction, DoConcurrentOffload,
+    TransferBOZ, Coarray, PointerPassObject, MultipleIdenticalDATA,
     DefaultStructConstructorNullPointer, AssumedRankIoItem,
     MultipleProgramUnitsOnSameLine, AllocatedForAssociated,
     OpenMPThreadprivateEquivalence, RelaxedCLocChecks, CudaPinned,
diff --git a/flang/lib/Lower/Allocatable.cpp b/flang/lib/Lower/Allocatable.cpp
index 865bc5fd38f9f..38cda9ae38123 100644
--- a/flang/lib/Lower/Allocatable.cpp
+++ b/flang/lib/Lower/Allocatable.cpp
@@ -31,14 +31,14 @@
 #include "flang/Optimizer/Dialect/MIF/MIFOps.h"
 #include "flang/Optimizer/Support/FatalError.h"
 #include "flang/Parser/parse-tree.h"
+#include "flang/Runtime/OpenMP/omp_alloc.h"
 #include "flang/Runtime/allocatable.h"
 #include "flang/Runtime/pointer.h"
-#include "flang/Runtime/OpenMP/omp_alloc.h"
 #include "flang/Semantics/tools.h"
 #include "flang/Semantics/type.h"
+#include "flang/Support/Fortran-features.h"
 #include "mlir/Dialect/OpenMP/OpenMPDialect.h"
 #include "mlir/Dialect/OpenMP/OpenMPInterfaces.h"
-#include "flang/Support/Fortran-features.h"
 #include "llvm/Support/CommandLine.h"
 
 /// By default fir memory operation fir::AllocMemOp/fir::FreeMemOp are used.



More information about the llvm-commits mailing list