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 @@ -4544,7 +4544,10 @@ void compileVariableReference(OperatorNode node, String op) {
// Cache the RuntimeScalar code reference at compile time.
// This matches Perl's behavior where the CV (code value) is cached
// in the compiled bytecode, surviving stash entry deletion.
RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(subName);
Object parseTimeCodeRef = node.getAnnotation("parseTimeCodeRef");
RuntimeScalar codeRef = parseTimeCodeRef instanceof RuntimeScalar runtimeScalar
? runtimeScalar
: GlobalVariable.getGlobalCodeRefForFreshLookup(subName);

// Allocate register and load from constant pool
int rd = allocateOutputRegister();
Expand All @@ -4558,7 +4561,7 @@ void compileVariableReference(OperatorNode node, String op) {
} else if (node.operand instanceof StringNode strNode) {
// Symbolic ref: &{'name'} — look up global code reference by string name
String globalName = NameNormalizer.normalizeVariableName(strNode.value, getCurrentPackage());
RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(globalName);
RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRefForFreshLookup(globalName);
int rd = allocateOutputRegister();
int constIdx = addToConstantPool(codeRef);
emit(Opcodes.LOAD_CONST);
Expand Down Expand Up @@ -4597,7 +4600,10 @@ void compileVariableReference(OperatorNode node, String op) {
// loading, so defined(\&Name) returns true
String subName = NameNormalizer.normalizeVariableName(
idNode.name, getCurrentPackage());
RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(subName);
Object parseTimeCodeRef = operandOp.getAnnotation("parseTimeCodeRef");
RuntimeScalar codeRef = parseTimeCodeRef instanceof RuntimeScalar runtimeScalar
? runtimeScalar
: GlobalVariable.getGlobalCodeRefForFreshLookup(subName);
if (codeRef.type == RuntimeScalarType.CODE
&& codeRef.value instanceof RuntimeCode rc) {
rc.isSymbolicReference = true;
Expand Down
20 changes: 15 additions & 5 deletions src/main/java/org/perlonjava/backend/jvm/Dereference.java
Original file line number Diff line number Diff line change
Expand Up @@ -971,11 +971,21 @@ static void handleArrowOperator(EmitterVisitor emitterVisitor, BinaryOperatorNod

// Allocate a unique callsite ID for inline method caching
int callsiteId = nextMethodCallsiteId++;
// Set debug line number to the call site (the object/receiver expression),
// so that caller() inside the called method reports the correct source line.
// Without this, the JVM frame reports the line of the closing ')' instead.
if (node.left.getIndex() > 0) {
ByteCodeSourceMapper.setDebugInfoLineNumber(emitterVisitor.ctx, node.left.getIndex());
// Set debug line number to the whole method call. Perl's caller()
// reports the closing line for a multi-line call expression, which
// is carried by the "->" node or its argument ListNode.
int callSiteIndex = node.getIndex();
if (node.right instanceof BinaryOperatorNode callNode
&& "(".equals(callNode.operator)
&& callNode.right != null
&& callNode.right.getIndex() > 0) {
callSiteIndex = callNode.right.getIndex();
}
if (callSiteIndex <= 0 && node.left.getIndex() > 0) {
callSiteIndex = node.left.getIndex();
}
if (callSiteIndex > 0) {
ByteCodeSourceMapper.setDebugInfoLineNumber(emitterVisitor.ctx, callSiteIndex);
}

mv.visitLdcInsn(callsiteId);
Expand Down
50 changes: 44 additions & 6 deletions src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
import org.perlonjava.frontend.semantic.ScopedSymbolTable;
import org.perlonjava.frontend.semantic.SymbolTable;
import org.perlonjava.runtime.runtimetypes.NameNormalizer;
import org.perlonjava.runtime.runtimetypes.GlobalVariable;
import org.perlonjava.runtime.runtimetypes.RuntimeBase;
import org.perlonjava.runtime.runtimetypes.RuntimeCode;
import org.perlonjava.runtime.runtimetypes.RuntimeContextType;
import org.perlonjava.runtime.runtimetypes.RuntimeScalar;

import java.util.Arrays;
import java.util.HashSet;
Expand Down Expand Up @@ -523,7 +525,19 @@ static void handleApplyOperator(EmitterVisitor emitterVisitor, BinaryOperatorNod
}
}

node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); // Target - left parameter: Code ref
if (node.left instanceof OperatorNode operatorNode
&& operatorNode.operator.equals("&")
&& operatorNode.getAnnotation("parseTimeCodeRef") instanceof RuntimeScalar codeRef) {
int codeRefId = GlobalVariable.registerCompiledCodeRef(codeRef);
mv.visitLdcInsn(codeRefId);
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/GlobalVariable",
"getCompiledCodeRef",
"(I)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
false);
} else {
node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); // Target - left parameter: Code ref
}

// Dereference the scalar to get the CODE reference if needed
// When we have &$x() the left side is OperatorNode("$") (the & is consumed by the parser)
Expand Down Expand Up @@ -710,11 +724,35 @@ static void handleApplyOperator(EmitterVisitor emitterVisitor, BinaryOperatorNod
}
}

// Set debug line number to the call site (the function name/reference expression),
// so that caller() inside the called subroutine reports the correct source line.
// Without this, the JVM frame reports the line of the closing ')' instead.
if (node.left != null && node.left.getIndex() > 0) {
ByteCodeSourceMapper.setDebugInfoLineNumber(emitterVisitor.ctx, node.left.getIndex());
// Undefined direct-call diagnostics report the line containing the
// function token, while caller() inside a successfully entered sub sees
// the completed call expression's closing line. Keep those as two
// separate bytecode locations.
int errorSiteIndex = node.left != null && node.left.getIndex() > 0
? node.left.getIndex()
: (node.getIndex() > 0 ? node.getIndex() : -1);
if (errorSiteIndex > 0) {
ByteCodeSourceMapper.setDebugInfoLineNumber(emitterVisitor.ctx, errorSiteIndex);
}

mv.visitVarInsn(Opcodes.ALOAD, codeRefSlot);
mv.visitVarInsn(Opcodes.ALOAD, nameSlot);
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/RuntimeCode",
"throwIfDirectCallUndefined",
"(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Ljava/lang/String;)V",
false);

// Set debug line number to the call site. Perl reports the line where a
// multi-line call expression completes for caller(), not the line
// containing the function name. The argument ListNode is indexed at the
// closing token.
int callSiteIndex = node.right != null && node.right.getIndex() > 0
? node.right.getIndex()
: (node.getIndex() > 0 ? node.getIndex() : (node.left != null ? node.left.getIndex() : -1));
if (callSiteIndex > 0) {
ByteCodeSourceMapper.setDebugInfoLineNumber(emitterVisitor.ctx, callSiteIndex);
}

mv.visitVarInsn(Opcodes.ALOAD, codeRefSlot);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,10 @@ static ListNode consumeArgsWithPrototype(Parser parser, String prototype, boolea
parser.throwError("syntax error");
}

// Preserve the end of the parsed argument list for call-site line
// reporting. caller() uses this to match Perl's behavior for
// multi-line direct sub calls.
args.setIndex(parser.tokenIndex);
return args;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,10 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) {
boolean subExists = isNewMethod;
String prototype = null;
List<String> attributes = null;
RuntimeScalar parseTimeCodeRef = null;
if (!isNewMethod && !isMethod && GlobalVariable.existsGlobalCodeRef(fullName)) {
RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(fullName);
parseTimeCodeRef = codeRef;
if (codeRef.value instanceof RuntimeCode runtimeCode) {
prototype = runtimeCode.prototype;
attributes = runtimeCode.attributes;
Expand Down Expand Up @@ -549,8 +551,12 @@ && isValidIndirectMethod(subName, parser)
}

// Rewrite and return the subroutine call as `&name(arguments)`
OperatorNode codeRefNode = new OperatorNode("&", nameNode, currentIndex);
if (parseTimeCodeRef != null) {
codeRefNode.setAnnotation("parseTimeCodeRef", parseTimeCodeRef);
}
return new BinaryOperatorNode("(",
new OperatorNode("&", nameNode, currentIndex),
codeRefNode,
arguments,
currentIndex);
} finally {
Expand Down
Loading
Loading