Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6f90620
Fix Module::Runtime test failures: #line directive, hints hash, reloa…
fglock Mar 22, 2026
3f75402
Fix base.pm isa check and error message formatting
fglock Mar 22, 2026
d9b3ef9
Fix parent.pm tests: normalize old-style package separator and improv…
fglock Mar 22, 2026
d5c0919
Fix Module::Metadata tests: Unicode regex, File::Spec path handling
fglock Mar 22, 2026
2b8361e
Fix %main:: to include top-level packages in stash enumeration
fglock Mar 22, 2026
a4fd199
Fix substr() with negative offsets that overshoot string start
fglock Mar 22, 2026
a58b4cc
Fix regex /u flag: only enable Unicode character classes when requested
fglock Mar 22, 2026
ce00ee9
Fix cached require error message to include 'Compilation failed'
fglock Mar 22, 2026
dba7944
Fix version qv flag and stringify for decimal versions
fglock Mar 22, 2026
d1cf8a0
Fix version module: strip trailing zeros and reject math ops
fglock Mar 22, 2026
e475e1c
Fix File::Temp: TEMPLATE option and PERMS support
fglock Mar 22, 2026
3d750ed
Fix File::Temp cleanup when chdir'd or using relative paths
fglock Mar 22, 2026
50e7205
Fix anonymous glob slot dereferencing (${*$fh}, %{*$fh}, @{*$fh})
fglock Mar 22, 2026
1f22855
Fix File::Temp tests: fileno, autoflush, template path handling
fglock Mar 22, 2026
9bf62c2
Revert fileno synthetic fd change to fix io/perlio_leaks.t regressions
fglock Mar 22, 2026
252e357
Fix DateTime test failures: overload warnings and custom warning cate…
fglock Mar 22, 2026
1a3a509
Fix indirect object syntax with blocks for undefined barewords
fglock Mar 22, 2026
8aa8d62
Fix warnings::warnif to work with Test::Warnings warning capture
fglock Mar 22, 2026
f0df197
Add warnings scope design doc and infrastructure
fglock Mar 22, 2026
1af9e9e
Implement lexical warning scope propagation for warnif()
fglock Mar 22, 2026
02a1a0a
Fix runtime warning scope check in RuntimeIO
fglock Mar 22, 2026
9fc0431
Add architecture documentation
fglock Mar 23, 2026
38832fe
Fix stringConcatWarnUninitialized to avoid double FETCH on tied scalars
fglock Mar 23, 2026
8d45476
Fix tied variable FETCH/STORE semantics for chained assignments
fglock Mar 23, 2026
1e420c8
Fix regex /i flag to not affect Unicode properties
fglock Mar 23, 2026
ad8b5df
Revert tied variable and regex changes that caused test regressions
fglock Mar 23, 2026
0fe84a1
Restore StringOperators fix for tied scalar concat (fixes op/gmagic.t)
fglock Mar 23, 2026
7c9d597
Fix /i flag handling for Unicode properties in extended character cla…
fglock Mar 23, 2026
e209ba6
Fix extended char class negation with /i flag
fglock Mar 23, 2026
a2ce08d
Fix case-insensitive matching for extended char class ranges and escapes
fglock Mar 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions dev/architecture/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# PerlOnJava Architecture Overview

This directory contains architecture documentation for the PerlOnJava compiler and runtime.

## Quick Overview

PerlOnJava is a Perl 5 implementation that compiles Perl source code to JVM bytecode. The system consists of:

1. **Frontend** (`org.perlonjava.frontend`)
- Lexer: Tokenizes Perl source code
- Parser: Builds Abstract Syntax Tree (AST)
- Semantic analysis: Variable resolution, scope handling

2. **Backend** (`org.perlonjava.backend`)
- JVM backend: Emits JVM bytecode using ASM library
- Bytecode interpreter: Interprets a subset of operations for eval STRING

3. **Runtime** (`org.perlonjava.runtime`)
- Runtime types: RuntimeScalar, RuntimeArray, RuntimeHash, RuntimeCode
- Operators: Arithmetic, string, comparison, I/O
- Perl modules: Built-in implementations of core modules

## Key Architecture Documents

| Document | Description |
|----------|-------------|
| [dynamic-scope.md](dynamic-scope.md) | Dynamic scoping via `local` and DynamicVariableManager |
| [lexical-pragmas.md](lexical-pragmas.md) | Lexical warnings, strict, and features |
| [../design/interpreter.md](../design/interpreter.md) | Bytecode interpreter design |
| [../design/variables_and_values.md](../design/variables_and_values.md) | Runtime value representation |

