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_refs mapping (sub_pad_slot, outer_pad_slot)

  • pp_anoncode captures cells at instantiation time

  • pp_entersub shares 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#

  1. Caller pushes args onto stack

  2. Caller pushes mark

  3. Caller pushes CV reference

  4. EnterSub pops CV, pops mark, collects args into aliased @_

  5. New pad allocated (slot 0 = @_)

  6. Closure cells shared into pad (if any)

  7. Jump to CV’s start_op

  8. Body executes

  9. LeaveSub collects return values, restores pad

  10. 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:

  1. Codegen: Analyze outer lexicals, record outer_refs in CV

  2. Runtime: pp_anoncode captures cells, pp_entersub shares 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.