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
47 changes: 47 additions & 0 deletions src/main/java/org/perlonjava/codegen/Dereference.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ static void handleArrayElementOperator(EmitterVisitor emitterVisitor, BinaryOper
emitterVisitor.ctx.logDebug("visit(BinaryOperatorNode) $var[] ");
varNode.accept(emitterVisitor.with(RuntimeContextType.LIST)); // target - left parameter

int arraySlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
boolean pooledArray = arraySlot >= 0;
if (!pooledArray) {
arraySlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
}
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, arraySlot);

ArrayLiteralNode right = (ArrayLiteralNode) node.right;
if (right.elements.size() == 1) {
Node elem = right.elements.getFirst();
Expand All @@ -45,29 +52,40 @@ static void handleArrayElementOperator(EmitterVisitor emitterVisitor, BinaryOper
if (elem instanceof NumberNode numberNode && numberNode.value.indexOf('.') == -1) {
try {
int index = Integer.parseInt(numberNode.value);
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, arraySlot);
emitterVisitor.ctx.mv.visitLdcInsn(index);
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/RuntimeArray",
arrayOperation, "(I)Lorg/perlonjava/runtime/RuntimeScalar;", false);
} catch (NumberFormatException e) {
// Fall back to RuntimeScalar if the number is too large
elem.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, arraySlot);
emitterVisitor.ctx.mv.visitInsn(Opcodes.SWAP);
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/RuntimeArray",
arrayOperation, "(Lorg/perlonjava/runtime/RuntimeScalar;)Lorg/perlonjava/runtime/RuntimeScalar;", false);
}
} else {
// Single element but not an integer literal
elem.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, arraySlot);
emitterVisitor.ctx.mv.visitInsn(Opcodes.SWAP);
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/RuntimeArray",
arrayOperation, "(Lorg/perlonjava/runtime/RuntimeScalar;)Lorg/perlonjava/runtime/RuntimeScalar;", false);
}
} else {
// emit the [0] as a RuntimeList
ListNode nodeRight = right.asListNode();
nodeRight.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, arraySlot);
emitterVisitor.ctx.mv.visitInsn(Opcodes.SWAP);
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/RuntimeArray",
arrayOperation, "(Lorg/perlonjava/runtime/RuntimeScalar;)Lorg/perlonjava/runtime/RuntimeScalar;", false);
}

if (pooledArray) {
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
}

EmitOperator.handleVoidContext(emitterVisitor);
return;
}
Expand All @@ -87,18 +105,29 @@ static void handleArrayElementOperator(EmitterVisitor emitterVisitor, BinaryOper

// Evaluate the block expression to get a RuntimeScalar (might be array/hash ref)
sigilNode.operand.accept(scalarVisitor);

int baseSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
boolean pooledBase = baseSlot >= 0;
if (!pooledBase) {
baseSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
}
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, baseSlot);

// Now apply the subscript using arrayDerefGet method
ArrayLiteralNode right = (ArrayLiteralNode) node.right;
if (right.elements.size() == 1) {
Node elem = right.elements.getFirst();
elem.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, baseSlot);
emitterVisitor.ctx.mv.visitInsn(Opcodes.SWAP);
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/RuntimeScalar",
"arrayDerefGet", "(Lorg/perlonjava/runtime/RuntimeScalar;)Lorg/perlonjava/runtime/RuntimeScalar;", false);
} else {
// Multiple indices - use slice
ListNode nodeRight = right.asListNode();
nodeRight.accept(emitterVisitor.with(RuntimeContextType.LIST));
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, baseSlot);
emitterVisitor.ctx.mv.visitInsn(Opcodes.SWAP);
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/RuntimeScalar",
"arrayDerefGetSlice", "(Lorg/perlonjava/runtime/RuntimeList;)Lorg/perlonjava/runtime/RuntimeList;", false);

Expand All @@ -110,6 +139,10 @@ static void handleArrayElementOperator(EmitterVisitor emitterVisitor, BinaryOper
emitterVisitor.ctx.mv.visitInsn(Opcodes.POP);
}
}

if (pooledBase) {
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
}

EmitOperator.handleVoidContext(emitterVisitor);
return;
Expand All @@ -126,13 +159,27 @@ static void handleArrayElementOperator(EmitterVisitor emitterVisitor, BinaryOper
emitterVisitor.ctx.logDebug("visit(BinaryOperatorNode) @var[] ");
sigilNode.accept(emitterVisitor.with(RuntimeContextType.LIST)); // target - left parameter

int arraySlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
boolean pooledArray = arraySlot >= 0;
if (!pooledArray) {
arraySlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
}
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, arraySlot);

