Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 117 additions & 101 deletions src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java
Original file line number Diff line number Diff line change
@@ -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();
Expand Down Expand Up @@ -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;
}

Expand Down
22 changes: 11 additions & 11 deletions src/main/java/org/perlonjava/runtime/mro/InheritanceResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down