Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ endif
# Development build - forces recompilation (use during active development)
dev: wrapper
ifeq ($(OS),Windows_NT)
gradlew.bat clean compileJava installDist
gradlew.bat clean compileJava shadowJar installDist
else
./gradlew clean compileJava installDist
./gradlew clean compileJava shadowJar installDist
endif

# Default test target - fast unit tests using perl_test_runner.pl
Expand Down
2 changes: 1 addition & 1 deletion dev/tools/generate_opcode_handlers.pl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
my $opcodes_file = 'src/main/java/org/perlonjava/interpreter/Opcodes.java';
my $bytecode_interpreter_file = 'src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java';
my $interpreted_code_file = 'src/main/java/org/perlonjava/interpreter/InterpretedCode.java';
my $bytecode_compiler_file = 'src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java';
my $bytecode_compiler_file = 'src/main/java/org/perlonjava/interpreter/CompileOperator.java'; # Changed from BytecodeCompiler.java
my $output_dir = 'src/main/java/org/perlonjava/interpreter';

# Read existing opcodes and LASTOP from Opcodes.java
Expand Down
87 changes: 60 additions & 27 deletions src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -2606,23 +2606,32 @@ void compileVariableReference(OperatorNode node, String op) {
if (node.operand instanceof IdentifierNode) {
String varName = "%" + ((IdentifierNode) node.operand).name;

// Check if it's a lexical hash
// Get the hash register
int hashReg;
if (hasVariable(varName)) {
// Lexical hash - use existing register
lastResultReg = getVariableRegister(varName);
return;
}

// Global hash - load it
int rd = allocateRegister();
String globalHashName = NameNormalizer.normalizeVariableName(((IdentifierNode) node.operand).name, getCurrentPackage());
int nameIdx = addToStringPool(globalHashName);
hashReg = getVariableRegister(varName);
} else {
// Global hash - load it
hashReg = allocateRegister();
String globalHashName = NameNormalizer.normalizeVariableName(((IdentifierNode) node.operand).name, getCurrentPackage());
int nameIdx = addToStringPool(globalHashName);

emit(Opcodes.LOAD_GLOBAL_HASH);
emitReg(rd);
emit(nameIdx);
emit(Opcodes.LOAD_GLOBAL_HASH);
emitReg(hashReg);
emit(nameIdx);
}

lastResultReg = rd;
// Check if we're in scalar context - if so, return hash as scalar (bucket info)
if (currentCallContext == RuntimeContextType.SCALAR) {
int rd = allocateRegister();
emit(Opcodes.ARRAY_SIZE); // Works for hashes too - calls .scalar()
emitReg(rd);
emitReg(hashReg);
lastResultReg = rd;
} else {
lastResultReg = hashReg;
}
} else {
throwCompilerException("Unsupported % operand: " + node.operand.getClass().getSimpleName());
}
Expand Down Expand Up @@ -2763,14 +2772,20 @@ private int getHighestVariableRegister() {
* - Reserved registers (0-2)
* - All variables in all scopes
* - Captured variables (closures)
* - Registers protected by enterScope() (baseRegisterForStatement)
*
* NOTE: This assumes that by the end of a statement, all intermediate values
* have either been stored in variables or used/discarded. Any value that needs
* to persist must be in a variable, not just a temporary register.
*/
private void recycleTemporaryRegisters() {
// Find highest variable register and reset nextRegister to one past it
baseRegisterForStatement = getHighestVariableRegister() + 1;
int highestVarReg = getHighestVariableRegister() + 1;

// CRITICAL: Never lower baseRegisterForStatement below what enterScope() set.
// This protects registers like foreach iterators that must survive across loop iterations.
// Use Math.max to preserve the higher value set by enterScope()
baseRegisterForStatement = Math.max(baseRegisterForStatement, highestVarReg);
nextRegister = baseRegisterForStatement;

// DO NOT reset lastResultReg - BlockNode needs it to return the last statement's result
Expand Down Expand Up @@ -3275,18 +3290,17 @@ public void visit(For1Node node) {
emitReg(iterReg);
emitReg(listReg);

// Step 3: Enter new scope for loop variable
enterScope();

// Step 4: Declare loop variable in the new scope
// Step 3: Allocate loop variable register BEFORE entering scope
// This ensures both iterReg and varReg are protected from recycling
int varReg = -1;
if (node.variable != null && node.variable instanceof OperatorNode) {
OperatorNode varOp = (OperatorNode) node.variable;
if (varOp.operator.equals("my") && varOp.operand instanceof OperatorNode) {
OperatorNode sigilOp = (OperatorNode) varOp.operand;
if (sigilOp.operator.equals("$") && sigilOp.operand instanceof IdentifierNode) {
String varName = "$" + ((IdentifierNode) sigilOp.operand).name;
varReg = addVariable(varName, "my");
// Don't add to scope yet - just allocate register
varReg = allocateRegister();
}
}
}
Expand All @@ -3296,12 +3310,31 @@ public void visit(For1Node node) {
varReg = allocateRegister();
}

// Step 5: Push loop info onto stack for last/next/redo
// Step 4: Enter new scope for loop variable
// Now baseRegisterForStatement will be set past both iterReg and varReg,
// protecting them from being recycled by recycleTemporaryRegisters()
enterScope();

// Step 5: If we have a named loop variable, add it to the scope now
if (node.variable != null && node.variable instanceof OperatorNode) {
OperatorNode varOp = (OperatorNode) node.variable;
if (varOp.operator.equals("my") && varOp.operand instanceof OperatorNode) {
OperatorNode sigilOp = (OperatorNode) varOp.operand;
if (sigilOp.operator.equals("$") && sigilOp.operand instanceof IdentifierNode) {
String varName = "$" + ((IdentifierNode) sigilOp.operand).name;
// Add to scope and track for variableRegistry
variableScopes.peek().put(varName, varReg);
allDeclaredVariables.put(varName, varReg);
}
}
}

// Step 6: Push loop info onto stack for last/next/redo
int loopStartPc = bytecode.size();
LoopInfo loopInfo = new LoopInfo(node.labelName, loopStartPc, true); // true = foreach is a true loop
loopStack.push(loopInfo);

// Step 6: Loop start - combined check/next/exit (superinstruction)
// Step 7: Loop start - combined check/next/exit (superinstruction)
// Emit FOREACH_NEXT_OR_EXIT superinstruction
// This combines: hasNext check, next() call, and conditional jump
// Format: FOREACH_NEXT_OR_EXIT varReg, iterReg, exitTarget (absolute address)
Expand All @@ -3311,23 +3344,23 @@ public void visit(For1Node node) {
int loopEndJumpPc = bytecode.size();
emitInt(0); // placeholder for exit target (absolute, will be patched)

// Step 7: Execute body (redo jumps here)
// Step 8: Execute body (redo jumps here)
if (node.body != null) {
node.body.accept(this);
}

// Step 8: Continue point (next jumps here)
// Step 9: Continue point (next jumps here)
loopInfo.continuePc = bytecode.size();

// Step 9: Jump back to loop start
// Step 10: Jump back to loop start
emit(Opcodes.GOTO);
emitInt(loopStartPc);

// Step 10: Loop end - patch the forward jump (last jumps here)
// Step 11: Loop end - patch the forward jump (last jumps here)
int loopEndPc = bytecode.size();
patchJump(loopEndJumpPc, loopEndPc);

// Step 11: Patch all last/next/redo jumps
// Step 12: Patch all last/next/redo jumps
for (int pc : loopInfo.breakPcs) {
patchJump(pc, loopEndPc);
}
Expand All @@ -3338,7 +3371,7 @@ public void visit(For1Node node) {
patchJump(pc, loopStartPc);
}

// Step 12: Pop loop info and exit scope
// Step 13: Pop loop info and exit scope
loopStack.pop();
exitScope();

Expand Down
Loading