Shop OBEX P1 Docs P2 Docs Learn Events
Coroutines in Spin — Parallax Forums

Coroutines in Spin

Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
edited 2010-11-06 22:31 in Propeller 1
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

Comments

  • David BetzDavid Betz Posts: 14,516
    edited 2010-11-05 14:25
    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)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2010-11-05 15:15
    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.Heater. Posts: 21,230
    edited 2010-11-05 23:55
    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?
  • lonesocklonesock Posts: 917
    edited 2010-11-06 00:55
    You know my motto: "Executing evil at 20 MIPS per cog" [8^)

    Phil, awesome stuff!

    Jonathan
  • RossHRossH Posts: 5,519
    edited 2010-11-06 03:56
    Very neat. Time to dig out my book on Modula-2!
  • LoopyBytelooseLoopyByteloose Posts: 12,537
    edited 2010-11-06 05:35
    Can this be done on separate cogs as well?
  • Bill HenningBill Henning Posts: 6,445
    edited 2010-11-06 08:01
    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
  • BradCBradC Posts: 2,601
    edited 2010-11-06 08:18
    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.
  • lardomlardom Posts: 1,659
    edited 2010-11-06 09:56
    DUH... What is meant by cooperative multitasking? How would you normally accomplish what Phil has done?
  • Heater.Heater. Posts: 21,230
    edited 2010-11-06 10:36
    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)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2010-11-06 11:01
    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.Heater. Posts: 21,230
    edited 2010-11-06 11:13
    Hmm..So anyone stumbled across any evil tricks that allow us to modify the stack pointer at will?
  • HumanoidoHumanoido Posts: 5,770
    edited 2010-11-06 20:14
    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)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2010-11-06 20:31
    Heater. wrote: »
    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
  • BradCBradC Posts: 2,601
    edited 2010-11-06 21:23
    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)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2010-11-06 22:31
    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
Sign In or Register to comment.