PDA

View Full Version : Coroutines in Spin



Phil Pilgrim (PhiPi)
11-05-2010, 10:22 PM
Here's a trick you can use to implement cooperative multitasking in Spin. In the following program, coroutine1 and coroutine2 take turns, each handing off to the other via a call to switch. The statement, swap_addr := word[@result - 2] in coroutine2 is what sets up the initial return address that gets swapped when switch is called. From there, switch does the rest.



CON

_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000

VAR

word swap_addr

OBJ

sio : "FullDuplexSerial"

PUB coroutine1

sio.start(31, 30, 0, 9600)
coroutine2
repeat
sio.tx("1")
switch

PUB coroutine2

swap_addr := word[@result - 2]
repeat
sio.tx("2")
switch
sio.tx("3")
switch

PUB switch : new

new := word[@new - 2]
word[@new - 2] := swap_addr
swap_addr := new

The output from the above program is 213121312131...

-Phil

David Betz
11-05-2010, 10:25 PM
That's really cool! I guess this requires a knowledge of how function call frames are constructed. How did you discover that? Is there an official document describing the Spin VM and function calling conventions?

Phil Pilgrim (PhiPi)
11-05-2010, 11:15 PM
AFAIK, there's no "official" document that covers Spin stack frames, although writers of tools like BST must know them in detail. In my case, I just probed around the result address until I found the return address.

-Phil

Heater.
11-06-2010, 07:55 AM
Phil,

That's brilliant.

Not quite as evil a lonesocks "let's us the undocumented stack layout" interface to his float 32 object but almost.

What you have here is a way to manipulate the Spin interpreters program counter. So why stop at coroutines? One of those coroutines could be a multi-tasking executive. Threads in Spin any one?

lonesock
11-06-2010, 08:55 AM
You know my motto: "Executing evil at 20 MIPS per cog" [8^)

Phil, awesome stuff!

Jonathan

RossH
11-06-2010, 11:56 AM
Very neat. Time to dig out my book on Modula-2!

Loopy Byteloose
11-06-2010, 01:35 PM
Can this be done on separate cogs as well?

Bill Henning
11-06-2010, 04:01 PM
Great work Phil!

I have not had enough coffee yet, but it occurs to me that it should be possible to extend this to more generic threads...

I have not tested the code below, and it is probably buggy as heck, but it should illustrate what I am thinking of...



const
MAX_THREADS = 10

var
word thread[MAX_THREADS],threads,next

pub coroutine1
newthread(1)
repeat
'do something
switch

pub coroutine2
newthread(2)
repeat
'do something
switch

pub coroutine3
newthread(3)
repeat
'do something
switch

pub newthread(me):new
thread[me-1]:=word[@new - 2]
threads++

pub killthread(which)
thread[which]:=0

pub switch:nxt
next++
if next>threads
next:=0
if thread[next]
word[@nxt-2]:=thread[next]




Here's a trick you can use to implement cooperative multitasking in Spin. In the following program, coroutine1 and coroutine2 take turns, each handing off to the other via a call to switch. The statement, swap_addr := word[@result - 2] in coroutine2 is what sets up the initial return address that gets swapped when switch is called. From there, switch does the rest.



CON

_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000

VAR

word swap_addr

OBJ

sio : "FullDuplexSerial"

PUB coroutine1

sio.start(31, 30, 0, 9600)
coroutine2
repeat
sio.tx("1")
switch

PUB coroutine2

swap_addr := word[@result - 2]
repeat
sio.tx("2")
switch
sio.tx("3")
switch

PUB switch : new

new := word[@new - 2]
word[@new - 2] := swap_addr
swap_addr := new

The output from the above program is 213121312131...

-Phil

BradC
11-06-2010, 04:18 PM
AFAIK, there's no "official" document that covers Spin stack frames, although writers of tools like BST must know them in detail.

As with all quirks of the language, the interpreter source is the definitive documentation.

lardom
11-06-2010, 05:56 PM
DUH... What is meant by cooperative multitasking? How would you normally accomplish what Phil has done?

Heater.
11-06-2010, 06:36 PM
lardom,


What is meant by cooperative multitasking?
If you are trying to run two or more threads you need a way to change at least the Program Counter so that you can transfer control from one thread to another. You also probably want to be able to change the Stack Pointer from one threads stack to another. Depending on your processor you will need to also switch a bunch of other registers. All this is a "task switch".

That task switching may be triggered by an interrupt, from a timer say, in which case you have "pre-emptive" task switching. A task switch can happen at any time and the thread has to control over that.

Alternatively task switching might only happen when a thread makes a call into the operating system. Say when it wants to read from a serial port. In that call the OS might need decide to switch to a new task as the old one has to wait for data. In this case we have "cooperative task switching" as the threads have to cooperate by being sure to call some OS function that can reschedule. One thread , in a long running loop can stall all the other threads.

Often there is a special OS call that does nothing but suspend the current task and look for another one that is ready to run.

Phil Pilgrim (PhiPi)
11-06-2010, 07:01 PM
Heater,

You're right, of course, about switching the stack pointer too. Since this was not implemented by my technique, one has to be careful about where in the program the switch is performed, i.e. not where local variables are used or in a subroutine level below where the multitasking was set up.

Ideally, each task would have its own stack so that only the stack pointer would get altered and not the stack's contents, as I have done.

-Phil

Heater.
11-06-2010, 07:13 PM
Hmm..So anyone stumbled across any evil tricks that allow us to modify the stack pointer at will?

Humanoido
11-07-2010, 03:14 AM
Very good work Phil. I like programs that show switching the tasks on and off at will, sharing pieces of tasks, and the concept of developing coprocessors within the machine.

Phil Pilgrim (PhiPi)
11-07-2010, 03:31 AM
Hmm..So anyone stumbled across any evil tricks that allow us to modify the stack pointer at will?
Looking at the interpreter source, that appears not to be possible. :(

-Phil

BradC
11-07-2010, 04:23 AM
Looking at the interpreter source, that appears not to be possible.

Not directly, but given the work developed in inserting PASM into a running interpreter cog, you could implement a "task switch" bytecode at the expense of some other lightly/unused routine.

Phil Pilgrim (PhiPi)
11-07-2010, 05:31 AM
Brad,

I'm sure the interpreter could be modified to accommodate stack switching. For now, though, I'm content to let it go as is with some common-sense caveats regarding its use.

Oddly enough, further testing shows that subroutine level seems not to be an issue, except for preserving local variables across a switch. The same applies to other nested structures like ifs and repeats, where I would have expected it to fail.

The fact of the matter is that I took a very naive approach to the while thing, and probably would not have bothered if I had thought about it more. Yet it seems to work; and I'm no longer sure why. :)

-Phil