Skip to content

Implement yield across call boundaries#23

Merged
wolfy-j merged 3 commits intomainfrom
fix/yield-across-call-boundaries
Mar 12, 2026
Merged

Implement yield across call boundaries#23
wolfy-j merged 3 commits intomainfrom
fix/yield-across-call-boundaries

Conversation

@wolfy-j
Copy link
Contributor

@wolfy-j wolfy-j commented Mar 12, 2026

Summary

  • System yield propagation through all VM call paths: callR, Call, getField/setField, objectArith, stringConcat, equals/lessThan/objectRational, and OP_TFORLOOP now detect yields and save continuation state for proper resume
  • Yield continuation mechanism: Each opcode that calls into user code checks yieldState after return. On yield, saves continuation type + target register, exits mainLoop. On resume, handleYieldContinuation finishes the opcode's post-call logic (store result, update loop control, adjust Pc)
  • Merged yield state: Combined yielded (bool) + yieldKind (uint8) into single yieldState field. coYield returns -2 to distinguish user yields from system yields without conflating thread-switch state
  • Coroutine boundary propagation: coResumePropagate handles system yield propagation through coroutine.resume boundaries, installing a continuation so the next resume re-enters the inner thread
  • Removed allocator: Switched to immutable value boxing (separate commit)

Supported yield-across patterns

Yields from Go functions (return -1) now work inside:

  • for...in iterators (TFORCALL/TFORLOOP)
  • Metamethod calls (__index, __newindex, __call, __add/__sub/etc., __unm, __len, __concat, __eq/__lt/__le)
  • pcall / xpcall (with proper error frame preservation)
  • Nested coroutine.resume chains (system yield propagates to host)
  • coroutine.wrap used as iterator
  • Any combination of the above

Test plan

  • 34 new yield-across-boundaries unit tests covering all call paths
  • Full go-lua test suite passes (all packages)
  • Zero benchmark regression on non-coroutine VM paths (< 3%)
  • Integration tests in wippy engine (11 tests) verify end-to-end yield/resume cycle

wolfy-j added 3 commits March 3, 2026 11:02
Tests cover all code paths that use nested mainLoop calls (callR/Call)
inside the bytecode loop, where yields from Go functions are not
properly propagated:

- OP_TFORLOOP: generic for-loop iterators (7 tests)
- __index/__newindex metamethods (3 tests)
- __add/__sub/__mul arithmetic metamethods (3 tests)
- __concat metamethod (1 test)
- __unm/__len metamethods (2 tests)
- __eq/__lt/__le comparison metamethods (3 tests)
- Mixed boundaries and nested scenarios (3 tests)

All 22 tests fail, confirming the VM limitation. The bug manifests as
either nil pointer dereference (when coroutine state is corrupted) or
double-execution of the iterator (callR returns early on yield but the
calling opcode continues processing with stale register state).
System yields (Go functions returning -1) now propagate correctly through
all VM call paths: callR, Call, getField, setField, objectArith, stringConcat,
equals, lessThan, and objectRational.

Each opcode site that calls into user code checks yieldState after the call
returns. On yield, it saves a continuation type (yieldCont) and target register
(yieldContRA), then exits mainLoop. On resume, handleYieldContinuation finishes
the opcode's post-call work (store result, update loop control, adjust Pc).

Yield state merges the old yielded bool and yieldKind into a single yieldState
field (0=none, 1=system, 2=user). coYield returns -2 to distinguish user yields
from system yields without pre-setting state before switchToParentThread.

coResumePropagate handles system yield propagation through coroutine boundaries,
installing a continuation so the next resume re-enters the inner thread.
@wolfy-j wolfy-j merged commit dc6e8db into main Mar 12, 2026
wolfy-j added a commit that referenced this pull request Mar 20, 2026
PR #24 introduced pool reset code referencing the old `yielded` bool
field, but PR #23 replaced it with `yieldState` uint8. Use proper
yieldNone/yieldSystem/yieldUser constants throughout.
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