diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index 5b4a35fb6..c14edf705 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -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 entry : parentRegistry.entrySet()) { String varName = entry.getKey(); @@ -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; } /** diff --git a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java index b9c5cc77c..505c00733 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java +++ b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java @@ -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] ); @@ -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] ); @@ -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] ); diff --git a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java index f99c19fcc..39d8eafea 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java @@ -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;