diff --git a/libgomp/env.c b/libgomp/env.c index d730c483d7f..f305b14cf00 100644 --- a/libgomp/env.c +++ b/libgomp/env.c @@ -75,6 +75,8 @@ struct gomp_task_icv gomp_global_icv = { unsigned long gomp_max_active_levels_var = gomp_supported_active_levels; bool gomp_cancel_var = false; +enum gomp_target_offload_t gomp_target_offload_var + = GOMP_TARGET_OFFLOAD_DEFAULT; int gomp_max_task_priority_var = 0; #ifndef HAVE_SYNC_BUILTINS gomp_mutex_t gomp_managed_threads_lock; @@ -374,6 +376,48 @@ parse_unsigned_long_list (const char *name, unsigned long *p1stvalue, return false; } +static void +parse_target_offload (const char *name, enum gomp_target_offload_t *offload) +{ + const char *env; + bool found = false; + enum gomp_target_offload_t new_offload; + + env = getenv (name); + if (env == NULL) + return; + + while (isspace ((unsigned char) *env)) + ++env; + if (strncasecmp (env, "default", 7) == 0) + { + env += 7; + found = true; + new_offload = GOMP_TARGET_OFFLOAD_DEFAULT; + } + else if (strncasecmp (env, "mandatory", 9) == 0) + { + env += 9; + found = true; + new_offload = GOMP_TARGET_OFFLOAD_MANDATORY; + } + else if (strncasecmp (env, "disabled", 8) == 0) + { + env += 8; + found = true; + new_offload = GOMP_TARGET_OFFLOAD_DISABLED; + } + while (isspace ((unsigned char) *env)) + ++env; + if (found && *env == '\0') + { + *offload = new_offload; + return; + } + + gomp_error ("Invalid value for environment variable OMP_TARGET_OFFLOAD"); +} + /* Parse environment variable set to a boolean or list of omp_proc_bind_t enum values. Return true if one was present and it was successfully parsed. */ @@ -1334,6 +1378,21 @@ handle_omp_display_env (unsigned long stacksize, int wait_policy) } fputs ("'\n", stderr); + fputs (" OMP_TARGET_OFFLOAD = '", stderr); + switch (gomp_target_offload_var) + { + case GOMP_TARGET_OFFLOAD_DEFAULT: + fputs ("DEFAULT", stderr); + break; + case GOMP_TARGET_OFFLOAD_MANDATORY: + fputs ("MANDATORY", stderr); + break; + case GOMP_TARGET_OFFLOAD_DISABLED: + fputs ("DISABLED", stderr); + break; + } + fputs ("'\n", stderr); + if (verbose) { fputs (" GOMP_CPU_AFFINITY = ''\n", stderr); @@ -1366,6 +1425,7 @@ initialize_env (void) parse_boolean ("OMP_CANCELLATION", &gomp_cancel_var); parse_boolean ("OMP_DISPLAY_AFFINITY", &gomp_display_affinity_var); parse_int ("OMP_DEFAULT_DEVICE", &gomp_global_icv.default_device_var, true); + parse_target_offload ("OMP_TARGET_OFFLOAD", &gomp_target_offload_var); parse_int ("OMP_MAX_TASK_PRIORITY", &gomp_max_task_priority_var, true); parse_unsigned_long ("OMP_MAX_ACTIVE_LEVELS", &gomp_max_active_levels_var, true); diff --git a/libgomp/libgomp.h b/libgomp/libgomp.h index 9d26de25cd7..da7ac037dcd 100644 --- a/libgomp/libgomp.h +++ b/libgomp/libgomp.h @@ -434,6 +434,13 @@ struct gomp_task_icv struct target_mem_desc *target_data; }; +enum gomp_target_offload_t +{ + GOMP_TARGET_OFFLOAD_DEFAULT, + GOMP_TARGET_OFFLOAD_MANDATORY, + GOMP_TARGET_OFFLOAD_DISABLED +}; + #define gomp_supported_active_levels INT_MAX extern struct gomp_task_icv gomp_global_icv; @@ -442,6 +449,7 @@ extern gomp_mutex_t gomp_managed_threads_lock; #endif extern unsigned long gomp_max_active_levels_var; extern bool gomp_cancel_var; +extern enum gomp_target_offload_t gomp_target_offload_var; extern int gomp_max_task_priority_var; extern unsigned long long gomp_spin_count_var, gomp_throttled_spin_count_var; extern unsigned long gomp_available_cpus, gomp_managed_threads; diff --git a/libgomp/libgomp.texi b/libgomp/libgomp.texi index a888613a26b..7c6d5fd6efa 100644 --- a/libgomp/libgomp.texi +++ b/libgomp/libgomp.texi @@ -1381,6 +1381,7 @@ beginning with @env{GOMP_} are GNU extensions. * OMP_PLACES:: Specifies on which CPUs the theads should be placed * OMP_STACKSIZE:: Set default thread stack size * OMP_SCHEDULE:: How threads are scheduled +* OMP_TARGET_OFFLOAD:: Controls offloading behaviour * OMP_THREAD_LIMIT:: Set the maximum number of threads * OMP_WAIT_POLICY:: How waiting threads are handled * GOMP_CPU_AFFINITY:: Bind threads to specific CPUs @@ -1654,6 +1655,30 @@ dynamic scheduling and a chunk size of 1 is used. +@node OMP_TARGET_OFFLOAD +@section @env{OMP_TARGET_OFFLOAD} -- Controls offloading behaviour +@cindex Environment Variable +@cindex Implementation specific setting +@table @asis +@item @emph{Description}: +Specifies the behaviour with regard to offloading code to a device. This +variable can be set to one of three values - @code{MANDATORY}, @code{DISABLED} +or @code{DEFAULT}. + +If set to @code{MANDATORY}, the program will terminate with an error if +the offload device is not present or is not supported. If set to +@code{DISABLED}, then offloading is disabled and all code will run on the +host. If set to @code{DEFAULT}, the program will try offloading to the +device first, then fall back to running code on the host if it cannot. + +If undefined, then the program will behave as if @code{DEFAULT} was set. + +@item @emph{Reference}: +@uref{https://www.openmp.org, OpenMP specification v5.0}, Section 6.17 +@end table + + + @node OMP_THREAD_LIMIT @section @env{OMP_THREAD_LIMIT} -- Set the maximum number of threads @cindex Environment Variable diff --git a/libgomp/target.c b/libgomp/target.c index ab7ac9ba8d2..bb643b32c59 100644 --- a/libgomp/target.c +++ b/libgomp/target.c @@ -116,7 +116,14 @@ resolve_device (int device_id) } if (device_id < 0 || device_id >= gomp_get_num_devices ()) - return NULL; + { + if (gomp_target_offload_var == GOMP_TARGET_OFFLOAD_MANDATORY + && device_id != GOMP_DEVICE_HOST_FALLBACK) + gomp_fatal ("OMP_TARGET_OFFLOAD is set to MANDATORY, " + "but device not found"); + + return NULL; + } gomp_mutex_lock (&devices[device_id].lock); if (devices[device_id].state == GOMP_DEVICE_UNINITIALIZED) @@ -124,6 +131,12 @@ resolve_device (int device_id) else if (devices[device_id].state == GOMP_DEVICE_FINALIZED) { gomp_mutex_unlock (&devices[device_id].lock); + + if (gomp_target_offload_var == GOMP_TARGET_OFFLOAD_MANDATORY + && device_id != GOMP_DEVICE_HOST_FALLBACK) + gomp_fatal ("OMP_TARGET_OFFLOAD is set to MANDATORY, " + "but device is finalized"); + return NULL; } gomp_mutex_unlock (&devices[device_id].lock); @@ -1997,9 +2010,16 @@ gomp_unload_device (struct gomp_device_descr *devicep) /* Host fallback for GOMP_target{,_ext} routines. */ static void -gomp_target_fallback (void (*fn) (void *), void **hostaddrs) +gomp_target_fallback (void (*fn) (void *), void **hostaddrs, + struct gomp_device_descr *devicep) { struct gomp_thread old_thr, *thr = gomp_thread (); + + if (gomp_target_offload_var == GOMP_TARGET_OFFLOAD_MANDATORY + && devicep != NULL) + gomp_fatal ("OMP_TARGET_OFFLOAD is set to MANDATORY, but device cannot " + "be used for offloading"); + old_thr = *thr; memset (thr, '\0', sizeof (*thr)); if (gomp_places_list) @@ -2107,7 +2127,7 @@ GOMP_target (int device, void (*fn) (void *), const void *unused, /* All shared memory devices should use the GOMP_target_ext function. */ || devicep->capabilities & GOMP_OFFLOAD_CAP_SHARED_MEM || !(fn_addr = gomp_get_target_fn_addr (devicep, fn))) - return gomp_target_fallback (fn, hostaddrs); + return gomp_target_fallback (fn, hostaddrs, devicep); struct target_mem_desc *tgt_vars = gomp_map_vars (devicep, mapnum, hostaddrs, NULL, sizes, kinds, false, @@ -2243,7 +2263,7 @@ GOMP_target_ext (int device, void (*fn) (void *), size_t mapnum, tgt_align, tgt_size); } } - gomp_target_fallback (fn, hostaddrs); + gomp_target_fallback (fn, hostaddrs, devicep); return; } @@ -2276,9 +2296,15 @@ GOMP_target_ext (int device, void (*fn) (void *), size_t mapnum, /* Host fallback for GOMP_target_data{,_ext} routines. */ static void -gomp_target_data_fallback (void) +gomp_target_data_fallback (struct gomp_device_descr *devicep) { struct gomp_task_icv *icv = gomp_icv (false); + + if (gomp_target_offload_var == GOMP_TARGET_OFFLOAD_MANDATORY + && devicep != NULL) + gomp_fatal ("OMP_TARGET_OFFLOAD is set to MANDATORY, but device cannot " + "be used for offloading"); + if (icv->target_data) { /* Even when doing a host fallback, if there are any active @@ -2302,7 +2328,7 @@ GOMP_target_data (int device, const void *unused, size_t mapnum, if (devicep == NULL || !(devicep->capabilities & GOMP_OFFLOAD_CAP_OPENMP_400) || (devicep->capabilities & GOMP_OFFLOAD_CAP_SHARED_MEM)) - return gomp_target_data_fallback (); + return gomp_target_data_fallback (devicep); struct target_mem_desc *tgt = gomp_map_vars (devicep, mapnum, hostaddrs, NULL, sizes, kinds, false, @@ -2321,7 +2347,7 @@ GOMP_target_data_ext (int device, size_t mapnum, void **hostaddrs, if (devicep == NULL || !(devicep->capabilities & GOMP_OFFLOAD_CAP_OPENMP_400) || devicep->capabilities & GOMP_OFFLOAD_CAP_SHARED_MEM) - return gomp_target_data_fallback (); + return gomp_target_data_fallback (devicep); struct target_mem_desc *tgt = gomp_map_vars (devicep, mapnum, hostaddrs, NULL, sizes, kinds, true, @@ -2617,7 +2643,7 @@ gomp_target_task_fn (void *data) || (devicep->can_run_func && !devicep->can_run_func (fn_addr))) { ttask->state = GOMP_TARGET_TASK_FALLBACK; - gomp_target_fallback (ttask->fn, ttask->hostaddrs); + gomp_target_fallback (ttask->fn, ttask->hostaddrs, devicep); return false; } @@ -3258,6 +3284,9 @@ gomp_target_init (void) num_devices = 0; devices = NULL; + if (gomp_target_offload_var == GOMP_TARGET_OFFLOAD_DISABLED) + return; + cur = OFFLOAD_PLUGINS; if (*cur) do