We've spent the previous 50 chapters binding to existing C libraries or writing our own static extensions. For the grand finale, we'll look at the ultimate power of Affix::Build: Runtime Code Generation.
Imagine a program where the user provides a mathematical formula, a data transformation rule, or a filter predicate as a string at runtime. You could evaluate this using Perl's eval, but for millions of data points, eval is slow. Instead, you can compile the user's logic into a native DLL, bind it with Affix, and run it at full CPU speed.
The Recipe
We will build a "High-Speed Formula Evaluator" that compiles arbitrary C math expressions into callable Perl functions.
use v5.40;
use Affix qw[:all];
use Affix::Build;
$|++;
# A helper to compile and bind a custom math function
sub make_math_function( $name, $expression ) {
# Spin up the JIT compiler
my $c = Affix::Build->new( name => $name );
# Wrap the user's expression in a standard C function
# We include math.h to give them access to sin, cos, pow, etc.
# Note the leading backslash: inline source must be passed as a SCALAR reference.
$c->add( \<<~"END", lang => 'c' );
#include <math.h>
double $name(double x, double y) {
return $expression;
}
END
# Compile and link immediately
my $lib = $c->link;
# Bind the new symbol
affix $lib, $name, [ Double, Double ] => Double;
}
#
print "Enter a C math expression (using variables 'x' and 'y'):\n> ";
# Example: (x * x) + sqrt(y)
my $formula = <STDIN>;
chomp $formula;
say "Compiling native optimizer for '$formula'...";
make_math_function( 'my_jit_func', $formula );
say "\nTesting with x=5, y=10:";
my $result = my_jit_func( 5, 10 );
say 'Result: ' . $result;
say "\nRunning 1,000,000 iterations...";
my $start = time();
my $sum = 0;
$sum += my_jit_func( $_, 0.5 ) for 1 .. 1_000_000;
say 'Finished in ' . ( time() - $start ) . ' seconds.'
How It Works
-
1. The Polyglot Builder
Affix::Builddoesn't care that your script is already running. It invokes the system's C compiler in a background process, generates a new dynamic library, and returns its path. -
2. Symbol Binding Because
affixcan load any library at any time, we simply point it at the newly created.soor.dllfile. The function we just "invented" becomes a first-class Perl subroutine. -
3. Performance Gain The user's formula is compiled by your system's C compiler at its default optimization level (you can request
-O3by passingflags => { cflags => '-O3' }toAffix::Build->new). Constant folding, loop unrolling, and vectorization are applied to the user's logic before it even runs. For heavy math, this can be 100x to 500x faster than a Perlevalloop.
Kitchen Reminders
-
Security WARNING: This recipe allows the user to run arbitrary C code on your system. Only use this technique if you trust the input source, or if you are running in a strictly sandboxed environment. A user could enter
system("rm -rf /")instead of a math formula! -
Caching Compiling a DLL takes time (usually 200-500ms). Don't use this for one-off calculations. Use it when you need to run the same complex logic millions of times.
-
Cleanup By default,
Affix::Buildkeeps its temporary build directory on disk. Passclean => 1toAffix::Build->newif you want the directory removed when the script exits. If you generate many functions in a long-running process, decide which behavior you want up front.
Comments