Writing XS Like a Pro - PERL_NO_GET_CONTEXT and Static Functions

The perlxs man page recommends to define the PERL_NO_GET_CONTEXT macro before including EXTERN.h, perl.h, and XSUB.h. If this macro is defined, it is assumed that the interpreter context is passed as a parameter to every function. If it's undefined, the context will typically be fetched from thread-local storage when calling the Perl API, which incurs a performance overhead.

Unfortunately, many XS modules still ship without defining PERL_NO_GET_CONTEXT. This is probably due to the fact that for most modules, setting this macro involves additional changes to the XS code. For example, if your XS file has static functions that call into the Perl API, you'll get somewhat cryptic error messages like the following:

/usr/lib/i386-linux-gnu/perl/5.20/CORE/perl.h:155:16: error: ‘my_perl’ undeclared (first use in this function)
 #  define aTHX my_perl
                ^
/usr/lib/i386-linux-gnu/perl/5.20/CORE/perl.h:168:18: note: in expansion of macro ‘aTHX’
 #  define aTHX_  aTHX,
                  ^
/usr/lib/i386-linux-gnu/perl/5.20/CORE/sv.h:333:41: note: in expansion of macro ‘aTHX_’
 #define SvREFCNT_dec(sv) S_SvREFCNT_dec(aTHX_ MUTABLE_SV(sv))
                                         ^
CommonMark.xs:99:5: note: in expansion of macro ‘SvREFCNT_dec’
     SvREFCNT_dec(obj);
     ^
CommonMark.xs: In function ‘S_node2sv’:

As you can see from the error message, SvREFCNT_dec is a macro that invokes S_SvREFCNT_dec with the interpreter context as additional parameter. The same goes for all other Perl API entry points. This is explained in detail in the perlguts man page.

The man page also has a section that explains how XS authors can pass in the context if PERL_NO_GET_CONTEXT is in effect. To get the best performance, you should use the third approach. Simply prepend pTHX_ to the parameter list of your static functions, and aTHX_ to the argument list when calling them.

Before:

static SV*
S_node2sv(cmark_node *node) {
    /* ... */
}

void
other_code() {
    S_node2sv(node);
}

After:

static SV*
S_node2sv(pTHX_ cmark_node *node) {
    /* ... */
}

void
other_code() {
    S_node2sv(aTHX_ node);
}

Alternatively, you can start functions that call the Perl API with dTHX;. But beware that this is less efficient.

Leave a comment

About Nick Wellnhofer

user-pic I'm a freelancer looking for contract work in Munich, Germany, or remotely. Check out my profiles on MetaCPAN, GitHub, LinkedIn and StackOverflow.