1111import org .perlonjava .runtime .ControlFlowType ;
1212import org .perlonjava .runtime .PerlCompilerException ;
1313import 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