| /* Copyright (C) 2020-2021 Free Software Foundation, Inc. | |
| Contributed by Jakub Jelinek <jakub@redhat.com>. | |
| This file is part of the GNU Offloading and Multi Processing Library | |
| (libgomp). | |
| Libgomp is free software; you can redistribute it and/or modify it | |
| under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 3, or (at your option) | |
| any later version. | |
| Libgomp is distributed in the hope that it will be useful, but WITHOUT ANY | |
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
| FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
| more details. | |
| Under Section 7 of GPL version 3, you are granted additional | |
| permissions described in the GCC Runtime Library Exception, version | |
| 3.1, as published by the Free Software Foundation. | |
| You should have received a copy of the GNU General Public License and | |
| a copy of the GCC Runtime Library Exception along with this program; | |
| see the files COPYING3 and COPYING.RUNTIME respectively. If not, see | |
| <http://www.gnu.org/licenses/>. */ | |
| /* This file contains wrappers for the system allocation routines. Most | |
| places in the OpenMP API do not make any provision for failure, so in | |
| general we cannot allow memory allocation to fail. */ | |
| #define _GNU_SOURCE | |
| #include "libgomp.h" | |
| #include <stdlib.h> | |
| #define omp_max_predefined_alloc omp_thread_mem_alloc | |
| struct omp_allocator_data | |
| { | |
| omp_memspace_handle_t memspace; | |
| omp_uintptr_t alignment; | |
| omp_uintptr_t pool_size; | |
| omp_uintptr_t used_pool_size; | |
| omp_allocator_handle_t fb_data; | |
| unsigned int sync_hint : 8; | |
| unsigned int access : 8; | |
| unsigned int fallback : 8; | |
| unsigned int pinned : 1; | |
| unsigned int partition : 7; | |
| #ifndef HAVE_SYNC_BUILTINS | |
| gomp_mutex_t lock; | |
| #endif | |
| }; | |
| struct omp_mem_header | |
| { | |
| void *ptr; | |
| size_t size; | |
| omp_allocator_handle_t allocator; | |
| void *pad; | |
| }; | |
| omp_allocator_handle_t | |
| omp_init_allocator (omp_memspace_handle_t memspace, int ntraits, | |
| const omp_alloctrait_t traits[]) | |
| { | |
| struct omp_allocator_data data | |
| = { memspace, 1, ~(uintptr_t) 0, 0, 0, omp_atv_contended, omp_atv_all, | |
| omp_atv_default_mem_fb, omp_atv_false, omp_atv_environment }; | |
| struct omp_allocator_data *ret; | |
| int i; | |
| if (memspace > omp_low_lat_mem_space) | |
| return omp_null_allocator; | |
| for (i = 0; i < ntraits; i++) | |
| switch (traits[i].key) | |
| { | |
| case omp_atk_sync_hint: | |
| switch (traits[i].value) | |
| { | |
| case omp_atv_default: | |
| data.sync_hint = omp_atv_contended; | |
| break; | |
| case omp_atv_contended: | |
| case omp_atv_uncontended: | |
| case omp_atv_sequential: | |
| case omp_atv_private: | |
| data.sync_hint = traits[i].value; | |
| break; | |
| default: | |
| return omp_null_allocator; | |
| } | |
| break; | |
| case omp_atk_alignment: | |
| if (traits[i].value == omp_atv_default) | |
| { | |
| data.alignment = 1; | |
| break; | |
| } | |
| if ((traits[i].value & (traits[i].value - 1)) != 0 | |
| || !traits[i].value) | |
| return omp_null_allocator; | |
| data.alignment = traits[i].value; | |
| break; | |
| case omp_atk_access: | |
| switch (traits[i].value) | |
| { | |
| case omp_atv_default: | |
| data.access = omp_atv_all; | |
| break; | |
| case omp_atv_all: | |
| case omp_atv_cgroup: | |
| case omp_atv_pteam: | |
| case omp_atv_thread: | |
| data.access = traits[i].value; | |
| break; | |
| default: | |
| return omp_null_allocator; | |
| } | |
| break; | |
| case omp_atk_pool_size: | |
| if (traits[i].value == omp_atv_default) | |
| data.pool_size = ~(uintptr_t) 0; | |
| else | |
| data.pool_size = traits[i].value; | |
| break; | |
| case omp_atk_fallback: | |
| switch (traits[i].value) | |
| { | |
| case omp_atv_default: | |
| data.fallback = omp_atv_default_mem_fb; | |
| break; | |
| case omp_atv_default_mem_fb: | |
| case omp_atv_null_fb: | |
| case omp_atv_abort_fb: | |
| case omp_atv_allocator_fb: | |
| data.fallback = traits[i].value; | |
| break; | |
| default: | |
| return omp_null_allocator; | |
| } | |
| break; | |
| case omp_atk_fb_data: | |
| data.fb_data = traits[i].value; | |
| break; | |
| case omp_atk_pinned: | |
| switch (traits[i].value) | |
| { | |
| case omp_atv_default: | |
| case omp_atv_false: | |
| data.pinned = omp_atv_false; | |
| break; | |
| case omp_atv_true: | |
| data.pinned = omp_atv_true; | |
| break; | |
| default: | |
| return omp_null_allocator; | |
| } | |
| break; | |
| case omp_atk_partition: | |
| switch (traits[i].value) | |
| { | |
| case omp_atv_default: | |
| data.partition = omp_atv_environment; | |
| break; | |
| case omp_atv_environment: | |
| case omp_atv_nearest: | |
| case omp_atv_blocked: | |
| case omp_atv_interleaved: | |
| data.partition = traits[i].value; | |
| break; | |
| default: | |
| return omp_null_allocator; | |
| } | |
| break; | |
| default: | |
| return omp_null_allocator; | |
| } | |
| if (data.alignment < sizeof (void *)) | |
| data.alignment = sizeof (void *); | |
| /* No support for these so far (for hbw will use memkind). */ | |
| if (data.pinned || data.memspace == omp_high_bw_mem_space) | |
| return omp_null_allocator; | |
| ret = gomp_malloc (sizeof (struct omp_allocator_data)); | |
| *ret = data; | |
| #ifndef HAVE_SYNC_BUILTINS | |
| gomp_mutex_init (&ret->lock); | |
| #endif | |
| return (omp_allocator_handle_t) ret; | |
| } | |
| void | |
| omp_destroy_allocator (omp_allocator_handle_t allocator) | |
| { | |
| if (allocator != omp_null_allocator) | |
| { | |
| #ifndef HAVE_SYNC_BUILTINS | |
| gomp_mutex_destroy (&((struct omp_allocator_data *) allocator)->lock); | |
| #endif | |
| free ((void *) allocator); | |
| } | |
| } | |
| ialias (omp_init_allocator) | |
| ialias (omp_destroy_allocator) | |
| static void * | |
| omp_aligned_alloc (size_t alignment, size_t size, | |
| omp_allocator_handle_t allocator) | |
| { | |
| struct omp_allocator_data *allocator_data; | |
| size_t new_size; | |
| void *ptr, *ret; | |
| if (__builtin_expect (size == 0, 0)) | |
| return NULL; | |
| retry: | |
| if (allocator == omp_null_allocator) | |
| { | |
| struct gomp_thread *thr = gomp_thread (); | |
| if (thr->ts.def_allocator == omp_null_allocator) | |
| thr->ts.def_allocator = gomp_def_allocator; | |
| allocator = (omp_allocator_handle_t) thr->ts.def_allocator; | |
| } | |
| if (allocator > omp_max_predefined_alloc) | |
| { | |
| allocator_data = (struct omp_allocator_data *) allocator; | |
| if (alignment < allocator_data->alignment) | |
| alignment = allocator_data->alignment; | |
| } | |
| else | |
| { | |
| allocator_data = NULL; | |
| if (alignment < sizeof (void *)) | |
| alignment = sizeof (void *); | |
| } | |
| new_size = sizeof (struct omp_mem_header); | |
| if (alignment > sizeof (void *)) | |
| new_size += alignment - sizeof (void *); | |
| if (__builtin_add_overflow (size, new_size, &new_size)) | |
| goto fail; | |
| if (__builtin_expect (allocator_data | |
| && allocator_data->pool_size < ~(uintptr_t) 0, 0)) | |
| { | |
| uintptr_t used_pool_size; | |
| if (new_size > allocator_data->pool_size) | |
| goto fail; | |
| #ifdef HAVE_SYNC_BUILTINS | |
| used_pool_size = __atomic_load_n (&allocator_data->used_pool_size, | |
| MEMMODEL_RELAXED); | |
| do | |
| { | |
| uintptr_t new_pool_size; | |
| if (__builtin_add_overflow (used_pool_size, new_size, | |
| &new_pool_size) | |
| || new_pool_size > allocator_data->pool_size) | |
| goto fail; | |
| if (__atomic_compare_exchange_n (&allocator_data->used_pool_size, | |
| &used_pool_size, new_pool_size, | |
| true, MEMMODEL_RELAXED, | |
| MEMMODEL_RELAXED)) | |
| break; | |
| } | |
| while (1); | |
| #else | |
| gomp_mutex_lock (&allocator_data->lock); | |
| if (__builtin_add_overflow (allocator_data->used_pool_size, new_size, | |
| &used_pool_size) | |
| || used_pool_size > allocator_data->pool_size) | |
| { | |
| gomp_mutex_unlock (&allocator_data->lock); | |
| goto fail; | |
| } | |
| allocator_data->used_pool_size = used_pool_size; | |
| gomp_mutex_unlock (&allocator_data->lock); | |
| #endif | |
| ptr = malloc (new_size); | |
| if (ptr == NULL) | |
| { | |
| #ifdef HAVE_SYNC_BUILTINS | |
| __atomic_add_fetch (&allocator_data->used_pool_size, -new_size, | |
| MEMMODEL_RELAXED); | |
| #else | |
| gomp_mutex_lock (&allocator_data->lock); | |
| allocator_data->used_pool_size -= new_size; | |
| gomp_mutex_unlock (&allocator_data->lock); | |
| #endif | |
| goto fail; | |
| } | |
| } | |
| else | |
| { | |
| ptr = malloc (new_size); | |
| if (ptr == NULL) | |
| goto fail; | |
| } | |
| if (alignment > sizeof (void *)) | |
| ret = (void *) (((uintptr_t) ptr | |
| + sizeof (struct omp_mem_header) | |
| + alignment - sizeof (void *)) & ~(alignment - 1)); | |
| else | |
| ret = (char *) ptr + sizeof (struct omp_mem_header); | |
| ((struct omp_mem_header *) ret)[-1].ptr = ptr; | |
| ((struct omp_mem_header *) ret)[-1].size = new_size; | |
| ((struct omp_mem_header *) ret)[-1].allocator = allocator; | |
| return ret; | |
| fail: | |
| if (allocator_data) | |
| { | |
| switch (allocator_data->fallback) | |
| { | |
| case omp_atv_default_mem_fb: | |
| if (alignment > sizeof (void *) | |
| || (allocator_data | |
| && allocator_data->pool_size < ~(uintptr_t) 0)) | |
| { | |
| allocator = omp_default_mem_alloc; | |
| goto retry; | |
| } | |
| /* Otherwise, we've already performed default mem allocation | |
| and if that failed, it won't succeed again (unless it was | |
| intermitent. Return NULL then, as that is the fallback. */ | |
| break; | |
| case omp_atv_null_fb: | |
| break; | |
| default: | |
| case omp_atv_abort_fb: | |
| gomp_fatal ("Out of memory allocating %lu bytes", | |
| (unsigned long) size); | |
| case omp_atv_allocator_fb: | |
| allocator = allocator_data->fb_data; | |
| goto retry; | |
| } | |
| } | |
| return NULL; | |
| } | |
| void * | |
| omp_alloc (size_t size, omp_allocator_handle_t allocator) | |
| { | |
| return omp_aligned_alloc (1, size, allocator); | |
| } | |
| /* Like omp_aligned_alloc, but apply on top of that: | |
| "For allocations that arise from this ... the null_fb value of the | |
| fallback allocator trait behaves as if the abort_fb had been specified." */ | |
| void * | |
| GOMP_alloc (size_t alignment, size_t size, uintptr_t allocator) | |
| { | |
| void *ret = omp_aligned_alloc (alignment, size, | |
| (omp_allocator_handle_t) allocator); | |
| if (__builtin_expect (ret == NULL, 0) && size) | |
| gomp_fatal ("Out of memory allocating %lu bytes", | |
| (unsigned long) size); | |
| return ret; | |
| } | |
| void | |
| omp_free (void *ptr, omp_allocator_handle_t allocator) | |
| { | |
| struct omp_mem_header *data; | |
| if (ptr == NULL) | |
| return; | |
| (void) allocator; | |
| data = &((struct omp_mem_header *) ptr)[-1]; | |
| if (data->allocator > omp_max_predefined_alloc) | |
| { | |
| struct omp_allocator_data *allocator_data | |
| = (struct omp_allocator_data *) (data->allocator); | |
| if (allocator_data->pool_size < ~(uintptr_t) 0) | |
| { | |
| #ifdef HAVE_SYNC_BUILTINS | |
| __atomic_add_fetch (&allocator_data->used_pool_size, -data->size, | |
| MEMMODEL_RELAXED); | |
| #else | |
| gomp_mutex_lock (&allocator_data->lock); | |
| allocator_data->used_pool_size -= data->size; | |
| gomp_mutex_unlock (&allocator_data->lock); | |
| #endif | |
| } | |
| } | |
| free (data->ptr); | |
| } | |
| ialias (omp_free) | |
| void | |
| GOMP_free (void *ptr, uintptr_t allocator) | |
| { | |
| return omp_free (ptr, (omp_allocator_handle_t) allocator); | |
| } |