diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 09c4501c38c935..eb38d3b6f3fde6 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -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 * diff --git a/Lib/test/test_ctypes/test_refcounts.py b/Lib/test/test_ctypes/test_refcounts.py index 012722d8486218..54a2ac9d1194d3 100644 --- a/Lib/test/test_ctypes/test_refcounts.py +++ b/Lib/test/test_ctypes/test_refcounts.py @@ -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() diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 3cb0b24668eb2a..3d9dbc536be699 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -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}, }; @@ -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; } diff --git a/Modules/_ctypes/callbacks.c b/Modules/_ctypes/callbacks.c index 7b9f6437c7d55f..6ca51f4c33f6e0 100644 --- a/Modules/_ctypes/callbacks.c +++ b/Modules/_ctypes/callbacks.c @@ -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); @@ -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; diff --git a/Modules/_ctypes/clinic/_ctypes.c.h b/Modules/_ctypes/clinic/_ctypes.c.h index 98a84cc14f4386..49b774921f01a0 100644 --- a/Modules/_ctypes/clinic/_ctypes.c.h +++ b/Modules/_ctypes/clinic/_ctypes.c.h @@ -27,6 +27,28 @@ _ctypes_CType_Type___sizeof__(PyObject *self, PyTypeObject *cls, PyObject *const return _ctypes_CType_Type___sizeof___impl(self, cls); } +PyDoc_STRVAR(_ctypes_CType_Type_get_ffi_closure_containers_count__doc__, +"get_ffi_closure_containers_count($type, /)\n" +"--\n" +"\n"); + +#define _CTYPES_CTYPE_TYPE_GET_FFI_CLOSURE_CONTAINERS_COUNT_METHODDEF \ + {"get_ffi_closure_containers_count", _PyCFunction_CAST(_ctypes_CType_Type_get_ffi_closure_containers_count), METH_METHOD|METH_FASTCALL|METH_KEYWORDS|METH_CLASS, _ctypes_CType_Type_get_ffi_closure_containers_count__doc__}, + +static PyObject * +_ctypes_CType_Type_get_ffi_closure_containers_count_impl(PyTypeObject *type, + PyTypeObject *cls); + +static PyObject * +_ctypes_CType_Type_get_ffi_closure_containers_count(PyTypeObject *type, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "get_ffi_closure_containers_count() takes no arguments"); + return NULL; + } + return _ctypes_CType_Type_get_ffi_closure_containers_count_impl(type, cls); +} + PyDoc_STRVAR(CDataType_from_address__doc__, "from_address($self, value, /)\n" "--\n" @@ -607,4 +629,4 @@ Simple_from_outparm(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py } return Simple_from_outparm_impl(self, cls); } -/*[clinic end generated code: output=9c6539a3559e6088 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=21ed02205e81ab88 input=a9049054013a1b77]*/ diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 20c68134be2804..02e05622fd920b 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -39,6 +39,24 @@ #include // 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; @@ -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; @@ -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 diff --git a/Modules/_ctypes/malloc_closure.c b/Modules/_ctypes/malloc_closure.c index bb4f8f21bd3f77..c954d899ed2720 100644 --- a/Modules/_ctypes/malloc_closure.c +++ b/Modules/_ctypes/malloc_closure.c @@ -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 @@ -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 @@ -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 @@ -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); diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 65f94e50e1bd7d..6a62773e8ffc1e 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -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 - @@ -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 -