diff --git a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java index a05fbdcaa..1ef9cfcdd 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java @@ -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 diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java b/src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java index d68ef2a3c..ee595c8d4 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java @@ -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()) { @@ -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 diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java b/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java index 6669946e1..0d6100f30 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java @@ -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) { @@ -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(); @@ -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) { @@ -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. *
diff --git a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java
index 71ed9640b..1d9b13089 100644
--- a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java
+++ b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java
@@ -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