diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index d984d922b..d14e585b8 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -70,6 +70,10 @@ public class BytecodeCompiler implements Visitor { int currentTokenIndex = -1; // Track current token for error reporting // Track last result register for expression chaining int lastResultReg = -1; + // Target output register for ALIAS elimination (same save/restore pattern as currentCallContext). + // Callers set this before accept(); callees consume it via allocateOutputRegister(). + // Compound visitors save/restore around non-final children. + int targetOutputReg = -1; // Track current calling context for subroutine calls int currentCallContext = RuntimeContextType.LIST; // Default to LIST @@ -509,17 +513,18 @@ public InterpretedCode compile(Node node, EmitterContext ctx) { currentCallContext = RuntimeContextType.LIST; } - // Visit the node to generate bytecode + int returnTargetReg = allocateRegister(); + targetOutputReg = returnTargetReg; + node.accept(this); - // Emit RETURN with last result register - // If no result was produced, return undef instead of register 0 ("this") + targetOutputReg = -1; + int returnReg; if (lastResultReg >= 0) { returnReg = lastResultReg; } else { - // No result - allocate register for undef - returnReg = allocateRegister(); + returnReg = returnTargetReg; emit(Opcodes.LOAD_UNDEF); emitReg(returnReg); } @@ -723,7 +728,7 @@ public void visit(BlockNode node) { // allocate a result register BEFORE entering the scope so it's valid after int outerResultReg = -1; if (currentCallContext != RuntimeContextType.VOID) { - outerResultReg = allocateRegister(); + outerResultReg = allocateOutputRegister(); } // Detect the BlockNode([local $_, For1Node(needsArrayOfAlias)]) pattern produced @@ -792,19 +797,16 @@ public void visit(BlockNode node) { pcToTokenIndex.put(pc, tokenIndex); } - // Standalone statements (not assignments) use VOID context - int savedContext = currentCallContext; - - // If this is not an assignment or other value-using construct, use VOID context - // EXCEPT for the last statement in a block, which should use the block's context boolean isLastStatement = (i == lastMeaningfulIndex); + int stmtTarget = (isLastStatement && outerResultReg >= 0) ? outerResultReg : -1; + int stmtContext; if (!isLastStatement && !(stmt instanceof BinaryOperatorNode && ((BinaryOperatorNode) stmt).operator.equals("="))) { - currentCallContext = RuntimeContextType.VOID; + stmtContext = RuntimeContextType.VOID; + } else { + stmtContext = currentCallContext; } - stmt.accept(this); - - currentCallContext = savedContext; + compileNode(stmt, stmtTarget, stmtContext); // Recycle temporary registers after each statement // enterScope() protects registers allocated before entering a scope @@ -813,9 +815,7 @@ public void visit(BlockNode node) { // Save the last statement's result to the outer register BEFORE exiting scope if (outerResultReg >= 0 && lastResultReg >= 0) { - emit(Opcodes.ALIAS); - emitReg(outerResultReg); - emitReg(lastResultReg); + emitAliasWithTarget(outerResultReg, lastResultReg); } if (regexSaveReg >= 0) { @@ -841,8 +841,7 @@ public void visit(BlockNode node) { @Override public void visit(NumberNode node) { - // Handle number literals with proper Perl semantics - int rd = allocateRegister(); + int rd = allocateOutputRegister(); // Remove underscores which Perl allows as digit separators (e.g., 10_000_000) String value = node.value.replace("_", ""); @@ -955,7 +954,7 @@ void handleArrayKeyValueSlice(BinaryOperatorNode node, OperatorNode leftOp) { } if (currentCallContext == RuntimeContextType.SCALAR) { - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.LIST_TO_SCALAR); emitReg(rd); emitReg(listReg); @@ -967,7 +966,7 @@ void handleArrayKeyValueSlice(BinaryOperatorNode node, OperatorNode leftOp) { @Override public void visit(StringNode node) { - int rd = allocateRegister(); + int rd = allocateOutputRegister(); int strIndex = addToStringPool(node.value); short opcode; @@ -1030,7 +1029,7 @@ public void visit(IdentifierNode node) { throwCompilerException("Bareword \"" + varName + "\" not allowed while \"strict subs\" in use"); } // Not strict - treat bareword as string literal - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.LOAD_STRING); emitReg(rd); int strIdx = addToStringPool(varName); @@ -1048,6 +1047,9 @@ public void visit(IdentifierNode node) { // Strip sigil and normalize name (e.g., "$x" → "main::x") String bareVarName = varName.substring(1); // Remove sigil String normalizedName = NameNormalizer.normalizeVariableName(bareVarName, getCurrentPackage()); + // Use allocateRegister() instead of allocateOutputRegister() because + // LOAD_GLOBAL_SCALAR for special variables like $1 returns a proxy object. + // The ALIAS operation is needed to copy the value before RESTORE_REGEX_STATE. int rd = allocateRegister(); int nameIdx = addToStringPool(normalizedName); @@ -1114,7 +1116,7 @@ void handleArrayElementAccess(BinaryOperatorNode node, OperatorNode leftOp) { int indexReg = lastResultReg; // Emit ARRAY_GET opcode - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.ARRAY_GET); emitReg(rd); emitReg(arrayReg); @@ -1219,7 +1221,7 @@ void handleArraySlice(BinaryOperatorNode node, OperatorNode leftOp) { } // Emit ARRAY_SLICE opcode - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.ARRAY_SLICE); emitReg(rd); emitReg(arrayReg); @@ -1289,7 +1291,7 @@ void handleHashElementAccess(BinaryOperatorNode node, OperatorNode leftOp) { emit(keyIdx); // Emit HASH_GET opcode - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.HASH_GET); emitReg(rd); emitReg(hashReg); @@ -1302,7 +1304,7 @@ void handleHashElementAccess(BinaryOperatorNode node, OperatorNode leftOp) { int keyReg = lastResultReg; // Emit HASH_GET opcode - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.HASH_GET); emitReg(rd); emitReg(hashReg); @@ -1500,7 +1502,7 @@ void handleHashKeyValueSlice(BinaryOperatorNode node, OperatorNode leftOp) { emitReg(keyReg); } - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.HASH_KEYVALUE_SLICE); emitReg(rd); emitReg(hashReg); @@ -1524,9 +1526,7 @@ void handleCompoundAssignment(BinaryOperatorNode node) { String op = node.operator; // Compile the right operand first (the value to add/subtract/etc.) - int savedContext = currentCallContext; - currentCallContext = RuntimeContextType.SCALAR; - node.right.accept(this); + compileNode(node.right, -1, RuntimeContextType.SCALAR); int valueReg = lastResultReg; // Get the left operand register (the variable or expression being assigned to) @@ -1557,26 +1557,14 @@ void handleCompoundAssignment(BinaryOperatorNode node) { } } else { // Other operator (not simple variable) - compile as expression in SCALAR context - node.left.accept(this); + compileNode(node.left, -1, RuntimeContextType.SCALAR); targetReg = lastResultReg; } } else { // Not an OperatorNode (could be BinaryOperatorNode like ($x &= $y)) // Compile the left side as an expression in SCALAR context - node.left.accept(this); + compileNode(node.left, -1, RuntimeContextType.SCALAR); targetReg = lastResultReg; - - // Convert to scalar if it's a list - if (!(lastResultReg == targetReg)) { - // Already handled - } else { - // May need to convert list to scalar - int scalarReg = allocateRegister(); - emit(Opcodes.LIST_TO_COUNT); - emitReg(scalarReg); - emitReg(targetReg); - targetReg = scalarReg; - } } // Emit the appropriate compound assignment opcode @@ -1602,7 +1590,6 @@ void handleCompoundAssignment(BinaryOperatorNode node) { case "//=" -> emit(Opcodes.DEFINED_OR_ASSIGN); // Defined-or default -> { throwCompilerException("Unknown compound assignment operator: " + op); - currentCallContext = savedContext; return; } } @@ -1625,7 +1612,6 @@ void handleCompoundAssignment(BinaryOperatorNode node) { // The result is stored in targetReg lastResultReg = targetReg; - currentCallContext = savedContext; } /** @@ -1636,10 +1622,7 @@ void handleGeneralArrayAccess(BinaryOperatorNode node) { // Compile the left side (the expression that should yield an array or arrayref) // Force LIST context so comma expressions like (0,0,1,1) create a list, // not just return the last value (which happens in scalar context) - int savedContext = currentCallContext; - currentCallContext = RuntimeContextType.LIST; - node.left.accept(this); - currentCallContext = savedContext; + compileNode(node.left, -1, RuntimeContextType.LIST); int baseReg = lastResultReg; // Compile the index expression (right side) @@ -1679,7 +1662,7 @@ void handleGeneralArrayAccess(BinaryOperatorNode node) { } // Now get the element - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.ARRAY_GET); emitReg(rd); emitReg(arrayReg); @@ -1738,7 +1721,7 @@ void handleGeneralHashAccess(BinaryOperatorNode node) { if (isGlobSlotAccess) { // For glob slot access, call hashDerefGetNonStrict directly // This uses RuntimeGlob's override which accesses the slot without dereferencing - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.GLOB_SLOT_GET); emitReg(rd); emitReg(baseReg); @@ -1766,7 +1749,7 @@ void handleGeneralHashAccess(BinaryOperatorNode node) { } // Now get the element - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.HASH_GET); emitReg(rd); emitReg(hashReg); @@ -1842,11 +1825,8 @@ void handlePushUnshift(BinaryOperatorNode node) { } // Compile the values to push/unshift (right operand) in list context - int savedContext = currentCallContext; - currentCallContext = RuntimeContextType.LIST; - node.right.accept(this); + compileNode(node.right, -1, RuntimeContextType.LIST); int valuesReg = lastResultReg; - currentCallContext = savedContext; // Emit ARRAY_PUSH or ARRAY_UNSHIFT opcode if (isPush) { @@ -1858,7 +1838,7 @@ void handlePushUnshift(BinaryOperatorNode node) { emitReg(valuesReg); // push/unshift return the new array size in scalar context - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.ARRAY_SIZE); emitReg(rd); emitReg(arrayReg); @@ -2806,7 +2786,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { String globalVarName = NameNormalizer.normalizeVariableName(((IdentifierNode) sigilOp.operand).name, getCurrentPackage()); int nameIdx = addToStringPool(globalVarName); - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emitWithToken(Opcodes.LOCAL_SCALAR, node.getIndex()); emitReg(rd); emit(nameIdx); @@ -2839,7 +2819,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { String globalVarName = NameNormalizer.normalizeVariableName(idNode.name, getCurrentPackage()); int nameIdx = addToStringPool(globalVarName); - int rd = allocateRegister(); + int rd = allocateOutputRegister(); if (sigil.equals("@")) { emitWithToken(Opcodes.LOCAL_ARRAY, node.getIndex()); } else { @@ -2887,7 +2867,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { String globalVarName = NameNormalizer.normalizeVariableName(idNode.name, getCurrentPackage()); int nameIdx = addToStringPool(globalVarName); - int rd = allocateRegister(); + int rd = allocateOutputRegister(); if (originalSigil.equals("$")) { emitWithToken(Opcodes.LOCAL_SCALAR, node.getIndex()); emitReg(rd); @@ -2937,7 +2917,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { int nameIdx = addToStringPool(globalVarName); int ourReg = hasVariable(varName) ? getVariableRegister(varName) : addVariable(varName, "our"); - int rd = allocateRegister(); + int rd = allocateOutputRegister(); switch (innerSigil) { case "$" -> { emit(Opcodes.LOAD_GLOBAL_SCALAR); @@ -3005,7 +2985,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { String globalVarName = NameNormalizer.normalizeVariableName(idNode.name, getCurrentPackage()); int nameIdx = addToStringPool(globalVarName); - int rd = allocateRegister(); + int rd = allocateOutputRegister(); if (varNode.operator.equals("$")) { emitWithToken(Opcodes.LOCAL_SCALAR, node.getIndex()); emitReg(rd); @@ -3055,7 +3035,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { String globalVarName = NameNormalizer.normalizeVariableName(idNode.name, getCurrentPackage()); int nameIdx = addToStringPool(globalVarName); - int rd = allocateRegister(); + int rd = allocateOutputRegister(); if (nestedVarNode.operator.equals("$")) { emitWithToken(Opcodes.LOCAL_SCALAR, node.getIndex()); emitReg(rd); @@ -3087,7 +3067,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { String globalVarName = NameNormalizer.normalizeVariableName(idNode.name, getCurrentPackage()); int nameIdx = addToStringPool(globalVarName); - int rd = allocateRegister(); + int rd = allocateOutputRegister(); if (originalSigil.equals("$")) { emitWithToken(Opcodes.LOCAL_SCALAR, node.getIndex()); emitReg(rd); @@ -3111,7 +3091,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { if (sigil.equals("*")) { String globalName = NameNormalizer.normalizeVariableName(idNode.name, getCurrentPackage()); int nameIdx = addToStringPool(globalName); - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.LOCAL_GLOB); emitReg(rd); emit(nameIdx); @@ -3123,7 +3103,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { } String globalVarName = NameNormalizer.normalizeVariableName(idNode.name, getCurrentPackage()); int nameIdx = addToStringPool(globalVarName); - int rd = allocateRegister(); + int rd = allocateOutputRegister(); if (sigil.equals("$")) { emitWithToken(Opcodes.LOCAL_SCALAR, node.getIndex()); } else if (sigil.equals("@")) { @@ -3179,13 +3159,23 @@ void compileVariableDeclaration(OperatorNode node, String op) { && sigilOp2.operand instanceof IdentifierNode idNode2) { String globalName = NameNormalizer.normalizeVariableName(idNode2.name, getCurrentPackage()); int nameIdx = addToStringPool(globalName); - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.LOCAL_GLOB); emitReg(rd); emit(nameIdx); lastResultReg = rd; return; } + // local $hash{key} or local $array[index] - localize a hash/array element + if (node.operand instanceof BinaryOperatorNode binOp + && (binOp.operator.equals("{") || binOp.operator.equals("["))) { + compileNode(binOp, -1, RuntimeContextType.SCALAR); + int elemReg = lastResultReg; + emit(Opcodes.PUSH_LOCAL_VARIABLE); + emitReg(elemReg); + lastResultReg = elemReg; + return; + } throwCompilerException("Unsupported local operand: " + node.operand.getClass().getSimpleName()); } throwCompilerException("Unsupported variable declaration operator: " + op); @@ -3200,7 +3190,7 @@ void compileVariableReference(OperatorNode node, String op) { // Check if this is a closure variable captured from outer scope via PersistentVariable if (currentSubroutineBeginId != 0 && currentSubroutineClosureVars.contains(varName)) { // This is a closure variable - use RETRIEVE_BEGIN_SCALAR - int rd = allocateRegister(); + int rd = allocateOutputRegister(); int nameIdx = addToStringPool(varName); emitWithToken(Opcodes.RETRIEVE_BEGIN_SCALAR, node.getIndex()); @@ -3226,6 +3216,9 @@ void compileVariableReference(OperatorNode node, String op) { getCurrentPackage() ); + // Use allocateRegister() instead of allocateOutputRegister() because + // LOAD_GLOBAL_SCALAR for special variables like $1 returns a proxy object. + // The ALIAS operation is needed to copy the value before RESTORE_REGEX_STATE. int rd = allocateRegister(); int nameIdx = addToStringPool(globalVarName); @@ -3240,12 +3233,9 @@ void compileVariableReference(OperatorNode node, String op) { // Execute the block to get a variable name string, then load that variable // Check strict refs at compile time — mirrors JVM path in EmitVariable.java - int savedCtx = currentCallContext; - currentCallContext = RuntimeContextType.SCALAR; - block.accept(this); - currentCallContext = savedCtx; + compileNode(block, -1, RuntimeContextType.SCALAR); int blockResultReg = lastResultReg; - int rd = allocateRegister(); + int rd = allocateOutputRegister(); if (isStrictRefsEnabled()) { // strict refs: scalarDeref() — throws for non-refs emitWithToken(Opcodes.DEREF_SCALAR_STRICT, node.getIndex()); @@ -3268,7 +3258,7 @@ void compileVariableReference(OperatorNode node, String op) { // Dereference the result // Use strict/non-strict variants to match JVM behavior and strict refs semantics. - int rd = allocateRegister(); + int rd = allocateOutputRegister(); if (isStrictRefsEnabled()) { emitWithToken(Opcodes.DEREF_SCALAR_STRICT, node.getIndex()); emitReg(rd); @@ -3295,7 +3285,7 @@ void compileVariableReference(OperatorNode node, String op) { // Check if we're in scalar context - if so, return array size if (currentCallContext == RuntimeContextType.SCALAR) { - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.ARRAY_SIZE); emitReg(rd); emitReg(arrayReg); @@ -3333,7 +3323,7 @@ void compileVariableReference(OperatorNode node, String op) { // Check if we're in scalar context - if so, return array size if (currentCallContext == RuntimeContextType.SCALAR) { - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.ARRAY_SIZE); emitReg(rd); emitReg(arrayReg); @@ -3351,7 +3341,7 @@ void compileVariableReference(OperatorNode node, String op) { // Dereference to get the array // The reference should contain a RuntimeArray // For @$scalar, we need to dereference it - int rd = allocateRegister(); + int rd = allocateOutputRegister(); if (isStrictRefsEnabled()) { emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); emitReg(rd); @@ -3371,14 +3361,11 @@ void compileVariableReference(OperatorNode node, String op) { } else if (node.operand instanceof BlockNode blockNode) { // @{ block } - evaluate block and dereference the result // The block should return an arrayref - int savedCtx = currentCallContext; - currentCallContext = RuntimeContextType.SCALAR; - blockNode.accept(this); - currentCallContext = savedCtx; + compileNode(blockNode, -1, RuntimeContextType.SCALAR); int refReg = lastResultReg; // Dereference to get the array - int rd = allocateRegister(); + int rd = allocateOutputRegister(); if (isStrictRefsEnabled()) { emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); emitReg(rd); @@ -3396,7 +3383,7 @@ void compileVariableReference(OperatorNode node, String op) { // Symbolic ref: @{'name'} or 'name'->@* — load global array by string name String globalName = NameNormalizer.normalizeVariableName(strNode.value, getCurrentPackage()); int nameIdx = addToStringPool(globalName); - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.LOAD_GLOBAL_ARRAY); emitReg(rd); emit(nameIdx); @@ -3432,7 +3419,7 @@ void compileVariableReference(OperatorNode node, String op) { // Check if we're in scalar context - if so, return hash as scalar (bucket info) if (currentCallContext == RuntimeContextType.SCALAR) { - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.ARRAY_SIZE); // Works for hashes too - calls .scalar() emitReg(rd); emitReg(hashReg); @@ -3457,7 +3444,7 @@ void compileVariableReference(OperatorNode node, String op) { emit(pkgIdx); } if (currentCallContext == RuntimeContextType.SCALAR) { - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.ARRAY_SIZE); emitReg(rd); emitReg(hashReg); @@ -3467,10 +3454,7 @@ void compileVariableReference(OperatorNode node, String op) { } } else if (node.operand instanceof BlockNode blockNode) { // %{ block } — evaluate block and dereference to hash - int savedCtx = currentCallContext; - currentCallContext = RuntimeContextType.SCALAR; - blockNode.accept(this); - currentCallContext = savedCtx; + compileNode(blockNode, -1, RuntimeContextType.SCALAR); int scalarReg = lastResultReg; int hashReg = allocateRegister(); if (isStrictRefsEnabled()) { @@ -3508,7 +3492,7 @@ void compileVariableReference(OperatorNode node, String op) { } // Allocate register for glob - int rd = allocateRegister(); + int rd = allocateOutputRegister(); int nameIdx = addToStringPool(varName); // Emit direct opcode LOAD_GLOB @@ -3523,7 +3507,7 @@ void compileVariableReference(OperatorNode node, String op) { if (!varName.contains("::")) { varName = getCurrentPackage() + "::" + varName; } - int rd = allocateRegister(); + int rd = allocateOutputRegister(); int nameIdx = addToStringPool(varName); emitWithToken(Opcodes.LOAD_GLOB, node.getIndex()); emitReg(rd); @@ -3532,7 +3516,7 @@ void compileVariableReference(OperatorNode node, String op) { } else if (node.operand instanceof OperatorNode) { node.operand.accept(this); int refReg = lastResultReg; - int rd = allocateRegister(); + int rd = allocateOutputRegister(); int pkgIdx = addToStringPool(getCurrentPackage()); if (isStrictRefsEnabled()) { emitWithToken(Opcodes.DEREF_GLOB, node.getIndex()); @@ -3557,7 +3541,7 @@ void compileVariableReference(OperatorNode node, String op) { subName = NameNormalizer.normalizeVariableName(subName, getCurrentPackage()); // Allocate register for code reference - int rd = allocateRegister(); + int rd = allocateOutputRegister(); int nameIdx = addToStringPool(subName); // Emit LOAD_GLOBAL_CODE @@ -3585,24 +3569,18 @@ void compileVariableReference(OperatorNode node, String op) { // Compile operand in LIST context to get the actual value // Example: \@array should get a reference to the array itself, // not its size (which would happen in SCALAR context) - int savedContext = currentCallContext; - currentCallContext = RuntimeContextType.LIST; - try { - node.operand.accept(this); - int valueReg = lastResultReg; + compileNode(node.operand, -1, RuntimeContextType.LIST); + int valueReg = lastResultReg; - // Allocate register for reference - int rd = allocateRegister(); + // Allocate register for reference + int rd = allocateOutputRegister(); - // Emit CREATE_REF - emit(Opcodes.CREATE_REF); - emitReg(rd); - emitReg(valueReg); + // Emit CREATE_REF + emit(Opcodes.CREATE_REF); + emitReg(rd); + emitReg(valueReg); - lastResultReg = rd; - } finally { - currentCallContext = savedContext; - } + lastResultReg = rd; } else { throwCompilerException("Reference operator requires operand"); } @@ -3627,6 +3605,33 @@ int allocateRegister() { return reg; } + int allocateOutputRegister() { + if (targetOutputReg >= 0) { + int reg = targetOutputReg; + targetOutputReg = -1; + return reg; + } + return allocateRegister(); + } + + void emitAliasWithTarget(int destReg, int srcReg) { + if (destReg >= 0 && srcReg >= 0 && destReg != srcReg) { + emit(Opcodes.ALIAS); + emitReg(destReg); + emitReg(srcReg); + } + } + + void compileNode(Node node, int targetReg, int callContext) { + int savedTarget = targetOutputReg; + int savedContext = currentCallContext; + targetOutputReg = targetReg; + currentCallContext = callContext; + node.accept(this); + targetOutputReg = savedTarget; + currentCallContext = savedContext; + } + // ========================================================================= // HELPER METHODS // ========================================================================= @@ -4026,6 +4031,7 @@ private void visitNamedSubroutine(SubroutineNode node) { if (closureVarIndices.isEmpty()) { RuntimeScalar codeScalar = new RuntimeScalar(subCode); + subCode.__SUB__ = codeScalar; // Set __SUB__ for self-reference int constIdx = addToConstantPool(codeScalar); emit(Opcodes.LOAD_CONST); emitReg(codeReg); @@ -4115,6 +4121,7 @@ private void visitAnonymousSubroutine(SubroutineNode node) { if (closureVarIndices.isEmpty()) { // No closures - just wrap the InterpretedCode RuntimeScalar codeScalar = new RuntimeScalar(subCode); + subCode.__SUB__ = codeScalar; // Set __SUB__ for self-reference int constIdx = addToConstantPool(codeScalar); emit(Opcodes.LOAD_CONST); emitReg(codeReg); @@ -4166,13 +4173,10 @@ private void visitEvalBlock(SubroutineNode node) { emitInt(0); // Placeholder for absolute catch address (4 bytes) // Compile the eval block body - node.block.accept(this); + compileNode(node.block, resultReg, currentCallContext); - // Store result from block if (lastResultReg >= 0) { - emit(Opcodes.ALIAS); - emitReg(resultReg); - emitReg(lastResultReg); + emitAliasWithTarget(resultReg, lastResultReg); } // Emit EVAL_END (clears $@) @@ -4398,7 +4402,7 @@ public void visit(For3Node node) { // so the value is accessible after the scope exits (same pattern as BlockNode). int outerResultReg = -1; if (currentCallContext != RuntimeContextType.VOID) { - outerResultReg = allocateRegister(); + outerResultReg = allocateOutputRegister(); } int labelIdx = -1; @@ -4413,15 +4417,11 @@ public void visit(For3Node node) { enterScope(); try { - // Just execute the body once, no loop if (node.body != null) { - node.body.accept(this); + compileNode(node.body, outerResultReg, currentCallContext); } - // Save last statement result into outer register before exiting scope if (outerResultReg >= 0 && lastResultReg >= 0) { - emit(Opcodes.ALIAS); - emitReg(outerResultReg); - emitReg(lastResultReg); + emitAliasWithTarget(outerResultReg, lastResultReg); } } finally { // Exit scope to clean up lexical variables @@ -4482,10 +4482,7 @@ public void visit(For3Node node) { int condReg = allocateRegister(); if (node.condition != null) { // Evaluate condition in SCALAR context (need boolean result) - int savedContext = currentCallContext; - currentCallContext = RuntimeContextType.SCALAR; - node.condition.accept(this); - currentCallContext = savedContext; + compileNode(node.condition, -1, RuntimeContextType.SCALAR); condReg = lastResultReg; } else { // No condition means infinite loop - load true @@ -4505,10 +4502,7 @@ public void visit(For3Node node) { int condReg = allocateRegister(); if (node.condition != null) { // Evaluate condition in SCALAR context (need boolean result) - int savedContext = currentCallContext; - currentCallContext = RuntimeContextType.SCALAR; - node.condition.accept(this); - currentCallContext = savedContext; + compileNode(node.condition, -1, RuntimeContextType.SCALAR); condReg = lastResultReg; } else { // No condition means infinite loop - load true @@ -4576,11 +4570,7 @@ public void visit(For3Node node) { @Override public void visit(IfNode node) { - // Compile condition in SCALAR context (need boolean value) - int savedContext = currentCallContext; - currentCallContext = RuntimeContextType.SCALAR; - node.condition.accept(this); - currentCallContext = savedContext; + compileNode(node.condition, -1, RuntimeContextType.SCALAR); int condReg = lastResultReg; // Mark position for forward jump to else/end @@ -4611,15 +4601,11 @@ public void visit(IfNode node) { patchIntOffset(ifFalsePos + 2, elseStart); // Compile else block - node.elseBranch.accept(this); + compileNode(node.elseBranch, thenResultReg, currentCallContext); 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.ALIAS); - emitReg(thenResultReg); - emitReg(elseResultReg); + if (thenResultReg >= 0 && elseResultReg >= 0) { + emitAliasWithTarget(thenResultReg, elseResultReg); } // Patch goto-end jump to here @@ -4648,48 +4634,28 @@ public void visit(TernaryOperatorNode node) { // rd = false_expr // end_label: - // Compile condition in scalar context (need boolean value) - int savedContext = currentCallContext; - currentCallContext = RuntimeContextType.SCALAR; - node.condition.accept(this); - int condReg = lastResultReg; - currentCallContext = savedContext; + int rd = allocateOutputRegister(); - // Allocate result register - int rd = allocateRegister(); + compileNode(node.condition, -1, RuntimeContextType.SCALAR); + int condReg = lastResultReg; - // Mark position for forward jump to false expression int ifFalsePos = bytecode.size(); emit(Opcodes.GOTO_IF_FALSE); emitReg(condReg); - emitInt(0); // Placeholder for false_label + emitInt(0); - // Compile true expression - node.trueExpr.accept(this); - int trueReg = lastResultReg; + compileNode(node.trueExpr, rd, currentCallContext); + emitAliasWithTarget(rd, lastResultReg); - // Move true result to rd - emit(Opcodes.ALIAS); - emitReg(rd); - emitReg(trueReg); - - // Jump over false expression int gotoEndPos = bytecode.size(); emit(Opcodes.GOTO); - emitInt(0); // Placeholder for end_label + emitInt(0); - // 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.ALIAS); - emitReg(rd); - emitReg(falseReg); + compileNode(node.falseExpr, rd, currentCallContext); + emitAliasWithTarget(rd, lastResultReg); // Patch goto-end jump to here int endPos = bytecode.size(); @@ -4750,7 +4716,7 @@ public void visit(ListNode node) { if (node.elements.isEmpty()) { // In SCALAR context, return undef; in LIST context, return empty list if (currentCallContext == RuntimeContextType.SCALAR) { - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(Opcodes.LOAD_UNDEF); emitReg(rd); lastResultReg = rd; @@ -4782,51 +4748,38 @@ public void visit(ListNode node) { // In list context, returns a RuntimeList with one element // List elements should be evaluated in LIST context if (node.elements.size() == 1) { - int savedContext = currentCallContext; - currentCallContext = RuntimeContextType.LIST; - try { - node.elements.get(0).accept(this); - int elemReg = lastResultReg; + compileNode(node.elements.get(0), -1, RuntimeContextType.LIST); + int elemReg = lastResultReg; - int listReg = allocateRegister(); - emit(Opcodes.CREATE_LIST); - emitReg(listReg); - emit(1); // count = 1 - emitReg(elemReg); - lastResultReg = listReg; - } finally { - currentCallContext = savedContext; - } + int listReg = allocateRegister(); + emit(Opcodes.CREATE_LIST); + emitReg(listReg); + emit(1); // count = 1 + emitReg(elemReg); + lastResultReg = listReg; return; } // General case: multiple elements in LIST context // Evaluate each element into a register - // List elements should be evaluated in LIST context - int savedContext = currentCallContext; - currentCallContext = RuntimeContextType.LIST; - try { - 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); - emitReg(listReg); - emit(node.elements.size()); // count + int[] elementRegs = new int[node.elements.size()]; + for (int i = 0; i < node.elements.size(); i++) { + compileNode(node.elements.get(i), -1, RuntimeContextType.LIST); + elementRegs[i] = lastResultReg; + } - // Emit register numbers for each element - for (int elemReg : elementRegs) { - emitReg(elemReg); - } + // Create RuntimeList with all elements + int listReg = allocateRegister(); + emit(Opcodes.CREATE_LIST); + emitReg(listReg); + emit(node.elements.size()); // count - lastResultReg = listReg; - } finally { - currentCallContext = savedContext; + // Emit register numbers for each element + for (int elemReg : elementRegs) { + emitReg(elemReg); } + + lastResultReg = listReg; } /** @@ -4909,7 +4862,7 @@ void handleLoopControlOperator(OperatorNode node, String op) { short createOp = op.equals("last") ? Opcodes.CREATE_LAST : op.equals("next") ? Opcodes.CREATE_NEXT : Opcodes.CREATE_REDO; - int rd = allocateRegister(); + int rd = allocateOutputRegister(); emit(createOp); emitReg(rd); int labelIdx = labelStr != null ? addToStringPool(labelStr) : 255; diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index e678164dc..dca2ce741 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -429,7 +429,12 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c int rd = bytecode[pc++]; int nameIdx = bytecode[pc++]; String name = code.stringPool[nameIdx]; - registers[rd] = GlobalVariable.getGlobalCodeRef(name); + if (name.equals("__SUB__")) { + // __SUB__ returns the current subroutine being executed + registers[rd] = RuntimeCode.selfReferenceMaybeNull(code.__SUB__); + } else { + registers[rd] = GlobalVariable.getGlobalCodeRef(name); + } } case Opcodes.STORE_GLOBAL_CODE -> { diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index f701df327..b0e8b090a 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -34,9 +34,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } // Set the context for subroutine calls in RHS - int savedContext = bytecodeCompiler.currentCallContext; - try { - bytecodeCompiler.currentCallContext = rhsContext; + int outerContext = bytecodeCompiler.currentCallContext; // Special case: my $x = value if (node.left instanceof OperatorNode leftOp) { @@ -62,7 +60,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, // Now register contains a reference to the persistent RuntimeScalar // Store the initializer value INTO that RuntimeScalar - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; // Set the value in the persistent scalar using SET_SCALAR @@ -79,7 +77,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, // Regular lexical variable (not captured) // Compile RHS first, before adding variable to scope, // so that `my $x = $x` reads the outer $x on the RHS - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; // Now allocate register for new lexical variable and add to symbol table @@ -107,7 +105,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emit(beginId); // Compile RHS (should evaluate to a list) - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int listReg = bytecodeCompiler.lastResultReg; // Populate array from list @@ -117,9 +115,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.registerVariable(varName, arrayReg); - // In scalar context, return the count of elements assigned - // In list/void context, return the array - if (bytecodeCompiler.currentCallContext == RuntimeContextType.SCALAR) { + if (rhsContext == RuntimeContextType.SCALAR) { int countReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); bytecodeCompiler.emitReg(countReg); @@ -131,28 +127,20 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, return; } - // Regular lexical array (not captured) - // Allocate register but don't add to scope yet, - // so that `my @a = @a` reads the outer @a on the RHS int arrayReg = bytecodeCompiler.allocateRegister(); - // Compile RHS first, before adding variable to scope - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int listReg = bytecodeCompiler.lastResultReg; - // Now add to symbol table and create array bytecodeCompiler.registerVariable(varName, arrayReg); bytecodeCompiler.emit(Opcodes.NEW_ARRAY); bytecodeCompiler.emitReg(arrayReg); - // Populate array from list using setFromList bytecodeCompiler.emit(Opcodes.ARRAY_SET_FROM_LIST); bytecodeCompiler.emitReg(arrayReg); bytecodeCompiler.emitReg(listReg); - // In scalar context, return the count of elements assigned - // In list/void context, return the array - if (bytecodeCompiler.currentCallContext == RuntimeContextType.SCALAR) { + if (rhsContext == RuntimeContextType.SCALAR) { int countReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); bytecodeCompiler.emitReg(countReg); @@ -178,7 +166,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emit(beginId); // Compile RHS (should evaluate to a list) - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int listReg = bytecodeCompiler.lastResultReg; // Populate hash from list @@ -197,7 +185,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, int hashReg = bytecodeCompiler.allocateRegister(); // Compile RHS first, before adding variable to scope - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int listReg = bytecodeCompiler.lastResultReg; // Now add to symbol table and create hash @@ -220,7 +208,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, String varName = ((IdentifierNode) myOperand).name; // Compile RHS first, before adding variable to scope - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; // Now allocate register and add to symbol table @@ -239,7 +227,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, if (myOperand instanceof ListNode listNode) { // Compile RHS first - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int listReg = bytecodeCompiler.lastResultReg; // Convert to list if needed @@ -338,7 +326,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, if (hashAccess.operator.equals("{")) { // Compile the hash access to get the hash element reference // This returns a RuntimeScalar that is aliased to the hash slot - hashAccess.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(hashAccess, -1, rhsContext); int elemReg = bytecodeCompiler.lastResultReg; // Push this hash element to the local variable stack @@ -346,7 +334,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(elemReg); // Compile RHS - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; // Assign value to the hash element (which is already localized) @@ -370,12 +358,12 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } // Compile RHS first - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; // It's a global variable - call makeLocal which returns the localized scalar - String packageName = bytecodeCompiler.getCurrentPackage(); - String globalVarName = packageName + "::" + ((IdentifierNode) sigilOp.operand).name; + String globalVarName = NameNormalizer.normalizeVariableName( + ((IdentifierNode) sigilOp.operand).name, bytecodeCompiler.getCurrentPackage()); int nameIdx = bytecodeCompiler.addToStringPool(globalVarName); int localReg = bytecodeCompiler.allocateRegister(); @@ -400,12 +388,12 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } // Compile RHS first - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; // It's a global array - get it and push to local stack - String packageName = bytecodeCompiler.getCurrentPackage(); - String globalVarName = packageName + "::" + ((IdentifierNode) sigilOp.operand).name; + String globalVarName = NameNormalizer.normalizeVariableName( + ((IdentifierNode) sigilOp.operand).name, bytecodeCompiler.getCurrentPackage()); int nameIdx = bytecodeCompiler.addToStringPool(globalVarName); int arrayReg = bytecodeCompiler.allocateRegister(); @@ -434,12 +422,12 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } // Compile RHS first - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; // It's a global hash - get it and push to local stack - String packageName = bytecodeCompiler.getCurrentPackage(); - String globalVarName = packageName + "::" + ((IdentifierNode) sigilOp.operand).name; + String globalVarName = NameNormalizer.normalizeVariableName( + ((IdentifierNode) sigilOp.operand).name, bytecodeCompiler.getCurrentPackage()); int nameIdx = bytecodeCompiler.addToStringPool(globalVarName); int hashReg = bytecodeCompiler.allocateRegister(); @@ -460,7 +448,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, return; } else if (sigilOp.operator.equals("*") && sigilOp.operand instanceof IdentifierNode) { // Handle local *glob = value - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; String globalName = NameNormalizer.normalizeVariableName( @@ -488,7 +476,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, int ourReg = bytecodeCompiler.hasVariable(varName) ? bytecodeCompiler.getVariableRegister(varName) : bytecodeCompiler.addVariable(varName, "our"); - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; switch (innerSigil) { @@ -558,7 +546,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } // Compile RHS first - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; // Get the global variable and localize it @@ -584,7 +572,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, // Multi-element case: local($x, $y) = (v1, v2) // Compile RHS first - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; // For each element in the list, localize and assign @@ -657,7 +645,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, int targetReg = bytecodeCompiler.getVariableRegister(leftVarName); // Compile RHS operand ($y) - rightBin.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(rightBin.right, -1, rhsContext); int rhsReg = bytecodeCompiler.lastResultReg; // Emit ADD_ASSIGN instead of ADD_SCALAR + ALIAS @@ -680,7 +668,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, if (leftOp.operand instanceof BlockNode block) { // ${block} = value — mirrors JVM EmitVariable.java case "$" - block.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(block, -1, rhsContext); int nameReg = bytecodeCompiler.lastResultReg; // Deref to get lvalue target (strict or non-strict) @@ -698,7 +686,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } // Now compile the RHS and assign - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; bytecodeCompiler.emit(Opcodes.SET_SCALAR); bytecodeCompiler.emitReg(derefReg); @@ -708,7 +696,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, return; } else if (leftOp.operand instanceof OperatorNode) { // $$var = value — mirrors JVM EmitVariable.java case "$" - leftOp.operand.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(leftOp.operand, -1, rhsContext); int nameReg = bytecodeCompiler.lastResultReg; int derefReg = bytecodeCompiler.allocateRegister(); @@ -724,7 +712,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emit(pkgIdx); } - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; bytecodeCompiler.emit(Opcodes.SET_SCALAR); bytecodeCompiler.emitReg(derefReg); @@ -737,7 +725,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, // Regular assignment: $x = value (no optimization) // Compile RHS first - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valueReg = bytecodeCompiler.lastResultReg; // Assign to LHS @@ -824,7 +812,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(valueReg); // In scalar context, return the array size; in list context, return the array - if (savedContext == RuntimeContextType.SCALAR) { + if (outerContext == RuntimeContextType.SCALAR) { // Convert array to scalar (returns size) int sizeReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); @@ -864,7 +852,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(valueReg); // In scalar context, return the hash size; in list context, return the hash - if (savedContext == RuntimeContextType.SCALAR) { + if (outerContext == RuntimeContextType.SCALAR) { // Convert hash to scalar (returns bucket info like "3/8") int sizeReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); @@ -877,7 +865,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } 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) - leftOp.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(leftOp, -1, rhsContext); int targetReg = bytecodeCompiler.lastResultReg; // Now assign the RHS value to the target register @@ -943,7 +931,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(rhsListReg); bytecodeCompiler.lastResultReg = resultReg; - bytecodeCompiler.currentCallContext = savedContext; + return; } @@ -969,7 +957,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } else if (leftOp.operator.equals("*") && leftOp.operand instanceof BlockNode) { // Symbolic typeglob assignment: *{"name"} = value (no strict refs) // Evaluate the block to get the glob name as a scalar, then load glob by name - leftOp.operand.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(leftOp.operand, -1, rhsContext); int nameScalarReg = bytecodeCompiler.lastResultReg; int globReg = bytecodeCompiler.allocateRegister(); @@ -989,7 +977,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, // Glob assignment where the glob comes from an expression, e.g. $ref->** = ... // or 'name'->** = ... // Compile the glob expression to obtain the RuntimeGlob, then store through it. - leftOp.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(leftOp, -1, rhsContext); int globReg = bytecodeCompiler.lastResultReg; bytecodeCompiler.emit(Opcodes.STORE_GLOB); @@ -1000,7 +988,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } else if (leftOp.operator.equals("pos")) { // pos($var) = value - lvalue assignment to regex position // pos() returns a PosLvalueScalar that can be assigned to - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, rhsContext); int lvalueReg = bytecodeCompiler.lastResultReg; // Use SET_SCALAR to assign through the lvalue @@ -1010,7 +998,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.lastResultReg = valueReg; } else if (leftOp.operator.equals("substr")) { - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, rhsContext); int lvalueReg = bytecodeCompiler.lastResultReg; bytecodeCompiler.emit(Opcodes.SET_SCALAR); @@ -1024,7 +1012,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, if (derefOp.operator.equals("$")) { // Compile the scalar to get the array reference - derefOp.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(derefOp, -1, rhsContext); int scalarRefReg = bytecodeCompiler.lastResultReg; // Dereference to get the actual array @@ -1047,7 +1035,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(valueReg); // In scalar context, return array size; in list context, return the array - if (savedContext == RuntimeContextType.SCALAR) { + if (outerContext == RuntimeContextType.SCALAR) { int sizeReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); bytecodeCompiler.emitReg(sizeReg); @@ -1153,7 +1141,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, ArrayLiteralNode indicesNode = (ArrayLiteralNode) leftBin.right; List indexRegs = new ArrayList<>(); for (Node indexNode : indicesNode.elements) { - indexNode.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(indexNode, -1, rhsContext); indexRegs.add(bytecodeCompiler.lastResultReg); } @@ -1166,18 +1154,14 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(indexReg); } - // Compile values (RHS of assignment) - node.right.accept(bytecodeCompiler); - int valuesReg = bytecodeCompiler.lastResultReg; - - // Emit direct opcode ARRAY_SLICE_SET + // Emit direct opcode ARRAY_SLICE_SET (use valueReg from line 729) bytecodeCompiler.emit(Opcodes.ARRAY_SLICE_SET); bytecodeCompiler.emitReg(arrayReg); bytecodeCompiler.emitReg(indicesReg); - bytecodeCompiler.emitReg(valuesReg); + bytecodeCompiler.emitReg(valueReg); bytecodeCompiler.lastResultReg = arrayReg; - bytecodeCompiler.currentCallContext = savedContext; + return; } } @@ -1223,7 +1207,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } else if (leftBin.left instanceof BinaryOperatorNode) { // Multidimensional case: $matrix[3][0] = value // Compile left side (which returns a scalar containing an array reference) - leftBin.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(leftBin.left, -1, rhsContext); int scalarReg = bytecodeCompiler.lastResultReg; // Dereference the array reference to get the actual array @@ -1253,21 +1237,17 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.throwCompilerException("Array assignment requires index expression"); } - indexNode.elements.get(0).accept(bytecodeCompiler); + bytecodeCompiler.compileNode(indexNode.elements.get(0), -1, rhsContext); int indexReg = bytecodeCompiler.lastResultReg; - // Compile RHS value - node.right.accept(bytecodeCompiler); - int assignValueReg = bytecodeCompiler.lastResultReg; - - // Emit ARRAY_SET + // Emit ARRAY_SET (use valueReg from line 729) bytecodeCompiler.emit(Opcodes.ARRAY_SET); bytecodeCompiler.emitReg(arrayReg); bytecodeCompiler.emitReg(indexReg); - bytecodeCompiler.emitReg(assignValueReg); + bytecodeCompiler.emitReg(valueReg); - bytecodeCompiler.lastResultReg = assignValueReg; - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.lastResultReg = valueReg; + return; } else if (leftBin.operator.equals("{")) { // Hash element/slice assignment @@ -1306,7 +1286,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emit(nameIdx); } } else if (hashOp.operand instanceof OperatorNode) { - hashOp.operand.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(hashOp.operand, -1, rhsContext); int scalarRefReg = bytecodeCompiler.lastResultReg; hashReg = bytecodeCompiler.allocateRegister(); if (bytecodeCompiler.isStrictRefsEnabled()) { @@ -1349,7 +1329,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, keyRegs.add(keyReg); } else { // Expression key - keyElement.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(keyElement, -1, rhsContext); keyRegs.add(bytecodeCompiler.lastResultReg); } } @@ -1363,18 +1343,14 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(keyReg); } - // Compile RHS values - node.right.accept(bytecodeCompiler); - int valuesReg = bytecodeCompiler.lastResultReg; - - // Emit direct opcode HASH_SLICE_SET + // Emit direct opcode HASH_SLICE_SET (use valueReg from line 729) bytecodeCompiler.emit(Opcodes.HASH_SLICE_SET); bytecodeCompiler.emitReg(hashReg); bytecodeCompiler.emitReg(keysListReg); - bytecodeCompiler.emitReg(valuesReg); + bytecodeCompiler.emitReg(valueReg); - bytecodeCompiler.lastResultReg = valuesReg; - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.lastResultReg = valueReg; + return; } else if (hashOp.operator.equals("$")) { // $hash{key} or $$ref{key} - dereference to get hash @@ -1405,7 +1381,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } } else { // $$ref{key} = value — compile the scalar ref expression and deref to hash - hashOp.operand.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(hashOp.operand, -1, rhsContext); int scalarReg = bytecodeCompiler.lastResultReg; hashReg = bytecodeCompiler.allocateRegister(); if (bytecodeCompiler.isStrictRefsEnabled()) { @@ -1427,7 +1403,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } else if (leftBin.left instanceof BinaryOperatorNode) { // Nested: $hash{outer}{inner} = value // Compile left side (returns scalar containing hash reference or autovivifies) - leftBin.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(leftBin.left, -1, rhsContext); int scalarReg = bytecodeCompiler.lastResultReg; // Dereference to get the hash (with autovivification) @@ -1472,22 +1448,18 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emit(keyIdx); } else { // Expression key: $hash{$var} or $hash{func()} - keyElement.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(keyElement, -1, rhsContext); keyReg = bytecodeCompiler.lastResultReg; } - // 3. Compile RHS value - node.right.accept(bytecodeCompiler); - int hashValueReg = bytecodeCompiler.lastResultReg; - - // 4. Emit HASH_SET + // 3. Emit HASH_SET (use valueReg from line 729) bytecodeCompiler.emit(Opcodes.HASH_SET); bytecodeCompiler.emitReg(hashReg); bytecodeCompiler.emitReg(keyReg); - bytecodeCompiler.emitReg(hashValueReg); + bytecodeCompiler.emitReg(valueReg); - bytecodeCompiler.lastResultReg = hashValueReg; - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.lastResultReg = valueReg; + return; } @@ -1497,7 +1469,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, Node rightSide = leftBin.right; if (rightSide instanceof HashLiteralNode hashKey) { // $ref->{key} = value — hash element via reference - leftBin.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(leftBin.left, -1, rhsContext); int refReg = bytecodeCompiler.lastResultReg; // Dereference to get the hash @@ -1526,7 +1498,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(keyReg); bytecodeCompiler.emit(keyIdx); } else { - keyElement.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(keyElement, -1, rhsContext); keyReg = bytecodeCompiler.lastResultReg; } } else { @@ -1534,19 +1506,17 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, return; } - // Compile RHS and emit HASH_SET - node.right.accept(bytecodeCompiler); - int valReg = bytecodeCompiler.lastResultReg; + // Emit HASH_SET (use valueReg from line 729) bytecodeCompiler.emit(Opcodes.HASH_SET); bytecodeCompiler.emitReg(hashReg); bytecodeCompiler.emitReg(keyReg); - bytecodeCompiler.emitReg(valReg); - bytecodeCompiler.lastResultReg = valReg; - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.emitReg(valueReg); + bytecodeCompiler.lastResultReg = valueReg; + return; } else if (rightSide instanceof ArrayLiteralNode arrayIdx) { // $ref->[index] = value — array element via reference - leftBin.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(leftBin.left, -1, rhsContext); int refReg = bytecodeCompiler.lastResultReg; // Dereference to get the array @@ -1568,18 +1538,16 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.throwCompilerException("Array index required for arrow assignment"); return; } - arrayIdx.elements.get(0).accept(bytecodeCompiler); + bytecodeCompiler.compileNode(arrayIdx.elements.get(0), -1, rhsContext); int idxReg = bytecodeCompiler.lastResultReg; - // Compile RHS and emit ARRAY_SET - node.right.accept(bytecodeCompiler); - int valReg = bytecodeCompiler.lastResultReg; + // Emit ARRAY_SET (use valueReg from line 729) bytecodeCompiler.emit(Opcodes.ARRAY_SET); bytecodeCompiler.emitReg(arrayReg); bytecodeCompiler.emitReg(idxReg); - bytecodeCompiler.emitReg(valReg); - bytecodeCompiler.lastResultReg = valReg; - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.emitReg(valueReg); + bytecodeCompiler.lastResultReg = valueReg; + return; } } @@ -1589,11 +1557,11 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, // that wraps a mutable reference. We can assign to it using SET_SCALAR. if (leftBin.operator.equals("(")) { // Call the function (which returns a RuntimeBaseProxy in lvalue context) - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, rhsContext); int lvalueReg = bytecodeCompiler.lastResultReg; // Compile RHS - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int rhsReg = bytecodeCompiler.lastResultReg; // Assign to the lvalue using SET_SCALAR @@ -1602,22 +1570,22 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(rhsReg); bytecodeCompiler.lastResultReg = rhsReg; - bytecodeCompiler.currentCallContext = savedContext; + return; } bytecodeCompiler.throwCompilerException("Assignment to non-identifier not yet supported: " + node.left.getClass().getSimpleName()); } else if (node.left instanceof TernaryOperatorNode) { LValueVisitor.getContext(node.left); - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, rhsContext); int lvalueReg = bytecodeCompiler.lastResultReg; - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int rhsReg = bytecodeCompiler.lastResultReg; bytecodeCompiler.emit(Opcodes.SET_SCALAR); bytecodeCompiler.emitReg(lvalueReg); bytecodeCompiler.emitReg(rhsReg); bytecodeCompiler.lastResultReg = rhsReg; - bytecodeCompiler.currentCallContext = savedContext; + } else if (node.left instanceof ListNode listNode) { // List assignment: ($a, $b) = ... or () = ... // In scalar context, returns the number of elements on RHS @@ -1709,7 +1677,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } int countReg = -1; - if (savedContext == RuntimeContextType.SCALAR) { + if (outerContext == RuntimeContextType.SCALAR) { countReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); bytecodeCompiler.emitReg(countReg); @@ -1739,15 +1707,9 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.lastResultReg = rhsListReg; } - bytecodeCompiler.currentCallContext = savedContext; } else { bytecodeCompiler.throwCompilerException("Assignment to non-identifier not yet supported: " + node.left.getClass().getSimpleName()); } - - } finally { - // Always restore the calling context - bytecodeCompiler.currentCallContext = savedContext; - } } static int resolveArrayForDollarHash(BytecodeCompiler bytecodeCompiler, OperatorNode dollarHashOp) { diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java index 951f2ee41..d9ef2edea 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java @@ -15,15 +15,11 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato // left = filehandle reference (\*STDERR) // right = list to print - // Compile the filehandle (left operand) - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, bytecodeCompiler.currentCallContext); int filehandleReg = bytecodeCompiler.lastResultReg; // Compile the content (right operand) in LIST context - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - node.right.accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.compileNode(node.right, -1, RuntimeContextType.LIST); int contentReg = bytecodeCompiler.lastResultReg; // Emit PRINT or SAY with both registers @@ -32,7 +28,7 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato bytecodeCompiler.emitReg(filehandleReg); // print/say return 1 on success - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.LOAD_INT); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitInt(1); @@ -47,8 +43,7 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato // left = format string // right = ListNode of arguments - // Compile the format string (left operand) - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, bytecodeCompiler.currentCallContext); int formatReg = bytecodeCompiler.lastResultReg; // Compile the arguments (right operand) into a list @@ -56,12 +51,9 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato // scalar expressions keep current context to avoid wrapping in RuntimeList int argsListReg = bytecodeCompiler.allocateRegister(); if (node.right instanceof ListNode argsList) { - int savedContext = bytecodeCompiler.currentCallContext; java.util.List argRegs = new java.util.ArrayList<>(); for (Node arg : argsList.elements) { - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - arg.accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.compileNode(arg, -1, RuntimeContextType.LIST); argRegs.add(bytecodeCompiler.lastResultReg); } bytecodeCompiler.emit(Opcodes.CREATE_LIST); @@ -71,22 +63,17 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato bytecodeCompiler.emitReg(argReg); } } else { - // Single argument - wrap in list - int savedContext2 = bytecodeCompiler.currentCallContext; - if (isArrayLikeNode(node.right)) { - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - } - node.right.accept(bytecodeCompiler); + int rightCtx = isArrayLikeNode(node.right) ? RuntimeContextType.LIST : bytecodeCompiler.currentCallContext; + bytecodeCompiler.compileNode(node.right, -1, rightCtx); int argReg = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext2; bytecodeCompiler.emit(Opcodes.CREATE_LIST); bytecodeCompiler.emitReg(argsListReg); - bytecodeCompiler.emit(1); // emit count = 1 + bytecodeCompiler.emit(1); bytecodeCompiler.emitReg(argReg); } // Call sprintf - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.SPRINTF); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(formatReg); @@ -142,26 +129,21 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato // left: scalar containing hash reference // right: HashLiteralNode containing key - // Compile the reference (left side) - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, RuntimeContextType.SCALAR); int scalarRefReg = bytecodeCompiler.lastResultReg; - // Dereference the scalar to get the actual hash int hashReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); bytecodeCompiler.emitReg(hashReg); bytecodeCompiler.emitReg(scalarRefReg); - // Get the key if (keyNode.elements.isEmpty()) { bytecodeCompiler.throwCompilerException("Hash dereference requires key"); } - // Compile the key - handle bareword autoquoting int keyReg; Node keyElement = keyNode.elements.get(0); if (keyElement instanceof IdentifierNode) { - // Bareword key: $ref->{key} -> key is autoquoted String keyString = ((IdentifierNode) keyElement).name; keyReg = bytecodeCompiler.allocateRegister(); int keyIdx = bytecodeCompiler.addToStringPool(keyString); @@ -169,13 +151,12 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato bytecodeCompiler.emitReg(keyReg); bytecodeCompiler.emit(keyIdx); } else { - // Expression key: $ref->{$var} - keyElement.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(keyElement, -1, RuntimeContextType.SCALAR); keyReg = bytecodeCompiler.lastResultReg; } // Access hash element - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.HASH_GET); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(hashReg); @@ -188,27 +169,23 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato // left: scalar containing array reference // right: ArrayLiteralNode containing index - // Compile the reference (left side) - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, RuntimeContextType.SCALAR); int scalarRefReg = bytecodeCompiler.lastResultReg; - // Dereference the scalar to get the actual array int arrayReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); bytecodeCompiler.emitReg(arrayReg); bytecodeCompiler.emitReg(scalarRefReg); - // Get the index if (indexNode.elements.isEmpty()) { bytecodeCompiler.throwCompilerException("Array dereference requires index"); } - // Compile the index expression - indexNode.elements.get(0).accept(bytecodeCompiler); + bytecodeCompiler.compileNode(indexNode.elements.get(0), -1, RuntimeContextType.SCALAR); int indexReg = bytecodeCompiler.lastResultReg; // Access array element - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.ARRAY_GET); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(arrayReg); @@ -228,20 +205,14 @@ else if (node.right instanceof ListNode) { } // This is a code reference call: $coderef->(args) - // Compile the code reference in scalar context - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, RuntimeContextType.SCALAR); int coderefReg = bytecodeCompiler.lastResultReg; - // Compile arguments in list context - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, RuntimeContextType.LIST); int argsReg = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext; // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit CALL_SUB opcode bytecodeCompiler.emit(Opcodes.CALL_SUB); @@ -281,13 +252,11 @@ else if (node.right instanceof BinaryOperatorNode rightCall) { } // Compile invocant in scalar context - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - invocantNode.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(invocantNode, -1, RuntimeContextType.SCALAR); int invocantReg = bytecodeCompiler.lastResultReg; // Compile method name in scalar context - methodNode.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(methodNode, -1, RuntimeContextType.SCALAR); int methodReg = bytecodeCompiler.lastResultReg; // Get currentSub (__SUB__ for SUPER:: resolution) @@ -298,13 +267,11 @@ else if (node.right instanceof BinaryOperatorNode rightCall) { bytecodeCompiler.emit(subIdx); // Compile arguments in list context - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - argsNode.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(argsNode, -1, RuntimeContextType.LIST); int argsReg = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext; // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit CALL_METHOD bytecodeCompiler.emit(Opcodes.CALL_METHOD); @@ -395,20 +362,13 @@ else if (node.right instanceof BinaryOperatorNode rightCall) { // Handle "join" operator specially to ensure proper context // Left operand (separator) needs SCALAR context, right operand (list) needs LIST context if (node.operator.equals("join")) { - // Save and set context for left operand (separator) - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, RuntimeContextType.SCALAR); int rs1 = bytecodeCompiler.lastResultReg; - // Set context for right operand (array/list) - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, RuntimeContextType.LIST); int rs2 = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext; - // Emit JOIN opcode - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.JOIN); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(rs1); @@ -418,21 +378,12 @@ else if (node.right instanceof BinaryOperatorNode rightCall) { return; } - // Handle function call operators specially to ensure arguments are in LIST context if (node.operator.equals("(") || node.operator.equals("()")) { - // Function call: subname(args) or $coderef->(args) - // Save and set context for left operand (code reference) - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, RuntimeContextType.SCALAR); int rs1 = bytecodeCompiler.lastResultReg; - // Arguments must ALWAYS be evaluated in LIST context - // Even if the call itself is in SCALAR context (e.g., scalar(func())) - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, RuntimeContextType.LIST); int rs2 = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext; // Emit CALL_SUB opcode int rd = CompileBinaryOperatorHelper.compileBinaryOperatorSwitch(bytecodeCompiler, node.operator, rs1, rs2, node.getIndex()); @@ -442,48 +393,24 @@ else if (node.right instanceof BinaryOperatorNode rightCall) { // Handle short-circuit operators specially - don't compile right operand yet! if (node.operator.equals("&&") || node.operator.equals("and")) { - // Logical AND with short-circuit evaluation - // Only evaluate right side if left side is true + int rd = bytecodeCompiler.allocateOutputRegister(); - // Compile left operand in scalar context (need boolean value) - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, rd, RuntimeContextType.SCALAR); int rs1 = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext; - - // Allocate result register and move left value to it - int rd = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(Opcodes.ALIAS); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(rs1); + bytecodeCompiler.emitAliasWithTarget(rd, rs1); - // Mark position for forward jump int skipRightPos = bytecodeCompiler.bytecode.size(); - - // Emit conditional jump: if (!rd) skip right evaluation bytecodeCompiler.emit(Opcodes.GOTO_IF_FALSE); bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitInt(0); // Placeholder for offset (will be patched) + bytecodeCompiler.emitInt(0); - // NOW compile right operand (only executed if left was true) - // Force SCALAR context so the right operand always produces a result register - int savedContext2 = bytecodeCompiler.currentCallContext; - if (bytecodeCompiler.currentCallContext == RuntimeContextType.VOID) { - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - } - node.right.accept(bytecodeCompiler); + int rightCtx = bytecodeCompiler.currentCallContext == RuntimeContextType.VOID ? RuntimeContextType.SCALAR : bytecodeCompiler.currentCallContext; + bytecodeCompiler.compileNode(node.right, rd, rightCtx); int rs2 = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext2; - - // Move right result to rd (overwriting left value) if (rs2 >= 0) { - bytecodeCompiler.emit(Opcodes.ALIAS); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(rs2); + bytecodeCompiler.emitAliasWithTarget(rd, rs2); } - // Patch the forward jump offset int skipRightTarget = bytecodeCompiler.bytecode.size(); bytecodeCompiler.patchIntOffset(skipRightPos + 2, skipRightTarget); @@ -492,47 +419,24 @@ else if (node.right instanceof BinaryOperatorNode rightCall) { } if (node.operator.equals("||") || node.operator.equals("or")) { - // Logical OR with short-circuit evaluation - // Only evaluate right side if left side is false + int rd = bytecodeCompiler.allocateOutputRegister(); - // Compile left operand in scalar context (need boolean value) - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, rd, RuntimeContextType.SCALAR); int rs1 = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext; - - // Allocate result register and move left value to it - int rd = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(Opcodes.ALIAS); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(rs1); + bytecodeCompiler.emitAliasWithTarget(rd, rs1); - // Mark position for forward jump int skipRightPos = bytecodeCompiler.bytecode.size(); - - // Emit conditional jump: if (rd) skip right evaluation bytecodeCompiler.emit(Opcodes.GOTO_IF_TRUE); bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitInt(0); // Placeholder for offset (will be patched) + bytecodeCompiler.emitInt(0); - // NOW compile right operand (only executed if left was false) - int savedContext2 = bytecodeCompiler.currentCallContext; - if (bytecodeCompiler.currentCallContext == RuntimeContextType.VOID) { - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - } - node.right.accept(bytecodeCompiler); + int rightCtx = bytecodeCompiler.currentCallContext == RuntimeContextType.VOID ? RuntimeContextType.SCALAR : bytecodeCompiler.currentCallContext; + bytecodeCompiler.compileNode(node.right, rd, rightCtx); int rs2 = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext2; - - // Move right result to rd (overwriting left value) if (rs2 >= 0) { - bytecodeCompiler.emit(Opcodes.ALIAS); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(rs2); + bytecodeCompiler.emitAliasWithTarget(rd, rs2); } - // Patch the forward jump offset int skipRightTarget = bytecodeCompiler.bytecode.size(); bytecodeCompiler.patchIntOffset(skipRightPos + 2, skipRightTarget); @@ -541,53 +445,29 @@ else if (node.right instanceof BinaryOperatorNode rightCall) { } if (node.operator.equals("//")) { - // Defined-OR with short-circuit evaluation - // Only evaluate right side if left side is undefined + int rd = bytecodeCompiler.allocateOutputRegister(); - // Compile left operand in scalar context (need to test definedness) - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, rd, RuntimeContextType.SCALAR); int rs1 = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext; - - // Allocate result register and move left value to it - int rd = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(Opcodes.ALIAS); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(rs1); + bytecodeCompiler.emitAliasWithTarget(rd, rs1); - // Check if left is defined int definedReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.DEFINED); bytecodeCompiler.emitReg(definedReg); bytecodeCompiler.emitReg(rd); - // Mark position for forward jump int skipRightPos = bytecodeCompiler.bytecode.size(); - - // Emit conditional jump: if (defined) skip right evaluation bytecodeCompiler.emit(Opcodes.GOTO_IF_TRUE); bytecodeCompiler.emitReg(definedReg); - bytecodeCompiler.emitInt(0); // Placeholder for offset (will be patched) + bytecodeCompiler.emitInt(0); - // NOW compile right operand (only executed if left was undefined) - int savedContext2 = bytecodeCompiler.currentCallContext; - if (bytecodeCompiler.currentCallContext == RuntimeContextType.VOID) { - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - } - node.right.accept(bytecodeCompiler); + int rightCtx = bytecodeCompiler.currentCallContext == RuntimeContextType.VOID ? RuntimeContextType.SCALAR : bytecodeCompiler.currentCallContext; + bytecodeCompiler.compileNode(node.right, rd, rightCtx); int rs2 = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext2; - - // Move right result to rd (overwriting left value) if (rs2 >= 0) { - bytecodeCompiler.emit(Opcodes.ALIAS); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(rs2); + bytecodeCompiler.emitAliasWithTarget(rd, rs2); } - // Patch the forward jump offset int skipRightTarget = bytecodeCompiler.bytecode.size(); bytecodeCompiler.patchIntOffset(skipRightPos + 2, skipRightTarget); @@ -638,12 +518,11 @@ else if (node.right instanceof BinaryOperatorNode rightCall) { // For !~, we need to negate the result if (node.operator.equals("!~")) { - // Compile the bound operator - boundOp.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(boundOp, -1, bytecodeCompiler.currentCallContext); int matchReg = bytecodeCompiler.lastResultReg; // Negate the result - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.NOT); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(matchReg); @@ -668,20 +547,15 @@ else if (node.right instanceof BinaryOperatorNode rightCall) { "&.", "|.", "^." -> true; default -> false; }; - int savedCtx = bytecodeCompiler.currentCallContext; - if (forceScalar) { - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - } - node.left.accept(bytecodeCompiler); + int outerCtx = bytecodeCompiler.currentCallContext; + int leftCtx = forceScalar ? RuntimeContextType.SCALAR : outerCtx; + bytecodeCompiler.compileNode(node.left, -1, leftCtx); int rs1 = bytecodeCompiler.lastResultReg; - // For =~ and !~, force SCALAR context on the right side (the regex/pattern) - if (!forceScalar && (node.operator.equals("=~") || node.operator.equals("!~"))) { - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - } - node.right.accept(bytecodeCompiler); + int rightCtx = (forceScalar || node.operator.equals("=~") || node.operator.equals("!~")) + ? RuntimeContextType.SCALAR : outerCtx; + bytecodeCompiler.compileNode(node.right, -1, rightCtx); int rs2 = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedCtx; // Emit opcode based on operator (delegated to helper method) int rd = CompileBinaryOperatorHelper.compileBinaryOperatorSwitch(bytecodeCompiler, node.operator, rs1, rs2, node.getIndex()); @@ -700,25 +574,20 @@ private static void compileBinaryAsListOp(BytecodeCompiler bytecodeCompiler, Bin bytecodeCompiler.emit(nameIdx); bytecodeCompiler.lastResultReg = fhReg; } else { - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, RuntimeContextType.SCALAR); } int fhReg = bytecodeCompiler.lastResultReg; java.util.List argRegs = new java.util.ArrayList<>(); argRegs.add(fhReg); - int savedContext = bytecodeCompiler.currentCallContext; if (node.right instanceof ListNode argsList) { for (Node arg : argsList.elements) { - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - arg.accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.compileNode(arg, -1, RuntimeContextType.LIST); argRegs.add(bytecodeCompiler.lastResultReg); } } else { - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - node.right.accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.compileNode(node.right, -1, RuntimeContextType.LIST); argRegs.add(bytecodeCompiler.lastResultReg); } @@ -741,7 +610,7 @@ private static void compileBinaryAsListOp(BytecodeCompiler bytecodeCompiler, Bin default -> throw new RuntimeException("Unknown operator: " + node.operator); }; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(opcode); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argsListReg); @@ -751,10 +620,10 @@ private static void compileBinaryAsListOp(BytecodeCompiler bytecodeCompiler, Bin } private static void compileTellBinaryOp(BytecodeCompiler bytecodeCompiler, BinaryOperatorNode node) { - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, RuntimeContextType.SCALAR); int fhReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.TELL); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(fhReg); @@ -763,19 +632,16 @@ private static void compileTellBinaryOp(BytecodeCompiler bytecodeCompiler, Binar } private static void compileJoinBinaryOp(BytecodeCompiler bytecodeCompiler, BinaryOperatorNode node) { - node.left.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.left, -1, RuntimeContextType.SCALAR); int separatorReg = bytecodeCompiler.lastResultReg; int listReg; if (node.right instanceof ListNode listNode) { - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; java.util.List argRegs = new java.util.ArrayList<>(); for (Node arg : listNode.elements) { - arg.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(arg, -1, RuntimeContextType.LIST); argRegs.add(bytecodeCompiler.lastResultReg); } - bytecodeCompiler.currentCallContext = savedContext; listReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.CREATE_LIST); bytecodeCompiler.emitReg(listReg); @@ -784,14 +650,11 @@ private static void compileJoinBinaryOp(BytecodeCompiler bytecodeCompiler, Binar bytecodeCompiler.emitReg(argReg); } } else { - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - node.right.accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.compileNode(node.right, -1, RuntimeContextType.LIST); listReg = bytecodeCompiler.lastResultReg; } - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.JOIN); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(separatorReg); diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java index 03a9bd7dc..288e02e14 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java @@ -17,7 +17,7 @@ public class CompileBinaryOperatorHelper { */ public static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler, String operator, int rs1, int rs2, int tokenIndex) { // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit opcode based on operator switch (operator) { diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index 25ad9a685..395f99dff 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -9,20 +9,14 @@ public class CompileOperator { private static void compileScalarOperand(BytecodeCompiler bc, OperatorNode node, String opName) { - int savedContext = bc.currentCallContext; - bc.currentCallContext = RuntimeContextType.SCALAR; - try { - if (node.operand instanceof ListNode list) { - if (!list.elements.isEmpty()) { - list.elements.get(0).accept(bc); - } else { - bc.throwCompilerException(opName + " requires an argument"); - } + if (node.operand instanceof ListNode list) { + if (!list.elements.isEmpty()) { + bc.compileNode(list.elements.get(0), -1, RuntimeContextType.SCALAR); } else { - node.operand.accept(bc); + bc.throwCompilerException(opName + " requires an argument"); } - } finally { - bc.currentCallContext = savedContext; + } else { + bc.compileNode(node.operand, -1, RuntimeContextType.SCALAR); } } @@ -88,24 +82,17 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // Force scalar context: scalar(expr) // Evaluates the operand and converts the result to scalar if (node.operand != null) { - // Evaluate operand in scalar context - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - try { - node.operand.accept(bytecodeCompiler); - int operandReg = bytecodeCompiler.lastResultReg; + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.SCALAR); + int operandReg = bytecodeCompiler.lastResultReg; - // Emit ARRAY_SIZE to convert to scalar - // This handles arrays/hashes (converts to size) and passes through scalars - int rd = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(operandReg); + // Emit ARRAY_SIZE to convert to scalar + // This handles arrays/hashes (converts to size) and passes through scalars + int rd = bytecodeCompiler.allocateOutputRegister(); + bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(operandReg); - bytecodeCompiler.lastResultReg = rd; - } finally { - bytecodeCompiler.currentCallContext = savedContext; - } + bytecodeCompiler.lastResultReg = rd; } else { bytecodeCompiler.throwCompilerException("scalar operator requires an operand"); } @@ -166,14 +153,11 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // Logical NOT operator: not $x or !$x // Evaluate operand in scalar context (need boolean value) if (node.operand != null) { - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.operand.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.SCALAR); int rs = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext; // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit NOT opcode bytecodeCompiler.emit(Opcodes.NOT); @@ -192,7 +176,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int rs = bytecodeCompiler.lastResultReg; // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit BITWISE_NOT_BINARY opcode bytecodeCompiler.emit(Opcodes.BITWISE_NOT_BINARY); @@ -211,7 +195,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int rs = bytecodeCompiler.lastResultReg; // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit BITWISE_NOT_STRING opcode bytecodeCompiler.emit(Opcodes.BITWISE_NOT_STRING); @@ -230,7 +214,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int rs = bytecodeCompiler.lastResultReg; // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit DEFINED opcode bytecodeCompiler.emit(Opcodes.DEFINED); @@ -261,7 +245,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int argReg = bytecodeCompiler.lastResultReg; // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit REF opcode bytecodeCompiler.emit(Opcodes.REF); @@ -289,7 +273,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int argReg = bytecodeCompiler.lastResultReg; // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Add current package to string pool int packageIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage()); @@ -321,7 +305,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int flagsReg = bytecodeCompiler.lastResultReg; // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit QUOTE_REGEX opcode bytecodeCompiler.emit(Opcodes.QUOTE_REGEX); @@ -390,27 +374,24 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // Handle &sub syntax (goto &foo) if (callTarget instanceof OperatorNode opNode && opNode.operator.equals("&")) { // This is a tail call: goto &sub + int outerContext = bytecodeCompiler.currentCallContext; // Evaluate the code reference in scalar context - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - callTarget.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(callTarget, -1, RuntimeContextType.SCALAR); int codeRefReg = bytecodeCompiler.lastResultReg; // Evaluate the arguments in list context (usually @_) - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - callNode.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(callNode.right, -1, RuntimeContextType.LIST); int argsReg = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext; // Allocate register for call result - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit CALL_SUB to invoke the code reference with proper context bytecodeCompiler.emit(Opcodes.CALL_SUB); bytecodeCompiler.emitReg(rd); // Result register bytecodeCompiler.emitReg(codeRefReg); // Code reference register bytecodeCompiler.emitReg(argsReg); // Arguments register - bytecodeCompiler.emit(savedContext); // Use saved calling context for the tail call + bytecodeCompiler.emit(outerContext); // Use outer calling context for the tail call // Then return the result bytecodeCompiler.emitWithToken(Opcodes.RETURN, node.getIndex()); @@ -447,7 +428,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("rand")) { // rand() or rand($max) // Calls Random.rand(max) where max defaults to 1 - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); if (node.operand != null) { // rand($max) - evaluate operand @@ -475,7 +456,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("sleep")) { // sleep $seconds // Calls Time.sleep(seconds) - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); if (node.operand != null) { // sleep($seconds) - evaluate operand @@ -500,7 +481,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.lastResultReg = rd; } else if (op.equals("alarm")) { - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); if (node.operand != null) { node.operand.accept(bytecodeCompiler); int argReg = bytecodeCompiler.lastResultReg; @@ -528,7 +509,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Return 1 (true) - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.LOAD_INT); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitInt(1); @@ -536,86 +517,59 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.lastResultReg = rd; } else if (op.equals("require")) { // require MODULE_NAME or require VERSION - // Evaluate operand in scalar context - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - try { - node.operand.accept(bytecodeCompiler); - int operandReg = bytecodeCompiler.lastResultReg; + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.SCALAR); + int operandReg = bytecodeCompiler.lastResultReg; - // Call ModuleOperators.require() - int rd = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(Opcodes.REQUIRE); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(operandReg); + int rd = bytecodeCompiler.allocateOutputRegister(); + bytecodeCompiler.emit(Opcodes.REQUIRE); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(operandReg); - bytecodeCompiler.lastResultReg = rd; - } finally { - bytecodeCompiler.currentCallContext = savedContext; - } + bytecodeCompiler.lastResultReg = rd; } else if (op.equals("pos")) { // pos($var) - get or set regex match position - // Returns an lvalue that can be assigned to - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - try { - node.operand.accept(bytecodeCompiler); - int operandReg = bytecodeCompiler.lastResultReg; + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.SCALAR); + int operandReg = bytecodeCompiler.lastResultReg; - // Call RuntimeScalar.pos() - int rd = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(Opcodes.POS); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(operandReg); + int rd = bytecodeCompiler.allocateOutputRegister(); + bytecodeCompiler.emit(Opcodes.POS); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(operandReg); - bytecodeCompiler.lastResultReg = rd; - } finally { - bytecodeCompiler.currentCallContext = savedContext; - } + bytecodeCompiler.lastResultReg = rd; } else if (op.equals("index") || op.equals("rindex")) { // index(str, substr, pos?) or rindex(str, substr, pos?) if (node.operand instanceof ListNode args) { + if (args.elements.isEmpty()) { + bytecodeCompiler.throwCompilerException("Not enough arguments for " + op); + } + bytecodeCompiler.compileNode(args.elements.get(0), -1, RuntimeContextType.SCALAR); + int strReg = bytecodeCompiler.lastResultReg; - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - try { - // Evaluate first arg (string) - if (args.elements.isEmpty()) { - bytecodeCompiler.throwCompilerException("Not enough arguments for " + op); - } - args.elements.get(0).accept(bytecodeCompiler); - int strReg = bytecodeCompiler.lastResultReg; - - // Evaluate second arg (substring) - if (args.elements.size() < 2) { - bytecodeCompiler.throwCompilerException("Not enough arguments for " + op); - } - args.elements.get(1).accept(bytecodeCompiler); - int substrReg = bytecodeCompiler.lastResultReg; - - // Evaluate third arg (position) - optional, defaults to undef - int posReg; - if (args.elements.size() >= 3) { - args.elements.get(2).accept(bytecodeCompiler); - posReg = bytecodeCompiler.lastResultReg; - } else { - posReg = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(Opcodes.LOAD_UNDEF); - bytecodeCompiler.emitReg(posReg); - } + if (args.elements.size() < 2) { + bytecodeCompiler.throwCompilerException("Not enough arguments for " + op); + } + bytecodeCompiler.compileNode(args.elements.get(1), -1, RuntimeContextType.SCALAR); + int substrReg = bytecodeCompiler.lastResultReg; - // Call index or rindex - int rd = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(op.equals("index") ? Opcodes.INDEX : Opcodes.RINDEX); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(strReg); - bytecodeCompiler.emitReg(substrReg); + int posReg; + if (args.elements.size() >= 3) { + bytecodeCompiler.compileNode(args.elements.get(2), -1, RuntimeContextType.SCALAR); + posReg = bytecodeCompiler.lastResultReg; + } else { + posReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.LOAD_UNDEF); bytecodeCompiler.emitReg(posReg); - - bytecodeCompiler.lastResultReg = rd; - } finally { - bytecodeCompiler.currentCallContext = savedContext; } + + int rd = bytecodeCompiler.allocateOutputRegister(); + bytecodeCompiler.emit(op.equals("index") ? Opcodes.INDEX : Opcodes.RINDEX); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(strReg); + bytecodeCompiler.emitReg(substrReg); + bytecodeCompiler.emitReg(posReg); + + bytecodeCompiler.lastResultReg = rd; } else { bytecodeCompiler.throwCompilerException(op + " requires a list of arguments"); } @@ -625,27 +579,22 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode && ((IdentifierNode) node.operand).name.equals("_"); if (isUnderscoreOperand) { - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(op.equals("stat") ? Opcodes.STAT_LASTHANDLE : Opcodes.LSTAT_LASTHANDLE); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); bytecodeCompiler.lastResultReg = rd; } else { - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - try { - node.operand.accept(bytecodeCompiler); - int operandReg = bytecodeCompiler.lastResultReg; + int outerContext = bytecodeCompiler.currentCallContext; + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.SCALAR); + int operandReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(op.equals("stat") ? Opcodes.STAT : Opcodes.LSTAT); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(operandReg); - bytecodeCompiler.emit(savedContext); - bytecodeCompiler.lastResultReg = rd; - } finally { - bytecodeCompiler.currentCallContext = savedContext; - } + int rd = bytecodeCompiler.allocateOutputRegister(); + bytecodeCompiler.emit(op.equals("stat") ? Opcodes.STAT : Opcodes.LSTAT); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(operandReg); + bytecodeCompiler.emit(outerContext); + bytecodeCompiler.lastResultReg = rd; } } else if (op.startsWith("-") && op.length() == 2) { // File test operators: -r, -w, -x, etc. @@ -657,7 +606,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode if (isUnderscoreOperand) { // Special case: -r _ uses cached file handle // Call FileTestOperator.fileTestLastHandle(String) - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); int operatorStrIndex = bytecodeCompiler.addToStringPool(op); // Emit FILETEST_LASTHANDLE opcode @@ -668,112 +617,106 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.lastResultReg = rd; } else { // Normal case: evaluate operand and test it - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - try { - node.operand.accept(bytecodeCompiler); - int operandReg = bytecodeCompiler.lastResultReg; - - int rd = bytecodeCompiler.allocateRegister(); - - // Map operator to opcode - char testChar = op.charAt(1); - short opcode; - switch (testChar) { - case 'r': - opcode = Opcodes.FILETEST_R; - break; - case 'w': - opcode = Opcodes.FILETEST_W; - break; - case 'x': - opcode = Opcodes.FILETEST_X; - break; - case 'o': - opcode = Opcodes.FILETEST_O; - break; - case 'R': - opcode = Opcodes.FILETEST_R_REAL; - break; - case 'W': - opcode = Opcodes.FILETEST_W_REAL; - break; - case 'X': - opcode = Opcodes.FILETEST_X_REAL; - break; - case 'O': - opcode = Opcodes.FILETEST_O_REAL; - break; - case 'e': - opcode = Opcodes.FILETEST_E; - break; - case 'z': - opcode = Opcodes.FILETEST_Z; - break; - case 's': - opcode = Opcodes.FILETEST_S; - break; - case 'f': - opcode = Opcodes.FILETEST_F; - break; - case 'd': - opcode = Opcodes.FILETEST_D; - break; - case 'l': - opcode = Opcodes.FILETEST_L; - break; - case 'p': - opcode = Opcodes.FILETEST_P; - break; - case 'S': - opcode = Opcodes.FILETEST_S_UPPER; - break; - case 'b': - opcode = Opcodes.FILETEST_B; - break; - case 'c': - opcode = Opcodes.FILETEST_C; - break; - case 't': - opcode = Opcodes.FILETEST_T; - break; - case 'u': - opcode = Opcodes.FILETEST_U; - break; - case 'g': - opcode = Opcodes.FILETEST_G; - break; - case 'k': - opcode = Opcodes.FILETEST_K; - break; - case 'T': - opcode = Opcodes.FILETEST_T_UPPER; - break; - case 'B': - opcode = Opcodes.FILETEST_B_UPPER; - break; - case 'M': - opcode = Opcodes.FILETEST_M; - break; - case 'A': - opcode = Opcodes.FILETEST_A; - break; - case 'C': - opcode = Opcodes.FILETEST_C_UPPER; - break; - default: - bytecodeCompiler.throwCompilerException("Unsupported file test operator: " + op); - return; - } - - bytecodeCompiler.emit(opcode); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(operandReg); + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.SCALAR); + int operandReg = bytecodeCompiler.lastResultReg; - bytecodeCompiler.lastResultReg = rd; - } finally { - bytecodeCompiler.currentCallContext = savedContext; + int rd = bytecodeCompiler.allocateOutputRegister(); + + // Map operator to opcode + char testChar = op.charAt(1); + short opcode; + switch (testChar) { + case 'r': + opcode = Opcodes.FILETEST_R; + break; + case 'w': + opcode = Opcodes.FILETEST_W; + break; + case 'x': + opcode = Opcodes.FILETEST_X; + break; + case 'o': + opcode = Opcodes.FILETEST_O; + break; + case 'R': + opcode = Opcodes.FILETEST_R_REAL; + break; + case 'W': + opcode = Opcodes.FILETEST_W_REAL; + break; + case 'X': + opcode = Opcodes.FILETEST_X_REAL; + break; + case 'O': + opcode = Opcodes.FILETEST_O_REAL; + break; + case 'e': + opcode = Opcodes.FILETEST_E; + break; + case 'z': + opcode = Opcodes.FILETEST_Z; + break; + case 's': + opcode = Opcodes.FILETEST_S; + break; + case 'f': + opcode = Opcodes.FILETEST_F; + break; + case 'd': + opcode = Opcodes.FILETEST_D; + break; + case 'l': + opcode = Opcodes.FILETEST_L; + break; + case 'p': + opcode = Opcodes.FILETEST_P; + break; + case 'S': + opcode = Opcodes.FILETEST_S_UPPER; + break; + case 'b': + opcode = Opcodes.FILETEST_B; + break; + case 'c': + opcode = Opcodes.FILETEST_C; + break; + case 't': + opcode = Opcodes.FILETEST_T; + break; + case 'u': + opcode = Opcodes.FILETEST_U; + break; + case 'g': + opcode = Opcodes.FILETEST_G; + break; + case 'k': + opcode = Opcodes.FILETEST_K; + break; + case 'T': + opcode = Opcodes.FILETEST_T_UPPER; + break; + case 'B': + opcode = Opcodes.FILETEST_B_UPPER; + break; + case 'M': + opcode = Opcodes.FILETEST_M; + break; + case 'A': + opcode = Opcodes.FILETEST_A; + break; + case 'C': + opcode = Opcodes.FILETEST_C_UPPER; + break; + default: + bytecodeCompiler.throwCompilerException("Unsupported file test operator: " + op); + return; } + + bytecodeCompiler.emit(opcode); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(operandReg); + + bytecodeCompiler.lastResultReg = rd; } } else if (op.equals("die")) { // die $message; @@ -908,7 +851,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode if (node.operand != null) { node.operand.accept(bytecodeCompiler); int stringReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Snapshot visible variables and pragma flags for this eval site int evalSiteIndex = bytecodeCompiler.evalSiteRegistries.size(); @@ -939,7 +882,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // Format: [SELECT] [rd] [rs_list] // Effect: rd = IOOperator.select(registers[rs_list], SCALAR) - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); boolean hasArgs = node.operand instanceof ListNode ln && !ln.elements.isEmpty(); if (hasArgs) { @@ -983,16 +926,10 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("unaryMinus")) { // Unary minus: -$x // Compile operand in scalar context (negation always produces a scalar) - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.operand.accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.SCALAR); int operandReg = bytecodeCompiler.lastResultReg; - // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); - - // Emit NEG_SCALAR + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.NEG_SCALAR); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(operandReg); @@ -1062,7 +999,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit ARRAY_POP bytecodeCompiler.emit(Opcodes.ARRAY_POP); @@ -1134,7 +1071,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit ARRAY_SHIFT bytecodeCompiler.emit(Opcodes.ARRAY_SHIFT); @@ -1222,7 +1159,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit direct opcode SPLICE bytecodeCompiler.emit(Opcodes.SPLICE); @@ -1258,7 +1195,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Allocate result register - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); // Emit direct opcode REVERSE bytecodeCompiler.emit(Opcodes.REVERSE); @@ -1375,7 +1312,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Emit HASH_EXISTS - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.HASH_EXISTS); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(hashReg); @@ -1386,18 +1323,118 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int arrayReg = compileArrayForExistsDelete(bytecodeCompiler, arrayAccess, node.getIndex()); int indexReg = compileArrayIndex(bytecodeCompiler, arrayAccess); - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.ARRAY_EXISTS); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(arrayReg); bytecodeCompiler.emitReg(indexReg); bytecodeCompiler.lastResultReg = rd; + } else if (arg instanceof BinaryOperatorNode arrowAccess && arrowAccess.operator.equals("->")) { + // Handle exists $hashref->{key} + if (arrowAccess.right instanceof HashLiteralNode keyNode) { + // Compile left side to get the hash reference + bytecodeCompiler.compileNode(arrowAccess.left, -1, RuntimeContextType.SCALAR); + int refReg = bytecodeCompiler.lastResultReg; + + // Dereference to get hash + int hashReg = bytecodeCompiler.allocateRegister(); + if (bytecodeCompiler.isStrictRefsEnabled()) { + bytecodeCompiler.emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); + bytecodeCompiler.emitReg(hashReg); + bytecodeCompiler.emitReg(refReg); + } else { + int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage()); + bytecodeCompiler.emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex()); + bytecodeCompiler.emitReg(hashReg); + bytecodeCompiler.emitReg(refReg); + bytecodeCompiler.emit(pkgIdx); + } + + // Compile key + int keyReg; + if (!keyNode.elements.isEmpty()) { + Node keyElement = keyNode.elements.get(0); + if (keyElement instanceof IdentifierNode) { + // Bareword key - autoquote + String keyString = ((IdentifierNode) keyElement).name; + keyReg = bytecodeCompiler.allocateRegister(); + int keyIdx = bytecodeCompiler.addToStringPool(keyString); + bytecodeCompiler.emit(Opcodes.LOAD_STRING); + bytecodeCompiler.emitReg(keyReg); + bytecodeCompiler.emit(keyIdx); + } else { + // Expression key + keyElement.accept(bytecodeCompiler); + keyReg = bytecodeCompiler.lastResultReg; + } + } else { + bytecodeCompiler.throwCompilerException("Hash key required for exists"); + return; + } + + // Emit HASH_EXISTS + int rd = bytecodeCompiler.allocateOutputRegister(); + bytecodeCompiler.emit(Opcodes.HASH_EXISTS); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(hashReg); + bytecodeCompiler.emitReg(keyReg); + + bytecodeCompiler.lastResultReg = rd; + } else if (arrowAccess.right instanceof ArrayLiteralNode indexNode) { + // Handle exists $arrayref->[index] + bytecodeCompiler.compileNode(arrowAccess.left, -1, RuntimeContextType.SCALAR); + int refReg = bytecodeCompiler.lastResultReg; + + // Dereference to get array + int arrayReg = bytecodeCompiler.allocateRegister(); + if (bytecodeCompiler.isStrictRefsEnabled()) { + bytecodeCompiler.emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); + bytecodeCompiler.emitReg(arrayReg); + bytecodeCompiler.emitReg(refReg); + } else { + int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage()); + bytecodeCompiler.emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, node.getIndex()); + bytecodeCompiler.emitReg(arrayReg); + bytecodeCompiler.emitReg(refReg); + bytecodeCompiler.emit(pkgIdx); + } + + // Compile index + int indexReg; + if (!indexNode.elements.isEmpty()) { + indexNode.elements.get(0).accept(bytecodeCompiler); + indexReg = bytecodeCompiler.lastResultReg; + } else { + bytecodeCompiler.throwCompilerException("Array index required for exists"); + return; + } + + // Emit ARRAY_EXISTS + int rd = bytecodeCompiler.allocateOutputRegister(); + bytecodeCompiler.emit(Opcodes.ARRAY_EXISTS); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(arrayReg); + bytecodeCompiler.emitReg(indexReg); + + bytecodeCompiler.lastResultReg = rd; + } else { + // Unknown arrow dereference pattern + arg.accept(bytecodeCompiler); + int argReg = bytecodeCompiler.lastResultReg; + + int rd = bytecodeCompiler.allocateOutputRegister(); + bytecodeCompiler.emit(Opcodes.EXISTS); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(argReg); + + bytecodeCompiler.lastResultReg = rd; + } } else { arg.accept(bytecodeCompiler); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.EXISTS); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -1483,7 +1520,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Use SLOW_OP for hash slice delete - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.HASH_SLICE_DELETE); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(hashReg); @@ -1586,7 +1623,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Emit HASH_DELETE - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.HASH_DELETE); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(hashReg); @@ -1632,7 +1669,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode keyReg = bytecodeCompiler.lastResultReg; } - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.HASH_DELETE); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(hashReg); @@ -1643,7 +1680,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int arrayReg = compileArrayForExistsDelete(bytecodeCompiler, arrayAccess, node.getIndex()); int indexReg = compileArrayIndex(bytecodeCompiler, arrayAccess); - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.ARRAY_DELETE); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(arrayReg); @@ -1654,7 +1691,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode arg.accept(bytecodeCompiler); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.DELETE); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -1669,17 +1706,10 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // 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; - } + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.LIST); int hashReg = bytecodeCompiler.lastResultReg; - // Emit HASH_KEYS - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.HASH_KEYS); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(hashReg); @@ -1687,7 +1717,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // 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) { + if (bytecodeCompiler.currentCallContext == RuntimeContextType.SCALAR) { int scalarReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); bytecodeCompiler.emitReg(scalarReg); @@ -1722,7 +1752,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode scalarReg = bytecodeCompiler.lastResultReg; } - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.CHOP); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(scalarReg); @@ -1736,25 +1766,15 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // 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; - } + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.LIST); int hashReg = bytecodeCompiler.lastResultReg; - // Emit HASH_VALUES - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.HASH_VALUES); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(hashReg); - // 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) { + if (bytecodeCompiler.currentCallContext == RuntimeContextType.SCALAR) { int scalarReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); bytecodeCompiler.emitReg(scalarReg); @@ -1845,7 +1865,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(oneReg); bytecodeCompiler.emitInt(1); - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.SUB_SCALAR); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(sizeReg); @@ -1872,7 +1892,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int stringReg = bytecodeCompiler.lastResultReg; // Call length builtin using SLOW_OP - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.LENGTH_OP); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(stringReg); @@ -1884,13 +1904,10 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode String argument = ((StringNode) ((ListNode) node.operand).elements.getFirst()).value; if (argument.isEmpty() || argument.equals("<>")) { // True diamond <> or <<>> : read from @ARGV / STDIN via DiamondIO.readline - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.operand.accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.SCALAR); int fhReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.READLINE); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(fhReg); @@ -1941,7 +1958,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Call open with context and args - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.OPEN); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); @@ -1997,14 +2014,10 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode if (args.elements.size() > 2) { // String provided - perform the match in SCALAR context so that // ($_ = "x") =~ m/.../ returns the lvalue, not a RuntimeList. - int savedCtx = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - args.elements.get(2).accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedCtx; + bytecodeCompiler.compileNode(args.elements.get(2), -1, RuntimeContextType.SCALAR); int stringReg = bytecodeCompiler.lastResultReg; - // Call MATCH_REGEX to perform the match - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.MATCH_REGEX); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(stringReg); @@ -2024,7 +2037,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(stringReg); bytecodeCompiler.emit(nameIdx); } - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.MATCH_REGEX); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(stringReg); @@ -2070,10 +2083,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // String provided in operand list (from =~ binding). // Must compile in SCALAR context: a list like ($_ = "x") must yield // the last element as a scalar lvalue, not a RuntimeList. - int savedCtx = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - args.elements.get(3).accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedCtx; + bytecodeCompiler.compileNode(args.elements.get(3), -1, RuntimeContextType.SCALAR); stringReg = bytecodeCompiler.lastResultReg; } else { // Use $_ as default @@ -2090,7 +2100,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Apply the regex match (which handles replacement) - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.MATCH_REGEX); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(stringReg); @@ -2127,7 +2137,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Call SUBSTR_VAR opcode - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.SUBSTR_VAR); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argsListReg); @@ -2151,7 +2161,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emit(nameIdx); } - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.CHOMP); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(targetReg); @@ -2167,7 +2177,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } int targetReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.CHOMP); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(targetReg); @@ -2185,7 +2195,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("wantarray")) { // wantarray operator: returns undef in VOID, false in SCALAR, true in LIST // Read register 2 (wantarray context) and convert to Perl convention - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.WANTARRAY); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(2); // Register 2 contains the calling context @@ -2203,13 +2213,9 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int formatReg = bytecodeCompiler.lastResultReg; // Compile remaining arguments; use LIST context only for array/hash args - int savedContext = bytecodeCompiler.currentCallContext; java.util.List argRegs = new java.util.ArrayList<>(); for (int i = 1; i < list.elements.size(); i++) { - Node arg = list.elements.get(i); - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - arg.accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.compileNode(list.elements.get(i), -1, RuntimeContextType.LIST); argRegs.add(bytecodeCompiler.lastResultReg); } @@ -2223,7 +2229,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Call sprintf with format and arg list - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.SPRINTF); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(formatReg); @@ -2236,7 +2242,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("int")) { compileScalarOperand(bytecodeCompiler, node, "int"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.INT); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2244,7 +2250,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("log")) { compileScalarOperand(bytecodeCompiler, node, "log"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.LOG); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2252,7 +2258,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("sqrt")) { compileScalarOperand(bytecodeCompiler, node, "sqrt"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.SQRT); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2260,7 +2266,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("cos")) { compileScalarOperand(bytecodeCompiler, node, "cos"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.COS); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2268,7 +2274,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("sin")) { compileScalarOperand(bytecodeCompiler, node, "sin"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.SIN); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2276,7 +2282,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("exp")) { compileScalarOperand(bytecodeCompiler, node, "exp"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.EXP); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2284,7 +2290,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("abs")) { compileScalarOperand(bytecodeCompiler, node, "abs"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.ABS); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2292,7 +2298,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("binary~")) { compileScalarOperand(bytecodeCompiler, node, "binary~"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.BINARY_NOT); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2300,7 +2306,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("integerBitwiseNot")) { compileScalarOperand(bytecodeCompiler, node, "integerBitwiseNot"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.INTEGER_BITWISE_NOT); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2308,7 +2314,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("ord")) { compileScalarOperand(bytecodeCompiler, node, "ord"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.ORD); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2316,7 +2322,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("ordBytes")) { compileScalarOperand(bytecodeCompiler, node, "ordBytes"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.ORD_BYTES); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2324,7 +2330,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("oct")) { compileScalarOperand(bytecodeCompiler, node, "oct"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.OCT); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2332,7 +2338,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("hex")) { compileScalarOperand(bytecodeCompiler, node, "hex"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.HEX); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2340,7 +2346,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("srand")) { compileScalarOperand(bytecodeCompiler, node, "srand"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.SRAND); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2348,7 +2354,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("chr")) { compileScalarOperand(bytecodeCompiler, node, "chr"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.CHR); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2356,7 +2362,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("chrBytes")) { compileScalarOperand(bytecodeCompiler, node, "chrBytes"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.CHR_BYTES); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2364,7 +2370,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("lengthBytes")) { compileScalarOperand(bytecodeCompiler, node, "lengthBytes"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.LENGTH_BYTES); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2372,7 +2378,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("quotemeta")) { compileScalarOperand(bytecodeCompiler, node, "quotemeta"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.QUOTEMETA); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2380,7 +2386,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("fc")) { compileScalarOperand(bytecodeCompiler, node, "fc"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.FC); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2388,7 +2394,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("lc")) { compileScalarOperand(bytecodeCompiler, node, "lc"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.LC); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2396,7 +2402,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("lcfirst")) { compileScalarOperand(bytecodeCompiler, node, "lcfirst"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.LCFIRST); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2404,7 +2410,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("uc")) { compileScalarOperand(bytecodeCompiler, node, "uc"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.UC); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2412,7 +2418,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("ucfirst")) { compileScalarOperand(bytecodeCompiler, node, "ucfirst"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.UCFIRST); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2420,7 +2426,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("sleep")) { compileScalarOperand(bytecodeCompiler, node, "sleep"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.SLEEP); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2428,7 +2434,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("tell")) { compileScalarOperand(bytecodeCompiler, node, "tell"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.TELL); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2436,7 +2442,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("rmdir")) { compileScalarOperand(bytecodeCompiler, node, "rmdir"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.RMDIR); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2444,7 +2450,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("closedir")) { compileScalarOperand(bytecodeCompiler, node, "closedir"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.CLOSEDIR); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2452,7 +2458,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("rewinddir")) { compileScalarOperand(bytecodeCompiler, node, "rewinddir"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.REWINDDIR); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2460,7 +2466,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("telldir")) { compileScalarOperand(bytecodeCompiler, node, "telldir"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.TELLDIR); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2468,7 +2474,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("chdir")) { compileScalarOperand(bytecodeCompiler, node, "chdir"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.CHDIR); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2476,7 +2482,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("exit")) { compileScalarOperand(bytecodeCompiler, node, "exit"); int argReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.EXIT); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -2522,7 +2528,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Emit TR_TRANSLITERATE operation - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.TR_TRANSLITERATE); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(searchReg); @@ -2539,7 +2545,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode node.operand.accept(bytecodeCompiler); int argsReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); short opcode = switch (op) { case "tie" -> Opcodes.TIE; case "untie" -> Opcodes.UNTIE; @@ -2558,7 +2564,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("getppid")) { // getppid() - returns parent process ID // Format: GETPPID rd - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emitWithToken(Opcodes.GETPPID, node.getIndex()); bytecodeCompiler.emitReg(rd); bytecodeCompiler.lastResultReg = rd; @@ -2571,7 +2577,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int rs1 = bytecodeCompiler.lastResultReg; list.elements.get(1).accept(bytecodeCompiler); int rs2 = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emitWithToken(Opcodes.ATAN2, node.getIndex()); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(rs1); @@ -2591,14 +2597,11 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.throwCompilerException("each requires an argument"); } - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - node.operand.accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.LIST); int containerReg = bytecodeCompiler.lastResultReg; // Pass container directly to EACH - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emitWithToken(Opcodes.EACH, node.getIndex()); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(containerReg); @@ -2629,10 +2632,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int argsReg; if (node.operand != null) { // Save current context and evaluate operand in list context - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; - node.operand.accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.LIST); int operandReg = bytecodeCompiler.lastResultReg; @@ -2649,7 +2649,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emit(0); } - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); short opcode = switch (op) { case "chmod" -> Opcodes.CHMOD; case "unlink" -> Opcodes.UNLINK; @@ -2716,13 +2716,10 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // in SCALAR context, then emit GLOB_OP. int globId = ScalarGlobOperator.currentId++; - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.operand.accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.SCALAR); int patternReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.GLOB_OP); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emit(globId); @@ -2731,13 +2728,10 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.lastResultReg = rd; } else if (op.equals("doFile")) { // do FILE: executes a Perl file - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.operand.accept(bytecodeCompiler); - bytecodeCompiler.currentCallContext = savedContext; + bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.SCALAR); int fileReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.DO_FILE); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(fileReg); @@ -2766,10 +2760,18 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } bytecodeCompiler.lastResultReg = -1; } else if (op.equals("time")) { - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.TIME_OP); bytecodeCompiler.emitReg(rd); bytecodeCompiler.lastResultReg = rd; + } else if (op.equals("__SUB__")) { + // __SUB__ returns the current subroutine being executed + int rd = bytecodeCompiler.allocateOutputRegister(); + int nameIdx = bytecodeCompiler.addToStringPool("__SUB__"); + bytecodeCompiler.emit(Opcodes.LOAD_GLOBAL_CODE); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emit(nameIdx); + bytecodeCompiler.lastResultReg = rd; } else { bytecodeCompiler.throwCompilerException("Unsupported operator: " + op); } diff --git a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java index 0a82b0c9b..1447d1a75 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java @@ -265,6 +265,7 @@ public String disassemble() { sb.append("Bytecode length: ").append(bytecode.length).append(" shorts\n\n"); int pc = 0; + try { while (pc < bytecode.length) { int startPc = pc; int opcode = bytecode[pc++]; @@ -1961,8 +1962,11 @@ public String disassemble() { rd = bytecode[pc++]; nameIdx = bytecode[pc++]; int rbhBeginId = bytecode[pc++]; + String rbhName = (nameIdx >= 0 && nameIdx < stringPool.length) + ? stringPool[nameIdx] + : ""; sb.append("RETRIEVE_BEGIN_HASH r").append(rd).append(" = BEGIN_").append(rbhBeginId) - .append("::").append(stringPool[nameIdx]).append("\n"); + .append("::").append(rbhName).append("\n"); break; } @@ -2298,12 +2302,26 @@ public String disassemble() { sb.append("RESTORE_REGEX_STATE r").append(rrsDummy).append("\n"); break; } + case Opcodes.SLOW_OP: { + // Deprecated: SLOW_OP was removed, all operations now use direct opcodes + // Format was: SLOW_OP slow_op_id rd argsReg ctx + int slowOpId = bytecode[pc++]; + rd = bytecode[pc++]; + int slowArgsReg = bytecode[pc++]; + int slowCtx = bytecode[pc++]; + sb.append("SLOW_OP(").append(slowOpId).append(") r").append(rd) + .append(" = slow(r").append(slowArgsReg).append(", ctx=").append(slowCtx).append(")\n"); + break; + } default: sb.append("UNKNOWN(").append(opcode).append(")\n"); break; } } + } catch (ArrayIndexOutOfBoundsException e) { + sb.append("\n*** Disassembly error at pc=").append(pc).append(": ").append(e.getMessage()).append(" ***\n"); + } return sb.toString(); } diff --git a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java index 054abfc73..eae31419c 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java +++ b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java @@ -838,8 +838,10 @@ public static int executeCreateClosure(int[] bytecode, int pc, RuntimeBase[] reg // Create a new InterpretedCode with the captured variables InterpretedCode closureCode = template.withCapturedVars(capturedVars); - // Wrap in RuntimeScalar - registers[rd] = new RuntimeScalar(closureCode); + // Wrap in RuntimeScalar and set __SUB__ for self-reference + RuntimeScalar codeRef = new RuntimeScalar(closureCode); + closureCode.__SUB__ = codeRef; + registers[rd] = codeRef; return pc; } diff --git a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java index 15d2bbd96..04425c2d7 100644 --- a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java @@ -838,6 +838,9 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S interpretedCode.subName = placeholder.subName; interpretedCode.packageName = placeholder.packageName; + // Set the __SUB__ field for self-reference + interpretedCode.__SUB__ = codeRef; + // Update placeholder in-place: set methodHandle to delegate to InterpretedCode placeholder.methodHandle = RuntimeCode.lookup.findVirtual( InterpretedCode.class, "apply", RuntimeCode.methodType); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java index ac7d877fa..301dd1da4 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java @@ -153,6 +153,8 @@ protected boolean removeEldestEntry(Map.Entry, MethodHandle> eldest) { public RuntimeList constantValue; // Field to hold the thread compiling this code public Supplier compilerSupplier; + // Self-reference for __SUB__ (set after construction for InterpretedCode) + public RuntimeScalar __SUB__; /** * Constructs a RuntimeCode instance with the specified prototype and attributes.