Skip to content

Commit 45bc565

Browse files
committed
switch to heap types
1 parent 124785a commit 45bc565

File tree

5 files changed

+110
-30
lines changed

5 files changed

+110
-30
lines changed

src/C/consts.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,32 @@ end
324324

325325
const PyTypePtr = Ptr{PyTypeObject}
326326

327+
@kwdef struct PyType_Slot
328+
slot::Cint = 0
329+
pfunc::Ptr{Cvoid} = C_NULL
330+
end
331+
332+
@kwdef struct PyType_Spec
333+
name::Cstring = C_NULL
334+
basicsize::Cint = 0
335+
itemsize::Cint = 0
336+
flags::Cuint = 0
337+
slots::Ptr{PyType_Slot} = C_NULL
338+
end
339+
340+
# These numbers are part of the CPython stable ABI and
341+
# are guaranteed to be the same.
342+
# https://raw.githubusercontent.com/python/cpython/main/Include/typeslots.h
343+
const Py_bf_getbuffer = 1
344+
const Py_bf_releasebuffer = 2
345+
const Py_tp_alloc = 47
346+
const Py_tp_dealloc = 52
347+
const Py_tp_methods = 64
348+
const Py_tp_new = 65
349+
const Py_tp_members = 72
350+
const Py_tp_getset = 73
351+
const Py_tp_free = 74
352+
327353
@kwdef struct PySimpleObject{T}
328354
ob_base::PyObject = PyObject()
329355
value::T

src/C/extras.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,32 @@ PyType_IsSubtypeFast(t, f::Integer) =
1313
PyMemoryView_GET_BUFFER(m) = Base.GC.@preserve m Ptr{Py_buffer}(UnsafePtr{PyMemoryViewObject}(asptr(m)).view)
1414

1515
PyType_CheckBuffer(t) = Base.GC.@preserve t begin
16-
p = UnsafePtr{PyTypeObject}(asptr(t)).as_buffer[]
17-
return p != C_NULL && p.get[!] != C_NULL
16+
p = PyType_GetSlot(asptr(t), Py_bf_getbuffer)
17+
return p != C_NULL
1818
end
1919

2020
PyObject_CheckBuffer(o) = Base.GC.@preserve o PyType_CheckBuffer(Py_Type(asptr(o)))
2121

2222
PyObject_GetBuffer(_o, b, flags) = Base.GC.@preserve _o begin
2323
o = asptr(_o)
24-
p = UnsafePtr{PyTypeObject}(Py_Type(o)).as_buffer[]
25-
if p == C_NULL || p.get[!] == C_NULL
24+
getbuf = PyType_GetSlot(Py_Type(o), Py_bf_getbuffer)
25+
if getbuf == C_NULL
2626
PyErr_SetString(
2727
POINTERS.PyExc_TypeError,
2828
"a bytes-like object is required, not '$(String(UnsafePtr{PyTypeObject}(Py_Type(o)).name[]))'",
2929
)
3030
return Cint(-1)
3131
end
32-
return ccall(p.get[!], Cint, (PyPtr, Ptr{Py_buffer}, Cint), o, b, flags)
32+
return ccall(getbuf, Cint, (PyPtr, Ptr{Py_buffer}, Cint), o, b, flags)
3333
end
3434

3535
PyBuffer_Release(_b) = begin
3636
b = UnsafePtr(Base.unsafe_convert(Ptr{Py_buffer}, _b))
3737
o = b.obj[]
3838
o == C_NULL && return
39-
p = UnsafePtr{PyTypeObject}(Py_Type(o)).as_buffer[]
40-
if (p != C_NULL && p.release[!] != C_NULL)
41-
ccall(p.release[!], Cvoid, (PyPtr, Ptr{Py_buffer}), o, b)
39+
releasebuf = PyType_GetSlot(Py_Type(o), Py_bf_releasebuffer)
40+
if releasebuf != C_NULL
41+
ccall(releasebuf, Cvoid, (PyPtr, Ptr{Py_buffer}), o, b)
4242
end
4343
b.obj[] = C_NULL
4444
Py_DecRef(o)

