Fibers: cooperative coroutines

Ruby-style cooperative coroutines (fiber_new/fiber_resume/fiber_yield/fiber_alive), implemented on real OS threads under the hood but never actually running concurrently: a mutex+condvar pair (ir/fiber.rs) ensures only one fiber's thread is ever unparked at a time, so control passes back and forth explicitly, deterministic and effectively single-threaded โ€” distinct from parallel_map above, which IS real parallelism. This demo is a classic fiber use case: a lazy, infinite Fibonacci generator the caller pulls values from one at a time, plus a second, independent fiber running the same generator function to show each fiber keeps its own state.

PatLang source
# Fibers: Ruby-style cooperative coroutines, implemented on real OS threads
# under the hood (see ir/fiber.rs) but never actually running concurrently
# -- a mutex+condvar pair ensures only one fiber's thread is ever unparked
# at a time, so control passes back and forth explicitly via fiber_yield/
# fiber_resume, deterministic and single-threaded in effect. Distinct from
# parallel_map (threading_demo.patlang), which IS real parallelism.
#
# Interpreter-only for now: compiled-native fiber support (patc1.exe) is a
# natural follow-up, not yet built -- this demo's transcript is captured
# via `pat --ir-run`, not a compiled binary.
#
# Classic fiber use case: a lazy, infinite generator the caller pulls
# values from one at a time, without the generator needing to know how
# many values will ever be requested.

make a function called fib_generator takes unused returns done
  let a = 0
  let b = 1
  while true do
    fiber_yield(a)
    let next_val = a + b
    let a = b
    let b = next_val
  end
  return "unreachable"
end

let gen = fiber_new("fib_generator")
let i = 0
while i < 12 do
  print(fiber_resume(gen, 0))
  let i = i + 1
end
print("generator still alive: " + fiber_alive(gen))

# A second, independent fiber running the SAME generator function,
# interleaved with the first -- proof each fiber has its own state
# (a, b) even though both call the same PatLang function.
print("--- a second, independent generator, interleaved ---")
let gen2 = fiber_new("fib_generator")
let j = 0
while j < 5 do
  print("gen1: " + fiber_resume(gen, 0) + "   gen2: " + fiber_resume(gen2, 0))
  let j = j + 1
end

Interpreted run on the build machine (pat --ir-run โ€” compiled-native fiber support is a natural follow-up, not yet built):

0
1
1
2
3
5
8
13
21
34
55
89
generator still alive: true
--- a second, independent generator, interleaved ---
gen1: 144   gen2: 0
gen1: 233   gen2: 1
gen1: 377   gen2: 1
gen1: 610   gen2: 2
gen1: 987   gen2: 3

No real "run in browser" button, for the same reason as the threading demo above: fiber_new spawns a real OS thread, unsupported in this WASM sandbox. The step-through below is a purely illustrative, non-functional mock โ€” the sequence shown is the real transcript's own numbers, stepped through one at a time by a plain JS button, not a live fiber.

Simulated step-through (not live โ€” see note above):