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

I'm working on a perl XS module that accesses an underlying library that happens to make use of libgmp. (it's perl bindings for the Nettle cryptographic library)

I'd like to be able to accept Math::BigInt::GMP objects as arguments to some of the functions, and then be able to make use of the underlying mpz_t within the XS code, but i can't see how to do that.

Math::BigInt::GMP version 1.36 has a typemap that claims to be able to extract an mpz_t with:

mpz_from_sv($arg);
But this is declared within GMP.xs itself as:
STATIC mpz_t * mpz_from_sv_nofail (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; } STATIC mpz_t * mpz_from_sv (SV *sv) { mpz_t *mpz; if (!(mpz = mpz_from_sv_nofail(sv))) croak("failed to fetch mpz pointer"); return mpz; }
Since these are static (and therefore internal to GMP.so), i'm pretty sure i can't use them directly in my own XS. And given that they have #if blocks based on preprocessor directives, i don't think i can reasonably even cargo-cult the code into my own .xs file and make sure that it works portably :/

Is there some clue i'm missing? Should i give up on direct mpz_t access, and force the communication across the XS boundaries to be laundered through some more primitive type (like an ASCII representation of a hex string)? That would allow me to work with more than just Math::BigInt::GMPs, but it's also much more inefficient than i'd like it to be.

Any suggestions for how i should approach this?

