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

This module is experimental, the api and the code generated will evolve over time.

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_store_weak($hv, $key, $key_len, $value_expr)

Store a value into a hash, weakening it if it's a reference. Useful for parent/owner pointers that should not hold strong references.

$b->hv_store_weak('hv', 'parent', 6, 'newSVsv(ST(1))');
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);

Callbacks & Triggers

Methods for calling Perl code from generated XS functions.

call_sv($cv_expr, \@args)

Generate code to call a coderef with given arguments. Uses G_DISCARD so return values are discarded. Useful for calling callbacks, event handlers, or triggers.

$b->call_sv('cb', ['self', 'new_val']);
# Generates: dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(self); ...
call_method($method_name, $invocant, \@args)

Generate code to call a method by name on an invocant. Uses G_DISCARD so return values are discarded. Useful for calling hooks like BUILD, DEMOLISH, triggers, or callbacks.

$b->call_method('_on_change', 'self', ['old_val', 'new_val']);
# Generates: call_method("_on_change", G_DISCARD);
rw_accessor_trigger($func_name, $attr, $len, $trigger_method)

Generate a read/write accessor that calls a trigger method after setting. The trigger method is called with the object and new value as arguments.

$b->rw_accessor_trigger('MyClass::name', 'name', 4, '_on_name_change');
# Accessor calls $self->_on_name_change($new_val) after setting
accessor_lazy_builder($func_name, $attr, $len, $builder_method)

Generate a read/write accessor with lazy initialization. On first read, if the value is undefined, calls the builder method to compute and cache the value.

$b->accessor_lazy_builder('MyClass::computed', 'computed', 8, '_build_computed');
# First read calls $self->_build_computed() and caches result
destroy_with_demolish($func_name)

Generate a DESTROY method that checks for and calls DEMOLISH if it exists. Passes $in_global_destruction as the second argument to DEMOLISH.

$b->destroy_with_demolish('MyClass::DESTROY');
# DESTROY calls $self->DEMOLISH($in_global_destruction) if defined

Control Flow & Extended Patterns

Advanced control flow helpers and expression builders.

do_loop() / end_do_while($condition)

Generate a do-while loop that executes at least once.

$b->do_loop
  ->line('/* loop body */')
->end_do_while('ptr != NULL');
# Generates: do { ... } while (ptr != NULL);
if_list_context()

Branch on list context. Use with else and endif.

$b->if_list_context
  ->return_list(['key_sv', 'val_sv'])
->else
  ->line('ST(0) = val_sv;')
  ->line('XSRETURN(1);')
->endif;
if_scalar_context()

Branch on scalar context. Use with else and endif.

$b->if_scalar_context
  ->line('ST(0) = count_sv;')
->else
  ->return_list(['@items'])
->endif;
extend_stack($count_expr)

Extend the stack to hold more return values.

$b->extend_stack('num_items');
# Generates: EXTEND(SP, num_items);
return_list(\@values)

Return multiple values from XS, handling stack extension and mortalization.

$b->return_list(['newSViv(1)', 'newSViv(2)', 'newSViv(3)']);
# Generates: EXTEND(SP, 3); ST(0) = sv_2mortal(...); ... XSRETURN(3);
declare_ternary($type, $name, $cond, $true_expr, $false_expr)

Declare a variable with ternary initialization.

$b->declare_ternary('SV*', 'val', 'items > 1', 'ST(1)', '&PL_sv_undef');
# Generates: SV* val = (items > 1) ? ST(1) : &PL_sv_undef;
assign_ternary($var, $cond, $true_expr, $false_expr)

Ternary assignment to existing variable.

$b->assign_ternary('result', 'found', '*svp', '&PL_sv_undef');
# Generates: result = (found) ? *svp : &PL_sv_undef;
delegate_method($func_name, $attr, $len, $target_method)

Generate a method that delegates to an attribute's method. Passes through all arguments and returns the result.

$b->delegate_method('get_name', 'delegate_obj', 12, 'name');
# Generates: sub get_name { $self->{delegate_obj}->name(@_) }

Singleton Pattern

Methods for implementing the singleton design pattern.

singleton_accessor($func_name, $class_name)

