Skip to content

Add support for threading on MacOS #4720

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion compileopts/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
platformVersion = "11.0.0" // first macosx platform with arm64 support
}
llvmvendor = "apple"
spec.Scheduler = "tasks"
spec.Scheduler = "threads"
spec.Linker = "ld.lld"
spec.Libc = "darwin-libSystem"
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer to
Expand All @@ -399,6 +399,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
)
spec.ExtraFiles = append(spec.ExtraFiles,
"src/internal/futex/futex_darwin.c",
"src/internal/task/task_threads.c",
"src/runtime/os_darwin.c",
"src/runtime/runtime_unix.c",
"src/runtime/signal.c")
Expand Down
11 changes: 11 additions & 0 deletions src/internal/task/darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build darwin

package task

import "unsafe"

// MacOS uses a pointer so unsafe.Pointer should be fine:
//
// typedef struct _opaque_pthread_t *__darwin_pthread_t;
// typedef __darwin_pthread_t pthread_t;
type threadID unsafe.Pointer
37 changes: 32 additions & 5 deletions src/internal/task/task_threads.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,28 @@

#define _GNU_SOURCE
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>

// BDWGC also uses SIGRTMIN+6 on Linux, which seems like a reasonable choice.
#ifdef __linux__
#include <semaphore.h>

// BDWGC also uses SIGRTMIN+6 on Linux, which seems like a reasonable choice.
#define taskPauseSignal (SIGRTMIN + 6)
#endif

#elif __APPLE__
#include <dispatch/dispatch.h>
// SIGIO is for interrupt-driven I/O.
// I don't think anybody should be using this nowadays, so I think we can
// repurpose it as a signal for GC.
// BDWGC uses a special way to pause/resume other threads on MacOS, which may be
// better but needs more work. Using signal keeps the code similar between Linux
// and MacOS.
#define taskPauseSignal SIGIO

#endif // __linux__, __APPLE__

// Pointer to the current task.Task structure.
// Ideally the entire task.Task structure would be a thread-local variable but
Expand All @@ -23,7 +35,11 @@ struct state_pass {
void *args;
void *task;
uintptr_t *stackTop;
#if __APPLE__
dispatch_semaphore_t startlock;
#else
sem_t startlock;
#endif
};

// Handle the GC pause in Go.
Expand All @@ -41,8 +57,7 @@ void tinygo_task_init(void *mainTask, pthread_t *thread, int *numCPU, void *cont
// Register the "GC pause" signal for the entire process.
// Using pthread_kill, we can still send the signal to a specific thread.
struct sigaction act = { 0 };
act.sa_flags = SA_SIGINFO;
act.sa_handler = &tinygo_task_gc_pause;
act.sa_handler = tinygo_task_gc_pause;
sigaction(taskPauseSignal, &act, NULL);

// Obtain the number of CPUs available on program start (for NumCPU).
Expand All @@ -69,7 +84,11 @@ static void* start_wrapper(void *arg) {

// Notify the caller that the thread has successfully started and
// initialized.
#if __APPLE__
dispatch_semaphore_signal(state->startlock);
#else
sem_post(&state->startlock);
#endif

// Run the goroutine function.
start(args);
Expand All @@ -93,11 +112,19 @@ int tinygo_task_start(uintptr_t fn, void *args, void *task, pthread_t *thread, u
.task = task,
.stackTop = stackTop,
};
#if __APPLE__
state.startlock = dispatch_semaphore_create(0);
#else
sem_init(&state.startlock, 0, 0);
#endif
int result = pthread_create(thread, NULL, &start_wrapper, &state);

// Wait until the thread has been created and read all state_pass variables.
#if __APPLE__
dispatch_semaphore_wait(state.startlock, DISPATCH_TIME_FOREVER);
#else
sem_wait(&state.startlock);
#endif

return result;
}
Expand Down
2 changes: 1 addition & 1 deletion src/internal/task/task_threads.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func gcScanGlobals()
var stackScanLock PMutex

//export tinygo_task_gc_pause
func tingyo_task_gc_pause() {
func tingyo_task_gc_pause(sig int32) {
// Wait until we get the signal to start scanning the stack.
Current().state.gcSem.Wait()

Expand Down