NAME

XS::JIT::Builder - interface for building XS/C code strings

SYNOPSIS

use XS::JIT;
use XS::JIT::Builder;
use File::Temp qw(tempdir);

my $cache_dir = tempdir(CLEANUP => 1);
my $b = XS::JIT::Builder->new;

# Build a simple accessor
$b->xs_function('get_name')
  ->xs_preamble
  ->get_self_hv
  ->hv_fetch('hv', 'name', 4, 'val')
  ->if('val != NULL')
    ->return_sv('*val')
  ->endif
  ->return_undef
  ->xs_end;

# Compile and use
XS::JIT->compile(
    code      => $b->code,
    name      => 'MyClass',
    cache_dir => $cache_dir,
    functions => {
        'MyClass::name' => { source => 'get_name', is_xs_native => 1 },
    },
);

my $obj = bless { name => 'Alice' }, 'MyClass';
print $obj->name;  # prints "Alice"

DESCRIPTION

XS::JIT::Builder provides a interface for generating C code dynamically. It handles indentation automatically and provides convenient methods for common XS patterns.

All methods return $self to enable method chaining. The generated code can be retrieved with code() and passed to XS::JIT->compile().

METHODS

Lifecycle

new(%options)

Create a new builder.

my $b = XS::JIT::Builder->new;
my $b = XS::JIT::Builder->new(use_tabs => 1);
my $b = XS::JIT::Builder->new(indent_width => 2);

Options:

use_tabs

Use tabs for indentation instead of spaces (default: false).

indent_width

Number of spaces per indentation level when not using tabs (default: 4).

code()

Return the accumulated C code as a string.

my $c_code = $b->code;
reset()

Clear the builder and reset state for reuse.

$b->reset;
# Builder is now empty, can build new code
write_file($filename)

Write the accumulated C code to a file. This is useful for:

  • Distribution - Generate the XS file once, ship it with your module

  • Debugging - Inspect the generated code

  • Performance - Skip runtime JIT compilation entirely

$b->write_file('MyModule.c');
# Now you can compile MyModule.c as a regular XS/C extension

Returns $self to enable method chaining.

Low-level Output

line($text)

Add a line with current indentation.

$b->line('int x = 42;');
raw($text)

Add raw text without automatic indentation or newline.

$b->raw('/* inline comment */');
comment($text)

Add a C comment.

$b->comment('This is a comment');
# Generates: /* This is a comment */
blank()

Add a blank line.

XS Structure

xs_function($name)

Start an XS function definition.

$b->xs_function('my_function');
# Generates: XS_EUPXS(my_function) {
xs_preamble()

Add standard XS preamble (dVAR, dXSARGS, etc.).

$b->xs_function('my_func')
  ->xs_preamble;
xs_end()

Close an XS function.

$b->xs_end;
# Generates: }
xs_return($count)

Add XSRETURN statement.

$b->xs_return('1');
# Generates: XSRETURN(1);
xs_return_undef()

Return undef from XS function.

$b->xs_return_undef;
# Generates: ST(0) = &PL_sv_undef; XSRETURN(1);
method_start($name, $min_args, $max_args, $usage)

Start a method with argument checking.

$b->method_start('set_value', 2, 2, '$self, $value');
# Generates function with items check and usage croak

Control Flow

if($condition)

Start an if block.

$b->if('items > 1');
elsif($condition)

Add an elsif clause.

$b->elsif('items == 1');
else()

Add an else clause.

$b->else;
endif()

Close an if/elsif/else block.

$b->if('x > 0')
  ->return_pv('positive')
->endif;
for($init, $cond, $incr)

Start a for loop.

$b->for('int i = 0', 'i < 10', 'i++');
while($condition)

Start a while loop.

$b->while('ptr != NULL');
endloop() / endfor() / endwhile()

Close a loop. All three are aliases.

$b->for('int i = 0', 'i < n', 'i++')
  ->raw('sum += array[i];')
->endfor;
block()

Start a bare block.

$b->block;
# Generates: {
endblock()

Close a bare block.

$b->block
  ->declare_iv('temp', 'x')
  ->raw('x = y; y = temp;')
->endblock;

Variable Declarations

declare($type, $name, $value)