Generate a class method that returns the singleton instance, creating it on first access. The instance is stored in a package variable $Class::_instance.

$b->singleton_accessor('instance', 'MyApp::Config');
# MyApp::Config->instance always returns same object
singleton_reset($func_name, $class_name)

Generate a class method that clears the singleton instance. The next call to the singleton accessor will create a fresh instance.

$b->singleton_reset('reset_instance', 'MyApp::Config');
# MyApp::Config->reset_instance clears the singleton

Registry Pattern

These methods generate registry pattern accessors for storing and retrieving items in a hash attribute by key.

registry_add($func_name, $registry_attr)

Generate a method to add an item to a registry hash. Creates the registry hash automatically if it doesn't exist. Returns $self for chaining.

$b->registry_add('register_handler', '_handlers');
# Usage: $obj->register_handler(click => sub { ... });
registry_get($func_name, $registry_attr)

Generate a method to retrieve an item from a registry hash by key. Returns undef if the key doesn't exist.

$b->registry_get('get_handler', '_handlers');
# Usage: my $handler = $obj->get_handler('click');
registry_remove($func_name, $registry_attr)

Generate a method to remove and return an item from a registry hash. Returns undef if the key doesn't exist.

$b->registry_remove('unregister_handler', '_handlers');
# Usage: my $old = $obj->unregister_handler('click');
registry_all($func_name, $registry_attr)

Generate a context-aware method to retrieve all registry items. In list context, returns key-value pairs. In scalar context, returns a shallow copy of the registry as a hashref.

$b->registry_all('all_handlers', '_handlers');
# List context: my %handlers = $obj->all_handlers;
# Scalar context: my $hashref = $obj->all_handlers;

Method Modifiers

These methods generate Moose/Moo-style method modifiers that wrap existing methods with before, after, or around hooks.

wrap_before($func_name, $orig_name, $before_cv_name)

Generate a wrapper that calls a "before" hook before the original method. The before hook receives the same arguments as the original. Its return value is discarded. The original's return value is preserved.

$b->wrap_before('save_with_log', 'MyClass::_orig_save', 'MyClass::_log_before');
# Calls _log_before(@args), then _orig_save(@args)
wrap_after($func_name, $orig_name, $after_cv_name)

Generate a wrapper that calls an "after" hook after the original method. The after hook receives the same arguments as the original. Its return value is discarded. The original's return value is preserved.

$b->wrap_after('save_with_notify', 'MyClass::_orig_save', 'MyClass::_notify_after');
# Calls _orig_save(@args), then _notify_after(@args)
wrap_around($func_name, $orig_name, $around_cv_name)

Generate a wrapper with "around" semantics. The around hook receives the original coderef as its first argument, followed by the original arguments. The around hook has full control and can modify arguments, skip the original, or modify return values.

$b->wrap_around('save_cached', 'MyClass::_orig_save', 'MyClass::_cache_around');
# _cache_around receives ($orig, @args)
# Can call: $orig->(@args) or skip or modify

Role/Mixin Composer

Generate multiple related methods that compose behavioral patterns.

role($role_name, \%opts)

Generate all methods for a single role. Available roles:

Comparable - Comparison methods (compare by a key attribute):

$b->role('Comparable');                        # Uses 'id' as compare key
$b->role('Comparable', { compare_key => 'name' });  # Uses 'name'

Generates: compare($other), equals($other), lt($other), gt($other), le($other), ge($other).

Cloneable - Object cloning:

$b->role('Cloneable');

Generates: clone() - shallow clone of hash-based object.

Serializable - Serialization methods:

$b->role('Serializable');

Generates: TO_JSON(), TO_HASH() - return hashref copy (JSON::XS compatible).

Observable - Observer pattern:

$b->role('Observable');
$b->role('Observable', { observers_attr => '_watchers' });

Generates: add_observer($callback), remove_observer($callback), notify_observers(@args).

with_roles(\@roles, \%opts)

Compose multiple roles in a single call:

$b->with_roles(['Comparable', 'Cloneable', 'Serializable']);

# With options:
$b->with_roles(['Comparable', 'Observable'], {
    compare_key    => 'name',
    observers_attr => '_listeners',
});

