syphilis has asked for the wisdom of the Perl Monks concerning the following question:

Hi,

I'm aiming to access the mpz_t (gmp library integer) value that's contained in a Math::BigInt::GMP object, where that object is contained within a Math::BigInt object (as is the norm).
So I've copied pertinent code straight out of Math::BigInt::GMP's GMP.xs, and the below demo script is meeting that aim just fine (on both Windows and Ubuntu).

But ... I've had to make one small (but significant) change to the GMP.xs code that I copied.
In the mpz_from_sv_nofail function, the original code contained the following:
#if GMP_HAS_MAGICEXT && mg->mg_virtual == &vtbl_gmp #endif
but that condition never holds for me, so I've changed it to:
#if GMP_HAS_MAGICEXT && mg->mg_virtual != &vtbl_gmp #endif
though simply commenting out that code also works fine, and might be safer.
Safer still would be to understand why this anomaly exists. The mpz_from_sv_nofail function appears to be called internally by Math::BigInt::GMP - where the original rendition of it must surely be working fine.
Does anyone know why I'm having to alter it ? (I am totally clueless about magic - a link to some explanatory documentation might help me, though I seriously doubt that.)

Anyway - here's the code that I'm running. There's not much point in running it if you don't have Math::BigInt::GMP installed.
This code is copy'n'pasted straight out of GMP.xs except that:
1) the alteration mentioned above has been made;
2) I added the magic_status XSub to see what GMP_HAS_MAGICEXT was set to;
3) The mpz_access XSub is simply GMP.xs' mpz_from_sv() function, rewritten to print out the value of the mpz_t instead of returning it.
use warnings; use Math::BigInt lib => 'GMP'; use Inline C => Config => LIBS => '-lgmp', USING => 'ParseRegExp', BUILD_NOISY => 1; use Inline C => <<'EOC'; #include <gmp.h> #ifndef PERL_UNUSED_ARG # define PERL_UNUSED_ARG(x) ((void)x) #endif #ifndef gv_stashpvs # define gv_stashpvs(name, create) gv_stashpvn(name, sizeof(name) - 1 +, create) #endif #ifndef PERL_MAGIC_ext # define PERL_MAGIC_ext '~' #endif #if defined(USE_ITHREADS) && defined(MGf_DUP) # define GMP_THREADSAFE 1 #else # define GMP_THREADSAFE 0 #endif #ifdef sv_magicext # define GMP_HAS_MAGICEXT 1 #else # define GMP_HAS_MAGICEXT 0 #endif #define NEW_GMP_MPZ_T RETVAL = malloc (sizeof(mpz_t)); #define NEW_GMP_MPZ_T_INIT RETVAL = malloc (sizeof(mpz_t)); mpz_init(* +RETVAL); #define GMP_GET_ARG_0 TEMP = mpz_from_sv(x); #define GMP_GET_ARG_1 TEMP_1 = mpz_from_sv(y); #define GMP_GET_ARGS_0_1 GMP_GET_ARG_0; GMP_GET_ARG_1; #if GMP_THREADSAFE STATIC int dup_gmp_mpz (pTHX_ MAGIC *mg, CLONE_PARAMS *params) { mpz_t *RETVAL; PERL_UNUSED_ARG(params); NEW_GMP_MPZ_T; mpz_init_set(*RETVAL, *((mpz_t *)mg->mg_ptr)); mg->mg_ptr = (char *)RETVAL; return 0; } #endif #if GMP_HAS_MAGICEXT STATIC MGVTBL vtbl_gmp = { NULL, /* get */ NULL, /* set */ NULL, /* len */ NULL, /* clear */ NULL, /* free */ # ifdef MGf_COPY NULL, /* copy */ # endif # ifdef MGf_DUP # if GMP_THREADSAFE dup_gmp_mpz, # else NULL, /* dup */ # endif # endif # ifdef MGf_LOCAL NULL, /* local */ # endif }; #endif void magic_status(void){ printf("GMP_HAS_MAGICEXT: %d\n", GMP_HAS_MAGICEXT); } STATIC mpz_t * (SV *sv) { MAGIC *mg; if (!sv_derived_from(sv, "Math::BigInt::GMP")) croak("not of type Math::BigInt::GMP"); for (mg = SvMAGIC(SvRV(sv)); mg; mg = mg->mg_moremagic) { if (mg->mg_type == PERL_MAGIC_ext #if GMP_HAS_MAGICEXT && mg->mg_virtual != &vtbl_gmp #endif ) { #if GMP_HAS_MAGICEXT return (mpz_t *)mg->mg_ptr; #else return INT2PTR(mpz_t *, SvIV((SV *)mg->mg_ptr)); #endif } } return (mpz_t *)NULL; } void access_mpz(SV * sv) { mpz_t *mpz; if (!(mpz = mpz_from_sv_nofail(sv))) croak("failed to fetch mpz pointer"); mpz_out_str(NULL, 10, mpz); } EOC magic_status(); my $x = Math::BigInt->new('-' . ('9876543210123456789' x 4)); access_mpz($x->{value}); # Ignores $x->{sign}
For me this outputs (after compilation):
GMP_HAS_MAGICEXT: 1 9876543210123456789987654321012345678998765432101234567899876543210123 +456789
If I revert to the original mpz_from_sv_nofail() code I get:
GMP_HAS_MAGICEXT: 1 failed to fetch mpz pointer at try.pl line 125.
Cheers,
Rob

