[mimalloc] Only use __builtin_thread_pointer() in multithreaded builds#26747
[mimalloc] Only use __builtin_thread_pointer() in multithreaded builds#26747juj merged 1 commit intoemscripten-core:mainfrom
__builtin_thread_pointer() in multithreaded builds#26747Conversation
In single-threaded builds, `__builtin_thread_pointer()` may return 0, breaking mimalloc's non-null thread pointer assumption and causing a segfault with `-sSAFE_HEAP`. Only use it when threading is enabled. Fixes: emscripten-core#26742.
|
Btw, does mimalloc compile on top of emmalloc? I see there are emmalloc specific fields in the config fields for mimalloc? |
|
I think we should probably fix |
|
Can you add the failing test to the list of Or better still add a SAFE_HEAP variant to on the tests in test_other.py that reproduces this issue? |
mimalloc is built on top of emmalloc. There are some design notes here: emscripten/system/lib/mimalloc/src/prim/emscripten/prim.c Lines 18 to 47 in 736b63f
I agree. Would it be worth adding a TODO comment in a follow-up PR? Also, feel free to revert this PR once
I could add the previously failing test to the CircleCI config, but I've just noticed that it doesn't reproduce in the emscripten/system/lib/mimalloc/src/prim/prim.c Lines 34 to 46 in 47e7d67 |
|
But we have a shortlist of |
…ilds Without this fix `__tls_base` can remain set to zero which leads `__builtin_thread_pointer` to return NULL, which is should not. See emscripten-core/emscripten#26747
|
Checking that FWIW, here's a minimal reproducer reduced by cvise: Details
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct {
_Atomic(uintptr_t) tid;
} mi_atomic_once_t;
// A first-class heap
typedef struct mi_heap_s mi_heap_t;
// A sub-process
typedef struct mi_subproc_s mi_subproc_t;
// A thread-local heap ("theap") owns a set of thread-local pages
typedef struct mi_theap_s mi_theap_t;
struct mi_heap_s {
mi_subproc_t *subproc;
};
struct mi_subproc_s {
_Atomic(int64_t) purge_expire; // REMOVE THIS TO ALSO SEGFAULT ON -g -sSAFE_HEAP=1
_Atomic(mi_heap_t *) heap_main;
};
struct mi_theap_s {
_Atomic(mi_heap_t *) heap;
};
static mi_subproc_t subproc_main = { 0 };
mi_heap_t heap_main = { 0 };
mi_theap_t theap_main = { &heap_main };
// the theap belonging to the main heap
__thread mi_theap_t *__mi_theap_main = NULL;
bool _mi_atomic_once_enter(mi_atomic_once_t *once) {
const uintptr_t current_tid = (uintptr_t)__builtin_thread_pointer();
if (once->tid == current_tid) {
return false;
}
return true;
}
#define mi_atomic_do_once \
static mi_atomic_once_t _mi_once = { 0 }; \
for (bool _mi_exec = _mi_atomic_once_enter(&_mi_once); _mi_exec; _mi_exec = false)
static void mi_heap_main_init(void) {
heap_main.subproc = &subproc_main;
}
static void mi_process_init_once(void) {
mi_heap_main_init();
}
void mi_process_init(void) {
mi_atomic_do_once {
mi_process_init_once();
}
}
void _mi_theap_default_set(mi_theap_t *theap) {
if (theap->heap->subproc->heap_main)
__mi_theap_main = theap;
}
static void __attribute__((constructor(101))) mi_process_attach(void) {
mi_process_init();
_mi_theap_default_set(&theap_main);
}Works without any issues: $ emcc -O2 -sSAFE_HEAP=1 -pthread reduced.c && node a.out.js
$ emcc -g -sSAFE_HEAP=1 reduced.c && node a.out.jsSegfault: $ emcc -O2 -sSAFE_HEAP=1 reduced.c && node a.out.js
...
RuntimeError: Aborted(segmentation fault). Build with -sASSERTIONS for more info.The original segfault happend here: emscripten/system/lib/mimalloc/src/init.c Lines 954 to 956 in 17a65db |
In single-threaded builds,
__builtin_thread_pointer()may return 0, breaking mimalloc's non-null thread pointer assumption and causing a segfault with-sSAFE_HEAP. Only use it when threading is enabled.Fixes: #26742.