El typemap T_ARRAY

La entrada T_ARRAY del fichero de typemap facilita el proceso de conversión entre arrays C y listas Perl. Este servicio es útil si tenemos funciones de una librería que espera recibir o devolver arrays. Si lo que se quiere es simplemente manipular la lista Perl en C es mejor usar la técnica introducida en las secciones 17.13 y 17.14.

Para ilustrar su uso estudiaremos el código de una subrutina que recibe y devuelve usando T_ARRAY un vector de dobles. La subrutina realiza la suma de prefijos y su funcionamiento queda ilustrado en la ejecución del siguiente programa de ejemplo:

lhp@nereida:~/Lperl/src/XSUB/Arrays$ cat -n usearrays.pl
 1  #!/usr/local/bin/perl -w
 2  use strict;
 3  use blib;
 4  use List::MoreUtils qw(all);
 5  use Regexp::Common;
 6  use Arrays;
 7  my @a = (@ARGV and all { /^$RE{num}{real}$/ } @ARGV)? @ARGV : 1..5;
 8  my @x = prefixsum(@a);
 9  print "Suma de prefijos de @a:\n  @x\n";
lhp@nereida:~/Lperl/src/XSUB/Arrays$ usearrays.pl
Suma de prefijos de 1 2 3 4 5:
  1 3 6 10 15
lhp@nereida:~/Lperl/src/XSUB/Arrays$ usearrays.pl 1 a b 3
Suma de prefijos de 1 2 3 4 5:
  1 3 6 10 15
lhp@nereida:~/Lperl/src/XSUB/Arrays$ usearrays.pl 5.2 3.1 -1 -2e1
Suma de prefijos de 5.2 3.1 -1 -2e1:
  5.2 8.3 7.3 -12.7
lhp@nereida:~/Lperl/src/XSUB/Arrays$ usearrays.pl 0
Suma de prefijos de 0:
  0

Para su funcionamiento, el código asociado con el typemap T_ARRAY requiere la colaboración del programador en varios puntos. El primero es que el tipo de entrada y/o salida de la XSUB debe tener un nombre que sea de la forma $subtipo."Array". Así deberá llamarse doubleArray si es un array de dobles, intArray si lo es de enteros, etc. Por tanto, en el ejemplo que nos ocupa, el tipo se llamará doubleArray y le asignamos la entrada T_ARRAY usando un fichero de typemap local al directorio de trabajo:

lhp@nereida:~/Lperl/src/XSUB/Arrays$ cat -nT typemap # La opción -T  muestra tabs
     1  doubleArray *^IT_ARRAY
lhp@nereida:~/Lperl/src/XSUB/Arrays$ cat -n typemap
     1  doubleArray *   T_ARRAY

El código de la XSUB prefixsum (líneas 30-41 del listado que sigue) comienza indicando mediante la elípsis que la función recibirá un número variable de argumentos. El primero argumento array no se corresponde con un argumento real de la función y se usará para indicar el nombre del array C que almacenará el vector C de números extraido de la lista Perl de escalares (observe la llamada en la línea 8 del código de usearrays.pl encima: no existe un primer argumento especial). De hecho, es este identificador array el que se corresponde con la variable $var del código del typemap.

lhp@nereida:~/Lperl/src/XSUB/Arrays$ cat -n Arrays.xs
  1  #include "EXTERN.h"
  2  #include "perl.h"
  3  #include "XSUB.h"
  4  #include "ppport.h"
  5
  6  typedef double doubleArray;
  7
  8  doubleArray * doubleArrayPtr ( int num ) {
 ..    ... # asigna memoria para "num" elementos de tipo double
 14  }
 15
 16  doubleArray *
 17  prefixSum ( double num, doubleArray * array ) {
 ..    ... # retorna la suma de prefijos de array 
 26  }
 27
 28  MODULE = Arrays         PACKAGE = Arrays
 29
 30  doubleArray *
 31  prefixsum( array, ...)
 32    doubleArray * array
 33   PREINIT:
 34    int size_RETVAL;
 35   CODE:
 36    size_RETVAL = ix_array;
 37    RETVAL = prefixSum( ix_array, array );
 38   OUTPUT:
 39    RETVAL
 40   CLEANUP:
 41    XSRETURN(size_RETVAL);

El código de entrada asociado con T_ARRAY asigna memoria para el nuevo array y después procede a copiar cada uno de los elementos en la lista de escalares Perl en el array recién creado. Para poder llevar a cabo dicha asignación de memoria el programador deberá proveer una función de asignación cuyo nombre debe ser igual a la meta-variable $ntype (el nombre del tipo con los astericos sustituidos por Ptr). Esa es la misión de la función doubleArrayPtr que aparece en la línea 8 del código XS. Además el código de entrada asociado con T_ARRAY declara la variable con nombre ix_$var (ix_array en el ejemplo) la cual contendrá el número de elementos en el array.

