[Openmp-commits] [openmp] 94f5dd0 - [libomp] OpenMP 6.0 Device-Scope Environment Variables (#197191)
via Openmp-commits
openmp-commits at lists.llvm.org
Mon Sep 21 22:08:54 PDT 2026
Author: Amit Tiwari
Date: 2026-09-22T10:38:48+05:30
New Revision: 94f5dd0eb25ed97603af1d0a501c412fcfea6696
URL: https://github.com/llvm/llvm-project/commit/94f5dd0eb25ed97603af1d0a501c412fcfea6696
DIFF: https://github.com/llvm/llvm-project/commit/94f5dd0eb25ed97603af1d0a501c412fcfea6696.diff
LOG: [libomp] OpenMP 6.0 Device-Scope Environment Variables (#197191)
The patch introduces `_ALL` `_DEV` `_DEV_<id>` env var suffixes to the
LLVM's OpenMP host runtime `libomp`. The eligible-list env var currently
supports `OMP_NUM_THREADS`.
Assisted by Claude Opus 4.7 / Claude 4.6 Sonnet
Added:
openmp/runtime/src/kmp_device_env.cpp
openmp/runtime/src/kmp_device_env.h
openmp/runtime/test/env/omp_default_device_all_rejected.c
openmp/runtime/test/env/omp_denylist_extra_coverage.c
openmp/runtime/test/env/omp_num_threads_all_demo.c
openmp/runtime/test/env/omp_num_threads_all_host.c
openmp/runtime/test/env/omp_num_threads_all_set_defaults.c
openmp/runtime/test/env/omp_num_threads_dev_all_fallback.c
openmp/runtime/test/env/omp_num_threads_dev_invalid.c
openmp/runtime/test/env/omp_num_threads_dev_overflow.c
openmp/runtime/test/env/omp_num_threads_dev_resolve.c
openmp/runtime/test/env/omp_num_threads_host_overrides_all.c
openmp/runtime/test/env/omp_num_threads_host_vs_device.c
openmp/runtime/test/env/omp_num_threads_warnings_off.c
Modified:
openmp/runtime/src/CMakeLists.txt
openmp/runtime/src/dllexports
openmp/runtime/src/i18n/en_US.txt
openmp/runtime/src/kmp_runtime.cpp
openmp/runtime/src/kmp_settings.cpp
Removed:
################################################################################
diff --git a/openmp/runtime/src/CMakeLists.txt b/openmp/runtime/src/CMakeLists.txt
index fc298312c01ed..f8bf6ac0cfd9f 100644
--- a/openmp/runtime/src/CMakeLists.txt
+++ b/openmp/runtime/src/CMakeLists.txt
@@ -86,6 +86,7 @@ else()
kmp_atomic.cpp
kmp_csupport.cpp
kmp_debug.cpp
+ kmp_device_env.cpp
kmp_itt.cpp
kmp_invoke_microtask.cpp
kmp_environment.cpp
diff --git a/openmp/runtime/src/dllexports b/openmp/runtime/src/dllexports
index a8196d447d627..24c141e078b5b 100644
--- a/openmp/runtime/src/dllexports
+++ b/openmp/runtime/src/dllexports
@@ -579,6 +579,7 @@ kmp_set_disp_num_buffers 890
__kmpc_free
__kmpc_init_allocator
__kmpc_destroy_allocator
+ __kmpc_get_device_env
%endif
omp_set_affinity_format 748
omp_get_affinity_format 749
diff --git a/openmp/runtime/src/i18n/en_US.txt b/openmp/runtime/src/i18n/en_US.txt
index 84a515383a805..a2f6d0f617224 100644
--- a/openmp/runtime/src/i18n/en_US.txt
+++ b/openmp/runtime/src/i18n/en_US.txt
@@ -491,6 +491,8 @@ AffIgnoringNonHybrid "%1$s ignored: This machine is not a hybrid archite
AffIgnoringNotAvailable "%1$s ignored: %2$s is not available. Using \"%3$s\" instead."
SetNumTeamsInParOrTeamsRegion "%1$s() called inside a parallel or teams region; call ignored."
SetTeamsThreadLimitInParOrTeamsRegion "%1$s() called inside a parallel or teams region; call ignored."
+MalformedDeviceEnvVar "%1$s: malformed device-scope environment variable name; expected `_ALL`, `_DEV`, or `_DEV_<n>` with a non-negative integer device id; ignored."
+DeviceEnvVarOnGlobalScope "%1$s: device-scope environment variable suffix (`_ALL`/`_DEV`/`_DEV_<n>`) is not allowed on this base name (global-scope ICV); ignored."
# --------------------------------------------------------------------------------------------------
-*- HINTS -*-
diff --git a/openmp/runtime/src/kmp_device_env.cpp b/openmp/runtime/src/kmp_device_env.cpp
new file mode 100644
index 0000000000000..d722e9d617b85
--- /dev/null
+++ b/openmp/runtime/src/kmp_device_env.cpp
@@ -0,0 +1,353 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// OpenMP 6.0 device-scope env-var registry. See kmp_device_env.h for the
+// public-internal contract;
+//
+//===----------------------------------------------------------------------===//
+
+#include "kmp.h"
+#include "kmp_device_env.h"
+#include "kmp_i18n.h"
+#include "kmp_str.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+// Eligible-var table. Only env vars associated with non-global-scope ICVs
+// (and not `OMP_DEFAULT_DEVICE`) are eligible for `_ALL`/`_DEV[_d]` forms,
+// per OpenMP 6.0.
+static char const *const __kmp_device_env_eligible_names[] = {
+ "OMP_NUM_THREADS",
+ NULL,
+};
+
+// Denylist of well-known OpenMP env vars that initialize *global-scope* ICVs
+// or `OMP_DEFAULT_DEVICE`.
+static char const *const __kmp_device_env_denied_bases[] = {
+ "OMP_DEFAULT_DEVICE",
+ "OMP_MAX_TASK_PRIORITY",
+ "OMP_TARGET_OFFLOAD",
+ "OMP_DISPLAY_ENV",
+ "OMP_DISPLAY_AFFINITY",
+ "OMP_AFFINITY_FORMAT",
+ "OMP_CANCELLATION",
+ "OMP_TOOL",
+ "OMP_TOOL_LIBRARIES",
+ "OMP_TOOL_VERBOSE_INIT",
+ "OMP_DEBUG",
+ NULL,
+};
+
+struct kmp_device_env_dev_node_t {
+ int device_id;
+ char *value;
+ struct kmp_device_env_dev_node_t *next;
+};
+
+struct kmp_device_env_state_t {
+ char *all_value; // <ENV>_ALL
+ char *dev_default_value; // <ENV>_DEV
+ kmp_device_env_dev_node_t *per_device; // <ENV>_DEV_<d>
+};
+
+static kmp_device_env_state_t *__kmp_device_env_states = NULL;
+
+static int __kmp_device_env_count(void) {
+ return sizeof(__kmp_device_env_eligible_names) /
+ sizeof(__kmp_device_env_eligible_names[0]) -
+ 1;
+}
+
+// Invariant: the eligible-name and denied-base tables must be disjoint --
+// the classifier short-circuits on the first eligible match and never
+// consults the denylist (see `__kmp_classify_device_env_name`). Verified
+// once-per-process in debug builds to protect future maintainers.
+static void __kmp_device_env_assert_tables_disjoint(void) {
+#ifdef KMP_DEBUG
+ for (int i = 0; __kmp_device_env_eligible_names[i] != NULL; ++i) {
+ for (int j = 0; __kmp_device_env_denied_bases[j] != NULL; ++j) {
+ KMP_DEBUG_ASSERT(strcmp(__kmp_device_env_eligible_names[i],
+ __kmp_device_env_denied_bases[j]) != 0);
+ }
+ }
+#endif
+}
+
+static void __kmp_device_env_lazy_init(void) {
+ if (__kmp_device_env_states != NULL)
+ return;
+ __kmp_device_env_assert_tables_disjoint();
+ int count = __kmp_device_env_count();
+ __kmp_device_env_states = (kmp_device_env_state_t *)KMP_INTERNAL_MALLOC(
+ sizeof(kmp_device_env_state_t) * count);
+ for (int i = 0; i < count; ++i) {
+ __kmp_device_env_states[i].all_value = NULL;
+ __kmp_device_env_states[i].dev_default_value = NULL;
+ __kmp_device_env_states[i].per_device = NULL;
+ }
+}
+
+static int __kmp_device_env_index(char const *base_name) {
+ if (base_name == NULL)
+ return -1;
+ for (int i = 0; __kmp_device_env_eligible_names[i] != NULL; ++i)
+ if (strcmp(__kmp_device_env_eligible_names[i], base_name) == 0)
+ return i;
+ return -1;
+}
+
+extern "C" char const *__kmp_device_env_eligible_name(int index) {
+ if (index < 0)
+ return NULL;
+ int count = __kmp_device_env_count();
+ if (index >= count)
+ return NULL;
+ return __kmp_device_env_eligible_names[index];
+}
+
+// Match `full_name` against `base_name + suffix`. Returns the pointer just
+// after the matched suffix on success, or NULL on failure.
+static char const *__kmp_strip_prefix_and_suffix(char const *full_name,
+ char const *base_name,
+ char const *suffix) {
+ size_t base_len = strlen(base_name);
+ if (strncmp(full_name, base_name, base_len) != 0)
+ return NULL;
+ char const *rest = full_name + base_len;
+ size_t sfx_len = strlen(suffix);
+ if (strncmp(rest, suffix, sfx_len) != 0)
+ return NULL;
+ return rest + sfx_len;
+}
+
+// True if `s` is non-empty and consists entirely of ASCII decimal digits.
+static int __kmp_is_nonneg_int(char const *s) {
+ if (s == NULL || *s == '\0')
+ return 0;
+ for (char const *p = s; *p; ++p) {
+ if (*p < '0' || *p > '9')
+ return 0;
+ }
+ return 1;
+}
+
+// Returns the parsed value on success, -1 on overflow/empty/non-digit input.
+// Leading zeros are allowed (e.g., `000000000001` is device 1). INT_MAX and
+// larger values are rejected so -1 remains the only error sentinel.
+static int __kmp_parse_dev_id(char const *s) {
+ if (!__kmp_is_nonneg_int(s))
+ return -1;
+ long long v = 0;
+ for (char const *p = s; *p; ++p) {
+ v = v * 10 + (*p - '0');
+ if (v >= (long long)INT_MAX)
+ return -1;
+ }
+ return (int)v;
+}
+
+enum kmp_dev_env_kind_t {
+ kmp_dev_env_none = 0,
+ kmp_dev_env_all, // <ENV>_ALL
+ kmp_dev_env_dev_default, // <ENV>_DEV
+ kmp_dev_env_dev_id, // <ENV>_DEV_<d>
+};
+
+// Try to classify `full_name` as `<base>_<suffix>` where suffix is `_ALL`,
+// `_DEV`, or `_DEV_<token>`. On match, returns the kind and (for `_DEV_<n>`)
+// fills `*out_dev_id` (or sets it to -1 to flag a malformed token, e.g. a
+// non-integer or overflowing token). Returns kmp_dev_env_none if `full_name`
+// does not have any of those exact suffixes for `base`.
+static kmp_dev_env_kind_t
+__kmp_match_suffix(char const *full_name, char const *base, int *out_dev_id) {
+ *out_dev_id = -1;
+ if (char const *tail = __kmp_strip_prefix_and_suffix(full_name, base, "_ALL"))
+ if (*tail == '\0')
+ return kmp_dev_env_all;
+ if (char const *tail =
+ __kmp_strip_prefix_and_suffix(full_name, base, "_DEV_")) {
+ int dev_id = __kmp_parse_dev_id(tail);
+ *out_dev_id = dev_id; // -1 signals malformed/overflowing token
+ return kmp_dev_env_dev_id;
+ }
+ if (char const *tail = __kmp_strip_prefix_and_suffix(full_name, base, "_DEV"))
+ if (*tail == '\0')
+ return kmp_dev_env_dev_default;
+ return kmp_dev_env_none;
+}
+
+// Decompose `full_name` into base index, kind and (optionally) device id.
+// Sets `*out_dev_id` only when the kind is `kmp_dev_env_dev_id`.
+//
+// `*out_index` is set to the eligible-table index when the base matches an
+// eligible name, or -1 if `full_name` matches a *denied* (global-scope)
+// base + suffix (the caller emits a warning in that case).
+//
+// Returns the kind. `kmp_dev_env_none` means the name is not a device-scope
+// variant of either an eligible base or a known denied base. The caller
+// should treat such names normally (no warning) so unrelated env vars like
+// `OMP_DEV_LIST` are not misinterpreted.
+static kmp_dev_env_kind_t __kmp_classify_device_env_name(char const *full_name,
+ int *out_index,
+ int *out_dev_id) {
+ *out_index = -1;
+ *out_dev_id = -1;
+
+ if (full_name == NULL || *full_name == '\0')
+ return kmp_dev_env_none;
+
+ // First, try to recognize a known eligible base + suffix pairing.
+ for (int i = 0; __kmp_device_env_eligible_names[i] != NULL; ++i) {
+ char const *base = __kmp_device_env_eligible_names[i];
+ int dev_id = -1;
+ kmp_dev_env_kind_t k = __kmp_match_suffix(full_name, base, &dev_id);
+ if (k != kmp_dev_env_none) {
+ *out_index = i;
+ *out_dev_id = dev_id;
+ return k;
+ }
+ }
+
+ // Second, check the denylist of well-known global-scope OMP env vars. Only
+ // these well-defined bases trigger the global-scope-rejection warning;
+ // unrelated user-defined env vars (e.g. `OMP_DEV_LIST`, `KMP_X`) are
+ // ignored silently.
+ for (int i = 0; __kmp_device_env_denied_bases[i] != NULL; ++i) {
+ char const *base = __kmp_device_env_denied_bases[i];
+ int dev_id = -1;
+ kmp_dev_env_kind_t k = __kmp_match_suffix(full_name, base, &dev_id);
+ if (k != kmp_dev_env_none)
+ return k; // *out_index stays -1 -- denylist hit
+ }
+
+ return kmp_dev_env_none;
+}
+
+static void __kmp_device_env_set_string(char **slot, char const *value) {
+ if (*slot != NULL) {
+ __kmp_str_free(slot);
+ }
+ *slot = __kmp_str_format("%s", value);
+}
+
+static void __kmp_device_env_set_per_device(kmp_device_env_state_t *st,
+ int device_id, char const *value) {
+ for (kmp_device_env_dev_node_t *n = st->per_device; n != NULL; n = n->next) {
+ if (n->device_id == device_id) {
+ __kmp_str_free(&n->value);
+ n->value = __kmp_str_format("%s", value);
+ return;
+ }
+ }
+ kmp_device_env_dev_node_t *node =
+ (kmp_device_env_dev_node_t *)KMP_INTERNAL_MALLOC(
+ sizeof(kmp_device_env_dev_node_t));
+ node->device_id = device_id;
+ node->value = __kmp_str_format("%s", value);
+ node->next = st->per_device;
+ st->per_device = node;
+}
+
+extern "C" int __kmp_device_env_record(char const *full_name,
+ char const *value) {
+ if (full_name == NULL || value == NULL)
+ return 0;
+
+ int idx = -1;
+ int dev_id = -1;
+ kmp_dev_env_kind_t kind =
+ __kmp_classify_device_env_name(full_name, &idx, &dev_id);
+ if (kind == kmp_dev_env_none)
+ return 0; // not a device-scope variant -- caller handles normally
+
+ // Suffix recognized but base is on the denylist (global-scope ICV). Per the
+ // OpenMP 6.0 restriction, reject with a warning.
+ if (idx < 0) {
+ KMP_WARNING(DeviceEnvVarOnGlobalScope, full_name);
+ return 1;
+ }
+
+ // For the eligible-base path, malformed `<ENV>_DEV_<token>` (non-integer or
+ // overflowing) is rejected before we touch the registry.
+ if (kind == kmp_dev_env_dev_id && dev_id < 0) {
+ KMP_WARNING(MalformedDeviceEnvVar, full_name);
+ return 1;
+ }
+
+ __kmp_device_env_lazy_init();
+ kmp_device_env_state_t *st = &__kmp_device_env_states[idx];
+
+ switch (kind) {
+ case kmp_dev_env_all:
+ __kmp_device_env_set_string(&st->all_value, value);
+ return 1;
+ case kmp_dev_env_dev_default:
+ __kmp_device_env_set_string(&st->dev_default_value, value);
+ return 1;
+ case kmp_dev_env_dev_id:
+ __kmp_device_env_set_per_device(st, dev_id, value);
+ return 1;
+ case kmp_dev_env_none:
+ break;
+ }
+ return 0;
+}
+
+// Look up the registry state for `base_name`, or NULL if it is not an
+// eligible base or nothing was recorded yet.
+static kmp_device_env_state_t *__kmp_device_env_lookup(char const *base_name) {
+ int idx = __kmp_device_env_index(base_name);
+ if (idx < 0 || __kmp_device_env_states == NULL)
+ return NULL;
+ return &__kmp_device_env_states[idx];
+}
+
+extern "C" char const *__kmpc_get_device_env(char const *base_name,
+ int device_id) {
+ // Host ICVs are observed through the existing OpenMP APIs. This entry
+ // point is for non-host devices only and takes a 0-based device id.
+ KMP_DEBUG_ASSERT(device_id >= 0);
+ if (device_id < 0)
+ return NULL;
+ kmp_device_env_state_t *st = __kmp_device_env_lookup(base_name);
+ if (st == NULL)
+ return NULL;
+
+ // Non-host device d: <ENV>_DEV_<d> > <ENV>_DEV > <ENV>_ALL > default.
+ for (kmp_device_env_dev_node_t *n = st->per_device; n != NULL; n = n->next) {
+ if (n->device_id == device_id)
+ return n->value;
+ }
+ if (st->dev_default_value != NULL)
+ return st->dev_default_value;
+ if (st->all_value != NULL)
+ return st->all_value;
+ return NULL;
+}
+
+extern "C" void __kmp_device_env_reset(void) {
+ if (__kmp_device_env_states == NULL)
+ return;
+ int count = __kmp_device_env_count();
+ for (int i = 0; i < count; ++i) {
+ kmp_device_env_state_t *st = &__kmp_device_env_states[i];
+ __kmp_str_free(&st->all_value);
+ __kmp_str_free(&st->dev_default_value);
+ kmp_device_env_dev_node_t *n = st->per_device;
+ while (n) {
+ kmp_device_env_dev_node_t *next = n->next;
+ __kmp_str_free(&n->value);
+ KMP_INTERNAL_FREE(n);
+ n = next;
+ }
+ st->per_device = NULL;
+ }
+ KMP_INTERNAL_FREE(__kmp_device_env_states);
+ __kmp_device_env_states = NULL;
+}
diff --git a/openmp/runtime/src/kmp_device_env.h b/openmp/runtime/src/kmp_device_env.h
new file mode 100644
index 0000000000000..32aa2c2477ff4
--- /dev/null
+++ b/openmp/runtime/src/kmp_device_env.h
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// Implements parsing, storage and precedence resolution for the OpenMP 6.0
+// device-scope environment variable extensions described in OpenMP 6.0
+// Section 3.2 (ICV initialization) and Chapter 4 (Environment Variables):
+//
+// <ENV> -- sets the host device ICV.
+// <ENV>_ALL -- sets the host and non-host device ICVs that are not
+// overridden by a more specific form.
+// <ENV>_DEV -- sets all non-host device ICVs that are not overridden
+// by an `<ENV>_DEV_<d>` form.
+// <ENV>_DEV_<d> -- sets the ICV for non-host device with id `d` (a
+// non-negative integer).
+//
+// Precedence:
+// Host: <ENV> > <ENV>_ALL > default
+// Device d: <ENV>_DEV_<d> > <ENV>_DEV > <ENV>_ALL > default
+//
+// Restrictions enforced:
+// * Device-specific environment variables are only accepted for env vars
+// listed in `__kmp_device_env_eligible_names` (i.e. those that initialize
+// device-scope ICVs and are not global-scope or `OMP_DEFAULT_DEVICE`).
+// Suffix forms applied to other env vars are ignored with a warning.
+// * `<ENV>_DEV_<token>` where <token> is not a non-negative integer is
+// rejected with a warning. The spec requires that device-specific
+// environment variables must not specify the host device; we accept only
+// non-negative integer non-host device ids.
+// * Conflicting settings of `<ENV>_DEV_<d>` (same d) follow last-parsed-wins
+// semantics, mirroring existing libomp env handling.
+
+#ifndef KMP_DEVICE_ENV_H
+#define KMP_DEVICE_ENV_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Consume `full_name=value` if it is a device-scope variant. Returns:
+// 1 -- consumed (stored, OR rejected with a warning); caller must skip it.
+// 0 -- not a device-scope variant; caller continues normal processing.
+int __kmp_device_env_record(char const *full_name, char const *value);
+
+// Resolve the effective string for `base_name` on a non-host device:
+// <ENV>_DEV_<d> > <ENV>_DEV > <ENV>_ALL > NULL.
+// `device_id` is 0-based and must be >= 0.
+char const *__kmpc_get_device_env(char const *base_name, int device_id);
+
+// Free all storage owned by the registry.
+void __kmp_device_env_reset(void);
+
+// Iterate the eligible base-name table; returns NULL once exhausted.
+char const *__kmp_device_env_eligible_name(int index);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // KMP_DEVICE_ENV_H
diff --git a/openmp/runtime/src/kmp_runtime.cpp b/openmp/runtime/src/kmp_runtime.cpp
index 22ec2605718a3..8ebebfcb7e056 100644
--- a/openmp/runtime/src/kmp_runtime.cpp
+++ b/openmp/runtime/src/kmp_runtime.cpp
@@ -13,6 +13,7 @@
#include "kmp.h"
#include "kmp_affinity.h"
#include "kmp_atomic.h"
+#include "kmp_device_env.h"
#include "kmp_environment.h"
#include "kmp_error.h"
#include "kmp_i18n.h"
@@ -8344,6 +8345,8 @@ void __kmp_cleanup(void) {
__kmp_affinity_format = NULL;
}
+ __kmp_device_env_reset();
+
__kmp_i18n_catclose();
if (__kmp_nesting_nth_level)
diff --git a/openmp/runtime/src/kmp_settings.cpp b/openmp/runtime/src/kmp_settings.cpp
index 3ab604e15c017..f01231b7a168b 100644
--- a/openmp/runtime/src/kmp_settings.cpp
+++ b/openmp/runtime/src/kmp_settings.cpp
@@ -16,6 +16,7 @@
#if KMP_USE_HIER_SCHED
#include "kmp_dispatch_hier.h"
#endif
+#include "kmp_device_env.h"
#include "kmp_environment.h"
#include "kmp_i18n.h"
#include "kmp_io.h"
@@ -6089,12 +6090,32 @@ static void __kmp_print_affinity_settings(const kmp_affinity_t *affinity) {
}
#endif
+// If `base_name` is on the device-scope eligible list, return the value of
+// `<base_name>_ALL` from `block` (or NULL). Returns NULL for non-eligible
+// names.
+static char const *__kmp_aux_env_blk_all_fallback(kmp_env_blk_t *block,
+ char const *base_name) {
+ for (int e = 0;; ++e) {
+ char const *base = __kmp_device_env_eligible_name(e);
+ if (base == NULL)
+ return NULL;
+ if (strcmp(base, base_name) != 0)
+ continue;
+ char *all_name = __kmp_str_format("%s_ALL", base_name);
+ char const *value = __kmp_env_blk_var(block, all_name);
+ __kmp_str_free(&all_name);
+ return value;
+ }
+}
+
static void __kmp_aux_env_initialize(kmp_env_blk_t *block) {
char const *value;
/* OMP_NUM_THREADS */
value = __kmp_env_blk_var(block, "OMP_NUM_THREADS");
+ if (value == NULL)
+ value = __kmp_aux_env_blk_all_fallback(block, "OMP_NUM_THREADS");
if (value) {
ompc_set_num_threads(__kmp_dflt_team_nth);
}
@@ -6139,20 +6160,6 @@ void __kmp_env_initialize(char const *string) {
}
__kmp_env_blk_init(&block, string);
- // update the set flag on all entries that have an env var
- for (i = 0; i < block.count; ++i) {
- if ((block.vars[i].name == NULL) || (*block.vars[i].name == '\0')) {
- continue;
- }
- if (block.vars[i].value == NULL) {
- continue;
- }
- kmp_setting_t *setting = __kmp_stg_find(block.vars[i].name);
- if (setting != NULL) {
- setting->set = 1;
- }
- }
-
// We need to know if blocktime and use_yield were set when processing
// OMP_WAIT_POLICY
blocktime_str = __kmp_env_blk_var(&block, "KMP_BLOCKTIME");
@@ -6223,6 +6230,25 @@ void __kmp_env_initialize(char const *string) {
#endif /* KMP_AFFINITY_SUPPORTED */
+ // Deliberately placed AFTER the KMP_WARNINGS special case above so that any
+ // warnings the pre-pass emits (MalformedDeviceEnvVar /
+ // DeviceEnvVarOnGlobalScope) honour the user-requested warning level.
+ for (i = 0; i < block.count; ++i) {
+ if ((block.vars[i].name == NULL) || (*block.vars[i].name == '\0')) {
+ continue;
+ }
+ if (block.vars[i].value == NULL) {
+ continue;
+ }
+ if (__kmp_device_env_record(block.vars[i].name, block.vars[i].value))
+ continue;
+
+ kmp_setting_t *setting = __kmp_stg_find(block.vars[i].name);
+ if (setting != NULL) {
+ setting->set = 1;
+ }
+ }
+
// Set up the nested proc bind type vector.
if (__kmp_nested_proc_bind.bind_types == NULL) {
__kmp_nested_proc_bind.bind_types =
@@ -6258,6 +6284,23 @@ void __kmp_env_initialize(char const *string) {
__kmp_stg_parse(block.vars[i].name, block.vars[i].value);
}
+ // Device-scope env-var post-pass: when the current block has `<ENV>_ALL` but
+ // no unsuffixed `<ENV>`, replay `_ALL` so the host ICV picks it up. We query
+ // the block (not the registry) so an unrelated kmp_set_defaults does not
+ // accidentally replay a stale `_ALL`.
+ for (int e = 0;; ++e) {
+ char const *base = __kmp_device_env_eligible_name(e);
+ if (base == NULL)
+ break;
+ if (__kmp_env_blk_var(&block, base) != NULL)
+ continue;
+ char *all_name = __kmp_str_format("%s_ALL", base);
+ char const *all_v = __kmp_env_blk_var(&block, all_name);
+ __kmp_str_free(&all_name);
+ if (all_v != NULL)
+ __kmp_stg_parse(base, all_v);
+ }
+
// If user locks have been allocated yet, don't reset the lock vptr table.
if (!__kmp_init_user_locks) {
if (__kmp_user_lock_kind == lk_default) {
diff --git a/openmp/runtime/test/env/omp_default_device_all_rejected.c b/openmp/runtime/test/env/omp_default_device_all_rejected.c
new file mode 100644
index 0000000000000..4fa11f1e91cae
--- /dev/null
+++ b/openmp/runtime/test/env/omp_default_device_all_rejected.c
@@ -0,0 +1,23 @@
+// RUN: %libomp-compile
+// RUN: env KMP_WARNINGS=1 OMP_DEFAULT_DEVICE_ALL=2 %libomp-run 2>&1 \
+// RUN: | FileCheck %s
+//
+// Suffix on global-scope base must be rejected with a warning AND not
+// reach the ICV.
+
+#include <omp.h>
+#include <stdio.h>
+
+int main(void) {
+ (void)omp_get_max_threads();
+ int dd = omp_get_default_device();
+ if (dd == 2) {
+ fprintf(stderr, "FAIL: OMP_DEFAULT_DEVICE_ALL=2 was applied; got %d\n", dd);
+ return 1;
+ }
+ printf("DONE\n");
+ return 0;
+}
+
+// CHECK: {{^OMP: Warning #[0-9]+:.*OMP_DEFAULT_DEVICE_ALL.*}}
+// CHECK: DONE
diff --git a/openmp/runtime/test/env/omp_denylist_extra_coverage.c b/openmp/runtime/test/env/omp_denylist_extra_coverage.c
new file mode 100644
index 0000000000000..6eb68acf63273
--- /dev/null
+++ b/openmp/runtime/test/env/omp_denylist_extra_coverage.c
@@ -0,0 +1,26 @@
+// RUN: %libomp-compile
+// RUN: env KMP_WARNINGS=1 OMP_TARGET_OFFLOAD_DEV_0=mandatory \
+// RUN: OMP_TOOL_LIBRARIES_ALL=libfoo.so \
+// RUN: OMP_CANCELLATION_DEV=true \
+// RUN: OMP_NUM_THREADS_ALL=8 \
+// RUN: %libomp-run 2>&1 | FileCheck %s
+//
+// Denylist: suffixed global OMP_* warns; OMP_NUM_THREADS_ALL still applies.
+
+#include <omp.h>
+#include <stdio.h>
+
+int main(void) {
+ int max = omp_get_max_threads();
+ if (max != 8) {
+ fprintf(stderr, "FAIL: omp_get_max_threads()=%d, expected 8\n", max);
+ return 1;
+ }
+ printf("DONE\n");
+ return 0;
+}
+
+// CHECK-DAG: {{^OMP: Warning #[0-9]+:.*OMP_TARGET_OFFLOAD_DEV_0.*}}
+// CHECK-DAG: {{^OMP: Warning #[0-9]+:.*OMP_TOOL_LIBRARIES_ALL.*}}
+// CHECK-DAG: {{^OMP: Warning #[0-9]+:.*OMP_CANCELLATION_DEV.*}}
+// CHECK: DONE
diff --git a/openmp/runtime/test/env/omp_num_threads_all_demo.c b/openmp/runtime/test/env/omp_num_threads_all_demo.c
new file mode 100644
index 0000000000000..4baa668dbd8d6
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_all_demo.c
@@ -0,0 +1,25 @@
+// RUN: %libomp-compile
+// RUN: env OMP_NUM_THREADS_ALL=8 OMP_NUM_THREADS_DEV_0=128 %libomp-run \
+// RUN: | FileCheck %s
+//
+// Demo: host=8 (_ALL), device 0=128 (_DEV_0), other devices=8 (_ALL fallback).
+
+#include <omp.h>
+#include <stdio.h>
+
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+int main(void) {
+ int host_max = omp_get_max_threads();
+ printf("host omp_get_max_threads() = %d\n", host_max);
+ for (int d = 0; d < 3; ++d) {
+ const char *v = __kmpc_get_device_env("OMP_NUM_THREADS", d);
+ printf("device %d resolved OMP_NUM_THREADS = %s\n", d, v ? v : "(default)");
+ }
+ return 0;
+}
+
+// CHECK: host omp_get_max_threads() = 8
+// CHECK: device 0 resolved OMP_NUM_THREADS = 128
+// CHECK: device 1 resolved OMP_NUM_THREADS = 8
+// CHECK: device 2 resolved OMP_NUM_THREADS = 8
diff --git a/openmp/runtime/test/env/omp_num_threads_all_host.c b/openmp/runtime/test/env/omp_num_threads_all_host.c
new file mode 100644
index 0000000000000..93694a5fb45a9
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_all_host.c
@@ -0,0 +1,16 @@
+// RUN: %libomp-compile && env OMP_NUM_THREADS_ALL=8 %libomp-run
+//
+// OpenMP 6.0: host falls through to `_ALL` when `<ENV>` is unset.
+
+#include <omp.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(void) {
+ int max = omp_get_max_threads();
+ if (max != 8) {
+ fprintf(stderr, "FAIL: omp_get_max_threads()=%d, expected 8\n", max);
+ return 1;
+ }
+ return 0;
+}
diff --git a/openmp/runtime/test/env/omp_num_threads_all_set_defaults.c b/openmp/runtime/test/env/omp_num_threads_all_set_defaults.c
new file mode 100644
index 0000000000000..d6205704827b9
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_all_set_defaults.c
@@ -0,0 +1,32 @@
+// RUN: %libomp-compile-and-run
+//
+// kmp_set_defaults("OMP_NUM_THREADS_ALL=N") must propagate to live threads,
+// and an unrelated kmp_set_defaults call must not wipe registry state.
+
+#include <omp.h>
+#include <stdio.h>
+
+extern void kmp_set_defaults(const char *);
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+int main(void) {
+ int rc = 0;
+ (void)omp_get_max_threads();
+
+ kmp_set_defaults("OMP_NUM_THREADS_ALL=16");
+ if (omp_get_max_threads() != 16) {
+ fprintf(stderr, "FAIL: after _ALL=16 expected 16 got %d\n",
+ omp_get_max_threads());
+ rc = 1;
+ }
+
+ kmp_set_defaults("KMP_BLOCKTIME=200");
+ const char *q = __kmpc_get_device_env("OMP_NUM_THREADS", 0);
+ if (q == NULL || q[0] != '1' || q[1] != '6' || q[2] != '\0') {
+ fprintf(stderr,
+ "FAIL: registry wiped by unrelated kmp_set_defaults; got %s\n",
+ q ? q : "(null)");
+ rc = 1;
+ }
+ return rc;
+}
diff --git a/openmp/runtime/test/env/omp_num_threads_dev_all_fallback.c b/openmp/runtime/test/env/omp_num_threads_dev_all_fallback.c
new file mode 100644
index 0000000000000..fc9ee4d4f056c
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_dev_all_fallback.c
@@ -0,0 +1,32 @@
+// RUN: %libomp-compile && env OMP_NUM_THREADS_ALL=8 %libomp-run
+//
+// OpenMP 6.0: every non-host device falls through to `_ALL` when no
+// `_DEV[_d]` is set.
+
+#include <omp.h>
+#include <stdio.h>
+#include <string.h>
+
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+static int check(int device_id, const char *got, const char *expect) {
+ if (!got || strcmp(got, expect) != 0) {
+ fprintf(stderr, "FAIL: device_id=%d got '%s' expected '%s'\n", device_id,
+ got ? got : "(null)", expect);
+ return 1;
+ }
+ return 0;
+}
+
+int main(void) {
+ int rc = 0;
+ if (omp_get_max_threads() != 8) {
+ fprintf(stderr, "FAIL: host omp_get_max_threads()=%d, expected 8\n",
+ omp_get_max_threads());
+ return 1;
+ }
+ rc |= check(0, __kmpc_get_device_env("OMP_NUM_THREADS", 0), "8");
+ rc |= check(1, __kmpc_get_device_env("OMP_NUM_THREADS", 1), "8");
+ rc |= check(2, __kmpc_get_device_env("OMP_NUM_THREADS", 2), "8");
+ return rc;
+}
diff --git a/openmp/runtime/test/env/omp_num_threads_dev_invalid.c b/openmp/runtime/test/env/omp_num_threads_dev_invalid.c
new file mode 100644
index 0000000000000..93dbb82d3eb18
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_dev_invalid.c
@@ -0,0 +1,22 @@
+// RUN: %libomp-compile
+// RUN: env KMP_WARNINGS=1 OMP_NUM_THREADS_DEV_abc=10 OMP_NUM_THREADS_ALL=4 \
+// RUN: %libomp-run 2>&1 | FileCheck %s
+//
+// Malformed `_DEV_<token>` is rejected with a warning, and a sibling
+// valid `_ALL` setting is unaffected.
+
+#include <omp.h>
+#include <stdio.h>
+
+int main(void) {
+ int max = omp_get_max_threads();
+ if (max != 4) {
+ fprintf(stderr, "FAIL: omp_get_max_threads()=%d, expected 4\n", max);
+ return 1;
+ }
+ printf("DONE\n");
+ return 0;
+}
+
+// CHECK: {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_DEV_abc.*}}
+// CHECK: DONE
diff --git a/openmp/runtime/test/env/omp_num_threads_dev_overflow.c b/openmp/runtime/test/env/omp_num_threads_dev_overflow.c
new file mode 100644
index 0000000000000..351231b12b4e8
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_dev_overflow.c
@@ -0,0 +1,46 @@
+// RUN: %libomp-compile
+// RUN: env KMP_WARNINGS=1 OMP_NUM_THREADS_DEV_2147483647=10 \
+// RUN: OMP_NUM_THREADS_DEV_99999999999=20 \
+// RUN: OMP_NUM_THREADS_DEV_000000000001=10 \
+// RUN: OMP_NUM_THREADS_DEV0=30 OMP_NUM_THREADS_ALLEY=40 \
+// RUN: OMP_NUM_THREADS_ALL=4 \
+// RUN: %libomp-run 2>&1 | FileCheck %s
+//
+// (a) `_DEV_<huge>` rejected with a warning;
+// (b) similar-looking names (`_DEV0`, `_ALLEY`) silently ignored, no warning;
+// (c) leading zeros are a valid device id (`000000000001` is device 1);
+// (d) sibling valid `_ALL=4` still applies.
+
+#include <omp.h>
+#include <stdio.h>
+#include <string.h>
+
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+int main(void) {
+ if (omp_get_max_threads() != 4) {
+ fprintf(stderr, "FAIL: host got %d, expected 4\n", omp_get_max_threads());
+ return 1;
+ }
+ const char *q = __kmpc_get_device_env("OMP_NUM_THREADS", 2147483646);
+ if (q == NULL || strcmp(q, "4") != 0) {
+ fprintf(stderr, "FAIL: large valid id expected '4', got %s\n",
+ q ? q : "(null)");
+ return 1;
+ }
+ q = __kmpc_get_device_env("OMP_NUM_THREADS", 1);
+ if (q == NULL || strcmp(q, "10") != 0) {
+ fprintf(stderr, "FAIL: padded device id expected '10', got %s\n",
+ q ? q : "(null)");
+ return 1;
+ }
+ printf("DONE\n");
+ return 0;
+}
+
+// CHECK: {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_DEV_2147483647.*}}
+// CHECK: {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_DEV_99999999999.*}}
+// CHECK-NOT: {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_DEV_000000000001.*}}
+// CHECK-NOT: {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_DEV0.*}}
+// CHECK-NOT: {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_ALLEY.*}}
+// CHECK: DONE
diff --git a/openmp/runtime/test/env/omp_num_threads_dev_resolve.c b/openmp/runtime/test/env/omp_num_threads_dev_resolve.c
new file mode 100644
index 0000000000000..072f1bf3f5c97
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_dev_resolve.c
@@ -0,0 +1,33 @@
+// RUN: %libomp-compile && \
+// RUN: env OMP_NUM_THREADS_ALL=8 OMP_NUM_THREADS_DEV=64 \
+// RUN: OMP_NUM_THREADS_DEV_0=128 %libomp-run
+//
+// OpenMP 6.0 non-host precedence: `_DEV_<d>` > `_DEV` > `_ALL` > default.
+
+#include <omp.h>
+#include <stdio.h>
+#include <string.h>
+
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+static int check(const char *got, int device_id, const char *expect) {
+ if (got == NULL || strcmp(got, expect) != 0) {
+ fprintf(stderr, "FAIL: device_id=%d resolved to '%s', expected '%s'\n",
+ device_id, got ? got : "(null)", expect);
+ return 1;
+ }
+ return 0;
+}
+
+int main(void) {
+ int rc = 0;
+ if (omp_get_max_threads() != 8) {
+ fprintf(stderr, "FAIL: host omp_get_max_threads()=%d, expected 8\n",
+ omp_get_max_threads());
+ return 1;
+ }
+ rc |= check(__kmpc_get_device_env("OMP_NUM_THREADS", 0), 0, "128"); // _DEV_0
+ rc |= check(__kmpc_get_device_env("OMP_NUM_THREADS", 1), 1, "64"); // _DEV
+ rc |= check(__kmpc_get_device_env("OMP_NUM_THREADS", 2), 2, "64"); // _DEV
+ return rc;
+}
diff --git a/openmp/runtime/test/env/omp_num_threads_host_overrides_all.c b/openmp/runtime/test/env/omp_num_threads_host_overrides_all.c
new file mode 100644
index 0000000000000..b6da422c4b0d0
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_host_overrides_all.c
@@ -0,0 +1,26 @@
+// RUN: %libomp-compile && \
+// RUN: env OMP_NUM_THREADS=4 OMP_NUM_THREADS_ALL=8 %libomp-run
+//
+// Host OMP_NUM_THREADS beats _ALL; sibling `_ALL` still applies on devices.
+
+#include <omp.h>
+#include <stdio.h>
+#include <string.h>
+
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+int main(void) {
+ int max = omp_get_max_threads();
+ if (max != 4) {
+ fprintf(stderr, "FAIL: omp_get_max_threads()=%d, expected 4\n", max);
+ return 1;
+ }
+ // Sibling `_ALL` must still resolve for non-host devices.
+ const char *dev0 = __kmpc_get_device_env("OMP_NUM_THREADS", 0);
+ if (dev0 == NULL || strcmp(dev0, "8") != 0) {
+ fprintf(stderr, "FAIL: dev 0 query expected '8' got '%s'\n",
+ dev0 ? dev0 : "(null)");
+ return 1;
+ }
+ return 0;
+}
diff --git a/openmp/runtime/test/env/omp_num_threads_host_vs_device.c b/openmp/runtime/test/env/omp_num_threads_host_vs_device.c
new file mode 100644
index 0000000000000..651ef1b833902
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_host_vs_device.c
@@ -0,0 +1,26 @@
+// RUN: %libomp-compile && \
+// RUN: env OMP_NUM_THREADS=4 OMP_NUM_THREADS_ALL=8 \
+// RUN: OMP_NUM_THREADS_DEV_0=128 %libomp-run
+//
+// Host ICV via omp_get_max_threads; devices via __kmpc_get_device_env.
+
+#include <omp.h>
+#include <stdio.h>
+#include <string.h>
+
+extern const char *__kmpc_get_device_env(const char *name, int device_id);
+
+int main(void) {
+ if (omp_get_max_threads() != 4)
+ return 1;
+
+ // Device 0 uses _DEV_0; device 1 falls through to _ALL.
+ const char *dev0 = __kmpc_get_device_env("OMP_NUM_THREADS", 0);
+ if (dev0 == NULL || strcmp(dev0, "128") != 0)
+ return 2;
+ const char *dev1 = __kmpc_get_device_env("OMP_NUM_THREADS", 1);
+ if (dev1 == NULL || strcmp(dev1, "8") != 0)
+ return 3;
+
+ return 0;
+}
diff --git a/openmp/runtime/test/env/omp_num_threads_warnings_off.c b/openmp/runtime/test/env/omp_num_threads_warnings_off.c
new file mode 100644
index 0000000000000..08b36a02124c4
--- /dev/null
+++ b/openmp/runtime/test/env/omp_num_threads_warnings_off.c
@@ -0,0 +1,23 @@
+// RUN: %libomp-compile
+// RUN: env KMP_WARNINGS=0 OMP_NUM_THREADS_DEV_abc=10 \
+// RUN: OMP_DEFAULT_DEVICE_ALL=2 OMP_NUM_THREADS_ALL=8 \
+// RUN: %libomp-run 2>&1 | FileCheck %s
+//
+// KMP_WARNINGS=0 silences device-scope warnings; OMP_NUM_THREADS_ALL=8 applies.
+
+#include <omp.h>
+#include <stdio.h>
+
+int main(void) {
+ int max = omp_get_max_threads();
+ if (max != 8) {
+ fprintf(stderr, "FAIL: omp_get_max_threads()=%d, expected 8\n", max);
+ return 1;
+ }
+ printf("DONE\n");
+ return 0;
+}
+
+// CHECK-NOT: {{^OMP: Warning #[0-9]+:.*OMP_NUM_THREADS_DEV_abc.*}}
+// CHECK-NOT: {{^OMP: Warning #[0-9]+:.*OMP_DEFAULT_DEVICE_ALL.*}}
+// CHECK: DONE
More information about the Openmp-commits
mailing list