This is a convenience method that calls role() for each role in the list.

Prebuilt Patterns

These methods generate complete XS functions for common patterns.

Constructors

new_simple($func_name)

Generate a minimal constructor: bless {}, $class. Fastest possible constructor with no argument processing.

$b->new_simple('new');
# Generates: my $obj = Class->new;
new_hash($func_name)

Generate a flexible constructor that accepts either flat hash or hashref args. All provided args are copied into the object.

$b->new_hash('new');
# Supports both:
# my $obj = Class->new(name => 'Alice', age => 30);
# my $obj = Class->new({ name => 'Alice', age => 30 });
new_array($func_name, $num_slots)

Generate an array-based constructor (Meow-style). Creates a blessed arrayref with pre-allocated slots initialized to undef.

$b->new_array('new', 5);
# my $obj = Class->new;  # $obj->[0..4] are undef
new_with_required($func_name, \@required_attrs)

Generate a constructor that validates required attributes. Croaks if any required attribute is missing or undef.

$b->new_with_required('new', ['name', 'id']);
# Class->new(name => 'Alice');  # croaks: "Missing required attribute 'id'"
# Class->new(name => 'Alice', id => 123);  # OK

Accepts either flat hash or hashref arguments, like new_hash.

new_with_build($func_name)

Generate a constructor that calls BUILD if it exists. Moose/Moo compatible. Accepts flat hash or hashref, and passes the args hash to BUILD.

$b->new_with_build('new');
# Supports:
# my $obj = Class->new(name => 'Alice');
# Calls: $obj->BUILD(\%args) if BUILD is defined
new_complete($func_name, \@attr_specs, $call_build)

Generate a unified constructor with full Moose/Moo-like attribute handling. Supports required, defaults, basic types, weak refs, coercion, and BUILD in one call.

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

$b->new_complete('new', [
    {
        name     => 'id',
        required => 1,
        type     => TYPE_INT,
        type_msg => 'id must be an integer',
    },
    {
        name       => 'name',
        default_pv => 'anonymous',
    },
    {
        name       => 'items',
        default_av => 1,           # empty []
        type       => TYPE_ARRAYREF,
    },
    {
        name       => 'meta',
        default_hv => 1,           # empty {}
    },
    {
        name       => 'count',
        default_iv => 0,
    },
    {
        name => 'parent',
        weak => 1,                 # weaken stored reference
    },
    {
        name   => 'age',
        coerce => 'to_int',        # call Class->to_int($val)
    },
], 1);  # 1 = call BUILD if exists

Attribute spec options:

name       - (required) Attribute name
required   - Croak if missing or undef
type       - TYPE_* constant for validation
type_msg   - Error message for type failure
weak       - Weaken stored reference (prevents circular refs)
coerce     - Method name to call for coercion
default_iv - Default integer value
default_nv - Default numeric value
default_pv - Default string value
default_av - If true, default to empty []
default_hv - If true, default to empty {}

Processing order: coercion → required check → type validation → defaults → weak refs → BUILD.

constructor($func_name, \@attrs)

Generate a constructor with specific attributes. 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']);

Accessors

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);
rw_accessor_typed($func_name, $attr_name, $attr_len, $type, $error_msg)

Generate a read-write accessor with inline type validation. On set, validates the value against the specified type and croaks with $error_msg if invalid.

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

$b->rw_accessor_typed('age', 'age', 3, TYPE_INT, 'age must be an integer');
$b->rw_accessor_typed('items', 'items', 5, TYPE_ARRAYREF, 'items must be an arrayref');

Type constants (from :types export tag):

TYPE_ANY        - No validation
TYPE_DEFINED    - Must be defined (not undef)
TYPE_INT        - Must be an integer
TYPE_NUM        - Must be a number
TYPE_STR        - Must be a string (not a reference)
TYPE_REF        - Must be a reference
TYPE_ARRAYREF   - Must be an arrayref
TYPE_HASHREF    - Must be a hashref
TYPE_CODEREF    - Must be a coderef
TYPE_OBJECT     - Must be a blessed object