lhp@nereida:~/Lperl/src/XSUB/Arrays$ cat -n /usr/share/perl/5.8/ExtUtils/typemap
   1  # basic C types
   2  int                     T_IV
  ..  .............................................
  45  double                  T_DOUBLE
  ..  .............................................$
  55
  56  #############################################################################
  57  INPUT
 ... .............................................
 110  T_DOUBLE
 111          $var = (double)SvNV($arg)
 ... .............................................
 176  T_ARRAY
 177          U32 ix_$var = $argoff;
 178          $var = $ntype(items -= $argoff);
 179          while (items--) {
 180              DO_ARRAY_ELEM;
 181              ix_$var++;
 182          }
 183          /* this is the number of elements in the array */
 184          ix_$var -= $argoff

La entrada INPUT de T_ARRAY (líneas 176-184) del código muestra en mas detalle las acciones que tienen lugar:

  1. Se declara ix_$var (ix_array en el ejemplo) y se inicializa al desplaamiento (offset) del argumento actual.
  2. Se solicita memoria para items - $argoff elementos del tipo del array confiando en que el programador ha proveído una función de asignación con nombre $ntype. Observe que el uso de items implica que, para que este código funcione, el array debe ser el último argumento de la lista. De paso se cambia el valor de items que pasa a tener el número de elementos en el array. Después se entra en un bucle en el que el campo numérico de cada elemento de la lista Perl es copiado en el array C. El traductor xsubpp sustituye la cadena DO_ARRAY_ELEM por:
                      $var[ix_$var - $argoff] = ($subtype)SvNV(ST(ix_$var));
    
    que en el ejemplo acaba convertido en:
                      array[ix_array - 0] = (double)SvNV(ST(ix_array));
    
  3. Por último ix_$var es actualizada para que guarde el tamaño del array.

El código de salida del typemap realiza el proceso inverso:

 194  OUTPUT
 ... .............................................
 240  T_DOUBLE
 241          sv_setnv($arg, (double)$var);
 ... .............................................
 273  T_ARRAY
 274          {
 275              U32 ix_$var;
 276              EXTEND(SP,size_$var);
 277              for (ix_$var = 0; ix_$var < size_$var; ix_$var++) {
 278                  ST(ix_$var) = sv_newmortal();
 279                  DO_ARRAY_ELEM
 280              }
 281          }
 ... .............................................

  1. En la línea 275 se declara ix_$var. Puesto que $var es el nombre de la variable siendo retornada, en la sección OUTPUT:, la cadena ix_$var se convierte en ix_RETVAL.
  2. En la línea 276 se extiende la pila para que contenga size_$var escalares. Por tanto el programador deberá proveer una variable con nombre size_$var que determine el tamaño de la lista que se devuelve. En el ejemplo size_RETVAL.
  3. Elemento por elemento se van creando nuevos escalares (línea 278) y copiando en la pila. La cadena DO_ARRAY_ELEM es expandida por xsubpp a:
    sv_setnv(ST(ix_$var), ($subtype)$var[ix_$var])
    

    La función sv_setnv copia el valor numérico (segundo argumento) en el escalar. El texto anterior se transforma en el ejemplo en:

    sv_setnv(ST(ix_RETVAL), (double)RETVAL[ix_RETVAL])
    

Finalmente el código generado por xsubpp para la XSUB prefixsum queda:

lhp@nereida:~/Lperl/src/XSUB/Arrays$ cat -n Arrays.c
 .  ...................
39  XS(XS_Arrays_prefixsum); /* prototype to pass -Wmissing-prototypes */
40  XS(XS_Arrays_prefixsum)
41  {
42      dXSARGS;
43      if (items < 1)
44          Perl_croak(aTHX_ "Usage: Arrays::prefixsum(array, ...)");
45      {
46          doubleArray *   array;
47  #line 34 "Arrays.xs"
48    int size_RETVAL;
49  #line 50 "Arrays.c"
50          doubleArray *   RETVAL;
51
52          U32 ix_array = 0;
53          array = doubleArrayPtr(items -= 0);
54          while (items--) {
55                  array[ix_array - 0] = (double)SvNV(ST(ix_array));
56              ix_array++;
57          }
58          /* this is the number of elements in the array */
59          ix_array -= 0;
60  #line 36 "Arrays.xs"
61    size_RETVAL = ix_array;
62    RETVAL = prefixSum( ix_array, array );
63  #line 64 "Arrays.c"
64          {
65              U32 ix_RETVAL;
66              EXTEND(SP,size_RETVAL);
67              for (ix_RETVAL = 0; ix_RETVAL < size_RETVAL; ix_RETVAL++) {
68                  ST(ix_RETVAL) = sv_newmortal();
69                  sv_setnv(ST(ix_RETVAL), (double)RETVAL[ix_RETVAL]);
70              }
71          }
72  #line 41 "Arrays.xs"
73    XSRETURN(size_RETVAL);
74  #line 75 "Arrays.c"
75      }
76      XSRETURN(1);
77  }
78
..  ...................
Para completar, veamos el código de la subrutina de asignación de memoria (fichero Arrays.xs):
 8  doubleArray * doubleArrayPtr ( int num ) {
 9    doubleArray * array;
10    SV * mortal;
11    mortal = sv_2mortal( NEWSV(1, num * sizeof(doubleArray) ) );
12    array = (doubleArray *) SvPVX( mortal );
13    return array;
14  }

Casiano Rodríguez León
2012-02-29