Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
colesbury committed Dec 18, 2023
1 parent 498a096 commit 70b5d54
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 22 deletions.
2 changes: 2 additions & 0 deletions Include/internal/mimalloc/mimalloc/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ mi_threadid_t _mi_thread_id(void) mi_attr_noexcept;
mi_heap_t* _mi_heap_main_get(void); // statically allocated main backing heap
void _mi_thread_done(mi_heap_t* heap);
void _mi_thread_data_collect(void);
void _mi_tld_init(mi_tld_t* tld, mi_heap_t* bheap);

// os.c
void _mi_os_init(void); // called from process init
Expand Down Expand Up @@ -170,6 +171,7 @@ size_t _mi_bin_size(uint8_t bin); // for stats
uint8_t _mi_bin(size_t size); // for stats

// "heap.c"
void _mi_heap_init_ex(mi_heap_t* heap, mi_tld_t* tld, mi_arena_id_t arena_id);
void _mi_heap_destroy_pages(mi_heap_t* heap);
void _mi_heap_collect_abandon(mi_heap_t* heap);
void _mi_heap_set_default_direct(mi_heap_t* heap);
Expand Down
23 changes: 23 additions & 0 deletions Include/internal/pycore_mimalloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,34 @@
# error "pycore_mimalloc.h must be included before mimalloc.h"
#endif

#define _Py_MIMALLOC_HEAP_MEM 0 // PyMem_Malloc() and friends
#define _Py_MIMALLOC_HEAP_OBJECT 1 // non-GC objects
#define _Py_MIMALLOC_HEAP_GC 2 // GC objects without pre-header
#define _Py_MIMALLOC_HEAP_GC_PRE 3 // GC objects with pre-header
#define _Py_MIMALLOC_HEAP_COUNT 4

#include "pycore_pymem.h"
#define MI_DEBUG_UNINIT PYMEM_CLEANBYTE
#define MI_DEBUG_FREED PYMEM_DEADBYTE
#define MI_DEBUG_PADDING PYMEM_FORBIDDENBYTE
#ifdef Py_DEBUG
# define MI_DEBUG 1
#else
# define MI_DEBUG 0
#endif

#include "mimalloc.h"
#include "mimalloc/types.h"
#include "mimalloc/internal.h"

struct _mimalloc_thread_state {
#ifdef Py_GIL_DISABLED
mi_heap_t *current_object_heap;
mi_heap_t heaps[_Py_MIMALLOC_HEAP_COUNT];
mi_tld_t tld;
#else
char _unused; // empty structs are not allowed
#endif
};

#endif // Py_INTERNAL_MIMALLOC_H
7 changes: 6 additions & 1 deletion Include/internal/pycore_tstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif

#include "pycore_mimalloc.h" // struct _mimalloc_thread_state


// Every PyThreadState is actually allocated as a _PyThreadStateImpl. The
// PyThreadState fields are exposed as part of the C API, although most fields
Expand All @@ -16,7 +18,10 @@ typedef struct _PyThreadStateImpl {
// semi-public fields are in PyThreadState.
PyThreadState base;

// TODO: add private fields here
#ifdef Py_GIL_DISABLED
struct _mimalloc_thread_state mimalloc;
#endif

} _PyThreadStateImpl;


Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
from test.support import os_helper
from test.support import (
STDLIB_DIR, swap_attr, swap_item, cpython_only, is_emscripten,
is_wasi, run_in_subinterp, run_in_subinterp_with_config, Py_TRACE_REFS)
is_wasi, run_in_subinterp, run_in_subinterp_with_config, Py_TRACE_REFS,
Py_GIL_DISABLED)
from test.support.import_helper import (
forget, make_legacy_pyc, unlink, unload, ready_to_import,
DirsOnSysPath, CleanImport)
Expand Down Expand Up @@ -2018,6 +2019,7 @@ def parse(cls, text):
return self


# @unittest.skipIf(Py_GIL_DISABLED, "deallocates objects from a different interpreter")
@requires_singlephase_init
class SinglephaseInitTests(unittest.TestCase):

Expand Down
22 changes: 16 additions & 6 deletions Objects/mimalloc/heap.c
Original file line number Diff line number Diff line change
Expand Up @@ -206,18 +206,28 @@ mi_heap_t* mi_heap_get_backing(void) {
return bheap;
}

