| //===----------------------------------------------------------------------===// |
| // |
| // 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; |
| } |