[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 May 25 09:12:44 PDT 2026
https://github.com/loopacino updated https://github.com/llvm/llvm-project/pull/197191
>From 809769a89ed0f72962a696212128973bb0577fa7 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/6] 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 5b3e437ba4321..4e60a5281a459 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 b1f81f50f21121d3f62537fd67b700f4bad112e1 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/6] 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 5abb21f304cb2debc0965a40091f535844fcc349 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/6] 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 b0173a613a67765def179eb69c374a0e88646bc0 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/6] 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 08e837d3dea11..7339ce97c608e 100644
--- a/openmp/runtime/src/i18n/en_US.txt
+++ b/openmp/runtime/src/i18n/en_US.txt
@@ -482,6 +482,8 @@ AffHWSubsetIgnoringAttr "KMP_HW_SUBSET: ignoring %1$s attribute. This machi
TargetMemNotAvailable "Target memory not available, will use default allocator."
AffIgnoringNonHybrid "%1$s ignored: This machine is not a hybrid architecutre. Using \"%2$s\" instead."
AffIgnoringNotAvailable "%1$s ignored: %2$s is not available. Using \"%3$s\" instead."
+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 840d03ac946ef0bd4ec39bf75964a378b328dc0f 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/6] 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 7339ce97c608e..effff2a0eee56 100644
--- a/openmp/runtime/src/i18n/en_US.txt
+++ b/openmp/runtime/src/i18n/en_US.txt
@@ -483,7 +483,7 @@ TargetMemNotAvailable "Target memory not available, will use default allo
AffIgnoringNonHybrid "%1$s ignored: This machine is not a hybrid architecutre. Using \"%2$s\" instead."
AffIgnoringNotAvailable "%1$s ignored: %2$s is not available. Using \"%3$s\" instead."
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 3a1cacef9ecffa041b4a4de5d4adfd7be0a30753 Mon Sep 17 00:00:00 2001
From: amtiwari <amtiwari at amd.com>
Date: Mon, 25 May 2026 11:53:03 -0400
Subject: [PATCH 6/6] tests
---
openmp/runtime/src/kmp_device_env.cpp | 3 +-
.../env/omp_default_device_all_rejected.c | 22 ++++++++++
.../test/env/omp_denylist_extra_coverage.c | 30 +++++++++++++
.../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 | 38 ++++++++++++++++
.../test/env/omp_num_threads_warnings_off.c | 36 ++++++++++++++++
12 files changed, 342 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..ec485bc2afff2
--- /dev/null
+++ b/openmp/runtime/test/env/omp_denylist_extra_coverage.c
@@ -0,0 +1,30 @@
+// 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
+//
+// Extra coverage for the device-scope denylist: well-known global-scope OMP
+// env vars must reject all suffix forms (`_ALL`, `_DEV`, `_DEV_<d>`) with a
+// warning AND must not affect the legitimate sibling `_ALL` on an eligible
+// base. Guards against accidental deletions from
+// `__kmp_device_env_denied_bases`.
+
+#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..c12bd420518eb
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_host_overrides_all.c
@@ -0,0 +1,38 @@
+// RUN: %libomp-compile && env OMP_NUM_THREADS=4 OMP_NUM_THREADS_ALL=8
+// %libomp-run
+//
+// OpenMP 6.0: host `<ENV>` beats `<ENV>_ALL`.
+//
+// Verified twice: (a) via the legacy host ICV (`omp_get_max_threads`), and
+// (b) via the device-scope query API to exercise the patch's registry path.
+// Without (b), the test passes vacuously without the patch since libomp's
+// legacy parser already handles unsuffixed `OMP_NUM_THREADS`.
+
+#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..5c8d407f67f53
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_warnings_off.c
@@ -0,0 +1,36 @@
+// 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
+//
+// With KMP_WARNINGS=0, the device-scope pre-pass must NOT emit warnings
+// for malformed (`OMP_NUM_THREADS_DEV_abc`) or denylist
+// (`OMP_DEFAULT_DEVICE_ALL`) suffix forms. The legitimate sibling `_ALL=8` on
+// an eligible base must still apply.
+
+#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
More information about the Openmp-commits
mailing list