Replies are listed 'Best First'.
Re: Math::BigInt::GMP - direct access to the gmp integer value
by Anonymous Monk on Jan 10, 2016 at 04:14 UTC
    I think that's because
    sv_magicext(SvRV(sv), NULL, PERL_MAGIC_ext, &vtbl_gmp, (void *)mpz, 0 +);
    (in attach_mpz_to_sv) uses pointer to vtbl_gmp defined in gmp.xs, and you're comparing with a pointer to an object defined in your file. Those are two different objects and have different addresses.

    As for deleting the line && mg->mg_virtual == &vtbl_gmp, well, Perl's magic is like black magic to me :) but perlapi says: Note that "sv_magicext" will allow things that "sv_magic" will not. In particular, you can... add more than one instance of the same 'how' (so, some other module can add another PERL_MAGIC_ext struct to the linked list of magics).

      Perl's magic is like black magic to me :)

      Not only do I not know how it works, but I also don't even know what it does !!

      Those are two different objects and have different addresses

      Thank you for wading through the code and spotting that - much appreciated !!
      I'll need to chew this over for a while.

      It means I either have to do something that will ensure that the addresses match, or just remove the problematic condition from the code.
      The latter is appealing, as I know immediately how to do that (and it reduces overhead). But I'm initially loathe to take that action as I don't know what risks it might create.

      Anyway - that's something I can work on.
      Thanks again.

      Cheers,
      Rob
        Not only do I not know how it works, but I also don't even know what it does !!
        My reading of that code is that it uses magic mostly to store the pointer to mpz in mg->mg_ptr. perlguts says its okay: "Extensions can use "PERL_MAGIC_ext" magic to 'attach' private information to variables (typically objects). This is especially useful because there is no way for normal perl code to corrupt this private information (unlike using extra elements of a hash object)". Then there is also that copying function dup_gmp_mpz, apparently for use with ithreads.
        But I'm initially loathe to take that action as I don't know what risks it might create.
        As far as I can tell, the risk is that something else will attach magic (with tag PERL_MAGIC_ext) to your Math::BigInt::GMP variable, and then you'll get wrong mg->mg_ptr. How likely is that? Probably pretty unlikely...
Re: Math::BigInt::GMP - direct access to the gmp integer value
by Will_the_Chill (Pilgrim) on Jan 10, 2016 at 23:58 UTC
Re: Math::BigInt::GMP - direct access to the gmp integer value
by Anonymous Monk on Jan 11, 2016 at 06:41 UTC

    Probably, there's no elegant solution here. Math::BigInt::GMP encapsulates the mpz_t opaquely; is extending that not an option? I've no idea how perl modules should handle data sharing with friends.

    Anyway, adding multiple magic is very simple with silly code:

    use Math::BigInt only => 'GMP'; use Devel::Peek; $$_ =~ m//g, Dump $_ for Math::BigInt->new(1)->{value};
    Without the mg_virtual test your code would return NULL, and probably crash somewhere. That's the sort of thing that gives XS its bad rep. :|

      Without the mg_virtual test your code would return NULL, and probably crash somewhere

      I haven't yet managed to get that to happen. Perhaps I'm just not trying hard enough.
      (Note to self: investigate what happens if NULL gets returned.)

      Update: Just tested what happens if NULL gets returned, and it's fine - just falls through to the slower overloading via stringification.
      The fun will start when a non-NULL (but incorrect) pointer gets returned.

      Current state of this code is to be found in the Math::GMPz module on github:
      git clone git@github.com:sisyphus/math-gmpz Math-GMPz
      The code in question is the C code (in GMPz.xs) that pertains to the overloading of operations with Math::BigInt objects - which is most of the overload_* subs in GMPz.xs.

      Cheers,
      Rob