diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java index 98010eb1e..576357ec7 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java @@ -1,9 +1,44 @@ package org.perlonjava.backend.bytecode; +import org.perlonjava.frontend.analysis.FindDeclarationVisitor; import org.perlonjava.frontend.astnode.*; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; public class CompileBinaryOperator { + + private static class DeclRewrite { + final OperatorNode declaration; + final String savedOperator; + final Node savedOperand; + + DeclRewrite(OperatorNode declaration, String savedOperator, Node savedOperand) { + this.declaration = declaration; + this.savedOperator = savedOperator; + this.savedOperand = savedOperand; + } + + void restore() { + declaration.operator = savedOperator; + declaration.operand = savedOperand; + } + } + + private static DeclRewrite extractMyDeclaration(BytecodeCompiler bc, Node rightNode) { + OperatorNode decl = FindDeclarationVisitor.findOperator(rightNode, "my"); + if (decl != null && decl.operand instanceof OperatorNode innerOp) { + String savedOp = decl.operator; + Node savedOperand = decl.operand; + int savedCtx = bc.currentCallContext; + bc.currentCallContext = RuntimeContextType.VOID; + decl.accept(bc); + bc.currentCallContext = savedCtx; + decl.operator = innerOp.operator; + decl.operand = innerOp.operand; + return new DeclRewrite(decl, savedOp, savedOperand); + } + return null; + } + static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperatorNode node) { // Track token index for error reporting bytecodeCompiler.currentTokenIndex = node.getIndex(); @@ -430,134 +465,115 @@ else if (node.right instanceof BinaryOperatorNode) { // 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 - - // Compile left operand in scalar context (need boolean value) - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.left.accept(bytecodeCompiler); - int rs1 = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext; - - // Allocate result register and move left value to it - int rd = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(Opcodes.MOVE); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(rs1); + DeclRewrite rewrite = extractMyDeclaration(bytecodeCompiler, node.right); + try { + int savedContext = bytecodeCompiler.currentCallContext; + bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; + node.left.accept(bytecodeCompiler); + int rs1 = bytecodeCompiler.lastResultReg; + bytecodeCompiler.currentCallContext = savedContext; - // Mark position for forward jump - int skipRightPos = bytecodeCompiler.bytecode.size(); + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.MOVE); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(rs1); - // 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) + int skipRightPos = bytecodeCompiler.bytecode.size(); + bytecodeCompiler.emit(Opcodes.GOTO_IF_FALSE); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitInt(0); - // NOW compile right operand (only executed if left was true) - node.right.accept(bytecodeCompiler); - int rs2 = bytecodeCompiler.lastResultReg; + node.right.accept(bytecodeCompiler); + int rs2 = bytecodeCompiler.lastResultReg; - // Move right result to rd (overwriting left value) - bytecodeCompiler.emit(Opcodes.MOVE); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(rs2); + bytecodeCompiler.emit(Opcodes.MOVE); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(rs2); - // Patch the forward jump offset - int skipRightTarget = bytecodeCompiler.bytecode.size(); - bytecodeCompiler.patchIntOffset(skipRightPos + 2, skipRightTarget); + int skipRightTarget = bytecodeCompiler.bytecode.size(); + bytecodeCompiler.patchIntOffset(skipRightPos + 2, skipRightTarget); - bytecodeCompiler.lastResultReg = rd; + bytecodeCompiler.lastResultReg = rd; + } finally { + if (rewrite != null) rewrite.restore(); + } return; } if (node.operator.equals("||") || node.operator.equals("or")) { - // Logical OR with short-circuit evaluation - // Only evaluate right side if left side is false - - // Compile left operand in scalar context (need boolean value) - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.left.accept(bytecodeCompiler); - int rs1 = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext; - - // Allocate result register and move left value to it - int rd = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(Opcodes.MOVE); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(rs1); + DeclRewrite rewrite = extractMyDeclaration(bytecodeCompiler, node.right); + try { + int savedContext = bytecodeCompiler.currentCallContext; + bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; + node.left.accept(bytecodeCompiler); + int rs1 = bytecodeCompiler.lastResultReg; + bytecodeCompiler.currentCallContext = savedContext; - // Mark position for forward jump - int skipRightPos = bytecodeCompiler.bytecode.size(); + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.MOVE); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(rs1); - // 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) + int skipRightPos = bytecodeCompiler.bytecode.size(); + bytecodeCompiler.emit(Opcodes.GOTO_IF_TRUE); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitInt(0); - // NOW compile right operand (only executed if left was false) - node.right.accept(bytecodeCompiler); - int rs2 = bytecodeCompiler.lastResultReg; + node.right.accept(bytecodeCompiler); + int rs2 = bytecodeCompiler.lastResultReg; - // Move right result to rd (overwriting left value) - bytecodeCompiler.emit(Opcodes.MOVE); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(rs2); + bytecodeCompiler.emit(Opcodes.MOVE); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(rs2); - // Patch the forward jump offset - int skipRightTarget = bytecodeCompiler.bytecode.size(); - bytecodeCompiler.patchIntOffset(skipRightPos + 2, skipRightTarget); + int skipRightTarget = bytecodeCompiler.bytecode.size(); + bytecodeCompiler.patchIntOffset(skipRightPos + 2, skipRightTarget); - bytecodeCompiler.lastResultReg = rd; + bytecodeCompiler.lastResultReg = rd; + } finally { + if (rewrite != null) rewrite.restore(); + } return; } if (node.operator.equals("//")) { - // Defined-OR with short-circuit evaluation - // Only evaluate right side if left side is undefined - - // Compile left operand in scalar context (need to test definedness) - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - node.left.accept(bytecodeCompiler); - int rs1 = bytecodeCompiler.lastResultReg; - bytecodeCompiler.currentCallContext = savedContext; - - // Allocate result register and move left value to it - int rd = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(Opcodes.MOVE); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(rs1); + DeclRewrite rewrite = extractMyDeclaration(bytecodeCompiler, node.right); + try { + int savedContext = bytecodeCompiler.currentCallContext; + bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; + node.left.accept(bytecodeCompiler); + int rs1 = bytecodeCompiler.lastResultReg; + bytecodeCompiler.currentCallContext = savedContext; - // Check if left is defined - int definedReg = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(Opcodes.DEFINED); - bytecodeCompiler.emitReg(definedReg); - bytecodeCompiler.emitReg(rd); + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.MOVE); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(rs1); - // Mark position for forward jump - int skipRightPos = bytecodeCompiler.bytecode.size(); + int definedReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.DEFINED); + bytecodeCompiler.emitReg(definedReg); + bytecodeCompiler.emitReg(rd); - // 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) + int skipRightPos = bytecodeCompiler.bytecode.size(); + bytecodeCompiler.emit(Opcodes.GOTO_IF_TRUE); + bytecodeCompiler.emitReg(definedReg); + bytecodeCompiler.emitInt(0); - // NOW compile right operand (only executed if left was undefined) - node.right.accept(bytecodeCompiler); - int rs2 = bytecodeCompiler.lastResultReg; + node.right.accept(bytecodeCompiler); + int rs2 = bytecodeCompiler.lastResultReg; - // Move right result to rd (overwriting left value) - bytecodeCompiler.emit(Opcodes.MOVE); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(rs2); + bytecodeCompiler.emit(Opcodes.MOVE); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(rs2); - // Patch the forward jump offset - int skipRightTarget = bytecodeCompiler.bytecode.size(); - bytecodeCompiler.patchIntOffset(skipRightPos + 2, skipRightTarget); + int skipRightTarget = bytecodeCompiler.bytecode.size(); + bytecodeCompiler.patchIntOffset(skipRightPos + 2, skipRightTarget); - bytecodeCompiler.lastResultReg = rd; + bytecodeCompiler.lastResultReg = rd; + } finally { + if (rewrite != null) rewrite.restore(); + } return; } diff --git a/src/main/java/org/perlonjava/runtime/mro/InheritanceResolver.java b/src/main/java/org/perlonjava/runtime/mro/InheritanceResolver.java index dae5aa00b..eccbb790a 100644 --- a/src/main/java/org/perlonjava/runtime/mro/InheritanceResolver.java +++ b/src/main/java/org/perlonjava/runtime/mro/InheritanceResolver.java @@ -318,18 +318,18 @@ public static RuntimeScalar findMethodInHierarchy(String methodName, String perl if (GlobalVariable.existsGlobalCodeRef(normalizedClassMethodName)) { RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(normalizedClassMethodName); // Perl method lookup should ignore undefined CODE slots (e.g. after `undef *pkg::method`). - if (!codeRef.getDefinedBoolean()) { - continue; - } - // Cache the found method - cacheMethod(cacheKey, codeRef); - - if (TRACE_METHOD_RESOLUTION) { - System.err.println(" FOUND method!"); - System.err.flush(); + // But don't skip to next class — fall through to AUTOLOAD check for this class. + if (codeRef.getDefinedBoolean()) { + // Cache the found method + cacheMethod(cacheKey, codeRef); + + if (TRACE_METHOD_RESOLUTION) { + System.err.println(" FOUND method!"); + System.err.flush(); + } + + return codeRef; } - - return codeRef; } // Method not found in current class, check AUTOLOAD