diff --git a/src/main/java/org/perlonjava/codegen/Dereference.java b/src/main/java/org/perlonjava/codegen/Dereference.java index 979f5656..08e538a9 100644 --- a/src/main/java/org/perlonjava/codegen/Dereference.java +++ b/src/main/java/org/perlonjava/codegen/Dereference.java @@ -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(); @@ -45,18 +52,23 @@ 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); } @@ -64,10 +76,16 @@ static void handleArrayElementOperator(EmitterVisitor emitterVisitor, BinaryOper // 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; } @@ -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); @@ -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; @@ -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) diff --git a/src/main/java/org/perlonjava/codegen/EmitLogicalOperator.java b/src/main/java/org/perlonjava/codegen/EmitLogicalOperator.java index a40fe9db..516c1ad4 100644 --- a/src/main/java/org/perlonjava/codegen/EmitLogicalOperator.java +++ b/src/main/java/org/perlonjava/codegen/EmitLogicalOperator.java @@ -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; @@ -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; + } + } } /** @@ -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; + } + } } /** diff --git a/src/main/java/org/perlonjava/runtime/RuntimeList.java b/src/main/java/org/perlonjava/runtime/RuntimeList.java index 735b45fc..dd0cfd1d 100644 --- a/src/main/java/org/perlonjava/runtime/RuntimeList.java +++ b/src/main/java/org/perlonjava/runtime/RuntimeList.java @@ -414,7 +414,18 @@ public RuntimeArray setFromList(RuntimeList value) { List 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) {