Skip to content

Fix interpreter $_ variable in foreach loops#220

Merged
fglock merged 4 commits into
masterfrom
fix-interpreter-foreach-dollar-underscore
Feb 23, 2026
Merged

Fix interpreter $_ variable in foreach loops#220
fglock merged 4 commits into
masterfrom
fix-interpreter-foreach-dollar-underscore

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Feb 23, 2026

Summary

Two bugs fixed in the bytecode interpreter for $_ in foreach loops.

Bug 1: local $_ produced $main::main::_ instead of $main::_

The local operator handler was manually concatenating packageName + "::" + name, but when $_ is parsed its IdentifierNode.name is already main::_, producing the double-qualified name. Fixed by using NameNormalizer.normalizeVariableName() consistently (6 sites).

Bug 2: $_ not aliased to loop element in foreach loops

The interpreter's For1Node handler stored the next iterator element into a register but never aliased it to $main::_, so $_ was always undefined inside the loop body.

Solution: two new superinstructions

  • LOCAL_SCALAR_SAVE_LEVEL rd levelReg nameIdx (302): atomically saves getLocalLevel() into levelReg before calling makeLocal(name). The pre-push level is used by POP_LOCAL_LEVEL after the loop to correctly restore $_ at any nesting depth.

  • FOREACH_GLOBAL_NEXT_OR_EXIT varReg iterReg nameIdx exitTarget (304): per-iteration superinstruction combining hasNext + next() + aliasGlobalVariable(name, element) + conditional exit.

  • POP_LOCAL_LEVEL rs (303): restores DynamicVariableManager to the saved pre-push level after loop exit.

Safe coordination

visit(BlockNode) detects the BlockNode([local $_, For1Node(needsArrayOfAlias)]) pattern and skips the local $_ child using a local variable skipFirstChild (not a mutable field), so For1Node owns the full local/loop/restore sequence.

Verified

for (1..3) { say $_ }                              # 1 2 3
$_ = 123; for (1..3) { show() } show()             # 1 2 3 123
# nested foreach loops restore $_ correctly

Two bugs fixed in the bytecode interpreter:

Bug 1: local $_ was producing $main::main::_ instead of $main::_
- Replace 6 manual packageName + "::" + name concatenations in the
  'local' operator handler with NameNormalizer.normalizeVariableName()

Bug 2: $_ not aliased to loop element in foreach loops
- Add two new superinstructions:
  LOCAL_SCALAR_SAVE_LEVEL (302): atomically saves getLocalLevel() before
    calling makeLocal(), so POP_LOCAL_LEVEL can correctly restore $_ after
    the loop regardless of nesting depth.
  FOREACH_GLOBAL_NEXT_OR_EXIT (304): per-iteration superinstruction combining
    hasNext + next() + aliasGlobalVariable + conditional exit.
  POP_LOCAL_LEVEL (303): restores DynamicVariableManager to saved level.
- visit(BlockNode) detects the BlockNode([local $_, For1Node(needsArrayOfAlias)])
  pattern and skips the 'local $_' child directly (safe local variable, not a
  mutable field), letting For1Node own the full local/loop/restore sequence.

Verified:
  for (1..3) { say $_ }  =>  1 2 3
  $_ = 123; for (1..3) { show() } show()  =>  1 2 3 123
  nested foreach loops with $_ restore correctly
Restructure For1Node bytecode from while-style to do-while style,
saving one GOTO per iteration:

Before:
  loopStart:
    FOREACH_NEXT_OR_EXIT -> exit   (check at top)
    body
    GOTO loopStart                 (back-edge every iteration)
  exit:

After:
  GOTO loopCheck                   (one-time entry jump)
  body:
    body
  loopCheck:
    FOREACH_NEXT_OR_EXIT -> body   (jump backward if has next)
  exit:                            (fall through if exhausted)

The superinstruction semantics are inverted: the target is now
bodyStart (jump there if has next) rather than exitTarget (jump
there if exhausted). Applies to both FOREACH_NEXT_OR_EXIT and
FOREACH_GLOBAL_NEXT_OR_EXIT.

Addresses issue #219.
@fglock fglock merged commit 2c3955c into master Feb 23, 2026
2 checks passed
@fglock fglock deleted the fix-interpreter-foreach-dollar-underscore branch February 23, 2026 12:46
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.

1 participant