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,124 @@ 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+ //export tinygo_signal_enable
357+ func tinygo_signal_enable (s uint32 )
358+
359+ // void tinygo_signal_handler(int sig);
360+ //
361+ //export tinygo_signal_handler
362+ func tinygo_signal_handler (s int32 ) {
363+ // This loop is essentially the atomic equivalent of the following:
364+ //
365+ // receivedSignals |= 1 << s
366+ //
367+ // TODO: use atomic.Uint32.And once we drop support for Go 1.22 instead of
368+ // this loop.
369+ for {
370+ mask := uint32 (1 ) << uint32 (s )
371+ val := atomic .LoadUint32 (& receivedSignals )
372+ swapped := atomic .CompareAndSwapUint32 (& receivedSignals , val , val | mask )
373+ if swapped {
374+ break
375+ }
376+ }
377+ }
378+
379+ //go:linkname signal_recv os/signal.signal_recv
380+ func signal_recv () uint32 {
381+ // Function called from os/signal to get the next received signal.
382+ val := <- signalChan
383+ checkSignals ()
384+ return val
385+ }
386+
387+ // Atomically find a signal that previously occured and send it into the
388+ // signalChan channel. Return true if at least one signal was delivered this
389+ // way, false otherwise.
390+ func checkSignals () bool {
391+ gotSignals := false
392+ for {
393+ // Extract the lowest numbered signal number from receivedSignals.
394+ val := atomic .LoadUint32 (& receivedSignals )
395+ if val == 0 {
396+ // There is no signal ready to be received by the program (common
397+ // case).
398+ return gotSignals
399+ }
400+ num := uint32 (bits .TrailingZeros32 (val ))
401+
402+ // Do a non-blocking send on signalChan.
403+ select {
404+ case signalChan <- num :
405+ // There was room free in the channel, so remove the signal number
406+ // from the receivedSignals mask.
407+ gotSignals = true
408+ default :
409+ // Could not send the signal number on the channel. This means
410+ // there's still a signal pending. In that case, let it be received
411+ // at which point checkSignals is called again to put the next one
412+ // in the channel buffer.
413+ return gotSignals
414+ }
415+
416+ // Atomically clear the signal number from receivedSignals.
417+ // TODO: use atomic.Uint32.Or once we drop support for Go 1.22 instead
418+ // of this loop.
419+ for {
420+ newVal := val &^ (1 << num )
421+ swapped := atomic .CompareAndSwapUint32 (& receivedSignals , val , newVal )
422+ if swapped {
423+ break
424+ }
425+ val = atomic .LoadUint32 (& receivedSignals )
426+ }
427+ }
428+ }
429+
430+ func waitForEvents () {
431+ if atomic .LoadUint32 (& hasSignals ) != 0 {
432+ // TODO: there is a race condition here. If a signal arrives between
433+ // checkSignals() and pause(), pause() will not exit early but instead
434+ // be delayed until the next signal arrives.
435+ // We should use something like this instead to avoid it:
436+ // - mask all active signals
437+ // - run checkSignals()
438+ // - run sigsuspend() with all active signals
439+ // - unmask all active signals
440+ // For a longer explanation of the problem, see:
441+ // https://www.cipht.net/2023/11/30/perils-of-pause.html
442+ checkSignals ()
443+ pause ()
444+ checkSignals ()
445+ } else {
446+ // The program doesn't use signals, so this is a deadlock.
447+ runtimePanic ("deadlocked: no event source" )
448+ }
449+ }
0 commit comments