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 extensionReturns
$selfto 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->namewith 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.