src/C/pointers.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ 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{PyType_Spec},) => PyPtr,
81+
:PyType_FromSpecWithBases => (Ptr{PyType_Spec}, PyPtr) => PyPtr,
82+
:PyType_GetSlot => (PyPtr, Cint) => Ptr{Cvoid},
8083
# MAPPING
8184
:PyMapping_HasKeyString => (PyPtr, Ptr{Cchar}) => Cint,
8285
:PyMapping_SetItemString => (PyPtr, Ptr{Cchar}, PyPtr) => Cint,

src/JlWrap/C.jl

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ using Serialization: serialize, deserialize
99
@kwdef struct PyJuliaValueObject
1010
ob_base::C.PyObject = C.PyObject()
1111
value::Int = 0
12-
weaklist::C.PyPtr = C_NULL
1312
end
1413

1514
const PyJuliaBase_Type = Ref(C.PyNULL)
@@ -21,9 +20,9 @@ const PYJLVALUES = []
2120
const PYJLFREEVALUES = Int[]
2221

2322
function _pyjl_new(t::C.PyPtr, ::C.PyPtr, ::C.PyPtr)
24-
o = ccall(UnsafePtr{C.PyTypeObject}(t).alloc[!], C.PyPtr, (C.PyPtr, C.Py_ssize_t), t, 0)
23+
allocptr = C.PyType_GetSlot(t, C.Py_tp_alloc)
24+
o = ccall(allocptr, C.PyPtr, (C.PyPtr, C.Py_ssize_t), t, 0)
2525
o == C.PyNULL && return C.PyNULL
26-
UnsafePtr{PyJuliaValueObject}(o).weaklist[] = C.PyNULL
2726
UnsafePtr{PyJuliaValueObject}(o).value[] = 0
2827
return o
2928
end
@@ -34,8 +33,8 @@ function _pyjl_dealloc(o::C.PyPtr)
3433
PYJLVALUES[idx] = nothing
3534
push!(PYJLFREEVALUES, idx)
3635
end
37-
UnsafePtr{PyJuliaValueObject}(o).weaklist[!] == C.PyNULL || C.PyObject_ClearWeakRefs(o)
38-
ccall(UnsafePtr{C.PyTypeObject}(C.Py_Type(o)).free[!], Cvoid, (C.PyPtr,), o)
36+
freeptr = C.PyType_GetSlot(C.Py_Type(o), C.Py_tp_free)
37+
ccall(freeptr, Cvoid, (C.PyPtr,), o)
3938
nothing
4039
end
4140

@@ -269,14 +268,12 @@ function _pyjl_deserialize(t::C.PyPtr, v::C.PyPtr)
269268
end
270269

271270
const _pyjlbase_name = "juliacall.ValueBase"
272-
const _pyjlbase_type = fill(C.PyTypeObject())
273271
const _pyjlbase_isnull_name = "_jl_isnull"
274272
const _pyjlbase_callmethod_name = "_jl_callmethod"
275273
const _pyjlbase_reduce_name = "__reduce__"
276274
const _pyjlbase_serialize_name = "_jl_serialize"
277275
const _pyjlbase_deserialize_name = "_jl_deserialize"
278276
const _pyjlbase_methods = Vector{C.PyMethodDef}()
279-
const _pyjlbase_as_buffer = fill(C.PyBufferProcs())
280277

