From: Stefan Eissing Date: Fri, 17 Jul 2026 12:10:03 +0000 (+0200) Subject: uint-spbset: reused empty chunks X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c5fd5eb55a58f0f370c69d56fd0dabfe76035474;p=thirdparty%2Fcurl.git uint-spbset: reused empty chunks Fix chunk allocations by reusing existing empty chunks when a new offset is needed. Before this fix, spbsets would only ever grow with added numbers outside the range of existing chunks. Closes #22340 --- diff --git a/lib/uint-spbset.c b/lib/uint-spbset.c index d4e6c8e70b..66b0fcb670 100644 --- a/lib/uint-spbset.c +++ b/lib/uint-spbset.c @@ -74,90 +74,142 @@ uint32_t Curl_uint32_spbset_count(struct uint32_spbset *bset) return n; } -static struct uint32_spbset_chunk *uint32_spbset_get_chunk( - struct uint32_spbset *bset, uint32_t i, bool grow) +static bool uint32_spbset_empty_chunk(struct uint32_spbset_chunk *chunk) { - struct uint32_spbset_chunk *chunk, **panchor = NULL; - uint32_t i_offset = (i & ~CURL_UINT32_SPBSET_CH_MASK); - - if(!bset) - return NULL; + uint32_t i; + for(i = 0; i < CURL_UINT32_SPBSET_CH_SLOTS; ++i) { + if(chunk->slots[i]) + return FALSE; + } + return TRUE; +} +static struct uint32_spbset_chunk *uint32_spbset_unlink_empty( + struct uint32_spbset *bset, uint32_t for_offset) +{ + struct uint32_spbset_chunk *chunk, **panchor = NULL; for(chunk = &bset->head; chunk; panchor = &chunk->next, chunk = chunk->next) { - if(chunk->offset == i_offset) { - return chunk; - } - else if(chunk->offset > i_offset) { - /* need new chunk here */ - chunk = NULL; + if(uint32_spbset_empty_chunk(chunk)) break; + } + if(chunk) { + if(chunk == &bset->head) { /* head chunk is empty */ + if(!bset->head.next || (for_offset < bset->head.next->offset)) { + return &bset->head; + } + /* swap head and next, unlink */ + chunk = bset->head.next; + memcpy(&bset->head, chunk, sizeof(bset->head)); + memset(chunk, 0, sizeof(*chunk)); + } + else { + *panchor = chunk->next; /* unlink */ + memset(chunk, 0, sizeof(*chunk)); + } + } + return chunk; +} + +static struct uint32_spbset_chunk *uint32_spbset_insert_chunk( + struct uint32_spbset *bset, struct uint32_spbset_chunk *nchunk) +{ + struct uint32_spbset_chunk *chunk, **panchor; + + /* insert nchunk into set's ordered chunk list */ + if(nchunk->offset < bset->head.offset) { + /* swap chunk and head */ + uint32_t offset = nchunk->offset; + memcpy(nchunk, &bset->head, sizeof(*nchunk)); + memset(&bset->head, 0, sizeof(bset->head)); + bset->head.next = nchunk; + bset->head.offset = offset; + return &bset->head; + } + DEBUGASSERT(nchunk->offset > bset->head.offset); + panchor = &bset->head.next; + for(chunk = *panchor; chunk; + panchor = &chunk->next, chunk = chunk->next) { + if(chunk->offset > nchunk->offset) { /* insert before this chunk */ + nchunk->next = chunk; + *panchor = nchunk; + return nchunk; } } + /* no chunk with larger offset, append */ + *panchor = nchunk; + return nchunk; +} - if(!grow) +static struct uint32_spbset_chunk *uint32_spbset_get_chunk( + struct uint32_spbset *bset, uint32_t i, bool grow) +{ + struct uint32_spbset_chunk *chunk; + uint32_t i_offset = (i & ~CURL_UINT32_SPBSET_CH_MASK); + + if(!bset) return NULL; - /* need a new one */ - chunk = curlx_calloc(1, sizeof(*chunk)); - if(!chunk) + for(chunk = &bset->head; chunk; chunk = chunk->next) { + if(chunk->offset == i_offset) + return chunk; + else if(chunk->offset > i_offset) + break; /* need new chunk here */ + } + if(!grow) /* just a check if the chunk exists */ return NULL; - if(panchor) { /* insert between panchor and *panchor */ - chunk->next = *panchor; - *panchor = chunk; + /* Is there an empty chunk to reuse? */ + chunk = uint32_spbset_unlink_empty(bset, i_offset); + if(chunk) { + chunk->offset = i_offset; + if(chunk == &bset->head) /* head chunk is empty, stayed linked */ + return &bset->head; + /* was really unlinked, need to insert below */ } - else { /* prepend to head, switching places */ - memcpy(chunk, &bset->head, sizeof(*chunk)); - memset(&bset->head, 0, sizeof(bset->head)); - bset->head.next = chunk; + else { + /* need a new one */ + chunk = curlx_calloc(1, sizeof(*chunk)); + if(!chunk) + return NULL; + chunk->offset = i_offset; } - chunk->offset = i_offset; - return chunk; + + return uint32_spbset_insert_chunk(bset, chunk); } bool Curl_uint32_spbset_add(struct uint32_spbset *bset, uint32_t i) { - struct uint32_spbset_chunk *chunk; - uint32_t i_chunk; - - chunk = uint32_spbset_get_chunk(bset, i, TRUE); + struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, TRUE); if(!chunk) return FALSE; DEBUGASSERT(i >= chunk->offset); - i_chunk = (i - chunk->offset); - DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS); - chunk->slots[(i_chunk / 64)] |= ((uint64_t)1 << (i_chunk % 64)); + i -= chunk->offset; + DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64)); + chunk->slots[(i / 64)] |= ((uint64_t)1 << (i % 64)); return TRUE; } void Curl_uint32_spbset_remove(struct uint32_spbset *bset, uint32_t i) { - struct uint32_spbset_chunk *chunk; - uint32_t i_chunk; - - chunk = uint32_spbset_get_chunk(bset, i, FALSE); + struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, FALSE); if(chunk) { DEBUGASSERT(i >= chunk->offset); - i_chunk = (i - chunk->offset); - DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS); - chunk->slots[(i_chunk / 64)] &= ~((uint64_t)1 << (i_chunk % 64)); + i -= chunk->offset; + DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64)); + chunk->slots[(i / 64)] &= ~((uint64_t)1 << (i % 64)); } } bool Curl_uint32_spbset_contains(struct uint32_spbset *bset, uint32_t i) { - struct uint32_spbset_chunk *chunk; - uint32_t i_chunk; - - chunk = uint32_spbset_get_chunk(bset, i, FALSE); + struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, FALSE); if(chunk) { DEBUGASSERT(i >= chunk->offset); - i_chunk = (i - chunk->offset); - DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS); - return (chunk->slots[i_chunk / 64] & - ((uint64_t)1 << (i_chunk % 64))) != 0; + i -= chunk->offset; + DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64)); + return (chunk->slots[i / 64] & ((uint64_t)1 << (i % 64))) != 0; } return FALSE; }