Skip to content
4 changes: 3 additions & 1 deletion dev/tools/perl_test_runner.pl
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,9 @@ sub run_single_test {
| op/sprintf.t
| base/lex.t }x
? "warn" : "";
local $ENV{JPERL_LARGECODE} = $test_file =~ m{opbasic/concat\.t$}
local $ENV{JPERL_LARGECODE} = $test_file =~ m{
opbasic/concat\.t$
| op/pack\.t$ }x
? "refactor" : "";
local $ENV{JPERL_OPTS} = $test_file =~ m{
re/pat.t
Expand Down
81 changes: 35 additions & 46 deletions src/main/java/org/perlonjava/codegen/EmitBinaryOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import static org.perlonjava.codegen.EmitOperator.emitOperator;

public class EmitBinaryOperator {
static final boolean ENABLE_SPILL_BINARY_LHS = System.getenv("JPERL_NO_SPILL_BINARY_LHS") == null;
static final boolean ENABLE_SPILL_BINARY_LHS = true;

static void handleBinaryOperator(EmitterVisitor emitterVisitor, BinaryOperatorNode node, OperatorHandler operatorHandler) {
EmitterVisitor scalarVisitor =
Expand Down Expand Up @@ -181,25 +181,20 @@ static void handleBinaryOperator(EmitterVisitor emitterVisitor, BinaryOperatorNo
}

MethodVisitor mv = emitterVisitor.ctx.mv;
if (ENABLE_SPILL_BINARY_LHS) {
node.left.accept(scalarVisitor); // left parameter
int leftSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
boolean pooled = leftSlot >= 0;
if (!pooled) {
leftSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
}
mv.visitVarInsn(Opcodes.ASTORE, leftSlot);
node.left.accept(scalarVisitor); // left parameter
int leftSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
boolean pooled = leftSlot >= 0;
if (!pooled) {
leftSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
}
mv.visitVarInsn(Opcodes.ASTORE, leftSlot);

right.accept(scalarVisitor); // right parameter
right.accept(scalarVisitor); // right parameter

mv.visitVarInsn(Opcodes.ALOAD, leftSlot);
mv.visitInsn(Opcodes.SWAP);
if (pooled) {
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
}
} else {
node.left.accept(scalarVisitor); // left parameter
right.accept(scalarVisitor); // right parameter
mv.visitVarInsn(Opcodes.ALOAD, leftSlot);
mv.visitInsn(Opcodes.SWAP);
if (pooled) {
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
}
// stack: [left, right]
emitOperator(node, emitterVisitor);
Expand All @@ -210,37 +205,31 @@ static void handleCompoundAssignment(EmitterVisitor emitterVisitor, BinaryOperat
EmitterVisitor scalarVisitor =
emitterVisitor.with(RuntimeContextType.SCALAR); // execute operands in scalar context
MethodVisitor mv = emitterVisitor.ctx.mv;
if (ENABLE_SPILL_BINARY_LHS) {
node.left.accept(scalarVisitor); // target - left parameter
int leftSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
boolean pooledLeft = leftSlot >= 0;
if (!pooledLeft) {
leftSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
}
mv.visitVarInsn(Opcodes.ASTORE, leftSlot);
node.left.accept(scalarVisitor); // target - left parameter
int leftSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
boolean pooledLeft = leftSlot >= 0;
if (!pooledLeft) {
leftSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
}
mv.visitVarInsn(Opcodes.ASTORE, leftSlot);

node.right.accept(scalarVisitor); // right parameter
int rightSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
boolean pooledRight = rightSlot >= 0;
if (!pooledRight) {
rightSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
}
mv.visitVarInsn(Opcodes.ASTORE, rightSlot);
node.right.accept(scalarVisitor); // right parameter
int rightSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
boolean pooledRight = rightSlot >= 0;
if (!pooledRight) {
rightSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
}
mv.visitVarInsn(Opcodes.ASTORE, rightSlot);

mv.visitVarInsn(Opcodes.ALOAD, leftSlot);
mv.visitInsn(Opcodes.DUP);
mv.visitVarInsn(Opcodes.ALOAD, rightSlot);
mv.visitVarInsn(Opcodes.ALOAD, leftSlot);
mv.visitInsn(Opcodes.DUP);
mv.visitVarInsn(Opcodes.ALOAD, rightSlot);

if (pooledRight) {
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
}
if (pooledLeft) {
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
}
} else {
node.left.accept(scalarVisitor); // target - left parameter
mv.visitInsn(Opcodes.DUP);
node.right.accept(scalarVisitor); // right parameter
if (pooledRight) {
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
}
if (pooledLeft) {
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
}
// perform the operation
String baseOperator = node.operator.substring(0, node.operator.length() - 1);
Expand Down
68 changes: 61 additions & 7 deletions src/main/java/org/perlonjava/codegen/EmitControlFlow.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.perlonjava.runtime.ControlFlowType;
import org.perlonjava.runtime.PerlCompilerException;
import org.perlonjava.runtime.RuntimeContextType;
import org.perlonjava.runtime.RuntimeScalarType;

/**
* Handles the emission of control flow bytecode instructions for Perl-like language constructs.
Expand Down Expand Up @@ -186,6 +187,7 @@ static void handleReturnOperator(EmitterVisitor emitterVisitor, OperatorNode nod
node.operand.accept(emitterVisitor.with(RuntimeContextType.RUNTIME));
}

ctx.mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.returnValueSlot);
ctx.mv.visitJumpInsn(Opcodes.GOTO, ctx.javaClassInfo.returnLabel);
}

Expand Down Expand Up @@ -252,6 +254,7 @@ static void handleGotoSubroutine(EmitterVisitor emitterVisitor, OperatorNode sub
}

// Jump to returnLabel (trampoline will handle it)
ctx.mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.returnValueSlot);
ctx.mv.visitJumpInsn(Opcodes.GOTO, ctx.javaClassInfo.returnLabel);
}

Expand Down Expand Up @@ -297,38 +300,87 @@ static void handleGotoLabel(EmitterVisitor emitterVisitor, OperatorNode node) {

// Evaluate the expression to get the label name at runtime
arg.accept(emitterVisitor.with(RuntimeContextType.SCALAR));


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

// If EXPR evaluates to a CODE reference, treat it like `goto &NAME` (tail call).
Label notCodeRef = new Label();
ctx.mv.visitVarInsn(Opcodes.ALOAD, targetSlot);
ctx.mv.visitFieldInsn(Opcodes.GETFIELD,
"org/perlonjava/runtime/RuntimeScalar",
"type",
"I");
ctx.mv.visitLdcInsn(RuntimeScalarType.CODE);
ctx.mv.visitJumpInsn(Opcodes.IF_ICMPNE, notCodeRef);

// Build a TAILCALL marker with the coderef and the current @_ array.
ctx.mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/RuntimeControlFlowList");
ctx.mv.visitInsn(Opcodes.DUP);
ctx.mv.visitVarInsn(Opcodes.ALOAD, targetSlot);
ctx.mv.visitVarInsn(Opcodes.ALOAD, 1); // current @_
ctx.mv.visitLdcInsn(ctx.compilerOptions.fileName != null ? ctx.compilerOptions.fileName : "(eval)");
int tailLineNumber = ctx.errorUtil != null ? ctx.errorUtil.getLineNumber(node.tokenIndex) : 0;
ctx.mv.visitLdcInsn(tailLineNumber);
ctx.mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
"org/perlonjava/runtime/RuntimeControlFlowList",
"<init>",
"(Lorg/perlonjava/runtime/RuntimeScalar;Lorg/perlonjava/runtime/RuntimeArray;Ljava/lang/String;I)V",
false);

ctx.javaClassInfo.resetStackLevel(); // Clean up stack before jumping

if (pooledTarget) {
ctx.javaClassInfo.releaseSpillSlot();
}

ctx.mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.returnValueSlot);
ctx.mv.visitJumpInsn(Opcodes.GOTO, ctx.javaClassInfo.returnLabel);


// Otherwise, treat it as a computed label name (dynamic goto).
ctx.mv.visitLabel(notCodeRef);

ctx.mv.visitVarInsn(Opcodes.ALOAD, targetSlot);
// Convert to string (label name)
ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"org/perlonjava/runtime/RuntimeScalar",
"toString",
"()Ljava/lang/String;",
false);
// For dynamic goto, we always create a RuntimeControlFlowList
// because we can't know at compile-time if the label is local or not

// For dynamic goto, create a RuntimeControlFlowList marker
// because we can't know at compile-time if the label is local or not.
ctx.mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/RuntimeControlFlowList");
ctx.mv.visitInsn(Opcodes.DUP_X1); // Stack: label, RuntimeControlFlowList, RuntimeControlFlowList
ctx.mv.visitInsn(Opcodes.SWAP); // Stack: label, RuntimeControlFlowList, label, RuntimeControlFlowList

ctx.mv.visitFieldInsn(Opcodes.GETSTATIC,
"org/perlonjava/runtime/ControlFlowType",
"GOTO",
"Lorg/perlonjava/runtime/ControlFlowType;");
ctx.mv.visitInsn(Opcodes.SWAP); // Stack: ..., ControlFlowType, label

// Push fileName
ctx.mv.visitLdcInsn(ctx.compilerOptions.fileName != null ? ctx.compilerOptions.fileName : "(eval)");
// Push lineNumber
int lineNumber = ctx.errorUtil != null ? ctx.errorUtil.getLineNumber(node.tokenIndex) : 0;
ctx.mv.visitLdcInsn(lineNumber);

ctx.mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
"org/perlonjava/runtime/RuntimeControlFlowList",
"<init>",
"(Lorg/perlonjava/runtime/ControlFlowType;Ljava/lang/String;Ljava/lang/String;I)V",
false);

if (pooledTarget) {
ctx.javaClassInfo.releaseSpillSlot();
}

int markerSlot = ctx.javaClassInfo.acquireSpillSlot();
boolean pooledMarker = markerSlot >= 0;
if (!pooledMarker) {
Expand All @@ -342,6 +394,7 @@ static void handleGotoLabel(EmitterVisitor emitterVisitor, OperatorNode node) {
if (pooledMarker) {
ctx.javaClassInfo.releaseSpillSlot();
}
ctx.mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.returnValueSlot);
ctx.mv.visitJumpInsn(Opcodes.GOTO, ctx.javaClassInfo.returnLabel);
return;
}
Expand Down Expand Up @@ -390,6 +443,7 @@ static void handleGotoLabel(EmitterVisitor emitterVisitor, OperatorNode node) {
if (pooledMarker) {
ctx.javaClassInfo.releaseSpillSlot();
}
ctx.mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.returnValueSlot);
ctx.mv.visitJumpInsn(Opcodes.GOTO, ctx.javaClassInfo.returnLabel);
return;
}
Expand Down
Loading