| #include "Python.h" | |
| #include "pycore_index_pool.h" | |
| #include "pycore_lock.h" | |
| #include <stdbool.h> | |
| #ifdef Py_GIL_DISABLED | |
| static inline void | |
| swap(int32_t *values, Py_ssize_t i, Py_ssize_t j) | |
| { | |
| int32_t tmp = values[i]; | |
| values[i] = values[j]; | |
| values[j] = tmp; | |
| } | |
| static bool | |
| heap_try_swap(_PyIndexHeap *heap, Py_ssize_t i, Py_ssize_t j) | |
| { | |
| if (i < 0 || i >= heap->size) { | |
| return 0; | |
| } | |
| if (j < 0 || j >= heap->size) { | |
| return 0; | |
| } | |
| if (i <= j) { | |
| if (heap->values[i] <= heap->values[j]) { | |
| return 0; | |
| } | |
| } | |
| else if (heap->values[j] <= heap->values[i]) { | |
| return 0; | |
| } | |
| swap(heap->values, i, j); | |
| return 1; | |
| } | |
| static inline Py_ssize_t | |
| parent(Py_ssize_t i) | |
| { | |
| return (i - 1) / 2; | |
| } | |
| static inline Py_ssize_t | |
| left_child(Py_ssize_t i) | |
| { | |
| return 2 * i + 1; | |
| } | |
| static inline Py_ssize_t | |
| right_child(Py_ssize_t i) | |
| { | |
| return 2 * i + 2; | |
| } | |
| static void | |
| heap_add(_PyIndexHeap *heap, int32_t val) | |
| { | |
| assert(heap->size < heap->capacity); | |
| // Add val to end | |
| heap->values[heap->size] = val; | |
| heap->size++; | |
| // Sift up | |
| for (Py_ssize_t cur = heap->size - 1; cur > 0; cur = parent(cur)) { | |
| if (!heap_try_swap(heap, cur, parent(cur))) { | |
| break; | |
| } | |
| } | |
| } | |
| static Py_ssize_t | |
| heap_min_child(_PyIndexHeap *heap, Py_ssize_t i) | |
| { | |
| if (left_child(i) < heap->size) { | |
| if (right_child(i) < heap->size) { | |
| Py_ssize_t lval = heap->values[left_child(i)]; | |
| Py_ssize_t rval = heap->values[right_child(i)]; | |
| return lval < rval ? left_child(i) : right_child(i); | |
| } | |
| return left_child(i); | |
| } | |
| else if (right_child(i) < heap->size) { | |
| return right_child(i); | |
| } | |
| return -1; | |
| } | |
| static int32_t | |
| heap_pop(_PyIndexHeap *heap) | |
| { | |
| assert(heap->size > 0); | |
| // Pop smallest and replace with the last element | |
| int32_t result = heap->values[0]; | |
| heap->values[0] = heap->values[heap->size - 1]; | |
| heap->size--; | |
| // Sift down | |
| for (Py_ssize_t cur = 0; cur < heap->size;) { | |
| Py_ssize_t min_child = heap_min_child(heap, cur); | |
| if (min_child > -1 && heap_try_swap(heap, cur, min_child)) { | |
| cur = min_child; | |
| } | |
| else { | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| static int | |
| heap_ensure_capacity(_PyIndexHeap *heap, Py_ssize_t limit) | |
| { | |
| assert(limit > 0); | |
| if (heap->capacity > limit) { | |
| return 0; | |
| } | |
| Py_ssize_t new_capacity = heap->capacity ? heap->capacity : 1024; | |
| while (new_capacity && new_capacity < limit) { | |
| new_capacity <<= 1; | |
| } | |
| if (!new_capacity) { | |
| return -1; | |
| } | |
| int32_t *new_values = PyMem_RawCalloc(new_capacity, sizeof(int32_t)); | |
| if (new_values == NULL) { | |
| return -1; | |
| } | |
| if (heap->values != NULL) { | |
| memcpy(new_values, heap->values, heap->capacity); | |
| PyMem_RawFree(heap->values); | |
| } | |
| heap->values = new_values; | |
| heap->capacity = new_capacity; | |
| return 0; | |
| } | |
| static void | |
| heap_fini(_PyIndexHeap *heap) | |
| { | |
| if (heap->values != NULL) { | |
| PyMem_RawFree(heap->values); | |
| heap->values = NULL; | |
| } | |
| heap->size = -1; | |
| heap->capacity = -1; | |
| } | |
| #define LOCK_POOL(pool) PyMutex_LockFlags(&pool->mutex, _Py_LOCK_DONT_DETACH) | |
| #define UNLOCK_POOL(pool) PyMutex_Unlock(&pool->mutex) | |
| int32_t | |
| _PyIndexPool_AllocIndex(_PyIndexPool *pool) | |
| { | |
| LOCK_POOL(pool); | |
| int32_t index; | |
| _PyIndexHeap *free_indices = &pool->free_indices; | |
| if (free_indices->size == 0) { | |
| // No free indices. Make sure the heap can always store all of the | |
| // indices that have been allocated to avoid having to allocate memory | |
| // (which can fail) when freeing an index. Freeing indices happens when | |
| // threads are being destroyed, which makes error handling awkward / | |
| // impossible. This arrangement shifts handling of allocation failures | |
| // to when indices are allocated, which happens at thread creation, | |
| // where we are better equipped to deal with failure. | |
| if (heap_ensure_capacity(free_indices, pool->next_index + 1) < 0) { | |
| UNLOCK_POOL(pool); | |
| PyErr_NoMemory(); | |
| return -1; | |
| } | |
| index = pool->next_index++; | |
| } | |
| else { | |
| index = heap_pop(free_indices); | |
| } | |
| pool->tlbc_generation++; | |
| UNLOCK_POOL(pool); | |
| return index; | |
| } | |
| void | |
| _PyIndexPool_FreeIndex(_PyIndexPool *pool, int32_t index) | |
| { | |
| LOCK_POOL(pool); | |
| pool->tlbc_generation++; | |
| heap_add(&pool->free_indices, index); | |
| UNLOCK_POOL(pool); | |
| } | |
| void | |
| _PyIndexPool_Fini(_PyIndexPool *pool) | |
| { | |
| heap_fini(&pool->free_indices); | |
| } | |
| #endif // Py_GIL_DISABLED |