From e4de7b49575c28fcd3a446e7478687e9611d110a Mon Sep 17 00:00:00 2001 From: Nia Waldvogel Date: Tue, 14 Dec 2021 16:48:11 -0500 Subject: [PATCH] internal/task: swap stack chain when switching goroutines This change swaps the stack chain when switching goroutines, ensuring that the chain is maintained consistently. This is only really currently necessary with asyncify on wasm. --- compiler/testdata/channel.ll | 3 ++- .../testdata/goroutine-cortex-m-qemu-tasks.ll | 3 ++- compiler/testdata/goroutine-wasm-asyncify.ll | 3 ++- .../testdata/goroutine-wasm-coroutines.ll | 3 ++- src/internal/task/gc_stack_chain.go | 19 +++++++++++++++++++ src/internal/task/gc_stack_noop.go | 9 +++++++++ src/internal/task/task.go | 3 +++ src/internal/task/task_asyncify.go | 2 ++ src/internal/task/task_stack.go | 3 +++ src/runtime/gc_stack_portable.go | 7 +++++++ 10 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 src/internal/task/gc_stack_chain.go create mode 100644 src/internal/task/gc_stack_noop.go diff --git a/compiler/testdata/channel.ll b/compiler/testdata/channel.ll index 36d7fd967..3f4d52351 100644 --- a/compiler/testdata/channel.ll +++ b/compiler/testdata/channel.ll @@ -5,7 +5,8 @@ target triple = "wasm32-unknown-wasi" %runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* } %runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } } -%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" } +%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.gcData", %"internal/task.state" } +%"internal/task.gcData" = type { i8* } %"internal/task.state" = type { i32, i8*, %"internal/task.stackState", i1 } %"internal/task.stackState" = type { i32, i32 } %runtime.chanSelectState = type { %runtime.channel*, i8* } diff --git a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll index d34a1eb43..6224f256a 100644 --- a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll +++ b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll @@ -5,7 +5,8 @@ target triple = "thumbv7m-unknown-unknown-eabi" %runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* } %runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } } -%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" } +%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.gcData", %"internal/task.state" } +%"internal/task.gcData" = type {} %"internal/task.state" = type { i32, i32* } %runtime.chanSelectState = type { %runtime.channel*, i8* } diff --git a/compiler/testdata/goroutine-wasm-asyncify.ll b/compiler/testdata/goroutine-wasm-asyncify.ll index 1ecf0c796..70ec4ef49 100644 --- a/compiler/testdata/goroutine-wasm-asyncify.ll +++ b/compiler/testdata/goroutine-wasm-asyncify.ll @@ -5,7 +5,8 @@ target triple = "wasm32-unknown-wasi" %runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* } %runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } } -%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" } +%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.gcData", %"internal/task.state" } +%"internal/task.gcData" = type { i8* } %"internal/task.state" = type { i32, i8*, %"internal/task.stackState", i1 } %"internal/task.stackState" = type { i32, i32 } %runtime.chanSelectState = type { %runtime.channel*, i8* } diff --git a/compiler/testdata/goroutine-wasm-coroutines.ll b/compiler/testdata/goroutine-wasm-coroutines.ll index 6f3082471..a0c429912 100644 --- a/compiler/testdata/goroutine-wasm-coroutines.ll +++ b/compiler/testdata/goroutine-wasm-coroutines.ll @@ -6,7 +6,8 @@ target triple = "wasm32-unknown-wasi" %runtime.funcValueWithSignature = type { i32, i8* } %runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* } %runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } } -%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" } +%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.gcData", %"internal/task.state" } +%"internal/task.gcData" = type {} %"internal/task.state" = type { i8* } %runtime.chanSelectState = type { %runtime.channel*, i8* } diff --git a/src/internal/task/gc_stack_chain.go b/src/internal/task/gc_stack_chain.go new file mode 100644 index 000000000..d3e400d37 --- /dev/null +++ b/src/internal/task/gc_stack_chain.go @@ -0,0 +1,19 @@ +//go:build (gc.conservative || gc.extalloc) && tinygo.wasm && !scheduler.coroutines +// +build gc.conservative gc.extalloc +// +build tinygo.wasm +// +build !scheduler.coroutines + +package task + +import "unsafe" + +//go:linkname swapStackChain runtime.swapStackChain +func swapStackChain(dst *unsafe.Pointer) + +type gcData struct { + stackChain unsafe.Pointer +} + +func (gcd *gcData) swap() { + swapStackChain(&gcd.stackChain) +} diff --git a/src/internal/task/gc_stack_noop.go b/src/internal/task/gc_stack_noop.go new file mode 100644 index 000000000..636748056 --- /dev/null +++ b/src/internal/task/gc_stack_noop.go @@ -0,0 +1,9 @@ +//go:build (!gc.conservative && !gc.extalloc) || !tinygo.wasm || scheduler.coroutines +// +build !gc.conservative,!gc.extalloc !tinygo.wasm scheduler.coroutines + +package task + +type gcData struct{} + +func (gcd *gcData) swap() { +} diff --git a/src/internal/task/task.go b/src/internal/task/task.go index bad501b61..b490a2026 100644 --- a/src/internal/task/task.go +++ b/src/internal/task/task.go @@ -15,6 +15,9 @@ type Task struct { // Data is a field which can be used for storing state information. Data uint64 + // gcData holds data for the GC. + gcData gcData + // state is the underlying running state of the task. state state } diff --git a/src/internal/task/task_asyncify.go b/src/internal/task/task_asyncify.go index d67f0e1ca..939008bcd 100644 --- a/src/internal/task/task_asyncify.go +++ b/src/internal/task/task_asyncify.go @@ -104,6 +104,7 @@ func (*stackState) unwind() func (t *Task) Resume() { // The current task must be saved and restored because this can nest on WASM with JS. prevTask := currentTask + t.gcData.swap() currentTask = t if !t.state.launched { t.state.launch() @@ -112,6 +113,7 @@ func (t *Task) Resume() { t.state.rewind() } currentTask = prevTask + t.gcData.swap() if t.state.asyncifysp > t.state.csp { runtimePanic("stack overflow") } diff --git a/src/internal/task/task_stack.go b/src/internal/task/task_stack.go index a703d10ac..59af65031 100644 --- a/src/internal/task/task_stack.go +++ b/src/internal/task/task_stack.go @@ -1,3 +1,4 @@ +//go:build scheduler.tasks // +build scheduler.tasks package task @@ -54,7 +55,9 @@ func pause() { // This may only be called from the scheduler. func (t *Task) Resume() { currentTask = t + t.gcData.swap() t.state.resume() + t.gcData.swap() currentTask = nil } diff --git a/src/runtime/gc_stack_portable.go b/src/runtime/gc_stack_portable.go index d4a046374..1cdd31f3c 100644 --- a/src/runtime/gc_stack_portable.go +++ b/src/runtime/gc_stack_portable.go @@ -1,3 +1,4 @@ +//go:build (gc.conservative || gc.extalloc) && tinygo.wasm // +build gc.conservative gc.extalloc // +build tinygo.wasm @@ -37,3 +38,9 @@ func markStack() { // construction. Calls to it are later replaced with regular stack bookkeeping // code. func trackPointer(ptr unsafe.Pointer) + +// swapStackChain swaps the stack chain. +// This is called from internal/task when switching goroutines. +func swapStackChain(dst **stackChainObject) { + *dst, stackChainStart = stackChainStart, *dst +}