You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[FEAT][Python] Tie Python wrapper lifetime to underlying C++ FFI object
Bind one Python wrapper to one C++ FFI object ("chandle") for the object's
lifetime, so identity is stable: `a.x is a.x`, `id(a.x)` stable across
drop+refetch, and `f(x) is x` for FFI returns. Works on both the GIL and
free-threaded (3.14t) builds.
Allocation layout:
- Two-layer custom-allocator hook in core libtvm_ffi (TVMFFIObjectAllocHeader +
TVMFFICustomAllocator registry); frontends override the process-wide default.
- The Cython module installs TVMFFIPyAllocate, prepending a 16-byte
PyCustomAllocHeader that encodes the wrapper binding. Rust and the reflection
dataclass path route through the same registry.
Binding state machine (python/tvm_ffi/cython/tvm_ffi_python_object.h):
- A tagged word (tagged_pyobj) holds the wrapper back-pointer plus low tag bits:
Detached / Active / Inactive / InTransit. Every FFI return funnels through
make_ret_object, reviving an Inactive cached allocation in place for a stable
id(). Cache-vs-free handshake across tp_dealloc / tp_free / delete_space.
- Free-threaded build: the word doubles as a per-word spin-lock (raw atomics in
namespace pyobj_detail); make_ret holds the lock across its alloc (InTransit is
the dealloc handshake's baton only). Active-hit revival uses PyUnstable_TryIncRef.
GIL build is byte-identical (all FT machinery behind #ifdef Py_GIL_DISABLED).
Also: drop cp38 from the cibuildwheel matrix (requires-python is >=3.9).
Test plan: new tests/python/test_pyobject_tying.py (Active/Inactive/InTransit
transitions, cache-on aliasing, _move(), pickle/threading/GC stress, finalizer
exclusion, free-threaded carrier stress). Full suite passes on 3.13 (GIL) and
3.14t (free-threaded).
0 commit comments