Skip to content

Commit 8d30002

Browse files
committed
simplify pycall! code to eliminate argument-tuple caching … the logic was just way too complicated and hard to get right, for questionable gain
1 parent f1a6357 commit 8d30002

File tree

1 file changed

+12
-86
lines changed

1 file changed

+12
-86
lines changed

src/pyfncall.jl

Lines changed: 12 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
2-
# pyarg_tuples[i] is a pointer to a python tuple of length i-1
3-
# const pyarg_tuples = Vector{PyPtr}(32)
4-
const pyarg_tuples = PyPtr[]
5-
61
"""
72
Low-level version of `pycall!(ret, o, ...; kwargs...)`
83
Sets `ret.o` to the result of the call, and returns `ret::PyObject`
@@ -21,90 +16,22 @@ Low-level version of `pycall!(ret, o, ...)` for when `kw` is already in python
2116
friendly format but you don't have the python tuple to hold the arguments
2217
(`pyargsptr`). Sets `ret.o` to the result of the call, and returns `ret::PyObject`.
2318
"""
24-
function _pycall!(ret::PyObject, o::Union{PyObject,PyPtr}, args,
25-
nargs::Int=length(args), kw::Union{Ptr{Cvoid}, PyObject}=C_NULL)
26-
# pyarg_tuples[i] is a pointer to a python tuple of length i-1
27-
for n in length(pyarg_tuples):nargs
28-
push!(pyarg_tuples, @pycheckn ccall((@pysym :PyTuple_New), PyPtr, (Int,), n))
29-
end
30-
check_pyargsptr(nargs)
31-
pyargsptr = pyarg_tuples[nargs+1]
32-
# We temporarily set pyarg_tuples[nargs+1] to NULL to ensure that nested
33-
# calls to pycall don't try to use the same tuple object (issue #533).
34-
pyarg_tuples[nargs+1] = C_NULL
19+
function _pycall!(ret::PyObject, o::Union{PyObject,PyPtr}, args, nargs::Int=length(args),
20+
kw::Union{Ptr{Cvoid}, PyObject}=C_NULL)
21+
pyargsptr = @pycheckn ccall((@pysym :PyTuple_New), PyPtr, (Int,), nargs)
3522
try
36-
pysetargs!(pyargsptr, args, nargs)
23+
for i = 1:nargs
24+
pyarg = PyObject(args[i])
25+
pyincref(pyarg) # PyTuple_SetItem steals the reference
26+
@pycheckz ccall((@pysym :PyTuple_SetItem), Cint,
27+
(PyPtr,Int,PyPtr), pyargsptr, i-1, pyarg)
28+
end
3729
return __pycall!(ret, pyargsptr, o, kw) #::PyObject
3830
finally
39-
if nargs > 0 && unsafe_load(pyargsptr).ob_refcnt > 1
40-
# we are no longer the only reference to the tuple,
41-
# so PyTuple_SetItem will fail (see check_pyargsptr)
42-
pydecref_(pyargsptr)
43-
else
44-
pyunsetargs!(pyargsptr, nargs) # garbage-collect args
45-
pydecref_(pyarg_tuples[nargs+1]) # no-op if it's still NULL
46-
pyarg_tuples[nargs+1] = pyargsptr # restore tuple cache
47-
end
48-
end
49-
end
50-
51-
"""
52-
Handle the situation where a callee had previously called incref on the
53-
arguments tuple that was passed to it. We need to hold the only reference to the
54-
arguments tuple, since setting a tuple item is only allowed when there is just
55-
one reference to the tuple (tuples are supposed to be immutable in Python in all
56-
other cases). Note that this actually happens when creating new builtin
57-
exceptions, ref: https://github.com/python/cpython/blob/480ab05d5fee2b8fa161f799af33086a4e68c7dd/Objects/exceptions.c#L48
58-
OTOH this py"def foo(*args): global z; z=args" doesn't trigger this.
59-
Fortunately, this check for ob_refcnt is fast - only a few cpu clock cycles.
60-
"""
61-
function check_pyargsptr(nargs::Int)
62-
if nargs > 0
63-
p = pyarg_tuples[nargs+1]
64-
if p == C_NULL || unsafe_load(p).ob_refcnt > 1
65-
pydecref_(p) # no-op for NULL p
66-
pyarg_tuples[nargs+1] =
67-
@pycheckn ccall((@pysym :PyTuple_New), PyPtr, (Int,), nargs)
68-
end
69-
end
70-
end
71-
72-
"""
73-
```
74-
pysetargs!(pyargsptr::PyPtr, args...)
75-
```
76-
Convert `args` to `PyObject`s, and set them as the elements of the Python tuple
77-
pointed to by `pyargsptr`
78-
"""
79-
function pysetargs!(pyargsptr::PyPtr, args, N::Int)
80-
for i = 1:N
81-
pysetarg!(pyargsptr, args[i], i)
82-
end
83-
end
84-
85-
"""
86-
Decref the `N`` elements of the Python tuple `pyargsptr` by setting the
87-
elements to NULL, to ensure that they are garbage-collected.
88-
"""
89-
function pyunsetargs!(pyargsptr::PyPtr, N::Int)
90-
for i = 1:N
91-
@pycheckz ccall((@pysym :PyTuple_SetItem), Cint, (PyPtr,Int,PyPtr), pyargsptr, i-1, C_NULL)
31+
pydecref_(pyargsptr)
9232
end
9333
end
9434

95-
"""
96-
```
97-
pysetarg!(pyargsptr::PyPtr, arg, i::Integer=1)
98-
```
99-
Convert `arg` to a `PyObject`, and set it as the `i-1`th element of the Python
100-
tuple pointed to by `pyargsptr`
101-
"""
102-
function pysetarg!(pyargsptr::PyPtr, arg, i::Integer=1)
103-
pyarg = PyObject(arg)
104-
pyincref(pyarg) # PyTuple_SetItem steals the reference
105-
@pycheckz ccall((@pysym :PyTuple_SetItem), Cint,
106-
(PyPtr,Int,PyPtr), pyargsptr, i-1, pyarg)
107-
end
10835
"""
10936
Lowest level version of `pycall!(ret, o, ...)`, assumes `pyargsptr` and `kw`
11037
have all their args set to Python values, so we can just call the function `o`.
@@ -116,9 +43,8 @@ function __pycall!(ret::PyObject, pyargsptr::PyPtr, o::Union{PyObject,PyPtr},
11643
try
11744
retptr = @pycheckn ccall((@pysym :PyObject_Call), PyPtr, (PyPtr,PyPtr,PyPtr), o,
11845
pyargsptr, kw)
119-
pyincref_(retptr)
120-
pydecref(ret)
121-
ret.o = retptr
46+
pydecref_(ret.o)
47+
ret.o = pyincref_(retptr)
12248
finally
12349
sigatomic_end()
12450
end

0 commit comments

Comments
 (0)