Replies are listed 'Best First'.
Re: accessing mpz_t * of Math::BigInt::GMP from XS
by syphilis (Archbishop) on Mar 18, 2011 at 00:50 UTC
    Does it *have* to be Math::BigInt::GMP ?
    It's very simple with Math::GMP or Math::GMPz objects:
    use Math::GMP; use Math::GMPz; use Inline C => Config => LIBS => '-lgmp', BUILD_NOISY => 1, USING => ParseRecDescent; use Inline C => <<'EOC'; #include <gmp.h> void my_mul_si(SV * rop, SV * op, SV * si) { mpz_mul_si (*(INT2PTR(mpz_t *, SvIV(SvRV(rop)))), *(INT2PTR(mpz_t *, SvIV(SvRV(op)))), SvIV(si)); } EOC my $ x = -10; my $g_rop = Math::GMP->new(); my $g_op = Math::GMP->new(1234567); my $z_rop = Math::GMPz->new(); my $z_op = Math::GMPz->new(1234567); my_mul_si($g_rop, $g_op, $x); my_mul_si($z_rop, $z_op, $x); my_mul_si($g_op, $g_op, $x); my_mul_si($z_op, $z_op, $x); print $g_rop, "\n", $z_rop, "\n", $g_op, "\n", $z_op, "\n"; __END__ Outputs: -12345670 -12345670 -12345670 -12345670
    I feel that it should also be do-able with Math::BigInt::GMP, too - but I'm currently stuck on trying to work out how that module's mpz_from_sv_nofail() passes its own sv_derived_from() check.
    Every object I pass to it in my own (speculative) code turns out to be a Math::BigInt (not the required Math::BigInt::GMP) object:
    use Math::BigInt lib => 'GMP'; use warnings; use strict; use Inline C => Config => BUILD_NOISY => 1; use Inline C => <<'EOC'; void derivation(SV * obj) { if(sv_derived_from(obj, "Math::BigInt::GMP")) printf("A Math::BigInt::GMP object\n"); else printf("Not a Math::BigInt::GMP object\n"); if(sv_derived_from(obj, "Math::BigInt")) printf("A Math::BigInt object\n"); else printf("Not a Math::BigInt object\n"); } EOC my $o = Math::BigInt->new(1234); derivation($o); __END__ Outputs: Not a Math::BigInt::GMP object A Math::BigInt object
    What's the trick ?

    Cheers,
    Rob
      yah, your approach is the approach i tried as well, and i'm seeing the same "not a Math::BigInt::GMP object" business. Devel::Peek shows some interesting details, but i don't know this stuff deeply enough to interpret it:
      0 dkg@pip:~$ perl -MDevel::Peek -e 'use Math::BigInt only => "GMP"; m +y $x = Math::BigInt->new(34); printf("%s\n", Dump($x));' SV = RV(0x907fe54) at 0x907fe48 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x9065818 SV = PVHV(0x906ad24) at 0x9065818 REFCNT = 1 FLAGS = (OBJECT,OVERLOAD,SHAREKEYS) STASH = 0x90a0f30 "Math::BigInt" ARRAY = 0x90872d8 (0:6, 1:2) hash quality = 125.0% KEYS = 2 FILL = 2 MAX = 7 RITER = -1 EITER = 0x0 Elt "value" HASH = 0x1e720953 SV = RV(0x907fe24) at 0x907fe18 REFCNT = 1 FLAGS = (ROK) RV = 0x90656f8 SV = PVMG(0x90e21a4) at 0x90656f8 REFCNT = 1 FLAGS = (OBJECT,RMG) IV = 0 NV = 0 PV = 0 MAGIC = 0x9097ba0 MG_VIRTUAL = 0xb781aea0 MG_TYPE = PERL_MAGIC_ext(~) MG_FLAGS = 0x10 MG_PTR = 0x9083500 "" STASH = 0x91ab538 "Math::BigInt::GMP" Elt "sign" HASH = 0xe7455c8f SV = PV(0x9063748) at 0x9065998 REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x90833b0 "+"\0 CUR = 1 LEN = 4 0 dkg@pip:~$
      And here's the same thing with Math::GMP:
      0 dkg@pip:~$ perl -MDevel::Peek -e 'use Math::GMP; my $x = Math::GMP- +>new(34); printf("%s\n", Dump($x));' SV = RV(0x868ce44) at 0x868ce38 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x8672998 SV = PVMG(0x86cc918) at 0x8672998 REFCNT = 1 FLAGS = (OBJECT,IOK,OVERLOAD,pIOK) IV = 141099448 NV = 0 PV = 0 STASH = 0x86adf00 "Math::GMP" 0 dkg@pip:~$
      aha -- if i change your code from:
      derivation($o);
      to:
      derivation($o->{value});
      then i get:
      A Math::BigInt::GMP object Not a Math::BigInt object
      So i think i can actually get access to the Math::BigInt::GMP at least.

      But i think i'm still stuck on how to deal with the #defines so that my module will be reasonably portable without requiring that the person building it know exactly what #defines were used when their instance of Math::BigInt::GMP was built.

      Any suggestions? Thanks for pointing me part of the way forward, at least! It's very helpful :)

        Yes, I eventually got around to passing $obj->{value}, but there's probably still something I'm missing. The following works for me - but it avoids a lot of the pre-processor stuff that Math::BigInt::GMP's GMP.xs contains, and I therefore wonder about its portability
        use Math::BigInt lib => 'GMP'; use Inline C => Config => LIBS => '-lgmp', USING => 'ParseRegExp', BUILD_NOISY => 1; use Inline C => <<'EOC'; #include <gmp.h> mpz_t * mpz_from_sv_nofail (SV *sv) { MAGIC *mg; if (!sv_derived_from(sv, "Math::BigInt::GMP")) croak("not of type Math::BigInt::GMP"); mg = SvMAGIC(SvRV(sv)); return (mpz_t *)mg->mg_ptr; } mpz_t * mpz_from_sv (SV *sv) { mpz_t *mpz; if (!(mpz = mpz_from_sv_nofail(sv))) croak("failed to fetch mpz pointer"); return mpz; } void my_mul(SV * bi_rop, SV * bi_op, SV * si) { mpz_t *t1, *t2; t1 = mpz_from_sv(bi_rop); t2 = mpz_from_sv(bi_op); mpz_mul_si(*t1, *t2, SvIV(si) * 10); } EOC my $rop = Math::BigInt->new(0); my $op = Math::BigInt->new(123456); my $x = -110; my_mul($rop->{value}, $op->{value}, $x); print $rop, "\n"; __END__ Outputs: -13580160
        I also wonder:
        1) How is the change of sign being effected by my_mul() - given that it should be working only with 'value';
        (Update: Setting $op to -123456 and multiplying by -110 still results in an output of -13580160. So 'sign' is *not* being dealt with correctly ... which is what I expected. )
        2) Why do I have to multiply the 3rd arg of my_mul() by 10 in order to have it produce the correct value in the Math::BigInt::GMP object.

        I'm sure there's a very good (and likely very simple) explanation for both.

        Cheers,
        Rob