From 3dadb30ad0a57b8efd2be861def7a8b71dfbff35 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Thu, 19 Feb 2026 19:03:04 +0100 Subject: [PATCH 01/10] fix: Protect foreach iterator registers from recycling Fixed register reuse bug in foreach loops that caused ClassCastException when trying to cast Integer to Iterator. Root cause: - Iterator register (iterReg) was allocated before enterScope() - Inside loop body, recycleTemporaryRegisters() would reset nextRegister - getHighestVariableRegister() didn't account for iterator register - On second iteration, iterator register got reused for LOAD_INT - FOREACH_NEXT_OR_EXIT tried to cast Integer to Iterator -> crash Solution: 1. Allocate both iterReg and varReg BEFORE enterScope() 2. Modified recycleTemporaryRegisters() to use Math.max() to preserve baseRegisterForStatement set by enterScope() 3. This protects iterator and loop variable registers across iterations Test improvements: - perf/benchmarks.t: 1886/1960 -> 1907/1960 (+21 tests, 96.2% -> 97.3%) Co-Authored-By: Claude Opus 4.6 --- .../interpreter/BytecodeCompiler.java | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java b/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java index 2afe2e686..34f231911 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java @@ -2763,6 +2763,7 @@ private int getHighestVariableRegister() { * - Reserved registers (0-2) * - All variables in all scopes * - Captured variables (closures) + * - Registers protected by enterScope() (baseRegisterForStatement) * * NOTE: This assumes that by the end of a statement, all intermediate values * have either been stored in variables or used/discarded. Any value that needs @@ -2770,7 +2771,12 @@ private int getHighestVariableRegister() { */ private void recycleTemporaryRegisters() { // Find highest variable register and reset nextRegister to one past it - baseRegisterForStatement = getHighestVariableRegister() + 1; + int highestVarReg = getHighestVariableRegister() + 1; + + // CRITICAL: Never lower baseRegisterForStatement below what enterScope() set. + // This protects registers like foreach iterators that must survive across loop iterations. + // Use Math.max to preserve the higher value set by enterScope() + baseRegisterForStatement = Math.max(baseRegisterForStatement, highestVarReg); nextRegister = baseRegisterForStatement; // DO NOT reset lastResultReg - BlockNode needs it to return the last statement's result @@ -3275,10 +3281,8 @@ public void visit(For1Node node) { emitReg(iterReg); emitReg(listReg); - // Step 3: Enter new scope for loop variable - enterScope(); - - // Step 4: Declare loop variable in the new scope + // Step 3: Allocate loop variable register BEFORE entering scope + // This ensures both iterReg and varReg are protected from recycling int varReg = -1; if (node.variable != null && node.variable instanceof OperatorNode) { OperatorNode varOp = (OperatorNode) node.variable; @@ -3286,7 +3290,8 @@ public void visit(For1Node node) { OperatorNode sigilOp = (OperatorNode) varOp.operand; if (sigilOp.operator.equals("$") && sigilOp.operand instanceof IdentifierNode) { String varName = "$" + ((IdentifierNode) sigilOp.operand).name; - varReg = addVariable(varName, "my"); + // Don't add to scope yet - just allocate register + varReg = allocateRegister(); } } } @@ -3296,12 +3301,31 @@ public void visit(For1Node node) { varReg = allocateRegister(); } - // Step 5: Push loop info onto stack for last/next/redo + // Step 4: Enter new scope for loop variable + // Now baseRegisterForStatement will be set past both iterReg and varReg, + // protecting them from being recycled by recycleTemporaryRegisters() + enterScope(); + + // Step 5: If we have a named loop variable, add it to the scope now + if (node.variable != null && node.variable instanceof OperatorNode) { + OperatorNode varOp = (OperatorNode) node.variable; + if (varOp.operator.equals("my") && varOp.operand instanceof OperatorNode) { + OperatorNode sigilOp = (OperatorNode) varOp.operand; + if (sigilOp.operator.equals("$") && sigilOp.operand instanceof IdentifierNode) { + String varName = "$" + ((IdentifierNode) sigilOp.operand).name; + // Add to scope and track for variableRegistry + variableScopes.peek().put(varName, varReg); + allDeclaredVariables.put(varName, varReg); + } + } + } + + // Step 6: Push loop info onto stack for last/next/redo int loopStartPc = bytecode.size(); LoopInfo loopInfo = new LoopInfo(node.labelName, loopStartPc, true); // true = foreach is a true loop loopStack.push(loopInfo); - // Step 6: Loop start - combined check/next/exit (superinstruction) + // Step 7: Loop start - combined check/next/exit (superinstruction) // Emit FOREACH_NEXT_OR_EXIT superinstruction // This combines: hasNext check, next() call, and conditional jump // Format: FOREACH_NEXT_OR_EXIT varReg, iterReg, exitTarget (absolute address) @@ -3311,23 +3335,23 @@ public void visit(For1Node node) { int loopEndJumpPc = bytecode.size(); emitInt(0); // placeholder for exit target (absolute, will be patched) - // Step 7: Execute body (redo jumps here) + // Step 8: Execute body (redo jumps here) if (node.body != null) { node.body.accept(this); } - // Step 8: Continue point (next jumps here) + // Step 9: Continue point (next jumps here) loopInfo.continuePc = bytecode.size(); - // Step 9: Jump back to loop start + // Step 10: Jump back to loop start emit(Opcodes.GOTO); emitInt(loopStartPc); - // Step 10: Loop end - patch the forward jump (last jumps here) + // Step 11: Loop end - patch the forward jump (last jumps here) int loopEndPc = bytecode.size(); patchJump(loopEndJumpPc, loopEndPc); - // Step 11: Patch all last/next/redo jumps + // Step 12: Patch all last/next/redo jumps for (int pc : loopInfo.breakPcs) { patchJump(pc, loopEndPc); } @@ -3338,7 +3362,7 @@ public void visit(For1Node node) { patchJump(pc, loopStartPc); } - // Step 12: Pop loop info and exit scope + // Step 13: Pop loop info and exit scope loopStack.pop(); exitScope(); From f8f25dd5259892b5a0c08b96711b2a49f0c9e3bf Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Thu, 19 Feb 2026 19:17:38 +0100 Subject: [PATCH 02/10] fix: Return scalar context value for array/hash assignments Fixed array and hash assignments to return scalar values when used in scalar context (e.g., `!(@a = @b)`). Root cause: - Array/hash assignments always returned the array/hash object - When used in scalar context (e.g., NOT operator), this caused ClassCastException: RuntimeArray cannot be cast to RuntimeScalar Solution: - Check savedContext (the calling context before assignment modified it) - If SCALAR context, emit ARRAY_SIZE after ARRAY_SET_FROM_LIST/HASH_SET_FROM_LIST - ARRAY_SIZE calls .scalar() which converts array to size, hash to bucket info Test improvements: - perf/benchmarks.t: 1907/1960 -> 1908/1960 (+1 test) - expr::aassign::boolean now passes Co-Authored-By: Claude Opus 4.6 --- .../interpreter/CompileAssignment.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/perlonjava/interpreter/CompileAssignment.java b/src/main/java/org/perlonjava/interpreter/CompileAssignment.java index acbcef366..be8bb543c 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileAssignment.java +++ b/src/main/java/org/perlonjava/interpreter/CompileAssignment.java @@ -745,7 +745,17 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(arrayReg); bytecodeCompiler.emitReg(valueReg); - bytecodeCompiler.lastResultReg = arrayReg; + // In scalar context, return the array size; in list context, return the array + if (savedContext == RuntimeContextType.SCALAR) { + // Convert array to scalar (returns size) + int sizeReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); + bytecodeCompiler.emitReg(sizeReg); + bytecodeCompiler.emitReg(arrayReg); + bytecodeCompiler.lastResultReg = sizeReg; + } else { + bytecodeCompiler.lastResultReg = arrayReg; + } } else if (leftOp.operator.equals("%") && leftOp.operand instanceof IdentifierNode) { // Hash assignment: %hash = ... String varName = "%" + ((IdentifierNode) leftOp.operand).name; @@ -769,7 +779,17 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(hashReg); bytecodeCompiler.emitReg(valueReg); - bytecodeCompiler.lastResultReg = hashReg; + // In scalar context, return the hash size; in list context, return the hash + if (savedContext == RuntimeContextType.SCALAR) { + // Convert hash to scalar (returns bucket info like "3/8") + int sizeReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); + bytecodeCompiler.emitReg(sizeReg); + bytecodeCompiler.emitReg(hashReg); + bytecodeCompiler.lastResultReg = sizeReg; + } else { + bytecodeCompiler.lastResultReg = hashReg; + } } else if (leftOp.operator.equals("our")) { // Assignment to our variable: our $x = value or our @x = value or our %x = value // Compile the our declaration first (which loads the global into a register) From 391b4e0f24c858563f5f2f190965614a30a7047d Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Thu, 19 Feb 2026 19:21:45 +0100 Subject: [PATCH 03/10] fix: Convert hash to scalar in scalar context Fixed hash variable references to return scalar values when used in scalar context (e.g., `!%h`). Root cause: - Hash references (%) always returned RuntimeHash object - When used in scalar context (e.g., NOT operator), this caused ClassCastException: RuntimeHash cannot be cast to RuntimeScalar Solution: - Check currentCallContext == RuntimeContextType.SCALAR for % operator - Emit ARRAY_SIZE after loading hash (calls .scalar() for conversion) - This matches the pattern already used for @ (array) operator Test improvements: - expr::hash::bool_empty, expr::hash::bool_full now pass - Hash boolean expressions work correctly in interpreter Co-Authored-By: Claude Opus 4.6 --- .../interpreter/BytecodeCompiler.java | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java b/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java index 34f231911..afb0cbb65 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java @@ -2606,23 +2606,32 @@ void compileVariableReference(OperatorNode node, String op) { if (node.operand instanceof IdentifierNode) { String varName = "%" + ((IdentifierNode) node.operand).name; - // Check if it's a lexical hash + // Get the hash register + int hashReg; if (hasVariable(varName)) { // Lexical hash - use existing register - lastResultReg = getVariableRegister(varName); - return; - } - - // Global hash - load it - int rd = allocateRegister(); - String globalHashName = NameNormalizer.normalizeVariableName(((IdentifierNode) node.operand).name, getCurrentPackage()); - int nameIdx = addToStringPool(globalHashName); + hashReg = getVariableRegister(varName); + } else { + // Global hash - load it + hashReg = allocateRegister(); + String globalHashName = NameNormalizer.normalizeVariableName(((IdentifierNode) node.operand).name, getCurrentPackage()); + int nameIdx = addToStringPool(globalHashName); - emit(Opcodes.LOAD_GLOBAL_HASH); - emitReg(rd); - emit(nameIdx); + emit(Opcodes.LOAD_GLOBAL_HASH); + emitReg(hashReg); + emit(nameIdx); + } - lastResultReg = rd; + // Check if we're in scalar context - if so, return hash as scalar (bucket info) + if (currentCallContext == RuntimeContextType.SCALAR) { + int rd = allocateRegister(); + emit(Opcodes.ARRAY_SIZE); // Works for hashes too - calls .scalar() + emitReg(rd); + emitReg(hashReg); + lastResultReg = rd; + } else { + lastResultReg = hashReg; + } } else { throwCompilerException("Unsupported % operand: " + node.operand.getClass().getSimpleName()); } From 874a70c127fb5256745d00b8af550efa2fd41b86 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Thu, 19 Feb 2026 19:25:33 +0100 Subject: [PATCH 04/10] fix: Update opcode generator for refactored BytecodeCompiler After refactoring BytecodeCompiler into CompileOperator.java, the opcode generator was looking for GENERATED_OPERATORS markers in the wrong file. Changed bytecode_compiler_file path from: BytecodeCompiler.java -> CompileOperator.java Note: Generator still needs updates to work with static context (generate bytecodeCompiler.method() instead of this.method()) Co-Authored-By: Claude Opus 4.6 --- dev/tools/generate_opcode_handlers.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tools/generate_opcode_handlers.pl b/dev/tools/generate_opcode_handlers.pl index a351b730e..8f17a40ff 100755 --- a/dev/tools/generate_opcode_handlers.pl +++ b/dev/tools/generate_opcode_handlers.pl @@ -8,7 +8,7 @@ my $opcodes_file = 'src/main/java/org/perlonjava/interpreter/Opcodes.java'; my $bytecode_interpreter_file = 'src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java'; my $interpreted_code_file = 'src/main/java/org/perlonjava/interpreter/InterpretedCode.java'; -my $bytecode_compiler_file = 'src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java'; +my $bytecode_compiler_file = 'src/main/java/org/perlonjava/interpreter/CompileOperator.java'; # Changed from BytecodeCompiler.java my $output_dir = 'src/main/java/org/perlonjava/interpreter'; # Read existing opcodes and LASTOP from Opcodes.java From 88f4f60406fed9bf6d27395822f5b1eb61cc83d3 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Thu, 19 Feb 2026 20:43:16 +0100 Subject: [PATCH 05/10] feat: Add sprintf operator support to interpreter Implemented sprintf for interpreter eval STRING execution. Changes: - Added SPRINTF opcode (234) to Opcodes.java - Implemented sprintf handler in BytecodeInterpreter.java - Added sprintf disassembly in InterpretedCode.java - Added sprintf compilation in CompileBinaryOperator.java - Handles sprintf as BinaryOperatorNode (format, args_list) - Creates RuntimeList from arguments and calls SprintfOperator.sprintf() Test improvements: - perf/benchmarks.t: 1905/1960 -> 1936/1960 (+31 tests, 97.2% -> 98.8%) - All sprintf tests now pass Format: SPRINTF rd formatReg argsListReg Co-Authored-By: Claude Opus 4.6 --- .../interpreter/BytecodeInterpreter.java | 14 ++++++ .../interpreter/CompileBinaryOperator.java | 48 +++++++++++++++++++ .../interpreter/CompileOperator.java | 37 ++++++++++++++ .../interpreter/InterpretedCode.java | 6 +++ .../org/perlonjava/interpreter/Opcodes.java | 30 +++++++----- 5 files changed, 122 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java index ef8d55bb9..d23381a2c 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java @@ -2337,6 +2337,20 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } + case Opcodes.SPRINTF: { + // sprintf($format, @args): rd = SprintfOperator.sprintf(formatReg, argsListReg) + // Format: SPRINTF rd formatReg argsListReg + int rd = bytecode[pc++]; + int formatReg = bytecode[pc++]; + int argsListReg = bytecode[pc++]; + + RuntimeScalar format = (RuntimeScalar) registers[formatReg]; + RuntimeList argsList = (RuntimeList) registers[argsListReg]; + + registers[rd] = org.perlonjava.operators.SprintfOperator.sprintf(format, argsList); + break; + } + // GENERATED_HANDLERS_END default: diff --git a/src/main/java/org/perlonjava/interpreter/CompileBinaryOperator.java b/src/main/java/org/perlonjava/interpreter/CompileBinaryOperator.java index 10fa9a1a7..dacfa4efe 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileBinaryOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileBinaryOperator.java @@ -37,6 +37,54 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato return; } + // Handle sprintf early (special handling for list of arguments) + if (node.operator.equals("sprintf")) { + // sprintf $format, @args + // left = format string + // right = ListNode of arguments + + // Compile the format string (left operand) + node.left.accept(bytecodeCompiler); + int formatReg = bytecodeCompiler.lastResultReg; + + // Compile the arguments (right operand) into a list + int argsListReg = bytecodeCompiler.allocateRegister(); + if (node.right instanceof ListNode) { + ListNode argsList = (ListNode) node.right; + // Compile each argument + java.util.List argRegs = new java.util.ArrayList<>(); + for (Node arg : argsList.elements) { + arg.accept(bytecodeCompiler); + argRegs.add(bytecodeCompiler.lastResultReg); + } + // Create list with arguments: CREATE_LIST rd count [regs...] + bytecodeCompiler.emit(Opcodes.CREATE_LIST); + bytecodeCompiler.emitReg(argsListReg); + bytecodeCompiler.emit(argRegs.size()); // emit count + for (int argReg : argRegs) { + bytecodeCompiler.emitReg(argReg); + } + } else { + // Single argument - wrap in list + node.right.accept(bytecodeCompiler); + int argReg = bytecodeCompiler.lastResultReg; + bytecodeCompiler.emit(Opcodes.CREATE_LIST); + bytecodeCompiler.emitReg(argsListReg); + bytecodeCompiler.emit(1); // emit count = 1 + bytecodeCompiler.emitReg(argReg); + } + + // Call sprintf + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.SPRINTF); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(formatReg); + bytecodeCompiler.emitReg(argsListReg); + + bytecodeCompiler.lastResultReg = rd; + return; + } + // Handle compound assignment operators (+=, -=, *=, /=, %=, .=, &=, |=, ^=, &.=, |.=, ^.=, binary&=, binary|=, binary^=, x=, **=, <<=, >>=, &&=, ||=) if (node.operator.equals("+=") || node.operator.equals("-=") || node.operator.equals("*=") || node.operator.equals("/=") || diff --git a/src/main/java/org/perlonjava/interpreter/CompileOperator.java b/src/main/java/org/perlonjava/interpreter/CompileOperator.java index ae7626c8c..bd23d630b 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileOperator.java @@ -1773,6 +1773,43 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(2); // Register 2 contains the calling context bytecodeCompiler.lastResultReg = rd; + } else if (op.equals("sprintf")) { + // sprintf($format, @args) - SprintfOperator.sprintf + if (node.operand instanceof ListNode) { + ListNode list = (ListNode) node.operand; + if (list.elements.isEmpty()) { + bytecodeCompiler.throwCompilerException("sprintf requires a format argument"); + } + + // First argument is the format string + list.elements.get(0).accept(bytecodeCompiler); + int formatReg = bytecodeCompiler.lastResultReg; + + // Compile remaining arguments and collect their registers + java.util.List argRegs = new java.util.ArrayList<>(); + for (int i = 1; i < list.elements.size(); i++) { + list.elements.get(i).accept(bytecodeCompiler); + argRegs.add(bytecodeCompiler.lastResultReg); + } + + // Create a RuntimeList with the arguments + int listReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.CREATE_LIST); + bytecodeCompiler.emitReg(listReg); + for (int argReg : argRegs) { + bytecodeCompiler.emitReg(argReg); + } + + // Call sprintf with format and arg list + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.SPRINTF); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(formatReg); + bytecodeCompiler.emitReg(listReg); + bytecodeCompiler.lastResultReg = rd; + } else { + bytecodeCompiler.throwCompilerException("sprintf requires arguments"); + } // GENERATED_OPERATORS_START } else if (op.equals("int")) { // int($x) - MathOperators.integer diff --git a/src/main/java/org/perlonjava/interpreter/InterpretedCode.java b/src/main/java/org/perlonjava/interpreter/InterpretedCode.java index e526d1be6..fb31d04a9 100644 --- a/src/main/java/org/perlonjava/interpreter/InterpretedCode.java +++ b/src/main/java/org/perlonjava/interpreter/InterpretedCode.java @@ -714,6 +714,12 @@ public String disassemble() { int keyReg = bytecode[pc++]; sb.append("GLOB_SLOT_GET r").append(rd).append(" = r").append(globReg2).append("{r").append(keyReg).append("}\n"); break; + case Opcodes.SPRINTF: + rd = bytecode[pc++]; + int formatReg = bytecode[pc++]; + int argsListReg = bytecode[pc++]; + sb.append("SPRINTF r").append(rd).append(" = sprintf(r").append(formatReg).append(", r").append(argsListReg).append(")\n"); + break; case Opcodes.PUSH_LOCAL_VARIABLE: rs = bytecode[pc++]; sb.append("PUSH_LOCAL_VARIABLE r").append(rs).append("\n"); diff --git a/src/main/java/org/perlonjava/interpreter/Opcodes.java b/src/main/java/org/perlonjava/interpreter/Opcodes.java index 3e3293730..62b5a6aa5 100644 --- a/src/main/java/org/perlonjava/interpreter/Opcodes.java +++ b/src/main/java/org/perlonjava/interpreter/Opcodes.java @@ -866,10 +866,26 @@ public class Opcodes { * Used for *X{HASH} style access to glob slots */ public static final short GLOB_SLOT_GET = 230; + /** Store via symbolic reference: GlobalVariable.getGlobalVariable(nameReg.toString()).set(valueReg) + * Format: STORE_SYMBOLIC_SCALAR nameReg valueReg */ + public static final short STORE_SYMBOLIC_SCALAR = 231; + + /** Load via symbolic reference: rd = GlobalVariable.getGlobalVariable(nameReg.toString()).get() + * Format: LOAD_SYMBOLIC_SCALAR rd nameReg */ + public static final short LOAD_SYMBOLIC_SCALAR = 232; + + /** File test on cached handle '_': rd = FileTestOperator.fileTestLastHandle(operator) + * Format: FILETEST_LASTHANDLE rd operator_string_idx */ + public static final short FILETEST_LASTHANDLE = 233; + + /** sprintf($format, @args): rd = SprintfOperator.sprintf(formatReg, argsListReg) + * Format: SPRINTF rd formatReg argsListReg */ + public static final short SPRINTF = 234; + // ================================================================= // BUILT-IN FUNCTION OPCODES - after LASTOP // Last manually-assigned opcode (for tool reference) - private static final short LASTOP = 230; + private static final short LASTOP = 234; // ================================================================= // Generated by dev/tools/generate_opcode_handlers.pl @@ -924,18 +940,6 @@ public class Opcodes { public static final short CHDIR = LASTOP + 42; public static final short EXIT = LASTOP + 43; - /** Store via symbolic reference: GlobalVariable.getGlobalVariable(nameReg.toString()).set(valueReg) - * Format: STORE_SYMBOLIC_SCALAR nameReg valueReg */ - public static final short STORE_SYMBOLIC_SCALAR = LASTOP + 44; - - /** Load via symbolic reference: rd = GlobalVariable.getGlobalVariable(nameReg.toString()).get() - * Format: LOAD_SYMBOLIC_SCALAR rd nameReg */ - public static final short LOAD_SYMBOLIC_SCALAR = LASTOP + 45; - - /** File test on cached handle '_': rd = FileTestOperator.fileTestLastHandle(operator) - * Format: FILETEST_LASTHANDLE rd operator_string_idx */ - public static final short FILETEST_LASTHANDLE = LASTOP + 46; - // GENERATED_OPCODES_END From 38c095fbd4452a7a462bbe4d640a57c8ae00654b Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Thu, 19 Feb 2026 20:58:04 +0100 Subject: [PATCH 06/10] feat: Add keys/values scalar context support to interpreter Fixed keys() and values() to work correctly in scalar context. Root cause: - keys/values compiled operand in current context - When in scalar context, %hash was converted to scalar (bucket info) - HASH_KEYS/HASH_VALUES tried to cast scalar to hash -> ClassCastException Solution: - Force LIST context when compiling keys/values operands - Check savedContext after HASH_KEYS/HASH_VALUES - If scalar context, emit ARRAY_SIZE to convert list to count - This matches Perl semantics: keys in scalar = count, keys in list = keys Test improvements: - perf/benchmarks.t: 1936/1960 -> 1946/1960 (+10 tests, 98.8% -> 99.3%) - All keys/values boolean and scalar context tests pass Co-Authored-By: Claude Opus 4.6 --- .../interpreter/CompileOperator.java | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/perlonjava/interpreter/CompileOperator.java b/src/main/java/org/perlonjava/interpreter/CompileOperator.java index bd23d630b..63860902f 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileOperator.java @@ -1496,8 +1496,14 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.throwCompilerException("keys requires a hash argument"); } - // Compile the hash operand - node.operand.accept(bytecodeCompiler); + // Compile the hash operand in LIST context (to avoid scalar conversion) + int savedContext = bytecodeCompiler.currentCallContext; + bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; + try { + node.operand.accept(bytecodeCompiler); + } finally { + bytecodeCompiler.currentCallContext = savedContext; + } int hashReg = bytecodeCompiler.lastResultReg; // Emit HASH_KEYS @@ -1506,7 +1512,18 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(hashReg); - bytecodeCompiler.lastResultReg = rd; + // keys() returns a list in list context, scalar count in scalar context + // The RuntimeHash.keys() method returns a RuntimeList + // In scalar context, convert to scalar (count) + if (savedContext == RuntimeContextType.SCALAR) { + int scalarReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); + bytecodeCompiler.emitReg(scalarReg); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.lastResultReg = scalarReg; + } else { + bytecodeCompiler.lastResultReg = rd; + } } else if (op.equals("values")) { // values %hash // operand: hash variable (OperatorNode("%" ...) or other expression) @@ -1514,8 +1531,14 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.throwCompilerException("values requires a hash argument"); } - // Compile the hash operand - node.operand.accept(bytecodeCompiler); + // Compile the hash operand in LIST context (to avoid scalar conversion) + int savedContext = bytecodeCompiler.currentCallContext; + bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; + try { + node.operand.accept(bytecodeCompiler); + } finally { + bytecodeCompiler.currentCallContext = savedContext; + } int hashReg = bytecodeCompiler.lastResultReg; // Emit HASH_VALUES @@ -1524,7 +1547,18 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(hashReg); - bytecodeCompiler.lastResultReg = rd; + // values() returns a list in list context, scalar count in scalar context + // The RuntimeHash.values() method returns a RuntimeList + // In scalar context, convert to scalar (count) + if (savedContext == RuntimeContextType.SCALAR) { + int scalarReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); + bytecodeCompiler.emitReg(scalarReg); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.lastResultReg = scalarReg; + } else { + bytecodeCompiler.lastResultReg = rd; + } } else if (op.equals("$#")) { // $#array - get last index of array (size - 1) // operand: array variable (OperatorNode("@" ...) or IdentifierNode) From ee4976864f1d18abcc918a8ec1fc9dec55581f28 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Thu, 19 Feb 2026 21:01:30 +0100 Subject: [PATCH 07/10] feat: Add chop operator support to interpreter Implemented chop operator for removing last character from string. Changes: - Added CHOP opcode (235) to Opcodes.java - Implemented chop handler in BytecodeInterpreter.java - Added chop disassembly in InterpretedCode.java - Added chop compilation in CompileOperator.java - Handles ListNode wrapping of operand (common parser pattern) - Calls StringOperators.chopScalar() which modifies in place Test improvements: - perf/benchmarks.t: 1946/1960 -> 1948/1960 (+2 tests, 99.3% -> 99.4%) - func::index::utf8_position_1, func::length::bool0_utf8 now pass Format: CHOP rd scalarReg Co-Authored-By: Claude Opus 4.6 --- .../interpreter/BytecodeInterpreter.java | 11 ++++++++ .../interpreter/CompileOperator.java | 28 +++++++++++++++++++ .../interpreter/InterpretedCode.java | 5 ++++ .../org/perlonjava/interpreter/Opcodes.java | 6 +++- 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java index d23381a2c..a7519434c 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java @@ -2351,6 +2351,17 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } + case Opcodes.CHOP: { + // chop($x): rd = StringOperators.chopScalar(scalarReg) + // Format: CHOP rd scalarReg + int rd = bytecode[pc++]; + int scalarReg = bytecode[pc++]; + + RuntimeScalar scalar = (RuntimeScalar) registers[scalarReg]; + registers[rd] = org.perlonjava.operators.StringOperators.chopScalar(scalar); + break; + } + // GENERATED_HANDLERS_END default: diff --git a/src/main/java/org/perlonjava/interpreter/CompileOperator.java b/src/main/java/org/perlonjava/interpreter/CompileOperator.java index 63860902f..2823a26ab 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileOperator.java @@ -1524,6 +1524,34 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else { bytecodeCompiler.lastResultReg = rd; } + } else if (op.equals("chop")) { + // chop $x - remove last character, modifies argument in place + // operand: ListNode containing scalar variable reference + if (node.operand == null) { + bytecodeCompiler.throwCompilerException("chop requires an argument"); + } + + // Extract the actual operand from ListNode if needed + Node actualOperand = node.operand; + if (actualOperand instanceof ListNode) { + ListNode list = (ListNode) actualOperand; + if (list.elements.isEmpty()) { + bytecodeCompiler.throwCompilerException("chop requires an argument"); + } + actualOperand = list.elements.get(0); + } + + // Compile the operand (should be an lvalue) + actualOperand.accept(bytecodeCompiler); + int scalarReg = bytecodeCompiler.lastResultReg; + + // Call chopScalar and store result back + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.CHOP); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(scalarReg); + + bytecodeCompiler.lastResultReg = rd; } else if (op.equals("values")) { // values %hash // operand: hash variable (OperatorNode("%" ...) or other expression) diff --git a/src/main/java/org/perlonjava/interpreter/InterpretedCode.java b/src/main/java/org/perlonjava/interpreter/InterpretedCode.java index fb31d04a9..46b1d85ab 100644 --- a/src/main/java/org/perlonjava/interpreter/InterpretedCode.java +++ b/src/main/java/org/perlonjava/interpreter/InterpretedCode.java @@ -720,6 +720,11 @@ public String disassemble() { int argsListReg = bytecode[pc++]; sb.append("SPRINTF r").append(rd).append(" = sprintf(r").append(formatReg).append(", r").append(argsListReg).append(")\n"); break; + case Opcodes.CHOP: + rd = bytecode[pc++]; + int scalarReg = bytecode[pc++]; + sb.append("CHOP r").append(rd).append(" = chop(r").append(scalarReg).append(")\n"); + break; case Opcodes.PUSH_LOCAL_VARIABLE: rs = bytecode[pc++]; sb.append("PUSH_LOCAL_VARIABLE r").append(rs).append("\n"); diff --git a/src/main/java/org/perlonjava/interpreter/Opcodes.java b/src/main/java/org/perlonjava/interpreter/Opcodes.java index 62b5a6aa5..6059af9d7 100644 --- a/src/main/java/org/perlonjava/interpreter/Opcodes.java +++ b/src/main/java/org/perlonjava/interpreter/Opcodes.java @@ -882,10 +882,14 @@ public class Opcodes { * Format: SPRINTF rd formatReg argsListReg */ public static final short SPRINTF = 234; + /** chop($x): rd = StringOperators.chopScalar(scalarReg) - modifies in place + * Format: CHOP rd scalarReg */ + public static final short CHOP = 235; + // ================================================================= // BUILT-IN FUNCTION OPCODES - after LASTOP // Last manually-assigned opcode (for tool reference) - private static final short LASTOP = 234; + private static final short LASTOP = 235; // ================================================================= // Generated by dev/tools/generate_opcode_handlers.pl From e3bd7f028d9d1736098a29f4b371e97e40281b76 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Thu, 19 Feb 2026 22:05:41 +0100 Subject: [PATCH 08/10] feat: Achieve 100% pass rate on perf/benchmarks.t with interpreter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement remaining operators and fix context handling to reach 100% pass rate (1960/1960 tests) on perf/benchmarks.t when running with JPERL_EVAL_USE_INTERPRETER=1. Operators implemented: - replaceRegex (s///): Added GET_REPLACEMENT_REGEX opcode (236) for regex substitution with proper =~ binding and context propagation - substr: Added SUBSTR_VAR opcode (237) for variable-argument substr with optional replacement parameter - Array dereference assignment: Added support for @$r = list assignments Context fixes: - grep: Fixed to return scalar count in SCALAR context instead of always returning RuntimeList, preventing ClassCastException in boolean context - Assignment: Properly handle scalar/list context for array assignments Build system: - Updated Makefile dev target to include shadowJar, ensuring JAR with all dependencies is built to target/ directory Progress: 1886/1960 (96.2%) → 1960/1960 (100.0%) tests passing Co-Authored-By: Claude Opus 4.6 --- Makefile | 4 +- .../interpreter/BytecodeInterpreter.java | 38 +++++++- .../interpreter/CompileAssignment.java | 34 +++++++ .../interpreter/CompileBinaryOperator.java | 45 +++++++++ .../CompileBinaryOperatorHelper.java | 2 +- .../interpreter/CompileOperator.java | 97 +++++++++++++++++++ .../interpreter/InterpretedCode.java | 13 +++ .../org/perlonjava/interpreter/Opcodes.java | 10 +- 8 files changed, 238 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 244afa93e..e028ecbcf 100644 --- a/Makefile +++ b/Makefile @@ -24,9 +24,9 @@ endif # Development build - forces recompilation (use during active development) dev: wrapper ifeq ($(OS),Windows_NT) - gradlew.bat clean compileJava installDist + gradlew.bat clean compileJava shadowJar installDist else - ./gradlew clean compileJava installDist + ./gradlew clean compileJava shadowJar installDist endif # Default test target - fast unit tests using perl_test_runner.pl diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java index a7519434c..96d8977ec 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java @@ -2075,7 +2075,13 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c RuntimeList list = listBase.getList(); RuntimeScalar closure = (RuntimeScalar) registers[closureReg]; RuntimeList result = org.perlonjava.operators.ListOperators.grep(list, closure, ctx); - registers[rd] = result; + + // In scalar context, return the count of elements + if (ctx == RuntimeContextType.SCALAR) { + registers[rd] = new RuntimeScalar(result.elements.size()); + } else { + registers[rd] = result; + } break; } @@ -2362,6 +2368,36 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } + case Opcodes.GET_REPLACEMENT_REGEX: { + // Get replacement regex: rd = RuntimeRegex.getReplacementRegex(pattern, replacement, flags) + // Format: GET_REPLACEMENT_REGEX rd pattern_reg replacement_reg flags_reg + int rd = bytecode[pc++]; + int patternReg = bytecode[pc++]; + int replacementReg = bytecode[pc++]; + int flagsReg = bytecode[pc++]; + + RuntimeScalar pattern = (RuntimeScalar) registers[patternReg]; + RuntimeScalar replacement = (RuntimeScalar) registers[replacementReg]; + RuntimeScalar flags = (RuntimeScalar) registers[flagsReg]; + + registers[rd] = org.perlonjava.regex.RuntimeRegex.getReplacementRegex(pattern, replacement, flags); + break; + } + + case Opcodes.SUBSTR_VAR: { + // substr with variable args: rd = Operator.substr(ctx, args...) + // Format: SUBSTR_VAR rd argsListReg ctx + int rd = bytecode[pc++]; + int argsListReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + + RuntimeList argsList = (RuntimeList) registers[argsListReg]; + RuntimeBase[] substrArgs = argsList.elements.toArray(new RuntimeBase[0]); + + registers[rd] = org.perlonjava.operators.Operator.substr(ctx, substrArgs); + break; + } + // GENERATED_HANDLERS_END default: diff --git a/src/main/java/org/perlonjava/interpreter/CompileAssignment.java b/src/main/java/org/perlonjava/interpreter/CompileAssignment.java index be8bb543c..5905797f1 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileAssignment.java +++ b/src/main/java/org/perlonjava/interpreter/CompileAssignment.java @@ -909,6 +909,40 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(valueReg); bytecodeCompiler.lastResultReg = valueReg; + } else if (leftOp.operator.equals("@") && leftOp.operand instanceof OperatorNode) { + // Array dereference assignment: @$r = ... + // The operand should be a scalar variable containing an array reference + OperatorNode derefOp = (OperatorNode) leftOp.operand; + + if (derefOp.operator.equals("$")) { + // Compile the scalar to get the array reference + derefOp.accept(bytecodeCompiler); + int scalarRefReg = bytecodeCompiler.lastResultReg; + + // Dereference to get the actual array + int arrayReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); + bytecodeCompiler.emitReg(arrayReg); + bytecodeCompiler.emitReg(scalarRefReg); + + // Assign the value to the dereferenced array + bytecodeCompiler.emit(Opcodes.ARRAY_SET_FROM_LIST); + bytecodeCompiler.emitReg(arrayReg); + bytecodeCompiler.emitReg(valueReg); + + // In scalar context, return array size; in list context, return the array + if (savedContext == RuntimeContextType.SCALAR) { + int sizeReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); + bytecodeCompiler.emitReg(sizeReg); + bytecodeCompiler.emitReg(arrayReg); + bytecodeCompiler.lastResultReg = sizeReg; + } else { + bytecodeCompiler.lastResultReg = arrayReg; + } + } else { + bytecodeCompiler.throwCompilerException("Assignment to unsupported array dereference"); + } } else { bytecodeCompiler.throwCompilerException("Assignment to unsupported operator: " + leftOp.operator); } diff --git a/src/main/java/org/perlonjava/interpreter/CompileBinaryOperator.java b/src/main/java/org/perlonjava/interpreter/CompileBinaryOperator.java index dacfa4efe..13f936bfa 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileBinaryOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileBinaryOperator.java @@ -533,6 +533,51 @@ else if (node.right instanceof BinaryOperatorNode) { return; } + // Handle =~ and !~ binding with regex operators + // When we have: $string =~ s/pattern/replacement/flags + // The right side is: OperatorNode(replaceRegex, ListNode[pattern, replacement, flags]) + // We need to add $string to the operand list and compile the operator + if ((node.operator.equals("=~") || node.operator.equals("!~")) + && node.right instanceof OperatorNode) { + OperatorNode rightOp = (OperatorNode) node.right; + if (rightOp.operand instanceof ListNode + && !rightOp.operator.equals("quoteRegex")) { + // Check if it's a regex operator (replaceRegex, matchRegex, tr, transliterate) + if (rightOp.operator.equals("replaceRegex") + || rightOp.operator.equals("matchRegex") + || rightOp.operator.equals("tr") + || rightOp.operator.equals("transliterate")) { + + // Create a copy of the operand list and add the left side (string) + ListNode originalList = (ListNode) rightOp.operand; + ListNode boundList = new ListNode(new java.util.ArrayList<>(originalList.elements), originalList.tokenIndex); + boundList.elements.add(node.left); + + // Create a new OperatorNode with the modified operand list + OperatorNode boundOp = new OperatorNode(rightOp.operator, boundList, rightOp.tokenIndex); + + // For !~, we need to negate the result + if (node.operator.equals("!~")) { + // Compile the bound operator + boundOp.accept(bytecodeCompiler); + int matchReg = bytecodeCompiler.lastResultReg; + + // Negate the result + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.NOT); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(matchReg); + bytecodeCompiler.lastResultReg = rd; + } else { + // For =~, just compile the bound operator + boundOp.accept(bytecodeCompiler); + } + + return; + } + } + } + // Compile left and right operands (for non-short-circuit operators) node.left.accept(bytecodeCompiler); int rs1 = bytecodeCompiler.lastResultReg; diff --git a/src/main/java/org/perlonjava/interpreter/CompileBinaryOperatorHelper.java b/src/main/java/org/perlonjava/interpreter/CompileBinaryOperatorHelper.java index a146e6f5c..944f7095e 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileBinaryOperatorHelper.java +++ b/src/main/java/org/perlonjava/interpreter/CompileBinaryOperatorHelper.java @@ -255,7 +255,7 @@ public static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(rs2); // List register bytecodeCompiler.emitReg(rs1); // Closure register - bytecodeCompiler.emit(RuntimeContextType.LIST); // Grep uses list context + bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); // Use current context } case "sort" -> { // Sort operator: sort { block } list diff --git a/src/main/java/org/perlonjava/interpreter/CompileOperator.java b/src/main/java/org/perlonjava/interpreter/CompileOperator.java index 2823a26ab..2102994a5 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileOperator.java @@ -1756,6 +1756,103 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(patternReg); bytecodeCompiler.emitReg(flagsReg); + bytecodeCompiler.lastResultReg = rd; + } else if (op.equals("replaceRegex")) { + // s/pattern/replacement/flags - regex substitution + // operand: ListNode containing [pattern, replacement, flags] or [pattern, replacement, flags, string] + if (node.operand == null || !(node.operand instanceof ListNode)) { + bytecodeCompiler.throwCompilerException("replaceRegex requires pattern, replacement, and flags"); + } + + ListNode args = (ListNode) node.operand; + if (args.elements.size() < 3) { + bytecodeCompiler.throwCompilerException("replaceRegex requires pattern, replacement, and flags"); + } + + // Compile pattern + args.elements.get(0).accept(bytecodeCompiler); + int patternReg = bytecodeCompiler.lastResultReg; + + // Compile replacement + args.elements.get(1).accept(bytecodeCompiler); + int replacementReg = bytecodeCompiler.lastResultReg; + + // Compile flags + args.elements.get(2).accept(bytecodeCompiler); + int flagsReg = bytecodeCompiler.lastResultReg; + + // Create replacement regex using GET_REPLACEMENT_REGEX opcode + int regexReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.GET_REPLACEMENT_REGEX); + bytecodeCompiler.emitReg(regexReg); + bytecodeCompiler.emitReg(patternReg); + bytecodeCompiler.emitReg(replacementReg); + bytecodeCompiler.emitReg(flagsReg); + + // Get the string to operate on (element 3 if provided, else $_) + int stringReg; + if (args.elements.size() > 3) { + // String provided in operand list (from =~ binding) + args.elements.get(3).accept(bytecodeCompiler); + stringReg = bytecodeCompiler.lastResultReg; + } else { + // Use $_ as default + String varName = "$_"; + if (bytecodeCompiler.hasVariable(varName)) { + stringReg = bytecodeCompiler.getVariableRegister(varName); + } else { + stringReg = bytecodeCompiler.allocateRegister(); + int nameIdx = bytecodeCompiler.addToStringPool("main::_"); + bytecodeCompiler.emit(Opcodes.LOAD_GLOBAL_SCALAR); + bytecodeCompiler.emitReg(stringReg); + bytecodeCompiler.emit(nameIdx); + } + } + + // Apply the regex match (which handles replacement) + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.MATCH_REGEX); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(stringReg); + bytecodeCompiler.emitReg(regexReg); + bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); + + bytecodeCompiler.lastResultReg = rd; + } else if (op.equals("substr")) { + // substr($string, $offset, $length, $replacement) + // operand is a ListNode with 2-4 elements + if (node.operand == null || !(node.operand instanceof ListNode)) { + bytecodeCompiler.throwCompilerException("substr requires arguments"); + } + + ListNode args = (ListNode) node.operand; + if (args.elements.size() < 2) { + bytecodeCompiler.throwCompilerException("substr requires at least 2 arguments"); + } + + // Compile arguments + java.util.List argRegs = new java.util.ArrayList<>(); + for (Node arg : args.elements) { + arg.accept(bytecodeCompiler); + argRegs.add(bytecodeCompiler.lastResultReg); + } + + // Create list with arguments: CREATE_LIST rd count [regs...] + int argsListReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.CREATE_LIST); + bytecodeCompiler.emitReg(argsListReg); + bytecodeCompiler.emit(argRegs.size()); // emit count + for (int argReg : argRegs) { + bytecodeCompiler.emitReg(argReg); + } + + // Call SUBSTR_VAR opcode + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.SUBSTR_VAR); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(argsListReg); + bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); + bytecodeCompiler.lastResultReg = rd; } else if (op.equals("chomp")) { // chomp($x) or chomp - remove trailing newlines diff --git a/src/main/java/org/perlonjava/interpreter/InterpretedCode.java b/src/main/java/org/perlonjava/interpreter/InterpretedCode.java index 46b1d85ab..3f5eabada 100644 --- a/src/main/java/org/perlonjava/interpreter/InterpretedCode.java +++ b/src/main/java/org/perlonjava/interpreter/InterpretedCode.java @@ -725,6 +725,19 @@ public String disassemble() { int scalarReg = bytecode[pc++]; sb.append("CHOP r").append(rd).append(" = chop(r").append(scalarReg).append(")\n"); break; + case Opcodes.GET_REPLACEMENT_REGEX: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; // pattern + rs2 = bytecode[pc++]; // replacement + int rs3 = bytecode[pc++]; // flags + sb.append("GET_REPLACEMENT_REGEX r").append(rd).append(" = getReplacementRegex(r").append(rs1).append(", r").append(rs2).append(", r").append(rs3).append(")\n"); + break; + case Opcodes.SUBSTR_VAR: + rd = bytecode[pc++]; + int substrArgsReg = bytecode[pc++]; + int substrCtx = bytecode[pc++]; + sb.append("SUBSTR_VAR r").append(rd).append(" = substr(r").append(substrArgsReg).append(", ctx=").append(substrCtx).append(")\n"); + break; case Opcodes.PUSH_LOCAL_VARIABLE: rs = bytecode[pc++]; sb.append("PUSH_LOCAL_VARIABLE r").append(rs).append("\n"); diff --git a/src/main/java/org/perlonjava/interpreter/Opcodes.java b/src/main/java/org/perlonjava/interpreter/Opcodes.java index 6059af9d7..5d97a61d9 100644 --- a/src/main/java/org/perlonjava/interpreter/Opcodes.java +++ b/src/main/java/org/perlonjava/interpreter/Opcodes.java @@ -886,10 +886,18 @@ public class Opcodes { * Format: CHOP rd scalarReg */ public static final short CHOP = 235; + /** Get replacement regex: rd = RuntimeRegex.getReplacementRegex(pattern, replacement, flags) + * Format: GET_REPLACEMENT_REGEX rd pattern_reg replacement_reg flags_reg */ + public static final short GET_REPLACEMENT_REGEX = 236; + + /** substr with variable args: rd = Operator.substr(ctx, args...) + * Format: SUBSTR_VAR rd argsListReg ctx */ + public static final short SUBSTR_VAR = 237; + // ================================================================= // BUILT-IN FUNCTION OPCODES - after LASTOP // Last manually-assigned opcode (for tool reference) - private static final short LASTOP = 235; + private static final short LASTOP = 237; // ================================================================= // Generated by dev/tools/generate_opcode_handlers.pl From 1136003e2335c60cbd421efbdb2f6375ea84ac15 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Thu, 19 Feb 2026 22:55:16 +0100 Subject: [PATCH 09/10] refactor: Optimize BytecodeInterpreter.execute() to reduce JIT compilation overhead Extracted 84 cold-path opcodes from the main execute() switch statement to reduce method bytecode size from 12,066 bytes to 8,228 bytes (32% reduction). This keeps the method close to the 8KB JIT compilation threshold for optimal performance. Changes: - Created OpcodeHandlerExtended.java with 30+ handler methods for: * String/regex operations (SPRINTF, CHOP, MATCH_REGEX, etc.) * Assignment operators (17 compound assignment ops) * Bitwise operations (8 binary bitwise ops) * String utilities (CHOMP, INDEX, RINDEX, POS, etc.) * I/O operations (PRINT, SAY, OPEN, READLINE) * Increment/decrement operations * Closure and iterator operations - Created OpcodeHandlerFileTest.java with consolidated file test handler using secondary switch for all 27 FILETEST_* opcodes - Updated BytecodeInterpreter.execute() to delegate to new handlers Performance: Interpreter backend (0.68s) is 9% faster than JVM compiler (0.75s) on perf/benchmarks.t with 1960 tests. All tests passing at 100%. Co-Authored-By: Claude Opus 4.6 --- .../interpreter/BytecodeInterpreter.java | 933 ++++-------------- .../interpreter/OpcodeHandlerExtended.java | 921 +++++++++++++++++ .../interpreter/OpcodeHandlerFileTest.java | 63 ++ 3 files changed, 1184 insertions(+), 733 deletions(-) create mode 100644 src/main/java/org/perlonjava/interpreter/OpcodeHandlerExtended.java create mode 100644 src/main/java/org/perlonjava/interpreter/OpcodeHandlerFileTest.java diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java index 96d8977ec..c3e7ddffb 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java @@ -308,40 +308,11 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } - case Opcodes.CREATE_CLOSURE: { + case Opcodes.CREATE_CLOSURE: // Create closure with captured variables // Format: CREATE_CLOSURE rd template_idx num_captures reg1 reg2 ... - int rd = bytecode[pc++]; - int templateIdx = bytecode[pc++]; - int numCaptures = bytecode[pc++]; - - // Get the template InterpretedCode from constants - InterpretedCode template = (InterpretedCode) code.constants[templateIdx]; - - // Capture the current register values - RuntimeBase[] capturedVars = new RuntimeBase[numCaptures]; - for (int i = 0; i < numCaptures; i++) { - int captureReg = bytecode[pc++]; - capturedVars[i] = registers[captureReg]; - } - - // Create a new InterpretedCode with the captured variables - InterpretedCode closureCode = new InterpretedCode( - template.bytecode, - template.constants, - template.stringPool, - template.maxRegisters, - capturedVars, // The captured variables! - template.sourceName, - template.sourceLine, - template.pcToTokenIndex, - template.variableRegistry // Preserve variable registry - ); - - // Wrap in RuntimeScalar - registers[rd] = new RuntimeScalar((RuntimeCode) closureCode); + pc = OpcodeHandlerExtended.executeCreateClosure(bytecode, pc, registers, code); break; - } case Opcodes.SET_SCALAR: { // Set scalar value: registers[rd] = registers[rs] @@ -551,49 +522,23 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c // ITERATOR OPERATIONS - For efficient foreach loops // ================================================================= - case Opcodes.ITERATOR_CREATE: { + case Opcodes.ITERATOR_CREATE: // Create iterator: rd = rs.iterator() - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - - RuntimeBase iterable = registers[rs]; - java.util.Iterator iterator = iterable.iterator(); - - // Store iterator as a constant (we need to preserve the Iterator object) - // Wrap in RuntimeScalar for storage - registers[rd] = new RuntimeScalar(iterator); + // Format: ITERATOR_CREATE rd rs + pc = OpcodeHandlerExtended.executeIteratorCreate(bytecode, pc, registers); break; - } - case Opcodes.ITERATOR_HAS_NEXT: { + case Opcodes.ITERATOR_HAS_NEXT: // Check iterator: rd = iterator.hasNext() - int rd = bytecode[pc++]; - int iterReg = bytecode[pc++]; - - RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; - @SuppressWarnings("unchecked") - java.util.Iterator iterator = - (java.util.Iterator) iterScalar.value; - - boolean hasNext = iterator.hasNext(); - registers[rd] = hasNext ? RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; + // Format: ITERATOR_HAS_NEXT rd iterReg + pc = OpcodeHandlerExtended.executeIteratorHasNext(bytecode, pc, registers); break; - } - case Opcodes.ITERATOR_NEXT: { + case Opcodes.ITERATOR_NEXT: // Get next element: rd = iterator.next() - int rd = bytecode[pc++]; - int iterReg = bytecode[pc++]; - - RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; - @SuppressWarnings("unchecked") - java.util.Iterator iterator = - (java.util.Iterator) iterScalar.value; - - RuntimeScalar next = iterator.next(); - registers[rd] = next; + // Format: ITERATOR_NEXT rd iterReg + pc = OpcodeHandlerExtended.executeIteratorNext(bytecode, pc, registers); break; - } case Opcodes.FOREACH_NEXT_OR_EXIT: { // Superinstruction for foreach loops @@ -626,139 +571,65 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c // COMPOUND ASSIGNMENT OPERATORS (with overload support) // ================================================================= - case Opcodes.SUBTRACT_ASSIGN: { - // Compound assignment: rd = rd -= rs (checks for (-= overload first) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - - RuntimeBase val1 = registers[rd]; - RuntimeBase val2 = registers[rs]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - - registers[rd] = MathOperators.subtractAssign(s1, s2); + case Opcodes.SUBTRACT_ASSIGN: + // Compound assignment: rd -= rs + // Format: SUBTRACT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeSubtractAssign(bytecode, pc, registers); break; - } - case Opcodes.MULTIPLY_ASSIGN: { - // Compound assignment: rd = rd *= rs (checks for (*= overload first) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - - RuntimeBase val1 = registers[rd]; - RuntimeBase val2 = registers[rs]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - - registers[rd] = MathOperators.multiplyAssign(s1, s2); + case Opcodes.MULTIPLY_ASSIGN: + // Compound assignment: rd *= rs + // Format: MULTIPLY_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeMultiplyAssign(bytecode, pc, registers); break; - } - - case Opcodes.DIVIDE_ASSIGN: { - // Compound assignment: rd = rd /= rs (checks for (/= overload first) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - - RuntimeBase val1 = registers[rd]; - RuntimeBase val2 = registers[rs]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - registers[rd] = MathOperators.divideAssign(s1, s2); + case Opcodes.DIVIDE_ASSIGN: + // Compound assignment: rd /= rs + // Format: DIVIDE_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeDivideAssign(bytecode, pc, registers); break; - } - - case Opcodes.MODULUS_ASSIGN: { - // Compound assignment: rd = rd %= rs (checks for (%= overload first) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - - RuntimeBase val1 = registers[rd]; - RuntimeBase val2 = registers[rs]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - registers[rd] = MathOperators.modulusAssign(s1, s2); + case Opcodes.MODULUS_ASSIGN: + // Compound assignment: rd %= rs + // Format: MODULUS_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeModulusAssign(bytecode, pc, registers); break; - } - case Opcodes.REPEAT_ASSIGN: { + case Opcodes.REPEAT_ASSIGN: // Compound assignment: rd x= rs - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeBase result = Operator.repeat( - registers[rd], - (RuntimeScalar) registers[rs], - 1 // scalar context - ); - ((RuntimeScalar) registers[rd]).set((RuntimeScalar) result); + // Format: REPEAT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeRepeatAssign(bytecode, pc, registers); break; - } - case Opcodes.POW_ASSIGN: { + case Opcodes.POW_ASSIGN: // Compound assignment: rd **= rs - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeBase val1 = registers[rd]; - RuntimeBase val2 = registers[rs]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - RuntimeScalar result = MathOperators.pow(s1, s2); - ((RuntimeScalar) registers[rd]).set(result); + // Format: POW_ASSIGN rd rs + pc = OpcodeHandlerExtended.executePowAssign(bytecode, pc, registers); break; - } - case Opcodes.LEFT_SHIFT_ASSIGN: { + case Opcodes.LEFT_SHIFT_ASSIGN: // Compound assignment: rd <<= rs - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rd]; - RuntimeScalar s2 = (RuntimeScalar) registers[rs]; - RuntimeScalar result = BitwiseOperators.shiftLeft(s1, s2); - s1.set(result); + // Format: LEFT_SHIFT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeLeftShiftAssign(bytecode, pc, registers); break; - } - case Opcodes.RIGHT_SHIFT_ASSIGN: { + case Opcodes.RIGHT_SHIFT_ASSIGN: // Compound assignment: rd >>= rs - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rd]; - RuntimeScalar s2 = (RuntimeScalar) registers[rs]; - RuntimeScalar result = BitwiseOperators.shiftRight(s1, s2); - s1.set(result); + // Format: RIGHT_SHIFT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeRightShiftAssign(bytecode, pc, registers); break; - } - case Opcodes.LOGICAL_AND_ASSIGN: { - // Compound assignment: rd &&= rs (short-circuit: only evaluate rs if rd is true) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = ((RuntimeBase) registers[rd]).scalar(); - if (!s1.getBoolean()) { - // Left side is false, result is left side (no assignment needed) - break; - } - // Left side is true, assign right side - RuntimeScalar s2 = ((RuntimeBase) registers[rs]).scalar(); - ((RuntimeScalar) registers[rd]).set(s2); + case Opcodes.LOGICAL_AND_ASSIGN: + // Compound assignment: rd &&= rs (short-circuit) + // Format: LOGICAL_AND_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeLogicalAndAssign(bytecode, pc, registers); break; - } - case Opcodes.LOGICAL_OR_ASSIGN: { - // Compound assignment: rd ||= rs (short-circuit: only evaluate rs if rd is false) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = ((RuntimeBase) registers[rd]).scalar(); - if (s1.getBoolean()) { - // Left side is true, result is left side (no assignment needed) - break; - } - // Left side is false, assign right side - RuntimeScalar s2 = ((RuntimeBase) registers[rs]).scalar(); - ((RuntimeScalar) registers[rd]).set(s2); + case Opcodes.LOGICAL_OR_ASSIGN: + // Compound assignment: rd ||= rs (short-circuit) + // Format: LOGICAL_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeLogicalOrAssign(bytecode, pc, registers); break; - } // ================================================================= // SHIFT OPERATIONS @@ -1122,77 +993,17 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c // MISCELLANEOUS // ================================================================= - case Opcodes.PRINT: { + case Opcodes.PRINT: // Print to filehandle - // Format: [PRINT] [rs_content] [rs_filehandle] - int contentReg = bytecode[pc++]; - int filehandleReg = bytecode[pc++]; - - Object val = registers[contentReg]; - - // Filehandle should be scalar - convert if needed - RuntimeBase fhBase = registers[filehandleReg]; - RuntimeScalar fh = (fhBase instanceof RuntimeScalar) - ? (RuntimeScalar) fhBase - : fhBase.scalar(); - - RuntimeList list; - if (val instanceof RuntimeList) { - list = (RuntimeList) val; - } else if (val instanceof RuntimeArray) { - // Convert RuntimeArray to RuntimeList - list = new RuntimeList(); - for (RuntimeScalar elem : (RuntimeArray) val) { - list.add(elem); - } - } else if (val instanceof RuntimeScalar) { - // Convert scalar to single-element list - list = new RuntimeList(); - list.add((RuntimeScalar) val); - } else { - list = new RuntimeList(); - } - - // Call IOOperator.print() - org.perlonjava.operators.IOOperator.print(list, fh); + // Format: PRINT contentReg filehandleReg + pc = OpcodeHandlerExtended.executePrint(bytecode, pc, registers); break; - } - case Opcodes.SAY: { + case Opcodes.SAY: // Say to filehandle - // Format: [SAY] [rs_content] [rs_filehandle] - int contentReg = bytecode[pc++]; - int filehandleReg = bytecode[pc++]; - - Object val = registers[contentReg]; - - // Filehandle should be scalar - convert if needed - RuntimeBase fhBase = registers[filehandleReg]; - RuntimeScalar fh = (fhBase instanceof RuntimeScalar) - ? (RuntimeScalar) fhBase - : fhBase.scalar(); - - RuntimeList list; - if (val instanceof RuntimeList) { - list = (RuntimeList) val; - } else if (val instanceof RuntimeArray) { - // Convert RuntimeArray to RuntimeList - list = new RuntimeList(); - for (RuntimeScalar elem : (RuntimeArray) val) { - list.add(elem); - } - } else if (val instanceof RuntimeScalar) { - // Convert scalar to single-element list - list = new RuntimeList(); - list.add((RuntimeScalar) val); - } else { - list = new RuntimeList(); - } - - // Call IOOperator.say() - org.perlonjava.operators.IOOperator.say(list, fh); + // Format: SAY contentReg filehandleReg + pc = OpcodeHandlerExtended.executeSay(bytecode, pc, registers); break; - } // ================================================================= // SUPERINSTRUCTIONS - Eliminate MOVE overhead @@ -1233,387 +1044,135 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } - case Opcodes.STRING_CONCAT_ASSIGN: { - // String concatenation and assign: rd .= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = StringOperators.stringConcat( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.STRING_CONCAT_ASSIGN: + // String concatenation and assign: rd .= rs + // Format: STRING_CONCAT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeStringConcatAssign(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_AND_ASSIGN: { - // Bitwise AND assignment: rd &= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = org.perlonjava.operators.BitwiseOperators.bitwiseAndBinary( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.BITWISE_AND_ASSIGN: + // Bitwise AND assignment: rd &= rs + // Format: BITWISE_AND_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBitwiseAndAssign(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_OR_ASSIGN: { - // Bitwise OR assignment: rd |= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = org.perlonjava.operators.BitwiseOperators.bitwiseOrBinary( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.BITWISE_OR_ASSIGN: + // Bitwise OR assignment: rd |= rs + // Format: BITWISE_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBitwiseOrAssign(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_XOR_ASSIGN: { - // Bitwise XOR assignment: rd ^= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = org.perlonjava.operators.BitwiseOperators.bitwiseXorBinary( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.BITWISE_XOR_ASSIGN: + // Bitwise XOR assignment: rd ^= rs + // Format: BITWISE_XOR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBitwiseXorAssign(bytecode, pc, registers); break; - } - case Opcodes.STRING_BITWISE_AND_ASSIGN: { - // String bitwise AND assignment: rd &.= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = org.perlonjava.operators.BitwiseOperators.bitwiseAndDot( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.STRING_BITWISE_AND_ASSIGN: + // String bitwise AND assignment: rd &.= rs + // Format: STRING_BITWISE_AND_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeStringBitwiseAndAssign(bytecode, pc, registers); break; - } - case Opcodes.STRING_BITWISE_OR_ASSIGN: { - // String bitwise OR assignment: rd |.= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = org.perlonjava.operators.BitwiseOperators.bitwiseOrDot( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.STRING_BITWISE_OR_ASSIGN: + // String bitwise OR assignment: rd |.= rs + // Format: STRING_BITWISE_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeStringBitwiseOrAssign(bytecode, pc, registers); break; - } - case Opcodes.STRING_BITWISE_XOR_ASSIGN: { - // String bitwise XOR assignment: rd ^.= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = org.perlonjava.operators.BitwiseOperators.bitwiseXorDot( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.STRING_BITWISE_XOR_ASSIGN: + // String bitwise XOR assignment: rd ^.= rs + // Format: STRING_BITWISE_XOR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeStringBitwiseXorAssign(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_AND_BINARY: { + case Opcodes.BITWISE_AND_BINARY: // Numeric bitwise AND: rd = rs1 binary& rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseAndBinary( - (RuntimeScalar) registers[rs1], - (RuntimeScalar) registers[rs2] - ); + // Format: BITWISE_AND_BINARY rd rs1 rs2 + pc = OpcodeHandlerExtended.executeBitwiseAndBinary(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_OR_BINARY: { + case Opcodes.BITWISE_OR_BINARY: // Numeric bitwise OR: rd = rs1 binary| rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseOrBinary( - (RuntimeScalar) registers[rs1], - (RuntimeScalar) registers[rs2] - ); + // Format: BITWISE_OR_BINARY rd rs1 rs2 + pc = OpcodeHandlerExtended.executeBitwiseOrBinary(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_XOR_BINARY: { + case Opcodes.BITWISE_XOR_BINARY: // Numeric bitwise XOR: rd = rs1 binary^ rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseXorBinary( - (RuntimeScalar) registers[rs1], - (RuntimeScalar) registers[rs2] - ); + // Format: BITWISE_XOR_BINARY rd rs1 rs2 + pc = OpcodeHandlerExtended.executeBitwiseXorBinary(bytecode, pc, registers); break; - } - case Opcodes.STRING_BITWISE_AND: { + case Opcodes.STRING_BITWISE_AND: // String bitwise AND: rd = rs1 &. rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseAndDot( - (RuntimeScalar) registers[rs1], - (RuntimeScalar) registers[rs2] - ); + // Format: STRING_BITWISE_AND rd rs1 rs2 + pc = OpcodeHandlerExtended.executeStringBitwiseAnd(bytecode, pc, registers); break; - } - case Opcodes.STRING_BITWISE_OR: { + case Opcodes.STRING_BITWISE_OR: // String bitwise OR: rd = rs1 |. rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseOrDot( - (RuntimeScalar) registers[rs1], - (RuntimeScalar) registers[rs2] - ); + // Format: STRING_BITWISE_OR rd rs1 rs2 + pc = OpcodeHandlerExtended.executeStringBitwiseOr(bytecode, pc, registers); break; - } - case Opcodes.STRING_BITWISE_XOR: { + case Opcodes.STRING_BITWISE_XOR: // String bitwise XOR: rd = rs1 ^. rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseXorDot( - (RuntimeScalar) registers[rs1], - (RuntimeScalar) registers[rs2] - ); + // Format: STRING_BITWISE_XOR rd rs1 rs2 + pc = OpcodeHandlerExtended.executeStringBitwiseXor(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_NOT_BINARY: { + case Opcodes.BITWISE_NOT_BINARY: // Numeric bitwise NOT: rd = binary~ rs - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseNotBinary( - (RuntimeScalar) registers[rs] - ); + // Format: BITWISE_NOT_BINARY rd rs + pc = OpcodeHandlerExtended.executeBitwiseNotBinary(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_NOT_STRING: { + case Opcodes.BITWISE_NOT_STRING: // String bitwise NOT: rd = ~. rs - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseNotDot( - (RuntimeScalar) registers[rs] - ); + // Format: BITWISE_NOT_STRING rd rs + pc = OpcodeHandlerExtended.executeBitwiseNotString(bytecode, pc, registers); break; - } // File test and stat operations - case Opcodes.STAT: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - int ctx = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.Stat.stat((RuntimeScalar) registers[rs], ctx); - break; - } - - case Opcodes.LSTAT: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - int ctx = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.Stat.lstat((RuntimeScalar) registers[rs], ctx); - break; - } - - case Opcodes.FILETEST_R: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-r", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_W: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-w", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_X: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-x", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_O: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-o", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_R_REAL: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-R", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_W_REAL: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-W", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_X_REAL: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-X", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_O_REAL: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-O", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_E: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-e", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_Z: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-z", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_S: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-s", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_F: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-f", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_D: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-d", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_L: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-l", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_P: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-p", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_S_UPPER: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-S", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_B: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-b", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_C: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-c", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_T: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-t", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_U: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-u", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_G: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-g", (RuntimeScalar) registers[rs]); + case Opcodes.STAT: + pc = OpcodeHandlerExtended.executeStat(bytecode, pc, registers); + break; + + case Opcodes.LSTAT: + pc = OpcodeHandlerExtended.executeLstat(bytecode, pc, registers); + break; + + // File test operations (opcodes 190-216) - delegated to handler + case Opcodes.FILETEST_R: + case Opcodes.FILETEST_W: + case Opcodes.FILETEST_X: + case Opcodes.FILETEST_O: + case Opcodes.FILETEST_R_REAL: + case Opcodes.FILETEST_W_REAL: + case Opcodes.FILETEST_X_REAL: + case Opcodes.FILETEST_O_REAL: + case Opcodes.FILETEST_E: + case Opcodes.FILETEST_Z: + case Opcodes.FILETEST_S: + case Opcodes.FILETEST_F: + case Opcodes.FILETEST_D: + case Opcodes.FILETEST_L: + case Opcodes.FILETEST_P: + case Opcodes.FILETEST_S_UPPER: + case Opcodes.FILETEST_B: + case Opcodes.FILETEST_C: + case Opcodes.FILETEST_T: + case Opcodes.FILETEST_U: + case Opcodes.FILETEST_G: + case Opcodes.FILETEST_K: + case Opcodes.FILETEST_T_UPPER: + case Opcodes.FILETEST_B_UPPER: + case Opcodes.FILETEST_M: + case Opcodes.FILETEST_A: + case Opcodes.FILETEST_C_UPPER: + pc = OpcodeHandlerFileTest.executeFileTest(bytecode, pc, registers, opcode); break; - } - - case Opcodes.FILETEST_K: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-k", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_T_UPPER: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-T", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_B_UPPER: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-B", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_M: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-M", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_A: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-A", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_C_UPPER: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-C", (RuntimeScalar) registers[rs]); - break; - } case Opcodes.PUSH_LOCAL_VARIABLE: { // Push variable to local stack: DynamicVariableManager.pushLocalVariable(rs) @@ -1630,150 +1189,89 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } - case Opcodes.OPEN: { + case Opcodes.OPEN: // Open file: rd = IOOperator.open(ctx, args...) - int rd = bytecode[pc++]; - int ctx = bytecode[pc++]; - int argsReg = bytecode[pc++]; - RuntimeArray argsArray = (RuntimeArray) registers[argsReg]; - RuntimeBase[] argsVarargs = argsArray.elements.toArray(new RuntimeBase[0]); - registers[rd] = org.perlonjava.operators.IOOperator.open(ctx, argsVarargs); + // Format: OPEN rd ctx argsReg + pc = OpcodeHandlerExtended.executeOpen(bytecode, pc, registers); break; - } - case Opcodes.READLINE: { - // Read line from filehandle: rd = Readline.readline(fh_ref, ctx) - int rd = bytecode[pc++]; - int fhReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.Readline.readline( - (RuntimeScalar) registers[fhReg], ctx - ); + case Opcodes.READLINE: + // Read line from filehandle + // Format: READLINE rd fhReg ctx + pc = OpcodeHandlerExtended.executeReadline(bytecode, pc, registers); break; - } - case Opcodes.MATCH_REGEX: { - // Match regex: rd = RuntimeRegex.matchRegex(quotedRegex, string, ctx) - int rd = bytecode[pc++]; - int stringReg = bytecode[pc++]; - int regexReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - registers[rd] = org.perlonjava.regex.RuntimeRegex.matchRegex( - (RuntimeScalar) registers[regexReg], // quotedRegex first - (RuntimeScalar) registers[stringReg], // string second - ctx - ); + case Opcodes.MATCH_REGEX: + // Match regex + // Format: MATCH_REGEX rd stringReg regexReg ctx + pc = OpcodeHandlerExtended.executeMatchRegex(bytecode, pc, registers); break; - } - case Opcodes.MATCH_REGEX_NOT: { - // Negated regex match: rd = !RuntimeRegex.matchRegex(quotedRegex, string, ctx) - int rd = bytecode[pc++]; - int stringReg = bytecode[pc++]; - int regexReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - RuntimeBase matchResult = org.perlonjava.regex.RuntimeRegex.matchRegex( - (RuntimeScalar) registers[regexReg], // quotedRegex first - (RuntimeScalar) registers[stringReg], // string second - ctx - ); - // Negate the boolean result - registers[rd] = new RuntimeScalar(matchResult.scalar().getBoolean() ? 0 : 1); + case Opcodes.MATCH_REGEX_NOT: + // Negated regex match + // Format: MATCH_REGEX_NOT rd stringReg regexReg ctx + pc = OpcodeHandlerExtended.executeMatchRegexNot(bytecode, pc, registers); break; - } - case Opcodes.CHOMP: { + case Opcodes.CHOMP: // Chomp: rd = rs.chomp() - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = registers[rs].chomp(); + // Format: CHOMP rd rs + pc = OpcodeHandlerExtended.executeChomp(bytecode, pc, registers); break; - } - case Opcodes.WANTARRAY: { - // Get wantarray context: rd = Operator.wantarray(wantarrayReg) - int rd = bytecode[pc++]; - int wantarrayReg = bytecode[pc++]; - int ctx = ((RuntimeScalar) registers[wantarrayReg]).getInt(); - registers[rd] = org.perlonjava.operators.Operator.wantarray(ctx); + case Opcodes.WANTARRAY: + // Get wantarray context + // Format: WANTARRAY rd wantarrayReg + pc = OpcodeHandlerExtended.executeWantarray(bytecode, pc, registers); break; - } - case Opcodes.REQUIRE: { - // Require module or version: rd = ModuleOperators.require(rs) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.ModuleOperators.require((RuntimeScalar) registers[rs]); + case Opcodes.REQUIRE: + // Require module or version + // Format: REQUIRE rd rs + pc = OpcodeHandlerExtended.executeRequire(bytecode, pc, registers); break; - } - case Opcodes.POS: { - // Get regex position: rd = rs.pos() - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = ((RuntimeScalar) registers[rs]).pos(); + case Opcodes.POS: + // Get regex position + // Format: POS rd rs + pc = OpcodeHandlerExtended.executePos(bytecode, pc, registers); break; - } - case Opcodes.INDEX: { - // Find substring position: rd = StringOperators.index(str, substr, pos) - int rd = bytecode[pc++]; - int strReg = bytecode[pc++]; - int substrReg = bytecode[pc++]; - int posReg = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.StringOperators.index( - (RuntimeScalar) registers[strReg], - (RuntimeScalar) registers[substrReg], - (RuntimeScalar) registers[posReg] - ); + case Opcodes.INDEX: + // Find substring position + // Format: INDEX rd strReg substrReg posReg + pc = OpcodeHandlerExtended.executeIndex(bytecode, pc, registers); break; - } - case Opcodes.RINDEX: { - // Find substring position from end: rd = StringOperators.rindex(str, substr, pos) - int rd = bytecode[pc++]; - int strReg = bytecode[pc++]; - int substrReg = bytecode[pc++]; - int posReg = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.StringOperators.rindex( - (RuntimeScalar) registers[strReg], - (RuntimeScalar) registers[substrReg], - (RuntimeScalar) registers[posReg] - ); + case Opcodes.RINDEX: + // Find substring position from end + // Format: RINDEX rd strReg substrReg posReg + pc = OpcodeHandlerExtended.executeRindex(bytecode, pc, registers); break; - } - case Opcodes.PRE_AUTOINCREMENT: { + case Opcodes.PRE_AUTOINCREMENT: // Pre-increment: ++rd - int rd = bytecode[pc++]; - ((RuntimeScalar) registers[rd]).preAutoIncrement(); + // Format: PRE_AUTOINCREMENT rd + pc = OpcodeHandlerExtended.executePreAutoIncrement(bytecode, pc, registers); break; - } - case Opcodes.POST_AUTOINCREMENT: { + case Opcodes.POST_AUTOINCREMENT: // Post-increment: rd = rs++ - // The postAutoIncrement() method increments the variable and returns the OLD value - int rd = bytecode[pc++]; // Destination register for old value - int rs = bytecode[pc++]; // Source variable register - registers[rd] = ((RuntimeScalar) registers[rs]).postAutoIncrement(); + // Format: POST_AUTOINCREMENT rd rs + pc = OpcodeHandlerExtended.executePostAutoIncrement(bytecode, pc, registers); break; - } - case Opcodes.PRE_AUTODECREMENT: { + case Opcodes.PRE_AUTODECREMENT: // Pre-decrement: --rd - int rd = bytecode[pc++]; - ((RuntimeScalar) registers[rd]).preAutoDecrement(); + // Format: PRE_AUTODECREMENT rd + pc = OpcodeHandlerExtended.executePreAutoDecrement(bytecode, pc, registers); break; - } - case Opcodes.POST_AUTODECREMENT: { + case Opcodes.POST_AUTODECREMENT: // Post-decrement: rd = rs-- - // The postAutoDecrement() method decrements the variable and returns the OLD value - int rd = bytecode[pc++]; // Destination register for old value - int rs = bytecode[pc++]; // Source variable register - registers[rd] = ((RuntimeScalar) registers[rs]).postAutoDecrement(); + // Format: POST_AUTODECREMENT rd rs + pc = OpcodeHandlerExtended.executePostAutoDecrement(bytecode, pc, registers); break; - } // ================================================================= // ERROR HANDLING @@ -2343,60 +1841,29 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } - case Opcodes.SPRINTF: { + case Opcodes.SPRINTF: // sprintf($format, @args): rd = SprintfOperator.sprintf(formatReg, argsListReg) // Format: SPRINTF rd formatReg argsListReg - int rd = bytecode[pc++]; - int formatReg = bytecode[pc++]; - int argsListReg = bytecode[pc++]; - - RuntimeScalar format = (RuntimeScalar) registers[formatReg]; - RuntimeList argsList = (RuntimeList) registers[argsListReg]; - - registers[rd] = org.perlonjava.operators.SprintfOperator.sprintf(format, argsList); + pc = OpcodeHandlerExtended.executeSprintf(bytecode, pc, registers); break; - } - case Opcodes.CHOP: { + case Opcodes.CHOP: // chop($x): rd = StringOperators.chopScalar(scalarReg) // Format: CHOP rd scalarReg - int rd = bytecode[pc++]; - int scalarReg = bytecode[pc++]; - - RuntimeScalar scalar = (RuntimeScalar) registers[scalarReg]; - registers[rd] = org.perlonjava.operators.StringOperators.chopScalar(scalar); + pc = OpcodeHandlerExtended.executeChop(bytecode, pc, registers); break; - } - case Opcodes.GET_REPLACEMENT_REGEX: { + case Opcodes.GET_REPLACEMENT_REGEX: // Get replacement regex: rd = RuntimeRegex.getReplacementRegex(pattern, replacement, flags) // Format: GET_REPLACEMENT_REGEX rd pattern_reg replacement_reg flags_reg - int rd = bytecode[pc++]; - int patternReg = bytecode[pc++]; - int replacementReg = bytecode[pc++]; - int flagsReg = bytecode[pc++]; - - RuntimeScalar pattern = (RuntimeScalar) registers[patternReg]; - RuntimeScalar replacement = (RuntimeScalar) registers[replacementReg]; - RuntimeScalar flags = (RuntimeScalar) registers[flagsReg]; - - registers[rd] = org.perlonjava.regex.RuntimeRegex.getReplacementRegex(pattern, replacement, flags); + pc = OpcodeHandlerExtended.executeGetReplacementRegex(bytecode, pc, registers); break; - } - case Opcodes.SUBSTR_VAR: { + case Opcodes.SUBSTR_VAR: // substr with variable args: rd = Operator.substr(ctx, args...) // Format: SUBSTR_VAR rd argsListReg ctx - int rd = bytecode[pc++]; - int argsListReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - - RuntimeList argsList = (RuntimeList) registers[argsListReg]; - RuntimeBase[] substrArgs = argsList.elements.toArray(new RuntimeBase[0]); - - registers[rd] = org.perlonjava.operators.Operator.substr(ctx, substrArgs); + pc = OpcodeHandlerExtended.executeSubstrVar(bytecode, pc, registers); break; - } // GENERATED_HANDLERS_END diff --git a/src/main/java/org/perlonjava/interpreter/OpcodeHandlerExtended.java b/src/main/java/org/perlonjava/interpreter/OpcodeHandlerExtended.java new file mode 100644 index 000000000..72ac301d6 --- /dev/null +++ b/src/main/java/org/perlonjava/interpreter/OpcodeHandlerExtended.java @@ -0,0 +1,921 @@ +package org.perlonjava.interpreter; + +import org.perlonjava.operators.*; +import org.perlonjava.regex.RuntimeRegex; +import org.perlonjava.runtime.*; + +/** + * Extended opcode handlers for recently added operations. + * + * Extracted from BytecodeInterpreter.execute() to reduce method size + * and keep it under the 8KB JIT compilation limit. + * + * Handles: SPRINTF, CHOP, GET_REPLACEMENT_REGEX, SUBSTR_VAR, and other + * less-frequently-used string/regex operations. + */ +public class OpcodeHandlerExtended { + + /** + * Execute sprintf operation. + * Format: SPRINTF rd formatReg argsListReg + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeSprintf(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int formatReg = bytecode[pc++]; + int argsListReg = bytecode[pc++]; + + RuntimeScalar format = (RuntimeScalar) registers[formatReg]; + RuntimeList argsList = (RuntimeList) registers[argsListReg]; + + registers[rd] = SprintfOperator.sprintf(format, argsList); + return pc; + } + + /** + * Execute chop operation. + * Format: CHOP rd scalarReg + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeChop(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int scalarReg = bytecode[pc++]; + + RuntimeScalar scalar = (RuntimeScalar) registers[scalarReg]; + registers[rd] = StringOperators.chopScalar(scalar); + return pc; + } + + /** + * Execute get replacement regex operation. + * Format: GET_REPLACEMENT_REGEX rd pattern_reg replacement_reg flags_reg + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeGetReplacementRegex(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int patternReg = bytecode[pc++]; + int replacementReg = bytecode[pc++]; + int flagsReg = bytecode[pc++]; + + RuntimeScalar pattern = (RuntimeScalar) registers[patternReg]; + RuntimeScalar replacement = (RuntimeScalar) registers[replacementReg]; + RuntimeScalar flags = (RuntimeScalar) registers[flagsReg]; + + registers[rd] = RuntimeRegex.getReplacementRegex(pattern, replacement, flags); + return pc; + } + + /** + * Execute substr with variable arguments. + * Format: SUBSTR_VAR rd argsListReg ctx + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeSubstrVar(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int argsListReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + + RuntimeList argsList = (RuntimeList) registers[argsListReg]; + RuntimeBase[] substrArgs = argsList.elements.toArray(new RuntimeBase[0]); + + registers[rd] = Operator.substr(ctx, substrArgs); + return pc; + } + + /** + * Execute repeat assign operation. + * Format: REPEAT_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeRepeatAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeBase result = Operator.repeat( + registers[rd], + (RuntimeScalar) registers[rs], + 1 // scalar context + ); + ((RuntimeScalar) registers[rd]).set((RuntimeScalar) result); + return pc; + } + + /** + * Execute power assign operation. + * Format: POW_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executePowAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeBase val1 = registers[rd]; + RuntimeBase val2 = registers[rs]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + RuntimeScalar result = MathOperators.pow(s1, s2); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute left shift assign operation. + * Format: LEFT_SHIFT_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeLeftShiftAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar s1 = (RuntimeScalar) registers[rd]; + RuntimeScalar s2 = (RuntimeScalar) registers[rs]; + RuntimeScalar result = BitwiseOperators.shiftLeft(s1, s2); + s1.set(result); + return pc; + } + + /** + * Execute right shift assign operation. + * Format: RIGHT_SHIFT_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeRightShiftAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar s1 = (RuntimeScalar) registers[rd]; + RuntimeScalar s2 = (RuntimeScalar) registers[rs]; + RuntimeScalar result = BitwiseOperators.shiftRight(s1, s2); + s1.set(result); + return pc; + } + + /** + * Execute logical AND assign operation. + * Format: LOGICAL_AND_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeLogicalAndAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar s1 = ((RuntimeBase) registers[rd]).scalar(); + if (!s1.getBoolean()) { + // Left side is false, result is left side (no assignment needed) + return pc; + } + // Left side is true, assign right side + RuntimeScalar s2 = ((RuntimeBase) registers[rs]).scalar(); + ((RuntimeScalar) registers[rd]).set(s2); + return pc; + } + + /** + * Execute logical OR assign operation. + * Format: LOGICAL_OR_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeLogicalOrAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar s1 = ((RuntimeBase) registers[rd]).scalar(); + if (s1.getBoolean()) { + // Left side is true, result is left side (no assignment needed) + return pc; + } + // Left side is false, assign right side + RuntimeScalar s2 = ((RuntimeBase) registers[rs]).scalar(); + ((RuntimeScalar) registers[rd]).set(s2); + return pc; + } + + /** + * Execute string concatenation assign operation. + * Format: STRING_CONCAT_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeStringConcatAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = StringOperators.stringConcat( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute bitwise AND assign operation. + * Format: BITWISE_AND_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeBitwiseAndAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = BitwiseOperators.bitwiseAndBinary( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute bitwise OR assign operation. + * Format: BITWISE_OR_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeBitwiseOrAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = BitwiseOperators.bitwiseOrBinary( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute bitwise XOR assign operation. + * Format: BITWISE_XOR_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeBitwiseXorAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = BitwiseOperators.bitwiseXorBinary( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute string bitwise AND assign operation. + * Format: STRING_BITWISE_AND_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeStringBitwiseAndAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = BitwiseOperators.bitwiseAndDot( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute string bitwise OR assign operation. + * Format: STRING_BITWISE_OR_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeStringBitwiseOrAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = BitwiseOperators.bitwiseOrDot( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute string bitwise XOR assign operation. + * Format: STRING_BITWISE_XOR_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeStringBitwiseXorAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = BitwiseOperators.bitwiseXorDot( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + // Bitwise binary operators (non-assignment) + + /** + * Execute bitwise AND binary operation. + * Format: BITWISE_AND_BINARY rd rs1 rs2 + */ + public static int executeBitwiseAndBinary(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseAndBinary( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + return pc; + } + + /** + * Execute bitwise OR binary operation. + * Format: BITWISE_OR_BINARY rd rs1 rs2 + */ + public static int executeBitwiseOrBinary(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseOrBinary( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + return pc; + } + + /** + * Execute bitwise XOR binary operation. + * Format: BITWISE_XOR_BINARY rd rs1 rs2 + */ + public static int executeBitwiseXorBinary(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseXorBinary( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + return pc; + } + + /** + * Execute string bitwise AND operation. + * Format: STRING_BITWISE_AND rd rs1 rs2 + */ + public static int executeStringBitwiseAnd(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseAndDot( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + return pc; + } + + /** + * Execute string bitwise OR operation. + * Format: STRING_BITWISE_OR rd rs1 rs2 + */ + public static int executeStringBitwiseOr(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseOrDot( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + return pc; + } + + /** + * Execute string bitwise XOR operation. + * Format: STRING_BITWISE_XOR rd rs1 rs2 + */ + public static int executeStringBitwiseXor(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseXorDot( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + return pc; + } + + /** + * Execute bitwise NOT binary operation. + * Format: BITWISE_NOT_BINARY rd rs + */ + public static int executeBitwiseNotBinary(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseNotBinary((RuntimeScalar) registers[rs]); + return pc; + } + + /** + * Execute bitwise NOT string operation. + * Format: BITWISE_NOT_STRING rd rs + */ + public static int executeBitwiseNotString(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseNotDot((RuntimeScalar) registers[rs]); + return pc; + } + + /** + * Execute stat operation. + * Format: STAT rd rs ctx + */ + public static int executeStat(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + int ctx = bytecode[pc++]; + registers[rd] = Stat.stat((RuntimeScalar) registers[rs], ctx); + return pc; + } + + /** + * Execute lstat operation. + * Format: LSTAT rd rs ctx + */ + public static int executeLstat(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + int ctx = bytecode[pc++]; + registers[rd] = Stat.lstat((RuntimeScalar) registers[rs], ctx); + return pc; + } + + /** + * Execute print operation. + * Format: PRINT contentReg filehandleReg + */ + public static int executePrint(short[] bytecode, int pc, RuntimeBase[] registers) { + int contentReg = bytecode[pc++]; + int filehandleReg = bytecode[pc++]; + + Object val = registers[contentReg]; + + // Filehandle should be scalar - convert if needed + RuntimeBase fhBase = registers[filehandleReg]; + RuntimeScalar fh = (fhBase instanceof RuntimeScalar) + ? (RuntimeScalar) fhBase + : fhBase.scalar(); + + RuntimeList list; + if (val instanceof RuntimeList) { + list = (RuntimeList) val; + } else if (val instanceof RuntimeArray) { + // Convert RuntimeArray to RuntimeList + list = new RuntimeList(); + for (RuntimeScalar elem : (RuntimeArray) val) { + list.add(elem); + } + } else if (val instanceof RuntimeScalar) { + // Convert scalar to single-element list + list = new RuntimeList(); + list.add((RuntimeScalar) val); + } else { + list = new RuntimeList(); + } + + // Call IOOperator.print() + IOOperator.print(list, fh); + return pc; + } + + /** + * Execute say operation. + * Format: SAY contentReg filehandleReg + */ + public static int executeSay(short[] bytecode, int pc, RuntimeBase[] registers) { + int contentReg = bytecode[pc++]; + int filehandleReg = bytecode[pc++]; + + Object val = registers[contentReg]; + + // Filehandle should be scalar - convert if needed + RuntimeBase fhBase = registers[filehandleReg]; + RuntimeScalar fh = (fhBase instanceof RuntimeScalar) + ? (RuntimeScalar) fhBase + : fhBase.scalar(); + + RuntimeList list; + if (val instanceof RuntimeList) { + list = (RuntimeList) val; + } else if (val instanceof RuntimeArray) { + // Convert RuntimeArray to RuntimeList + list = new RuntimeList(); + for (RuntimeScalar elem : (RuntimeArray) val) { + list.add(elem); + } + } else if (val instanceof RuntimeScalar) { + // Convert scalar to single-element list + list = new RuntimeList(); + list.add((RuntimeScalar) val); + } else { + list = new RuntimeList(); + } + + // Call IOOperator.say() + IOOperator.say(list, fh); + return pc; + } + + /** + * Execute chomp operation. + * Format: CHOMP rd rs + */ + public static int executeChomp(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = registers[rs].chomp(); + return pc; + } + + /** + * Execute wantarray operation. + * Format: WANTARRAY rd wantarrayReg + */ + public static int executeWantarray(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int wantarrayReg = bytecode[pc++]; + int ctx = ((RuntimeScalar) registers[wantarrayReg]).getInt(); + registers[rd] = Operator.wantarray(ctx); + return pc; + } + + /** + * Execute require operation. + * Format: REQUIRE rd rs + */ + public static int executeRequire(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = ModuleOperators.require((RuntimeScalar) registers[rs]); + return pc; + } + + /** + * Execute pos operation. + * Format: POS rd rs + */ + public static int executePos(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = ((RuntimeScalar) registers[rs]).pos(); + return pc; + } + + /** + * Execute index operation. + * Format: INDEX rd strReg substrReg posReg + */ + public static int executeIndex(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int strReg = bytecode[pc++]; + int substrReg = bytecode[pc++]; + int posReg = bytecode[pc++]; + registers[rd] = StringOperators.index( + (RuntimeScalar) registers[strReg], + (RuntimeScalar) registers[substrReg], + (RuntimeScalar) registers[posReg] + ); + return pc; + } + + /** + * Execute rindex operation. + * Format: RINDEX rd strReg substrReg posReg + */ + public static int executeRindex(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int strReg = bytecode[pc++]; + int substrReg = bytecode[pc++]; + int posReg = bytecode[pc++]; + registers[rd] = StringOperators.rindex( + (RuntimeScalar) registers[strReg], + (RuntimeScalar) registers[substrReg], + (RuntimeScalar) registers[posReg] + ); + return pc; + } + + /** + * Execute pre-increment operation. + * Format: PRE_AUTOINCREMENT rd + */ + public static int executePreAutoIncrement(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + ((RuntimeScalar) registers[rd]).preAutoIncrement(); + return pc; + } + + /** + * Execute post-increment operation. + * Format: POST_AUTOINCREMENT rd rs + */ + public static int executePostAutoIncrement(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = ((RuntimeScalar) registers[rs]).postAutoIncrement(); + return pc; + } + + /** + * Execute pre-decrement operation. + * Format: PRE_AUTODECREMENT rd + */ + public static int executePreAutoDecrement(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + ((RuntimeScalar) registers[rd]).preAutoDecrement(); + return pc; + } + + /** + * Execute post-decrement operation. + * Format: POST_AUTODECREMENT rd rs + */ + public static int executePostAutoDecrement(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = ((RuntimeScalar) registers[rs]).postAutoDecrement(); + return pc; + } + + /** + * Execute open operation. + * Format: OPEN rd ctx argsReg + */ + public static int executeOpen(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int ctx = bytecode[pc++]; + int argsReg = bytecode[pc++]; + RuntimeArray argsArray = (RuntimeArray) registers[argsReg]; + RuntimeBase[] argsVarargs = argsArray.elements.toArray(new RuntimeBase[0]); + registers[rd] = IOOperator.open(ctx, argsVarargs); + return pc; + } + + /** + * Execute readline operation. + * Format: READLINE rd fhReg ctx + */ + public static int executeReadline(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int fhReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + registers[rd] = Readline.readline((RuntimeScalar) registers[fhReg], ctx); + return pc; + } + + /** + * Execute match regex operation. + * Format: MATCH_REGEX rd stringReg regexReg ctx + */ + public static int executeMatchRegex(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int stringReg = bytecode[pc++]; + int regexReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + registers[rd] = RuntimeRegex.matchRegex( + (RuntimeScalar) registers[regexReg], // quotedRegex first + (RuntimeScalar) registers[stringReg], // string second + ctx + ); + return pc; + } + + /** + * Execute negated match regex operation. + * Format: MATCH_REGEX_NOT rd stringReg regexReg ctx + */ + public static int executeMatchRegexNot(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int stringReg = bytecode[pc++]; + int regexReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + RuntimeBase matchResult = RuntimeRegex.matchRegex( + (RuntimeScalar) registers[regexReg], // quotedRegex first + (RuntimeScalar) registers[stringReg], // string second + ctx + ); + // Negate the boolean result + registers[rd] = new RuntimeScalar(matchResult.scalar().getBoolean() ? 0 : 1); + return pc; + } + + /** + * Execute create closure operation. + * Format: CREATE_CLOSURE rd template_idx num_captures reg1 reg2 ... + */ + public static int executeCreateClosure(short[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { + int rd = bytecode[pc++]; + int templateIdx = bytecode[pc++]; + int numCaptures = bytecode[pc++]; + + // Get the template InterpretedCode from constants + InterpretedCode template = (InterpretedCode) code.constants[templateIdx]; + + // Capture the current register values + RuntimeBase[] capturedVars = new RuntimeBase[numCaptures]; + for (int i = 0; i < numCaptures; i++) { + int captureReg = bytecode[pc++]; + capturedVars[i] = registers[captureReg]; + } + + // Create a new InterpretedCode with the captured variables + InterpretedCode closureCode = new InterpretedCode( + template.bytecode, + template.constants, + template.stringPool, + template.maxRegisters, + capturedVars, // The captured variables! + template.sourceName, + template.sourceLine, + template.pcToTokenIndex, + template.variableRegistry // Preserve variable registry + ); + + // Wrap in RuntimeScalar + registers[rd] = new RuntimeScalar((RuntimeCode) closureCode); + return pc; + } + + /** + * Execute iterator create operation. + * Format: ITERATOR_CREATE rd rs + */ + public static int executeIteratorCreate(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + + RuntimeBase iterable = registers[rs]; + java.util.Iterator iterator = iterable.iterator(); + + // Store iterator as a constant (preserve the Iterator object) + // Wrap in RuntimeScalar for storage + registers[rd] = new RuntimeScalar(iterator); + return pc; + } + + /** + * Execute iterator has next operation. + * Format: ITERATOR_HAS_NEXT rd iterReg + */ + public static int executeIteratorHasNext(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int iterReg = bytecode[pc++]; + + RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; + @SuppressWarnings("unchecked") + java.util.Iterator iterator = + (java.util.Iterator) iterScalar.value; + + boolean hasNext = iterator.hasNext(); + registers[rd] = hasNext ? RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; + return pc; + } + + /** + * Execute iterator next operation. + * Format: ITERATOR_NEXT rd iterReg + */ + public static int executeIteratorNext(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int iterReg = bytecode[pc++]; + + RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; + @SuppressWarnings("unchecked") + java.util.Iterator iterator = + (java.util.Iterator) iterScalar.value; + + RuntimeScalar next = iterator.next(); + registers[rd] = next; + return pc; + } + + /** + * Execute subtract assign operation. + * Format: SUBTRACT_ASSIGN rd rs + */ + public static int executeSubtractAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + + RuntimeBase val1 = registers[rd]; + RuntimeBase val2 = registers[rs]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.subtractAssign(s1, s2); + return pc; + } + + /** + * Execute multiply assign operation. + * Format: MULTIPLY_ASSIGN rd rs + */ + public static int executeMultiplyAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + + RuntimeBase val1 = registers[rd]; + RuntimeBase val2 = registers[rs]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.multiplyAssign(s1, s2); + return pc; + } + + /** + * Execute divide assign operation. + * Format: DIVIDE_ASSIGN rd rs + */ + public static int executeDivideAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + + RuntimeBase val1 = registers[rd]; + RuntimeBase val2 = registers[rs]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.divideAssign(s1, s2); + return pc; + } + + /** + * Execute modulus assign operation. + * Format: MODULUS_ASSIGN rd rs + */ + public static int executeModulusAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + + RuntimeBase val1 = registers[rd]; + RuntimeBase val2 = registers[rs]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.modulusAssign(s1, s2); + return pc; + } +} diff --git a/src/main/java/org/perlonjava/interpreter/OpcodeHandlerFileTest.java b/src/main/java/org/perlonjava/interpreter/OpcodeHandlerFileTest.java new file mode 100644 index 000000000..9774cd194 --- /dev/null +++ b/src/main/java/org/perlonjava/interpreter/OpcodeHandlerFileTest.java @@ -0,0 +1,63 @@ +package org.perlonjava.interpreter; + +import org.perlonjava.operators.FileTestOperator; +import org.perlonjava.runtime.*; + +/** + * File test opcode handlers. + * + * Extracted from BytecodeInterpreter.execute() to reduce method size. + * Handles all file test operations (opcodes 190-216). + */ +public class OpcodeHandlerFileTest { + + /** + * Execute file test operation based on opcode. + * Format: FILETEST_* rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @param opcode The file test opcode (190-216) + * @return Updated program counter + */ + public static int executeFileTest(short[] bytecode, int pc, RuntimeBase[] registers, short opcode) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + + // Map opcode to test flag + String testFlag = switch (opcode) { + case Opcodes.FILETEST_R -> "-r"; + case Opcodes.FILETEST_W -> "-w"; + case Opcodes.FILETEST_X -> "-x"; + case Opcodes.FILETEST_O -> "-o"; + case Opcodes.FILETEST_R_REAL -> "-R"; + case Opcodes.FILETEST_W_REAL -> "-W"; + case Opcodes.FILETEST_X_REAL -> "-X"; + case Opcodes.FILETEST_O_REAL -> "-O"; + case Opcodes.FILETEST_E -> "-e"; + case Opcodes.FILETEST_Z -> "-z"; + case Opcodes.FILETEST_S -> "-s"; + case Opcodes.FILETEST_F -> "-f"; + case Opcodes.FILETEST_D -> "-d"; + case Opcodes.FILETEST_L -> "-l"; + case Opcodes.FILETEST_P -> "-p"; + case Opcodes.FILETEST_S_UPPER -> "-S"; + case Opcodes.FILETEST_B -> "-b"; + case Opcodes.FILETEST_C -> "-c"; + case Opcodes.FILETEST_T -> "-t"; + case Opcodes.FILETEST_U -> "-u"; + case Opcodes.FILETEST_G -> "-g"; + case Opcodes.FILETEST_K -> "-k"; + case Opcodes.FILETEST_T_UPPER -> "-T"; + case Opcodes.FILETEST_B_UPPER -> "-B"; + case Opcodes.FILETEST_M -> "-M"; + case Opcodes.FILETEST_A -> "-A"; + case Opcodes.FILETEST_C_UPPER -> "-C"; + default -> throw new RuntimeException("Unknown file test opcode: " + opcode); + }; + + registers[rd] = FileTestOperator.fileTest(testFlag, (RuntimeScalar) registers[rs]); + return pc; + } +} From c7920c178fca1cc612f227aff607ae39c1884ed6 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Thu, 19 Feb 2026 23:15:10 +0100 Subject: [PATCH 10/10] fix: Correct matchRegex to perform match when string is bound via =~ When $string =~ m/pattern/ is compiled, the =~ binding adds the string to the matchRegex operand list. However, matchRegex was only creating the regex object and returning it, instead of checking if a string was provided and performing the match. This caused eval'd regex matches with the interpreter to return the regex object (?^:pattern) instead of the boolean match result. Changes: - Updated matchRegex compilation to check if element 3 (string) exists - If string is provided, emit MATCH_REGEX opcode to perform the match - If no string, return the regex object (for cases like: $r = qr/pattern/) Test results: - re/regexp.t with JPERL_EVAL_USE_INTERPRETER: 78.6% (was 17.9%) - perf/benchmarks.t with JPERL_EVAL_USE_INTERPRETER: 100% (maintained) Co-Authored-By: Claude Opus 4.6 --- .../interpreter/CompileOperator.java | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/perlonjava/interpreter/CompileOperator.java b/src/main/java/org/perlonjava/interpreter/CompileOperator.java index 2102994a5..e946740bb 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileOperator.java @@ -1730,8 +1730,8 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.lastResultReg = rd; } else if (op.equals("matchRegex")) { - // m/pattern/flags - create a regex and return it (for use with =~) - // operand: ListNode containing pattern string and flags string + // m/pattern/flags - create a regex and optionally match against a string + // operand: ListNode containing pattern, flags, and optionally string (from =~ binding) if (node.operand == null || !(node.operand instanceof ListNode)) { bytecodeCompiler.throwCompilerException("matchRegex requires pattern and flags"); } @@ -1750,13 +1750,31 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int flagsReg = bytecodeCompiler.lastResultReg; // Create quoted regex using QUOTE_REGEX opcode - int rd = bytecodeCompiler.allocateRegister(); + int regexReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.QUOTE_REGEX); - bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(regexReg); bytecodeCompiler.emitReg(patternReg); bytecodeCompiler.emitReg(flagsReg); - bytecodeCompiler.lastResultReg = rd; + // Check if a string was provided (from =~ binding) + if (args.elements.size() > 2) { + // String provided - perform the match + args.elements.get(2).accept(bytecodeCompiler); + int stringReg = bytecodeCompiler.lastResultReg; + + // Call MATCH_REGEX to perform the match + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.MATCH_REGEX); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(stringReg); + bytecodeCompiler.emitReg(regexReg); + bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); + + bytecodeCompiler.lastResultReg = rd; + } else { + // No string provided - just return the regex object + bytecodeCompiler.lastResultReg = regexReg; + } } else if (op.equals("replaceRegex")) { // s/pattern/replacement/flags - regex substitution // operand: ListNode containing [pattern, replacement, flags] or [pattern, replacement, flags, string]