33package runtime
44
55import (
6+ "math/bits"
7+ "sync/atomic"
68 "unsafe"
79)
810
@@ -12,6 +14,9 @@ func libc_write(fd int32, buf unsafe.Pointer, count uint) int
1214//export usleep
1315func usleep (usec uint ) int
1416
17+ //export pause
18+ func pause () int32
19+
1520// void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
1621// Note: off_t is defined as int64 because:
1722// - musl (used on Linux) always defines it as int64
@@ -217,8 +222,47 @@ func nanosecondsToTicks(ns int64) timeUnit {
217222}
218223
219224func sleepTicks (d timeUnit ) {
220- // timeUnit is in nanoseconds, so need to convert to microseconds here.
221- usleep (uint (d ) / 1000 )
225+ // When there are no signal handlers present, we can simply go to sleep.
226+ if ! hasSignals {
227+ // timeUnit is in nanoseconds, so need to convert to microseconds here.
228+ usleep (uint (d ) / 1000 )
229+ return
230+ }
231+
232+ if GOOS == "darwin" {
233+ // Check for incoming signals.
234+ if checkSignals () {
235+ // Received a signal, so there's probably at least one goroutine
236+ // that's runnable again.
237+ return
238+ }
239+
240+ // WARNING: there is a race condition here. If a signal arrives between
241+ // checkSignals() and usleep(), the usleep() call will not exit early so
242+ // the signal is delayed until usleep finishes or another signal
243+ // arrives.
244+ // There doesn't appear to be a simple way to fix this on MacOS.
245+
246+ // timeUnit is in nanoseconds, so need to convert to microseconds here.
247+ result := usleep (uint (d ) / 1000 )
248+ if result != 0 {
249+ checkSignals ()
250+ }
251+ } else {
252+ // Linux (and various other POSIX systems) implement sigtimedwait so we
253+ // can do this in a non-racy way.
254+ tinygo_wfi_mask (activeSignals )
255+ if checkSignals () {
256+ tinygo_wfi_unmask ()
257+ return
258+ }
259+ signal := tinygo_wfi_sleep (activeSignals , uint64 (d ))
260+ if signal >= 0 {
261+ tinygo_signal_handler (signal )
262+ checkSignals ()
263+ }
264+ tinygo_wfi_unmask ()
265+ }
222266}
223267
224268func getTime (clock int32 ) uint64 {
@@ -307,3 +351,183 @@ func growHeap() bool {
307351 setHeapEnd (heapStart + heapSize )
308352 return true
309353}
354+
355+ func init () {
356+ // Set up a channel to receive signals into.
357+ signalChan = make (chan uint32 , 1 )
358+ }
359+
360+ var signalChan chan uint32
361+
362+ // Indicate whether signals have been registered.
363+ var hasSignals bool
364+
365+ // Mask of signals that have been received. The signal handler atomically ORs
366+ // signals into this value.
367+ var receivedSignals uint32
368+
369+ var activeSignals uint32
370+
371+ //go:linkname signal_enable os/signal.signal_enable
372+ func signal_enable (s uint32 ) {
373+ if s >= 32 {
374+ // TODO: to support higher signal numbers, we need to turn
375+ // receivedSignals into a uint32 array.
376+ runtimePanicAt (returnAddress (0 ), "unsupported signal number" )
377+ }
378+ hasSignals = true
379+ activeSignals |= 1 << s
380+ // It's easier to implement this function in C.
381+ tinygo_signal_enable (s )
382+ }
383+
384+ //go:linkname signal_ignore os/signal.signal_ignore
385+ func signal_ignore (s uint32 ) {
386+ if s >= 32 {
387+ // TODO: to support higher signal numbers, we need to turn
388+ // receivedSignals into a uint32 array.
389+ runtimePanicAt (returnAddress (0 ), "unsupported signal number" )
390+ }
391+ activeSignals &^= 1 << s
392+ tinygo_signal_ignore (s )
393+ }
394+
395+ //go:linkname signal_disable os/signal.signal_disable
396+ func signal_disable (s uint32 ) {
397+ if s >= 32 {
398+ // TODO: to support higher signal numbers, we need to turn
399+ // receivedSignals into a uint32 array.
400+ runtimePanicAt (returnAddress (0 ), "unsupported signal number" )
401+ }
402+ activeSignals &^= 1 << s
403+ tinygo_signal_disable (s )
404+ }
405+
406+ //go:linkname signal_waitUntilIdle os/signal.signalWaitUntilIdle
407+ func signal_waitUntilIdle () {
408+ // Make sure all signals are sent on the channel.
409+ for atomic .LoadUint32 (& receivedSignals ) != 0 {
410+ checkSignals ()
411+ Gosched ()
412+ }
413+
414+ // Make sure all signals are processed.
415+ for len (signalChan ) != 0 {
416+ Gosched ()
417+ }
418+ }
419+
420+ //export tinygo_signal_enable
421+ func tinygo_signal_enable (s uint32 )
422+
423+ //export tinygo_signal_ignore
424+ func tinygo_signal_ignore (s uint32 )
425+
426+ //export tinygo_signal_disable
427+ func tinygo_signal_disable (s uint32 )
428+
429+ // void tinygo_signal_handler(int sig);
430+ //
431+ //export tinygo_signal_handler
432+ func tinygo_signal_handler (s int32 ) {
433+ // This loop is essentially the atomic equivalent of the following:
434+ //
435+ // receivedSignals |= 1 << s
436+ //
437+ // TODO: use atomic.Uint32.And once we drop support for Go 1.22 instead of
438+ // this loop.
439+ for {
440+ mask := uint32 (1 ) << uint32 (s )
441+ val := atomic .LoadUint32 (& receivedSignals )
442+ swapped := atomic .CompareAndSwapUint32 (& receivedSignals , val , val | mask )
443+ if swapped {
444+ break
445+ }
446+ }
447+ }
448+
449+ //go:linkname signal_recv os/signal.signal_recv
450+ func signal_recv () uint32 {
451+ // Function called from os/signal to get the next received signal.
452+ val := <- signalChan
453+ checkSignals ()
454+ return val
455+ }
456+
457+ // Atomically find a signal that previously occured and send it into the
458+ // signalChan channel. Return true if at least one signal was delivered this
459+ // way, false otherwise.
460+ func checkSignals () bool {
461+ gotSignals := false
462+ for {
463+ // Extract the lowest numbered signal number from receivedSignals.
464+ val := atomic .LoadUint32 (& receivedSignals )
465+ if val == 0 {
466+ // There is no signal ready to be received by the program (common
467+ // case).
468+ return gotSignals
469+ }
470+ num := uint32 (bits .TrailingZeros32 (val ))
471+
472+ // Do a non-blocking send on signalChan.
473+ select {
474+ case signalChan <- num :
475+ // There was room free in the channel, so remove the signal number
476+ // from the receivedSignals mask.
477+ gotSignals = true
478+ default :
479+ // Could not send the signal number on the channel. This means
480+ // there's still a signal pending. In that case, let it be received
481+ // at which point checkSignals is called again to put the next one
482+ // in the channel buffer.
483+ return gotSignals
484+ }
485+
486+ // Atomically clear the signal number from receivedSignals.
487+ // TODO: use atomic.Uint32.Or once we drop support for Go 1.22 instead
488+ // of this loop.
489+ for {
490+ newVal := val &^ (1 << num )
491+ swapped := atomic .CompareAndSwapUint32 (& receivedSignals , val , newVal )
492+ if swapped {
493+ break
494+ }
495+ val = atomic .LoadUint32 (& receivedSignals )
496+ }
497+ }
498+ }
499+
500+ //export tinygo_wfi_mask
501+ func tinygo_wfi_mask (active uint32 )
502+
503+ //export tinygo_wfi_sleep
504+ func tinygo_wfi_sleep (active uint32 , timeout uint64 ) int32
505+
506+ //export tinygo_wfi_wait
507+ func tinygo_wfi_wait (active uint32 ) int32
508+
509+ //export tinygo_wfi_unmask
510+ func tinygo_wfi_unmask ()
511+
512+ func waitForEvents () {
513+ if hasSignals {
514+ // We could have used pause() here, but that function is impossible to
515+ // use in a race-free way:
516+ // https://www.cipht.net/2023/11/30/perils-of-pause.html
517+ // Therefore we need something better.
518+ // Note: this is unsafe with multithreading, because sigprocmask is only
519+ // defined for single-threaded applictions.
520+ tinygo_wfi_mask (activeSignals )
521+ if checkSignals () {
522+ tinygo_wfi_unmask ()
523+ return
524+ }
525+ signal := tinygo_wfi_wait (activeSignals )
526+ tinygo_signal_handler (signal )
527+ checkSignals ()
528+ tinygo_wfi_unmask ()
529+ } else {
530+ // The program doesn't use signals, so this is a deadlock.
531+ runtimePanic ("deadlocked: no event source" )
532+ }
533+ }
0 commit comments