Coroutines in Spin
Phil Pilgrim (PhiPi)
Posts: 23,514
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.
-Phil
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 := newThe output from the above program is 213121312131...
-Phil
Comments
-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?
Phil, awesome stuff!
Jonathan
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...
As with all quirks of the language, the interpreter source is the definitive documentation.
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.
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
-Phil
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.
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