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 pad- Return 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 CV - Runtime:
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 (:lvalue, etc.) | 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.