Skip to content

Commit 2cb07d1

Browse files
authored
Switch to heap types using PyType_FromSpec (#643)
* Switch to heap types using PyType_FromSpec - Convert PyJuliaBase_Type creation from manual PyTypeObject to PyType_FromSpec - Add PyType_Spec and PyType_Slot structs to C API bindings - Add all Python type slot constants from typeslots.h - Use __weaklistoffset__ member definition for older Python compatibility - Implement proper slot-based type definition with buffer protocol support Closes #641 * Clean up PyType_Spec creation - Remove unnecessary itemsize field (defaults to 0) - Remove unnecessary Cint conversion for basicsize (happens automatically) * update release notes * Remove unused _pyjlbase_as_buffer constant Buffer protocol methods are now handled through PyType_Slot mechanism, making the old _pyjlbase_as_buffer constant unnecessary. * revert --------- Co-authored-by: Christopher Doris <github.com/cjdoris>
1 parent a0d4ee8 commit 2cb07d1

File tree

4 files changed

+142
-16
lines changed

4 files changed

+142
-16
lines changed

docs/src/releasenotes.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Release Notes
22

3+
## Unreleased
4+
* Internal: Use heap-allocated types (PyType_FromSpec) to improve ABI compatibility.
5+
36
## 0.9.26 (2025-07-15)
47
* Added PySide6 support to the GUI compatibility layer.
58
* Added FAQ on interactive threads.

src/C/consts.jl

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,3 +345,104 @@ const NPY_ARRAY_ALIGNED = 0x0100
345345
const NPY_ARRAY_NOTSWAPPED = 0x0200
346346
const NPY_ARRAY_WRITEABLE = 0x0400
347347
const NPY_ARR_HAS_DESCR = 0x0800
348+
349+
# Python type slot constants
350+
# From https://github.com/python/cpython/blob/main/Include/typeslots.h
351+
const Py_bf_getbuffer = Cint(1)
352+
const Py_bf_releasebuffer = Cint(2)
353+
const Py_mp_ass_subscript = Cint(3)
354+
const Py_mp_length = Cint(4)
355+
const Py_mp_subscript = Cint(5)
356+
const Py_nb_absolute = Cint(6)
357+
const Py_nb_add = Cint(7)
358+
const Py_nb_and = Cint(8)
359+
const Py_nb_bool = Cint(9)
360+
const Py_nb_divmod = Cint(10)
361+
const Py_nb_float = Cint(11)
362+
const Py_nb_floor_divide = Cint(12)
363+
const Py_nb_index = Cint(13)
364+
const Py_nb_inplace_add = Cint(14)
365+
const Py_nb_inplace_and = Cint(15)
366+
const Py_nb_inplace_floor_divide = Cint(16)
367+
const Py_nb_inplace_lshift = Cint(17)
368+
const Py_nb_inplace_multiply = Cint(18)
369+
const Py_nb_inplace_or = Cint(19)
370+
const Py_nb_inplace_power = Cint(20)
371+
const Py_nb_inplace_remainder = Cint(21)
372+
const Py_nb_inplace_rshift = Cint(22)
373+
const Py_nb_inplace_subtract = Cint(23)
374+
const Py_nb_inplace_true_divide = Cint(24)
375+
const Py_nb_inplace_xor = Cint(25)
376+
const Py_nb_int = Cint(26)
377+
const Py_nb_invert = Cint(27)
378+
const Py_nb_lshift = Cint(28)
379+
const Py_nb_multiply = Cint(29)
380+
const Py_nb_negative = Cint(30)
381+
const Py_nb_or = Cint(31)
382+
const Py_nb_positive = Cint(32)
383+
const Py_nb_power = Cint(33)
384+
const Py_nb_remainder = Cint(34)
385+
const Py_nb_rshift = Cint(35)
386+
const Py_nb_subtract = Cint(36)
387+
const Py_nb_true_divide = Cint(37)
388+
const Py_nb_xor = Cint(38)
389+
const Py_sq_ass_item = Cint(39)
390+
const Py_sq_concat = Cint(40)
391+
const Py_sq_contains = Cint(41)
392+
const Py_sq_inplace_concat = Cint(42)
393+
const Py_sq_inplace_repeat = Cint(43)
394+
const Py_sq_item = Cint(44)
395+
const Py_sq_length = Cint(45)
396+
const Py_sq_repeat = Cint(46)
397+
const Py_tp_alloc = Cint(47)
398+
const Py_tp_base = Cint(48)
399+
const Py_tp_bases = Cint(49)
400+
const Py_tp_call = Cint(50)
401+
const Py_tp_clear = Cint(51)
402+
const Py_tp_dealloc = Cint(52)
403+
const Py_tp_del = Cint(53)
404+
const Py_tp_descr_get = Cint(54)
405+
const Py_tp_descr_set = Cint(55)
406+
const Py_tp_doc = Cint(56)
407+
const Py_tp_getattr = Cint(57)
408+
const Py_tp_getattro = Cint(58)
409+
const Py_tp_hash = Cint(59)
410+
const Py_tp_init = Cint(60)
411+
const Py_tp_is_gc = Cint(61)
412+
const Py_tp_iter = Cint(62)
413+
const Py_tp_iternext = Cint(63)
414+
const Py_tp_methods = Cint(64)
415+
const Py_tp_new = Cint(65)
416+
const Py_tp_repr = Cint(66)
417+
const Py_tp_richcompare = Cint(67)
418+
const Py_tp_setattr = Cint(68)
419+
const Py_tp_setattro = Cint(69)
420+
const Py_tp_str = Cint(70)
421+
const Py_tp_traverse = Cint(71)
422+
const Py_tp_members = Cint(72)
423+
const Py_tp_getset = Cint(73)
424+
const Py_tp_free = Cint(74)
425+
const Py_nb_matrix_multiply = Cint(75)
426+
const Py_nb_inplace_matrix_multiply = Cint(76)
427+
const Py_am_await = Cint(77)
428+
const Py_am_aiter = Cint(78)
429+
const Py_am_anext = Cint(79)
430+
const Py_tp_finalize = Cint(80)
431+
const Py_am_send = Cint(81)
432+
const Py_tp_vectorcall = Cint(82)
433+
const Py_tp_token = Cint(83)
434+
435+
# PyType_Spec and PyType_Slot structs
436+
# From https://docs.python.org/3/c-api/type.html#c.PyType_Spec
437+
@kwdef struct PyType_Slot
438+
slot::Cint = 0
439+
pfunc::Ptr{Cvoid} = C_NULL
440+
end
441+
442+
@kwdef struct PyType_Spec
443+
name::Cstring = C_NULL
444+
basicsize::Cint = 0
445+
itemsize::Cint = 0
446+
flags::Cuint = 0
447+
slots::Ptr{PyType_Slot} = C_NULL
448+
end

src/C/pointers.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ const CAPI_FUNC_SIGS = Dict{Symbol,Pair{Tuple,Type}}(
7777
:PyType_IsSubtype => (PyPtr, PyPtr) => Cint,
7878
:PyType_Ready => (PyPtr,) => Cint,
7979
:PyType_GenericNew => (PyPtr, PyPtr, PyPtr) => PyPtr,
80+
:PyType_FromSpec => (Ptr{Cvoid},) => PyPtr,
8081
# MAPPING
8182
:PyMapping_HasKeyString => (PyPtr, Ptr{Cchar}) => Cint,
8283
:PyMapping_SetItemString => (PyPtr, Ptr{Cchar}, PyPtr) => Cint,

src/JlWrap/C.jl

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -269,14 +269,16 @@ function _pyjl_deserialize(t::C.PyPtr, v::C.PyPtr)
269269
end
270270

271271
const _pyjlbase_name = "juliacall.ValueBase"
272-
const _pyjlbase_type = fill(C.PyTypeObject())
273272
const _pyjlbase_isnull_name = "_jl_isnull"
274273
const _pyjlbase_callmethod_name = "_jl_callmethod"
275274
const _pyjlbase_reduce_name = "__reduce__"
276275
const _pyjlbase_serialize_name = "_jl_serialize"
277276
const _pyjlbase_deserialize_name = "_jl_deserialize"
277+
const _pyjlbase_weaklistoffset_name = "__weaklistoffset__"
278278
const _pyjlbase_methods = Vector{C.PyMethodDef}()
279-
const _pyjlbase_as_buffer = fill(C.PyBufferProcs())
279+
const _pyjlbase_members = Vector{C.PyMemberDef}()
280+
const _pyjlbase_slots = Vector{C.PyType_Slot}()
281+
const _pyjlbase_spec = fill(C.PyType_Spec())
280282

281283
function init_c()
282284
empty!(_pyjlbase_methods)
@@ -309,25 +311,44 @@ function init_c()
309311
),
310312
C.PyMethodDef(),
311313
)
312-
_pyjlbase_as_buffer[] = C.PyBufferProcs(
313-
get = @cfunction(_pyjl_get_buffer, Cint, (C.PyPtr, Ptr{C.Py_buffer}, Cint)),
314-
release = @cfunction(_pyjl_release_buffer, Cvoid, (C.PyPtr, Ptr{C.Py_buffer})),
314+
315+
# Create members for weakref support
316+
empty!(_pyjlbase_members)
317+
push!(
318+
_pyjlbase_members,
319+
C.PyMemberDef(
320+
name = pointer(_pyjlbase_weaklistoffset_name),
321+
typ = C.Py_T_PYSSIZET,
322+
offset = fieldoffset(PyJuliaValueObject, 3),
323+
flags = C.Py_READONLY,
324+
),
325+
C.PyMemberDef(), # NULL terminator
326+
)
327+
328+
# Create slots for PyType_Spec
329+
empty!(_pyjlbase_slots)
330+
push!(
331+
_pyjlbase_slots,
332+
C.PyType_Slot(slot = C.Py_tp_new, pfunc = @cfunction(_pyjl_new, C.PyPtr, (C.PyPtr, C.PyPtr, C.PyPtr))),
333+
C.PyType_Slot(slot = C.Py_tp_dealloc, pfunc = @cfunction(_pyjl_dealloc, Cvoid, (C.PyPtr,))),
334+
C.PyType_Slot(slot = C.Py_tp_methods, pfunc = pointer(_pyjlbase_methods)),
335+
C.PyType_Slot(slot = C.Py_tp_members, pfunc = pointer(_pyjlbase_members)),
336+
C.PyType_Slot(slot = C.Py_bf_getbuffer, pfunc = @cfunction(_pyjl_get_buffer, Cint, (C.PyPtr, Ptr{C.Py_buffer}, Cint))),
337+
C.PyType_Slot(slot = C.Py_bf_releasebuffer, pfunc = @cfunction(_pyjl_release_buffer, Cvoid, (C.PyPtr, Ptr{C.Py_buffer}))),
338+
C.PyType_Slot(), # NULL terminator
315339
)
316-
_pyjlbase_type[] = C.PyTypeObject(
340+
341+
# Create PyType_Spec
342+
_pyjlbase_spec[] = C.PyType_Spec(
317343
name = pointer(_pyjlbase_name),
318344
basicsize = sizeof(PyJuliaValueObject),
319-
# new = C.POINTERS.PyType_GenericNew,
320-
new = @cfunction(_pyjl_new, C.PyPtr, (C.PyPtr, C.PyPtr, C.PyPtr)),
321-
dealloc = @cfunction(_pyjl_dealloc, Cvoid, (C.PyPtr,)),
322345
flags = C.Py_TPFLAGS_BASETYPE | C.Py_TPFLAGS_HAVE_VERSION_TAG,
323-
weaklistoffset = fieldoffset(PyJuliaValueObject, 3),
324-
# getattro = C.POINTERS.PyObject_GenericGetAttr,
325-
# setattro = C.POINTERS.PyObject_GenericSetAttr,
326-
methods = pointer(_pyjlbase_methods),
327-
as_buffer = pointer(_pyjlbase_as_buffer),
346+
slots = pointer(_pyjlbase_slots),
328347
)
329-
o = PyJuliaBase_Type[] = C.PyPtr(pointer(_pyjlbase_type))
330-
if C.PyType_Ready(o) == -1
348+
349+
# Create type using PyType_FromSpec
350+
o = PyJuliaBase_Type[] = C.PyType_FromSpec(pointer(_pyjlbase_spec))
351+
if o == C.PyNULL
331352
C.PyErr_Print()
332353
error("Error initializing 'juliacall.ValueBase'")
333354
end

0 commit comments

Comments
 (0)