Skip to content

Commit ed652d6

Browse files
authored
Merge pull request #159 from fglock/fix-eval-controlflow
Fix eval control-flow and semantics
2 parents e0c7e29 + 8fce6a2 commit ed652d6

21 files changed

Lines changed: 655 additions & 216 deletions

dev/tools/perl_test_runner.pl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,9 @@ sub run_single_test {
251251
| op/sprintf.t
252252
| base/lex.t }x
253253
? "warn" : "";
254-
local $ENV{JPERL_LARGECODE} = $test_file =~ m{opbasic/concat\.t$}
254+
local $ENV{JPERL_LARGECODE} = $test_file =~ m{
255+
opbasic/concat\.t$
256+
| op/pack\.t$ }x
255257
? "refactor" : "";
256258
local $ENV{JPERL_OPTS} = $test_file =~ m{
257259
re/pat.t

src/main/java/org/perlonjava/codegen/EmitBinaryOperator.java

Lines changed: 35 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import static org.perlonjava.codegen.EmitOperator.emitOperator;
1616

1717
public class EmitBinaryOperator {
18-
static final boolean ENABLE_SPILL_BINARY_LHS = System.getenv("JPERL_NO_SPILL_BINARY_LHS") == null;
18+
static final boolean ENABLE_SPILL_BINARY_LHS = true;
1919

2020
static void handleBinaryOperator(EmitterVisitor emitterVisitor, BinaryOperatorNode node, OperatorHandler operatorHandler) {
2121
EmitterVisitor scalarVisitor =
@@ -181,25 +181,20 @@ static void handleBinaryOperator(EmitterVisitor emitterVisitor, BinaryOperatorNo
181181
}
182182

183183
MethodVisitor mv = emitterVisitor.ctx.mv;
184-
if (ENABLE_SPILL_BINARY_LHS) {
185-
node.left.accept(scalarVisitor); // left parameter
186-
int leftSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
187-
boolean pooled = leftSlot >= 0;
188-
if (!pooled) {
189-
leftSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
190-
}
191-
mv.visitVarInsn(Opcodes.ASTORE, leftSlot);
184+
node.left.accept(scalarVisitor); // left parameter
185+
int leftSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
186+
boolean pooled = leftSlot >= 0;
187+
if (!pooled) {
188+
leftSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
189+
}
190+
mv.visitVarInsn(Opcodes.ASTORE, leftSlot);
192191

193-
right.accept(scalarVisitor); // right parameter
192+
right.accept(scalarVisitor); // right parameter
194193

195-
mv.visitVarInsn(Opcodes.ALOAD, leftSlot);
196-
mv.visitInsn(Opcodes.SWAP);
197-
if (pooled) {
198-
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
199-
}
200-
} else {
201-
node.left.accept(scalarVisitor); // left parameter
202-
right.accept(scalarVisitor); // right parameter
194+
mv.visitVarInsn(Opcodes.ALOAD, leftSlot);
195+
mv.visitInsn(Opcodes.SWAP);
196+
if (pooled) {
197+
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
203198
}
204199
// stack: [left, right]
205200
emitOperator(node, emitterVisitor);
@@ -210,37 +205,31 @@ static void handleCompoundAssignment(EmitterVisitor emitterVisitor, BinaryOperat
210205
EmitterVisitor scalarVisitor =
211206
emitterVisitor.with(RuntimeContextType.SCALAR); // execute operands in scalar context
212207
MethodVisitor mv = emitterVisitor.ctx.mv;
213-
if (ENABLE_SPILL_BINARY_LHS) {
214-
node.left.accept(scalarVisitor); // target - left parameter
215-
int leftSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
216-
boolean pooledLeft = leftSlot >= 0;
217-
if (!pooledLeft) {
218-
leftSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
219-
}
220-
mv.visitVarInsn(Opcodes.ASTORE, leftSlot);
208+
node.left.accept(scalarVisitor); // target - left parameter
209+
int leftSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
210+
boolean pooledLeft = leftSlot >= 0;
211+
if (!pooledLeft) {
212+
leftSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
213+
}
214+
mv.visitVarInsn(Opcodes.ASTORE, leftSlot);
221215

222-
node.right.accept(scalarVisitor); // right parameter
223-
int rightSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
224-
boolean pooledRight = rightSlot >= 0;
225-
if (!pooledRight) {
226-
rightSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
227-
}
228-
mv.visitVarInsn(Opcodes.ASTORE, rightSlot);
216+
node.right.accept(scalarVisitor); // right parameter
217+
int rightSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
218+
boolean pooledRight = rightSlot >= 0;
219+
if (!pooledRight) {
220+
rightSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
221+
}
222+
mv.visitVarInsn(Opcodes.ASTORE, rightSlot);
229223

230-
mv.visitVarInsn(Opcodes.ALOAD, leftSlot);
231-
mv.visitInsn(Opcodes.DUP);
232-
mv.visitVarInsn(Opcodes.ALOAD, rightSlot);
224+
mv.visitVarInsn(Opcodes.ALOAD, leftSlot);
225+
mv.visitInsn(Opcodes.DUP);
226+
mv.visitVarInsn(Opcodes.ALOAD, rightSlot);
233227

234-
if (pooledRight) {
235-
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
236-
}
237-
if (pooledLeft) {
238-
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
239-
}
240-
} else {
241-
node.left.accept(scalarVisitor); // target - left parameter
242-
mv.visitInsn(Opcodes.DUP);
243-
node.right.accept(scalarVisitor); // right parameter
228+
if (pooledRight) {
229+
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
230+
}
231+
if (pooledLeft) {
232+
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
244233
}
245234
// perform the operation
246235
String baseOperator = node.operator.substring(0, node.operator.length() - 1);

src/main/java/org/perlonjava/codegen/EmitControlFlow.java

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.perlonjava.runtime.ControlFlowType;
1212
import org.perlonjava.runtime.PerlCompilerException;
1313
import org.perlonjava.runtime.RuntimeContextType;
14+
import org.perlonjava.runtime.RuntimeScalarType;
1415

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

190+
ctx.mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.returnValueSlot);
189191
ctx.mv.visitJumpInsn(Opcodes.GOTO, ctx.javaClassInfo.returnLabel);
190192
}
191193

@@ -252,6 +254,7 @@ static void handleGotoSubroutine(EmitterVisitor emitterVisitor, OperatorNode sub
252254
}
253255

254256
// Jump to returnLabel (trampoline will handle it)
257+
ctx.mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.returnValueSlot);
255258
ctx.mv.visitJumpInsn(Opcodes.GOTO, ctx.javaClassInfo.returnLabel);
256259
}
257260

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

298301
// Evaluate the expression to get the label name at runtime
299302
arg.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
300-
303+
304+
int targetSlot = ctx.javaClassInfo.acquireSpillSlot();
305+
boolean pooledTarget = targetSlot >= 0;
306+
if (!pooledTarget) {
307+
targetSlot = ctx.symbolTable.allocateLocalVariable();
308+
}
309+
ctx.mv.visitVarInsn(Opcodes.ASTORE, targetSlot);
310+
311+
// If EXPR evaluates to a CODE reference, treat it like `goto &NAME` (tail call).
312+
Label notCodeRef = new Label();
313+
ctx.mv.visitVarInsn(Opcodes.ALOAD, targetSlot);
314+
ctx.mv.visitFieldInsn(Opcodes.GETFIELD,
315+
"org/perlonjava/runtime/RuntimeScalar",
316+
"type",
317+
"I");
318+
ctx.mv.visitLdcInsn(RuntimeScalarType.CODE);
319+
ctx.mv.visitJumpInsn(Opcodes.IF_ICMPNE, notCodeRef);
320+
321+
// Build a TAILCALL marker with the coderef and the current @_ array.
322+
ctx.mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/RuntimeControlFlowList");
323+
ctx.mv.visitInsn(Opcodes.DUP);
324+
ctx.mv.visitVarInsn(Opcodes.ALOAD, targetSlot);
325+
ctx.mv.visitVarInsn(Opcodes.ALOAD, 1); // current @_
326+
ctx.mv.visitLdcInsn(ctx.compilerOptions.fileName != null ? ctx.compilerOptions.fileName : "(eval)");
327+
int tailLineNumber = ctx.errorUtil != null ? ctx.errorUtil.getLineNumber(node.tokenIndex) : 0;
328+
ctx.mv.visitLdcInsn(tailLineNumber);
329+
ctx.mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
330+
"org/perlonjava/runtime/RuntimeControlFlowList",
331+
"<init>",
332+
"(Lorg/perlonjava/runtime/RuntimeScalar;Lorg/perlonjava/runtime/RuntimeArray;Ljava/lang/String;I)V",
333+
false);
334+
335+
ctx.javaClassInfo.resetStackLevel(); // Clean up stack before jumping
336+
337+
if (pooledTarget) {
338+
ctx.javaClassInfo.releaseSpillSlot();
339+
}
340+
341+
ctx.mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.returnValueSlot);
342+
ctx.mv.visitJumpInsn(Opcodes.GOTO, ctx.javaClassInfo.returnLabel);
343+
344+
345+
// Otherwise, treat it as a computed label name (dynamic goto).
346+
ctx.mv.visitLabel(notCodeRef);
347+
348+
ctx.mv.visitVarInsn(Opcodes.ALOAD, targetSlot);
301349
// Convert to string (label name)
302350
ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
303351
"org/perlonjava/runtime/RuntimeScalar",
304352
"toString",
305353
"()Ljava/lang/String;",
306354
false);
307-
308-
// For dynamic goto, we always create a RuntimeControlFlowList
309-
// because we can't know at compile-time if the label is local or not
355+
356+
// For dynamic goto, create a RuntimeControlFlowList marker
357+
// because we can't know at compile-time if the label is local or not.
310358
ctx.mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/RuntimeControlFlowList");
311359
ctx.mv.visitInsn(Opcodes.DUP_X1); // Stack: label, RuntimeControlFlowList, RuntimeControlFlowList
312360
ctx.mv.visitInsn(Opcodes.SWAP); // Stack: label, RuntimeControlFlowList, label, RuntimeControlFlowList
313-
361+
314362
ctx.mv.visitFieldInsn(Opcodes.GETSTATIC,
315363
"org/perlonjava/runtime/ControlFlowType",
316364
"GOTO",
317365
"Lorg/perlonjava/runtime/ControlFlowType;");
318366
ctx.mv.visitInsn(Opcodes.SWAP); // Stack: ..., ControlFlowType, label
319-
367+
320368
// Push fileName
321369
ctx.mv.visitLdcInsn(ctx.compilerOptions.fileName != null ? ctx.compilerOptions.fileName : "(eval)");
322370
// Push lineNumber
323371
int lineNumber = ctx.errorUtil != null ? ctx.errorUtil.getLineNumber(node.tokenIndex) : 0;
324372
ctx.mv.visitLdcInsn(lineNumber);
325-
373+
326374
ctx.mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
327375
"org/perlonjava/runtime/RuntimeControlFlowList",
328376
"<init>",
329377
"(Lorg/perlonjava/runtime/ControlFlowType;Ljava/lang/String;Ljava/lang/String;I)V",
330378
false);
331379

380+
if (pooledTarget) {
381+
ctx.javaClassInfo.releaseSpillSlot();
382+
}
383+
332384
int markerSlot = ctx.javaClassInfo.acquireSpillSlot();
333385
boolean pooledMarker = markerSlot >= 0;
334386
if (!pooledMarker) {
@@ -342,6 +394,7 @@ static void handleGotoLabel(EmitterVisitor emitterVisitor, OperatorNode node) {
342394
if (pooledMarker) {
343395
ctx.javaClassInfo.releaseSpillSlot();
344396
}
397+
ctx.mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.returnValueSlot);
345398
ctx.mv.visitJumpInsn(Opcodes.GOTO, ctx.javaClassInfo.returnLabel);
346399
return;
347400
}
@@ -390,6 +443,7 @@ static void handleGotoLabel(EmitterVisitor emitterVisitor, OperatorNode node) {
390443
if (pooledMarker) {
391444
ctx.javaClassInfo.releaseSpillSlot();
392445
}
446+
ctx.mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.returnValueSlot);
393447
ctx.mv.visitJumpInsn(Opcodes.GOTO, ctx.javaClassInfo.returnLabel);
394448
return;
395449
}

0 commit comments

Comments
 (0)