Declare a variable of any type.

$b->declare('int', 'count', '0');
# Generates: int count = 0;
declare_sv($name, $value)

Declare an SV* variable.

$b->declare_sv('arg', 'ST(1)');
# Generates: SV* arg = ST(1);
declare_iv($name, $value)

Declare an IV (integer) variable.

$b->declare_iv('count', 'items - 1');
# Generates: IV count = items - 1;
declare_nv($name, $value)

Declare an NV (double) variable.

$b->declare_nv('total', '0.0');
# Generates: NV total = 0.0;
declare_pv($name, $value)

Declare a const char* variable.

$b->declare_pv('str', 'SvPV_nolen(ST(0))');
# Generates: const char* str = SvPV_nolen(ST(0));
declare_hv($name, $value)

Declare an HV* variable.

$b->declare_hv('hash', '(HV*)SvRV(arg)');
declare_av($name, $value)

Declare an AV* variable.

$b->declare_av('array', '(AV*)SvRV(arg)');
new_hv($name)

Declare and create a new hash.

$b->new_hv('hv');
# Generates: HV* hv = newHV();
new_av($name)

Declare and create a new array.

$b->new_av('results');
# Generates: AV* results = newAV();
assign($var, $value)

Assign a value to an existing variable.

$b->assign('count', 'count + 1');
# Generates: count = count + 1;

Type Checking

check_items($min, $max, $usage)

Check argument count, croak if wrong. Use max=-1 for no upper limit.

$b->check_items(1, 2, '$self, $value?');
check_defined($sv, $error_msg)

Croak if SV is not defined.

$b->check_defined('ST(1)', 'value required');
check_hashref($sv, $error_msg)

Croak if SV is not a hash reference.

$b->check_hashref('ST(0)', 'expected hashref');
check_arrayref($sv, $error_msg)

Croak if SV is not an array reference.

$b->check_arrayref('arg', 'expected arrayref');

SV Conversion

sv_to_iv($result_var, $sv)

Convert SV to integer.

$b->sv_to_iv('count', 'ST(1)');
# Generates: IV count = SvIV(ST(1));
sv_to_nv($result_var, $sv)

Convert SV to double.

$b->sv_to_nv('value', 'ST(1)');
sv_to_pv($result_var, $len_var, $sv)

Convert SV to string with length.

$b->sv_to_pv('str', 'len', 'ST(1)');
# Generates: STRLEN len; const char* str = SvPV(ST(1), len);
sv_to_bool($result_var, $sv)

Convert SV to boolean.

$b->sv_to_bool('flag', 'ST(1)');
new_sv_iv($result_var, $value)

Create new SV from integer.

$b->new_sv_iv('result', 'count');
# Generates: SV* result = newSViv(count);
new_sv_nv($result_var, $value)

Create new SV from double.

$b->new_sv_nv('result', 'total');
new_sv_pv($result_var, $str, $len)

Create new SV from string.

$b->new_sv_pv('result', 'buffer', 'len');
# Generates: SV* result = newSVpvn(buffer, len);

Return Helpers

return_iv($value)

Return an integer value.

$b->return_iv('42');
$b->return_iv('count');
return_nv($value)

Return a double value.

$b->return_nv('3.14');
$b->return_nv('result');
return_pv($str)

Return a string value. The string is automatically quoted if it doesn't start with a letter/underscore (i.e., if it's not a C variable name).

$b->return_pv('hello');      # Returns literal "hello"
$b->return_pv('buffer');     # Returns C variable buffer
$b->return_pv('"quoted"');   # Returns literal "quoted"
return_sv($sv)

Return an SV.

$b->return_sv('result');
$b->return_sv('sv_2mortal(newSViv(42))');
return_yes()

Return true (&PL_sv_yes).

return_no()

Return false (&PL_sv_no).

return_undef()

Return undef.

return_self()

Return the invocant ($self).

$b->return_self;
# For method chaining

Self/Object Methods

get_self()

Get the invocant as SV*.

$b->get_self;
# Generates: SV* self = ST(0);
get_self_hv()

Get the invocant and its underlying hash.

$b->get_self_hv;
# Generates: SV* self = ST(0); HV* hv = (HV*)SvRV(self);
mortal($result_var, $sv_expr)

