diff --git a/src/main/java/org/perlonjava/app/cli/CompilerOptions.java b/src/main/java/org/perlonjava/app/cli/CompilerOptions.java index b12529d14..9736fcc9a 100644 --- a/src/main/java/org/perlonjava/app/cli/CompilerOptions.java +++ b/src/main/java/org/perlonjava/app/cli/CompilerOptions.java @@ -34,7 +34,7 @@ */ public class CompilerOptions implements Cloneable { public boolean debugEnabled = false; - public boolean disassembleEnabled = false; + public boolean disassembleEnabled = System.getenv("JPERL_DISASSEMBLE") != null; public boolean useInterpreter = false; public boolean tokenizeOnly = false; public boolean parseOnly = false; diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index c14edf705..5775c6a8c 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -369,6 +369,30 @@ private boolean isNonAsciiLengthOneScalarAllowedUnderNoUtf8(String sigil, String * @param varName The variable name with sigil (e.g., "$A", "@array") * @return true if access should be blocked under strict vars */ + /** Returns true if strict refs is currently enabled at compile time. */ + boolean isStrictRefsEnabled() { + if (emitterContext == null || emitterContext.symbolTable == null) { + return false; + } + return emitterContext.symbolTable.isStrictOptionEnabled(Strict.HINT_STRICT_REFS); + } + + /** Returns the current strict options bitmask at this point in compilation. */ + int getCurrentStrictOptions() { + if (emitterContext == null || emitterContext.symbolTable == null) { + return 0; + } + return emitterContext.symbolTable.strictOptionsStack.peek(); + } + + /** Returns the current feature flags bitmask at this point in compilation. */ + int getCurrentFeatureFlags() { + if (emitterContext == null || emitterContext.symbolTable == null) { + return 0; + } + return emitterContext.symbolTable.featureFlagsStack.peek(); + } + boolean shouldBlockGlobalUnderStrictVars(String varName) { // Only check if strict vars is enabled if (emitterContext == null || emitterContext.symbolTable == null) { @@ -2194,6 +2218,25 @@ void compileVariableDeclaration(OperatorNode node, String op) { OperatorNode sigilOp = (OperatorNode) node.operand; String sigil = sigilOp.operator; + if (sigil.equals("*") && sigilOp.operand instanceof IdentifierNode) { + // local *glob — save glob state and return same glob object + // Mirrors JVM path: load glob, call DynamicVariableManager.pushLocalVariable(RuntimeGlob) + String globName = NameNormalizer.normalizeVariableName(((IdentifierNode) sigilOp.operand).name, getCurrentPackage()); + int nameIdx = addToStringPool(globName); + + int globReg = allocateRegister(); + emitWithToken(Opcodes.LOAD_GLOB, node.getIndex()); + emitReg(globReg); + emit(nameIdx); + + // Push glob to local variable stack (saves state, returns same object) + emit(Opcodes.PUSH_LOCAL_VARIABLE); + emitReg(globReg); + + lastResultReg = globReg; + return; + } + if (sigil.equals("$") && sigilOp.operand instanceof IdentifierNode) { String varName = "$" + ((IdentifierNode) sigilOp.operand).name; @@ -2601,31 +2644,60 @@ void compileVariableReference(OperatorNode node, String op) { operandOp.accept(this); int refReg = lastResultReg; - // Dereference to get the array - // The reference should contain a RuntimeArray - // For @$scalar, we need to dereference it int rd = allocateRegister(); - emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); - emitReg(rd); - emitReg(refReg); + if (isStrictRefsEnabled()) { + emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); + emitReg(rd); + emitReg(refReg); + } else { + int pkgIdx = addToStringPool(getCurrentPackage()); + emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, node.getIndex()); + emitReg(rd); + emitReg(refReg); + emit(pkgIdx); + } lastResultReg = rd; - // Note: We don't check scalar context here because dereferencing - // should return the array itself. The slice or other operation - // will handle scalar context conversion if needed. } else if (node.operand instanceof BlockNode) { // @{ block } - evaluate block and dereference the result - // The block should return an arrayref BlockNode blockNode = (BlockNode) node.operand; blockNode.accept(this); int refReg = lastResultReg; - // Dereference to get the array int rd = allocateRegister(); - emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); - emitReg(rd); - emitReg(refReg); + if (isStrictRefsEnabled()) { + emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); + emitReg(rd); + emitReg(refReg); + } else { + int pkgIdx = addToStringPool(getCurrentPackage()); + emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, node.getIndex()); + emitReg(rd); + emitReg(refReg); + emit(pkgIdx); + } + lastResultReg = rd; + } else if (node.operand instanceof StringNode strNode) { + // @{'name'} — symbolic array reference + int nameReg = allocateRegister(); + int strIdx = addToStringPool(strNode.value); + emit(Opcodes.LOAD_STRING); + emitReg(nameReg); + emit(strIdx); + + int rd = allocateRegister(); + if (isStrictRefsEnabled()) { + emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); + emitReg(rd); + emitReg(nameReg); + } else { + int pkgIdx = addToStringPool(getCurrentPackage()); + emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, node.getIndex()); + emitReg(rd); + emitReg(nameReg); + emit(pkgIdx); + } lastResultReg = rd; } else { throwCompilerException("Unsupported @ operand: " + node.operand.getClass().getSimpleName()); @@ -2666,9 +2738,17 @@ void compileVariableReference(OperatorNode node, String op) { refOp.accept(this); int scalarReg = lastResultReg; int hashReg = allocateRegister(); - emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); - emitReg(hashReg); - emitReg(scalarReg); + if (isStrictRefsEnabled()) { + emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); + emitReg(hashReg); + emitReg(scalarReg); + } else { + int pkgIdx = addToStringPool(getCurrentPackage()); + emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex()); + emitReg(hashReg); + emitReg(scalarReg); + emit(pkgIdx); + } if (currentCallContext == RuntimeContextType.SCALAR) { int rd = allocateRegister(); emit(Opcodes.ARRAY_SIZE); @@ -2683,15 +2763,44 @@ void compileVariableReference(OperatorNode node, String op) { blockNode.accept(this); int scalarReg = lastResultReg; int hashReg = allocateRegister(); - emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); - emitReg(hashReg); - emitReg(scalarReg); + if (isStrictRefsEnabled()) { + emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); + emitReg(hashReg); + emitReg(scalarReg); + } else { + int pkgIdx = addToStringPool(getCurrentPackage()); + emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex()); + emitReg(hashReg); + emitReg(scalarReg); + emit(pkgIdx); + } + lastResultReg = hashReg; + } else if (node.operand instanceof StringNode strNode) { + // %{'name'} — symbolic hash reference + int nameReg = allocateRegister(); + int strIdx = addToStringPool(strNode.value); + emit(Opcodes.LOAD_STRING); + emitReg(nameReg); + emit(strIdx); + + int hashReg = allocateRegister(); + if (isStrictRefsEnabled()) { + emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); + emitReg(hashReg); + emitReg(nameReg); + } else { + int pkgIdx = addToStringPool(getCurrentPackage()); + emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex()); + emitReg(hashReg); + emitReg(nameReg); + emit(pkgIdx); + } lastResultReg = hashReg; } else { throwCompilerException("Unsupported % operand: " + node.operand.getClass().getSimpleName()); } } else if (op.equals("*")) { - // Glob variable dereference: *x + // Glob variable dereference: *x or *{expr} if (node.operand instanceof IdentifierNode) { IdentifierNode idNode = (IdentifierNode) node.operand; String varName = idNode.name; @@ -2710,6 +2819,28 @@ void compileVariableReference(OperatorNode node, String op) { emitReg(rd); emit(nameIdx); + lastResultReg = rd; + } else if (node.operand instanceof BlockNode || node.operand instanceof StringNode) { + // *{expr} or *{'name'} — dynamic glob via symbolic reference + node.operand.accept(this); + int nameReg = lastResultReg; + + int rd = allocateRegister(); + emitWithToken(Opcodes.LOAD_SYMBOLIC_GLOB, node.getIndex()); + emitReg(rd); + emitReg(nameReg); + + lastResultReg = rd; + } else if (node.operand instanceof OperatorNode) { + // *$ref or **postfix — dereference scalar as glob + node.operand.accept(this); + int scalarReg = lastResultReg; + + int rd = allocateRegister(); + emitWithToken(Opcodes.DEREF_GLOB, node.getIndex()); + emitReg(rd); + emitReg(scalarReg); + lastResultReg = rd; } else { throwCompilerException("Unsupported * operand: " + node.operand.getClass().getSimpleName()); diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index b14f13fe8..e960c83b8 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -1376,22 +1376,27 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c case Opcodes.DEREF: { // Dereference: rd = $$rs (scalar reference dereference) - // Can receive RuntimeScalar or RuntimeList + // Always call scalarDeref() — throws "Not a SCALAR reference" for + // non-reference types (IO, FORMAT, etc.), matching Perl semantics. int rd = bytecode[pc++]; int rs = bytecode[pc++]; RuntimeBase value = registers[rs]; - // Only dereference if it's a RuntimeScalar with REFERENCE type if (value instanceof RuntimeScalar) { - RuntimeScalar scalar = (RuntimeScalar) value; - if (scalar.type == RuntimeScalarType.REFERENCE) { - registers[rd] = scalar.scalarDeref(); + RuntimeScalar sv = (RuntimeScalar) value; + // Call scalarDeref() for scalar refs, undef, non-ref types (strings, globs, etc.) + // Pass through non-scalar reference types (array/hash/code/regex refs) — + // those are handled by the JVM compiler as non-scalar refs and should not + // throw here (decl-refs.t uses $$arrayref in no-strict context). + if (sv.type == RuntimeScalarType.ARRAYREFERENCE + || sv.type == RuntimeScalarType.HASHREFERENCE + || sv.type == RuntimeScalarType.CODE + || sv.type == RuntimeScalarType.REGEX) { + registers[rd] = sv; // pass through non-scalar refs } else { - // Non-reference scalar, just copy - registers[rd] = value; + registers[rd] = sv.scalarDeref(); } } else { - // RuntimeList or other types, pass through registers[rd] = value; } break; @@ -1774,6 +1779,11 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c case Opcodes.SELECT_OP: case Opcodes.LOAD_GLOB: case Opcodes.SLEEP_OP: + case Opcodes.LOAD_SYMBOLIC_GLOB: + case Opcodes.DEREF_GLOB: + case Opcodes.DEREF_HASH_NONSTRICT: + case Opcodes.DEREF_ARRAY_NONSTRICT: + case Opcodes.DEREF_NONSTRICT: pc = executeSpecialIO(opcode, bytecode, pc, registers, code); break; @@ -2219,17 +2229,19 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc, int rs = bytecode[pc++]; RuntimeBase value = registers[rs]; - // Only dereference if it's a RuntimeScalar with REFERENCE type + // Call scalarDeref() for scalar refs, undef, non-ref types (strings, globs, etc.) + // Pass through non-scalar reference types (array/hash/code/regex refs). if (value instanceof RuntimeScalar) { - RuntimeScalar scalar = (RuntimeScalar) value; - if (scalar.type == RuntimeScalarType.REFERENCE) { - registers[rd] = scalar.scalarDeref(); + RuntimeScalar sv = (RuntimeScalar) value; + if (sv.type == RuntimeScalarType.ARRAYREFERENCE + || sv.type == RuntimeScalarType.HASHREFERENCE + || sv.type == RuntimeScalarType.CODE + || sv.type == RuntimeScalarType.REGEX) { + registers[rd] = sv; // pass through non-scalar refs } else { - // Non-reference scalar, just copy - registers[rd] = value; + registers[rd] = sv.scalarDeref(); } } else { - // RuntimeList or other types, pass through registers[rd] = value; } return pc; @@ -3099,6 +3111,16 @@ private static int executeSpecialIO(int opcode, int[] bytecode, int pc, return SlowOpcodeHandler.executeLoadGlob(bytecode, pc, registers, code); case Opcodes.SLEEP_OP: return SlowOpcodeHandler.executeSleep(bytecode, pc, registers); + case Opcodes.LOAD_SYMBOLIC_GLOB: + return SlowOpcodeHandler.executeLoadSymbolicGlob(bytecode, pc, registers); + case Opcodes.DEREF_GLOB: + return SlowOpcodeHandler.executeDerefGlob(bytecode, pc, registers); + case Opcodes.DEREF_HASH_NONSTRICT: + return SlowOpcodeHandler.executeDerefHashNonStrict(bytecode, pc, registers, code); + case Opcodes.DEREF_ARRAY_NONSTRICT: + return SlowOpcodeHandler.executeDerefArrayNonStrict(bytecode, pc, registers, code); + case Opcodes.DEREF_NONSTRICT: + return SlowOpcodeHandler.executeDerefNonStrict(bytecode, pc, registers, code); default: throw new RuntimeException("Unknown special I/O opcode: " + opcode); } diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index 3ee669681..748103d65 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -896,6 +896,27 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(globReg); bytecodeCompiler.emitReg(valueReg); + bytecodeCompiler.lastResultReg = globReg; + } else if (leftOp.operator.equals("*") && + (leftOp.operand instanceof BlockNode || + leftOp.operand instanceof OperatorNode || + leftOp.operand instanceof StringNode)) { + // Dynamic typeglob assignment: *{"Pkg::name"} = value, *$ref = value, *{'name'} = value + // Evaluate the expression to get the glob name at runtime + leftOp.operand.accept(bytecodeCompiler); + int nameReg = bytecodeCompiler.lastResultReg; + + // Load the glob via symbolic reference + int globReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emitWithToken(Opcodes.LOAD_SYMBOLIC_GLOB, node.getIndex()); + bytecodeCompiler.emitReg(globReg); + bytecodeCompiler.emitReg(nameReg); + + // Store value to glob + bytecodeCompiler.emit(Opcodes.STORE_GLOB); + bytecodeCompiler.emitReg(globReg); + bytecodeCompiler.emitReg(valueReg); + bytecodeCompiler.lastResultReg = globReg; } else if (leftOp.operator.equals("pos")) { // pos($var) = value - lvalue assignment to regex position diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index 21a5ea90d..1ed2a34e6 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -835,10 +835,16 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // Allocate register for result int rd = bytecodeCompiler.allocateRegister(); - // Emit direct opcode EVAL_STRING + // Emit direct opcode EVAL_STRING with call-site strict/feature/warning flags + // so EvalStringHandler inherits the pragmas in effect at the eval call site + // (not just the end-of-compilation snapshot in InterpretedCode) + int callSiteStrictOptions = bytecodeCompiler.getCurrentStrictOptions(); + int callSiteFeatureFlags = bytecodeCompiler.getCurrentFeatureFlags(); bytecodeCompiler.emitWithToken(Opcodes.EVAL_STRING, node.getIndex()); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(stringReg); + bytecodeCompiler.emitInt(callSiteStrictOptions); + bytecodeCompiler.emitInt(callSiteFeatureFlags); bytecodeCompiler.lastResultReg = rd; } else { @@ -1669,13 +1675,19 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.throwCompilerException("open requires arguments"); } - // Compile all arguments into a list + // Compile the filehandle argument (first arg) as an lvalue register + // We must NOT push it through ARRAY_PUSH (which copies via addToArray), + // because IOOperator.open needs to call fileHandle.set() on the actual lvalue. + argsList.elements.get(0).accept(bytecodeCompiler); + int fhReg = bytecodeCompiler.lastResultReg; + + // Compile remaining arguments into a list (mode, filename/ref, ...) int argsReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.NEW_ARRAY); bytecodeCompiler.emitReg(argsReg); - for (Node arg : argsList.elements) { - arg.accept(bytecodeCompiler); + for (int i = 1; i < argsList.elements.size(); i++) { + argsList.elements.get(i).accept(bytecodeCompiler); int elemReg = bytecodeCompiler.lastResultReg; bytecodeCompiler.emit(Opcodes.ARRAY_PUSH); @@ -1683,11 +1695,13 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(elemReg); } - // Call open with context and args + // Call open: OPEN rd ctx fhReg argsReg + // fhReg is the actual lvalue register for the filehandle (written back directly) int rd = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.OPEN); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); + bytecodeCompiler.emitReg(fhReg); bytecodeCompiler.emitReg(argsReg); bytecodeCompiler.lastResultReg = rd; diff --git a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java index 13db3320e..862d0f633 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java @@ -49,7 +49,9 @@ public static RuntimeScalar evalString(String perlCode, InterpretedCode currentCode, RuntimeBase[] registers, String sourceName, - int sourceLine) { + int sourceLine, + int callSiteStrictOptions, + int callSiteFeatureFlags) { try { // Step 1: Clear $@ at start of eval GlobalVariable.getGlobalVariable("main::@").set(""); @@ -65,13 +67,15 @@ public static RuntimeScalar evalString(String perlCode, opts.fileName = sourceName + " (eval)"; ScopedSymbolTable symbolTable = new ScopedSymbolTable(); - // Inherit lexical pragma flags from parent if available + // Inherit lexical pragma flags from the call site (not end-of-compilation snapshot) + // callSiteStrictOptions/callSiteFeatureFlags are embedded in the bytecode at the eval + // call site, capturing the exact pragmas in effect at that point (e.g. inside a + // "no strict 'refs'" block). Fall back to currentCode snapshot if not available. + symbolTable.strictOptionsStack.pop(); + symbolTable.strictOptionsStack.push(callSiteStrictOptions); + symbolTable.featureFlagsStack.pop(); + symbolTable.featureFlagsStack.push(callSiteFeatureFlags); if (currentCode != null) { - // Replace default values with parent's flags - symbolTable.strictOptionsStack.pop(); - symbolTable.strictOptionsStack.push(currentCode.strictOptions); - symbolTable.featureFlagsStack.pop(); - symbolTable.featureFlagsStack.push(currentCode.featureFlags); symbolTable.warningFlagsStack.pop(); symbolTable.warningFlagsStack.push((java.util.BitSet) currentCode.warningFlags.clone()); } diff --git a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java index d90e9619d..a6d927b5d 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java @@ -730,6 +730,28 @@ public String disassemble() { int keyReg = bytecode[pc++]; sb.append("GLOB_SLOT_GET r").append(rd).append(" = r").append(globReg2).append("{r").append(keyReg).append("}\n"); break; + case Opcodes.LOAD_SYMBOLIC_GLOB: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + sb.append("LOAD_SYMBOLIC_GLOB r").append(rd).append(" = getGlobalIO(r").append(rs1).append(")\n"); + break; + case Opcodes.DEREF_GLOB: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + sb.append("DEREF_GLOB r").append(rd).append(" = r").append(rs1).append(".globDeref()\n"); + break; + case Opcodes.DEREF_HASH_NONSTRICT: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + rs2 = bytecode[pc++]; + sb.append("DEREF_HASH_NONSTRICT r").append(rd).append(" = r").append(rs1).append(".hashDerefNonStrict(pool[").append(rs2).append("])\n"); + break; + case Opcodes.DEREF_ARRAY_NONSTRICT: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + rs2 = bytecode[pc++]; + sb.append("DEREF_ARRAY_NONSTRICT r").append(rd).append(" = r").append(rs1).append(".arrayDerefNonStrict(pool[").append(rs2).append("])\n"); + break; case Opcodes.SPRINTF: rd = bytecode[pc++]; int formatReg = bytecode[pc++]; @@ -766,8 +788,9 @@ public String disassemble() { case Opcodes.OPEN: rd = bytecode[pc++]; int openCtx = bytecode[pc++]; + int openFhReg = bytecode[pc++]; int openArgs = bytecode[pc++]; - sb.append("OPEN r").append(rd).append(" = open(ctx=").append(openCtx).append(", r").append(openArgs).append(")\n"); + sb.append("OPEN r").append(rd).append(" = open(ctx=").append(openCtx).append(", fh=r").append(openFhReg).append(", args=r").append(openArgs).append(")\n"); break; case Opcodes.READLINE: rd = bytecode[pc++]; @@ -863,6 +886,14 @@ public String disassemble() { rs = bytecode[pc++]; sb.append("DEREF r").append(rd).append(" = ${r").append(rs).append("}\n"); break; + case Opcodes.DEREF_NONSTRICT: { + rd = bytecode[pc++]; + rs = bytecode[pc++]; + int derefNsPkgIdx = bytecode[pc++]; + sb.append("DEREF_NONSTRICT r").append(rd).append(" = ${r").append(rs) + .append("} pkg=").append(stringPool[derefNsPkgIdx]).append("\n"); + break; + } case Opcodes.GET_TYPE: rd = bytecode[pc++]; rs = bytecode[pc++]; diff --git a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java index 505c00733..efd8ab846 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java +++ b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java @@ -713,14 +713,28 @@ public static int executePostAutoDecrement(int[] bytecode, int pc, RuntimeBase[] /** * Execute open operation. - * Format: OPEN rd ctx argsReg + * Format: OPEN rd ctx fhReg argsReg + * + * fhReg is the actual lvalue register for the filehandle. IOOperator.open calls + * fileHandle.set() on args[0], so we pass registers[fhReg] directly (not a copy + * from ARRAY_PUSH which would call addToArray -> new RuntimeScalar(this)). + * After the call, registers[fhReg] has been updated in place by set(). */ public static int executeOpen(int[] bytecode, int pc, RuntimeBase[] registers) { int rd = bytecode[pc++]; int ctx = bytecode[pc++]; + int fhReg = bytecode[pc++]; int argsReg = bytecode[pc++]; RuntimeArray argsArray = (RuntimeArray) registers[argsReg]; - RuntimeBase[] argsVarargs = argsArray.elements.toArray(new RuntimeBase[0]); + + // Build varargs with the actual fh lvalue as args[0], then the rest + RuntimeBase fhLvalue = registers[fhReg]; + RuntimeBase[] argsVarargs = new RuntimeBase[argsArray.elements.size() + 1]; + argsVarargs[0] = fhLvalue; + for (int i = 0; i < argsArray.elements.size(); i++) { + argsVarargs[i + 1] = argsArray.elements.get(i); + } + registers[rd] = IOOperator.open(ctx, argsVarargs); return pc; } diff --git a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java index 559e67830..6fccae878 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java @@ -1071,5 +1071,25 @@ public class Opcodes { * Effect: Restores previous packageName */ public static final short POP_PACKAGE = 308; + /** Load glob via symbolic reference: rd = GlobalVariable.getGlobalIO(nameReg.toString()) + * Format: LOAD_SYMBOLIC_GLOB rd nameReg */ + public static final short LOAD_SYMBOLIC_GLOB = 333; + + /** Dereference scalar as glob: rd = scalarReg.globDeref() + * Format: DEREF_GLOB rd scalarReg */ + public static final short DEREF_GLOB = 334; + + /** Dereference scalar as hash (no strict refs): rd = scalarReg.hashDerefNonStrict(pkg) + * Format: DEREF_HASH_NONSTRICT rd scalarReg packageIdx */ + public static final short DEREF_HASH_NONSTRICT = 335; + + /** Dereference scalar as array (no strict refs): rd = scalarReg.arrayDerefNonStrict(pkg) + * Format: DEREF_ARRAY_NONSTRICT rd scalarReg packageIdx */ + public static final short DEREF_ARRAY_NONSTRICT = 336; + + /** Dereference scalar (no strict refs): rd = scalarReg.scalarDerefNonStrict(pkg) + * Format: DEREF_NONSTRICT rd scalarReg packageIdx */ + public static final short DEREF_NONSTRICT = 337; + private Opcodes() {} // Utility class - no instantiation } diff --git a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java index 39d8eafea..98841ee3e 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java @@ -240,6 +240,9 @@ public static int executeEvalString( int rd = bytecode[pc++]; int stringReg = bytecode[pc++]; + // Read call-site strict/feature flags embedded by CompileOperator at the eval site + int callSiteStrictOptions = bytecode[pc++]; + int callSiteFeatureFlags = bytecode[pc++]; // Get the code string - handle both RuntimeScalar and RuntimeList (from string interpolation) RuntimeBase codeValue = registers[stringReg]; @@ -255,10 +258,12 @@ public static int executeEvalString( // Call EvalStringHandler to parse, compile, and execute RuntimeScalar result = EvalStringHandler.evalString( perlCode, - code, // Current InterpretedCode for context - registers, // Current registers for variable access + code, // Current InterpretedCode for context + registers, // Current registers for variable access code.sourceName, - code.sourceLine + code.sourceLine, + callSiteStrictOptions, // Strict flags at the eval call site + callSiteFeatureFlags // Feature flags at the eval call site ); registers[rd] = result; @@ -290,6 +295,108 @@ public static int executeSelect( return pc; } + /** + * DEREF_HASH_NONSTRICT: rd = scalarReg.hashDerefNonStrict(pkg) + * Format: [DEREF_HASH_NONSTRICT] [rd] [scalarReg] [packageIdx] + * Effect: Dereferences a scalar as a hash with no-strict-refs semantics + */ + public static int executeDerefHashNonStrict( + int[] bytecode, + int pc, + RuntimeBase[] registers, + InterpretedCode code) { + + int rd = bytecode[pc++]; + int scalarReg = bytecode[pc++]; + int packageIdx = bytecode[pc++]; + + String packageName = code.stringPool[packageIdx]; + RuntimeScalar scalar = (RuntimeScalar) registers[scalarReg]; + registers[rd] = scalar.hashDerefNonStrict(packageName); + return pc; + } + + /** + * DEREF_ARRAY_NONSTRICT: rd = scalarReg.arrayDerefNonStrict(pkg) + * Format: [DEREF_ARRAY_NONSTRICT] [rd] [scalarReg] [packageIdx] + * Effect: Dereferences a scalar as an array with no-strict-refs semantics + */ + public static int executeDerefArrayNonStrict( + int[] bytecode, + int pc, + RuntimeBase[] registers, + InterpretedCode code) { + + int rd = bytecode[pc++]; + int scalarReg = bytecode[pc++]; + int packageIdx = bytecode[pc++]; + + String packageName = code.stringPool[packageIdx]; + RuntimeScalar scalar = (RuntimeScalar) registers[scalarReg]; + registers[rd] = scalar.arrayDerefNonStrict(packageName); + return pc; + } + + /** + * DEREF_NONSTRICT: rd = scalarReg.scalarDerefNonStrict(pkg) + * Format: [DEREF_NONSTRICT] [rd] [scalarReg] [packageIdx] + * Effect: Dereferences a scalar as a scalar with no-strict-refs semantics ($$ref or symbolic ref) + */ + public static int executeDerefNonStrict( + int[] bytecode, + int pc, + RuntimeBase[] registers, + InterpretedCode code) { + + int rd = bytecode[pc++]; + int scalarReg = bytecode[pc++]; + int packageIdx = bytecode[pc++]; + + String packageName = code.stringPool[packageIdx]; + RuntimeScalar scalar = (RuntimeScalar) registers[scalarReg]; + registers[rd] = scalar.scalarDerefNonStrict(packageName); + return pc; + } + + /** + * DEREF_GLOB: rd = scalarReg.globDeref() + * Format: [DEREF_GLOB] [rd] [scalarReg] + * Effect: Dereferences a scalar as a glob (** postfix deref) + */ + public static int executeDerefGlob( + int[] bytecode, + int pc, + RuntimeBase[] registers) { + + int rd = bytecode[pc++]; + int scalarReg = bytecode[pc++]; + + RuntimeScalar scalar = (RuntimeScalar) registers[scalarReg]; + registers[rd] = scalar.globDeref(); + return pc; + } + + /** + * LOAD_SYMBOLIC_GLOB: rd = getGlobalIO(nameReg.toString()) + * Format: [LOAD_SYMBOLIC_GLOB] [rd] [nameReg] + * Effect: Loads a glob via a runtime string expression (e.g. *{"Pkg::name"}) + */ + public static int executeLoadSymbolicGlob( + int[] bytecode, + int pc, + RuntimeBase[] registers) { + + int rd = bytecode[pc++]; + int nameReg = bytecode[pc++]; + + // Normalize the name with the current package (e.g. "mysub" -> "main::mysub") + String rawName = registers[nameReg].toString(); + String pkg = InterpreterState.currentPackage.get().toString(); + String globName = NameNormalizer.normalizeVariableName(rawName, pkg); + registers[rd] = GlobalVariable.getGlobalIO(globName); + return pc; + } + /** * SLOW_LOAD_GLOB: rd = getGlobalIO(name) * Format: [SLOW_LOAD_GLOB] [rd] [name_idx] @@ -921,12 +1028,18 @@ public static int executeGlobSlotGet( // Use runtime current package — correct for both regular code and eval STRING String pkg = InterpreterState.currentPackage.get().toString(); - // Convert to scalar if needed - RuntimeScalar glob = globBase.scalar(); - - // Call hashDerefGetNonStrict which for RuntimeGlob accesses the slot directly - // without dereferencing the glob as a hash - registers[rd] = glob.hashDerefGetNonStrict(key, pkg); + RuntimeScalar result; + if (globBase instanceof RuntimeGlob globObj) { + // Direct glob — access slot via a scalar wrapper that holds the glob reference + // RuntimeGlob.hashDerefGetNonStrict is not available directly; use scalar() to get + // a RuntimeScalar of type GLOB, then call hashDerefGetNonStrict on it. + // But scalar() on a RuntimeGlob returns a GLOB-typed scalar that delegates correctly. + result = globObj.scalar().hashDerefGetNonStrict(key, pkg); + } else { + // Already a scalar (e.g. from a variable holding a glob) + result = globBase.scalar().hashDerefGetNonStrict(key, pkg); + } + registers[rd] = result; return pc; } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java index e3f201f33..374c60118 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java @@ -1106,6 +1106,13 @@ public RuntimeScalar scalarDerefNonStrict(String packageName) { } return switch (type) { + case UNDEF -> { + // Autovivify: create a new scalar reference for undefined values (same as scalarDeref) + RuntimeScalar newScalar = new RuntimeScalar(); + this.value = newScalar; + this.type = RuntimeScalarType.REFERENCE; + yield newScalar; + } case REFERENCE -> (RuntimeScalar) value; case TIED_SCALAR -> tiedFetch().scalarDerefNonStrict(packageName); default -> {