Note: undef values bypass type checking (except for TYPE_DEFINED).

rw_accessor_weak($func_name, $attr_name, $attr_len)

Generate a read-write accessor that auto-weakens stored references. Use this for parent/owner references to prevent circular reference leaks.

$b->rw_accessor_weak('parent', 'parent', 6);
# $child->parent($parent);  # stored as weak reference
# Prevents: $parent->{children} = [$child]; $child->{parent} = $parent;

The reference is weakened only if it's actually a reference. Scalars and undef are stored normally.

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

Cloning

clone_hash($func_name)

Generate a shallow clone method for hash-based objects. Creates a new object with copies of all key/value pairs, blessed into the same class.

$b->clone_hash('clone');
# my $copy = $obj->clone;
# $copy is independent - modifying it doesn't affect $obj

Note: This is a shallow clone. Nested references point to the same data.

clone_array($func_name)

Generate a shallow clone method for array-based objects. Creates a new object with copies of all elements, blessed into the same class.

$b->clone_array('clone');
# my $copy = $obj->clone;  # blessed arrayref copy

Note: This is a shallow clone. Nested references point to the same data.

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;

Direct AvARRAY Access

These methods generate code for ultra-fast array slot access, bypassing av_fetch/av_store. Inspired by Meow's optimized accessors.

av_direct($result_var, $av_expr)

Get direct array pointer for fast slot access.

$b->av_direct('slots', '(AV*)SvRV(self)');
# Generates: SV** slots = AvARRAY((AV*)SvRV(self));
av_slot_read($result_var, $slots_var, $slot)

Read a slot value with undef fallback.

$b->av_slot_read('val', 'slots', 0);
# Generates: SV* val = slots[0] ? slots[0] : &PL_sv_undef;
av_slot_write($slots_var, $slot, $value)

Write a value to a slot with ref counting.

$b->av_slot_write('slots', 0, 'new_val');
# Generates proper SvREFCNT_dec and SvREFCNT_inc

Type Checking

Generate type validation code. Constants can be exported via :types tag.

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

$b->check_value_type('val', TYPE_ARRAYREF, undef, 'value must be an arrayref');
check_value_type($sv, $type, $classname, $error_msg)

Generate a type check with croak on failure.

$b->check_value_type('ST(1)', TYPE_HASHREF, undef, 'Expected hashref');
# Generates: if (!(SvROK(ST(1)) && SvTYPE(SvRV(ST(1))) == SVt_PVHV)) croak(...);

Type constants:

TYPE_ANY        - No check
TYPE_DEFINED    - SvOK
TYPE_INT        - SvIOK
TYPE_NUM        - SvNOK or SvIOK
TYPE_STR        - SvPOK
TYPE_REF        - SvROK
TYPE_ARRAYREF   - SvROK + SVt_PVAV
TYPE_HASHREF    - SvROK + SVt_PVHV
TYPE_CODEREF    - SvROK + SVt_PVCV
TYPE_OBJECT     - sv_isobject
TYPE_BLESSED    - sv_derived_from($classname)

Lazy Initialization

Generate accessors that lazily initialize with a default value.

lazy_init_dor($func_name, $attr_name, $attr_len, $default_expr, $is_mortal)

Generate lazy init accessor using //= (defined-or-assign).

$b->lazy_init_dor('MyClass_items', 'items', 5, 'newRV_noinc((SV*)newAV())', 0);
# sub items { $self->{items} //= [] }
lazy_init_or($func_name, $attr_name, $attr_len, $default_expr, $is_mortal)

Generate lazy init accessor using ||= (or-assign).

$b->lazy_init_or('MyClass_name', 'name', 4, 'newSVpvn("default", 7)', 0);
# sub name { $self->{name} ||= 'default' }
slot_lazy_init_dor($func_name, $slot, $default_expr, $is_mortal)

Slot-based lazy init with //=.

$b->slot_lazy_init_dor('get_cache', 2, 'newRV_noinc((SV*)newHV())', 0);
slot_lazy_init_or($func_name, $slot, $default_expr, $is_mortal)

Slot-based lazy init with ||=.

Setter Patterns

setter_chain($func_name, $attr_name, $attr_len)

