From d49e3db21d0c237334ffe58d5a8c43e0e3e8f66b Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 2 Aug 2024 17:10:52 +0200 Subject: [PATCH] runtime: add support for os/signal This adds support for enabling and listening to signals on Linux and MacOS. TODO: also support disabling signals. --- builder/musl.go | 1 + compileopts/target.go | 2 ++ lib/macos-minimal-sdk | 2 +- main_test.go | 9 +++++++ src/os/signal/signal.go | 14 ----------- src/runtime/runtime_unix.go | 38 ++++++++++++++++++++++++++++++ src/runtime/signal.c | 19 +++++++++++++++ src/syscall/syscall_libc_darwin.go | 2 ++ testdata/signal/out.txt | 2 ++ testdata/signal/signal.go | 34 ++++++++++++++++++++++++++ 10 files changed, 108 insertions(+), 15 deletions(-) delete mode 100644 src/os/signal/signal.go create mode 100644 src/runtime/signal.c create mode 100644 testdata/signal/out.txt create mode 100644 testdata/signal/signal.go diff --git a/builder/musl.go b/builder/musl.go index 9b5c52704e..375ea99209 100644 --- a/builder/musl.go +++ b/builder/musl.go @@ -124,6 +124,7 @@ var Musl = Library{ "malloc/mallocng/*.c", "mman/*.c", "math/*.c", + "signal/" + arch + "/*.s", "signal/*.c", "stdio/*.c", "string/*.c", diff --git a/compileopts/target.go b/compileopts/target.go index fdb29e2109..cc56d1f343 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -356,6 +356,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { "-arch", arch, "-platform_version", "macos", platformVersion, platformVersion, ) + spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/signal.c") } else if goos == "linux" { spec.Linker = "ld.lld" spec.RTLib = "compiler-rt" @@ -375,6 +376,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { // proper threading. spec.CFlags = append(spec.CFlags, "-mno-outline-atomics") } + spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/signal.c") } else if goos == "windows" { spec.Linker = "ld.lld" spec.Libc = "mingw-w64" diff --git a/lib/macos-minimal-sdk b/lib/macos-minimal-sdk index ebb736fda2..5a77db2d29 160000 --- a/lib/macos-minimal-sdk +++ b/lib/macos-minimal-sdk @@ -1 +1 @@ -Subproject commit ebb736fda2bec7cea38dcda807518b835a539525 +Subproject commit 5a77db2d29b16b26a2a66f9386358c19e2afad6d diff --git a/main_test.go b/main_test.go index 057e008e87..33c7e01780 100644 --- a/main_test.go +++ b/main_test.go @@ -76,6 +76,7 @@ func TestBuild(t *testing.T) { "oldgo/", "print.go", "reflect.go", + "signal/", "slice.go", "sort.go", "stdlib.go", @@ -202,6 +203,7 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { // isWebAssembly := strings.HasPrefix(spec.Triple, "wasm") isWASI := strings.HasPrefix(options.Target, "wasi") isWebAssembly := isWASI || strings.HasPrefix(options.Target, "wasm") || (options.Target == "" && strings.HasPrefix(options.GOARCH, "wasm")) + isBaremetal := options.Target == "simavr" || options.Target == "cortex-m-qemu" || options.Target == "riscv-qemu" for _, name := range tests { if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") { @@ -254,6 +256,13 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { continue } } + if isWebAssembly || isBaremetal || options.GOOS == "windows" { + switch name { + case "signal/": + // Signals only work on POSIX-like systems. + continue + } + } name := name // redefine to avoid race condition t.Run(name, func(t *testing.T) { diff --git a/src/os/signal/signal.go b/src/os/signal/signal.go deleted file mode 100644 index 41ceaf4853..0000000000 --- a/src/os/signal/signal.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package signal - -import ( - "os" -) - -// Just stubbing the functions for now since signal handling is not yet implemented in tinygo -func Reset(sig ...os.Signal) {} -func Ignore(sig ...os.Signal) {} -func Notify(c chan<- os.Signal, sig ...os.Signal) {} diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index 8c5a42ff7c..e40f0e3a9b 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -256,3 +256,41 @@ func growHeap() bool { setHeapEnd(heapStart + heapSize) return true } + +func init() { + // Set up a channel to receive signals into. + // A channel size of 1 should be sufficient in most cases, but using 4 just + // to be sure. + signalChan = make(chan uint32, 4) +} + +var signalChan chan uint32 + +//go:linkname signal_enable os/signal.signal_enable +func signal_enable(s uint32) { + // It's easier to implement this function in C. + tinygo_signal_enable(s) +} + +//export tinygo_signal_enable +func tinygo_signal_enable(s uint32) + +// void tinygo_signal_handler(int sig); +// +//export tinygo_signal_handler +func tinygo_signal_handler(s int32) { + select { + case signalChan <- uint32(s): + default: + // TODO: we should handle this in a better way somehow. + // Maybe just ignore the signal, assuming that nothing is reading from + // the notify channel? + runtimePanic("could not deliver signal: runtime internal channel is full") + } +} + +//go:linkname signal_recv os/signal.signal_recv +func signal_recv() uint32 { + // Function called from os/signal to get the next received signal. + return <-signalChan +} diff --git a/src/runtime/signal.c b/src/runtime/signal.c new file mode 100644 index 0000000000..ffa30543da --- /dev/null +++ b/src/runtime/signal.c @@ -0,0 +1,19 @@ +//go:build none + +// Ignore the //go:build above. This file is manually included on Linux and +// MacOS to provide os/signal support. + +#include +#include +#include + +// Signal handler in the runtime. +void tinygo_signal_handler(int sig); + +// Enable a signal from the runtime. +void tinygo_signal_enable(uint32_t sig) { + struct sigaction act = { 0 }; + act.sa_flags = SA_SIGINFO; + act.sa_handler = &tinygo_signal_handler; + sigaction(sig, &act, NULL); +} diff --git a/src/syscall/syscall_libc_darwin.go b/src/syscall/syscall_libc_darwin.go index d64f1061f3..bb9b3661b6 100644 --- a/src/syscall/syscall_libc_darwin.go +++ b/src/syscall/syscall_libc_darwin.go @@ -93,6 +93,8 @@ const ( SIGPIPE Signal = 13 /* write on a pipe with no one to read it */ SIGTERM Signal = 15 /* software termination signal from kill */ SIGCHLD Signal = 20 /* to parent on child stop or exit */ + SIGUSR1 Signal = 30 /* user defined signal 1 */ + SIGUSR2 Signal = 31 /* user defined signal 2 */ ) func (s Signal) Signal() {} diff --git a/testdata/signal/out.txt b/testdata/signal/out.txt new file mode 100644 index 0000000000..c4726d7174 --- /dev/null +++ b/testdata/signal/out.txt @@ -0,0 +1,2 @@ +got expected signal +exiting signal program diff --git a/testdata/signal/signal.go b/testdata/signal/signal.go new file mode 100644 index 0000000000..d465715303 --- /dev/null +++ b/testdata/signal/signal.go @@ -0,0 +1,34 @@ +package main + +// Test POSIX signals. +// TODO: run `tinygo test os/signal` instead, once CGo errno return values are +// supported. + +import ( + "os" + "os/signal" + "syscall" + "time" +) + +func main() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGUSR1) + + // Wait for signals to arrive. + go func() { + for sig := range c { + if sig == syscall.SIGUSR1 { + println("got expected signal") + } else { + println("got signal:", sig.String()) + } + } + }() + + // Send the signal. + syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) + + time.Sleep(time.Millisecond * 100) + println("exiting signal program") +}