// emit the [10, 20] as a RuntimeList
ListNode nodeRight = ((ArrayLiteralNode) node.right).asListNode();
nodeRight.accept(emitterVisitor.with(RuntimeContextType.LIST));

emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, arraySlot);
emitterVisitor.ctx.mv.visitInsn(Opcodes.SWAP);

emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/RuntimeArray",
arrayOperation + "Slice", "(Lorg/perlonjava/runtime/RuntimeList;)Lorg/perlonjava/runtime/RuntimeList;", false);

if (pooledArray) {
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
}

// Handle context conversion for array slices
if (emitterVisitor.ctx.contextType == RuntimeContextType.SCALAR) {
// Convert RuntimeList to RuntimeScalar (Perl scalar slice semantics = last element or undef)
Expand Down
137 changes: 91 additions & 46 deletions src/main/java/org/perlonjava/codegen/EmitLogicalOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.perlonjava.astnode.Node;
import org.perlonjava.astnode.BinaryOperatorNode;
import org.perlonjava.astnode.OperatorNode;
import org.perlonjava.astnode.TernaryOperatorNode;
Expand Down Expand Up @@ -163,48 +164,59 @@ static void emitLogicalOperator(EmitterVisitor emitterVisitor, BinaryOperatorNod
// check if the right operand contains a variable declaration,
// if so, move the declaration outside of the logical operator
OperatorNode declaration = FindDeclarationVisitor.findOperator(node.right, "my");
if (declaration != null) {
if (declaration.operand instanceof OperatorNode operatorNode) {

String savedOperator = null;
org.perlonjava.astnode.Node savedOperand = null;
boolean rewritten = false;
try {
if (declaration != null && declaration.operand instanceof OperatorNode operatorNode) {
savedOperator = declaration.operator;
savedOperand = declaration.operand;

// emit bytecode for the declaration
declaration.accept(emitterVisitor.with(RuntimeContextType.VOID));
// replace the declaration with it's operand
// replace the declaration with its operand (temporarily)
declaration.operator = operatorNode.operator;
declaration.operand = operatorNode.operand;
} else {
// TODO: find an example where this happens
rewritten = true;
}
}

// Evaluate LHS in scalar context (for boolean test)
node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
// Stack: [RuntimeScalar]
// Evaluate LHS in scalar context (for boolean test)
node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
// Stack: [RuntimeScalar]

mv.visitInsn(Opcodes.DUP);
// Stack: [RuntimeScalar, RuntimeScalar]
mv.visitInsn(Opcodes.DUP);
// Stack: [RuntimeScalar, RuntimeScalar]

// Test boolean value
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/RuntimeBase", getBoolean, "()Z", false);
// Stack: [RuntimeScalar, boolean]
// Test boolean value
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/RuntimeBase", getBoolean, "()Z", false);
// Stack: [RuntimeScalar, boolean]

// If true, jump to convert label
mv.visitJumpInsn(compareOpcode, convertLabel);
// If true, jump to convert label
mv.visitJumpInsn(compareOpcode, convertLabel);

// LHS is false: evaluate RHS in LIST context
mv.visitInsn(Opcodes.POP); // Remove LHS
node.right.accept(emitterVisitor.with(RuntimeContextType.LIST));
// Stack: [RuntimeList]
mv.visitJumpInsn(Opcodes.GOTO, endLabel);
// LHS is false: evaluate RHS in LIST context
mv.visitInsn(Opcodes.POP); // Remove LHS
node.right.accept(emitterVisitor.with(RuntimeContextType.LIST));
// Stack: [RuntimeList]
mv.visitJumpInsn(Opcodes.GOTO, endLabel);

// LHS is true: convert scalar to list
mv.visitLabel(convertLabel);
// Stack: [RuntimeScalar]
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/RuntimeScalar",
"getList", "()Lorg/perlonjava/runtime/RuntimeList;", false);
// Stack: [RuntimeList]
// LHS is true: convert scalar to list
mv.visitLabel(convertLabel);
// Stack: [RuntimeScalar]
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/RuntimeScalar",
"getList", "()Lorg/perlonjava/runtime/RuntimeList;", false);
// Stack: [RuntimeList]

mv.visitLabel(endLabel);
// Stack: [RuntimeList] from both branches
EmitOperator.handleVoidContext(emitterVisitor);
mv.visitLabel(endLabel);
// Stack: [RuntimeList] from both branches
EmitOperator.handleVoidContext(emitterVisitor);
} finally {
if (rewritten) {
declaration.operator = savedOperator;
declaration.operand = savedOperand;
}
}
}

