NAME

XS::Tutorial::Two - working with more than one value at a time

Introduction

In XS::Tutorial::One, we learned the basic components of XS, and integrated two C functions into Perl which were slower than their Perl builtin equivalents. Oh well, we'll strive to do better in future tutorials. This chapter is going to show you how to define xsubs that accept multiple parameters.

I realize that sounds incredibly dull, but I promise along the way you'll pickup some invaluable XS skills that can be used to create your own super fast programs.

Module Code

As before, we'll define the module code to load our XS. This is all that's required:

package XS::Tutorial::Two;
require XSLoader;

XSLoader::load();
1;

That should be saved as lib/XS/Tutorial/Two.pm.

XS Code

The top of the XS file will look similar to the previous chapter:

#define PERL_NO_GET_CONTEXT // we'll define thread context if necessary (faster)
#include "EXTERN.h"         // globals/constant import locations
#include "perl.h"           // Perl symbols, structures and constants definition
#include "XSUB.h"           // xsubpp functions and macros
#include "stdint.h"         // portable integer types

MODULE = XS::Tutorial::Two  PACKAGE = XS::Tutorial::Two
PROTOTYPES: ENABLE

Remember to append any XS code after the PROTOTYPES line. This should be saved as lib/XS/Tutorial/Two.xs.

Adding numbers

Here's a simple declaration of an xsub that adds two integers:

int
add_ints (addend1, addend2)
  int addend1
  int addend2
  CODE:
    RETVAL = addend1 + addend2;
  OUTPUT:
    RETVAL

This declares an xsub called add_ints which accepts two integers and whose return type is int. Note the K&R style of the function definition. This can also be written as:

add_ints (int addend1, int addend2)

But you rarely see it done that way in the wild. I don't know if that's a cargo cult thing or there are edge cases to the xsub compiler that I'm not aware of. Just to be safe, I'll keep doing it the way everyone else does (the cult persists!).

Whereas before we were essentially mapping C functions like srand to Perl, here we're declaring our own logic: add_ints isn't imported from anywhere, we're declaring it as a new function.

Since add_ints is a new function, we need to define the logic of it, and that's where the CODE section comes in. Here we can write C code which forms the body of the function. In this example, I add the two subroutine parameters together and assign the result to RETVAL.

RETVAL ("RETurn VALue") is a special variable that is declared by the xsub processor (xsubpp). The OUTPUT section accepts the return variable for the xsub, placing it on the stack, so that calling code will receive it.

Adding more than two numbers

Adding two numbers is all well and good, but lists are the lingua franca of Perl. Let's update the add_ints xsub to accept n values:

int32_t
add_ints (...)
  CODE:
    uint32_t i;
    for (i = 0; i < items; i++) {
      if (!SvOK(ST(i)) || !SvIOK(ST(i)))
        croak("requires a list of integers");

      RETVAL += SvIVX(ST(i));
    }
  OUTPUT:
    RETVAL

First off, notice I've updated the return value. One issue with using int in C is it may be a different size on different machine architectures. int32_t is from the stdint.h header, and guaranteed to be a 32 bit signed integer.

I've replaced the function parameters with ... which indicates the function accepts a variable number of arguments, just like in C. In the CODE section, I declare a uint32_t integer called i (uint32_t is a 32 bit unsigned integer).

The for loop uses the special variable items (the number of arguments passed to the function) to iterate over the arguments. The if statement calls the macro ST to access the stack variable at position i. This is used to check that the scalar is defined (SvOK) and that it is an integer (SvIOK). If either test fails, the code calls croak to throw a fatal exception.

Otherwise the integer value is extracted from the scalar (SvIVX) and added to RETVAL. If all of these C macros look strange to you, don't worry, they are weird! They are part of the Perl C API, and they're documented in perlapi.

Edge cases

It's probably a good time to write some tests for this function, here's a start:

use Test::More;

BEGIN { use_ok 'XS::Tutorial::Two' }

cmp_ok XS::Tutorial::Two::add_ints(7,3), '==', 10;
cmp_ok XS::Tutorial::Two::add_ints(1500, 21000, -1000), '==', 21500;

done_testing;

I saved that file as t/two.t, and run it by building the distribution with make:

perl Makefile.PL && make && make test

Do you know what the return value would be if add_ints was called with no arguments? Maybe undef, since if there are no arguments, the for loop will not have any iterations. Here's a test for that condition:

ok !defined XS::Tutorial::Two::add_ints(), 'empty list returns undef';

Re-building and running the tests with:

make clean && perl Makefile.PL &&  make && make test

That test fails, because the return value is zero! This is a quirk of C: uninitialized integers can be zero. Let's fix the xsub to return undef when it doesn't receive any arguments:

SV *
add_ints (...)
  PPCODE:
    uint32_t i;
    int32_t total = 0;
    if (items > 0) {
      for (i = 0; i < items; i++) {
        if (!SvOK(ST(i)) || !SvIOK(ST(i)))
          croak("requires a list of integers");

        total += SvIVX(ST(i));
      }
      PUSHs(sv_2mortal(newSViv(total)));
    }
    else {
      PUSHs(sv_newmortal());
    }

Woah, quite a few changes! First I've changed the return type to SV *, from int32_t. The reason for this will become clear in a moment. The CODE section is now called PPCODE, which tells xsubpp that we will be managing the return value of xsub ourselves, hence the OUTPUT section is gone.

I've declared a new variable called total to capture the running total of the arguments as they're added. If we received at least one argument, total is copied into a new scalar integer value (newSViv), its reference count is corrected (sv_2mortal) and it is pushed onto the stack pointer (PUSHs).

Otherwise a new undef scalar is declared with sv_newmortal and that is pushed onto the stack pointer instead. So in both cases we're returning an SV. And as we're returning a Perl type instead of a C type (int32_t) there is no need for xsubpp to cast our return value into a Perl scalar, we're already doing it.

References

  • XS::Tutorial::One contains the background information necessary to understand this tutorial

  • perlxs defines the keywords recognized by xsubpp =item * perlapi lists the C macros used to interact with Perl data structures (and the interpreter)

  • The stdint.h C library provides sets of portable integer types