[Openmp-commits] [openmp] 94f5dd0 - [libomp] OpenMP 6.0 Device-Scope Environment Variables (#197191)

via Openmp-commits openmp-commits at lists.llvm.org
Mon Sep 21 22:08:54 PDT 2026


Author: Amit Tiwari
Date: 2026-09-22T10:38:48+05:30
New Revision: 94f5dd0eb25ed97603af1d0a501c412fcfea6696

URL: https://github.com/llvm/llvm-project/commit/94f5dd0eb25ed97603af1d0a501c412fcfea6696
DIFF: https://github.com/llvm/llvm-project/commit/94f5dd0eb25ed97603af1d0a501c412fcfea6696.diff

LOG: [libomp] OpenMP 6.0 Device-Scope Environment Variables  (#197191)

The patch introduces `_ALL` `_DEV` `_DEV_<id>` env var suffixes to the
LLVM's OpenMP host runtime `libomp`. The eligible-list env var currently
supports `OMP_NUM_THREADS`.

Assisted by Claude Opus 4.7 / Claude 4.6 Sonnet

Added: 
    openmp/runtime/src/kmp_device_env.cpp
    openmp/runtime/src/kmp_device_env.h
    openmp/runtime/test/env/omp_default_device_all_rejected.c
    openmp/runtime/test/env/omp_denylist_extra_coverage.c
    openmp/runtime/test/env/omp_num_threads_all_demo.c
    openmp/runtime/test/env/omp_num_threads_all_host.c
    openmp/runtime/test/env/omp_num_threads_all_set_defaults.c
    openmp/runtime/test/env/omp_num_threads_dev_all_fallback.c
    openmp/runtime/test/env/omp_num_threads_dev_invalid.c
    openmp/runtime/test/env/omp_num_threads_dev_overflow.c
    openmp/runtime/test/env/omp_num_threads_dev_resolve.c
    openmp/runtime/test/env/omp_num_threads_host_overrides_all.c
    openmp/runtime/test/env/omp_num_threads_host_vs_device.c
    openmp/runtime/test/env/omp_num_threads_warnings_off.c

Modified: 
    openmp/runtime/src/CMakeLists.txt
    openmp/runtime/src/dllexports
    openmp/runtime/src/i18n/en_US.txt
    openmp/runtime/src/kmp_runtime.cpp
    openmp/runtime/src/kmp_settings.cpp

Removed: 
    


################################################################################
diff  --git a/openmp/runtime/src/CMakeLists.txt b/openmp/runtime/src/CMakeLists.txt
index fc298312c01ed..f8bf6ac0cfd9f 100644
--- a/openmp/runtime/src/CMakeLists.txt
+++ b/openmp/runtime/src/CMakeLists.txt
@@ -86,6 +86,7 @@ else()
     kmp_atomic.cpp
     kmp_csupport.cpp
     kmp_debug.cpp
+    kmp_device_env.cpp
     kmp_itt.cpp
     kmp_invoke_microtask.cpp
     kmp_environment.cpp

diff  --git a/openmp/runtime/src/dllexports b/openmp/runtime/src/dllexports
index a8196d447d627..24c141e078b5b 100644
--- a/openmp/runtime/src/dllexports
+++ b/openmp/runtime/src/dllexports
@@ -579,6 +579,7 @@ kmp_set_disp_num_buffers                    890
         __kmpc_free
         __kmpc_init_allocator
         __kmpc_destroy_allocator
+        __kmpc_get_device_env
     %endif
     omp_set_affinity_format                 748
     omp_get_affinity_format                 749

diff  --git a/openmp/runtime/src/i18n/en_US.txt b/openmp/runtime/src/i18n/en_US.txt
index 84a515383a805..a2f6d0f617224 100644
--- a/openmp/runtime/src/i18n/en_US.txt
+++ b/openmp/runtime/src/i18n/en_US.txt
@@ -491,6 +491,8 @@ AffIgnoringNonHybrid         "%1$s ignored: This machine is not a hybrid archite
 AffIgnoringNotAvailable      "%1$s ignored: %2$s is not available. Using \"%3$s\" instead."
 SetNumTeamsInParOrTeamsRegion "%1$s() called inside a parallel or teams region; call ignored."
 SetTeamsThreadLimitInParOrTeamsRegion "%1$s() called inside a parallel or teams region; call ignored."
+MalformedDeviceEnvVar        "%1$s: malformed device-scope environment variable name; expected `_ALL`, `_DEV`, or `_DEV_<n>` with a non-negative integer device id; ignored."
+DeviceEnvVarOnGlobalScope    "%1$s: device-scope environment variable suffix (`_ALL`/`_DEV`/`_DEV_<n>`) is not allowed on this base name (global-scope ICV); ignored."
 
 # --------------------------------------------------------------------------------------------------
 -*- HINTS -*-

diff  --git a/openmp/runtime/src/kmp_device_env.cpp b/openmp/runtime/src/kmp_device_env.cpp
new file mode 100644
index 0000000000000..d722e9d617b85
--- /dev/null
+++ b/openmp/runtime/src/kmp_device_env.cpp
@@ -0,0 +1,353 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// OpenMP 6.0 device-scope env-var registry. See kmp_device_env.h for the
+// public-internal contract;
+//
+//===----------------------------------------------------------------------===//
+
+#include "kmp.h"
+#include "kmp_device_env.h"
+#include "kmp_i18n.h"
+#include "kmp_str.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+// Eligible-var table. Only env vars associated with non-global-scope ICVs
+// (and not `OMP_DEFAULT_DEVICE`) are eligible for `_ALL`/`_DEV[_d]` forms,
+// per OpenMP 6.0.
+static char const *const __kmp_device_env_eligible_names[] = {
+    "OMP_NUM_THREADS",
+    NULL,
+};
+
+// Denylist of well-known OpenMP env vars that initialize *global-scope* ICVs
+// or `OMP_DEFAULT_DEVICE`.
+static char const *const __kmp_device_env_denied_bases[] = {
+    "OMP_DEFAULT_DEVICE",
+    "OMP_MAX_TASK_PRIORITY",
+    "OMP_TARGET_OFFLOAD",
+    "OMP_DISPLAY_ENV",
+    "OMP_DISPLAY_AFFINITY",
+    "OMP_AFFINITY_FORMAT",
+    "OMP_CANCELLATION",
+    "OMP_TOOL",
+    "OMP_TOOL_LIBRARIES",
+    "OMP_TOOL_VERBOSE_INIT",
+    "OMP_DEBUG",
+    NULL,
+};
+
+struct kmp_device_env_dev_node_t {
+  int device_id;
+  char *value;
+  struct kmp_device_env_dev_node_t *next;
+};
+
+struct kmp_device_env_state_t {
+  char *all_value; // <ENV>_ALL
+  char *dev_default_value; // <ENV>_DEV
+  kmp_device_env_dev_node_t *per_device; // <ENV>_DEV_<d>
+};
+
+static kmp_device_env_state_t *__kmp_device_env_states = NULL;
+
+static int __kmp_device_env_count(void) {
+  return sizeof(__kmp_device_env_eligible_names) /
+             sizeof(__kmp_device_env_eligible_names[0]) -
+         1;
+}
+
+// Invariant: the eligible-name and denied-base tables must be disjoint --
+// the classifier short-circuits on the first eligible match and never
+// consults the denylist (see `__kmp_classify_device_env_name`). Verified
+// once-per-process in debug builds to protect future maintainers.
+static void __kmp_device_env_assert_tables_disjoint(void) {
+#ifdef KMP_DEBUG
+  for (int i = 0; __kmp_device_env_eligible_names[i] != NULL; ++i) {
+    for (int j = 0; __kmp_device_env_denied_bases[j] != NULL; ++j) {
+      KMP_DEBUG_ASSERT(strcmp(__kmp_device_env_eligible_names[i],
+                              __kmp_device_env_denied_bases[j]) != 0);
+    }
+  }
+#endif
+}
+
+static void __kmp_device_env_lazy_init(void) {
+  if (__kmp_device_env_states != NULL)
+    return;
+  __kmp_device_env_assert_tables_disjoint();
+  int count = __kmp_device_env_count();
+  __kmp_device_env_states = (kmp_device_env_state_t *)KMP_INTERNAL_MALLOC(
+      sizeof(kmp_device_env_state_t) * count);
+  for (int i = 0; i < count; ++i) {
+    __kmp_device_env_states[i].all_value = NULL;
+    __kmp_device_env_states[i].dev_default_value = NULL;
+    __kmp_device_env_states[i].per_device = NULL;
+  }
+}
+
+static int __kmp_device_env_index(char const *base_name) {
+  if (base_name == NULL)
+    return -1;
+  for (int i = 0; __kmp_device_env_eligible_names[i] != NULL; ++i)
+    if (strcmp(__kmp_device_env_eligible_names[i], base_name) == 0)
+      return i;
+  return -1;
+}
+
+extern "C" char const *__kmp_device_env_eligible_name(int index) {
+  if (index < 0)
+    return NULL;
+  int count = __kmp_device_env_count();
+  if (index >= count)
+    return NULL;
+  return __kmp_device_env_eligible_names[index];
+}
+
+// Match `full_name` against `base_name + suffix`. Returns the pointer just
+// after the matched suffix on success, or NULL on failure.
+static char const *__kmp_strip_prefix_and_suffix(char const *full_name,
+                                                 char const *base_name,
+                                                 char const *suffix) {
+  size_t base_len = strlen(base_name);
+  if (strncmp(full_name, base_name, base_len) != 0)
+    return NULL;
+  char const *rest = full_name + base_len;
+  size_t sfx_len = strlen(suffix);
+  if (strncmp(rest, suffix, sfx_len) != 0)
+    return NULL;
+  return rest + sfx_len;
+}
+
+// True if `s` is non-empty and consists entirely of ASCII decimal digits.
+static int __kmp_is_nonneg_int(char const *s) {
+  if (s == NULL || *s == '\0')
+    return 0;
+  for (char const *p = s; *p; ++p) {
+    if (*p < '0' || *p > '9')
+      return 0;
+  }
+  return 1;
+}
+
+// Returns the parsed value on success, -1 on overflow/empty/non-digit input.
+// Leading zeros are allowed (e.g., `000000000001` is device 1). INT_MAX and
+// larger values are rejected so -1 remains the only error sentinel.
+static int __kmp_parse_dev_id(char const *s) {
+  if (!__kmp_is_nonneg_int(s))
+    return -1;
+  long long v = 0;
+  for (char const *p = s; *p; ++p) {
+    v = v * 10 + (*p - '0');
+    if (v >= (long long)INT_MAX)
+      return -1;
+  }
+  return (int)v;
+}
+
+enum kmp_dev_env_kind_t {
+  kmp_dev_env_none = 0,
+  kmp_dev_env_all, // <ENV>_ALL
+  kmp_dev_env_dev_default, // <ENV>_DEV
+  kmp_dev_env_dev_id, // <ENV>_DEV_<d>
+};
+
+// Try to classify `full_name` as `<base>_<suffix>` where suffix is `_ALL`,
+// `_DEV`, or `_DEV_<token>`. On match, returns the kind and (for `_DEV_<n>`)
+// fills `*out_dev_id` (or sets it to -1 to flag a malformed token, e.g. a
+// non-integer or overflowing token). Returns kmp_dev_env_none if `full_name`
+// does not have any of those exact suffixes for `base`.
+static kmp_dev_env_kind_t
+__kmp_match_suffix(char const *full_name, char const *base, int *out_dev_id) {
+  *out_dev_id = -1;
+  if (char const *tail = __kmp_strip_prefix_and_suffix(full_name, base, "_ALL"))
+    if (*tail == '\0')
+      return kmp_dev_env_all;
+  if (char const *tail =
+          __kmp_strip_prefix_and_suffix(full_name, base, "_DEV_")) {
+    int dev_id = __kmp_parse_dev_id(tail);
+    *out_dev_id = dev_id; // -1 signals malformed/overflowing token
+    return kmp_dev_env_dev_id;
+  }
+  if (char const *tail = __kmp_strip_prefix_and_suffix(full_name, base, "_DEV"))
+    if (*tail == '\0')
+      return kmp_dev_env_dev_default;
+  return kmp_dev_env_none;
+}
+
+// Decompose `full_name` into base index, kind and (optionally) device id.
+// Sets `*out_dev_id` only when the kind is `kmp_dev_env_dev_id`.
+//
+// `*out_index` is set to the eligible-table index when the base matches an
+// eligible name, or -1 if `full_name` matches a *denied* (global-scope)
+// base + suffix (the caller emits a warning in that case).
+//
+// Returns the kind. `kmp_dev_env_none` means the name is not a device-scope
+// variant of either an eligible base or a known denied base. The caller
+// should treat such names normally (no warning) so unrelated env vars like
+// `OMP_DEV_LIST` are not misinterpreted.
+static kmp_dev_env_kind_t __kmp_classify_device_env_name(char const *full_name,
+                                                         int *out_index,
+                                                         int *out_dev_id) {
+  *out_index = -1;
+  *out_dev_id = -1;
+
+  if (full_name == NULL || *full_name == '\0')
+    return kmp_dev_env_none;
+
+  // First, try to recognize a known eligible base + suffix pairing.
+  for (int i = 0; __kmp_device_env_eligible_names[i] != NULL; ++i) {
+    char const *base = __kmp_device_env_eligible_names[i];
+    int dev_id = -1;
+    kmp_dev_env_kind_t k = __kmp_match_suffix(full_name, base, &dev_id);
+    if (k != kmp_dev_env_none) {
+      *out_index = i;
+      *out_dev_id = dev_id;
+      return k;
+    }
+  }
+
+  // Second, check the denylist of well-known global-scope OMP env vars. Only
+  // these well-defined bases trigger the global-scope-rejection warning;
+  // unrelated user-defined env vars (e.g. `OMP_DEV_LIST`, `KMP_X`) are
+  // ignored silently.
+  for (int i = 0; __kmp_device_env_denied_bases[i] != NULL; ++i) {
+    char const *base = __kmp_device_env_denied_bases[i];
+    int dev_id = -1;
+    kmp_dev_env_kind_t k = __kmp_match_suffix(full_name, base, &dev_id);
+    if (k != kmp_dev_env_none)
+      return k; // *out_index stays -1 -- denylist hit
+  }
+
+  return kmp_dev_env_none;
+}
+
+static void __kmp_device_env_set_string(char **slot, char const *value) {
+  if (*slot != NULL) {
+    __kmp_str_free(slot);
+  }
+  *slot = __kmp_str_format("%s", value);
+}
+
+static void __kmp_device_env_set_per_device(kmp_device_env_state_t *st,
+                                            int device_id, char const *value) {
+  for (kmp_device_env_dev_node_t *n = st->per_device; n != NULL; n = n->next) {
+    if (n->device_id == device_id) {
+      __kmp_str_free(&n->value);
+      n->value = __kmp_str_format("%s", value);
+      return;
+    }
+  }
+  kmp_device_env_dev_node_t *node =
+      (kmp_device_env_dev_node_t *)KMP_INTERNAL_MALLOC(
+          sizeof(kmp_device_env_dev_node_t));
+  node->device_id = device_id;
+  node->value = __kmp_str_format("%s", value);
+  node->next = st->per_device;
+  st->per_device = node;
+}
+
+extern "C" int __kmp_device_env_record(char const *full_name,
+                                       char const *value) {
+  if (full_name == NULL || value == NULL)
+    return 0;
+
+  int idx = -1;
+  int dev_id = -1;
+  kmp_dev_env_kind_t kind =
+      __kmp_classify_device_env_name(full_name, &idx, &dev_id);
+  if (kind == kmp_dev_env_none)
+    return 0; // not a device-scope variant -- caller handles normally
+
+  // Suffix recognized but base is on the denylist (global-scope ICV). Per the
+  // OpenMP 6.0 restriction, reject with a warning.
+  if (idx < 0) {
+    KMP_WARNING(DeviceEnvVarOnGlobalScope, full_name);
+    return 1;
+  }
+
+  // For the eligible-base path, malformed `<ENV>_DEV_<token>` (non-integer or
+  // overflowing) is rejected before we touch the registry.
+  if (kind == kmp_dev_env_dev_id && dev_id < 0) {
+    KMP_WARNING(MalformedDeviceEnvVar, full_name);
+    return 1;
+  }
+
+  __kmp_device_env_lazy_init();
+  kmp_device_env_state_t *st = &__kmp_device_env_states[idx];
+
+  switch (kind) {
+  case kmp_dev_env_all:
+    __kmp_device_env_set_string(&st->all_value, value);
+    return 1;
+  case kmp_dev_env_dev_default:
+    __kmp_device_env_set_string(&st->dev_default_value, value);
+    return 1;
+  case kmp_dev_env_dev_id:
+    __kmp_device_env_set_per_device(st, dev_id, value);
+    return 1;
+  case kmp_dev_env_none:
+    break;
+  }
+  return 0;
+}
+
+// Look up the registry state for `base_name`, or NULL if it is not an
+// eligible base or nothing was recorded yet.
+static kmp_device_env_state_t *__kmp_device_env_lookup(char const *base_name) {
+  int idx = __kmp_device_env_index(base_name);
+  if (idx < 0 || __kmp_device_env_states == NULL)
+    return NULL;
+  return &__kmp_device_env_states[idx];
+}
+
+extern "C" char const *__kmpc_get_device_env(char const *base_name,
+                                             int device_id) {
+  // Host ICVs are observed through the existing OpenMP APIs. This entry
+  // point is for non-host devices only and takes a 0-based device id.
+  KMP_DEBUG_ASSERT(device_id >= 0);
+  if (device_id < 0)
+    return NULL;
+  kmp_device_env_state_t *st = __kmp_device_env_lookup(base_name);
+  if (st == NULL)
+    return NULL;
+
+  // Non-host device d: <ENV>_DEV_<d> > <ENV>_DEV > <ENV>_ALL > default.
+  for (kmp_device_env_dev_node_t *n = st->per_device; n != NULL; n = n->next) {
+    if (n->device_id == device_id)
+      return n->value;
+  }
+  if (st->dev_default_value != NULL)
+    return st->dev_default_value;
+  if (st->all_value != NULL)
+    return st->all_value;
+  return NULL;
+}
+
+extern "C" void __kmp_device_env_reset(void) {
+  if (__kmp_device_env_states == NULL)
+    return;
+  int count = __kmp_device_env_count();
+  for (int i = 0; i < count; ++i) {
+    kmp_device_env_state_t *st = &__kmp_device_env_states[i];
+    __kmp_str_free(&st->all_value);
+    __kmp_str_free(&st->dev_default_value);
+    kmp_device_env_dev_node_t *n = st->per_device;
+    while (n) {
+      kmp_device_env_dev_node_t *next = n->next;
+      __kmp_str_free(&n->value);
+      KMP_INTERNAL_FREE(n);
+      n = next;
+    }
+    st->per_device = NULL;
+  }
+  KMP_INTERNAL_FREE(__kmp_device_env_states);
+  __kmp_device_env_states = NULL;
+}

diff  --git a/openmp/runtime/src/kmp_device_env.h b/openmp/runtime/src/kmp_device_env.h
new file mode 100644
index 0000000000000..32aa2c2477ff4
--- /dev/null
+++ b/openmp/runtime/src/kmp_device_env.h
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Implements parsing, storage and precedence resolution for the OpenMP 6.0
+// device-scope environment variable extensions described in OpenMP 6.0
+// Section 3.2 (ICV initialization) and Chapter 4 (Environment Variables):
+//
+//   <ENV>          -- sets the host device ICV.
+//   <ENV>_ALL      -- sets the host and non-host device ICVs that are not
+//                     overridden by a more specific form.
+//   <ENV>_DEV      -- sets all non-host device ICVs that are not overridden
+//                     by an `<ENV>_DEV_<d>` form.
+//   <ENV>_DEV_<d>  -- sets the ICV for non-host device with id `d` (a
+//                     non-negative integer).
+//
+// Precedence:
+//   Host:     <ENV>           > <ENV>_ALL                            > default
+//   Device d: <ENV>_DEV_<d>   > <ENV>_DEV    > <ENV>_ALL             > default
+//
+// Restrictions enforced:
+//   * Device-specific environment variables are only accepted for env vars
+//     listed in `__kmp_device_env_eligible_names` (i.e. those that initialize
+//     device-scope ICVs and are not global-scope or `OMP_DEFAULT_DEVICE`).
+//     Suffix forms applied to other env vars are ignored with a warning.
+//   * `<ENV>_DEV_<token>` where <token> is not a non-negative integer is
+//     rejected with a warning. The spec requires that device-specific
+//     environment variables must not specify the host device; we accept only
+//     non-negative integer non-host device ids.
+//   * Conflicting settings of `<ENV>_DEV_<d>` (same d) follow last-parsed-wins
+//     semantics, mirroring existing libomp env handling.
+
+#ifndef KMP_DEVICE_ENV_H
+#define KMP_DEVICE_ENV_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Consume `full_name=value` if it is a device-scope variant. Returns:
+//   1 -- consumed (stored, OR rejected with a warning); caller must skip it.
+//   0 -- not a device-scope variant; caller continues normal processing.
+int __kmp_device_env_record(char const *full_name, char const *value);
+
+// Resolve the effective string for `base_name` on a non-host device:
+//   <ENV>_DEV_<d> > <ENV>_DEV > <ENV>_ALL > NULL.
+// `device_id` is 0-based and must be >= 0.
+char const *__kmpc_get_device_env(char const *base_name, int device_id);
+
+// Free all storage owned by the registry.
+void __kmp_device_env_reset(void);
+
+// Iterate the eligible base-name table; returns NULL once exhausted.
+char const *__kmp_device_env_eligible_name(int index);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // KMP_DEVICE_ENV_H

diff  --git a/openmp/runtime/src/kmp_runtime.cpp b/openmp/runtime/src/kmp_runtime.cpp
index 22ec2605718a3..8ebebfcb7e056 100644
--- a/openmp/runtime/src/kmp_runtime.cpp
+++ b/openmp/runtime/src/kmp_runtime.cpp
@@ -13,6 +13,7 @@
 #include "kmp.h"
 #include "kmp_affinity.h"
 #include "kmp_atomic.h"
+#include "kmp_device_env.h"
 #include "kmp_environment.h"
 #include "kmp_error.h"
 #include "kmp_i18n.h"
@@ -8344,6 +8345,8 @@ void __kmp_cleanup(void) {
     __kmp_affinity_format = NULL;
   }
 
+  __kmp_device_env_reset();
+
   __kmp_i18n_catclose();
 
   if (__kmp_nesting_nth_level)

diff  --git a/openmp/runtime/src/kmp_settings.cpp b/openmp/runtime/src/kmp_settings.cpp
index 3ab604e15c017..f01231b7a168b 100644
--- a/openmp/runtime/src/kmp_settings.cpp
+++ b/openmp/runtime/src/kmp_settings.cpp
@@ -16,6 +16,7 @@
 #if KMP_USE_HIER_SCHED
 #include "kmp_dispatch_hier.h"
 #endif
+#include "kmp_device_env.h"
 #include "kmp_environment.h"
 #include "kmp_i18n.h"
 #include "kmp_io.h"
@@ -6089,12 +6090,32 @@ static void __kmp_print_affinity_settings(const kmp_affinity_t *affinity) {
 }
 #endif
 
+// If `base_name` is on the device-scope eligible list, return the value of
+// `<base_name>_ALL` from `block` (or NULL). Returns NULL for non-eligible
+// names.
+static char const *__kmp_aux_env_blk_all_fallback(kmp_env_blk_t *block,
+                                                  char const *base_name) {
+  for (int e = 0;; ++e) {
+    char const *base = __kmp_device_env_eligible_name(e);
+    if (base == NULL)
+      return NULL;
+    if (strcmp(base, base_name) != 0)
+      continue;
+    char *all_name = __kmp_str_format("%s_ALL", base_name);
+    char const *value = __kmp_env_blk_var(block, all_name);
+    __kmp_str_free(&all_name);
+    return value;
+  }
+}
+
 static void __kmp_aux_env_initialize(kmp_env_blk_t *block) {
 
   char const *value;
 
   /* OMP_NUM_THREADS */
   value = __kmp_env_blk_var(block, "OMP_NUM_THREADS");
+  if (value == NULL)
+    value = __kmp_aux_env_blk_all_fallback(block, "OMP_NUM_THREADS");
   if (value) {
     ompc_set_num_threads(__kmp_dflt_team_nth);
   }
@@ -6139,20 +6160,6 @@ void __kmp_env_initialize(char const *string) {
   }
   __kmp_env_blk_init(&block, string);
 
-  // update the set flag on all entries that have an env var
-  for (i = 0; i < block.count; ++i) {
-    if ((block.vars[i].name == NULL) || (*block.vars[i].name == '\0')) {
-      continue;
-    }
-    if (block.vars[i].value == NULL) {
-      continue;
-    }
-    kmp_setting_t *setting = __kmp_stg_find(block.vars[i].name);
-    if (setting != NULL) {
-      setting->set = 1;
-    }
-  }
-
   // We need to know if blocktime and use_yield were set when processing
   // OMP_WAIT_POLICY
   blocktime_str = __kmp_env_blk_var(&block, "KMP_BLOCKTIME");
@@ -6223,6 +6230,25 @@ void __kmp_env_initialize(char const *string) {
 
 #endif /* KMP_AFFINITY_SUPPORTED */
 
+  // Deliberately placed AFTER the KMP_WARNINGS special case above so that any
+  // warnings the pre-pass emits (MalformedDeviceEnvVar /
+  // DeviceEnvVarOnGlobalScope) honour the user-requested warning level.
+  for (i = 0; i < block.count; ++i) {
+    if ((block.vars[i].name == NULL) || (*block.vars[i].name == '\0')) {
+      continue;
+    }
+    if (block.vars[i].value == NULL) {
+      continue;
+    }
+    if (__kmp_device_env_record(block.vars[i].name, block.vars[i].value))
+      continue;
+
+    kmp_setting_t *setting = __kmp_stg_find(block.vars[i].name);
+    if (setting != NULL) {
+      setting->set = 1;
+    }
+  }
+
   // Set up the nested proc bind type vector.
   if (__kmp_nested_proc_bind.bind_types == NULL) {
     __kmp_nested_proc_bind.bind_types =
@@ -6258,6 +6284,23 @@ void __kmp_env_initialize(char const *string) {
     __kmp_stg_parse(block.vars[i].name, block.vars[i].value);
   }
 
+  // Device-scope env-var post-pass: when the current block has `<ENV>_ALL` but
+  // no unsuffixed `<ENV>`, replay `_ALL` so the host ICV picks it up. We query
+  // the block (not the registry) so an unrelated kmp_set_defaults does not
+  // accidentally replay a stale `_ALL`.
+  for (int e = 0;; ++e) {
+    char const *base = __kmp_device_env_eligible_name(e);
+    if (base == NULL)
+      break;
+    if (__kmp_env_blk_var(&block, base) != NULL)
+      continue;
+    char *all_name = __kmp_str_format("%s_ALL", base);
+    char const *all_v = __kmp_env_blk_var(&block, all_name);
+    __kmp_str_free(&all_name);
+    if (all_v != NULL)
+      __kmp_stg_parse(base, all_v);
+  }
+
   // If user locks have been allocated yet, don't reset the lock vptr table.
   if (!__kmp_init_user_locks) {
     if (__kmp_user_lock_kind == lk_default) {

diff  --git a/openmp/runtime/test/env/omp_default_device_all_rejected.c b/openmp/runtime/test/env/omp_default_device_all_rejected.c
new file mode 100644
index 0000000000000..4fa11f1e91cae
--- /dev/null
+++ b/openmp/runtime/test/env/omp_default_device_all_rejected.c
@@ -0,0 +1,23 @@
+// RUN: %libomp-compile
+// RUN: env KMP_WARNINGS=1 OMP_DEFAULT_DEVICE_ALL=2 %libomp-run 2>&1 \
+// RUN:   | FileCheck %s
+//
+// Suffix on global-scope base must be rejected with a warning AND not
+// reach the ICV.
+
+#include <omp.h>
+#include <stdio.h>
+
+int main(void) {
+  (void)omp_get_max_threads();
+  int dd = omp_get_default_device();
+  if (dd == 2) {
+    fprintf(stderr, "FAIL: OMP_DEFAULT_DEVICE_ALL=2 was applied; got %d\n", dd);
+    return 1;
+  }
+  printf("DONE\n");
+  return 0;
+}
+
+// CHECK: {{^OMP: Warning #[0-9]+:.*OMP_DEFAULT_DEVICE_ALL.*}}
+// CHECK: DONE

diff  --git a/openmp/runtime/test/env/omp_denylist_extra_coverage.c b/openmp/runtime/test/env/omp_denylist_extra_coverage.c
new file mode 100644
index 0000000000000..6eb68acf63273
--- /dev/null
+++ b/openmp/runtime/test/env/omp_denylist_extra_coverage.c
@@ -0,0 +1,26 @@
+// RUN: %libomp-compile
+// RUN: env KMP_WARNINGS=1 OMP_TARGET_OFFLOAD_DEV_0=mandatory \
+// RUN:   OMP_TOOL_LIBRARIES_ALL=libfoo.so \
+// RUN:   OMP_CANCELLATION_DEV=true \
+// RUN:   OMP_NUM_THREADS_ALL=8 \
+// RUN:   %libomp-run 2>&1 | FileCheck %s
+//
+// Denylist: suffixed global OMP_* warns; OMP_NUM_THREADS_ALL still applies.
+
+#include <omp.h>
+#include <stdio.h>
+
+int main(void) {
+  int max = omp_get_max_threads();
+  if (max != 8) {
+    fprintf(stderr, "FAIL: omp_get_max_threads()=%d, expected 8\n", max);
+    return 1;
+  }
+  printf("DONE\n");
+  return 0;
+}
+
+// CHECK-DAG: {{^OMP: Warning #[0-9]+:.*OMP_TARGET_OFFLOAD_DEV_0.*}}
+// CHECK-DAG: {{^OMP: Warning #[0-9]+:.*OMP_TOOL_LIBRARIES_ALL.*}}
+// CHECK-DAG: {{^OMP: Warning #[0-9]+:.*OMP_CANCELLATION_DEV.*}}
+// CHECK: DONE

diff  --git a/openmp/runtime/test/env/omp_num_threads_all_demo.c b/openmp/runtime/test/env/omp_num_threads_all_demo.c
new file mode 100644
index 0000000000000..4baa668dbd8d6
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_all_demo.c
@@ -0,0 +1,25 @@
+// RUN: %libomp-compile
+// RUN: env OMP_NUM_THREADS_ALL=8 OMP_NUM_THREADS_DEV_0=128 %libomp-run \
+// RUN:   | FileCheck %s
+//
+// Demo: host=8 (_ALL), device 0=128 (_DEV_0), other devices=8 (_ALL fallback).
+
+#include <omp.h>
+#include <stdio.h>
+
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+int main(void) {
+  int host_max = omp_get_max_threads();
+  printf("host omp_get_max_threads() = %d\n", host_max);
+  for (int d = 0; d < 3; ++d) {
+    const char *v = __kmpc_get_device_env("OMP_NUM_THREADS", d);
+    printf("device %d resolved OMP_NUM_THREADS = %s\n", d, v ? v : "(default)");
+  }
+  return 0;
+}
+
+// CHECK: host omp_get_max_threads() = 8
+// CHECK: device 0 resolved OMP_NUM_THREADS = 128
+// CHECK: device 1 resolved OMP_NUM_THREADS = 8
+// CHECK: device 2 resolved OMP_NUM_THREADS = 8

diff  --git a/openmp/runtime/test/env/omp_num_threads_all_host.c b/openmp/runtime/test/env/omp_num_threads_all_host.c
new file mode 100644
index 0000000000000..93694a5fb45a9
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_all_host.c
@@ -0,0 +1,16 @@
+// RUN: %libomp-compile && env OMP_NUM_THREADS_ALL=8 %libomp-run
+//
+// OpenMP 6.0: host falls through to `_ALL` when `<ENV>` is unset.
+
+#include <omp.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(void) {
+  int max = omp_get_max_threads();
+  if (max != 8) {
+    fprintf(stderr, "FAIL: omp_get_max_threads()=%d, expected 8\n", max);
+    return 1;
+  }
+  return 0;
+}

diff  --git a/openmp/runtime/test/env/omp_num_threads_all_set_defaults.c b/openmp/runtime/test/env/omp_num_threads_all_set_defaults.c
new file mode 100644
index 0000000000000..d6205704827b9
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_all_set_defaults.c
@@ -0,0 +1,32 @@
+// RUN: %libomp-compile-and-run
+//
+// kmp_set_defaults("OMP_NUM_THREADS_ALL=N") must propagate to live threads,
+// and an unrelated kmp_set_defaults call must not wipe registry state.
+
+#include <omp.h>
+#include <stdio.h>
+
+extern void kmp_set_defaults(const char *);
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+int main(void) {
+  int rc = 0;
+  (void)omp_get_max_threads();
+
+  kmp_set_defaults("OMP_NUM_THREADS_ALL=16");
+  if (omp_get_max_threads() != 16) {
+    fprintf(stderr, "FAIL: after _ALL=16 expected 16 got %d\n",
+            omp_get_max_threads());
+    rc = 1;
+  }
+
+  kmp_set_defaults("KMP_BLOCKTIME=200");
+  const char *q = __kmpc_get_device_env("OMP_NUM_THREADS", 0);
+  if (q == NULL || q[0] != '1' || q[1] != '6' || q[2] != '\0') {
+    fprintf(stderr,
+            "FAIL: registry wiped by unrelated kmp_set_defaults; got %s\n",
+            q ? q : "(null)");
+    rc = 1;
+  }
+  return rc;
+}

diff  --git a/openmp/runtime/test/env/omp_num_threads_dev_all_fallback.c b/openmp/runtime/test/env/omp_num_threads_dev_all_fallback.c
new file mode 100644
index 0000000000000..fc9ee4d4f056c
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_dev_all_fallback.c
@@ -0,0 +1,32 @@
+// RUN: %libomp-compile && env OMP_NUM_THREADS_ALL=8 %libomp-run
+//
+// OpenMP 6.0: every non-host device falls through to `_ALL` when no
+// `_DEV[_d]` is set.
+
+#include <omp.h>
+#include <stdio.h>
+#include <string.h>
+
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+static int check(int device_id, const char *got, const char *expect) {
+  if (!got || strcmp(got, expect) != 0) {
+    fprintf(stderr, "FAIL: device_id=%d got '%s' expected '%s'\n", device_id,
+            got ? got : "(null)", expect);
+    return 1;
+  }
+  return 0;
+}
+
+int main(void) {
+  int rc = 0;
+  if (omp_get_max_threads() != 8) {
+    fprintf(stderr, "FAIL: host omp_get_max_threads()=%d, expected 8\n",
+            omp_get_max_threads());
+    return 1;
+  }
+  rc |= check(0, __kmpc_get_device_env("OMP_NUM_THREADS", 0), "8");
+  rc |= check(1, __kmpc_get_device_env("OMP_NUM_THREADS", 1), "8");
+  rc |= check(2, __kmpc_get_device_env("OMP_NUM_THREADS", 2), "8");
+  return rc;
+}

diff  --git a/openmp/runtime/test/env/omp_num_threads_dev_invalid.c b/openmp/runtime/test/env/omp_num_threads_dev_invalid.c
new file mode 100644
index 0000000000000..93dbb82d3eb18
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_dev_invalid.c
@@ -0,0 +1,22 @@
+// RUN: %libomp-compile
+// RUN: env KMP_WARNINGS=1 OMP_NUM_THREADS_DEV_abc=10 OMP_NUM_THREADS_ALL=4 \
+// RUN:   %libomp-run 2>&1 | FileCheck %s
+//
+// Malformed `_DEV_<token>` is rejected with a warning, and a sibling
+// valid `_ALL` setting is unaffected.
+
+#include <omp.h>
+#include <stdio.h>
+
+int main(void) {
+  int max = omp_get_max_threads();
+  if (max != 4) {
+    fprintf(stderr, "FAIL: omp_get_max_threads()=%d, expected 4\n", max);
+    return 1;
+  }
+  printf("DONE\n");
+  return 0;
+}
+
+// CHECK: {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_DEV_abc.*}}
+// CHECK: DONE

diff  --git a/openmp/runtime/test/env/omp_num_threads_dev_overflow.c b/openmp/runtime/test/env/omp_num_threads_dev_overflow.c
new file mode 100644
index 0000000000000..351231b12b4e8
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_dev_overflow.c
@@ -0,0 +1,46 @@
+// RUN: %libomp-compile
+// RUN: env KMP_WARNINGS=1 OMP_NUM_THREADS_DEV_2147483647=10 \
+// RUN:   OMP_NUM_THREADS_DEV_99999999999=20 \
+// RUN:   OMP_NUM_THREADS_DEV_000000000001=10 \
+// RUN:   OMP_NUM_THREADS_DEV0=30 OMP_NUM_THREADS_ALLEY=40 \
+// RUN:   OMP_NUM_THREADS_ALL=4 \
+// RUN:   %libomp-run 2>&1 | FileCheck %s
+//
+// (a) `_DEV_<huge>` rejected with a warning;
+// (b) similar-looking names (`_DEV0`, `_ALLEY`) silently ignored, no warning;
+// (c) leading zeros are a valid device id (`000000000001` is device 1);
+// (d) sibling valid `_ALL=4` still applies.
+
+#include <omp.h>
+#include <stdio.h>
+#include <string.h>
+
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+int main(void) {
+  if (omp_get_max_threads() != 4) {
+    fprintf(stderr, "FAIL: host got %d, expected 4\n", omp_get_max_threads());
+    return 1;
+  }
+  const char *q = __kmpc_get_device_env("OMP_NUM_THREADS", 2147483646);
+  if (q == NULL || strcmp(q, "4") != 0) {
+    fprintf(stderr, "FAIL: large valid id expected '4', got %s\n",
+            q ? q : "(null)");
+    return 1;
+  }
+  q = __kmpc_get_device_env("OMP_NUM_THREADS", 1);
+  if (q == NULL || strcmp(q, "10") != 0) {
+    fprintf(stderr, "FAIL: padded device id expected '10', got %s\n",
+            q ? q : "(null)");
+    return 1;
+  }
+  printf("DONE\n");
+  return 0;
+}
+
+// CHECK:     {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_DEV_2147483647.*}}
+// CHECK:     {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_DEV_99999999999.*}}
+// CHECK-NOT: {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_DEV_000000000001.*}}
+// CHECK-NOT: {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_DEV0.*}}
+// CHECK-NOT: {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_ALLEY.*}}
+// CHECK:     DONE

diff  --git a/openmp/runtime/test/env/omp_num_threads_dev_resolve.c b/openmp/runtime/test/env/omp_num_threads_dev_resolve.c
new file mode 100644
index 0000000000000..072f1bf3f5c97
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_dev_resolve.c
@@ -0,0 +1,33 @@
+// RUN: %libomp-compile && \
+// RUN:   env OMP_NUM_THREADS_ALL=8 OMP_NUM_THREADS_DEV=64 \
+// RUN:   OMP_NUM_THREADS_DEV_0=128 %libomp-run
+//
+// OpenMP 6.0 non-host precedence: `_DEV_<d>` > `_DEV` > `_ALL` > default.
+
+#include <omp.h>
+#include <stdio.h>
+#include <string.h>
+
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+static int check(const char *got, int device_id, const char *expect) {
+  if (got == NULL || strcmp(got, expect) != 0) {
+    fprintf(stderr, "FAIL: device_id=%d resolved to '%s', expected '%s'\n",
+            device_id, got ? got : "(null)", expect);
+    return 1;
+  }
+  return 0;
+}
+
+int main(void) {
+  int rc = 0;
+  if (omp_get_max_threads() != 8) {
+    fprintf(stderr, "FAIL: host omp_get_max_threads()=%d, expected 8\n",
+            omp_get_max_threads());
+    return 1;
+  }
+  rc |= check(__kmpc_get_device_env("OMP_NUM_THREADS", 0), 0, "128"); // _DEV_0
+  rc |= check(__kmpc_get_device_env("OMP_NUM_THREADS", 1), 1, "64"); // _DEV
+  rc |= check(__kmpc_get_device_env("OMP_NUM_THREADS", 2), 2, "64"); // _DEV
+  return rc;
+}

diff  --git a/openmp/runtime/test/env/omp_num_threads_host_overrides_all.c b/openmp/runtime/test/env/omp_num_threads_host_overrides_all.c
new file mode 100644
index 0000000000000..b6da422c4b0d0
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_host_overrides_all.c
@@ -0,0 +1,26 @@
+// RUN: %libomp-compile && \
+// RUN:   env OMP_NUM_THREADS=4 OMP_NUM_THREADS_ALL=8 %libomp-run
+//
+// Host OMP_NUM_THREADS beats _ALL; sibling `_ALL` still applies on devices.
+
+#include <omp.h>
+#include <stdio.h>
+#include <string.h>
+
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+int main(void) {
+  int max = omp_get_max_threads();
+  if (max != 4) {
+    fprintf(stderr, "FAIL: omp_get_max_threads()=%d, expected 4\n", max);
+    return 1;
+  }
+  // Sibling `_ALL` must still resolve for non-host devices.
+  const char *dev0 = __kmpc_get_device_env("OMP_NUM_THREADS", 0);
+  if (dev0 == NULL || strcmp(dev0, "8") != 0) {
+    fprintf(stderr, "FAIL: dev 0 query expected '8' got '%s'\n",
+            dev0 ? dev0 : "(null)");
+    return 1;
+  }
+  return 0;
+}

diff  --git a/openmp/runtime/test/env/omp_num_threads_host_vs_device.c b/openmp/runtime/test/env/omp_num_threads_host_vs_device.c
new file mode 100644
index 0000000000000..651ef1b833902
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_host_vs_device.c
@@ -0,0 +1,26 @@
+// RUN: %libomp-compile && \
+// RUN:   env OMP_NUM_THREADS=4 OMP_NUM_THREADS_ALL=8 \
+// RUN:   OMP_NUM_THREADS_DEV_0=128 %libomp-run
+//
+// Host ICV via omp_get_max_threads; devices via __kmpc_get_device_env.
+
+#include <omp.h>
+#include <stdio.h>
+#include <string.h>
+
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+int main(void) {
+  if (omp_get_max_threads() != 4)
+    return 1;
+
+  // Device 0 uses _DEV_0; device 1 falls through to _ALL.
+  const char *dev0 = __kmpc_get_device_env("OMP_NUM_THREADS", 0);
+  if (dev0 == NULL || strcmp(dev0, "128") != 0)
+    return 2;
+  const char *dev1 = __kmpc_get_device_env("OMP_NUM_THREADS", 1);
+  if (dev1 == NULL || strcmp(dev1, "8") != 0)
+    return 3;
+
+  return 0;
+}

diff  --git a/openmp/runtime/test/env/omp_num_threads_warnings_off.c b/openmp/runtime/test/env/omp_num_threads_warnings_off.c
new file mode 100644
index 0000000000000..08b36a02124c4
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_warnings_off.c
@@ -0,0 +1,23 @@
+// RUN: %libomp-compile
+// RUN: env KMP_WARNINGS=0 OMP_NUM_THREADS_DEV_abc=10 \
+// RUN:   OMP_DEFAULT_DEVICE_ALL=2 OMP_NUM_THREADS_ALL=8 \
+// RUN:   %libomp-run 2>&1 | FileCheck %s
+//
+// KMP_WARNINGS=0 silences device-scope warnings; OMP_NUM_THREADS_ALL=8 applies.
+
+#include <omp.h>
+#include <stdio.h>
+
+int main(void) {
+  int max = omp_get_max_threads();
+  if (max != 8) {
+    fprintf(stderr, "FAIL: omp_get_max_threads()=%d, expected 8\n", max);
+    return 1;
+  }
+  printf("DONE\n");
+  return 0;
+}
+
+// CHECK-NOT: {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_DEV_abc.*}}
+// CHECK-NOT: {{^OMP: Warning #[0-9]+:.*OMP_DEFAULT_DEVICE_ALL.*}}
+// CHECK: DONE


        


More information about the Openmp-commits mailing list