/**
Expand Down Expand Up @@ -283,29 +295,62 @@ private static void emitLogicalOperatorSimple(EmitterVisitor emitterVisitor, Bin
return;
}

// check if the right operand contains a variable declaration
// If the right operand contains a variable declaration (e.g. `... && my $x = ...`),
// emit the declaration first and then temporarily rewrite that AST node so the logical
// operator sees just the declared variable/expression.
//
// IMPORTANT: this rewrite must be temporary. The compiler may re-run code generation
// (e.g. diagnostic second pass after an ASM frame compute crash), and mutating the AST
// permanently can change semantics and even introduce spurious errors.
OperatorNode declaration = FindDeclarationVisitor.findOperator(node.right, "my");
if (declaration != null) {
if (declaration.operand instanceof OperatorNode operatorNode) {
String savedOperator = null;
Node savedOperand = null;
boolean rewritten = false;
JavaClassInfo.SpillRef resultRef = null;
try {
if (declaration != null && declaration.operand instanceof OperatorNode operatorNode) {
savedOperator = declaration.operator;
savedOperand = declaration.operand;

declaration.accept(emitterVisitor.with(RuntimeContextType.VOID));
declaration.operator = operatorNode.operator;
declaration.operand = operatorNode.operand;
rewritten = true;
}
}

// For RUNTIME context, preserve it; otherwise use SCALAR for boolean evaluation
int operandContext = emitterVisitor.ctx.contextType == RuntimeContextType.RUNTIME
? RuntimeContextType.RUNTIME
: RuntimeContextType.SCALAR;
// For RUNTIME context, preserve it; otherwise use SCALAR for boolean evaluation
int operandContext = emitterVisitor.ctx.contextType == RuntimeContextType.RUNTIME
? RuntimeContextType.RUNTIME
: RuntimeContextType.SCALAR;

node.left.accept(emitterVisitor.with(operandContext));
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/RuntimeBase", getBoolean, "()Z", false);
mv.visitJumpInsn(compareOpcode, endLabel);
mv.visitInsn(Opcodes.POP);
node.right.accept(emitterVisitor.with(operandContext));
mv.visitLabel(endLabel);
EmitOperator.handleVoidContext(emitterVisitor);
resultRef = emitterVisitor.ctx.javaClassInfo.acquireSpillRefOrAllocate(emitterVisitor.ctx.symbolTable);

// Evaluate LHS and store it.
node.left.accept(emitterVisitor.with(operandContext));
emitterVisitor.ctx.javaClassInfo.storeSpillRef(mv, resultRef);

// Boolean test on the stored LHS.
emitterVisitor.ctx.javaClassInfo.loadSpillRef(mv, resultRef);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/RuntimeBase", getBoolean, "()Z", false);
mv.visitJumpInsn(compareOpcode, endLabel);

// LHS didn't short-circuit: evaluate RHS, overwrite result.
node.right.accept(emitterVisitor.with(operandContext));
emitterVisitor.ctx.javaClassInfo.storeSpillRef(mv, resultRef);

// Return whichever side won the short-circuit.
mv.visitLabel(endLabel);
emitterVisitor.ctx.javaClassInfo.loadSpillRef(mv, resultRef);
EmitOperator.handleVoidContext(emitterVisitor);
} finally {
if (resultRef != null) {
emitterVisitor.ctx.javaClassInfo.releaseSpillRef(resultRef);
}
if (rewritten) {
declaration.operator = savedOperator;
declaration.operand = savedOperand;
}
}
}

/**
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/org/perlonjava/runtime/RuntimeList.java
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,18 @@ public RuntimeArray setFromList(RuntimeList value) {
List<RuntimeScalar> remaining = (rhsIndex < rhsSize)
? new ArrayList<>(rhsElements.subList(rhsIndex, rhsSize))
: new ArrayList<>();
runtimeArray.elements = remaining;

// For tied arrays, we must assign through the tied interface.
// Overwriting `elements` would discard the TieArray wrapper while leaving
// `type == TIED_ARRAY`, leading to ClassCastException when tie operations
// cast `array.elements` back to TieArray.
if (runtimeArray.type == RuntimeArray.TIED_ARRAY) {
RuntimeList remainingList = new RuntimeList();
remainingList.elements.addAll(remaining);
runtimeArray.setFromList(remainingList);
} else {
runtimeArray.elements = remaining;
}
result.elements.addAll(remaining); // Use original references
rhsIndex = rhsSize; // Consume the rest
} else if (elem instanceof RuntimeHash runtimeHash) {
Expand Down