Some tricks for prettier xs

XS has a reputation of being ugly and cumbersome, but in my experience, it doesn't have to be. Let's take for example this snippet from my Thread::Csp::Promise class:

MODULE = Thread::Csp PACKAGE = Thread::Csp::Promise PREFIX = promise_

SV* promise_get(Promise* promise)

bool promise_is_finished(Promise* promise)

SV* promise_get_notifier(Promise* promise)

How did I write XS with so little code/boilerplate? By using XS the way it was originally intended: to glue Perl and C together, not to implement any behavior.

No CODE

A lot of people seem to think you need a CODE block in your XS functions, but often you don't. For example
SV* promise_get(Promise* promise)
is actually equivalent to
SV* promise_get(Promise* promise)
CODE:
    RETVAL = promise_get(promise);
OUTPUT:
    RETVAL
By giving the `promise_get` function the right shape and name, I don't need to write any of that.

This doesn't only mean less code (which is always good), it also means that it's much easier to split a large amount of code into multiple files, as doing this in C is much easier than doing it in XS (e.g. DBI.xs is 5700 lines). This aids in making your project more maintainable.

No K&R

The second thing you may notice is that I'm declaring the types of the arguments in ANSI style (within the parentheses), not the common K&R style like:
SV*
promise_get(promise)
    Promise* promise;
The author of perlxs and perlxstut was clearly fond of K&R style, and everyone seems to have copied it from the documentation, but ANSI style is far more familiar to most people, and less repetitive. While the K&R style can do a few things ANSI style can't (e.g. with regards to custom conversions), it's very uncommon to need any of that.

Typemaps

To convert from arguments from Perl values to C values, and vice-versa for the return values, I used type maps.
TYPEMAP
Promise* T_PROMISE

INPUT
T_PROMISE
        $var = sv_to_promise($arg)

OUTPUT
T_PROMISE
        $arg = promise_to_sv($var);

Using these templates, you don't need the XS or the individual functions to worry about type conversions for the most common argument types.

Prefix

All the XS packages in my module are defined with a prefix:
MODULE = Thread::Csp PACKAGE = Thread::Csp::Promise PREFIX = promise_
That way I can namespace my C functions to all start with `promise_`, but on the Perl side promise_get will be a sub called get (in the package Thread::Csp::Promise).

C_ARGS

I still had one function where the default glue code didn't quite cut it, because that had a slurpy argument so I couldn't map arguments 1-on-1. Instead of using a `CODE` block here, I used the C_ARGS to override only how the arguments are passed from Perl to C, without overriding any of the rest of the code generation. And then I defined a helper that turns the arguments on the stack into an array that is passed to the function.
Promise* thread_spawn(SV* class, SV* module, SV* function, ...)
        C_ARGS:
                slurp_arguments(1)

Leave a comment

About Leon Timmermans

user-pic