Generate a setter that returns $self for chaining.

$b->setter_chain('set_name', 'name', 4);
# $obj->set_name('foo')->set_age(42)
slot_setter_chain($func_name, $slot)

Slot-based setter chain.

setter_return_value($func_name, $attr_name, $attr_len)

Generate a setter that returns the value set.

$b->setter_return_value('set_name', 'name', 4);
# my $v = $obj->set_name('foo');  # $v is 'foo'

Array Attribute Operations

Generate methods for manipulating array attributes in hash-based objects.

attr_push($func_name, $attr_name, $attr_len)
$b->attr_push('add_item', 'items', 5);
# push @{$self->{items}}, @values
attr_pop($func_name, $attr_name, $attr_len)
$b->attr_pop('pop_item', 'items', 5);
# pop @{$self->{items}}
attr_shift($func_name, $attr_name, $attr_len)
$b->attr_shift('shift_item', 'items', 5);
# shift @{$self->{items}}
attr_unshift($func_name, $attr_name, $attr_len)
$b->attr_unshift('prepend_item', 'items', 5);
# unshift @{$self->{items}}, @values
attr_count($func_name, $attr_name, $attr_len)
$b->attr_count('item_count', 'items', 5);
# scalar @{$self->{items}}
attr_clear($func_name, $attr_name, $attr_len)
$b->attr_clear('clear_items', 'items', 5);
# @{$self->{items}} = ()

Hash Attribute Operations

Generate methods for manipulating hash attributes.

attr_keys($func_name, $attr_name, $attr_len)
$b->attr_keys('cache_keys', 'cache', 5);
# keys %{$self->{cache}}
attr_values($func_name, $attr_name, $attr_len)
$b->attr_values('cache_values', 'cache', 5);
# values %{$self->{cache}}
attr_delete($func_name, $attr_name, $attr_len)
$b->attr_delete('delete_cache', 'cache', 5);
# delete $self->{cache}{$key}
attr_hash_clear($func_name, $attr_name, $attr_len)
$b->attr_hash_clear('clear_cache', 'cache', 5);
# %{$self->{cache}} = ()

Conditional DSL (Struct::Conditional Format)

Generate conditional C code from declarative Perl data structures. This uses the same format as Struct::Conditional, allowing you to define conditionals as data rather than imperative code.

conditional(\%struct)

Generate C code from a Struct::Conditional-compatible hashref. Supports if/elsif/else and given/when patterns.

Basic if/else:

$b->conditional({
    if => {
        key  => 'num',      # C variable to test
        gt   => 0,          # expression: greater than
        then => {
            line => 'XSRETURN_IV(1);'
        }
    },
    else => {
        then => {
            line => 'XSRETURN_IV(0);'
        }
    }
});

if/elsif/else chain:

$b->conditional({
    if => {
        key  => 'type_sv',
        eq   => 'int',
        then => { line => 'handle_int(val);' }
    },
    elsif => {
        key  => 'type_sv',
        eq   => 'str',
        then => { line => 'handle_str(val);' }
    },
    else => {
        then => { line => 'handle_default(val);' }
    }
});

Multiple elsif (as arrayref):

$b->conditional({
    if => { key => 'n', gt => 100, then => { return_iv => '3' } },
    elsif => [
        { key => 'n', gt => 50, then => { return_iv => '2' } },
        { key => 'n', gt => 0,  then => { return_iv => '1' } },
    ],
    else => { then => { return_iv => '0' } }
});

given/when (switch-style):

$b->conditional({
    given => {
        key => 'type_sv',
        when => {
            int     => { line => 'XSRETURN_IV(1);' },
            str     => { line => 'XSRETURN_IV(2);' },
            array   => { line => 'XSRETURN_IV(3);' },
            default => { line => 'XSRETURN_IV(0);' }
        }
    }
});

given/when with array (ordered matching):

$b->conditional({
    given => {
        key => 'country',
        when => [
            { m => 'Thai', then => { return_iv => '1' } },
            { m => 'Indo', then => { return_iv => '2' } },
        ],
        default => { return_iv => '0' }
    }
});

Logical chaining (and/or):

