1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! Cooperative yield: `yield_strand` (explicit) and `maybe_yield` (safety valve).
//!
//! ## Cooperative Yield Safety Valve
//!
//! Prevents tight TCO loops from starving other strands and making the process
//! unresponsive. When enabled via `SEQ_YIELD_INTERVAL`, yields after N tail calls.
//!
//! Configuration:
//! `SEQ_YIELD_INTERVAL=10000` - Yield every 10,000 tail calls (default: 0 = disabled)
//!
//! Scope:
//! - Covers: User-defined word tail calls (musttail) and quotation tail calls
//! - Does NOT cover: Closure calls (they use regular calls, bounded by stack)
//! - Does NOT cover: Non-tail recursive calls (bounded by stack)
//!
//! This is intentional: the safety valve targets unbounded TCO loops.
//!
//! Design:
//! - Zero overhead when disabled (threshold=0 short-circuits immediately)
//! - Thread-local counter avoids synchronization overhead
//! - Called before every musttail in generated code
//! - Threshold is cached on first access via OnceLock
//!
//! Thread-Local Counter Behavior:
//! The counter is per-OS-thread, not per-coroutine. Multiple coroutines on the
//! same OS thread share the counter, which may cause yields slightly more
//! frequently than the configured interval. This is intentional:
//! - Avoids coroutine-local storage overhead
//! - Still achieves the goal of preventing starvation
//! - Actual yield frequency is still bounded by the threshold
use crateStack;
use coroutine;
use Cell;
use OnceLock;
/// Cached yield interval threshold (0 = disabled)
static YIELD_THRESHOLD: = new;
thread_local!
/// Get the yield threshold from environment (cached)
///
/// Returns 0 (disabled) if SEQ_YIELD_INTERVAL is not set or invalid.
/// Prints a warning to stderr if the value is set but invalid.
/// Yield execution to allow other coroutines to run
///
/// # Safety
/// Always safe to call from within a May coroutine.
pub unsafe extern "C"
/// Maybe yield to other coroutines based on tail call count
///
/// Called before every tail call in generated code. When SEQ_YIELD_INTERVAL
/// is set, yields after that many tail calls to prevent starvation.
///
/// # Performance
/// - Disabled (default): Single branch on cached threshold (< 1ns)
/// - Enabled: Increment + compare + occasional yield (~10-20ns average)
///
/// # Safety
/// Always safe to call. No-op when not in a May coroutine context.
pub extern "C"