mi_decl_nodiscard mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id) {
mi_heap_t* bheap = mi_heap_get_backing();
mi_heap_t* heap = mi_heap_malloc_tp(bheap, mi_heap_t); // todo: OS allocate in secure mode?
if (heap == NULL) return NULL;
void _mi_heap_init_ex(mi_heap_t* heap, mi_tld_t* tld, mi_arena_id_t arena_id)
{
_mi_memcpy_aligned(heap, &_mi_heap_empty, sizeof(mi_heap_t));
heap->tld = bheap->tld;
heap->tld = tld;
heap->thread_id = _mi_thread_id();
heap->arena_id = arena_id;
_mi_random_split(&bheap->random, &heap->random);
if (heap == tld->heap_backing) {
_mi_random_init(&heap->random);
}
else {
_mi_random_split(&tld->heap_backing->random, &heap->random);
}
heap->cookie = _mi_heap_random_next(heap) | 1;
heap->keys[0] = _mi_heap_random_next(heap);
heap->keys[1] = _mi_heap_random_next(heap);
}

mi_decl_nodiscard mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id) {
mi_heap_t* bheap = mi_heap_get_backing();
mi_heap_t* heap = mi_heap_malloc_tp(bheap, mi_heap_t); // todo: OS allocate in secure mode?
if (heap == NULL) return NULL;
_mi_heap_init_ex(heap, bheap->tld, arena_id);
heap->no_reclaim = true; // don't reclaim abandoned pages or otherwise destroy is unsafe
// push on the thread local heaps list
heap->next = heap->tld->heaps;
Expand Down
24 changes: 10 additions & 14 deletions Objects/mimalloc/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -297,24 +297,20 @@ static bool _mi_heap_init(void) {
mi_thread_data_t* td = mi_thread_data_zalloc();
if (td == NULL) return false;

mi_tld_t* tld = &td->tld;
mi_heap_t* heap = &td->heap;
_mi_tld_init(&td->tld, &td->heap);
_mi_heap_init_ex(&td->heap, &td->tld, _mi_arena_id_none());
_mi_heap_set_default_direct(&td->heap);
}
return false;
}

void _mi_tld_init(mi_tld_t* tld, mi_heap_t* bheap) {
_mi_memcpy_aligned(tld, &tld_empty, sizeof(*tld));
_mi_memcpy_aligned(heap, &_mi_heap_empty, sizeof(*heap));
heap->thread_id = _mi_thread_id();
_mi_random_init(&heap->random);
heap->cookie = _mi_heap_random_next(heap) | 1;
heap->keys[0] = _mi_heap_random_next(heap);
heap->keys[1] = _mi_heap_random_next(heap);
heap->tld = tld;
tld->heap_backing = heap;
tld->heaps = heap;
tld->segments.stats = &tld->stats;
tld->segments.os = &tld->os;
tld->os.stats = &tld->stats;
_mi_heap_set_default_direct(heap);
}
return false;
tld->heap_backing = bheap;
tld->heaps = bheap;
}

// Free the thread local default heap (called from `mi_thread_done`)
Expand Down
36 changes: 36 additions & 0 deletions Objects/obmalloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,37 @@ _PyMem_RawFree(void *Py_UNUSED(ctx), void *ptr)
void *
_PyMem_MiMalloc(void *ctx, size_t size)
{
#ifdef Py_GIL_DISABLED
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
mi_heap_t *heap = &tstate->mimalloc.heaps[_Py_MIMALLOC_HEAP_MEM];
return mi_heap_malloc(heap, size);
#else
return mi_malloc(size);
#endif
}

void *
_PyMem_MiCalloc(void *ctx, size_t nelem, size_t elsize)
{
#ifdef Py_GIL_DISABLED
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
mi_heap_t *heap = &tstate->mimalloc.heaps[_Py_MIMALLOC_HEAP_MEM];
return mi_heap_calloc(heap, nelem, elsize);
#else
return mi_calloc(nelem, elsize);
#endif
}

void *
_PyMem_MiRealloc(void *ctx, void *ptr, size_t size)
{
#ifdef Py_GIL_DISABLED
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
mi_heap_t *heap = &tstate->mimalloc.heaps[_Py_MIMALLOC_HEAP_MEM];
return mi_heap_realloc(heap, ptr, size);
#else
return mi_realloc(ptr, size);
#endif
}