## Compilation Pipeline

```
Perl Source
┌─────────┐
│ Lexer │ Tokenizes source into LexerTokens
└────┬────┘
┌─────────┐
│ Parser │ Builds AST (AbstractNode tree)
└────┬────┘
┌─────────────┐
│ Visitors │ Analysis passes (variable resolution, etc.)
└─────┬───────┘
┌───────────────┐
│ JVM Emitter │ Generates bytecode via ASM
└───────┬───────┘
JVM Bytecode
```

## Runtime Architecture

```
┌────────────────────────────────────────────────┐
│ Perl Code │
└────────────────────────────────────────────────┘
┌────────────────────────────────────────────────┐
│ Runtime Types │
│ RuntimeScalar, RuntimeArray, RuntimeHash │
│ RuntimeCode, RuntimeGlob, RuntimeIO │
└────────────────────────────────────────────────┘
┌────────────────────────────────────────────────┐
│ Global State │
│ GlobalVariable (package variables) │
│ DynamicVariableManager (local scoping) │
│ CallerStack (call frame tracking) │
└────────────────────────────────────────────────┘
┌────────────────────────────────────────────────┐
│ JVM │
└────────────────────────────────────────────────┘
```

## Key Design Decisions

1. **Direct JVM Bytecode**: We emit bytecode directly rather than generating Java source, enabling better optimization and avoiding Java language limitations.

2. **Dual Backend**: JVM bytecode for compiled code, bytecode interpreter for `eval STRING` to avoid runtime class generation overhead.

3. **Dynamic Scoping**: Implemented via `DynamicVariableManager` which maintains a stack of saved values, restored on scope exit.

4. **Lexical Pragmas**: Warnings and strict are tracked in the symbol table at compile time and propagate via `CompilerFlagNode`.

## See Also

- [AGENTS.md](../../AGENTS.md) - Development guidelines
- [dev/design/](../design/) - Detailed design documents
256 changes: 256 additions & 0 deletions dev/architecture/dynamic-scope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
# Dynamic Scoping in PerlOnJava

This document explains how PerlOnJava implements Perl's dynamic scoping via the `local` keyword and how the same mechanism is used for other features.

## Overview

Perl's `local` keyword provides dynamic scoping: it temporarily saves a variable's value and restores it when the current scope exits. This is different from lexical scoping (`my`), which creates a new variable visible only in the current block.

```perl
$x = "global";
sub foo {
local $x = "local";
bar(); # sees $x = "local"
}
sub bar {
print $x; # prints "local" when called from foo()
}
foo();
print $x; # prints "global"
```

## Implementation

### Core Components

#### 1. DynamicState Interface

All values that can be dynamically scoped implement `DynamicState`:

```java
public interface DynamicState {
void dynamicSaveState(); // Save current state
void dynamicRestoreState(); // Restore saved state
}
```

Implementations:
- `RuntimeScalar` - scalar variables
- `RuntimeArray` - array variables
- `RuntimeHash` - hash variables
- `RuntimeGlob` - typeglobs
- `DeferBlock` - defer block execution
- `RegexState` - regex match state ($1, $2, etc.)

#### 2. DynamicVariableManager

Manages a stack of saved states:

```java
public class DynamicVariableManager {
private static final Deque<DynamicState> variableStack = new ArrayDeque<>();

// Save current state and push onto stack
public static void pushLocalVariable(DynamicState variable) {
variable.dynamicSaveState();
variableStack.addLast(variable);
}

// Restore all states back to a saved level
public static void popToLocalLevel(int targetLevel) {
while (variableStack.size() > targetLevel) {
DynamicState variable = variableStack.removeLast();
variable.dynamicRestoreState();
}
}

// Get current stack level (saved at block entry)
public static int getLocalLevel() {
return variableStack.size();
}
}
```

#### 3. RuntimeScalar State Management

Each `RuntimeScalar` has its own save stack:

```java
public class RuntimeScalar implements DynamicState {
private static final Stack<RuntimeScalar> dynamicStateStack = new Stack<>();

@Override
public void dynamicSaveState() {
// Save a copy of current state
RuntimeScalar copy = new RuntimeScalar();
copy.type = this.type;
copy.value = this.value;
dynamicStateStack.push(copy);
}

@Override
public void dynamicRestoreState() {
RuntimeScalar saved = dynamicStateStack.pop();
this.type = saved.type;
this.value = saved.value;
}
}
```

