wasm: use precise GC for WebAssembly (including WASI)

With a few small modifications, all the problems with `-gc=precise` in
WebAssembly seem to have been fixed.

I didn't do any performance measurements, but this is supposed to
improve GC performance.
This commit is contained in:
Ayke van Laethem
2024-10-22 15:53:57 +02:00
committed by Ron Evans
parent 24c11d4ba5
commit 0f95b4102d
6 changed files with 25 additions and 14 deletions
+3
View File
@@ -30,3 +30,6 @@ type Task struct {
// the given function and falls back to the default stack size. It is replaced // the given function and falls back to the default stack size. It is replaced
// with a load from a special section just before codegen. // with a load from a special section just before codegen.
func getGoroutineStackSize(fn uintptr) uintptr func getGoroutineStackSize(fn uintptr) uintptr
//go:linkname runtime_alloc runtime.alloc
func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer
+19 -11
View File
@@ -35,11 +35,16 @@ type state struct {
type stackState struct { type stackState struct {
// asyncify is the stack pointer of the asyncify stack. // asyncify is the stack pointer of the asyncify stack.
// This starts from the bottom and grows upwards. // This starts from the bottom and grows upwards.
asyncifysp uintptr asyncifysp unsafe.Pointer
// asyncify is stack pointer of the C stack. // asyncify is stack pointer of the C stack.
// This starts from the top and grows downwards. // This starts from the top and grows downwards.
csp uintptr csp unsafe.Pointer
// Pointer to the first (lowest address) of the stack. It must never be
// overwritten. It can be checked from time to time to see whether a stack
// overflow happened in the past.
canaryPtr *uintptr
} }
// start creates and starts a new goroutine with the given function and arguments. // start creates and starts a new goroutine with the given function and arguments.
@@ -63,12 +68,18 @@ func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
s.args = args s.args = args
// Create a stack. // Create a stack.
stack := make([]uintptr, stackSize/unsafe.Sizeof(uintptr(0))) stack := runtime_alloc(stackSize, nil)
// Set up the stack canary, a random number that should be checked when
// switching from the task back to the scheduler. The stack canary pointer
// points to the first word of the stack. If it has changed between now and
// the next stack switch, there was a stack overflow.
s.canaryPtr = (*uintptr)(stack)
*s.canaryPtr = stackCanary
// Calculate stack base addresses. // Calculate stack base addresses.
s.asyncifysp = uintptr(unsafe.Pointer(&stack[0])) s.asyncifysp = unsafe.Add(stack, unsafe.Sizeof(uintptr(0)))
s.csp = uintptr(unsafe.Pointer(&stack[0])) + uintptr(len(stack))*unsafe.Sizeof(uintptr(0)) s.csp = unsafe.Add(stack, stackSize)
stack[0] = stackCanary
} }
//go:linkname runqueuePushBack runtime.runqueuePushBack //go:linkname runqueuePushBack runtime.runqueuePushBack
@@ -85,14 +96,11 @@ func Current() *Task {
// Pause suspends the current task and returns to the scheduler. // Pause suspends the current task and returns to the scheduler.
// This function may only be called when running on a goroutine stack, not when running on the system stack. // This function may only be called when running on a goroutine stack, not when running on the system stack.
func Pause() { func Pause() {
// This is mildly unsafe but this is also the only place we can do this. if *currentTask.state.canaryPtr != stackCanary {
if *(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) != stackCanary {
runtimePanic("stack overflow") runtimePanic("stack overflow")
} }
currentTask.state.unwind() currentTask.state.unwind()
*(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) = stackCanary
} }
//export tinygo_unwind //export tinygo_unwind
@@ -113,7 +121,7 @@ func (t *Task) Resume() {
} }
currentTask = prevTask currentTask = prevTask
t.gcData.swap() t.gcData.swap()
if t.state.asyncifysp > t.state.csp { if uintptr(t.state.asyncifysp) > uintptr(t.state.csp) {
runtimePanic("stack overflow") runtimePanic("stack overflow")
} }
} }
-3
View File
@@ -104,9 +104,6 @@ var startTask [0]uint8
//go:linkname runqueuePushBack runtime.runqueuePushBack //go:linkname runqueuePushBack runtime.runqueuePushBack
func runqueuePushBack(*Task) func runqueuePushBack(*Task)
//go:linkname runtime_alloc runtime.alloc
func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer
// start creates and starts a new goroutine with the given function and arguments. // start creates and starts a new goroutine with the given function and arguments.
// The new goroutine is scheduled to run later. // The new goroutine is scheduled to run later.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
+1
View File
@@ -8,6 +8,7 @@
"linker": "wasm-ld", "linker": "wasm-ld",
"libc": "wasi-libc", "libc": "wasi-libc",
"rtlib": "compiler-rt", "rtlib": "compiler-rt",
"gc": "precise",
"scheduler": "asyncify", "scheduler": "asyncify",
"default-stack-size": 65536, "default-stack-size": 65536,
"cflags": [ "cflags": [
+1
View File
@@ -9,6 +9,7 @@
"linker": "wasm-ld", "linker": "wasm-ld",
"libc": "wasmbuiltins", "libc": "wasmbuiltins",
"rtlib": "compiler-rt", "rtlib": "compiler-rt",
"gc": "precise",
"scheduler": "asyncify", "scheduler": "asyncify",
"default-stack-size": 65536, "default-stack-size": 65536,
"cflags": [ "cflags": [
+1
View File
@@ -8,6 +8,7 @@
"linker": "wasm-ld", "linker": "wasm-ld",
"libc": "wasi-libc", "libc": "wasi-libc",
"rtlib": "compiler-rt", "rtlib": "compiler-rt",
"gc": "precise",
"scheduler": "asyncify", "scheduler": "asyncify",
"default-stack-size": 65536, "default-stack-size": 65536,
"cflags": [ "cflags": [