Skip to content

Commit

Permalink
emmalloc: Add an option to not define the standard exports (#20487)
Browse files Browse the repository at this point in the history
With this PR if emmalloc.c is built with -DEMMALLOC_NO_STD_EXPORTS then we do
not define malloc, free, etc. That means we only provide emmalloc_malloc,
emmalloc_free, etc., the prefixed versions. They can then be used alongside another
malloc impl.

This will be useful in a later PR that adds a two-tiered allocator: a fast multithreaded
one, and underneath it emmalloc, which will function as the "system allocator" for it.
That is, emmalloc will play the role of VirtualAlloc on windows or mmap on POSIX,
a way for the main allocator to get system memory. (We can't just use sbrk for that
purpose because we also want to free memory to the system.) For that goal,
emmalloc seems suitable as it is compact (we don't need it to be super-fast; this is
the system allocator that will be called rarely, compared to the fast one before it).
And for emmalloc to be used like that we need this PR so that we can build
emmalloc alongside another allocator (that other allocator will define malloc etc.
itself).

Helps #18369
  • Loading branch information
kripken authored Oct 19, 2023
1 parent 4608c2e commit 612d382
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 8 deletions.
51 changes: 43 additions & 8 deletions system/lib/emmalloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@
* - Debugging and logging directly uses console.log via uses EM_ASM, not
* printf etc., to minimize any risk of debugging or logging depending on
* malloc.
*
* Exporting:
*
* - By default we declare not only emmalloc_malloc, emmalloc_free, etc. but
* also the standard library methods like malloc, free, and some aliases.
* You can override this by defining EMMALLOC_NO_STD_EXPORTS, in which case
* we only declare the emalloc_* ones but not the standard ones.
*/

#include <stdalign.h>
Expand All @@ -63,7 +70,13 @@ static_assert((((int32_t)0x80000000U) >> 31) == -1, "This malloc implementation
#define MALLOC_ALIGNMENT alignof(max_align_t)
static_assert(alignof(max_align_t) == 8, "max_align_t must be correct");

#ifdef EMMALLOC_NO_STD_EXPORTS
#define EMMALLOC_EXPORT
#define EMMALLOC_ALIAS(ALIAS, ORIGINAL)
#else
#define EMMALLOC_EXPORT __attribute__((weak, __visibility__("default")))
#define EMMALLOC_ALIAS(ALIAS, ORIGINAL) extern __typeof(ORIGINAL) ALIAS __attribute__((alias(#ORIGINAL)));
#endif

#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define MAX(x, y) ((x) > (y) ? (x) : (y))
Expand Down Expand Up @@ -812,31 +825,37 @@ void *emmalloc_memalign(size_t alignment, size_t size)
MALLOC_RELEASE();
return ptr;
}
extern __typeof(emmalloc_memalign) emscripten_builtin_memalign __attribute__((alias("emmalloc_memalign")));
EMMALLOC_ALIAS(emscripten_builtin_memalign, emmalloc_memalign);

#ifndef EMMALLOC_NO_STD_EXPORTS
void * EMMALLOC_EXPORT memalign(size_t alignment, size_t size)
{
return emmalloc_memalign(alignment, size);
}
#endif

#ifndef EMMALLOC_NO_STD_EXPORTS
void * EMMALLOC_EXPORT aligned_alloc(size_t alignment, size_t size)
{
if ((alignment % sizeof(void *) != 0) || (size % alignment) != 0)
return 0;
return emmalloc_memalign(alignment, size);
}
#endif

void *emmalloc_malloc(size_t size)
{
return emmalloc_memalign(MALLOC_ALIGNMENT, size);
}
extern __typeof(emmalloc_malloc) emscripten_builtin_malloc __attribute__((alias("emmalloc_malloc")));
extern __typeof(emmalloc_malloc) __libc_malloc __attribute__((alias("emmalloc_malloc")));
EMMALLOC_ALIAS(emscripten_builtin_malloc, emmalloc_malloc);
EMMALLOC_ALIAS(__libc_malloc, emmalloc_malloc);

#ifndef EMMALLOC_NO_STD_EXPORTS
void * EMMALLOC_EXPORT malloc(size_t size)
{
return emmalloc_malloc(size);
}
#endif

size_t emmalloc_usable_size(void *ptr)
{
Expand All @@ -858,10 +877,12 @@ size_t emmalloc_usable_size(void *ptr)
return size - REGION_HEADER_SIZE;
}

#ifndef EMMALLOC_NO_STD_EXPORTS
size_t EMMALLOC_EXPORT malloc_usable_size(void *ptr)
{
return emmalloc_usable_size(ptr);
}
#endif

void emmalloc_free(void *ptr)
{
Expand Down Expand Up @@ -932,13 +953,15 @@ void emmalloc_free(void *ptr)
emmalloc_validate_memory_regions();
#endif
}
extern __typeof(emmalloc_free) emscripten_builtin_free __attribute__((alias("emmalloc_free")));
extern __typeof(emmalloc_free) __libc_free __attribute__((alias("emmalloc_free")));
EMMALLOC_ALIAS(emscripten_builtin_free, emmalloc_free);
EMMALLOC_ALIAS(__libc_free, emmalloc_free);

#ifndef EMMALLOC_NO_STD_EXPORTS
void EMMALLOC_EXPORT free(void *ptr)
{
return emmalloc_free(ptr);
}
#endif

// Can be called to attempt to increase or decrease the size of the given region
// to a new size (in-place). Returns 1 if resize succeeds, and 0 on failure.
Expand Down Expand Up @@ -1067,10 +1090,12 @@ void *emmalloc_aligned_realloc(void *ptr, size_t alignment, size_t size)
return newptr;
}

