diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java b/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java index 88b07e267..e85df291a 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java @@ -530,6 +530,18 @@ public void visit(BinaryOperatorNode node) { emit(rs1); emit(rs2); } + case ">" -> { + emit(Opcodes.GT_NUM); + emit(rd); + emit(rs1); + emit(rs2); + } + case "!=" -> { + emit(Opcodes.NE_NUM); + emit(rd); + emit(rs1); + emit(rs2); + } case "()", "->" -> { // Apply operator: $coderef->(args) or &subname(args) // left (rs1) = code reference (RuntimeScalar containing RuntimeCode or SubroutineNode) @@ -561,6 +573,116 @@ public void visit(BinaryOperatorNode node) { emit(rs1); emit(rs2); } + case ".." -> { + // Range operator: start..end + // Create a PerlRange object which can be iterated or converted to a list + + // Optimization: if both operands are constant numbers, create range at compile time + if (node.left instanceof NumberNode && node.right instanceof NumberNode) { + try { + int start = Integer.parseInt(((NumberNode) node.left).value); + int end = Integer.parseInt(((NumberNode) node.right).value); + + // Create PerlRange with RuntimeScalarCache integers + RuntimeScalar startScalar = RuntimeScalarCache.getScalarInt(start); + RuntimeScalar endScalar = RuntimeScalarCache.getScalarInt(end); + PerlRange range = PerlRange.createRange(startScalar, endScalar); + + // Store in constant pool and load + int constIdx = addToConstantPool(range); + emit(Opcodes.LOAD_CONST); + emit(rd); + emit(constIdx); + } catch (NumberFormatException e) { + throw new RuntimeException("Range operator requires integer values: " + e.getMessage()); + } + } else { + // Runtime range creation using RANGE opcode + // rs1 and rs2 already contain the start and end values + emit(Opcodes.RANGE); + emit(rd); + emit(rs1); + emit(rs2); + } + } + case "&&", "and" -> { + // Logical AND with short-circuit evaluation + // Semantics: if left is false, return left; else evaluate and return right + // Implementation: + // rd = left + // if (!rd) goto skip_right + // rd = right + // skip_right: + + // Left operand is already in rs1 + // Move to result register + emit(Opcodes.MOVE); + emit(rd); + emit(rs1); + + // Mark position for forward jump + int skipRightPos = bytecode.size(); + + // Emit conditional jump: if (!rd) skip right evaluation + emit(Opcodes.GOTO_IF_FALSE); + emit(rd); + emitInt(0); // Placeholder for offset (will be patched) + + // Right operand is already in rs2 + // Move to result register (overwriting left value) + emit(Opcodes.MOVE); + emit(rd); + emit(rs2); + + // Patch the forward jump offset + int skipRightTarget = bytecode.size(); + patchIntOffset(skipRightPos + 2, skipRightTarget); + } + case "||", "or" -> { + // Logical OR with short-circuit evaluation + // Semantics: if left is true, return left; else evaluate and return right + // Implementation: + // rd = left + // if (rd) goto skip_right + // rd = right + // skip_right: + + // Left operand is already in rs1 + // Move to result register + emit(Opcodes.MOVE); + emit(rd); + emit(rs1); + + // Mark position for forward jump + int skipRightPos = bytecode.size(); + + // Emit conditional jump: if (rd) skip right evaluation + emit(Opcodes.GOTO_IF_TRUE); + emit(rd); + emitInt(0); // Placeholder for offset (will be patched) + + // Right operand is already in rs2 + // Move to result register (overwriting left value) + emit(Opcodes.MOVE); + emit(rd); + emit(rs2); + + // Patch the forward jump offset + int skipRightTarget = bytecode.size(); + patchIntOffset(skipRightPos + 2, skipRightTarget); + } + case "map" -> { + // Map operator: map { block } list + // rs1 = closure (SubroutineNode compiled to code reference) + // rs2 = list expression + + // Emit MAP opcode + emit(Opcodes.MAP); + emit(rd); + emit(rs2); // List register + emit(rs1); // Closure register + emit(RuntimeContextType.LIST); // Map always uses list context + } default -> throw new RuntimeException("Unsupported operator: " + node.operator); } @@ -601,8 +723,15 @@ public void visit(OperatorNode node) { lastResultReg = registerMap.get(varName); } else { // Global variable - load it + // Add package prefix if not present (match compiler behavior) + String globalVarName = varName; + if (!globalVarName.contains("::")) { + // Remove $ sigil, add package, restore sigil + globalVarName = "main::" + varName.substring(1); + } + int rd = allocateRegister(); - int nameIdx = addToStringPool(varName); + int nameIdx = addToStringPool(globalVarName); emit(Opcodes.LOAD_GLOBAL_SCALAR); emit(rd); @@ -676,6 +805,15 @@ public void visit(OperatorNode node) { } else { throw new RuntimeException("Reference operator requires operand"); } + } else if (op.equals("package")) { + // Package declaration: package Foo; + // This is a compile-time directive that sets the namespace context. + // It doesn't generate any runtime bytecode. + // The operand is an IdentifierNode with the package name. + + // Don't emit any bytecode - just leave lastResultReg unchanged + // (or set to -1 to indicate no result) + lastResultReg = -1; } else if (op.equals("say") || op.equals("print")) { // say/print $x if (node.operand != null) { @@ -685,6 +823,25 @@ public void visit(OperatorNode node) { emit(op.equals("say") ? Opcodes.SAY : Opcodes.PRINT); emit(rs); } + } else if (op.equals("not")) { + // Logical NOT operator: not $x + // Evaluate operand and emit NOT opcode + if (node.operand != null) { + node.operand.accept(this); + int rs = lastResultReg; + + // Allocate result register + int rd = allocateRegister(); + + // Emit NOT opcode + emit(Opcodes.NOT); + emit(rd); + emit(rs); + + lastResultReg = rd; + } else { + throw new RuntimeException("NOT operator requires operand"); + } } else if (op.equals("++") || op.equals("--") || op.equals("++postfix") || op.equals("--postfix")) { // Pre/post increment/decrement boolean isPostfix = op.endsWith("postfix"); @@ -769,6 +926,34 @@ public void visit(OperatorNode node) { emit(undefReg); } lastResultReg = -1; // No result after return + } else if (op.equals("rand")) { + // rand() or rand($max) + // Calls Random.rand(max) where max defaults to 1 + int rd = allocateRegister(); + + if (node.operand != null) { + // rand($max) - evaluate operand + node.operand.accept(this); + int maxReg = lastResultReg; + + // Emit RAND opcode + emit(Opcodes.RAND); + emit(rd); + emit(maxReg); + } else { + // rand() with no argument - defaults to 1 + int oneReg = allocateRegister(); + emit(Opcodes.LOAD_INT); + emit(oneReg); + emitInt(1); + + // Emit RAND opcode + emit(Opcodes.RAND); + emit(rd); + emit(oneReg); + } + + lastResultReg = rd; } else if (op.equals("die")) { // die $message; if (node.operand != null) { @@ -903,32 +1088,200 @@ private void emitInt(int value) { bytecode.write(value & 0xFF); } + /** + * Emit a 2-byte short value (big-endian for jump offsets). + */ + private void emitShort(int value) { + bytecode.write((value >> 8) & 0xFF); + bytecode.write(value & 0xFF); + } + + /** + * Patch a 4-byte int offset at the specified position. + * Used for forward jumps where the target is unknown at emit time. + */ + private void patchIntOffset(int position, int target) { + byte[] bytes = bytecode.toByteArray(); + // Store absolute target address (not relative offset) + bytes[position] = (byte)((target >> 24) & 0xFF); + bytes[position + 1] = (byte)((target >> 16) & 0xFF); + bytes[position + 2] = (byte)((target >> 8) & 0xFF); + bytes[position + 3] = (byte)(target & 0xFF); + // Rebuild the stream with patched bytes + bytecode.reset(); + bytecode.write(bytes, 0, bytes.length); + } + + /** + * Patch a 2-byte short offset at the specified position. + * Used for forward jumps where the target is unknown at emit time. + */ + private void patchShortOffset(int position, int target) { + byte[] bytes = bytecode.toByteArray(); + // Store absolute target address (not relative offset) + bytes[position] = (byte)((target >> 8) & 0xFF); + bytes[position + 1] = (byte)(target & 0xFF); + // Rebuild the stream with patched bytes + bytecode.reset(); + bytecode.write(bytes, 0, bytes.length); + } + // ========================================================================= // UNIMPLEMENTED VISITOR METHODS (TODO) // ========================================================================= @Override public void visit(ArrayLiteralNode node) { - throw new UnsupportedOperationException("Arrays not yet implemented"); + // Array literal: [expr1, expr2, ...] + // In Perl, [..] creates an ARRAY REFERENCE (RuntimeScalar containing RuntimeArray) + // Implementation: + // 1. Create a list with all elements + // 2. Convert list to array reference using CREATE_ARRAY (which now returns reference) + + // Fast path: empty array + if (node.elements.isEmpty()) { + // Create empty RuntimeList + int listReg = allocateRegister(); + emit(Opcodes.CREATE_LIST); + emit(listReg); + emit(0); // count = 0 + + // Convert to RuntimeArray reference (CREATE_ARRAY now returns reference!) + int refReg = allocateRegister(); + emit(Opcodes.CREATE_ARRAY); + emit(refReg); + emit(listReg); + + lastResultReg = refReg; + return; + } + + // General case: evaluate all elements + int[] elementRegs = new int[node.elements.size()]; + for (int i = 0; i < node.elements.size(); i++) { + node.elements.get(i).accept(this); + elementRegs[i] = lastResultReg; + } + + // Create RuntimeList with all elements + int listReg = allocateRegister(); + emit(Opcodes.CREATE_LIST); + emit(listReg); + emit(node.elements.size()); // count + + // Emit register numbers for each element + for (int elemReg : elementRegs) { + emit(elemReg); + } + + // Convert list to array reference (CREATE_ARRAY now returns reference!) + int refReg = allocateRegister(); + emit(Opcodes.CREATE_ARRAY); + emit(refReg); + emit(listReg); + + lastResultReg = refReg; } @Override public void visit(HashLiteralNode node) { - throw new UnsupportedOperationException("Hashes not yet implemented"); + // Hash literal: {key1 => value1, key2 => value2, ...} + // Can also contain array expansion: {a => 1, @x} + // In Perl, {...} creates a HASH REFERENCE (RuntimeScalar containing RuntimeHash) + // + // Implementation: + // 1. Create a RuntimeList with all elements (including arrays) + // 2. Call CREATE_HASH which flattens arrays and creates hash reference + + // Fast path: empty hash + if (node.elements.isEmpty()) { + int listReg = allocateRegister(); + emit(Opcodes.CREATE_LIST); + emit(listReg); + emit(0); // count = 0 + + // Create hash reference (CREATE_HASH now returns reference!) + int refReg = allocateRegister(); + emit(Opcodes.CREATE_HASH); + emit(refReg); + emit(listReg); + + lastResultReg = refReg; + return; + } + + // General case: evaluate all elements + int[] elementRegs = new int[node.elements.size()]; + for (int i = 0; i < node.elements.size(); i++) { + node.elements.get(i).accept(this); + elementRegs[i] = lastResultReg; + } + + // Create RuntimeList with all elements + // Arrays will be included as-is; RuntimeHash.createHash() will flatten them + int listReg = allocateRegister(); + emit(Opcodes.CREATE_LIST); + emit(listReg); + emit(node.elements.size()); // count + + // Emit register numbers for each element + for (int elemReg : elementRegs) { + emit(elemReg); + } + + // Create hash reference (CREATE_HASH now returns reference!) + int refReg = allocateRegister(); + emit(Opcodes.CREATE_HASH); + emit(refReg); + emit(listReg); + + lastResultReg = refReg; } @Override public void visit(SubroutineNode node) { - // For now, only handle anonymous subroutines used as eval blocks if (node.useTryCatch) { // This is an eval block: eval { ... } visitEvalBlock(node); + } else if (node.name == null || node.name.equals("")) { + // Anonymous subroutine: sub { ... } + visitAnonymousSubroutine(node); } else { - // Regular named or anonymous subroutine - not yet supported - throw new UnsupportedOperationException("Named subroutines not yet implemented in interpreter"); + // Named subroutine - not yet supported + throw new UnsupportedOperationException("Named subroutines not yet implemented in interpreter: " + node.name); } } + /** + * Visit an anonymous subroutine: sub { ... } + * + * Compiles the subroutine body to bytecode and creates an InterpretedCode instance. + * Handles closure variable capture if needed. + * + * The result is an InterpretedCode wrapped in RuntimeScalar, stored in lastResultReg. + */ + private void visitAnonymousSubroutine(SubroutineNode node) { + // Create a new BytecodeCompiler for the subroutine body + BytecodeCompiler subCompiler = new BytecodeCompiler(this.sourceName, node.getIndex()); + + // Compile the subroutine body to InterpretedCode + InterpretedCode subCode = subCompiler.compile(node.block); + + // Wrap InterpretedCode in RuntimeScalar + // Explicitly cast to RuntimeCode to ensure RuntimeScalar(RuntimeCode) constructor is called + RuntimeScalar codeScalar = new RuntimeScalar((RuntimeCode) subCode); + + // Store the wrapped code in constants pool and load it into a register + int constIdx = addToConstantPool(codeScalar); + int rd = allocateRegister(); + + emit(Opcodes.LOAD_CONST); + emit(rd); + emit(constIdx); + + lastResultReg = rd; + } + /** * Visit an eval block: eval { ... } * @@ -1146,12 +1499,115 @@ public void visit(For3Node node) { @Override public void visit(IfNode node) { - throw new UnsupportedOperationException("If statements not yet implemented"); + // Compile condition + node.condition.accept(this); + int condReg = lastResultReg; + + // Mark position for forward jump to else/end + int ifFalsePos = bytecode.size(); + emit(Opcodes.GOTO_IF_FALSE); + emit(condReg); + emitInt(0); // Placeholder for else/end target + + // Compile then block + if (node.thenBranch != null) { + node.thenBranch.accept(this); + } + int thenResultReg = lastResultReg; + + if (node.elseBranch != null) { + // Need to jump over else block after then block executes + int gotoEndPos = bytecode.size(); + emit(Opcodes.GOTO); + emitInt(0); // Placeholder for end target + + // Patch if-false jump to here (start of else block) + int elseStart = bytecode.size(); + patchIntOffset(ifFalsePos + 2, elseStart); + + // Compile else block + node.elseBranch.accept(this); + int elseResultReg = lastResultReg; + + // Both branches should produce results in the same register + // If they differ, move else result to then result register + if (thenResultReg >= 0 && elseResultReg >= 0 && thenResultReg != elseResultReg) { + emit(Opcodes.MOVE); + emit(thenResultReg); + emit(elseResultReg); + } + + // Patch goto-end jump to here + int endPos = bytecode.size(); + patchIntOffset(gotoEndPos + 1, endPos); + + lastResultReg = thenResultReg >= 0 ? thenResultReg : elseResultReg; + } else { + // No else block - patch if-false jump to here (after then block) + int endPos = bytecode.size(); + patchIntOffset(ifFalsePos + 2, endPos); + + lastResultReg = thenResultReg; + } } @Override public void visit(TernaryOperatorNode node) { - throw new UnsupportedOperationException("Ternary operator not yet implemented"); + // condition ? true_expr : false_expr + // Implementation: + // eval condition + // if (!condition) goto false_label + // rd = true_expr + // goto end_label + // false_label: + // rd = false_expr + // end_label: + + // Compile condition + node.condition.accept(this); + int condReg = lastResultReg; + + // Allocate result register + int rd = allocateRegister(); + + // Mark position for forward jump to false expression + int ifFalsePos = bytecode.size(); + emit(Opcodes.GOTO_IF_FALSE); + emit(condReg); + emitInt(0); // Placeholder for false_label + + // Compile true expression + node.trueExpr.accept(this); + int trueReg = lastResultReg; + + // Move true result to rd + emit(Opcodes.MOVE); + emit(rd); + emit(trueReg); + + // Jump over false expression + int gotoEndPos = bytecode.size(); + emit(Opcodes.GOTO); + emitInt(0); // Placeholder for end_label + + // Patch if-false jump to here (start of false expression) + int falseStart = bytecode.size(); + patchIntOffset(ifFalsePos + 2, falseStart); + + // Compile false expression + node.falseExpr.accept(this); + int falseReg = lastResultReg; + + // Move false result to rd + emit(Opcodes.MOVE); + emit(rd); + emit(falseReg); + + // Patch goto-end jump to here + int endPos = bytecode.size(); + patchIntOffset(gotoEndPos + 1, endPos); + + lastResultReg = rd; } @Override diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java index 8e4a24f0c..ebb663391 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java @@ -361,6 +361,30 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } + case Opcodes.GT_NUM: { + // Greater than: rd = (rs1 > rs2) + int rd = bytecode[pc++] & 0xFF; + int rs1 = bytecode[pc++] & 0xFF; + int rs2 = bytecode[pc++] & 0xFF; + registers[rd] = CompareOperators.greaterThan( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + break; + } + + case Opcodes.NE_NUM: { + // Not equal: rd = (rs1 != rs2) + int rd = bytecode[pc++] & 0xFF; + int rs1 = bytecode[pc++] & 0xFF; + int rs2 = bytecode[pc++] & 0xFF; + registers[rd] = CompareOperators.notEqualTo( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + break; + } + // ================================================================= // LOGICAL OPERATORS // ================================================================= @@ -423,9 +447,25 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c } case Opcodes.CREATE_ARRAY: { - // Create array: rd = [] + // Create array reference from list: rd = new RuntimeArray(rs_list).createReference() + // Array literals always return references in Perl int rd = bytecode[pc++] & 0xFF; - registers[rd] = new RuntimeArray(); + int listReg = bytecode[pc++] & 0xFF; + + // Convert to list (polymorphic - works for PerlRange, RuntimeList, etc.) + RuntimeBase source = registers[listReg]; + RuntimeArray array; + if (source instanceof RuntimeArray) { + // Already an array - pass through + array = (RuntimeArray) source; + } else { + // Convert to list, then to array (works for PerlRange, RuntimeList, etc.) + RuntimeList list = source.getList(); + array = new RuntimeArray(list); + } + + // Create reference (array literals always return references!) + registers[rd] = array.createReference(); break; } @@ -457,13 +497,6 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } - case Opcodes.CREATE_HASH: { - // Create hash: rd = {} - int rd = bytecode[pc++] & 0xFF; - registers[rd] = new RuntimeHash(); - break; - } - // ================================================================= // SUBROUTINE CALLS // ================================================================= @@ -860,6 +893,59 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } + case Opcodes.RANGE: { + // Create range: rd = PerlRange.createRange(rs_start, rs_end) + int rd = bytecode[pc++] & 0xFF; + int startReg = bytecode[pc++] & 0xFF; + int endReg = bytecode[pc++] & 0xFF; + + RuntimeScalar start = (RuntimeScalar) registers[startReg]; + RuntimeScalar end = (RuntimeScalar) registers[endReg]; + PerlRange range = PerlRange.createRange(start, end); + registers[rd] = range; + break; + } + + case Opcodes.CREATE_HASH: { + // Create hash reference from list: rd = RuntimeHash.createHash(rs_list).createReference() + // Hash literals always return references in Perl + // This flattens any arrays in the list and creates key-value pairs + int rd = bytecode[pc++] & 0xFF; + int listReg = bytecode[pc++] & 0xFF; + + RuntimeBase list = registers[listReg]; + RuntimeHash hash = RuntimeHash.createHash(list); + + // Create reference (hash literals always return references!) + registers[rd] = hash.createReference(); + break; + } + + case Opcodes.RAND: { + // Random number: rd = Random.rand(max) + int rd = bytecode[pc++] & 0xFF; + int maxReg = bytecode[pc++] & 0xFF; + + RuntimeScalar max = (RuntimeScalar) registers[maxReg]; + registers[rd] = org.perlonjava.operators.Random.rand(max); + break; + } + + case Opcodes.MAP: { + // Map operator: rd = ListOperators.map(list, closure, ctx) + int rd = bytecode[pc++] & 0xFF; + int listReg = bytecode[pc++] & 0xFF; + int closureReg = bytecode[pc++] & 0xFF; + int ctx = bytecode[pc++] & 0xFF; + + RuntimeBase listBase = registers[listReg]; + RuntimeList list = listBase.getList(); + RuntimeScalar closure = (RuntimeScalar) registers[closureReg]; + RuntimeList result = org.perlonjava.operators.ListOperators.map(list, closure, ctx); + registers[rd] = result; + break; + } + // ================================================================= // SLOW OPERATIONS // ================================================================= diff --git a/src/main/java/org/perlonjava/interpreter/InterpretedCode.java b/src/main/java/org/perlonjava/interpreter/InterpretedCode.java index 8d1f3b36f..ad3b368ef 100644 --- a/src/main/java/org/perlonjava/interpreter/InterpretedCode.java +++ b/src/main/java/org/perlonjava/interpreter/InterpretedCode.java @@ -86,6 +86,16 @@ public RuntimeList apply(String subroutineName, RuntimeArray args, int callConte return BytecodeInterpreter.execute(this, args, callContext, subroutineName); } + /** + * Override RuntimeCode.defined() to return true for InterpretedCode. + * InterpretedCode doesn't use methodHandle, so the parent defined() check fails. + * But InterpretedCode instances are always "defined" - they contain executable bytecode. + */ + @Override + public boolean defined() { + return true; + } + /** * Create an InterpretedCode with captured variables (for closures). * @@ -194,7 +204,15 @@ public String disassemble() { int constIdx = bytecode[pc++] & 0xFF; sb.append("LOAD_CONST r").append(rd).append(" = constants[").append(constIdx).append("]"); if (constants != null && constIdx < constants.length) { - sb.append(" (").append(constants[constIdx]).append(")"); + Object obj = constants[constIdx]; + sb.append(" ("); + if (obj instanceof RuntimeScalar) { + RuntimeScalar scalar = (RuntimeScalar) obj; + sb.append("RuntimeScalar{type=").append(scalar.type).append(", value=").append(scalar.value.getClass().getSimpleName()).append("}"); + } else { + sb.append(obj); + } + sb.append(")"); } sb.append("\n"); break; @@ -253,6 +271,18 @@ public String disassemble() { rs2 = bytecode[pc++] & 0xFF; sb.append("LT_NUM r").append(rd).append(" = r").append(rs1).append(" < r").append(rs2).append("\n"); break; + case Opcodes.GT_NUM: + rd = bytecode[pc++] & 0xFF; + rs1 = bytecode[pc++] & 0xFF; + rs2 = bytecode[pc++] & 0xFF; + sb.append("GT_NUM r").append(rd).append(" = r").append(rs1).append(" > r").append(rs2).append("\n"); + break; + case Opcodes.NE_NUM: + rd = bytecode[pc++] & 0xFF; + rs1 = bytecode[pc++] & 0xFF; + rs2 = bytecode[pc++] & 0xFF; + sb.append("NE_NUM r").append(rd).append(" = r").append(rs1).append(" != r").append(rs2).append("\n"); + break; case Opcodes.INC_REG: rd = bytecode[pc++] & 0xFF; sb.append("INC_REG r").append(rd).append("++\n"); @@ -339,6 +369,56 @@ public String disassemble() { rd = bytecode[pc++] & 0xFF; sb.append("EVAL_CATCH r").append(rd).append("\n"); break; + case Opcodes.ARRAY_GET: + rd = bytecode[pc++] & 0xFF; + int arrayReg = bytecode[pc++] & 0xFF; + int indexReg = bytecode[pc++] & 0xFF; + sb.append("ARRAY_GET r").append(rd).append(" = r").append(arrayReg).append("[r").append(indexReg).append("]\n"); + break; + case Opcodes.ARRAY_SIZE: + rd = bytecode[pc++] & 0xFF; + arrayReg = bytecode[pc++] & 0xFF; + sb.append("ARRAY_SIZE r").append(rd).append(" = size(r").append(arrayReg).append(")\n"); + break; + case Opcodes.CREATE_ARRAY: + rd = bytecode[pc++] & 0xFF; + int listSourceReg = bytecode[pc++] & 0xFF; + sb.append("CREATE_ARRAY r").append(rd).append(" = array(r").append(listSourceReg).append(")\n"); + break; + case Opcodes.HASH_GET: + rd = bytecode[pc++] & 0xFF; + int hashGetReg = bytecode[pc++] & 0xFF; + int keyGetReg = bytecode[pc++] & 0xFF; + sb.append("HASH_GET r").append(rd).append(" = r").append(hashGetReg).append("{r").append(keyGetReg).append("}\n"); + break; + case Opcodes.HASH_SET: + int hashSetReg = bytecode[pc++] & 0xFF; + int keySetReg = bytecode[pc++] & 0xFF; + int valueSetReg = bytecode[pc++] & 0xFF; + sb.append("HASH_SET r").append(hashSetReg).append("{r").append(keySetReg).append("} = r").append(valueSetReg).append("\n"); + break; + case Opcodes.HASH_EXISTS: + rd = bytecode[pc++] & 0xFF; + int hashExistsReg = bytecode[pc++] & 0xFF; + int keyExistsReg = bytecode[pc++] & 0xFF; + sb.append("HASH_EXISTS r").append(rd).append(" = exists r").append(hashExistsReg).append("{r").append(keyExistsReg).append("}\n"); + break; + case Opcodes.HASH_DELETE: + rd = bytecode[pc++] & 0xFF; + int hashDeleteReg = bytecode[pc++] & 0xFF; + int keyDeleteReg = bytecode[pc++] & 0xFF; + sb.append("HASH_DELETE r").append(rd).append(" = delete r").append(hashDeleteReg).append("{r").append(keyDeleteReg).append("}\n"); + break; + case Opcodes.HASH_KEYS: + rd = bytecode[pc++] & 0xFF; + int hashKeysReg = bytecode[pc++] & 0xFF; + sb.append("HASH_KEYS r").append(rd).append(" = keys(r").append(hashKeysReg).append(")\n"); + break; + case Opcodes.HASH_VALUES: + rd = bytecode[pc++] & 0xFF; + int hashValuesReg = bytecode[pc++] & 0xFF; + sb.append("HASH_VALUES r").append(rd).append(" = values(r").append(hashValuesReg).append(")\n"); + break; case Opcodes.CREATE_LIST: { rd = bytecode[pc++] & 0xFF; int listCount = bytecode[pc++] & 0xFF; @@ -371,6 +451,35 @@ public String disassemble() { listReg = bytecode[pc++] & 0xFF; sb.append("SELECT r").append(rd).append(" = select(r").append(listReg).append(")\n"); break; + case Opcodes.RANGE: + rd = bytecode[pc++] & 0xFF; + int startReg = bytecode[pc++] & 0xFF; + int endReg = bytecode[pc++] & 0xFF; + sb.append("RANGE r").append(rd).append(" = r").append(startReg).append("..r").append(endReg).append("\n"); + break; + case Opcodes.CREATE_HASH: + rd = bytecode[pc++] & 0xFF; + int hashListReg = bytecode[pc++] & 0xFF; + sb.append("CREATE_HASH r").append(rd).append(" = hash_ref(r").append(hashListReg).append(")\n"); + break; + case Opcodes.RAND: + rd = bytecode[pc++] & 0xFF; + int maxReg = bytecode[pc++] & 0xFF; + sb.append("RAND r").append(rd).append(" = rand(r").append(maxReg).append(")\n"); + break; + case Opcodes.MAP: + rd = bytecode[pc++] & 0xFF; + rs1 = bytecode[pc++] & 0xFF; // list register + rs2 = bytecode[pc++] & 0xFF; // closure register + int mapCtx = bytecode[pc++] & 0xFF; // context + sb.append("MAP r").append(rd).append(" = map(r").append(rs1) + .append(", r").append(rs2).append(", ctx=").append(mapCtx).append(")\n"); + break; + case Opcodes.NOT: + rd = bytecode[pc++] & 0xFF; + rs = bytecode[pc++] & 0xFF; + sb.append("NOT r").append(rd).append(" = !r").append(rs).append("\n"); + break; case Opcodes.SLOW_OP: { int slowOpId = bytecode[pc++] & 0xFF; String opName = SlowOpcodeHandler.getSlowOpName(slowOpId); diff --git a/src/main/java/org/perlonjava/interpreter/Opcodes.java b/src/main/java/org/perlonjava/interpreter/Opcodes.java index e5176f7a5..d3b4ccb93 100644 --- a/src/main/java/org/perlonjava/interpreter/Opcodes.java +++ b/src/main/java/org/perlonjava/interpreter/Opcodes.java @@ -4,7 +4,7 @@ * Bytecode opcodes for the PerlOnJava interpreter. * * Design: Pure register machine with 3-address code format. - * DENSE opcodes (0-74, NO GAPS) enable JVM tableswitch optimization. + * DENSE opcodes (0-92, NO GAPS) enable JVM tableswitch optimization. * * Register architecture is REQUIRED for control flow correctness: * Perl's GOTO/last/next/redo would corrupt a stack-based architecture. @@ -221,7 +221,7 @@ public class Opcodes { /** Hash values: rd = hash_reg.values() */ public static final byte HASH_VALUES = 55; - /** Create hash: rd = new RuntimeHash() */ + /** Create hash reference from list: rd = RuntimeHash.createHash(rs_list).createReference() */ public static final byte CREATE_HASH = 56; // ================================================================= @@ -369,20 +369,6 @@ public class Opcodes { */ public static final byte CREATE_LIST = 86; - // ================================================================= - // STRING OPERATIONS (88) - // ================================================================= - - /** Join list elements with separator: rd = join(rs_separator, rs_list) */ - public static final byte JOIN = 88; - - // ================================================================= - // I/O OPERATIONS (89) - // ================================================================= - - /** Select default output filehandle: rd = IOOperator.select(rs_list, SCALAR) */ - public static final byte SELECT = 89; - // ================================================================= // SLOW OPERATIONS (87) - Single opcode for rarely-used operations // ================================================================= @@ -397,13 +383,36 @@ public class Opcodes { * CPU i-cache optimization while allowing unlimited rare operations. * * Philosophy: - * - Fast operations (0-199): Direct opcodes in main switch + * - Fast operations (0-90): Direct opcodes in main switch * - Slow operations (via SLOW_OP): Delegated to SlowOpcodeHandler * * Performance: Adds ~5ns overhead but keeps main loop ~10-15% faster. */ public static final byte SLOW_OP = 87; + // ================================================================= + // STRING OPERATIONS (88) + // ================================================================= + + /** Join list elements with separator: rd = join(rs_separator, rs_list) */ + public static final byte JOIN = 88; + + // ================================================================= + // I/O OPERATIONS (89) + // ================================================================= + + /** Select default output filehandle: rd = IOOperator.select(rs_list, SCALAR) */ + public static final byte SELECT = 89; + + /** Create range: rd = PerlRange.createRange(rs_start, rs_end) */ + public static final byte RANGE = 90; + + /** Random number: rd = Random.rand(rs_max) */ + public static final byte RAND = 91; + + /** Map operator: rd = ListOperators.map(list_reg, closure_reg, context) */ + public static final byte MAP = 92; + // ================================================================= // Slow Operation IDs (0-255) // ================================================================= @@ -477,7 +486,7 @@ public class Opcodes { public static final int SLOWOP_LOAD_GLOB = 21; // ================================================================= - // OPCODES 88-255: RESERVED FOR FUTURE FAST OPERATIONS + // OPCODES 93-255: RESERVED FOR FUTURE FAST OPERATIONS // ================================================================= // This range is reserved for frequently-used operations that benefit // from being in the main interpreter switch for optimal CPU i-cache usage.