#include #include "Python.h" #include "pycore_index_pool.h" #include "pycore_lock.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); } UNLOCK_POOL(pool); return index; } void _PyIndexPool_FreeIndex(_PyIndexPool *pool, int32_t index) { LOCK_POOL(pool); heap_add(&pool->free_indices, index); UNLOCK_POOL(pool); } void _PyIndexPool_Fini(_PyIndexPool *pool) { heap_fini(&pool->free_indices); } #endif // Py_GIL_DISABLED