$b->conditional({
    if => {
        key => 'x',
        gt  => 0,
        and => {
            key => 'y',
            gt  => 0
        },
        then => { line => 'handle_positive_quadrant();' }
    }
});

$b->conditional({
    if => {
        key => 'status',
        eq  => 'active',
        or  => {
            key => 'status',
            eq  => 'pending'
        },
        then => { line => 'process();' }
    }
});

Expression operators:

gt   => N     # SvIV(key) > N
lt   => N     # SvIV(key) < N
gte  => N     # SvIV(key) >= N
lte  => N     # SvIV(key) <= N
eq   => 'str' # strEQ(SvPV_nolen(key), "str")
ne   => 'str' # !strEQ(...)
m    => 'pat' # strstr(SvPV_nolen(key), "pat") != NULL
im   => 'pat' # case-insensitive match
nm   => 'pat' # NOT match
inm  => 'pat' # case-insensitive NOT match
exists => 1   # SvOK(key)
true   => 1   # SvTRUE(key)

Then block actions:

then => { line => '...' }        # raw C line
then => { return_iv => 'N' }     # XSRETURN_IV(N)
then => { return_nv => 'N' }     # return double
then => { return_pv => '"str"' } # XSRETURN_PV(str)
then => { return_sv => 'expr' }  # return SV expression
then => { croak => 'message' }   # croak("message")
then => [ {...}, {...} ]         # multiple actions

Complete Conditional Example

use XS::JIT;
use XS::JIT::Builder;

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

$b->xs_function('classify_number')
  ->xs_preamble
  ->declare_iv('num', 'SvIV(ST(0))')
  ->conditional({
      if => {
          key => 'num',
          gt  => 0,
          then => { return_pv => '"positive"' }
      },
      elsif => {
          key => 'num',
          lt  => 0,
          then => { return_pv => '"negative"' }
      },
      else => {
          then => { return_pv => '"zero"' }
      }
  })
  ->xs_end;

XS::JIT->compile(
    code => $b->code,
    name => 'NumClass',
    functions => {
        'NumClass::classify' => { source => 'classify_number', is_xs_native => 1 }
    }
);

say NumClass::classify(42);   # "positive"
say NumClass::classify(-5);   # "negative"
say NumClass::classify(0);    # "zero"

Switch Statement Helper

The switch method provides an optimized way to generate multi-branch conditionals on a single key, avoiding Perl's hash duplicate-key limitation with elsif. It automatically detects whether all cases use the same comparison type and applies optimizations.

switch($key, \@cases, [\%default])

Generate optimized switch-style conditional code. The key is a C variable name, cases is an arrayref of clause hashrefs, and default is an optional hashref of actions for the default case.

Basic string switch:

$b->switch('type_str', [
    { eq => 'int',   then => { return_iv => '1' } },
    { eq => 'str',   then => { return_iv => '2' } },
    { eq => 'array', then => { return_iv => '3' } },
], { return_iv => '0' });

This generates optimized C code that:

  • Caches SvPV() once at the start

  • Uses memEQ() with length pre-check for efficient string comparison

  • Generates an if/elsif/else chain

Numeric switch:

$b->switch('code', [
    { eq => 200, then => { return_pv => '"OK"' } },
    { eq => 404, then => { return_pv => '"Not Found"' } },
    { eq => 500, then => { return_pv => '"Server Error"' } },
], { return_pv => '"Unknown"' });

For numeric comparisons, this generates code that:

  • Caches SvIV() once at the start

  • Uses direct numeric comparison

Range-based switch:

$b->switch('score', [
    { gte => 90, then => { return_pv => '"A"' } },
    { gte => 80, then => { return_pv => '"B"' } },
    { gte => 70, then => { return_pv => '"C"' } },
    { gte => 60, then => { return_pv => '"D"' } },
], { return_pv => '"F"' });

Complex switch with AND/OR:

$b->switch('status_code', [
    { gte => 200, lte => 299, then => { return_pv => '"success"' } },
    { gte => 300, lte => 399, then => { return_pv => '"redirect"' } },
    { gte => 400, lte => 499, then => { return_pv => '"client_error"' } },
    { gte => 500, lte => 599, then => { return_pv => '"server_error"' } },
], { return_pv => '"unknown"' });

