NAME

XS::JIT - Lightweight JIT compiler for XS code

VERSION

Version 0.03

SYNOPSIS

use XS::JIT;

# Compile C code and install functions
XS::JIT->compile(
    code      => $c_code,
    name      => 'MyModule::JIT::Foo',
    functions => {
        # Simple form - XS::JIT generates a wrapper
        'Foo::get' => 'jit_get',

        # Hashref form - for XS-native functions (no wrapper)
        'Foo::new'  => { source => 'jit_new',  is_xs_native => 1 },
        'Foo::name' => { source => 'jit_name', is_xs_native => 1 },
    },
    cache_dir => '_CACHED_XS',  # optional, defaults to _CACHED_XS
    force     => 0,             # optional, force recompile
);

DESCRIPTION

XS::JIT is a lightweight alternative to Inline::C for runtime JIT compilation of XS code. It's specifically optimized for use cases where you're generating C code dynamically and need to compile and load it at runtime.

Unlike Inline::C, XS::JIT:

  • Skips C code parsing (no Parse::RecDescent dependency)

  • Skips xsubpp (generates C code directly)

  • Uses direct compiler invocation (no make/Makefile.PL)

  • Provides a C API for use from other XS modules

METHODS

compile

my $ok = XS::JIT->compile(%options);

Compiles C code and installs the specified functions into Perl namespaces.

Options:

code (required)

The C source code to compile. This should be valid C code that uses the Perl C API (EXTERN.h, perl.h, XSUB.h are included automatically).

name (required)

A unique module name for caching purposes (e.g., "MyApp::JIT::Class_0"). This is used to generate the boot function name and cache path.

functions (required)

A hashref mapping target Perl function names to source C function names. Values can be either a simple string (the C function name) or a hashref with options:

# Simple string form
functions => {
    'Package::method' => 'c_function_name',
}

# Hashref form with options
functions => {
    'Package::method' => {
        source       => 'c_function_name',
        is_xs_native => 1,  # function handles XS stack itself
    },
}

The is_xs_native option is important for performance. Set it to 1 when your C function is already written as a proper XS function using XS_EUPXS(), dXSARGS, ST(), and XSRETURN(). This tells XS::JIT to create a simple alias instead of generating a wrapper function, avoiding any overhead.

cache_dir

Optional. Directory for caching compiled modules. Defaults to "_CACHED_XS".

force

Optional. If true, forces recompilation even if a cached version exists.

Returns 1 on success, 0 on failure.

is_cached

my $cached = XS::JIT->is_cached($code, $name, $cache_dir);

Checks if a compiled module exists in the cache.

Arguments:

$code

The C source code.

$name

The module name.

$cache_dir

Optional. Cache directory. Defaults to "_CACHED_XS".

Returns 1 if cached, 0 otherwise.

generate_code

my $c_code = XS::JIT->generate_code($user_code, $name, \%functions);

Generates the complete C source code with XS wrappers and boot function, without compiling it. Useful for debugging or custom build processes.

Arguments:

$user_code

The user's C source code.

$name

The module name (used for boot function naming).

\%functions

A hashref mapping Perl function names to C function names.

Returns the complete generated C code as a string.

inc_dir

my $dir = XS::JIT->inc_dir();

Returns the directory containing the XS::JIT header file (xs_jit.h). This is useful for modules that want to use the XS::JIT C API.

Dies if the installation directory cannot be found.

cflags

my $cflags = XS::JIT->cflags();

Returns compiler flags needed to compile code that uses the XS::JIT C API. Currently returns -I/path/to/xs_jit.h.

libs

my $libs = XS::JIT->libs();

Returns linker flags needed for XS::JIT in the format -L/path -lxs_jit.

static_libs

my $lib_path = XS::JIT->static_libs();

Returns the full path to the static library libxs_jit.a, or undef if not found. Useful for build systems that prefer explicit static linking.

Use both cflags and libs in your Makefile.PL:

use XS::JIT;
WriteMakefile(
    ...
    INC  => XS::JIT->cflags(),
    LIBS => [XS::JIT->libs()],
);

WRITING C FUNCTIONS

XS::JIT supports two styles of C functions: wrapper-style functions that return an SV*, and XS-native functions that handle the stack directly.

Wrapper-Style Functions (default)

These functions take SV* self as the first argument and return an SV*. XS::JIT generates a wrapper that handles the Perl stack:

SV* my_getter(SV* self) {
    dTHX;
    /* self is the invocant (class or object) */
    return newSVpv("hello", 0);
}

XS-Native Functions (recommended for performance)

For best performance, write functions using the XS conventions directly and set is_xs_native => 1 in the function mapping. This avoids wrapper overhead entirely:

