Lately, I’ve managed to pass a BigInt to C and back (Passing GMP variables from C(++) to Julia - #10 by Questioner). Now suppose I want to generate Fibonacci numbers in C and pass them to Julia. I thought I just take the analogous code for arrays
since a Vector{BigInt} is stored under the hood as an array of pointers to BigInt objects (which aren’t stored inline since they are mutable structs), and BigInt objects are laid out identically to mpz_t (so that they can be passed by reference to GMP functions).
in the post Passing a BigInt (mpz_t for GMP) array to C I tried in vain to pass a GMP number to C. Upon inspecting the memory, it became apparent that C stored the “chunks” of the big integer in adjacent memory blocks, whereas in Julia the number was scattered.
Firstly, it would be necessary to make the big integer accessible to C, so that passing it to C works, and receiving it also works.
But then, for speed reasons it would seem recommendable to have Julia also store the number chunks in adjacent memory blocks.
I would probably enjoy implementing this myself; the only problem is that I don’t quite know where this functionality is in the Julia code.
I’ve been pointed to the right file (base/gmp.jl), but it has to be said that I don’t properly understand that code, since I don’t know many of the symbols and names therein.
That code in gmp.jl simply passes BigInt objects directly to ccall as Ref{BigInt}, which should be declared as an mpz_t* argument (a single pointer, not an array of pointers) on the C side.
That is, if you have a C function void foo(mpz_t *arg) { .... }, then you could call it in Julia as @ccall mylibrary.foo(n::Ref{BigInt})::Cvoid where n is a BigInt.
If you modify arg on the C side, then the changes will be visible in n on the Julia side.
x = BigInt(40000000000000000000000000000000000000000000000000000000000000000000000000000000000000)
y = BigInt(40000000000000000000000000000000000000000000000000000000000000000000000000000000000000)
a = [x, y]
@ccall "gmptest.so".AddToS(a::Ptr{BigInt})::Cvoid
If you have a nums::Vector{BigInt} in Julia, e.g. nums = BigInt[1,2,3], I think you can just pass it as Ptr{BigInt} to a C function that takes an mpz_t *. That is, write a C function like:
void foo(mpz_t *nums, size_t len) {
for (size_t i = 0; i < len; ++i)
mpz_set_ui(&nums[i], i);
}
Oh, I see, this isn’t going to work because BigInt is a mutable struct in Julia, so a nums::Vector{BigInt} is internally an array of pointers to Julia objects (jl_value_t*).
Just a note of caution: Different entries in an Array{BigInt} may actually be the same object. For example, when you do zeros(BigInt, N), Julia only allocates a single BigInt(0) that’s referenced by every element of the array. This is safe as long as you stick to Julia’s public API, which only provides out-of-place operations, but if you’re using in-place operations on the C side (or you’re using non-public Julia functions), you need to be careful. Minimal example of what can go wrong:
julia> xs = zeros(BigInt, 2)
2-element Vector{BigInt}:
0
0
julia> xs[1] === xs[2] # Uh oh!
true
julia> Base.GMP.MPZ.add_ui!(xs[1], BigInt(1)) # Increment the first element of xs
1
julia> xs # Oops...
2-element Vector{BigInt}:
1
1
If you call the BigInt constructor separately for each element that should guarantee distinct objects, e.g. [BigInt(0) for _ = 1:10] instead of zeros(BigInt, 10).