From 0a9d50a66ef29fe9f399de7448cd3d2d414a46d3 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 6 Mar 2026 15:48:31 +0100 Subject: [PATCH 1/8] Refactor bytecode compiler to use compileNode() helper for context save/restore Add compileNode(Node, int targetReg, int callContext) helper method that encapsulates the save/restore pattern for targetOutputReg and currentCallContext. Replace verbose manual save/restore boilerplate across BytecodeCompiler, CompileOperator, and CompileBinaryOperator. Also add compileScalarOperand() helper in CompileOperator for the common scalar-context compilation pattern. This eliminates the targetOutputRegStack (ArrayDeque-based) mechanism and reduces net code by ~280 lines while preserving identical behavior. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../backend/bytecode/BytecodeCompiler.java | 330 ++++----- .../bytecode/CompileBinaryOperator.java | 236 ++----- .../bytecode/CompileBinaryOperatorHelper.java | 2 +- .../backend/bytecode/CompileOperator.java | 634 ++++++++---------- 4 files changed, 459 insertions(+), 743 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index d984d922b..69c48ab33 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 @@ -723,7 +727,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 +796,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 +814,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 +840,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 +953,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 +965,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 +1028,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,7 +1046,7 @@ 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()); - int rd = allocateRegister(); + int rd = allocateOutputRegister(); int nameIdx = addToStringPool(normalizedName); emit(Opcodes.LOAD_GLOBAL_SCALAR); @@ -1114,7 +1112,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 +1217,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 +1287,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 +1300,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 +1498,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 +1522,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) @@ -1602,7 +1598,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 +1620,6 @@ void handleCompoundAssignment(BinaryOperatorNode node) { // The result is stored in targetReg lastResultReg = targetReg; - currentCallContext = savedContext; } /** @@ -1636,10 +1630,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 +1670,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 +1729,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 +1757,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 +1833,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 +1846,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 +2794,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 +2827,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 +2875,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 +2925,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 +2993,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 +3043,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 +3075,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 +3099,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 +3111,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,7 +3167,7 @@ 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); @@ -3200,7 +3188,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,7 +3214,7 @@ void compileVariableReference(OperatorNode node, String op) { getCurrentPackage() ); - int rd = allocateRegister(); + int rd = allocateOutputRegister(); int nameIdx = addToStringPool(globalVarName); emit(Opcodes.LOAD_GLOBAL_SCALAR); @@ -3240,12 +3228,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 +3253,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 +3280,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 +3318,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 +3336,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 +3356,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 +3378,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 +3414,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 +3439,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 +3449,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 +3487,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 +3502,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 +3511,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 +3536,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 +3564,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 +3600,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 // ========================================================================= @@ -4166,13 +4166,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 +4395,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 +4410,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 +4475,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 +4495,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 +4563,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 +4594,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 +4627,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 - - // Compile true expression - node.trueExpr.accept(this); - int trueReg = lastResultReg; + emitInt(0); - // Move true result to rd - emit(Opcodes.ALIAS); - emitReg(rd); - emitReg(trueReg); + compileNode(node.trueExpr, rd, currentCallContext); + emitAliasWithTarget(rd, lastResultReg); - // 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 +4709,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 +4741,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 +4855,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/CompileBinaryOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java index 951f2ee41..393bbc94c 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java @@ -20,10 +20,7 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato 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 +29,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); @@ -56,12 +53,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 +65,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); @@ -175,7 +164,7 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato } // Access hash element - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.HASH_GET); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(hashReg); @@ -208,7 +197,7 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato 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 +217,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 +264,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 +279,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 +374,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 +390,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 +405,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 +431,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 +457,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); @@ -643,7 +535,7 @@ else if (node.right instanceof BinaryOperatorNode rightCall) { 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 +560,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()); @@ -707,18 +594,13 @@ private static void compileBinaryAsListOp(BytecodeCompiler bytecodeCompiler, Bin 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 +623,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); @@ -754,7 +636,7 @@ private static void compileTellBinaryOp(BytecodeCompiler bytecodeCompiler, Binar node.left.accept(bytecodeCompiler); int fhReg = bytecodeCompiler.lastResultReg; - int rd = bytecodeCompiler.allocateRegister(); + int rd = bytecodeCompiler.allocateOutputRegister(); bytecodeCompiler.emit(Opcodes.TELL); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(fhReg); @@ -768,14 +650,11 @@ private static void compileJoinBinaryOp(BytecodeCompiler bytecodeCompiler, Binar 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 +663,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..385bc5538 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,7 +1323,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_EXISTS); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(arrayReg); @@ -1397,7 +1334,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.EXISTS); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(argReg); @@ -1483,7 +1420,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 +1523,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 +1569,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 +1580,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 +1591,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 +1606,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 +1617,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 +1652,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 +1666,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 +1765,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 +1792,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 +1804,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 +1858,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 +1914,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 +1937,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 +1983,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 +2000,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 +2037,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 +2061,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 +2077,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 +2095,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 +2113,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 +2129,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 +2142,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 +2150,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 +2158,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 +2166,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 +2174,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 +2182,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 +2190,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 +2198,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 +2206,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 +2214,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 +2222,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 +2230,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 +2238,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 +2246,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 +2254,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 +2262,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 +2270,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 +2278,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 +2286,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 +2294,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 +2302,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 +2310,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 +2318,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 +2326,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 +2334,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 +2342,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 +2350,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 +2358,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 +2366,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 +2374,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 +2382,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 +2428,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 +2445,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 +2464,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 +2477,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 +2497,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 +2532,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 +2549,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 +2616,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 +2628,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,7 +2660,7 @@ 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; From e876c51e28788a4cf2cc0f113d3997b2845a6505 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 6 Mar 2026 16:00:54 +0100 Subject: [PATCH 2/8] Convert CompileAssignment.java to use compileNode() helper Replace all 51 .accept() calls with compileNode() using explicit context, remove try/finally wrapper, eliminate 9 redundant intermediate context restores, and rename savedContext to outerContext for the 4 read-only comparison sites. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../backend/bytecode/CompileAssignment.java | 152 ++++++++---------- 1 file changed, 67 insertions(+), 85 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index f701df327..e9a2ac519 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,7 +358,7 @@ 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 @@ -400,7 +388,7 @@ 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 @@ -434,7 +422,7 @@ 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 @@ -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); } @@ -1167,7 +1155,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } // Compile values (RHS of assignment) - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valuesReg = bytecodeCompiler.lastResultReg; // Emit direct opcode ARRAY_SLICE_SET @@ -1177,7 +1165,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(valuesReg); bytecodeCompiler.lastResultReg = arrayReg; - bytecodeCompiler.currentCallContext = savedContext; + return; } } @@ -1223,7 +1211,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,11 +1241,11 @@ 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); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int assignValueReg = bytecodeCompiler.lastResultReg; // Emit ARRAY_SET @@ -1267,7 +1255,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(assignValueReg); bytecodeCompiler.lastResultReg = assignValueReg; - bytecodeCompiler.currentCallContext = savedContext; + return; } else if (leftBin.operator.equals("{")) { // Hash element/slice assignment @@ -1306,7 +1294,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 +1337,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); } } @@ -1364,7 +1352,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } // Compile RHS values - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valuesReg = bytecodeCompiler.lastResultReg; // Emit direct opcode HASH_SLICE_SET @@ -1374,7 +1362,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(valuesReg); bytecodeCompiler.lastResultReg = valuesReg; - bytecodeCompiler.currentCallContext = savedContext; + return; } else if (hashOp.operator.equals("$")) { // $hash{key} or $$ref{key} - dereference to get hash @@ -1405,7 +1393,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 +1415,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,12 +1460,12 @@ 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); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int hashValueReg = bytecodeCompiler.lastResultReg; // 4. Emit HASH_SET @@ -1487,7 +1475,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(hashValueReg); bytecodeCompiler.lastResultReg = hashValueReg; - bytecodeCompiler.currentCallContext = savedContext; + return; } @@ -1497,7 +1485,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 +1514,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 { @@ -1535,18 +1523,18 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } // Compile RHS and emit HASH_SET - node.right.accept(bytecodeCompiler); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valReg = bytecodeCompiler.lastResultReg; bytecodeCompiler.emit(Opcodes.HASH_SET); bytecodeCompiler.emitReg(hashReg); bytecodeCompiler.emitReg(keyReg); bytecodeCompiler.emitReg(valReg); bytecodeCompiler.lastResultReg = valReg; - bytecodeCompiler.currentCallContext = savedContext; + 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 +1556,18 @@ 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); + bytecodeCompiler.compileNode(node.right, -1, rhsContext); int valReg = bytecodeCompiler.lastResultReg; bytecodeCompiler.emit(Opcodes.ARRAY_SET); bytecodeCompiler.emitReg(arrayReg); bytecodeCompiler.emitReg(idxReg); bytecodeCompiler.emitReg(valReg); bytecodeCompiler.lastResultReg = valReg; - bytecodeCompiler.currentCallContext = savedContext; + return; } } @@ -1589,11 +1577,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 +1590,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 +1697,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 +1727,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) { From 95c091bfab841279e251d32935a55db0874d150c Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 6 Mar 2026 16:13:17 +0100 Subject: [PATCH 3/8] Eliminate redundant ALIAS opcodes by propagating targetOutputReg Set targetOutputReg before compiling subroutine/block bodies in compile() so that allocateOutputRegister() can place results directly in the return register. Convert remaining raw .accept() calls in CompileBinaryOperator to compileNode() to properly scope targetOutputReg for intermediate operands. Reduces ALIAS opcodes from 31 to 4 in benchmark_lexical.pl disassembly. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../backend/bytecode/BytecodeCompiler.java | 11 ++++--- .../bytecode/CompileBinaryOperator.java | 33 ++++++------------- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index 69c48ab33..dc4a23c00 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -513,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); } diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java index 393bbc94c..d9ef2edea 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java @@ -15,8 +15,7 @@ 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 @@ -44,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 @@ -131,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); @@ -158,8 +151,7 @@ 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; } @@ -177,23 +169,19 @@ 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 @@ -530,8 +518,7 @@ 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 @@ -587,7 +574,7 @@ 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; @@ -633,7 +620,7 @@ 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.allocateOutputRegister(); @@ -645,7 +632,7 @@ 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; From 26f5f7d06077925f5294da706681a2a411b0dd45 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 6 Mar 2026 16:38:24 +0100 Subject: [PATCH 4/8] Fix $1 capture variable regression in eval blocks LOAD_GLOBAL_SCALAR for special variables like $1 returns a proxy object that reads from RuntimeRegex.lastCaptureGroups. When RESTORE_REGEX_STATE runs at the end of a block, it restores lastCaptureGroups to its previous value, causing the proxy to return undef. The fix is to use allocateRegister() instead of allocateOutputRegister() for LOAD_GLOBAL_SCALAR, so that the ALIAS operation copies the value before RESTORE_REGEX_STATE runs. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../perlonjava/backend/bytecode/BytecodeCompiler.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index dc4a23c00..d546ed085 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -1047,7 +1047,10 @@ 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()); - int rd = allocateOutputRegister(); + // 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); emit(Opcodes.LOAD_GLOBAL_SCALAR); @@ -3215,7 +3218,10 @@ void compileVariableReference(OperatorNode node, String op) { getCurrentPackage() ); - int rd = allocateOutputRegister(); + // 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); emit(Opcodes.LOAD_GLOBAL_SCALAR); From be8625be2a33664a14eb0beace2f2a944b969c16 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 6 Mar 2026 17:00:21 +0100 Subject: [PATCH 5/8] Fix interpreter local, __SUB__, and SUPER:: support - Add local $hash{key} and local $array[index] support in interpreter - Add __SUB__ field to RuntimeCode for self-reference in InterpretedCode - Set __SUB__ when InterpretedCode subroutines are created - Add __SUB__ operator handling in CompileOperator - Fix local $Foo::x = value to use NameNormalizer for qualified names Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../backend/bytecode/BytecodeCompiler.java | 12 ++++++++++++ .../backend/bytecode/BytecodeInterpreter.java | 7 ++++++- .../backend/bytecode/CompileAssignment.java | 12 ++++++------ .../perlonjava/backend/bytecode/CompileOperator.java | 8 ++++++++ .../backend/bytecode/OpcodeHandlerExtended.java | 6 ++++-- .../perlonjava/frontend/parser/SubroutineParser.java | 3 +++ .../perlonjava/runtime/runtimetypes/RuntimeCode.java | 2 ++ 7 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index d546ed085..609a904f7 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -3178,6 +3178,16 @@ void compileVariableDeclaration(OperatorNode node, String op) { 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); @@ -4033,6 +4043,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); @@ -4122,6 +4133,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); 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 e9a2ac519..f5dd7b6eb 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -362,8 +362,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, 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(); @@ -392,8 +392,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, 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(); @@ -426,8 +426,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, 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(); diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index 385bc5538..de0f760fb 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -2664,6 +2664,14 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode 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/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. From 3e7778ae5941c85f2bcf3737c4cb769c617f7fe6 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 6 Mar 2026 17:09:30 +0100 Subject: [PATCH 6/8] Fix interpreter double compilation and exists $hashref->{key} - Fix hash/array element assignment compiling RHS twice, causing side effects like shift() to execute twice - Add support for exists $hashref->{key} and exists $arrayref->[idx] using fast HASH_EXISTS/ARRAY_EXISTS opcodes instead of slow path Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../backend/bytecode/CompileAssignment.java | 54 +++------- .../backend/bytecode/CompileOperator.java | 100 ++++++++++++++++++ 2 files changed, 117 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index f5dd7b6eb..b0e8b090a 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -1154,15 +1154,11 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(indexReg); } - // Compile values (RHS of assignment) - bytecodeCompiler.compileNode(node.right, -1, rhsContext); - 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; @@ -1244,17 +1240,13 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.compileNode(indexNode.elements.get(0), -1, rhsContext); int indexReg = bytecodeCompiler.lastResultReg; - // Compile RHS value - bytecodeCompiler.compileNode(node.right, -1, rhsContext); - 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.lastResultReg = valueReg; return; } else if (leftBin.operator.equals("{")) { @@ -1351,17 +1343,13 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(keyReg); } - // Compile RHS values - bytecodeCompiler.compileNode(node.right, -1, rhsContext); - 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.lastResultReg = valueReg; return; } else if (hashOp.operator.equals("$")) { @@ -1464,17 +1452,13 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, keyReg = bytecodeCompiler.lastResultReg; } - // 3. Compile RHS value - bytecodeCompiler.compileNode(node.right, -1, rhsContext); - 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.lastResultReg = valueReg; return; } @@ -1522,14 +1506,12 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, return; } - // Compile RHS and emit HASH_SET - bytecodeCompiler.compileNode(node.right, -1, rhsContext); - 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.emitReg(valueReg); + bytecodeCompiler.lastResultReg = valueReg; return; } else if (rightSide instanceof ArrayLiteralNode arrayIdx) { @@ -1559,14 +1541,12 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.compileNode(arrayIdx.elements.get(0), -1, rhsContext); int idxReg = bytecodeCompiler.lastResultReg; - // Compile RHS and emit ARRAY_SET - bytecodeCompiler.compileNode(node.right, -1, rhsContext); - 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.emitReg(valueReg); + bytecodeCompiler.lastResultReg = valueReg; return; } diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index de0f760fb..395f99dff 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -1330,6 +1330,106 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode 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; From 958de1dac8e5cb42355c7d9869ffa3d542c132e9 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 6 Mar 2026 17:17:13 +0100 Subject: [PATCH 7/8] Fix bytecode disassembler crashes with --disassemble --interpreter - Add bounds checking for stringPool access in RETRIEVE_BEGIN_HASH - Add try-catch wrapper around disassembly loop to gracefully handle malformed bytecode instead of crashing - Add SLOW_OP handler (deprecated but needed for completeness) Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../backend/bytecode/InterpretedCode.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) 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(); } From 774b888ba5e72218c146162e240ddbf94a100178 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 6 Mar 2026 17:59:44 +0100 Subject: [PATCH 8/8] Fix compound assignment in eval by using compileNode() with SCALAR context The handleCompoundAssignment() method was using node.left.accept(this) without setting SCALAR context, causing incorrect behavior when the outer context was different (e.g., in eval blocks). This caused expressions like `($x &= $y) .= "x"` to fail inside eval. Also removes the incorrect LIST_TO_COUNT conversion which was destroying lvalue references for non-simple left operands. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../backend/bytecode/BytecodeCompiler.java | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index 609a904f7..d14e585b8 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -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