XS_EUPXS(my_getter) {
    dVAR; dXSARGS;
    PERL_UNUSED_VAR(cv);
    SV* self = ST(0);
    HV* hv = (HV*)SvRV(self);
    SV** valp = hv_fetch(hv, "value", 5, 0);
    ST(0) = (valp && *valp) ? *valp : &PL_sv_undef;
    XSRETURN(1);
}

Register with:

functions => {
    'Package::getter' => { source => 'my_getter', is_xs_native => 1 },
}

Functions with Variable Arguments

Use JIT_ARGS or dTHX; dXSARGS to access additional arguments:

SV* my_setter(SV* self, ...) {
    JIT_ARGS;

    if (items < 2) {
        croak("Value required");
    }

    SV* value = ST(1);  /* First argument after self */
    /* ... do something with value ... */
    return newSVsv(value);
}

Returning Self for Method Chaining

When returning self for method chaining, you must increment the reference count:

SV* my_chainable(SV* self, ...) {
    JIT_ARGS;
    /* ... modify object ... */
    SvREFCNT_inc(self);
    return self;
}

Creating Objects

SV* my_constructor(SV* class_sv, ...) {
    dTHX;
    const char* classname = SvPV_nolen(class_sv);
    HV* self_hv = newHV();

    /* Store attributes */
    hv_store(self_hv, "attr", 4, newSViv(0), 0);

    /* Bless and return */
    return sv_bless(newRV_noinc((SV*)self_hv),
                    gv_stashpv(classname, GV_ADD));
}

C API

XS::JIT provides a C API that can be used directly from other XS modules without Perl stack overhead. Include the header file:

#include "xs_jit.h"

The function mapping structure:

typedef struct {
    const char *target;   /* "Package::funcname" - where to install */
    const char *source;   /* "c_func_name" - function in user's C code */
    int has_varargs;      /* 1 if function takes variable arguments */
    int is_xs_native;     /* 1 if function is already XS-native */
} XS_JIT_Func;

Example usage:

XS_JIT_Func funcs[] = {
    { "Foo::new",  "jit_new",  0, 1 },  /* XS-native, no wrapper */
    { "Foo::name", "jit_name", 0, 1 },  /* XS-native, no wrapper */
    { NULL, NULL, 0, 0 }
};

xs_jit_compile(aTHX_ c_code, "MyModule::JIT::Foo",
               funcs, 2, NULL, 0);

Set is_xs_native to 1 when your functions use XS_EUPXS(), dXSARGS, and XSRETURN() directly. This creates simple aliases instead of wrappers.

The header file location can be found programmatically:

use XS::JIT;
print XS::JIT->inc_dir(), "\n";

Or use the cflags method in your Makefile.PL:

use XS::JIT;
WriteMakefile(
    ...
    INC => XS::JIT->cflags(),
);

CONVENIENCE MACROS

JIT_ARGS

The JIT_ARGS macro initializes both the thread context and the XS argument stack in a single statement. Use this at the start of functions that need to access variable arguments:

SV* my_function(SV* self, ...) {
    JIT_ARGS;  /* expands to: dTHX; dXSARGS */

    if (items < 2) croak("Need at least one argument");
    SV* arg = ST(1);
    return newSVsv(arg);
}

This is equivalent to:

SV* my_function(SV* self, ...) {
    dTHX;
    dXSARGS;
    ...
}

INLINE::C COMPATIBILITY

XS::JIT provides the following macros for compatibility with code written for Inline::C:

Inline_Stack_Vars     - equivalent to dXSARGS
Inline_Stack_Items    - number of arguments (items)
Inline_Stack_Item(x)  - get argument x (ST(x))
Inline_Stack_Reset    - reset stack pointer (sp = mark)
Inline_Stack_Push(x)  - push value onto stack (XPUSHs(x))
Inline_Stack_Done     - finalize stack (PUTBACK)
Inline_Stack_Return(x) - return x values (XSRETURN(x))
Inline_Stack_Void     - return no values (XSRETURN(0))

BENCHMARK

============================================================
XS::JIT vs Inline::C Benchmark
============================================================

Testing XS::JIT...
----------------------------------------
  First compile:  0.3311 seconds
  Already loaded: 0.000026 seconds
  Runtime (10k iterations): 0.0094 seconds

Testing Inline::C...
----------------------------------------
  First compile:  0.7568 seconds
  Runtime (10k iterations): 0.0127 seconds

============================================================
Summary
============================================================
First compile speedup: 2.3x faster
Runtime performance:   1.36x faster (XS::JIT)

SEE ALSO

Inline::C - The original runtime C compiler for Perl (which has more features)

perlxs - XS language reference

perlguts - Perl internal functions for XS programming

perlapi - Perl API listing

AUTHOR

LNATION <email@lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)