Shop OBEX P1 Docs P2 Docs Learn Events
PNut/Spin2 Latest Version (v48.1 - preprocessor and flash-image saving) - Page 66 — Parallax Forums

PNut/Spin2 Latest Version (v48.1 - preprocessor and flash-image saving)

1636466686972

Comments

  • evanhevanh Posts: 16,075
    edited 2024-12-04 14:43

    @cgracey said:
    Question for you all:

    I am adding round-robin cooperative multitasking to the Spin2 interpreter. This way, a single Spin2 cog can run many Spin2 programs by splitting its time among them.

    Should I implement a duty value for each task, so rather than always executing it in the round-robin loop, it can be set to execute (1..64)/64ths of the time when its turn comes up?

    I'd want to be able to wait on a hardware event that then gives priority at task switch.

    EDIT: And as an extension - Waiting on any global changes. This wouldn't have any priority though as it would be using the task's own time to repeatedly resolve the equation being waited on.

  • TonyB_TonyB_ Posts: 2,196
    edited 2024-12-04 15:12

    I don't use Spin2 so my comments probably aren't worth a lot. Could a critical task "call" the next task instead of doing a "jump" to it? Next task returns to critical task, which then a bit later calls a new next task? Also, is there any way of making task switching automatic?

  • evanhevanh Posts: 16,075
    edited 2024-12-04 15:22

    @TonyB_ said:
    I don't use Spin2 so my comments probably aren't worth a lot. Could a critical task "call" the next task instead of doing a "jump" to it? Next task returns to critical task, which then a bit later calls a new next task?

    That would defeat the purpose. Each task is meant to follow its own autonomous sequence (state machine). Just call a library/object otherwise.

    Also, is there any way of making task switching automatic?

    That's often done as part of a support library. Not so much automatic but rather just inserted as a wrapper around the library.

    The alternative would be to implement full pre-emptive multitasking.

  • Having each task decide when to pass the reins on is very useful, rather than being a nuisance.

  • RaymanRayman Posts: 14,789

    Wonder how inline assembly would be handled... Seems like two threads trying to do large cog code at the same time could conflict...

  • @evanh said:

    That would defeat the purpose. Each task is meant to follow its own autonomous sequence (state machine). Just call a library/object otherwise.

    Each task (a) chooses when to sleep, (b) cannot tell whether it was woken up by a "call" or a "jump" and (c) does not know or care which task it wakes up. If only one task makes a "call" then it could act as task manager. N.B. "call" / "jump" is not an instruction call / jump.

  • evanhevanh Posts: 16,075
    edited 2024-12-04 20:22

    @TonyB_ said:
    Each task (a) chooses when to sleep, (b) cannot tell whether it was woken up by a "call" or a "jump" and (c) does not know or care which task it wakes up. If only one task makes a "call" then it could act as task manager. N.B. "call" / "jump" is not an instruction call / jump.

    As I understand it, these new task methods are the mechanism of task management.

    You're probably going to have to do a write-up to explain your idea and how it won't create a lot of overheads when implemented as user Spin.

  • roglohrogloh Posts: 5,852
    edited 2024-12-04 22:22

    @evanh said:

    That's often done as part of a support library. Not so much automatic but rather just inserted as a wrapper around the library.

    The alternative would be to implement full pre-emptive multitasking.

    If an ISR was setup to run periodically you might be able to get it to somehow call TASKNEXT from there....? May need to handle some ISR/stack cleanup between invocations. Wasn't SPIN2 written to allow interrupts to still occur most (all?) of the time...?

    Co-operative multitasking is handy. It'd be neat if there was some cleanish way to eventually morph this capability into something that could be somewhat pre-emptive in nature... or have some future plan for doing that too down the track rather than throwing it all out and starting over.

  • cgraceycgracey Posts: 14,232
    edited 2024-12-04 22:30

    @rogloh said:
    @evanh said:

    If an ISR was setup to run periodically you might be able to get it to somehow call TASKNEXT from there....? May need to handle some ISR/stack cleanup between invocations. Wasn't SPIN2 written to allow interrupts to still occur most (all?) of the time...?

    Asynchronously interrupting the interpreter to switch interpreter contexts would be problematic because you would interrupt at times when many registers are holding vital state information as a bytecode executes, whereas a TASKNEXT() instruction (or any other instruction) only executes at junctures where the interpreter has its entire state confined to only 8 contiguous registers.

    So, you can do PASM interrupts to run PASM code, but not affect Spin2 execution, because you don't know what is going on with the interpreter.

    What I should have done, but didn't realize at the time, was to make an interrupt that would trigger on XBYTE. Then, we could have cleanly interrupted the interpreter.

  • cgraceycgracey Posts: 14,232

    @Rayman said:
    Wonder how inline assembly would be handled... Seems like two threads trying to do large cog code at the same time could conflict...

    Inline assembly is like a Spin2 instruction, just like TASKNEXT() is a Spin2 instruction, so it is atomic and won't be interrupted.

    Every task needs to get a piece of its work done and then do a TASKNEXT() to move things along, returning to the next instruction later, when the prior task executes its TASKNEXT() instruction.

  • cgraceycgracey Posts: 14,232

    @bob_g4bby said:
    Having each task decide when to pass the reins on is very useful, rather than being a nuisance.

    Yeah, you won't suffer interruptions of code sequences which need to finish before task switching. When it's okay for a break, each task executes a TASNKEXT() instruction.

  • @cgracey said:
    What I should have done, but didn't realize at the time, was to make an interrupt that would trigger on XBYTE. Then, we could have cleanly interrupted the interpreter.

    Yes, that. Would help with the CPU emulators as well...

    Did you read my earlier post about this? May have gotten lost in the page break.

  • cgraceycgracey Posts: 14,232
    edited 2024-12-04 22:42

    @evanh said:

    That would defeat the purpose. Each task is meant to follow its own autonomous sequence (state machine). Just call a library/object otherwise.

    That's often done as part of a support library. Not so much automatic but rather just inserted as a wrapper around the library.

    The alternative would be to implement full pre-emptive multitasking.

    The closest we could get to automatic task switching would be to add some interpreter code to, say, the branch instructions, which execute frequently, and have that extra interperter code do the switch. Branches come up ALL the time, I think, so they could be clean points for task switching.

    The TASKNEXT() instruction, incidentally, compiles to a single byte, so those can be sprinkled throughout your code without much cost. And YOU get to decide where they happen.

  • cgraceycgracey Posts: 14,232
    edited 2024-12-05 00:32

    @evanh said:

    I'd want to be able to wait on a hardware event that then gives priority at task switch.

    EDIT: And as an extension - Waiting on any global changes. This wouldn't have any priority though as it would be using the task's own time to repeatedly resolve the equation being waited on.

    You could have hardware interrupts run PASM code which would set a flag for the interpreter to see when executing the branch instructions. That would be one way of making event-driven task switching.

  • cgraceycgracey Posts: 14,232

    @evanh said:

    Ah, what's the plan for PropTool then? Is it officially depreciated?

    Jeff has been busy handling our IT, but he can take time at some point to integrate the latest compiler into PropTool.

  • cgraceycgracey Posts: 14,232
    edited 2024-12-04 22:55

    @bob_g4bby said:
    With so much processing power provided by each cog, this would be a very useful, tidy way of using a SPIN2 cog to it's full extent. I have written multitasked flashforth applications in the past and found the modularity very readable, especially when coming back to it years later. I would favour unlimited tasks if there's a choice.

    I would favour a handle, so that a task may be stopped by external code, not just itself.
    When a task is terminated, it would be nice to allow the task to tidy up before shutting down, rather than a 'crash' termination. Might need another keyword for that, though?
    How would the user determine the required stack depth for each task?
    How about setting the task repeat time in mS?

    I could return a handle to each new task from TASKINIT, no problem. That could be used to TASKSTOP that task from elsewhere. If you want a smooth shutdown, that could be handled through a VARiable. I could also put some readable/writable status bits into each task that the task could use for any purpose. Anyone with the handle could r/w them, too.

    Yeah, having state be a matter of execution point is a lot easier to understand than a bunch of state data which must be swept through repeatedly and handled with a bunch of IFs and CASEs. It's like moving the expression of intent to a more brain-friendly domain.

  • RaymanRayman Posts: 14,789

    Wondering how this would be implemented at top level...

    Wouldn't you want to be able to test with same code with both CogSpin() and TaskInit() to make sure it works the same way as expected?

    Maybe TaskSpin is better name?

  • cgraceycgracey Posts: 14,232

    @Wuerfel_21 said:

    Yes, that. Would help with the CPU emulators as well...

    Did you read my earlier post about this? May have gotten lost in the page break.

    You mean about having an unlimited linked list of tasks? Yes, that is a good idea. I've been trying to visualize how it would work, in practice. It would make stopping an arbitrary task more difficult, because the list would have to be traversed.

    Having a central repository of pointers in a VAR array of longs would make things faster, but require slight initial setup, like setting a register to the address of the list.

  • cgraceycgracey Posts: 14,232

    @Rayman said:
    Wondering how this would be implemented at top level...

    Wouldn't you want to be able to test with same code with both CogSpin() and TaskInit() to make sure it works the same way as expected?

    Maybe TaskSpin is better name?

    TASKSPIN, yes!!! COGSPIN and TASKINIT are incongruous, but that would solve the naming problem.

    TASKSPIN and COGSPIN could have the same sets of arguments, too.

  • evanhevanh Posts: 16,075
    edited 2024-12-04 23:18

    @cgracey said:
    Jeff has been busy handling our IT, but he can take time at some point to integrate the latest compiler into PropTool.

    If it doesn't get the same C-like preprocessor that everyone else is implementing then it's a dead duck. If you're going to support a preprocessor then it has to be official for compatibility.
    PS: And Pnut will quickly lose its value too if everyone else is using a supported feature it doesn't have.

  • cgraceycgracey Posts: 14,232
    edited 2024-12-05 09:44

    @evanh said:

    If it doesn't get the same C-like preprocessor that everyone else is implementing then it's a dead duck. If you're going to support a preprocessor then it has to be official for compatibility.
    PS: And Pnut will quickly lose its value too if everyone else is using a supported feature it doesn't have.

    I think I've just realized how this can be done in a very simple way. I can work from a buffered copy of the source code and go through it, handling all of the preprocessing commands. For al those sections which need to be removed, including the preprocessor commands, I can just fill them with spaces. Then, I can just run the compiler as normal and it never even sees all the removed code and preprocessor commands, while maintaining proper source-code offsets for error reporting.

    Maintaining proper offsets for the source-code errors was what always haunted me about preprocessing.

  • roglohrogloh Posts: 5,852
    edited 2024-12-05 02:52

    Yes that is what you will often see if you examine the output of pre-processors, lots of empty lines where some conditional code has been removed.

    Doing just this space replacement will be useful for that aspect, although a pre-processor also typically has other features like macro replacement and include files. Macro substitution would be difficult to do and probably less necessary given the existence of SPIN2 constants, but an #include file feature would be very handy in some situations when coupled with conditional compilation to manage groups of library methods brought into the build and avoids much larger files that have to contain the superset of everything inside them.

    #ifdef PSRAM
    #include psramfuncs.spin2
    #endif
    
    #ifdef HYPERRAM 
    #include hyperramfuncs.spin2
    #endif
    

    The other way to do this is to build a large file with a superset of both PSRAM and HYPERRAM and other types inside and wrap ifdefs around each related block. Can become unwieldy to manage once the source file gets very large.

    The #ifdef around DAT blocks will be useful too when you need different data included - e.g. different selected fonts etc for graphics. Again the #include style feature is helpful when resources are split over multiple files.
    EDIT: actually fonts are not a good example as you can probably use the file option with binary data, but I'm thinking more when the data is not just a binary blob, but has other structured formats involved.

  • @evanh said:

    If it doesn't get the same C-like preprocessor that everyone else is implementing then it's a dead duck. If you're going to support a preprocessor then it has to be official for compatibility.
    PS: And Pnut will quickly lose its value too if everyone else is using a supported feature it doesn't have.

    Regrettably, mine and @macca's preprocessors are different -- mine is strictly a text preprocessor (like the C one) that runs as a first pass before anything else. Marco's is more integrated into the compiler so it interacts with things like CON declarations. I'm not sure which approach PNut-TS takes. Openspin uses the same approach (and indeed basically the same code) as flexspin.

    I hope there's a common subset that can work in all, but we need to figure that out.

  • bob_g4bbybob_g4bby Posts: 440
    edited 2024-12-05 14:41

    Trying out debug gating I don't get the results expected - maybe I haven't got the syntax right:-

    {Spin2_v46}
    
    CON _clkfreq = 10000000
    
        DEBUG_MASK = 2
    
    DAT ORG
    
        MOV i,#9
    loop    NOP
        DEBUG [1] (UHEX_LONG(i))
        DEBUG [2] (UDEC_LONG(i))
        DJNF i,#loop
        JMP #$
    
    i   RES 1
    

    With DEBUG_MASK = 1, no readout of i is displayed
    With DEBUG_MASK = 2, I get 10 readouts of i expressed in hex, when I expect decimal
    With DEBUG_MASK = 3, I get 10 readouts of i expressed in hex, but no decimal ones

  • cgraceycgracey Posts: 14,232

    @bob_g4bby said:
    Trying out debug gating I don't get the results expected - maybe I haven't got the syntax right:-

    {Spin2_v46}
    
    CON   _clkfreq = 10000000
    
      DEBUG_MASK = 2
    
    DAT   ORG
    
      MOV i,#9
    loop  NOP
      DEBUG [1] (UHEX_LONG(i))
      DEBUG [2] (UDEC_LONG(i))
      DJNF i,#loop
      JMP #$
    
    i RES 1
    

    With DEBUG_MASK = 1, no readout of i is displayed
    With DEBUG_MASK = 2, I get 10 readouts of i expressed in hex, when I expect decimal
    With DEBUG_MASK = 3, I get 10 readouts of i expressed in hex, but no decimal ones

    You've got DEBUG_MASK set to 2 (%00000000_00000000_00000000_00000010), so only bit 1 is set. That means only DEBUG[1] is going to compile.

  • cgraceycgracey Posts: 14,232
    edited 2024-12-05 15:36

    @ersmith said:

    Regrettably, mine and @macca's preprocessors are different -- mine is strictly a text preprocessor (like the C one) that runs as a first pass before anything else. Marco's is more integrated into the compiler so it interacts with things like CON declarations. I'm not sure which approach PNut-TS takes. Openspin uses the same approach (and indeed basically the same code) as flexspin.

    I hope there's a common subset that can work in all, but we need to figure that out.

    Jeff and I were talking about this last night and thought it would best if the preprocessor variables came soley from above the object file, in the form of parameters. We were thinking that existing OBJ parameters could be seen by the preprocessor, and that would be it.

    someobject : "objectfile" | X = 1, Y = 2, Z = 3

    So, the preprocessor would only see X, Y, and Z. Someobject's internal CON blocks would also see X, Y, and Z, as is already the case. I think this would keep things clean, instead of chicken-and-egg conundrums with CON block declarations interacting with the preprocessor.

  • @cgracey said:
    Jeff and I were talking about this last night and thought it would best if the preprocessor variables came soley from above the object file, in the form of parameters. We were thinking that existing OBJ parameters could be seen by the preprocessor, and that would be it.

    someobject : "objectfile" | X = 1, Y = 2, Z = 3

    So, the preprocessor would only see X, Y, and Z. Someobject's internal CON blocks would also see X, Y, and Z, as is already the case. I think this would keep things clean, instead of chicken-and-egg conundrums with CON block declarations interacting with the preprocessor.

    My 50 cents (excl. tax):
    - every file should see definitions given on the command line
    - I don't think conflating CON symbols and defines is a good idea, so I'd use a different syntax for the latter. Maybe #X = 1.
    - flexspin has a #pragma exportdef X directive that makes a definition from the current file global for all files processed afterwards. This is a good idea, since that allows setting global defines without a command-line argument.

  • @cgracey said:
    Question for you all:
    I am adding round-robin cooperative multitasking to the Spin2 interpreter. This way, a single Spin2 cog can run many Spin2 programs by splitting its time among them.
    Should I implement a duty value for each task, so rather than always executing it in the round-robin loop, it can be set to execute (1..64)/64ths of the time when its turn comes up?
    And does anyone have any opinion on how many tasks should be supported? I've got 16 working now, but I could make it unlimited. Do you see any value in that?

    I think this could be useful. But I see no reason why this has to go into the interpreter or compiler. It could be implemented as a simple library/object and TASKINIT/NEXT/STOP can be simple function you can call. Of course, to switch the context it is necessary to know some secrets of how the interpreter or comiler internally handles the stack. So the implementations for PNut and FlexProp have to be different.

    And if its a simple object then there is no need to decide if the number of tasks has to be limited or what the resolution of the time slice or priority is. If somebody is not happy with the implementation he/she can make his/her own.

  • Does cooperative mean that there is no interrupt based switch as opposed to pre-emptive multitasking? If that's true then there is no need to make anything atomic. Task switches happen only when TASKNEXT is called, or am I missing something?

  • cgraceycgracey Posts: 14,232
    edited 2024-12-05 17:32

    @ManAtWork said:
    Does cooperative mean that there is no interrupt based switch as opposed to pre-emptive multitasking? If that's true then there is no need to make anything atomic. Task switches happen only when TASKNEXT is called, or am I missing something?

    That's correct. Between instructions, the entire interpreter state is in 8 contiguous registers. These can be pushed onto the stack and later popped to restore state. This is the basis of the task switching. I think it's cleanest to put it into the interpreter, because it's quite compact and fast that way.

Sign In or Register to comment.