We are getting deprecation warnings for uses of & in ccall’s in Nemo.jl. It’s time we replaced them, but I don’t currently understand what is required.
To make the question concrete, I give an example. On the C side we basically have a struct, called fmpz_poly, say. Various functions take pointers to such structs as both inputs and as outputs, e.g. we have functions which effectively look like this:
/* set r = a + b */
void fmpz_poly_add(fmpz_poly * r, const fmpz_poly * a, const fmpz_poly * c)
{
/* ... */
}
On the Julia side, we define:
type fmpz_poly
# some fields
end
Now we wish to pass fmpz_poly’s created on the Julia side, to the C function fmpz_poly_add say, safely and efficiently.
At present we do the following:
function +(a::fmpz_poly, b::fmpz_poly)
r = fmpz_poly()
ccall((:fmpz_poly_add, :libflint), Void, (Ptr{fmpz_poly}, Ptr{fmpz_poly}, Ptr{fmpz_poly}), &r, &a, &b)
return r
end
The recommendation seems to be to use Ref{fmpz_poly} instead of & and Ptr{fmpz_poly}. But I want to understand what this is doing.
The documentation says that Ref{fmpz_poly} prevents Julia gc from cleaning up the reference until there is no longer any reference to the Ref itself. However, the ccall interface also says that it maintains a reference to its arguments to prevent gc whilst the ccall is being executed. And it says that the Ref argument is converted to a native pointer before passing to the C side (by calling some function for this purpose).
This confuses me, since I don’t see the purpose of using a Ref, since the arguments to ccall are automatically protected from gc.
Basically, I can’t tell from the documentation which of the following is expected, and what the performance ramifications of each solution is (or whether they are valid Julia):
function +(a::fmpz_poly, b::fmpz_poly)
r = fmpz_poly()
reff = Ref{fmpz_poly}(r)
refa = Ref{fmpz_poly}(a)
refb = Ref{fmpz_poly}(b)
ccall((:fmpz_poly_add, :libflint), Void, (Ref{fmpz_poly}, Ref{fmpz_poly}, Ref{fmpz_poly}), refr, refa, refb)
return r
end
function +(a::fmpz_poly, b::fmpz_poly)
r = fmpz_poly()
ccall((:fmpz_poly_add, :libflint), Void, (Ref{fmpz_poly}, Ref{fmpz_poly}, Ref{fmpz_poly}),
Ref{fmpz_poly}(r), Ref{fmpz_poly}(a), Ref{fmpz_poly}(b))
return r
end
function +(a::fmpz_poly, b::fmpz_poly)
r = fmpz_poly()
ccall((:fmpz_poly_add, :libflint), Void, (Ref{fmpz_poly}, Ref{fmpz_poly}, Ref{fmpz_poly}), r, a, b)
return r
end
Clearly, we do need Julia to not gc cleanup the objects a, b and r whilst the ccall is in effect. But our entire library is full of ccalls and we obviously want to do things as efficiently as possible. So which of the above is the best strategy?