### Code Generation

When the compiler sees `local $x`:

1. **Block Entry**: Save current local level
```java
int savedLevel = DynamicVariableManager.getLocalLevel();
```

2. **Local Assignment**: Save and modify variable
```java
DynamicVariableManager.pushLocalVariable(variable);
variable.set(newValue);
```

3. **Block Exit**: Restore all local variables (in finally block)
```java
DynamicVariableManager.popToLocalLevel(savedLevel);
```

### Detection of Local Usage

`FindDeclarationVisitor` scans AST blocks to detect if `local` is used:

```java
public static boolean containsLocalOrDefer(Node blockNode) {
FindDeclarationVisitor visitor = new FindDeclarationVisitor();
visitor.operatorName = "local";
blockNode.accept(visitor);
return visitor.containsLocalOperator || visitor.containsDefer;
}
```

This allows the compiler to skip local setup/teardown for blocks that don't need it.

## Other Uses of DynamicVariableManager

The same mechanism is used for several other features:

### 1. Defer Blocks

`defer { ... }` blocks execute code when scope exits:

```perl
{
defer { print "cleanup\n" }
print "work\n";
} # prints: work, cleanup
```

Implementation:
```java
public class DeferBlock implements DynamicState {
private final RuntimeCode code;

@Override
public void dynamicRestoreState() {
// Execute the defer block
code.apply(new RuntimeArray(), RuntimeContextType.VOID);
}
}
```

### 2. Regex State

Match variables (`$1`, `$2`, `$&`, etc.) are saved/restored:

```java
public class RegexState implements DynamicState {
// Saves: captureGroups, lastMatch, prematch, postmatch, etc.
}
```

This ensures regex state is properly scoped in nested matches.

### 3. Warning Scope (${^WARNING_SCOPE})

Runtime warning suppression uses local semantics:

```perl
{
no warnings 'DateTime'; # Sets local ${^WARNING_SCOPE} = scopeId
DateTime->new(...); # warnif() checks ${^WARNING_SCOPE}
} # ${^WARNING_SCOPE} restored to 0
```

The `CompilerFlagNode` emits:
```java
GlobalRuntimeScalar.makeLocal("${^WARNING_SCOPE}");
scopeVar.set(scopeId);
```

### 4. Signal Handlers

`local $SIG{__WARN__}` and `local $SIG{__DIE__}` use the same mechanism:

```perl
{
local $SIG{__WARN__} = sub { ... };
# warnings go to custom handler
} # original handler restored
```

## Exception Safety

`popToLocalLevel()` is exception-safe:

```java
public static void popToLocalLevel(int targetLevel) {
Throwable pendingException = null;

while (variableStack.size() > targetLevel) {
DynamicState variable = variableStack.removeLast();
try {
variable.dynamicRestoreState();
} catch (Throwable t) {
// Continue cleanup, remember last exception
pendingException = t;
}
}

// Re-throw after all cleanup
if (pendingException != null) {
throw pendingException;
}
}
```

This ensures:
1. All local variables are restored even if one throws
2. Defer blocks all execute even if one throws
3. The last exception "wins" (Perl semantics)

## Performance Considerations

1. **Stack Allocation**: Uses `ArrayDeque` (no synchronization overhead)
2. **Lazy Detection**: `containsLocalOrDefer()` avoids setup for blocks without `local`
3. **Per-Variable Stacks**: Each variable type manages its own save stack

## Files

| File | Purpose |
|------|---------|
| `DynamicState.java` | Interface for saveable state |
| `DynamicVariableManager.java` | Central stack management |
| `RuntimeScalar.java` | Scalar save/restore |
| `RuntimeArray.java` | Array save/restore |
| `RuntimeHash.java` | Hash save/restore |
| `DeferBlock.java` | Defer block execution |
| `RegexState.java` | Regex state save/restore |
| `Local.java` | Code generation helpers |
| `FindDeclarationVisitor.java` | Detection of local usage |

## See Also

- [lexical-pragmas.md](lexical-pragmas.md) - How warnings/strict use this mechanism
- [../design/warnings-scope.md](../design/warnings-scope.md) - Warning scope design
Loading
Loading