From 2dfdbf17666a2e9e93f70df5fbb0c24251d414cd Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Fri, 20 Feb 2026 14:37:17 +0100 Subject: [PATCH 1/5] feat: Add compiler support for getppid operator Add compilation support for the getppid() operator in the bytecode interpreter. The opcode (135) and execution handler were already implemented in SlowOpcodeHandler.executeGetppid(), this change adds the compiler-side code generation in CompileOperator.java. Format: GETPPID rd Effect: rd = parent process ID Note: Test 143 in op/lex_assign.t still fails due to an unrelated issue with tied variable assignments not calling STORE in the interpreter. Co-Authored-By: Claude Opus 4.6 --- .../java/org/perlonjava/interpreter/CompileOperator.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/org/perlonjava/interpreter/CompileOperator.java b/src/main/java/org/perlonjava/interpreter/CompileOperator.java index 9371e7d8f..0967fdf24 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileOperator.java @@ -2620,6 +2620,13 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else { bytecodeCompiler.throwCompilerException(op + " requires arguments"); } + } else if (op.equals("getppid")) { + // getppid() - returns parent process ID + // Format: GETPPID rd + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emitWithToken(Opcodes.GETPPID, node.getIndex()); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.lastResultReg = rd; } else { bytecodeCompiler.throwCompilerException("Unsupported operator: " + op); } From 8fbcc05aacf02518655d8e544343c88b999e5e9d Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Fri, 20 Feb 2026 14:54:14 +0100 Subject: [PATCH 2/5] feat: Set $Package::VERSION in interpreter for package declarations Add version variable initialization when package declarations include a version number. This matches the compiler behavior where $Package::VERSION is automatically set when declaring 'package Foo 1.23;'. Implementation: - Check for package version in symbol table during package operator compilation - Set $Package::VERSION using GlobalVariable.getGlobalVariable() - Mirrors EmitOperator.java:761-766 from compiler Test impact: - op/packagev.t: Both compiler and interpreter remain at 218/307 passing - The remaining 89 failures are due to other issues: - Version validation/syntax errors not being detected - version->new() parsing issues - eval return value handling These are shared issues affecting both backends equally. Co-Authored-By: Claude Opus 4.6 --- .../java/org/perlonjava/interpreter/CompileOperator.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/org/perlonjava/interpreter/CompileOperator.java b/src/main/java/org/perlonjava/interpreter/CompileOperator.java index 0967fdf24..225ecca90 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileOperator.java @@ -63,6 +63,15 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode Boolean isClassAnnotation = (Boolean) node.getAnnotation("isClass"); boolean isClass = op.equals("class") || (isClassAnnotation != null && isClassAnnotation); + // Check if there's a version associated with this package and set $Package::VERSION + String version = bytecodeCompiler.symbolTable.getPackageVersion(packageName); + if (version != null) { + // Set $PackageName::VERSION at compile time using GlobalVariable + String versionVarName = packageName + "::VERSION"; + org.perlonjava.runtime.GlobalVariable.getGlobalVariable(versionVarName) + .set(new org.perlonjava.runtime.RuntimeScalar(version)); + } + // Update the current package/class in symbol table // This tracks package name, isClass flag, and version bytecodeCompiler.symbolTable.setCurrentPackage(packageName, isClass); From b43ab7b907e7997ed9c19d699030dbe42cf5524a Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Fri, 20 Feb 2026 15:15:38 +0100 Subject: [PATCH 3/5] feat: Add process control operators to interpreter (getpgrp, setpgrp, getpriority, atan2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add compiler support for process control and math operators in the interpreter: - getpgrp($pid) - get process group - setpgrp($pid, $pgrp) - set process group - getpriority($which, $who) - get process priority - atan2($y, $x) - arctangent of y/x Implementation: - Opcodes already existed (GETPGRP=136, SETPGRP=137, GETPRIORITY=138, ATAN2=241) - Execution handlers already in SlowOpcodeHandler and ScalarBinaryOpcodeHandler - Added compilation code generation in CompileOperator.java Test impact (with JPERL_EVAL_USE_INTERPRETER=1): - op/lex_assign.t: 320/353 → 323/353 (+3 tests) - Fixes tests that use these operators inside eval blocks Remaining unsupported operators in eval blocks: - qx, each, caller, fileno, getc, chmod, umask, unlink, utime - rename, link, readlink, system, localtime, gmtime, pack, vec, crypt Co-Authored-By: Claude Opus 4.6 --- .../interpreter/CompileOperator.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/main/java/org/perlonjava/interpreter/CompileOperator.java b/src/main/java/org/perlonjava/interpreter/CompileOperator.java index 225ecca90..bb1731256 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileOperator.java @@ -2636,6 +2636,84 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitWithToken(Opcodes.GETPPID, node.getIndex()); bytecodeCompiler.emitReg(rd); bytecodeCompiler.lastResultReg = rd; + } else if (op.equals("getpgrp")) { + // getpgrp($pid) - returns process group + // Format: GETPGRP rd pidReg + if (node.operand != null) { + node.operand.accept(bytecodeCompiler); + int pidReg = bytecodeCompiler.lastResultReg; + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emitWithToken(Opcodes.GETPGRP, node.getIndex()); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(pidReg); + bytecodeCompiler.lastResultReg = rd; + } else { + bytecodeCompiler.throwCompilerException("getpgrp requires an argument"); + } + } else if (op.equals("setpgrp")) { + // setpgrp($pid, $pgrp) - sets process group + // Format: SETPGRP pidReg pgrpReg + if (node.operand instanceof ListNode) { + ListNode list = (ListNode) node.operand; + if (list.elements.size() >= 2) { + list.elements.get(0).accept(bytecodeCompiler); + int pidReg = bytecodeCompiler.lastResultReg; + list.elements.get(1).accept(bytecodeCompiler); + int pgrpReg = bytecodeCompiler.lastResultReg; + bytecodeCompiler.emitWithToken(Opcodes.SETPGRP, node.getIndex()); + bytecodeCompiler.emitReg(pidReg); + bytecodeCompiler.emitReg(pgrpReg); + bytecodeCompiler.lastResultReg = -1; // No return value + } else { + bytecodeCompiler.throwCompilerException("setpgrp requires two arguments"); + } + } else { + bytecodeCompiler.throwCompilerException("setpgrp requires two arguments"); + } + } else if (op.equals("getpriority")) { + // getpriority($which, $who) - returns process priority + // Format: GETPRIORITY rd whichReg whoReg + if (node.operand instanceof ListNode) { + ListNode list = (ListNode) node.operand; + if (list.elements.size() >= 2) { + list.elements.get(0).accept(bytecodeCompiler); + int whichReg = bytecodeCompiler.lastResultReg; + list.elements.get(1).accept(bytecodeCompiler); + int whoReg = bytecodeCompiler.lastResultReg; + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emitWithToken(Opcodes.GETPRIORITY, node.getIndex()); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(whichReg); + bytecodeCompiler.emitReg(whoReg); + bytecodeCompiler.lastResultReg = rd; + } else { + bytecodeCompiler.throwCompilerException("getpriority requires two arguments"); + } + } else { + bytecodeCompiler.throwCompilerException("getpriority requires two arguments"); + } + } else if (op.equals("atan2")) { + // atan2($y, $x) - returns arctangent of y/x + // Format: ATAN2 rd rs1 rs2 + if (node.operand instanceof ListNode) { + ListNode list = (ListNode) node.operand; + if (list.elements.size() >= 2) { + list.elements.get(0).accept(bytecodeCompiler); + int rs1 = bytecodeCompiler.lastResultReg; + list.elements.get(1).accept(bytecodeCompiler); + int rs2 = bytecodeCompiler.lastResultReg; + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emitWithToken(Opcodes.ATAN2, node.getIndex()); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(rs1); + bytecodeCompiler.emitReg(rs2); + bytecodeCompiler.lastResultReg = rd; + } else { + bytecodeCompiler.throwCompilerException("atan2 requires two arguments"); + } + } else { + bytecodeCompiler.throwCompilerException("atan2 requires two arguments"); + } } else { bytecodeCompiler.throwCompilerException("Unsupported operator: " + op); } From 4fa4549d7456ba1714434bf001f389572b8de875 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Fri, 20 Feb 2026 15:25:27 +0100 Subject: [PATCH 4/5] fix: Support array/hash element increment in interpreter eval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix parser error "Increment/decrement operator requires operand" when incrementing array or hash elements inside eval blocks with interpreter. Problem: - `$_[0]++` or `$x[0]++` failed in eval with JPERL_EVAL_USE_INTERPRETER=1 - Error: "Increment/decrement operator requires operand" - CompileOperator only handled IdentifierNode and simple OperatorNode Solution: - Add BinaryOperatorNode handling for array/hash element access - Compile the lvalue (element access) first - Apply increment/decrement opcodes to the result Test fixes (JPERL_EVAL_USE_INTERPRETER=1): - op/attrs.t: 43/126 → 44/126 (+1 test) - Fixes test 2: eval '$anon1 = sub : method { $_[0]++ }' Implementation: - Handles $x[0]++, $_[0]++, $x{key}++, etc. - Supports both prefix (++$x[0]) and postfix ($x[0]++) - Uses existing POST_AUTOINCREMENT/POST_AUTODECREMENT opcodes Co-Authored-By: Claude Opus 4.6 --- .../interpreter/CompileOperator.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/main/java/org/perlonjava/interpreter/CompileOperator.java b/src/main/java/org/perlonjava/interpreter/CompileOperator.java index bb1731256..71cdfced2 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileOperator.java @@ -376,6 +376,37 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else { bytecodeCompiler.throwCompilerException("Invalid operand for increment/decrement operator"); } + } else if (node.operand instanceof BinaryOperatorNode) { + // Handle array/hash element increment: $x[0]++, $x{key}++, $_[0]++ + BinaryOperatorNode binOp = (BinaryOperatorNode) node.operand; + + // Compile the lvalue (array/hash element access) + // This will generate code to get the element reference + node.operand.accept(bytecodeCompiler); + int elementReg = bytecodeCompiler.lastResultReg; + + // Apply increment/decrement to the element + if (isPostfix) { + // Postfix: returns old value before modifying + int resultReg = bytecodeCompiler.allocateRegister(); + if (isIncrement) { + bytecodeCompiler.emit(Opcodes.POST_AUTOINCREMENT); + } else { + bytecodeCompiler.emit(Opcodes.POST_AUTODECREMENT); + } + bytecodeCompiler.emitReg(resultReg); // Destination for old value + bytecodeCompiler.emitReg(elementReg); // Element to modify + bytecodeCompiler.lastResultReg = resultReg; + } else { + // Prefix: returns new value after modifying + if (isIncrement) { + bytecodeCompiler.emit(Opcodes.PRE_AUTOINCREMENT); + } else { + bytecodeCompiler.emit(Opcodes.PRE_AUTODECREMENT); + } + bytecodeCompiler.emitReg(elementReg); + bytecodeCompiler.lastResultReg = elementReg; + } } else { bytecodeCompiler.throwCompilerException("Increment/decrement operator requires operand"); } From cdcfe538cad0bc3b3548f1d43efa98f3ceba7b3a Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Fri, 20 Feb 2026 15:42:20 +0100 Subject: [PATCH 5/5] refactor: Simplify increment/decrement with recursive approach Simplify the increment/decrement operator implementation using a recursive approach while keeping the fast path for lexical variables. Before: 145 lines with special cases for each node type After: 45 lines with optimized path + general recursive path Benefits: - Reduces CompileOperator.java from 2752 to 2651 lines (-101 lines) - Handles ALL lvalue types: $x++, $_[0]++, $x{key}++, $obj->{field}++ - Simpler to understand and maintain - Reduces JVM bytecode size (helps keep method under 8K limit) Implementation: - Fast path: simple lexical variable (IdentifierNode) - General: node.operand.accept() for ANY lvalue - Apply increment/decrement opcodes to result Co-Authored-By: Claude Opus 4.6 --- .../interpreter/CompileOperator.java | 139 +++--------------- 1 file changed, 19 insertions(+), 120 deletions(-) diff --git a/src/main/java/org/perlonjava/interpreter/CompileOperator.java b/src/main/java/org/perlonjava/interpreter/CompileOperator.java index 71cdfced2..4eca9bb39 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileOperator.java @@ -265,147 +265,46 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.lastResultReg = rd; } else if (op.equals("++") || op.equals("--") || op.equals("++postfix") || op.equals("--postfix")) { - // Pre/post increment/decrement + // Pre/post increment/decrement - recursive approach with fast path optimization boolean isPostfix = op.endsWith("postfix"); boolean isIncrement = op.startsWith("++"); - if (node.operand instanceof IdentifierNode) { - String varName = ((IdentifierNode) node.operand).name; - - if (bytecodeCompiler.hasVariable(varName)) { - int varReg = bytecodeCompiler.getVariableRegister(varName); - - // Use optimized autoincrement/decrement opcodes - if (isPostfix) { - // Postfix: returns old value before modifying - // Need TWO registers: one for result (old value), one for variable - int resultReg = bytecodeCompiler.allocateRegister(); - if (isIncrement) { - bytecodeCompiler.emit(Opcodes.POST_AUTOINCREMENT); - } else { - bytecodeCompiler.emit(Opcodes.POST_AUTODECREMENT); - } - bytecodeCompiler.emitReg(resultReg); // Destination for old value - bytecodeCompiler.emitReg(varReg); // Variable to modify in-place - bytecodeCompiler.lastResultReg = resultReg; - } else { - // Prefix: returns new value after modifying - if (isIncrement) { - bytecodeCompiler.emit(Opcodes.PRE_AUTOINCREMENT); - } else { - bytecodeCompiler.emit(Opcodes.PRE_AUTODECREMENT); - } - bytecodeCompiler.emitReg(varReg); - bytecodeCompiler.lastResultReg = varReg; - } - } else { - bytecodeCompiler.throwCompilerException("Increment/decrement of non-lexical variable not yet supported"); - } - } else if (node.operand instanceof OperatorNode) { - // Handle $x++ - OperatorNode innerOp = (OperatorNode) node.operand; - if (innerOp.operator.equals("$") && innerOp.operand instanceof IdentifierNode) { - String varName = "$" + ((IdentifierNode) innerOp.operand).name; - + if (node.operand != null) { + // Fast path: simple lexical variable (most common case) + if (node.operand instanceof IdentifierNode) { + String varName = ((IdentifierNode) node.operand).name; if (bytecodeCompiler.hasVariable(varName)) { int varReg = bytecodeCompiler.getVariableRegister(varName); - - // Use optimized autoincrement/decrement opcodes if (isPostfix) { - // Postfix: returns old value before modifying - // Need TWO registers: one for result (old value), one for variable int resultReg = bytecodeCompiler.allocateRegister(); - if (isIncrement) { - bytecodeCompiler.emit(Opcodes.POST_AUTOINCREMENT); - } else { - bytecodeCompiler.emit(Opcodes.POST_AUTODECREMENT); - } - bytecodeCompiler.emitReg(resultReg); // Destination for old value - bytecodeCompiler.emitReg(varReg); // Variable to modify in-place + bytecodeCompiler.emit(isIncrement ? Opcodes.POST_AUTOINCREMENT : Opcodes.POST_AUTODECREMENT); + bytecodeCompiler.emitReg(resultReg); + bytecodeCompiler.emitReg(varReg); bytecodeCompiler.lastResultReg = resultReg; } else { - if (isIncrement) { - bytecodeCompiler.emit(Opcodes.PRE_AUTOINCREMENT); - } else { - bytecodeCompiler.emit(Opcodes.PRE_AUTODECREMENT); - } + bytecodeCompiler.emit(isIncrement ? Opcodes.PRE_AUTOINCREMENT : Opcodes.PRE_AUTODECREMENT); bytecodeCompiler.emitReg(varReg); bytecodeCompiler.lastResultReg = varReg; } - } else { - // Global variable increment/decrement - // Normalize global variable name (remove sigil, add package) - String bareVarName = varName.substring(1); // Remove "$" - String normalizedName = NameNormalizer.normalizeVariableName(bareVarName, bytecodeCompiler.getCurrentPackage()); - int nameIdx = bytecodeCompiler.addToStringPool(normalizedName); - - // Load global variable - int globalReg = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(Opcodes.LOAD_GLOBAL_SCALAR); - bytecodeCompiler.emitReg(globalReg); - bytecodeCompiler.emit(nameIdx); - - // Apply increment/decrement - if (isPostfix) { - // Postfix: returns old value before modifying - // Need TWO registers: one for result (old value), one for variable - int resultReg = bytecodeCompiler.allocateRegister(); - if (isIncrement) { - bytecodeCompiler.emit(Opcodes.POST_AUTOINCREMENT); - } else { - bytecodeCompiler.emit(Opcodes.POST_AUTODECREMENT); - } - bytecodeCompiler.emitReg(resultReg); // Destination for old value - bytecodeCompiler.emitReg(globalReg); // Variable to modify in-place - bytecodeCompiler.lastResultReg = resultReg; - } else { - if (isIncrement) { - bytecodeCompiler.emit(Opcodes.PRE_AUTOINCREMENT); - } else { - bytecodeCompiler.emit(Opcodes.PRE_AUTODECREMENT); - } - bytecodeCompiler.emitReg(globalReg); - bytecodeCompiler.lastResultReg = globalReg; - } - - // NOTE: Do NOT store back to global variable! - // The POST/PRE_AUTO* opcodes modify the global variable directly - // and return the appropriate value (old for postfix, new for prefix). - // Storing back would overwrite the modification with the return value. + return; } - } else { - bytecodeCompiler.throwCompilerException("Invalid operand for increment/decrement operator"); } - } else if (node.operand instanceof BinaryOperatorNode) { - // Handle array/hash element increment: $x[0]++, $x{key}++, $_[0]++ - BinaryOperatorNode binOp = (BinaryOperatorNode) node.operand; - // Compile the lvalue (array/hash element access) - // This will generate code to get the element reference + // General case: recursively compile the lvalue + // Handles: $x++, $x[0]++, $x{key}++, $_[0]++, $obj->{field}++, etc. node.operand.accept(bytecodeCompiler); - int elementReg = bytecodeCompiler.lastResultReg; + int operandReg = bytecodeCompiler.lastResultReg; - // Apply increment/decrement to the element if (isPostfix) { - // Postfix: returns old value before modifying int resultReg = bytecodeCompiler.allocateRegister(); - if (isIncrement) { - bytecodeCompiler.emit(Opcodes.POST_AUTOINCREMENT); - } else { - bytecodeCompiler.emit(Opcodes.POST_AUTODECREMENT); - } - bytecodeCompiler.emitReg(resultReg); // Destination for old value - bytecodeCompiler.emitReg(elementReg); // Element to modify + bytecodeCompiler.emit(isIncrement ? Opcodes.POST_AUTOINCREMENT : Opcodes.POST_AUTODECREMENT); + bytecodeCompiler.emitReg(resultReg); + bytecodeCompiler.emitReg(operandReg); bytecodeCompiler.lastResultReg = resultReg; } else { - // Prefix: returns new value after modifying - if (isIncrement) { - bytecodeCompiler.emit(Opcodes.PRE_AUTOINCREMENT); - } else { - bytecodeCompiler.emit(Opcodes.PRE_AUTODECREMENT); - } - bytecodeCompiler.emitReg(elementReg); - bytecodeCompiler.lastResultReg = elementReg; + bytecodeCompiler.emit(isIncrement ? Opcodes.PRE_AUTOINCREMENT : Opcodes.PRE_AUTODECREMENT); + bytecodeCompiler.emitReg(operandReg); + bytecodeCompiler.lastResultReg = operandReg; } } else { bytecodeCompiler.throwCompilerException("Increment/decrement operator requires operand");