Complete Switch Example

use XS::JIT;
use XS::JIT::Builder;

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

$b->xs_function('get_type_id')
  ->xs_preamble
  ->declare_sv('type', 'ST(0)')
  ->switch('type', [
      { eq => 'integer', then => { return_iv => '1' } },
      { eq => 'string',  then => { return_iv => '2' } },
      { eq => 'float',   then => { return_iv => '3' } },
      { eq => 'boolean', then => { return_iv => '4' } },
      { eq => 'array',   then => { return_iv => '5' } },
      { eq => 'hash',    then => { return_iv => '6' } },
  ], { return_iv => '0' })
  ->xs_end;

XS::JIT->compile(
    code => $b->code,
    name => 'TypeID',
    functions => {
        'TypeID::get' => { source => 'get_type_id', is_xs_native => 1 }
    }
);

say TypeID::get('integer');  # 1
say TypeID::get('string');   # 2
say TypeID::get('unknown');  # 0

Switch vs Conditional

Use switch when:

  • You have multiple conditions on the same key

  • All cases compare the same variable

  • You want automatic optimization for string/numeric comparisons

  • You want cleaner syntax without repeating the key

Use conditional when:

  • Conditions involve different variables

  • You need complex nested AND/OR logic

  • You're matching patterns with m/im

  • You prefer the Struct::Conditional format

Bulk Code Generators

These methods generate multiple related functions from a single declarative specification.

Enum/Constant Generator

enum($name, \@values, [\%options])

Generate a set of related constants with validation functions. This is useful for creating type-safe enumerated values with minimal boilerplate.

$b->enum('Status', [qw(PENDING ACTIVE INACTIVE DELETED)]);

Generates the following XS functions:

  • STATUS_PENDING() - returns 0

  • STATUS_ACTIVE() - returns 1

  • STATUS_INACTIVE() - returns 2

  • STATUS_DELETED() - returns 3

  • is_valid_status($val) - returns true if value is valid enum (0-3)

  • status_name($val) - returns string name for numeric value

Options:

  • start - Starting numeric value (default: 0)

  • prefix - Prefix for constant names (default: uc($name) . '_')

# Custom start value
$b->enum('Priority', [qw(LOW MEDIUM HIGH CRITICAL)], { start => 1 });
# Generates: PRIORITY_LOW => 1, PRIORITY_MEDIUM => 2, etc.

# Custom prefix
$b->enum('Color', [qw(RED GREEN BLUE)], { prefix => 'CLR_' });
# Generates: CLR_RED => 0, CLR_GREEN => 1, CLR_BLUE => 2
enum_functions($name, $package)

Get a hashref of function definitions for use with XS::JIT->compile(). This returns the correct mapping for all functions generated by enum().

my $b = XS::JIT::Builder->new;
$b->enum('Status', [qw(PENDING ACTIVE INACTIVE DELETED)]);

my $functions = $b->enum_functions('Status', 'MyApp::Status');

XS::JIT->compile(
    code      => $b->code,
    name      => 'MyApp::Status',
    functions => $functions,
);

# Now you can use:
print MyApp::Status::STATUS_PENDING();           # 0
print MyApp::Status::STATUS_ACTIVE();            # 1
print MyApp::Status::is_valid_status(2);         # true
print MyApp::Status::status_name(1);             # "ACTIVE"

Complete Enum Example

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

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

# Generate enum for HTTP status categories
$b->enum('HttpCategory', [qw(INFORMATIONAL SUCCESS REDIRECT CLIENT_ERROR SERVER_ERROR)]);

XS::JIT->compile(
    code      => $b->code,
    name      => 'HTTP',
    cache_dir => $cache_dir,
    functions => $b->enum_functions('HttpCategory', 'HTTP'),
);

