Skip to content
Closed
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
63 changes: 51 additions & 12 deletions src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ public class BytecodeCompiler implements Visitor {
// Stack to save/restore register state when entering/exiting scopes
private final Stack<Integer> savedNextRegister = new Stack<>();
private final Stack<Integer> savedBaseRegister = new Stack<>();

// Loop label stack for last/next/redo control flow
// Each entry tracks loop boundaries and optional label
private final Stack<LoopInfo> loopStack = new Stack<>();
Expand Down Expand Up @@ -708,6 +707,10 @@ public void visit(BlockNode node) {
&& localOp.operator.equals("local");

enterScope();
// Sync symbolTable package scope with block scope so that getCurrentPackage()
// at each eval() call site inside this block reflects the correct package.
// e.g. after "package Foo {}" closes, outer eval() call sites see "main" again.
int stScopeIdx = symbolTable.enterScope();

// Visit each statement in the block
int numStatements = node.elements.size();
Expand Down Expand Up @@ -749,7 +752,8 @@ public void visit(BlockNode node) {
emitReg(lastResultReg);
}

// Exit scope restores register state
// Exit scope restores register state and symbolTable package
symbolTable.exitScope(stScopeIdx);
exitScope();

// Set lastResultReg to the outer register (or -1 if VOID context)
Expand Down Expand Up @@ -1243,7 +1247,10 @@ void handleCompoundAssignment(BinaryOperatorNode node) {
// Global variable - need to load it first
isGlobal = true;
targetReg = allocateRegister();
String normalizedName = NameNormalizer.normalizeVariableName(varName, getCurrentPackage());
// Strip sigil before normalizing ("$x" -> "x", "$main::n" -> "main::n")
// matching CompileAssignment.java's pattern for STORE_GLOBAL_SCALAR
String bareVarName = varName.substring(1);
String normalizedName = NameNormalizer.normalizeVariableName(bareVarName, getCurrentPackage());
int nameIdx = addToStringPool(normalizedName);
emit(Opcodes.LOAD_GLOBAL_SCALAR);
emitReg(targetReg);
Expand Down Expand Up @@ -1303,7 +1310,7 @@ void handleCompoundAssignment(BinaryOperatorNode node) {
emitReg(targetReg);
emitReg(valueReg);

// If it's a global variable, store it back
// If it's a global variable, store the result back
if (isGlobal) {
OperatorNode leftOp = (OperatorNode) node.left;
String varName = "$" + ((IdentifierNode) leftOp.operand).name;
Expand All @@ -1313,7 +1320,9 @@ void handleCompoundAssignment(BinaryOperatorNode node) {
throwCompilerException("Global symbol \"" + varName + "\" requires explicit package name");
}

String normalizedName = NameNormalizer.normalizeVariableName(varName, getCurrentPackage());
// Strip sigil before normalizing — same as CompileAssignment.java
String bareVarName = varName.substring(1);
String normalizedName = NameNormalizer.normalizeVariableName(bareVarName, getCurrentPackage());
int nameIdx = addToStringPool(normalizedName);
emit(Opcodes.STORE_GLOBAL_SCALAR);
emit(nameIdx);
Expand Down Expand Up @@ -2541,11 +2550,19 @@ void compileVariableReference(OperatorNode node, String op) {
node.operand.accept(this);
int refReg = lastResultReg;

// Dereference the result
// Dereference the result — pick strict or non-strict opcode at compile time
int rd = allocateRegister();
emitWithToken(Opcodes.DEREF, node.getIndex());
emitReg(rd);
emitReg(refReg);
if (isStrictRefsEnabled()) {
emitWithToken(Opcodes.DEREF_SCALAR_STRICT, node.getIndex());
emitReg(rd);
emitReg(refReg);
} else {
int pkgIdx = addToStringPool(getCurrentPackage());
emitWithToken(Opcodes.DEREF_SCALAR_NONSTRICT, node.getIndex());
emitReg(rd);
emitReg(refReg);
emit(pkgIdx);
}

lastResultReg = rd;
} else {
Expand Down Expand Up @@ -3437,8 +3454,18 @@ public void visit(For1Node node) {
globalLoopVarName = NameNormalizer.normalizeVariableName(idNode.name, getCurrentPackage());
}

// Step 1: Evaluate list in list context
node.list.accept(this);
// Step 1: Evaluate list in list context (guard against null from malformed parse)
// A null list can occur when the parser misinterprets e.g. "{ q,bar, }" as a for-loop.
// Emit an empty list so all subsequent iterator opcodes operate on valid registers.
if (node.list == null) {
int emptyReg = allocateRegister();
emit(Opcodes.CREATE_LIST);
emitReg(emptyReg);
emit(0); // zero elements
lastResultReg = emptyReg;
} else {
node.list.accept(this);
}
int listReg = lastResultReg;

// Step 2: Create iterator from the list
Expand Down Expand Up @@ -3573,7 +3600,19 @@ public void visit(For3Node node) {
if (node.body != null) {
node.body.accept(this);
}
lastResultReg = -1; // Block returns empty
// Bare blocks produce no value; set lastResultReg to -1.
// If we are in a non-VOID context, the enclosing BlockNode's
// outerResultReg will be left uninitialized — emit LOAD_UNDEF
// here so that any downstream opcode that reads the register
// (e.g. ARRAY_SIZE from a scalar() wrapper) sees a defined value.
if (currentCallContext != RuntimeContextType.VOID) {
int rd = allocateRegister();
emit(Opcodes.LOAD_UNDEF);
emitReg(rd);
lastResultReg = rd;
} else {
lastResultReg = -1;
}
} finally {
// Exit scope to clean up lexical variables
exitScope();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,33 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,

bytecodeCompiler.lastResultReg = arrayReg;
return;
} else if (sigilOp.operator.equals("*") && sigilOp.operand instanceof IdentifierNode) {
// Handle local *glob = value
String globName = ((IdentifierNode) sigilOp.operand).name;
String normalizedName = NameNormalizer.normalizeVariableName(globName, bytecodeCompiler.getCurrentPackage());
int nameIdx = bytecodeCompiler.addToStringPool(normalizedName);

// Compile RHS first
node.right.accept(bytecodeCompiler);
int valueReg = bytecodeCompiler.lastResultReg;

// Load the glob
int globReg = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emit(Opcodes.LOAD_GLOB);
bytecodeCompiler.emitReg(globReg);
bytecodeCompiler.emit(nameIdx);

// Push glob to local variable stack for restoration on scope exit
bytecodeCompiler.emit(Opcodes.PUSH_LOCAL_VARIABLE);
bytecodeCompiler.emitReg(globReg);

// Store the RHS value into the glob
bytecodeCompiler.emit(Opcodes.STORE_GLOB);
bytecodeCompiler.emitReg(globReg);
bytecodeCompiler.emitReg(valueReg);

bytecodeCompiler.lastResultReg = globReg;
return;
} else if (sigilOp.operator.equals("%") && sigilOp.operand instanceof IdentifierNode) {
// Handle local %hash = value
String varName = "%" + ((IdentifierNode) sigilOp.operand).name;
Expand Down Expand Up @@ -938,6 +965,25 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(globReg);
bytecodeCompiler.emitReg(valueReg);

bytecodeCompiler.lastResultReg = globReg;
} else if (leftOp.operator.equals("*") && leftOp.getAnnotation("postfixDeref") != null) {
// Postfix-deref glob lvalue: 'name'->** = value
// Evaluate the operand expression to get the glob name as a scalar,
// then load that glob dynamically and store the RHS into it.
leftOp.operand.accept(bytecodeCompiler);
int nameScalarReg = bytecodeCompiler.lastResultReg;

int globReg = bytecodeCompiler.allocateRegister();
int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage());
bytecodeCompiler.emitWithToken(Opcodes.LOAD_GLOB_DYNAMIC, node.getIndex());
bytecodeCompiler.emitReg(globReg);
bytecodeCompiler.emitReg(nameScalarReg);
bytecodeCompiler.emit(pkgIdx);

bytecodeCompiler.emit(Opcodes.STORE_GLOB);
bytecodeCompiler.emitReg(globReg);
bytecodeCompiler.emitReg(valueReg);

bytecodeCompiler.lastResultReg = globReg;
} else if (leftOp.operator.equals("pos")) {
// pos($var) = value - lvalue assignment to regex position
Expand Down Expand Up @@ -986,7 +1032,13 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.throwCompilerException("Assignment to unsupported array dereference");
}
} else {
bytecodeCompiler.throwCompilerException("Assignment to unsupported operator: " + leftOp.operator);
// Match Perl's error message for non-lvalue operators (e.g. chop, chomp)
String op = leftOp.operator;
if (op.equals("chop") || op.equals("chomp")) {
bytecodeCompiler.throwCompilerException("Can't modify " + op + " in scalar assignment");
} else {
bytecodeCompiler.throwCompilerException("Can't modify " + op + " in assignment");
}
}
} else if (node.left instanceof IdentifierNode) {
String varName = ((IdentifierNode) node.left).name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -835,10 +835,17 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
// Allocate register for result
int rd = bytecodeCompiler.allocateRegister();

// Emit direct opcode EVAL_STRING
// Bake the call-site package into the opcode at compile time.
// ScopedSymbolTable.getCurrentPackage() reflects the packageStack at this
// exact node — e.g. "Foo" inside "package Foo { }", "main" outside.
// This is the interpreter equivalent of the JVM evalTag's ctx.symbolTable.
// Format: EVAL_STRING rd stringReg pkgIdx
int pkgIdx = bytecodeCompiler.addToStringPool(
bytecodeCompiler.getCurrentPackage());
bytecodeCompiler.emitWithToken(Opcodes.EVAL_STRING, node.getIndex());
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(stringReg);
bytecodeCompiler.emit(pkgIdx);

bytecodeCompiler.lastResultReg = rd;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public static RuntimeScalar evalString(String perlCode,
InterpretedCode currentCode,
RuntimeBase[] registers,
String sourceName,
int sourceLine) {
int sourceLine,
String callSitePackage) {
try {
// Step 1: Clear $@ at start of eval
GlobalVariable.getGlobalVariable("main::@").set("");
Expand All @@ -76,13 +77,10 @@ public static RuntimeScalar evalString(String perlCode,
symbolTable.warningFlagsStack.push((java.util.BitSet) currentCode.warningFlags.clone());
}

// Inherit the compile-time package from the calling code, matching what
// evalStringHelper (JVM path) does via capturedSymbolTable.snapShot().
// Using the compile-time package (not InterpreterState.currentPackage which is
// the runtime package) ensures bare names like *named resolve to FOO3::named
// when the eval call site is inside "package FOO3".
String compilePackage = (currentCode != null) ? currentCode.compilePackage : "main";
symbolTable.setCurrentPackage(compilePackage, false);
// Use the call-site package baked into the EVAL_STRING opcode at compile time.
// Each eval() call site emits its own pkgIdx from ScopedSymbolTable.getCurrentPackage()
// at that exact point — equivalent to the JVM evalTag's ctx.symbolTable package.
symbolTable.setCurrentPackage(callSitePackage, false);

ErrorMessageUtil errorUtil = new ErrorMessageUtil(sourceName, tokens);
EmitterContext ctx = new EmitterContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1236,7 +1236,9 @@ public String disassemble() {
case Opcodes.EVAL_STRING:
rd = bytecode[pc++];
rs = bytecode[pc++];
sb.append("EVAL_STRING r").append(rd).append(" = eval(r").append(rs).append(")\n");
int evalPkg = bytecode[pc++];
sb.append("EVAL_STRING r").append(rd).append(" = eval(r").append(rs)
.append(", pkg=").append(stringPool[evalPkg]).append(")\n");
break;
case Opcodes.SELECT_OP:
rd = bytecode[pc++];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -735,8 +735,8 @@ public static int executeMatchRegex(int[] bytecode, int pc, RuntimeBase[] regist
int regexReg = bytecode[pc++];
int ctx = bytecode[pc++];
registers[rd] = RuntimeRegex.matchRegex(
(RuntimeScalar) registers[regexReg], // quotedRegex first
(RuntimeScalar) registers[stringReg], // string second
registers[regexReg].scalar(), // quotedRegex first
registers[stringReg].scalar(), // string second
ctx
);
return pc;
Expand All @@ -752,8 +752,8 @@ public static int executeMatchRegexNot(int[] bytecode, int pc, RuntimeBase[] reg
int regexReg = bytecode[pc++];
int ctx = bytecode[pc++];
RuntimeBase matchResult = RuntimeRegex.matchRegex(
(RuntimeScalar) registers[regexReg], // quotedRegex first
(RuntimeScalar) registers[stringReg], // string second
registers[regexReg].scalar(), // quotedRegex first
registers[stringReg].scalar(), // string second
ctx
);
// Negate the boolean result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ public static int executeEvalString(

int rd = bytecode[pc++];
int stringReg = bytecode[pc++];
// Read call-site package baked in by CompileOperator — interpreter equivalent
// of the JVM evalTag's ctx.symbolTable.getCurrentPackage().
String callSitePackage = code.stringPool[bytecode[pc++]];

// Get the code string - handle both RuntimeScalar and RuntimeList (from string interpolation)
RuntimeBase codeValue = registers[stringReg];
Expand All @@ -255,10 +258,11 @@ public static int executeEvalString(
// Call EvalStringHandler to parse, compile, and execute
RuntimeScalar result = EvalStringHandler.evalString(
perlCode,
code, // Current InterpretedCode for context
registers, // Current registers for variable access
code, // Current InterpretedCode for context
registers, // Current registers for variable access
code.sourceName,
code.sourceLine
code.sourceLine,
callSitePackage // Call-site package baked in at compile time
);

registers[rd] = result;
Expand Down