Subroutines#
Subroutines are reusable code blocks in Perl. PetaPerl implements Perl5-compatible subroutines with full support for lexical variables, closures, and signatures.
Declaration#
sub greet {
print "Hello, World!\n";
}
# Named sub
sub add {
my ($a, $b) = @_;
return $a + $b;
}
# Anonymous sub
my $sub = sub {
my $x = shift;
return $x * 2;
};
Arguments (@_)#
Arguments are passed via the special @_ array. Each element is aliased to the caller’s variable.
sub modify {
$_[0] = "modified"; # Modifies caller's variable
}
my $x = "original";
modify($x);
say $x; # "modified"
Copying arguments:
sub safe_modify {
my ($val) = @_; # Copy, not alias
$val = "modified"; # Does not affect caller
}
Direct access:
sub first { $_[0] }
sub second { $_[1] }
Return Values#
Explicit Return#
sub max {
my ($a, $b) = @_;
return $a if $a > $b;
return $b;
}
Implicit Return#
The last expression’s value is returned automatically.
sub square {
my $x = shift;
$x * $x # Implicit return
}
Context-Sensitive Returns#
sub get_data {
if (wantarray) {
return (1, 2, 3); # List context
} else {
return "scalar data"; # Scalar context
}
}
my @list = get_data(); # (1, 2, 3)
my $scalar = get_data(); # "scalar data"
Note: PetaPerl’s wantarray works for direct calls and most common patterns. Complex nesting may have edge cases.
Prototypes#
Prototypes provide compile-time argument checking and context forcing.
sub scalar_arg ($) {
my $x = shift;
}
sub array_arg (@) {
my @vals = @_;
}
sub no_args () {
return 42;
}
Common prototypes:
Prototype |
Meaning |
|---|---|
|
Single scalar argument |
|
Array of arguments (slurpy) |
|
Hash (even-length list) |
|
Subroutine reference |
|
Bareword or typeglob |
|
Scalar reference |
|
Array reference |
|
Hash reference |
|
No arguments (constant sub) |
PetaPerl Status: Basic prototype parsing implemented. Runtime enforcement partial.
Signatures (Experimental)#
Perl 5.20+ signatures provide named parameters with optional type constraints and defaults.
sub greet ($name, $greeting = "Hello") {
say "$greeting, $name!";
}
greet("Alice"); # Hello, Alice!
greet("Bob", "Hi"); # Hi, Bob!
Slurpy parameters:
sub sum ($first, @rest) {
my $total = $first;
$total += $_ for @rest;
return $total;
}
sum(1, 2, 3, 4); # 10
PetaPerl Status: Signature parsing implemented in AST. Codegen lowers to pad allocation. Runtime parameter assignment works. Default values and slurpy params functional.
Closures#
Anonymous subs capture lexical variables from enclosing scope.
sub make_counter {
my $count = 0;
return sub {
return ++$count;
};
}
my $c1 = make_counter();
say $c1->(); # 1
say $c1->(); # 2
my $c2 = make_counter();
say $c2->(); # 1 (independent counter)
Closure capture mechanism:
Parser identifies outer lexicals referenced in sub body
Codegen records
outer_refsmapping (sub_pad_slot, outer_pad_slot)pp_anoncodecaptures cells at instantiation timepp_entersubshares captured cells into sub’s pad
Example: Closure factory
sub make_adder {
my $offset = shift;
return sub {
my $x = shift;
return $x + $offset; # Captures $offset
};
}
my $add5 = make_adder(5);
my $add10 = make_adder(10);
say $add5->(3); # 8
say $add10->(3); # 13
Each closure captures its own $offset cell. Modifications affect only that closure’s instance.
Method Calls#
my $obj = Some::Class->new();
$obj->method($arg);
# Indirect object notation (discouraged)
method $obj $arg;
PetaPerl Status: Method dispatch via pp_method, pp_method_named. ISA lookup implemented. AUTOLOAD supported.
Special Subroutines#
AUTOLOAD#
Called when undefined sub is invoked.
package Foo;
our $AUTOLOAD;
sub AUTOLOAD {
my $method = $AUTOLOAD;
$method =~ s/.*:://; # Strip package
print "Called undefined method: $method\n";
}
Foo->undefined_method(); # Triggers AUTOLOAD
PetaPerl Status: AUTOLOAD dispatch implemented in pp_entersub. Sets $Package::AUTOLOAD correctly.
BEGIN, END, INIT, CHECK, UNITCHECK#
PetaPerl Status: BEGIN blocks execute during compilation. END blocks execute at program exit. INIT, CHECK, and UNITCHECK have partial support.
Recursion#
Recursive calls are supported with depth limit.
sub factorial {
my $n = shift;
return 1 if $n <= 1;
return $n * factorial($n - 1);
}
Recursion limit: Default 1000 levels (configurable via PPERL_MAX_RECURSION). Prevents stack overflow.
Calling Conventions#
With parentheses#
foo(); # No arguments
foo($a); # One argument
foo($a, $b); # Multiple arguments
Without parentheses (ampersand form)#
&foo; # Pass current @_ to foo
When called as &foo (no parens), the current @_ is passed to the callee. This enables wrapper functions:
sub wrapper {
log_call();
goto &real_function; # Tail call with current @_
}
Goto and Tail Calls#
sub outer {
# ... setup ...
goto &inner; # Replace current call frame
}
goto &sub performs tail call optimization: replaces current frame instead of adding a new one.
PetaPerl Status: goto &sub implemented in pp_goto. Tail recursion optimization functional.
Advanced Features#
Constant Subs#
use constant PI => 3.14159;
use constant DEBUG => 0;
# Equivalent to:
sub PI () { 3.14159 }
Constant subs have empty prototype () and return fixed value. Perl can inline them at compile time.
PetaPerl Status: use constant creates constant subs. pp_entersub returns constant value directly without entering sub.
Lvalue Subs#
sub get_value : lvalue {
$global_var;
}
get_value() = 42; # Assigns to $global_var
PetaPerl Status: :lvalue attribute not implemented. pp_leavesublv stub exists.
State Variables#
sub counter {
state $count = 0;
return ++$count;
}
state variables persist across calls (initialized once).
PetaPerl Status: State variables are implemented. Variables are initialized once and persist across calls.
Built-in Functions#
wantarray#
Returns calling context: undef (void), 0 (scalar), 1 (list).
sub context_aware {
if (!defined wantarray) {
say "void context";
} elsif (wantarray) {
say "list context";
return (1, 2, 3);
} else {
say "scalar context";
return 42;
}
}
PetaPerl Status: Partial implementation. Works for direct calls. May fail in complex nesting.
caller#
Returns information about calling stack frame.
# Scalar context: boolean (is there a caller?)
if (caller) {
# Called from another sub
}
# List context: detailed info
my ($package, $filename, $line) = caller(0);
my ($pkg, $file, $line, $sub) = caller(1); # One frame up
PetaPerl Status: Implemented in pp_caller. Returns (package, filename, line, subroutine, ...) in list context.
PetaPerl Implementation Notes#
Op Tree Structure#
Named subs register CV (Code Value) in arena:
SubDef → lower_sub() → NextState → body ops → LeaveSub
↓
Register CV(name, start_op, pad_size, outer_refs)
Anonymous subs emit AnonCode op:
AnonSub → lower_anon_sub() → NextState → body ops → LeaveSub
↓
AnonCode (pushes CV onto stack)
Call Sequence#
Caller pushes args onto stack
Caller pushes mark
Caller pushes CV reference
EnterSubpops CV, pops mark, collects args into aliased@_New pad allocated (slot 0 =
@_)Closure cells shared into pad (if any)
Jump to CV’s start_op
Body executes
LeaveSubcollects return values, restores padReturn to caller
Pad Layout#
Slot 0: @_ (always, populated by EnterSub)
Slot 1+: Lexical variables in declaration order
Signature parameters become pad slots:
sub foo ($x, $y, @rest) { }
# Pad layout:
# 0: @_
# 1: $x
# 2: $y
# 3: @rest
Closure Capture#
Two-stage process:
Codegen: Analyze outer lexicals, record
outer_refsin CVRuntime:
pp_anoncodecaptures cells,pp_entersubshares them
Why two stages? Closure factories need per-instance capture, not per-definition.
Known Limitations#
Feature |
Status |
|---|---|
Named parameters |
Working |
Prototypes |
Parsed, partial enforcement |
Signatures |
Working (basic) |
Closures |
Working |
Recursion |
Working (1000 depth limit) |
AUTOLOAD |
Working |
Method dispatch |
Working |
wantarray |
Partial |
caller |
Working |
BEGIN/END blocks |
BEGIN works; END/INIT/CHECK partial |
Attributes ( |
Parsed, not enforced |
goto &sub |
Working |
State variables |
Basic support |
Performance#
PetaPerl subs execute via the interpreter with fast-path dispatch. The JIT compiler (Cranelift) handles hot loops within subroutines but does not JIT subroutine call/return itself.
The interpreter’s function-pointer dispatch table and inline fast paths for common operations (arithmetic, comparison, assignment) minimize per-op overhead.
Closure overhead: Cell allocation per captured variable. Minimal at runtime (Arc clone).
Testing#
See t/05-op/sub.t, t/05-op/closure.t for comprehensive test coverage.