Skip to content

Comments

Fix interpreter DEREF opcode regression in op/decl-refs.t#226

Merged
fglock merged 5 commits intomasterfrom
fix-interpreter-deref-glob
Feb 23, 2026
Merged

Fix interpreter DEREF opcode regression in op/decl-refs.t#226
fglock merged 5 commits intomasterfrom
fix-interpreter-deref-glob

Conversation

@fglock
Copy link
Owner

@fglock fglock commented Feb 23, 2026

Fixes #225

Problem

The interpreter DEREF opcode was calling scalarDeref() on all RuntimeScalar types, causing ARRAYREFERENCE, HASHREFERENCE, CODE, and REGEX types to throw 'Not a SCALAR reference' in no strict 'refs' contexts. This crashed op/decl-refs.t at line 105 with only 169/408 tests passing.

Root Cause

In no strict 'refs' contexts, the JVM compiler calls scalarDerefNonStrict() which passes non-scalar reference types through. The interpreter was incorrectly calling scalarDeref() on all types.

Fix

Pass through non-scalar reference types (ARRAYREFERENCE, HASHREFERENCE, CODE, REGEX) unchanged in the DEREF opcode handler. Only call scalarDeref() for scalar refs, undef, and non-reference types (strings, globs, FORMAT, IO, etc.).

Also adds:

  • DEREF_NONSTRICT opcode (337) for future use
  • executeDerefNonStrict() handler in SlowOpcodeHandler
  • Disassembler entry in InterpretedCode
  • UNDEF autovivification in scalarDerefNonStrict() matching scalarDeref() behavior

Results

Test Before After
op/decl-refs.t (interpreter) 169/408 272/408
op/postfixderef.t (interpreter) 85/86 85/86 ✓
op/require_gh20577.t (interpreter) 7/9 7/9 ✓

…inheritance

Multiple fixes for interpreter mode (JPERL_EVAL_USE_INTERPRETER=1):

1. Call-site strict/feature flags for eval STRING (CompileOperator, SlowOpcodeHandler,
   EvalStringHandler):
   Embed strict/feature flags at each eval call site in the bytecode. Previously
   EvalStringHandler used the end-of-compilation snapshot from InterpretedCode, which
   missed lexically-scoped pragma changes (e.g. 'no strict refs' inside a block).
   Now the exact flags at the eval call site are passed to EvalStringHandler.

2. Strict-refs-aware deref opcodes (BytecodeCompiler, Opcodes, SlowOpcodeHandler,
   BytecodeInterpreter):
   Added DEREF_HASH_NONSTRICT and DEREF_ARRAY_NONSTRICT opcodes. BytecodeCompiler
   now emits these when 'no strict refs' is in effect at compile time, matching the
   JVM path which calls hashDerefNonStrict/arrayDerefNonStrict.
   Handles: %$ref, %{block}, %{'name'}, @$ref, @{block}, @{'name'}.

3. Dynamic glob load/assign (BytecodeCompiler, CompileAssignment, Opcodes,
   SlowOpcodeHandler, BytecodeInterpreter):
   Added LOAD_SYMBOLIC_GLOB opcode for *{expr} and *{'name'} glob access.
   Added DEREF_GLOB opcode for ** postfix glob dereference.
   CompileAssignment now handles *(BlockNode), *(StringNode), *(OperatorNode)
   for dynamic glob assignment: *{'name'} = value, *{expr} = value.

4. @{'name'} and %{'name'} symref support (BytecodeCompiler):
   Added StringNode cases for @ and % dereference operators.

op/postfixderef.t: 85/128 -> 81/128 interpreter (was 78/128, improved by 3)
op/require_gh20577.t: 0/0 -> 4/9 interpreter (was 0/0, improved by 4)
…ormalization

1. local *glob support (BytecodeCompiler):
   Added '*' sigil case to compileVariableDeclaration. Emits LOAD_GLOB +
   PUSH_LOCAL_VARIABLE, matching JVM path (EmitOperatorLocal). This allows
   'local *mysubalias' to correctly save/restore the glob state so that
   assignments inside eval STRING persist in the outer scope.

2. DEREF always calls scalarDeref() (BytecodeInterpreter):
   Fixed both DEREF cases in the main switch and executeTypeOps helper.
   Previously, DEREF silently passed through non-REFERENCE scalars. Now it
   always calls scalarDeref() which throws 'Not a SCALAR reference' for IO,
   FORMAT, and other non-reference types, matching Perl semantics.

3. LOAD_SYMBOLIC_GLOB name normalization (SlowOpcodeHandler):
   executeLoadSymbolicGlob now normalizes the glob name with the current
   package (e.g. 'mysub' -> 'main::mysub') before calling getGlobalIO().
   Previously it used the raw name, creating a different glob object from
   the one where the sub was actually defined.

4. GLOB_SLOT_GET handles RuntimeGlob directly (SlowOpcodeHandler):
   executeGlobSlotGet now checks instanceof RuntimeGlob and calls
   hashDerefGetNonStrict on it directly (which RuntimeGlob overrides to
   call getGlobSlot). Previously .scalar() was called first which could
   lose the glob type.

5. Disassembler additions (InterpretedCode):
   Added LOAD_SYMBOLIC_GLOB, DEREF_GLOB, DEREF_HASH_NONSTRICT,
   DEREF_ARRAY_NONSTRICT to the bytecode disassembler.

op/postfixderef.t: interpreter now matches JVM exactly (85/85)
The DEREF opcode was calling scalarDeref() on all RuntimeScalar types,
causing 'Not a SCALAR reference' crashes for ARRAYREFERENCE, HASHREFERENCE,
CODE, and REGEX types in no-strict-refs contexts (e.g. decl-refs.t).

Fix: pass through non-scalar reference types (ARRAYREFERENCE, HASHREFERENCE,
CODE, REGEX) unchanged, and only call scalarDeref() for scalar refs, undef,
and non-reference types (strings, globs, FORMAT, IO, etc.).

This matches the JVM compiler behavior where scalarDerefNonStrict() is used
for no-strict contexts (which also passes through non-scalar refs).

Also adds:
- DEREF_NONSTRICT opcode (337) for future use
- executeDerefNonStrict() handler in SlowOpcodeHandler
- DEREF_NONSTRICT disassembler entry in InterpretedCode
- UNDEF autovivification in scalarDerefNonStrict() matching scalarDeref()

Results:
- op/decl-refs.t interpreter: 272/408 (was 169, regression fixed)
- op/postfixderef.t interpreter: 85/86 (unchanged)
- op/require_gh20577.t interpreter: 7/9 (unchanged)
@fglock fglock merged commit 7cfa085 into master Feb 23, 2026
2 checks passed
@fglock fglock deleted the fix-interpreter-deref-glob branch February 23, 2026 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Interpreter: ARRAY_PUSH copies lvalue scalars, breaking open() filehandle assignment

1 participant