// Variable-length O(1) disk space allocator // Copyright (c) Vitaliy Filippov, 2025+ // License: VNPL-1.1 (see README.md for details) #include #include #include #include #include "multilist.h" multilist_alloc_t::multilist_alloc_t(uint32_t count, uint32_t maxn): count(count), maxn(maxn) { // not-so-memory-efficient: 16 MB memory per 1 GB buffer space, but buffer spaces are small, so OK assert(count > 1 && count < 0x80000000); sizes.resize(count); nexts.resize(count); // nexts[i] = 0 -> area is used; nexts[i] = 1 -> no next; nexts[i] >= 2 -> next item prevs.resize(count); heads.resize(maxn); // heads[i] = 0 -> empty list; heads[i] >= 1 -> list head sizes[0] = count; sizes[count-1] = -count; // end nexts[0] = 1; heads[maxn-1] = 1; #ifdef MULTILIST_TRACE print(); #endif } bool multilist_alloc_t::is_free(uint32_t pos) { assert(pos < count); if (sizes[pos] < 0) pos += sizes[pos]+1; while (pos > 0 && !sizes[pos]) pos--; return nexts[pos] > 0; } uint32_t multilist_alloc_t::find(uint32_t size) { assert(size > 0); assert(size <= maxn); for (uint32_t i = size-1; i < maxn; i++) { if (heads[i]) { return heads[i]-1; } } return UINT32_MAX; } void multilist_alloc_t::verify() { std::set reachable; for (int i = 0; i < maxn; i++) { uint32_t cur = heads[i]; while (cur) { if (!nexts[cur-1]) { fprintf(stderr, "ERROR: item %d from freelist %d is not free\n", cur-1, i); print(); abort(); } if (nexts[cur-1] >= count+2) { fprintf(stderr, "ERROR: next out of range at %d: %d\n", cur-1, nexts[cur-1]); print(); abort(); } if (!(i < maxn-1 ? sizes[cur-1] == i+1 : (sizes[cur-1] >= i+1))) { fprintf(stderr, "ERROR: item %d is in wrong freelist: expected size %d, but actual size is %d\n", cur-1, i+1, sizes[cur-1]); print(); abort(); } if (reachable.find(cur-1) != reachable.end()) { fprintf(stderr, "ERROR: doubly-claimed item %d\n", cur-1); print(); abort(); } reachable.insert(cur-1); cur = nexts[cur-1]-1; } } for (int i = 0; i < count; ) { if (sizes[i]) { assert(i+sizes[i] <= count); if (sizes[i] > 1 && sizes[i+sizes[i]-1] != -sizes[i]) { fprintf(stderr, "ERROR: start/end mismatch at %d: sizes[%d] should be %d, but is %d\n", i, i+sizes[i]-1, -sizes[i], sizes[i+sizes[i]-1]); print(); abort(); } for (int j = i+1; j < i+sizes[i]-1; j++) { if (sizes[j]) { fprintf(stderr, "ERROR: internal non-zero at %d: %d\n", j, sizes[j]); print(); abort(); } } if (nexts[i] && reachable.find(i) == reachable.end()) { fprintf(stderr, "ERROR: %d is unreachable from heads\n", i); print(); abort(); } if (nexts[i] >= 2) { if (nexts[i] >= 2+count) { fprintf(stderr, "ERROR: next out of range at %d: %d\n", i, nexts[i]); print(); abort(); } if (prevs[nexts[i]-2] != i+1) { fprintf(stderr, "ERROR: prev[next] (%d) != this (%d) at %d", prevs[nexts[i]-2], i+1, i); print(); abort(); } } i += (sizes[i] > 1 ? sizes[i] : 1); } else i++; } } void multilist_alloc_t::print() { printf("heads:"); for (int i = 0; i < maxn; i++) if (heads[i]) printf(" %u=%u", i, heads[i]); printf("\n"); printf("sizes:"); for (int i = 0; i < count; i++) if (sizes[i]) printf(" %d=%d", i, sizes[i]); printf("\n"); printf("prevs:"); for (int i = 0; i < count; i++) if (prevs[i]) printf(" %d=%d", i, prevs[i]); printf("\n"); printf("nexts:"); for (int i = 0; i < count; i++) if (nexts[i]) printf(" %d=%d", i, nexts[i]); printf("\n"); printf("items:"); for (int i = 0; i < count; ) { if (sizes[i]) { printf(" %u=(s:%d,n:%u,p:%u)", i, sizes[i], nexts[i], prevs[i]); assert(i+sizes[i] <= count); i += (sizes[i] > 1 ? sizes[i] : 1); } else i++; } printf("\n"); } void multilist_alloc_t::use(uint32_t pos, uint32_t size) { assert(pos+size <= count && size > 0); if (sizes[pos] <= 0) { uint32_t start = pos; if (sizes[start] < 0) start += sizes[start]+1; else while (start > 0 && !sizes[start]) start--; assert(sizes[start] >= size); use_full(start); uint32_t full = sizes[start]; sizes[pos-1] = -pos+start; sizes[start] = pos-start; free(start); sizes[pos+size-1] = -size; sizes[pos] = size; if (pos+size < start+full) { sizes[start+full-1] = -(start+full-pos-size); sizes[pos+size] = start+full-pos-size; free(pos+size); } } else { assert(sizes[pos] >= size); use_full(pos); if (sizes[pos] > size) { uint32_t full = sizes[pos]; sizes[pos+size-1] = -size; sizes[pos] = size; sizes[pos+full-1] = -full+size; sizes[pos+size] = full-size; free(pos+size); } } #ifdef MULTILIST_TRACE print(); #endif } void multilist_alloc_t::use_full(uint32_t pos) { uint32_t prevsize = sizes[pos]; assert(prevsize); assert(nexts[pos]); uint32_t pi = (prevsize < maxn ? prevsize : maxn)-1; if (heads[pi] == pos+1) heads[pi] = nexts[pos]-1; if (prevs[pos]) nexts[prevs[pos]-1] = nexts[pos]; if (nexts[pos] >= 2) prevs[nexts[pos]-2] = prevs[pos]; prevs[pos] = 0; nexts[pos] = 0; } void multilist_alloc_t::free(uint32_t pos) { do_free(pos); #ifdef MULTILIST_TRACE print(); #endif } void multilist_alloc_t::do_free(uint32_t pos) { assert(!nexts[pos]); uint32_t size = sizes[pos]; assert(size > 0); // merge with previous? if (pos > 0 && nexts[pos+(sizes[pos-1] == 1 ? -1 : sizes[pos-1])] > 0) { assert(sizes[pos-1] < 0 || sizes[pos-1] == 1); uint32_t prevsize = sizes[pos-1] < 0 ? -sizes[pos-1] : 1; use_full(pos-prevsize); sizes[pos] = 0; sizes[pos-1] = 0; size += prevsize; pos -= prevsize; sizes[pos+size-1] = -size; sizes[pos] = size; } // merge with next? if (pos+size < count && nexts[pos+size] >= 1) { uint32_t nextsize = sizes[pos+size]; use_full(pos+size); sizes[pos+size] = 0; sizes[pos+size-1] = 0; size += nextsize; sizes[pos+size-1] = -size; sizes[pos] = size; } uint32_t ni = (size < maxn ? size : maxn)-1; nexts[pos] = heads[ni]+1; prevs[pos] = 0; if (heads[ni]) prevs[heads[ni]-1] = pos+1; heads[ni] = pos+1; } multilist_index_t::multilist_index_t(uint32_t count, uint32_t max_used, uint32_t init_used): count(count), max_used(max_used) { assert(init_used < max_used); nexts.resize(count, UINT32_MAX); prevs.resize(count, UINT32_MAX); heads.resize(max_used, UINT32_MAX); for (size_t i = 0; i < count-1; i++) { nexts[i] = i+1; prevs[i+1] = i; } prevs[0] = UINT32_MAX; nexts[count-1] = UINT32_MAX; heads[init_used] = 0; } uint32_t multilist_index_t::find(uint32_t wanted_used) { assert(wanted_used < max_used); return heads[wanted_used]; } uint32_t multilist_index_t::next(uint32_t pos) { return nexts[pos]; } void multilist_index_t::change(uint32_t pos, uint32_t old_used, uint32_t new_used) { if (new_used == old_used) return; assert(old_used < max_used && new_used < max_used); if (prevs[pos] != UINT32_MAX) nexts[prevs[pos]] = nexts[pos]; if (nexts[pos] != UINT32_MAX) prevs[nexts[pos]] = prevs[pos]; if (heads[old_used] == pos) heads[old_used] = nexts[pos]; prevs[pos] = UINT32_MAX; if (heads[new_used] != UINT32_MAX) prevs[heads[new_used]] = pos; nexts[pos] = heads[new_used]; heads[new_used] = pos; } void multilist_index_t::print() { printf("heads:"); for (int i = 0; i < max_used; i++) if (heads[i] != UINT32_MAX) printf(" %u=%u", i, heads[i]); printf("\n"); printf("prevs:"); for (int i = 0; i < count; i++) if (prevs[i] != UINT32_MAX) printf(" %d=%d", i, prevs[i]); printf("\n"); printf("nexts:"); for (int i = 0; i < count; i++) if (nexts[i] != UINT32_MAX) printf(" %d=%d", i, nexts[i]); printf("\n"); }