Compiling & Running
Flags, optimization levels, the compile-then-run loop, when to use -M vs a full compile.
__C__ Blocks
Drop into raw C from inside Perl. Variable name mapping, runtime helpers, memory rules.
Module Search Paths
How use and require resolve — compile-time vs runtime, dedup, auto-build.
Debugging & Diagnostics
Reading --debug output, common failure modes, keeping the generated C.
Perl Compatibility
What works, what doesn't, where Perla's semantics differ from real Perl.
Cannoli Integration
Running a Perla-compiled web app under Cannoli's preforking HTTP server.
Compiling & Running
Perla has four main modes of invocation:
| Command | What it does |
|---|---|
perla script.pl | Compile and run. Cleans up the binary after. |
perla -o prog script.pl | Compile to ./prog, don't run. |
perla -c -o out.c script.pl | Emit C code only; skip the cc step. |
perla -M Module.pm | Precompile the module to Module.pm.o. |
perla -M dir/ | Recursively precompile every .pm under dir/. |
perla -e 'code' | Run a one-liner. |
All command-line flags
| Flag | Effect |
|---|---|
-o FILE | Output path (executable in normal mode, C file with -c). |
-c | Generate C only; don't invoke the C compiler. |
-e 'CODE' | Execute an inline one-liner. |
-M FILE | Precompile module/directory. |
-I PATH | Add to compile-time @INC. |
--cc BIN | Use a specific C compiler (default gcc, alternative tcc). |
--debug | Verbose compile-time tracing. Prints module search, cache events, cc command. |
--keep | Don't delete the intermediate .c after compile. |
-O0 .. -O3, -Os, -Ofast | gcc optimization (default -O0; see below). |
--precompile-deps | Auto-build .pm.o+.pm.so for every module encountered. |
--shared | Prefer .pm.so (runtime dlopen) over .pm.o (static link). |
--force-rebuild | perla -M rebuilds every module, even cached ones. |
--vm | Run via Strada codegen interpreter path (no cc step). |
--strada | Emit Strada source instead of C. |
Optimization levels
Defaults to -O0 — optimized for compile speed during dev. For release builds, use -O2 or -O3.
| Flag | Added gcc args | Use case |
|---|---|---|
| (default) | -O0 -w | Dev iteration (fastest compile) |
-O2 | -O2 -w -flto | Standard release |
-O3 | -O3 -w -flto -march=native | Max performance, native CPU |
-Os | -Os -w -flto | Size-optimized |
-Ofast | -Ofast -w -flto -march=native | Aggressive (may violate strict IEEE) |
On a medium-sized web app, the gcc phase goes from ~15s at -O0 to ~100s at -O2. Worth it for release; not worth it during iteration.
__C__ Blocks
__C__ { ... } drops raw C into the generated output. Perla doesn't parse or rewrite the block — it's emitted verbatim. Three valid positions:
File scope
Place outside any sub. Use for #includes, static state, and helper functions that you'll call from multiple __C__ blocks.
__C__ {
#include <sys/time.h>
#include <unistd.h>
static int call_counter = 0;
}
Inside a sub
Emitted as a { ... } block inside the generated C function body.
sub add { my ($a, $b) = @_; my $sum = 0; __C__ { int64_t __a = strada_to_int(v_a); int64_t __b = strada_to_int(v_b); strada_decref(v_sum); v_sum = strada_new_int(__a + __b); } return $sum; }
Expression context (GCC statement-expression)
my $pid = __C__ {
({ StradaValue *__r = strada_new_int((int64_t)getpid()); __r; })
};
Variable name mapping
Critical gotcha. Perl $foo becomes C v_foo — Perla does not substitute $x → v_x inside __C__ blocks. You must use the C names directly. Use --keep to inspect the generated C if unsure.
| Perl | C identifier |
|---|---|
$foo | v_foo |
@arr | v_arr |
%hash | v_hash |
@_ | perla_at_ |
$_ | perla_dollar_underscore |
$1..$9 | strada_capture_var(N) |
$! | strerror(errno) read / errno = N write |
$/ $\ $, | perla_irs perla_ors perla_ofs |
$| | __perla_autoflush (C int) |
$$ | getpid() |
$0 | perla_dollar_zero |
$^W $^O $^V $^X | v__caret_W, v__caret_O, etc. |
$] | v__rbrack_ |
$Pkg::scalar (fully-qualified) | perla_scalar_get("Pkg", "scalar") |
@Pkg::arr | perla_array_get("Pkg", "arr") |
%Pkg::hash | perla_hash_get("Pkg", "hash") |
Runtime helpers available
All strada_* and perla_* functions are in scope. The most useful:
| Helper | Use |
|---|---|
strada_to_int(sv) | int64_t, handles tagged ints transparently |
strada_to_num(sv) | double |
strada_to_str(sv) | malloc'd char* — caller must free() |
strada_to_str_buf(sv, buf, sz) | writes to caller's buffer; no alloc |
strada_new_int(n) strada_new_num(d) strada_new_str(s) | construct boxed values |
strada_new_array() strada_new_hash() | empty containers |
strada_incref(sv) strada_decref(sv) | refcount bookkeeping |
strada_hv_store_take(hv, key, val) | hash insert, takes ownership |
strada_array_push_take(av, elem) | array append, takes ownership |
STRADA_IS_TAGGED_INT(sv) | Always check before touching sv->type or sv->value |
perla_bless(ref, "Pkg") perla_blessed(sv) | Perl blessing |
perla_method_dispatch(obj, method, args) | Full Perl method dispatch |
perla_scalar_get/set(pkg, name) | Package-scoped scalar via stash |
perla_dbi_connect, perla_dbi_prepare, perla_dbi_execute | DBI bridge |
Memory rules
- Tagged-int guard. Integers in range
-2^62..2^62-1are encoded directly in pointers. Any code that readssv->type/sv->value/sv->meta/sv->refcountmust checkSTRADA_IS_TAGGED_INT(sv)first. The high-level helpers handle this for you. strada_to_strallocates. Alwaysfree()the result. For no-alloc, usestrada_to_str_bufwith a stack buffer.- Decref before reassign.
strada_decref(v_result); v_result = strada_new_int(42);— forgetting leaks; double-free is worse. _takevs plain for container inserts.strada_hv_store_take(hv, "k", strada_new_str("v"))keeps refcount at 1;strada_hv_storeincrefs. Use_takefor freshly-allocated values, plain store for existing variables.
Complete worked example
use strict; __C__ { #include <sys/resource.h> } sub rss_bytes { my $r = 0; __C__ { struct rusage __u; if (getrusage(RUSAGE_SELF, &__u) == 0) { /* ru_maxrss is KB on Linux, bytes on macOS */ strada_decref(v_r); v_r = strada_new_int((int64_t)__u.ru_maxrss * 1024); } } return $r; } printf "RSS: %d MB\n", int(rss_bytes() / 1024 / 1024);
For the complete __C__ reference including FFI examples, see the Markdown version on GitHub.
Module Search Paths
Perla has two separate search paths for use and require.
Compile-time path (consulted when parsing use X)
Built by _build_lib_paths, in search order:
- Directory of the input file
- Current directory (
.) $PERLA5LIBenv var$PERL5LIB(Perl compatibility)$HOME/perla/lib(if it exists)-I pathCLI flags (prepended)use lib "..."statements during parse
Not included by default: /opt/bzperl/... or any large CPAN tree. Including it would trigger transitive source parsing of every module in the tree, blowing compile time. Add it via use lib "..." in your script if you need it.
Runtime path (consulted for require and lazy-loads)
Baked into the executable at compile time. Plus at startup: $PERLA_LIB, ~/.perla/perla.conf, /etc/perla.conf. Also probes archlib subdirs (x86_64-linux/) for each path.
Dedup
Two layers prevent re-parsing:
- Name-keyed:
use Foo::Bartwice gets caught by matching the Perl module name. - Path-keyed: two different
usestatements resolving to the same file (lib-path collisions) get caught by matching the resolved path.
The precompile cache
perla -M Foo/Bar.pm writes:
Foo/Bar.pm.o— object file for static linkingFoo/Bar.pm.deps— transitive dependency listFoo/Bar.pm.so— shared library (ifPERLA_BUILD_PM_SO=1or--shared)
Subsequent compiles that use this module pick up the cached .pm.o instead of re-parsing. Useful for warming a module tree:
$ perla -M lib/ # recursively precompile every .pm under lib/
Auto-build heuristic
By default, a module used in your program is only auto-precompiled if its source matches an import-installing pattern (sub import, our @EXPORT, *{$M.import}). Everything else is left for runtime require. This prevents runaway cascade compiles.
To precompile every dependency, use --precompile-deps.
Debugging & Diagnostics
--debug output vocabulary
| Trace line | Meaning |
|---|---|
use Foo | Starting to load module |
use Foo -> already loaded (skip) | Name-keyed dedup hit |
Foo: resolved to already-loaded path ... | Path-keyed dedup hit |
Foo: cache HIT /path [.pm.o (static)] | Found precompiled artifact, will link |
Foo: cache MISS /path — auto-building | Narrow heuristic matched, spawning perla -M |
Foo: source /path (non-tree, will inline) | Project-local module, inlining source |
Foo: XS module detected — skipping | Found auto/.../*.so, avoiding DynaLoader |
[cc] gcc ... | Full C compiler invocation (grep for this!) |
[cc] done in XX.XXs (ok) | cc finished with timing |
[require] ... | Runtime require activity (inside the compiled binary) |
Common failure modes
sh: 1: foo: not found after compile
The compiled binary wasn't on $PATH. Recent Perla prefixes ./ automatically; if you see this, rebuild Perla.
redefinition of 'perla_sub_Foo_bar' at link
Either the source defines the same sub twice (Perla now matches Perl's last-wins semantic — rebuild Perla), or a dependency cycle re-parses the file (caught by path-keyed dedup).
undefined reference to perla_mod_init_Xxx at link
A precompiled .pm.o was built with the wrong module name. Re-run perla -M /full/path/to/module.pm or set PERLA_MODULE_NAME=Foo::Bar explicitly.
Can't locate object method X via package "REF(0x0x...)"
A blessed ref was stringified for use as a class name. perla_bless should unwrap this via perla_class_name — rebuild Perla if you see this today.
Compile works but program behaves weirdly
Run with --keep, grep the generated .c for the affected sub. Usually a codegen issue with a specific Perl idiom.
Environment variables
| Var | Effect |
|---|---|
PERLA_DEBUG=1 | Same as --debug |
PERLA_PERL | Path to a real perl binary (for @INC fallback probe) |
PERL5LIB | Compile-time search path (colon-separated) |
PERLA_LIB | Runtime-require search path |
PERLA_DBI_DEBUG=1 | Trace DBI method calls |
PERLA_METHOD_DEBUG=1 | Trace method dispatch |
PERLA_BUILD_PM_SO=1 | perla -M also builds .pm.so |
PERLA_SHARED=1 | Same as --shared |
PERLA_FORCE_REBUILD=1 | Same as --force-rebuild |
PERLA_NO_AUTO_BUILD=1 | Disable narrow auto-build heuristic |
Perl Compatibility
What works
- Core syntax: sigils, arrays, hashes, regex (
=~,s///,m//with/i,/s,/x,/g,/e),foreach/map/grep/sort,push/pop/shift/unshift, slice syntax, string interpolation,heredoc,qw//. - OO:
bless,@ISA,SUPER::,AUTOLOAD, multiple inheritance with Perl's method resolution order,overload("",==, arithmetic, etc.). - Moose-ish:
has,extends,with,before/after/around,isa,meta, auto-accessors — native C runtime implementation (not Class::MOP). - Exception handling:
eval { },die,$@,Try::Tiny, typedcatch. - Core modules:
strict,warnings,Carp,Data::Dumper,JSON,YAML,URI::Escape,File::Spec,File::Temp,File::Find,List::Util,Scalar::Util,Time::HiRes,Encode,Digest::MD5/SHA,MIME::Base64,HTTP::Date,Storable::dclone. - DBI: connection, prepare, execute, bind, fetchrow_hashref/array, selectrow_array, etc. MySQL and SQLite drivers via native implementation.
- DBIx::Class: a minimal stub in your
lib/DBIx/Class/provideshas_one/has_many/belongs_to,ResultSet::search/first/all,Bootstrap::Simple. Not the real DBIC — it's a re-implementation of its public API. - Drogo (web framework): works, including
Drogo::Server::Cannoli.
What doesn't work (yet)
eval STRINGat runtime. Has an opt-in path viacore::full_eval_on()but it shells out to a childperlacompile — slow and not ergonomic.eval BLOCKworks normally for exception handling.- XS modules without native implementations. Detected and silently skipped. Wrap the C library yourself via
__C__or find a Perl equivalent. - DynaLoader. Not wired up; XS bootstrap would need a separate project.
- Threads (Perl's
threadsmodule). Perla is single-threaded per process; useforkfor concurrency (async::*helpers work). - Source filters. Parse happens before any runtime, so source filters that modify the source at
use-time don't apply. - Some corner-case Perl idioms. Tie, symbol-table trickery (beyond
@ISA/@EXPORT),localon magical vars. Most work; a fraction don't. Report them.
Cannoli Integration
Cannoli is the Strada-native preforking HTTP server. Perla-compiled apps load into it via the cannoli_perla.so bridge.
$ cd /path/to/cannoli/lib/perla $ make # builds cannoli_perla.so $ make demo # builds Demo.pm.so $ /path/to/cannoli/cannoli \ --library "./cannoli_perla.so:\ so=$(pwd)/Demo.pm.so;\ init=perla_mod_init_Demo;\ handler=perla_sub_Demo_hello" \ --dev -p 8080 $ curl http://127.0.0.1:8080/ Hello from a Perla-compiled handler!
The bridge parses three config options:
so=— path to the Perla-compiled.pm.so(handler module)init=— module init function to call once at startup (usuallyperla_mod_init_Foo)handler=— per-request handler function (usuallyperla_sub_Foo_handle)
See the cannoli_perla source for full detail. Cannoli ships with a parallel cannoli_perl.so for running real Perl (via libperl) if you'd rather stay in that world.
More
Markdown docs on GitHub →
The source-tree documentation has longer prose, internal notes, and memory-rule details.
Test suite →
68 tests covering the features Perla guarantees. Good reference for what definitely works.
Strada CLAUDE.md →
The Strada language reference that underlies Perla.
Back to Getting Started →
The 60-second install and first-program guide.