use page-map on emscripten to reduce the alignment requirements (leading to less memory usage); allow environment variables from emscripten, see also issue #1455
diff --git a/CMakeLists.txt b/CMakeLists.txt index 0045689..fe63e72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -464,7 +464,7 @@ endif() if(MI_GUARDED STREQUAL "DEFAULT") - if(MI_DEBUG) + if(MI_DEBUG AND NOT (EMSCRIPTEN OR CMAKE_SYSTEM_NAME STREQUAL "WASI")) # no memory protection on wasm set(MI_GUARDED "FULL") else() set(MI_GUARDED "OFF") @@ -552,8 +552,8 @@ list(APPEND mi_defines MI_PROFILE=0) endif() -if(CMAKE_SYSTEM_NAME STREQUAL "WASI") - message(STATUS "Enabling MI_FREE_USE_PAGEMAP since the target (WASI) does not support large OS aligned allocations well") +if(CMAKE_SYSTEM_NAME STREQUAL "WASI" OR EMSCRIPTEN) + message(STATUS "Enabling MI_FREE_USE_PAGEMAP since the target (${CMAKE_SYSTEM_NAME}) does not support large OS aligned allocations well") set(MI_FREE_USE_PAGEMAP ON) endif() if(MI_FREE_USE_PAGEMAP) @@ -898,6 +898,9 @@ list(APPEND mi_libraries "${MI_LIB_ATOMIC}") endif() endif() +if(EMSCRIPTEN) + list(APPEND mi_libraries -sALLOW_MEMORY_GROWTH=1) # mimalloc reserves memory in large chunks +endif() # set language for source files now if(MI_USE_CXX) @@ -1064,6 +1067,9 @@ target_compile_definitions(mimalloc PRIVATE ${mi_defines} MI_SHARED_LIB MI_SHARED_LIB_EXPORT) target_compile_options(mimalloc PRIVATE ${mi_cflags} ${mi_cflags_dynamic}) target_link_libraries(mimalloc PRIVATE ${mi_libraries}) + if(EMSCRIPTEN) + target_link_options(mimalloc PRIVATE -Wno-pthreads-mem-growth) + endif() target_include_directories(mimalloc PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:${mi_install_incdir}> @@ -1213,7 +1219,7 @@ # run tests through the emulator when cross compiling (e.g. `node` for emscripten) set(mi_test_runner ${CMAKE_CROSSCOMPILING_EMULATOR}) if(EMSCRIPTEN) - add_link_options(-sALLOW_MEMORY_GROWTH=1) + add_link_options(-sNODE_HOST_ENV=1) # pass environment variables if(CMAKE_C_FLAGS MATCHES "-pthread") add_link_options(-pthread -sPTHREAD_POOL_SIZE=40 -Wno-pthreads-mem-growth) # pre-spawn workers so threads can start while main blocks endif()
diff --git a/readme.md b/readme.md index 0c423bb..74a0c9c 100644 --- a/readme.md +++ b/readme.md
@@ -247,6 +247,22 @@ > cmake ../.. -G "Visual Studio 17 2022" -T ClangCl ``` +## Emscripten + +You can build for WebAssembly with [Emscripten](https://emscripten.org) using `emcmake`. +Add `-pthread` to the compiler flags for a multi-threaded build. The tests run under `node`: + +``` +> emcmake cmake ../.. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-pthread +> cmake --build . +> ctest +``` + +When linking with mimalloc, use `-sALLOW_MEMORY_GROWTH` (as mimalloc reserves its arena memory up front; this is +added automatically when linking with the CMake target or `pkg-config`), and `-pthread` if mimalloc was built with it. +Mimalloc reads `MIMALLOC_` options from the program environment; +under `node` you can pass the host environment with `-sNODE_HOST_ENV`, or otherwise set `ENV` in the JavaScript module. + ## Single Source
diff --git a/src/prim/emscripten/prim.c b/src/prim/emscripten/prim.c index 5dd6e5c..eccb662 100644 --- a/src/prim/emscripten/prim.c +++ b/src/prim/emscripten/prim.c
@@ -9,6 +9,7 @@ #include "mimalloc.h" #include "mimalloc/prim.h" +#include "mimalloc/util/libc.h" #include "mimalloc/heap/init.h" #include <sched.h> // sched_yield @@ -72,6 +73,16 @@ // Allocation //--------------------------------------------- +// The builtin allocator aborts on OOM by default (`-sABORTING_MALLOC`), so we fail early +// if the heap cannot grow enough (e.g. without `-sALLOW_MEMORY_GROWTH`) and let mimalloc fall back. +// (This is conservative as the builtin allocator may still have enough free space internally.) +static bool mi_prim_heap_has_room(size_t size, size_t alignment) { + const uintptr_t top = *emscripten_get_sbrk_ptr(); + const size_t max = emscripten_get_heap_max(); + const size_t needed = size + alignment + 64*MI_KiB; // allow for alignment, overhead, and sbrk granularity + return (needed > size && top < max && needed <= max - top); +} + // Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned. int _mi_prim_alloc(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr) { MI_UNUSED(try_alignment); MI_UNUSED(allow_large); MI_UNUSED(commit); MI_UNUSED(hint_addr); @@ -85,6 +96,10 @@ if (try_alignment < MIN_EMMALLOC_ALIGN) { try_alignment = MIN_EMMALLOC_ALIGN; } + if (!mi_prim_heap_has_room(size, try_alignment)) { + *addr = NULL; + return ENOMEM; + } void* p = emscripten_builtin_memalign(try_alignment, size); *addr = p; if (p == 0) { @@ -185,11 +200,23 @@ // Environment //---------------------------------------------------------------- +// Under node, host environment variables are only visible when linking with `-sNODE_HOST_ENV=1`. +// (Define `MI_NO_GETENV` to exclude this code) +extern char** environ; + int _mi_prim_getenv(const char* name, char* result, size_t result_size) { - // For code size reasons, do not support environ customization for now. - MI_UNUSED(name); - MI_UNUSED(result); - MI_UNUSED(result_size); + if (name==NULL) return -1; + const size_t len = _mi_strlen(name); + if (len == 0) return -1; + char** env = environ; + if (env == NULL) return -1; // not yet initialized; try again later + for (int i = 0; i < 10000 && env[i] != NULL; i++) { + const char* s = env[i]; + if (_mi_strnicmp(name, s, len) == 0 && s[len] == '=') { // case insensitive + if (!_mi_strlcpy(result, s + len + 1, result_size)) return -1; + return 1; + } + } return 0; // not found }
diff --git a/src/util/options.c b/src/util/options.c index 5940532..3ce1316 100644 --- a/src/util/options.c +++ b/src/util/options.c
@@ -603,6 +603,8 @@ #if MI_DEBUG mi_decl_noreturn mi_decl_cold void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, const char* func ) mi_attr_noexcept { _mi_fprintf(NULL, NULL, "mimalloc: assertion failed: at \"%s\":%u, %s\n assertion: \"%s\"\n", fname, line, (func==NULL?"":func), assertion); + // flush delayed output (if not yet initialized) so the message is not lost + if (mi_atomic_load_ptr_acquire(void,&mi_out_default) == NULL) { mi_out_buf_flush(&mi_out_stderr, true, NULL); } abort(); } #endif