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,22 @@ func nanosecondsToTicks(ns int64) timeUnit {
217222}
218223
219224func sleepTicks (d timeUnit ) {
225+ // Check for incoming signals.
226+ if checkSignals () {
227+ // Received a signal, so there's probably at least one goroutine that's
228+ // runnable again.
229+ return
230+ }
231+
232+ // TODO: there is a race condition here. If a signal arrives between
233+ // checkSignals() and usleep(), the usleep() call will not exit early so the
234+ // signal is delayed until usleep finishes or another signal arrives.
235+
220236 // timeUnit is in nanoseconds, so need to convert to microseconds here.
221- usleep (uint (d ) / 1000 )
237+ result := usleep (uint (d ) / 1000 )
238+ if result != 0 {
239+ checkSignals ()
240+ }
222241}
223242
224243func getTime (clock int32 ) uint64 {
@@ -307,3 +326,164 @@ func growHeap() bool {
307326 setHeapEnd (heapStart + heapSize )
308327 return true
309328}
329+
330+ func init () {
331+ // Set up a channel to receive signals into.
332+ signalChan = make (chan uint32 , 1 )
333+ }
334+
335+ var signalChan chan uint32
336+
337+ // Simple boolean that's true when any signals have been registered.
338+ var hasSignals uint32
339+
340+ // Mask of signals that have been received. The signal handler atomically ORs
341+ // signals into this value.
342+ var receivedSignals uint32
343+
344+ //go:linkname signal_enable os/signal.signal_enable
345+ func signal_enable (s uint32 ) {
346+ if s >= 32 {
347+ // TODO: to support higher signal numbers, we need to turn
348+ // receivedSignals into a uint32 array.
349+ runtimePanicAt (returnAddress (0 ), "unsupported signal number" )
350+ }
351+ atomic .StoreUint32 (& hasSignals , 1 )
352+ // It's easier to implement this function in C.
353+ tinygo_signal_enable (s )
354+ }
355+
356+ //go:linkname signal_ignore os/signal.signal_ignore
357+ func signal_ignore (s uint32 ) {
358+ if s >= 32 {
359+ // TODO: to support higher signal numbers, we need to turn
360+ // receivedSignals into a uint32 array.
361+ runtimePanicAt (returnAddress (0 ), "unsupported signal number" )
362+ }
363+ tinygo_signal_ignore (s )
364+ }
365+
366+ //go:linkname signal_disable os/signal.signal_disable
367+ func signal_disable (s uint32 ) {
368+ if s >= 32 {
369+ // TODO: to support higher signal numbers, we need to turn
370+ // receivedSignals into a uint32 array.
371+ runtimePanicAt (returnAddress (0 ), "unsupported signal number" )
372+ }
373+ tinygo_signal_disable (s )
374+ }
375+
376+ //go:linkname signal_waitUntilIdle os/signal.signalWaitUntilIdle
377+ func signal_waitUntilIdle () {
378+ // Make sure all signals are sent on the channel.
379+ for atomic .LoadUint32 (& receivedSignals ) != 0 {
380+ checkSignals ()
381+ Gosched ()
382+ }
383+
384+ // Make sure all signals are processed.
385+ for len (signalChan ) != 0 {
386+ Gosched ()
387+ }
388+ }
389+
390+ //export tinygo_signal_enable
391+ func tinygo_signal_enable (s uint32 )
392+
393+ //export tinygo_signal_ignore
394+ func tinygo_signal_ignore (s uint32 )
395+
396+ //export tinygo_signal_disable
397+ func tinygo_signal_disable (s uint32 )
398+
399+ // void tinygo_signal_handler(int sig);
400+ //
401+ //export tinygo_signal_handler
402+ func tinygo_signal_handler (s int32 ) {
403+ // This loop is essentially the atomic equivalent of the following:
404+ //
405+ // receivedSignals |= 1 << s
406+ //
407+ // TODO: use atomic.Uint32.And once we drop support for Go 1.22 instead of
408+ // this loop.
409+ for {
410+ mask := uint32 (1 ) << uint32 (s )
411+ val := atomic .LoadUint32 (& receivedSignals )
412+ swapped := atomic .CompareAndSwapUint32 (& receivedSignals , val , val | mask )
413+ if swapped {
414+ break
415+ }
416+ }
417+ }
418+
419+ //go:linkname signal_recv os/signal.signal_recv
420+ func signal_recv () uint32 {
421+ // Function called from os/signal to get the next received signal.
422+ val := <- signalChan
423+ checkSignals ()
424+ return val
425+ }
426+
427+ // Atomically find a signal that previously occured and send it into the
428+ // signalChan channel. Return true if at least one signal was delivered this
429+ // way, false otherwise.
430+ func checkSignals () bool {
431+ gotSignals := false
432+ for {
433+ // Extract the lowest numbered signal number from receivedSignals.
434+ val := atomic .LoadUint32 (& receivedSignals )
435+ if val == 0 {
436+ // There is no signal ready to be received by the program (common
437+ // case).
438+ return gotSignals
439+ }
440+ num := uint32 (bits .TrailingZeros32 (val ))
441+
442+ // Do a non-blocking send on signalChan.
443+ select {
444+ case signalChan <- num :
445+ // There was room free in the channel, so remove the signal number
446+ // from the receivedSignals mask.
447+ gotSignals = true
448+ default :
449+ // Could not send the signal number on the channel. This means
450+ // there's still a signal pending. In that case, let it be received
451+ // at which point checkSignals is called again to put the next one
452+ // in the channel buffer.
453+ return gotSignals
454+ }
455+
456+ // Atomically clear the signal number from receivedSignals.
457+ // TODO: use atomic.Uint32.Or once we drop support for Go 1.22 instead
458+ // of this loop.
459+ for {
460+ newVal := val &^ (1 << num )
461+ swapped := atomic .CompareAndSwapUint32 (& receivedSignals , val , newVal )
462+ if swapped {
463+ break
464+ }
465+ val = atomic .LoadUint32 (& receivedSignals )
466+ }
467+ }
468+ }
469+
470+ func waitForEvents () {
471+ if atomic .LoadUint32 (& hasSignals ) != 0 {
472+ // TODO: there is a race condition here. If a signal arrives between
473+ // checkSignals() and pause(), pause() will not exit early but instead
474+ // be delayed until the next signal arrives.
475+ // We should use something like this instead to avoid it:
476+ // - mask all active signals
477+ // - run checkSignals()
478+ // - run sigsuspend() with all active signals
479+ // - unmask all active signals
480+ // For a longer explanation of the problem, see:
481+ // https://www.cipht.net/2023/11/30/perils-of-pause.html
482+ checkSignals ()
483+ pause ()
484+ checkSignals ()
485+ } else {
486+ // The program doesn't use signals, so this is a deadlock.
487+ runtimePanic ("deadlocked: no event source" )
488+ }
489+ }
0 commit comments