Turn a Perl XS File into C with xsubpp

A Perl module with a broken XS build usually fell over at xsubpp, the tool that turns XS glue code into plain C. xsubpp supplies the wiring that lets Perl call XSUBs; a C compiler and the rest of the module build still come afterwards. You will turn a small XS source file into generated C, inspect the result, and keep the original source safe throughout.

1. Check the installed tool

Start by checking which executable will actually run, and which Perl release owns it:

$ command -v xsubpp
/usr/bin/xsubpp
$ perl -v
This is perl 5, version 38, subversion 2 (v5.38.2)
$ xsubpp -v
xsubpp version 3.51

That output is from the installed perl 5.38.2 package. Other Perl releases can ship a different ExtUtils::ParseXS version, so record this check whenever a generated file needs to be reproducible elsewhere.

Checkpoint: if command -v prints nothing, stop. Install or repair Perl through your normal package management, rather than guessing at an alternate path.

2. Create a minimal XS input

In an empty working directory, save this as sample.xs. The MODULE and PACKAGE lines name the Perl-facing module, and the XSUB itself just adds two integers:

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

MODULE = Demo    PACKAGE = Demo

PROTOTYPES: ENABLE

int
add(int left, int right)
    CODE:
        RETVAL = left + right;
    OUTPUT:
        RETVAL

PROTOTYPES: ENABLE makes the generated binding carry a Perl prototype for this XSUB. Keep this file: it is the thing you review and maintain. The generated C file is just an intermediate build product, not source of truth.

For a real extension, use the XS file and typemap that extension's own build system selects. Do not paste this tiny example into an existing module without first checking its package name and build metadata.

3. Generate C without risking an existing file

Run xsubpp with the XS path as its final argument. With no output option, the generated C goes to standard output, so redirect it to a new, disposable name first:

$ xsubpp sample.xs > sample.c.new
$ test -s sample.c.new
$ mv sample.c.new sample.c

The first command can create or truncate sample.c.new, which is exactly why that name should be one you can lose. The mv only happens once the command and the non-empty check both succeed. If generation fails, inspect the diagnostic and remove the incomplete temporary file once you are sure you do not need it. Never delete an older sample.c as part of a blind recovery attempt.

On this machine, the generated file opens with an automatic-generation notice and includes the original headers. Confirm the expected XSUB wrapper is actually there:

$ rg -n 'XS_Demo_add|#line' sample.c
160:XS_EUPXS(XS_Demo_add); /* prototype to pass -Wmissing-prototypes */
161:XS_EUPXS(XS_Demo_add)

The exact line numbers shift with the input and the installed Perl release. What matters is a generated wrapper for Demo::add, not the specific line it lands on.

4. Understand line directives and output selection

By default, xsubpp adds C preprocessor #line directives. They let later compiler diagnostics point straight back at the XS input, which is handy while debugging. Keep them, unless another tool downstream specifically needs generated C without them.

To write directly to a chosen file, use -output:

$ xsubpp -output sample.c.new sample.xs
$ test -s sample.c.new
$ mv sample.c.new sample.c

To suppress the line directives, add -nolinenumbers:

$ xsubpp -nolinenumbers -output sample-no-lines.c sample.xs
$ test -s sample-no-lines.c
$ rg -n '^#line' sample-no-lines.c || echo 'no #line directives found'
no #line directives found

Do not mistake any of this for compiling the C. xsubpp never links a shared object or installs a Perl module; a normal XS build then hands off to the module's own build rules and a C compiler.

5. Check typemaps before changing options

xsubpp uses typemaps to translate between C arguments and Perl values. It searches for default files in this relative order, with the rightmost match taking precedence:

../../../typemap:../../typemap:../typemap:typemap

It also falls back to Perl's own installed default typemap. A custom typemap file can quietly change how an otherwise identical XS file gets converted. If an argument or return value is not mapped the way you expect, check the module's build directory first, then pass a deliberate file with -typemap PATH:

$ xsubpp -typemap /path/to/project/typemap -output sample.c.new sample.xs

You can repeat -typemap; the last one wins. Check the path before running this, because selecting the wrong typemap produces valid-looking C with the wrong Perl/C conversions baked in.

Common traps

Options such as -except, -prototypes, -noversioncheck, and -nooptimize all change generated behaviour. Add them only when the module's build instructions actually require them, then read the diff in the generated C to see what changed.

Done means