Packages
hackney
2.0.0-beta.1
4.7.2
4.7.1
4.7.0
4.6.1
4.6.0
4.5.2
4.5.1
4.5.0
4.4.5
4.4.3
4.4.2
4.4.1
4.4.0
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.0
4.0.3
4.0.2
4.0.1
4.0.0
3.2.1
3.2.0
3.1.2
3.1.1
3.1.0
3.0.3
3.0.2
3.0.1
3.0.0
retired
2.0.1
2.0.0
2.0.0-beta.1
1.25.0
1.24.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.2
1.18.1
1.18.0
1.17.4
1.17.3
1.17.2
1.17.1
1.17.0
1.16.0
1.15.2
1.15.1
1.15.0
1.14.3
1.14.2
1.14.0
1.13.0
1.12.1
1.12.0
1.11.0
1.10.1
1.10.0
1.9.0
1.8.6
1.8.5
1.8.4
1.8.3
1.8.2
1.8.0
1.7.1
1.7.0
1.6.6
retired
1.6.5
1.6.4
retired
1.6.3
1.6.2
1.6.1
1.6.0
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.10
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.0
1.0.6
1.0.5
1.0.2
1.0.1
0.15.2
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
Simple HTTP client with HTTP/1.1, HTTP/2, and HTTP/3 support
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
c_src/lsquic/src/liblsquic/lsquic_malo.c
/* Copyright (c) 2017 - 2022 LiteSpeed Technologies Inc. See LICENSE. */
/*
* lsquic_malo.c -- malo allocator implementation.
*
* The malo allocator is a pool of objects of fixed size. It tries to
* allocate and deallocate objects as fast as possible. To do so, it
* does the following:
*
* 1. Allocations occur 4 KB at a time.
* 2. No division or multiplication operations are performed for
* appropriately sized objects. (More on this below.)
*
* (In recent testing, malo was about 2.7 times faster than malloc for
* 64-byte objects.)
*
* Besides speed, two other important characteristics distinguish it
* from other pool allocators:
*
* 1. To free (put) an object, one does not need a pointer to the malo
* object. This makes this allocator easy to use.
* 2. A built-in iterator is provided to iterate over all allocated
* objects (with ability safely to release objects while iterator
* is active). This may be useful in some circumstances.
*
* To gain all these advantages, there are trade-offs:
*
* 1. There are two memory penalties:
* a. Per object overhead. If an object is at least ROUNDUP_THRESH in
* size as the next power of two, the allocator uses that power of
* two value as the object size. This is done to avoid using
* division and multiplication. For example, a 104-byte object
* will have a 24-byte overhead.
* b. Per page overhead. Page links occupy some bytes in the
* page. To keep things fast, at least one slot per page is
* always occupied, independent of object size. Thus, for a
* 1 KB object size, 25% of the page is used for the page
* header.
* 2. 4 KB pages are not freed until the malo allocator is destroyed.
* This is something to keep in mind.
*
* P.S. In Russian, "malo" (мало) means "little" or "few". Thus, the
* malo allocator aims to perform its job in as few CPU cycles as
* possible.
*/
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/queue.h>
#ifdef WIN32
#include <vc_compat.h>
#include <intrin.h>
#endif
#include "fiu-local.h"
#include "lsquic_malo.h"
#ifndef LSQUIC_USE_POOLS
#define LSQUIC_USE_POOLS 1
#endif
/* 64 slots in a 4KB page means that the smallest object is 64 bytes.
* The largest object is 2KB.
*/
#define MALO_MIN_NBITS 6
#define MALO_MAX_NBITS 11
#define ROUNDUP_THRESH 0.75f
/* A "free page" is a page with free slots available.
*/
#if LSQUIC_USE_POOLS
static unsigned find_free_slot (uint64_t slots);
static unsigned size_in_bits (size_t sz);
#endif
struct malo_page {
SLIST_ENTRY(malo_page) next_page;
LIST_ENTRY(malo_page) next_free_page;
struct malo *malo;
uint64_t slots,
full_slot_mask;
unsigned nbits; /* If pow is zero, stores object size */
unsigned initial_slot;
int pow; /* True if object is power of 2 */
};
typedef char malo_header_fits_in_one_slot
[(sizeof(struct malo_page) > (1 << MALO_MIN_NBITS)) ? -1 : 1];
#if !LSQUIC_USE_POOLS
struct nopool_elem
{
TAILQ_ENTRY(nopool_elem) next;
struct malo *malo;
unsigned char data[0];
};
#endif
struct malo {
#if LSQUIC_USE_POOLS
struct malo_page page_header;
SLIST_HEAD(, malo_page) all_pages;
LIST_HEAD(, malo_page) free_pages;
struct {
struct malo_page *cur_page;
unsigned next_slot;
} iter;
#else
/* List of all elements: used by the iterator */
TAILQ_HEAD(, nopool_elem) elems;
size_t obj_size;
struct nopool_elem *next_iter_elem;
#endif
};
struct malo *
lsquic_malo_create (size_t obj_size)
{
#if LSQUIC_USE_POOLS
int pow, n_slots;
unsigned nbits;
obj_size = (obj_size + 7) & -8;
nbits = size_in_bits(obj_size);
if (nbits < MALO_MIN_NBITS)
nbits = MALO_MIN_NBITS;
else if (nbits > MALO_MAX_NBITS)
{
errno = EOVERFLOW;
return NULL;
}
pow = obj_size <= (1 << MALO_MIN_NBITS)
|| (float) obj_size / (1 << nbits) > ROUNDUP_THRESH;
struct malo *malo;
if (0 != posix_memalign((void **) &malo, 0x1000, 0x1000))
return NULL;
SLIST_INIT(&malo->all_pages);
LIST_INIT(&malo->free_pages);
malo->iter.cur_page = &malo->page_header;
malo->iter.next_slot = 0;
if (pow)
n_slots = sizeof(*malo) / (1 << nbits)
+ ((sizeof(*malo) % (1 << nbits)) > 0);
else
n_slots = sizeof(*malo) / obj_size
+ ((sizeof(*malo) % obj_size) > 0);
struct malo_page *const page = &malo->page_header;
SLIST_INSERT_HEAD(&malo->all_pages, page, next_page);
LIST_INSERT_HEAD(&malo->free_pages, page, next_free_page);
page->malo = malo;
if (!pow)
page->full_slot_mask = (1ULL << (0x1000 / obj_size)) - 1;
else if (nbits == MALO_MIN_NBITS)
page->full_slot_mask = ~0ULL;
else
page->full_slot_mask = (1ULL << (1 << (12 - nbits))) - 1;
page->slots = (1ULL << n_slots) - 1;
page->pow = pow;
page->nbits = pow ? nbits : obj_size;
page->initial_slot = n_slots;
return malo;
#else
struct malo *malo;
/* Use the same sizing mechanism as in the normal version */
if (obj_size < (1 << MALO_MIN_NBITS))
obj_size = 1 << MALO_MIN_NBITS;
else
obj_size = (obj_size + 7) & -8;
malo = malloc(sizeof(*malo));
if (malo)
{
TAILQ_INIT(&malo->elems);
malo->obj_size = obj_size;
malo->next_iter_elem = NULL;
return malo;
}
else
return NULL;
#endif
}
#if LSQUIC_USE_POOLS
static struct malo_page *
allocate_page (struct malo *malo)
{
struct malo_page *page;
if (0 != posix_memalign((void **) &page, 0x1000, 0x1000))
return NULL;
SLIST_INSERT_HEAD(&malo->all_pages, page, next_page);
LIST_INSERT_HEAD(&malo->free_pages, page, next_free_page);
page->slots = 1;
page->full_slot_mask = malo->page_header.full_slot_mask;
page->nbits = malo->page_header.nbits;
page->pow = malo->page_header.pow;
page->malo = malo;
page->initial_slot = 1;
return page;
}
#endif
#define FAIL_NOMEM do { errno = ENOMEM; return NULL; } while (0)
/* Get a new object. */
void *
lsquic_malo_get (struct malo *malo)
{
#if LSQUIC_USE_POOLS
fiu_do_on("malo/get", FAIL_NOMEM);
struct malo_page *page = LIST_FIRST(&malo->free_pages);
if (!page)
{
page = allocate_page(malo);
if (!page)
return NULL;
}
unsigned slot = find_free_slot(page->slots);
page->slots |= (1ULL << slot);
if (page->full_slot_mask == page->slots)
LIST_REMOVE(page, next_free_page);
if (page->pow)
return (char *) page + (slot << page->nbits);
else
return (char *) page + (slot * page->nbits);
#else
struct nopool_elem *el;
el = malloc(sizeof(*el) + malo->obj_size);
if (el)
{
TAILQ_INSERT_HEAD(&malo->elems, el, next);
el->malo = malo;
return el->data;
}
else
return NULL;
#endif
}
/* Return obj to the pool */
void
lsquic_malo_put (void *obj)
{
#if LSQUIC_USE_POOLS
uintptr_t page_addr = (uintptr_t) obj & ~((1 << 12) - 1);
struct malo_page *page = (void *) page_addr;
unsigned slot;
if (page->pow)
slot = ((uintptr_t) obj - page_addr) >> page->nbits;
else
slot = ((uintptr_t) obj - page_addr) / page->nbits;
if (page->full_slot_mask == page->slots)
LIST_INSERT_HEAD(&page->malo->free_pages, page, next_free_page);
page->slots &= ~(1ULL << slot);
#else
struct nopool_elem *el;
el = (struct nopool_elem *) ((char *) obj - sizeof(*el));
if (el == el->malo->next_iter_elem)
el->malo->next_iter_elem = TAILQ_NEXT(el->malo->next_iter_elem, next);
TAILQ_REMOVE(&el->malo->elems, el, next);
free(el);
#endif
}
void
lsquic_malo_destroy (struct malo *malo)
{
#if LSQUIC_USE_POOLS
struct malo_page *page, *next;
page = SLIST_FIRST(&malo->all_pages);
while (page != &malo->page_header)
{
next = SLIST_NEXT(page, next_page);
#ifndef WIN32
free(page);
#else
_aligned_free(page);
#endif
page = next;
}
#ifndef WIN32
free(page);
#else
_aligned_free(page);
#endif
#else
struct nopool_elem *el, *next_el;
for (el = TAILQ_FIRST(&malo->elems); el; el = next_el)
{
next_el = TAILQ_NEXT(el, next);
free(el);
}
free(malo);
#endif
}
/* The iterator is built-in. Usage:
* void *obj;
* for (obj = lsquic_malo_first(malo); obj; lsquic_malo_next(malo))
* do_stuff(obj);
*/
void *
lsquic_malo_first (struct malo *malo)
{
#if LSQUIC_USE_POOLS
malo->iter.cur_page = SLIST_FIRST(&malo->all_pages);
malo->iter.next_slot = malo->iter.cur_page->initial_slot;
#else
malo->next_iter_elem = TAILQ_FIRST(&malo->elems);
#endif
return lsquic_malo_next(malo);
}
void *
lsquic_malo_next (struct malo *malo)
{
#if LSQUIC_USE_POOLS
struct malo_page *page;
unsigned max_slot, slot;
page = malo->iter.cur_page;
if (page)
{
if (page->pow)
max_slot = 1 << (12 - page->nbits); /* Same for all pages */
else
max_slot = 0x1000 / page->nbits;
slot = malo->iter.next_slot;
while (1)
{
for (; slot < max_slot; ++slot)
{
if (page->slots & (1ULL << slot))
{
malo->iter.cur_page = page;
malo->iter.next_slot = slot + 1;
if (page->pow)
return (char *) page + (slot << page->nbits);
else
{
assert(slot * (page->nbits + 1) < 0x1000);
return (char *) page + (slot * page->nbits);
}
}
}
page = SLIST_NEXT(page, next_page);
if (page)
slot = page->initial_slot;
else
{
malo->iter.cur_page = NULL; /* Stop iterator */
return NULL;
}
}
}
return NULL;
#else
struct nopool_elem *retval;
if (malo->next_iter_elem)
{
retval = malo->next_iter_elem;
malo->next_iter_elem = TAILQ_NEXT(malo->next_iter_elem, next);
return retval->data;
}
else
return NULL;
#endif
}
#if LSQUIC_USE_POOLS
static unsigned
size_in_bits (size_t sz)
{
#if __GNUC__
unsigned clz = sz > 1 ? __builtin_clz(sz - 1) : 31;
return 32 - clz;
#elif defined(WIN32)
unsigned char s;
unsigned long idx;
s = _BitScanReverse(&idx, sz);
assert(s);
return (unsigned) idx + 1;
#else
#error This function contains a bug!
unsigned clz;
size_t y;
--sz;
clz = 32;
y = sz >> 16; if (y) { clz -= 16; sz = y; }
y = sz >> 8; if (y) { clz -= 8; sz = y; }
y = sz >> 4; if (y) { clz -= 4; sz = y; }
y = sz >> 2; if (y) { clz -= 2; sz = y; }
y = sz >> 1; if (y) return 32 - clz + 1;
return 32 - clz + sz;
#endif
}
static unsigned
find_free_slot (uint64_t slots)
{
#if __GNUC__
return __builtin_ffsll(~slots) - 1;
#else
unsigned n;
slots =~ slots;
n = 0;
if (0 == (slots & ((1ULL << 32) - 1))) { n += 32; slots >>= 32; }
if (0 == (slots & ((1ULL << 16) - 1))) { n += 16; slots >>= 16; }
if (0 == (slots & ((1ULL << 8) - 1))) { n += 8; slots >>= 8; }
if (0 == (slots & ((1ULL << 4) - 1))) { n += 4; slots >>= 4; }
if (0 == (slots & ((1ULL << 2) - 1))) { n += 2; slots >>= 2; }
if (0 == (slots & ((1ULL << 1) - 1))) { n += 1; slots >>= 1; }
return n;
#endif
}
#endif
size_t
lsquic_malo_mem_used (const struct malo *malo)
{
#if LSQUIC_USE_POOLS
const struct malo_page *page;
size_t size;
size = 0;
SLIST_FOREACH(page, &malo->all_pages, next_page)
size += sizeof(*page);
return size;
#else
return 0;
#endif
}