mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-07 12:33:42 +00:00
refactor coroutine lowering and tasks
This commit is contained in:
+24
-24
@@ -24,6 +24,7 @@ package runtime
|
||||
// element of the receiving coroutine and setting the 'comma-ok' value to false.
|
||||
|
||||
import (
|
||||
"internal/task"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@@ -46,7 +47,7 @@ type channelBlockedList struct {
|
||||
// If this channel operation is not part of a select, then the pointer field of the state holds the data buffer.
|
||||
// If this channel operation is part of a select, then the pointer field of the state holds the recieve buffer.
|
||||
// If this channel operation is a receive, then the data field should be set to zero when resuming due to channel closure.
|
||||
t *task
|
||||
t *task.Task
|
||||
|
||||
// s is a pointer to the channel select state corresponding to this operation.
|
||||
// This will be nil if and only if this channel operation is not part of a select statement.
|
||||
@@ -141,24 +142,24 @@ func (ch *channel) resumeRX(ok bool) unsafe.Pointer {
|
||||
b, ch.blocked = ch.blocked, ch.blocked.next
|
||||
|
||||
// get destination pointer
|
||||
dst := b.t.state().ptr
|
||||
dst := b.t.Ptr
|
||||
|
||||
if !ok {
|
||||
// the result value is zero
|
||||
memzero(dst, ch.elementSize)
|
||||
b.t.state().data = 0
|
||||
b.t.Data = 0
|
||||
}
|
||||
|
||||
if b.s != nil {
|
||||
// tell the select op which case resumed
|
||||
b.t.state().ptr = unsafe.Pointer(b.s)
|
||||
b.t.Ptr = unsafe.Pointer(b.s)
|
||||
|
||||
// detach associated operations
|
||||
b.detach()
|
||||
}
|
||||
|
||||
// push task onto runqueue
|
||||
runqueuePushBack(b.t)
|
||||
runqueue.Push(b.t)
|
||||
|
||||
return dst
|
||||
}
|
||||
@@ -171,21 +172,21 @@ func (ch *channel) resumeTX() unsafe.Pointer {
|
||||
b, ch.blocked = ch.blocked, ch.blocked.next
|
||||
|
||||
// get source pointer
|
||||
src := b.t.state().ptr
|
||||
src := b.t.Ptr
|
||||
|
||||
if b.s != nil {
|
||||
// use state's source pointer
|
||||
src = b.s.value
|
||||
|
||||
// tell the select op which case resumed
|
||||
b.t.state().ptr = unsafe.Pointer(b.s)
|
||||
b.t.Ptr = unsafe.Pointer(b.s)
|
||||
|
||||
// detach associated operations
|
||||
b.detach()
|
||||
}
|
||||
|
||||
// push task onto runqueue
|
||||
runqueuePushBack(b.t)
|
||||
runqueue.Push(b.t)
|
||||
|
||||
return src
|
||||
}
|
||||
@@ -424,17 +425,16 @@ func chanSend(ch *channel, value unsafe.Pointer) {
|
||||
}
|
||||
|
||||
// wait for reciever
|
||||
sender := getCoroutine()
|
||||
sender := task.Current()
|
||||
ch.state = chanStateSend
|
||||
senderState := sender.state()
|
||||
senderState.ptr = value
|
||||
sender.Ptr = value
|
||||
ch.blocked = &channelBlockedList{
|
||||
next: ch.blocked,
|
||||
t: sender,
|
||||
}
|
||||
chanDebug(ch)
|
||||
yield()
|
||||
senderState.ptr = nil
|
||||
task.Pause()
|
||||
sender.Ptr = nil
|
||||
}
|
||||
|
||||
// chanRecv receives a single value over a channel.
|
||||
@@ -454,18 +454,17 @@ func chanRecv(ch *channel, value unsafe.Pointer) bool {
|
||||
}
|
||||
|
||||
// wait for a value
|
||||
receiver := getCoroutine()
|
||||
receiver := task.Current()
|
||||
ch.state = chanStateRecv
|
||||
receiverState := receiver.state()
|
||||
receiverState.ptr, receiverState.data = value, 1
|
||||
receiver.Ptr, receiver.Data = value, 1
|
||||
ch.blocked = &channelBlockedList{
|
||||
next: ch.blocked,
|
||||
t: receiver,
|
||||
}
|
||||
chanDebug(ch)
|
||||
yield()
|
||||
ok := receiverState.data == 1
|
||||
receiverState.ptr, receiverState.data = nil, 0
|
||||
task.Pause()
|
||||
ok := receiver.Data == 1
|
||||
receiver.Ptr, receiver.Data = nil, 0
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -515,7 +514,7 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelB
|
||||
for i, v := range states {
|
||||
ops[i] = channelBlockedList{
|
||||
next: v.ch.blocked,
|
||||
t: getCoroutine(),
|
||||
t: task.Current(),
|
||||
s: &states[i],
|
||||
allSelectOps: ops,
|
||||
}
|
||||
@@ -547,14 +546,15 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelB
|
||||
}
|
||||
|
||||
// expose rx buffer
|
||||
getCoroutine().state().ptr = recvbuf
|
||||
getCoroutine().state().data = 1
|
||||
t := task.Current()
|
||||
t.Ptr = recvbuf
|
||||
t.Data = 1
|
||||
|
||||
// wait for one case to fire
|
||||
yield()
|
||||
task.Pause()
|
||||
|
||||
// figure out which one fired and return the ok value
|
||||
return (uintptr(getCoroutine().state().ptr) - uintptr(unsafe.Pointer(&states[0]))) / unsafe.Sizeof(chanSelectState{}), getCoroutine().state().data != 0
|
||||
return (uintptr(t.Ptr) - uintptr(unsafe.Pointer(&states[0]))) / unsafe.Sizeof(chanSelectState{}), t.Data != 0
|
||||
}
|
||||
|
||||
// tryChanSelect is like chanSelect, but it does a non-blocking select operation.
|
||||
|
||||
+2
-11
@@ -10,17 +10,8 @@ const Compiler = "tinygo"
|
||||
// package.
|
||||
func initAll()
|
||||
|
||||
// A function call to this function is replaced with one of the following,
|
||||
// depending on whether the scheduler is necessary:
|
||||
//
|
||||
// Without scheduler:
|
||||
//
|
||||
// main.main()
|
||||
//
|
||||
// With scheduler:
|
||||
//
|
||||
// main.main()
|
||||
// scheduler()
|
||||
// callMain is a placeholder for the program main function.
|
||||
// All references to this are replaced with references to the program main function by the compiler.
|
||||
func callMain()
|
||||
|
||||
func GOMAXPROCS(n int) int {
|
||||
|
||||
@@ -40,7 +40,10 @@ func main() {
|
||||
initAll()
|
||||
|
||||
// Compiler-generated call to main.main().
|
||||
callMain()
|
||||
go callMain()
|
||||
|
||||
// Run the scheduler.
|
||||
scheduler()
|
||||
}
|
||||
|
||||
func preinit() {
|
||||
|
||||
@@ -17,7 +17,8 @@ type timeUnit int64
|
||||
func main() {
|
||||
preinit()
|
||||
initAll()
|
||||
callMain()
|
||||
go callMain()
|
||||
scheduler()
|
||||
abort()
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ type timeUnit int64
|
||||
func main() {
|
||||
preinit()
|
||||
initAll()
|
||||
callMain()
|
||||
go callMain()
|
||||
scheduler()
|
||||
abort()
|
||||
}
|
||||
|
||||
|
||||
@@ -40,29 +40,6 @@ func preinit() {
|
||||
}
|
||||
}
|
||||
|
||||
// calleeSavedRegs is the list of registers that must be saved and restored when
|
||||
// switching between tasks. Also see scheduler_cortexm.S that relies on the
|
||||
// exact layout of this struct.
|
||||
type calleeSavedRegs struct {
|
||||
r4 uintptr
|
||||
r5 uintptr
|
||||
r6 uintptr
|
||||
r7 uintptr
|
||||
r8 uintptr
|
||||
r9 uintptr
|
||||
r10 uintptr
|
||||
r11 uintptr
|
||||
}
|
||||
|
||||
// prepareStartTask stores fn and args in some callee-saved registers that can
|
||||
// then be used by the startTask function (implemented in assembly) to set up
|
||||
// the initial stack pointer and initial argument with the pointer to the object
|
||||
// with the goroutine start arguments.
|
||||
func (r *calleeSavedRegs) prepareStartTask(fn, args uintptr) {
|
||||
r.r4 = fn
|
||||
r.r5 = args
|
||||
}
|
||||
|
||||
func abort() {
|
||||
// disable all interrupts
|
||||
arm.DisableInterrupts()
|
||||
|
||||
@@ -21,7 +21,8 @@ var timestamp timeUnit
|
||||
func main() {
|
||||
preinit()
|
||||
initAll()
|
||||
callMain()
|
||||
go callMain()
|
||||
scheduler()
|
||||
arm.SemihostingCall(arm.SemihostingReportException, arm.SemihostingApplicationExit)
|
||||
abort()
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ func main() {
|
||||
preinit()
|
||||
initPeripherals()
|
||||
initAll()
|
||||
callMain()
|
||||
go callMain()
|
||||
scheduler()
|
||||
abort()
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ func main() {
|
||||
systemInit()
|
||||
preinit()
|
||||
initAll()
|
||||
callMain()
|
||||
go callMain()
|
||||
scheduler()
|
||||
abort()
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ type timeUnit int64
|
||||
func main() {
|
||||
preinit()
|
||||
initAll()
|
||||
callMain()
|
||||
go callMain()
|
||||
scheduler()
|
||||
abort()
|
||||
}
|
||||
|
||||
@@ -52,7 +52,10 @@ func main() int {
|
||||
initAll()
|
||||
|
||||
// Compiler-generated call to main.main().
|
||||
callMain()
|
||||
go callMain()
|
||||
|
||||
// Run scheduler.
|
||||
scheduler()
|
||||
|
||||
// For libc compatibility.
|
||||
return 0
|
||||
|
||||
@@ -21,7 +21,8 @@ func fd_write(id uint32, iovs *wasiIOVec, iovs_len uint, nwritten *uint) (errno
|
||||
//export _start
|
||||
func _start() {
|
||||
initAll()
|
||||
callMain()
|
||||
go callMain()
|
||||
scheduler()
|
||||
}
|
||||
|
||||
// Using global variables to avoid heap allocation.
|
||||
@@ -50,7 +51,9 @@ func setEventHandler(fn func()) {
|
||||
|
||||
//go:export resume
|
||||
func resume() {
|
||||
handleEvent()
|
||||
go func() {
|
||||
handleEvent()
|
||||
}()
|
||||
}
|
||||
|
||||
//go:export go_scheduler
|
||||
|
||||
+31
-145
@@ -14,27 +14,16 @@ package runtime
|
||||
// of the coroutine-based scheduler, it is the coroutine pointer (a *i8 in
|
||||
// LLVM).
|
||||
|
||||
import "unsafe"
|
||||
import (
|
||||
"internal/task"
|
||||
)
|
||||
|
||||
const schedulerDebug = false
|
||||
|
||||
// State of a task. Internally represented as:
|
||||
//
|
||||
// {i8* next, i8* ptr, i32/i64 data}
|
||||
type taskState struct {
|
||||
next *task
|
||||
ptr unsafe.Pointer
|
||||
data uint
|
||||
}
|
||||
|
||||
// Queues used by the scheduler.
|
||||
//
|
||||
// TODO: runqueueFront can be removed by making the run queue a circular linked
|
||||
// list. The runqueueBack will simply refer to the front in the 'next' pointer.
|
||||
var (
|
||||
runqueueFront *task
|
||||
runqueueBack *task
|
||||
sleepQueue *task
|
||||
runqueue task.Queue
|
||||
sleepQueue *task.Task
|
||||
sleepQueueBaseTime timeUnit
|
||||
)
|
||||
|
||||
@@ -46,14 +35,14 @@ func scheduleLog(msg string) {
|
||||
}
|
||||
|
||||
// Simple logging with a task pointer, for debugging.
|
||||
func scheduleLogTask(msg string, t *task) {
|
||||
func scheduleLogTask(msg string, t *task.Task) {
|
||||
if schedulerDebug {
|
||||
println("---", msg, t)
|
||||
}
|
||||
}
|
||||
|
||||
// Simple logging with a channel and task pointer.
|
||||
func scheduleLogChan(msg string, ch *channel, t *task) {
|
||||
func scheduleLogChan(msg string, ch *channel, t *task.Task) {
|
||||
if schedulerDebug {
|
||||
println("---", msg, ch, t)
|
||||
}
|
||||
@@ -67,7 +56,7 @@ func scheduleLogChan(msg string, ch *channel, t *task) {
|
||||
//go:noinline
|
||||
func deadlock() {
|
||||
// call yield without requesting a wakeup
|
||||
yield()
|
||||
task.Pause()
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
@@ -80,122 +69,20 @@ func Goexit() {
|
||||
deadlock()
|
||||
}
|
||||
|
||||
// unblock unblocks a task and returns the next value
|
||||
func unblock(t *task) *task {
|
||||
state := t.state()
|
||||
next := state.next
|
||||
state.next = nil
|
||||
activateTask(t)
|
||||
return next
|
||||
}
|
||||
|
||||
// unblockChain unblocks the next task on the stack/queue, returning it
|
||||
// also updates the chain, putting the next element into the chain pointer
|
||||
// if the chain is used as a queue, tail is used as a pointer to the final insertion point
|
||||
// if the chain is used as a stack, tail should be nil
|
||||
func unblockChain(chain **task, tail ***task) *task {
|
||||
t := *chain
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
*chain = unblock(t)
|
||||
if tail != nil && *chain == nil {
|
||||
*tail = chain
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// dropChain drops a task from the given stack or queue
|
||||
// if the chain is used as a queue, tail is used as a pointer to the field containing a pointer to the next insertion point
|
||||
// if the chain is used as a stack, tail should be nil
|
||||
func dropChain(t *task, chain **task, tail ***task) {
|
||||
for c := chain; *c != nil; c = &((*c).state().next) {
|
||||
if *c == t {
|
||||
next := (*c).state().next
|
||||
if next == nil && tail != nil {
|
||||
*tail = c
|
||||
}
|
||||
*c = next
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("runtime: task not in chain")
|
||||
}
|
||||
|
||||
// Pause the current task for a given time.
|
||||
//go:linkname sleep time.Sleep
|
||||
func sleep(duration int64) {
|
||||
addSleepTask(getCoroutine(), duration)
|
||||
yield()
|
||||
}
|
||||
|
||||
func avrSleep(duration int64) {
|
||||
sleepTicks(timeUnit(duration / tickMicros))
|
||||
}
|
||||
|
||||
// Add a non-queued task to the run queue.
|
||||
//
|
||||
// This is a compiler intrinsic, and is called from a callee to reactivate the
|
||||
// caller.
|
||||
func activateTask(t *task) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
scheduleLogTask(" set runnable:", t)
|
||||
runqueuePushBack(t)
|
||||
}
|
||||
|
||||
// getTaskStateData is a helper function to get the current .data field of the
|
||||
// goroutine state.
|
||||
//go:inline
|
||||
func getTaskStateData(t *task) uint {
|
||||
return t.state().data
|
||||
}
|
||||
|
||||
// Add this task to the end of the run queue. May also destroy the task if it's
|
||||
// done.
|
||||
func runqueuePushBack(t *task) {
|
||||
if schedulerDebug {
|
||||
scheduleLogTask(" pushing back:", t)
|
||||
if t.state().next != nil {
|
||||
panic("runtime: runqueuePushBack: expected next task to be nil")
|
||||
}
|
||||
}
|
||||
if runqueueBack == nil { // empty runqueue
|
||||
runqueueBack = t
|
||||
runqueueFront = t
|
||||
} else {
|
||||
lastTaskState := runqueueBack.state()
|
||||
lastTaskState.next = t
|
||||
runqueueBack = t
|
||||
}
|
||||
}
|
||||
|
||||
// Get a task from the front of the run queue. Returns nil if there is none.
|
||||
func runqueuePopFront() *task {
|
||||
t := runqueueFront
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
state := t.state()
|
||||
runqueueFront = state.next
|
||||
if runqueueFront == nil {
|
||||
// Runqueue is empty now.
|
||||
runqueueBack = nil
|
||||
}
|
||||
state.next = nil
|
||||
return t
|
||||
// Add this task to the end of the run queue.
|
||||
func runqueuePushBack(t *task.Task) {
|
||||
runqueue.Push(t)
|
||||
}
|
||||
|
||||
// Add this task to the sleep queue, assuming its state is set to sleeping.
|
||||
func addSleepTask(t *task, duration int64) {
|
||||
func addSleepTask(t *task.Task, duration int64) {
|
||||
if schedulerDebug {
|
||||
println(" set sleep:", t, uint(duration/tickMicros))
|
||||
if t.state().next != nil {
|
||||
if t.Next != nil {
|
||||
panic("runtime: addSleepTask: expected next task to be nil")
|
||||
}
|
||||
}
|
||||
t.state().data = uint(duration / tickMicros) // TODO: longer durations
|
||||
t.Data = uint(duration / tickMicros) // TODO: longer durations
|
||||
now := ticks()
|
||||
if sleepQueue == nil {
|
||||
scheduleLog(" -> sleep new queue")
|
||||
@@ -206,20 +93,20 @@ func addSleepTask(t *task, duration int64) {
|
||||
|
||||
// Add to sleep queue.
|
||||
q := &sleepQueue
|
||||
for ; *q != nil; q = &((*q).state()).next {
|
||||
if t.state().data < (*q).state().data {
|
||||
for ; *q != nil; q = &(*q).Next {
|
||||
if t.Data < (*q).Data {
|
||||
// this will finish earlier than the next - insert here
|
||||
break
|
||||
} else {
|
||||
// this will finish later - adjust delay
|
||||
t.state().data -= (*q).state().data
|
||||
t.Data -= (*q).Data
|
||||
}
|
||||
}
|
||||
if *q != nil {
|
||||
// cut delay time between this sleep task and the next
|
||||
(*q).state().data -= t.state().data
|
||||
(*q).Data -= t.Data
|
||||
}
|
||||
t.state().next = *q
|
||||
t.Next = *q
|
||||
*q = t
|
||||
}
|
||||
|
||||
@@ -236,17 +123,16 @@ func scheduler() {
|
||||
|
||||
// Add tasks that are done sleeping to the end of the runqueue so they
|
||||
// will be executed soon.
|
||||
if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.state().data) {
|
||||
if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.Data) {
|
||||
t := sleepQueue
|
||||
scheduleLogTask(" awake:", t)
|
||||
state := t.state()
|
||||
sleepQueueBaseTime += timeUnit(state.data)
|
||||
sleepQueue = state.next
|
||||
state.next = nil
|
||||
runqueuePushBack(t)
|
||||
sleepQueueBaseTime += timeUnit(t.Data)
|
||||
sleepQueue = t.Next
|
||||
t.Next = nil
|
||||
runqueue.Push(t)
|
||||
}
|
||||
|
||||
t := runqueuePopFront()
|
||||
t := runqueue.Pop()
|
||||
if t == nil {
|
||||
if sleepQueue == nil {
|
||||
// No more tasks to execute.
|
||||
@@ -256,11 +142,11 @@ func scheduler() {
|
||||
scheduleLog(" no tasks left!")
|
||||
return
|
||||
}
|
||||
timeLeft := timeUnit(sleepQueue.state().data) - (now - sleepQueueBaseTime)
|
||||
timeLeft := timeUnit(sleepQueue.Data) - (now - sleepQueueBaseTime)
|
||||
if schedulerDebug {
|
||||
println(" sleeping...", sleepQueue, uint(timeLeft))
|
||||
for t := sleepQueue; t != nil; t = t.state().next {
|
||||
println(" task sleeping:", t, timeUnit(t.state().data))
|
||||
for t := sleepQueue; t != nil; t = t.Next {
|
||||
println(" task sleeping:", t, timeUnit(t.Data))
|
||||
}
|
||||
}
|
||||
sleepTicks(timeLeft)
|
||||
@@ -275,11 +161,11 @@ func scheduler() {
|
||||
|
||||
// Run the given task.
|
||||
scheduleLogTask(" run:", t)
|
||||
t.resume()
|
||||
t.Resume()
|
||||
}
|
||||
}
|
||||
|
||||
func Gosched() {
|
||||
runqueuePushBack(getCoroutine())
|
||||
yield()
|
||||
runqueue.Push(task.Current())
|
||||
task.Pause()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// +build !scheduler.none
|
||||
|
||||
package runtime
|
||||
|
||||
import "internal/task"
|
||||
|
||||
// Pause the current task for a given time.
|
||||
//go:linkname sleep time.Sleep
|
||||
func sleep(duration int64) {
|
||||
addSleepTask(task.Current(), duration)
|
||||
task.Pause()
|
||||
}
|
||||
@@ -2,103 +2,8 @@
|
||||
|
||||
package runtime
|
||||
|
||||
// This file implements the Go scheduler using coroutines.
|
||||
// A goroutine contains a whole stack. A coroutine is just a single function.
|
||||
// How do we use coroutines for goroutines, then?
|
||||
// * Every function that contains a blocking call (like sleep) is marked
|
||||
// blocking, and all it's parents (callers) are marked blocking as well
|
||||
// transitively until the root (main.main or a go statement).
|
||||
// * A blocking function that calls a non-blocking function is called as
|
||||
// usual.
|
||||
// * A blocking function that calls a blocking function passes its own
|
||||
// coroutine handle as a parameter to the subroutine. When the subroutine
|
||||
// returns, it will re-insert the parent into the scheduler.
|
||||
// Note that we use the type 'task' to refer to a coroutine, for compatibility
|
||||
// with the task-based scheduler. A task type here does not represent the whole
|
||||
// task, but just the topmost coroutine. For most of the scheduler, this
|
||||
// difference doesn't matter.
|
||||
//
|
||||
// For more background on coroutines in LLVM:
|
||||
// https://llvm.org/docs/Coroutines.html
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// A coroutine instance, wrapped here to provide some type safety. The value
|
||||
// must not be used directly, it is meant to be used as an opaque *i8 in LLVM.
|
||||
type task uint8
|
||||
|
||||
//go:export llvm.coro.resume
|
||||
func (t *task) resume()
|
||||
|
||||
//go:export llvm.coro.destroy
|
||||
func (t *task) destroy()
|
||||
|
||||
//go:export llvm.coro.done
|
||||
func (t *task) done() bool
|
||||
|
||||
//go:export llvm.coro.promise
|
||||
func (t *task) _promise(alignment int32, from bool) unsafe.Pointer
|
||||
|
||||
// Get the state belonging to a task.
|
||||
func (t *task) state() *taskState {
|
||||
return (*taskState)(t._promise(int32(unsafe.Alignof(taskState{})), false))
|
||||
}
|
||||
|
||||
func makeGoroutine(uintptr) uintptr
|
||||
|
||||
// Compiler stub to get the current goroutine. Calls to this function are
|
||||
// removed in the goroutine lowering pass.
|
||||
func getCoroutine() *task
|
||||
|
||||
// setTaskStatePtr is a helper function to set the current .ptr field of a
|
||||
// coroutine promise.
|
||||
func setTaskStatePtr(t *task, value unsafe.Pointer) {
|
||||
t.state().ptr = value
|
||||
}
|
||||
|
||||
// getTaskStatePtr is a helper function to get the current .ptr field from a
|
||||
// coroutine promise.
|
||||
func getTaskStatePtr(t *task) unsafe.Pointer {
|
||||
if t == nil {
|
||||
blockingPanic()
|
||||
}
|
||||
return t.state().ptr
|
||||
}
|
||||
|
||||
// yield suspends execution of the current goroutine
|
||||
// any wakeups must be configured before calling yield
|
||||
func yield()
|
||||
|
||||
// getSystemStackPointer returns the current stack pointer of the system stack.
|
||||
// This is always the current stack pointer.
|
||||
func getSystemStackPointer() uintptr {
|
||||
return getCurrentStackPointer()
|
||||
}
|
||||
|
||||
func fakeCoroutine(dst **task) {
|
||||
*dst = getCoroutine()
|
||||
for {
|
||||
yield()
|
||||
}
|
||||
}
|
||||
|
||||
func getFakeCoroutine() *task {
|
||||
// this isnt defined behavior, but this is what our implementation does
|
||||
// this is really a horrible hack
|
||||
var t *task
|
||||
go fakeCoroutine(&t)
|
||||
|
||||
// the first line of fakeCoroutine will have completed by now
|
||||
return t
|
||||
}
|
||||
|
||||
// noret is a placeholder that can be used to indicate that an async function is not going to directly return here
|
||||
func noret()
|
||||
|
||||
func getParentHandle() *task
|
||||
|
||||
func llvmCoroRefHolder() {
|
||||
noret()
|
||||
getParentHandle()
|
||||
getCoroutine()
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ tinygo_startTask:
|
||||
blx r4
|
||||
|
||||
// After return, exit this goroutine. This is a tail call.
|
||||
bl runtime.yield
|
||||
bl tinygo_pause
|
||||
|
||||
.section .text.tinygo_getSystemStackPointer
|
||||
.global tinygo_getSystemStackPointer
|
||||
@@ -35,24 +35,23 @@ tinygo_getSystemStackPointer:
|
||||
.global tinygo_switchToScheduler
|
||||
.type tinygo_switchToScheduler, %function
|
||||
tinygo_switchToScheduler:
|
||||
// r0 = oldTask *task
|
||||
// r0 = sp *uintptr
|
||||
|
||||
// Currently on the task stack (SP=PSP). We need to store the position on
|
||||
// the stack where the in-use registers will be stored.
|
||||
mov r1, sp
|
||||
subs r1, #36
|
||||
str r1, [r0, #36]
|
||||
str r1, [r0]
|
||||
|
||||
b tinygo_swapTask
|
||||
|
||||
.global tinygo_switchToTask
|
||||
.type tinygo_switchToTask, %function
|
||||
tinygo_switchToTask:
|
||||
// r0 = newTask *task
|
||||
// r0 = sp uintptr
|
||||
|
||||
// Currently on the scheduler stack (SP=MSP). We'll have to update the PSP,
|
||||
// and then we can invoke swapTask.
|
||||
ldr r0, [r0, #36]
|
||||
msr PSP, r0
|
||||
|
||||
// Continue executing in the swapTask function, which swaps the stack
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// +build scheduler.none
|
||||
|
||||
package runtime
|
||||
|
||||
//go:linkname sleep time.Sleep
|
||||
func sleep(duration int64) {
|
||||
sleepTicks(timeUnit(duration / tickMicros))
|
||||
}
|
||||
|
||||
// getSystemStackPointer returns the current stack pointer of the system stack.
|
||||
// This is always the current stack pointer.
|
||||
func getSystemStackPointer() uintptr {
|
||||
return getCurrentStackPointer()
|
||||
}
|
||||
@@ -2,110 +2,6 @@
|
||||
|
||||
package runtime
|
||||
|
||||
import "unsafe"
|
||||
|
||||
const stackSize = 1024
|
||||
|
||||
// Stack canary, to detect a stack overflow. The number is a random number
|
||||
// generated by random.org. The bit fiddling dance is necessary because
|
||||
// otherwise Go wouldn't allow the cast to a smaller integer size.
|
||||
const stackCanary = uintptr(uint64(0x670c1333b83bf575) & uint64(^uintptr(0)))
|
||||
|
||||
var (
|
||||
currentTask *task // currently running goroutine, or nil
|
||||
)
|
||||
|
||||
// This type points to the bottom of the goroutine stack and contains some state
|
||||
// that must be kept with the task. The last field is a canary, which is
|
||||
// necessary to make sure that no stack overflow occured when switching tasks.
|
||||
type task struct {
|
||||
// The order of fields in this structs must be kept in sync with assembly!
|
||||
calleeSavedRegs
|
||||
pc uintptr
|
||||
sp uintptr
|
||||
taskState
|
||||
canaryPtr *uintptr // used to detect stack overflows
|
||||
}
|
||||
|
||||
// getCoroutine returns the currently executing goroutine. It is used as an
|
||||
// intrinsic when compiling channel operations, but is not necessary with the
|
||||
// task-based scheduler.
|
||||
//go:inline
|
||||
func getCoroutine() *task {
|
||||
return currentTask
|
||||
}
|
||||
|
||||
// state is a small helper that returns the task state, and is provided for
|
||||
// compatibility with the coroutine implementation.
|
||||
//go:inline
|
||||
func (t *task) state() *taskState {
|
||||
return &t.taskState
|
||||
}
|
||||
|
||||
// resume is a small helper that resumes this task until this task switches back
|
||||
// to the scheduler.
|
||||
func (t *task) resume() {
|
||||
currentTask = t
|
||||
switchToTask(t)
|
||||
currentTask = nil
|
||||
}
|
||||
|
||||
// switchToScheduler saves the current state on the stack, saves the current
|
||||
// stack pointer in the task, and switches to the scheduler. It must only be
|
||||
// called when actually running on this task.
|
||||
// When it returns, the scheduler has switched back to this task (for example,
|
||||
// after a blocking operation completed).
|
||||
//export tinygo_switchToScheduler
|
||||
func switchToScheduler(t *task)
|
||||
|
||||
// switchToTask switches from the scheduler to the task. It must only be called
|
||||
// from the scheduler.
|
||||
// When this function returns, the task just yielded control back to the
|
||||
// scheduler.
|
||||
//export tinygo_switchToTask
|
||||
func switchToTask(t *task)
|
||||
|
||||
// startTask is a small wrapper function that sets up the first (and only)
|
||||
// argument to the new goroutine and makes sure it is exited when the goroutine
|
||||
// finishes.
|
||||
//go:extern tinygo_startTask
|
||||
var startTask [0]uint8
|
||||
|
||||
// startGoroutine starts a new goroutine with the given function pointer and
|
||||
// argument. It creates a new goroutine stack, prepares it for execution, and
|
||||
// adds it to the runqueue.
|
||||
func startGoroutine(fn, args uintptr) {
|
||||
stack := alloc(stackSize)
|
||||
t := (*task)(unsafe.Pointer(uintptr(stack) + stackSize - unsafe.Sizeof(task{})))
|
||||
|
||||
// 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.
|
||||
t.canaryPtr = (*uintptr)(unsafe.Pointer(stack))
|
||||
*t.canaryPtr = stackCanary
|
||||
|
||||
// Store the initial sp/pc for the startTask function (implemented in
|
||||
// assembly).
|
||||
t.sp = uintptr(stack) + stackSize - unsafe.Sizeof(task{})
|
||||
t.pc = uintptr(unsafe.Pointer(&startTask))
|
||||
t.prepareStartTask(fn, args)
|
||||
scheduleLogTask(" start goroutine:", t)
|
||||
runqueuePushBack(t)
|
||||
}
|
||||
|
||||
// yield suspends execution of the current goroutine
|
||||
// any wakeups must be configured before calling yield
|
||||
//export runtime.yield
|
||||
func yield() {
|
||||
// Check whether the canary (the lowest address of the stack) is still
|
||||
// valid. If it is not, a stack overflow has occured.
|
||||
if *currentTask.canaryPtr != stackCanary {
|
||||
runtimePanic("goroutine stack overflow")
|
||||
}
|
||||
switchToScheduler(currentTask)
|
||||
}
|
||||
|
||||
// getSystemStackPointer returns the current stack pointer of the system stack.
|
||||
// This is not necessarily the same as the current stack pointer.
|
||||
//export tinygo_getSystemStackPointer
|
||||
|
||||
Reference in New Issue
Block a user