@@ -50,25 +50,42 @@ int jitgen_traverse(PyObject* obj, visitproc visit, void* arg) {
5050 JitGenObject* jit_gen = JitGenObject::cast (obj);
5151 if (jit_gen != nullptr ) {
5252 const GenDataFooter* gen_footer = jit_gen->genDataFooter ();
53- if (gen_footer->yieldPoint == nullptr ) {
54- return 0 ;
55- }
56- size_t deopt_idx = gen_footer->yieldPoint ->deoptIdx ();
57- const DeoptMetadata& meta =
58- gen_footer->code_rt ->getDeoptMetadata (deopt_idx);
59- for (const LiveValue& value : meta.live_values ) {
60- if (value.ref_kind != hir::RefKind::kOwned ) {
61- continue ;
53+ // Only visit JIT-specific live values if we have a valid yield point.
54+ // If yieldPoint is null, the generator hasn't yielded yet or has completed,
55+ // but we still need to call PyGen_Type.tp_traverse below to visit standard
56+ // generator references (gi_code, gi_frame, etc.).
57+ if (gen_footer->yieldPoint != nullptr ) {
58+ size_t deopt_idx = gen_footer->yieldPoint ->deoptIdx ();
59+ const DeoptMetadata& meta =
60+ gen_footer->code_rt ->getDeoptMetadata (deopt_idx);
61+ for (const LiveValue& value : meta.live_values ) {
62+ if (value.ref_kind != hir::RefKind::kOwned ) {
63+ continue ;
64+ }
65+ codegen::PhyLocation loc = value.location ;
66+ JIT_CHECK (
67+ !loc.is_register (),
68+ " DeoptMetadata for Yields should not reference registers" );
69+ PyObject* v = *reinterpret_cast <PyObject**>(
70+ reinterpret_cast <uintptr_t >(gen_footer) + loc.loc );
71+ Py_VISIT (v);
6272 }
63- codegen::PhyLocation loc = value.location ;
64- JIT_CHECK (
65- !loc.is_register (),
66- " DeoptMetadata for Yields should not reference registers" );
67- PyObject* v = *reinterpret_cast <PyObject**>(
68- reinterpret_cast <uintptr_t >(gen_footer) + loc.loc );
69- Py_VISIT (v);
73+ JIT_CHECK (JitGen_CheckAny (obj), " Deopted during GC traversal" );
7074 }
71- JIT_CHECK (JitGen_CheckAny (obj), " Deopted during GC traversal" );
75+
76+ #if PY_VERSION_HEX < 0x030E0000
77+ // In lightweight frame mode, frame->f_funcobj is set to a reifier singleton
78+ // rather than the actual function. The real function is stored in the
79+ // FrameHeader and contains func_closure with closure cells that may
80+ // participate in reference cycles. We must visit it explicitly since
81+ // _PyFrame_Traverse won't see it.
82+ if (getConfig ().frame_mode == FrameMode::kLightweight &&
83+ jit_gen->gi_frame_state < FRAME_CLEARED) {
84+ _PyInterpreterFrame* frame = generatorFrame (jit_gen);
85+ BorrowedRef<PyFunctionObject> func = jitFrameGetFunction (frame);
86+ Py_VISIT (func.get ());
87+ }
88+ #endif
7289 }
7390 // Try to use CPython traverse as much as we can as it has internals which
7491 // are hard to borrow in 3.14 (compares 'visit' to a speicifc internal
@@ -337,6 +354,26 @@ void jitgen_finalize(PyObject* obj) {
337354 PyGen_Type.tp_finalize (obj);
338355}
339356
357+ int jitgen_clear (PyObject* obj) {
358+ // tp_clear is called by the cyclic GC to break reference cycles.
359+ // We need to deopt the JIT generator so that the standard generator
360+ // tp_clear can properly clear all references.
361+ //
362+ // If the generator is currently executing (gi_frame_state ==
363+ // FRAME_EXECUTING), we cannot deopt it. In that case, return 0 without
364+ // clearing - the GC will try again later or the generator will complete and
365+ // be collected normally.
366+ if (!deopt_jit_gen (obj)) {
367+ return 0 ;
368+ }
369+ // After deopting, the object is now a regular PyGenObject/PyCoroObject.
370+ // Delegate to the base type's tp_clear to break cycles.
371+ if (PyGen_Type.tp_clear != nullptr ) {
372+ return PyGen_Type.tp_clear (obj);
373+ }
374+ return 0 ;
375+ }
376+
340377typedef struct {
341378 PyObject_HEAD
342379 PyObject* cw_coroutine;
@@ -600,6 +637,7 @@ static PyAsyncMethods jitcoro_as_async = {
600637PyType_Slot gen_slots[] = {
601638 {Py_tp_dealloc, reinterpret_cast <void *>(jitgen_dealloc)},
602639 {Py_tp_traverse, reinterpret_cast <void *>(jitgen_traverse)},
640+ {Py_tp_clear, reinterpret_cast <void *>(jitgen_clear)},
603641 {Py_tp_finalize, reinterpret_cast <void *>(jitgen_finalize)},
604642 {Py_tp_iter, reinterpret_cast <void *>(PyObject_SelfIter)},
605643 {Py_tp_iternext, reinterpret_cast <void *>(jitgen_iternext)},
@@ -635,6 +673,7 @@ static_assert(sizeof(PyGenObject) == sizeof(PyCoroObject));
635673PyType_Slot coro_slots[] = {
636674 {Py_tp_dealloc, reinterpret_cast <void *>(jitgen_dealloc)},
637675 {Py_tp_traverse, reinterpret_cast <void *>(jitgen_traverse)},
676+ {Py_tp_clear, reinterpret_cast <void *>(jitgen_clear)},
638677 {Py_tp_finalize, reinterpret_cast <void *>(jitgen_finalize)},
639678 {Py_tp_methods, jitcoro_methods},
640679 {Py_tp_members, jitcoro_memberlist},
@@ -666,7 +705,16 @@ void deopt_jit_gen_object_only(JitGenObject* gen) {
666705 Py_SET_TYPE (reinterpret_cast <PyObject*>(gen), type);
667706 if (getConfig ().frame_mode == FrameMode::kLightweight ) {
668707 auto frame = generatorFrame (gen);
669- jitFrameRemoveReifier (frame);
708+ if (gen->gi_frame_state != FRAME_CLEARED) {
709+ jitFrameRemoveReifier (frame);
710+ } else if constexpr (PY_VERSION_HEX < 0x030E0000 ) {
711+ // Normally we'll clear the function via jitFrameClearExceptCode. But
712+ // a user can call clear on a reified frame object which transfers
713+ // ownership of the _PyInterpreterFrame to the PyFrameObject and marks
714+ // the generator frame as cleared. In that case we still need to decref
715+ // the function which is stored before the _PyInterpreterFrame in 3.12.
716+ Py_XDECREF (jitFrameGetFunction (frame));
717+ }
670718 }
671719}
672720
0 commit comments