#ifndef EMMALLOC_NO_STD_EXPORTS
void * EMMALLOC_EXPORT aligned_realloc(void *ptr, size_t alignment, size_t size)
{
return emmalloc_aligned_realloc(ptr, alignment, size);
}
#endif

// realloc_try() is like realloc(), but only attempts to try to resize the existing memory
// area. If resizing the existing memory area fails, then realloc_try() will return 0
Expand Down Expand Up @@ -1154,12 +1179,14 @@ void *emmalloc_realloc(void *ptr, size_t size)
{
return emmalloc_aligned_realloc(ptr, MALLOC_ALIGNMENT, size);
}
extern __typeof(emmalloc_realloc) __libc_realloc __attribute__((alias("emmalloc_realloc")));
EMMALLOC_ALIAS(__libc_realloc, emmalloc_realloc);

#ifndef EMMALLOC_NO_STD_EXPORTS
void * EMMALLOC_EXPORT realloc(void *ptr, size_t size)
{
return emmalloc_realloc(ptr, size);
}
#endif

// realloc_uninitialized() is like realloc(), but old memory contents
// will be undefined after reallocation. (old memory is not preserved in any case)
Expand All @@ -1177,10 +1204,12 @@ int emmalloc_posix_memalign(void **memptr, size_t alignment, size_t size)
return *memptr ? 0 : 12/*ENOMEM*/;
}

#ifndef EMMALLOC_NO_STD_EXPORTS
int EMMALLOC_EXPORT posix_memalign(void **memptr, size_t alignment, size_t size)
{
return emmalloc_posix_memalign(memptr, alignment, size);
}
#endif

void *emmalloc_calloc(size_t num, size_t size)
{
Expand All @@ -1190,12 +1219,14 @@ void *emmalloc_calloc(size_t num, size_t size)
memset(ptr, 0, bytes);
return ptr;
}
extern __typeof(emmalloc_calloc) __libc_calloc __attribute__((alias("emmalloc_calloc")));
EMMALLOC_ALIAS(__libc_calloc, emmalloc_calloc);

#ifndef EMMALLOC_NO_STD_EXPORTS
void * EMMALLOC_EXPORT calloc(size_t num, size_t size)
{
return emmalloc_calloc(num, size);
}
#endif

static int count_linked_list_size(Region *list)
{
Expand Down Expand Up @@ -1286,12 +1317,14 @@ struct mallinfo emmalloc_mallinfo()
return info;
}

#ifndef EMMALLOC_NO_STD_EXPORTS
struct mallinfo EMMALLOC_EXPORT mallinfo()
{
return emmalloc_mallinfo();
}
#endif

// Note! This function is not fully multithreadin safe: while this function is running, other threads should not be
// Note! This function is not fully multithreading safe: while this function is running, other threads should not be
// allowed to call sbrk()!
static int trim_dynamic_heap_reservation(size_t pad)
{
Expand Down Expand Up @@ -1352,10 +1385,12 @@ int emmalloc_trim(size_t pad)
return success;
}

#ifndef EMMALLOC_NO_STD_EXPORTS
int EMMALLOC_EXPORT malloc_trim(size_t pad)
{
return emmalloc_trim(pad);
}
#endif

size_t emmalloc_dynamic_heap_size()
{
Expand Down
36 changes: 36 additions & 0 deletions test/other/test_emmalloc_in_addition.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <assert.h>
#include <emscripten/console.h>
#include <emscripten/emmalloc.h>

int main() {
// Verify we can call both malloc and emmalloc_malloc, and that those are
// different functions, unless TEST_EMMALLOC_IS_MALLOC is set (in that case,
// emmalloc is malloc because we let emmalloc define the standard exports like
// malloc).

// We have allocated nothing so far, but there may be some initial allocation
// from startup.
size_t initial = emmalloc_dynamic_heap_size();
emscripten_console_logf("initial: %zu\n", initial);

const size_t ONE_MB = 1024 * 1024;
void* one = malloc(ONE_MB);
assert(one);
#ifndef TEST_EMMALLOC_IS_MALLOC
// We have allocated using malloc, but not emmalloc, so emmalloc reports no
// change in usage.
assert(emmalloc_dynamic_heap_size() == initial);
#else
// malloc == emmalloc_malloc, so emmalloc will report additional usage (of the
// size of the allocation, or perhaps more if it overallocated as an
// optimization).
assert(emmalloc_dynamic_heap_size() >= initial + ONE_MB);
#endif

void* two = emmalloc_malloc(ONE_MB);
assert(two);
// We have allocated using emmalloc, so now emmalloc definitely reports usage.
assert(emmalloc_dynamic_heap_size() >= initial + ONE_MB);

emscripten_console_log("success");
}
15 changes: 15 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -14033,3 +14033,18 @@ def test_hello_world_argv(self):

def test_arguments_global(self):
self.emcc(test_file('hello_world_argv.c'), ['-sENVIRONMENT=web', '-sSTRICT', '--closure=1', '-O2'])

@parameterized({
'no_std_exp': (['-DEMMALLOC_NO_STD_EXPORTS'],),
# When we let emmalloc build with the standard exports like malloc,
# emmalloc == malloc.
'with_std_exp': (['-DTEST_EMMALLOC_IS_MALLOC'],),
})
def test_emmalloc_in_addition(self, args):
# Test that we can use emmalloc in addition to another malloc impl. When we
# build emmalloc using -DEMMALLOC_NO_STD_EXPORTS it will not export malloc
# etc., and only provide the emmalloc_malloc etc. family of functions that
# we can use.
emmalloc = path_from_root('system', 'lib', 'emmalloc.c')
self.run_process([EMCC, test_file('other/test_emmalloc_in_addition.c'), emmalloc] + args)
self.assertContained('success', self.run_js('a.out.js'))

0 comments on commit 612d382

Please sign in to comment.