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.
The route
Jump straight to the step you need, or tick off Done means at the end.
- Time: about 15 minutes for this smoke test, if Perl is already installed.
- You need: a shell, the
perlpackage, and a writable working directory. None of this needssudo. - Careful: do not run these commands in a source tree that has an uncommitted generated file you cannot afford to lose.
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
- Unknown option. Run
xsubpp -vand compare the installed tool against the build instructions. Options are version-specific; do not paste a flag from another Perl release without checking first. - Unexpected prototype warning. Decide the module's prototype policy and use an XS
PROTOTYPES:declaration that matches it. Do not silence the warning by hiding the generated output instead. - Missing conversion. Check the selected typemap and the C type before anything else. Adding
-noargtypesor-noinoutdisables recognised syntax; it does not fix a bad mapping. - Wrong generated file. Check the working directory, the input path, and the
-outputvalue. xsubpp reads the file you name and writes either the requested file or standard output, nothing else.
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
- Recorded versions: you noted the installed Perl and xsubpp versions.
- Source stays authoritative: your XS file is untouched and the generated C is non-empty.
- Wrapper confirmed: the generated C contains the expected XSUB wrapper.
- Know your settings: you know whether
#linedirectives and any custom typemap are in play. - Safe on failure: a failed conversion cannot overwrite the previous generated file.