Skip to content

Overriding the memory allocator is needlessly complex #285

@saghul

Description

@saghul

I'm experimenting with using mimalloc on txiki.js and when it came to overriding the allocator in QuickJS it seems like a lot of bolierplate is needed:

static void *js_def_malloc(JSMallocState *s, size_t size)
{
    void *ptr;

    /* Do not allocate zero bytes: behavior is platform dependent */
    assert(size != 0);

    if (unlikely(s->malloc_size + size > s->malloc_limit))
        return NULL;

    ptr = malloc(size);
    if (!ptr)
        return NULL;

    s->malloc_count++;
    s->malloc_size += js__malloc_usable_size(ptr) + MALLOC_OVERHEAD;
    return ptr;
}

static void js_def_free(JSMallocState *s, void *ptr)
{
    if (!ptr)
        return;

    s->malloc_count--;
    s->malloc_size -= js__malloc_usable_size(ptr) + MALLOC_OVERHEAD;
    free(ptr);
}

static void *js_def_realloc(JSMallocState *s, void *ptr, size_t size)
{
    size_t old_size;

    if (!ptr) {
        if (size == 0)
            return NULL;
        return js_def_malloc(s, size);
    }
    old_size = js__malloc_usable_size(ptr);
    if (size == 0) {
        s->malloc_count--;
        s->malloc_size -= old_size + MALLOC_OVERHEAD;
        free(ptr);
        return NULL;
    }
    if (s->malloc_size + size - old_size > s->malloc_limit)
        return NULL;

    ptr = realloc(ptr, size);
    if (!ptr)
        return NULL;

    s->malloc_size += js__malloc_usable_size(ptr) - old_size;
    return ptr;
}

This is because the allocator customization works as follows:

typedef struct JSMallocFunctions {
    void *(*js_malloc)(JSMallocState *s, size_t size);
    void (*js_free)(JSMallocState *s, void *ptr);
    void *(*js_realloc)(JSMallocState *s, void *ptr, size_t size);
    size_t (*js_malloc_usable_size)(const void *ptr);
} JSMallocFunctions;

So if someone just wants to use mi_malloc and friends they need to copy the whole code to change a couple of lines.

Here is a proposal: alter JSMallocFunctions to take the system allocator into account:

typedef struct JSMallocFunctions {
    void *(*js_malloc)(JSMallocState *s, size_t size);
    void (*js_free)(JSMallocState *s, void *ptr);
    void *(*js_realloc)(JSMallocState *s, void *ptr, size_t size);
    size_t (*js_malloc_usable_size)(const void *ptr);
    void *(*sys_malloc)(size_t size);
    void (*sys_free)(void *ptr);
    void *(*sys_realloc)(void *ptr, size_t size);
} JSMallocFunctions;

This way, if all you want is to modify the system allocator to use mimalloc (for example), you'd do:

static const JSMallocFunctions mi_mf = {
    NULL,
    NULL,
    NULL,
    mi_malloc_usable_size,
    mi_malloc,
    mi_free,
    mi_realloc
};

Internally QuickJS would use the default functions and delegate to the overriden system allocator.

Thoughts?

PS: Why is QuickJS doing that extra MALLOC_OVERHEAD thing?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions