Skip to content

gh-117142: ctypes: Migrate global closure freelist to module state #117875

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Include/internal/pycore_typeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ _PyStaticType_GET_WEAKREFS_LISTPTR(static_builtin_state *state)
return &state->tp_weaklist;
}

/* Like PyType_GetModule, but skips verification
* that type is a heap type with an associated module */
static inline PyObject *
_PyType_GetModule(PyTypeObject *type)
{
assert(PyType_Check(type));
assert(type->tp_flags & Py_TPFLAGS_HEAPTYPE);
PyHeapTypeObject *et = (PyHeapTypeObject *)type;
return et->ht_module;
}

/* Like PyType_GetModuleState, but skips verification
* that type is a heap type with an associated module */
static inline void *
Expand Down
13 changes: 13 additions & 0 deletions Lib/test/test_ctypes/test_refcounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ def test_finalize(self):
)
script_helper.assert_python_ok("-c", script)

def test_many_closures_per_module(self):
# check if mmap() and munmap() get called multiple times
script = (
"import ctypes;"
"pyfunc = lambda: 0;"
"cfunc_type = ctypes.CFUNCTYPE(ctypes.c_int);"
"cfuncs = [cfunc_type(pyfunc) for i in range(500)];"
"ctype_type = ctypes.Union.__class__.__base__;"
"n_containers = ctype_type.get_ffi_closure_containers_count();"
"exit(n_containers and n_containers < 2)"
)
script_helper.assert_python_ok("-c", script)


if __name__ == '__main__':
unittest.main()
20 changes: 20 additions & 0 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -558,12 +558,30 @@ _ctypes_CType_Type___sizeof___impl(PyObject *self, PyTypeObject *cls)
return PyLong_FromSsize_t(size);
}

/*[clinic input]
@classmethod
_ctypes.CType_Type.get_ffi_closure_containers_count

cls: defining_class
/
[clinic start generated code]*/

static PyObject *
_ctypes_CType_Type_get_ffi_closure_containers_count_impl(PyTypeObject *type,
PyTypeObject *cls)
/*[clinic end generated code: output=619f776a42f7c3aa input=285058c2f984defc]*/
{
ctypes_state *st = get_module_state_by_class(cls);
return PyLong_FromSsize_t(st->malloc_closure.narenas);
}

static PyObject *
CType_Type_repeat(PyObject *self, Py_ssize_t length);


static PyMethodDef ctype_methods[] = {
_CTYPES_CTYPE_TYPE___SIZEOF___METHODDEF
_CTYPES_CTYPE_TYPE_GET_FFI_CLOSURE_CONTAINERS_COUNT_METHODDEF
{0},
};

Expand Down Expand Up @@ -5936,6 +5954,8 @@ module_clear(PyObject *module) {
Py_CLEAR(st->PyComError_Type);
#endif
Py_CLEAR(st->PyCType_Type);
clear_malloc_closure_free_list(st);
memset(&st->malloc_closure, 0, sizeof(malloc_closure_state));
return 0;
}

Expand Down
4 changes: 2 additions & 2 deletions Modules/_ctypes/callbacks.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ CThunkObject_dealloc(PyObject *myself)
PyObject_GC_UnTrack(self);
(void)CThunkObject_clear(myself);
if (self->pcl_write) {
Py_ffi_closure_free(self->pcl_write);
Py_ffi_closure_free(tp, self->pcl_write);
}
PyObject_GC_Del(self);
Py_DECREF(tp);
Expand Down Expand Up @@ -364,7 +364,7 @@ CThunkObject *_ctypes_alloc_callback(ctypes_state *st,

assert(CThunk_CheckExact(st, (PyObject *)p));

p->pcl_write = Py_ffi_closure_alloc(sizeof(ffi_closure), &p->pcl_exec);
p->pcl_write = Py_ffi_closure_alloc(st, sizeof(ffi_closure), &p->pcl_exec);
if (p->pcl_write == NULL) {
PyErr_NoMemory();
goto error;
Expand Down
24 changes: 23 additions & 1 deletion Modules/_ctypes/clinic/_ctypes.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 25 additions & 4 deletions Modules/_ctypes/ctypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@
#include <Unknwn.h> // for IUnknown interface
#endif

typedef union _tagITEM {
ffi_closure closure;
union _tagITEM *next;
} malloc_closure_item;

typedef struct _tag_arena {
struct _tag_arena *prev_arena;
malloc_closure_item items[1];
} malloc_closure_arena;

typedef struct {
int pagesize;
malloc_closure_item *free_list;
malloc_closure_arena *last_arena;
Py_ssize_t arena_size;
Py_ssize_t narenas;
} malloc_closure_state;

typedef struct {
PyTypeObject *DictRemover_Type;
PyTypeObject *PyCArg_Type;
Expand Down Expand Up @@ -71,6 +89,7 @@ typedef struct {
PyObject *error_object_name; // callproc.c
PyObject *PyExc_ArgError;
PyObject *swapped_suffix;
malloc_closure_state malloc_closure;
} ctypes_state;


Expand Down Expand Up @@ -450,11 +469,13 @@ extern int _ctypes_simple_instance(ctypes_state *st, PyObject *obj);
PyObject *_ctypes_get_errobj(ctypes_state *st, int **pspace);

#ifdef USING_MALLOC_CLOSURE_DOT_C
void Py_ffi_closure_free(void *p);
void *Py_ffi_closure_alloc(size_t size, void** codeloc);
void Py_ffi_closure_free(PyTypeObject *thunk_tp, void *p);
void *Py_ffi_closure_alloc(ctypes_state *st, size_t size, void** codeloc);
void clear_malloc_closure_free_list(ctypes_state *st);
#else
#define Py_ffi_closure_free ffi_closure_free
#define Py_ffi_closure_alloc ffi_closure_alloc
#define Py_ffi_closure_free(tp, p) ffi_closure_free(p)
#define Py_ffi_closure_alloc(st, size, codeloc) ffi_closure_alloc(size, codeloc)
#define clear_malloc_closure_free_list(st) ((void)0)
#endif


Expand Down
92 changes: 66 additions & 26 deletions Modules/_ctypes/malloc_closure.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,15 @@

/******************************************************************/

typedef union _tagITEM {
ffi_closure closure;
union _tagITEM *next;
} ITEM;
typedef malloc_closure_item ITEM;
typedef malloc_closure_arena ARENA;

static ITEM *free_list;
static int _pagesize;

static void more_core(void)
static void
more_core(malloc_closure_state *st)
{
ITEM *item;
int count, i;
int _pagesize = st->pagesize;

/* determine the pagesize */
#ifdef MS_WIN32
Expand All @@ -56,45 +53,74 @@ static void more_core(void)
#endif
}
#endif
st->pagesize = _pagesize;

/* calculate the number of nodes to allocate */
count = BLOCKSIZE / sizeof(ITEM);
count = (BLOCKSIZE - sizeof(ARENA *)) / sizeof(ITEM);
if (count <= 0) {
return;
}
st->arena_size = sizeof(ARENA *) + count * sizeof(ITEM);

/* allocate a memory block */
#ifdef MS_WIN32
item = (ITEM *)VirtualAlloc(NULL,
count * sizeof(ITEM),
ARENA *arena = (ARENA *)VirtualAlloc(NULL,
st->arena_size,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
if (item == NULL)
if (arena == NULL)
return;
#else
item = (ITEM *)mmap(NULL,
count * sizeof(ITEM),
ARENA *arena = (ARENA *)mmap(NULL,
st->arena_size,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0);
if (item == (void *)MAP_FAILED)
if (arena == (void *)MAP_FAILED)
return;
#endif

arena->prev_arena = st->last_arena;
st->last_arena = arena;
++st->narenas;
item = arena->items;

#ifdef MALLOC_CLOSURE_DEBUG
printf("block at %p allocated (%d bytes), %d ITEMs\n",
item, count * (int)sizeof(ITEM), count);
#endif
/* put them into the free list */
for (i = 0; i < count; ++i) {
item->next = free_list;
free_list = item;
item->next = st->free_list;
st->free_list = item;
++item;
}
}

void
clear_malloc_closure_free_list(ctypes_state *state)
{
malloc_closure_state *st = &state->malloc_closure;
while (st->narenas > 0) {
ARENA *arena = st->last_arena;
assert(arena != NULL);
st->last_arena = arena->prev_arena;
#ifdef MS_WIN32
VirtualFree(arena, 0, MEM_RELEASE);
#else
munmap(arena, st->arena_size);
#endif
st->narenas--;
}
assert(st->last_arena == NULL);
}

/******************************************************************/

/* put the item back into the free list */
void Py_ffi_closure_free(void *p)
void
Py_ffi_closure_free(PyTypeObject *thunk_tp, void *p)
{
#ifdef HAVE_FFI_CLOSURE_ALLOC
#ifdef USING_APPLE_OS_LIBFFI
Expand All @@ -110,13 +136,24 @@ void Py_ffi_closure_free(void *p)
}
#endif
#endif
PyObject *module = _PyType_GetModule(thunk_tp);
if (module == NULL) {
return;
}
ctypes_state *state = get_module_state(module);

malloc_closure_state *st = &state->malloc_closure;
if (st->narenas <= 0) {
return;
}
ITEM *item = (ITEM *)p;
item->next = free_list;
free_list = item;
item->next = st->free_list;
st->free_list = item;
}

/* return one item from the free list, allocating more if needed */
void *Py_ffi_closure_alloc(size_t size, void** codeloc)
void *
Py_ffi_closure_alloc(ctypes_state *state, size_t size, void** codeloc)
{
#ifdef HAVE_FFI_CLOSURE_ALLOC
#ifdef USING_APPLE_OS_LIBFFI
Expand All @@ -132,12 +169,15 @@ void *Py_ffi_closure_alloc(size_t size, void** codeloc)
#endif
#endif
ITEM *item;
if (!free_list)
more_core();
if (!free_list)
malloc_closure_state *st = &state->malloc_closure;
if (!st->free_list) {
more_core(st);
}
if (!st->free_list) {
return NULL;
item = free_list;
free_list = item->next;
}
item = st->free_list;
st->free_list = item->next;
#ifdef _M_ARM
// set Thumb bit so that blx is called correctly
*codeloc = (ITEM*)((uintptr_t)item | 1);
Expand Down
2 changes: 0 additions & 2 deletions Tools/c-analyzer/cpython/globals-to-fix.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,6 @@ Modules/_tkinter.c - trbInCmd -
## other
Include/datetime.h - PyDateTimeAPI -
Modules/_ctypes/cfield.c _ctypes_get_fielddesc initialized -
Modules/_ctypes/malloc_closure.c - _pagesize -
Modules/_cursesmodule.c - initialised -
Modules/_cursesmodule.c - initialised_setupterm -
Modules/_cursesmodule.c - initialisedcolors -
Expand All @@ -461,7 +460,6 @@ Modules/readline.c - libedit_history_start -
## state

Modules/_ctypes/cfield.c - formattable -
Modules/_ctypes/malloc_closure.c - free_list -
Modules/_curses_panel.c - lop -
Modules/_ssl/debughelpers.c _PySSL_keylog_callback lock -
Modules/_tkinter.c - quitMainLoop -
Expand Down