From c742d956ee41369c070fc3c3e46a226edeaf85f4 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Tue, 10 Mar 2026 15:49:58 +0100 Subject: [PATCH] Fix ASM frame crash when range operator RHS is a constant sub The range operator (..) was not spilling its left operand before evaluating the right side. When the RHS was a constant sub call (e.g., @a[0..CONST]), the non-local control flow handling could leave the JVM operand stack in an inconsistent state, causing "Incompatible stack heights" errors during ASM frame computation. Fix: Apply the same spill pattern used by other binary operators (substr, concat, push, etc.) - store LHS in a local variable before evaluating RHS, then restore it. Reproducer: use constant END => 2; sub test { my @a = (1,2,3,4); return @a[0..END]; } Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../perlonjava/backend/jvm/EmitOperator.java | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java b/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java index 08867fc82..7cecf6518 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java @@ -541,9 +541,31 @@ static void handleGlobBuiltin(EmitterVisitor emitterVisitor, OperatorNode node) // Handles the 'range' operator, which creates a range of values. static void handleRangeOperator(EmitterVisitor emitterVisitor, BinaryOperatorNode node) { - // Accept both left and right operands in SCALAR context. - node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); - node.right.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); + // Spill the left operand before evaluating the right side so non-local control flow + // propagation can't jump to returnLabel with an extra value on the JVM operand stack. + if (ENABLE_SPILL_BINARY_LHS) { + MethodVisitor mv = emitterVisitor.ctx.mv; + node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); + + int leftSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot(); + boolean pooled = leftSlot >= 0; + if (!pooled) { + leftSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable(); + } + mv.visitVarInsn(Opcodes.ASTORE, leftSlot); + + node.right.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); + + mv.visitVarInsn(Opcodes.ALOAD, leftSlot); + mv.visitInsn(Opcodes.SWAP); + + if (pooled) { + emitterVisitor.ctx.javaClassInfo.releaseSpillSlot(); + } + } else { + node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); + node.right.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); + } emitOperator(node, emitterVisitor); }