| 
 | 1 | +//go:build none  | 
 | 2 | + | 
 | 3 | +#define _GNU_SOURCE  | 
 | 4 | +#include <pthread.h>  | 
 | 5 | +#include <semaphore.h>  | 
 | 6 | +#include <signal.h>  | 
 | 7 | +#include <stdint.h>  | 
 | 8 | +#include <stdio.h>  | 
 | 9 | + | 
 | 10 | +// BDWGC also uses SIGRTMIN+6 on Linux, which seems like a reasonable choice.  | 
 | 11 | +#ifdef __linux__  | 
 | 12 | +#define taskPauseSignal (SIGRTMIN + 6)  | 
 | 13 | +#endif  | 
 | 14 | + | 
 | 15 | +// Pointer to the current task.Task structure.  | 
 | 16 | +// Ideally the entire task.Task structure would be a thread-local variable but  | 
 | 17 | +// this also works.  | 
 | 18 | +static __thread void *current_task;  | 
 | 19 | + | 
 | 20 | +struct state_pass {  | 
 | 21 | +    void      *(*start)(void*);  | 
 | 22 | +    void      *args;  | 
 | 23 | +    void      *task;  | 
 | 24 | +    uintptr_t *stackTop;  | 
 | 25 | +    sem_t     startlock;  | 
 | 26 | +};  | 
 | 27 | + | 
 | 28 | +// Handle the GC pause in Go.  | 
 | 29 | +void tinygo_task_gc_pause(int sig);  | 
 | 30 | + | 
 | 31 | +// Initialize the main thread.  | 
 | 32 | +void tinygo_task_init(void *mainTask, pthread_t *thread, void *context) {  | 
 | 33 | +    // Make sure the current task pointer is set correctly for the main  | 
 | 34 | +    // goroutine as well.  | 
 | 35 | +    current_task = mainTask;  | 
 | 36 | + | 
 | 37 | +    // Store the thread ID of the main thread.  | 
 | 38 | +    *thread = pthread_self();  | 
 | 39 | + | 
 | 40 | +    // Register the "GC pause" signal for the entire process.  | 
 | 41 | +    // Using pthread_kill, we can still send the signal to a specific thread.  | 
 | 42 | +    struct sigaction act = { 0 };  | 
 | 43 | +    act.sa_flags = SA_SIGINFO;  | 
 | 44 | +    act.sa_handler = &tinygo_task_gc_pause;  | 
 | 45 | +    sigaction(taskPauseSignal, &act, NULL);  | 
 | 46 | +}  | 
 | 47 | + | 
 | 48 | +void tinygo_task_exited(void*);  | 
 | 49 | + | 
 | 50 | +// Helper to start a goroutine while also storing the 'task' structure.  | 
 | 51 | +static void* start_wrapper(void *arg) {  | 
 | 52 | +    struct state_pass *state = arg;  | 
 | 53 | +    void *(*start)(void*) = state->start;  | 
 | 54 | +    void *args = state->args;  | 
 | 55 | +    current_task = state->task;  | 
 | 56 | + | 
 | 57 | +    // Save the current stack pointer in the goroutine state, for the GC.  | 
 | 58 | +    int stackAddr;  | 
 | 59 | +    *(state->stackTop) = (uintptr_t)(&stackAddr);  | 
 | 60 | + | 
 | 61 | +    // Notify the caller that the thread has successfully started and  | 
 | 62 | +    // initialized.  | 
 | 63 | +    sem_post(&state->startlock);  | 
 | 64 | + | 
 | 65 | +    // Run the goroutine function.  | 
 | 66 | +    start(args);  | 
 | 67 | + | 
 | 68 | +    // Notify the Go side this thread will exit.  | 
 | 69 | +    tinygo_task_exited(current_task);  | 
 | 70 | + | 
 | 71 | +    return NULL;  | 
 | 72 | +};  | 
 | 73 | + | 
 | 74 | +// Start a new goroutine in an OS thread.  | 
 | 75 | +int tinygo_task_start(uintptr_t fn, void *args, void *task, pthread_t *thread, uintptr_t *stackTop, void *context) {  | 
 | 76 | +    // Sanity check. Should get optimized away.  | 
 | 77 | +    if (sizeof(pthread_t) != sizeof(void*)) {  | 
 | 78 | +        __builtin_trap();  | 
 | 79 | +    }  | 
 | 80 | + | 
 | 81 | +    struct state_pass state = {  | 
 | 82 | +        .start     = (void*)fn,  | 
 | 83 | +        .args      = args,  | 
 | 84 | +        .task      = task,  | 
 | 85 | +        .stackTop  = stackTop,  | 
 | 86 | +    };  | 
 | 87 | +    sem_init(&state.startlock, 0, 0);  | 
 | 88 | +    int result = pthread_create(thread, NULL, &start_wrapper, &state);  | 
 | 89 | + | 
 | 90 | +    // Wait until the thread has been created and read all state_pass variables.  | 
 | 91 | +    sem_wait(&state.startlock);  | 
 | 92 | + | 
 | 93 | +    return result;  | 
 | 94 | +}  | 
 | 95 | + | 
 | 96 | +// Return the current task (for task.Current()).  | 
 | 97 | +void* tinygo_task_current(void) {  | 
 | 98 | +    return current_task;  | 
 | 99 | +}  | 
 | 100 | + | 
 | 101 | +// Send a signal to cause the task to pause for the GC mark phase.  | 
 | 102 | +void tinygo_task_send_gc_signal(pthread_t thread) {  | 
 | 103 | +    pthread_kill(thread, taskPauseSignal);  | 
 | 104 | +}  | 
0 commit comments