|  | /* Sparse Arrays for Objective C dispatch tables | 
|  | Copyright (C) 1993-2025 Free Software Foundation, Inc. | 
|  |  | 
|  | This file is part of GCC. | 
|  |  | 
|  | GCC 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. | 
|  |  | 
|  | GCC 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/>.  */ | 
|  |  | 
|  | #include "objc-private/common.h" | 
|  | #include "objc-private/sarray.h" | 
|  | #include "objc/runtime.h" /* For objc_malloc */ | 
|  | #include "objc/thr.h"     /* For objc_mutex_lock */ | 
|  | #include "objc-private/module-abi-8.h" | 
|  | #include "objc-private/runtime.h" | 
|  | #include <stdio.h> | 
|  | #include <string.h> /* For memset */ | 
|  | #include <assert.h> /* For assert */ | 
|  |  | 
|  | int nbuckets = 0;					/* !T:MUTEX */ | 
|  | int nindices = 0;					/* !T:MUTEX */ | 
|  | int narrays = 0;					/* !T:MUTEX */ | 
|  | int idxsize = 0;					/* !T:MUTEX */ | 
|  |  | 
|  | static void *first_free_data = NULL;			/* !T:MUTEX */ | 
|  |  | 
|  | #ifdef OBJC_SPARSE2 | 
|  | const char *__objc_sparse2_id = "2 level sparse indices"; | 
|  | #endif | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  | const char *__objc_sparse3_id = "3 level sparse indices"; | 
|  | #endif | 
|  |  | 
|  | /* This function removes any structures left over from free operations | 
|  | that were not safe in a multi-threaded environment. */ | 
|  | void | 
|  | sarray_remove_garbage (void) | 
|  | { | 
|  | void **vp; | 
|  | void *np; | 
|  |  | 
|  | objc_mutex_lock (__objc_runtime_mutex); | 
|  |  | 
|  | vp = first_free_data; | 
|  | first_free_data = NULL; | 
|  |  | 
|  | while (vp) | 
|  | { | 
|  | np = *vp; | 
|  | objc_free (vp); | 
|  | vp = np; | 
|  | } | 
|  |  | 
|  | objc_mutex_unlock (__objc_runtime_mutex); | 
|  | } | 
|  |  | 
|  | /* Free a block of dynamically allocated memory.  If we are in | 
|  | multi-threaded mode, it is ok to free it.  If not, we add it to the | 
|  | garbage heap to be freed later. */ | 
|  | static void | 
|  | sarray_free_garbage (void *vp) | 
|  | { | 
|  | objc_mutex_lock (__objc_runtime_mutex); | 
|  |  | 
|  | if (__objc_runtime_threads_alive == 1) | 
|  | { | 
|  | objc_free (vp); | 
|  | if (first_free_data) | 
|  | sarray_remove_garbage (); | 
|  | } | 
|  | else | 
|  | { | 
|  | *(void **)vp = first_free_data; | 
|  | first_free_data = vp; | 
|  | } | 
|  |  | 
|  | objc_mutex_unlock (__objc_runtime_mutex); | 
|  | } | 
|  |  | 
|  | /* sarray_at_put copies data in such a way as to be thread reader | 
|  | safe.  */ | 
|  | void | 
|  | sarray_at_put (struct sarray *array, sidx index, void *element) | 
|  | { | 
|  | #ifdef OBJC_SPARSE3 | 
|  | struct sindex **the_index; | 
|  | struct sindex *new_index; | 
|  | #endif | 
|  | struct sbucket **the_bucket; | 
|  | struct sbucket *new_bucket; | 
|  | #ifdef OBJC_SPARSE3 | 
|  | size_t ioffset; | 
|  | #endif | 
|  | size_t boffset; | 
|  | size_t eoffset; | 
|  | #ifdef PRECOMPUTE_SELECTORS | 
|  | union sofftype xx; | 
|  | xx.idx = index; | 
|  | #ifdef OBJC_SPARSE3 | 
|  | ioffset = xx.off.ioffset; | 
|  | #endif | 
|  | boffset = xx.off.boffset; | 
|  | eoffset = xx.off.eoffset; | 
|  | #else /* not PRECOMPUTE_SELECTORS */ | 
|  | #ifdef OBJC_SPARSE3 | 
|  | ioffset = index/INDEX_CAPACITY; | 
|  | boffset = (index/BUCKET_SIZE)%INDEX_SIZE; | 
|  | eoffset = index%BUCKET_SIZE; | 
|  | #else | 
|  | boffset = index/BUCKET_SIZE; | 
|  | eoffset = index%BUCKET_SIZE; | 
|  | #endif | 
|  | #endif /* not PRECOMPUTE_SELECTORS */ | 
|  |  | 
|  | assert (soffset_decode (index) < array->capacity); /* Range check */ | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  | the_index = &(array->indices[ioffset]); | 
|  | the_bucket = &((*the_index)->buckets[boffset]); | 
|  | #else | 
|  | the_bucket = &(array->buckets[boffset]); | 
|  | #endif | 
|  |  | 
|  | if ((*the_bucket)->elems[eoffset] == element) | 
|  | return;		/* Great! we just avoided a lazy copy.  */ | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  |  | 
|  | /* First, perform lazy copy/allocation of index if needed.  */ | 
|  |  | 
|  | if ((*the_index) == array->empty_index) | 
|  | { | 
|  | /* The index was previously empty, allocate a new.  */ | 
|  | new_index = (struct sindex *) objc_malloc (sizeof (struct sindex)); | 
|  | memcpy (new_index, array->empty_index, sizeof (struct sindex)); | 
|  | new_index->version.version = array->version.version; | 
|  | *the_index = new_index;                     /* Prepared for install. */ | 
|  | the_bucket = &((*the_index)->buckets[boffset]); | 
|  |  | 
|  | nindices += 1; | 
|  | } | 
|  | else if ((*the_index)->version.version != array->version.version) | 
|  | { | 
|  | /* This index must be lazy copied.  */ | 
|  | struct sindex *old_index = *the_index; | 
|  | new_index = (struct sindex *) objc_malloc (sizeof (struct sindex)); | 
|  | memcpy (new_index, old_index, sizeof (struct sindex)); | 
|  | new_index->version.version = array->version.version; | 
|  | *the_index = new_index;                     /* Prepared for install. */ | 
|  | the_bucket = &((*the_index)->buckets[boffset]); | 
|  |  | 
|  | nindices += 1; | 
|  | } | 
|  |  | 
|  | #endif /* OBJC_SPARSE3 */ | 
|  |  | 
|  | /* Next, perform lazy allocation/copy of the bucket if needed.  */ | 
|  | if ((*the_bucket) == array->empty_bucket) | 
|  | { | 
|  | /* The bucket was previously empty (or something like that), | 
|  | allocate a new.  This is the effect of `lazy' allocation.  */ | 
|  | new_bucket = (struct sbucket *) objc_malloc (sizeof (struct sbucket)); | 
|  | memcpy ((void *) new_bucket, (const void *) array->empty_bucket, | 
|  | sizeof (struct sbucket)); | 
|  | new_bucket->version.version = array->version.version; | 
|  | *the_bucket = new_bucket;                   /* Prepared for install. */ | 
|  |  | 
|  | nbuckets += 1; | 
|  |  | 
|  | } | 
|  | else if ((*the_bucket)->version.version != array->version.version) | 
|  | { | 
|  | /* Perform lazy copy.  */ | 
|  | struct sbucket *old_bucket = *the_bucket; | 
|  | new_bucket = (struct sbucket *) objc_malloc (sizeof (struct sbucket)); | 
|  | memcpy (new_bucket, old_bucket, sizeof (struct sbucket)); | 
|  | new_bucket->version.version = array->version.version; | 
|  | *the_bucket = new_bucket;                   /* Prepared for install. */ | 
|  |  | 
|  | nbuckets += 1; | 
|  | } | 
|  | (*the_bucket)->elems[eoffset] = element; | 
|  | } | 
|  |  | 
|  | void | 
|  | sarray_at_put_safe (struct sarray *array, sidx index, void *element) | 
|  | { | 
|  | if (soffset_decode (index) >= array->capacity) | 
|  | sarray_realloc (array, soffset_decode (index) + 1); | 
|  | sarray_at_put (array, index, element); | 
|  | } | 
|  |  | 
|  | struct sarray * | 
|  | sarray_new (int size, void *default_element) | 
|  | { | 
|  | struct sarray *arr; | 
|  | #ifdef OBJC_SPARSE3 | 
|  | size_t num_indices = ((size - 1)/(INDEX_CAPACITY)) + 1; | 
|  | struct sindex **new_indices; | 
|  | #else /* OBJC_SPARSE2 */ | 
|  | size_t num_indices = ((size - 1)/BUCKET_SIZE) + 1; | 
|  | struct sbucket **new_buckets; | 
|  | #endif | 
|  | size_t counter; | 
|  |  | 
|  | assert (size > 0); | 
|  |  | 
|  | /* Allocate core array.  */ | 
|  | arr = (struct sarray *) objc_malloc (sizeof (struct sarray)); | 
|  | arr->version.version = 0; | 
|  |  | 
|  | /* Initialize members.  */ | 
|  | #ifdef OBJC_SPARSE3 | 
|  | arr->capacity = num_indices*INDEX_CAPACITY; | 
|  | new_indices = (struct sindex **) | 
|  | objc_malloc (sizeof (struct sindex *) * num_indices); | 
|  |  | 
|  | arr->empty_index = (struct sindex *) objc_malloc (sizeof (struct sindex)); | 
|  | arr->empty_index->version.version = 0; | 
|  |  | 
|  | narrays  += 1; | 
|  | idxsize  += num_indices; | 
|  | nindices += 1; | 
|  |  | 
|  | #else /* OBJC_SPARSE2 */ | 
|  | arr->capacity = num_indices*BUCKET_SIZE; | 
|  | new_buckets = (struct sbucket **) | 
|  | objc_malloc (sizeof (struct sbucket *) * num_indices); | 
|  |  | 
|  | narrays  += 1; | 
|  | idxsize  += num_indices; | 
|  |  | 
|  | #endif | 
|  |  | 
|  | arr->empty_bucket = (struct sbucket *) objc_malloc (sizeof (struct sbucket)); | 
|  | arr->empty_bucket->version.version = 0; | 
|  |  | 
|  | nbuckets += 1; | 
|  |  | 
|  | arr->ref_count = 1; | 
|  | arr->is_copy_of = (struct sarray *) 0; | 
|  |  | 
|  | for (counter = 0; counter < BUCKET_SIZE; counter++) | 
|  | arr->empty_bucket->elems[counter] = default_element; | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  | for (counter = 0; counter < INDEX_SIZE; counter++) | 
|  | arr->empty_index->buckets[counter] = arr->empty_bucket; | 
|  |  | 
|  | for (counter = 0; counter < num_indices; counter++) | 
|  | new_indices[counter] = arr->empty_index; | 
|  |  | 
|  | #else /* OBJC_SPARSE2 */ | 
|  |  | 
|  | for (counter = 0; counter < num_indices; counter++) | 
|  | new_buckets[counter] = arr->empty_bucket; | 
|  |  | 
|  | #endif | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  | arr->indices = new_indices; | 
|  | #else /* OBJC_SPARSE2 */ | 
|  | arr->buckets = new_buckets; | 
|  | #endif | 
|  |  | 
|  | return arr; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Reallocate the sparse array to hold `newsize' entries Note: We | 
|  | really allocate and then free.  We have to do this to ensure that | 
|  | any concurrent readers notice the update.  */ | 
|  | void | 
|  | sarray_realloc (struct sarray *array, int newsize) | 
|  | { | 
|  | #ifdef OBJC_SPARSE3 | 
|  | size_t old_max_index = (array->capacity - 1)/INDEX_CAPACITY; | 
|  | size_t new_max_index = ((newsize - 1)/INDEX_CAPACITY); | 
|  | size_t rounded_size = (new_max_index + 1) * INDEX_CAPACITY; | 
|  |  | 
|  | struct sindex **new_indices; | 
|  | struct sindex **old_indices; | 
|  |  | 
|  | #else /* OBJC_SPARSE2 */ | 
|  | size_t old_max_index = (array->capacity - 1)/BUCKET_SIZE; | 
|  | size_t new_max_index = ((newsize - 1)/BUCKET_SIZE); | 
|  | size_t rounded_size = (new_max_index + 1) * BUCKET_SIZE; | 
|  |  | 
|  | struct sbucket **new_buckets; | 
|  | struct sbucket **old_buckets; | 
|  |  | 
|  | #endif | 
|  |  | 
|  | size_t counter; | 
|  |  | 
|  | assert (newsize > 0); | 
|  |  | 
|  | /* The size is the same, just ignore the request.  */ | 
|  | if (rounded_size <= array->capacity) | 
|  | return; | 
|  |  | 
|  | assert (array->ref_count == 1);	/* stop if lazy copied... */ | 
|  |  | 
|  | /* We are asked to extend the array -- allocate new bucket table, | 
|  | and insert empty_bucket in newly allocated places.  */ | 
|  | if (rounded_size > array->capacity) | 
|  | { | 
|  | #ifdef OBJC_SPARSE3 | 
|  | new_max_index += 4; | 
|  | rounded_size = (new_max_index + 1) * INDEX_CAPACITY; | 
|  | #else /* OBJC_SPARSE2 */ | 
|  | new_max_index += 4; | 
|  | rounded_size = (new_max_index + 1) * BUCKET_SIZE; | 
|  | #endif | 
|  |  | 
|  | /* Update capacity.  */ | 
|  | array->capacity = rounded_size; | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  | /* Alloc to force re-read by any concurrent readers.  */ | 
|  | old_indices = array->indices; | 
|  | new_indices = (struct sindex **) | 
|  | objc_malloc ((new_max_index + 1) * sizeof (struct sindex *)); | 
|  | #else /* OBJC_SPARSE2 */ | 
|  | old_buckets = array->buckets; | 
|  | new_buckets = (struct sbucket **) | 
|  | objc_malloc ((new_max_index + 1) * sizeof (struct sbucket *)); | 
|  | #endif | 
|  |  | 
|  | /* Copy buckets below old_max_index (they are still valid).  */ | 
|  | for (counter = 0; counter <= old_max_index; counter++ ) | 
|  | { | 
|  | #ifdef OBJC_SPARSE3 | 
|  | new_indices[counter] = old_indices[counter]; | 
|  | #else /* OBJC_SPARSE2 */ | 
|  | new_buckets[counter] = old_buckets[counter]; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  | /* Reset entries above old_max_index to empty_bucket.  */ | 
|  | for (counter = old_max_index + 1; counter <= new_max_index; counter++) | 
|  | new_indices[counter] = array->empty_index; | 
|  | #else /* OBJC_SPARSE2 */ | 
|  | /* Reset entries above old_max_index to empty_bucket.  */ | 
|  | for (counter = old_max_index + 1; counter <= new_max_index; counter++) | 
|  | new_buckets[counter] = array->empty_bucket; | 
|  | #endif | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  | /* Install the new indices.  */ | 
|  | array->indices = new_indices; | 
|  | #else /* OBJC_SPARSE2 */ | 
|  | array->buckets = new_buckets; | 
|  | #endif | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  | /* Free the old indices.  */ | 
|  | sarray_free_garbage (old_indices); | 
|  | #else /* OBJC_SPARSE2 */ | 
|  | sarray_free_garbage (old_buckets); | 
|  | #endif | 
|  |  | 
|  | idxsize += (new_max_index-old_max_index); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Free a sparse array allocated with sarray_new */ | 
|  | void | 
|  | sarray_free (struct sarray *array) { | 
|  | #ifdef OBJC_SPARSE3 | 
|  | size_t old_max_index = (array->capacity - 1)/INDEX_CAPACITY; | 
|  | struct sindex **old_indices; | 
|  | #else | 
|  | size_t old_max_index = (array->capacity - 1)/BUCKET_SIZE; | 
|  | struct sbucket **old_buckets; | 
|  | #endif | 
|  | size_t counter = 0; | 
|  |  | 
|  | assert (array->ref_count != 0);	/* Freed multiple times!!! */ | 
|  |  | 
|  | if (--(array->ref_count) != 0)	/* There exists copies of me */ | 
|  | return; | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  | old_indices = array->indices; | 
|  | #else | 
|  | old_buckets = array->buckets; | 
|  | #endif | 
|  |  | 
|  | /* Free all entries that do not point to empty_bucket.  */ | 
|  | for (counter = 0; counter <= old_max_index; counter++ ) | 
|  | { | 
|  | #ifdef OBJC_SPARSE3 | 
|  | struct sindex *idx = old_indices[counter]; | 
|  | if ((idx != array->empty_index) | 
|  | && (idx->version.version == array->version.version)) | 
|  | { | 
|  | int c2; | 
|  | for (c2 = 0; c2 < INDEX_SIZE; c2++) | 
|  | { | 
|  | struct sbucket *bkt = idx->buckets[c2]; | 
|  | if ((bkt != array->empty_bucket) | 
|  | && (bkt->version.version == array->version.version)) | 
|  | { | 
|  | sarray_free_garbage (bkt); | 
|  | nbuckets -= 1; | 
|  | } | 
|  | } | 
|  | sarray_free_garbage (idx); | 
|  | nindices -= 1; | 
|  | } | 
|  | #else /* OBJC_SPARSE2 */ | 
|  | struct sbucket *bkt = old_buckets[counter]; | 
|  | if ((bkt != array->empty_bucket) | 
|  | && (bkt->version.version == array->version.version)) | 
|  | { | 
|  | sarray_free_garbage (bkt); | 
|  | nbuckets -= 1; | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  | /* Free empty_index.  */ | 
|  | if (array->empty_index->version.version == array->version.version) | 
|  | { | 
|  | sarray_free_garbage (array->empty_index); | 
|  | nindices -= 1; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* Free empty_bucket.  */ | 
|  | if (array->empty_bucket->version.version == array->version.version) | 
|  | { | 
|  | sarray_free_garbage (array->empty_bucket); | 
|  | nbuckets -= 1; | 
|  | } | 
|  | idxsize -= (old_max_index + 1); | 
|  | narrays -= 1; | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  | /* Free bucket table.  */ | 
|  | sarray_free_garbage (array->indices); | 
|  | #else | 
|  | /* Free bucket table.  */ | 
|  | sarray_free_garbage (array->buckets); | 
|  | #endif | 
|  |  | 
|  | /* If this is a copy of another array, we free it (which might just | 
|  | decrement its reference count so it will be freed when no longer | 
|  | in use).  */ | 
|  | if (array->is_copy_of) | 
|  | sarray_free (array->is_copy_of); | 
|  |  | 
|  | /* Free array.  */ | 
|  | sarray_free_garbage (array); | 
|  | } | 
|  |  | 
|  | /* This is a lazy copy.  Only the core of the structure is actually | 
|  | copied.  */ | 
|  | struct sarray * | 
|  | sarray_lazy_copy (struct sarray *oarr) | 
|  | { | 
|  | struct sarray *arr; | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  | size_t num_indices = ((oarr->capacity - 1)/INDEX_CAPACITY) + 1; | 
|  | struct sindex **new_indices; | 
|  | #else /* OBJC_SPARSE2 */ | 
|  | size_t num_indices = ((oarr->capacity - 1)/BUCKET_SIZE) + 1; | 
|  | struct sbucket **new_buckets; | 
|  | #endif | 
|  |  | 
|  | /* Allocate core array.  */ | 
|  | arr = (struct sarray *) objc_malloc (sizeof (struct sarray)); /* !!! */ | 
|  | arr->version.version = oarr->version.version + 1; | 
|  | #ifdef OBJC_SPARSE3 | 
|  | arr->empty_index = oarr->empty_index; | 
|  | #endif | 
|  | arr->empty_bucket = oarr->empty_bucket; | 
|  | arr->ref_count = 1; | 
|  | oarr->ref_count += 1; | 
|  | arr->is_copy_of = oarr; | 
|  | arr->capacity = oarr->capacity; | 
|  |  | 
|  | #ifdef OBJC_SPARSE3 | 
|  | /* Copy bucket table.  */ | 
|  | new_indices = (struct sindex **) | 
|  | objc_malloc (sizeof (struct sindex *) * num_indices); | 
|  | memcpy (new_indices, oarr->indices, sizeof (struct sindex *) * num_indices); | 
|  | arr->indices = new_indices; | 
|  | #else | 
|  | /* Copy bucket table.  */ | 
|  | new_buckets = (struct sbucket **) | 
|  | objc_malloc (sizeof (struct sbucket *) * num_indices); | 
|  | memcpy (new_buckets, oarr->buckets, sizeof (struct sbucket *) * num_indices); | 
|  | arr->buckets = new_buckets; | 
|  | #endif | 
|  |  | 
|  | idxsize += num_indices; | 
|  | narrays += 1; | 
|  |  | 
|  | return arr; | 
|  | } |