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
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.perlonjava.backend.bytecode;

import org.perlonjava.frontend.analysis.FindDeclarationVisitor;
import org.perlonjava.frontend.analysis.Visitor;
import org.perlonjava.backend.jvm.EmitterMethodCreator;
import org.perlonjava.backend.jvm.EmitterContext;
Expand Down Expand Up @@ -546,7 +547,7 @@ public InterpretedCode compile(Node node, EmitterContext ctx) {
}

// Build InterpretedCode
return new InterpretedCode(
InterpretedCode result = new InterpretedCode(
toShortArray(),
constants.toArray(),
stringPool.toArray(new String[0]),
Expand All @@ -562,6 +563,9 @@ public InterpretedCode compile(Node node, EmitterContext ctx) {
warningFlags, // Warning flags for eval STRING inheritance
symbolTable.getCurrentPackage() // Compile-time package for eval STRING name resolution
);
result.containsRegex = FindDeclarationVisitor.findOperator(node, "matchRegex") != null
|| FindDeclarationVisitor.findOperator(node, "replaceRegex") != null;
return result;
}

// =========================================================================
Expand Down Expand Up @@ -4103,6 +4107,12 @@ public void visit(For1Node node) {
variableScopes.peek().put(varName, varReg);
allDeclaredVariables.put(varName, varReg);
}
} else if (varOp.operator.equals("$") && varOp.operand instanceof IdentifierNode
&& globalLoopVarName == null) {
String varName = "$" + ((IdentifierNode) varOp.operand).name;
if (hasVariable(varName)) {
variableScopes.peek().put(varName, varReg);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
String frameSubName = subroutineName != null ? subroutineName : (code.subName != null ? code.subName : "(eval)");
InterpreterState.push(code, framePackageName, frameSubName);

int regexLocalLevel = -1;
if (code.containsRegex) {
regexLocalLevel = DynamicVariableManager.getLocalLevel();
RuntimeRegexState.pushLocal();
}

// Pure register file (NOT stack-based - matches compiler for control flow correctness)
RuntimeBase[] registers = new RuntimeBase[code.maxRegisters];

Expand Down Expand Up @@ -88,14 +94,16 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
break;

case Opcodes.RETURN: {
// Return from subroutine: return rd
int retReg = bytecode[pc++];
RuntimeBase retVal = registers[retReg];

if (retVal == null) {
return new RuntimeList();
}
return retVal.getList();
RuntimeList retList = retVal.getList();
if (code.containsRegex) {
RuntimeList.resolveMatchProxies(retList);
}
return retList;
}

case Opcodes.GOTO: {
Expand Down Expand Up @@ -2245,7 +2253,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
String errorMessage = formatInterpreterError(code, pc, e);
throw new RuntimeException(errorMessage, e);
} finally {
// Always pop the interpreter state
if (regexLocalLevel >= 0) {
DynamicVariableManager.popToLocalLevel(regexLocalLevel);
}
InterpreterState.pop();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public class InterpretedCode extends RuntimeCode {
public final BitSet warningFlags; // Warning flags at compile time
public final String compilePackage; // Package at compile time (for eval STRING name resolution)

public boolean containsRegex; // Whether this code contains regex ops (for match var scoping)

// Debug information (optional)
public final String sourceName; // Source file name (for stack traces)
public final int sourceLine; // Source line number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,15 @@ private static byte[] getBytecodeInternal(EmitterContext ctx, Node ast, boolean
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeBase", "getList", "()Lorg/perlonjava/runtime/runtimetypes/RuntimeList;", false);
mv.visitVarInsn(Opcodes.ASTORE, returnListSlot);

if (localRecord.containsRegex()) {
mv.visitVarInsn(Opcodes.ALOAD, returnListSlot);
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/RuntimeList",
"resolveMatchProxies",
"(Lorg/perlonjava/runtime/runtimetypes/RuntimeList;)V",
false);
}

// Phase 3: Check for control flow markers
// RuntimeList is on stack after getList()

Expand Down
38 changes: 14 additions & 24 deletions src/main/java/org/perlonjava/backend/jvm/Local.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,32 @@ public class Local {
* and the index of the dynamic variable stack.
*/
static localRecord localSetup(EmitterContext ctx, Node ast, MethodVisitor mv) {
// Check if the code contains a 'local' operator
boolean containsLocalOperator = FindDeclarationVisitor.findOperator(ast, "local") != null;
boolean containsRegex = FindDeclarationVisitor.findOperator(ast, "matchRegex") != null
|| FindDeclarationVisitor.findOperator(ast, "replaceRegex") != null;
boolean needsDynamicSave = containsLocalOperator || containsRegex;
int dynamicIndex = -1;
if (containsLocalOperator) {
// Allocate a local variable to store the dynamic variable stack index
if (needsDynamicSave) {
dynamicIndex = ctx.symbolTable.allocateLocalVariable();
// Get the current level of the dynamic variable stack and store it
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/DynamicVariableManager",
"getLocalLevel",
"()I",
false);
mv.visitVarInsn(Opcodes.ISTORE, dynamicIndex);
}
return new localRecord(containsLocalOperator, dynamicIndex);
if (containsRegex) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/RuntimeRegexState",
"pushLocal",
"()V",
false);
}
return new localRecord(needsDynamicSave, containsRegex, dynamicIndex);
}

/**
* Tears down the local variable setup by restoring the dynamic variable stack
* to its previous level if a 'local' operator was present.
*
* @param localRecord The record containing information about the 'local' operator
* and the dynamic variable stack index.
* @param mv The method visitor used to generate bytecode instructions.
*/
static void localTeardown(localRecord localRecord, MethodVisitor mv) {
// Add `local` teardown logic
if (localRecord.containsLocalOperator()) {
// Restore the dynamic variable stack to the recorded level
if (localRecord.needsDynamicSave()) {
mv.visitVarInsn(Opcodes.ILOAD, localRecord.dynamicIndex());
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/DynamicVariableManager",
Expand All @@ -60,13 +57,6 @@ static void localTeardown(localRecord localRecord, MethodVisitor mv) {
}
}

/**
* A record to store information about the presence of a 'local' operator
* and the index of the dynamic variable stack.
*
* @param containsLocalOperator Indicates if a 'local' operator is present.
* @param dynamicIndex The index of the dynamic variable stack.
*/
record localRecord(boolean containsLocalOperator, int dynamicIndex) {
record localRecord(boolean needsDynamicSave, boolean containsRegex, int dynamicIndex) {
}
}
23 changes: 23 additions & 0 deletions src/main/java/org/perlonjava/runtime/regex/RuntimeRegex.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,29 @@ protected boolean removeEldestEntry(Map.Entry<String, RuntimeRegex> eldest) {
public static String lastSuccessfulMatchString = null;
// ${^LAST_SUCCESSFUL_PATTERN}
public static RuntimeRegex lastSuccessfulPattern = null;

public static Object[] saveMatchState() {
return new Object[]{
globalMatcher, globalMatchString,
lastMatchedString, lastMatchStart, lastMatchEnd,
lastSuccessfulMatchedString, lastSuccessfulMatchStart, lastSuccessfulMatchEnd,
lastSuccessfulMatchString, lastSuccessfulPattern
};
}

public static void restoreMatchState(Object[] state) {
globalMatcher = (Matcher) state[0];
globalMatchString = (String) state[1];
lastMatchedString = (String) state[2];
lastMatchStart = (Integer) state[3];
lastMatchEnd = (Integer) state[4];
lastSuccessfulMatchedString = (String) state[5];
lastSuccessfulMatchStart = (Integer) state[6];
lastSuccessfulMatchEnd = (Integer) state[7];
lastSuccessfulMatchString = (String) state[8];
lastSuccessfulPattern = (RuntimeRegex) state[9];
}

// Indicates if \G assertion is used
private final boolean useGAssertion = false;
// Compiled regex pattern
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public static RuntimeGlob pushLocalVariable(RuntimeGlob variable) {
return variable;
}

public static void pushLocalDynamicState(DynamicState state) {
state.dynamicSaveState();
variableStack.push(state);
}

/**
* Pops dynamic variables from the stack until the stack size matches the specified target local level.
* This is useful for restoring the stack to a previous state by removing any variables added after that state.
Expand Down
36 changes: 25 additions & 11 deletions src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.perlonjava.backend.bytecode.BytecodeCompiler;
import org.perlonjava.backend.bytecode.InterpretedCode;
import org.perlonjava.backend.bytecode.InterpreterState;
import org.perlonjava.runtime.regex.RuntimeRegex;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
Expand Down Expand Up @@ -414,6 +415,7 @@ public static Class<?> evalStringHelper(RuntimeScalar code, String evalTag, Obje
// Then when "say @arr" is parsed in the BEGIN, it resolves to BEGIN_PKG_x::@arr
// which is aliased to the runtime array with values (a, b).
Map<Integer, SymbolTable.SymbolEntry> capturedVars = capturedSymbolTable.getAllVisibleVariables();
List<String> evalAliasKeys = new ArrayList<>();
for (SymbolTable.SymbolEntry entry : capturedVars.values()) {
if (!entry.name().equals("@_") && !entry.decl().isEmpty() && !entry.name().startsWith("&")) {
if (!entry.decl().equals("our")) {
Expand All @@ -433,6 +435,7 @@ public static Class<?> evalStringHelper(RuntimeScalar code, String evalTag, Obje
// entry.name() is "@arr" but the key should be "packageName::arr"
String varNameWithoutSigil = entry.name().substring(1); // Remove the sigil
String fullName = packageName + "::" + varNameWithoutSigil;
evalAliasKeys.add(fullName);

// Alias the global to the runtime value
if (runtimeValue instanceof RuntimeArray) {
Expand Down Expand Up @@ -565,6 +568,17 @@ public static Class<?> evalStringHelper(RuntimeScalar code, String evalTag, Obje

setCurrentScope(capturedSymbolTable);

// Clean up eval STRING aliases from global namespace.
// These aliases were created before parsing so BEGIN blocks inside the eval
// could access outer lexicals. After compilation, they are no longer needed.
// Leaving them would cause `my` re-declarations in loops to pick up stale
// values via retrieveBeginScalar instead of creating fresh objects.
for (String key : evalAliasKeys) {
GlobalVariable.globalVariables.remove(key);
GlobalVariable.globalArrays.remove(key);
GlobalVariable.globalHashes.remove(key);
}

// Store source lines in symbol table if $^P flags are set
// Do this on both success and failure paths when flags require retention
// Use the original evalString and actualFileName; AST may be null on failure
Expand Down Expand Up @@ -743,6 +757,7 @@ public static RuntimeList evalStringWithInterpreter(

// Save dynamic variable level to restore after eval
int dynamicVarLevel = DynamicVariableManager.getLocalLevel();
List<String> evalAliasKeys = new ArrayList<>();

try {
String evalString = code.toString();
Expand Down Expand Up @@ -791,6 +806,7 @@ public static RuntimeList evalStringWithInterpreter(
String packageName = PersistentVariable.beginPackage(operatorAst.id);
String varNameWithoutSigil = entry.name().substring(1);
String fullName = packageName + "::" + varNameWithoutSigil;
evalAliasKeys.add(fullName);

if (runtimeValue instanceof RuntimeArray) {
GlobalVariable.globalArrays.put(fullName, (RuntimeArray) runtimeValue);
Expand Down Expand Up @@ -1018,6 +1034,13 @@ public static RuntimeList evalStringWithInterpreter(
// Restore dynamic variables (local) to their state before eval
DynamicVariableManager.popToLocalLevel(dynamicVarLevel);

// Clean up eval STRING aliases from global namespace
for (String key : evalAliasKeys) {
GlobalVariable.globalVariables.remove(key);
GlobalVariable.globalArrays.remove(key);
GlobalVariable.globalHashes.remove(key);
}

// Store source lines in debugger symbol table if $^P flags are set
// Do this on both success and failure paths when flags require retention
// ast and tokens may be null if parsing failed early, but storeSourceLines handles that
Expand Down Expand Up @@ -1691,13 +1714,11 @@ public boolean defined() {
*/
public RuntimeList apply(RuntimeArray a, int callContext) {
if (constantValue != null) {
// Alternative way to create constants like: `$constant::{_CAN_PCS} = \$const`
return new RuntimeList(constantValue);
}
try {
// Wait for the compilerThread to finish if it exists
if (this.compilerSupplier != null) {
this.compilerSupplier.get(); // Wait for the task to finish
this.compilerSupplier.get();
}

if (isStatic) {
Expand All @@ -1706,17 +1727,13 @@ public RuntimeList apply(RuntimeArray a, int callContext) {
return (RuntimeList) this.methodHandle.invoke(this.codeObject, a, callContext);
}
} catch (NullPointerException e) {

if (this.methodHandle == null) {
throw new PerlCompilerException("Subroutine exists but has null method handle (possible compilation or registration error) at ");
} else if (this.codeObject == null && !isStatic) {
throw new PerlCompilerException("Subroutine exists but has null code object at ");
} else {
// Original NPE from somewhere else
throw new PerlCompilerException("Null pointer exception in subroutine call: " + e.getMessage() + " at ");
}

//throw new PerlCompilerException("Undefined subroutine called at ");
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
if (!(targetException instanceof RuntimeException)) {
Expand All @@ -1730,14 +1747,11 @@ public RuntimeList apply(RuntimeArray a, int callContext) {

public RuntimeList apply(String subroutineName, RuntimeArray a, int callContext) {
if (constantValue != null) {
// Alternative way to create constants like: `$constant::{_CAN_PCS} = \$const`
return new RuntimeList(constantValue);
}
try {
// Wait for the compilerThread to finish if it exists
if (this.compilerSupplier != null) {
// System.out.println("Waiting for compiler thread to finish...");
this.compilerSupplier.get(); // Wait for the task to finish
this.compilerSupplier.get();
}

if (isStatic) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,15 @@ public RuntimeList getList() {
return this;
}

public static void resolveMatchProxies(RuntimeList list) {
for (int i = 0; i < list.elements.size(); i++) {
RuntimeBase elem = list.elements.get(i);
if (elem instanceof ScalarSpecialVariable ssv) {
list.elements.set(i, ssv.getValueAsScalar());
}
}
}

/**
* Evaluates the boolean representation of the list.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.perlonjava.runtime.runtimetypes;

import org.perlonjava.runtime.regex.RuntimeRegex;

public class RuntimeRegexState implements DynamicState {

private Object[] savedState;

public static void pushLocal() {
DynamicVariableManager.pushLocalDynamicState(new RuntimeRegexState());
}

@Override
public void dynamicSaveState() {
savedState = RuntimeRegex.saveMatchState();
}

@Override
public void dynamicRestoreState() {
if (savedState != null) {
RuntimeRegex.restoreMatchState(savedState);
savedState = null;
}
}
}
Loading