@@ -23,16 +23,15 @@ type state struct {
2323 // is needed to be able to scan the stack.
2424 stackTop uintptr
2525
26+ // Lowest address of the stack.
27+ // This is populated when the thread is stopped by the GC.
28+ stackBottom uintptr
29+
2630 // Next task in the activeTasks queue.
2731 QueueNext * Task
2832
2933 // Semaphore to pause/resume the thread atomically.
3034 pauseSem Semaphore
31-
32- // Semaphore used for stack scanning.
33- // We can't reuse pauseSem here since the thread might have been paused for
34- // other reasons (for example, because it was waiting on a channel).
35- gcSem Semaphore
3635}
3736
3837// Goroutine counter, starting at 0 for the main goroutine.
@@ -96,6 +95,9 @@ func (t *Task) Resume() {
9695 t .state .pauseSem .Post ()
9796}
9897
98+ // otherGoroutines is the total number of live goroutines minus one.
99+ var otherGoroutines uint32
100+
99101// Start a new OS thread.
100102func start (fn uintptr , args unsafe.Pointer , stackSize uintptr ) {
101103 t := & Task {}
@@ -115,6 +117,7 @@ func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
115117 }
116118 t .state .QueueNext = activeTasks
117119 activeTasks = t
120+ otherGoroutines ++
118121 activeTaskLock .Unlock ()
119122}
120123
@@ -135,6 +138,7 @@ func taskExited(t *Task) {
135138 break
136139 }
137140 }
141+ otherGoroutines --
138142 activeTaskLock .Unlock ()
139143
140144 // Sanity check.
@@ -143,9 +147,42 @@ func taskExited(t *Task) {
143147 }
144148}
145149
146- // Futex to wait on until all tasks have finished scanning the stack.
147- // This is basically a sync.WaitGroup.
148- var scanDoneFutex Futex
150+ // scanWaitGroup is used to wait on until all threads have finished the current state transition.
151+ var scanWaitGroup waitGroup
152+
153+ type waitGroup struct {
154+ f Futex
155+ }
156+
157+ func initWaitGroup (n uint32 ) waitGroup {
158+ var wg waitGroup
159+ wg .f .Store (n )
160+ return wg
161+ }
162+
163+ func (wg * waitGroup ) done () {
164+ if wg .f .Add (^ uint32 (0 )) == 0 {
165+ wg .f .WakeAll ()
166+ }
167+ }
168+
169+ func (wg * waitGroup ) wait () {
170+ for {
171+ val := wg .f .Load ()
172+ if val == 0 {
173+ return
174+ }
175+ wg .f .Wait (val )
176+ }
177+ }
178+
179+ // gcState is used to track and notify threads when the GC is stopping/resuming.
180+ var gcState Futex
181+
182+ const (
183+ gcStateResumed = iota
184+ gcStateStopped
185+ )
149186
150187// GC scan phase. Because we need to stop the world while scanning, this kinda
151188// needs to be done in the tasks package.
@@ -155,100 +192,99 @@ var scanDoneFutex Futex
155192func GCStopWorldAndScan () {
156193 current := Current ()
157194
158- // Don't allow new goroutines to be started while pausing/resuming threads
159- // in the stop-the-world phase.
160- activeTaskLock .Lock ()
195+ // NOTE: This does not need to be atomic.
196+ if gcState .Load () == gcStateResumed {
197+ // Don't allow new goroutines to be started while pausing/resuming threads
198+ // in the stop-the-world phase.
199+ activeTaskLock .Lock ()
161200
162- // Pause all other threads.
163- numOtherThreads := uint32 (0 )
164- for t := activeTasks ; t != nil ; t = t .state .QueueNext {
165- if t != current {
166- numOtherThreads ++
167- tinygo_task_send_gc_signal (t .state .thread )
168- }
169- }
201+ // Wait for threads to finish resuming.
202+ scanWaitGroup .wait ()
170203
171- // Store the number of threads to wait for in the futex .
172- // This is the equivalent of doing an initial wg.Add(numOtherThreads) .
173- scanDoneFutex .Store (numOtherThreads )
204+ // Change the gc state to stopped .
205+ // NOTE: This does not need to be atomic .
206+ gcState .Store (gcStateStopped )
174207
175- // Scan the current stack, and all current registers .
176- scanCurrentStack ( )
208+ // Set the number of threads to wait for .
209+ scanWaitGroup = initWaitGroup ( otherGoroutines )
177210
178- // Wake each paused thread for the first time so it will scan the stack.
179- for t := activeTasks ; t != nil ; t = t .state .QueueNext {
180- if t != current {
181- t .state .gcSem .Post ()
211+ // Pause all other threads.
212+ for t := activeTasks ; t != nil ; t = t .state .QueueNext {
213+ if t != current {
214+ tinygo_task_send_gc_signal (t .state .thread )
215+ }
182216 }
217+
218+ // Wait for the threads to finish stopping.
219+ scanWaitGroup .wait ()
183220 }
184221
185- // Wait until all threads have finished scanning their stack.
186- // This is the equivalent of wg.Wait()
187- for {
188- val := scanDoneFutex .Load ()
189- if val == 0 {
190- break
222+ // Scan other thread stacks.
223+ for t := activeTasks ; t != nil ; t = t .state .QueueNext {
224+ if t != current {
225+ markRoots (t .state .stackBottom , t .state .stackTop )
191226 }
192- scanDoneFutex .Wait (val )
193227 }
194228
229+ // Scan the current stack, and all current registers.
230+ scanCurrentStack ()
231+
195232 // Scan all globals (implemented in the runtime).
196233 gcScanGlobals ()
197234}
198235
199236// After the GC is done scanning, resume all other threads.
200- //
201- // This must only be called after a GCStopWorldAndScan call.
202237func GCResumeWorld () {
203- current := Current ()
204-
205- // Wake each paused thread for the second time, so they will resume normal
206- // operation.
207- for t := activeTasks ; t != nil ; t = t .state .QueueNext {
208- if t != current {
209- t .state .gcSem .Post ()
210- }
238+ // NOTE: This does not need to be atomic.
239+ if gcState .Load () == gcStateResumed {
240+ // This is already resumed.
241+ return
211242 }
212243
244+ // Set the wait group to track resume progress.
245+ scanWaitGroup = initWaitGroup (otherGoroutines )
246+
247+ // Set the state to resumed.
248+ gcState .Store (gcStateResumed )
249+
250+ // Wake all of the stopped threads.
251+ gcState .WakeAll ()
252+
213253 // Allow goroutines to start and exit again.
214254 activeTaskLock .Unlock ()
215255}
216256
257+ //go:linkname markRoots runtime.markRoots
258+ func markRoots (start , end uintptr )
259+
217260// Scan globals, implemented in the runtime package.
218261func gcScanGlobals ()
219262
220263var stackScanLock PMutex
221264
222265//export tinygo_task_gc_pause
223266func tingyo_task_gc_pause (sig int32 ) {
224- // Wait until we get the signal to start scanning the stack.
225- Current ().state .gcSem .Wait ()
226-
227- // Scan the thread stack.
228- // Only scan a single thread stack at a time, because the GC marking phase
229- // doesn't support parallelism.
230- // TODO: it may be possible to call markRoots directly (without saving
231- // registers) since we are in a signal handler that already saved a bunch of
232- // registers. This is an optimization left for a future time.
233- stackScanLock .Lock ()
234- scanCurrentStack ()
235- stackScanLock .Unlock ()
267+ // Write the entrty stack pointer to the state.
268+ Current ().state .stackBottom = uintptr (stacksave ())
269+
270+ // Notify the GC that we are stopped.
271+ scanWaitGroup .done ()
236272
237- // Equivalent of wg.Done(): subtract one from the futex and if the result is
238- // 0 (meaning we were the last in the waitgroup), wake the waiting thread.
239- n := uint32 (1 )
240- if scanDoneFutex .Add (- n ) == 0 {
241- scanDoneFutex .Wake ()
273+ // Wait for the GC to resume.
274+ for gcState .Load () == gcStateStopped {
275+ gcState .Wait (gcStateStopped )
242276 }
243277
244- // Wait until we get the signal we can resume normally (after the mark phase
245- // has finished).
246- Current ().state .gcSem .Wait ()
278+ // Notify the GC that we have resumed.
279+ scanWaitGroup .done ()
247280}
248281
249282//go:export tinygo_scanCurrentStack
250283func scanCurrentStack ()
251284
285+ //go:linkname stacksave runtime.stacksave
286+ func stacksave () unsafe.Pointer
287+
252288// Return the highest address of the current stack.
253289func StackTop () uintptr {
254290 return Current ().state .stackTop
0 commit comments