Create a mortal copy of an SV.

$b->mortal('result', 'newSViv(42)');
# Generates: SV* result = sv_2mortal(newSViv(42));

Hash Operations

hv_fetch($hv, $key, $len, $result_var)

Fetch from a hash with a literal string key.

$b->hv_fetch('hv', 'name', 4, 'val');
# Generates: SV** val = hv_fetch(hv, "name", 4, 0);
hv_fetch_sv($hv, $key_expr, $len_expr, $result_var)

Fetch from a hash with a C expression key.

$b->hv_fetch_sv('hv', 'key', 'key_len', 'val');
# Generates: SV** val = hv_fetch(hv, key, key_len, 0);
hv_store($hv, $key, $len, $value)

Store into a hash with a literal string key.

$b->hv_store('hv', 'name', 4, 'newSVpv("Alice", 0)');
# Generates: (void)hv_store(hv, "name", 4, newSVpv("Alice", 0), 0);
hv_store_sv($hv, $key_expr, $len_expr, $value)

Store into a hash with a C expression key.

$b->hv_store_sv('hv', 'key', 'key_len', 'newSVsv(val)');
hv_fetch_return($hv, $key, $len)

Fetch from hash and return the value (or undef if not found).

$b->hv_fetch_return('hv', 'name', 4);
hv_delete($hv, $key, $len)

Delete a key from a hash.

$b->hv_delete('hv', 'name', 4);
hv_exists($hv, $key, $len, $result_var)

Check if a key exists in a hash.

$b->hv_exists('hv', 'name', 4, 'found');

Array Operations

av_fetch($av, $index, $result_var)

Fetch from an array.

$b->av_fetch('av', 'i', 'elem');
# Generates: SV** elem = av_fetch(av, i, 0);
av_store($av, $index, $value)

Store into an array.

$b->av_store('av', 'i', 'newSViv(42)');
av_push($av, $value)

Push onto an array.

$b->av_push('results', 'newSViv(n)');
av_len($av, $result_var)

Get the highest index of an array.

$b->av_len('av', 'last_idx');
# Generates: SSize_t last_idx = av_len(av);

Prebuilt Patterns

These methods generate complete XS functions for common patterns.

accessor($attr_name, \%options)

Generate a read-write accessor (or read-only with readonly => 1). The function name is the attribute name.

$b->accessor('name');                        # read-write
$b->accessor('id', { readonly => 1 });       # read-only
ro_accessor($func_name, $attr_name, $attr_len)

Generate a complete read-only accessor function.

$b->ro_accessor('get_name', 'name', 4);
rw_accessor($func_name, $attr_name, $attr_len)

Generate a complete read-write accessor function.

$b->rw_accessor('name', 'name', 4);
predicate($attr_name)

Generate a predicate method (has_$attr_name).

$b->predicate('name');
# Generates has_name() that returns true if 'name' key exists
clearer($attr_name)

Generate a clearer method (clear_$attr_name).

$b->clearer('cache');
# Generates clear_cache() that deletes 'cache' key
constructor($func_name, \@attrs)

Generate a constructor. Attrs is arrayref of [$name, $len] pairs or simple attribute name strings.

$b->constructor('new', [
    ['name', 4],
    ['age', 3],
]);

# Or with auto-calculated lengths:
$b->constructor('new', ['name', 'age']);

COMPLETE EXAMPLES

Simple Class with Accessors

use XS::JIT;
use XS::JIT::Builder;
use File::Temp qw(tempdir);

my $cache = tempdir(CLEANUP => 1);
my $b = XS::JIT::Builder->new;

# Constructor
$b->xs_function('person_new')
  ->xs_preamble
  ->new_hv('hv')
  ->if('items >= 2')
    ->hv_store('hv', 'name', 4, 'newSVsv(ST(1))')
  ->endif
  ->if('items >= 3')
    ->hv_store('hv', 'age', 3, 'newSVsv(ST(2))')
  ->endif
  ->raw('SV* self = newRV_noinc((SV*)hv);')
  ->raw('sv_bless(self, gv_stashpv("Person", GV_ADD));')
  ->return_sv('self')
  ->xs_end
  ->blank;

