diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index b8ce5c3fc..3533fd10f 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -366,6 +366,12 @@ 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 in the symbol table. */ + boolean isStrictRefsEnabled() { + return emitterContext != null && emitterContext.symbolTable != null + && emitterContext.symbolTable.isStrictOptionEnabled(Strict.HINT_STRICT_REFS); + } + boolean shouldBlockGlobalUnderStrictVars(String varName) { // Only check if strict vars is enabled if (emitterContext == null || emitterContext.symbolTable == null) { @@ -2484,7 +2490,11 @@ void compileVariableReference(OperatorNode node, String op) { // Lexical variable - use existing register lastResultReg = getVariableRegister(varName); } else { - // Global variable - load it + // Global variable - check strict vars then load + if (shouldBlockGlobalUnderStrictVars(varName)) { + throwCompilerException("Global symbol \"" + varName + "\" requires explicit package name"); + } + // Use NameNormalizer to properly handle special variables (like $&) // which must always be in the "main" package String globalVarName = varName.substring(1); // Remove $ sigil first @@ -2507,16 +2517,23 @@ void compileVariableReference(OperatorNode node, String op) { // Execute the block to get a variable name string, then load that variable BlockNode block = (BlockNode) node.operand; - // Compile the block + // Check strict refs at compile time — mirrors JVM path in EmitVariable.java block.accept(this); int blockResultReg = lastResultReg; - - // Load via symbolic reference int rd = allocateRegister(); - emitWithToken(Opcodes.LOAD_SYMBOLIC_SCALAR, node.getIndex()); - emitReg(rd); - emitReg(blockResultReg); - + if (isStrictRefsEnabled()) { + // strict refs: scalarDeref() — throws for non-refs + emitWithToken(Opcodes.DEREF_SCALAR_STRICT, node.getIndex()); + emitReg(rd); + emitReg(blockResultReg); + } else { + // no strict refs: scalarDerefNonStrict(pkg) — allows symrefs + int pkgIdx = addToStringPool(getCurrentPackage()); + emitWithToken(Opcodes.DEREF_SCALAR_NONSTRICT, node.getIndex()); + emitReg(rd); + emitReg(blockResultReg); + emit(pkgIdx); + } lastResultReg = rd; } else if (node.operand instanceof OperatorNode) { // Operator dereference: $$x, $${expr}, etc. diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index 485392a19..21bc64746 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -1769,12 +1769,15 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c pc = executeSystemOps(opcode, bytecode, pc, registers); break; - // Group 9: Special I/O (151-154) and DEREF_GLOB + // Group 9: Special I/O (151-154), glob ops, strict deref case Opcodes.EVAL_STRING: case Opcodes.SELECT_OP: case Opcodes.LOAD_GLOB: case Opcodes.SLEEP_OP: case Opcodes.DEREF_GLOB: + case Opcodes.LOAD_GLOB_DYNAMIC: + case Opcodes.DEREF_SCALAR_STRICT: + case Opcodes.DEREF_SCALAR_NONSTRICT: pc = executeSpecialIO(opcode, bytecode, pc, registers, code); break; @@ -3087,7 +3090,8 @@ private static int executeSystemOps(int opcode, int[] bytecode, int pc, /** * Execute special I/O operations (opcodes 151-154). - * Handles: EVAL_STRING, SELECT_OP, LOAD_GLOB, SLEEP_OP, DEREF_GLOB + * Handles: EVAL_STRING, SELECT_OP, LOAD_GLOB, SLEEP_OP, DEREF_GLOB, LOAD_GLOB_DYNAMIC, + * DEREF_SCALAR_STRICT, DEREF_SCALAR_NONSTRICT */ private static int executeSpecialIO(int opcode, int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { @@ -3102,6 +3106,12 @@ private static int executeSpecialIO(int opcode, int[] bytecode, int pc, return SlowOpcodeHandler.executeSleep(bytecode, pc, registers); case Opcodes.DEREF_GLOB: return SlowOpcodeHandler.executeDerefGlob(bytecode, pc, registers, code); + case Opcodes.LOAD_GLOB_DYNAMIC: + return SlowOpcodeHandler.executeLoadGlobDynamic(bytecode, pc, registers, code); + case Opcodes.DEREF_SCALAR_STRICT: + return SlowOpcodeHandler.executeDerefScalarStrict(bytecode, pc, registers); + case Opcodes.DEREF_SCALAR_NONSTRICT: + return SlowOpcodeHandler.executeDerefScalarNonStrict(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..372da7562 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -641,36 +641,59 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, // We need to evaluate the LHS FIRST to get the variable name, // then evaluate the RHS, to ensure the RHS doesn't clobber the LHS registers if (node.left instanceof OperatorNode leftOp && leftOp.operator.equals("$")) { + boolean strictRefsEnabled = bytecodeCompiler.isStrictRefsEnabled(); + if (leftOp.operand instanceof BlockNode) { - // ${block} = value + // ${block} = value — mirrors JVM EmitVariable.java case "$" BlockNode block = (BlockNode) leftOp.operand; block.accept(bytecodeCompiler); int nameReg = bytecodeCompiler.lastResultReg; - // Now compile the RHS + // Deref to get lvalue target (strict or non-strict) + int derefReg = bytecodeCompiler.allocateRegister(); + if (strictRefsEnabled) { + bytecodeCompiler.emitWithToken(Opcodes.DEREF_SCALAR_STRICT, node.getIndex()); + bytecodeCompiler.emitReg(derefReg); + bytecodeCompiler.emitReg(nameReg); + } else { + int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage()); + bytecodeCompiler.emitWithToken(Opcodes.DEREF_SCALAR_NONSTRICT, node.getIndex()); + bytecodeCompiler.emitReg(derefReg); + bytecodeCompiler.emitReg(nameReg); + bytecodeCompiler.emit(pkgIdx); + } + + // Now compile the RHS and assign node.right.accept(bytecodeCompiler); int valueReg = bytecodeCompiler.lastResultReg; - - // Use STORE_SYMBOLIC_SCALAR to store via symbolic reference - bytecodeCompiler.emit(Opcodes.STORE_SYMBOLIC_SCALAR); - bytecodeCompiler.emitReg(nameReg); + bytecodeCompiler.emit(Opcodes.SET_SCALAR); + bytecodeCompiler.emitReg(derefReg); bytecodeCompiler.emitReg(valueReg); bytecodeCompiler.lastResultReg = valueReg; return; } else if (leftOp.operand instanceof OperatorNode) { - // $$var = value (scalar dereference assignment) - // Evaluate the inner expression to get the variable name + // $$var = value — mirrors JVM EmitVariable.java case "$" leftOp.operand.accept(bytecodeCompiler); int nameReg = bytecodeCompiler.lastResultReg; - // Now compile the RHS + int derefReg = bytecodeCompiler.allocateRegister(); + if (strictRefsEnabled) { + bytecodeCompiler.emitWithToken(Opcodes.DEREF_SCALAR_STRICT, node.getIndex()); + bytecodeCompiler.emitReg(derefReg); + bytecodeCompiler.emitReg(nameReg); + } else { + int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage()); + bytecodeCompiler.emitWithToken(Opcodes.DEREF_SCALAR_NONSTRICT, node.getIndex()); + bytecodeCompiler.emitReg(derefReg); + bytecodeCompiler.emitReg(nameReg); + bytecodeCompiler.emit(pkgIdx); + } + node.right.accept(bytecodeCompiler); int valueReg = bytecodeCompiler.lastResultReg; - - // Use STORE_SYMBOLIC_SCALAR to store via symbolic reference - bytecodeCompiler.emit(Opcodes.STORE_SYMBOLIC_SCALAR); - bytecodeCompiler.emitReg(nameReg); + bytecodeCompiler.emit(Opcodes.SET_SCALAR); + bytecodeCompiler.emitReg(derefReg); bytecodeCompiler.emitReg(valueReg); bytecodeCompiler.lastResultReg = valueReg; @@ -896,6 +919,25 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(globReg); bytecodeCompiler.emitReg(valueReg); + bytecodeCompiler.lastResultReg = globReg; + } else if (leftOp.operator.equals("*") && leftOp.operand instanceof BlockNode) { + // Symbolic typeglob assignment: *{"name"} = value (no strict refs) + // Evaluate the block to get the glob name as a scalar, then load glob by name + leftOp.operand.accept(bytecodeCompiler); + int nameScalarReg = bytecodeCompiler.lastResultReg; + + int globReg = bytecodeCompiler.allocateRegister(); + int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage()); + bytecodeCompiler.emitWithToken(Opcodes.LOAD_GLOB_DYNAMIC, node.getIndex()); + bytecodeCompiler.emitReg(globReg); + bytecodeCompiler.emitReg(nameScalarReg); + bytecodeCompiler.emit(pkgIdx); + + // 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..c419ee711 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -1669,15 +1669,25 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.throwCompilerException("open requires arguments"); } - // Compile all arguments into a list + // Compile all arguments into a list. + // Track the first-arg (filehandle) register so we can write the GLOB back + // after OPEN — IOOperator.open() does fileHandle.set() on a copy in the array, + // so we must propagate the result back to the original lexical register. int argsReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.NEW_ARRAY); bytecodeCompiler.emitReg(argsReg); + int fhReg = -1; + boolean first = true; for (Node arg : argsList.elements) { arg.accept(bytecodeCompiler); int elemReg = bytecodeCompiler.lastResultReg; + if (first) { + fhReg = elemReg; // remember the filehandle lvalue register + first = false; + } + bytecodeCompiler.emit(Opcodes.ARRAY_PUSH); bytecodeCompiler.emitReg(argsReg); bytecodeCompiler.emitReg(elemReg); @@ -1690,6 +1700,24 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); bytecodeCompiler.emitReg(argsReg); + // Write the (now-modified) first element of args back to the fh register. + // IOOperator.open() calls fileHandle.set(glob) on a copy inside the array, + // so we must retrieve element 0 and store it back into the lexical $fh. + if (fhReg >= 0) { + int idx0Reg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.LOAD_INT); + bytecodeCompiler.emitReg(idx0Reg); + bytecodeCompiler.emit(0); // index 0 + int gotReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.ARRAY_GET); + bytecodeCompiler.emitReg(gotReg); + bytecodeCompiler.emitReg(argsReg); + bytecodeCompiler.emitReg(idx0Reg); + bytecodeCompiler.emit(Opcodes.SET_SCALAR); + bytecodeCompiler.emitReg(fhReg); + bytecodeCompiler.emitReg(gotReg); + } + bytecodeCompiler.lastResultReg = rd; } else if (op.equals("matchRegex")) { // m/pattern/flags - create a regex and optionally match against a string diff --git a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java index d4741b087..2ba27a76e 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java @@ -1076,5 +1076,22 @@ public class Opcodes { * Format: DEREF_GLOB rd rs nameIdx(currentPackage) */ public static final short DEREF_GLOB = 333; + /** Load glob by runtime name (symbolic ref): rd = GlobalVariable.getGlobalIO(normalize(nameReg, pkg)) + * Used for *{"name"} = value typeglob assignment with dynamic name + * Format: LOAD_GLOB_DYNAMIC rd nameReg pkgIdx */ + public static final short LOAD_GLOB_DYNAMIC = 334; + + /** Scalar dereference (strict refs): rd = rs.scalarDeref() + * Throws "Can't use string as a SCALAR ref while strict refs in use" for non-refs. + * Matches JVM path: scalarDeref() — used when strict refs is enabled. + * Format: DEREF_SCALAR_STRICT rd rs */ + public static final short DEREF_SCALAR_STRICT = 335; + + /** Scalar dereference (no strict refs): rd = rs.scalarDerefNonStrict(pkg) + * Allows symbolic references (string name -> global variable lookup). + * Matches JVM path: scalarDerefNonStrict(pkg) — used when strict refs is disabled. + * Format: DEREF_SCALAR_NONSTRICT rd rs pkgIdx */ + public static final short DEREF_SCALAR_NONSTRICT = 336; + 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 bea587b70..09eb9aac0 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java @@ -313,6 +313,64 @@ public static int executeLoadGlob( return pc; } + /** + * LOAD_GLOB_DYNAMIC: rd = GlobalVariable.getGlobalIO(normalize(nameReg, pkg)) + * Format: LOAD_GLOB_DYNAMIC rd nameReg pkgIdx + * Effect: Loads a glob by runtime name — used for *{"name"} = value symbolic assignment + */ + public static int executeLoadGlobDynamic( + int[] bytecode, + int pc, + RuntimeBase[] registers, + InterpretedCode code) { + + int rd = bytecode[pc++]; + int nameReg = bytecode[pc++]; + int pkgIdx = bytecode[pc++]; + + String pkg = code.stringPool[pkgIdx]; + String name = ((RuntimeScalar) registers[nameReg]).toString(); + String globalName = NameNormalizer.normalizeVariableName(name, pkg); + + registers[rd] = GlobalVariable.getGlobalIO(globalName); + return pc; + } + + /** + * DEREF_SCALAR_STRICT: rd = rs.scalarDeref() + * Format: DEREF_SCALAR_STRICT rd rs + * Matches JVM path: scalarDeref() — throws for non-refs under strict refs. + */ + public static int executeDerefScalarStrict( + int[] bytecode, + int pc, + RuntimeBase[] registers) { + + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = ((RuntimeScalar) registers[rs]).scalarDeref(); + return pc; + } + + /** + * DEREF_SCALAR_NONSTRICT: rd = rs.scalarDerefNonStrict(pkg) + * Format: DEREF_SCALAR_NONSTRICT rd rs pkgIdx + * Matches JVM path: scalarDerefNonStrict(pkg) — allows symbolic refs. + */ + public static int executeDerefScalarNonStrict( + int[] bytecode, + int pc, + RuntimeBase[] registers, + InterpretedCode code) { + + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + int pkgIdx = bytecode[pc++]; + String pkg = code.stringPool[pkgIdx]; + registers[rd] = ((RuntimeScalar) registers[rs]).scalarDerefNonStrict(pkg); + return pc; + } + /** * DEREF_GLOB: rd = rs.globDerefNonStrict(currentPackage) * Format: DEREF_GLOB rd rs nameIdx(currentPackage)