diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index 557bbbaf7..eedb514b2 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -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 diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index 49a2ceace..4374be4fe 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -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.) diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitLogicalOperator.java b/src/main/java/org/perlonjava/backend/jvm/EmitLogicalOperator.java index 55f8b79b5..2bd966f57 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitLogicalOperator.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitLogicalOperator.java @@ -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; } diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java b/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java index bd97a7750..6151612e7 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java @@ -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); } } @@ -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) { diff --git a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java index 8ada9151b..9cba34c7d 100644 --- a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java @@ -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; @@ -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