# Name accessor (read-write)
$b->rw_accessor('person_name', 'name', 4)->blank;

# Age accessor (read-only) 
$b->ro_accessor('person_age', 'age', 3);

XS::JIT->compile(
    code      => $b->code,
    name      => 'Person',
    cache_dir => $cache,
    functions => {
        'Person::new'  => { source => 'person_new', is_xs_native => 1 },
        'Person::name' => { source => 'person_name', is_xs_native => 1 },
        'Person::age'  => { source => 'person_age', is_xs_native => 1 },
    },
);

my $p = Person->new('Alice', 30);
say $p->name;        # Alice
$p->name('Bob');     # set name
say $p->age;         # 30

Class with Inheritance

my $b = XS::JIT::Builder->new;

# Base class constructor
$b->xs_function('animal_new')
  ->xs_preamble
  ->new_hv('hv')
  ->hv_store('hv', 'name', 4, 'items > 1 ? newSVsv(ST(1)) : newSVpv("", 0)')
  ->raw('SV* self = newRV_noinc((SV*)hv);')
  ->raw('sv_bless(self, gv_stashpv(SvPV_nolen(ST(0)), GV_ADD));')
  ->return_sv('self')
  ->xs_end
  ->blank;

# Dog constructor (overrides Animal)
$b->xs_function('dog_new')
  ->xs_preamble
  ->new_hv('hv')
  ->hv_store('hv', 'name', 4, 'items > 1 ? newSVsv(ST(1)) : newSVpv("", 0)')
  ->hv_store('hv', 'breed', 5, 'items > 2 ? newSVsv(ST(2)) : newSVpv("mutt", 0)')
  ->raw('SV* self = newRV_noinc((SV*)hv);')
  ->raw('sv_bless(self, gv_stashpv("Dog", GV_ADD));')
  ->return_sv('self')
  ->xs_end;

# Compile and set up inheritance
XS::JIT->compile(...);

package Dog;
use parent -norequire => 'Animal';

Ultra-fast Array-based Objects with Inline Ops

For maximum performance, use array-based objects with inline ops. This bypasses XS call overhead entirely at compile time:

use XS::JIT;
use XS::JIT::Builder qw(:inline);

# Generate op-based accessors
my $b = XS::JIT::Builder->new;
$b->op_ro_accessor('get_name', 0);  # slot 0 is read-only
$b->op_rw_accessor('age', 1);       # slot 1 is read-write

XS::JIT->compile(
    code      => $b->code,
    name      => 'Cat',
    cache_dir => $cache,
    functions => {
        'Cat::name' => { source => 'get_name', is_xs_native => 1 },
        'Cat::age'  => { source => 'age', is_xs_native => 1 },
    },
);

# Register inline ops for compile-time optimization
XS::JIT::Builder::inline_init();
XS::JIT::Builder::inline_register(\&Cat::name, INLINE_GETTER, 0);
XS::JIT::Builder::inline_register(\&Cat::age, INLINE_SETTER, 1);

# Now function calls are replaced with custom ops at compile time!
package Cat;
sub new { bless [$_[1], $_[2]], $_[0] }

package main;
my $cat = Cat->new('Whiskers', 3);
say $cat->name;      # Inline op - no XS call overhead
$cat->age(4);        # Inline setter

Inline Op Types

The following constants are available via :inline export tag:

INLINE_NONE (0)

No inlining.

INLINE_GETTER (1)

Read-only slot accessor. Replaces $obj->name with a custom op that reads directly from $obj->[slot].

INLINE_SETTER (2)

Read-write slot accessor. Supports both getter and setter modes.

INLINE_HV_GETTER (3)

Read-only hash accessor (not yet implemented).

INLINE_HV_SETTER (4)

Read-write hash accessor (not yet implemented).

Custom Op Builder Methods

These methods generate code for building custom Perl ops, enabling compile-time optimization through call checkers.

PP Function Builders

PP (push-pop) functions are the runtime handlers for custom ops.

pp_start($name)

Start a pp function definition.

$b->pp_start('pp_my_op');
# Generates: OP* pp_my_op(pTHX) {
pp_end()

End a pp function with return NORMAL.

$b->pp_end;
# Generates: return NORMAL; }
pp_dsp()

