aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2024-10-22 15:53:57 +0200
committerRon Evans <[email protected]>2024-10-23 09:13:30 +0100
commit0f95b4102d83b6977277ea7d5e87ad538eddc5ef (patch)
tree8a63d2ea5f7028a9edeaa663f7782b9d4b9a101e
parent24c11d4ba5f722ef646ae0e1ee06b90b2411590c (diff)
downloadtinygo-0f95b4102d83b6977277ea7d5e87ad538eddc5ef.tar.gz
tinygo-0f95b4102d83b6977277ea7d5e87ad538eddc5ef.zip
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.
-rw-r--r--src/internal/task/task.go3
-rw-r--r--src/internal/task/task_asyncify.go30
-rw-r--r--src/internal/task/task_stack.go3
-rw-r--r--targets/wasip1.json1
-rw-r--r--targets/wasip2.json1
-rw-r--r--targets/wasm.json1
6 files changed, 25 insertions, 14 deletions
diff --git a/src/internal/task/task.go b/src/internal/task/task.go
index c45739097..c1ee57dda 100644
--- a/src/internal/task/task.go
+++ b/src/internal/task/task.go
@@ -30,3 +30,6 @@ type Task struct {
// the given function and falls back to the default stack size. It is replaced
// with a load from a special section just before codegen.
func getGoroutineStackSize(fn uintptr) uintptr
+
+//go:linkname runtime_alloc runtime.alloc
+func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer
diff --git a/src/internal/task/task_asyncify.go b/src/internal/task/task_asyncify.go
index b16ddd7d8..55a1044e4 100644
--- a/src/internal/task/task_asyncify.go
+++ b/src/internal/task/task_asyncify.go
@@ -35,11 +35,16 @@ type state struct {
type stackState struct {
// asyncify is the stack pointer of the asyncify stack.
// This starts from the bottom and grows upwards.
- asyncifysp uintptr
+ asyncifysp unsafe.Pointer
// asyncify is stack pointer of the C stack.
// 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.
@@ -63,12 +68,18 @@ func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
s.args = args
// 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.
- s.asyncifysp = uintptr(unsafe.Pointer(&stack[0]))
- s.csp = uintptr(unsafe.Pointer(&stack[0])) + uintptr(len(stack))*unsafe.Sizeof(uintptr(0))
- stack[0] = stackCanary
+ s.asyncifysp = unsafe.Add(stack, unsafe.Sizeof(uintptr(0)))
+ s.csp = unsafe.Add(stack, stackSize)
}
//go:linkname runqueuePushBack runtime.runqueuePushBack
@@ -85,14 +96,11 @@ func Current() *Task {
// 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.
func Pause() {
- // This is mildly unsafe but this is also the only place we can do this.
- if *(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) != stackCanary {
+ if *currentTask.state.canaryPtr != stackCanary {
runtimePanic("stack overflow")
}
currentTask.state.unwind()
-
- *(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) = stackCanary
}
//export tinygo_unwind
@@ -113,7 +121,7 @@ func (t *Task) Resume() {
}
currentTask = prevTask
t.gcData.swap()
- if t.state.asyncifysp > t.state.csp {
+ if uintptr(t.state.asyncifysp) > uintptr(t.state.csp) {
runtimePanic("stack overflow")
}
}
diff --git a/src/internal/task/task_stack.go b/src/internal/task/task_stack.go
index f566de041..551612425 100644
--- a/src/internal/task/task_stack.go
+++ b/src/internal/task/task_stack.go
@@ -104,9 +104,6 @@ var startTask [0]uint8
//go:linkname runqueuePushBack runtime.runqueuePushBack
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.
// The new goroutine is scheduled to run later.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
diff --git a/targets/wasip1.json b/targets/wasip1.json
index 409be9ec9..8d1966e78 100644
--- a/targets/wasip1.json
+++ b/targets/wasip1.json
@@ -8,6 +8,7 @@
"linker": "wasm-ld",
"libc": "wasi-libc",
"rtlib": "compiler-rt",
+ "gc": "precise",
"scheduler": "asyncify",
"default-stack-size": 65536,
"cflags": [
diff --git a/targets/wasip2.json b/targets/wasip2.json
index 4b0e67591..7c8394c8e 100644
--- a/targets/wasip2.json
+++ b/targets/wasip2.json
@@ -9,6 +9,7 @@
"linker": "wasm-ld",
"libc": "wasmbuiltins",
"rtlib": "compiler-rt",
+ "gc": "precise",
"scheduler": "asyncify",
"default-stack-size": 65536,
"cflags": [
diff --git a/targets/wasm.json b/targets/wasm.json
index 050ee105e..48a678891 100644
--- a/targets/wasm.json
+++ b/targets/wasm.json
@@ -8,6 +8,7 @@
"linker": "wasm-ld",
"libc": "wasi-libc",
"rtlib": "compiler-rt",
+ "gc": "precise",
"scheduler": "asyncify",
"default-stack-size": 65536,
"cflags": [