# Use the generated functions
use constant {
    HTTP_INFORMATIONAL => HTTP::HTTPCATEGORY_INFORMATIONAL(),
    HTTP_SUCCESS       => HTTP::HTTPCATEGORY_SUCCESS(),
    HTTP_REDIRECT      => HTTP::HTTPCATEGORY_REDIRECT(),
    HTTP_CLIENT_ERROR  => HTTP::HTTPCATEGORY_CLIENT_ERROR(),
    HTTP_SERVER_ERROR  => HTTP::HTTPCATEGORY_SERVER_ERROR(),
};

sub categorize_status {
    my ($code) = @_;
    return HTTP_INFORMATIONAL if $code >= 100 && $code < 200;
    return HTTP_SUCCESS       if $code >= 200 && $code < 300;
    return HTTP_REDIRECT      if $code >= 300 && $code < 400;
    return HTTP_CLIENT_ERROR  if $code >= 400 && $code < 500;
    return HTTP_SERVER_ERROR  if $code >= 500 && $code < 600;
    return -1;
}

my $cat = categorize_status(404);
print HTTP::is_valid_httpcategory($cat) ? "Valid" : "Invalid";  # "Valid"
print HTTP::httpcategory_name($cat);                             # "CLIENT_ERROR"

Enum Use Cases

  • Database status fields - Track record states (pending, active, deleted)

  • State machines - Define valid states with validation

  • Configuration options - Type-safe config values

  • Protocol codes - HTTP status codes, error codes, message types

Memoization Wrapper

Generate cached versions of expensive methods. The cache is stored in an object attribute and can be cleared at any time.

memoize($func_name, [\%options])

Generate a memoized wrapper for a method. The original method must be renamed to _orig_$func_name before compilation. The wrapper checks a cache before calling the original, stores results, and optionally supports TTL expiration.

$b->memoize('expensive_calc');

# With options
$b->memoize('fetch_data', {
    cache => '_fetch_cache',  # custom cache attribute name
    ttl   => 300,             # expire after 300 seconds
});

Generates the following XS functions:

  • $func_name() - The memoized wrapper that checks cache first

  • clear_$func_name_cache() - Clears the cache for this function

Options:

  • cache - Name of the hash attribute to store cache (default: '_memoize_cache')

  • ttl - Time-to-live in seconds. If set, cached values expire after this duration.

Cache key generation:

The cache key is built by concatenating all arguments (except $self) with ASCII field separator (0x1C). This handles most argument types correctly.

How it works:

1. Arguments are joined to form a cache key
2. Cache is checked for existing value
3. If TTL is set, timestamp is verified
4. On cache miss, calls $self->_orig_$func_name(@args)
5. Result is stored in cache and returned
memoize_functions($func_name, $package)

Get a hashref of function definitions for use with XS::JIT->compile().

my $functions = $b->memoize_functions('expensive_calc', 'MyClass');

XS::JIT->compile(
    code      => $b->code,
    name      => 'MyClass',
    functions => $functions,
);

Complete Memoization Example

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

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

# Generate memoized wrapper for 'compute' method
$b->memoize('compute', { ttl => 60 });  # 60 second TTL

XS::JIT->compile(
    code      => $b->code,
    name      => 'Calculator',
    cache_dir => $cache_dir,
    functions => $b->memoize_functions('compute', 'Calculator'),
);

package Calculator;

# The original method - renamed with _orig_ prefix
sub _orig_compute {
    my ($self, $x, $y) = @_;
    # Expensive calculation...
    sleep 1;  # simulate slow operation
    return $x * $y;
}

sub new { bless {}, shift }

package main;

my $calc = Calculator->new;

# First call - slow (calls _orig_compute)
my $result1 = $calc->compute(6, 7);  # 42

# Second call - instant (from cache)
my $result2 = $calc->compute(6, 7);  # 42 (cached)

# Different args - slow again
my $result3 = $calc->compute(3, 4);  # 12

# Clear cache
$calc->clear_compute_cache;

# Next call will be slow again
my $result4 = $calc->compute(6, 7);  # 42 (recalculated)

Memoization Use Cases

  • Database query caching - Cache expensive DB lookups

  • API response caching - Cache external API calls with TTL

  • Expensive computations - Cache results of CPU-intensive calculations

  • Configuration lookups - Cache parsed config values

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.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 1019:

Non-ASCII character seen before =encoding in '→'. Assuming UTF-8