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
110 changes: 56 additions & 54 deletions src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,13 @@ public BytecodeCompiler(String sourceName, int sourceLine, ErrorMessageUtil erro
globalScope.put("wantarray", 2);

if (parentRegistry != null) {
// Add parent scope variables (for eval STRING variable capture)
// Add parent scope variables to the global scope so hasVariable() finds them.
// A "my $x" inside the eval will call addVariable() which puts $x into the
// current (inner) scope, shadowing the global-scope entry — so lookups find
// the local my-declared register first (correct shadowing behaviour).
globalScope.putAll(parentRegistry);

// Mark parent scope variables as captured so assignments use SET_SCALAR
// Also mark them as captured so assignments use SET_SCALAR (not STORE_SCALAR)
capturedVarIndices = new HashMap<>();
for (Map.Entry<String, Integer> entry : parentRegistry.entrySet()) {
String varName = entry.getKey();
Expand Down Expand Up @@ -814,72 +817,71 @@ public void visit(IdentifierNode node) {
// Variable reference
String varName = node.name;

// Check if this is a captured variable (with sigil)
// Try common sigils: $, @, %
String[] sigils = {"$", "@", "%"};

// Check local scope first (my declarations shadow captured vars from outer scope).
// This is critical for eval STRING: if the eval declares "my $x", that new $x
// must shadow any $x captured from the outer scope (which lives at a different
// register index from the outer code's register file).
if (hasVariable(varName)) {
lastResultReg = getVariableRegister(varName);
return;
}
for (String sigil : sigils) {
String varNameWithSigil = sigil + varName;
if (hasVariable(varNameWithSigil)) {
lastResultReg = getVariableRegister(varNameWithSigil);
return;
}
}

// Not in local scope - check captured variables from outer eval scope.
// capturedVarIndices holds variables captured from the parent InterpretedCode
// (set up by EvalStringHandler). Only fall through to this if no local my
// declaration shadows the name.
for (String sigil : sigils) {
String varNameWithSigil = sigil + varName;
if (capturedVarIndices != null && capturedVarIndices.containsKey(varNameWithSigil)) {
// Captured variable - use its pre-allocated register
lastResultReg = capturedVarIndices.get(varNameWithSigil);
return;
}
}

// Check if it's a lexical variable (may have sigil or not)
if (hasVariable(varName)) {
// Lexical variable - already has a register
lastResultReg = getVariableRegister(varName);
} else {
// Try with sigils
boolean found = false;
for (String sigil : sigils) {
String varNameWithSigil = sigil + varName;
if (hasVariable(varNameWithSigil)) {
lastResultReg = getVariableRegister(varNameWithSigil);
found = true;
break;
}
// Not a lexical variable - could be a global or a bareword
// Check for strict subs violation (bareword without sigil)
if (!varName.startsWith("$") && !varName.startsWith("@") && !varName.startsWith("%")) {
// This is a bareword (no sigil)
if (emitterContext != null && emitterContext.symbolTable != null &&
emitterContext.symbolTable.isStrictOptionEnabled(Strict.HINT_STRICT_SUBS)) {
throwCompilerException("Bareword \"" + varName + "\" not allowed while \"strict subs\" in use");
}
// Not strict - treat bareword as string literal
int rd = allocateRegister();
emit(Opcodes.LOAD_STRING);
emitReg(rd);
int strIdx = addToStringPool(varName);
emit(strIdx);
lastResultReg = rd;
return;
}

if (!found) {
// Not a lexical variable - could be a global or a bareword
// Check for strict subs violation (bareword without sigil)
if (!varName.startsWith("$") && !varName.startsWith("@") && !varName.startsWith("%")) {
// This is a bareword (no sigil)
if (emitterContext != null && emitterContext.symbolTable != null &&
emitterContext.symbolTable.isStrictOptionEnabled(Strict.HINT_STRICT_SUBS)) {
throwCompilerException("Bareword \"" + varName + "\" not allowed while \"strict subs\" in use");
}
// Not strict - treat bareword as string literal
int rd = allocateRegister();
emit(Opcodes.LOAD_STRING);
emitReg(rd);
int strIdx = addToStringPool(varName);
emit(strIdx);
lastResultReg = rd;
return;
}

// Global variable
// Check strict vars before accessing
if (shouldBlockGlobalUnderStrictVars(varName)) {
throwCompilerException("Global symbol \"" + varName + "\" requires explicit package name");
}
// Global variable
// Check strict vars before accessing
if (shouldBlockGlobalUnderStrictVars(varName)) {
throwCompilerException("Global symbol \"" + varName + "\" requires explicit package name");
}

// Strip sigil and normalize name (e.g., "$x" → "main::x")
String bareVarName = varName.substring(1); // Remove sigil
String normalizedName = NameNormalizer.normalizeVariableName(bareVarName, getCurrentPackage());
int rd = allocateRegister();
int nameIdx = addToStringPool(normalizedName);
// Strip sigil and normalize name (e.g., "$x" → "main::x")
String bareVarName = varName.substring(1); // Remove sigil
String normalizedName = NameNormalizer.normalizeVariableName(bareVarName, getCurrentPackage());
int rd = allocateRegister();
int nameIdx = addToStringPool(normalizedName);

emit(Opcodes.LOAD_GLOBAL_SCALAR);
emitReg(rd);
emit(nameIdx);
emit(Opcodes.LOAD_GLOBAL_SCALAR);
emitReg(rd);
emit(nameIdx);

lastResultReg = rd;
}
}
lastResultReg = rd;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,12 +369,16 @@ public static int executeStringBitwiseXorAssign(int[] bytecode, int pc, RuntimeB
/**
* Execute bitwise AND binary operation.
* Format: BITWISE_AND_BINARY rd rs1 rs2
*
* Uses the context-sensitive bitwiseAnd() (not bitwiseAndBinary()) to match
* the JVM path: if operands are non-numeric strings, dispatches to string ops.
* bitwiseAndBinary() is only for the explicit "binary&" operator.
*/
public static int executeBitwiseAndBinary(int[] bytecode, int pc, RuntimeBase[] registers) {
int rd = bytecode[pc++];
int rs1 = bytecode[pc++];
int rs2 = bytecode[pc++];
registers[rd] = BitwiseOperators.bitwiseAndBinary(
registers[rd] = BitwiseOperators.bitwiseAnd(
(RuntimeScalar) registers[rs1],
(RuntimeScalar) registers[rs2]
);
Expand All @@ -384,12 +388,16 @@ public static int executeBitwiseAndBinary(int[] bytecode, int pc, RuntimeBase[]
/**
* Execute bitwise OR binary operation.
* Format: BITWISE_OR_BINARY rd rs1 rs2
*
* Uses the context-sensitive bitwiseOr() (not bitwiseOrBinary()) to match
* the JVM path: if operands are non-numeric strings, dispatches to string ops.
* bitwiseOrBinary() is only for the explicit "binary|" operator.
*/
public static int executeBitwiseOrBinary(int[] bytecode, int pc, RuntimeBase[] registers) {
int rd = bytecode[pc++];
int rs1 = bytecode[pc++];
int rs2 = bytecode[pc++];
registers[rd] = BitwiseOperators.bitwiseOrBinary(
registers[rd] = BitwiseOperators.bitwiseOr(
(RuntimeScalar) registers[rs1],
(RuntimeScalar) registers[rs2]
);
Expand All @@ -399,12 +407,16 @@ public static int executeBitwiseOrBinary(int[] bytecode, int pc, RuntimeBase[] r
/**
* Execute bitwise XOR binary operation.
* Format: BITWISE_XOR_BINARY rd rs1 rs2
*
* Uses the context-sensitive bitwiseXor() (not bitwiseXorBinary()) to match
* the JVM path: if operands are non-numeric strings, dispatches to string ops.
* bitwiseXorBinary() is only for the explicit "binary^" operator.
*/
public static int executeBitwiseXorBinary(int[] bytecode, int pc, RuntimeBase[] registers) {
int rd = bytecode[pc++];
int rs1 = bytecode[pc++];
int rs2 = bytecode[pc++];
registers[rd] = BitwiseOperators.bitwiseXorBinary(
registers[rd] = BitwiseOperators.bitwiseXor(
(RuntimeScalar) registers[rs1],
(RuntimeScalar) registers[rs2]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -777,10 +777,8 @@ public static int executeListSliceFrom(

int rd = bytecode[pc++];
int listReg = bytecode[pc++];
// Read startIndex as 2 shorts (int = high 16 bits + low 16 bits)
int high = bytecode[pc++] & 0xFFFF;
int low = bytecode[pc++] & 0xFFFF;
int startIndex = (high << 16) | low;
// Read startIndex as a single int (emitted by CompileAssignment via emitInt)
int startIndex = bytecode[pc++];

RuntimeBase listBase = registers[listReg];
RuntimeList sourceList;
Expand Down