Skip to content

Commit 28f51ca

Browse files
committed
WIP map every goroutine to a new OS thread
1 parent 7601c6d commit 28f51ca

13 files changed

+984
-2
lines changed

GNUmakefile

+1
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,7 @@ endif
934934
@cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src
935935
@cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src
936936
@cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src
937+
@cp -rp lib/musl/src/misc build/release/tinygo/lib/musl/src
937938
@cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src
938939
@cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src
939940
@cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src

builder/musl.go

+1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ var libMusl = Library{
127127
"malloc/mallocng/*.c",
128128
"mman/*.c",
129129
"math/*.c",
130+
"misc/*.c",
130131
"multibyte/*.c",
131132
"signal/" + arch + "/*.s",
132133
"signal/*.c",

compileopts/options.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
var (
1111
validBuildModeOptions = []string{"default", "c-shared"}
1212
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"}
13-
validSchedulerOptions = []string{"none", "tasks", "asyncify"}
13+
validSchedulerOptions = []string{"none", "tasks", "asyncify", "threads"}
1414
validSerialOptions = []string{"none", "uart", "usb", "rtt"}
1515
validPrintSizeOptions = []string{"none", "short", "full"}
1616
validPanicStrategyOptions = []string{"print", "trap"}

compileopts/target.go

+1
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
414414
}
415415
spec.ExtraFiles = append(spec.ExtraFiles,
416416
"src/internal/task/futex_linux.c",
417+
"src/internal/task/task_threads.c",
417418
"src/runtime/runtime_unix.c",
418419
"src/runtime/signal.c")
419420
case "windows":

src/internal/task/linux.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//go:build linux && !baremetal
2+
3+
package task
4+
5+
import "unsafe"
6+
7+
// Musl uses a pointer (or unsigned long for C++) so unsafe.Pointer should be
8+
// fine.
9+
type threadID unsafe.Pointer

src/internal/task/semaphore.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package task
2+
3+
// Barebones semaphore implementation.
4+
// The main limitation is that if there are multiple waiters, a single Post()
5+
// call won't do anything. Only when Post() has been called to awaken all
6+
// waiters will the waiters proceed.
7+
// This limitation is not a problem when there will only be a single waiter.
8+
type Semaphore struct {
9+
futex Futex
10+
}
11+
12+
// Post (unlock) the semaphore, incrementing the value in the semaphore.
13+
func (s *Semaphore) Post() {
14+
newValue := s.futex.Add(1)
15+
if newValue == 0 {
16+
s.futex.WakeAll()
17+
}
18+
}
19+
20+
// Wait (lock) the semaphore, decrementing the value in the semaphore.
21+
func (s *Semaphore) Wait() {
22+
delta := int32(-1)
23+
value := s.futex.Add(uint32(delta))
24+
for {
25+
if int32(value) >= 0 {
26+
// Semaphore unlocked!
27+
return
28+
}
29+
s.futex.Wait(value)
30+
value = s.futex.Load()
31+
}
32+
}

src/internal/task/task_threads.c

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
sem_t startlock;
25+
};
26+
27+
// Handle the GC pause in Go.
28+
void tinygo_task_gc_pause(int sig);
29+
30+
// Initialize threads from the C side.
31+
void tinygo_task_init(void *mainTask, void *context) {
32+
// Make sure the current task pointer is set correctly for the main
33+
// goroutine as well.
34+
current_task = mainTask;
35+
36+
// Register the "GC pause" signal for the entire process.
37+
// Using pthread_kill, we can still send the signal to a specific thread.
38+
struct sigaction act = { 0 };
39+
act.sa_flags = SA_SIGINFO;
40+
act.sa_handler = &tinygo_task_gc_pause;
41+
sigaction(taskPauseSignal, &act, NULL);
42+
}
43+
44+
void tinygo_task_exited(void*);
45+
46+
// Helper to start a goroutine while also storing the 'task' structure.
47+
static void* start_wrapper(void *arg) {
48+
struct state_pass *state = arg;
49+
void *(*start)(void*) = state->start;
50+
void *args = state->args;
51+
current_task = state->task;
52+
53+
// Notify the caller that the thread has successfully started and
54+
// initialized.
55+
sem_post(&state->startlock);
56+
57+
// Run the goroutine function.
58+
start(args);
59+
60+
// Notify the Go side this thread will exit.
61+
tinygo_task_exited(current_task);
62+
63+
return NULL;
64+
};
65+
66+
// Start a new goroutine in an OS thread.
67+
int tinygo_task_start(uintptr_t fn, void *args, void *task, pthread_t *thread, uint64_t id, void *context) {
68+
// Sanity check. Should get optimized away.
69+
if (sizeof(pthread_t) != sizeof(void*)) {
70+
__builtin_trap();
71+
}
72+
73+
struct state_pass state = {
74+
.start = (void*)fn,
75+
.args = args,
76+
.task = task,
77+
};
78+
sem_init(&state.startlock, 0, 0);
79+
int result = pthread_create(thread, NULL, &start_wrapper, &state);
80+
81+
// Wait until the thread has been crated and read all state_pass variables.
82+
sem_wait(&state.startlock);
83+
84+
return result;
85+
}
86+
87+
// Return the current task (for task.Current()).
88+
void* tinygo_task_current(void) {
89+
return current_task;
90+
}
91+
92+
// Obtain the highest address of the stack.
93+
uintptr_t tinygo_task_stacktop(void) {
94+
pthread_attr_t attr;
95+
pthread_getattr_np(pthread_self(), &attr);
96+
void *stackbase;
97+
size_t stacksize;
98+
pthread_attr_getstack(&attr, &stackbase, &stacksize);
99+
pthread_attr_destroy(&attr);
100+
return (uintptr_t)stackbase + (uintptr_t)stacksize;
101+
}
102+
103+
// Send a signal to cause the task to pause for the GC mark phase.
104+
void tinygo_task_send_gc_signal(pthread_t thread) {
105+
pthread_kill(thread, taskPauseSignal);
106+
}

0 commit comments

Comments
 (0)