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 @@ -493,6 +493,12 @@ public InterpretedCode compile(Node node, EmitterContext ctx) {
// Use the calling context from EmitterContext for top-level expressions
// This is crucial for eval STRING to propagate context correctly
currentCallContext = ctx.contextType;
// Inherit package from the JVM compiler context so unqualified sub calls
// resolve in the correct package (not main)
if (ctx.symbolTable != null) {
symbolTable.setCurrentPackage(ctx.symbolTable.getCurrentPackage(),
ctx.symbolTable.currentPackageIsClass());
}
}

// If we have captured variables, allocate registers for them
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -1446,6 +1446,45 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
bytecodeCompiler.emitReg(hashReg);
bytecodeCompiler.emitReg(keyReg);

bytecodeCompiler.lastResultReg = rd;
} else if (arg instanceof BinaryOperatorNode && ((BinaryOperatorNode) arg).operator.equals("->")) {
// Arrow dereference: delete $ref->{key}
BinaryOperatorNode arrowAccess = (BinaryOperatorNode) arg;
// Compile the reference expression
arrowAccess.left.accept(bytecodeCompiler);
int scalarReg = bytecodeCompiler.lastResultReg;

int hashReg = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emitWithToken(Opcodes.DEREF_HASH, node.getIndex());
bytecodeCompiler.emitReg(hashReg);
bytecodeCompiler.emitReg(scalarReg);

// Compile key
int keyReg;
if (arrowAccess.right instanceof HashLiteralNode keyNode && !keyNode.elements.isEmpty()) {
Node keyElement = keyNode.elements.get(0);
if (keyElement instanceof IdentifierNode) {
String keyString = ((IdentifierNode) keyElement).name;
keyReg = bytecodeCompiler.allocateRegister();
int keyIdx = bytecodeCompiler.addToStringPool(keyString);
bytecodeCompiler.emit(Opcodes.LOAD_STRING);
bytecodeCompiler.emitReg(keyReg);
bytecodeCompiler.emit(keyIdx);
} else {
keyElement.accept(bytecodeCompiler);
keyReg = bytecodeCompiler.lastResultReg;
}
} else {
arrowAccess.right.accept(bytecodeCompiler);
keyReg = bytecodeCompiler.lastResultReg;
}

int rd = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emit(Opcodes.HASH_DELETE);
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(hashReg);
bytecodeCompiler.emitReg(keyReg);

bytecodeCompiler.lastResultReg = rd;
} else {
// For now, use SLOW_OP for other cases (hash slice delete, array delete, etc.)
Expand Down
35 changes: 28 additions & 7 deletions src/main/java/org/perlonjava/backend/jvm/EmitLogicalOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -287,14 +287,35 @@ private static void emitLogicalOperatorSimple(EmitterVisitor emitterVisitor, Bin

if (emitterVisitor.ctx.contextType == RuntimeContextType.VOID) {
evalTrace("EmitLogicalOperatorSimple VOID op=" + node.operator + " emit LHS in SCALAR; RHS in SCALAR");
node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeBase", getBoolean, "()Z", false);
mv.visitJumpInsn(compareOpcode, endLabel);

node.right.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
mv.visitInsn(Opcodes.POP);

mv.visitLabel(endLabel);
OperatorNode voidDeclaration = FindDeclarationVisitor.findOperator(node.right, "my");
String voidSavedOperator = null;
Node voidSavedOperand = null;
boolean voidRewritten = false;
try {
if (voidDeclaration != null && voidDeclaration.operand instanceof OperatorNode voidOperatorNode) {
voidSavedOperator = voidDeclaration.operator;
voidSavedOperand = voidDeclaration.operand;
voidDeclaration.accept(emitterVisitor.with(RuntimeContextType.VOID));
voidDeclaration.operator = voidOperatorNode.operator;
voidDeclaration.operand = voidOperatorNode.operand;
voidRewritten = true;
}

node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeBase", getBoolean, "()Z", false);
mv.visitJumpInsn(compareOpcode, endLabel);

node.right.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
mv.visitInsn(Opcodes.POP);

mv.visitLabel(endLabel);
} finally {
if (voidRewritten) {
voidDeclaration.operator = voidSavedOperator;
voidDeclaration.operand = voidSavedOperand;
}
}
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1519,6 +1519,12 @@ public static RuntimeCode createRuntimeCode(

// If interpreter fallback disabled, re-throw to use existing AST splitter logic
throw e;
} catch (VerifyError e) {
if (USE_INTERPRETER_FALLBACK) {
System.err.println("Note: JVM VerifyError (" + e.getMessage().split("\n")[0] + "), using interpreter backend.");
return compileToInterpreter(ast, ctx, useTryCatch);
}
throw new RuntimeException(e);
}
}

Expand Down Expand Up @@ -1613,8 +1619,8 @@ private static InterpretedCode compileToInterpreter(
ctx.errorUtil
);

// Compile AST to interpreter bytecode
InterpretedCode code = compiler.compile(ast);
// Compile AST to interpreter bytecode (pass ctx for package context and closure detection)
InterpretedCode code = compiler.compile(ast, ctx);

// Handle captured variables if needed (for closures)
if (ctx.capturedEnv != null && ctx.capturedEnv.length > skipVariables) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,11 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S
field.set(placeholder.codeObject, codeRef);

} else if (runtimeCode instanceof InterpretedCode) {
// InterpretedCode path - replace codeRef.value entirely
// InterpretedCode path - update placeholder in-place (not replace codeRef.value)
// This is critical: hash assignments copy RuntimeScalar but share the same
// RuntimeCode value object. If we replace codeRef.value, hash copies won't see
// the update. By setting methodHandle/codeObject on the placeholder, ALL
// references (including hash copies) will see the compiled code.
InterpretedCode interpretedCode =
(InterpretedCode) runtimeCode;

Expand All @@ -839,8 +843,10 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S
interpretedCode.subName = placeholder.subName;
interpretedCode.packageName = placeholder.packageName;

// REPLACE the global reference
codeRef.value = interpretedCode;
// Update placeholder in-place: set methodHandle to delegate to InterpretedCode
placeholder.methodHandle = RuntimeCode.lookup.findVirtual(
InterpretedCode.class, "apply", RuntimeCode.methodType);
placeholder.codeObject = interpretedCode;
}
} catch (Exception e) {
// Handle any exceptions during subroutine creation
Expand Down