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 36d7fd96..3f4d5235 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 d34a1eb4..6224f256 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 1ecf0c79..70ec4ef4 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 6f308247..a0c42991 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 00000000..d3e400d3 --- /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 00000000..63674805 --- /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 bad501b6..b490a202 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 d67f0e1c..939008bc 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 a703d10a..59af6503 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 d4a04637..1cdd31f3 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 +}