Add dSP declaration for stack access.

$b->pp_dsp;
# Generates: dSP;
pp_get_self()

Get the invocant from the stack without popping.

$b->pp_get_self;
# Generates: SV* self = TOPs;
pp_pop_self()

Pop the invocant from the stack.

$b->pp_pop_self;
# Generates: SV* self = POPs;
pp_pop_sv($name)

Pop an SV from the stack.

$b->pp_pop_sv('value');
# Generates: SV* value = POPs;
pp_pop_nv($name)

Pop a numeric value from the stack.

$b->pp_pop_nv('amount');
# Generates: NV amount = POPn;
pp_pop_iv($name)

Pop an integer value from the stack.

$b->pp_pop_iv('count');
# Generates: IV count = POPi;
pp_get_slots()

Get the slots array from self (for array-based objects).

$b->pp_get_slots;
# Generates: AV* slots = (AV*)SvRV(self);
pp_slot($name, $index)

Access a specific slot from the slots array.

$b->pp_slot('name_sv', 0);
# Generates: SV* name_sv = *av_fetch(slots, 0, 0);
pp_return_sv($expr)

Return an SV to the stack.

$b->pp_return_sv('name_sv');
# Generates: SETs(name_sv); RETURN;
pp_return_nv($expr)

Return a numeric value.

$b->pp_return_nv('result');
# Generates: SETn(result); RETURN;
pp_return_iv($expr)

Return an integer value.

$b->pp_return_iv('count');
# Generates: SETi(count); RETURN;
pp_return_pv($expr)

Return a string value.

$b->pp_return_pv('str');
# Generates: SETs(sv_2mortal(newSVpv(str, 0))); RETURN;
pp_return()

Return without modifying the stack.

$b->pp_return;
# Generates: return NORMAL;

Call Checker Builders

Call checkers run at compile time to replace subroutine calls with custom ops.

ck_start($name)

Start a call checker function.

$b->ck_start('ck_my_method');
# Generates: OP* ck_my_method(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
ck_end()

End a call checker with fallback.

$b->ck_end;
# Generates: return ck_entersub_args_proto_or_list(...); }
ck_preamble()

Add standard call checker preamble to extract arguments.

$b->ck_preamble;
# Extracts pushmark, args, method from the optree
ck_build_unop($pp_func, $targ_expr)

Build a custom unary op (one argument).

$b->ck_build_unop('pp_my_getter', 'slot_num');
# Generates code to create OP_CUSTOM with the specified pp function
ck_build_binop($pp_func, $targ_expr)

Build a custom binary op (two arguments).

$b->ck_build_binop('pp_my_setter', 'slot_num');
ck_fallback()

Fall through to default call checking.

$b->ck_fallback;
# Generates: return ck_entersub_args_proto_or_list(...);

XOP Helpers

XOP (extended op) declarations register custom ops with Perl.

xop_declare($name, $pp_func, $desc)

Declare a custom op descriptor.

$b->xop_declare('my_xop', 'pp_my_op', 'my custom op');
# Generates static XOP declaration and registration
register_checker($cv_expr, $ck_func, $ckobj_expr)

Register a call checker for a CV.

$b->register_checker('cv', 'ck_my_method', 'newSViv(slot)');
# Generates: cv_set_call_checker(cv, ck_my_method, ckobj);

Custom Op Example

my $b = XS::JIT::Builder->new;

# Declare the XOP
$b->xop_declare('slot_getter_xop', 'pp_slot_getter', 'slot getter');

# Build the pp function
$b->pp_start('pp_slot_getter')
  ->pp_dsp
  ->pp_get_self
  ->pp_get_slots
  ->line('IV slot = PL_op->op_targ;')
  ->pp_slot('val', 'slot')
  ->pp_return_sv('val')
  ->pp_end;

# Build the call checker
$b->ck_start('ck_slot_getter')
  ->ck_preamble
  ->ck_build_unop('pp_slot_getter', 'SvIV(ckobj)')
  ->ck_end;

SEE ALSO

XS::JIT - The main JIT compiler module

The C API is available in xs_jit_builder.h for direct use from XS code.

AUTHOR

LNATION <email@lnation.org>

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.