That is a pretty interesting exchange, it indicates that this memory leak has been known to exist for 6 years, and the remedy is also discussed there. Details and answers to questions asked in this thread follow.
To answer Dima's question first, the Pari documentation that I am referring to is the User's Guide to the PARI library (libpari.pdf). Now for the details ...
The Pari GEN constructed by pari.ellinit is what Pari calls a "lazy vector". These are discussed in section 12.3, which begins by saying
"The functions in this section are used to implement ell structures and analogous objects, which are vectors some of whose components are initialized to dummy values, later computed on demand."
The only example known to me of a lazy vector is the GEN constructed by the
ellinit function.
A "Technical Note" in thst section contains more detail:
"In the current implementation, S is a t_VEC with d + 1 entries. The first d components are ordinary t_GEN entries, which you can read or assign to in the customary way. But the last component gel(S, d + 1), a t_VEC of length n initialized to zerovec(n), must be handled in a special way: you should never access or modify its components directly, only through the API we are about to describe."
You can see an example of this behavior in Sage as follows:
sage: import cypari2
sage: pari = cypari2.Pari()
sage: e = pari.ellinit([11, 0]); e
[0, 0, 0, 11, 0, 0, 22, 0, -121, -528, 0, -85184, 1728, Vecsmall([1]),
[Vecsmall([64, -1])], [0, 0, 0, 0, 0, 0, 0, 0]]
The last entry of e is a t_VEC containing 8 references to the PARI Zero GEN, created by obj_init. The ellrootno function fills in this vector, replacing the Zero placeholders by pointers to Pari GENS on the heap. To see this in sage:
sage: pari.ellrootno(e)
1
sage: e
[0, 0, 0, 11, 0, 0, 22, 0, -121, -528, 0, -85184, 1728, Vecsmall([1]), [Vecsmall([64, -1])], [0, 0, 0, 0, 0, [3872, 4, [2, 5; 11, 2], [[5, 3, 0, 2], [2, 3, 0, 2]]], [1, Vecsmall([-1, 1])], [[2, 11]~]]]
To answer Giorgi's question one can observe that, while ellrootno *usually* fills in the lazy components, sometimes it doesn't need to:
sage: e = pari.ellinit([2**70, 0]); e
[0, 0, 0, 1180591620717411303424, 0, 0, 2361183241434822606848, 0, -1393796574908163946345982392040522594123776, -56668397794435742564352, 0, -105312291668557186697918027683670432318895095400549111254310977536, 1728, Vecsmall([1]), [Vecsmall([64, -1])], [0, 0, 0, 0, 0, 0, 0, 0]]
sage: pari.ellrootno(e)
1
sage: e
[0, 0, 0, 1180591620717411303424, 0, 0, 2361183241434822606848, 0, -1393796574908163946345982392040522594123776, -56668397794435742564352, 0, -105312291668557186697918027683670432318895095400549111254310977536, 1728, Vecsmall([1]), [Vecsmall([64, -1])], [0, 0, 0, 0, 0, [32, 4, Mat([2, 5]), [[5, -7, 0, 4]]], [1, Vecsmall([-1])], [[2]~, [131072, 0, 0, 0], [0, 0, 0, 4, 0, 0, 8, 0, -16, -192, 0, -4096, 1728, Vecsmall([1]), [Vecsmall([64, -1])], [0, 0, 0, 0, 0, 0, 0, 0]]]]]
(Note that the vector in the last entry is still all zeroes.)
In any case, the heap GENs added lazily by ellrootno cause the memory leak. If the PARI GEN managed by the Python Gen e is passed to gunclone (as happens in the Gen.__dealloc__ method) those added heap GENs are not freed. However, the documented API addresses this issue via the function obj_free:
"void obj_free(GEN S) destroys all clones stored in the n tagged components, and replace them by the initial value 0. The regular entries of S are unaffected, and S remains a valid object. This is used to avoid memory leaks."
Thus the fix for this memory leak (now implemented in CyPari) is to track which Python Gen objects are managing "lazy vector" GENs, and to modify the Gen.__dealloc__ method to make it call obj_free before calling gunclone for those objects. Unfortunately, if obj_free is passed a GEN which is not a lazy vector, i.e. was not initialized by obj_init, then a segfault results. So the tracking is essential. In CyPari I added a cdef field to the Gen class which is used to track whether the GEN is a lazy vector. I had to modify the auto_gen code to allow for setting that flag for all such GENs. (Currently that means only the GENs produced by calling ellinit. I couldn't find any others, although obj_init does seem to be called by rnfinit as well.)
- Marc