Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ public InterpretedCode(int[] bytecode, Object[] constants, String[] stringPool,
if (this.packageName == null && compilePackage != null) {
this.packageName = compilePackage;
}
this.cvStartFile = sourceName;
this.cvStartLine = sourceLine;
// Scan bytecodes to find registers used by SCOPE_EXIT_CLEANUP opcodes.
// These are the actual "my" variable registers that need cleanup during
// exception propagation. Temporaries (hash element aliases, method return
Expand Down
16 changes: 15 additions & 1 deletion src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,18 @@ public static void emitSubroutine(EmitterContext ctx, SubroutineNode node) {
if (CompilerOptions.DEBUG_ENABLED) ctx.logDebug("Generated class env: " + Arrays.toString(newEnv));
RuntimeCode.anonSubs.put(subCtx.javaClassInfo.javaClassName, generatedClass); // Cache the class

String cvStartFile = "-e";
int cvStartLine = 0;
if (ctx.errorUtil != null && node.block != null) {
var loc = ctx.errorUtil.getSourceLocationAccurate(node.block.getIndex());
cvStartLine = loc.lineNumber();
if (loc.fileName() != null && !loc.fileName().isEmpty()) {
cvStartFile = loc.fileName();
}
} else if (ctx.compilerOptions != null && ctx.compilerOptions.fileName != null) {
cvStartFile = ctx.compilerOptions.fileName;
}

// Transfer pad constants (cached string literals referenced via \) from compile time
// to a registry so makeCodeObject() can attach them to the RuntimeCode at runtime.
if (subCtx.javaClassInfo.padConstants != null && !subCtx.javaClassInfo.padConstants.isEmpty()) {
Expand Down Expand Up @@ -289,11 +301,13 @@ public static void emitSubroutine(EmitterContext ctx, SubroutineNode node) {
mv.visitInsn(Opcodes.ACONST_NULL);
}
mv.visitLdcInsn(ctx.symbolTable.getCurrentPackage());
mv.visitLdcInsn(cvStartFile);
mv.visitLdcInsn(cvStartLine);
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/RuntimeCode",
"makeCodeObject",
"(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
"(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
false);
} catch (InterpreterFallbackException fallback) {
// JVM compilation failed (e.g., ASM frame crash) - use InterpretedCode instead
Expand Down
35 changes: 30 additions & 5 deletions src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java
Original file line number Diff line number Diff line change
Expand Up @@ -1733,7 +1733,7 @@ public static RuntimeCode createRuntimeCode(
if (SHOW_FALLBACK) {
System.err.println("Note: JVM compilation succeeded.");
}
return wrapAsCompiledCode(generatedClass, ctx);
return wrapAsCompiledCode(generatedClass, ctx, ast);

} catch (MethodTooLargeException e) {
if (USE_INTERPRETER_FALLBACK) {
Expand Down Expand Up @@ -1788,9 +1788,10 @@ public static RuntimeCode createRuntimeCode(
*
* @param generatedClass The compiled JVM class
* @param ctx The compiler context
* @param ast Body root node (for {@code B::CV->START} source line)
* @return CompiledCode wrapping the compiled class
*/
private static CompiledCode wrapAsCompiledCode(Class<?> generatedClass, EmitterContext ctx) {
private static CompiledCode wrapAsCompiledCode(Class<?> generatedClass, EmitterContext ctx, Node ast) {
try {
// Get the constructor (may have parameters for captured variables)
String[] env = (ctx.capturedEnv != null) ? ctx.capturedEnv : ctx.symbolTable.getVariableNames();
Expand Down Expand Up @@ -1828,15 +1829,19 @@ private static CompiledCode wrapAsCompiledCode(Class<?> generatedClass, EmitterC
RuntimeScalar selfRef = new RuntimeScalar();
selfRef.type = RuntimeScalarType.CODE;
// Note: ctx doesn't have prototype field, it's set separately by caller
selfRef.value = new CompiledCode(methodHandle, codeObject, null, generatedClass, ctx);
CompiledCode cc = new CompiledCode(methodHandle, codeObject, null, generatedClass, ctx);
applyCvStartFromAst(ctx, ast, cc);
selfRef.value = cc;
field.set(codeObject, selfRef);

return (CompiledCode) selfRef.value;
return cc;
} else {
// Has captured variables - caller must instantiate later
// Return a CompiledCode with null codeObject/methodHandle
// The caller will fill these in via reflection (see SubroutineParser pattern)
return new CompiledCode(null, null, null, generatedClass, ctx);
CompiledCode cc = new CompiledCode(null, null, null, generatedClass, ctx);
applyCvStartFromAst(ctx, ast, cc);
return cc;
}

} catch (VerifyError ve) {
Expand All @@ -1850,6 +1855,26 @@ private static CompiledCode wrapAsCompiledCode(Class<?> generatedClass, EmitterC
}
}

private static void applyCvStartFromAst(EmitterContext ctx, Node ast, RuntimeCode target) {
if (target == null || ctx == null) {
return;
}
String cvFile = "-e";
if (ctx.compilerOptions != null && ctx.compilerOptions.fileName != null) {
cvFile = ctx.compilerOptions.fileName;
}
int cvLine = 0;
if (ctx.errorUtil != null && ast != null) {
ErrorMessageUtil.SourceLocation loc = ctx.errorUtil.getSourceLocationAccurate(ast.getIndex());
cvLine = loc.lineNumber();
if (loc.fileName() != null && !loc.fileName().isEmpty()) {
cvFile = loc.fileName();
}
}
target.cvStartFile = cvFile;
target.cvStartLine = cvLine;
}

/**
* Compile AST to interpreter bytecode.
* <p>
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,18 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S
? fullName.substring(0, lastSep)
: parser.ctx.symbolTable.getCurrentPackage();

// B::CV->START line must be available before lazy compilation instantiates
// the JVM/interpreted body — e.g. Fennec::Lite calls B::svref_2object($cr)
// from _add_tests while registering coderefs.
if (parser.ctx.errorUtil != null && block != null) {
var loc = parser.ctx.errorUtil.getSourceLocationAccurate(block.tokenIndex);
placeholder.cvStartFile = loc.fileName();
placeholder.cvStartLine = loc.lineNumber();
} else if (parser.ctx.compilerOptions != null
&& parser.ctx.compilerOptions.fileName != null) {
placeholder.cvStartFile = parser.ctx.compilerOptions.fileName;
}

// Optimization - https://github.com/fglock/PerlOnJava/issues/8
// Prepare capture variables
Map<Integer, SymbolTable.SymbolEntry> outerVars = parser.ctx.symbolTable.getAllVisibleVariables();
Expand Down Expand Up @@ -1453,6 +1465,8 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S
field.set(placeholder.codeObject, codeRef);

installClosureCaptureMetadata(placeholder, paramList);
placeholder.cvStartFile = compiledCode.cvStartFile;
placeholder.cvStartLine = compiledCode.cvStartLine;

} else if (runtimeCode instanceof InterpretedCode interpretedCode) {
// InterpretedCode path - update placeholder in-place (not replace codeRef.value)
Expand Down Expand Up @@ -1486,6 +1500,8 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S
placeholder.subroutine = interpretedCode;
placeholder.codeObject = interpretedCode;
installClosureCaptureMetadata(placeholder, paramList);
placeholder.cvStartFile = interpretedCode.cvStartFile;
placeholder.cvStartLine = interpretedCode.cvStartLine;
}
} catch (VerifyError ve) {
// VerifyError extends Error (not Exception), so it's not caught by catch(Exception).
Expand Down Expand Up @@ -1519,6 +1535,8 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S
placeholder.subroutine = interpretedCode;
placeholder.codeObject = interpretedCode;
installClosureCaptureMetadata(placeholder, paramList);
placeholder.cvStartFile = interpretedCode.cvStartFile;
placeholder.cvStartLine = interpretedCode.cvStartLine;
} catch (Exception e) {
// Handle any exceptions during subroutine creation
throw new PerlCompilerException("Subroutine error: " + e.getMessage());
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/org/perlonjava/runtime/perlmodule/Internals.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public static void initialize() {
// to approximate the real-Perl GVf_IMPORTED_CV bit so callers
// such as Pod::Coverage can skip imported helpers.
internals.registerMethod("jperl_is_imported_sub", "jperl_is_imported_sub", "$");
internals.registerMethod("jperl_cv_start_location", "jperlCvStartLocation", "$");
} catch (NoSuchMethodException e) {
System.err.println("Warning: Missing Internals method: " + e.getMessage());
}
Expand Down Expand Up @@ -613,4 +614,35 @@ public static RuntimeList jperl_is_imported_sub(RuntimeArray args, int ctx) {
}
return new RuntimeScalar().getList();
}

/**
* Returns (filename, line) for the start of a coderef body — what Perl's
* {@code B::svref_2object($cv)->START->line} reports. Used by the bundled
* {@code B} stub (e.g. Fennec::Lite's {@code FENNEC_ITEM} filtering).
*/
public static RuntimeList jperlCvStartLocation(RuntimeArray args, int ctx) {
String defFile = "-e";
if (args.size() == 0) {
return new RuntimeList(new RuntimeScalar(defFile), new RuntimeScalar(0));
}
RuntimeScalar s = args.get(0);
if (s == null) {
return new RuntimeList(new RuntimeScalar(defFile), new RuntimeScalar(0));
}
s = s.scalar();
if (s.type != RuntimeScalarType.CODE || !(s.value instanceof RuntimeCode code)) {
return new RuntimeList(new RuntimeScalar(defFile), new RuntimeScalar(0));
}
String file = code.cvStartFile;
int line = code.cvStartLine;
if ((line <= 0 || file == null || file.isEmpty())
&& code instanceof org.perlonjava.backend.bytecode.InterpretedCode ic) {
file = ic.sourceName != null ? ic.sourceName : defFile;
line = ic.sourceLine;
}
if (file == null || file.isEmpty()) {
file = defFile;
}
return new RuntimeList(new RuntimeScalar(file), new RuntimeScalar(line));
}
}
26 changes: 26 additions & 0 deletions src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,13 @@ public static void requireLvalueCallable(RuntimeCode code, int callContext, Stri
// behaviour, where the CV's CvGV points to a free-floating GV with the name.
public boolean explicitlyRenamed = false;

/**
* Source location of the start of this CV's body (Perl {@code B::CV->START->line} /
* COP). Used by the stub {@code B} module; {@code 0} means unknown.
*/
public String cvStartFile;
public int cvStartLine;

/**
* When a coderef is installed with {@code *Package::name = $cr}, records the
* stash slot FQN for introspection ({@code Sub::Util::subname}, {@code B::CV})
Expand Down Expand Up @@ -2018,6 +2025,19 @@ public static RuntimeScalar makeCodeObject(Object codeObject, String prototype)
* @throws Exception if an error occurs during method retrieval
*/
public static RuntimeScalar makeCodeObject(Object codeObject, String prototype, String packageName) throws Exception {
return makeCodeObject(codeObject, prototype, packageName, null, 0);
}

/**
* Like {@link #makeCodeObject(Object, String, String)} plus COP source
* location for {@code B::CV->START} (e.g. Fennec::Lite line filtering).
*/
public static RuntimeScalar makeCodeObject(
Object codeObject,
String prototype,
String packageName,
String cvStartFile,
int cvStartLine) throws Exception {
// Retrieve the class of the provided code object
Class<?> clazz = codeObject.getClass();

Expand All @@ -2031,6 +2051,12 @@ public static RuntimeScalar makeCodeObject(Object codeObject, String prototype,
if (packageName != null) {
code.packageName = packageName;
}
if (cvStartFile != null && !cvStartFile.isEmpty()) {
code.cvStartFile = cvStartFile;
}
if (cvStartLine > 0) {
code.cvStartLine = cvStartLine;
}

// Look up pad constants registered at compile time for this class.
// These track cached string literals referenced via \ inside the sub,
Expand Down
19 changes: 17 additions & 2 deletions src/main/perl/lib/B.pm
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,23 @@ package B::CV {

sub START {
# Return a B::COP (control op) so optree walkers find file/line info.
# Real Perl returns the first op of the sub body; for PerlOnJava we
# return a COP with the best location info we have.
# Real Perl returns the first op of the sub body; for PerlOnJava use
# Internals::jperl_cv_start_location when available.
my $self = shift;
my $ref = $self->{ref};
if ($ref && ref($ref) eq 'CODE') {
local $@;
eval { require Internals; 1 } or return B::COP->new("-e", 0);
my @loc = Internals::jperl_cv_start_location($ref);
if (@loc >= 2) {
my ($file, $line) = @loc;
$file ||= "-e";
$line ||= 0;
if ($line > 0) {
return B::COP->new($file, $line);
}
}
}
return B::COP->new("-e", 0);
}

Expand Down
Loading