void
Expand All @@ -112,20 +130,38 @@ _PyMem_MiFree(void *ctx, void *ptr)
void *
_PyObject_MiMalloc(void *ctx, size_t nbytes)
{
#ifdef Py_GIL_DISABLED
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
mi_heap_t *heap = tstate->mimalloc.current_object_heap;
return mi_heap_malloc(heap, nbytes);
#else
return mi_malloc(nbytes);
#endif
}

void *
_PyObject_MiCalloc(void *ctx, size_t nelem, size_t elsize)
{
#ifdef Py_GIL_DISABLED
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
mi_heap_t *heap = tstate->mimalloc.current_object_heap;
return mi_heap_calloc(heap, nelem, elsize);
#else
return mi_calloc(nelem, elsize);
#endif
}


void *
_PyObject_MiRealloc(void *ctx, void *ptr, size_t nbytes)
{
#ifdef Py_GIL_DISABLED
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
mi_heap_t *heap = tstate->mimalloc.current_object_heap;
return mi_heap_realloc(heap, ptr, nbytes);
#else
return mi_realloc(ptr, nbytes);
#endif
}

void
Expand Down
56 changes: 56 additions & 0 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@ tstate_is_bound(PyThreadState *tstate)
static void bind_gilstate_tstate(PyThreadState *);
static void unbind_gilstate_tstate(PyThreadState *);

static void tstate_mimalloc_bind(PyThreadState *);
static void tstate_mimalloc_clear(PyThreadState *);

static void
bind_tstate(PyThreadState *tstate)
{
Expand All @@ -256,6 +259,9 @@ bind_tstate(PyThreadState *tstate)
tstate->native_thread_id = PyThread_get_thread_native_id();
#endif

// mimalloc state needs to be initialized from the active thread.
tstate_mimalloc_bind(tstate);

tstate->_status.bound = 1;
}

Expand Down Expand Up @@ -1533,6 +1539,8 @@ PyThreadState_Clear(PyThreadState *tstate)
tstate->on_delete(tstate->on_delete_data);
}

tstate_mimalloc_clear(tstate);

tstate->_status.cleared = 1;

// XXX Call _PyThreadStateSwap(runtime, NULL) here if "current".
Expand Down Expand Up @@ -2495,3 +2503,51 @@ _PyThreadState_MustExit(PyThreadState *tstate)
}
return 1;
}

/********************/
/* mimalloc support */
/********************/

static void
tstate_mimalloc_bind(PyThreadState *tstate)
{
#ifdef Py_GIL_DISABLED
struct _mimalloc_thread_state *mts = &((_PyThreadStateImpl*)tstate)->mimalloc;

// Initialize the mimalloc thread state. This must be called from the
// same thread that will use the thread state. The "mem" heap doubles as
// the "backing" heap.
mi_tld_t *tld = &mts->tld;
_mi_tld_init(tld, &mts->heaps[_Py_MIMALLOC_HEAP_MEM]);

// Initialize each heap
for (Py_ssize_t i = 0; i < _Py_MIMALLOC_HEAP_COUNT; i++) {
_mi_heap_init_ex(&mts->heaps[i], tld, _mi_arena_id_none());
}

// By default, object allocations use _Py_MIMALLOC_HEAP_OBJECT.
// _PyObject_GC_New() and similar functions temporarily override this to
// use one of the GC heaps.
mts->current_object_heap = &mts->heaps[_Py_MIMALLOC_HEAP_OBJECT];
#endif
}

static void
tstate_mimalloc_clear(PyThreadState *tstate)
{
#ifdef Py_GIL_DISABLED
if (!tstate->_status.bound) {
// The mimalloc heaps are only initialized when the thread is bound.
return;
}

_PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate;
for (Py_ssize_t i = 0; i < _Py_MIMALLOC_HEAP_COUNT; i++) {
// Abandon all segments in use by this thread. This pushes them to
// a shared pool to later be reclaimed by other threads. It's important
// to do this before the thread state is destroyed so that objects
// remain visible to the GC.
_mi_heap_collect_abandon(&tstate_impl->mimalloc.heaps[i]);
}
#endif
}

0 comments on commit 70b5d54

Please sign in to comment.