281278
function init_c()
282279
empty!(_pyjlbase_methods)
@@ -309,28 +306,37 @@ function init_c()
309306
),
310307
C.PyMethodDef(),
311308
)
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})),
315-
)
316-
_pyjlbase_type[] = C.PyTypeObject(
309+
slots = C.PyType_Slot[
310+
C.PyType_Slot(slot = C.Py_tp_dealloc,
311+
pfunc = @cfunction(_pyjl_dealloc, Cvoid, (C.PyPtr,))),
312+
C.PyType_Slot(slot = C.Py_tp_new,
313+
pfunc = @cfunction(_pyjl_new, C.PyPtr,
314+
(C.PyPtr, C.PyPtr, C.PyPtr))),
315+
C.PyType_Slot(slot = C.Py_tp_methods, pfunc = pointer(_pyjlbase_methods)),
316+
C.PyType_Slot(slot = C.Py_bf_getbuffer,
317+
pfunc = @cfunction(_pyjl_get_buffer, Cint,
318+
(C.PyPtr, Ptr{C.Py_buffer}, Cint))),
319+
C.PyType_Slot(slot = C.Py_bf_releasebuffer,
320+
pfunc = @cfunction(_pyjl_release_buffer, Cvoid,
321+
(C.PyPtr, Ptr{C.Py_buffer}))),
322+
C.PyType_Slot(),
323+
]
324+
spec = C.PyType_Spec(
317325
name = pointer(_pyjlbase_name),
318326
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,)),
327+
itemsize = 0,
322328
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),
329+
slots = pointer(slots),
328330
)
329-
o = PyJuliaBase_Type[] = C.PyPtr(pointer(_pyjlbase_type))
330-
if C.PyType_Ready(o) == -1
331+
spec_ref = Ref(spec)
332+
o = GC.@preserve spec slots _pyjlbase_methods spec_ref begin
333+
C.PyType_FromSpec(Base.unsafe_convert(Ptr{C.PyType_Spec}, spec_ref))
334+
end
335+
if o == C.PyNULL
331336
C.PyErr_Print()
332337
error("Error initializing 'juliacall.ValueBase'")
333338
end
339+
PyJuliaBase_Type[] = o
334340
end
335341

336342
function __init__()

test/HeapType.jl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
@testitem "heap type from spec" begin
2+
using PythonCall
3+
using PythonCall.C
4+
5+
function _ht_answer(self::C.PyPtr, args::C.PyPtr)
6+
return C.PyLong_FromLongLong(42)
7+
end
8+
9+
meths = C.PyMethodDef[
10+
C.PyMethodDef(
11+
name = pointer("answer"),
12+
meth = @cfunction(_ht_answer, C.PyPtr, (C.PyPtr, C.PyPtr)),
13+
flags = C.Py_METH_NOARGS,
14+
),
15+
C.PyMethodDef(),
16+
]
17+
18+
slots = C.PyType_Slot[
19+
C.PyType_Slot(slot = C.Py_tp_methods, pfunc = pointer(meths)),
20+
C.PyType_Slot(slot = C.Py_tp_new, pfunc = C.POINTERS.PyType_GenericNew),
21+
C.PyType_Slot(),
22+
]
23+
24+
spec = C.PyType_Spec(
25+
name = pointer("juliacall.HeapType"),
26+
basicsize = sizeof(C.PyObject),
27+
itemsize = 0,
28+
flags = C.Py_TPFLAGS_DEFAULT | C.Py_TPFLAGS_BASETYPE,
29+
slots = pointer(slots),
30+
)
31+
32+
spec_ref = Ref(spec)
33+
typ_ptr = GC.@preserve spec slots meths spec_ref begin
34+
C.PyType_FromSpec(Base.unsafe_convert(Ptr{C.PyType_Spec}, spec_ref))
35+
end
36+
@test typ_ptr != C.PyNULL
37+
typ = PythonCall.pynew(typ_ptr)
38+
39+
obj = pycall(typ)
40+
ans = pyconvert(Int, pycall(pygetattr(obj, "answer")))
41+
@test ans == 42
42+
43+
PythonCall.pydel!(obj)
44+
PythonCall.pydel!(typ)
45+
end

0 commit comments

Comments
 (0)