Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b0c3aed
Add ControlFlowType enum, ControlFlowMarker class, and RuntimeControl…
fglock Nov 6, 2025
e7b78e9
Modify EmitControlFlow to return marked RuntimeList for non-local con…
fglock Nov 6, 2025
48d5cd5
Add control flow check infrastructure (disabled) - WIP Phase 3
fglock Nov 6, 2025
10f631b
Phase 2 complete: Non-local control flow via tagged returns (99.8%)
fglock Nov 6, 2025
937c0b3
Fix plan: Skip Phase 3 (call-site checks), renumber to make straightf…
fglock Nov 6, 2025
86daaee
Phase 3 partial: Add TAILCALL trampoline at returnLabel (99.8% mainta…
fglock Nov 6, 2025
c03f76f
Phase 3: Tail call trampoline working, call-site checks deferred
fglock Nov 6, 2025
28f39a0
feat: Add feature flags and Phase 3 loop handler infrastructure
fglock Nov 6, 2025
0d1cccb
feat: Implement Phase 4 (Top-Level Safety)
fglock Nov 6, 2025
4a8a369
feat: Propagate isMainProgram flag to code generation
fglock Nov 6, 2025
1f5bc8b
feat: Add source location tracking to control flow markers
fglock Nov 6, 2025
5544bbc
feat: Pass source location to RuntimeControlFlowList
fglock Nov 6, 2025
065bc87
docs: Streamline design doc to action-oriented plan
fglock Nov 6, 2025
c4db263
feat: Dynamic slot allocation for control flow temp storage
fglock Nov 6, 2025
a183087
docs: Update plan with ASM frame computation findings
fglock Nov 6, 2025
e8c5a85
docs: Add critical decision document for control flow architecture
fglock Nov 6, 2025
e3bcbad
docs: Document existing SKIP workarounds to be removed
fglock Nov 6, 2025
b9ec4d0
fix: Remove top-level safety check - restored 99.8% pass rate
fglock Nov 6, 2025
0405780
docs: Update plan - Phases 5 & 6 complete (99.9% pass rate)
fglock Nov 6, 2025
c5c895d
test: Add comprehensive control flow unit tests
fglock Nov 6, 2025
fe226a3
Fix goto __SUB__ tail call by detecting it in handleGotoLabel
fglock Nov 6, 2025
326a856
Update MILESTONES.md: goto __SUB__ is now working
fglock Nov 6, 2025
bb0e5fe
Update design document: Phase 7 (Tail Call Trampoline) COMPLETE
fglock Nov 6, 2025
8c674dd
Add comprehensive comments explaining disabled features and next steps
fglock Nov 6, 2025
d5477dd
Update MILESTONES.md: Mark Phase 7 (Non-local control flow) as COMPLETE
fglock Nov 6, 2025
84084ac
Update FEATURE_MATRIX.md: Add tail call features, simplify control fl…
fglock Nov 6, 2025
055630b
docs
fglock Nov 6, 2025
cb28263
fix: Prevent RuntimeControlFlowList from being corrupted as data
fglock Nov 6, 2025
7cc0bf1
docs: Document RuntimeControlFlowList data corruption fix
fglock Nov 6, 2025
2d03783
docs: Comprehensive analysis of ASM frame computation blocker
fglock Nov 6, 2025
ee4cb82
feat: Implement runtime control flow registry for 'last SKIP' support
fglock Nov 6, 2025
b2e23ef
docs: Add comprehensive documentation for control flow registry solution
fglock Nov 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: Add source location tracking to control flow markers
- Added fileName and lineNumber fields to ControlFlowMarker
- Updated RuntimeControlFlowList constructors to accept location info
- Added ControlFlowMarker.throwError() method for consistent error messages
  with source location
- Updated RuntimeCode.apply() to use marker.throwError()
- Updated loop handler to use marker.throwError()
- Removed redundant throwControlFlowError() helper method

Error messages now include exact source location where control flow originated.
Next: Update EmitControlFlow to pass actual file/line numbers.
  • Loading branch information
fglock committed Nov 6, 2025
commit 1f5bc8badef3936309117cf6fef5f30a6a6e5355
10 changes: 8 additions & 2 deletions dev/design/TAGGED_RETURN_CONTROL_FLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -719,19 +719,25 @@ OUTER: for (@outer) {
public final String label; // For LAST/NEXT/REDO/GOTO (null for unlabeled)
public final RuntimeScalar codeRef; // For TAILCALL
public final RuntimeArray args; // For TAILCALL
public final String fileName; // Source file where control flow originated
public final int lineNumber; // Line number where control flow originated

// Constructor for control flow (last/next/redo/goto)
public ControlFlowMarker(ControlFlowType type, String label) {
public ControlFlowMarker(ControlFlowType type, String label, String fileName, int lineNumber) {
this.type = type;
this.label = label;
this.fileName = fileName;
this.lineNumber = lineNumber;
this.codeRef = null;
this.args = null;
}

// Constructor for tail call (goto &NAME)
public ControlFlowMarker(RuntimeScalar codeRef, RuntimeArray args) {
public ControlFlowMarker(RuntimeScalar codeRef, RuntimeArray args, String fileName, int lineNumber) {
this.type = ControlFlowType.TAILCALL;
this.label = null;
this.fileName = fileName;
this.lineNumber = lineNumber;
this.codeRef = codeRef;
this.args = args;
}
Expand Down
28 changes: 8 additions & 20 deletions src/main/java/org/perlonjava/codegen/EmitForeach.java
Original file line number Diff line number Diff line change
Expand Up @@ -422,30 +422,18 @@ private static void emitControlFlowHandler(

// Cast to RuntimeControlFlowList
mv.visitTypeInsn(Opcodes.CHECKCAST, "org/perlonjava/runtime/RuntimeControlFlowList");
mv.visitInsn(Opcodes.DUP);

// Get control flow type
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
// Get the marker field
mv.visitFieldInsn(Opcodes.GETFIELD,
"org/perlonjava/runtime/RuntimeControlFlowList",
"getControlFlowType",
"()Lorg/perlonjava/runtime/ControlFlowType;",
false);
mv.visitInsn(Opcodes.DUP);
"marker",
"Lorg/perlonjava/runtime/ControlFlowMarker;");

// Get label
mv.visitInsn(Opcodes.SWAP);
// Call marker.throwError() - this method never returns
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"org/perlonjava/runtime/RuntimeControlFlowList",
"getControlFlowLabel",
"()Ljava/lang/String;",
false);

// Call helper method to throw appropriate error
// Stack: [ControlFlowType, String label]
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/RuntimeCode",
"throwControlFlowError",
"(Lorg/perlonjava/runtime/ControlFlowType;Ljava/lang/String;)V",
"org/perlonjava/runtime/ControlFlowMarker",
"throwError",
"()V",
false);

// Should never reach here (method throws), but add RETURN for verifier
Expand Down
47 changes: 45 additions & 2 deletions src/main/java/org/perlonjava/runtime/ControlFlowMarker.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,25 @@ public class ControlFlowMarker {
/** The arguments for TAILCALL (goto &NAME) */
public final RuntimeArray args;

/** Source file name where the control flow originated (for error messages) */
public final String fileName;

/** Line number where the control flow originated (for error messages) */
public final int lineNumber;

/**
* Constructor for control flow (last/next/redo/goto).
*
* @param type The control flow type
* @param label The label to jump to (null for unlabeled)
* @param fileName Source file name (for error messages)
* @param lineNumber Line number (for error messages)
*/
public ControlFlowMarker(ControlFlowType type, String label) {
public ControlFlowMarker(ControlFlowType type, String label, String fileName, int lineNumber) {
this.type = type;
this.label = label;
this.fileName = fileName;
this.lineNumber = lineNumber;
this.codeRef = null;
this.args = null;
}
Expand All @@ -35,12 +45,45 @@ public ControlFlowMarker(ControlFlowType type, String label) {
*
* @param codeRef The code reference to call
* @param args The arguments to pass
* @param fileName Source file name (for error messages)
* @param lineNumber Line number (for error messages)
*/
public ControlFlowMarker(RuntimeScalar codeRef, RuntimeArray args) {
public ControlFlowMarker(RuntimeScalar codeRef, RuntimeArray args, String fileName, int lineNumber) {
this.type = ControlFlowType.TAILCALL;
this.label = null;
this.fileName = fileName;
this.lineNumber = lineNumber;
this.codeRef = codeRef;
this.args = args;
}

/**
* Throws an appropriate PerlCompilerException for this control flow that couldn't be handled.
* Includes source file and line number in the error message.
*
* @throws PerlCompilerException Always throws with contextual error message
*/
public void throwError() {
String location = " at " + fileName + " line " + lineNumber;

if (type == ControlFlowType.TAILCALL) {
// Tail call should have been handled by trampoline at returnLabel
throw new PerlCompilerException("Tail call escaped to top level (internal error)" + location);
} else if (type == ControlFlowType.GOTO) {
if (label != null) {
throw new PerlCompilerException("Can't find label " + label + location);
} else {
throw new PerlCompilerException("goto must have a label" + location);
}
} else {
// last/next/redo
String operation = type.name().toLowerCase();
if (label != null) {
throw new PerlCompilerException("Label not found for \"" + operation + " " + label + "\"" + location);
} else {
throw new PerlCompilerException("Can't \"" + operation + "\" outside a loop block" + location);
}
}
}
}

71 changes: 4 additions & 67 deletions src/main/java/org/perlonjava/runtime/RuntimeCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -776,25 +776,8 @@ public RuntimeList apply(RuntimeArray a, int callContext) {
ControlFlowType cfType = cfList.getControlFlowType();
String label = cfList.getControlFlowLabel();

// This is a control flow statement that escaped to top level - error!
if (cfType == ControlFlowType.TAILCALL) {
// Tail call should have been handled by trampoline at returnLabel
throw new PerlCompilerException("Tail call escaped to top level (internal error) at ");
} else if (cfType == ControlFlowType.GOTO) {
if (label != null) {
throw new PerlCompilerException("Can't find label " + label + " at ");
} else {
throw new PerlCompilerException("goto must have a label at ");
}
} else {
// last/next/redo
String operation = cfType.name().toLowerCase();
if (label != null) {
throw new PerlCompilerException("Label not found for \"" + operation + " " + label + "\" at ");
} else {
throw new PerlCompilerException("Can't \"" + operation + "\" outside a loop block at ");
}
}
// Let the marker throw the error with proper source location
cfList.marker.throwError();
}

return result;
Expand Down Expand Up @@ -847,25 +830,8 @@ public RuntimeList apply(String subroutineName, RuntimeArray a, int callContext)
ControlFlowType cfType = cfList.getControlFlowType();
String label = cfList.getControlFlowLabel();

// This is a control flow statement that escaped to top level - error!
if (cfType == ControlFlowType.TAILCALL) {
// Tail call should have been handled by trampoline at returnLabel
throw new PerlCompilerException("Tail call escaped to top level (internal error) at ");
} else if (cfType == ControlFlowType.GOTO) {
if (label != null) {
throw new PerlCompilerException("Can't find label " + label + " at ");
} else {
throw new PerlCompilerException("goto must have a label at ");
}
} else {
// last/next/redo
String operation = cfType.name().toLowerCase();
if (label != null) {
throw new PerlCompilerException("Label not found for \"" + operation + " " + label + "\" at ");
} else {
throw new PerlCompilerException("Can't \"" + operation + "\" outside a loop block at ");
}
}
// Let the marker throw the error with proper source location
cfList.marker.throwError();
}

return result;
Expand Down Expand Up @@ -1003,33 +969,4 @@ public void dynamicRestoreState() {
throw new PerlCompilerException("Can't modify anonymous subroutine");
}

/**
* Helper method to throw appropriate control flow error.
* Called from loop handlers in main program when control flow escapes.
*
* @param cfType The control flow type
* @param label The label (may be null)
* @throws PerlCompilerException Always throws with appropriate message
*/
public static void throwControlFlowError(ControlFlowType cfType, String label) {
if (cfType == ControlFlowType.TAILCALL) {
// Tail call should have been handled by trampoline at returnLabel
throw new PerlCompilerException("Tail call escaped to top level (internal error) at ");
} else if (cfType == ControlFlowType.GOTO) {
if (label != null) {
throw new PerlCompilerException("Can't find label " + label + " at ");
} else {
throw new PerlCompilerException("goto must have a label at ");
}
} else {
// last/next/redo
String operation = cfType.name().toLowerCase();
if (label != null) {
throw new PerlCompilerException("Label not found for \"" + operation + " " + label + "\" at ");
} else {
throw new PerlCompilerException("Can't \"" + operation + "\" outside a loop block at ");
}
}
}

}
12 changes: 8 additions & 4 deletions src/main/java/org/perlonjava/runtime/RuntimeControlFlowList.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,25 @@ public class RuntimeControlFlowList extends RuntimeList {
*
* @param type The control flow type
* @param label The label to jump to (null for unlabeled)
* @param fileName Source file name (for error messages)
* @param lineNumber Line number (for error messages)
*/
public RuntimeControlFlowList(ControlFlowType type, String label) {
public RuntimeControlFlowList(ControlFlowType type, String label, String fileName, int lineNumber) {
super();
this.marker = new ControlFlowMarker(type, label);
this.marker = new ControlFlowMarker(type, label, fileName, lineNumber);
}

/**
* Constructor for tail call (goto &NAME).
*
* @param codeRef The code reference to call
* @param args The arguments to pass
* @param fileName Source file name (for error messages)
* @param lineNumber Line number (for error messages)
*/
public RuntimeControlFlowList(RuntimeScalar codeRef, RuntimeArray args) {
public RuntimeControlFlowList(RuntimeScalar codeRef, RuntimeArray args, String fileName, int lineNumber) {
super();
this.marker = new ControlFlowMarker(codeRef, args);
this.marker = new ControlFlowMarker(codeRef, args, fileName, lineNumber);
}

/**
Expand Down