| 1 | // Copyright (c) 2026 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | // |
| 5 | // Go-style goroutine runtime for V. |
| 6 | // Implements the GMP (Goroutine-Machine-Processor) scheduling model |
| 7 | // translated from the Go runtime (src/runtime/proc.go, runtime2.go). |
| 8 | // |
| 9 | // Goroutine - goroutine: lightweight unit of execution with its own stack |
| 10 | // Machine - machine: OS thread that executes goroutines |
| 11 | // Processor - processor: logical processor that manages a local run queue |
| 12 | // |
| 13 | // Design doc: https://golang.org/s/go11sched |
| 14 | @[has_globals] |
| 15 | module goroutines |
| 16 | |
| 17 | import sync |
| 18 | import runtime as _ |
| 19 | |
| 20 | // GoFn is the type for a goroutine function: takes a voidptr argument. |
| 21 | pub type GoFn = fn (voidptr) |
| 22 | |
| 23 | // Default goroutine stack size. |
| 24 | // Go uses 8KB with growable stacks; V/C needs more since stacks are fixed. |
| 25 | const default_stack_size = 256 * 1024 |
| 26 | |
| 27 | // Maximum number of goroutines in a Processor's local run queue (matches Go's 256) |
| 28 | const local_queue_size = 256 |
| 29 | |
| 30 | // How often to check the global queue for fairness (matches Go's 61) |
| 31 | const global_queue_check_interval = 61 |
| 32 | |
| 33 | // GStatus represents goroutine states (matches Go's runtime2.go) |
| 34 | pub enum GStatus { |
| 35 | idle // just allocated, not yet initialized |
| 36 | runnable // on a run queue, not currently executing |
| 37 | running // executing user code on an Machine |
| 38 | waiting // blocked (channel, mutex, etc.) |
| 39 | dead // finished execution, available for reuse |
| 40 | copystack // stack is being moved (not used yet) |
| 41 | } |
| 42 | |
| 43 | // PStatus represents processor states |
| 44 | pub enum PStatus { |
| 45 | idle // not being used, available on idle list |
| 46 | running // owned by an Machine and executing code |
| 47 | stopped // halted |
| 48 | dead // no longer used |
| 49 | } |
| 50 | |
| 51 | // Goroutine represents a goroutine - the fundamental unit of concurrent execution. |
| 52 | // Translated from Go's `type g struct` in runtime2.go. |
| 53 | pub struct Goroutine { |
| 54 | pub mut: |
| 55 | id u64 // unique goroutine id |
| 56 | status GStatus = .idle // current state |
| 57 | stack voidptr // stack memory (allocated) |
| 58 | stack_size int = default_stack_size // stack allocation size |
| 59 | context Context // saved CPU context for switching (ucontext_t or similar) |
| 60 | fn_ptr voidptr // function to execute |
| 61 | fn_arg voidptr // argument to the function |
| 62 | sched_link &Goroutine = unsafe { nil } // linked list link for run queues |
| 63 | m &Machine = unsafe { nil } // current Machine executing this Goroutine (nil if not running) |
| 64 | parent_id u64 // goroutine id of creator |
| 65 | wait_reason string // if status==waiting, why |
| 66 | preempt bool // preemption signal |
| 67 | } |
| 68 | |
| 69 | // Machine represents a machine (OS thread) that executes goroutines. |
| 70 | // Translated from Go's `type m struct` in runtime2.go. |
| 71 | pub struct Machine { |
| 72 | pub mut: |
| 73 | id i64 |
| 74 | g0 &Goroutine = unsafe { nil } // goroutine with scheduling stack |
| 75 | curg &Goroutine = unsafe { nil } // current running goroutine |
| 76 | p &Processor = unsafe { nil } // attached processor (nil if not executing Go code) |
| 77 | spinning bool // looking for work |
| 78 | blocked bool // blocked on a note |
| 79 | park sync.Semaphore // for parking/unparking |
| 80 | sched_link &Machine = unsafe { nil } // linked list for idle Machine list |
| 81 | thread thread // underlying OS thread handle |
| 82 | } |
| 83 | |
| 84 | // Processor represents a processor - a resource required to execute goroutines. |
| 85 | // Each Processor has a local run queue. Translated from Go's `type p struct` in runtime2.go. |
| 86 | pub struct Processor { |
| 87 | pub mut: |
| 88 | id i32 |
| 89 | status PStatus = .idle |
| 90 | m &Machine = unsafe { nil } // back-link to associated Machine |
| 91 | sched_tick u32 // incremented on every scheduler call |
| 92 | |
| 93 | // Local run queue - lock-free SPMC ring buffer (matches Go's design) |
| 94 | runq_head u32 // consumer index (atomic) |
| 95 | runq_tail u32 // producer index (atomic) |
| 96 | runq [local_queue_size]&Goroutine // circular buffer |
| 97 | runnext &Goroutine = unsafe { nil } // next Goroutine to run (fast path, like Go's runnext) |
| 98 | |
| 99 | // Free Goroutine list for reuse |
| 100 | g_free GoroutineList |
| 101 | |
| 102 | // ID cache to avoid contention on the global counter |
| 103 | goid_cache u64 |
| 104 | goid_cache_end u64 |
| 105 | |
| 106 | link &Processor = unsafe { nil } // linked list for idle Processor list |
| 107 | } |
| 108 | |
| 109 | // Sched is the global scheduler state. |
| 110 | // Translated from Go's `type schedt struct` in runtime2.go. |
| 111 | pub struct Sched { |
| 112 | pub mut: |
| 113 | goid_gen u64 // global goroutine ID generator (atomic) |
| 114 | |
| 115 | mu SpinLock // protects idle lists, global queue (spinlock is ucontext-safe) |
| 116 | |
| 117 | // Idle Machine's waiting for work |
| 118 | midle &Machine = unsafe { nil } |
| 119 | nmidle i32 |
| 120 | |
| 121 | // Idle Processor's |
| 122 | pidle &Processor = unsafe { nil } |
| 123 | npidle i32 |
| 124 | |
| 125 | // Global run queue |
| 126 | runq GoroutineQueue |
| 127 | nmspinning i32 // number of spinning Machine's (atomic) |
| 128 | |
| 129 | // All Processor's (indexed by id) |
| 130 | allp []&Processor |
| 131 | |
| 132 | // Total Machine count |
| 133 | mnext i64 |
| 134 | maxmcount i32 = 10000 |
| 135 | |
| 136 | // Global Goroutine free list |
| 137 | g_free_mu SpinLock |
| 138 | g_free GoroutineList |
| 139 | g_free_count i32 |
| 140 | |
| 141 | // Shutdown |
| 142 | stopped bool |
| 143 | } |
| 144 | |
| 145 | // GoroutineQueue is a simple linked-list queue of Goroutine's (matches Go's gQueue). |
| 146 | pub struct GoroutineQueue { |
| 147 | pub mut: |
| 148 | head &Goroutine = unsafe { nil } |
| 149 | tail &Goroutine = unsafe { nil } |
| 150 | size i32 |
| 151 | } |
| 152 | |
| 153 | // GoroutineList is a list of Goroutine's. |
| 154 | pub struct GoroutineList { |
| 155 | pub mut: |
| 156 | head &Goroutine = unsafe { nil } |
| 157 | count i32 |
| 158 | } |
| 159 | |
| 160 | fn (mut q GoroutineQueue) push_back(gp &Goroutine) { |
| 161 | mut g := unsafe { gp } |
| 162 | g.sched_link = unsafe { nil } |
| 163 | if q.tail != unsafe { nil } { |
| 164 | q.tail.sched_link = g |
| 165 | } else { |
| 166 | q.head = g |
| 167 | } |
| 168 | q.tail = g |
| 169 | q.size++ |
| 170 | } |
| 171 | |
| 172 | fn (mut q GoroutineQueue) push(gp &Goroutine) { |
| 173 | mut g := unsafe { gp } |
| 174 | g.sched_link = q.head |
| 175 | q.head = g |
| 176 | if q.tail == unsafe { nil } { |
| 177 | q.tail = g |
| 178 | } |
| 179 | q.size++ |
| 180 | } |
| 181 | |
| 182 | fn (mut q GoroutineQueue) pop() &Goroutine { |
| 183 | if q.head == unsafe { nil } { |
| 184 | return unsafe { nil } |
| 185 | } |
| 186 | gp := q.head |
| 187 | q.head = gp.sched_link |
| 188 | if q.head == unsafe { nil } { |
| 189 | q.tail = unsafe { nil } |
| 190 | } |
| 191 | q.size-- |
| 192 | return gp |
| 193 | } |
| 194 | |
| 195 | fn (q &GoroutineQueue) empty() bool { |
| 196 | return q.head == unsafe { nil } |
| 197 | } |
| 198 | |
| 199 | fn (mut l GoroutineList) push(gp &Goroutine) { |
| 200 | mut g := unsafe { gp } |
| 201 | g.sched_link = l.head |
| 202 | l.head = g |
| 203 | l.count++ |
| 204 | } |
| 205 | |
| 206 | fn (mut l GoroutineList) pop() &Goroutine { |
| 207 | if l.head == unsafe { nil } { |
| 208 | return unsafe { nil } |
| 209 | } |
| 210 | gp := l.head |
| 211 | l.head = gp.sched_link |
| 212 | l.count-- |
| 213 | return gp |
| 214 | } |
| 215 | |
| 216 | fn (l &GoroutineList) empty() bool { |
| 217 | return l.head == unsafe { nil } |
| 218 | } |
| 219 | |
| 220 | // Global scheduler instance |
| 221 | __global gsched = Sched{} |
| 222 | |
| 223 | // Number of processors (defaults to number of CPU cores) |
| 224 | __global gomaxprocs = i32(0) |
| 225 | |
| 226 | // All goroutines ever created (for debugging) |
| 227 | __global allgs_mu = SpinLock{} |
| 228 | __global allgs = []&Goroutine{} |
| 229 | |