From 3b55221f27cb12e59d115a5c5d41aec8fe8de265 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Wed, 25 Feb 2026 15:29:47 +0100 Subject: [PATCH 1/3] Fix interpreter: chop polymorphic, each LIST context, EACH direct container - OpcodeHandlerExtended.executeChop: use polymorphic .chop() instead of hard-casting to RuntimeScalar - fixes chop on arrays - CompileOperator each: force LIST context on operand so %h stays a RuntimeHash rather than being converted to ARRAY_SIZE (scalar size). Mirrors JVM handleEach which uses RuntimeContextType.LIST. - MiscOpcodeHandler: handle EACH before the RuntimeList cast since EACH now receives the container (RuntimeHash/RuntimeArray) directly --- .../backend/bytecode/CompileOperator.java | 47 ++++--------------- .../backend/bytecode/MiscOpcodeHandler.java | 18 +++---- .../bytecode/OpcodeHandlerExtended.java | 3 +- 3 files changed, 18 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index 991f416ae..971e9dc1f 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -2693,51 +2693,24 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.throwCompilerException("atan2 requires two arguments"); } } else if (op.equals("each")) { - // each %hash or each @array - needs the container itself, not flattened - // Format: OPCODE rd argsReg ctx - + // each %hash or each @array - needs the container itself, not flattened. + // Compile operand in LIST context so %h stays a RuntimeHash (not scalar size). + // Mirrors JVM handleEach which uses RuntimeContextType.LIST. if (node.operand == null) { bytecodeCompiler.throwCompilerException("each requires an argument"); } - int containerReg; - // Check if operand is a hash/array variable dereference (% or @) - if (node.operand instanceof OperatorNode) { - OperatorNode opNode = (OperatorNode) node.operand; - String varOp = opNode.operator; - if ((varOp.equals("%") || varOp.equals("@")) && opNode.operand instanceof IdentifierNode) { - // Direct hash/array variable: %h or @a - // Load the variable container directly - IdentifierNode varName = (IdentifierNode) opNode.operand; - String fullVarName = varOp + varName.name; - - if (bytecodeCompiler.hasVariable(fullVarName)) { - containerReg = bytecodeCompiler.getVariableRegister(fullVarName); - } else { - bytecodeCompiler.throwCompilerException("Variable " + fullVarName + " not found"); - return; // unreachable - } - } else { - // Complex expression - compile normally - node.operand.accept(bytecodeCompiler); - containerReg = bytecodeCompiler.lastResultReg; - } - } else { - // Not an operator node - compile normally - node.operand.accept(bytecodeCompiler); - containerReg = bytecodeCompiler.lastResultReg; - } - - // Wrap container in a list for the handler - int argsReg = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(Opcodes.SCALAR_TO_LIST); - bytecodeCompiler.emitReg(argsReg); - bytecodeCompiler.emitReg(containerReg); + int savedContext = bytecodeCompiler.currentCallContext; + bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; + node.operand.accept(bytecodeCompiler); + bytecodeCompiler.currentCallContext = savedContext; + int containerReg = bytecodeCompiler.lastResultReg; + // Pass container directly to EACH int rd = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emitWithToken(Opcodes.EACH, node.getIndex()); bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(argsReg); + bytecodeCompiler.emitReg(containerReg); bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); bytecodeCompiler.lastResultReg = rd; } else if (op.equals("chmod") || op.equals("unlink") || op.equals("utime") || diff --git a/src/main/java/org/perlonjava/backend/bytecode/MiscOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/MiscOpcodeHandler.java index 144304f7d..f8b97de2f 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/MiscOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/MiscOpcodeHandler.java @@ -26,6 +26,12 @@ public static int execute(int opcode, int[] bytecode, int pc, RuntimeBase[] regi int argsReg = bytecode[pc++]; int ctx = bytecode[pc++]; + // EACH receives the container directly (RuntimeHash or RuntimeArray), not a RuntimeList + if (opcode == Opcodes.EACH) { + registers[rd] = registers[argsReg].each(ctx); + return pc; + } + RuntimeList args = (RuntimeList) registers[argsReg]; RuntimeBase[] argsArray = args.elements.toArray(new RuntimeBase[0]); @@ -45,17 +51,7 @@ public static int execute(int opcode, int[] bytecode, int pc, RuntimeBase[] regi } case Opcodes.SYSTEM -> SystemOperator.system(args, false, ctx); case Opcodes.CALLER -> RuntimeCode.caller(args, ctx); - case Opcodes.EACH -> { - // EACH needs the container directly, not wrapped in a list - // The argsReg should contain a list with one element (the container) - if (!args.elements.isEmpty()) { - RuntimeBase container = args.elements.get(0); - // Call each on the container - yield container.each(ctx); - } else { - throw new RuntimeException("each requires a hash or array argument"); - } - } + // EACH is handled above before the RuntimeList cast case Opcodes.PACK -> Pack.pack(args); case Opcodes.UNPACK -> Unpack.unpack(ctx, argsArray); case Opcodes.VEC -> Vec.vec(args); diff --git a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java index ba9c71596..3ce4057f6 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java +++ b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java @@ -49,8 +49,7 @@ public static int executeChop(int[] bytecode, int pc, RuntimeBase[] registers) { int rd = bytecode[pc++]; int scalarReg = bytecode[pc++]; - RuntimeScalar scalar = (RuntimeScalar) registers[scalarReg]; - registers[rd] = StringOperators.chopScalar(scalar); + registers[rd] = registers[scalarReg].chop(); return pc; } From fa477fd623c8f7e2d156fcbb45b889e8aea32ad6 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Wed, 25 Feb 2026 15:35:07 +0100 Subject: [PATCH 2/3] Fix interpreter: glob assignment lvalue, DEREF error propagation, each/chop - CompileAssignment: handle '*' as glob lvalue (e.g. 'foo'->** = 'bar'->**) Fixes: op/postfixderef.t tests 27-29 (glob access syntax) - BytecodeInterpreter DEREF: always call scalarDeref() so non-reference types (IO, FORMAT, etc.) correctly throw 'Not a SCALAR reference' Fixes: op/postfixderef.t tests 59, 63 - CompileOperator each: force LIST context on operand so %h remains a RuntimeHash instead of being converted to ARRAY_SIZE via scalar context Fixes: op/lex_assign.t test 86 (each %h inside scalar assignment) - OpcodeHandlerExtended.executeChop: use polymorphic .chop() instead of hard-casting to RuntimeScalar - MiscOpcodeHandler: handle EACH before RuntimeList cast since EACH now receives the container (RuntimeHash/RuntimeArray) directly --- .../backend/bytecode/BytecodeInterpreter.java | 13 ++++--------- .../backend/bytecode/CompileAssignment.java | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index 0ab7c2261..1122edf2b 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -1405,15 +1405,10 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c int rs = bytecode[pc++]; RuntimeBase value = registers[rs]; - // Only dereference if it's a RuntimeScalar with REFERENCE type - if (value instanceof RuntimeScalar) { - RuntimeScalar scalar = (RuntimeScalar) value; - if (scalar.type == RuntimeScalarType.REFERENCE) { - registers[rd] = scalar.scalarDeref(); - } else { - // Non-reference scalar, just copy - registers[rd] = value; - } + if (value instanceof RuntimeScalar scalar) { + // Always go through scalarDeref() so non-reference types throw + // "Not a SCALAR reference" (matches JVM path behaviour) + registers[rd] = scalar.scalarDeref(); } else { // RuntimeList or other types, pass through registers[rd] = value; diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index 14aa3f819..25e0c5404 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -991,6 +991,22 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } else { bytecodeCompiler.throwCompilerException("Assignment to unsupported array dereference"); } + } else if (leftOp.operator.equals("*")) { + // Glob assignment: *foo = *bar or 'foo'->** = ... + // Compile LHS glob, compile RHS, emit STORE_GLOB + int savedCtx = bytecodeCompiler.currentCallContext; + bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; + node.left.accept(bytecodeCompiler); + bytecodeCompiler.currentCallContext = savedCtx; + int globReg = bytecodeCompiler.lastResultReg; + + node.right.accept(bytecodeCompiler); + int valueReg2 = bytecodeCompiler.lastResultReg; + + bytecodeCompiler.emit(Opcodes.STORE_GLOB); + bytecodeCompiler.emitReg(globReg); + bytecodeCompiler.emitReg(valueReg2); + bytecodeCompiler.lastResultReg = globReg; } else { // chop/chomp cannot be used as lvalues (matches JVM compiler message) if (leftOp.operator.equals("chop") || leftOp.operator.equals("chomp")) { From b2a047074e0e04f0c811d3749058b5e4580017bd Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Wed, 25 Feb 2026 16:46:12 +0100 Subject: [PATCH 3/3] Fix DEREF_SCALAR_STRICT/NONSTRICT: use .scalar() to handle RuntimeList operands --- .../org/perlonjava/backend/bytecode/SlowOpcodeHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java index 37c82cb41..d76d18b75 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java @@ -354,7 +354,7 @@ public static int executeDerefScalarStrict( int rd = bytecode[pc++]; int rs = bytecode[pc++]; - registers[rd] = ((RuntimeScalar) registers[rs]).scalarDeref(); + registers[rd] = registers[rs].scalar().scalarDeref(); return pc; } @@ -373,7 +373,7 @@ public static int executeDerefScalarNonStrict( int rs = bytecode[pc++]; int pkgIdx = bytecode[pc++]; String pkg = code.stringPool[pkgIdx]; - registers[rd] = ((RuntimeScalar) registers[rs]).scalarDerefNonStrict(pkg); + registers[rd] = registers[rs].scalar().scalarDerefNonStrict(pkg); return pc; }