[Openmp-commits] [openmp] [OpenMP][Runtime] OpenMP 6.0 Device-Scope Environment Variables (PR #197191)

Amit Tiwari via Openmp-commits openmp-commits at lists.llvm.org
Mon Jun 22 02:52:19 PDT 2026


https://github.com/loopacino updated https://github.com/llvm/llvm-project/pull/197191

>From 43cc1bcf95eaa06c456798a804568aff44fbd923 Mon Sep 17 00:00:00 2001
From: amtiwari <amtiwari at amd.com>
Date: Mon, 4 May 2026 07:16:10 -0400
Subject: [PATCH 1/8] device-scope_env_var

---
 openmp/runtime/src/CMakeLists.txt     |   1 +
 openmp/runtime/src/kmp_device_env.cpp | 357 ++++++++++++++++++++++++++
 openmp/runtime/src/kmp_device_env.h   |  69 +++++
 openmp/runtime/src/kmp_settings.cpp   |  29 ++-
 4 files changed, 455 insertions(+), 1 deletion(-)
 create mode 100644 openmp/runtime/src/kmp_device_env.cpp
 create mode 100644 openmp/runtime/src/kmp_device_env.h

diff --git a/openmp/runtime/src/CMakeLists.txt b/openmp/runtime/src/CMakeLists.txt
index 463b8fe2f14aa..1ac72487920dc 100644
--- a/openmp/runtime/src/CMakeLists.txt
+++ b/openmp/runtime/src/CMakeLists.txt
@@ -85,6 +85,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/kmp_device_env.cpp b/openmp/runtime/src/kmp_device_env.cpp
new file mode 100644
index 0000000000000..b4e3b336a1894
--- /dev/null
+++ b/openmp/runtime/src/kmp_device_env.cpp
@@ -0,0 +1,357 @@
+//===----------------------------------------------------------------------===//
+//
+// 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; see openmp/docs/OpenMP_6_0_DeviceScope_EnvVars_
+// Walkthrough.md for the design notes and examples.
+//
+//===----------------------------------------------------------------------===//
+
+#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 *host_value; // <ENV>
+  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) {
+  int count = 0;
+  while (__kmp_device_env_eligible_names[count] != NULL)
+    ++count;
+  return count;
+}
+
+static void __kmp_device_env_lazy_init(void) {
+  if (__kmp_device_env_states != NULL)
+    return;
+  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].host_value = NULL;
+    __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.
+// We cap valid device ids at INT_MAX-1 to leave INT_MAX as a sentinel.
+static int __kmp_parse_dev_id(char const *s) {
+  if (!__kmp_is_nonneg_int(s))
+    return -1;
+  // Reject obviously-overflowing inputs early.
+  size_t len = strlen(s);
+  if (len > 10)
+    return -1;
+  long long v = 0;
+  for (char const *p = s; *p; ++p) {
+    v = v * 10 + (*p - '0');
+    if (v >= 2147483647LL)
+      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;
+}
+
+extern "C" char const *__kmp_resolve_device_env(char const *base_name,
+                                                int device_id) {
+  int idx = __kmp_device_env_index(base_name);
+  if (idx < 0 || __kmp_device_env_states == NULL)
+    return NULL;
+  kmp_device_env_state_t *st = &__kmp_device_env_states[idx];
+
+  if (device_id < 0) {
+    // Host: <ENV> > <ENV>_ALL > default.
+    if (st->host_value != NULL)
+      return st->host_value;
+    if (st->all_value != NULL)
+      return st->all_value;
+    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_observe_host(char const *full_name,
+                                              char const *value) {
+  int idx = __kmp_device_env_index(full_name);
+  if (idx < 0 || value == NULL)
+    return;
+  __kmp_device_env_lazy_init();
+  __kmp_device_env_set_string(&__kmp_device_env_states[idx].host_value, value);
+}
+
+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->host_value);
+    __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;
+}
+
+// Public test/query helper. Returns the resolved value for `name` on
+// `device_id`.
+extern "C" char const *__kmpc_get_resolved_device_env(char const *name,
+                                                      int device_id) {
+  return __kmp_resolve_device_env(name, device_id);
+}
diff --git a/openmp/runtime/src/kmp_device_env.h b/openmp/runtime/src/kmp_device_env.h
new file mode 100644
index 0000000000000..e40b61743daab
--- /dev/null
+++ b/openmp/runtime/src/kmp_device_env.h
@@ -0,0 +1,69 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_table` (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 `device_id` per the
+// precedence rules above. Pass `device_id == -1` to request the host;
+// any other negative value returns NULL (defensive). Returns NULL when no
+// source applies (caller falls back to its own default).
+char const *__kmp_resolve_device_env(char const *base_name, int device_id);
+
+// Record an unsuffixed `<ENV>=value` pair so the host query is consistent
+// with the host ICV. No-op for non-eligible names.
+void __kmp_device_env_observe_host(char const *full_name, char const *value);
+
+// 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_settings.cpp b/openmp/runtime/src/kmp_settings.cpp
index 66ef6f8097dce..63cb6b885f9e8 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,6 +6090,8 @@ static void __kmp_aux_env_initialize(kmp_env_blk_t *block) {
 
   /* OMP_NUM_THREADS */
   value = __kmp_env_blk_var(block, "OMP_NUM_THREADS");
+  if (value == NULL)
+    value = __kmp_env_blk_var(block, "OMP_NUM_THREADS_ALL");
   if (value) {
     ompc_set_num_threads(__kmp_dflt_team_nth);
   }
@@ -6133,7 +6136,8 @@ 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
+  // Device-scope env-var pre-pass: route `<ENV>_ALL`,
+  // `<ENV>_DEV`, `<ENV>_DEV_<d>` into the device-scope registry.
   for (i = 0; i < block.count; ++i) {
     if ((block.vars[i].name == NULL) || (*block.vars[i].name == '\0')) {
       continue;
@@ -6141,6 +6145,10 @@ void __kmp_env_initialize(char const *string) {
     if (block.vars[i].value == NULL) {
       continue;
     }
+    if (__kmp_device_env_record(block.vars[i].name, block.vars[i].value))
+      continue;
+    __kmp_device_env_observe_host(block.vars[i].name, block.vars[i].value);
+
     kmp_setting_t *setting = __kmp_stg_find(block.vars[i].name);
     if (setting != NULL) {
       setting->set = 1;
@@ -6250,6 +6258,25 @@ 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);
+      __kmp_device_env_observe_host(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) {

>From 7d52f329ba8396a51514829dc849f1f20a6ec698 Mon Sep 17 00:00:00 2001
From: amtiwari <amtiwari at amd.com>
Date: Mon, 4 May 2026 07:18:12 -0400
Subject: [PATCH 2/8] kmp_free

---
 openmp/runtime/src/kmp_runtime.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/openmp/runtime/src/kmp_runtime.cpp b/openmp/runtime/src/kmp_runtime.cpp
index c402645af9ad6..99ee6cf909590 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"
@@ -8328,6 +8329,8 @@ void __kmp_cleanup(void) {
     __kmp_affinity_format = NULL;
   }
 
+  __kmp_device_env_reset();
+
   __kmp_i18n_catclose();
 
   if (__kmp_nesting_nth_level)

>From e5a4efd1c0bc98ed5ab3e86ff31bf451ffba7992 Mon Sep 17 00:00:00 2001
From: amtiwari <amtiwari at amd.com>
Date: Mon, 4 May 2026 07:27:53 -0400
Subject: [PATCH 3/8] device_id_fix

---
 openmp/runtime/src/kmp_device_env.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/openmp/runtime/src/kmp_device_env.cpp b/openmp/runtime/src/kmp_device_env.cpp
index b4e3b336a1894..227e3edf35188 100644
--- a/openmp/runtime/src/kmp_device_env.cpp
+++ b/openmp/runtime/src/kmp_device_env.cpp
@@ -297,14 +297,16 @@ extern "C" char const *__kmp_resolve_device_env(char const *base_name,
     return NULL;
   kmp_device_env_state_t *st = &__kmp_device_env_states[idx];
 
-  if (device_id < 0) {
-    // Host: <ENV> > <ENV>_ALL > default.
+  // Host: <ENV> > <ENV>_ALL
+  if (device_id == -1) {
     if (st->host_value != NULL)
       return st->host_value;
     if (st->all_value != NULL)
       return st->all_value;
     return NULL;
   }
+  if (device_id < 0)
+    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) {

>From 5e0b113e1e291083278d470feef0b61fbd7ecf82 Mon Sep 17 00:00:00 2001
From: amtiwari <amtiwari at amd.com>
Date: Mon, 4 May 2026 07:28:24 -0400
Subject: [PATCH 4/8] diag

---
 openmp/runtime/src/dllexports     | 1 +
 openmp/runtime/src/i18n/en_US.txt | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/openmp/runtime/src/dllexports b/openmp/runtime/src/dllexports
index 8a70f8bc6d20c..be1b5ffb62c2b 100644
--- a/openmp/runtime/src/dllexports
+++ b/openmp/runtime/src/dllexports
@@ -556,6 +556,7 @@ kmp_set_disp_num_buffers                    890
         __kmpc_free
         __kmpc_init_allocator
         __kmpc_destroy_allocator
+        __kmpc_get_resolved_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 b3699df6db65d..7e078c0a83158 100644
--- a/openmp/runtime/src/i18n/en_US.txt
+++ b/openmp/runtime/src/i18n/en_US.txt
@@ -484,6 +484,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 or not eligible); ignored."
 
 # --------------------------------------------------------------------------------------------------
 -*- HINTS -*-

>From 0f088f5e905213a23a11ab85111045322045565f Mon Sep 17 00:00:00 2001
From: amtiwari <amtiwari at amd.com>
Date: Tue, 12 May 2026 09:14:42 -0400
Subject: [PATCH 5/8] review_changes

---
 openmp/runtime/src/i18n/en_US.txt     |  2 +-
 openmp/runtime/src/kmp_device_env.cpp | 22 ++++++++++
 openmp/runtime/src/kmp_settings.cpp   | 59 ++++++++++++++++++---------
 3 files changed, 62 insertions(+), 21 deletions(-)

diff --git a/openmp/runtime/src/i18n/en_US.txt b/openmp/runtime/src/i18n/en_US.txt
index 7e078c0a83158..9bb25e85ccb8e 100644
--- a/openmp/runtime/src/i18n/en_US.txt
+++ b/openmp/runtime/src/i18n/en_US.txt
@@ -485,7 +485,7 @@ AffIgnoringNotAvailable      "%1$s ignored: %2$s is not available. Using \"%3$s\
 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 or not eligible); 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
index 227e3edf35188..a7c66efef1699 100644
--- a/openmp/runtime/src/kmp_device_env.cpp
+++ b/openmp/runtime/src/kmp_device_env.cpp
@@ -67,9 +67,25 @@ static int __kmp_device_env_count(void) {
   return count;
 }
 
+// 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);
@@ -301,6 +317,12 @@ extern "C" char const *__kmp_resolve_device_env(char const *base_name,
   if (device_id == -1) {
     if (st->host_value != NULL)
       return st->host_value;
+    // Defensive: after `__kmp_env_initialize` completes, `host_value` is
+    // always populated whenever `<ENV>` or `<ENV>_ALL` was set (the post-pass
+    // calls `observe_host` after replaying `_ALL`). This `all_value` fallback
+    // covers the narrow window of an early query during init bootstrap (e.g.
+    // a query issued from inside `__kmp_stg_parse` while the post-pass has
+    // not yet run). Kept deliberately so the contract holds end-to-end.
     if (st->all_value != NULL)
       return st->all_value;
     return NULL;
diff --git a/openmp/runtime/src/kmp_settings.cpp b/openmp/runtime/src/kmp_settings.cpp
index 63cb6b885f9e8..fa4237f5f7b21 100644
--- a/openmp/runtime/src/kmp_settings.cpp
+++ b/openmp/runtime/src/kmp_settings.cpp
@@ -6084,6 +6084,24 @@ 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;
@@ -6091,7 +6109,7 @@ static void __kmp_aux_env_initialize(kmp_env_blk_t *block) {
   /* OMP_NUM_THREADS */
   value = __kmp_env_blk_var(block, "OMP_NUM_THREADS");
   if (value == NULL)
-    value = __kmp_env_blk_var(block, "OMP_NUM_THREADS_ALL");
+    value = __kmp_aux_env_blk_all_fallback(block, "OMP_NUM_THREADS");
   if (value) {
     ompc_set_num_threads(__kmp_dflt_team_nth);
   }
@@ -6136,25 +6154,6 @@ void __kmp_env_initialize(char const *string) {
   }
   __kmp_env_blk_init(&block, string);
 
-  // Device-scope env-var pre-pass: route `<ENV>_ALL`,
-  // `<ENV>_DEV`, `<ENV>_DEV_<d>` into the device-scope registry.
-  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_device_env_observe_host(block.vars[i].name, block.vars[i].value);
-
-    kmp_setting_t *setting = __kmp_stg_find(block.vars[i].name);
-    if (setting != NULL) {
-      setting->set = 1;
-    }
-  }
-
   // We need to know if blocktime was set when processing OMP_WAIT_POLICY
   blocktime_str = __kmp_env_blk_var(&block, "KMP_BLOCKTIME");
 
@@ -6223,6 +6222,26 @@ 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_device_env_observe_host(block.vars[i].name, block.vars[i].value);
+
+    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 =

>From 8f7ad8da0a7546bc75c6660abb561110f40a75f4 Mon Sep 17 00:00:00 2001
From: amtiwari <amtiwari at amd.com>
Date: Thu, 11 Jun 2026 09:01:25 -0400
Subject: [PATCH 6/8] tests

---
 openmp/runtime/src/kmp_device_env.cpp         |  3 +-
 .../env/omp_default_device_all_rejected.c     | 22 ++++++++++
 .../test/env/omp_denylist_extra_coverage.c    | 26 +++++++++++
 .../test/env/omp_num_threads_all_demo.c       | 25 +++++++++++
 .../test/env/omp_num_threads_all_host.c       | 16 +++++++
 .../env/omp_num_threads_all_set_defaults.c    | 39 +++++++++++++++++
 .../env/omp_num_threads_dev_all_fallback.c    | 32 ++++++++++++++
 .../test/env/omp_num_threads_dev_invalid.c    | 22 ++++++++++
 .../test/env/omp_num_threads_dev_overflow.c   | 38 ++++++++++++++++
 .../test/env/omp_num_threads_dev_resolve.c    | 43 +++++++++++++++++++
 .../env/omp_num_threads_host_overrides_all.c  | 33 ++++++++++++++
 .../test/env/omp_num_threads_warnings_off.c   | 33 ++++++++++++++
 12 files changed, 330 insertions(+), 2 deletions(-)
 create mode 100644 openmp/runtime/test/env/omp_default_device_all_rejected.c
 create mode 100644 openmp/runtime/test/env/omp_denylist_extra_coverage.c
 create mode 100644 openmp/runtime/test/env/omp_num_threads_all_demo.c
 create mode 100644 openmp/runtime/test/env/omp_num_threads_all_host.c
 create mode 100644 openmp/runtime/test/env/omp_num_threads_all_set_defaults.c
 create mode 100644 openmp/runtime/test/env/omp_num_threads_dev_all_fallback.c
 create mode 100644 openmp/runtime/test/env/omp_num_threads_dev_invalid.c
 create mode 100644 openmp/runtime/test/env/omp_num_threads_dev_overflow.c
 create mode 100644 openmp/runtime/test/env/omp_num_threads_dev_resolve.c
 create mode 100644 openmp/runtime/test/env/omp_num_threads_host_overrides_all.c
 create mode 100644 openmp/runtime/test/env/omp_num_threads_warnings_off.c

diff --git a/openmp/runtime/src/kmp_device_env.cpp b/openmp/runtime/src/kmp_device_env.cpp
index a7c66efef1699..4238e81803c9f 100644
--- a/openmp/runtime/src/kmp_device_env.cpp
+++ b/openmp/runtime/src/kmp_device_env.cpp
@@ -7,8 +7,7 @@
 //===----------------------------------------------------------------------===//
 //
 // OpenMP 6.0 device-scope env-var registry. See kmp_device_env.h for the
-// public-internal contract; see openmp/docs/OpenMP_6_0_DeviceScope_EnvVars_
-// Walkthrough.md for the design notes and examples.
+// public-internal contract;
 //
 //===----------------------------------------------------------------------===//
 
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..3d869ff5bb235
--- /dev/null
+++ b/openmp/runtime/test/env/omp_default_device_all_rejected.c
@@ -0,0 +1,22 @@
+// RUN: %libomp-compile
+// RUN: env KMP_WARNINGS=1 OMP_DEFAULT_DEVICE_ALL=2 %libomp-run 2>&1 | 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..fd3e9aacb668f
--- /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 | 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_resolved_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_resolved_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..3f18a96d6b915
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_all_set_defaults.c
@@ -0,0 +1,39 @@
+// 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_resolved_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;
+  }
+  const char *q = __kmpc_get_resolved_device_env("OMP_NUM_THREADS", -1);
+  if (q == NULL || q[0] != '1' || q[1] != '6' || q[2] != '\0') {
+    fprintf(stderr, "FAIL: query host expected '16' got %s\n",
+            q ? q : "(null)");
+    rc = 1;
+  }
+
+  kmp_set_defaults("KMP_BLOCKTIME=200");
+  q = __kmpc_get_resolved_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..395e021df6a8d
--- /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_resolved_device_env(const char *name,
+                                                  int device_id);
+
+static int check(int device_id, const char *expect) {
+  const char *got =
+      __kmpc_get_resolved_device_env("OMP_NUM_THREADS", device_id);
+  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) {
+  (void)omp_get_max_threads();
+  int rc = 0;
+  rc |= check(-1, "8"); // host
+  rc |= check(0, "8");
+  rc |= check(1, "8");
+  rc |= check(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..00d77fd24d1af
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_dev_overflow.c
@@ -0,0 +1,38 @@
+// 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_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) sibling valid `_ALL=4` still applies.
+
+#include <omp.h>
+#include <stdio.h>
+#include <string.h>
+
+extern const char *__kmpc_get_resolved_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_resolved_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;
+  }
+  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_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..4ab49db0372c3
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_dev_resolve.c
@@ -0,0 +1,43 @@
+// RUN: %libomp-compile && env OMP_NUM_THREADS_ALL=8 OMP_NUM_THREADS_DEV=64
+// 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 <stdlib.h>
+#include <string.h>
+
+extern const char *__kmpc_get_resolved_device_env(const char *name,
+                                                  int device_id);
+
+static int check(const char *name, int device_id, const char *expect) {
+  const char *got = __kmpc_get_resolved_device_env(name, device_id);
+  if (got == NULL) {
+    fprintf(stderr, "FAIL: %s device_id=%d resolved to NULL, expected %s\n",
+            name, device_id, expect);
+    return 1;
+  }
+  if (strcmp(got, expect) != 0) {
+    fprintf(stderr, "FAIL: %s device_id=%d resolved to '%s', expected '%s'\n",
+            name, device_id, got, expect);
+    return 1;
+  }
+  return 0;
+}
+
+int main(void) {
+  int rc = 0;
+  (void)omp_get_max_threads();
+  rc |= check("OMP_NUM_THREADS", -1, "8"); // host: _ALL
+  rc |= check("OMP_NUM_THREADS", 0, "128"); // _DEV_0 wins
+  rc |= check("OMP_NUM_THREADS", 1, "64"); // _DEV
+  rc |= check("OMP_NUM_THREADS", 2, "64"); // _DEV (no _DEV_2)
+
+  // Strict host-sentinel contract: only -1 is host.
+  if (__kmpc_get_resolved_device_env("OMP_NUM_THREADS", -2) != NULL) {
+    fprintf(stderr, "FAIL: device_id=-2 must return NULL\n");
+    rc = 1;
+  }
+  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..b6e90b5a368bf
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_host_overrides_all.c
@@ -0,0 +1,33 @@
+// RUN: %libomp-compile && env OMP_NUM_THREADS=4 OMP_NUM_THREADS_ALL=8
+// %libomp-run
+//
+// Host OMP_NUM_THREADS beats _ALL; legacy ICV and query API agree.
+
+#include <omp.h>
+#include <stdio.h>
+#include <string.h>
+
+extern const char *__kmpc_get_resolved_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;
+  }
+  const char *host = __kmpc_get_resolved_device_env("OMP_NUM_THREADS", -1);
+  if (host == NULL || strcmp(host, "4") != 0) {
+    fprintf(stderr, "FAIL: host query expected '4' got '%s'\n",
+            host ? host : "(null)");
+    return 1;
+  }
+  // Sibling `_ALL` must still resolve for non-host devices.
+  const char *dev0 = __kmpc_get_resolved_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_warnings_off.c b/openmp/runtime/test/env/omp_num_threads_warnings_off.c
new file mode 100644
index 0000000000000..6b8b631ec4fba
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_warnings_off.c
@@ -0,0 +1,33 @@
+// 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>
+#include <string.h>
+
+extern const char *__kmpc_get_resolved_device_env(const char *name,
+                                                  int device_id);
+
+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;
+  }
+  const char *host = __kmpc_get_resolved_device_env("OMP_NUM_THREADS", -1);
+  if (host == NULL || strcmp(host, "8") != 0) {
+    fprintf(stderr, "FAIL: host query expected '8' got '%s'\n",
+            host ? host : "(null)");
+    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

>From c84b5ab6afcd7030ec2f62b9795fd7d07a361e7c Mon Sep 17 00:00:00 2001
From: amtiwari <amtiwari at amd.com>
Date: Thu, 11 Jun 2026 08:59:33 -0400
Subject: [PATCH 7/8] run-lines-indent

---
 openmp/runtime/test/env/omp_default_device_all_rejected.c | 3 ++-
 openmp/runtime/test/env/omp_num_threads_all_demo.c        | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/openmp/runtime/test/env/omp_default_device_all_rejected.c b/openmp/runtime/test/env/omp_default_device_all_rejected.c
index 3d869ff5bb235..4fa11f1e91cae 100644
--- a/openmp/runtime/test/env/omp_default_device_all_rejected.c
+++ b/openmp/runtime/test/env/omp_default_device_all_rejected.c
@@ -1,5 +1,6 @@
 // RUN: %libomp-compile
-// RUN: env KMP_WARNINGS=1 OMP_DEFAULT_DEVICE_ALL=2 %libomp-run 2>&1 | FileCheck %s
+// 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.
diff --git a/openmp/runtime/test/env/omp_num_threads_all_demo.c b/openmp/runtime/test/env/omp_num_threads_all_demo.c
index fd3e9aacb668f..eba36b6d7b8a2 100644
--- a/openmp/runtime/test/env/omp_num_threads_all_demo.c
+++ b/openmp/runtime/test/env/omp_num_threads_all_demo.c
@@ -1,5 +1,6 @@
 // RUN: %libomp-compile
-// RUN: env OMP_NUM_THREADS_ALL=8 OMP_NUM_THREADS_DEV_0=128 %libomp-run | FileCheck %s
+// 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).
 

>From f399b65f3eb24b123959f4ec8fa37c72cb72984f Mon Sep 17 00:00:00 2001
From: amtiwari <amtiwari at amd.com>
Date: Mon, 22 Jun 2026 05:51:38 -0400
Subject: [PATCH 8/8] resolve_kmpc_add_helpers

---
 openmp/runtime/src/dllexports                 |  1 -
 openmp/runtime/src/exports_so.txt             |  2 +
 openmp/runtime/src/kmp_device_env.cpp         | 53 +++++++++++--------
 openmp/runtime/src/kmp_device_env.h           | 13 +++--
 .../test/env/omp_num_threads_all_demo.c       |  5 +-
 .../env/omp_num_threads_all_set_defaults.c    |  8 +--
 .../env/omp_num_threads_dev_all_fallback.c    | 16 +++---
 .../test/env/omp_num_threads_dev_overflow.c   |  5 +-
 .../test/env/omp_num_threads_dev_resolve.c    | 38 +++++--------
 .../env/omp_num_threads_host_overrides_all.c  | 12 ++---
 .../test/env/omp_num_threads_host_vs_device.c | 31 +++++++++++
 .../test/env/omp_num_threads_warnings_off.c   |  5 +-
 12 files changed, 109 insertions(+), 80 deletions(-)
 create mode 100644 openmp/runtime/test/env/omp_num_threads_host_vs_device.c

diff --git a/openmp/runtime/src/dllexports b/openmp/runtime/src/dllexports
index be1b5ffb62c2b..8a70f8bc6d20c 100644
--- a/openmp/runtime/src/dllexports
+++ b/openmp/runtime/src/dllexports
@@ -556,7 +556,6 @@ kmp_set_disp_num_buffers                    890
         __kmpc_free
         __kmpc_init_allocator
         __kmpc_destroy_allocator
-        __kmpc_get_resolved_device_env
     %endif
     omp_set_affinity_format                 748
     omp_get_affinity_format                 749
diff --git a/openmp/runtime/src/exports_so.txt b/openmp/runtime/src/exports_so.txt
index d826882d98804..f036c9c2d23e6 100644
--- a/openmp/runtime/src/exports_so.txt
+++ b/openmp/runtime/src/exports_so.txt
@@ -53,6 +53,8 @@ VERSION {
         __kmp_thread_pool;
 
 	__kmp_reset_stats;
+        __kmp_resolve_host_env;
+        __kmp_resolve_device_env;
 
 #if USE_ITT_BUILD
         #
diff --git a/openmp/runtime/src/kmp_device_env.cpp b/openmp/runtime/src/kmp_device_env.cpp
index 4238e81803c9f..5bf030f4e7433 100644
--- a/openmp/runtime/src/kmp_device_env.cpp
+++ b/openmp/runtime/src/kmp_device_env.cpp
@@ -305,29 +305,43 @@ extern "C" int __kmp_device_env_record(char const *full_name,
   return 0;
 }
 
-extern "C" char const *__kmp_resolve_device_env(char const *base_name,
-                                                int device_id) {
+// 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;
-  kmp_device_env_state_t *st = &__kmp_device_env_states[idx];
+  return &__kmp_device_env_states[idx];
+}
 
-  // Host: <ENV> > <ENV>_ALL
-  if (device_id == -1) {
-    if (st->host_value != NULL)
-      return st->host_value;
-    // Defensive: after `__kmp_env_initialize` completes, `host_value` is
-    // always populated whenever `<ENV>` or `<ENV>_ALL` was set (the post-pass
-    // calls `observe_host` after replaying `_ALL`). This `all_value` fallback
-    // covers the narrow window of an early query during init bootstrap (e.g.
-    // a query issued from inside `__kmp_stg_parse` while the post-pass has
-    // not yet run). Kept deliberately so the contract holds end-to-end.
-    if (st->all_value != NULL)
-      return st->all_value;
+extern "C" char const *__kmp_resolve_host_env(char const *base_name) {
+  kmp_device_env_state_t *st = __kmp_device_env_lookup(base_name);
+  if (st == NULL)
     return NULL;
-  }
+  // Host: <ENV> > <ENV>_ALL.
+  if (st->host_value != NULL)
+    return st->host_value;
+  // Defensive: after `__kmp_env_initialize` completes, `host_value` is
+  // always populated whenever `<ENV>` or `<ENV>_ALL` was set (the post-pass
+  // calls `observe_host` after replaying `_ALL`). This `all_value` fallback
+  // covers the narrow window of an early query during init bootstrap (e.g.
+  // a query issued from inside `__kmp_stg_parse` while the post-pass has
+  // not yet run). Kept deliberately so the contract holds end-to-end.
+  if (st->all_value != NULL)
+    return st->all_value;
+  return NULL;
+}
+
+extern "C" char const *__kmp_resolve_device_env(char const *base_name,
+                                                int device_id) {
+  // Host queries must use `__kmp_resolve_host_env`; this entry point is for
+  // non-host devices only and takes a 0-based, non-negative 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) {
@@ -371,10 +385,3 @@ extern "C" void __kmp_device_env_reset(void) {
   KMP_INTERNAL_FREE(__kmp_device_env_states);
   __kmp_device_env_states = NULL;
 }
-
-// Public test/query helper. Returns the resolved value for `name` on
-// `device_id`.
-extern "C" char const *__kmpc_get_resolved_device_env(char const *name,
-                                                      int device_id) {
-  return __kmp_resolve_device_env(name, device_id);
-}
diff --git a/openmp/runtime/src/kmp_device_env.h b/openmp/runtime/src/kmp_device_env.h
index e40b61743daab..eeb6d4c6b613c 100644
--- a/openmp/runtime/src/kmp_device_env.h
+++ b/openmp/runtime/src/kmp_device_env.h
@@ -46,10 +46,15 @@ extern "C" {
 //   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 `device_id` per the
-// precedence rules above. Pass `device_id == -1` to request the host;
-// any other negative value returns NULL (defensive). Returns NULL when no
-// source applies (caller falls back to its own default).
+// Resolve the effective string for `base_name` on the host device:
+//   <ENV> > <ENV>_ALL > NULL.
+// Host and device queries are intentionally separate functions so a caller
+// can never accidentally pass a non-host device id (or vice versa).
+char const *__kmp_resolve_host_env(char const *base_name);
+
+// 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 *__kmp_resolve_device_env(char const *base_name, int device_id);
 
 // Record an unsuffixed `<ENV>=value` pair so the host query is consistent
diff --git a/openmp/runtime/test/env/omp_num_threads_all_demo.c b/openmp/runtime/test/env/omp_num_threads_all_demo.c
index eba36b6d7b8a2..258e3be92b380 100644
--- a/openmp/runtime/test/env/omp_num_threads_all_demo.c
+++ b/openmp/runtime/test/env/omp_num_threads_all_demo.c
@@ -7,14 +7,13 @@
 #include <omp.h>
 #include <stdio.h>
 
-extern const char *__kmpc_get_resolved_device_env(const char *name,
-                                                  int device_id);
+extern const char *__kmp_resolve_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_resolved_device_env("OMP_NUM_THREADS", d);
+    const char *v = __kmp_resolve_device_env("OMP_NUM_THREADS", d);
     printf("device %d resolved OMP_NUM_THREADS = %s\n", d, v ? v : "(default)");
   }
   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
index 3f18a96d6b915..923b78b47cf48 100644
--- a/openmp/runtime/test/env/omp_num_threads_all_set_defaults.c
+++ b/openmp/runtime/test/env/omp_num_threads_all_set_defaults.c
@@ -7,8 +7,8 @@
 #include <stdio.h>
 
 extern void kmp_set_defaults(const char *);
-extern const char *__kmpc_get_resolved_device_env(const char *name,
-                                                  int device_id);
+extern const char *__kmp_resolve_host_env(const char *name);
+extern const char *__kmp_resolve_device_env(const char *name, int device_id);
 
 int main(void) {
   int rc = 0;
@@ -20,7 +20,7 @@ int main(void) {
             omp_get_max_threads());
     rc = 1;
   }
-  const char *q = __kmpc_get_resolved_device_env("OMP_NUM_THREADS", -1);
+  const char *q = __kmp_resolve_host_env("OMP_NUM_THREADS");
   if (q == NULL || q[0] != '1' || q[1] != '6' || q[2] != '\0') {
     fprintf(stderr, "FAIL: query host expected '16' got %s\n",
             q ? q : "(null)");
@@ -28,7 +28,7 @@ int main(void) {
   }
 
   kmp_set_defaults("KMP_BLOCKTIME=200");
-  q = __kmpc_get_resolved_device_env("OMP_NUM_THREADS", 0);
+  q = __kmp_resolve_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",
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
index 395e021df6a8d..bf1c83178825d 100644
--- a/openmp/runtime/test/env/omp_num_threads_dev_all_fallback.c
+++ b/openmp/runtime/test/env/omp_num_threads_dev_all_fallback.c
@@ -7,12 +7,10 @@
 #include <stdio.h>
 #include <string.h>
 
-extern const char *__kmpc_get_resolved_device_env(const char *name,
-                                                  int device_id);
+extern const char *__kmp_resolve_host_env(const char *name);
+extern const char *__kmp_resolve_device_env(const char *name, int device_id);
 
-static int check(int device_id, const char *expect) {
-  const char *got =
-      __kmpc_get_resolved_device_env("OMP_NUM_THREADS", 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);
@@ -24,9 +22,9 @@ static int check(int device_id, const char *expect) {
 int main(void) {
   (void)omp_get_max_threads();
   int rc = 0;
-  rc |= check(-1, "8"); // host
-  rc |= check(0, "8");
-  rc |= check(1, "8");
-  rc |= check(2, "8");
+  rc |= check(-1, __kmp_resolve_host_env("OMP_NUM_THREADS"), "8"); // host
+  rc |= check(0, __kmp_resolve_device_env("OMP_NUM_THREADS", 0), "8");
+  rc |= check(1, __kmp_resolve_device_env("OMP_NUM_THREADS", 1), "8");
+  rc |= check(2, __kmp_resolve_device_env("OMP_NUM_THREADS", 2), "8");
   return rc;
 }
diff --git a/openmp/runtime/test/env/omp_num_threads_dev_overflow.c b/openmp/runtime/test/env/omp_num_threads_dev_overflow.c
index 00d77fd24d1af..742a5bb6e7948 100644
--- a/openmp/runtime/test/env/omp_num_threads_dev_overflow.c
+++ b/openmp/runtime/test/env/omp_num_threads_dev_overflow.c
@@ -13,15 +13,14 @@
 #include <stdio.h>
 #include <string.h>
 
-extern const char *__kmpc_get_resolved_device_env(const char *name,
-                                                  int device_id);
+extern const char *__kmp_resolve_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_resolved_device_env("OMP_NUM_THREADS", 2147483646);
+  const char *q = __kmp_resolve_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)");
diff --git a/openmp/runtime/test/env/omp_num_threads_dev_resolve.c b/openmp/runtime/test/env/omp_num_threads_dev_resolve.c
index 4ab49db0372c3..2db0aaae4f2d7 100644
--- a/openmp/runtime/test/env/omp_num_threads_dev_resolve.c
+++ b/openmp/runtime/test/env/omp_num_threads_dev_resolve.c
@@ -1,5 +1,6 @@
-// RUN: %libomp-compile && env OMP_NUM_THREADS_ALL=8 OMP_NUM_THREADS_DEV=64
-// OMP_NUM_THREADS_DEV_0=128 %libomp-run
+// 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.
 
@@ -8,19 +9,13 @@
 #include <stdlib.h>
 #include <string.h>
 
-extern const char *__kmpc_get_resolved_device_env(const char *name,
-                                                  int device_id);
+extern const char *__kmp_resolve_host_env(const char *name);
+extern const char *__kmp_resolve_device_env(const char *name, int device_id);
 
-static int check(const char *name, int device_id, const char *expect) {
-  const char *got = __kmpc_get_resolved_device_env(name, device_id);
-  if (got == NULL) {
-    fprintf(stderr, "FAIL: %s device_id=%d resolved to NULL, expected %s\n",
-            name, device_id, expect);
-    return 1;
-  }
-  if (strcmp(got, expect) != 0) {
-    fprintf(stderr, "FAIL: %s device_id=%d resolved to '%s', expected '%s'\n",
-            name, device_id, got, expect);
+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;
@@ -29,15 +24,10 @@ static int check(const char *name, int device_id, const char *expect) {
 int main(void) {
   int rc = 0;
   (void)omp_get_max_threads();
-  rc |= check("OMP_NUM_THREADS", -1, "8"); // host: _ALL
-  rc |= check("OMP_NUM_THREADS", 0, "128"); // _DEV_0 wins
-  rc |= check("OMP_NUM_THREADS", 1, "64"); // _DEV
-  rc |= check("OMP_NUM_THREADS", 2, "64"); // _DEV (no _DEV_2)
-
-  // Strict host-sentinel contract: only -1 is host.
-  if (__kmpc_get_resolved_device_env("OMP_NUM_THREADS", -2) != NULL) {
-    fprintf(stderr, "FAIL: device_id=-2 must return NULL\n");
-    rc = 1;
-  }
+  rc |= check(__kmp_resolve_host_env("OMP_NUM_THREADS"), -1, "8"); // host: _ALL
+  rc |=
+      check(__kmp_resolve_device_env("OMP_NUM_THREADS", 0), 0, "128"); // _DEV_0
+  rc |= check(__kmp_resolve_device_env("OMP_NUM_THREADS", 1), 1, "64"); // _DEV
+  rc |= check(__kmp_resolve_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
index b6e90b5a368bf..f0f22c3c00252 100644
--- a/openmp/runtime/test/env/omp_num_threads_host_overrides_all.c
+++ b/openmp/runtime/test/env/omp_num_threads_host_overrides_all.c
@@ -1,5 +1,5 @@
-// RUN: %libomp-compile && env OMP_NUM_THREADS=4 OMP_NUM_THREADS_ALL=8
-// %libomp-run
+// RUN: %libomp-compile && \
+// RUN:   env OMP_NUM_THREADS=4 OMP_NUM_THREADS_ALL=8 %libomp-run
 //
 // Host OMP_NUM_THREADS beats _ALL; legacy ICV and query API agree.
 
@@ -7,8 +7,8 @@
 #include <stdio.h>
 #include <string.h>
 
-extern const char *__kmpc_get_resolved_device_env(const char *name,
-                                                  int device_id);
+extern const char *__kmp_resolve_host_env(const char *name);
+extern const char *__kmp_resolve_device_env(const char *name, int device_id);
 
 int main(void) {
   int max = omp_get_max_threads();
@@ -16,14 +16,14 @@ int main(void) {
     fprintf(stderr, "FAIL: omp_get_max_threads()=%d, expected 4\n", max);
     return 1;
   }
-  const char *host = __kmpc_get_resolved_device_env("OMP_NUM_THREADS", -1);
+  const char *host = __kmp_resolve_host_env("OMP_NUM_THREADS");
   if (host == NULL || strcmp(host, "4") != 0) {
     fprintf(stderr, "FAIL: host query expected '4' got '%s'\n",
             host ? host : "(null)");
     return 1;
   }
   // Sibling `_ALL` must still resolve for non-host devices.
-  const char *dev0 = __kmpc_get_resolved_device_env("OMP_NUM_THREADS", 0);
+  const char *dev0 = __kmp_resolve_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)");
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..7c6f88e6698f9
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_host_vs_device.c
@@ -0,0 +1,31 @@
+// RUN: %libomp-compile && \
+// RUN:   env OMP_NUM_THREADS=4 OMP_NUM_THREADS_ALL=8 \
+// RUN:   OMP_NUM_THREADS_DEV_0=128 %libomp-run
+//
+// Host and non-host devices resolve through separate functions.
+
+#include <omp.h>
+#include <stdio.h>
+#include <string.h>
+
+extern const char *__kmp_resolve_host_env(const char *name);
+extern const char *__kmp_resolve_device_env(const char *name, int device_id);
+
+int main(void) {
+  (void)omp_get_max_threads();
+
+  // Host uses the unsuffixed value, not _ALL.
+  const char *host = __kmp_resolve_host_env("OMP_NUM_THREADS");
+  if (host == NULL || strcmp(host, "4") != 0)
+    return 1;
+
+  // Device 0 uses _DEV_0; device 1 falls through to _ALL.
+  const char *dev0 = __kmp_resolve_device_env("OMP_NUM_THREADS", 0);
+  if (dev0 == NULL || strcmp(dev0, "128") != 0)
+    return 2;
+  const char *dev1 = __kmp_resolve_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
index 6b8b631ec4fba..12a65aefb6f76 100644
--- a/openmp/runtime/test/env/omp_num_threads_warnings_off.c
+++ b/openmp/runtime/test/env/omp_num_threads_warnings_off.c
@@ -9,8 +9,7 @@
 #include <stdio.h>
 #include <string.h>
 
-extern const char *__kmpc_get_resolved_device_env(const char *name,
-                                                  int device_id);
+extern const char *__kmp_resolve_host_env(const char *name);
 
 int main(void) {
   int max = omp_get_max_threads();
@@ -18,7 +17,7 @@ int main(void) {
     fprintf(stderr, "FAIL: omp_get_max_threads()=%d, expected 8\n", max);
     return 1;
   }
-  const char *host = __kmpc_get_resolved_device_env("OMP_NUM_THREADS", -1);
+  const char *host = __kmp_resolve_host_env("OMP_NUM_THREADS");
   if (host == NULL || strcmp(host, "8") != 0) {
     fprintf(stderr, "FAIL: host query expected '8' got '%s'\n",
             host ? host : "(null)");



More information about the Openmp-commits mailing list