From 106124604d4818032e7bbeb481d68328aa0932d3 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Thu, 19 Feb 2026 16:22:37 +0100 Subject: [PATCH] fix: Handle glob slot access (*X{HASH}) correctly in interpreter eval When accessing glob slots like *X{HASH} inside eval STRING, the interpreter was incorrectly dereferencing the glob as a hash (returning %-) and then trying to access the "HASH" key, which caused "No group with name " errors when the glob was aliased to special variables like *-. The baseline compiler works correctly because RuntimeGlob overrides hashDerefGetNonStrict() to directly access glob slots via getGlobSlot(), bypassing hash dereference. The interpreter was using DEREF_HASH + HASH_GET which doesn't invoke this override. Solution: Added GLOB_SLOT_GET opcode (230, before LASTOP to avoid generated section) that detects glob slot access patterns (*X{key}) in BytecodeCompiler.handleGeneralHashAccess() and emits a single operation calling glob.hashDerefGetNonStrict(key, package) to match the baseline behavior. Added documentation comment explaining where to add manual opcodes to prevent them from being overwritten by the opcode generation tool. Fixes re/reg_namedcapture.t test 1 (0/2 -> 1/2 passing) Co-Authored-By: Claude Opus 4.6 --- .../interpreter/BytecodeCompiler.java | 49 +++++++++++++------ .../interpreter/BytecodeInterpreter.java | 7 +++ .../interpreter/InterpretedCode.java | 6 +++ .../org/perlonjava/interpreter/Opcodes.java | 13 ++++- .../interpreter/SlowOpcodeHandler.java | 28 +++++++++++ 5 files changed, 86 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java b/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java index ca8c268ee..6e8a00636 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java @@ -1449,25 +1449,42 @@ private void handleGeneralHashAccess(BinaryOperatorNode node) { keyReg = lastResultReg; } - // The base might be either: - // 1. A RuntimeHash (from %hash which was a hash variable) - // 2. A RuntimeScalar containing a hashref (from $hash{outer}) - // We need to handle both cases. Dereference if needed. + // Check if this is a glob slot access: *X{key} + // In this case, node.left is an OperatorNode with operator "*" + boolean isGlobSlotAccess = (node.left instanceof OperatorNode) && + ((OperatorNode) node.left).operator.equals("*"); + + if (isGlobSlotAccess) { + // For glob slot access, call hashDerefGetNonStrict directly + // This uses RuntimeGlob's override which accesses the slot without dereferencing + int rd = allocateRegister(); + emit(Opcodes.GLOB_SLOT_GET); + emitReg(rd); + emitReg(baseReg); + emitReg(keyReg); - // For now, let's assume it's a scalar with hashref and dereference it first - int hashReg = allocateRegister(); - emit(Opcodes.DEREF_HASH); - emitReg(hashReg); - emitReg(baseReg); + lastResultReg = rd; + } else { + // Normal hash access: dereference first, then get element + // The base might be either: + // 1. A RuntimeHash (from %hash which was a hash variable) + // 2. A RuntimeScalar containing a hashref (from $hash{outer}) + // We need to handle both cases. Dereference if needed. - // Now get the element - int rd = allocateRegister(); - emit(Opcodes.HASH_GET); - emitReg(rd); - emitReg(hashReg); - emitReg(keyReg); + int hashReg = allocateRegister(); + emit(Opcodes.DEREF_HASH); + emitReg(hashReg); + emitReg(baseReg); - lastResultReg = rd; + // Now get the element + int rd = allocateRegister(); + emit(Opcodes.HASH_GET); + emitReg(rd); + emitReg(hashReg); + emitReg(keyReg); + + lastResultReg = rd; + } } else { throwCompilerException("Multi-element hash access not yet implemented"); } diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java index 3d1318885..ef8d55bb9 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java @@ -2330,6 +2330,13 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } + case Opcodes.GLOB_SLOT_GET: { + // Glob slot access: rd = glob.hashDerefGetNonStrict(key, "main") + // Format: GLOB_SLOT_GET rd globReg keyReg + pc = SlowOpcodeHandler.executeGlobSlotGet(bytecode, pc, registers); + break; + } + // GENERATED_HANDLERS_END default: diff --git a/src/main/java/org/perlonjava/interpreter/InterpretedCode.java b/src/main/java/org/perlonjava/interpreter/InterpretedCode.java index 0cf0117b1..e526d1be6 100644 --- a/src/main/java/org/perlonjava/interpreter/InterpretedCode.java +++ b/src/main/java/org/perlonjava/interpreter/InterpretedCode.java @@ -708,6 +708,12 @@ public String disassemble() { int opStrIdx = bytecode[pc++]; sb.append("FILETEST_LASTHANDLE r").append(rd).append(" = ").append(stringPool[opStrIdx]).append(" _\n"); break; + case Opcodes.GLOB_SLOT_GET: + rd = bytecode[pc++]; + int globReg2 = bytecode[pc++]; + 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.PUSH_LOCAL_VARIABLE: rs = bytecode[pc++]; sb.append("PUSH_LOCAL_VARIABLE r").append(rs).append("\n"); diff --git a/src/main/java/org/perlonjava/interpreter/Opcodes.java b/src/main/java/org/perlonjava/interpreter/Opcodes.java index 4a4fa1611..3e3293730 100644 --- a/src/main/java/org/perlonjava/interpreter/Opcodes.java +++ b/src/main/java/org/perlonjava/interpreter/Opcodes.java @@ -855,10 +855,21 @@ public class Opcodes { /** Logical OR assignment: target ||= value */ public static final short LOGICAL_OR_ASSIGN = 229; + // ================================================================= + // MANUAL OPCODE ADDITIONS + // Add new custom opcodes HERE (before LASTOP), not in the generated section below. + // The GENERATED_OPCODES section is automatically regenerated and will overwrite any manual additions. + // After adding an opcode here, increment LASTOP to match the highest opcode number. + // ================================================================= + + /** Glob slot access: rd = glob.hashDerefGetNonStrict(key, "main") + * Used for *X{HASH} style access to glob slots */ + public static final short GLOB_SLOT_GET = 230; + // ================================================================= // BUILT-IN FUNCTION OPCODES - after LASTOP // Last manually-assigned opcode (for tool reference) - private static final short LASTOP = 229; + private static final short LASTOP = 230; // ================================================================= // Generated by dev/tools/generate_opcode_handlers.pl diff --git a/src/main/java/org/perlonjava/interpreter/SlowOpcodeHandler.java b/src/main/java/org/perlonjava/interpreter/SlowOpcodeHandler.java index adcdffdc8..2f3034287 100644 --- a/src/main/java/org/perlonjava/interpreter/SlowOpcodeHandler.java +++ b/src/main/java/org/perlonjava/interpreter/SlowOpcodeHandler.java @@ -1025,6 +1025,34 @@ public static int executeFiletestLastHandle( return pc; } + /** + * GLOB_SLOT_GET: rd = glob.hashDerefGetNonStrict(key, "main") + * Format: [GLOB_SLOT_GET] [rd] [globReg] [keyReg] + * Effect: Access glob slot (like *X{HASH}) using RuntimeGlob's override + * This ensures proper glob slot access without incorrectly dereferencing the glob as a hash + */ + public static int executeGlobSlotGet( + short[] bytecode, + int pc, + RuntimeBase[] registers) { + + int rd = bytecode[pc++]; + int globReg = bytecode[pc++]; + int keyReg = bytecode[pc++]; + + RuntimeBase globBase = registers[globReg]; + RuntimeScalar key = (RuntimeScalar) registers[keyReg]; + + // 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, "main"); + + return pc; + } + private SlowOpcodeHandler() { // Utility class - no instantiation }