diff --git a/hpy/debug/src/autogen_debug_ctx_init.h b/hpy/debug/src/autogen_debug_ctx_init.h index d3b116e28..ef3db6e84 100644 --- a/hpy/debug/src/autogen_debug_ctx_init.h +++ b/hpy/debug/src/autogen_debug_ctx_init.h @@ -11,6 +11,7 @@ */ DHPy debug_ctx_Module_Create(HPyContext *dctx, HPyModuleDef *def); +void *debug_ctx_Module_GetState(HPyContext *dctx, DHPy module); DHPy debug_ctx_Dup(HPyContext *dctx, DHPy h); void debug_ctx_Close(HPyContext *dctx, DHPy h); DHPy debug_ctx_Long_FromLong(HPyContext *dctx, long value); @@ -243,6 +244,7 @@ static inline void debug_ctx_init_fields(HPyContext *dctx, HPyContext *uctx) dctx->h_TupleType = DHPy_open(dctx, uctx->h_TupleType); dctx->h_ListType = DHPy_open(dctx, uctx->h_ListType); dctx->ctx_Module_Create = &debug_ctx_Module_Create; + dctx->ctx_Module_GetState = &debug_ctx_Module_GetState; dctx->ctx_Dup = &debug_ctx_Dup; dctx->ctx_Close = &debug_ctx_Close; dctx->ctx_Long_FromLong = &debug_ctx_Long_FromLong; diff --git a/hpy/debug/src/autogen_debug_wrappers.c b/hpy/debug/src/autogen_debug_wrappers.c index 606bbbcef..fa65f1359 100644 --- a/hpy/debug/src/autogen_debug_wrappers.c +++ b/hpy/debug/src/autogen_debug_wrappers.c @@ -17,6 +17,11 @@ DHPy debug_ctx_Module_Create(HPyContext *dctx, HPyModuleDef *def) return DHPy_open(dctx, HPyModule_Create(get_info(dctx)->uctx, def)); } +void *debug_ctx_Module_GetState(HPyContext *dctx, DHPy module) +{ + return HPyModule_GetState(get_info(dctx)->uctx, DHPy_unwrap(dctx, module)); +} + DHPy debug_ctx_Dup(HPyContext *dctx, DHPy h) { return DHPy_open(dctx, HPy_Dup(get_info(dctx)->uctx, DHPy_unwrap(dctx, h))); diff --git a/hpy/debug/src/debug_ctx_cpython.c b/hpy/debug/src/debug_ctx_cpython.c index 7a4798c5a..fa4701f44 100644 --- a/hpy/debug/src/debug_ctx_cpython.c +++ b/hpy/debug/src/debug_ctx_cpython.c @@ -24,6 +24,7 @@ #include #include "debug_internal.h" #include "hpy/runtime/ctx_type.h" // for call_traverseproc_from_trampoline +#include "hpy/runtime/ctx_module.h" // for call_mod_traverseproc_from_trampoline #include "handles.h" // for _py2h and _h2py #if defined(_MSC_VER) # include /* for alloca() */ @@ -186,6 +187,13 @@ void debug_ctx_CallRealFunctionFromTrampoline(HPyContext *dctx, a->visit, a->arg); return; } + case HPyFunc_MODTRAVERSEPROC: { + HPyFunc_traverseproc f = (HPyFunc_traverseproc)func; + _HPyFunc_args_TRAVERSEPROC *a = (_HPyFunc_args_TRAVERSEPROC*)args; + a->result = call_mod_traverseproc_from_trampoline(f, a->self, + a->visit, a->arg); + return; + } #include "autogen_debug_ctx_call.i" default: Py_FatalError("Unsupported HPyFunc_Signature in debug_ctx_cpython.c"); diff --git a/hpy/devel/include/hpy.h b/hpy/devel/include/hpy.h index 4f063e374..fd788795d 100644 --- a/hpy/devel/include/hpy.h +++ b/hpy/devel/include/hpy.h @@ -168,6 +168,7 @@ typedef struct _HPyContext_s HPyContext; // CPython-ABI # include "hpy/runtime/ctx_funcs.h" # include "hpy/runtime/ctx_type.h" +# include "hpy/runtime/ctx_module.h" # include "hpy/cpython/misc.h" # include "hpy/cpython/autogen_api_impl.h" #endif diff --git a/hpy/devel/include/hpy/autogen_hpyslot.h b/hpy/devel/include/hpy/autogen_hpyslot.h index bc07ec2ec..7a8bace71 100644 --- a/hpy/devel/include/hpy/autogen_hpyslot.h +++ b/hpy/devel/include/hpy/autogen_hpyslot.h @@ -66,6 +66,8 @@ typedef enum { HPy_nb_inplace_matrix_multiply = 76, HPy_tp_finalize = 80, HPy_tp_destroy = 1000, + HPy_m_traverse = 1001, + HPy_m_destroy = 1002, } HPySlot_Slot; #define _HPySlot_SIG__HPy_bf_getbuffer HPyFunc_GETBUFFERPROC @@ -123,3 +125,5 @@ typedef enum { #define _HPySlot_SIG__HPy_nb_inplace_matrix_multiply HPyFunc_BINARYFUNC #define _HPySlot_SIG__HPy_tp_finalize HPyFunc_DESTRUCTOR #define _HPySlot_SIG__HPy_tp_destroy HPyFunc_DESTROYFUNC +#define _HPySlot_SIG__HPy_m_traverse HPyFunc_MODTRAVERSEPROC +#define _HPySlot_SIG__HPy_m_destroy HPyFunc_MODDESTROYFUNC diff --git a/hpy/devel/include/hpy/cpython/hpyfunc_trampolines.h b/hpy/devel/include/hpy/cpython/hpyfunc_trampolines.h index a23893a8e..6e463d0b5 100644 --- a/hpy/devel/include/hpy/cpython/hpyfunc_trampolines.h +++ b/hpy/devel/include/hpy/cpython/hpyfunc_trampolines.h @@ -68,6 +68,10 @@ typedef int (*_HPyCFunction_INITPROC)(HPyContext*, HPy, HPy *, HPy_ssize_t, HPy) #define _HPyFunc_TRAMPOLINE_HPyFunc_DESTROYFUNC(SYM, IMPL) \ static void SYM(void) { abort(); } +/* special case: the same for HPy_m_destroy for module state */ +#define _HPyFunc_TRAMPOLINE_HPyFunc_MODDESTROYFUNC(SYM, IMPL) \ + static void SYM(void) { abort(); } + /* this needs to be written manually because HPy has a different type for "op": HPy_RichCmpOp instead of int */ typedef HPy (*_HPyCFunction_RICHCMPFUNC)(HPyContext *, HPy, HPy, int); @@ -107,4 +111,11 @@ typedef int (*_HPyCFunction_RELEASEBUFFERPROC)(HPyContext *, HPy, HPy_buffer *); visit, arg); \ } +#define _HPyFunc_TRAMPOLINE_HPyFunc_MODTRAVERSEPROC(SYM, IMPL) \ + static int SYM(cpy_PyObject *self, cpy_visitproc visit, void *arg) \ + { \ + return call_mod_traverseproc_from_trampoline((HPyFunc_traverseproc)IMPL, self, \ + visit, arg); \ + } + #endif // HPY_CPYTHON_HPYFUNC_TRAMPOLINES_H diff --git a/hpy/devel/include/hpy/cpython/misc.h b/hpy/devel/include/hpy/cpython/misc.h index d2befe9c6..d6ac24887 100644 --- a/hpy/devel/include/hpy/cpython/misc.h +++ b/hpy/devel/include/hpy/cpython/misc.h @@ -284,6 +284,11 @@ HPyAPI_FUNC HPy HPyModule_Create(HPyContext *ctx, HPyModuleDef *mdef) return ctx_Module_Create(ctx, mdef); } +HPyAPI_FUNC void* HPyModule_GetState(HPyContext *ctx, HPy mod) +{ + return ctx_Module_GetState(ctx, mod); +} + HPyAPI_FUNC HPy HPyType_FromSpec(HPyContext *ctx, HPyType_Spec *spec, HPyType_SpecParam *params) { return ctx_Type_FromSpec(ctx, spec, params); diff --git a/hpy/devel/include/hpy/hpyfunc.h b/hpy/devel/include/hpy/hpyfunc.h index 700b274fc..a79a1853e 100644 --- a/hpy/devel/include/hpy/hpyfunc.h +++ b/hpy/devel/include/hpy/hpyfunc.h @@ -39,6 +39,8 @@ typedef enum { HPyFunc_OBJOBJPROC, HPyFunc_TRAVERSEPROC, HPyFunc_DESTRUCTOR, + HPyFunc_MODTRAVERSEPROC, + HPyFunc_MODDESTROYFUNC, } HPyFunc_Signature; @@ -109,6 +111,10 @@ typedef int (*HPyFunc_visitproc)(HPyField *, void *); #include "autogen_hpyfunc_declare.h" +// Few special cases not handled by the autogen: +#define _HPyFunc_DECLARE_HPyFunc_MODTRAVERSEPROC(SYM) static int SYM(void *object, HPyFunc_visitproc visit, void *arg) +#define _HPyFunc_DECLARE_HPyFunc_MODDESTROYFUNC(SYM) static void SYM(void *object) + #ifdef HPY_UNIVERSAL_ABI # include "universal/hpyfunc_trampolines.h" # include "universal/autogen_hpyfunc_trampolines.h" diff --git a/hpy/devel/include/hpy/runtime/ctx_funcs.h b/hpy/devel/include/hpy/runtime/ctx_funcs.h index dee5f214f..b77fd1c38 100644 --- a/hpy/devel/include/hpy/runtime/ctx_funcs.h +++ b/hpy/devel/include/hpy/runtime/ctx_funcs.h @@ -26,6 +26,8 @@ _HPy_HIDDEN void ctx_ListBuilder_Cancel(HPyContext *ctx, HPyListBuilder builder) // ctx_module.c _HPy_HIDDEN HPy ctx_Module_Create(HPyContext *ctx, HPyModuleDef *hpydef); +// ctx_bytes.c +_HPy_HIDDEN void* ctx_Module_GetState(HPyContext *ctx, HPy mod); // ctx_object.c _HPy_HIDDEN void ctx_Dump(HPyContext *ctx, HPy h); diff --git a/hpy/devel/include/hpy/runtime/ctx_module.h b/hpy/devel/include/hpy/runtime/ctx_module.h new file mode 100644 index 000000000..414078f03 --- /dev/null +++ b/hpy/devel/include/hpy/runtime/ctx_module.h @@ -0,0 +1,13 @@ +#ifndef HPY_COMMON_RUNTIME_CTX_MODULE_H +#define HPY_COMMON_RUNTIME_CTX_MODULE_H + +#include +#include "hpy.h" +#include "hpy/hpytype.h" + +_HPy_HIDDEN int call_mod_traverseproc_from_trampoline(HPyFunc_traverseproc tp_traverse, + PyObject *self, + cpy_visitproc cpy_visit, + void *cpy_arg); + +#endif /* HPY_COMMON_RUNTIME_CTX_MODULE_H */ diff --git a/hpy/devel/include/hpy/runtime/ctx_type.h b/hpy/devel/include/hpy/runtime/ctx_type.h index a851b60ac..9e2063543 100644 --- a/hpy/devel/include/hpy/runtime/ctx_type.h +++ b/hpy/devel/include/hpy/runtime/ctx_type.h @@ -8,6 +8,11 @@ _HPy_HIDDEN PyMethodDef *create_method_defs(HPyDef *hpydefs[], PyMethodDef *legacy_methods); +_HPy_HIDDEN int call_struct_traverseproc_from_trampoline(HPyFunc_traverseproc tp_traverse, + void *struct_data, + cpy_visitproc cpy_visit, + void *cpy_arg); + _HPy_HIDDEN int call_traverseproc_from_trampoline(HPyFunc_traverseproc tp_traverse, PyObject *self, cpy_visitproc cpy_visit, diff --git a/hpy/devel/include/hpy/universal/autogen_ctx.h b/hpy/devel/include/hpy/universal/autogen_ctx.h index fab59173c..97702ed37 100644 --- a/hpy/devel/include/hpy/universal/autogen_ctx.h +++ b/hpy/devel/include/hpy/universal/autogen_ctx.h @@ -92,6 +92,7 @@ struct _HPyContext_s { HPy h_TupleType; HPy h_ListType; HPy (*ctx_Module_Create)(HPyContext *ctx, HPyModuleDef *def); + void *(*ctx_Module_GetState)(HPyContext *ctx, HPy module); HPy (*ctx_Dup)(HPyContext *ctx, HPy h); void (*ctx_Close)(HPyContext *ctx, HPy h); HPy (*ctx_Long_FromLong)(HPyContext *ctx, long value); diff --git a/hpy/devel/include/hpy/universal/autogen_trampolines.h b/hpy/devel/include/hpy/universal/autogen_trampolines.h index 75a9c736c..39e227fe7 100644 --- a/hpy/devel/include/hpy/universal/autogen_trampolines.h +++ b/hpy/devel/include/hpy/universal/autogen_trampolines.h @@ -14,6 +14,10 @@ HPyAPI_FUNC HPy HPyModule_Create(HPyContext *ctx, HPyModuleDef *def) { return ctx->ctx_Module_Create ( ctx, def ); } +HPyAPI_FUNC void *HPyModule_GetState(HPyContext *ctx, HPy module) { + return ctx->ctx_Module_GetState ( ctx, module ); +} + HPyAPI_FUNC HPy HPy_Dup(HPyContext *ctx, HPy h) { return ctx->ctx_Dup ( ctx, h ); } diff --git a/hpy/devel/include/hpy/universal/hpyfunc_trampolines.h b/hpy/devel/include/hpy/universal/hpyfunc_trampolines.h index e69146fa7..7ebaebb5a 100644 --- a/hpy/devel/include/hpy/universal/hpyfunc_trampolines.h +++ b/hpy/devel/include/hpy/universal/hpyfunc_trampolines.h @@ -100,6 +100,9 @@ typedef struct { #define _HPyFunc_TRAMPOLINE_HPyFunc_DESTROYFUNC(SYM, IMPL) \ static void SYM(void) { abort(); } +/* special case: the same for HPy_m_destroy for module state */ +#define _HPyFunc_TRAMPOLINE_HPyFunc_MODDESTROYFUNC(SYM, IMPL) \ + static void SYM(void) { abort(); } /* this needs to be written manually because HPy has a different type for "op": HPy_RichCmpOp instead of int */ @@ -160,6 +163,15 @@ typedef struct { return a.result; \ } +#define _HPyFunc_TRAMPOLINE_HPyFunc_MODTRAVERSEPROC(SYM, IMPL) \ + static int SYM(cpy_PyObject *self, cpy_visitproc visit, void *arg) \ + { \ + _HPyFunc_args_TRAVERSEPROC a = { self, visit, arg }; \ + _HPy_CallRealFunctionFromTrampoline( \ + _ctx_for_trampolines, HPyFunc_MODTRAVERSEPROC, (HPyCFunction)IMPL, &a); \ + return a.result; \ + } + #endif // HPY_UNIVERSAL_HPYFUNC_TRAMPOLINES_H diff --git a/hpy/devel/src/runtime/ctx_module.c b/hpy/devel/src/runtime/ctx_module.c index e050147e3..9267a343b 100644 --- a/hpy/devel/src/runtime/ctx_module.c +++ b/hpy/devel/src/runtime/ctx_module.c @@ -11,6 +11,53 @@ static PyModuleDef empty_moduledef = { PyModuleDef_HEAD_INIT }; +/* This is a hack similar to what we do with types: + We need some extra space to store our extra slots on + the modules, the actual user data will be appended + to this struct which we must align by using the union + at the end. +*/ +typedef struct { + HPyFunc_traverseproc m_traverse_impl; + HPyFunc_destroyfunc m_destroy_impl; + union { + unsigned char user_data_payload[1]; + max_align_t _align; + }; +} HPyMod_HEAD_t; + +#define HPyMod_HEAD_SIZE (offsetof(HPyMod_HEAD_t, user_data_payload)) + +static int _decref_visitor(HPyField *pf, void *arg) +{ + PyObject *old_object = _hf2py(*pf); + *pf = HPyField_NULL; + Py_XDECREF(old_object); + return 0; +} + +static int hpymod_clear(PyObject *self) +{ + HPyMod_HEAD_t *extra = (HPyMod_HEAD_t*) PyModule_GetState(self); + extra->m_traverse_impl(&extra->user_data_payload, _decref_visitor, NULL); + return 0; +} + +static void hpymod_free(void *self) +{ + HPyMod_HEAD_t *extra = (HPyMod_HEAD_t*) PyModule_GetState((PyObject*) self); + if (extra->m_traverse_impl) + extra->m_traverse_impl(&extra->user_data_payload, _decref_visitor, NULL); + if (extra->m_destroy_impl) + extra->m_destroy_impl(&extra->user_data_payload); +} + +_HPy_HIDDEN void* +ctx_Module_GetState(HPyContext *ctx, HPy h_mod) { + HPyMod_HEAD_t *internal = (HPyMod_HEAD_t *) PyModule_GetState(_h2py(h_mod)); + return internal ? &internal->user_data_payload : NULL; +} + _HPy_HIDDEN HPy ctx_Module_Create(HPyContext *ctx, HPyModuleDef *hpydef) { @@ -27,12 +74,63 @@ ctx_Module_Create(HPyContext *ctx, HPyModuleDef *hpydef) memcpy(def, &empty_moduledef, sizeof(PyModuleDef)); def->m_name = hpydef->name; def->m_doc = hpydef->doc; - def->m_size = hpydef->size; def->m_methods = create_method_defs(hpydef->defines, hpydef->legacy_methods); + + HPyMod_HEAD_t extra = { .m_traverse_impl = NULL, .m_destroy_impl = NULL }; + bool needs_state = hpydef->size != -1; + if (hpydef->defines != NULL) { + for (int i = 0; hpydef->defines[i] != NULL; i++) { + HPyDef *src = hpydef->defines[i]; + if (src->kind != HPyDef_Kind_Slot) + continue; + if (src->slot.slot == HPy_m_destroy) { + extra.m_destroy_impl = (HPyFunc_destroyfunc) src->slot.impl; + // No trampoline, this is HPy specific slot not called by + // CPython runtime, but by us in hpymod_free + continue; + } + if (src->slot.slot == HPy_m_traverse) { + def->m_traverse = (traverseproc) src->slot.cpy_trampoline; + extra.m_traverse_impl = (HPyFunc_traverseproc) src->slot.impl; + def->m_clear = &hpymod_clear; + } else { + const size_t buffer_size = 256; + char buffer[buffer_size]; + snprintf(buffer, buffer_size, "Unsupported slot number %d for module '%s'", src->slot.slot, hpydef->name); + HPy_FatalError(ctx, buffer); + } + } + } + + if (needs_state) { + // The assumption is such that most of the time if there is a module + // state, then there will be some HPyFields to traverse, so we always + // allocate our custom state. This way HPyModule_GetState can assume + // that state is always HPyMod_Extra_t. + def->m_size = HPyMod_HEAD_SIZE + hpydef->size; + def->m_free = &hpymod_free; + } + if (def->m_methods == NULL) { PyMem_Free(def); return HPy_NULL; } PyObject *result = PyModule_Create(def); + if (needs_state) { + HPyMod_HEAD_t *state = (HPyMod_HEAD_t*) PyModule_GetState(result); + state->m_traverse_impl = extra.m_traverse_impl; + state->m_destroy_impl = extra.m_destroy_impl; + } return _py2h(result); } + +_HPy_HIDDEN int call_mod_traverseproc_from_trampoline(HPyFunc_traverseproc tp_traverse, + PyObject *self, + cpy_visitproc cpy_visit, + void *cpy_arg) +{ + return call_struct_traverseproc_from_trampoline(tp_traverse, + &((HPyMod_HEAD_t*) PyModule_GetState(self))->user_data_payload, + cpy_visit, + cpy_arg); +} \ No newline at end of file diff --git a/hpy/devel/src/runtime/ctx_type.c b/hpy/devel/src/runtime/ctx_type.c index 18df29c88..1802a8600 100644 --- a/hpy/devel/src/runtime/ctx_type.c +++ b/hpy/devel/src/runtime/ctx_type.c @@ -921,11 +921,22 @@ static int hpy2cpy_visit(HPyField *f, void *v_args) return cpy_visit(cpy_obj, cpy_arg); } +_HPy_HIDDEN int call_struct_traverseproc_from_trampoline(HPyFunc_traverseproc tp_traverse, + void *struct_data, + cpy_visitproc cpy_visit, + void *cpy_arg) +{ + hpy2cpy_visit_args_t args = { cpy_visit, cpy_arg }; + return tp_traverse(struct_data, hpy2cpy_visit, &args); +} + _HPy_HIDDEN int call_traverseproc_from_trampoline(HPyFunc_traverseproc tp_traverse, PyObject *self, cpy_visitproc cpy_visit, void *cpy_arg) { - hpy2cpy_visit_args_t args = { cpy_visit, cpy_arg }; - return tp_traverse(_pyobj_as_struct(self), hpy2cpy_visit, &args); + return call_struct_traverseproc_from_trampoline(tp_traverse, + _pyobj_as_struct(self), + cpy_visit, + cpy_arg); } diff --git a/hpy/tools/autogen/conf.py b/hpy/tools/autogen/conf.py index 6d6b55449..27db10930 100644 --- a/hpy/tools/autogen/conf.py +++ b/hpy/tools/autogen/conf.py @@ -113,4 +113,5 @@ 'HPy_ReenterPythonExecution': 'PyEval_RestoreThread', 'HPyGlobal_Load': None, 'HPyGlobal_Store': None, + 'HPyModule_GetState': None, } diff --git a/hpy/tools/autogen/public_api.h b/hpy/tools/autogen/public_api.h index 9b48e64ec..ebf52e40e 100644 --- a/hpy/tools/autogen/public_api.h +++ b/hpy/tools/autogen/public_api.h @@ -114,6 +114,8 @@ HPy h_TupleType; /* built-in 'tuple' */ HPy h_ListType; /* built-in 'list' */ HPy HPyModule_Create(HPyContext *ctx, HPyModuleDef *def); +void* HPyModule_GetState(HPyContext *ctx, HPy module); + HPy HPy_Dup(HPyContext *ctx, HPy h); void HPy_Close(HPyContext *ctx, HPy h); @@ -586,5 +588,7 @@ typedef enum { /* extra HPy slots */ HPy_tp_destroy = SLOT(1000, HPyFunc_DESTROYFUNC), + HPy_m_traverse = SLOT(1001, HPyFunc_MODTRAVERSEPROC), + HPy_m_destroy = SLOT(1002, HPyFunc_MODDESTROYFUNC), } HPySlot_Slot; diff --git a/hpy/universal/src/autogen_ctx_def.h b/hpy/universal/src/autogen_ctx_def.h index 6bb7ac47a..0a5c3aa65 100644 --- a/hpy/universal/src/autogen_ctx_def.h +++ b/hpy/universal/src/autogen_ctx_def.h @@ -16,6 +16,7 @@ struct _HPyContext_s g_universal_ctx = { .ctx_version = 1, /* h_None & co. are initialized by init_universal_ctx() */ .ctx_Module_Create = &ctx_Module_Create, + .ctx_Module_GetState = &ctx_Module_GetState, .ctx_Dup = &ctx_Dup, .ctx_Close = &ctx_Close, .ctx_Long_FromLong = &ctx_Long_FromLong, diff --git a/hpy/universal/src/ctx_meth.c b/hpy/universal/src/ctx_meth.c index 1c535c72a..7ebb17726 100644 --- a/hpy/universal/src/ctx_meth.c +++ b/hpy/universal/src/ctx_meth.c @@ -1,6 +1,7 @@ #include #include "ctx_meth.h" #include "hpy/runtime/ctx_type.h" +#include "hpy/runtime/ctx_module.h" #include "handles.h" static void _buffer_h2py(HPyContext *ctx, const HPy_buffer *src, Py_buffer *dest) @@ -113,6 +114,13 @@ ctx_CallRealFunctionFromTrampoline(HPyContext *ctx, HPyFunc_Signature sig, a->visit, a->arg); return; } + case HPyFunc_MODTRAVERSEPROC: { + HPyFunc_traverseproc f = (HPyFunc_traverseproc)func; + _HPyFunc_args_TRAVERSEPROC *a = (_HPyFunc_args_TRAVERSEPROC*)args; + a->result = call_mod_traverseproc_from_trampoline(f, a->self, + a->visit, a->arg); + return; + } #include "autogen_ctx_call.i" default: Py_FatalError("Unsupported HPyFunc_Signature in ctx_meth.c"); diff --git a/test/support.py b/test/support.py index 389b92420..11af76fb8 100644 --- a/test/support.py +++ b/test/support.py @@ -378,6 +378,15 @@ def supports_refcounts(self): """ return sys.implementation.name == "cpython" + def supports_gc_module(self): + """ Returns True if the underlying Python implementation supports + the gc module and its functions such as gc.get_referents. + + By default returns True on CPython and False on other + implementations. + """ + return sys.implementation.name == "cpython" + def supports_ordinary_make_module_imports(self): """ Returns True if `.make_module(...)` loads modules using a standard Python import mechanism (e.g. `importlib.import_module`). diff --git a/test/test_hpymodule.py b/test/test_hpymodule.py index 0b536eefb..674c6d7c9 100644 --- a/test/test_hpymodule.py +++ b/test/test_hpymodule.py @@ -24,3 +24,122 @@ def test_HPyModule_Create(self): assert m.__spec__ is None assert set(vars(m).keys()) == { '__name__', '__doc__', '__package__', '__loader__', '__spec__'} + + def test_HPyModule_GetState(self): + mod = self.make_module(""" + #include + + typedef struct { + HPyField field; + char *rawData; + } foo_data_t; + + HPyDef_SLOT(foo_traverse, foo_traverse_impl, HPy_m_traverse) + static int foo_traverse_impl(void *self, HPyFunc_visitproc visit, void *arg) + { + foo_data_t *data = (foo_data_t *)self; + HPy_VISIT(&data->field); + return 0; + } + + HPyDef_SLOT(foo_destroy, foo_destroy_impl, HPy_m_destroy) + static void foo_destroy_impl(void *self) + { + foo_data_t *data = (foo_data_t *)self; + free(data->rawData); + } + + HPyDef_METH(bar, "bar", bar_impl, HPyFunc_NOARGS) + static HPy bar_impl(HPyContext *ctx, HPy self) + { + foo_data_t *data = (foo_data_t *)HPyModule_GetState(ctx, self); + return HPyUnicode_FromString(ctx, data->rawData); + } + + HPyDef_METH(getf, "getf", getf_impl, HPyFunc_NOARGS) + static HPy getf_impl(HPyContext *ctx, HPy self) + { + foo_data_t *data = (foo_data_t *)HPyModule_GetState(ctx, self); + return HPyField_Load(ctx, self, data->field); + } + + static HPyDef *foo_defines[] = { + &bar, + &getf, + NULL, + NULL, + NULL + }; + + HPyDef_METH(f, "f", f_impl, HPyFunc_VARARGS) + static HPy f_impl(HPyContext *ctx, HPy self, HPy *args, HPy_ssize_t nargs) + { + HPy field_value = args[0]; + bool implement_traverse = HPyLong_AsLong(ctx, args[1]); + bool implement_destroy = HPyLong_AsLong(ctx, args[2]); + + // Dynamically add traverse and/or destroy to foo_defines + foo_defines[2] = NULL; + foo_defines[3] = NULL; + size_t next_defines_idx = 2; + if (implement_traverse) + foo_defines[next_defines_idx++] = &foo_traverse; + if (implement_destroy) + foo_defines[next_defines_idx++] = &foo_destroy; + + HPyModuleDef def = { + .name = "foo", + .size = sizeof(foo_data_t), + .defines = foo_defines, + }; + HPy module = HPyModule_Create(ctx, &def); + foo_data_t *data = (foo_data_t *)HPyModule_GetState(ctx, module); + + // Example of dynamically allocated native memory + if (implement_destroy) { + data->rawData = malloc(4); + memcpy(data->rawData, "ABQ", 4); + } + + // Example of HPyField + if (implement_traverse) { + data->field = HPyField_NULL; + HPyField_Store(ctx, self, &data->field, field_value); + } + + return module; + } + + @EXPORT(f) + @INIT + """) + + def test_with(traverse, destroy): + obj = {'some': 'object'} + m = mod.f(obj, traverse, destroy) + if destroy: + assert m.bar() == "ABQ" + if traverse: + assert m.getf() is obj + + # Check that tp_traverse visits the object + if traverse and self.supports_gc_module(): + import gc + referents = gc.get_referents(m) + assert obj in referents + + del m + # Note: it seems that CPython cannot really remove the module. + # According to gc.get_referrers is referenced by the builtin functions + # located in it, and it seems that this cycle is not detected? + # if self.supports_refcounts(): + # from sys import getrefcount + # obj_refcnt = getrefcount(obj) + # del m + # gc.collect() + # assert getrefcount(obj) == obj_refcnt - 1 + + test_with(traverse=True, destroy=True) + test_with(traverse=False, destroy=True) + test_with(traverse=True, destroy=False) + test_with(traverse=False, destroy=False) \ No newline at end of file